From 1110e20e96604b0b872e86e401bb8ac710b78392 Mon Sep 17 00:00:00 2001 From: Amazon GitHub Automation <54958958+amazon-auto@users.noreply.github.com> Date: Wed, 13 Nov 2019 17:15:46 -0800 Subject: [PATCH 0001/1165] Initial commit --- CODE_OF_CONDUCT.md | 4 ++ CONTRIBUTING.md | 61 ++++++++++++++++ LICENSE | 175 +++++++++++++++++++++++++++++++++++++++++++++ NOTICE | 1 + README.md | 13 ++++ 5 files changed, 254 insertions(+) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 NOTICE create mode 100644 README.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..5b627cfa --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,4 @@ +## Code of Conduct +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). +For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact +opensource-codeofconduct@amazon.com with any additional questions or comments. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..914e0741 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,61 @@ +# Contributing Guidelines + +Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional +documentation, we greatly value feedback and contributions from our community. + +Please read through this document before submitting any issues or pull requests to ensure we have all the necessary +information to effectively respond to your bug report or contribution. + + +## Reporting Bugs/Feature Requests + +We welcome you to use the GitHub issue tracker to report bugs or suggest features. + +When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already +reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: + +* A reproducible test case or series of steps +* The version of our code being used +* Any modifications you've made relevant to the bug +* Anything unusual about your environment or deployment + + +## Contributing via Pull Requests +Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: + +1. You are working against the latest source on the *master* branch. +2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. +3. You open an issue to discuss any significant work - we would hate for your time to be wasted. + +To send us a pull request, please: + +1. Fork the repository. +2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. +3. Ensure local tests pass. +4. Commit to your fork using clear commit messages. +5. Send us a pull request, answering any default questions in the pull request interface. +6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. + +GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and +[creating a pull request](https://help.github.com/articles/creating-a-pull-request/). + + +## Finding contributions to work on +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' issues is a great place to start. + + +## Code of Conduct +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). +For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact +opensource-codeofconduct@amazon.com with any additional questions or comments. + + +## Security issue notifications +If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. + + +## Licensing + +See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. + +We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..67db8588 --- /dev/null +++ b/LICENSE @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000..b8d0d46a --- /dev/null +++ b/NOTICE @@ -0,0 +1 @@ +Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/README.md b/README.md new file mode 100644 index 00000000..8f46b7c6 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +## My Project + +TODO: Fill this README out! + +Be sure to: + +* Change the title in this README +* Edit your repository description on GitHub + +## License + +This project is licensed under the Apache-2.0 License. + From 89f29154c46170a7ab045f71320902a0f7bf8041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Derek=20Bolt=F0=9F=90=BE?= Date: Fri, 13 Dec 2019 14:15:13 -0800 Subject: [PATCH 0002/1165] Copy over initial braket-python-qdk implementation --- .coveragerc | 21 + .gitignore | 26 + .pre-commit-config.yaml | 18 + README.md | 145 +- bin/apply-header.py | 57 + doc/Makefile | 20 + doc/conf.py | 30 + doc/index.rst | 16 + pyproject.toml | 2 + setup.cfg | 27 + setup.py | 46 + src/braket/aws/__init__.py | 20 + src/braket/aws/aws_qpu.py | 146 ++ src/braket/aws/aws_qpu_arns.py | 19 + src/braket/aws/aws_quantum_simulator.py | 96 ++ src/braket/aws/aws_quantum_simulator_arns.py | 20 + src/braket/aws/aws_quantum_task.py | 237 ++++ src/braket/aws/aws_quantum_task_result.py | 99 ++ src/braket/aws/aws_session.py | 143 ++ src/braket/circuits/__init__.py | 27 + src/braket/circuits/angled_gate.py | 53 + src/braket/circuits/ascii_circuit_diagram.py | 115 ++ src/braket/circuits/circuit.py | 392 ++++++ src/braket/circuits/circuit_diagram.py | 32 + src/braket/circuits/gate.py | 136 ++ src/braket/circuits/gates.py | 1182 +++++++++++++++++ src/braket/circuits/instruction.py | 127 ++ src/braket/circuits/moments.py | 213 +++ src/braket/circuits/operator.py | 33 + src/braket/circuits/qubit.py | 60 + src/braket/circuits/qubit_set.py | 89 ++ src/braket/devices/__init__.py | 15 + src/braket/devices/device.py | 83 ++ src/braket/devices/device_details.py | 28 + src/braket/ipython_utils.py | 39 + src/braket/tasks/__init__.py | 26 + src/braket/tasks/quantum_task.py | 45 + src/braket/tasks/quantum_task_result.py | 122 ++ test/integ_tests/conftest.py | 72 + test/integ_tests/test_aws_session_s3.py | 51 + test/integ_tests/test_common.py | 2 + test/integ_tests/test_device_creation.py | 44 + .../test_simulator_quantum_task.py | 62 + .../braket/aws/common_test_utils.py | 112 ++ test/unit_tests/braket/aws/test_aws_qpu.py | 213 +++ .../braket/aws/test_aws_quantum_simulator.py | 131 ++ .../braket/aws/test_aws_quantum_task.py | 238 ++++ .../aws/test_aws_quantum_task_result.py | 112 ++ .../unit_tests/braket/aws/test_aws_session.py | 175 +++ .../braket/circuits/test_angled_gate.py | 45 + .../circuits/test_ascii_circuit_diagram.py | 137 ++ .../braket/circuits/test_circuit.py | 329 +++++ test/unit_tests/braket/circuits/test_gate.py | 124 ++ test/unit_tests/braket/circuits/test_gates.py | 193 +++ .../braket/circuits/test_instruction.py | 129 ++ .../braket/circuits/test_moments.py | 170 +++ test/unit_tests/braket/circuits/test_qubit.py | 54 + .../braket/circuits/test_qubit_set.py | 64 + .../braket/tasks/test_quantum_task_result.py | 65 + .../braket/tasks/test_tasks_init.py | 32 + test/unit_tests/braket/test_ipython_utils.py | 53 + ...aket_default_s3_bucket.cloudformation.yaml | 12 + ...ket_required_resources.cloudformation.yaml | 78 ++ tools/create_braket_resources.py | 95 ++ tox.ini | 79 ++ 65 files changed, 6870 insertions(+), 6 deletions(-) create mode 100644 .coveragerc create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 bin/apply-header.py create mode 100644 doc/Makefile create mode 100644 doc/conf.py create mode 100644 doc/index.rst create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 src/braket/aws/__init__.py create mode 100644 src/braket/aws/aws_qpu.py create mode 100644 src/braket/aws/aws_qpu_arns.py create mode 100644 src/braket/aws/aws_quantum_simulator.py create mode 100644 src/braket/aws/aws_quantum_simulator_arns.py create mode 100644 src/braket/aws/aws_quantum_task.py create mode 100644 src/braket/aws/aws_quantum_task_result.py create mode 100644 src/braket/aws/aws_session.py create mode 100644 src/braket/circuits/__init__.py create mode 100644 src/braket/circuits/angled_gate.py create mode 100644 src/braket/circuits/ascii_circuit_diagram.py create mode 100644 src/braket/circuits/circuit.py create mode 100644 src/braket/circuits/circuit_diagram.py create mode 100644 src/braket/circuits/gate.py create mode 100644 src/braket/circuits/gates.py create mode 100644 src/braket/circuits/instruction.py create mode 100644 src/braket/circuits/moments.py create mode 100644 src/braket/circuits/operator.py create mode 100644 src/braket/circuits/qubit.py create mode 100644 src/braket/circuits/qubit_set.py create mode 100644 src/braket/devices/__init__.py create mode 100644 src/braket/devices/device.py create mode 100644 src/braket/devices/device_details.py create mode 100644 src/braket/ipython_utils.py create mode 100644 src/braket/tasks/__init__.py create mode 100644 src/braket/tasks/quantum_task.py create mode 100644 src/braket/tasks/quantum_task_result.py create mode 100644 test/integ_tests/conftest.py create mode 100644 test/integ_tests/test_aws_session_s3.py create mode 100644 test/integ_tests/test_common.py create mode 100644 test/integ_tests/test_device_creation.py create mode 100644 test/integ_tests/test_simulator_quantum_task.py create mode 100644 test/unit_tests/braket/aws/common_test_utils.py create mode 100644 test/unit_tests/braket/aws/test_aws_qpu.py create mode 100644 test/unit_tests/braket/aws/test_aws_quantum_simulator.py create mode 100644 test/unit_tests/braket/aws/test_aws_quantum_task.py create mode 100644 test/unit_tests/braket/aws/test_aws_quantum_task_result.py create mode 100644 test/unit_tests/braket/aws/test_aws_session.py create mode 100644 test/unit_tests/braket/circuits/test_angled_gate.py create mode 100644 test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py create mode 100644 test/unit_tests/braket/circuits/test_circuit.py create mode 100644 test/unit_tests/braket/circuits/test_gate.py create mode 100644 test/unit_tests/braket/circuits/test_gates.py create mode 100644 test/unit_tests/braket/circuits/test_instruction.py create mode 100644 test/unit_tests/braket/circuits/test_moments.py create mode 100644 test/unit_tests/braket/circuits/test_qubit.py create mode 100644 test/unit_tests/braket/circuits/test_qubit_set.py create mode 100644 test/unit_tests/braket/tasks/test_quantum_task_result.py create mode 100644 test/unit_tests/braket/tasks/test_tasks_init.py create mode 100644 test/unit_tests/braket/test_ipython_utils.py create mode 100644 tools/braket_default_s3_bucket.cloudformation.yaml create mode 100644 tools/braket_required_resources.cloudformation.yaml create mode 100644 tools/create_braket_resources.py create mode 100644 tox.ini diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..b2b0a874 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,21 @@ +[run] +parallel = True +branch = True +source = + braket +omit = + **/braket/ir/* + +[paths] +source = + src/braket + .tox/*/lib/python*/site-packages/braket + +[report] +show_missing = True + +[html] +directory = build/coverage + +[xml] +output = build/coverage/coverage.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..889115be --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +*~ +*# +*.swp +*.idea +*.DS_Store +build_files.tar.gz + +.ycm_extra_conf.py +.tox +.python-version + +__pycache__/ +*.py[cod] +*$py.class +*.egg-info/ +*.ipynb_checkpoints/ + +/.coverage +/.coverage.* +/.cache +/.pytest_cache +/.mypy_cache + +/doc/_apidoc/ +/build +/venv diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..d03e4d3d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +repos: +- repo: local + hooks: + - id: tox + name: tox + stages: [push] + language: system + entry: tox + types: [python] + pass_filenames: false + + - id: tox_integ_tests + name: tox integ tests + stages: [push] + language: system + entry: tox -e integ-tests + types: [python] + pass_filenames: false diff --git a/README.md b/README.md index 8f46b7c6..99d623be 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,146 @@ -## My Project +**DO NOT SHARE OR TALK ABOUT THE CONTENTS OF THIS LIBRARY per the Amazon Beta NDA you signed.** -TODO: Fill this README out! +Amazon Braket Python SDK is an open source library for interacting with quantum devices on Amazon Braket. -Be sure to: +## Installation -* Change the title in this README -* Edit your repository description on GitHub +### Prerequisites +- Python 3.7.2+ + +### Steps + +1. Get a confirmation email from Amazon Braket confirming you have access to the service. + +2. (Optional) Recommended to work inside a virtual environment. You can skip this step if you don't care about mucking with your global python dependencies. See [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) if you don't have it already installed. + +```bash +mkdir braket +cd braket + +virtualenv venv +source venv/bin/activate +``` + +2. Install [AWS CLI](https://github.com/aws/aws-cli#installation) + +```bash +pip install awscli +``` + +3. [Configure AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) settings to have a profile that can access your AWS account. Once a profile has been created set the `AWS_PROFILE` such that all future steps can connect to your AWS account. + +```bash +export AWS_PROFILE=YOUR_PROFILE_NAME +``` + +4. Install `braket-python-sdk` package. + +```bash +git clone https://github.com/aws/braket-python-sdk.git +pip install -e braket-python-sdk +``` + +To install test dependencies for running tests locally run: +```bash +pip install -e "braket-python-sdk[test]" +``` + +5. Install latest Amazon Braket model in AWS CLI. + +```bash +aws s3 cp s3://braket-external-assets-prod-us-west-2/models/aqx-2019-09-01.normal.json aqx-model.json +aws configure add-model --service-model "file://aqx-model.json" --service-name aqx +``` + +6. Create the necessary Amazon Braket resources in your AWS account. + +This will create the required resources and a default S3 bucket, `braket-output-${AWS::AccountId}`, for storing Amazon Braket outputs. If you don't want to create a bucket and will create your own than drop the `--create-default-bucket` from the command below. +```bash +python tools/create_braket_resources.py --create-default-bucket +``` + +7. You can now call Amazon Braket from the `braket-python-sdk`. + +```python +from braket.circuits import Circuit +from braket.aws import AwsQuantumSimulator + +device = AwsQuantumSimulator("arn:aws:aqx:::quantum-simulator:aqx:qs1") +s3_folder = ("braket-output-AWS_ACCOUNT_ID", "folder-name") + +bell = Circuit().h(0).cnot(0, 1) +print(device.run(bell, s3_folder).result().measurement_counts) +``` + +You should get output similar to... +``` +Counter({'11': 50, '00': 50}) +``` + +7. Install [Jupyter](https://jupyter.org/install) and create an braket kernel. +``` +pip install jupyter ipykernel +python -m ipykernel install --user --name braket +``` + +8. You can now launch Jupyter and use the SDK within it. +``` +jupyter notebook +``` + +## Sample Notebooks +TODO + +## Documentation + +First `cd` into the `doc` directory and run: +```bash +make html +``` + +Then open `BRAKET_SDK_ROOT/build/documentation/html/index.html` in a browser to view the docs. + +## Testing + +Make sure to install test dependencies first: +``` +pip install -e "braket-python-sdk[test]" +``` + +### Unit Tests +``` +tox -e unit-tests +``` + +To run an individual test +```bash +tox -e unit-tests -- -k 'your_test' +``` + +To run linters and doc generators and unit tests +```bash +tox +``` + +### Integration Tests + +Set the `AWS_PROFILE` +```bash +export AWS_PROFILE=PROFILE_FROM_STEP_3 +``` + +Create an S3 bucket in the same account as the `AWS_PROFILE` with the following naming convention `braket-sdk-integ-tests-{account_id}`. + +Run the tests +```bash +tox -e integ-tests +``` + +To run an individual test +```bash +tox -e integ-tests -- -k 'your_test' +``` ## License This project is licensed under the Apache-2.0 License. - diff --git a/bin/apply-header.py b/bin/apply-header.py new file mode 100644 index 00000000..7e7ffe31 --- /dev/null +++ b/bin/apply-header.py @@ -0,0 +1,57 @@ +import os + +""" +Run from the root of the git repository, will apply contents of header to the beginning of +all *.py files in the chosen directories. Script is idempotent, meaning it won't apply the +header to a file that already contains it. + +Usage: python bin/apply-header.py +""" + +HEADER = """# Copyright 2019-2019 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. + +""" + +ROOT_DIRS = ["src", "test", "."] + + +def main(): + for root_dir in ROOT_DIRS: + for root, dirs, files in os.walk(root_dir): + for py_file in python_files(files): + idempotent_prepend(os.path.join(root, py_file), HEADER) + + # don't recurse "." directory, just look at local files + if root_dir == ".": + break + + +def python_files(files): + return [file for file in files if file.endswith("py")] + + +def idempotent_prepend(filename: str, new_content: str) -> None: + with open(filename, "r+") as file: + existing_content = file.read() + + if existing_content.startswith(new_content): + print(f"Skipping {filename}, already contains the content.") + else: + print(f"Applying content to {filename}...") + file.seek(0, 0) + file.write(new_content + existing_content) + + +if __name__ == "__main__": + main() diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 00000000..9bc8a96e --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = python -msphinx +SPHINXPROJ = braket-sdk +SOURCEDIR = . +BUILDDIR = ../build/documentation + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 00000000..5281054a --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,30 @@ +"""Sphinx configuration.""" +import datetime + +import pkg_resources + +# Sphinx configuration below. +project = "braket-sdk" +version = pkg_resources.require(project)[0].version +release = version +copyright = "{}, Amazon.com".format(datetime.datetime.now().year) + +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", + "sphinx.ext.todo", + "sphinx.ext.coverage", +] + +source_suffix = ".rst" +master_doc = "index" + +autoclass_content = "both" +autodoc_member_order = "bysource" +default_role = "py:obj" + +html_theme = "sphinx_rtd_theme" +htmlhelp_basename = "{}doc".format(project) + +napoleon_use_rtype = False diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 00000000..079e4eba --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,16 @@ +Amazon Braket Python SDK +======================== + +Amazon Braket Python SDK is an open source library for interacting with quantum devices on Amazon Braket. + +TODO describe the different feature sets / abstractions. + +Here you'll find an overview and API documentation for Amazon Braket Python SDK. The project homepage is in GitHub, https://github.com/aws/braket-python-sdk, where you can find the SDK source and installation instructions for the library. + +Indices and tables +__________________ + +* :doc:`_apidoc/modules` +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..aa4949aa --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.black] +line-length = 100 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..8a08eedc --- /dev/null +++ b/setup.cfg @@ -0,0 +1,27 @@ +[aliases] +test=pytest + +[tool:pytest] +xfail_strict = true +addopts = + --verbose +testpaths = test/unit_tests + +[isort] +line_length = 100 +multi_line_output = 3 +include_trailing_comma = true + +[flake8] +ignore = + E203, # not pep8, black adds whitespace before ':' + W503, # not pep8, black adds line break before binary operator +max_line_length = 100 +max-complexity = 10 +exclude = + __pycache__ + .tox + .git + bin + build + venv diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..7257d632 --- /dev/null +++ b/setup.py @@ -0,0 +1,46 @@ +# Copyright 2019-2019 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 setuptools import find_namespace_packages, setup + +setup( + name="braket-sdk", + version="0.1.1", + license="Apache License 2.0", + python_requires=">= 3.7.2", + packages=find_namespace_packages(where="src", exclude=("test",)), + package_dir={"": "src"}, + install_requires=[ + "braket-ir @ git+https://github.com/aws/braket-python-ir.git", + "boltons", + "boto3", + "nest-asyncio", + "numpy", + ], + extras_require={ + "test": [ + "black", + "flake8", + "isort", + "pre-commit", + "pylint", + "pytest", + "pytest-cov", + "pytest-rerunfailures", + "pytest-xdist", + "sphinx", + "sphinx-rtd-theme", + "tox", + ] + }, +) diff --git a/src/braket/aws/__init__.py b/src/braket/aws/__init__.py new file mode 100644 index 00000000..a304b490 --- /dev/null +++ b/src/braket/aws/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2019-2019 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.aws.aws_qpu import AwsQpu # noqa: F401 +from braket.aws.aws_qpu_arns import AwsQpuArns # noqa: F401 +from braket.aws.aws_quantum_simulator import AwsQuantumSimulator # noqa: F401 +from braket.aws.aws_quantum_simulator_arns import AwsQuantumSimulatorArns # noqa: F401 +from braket.aws.aws_quantum_task import AwsQuantumTask # noqa: F401 +from braket.aws.aws_quantum_task_result import AwsQuantumTaskResult # noqa: F401 +from braket.aws.aws_session import AwsSession # noqa: F401 diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py new file mode 100644 index 00000000..3fc0f82e --- /dev/null +++ b/src/braket/aws/aws_qpu.py @@ -0,0 +1,146 @@ +# Copyright 2019-2019 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 boto3 +from braket.aws.aws_qpu_arns import AwsQpuArns +from braket.aws.aws_quantum_task import AwsQuantumTask +from braket.aws.aws_session import AwsSession +from braket.devices.device import Device + + +class AwsQpu(Device): + """ + Amazon Braket implementation of a QPU. + Use this class to retrieve the latest metadata about the QPU, and run a circuit on the QPU. + """ + + QPU_REGIONS = {AwsQpuArns.RIGETTI: ["us-west-1"], AwsQpuArns.IONQ: ["us-east-1"]} + + def __init__(self, arn: str, aws_session=None): + """ + Args: + arn (str): QPU ARN, e.g. "arn:aws:aqx:::qpu:ionq" + aws_session (AwsSession, optional) aws_session: AWS session object. Default = None. + + Raises: + ValueError: If unknown `arn` is supplied. + + Note: + QPUs are physically located in specific AWS regions. If the supplied `aws_session` + is connected to a region that the QPU is not in then a cloned `aws_session` + will be created for the QPU region. + + See `braket.aws.aws_qpu.AwsQpu.QPU_REGIONS` for the regions the QPUs are located in. + """ + super().__init__( + name=None, status=None, status_reason=None, supported_quantum_operations=None + ) + self._arn = arn + self._aws_session = self._aws_session_for_qpu(arn, aws_session) + self._qubit_count: int = None + # TODO: convert into graph object of qubits, type TBD + self._connectivity_graph = None + + self.refresh_metadata() + + def run(self, *aws_quantum_task_args, **aws_quantum_task_kwargs) -> AwsQuantumTask: + """ + Run a circuit on this AWS quantum device. + + Args: + *aws_quantum_task_args: Variable length positional arguments for + `braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit()`. + **aws_quantum_task_kwargs: Variable length keyword arguments for + `braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit()`. + + Returns: + AwsQuantumTask: AwsQuantumTask that is tracking the circuit execution on the device. + + Examples: + >>> circuit = Circuit().h(0).cnot(0, 1) + >>> device = AwsQpu("arn:aws:aqx:::qpu:rigetti") + >>> device.run(circuit, ("bucket-foo", "key-bar")) + + >>> circuit = Circuit().h(0).cnot(0, 1) + >>> device = AwsQpu("arn:aws:aqx:::qpu:rigetti") + >>> device.run(circuit=circuit, s3_destination_folder=("bucket-foo", "key-bar")) + + See Also: + `braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit()` + """ + return AwsQuantumTask.from_circuit( + self._aws_session, self._arn, *aws_quantum_task_args, **aws_quantum_task_kwargs + ) + + def refresh_metadata(self) -> None: + """ + Refresh AwsQpu object with most up to date QPU metadata. + """ + qpu_metadata = self._aws_session.get_qpu_metadata(self._arn) + self._name = qpu_metadata.get("name") + self._status = qpu_metadata.get("status") + self._status_reason = qpu_metadata.get("statusReason") + # TODO: convert string into gate types + self._supported_quantum_operations = qpu_metadata.get("supportedQuantumOperations") + self._qubit_count = qpu_metadata.get("qubitCount") + if "connectivity" in qpu_metadata: + self._connectivity_graph = qpu_metadata.get("connectivity").get("connectivityGraph") + + @property + def arn(self) -> str: + """str: Return arn of QPU.""" + return self._arn + + @property + def qubit_count(self) -> int: + """int: Return maximum number of qubits that can be run on QPU.""" + return self._qubit_count + + @property + def connectivity_graph(self): + """Return connectivity graph of QPU.""" + return self._connectivity_graph + + def _aws_session_for_qpu(self, qpu_arn: str, aws_session: AwsSession) -> AwsSession: + """ + Get an AwsSession for the QPU ARN. QPUs are only available in certain regions so any + supplied AwsSession in a region the QPU doesn't support will need to be adjusted. + """ + + qpu_regions = AwsQpu.QPU_REGIONS.get(qpu_arn, []) + if not qpu_regions: + raise ValueError(f"Unknown QPU {qpu_arn} was supplied.") + + if aws_session: + if aws_session.boto_session.region_name in qpu_regions: + return aws_session + else: + creds = aws_session.boto_session.get_credentials() + boto_session = boto3.Session( + aws_access_key_id=creds.access_key, + aws_secret_access_key=creds.secret_key, + aws_session_token=creds.token, + profile_name=aws_session.boto_session.profile_name, + region_name=qpu_regions[0], + ) + return AwsSession(boto_session=boto_session) + else: + boto_session = boto3.Session(region_name=qpu_regions[0]) + return AwsSession(boto_session=boto_session) + + def __repr__(self): + return "QPU('name': {}, 'arn': {})".format(self.name, self.arn) + + def __eq__(self, other): + if isinstance(other, AwsQpu): + return self.arn == other.arn + return NotImplemented diff --git a/src/braket/aws/aws_qpu_arns.py b/src/braket/aws/aws_qpu_arns.py new file mode 100644 index 00000000..404c2103 --- /dev/null +++ b/src/braket/aws/aws_qpu_arns.py @@ -0,0 +1,19 @@ +# Copyright 2019-2019 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 enum import Enum + + +class AwsQpuArns(str, Enum): + RIGETTI = "arn:aws:aqx:::qpu:rigetti" + IONQ = "arn:aws:aqx:::qpu:ionq" diff --git a/src/braket/aws/aws_quantum_simulator.py b/src/braket/aws/aws_quantum_simulator.py new file mode 100644 index 00000000..fd8795be --- /dev/null +++ b/src/braket/aws/aws_quantum_simulator.py @@ -0,0 +1,96 @@ +# Copyright 2019-2019 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.aws.aws_quantum_task import AwsQuantumTask +from braket.aws.aws_session import AwsSession +from braket.devices.device import Device + + +class AwsQuantumSimulator(Device): + """ + Amazon Braket implementation of a quantum simulator. + Use this class to retrieve the latest metadata about the simulator, + and run a circuit on the simulator. + """ + + def __init__(self, arn: str, aws_session=None): + """ + Args: + arn (str): Simulator type ARN e.g. "arn:aws:aqx:::quantum-simulator:aqx:qs1". + aws_session (AwsSession, optional) aws_session: AWS session object. Default = None. + """ + super().__init__( + name=None, status=None, status_reason=None, supported_quantum_operations=None + ) + self._arn = arn + self._aws_session = aws_session or AwsSession() + self._qubit_count: int = None + + self.refresh_metadata() + + def run(self, *aws_quantum_task_args, **aws_quantum_task_kwargs) -> AwsQuantumTask: + """ + Run a circuit on this AWS quantum device. + + Args: + *aws_quantum_task_args: Variable length positional arguments for + `braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit()`. + **aws_quantum_task_kwargs: Variable length keyword arguments for + `braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit()`. + + Returns: + AwsQuantumTask: AwsQuantumTask that is tracking the circuit execution on the device. + + Examples: + >>> circuit = Circuit().h(0).cnot(0, 1) + >>> device = AwsQuantumSimulator("arn:aws:aqx:::quantum-simulator:aqx:qs1") + >>> device.run(circuit, ("bucket-foo", "key-bar")) + + >>> circuit = Circuit().h(0).cnot(0, 1) + >>> device = AwsQuantumSimulator("arn:aws:aqx:::quantum-simulator:aqx:qs1") + >>> device.run(circuit=circuit, s3_destination_folder=("bucket-foo", "key-bar")) + + See Also: + `braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit()` + """ + return AwsQuantumTask.from_circuit( + self._aws_session, self._arn, *aws_quantum_task_args, **aws_quantum_task_kwargs + ) + + def refresh_metadata(self) -> None: + """Refresh AwsQuantumSimulator object with most up to date simulator metadata.""" + simulator_metadata = self._aws_session.get_simulator_metadata(self._arn) + self._name = simulator_metadata.get("name") + self._status = simulator_metadata.get("status") + self._status_reason = simulator_metadata.get("statusReason") + # TODO: convert string into gate types + self._supported_quantum_operations = simulator_metadata.get("supportedQuantumOperations") + self._qubit_count = simulator_metadata.get("qubitCount") + + @property + def arn(self) -> str: + """str: Return arn of simulator.""" + return self._arn + + @property + def qubit_count(self) -> int: + """int: Return maximum number of qubits that can be run on simulator.""" + return self._qubit_count + + def __repr__(self): + return f"QuantumSimulator('name': {self.name}, 'arn': {self.arn})" + + def __eq__(self, other): + if isinstance(other, AwsQuantumSimulator): + return self.arn == other.arn + return NotImplemented diff --git a/src/braket/aws/aws_quantum_simulator_arns.py b/src/braket/aws/aws_quantum_simulator_arns.py new file mode 100644 index 00000000..3c2c7a36 --- /dev/null +++ b/src/braket/aws/aws_quantum_simulator_arns.py @@ -0,0 +1,20 @@ +# Copyright 2019-2019 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 enum import Enum + + +class AwsQuantumSimulatorArns(str, Enum): + QS1 = "arn:aws:aqx:::quantum-simulator:aqx:qs1" + QS2 = "arn:aws:aqx:::quantum-simulator:aqx:qs2" + QS3 = "arn:aws:aqx:::quantum-simulator:aqx:qs3" diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py new file mode 100644 index 00000000..b87da30c --- /dev/null +++ b/src/braket/aws/aws_quantum_task.py @@ -0,0 +1,237 @@ +# Copyright 2019-2019 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 asyncio +import time +from typing import Any, Dict + +from braket.aws.aws_quantum_task_result import AwsQuantumTaskResult +from braket.aws.aws_session import AwsSession +from braket.circuits.circuit import Circuit +from braket.tasks import QuantumTask + + +class AwsQuantumTask(QuantumTask): + """Amazon Braket implementation of a quantum task.""" + + # TODO: Add API documentation that defines these states. Make it clear this is the contract. + TERMINAL_STATES = {"COMPLETED", "FAILED", "CANCELLED"} + RESULTS_READY_STATES = {"COMPLETED"} + + GATE_IR_TYPE = "jaqcd" + DEFAULT_SHOTS = 1_000 + + @staticmethod + def from_circuit( + aws_session: AwsSession, + device_arn: str, + circuit: Circuit, + s3_destination_folder: AwsSession.S3DestinationFolder, + shots: int = DEFAULT_SHOTS, + ): + """ + AwsQuantumTask factory method that serializes the circuit, submits to Amazon Braket, and + returns back a AwsQuantumTask tracking the execution. + + Args: + aws_session (AwsSession): AwsSession to call AWS with. + device_arn (str): AWS quantum device arn. + circuit (Circuit): Circuit to run on device. + s3_destination_folder (AwsSession.S3DestinationFolder): NamedTuple with bucket (index 0) + and key (index 1) that is the results destination folder in S3. + shots (int): The number of times to run the circuit on the quantum device. If the + device is a classical simulator then this implies sampling the state N times, + where N = `shots`. Default = 1_000. + + Returns: + AwsQuantumTask: AwsQuantumTask that is tracking the circuit execution on the device. + + Note: + The following arguments are typically defined via clients of Device. + - `circuit` + - `s3_destination_folder` + - `shots` + """ + if len(s3_destination_folder) != 2: + raise ValueError( + "s3_destination_folder must be of size 2 with a 'bucket' and 'key' respectively." + ) + + create_task_kwargs = { + "backendArn": device_arn, + "resultsS3Bucket": s3_destination_folder[0], + "resultsS3Prefix": s3_destination_folder[1], + "ir": circuit.to_ir().json(), + "irType": AwsQuantumTask.GATE_IR_TYPE, + "gateModelConfig": {"qubitCount": circuit.qubit_count}, + "shots": shots, + } + task_arn = aws_session.create_quantum_task(**create_task_kwargs) + return AwsQuantumTask(task_arn, aws_session) + + def __init__( + self, + arn: str, + aws_session: AwsSession, + poll_timeout_seconds: int = 120, + poll_interval_seconds: int = 0.25, + ): + """ + Args: + arn (str): The AWS quantum task ARN. + aws_session (AwsSession): The AwsSession for communicating with AWS. + poll_timeout_seconds (int): The polling timeout for result(), default 120 seconds. + poll_internal_seconds (int): The polling interval for result(), default 0.25 seconds. + """ + self._arn: str = arn + self._aws_session: AwsSession = aws_session + self._poll_timeout_seconds = poll_timeout_seconds + self._poll_interval_seconds = poll_interval_seconds + + self._metadata: Dict[str, Any] = {} + self._result: AwsQuantumTaskResult = None + self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) + + @property + def id(self) -> str: + """str: The AWS quantum task ARN.""" + return self._arn + + def cancel(self) -> None: + """Cancel the quantum task. This cancels the future and the task in Amazon Braket.""" + self._future.cancel() + self._aws_session.cancel_quantum_task(self._arn) + + def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: + """ + Get task metadata defined in Amazon Braket. + + Args: + use_cached_value (bool, optional): If true returns the last value retrieved from + Amazon Braket GetQuantumTask API else the API is called and the cache is updated. + Default = False. + + Returns: + Dict[str, Any]: The Amazon Braket GetQuantumTask API response. TODO: INSERT BOTO3 LINK. + If `use_cached_value` is True then Amazon Braket is not called and the last value + retrieved is returned. + """ + if not use_cached_value: + self._metadata = self._aws_session.get_quantum_task(self._arn) + return self._metadata + + def state(self, use_cached_value: bool = False) -> str: + """ + State of the quantum task. + + Args: + use_cached_value (bool, optional): If true returns the last state value retrieved from + Amazon Braket GetQuantumTask API else the API is called and the cache is updated. + Default = False. + + Returns: + str: The value of "status" in `metadata()`. This is the value of the "status" key + in the Amazon Braket GetQuantumTask API call. TODO: INSERT BOTO3 DOC LINK. If + `use_cached_value` is True then Amazon Braket is not called and the last value retrieved + is returned. + + See Also: + `metadata()` + """ + return self.metadata(use_cached_value).get("status") + + def result(self) -> AwsQuantumTaskResult: + """ + Get the quantum task result by polling Amazon Braket to see if the task is completed. Once + the task is completed the result is retrieved from S3 and returned as a QuantumTaskResult. + + This method is a blocking thread call and will synchronously return back a result. Call + async_result() if you require an asynchronous invocation. + + Consecutive calls to this method will return back a cached result. + """ + try: + return asyncio.get_event_loop().run_until_complete(self.async_result()) + except asyncio.CancelledError: + # Future was cancelled, return whatever is in self._result if anything + return self._result + + def async_result(self) -> asyncio.Task: + """ + Get the quantum task result asynchronously. + + Consecutive calls to this method will return back a cached result. + """ + if ( + self._future.done() + and self.metadata(use_cached_value=True).get("status") + not in AwsQuantumTask.TERMINAL_STATES + ): # Future timed out + self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) + return self._future + + async def _create_future(self) -> asyncio.Task: + """ + Wrap the _wait_for_completion coroutine inside a future-like object. + Invoking this method will start the coroutine and return back the future-like object + that contains it. Note that this does not block on the coroutine to finish. + + Returns: + asyncio.Task: An asyncio Task that contains the _wait_for_completion() coroutine. + """ + return asyncio.create_task(self._wait_for_completion()) + + async def _wait_for_completion(self) -> AwsQuantumTaskResult: + """ + Waits for the quantum task to be completed and returns back result from S3. + + Returns: + AwsQuantumTaskResult: If the task ends up in the `AwsQuantumTask.RESULTS_READY_STATES` + state within the time limit then the result from S3 is loaded and returned. None is + returned if a timeout occurs or task state is in `AwsQuantumTask.TERMINAL_STATES` + but not `AwsQuantumTask.RESULTS_READY_STATES`. + + Note: + Timeout and sleep intervals are defined in the constructor fields + `poll_timeout_seconds` and `poll_interval_seconds` respectively. + """ + start_time = time.time() + + while (time.time() - start_time) < self._poll_timeout_seconds: + current_metadata = self.metadata() + if current_metadata["status"] in AwsQuantumTask.RESULTS_READY_STATES: + result_string = self._aws_session.retrieve_s3_object_body( + current_metadata["resultsS3Bucket"], current_metadata["resultsS3ObjectKey"] + ) + self._result = AwsQuantumTaskResult.from_string(result_string) + return self._result + elif current_metadata["status"] in AwsQuantumTask.TERMINAL_STATES: + self._result = None + return None + else: + await asyncio.sleep(self._poll_interval_seconds) + + # Timed out + self._result = None + return None + + def __repr__(self) -> str: + return "AwsQuantumTask('id':{})".format(self.id) + + def __eq__(self, other) -> bool: + if isinstance(other, AwsQuantumTask): + return self.id == other.id + return NotImplemented + + def __hash__(self) -> int: + return hash(self.id) diff --git a/src/braket/aws/aws_quantum_task_result.py b/src/braket/aws/aws_quantum_task_result.py new file mode 100644 index 00000000..250a3a18 --- /dev/null +++ b/src/braket/aws/aws_quantum_task_result.py @@ -0,0 +1,99 @@ +# Copyright 2019-2019 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 dataclasses import dataclass +from typing import Any, Dict + +import numpy as np +from braket.tasks.quantum_task_result import QuantumTaskResult + + +@dataclass +class AwsQuantumTaskResult(QuantumTaskResult): + """ + Result of an AWS quantum task execution. This class is intended + to be initialized by a QuantumTask class. + + Args: + measurements (numpy.ndarray): 2d array - row is shot, column is qubit. + 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. + 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. + measurements_copied_from_device (bool): flag whether `measurements` were copied from device. + If false, `measurements` are calculated from device data. + measurement_counts_copied_from_device (bool): flag whether `measurement_counts` were copied + from device. If false, `measurement_counts` are calculated from device data. + measurement_probabilities_copied_from_device (bool): flag whether + `measurement_probabilities` were copied from device. If false, + `measurement_probabilities` are calculated from device data. + task_metadata (Dict[str, Any]): Dictionary of task metadata. TODO: Link boto3 docs. + state_vector (Dict[str, float]): Dictionary where key is state and value is probability. + """ + + task_metadata: Dict[str, Any] + state_vector: Dict[str, float] = None + + @staticmethod + def from_string(result: str) -> "AwsQuantumTaskResult": + """ + Create AwsQuantumTaskResult from string with the S3 format defined by the Amazon Braket + Service. TODO: Link Amazon Braket S3 format docs. + + Args: + result (str): JSON object string, whose keys are AwsQuantumTaskResult attributes. + + Returns: + AwsQuantumTaskResult: A AwsQuantumTaskResult based on a string loaded from S3. + """ + json_obj = json.loads(result) + state_vector = json_obj.get("StateVector", None) + task_metadata = json_obj["TaskMetadata"] + if "Measurements" in json_obj: + measurements = np.asarray(json_obj["Measurements"], dtype=int) + m_counts = QuantumTaskResult.measurement_counts_from_measurements(measurements) + m_probabilities = QuantumTaskResult.measurement_probabilities_from_measurement_counts( + m_counts + ) + measurements_copied_from_device = True + m_counts_copied_from_device = False + m_probabilities_copied_from_device = False + if "MeasurementProbabilities" in json_obj: + shots = task_metadata["Shots"] + m_probabilities = json_obj["MeasurementProbabilities"] + measurements = QuantumTaskResult.measurements_from_measurement_probabilities( + m_probabilities, shots + ) + m_counts = QuantumTaskResult.measurement_counts_from_measurements(measurements) + measurements_copied_from_device = False + m_counts_copied_from_device = False + m_probabilities_copied_from_device = True + return AwsQuantumTaskResult( + state_vector=state_vector, + task_metadata=task_metadata, + measurements=measurements, + measurement_counts=m_counts, + measurement_probabilities=m_probabilities, + measurements_copied_from_device=measurements_copied_from_device, + measurement_counts_copied_from_device=m_counts_copied_from_device, + measurement_probabilities_copied_from_device=m_probabilities_copied_from_device, + ) + + def __eq__(self, other) -> bool: + if isinstance(other, AwsQuantumTaskResult): + self_fields = (self.task_metadata, self.state_vector) + other_fields = (other.task_metadata, other.state_vector) + return self_fields == other_fields and super().__eq__(other) + return NotImplemented diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py new file mode 100644 index 00000000..c4f8f834 --- /dev/null +++ b/src/braket/aws/aws_session.py @@ -0,0 +1,143 @@ +# Copyright 2019-2019 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 typing import Any, Dict, NamedTuple + +import boto3 + + +class AwsSession(object): + """Manage interactions with AWS services.""" + + S3DestinationFolder = NamedTuple("S3DestinationFolder", [("bucket", str), ("key", int)]) + + BRAKET_ENDPOINTS = { + "us-west-1": "https://fdoco1n1x7.execute-api.us-west-1.amazonaws.com/Prod", + "us-west-2": "https://xe15dbdvw6.execute-api.us-west-2.amazonaws.com/Prod", + "us-east-1": "https://kqjovr0n70.execute-api.us-east-1.amazonaws.com/Prod", + } + + # similar to sagemaker sdk: + # https://github.com/aws/sagemaker-python-sdk/blob/master/src/sagemaker/session.py + def __init__(self, boto_session=None, braket_client=None): + """ + Args: + boto_session: boto3 session object + braket_client: boto3 braket client + + Raises: + ValueError: If Amazon Braket does not exist for the `boto_session`'s region. + """ + + self.boto_session = boto_session or boto3.Session() + + if braket_client: + self.braket_client = braket_client + else: + region = self.boto_session.region_name + endpoint = AwsSession.BRAKET_ENDPOINTS.get(region, None) + if not endpoint: + supported_regions = list(AwsSession.BRAKET_ENDPOINTS.keys()) + raise ValueError( + f"No braket endpoint for {region}, supported regions are {supported_regions}" + ) + + self.braket_client = self.boto_session.client("aqx", endpoint_url=endpoint) + + # + # Quantum Tasks + # + def cancel_quantum_task(self, arn: str) -> None: + """ + Cancel the quantum task. + + Args: + arn (str): ARN of the quantum task to cancel. + """ + self.braket_client.cancel_quantum_task(quantumTaskArn=arn) + + def create_quantum_task(self, **boto3_kwargs) -> str: + """ + Create a quantum task. + + Args: + **boto3_kwargs: Keyword arguments for the Braket CreateQuantumTask API. + + Returns: + str: Quantum task ARN. + """ + response = self.braket_client.create_quantum_task(**boto3_kwargs) + return response["quantumTaskArn"] + + def get_quantum_task(self, arn: str) -> Dict[str, Any]: + """ + Get the quantum task. + + Args: + arn (str): ARN of the quantum task to cancel. + + Returns: + Dict[str, Any]: Braket GetQuantumTask API result. + """ + return self.braket_client.get_quantum_task(quantumTaskArn=arn) + + def retrieve_s3_object_body(self, s3_bucket: str, s3_object_key: str) -> str: + """ + Retrieve S3 object body + + Args: + s3_bucket (str): S3 bucket name + s3_object_key (str): S3 object key within `s3_bucket` + + Returns: + str: Body of S3 object + """ + s3 = self.boto_session.resource("s3") + obj = s3.Object(s3_bucket, s3_object_key) + return obj.get()["Body"].read().decode("utf-8") + + # TODO: add in boto3 exception handling once we have exception types in API + def get_qpu_metadata(self, arn: str) -> Dict[str, Any]: + """ + Call AWS API describe_qpus to return metadata about QPU + + Args: + arn (str): QPU ARN + + Returns: + Dict[str, Any]: QPU metadata + """ + try: + response = self.braket_client.describe_qpus(qpuArns=[arn]) + qpu_metadata = response.get("qpus")[0] + return qpu_metadata + except Exception as e: + raise e + + # TODO: add in boto3 exception handling once we have exception types in API + def get_simulator_metadata(self, arn: str) -> Dict[str, Any]: + """ + Call AWS API describe_quantum_simulators to return metadata about simulator + + Args: + arn (str): simulator ARN + + Returns: + Dict[str, Any]: simulator metadata + """ + try: + response = self.braket_client.describe_quantum_simulators(quantumSimulatorArns=[arn]) + simulator_metadata = response.get("quantumSimulators")[0] + return simulator_metadata + except Exception as e: + raise e diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py new file mode 100644 index 00000000..35f41cdc --- /dev/null +++ b/src/braket/circuits/__init__.py @@ -0,0 +1,27 @@ +# Copyright 2019-2019 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. + +# Execute initialization code in circuit module +import braket.circuits.circuit as circuit # noqa: F401 +# Execute initialization code in gates module +import braket.circuits.gates as gates # noqa: F401 +from braket.circuits.angled_gate import AngledGate # 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.gate import Gate # noqa: F401 +from braket.circuits.instruction import Instruction # noqa: F401 +from braket.circuits.moments import Moments, MomentsKey # noqa: F401 +from braket.circuits.operator import Operator # noqa: F401 +from braket.circuits.qubit import Qubit, QubitInput # noqa: F401 +from braket.circuits.qubit_set import QubitSet, QubitSetInput # noqa: F401 diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py new file mode 100644 index 00000000..88b44875 --- /dev/null +++ b/src/braket/circuits/angled_gate.py @@ -0,0 +1,53 @@ +# Copyright 2019-2019 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 typing import Sequence + +from braket.circuits.gate import Gate + + +class AngledGate(Gate): + """ + Class `AngledGate` represents a quantum gate that operates on N qubits and an angle. + """ + + def __init__(self, angle: float, qubit_count: int, ascii_symbols: Sequence[str]): + """ + Args: + angle (float): Angle of gate in radians + qubit_count (int): Number of qubits this gate interacts with. + ascii_symbols (Sequence[str]): ASCII string symbols for the gate, these are used when + printing a diagram of circuits. Length must be the same as `qubit_count`, and + index ordering is expected to correlate with target ordering on the instruction. + For instance, if CNOT instruction has the control qubit on the first index and + target qubit on the second index. Then ASCII symbols would have ["C", "X"] to + correlate a symbol with that index. + + Raises: + ValueError: `qubit_count` is less than 1, `ascii_symbols` are None, or + `ascii_symbols` length != `qubit_count`, or `angle` is None + """ + super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + if angle is None: + raise ValueError(f"angle must not be None") + self._angle = angle + + @property + def angle(self) -> float: + """ + Returns angle for the gate + + Returns: + angle (float): angle of gate in radians + """ + return self._angle diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py new file mode 100644 index 00000000..04cf2b4a --- /dev/null +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -0,0 +1,115 @@ +# Copyright 2019-2019 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 typing import List + +from braket.circuits.circuit_diagram import CircuitDiagram +from braket.circuits.gate import Gate +from braket.circuits.instruction import Instruction +from braket.circuits.qubit_set import QubitSet + + +class AsciiCircuitDiagram(CircuitDiagram): + """Builds ASCII string circuit diagrams.""" + + @staticmethod + def build_diagram(circuit) -> str: + """ + Build an ASCII string circuit diagram. + + Args: + circuit (Circuit): Circuit to build a diagram of. + + Returns: + str: ASCII string circuit diagram. + """ + + if not circuit.instructions: + return "" + + 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) + + time_slices = circuit.moments.time_slices() + + # Moment columns + moments_strs = [] + for time, instructions in time_slices.items(): + moment_str = AsciiCircuitDiagram._ascii_diagram_moment( + time, circuit_qubits, instructions + ) + moments_strs.append(moment_str) + + # Unite strings + lines = y_axis_str.split("\n") + for moment_str in moments_strs: + for i, moment_line in enumerate(moment_str.split("\n")): + lines[i] += moment_line + + # Time on top and bottom + lines.append(lines[0]) + + return "\n".join(lines) + + @staticmethod + def _ascii_diagram_moment( + time: int, circuit_qubits: QubitSet, instructions: List[Instruction] + ) -> str: + """ + Return an ASCII string diagram of the circuit at a particular time moment. + + Returns: + str: ASCII string diagram for the given time moment. + """ + symbols = {qubit: "-" for qubit in circuit_qubits} + margins = {qubit: " " for qubit in circuit_qubits} + + for instr in instructions: + # Can only print Gate operators at the moment + if not isinstance(instr.operator, Gate): + continue + + qubits = circuit_qubits.intersection( + set(range(min(instr.target), max(instr.target) + 1)) + ) + for qubit in qubits: + # Determine if the qubit is part of the instruction or in the middle of a + # multi qubit gate. + if qubit in instr.target: + instr_qubit_index = [ + index for index, q in enumerate(instr.target) if q == qubit + ][0] + symbols[qubit] = instr.operator.ascii_symbols[instr_qubit_index] + else: + symbols[qubit] = "|" + + # set the margin to be a connector if not on the first qubit + if qubit != min(instr.target): + margins[qubit] = "|" + + symbols_width = max([len(symbol) for symbol in symbols.values()]) + + output = "{0:{width}}|\n".format(str(time), width=symbols_width) + for qubit in circuit_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 diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py new file mode 100644 index 00000000..f554f1f6 --- /dev/null +++ b/src/braket/circuits/circuit.py @@ -0,0 +1,392 @@ +# Copyright 2019-2019 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 typing import Callable, Dict, Iterable, TypeVar + +from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram +from braket.circuits.instruction import Instruction +from braket.circuits.moments import Moments +from braket.circuits.qubit import QubitInput +from braket.circuits.qubit_set import QubitSet, QubitSetInput +from braket.ir.jaqcd import Program + +SubroutineReturn = TypeVar("SubroutineReturn", Iterable[Instruction], Instruction) +SubroutineCallable = TypeVar("SubroutineCallable", bound=Callable[..., SubroutineReturn]) +AddableTypes = TypeVar("AddableTypes", SubroutineReturn, SubroutineCallable) + +# TODO: Add parameterization + + +class Circuit: + """ + A representation of a quantum circuit that contains the instructions to be performed on a + quantum device. See :mod:`braket.circuits.gates` module for all the supported instructions. + """ + + @classmethod + def register_subroutine(cls, func: SubroutineCallable) -> None: + """ + Register the subroutine `func` as an attribute of the Circuit class. The attribute name + is the name of `func`. + + Args: + func (Callable[..., Union[Instruction, Iterable[Instruction]]]): The function of the + subroutine to be added to the class. + + Examples: + >>> def h_on_all(target): + ... circ = Circuit() + ... 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: + return self.add(func, *args, **kwargs) + + function_name = func.__name__ + setattr(cls, function_name, method_from_subroutine) + + function_attr = getattr(cls, function_name) + setattr(function_attr, "__doc__", func.__doc__) + + def __init__(self, addable: AddableTypes = None, *args, **kwargs): + """ + Args: + addable (Instruction, iterable of Instruction, or SubroutineCallable, optional): The + instruction-like item(s) to add to self. Default = None. + *args: Variable length argument list. Supports any arguments that `add()` offers. + **kwargs: Arbitrary keyword arguments. Supports any keyword arguments that `add()` + offers. + + Raises: + TypeError: If `addable` is an unsupported type. + + Examples: + >>> circ = Circuit([Instruction(Gate.H, 4), Instruction(Gate.CNot, [4, 5])]) + >>> circ = Circuit().h(0).cnot(0, 1) + + >>> @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]) + """ + self._moments: Moments = Moments() + + if addable is not None: + self.add(addable, *args, **kwargs) + + @property + def depth(self) -> int: + """int: Get the circuit depth.""" + return self._moments.depth + + @property + def instructions(self) -> Iterable[Instruction]: + """Iterable[Instruction]: Get an iterable of instructions in the circuit.""" + return self._moments.values() + + @property + def moments(self) -> Moments: + """Moments: Get the moments for this circuit.""" + return self._moments + + @property + def qubit_count(self) -> int: + """Get the qubit count for this circuit.""" + return self._moments.qubit_count + + @property + def qubits(self) -> QubitSet: + """QubitSet: Get a copy of the qubits for this circuit.""" + return QubitSet(self._moments.qubits) + + def add_instruction( + self, + instruction: Instruction, + target: QubitSetInput = None, + target_mapping: Dict[QubitInput, QubitInput] = {}, + ) -> "Circuit": + """ + Add an instruction to self, returns self for chaining ability. + + Args: + instruction (Instruction): Instruction to add into self. + target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the + instruction. If instruction is a single qubit gate than an instruction is created + for every index in `target`. Default = None. + target_mapping (dictionary[int or Qubit, int or Qubit], optional): 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 = {}. + + Returns: + Circuit: self + + Raises: + TypeError: If both `target_mapping` and `target` are supplied. + + Examples: + >>> instr = Instruction(Gate.CNot, [0, 1]) + >>> circ = Circuit().add_instruction(instr) + >>> print(circ.instructions[0]) + Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(0), Qubit(1))) + + >>> instr = Instruction(Gate.CNot, [0, 1]) + >>> circ = Circuit().add_instruction(instr, target_mapping={0: 10, 1: 11}) + >>> print(circ.instructions[0]) + Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(10), Qubit(11))) + + >>> instr = Instruction(Gate.CNot, [0, 1]) + >>> circ = Circuit().add_instruction(instr, target=[10, 11]) + >>> print(circ.instructions[0]) + Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(10), Qubit(11))) + + >>> instr = Instruction(Gate.H, 0) + >>> circ = Circuit().add_instruction(instr, target=[10, 11]) + >>> print(circ.instructions[0]) + Instruction('operator': 'H', 'target': QubitSet(Qubit(10),)) + >>> print(circ.instructions[1]) + Instruction('operator': 'H', 'target': QubitSet(Qubit(11),)) + """ + if target_mapping and target is not None: + raise TypeError("Both 'target_mapping' and 'target' cannot be supplied.") + + if not target_mapping and not target: + # Nothing has been supplied, add instruction + instructions_to_add = [instruction] + elif target_mapping: + # Target mapping has been supplied, copy instruction + instructions_to_add = [instruction.copy(target_mapping=target_mapping)] + elif hasattr(instruction.operator, "qubit_count") and instruction.operator.qubit_count == 1: + # single qubit operator with target, add an instruction for each target + instructions_to_add = [instruction.copy(target=qubit) for qubit in target] + else: + # non single qubit operator with target, add instruction with target + instructions_to_add = [instruction.copy(target=target)] + + self._moments.add(instructions_to_add) + + return self + + def add_circuit( + self, + circuit: "Circuit", + target: QubitSetInput = None, + target_mapping: Dict[QubitInput, QubitInput] = {}, + ) -> "Circuit": + """ + Add a `circuit` to self, returns self for chaining ability. This is a composite form of + `add_instruction()` since it adds all the instructions of `circuit` to this circuit. + + Args: + circuit (Circuit): Circuit to add into self. + target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the + supplied circuit. This is a macro over `target_mapping`; `target` is converted to + a `target_mapping` by zipping together a sorted `circuit.qubits` and `target`. + Default = None. + target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of + qubit mappings to apply to the qubits of `circuit.instructions`. Key is the qubit + to be mapped and the value is what it will be changed to. Default = {}. + + Returns: + Circuit: self + + Raises: + TypeError: If both `target_mapping` and `target` are supplied. + + Note: + Supplying `target` sorts `circuit.qubits` in order to have deterministic behavior since + `circuit.qubits` ordering is based on how instructions are inserted. Therefore be + cautious using this parameter with circuits that have large number of qubits as the sort + can become costly. If performance is a concern then use `target_mapping` since that has + only a linear runtime to remap the qubits. + + Examples: + >>> widget = Circuit().h(0).cnot([0, 1]) + >>> circ = Circuit().add_circuit(widget) + >>> print(circ.instructions[0]) + Instruction('operator': 'H', 'target': QubitSet(Qubit(0),)) + >>> print(circ.instructions[1]) + Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(0), Qubit(1))) + + >>> widget = Circuit().h(0).cnot([0, 1]) + >>> circ = Circuit().add_circuit(widget, target_mapping={0: 10, 1: 11}) + >>> print(circ.instructions[0]) + Instruction('operator': 'H', 'target': QubitSet(Qubit(10),)) + >>> print(circ.instructions[1]) + Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(10), Qubit(11))) + + >>> widget = Circuit().h(0).cnot([0, 1]) + >>> circ = Circuit().add_circuit(widget, target=[10, 11]) + >>> print(circ.instructions[0]) + Instruction('operator': 'H', 'target': QubitSet(Qubit(10),)) + >>> print(circ.instructions[1]) + Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(10), Qubit(11))) + """ + if target_mapping and target is not None: + raise TypeError("Both 'target_mapping' and 'target' cannot be supplied.") + elif target is not None: + keys = sorted(circuit.qubits) + values = target + target_mapping = dict(zip(keys, values)) + + for instruction in circuit.instructions: + self.add_instruction(instruction, target_mapping=target_mapping) + + return self + + def add(self, addable: AddableTypes, *args, **kwargs) -> "Circuit": + """ + Generic add method for adding instruction-like item(s) to self. Any arguments that + `add_circuit()` and / or `add_instruction()` supports is supported by this method. + If adding a subroutine then check with that subroutines docs to determine what input it + allows. + + Args: + addable (Instruction, iterable of Instruction, or SubroutineCallable, optional): The + instruction-like item(s) to add to self. Default = None. + *args: Variable length argument list. + **kwargs: Arbitrary keyword arguments. + + Returns: + Circuit: self + + Raises: + TypeError: If `addable` is an unsupported type + + See Also: + `add_circuit()` + + `add_instruction()` + + Examples: + >>> circ = Circuit().add([Instruction(Gate.H, 4), Instruction(Gate.CNot, [4, 5])]) + + >>> circ = Circuit().h(4).cnot([4, 5]) + + >>> @circuit.subroutine() + >>> def bell_pair(target): + ... return Circuit().h(target[0]).cnot(target[0: 2]) + ... + >>> circ = Circuit().add(bell_pair, [4,5]) + """ + + def _flatten(addable): + if isinstance(addable, Iterable): + for item in addable: + yield from _flatten(item) + else: + yield addable + + for item in _flatten(addable): + if isinstance(item, Instruction): + self.add_instruction(item, *args, **kwargs) + elif isinstance(item, Circuit): + self.add_circuit(item, *args, **kwargs) + elif callable(item): + self.add(item(*args, **kwargs)) + else: + raise TypeError(f"Cannot add a '{type(item)}' to a Circuit") + + return self + + def diagram(self, circuit_diagram_class=AsciiCircuitDiagram) -> str: + """ + Get a diagram for the current circuit. + + Args: + circuit_diagram_class (Class, optional): A CircuitDiagram class that will build the + diagram for this circuit. Default = AsciiCircuitDiagram. + + Returns: + str: String circuit diagram. + """ + return circuit_diagram_class.build_diagram(self) + + def to_ir(self) -> Program: + """ + Converts the circuit into the canonical intermediate representation. + If the circuit is sent over the wire then this method is called before it is sent. + + Returns: + (Program): A JSON AWS Quantum Circuit Description (jaqcd) program. + """ + ir_instructions = [instr.to_ir() for instr in self.instructions] + return Program(instructions=ir_instructions) + + def _copy(self) -> "Circuit": + """ + Return a shallow copy of the circuit. + + Returns: + Circuit: Shallow copy of the circuit. + """ + return Circuit().add(self.instructions) + + def __iadd__(self, addable: AddableTypes) -> "Circuit": + return self.add(addable) + + def __add__(self, addable: AddableTypes) -> "Circuit": + new = self._copy() + new.add(addable) + return new + + def __repr__(self): + return f"Circuit('instructions': {list(self.instructions)})" + + def __str__(self): + return self.diagram(AsciiCircuitDiagram) + + def __eq__(self, other): + if isinstance(other, Circuit): + return list(self.instructions) == list(other.instructions) + return NotImplemented + + +def subroutine(register=False): + """ + Subroutine is a function that returns instructions or circuits. + + Args: + register (bool, optional): If true, monkey patch this subroutine into the Circuit class. + Default = False. + + Examples: + >>> @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),)) + """ + + def subroutine_function_wrapper(func: Callable[..., SubroutineReturn]) -> SubroutineReturn: + if register: + Circuit.register_subroutine(func) + return func + + return subroutine_function_wrapper diff --git a/src/braket/circuits/circuit_diagram.py b/src/braket/circuits/circuit_diagram.py new file mode 100644 index 00000000..ce45ba3a --- /dev/null +++ b/src/braket/circuits/circuit_diagram.py @@ -0,0 +1,32 @@ +# Copyright 2019-2019 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 abc import ABC, abstractmethod + + +class CircuitDiagram(ABC): + """A class that builds circuit diagrams.""" + + @staticmethod + @abstractmethod + def build_diagram(circuit) -> str: + """ + Build a diagram for the suppplied `circuit`. + + Args: + circuit (Circuit): The circuit to build a diagram for. + + Returns: + str: String representation for the circuit diagram. + Empty string is returned for an empty circuit. + """ diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py new file mode 100644 index 00000000..9c43a66b --- /dev/null +++ b/src/braket/circuits/gate.py @@ -0,0 +1,136 @@ +# Copyright 2019-2019 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 typing import Any, Sequence, Tuple + +import numpy as np +from braket.circuits.operator import Operator +from braket.circuits.qubit_set import QubitSet + +# TODO: Add parameters support +# TODO: Add printing / visualization capabilities + + +class Gate(Operator): + """ + Class `Gate` represents a quantum gate that operates on N qubits and are considered the + building blocks of quantum circuits. This class is considered the gate definition containing + the meta-data that defines what a gate is and what it can do. + """ + + def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): + """ + Args: + qubit_count (int): Number of qubits this gate interacts with. + ascii_symbols (Sequence[str]): ASCII string symbols for the gate, these are used when + printing a diagram of circuits. Length must be the same as `qubit_count`, and + index ordering is expected to correlate with target ordering on the instruction. + For instance, if CNOT instruction has the control qubit on the first index and + target qubit on the second index. Then ASCII symbols would have ["C", "X"] to + correlate a symbol with that index. + + Raises: + ValueError: `qubit_count` is less than 1, `ascii_symbols` are None, or + `ascii_symbols` length != `qubit_count` + """ + + if qubit_count < 1: + raise ValueError(f"qubit_count, {qubit_count}, must be greater than zero") + self._qubit_count = qubit_count + + if ascii_symbols is None: + raise ValueError(f"ascii_symbols must not be None") + + if len(ascii_symbols) != qubit_count: + msg = f"ascii_symbols, {ascii_symbols}, length must equal qubit_count, {qubit_count}" + raise ValueError(msg) + self._ascii_symbols = tuple(ascii_symbols) + + @property + def qubit_count(self) -> int: + """int: Returns number of qubits this gate interacts with.""" + return self._qubit_count + + @property + def ascii_symbols(self) -> Tuple[str]: + """Tuple[str]: Returns the ascii symbols for the gate.""" + return self._ascii_symbols + + @property + def name(self) -> str: + """ + Returns the name of the gate + + Returns: + name of the gate as a string + """ + return self.__class__.__name__ + + def to_ir(self, target: QubitSet) -> Any: + """Returns IR object of gate and target + + Args: + target (QubitSet): target qubit(s) + Returns: + IR object of gate and target + """ + raise NotImplementedError("to_ir has not been implemented yet.") + + def to_matrix(self, *args, **kwargs) -> Any: + """Returns gate in matrix representation + + Returns: + np.ndarray: gate in matrix representation + """ + raise NotImplementedError("to_matrix has not been implemented yet.") + + def __eq__(self, other): + if not isinstance(other, Gate): + return NotImplemented + if self.name == other.name: + if hasattr(self, "angle"): + return self.angle == other.angle + return True + else: + return False + + def matrix_equivalence(self, other): + """ + Return if the matrix form of two gates are equivalent + + Args: + other (Gate): Gate instance to compare + + Returns: + true if matrix forms of this gate and the other gate are equivalent + """ + if not isinstance(other, Gate): + return NotImplemented + try: + return np.allclose(self.to_matrix(), other.to_matrix()) + except ValueError: + return False + + def __repr__(self): + if hasattr(self, "angle"): + return f"{self.name}('angle': {self.angle}, 'qubit_count': {self.qubit_count})" + return f"{self.name}('qubit_count': {self.qubit_count})" + + @classmethod + def register_gate(cls, gate: "Gate"): + """Register a gate implementation by monkey patching into the Gate class. + + Args: + gate (Gate): Gate instance to register. + """ + setattr(cls, gate.__name__, gate) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py new file mode 100644 index 00000000..bb82d4e0 --- /dev/null +++ b/src/braket/circuits/gates.py @@ -0,0 +1,1182 @@ +# Copyright 2019-2019 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 typing import Iterable + +import braket.ir.jaqcd as ir +import numpy as np +from braket.circuits import circuit +from braket.circuits.angled_gate import AngledGate +from braket.circuits.gate import Gate +from braket.circuits.instruction import Instruction +from braket.circuits.qubit import QubitInput +from braket.circuits.qubit_set import QubitSet, QubitSetInput + +# TODO: look into adding angle to diagrams + +""" +To add a new gate: + 1. Implement the class and extend `Gate` + 2. Add a method with the `@circuit.subroutine(register=True)` decorator. Method name + will be monkey patched into the Circuit class. This method will be the default way + clients add this gate to a circuit. + 3. Register the class with the `Gate` class via `Gate.register_gate()`. +""" + +# Single qubit gates # + + +class H(Gate): + """Hadamard gate.""" + + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["H"]) + + def to_ir(self, target: QubitSet): + return ir.H(target=target[0]) + + def to_matrix(self) -> np.ndarray: + return 1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) + + @staticmethod + @circuit.subroutine(register=True) + def h(target: QubitSetInput) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + + Returns: + Iterable[Instruction]: Iterable of H instructions. + + Examples: + >>> circ = Circuit().h(0) + >>> circ = Circuit().h([0, 1, 2]) + """ + return [Instruction(Gate.H(), target=qubit) for qubit in QubitSet(target)] + + +Gate.register_gate(H) + + +class I(Gate): # noqa: E742, E261 + """Identity gate.""" + + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["I"]) + + def to_ir(self, target: QubitSet): + return ir.I(target=target[0]) + + def to_matrix(self) -> np.ndarray: + return np.array([[1.0, 0.0], [0.0, 1.0]], dtype=complex) + + @staticmethod + @circuit.subroutine(register=True) + def i(target: QubitSetInput) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + + Returns: + Iterable[Instruction]: Iterable of I instructions. + + Examples: + >>> circ = Circuit().i(0) + >>> circ = Circuit().i([0, 1, 2]) + """ + return [Instruction(Gate.I(), target=qubit) for qubit in QubitSet(target)] + + +Gate.register_gate(I) + + +class X(Gate): + """Pauli-X gate.""" + + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["X"]) + + def to_ir(self, target: QubitSet): + return ir.X(target=target[0]) + + def to_matrix(self) -> np.ndarray: + return np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) + + @staticmethod + @circuit.subroutine(register=True) + def x(target: QubitSetInput) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + + Returns: + Iterable[Instruction]: Iterable of X instructions. + + Examples: + >>> circ = Circuit().x(0) + >>> circ = Circuit().x([0, 1, 2]) + """ + return [Instruction(Gate.X(), target=qubit) for qubit in QubitSet(target)] + + +Gate.register_gate(X) + + +class Y(Gate): + """Pauli-Y gate.""" + + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["Y"]) + + def to_ir(self, target: QubitSet): + return ir.Y(target=target[0]) + + def to_matrix(self) -> np.ndarray: + return np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) + + @staticmethod + @circuit.subroutine(register=True) + def y(target: QubitSetInput) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + + Returns: + Iterable[Instruction]: Iterable of Y instructions. + + Examples: + >>> circ = Circuit().y(0) + >>> circ = Circuit().y([0, 1, 2]) + """ + return [Instruction(Gate.Y(), target=qubit) for qubit in QubitSet(target)] + + +Gate.register_gate(Y) + + +class Z(Gate): + """Pauli-Z gate.""" + + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["Z"]) + + def to_ir(self, target: QubitSet): + return ir.Z(target=target[0]) + + def to_matrix(self) -> np.ndarray: + return np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) + + @staticmethod + @circuit.subroutine(register=True) + def z(target: QubitSetInput) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + + Returns: + Iterable[Instruction]: Iterable of Z instructions. + + Examples: + >>> circ = Circuit().z(0) + >>> circ = Circuit().z([0, 1, 2]) + """ + return [Instruction(Gate.Z(), target=qubit) for qubit in QubitSet(target)] + + +Gate.register_gate(Z) + + +class S(Gate): + """S gate.""" + + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["S"]) + + def to_ir(self, target: QubitSet): + return ir.S(target=target[0]) + + def to_matrix(self) -> np.ndarray: + + return np.array([[1.0, 0.0], [0.0, 1.0j]], dtype=complex) + + @staticmethod + @circuit.subroutine(register=True) + def s(target: QubitSetInput) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + + Returns: + Iterable[Instruction]: Iterable of S instructions. + + Examples: + >>> circ = Circuit().s(0) + >>> circ = Circuit().s([0, 1, 2]) + """ + return [Instruction(Gate.S(), target=qubit) for qubit in QubitSet(target)] + + +Gate.register_gate(S) + + +class Si(Gate): + """Conjugate transpose of S gate.""" + + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["Si"]) + + def to_ir(self, target: QubitSet): + return ir.Si(target=target[0]) + + def to_matrix(self) -> np.ndarray: + return np.array([[1, 0], [0, -1j]], dtype=complex) + + @staticmethod + @circuit.subroutine(register=True) + def si(target: QubitSetInput) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + + Returns: + Iterable[Instruction]: Iterable of Si instructions. + + Examples: + >>> circ = Circuit().si(0) + >>> circ = Circuit().si([0, 1, 2]) + """ + return [Instruction(Gate.Si(), target=qubit) for qubit in QubitSet(target)] + + +Gate.register_gate(Si) + + +class T(Gate): + """T gate.""" + + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["T"]) + + def to_ir(self, target: QubitSet): + return ir.T(target=target[0]) + + def to_matrix(self) -> np.ndarray: + return np.array([[1.0, 0.0], [0.0, np.exp(1j * np.pi / 4)]], dtype=complex) + + @staticmethod + @circuit.subroutine(register=True) + def t(target: QubitSetInput) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + + Returns: + Iterable[Instruction]: Iterable of T instructions. + + Examples: + >>> circ = Circuit().t(0) + >>> circ = Circuit().t([0, 1, 2]) + """ + return [Instruction(Gate.T(), target=qubit) for qubit in QubitSet(target)] + + +Gate.register_gate(T) + + +class Ti(Gate): + """Conjugate transpose of T gate.""" + + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["Ti"]) + + def to_ir(self, target: QubitSet): + return ir.Ti(target=target[0]) + + def to_matrix(self) -> np.ndarray: + return np.array([[1.0, 0.0], [0.0, np.exp(-1j * np.pi / 4)]], dtype=complex) + + @staticmethod + @circuit.subroutine(register=True) + def ti(target: QubitSetInput) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + + Returns: + Iterable[Instruction]: Iterable of Ti instructions. + + Examples: + >>> circ = Circuit().ti(0) + >>> circ = Circuit().ti([0, 1, 2]) + """ + return [Instruction(Gate.Ti(), target=qubit) for qubit in QubitSet(target)] + + +Gate.register_gate(Ti) + + +class V(Gate): + """Square root of not gate.""" + + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["V"]) + + def to_ir(self, target: QubitSet): + return ir.V(target=target[0]) + + def to_matrix(self) -> np.ndarray: + return np.array([[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]], dtype=complex) + + @staticmethod + @circuit.subroutine(register=True) + def v(target: QubitSetInput) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + + Returns: + Iterable[Instruction]: Iterable of V instructions. + + Examples: + >>> circ = Circuit().v(0) + >>> circ = Circuit().v([0, 1, 2]) + """ + return [Instruction(Gate.V(), target=qubit) for qubit in QubitSet(target)] + + +Gate.register_gate(V) + + +class Vi(Gate): + """Conjugate transpose of square root of not gate.""" + + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["Vi"]) + + def to_ir(self, target: QubitSet): + return ir.Vi(target=target[0]) + + def to_matrix(self) -> np.ndarray: + return np.array(([[0.5 - 0.5j, 0.5 + 0.5j], [0.5 + 0.5j, 0.5 - 0.5j]]), dtype=complex) + + @staticmethod + @circuit.subroutine(register=True) + def vi(target: QubitSetInput) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + + Returns: + Iterable[Instruction]: Iterable of Vi instructions. + + Examples: + >>> circ = Circuit().vi(0) + >>> circ = Circuit().vi([0, 1, 2]) + """ + return [Instruction(Gate.Vi(), target=qubit) for qubit in QubitSet(target)] + + +Gate.register_gate(Vi) + + +# Single qubit gates with rotation # + + +class Rx(AngledGate): + """X-axis rotation gate. + + Args: + angle (float): angle in radians. + """ + + def __init__(self, angle: float): + super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Rx"]) + + def to_ir(self, target: QubitSet) -> np.ndarray: + return ir.Rx(target=target[0], angle=self.angle) + + def to_matrix(self): + cos = np.cos(self.angle / 2) + sin = np.sin(self.angle / 2) + return np.array([[cos, -1j * sin], [-1j * sin, cos]], dtype=complex) + + @staticmethod + @circuit.subroutine(register=True) + def rx(target: QubitInput, angle: float) -> Instruction: + """Registers this function into the circuit class. + + Args: + target (Qubit or int): Target qubit index. + angle (float): Angle in radians. + + Returns: + Instruction: Rx instruction. + + Examples: + >>> circ = Circuit().rx(0, 0.15) + """ + return [Instruction(Gate.Rx(angle), target=qubit) for qubit in QubitSet(target)] + + +Gate.register_gate(Rx) + + +class Ry(AngledGate): + """Y-axis rotation gate. + + Args: + angle (float): angle in radians. + """ + + def __init__(self, angle: float): + super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Ry"]) + + def to_ir(self, target: QubitSet) -> np.ndarray: + return ir.Ry(target=target[0], angle=self.angle) + + def to_matrix(self): + cos = np.cos(self.angle / 2) + sin = np.sin(self.angle / 2) + return np.array([[cos, -sin], [+sin, cos]], dtype=complex) + + @staticmethod + @circuit.subroutine(register=True) + def ry(target: QubitInput, angle: float) -> Instruction: + """Registers this function into the circuit class. + + Args: + target (Qubit or int): Target qubit index. + angle (float): Angle in radians. + + Returns: + Instruction: Ry instruction. + + Examples: + >>> circ = Circuit().ry(0, 0.15) + """ + return [Instruction(Gate.Ry(angle), target=qubit) for qubit in QubitSet(target)] + + +Gate.register_gate(Ry) + + +class Rz(AngledGate): + """Z-axis rotation gate. + + Args: + angle (float): angle in radians. + """ + + def __init__(self, angle: float): + super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Rz"]) + + def to_ir(self, target: QubitSet) -> np.ndarray: + return ir.Rz(target=target[0], angle=self.angle) + + def to_matrix(self): + return np.array( + [[np.exp(-1j * self.angle / 2), 0], [0, np.exp(1j * self.angle / 2)]], dtype=complex + ) + + @staticmethod + @circuit.subroutine(register=True) + def rz(target: QubitInput, angle: float) -> Instruction: + """Registers this function into the circuit class. + + Args: + target (Qubit or int): Target qubit index. + angle (float): Angle in radians. + + Returns: + Instruction: Rz instruction. + + Examples: + >>> circ = Circuit().rz(0, 0.15) + """ + return [Instruction(Gate.Rz(angle), target=qubit) for qubit in QubitSet(target)] + + +Gate.register_gate(Rz) + + +class PhaseShift(AngledGate): + """Phase shift gate. + + Args: + angle (float): angle in radians. + """ + + def __init__(self, angle: float): + super().__init__(angle=angle, qubit_count=1, ascii_symbols=["PHASE"]) + + def to_ir(self, target: QubitSet) -> np.ndarray: + return ir.PhaseShift(target=target[0], angle=self.angle) + + def to_matrix(self): + return np.array([[1.0, 0.0], [0.0, np.exp(1j * self.angle)]], dtype=complex) + + @staticmethod + @circuit.subroutine(register=True) + def phaseshift(target: QubitInput, angle: float) -> Instruction: + """Registers this function into the circuit class. + + Args: + target (Qubit or int): Target qubit index. + angle (float): Angle in radians. + + Returns: + Instruction: PhaseShift instruction. + + Examples: + >>> circ = Circuit().phaseshift(0, 0.15) + """ + return [Instruction(Gate.PhaseShift(angle), target=qubit) for qubit in QubitSet(target)] + + +Gate.register_gate(PhaseShift) + + +# Two qubit gates # + + +class CNot(Gate): + """Controlled NOT gate.""" + + def __init__(self): + super().__init__(qubit_count=2, ascii_symbols=["C", "X"]) + + def to_ir(self, target: QubitSet): + return ir.CNot(control=target[0], target=target[1]) + + def to_matrix(self): + return np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0], + ], + dtype=complex, + ) + + @staticmethod + @circuit.subroutine(register=True) + def cnot(control: QubitInput, target: QubitInput) -> Instruction: + """Registers this function into the circuit class. + + Args: + control (Qubit or int): Control qubit index. + target (Qubit or int): Target qubit index. + + Returns: + Instruction: CNot instruction. + + Examples: + >>> circ = Circuit().cnot(0, 1) + """ + return Instruction(Gate.CNot(), target=[control, target]) + + +Gate.register_gate(CNot) + + +class Swap(Gate): + """Swap gate.""" + + def __init__(self): + super().__init__(qubit_count=2, ascii_symbols=["SWAP", "SWAP"]) + + def to_ir(self, target: QubitSet): + return ir.Swap(targets=[target[0], target[1]]) + + def to_matrix(self): + return np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + ], + dtype=complex, + ) + + @staticmethod + @circuit.subroutine(register=True) + def swap(targets: QubitSet) -> Instruction: + """Registers this function into the circuit class. + + Args: + targets (QubitSet): Target qubit indices. + + Returns: + Instruction: Swap instruction. + + Examples: + >>> circ = Circuit().swap(0, 1) + """ + return Instruction(Gate.Swap(), target=targets) + + +Gate.register_gate(Swap) + + +class ISwap(Gate): + """ISwap gate.""" + + def __init__(self): + super().__init__(qubit_count=2, ascii_symbols=["ISWAP", "ISWAP"]) + + def to_ir(self, target: QubitSet): + return ir.ISwap(targets=[target[0], target[1]]) + + def to_matrix(self): + return np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 1.0j, 0.0], + [0.0, 1.0j, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + ], + dtype=complex, + ) + + @staticmethod + @circuit.subroutine(register=True) + def iswap(targets: QubitSet) -> Instruction: + """Registers this function into the circuit class. + + Args: + targets (QubitSet): Target qubit indices. + + Returns: + Instruction: ISwap instruction. + + Examples: + >>> circ = Circuit().iswap(0, 1) + """ + return Instruction(Gate.ISwap(), target=targets) + + +Gate.register_gate(ISwap) + + +class PSwap(AngledGate): + """PSwap gate. + + Args: + angle (float): angle in radians. + """ + + def __init__(self, angle: float): + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["PSWAP", "PSWAP"]) + + def to_ir(self, target: QubitSet): + return ir.PSwap(targets=[target[0], target[1]], angle=self.angle) + + def to_matrix(self): + return np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, np.exp(1j * self.angle), 0.0], + [0.0, np.exp(1j * self.angle), 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + ], + dtype=complex, + ) + + @staticmethod + @circuit.subroutine(register=True) + def pswap(targets: QubitSet, angle: float) -> Instruction: + """Registers this function into the circuit class. + + Args: + targets (Qubit or int): Target qubit indices. + + Returns: + Instruction: PSwap instruction. + + Examples: + >>> circ = Circuit().pswap(0, 1, 0.15) + """ + return Instruction(Gate.PSwap(angle), target=targets) + + +Gate.register_gate(PSwap) + + +class CPhaseShift(AngledGate): + """Controlled phase shift gate. + + Args: + angle (float): angle in radians. + """ + + def __init__(self, angle: float): + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE"]) + + def to_ir(self, target: QubitSet) -> np.ndarray: + return ir.CPhaseShift(control=target[0], target=target[1], angle=self.angle) + + def to_matrix(self): + return np.diag([1.0, 1.0, 1.0, np.exp(1j * self.angle)]) + + @staticmethod + @circuit.subroutine(register=True) + def cphaseshift(control: QubitInput, target: QubitInput, angle: float) -> Instruction: + """Registers this function into the circuit class. + + Args: + control (Qubit or int): Control qubit index. + target (Qubit or int): Target qubit index. + angle (float): Angle in radians. + + Returns: + Instruction: CPhaseShift instruction. + + Examples: + >>> circ = Circuit().cphaseshift(0, 1, 0.15) + """ + return Instruction(Gate.CPhaseShift(angle), target=[control, target]) + + +Gate.register_gate(CPhaseShift) + + +class CPhaseShift00(AngledGate): + """Controlled phase shift gate for phasing the \\|00> state. + + Args: + angle (float): angle in radians. + """ + + def __init__(self, angle: float): + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE00"]) + + def to_ir(self, target: QubitSet) -> np.ndarray: + return ir.CPhaseShift00(control=target[0], target=target[1], angle=self.angle) + + def to_matrix(self): + return np.diag([np.exp(1j * self.angle), 1.0, 1.0, 1.0]) + + @staticmethod + @circuit.subroutine(register=True) + def cphaseshift00(control: QubitInput, target: QubitInput, angle: float) -> Instruction: + """Registers this function into the circuit class. + + Args: + control (Qubit or int): Control qubit index. + target (Qubit or int): Target qubit index. + angle (float): Angle in radians. + + Returns: + Instruction: CPhaseShift00 instruction. + + Examples: + >>> circ = Circuit().cphaseshift00(0, 1, 0.15) + """ + return Instruction(Gate.CPhaseShift00(angle), target=[control, target]) + + +Gate.register_gate(CPhaseShift00) + + +class CPhaseShift01(AngledGate): + """Controlled phase shift gate for phasing the \\|01> state. + + Args: + angle (float): angle in radians. + """ + + def __init__(self, angle: float): + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE01"]) + + def to_ir(self, target: QubitSet) -> np.ndarray: + return ir.CPhaseShift01(control=target[0], target=target[1], angle=self.angle) + + def to_matrix(self): + return np.diag([1.0, np.exp(1j * self.angle), 1.0, 1.0]) + + @staticmethod + @circuit.subroutine(register=True) + def cphaseshift01(control: QubitInput, target: QubitInput, angle: float) -> Instruction: + """Registers this function into the circuit class. + + Args: + control (Qubit or int): Control qubit index. + target (Qubit or int): Target qubit index. + angle (float): Angle in radians. + + Returns: + Instruction: CPhaseShift01 instruction. + + Examples: + >>> circ = Circuit().cphaseshift01(0, 1, 0.15) + """ + return Instruction(Gate.CPhaseShift01(angle), target=[control, target]) + + +Gate.register_gate(CPhaseShift01) + + +class CPhaseShift10(AngledGate): + """Controlled phase shift gate for phasing the \\|10> state. + + Args: + angle (float): angle in radians. + """ + + def __init__(self, angle: float): + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE10"]) + + def to_ir(self, target: QubitSet) -> np.ndarray: + return ir.CPhaseShift10(control=target[0], target=target[1], angle=self.angle) + + def to_matrix(self): + return np.diag([1.0, 1.0, np.exp(1j * self.angle), 1.0]) + + @staticmethod + @circuit.subroutine(register=True) + def cphaseshift10(control: QubitInput, target: QubitInput, angle: float) -> Instruction: + """Registers this function into the circuit class. + + Args: + control (Qubit or int): Control qubit index. + target (Qubit or int): Target qubit index. + angle (float): Angle in radians. + + Returns: + Instruction: CPhaseShift10 instruction. + + Examples: + >>> circ = Circuit().cphaseshift10(0, 1, 0.15) + """ + return Instruction(Gate.CPhaseShift10(angle), target=[control, target]) + + +Gate.register_gate(CPhaseShift10) + + +class CY(Gate): + """Controlled Pauli-Y gate.""" + + def __init__(self): + super().__init__(qubit_count=2, ascii_symbols=["C", "Y"]) + + def to_ir(self, target: QubitSet): + return ir.CY(control=target[0], target=target[1]) + + def to_matrix(self): + return np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 0.0, -1.0j], + [0.0, 0.0, +1.0j, 0.0], + ], + dtype=complex, + ) + + @staticmethod + @circuit.subroutine(register=True) + def cy(control: QubitInput, target: QubitInput) -> Instruction: + """Registers this function into the circuit class. + + Args: + control (Qubit or int): Control qubit index. + target (Qubit or int): Target qubit index. + + Returns: + Instruction: CY instruction. + + Examples: + >>> circ = Circuit().cy(0, 1) + """ + return Instruction(Gate.CY(), target=[control, target]) + + +Gate.register_gate(CY) + + +class CZ(Gate): + """Controlled Pauli-Z gate.""" + + def __init__(self): + super().__init__(qubit_count=2, ascii_symbols=["C", "Z"]) + + def to_ir(self, target: QubitSet): + return ir.CZ(control=target[0], target=target[1]) + + def to_matrix(self): + return np.diag([1.0, 1.0, 1.0, -1.0]) + + @staticmethod + @circuit.subroutine(register=True) + def cz(control: QubitInput, target: QubitInput) -> Instruction: + """Registers this function into the circuit class. + + Args: + control (Qubit or int): Control qubit index. + target (Qubit or int): Target qubit index. + + Returns: + Instruction: CZ instruction. + + Examples: + >>> circ = Circuit().cz(0, 1) + """ + return Instruction(Gate.CZ(), target=[control, target]) + + +Gate.register_gate(CZ) + + +class XX(AngledGate): + """Ising XX coupling gate. + + Args: + angle (float): angle in radians. + """ + + def __init__(self, angle: float): + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["XX", "XX"]) + + def to_ir(self, target: QubitSet): + return ir.XX(targets=[target[0], target[1]], angle=self.angle) + + def to_matrix(self): + return (1 / math.sqrt(2)) * np.array( + [ + [1.0, 0.0, 0.0, -1.0j * np.exp(1.0j * self.angle)], + [0.0, 1.0, -1.0j, 0.0], + [0.0, -1.0j, 1.0, 0.0], + [-1.0j * np.exp(-1.0j * self.angle), 0.0, 0.0, 1.0], + ], + dtype=complex, + ) + + @staticmethod + @circuit.subroutine(register=True) + def xx(targets: QubitSet, angle: float) -> Instruction: + """Registers this function into the circuit class. + + Args: + targets (Qubit or int): Target qubit indices. + angle (float): Angle in radians. + + Returns: + Instruction: XX instruction. + + Examples: + >>> circ = Circuit().xx(0, 1, 0.15) + """ + return Instruction(Gate.XX(angle), target=targets) + + +Gate.register_gate(XX) + + +class YY(AngledGate): + """Ising YY coupling gate. + + Args: + angle (float): angle in radians. + """ + + def __init__(self, angle: float): + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["YY", "YY"]) + + def to_ir(self, target: QubitSet): + return ir.YY(targets=[target[0], target[1]], angle=self.angle) + + def to_matrix(self): + cos = np.cos(self.angle) + sin = np.sin(self.angle) + return np.array( + [ + [cos, 0.0, 0.0, 1.0j * sin], + [0.0, cos, -1.0j * sin, 0.0], + [0.0, -1.0j * sin, cos, 0.0], + [1.0j * sin, 0.0, 0.0, cos], + ], + dtype=complex, + ) + + @staticmethod + @circuit.subroutine(register=True) + def yy(targets: QubitSet, angle: float) -> Instruction: + """Registers this function into the circuit class. + + Args: + targets (Qubit or int): Target qubit indices. + angle (float): Angle in radians. + + Returns: + Instruction: YY instruction. + + Examples: + >>> circ = Circuit().yy(0, 1, 0.15) + """ + return Instruction(Gate.YY(angle), target=targets) + + +Gate.register_gate(YY) + + +class ZZ(AngledGate): + """Ising ZZ coupling gate. + + Args: + angle (float): angle in radians. + """ + + def __init__(self, angle: float): + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["ZZ", "ZZ"]) + + def to_ir(self, target: QubitSet): + return ir.ZZ(targets=[target[0], target[1]], angle=self.angle) + + def to_matrix(self): + return np.array( + [ + [np.exp(1j * (self.angle / 2)), 0.0, 0.0, 0.0], + [0.0, np.exp(-1j * (self.angle / 2)), 0.0, 0.0], + [0.0, 0.0, np.exp(-1j * (self.angle / 2)), 0.0], + [0.0, 0.0, 0.0, np.exp(1j * (self.angle / 2))], + ], + dtype=complex, + ) + + @staticmethod + @circuit.subroutine(register=True) + def zz(targets: QubitSet, angle: float) -> Instruction: + """Registers this function into the circuit class. + + Args: + targets (Qubit or int): Target qubit indices. + angle (float): Angle in radians. + + Returns: + Instruction: ZZ instruction. + + Examples: + >>> circ = Circuit().zz(0, 1, 0.15) + """ + return Instruction(Gate.ZZ(angle), target=targets) + + +Gate.register_gate(ZZ) + + +# Three qubit gates # + + +class CCNot(Gate): + """CCNOT gate or Toffoli gate.""" + + def __init__(self): + super().__init__(qubit_count=3, ascii_symbols=["C", "C", "X"]) + + def to_ir(self, target: QubitSet): + return ir.CCNot(controls=[target[0], target[1]], target=target[2]) + + def to_matrix(self): + return np.array( + [ + [1, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 0, 1, 0], + ], + dtype=complex, + ) + + @staticmethod + @circuit.subroutine(register=True) + def ccnot(controls: QubitSet, target: QubitInput) -> Instruction: + """Registers this function into the circuit class. + + Args: + controls (QubitSet): Control qubit indices. + target (Qubit or int): Target qubit index. + + Returns: + Instruction: CCNot instruction. + + Examples: + >>> circ = Circuit().ccnot(controls=[0, 1], target=2) + """ + return Instruction(Gate.CCNot(), target=[controls[0], controls[1], target]) + + +Gate.register_gate(CCNot) + + +class CSwap(Gate): + """Controlled Swap gate.""" + + def __init__(self): + super().__init__(qubit_count=3, ascii_symbols=["C", "SWAP", "SWAP"]) + + def to_ir(self, target: QubitSet): + return ir.CSwap(control=target[0], targets=[target[1], target[2]]) + + def to_matrix(self): + return np.array( + [ + [1, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1], + ], + dtype=complex, + ) + + @staticmethod + @circuit.subroutine(register=True) + def cswap(control: QubitInput, targets: QubitSet) -> Instruction: + """Registers this function into the circuit class. + + Args: + control (Qubit or int): Control qubit index + targets (QubitSet): Target qubit indices. + + Returns: + Instruction: CSwap instruction. + + Examples: + >>> circ = Circuit().cswap(0, 1, 2) + """ + return Instruction(Gate.CSwap(), target=[control, targets[0], targets[1]]) + + +Gate.register_gate(CSwap) diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py new file mode 100644 index 00000000..c34da1ba --- /dev/null +++ b/src/braket/circuits/instruction.py @@ -0,0 +1,127 @@ +# Copyright 2019-2019 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 typing import Dict + +from braket.circuits.operator import Operator +from braket.circuits.qubit import QubitInput +from braket.circuits.qubit_set import QubitSet, QubitSetInput + +# TODO: Add parameters support +# TODO: Rename to QuantumInstruction, and change Operator to Gate, then rename "target" to "qubits" + + +class Instruction: + """ + Instruction is a quantum directive that describes the task to be performed on a quantum device. + """ + + def __init__(self, operator: Operator, target: QubitSetInput): + """ + Args: + operator (Operator): Operator for the instruction. + target (int, Qubit, or iterable of int / Qubit): Target qubits the operator is + applied to. + + Raises: + ValueError: If `operator` is empty or any integer in `target` does not meet the Qubit + or QubitSet class requirements. + TypeError: If a Qubit class cannot be constructed from `target` due to incorrect typing + + Examples: + >>> Instruction(Gate.CNot(), [0, 1]) + Instruction('operator': CNOT, 'target': QubitSet(Qubit(0), Qubit(1))) + >>> instr = Instruction(Gate.CNot()), QubitSet([0, 1])]) + Instruction('operator': CNOT, 'target': QubitSet(Qubit(0), Qubit(1))) + >>> instr = Instruction(Gate.H(), 0) + Instruction('operator': H, 'target': QubitSet(Qubit(0),)) + >>> instr = Instruction(Gate.Rx(0.12), 0) + Instruction('operator': Rx, 'target': QubitSet(Qubit(0),)) + """ + if not operator: + raise ValueError("Operator cannot be empty") + self._operator = operator + self._target = QubitSet(target) + + @property + def operator(self) -> Operator: + """Operator: Operator for the instruction, e.g. `Gate`.""" + return self._operator + + @property + def target(self) -> QubitSet: + """ + QubitSet: Target qubits the operator is applied to. + + Note: + Don't mutate this property, any mutations can have unexpected consequences. + """ + return self._target + + def to_ir(self): + """ + Converts the operator into the canonical intermediate representation. + If the operator is sent over the wire then this method is called before it is sent. + """ + return self.operator.to_ir(self.target) + + def copy( + self, target_mapping: Dict[QubitInput, QubitInput] = {}, target: QubitSetInput = None + ) -> "Instruction": + """ + Return a shallow copy of the instruction. + + Note: + If `target_mapping` is supplied then `self.target` will be mapped to the specified + qubits. This is useful for when wanting to apply an instruction to a circuit but want + to change the target qubits. + + Args: + target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of + qubit mappings to apply to the target. Key is the qubit in this `target` and the + value is what the key will be changed to. Default = {}. + target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the new + instruction. + + Returns: + Instruction: Shallow copy of the instruction. + + Raises: + TypeError: If both `target_mapping` and `target` are supplied. + + Examples: + >>> instr = Instruction(Gate.H(), 0) + >>> new_instr = instr.copy() + >>> new_instr.target + QubitSet(Qubit(0)) + >>> new_instr = instr.copy(target_mapping={0: 5}) + >>> new_instr.target + QubitSet(Qubit(5)) + >>> new_instr = instr.copy(target=[5]) + >>> new_instr.target + QubitSet(Qubit(5)) + """ + if target_mapping and target is not None: + raise TypeError("Both 'target_mapping' and 'target' cannot be supplied.") + elif target is not None: + return Instruction(self.operator, target) + else: + return Instruction(self.operator, self.target.map(target_mapping)) + + def __repr__(self): + return f"Instruction('operator': {self.operator}, 'target': {self.target})" + + def __eq__(self, other): + if isinstance(other, Instruction): + return (self.operator, self.target) == (other.operator, other.target) + return NotImplemented diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py new file mode 100644 index 00000000..c553b00f --- /dev/null +++ b/src/braket/circuits/moments.py @@ -0,0 +1,213 @@ +# Copyright 2019-2019 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 typing import ( + Dict, + ItemsView, + Iterable, + KeysView, + List, + Mapping, + NamedTuple, + OrderedDict, + ValuesView, +) + +from braket.circuits.instruction import Instruction +from braket.circuits.qubit import Qubit +from braket.circuits.qubit_set import QubitSet + + +class MomentsKey(NamedTuple): + """Key of the Moments mapping.""" + + time: int + qubits: QubitSet + + +class Moments(Mapping[MomentsKey, Instruction]): + """ + An ordered mapping of `MomentsKey` to `Instruction`. This is the core data structure that + contains instructions, the ordering of which they are inserted, and the time slices that they + occur. `Moments` implements `Mapping` and therefore offers the same experience as a read-only + dictionary and is mutable only through the `add()` method. + + This data structure is useful for anything that needs to know about the dependency of + instructions such as printing or optimizing circuit structure before sending to the quantum + device. The original insertion order is preserved and can be retrieved via the `values()` + method. + + Args: + instructions (Iterable[Instruction], optional): Instructions to initialize self with. + Default = []. + + Examples: + >>> moments = Moments() + >>> moments.add([Instruction(Gate.H, 0), Instruction(Gate.CNot, [0, 1])]) + >>> moments.add([Instruction(Gate.H, 0), Instruction(Gate.H, 1)]) + >>> for i, item in enumerate(moments.items()): + ... 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)])) + Item 1 + Key: MomentsKey(time=1, qubits=QubitSet([Qubit(0), Qubit(1)])) + Value: Instruction('operator': CNOT, 'target': QubitSet([Qubit(0), Qubit(1)])) + Item 2 + Key: MomentsKey(time=2, qubits=QubitSet([Qubit(0)])) + Value: Instruction('operator': H, 'target': QubitSet([Qubit(0)])) + Item 3 + Key: MomentsKey(time=2, qubits=QubitSet([Qubit(1)])) + Value: Instruction('operator': H, 'target': QubitSet([Qubit(1)])) + """ + + def __init__(self, instructions: Iterable[Instruction] = []): + self._moments: OrderedDict[MomentsKey, Instruction] = OrderedDict() + self._max_times: Dict[Qubit, int] = {} + self._qubits = QubitSet() + self._depth = 0 + + self.add(instructions) + + @property + def depth(self) -> int: + """int: Get the depth of self, i.e. number of time slices.""" + return self._depth + + @property + def qubit_count(self) -> int: + """int: Get the number of qubits used across all the instructions.""" + return len(self._qubits) + + @property + def qubits(self) -> QubitSet: + """ + QubitSet: Get the qubits used across all the instructions. The order of qubits is based + on the order the instructions were added. + + Note: + Don't mutate this object, any changes may impact the behavior of this class and / or + consumers. If you need to mutate this than copy it via `QubitSet(moments.qubits())`. + """ + return self._qubits + + def time_slices(self) -> Dict[int, List[Instruction]]: + """ + Get instructions keyed by time. + + Returns: + Dict[int, List[Instruction]]: Key is the time and value is a list of instructions that + occur at that moment in time. The order of instructions is in no particular order. + + Note: + This is a computed result over self and can be freely mutated. Be aware that this + is re-computed every call and has a computational runtime O(N) where N is the number + of instructions in self. + """ + + time_slices = {} + for key, instruction in self._moments.items(): + instructions = time_slices.get(key.time, []) + instructions.append(instruction) + time_slices[key.time] = instructions + + return time_slices + + def add(self, instructions: Iterable[Instruction]) -> None: + """ + Add instructions to self. + + Args: + instructions (Iterable[Instruction]): Instructions to add to self. The instruction will + be added to the max time slice that can fit the instruction. + """ + for instruction in instructions: + self._add(instruction) + + def _add(self, instruction: Instruction) -> None: + qubit_range = range(min(instruction.target), max(instruction.target) + 1) + time = max([self._max_time_for_qubit(qubit) for qubit in qubit_range]) + 1 + + # Mark all qubits in the range to avoid another gate being placed in the overlap. + # For example CNOT(0, 5) would draw a line from 0 to 5 and therefore should prevent + # another instruction using those qubits in that time moment. + for qubit in qubit_range: + self._max_times[qubit] = max(time, self._max_time_for_qubit(qubit)) + + self._moments[MomentsKey(time, instruction.target)] = instruction + self._qubits.update(instruction.target) + self._depth = max(self._depth, time + 1) + + 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 + # + + def keys(self) -> KeysView[MomentsKey]: + """Return a view of self's keys.""" + return self._moments.keys() + + def items(self) -> ItemsView[MomentsKey, Instruction]: + """Return a view of self's (key, instruction).""" + return self._moments.items() + + def values(self) -> ValuesView[Instruction]: + """Return a view of self's instructions.""" + return self._moments.values() + + def get(self, key: MomentsKey, default=None) -> Instruction: + """ + Get the instruction in self by key. + + Args: + key (MomentsKey): Key of the instruction to fetch. + default (Any, optional): Value to return if `key` not in moment. Default = None. + + Returns: + Instruction: moments[key] if key in moments else `default` is returned. + """ + return self._moments.get(key, default) + + def __getitem__(self, key): + return self._moments.__getitem__(key) + + def __iter__(self): + return self._moments.__iter__() + + def __len__(self): + return self._moments.__len__() + + def __contains__(self, item): + return self._moments.__contains__(item) + + def __eq__(self, other): + if isinstance(other, Moments): + return (self._moments) == (other._moments) + return NotImplemented + + def __ne__(self, other): + result = self.__eq__(other) + if result is not NotImplemented: + return not result + return NotImplemented + + def __repr__(self): + return self._moments.__repr__() + + def __str__(self): + return self._moments.__str__() diff --git a/src/braket/circuits/operator.py b/src/braket/circuits/operator.py new file mode 100644 index 00000000..bd60bb57 --- /dev/null +++ b/src/braket/circuits/operator.py @@ -0,0 +1,33 @@ +# Copyright 2019-2019 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 abc import ABC, abstractmethod + + +class Operator(ABC): + """A quantum operator is the abstract definition of an operation for a quantum device.""" + + @property + @abstractmethod + def name(self) -> str: + """str: The operator name.""" + + @abstractmethod + def to_ir(self, *args, **kwargs): + """ + Converts the operator into the canonical intermediate representation. + If the operator is sent over the wire then this method is called before it is sent. + + Args: + target (QubitSet): Target qubits the operator is applied to. + """ diff --git a/src/braket/circuits/qubit.py b/src/braket/circuits/qubit.py new file mode 100644 index 00000000..99fca444 --- /dev/null +++ b/src/braket/circuits/qubit.py @@ -0,0 +1,60 @@ +# Copyright 2019-2019 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 typing import TypeVar + +QubitInput = TypeVar("QubitInput", "Qubit", int) + + +class Qubit(int): + """ + 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): + """ + Args: + index (int): Index of the qubit. + + Raises: + ValueError: If `index` is less than zero. + + Examples: + >>> Qubit(0) + >>> Qubit(1) + """ + if index < 0: + raise ValueError(f"Supplied index, {index}, cannot be less than zero.") + return super().__new__(cls, index) + + def __repr__(self): + return f"Qubit({super().__repr__()})" + + def __str__(self): + return self.__repr__() + + @staticmethod + def new(qubit: QubitInput) -> "Qubit": + """ + Helper constructor, if input is a Qubit then it returns back the same value + else a new Qubit is constructed. + + Args: + qubit (int or Qubit): Qubit index. If type == Qubit then the `qubit` is returned. + """ + + if isinstance(qubit, Qubit): + return qubit + else: + return Qubit(qubit) diff --git a/src/braket/circuits/qubit_set.py b/src/braket/circuits/qubit_set.py new file mode 100644 index 00000000..d8087ccb --- /dev/null +++ b/src/braket/circuits/qubit_set.py @@ -0,0 +1,89 @@ +# Copyright 2019-2019 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 typing import Dict, Iterable, TypeVar + +from boltons.setutils import IndexedSet +from braket.circuits.qubit import Qubit, QubitInput + +QubitSetInput = TypeVar("QubitSetInput", QubitInput, Iterable[QubitInput]) + + +class QubitSet(IndexedSet): + """ + An ordered unique set of quantum bits. + + Note: + QubitSet implements __hash__() but is a mutable object, therefore be careful when + mutating this object. + """ + + def __init__(self, qubits: QubitSetInput = []): + """ + Args: + qubits (int, Qubit, or iterable of int / Qubit): Qubits to be part of the QubitSet. + + Examples: + >>> 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) + """ + + def _flatten(other): + if isinstance(other, Iterable): + for item in other: + yield from _flatten(item) + else: + yield other + + _qubits = [Qubit.new(qubit) for qubit in _flatten(qubits)] + 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`. + If this instance contains a qubit not in the `mapping` then that qubit is not modified. + + Args: + mapping (dictionary[int or Qubit, int or Qubit]): A dictionary of qubit mappings to + apply. Key is the qubit in this instance to be targeted and the value is what + the key will be changed to. + + Returns: + QubitSet: A new QubitSet with the `mapping` applied. + + Examples: + >>> qubits = QubitSet([0, 1]) + >>> mapping = {0: 10, Qubit(1): Qubit(11)} + >>> qubits.map(mapping) + QubitSet([Qubit(10), Qubit(11)]) + """ + + new_qubits = [mapping.get(qubit, qubit) for qubit in self] + + return QubitSet(new_qubits) + + def __hash__(self): + return hash(tuple(self)) diff --git a/src/braket/devices/__init__.py b/src/braket/devices/__init__.py new file mode 100644 index 00000000..9ef21e39 --- /dev/null +++ b/src/braket/devices/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2019-2019 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.devices.device import Device # noqa: F401 +from braket.devices.device_details import DeviceDetails # noqa: F401 diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py new file mode 100644 index 00000000..05d469d7 --- /dev/null +++ b/src/braket/devices/device.py @@ -0,0 +1,83 @@ +# Copyright 2019-2019 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 abc import ABC, abstractmethod +from typing import List + +from braket.tasks.quantum_task import QuantumTask + + +class Device(ABC): + """ + An abstraction over quantum devices which includes quantum computers and simulators. + + :param str name: name of quantum device + :param str status: status of quantum device + :param str status_reason: status reason of quantum device + :param List[str] supported_quantum_operations: supported quantum operations of quantum device + """ + + def __init__( + self, name: str, status: str, status_reason: str, supported_quantum_operations: List[str] + ): + self._name = name + self._status = status + self._status_reason = status_reason + self._supported_quantum_operations = supported_quantum_operations + + @abstractmethod + def run(self, circuit, shots: int) -> QuantumTask: + """ + Run a circuit on this quantum device. + + :param Circuit circuit: circuit to run on device + :param int shots: Number of shots to run circuit + :return: the created quantum task + :rtype: QuantumTask + """ + + @property + def name(self) -> str: + """ + Return name of Device + + :rtype: str + """ + return self._name + + @property + def status(self) -> str: + """ + Return status of Device + + :rtype: str + """ + return self._status + + @property + def status_reason(self) -> str: + """ + Return status reason of Device + + :rtype: str + """ + return self._status_reason + + @property + def supported_quantum_operations(self) -> List[str]: + """ + Return supported quantum operations of Device + + :rtype: List[str] + """ + return self._supported_quantum_operations diff --git a/src/braket/devices/device_details.py b/src/braket/devices/device_details.py new file mode 100644 index 00000000..6e21839f --- /dev/null +++ b/src/braket/devices/device_details.py @@ -0,0 +1,28 @@ +# Copyright 2019-2019 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 dataclasses import dataclass +from uuid import UUID + + +@dataclass +class DeviceDetails: + """ + Details of a quantum device. This class is intended to be initailized by a Device class. + """ + + # TODO: Add fields we need + + # Placeholder fields + id: UUID + qubit_count: int diff --git a/src/braket/ipython_utils.py b/src/braket/ipython_utils.py new file mode 100644 index 00000000..a223d2b7 --- /dev/null +++ b/src/braket/ipython_utils.py @@ -0,0 +1,39 @@ +# Copyright 2019-2019 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 sys + + +def running_in_jupyter(): + """ + Determine if running within Jupyter. + + Inspired by https://github.com/ipython/ipython/issues/11694 + + Returns: + 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"] + + 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 diff --git a/src/braket/tasks/__init__.py b/src/braket/tasks/__init__.py new file mode 100644 index 00000000..dd472399 --- /dev/null +++ b/src/braket/tasks/__init__.py @@ -0,0 +1,26 @@ +# Copyright 2019-2019 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 braket.ipython_utils as ipython_utils +from braket.tasks.quantum_task import QuantumTask # noqa: F401 +from braket.tasks.quantum_task_result import QuantumTaskResult # noqa: F401 + +# Apply nest_asyncio if currently running within Jupyter. This ensures anything that uses +# asyncio will run in Jupyter without any issues. +# +# Inspired by https://github.com/ipython/ipython/issues/11694 and +# https://github.com/jupyter/notebook/issues/3397#issuecomment-419386811 +if ipython_utils.running_in_jupyter(): + import nest_asyncio + + nest_asyncio.apply() diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py new file mode 100644 index 00000000..7d192dbf --- /dev/null +++ b/src/braket/tasks/quantum_task.py @@ -0,0 +1,45 @@ +# Copyright 2019-2019 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 asyncio +from abc import ABC, abstractmethod + +from braket.tasks.quantum_task_result import QuantumTaskResult + + +class QuantumTask(ABC): + """An abstraction over a quantum task on a quantum device.""" + + @property + @abstractmethod + def id(self) -> str: + """str: The task id.""" + + @abstractmethod + def cancel(self) -> None: + """Cancel the quantum task.""" + + @abstractmethod + def state(self) -> str: + """str: State of the quantum task""" + + @abstractmethod + def result(self) -> QuantumTaskResult: + """ + QuantumTaskResult: Get the quantum task result. Call async_result if you want the + result in an async way. + """ + + @abstractmethod + def async_result(self) -> asyncio.Task: + """asyncio.Task: Get the quantum task result asynchronously.""" diff --git a/src/braket/tasks/quantum_task_result.py b/src/braket/tasks/quantum_task_result.py new file mode 100644 index 00000000..2646a4ad --- /dev/null +++ b/src/braket/tasks/quantum_task_result.py @@ -0,0 +1,122 @@ +# Copyright 2019-2019 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 dataclasses import dataclass +from typing import Counter, Dict + +import numpy as np + + +@dataclass +class QuantumTaskResult: + """ + Result of a quantum task execution. This class is intended + to be initialized by a QuantumTask class. + + Args: + measurements (numpy.ndarray): 2d array - row is shot, column is qubit. + 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. + 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. + measurements_copied_from_device (bool): flag whether `measurements` were copied from device. + If false, `measurements` are calculated from device data. + measurement_counts_copied_from_device (bool): flag whether `measurement_counts` were copied + from device. If false, `measurement_counts` are calculated from device data. + measurement_probabilities_copied_from_device (bool): flag whether + `measurement_probabilities` were copied from device. If false, + `measurement_probabilities` are calculated from device data. + """ + + measurements: np.ndarray + measurement_counts: Counter + measurement_probabilities: Dict[str, float] + measurements_copied_from_device: bool + measurement_counts_copied_from_device: bool + measurement_probabilities_copied_from_device: bool + + def __eq__(self, other) -> bool: + if isinstance(other, QuantumTaskResult): + # __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 + # elements in the array are equal. + return (self.measurements == other.measurements).all() + return NotImplemented + + @staticmethod + def measurement_counts_from_measurements(measurements: np.ndarray) -> Counter: + """ + Creates measurement counts from measurements + + Args: + measurements (numpy.ndarray): 2d array - row is shot, 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 = [] + for j in range(len(measurements)): + bitstrings.append("".join([str(element) for element in measurements[j]])) + return Counter(bitstrings) + + @staticmethod + 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. + """ + measurement_probabilities = {} + shots = sum(measurement_counts.values()) + + for key, count in measurement_counts.items(): + measurement_probabilities[key] = count / shots + return measurement_probabilities + + @staticmethod + 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: + 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. + """ + measurements_list = [] + for bitstring in measurement_probabilities: + measurement = list(bitstring) + individual_measurement_list = [measurement] * int( + round(measurement_probabilities[bitstring] * shots) + ) + measurements_list.extend(individual_measurement_list) + return np.asarray(measurements_list, dtype=int) diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py new file mode 100644 index 00000000..ab3515e7 --- /dev/null +++ b/test/integ_tests/conftest.py @@ -0,0 +1,72 @@ +# Copyright 2019-2019 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 + +import boto3 +import pytest +from botocore.exceptions import ClientError +from braket.aws.aws_session import AwsSession + + +@pytest.fixture(scope="session") +def boto_session(): + profile_name = os.environ["AWS_PROFILE"] + return boto3.session.Session(profile_name=profile_name) + + +@pytest.fixture(scope="session") +def aws_session(boto_session): + return AwsSession(boto_session) + + +@pytest.fixture(scope="session") +def s3_resource(boto_session): + return boto_session.resource("s3") + + +@pytest.fixture(scope="session") +def s3_bucket(s3_resource, boto_session): + """Create / get S3 bucket for tests""" + + region_name = boto_session.region_name + account_id = boto_session.client("sts").get_caller_identity()["Account"] + bucket_name = f"braket-sdk-integ-tests-{account_id}" + bucket = s3_resource.Bucket(bucket_name) + + try: + bucket.create(ACL="private", CreateBucketConfiguration={"LocationConstraint": region_name}) + except ClientError as e: + if e.response["Error"]["Code"] == "BucketAlreadyOwnedByYou": + pass + else: + raise e + + return bucket_name + + +@pytest.fixture(scope="module") +def s3_prefix(): + """Returns the module path of the test, e.g. integ_tests/test_simulator_quantum_task""" + + # current test path, e.g. ... + # test/integ_tests/test_simulator_quantum_task.py::test_simulator_quantum_task (setup) + current_test_path = os.environ.get("PYTEST_CURRENT_TEST") + + # strip off the filename extension and test/ + return current_test_path.rsplit(".py")[0].replace("test/", "") + + +@pytest.fixture(scope="module") +def s3_destination_folder(s3_bucket, s3_prefix): + return AwsSession.S3DestinationFolder(s3_bucket, s3_prefix) diff --git a/test/integ_tests/test_aws_session_s3.py b/test/integ_tests/test_aws_session_s3.py new file mode 100644 index 00000000..3d009c19 --- /dev/null +++ b/test/integ_tests/test_aws_session_s3.py @@ -0,0 +1,51 @@ +# Copyright 2019-2019 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 pytest +from braket.aws import AwsQpuArns + +TEST_S3_OBJ_CONTENTS = { + "TaskMetadata": { + "Id": "UUID_blah", + "Status": "COMPLETED", + "BackendArn": AwsQpuArns.RIGETTI, + "CwLogGroupArn": "blah", + "Program": "....", + "CreatedAt": "02/12/22 21:23", + "UpdatedAt": "02/13/22 21:23", + } +} + + +@pytest.fixture() +def s3_key(s3_resource, s3_bucket, s3_prefix): + obj = s3_resource.Object(s3_bucket, f"{s3_prefix}/test_task_reading.json") + + try: + obj_body = obj.get()["Body"].read().decode("utf-8") + assert obj_body == json.dumps(TEST_S3_OBJ_CONTENTS) + except s3_resource.meta.client.exceptions.NoSuchKey: + # Put s3 object + obj.put(ACL="private", Body=json.dumps(TEST_S3_OBJ_CONTENTS, indent=4)) + except AssertionError: + # Put s3 object + obj.put(ACL="private", Body=json.dumps(TEST_S3_OBJ_CONTENTS, indent=4)) + + return obj.key + + +def test_retrieve_s3_object_body(aws_session, s3_bucket, s3_key): + obj_body = aws_session.retrieve_s3_object_body(s3_bucket, s3_key) + assert obj_body == json.dumps(TEST_S3_OBJ_CONTENTS, indent=4) diff --git a/test/integ_tests/test_common.py b/test/integ_tests/test_common.py new file mode 100644 index 00000000..0b878f56 --- /dev/null +++ b/test/integ_tests/test_common.py @@ -0,0 +1,2 @@ +TEST_QPU_ARN = "arn:aws:aqx:::qpu:aqx:integ_test_qpu" +TEST_SIMULATOR_ARN = "arn:aws:aqx:::quantum-simulator:aqx:integ_test_simulator" diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py new file mode 100644 index 00000000..3d87f048 --- /dev/null +++ b/test/integ_tests/test_device_creation.py @@ -0,0 +1,44 @@ +# Copyright 2019-2019 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.aws import AwsQpu, AwsQpuArns, AwsQuantumSimulator, AwsQuantumSimulatorArns + + +@pytest.mark.parametrize( + "qpu_arn,qpu_name", [(AwsQpuArns.RIGETTI, "Rigetti"), (AwsQpuArns.IONQ, "IonQ")] +) +def test_qpu_creation(qpu_arn, qpu_name, aws_session): + qpu = AwsQpu(qpu_arn, aws_session=aws_session) + assert qpu.arn == qpu_arn + assert qpu.name == qpu_name + + +def test_device_across_regions(aws_session): + # assert QPUs across different regions can be created using the same aws_session + AwsQpu(AwsQpuArns.RIGETTI, aws_session) + AwsQpu(AwsQpuArns.IONQ, aws_session) + + +@pytest.mark.parametrize( + "simulator_arn,simulator_name", + [ + (AwsQuantumSimulatorArns.QS1, "quantum-simulator-1"), + (AwsQuantumSimulatorArns.QS2, "quantum-simulator-2"), + (AwsQuantumSimulatorArns.QS3, "quantum-simulator-3"), + ], +) +def test_simulator_creation(simulator_arn, simulator_name, aws_session): + simulator = AwsQuantumSimulator(simulator_arn, aws_session=aws_session) + assert simulator.arn == simulator_arn + assert simulator.name == simulator_name diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py new file mode 100644 index 00000000..31ab54be --- /dev/null +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -0,0 +1,62 @@ +# Copyright 2019-2019 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.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns +from braket.circuits import Circuit + + +@pytest.mark.parametrize( + "simulator_arn", [AwsQuantumSimulatorArns.QS1, AwsQuantumSimulatorArns.QS3] +) +def test_bell_pair(simulator_arn, aws_session, s3_destination_folder): + device = AwsQuantumSimulator(simulator_arn, aws_session) + bell = Circuit().h(0).cnot(0, 1) + result = device.run(bell, s3_destination_folder, shots=750).result() + + assert 0.40 < result.measurement_probabilities["00"] < 0.60 + assert 0.40 < result.measurement_probabilities["11"] < 0.60 + assert len(result.measurements) == 750 + + +@pytest.mark.parametrize( + "simulator_arn", + [ # TODO Uncomment out below once proper ordering fix has been applied to QS1 + # AwsQuantumSimulatorArns.QS1, + AwsQuantumSimulatorArns.QS3 + ], +) +def test_qubit_ordering(simulator_arn, aws_session, s3_destination_folder): + device = AwsQuantumSimulator(simulator_arn, aws_session) + + # |110> should get back value of "110" + state_110 = Circuit().x(0).x(1).i(2) + result = device.run(state_110, s3_destination_folder).result() + assert result.measurement_counts.most_common(1)[0][0] == "110" + + # |001> should get back value of "001" + state_001 = Circuit().i(0).i(1).x(2) + result = device.run(state_001, s3_destination_folder).result() + assert result.measurement_counts.most_common(1)[0][0] == "001" + + +def test_qs2_quantum_task(aws_session, s3_destination_folder): + device = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS2, aws_session) + + bell = Circuit().h(range(8)) + measurements = device.run(bell, s3_destination_folder, shots=1).result().measurements + + # 1 shot + assert len(measurements) == 1 + + # 8 qubits + assert len(measurements[0]) == 8 diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py new file mode 100644 index 00000000..4e89653b --- /dev/null +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -0,0 +1,112 @@ +# Copyright 2019-2019 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.aws.aws_qpu_arns import AwsQpuArns +from braket.aws.aws_quantum_simulator_arns import AwsQuantumSimulatorArns + + +class MockDevices: + + MOCK_RIGETTI_QPU_1 = { + "arn": AwsQpuArns.RIGETTI, + "qubitCount": 16, + "connectivity": {"connectivityGraph": {"0": ["1", "2"], "1": ["0", "2"], "2": ["0", "1"]}}, + "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "T"], + "name": "Rigetti", + "status": "AVAILABLE", + } + + MOCK_RIGETTI_QPU_2 = { + "arn": AwsQpuArns.RIGETTI, + "qubitCount": 30, + "connectivity": {"connectivityGraph": {"0": ["1", "2"], "1": ["0", "2"], "2": ["0", "1"]}}, + "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "T", "S"], + "name": "Rigetti", + "status": "UNAVAILABLE", + "statusReason": "Under maintenance", + } + + MOCK_IONQ_QPU = { + "arn": AwsQpuArns.IONQ, + "qubitCount": 11, + "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "Toffoli"], + "name": "IonQ", + "status": "UNAVAILABLE", + "statusReason": "Under maintenance", + } + + MOCK_QS1_SIMULATOR_1 = { + "arn": AwsQuantumSimulatorArns.QS1, + "qubitCount": 23, + "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "Toffoli"], + "name": "integ_test_simulator", + "status": "AVAILABLE", + } + + MOCK_QS1_SIMULATOR_2 = { + "arn": AwsQuantumSimulatorArns.QS1, + "qubitCount": 30, + "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "Toffoli", "Phase", "CPhase"], + "name": "integ_test_simulator", + "status": "UNAVAILABLE", + "statusReason": "Temporary network issue", + } + + +class MockS3: + + MOCK_S3_RESULT_1 = json.dumps( + { + "StateVector": {"00": 0.19999, "01": 0.80000, "10": 0.00000, "11": 0.00001}, + "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], + "TaskMetadata": { + "Id": "UUID_blah_1", + "Status": "COMPLETED", + "BackendArn": AwsQpuArns.RIGETTI, + "CwLogGroupArn": "blah", + "Program": "....", + }, + } + ) + + MOCK_S3_RESULT_2 = json.dumps( + { + "StateVector": {"00": 0.80000, "01": 0.00000, "10": 0.00000, "11": 0.19999}, + "Measurements": [[0, 0], [0, 0], [0, 0], [1, 1]], + "TaskMetadata": { + "Id": "UUID_blah_2", + "Status": "COMPLETED", + "BackendArn": AwsQpuArns.RIGETTI, + "CwLogGroupArn": "blah", + "Program": "....", + }, + } + ) + + MOCK_S3_RESULT_3 = json.dumps( + { + "TaskMetadata": { + "Id": "1231231", + "Status": "COMPLETED", + "BackendArn": "test_arn", + "BackendTranslation": "...", + "Created": 1574140385.0697668, + "Modified": 1574140388.6908717, + "Shots": 100, + "GateModelConfig": {"QubitCount": 6}, + }, + "MeasurementProbabilities": {"011000": 0.9999999999999982}, + } + ) diff --git a/test/unit_tests/braket/aws/test_aws_qpu.py b/test/unit_tests/braket/aws/test_aws_qpu.py new file mode 100644 index 00000000..06b7141d --- /dev/null +++ b/test/unit_tests/braket/aws/test_aws_qpu.py @@ -0,0 +1,213 @@ +# Copyright 2019-2019 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 Mock, patch + +import braket.aws.aws_qpu # noqa F401 +import pytest +from braket.aws import AwsQpu, AwsQpuArns +from braket.circuits import Circuit +from common_test_utils import MockDevices + + +@pytest.fixture +def qpu(aws_session): + def _qpu(arn): + aws_session.get_qpu_metadata.return_value = MockDevices.MOCK_RIGETTI_QPU_1 + return AwsQpu(arn, aws_session) + + return _qpu + + +@pytest.fixture +def s3_destination_folder(): + return ("bucket-foo", "key-bar") + + +@pytest.fixture +def circuit(): + return Circuit().h(0) + + +@pytest.fixture +def boto_session(): + _boto_session = Mock() + _boto_session.region_name = AwsQpu.QPU_REGIONS[AwsQpuArns.RIGETTI][0] + return _boto_session + + +@pytest.fixture +def aws_session(): + _boto_session = Mock() + _boto_session.region_name = AwsQpu.QPU_REGIONS[AwsQpuArns.RIGETTI][0] + _aws_session = Mock() + _aws_session.boto_session = _boto_session + return _aws_session + + +@pytest.mark.xfail(raises=ValueError) +def test_unknown_qpu_arn(aws_session): + AwsQpu("foobar", aws_session) + + +def test_aws_session_in_qpu_region(aws_session): + arn = AwsQpuArns.RIGETTI + aws_session.boto_session.region_name = AwsQpu.QPU_REGIONS[arn][0] + aws_session.get_qpu_metadata.return_value = MockDevices.MOCK_RIGETTI_QPU_1 + AwsQpu(arn, aws_session) + + aws_session.get_qpu_metadata.assert_called_with(arn) + + +@patch("braket.aws.aws_qpu.AwsSession") +@patch("boto3.Session") +def test_aws_session_in_another_qpu_region( + boto_session_init, aws_session_init, boto_session, aws_session +): + arn = AwsQpuArns.RIGETTI + region = AwsQpu.QPU_REGIONS.get(arn)[0] + + boto_session_init.return_value = boto_session + aws_session_init.return_value = aws_session + aws_session.get_qpu_metadata.return_value = MockDevices.MOCK_RIGETTI_QPU_1 + + creds = Mock() + creds.access_key = "access key" + creds.secret_key = "secret key" + creds.token = "token" + + different_region_aws_session = Mock() + different_region_aws_session.boto_session.get_credentials.return_value = creds + different_region_aws_session.boto_session.profile_name = "profile name" + different_region_aws_session.boto_session.region_name = "foobar" + + AwsQpu(arn, different_region_aws_session) + + # assert creds, profile, and region were correctly supplied + boto_session_init.assert_called_with( + aws_access_key_id=creds.access_key, + aws_secret_access_key=creds.secret_key, + aws_session_token=creds.token, + profile_name=different_region_aws_session.boto_session.profile_name, + region_name=region, + ) + + # assert supplied session, different_region_aws_session, was replaced + aws_session.get_qpu_metadata.assert_called_with(arn) + + +@patch("braket.aws.aws_qpu.AwsSession") +@patch("boto3.Session") +def test_no_aws_session_supplied(boto_session_init, aws_session_init, boto_session, aws_session): + arn = AwsQpuArns.RIGETTI + region = AwsQpu.QPU_REGIONS.get(arn)[0] + + boto_session_init.return_value = boto_session + aws_session_init.return_value = aws_session + aws_session.get_qpu_metadata.return_value = MockDevices.MOCK_RIGETTI_QPU_1 + + AwsQpu(arn) + + boto_session_init.assert_called_with(region_name=region) + aws_session.get_qpu_metadata.assert_called_with(arn) + + +def test_qpu_refresh_metadata_success(aws_session): + aws_session.get_qpu_metadata.return_value = MockDevices.MOCK_RIGETTI_QPU_1 + qpu = AwsQpu(AwsQpuArns.RIGETTI, aws_session) + assert qpu.arn == MockDevices.MOCK_RIGETTI_QPU_1.get("arn") + assert qpu.name == MockDevices.MOCK_RIGETTI_QPU_1.get("name") + assert qpu.qubit_count == MockDevices.MOCK_RIGETTI_QPU_1.get("qubitCount") + assert qpu.connectivity_graph == MockDevices.MOCK_RIGETTI_QPU_1.get("connectivity").get( + "connectivityGraph" + ) + assert qpu.supported_quantum_operations == MockDevices.MOCK_RIGETTI_QPU_1.get( + "supportedQuantumOperations" + ) + assert qpu.status == MockDevices.MOCK_RIGETTI_QPU_1.get("status") + assert qpu.status_reason is None + + # describe_qpus now returns new metadata + aws_session.get_qpu_metadata.return_value = MockDevices.MOCK_RIGETTI_QPU_2 + qpu.refresh_metadata() + assert qpu.arn == MockDevices.MOCK_RIGETTI_QPU_2.get("arn") + assert qpu.name == MockDevices.MOCK_RIGETTI_QPU_2.get("name") + assert qpu.qubit_count == MockDevices.MOCK_RIGETTI_QPU_2.get("qubitCount") + assert qpu.connectivity_graph == MockDevices.MOCK_RIGETTI_QPU_2.get("connectivity").get( + "connectivityGraph" + ) + assert qpu.supported_quantum_operations == MockDevices.MOCK_RIGETTI_QPU_2.get( + "supportedQuantumOperations" + ) + assert qpu.status == MockDevices.MOCK_RIGETTI_QPU_2.get("status") + assert qpu.status_reason == MockDevices.MOCK_RIGETTI_QPU_2.get("statusReason") + + +def test_qpu_refresh_metadata_error(aws_session): + err_message = "nooo!" + aws_session.get_qpu_metadata.side_effect = RuntimeError(err_message) + with pytest.raises(RuntimeError) as excinfo: + AwsQpu(AwsQpuArns.RIGETTI, aws_session) + assert err_message in str(excinfo.value) + + +def test_equality(qpu, aws_session): + qpu_1 = qpu(AwsQpuArns.RIGETTI) + qpu_2 = qpu(AwsQpuArns.RIGETTI) + aws_session.get_qpu_metadata.return_value = MockDevices.MOCK_IONQ_QPU + aws_session.boto_session.region_name = AwsQpu.QPU_REGIONS[AwsQpuArns.IONQ][0] + other_qpu = AwsQpu(AwsQpuArns.IONQ, aws_session) + non_qpu = "HI" + + assert qpu_1 == qpu_2 + assert qpu_1 is not qpu_2 + assert qpu_1 != other_qpu + assert qpu_1 != non_qpu + + +def test_repr(qpu): + qpu = qpu(AwsQpuArns.RIGETTI) + expected = "QPU('name': {}, 'arn': {})".format(qpu.name, qpu.arn) + assert repr(qpu) == expected + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit") +def test_run_with_positional_args(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): + _run_and_assert(aws_quantum_task_mock, qpu, [circuit, s3_destination_folder], {}) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit") +def test_run_with_kwargs(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): + _run_and_assert( + aws_quantum_task_mock, + qpu, + [], + {"circuit": circuit, "s3_destination_folder": s3_destination_folder}, + ) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit") +def test_run_with_positional_args_and_kwargs( + aws_quantum_task_mock, qpu, circuit, s3_destination_folder +): + _run_and_assert(aws_quantum_task_mock, qpu, [circuit, s3_destination_folder], {"shots": 100}) + + +def _run_and_assert(aws_quantum_task_mock, qpu, run_args, run_kwargs): + task_mock = Mock() + aws_quantum_task_mock.return_value = task_mock + + qpu = qpu(AwsQpuArns.RIGETTI) + task = qpu.run(*run_args, **run_kwargs) + assert task == task_mock + aws_quantum_task_mock.assert_called_with(qpu._aws_session, qpu.arn, *run_args, **run_kwargs) diff --git a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py new file mode 100644 index 00000000..92c25067 --- /dev/null +++ b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py @@ -0,0 +1,131 @@ +# Copyright 2019-2019 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 Mock, patch + +import pytest +from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns +from braket.circuits import Circuit +from common_test_utils import MockDevices + + +@pytest.fixture +def simulator(): + def _simulator(arn): + mock_session = Mock() + mock_session.get_simulator_metadata.return_value = MockDevices.MOCK_QS1_SIMULATOR_1 + return AwsQuantumSimulator(arn, mock_session) + + return _simulator + + +@pytest.fixture +def circuit(): + return Circuit().h(0) + + +@pytest.fixture +def s3_destination_folder(): + return ("bucket-foo", "key-bar") + + +def test_simulator_refresh_metadata_success(): + mock_session = Mock() + expected_metadata = MockDevices.MOCK_QS1_SIMULATOR_1 + mock_session.get_simulator_metadata.return_value = expected_metadata + simulator = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS1, mock_session) + assert simulator.arn == expected_metadata.get("arn") + assert simulator.name == expected_metadata.get("name") + assert simulator.qubit_count == expected_metadata.get("qubitCount") + assert simulator.supported_quantum_operations == expected_metadata.get( + "supportedQuantumOperations" + ) + assert simulator.status == expected_metadata.get("status") + assert simulator.status_reason is None + + # describe_simulators now returns new metadata + expected_metadata = MockDevices.MOCK_QS1_SIMULATOR_2 + mock_session.get_simulator_metadata.return_value = expected_metadata + simulator.refresh_metadata() + assert simulator.arn == expected_metadata.get("arn") + assert simulator.name == expected_metadata.get("name") + assert simulator.qubit_count == expected_metadata.get("qubitCount") + assert simulator.supported_quantum_operations == expected_metadata.get( + "supportedQuantumOperations" + ) + assert simulator.status == expected_metadata.get("status") + assert simulator.status_reason == expected_metadata.get("statusReason") + + +def test_simulator_refresh_metadata_error(): + mock_session = Mock() + err_message = "nooo!" + mock_session.get_simulator_metadata.side_effect = RuntimeError(err_message) + with pytest.raises(RuntimeError) as excinfo: + AwsQuantumSimulator(AwsQuantumSimulatorArns.QS1, mock_session) + assert err_message in str(excinfo.value) + + +def test_equality(simulator): + simulator_1 = simulator(AwsQuantumSimulatorArns.QS1) + simulator_2 = simulator(AwsQuantumSimulatorArns.QS1) + other_simulator = Mock(spec=AwsQuantumSimulator) + other_simulator.arn.return_value = "OTHER_ARN" + non_simulator = "HI" + + assert simulator_1 == simulator_2 + assert simulator_1 is not simulator_2 + assert simulator_1 != other_simulator + assert simulator_1 != non_simulator + + +def test_repr(simulator): + simulator = simulator(AwsQuantumSimulatorArns.QS1) + expected = "QuantumSimulator('name': {}, 'arn': {})".format(simulator.name, simulator.arn) + assert repr(simulator) == expected + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit") +def test_run_with_positional_args(aws_quantum_task_mock, simulator, circuit, s3_destination_folder): + _run_and_assert(aws_quantum_task_mock, simulator, [circuit, s3_destination_folder], {}) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit") +def test_run_with_kwargs(aws_quantum_task_mock, simulator, circuit, s3_destination_folder): + _run_and_assert( + aws_quantum_task_mock, + simulator, + [], + {"circuit": circuit, "s3_destination_folder": s3_destination_folder}, + ) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit") +def test_run_with_positional_args_and_kwargs( + aws_quantum_task_mock, simulator, circuit, s3_destination_folder +): + _run_and_assert( + aws_quantum_task_mock, simulator, [circuit, s3_destination_folder], {"shots": 100} + ) + + +def _run_and_assert(aws_quantum_task_mock, simulator, run_args, run_kwargs): + task_mock = Mock() + aws_quantum_task_mock.return_value = task_mock + + simulator = simulator(AwsQuantumSimulatorArns.QS1) + task = simulator.run(*run_args, **run_kwargs) + assert task == task_mock + aws_quantum_task_mock.assert_called_with( + simulator._aws_session, simulator.arn, *run_args, **run_kwargs + ) diff --git a/test/unit_tests/braket/aws/test_aws_quantum_task.py b/test/unit_tests/braket/aws/test_aws_quantum_task.py new file mode 100644 index 00000000..b62a0274 --- /dev/null +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -0,0 +1,238 @@ +# Copyright 2019-2019 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 asyncio +from unittest.mock import Mock + +import pytest +from braket.aws import AwsQuantumTask, AwsQuantumTaskResult +from braket.aws.aws_session import AwsSession +from braket.circuits import Circuit +from common_test_utils import MockS3 + +S3_TARGET = AwsSession.S3DestinationFolder("foo", "bar") + + +@pytest.fixture +def aws_session(): + mock = Mock() + _mock_state(mock, "RUNNING") + return mock + + +@pytest.fixture +def quantum_task(aws_session): + return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) + + +@pytest.fixture +def arn(): + return "foo:bar:arn" + + +@pytest.fixture +def circuit(): + return Circuit().h(0).cnot(0, 1) + + +def test_equality(arn, aws_session): + quantum_task_1 = AwsQuantumTask(arn, aws_session) + quantum_task_2 = AwsQuantumTask(arn, aws_session) + other_quantum_task = AwsQuantumTask("different:arn", aws_session) + non_quantum_task = quantum_task_1.id + + assert quantum_task_1 == quantum_task_2 + assert quantum_task_1 is not quantum_task_2 + assert quantum_task_1 != other_quantum_task + assert quantum_task_1 != non_quantum_task + + +def test_str(quantum_task): + expected = "AwsQuantumTask('id':{})".format(quantum_task.id) + assert str(quantum_task) == expected + + +def test_hash(quantum_task): + hash(quantum_task) == hash(quantum_task.id) + + +def test_id_getter(arn, aws_session): + quantum_task = AwsQuantumTask(arn, aws_session) + assert quantum_task.id == arn + + +@pytest.mark.xfail(raises=AttributeError) +def test_no_id_setter(quantum_task): + quantum_task.id = 123 + + +def test_metadata(quantum_task): + metadata_1 = {"status": "RUNNING"} + quantum_task._aws_session.get_quantum_task.return_value = metadata_1 + assert quantum_task.metadata() == metadata_1 + quantum_task._aws_session.get_quantum_task.assert_called_with(quantum_task.id) + + metadata_2 = {"status": "COMPLETED"} + quantum_task._aws_session.get_quantum_task.return_value = metadata_2 + assert quantum_task.metadata(use_cached_value=True) == metadata_1 + + +def test_state(quantum_task): + state_1 = "RUNNING" + _mock_state(quantum_task._aws_session, state_1) + assert quantum_task.state() == state_1 + quantum_task._aws_session.get_quantum_task.assert_called_with(quantum_task.id) + + state_2 = "COMPLETED" + _mock_state(quantum_task._aws_session, state_2) + assert quantum_task.state(use_cached_value=True) == state_1 + + +def test_cancel(quantum_task): + future = quantum_task.async_result() + + assert not future.done() + quantum_task.cancel() + + assert quantum_task.result() is None + assert future.cancelled() + quantum_task._aws_session.cancel_quantum_task.assert_called_with(quantum_task.id) + + +def test_result(quantum_task): + _mock_state(quantum_task._aws_session, "COMPLETED") + _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_1) + + expected = AwsQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) + assert quantum_task.result() == expected + + s3_bucket = quantum_task.metadata()["resultsS3Bucket"] + s3_object_key = quantum_task.metadata()["resultsS3ObjectKey"] + quantum_task._aws_session.retrieve_s3_object_body.assert_called_with(s3_bucket, s3_object_key) + + +def test_result_is_cached(quantum_task): + _mock_state(quantum_task._aws_session, "COMPLETED") + _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_1) + quantum_task.result() + + _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_2) + expected = AwsQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) + assert quantum_task.result() == expected + + +def test_async_result(quantum_task): + 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_state(quantum_task._aws_session, "RUNNING") + _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_1) + + future = quantum_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_state(quantum_task._aws_session, "COMPLETED") + 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() + + expected = AwsQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) + assert result_from_callback == expected + assert result_from_waiting == expected + assert result_from_future == expected + + +def test_failed_task(quantum_task): + _mock_state(quantum_task._aws_session, "FAILED") + _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_1) + result = quantum_task.result() + assert result is None + + +def test_timeout(aws_session): + _mock_state(aws_session, "RUNNING") + _mock_s3(aws_session, MockS3.MOCK_S3_RESULT_1) + + # Setup the poll timing such that the timeout will occur after one API poll + quantum_task = AwsQuantumTask( + "foo:bar:arn", aws_session, poll_timeout_seconds=0.5, poll_interval_seconds=1 + ) + assert quantum_task.result() is None + + _mock_state(aws_session, "COMPLETED") + assert quantum_task.result() == AwsQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) + + +@pytest.mark.xfail(raises=ValueError) +def test_from_circuit_invalid_s3_folder(aws_session, arn, circuit): + AwsQuantumTask.from_circuit(aws_session, arn, circuit, ("bucket",)) + + +def test_from_circuit_default_shots(aws_session, arn, circuit): + mocked_task_arn = "task-arn-1" + aws_session.create_quantum_task.return_value = mocked_task_arn + + task = AwsQuantumTask.from_circuit(aws_session, arn, circuit, S3_TARGET) + assert task == AwsQuantumTask(mocked_task_arn, aws_session) + + _assert_create_quantum_task_called_with( + aws_session, arn, circuit, S3_TARGET, AwsQuantumTask.DEFAULT_SHOTS + ) + + +def test_from_circuit_with_shots(aws_session, arn, circuit): + mocked_task_arn = "task-arn-1" + aws_session.create_quantum_task.return_value = mocked_task_arn + shots = 53 + + task = AwsQuantumTask.from_circuit(aws_session, arn, circuit, S3_TARGET) + assert task == AwsQuantumTask(mocked_task_arn, aws_session) + + _assert_create_quantum_task_called_with(aws_session, arn, circuit, S3_TARGET, shots) + + +def _assert_create_quantum_task_called_with(aws_session, arn, circuit, s3_results_prefix, shots): + aws_session.create_quantum_task.assert_called_with( + **{ + "backendArn": arn, + "resultsS3Bucket": s3_results_prefix[0], + "resultsS3Prefix": s3_results_prefix[1], + "ir": circuit.to_ir().json(), + "irType": AwsQuantumTask.GATE_IR_TYPE, + "gateModelConfig": {"qubitCount": circuit.qubit_count}, + "shots": AwsQuantumTask.DEFAULT_SHOTS, + } + ) + + +def _mock_state(aws_session, state): + return_value = { + "status": state, + "resultsS3Bucket": S3_TARGET.bucket, + "resultsS3ObjectKey": S3_TARGET.key, + } + aws_session.get_quantum_task.return_value = return_value + + +def _mock_s3(aws_session, result): + aws_session.retrieve_s3_object_body.return_value = result diff --git a/test/unit_tests/braket/aws/test_aws_quantum_task_result.py b/test/unit_tests/braket/aws/test_aws_quantum_task_result.py new file mode 100644 index 00000000..0fb5b3ea --- /dev/null +++ b/test/unit_tests/braket/aws/test_aws_quantum_task_result.py @@ -0,0 +1,112 @@ +# Copyright 2019-2019 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 typing import Any, Counter, Dict + +import numpy as np +import pytest +from braket.aws.aws_quantum_task_result import AwsQuantumTaskResult +from common_test_utils import MockS3 + + +@pytest.fixture +def result_str_1(): + return MockS3.MOCK_S3_RESULT_1 + + +@pytest.fixture +def result_str_2(): + return MockS3.MOCK_S3_RESULT_2 + + +@pytest.fixture +def result_str_3(): + return MockS3.MOCK_S3_RESULT_3 + + +def test_state_vector(): + state_vector: Dict[str, float] = {"00": 0.00, "01": 0.50, "10": 0.50, "11": 0.00} + result: AwsQuantumTaskResult = AwsQuantumTaskResult( + measurements=None, + task_metadata=None, + state_vector=state_vector, + measurement_counts=None, + measurement_probabilities=None, + measurements_copied_from_device=False, + measurement_counts_copied_from_device=False, + measurement_probabilities_copied_from_device=False, + ) + assert result.state_vector == state_vector + + +def test_task_metadata(): + task_metadata: Dict[str, Any] = { + "Id": "UUID_blah", + "Status": "COMPLETED", + "BackendArn": "Rigetti_Arn", + "CwLogGroupArn": "blah", + "Program": "....", + "CreatedAt": "02/12/22 21:23", + "UpdatedAt": "02/13/22 21:23", + } + result: AwsQuantumTaskResult = AwsQuantumTaskResult( + measurements=None, + task_metadata=task_metadata, + measurement_counts=None, + measurement_probabilities=None, + measurements_copied_from_device=False, + measurement_counts_copied_from_device=False, + measurement_probabilities_copied_from_device=False, + ) + assert result.task_metadata == task_metadata + + +def test_from_string_measurements(result_str_1): + result_obj = json.loads(result_str_1) + task_result = AwsQuantumTaskResult.from_string(result_str_1) + expected_measurements = np.asarray(result_obj["Measurements"], dtype=int) + assert task_result.task_metadata == result_obj["TaskMetadata"] + assert task_result.state_vector == result_obj["StateVector"] + assert np.array2string(task_result.measurements) == np.array2string(expected_measurements) + assert not task_result.measurement_counts_copied_from_device + assert not task_result.measurement_probabilities_copied_from_device + assert task_result.measurements_copied_from_device + + +def test_from_string_measurement_probabilities(result_str_3): + result_obj = json.loads(result_str_3) + task_result = AwsQuantumTaskResult.from_string(result_str_3) + assert task_result.measurement_probabilities == result_obj["MeasurementProbabilities"] + assert task_result.task_metadata == result_obj["TaskMetadata"] + assert task_result.state_vector is None + shots = 100 + measurement_list = [list("011000") for x 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 not task_result.measurement_counts_copied_from_device + assert task_result.measurement_probabilities_copied_from_device + assert not task_result.measurements_copied_from_device + + +def test_equality(result_str_1, result_str_2): + result_1 = AwsQuantumTaskResult.from_string(result_str_1) + result_2 = AwsQuantumTaskResult.from_string(result_str_1) + other_result = AwsQuantumTaskResult.from_string(result_str_2) + non_result = "not a aws quantum task result" + + assert result_1 == result_2 + assert result_1 is not result_2 + assert result_1 != other_result + assert result_1 != non_result diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py new file mode 100644 index 00000000..42f37a52 --- /dev/null +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -0,0 +1,175 @@ +# Copyright 2019-2019 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 + +import pytest +from botocore.exceptions import ClientError +from braket.aws import AwsQpuArns, AwsQuantumSimulatorArns, AwsSession +from common_test_utils import MockDevices + +TEST_S3_OBJ_CONTENTS = { + "TaskMetadata": { + "Id": "UUID_blah", + "Status": "COMPLETED", + "BackendArn": AwsQpuArns.RIGETTI, + "CwLogGroupArn": "blah", + "Program": "....", + "CreatedAt": "02/12/22 21:23", + "UpdatedAt": "02/13/22 21:23", + } +} + + +@pytest.fixture +def boto_session(): + _boto_session = Mock() + _boto_session.region_name = "us-west-2" + return _boto_session + + +@pytest.fixture +def aws_session(boto_session): + return AwsSession(boto_session=boto_session, braket_client=Mock()) + + +@pytest.mark.xfail(raises=ValueError) +def test_no_endpoint_for_region(): + boto_session = Mock() + boto_session.region_name = "foobar" + AwsSession(boto_session=boto_session, braket_client=None) + + +def test_uses_endpoint_for_region(boto_session): + AwsSession(boto_session=boto_session, braket_client=None) + + boto_session.client.assert_called_with( + "aqx", endpoint_url=AwsSession.BRAKET_ENDPOINTS[boto_session.region_name] + ) + + +def test_uses_supplied_braket_client(): + boto_session = Mock() + boto_session.region_name = "foobar" + braket_client = Mock() + aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) + + assert aws_session.braket_client == braket_client + + +def test_retrieve_s3_object_body_success(boto_session): + bucket_name = "braket-integ-test" + filename = "tasks/test_task_1.json" + + mock_resource = Mock() + boto_session.resource.return_value = mock_resource + mock_object = Mock() + mock_resource.Object.return_value = mock_object + mock_body_object = Mock() + mock_object.get.return_value = {"Body": mock_body_object} + mock_read_object = Mock() + mock_body_object.read.return_value = mock_read_object + 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) + return_value = aws_session.retrieve_s3_object_body(bucket_name, filename) + assert return_value == json.dumps(TEST_S3_OBJ_CONTENTS) + + +@pytest.mark.xfail(raises=ClientError) +def test_retrieve_s3_object_body_client_error(boto_session): + bucket_name = "braket-integ-test" + filename = "tasks/test_task_1.json" + + mock_resource = Mock() + boto_session.resource.return_value = mock_resource + mock_object = Mock() + mock_resource.Object.return_value = mock_object + mock_object.get.side_effect = ClientError( + {"Error": {"Code": "ValidationException", "Message": "NoSuchKey"}}, "Operation" + ) + aws_session = AwsSession(boto_session=boto_session) + aws_session.retrieve_s3_object_body(bucket_name, filename) + + +def test_get_qpu_metadata_success(boto_session): + braket_client = Mock() + braket_client.describe_qpus.return_value = {"qpus": [MockDevices.MOCK_RIGETTI_QPU_1]} + aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) + qpu_metadata = aws_session.get_qpu_metadata(AwsQpuArns.RIGETTI) + assert qpu_metadata == MockDevices.MOCK_RIGETTI_QPU_1 + + +# TODO: revisit once we actually use boto3 +@pytest.mark.xfail(raises=ClientError) +def test_get_qpu_metadata_client_error(boto_session): + braket_client = Mock() + braket_client.describe_qpus.side_effect = ClientError( + {"Error": {"Code": "ValidationException", "Message": "NoSuchQpu"}}, "Operation" + ) + aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) + aws_session.get_qpu_metadata(AwsQpuArns.RIGETTI) + + +def test_get_simulator_metadata_success(boto_session): + braket_client = Mock() + braket_client.describe_quantum_simulators.return_value = { + "quantumSimulators": [MockDevices.MOCK_QS1_SIMULATOR_1] + } + aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) + simulator_metadata = aws_session.get_simulator_metadata(AwsQuantumSimulatorArns.QS1) + assert simulator_metadata == MockDevices.MOCK_QS1_SIMULATOR_1 + + +# TODO: revisit once we actually use boto3 +@pytest.mark.xfail(raises=ClientError) +def test_get_simulator_metadata_client_error(boto_session): + braket_client = Mock() + braket_client.describe_quantum_simulators.side_effect = ClientError( + {"Error": {"Code": "ValidationException", "Message": "NoSuchSimulator"}}, "Operation" + ) + aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) + aws_session.get_simulator_metadata(AwsQuantumSimulatorArns.QS1) + + +def test_cancel_quantum_task(aws_session): + arn = "foo:bar:arn" + aws_session.braket_client.cancel_quantum_task.return_value = {"quantumTaskArn": arn} + + assert aws_session.cancel_quantum_task(arn) is None + aws_session.braket_client.cancel_quantum_task.assert_called_with(quantumTaskArn=arn) + + +def test_create_quantum_task(aws_session): + arn = "foo:bar:arn" + aws_session.braket_client.create_quantum_task.return_value = {"quantumTaskArn": arn} + + kwargs = { + "backendArn": "arn:aws:us-west-2:abc:xyz:abc", + "cwLogGroupArn": "arn:aws:us-west-2:abc:xyz:abc", + "destinationUrl": "http://s3-us-west-2.amazonaws.com/task-output-derebolt-1/output.json", + "program": {"ir": '{"instructions":[]}', "qubitCount": 4}, + } + assert aws_session.create_quantum_task(**kwargs) == arn + aws_session.braket_client.create_quantum_task.assert_called_with(**kwargs) + + +def test_get_quantum_task(aws_session): + arn = "foo:bar:arn" + return_value = {"quantumTaskArn": arn} + aws_session.braket_client.get_quantum_task.return_value = return_value + + assert aws_session.get_quantum_task(arn) == return_value + aws_session.braket_client.get_quantum_task.assert_called_with(quantumTaskArn=arn) diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py new file mode 100644 index 00000000..b429f023 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -0,0 +1,45 @@ +# Copyright 2019-2019 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 AngledGate, Gate + + +@pytest.fixture +def angled_gate(): + return AngledGate(angle=0.15, qubit_count=1, ascii_symbols=["foo"]) + + +def test_is_operator(angled_gate): + assert isinstance(angled_gate, Gate) + + +@pytest.mark.xfail(raises=ValueError) +def test_angle_is_none(): + AngledGate(qubit_count=1, ascii_symbols=["foo"], angle=None) + + +def test_getters(): + qubit_count = 2 + ascii_symbols = ("foo", "bar") + angle = 0.15 + gate = AngledGate(angle=angle, qubit_count=qubit_count, ascii_symbols=ascii_symbols) + + assert gate.qubit_count == qubit_count + assert gate.ascii_symbols == ascii_symbols + assert gate.angle == angle + + +@pytest.mark.xfail(raises=AttributeError) +def test_angle_setter(angled_gate): + angled_gate.angle = 0.14 diff --git a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py new file mode 100644 index 00000000..1763092f --- /dev/null +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -0,0 +1,137 @@ +# Copyright 2019-2019 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 AsciiCircuitDiagram, Circuit, Gate, Instruction, Operator + + +def test_empty_circuit(): + assert AsciiCircuitDiagram.build_diagram(Circuit()) == "" + + +def test_qubit_width(): + circ = Circuit().h(0).h(100) + expected = ( + "T : |0|", + " ", + "q0 : -H-", + " ", + "q100 : -H-", + "", + "T : |0|", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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 |", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_diagram(circ) == expected + + +def test_connector_across_two_qubits(): + circ = Circuit().cnot(3, 4).h(range(2, 6)) + expected = ( + "T : |0|1|", + " ", + "q2 : -H---", + " ", + "q3 : -C-H-", + " | ", + "q4 : -X-H-", + " ", + "q5 : -H---", + "", + "T : |0|1|", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_diagram(circ) == expected + + +def test_connector_across_gt_two_qubits(): + circ = Circuit().h(4).cnot(3, 5).h(4).h(2) + expected = ( + "T : |0|1|2|", + " ", + "q2 : -H-----", + " ", + "q3 : ---C---", + " | ", + "q4 : -H-|-H-", + " | ", + "q5 : ---X---", + "", + "T : |0|1|2|", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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|2|", + " ", + "q3 : ---C---", + " | ", + "q4 : -H-|-H-", + " | ", + "q100 : ---X---", + " ", + "q101 : -H-----", + "", + "T : |0|1|2|", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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-C-", + " | ", + "q2 : ---X-", + "", + "T : |0|1|", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_diagram(circ) == expected diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py new file mode 100644 index 00000000..a57c587b --- /dev/null +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -0,0 +1,329 @@ +# Copyright 2019-2019 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 Mock + +import braket.ir.jaqcd as jaqcd +import pytest +from braket.circuits import ( + AsciiCircuitDiagram, + Circuit, + Gate, + Instruction, + Moments, + QubitSet, + circuit, +) + + +@pytest.fixture +def cnot(): + return Circuit().add_instruction(Instruction(Gate.CNot(), [0, 1])) + + +@pytest.fixture +def cnot_instr(): + return Instruction(Gate.CNot(), [0, 1]) + + +@pytest.fixture +def h(): + return Circuit().add_instruction(Instruction(Gate.H(), 0)) + + +@pytest.fixture +def h_instr(): + return Instruction(Gate.H(), 0) + + +@pytest.fixture +def bell_pair(): + return ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + ) + + +def test_repr(h): + expected = f"Circuit('instructions': {list(h.instructions)})" + assert repr(h) == expected + + +def test_str(h): + expected = AsciiCircuitDiagram.build_diagram(h) + assert str(h) == expected + + +def test_equality(): + circ_1 = Circuit().h(0) + circ_2 = Circuit().h(0) + other_circ = Circuit().h(1) + non_circ = "non circuit" + + assert circ_1 == circ_2 + assert circ_1 is not circ_2 + assert circ_1 != other_circ + assert circ_1 != non_circ + + +def test_add_instruction_default(cnot_instr): + circ = Circuit().add_instruction(cnot_instr) + assert list(circ.instructions) == [cnot_instr] + + +def test_add_instruction_with_mapping(cnot_instr): + expected = [Instruction(Gate.CNot(), [10, 11])] + circ = Circuit().add_instruction(cnot_instr, target_mapping={0: 10, 1: 11}) + assert list(circ.instructions) == expected + + +def test_add_instruction_with_target(cnot_instr): + expected = [Instruction(Gate.CNot(), [10, 11])] + circ = Circuit().add_instruction(cnot_instr, target=[10, 11]) + assert list(circ.instructions) == expected + + +def test_add_multiple_single_qubit_instruction(h_instr): + circ = Circuit().add_instruction(h_instr, target=[0, 1, 2, 3]) + expected = Circuit().h(0).h(1).h(2).h(3) + assert circ == expected + + +@pytest.mark.xfail(raises=TypeError) +def test_add_instruction_with_target_and_mapping(h): + Circuit().add_instruction(h, target=[10], target_mapping={0: 10}) + + +def test_add_circuit_default(bell_pair): + circ = Circuit().add_circuit(bell_pair) + assert circ == bell_pair + + +def test_add_circuit_with_mapping(bell_pair): + circ = Circuit().add_circuit(bell_pair, target_mapping={0: 10, 1: 11}) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 10)) + .add_instruction(Instruction(Gate.CNot(), [10, 11])) + ) + assert circ == expected + + +def test_add_circuit_with_target(bell_pair): + circ = Circuit().add_circuit(bell_pair, target=[10, 11]) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 10)) + .add_instruction(Instruction(Gate.CNot(), [10, 11])) + ) + assert circ == expected + + +def test_add_circuit_with_target_and_non_continuous_qubits(): + widget = Circuit().h(5).h(50).h(100) + circ = Circuit().add_circuit(widget, target=[1, 3, 5]) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 1)) + .add_instruction(Instruction(Gate.H(), 3)) + .add_instruction(Instruction(Gate.H(), 5)) + ) + assert circ == expected + + +@pytest.mark.xfail(raises=TypeError) +def test_add_circuit_with_target_and_mapping(h): + Circuit().add_circuit(h, target=[10], target_mapping={0: 10}) + + +def test_add_with_instruction_with_default(cnot_instr): + circ = Circuit().add(cnot_instr) + assert circ == Circuit().add_instruction(cnot_instr) + + +def test_add_with_instruction_with_mapping(cnot_instr): + target_mapping = {0: 10, 1: 11} + circ = Circuit().add(cnot_instr, target_mapping=target_mapping) + expected = Circuit().add_instruction(cnot_instr, target_mapping=target_mapping) + assert circ == expected + + +def test_add_with_instruction_with_target(cnot_instr): + target = [10, 11] + circ = Circuit().add(cnot_instr, target=target) + expected = Circuit().add_instruction(cnot_instr, target=target) + assert circ == expected + + +def test_add_with_circuit_with_default(bell_pair): + circ = Circuit().add(bell_pair) + assert circ == Circuit().add_circuit(bell_pair) + + +def test_add_with_circuit_with_mapping(bell_pair): + target_mapping = {0: 10, 1: 11} + circ = Circuit().add(bell_pair, target_mapping=target_mapping) + expected = Circuit().add_circuit(bell_pair, target_mapping=target_mapping) + assert circ == expected + + +def test_add_with_circuit_with_target(bell_pair): + target = [10, 11] + circ = Circuit().add(bell_pair, target=target) + expected = Circuit().add_circuit(bell_pair, target=target) + assert circ == expected + + +def test_iadd_operator(cnot_instr, h): + circ = Circuit() + circ += h + circ += cnot_instr + circ += [h, cnot_instr] + + assert circ == Circuit().add(h).add(cnot_instr).add(h).add(cnot_instr) + + +def test_add_operator(h, cnot, bell_pair): + addition = h + bell_pair + h + h + expected = Circuit().add(h).add(bell_pair).add(h).add(h) + + assert addition == expected + assert addition != (h + h + bell_pair + h) + + +@pytest.mark.xfail(raises=TypeError) +def test_iadd_with_unknown_type(h): + h += 100 + + +def test_subroutine_register(): + # register a private method to avoid Sphinx docs picking this up + @circuit.subroutine(register=True) + def _foo(target): + """this docstring will be added to the registered attribute""" + return Instruction(Gate.H(), target) + + circ = Circuit()._foo(0) + assert circ == Circuit(Instruction(Gate.H(), 0)) + assert Circuit._foo.__doc__ == _foo.__doc__ + + +def test_subroutine_returns_circuit(): + @circuit.subroutine() + def foo(target): + return Circuit().add(Instruction(Gate.H(), 0)) + + circ = Circuit().add(foo, 0) + assert circ == Circuit(Instruction(Gate.H(), 0)) + + +def test_subroutine_returns_instruction(): + @circuit.subroutine() + def foo(target): + return Instruction(Gate.H(), 0) + + circ = Circuit().add(foo, 0) + assert circ == Circuit(Instruction(Gate.H(), 0)) + + +def test_subroutine_returns_iterable(): + @circuit.subroutine() + def foo(target): + for qubit in range(1): + yield Instruction(Gate.H(), qubit) + + circ = Circuit().add(foo, 0) + assert circ == Circuit(Instruction(Gate.H(), 0)) + + +def test_subroutine_nested(): + @circuit.subroutine() + def h(target): + for qubit in target: + yield Instruction(Gate.H(), qubit) + + @circuit.subroutine() + def h_nested(target): + for qubit in target: + yield h(target) + + circ = Circuit().add(h_nested, [0, 1]) + expected = Circuit([Instruction(Gate.H(), j) for i in range(2) for j in range(2)]) + assert circ == expected + + +def test_ir_empty_instructions(): + circ = Circuit() + assert circ.to_ir() == jaqcd.Program(instructions=[]) + + +def test_ir_non_empty_instructions(): + circ = Circuit().h(0).cnot(0, 1) + expected = jaqcd.Program(instructions=[jaqcd.H(target=0), jaqcd.CNot(control=0, target=1)]) + assert circ.to_ir() == expected + + +def test_depth_getter(h): + assert h.depth is h._moments.depth + + +@pytest.mark.xfail(raises=AttributeError) +def test_depth_setter(h): + h.depth = 1 + + +def test_instructions_getter(h): + assert list(h.instructions) == list(h._moments.values()) + + +@pytest.mark.xfail(raises=AttributeError) +def test_instructions_setter(h, h_instr): + h.instructions = iter([h_instr]) + + +def test_moments_getter(h): + assert h.moments is h._moments + + +@pytest.mark.xfail(raises=AttributeError) +def test_moments_setter(h): + h.moments = Moments() + + +def test_qubit_count_getter(h): + assert h.qubit_count is h._moments.qubit_count + + +@pytest.mark.xfail(raises=AttributeError) +def test_qubit_count_setter(h): + h.qubit_count = 1 + + +def test_qubits_getter(h): + assert h.qubits == h._moments.qubits + assert h.qubits is not h._moments.qubits + + +@pytest.mark.xfail(raises=AttributeError) +def test_qubits_setter(h): + h.qubits = QubitSet(1) + + +def test_diagram(h): + expected = "foo bar diagram" + mock_diagram = Mock() + mock_diagram.build_diagram.return_value = expected + + assert h.diagram(mock_diagram) == expected + mock_diagram.build_diagram.assert_called_with(h) diff --git a/test/unit_tests/braket/circuits/test_gate.py b/test/unit_tests/braket/circuits/test_gate.py new file mode 100644 index 00000000..8bab8558 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_gate.py @@ -0,0 +1,124 @@ +# Copyright 2019-2019 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 Gate, Operator + + +@pytest.fixture +def gate(): + return Gate(qubit_count=1, ascii_symbols=["foo"]) + + +def test_is_operator(gate): + assert isinstance(gate, Operator) + + +@pytest.mark.xfail(raises=ValueError) +def test_qubit_count_lt_one(): + Gate(qubit_count=0, ascii_symbols=[]) + + +@pytest.mark.xfail(raises=ValueError) +def test_none_ascii(): + Gate(qubit_count=1, ascii_symbols=None) + + +@pytest.mark.xfail(raises=ValueError) +def test_mismatch_length_ascii(): + Gate(qubit_count=1, ascii_symbols=["foo", "bar"]) + + +def test_name(gate): + expected = gate.__class__.__name__ + assert gate.name == expected + + +def test_getters(): + qubit_count = 2 + ascii_symbols = ("foo", "bar") + gate = Gate(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + + assert gate.qubit_count == qubit_count + assert gate.ascii_symbols == ascii_symbols + + +@pytest.mark.xfail(raises=AttributeError) +def test_qubit_count_setter(gate): + gate.qubit_count = 10 + + +@pytest.mark.xfail(raises=AttributeError) +def test_ascii_symbols_setter(gate): + gate.ascii_symbols = ["foo", "bar"] + + +@pytest.mark.xfail(raises=AttributeError) +def test_name_setter(gate): + gate.name = "hi" + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_to_ir_not_implemented_by_default(gate): + gate.to_ir(None) + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_to_matrix_not_implemented_by_default(gate): + gate.to_matrix(None) + + +def test_matrix_equivalence(): + gate1 = Gate.H() + gate2 = Gate.H() + gate3 = Gate.CNot() + assert gate1.matrix_equivalence(gate2) + assert not gate2.matrix_equivalence(gate3) + + +def test_matrix_equivalence_non_gate(): + gate1 = Gate.H() + x = 1 + assert gate1.matrix_equivalence(x) == NotImplemented + + +def test_str(gate): + expected = "{}('qubit_count': {})".format(gate.name, gate.qubit_count) + assert str(gate) == expected + + +def test_str_angle(): + gate = Gate.Rx(0.5) + expected = "{}('angle': {}, 'qubit_count': {})".format(gate.name, gate.angle, gate.qubit_count) + assert str(gate) == expected + + +def test_equality(): + gate_1 = Gate(qubit_count=1, ascii_symbols=["foo"]) + gate_2 = Gate(qubit_count=1, ascii_symbols=["foo"]) + other_gate = Gate.Rx(angle=0.34) + non_gate = "non gate" + + assert gate_1 == gate_2 + assert gate_1 is not gate_2 + assert gate_1 != other_gate + assert gate_1 != non_gate + + +def test_register_gate(): + class _FooGate(Gate): + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["foo"]) + + Gate.register_gate(_FooGate) + assert Gate._FooGate().name == _FooGate().name diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py new file mode 100644 index 00000000..4ffc8724 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -0,0 +1,193 @@ +# Copyright 2019-2019 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 braket.ir.jaqcd as ir +import numpy as np +import pytest +from braket.circuits import Circuit, Gate, Instruction, QubitSet +from braket.ir.jaqcd.shared_models import ( + Angle, + DoubleControl, + DoubleTarget, + SingleControl, + SingleTarget, +) + +testdata = [ + (Gate.H, "h", ir.H, [SingleTarget]), + (Gate.I, "i", ir.I, [SingleTarget]), + (Gate.X, "x", ir.X, [SingleTarget]), + (Gate.Y, "y", ir.Y, [SingleTarget]), + (Gate.Z, "z", ir.Z, [SingleTarget]), + (Gate.S, "s", ir.S, [SingleTarget]), + (Gate.Si, "si", ir.Si, [SingleTarget]), + (Gate.T, "t", ir.T, [SingleTarget]), + (Gate.Ti, "ti", ir.Ti, [SingleTarget]), + (Gate.V, "v", ir.V, [SingleTarget]), + (Gate.Vi, "vi", ir.Vi, [SingleTarget]), + (Gate.Rx, "rx", ir.Rx, [SingleTarget, Angle]), + (Gate.Ry, "ry", ir.Ry, [SingleTarget, Angle]), + (Gate.Rz, "rz", ir.Rz, [SingleTarget, Angle]), + (Gate.CNot, "cnot", ir.CNot, [SingleTarget, SingleControl]), + (Gate.CCNot, "ccnot", ir.CCNot, [SingleTarget, DoubleControl]), + (Gate.Swap, "swap", ir.Swap, [DoubleTarget]), + (Gate.CSwap, "cswap", ir.CSwap, [SingleControl, DoubleTarget]), + (Gate.ISwap, "iswap", ir.ISwap, [DoubleTarget]), + (Gate.PSwap, "pswap", ir.PSwap, [DoubleTarget, Angle]), + (Gate.PhaseShift, "phaseshift", ir.PhaseShift, [SingleTarget, Angle]), + (Gate.CPhaseShift, "cphaseshift", ir.CPhaseShift, [SingleControl, SingleTarget, Angle]), + (Gate.CPhaseShift00, "cphaseshift00", ir.CPhaseShift00, [SingleControl, SingleTarget, Angle]), + (Gate.CPhaseShift01, "cphaseshift01", ir.CPhaseShift01, [SingleControl, SingleTarget, Angle]), + (Gate.CPhaseShift10, "cphaseshift10", ir.CPhaseShift10, [SingleControl, SingleTarget, Angle]), + (Gate.CY, "cy", ir.CY, [SingleTarget, SingleControl]), + (Gate.CZ, "cz", ir.CZ, [SingleTarget, SingleControl]), + (Gate.XX, "xx", ir.XX, [DoubleTarget, Angle]), + (Gate.YY, "yy", ir.YY, [DoubleTarget, Angle]), + (Gate.ZZ, "zz", ir.ZZ, [DoubleTarget, Angle]), +] + + +def single_target_valid_input(): + return {"target": 2} + + +def double_target_valid_input(): + return {"targets": [2, 3]} + + +def angle_valid_input(): + return {"angle": 0.123} + + +def single_control_valid_input(): + return {"control": 0} + + +def double_control_valid_input(): + return {"controls": [0, 1]} + + +valid_ir_switcher = { + "SingleTarget": single_target_valid_input, + "DoubleTarget": double_target_valid_input, + "Angle": angle_valid_input, + "SingleControl": single_control_valid_input, + "DoubleControl": double_control_valid_input, +} + + +def create_valid_ir_input(irsubclasses): + input = {} + for subclass in irsubclasses: + input.update(valid_ir_switcher.get(subclass.__name__, lambda: "Invalid subclass")()) + 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: + if subclass == SingleTarget: + qubit_set.extend(list(single_target_valid_input().values())) + elif subclass == DoubleTarget: + qubit_set.extend(list(double_target_valid_input().values())) + elif subclass == SingleControl: + qubit_set = list(single_control_valid_input().values()) + qubit_set + elif subclass == DoubleControl: + qubit_set = list(double_control_valid_input().values()) + qubit_set + elif subclass == Angle: + pass + else: + raise ValueError("Invalid subclass") + input["target"] = QubitSet(qubit_set) + return input + + +def create_valid_gate_class_input(irsubclasses): + input = {} + if Angle in irsubclasses: + input.update(angle_valid_input()) + return input + + +def create_valid_instruction_input(testclass, irsubclasses): + input = create_valid_target_input(irsubclasses) + input["operator"] = testclass(**create_valid_gate_class_input(irsubclasses)) + return input + + +def calculate_qubit_count(irsubclasses): + qubit_count = 0 + for subclass in irsubclasses: + if subclass == SingleTarget: + qubit_count += 1 + elif subclass == DoubleTarget: + qubit_count += 2 + elif subclass == SingleControl: + qubit_count += 1 + elif subclass == DoubleControl: + qubit_count += 2 + elif subclass == Angle: + pass + else: + raise ValueError("Invalid subclass") + return qubit_count + + +@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses", testdata) +def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses): + expected = irclass(**create_valid_ir_input(irsubclasses)) + actual = testclass(**create_valid_gate_class_input(irsubclasses)).to_ir( + **create_valid_target_input(irsubclasses) + ) + assert actual == expected + + +@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses", testdata) +def test_ir_instruction_level(testclass, subroutine_name, irclass, irsubclasses): + expected = irclass(**create_valid_ir_input(irsubclasses)) + instruction = Instruction(**create_valid_instruction_input(testclass, irsubclasses)) + actual = instruction.to_ir() + assert actual == expected + + +@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses", testdata) +def test_gate_subroutine(testclass, subroutine_name, irclass, irsubclasses): + qubit_count = calculate_qubit_count(irsubclasses) + subroutine = getattr(Circuit(), subroutine_name) + assert subroutine(**create_valid_ir_input(irsubclasses)) == Circuit( + Instruction(**create_valid_instruction_input(testclass, irsubclasses)) + ) + 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)), target=target + ) + ) + subroutine = getattr(Circuit(), subroutine_name) + subroutine_input = {"target": multi_targets} + if Angle in irsubclasses: + subroutine_input.update(angle_valid_input()) + assert subroutine(**subroutine_input) == Circuit(instruction_list) + + +@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses", testdata) +def test_gate_to_matrix(testclass, subroutine_name, irclass, irsubclasses): + gate1 = testclass(**create_valid_gate_class_input(irsubclasses)) + gate2 = testclass(**create_valid_gate_class_input(irsubclasses)) + assert isinstance(gate1.to_matrix(), np.ndarray) + assert gate1.matrix_equivalence(gate2) diff --git a/test/unit_tests/braket/circuits/test_instruction.py b/test/unit_tests/braket/circuits/test_instruction.py new file mode 100644 index 00000000..136fb175 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_instruction.py @@ -0,0 +1,129 @@ +# Copyright 2019-2019 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 Gate, Instruction, Qubit, QubitSet + + +@pytest.fixture +def instr(): + return Instruction(Gate.H(), 0) + + +@pytest.fixture +def cnot(): + return Instruction(Gate.CNot(), [0, 1]) + + +@pytest.mark.xfail(raises=ValueError) +def test_empty_operator(): + Instruction(None, target=0) + + +def test_init_with_qubits(): + target = QubitSet([0, 1]) + instr = Instruction(Gate.CNot(), target) + assert instr.target == target + + +def test_init_with_qubit(): + target = Qubit(0) + instr = Instruction(Gate.H(), target) + assert instr.target == QubitSet(0) + + +def test_init_with_int(): + target = 0 + instr = Instruction(Gate.H(), target) + assert instr.target == QubitSet(0) + + +def test_init_with_sequence(): + target = [0, Qubit(1), 2] + instr = Instruction(Gate.CNot(), target) + assert instr.target == QubitSet([0, 1, 2]) + + +def test_getters(): + target = [0, 1] + operator = Gate.CNot() + instr = Instruction(operator, target) + + assert instr.operator == operator + assert instr.target == QubitSet([0, 1]) + + +@pytest.mark.xfail(raises=AttributeError) +def test_operator_setter(instr): + instr.operator = Gate.H() + + +@pytest.mark.xfail(raises=AttributeError) +def test_target_setter(instr): + instr.target = QubitSet(0) + + +def test_str(instr): + expected = "Instruction('operator': {}, 'target': {})".format(instr.operator, instr.target) + assert str(instr) == expected + + +def test_equality(): + instr_1 = Instruction(Gate.H(), 0) + instr_2 = Instruction(Gate.H(), 0) + other_instr = Instruction(Gate.CNot(), [0, 1]) + non_instr = "non instruction" + + assert instr_1 == instr_2 + assert instr_1 is not instr_2 + assert instr_1 != other_instr + assert instr_1 != non_instr + + +def test_to_ir(): + expected_target = QubitSet([0, 1]) + expected_ir = "foo bar value" + + class FooGate(Gate): + def __init__(self): + super().__init__(qubit_count=2, ascii_symbols=["foo", "bar"]) + + def to_ir(self, target): + assert target == expected_target + return expected_ir + + instr = Instruction(FooGate(), expected_target) + assert instr.to_ir() == expected_ir + + +def test_copy_creates_new_object(instr): + copy = instr.copy() + assert copy == instr + assert copy is not instr + + +def test_copy_with_mapping(cnot): + target_mapping = {0: 10, 1: 11} + expected = Instruction(Gate.CNot(), [10, 11]) + assert cnot.copy(target_mapping=target_mapping) == expected + + +def test_copy_with_target(cnot): + target = [10, 11] + expected = Instruction(Gate.CNot(), target) + assert cnot.copy(target=target) == expected + + +@pytest.mark.xfail(raises=TypeError) +def test_copy_with_target_and_mapping(instr): + instr.copy(target=[10], target_mapping={0: 10}) diff --git a/test/unit_tests/braket/circuits/test_moments.py b/test/unit_tests/braket/circuits/test_moments.py new file mode 100644 index 00000000..fc22270c --- /dev/null +++ b/test/unit_tests/braket/circuits/test_moments.py @@ -0,0 +1,170 @@ +# Copyright 2019-2019 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 import OrderedDict + +import pytest +from braket.circuits import Gate, Instruction, Moments, MomentsKey, QubitSet + + +def cnot(q1, q2): + return Instruction(Gate.CNot(), [q1, q2]) + + +def h(q): + return Instruction(Gate.H(), q) + + +@pytest.fixture +def moments(): + return Moments([h(0), h(0)]) + + +def test_add(): + moments = Moments() + moments.add([h(0)]) + moments.add([h(0)]) + + expected = OrderedDict() + expected[MomentsKey(0, QubitSet(0))] = h(0) + expected[MomentsKey(1, QubitSet(0))] = h(0) + assert OrderedDict(moments) == expected + + +def test_default_constructor(): + moments = Moments() + assert OrderedDict(moments) == OrderedDict() + assert moments.depth == 0 + assert moments.qubits == QubitSet() + + +def test_constructor_with_instructions(): + moments = Moments([h(0), h(1)]) + expected = Moments() + expected.add([h(0)]) + expected.add([h(1)]) + assert moments == expected + + +def test_depth(): + moments = Moments([h(0), h(1)]) + assert moments.depth == 1 + + moments.add([cnot(0, 2), h(3)]) + assert moments.depth == 2 + + +@pytest.mark.xfail(raises=AttributeError) +def test_depth_setter(moments): + moments.depth = 5 + + +def test_overlaping_qubits(): + moments = Moments([h(0), h(0)]) + assert moments.depth == 2 + + moments.add([cnot(0, 2), h(1)]) + assert moments.depth == 4 + + +def test_qubits(): + moments = Moments([h(0), h(10), h(5)]) + expected = QubitSet([0, 10, 5]) + assert moments.qubits == expected + assert moments.qubit_count == len(expected) + + +@pytest.mark.xfail(raises=AttributeError) +def test_qubits_setter(moments): + moments.qubits = QubitSet(1) + + +@pytest.mark.xfail(raises=AttributeError) +def test_qubit_count_setter(moments): + moments.qubit_count = 1 + + +def test_time_slices(): + moments = Moments([h(0), h(1), cnot(0, 1)]) + expected = {0: [h(0), h(1)], 1: [cnot(0, 1)]} + assert moments.time_slices() == expected + + +def test_keys(): + moments = Moments([h(0), h(0), h(1)]) + expected = [MomentsKey(0, QubitSet(0)), MomentsKey(1, QubitSet(0)), MomentsKey(0, QubitSet(1))] + assert list(moments.keys()) == expected + + +def test_items(): + moments = Moments([h(0), h(0), h(1)]) + expected = [ + (MomentsKey(0, QubitSet(0)), h(0)), + (MomentsKey(1, QubitSet(0)), h(0)), + (MomentsKey(0, QubitSet(1)), h(1)), + ] + assert list(moments.items()) == expected + + +def test_values(): + moments = Moments([h(0), h(0), h(1)]) + expected = [h(0), h(0), h(1)] + assert list(moments.values()) == expected + + +def test_get(): + moments = Moments([h(0)]) + unknown_key = MomentsKey(100, QubitSet(100)) + assert moments.get(MomentsKey(0, QubitSet(0))) == h(0) + assert moments.get(unknown_key) is None + assert moments.get(unknown_key, h(0)) == h(0) + + +def test_getitem(): + moments = Moments([h(0)]) + assert moments[MomentsKey(0, QubitSet(0))] == h(0) + + +def test_iter(moments): + assert [key for key in moments] == list(moments.keys()) + + +def test_len(): + moments = Moments([h(0), h(0)]) + assert len(moments) == 2 + + +def test_contains(): + moments = Moments([h(0), h(0)]) + assert MomentsKey(0, QubitSet(0)) in moments + assert MomentsKey(0, QubitSet(100)) not in moments + + +def test_equals(): + moments_1 = Moments([h(0)]) + moments_2 = Moments([h(0)]) + other_moments = Moments([h(1)]) + non_moments = "non moments" + + assert moments_1 == moments_2 + assert moments_1 is not moments_2 + assert moments_1 != other_moments + assert moments_1 != non_moments + + +def test_repr(moments): + assert repr(moments) == repr(OrderedDict(moments)) + + +def test_str(moments): + assert str(moments) == str(OrderedDict(moments)) diff --git a/test/unit_tests/braket/circuits/test_qubit.py b/test/unit_tests/braket/circuits/test_qubit.py new file mode 100644 index 00000000..7af83a9b --- /dev/null +++ b/test/unit_tests/braket/circuits/test_qubit.py @@ -0,0 +1,54 @@ +# Copyright 2019-2019 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 Qubit + + +@pytest.fixture +def qubit(): + return Qubit(5) + + +@pytest.mark.xfail(raises=ValueError) +def test_index_lt_zero(): + Qubit(-1) + + +@pytest.mark.xfail(raises=TypeError) +def test_index_non_int(): + Qubit("not a number") + + +def test_index_gte_zero(): + Qubit(0) + Qubit(5) + + +def test_str(qubit): + expected = "Qubit({})".format(int(qubit)) + assert str(qubit) == expected + + +def test_new_with_qubit(): + qubit = Qubit(0) + qubit_new = Qubit.new(qubit) + assert qubit_new == qubit + assert qubit_new is qubit + + +def test_new_with_int(): + qubit = 0 + qubit_new = Qubit.new(qubit) + assert qubit_new == qubit + assert qubit_new is not qubit diff --git a/test/unit_tests/braket/circuits/test_qubit_set.py b/test/unit_tests/braket/circuits/test_qubit_set.py new file mode 100644 index 00000000..e1666f1c --- /dev/null +++ b/test/unit_tests/braket/circuits/test_qubit_set.py @@ -0,0 +1,64 @@ +# Copyright 2019-2019 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 Qubit, QubitSet + + +@pytest.fixture +def qubits(): + return QubitSet([0, Qubit(1)]) + + +def test_qubit_set_is_ordered_set(): + qubits = QubitSet([1, 2, 3, 1, 2, 3]) + assert qubits == QubitSet([1, 2, 3]) + + +def test_default_input(): + assert QubitSet() == QubitSet([]) + + +def test_with_single(): + assert QubitSet(0) == tuple([Qubit(0)]) + + +def test_with_iterable(): + assert QubitSet([0, 1]) == tuple([Qubit(0), Qubit(1)]) + + +def test_with_nested_iterable(): + assert QubitSet([0, 1, [2, 3]]) == tuple([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)]) + + +def test_map_creates_new_object(qubits): + mapped_qubits = qubits.map({}) + assert mapped_qubits == qubits + assert mapped_qubits is not qubits + + +def test_map_happy_case(): + mapping = {Qubit(0): Qubit(11), Qubit(1): Qubit(5)} + qubits = QubitSet([0, 1]) + mapped_qubits = qubits.map(mapping) + + assert mapped_qubits == QubitSet([11, 5]) + + +def test_hash(qubits): + assert hash(qubits) == hash(tuple(qubits)) diff --git a/test/unit_tests/braket/tasks/test_quantum_task_result.py b/test/unit_tests/braket/tasks/test_quantum_task_result.py new file mode 100644 index 00000000..0af6c054 --- /dev/null +++ b/test/unit_tests/braket/tasks/test_quantum_task_result.py @@ -0,0 +1,65 @@ +# Copyright 2019-2019 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 typing import Counter + +import numpy as np +from braket.tasks import QuantumTaskResult + + +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]] + ) + measurement_counts = QuantumTaskResult.measurement_counts_from_measurements(measurements) + expected_counts: Counter = {"1010": 3, "0000": 1, "1000": 2} + assert expected_counts == measurement_counts + + +def test_measurement_probabilities_from_measurement_counts(): + counts = {"00": 1, "01": 1, "10": 1, "11": 97} + probabilities = {"00": 0.01, "01": 0.01, "10": 0.01, "11": 0.97} + + measurement_probabilities = QuantumTaskResult.measurement_probabilities_from_measurement_counts( + counts + ) + + assert measurement_probabilities == probabilities + + +def test_measurements_from_measurement_probabilities(): + shots = 5 + probabilities = {"00": 0.2, "01": 0.2, "10": 0.2, "11": 0.4} + measurements_list = [["0", "0"], ["0", "1"], ["1", "0"], ["1", "1"], ["1", "1"]] + expected_results = np.asarray(measurements_list, dtype=int) + + measurements = QuantumTaskResult.measurements_from_measurement_probabilities( + probabilities, shots + ) + + assert np.allclose(measurements, expected_results) + + +def test_equality(): + measurements_1 = np.array([[0, 0], [1, 1]]) + measurements_2 = np.array([[0, 0], [0, 0]]) + + result_1 = QuantumTaskResult(measurements_1, None, None, False, True, True) + result_2 = QuantumTaskResult(measurements_1, None, None, False, True, True) + other_result = QuantumTaskResult(measurements_2, None, None, False, True, True) + non_result = "not a quantum task result" + + assert result_1 == result_2 + assert result_1 is not result_2 + assert result_1 != other_result + assert result_1 != non_result diff --git a/test/unit_tests/braket/tasks/test_tasks_init.py b/test/unit_tests/braket/tasks/test_tasks_init.py new file mode 100644 index 00000000..65414bc8 --- /dev/null +++ b/test/unit_tests/braket/tasks/test_tasks_init.py @@ -0,0 +1,32 @@ +# Copyright 2019-2019 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 sys +from unittest.mock import patch + +import braket.tasks + + +@patch("braket.ipython_utils.running_in_jupyter") +def test_nest_asyncio_not_applied(running_in_jupyter): + running_in_jupyter.return_value = False + importlib.reload(braket.tasks) + assert "nest_asyncio" not in sys.modules + + +@patch("braket.ipython_utils.running_in_jupyter") +def test_nest_asyncio_is_applied(running_in_jupyter): + running_in_jupyter.return_value = True + importlib.reload(braket.tasks) + assert "nest_asyncio" in sys.modules diff --git a/test/unit_tests/braket/test_ipython_utils.py b/test/unit_tests/braket/test_ipython_utils.py new file mode 100644 index 00000000..ec1006c9 --- /dev/null +++ b/test/unit_tests/braket/test_ipython_utils.py @@ -0,0 +1,53 @@ +# Copyright 2019-2019 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 sys +from unittest.mock import Mock + +import braket.ipython_utils as ipython_utils + + +def test_running_in_jupyter(): + assert not ipython_utils.running_in_jupyter() + + +def test_ipython_imported_but_ipython_none(): + _mock_ipython(None) + assert not ipython_utils.running_in_jupyter() + + +def test_ipython_imported_but_not_in_jupyter(): + _mock_ipython(MockIPython(None)) + assert not ipython_utils.running_in_jupyter() + + +def test_ipython_imported_and_in_jupyter(): + _mock_ipython(MockIPython("non empty kernel")) + assert ipython_utils.running_in_jupyter() + + +def get_ipython(): + pass + + +def _mock_ipython(get_ipython_result): + module = sys.modules["test_ipython_utils"] + sys.modules["IPython"] = module + + get_ipython = Mock(return_value=get_ipython_result) + sys.modules["IPython"].__dict__["get_ipython"] = get_ipython + + +class MockIPython: + def __init__(self, kernel): + self.kernel = kernel diff --git a/tools/braket_default_s3_bucket.cloudformation.yaml b/tools/braket_default_s3_bucket.cloudformation.yaml new file mode 100644 index 00000000..15355a8b --- /dev/null +++ b/tools/braket_default_s3_bucket.cloudformation.yaml @@ -0,0 +1,12 @@ +Description: Default S3 Bucket for Amazon Braket +Resources: + IntegTestBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: + Fn::Sub: "braket-output-${AWS::AccountId}" + AccessControl: Private + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 diff --git a/tools/braket_required_resources.cloudformation.yaml b/tools/braket_required_resources.cloudformation.yaml new file mode 100644 index 00000000..b61247f2 --- /dev/null +++ b/tools/braket_required_resources.cloudformation.yaml @@ -0,0 +1,78 @@ +Description: Required Amazon Braket resources +Resources: + AmazonBraketFullAccess: + Type: "AWS::IAM::Role" + Properties: + # Must be named AQxFullAccess + RoleName: AQxFullAccess + Description: Role that Amazon Braket assumes in order to read / write resources. + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - braket.amazonaws.com + Action: + - sts:AssumeRole + Path: / + Policies: + - PolicyName: AmazonS3Access + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - "s3:PutObject" + - "s3:GetObject" + - "s3:ListBucket" + Resource: "*" + - PolicyName: SagemakerJobAccess + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - "sagemaker:CreateTrainingJob" + - "sagemaker:DescribeTrainingJob" + - "sagemaker:StopTrainingJob" + - "iam:PassRole" + Resource: "*" + + AmazonBraketJobsFullAccess: + Type: "AWS::IAM::Role" + Properties: + # Can be renamed if you desire. This is the role supplied to the create-quantum-job API + RoleName: AmazonBraketJobsFullAccess + Description: Role to supply to Amazon Braket's create-quantum-job API. + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - sagemaker.amazonaws.com + Action: + - sts:AssumeRole + Path: / + Policies: + - PolicyName: SagemakerCreateTrainingJobPermissions + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - "cloudwatch:PutMetricData" + - "logs:CreateLogStream" + - "logs:PutLogEvents" + - "logs:CreateLogGroup" + - "logs:DescribeLogStreams" + - "s3:GetObject" + - "s3:PutObject" + - "s3:ListBucket" + - "ecr:GetAuthorizationToken" + - "ecr:BatchCheckLayerAvailability" + - "ecr:GetDownloadUrlForLayer" + - "ecr:BatchGetImage" + - "aqx:*" + Resource: "*" diff --git a/tools/create_braket_resources.py b/tools/create_braket_resources.py new file mode 100644 index 00000000..6e2f6830 --- /dev/null +++ b/tools/create_braket_resources.py @@ -0,0 +1,95 @@ +import argparse +import json +import os + +import boto3 +import yaml +from botocore.exceptions import ClientError + +REQUIRED_RESOURCES_CFN_FILENAME = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "braket_required_resources.cloudformation.yaml" +) +REQUIRED_RESOURCES_STACK_NAME = "AmazonBraketResources" + +DEFAULT_BUCKET_CFN_FILENAME = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "braket_default_s3_bucket.cloudformation.yaml" +) +DEFAULT_BUCKET_STACK_NAME = "AmazonBraketDefaultS3Bucket" + + +def main(): + parser = argparse.ArgumentParser( + description="Create the required AWS resources for Amazon Braket." + ) + parser.add_argument( + "--create-default-bucket", + dest="create_default_bucket", + action="store_true", + help="Creates a default S3 bucket for you to use with Amazon Braket, " + + "e.g. 'braket-output-${AWS::AccountId}'", + ) + args = parser.parse_args() + + boto_session = boto3.session.Session() + region = boto_session.region_name + cfn_client = boto_session.client("cloudformation") + + # Create required resources + _create_cfn_stack( + cfn_client, REQUIRED_RESOURCES_STACK_NAME, REQUIRED_RESOURCES_CFN_FILENAME, region + ) + + # Create default S3 bucket if specified + if args.create_default_bucket: + _create_cfn_stack( + cfn_client, DEFAULT_BUCKET_STACK_NAME, DEFAULT_BUCKET_CFN_FILENAME, region + ) + + +def _read_cfn_yaml_file(filename): + """Read the contents of a cloud formation template YAML file and return as a JSON string.""" + with open(filename, "r") as file: + yaml_content = file.read() + return json.dumps(yaml.load(yaml_content, Loader=yaml.BaseLoader)) + + +def _create_cfn_stack(cfn_client, stack_name, body_filename, region): + """ + Creates a CloudFormation stack. If stack already exists then it is deleted and then re-created + with the supplied template body. + """ + + template_body = _read_cfn_yaml_file(body_filename) + + print( + f"\nFollow in console: https://{region}.console.aws.amazon.com/cloudformation/home?" + + f"region={region}#/stacks/stackinfo?stackId={stack_name}" + ) + + try: + cfn_client.describe_stacks(StackName=stack_name) + print(f"Stack {stack_name} already exists, deleting it...") + cfn_client.delete_stack(StackName=stack_name) + + delete_waiter = cfn_client.get_waiter("stack_delete_complete") + delete_waiter.wait(StackName=stack_name) + print(f"Stack {stack_name} deleted") + except ClientError as e: + if e.response["Error"]["Code"] == "ValidationError": + # CFN stack doesn't exist, move on. + pass + else: + raise e + + cfn_client.create_stack( + StackName=stack_name, TemplateBody=template_body, Capabilities=["CAPABILITY_NAMED_IAM"] + ) + + print(f"Creating stack {stack_name}...") + create_waiter = cfn_client.get_waiter("stack_create_complete") + create_waiter.wait(StackName=stack_name) + print(f"Stack {stack_name} created") + + +if __name__ == "__main__": + main() diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..d81e4105 --- /dev/null +++ b/tox.ini @@ -0,0 +1,79 @@ +[tox] +envlist = linters,docs,unit-tests + +[testenv:unit-tests] +basepython = python3.7 +# {posargs} contains additional arguments specified when invoking tox. e.g. tox -- -s -k test_foo.py +commands = + coverage run -m pytest {posargs} + coverage combine + coverage report + coverage html +extras = test + +[testenv:integ-tests] +basepython = python3.7 +# {posargs} contains additional arguments specified when invoking tox. e.g. tox -- -s -k test_foo.py +passenv = + AWS_PROFILE +commands = + pytest test/integ_tests {posargs} +extras = test + +[testenv:linters] +basepython = python3.7 +skip_install = true +deps = + {[testenv:black]deps} + {[testenv:isort]deps} + {[testenv:flake8]deps} +commands = + {[testenv:black]commands} + {[testenv:isort]commands} + {[testenv:flake8]commands} + +[testenv:flake8] +basepython = python3.7 +skip_install = true +deps = + flake8 +commands = + flake8 {posargs} + +[testenv:isort] +basepython = python3.7 +skip_install = true +deps = + isort +commands = + isort -rc . {posargs} + +[testenv:black] +basepython = python3.7 +skip_install = true +deps = + black +commands = + black ./ {posargs} + +[testenv:docs] +basepython = python3.7 +deps = + sphinx + sphinx-rtd-theme +commands = + sphinx-apidoc -e -f -M --implicit-namespaces -H "API Reference" -o doc/_apidoc src/braket + sphinx-build -E -T -b html doc build/documentation/html + +[testenv:serve-docs] +basepython = python3.7 +skip_install = true +changedir = build/documentation/html +commands = + python -m http.server {posargs} + +[testenv:zip-build] +basepython = python3.7 +skip_install = true +commands = + /usr/bin/tar -czvf build_files.tar.gz build/ From 27a5e92c83e15eb55e15a6e8dbb87094e8e2c007 Mon Sep 17 00:00:00 2001 From: Derek Date: Mon, 16 Dec 2019 15:11:40 -0800 Subject: [PATCH 0003/1165] Added branch name to installation steps (#9) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 99d623be..d4162cbf 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,11 @@ export AWS_PROFILE=YOUR_PROFILE_NAME 4. Install `braket-python-sdk` package. ```bash -git clone https://github.com/aws/braket-python-sdk.git +git clone https://github.com/aws/braket-python-sdk.git --branch stable/latest pip install -e braket-python-sdk ``` -To install test dependencies for running tests locally run: +If you want to run tests and / or do any development on the framework run: ```bash pip install -e "braket-python-sdk[test]" ``` From 7689c8520ff68efeb83e6a697d5451cc9b1419bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Derek=20Bolt=F0=9F=90=BE?= Date: Tue, 17 Dec 2019 17:14:55 -0800 Subject: [PATCH 0004/1165] No longer copy profile name when copying session Create-quantum-job was throwing "botocore.exceptions.ProfileNotFound: The config profile (default) could not be found" since the container does not have a profile "default" configuration. There are some potential workarounds but the easiest is to just remove setting the profile name all together. Since we are already copying the credentials and region there really is no value in copying the profile hence this change. --- src/braket/aws/aws_qpu.py | 1 - test/unit_tests/braket/aws/test_aws_qpu.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py index 3fc0f82e..835612b6 100644 --- a/src/braket/aws/aws_qpu.py +++ b/src/braket/aws/aws_qpu.py @@ -129,7 +129,6 @@ def _aws_session_for_qpu(self, qpu_arn: str, aws_session: AwsSession) -> AwsSess aws_access_key_id=creds.access_key, aws_secret_access_key=creds.secret_key, aws_session_token=creds.token, - profile_name=aws_session.boto_session.profile_name, region_name=qpu_regions[0], ) return AwsSession(boto_session=boto_session) diff --git a/test/unit_tests/braket/aws/test_aws_qpu.py b/test/unit_tests/braket/aws/test_aws_qpu.py index 06b7141d..4ffa94dd 100644 --- a/test/unit_tests/braket/aws/test_aws_qpu.py +++ b/test/unit_tests/braket/aws/test_aws_qpu.py @@ -93,12 +93,11 @@ def test_aws_session_in_another_qpu_region( AwsQpu(arn, different_region_aws_session) - # assert creds, profile, and region were correctly supplied + # assert creds, and region were correctly supplied boto_session_init.assert_called_with( aws_access_key_id=creds.access_key, aws_secret_access_key=creds.secret_key, aws_session_token=creds.token, - profile_name=different_region_aws_session.boto_session.profile_name, region_name=region, ) From efc1d7c640eb904fb4b6c44f5a53176ca7948e30 Mon Sep 17 00:00:00 2001 From: Dylan Shields Date: Tue, 17 Dec 2019 23:32:26 -0800 Subject: [PATCH 0005/1165] Use CloudFormation quick-create link instead of tools script for resource creation --- README.md | 8 +- ...aket_default_s3_bucket.cloudformation.yaml | 12 --- ...ket_required_resources.cloudformation.yaml | 78 --------------- tools/create_braket_resources.py | 95 ------------------- 4 files changed, 4 insertions(+), 189 deletions(-) delete mode 100644 tools/braket_default_s3_bucket.cloudformation.yaml delete mode 100644 tools/braket_required_resources.cloudformation.yaml delete mode 100644 tools/create_braket_resources.py diff --git a/README.md b/README.md index d4162cbf..cd583c38 100644 --- a/README.md +++ b/README.md @@ -54,10 +54,10 @@ aws configure add-model --service-model "file://aqx-model.json" --service-name a 6. Create the necessary Amazon Braket resources in your AWS account. -This will create the required resources and a default S3 bucket, `braket-output-${AWS::AccountId}`, for storing Amazon Braket outputs. If you don't want to create a bucket and will create your own than drop the `--create-default-bucket` from the command below. -```bash -python tools/create_braket_resources.py --create-default-bucket -``` +Follow the link below to create the resources using CloudFormation. This will create the required IAM resources and a default S3 bucket, `braket-output-${AWS::AccountId}`, for storing Amazon Braket outputs. + +[Quick-Create in AWS CloudFormation](https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https://braket-external-assets-prod-us-west-2.s3-us-west-2.amazonaws.com/templates/braket-resources.yaml&stackName=BraketResources) + 7. You can now call Amazon Braket from the `braket-python-sdk`. diff --git a/tools/braket_default_s3_bucket.cloudformation.yaml b/tools/braket_default_s3_bucket.cloudformation.yaml deleted file mode 100644 index 15355a8b..00000000 --- a/tools/braket_default_s3_bucket.cloudformation.yaml +++ /dev/null @@ -1,12 +0,0 @@ -Description: Default S3 Bucket for Amazon Braket -Resources: - IntegTestBucket: - Type: AWS::S3::Bucket - Properties: - BucketName: - Fn::Sub: "braket-output-${AWS::AccountId}" - AccessControl: Private - BucketEncryption: - ServerSideEncryptionConfiguration: - - ServerSideEncryptionByDefault: - SSEAlgorithm: AES256 diff --git a/tools/braket_required_resources.cloudformation.yaml b/tools/braket_required_resources.cloudformation.yaml deleted file mode 100644 index b61247f2..00000000 --- a/tools/braket_required_resources.cloudformation.yaml +++ /dev/null @@ -1,78 +0,0 @@ -Description: Required Amazon Braket resources -Resources: - AmazonBraketFullAccess: - Type: "AWS::IAM::Role" - Properties: - # Must be named AQxFullAccess - RoleName: AQxFullAccess - Description: Role that Amazon Braket assumes in order to read / write resources. - AssumeRolePolicyDocument: - Version: 2012-10-17 - Statement: - - Effect: Allow - Principal: - Service: - - braket.amazonaws.com - Action: - - sts:AssumeRole - Path: / - Policies: - - PolicyName: AmazonS3Access - PolicyDocument: - Version: 2012-10-17 - Statement: - - Effect: Allow - Action: - - "s3:PutObject" - - "s3:GetObject" - - "s3:ListBucket" - Resource: "*" - - PolicyName: SagemakerJobAccess - PolicyDocument: - Version: 2012-10-17 - Statement: - - Effect: Allow - Action: - - "sagemaker:CreateTrainingJob" - - "sagemaker:DescribeTrainingJob" - - "sagemaker:StopTrainingJob" - - "iam:PassRole" - Resource: "*" - - AmazonBraketJobsFullAccess: - Type: "AWS::IAM::Role" - Properties: - # Can be renamed if you desire. This is the role supplied to the create-quantum-job API - RoleName: AmazonBraketJobsFullAccess - Description: Role to supply to Amazon Braket's create-quantum-job API. - AssumeRolePolicyDocument: - Version: 2012-10-17 - Statement: - - Effect: Allow - Principal: - Service: - - sagemaker.amazonaws.com - Action: - - sts:AssumeRole - Path: / - Policies: - - PolicyName: SagemakerCreateTrainingJobPermissions - PolicyDocument: - Version: 2012-10-17 - Statement: - - Effect: Allow - Action: - - "cloudwatch:PutMetricData" - - "logs:CreateLogStream" - - "logs:PutLogEvents" - - "logs:CreateLogGroup" - - "logs:DescribeLogStreams" - - "s3:GetObject" - - "s3:PutObject" - - "s3:ListBucket" - - "ecr:GetAuthorizationToken" - - "ecr:BatchCheckLayerAvailability" - - "ecr:GetDownloadUrlForLayer" - - "ecr:BatchGetImage" - - "aqx:*" - Resource: "*" diff --git a/tools/create_braket_resources.py b/tools/create_braket_resources.py deleted file mode 100644 index 6e2f6830..00000000 --- a/tools/create_braket_resources.py +++ /dev/null @@ -1,95 +0,0 @@ -import argparse -import json -import os - -import boto3 -import yaml -from botocore.exceptions import ClientError - -REQUIRED_RESOURCES_CFN_FILENAME = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "braket_required_resources.cloudformation.yaml" -) -REQUIRED_RESOURCES_STACK_NAME = "AmazonBraketResources" - -DEFAULT_BUCKET_CFN_FILENAME = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "braket_default_s3_bucket.cloudformation.yaml" -) -DEFAULT_BUCKET_STACK_NAME = "AmazonBraketDefaultS3Bucket" - - -def main(): - parser = argparse.ArgumentParser( - description="Create the required AWS resources for Amazon Braket." - ) - parser.add_argument( - "--create-default-bucket", - dest="create_default_bucket", - action="store_true", - help="Creates a default S3 bucket for you to use with Amazon Braket, " - + "e.g. 'braket-output-${AWS::AccountId}'", - ) - args = parser.parse_args() - - boto_session = boto3.session.Session() - region = boto_session.region_name - cfn_client = boto_session.client("cloudformation") - - # Create required resources - _create_cfn_stack( - cfn_client, REQUIRED_RESOURCES_STACK_NAME, REQUIRED_RESOURCES_CFN_FILENAME, region - ) - - # Create default S3 bucket if specified - if args.create_default_bucket: - _create_cfn_stack( - cfn_client, DEFAULT_BUCKET_STACK_NAME, DEFAULT_BUCKET_CFN_FILENAME, region - ) - - -def _read_cfn_yaml_file(filename): - """Read the contents of a cloud formation template YAML file and return as a JSON string.""" - with open(filename, "r") as file: - yaml_content = file.read() - return json.dumps(yaml.load(yaml_content, Loader=yaml.BaseLoader)) - - -def _create_cfn_stack(cfn_client, stack_name, body_filename, region): - """ - Creates a CloudFormation stack. If stack already exists then it is deleted and then re-created - with the supplied template body. - """ - - template_body = _read_cfn_yaml_file(body_filename) - - print( - f"\nFollow in console: https://{region}.console.aws.amazon.com/cloudformation/home?" - + f"region={region}#/stacks/stackinfo?stackId={stack_name}" - ) - - try: - cfn_client.describe_stacks(StackName=stack_name) - print(f"Stack {stack_name} already exists, deleting it...") - cfn_client.delete_stack(StackName=stack_name) - - delete_waiter = cfn_client.get_waiter("stack_delete_complete") - delete_waiter.wait(StackName=stack_name) - print(f"Stack {stack_name} deleted") - except ClientError as e: - if e.response["Error"]["Code"] == "ValidationError": - # CFN stack doesn't exist, move on. - pass - else: - raise e - - cfn_client.create_stack( - StackName=stack_name, TemplateBody=template_body, Capabilities=["CAPABILITY_NAMED_IAM"] - ) - - print(f"Creating stack {stack_name}...") - create_waiter = cfn_client.get_waiter("stack_create_complete") - create_waiter.wait(StackName=stack_name) - print(f"Stack {stack_name} created") - - -if __name__ == "__main__": - main() From 28ebe6ecf765f217c715025e3b2d877da5c0a114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Derek=20Bolt=F0=9F=90=BE?= Date: Wed, 18 Dec 2019 11:40:21 -0800 Subject: [PATCH 0006/1165] Updated to version v0.1.2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7257d632..712bb295 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="braket-sdk", - version="0.1.1", + version="0.1.2", license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), From 35ca64a3d097211c160f80f16bb338db46d03b62 Mon Sep 17 00:00:00 2001 From: Dylan Shields Date: Mon, 6 Jan 2020 11:16:45 -0800 Subject: [PATCH 0007/1165] Update to use new state vector wire format (#13) --- src/braket/aws/aws_quantum_task_result.py | 8 +++++-- .../test_simulator_quantum_task.py | 14 ++++++++++++- .../braket/aws/common_test_utils.py | 4 ++-- .../aws/test_aws_quantum_task_result.py | 21 +++++++++++++------ 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/braket/aws/aws_quantum_task_result.py b/src/braket/aws/aws_quantum_task_result.py index 250a3a18..b3946116 100644 --- a/src/braket/aws/aws_quantum_task_result.py +++ b/src/braket/aws/aws_quantum_task_result.py @@ -40,11 +40,11 @@ class AwsQuantumTaskResult(QuantumTaskResult): `measurement_probabilities` were copied from device. If false, `measurement_probabilities` are calculated from device data. task_metadata (Dict[str, Any]): Dictionary of task metadata. TODO: Link boto3 docs. - state_vector (Dict[str, float]): Dictionary where key is state and value is probability. + state_vector (Dict[str, complex]): Dictionary where key is state and value is amplitude. """ task_metadata: Dict[str, Any] - state_vector: Dict[str, float] = None + state_vector: Dict[str, complex] = None @staticmethod def from_string(result: str) -> "AwsQuantumTaskResult": @@ -80,6 +80,10 @@ def from_string(result: str) -> "AwsQuantumTaskResult": measurements_copied_from_device = False m_counts_copied_from_device = False m_probabilities_copied_from_device = True + if "StateVector" in json_obj: + state_vector = json_obj.get("StateVector", None) + for state in state_vector: + state_vector[state] = complex(*state_vector[state]) return AwsQuantumTaskResult( state_vector=state_vector, task_metadata=task_metadata, diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 31ab54be..d61228db 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -10,6 +10,8 @@ # 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 pytest from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns from braket.circuits import Circuit @@ -31,7 +33,7 @@ def test_bell_pair(simulator_arn, aws_session, s3_destination_folder): @pytest.mark.parametrize( "simulator_arn", [ # TODO Uncomment out below once proper ordering fix has been applied to QS1 - # AwsQuantumSimulatorArns.QS1, + AwsQuantumSimulatorArns.QS1, AwsQuantumSimulatorArns.QS3 ], ) @@ -49,6 +51,16 @@ def test_qubit_ordering(simulator_arn, aws_session, s3_destination_folder): assert result.measurement_counts.most_common(1)[0][0] == "001" +def test_state_vector(aws_session, s3_destination_folder): + device = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS3, aws_session) + bell = Circuit().h(0).cnot(0, 1) + state_vector = device.run(bell, s3_destination_folder, shots=1).result().state_vector + assert state_vector["00"] == complex(1 / math.sqrt(2), 0) + assert state_vector["01"] == 0 + assert state_vector["10"] == 0 + assert state_vector["11"] == complex(1 / math.sqrt(2), 0) + + def test_qs2_quantum_task(aws_session, s3_destination_folder): device = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS2, aws_session) diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 4e89653b..2ff2ac1e 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -69,7 +69,7 @@ class MockS3: MOCK_S3_RESULT_1 = json.dumps( { - "StateVector": {"00": 0.19999, "01": 0.80000, "10": 0.00000, "11": 0.00001}, + "StateVector": {"00": [0.2, 0.2], "01": [0.3, 0.1], "10": [0.1, 0.3], "11": [0.2, 0.2]}, "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], "TaskMetadata": { "Id": "UUID_blah_1", @@ -83,7 +83,7 @@ class MockS3: MOCK_S3_RESULT_2 = json.dumps( { - "StateVector": {"00": 0.80000, "01": 0.00000, "10": 0.00000, "11": 0.19999}, + "StateVector": {"00": [0.2, 0.2], "01": [0.3, 0.1], "10": [0.1, 0.3], "11": [0.2, 0.2]}, "Measurements": [[0, 0], [0, 0], [0, 0], [1, 1]], "TaskMetadata": { "Id": "UUID_blah_2", diff --git a/test/unit_tests/braket/aws/test_aws_quantum_task_result.py b/test/unit_tests/braket/aws/test_aws_quantum_task_result.py index 0fb5b3ea..4c1a876f 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task_result.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task_result.py @@ -35,19 +35,28 @@ def result_str_3(): return MockS3.MOCK_S3_RESULT_3 -def test_state_vector(): - state_vector: Dict[str, float] = {"00": 0.00, "01": 0.50, "10": 0.50, "11": 0.00} +@pytest.fixture +def parsed_state_vector(): + return { + "00": complex(0.2, 0.2), + "01": complex(0.3, 0.1), + "10": complex(0.1, 0.3), + "11": complex(0.2, 0.2), + } + + +def test_state_vector(parsed_state_vector): result: AwsQuantumTaskResult = AwsQuantumTaskResult( measurements=None, task_metadata=None, - state_vector=state_vector, + state_vector=parsed_state_vector, measurement_counts=None, measurement_probabilities=None, measurements_copied_from_device=False, measurement_counts_copied_from_device=False, measurement_probabilities_copied_from_device=False, ) - assert result.state_vector == state_vector + assert result.state_vector == parsed_state_vector def test_task_metadata(): @@ -72,12 +81,12 @@ def test_task_metadata(): assert result.task_metadata == task_metadata -def test_from_string_measurements(result_str_1): +def test_from_string_measurements(result_str_1, parsed_state_vector): result_obj = json.loads(result_str_1) task_result = AwsQuantumTaskResult.from_string(result_str_1) expected_measurements = np.asarray(result_obj["Measurements"], dtype=int) assert task_result.task_metadata == result_obj["TaskMetadata"] - assert task_result.state_vector == result_obj["StateVector"] + assert task_result.state_vector == parsed_state_vector assert np.array2string(task_result.measurements) == np.array2string(expected_measurements) assert not task_result.measurement_counts_copied_from_device assert not task_result.measurement_probabilities_copied_from_device From bb8b1a43df96c41639e19c05a06b20025327f839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Derek=20Bolt=F0=9F=90=BE?= Date: Wed, 15 Jan 2020 13:28:54 -0800 Subject: [PATCH 0008/1165] Changed Amazon Braket's AWS model name from 'aqx' to 'braket' --- README.md | 9 +++++++-- setup.py | 2 +- src/braket/aws/aws_session.py | 2 +- test/integ_tests/test_simulator_quantum_task.py | 2 +- test/unit_tests/braket/aws/test_aws_session.py | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cd583c38..7d185d09 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,8 @@ pip install -e "braket-python-sdk[test]" 5. Install latest Amazon Braket model in AWS CLI. ```bash -aws s3 cp s3://braket-external-assets-prod-us-west-2/models/aqx-2019-09-01.normal.json aqx-model.json -aws configure add-model --service-model "file://aqx-model.json" --service-name aqx +aws s3 cp s3://braket-external-assets-prod-us-west-2/models/braket-2019-09-01.normal.json braket-model.json +aws configure add-model --service-model "file://braket-model.json" --service-name braket ``` 6. Create the necessary Amazon Braket resources in your AWS account. @@ -88,6 +88,11 @@ python -m ipykernel install --user --name braket jupyter notebook ``` +## Update to latest changes + +1. `cd` into the GitHub repository directory +2. run `git pull` + ## Sample Notebooks TODO diff --git a/setup.py b/setup.py index 712bb295..edefb1ac 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="braket-sdk", - version="0.1.2", + version="0.2.0", license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index c4f8f834..c36130e4 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -52,7 +52,7 @@ def __init__(self, boto_session=None, braket_client=None): f"No braket endpoint for {region}, supported regions are {supported_regions}" ) - self.braket_client = self.boto_session.client("aqx", endpoint_url=endpoint) + self.braket_client = self.boto_session.client("braket", endpoint_url=endpoint) # # Quantum Tasks diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index d61228db..69a3409a 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -34,7 +34,7 @@ def test_bell_pair(simulator_arn, aws_session, s3_destination_folder): "simulator_arn", [ # TODO Uncomment out below once proper ordering fix has been applied to QS1 AwsQuantumSimulatorArns.QS1, - AwsQuantumSimulatorArns.QS3 + AwsQuantumSimulatorArns.QS3, ], ) def test_qubit_ordering(simulator_arn, aws_session, s3_destination_folder): diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index 42f37a52..4a281672 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -55,7 +55,7 @@ def test_uses_endpoint_for_region(boto_session): AwsSession(boto_session=boto_session, braket_client=None) boto_session.client.assert_called_with( - "aqx", endpoint_url=AwsSession.BRAKET_ENDPOINTS[boto_session.region_name] + "braket", endpoint_url=AwsSession.BRAKET_ENDPOINTS[boto_session.region_name] ) From 2162c1297cdcabc02940e6d2236647af5252c2f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Derek=20Bolt=F0=9F=90=BE?= Date: Thu, 16 Jan 2020 09:29:53 -0800 Subject: [PATCH 0009/1165] Removed unnecessary comment --- setup.py | 2 +- test/integ_tests/test_simulator_quantum_task.py | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index edefb1ac..48aa306c 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="braket-sdk", - version="0.2.0", + version="0.2.1", license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 69a3409a..2ccd5b7c 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -31,11 +31,7 @@ def test_bell_pair(simulator_arn, aws_session, s3_destination_folder): @pytest.mark.parametrize( - "simulator_arn", - [ # TODO Uncomment out below once proper ordering fix has been applied to QS1 - AwsQuantumSimulatorArns.QS1, - AwsQuantumSimulatorArns.QS3, - ], + "simulator_arn", [AwsQuantumSimulatorArns.QS1, AwsQuantumSimulatorArns.QS3] ) def test_qubit_ordering(simulator_arn, aws_session, s3_destination_folder): device = AwsQuantumSimulator(simulator_arn, aws_session) From ab1a65d74b084e0346e4e2c9e38bfe9222728dd8 Mon Sep 17 00:00:00 2001 From: kshitijc Date: Mon, 20 Jan 2020 10:19:19 -0800 Subject: [PATCH 0010/1165] Update API Endpoints (#17) --- setup.py | 2 +- src/braket/aws/aws_session.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 48aa306c..c8804110 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="braket-sdk", - version="0.2.1", + version="0.2.2", license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index c36130e4..13f6f721 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -22,9 +22,9 @@ class AwsSession(object): S3DestinationFolder = NamedTuple("S3DestinationFolder", [("bucket", str), ("key", int)]) BRAKET_ENDPOINTS = { - "us-west-1": "https://fdoco1n1x7.execute-api.us-west-1.amazonaws.com/Prod", - "us-west-2": "https://xe15dbdvw6.execute-api.us-west-2.amazonaws.com/Prod", - "us-east-1": "https://kqjovr0n70.execute-api.us-east-1.amazonaws.com/Prod", + "us-west-1": "https://fdoco1n1x7.execute-api.us-west-1.amazonaws.com/V2", + "us-west-2": "https://xe15dbdvw6.execute-api.us-west-2.amazonaws.com/V2", + "us-east-1": "https://kqjovr0n70.execute-api.us-east-1.amazonaws.com/V2", } # similar to sagemaker sdk: From 25d3482cf8a5d6a045619b92fb59f8131c8caa39 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 21 Jan 2020 15:41:33 -0800 Subject: [PATCH 0011/1165] Add XY gate (#18) * Add XY gate * Make zip-build platform-independent --- src/braket/circuits/gates.py | 46 +++++++++++++++++++ test/unit_tests/braket/circuits/test_gates.py | 1 + tox.ini | 2 +- 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index bb82d4e0..abb9b3ee 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -726,6 +726,52 @@ def pswap(targets: QubitSet, angle: float) -> Instruction: Gate.register_gate(PSwap) +class XY(AngledGate): + """XY gate. + + Args: + angle (float): angle in radians. + """ + + def __init__(self, angle: float): + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["XY", "XY"]) + + def to_ir(self, target: QubitSet): + return ir.XY(targets=[target[0], target[1]], angle=self.angle) + + def to_matrix(self): + cos = np.cos(self.angle / 2) + sin = np.sin(self.angle / 2) + return np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, cos, 1.0j * sin, 0.0], + [0.0, 1.0j * sin, cos, 0.0], + [0.0, 0.0, 0.0, 1.0], + ], + dtype=complex, + ) + + @staticmethod + @circuit.subroutine(register=True) + def xy(targets: QubitSet, angle: float) -> Instruction: + """Registers this function into the circuit class. + + Args: + targets (Qubit or int): Target qubit indices. + + Returns: + Instruction: XY instruction. + + Examples: + >>> circ = Circuit().xy(0, 1, 0.15) + """ + return Instruction(Gate.XY(angle), target=targets) + + +Gate.register_gate(XY) + + class CPhaseShift(AngledGate): """Controlled phase shift gate. diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 4ffc8724..f2f6501a 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -44,6 +44,7 @@ (Gate.CSwap, "cswap", ir.CSwap, [SingleControl, DoubleTarget]), (Gate.ISwap, "iswap", ir.ISwap, [DoubleTarget]), (Gate.PSwap, "pswap", ir.PSwap, [DoubleTarget, Angle]), + (Gate.XY, "xy", ir.XY, [DoubleTarget, Angle]), (Gate.PhaseShift, "phaseshift", ir.PhaseShift, [SingleTarget, Angle]), (Gate.CPhaseShift, "cphaseshift", ir.CPhaseShift, [SingleControl, SingleTarget, Angle]), (Gate.CPhaseShift00, "cphaseshift00", ir.CPhaseShift00, [SingleControl, SingleTarget, Angle]), diff --git a/tox.ini b/tox.ini index d81e4105..daec479a 100644 --- a/tox.ini +++ b/tox.ini @@ -76,4 +76,4 @@ commands = basepython = python3.7 skip_install = true commands = - /usr/bin/tar -czvf build_files.tar.gz build/ + /bin/sh -c 'tar -czvf build_files.tar.gz build/' From 64e80229af5b201b21ffad2fb403437085b34749 Mon Sep 17 00:00:00 2001 From: kshitijc Date: Wed, 22 Jan 2020 13:59:21 -0800 Subject: [PATCH 0012/1165] Retrieve GateModelParameters and GateModelProperties from BackendParameters and properties objects (#14) --- setup.py | 2 +- src/braket/aws/aws_qpu.py | 9 +-- src/braket/aws/aws_quantum_task.py | 2 +- .../braket/aws/common_test_utils.py | 57 +++++++++++++++---- test/unit_tests/braket/aws/test_aws_qpu.py | 32 ++++++----- .../braket/aws/test_aws_quantum_task.py | 2 +- 6 files changed, 71 insertions(+), 33 deletions(-) diff --git a/setup.py b/setup.py index c8804110..2da31d90 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="braket-sdk", - version="0.2.2", + version="0.3.0", license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py index 835612b6..e624ae62 100644 --- a/src/braket/aws/aws_qpu.py +++ b/src/braket/aws/aws_qpu.py @@ -90,10 +90,11 @@ def refresh_metadata(self) -> None: self._status = qpu_metadata.get("status") self._status_reason = qpu_metadata.get("statusReason") # TODO: convert string into gate types - self._supported_quantum_operations = qpu_metadata.get("supportedQuantumOperations") - self._qubit_count = qpu_metadata.get("qubitCount") - if "connectivity" in qpu_metadata: - self._connectivity_graph = qpu_metadata.get("connectivity").get("connectivityGraph") + qpu_properties = qpu_metadata.get("properties").get("gateModelProperties") + self._supported_quantum_operations = qpu_properties.get("supportedQuantumOperations") + self._qubit_count = qpu_properties.get("qubitCount") + if "connectivity" in qpu_properties: + self._connectivity_graph = qpu_properties.get("connectivity").get("connectivityGraph") @property def arn(self) -> str: diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index b87da30c..f01b8039 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -73,7 +73,7 @@ def from_circuit( "resultsS3Prefix": s3_destination_folder[1], "ir": circuit.to_ir().json(), "irType": AwsQuantumTask.GATE_IR_TYPE, - "gateModelConfig": {"qubitCount": circuit.qubit_count}, + "backendParameters": {"gateModelParameters": {"qubitCount": circuit.qubit_count}}, "shots": shots, } task_arn = aws_session.create_quantum_task(**create_task_kwargs) diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 2ff2ac1e..dfef8970 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -21,18 +21,30 @@ class MockDevices: MOCK_RIGETTI_QPU_1 = { "arn": AwsQpuArns.RIGETTI, - "qubitCount": 16, - "connectivity": {"connectivityGraph": {"0": ["1", "2"], "1": ["0", "2"], "2": ["0", "1"]}}, - "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "T"], + "properties": { + "gateModelProperties": { + "qubitCount": 16, + "connectivity": { + "connectivityGraph": {"0": ["1", "2"], "1": ["0", "2"], "2": ["0", "1"]} + }, + "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "T"], + } + }, "name": "Rigetti", "status": "AVAILABLE", } MOCK_RIGETTI_QPU_2 = { "arn": AwsQpuArns.RIGETTI, - "qubitCount": 30, - "connectivity": {"connectivityGraph": {"0": ["1", "2"], "1": ["0", "2"], "2": ["0", "1"]}}, - "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "T", "S"], + "properties": { + "gateModelProperties": { + "qubitCount": 30, + "connectivity": { + "connectivityGraph": {"0": ["1", "2"], "1": ["0", "2"], "2": ["0", "1"]} + }, + "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "T", "S"], + } + }, "name": "Rigetti", "status": "UNAVAILABLE", "statusReason": "Under maintenance", @@ -40,8 +52,12 @@ class MockDevices: MOCK_IONQ_QPU = { "arn": AwsQpuArns.IONQ, - "qubitCount": 11, - "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "Toffoli"], + "properties": { + "gateModelProperties": { + "qubitCount": 11, + "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "Toffoli"], + } + }, "name": "IonQ", "status": "UNAVAILABLE", "statusReason": "Under maintenance", @@ -49,16 +65,33 @@ class MockDevices: MOCK_QS1_SIMULATOR_1 = { "arn": AwsQuantumSimulatorArns.QS1, - "qubitCount": 23, - "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "Toffoli"], + "properties": { + "gateModelProperties": { + "qubitCount": 23, + "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "Toffoli"], + } + }, "name": "integ_test_simulator", "status": "AVAILABLE", } MOCK_QS1_SIMULATOR_2 = { "arn": AwsQuantumSimulatorArns.QS1, - "qubitCount": 30, - "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "Toffoli", "Phase", "CPhase"], + "properties": { + "gateModelProperties": { + "qubitCount": 30, + "supportedQuantumOperations": [ + "CNOT", + "H", + "RZ", + "RY", + "RZ", + "Toffoli", + "Phase", + "CPhase", + ], + } + }, "name": "integ_test_simulator", "status": "UNAVAILABLE", "statusReason": "Temporary network issue", diff --git a/test/unit_tests/braket/aws/test_aws_qpu.py b/test/unit_tests/braket/aws/test_aws_qpu.py index 4ffa94dd..23cfd707 100644 --- a/test/unit_tests/braket/aws/test_aws_qpu.py +++ b/test/unit_tests/braket/aws/test_aws_qpu.py @@ -126,13 +126,15 @@ def test_qpu_refresh_metadata_success(aws_session): qpu = AwsQpu(AwsQpuArns.RIGETTI, aws_session) assert qpu.arn == MockDevices.MOCK_RIGETTI_QPU_1.get("arn") assert qpu.name == MockDevices.MOCK_RIGETTI_QPU_1.get("name") - assert qpu.qubit_count == MockDevices.MOCK_RIGETTI_QPU_1.get("qubitCount") - assert qpu.connectivity_graph == MockDevices.MOCK_RIGETTI_QPU_1.get("connectivity").get( - "connectivityGraph" - ) - assert qpu.supported_quantum_operations == MockDevices.MOCK_RIGETTI_QPU_1.get( - "supportedQuantumOperations" - ) + assert qpu.qubit_count == MockDevices.MOCK_RIGETTI_QPU_1.get("properties").get( + "gateModelProperties" + ).get("qubitCount") + assert qpu.connectivity_graph == MockDevices.MOCK_RIGETTI_QPU_1.get("properties").get( + "gateModelProperties" + ).get("connectivity").get("connectivityGraph") + assert qpu.supported_quantum_operations == MockDevices.MOCK_RIGETTI_QPU_1.get("properties").get( + "gateModelProperties" + ).get("supportedQuantumOperations") assert qpu.status == MockDevices.MOCK_RIGETTI_QPU_1.get("status") assert qpu.status_reason is None @@ -141,13 +143,15 @@ def test_qpu_refresh_metadata_success(aws_session): qpu.refresh_metadata() assert qpu.arn == MockDevices.MOCK_RIGETTI_QPU_2.get("arn") assert qpu.name == MockDevices.MOCK_RIGETTI_QPU_2.get("name") - assert qpu.qubit_count == MockDevices.MOCK_RIGETTI_QPU_2.get("qubitCount") - assert qpu.connectivity_graph == MockDevices.MOCK_RIGETTI_QPU_2.get("connectivity").get( - "connectivityGraph" - ) - assert qpu.supported_quantum_operations == MockDevices.MOCK_RIGETTI_QPU_2.get( - "supportedQuantumOperations" - ) + assert qpu.qubit_count == MockDevices.MOCK_RIGETTI_QPU_2.get("properties").get( + "gateModelProperties" + ).get("qubitCount") + assert qpu.connectivity_graph == MockDevices.MOCK_RIGETTI_QPU_2.get("properties").get( + "gateModelProperties" + ).get("connectivity").get("connectivityGraph") + assert qpu.supported_quantum_operations == MockDevices.MOCK_RIGETTI_QPU_2.get("properties").get( + "gateModelProperties" + ).get("supportedQuantumOperations") assert qpu.status == MockDevices.MOCK_RIGETTI_QPU_2.get("status") assert qpu.status_reason == MockDevices.MOCK_RIGETTI_QPU_2.get("statusReason") 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 b62a0274..250cabe4 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -219,7 +219,7 @@ def _assert_create_quantum_task_called_with(aws_session, arn, circuit, s3_result "resultsS3Prefix": s3_results_prefix[1], "ir": circuit.to_ir().json(), "irType": AwsQuantumTask.GATE_IR_TYPE, - "gateModelConfig": {"qubitCount": circuit.qubit_count}, + "backendParameters": {"gateModelParameters": {"qubitCount": circuit.qubit_count}}, "shots": AwsQuantumTask.DEFAULT_SHOTS, } ) From 068b331cc92b682dbd2e0c4bb47a2a2bcda5b54f Mon Sep 17 00:00:00 2001 From: kshitijc Date: Thu, 23 Jan 2020 16:08:38 -0800 Subject: [PATCH 0013/1165] Modify AwsQpu and AwsQuantumSimulator to add generic properties field (#19) * Modify AwsQpu and AwsQuantumSimulator to add generic properties field --- src/braket/aws/aws_qpu.py | 41 +++++------ src/braket/aws/aws_qpu_arns.py | 1 + src/braket/aws/aws_quantum_simulator.py | 22 +++--- src/braket/devices/device.py | 16 +--- .../braket/aws/common_test_utils.py | 73 +++++++++++++++++++ test/unit_tests/braket/aws/test_aws_qpu.py | 67 +++++++++-------- .../braket/aws/test_aws_quantum_simulator.py | 8 +- 7 files changed, 146 insertions(+), 82 deletions(-) diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py index e624ae62..626b2599 100644 --- a/src/braket/aws/aws_qpu.py +++ b/src/braket/aws/aws_qpu.py @@ -10,6 +10,8 @@ # 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 typing import Any, Dict + import boto3 from braket.aws.aws_qpu_arns import AwsQpuArns from braket.aws.aws_quantum_task import AwsQuantumTask @@ -23,7 +25,11 @@ class AwsQpu(Device): Use this class to retrieve the latest metadata about the QPU, and run a circuit on the QPU. """ - QPU_REGIONS = {AwsQpuArns.RIGETTI: ["us-west-1"], AwsQpuArns.IONQ: ["us-east-1"]} + QPU_REGIONS = { + AwsQpuArns.RIGETTI: ["us-west-1"], + AwsQpuArns.IONQ: ["us-east-1"], + AwsQpuArns.DWAVE: ["us-west-2"], + } def __init__(self, arn: str, aws_session=None): """ @@ -41,15 +47,10 @@ def __init__(self, arn: str, aws_session=None): See `braket.aws.aws_qpu.AwsQpu.QPU_REGIONS` for the regions the QPUs are located in. """ - super().__init__( - name=None, status=None, status_reason=None, supported_quantum_operations=None - ) + super().__init__(name=None, status=None, status_reason=None) self._arn = arn self._aws_session = self._aws_session_for_qpu(arn, aws_session) - self._qubit_count: int = None - # TODO: convert into graph object of qubits, type TBD - self._connectivity_graph = None - + self._properties = None self.refresh_metadata() def run(self, *aws_quantum_task_args, **aws_quantum_task_kwargs) -> AwsQuantumTask: @@ -89,12 +90,12 @@ def refresh_metadata(self) -> None: self._name = qpu_metadata.get("name") self._status = qpu_metadata.get("status") self._status_reason = qpu_metadata.get("statusReason") - # TODO: convert string into gate types - qpu_properties = qpu_metadata.get("properties").get("gateModelProperties") - self._supported_quantum_operations = qpu_properties.get("supportedQuantumOperations") - self._qubit_count = qpu_properties.get("qubitCount") - if "connectivity" in qpu_properties: - self._connectivity_graph = qpu_properties.get("connectivity").get("connectivityGraph") + qpu_properties = qpu_metadata.get("properties") + self._properties = ( + qpu_properties.get("annealingModelProperties", {}).get("dWaveProperties") + if "annealingModelProperties" in qpu_properties + else qpu_properties.get("gateModelProperties") + ) @property def arn(self) -> str: @@ -102,14 +103,10 @@ def arn(self) -> str: return self._arn @property - def qubit_count(self) -> int: - """int: Return maximum number of qubits that can be run on QPU.""" - return self._qubit_count - - @property - def connectivity_graph(self): - """Return connectivity graph of QPU.""" - return self._connectivity_graph + # TODO: Add a link to the boto3 docs + def properties(self) -> Dict[str, Any]: + """Dict[str, Any]: Return the qpu specific properties""" + return self._properties def _aws_session_for_qpu(self, qpu_arn: str, aws_session: AwsSession) -> AwsSession: """ diff --git a/src/braket/aws/aws_qpu_arns.py b/src/braket/aws/aws_qpu_arns.py index 404c2103..514ad15f 100644 --- a/src/braket/aws/aws_qpu_arns.py +++ b/src/braket/aws/aws_qpu_arns.py @@ -17,3 +17,4 @@ class AwsQpuArns(str, Enum): RIGETTI = "arn:aws:aqx:::qpu:rigetti" IONQ = "arn:aws:aqx:::qpu:ionq" + DWAVE = "arn:aws:aqx:::qpu:d-wave" diff --git a/src/braket/aws/aws_quantum_simulator.py b/src/braket/aws/aws_quantum_simulator.py index fd8795be..dc83e870 100644 --- a/src/braket/aws/aws_quantum_simulator.py +++ b/src/braket/aws/aws_quantum_simulator.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 typing import Any, Dict + from braket.aws.aws_quantum_task import AwsQuantumTask from braket.aws.aws_session import AwsSession from braket.devices.device import Device @@ -29,13 +31,10 @@ def __init__(self, arn: str, aws_session=None): arn (str): Simulator type ARN e.g. "arn:aws:aqx:::quantum-simulator:aqx:qs1". aws_session (AwsSession, optional) aws_session: AWS session object. Default = None. """ - super().__init__( - name=None, status=None, status_reason=None, supported_quantum_operations=None - ) + super().__init__(name=None, status=None, status_reason=None) self._arn = arn self._aws_session = aws_session or AwsSession() - self._qubit_count: int = None - + self._properties: Dict[str, Any] = None self.refresh_metadata() def run(self, *aws_quantum_task_args, **aws_quantum_task_kwargs) -> AwsQuantumTask: @@ -73,9 +72,9 @@ def refresh_metadata(self) -> None: self._name = simulator_metadata.get("name") self._status = simulator_metadata.get("status") self._status_reason = simulator_metadata.get("statusReason") - # TODO: convert string into gate types - self._supported_quantum_operations = simulator_metadata.get("supportedQuantumOperations") - self._qubit_count = simulator_metadata.get("qubitCount") + self._properties = { + k: simulator_metadata.get(k) for k in ["supportedQuantumOperations", "qubitCount"] + } @property def arn(self) -> str: @@ -83,9 +82,10 @@ def arn(self) -> str: return self._arn @property - def qubit_count(self) -> int: - """int: Return maximum number of qubits that can be run on simulator.""" - return self._qubit_count + # TODO: Add a link to the boto3 docs + def properties(self) -> Dict[str, Any]: + """Dict[str, Any]: Return the simulator specific properties""" + return self._properties def __repr__(self): return f"QuantumSimulator('name': {self.name}, 'arn': {self.arn})" diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 05d469d7..92dea104 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -12,7 +12,6 @@ # language governing permissions and limitations under the License. from abc import ABC, abstractmethod -from typing import List from braket.tasks.quantum_task import QuantumTask @@ -24,16 +23,12 @@ class Device(ABC): :param str name: name of quantum device :param str status: status of quantum device :param str status_reason: status reason of quantum device - :param List[str] supported_quantum_operations: supported quantum operations of quantum device """ - def __init__( - self, name: str, status: str, status_reason: str, supported_quantum_operations: List[str] - ): + def __init__(self, name: str, status: str, status_reason: str): self._name = name self._status = status self._status_reason = status_reason - self._supported_quantum_operations = supported_quantum_operations @abstractmethod def run(self, circuit, shots: int) -> QuantumTask: @@ -72,12 +67,3 @@ def status_reason(self) -> str: :rtype: str """ return self._status_reason - - @property - def supported_quantum_operations(self) -> List[str]: - """ - Return supported quantum operations of Device - - :rtype: List[str] - """ - return self._supported_quantum_operations diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index dfef8970..9ae63d6d 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -50,6 +50,79 @@ class MockDevices: "statusReason": "Under maintenance", } + MOCK_DWAVE_QPU_1 = { + "arn": AwsQpuArns.DWAVE, + "properties": { + "annealingModelProperties": { + "dWaveProperties": { + "activeQubitCount": 2, + "annealingOffsetStep": 2.0, + "annealingOffsetStepPhi0": 4.0, + "annealingOffsetRanges": [[1.34, 5.23], [3.24, 1.44]], + "annealingDurationRange": [3, 5], + "couplers": [[1, 2]], + "defaultAnnealingDuration": 4, + "defaultProgrammingThermalizationDuration": 2, + "defaultReadoutThermalizationDuration": 1, + "extendedJRange": [3.0, 4.0], + "hGainScheduleRange": [2.0, 3.0], + "hRange": [3.4, 5.6], + "jRange": [1.0, 2.0], + "maximumAnnealingSchedulePoints": 3, + "maximumHGainSchedulePoints": 2, + "qubitCount": 3, + "qubits": [0, 2], + "perQubitCouplingRange": [1.0, 3.0], + "programmingThermalizationDurationRange": [1, 2], + "quotaConversionRate": 2.5, + "readoutThermalizationDurationRange": [4, 6], + "shotsRange": [3, 5], + "taskRunDurationRange": [3, 6], + "topology": {"type": "chimera", "topology": [1, 1, 1]}, + } + } + }, + "name": "D-Wave", + "status": "AVAILABLE", + } + + MOCK_DWAVE_QPU_2 = { + "arn": AwsQpuArns.DWAVE, + "properties": { + "annealingModelProperties": { + "dWaveProperties": { + "activeQubitCount": 3, + "annealingOffsetStep": 2.0, + "annealingOffsetStepPhi0": 4.0, + "annealingOffsetRanges": [[1.34, 5.23], [3.24, 1.44]], + "annealingDurationRange": [3, 5], + "couplers": [[1, 2]], + "defaultAnnealingDuration": 4, + "defaultProgrammingThermalizationDuration": 2, + "defaultReadoutThermalizationDuration": 1, + "extendedJRange": [3.0, 4.0], + "hGainScheduleRange": [2.0, 3.0], + "hRange": [3.4, 5.6], + "jRange": [1.0, 2.0], + "maximumAnnealingSchedulePoints": 3, + "maximumHGainSchedulePoints": 2, + "qubitCount": 3, + "qubits": [0, 1, 2], + "perQubitCouplingRange": [1.0, 3.0], + "programmingThermalizationDurationRange": [1, 2], + "quotaConversionRate": 2.5, + "readoutThermalizationDurationRange": [4, 6], + "shotsRange": [3, 5], + "taskRunDurationRange": [3, 6], + "topology": {"type": "chimera", "topology": [1, 1, 1]}, + } + } + }, + "name": "D-Wave", + "status": "UNAVAILABLE", + "statusReason": "Under maintenance", + } + MOCK_IONQ_QPU = { "arn": AwsQpuArns.IONQ, "properties": { diff --git a/test/unit_tests/braket/aws/test_aws_qpu.py b/test/unit_tests/braket/aws/test_aws_qpu.py index 23cfd707..460f428e 100644 --- a/test/unit_tests/braket/aws/test_aws_qpu.py +++ b/test/unit_tests/braket/aws/test_aws_qpu.py @@ -121,39 +121,34 @@ def test_no_aws_session_supplied(boto_session_init, aws_session_init, boto_sessi aws_session.get_qpu_metadata.assert_called_with(arn) -def test_qpu_refresh_metadata_success(aws_session): - aws_session.get_qpu_metadata.return_value = MockDevices.MOCK_RIGETTI_QPU_1 - qpu = AwsQpu(AwsQpuArns.RIGETTI, aws_session) - assert qpu.arn == MockDevices.MOCK_RIGETTI_QPU_1.get("arn") - assert qpu.name == MockDevices.MOCK_RIGETTI_QPU_1.get("name") - assert qpu.qubit_count == MockDevices.MOCK_RIGETTI_QPU_1.get("properties").get( - "gateModelProperties" - ).get("qubitCount") - assert qpu.connectivity_graph == MockDevices.MOCK_RIGETTI_QPU_1.get("properties").get( - "gateModelProperties" - ).get("connectivity").get("connectivityGraph") - assert qpu.supported_quantum_operations == MockDevices.MOCK_RIGETTI_QPU_1.get("properties").get( - "gateModelProperties" - ).get("supportedQuantumOperations") - assert qpu.status == MockDevices.MOCK_RIGETTI_QPU_1.get("status") - assert qpu.status_reason is None +@pytest.mark.parametrize( + "qpu_arn, properties_keys, initial_qpu_data, modified_qpu_data", + [ + ( + AwsQpuArns.RIGETTI, + ["gateModelProperties"], + MockDevices.MOCK_RIGETTI_QPU_1, + MockDevices.MOCK_RIGETTI_QPU_2, + ), + ( + AwsQpuArns.DWAVE, + ["annealingModelProperties", "dWaveProperties"], + MockDevices.MOCK_DWAVE_QPU_1, + MockDevices.MOCK_DWAVE_QPU_2, + ), + ], +) +def test_qpu_refresh_metadata_success( + aws_session, qpu_arn, properties_keys, initial_qpu_data, modified_qpu_data +): + aws_session.get_qpu_metadata.return_value = initial_qpu_data + qpu = AwsQpu(qpu_arn, aws_session) + _assert_qpu_fields(qpu, properties_keys, initial_qpu_data) # describe_qpus now returns new metadata - aws_session.get_qpu_metadata.return_value = MockDevices.MOCK_RIGETTI_QPU_2 + aws_session.get_qpu_metadata.return_value = modified_qpu_data qpu.refresh_metadata() - assert qpu.arn == MockDevices.MOCK_RIGETTI_QPU_2.get("arn") - assert qpu.name == MockDevices.MOCK_RIGETTI_QPU_2.get("name") - assert qpu.qubit_count == MockDevices.MOCK_RIGETTI_QPU_2.get("properties").get( - "gateModelProperties" - ).get("qubitCount") - assert qpu.connectivity_graph == MockDevices.MOCK_RIGETTI_QPU_2.get("properties").get( - "gateModelProperties" - ).get("connectivity").get("connectivityGraph") - assert qpu.supported_quantum_operations == MockDevices.MOCK_RIGETTI_QPU_2.get("properties").get( - "gateModelProperties" - ).get("supportedQuantumOperations") - assert qpu.status == MockDevices.MOCK_RIGETTI_QPU_2.get("status") - assert qpu.status_reason == MockDevices.MOCK_RIGETTI_QPU_2.get("statusReason") + _assert_qpu_fields(qpu, properties_keys, modified_qpu_data) def test_qpu_refresh_metadata_error(aws_session): @@ -214,3 +209,15 @@ def _run_and_assert(aws_quantum_task_mock, qpu, run_args, run_kwargs): task = qpu.run(*run_args, **run_kwargs) assert task == task_mock aws_quantum_task_mock.assert_called_with(qpu._aws_session, qpu.arn, *run_args, **run_kwargs) + + +def _assert_qpu_fields(qpu, properties_keys, expected_qpu_data): + assert qpu.arn == expected_qpu_data.get("arn") + assert qpu.name == expected_qpu_data.get("name") + expected_qpu_properties = expected_qpu_data.get("properties") + for key in properties_keys: + expected_qpu_properties = expected_qpu_properties.get(key) + for property_name in expected_qpu_properties: + assert qpu.properties[property_name] == expected_qpu_properties.get(property_name) + assert qpu.status == expected_qpu_data.get("status") + assert qpu.status_reason == expected_qpu_data.get("statusReason") diff --git a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py index 92c25067..28c8c9ff 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py @@ -46,8 +46,8 @@ def test_simulator_refresh_metadata_success(): simulator = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS1, mock_session) assert simulator.arn == expected_metadata.get("arn") assert simulator.name == expected_metadata.get("name") - assert simulator.qubit_count == expected_metadata.get("qubitCount") - assert simulator.supported_quantum_operations == expected_metadata.get( + assert simulator.properties["qubitCount"] == expected_metadata.get("qubitCount") + assert simulator.properties["supportedQuantumOperations"] == expected_metadata.get( "supportedQuantumOperations" ) assert simulator.status == expected_metadata.get("status") @@ -59,8 +59,8 @@ def test_simulator_refresh_metadata_success(): simulator.refresh_metadata() assert simulator.arn == expected_metadata.get("arn") assert simulator.name == expected_metadata.get("name") - assert simulator.qubit_count == expected_metadata.get("qubitCount") - assert simulator.supported_quantum_operations == expected_metadata.get( + assert simulator.properties["qubitCount"] == expected_metadata.get("qubitCount") + assert simulator.properties["supportedQuantumOperations"] == expected_metadata.get( "supportedQuantumOperations" ) assert simulator.status == expected_metadata.get("status") From 82b199ad5330c7f224eb72ad342e862769322640 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Mon, 27 Jan 2020 11:28:35 -0800 Subject: [PATCH 0014/1165] Update to README (#20) * Update to README Restructure content to move prereqs to a common section. Copy edit pass for style and language. Formatting changes for hierarchy and organization. * Update readme for private beta Multiple changes throughout to clarify steps to configure one's environment for the beta release. * Update to readme Addressed feedback, corrected a couple of format issues, removed step to create a kernel to simplify. * update to readme Added additional content for using a qpu, incorporated feedback. * restoring section on using a virtenv --- README.md | 215 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 158 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 7d185d09..26d61b5b 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,225 @@ -**DO NOT SHARE OR TALK ABOUT THE CONTENTS OF THIS LIBRARY per the Amazon Beta NDA you signed.** +**This prerelease documentation is confidential and is provided under the terms of your nondisclosure agreement with Amazon Web Services (AWS) or other agreement governing your receipt of AWS confidential information.** -Amazon Braket Python SDK is an open source library for interacting with quantum devices on 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. -## Installation +## Prerequisites +Before you begin working with the Amazon Braket SDK, make sure that you've installed or configured the following prerequisites. -### Prerequisites -- Python 3.7.2+ +### Conda +Use the instructions for installing Conda from https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html. +Choose a regular installation, and then choose the Anaconda installer. Conda also installs Python, which is required for other steps in this document. Download and install the **Python 3.7 version** of Anaconda. -### Steps +### Using a Virtual Environment +For information about Conda and virtual environments, see [Conda vs. pip vs. virtualenv commands](https://docs.conda.io/projects/conda/en/latest/commands.html#conda-vs-pip-vs-virtualenv-commands). -1. Get a confirmation email from Amazon Braket confirming you have access to the service. +#### Creating a virtual environment +Use a virtual environment to interact with Amazon Braket to avoid changes to your global environment variables. The following commands create aand activate a virtual environment in Conda. If you want to use a name for the environment other than `yourenvname`, just change the value to the name you want to use. You may want to create folder in the file system to create the virtual environment. +Use the following command to create a directory +`mkdir braket` -2. (Optional) Recommended to work inside a virtual environment. You can skip this step if you don't care about mucking with your global python dependencies. See [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) if you don't have it already installed. +Then use the following command to move the cursor to the directory you created +`cd braket` ```bash -mkdir braket -cd braket +conda create -n braketvirtenv python=3.7 anaconda +``` +Press **y** when asked to confirm. -virtualenv venv -source venv/bin/activate +Then run the following command to activate the virtual environment. If you changed the environment name in the preceding command, be sure to change in this command as well. +```bash +source activate braketvirtenv ``` - -2. Install [AWS CLI](https://github.com/aws/aws-cli#installation) +### Git +Install Git from https://git-scm.com/downloads. Installation instructions are provided on the download page. + +### Access to Amazon Braket (Private Beta) +You can configure your environment for the Amazon Braket Python SDK, but you need to be granted permission to access Amazon Braket (Private Beta). Before you can execute code against Amazon Braket. If you’ve not received notification that you have been added to the beta, please contact the Braket team for assistance. + +### IAM user or role with required permissions +To perform the steps in this document and to interact with Amazon Braket, use an account with administrator privileges, such as one with the AdministratorAccess policy applied. To learn more about IAM user, roles, and policies, see [Adding and Removing IAM Identity Permissions](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage-attach-detach.html). + +### AWS CLI +#### Install and configure the AWS Command Line Interface (CLI) +Install the [AWS CLI](https://github.com/aws/aws-cli#installation) so that you can interact with AWS via the command line. This is required to perform some steps in this document. Instructions to install and configure the CLI are included on the installation page. + +#### Use the following command to install the AWS CLI ```bash pip install awscli ``` - -3. [Configure AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) settings to have a profile that can access your AWS account. Once a profile has been created set the `AWS_PROFILE` such that all future steps can connect to your AWS account. + +#### Configure a profile for the AWS CLI +Configure a CLI 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). + +After you create a profile, use the following command to set the `AWS_PROFILE` so that all future commands can access your AWS account and resources. ```bash export AWS_PROFILE=YOUR_PROFILE_NAME ``` - -4. Install `braket-python-sdk` package. +### Configure your AWS account with the resources necessary for Amazon Braket +Use the following link to an AWS CloudFormation template to automatically create the resources that Amazon Braket uses or interacts with in your account. The template creates the following resources: +- An S3 bucket to store job output. The bucket is named `braket-output-AWSaccountId` where AWSAccountId is your account ID. For example, if your AWS account ID is 123456789012, the bucket is named `braket-output-123456789012`. +- IAM roles named AmazonBraketJobExecutionRole, which is used to run jobs, and AQxFullAccess which is used to interact with the AWS resources that Amazon Braket needs. +- An IAM policy, AmazonBraketFullAccess, that includes permission to use Amazon Braket actions, as well as the permissions necessary to access the S3 bucket created. If you want to use a role that does not have admin permissions, you can apply the AmazonBraketFullAccess policy to the user or role you are using to grant the permissions required to use Amazon Braket beta. + +If you are already logged in to AWS when you click the link to open the template, the resources are created in the current account. If you want to use Amazon Braket beta in a different account, first log in using that account. + +[Amazon Braket CloudFormation Stack Creation template](https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https://braket-external-assets-prod-us-west-2.s3-us-west-2.amazonaws.com/templates/braket-resources.yaml&stackName=BraketResources) + +When the template loads, select the **I acknowledge that AWS CloudFormation might create IAM resources with custom names** checkbox, and then choose **Create Stack**. + +Wait until the Status changes to **CREATE_COMPLETE**. You may need to refresh the page to see the current status of the stack creation. + +## Setting up the Braket Python SDK +Use the steps in this section to install and configure the Braket Python SDK for your environment. You should perform the steps in the order in which they are included in this document. + +### Install the braket-python-sdk package +Use the following commands to install the Braket Python SDK package. ```bash git clone https://github.com/aws/braket-python-sdk.git --branch stable/latest -pip install -e braket-python-sdk ``` - -If you want to run tests and / or do any development on the framework run: ```bash -pip install -e "braket-python-sdk[test]" +pip install -e braket-python-sdk ``` - -5. Install latest Amazon Braket model in AWS CLI. + +If you receive an error related to SSH, see the following information to create and add an SSH key to your GitHub account. +- [Generate a new SSH key](https://help.github.com/en/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent) +- [Add an SSH Key to Your Account](https://help.github.com/en/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account) + +### Install latest Amazon Braket model in AWS CLI +Run the following commands to install the Braket model. The file is downloaded to the current location. Position the cursor in the folder where you want to download the file before running the command. ```bash aws s3 cp s3://braket-external-assets-prod-us-west-2/models/braket-2019-09-01.normal.json braket-model.json +``` +```bash aws configure add-model --service-model "file://braket-model.json" --service-name braket ``` +## Validate the Configuration +Your environment should now be configured to successfully use the Braket Python SDK to interact with Amazon Braket. You can use the following example to confirm that your settings are correct. -6. Create the necessary Amazon Braket resources in your AWS account. +You can confirm that your environment is correctly configured in either of the following ways: +- Create a Python file +- Use a Jupyter notebook -Follow the link below to create the resources using CloudFormation. This will create the required IAM resources and a default S3 bucket, `braket-output-${AWS::AccountId}`, for storing Amazon Braket outputs. +## Code sample for validating your configuration +Use the following code sample to validate your environment configuration. +```python +from braket.circuits import Circuit +from braket.aws import AwsQuantumSimulator + +device_arn = "arn:aws:aqx:::quantum-simulator:aqx:qs1" +s3_folder = ("braket-output-AWS_ACCOUNT_ID", "folder-name") -[Quick-Create in AWS CloudFormation](https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https://braket-external-assets-prod-us-west-2.s3-us-west-2.amazonaws.com/templates/braket-resources.yaml&stackName=BraketResources) +bell = Circuit().h(0).cnot(0, 1) +print(device.run(bell, s3_folder).result().measurement_counts) +``` +The code sample imports the Amazon Braket framework, then defines the execution environment as the AWSQuantumSimulator and the device to use. The `s3_folder` statement defines the Amazon S3 bucket for job output. It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the job. -7. You can now call Amazon Braket from the `braket-python-sdk`. +### Available Simulators +There are currently three simulators available for Amazon Braket. To specify which simulator to use, change the code sample to replace the value for the `AwsQuantumSimulator` to one of the following values: +- `arn:aws:aqx:::quantum-simulator:aqx:qs1` – A Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the distribution - an array of bit strings that appears as though it came from a quantum computer. Outputs only shots and does not provide a state vector. +- `arn:aws:aqx:::quantum-simulator:aqx:qs2` – a TensorNetwork simulator. Provides an approximation of running a job on a quantum computer. +- `arn:aws:aqx:::quantum-simulator:aqx:qs3` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. This simulator samples from the distribution but includes the entire state vector. This generates more data, and therefore incurs additional costs for storage of data in Amazon S3. +#### To validate your configuration using a Python file +1. Open a text editor. +2. Copy the code sample (above), then paste it into the text editor. +3. Replace the `AWS_ACCOUNT_ID` in the value for `s3_folder` to your 12-digit AWS account ID. It should look similar to the following: + `s3_folder = ("braket-output-123456789012", "folder-name")` +4. Save the file with the name `bellpair.py`. +5. Run the following command to run the Python file: + ```bash + python bellpair.py + ``` +You should see a result similar to the following: +```Counter({'11': 522, '00': 478})``` + +#### To validate your configuration using a Jupyter notebook +See [Installing the Jupyter Software](https://jupyter.org/install) for information about how to install Jupyter. You can use either JupyterLab or classic Jupyter Notebook. + +After you have installed Jupyter, use this command to open a Jupyter notebook so you can use the SDK within the notebook: +```bash +jupyter notebook +``` +Jupyter opens in a browser window. Choose **New**, and then under **Notebooks**, choose **Python3**. + +Copy the code sample (above) into the notebook. Be sure to change the value for the `s3_folder` to replace `AWS_ACCOUNT_ID` with your 12-digit AWS Account ID. You can find your AWS account ID in the AWS console. The entry should look similar to the following: +`s3_folder = ("braket-output-123456789012", "folder-name")` + +Choose **Run** to execute the code to confirm that your environment is configured correctly. + +When the job completes, you should see output similar to the following: +`Counter({'00': 519, '11': 481})` + +## Running a Quantum Algorithm on a Quantum Computer +With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The steps to do so are the same as those described to validate your environment. Just replace the example code provided in this document with your own code. + +The following example executes the same Bell Pair example described to validate your configuration against an IonQ quantum computer. ```python from braket.circuits import Circuit from braket.aws import AwsQuantumSimulator -device = AwsQuantumSimulator("arn:aws:aqx:::quantum-simulator:aqx:qs1") +device_arn = "arn:aws:aqx:::qpu:ionq" s3_folder = ("braket-output-AWS_ACCOUNT_ID", "folder-name") - + bell = Circuit().h(0).cnot(0, 1) print(device.run(bell, s3_folder).result().measurement_counts) ``` - -You should get output similar to... -``` -Counter({'11': 50, '00': 50}) -``` -7. Install [Jupyter](https://jupyter.org/install) and create an braket kernel. -``` -pip install jupyter ipykernel -python -m ipykernel install --user --name braket -``` - -8. You can now launch Jupyter and use the SDK within it. -``` -jupyter notebook +Specify which quantum computer hardware to use by changing the value of the `device_arn` to the value for quantum computer to use: +- **IonQ** "arn:aws:aqx:::qpu:ionq" +- **Rigetti** "arn:aws:aqx:::qpu:rigetti" +- **D-Wave** Not yet available + +### Deactivat the virtual environment +After you are finished using the virtual environment to interact with Amazon Braket, you can deactivate it using the following command. + +```bash +source deactivate ``` -## Update to latest changes +When you want to use it again, you can reactivate it. + -1. `cd` into the GitHub repository directory -2. run `git pull` +## Updating to the latest release +We will periodically make updates and changes the SDK or the model. When you are notified of a change that requires action on your part, use the following steps to update your environment to the latest version. + +### To get the lastest updates +Position your cursor in the folder where you cloned the GitHub repo for the Braket Python SDK, then run the following command to get the latest version. +```bash +git pull +``` ## Sample Notebooks -TODO +Coming soon ## Documentation +You can generate the documentation for the SDK. First change directories (`cd`) to position the cursor in the (`doc`) directory. +Then run the following command to generate the HTML documentation files: -First `cd` into the `doc` directory and run: ```bash make html ``` -Then open `BRAKET_SDK_ROOT/build/documentation/html/index.html` in a browser to view the docs. - -## Testing +To view the generated documentation, open the following file in a browser: +`BRAKET_SDK_ROOT/build/documentation/html/index.html` +## Install the SDK for Testing Make sure to install test dependencies first: -``` +```bash pip install -e "braket-python-sdk[test]" ``` ### Unit Tests -``` +```bash tox -e unit-tests ``` To run an individual test -```bash +``` tox -e unit-tests -- -k 'your_test' ``` @@ -129,9 +230,9 @@ tox ### Integration Tests -Set the `AWS_PROFILE` +Set the `AWS_PROFILE` information in the Prerequisites section of this document. ```bash -export AWS_PROFILE=PROFILE_FROM_STEP_3 +export AWS_PROFILE=Your_Profile_Name ``` Create an S3 bucket in the same account as the `AWS_PROFILE` with the following naming convention `braket-sdk-integ-tests-{account_id}`. From 03fc6c1ed7218b0ac650f7067052393b1385f91c Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Mon, 27 Jan 2020 13:36:02 -0800 Subject: [PATCH 0015/1165] Add AnnealingQuantumTaskResult (#22) --- src/braket/aws/__init__.py | 1 - src/braket/aws/aws_quantum_task.py | 14 +- src/braket/aws/aws_quantum_task_result.py | 103 ---------- src/braket/tasks/__init__.py | 5 +- .../tasks/annealing_quantum_task_result.py | 152 ++++++++++++++ ...t.py => gate_model_quantum_task_result.py} | 64 +++++- src/braket/tasks/quantum_task.py | 22 +- test/unit_tests/braket/aws/test_aws_qpu.py | 2 +- .../braket/aws/test_aws_quantum_task.py | 11 +- .../test_annealing_quantum_task_result.py | 194 ++++++++++++++++++ .../test_gate_model_quantum_task_result.py} | 95 +++++++-- .../braket/tasks/test_quantum_task_result.py | 65 ------ 12 files changed, 523 insertions(+), 205 deletions(-) delete mode 100644 src/braket/aws/aws_quantum_task_result.py create mode 100644 src/braket/tasks/annealing_quantum_task_result.py rename src/braket/tasks/{quantum_task_result.py => gate_model_quantum_task_result.py} (64%) create mode 100644 test/unit_tests/braket/tasks/test_annealing_quantum_task_result.py rename test/unit_tests/braket/{aws/test_aws_quantum_task_result.py => tasks/test_gate_model_quantum_task_result.py} (53%) delete mode 100644 test/unit_tests/braket/tasks/test_quantum_task_result.py diff --git a/src/braket/aws/__init__.py b/src/braket/aws/__init__.py index a304b490..d351adf0 100644 --- a/src/braket/aws/__init__.py +++ b/src/braket/aws/__init__.py @@ -16,5 +16,4 @@ from braket.aws.aws_quantum_simulator import AwsQuantumSimulator # noqa: F401 from braket.aws.aws_quantum_simulator_arns import AwsQuantumSimulatorArns # noqa: F401 from braket.aws.aws_quantum_task import AwsQuantumTask # noqa: F401 -from braket.aws.aws_quantum_task_result import AwsQuantumTaskResult # noqa: F401 from braket.aws.aws_session import AwsSession # noqa: F401 diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index f01b8039..24fe2392 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -15,12 +15,12 @@ import time from typing import Any, Dict -from braket.aws.aws_quantum_task_result import AwsQuantumTaskResult from braket.aws.aws_session import AwsSession from braket.circuits.circuit import Circuit -from braket.tasks import QuantumTask +from braket.tasks import GateModelQuantumTaskResult, QuantumTask +# TODO: add AnnealingQuantumTaskResult class AwsQuantumTask(QuantumTask): """Amazon Braket implementation of a quantum task.""" @@ -99,7 +99,7 @@ def __init__( self._poll_interval_seconds = poll_interval_seconds self._metadata: Dict[str, Any] = {} - self._result: AwsQuantumTaskResult = None + self._result: GateModelQuantumTaskResult = None self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) @property @@ -150,7 +150,7 @@ def state(self, use_cached_value: bool = False) -> str: """ return self.metadata(use_cached_value).get("status") - def result(self) -> AwsQuantumTaskResult: + def result(self) -> GateModelQuantumTaskResult: """ Get the quantum task result by polling Amazon Braket to see if the task is completed. Once the task is completed the result is retrieved from S3 and returned as a QuantumTaskResult. @@ -191,12 +191,12 @@ async def _create_future(self) -> asyncio.Task: """ return asyncio.create_task(self._wait_for_completion()) - async def _wait_for_completion(self) -> AwsQuantumTaskResult: + async def _wait_for_completion(self) -> GateModelQuantumTaskResult: """ Waits for the quantum task to be completed and returns back result from S3. Returns: - AwsQuantumTaskResult: If the task ends up in the `AwsQuantumTask.RESULTS_READY_STATES` + GateModelQuantumTaskResult: If the task is in the `AwsQuantumTask.RESULTS_READY_STATES` state within the time limit then the result from S3 is loaded and returned. None is returned if a timeout occurs or task state is in `AwsQuantumTask.TERMINAL_STATES` but not `AwsQuantumTask.RESULTS_READY_STATES`. @@ -213,7 +213,7 @@ async def _wait_for_completion(self) -> AwsQuantumTaskResult: result_string = self._aws_session.retrieve_s3_object_body( current_metadata["resultsS3Bucket"], current_metadata["resultsS3ObjectKey"] ) - self._result = AwsQuantumTaskResult.from_string(result_string) + self._result = GateModelQuantumTaskResult.from_string(result_string) return self._result elif current_metadata["status"] in AwsQuantumTask.TERMINAL_STATES: self._result = None diff --git a/src/braket/aws/aws_quantum_task_result.py b/src/braket/aws/aws_quantum_task_result.py deleted file mode 100644 index b3946116..00000000 --- a/src/braket/aws/aws_quantum_task_result.py +++ /dev/null @@ -1,103 +0,0 @@ -# Copyright 2019-2019 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 dataclasses import dataclass -from typing import Any, Dict - -import numpy as np -from braket.tasks.quantum_task_result import QuantumTaskResult - - -@dataclass -class AwsQuantumTaskResult(QuantumTaskResult): - """ - Result of an AWS quantum task execution. This class is intended - to be initialized by a QuantumTask class. - - Args: - measurements (numpy.ndarray): 2d array - row is shot, column is qubit. - 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. - 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. - measurements_copied_from_device (bool): flag whether `measurements` were copied from device. - If false, `measurements` are calculated from device data. - measurement_counts_copied_from_device (bool): flag whether `measurement_counts` were copied - from device. If false, `measurement_counts` are calculated from device data. - measurement_probabilities_copied_from_device (bool): flag whether - `measurement_probabilities` were copied from device. If false, - `measurement_probabilities` are calculated from device data. - task_metadata (Dict[str, Any]): Dictionary of task metadata. TODO: Link boto3 docs. - state_vector (Dict[str, complex]): Dictionary where key is state and value is amplitude. - """ - - task_metadata: Dict[str, Any] - state_vector: Dict[str, complex] = None - - @staticmethod - def from_string(result: str) -> "AwsQuantumTaskResult": - """ - Create AwsQuantumTaskResult from string with the S3 format defined by the Amazon Braket - Service. TODO: Link Amazon Braket S3 format docs. - - Args: - result (str): JSON object string, whose keys are AwsQuantumTaskResult attributes. - - Returns: - AwsQuantumTaskResult: A AwsQuantumTaskResult based on a string loaded from S3. - """ - json_obj = json.loads(result) - state_vector = json_obj.get("StateVector", None) - task_metadata = json_obj["TaskMetadata"] - if "Measurements" in json_obj: - measurements = np.asarray(json_obj["Measurements"], dtype=int) - m_counts = QuantumTaskResult.measurement_counts_from_measurements(measurements) - m_probabilities = QuantumTaskResult.measurement_probabilities_from_measurement_counts( - m_counts - ) - measurements_copied_from_device = True - m_counts_copied_from_device = False - m_probabilities_copied_from_device = False - if "MeasurementProbabilities" in json_obj: - shots = task_metadata["Shots"] - m_probabilities = json_obj["MeasurementProbabilities"] - measurements = QuantumTaskResult.measurements_from_measurement_probabilities( - m_probabilities, shots - ) - m_counts = QuantumTaskResult.measurement_counts_from_measurements(measurements) - measurements_copied_from_device = False - m_counts_copied_from_device = False - m_probabilities_copied_from_device = True - if "StateVector" in json_obj: - state_vector = json_obj.get("StateVector", None) - for state in state_vector: - state_vector[state] = complex(*state_vector[state]) - return AwsQuantumTaskResult( - state_vector=state_vector, - task_metadata=task_metadata, - measurements=measurements, - measurement_counts=m_counts, - measurement_probabilities=m_probabilities, - measurements_copied_from_device=measurements_copied_from_device, - measurement_counts_copied_from_device=m_counts_copied_from_device, - measurement_probabilities_copied_from_device=m_probabilities_copied_from_device, - ) - - def __eq__(self, other) -> bool: - if isinstance(other, AwsQuantumTaskResult): - self_fields = (self.task_metadata, self.state_vector) - other_fields = (other.task_metadata, other.state_vector) - return self_fields == other_fields and super().__eq__(other) - return NotImplemented diff --git a/src/braket/tasks/__init__.py b/src/braket/tasks/__init__.py index dd472399..0fa1eb25 100644 --- a/src/braket/tasks/__init__.py +++ b/src/braket/tasks/__init__.py @@ -12,8 +12,11 @@ # language governing permissions and limitations under the License. import braket.ipython_utils as ipython_utils +from braket.tasks.annealing_quantum_task_result import ( # noqa: F401 # TODO: remove + AnnealingQuantumTaskResult, +) +from braket.tasks.gate_model_quantum_task_result import GateModelQuantumTaskResult # noqa: F401 from braket.tasks.quantum_task import QuantumTask # noqa: F401 -from braket.tasks.quantum_task_result import QuantumTaskResult # noqa: F401 # Apply nest_asyncio if currently running within Jupyter. This ensures anything that uses # asyncio will run in Jupyter without any issues. diff --git a/src/braket/tasks/annealing_quantum_task_result.py b/src/braket/tasks/annealing_quantum_task_result.py new file mode 100644 index 00000000..bca36466 --- /dev/null +++ b/src/braket/tasks/annealing_quantum_task_result.py @@ -0,0 +1,152 @@ +# Copyright 2019-2019 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 dataclasses import dataclass +from typing import Any, Dict + +import numpy + + +@dataclass +class AnnealingQuantumTaskResult: + """ + Result of an annealing 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) + where row is solution, column is value of the variable, 'solution_count' (numpy.ndarray) + - list of number of times the solutions occurred, and 'value' (numpy.ndarray) - + list of the output or energy of the solutions + variable_count (int): the number of variables + problem_type (str): the type of problem ('ising' or 'qubo') + task_metadata (Dict[str, Any]): Dictionary of task metadata. TODO: Link boto3 docs. + additional_metadata (Dict[str, Any]): A dictionary of additional metadata + that's device-specific TODO: Link result schema docs + """ + + record_array: numpy.recarray + variable_count: int + problem_type: str + task_metadata: Dict[str, Any] + additional_metadata: Dict[str, Any] + + def data(self, selected_fields=None, sorted_by="value", reverse=False): + """ + Iterate over the data in record_array + + Args: + selected_fields (List[str], optional, default=None): selected fields to return. + Options are 'solution', 'value', and 'solution_count' + sorted_by (str, optional, default='value'): data returned will be sorted by this field. + Options are 'solution', 'value', and 'solution_count' + reverse (bool, optional, default=False): whether data returned will be in reverse order + + Yields: + 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)) + else: + order = numpy.argsort(self.record_array[sorted_by]) + + if reverse: + order = numpy.flip(order) + + for i in order: + yield tuple(self.record_array[field][i] for field in selected_fields) + + def __eq__(self, other) -> 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 + # elements in the array are equal. + self_fields = ( + self.variable_count, + self.problem_type, + self.task_metadata, + self.additional_metadata, + ) + other_fields = ( + other.variable_count, + other.problem_type, + other.task_metadata, + other.additional_metadata, + ) + return (self.record_array == other.record_array).all() and self_fields == other_fields + return NotImplemented + + @staticmethod + def from_string(result: str) -> "AnnealingQuantumTaskResult": + """ + Create AnnealingQuantumTaskResult from string + + Args: + result (str): JSON object string + + Returns: + AnnealingQuantumTaskResult: An AnnealingQuantumTaskResult based on a string. + """ + json_obj = json.loads(result) + solutions = numpy.asarray(json_obj["Solutions"], dtype=int) + values = numpy.asarray(json_obj["Values"], dtype=float) + if json_obj["SolutionCounts"] is None: + solution_counts = numpy.ones(len(solutions), dtype=int) + else: + solution_counts = numpy.asarray(json_obj["SolutionCounts"], dtype=int) + record_array = AnnealingQuantumTaskResult.create_record_array( + solutions, solution_counts, values + ) + variable_count = json_obj["VariableCount"] + problem_type = json_obj["ProblemType"] + task_metadata = json_obj["TaskMetadata"] + additional_metadata = {} + for key in json_obj.keys(): + if key.endswith("Metadata") and key != "TaskMetadata": + additional_metadata[key] = json_obj[key] + return AnnealingQuantumTaskResult( + record_array=record_array, + variable_count=variable_count, + problem_type=problem_type, + task_metadata=task_metadata, + additional_metadata=additional_metadata, + ) + + @staticmethod + def create_record_array( + solutions: numpy.ndarray, solution_counts: numpy.ndarray, values: numpy.ndarray + ) -> numpy.recarray: + """ + Create 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 + """ + num_solutions, variable_count = solutions.shape + datatypes = [ + ("solution", solutions.dtype, (variable_count,)), + ("value", values.dtype), + ("solution_count", solution_counts.dtype), + ] + + record = numpy.rec.array(numpy.zeros(num_solutions, dtype=datatypes)) + record["solution"] = solutions + record["value"] = values + record["solution_count"] = solution_counts + return record diff --git a/src/braket/tasks/quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py similarity index 64% rename from src/braket/tasks/quantum_task_result.py rename to src/braket/tasks/gate_model_quantum_task_result.py index 2646a4ad..9bb3690b 100644 --- a/src/braket/tasks/quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -11,16 +11,17 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import json from dataclasses import dataclass -from typing import Counter, Dict +from typing import Any, Counter, Dict import numpy as np @dataclass -class QuantumTaskResult: +class GateModelQuantumTaskResult: """ - Result of a 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: @@ -37,6 +38,8 @@ class QuantumTaskResult: measurement_probabilities_copied_from_device (bool): flag whether `measurement_probabilities` were copied from device. If false, `measurement_probabilities` are calculated from device data. + task_metadata (Dict[str, Any]): Dictionary of task metadata. TODO: Link boto3 docs. + state_vector (Dict[str, complex]): Dictionary where key is state and value is amplitude. """ measurements: np.ndarray @@ -45,13 +48,17 @@ class QuantumTaskResult: measurements_copied_from_device: bool measurement_counts_copied_from_device: bool measurement_probabilities_copied_from_device: bool + task_metadata: Dict[str, Any] + state_vector: Dict[str, complex] = None def __eq__(self, other) -> bool: - if isinstance(other, QuantumTaskResult): + if isinstance(other, GateModelQuantumTaskResult): # __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 # elements in the array are equal. - return (self.measurements == other.measurements).all() + self_fields = (self.task_metadata, self.state_vector) + other_fields = (other.task_metadata, other.state_vector) + return (self.measurements == other.measurements).all() and self_fields == other_fields return NotImplemented @staticmethod @@ -120,3 +127,50 @@ def measurements_from_measurement_probabilities( ) measurements_list.extend(individual_measurement_list) return np.asarray(measurements_list, dtype=int) + + @staticmethod + def from_string(result: str) -> "GateModelQuantumTaskResult": + """ + Create GateModelQuantumTaskResult from string + + Args: + result (str): JSON object string, whose keys are GateModelQuantumTaskResult attributes. + + Returns: + GateModelQuantumTaskResult: A GateModelQuantumTaskResult based on a string + """ + json_obj = json.loads(result) + task_metadata = json_obj["TaskMetadata"] + state_vector = json_obj.get("StateVector", None) + if state_vector: + for state in state_vector: + state_vector[state] = complex(*state_vector[state]) + if "Measurements" in json_obj: + measurements = np.asarray(json_obj["Measurements"], dtype=int) + m_counts = GateModelQuantumTaskResult.measurement_counts_from_measurements(measurements) + m_probs = GateModelQuantumTaskResult.measurement_probabilities_from_measurement_counts( + m_counts + ) + measurements_copied_from_device = True + m_counts_copied_from_device = False + m_probabilities_copied_from_device = False + elif "MeasurementProbabilities" in json_obj: + shots = task_metadata["Shots"] + m_probs = json_obj["MeasurementProbabilities"] + measurements = GateModelQuantumTaskResult.measurements_from_measurement_probabilities( + m_probs, shots + ) + m_counts = GateModelQuantumTaskResult.measurement_counts_from_measurements(measurements) + measurements_copied_from_device = False + m_counts_copied_from_device = False + m_probabilities_copied_from_device = True + return GateModelQuantumTaskResult( + state_vector=state_vector, + task_metadata=task_metadata, + measurements=measurements, + measurement_counts=m_counts, + measurement_probabilities=m_probs, + measurements_copied_from_device=measurements_copied_from_device, + measurement_counts_copied_from_device=m_counts_copied_from_device, + measurement_probabilities_copied_from_device=m_probabilities_copied_from_device, + ) diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py index 7d192dbf..6c8e9b8b 100644 --- a/src/braket/tasks/quantum_task.py +++ b/src/braket/tasks/quantum_task.py @@ -13,8 +13,10 @@ import asyncio from abc import ABC, abstractmethod +from typing import Any, Dict, Union -from braket.tasks.quantum_task_result import QuantumTaskResult +from braket.tasks.annealing_quantum_task_result import AnnealingQuantumTaskResult +from braket.tasks.gate_model_quantum_task_result import GateModelQuantumTaskResult class QuantumTask(ABC): @@ -34,12 +36,24 @@ def state(self) -> str: """str: State of the quantum task""" @abstractmethod - def result(self) -> QuantumTaskResult: + def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: """ - QuantumTaskResult: Get the quantum task result. Call async_result if you want the - result in an async way. + Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: Get the quantum task result. + Call async_result if you want the result in an async way. """ @abstractmethod 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]: + """ + Get task metadata. + + Args: + use_cached_value (bool, optional): If true returns the last value retrieved + + Returns: + Dict[str, Any]: The metadata regarding the task If `use_cached_value` is True + then the last value retrieved is returned. + """ diff --git a/test/unit_tests/braket/aws/test_aws_qpu.py b/test/unit_tests/braket/aws/test_aws_qpu.py index 460f428e..7cab4252 100644 --- a/test/unit_tests/braket/aws/test_aws_qpu.py +++ b/test/unit_tests/braket/aws/test_aws_qpu.py @@ -13,7 +13,6 @@ from unittest.mock import Mock, patch -import braket.aws.aws_qpu # noqa F401 import pytest from braket.aws import AwsQpu, AwsQpuArns from braket.circuits import Circuit @@ -141,6 +140,7 @@ def test_no_aws_session_supplied(boto_session_init, aws_session_init, boto_sessi def test_qpu_refresh_metadata_success( aws_session, qpu_arn, properties_keys, initial_qpu_data, modified_qpu_data ): + aws_session.boto_session.region_name = AwsQpu.QPU_REGIONS[qpu_arn][0] aws_session.get_qpu_metadata.return_value = initial_qpu_data qpu = AwsQpu(qpu_arn, aws_session) _assert_qpu_fields(qpu, properties_keys, initial_qpu_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 250cabe4..7ee11550 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -15,9 +15,10 @@ from unittest.mock import Mock import pytest -from braket.aws import AwsQuantumTask, AwsQuantumTaskResult +from braket.aws import AwsQuantumTask from braket.aws.aws_session import AwsSession from braket.circuits import Circuit +from braket.tasks import GateModelQuantumTaskResult from common_test_utils import MockS3 S3_TARGET = AwsSession.S3DestinationFolder("foo", "bar") @@ -113,7 +114,7 @@ def test_result(quantum_task): _mock_state(quantum_task._aws_session, "COMPLETED") _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_1) - expected = AwsQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) + expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) assert quantum_task.result() == expected s3_bucket = quantum_task.metadata()["resultsS3Bucket"] @@ -127,7 +128,7 @@ def test_result_is_cached(quantum_task): quantum_task.result() _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_2) - expected = AwsQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) + expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) assert quantum_task.result() == expected @@ -156,7 +157,7 @@ def set_result_from_callback(future): # via future.result(). Note that this would fail if the future is not complete. result_from_future = future.result() - expected = AwsQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) + expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) assert result_from_callback == expected assert result_from_waiting == expected assert result_from_future == expected @@ -180,7 +181,7 @@ def test_timeout(aws_session): assert quantum_task.result() is None _mock_state(aws_session, "COMPLETED") - assert quantum_task.result() == AwsQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) + assert quantum_task.result() == GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) @pytest.mark.xfail(raises=ValueError) 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 new file mode 100644 index 00000000..36f830dd --- /dev/null +++ b/test/unit_tests/braket/tasks/test_annealing_quantum_task_result.py @@ -0,0 +1,194 @@ +# Copyright 2019-2019 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.aws.aws_qpu_arns import AwsQpuArns +from braket.tasks import AnnealingQuantumTaskResult + + +@pytest.fixture +def solutions(): + return [[-1, -1, -1, -1], [1, -1, 1, 1], [1, -1, -1, 1]] + + +@pytest.fixture +def values(): + return [0.0, 1.0, 2.0] + + +@pytest.fixture +def variable_count(): + return 4 + + +@pytest.fixture +def solution_counts(): + return [3, 2, 4] + + +@pytest.fixture +def problem_type(): + return "ising" + + +@pytest.fixture +def task_metadata(): + return {"Id": "UUID_blah_1", "Status": "COMPLETED", "BackendArn": AwsQpuArns.DWAVE, "Shots": 5} + + +@pytest.fixture +def dwave_metadata(): + return { + "ActiveVariables": [0], + "Timing": { + "QpuSamplingTime": 1575, + "QpuAnnealTimePerSample": 20, + "QpuReadoutTimePerSample": 274, + "QpuAccessTime": 10917, + "QpuAccessOverheadTime": 3382, + "QpuProgrammingTime": 9342, + "QpuDelayTimePerSample": 21, + "TotalPostProcessingTime": 117, + "PostProcessingOverheadTime": 117, + "TotalRealTime": 10917, + "RunTimeChip": 1575, + "AnnealTimePerRun": 20, + "ReadoutTimePerRun": 274, + }, + } + + +@pytest.fixture +def result_str_1( + solutions, values, solution_counts, variable_count, problem_type, dwave_metadata, task_metadata +): + return json.dumps( + { + "Solutions": solutions, + "VariableCount": variable_count, + "Values": values, + "SolutionCounts": solution_counts, + "ProblemType": problem_type, + "DWaveMetadata": dwave_metadata, + "TaskMetadata": task_metadata, + } + ) + + +@pytest.fixture +def result_str_2(solutions, values, variable_count, problem_type, dwave_metadata, task_metadata): + return json.dumps( + { + "Solutions": solutions, + "VariableCount": variable_count, + "Values": values, + "SolutionCounts": None, + "ProblemType": problem_type, + "DWaveMetadata": dwave_metadata, + "TaskMetadata": task_metadata, + } + ) + + +@pytest.fixture +def annealing_result( + solutions, values, solution_counts, variable_count, problem_type, dwave_metadata, task_metadata +): + solutions = np.asarray(solutions, dtype=int) + values = np.asarray(values, dtype=float) + solution_counts = np.asarray(solution_counts, dtype=int) + record_array = AnnealingQuantumTaskResult.create_record_array( + solutions, solution_counts, values + ) + return AnnealingQuantumTaskResult( + record_array=record_array, + variable_count=variable_count, + problem_type=problem_type, + task_metadata=task_metadata, + additional_metadata={"DWaveMetadata": dwave_metadata}, + ) + + +def test_from_string( + result_str_1, + solutions, + values, + solution_counts, + variable_count, + problem_type, + task_metadata, + dwave_metadata, +): + result = AnnealingQuantumTaskResult.from_string(result_str_1) + solutions = np.asarray(solutions, dtype=int) + values = np.asarray(values, dtype=float) + solution_counts = np.asarray(solution_counts, dtype=int) + assert result.variable_count == variable_count + assert result.problem_type == problem_type + assert result.task_metadata == task_metadata + assert result.additional_metadata == {"DWaveMetadata": dwave_metadata} + np.testing.assert_equal( + result.record_array, + AnnealingQuantumTaskResult.create_record_array(solutions, solution_counts, values), + ) + + +def test_from_string_solution_counts_none(result_str_2, solutions): + result = AnnealingQuantumTaskResult.from_string(result_str_2) + np.testing.assert_equal(result.record_array.solution_count, np.ones(len(solutions), dtype=int)) + + +def test_data_sort_by_none(annealing_result, solutions, values, solution_counts): + d = list(annealing_result.data(sorted_by=None)) + for i in range(len(solutions)): + assert (d[i][0] == solutions[i]).all() + assert d[i][1] == values[i] + assert d[i][2] == solution_counts[i] + + +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]]) + + +def test_data_reverse(annealing_result, solutions, values, solution_counts): + d = list(annealing_result.data(reverse=True)) + num_solutions = len(solutions) + for i in range(num_solutions): + assert (d[i][0] == solutions[num_solutions - i - 1]).all() + assert d[i][1] == values[num_solutions - i - 1] + assert d[i][2] == solution_counts[num_solutions - i - 1] + + +def test_data_sort_by(annealing_result, solutions, values, solution_counts): + d = list(annealing_result.data(sorted_by="solution_count")) + min_index = np.argmin(solution_counts) + assert (d[0][0] == solutions[min_index]).all() + assert d[0][1] == values[min_index] + assert d[0][2] == solution_counts[min_index] + + +def test_equality(result_str_1, result_str_2): + result_1 = AnnealingQuantumTaskResult.from_string(result_str_1) + result_2 = AnnealingQuantumTaskResult.from_string(result_str_1) + other_result = AnnealingQuantumTaskResult.from_string(result_str_2) + non_result = "not a quantum task result" + + assert result_1 == result_2 + assert result_1 is not result_2 + assert result_1 != other_result + assert result_1 != non_result diff --git a/test/unit_tests/braket/aws/test_aws_quantum_task_result.py b/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py similarity index 53% rename from test/unit_tests/braket/aws/test_aws_quantum_task_result.py rename to test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py index 4c1a876f..6fcbf8d0 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task_result.py +++ b/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py @@ -16,23 +16,57 @@ import numpy as np import pytest -from braket.aws.aws_quantum_task_result import AwsQuantumTaskResult -from common_test_utils import MockS3 +from braket.aws.aws_qpu_arns import AwsQpuArns +from braket.tasks import GateModelQuantumTaskResult @pytest.fixture def result_str_1(): - return MockS3.MOCK_S3_RESULT_1 + return json.dumps( + { + "StateVector": {"00": [0.2, 0.2], "01": [0.3, 0.1], "10": [0.1, 0.3], "11": [0.2, 0.2]}, + "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], + "TaskMetadata": { + "Id": "UUID_blah_1", + "Status": "COMPLETED", + "BackendArn": AwsQpuArns.RIGETTI, + }, + } + ) @pytest.fixture def result_str_2(): - return MockS3.MOCK_S3_RESULT_2 + return json.dumps( + { + "StateVector": {"00": [0.2, 0.2], "01": [0.3, 0.1], "10": [0.1, 0.3], "11": [0.2, 0.2]}, + "Measurements": [[0, 0], [0, 0], [0, 0], [1, 1]], + "TaskMetadata": { + "Id": "UUID_blah_2", + "Status": "COMPLETED", + "BackendArn": AwsQpuArns.RIGETTI, + }, + } + ) @pytest.fixture def result_str_3(): - return MockS3.MOCK_S3_RESULT_3 + return json.dumps( + { + "TaskMetadata": { + "Id": "1231231", + "Status": "COMPLETED", + "BackendArn": "test_arn", + "BackendTranslation": "...", + "Created": 1574140385.0697668, + "Modified": 1574140388.6908717, + "Shots": 100, + "GateModelConfig": {"QubitCount": 6}, + }, + "MeasurementProbabilities": {"011000": 0.9999999999999982}, + } + ) @pytest.fixture @@ -45,8 +79,43 @@ def parsed_state_vector(): } +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]] + ) + measurement_counts = GateModelQuantumTaskResult.measurement_counts_from_measurements( + measurements + ) + expected_counts: Counter = {"1010": 3, "0000": 1, "1000": 2} + assert expected_counts == measurement_counts + + +def test_measurement_probabilities_from_measurement_counts(): + counts = {"00": 1, "01": 1, "10": 1, "11": 97} + probabilities = {"00": 0.01, "01": 0.01, "10": 0.01, "11": 0.97} + + m_probabilities = GateModelQuantumTaskResult.measurement_probabilities_from_measurement_counts( + counts + ) + + assert m_probabilities == probabilities + + +def test_measurements_from_measurement_probabilities(): + shots = 5 + probabilities = {"00": 0.2, "01": 0.2, "10": 0.2, "11": 0.4} + measurements_list = [["0", "0"], ["0", "1"], ["1", "0"], ["1", "1"], ["1", "1"]] + expected_results = np.asarray(measurements_list, dtype=int) + + measurements = GateModelQuantumTaskResult.measurements_from_measurement_probabilities( + probabilities, shots + ) + + assert np.allclose(measurements, expected_results) + + def test_state_vector(parsed_state_vector): - result: AwsQuantumTaskResult = AwsQuantumTaskResult( + result: GateModelQuantumTaskResult = GateModelQuantumTaskResult( measurements=None, task_metadata=None, state_vector=parsed_state_vector, @@ -69,7 +138,7 @@ def test_task_metadata(): "CreatedAt": "02/12/22 21:23", "UpdatedAt": "02/13/22 21:23", } - result: AwsQuantumTaskResult = AwsQuantumTaskResult( + result: GateModelQuantumTaskResult = GateModelQuantumTaskResult( measurements=None, task_metadata=task_metadata, measurement_counts=None, @@ -83,7 +152,7 @@ def test_task_metadata(): def test_from_string_measurements(result_str_1, parsed_state_vector): result_obj = json.loads(result_str_1) - task_result = AwsQuantumTaskResult.from_string(result_str_1) + task_result = GateModelQuantumTaskResult.from_string(result_str_1) expected_measurements = np.asarray(result_obj["Measurements"], dtype=int) assert task_result.task_metadata == result_obj["TaskMetadata"] assert task_result.state_vector == parsed_state_vector @@ -95,7 +164,7 @@ def test_from_string_measurements(result_str_1, parsed_state_vector): def test_from_string_measurement_probabilities(result_str_3): result_obj = json.loads(result_str_3) - task_result = AwsQuantumTaskResult.from_string(result_str_3) + task_result = GateModelQuantumTaskResult.from_string(result_str_3) assert task_result.measurement_probabilities == result_obj["MeasurementProbabilities"] assert task_result.task_metadata == result_obj["TaskMetadata"] assert task_result.state_vector is None @@ -110,10 +179,10 @@ def test_from_string_measurement_probabilities(result_str_3): def test_equality(result_str_1, result_str_2): - result_1 = AwsQuantumTaskResult.from_string(result_str_1) - result_2 = AwsQuantumTaskResult.from_string(result_str_1) - other_result = AwsQuantumTaskResult.from_string(result_str_2) - non_result = "not a aws quantum task result" + result_1 = GateModelQuantumTaskResult.from_string(result_str_1) + result_2 = GateModelQuantumTaskResult.from_string(result_str_1) + other_result = GateModelQuantumTaskResult.from_string(result_str_2) + non_result = "not a quantum task result" assert result_1 == result_2 assert result_1 is not result_2 diff --git a/test/unit_tests/braket/tasks/test_quantum_task_result.py b/test/unit_tests/braket/tasks/test_quantum_task_result.py deleted file mode 100644 index 0af6c054..00000000 --- a/test/unit_tests/braket/tasks/test_quantum_task_result.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright 2019-2019 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 typing import Counter - -import numpy as np -from braket.tasks import QuantumTaskResult - - -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]] - ) - measurement_counts = QuantumTaskResult.measurement_counts_from_measurements(measurements) - expected_counts: Counter = {"1010": 3, "0000": 1, "1000": 2} - assert expected_counts == measurement_counts - - -def test_measurement_probabilities_from_measurement_counts(): - counts = {"00": 1, "01": 1, "10": 1, "11": 97} - probabilities = {"00": 0.01, "01": 0.01, "10": 0.01, "11": 0.97} - - measurement_probabilities = QuantumTaskResult.measurement_probabilities_from_measurement_counts( - counts - ) - - assert measurement_probabilities == probabilities - - -def test_measurements_from_measurement_probabilities(): - shots = 5 - probabilities = {"00": 0.2, "01": 0.2, "10": 0.2, "11": 0.4} - measurements_list = [["0", "0"], ["0", "1"], ["1", "0"], ["1", "1"], ["1", "1"]] - expected_results = np.asarray(measurements_list, dtype=int) - - measurements = QuantumTaskResult.measurements_from_measurement_probabilities( - probabilities, shots - ) - - assert np.allclose(measurements, expected_results) - - -def test_equality(): - measurements_1 = np.array([[0, 0], [1, 1]]) - measurements_2 = np.array([[0, 0], [0, 0]]) - - result_1 = QuantumTaskResult(measurements_1, None, None, False, True, True) - result_2 = QuantumTaskResult(measurements_1, None, None, False, True, True) - other_result = QuantumTaskResult(measurements_2, None, None, False, True, True) - non_result = "not a quantum task result" - - assert result_1 == result_2 - assert result_1 is not result_2 - assert result_1 != other_result - assert result_1 != non_result From 0e0f1d9246ed04367a598724414ca4b4f3e6386d Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 27 Jan 2020 14:02:06 -0800 Subject: [PATCH 0016/1165] Add annealing problem creation (#21) Add annealing problem creation --- src/braket/annealing/problem.py | 155 ++++++++++++++++++ .../braket/annealing/test_problem.py | 52 ++++++ 2 files changed, 207 insertions(+) create mode 100644 src/braket/annealing/problem.py create mode 100644 test/unit_tests/braket/annealing/test_problem.py diff --git a/src/braket/annealing/problem.py b/src/braket/annealing/problem.py new file mode 100644 index 00000000..9d7d2be4 --- /dev/null +++ b/src/braket/annealing/problem.py @@ -0,0 +1,155 @@ +# Copyright 2019-2019 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 enum import Enum +from typing import Dict, Tuple + +import braket.ir.annealing as ir + + +class ProblemType(str, Enum): + """ The type of annealing problem. + QUBO: Quadratic Unconstrained Binary Optimization, with values 1 and 0 + ISING: Ising model, with values +/-1 + """ + + QUBO = "QUBO" + ISING = "ISING" + + +class Problem: + """ Represents an annealing problem. + + """ + + def __init__( + self, + problem_type: ProblemType, + linear: Dict[int, float] = None, + quadratic: Dict[Tuple[int, int], float] = None, + ): + """ + + Args: + problem_type (ProblemType): The type of annealing problem + linear (Dict[int, float]): The linear terms of this problem, + as a map of variable to coefficient + quadratic (Dict[Tuple[int, int], float]): The quadratic terms of this problem, + as a map of variables to coefficient + + Examples: + >>> problem = Problem( + >>> ProblemType.ISING, + >>> linear={1: 3.14}, + >>> quadratic={(1, 2): 10.08}, + >>> ) + >>> problem.add_linear_term(2, 1.618).add_quadratic_term((3, 4), 1337) + """ + self._problem_type = problem_type + self._linear = linear or {} + self._quadratic = quadratic or {} + + @property + def problem_type(self) -> ProblemType: + """ The type of annealing problem. + + Returns: + ProblemType: The type of annealing problem + """ + return self._problem_type + + @property + 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 + """ + return self._linear + + @property + 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, + as a map of variables to coefficient + """ + return self._quadratic + + def add_linear_term(self, term: int, coefficient: float) -> Problem: + """ Adds a linear term to the problem. + + Args: + term (int): The variable of the linear term + coefficient (float): The coefficient of the linear term + + Returns: + Problem: This problem object + """ + self._linear[term] = coefficient + return self + + 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 + + Returns: + Problem: This problem object + """ + self._linear.update(coefficients) + return self + + 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 + coefficient (flost): The coefficient of the quadratic term + + Returns: + Problem: This problem object + """ + self._quadratic[term] = coefficient + return self + + 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 + + Returns: + Problem: This problem object + """ + self._quadratic.update(coefficients) + return self + + def to_ir(self): + """ Converts this problem into IR representation. + + Returns: + ir.Problem: IR representation of this problem object + """ + return ir.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 + }, + ) diff --git a/test/unit_tests/braket/annealing/test_problem.py b/test/unit_tests/braket/annealing/test_problem.py new file mode 100644 index 00000000..d8176602 --- /dev/null +++ b/test/unit_tests/braket/annealing/test_problem.py @@ -0,0 +1,52 @@ +# Copyright 2019-2019 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 braket.ir.annealing as ir +from braket.annealing.problem import Problem, ProblemType + + +def test_creation(): + problem = Problem(ProblemType.ISING, linear={1: 3.14}, quadratic={(1, 2): 10.08}) + assert problem.problem_type == ProblemType.ISING + assert problem.linear == {1: 3.14} + assert problem.quadratic == {(1, 2): 10.08} + + +def test_add_linear_term(): + problem = Problem(ProblemType.QUBO) + problem.add_linear_term(1, 3.14) + assert problem.linear == {1: 3.14} + + +def test_add_linear_terms(): + problem = Problem(ProblemType.QUBO) + problem.add_linear_terms({1: 3.14}) + assert problem.linear == {1: 3.14} + + +def test_add_quadratic_term(): + problem = Problem(ProblemType.QUBO) + problem.add_quadratic_term((1, 2), 10.08) + assert problem.quadratic == {(1, 2): 10.08} + + +def test_add_quadratic_terms(): + problem = Problem(ProblemType.QUBO) + problem.add_quadratic_terms({(1, 2): 10.08}) + assert problem.quadratic == {(1, 2): 10.08} + + +def test__to_ir(): + problem = Problem(ProblemType.QUBO).add_linear_term(1, 3.14).add_quadratic_term((1, 2), 10.08) + assert problem.to_ir() == ir.Problem( + type=ir.ProblemType.QUBO, linear={1: 3.14}, quadratic={"1,2": 10.08} + ) From 4bcd71e7e7d0c2b40643247c0b3deb885c6fe42b Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Tue, 28 Jan 2020 12:03:41 -0800 Subject: [PATCH 0017/1165] update to readme (#23) * update to readme reworking steps to resolve some issues on Mac * Update to update installation steps Changed to remove Conda and download repo zip files instead of configure SSH * update to readme Clean up to remove Conda, misc formatting * update to readme Formatting change * Update README.md * Update README.md addressing feedback from Ava * Update README.md added a note about jobs taking up to 24 hours on IonQ * Update README.md modified descriptions of simulators. * Update README.md Removed step to move the bellpair file to the virtual env. --- README.md | 139 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 97 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 26d61b5b..375c0df5 100644 --- a/README.md +++ b/README.md @@ -5,30 +5,48 @@ 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. -### Conda -Use the instructions for installing Conda from https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html. -Choose a regular installation, and then choose the Anaconda installer. Conda also installs Python, which is required for other steps in this document. Download and install the **Python 3.7 version** of Anaconda. +### Python 3.7.2 or greater +Download and install Python 3.7.2 or greater from [Python.org](https://www.python.org/downloads/). +If you are using Windows, choose **Add Python to environment variables** before you begin the installation. ### Using a Virtual Environment -For information about Conda and virtual environments, see [Conda vs. pip vs. virtualenv commands](https://docs.conda.io/projects/conda/en/latest/commands.html#conda-vs-pip-vs-virtualenv-commands). +If you want to use a virtual environment for interacting with the Amazon Braket SDK, first install virtualenv with the following command: +```bash +pip install virtualenv +``` + +To learn more, see [virtualenv](https://virtualenv.pypa.io/en/stable/installation/). -#### Creating a virtual environment -Use a virtual environment to interact with Amazon Braket to avoid changes to your global environment variables. The following commands create aand activate a virtual environment in Conda. If you want to use a name for the environment other than `yourenvname`, just change the value to the name you want to use. You may want to create folder in the file system to create the virtual environment. -Use the following command to create a directory -`mkdir braket` +On Windows, you should open a new terminal window to install `virtualenv`. If you use a terminal window that was open before you installed Python, the terminal window does not use the Python environment variables needed. + +After you install `virtualenv`, use the following command to create a virtual environment. When you run this command, it creates a folder named `braketvirtenv`, and uses that folder as the root folder of the virtual environment. You should run the command from a location where you want to create the virtual environment. + +**To create a virtual environment** +```bash +virtualenv braketvirtenv +``` + +Then use one of the following options to activate the virtual environment. + +**To activate the virtual environment on Mac and Linux**: +```bash +source braketvirtenv/bin/activate +``` -Then use the following command to move the cursor to the directory you created -`cd braket` +**To activate the virtual environment on Windows** +```bash +cd braketvirtenv\scripts +``` ```bash -conda create -n braketvirtenv python=3.7 anaconda +activate ``` -Press **y** when asked to confirm. -Then run the following command to activate the virtual environment. If you changed the environment name in the preceding command, be sure to change in this command as well. +Then run this command to return the cursor to the parent folder ```bash -source activate braketvirtenv +cd .. ``` + ### Git Install Git from https://git-scm.com/downloads. Installation instructions are provided on the download page. @@ -71,21 +89,43 @@ When the template loads, select the **I acknowledge that AWS CloudFormation migh Wait until the Status changes to **CREATE_COMPLETE**. You may need to refresh the page to see the current status of the stack creation. -## Setting up the Braket Python SDK -Use the steps in this section to install and configure the Braket Python SDK for your environment. You should perform the steps in the order in which they are included in this document. +## Setting up the Amazon Braket Python SDKs +Use the steps in this section to install and configure the Amazon Braket Python SDKs for your environment. You should perform the steps in the order in which they are included in this document. -### Install the braket-python-sdk package -Use the following commands to install the Braket Python SDK package. +### Download the Amazon Braket GitHub Repositories +The easiest way to get the SDKs is to download them directly from the GitHub site. Because the repositories are private during the Private Beta period, an SSH key is required to access the files remotely from a terminal session. If you download them directly from the GitHub site, you can just extract the files to your system or virtual environment without the extra steps of using an SSH key. You need to log in to GitHub using the account that was whitelisted for the Amazon Braket (Private Beta). + +Use the following links to download the Amazon Braket Python SDK repos: +- [braket-python-ir](https://github.com/aws/braket-python-ir/archive/stable/latest.zip) +- [braket-python-sdk](https://github.com/aws/braket-python-sdk/archive/stable/latest.zip) + +### Extract the SDK .zip files +Because the files were downloaded directly from GitHub, the folder in the .zip file includes the name of the branch of the GitHub repo that was downloaded, in this case the `stable/latest` branch. But to use the files in the SDK, we need to rename the folder to the original name. + +**To rename the folders in the SDK .zip files** +First, extract the .zip files to a location of your choosing. Then open the location where you extracted the folders to. You can use either the GUI file system tools in your OS, or the command line. You should see 2 folders with the following names: +- braket-python-ir-stable-latest +- braket-python-sdk-stable-latest + +Rename the folders to the following: +- braket-python-ir +- braket-python-sdk + +Then copy the renamed files and paste them into the `braketvirtenv` folder where you created a virtual environment. Your folder structure should look like this: ```bash -git clone https://github.com/aws/braket-python-sdk.git --branch stable/latest +..\YourFolder\braketvirtenv\braket-python-ir\ ``` + +### Install the SDK packages +Use the following commands to install the SDKs in the order that they appear: + ```bash -pip install -e braket-python-sdk +pip install -e braket-python-ir ``` -If you receive an error related to SSH, see the following information to create and add an SSH key to your GitHub account. -- [Generate a new SSH key](https://help.github.com/en/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent) -- [Add an SSH Key to Your Account](https://help.github.com/en/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account) +```bash +pip install -e braket-python-sdk +``` ### Install latest Amazon Braket model in AWS CLI Run the following commands to install the Braket model. The file is downloaded to the current location. Position the cursor in the folder where you want to download the file before running the command. @@ -109,7 +149,7 @@ Use the following code sample to validate your environment configuration. from braket.circuits import Circuit from braket.aws import AwsQuantumSimulator -device_arn = "arn:aws:aqx:::quantum-simulator:aqx:qs1" +device = AwsQuantumSimulator("arn:aws:aqx:::quantum-simulator:aqx:qs1") s3_folder = ("braket-output-AWS_ACCOUNT_ID", "folder-name") bell = Circuit().h(0).cnot(0, 1) @@ -120,9 +160,9 @@ The code sample imports the Amazon Braket framework, then defines the execution ### Available Simulators There are currently three simulators available for Amazon Braket. To specify which simulator to use, change the code sample to replace the value for the `AwsQuantumSimulator` to one of the following values: -- `arn:aws:aqx:::quantum-simulator:aqx:qs1` – A Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the distribution - an array of bit strings that appears as though it came from a quantum computer. Outputs only shots and does not provide a state vector. -- `arn:aws:aqx:::quantum-simulator:aqx:qs2` – a TensorNetwork simulator. Provides an approximation of running a job on a quantum computer. -- `arn:aws:aqx:::quantum-simulator:aqx:qs3` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. This simulator samples from the distribution but includes the entire state vector. This generates more data, and therefore incurs additional costs for storage of data in Amazon S3. +- `arn:aws:aqx:::quantum-simulator:aqx:qs1` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the state vector and outputs an array of bit strings that appears as though it came from a quantum computer. Does not provide a state vector. +- `arn:aws:aqx:::quantum-simulator:aqx:qs2` – a tensor network simulator. Provides an approximation of running a job on a quantum computer. +- `arn:aws:aqx:::quantum-simulator:aqx:qs3` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples from the state vector but includes the entire state vector. This generates more data, and therefore incurs additional costs for storage of data in Amazon S3. #### To validate your configuration using a Python file 1. Open a text editor. @@ -130,7 +170,7 @@ There are currently three simulators available for Amazon Braket. To specify whi 3. Replace the `AWS_ACCOUNT_ID` in the value for `s3_folder` to your 12-digit AWS account ID. It should look similar to the following: `s3_folder = ("braket-output-123456789012", "folder-name")` 4. Save the file with the name `bellpair.py`. -5. Run the following command to run the Python file: +5. Make sure `braketvirtenv` is activated, and then run the following command in the location where you saved the bellpair.py file to run it: ```bash python bellpair.py ``` @@ -140,11 +180,22 @@ You should see a result similar to the following: #### To validate your configuration using a Jupyter notebook See [Installing the Jupyter Software](https://jupyter.org/install) for information about how to install Jupyter. You can use either JupyterLab or classic Jupyter Notebook. +Run the following commands to install Jupyter and then create a Braket kernel. +```bash +pip install jupyter ipykernel +``` + +```bash +python -m ipykernel install --user --name braket +``` + After you have installed Jupyter, use this command to open a Jupyter notebook so you can use the SDK within the notebook: ```bash jupyter notebook ``` -Jupyter opens in a browser window. Choose **New**, and then under **Notebooks**, choose **Python3**. +Jupyter opens in a browser window. Choose **New**, and then under **Notebooks**, choose **braket**. + +**Note** If you are using a Jupyter notebook from an prior installation and did not create a Braket kernel, you will not see braket available for the notebook type. Choose Python3 instead. If you choose Python3, you must have the Braket packages installed globally. Copy the code sample (above) into the notebook. Be sure to change the value for the `s3_folder` to replace `AWS_ACCOUNT_ID` with your 12-digit AWS Account ID. You can find your AWS account ID in the AWS console. The entry should look similar to the following: `s3_folder = ("braket-output-123456789012", "folder-name")` @@ -160,9 +211,9 @@ With Amazon Braket, you can run your quantum circuit on a physical quantum compu The following example executes the same Bell Pair example described to validate your configuration against an IonQ quantum computer. ```python from braket.circuits import Circuit -from braket.aws import AwsQuantumSimulator - -device_arn = "arn:aws:aqx:::qpu:ionq" +from braket.aws import AwsQpu + +device = AwsQpu("arn:aws:aqx:::qpu:rigetti") s3_folder = ("braket-output-AWS_ACCOUNT_ID", "folder-name") bell = Circuit().h(0).cnot(0, 1) @@ -170,28 +221,34 @@ print(device.run(bell, s3_folder).result().measurement_counts) ``` Specify which quantum computer hardware to use by changing the value of the `device_arn` to the value for quantum computer to use: -- **IonQ** "arn:aws:aqx:::qpu:ionq" +- **IonQ** "arn:aws:aqx:::qpu:ionq" (Jobs may take 24 hours to complete on IonQ.) - **Rigetti** "arn:aws:aqx:::qpu:rigetti" - **D-Wave** Not yet available -### Deactivat the virtual environment +### Deactivate the virtual environment After you are finished using the virtual environment to interact with Amazon Braket, you can deactivate it using the following command. +**To deactivate the virtual environment on Mac or Linux** ```bash -source deactivate +source braketvirtenv/bin/deactivate ``` -When you want to use it again, you can reactivate it. +**To deactivate the virtual environment on Windows** +```bash +cd braketvirtenv\scripts +``` +```bash +deactivate +``` + +When you want to use it again, you can reactivate it with the same command you used previously. ## Updating to the latest release We will periodically make updates and changes the SDK or the model. When you are notified of a change that requires action on your part, use the following steps to update your environment to the latest version. ### To get the lastest updates -Position your cursor in the folder where you cloned the GitHub repo for the Braket Python SDK, then run the following command to get the latest version. -```bash -git pull -``` +Information to be provided when the next update is available. ## Sample Notebooks Coming soon @@ -229,7 +286,6 @@ tox ``` ### Integration Tests - Set the `AWS_PROFILE` information in the Prerequisites section of this document. ```bash export AWS_PROFILE=Your_Profile_Name @@ -248,5 +304,4 @@ tox -e integ-tests -- -k 'your_test' ``` ## License - This project is licensed under the Apache-2.0 License. From 57bbb497c7c0c4b78677b9f32c96d19543a72897 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Tue, 28 Jan 2020 13:00:51 -0800 Subject: [PATCH 0018/1165] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 375c0df5..aa14c6a9 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ When the job completes, you should see output similar to the following: ## Running a Quantum Algorithm on a Quantum Computer With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The steps to do so are the same as those described to validate your environment. Just replace the example code provided in this document with your own code. -The following example executes the same Bell Pair example described to validate your configuration against an IonQ quantum computer. +The following example executes the same Bell Pair example described to validate your configuration against an Rigetti quantum computer. ```python from braket.circuits import Circuit from braket.aws import AwsQpu From 5ef8e99a4074ff50f00749ea6e3b46386d14e0b7 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Tue, 28 Jan 2020 13:01:26 -0800 Subject: [PATCH 0019/1165] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa14c6a9..228e4c31 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ When the job completes, you should see output similar to the following: ## Running a Quantum Algorithm on a Quantum Computer With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The steps to do so are the same as those described to validate your environment. Just replace the example code provided in this document with your own code. -The following example executes the same Bell Pair example described to validate your configuration against an Rigetti quantum computer. +The following example executes the same Bell Pair example described to validate your configuration against a Rigetti quantum computer. ```python from braket.circuits import Circuit from braket.aws import AwsQpu From 3dd3684776c91285457be0fb36ae8c6eb555b4a1 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Wed, 29 Jan 2020 09:42:57 -0800 Subject: [PATCH 0020/1165] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 228e4c31..4e923623 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ bell = Circuit().h(0).cnot(0, 1) print(device.run(bell, s3_folder).result().measurement_counts) ``` -The code sample imports the Amazon Braket framework, then defines the execution environment as the AWSQuantumSimulator and the device to use. The `s3_folder` statement defines the Amazon S3 bucket for job output. It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the job. +The code sample imports the Amazon Braket framework, then defines the execution environment as the AWSQuantumSimulator and the device to use. The `s3_folder` statement defines the Amazon S3 bucket for job output and the folder in the bucket to store job output. This folder is created when you run the job. It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the job. ### Available Simulators There are currently three simulators available for Amazon Braket. To specify which simulator to use, change the code sample to replace the value for the `AwsQuantumSimulator` to one of the following values: @@ -167,7 +167,7 @@ There are currently three simulators available for Amazon Braket. To specify whi #### To validate your configuration using a Python file 1. Open a text editor. 2. Copy the code sample (above), then paste it into the text editor. -3. Replace the `AWS_ACCOUNT_ID` in the value for `s3_folder` to your 12-digit AWS account ID. It should look similar to the following: +3. Replace the `AWS_ACCOUNT_ID` in the value for `s3_folder` to your 12-digit AWS account ID. If you want to use a different folder in the bucket, change `folder-name` to the name of the folder to create. If the folder already exists it uses the existing folder. Your statement should look similar to the following: `s3_folder = ("braket-output-123456789012", "folder-name")` 4. Save the file with the name `bellpair.py`. 5. Make sure `braketvirtenv` is activated, and then run the following command in the location where you saved the bellpair.py file to run it: @@ -197,7 +197,7 @@ Jupyter opens in a browser window. Choose **New**, and then under **Notebooks**, **Note** If you are using a Jupyter notebook from an prior installation and did not create a Braket kernel, you will not see braket available for the notebook type. Choose Python3 instead. If you choose Python3, you must have the Braket packages installed globally. -Copy the code sample (above) into the notebook. Be sure to change the value for the `s3_folder` to replace `AWS_ACCOUNT_ID` with your 12-digit AWS Account ID. You can find your AWS account ID in the AWS console. The entry should look similar to the following: +Copy the code sample (above) into the notebook. Be sure to change the value for the `s3_folder` to replace `AWS_ACCOUNT_ID` with your 12-digit AWS Account ID. You can find your AWS account ID in the AWS console. If you want to use a different folder in the bucket, change `folder-name` to the name of the folder to create. If the folder already exists it uses the existing folder. Your statement should look similar to the following: `s3_folder = ("braket-output-123456789012", "folder-name")` Choose **Run** to execute the code to confirm that your environment is configured correctly. From 88f169b123ecad9b5d0c32937e27ba6e352e6863 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Mon, 27 Jan 2020 11:28:35 -0800 Subject: [PATCH 0021/1165] Update to README (#20) * Update to README Restructure content to move prereqs to a common section. Copy edit pass for style and language. Formatting changes for hierarchy and organization. * Update readme for private beta Multiple changes throughout to clarify steps to configure one's environment for the beta release. * Update to readme Addressed feedback, corrected a couple of format issues, removed step to create a kernel to simplify. * update to readme Added additional content for using a qpu, incorporated feedback. * restoring section on using a virtenv --- README.md | 215 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 158 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 7d185d09..26d61b5b 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,225 @@ -**DO NOT SHARE OR TALK ABOUT THE CONTENTS OF THIS LIBRARY per the Amazon Beta NDA you signed.** +**This prerelease documentation is confidential and is provided under the terms of your nondisclosure agreement with Amazon Web Services (AWS) or other agreement governing your receipt of AWS confidential information.** -Amazon Braket Python SDK is an open source library for interacting with quantum devices on 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. -## Installation +## Prerequisites +Before you begin working with the Amazon Braket SDK, make sure that you've installed or configured the following prerequisites. -### Prerequisites -- Python 3.7.2+ +### Conda +Use the instructions for installing Conda from https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html. +Choose a regular installation, and then choose the Anaconda installer. Conda also installs Python, which is required for other steps in this document. Download and install the **Python 3.7 version** of Anaconda. -### Steps +### Using a Virtual Environment +For information about Conda and virtual environments, see [Conda vs. pip vs. virtualenv commands](https://docs.conda.io/projects/conda/en/latest/commands.html#conda-vs-pip-vs-virtualenv-commands). -1. Get a confirmation email from Amazon Braket confirming you have access to the service. +#### Creating a virtual environment +Use a virtual environment to interact with Amazon Braket to avoid changes to your global environment variables. The following commands create aand activate a virtual environment in Conda. If you want to use a name for the environment other than `yourenvname`, just change the value to the name you want to use. You may want to create folder in the file system to create the virtual environment. +Use the following command to create a directory +`mkdir braket` -2. (Optional) Recommended to work inside a virtual environment. You can skip this step if you don't care about mucking with your global python dependencies. See [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) if you don't have it already installed. +Then use the following command to move the cursor to the directory you created +`cd braket` ```bash -mkdir braket -cd braket +conda create -n braketvirtenv python=3.7 anaconda +``` +Press **y** when asked to confirm. -virtualenv venv -source venv/bin/activate +Then run the following command to activate the virtual environment. If you changed the environment name in the preceding command, be sure to change in this command as well. +```bash +source activate braketvirtenv ``` - -2. Install [AWS CLI](https://github.com/aws/aws-cli#installation) +### Git +Install Git from https://git-scm.com/downloads. Installation instructions are provided on the download page. + +### Access to Amazon Braket (Private Beta) +You can configure your environment for the Amazon Braket Python SDK, but you need to be granted permission to access Amazon Braket (Private Beta). Before you can execute code against Amazon Braket. If you’ve not received notification that you have been added to the beta, please contact the Braket team for assistance. + +### IAM user or role with required permissions +To perform the steps in this document and to interact with Amazon Braket, use an account with administrator privileges, such as one with the AdministratorAccess policy applied. To learn more about IAM user, roles, and policies, see [Adding and Removing IAM Identity Permissions](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage-attach-detach.html). + +### AWS CLI +#### Install and configure the AWS Command Line Interface (CLI) +Install the [AWS CLI](https://github.com/aws/aws-cli#installation) so that you can interact with AWS via the command line. This is required to perform some steps in this document. Instructions to install and configure the CLI are included on the installation page. + +#### Use the following command to install the AWS CLI ```bash pip install awscli ``` - -3. [Configure AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) settings to have a profile that can access your AWS account. Once a profile has been created set the `AWS_PROFILE` such that all future steps can connect to your AWS account. + +#### Configure a profile for the AWS CLI +Configure a CLI 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). + +After you create a profile, use the following command to set the `AWS_PROFILE` so that all future commands can access your AWS account and resources. ```bash export AWS_PROFILE=YOUR_PROFILE_NAME ``` - -4. Install `braket-python-sdk` package. +### Configure your AWS account with the resources necessary for Amazon Braket +Use the following link to an AWS CloudFormation template to automatically create the resources that Amazon Braket uses or interacts with in your account. The template creates the following resources: +- An S3 bucket to store job output. The bucket is named `braket-output-AWSaccountId` where AWSAccountId is your account ID. For example, if your AWS account ID is 123456789012, the bucket is named `braket-output-123456789012`. +- IAM roles named AmazonBraketJobExecutionRole, which is used to run jobs, and AQxFullAccess which is used to interact with the AWS resources that Amazon Braket needs. +- An IAM policy, AmazonBraketFullAccess, that includes permission to use Amazon Braket actions, as well as the permissions necessary to access the S3 bucket created. If you want to use a role that does not have admin permissions, you can apply the AmazonBraketFullAccess policy to the user or role you are using to grant the permissions required to use Amazon Braket beta. + +If you are already logged in to AWS when you click the link to open the template, the resources are created in the current account. If you want to use Amazon Braket beta in a different account, first log in using that account. + +[Amazon Braket CloudFormation Stack Creation template](https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https://braket-external-assets-prod-us-west-2.s3-us-west-2.amazonaws.com/templates/braket-resources.yaml&stackName=BraketResources) + +When the template loads, select the **I acknowledge that AWS CloudFormation might create IAM resources with custom names** checkbox, and then choose **Create Stack**. + +Wait until the Status changes to **CREATE_COMPLETE**. You may need to refresh the page to see the current status of the stack creation. + +## Setting up the Braket Python SDK +Use the steps in this section to install and configure the Braket Python SDK for your environment. You should perform the steps in the order in which they are included in this document. + +### Install the braket-python-sdk package +Use the following commands to install the Braket Python SDK package. ```bash git clone https://github.com/aws/braket-python-sdk.git --branch stable/latest -pip install -e braket-python-sdk ``` - -If you want to run tests and / or do any development on the framework run: ```bash -pip install -e "braket-python-sdk[test]" +pip install -e braket-python-sdk ``` - -5. Install latest Amazon Braket model in AWS CLI. + +If you receive an error related to SSH, see the following information to create and add an SSH key to your GitHub account. +- [Generate a new SSH key](https://help.github.com/en/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent) +- [Add an SSH Key to Your Account](https://help.github.com/en/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account) + +### Install latest Amazon Braket model in AWS CLI +Run the following commands to install the Braket model. The file is downloaded to the current location. Position the cursor in the folder where you want to download the file before running the command. ```bash aws s3 cp s3://braket-external-assets-prod-us-west-2/models/braket-2019-09-01.normal.json braket-model.json +``` +```bash aws configure add-model --service-model "file://braket-model.json" --service-name braket ``` +## Validate the Configuration +Your environment should now be configured to successfully use the Braket Python SDK to interact with Amazon Braket. You can use the following example to confirm that your settings are correct. -6. Create the necessary Amazon Braket resources in your AWS account. +You can confirm that your environment is correctly configured in either of the following ways: +- Create a Python file +- Use a Jupyter notebook -Follow the link below to create the resources using CloudFormation. This will create the required IAM resources and a default S3 bucket, `braket-output-${AWS::AccountId}`, for storing Amazon Braket outputs. +## Code sample for validating your configuration +Use the following code sample to validate your environment configuration. +```python +from braket.circuits import Circuit +from braket.aws import AwsQuantumSimulator + +device_arn = "arn:aws:aqx:::quantum-simulator:aqx:qs1" +s3_folder = ("braket-output-AWS_ACCOUNT_ID", "folder-name") -[Quick-Create in AWS CloudFormation](https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https://braket-external-assets-prod-us-west-2.s3-us-west-2.amazonaws.com/templates/braket-resources.yaml&stackName=BraketResources) +bell = Circuit().h(0).cnot(0, 1) +print(device.run(bell, s3_folder).result().measurement_counts) +``` +The code sample imports the Amazon Braket framework, then defines the execution environment as the AWSQuantumSimulator and the device to use. The `s3_folder` statement defines the Amazon S3 bucket for job output. It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the job. -7. You can now call Amazon Braket from the `braket-python-sdk`. +### Available Simulators +There are currently three simulators available for Amazon Braket. To specify which simulator to use, change the code sample to replace the value for the `AwsQuantumSimulator` to one of the following values: +- `arn:aws:aqx:::quantum-simulator:aqx:qs1` – A Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the distribution - an array of bit strings that appears as though it came from a quantum computer. Outputs only shots and does not provide a state vector. +- `arn:aws:aqx:::quantum-simulator:aqx:qs2` – a TensorNetwork simulator. Provides an approximation of running a job on a quantum computer. +- `arn:aws:aqx:::quantum-simulator:aqx:qs3` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. This simulator samples from the distribution but includes the entire state vector. This generates more data, and therefore incurs additional costs for storage of data in Amazon S3. +#### To validate your configuration using a Python file +1. Open a text editor. +2. Copy the code sample (above), then paste it into the text editor. +3. Replace the `AWS_ACCOUNT_ID` in the value for `s3_folder` to your 12-digit AWS account ID. It should look similar to the following: + `s3_folder = ("braket-output-123456789012", "folder-name")` +4. Save the file with the name `bellpair.py`. +5. Run the following command to run the Python file: + ```bash + python bellpair.py + ``` +You should see a result similar to the following: +```Counter({'11': 522, '00': 478})``` + +#### To validate your configuration using a Jupyter notebook +See [Installing the Jupyter Software](https://jupyter.org/install) for information about how to install Jupyter. You can use either JupyterLab or classic Jupyter Notebook. + +After you have installed Jupyter, use this command to open a Jupyter notebook so you can use the SDK within the notebook: +```bash +jupyter notebook +``` +Jupyter opens in a browser window. Choose **New**, and then under **Notebooks**, choose **Python3**. + +Copy the code sample (above) into the notebook. Be sure to change the value for the `s3_folder` to replace `AWS_ACCOUNT_ID` with your 12-digit AWS Account ID. You can find your AWS account ID in the AWS console. The entry should look similar to the following: +`s3_folder = ("braket-output-123456789012", "folder-name")` + +Choose **Run** to execute the code to confirm that your environment is configured correctly. + +When the job completes, you should see output similar to the following: +`Counter({'00': 519, '11': 481})` + +## Running a Quantum Algorithm on a Quantum Computer +With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The steps to do so are the same as those described to validate your environment. Just replace the example code provided in this document with your own code. + +The following example executes the same Bell Pair example described to validate your configuration against an IonQ quantum computer. ```python from braket.circuits import Circuit from braket.aws import AwsQuantumSimulator -device = AwsQuantumSimulator("arn:aws:aqx:::quantum-simulator:aqx:qs1") +device_arn = "arn:aws:aqx:::qpu:ionq" s3_folder = ("braket-output-AWS_ACCOUNT_ID", "folder-name") - + bell = Circuit().h(0).cnot(0, 1) print(device.run(bell, s3_folder).result().measurement_counts) ``` - -You should get output similar to... -``` -Counter({'11': 50, '00': 50}) -``` -7. Install [Jupyter](https://jupyter.org/install) and create an braket kernel. -``` -pip install jupyter ipykernel -python -m ipykernel install --user --name braket -``` - -8. You can now launch Jupyter and use the SDK within it. -``` -jupyter notebook +Specify which quantum computer hardware to use by changing the value of the `device_arn` to the value for quantum computer to use: +- **IonQ** "arn:aws:aqx:::qpu:ionq" +- **Rigetti** "arn:aws:aqx:::qpu:rigetti" +- **D-Wave** Not yet available + +### Deactivat the virtual environment +After you are finished using the virtual environment to interact with Amazon Braket, you can deactivate it using the following command. + +```bash +source deactivate ``` -## Update to latest changes +When you want to use it again, you can reactivate it. + -1. `cd` into the GitHub repository directory -2. run `git pull` +## Updating to the latest release +We will periodically make updates and changes the SDK or the model. When you are notified of a change that requires action on your part, use the following steps to update your environment to the latest version. + +### To get the lastest updates +Position your cursor in the folder where you cloned the GitHub repo for the Braket Python SDK, then run the following command to get the latest version. +```bash +git pull +``` ## Sample Notebooks -TODO +Coming soon ## Documentation +You can generate the documentation for the SDK. First change directories (`cd`) to position the cursor in the (`doc`) directory. +Then run the following command to generate the HTML documentation files: -First `cd` into the `doc` directory and run: ```bash make html ``` -Then open `BRAKET_SDK_ROOT/build/documentation/html/index.html` in a browser to view the docs. - -## Testing +To view the generated documentation, open the following file in a browser: +`BRAKET_SDK_ROOT/build/documentation/html/index.html` +## Install the SDK for Testing Make sure to install test dependencies first: -``` +```bash pip install -e "braket-python-sdk[test]" ``` ### Unit Tests -``` +```bash tox -e unit-tests ``` To run an individual test -```bash +``` tox -e unit-tests -- -k 'your_test' ``` @@ -129,9 +230,9 @@ tox ### Integration Tests -Set the `AWS_PROFILE` +Set the `AWS_PROFILE` information in the Prerequisites section of this document. ```bash -export AWS_PROFILE=PROFILE_FROM_STEP_3 +export AWS_PROFILE=Your_Profile_Name ``` Create an S3 bucket in the same account as the `AWS_PROFILE` with the following naming convention `braket-sdk-integ-tests-{account_id}`. From 96e81b5e595d99115a6ffff4ad002689ccc3e42c Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Tue, 28 Jan 2020 12:03:41 -0800 Subject: [PATCH 0022/1165] update to readme (#23) * update to readme reworking steps to resolve some issues on Mac * Update to update installation steps Changed to remove Conda and download repo zip files instead of configure SSH * update to readme Clean up to remove Conda, misc formatting * update to readme Formatting change * Update README.md * Update README.md addressing feedback from Ava * Update README.md added a note about jobs taking up to 24 hours on IonQ * Update README.md modified descriptions of simulators. * Update README.md Removed step to move the bellpair file to the virtual env. --- README.md | 139 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 97 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 26d61b5b..375c0df5 100644 --- a/README.md +++ b/README.md @@ -5,30 +5,48 @@ 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. -### Conda -Use the instructions for installing Conda from https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html. -Choose a regular installation, and then choose the Anaconda installer. Conda also installs Python, which is required for other steps in this document. Download and install the **Python 3.7 version** of Anaconda. +### Python 3.7.2 or greater +Download and install Python 3.7.2 or greater from [Python.org](https://www.python.org/downloads/). +If you are using Windows, choose **Add Python to environment variables** before you begin the installation. ### Using a Virtual Environment -For information about Conda and virtual environments, see [Conda vs. pip vs. virtualenv commands](https://docs.conda.io/projects/conda/en/latest/commands.html#conda-vs-pip-vs-virtualenv-commands). +If you want to use a virtual environment for interacting with the Amazon Braket SDK, first install virtualenv with the following command: +```bash +pip install virtualenv +``` + +To learn more, see [virtualenv](https://virtualenv.pypa.io/en/stable/installation/). -#### Creating a virtual environment -Use a virtual environment to interact with Amazon Braket to avoid changes to your global environment variables. The following commands create aand activate a virtual environment in Conda. If you want to use a name for the environment other than `yourenvname`, just change the value to the name you want to use. You may want to create folder in the file system to create the virtual environment. -Use the following command to create a directory -`mkdir braket` +On Windows, you should open a new terminal window to install `virtualenv`. If you use a terminal window that was open before you installed Python, the terminal window does not use the Python environment variables needed. + +After you install `virtualenv`, use the following command to create a virtual environment. When you run this command, it creates a folder named `braketvirtenv`, and uses that folder as the root folder of the virtual environment. You should run the command from a location where you want to create the virtual environment. + +**To create a virtual environment** +```bash +virtualenv braketvirtenv +``` + +Then use one of the following options to activate the virtual environment. + +**To activate the virtual environment on Mac and Linux**: +```bash +source braketvirtenv/bin/activate +``` -Then use the following command to move the cursor to the directory you created -`cd braket` +**To activate the virtual environment on Windows** +```bash +cd braketvirtenv\scripts +``` ```bash -conda create -n braketvirtenv python=3.7 anaconda +activate ``` -Press **y** when asked to confirm. -Then run the following command to activate the virtual environment. If you changed the environment name in the preceding command, be sure to change in this command as well. +Then run this command to return the cursor to the parent folder ```bash -source activate braketvirtenv +cd .. ``` + ### Git Install Git from https://git-scm.com/downloads. Installation instructions are provided on the download page. @@ -71,21 +89,43 @@ When the template loads, select the **I acknowledge that AWS CloudFormation migh Wait until the Status changes to **CREATE_COMPLETE**. You may need to refresh the page to see the current status of the stack creation. -## Setting up the Braket Python SDK -Use the steps in this section to install and configure the Braket Python SDK for your environment. You should perform the steps in the order in which they are included in this document. +## Setting up the Amazon Braket Python SDKs +Use the steps in this section to install and configure the Amazon Braket Python SDKs for your environment. You should perform the steps in the order in which they are included in this document. -### Install the braket-python-sdk package -Use the following commands to install the Braket Python SDK package. +### Download the Amazon Braket GitHub Repositories +The easiest way to get the SDKs is to download them directly from the GitHub site. Because the repositories are private during the Private Beta period, an SSH key is required to access the files remotely from a terminal session. If you download them directly from the GitHub site, you can just extract the files to your system or virtual environment without the extra steps of using an SSH key. You need to log in to GitHub using the account that was whitelisted for the Amazon Braket (Private Beta). + +Use the following links to download the Amazon Braket Python SDK repos: +- [braket-python-ir](https://github.com/aws/braket-python-ir/archive/stable/latest.zip) +- [braket-python-sdk](https://github.com/aws/braket-python-sdk/archive/stable/latest.zip) + +### Extract the SDK .zip files +Because the files were downloaded directly from GitHub, the folder in the .zip file includes the name of the branch of the GitHub repo that was downloaded, in this case the `stable/latest` branch. But to use the files in the SDK, we need to rename the folder to the original name. + +**To rename the folders in the SDK .zip files** +First, extract the .zip files to a location of your choosing. Then open the location where you extracted the folders to. You can use either the GUI file system tools in your OS, or the command line. You should see 2 folders with the following names: +- braket-python-ir-stable-latest +- braket-python-sdk-stable-latest + +Rename the folders to the following: +- braket-python-ir +- braket-python-sdk + +Then copy the renamed files and paste them into the `braketvirtenv` folder where you created a virtual environment. Your folder structure should look like this: ```bash -git clone https://github.com/aws/braket-python-sdk.git --branch stable/latest +..\YourFolder\braketvirtenv\braket-python-ir\ ``` + +### Install the SDK packages +Use the following commands to install the SDKs in the order that they appear: + ```bash -pip install -e braket-python-sdk +pip install -e braket-python-ir ``` -If you receive an error related to SSH, see the following information to create and add an SSH key to your GitHub account. -- [Generate a new SSH key](https://help.github.com/en/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent) -- [Add an SSH Key to Your Account](https://help.github.com/en/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account) +```bash +pip install -e braket-python-sdk +``` ### Install latest Amazon Braket model in AWS CLI Run the following commands to install the Braket model. The file is downloaded to the current location. Position the cursor in the folder where you want to download the file before running the command. @@ -109,7 +149,7 @@ Use the following code sample to validate your environment configuration. from braket.circuits import Circuit from braket.aws import AwsQuantumSimulator -device_arn = "arn:aws:aqx:::quantum-simulator:aqx:qs1" +device = AwsQuantumSimulator("arn:aws:aqx:::quantum-simulator:aqx:qs1") s3_folder = ("braket-output-AWS_ACCOUNT_ID", "folder-name") bell = Circuit().h(0).cnot(0, 1) @@ -120,9 +160,9 @@ The code sample imports the Amazon Braket framework, then defines the execution ### Available Simulators There are currently three simulators available for Amazon Braket. To specify which simulator to use, change the code sample to replace the value for the `AwsQuantumSimulator` to one of the following values: -- `arn:aws:aqx:::quantum-simulator:aqx:qs1` – A Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the distribution - an array of bit strings that appears as though it came from a quantum computer. Outputs only shots and does not provide a state vector. -- `arn:aws:aqx:::quantum-simulator:aqx:qs2` – a TensorNetwork simulator. Provides an approximation of running a job on a quantum computer. -- `arn:aws:aqx:::quantum-simulator:aqx:qs3` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. This simulator samples from the distribution but includes the entire state vector. This generates more data, and therefore incurs additional costs for storage of data in Amazon S3. +- `arn:aws:aqx:::quantum-simulator:aqx:qs1` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the state vector and outputs an array of bit strings that appears as though it came from a quantum computer. Does not provide a state vector. +- `arn:aws:aqx:::quantum-simulator:aqx:qs2` – a tensor network simulator. Provides an approximation of running a job on a quantum computer. +- `arn:aws:aqx:::quantum-simulator:aqx:qs3` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples from the state vector but includes the entire state vector. This generates more data, and therefore incurs additional costs for storage of data in Amazon S3. #### To validate your configuration using a Python file 1. Open a text editor. @@ -130,7 +170,7 @@ There are currently three simulators available for Amazon Braket. To specify whi 3. Replace the `AWS_ACCOUNT_ID` in the value for `s3_folder` to your 12-digit AWS account ID. It should look similar to the following: `s3_folder = ("braket-output-123456789012", "folder-name")` 4. Save the file with the name `bellpair.py`. -5. Run the following command to run the Python file: +5. Make sure `braketvirtenv` is activated, and then run the following command in the location where you saved the bellpair.py file to run it: ```bash python bellpair.py ``` @@ -140,11 +180,22 @@ You should see a result similar to the following: #### To validate your configuration using a Jupyter notebook See [Installing the Jupyter Software](https://jupyter.org/install) for information about how to install Jupyter. You can use either JupyterLab or classic Jupyter Notebook. +Run the following commands to install Jupyter and then create a Braket kernel. +```bash +pip install jupyter ipykernel +``` + +```bash +python -m ipykernel install --user --name braket +``` + After you have installed Jupyter, use this command to open a Jupyter notebook so you can use the SDK within the notebook: ```bash jupyter notebook ``` -Jupyter opens in a browser window. Choose **New**, and then under **Notebooks**, choose **Python3**. +Jupyter opens in a browser window. Choose **New**, and then under **Notebooks**, choose **braket**. + +**Note** If you are using a Jupyter notebook from an prior installation and did not create a Braket kernel, you will not see braket available for the notebook type. Choose Python3 instead. If you choose Python3, you must have the Braket packages installed globally. Copy the code sample (above) into the notebook. Be sure to change the value for the `s3_folder` to replace `AWS_ACCOUNT_ID` with your 12-digit AWS Account ID. You can find your AWS account ID in the AWS console. The entry should look similar to the following: `s3_folder = ("braket-output-123456789012", "folder-name")` @@ -160,9 +211,9 @@ With Amazon Braket, you can run your quantum circuit on a physical quantum compu The following example executes the same Bell Pair example described to validate your configuration against an IonQ quantum computer. ```python from braket.circuits import Circuit -from braket.aws import AwsQuantumSimulator - -device_arn = "arn:aws:aqx:::qpu:ionq" +from braket.aws import AwsQpu + +device = AwsQpu("arn:aws:aqx:::qpu:rigetti") s3_folder = ("braket-output-AWS_ACCOUNT_ID", "folder-name") bell = Circuit().h(0).cnot(0, 1) @@ -170,28 +221,34 @@ print(device.run(bell, s3_folder).result().measurement_counts) ``` Specify which quantum computer hardware to use by changing the value of the `device_arn` to the value for quantum computer to use: -- **IonQ** "arn:aws:aqx:::qpu:ionq" +- **IonQ** "arn:aws:aqx:::qpu:ionq" (Jobs may take 24 hours to complete on IonQ.) - **Rigetti** "arn:aws:aqx:::qpu:rigetti" - **D-Wave** Not yet available -### Deactivat the virtual environment +### Deactivate the virtual environment After you are finished using the virtual environment to interact with Amazon Braket, you can deactivate it using the following command. +**To deactivate the virtual environment on Mac or Linux** ```bash -source deactivate +source braketvirtenv/bin/deactivate ``` -When you want to use it again, you can reactivate it. +**To deactivate the virtual environment on Windows** +```bash +cd braketvirtenv\scripts +``` +```bash +deactivate +``` + +When you want to use it again, you can reactivate it with the same command you used previously. ## Updating to the latest release We will periodically make updates and changes the SDK or the model. When you are notified of a change that requires action on your part, use the following steps to update your environment to the latest version. ### To get the lastest updates -Position your cursor in the folder where you cloned the GitHub repo for the Braket Python SDK, then run the following command to get the latest version. -```bash -git pull -``` +Information to be provided when the next update is available. ## Sample Notebooks Coming soon @@ -229,7 +286,6 @@ tox ``` ### Integration Tests - Set the `AWS_PROFILE` information in the Prerequisites section of this document. ```bash export AWS_PROFILE=Your_Profile_Name @@ -248,5 +304,4 @@ tox -e integ-tests -- -k 'your_test' ``` ## License - This project is licensed under the Apache-2.0 License. From 0753aa3ff6ba156ccb43e3d8cb6dda6b6404e2d9 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Tue, 28 Jan 2020 13:00:51 -0800 Subject: [PATCH 0023/1165] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 375c0df5..aa14c6a9 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ When the job completes, you should see output similar to the following: ## Running a Quantum Algorithm on a Quantum Computer With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The steps to do so are the same as those described to validate your environment. Just replace the example code provided in this document with your own code. -The following example executes the same Bell Pair example described to validate your configuration against an IonQ quantum computer. +The following example executes the same Bell Pair example described to validate your configuration against an Rigetti quantum computer. ```python from braket.circuits import Circuit from braket.aws import AwsQpu From bacefe4a8205234b804262ce9eafaedd90aecfd9 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Tue, 28 Jan 2020 13:01:26 -0800 Subject: [PATCH 0024/1165] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa14c6a9..228e4c31 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ When the job completes, you should see output similar to the following: ## Running a Quantum Algorithm on a Quantum Computer With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The steps to do so are the same as those described to validate your environment. Just replace the example code provided in this document with your own code. -The following example executes the same Bell Pair example described to validate your configuration against an Rigetti quantum computer. +The following example executes the same Bell Pair example described to validate your configuration against a Rigetti quantum computer. ```python from braket.circuits import Circuit from braket.aws import AwsQpu From 7ed6c8fe903e89e824afc8690179bbebec021a2e Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Wed, 29 Jan 2020 09:42:57 -0800 Subject: [PATCH 0025/1165] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 228e4c31..4e923623 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ bell = Circuit().h(0).cnot(0, 1) print(device.run(bell, s3_folder).result().measurement_counts) ``` -The code sample imports the Amazon Braket framework, then defines the execution environment as the AWSQuantumSimulator and the device to use. The `s3_folder` statement defines the Amazon S3 bucket for job output. It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the job. +The code sample imports the Amazon Braket framework, then defines the execution environment as the AWSQuantumSimulator and the device to use. The `s3_folder` statement defines the Amazon S3 bucket for job output and the folder in the bucket to store job output. This folder is created when you run the job. It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the job. ### Available Simulators There are currently three simulators available for Amazon Braket. To specify which simulator to use, change the code sample to replace the value for the `AwsQuantumSimulator` to one of the following values: @@ -167,7 +167,7 @@ There are currently three simulators available for Amazon Braket. To specify whi #### To validate your configuration using a Python file 1. Open a text editor. 2. Copy the code sample (above), then paste it into the text editor. -3. Replace the `AWS_ACCOUNT_ID` in the value for `s3_folder` to your 12-digit AWS account ID. It should look similar to the following: +3. Replace the `AWS_ACCOUNT_ID` in the value for `s3_folder` to your 12-digit AWS account ID. If you want to use a different folder in the bucket, change `folder-name` to the name of the folder to create. If the folder already exists it uses the existing folder. Your statement should look similar to the following: `s3_folder = ("braket-output-123456789012", "folder-name")` 4. Save the file with the name `bellpair.py`. 5. Make sure `braketvirtenv` is activated, and then run the following command in the location where you saved the bellpair.py file to run it: @@ -197,7 +197,7 @@ Jupyter opens in a browser window. Choose **New**, and then under **Notebooks**, **Note** If you are using a Jupyter notebook from an prior installation and did not create a Braket kernel, you will not see braket available for the notebook type. Choose Python3 instead. If you choose Python3, you must have the Braket packages installed globally. -Copy the code sample (above) into the notebook. Be sure to change the value for the `s3_folder` to replace `AWS_ACCOUNT_ID` with your 12-digit AWS Account ID. You can find your AWS account ID in the AWS console. The entry should look similar to the following: +Copy the code sample (above) into the notebook. Be sure to change the value for the `s3_folder` to replace `AWS_ACCOUNT_ID` with your 12-digit AWS Account ID. You can find your AWS account ID in the AWS console. If you want to use a different folder in the bucket, change `folder-name` to the name of the folder to create. If the folder already exists it uses the existing folder. Your statement should look similar to the following: `s3_folder = ("braket-output-123456789012", "folder-name")` Choose **Run** to execute the code to confirm that your environment is configured correctly. From 7d697dd12553fda5ae2faf49b6d2f5b7a644c898 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 30 Jan 2020 16:15:32 -0800 Subject: [PATCH 0026/1165] Add annealing task creation (#24) Add annealing task creation --- src/braket/aws/aws_qpu.py | 28 ++- src/braket/aws/aws_quantum_simulator.py | 8 +- src/braket/aws/aws_quantum_task.py | 131 +++++++++++--- src/braket/devices/device.py | 50 +++--- .../tasks/annealing_quantum_task_result.py | 4 +- .../tasks/gate_model_quantum_task_result.py | 4 +- .../braket/aws/common_test_utils.py | 34 ++++ test/unit_tests/braket/aws/test_aws_qpu.py | 6 +- .../braket/aws/test_aws_quantum_simulator.py | 6 +- .../braket/aws/test_aws_quantum_task.py | 167 ++++++++++++++---- 10 files changed, 330 insertions(+), 108 deletions(-) diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py index 626b2599..8b126844 100644 --- a/src/braket/aws/aws_qpu.py +++ b/src/braket/aws/aws_qpu.py @@ -22,7 +22,7 @@ class AwsQpu(Device): """ Amazon Braket implementation of a QPU. - Use this class to retrieve the latest metadata about the QPU, and run a circuit on the QPU. + Use this class to retrieve the latest metadata about the QPU, and run a quantum task on the QPU. """ QPU_REGIONS = { @@ -55,16 +55,16 @@ def __init__(self, arn: str, aws_session=None): def run(self, *aws_quantum_task_args, **aws_quantum_task_kwargs) -> AwsQuantumTask: """ - Run a circuit on this AWS quantum device. + Run a quantum task specification (circuit or annealing problem) on this AWS quantum device. Args: *aws_quantum_task_args: Variable length positional arguments for - `braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit()`. + `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. **aws_quantum_task_kwargs: Variable length keyword arguments for - `braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit()`. + `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. Returns: - AwsQuantumTask: AwsQuantumTask that is tracking the circuit execution on the device. + AwsQuantumTask: AwsQuantumTask that tracks the execution on the device. Examples: >>> circuit = Circuit().h(0).cnot(0, 1) @@ -73,12 +73,24 @@ def run(self, *aws_quantum_task_args, **aws_quantum_task_kwargs) -> AwsQuantumTa >>> circuit = Circuit().h(0).cnot(0, 1) >>> device = AwsQpu("arn:aws:aqx:::qpu:rigetti") - >>> device.run(circuit=circuit, s3_destination_folder=("bucket-foo", "key-bar")) + >>> device.run(task_specification=circuit, + >>> s3_destination_folder=("bucket-foo", "key-bar")) + + >>> problem = Problem( + >>> ProblemType.ISING, + >>> linear={1: 3.14}, + >>> quadratic={(1, 2): 10.08}, + >>> ) + >>> device = AwsQpu("arn:aws:aqx:::qpu:d-wave") + >>> device.run(problem, ("bucket-foo", "key-bar"), + >>> backend_parameters = {"dWaveParameters": {"postprocessingType": "SAMPLING"}}) See Also: - `braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit()` + `braket.aws.aws_quantum_task.AwsQuantumTask.create()` """ - return AwsQuantumTask.from_circuit( + + # TODO: Restrict execution to compatible task types + return AwsQuantumTask.create( self._aws_session, self._arn, *aws_quantum_task_args, **aws_quantum_task_kwargs ) diff --git a/src/braket/aws/aws_quantum_simulator.py b/src/braket/aws/aws_quantum_simulator.py index dc83e870..978c5308 100644 --- a/src/braket/aws/aws_quantum_simulator.py +++ b/src/braket/aws/aws_quantum_simulator.py @@ -43,9 +43,9 @@ def run(self, *aws_quantum_task_args, **aws_quantum_task_kwargs) -> AwsQuantumTa Args: *aws_quantum_task_args: Variable length positional arguments for - `braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit()`. + `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. **aws_quantum_task_kwargs: Variable length keyword arguments for - `braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit()`. + `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. Returns: AwsQuantumTask: AwsQuantumTask that is tracking the circuit execution on the device. @@ -60,9 +60,9 @@ def run(self, *aws_quantum_task_args, **aws_quantum_task_kwargs) -> AwsQuantumTa >>> device.run(circuit=circuit, s3_destination_folder=("bucket-foo", "key-bar")) See Also: - `braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit()` + `braket.aws.aws_quantum_task.AwsQuantumTask.create()` """ - return AwsQuantumTask.from_circuit( + return AwsQuantumTask.create( self._aws_session, self._arn, *aws_quantum_task_args, **aws_quantum_task_kwargs ) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 24fe2392..6468aa52 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -11,13 +11,17 @@ # 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 import time -from typing import Any, Dict +from functools import singledispatch +from typing import Any, Callable, Dict, Union +from braket.annealing.problem import Problem from braket.aws.aws_session import AwsSession from braket.circuits.circuit import Circuit -from braket.tasks import GateModelQuantumTaskResult, QuantumTask +from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult, QuantumTask # TODO: add AnnealingQuantumTaskResult @@ -29,36 +33,45 @@ class AwsQuantumTask(QuantumTask): RESULTS_READY_STATES = {"COMPLETED"} GATE_IR_TYPE = "jaqcd" + ANNEALING_IR_TYPE = "annealing" DEFAULT_SHOTS = 1_000 @staticmethod - def from_circuit( + def create( aws_session: AwsSession, device_arn: str, - circuit: Circuit, + task_specification: Union[Circuit, Problem], s3_destination_folder: AwsSession.S3DestinationFolder, shots: int = DEFAULT_SHOTS, - ): + backend_parameters: Dict[str, Any] = None, + *args, + **kwargs, + ) -> AwsQuantumTask: """ - AwsQuantumTask factory method that serializes the circuit, submits to Amazon Braket, and - returns back a AwsQuantumTask tracking the execution. + AwsQuantumTask factory method that serializes a quantum task specification + (either a quantum circuit or annealing problem), submits it to Amazon Braket, + and returns back an AwsQuantumTask tracking the execution. Args: aws_session (AwsSession): AwsSession to call AWS with. device_arn (str): AWS quantum device arn. - circuit (Circuit): Circuit to run on device. + task_specification (Union[Circuit, Problem]): Specification of task + (circuit or annealing problem) to run on device. s3_destination_folder (AwsSession.S3DestinationFolder): NamedTuple with bucket (index 0) and key (index 1) that is the results destination folder in S3. - shots (int): The number of times to run the circuit on the quantum device. If the - device is a classical simulator then this implies sampling the state N times, + shots (int): The number of times to run the circuit or annealing task on the device. + If the device is a classical simulator then this implies sampling the state N times, where N = `shots`. Default = 1_000. + backend_parameters (Dict[str, Any]): Additional parameters to pass to the device. + For example, for D-Wave: + >>> backend_parameters = {"dWaveParameters": {"postprocess": "OPTIMIZATION"}} Returns: - AwsQuantumTask: AwsQuantumTask that is tracking the circuit execution on the device. + AwsQuantumTask: AwsQuantumTask tracking the task execution on the device. Note: The following arguments are typically defined via clients of Device. - - `circuit` + - `task_specification` - `s3_destination_folder` - `shots` """ @@ -67,22 +80,21 @@ def from_circuit( "s3_destination_folder must be of size 2 with a 'bucket' and 'key' respectively." ) - create_task_kwargs = { - "backendArn": device_arn, - "resultsS3Bucket": s3_destination_folder[0], - "resultsS3Prefix": s3_destination_folder[1], - "ir": circuit.to_ir().json(), - "irType": AwsQuantumTask.GATE_IR_TYPE, - "backendParameters": {"gateModelParameters": {"qubitCount": circuit.qubit_count}}, - "shots": shots, - } - task_arn = aws_session.create_quantum_task(**create_task_kwargs) - return AwsQuantumTask(task_arn, aws_session) + create_task_kwargs = _create_common_params(device_arn, s3_destination_folder, shots) + return _create_internal( + task_specification, + aws_session, + create_task_kwargs, + backend_parameters or {}, + *args, + **kwargs, + ) def __init__( self, arn: str, aws_session: AwsSession, + results_formatter: Callable[[str], Any], poll_timeout_seconds: int = 120, poll_interval_seconds: int = 0.25, ): @@ -90,16 +102,19 @@ def __init__( Args: arn (str): The AWS quantum task ARN. aws_session (AwsSession): The AwsSession for communicating with AWS. + results_formatter (Callable[[str], Any]): A function that deserializes a string + into a results structure (such as GateModelQuantumTaskResult) poll_timeout_seconds (int): The polling timeout for result(), default 120 seconds. - poll_internal_seconds (int): The polling interval for result(), default 0.25 seconds. + poll_interval_seconds (int): The polling interval for result(), default 0.25 seconds. """ self._arn: str = arn self._aws_session: AwsSession = aws_session + self._results_formatter = results_formatter self._poll_timeout_seconds = poll_timeout_seconds self._poll_interval_seconds = poll_interval_seconds self._metadata: Dict[str, Any] = {} - self._result: GateModelQuantumTaskResult = None + self._result: Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult] = None self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) @property @@ -150,7 +165,7 @@ def state(self, use_cached_value: bool = False) -> str: """ return self.metadata(use_cached_value).get("status") - def result(self) -> GateModelQuantumTaskResult: + def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: """ Get the quantum task result by polling Amazon Braket to see if the task is completed. Once the task is completed the result is retrieved from S3 and returned as a QuantumTaskResult. @@ -213,7 +228,7 @@ async def _wait_for_completion(self) -> GateModelQuantumTaskResult: result_string = self._aws_session.retrieve_s3_object_body( current_metadata["resultsS3Bucket"], current_metadata["resultsS3ObjectKey"] ) - self._result = GateModelQuantumTaskResult.from_string(result_string) + self._result = self._results_formatter(result_string) return self._result elif current_metadata["status"] in AwsQuantumTask.TERMINAL_STATES: self._result = None @@ -226,7 +241,7 @@ async def _wait_for_completion(self) -> GateModelQuantumTaskResult: return None def __repr__(self) -> str: - return "AwsQuantumTask('id':{})".format(self.id) + return f"AwsQuantumTask('id':{self.id})" def __eq__(self, other) -> bool: if isinstance(other, AwsQuantumTask): @@ -235,3 +250,63 @@ def __eq__(self, other) -> bool: def __hash__(self) -> int: return hash(self.id) + + +@singledispatch +def _create_internal( + task_specification: Union[Circuit, Problem], + aws_session: AwsSession, + create_task_kwargs: Dict[str, Any], + backend_parameters: Dict[str, Any], + *args, + **kwargs, +) -> AwsQuantumTask: + raise TypeError("Invalid task specification type") + + +@_create_internal.register +def _( + circuit: Circuit, aws_session: AwsSession, create_task_kwargs: Dict[str, Any], *args, **kwargs, +) -> AwsQuantumTask: + create_task_kwargs.update( + { + "ir": circuit.to_ir().json(), + "irType": AwsQuantumTask.GATE_IR_TYPE, + "backendParameters": {"gateModelParameters": {"qubitCount": circuit.qubit_count}}, + } + ) + + task_arn = aws_session.create_quantum_task(**create_task_kwargs) + return AwsQuantumTask(task_arn, aws_session, GateModelQuantumTaskResult.from_string) + + +@_create_internal.register +def _( + problem: Problem, + aws_session: AwsSession, + create_task_kwargs: Dict[str, Any], + backend_parameters: Dict[str, Any], + *args, + **kwargs, +) -> AwsQuantumTask: + create_task_kwargs.update( + { + "ir": problem.to_ir().json(), + "irType": AwsQuantumTask.ANNEALING_IR_TYPE, + "backendParameters": {"annealingModelParameters": backend_parameters}, + } + ) + + task_arn = aws_session.create_quantum_task(**create_task_kwargs) + return AwsQuantumTask(task_arn, aws_session, AnnealingQuantumTaskResult.from_string) + + +def _create_common_params( + device_arn: str, s3_destination_folder: AwsSession.S3DestinationFolder, shots: int +) -> Dict[str, Any]: + return { + "backendArn": device_arn, + "resultsS3Bucket": s3_destination_folder[0], + "resultsS3Prefix": s3_destination_folder[1], + "shots": shots, + } diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 92dea104..40b45371 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -12,58 +12,66 @@ # language governing permissions and limitations under the License. from abc import ABC, abstractmethod +from typing import Union +from braket.annealing.problem import Problem +from braket.circuits import Circuit from braket.tasks.quantum_task import QuantumTask class Device(ABC): - """ - An abstraction over quantum devices which includes quantum computers and simulators. + """ An abstraction over quantum devices which includes quantum computers and simulators. - :param str name: name of quantum device - :param str status: status of quantum device - :param str status_reason: status reason of quantum device """ def __init__(self, name: str, status: str, status_reason: str): + """ + Args: + name: Name of quantum device + status: Status of quantum device + status_reason: Status reason of quantum device + """ self._name = name self._status = status self._status_reason = status_reason @abstractmethod - def run(self, circuit, shots: int) -> QuantumTask: - """ - Run a circuit on this quantum device. + def run(self, task_specification: Union[Circuit, Problem], shots: int) -> QuantumTask: + """ Run a quantum task specification (circuit or annealing program) on this quantum device. - :param Circuit circuit: circuit to run on device - :param int shots: Number of shots to run circuit - :return: the created quantum task - :rtype: QuantumTask + Args: + task_specification (Union[Circuit, Problem]): Specification of task + (circuit or annealing problem) to run on device. + shots (int): The number of times to run the circuit or annealing task on the device. + + Returns: + QuantumTask: The QuantumTask tracking task execution on this device """ + pass @property def name(self) -> str: - """ - Return name of Device + """ Return the name of this Device. - :rtype: str + Returns: + str: The name of this Device """ return self._name @property def status(self) -> str: - """ - Return status of Device + """ Return the status of this Device. - :rtype: str + Returns: + str: The status of thi Device """ return self._status @property def status_reason(self) -> str: - """ - Return status reason of Device + """ Return the status reason of this Device. - :rtype: str + Returns: + str: The status reason of this Device """ return self._status_reason diff --git a/src/braket/tasks/annealing_quantum_task_result.py b/src/braket/tasks/annealing_quantum_task_result.py index bca36466..2ea61f95 100644 --- a/src/braket/tasks/annealing_quantum_task_result.py +++ b/src/braket/tasks/annealing_quantum_task_result.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 json from dataclasses import dataclass from typing import Any, Dict @@ -91,7 +93,7 @@ def __eq__(self, other) -> bool: return NotImplemented @staticmethod - def from_string(result: str) -> "AnnealingQuantumTaskResult": + def from_string(result: str) -> AnnealingQuantumTaskResult: """ Create AnnealingQuantumTaskResult from string diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 9bb3690b..95b1d9bc 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.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 json from dataclasses import dataclass from typing import Any, Counter, Dict @@ -129,7 +131,7 @@ def measurements_from_measurement_probabilities( return np.asarray(measurements_list, dtype=int) @staticmethod - def from_string(result: str) -> "GateModelQuantumTaskResult": + def from_string(result: str) -> GateModelQuantumTaskResult: """ Create GateModelQuantumTaskResult from string diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 9ae63d6d..6530fea8 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -216,3 +216,37 @@ class MockS3: "MeasurementProbabilities": {"011000": 0.9999999999999982}, } ) + + MOCK_S3_RESULT_4 = json.dumps( + { + "Solutions": [[-1, -1, -1, -1], [1, -1, 1, 1], [1, -1, -1, 1]], + "VariableCount": 4, + "Values": [0.0, 1.0, 2.0], + "SolutionCounts": None, + "ProblemType": "ising", + "DWaveMetadata": { + "ActiveVariables": [0], + "Timing": { + "QpuSamplingTime": 1575, + "QpuAnnealTimePerSample": 20, + "QpuReadoutTimePerSample": 274, + "QpuAccessTime": 10917, + "QpuAccessOverheadTime": 3382, + "QpuProgrammingTime": 9342, + "QpuDelayTimePerSample": 21, + "TotalPostProcessingTime": 117, + "PostProcessingOverheadTime": 117, + "TotalRealTime": 10917, + "RunTimeChip": 1575, + "AnnealTimePerRun": 20, + "ReadoutTimePerRun": 274, + }, + }, + "TaskMetadata": { + "Id": "UUID_blah_1", + "Status": "COMPLETED", + "BackendArn": AwsQpuArns.DWAVE, + "Shots": 5, + }, + } + ) diff --git a/test/unit_tests/braket/aws/test_aws_qpu.py b/test/unit_tests/braket/aws/test_aws_qpu.py index 7cab4252..17a0ef4b 100644 --- a/test/unit_tests/braket/aws/test_aws_qpu.py +++ b/test/unit_tests/braket/aws/test_aws_qpu.py @@ -179,12 +179,12 @@ def test_repr(qpu): assert repr(qpu) == expected -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_positional_args(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): _run_and_assert(aws_quantum_task_mock, qpu, [circuit, s3_destination_folder], {}) -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_kwargs(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): _run_and_assert( aws_quantum_task_mock, @@ -194,7 +194,7 @@ def test_run_with_kwargs(aws_quantum_task_mock, qpu, circuit, s3_destination_fol ) -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_positional_args_and_kwargs( aws_quantum_task_mock, qpu, circuit, s3_destination_folder ): diff --git a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py index 28c8c9ff..f11afb2d 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py @@ -95,12 +95,12 @@ def test_repr(simulator): assert repr(simulator) == expected -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_positional_args(aws_quantum_task_mock, simulator, circuit, s3_destination_folder): _run_and_assert(aws_quantum_task_mock, simulator, [circuit, s3_destination_folder], {}) -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_kwargs(aws_quantum_task_mock, simulator, circuit, s3_destination_folder): _run_and_assert( aws_quantum_task_mock, @@ -110,7 +110,7 @@ def test_run_with_kwargs(aws_quantum_task_mock, simulator, circuit, s3_destinati ) -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.from_circuit") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_positional_args_and_kwargs( aws_quantum_task_mock, simulator, circuit, s3_destination_folder ): 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 7ee11550..357617c8 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -15,10 +15,11 @@ from unittest.mock import Mock import pytest +from braket.annealing.problem import Problem, ProblemType from braket.aws import AwsQuantumTask from braket.aws.aws_session import AwsSession from braket.circuits import Circuit -from braket.tasks import GateModelQuantumTaskResult +from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult from common_test_utils import MockS3 S3_TARGET = AwsSession.S3DestinationFolder("foo", "bar") @@ -33,7 +34,21 @@ def aws_session(): @pytest.fixture def quantum_task(aws_session): - return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) + return AwsQuantumTask("foo:bar:arn", aws_session, lambda x: x, poll_timeout_seconds=2) + + +@pytest.fixture +def circuit_task(aws_session): + return AwsQuantumTask( + "foo:bar:arn", aws_session, GateModelQuantumTaskResult.from_string, poll_timeout_seconds=2 + ) + + +@pytest.fixture +def annealing_task(aws_session): + return AwsQuantumTask( + "foo:bar:arn", aws_session, AnnealingQuantumTaskResult.from_string, poll_timeout_seconds=2 + ) @pytest.fixture @@ -46,10 +61,15 @@ def circuit(): return Circuit().h(0).cnot(0, 1) +@pytest.fixture +def problem(): + return Problem(ProblemType.ISING, linear={1: 3.14}, quadratic={(1, 2): 10.08}) + + def test_equality(arn, aws_session): - quantum_task_1 = AwsQuantumTask(arn, aws_session) - quantum_task_2 = AwsQuantumTask(arn, aws_session) - other_quantum_task = AwsQuantumTask("different:arn", aws_session) + quantum_task_1 = AwsQuantumTask(arn, aws_session, lambda x: x) + quantum_task_2 = AwsQuantumTask(arn, aws_session, lambda x: x) + other_quantum_task = AwsQuantumTask("different:arn", aws_session, lambda x: x) non_quantum_task = quantum_task_1.id assert quantum_task_1 == quantum_task_2 @@ -64,11 +84,11 @@ def test_str(quantum_task): def test_hash(quantum_task): - hash(quantum_task) == hash(quantum_task.id) + assert hash(quantum_task) == hash(quantum_task.id) def test_id_getter(arn, aws_session): - quantum_task = AwsQuantumTask(arn, aws_session) + quantum_task = AwsQuantumTask(arn, aws_session, lambda x: x) assert quantum_task.id == arn @@ -110,38 +130,50 @@ def test_cancel(quantum_task): quantum_task._aws_session.cancel_quantum_task.assert_called_with(quantum_task.id) -def test_result(quantum_task): - _mock_state(quantum_task._aws_session, "COMPLETED") - _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_1) +def test_result_circuit(circuit_task): + _mock_state(circuit_task._aws_session, "COMPLETED") + _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_1) expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) - assert quantum_task.result() == expected + assert circuit_task.result() == expected - s3_bucket = quantum_task.metadata()["resultsS3Bucket"] - s3_object_key = quantum_task.metadata()["resultsS3ObjectKey"] - quantum_task._aws_session.retrieve_s3_object_body.assert_called_with(s3_bucket, s3_object_key) + s3_bucket = circuit_task.metadata()["resultsS3Bucket"] + s3_object_key = circuit_task.metadata()["resultsS3ObjectKey"] + circuit_task._aws_session.retrieve_s3_object_body.assert_called_with(s3_bucket, s3_object_key) -def test_result_is_cached(quantum_task): - _mock_state(quantum_task._aws_session, "COMPLETED") - _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_1) - quantum_task.result() +def test_result_annealing(annealing_task): + _mock_state(annealing_task._aws_session, "COMPLETED") + _mock_s3(annealing_task._aws_session, MockS3.MOCK_S3_RESULT_4) + + expected = AnnealingQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_4) + assert annealing_task.result() == expected + + s3_bucket = annealing_task.metadata()["resultsS3Bucket"] + s3_object_key = annealing_task.metadata()["resultsS3ObjectKey"] + annealing_task._aws_session.retrieve_s3_object_body.assert_called_with(s3_bucket, s3_object_key) - _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_2) + +def test_result_is_cached(circuit_task): + _mock_state(circuit_task._aws_session, "COMPLETED") + _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_1) + circuit_task.result() + + _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_2) expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) - assert quantum_task.result() == expected + assert circuit_task.result() == expected -def test_async_result(quantum_task): +def test_async_result(circuit_task): 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_state(quantum_task._aws_session, "RUNNING") - _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_1) + _mock_state(circuit_task._aws_session, "RUNNING") + _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_1) - future = quantum_task.async_result() + future = circuit_task.async_result() # test the different ways to get the result from async @@ -150,7 +182,7 @@ def set_result_from_callback(future): future.add_done_callback(set_result_from_callback) # via asyncio waiting for result - _mock_state(quantum_task._aws_session, "COMPLETED") + _mock_state(circuit_task._aws_session, "COMPLETED") event_loop = asyncio.get_event_loop() result_from_waiting = event_loop.run_until_complete(future) @@ -176,7 +208,11 @@ def test_timeout(aws_session): # Setup the poll timing such that the timeout will occur after one API poll quantum_task = AwsQuantumTask( - "foo:bar:arn", aws_session, poll_timeout_seconds=0.5, poll_interval_seconds=1 + "foo:bar:arn", + aws_session, + GateModelQuantumTaskResult.from_string, + poll_timeout_seconds=0.5, + poll_interval_seconds=1, ) assert quantum_task.result() is None @@ -185,19 +221,34 @@ def test_timeout(aws_session): @pytest.mark.xfail(raises=ValueError) -def test_from_circuit_invalid_s3_folder(aws_session, arn, circuit): - AwsQuantumTask.from_circuit(aws_session, arn, circuit, ("bucket",)) +def test_create_invalid_s3_folder(aws_session, arn, circuit): + AwsQuantumTask.create(aws_session, arn, circuit, ("bucket",)) + + +@pytest.mark.xfail(raises=TypeError) +def test_create_invalid_task_specification(aws_session, arn, circuit): + mocked_task_arn = "task-arn-1" + aws_session.create_quantum_task.return_value = mocked_task_arn + AwsQuantumTask.create(aws_session, arn, "foo", S3_TARGET) def test_from_circuit_default_shots(aws_session, arn, circuit): mocked_task_arn = "task-arn-1" aws_session.create_quantum_task.return_value = mocked_task_arn - task = AwsQuantumTask.from_circuit(aws_session, arn, circuit, S3_TARGET) - assert task == AwsQuantumTask(mocked_task_arn, aws_session) + task = AwsQuantumTask.create(aws_session, arn, circuit, S3_TARGET) + assert task == AwsQuantumTask( + mocked_task_arn, aws_session, GateModelQuantumTaskResult.from_string + ) _assert_create_quantum_task_called_with( - aws_session, arn, circuit, S3_TARGET, AwsQuantumTask.DEFAULT_SHOTS + aws_session, + arn, + circuit, + AwsQuantumTask.GATE_IR_TYPE, + S3_TARGET, + AwsQuantumTask.DEFAULT_SHOTS, + {"gateModelParameters": {"qubitCount": circuit.qubit_count}}, ) @@ -206,22 +257,60 @@ def test_from_circuit_with_shots(aws_session, arn, circuit): aws_session.create_quantum_task.return_value = mocked_task_arn shots = 53 - task = AwsQuantumTask.from_circuit(aws_session, arn, circuit, S3_TARGET) - assert task == AwsQuantumTask(mocked_task_arn, aws_session) + task = AwsQuantumTask.create(aws_session, arn, circuit, S3_TARGET, shots=shots) + assert task == AwsQuantumTask( + mocked_task_arn, aws_session, GateModelQuantumTaskResult.from_string + ) + + _assert_create_quantum_task_called_with( + aws_session, + arn, + circuit, + AwsQuantumTask.GATE_IR_TYPE, + S3_TARGET, + shots, + {"gateModelParameters": {"qubitCount": circuit.qubit_count}}, + ) + - _assert_create_quantum_task_called_with(aws_session, arn, circuit, S3_TARGET, shots) +def test_from_annealing(aws_session, arn, problem): + mocked_task_arn = "task-arn-1" + aws_session.create_quantum_task.return_value = mocked_task_arn + + task = AwsQuantumTask.create( + aws_session, + arn, + problem, + S3_TARGET, + backend_parameters={"dWaveParameters": {"postprocessingType": "OPTIMIZATION"}}, + ) + assert task == AwsQuantumTask( + mocked_task_arn, aws_session, AnnealingQuantumTaskResult.from_string + ) + + _assert_create_quantum_task_called_with( + aws_session, + arn, + problem, + AwsQuantumTask.ANNEALING_IR_TYPE, + S3_TARGET, + AwsQuantumTask.DEFAULT_SHOTS, + {"annealingModelParameters": {"dWaveParameters": {"postprocessingType": "OPTIMIZATION"}}}, + ) -def _assert_create_quantum_task_called_with(aws_session, arn, circuit, s3_results_prefix, shots): +def _assert_create_quantum_task_called_with( + aws_session, arn, task_description, ir_type, s3_results_prefix, shots, backend_parameters +): aws_session.create_quantum_task.assert_called_with( **{ "backendArn": arn, "resultsS3Bucket": s3_results_prefix[0], "resultsS3Prefix": s3_results_prefix[1], - "ir": circuit.to_ir().json(), - "irType": AwsQuantumTask.GATE_IR_TYPE, - "backendParameters": {"gateModelParameters": {"qubitCount": circuit.qubit_count}}, - "shots": AwsQuantumTask.DEFAULT_SHOTS, + "ir": task_description.to_ir().json(), + "irType": ir_type, + "backendParameters": backend_parameters, + "shots": shots, } ) From 1eddc382ec1702a66f6671594ee5d620bde67e36 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Tue, 11 Feb 2020 09:24:41 -0800 Subject: [PATCH 0027/1165] Update to section on creating resources Included a note in the section about creating resources for Amazon Braket to indicate that the template should be used only once per AWS account. --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e923623..f207d6d6 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,10 @@ export AWS_PROFILE=YOUR_PROFILE_NAME ``` ### Configure your AWS account with the resources necessary for Amazon Braket -Use the following link to an AWS CloudFormation template to automatically create the resources that Amazon Braket uses or interacts with in your account. The template creates the following resources: +Use the following link to an AWS CloudFormation template to automatically create the resources that Amazon Braket uses or interacts with in your account. +** Important ** +If you are using IAM roles for multiple users in your organization to access the Amazon Braket Private Beta from the same account, only open the template and create the stack once for the account. Each role in the account then has access to the resources Amazon Braket needs in the account. +The template creates the following resources: - An S3 bucket to store job output. The bucket is named `braket-output-AWSaccountId` where AWSAccountId is your account ID. For example, if your AWS account ID is 123456789012, the bucket is named `braket-output-123456789012`. - IAM roles named AmazonBraketJobExecutionRole, which is used to run jobs, and AQxFullAccess which is used to interact with the AWS resources that Amazon Braket needs. - An IAM policy, AmazonBraketFullAccess, that includes permission to use Amazon Braket actions, as well as the permissions necessary to access the S3 bucket created. If you want to use a role that does not have admin permissions, you can apply the AmazonBraketFullAccess policy to the user or role you are using to grant the permissions required to use Amazon Braket beta. From baea9760e59eb95632a140e78dd309bb7be2a5c0 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Tue, 11 Feb 2020 09:27:43 -0800 Subject: [PATCH 0028/1165] Update README.md --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f207d6d6..b5b3aa11 100644 --- a/README.md +++ b/README.md @@ -76,9 +76,12 @@ export AWS_PROFILE=YOUR_PROFILE_NAME ``` ### Configure your AWS account with the resources necessary for Amazon Braket -Use the following link to an AWS CloudFormation template to automatically create the resources that Amazon Braket uses or interacts with in your account. -** Important ** -If you are using IAM roles for multiple users in your organization to access the Amazon Braket Private Beta from the same account, only open the template and create the stack once for the account. Each role in the account then has access to the resources Amazon Braket needs in the account. +Use the following link to an AWS CloudFormation template to automatically create the resources that Amazon Braket uses or interacts with in your account. + +**Important** + +If you are using IAM roles for multiple users in your organization to access the Amazon Braket Private Beta from the same account, only open the template and create the stack once for the account. Each role in the account then has access to the resources Amazon Braket needs in the account. + The template creates the following resources: - An S3 bucket to store job output. The bucket is named `braket-output-AWSaccountId` where AWSAccountId is your account ID. For example, if your AWS account ID is 123456789012, the bucket is named `braket-output-123456789012`. - IAM roles named AmazonBraketJobExecutionRole, which is used to run jobs, and AQxFullAccess which is used to interact with the AWS resources that Amazon Braket needs. From 5e40184fa81c48bd7a38e96fce9dbb8fba31c866 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Tue, 11 Feb 2020 14:40:09 -0800 Subject: [PATCH 0029/1165] Add note about using stable branch (#25) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4e923623..dbb3052d 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,8 @@ Use the following links to download the Amazon Braket Python SDK repos: ### Extract the SDK .zip files Because the files were downloaded directly from GitHub, the folder in the .zip file includes the name of the branch of the GitHub repo that was downloaded, in this case the `stable/latest` branch. But to use the files in the SDK, we need to rename the folder to the original name. +Note: Make sure you are always using the branch 'stable/latest' and not 'master' for the SDK. 'master' may contain unstable changes. + **To rename the folders in the SDK .zip files** First, extract the .zip files to a location of your choosing. Then open the location where you extracted the folders to. You can use either the GUI file system tools in your OS, or the command line. You should see 2 folders with the following names: - braket-python-ir-stable-latest From 81d219237d4317b0663824345ab597fcdeffe95c Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Tue, 11 Feb 2020 14:43:38 -0800 Subject: [PATCH 0030/1165] Add note about using stable branch --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b5b3aa11..8e29c098 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,8 @@ Use the following links to download the Amazon Braket Python SDK repos: ### Extract the SDK .zip files Because the files were downloaded directly from GitHub, the folder in the .zip file includes the name of the branch of the GitHub repo that was downloaded, in this case the `stable/latest` branch. But to use the files in the SDK, we need to rename the folder to the original name. +Note: Make sure you are always using the branch 'stable/latest' and not 'master' for the SDK. 'master' may contain unstable changes. + **To rename the folders in the SDK .zip files** First, extract the .zip files to a location of your choosing. Then open the location where you extracted the folders to. You can use either the GUI file system tools in your OS, or the command line. You should see 2 folders with the following names: - braket-python-ir-stable-latest From 442018ea10aa69fa736252eade397f2ef0eeef71 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Wed, 12 Feb 2020 15:37:40 -0800 Subject: [PATCH 0031/1165] Update index.rst Updates to the description, links, and removed incomplete section. --- doc/index.rst | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/index.rst b/doc/index.rst index 079e4eba..a3cad250 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,11 +1,9 @@ -Amazon Braket Python SDK +Amazon Braket (Private Beta) Python SDK ======================== -Amazon Braket Python SDK is an open source library for interacting with quantum devices on Amazon Braket. +Amazon Braket Python SDK is an open source library for interacting with quantum devices on Amazon Braket (Private Beta). -TODO describe the different feature sets / abstractions. - -Here you'll find an overview and API documentation for Amazon Braket Python SDK. The project homepage is in GitHub, https://github.com/aws/braket-python-sdk, where you can find the SDK source and installation instructions for the library. +This documentation provides information about the Amazon Braket Python SDK API. For information about how to configure your environment to use the braket-python-sdk, please see the Readme in the GitHub repo for this project at https://github.com/aws/braket-python-sdk/tree/stable/latest. Indices and tables __________________ From 7c93d0d4e6cb2c9b9af4428a6972f3e26f8ce5b3 Mon Sep 17 00:00:00 2001 From: kshitijc Date: Thu, 13 Feb 2020 10:24:05 -0800 Subject: [PATCH 0032/1165] Modify AsciiCircuitDiagram to account for width of timestamps (#26) --- src/braket/circuits/ascii_circuit_diagram.py | 2 +- .../circuits/test_ascii_circuit_diagram.py | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index 04cf2b4a..51cbe9da 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -104,7 +104,7 @@ def _ascii_diagram_moment( if qubit != min(instr.target): margins[qubit] = "|" - symbols_width = max([len(symbol) for symbol in symbols.values()]) + symbols_width = max([len(symbol) for symbol in symbols.values()] + [len(str(time))]) output = "{0:{width}}|\n".format(str(time), width=symbols_width) for qubit in circuit_qubits: 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 1763092f..5fcc15ca 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -55,6 +55,52 @@ def to_ir(self, target): assert AsciiCircuitDiagram.build_diagram(circ) == expected +def test_time_width(): + circ = Circuit() + num_qubits = 15 + 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|7|8|9|10|11|12|13|", + " ", + "q0 : -C-------------------------------", + " | ", + "q1 : -X-C-----------------------------", + " | ", + "q2 : ---X-C---------------------------", + " | ", + "q3 : -----X-C-------------------------", + " | ", + "q4 : -------X-C-----------------------", + " | ", + "q5 : ---------X-C---------------------", + " | ", + "q6 : -----------X-C-------------------", + " | ", + "q7 : -------------X-C-----------------", + " | ", + "q8 : ---------------X-C---------------", + " | ", + "q9 : -----------------X-C-------------", + " | ", + "q10 : -------------------X-C-----------", + " | ", + "q11 : ---------------------X--C--------", + " | ", + "q12 : ------------------------X--C-----", + " | ", + "q13 : ---------------------------X--C--", + " | ", + "q14 : ------------------------------X--", + "", + "T : |0|1|2|3|4|5|6|7|8|9|10|11|12|13|", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_diagram(circ) == expected + + def test_connector_across_two_qubits(): circ = Circuit().cnot(3, 4).h(range(2, 6)) expected = ( From 897df1dbabab26b4e69811fd33a3767f26d52eee Mon Sep 17 00:00:00 2001 From: kshitijc Date: Thu, 13 Feb 2020 10:24:31 -0800 Subject: [PATCH 0033/1165] Add parameters to the ascii representation of parameterized gates (#27) * Add parameters to the ascii representation of parameterized gates * Limit precision while printing angles for gates --- src/braket/circuits/gates.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index abb9b3ee..cbbb2418 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -412,7 +412,7 @@ class Rx(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Rx"]) + super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Rx({:.3g})".format(angle)]) def to_ir(self, target: QubitSet) -> np.ndarray: return ir.Rx(target=target[0], angle=self.angle) @@ -451,7 +451,7 @@ class Ry(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Ry"]) + super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Ry({:.3g})".format(angle)]) def to_ir(self, target: QubitSet) -> np.ndarray: return ir.Ry(target=target[0], angle=self.angle) @@ -490,7 +490,7 @@ class Rz(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Rz"]) + super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Rz({:.3g})".format(angle)]) def to_ir(self, target: QubitSet) -> np.ndarray: return ir.Rz(target=target[0], angle=self.angle) @@ -529,7 +529,7 @@ class PhaseShift(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=1, ascii_symbols=["PHASE"]) + super().__init__(angle=angle, qubit_count=1, ascii_symbols=["PHASE({:.3g})".format(angle)]) def to_ir(self, target: QubitSet) -> np.ndarray: return ir.PhaseShift(target=target[0], angle=self.angle) @@ -690,7 +690,7 @@ class PSwap(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["PSWAP", "PSWAP"]) + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["PSWAP({:.3g})".format(angle), "PSWAP({:.3g})".format(angle)]) def to_ir(self, target: QubitSet): return ir.PSwap(targets=[target[0], target[1]], angle=self.angle) @@ -734,7 +734,7 @@ class XY(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["XY", "XY"]) + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["XY({:.3g})".format(angle), "XY({:.3g})".format(angle)]) def to_ir(self, target: QubitSet): return ir.XY(targets=[target[0], target[1]], angle=self.angle) @@ -780,7 +780,7 @@ class CPhaseShift(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE"]) + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE({:.3g})".format(angle)]) def to_ir(self, target: QubitSet) -> np.ndarray: return ir.CPhaseShift(control=target[0], target=target[1], angle=self.angle) @@ -818,7 +818,7 @@ class CPhaseShift00(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE00"]) + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE00({:.3g})".format(angle)]) def to_ir(self, target: QubitSet) -> np.ndarray: return ir.CPhaseShift00(control=target[0], target=target[1], angle=self.angle) @@ -856,7 +856,7 @@ class CPhaseShift01(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE01"]) + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE01({:.3g})".format(angle)]) def to_ir(self, target: QubitSet) -> np.ndarray: return ir.CPhaseShift01(control=target[0], target=target[1], angle=self.angle) @@ -894,7 +894,7 @@ class CPhaseShift10(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE10"]) + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE10({:.3g})".format(angle)]) def to_ir(self, target: QubitSet) -> np.ndarray: return ir.CPhaseShift10(control=target[0], target=target[1], angle=self.angle) @@ -1006,7 +1006,7 @@ class XX(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["XX", "XX"]) + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["XX({:.3g})".format(angle), "XX({:.3g})".format(angle)]) def to_ir(self, target: QubitSet): return ir.XX(targets=[target[0], target[1]], angle=self.angle) @@ -1051,7 +1051,7 @@ class YY(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["YY", "YY"]) + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["YY({:.3g})".format(angle), "YY({:.3g})".format(angle)]) def to_ir(self, target: QubitSet): return ir.YY(targets=[target[0], target[1]], angle=self.angle) @@ -1098,7 +1098,7 @@ class ZZ(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["ZZ", "ZZ"]) + super().__init__(angle=angle, qubit_count=2, ascii_symbols=["ZZ({:.3g})".format(angle), "ZZ({:.3g})".format(angle)]) def to_ir(self, target: QubitSet): return ir.ZZ(targets=[target[0], target[1]], angle=self.angle) From 9f99e29b99aefbc1d7c2b69ed9c67ba7c39c2933 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Mon, 17 Feb 2020 18:49:27 -0800 Subject: [PATCH 0034/1165] Fix return types of to_ir and to_matrix responses (#28) Move the `ndarray` return type from `to_ir` to `to_matrix` --- src/braket/circuits/gates.py | 102 ++++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 37 deletions(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index cbbb2418..062ebdcf 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -414,10 +414,10 @@ class Rx(AngledGate): def __init__(self, angle: float): super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Rx({:.3g})".format(angle)]) - def to_ir(self, target: QubitSet) -> np.ndarray: + def to_ir(self, target: QubitSet): return ir.Rx(target=target[0], angle=self.angle) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: cos = np.cos(self.angle / 2) sin = np.sin(self.angle / 2) return np.array([[cos, -1j * sin], [-1j * sin, cos]], dtype=complex) @@ -453,10 +453,10 @@ class Ry(AngledGate): def __init__(self, angle: float): super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Ry({:.3g})".format(angle)]) - def to_ir(self, target: QubitSet) -> np.ndarray: + def to_ir(self, target: QubitSet): return ir.Ry(target=target[0], angle=self.angle) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: cos = np.cos(self.angle / 2) sin = np.sin(self.angle / 2) return np.array([[cos, -sin], [+sin, cos]], dtype=complex) @@ -492,10 +492,10 @@ class Rz(AngledGate): def __init__(self, angle: float): super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Rz({:.3g})".format(angle)]) - def to_ir(self, target: QubitSet) -> np.ndarray: + def to_ir(self, target: QubitSet): return ir.Rz(target=target[0], angle=self.angle) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: return np.array( [[np.exp(-1j * self.angle / 2), 0], [0, np.exp(1j * self.angle / 2)]], dtype=complex ) @@ -531,10 +531,10 @@ class PhaseShift(AngledGate): def __init__(self, angle: float): super().__init__(angle=angle, qubit_count=1, ascii_symbols=["PHASE({:.3g})".format(angle)]) - def to_ir(self, target: QubitSet) -> np.ndarray: + def to_ir(self, target: QubitSet): return ir.PhaseShift(target=target[0], angle=self.angle) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, np.exp(1j * self.angle)]], dtype=complex) @staticmethod @@ -570,7 +570,7 @@ def __init__(self): def to_ir(self, target: QubitSet): return ir.CNot(control=target[0], target=target[1]) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: return np.array( [ [1.0, 0.0, 0.0, 0.0], @@ -611,7 +611,7 @@ def __init__(self): def to_ir(self, target: QubitSet): return ir.Swap(targets=[target[0], target[1]]) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: return np.array( [ [1.0, 0.0, 0.0, 0.0], @@ -651,7 +651,7 @@ def __init__(self): def to_ir(self, target: QubitSet): return ir.ISwap(targets=[target[0], target[1]]) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: return np.array( [ [1.0, 0.0, 0.0, 0.0], @@ -690,12 +690,16 @@ class PSwap(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["PSWAP({:.3g})".format(angle), "PSWAP({:.3g})".format(angle)]) + super().__init__( + angle=angle, + qubit_count=2, + ascii_symbols=["PSWAP({:.3g})".format(angle), "PSWAP({:.3g})".format(angle)], + ) def to_ir(self, target: QubitSet): return ir.PSwap(targets=[target[0], target[1]], angle=self.angle) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: return np.array( [ [1.0, 0.0, 0.0, 0.0], @@ -734,12 +738,16 @@ class XY(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["XY({:.3g})".format(angle), "XY({:.3g})".format(angle)]) + super().__init__( + angle=angle, + qubit_count=2, + ascii_symbols=["XY({:.3g})".format(angle), "XY({:.3g})".format(angle)], + ) def to_ir(self, target: QubitSet): return ir.XY(targets=[target[0], target[1]], angle=self.angle) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: cos = np.cos(self.angle / 2) sin = np.sin(self.angle / 2) return np.array( @@ -780,12 +788,14 @@ class CPhaseShift(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE({:.3g})".format(angle)]) + super().__init__( + angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE({:.3g})".format(angle)] + ) - def to_ir(self, target: QubitSet) -> np.ndarray: + def to_ir(self, target: QubitSet): return ir.CPhaseShift(control=target[0], target=target[1], angle=self.angle) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, 1.0, np.exp(1j * self.angle)]) @staticmethod @@ -818,12 +828,14 @@ class CPhaseShift00(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE00({:.3g})".format(angle)]) + super().__init__( + angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE00({:.3g})".format(angle)] + ) - def to_ir(self, target: QubitSet) -> np.ndarray: + def to_ir(self, target: QubitSet): return ir.CPhaseShift00(control=target[0], target=target[1], angle=self.angle) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: return np.diag([np.exp(1j * self.angle), 1.0, 1.0, 1.0]) @staticmethod @@ -856,12 +868,14 @@ class CPhaseShift01(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE01({:.3g})".format(angle)]) + super().__init__( + angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE01({:.3g})".format(angle)] + ) - def to_ir(self, target: QubitSet) -> np.ndarray: + def to_ir(self, target: QubitSet): return ir.CPhaseShift01(control=target[0], target=target[1], angle=self.angle) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: return np.diag([1.0, np.exp(1j * self.angle), 1.0, 1.0]) @staticmethod @@ -894,12 +908,14 @@ class CPhaseShift10(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE10({:.3g})".format(angle)]) + super().__init__( + angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE10({:.3g})".format(angle)] + ) - def to_ir(self, target: QubitSet) -> np.ndarray: + def to_ir(self, target: QubitSet): return ir.CPhaseShift10(control=target[0], target=target[1], angle=self.angle) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, np.exp(1j * self.angle), 1.0]) @staticmethod @@ -933,7 +949,7 @@ def __init__(self): def to_ir(self, target: QubitSet): return ir.CY(control=target[0], target=target[1]) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: return np.array( [ [1.0, 0.0, 0.0, 0.0], @@ -974,7 +990,7 @@ def __init__(self): def to_ir(self, target: QubitSet): return ir.CZ(control=target[0], target=target[1]) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, 1.0, -1.0]) @staticmethod @@ -1006,12 +1022,16 @@ class XX(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["XX({:.3g})".format(angle), "XX({:.3g})".format(angle)]) + super().__init__( + angle=angle, + qubit_count=2, + ascii_symbols=["XX({:.3g})".format(angle), "XX({:.3g})".format(angle)], + ) def to_ir(self, target: QubitSet): return ir.XX(targets=[target[0], target[1]], angle=self.angle) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: return (1 / math.sqrt(2)) * np.array( [ [1.0, 0.0, 0.0, -1.0j * np.exp(1.0j * self.angle)], @@ -1051,12 +1071,16 @@ class YY(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["YY({:.3g})".format(angle), "YY({:.3g})".format(angle)]) + super().__init__( + angle=angle, + qubit_count=2, + ascii_symbols=["YY({:.3g})".format(angle), "YY({:.3g})".format(angle)], + ) def to_ir(self, target: QubitSet): return ir.YY(targets=[target[0], target[1]], angle=self.angle) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: cos = np.cos(self.angle) sin = np.sin(self.angle) return np.array( @@ -1098,12 +1122,16 @@ class ZZ(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=2, ascii_symbols=["ZZ({:.3g})".format(angle), "ZZ({:.3g})".format(angle)]) + super().__init__( + angle=angle, + qubit_count=2, + ascii_symbols=["ZZ({:.3g})".format(angle), "ZZ({:.3g})".format(angle)], + ) def to_ir(self, target: QubitSet): return ir.ZZ(targets=[target[0], target[1]], angle=self.angle) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: return np.array( [ [np.exp(1j * (self.angle / 2)), 0.0, 0.0, 0.0], @@ -1147,7 +1175,7 @@ def __init__(self): def to_ir(self, target: QubitSet): return ir.CCNot(controls=[target[0], target[1]], target=target[2]) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: return np.array( [ [1, 0, 0, 0, 0, 0, 0, 0], @@ -1192,7 +1220,7 @@ def __init__(self): def to_ir(self, target: QubitSet): return ir.CSwap(control=target[0], targets=[target[1], target[2]]) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: return np.array( [ [1, 0, 0, 0, 0, 0, 0, 0], From 6d47fd982997ca41b4f4caa00dab879a57896151 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 18 Feb 2020 14:35:32 -0800 Subject: [PATCH 0035/1165] Rquire circuit, location and shots in device.run (#29) These fields are always required regardless for task creation, so they should also be part of the device.run contract. --- src/braket/aws/aws_qpu.py | 25 ++++++++-- src/braket/aws/aws_quantum_simulator.py | 25 ++++++++-- src/braket/devices/device.py | 9 ++-- test/unit_tests/braket/aws/test_aws_qpu.py | 38 +++++++++++---- .../braket/aws/test_aws_quantum_simulator.py | 48 ++++++++++++++++--- 5 files changed, 121 insertions(+), 24 deletions(-) diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py index 8b126844..5afb3bbe 100644 --- a/src/braket/aws/aws_qpu.py +++ b/src/braket/aws/aws_qpu.py @@ -10,12 +10,14 @@ # 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 typing import Any, Dict +from typing import Any, Dict, Optional, Union import boto3 +from braket.annealing.problem import Problem from braket.aws.aws_qpu_arns import AwsQpuArns from braket.aws.aws_quantum_task import AwsQuantumTask from braket.aws.aws_session import AwsSession +from braket.circuits import Circuit from braket.devices.device import Device @@ -53,11 +55,22 @@ def __init__(self, arn: str, aws_session=None): self._properties = None self.refresh_metadata() - def run(self, *aws_quantum_task_args, **aws_quantum_task_kwargs) -> AwsQuantumTask: + def run( + self, + task_specification: Union[Circuit, Problem], + s3_destination_folder: AwsSession.S3DestinationFolder, + shots: Optional[int] = None, + *aws_quantum_task_args, + **aws_quantum_task_kwargs, + ) -> AwsQuantumTask: """ Run a quantum task specification (circuit or annealing problem) on this AWS quantum device. Args: + task_specification (Union[Circuit, Problem]): Specification of task + (circuit or annealing problem) to run on device. + s3_destination_folder: The S3 location to save the task's results + shots (Optional[int]): The number of times to run the circuit or annealing task *aws_quantum_task_args: Variable length positional arguments for `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. **aws_quantum_task_kwargs: Variable length keyword arguments for @@ -91,7 +104,13 @@ def run(self, *aws_quantum_task_args, **aws_quantum_task_kwargs) -> AwsQuantumTa # TODO: Restrict execution to compatible task types return AwsQuantumTask.create( - self._aws_session, self._arn, *aws_quantum_task_args, **aws_quantum_task_kwargs + self._aws_session, + self._arn, + task_specification, + s3_destination_folder, + shots, + *aws_quantum_task_args, + **aws_quantum_task_kwargs, ) def refresh_metadata(self) -> None: diff --git a/src/braket/aws/aws_quantum_simulator.py b/src/braket/aws/aws_quantum_simulator.py index 978c5308..f1c7d9db 100644 --- a/src/braket/aws/aws_quantum_simulator.py +++ b/src/braket/aws/aws_quantum_simulator.py @@ -11,10 +11,12 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import Any, Dict +from typing import Any, Dict, Optional, Union +from braket.annealing.problem import Problem from braket.aws.aws_quantum_task import AwsQuantumTask from braket.aws.aws_session import AwsSession +from braket.circuits import Circuit from braket.devices.device import Device @@ -37,11 +39,22 @@ def __init__(self, arn: str, aws_session=None): self._properties: Dict[str, Any] = None self.refresh_metadata() - def run(self, *aws_quantum_task_args, **aws_quantum_task_kwargs) -> AwsQuantumTask: + def run( + self, + task_specification: Union[Circuit, Problem], + s3_destination_folder: AwsSession.S3DestinationFolder, + shots: Optional[int] = None, + *aws_quantum_task_args, + **aws_quantum_task_kwargs, + ) -> AwsQuantumTask: """ Run a circuit on this AWS quantum device. Args: + task_specification (Union[Circuit, Problem]): Specification of task + (circuit or annealing problem) to run on device. + s3_destination_folder: The S3 location to save the task's results + shots (Optional[int]): The number of times to run the circuit or annealing task *aws_quantum_task_args: Variable length positional arguments for `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. **aws_quantum_task_kwargs: Variable length keyword arguments for @@ -63,7 +76,13 @@ def run(self, *aws_quantum_task_args, **aws_quantum_task_kwargs) -> AwsQuantumTa `braket.aws.aws_quantum_task.AwsQuantumTask.create()` """ return AwsQuantumTask.create( - self._aws_session, self._arn, *aws_quantum_task_args, **aws_quantum_task_kwargs + self._aws_session, + self._arn, + task_specification, + s3_destination_folder, + shots, + *aws_quantum_task_args, + **aws_quantum_task_kwargs, ) def refresh_metadata(self) -> None: diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 40b45371..9d9c4e51 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. from abc import ABC, abstractmethod -from typing import Union +from typing import Optional, Union from braket.annealing.problem import Problem from braket.circuits import Circuit @@ -36,13 +36,16 @@ def __init__(self, name: str, status: str, status_reason: str): self._status_reason = status_reason @abstractmethod - def run(self, task_specification: Union[Circuit, Problem], shots: int) -> QuantumTask: + def run( + self, task_specification: Union[Circuit, Problem], location, shots: Optional[int] + ) -> QuantumTask: """ Run a quantum task specification (circuit or annealing program) on this quantum device. Args: task_specification (Union[Circuit, Problem]): Specification of task (circuit or annealing problem) to run on device. - shots (int): The number of times to run the circuit or annealing task on the device. + location: The location to save the task's results + shots (Optional[int]): The number of times to run the circuit or annealing task Returns: QuantumTask: The QuantumTask tracking task execution on this device diff --git a/test/unit_tests/braket/aws/test_aws_qpu.py b/test/unit_tests/braket/aws/test_aws_qpu.py index 17a0ef4b..f0355de7 100644 --- a/test/unit_tests/braket/aws/test_aws_qpu.py +++ b/test/unit_tests/braket/aws/test_aws_qpu.py @@ -181,16 +181,20 @@ def test_repr(qpu): @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_positional_args(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): - _run_and_assert(aws_quantum_task_mock, qpu, [circuit, s3_destination_folder], {}) + _run_and_assert(aws_quantum_task_mock, qpu, circuit, s3_destination_folder, 1000, ["foo"], {}) @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_kwargs(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): _run_and_assert( - aws_quantum_task_mock, - qpu, - [], - {"circuit": circuit, "s3_destination_folder": s3_destination_folder}, + aws_quantum_task_mock, qpu, circuit, s3_destination_folder, 1000, [], {"bar": 1, "baz": 2} + ) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_with_kwargs_no_shots(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): + _run_and_assert( + aws_quantum_task_mock, qpu, circuit, s3_destination_folder, None, [], {"bar": 1, "baz": 2} ) @@ -198,17 +202,33 @@ def test_run_with_kwargs(aws_quantum_task_mock, qpu, circuit, s3_destination_fol def test_run_with_positional_args_and_kwargs( aws_quantum_task_mock, qpu, circuit, s3_destination_folder ): - _run_and_assert(aws_quantum_task_mock, qpu, [circuit, s3_destination_folder], {"shots": 100}) + _run_and_assert( + aws_quantum_task_mock, + qpu, + circuit, + s3_destination_folder, + 1000, + ["foo"], + {"bar": 1, "baz": 2}, + ) -def _run_and_assert(aws_quantum_task_mock, qpu, run_args, run_kwargs): +def _run_and_assert( + aws_quantum_task_mock, qpu, circuit, s3_destination_folder, shots, run_args, run_kwargs +): task_mock = Mock() aws_quantum_task_mock.return_value = task_mock qpu = qpu(AwsQpuArns.RIGETTI) - task = qpu.run(*run_args, **run_kwargs) + task = ( + qpu.run(circuit, s3_destination_folder, shots, *run_args, **run_kwargs) + if shots + else qpu.run(circuit, s3_destination_folder, *run_args, **run_kwargs) + ) assert task == task_mock - aws_quantum_task_mock.assert_called_with(qpu._aws_session, qpu.arn, *run_args, **run_kwargs) + aws_quantum_task_mock.assert_called_with( + qpu._aws_session, qpu.arn, circuit, s3_destination_folder, shots, *run_args, **run_kwargs + ) def _assert_qpu_fields(qpu, properties_keys, expected_qpu_data): diff --git a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py index f11afb2d..ccd4f192 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py @@ -97,7 +97,9 @@ def test_repr(simulator): @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_positional_args(aws_quantum_task_mock, simulator, circuit, s3_destination_folder): - _run_and_assert(aws_quantum_task_mock, simulator, [circuit, s3_destination_folder], {}) + _run_and_assert( + aws_quantum_task_mock, simulator, circuit, s3_destination_folder, 1000, ["foo"], {} + ) @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") @@ -105,8 +107,24 @@ def test_run_with_kwargs(aws_quantum_task_mock, simulator, circuit, s3_destinati _run_and_assert( aws_quantum_task_mock, simulator, + circuit, + s3_destination_folder, + None, + [], + {"bar": 1, "baz": 2}, + ) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_with_kwargs_no_shots(aws_quantum_task_mock, simulator, circuit, s3_destination_folder): + _run_and_assert( + aws_quantum_task_mock, + simulator, + circuit, + s3_destination_folder, + 1000, [], - {"circuit": circuit, "s3_destination_folder": s3_destination_folder}, + {"bar": 1, "baz": 2}, ) @@ -115,17 +133,35 @@ def test_run_with_positional_args_and_kwargs( aws_quantum_task_mock, simulator, circuit, s3_destination_folder ): _run_and_assert( - aws_quantum_task_mock, simulator, [circuit, s3_destination_folder], {"shots": 100} + aws_quantum_task_mock, + simulator, + circuit, + s3_destination_folder, + 1000, + ["foo"], + {"bar": 1, "baz": 2}, ) -def _run_and_assert(aws_quantum_task_mock, simulator, run_args, run_kwargs): +def _run_and_assert( + aws_quantum_task_mock, simulator, circuit, s3_destination_folder, shots, run_args, run_kwargs +): task_mock = Mock() aws_quantum_task_mock.return_value = task_mock simulator = simulator(AwsQuantumSimulatorArns.QS1) - task = simulator.run(*run_args, **run_kwargs) + task = ( + simulator.run(circuit, s3_destination_folder, shots, *run_args, **run_kwargs) + if shots + else simulator.run(circuit, s3_destination_folder, *run_args, **run_kwargs) + ) assert task == task_mock aws_quantum_task_mock.assert_called_with( - simulator._aws_session, simulator.arn, *run_args, **run_kwargs + simulator._aws_session, + simulator.arn, + circuit, + s3_destination_folder, + shots, + *run_args, + **run_kwargs ) From d2861d6bd6c917d8ce98712450b0943dba9f7ed3 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 19 Feb 2020 14:44:45 -0800 Subject: [PATCH 0036/1165] Add args and kwargs to create_task internal (#30) * Add args and kwargs to create_task internal So polling parameters can be set * Cleaner shots default passing Use None for default shots until create_task API is invoked --- src/braket/aws/aws_qpu.py | 2 +- src/braket/aws/aws_quantum_simulator.py | 2 +- src/braket/aws/aws_quantum_task.py | 31 +++++++++++++++++-------- src/braket/devices/device.py | 7 +++++- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py index 5afb3bbe..849fb1fe 100644 --- a/src/braket/aws/aws_qpu.py +++ b/src/braket/aws/aws_qpu.py @@ -70,7 +70,7 @@ def run( task_specification (Union[Circuit, Problem]): Specification of task (circuit or annealing problem) to run on device. s3_destination_folder: The S3 location to save the task's results - shots (Optional[int]): The number of times to run the circuit or annealing task + shots (Optional[int]): The number of times to run the circuit or annealing problem *aws_quantum_task_args: Variable length positional arguments for `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. **aws_quantum_task_kwargs: Variable length keyword arguments for diff --git a/src/braket/aws/aws_quantum_simulator.py b/src/braket/aws/aws_quantum_simulator.py index f1c7d9db..3d00c49a 100644 --- a/src/braket/aws/aws_quantum_simulator.py +++ b/src/braket/aws/aws_quantum_simulator.py @@ -54,7 +54,7 @@ def run( task_specification (Union[Circuit, Problem]): Specification of task (circuit or annealing problem) to run on device. s3_destination_folder: The S3 location to save the task's results - shots (Optional[int]): The number of times to run the circuit or annealing task + shots (Optional[int]): The number of times to run the circuit or annealing problem *aws_quantum_task_args: Variable length positional arguments for `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. **aws_quantum_task_kwargs: Variable length keyword arguments for diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 6468aa52..60dade82 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -16,7 +16,7 @@ import asyncio import time from functools import singledispatch -from typing import Any, Callable, Dict, Union +from typing import Any, Callable, Dict, Optional, Union from braket.annealing.problem import Problem from braket.aws.aws_session import AwsSession @@ -36,13 +36,16 @@ class AwsQuantumTask(QuantumTask): ANNEALING_IR_TYPE = "annealing" DEFAULT_SHOTS = 1_000 + DEFAULT_RESULTS_POLL_TIMEOUT = 120 + DEFAULT_RESULTS_POLL_INTERVAL = 0.25 + @staticmethod def create( aws_session: AwsSession, device_arn: str, task_specification: Union[Circuit, Problem], s3_destination_folder: AwsSession.S3DestinationFolder, - shots: int = DEFAULT_SHOTS, + shots: Optional[int] = None, backend_parameters: Dict[str, Any] = None, *args, **kwargs, @@ -59,9 +62,9 @@ def create( (circuit or annealing problem) to run on device. s3_destination_folder (AwsSession.S3DestinationFolder): NamedTuple with bucket (index 0) and key (index 1) that is the results destination folder in S3. - shots (int): The number of times to run the circuit or annealing task on the device. - If the device is a classical simulator then this implies sampling the state N times, - where N = `shots`. Default = 1_000. + shots (Optional[int]): The number of times to run the circuit or annealing problem + on the device. If the device is a classical simulator then this implies sampling + the state N times, where N = `shots`. If not set, will default to 1_000. backend_parameters (Dict[str, Any]): Additional parameters to pass to the device. For example, for D-Wave: >>> backend_parameters = {"dWaveParameters": {"postprocess": "OPTIMIZATION"}} @@ -80,7 +83,11 @@ def create( "s3_destination_folder must be of size 2 with a 'bucket' and 'key' respectively." ) - create_task_kwargs = _create_common_params(device_arn, s3_destination_folder, shots) + create_task_kwargs = _create_common_params( + device_arn, + s3_destination_folder, + shots if shots is not None else AwsQuantumTask.DEFAULT_SHOTS, + ) return _create_internal( task_specification, aws_session, @@ -95,8 +102,8 @@ def __init__( arn: str, aws_session: AwsSession, results_formatter: Callable[[str], Any], - poll_timeout_seconds: int = 120, - poll_interval_seconds: int = 0.25, + poll_timeout_seconds: int = DEFAULT_RESULTS_POLL_TIMEOUT, + poll_interval_seconds: int = DEFAULT_RESULTS_POLL_INTERVAL, ): """ Args: @@ -277,7 +284,9 @@ def _( ) task_arn = aws_session.create_quantum_task(**create_task_kwargs) - return AwsQuantumTask(task_arn, aws_session, GateModelQuantumTaskResult.from_string) + return AwsQuantumTask( + task_arn, aws_session, GateModelQuantumTaskResult.from_string, *args, **kwargs + ) @_create_internal.register @@ -298,7 +307,9 @@ def _( ) task_arn = aws_session.create_quantum_task(**create_task_kwargs) - return AwsQuantumTask(task_arn, aws_session, AnnealingQuantumTaskResult.from_string) + return AwsQuantumTask( + task_arn, aws_session, AnnealingQuantumTaskResult.from_string, *args, **kwargs + ) def _create_common_params( diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 9d9c4e51..b672f485 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -37,7 +37,12 @@ def __init__(self, name: str, status: str, status_reason: str): @abstractmethod def run( - self, task_specification: Union[Circuit, Problem], location, shots: Optional[int] + self, + task_specification: Union[Circuit, Problem], + location, + shots: Optional[int], + *args, + **kwargs ) -> QuantumTask: """ Run a quantum task specification (circuit or annealing program) on this quantum device. From 346b98762b1cb5c364b035d2c91e08937af1bfce Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Thu, 20 Feb 2020 16:12:45 -0800 Subject: [PATCH 0037/1165] Update README.md added a note about how to remain anonymous when interacting with this repository --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 8e29c098..9423fa18 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ 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. +**Important** + +If you **Star** or **Watch** this repository, other users that have access to this repository are able to see your user name in the list of watchers. If you want to remain anonymous, you should not Watch or Star this repository, nor post any comments or submit a pull request. + ## Prerequisites Before you begin working with the Amazon Braket SDK, make sure that you've installed or configured the following prerequisites. From 6ec177e8b9c5c310cb72390acf0359e3c0e90c84 Mon Sep 17 00:00:00 2001 From: T-Bone Date: Fri, 21 Feb 2020 15:17:00 -0800 Subject: [PATCH 0038/1165] A few bug fixes and added bell example (#32) * A few bug fixes and added bell example * Update bell.py Replaced string QS1 ARN with AwsQuantumSimulatorArns.QS1 in bell pair example. --- .gitignore | 1 + examples/bell.py | 12 ++++++++++++ setup.py | 2 +- src/braket/aws/aws_quantum_task.py | 7 ++++++- src/braket/circuits/circuit.py | 2 +- 5 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 examples/bell.py diff --git a/.gitignore b/.gitignore index 889115be..4852cb8f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ __pycache__/ /.cache /.pytest_cache /.mypy_cache +/pip-wheel-metadata /doc/_apidoc/ /build diff --git a/examples/bell.py b/examples/bell.py new file mode 100644 index 00000000..c18dcac1 --- /dev/null +++ b/examples/bell.py @@ -0,0 +1,12 @@ +import boto3 +from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns +from braket.circuits import Circuit + +aws_account_id = boto3.client("sts").get_caller_identity()["Account"] + +device = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS1) +s3_folder = (f"braket-output-{aws_account_id}", "folder-name") + +# https://wikipedia.org/wiki/Bell_state +bell = Circuit().h(0).cnot(0, 1) +print(device.run(bell, s3_folder).result().measurement_counts) diff --git a/setup.py b/setup.py index 2da31d90..0982d067 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="braket-sdk", - version="0.3.0", + version="0.3.1", license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 60dade82..3bd28ed2 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -273,7 +273,12 @@ def _create_internal( @_create_internal.register def _( - circuit: Circuit, aws_session: AwsSession, create_task_kwargs: Dict[str, Any], *args, **kwargs, + circuit: Circuit, + aws_session: AwsSession, + create_task_kwargs: Dict[str, Any], + backend_parameters: Dict[str, Any], + *args, + **kwargs, ) -> AwsQuantumTask: create_task_kwargs.update( { diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index f554f1f6..ae52a1b3 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -48,7 +48,7 @@ def register_subroutine(cls, func: SubroutineCallable) -> None: ... circ = Circuit() ... for qubit in target: ... circ += Instruction(Gate.H, qubit) - ... return circ + ... return circ ... >>> Circuit.register_subroutine(h_on_all) >>> circ = Circuit().h_on_all(range(2)) From 8262483ec92352c82d4879398873c0be7acfcb13 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Mon, 24 Feb 2020 10:17:51 -0800 Subject: [PATCH 0039/1165] Add Unitary Gate (#31) * Add Unitary Gate * Formatting changes * Display name changes * Change error message * Add additional tests * Add docs and tests * Remove nesting for test kwargs --- src/braket/circuits/gates.py | 79 +++++++ test/unit_tests/braket/circuits/test_gates.py | 210 +++++++++++++----- 2 files changed, 233 insertions(+), 56 deletions(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 062ebdcf..41ec6c77 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -1254,3 +1254,82 @@ def cswap(control: QubitInput, targets: QubitSet) -> Instruction: Gate.register_gate(CSwap) + + +class Unitary(Gate): + """Arbitrary unitary gate + + Args: + matrix (numpy.ndarray): Unitary matrix which defines the gate. + display_name (str): Name to be used for an instance of this unitary gate + for circuit diagrams. Defaults to `U`. + + Raises: + ValueError: If `matrix` is not a two-dimensional square matrix, + or has a dimension length which is not a positive exponent of 2, + or is non-unitary. + """ + + def __init__(self, matrix: np.ndarray, display_name: str = "U"): + if len(matrix.shape) != 2 or matrix.shape[0] != matrix.shape[1]: + raise ValueError(f"{matrix} is not a two-dimensional square matrix") + + self._matrix = np.array(matrix, dtype=complex) + qubit_count = int(np.log2(self._matrix.shape[0])) + if 2 ** qubit_count != self._matrix.shape[0] or qubit_count < 1: + raise ValueError( + f"`matrix` dimension {self._matrix.shape[0]} is not a positive exponent of 2" + ) + + if not Unitary._is_unitary(self._matrix): + raise ValueError(f"{self._matrix} is not unitary") + + super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) + + def to_matrix(self): + return np.array(self._matrix) + + def to_ir(self, target: QubitSet): + return ir.Unitary( + targets=[qubit for qubit in target], + matrix=Unitary._transform_matrix_to_ir(self._matrix), + ) + + @staticmethod + def _is_unitary(matrix: np.ndarray): + return np.allclose(np.eye(len(matrix)), matrix.dot(matrix.T.conj())) + + @staticmethod + def _transform_matrix_to_ir(matrix: np.ndarray): + return [[[element.real, element.imag] for element in row] for row in matrix.tolist()] + + @staticmethod + @circuit.subroutine(register=True) + def unitary(targets: QubitSet, matrix: np.ndarray, display_name: str = "U") -> Instruction: + """Registers this function into the circuit class. + + Args: + targets (QubitSet): Target qubits. + matrix (numpy.ndarray): Unitary matrix which defines the gate. Matrix should be + compatible with the supplied targets, with 2 ** len(targets) == matrix.shape[0]. + display_name (str): Name to be used for an instance of this unitary gate + for circuit diagrams. Defaults to `U`. + + Returns: + Instruction: Unitary instruction. + + Raises: + ValueError: If `matrix` is not a two-dimensional square matrix, + or has a dimension length which is not compatible with the `targets`, + or is non-unitary, + + Examples: + >>> circ = Circuit().unitary(matrix=np.array([[0, 1],[1, 0]]), targets=[0]) + """ + if 2 ** len(targets) != matrix.shape[0]: + raise ValueError("Dimensions of the supplied unitary are incompatible with the targets") + + return Instruction(Gate.Unitary(matrix, display_name), target=targets) + + +Gate.register_gate(Unitary) diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index f2f6501a..659d7029 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -19,74 +19,147 @@ Angle, DoubleControl, DoubleTarget, + MultiTarget, SingleControl, SingleTarget, + TwoDimensionalMatrix, ) testdata = [ - (Gate.H, "h", ir.H, [SingleTarget]), - (Gate.I, "i", ir.I, [SingleTarget]), - (Gate.X, "x", ir.X, [SingleTarget]), - (Gate.Y, "y", ir.Y, [SingleTarget]), - (Gate.Z, "z", ir.Z, [SingleTarget]), - (Gate.S, "s", ir.S, [SingleTarget]), - (Gate.Si, "si", ir.Si, [SingleTarget]), - (Gate.T, "t", ir.T, [SingleTarget]), - (Gate.Ti, "ti", ir.Ti, [SingleTarget]), - (Gate.V, "v", ir.V, [SingleTarget]), - (Gate.Vi, "vi", ir.Vi, [SingleTarget]), - (Gate.Rx, "rx", ir.Rx, [SingleTarget, Angle]), - (Gate.Ry, "ry", ir.Ry, [SingleTarget, Angle]), - (Gate.Rz, "rz", ir.Rz, [SingleTarget, Angle]), - (Gate.CNot, "cnot", ir.CNot, [SingleTarget, SingleControl]), - (Gate.CCNot, "ccnot", ir.CCNot, [SingleTarget, DoubleControl]), - (Gate.Swap, "swap", ir.Swap, [DoubleTarget]), - (Gate.CSwap, "cswap", ir.CSwap, [SingleControl, DoubleTarget]), - (Gate.ISwap, "iswap", ir.ISwap, [DoubleTarget]), - (Gate.PSwap, "pswap", ir.PSwap, [DoubleTarget, Angle]), - (Gate.XY, "xy", ir.XY, [DoubleTarget, Angle]), - (Gate.PhaseShift, "phaseshift", ir.PhaseShift, [SingleTarget, Angle]), - (Gate.CPhaseShift, "cphaseshift", ir.CPhaseShift, [SingleControl, SingleTarget, Angle]), - (Gate.CPhaseShift00, "cphaseshift00", ir.CPhaseShift00, [SingleControl, SingleTarget, Angle]), - (Gate.CPhaseShift01, "cphaseshift01", ir.CPhaseShift01, [SingleControl, SingleTarget, Angle]), - (Gate.CPhaseShift10, "cphaseshift10", ir.CPhaseShift10, [SingleControl, SingleTarget, Angle]), - (Gate.CY, "cy", ir.CY, [SingleTarget, SingleControl]), - (Gate.CZ, "cz", ir.CZ, [SingleTarget, SingleControl]), - (Gate.XX, "xx", ir.XX, [DoubleTarget, Angle]), - (Gate.YY, "yy", ir.YY, [DoubleTarget, Angle]), - (Gate.ZZ, "zz", ir.ZZ, [DoubleTarget, Angle]), + (Gate.H, "h", ir.H, [SingleTarget], {}), + (Gate.I, "i", ir.I, [SingleTarget], {}), + (Gate.X, "x", ir.X, [SingleTarget], {}), + (Gate.Y, "y", ir.Y, [SingleTarget], {}), + (Gate.Z, "z", ir.Z, [SingleTarget], {}), + (Gate.S, "s", ir.S, [SingleTarget], {}), + (Gate.Si, "si", ir.Si, [SingleTarget], {}), + (Gate.T, "t", ir.T, [SingleTarget], {}), + (Gate.Ti, "ti", ir.Ti, [SingleTarget], {}), + (Gate.V, "v", ir.V, [SingleTarget], {}), + (Gate.Vi, "vi", ir.Vi, [SingleTarget], {}), + (Gate.Rx, "rx", ir.Rx, [SingleTarget, Angle], {}), + (Gate.Ry, "ry", ir.Ry, [SingleTarget, Angle], {}), + (Gate.Rz, "rz", ir.Rz, [SingleTarget, Angle], {}), + (Gate.CNot, "cnot", ir.CNot, [SingleTarget, SingleControl], {}), + (Gate.CCNot, "ccnot", ir.CCNot, [SingleTarget, DoubleControl], {}), + (Gate.Swap, "swap", ir.Swap, [DoubleTarget], {}), + (Gate.CSwap, "cswap", ir.CSwap, [SingleControl, DoubleTarget], {}), + (Gate.ISwap, "iswap", ir.ISwap, [DoubleTarget], {}), + (Gate.PSwap, "pswap", ir.PSwap, [DoubleTarget, Angle], {}), + (Gate.XY, "xy", ir.XY, [DoubleTarget, Angle], {}), + (Gate.PhaseShift, "phaseshift", ir.PhaseShift, [SingleTarget, Angle], {}), + (Gate.CPhaseShift, "cphaseshift", ir.CPhaseShift, [SingleControl, SingleTarget, Angle], {}), + ( + Gate.CPhaseShift00, + "cphaseshift00", + ir.CPhaseShift00, + [SingleControl, SingleTarget, Angle], + {}, + ), + ( + Gate.CPhaseShift01, + "cphaseshift01", + ir.CPhaseShift01, + [SingleControl, SingleTarget, Angle], + {}, + ), + ( + Gate.CPhaseShift10, + "cphaseshift10", + ir.CPhaseShift10, + [SingleControl, SingleTarget, Angle], + {}, + ), + (Gate.CY, "cy", ir.CY, [SingleTarget, SingleControl], {}), + (Gate.CZ, "cz", ir.CZ, [SingleTarget, SingleControl], {}), + (Gate.XX, "xx", ir.XX, [DoubleTarget, Angle], {}), + (Gate.YY, "yy", ir.YY, [DoubleTarget, Angle], {}), + (Gate.ZZ, "zz", ir.ZZ, [DoubleTarget, Angle], {}), + ( + Gate.Unitary, + "unitary", + ir.Unitary, + [TwoDimensionalMatrix, MultiTarget], + {"input_type": complex}, + ), + ( + Gate.Unitary, + "unitary", + ir.Unitary, + [TwoDimensionalMatrix, MultiTarget], + {"input_type": float}, + ), + ( + Gate.Unitary, + "unitary", + ir.Unitary, + [TwoDimensionalMatrix, MultiTarget], + {"input_type": int}, + ), ] -def single_target_valid_input(): +invalid_unitary_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]])), + (np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])), + (np.array([[0, 1], [1, 1]])), + (np.array([[0, 1], ["a", 0]])), +] + + +def single_target_valid_input(**kwargs): return {"target": 2} -def double_target_valid_input(): +def double_target_valid_input(**kwargs): return {"targets": [2, 3]} -def angle_valid_input(): +def angle_valid_input(**kwargs): return {"angle": 0.123} -def single_control_valid_input(): +def single_control_valid_input(**kwargs): return {"control": 0} -def double_control_valid_input(): +def double_control_valid_input(**kwargs): return {"controls": [0, 1]} +def multi_target_valid_input(**kwargs): + return {"targets": [5]} + + +def two_dimensional_matrix_valid_ir_input(**kwargs): + return {"matrix": [[[0, 0], [1, 0]], [[1, 0], [0, 0]]]} + + +def two_dimensional_matrix_valid_input(**kwargs): + input_type = kwargs.get("input_type") + return {"matrix": np.array([[input_type(0), input_type(1)], [input_type(1), input_type(0)]])} + + valid_ir_switcher = { "SingleTarget": single_target_valid_input, "DoubleTarget": double_target_valid_input, "Angle": angle_valid_input, "SingleControl": single_control_valid_input, "DoubleControl": double_control_valid_input, + "MultiTarget": multi_target_valid_input, + "TwoDimensionalMatrix": two_dimensional_matrix_valid_ir_input, } +valid_subroutine_switcher = dict( + valid_ir_switcher, **{"TwoDimensionalMatrix": two_dimensional_matrix_valid_input,} +) + + def create_valid_ir_input(irsubclasses): input = {} for subclass in irsubclasses: @@ -94,6 +167,15 @@ def create_valid_ir_input(irsubclasses): 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) + ) + return input + + def create_valid_target_input(irsubclasses): input = {} qubit_set = [] @@ -103,11 +185,13 @@ def create_valid_target_input(irsubclasses): qubit_set.extend(list(single_target_valid_input().values())) elif subclass == DoubleTarget: qubit_set.extend(list(double_target_valid_input().values())) + elif subclass == MultiTarget: + qubit_set.extend(list(multi_target_valid_input().values())) elif subclass == SingleControl: qubit_set = list(single_control_valid_input().values()) + qubit_set elif subclass == DoubleControl: qubit_set = list(double_control_valid_input().values()) + qubit_set - elif subclass == Angle: + elif subclass == Angle or subclass == TwoDimensionalMatrix: pass else: raise ValueError("Invalid subclass") @@ -115,16 +199,18 @@ def create_valid_target_input(irsubclasses): return input -def create_valid_gate_class_input(irsubclasses): +def create_valid_gate_class_input(irsubclasses, **kwargs): input = {} if Angle in irsubclasses: input.update(angle_valid_input()) + if TwoDimensionalMatrix in irsubclasses: + input.update(two_dimensional_matrix_valid_input(**kwargs)) return input -def create_valid_instruction_input(testclass, irsubclasses): +def create_valid_instruction_input(testclass, irsubclasses, **kwargs): input = create_valid_target_input(irsubclasses) - input["operator"] = testclass(**create_valid_gate_class_input(irsubclasses)) + input["operator"] = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) return input @@ -139,36 +225,38 @@ def calculate_qubit_count(irsubclasses): qubit_count += 1 elif subclass == DoubleControl: qubit_count += 2 - elif subclass == Angle: + elif subclass == MultiTarget: + qubit_count += 3 + elif subclass == Angle or subclass == TwoDimensionalMatrix: pass else: raise ValueError("Invalid subclass") return qubit_count -@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses", testdata) -def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses): +@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) +def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs): expected = irclass(**create_valid_ir_input(irsubclasses)) - actual = testclass(**create_valid_gate_class_input(irsubclasses)).to_ir( + actual = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)).to_ir( **create_valid_target_input(irsubclasses) ) assert actual == expected -@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses", testdata) -def test_ir_instruction_level(testclass, subroutine_name, irclass, irsubclasses): +@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) +def test_ir_instruction_level(testclass, subroutine_name, irclass, irsubclasses, kwargs): expected = irclass(**create_valid_ir_input(irsubclasses)) - instruction = Instruction(**create_valid_instruction_input(testclass, irsubclasses)) + instruction = Instruction(**create_valid_instruction_input(testclass, irsubclasses, **kwargs)) actual = instruction.to_ir() assert actual == expected -@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses", testdata) -def test_gate_subroutine(testclass, subroutine_name, irclass, irsubclasses): +@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) +def test_gate_subroutine(testclass, subroutine_name, irclass, irsubclasses, kwargs): qubit_count = calculate_qubit_count(irsubclasses) subroutine = getattr(Circuit(), subroutine_name) - assert subroutine(**create_valid_ir_input(irsubclasses)) == Circuit( - Instruction(**create_valid_instruction_input(testclass, irsubclasses)) + assert subroutine(**create_valid_subroutine_input(irsubclasses, **kwargs)) == Circuit( + Instruction(**create_valid_instruction_input(testclass, irsubclasses, **kwargs)) ) if qubit_count == 1: multi_targets = [0, 1, 2] @@ -176,7 +264,8 @@ def test_gate_subroutine(testclass, subroutine_name, irclass, irsubclasses): for target in multi_targets: instruction_list.append( Instruction( - operator=testclass(**create_valid_gate_class_input(irsubclasses)), target=target + operator=testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)), + target=target, ) ) subroutine = getattr(Circuit(), subroutine_name) @@ -186,9 +275,18 @@ def test_gate_subroutine(testclass, subroutine_name, irclass, irsubclasses): assert subroutine(**subroutine_input) == Circuit(instruction_list) -@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses", testdata) -def test_gate_to_matrix(testclass, subroutine_name, irclass, irsubclasses): - gate1 = testclass(**create_valid_gate_class_input(irsubclasses)) - gate2 = testclass(**create_valid_gate_class_input(irsubclasses)) +@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) +def test_gate_to_matrix(testclass, subroutine_name, irclass, irsubclasses, kwargs): + gate1 = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) + gate2 = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) assert isinstance(gate1.to_matrix(), np.ndarray) assert gate1.matrix_equivalence(gate2) + + +# Additional Unitary gate tests + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("matrix", invalid_unitary_matrices) +def test_unitary_invalid_matrix(matrix): + Gate.Unitary(matrix=matrix) From f020388159b6c641a3f7190a186e6b20d2d1960c Mon Sep 17 00:00:00 2001 From: T-Bone Date: Tue, 25 Feb 2020 17:40:50 -0800 Subject: [PATCH 0040/1165] Move isort before black in tox as it reverts blacks changes; ignore E231 as it is not PEP8 (#33) --- setup.cfg | 1 + src/braket/circuits/__init__.py | 1 + tox.ini | 5 +++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 8a08eedc..8d5e6ff2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,6 +15,7 @@ include_trailing_comma = true [flake8] ignore = E203, # not pep8, black adds whitespace before ':' + E231, # not pep8, https://www.python.org/dev/peps/pep-0008/#pet-peeves W503, # not pep8, black adds line break before binary operator max_line_length = 100 max-complexity = 10 diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py index 35f41cdc..d3785ff8 100644 --- a/src/braket/circuits/__init__.py +++ b/src/braket/circuits/__init__.py @@ -13,6 +13,7 @@ # Execute initialization code in circuit module import braket.circuits.circuit as circuit # noqa: F401 + # Execute initialization code in gates module import braket.circuits.gates as gates # noqa: F401 from braket.circuits.angled_gate import AngledGate # noqa: F401 diff --git a/tox.ini b/tox.ini index daec479a..414dc56a 100644 --- a/tox.ini +++ b/tox.ini @@ -24,12 +24,13 @@ extras = test basepython = python3.7 skip_install = true deps = - {[testenv:black]deps} {[testenv:isort]deps} + {[testenv:black]deps} {[testenv:flake8]deps} commands = - {[testenv:black]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:flake8] From 158a088b992c134f82bf978b5a1a938914d12855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=A5=A9=20Flora?= Date: Wed, 26 Feb 2020 11:14:03 -0800 Subject: [PATCH 0041/1165] Add shots to example. https://issues.amazon.com/issues/AMAZON-QX-77 --- src/braket/aws/aws_quantum_simulator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/braket/aws/aws_quantum_simulator.py b/src/braket/aws/aws_quantum_simulator.py index 3d00c49a..9c5e97df 100644 --- a/src/braket/aws/aws_quantum_simulator.py +++ b/src/braket/aws/aws_quantum_simulator.py @@ -66,11 +66,11 @@ def run( Examples: >>> circuit = Circuit().h(0).cnot(0, 1) >>> device = AwsQuantumSimulator("arn:aws:aqx:::quantum-simulator:aqx:qs1") - >>> device.run(circuit, ("bucket-foo", "key-bar")) + >>> device.run(circuit, ("bucket", "key"), shots=1000) >>> circuit = Circuit().h(0).cnot(0, 1) >>> device = AwsQuantumSimulator("arn:aws:aqx:::quantum-simulator:aqx:qs1") - >>> device.run(circuit=circuit, s3_destination_folder=("bucket-foo", "key-bar")) + >>> device.run(circuit=circuit, s3_destination_folder=("bucket", "key"), shots=1000) See Also: `braket.aws.aws_quantum_task.AwsQuantumTask.create()` From 5c01acd9beec8998af3dae10648db4291b13427a Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Wed, 26 Feb 2020 13:10:24 -0800 Subject: [PATCH 0042/1165] Update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9423fa18..646c937f 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ **This prerelease documentation is confidential and is provided under the terms of your nondisclosure agreement with Amazon Web Services (AWS) or other agreement governing your receipt of AWS confidential information.** -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. +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. This document describes how to configure your environment to use the Amazon Braket (Private Beta) locally on your computer. It does not include information about how to use Amazon Braket (Private Beta) in the AWS console. + +Tp provide feedback or request support, please contact the Amazon Braket team at [amazon-braket-preview-support@amazon.com](mailto:amazon-braket-preview-support@amazon.com?subject=Add%20a%20brief%20description%20of%20the%20issue). **Important** -If you **Star** or **Watch** this repository, other users that have access to this repository are able to see your user name in the list of watchers. If you want to remain anonymous, you should not Watch or Star this repository, nor post any comments or submit a pull request. +If you **Star**, **Watch**, or submit a pull request for this repository, other users that have access to this repository are able to see your user name in the list of watchers. If you want to remain anonymous, you should not Watch or Star this repository, nor post any comments or submit a pull request. ## Prerequisites Before you begin working with the Amazon Braket SDK, make sure that you've installed or configured the following prerequisites. @@ -55,7 +57,7 @@ cd .. Install Git from https://git-scm.com/downloads. Installation instructions are provided on the download page. ### Access to Amazon Braket (Private Beta) -You can configure your environment for the Amazon Braket Python SDK, but you need to be granted permission to access Amazon Braket (Private Beta). Before you can execute code against Amazon Braket. If you’ve not received notification that you have been added to the beta, please contact the Braket team for assistance. +You can configure your environment for the Amazon Braket Python SDK, but you need to be granted permission to access Amazon Braket (Private Beta) before you can start making code requests to Amazon Braket. If you’ve not received notification that you have been added to the beta, please contact the Amazon Braket team for assistance using this email address: [amazon-braket-preview-support@amazon.com](mailto:amazon-braket-preview-support@amazon.com?subject=Add%20a%20brief%20description%20of%20the%20issue). ### IAM user or role with required permissions To perform the steps in this document and to interact with Amazon Braket, use an account with administrator privileges, such as one with the AdministratorAccess policy applied. To learn more about IAM user, roles, and policies, see [Adding and Removing IAM Identity Permissions](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage-attach-detach.html). From dbee8271a9ff0605847270c0f510956f853e3159 Mon Sep 17 00:00:00 2001 From: T-Bone Date: Wed, 26 Feb 2020 16:28:27 -0800 Subject: [PATCH 0043/1165] Fix infinite recursion for Qubit(str) AMAZON-QX-94 (#36) --- src/braket/circuits/qubit.py | 4 ++-- src/braket/circuits/qubit_set.py | 6 +++--- test/unit_tests/braket/circuits/test_qubit_set.py | 5 +++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/braket/circuits/qubit.py b/src/braket/circuits/qubit.py index 99fca444..b7455243 100644 --- a/src/braket/circuits/qubit.py +++ b/src/braket/circuits/qubit.py @@ -11,9 +11,9 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import TypeVar +from typing import Union -QubitInput = TypeVar("QubitInput", "Qubit", int) +QubitInput = Union["Qubit", int] class Qubit(int): diff --git a/src/braket/circuits/qubit_set.py b/src/braket/circuits/qubit_set.py index d8087ccb..fa5a32a0 100644 --- a/src/braket/circuits/qubit_set.py +++ b/src/braket/circuits/qubit_set.py @@ -11,12 +11,12 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import Dict, Iterable, TypeVar +from typing import Dict, Iterable, Union from boltons.setutils import IndexedSet from braket.circuits.qubit import Qubit, QubitInput -QubitSetInput = TypeVar("QubitSetInput", QubitInput, Iterable[QubitInput]) +QubitSetInput = Union[QubitInput, Iterable[QubitInput]] class QubitSet(IndexedSet): @@ -52,7 +52,7 @@ def __init__(self, qubits: QubitSetInput = []): """ def _flatten(other): - if isinstance(other, Iterable): + if isinstance(other, Iterable) and not isinstance(other, str): for item in other: yield from _flatten(item) else: diff --git a/test/unit_tests/braket/circuits/test_qubit_set.py b/test/unit_tests/braket/circuits/test_qubit_set.py index e1666f1c..44563837 100644 --- a/test/unit_tests/braket/circuits/test_qubit_set.py +++ b/test/unit_tests/braket/circuits/test_qubit_set.py @@ -46,6 +46,11 @@ def test_with_qubit_set(): assert QubitSet([qubits, [2, 3]]) == tuple([Qubit(0), Qubit(1), Qubit(2), Qubit(3)]) +def test_flattening_does_not_recurse_infinitely(): + with pytest.raises(TypeError): # str instead of expected int + QubitSet("kaboom") + + def test_map_creates_new_object(qubits): mapped_qubits = qubits.map({}) assert mapped_qubits == qubits From e758086525a8814da89ca82c3d6f972bef20343f Mon Sep 17 00:00:00 2001 From: T-Bone Date: Thu, 27 Feb 2020 13:58:27 -0800 Subject: [PATCH 0044/1165] Updated examples to instantiate gates instead of singletons AMAZON-QX-92 (#38) --- src/braket/circuits/circuit.py | 14 +++++++------- src/braket/circuits/moments.py | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index ae52a1b3..a55bc6db 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -47,7 +47,7 @@ def register_subroutine(cls, func: SubroutineCallable) -> None: >>> def h_on_all(target): ... circ = Circuit() ... for qubit in target: - ... circ += Instruction(Gate.H, qubit) + ... circ += Instruction(Gate.H(), qubit) ... return circ ... >>> Circuit.register_subroutine(h_on_all) @@ -81,7 +81,7 @@ def __init__(self, addable: AddableTypes = None, *args, **kwargs): TypeError: If `addable` is an unsupported type. Examples: - >>> circ = Circuit([Instruction(Gate.H, 4), Instruction(Gate.CNot, [4, 5])]) + >>> circ = Circuit([Instruction(Gate.H(), 4), Instruction(Gate.CNot(), [4, 5])]) >>> circ = Circuit().h(0).cnot(0, 1) >>> @circuit.subroutine(register=True) @@ -146,22 +146,22 @@ def add_instruction( TypeError: If both `target_mapping` and `target` are supplied. Examples: - >>> instr = Instruction(Gate.CNot, [0, 1]) + >>> instr = Instruction(Gate.CNot(), [0, 1]) >>> circ = Circuit().add_instruction(instr) >>> print(circ.instructions[0]) Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(0), Qubit(1))) - >>> instr = Instruction(Gate.CNot, [0, 1]) + >>> instr = Instruction(Gate.CNot(), [0, 1]) >>> circ = Circuit().add_instruction(instr, target_mapping={0: 10, 1: 11}) >>> print(circ.instructions[0]) Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(10), Qubit(11))) - >>> instr = Instruction(Gate.CNot, [0, 1]) + >>> instr = Instruction(Gate.CNot(), [0, 1]) >>> circ = Circuit().add_instruction(instr, target=[10, 11]) >>> print(circ.instructions[0]) Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(10), Qubit(11))) - >>> instr = Instruction(Gate.H, 0) + >>> instr = Instruction(Gate.H(), 0) >>> circ = Circuit().add_instruction(instr, target=[10, 11]) >>> print(circ.instructions[0]) Instruction('operator': 'H', 'target': QubitSet(Qubit(10),)) @@ -280,7 +280,7 @@ def add(self, addable: AddableTypes, *args, **kwargs) -> "Circuit": `add_instruction()` Examples: - >>> circ = Circuit().add([Instruction(Gate.H, 4), Instruction(Gate.CNot, [4, 5])]) + >>> circ = Circuit().add([Instruction(Gate.H(), 4), Instruction(Gate.CNot(), [4, 5])]) >>> circ = Circuit().h(4).cnot([4, 5]) diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index c553b00f..5e4da779 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -53,8 +53,8 @@ class Moments(Mapping[MomentsKey, Instruction]): Examples: >>> moments = Moments() - >>> moments.add([Instruction(Gate.H, 0), Instruction(Gate.CNot, [0, 1])]) - >>> moments.add([Instruction(Gate.H, 0), Instruction(Gate.H, 1)]) + >>> moments.add([Instruction(Gate.H(), 0), Instruction(Gate.CNot(), [0, 1])]) + >>> moments.add([Instruction(Gate.H(), 0), Instruction(Gate.H(), 1)]) >>> for i, item in enumerate(moments.items()): ... print(f"Item {i}") ... print(f"\\tKey: {item[0]}") From 055ba216dd5553407584b69a104f49de69170a7f Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 27 Feb 2020 15:49:07 -0800 Subject: [PATCH 0045/1165] Add Bell state example (#39) * Added bell example * Changed example in README --- README.md | 21 ++++++++++++++------- examples/bell.py | 12 ++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 examples/bell.py diff --git a/README.md b/README.md index 646c937f..f77f559d 100644 --- a/README.md +++ b/README.md @@ -159,12 +159,16 @@ You can confirm that your environment is correctly configured in either of the f ## Code sample for validating your configuration Use the following code sample to validate your environment configuration. + ```python +import boto3 +from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns from braket.circuits import Circuit -from braket.aws import AwsQuantumSimulator - -device = AwsQuantumSimulator("arn:aws:aqx:::quantum-simulator:aqx:qs1") -s3_folder = ("braket-output-AWS_ACCOUNT_ID", "folder-name") + +aws_account_id = boto3.client("sts").get_caller_identity()["Account"] + +device = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS1) +s3_folder = (f"braket-output-{aws_account_id}", "folder-name") bell = Circuit().h(0).cnot(0, 1) print(device.run(bell, s3_folder).result().measurement_counts) @@ -224,11 +228,14 @@ With Amazon Braket, you can run your quantum circuit on a physical quantum compu The following example executes the same Bell Pair example described to validate your configuration against a Rigetti quantum computer. ```python +import boto3 from braket.circuits import Circuit -from braket.aws import AwsQpu +from braket.aws import AwsQpu, AwsQpuArns + +aws_account_id = boto3.client("sts").get_caller_identity()["Account"] -device = AwsQpu("arn:aws:aqx:::qpu:rigetti") -s3_folder = ("braket-output-AWS_ACCOUNT_ID", "folder-name") +device = AwsQpu(AwsQpuArns.RIGETTI) +s3_folder = (f"braket-output-{aws_account_id}", "folder-name") bell = Circuit().h(0).cnot(0, 1) print(device.run(bell, s3_folder).result().measurement_counts) diff --git a/examples/bell.py b/examples/bell.py new file mode 100644 index 00000000..c18dcac1 --- /dev/null +++ b/examples/bell.py @@ -0,0 +1,12 @@ +import boto3 +from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns +from braket.circuits import Circuit + +aws_account_id = boto3.client("sts").get_caller_identity()["Account"] + +device = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS1) +s3_folder = (f"braket-output-{aws_account_id}", "folder-name") + +# https://wikipedia.org/wiki/Bell_state +bell = Circuit().h(0).cnot(0, 1) +print(device.run(bell, s3_folder).result().measurement_counts) From 49e04a487d3e92c71151aabb85e4224848d6392d Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Thu, 27 Feb 2020 15:51:21 -0800 Subject: [PATCH 0046/1165] Remove dead code (#40) --- src/braket/devices/__init__.py | 1 - src/braket/devices/device_details.py | 28 ---------------------------- test/integ_tests/test_common.py | 2 -- 3 files changed, 31 deletions(-) delete mode 100644 src/braket/devices/device_details.py delete mode 100644 test/integ_tests/test_common.py diff --git a/src/braket/devices/__init__.py b/src/braket/devices/__init__.py index 9ef21e39..b797522f 100644 --- a/src/braket/devices/__init__.py +++ b/src/braket/devices/__init__.py @@ -12,4 +12,3 @@ # language governing permissions and limitations under the License. from braket.devices.device import Device # noqa: F401 -from braket.devices.device_details import DeviceDetails # noqa: F401 diff --git a/src/braket/devices/device_details.py b/src/braket/devices/device_details.py deleted file mode 100644 index 6e21839f..00000000 --- a/src/braket/devices/device_details.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2019-2019 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 dataclasses import dataclass -from uuid import UUID - - -@dataclass -class DeviceDetails: - """ - Details of a quantum device. This class is intended to be initailized by a Device class. - """ - - # TODO: Add fields we need - - # Placeholder fields - id: UUID - qubit_count: int diff --git a/test/integ_tests/test_common.py b/test/integ_tests/test_common.py deleted file mode 100644 index 0b878f56..00000000 --- a/test/integ_tests/test_common.py +++ /dev/null @@ -1,2 +0,0 @@ -TEST_QPU_ARN = "arn:aws:aqx:::qpu:aqx:integ_test_qpu" -TEST_SIMULATOR_ARN = "arn:aws:aqx:::quantum-simulator:aqx:integ_test_simulator" From 0c88004e42c4baa682893133e4bb9e861399de68 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 28 Feb 2020 12:12:22 -0800 Subject: [PATCH 0047/1165] Merging in doc changes (#41) * Update to README (#20) * Update to README Restructure content to move prereqs to a common section. Copy edit pass for style and language. Formatting changes for hierarchy and organization. * Update readme for private beta Multiple changes throughout to clarify steps to configure one's environment for the beta release. * Update to readme Addressed feedback, corrected a couple of format issues, removed step to create a kernel to simplify. * update to readme Added additional content for using a qpu, incorporated feedback. * restoring section on using a virtenv * update to readme (#23) * update to readme reworking steps to resolve some issues on Mac * Update to update installation steps Changed to remove Conda and download repo zip files instead of configure SSH * update to readme Clean up to remove Conda, misc formatting * update to readme Formatting change * Update README.md * Update README.md addressing feedback from Ava * Update README.md added a note about jobs taking up to 24 hours on IonQ * Update README.md modified descriptions of simulators. * Update README.md Removed step to move the bellpair file to the virtual env. * Update README.md * Update README.md * Update README.md * Update to section on creating resources Included a note in the section about creating resources for Amazon Braket to indicate that the template should be used only once per AWS account. * Update README.md * Add note about using stable branch * Update README.md added a note about how to remain anonymous when interacting with this repository * updating strings for documentation * resolving lint errors * Update README.md * all errors resolved, docs build * Add Bell state example (#39) * Added bell example * Changed example in README * Correct typos, outdated docstring Co-authored-by: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Co-authored-by: Ava Wang <57644099+avawang1@users.noreply.github.com> --- README.md | 39 ++++-- doc/index.rst | 2 +- src/braket/aws/aws_qpu.py | 38 +++--- src/braket/aws/aws_quantum_simulator.py | 17 +-- src/braket/aws/aws_quantum_task.py | 114 +++++++++--------- src/braket/aws/aws_session.py | 40 +++--- src/braket/circuits/angled_gate.py | 22 ++-- src/braket/circuits/ascii_circuit_diagram.py | 4 +- src/braket/circuits/circuit.py | 55 ++++----- src/braket/circuits/circuit_diagram.py | 4 +- src/braket/circuits/gate.py | 20 +-- src/braket/circuits/gates.py | 22 ++-- src/braket/circuits/instruction.py | 24 ++-- src/braket/circuits/moments.py | 38 +++--- src/braket/circuits/operator.py | 4 +- src/braket/circuits/qubit.py | 2 +- src/braket/circuits/qubit_set.py | 8 +- src/braket/devices/device.py | 17 +-- src/braket/ipython_utils.py | 2 +- .../tasks/annealing_quantum_task_result.py | 19 ++- .../tasks/gate_model_quantum_task_result.py | 6 +- src/braket/tasks/quantum_task.py | 11 +- 22 files changed, 270 insertions(+), 238 deletions(-) diff --git a/README.md b/README.md index dbb3052d..bf4fc575 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ **This prerelease documentation is confidential and is provided under the terms of your nondisclosure agreement with Amazon Web Services (AWS) or other agreement governing your receipt of AWS confidential information.** -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. +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. This document describes how to configure your environment to use the Amazon Braket (Private Beta) locally on your computer. It does not include information about how to use Amazon Braket (Private Beta) in the AWS console. + +To provide feedback or request support, please contact the Amazon Braket team at [amazon-braket-preview-support@amazon.com](mailto:amazon-braket-preview-support@amazon.com?subject=Add%20a%20brief%20description%20of%20the%20issue). + +**Important** + +If you **Star**, **Watch**, or submit a pull request for this repository, other users that have access to this repository are able to see your user name in the list of watchers. If you want to remain anonymous, you should not Watch or Star this repository, nor post any comments or submit a pull request. ## Prerequisites Before you begin working with the Amazon Braket SDK, make sure that you've installed or configured the following prerequisites. @@ -51,7 +57,7 @@ cd .. Install Git from https://git-scm.com/downloads. Installation instructions are provided on the download page. ### Access to Amazon Braket (Private Beta) -You can configure your environment for the Amazon Braket Python SDK, but you need to be granted permission to access Amazon Braket (Private Beta). Before you can execute code against Amazon Braket. If you’ve not received notification that you have been added to the beta, please contact the Braket team for assistance. +You can configure your environment for the Amazon Braket Python SDK, but you need to be granted permission to access Amazon Braket (Private Beta) before you can start making code requests to Amazon Braket. If you’ve not received notification that you have been added to the beta, please contact the Amazon Braket team for assistance using this email address: [amazon-braket-preview-support@amazon.com](mailto:amazon-braket-preview-support@amazon.com?subject=Add%20a%20brief%20description%20of%20the%20issue). ### IAM user or role with required permissions To perform the steps in this document and to interact with Amazon Braket, use an account with administrator privileges, such as one with the AdministratorAccess policy applied. To learn more about IAM user, roles, and policies, see [Adding and Removing IAM Identity Permissions](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage-attach-detach.html). @@ -76,7 +82,13 @@ export AWS_PROFILE=YOUR_PROFILE_NAME ``` ### Configure your AWS account with the resources necessary for Amazon Braket -Use the following link to an AWS CloudFormation template to automatically create the resources that Amazon Braket uses or interacts with in your account. The template creates the following resources: +Use the following link to an AWS CloudFormation template to automatically create the resources that Amazon Braket uses or interacts with in your account. + +**Important** + +If you are using IAM roles for multiple users in your organization to access the Amazon Braket Private Beta from the same account, only open the template and create the stack once for the account. Each role in the account then has access to the resources Amazon Braket needs in the account. + +The template creates the following resources: - An S3 bucket to store job output. The bucket is named `braket-output-AWSaccountId` where AWSAccountId is your account ID. For example, if your AWS account ID is 123456789012, the bucket is named `braket-output-123456789012`. - IAM roles named AmazonBraketJobExecutionRole, which is used to run jobs, and AQxFullAccess which is used to interact with the AWS resources that Amazon Braket needs. - An IAM policy, AmazonBraketFullAccess, that includes permission to use Amazon Braket actions, as well as the permissions necessary to access the S3 bucket created. If you want to use a role that does not have admin permissions, you can apply the AmazonBraketFullAccess policy to the user or role you are using to grant the permissions required to use Amazon Braket beta. @@ -147,12 +159,16 @@ You can confirm that your environment is correctly configured in either of the f ## Code sample for validating your configuration Use the following code sample to validate your environment configuration. + ```python +import boto3 +from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns from braket.circuits import Circuit -from braket.aws import AwsQuantumSimulator - -device = AwsQuantumSimulator("arn:aws:aqx:::quantum-simulator:aqx:qs1") -s3_folder = ("braket-output-AWS_ACCOUNT_ID", "folder-name") + +aws_account_id = boto3.client("sts").get_caller_identity()["Account"] + +device = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS1) +s3_folder = (f"braket-output-{aws_account_id}", "folder-name") bell = Circuit().h(0).cnot(0, 1) print(device.run(bell, s3_folder).result().measurement_counts) @@ -212,11 +228,14 @@ With Amazon Braket, you can run your quantum circuit on a physical quantum compu The following example executes the same Bell Pair example described to validate your configuration against a Rigetti quantum computer. ```python +import boto3 from braket.circuits import Circuit -from braket.aws import AwsQpu +from braket.aws import AwsQpu, AwsQpuArns + +aws_account_id = boto3.client("sts").get_caller_identity()["Account"] -device = AwsQpu("arn:aws:aqx:::qpu:rigetti") -s3_folder = ("braket-output-AWS_ACCOUNT_ID", "folder-name") +device = AwsQpu(AwsQpuArns.RIGETTI) +s3_folder = (f"braket-output-{aws_account_id}", "folder-name") bell = Circuit().h(0).cnot(0, 1) print(device.run(bell, s3_folder).result().measurement_counts) diff --git a/doc/index.rst b/doc/index.rst index a3cad250..14b5e812 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,5 +1,5 @@ Amazon Braket (Private Beta) Python SDK -======================== +======================================= Amazon Braket Python SDK is an open source library for interacting with quantum devices on Amazon Braket (Private Beta). diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py index 849fb1fe..a965557f 100644 --- a/src/braket/aws/aws_qpu.py +++ b/src/braket/aws/aws_qpu.py @@ -23,8 +23,9 @@ class AwsQpu(Device): """ - Amazon Braket implementation of a QPU. - Use this class to retrieve the latest metadata about the QPU, and run a quantum task on the QPU. + Amazon Braket implementation of a Quantum Processing Unit (QPU). + Use this class to retrieve the latest metadata about the QPU, and to run a quantum task on the + QPU. """ QPU_REGIONS = { @@ -36,18 +37,20 @@ class AwsQpu(Device): def __init__(self, arn: str, aws_session=None): """ Args: - arn (str): QPU ARN, e.g. "arn:aws:aqx:::qpu:ionq" - aws_session (AwsSession, optional) aws_session: AWS session object. Default = None. + arn (str): The ARN of the QPU, for example, "arn:aws:aqx:::qpu:ionq" + aws_session (AwsSession, optional) aws_session: An AWS session object. Default = None. Raises: - ValueError: If unknown `arn` is supplied. + ValueError: If an unknown `arn` is specified. Note: - QPUs are physically located in specific AWS regions. If the supplied `aws_session` - is connected to a region that the QPU is not in then a cloned `aws_session` - will be created for the QPU region. + QPUs are physically located in specific AWS Regions. In some cases, the current + `aws_session` connects to a Region other than the Region in which the QPU is + physically located. When this occurs, a cloned `aws_session` is created for the Region + the QPU is located in. - See `braket.aws.aws_qpu.AwsQpu.QPU_REGIONS` for the regions the QPUs are located in. + See `braket.aws.aws_qpu.AwsQpu.QPU_REGIONS` for the AWS Regions the QPUs are located + in. """ super().__init__(name=None, status=None, status_reason=None) self._arn = arn @@ -64,7 +67,8 @@ def run( **aws_quantum_task_kwargs, ) -> AwsQuantumTask: """ - Run a quantum task specification (circuit or annealing problem) on this AWS quantum device. + Run a quantum task specification on this quantum device. A task can be a circuit or an + annealing problem. Currently, only circuits are supported in the Private Beta. Args: task_specification (Union[Circuit, Problem]): Specification of task @@ -77,7 +81,7 @@ def run( `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. Returns: - AwsQuantumTask: AwsQuantumTask that tracks the execution on the device. + AwsQuantumTask: An AwsQuantumTask that tracks the execution on the device. Examples: >>> circuit = Circuit().h(0).cnot(0, 1) @@ -115,7 +119,7 @@ def run( def refresh_metadata(self) -> None: """ - Refresh AwsQpu object with most up to date QPU metadata. + Refresh the `AwsQpu` object with the most recent QPU metadata. """ qpu_metadata = self._aws_session.get_qpu_metadata(self._arn) self._name = qpu_metadata.get("name") @@ -130,19 +134,21 @@ def refresh_metadata(self) -> None: @property def arn(self) -> str: - """str: Return arn of QPU.""" + """str: Return the ARN of the QPU""" return self._arn @property # TODO: Add a link to the boto3 docs def properties(self) -> Dict[str, Any]: - """Dict[str, Any]: Return the qpu specific properties""" + """Dict[str, Any]: Return the QPU properties""" return self._properties def _aws_session_for_qpu(self, qpu_arn: str, aws_session: AwsSession) -> AwsSession: """ - Get an AwsSession for the QPU ARN. QPUs are only available in certain regions so any - supplied AwsSession in a region the QPU doesn't support will need to be adjusted. + Get an AwsSession for the QPU ARN. QPUs are physically located in specific AWS Regions. + The AWS sessions should connect to the Region that the QPU is located in. + + See `braket.aws.aws_qpu.AwsQpu.QPU_REGIONS` for the AWS Regions the QPUs are located in. """ qpu_regions = AwsQpu.QPU_REGIONS.get(qpu_arn, []) diff --git a/src/braket/aws/aws_quantum_simulator.py b/src/braket/aws/aws_quantum_simulator.py index 9c5e97df..9fcbaf6d 100644 --- a/src/braket/aws/aws_quantum_simulator.py +++ b/src/braket/aws/aws_quantum_simulator.py @@ -24,14 +24,15 @@ class AwsQuantumSimulator(Device): """ Amazon Braket implementation of a quantum simulator. Use this class to retrieve the latest metadata about the simulator, - and run a circuit on the simulator. + and to run a task on the simulator. """ def __init__(self, arn: str, aws_session=None): """ Args: - arn (str): Simulator type ARN e.g. "arn:aws:aqx:::quantum-simulator:aqx:qs1". - aws_session (AwsSession, optional) aws_session: AWS session object. Default = None. + arn (str): The ARN of the simulator, for example, + "arn:aws:aqx:::quantum-simulator:aqx:qs1". + aws_session (AwsSession, optional) aws_session: An AWS session object. Default = None. """ super().__init__(name=None, status=None, status_reason=None) self._arn = arn @@ -48,7 +49,7 @@ def run( **aws_quantum_task_kwargs, ) -> AwsQuantumTask: """ - Run a circuit on this AWS quantum device. + Run a task on the simulator device. Args: task_specification (Union[Circuit, Problem]): Specification of task @@ -61,7 +62,7 @@ def run( `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. Returns: - AwsQuantumTask: AwsQuantumTask that is tracking the circuit execution on the device. + AwsQuantumTask: An AwsQuantumTask that tracks the task execution on the device. Examples: >>> circuit = Circuit().h(0).cnot(0, 1) @@ -86,7 +87,7 @@ def run( ) def refresh_metadata(self) -> None: - """Refresh AwsQuantumSimulator object with most up to date simulator metadata.""" + """Refresh the AwsQuantumSimulator object with the most recent simulator metadata.""" simulator_metadata = self._aws_session.get_simulator_metadata(self._arn) self._name = simulator_metadata.get("name") self._status = simulator_metadata.get("status") @@ -97,13 +98,13 @@ def refresh_metadata(self) -> None: @property def arn(self) -> str: - """str: Return arn of simulator.""" + """str: Returns the ARN of the simulator.""" return self._arn @property # TODO: Add a link to the boto3 docs def properties(self) -> Dict[str, Any]: - """Dict[str, Any]: Return the simulator specific properties""" + """Dict[str, Any]: Return the simulator properties""" return self._properties def __repr__(self): diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 3bd28ed2..ec154986 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -26,7 +26,8 @@ # TODO: add AnnealingQuantumTaskResult class AwsQuantumTask(QuantumTask): - """Amazon Braket implementation of a quantum task.""" + """Amazon Braket implementation of a quantum task. A task can be a circuit or an annealing + problem. Currently, only circuits are supported in the Private Beta.""" # TODO: Add API documentation that defines these states. Make it clear this is the contract. TERMINAL_STATES = {"COMPLETED", "FAILED", "CANCELLED"} @@ -50,28 +51,32 @@ def create( *args, **kwargs, ) -> AwsQuantumTask: - """ - AwsQuantumTask factory method that serializes a quantum task specification + """AwsQuantumTask factory method that serializes a quantum task specification (either a quantum circuit or annealing problem), submits it to Amazon Braket, and returns back an AwsQuantumTask tracking the execution. Args: - aws_session (AwsSession): AwsSession to call AWS with. - device_arn (str): AWS quantum device arn. - task_specification (Union[Circuit, Problem]): Specification of task - (circuit or annealing problem) to run on device. - s3_destination_folder (AwsSession.S3DestinationFolder): NamedTuple with bucket (index 0) - and key (index 1) that is the results destination folder in S3. - shots (Optional[int]): The number of times to run the circuit or annealing problem - on the device. If the device is a classical simulator then this implies sampling - the state N times, where N = `shots`. If not set, will default to 1_000. - backend_parameters (Dict[str, Any]): Additional parameters to pass to the device. - For example, for D-Wave: - >>> backend_parameters = {"dWaveParameters": {"postprocess": "OPTIMIZATION"}} + aws_session (AwsSession): AwsSession to connect to AWS with. + + device_arn (str): The ARN of the quantum device. + + task_specification (Union[Circuit, Problem]): The specification of the task + to run on device. + + 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 task results in. + + shots (int): The number of times to run the task on the device. If the device is a + simulator, this implies the state is sampled N times, where N = `shots`. Default + shots = 1_000. + + backend_parameters (Dict[str, Any]): Additional parameters to send to the device. + For example, for D-Wave: + >>> backend_parameters = {"dWaveParameters": {"postprocess": "OPTIMIZATION"}} Returns: AwsQuantumTask: AwsQuantumTask tracking the task execution on the device. - Note: The following arguments are typically defined via clients of Device. - `task_specification` @@ -107,13 +112,15 @@ def __init__( ): """ Args: - arn (str): The AWS quantum task ARN. - aws_session (AwsSession): The AwsSession for communicating with AWS. + arn (str): The ARN of the task. + aws_session (AwsSession): The `AwsSession` for connecting to AWS services. results_formatter (Callable[[str], Any]): A function that deserializes a string - into a results structure (such as GateModelQuantumTaskResult) - poll_timeout_seconds (int): The polling timeout for result(), default 120 seconds. - poll_interval_seconds (int): The polling interval for result(), default 0.25 seconds. + into a results structure (such as `GateModelQuantumTaskResult`) + poll_timeout_seconds (int): The polling timeout for result(), default is 120 seconds. + poll_interval_seconds (int): The polling interval for result(), default is 0.25 + seconds. """ + self._arn: str = arn self._aws_session: AwsSession = aws_session self._results_formatter = results_formatter @@ -126,7 +133,7 @@ def __init__( @property def id(self) -> str: - """str: The AWS quantum task ARN.""" + """str: The ARN of the quantum task.""" return self._arn def cancel(self) -> None: @@ -137,16 +144,15 @@ def cancel(self) -> None: def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: """ Get task metadata defined in Amazon Braket. - Args: - use_cached_value (bool, optional): If true returns the last value retrieved from - Amazon Braket GetQuantumTask API else the API is called and the cache is updated. - Default = False. - + use_cached_value (bool, optional): 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: - Dict[str, Any]: The Amazon Braket GetQuantumTask API response. TODO: INSERT BOTO3 LINK. - If `use_cached_value` is True then Amazon Braket is not called and the last value - retrieved is returned. + 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. """ if not use_cached_value: self._metadata = self._aws_session.get_quantum_task(self._arn) @@ -154,19 +160,16 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: def state(self, use_cached_value: bool = False) -> str: """ - State of the quantum task. - + The state of the quantum task. Args: - use_cached_value (bool, optional): If true returns the last state value retrieved from - Amazon Braket GetQuantumTask API else the API is called and the cache is updated. - Default = False. - + use_cached_value (bool, optional): 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 API call. TODO: INSERT BOTO3 DOC LINK. If - `use_cached_value` is True then Amazon Braket is not called and the last value retrieved - is returned. - + 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()` """ @@ -174,13 +177,13 @@ def state(self, use_cached_value: bool = False) -> str: def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: """ - Get the quantum task result by polling Amazon Braket to see if the task is completed. Once - the task is completed the result is retrieved from S3 and returned as a QuantumTaskResult. + Get the quantum task result by polling Amazon Braket to see if the task is completed. + Once the task is completed, the result is retrieved from S3 and returned as a + `QuantumTaskResult`. - This method is a blocking thread call and will synchronously return back a result. Call + This method is a blocking thread call and synchronously returns a result. Call async_result() if you require an asynchronous invocation. - - Consecutive calls to this method will return back a cached result. + Consecutive calls to this method return a cached result from the preceding request. """ try: return asyncio.get_event_loop().run_until_complete(self.async_result()) @@ -190,9 +193,8 @@ def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult def async_result(self) -> asyncio.Task: """ - Get the quantum task result asynchronously. - - Consecutive calls to this method will return back a cached result. + Get the quantum task result asynchronously. Consecutive calls to this method return + the result cached from the most recent request. """ if ( self._future.done() @@ -204,8 +206,8 @@ def async_result(self) -> asyncio.Task: async def _create_future(self) -> asyncio.Task: """ - Wrap the _wait_for_completion coroutine inside a future-like object. - Invoking this method will start the coroutine and return back the 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. Returns: @@ -215,14 +217,12 @@ async def _create_future(self) -> asyncio.Task: async def _wait_for_completion(self) -> GateModelQuantumTaskResult: """ - Waits for the quantum task to be completed and returns back result from S3. - + Waits for the quantum task to be completed, then returns the result from the S3 bucket. Returns: GateModelQuantumTaskResult: If the task is in the `AwsQuantumTask.RESULTS_READY_STATES` - state within the time limit then the result from S3 is loaded and returned. None is - returned if a timeout occurs or task state is in `AwsQuantumTask.TERMINAL_STATES` - but not `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. + TERMINAL_STATES` but not `AwsQuantumTask.RESULTS_READY_STATES`. Note: Timeout and sleep intervals are defined in the constructor fields `poll_timeout_seconds` and `poll_interval_seconds` respectively. diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 13f6f721..a741a212 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -32,11 +32,11 @@ class AwsSession(object): def __init__(self, boto_session=None, braket_client=None): """ Args: - boto_session: boto3 session object - braket_client: boto3 braket client + boto_session: A boto3 session object + braket_client: A boto3 Braket client Raises: - ValueError: If Amazon Braket does not exist for the `boto_session`'s region. + ValueError: If Braket is not available in the Region used for the boto3 session. """ self.boto_session = boto_session or boto3.Session() @@ -49,7 +49,7 @@ def __init__(self, boto_session=None, braket_client=None): if not endpoint: supported_regions = list(AwsSession.BRAKET_ENDPOINTS.keys()) raise ValueError( - f"No braket endpoint for {region}, supported regions are {supported_regions}" + f"No braket endpoint for {region}, supported Regions are {supported_regions}" ) self.braket_client = self.boto_session.client("braket", endpoint_url=endpoint) @@ -62,7 +62,7 @@ def cancel_quantum_task(self, arn: str) -> None: Cancel the quantum task. Args: - arn (str): ARN of the quantum task to cancel. + arn (str): The ARN of the quantum task to cancel. """ self.braket_client.cancel_quantum_task(quantumTaskArn=arn) @@ -71,36 +71,36 @@ def create_quantum_task(self, **boto3_kwargs) -> str: Create a quantum task. Args: - **boto3_kwargs: Keyword arguments for the Braket CreateQuantumTask API. + **boto3_kwargs: Keyword arguments for the Amazon Braket `CreateQuantumTask` operation. Returns: - str: Quantum task ARN. + str: The ARN of the quantum task. """ response = self.braket_client.create_quantum_task(**boto3_kwargs) return response["quantumTaskArn"] def get_quantum_task(self, arn: str) -> Dict[str, Any]: """ - Get the quantum task. + Gets the quantum task. Args: - arn (str): ARN of the quantum task to cancel. + arn (str): The ARN of the quantum task to cancel. Returns: - Dict[str, Any]: Braket GetQuantumTask API result. + Dict[str, Any]: The response from the Amazon Braket `GetQuantumTask` operation. """ return self.braket_client.get_quantum_task(quantumTaskArn=arn) def retrieve_s3_object_body(self, s3_bucket: str, s3_object_key: str) -> str: """ - Retrieve S3 object body + Retrieve the S3 object body Args: - s3_bucket (str): S3 bucket name - s3_object_key (str): S3 object key within `s3_bucket` + s3_bucket (str): The S3 bucket name + s3_object_key (str): The S3 object key within the `s3_bucket` Returns: - str: Body of S3 object + str: The body of the S3 object """ s3 = self.boto_session.resource("s3") obj = s3.Object(s3_bucket, s3_object_key) @@ -109,10 +109,11 @@ def retrieve_s3_object_body(self, s3_bucket: str, s3_object_key: str) -> str: # TODO: add in boto3 exception handling once we have exception types in API def get_qpu_metadata(self, arn: str) -> Dict[str, Any]: """ - Call AWS API describe_qpus to return metadata about QPU + Calls the Amazon Braket `DescribeQpus` (`describe_qpus`) operation to retrieve + QPU metadata. Args: - arn (str): QPU ARN + arn (str): The ARN of the QPU to retrieve metadata from Returns: Dict[str, Any]: QPU metadata @@ -127,13 +128,14 @@ def get_qpu_metadata(self, arn: str) -> Dict[str, Any]: # TODO: add in boto3 exception handling once we have exception types in API def get_simulator_metadata(self, arn: str) -> Dict[str, Any]: """ - Call AWS API describe_quantum_simulators to return metadata about simulator + Calls the Amazon Braket `DescribeQuantumSimulators` (`describe_quantum_simulators`) to + retrieve simulator metadata Args: - arn (str): simulator ARN + arn (str): The ARN of the simulator to retrieve metadata from Returns: - Dict[str, Any]: simulator metadata + Dict[str, Any]: Simulator metadata """ try: response = self.braket_client.describe_quantum_simulators(quantumSimulatorArns=[arn]) diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index 88b44875..bf9545a5 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -24,18 +24,18 @@ class AngledGate(Gate): def __init__(self, angle: float, qubit_count: int, ascii_symbols: Sequence[str]): """ Args: - angle (float): Angle of gate in radians - qubit_count (int): Number of qubits this gate interacts with. - ascii_symbols (Sequence[str]): ASCII string symbols for the gate, these are used when - printing a diagram of circuits. Length must be the same as `qubit_count`, and - index ordering is expected to correlate with target ordering on the instruction. - For instance, if CNOT instruction has the control qubit on the first index and - target qubit on the second index. Then ASCII symbols would have ["C", "X"] to + angle (float): The angle of the gate in radians. + qubit_count (int): The number of qubits that this gate interacts with. + ascii_symbols (Sequence[str]): ASCII string symbols for the gate. These are used when + 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. + For instance, if a CNOT instruction has the control qubit on the first index and + target qubit on the second index, the ASCII symbols should have `["C", "X"]` to correlate a symbol with that index. Raises: - ValueError: `qubit_count` is less than 1, `ascii_symbols` are None, or - `ascii_symbols` length != `qubit_count`, or `angle` is None + ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or + `ascii_symbols` length != `qubit_count`, or `angle` is`None` """ super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) if angle is None: @@ -45,9 +45,9 @@ def __init__(self, angle: float, qubit_count: int, ascii_symbols: Sequence[str]) @property def angle(self) -> float: """ - Returns angle for the gate + Returns the angle for the gate Returns: - angle (float): angle of gate in radians + angle (float): The angle of the gate in radians """ return self._angle diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index 51cbe9da..38573a2c 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -73,10 +73,10 @@ def _ascii_diagram_moment( time: int, circuit_qubits: QubitSet, instructions: List[Instruction] ) -> str: """ - Return an ASCII string diagram of the circuit at a particular time moment. + Return an ASCII string diagram of the circuit at a particular moment in time. Returns: - str: ASCII string diagram for the given time moment. + str: An ASCII string diagram for the specified moment in time. """ symbols = {qubit: "-" for qubit in circuit_qubits} margins = {qubit: " " for qubit in circuit_qubits} diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index a55bc6db..5302c69b 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -30,18 +30,18 @@ class Circuit: """ A representation of a quantum circuit that contains the instructions to be performed on a - quantum device. See :mod:`braket.circuits.gates` module for all the supported instructions. + quantum device. See :mod:`braket.circuits.gates` module for all of the supported instructions. """ @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: func (Callable[..., Union[Instruction, Iterable[Instruction]]]): The function of the - subroutine to be added to the class. + subroutine to add to the class. Examples: >>> def h_on_all(target): @@ -71,7 +71,7 @@ def method_from_subroutine(self, *args, **kwargs) -> SubroutineReturn: def __init__(self, addable: AddableTypes = None, *args, **kwargs): """ Args: - addable (Instruction, iterable of Instruction, or SubroutineCallable, optional): The + addable (Instruction, iterable of `Instruction`, or `SubroutineCallable`, optional): The instruction-like item(s) to add to self. Default = None. *args: Variable length argument list. Supports any arguments that `add()` offers. **kwargs: Arbitrary keyword arguments. Supports any keyword arguments that `add()` @@ -103,12 +103,12 @@ def depth(self) -> int: @property def instructions(self) -> Iterable[Instruction]: - """Iterable[Instruction]: Get an iterable of instructions in the circuit.""" + """Iterable[Instruction]: Get an `iterable` of instructions in the circuit.""" return self._moments.values() @property def moments(self) -> Moments: - """Moments: Get the moments for this circuit.""" + """Moments: Get the `moments` for this circuit.""" return self._moments @property @@ -128,13 +128,14 @@ def add_instruction( target_mapping: Dict[QubitInput, QubitInput] = {}, ) -> "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. + instruction (Instruction): `Instruction` to add into `self`. target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the - instruction. If instruction is a single qubit gate than an instruction is created - for every index in `target`. Default = None. + `instruction`. If a single qubit gate, an instruction is created for every index + in `target`. + Default = None. target_mapping (dictionary[int or Qubit, int or Qubit], optional): 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 = {}. @@ -169,7 +170,7 @@ def add_instruction( Instruction('operator': 'H', 'target': QubitSet(Qubit(11),)) """ if target_mapping and target is not None: - raise TypeError("Both 'target_mapping' and 'target' cannot be supplied.") + raise TypeError("Only one of 'target_mapping' or 'target' can be supplied.") if not target_mapping and not target: # Nothing has been supplied, add instruction @@ -196,7 +197,7 @@ def add_circuit( ) -> "Circuit": """ Add a `circuit` to self, returns self for chaining ability. This is a composite form of - `add_instruction()` since it adds all the instructions of `circuit` to this circuit. + `add_instruction()` since it adds all of the instructions of `circuit` to this circuit. Args: circuit (Circuit): Circuit to add into self. @@ -206,7 +207,7 @@ def add_circuit( Default = None. target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of qubit mappings to apply to the qubits of `circuit.instructions`. Key is the qubit - to be mapped and the value is what it will be changed to. Default = {}. + to map, and the Value is what to change it to. Default = {}. Returns: Circuit: self @@ -215,11 +216,11 @@ def add_circuit( TypeError: If both `target_mapping` and `target` are supplied. Note: - Supplying `target` sorts `circuit.qubits` in order to have deterministic behavior since - `circuit.qubits` ordering is based on how instructions are inserted. Therefore be - cautious using this parameter with circuits that have large number of qubits as the sort - can become costly. If performance is a concern then use `target_mapping` since that has - only a linear runtime to remap the qubits. + Supplying `target` sorts `circuit.qubits` to have deterministic behavior since + `circuit.qubits` ordering is based on how instructions are inserted. + Use caution when using this with circuits that with a lot of qubits, as the sort + can be resource-intensive. Use `target_mapping` to use a linear runtime to remap + the qubits. Examples: >>> widget = Circuit().h(0).cnot([0, 1]) @@ -244,7 +245,7 @@ def add_circuit( Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(10), Qubit(11))) """ if target_mapping and target is not None: - raise TypeError("Both 'target_mapping' and 'target' cannot be supplied.") + raise TypeError("Only one of 'target_mapping' or 'target' can be supplied.") elif target is not None: keys = sorted(circuit.qubits) values = target @@ -258,8 +259,8 @@ def add_circuit( def add(self, addable: AddableTypes, *args, **kwargs) -> "Circuit": """ Generic add method for adding instruction-like item(s) to self. Any arguments that - `add_circuit()` and / or `add_instruction()` supports is supported by this method. - If adding a subroutine then check with that subroutines docs to determine what input it + `add_circuit()` and / or `add_instruction()` supports are supported by this method. + If adding a subroutine, check with that subroutines documentation to determine what input it allows. Args: @@ -315,21 +316,21 @@ def diagram(self, circuit_diagram_class=AsciiCircuitDiagram) -> str: Get a diagram for the current circuit. Args: - circuit_diagram_class (Class, optional): A CircuitDiagram class that will build the + circuit_diagram_class (Class, optional): A `CircuitDiagram` class that builds the diagram for this circuit. Default = AsciiCircuitDiagram. Returns: - str: String circuit diagram. + str: An ASCII string circuit diagram. """ return circuit_diagram_class.build_diagram(self) def to_ir(self) -> Program: """ Converts the circuit into the canonical intermediate representation. - If the circuit is sent over the wire then this method is called before it is sent. + If the circuit is sent over the wire, this method is called before it is sent. Returns: - (Program): A JSON AWS Quantum Circuit Description (jaqcd) program. + (Program): An AWS quantum circuit description program in JSON format. """ ir_instructions = [instr.to_ir() for instr in self.instructions] return Program(instructions=ir_instructions) @@ -339,7 +340,7 @@ def _copy(self) -> "Circuit": Return a shallow copy of the circuit. Returns: - Circuit: Shallow copy of the circuit. + Circuit: A shallow copy of the circuit. """ return Circuit().add(self.instructions) @@ -368,7 +369,7 @@ def subroutine(register=False): Subroutine is a function that returns instructions or circuits. Args: - register (bool, optional): If true, monkey patch this subroutine into the Circuit class. + register (bool, optional): If `True`, adds this subroutine into the `Circuit` class. Default = False. Examples: diff --git a/src/braket/circuits/circuit_diagram.py b/src/braket/circuits/circuit_diagram.py index ce45ba3a..821db691 100644 --- a/src/braket/circuits/circuit_diagram.py +++ b/src/braket/circuits/circuit_diagram.py @@ -21,12 +21,12 @@ class CircuitDiagram(ABC): @abstractmethod def build_diagram(circuit) -> str: """ - Build a diagram for the suppplied `circuit`. + Build a diagram for the specified `circuit`. Args: circuit (Circuit): The circuit to build a diagram for. Returns: str: String representation for the circuit diagram. - Empty string is returned for an empty circuit. + An empty string is returned for an empty circuit. """ diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index 9c43a66b..bb08327b 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -23,16 +23,16 @@ class Gate(Operator): """ - Class `Gate` represents a quantum gate that operates on N qubits and 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 meta-data that defines what a gate is and what it can do. + the metadata that defines what a gate is and what it does. """ def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): """ Args: qubit_count (int): Number of qubits this gate interacts with. - ascii_symbols (Sequence[str]): ASCII string symbols for the gate, these are used when + ascii_symbols (Sequence[str]): ASCII string symbols for the gate. These are used when printing a diagram of circuits. Length must be the same as `qubit_count`, and index ordering is expected to correlate with target ordering on the instruction. For instance, if CNOT instruction has the control qubit on the first index and @@ -72,7 +72,7 @@ def name(self) -> str: Returns the name of the gate Returns: - name of the gate as a string + The name of the gate as a string """ return self.__class__.__name__ @@ -82,15 +82,15 @@ def to_ir(self, target: QubitSet) -> Any: Args: target (QubitSet): target qubit(s) Returns: - IR object of gate and target + IR object of the gate and target """ raise NotImplementedError("to_ir has not been implemented yet.") def to_matrix(self, *args, **kwargs) -> Any: - """Returns gate in matrix representation + """Returns a matrix representation of the gate Returns: - np.ndarray: gate in matrix representation + np.ndarray: A matrix representation of the gate """ raise NotImplementedError("to_matrix has not been implemented yet.") @@ -109,10 +109,10 @@ def matrix_equivalence(self, other): Return if the matrix form of two gates are equivalent Args: - other (Gate): Gate instance to compare + other (Gate): Gate instance to compare this gate to Returns: - true if matrix forms of this gate and the other gate are equivalent + True if matrix forms of this gate and the gate compared to are equivalent """ if not isinstance(other, Gate): return NotImplemented @@ -128,7 +128,7 @@ def __repr__(self): @classmethod def register_gate(cls, gate: "Gate"): - """Register a gate implementation by monkey patching into the Gate class. + """Register a gate implementation by adding it into the Gate class. Args: gate (Gate): Gate instance to register. diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 41ec6c77..a00659c0 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -29,7 +29,7 @@ To add a new gate: 1. Implement the class and extend `Gate` 2. Add a method with the `@circuit.subroutine(register=True)` decorator. Method name - will be monkey patched into the Circuit class. This method will be the default way + will be added into the `Circuit` class. This method is the default way clients add this gate to a circuit. 3. Register the class with the `Gate` class via `Gate.register_gate()`. """ @@ -58,7 +58,7 @@ def h(target: QubitSetInput) -> Iterable[Instruction]: target (Qubit, int, or iterable of Qubit / int): Target qubit(s) Returns: - Iterable[Instruction]: Iterable of H instructions. + Iterable[Instruction]: `Iterable` of H instructions. Examples: >>> circ = Circuit().h(0) @@ -91,7 +91,7 @@ def i(target: QubitSetInput) -> Iterable[Instruction]: target (Qubit, int, or iterable of Qubit / int): Target qubit(s) Returns: - Iterable[Instruction]: Iterable of I instructions. + Iterable[Instruction]: `Iterable` of I instructions. Examples: >>> circ = Circuit().i(0) @@ -124,7 +124,7 @@ def x(target: QubitSetInput) -> Iterable[Instruction]: target (Qubit, int, or iterable of Qubit / int): Target qubit(s) Returns: - Iterable[Instruction]: Iterable of X instructions. + Iterable[Instruction]: `Iterable` of X instructions. Examples: >>> circ = Circuit().x(0) @@ -157,7 +157,7 @@ def y(target: QubitSetInput) -> Iterable[Instruction]: target (Qubit, int, or iterable of Qubit / int): Target qubit(s) Returns: - Iterable[Instruction]: Iterable of Y instructions. + Iterable[Instruction]: `Iterable` of Y instructions. Examples: >>> circ = Circuit().y(0) @@ -190,7 +190,7 @@ def z(target: QubitSetInput) -> Iterable[Instruction]: target (Qubit, int, or iterable of Qubit / int): Target qubit(s) Returns: - Iterable[Instruction]: Iterable of Z instructions. + Iterable[Instruction]: `Iterable` of Z instructions. Examples: >>> circ = Circuit().z(0) @@ -224,7 +224,7 @@ def s(target: QubitSetInput) -> Iterable[Instruction]: target (Qubit, int, or iterable of Qubit / int): Target qubit(s) Returns: - Iterable[Instruction]: Iterable of S instructions. + Iterable[Instruction]: `Iterable` of S instructions. Examples: >>> circ = Circuit().s(0) @@ -290,7 +290,7 @@ def t(target: QubitSetInput) -> Iterable[Instruction]: target (Qubit, int, or iterable of Qubit / int): Target qubit(s) Returns: - Iterable[Instruction]: Iterable of T instructions. + Iterable[Instruction]: `Iterable` of T instructions. Examples: >>> circ = Circuit().t(0) @@ -323,7 +323,7 @@ def ti(target: QubitSetInput) -> Iterable[Instruction]: target (Qubit, int, or iterable of Qubit / int): Target qubit(s) Returns: - Iterable[Instruction]: Iterable of Ti instructions. + Iterable[Instruction]: `Iterable` of Ti instructions. Examples: >>> circ = Circuit().ti(0) @@ -356,7 +356,7 @@ def v(target: QubitSetInput) -> Iterable[Instruction]: target (Qubit, int, or iterable of Qubit / int): Target qubit(s) Returns: - Iterable[Instruction]: Iterable of V instructions. + Iterable[Instruction]: `Iterable` of V instructions. Examples: >>> circ = Circuit().v(0) @@ -389,7 +389,7 @@ def vi(target: QubitSetInput) -> Iterable[Instruction]: target (Qubit, int, or iterable of Qubit / int): Target qubit(s) Returns: - Iterable[Instruction]: Iterable of Vi instructions. + Iterable[Instruction]: `Iterable` of Vi instructions. Examples: >>> circ = Circuit().vi(0) diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index c34da1ba..eae4872c 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -23,20 +23,21 @@ class Instruction: """ - Instruction is a quantum directive that describes the task to be performed on a quantum device. + An instruction is a quantum directive that describes the task to perform on a quantum device. """ def __init__(self, operator: Operator, target: QubitSetInput): """ Args: operator (Operator): Operator for the instruction. - target (int, Qubit, or iterable of int / Qubit): Target qubits the operator is + target (int, Qubit, or iterable of int / Qubit): Target qubits that the operator is applied to. Raises: ValueError: If `operator` is empty or any integer in `target` does not meet the Qubit or QubitSet class requirements. - TypeError: If a Qubit class cannot be constructed from `target` due to incorrect typing + TypeError: If a Qubit class can't be constructed from `target` due to an incorrect + `typing`. Examples: >>> Instruction(Gate.CNot(), [0, 1]) @@ -55,13 +56,13 @@ def __init__(self, operator: Operator, target: QubitSetInput): @property def operator(self) -> Operator: - """Operator: Operator for the instruction, e.g. `Gate`.""" + """Operator: The operator for the instruction, for example, `Gate`.""" return self._operator @property def target(self) -> QubitSet: """ - QubitSet: Target qubits the operator is applied to. + QubitSet: Target qubits that the operator is applied to. Note: Don't mutate this property, any mutations can have unexpected consequences. @@ -71,7 +72,7 @@ def target(self) -> QubitSet: def to_ir(self): """ Converts the operator into the canonical intermediate representation. - If the operator is sent over the wire then this method is called before it is sent. + If the operator is passed in a request, this method is called before it is passed. """ return self.operator.to_ir(self.target) @@ -82,19 +83,18 @@ def copy( Return a shallow copy of the instruction. Note: - If `target_mapping` is supplied then `self.target` will be mapped to the specified - qubits. This is useful for when wanting to apply an instruction to a circuit but want - to change the target qubits. + If `target_mapping` is specified, then `self.target` is mapped to the specified + qubits. This is useful apply an instruction to a circuit and change the target qubits. Args: target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of qubit mappings to apply to the target. Key is the qubit in this `target` and the - value is what the key will be changed to. Default = {}. + value is what the key is changed to. Default = {}. target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the new instruction. Returns: - Instruction: Shallow copy of the instruction. + Instruction: A shallow copy of the instruction. Raises: TypeError: If both `target_mapping` and `target` are supplied. @@ -112,7 +112,7 @@ def copy( QubitSet(Qubit(5)) """ if target_mapping and target is not None: - raise TypeError("Both 'target_mapping' and 'target' cannot be supplied.") + raise TypeError("Only 'target_mapping' or 'target' can be supplied, but not both.") elif target is not None: return Instruction(self.operator, target) else: diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 5e4da779..495fe3f6 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -37,15 +37,15 @@ class MomentsKey(NamedTuple): class Moments(Mapping[MomentsKey, Instruction]): """ - An ordered mapping of `MomentsKey` to `Instruction`. This is the core data structure that - contains instructions, the ordering of which they are inserted, and the time slices that they - occur. `Moments` implements `Mapping` and therefore offers the same experience as a read-only - dictionary and is mutable only through the `add()` method. + An ordered mapping of `MomentsKey` 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. - This data structure is useful for anything that needs to know about the dependency of - instructions such as printing or optimizing circuit structure before sending to the quantum - device. The original insertion order is preserved and can be retrieved via the `values()` - method. + This data structure is useful to determine a dependency of instructions, such as + printing or optimizing circuit structure, before sending it to a quantum + device. The original insertion order is preserved and can be retrieved via the `values()` + method. Args: instructions (Iterable[Instruction], optional): Instructions to initialize self with. @@ -84,23 +84,23 @@ def __init__(self, instructions: Iterable[Instruction] = []): @property def depth(self) -> int: - """int: Get the depth of self, i.e. number of time slices.""" + """int: Get the depth (number of slices) of self.""" return self._depth @property def qubit_count(self) -> int: - """int: Get the number of qubits used across all the instructions.""" + """int: Get the number of qubits used across all of the instructions.""" return len(self._qubits) @property def qubits(self) -> QubitSet: """ - QubitSet: Get the qubits used across all the instructions. The order of qubits is based - on the order 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 - consumers. If you need to mutate this than copy it via `QubitSet(moments.qubits())`. + consumers. If you need to mutate this, then copy it via `QubitSet(moments.qubits())`. """ return self._qubits @@ -113,8 +113,8 @@ def time_slices(self) -> Dict[int, List[Instruction]]: occur at that moment in time. The order of instructions is in no particular order. Note: - This is a computed result over self and can be freely mutated. Be aware that this - is re-computed every call and has a computational runtime O(N) where N is the number + This is a computed result over self and can be freely mutated. This is re-computed with + every call, with a computational runtime O(N) where N is the number of instructions in self. """ @@ -131,8 +131,8 @@ def add(self, instructions: Iterable[Instruction]) -> None: Add instructions to self. Args: - instructions (Iterable[Instruction]): Instructions to add to self. The instruction will - be added to the max time slice that can fit the instruction. + instructions (Iterable[Instruction]): Instructions to add to self. The instruction + are added to the max time slice in which the instruction fits. """ for instruction in instructions: self._add(instruction) @@ -176,10 +176,10 @@ def get(self, key: MomentsKey, default=None) -> Instruction: Args: key (MomentsKey): Key of the instruction to fetch. - default (Any, optional): Value to return if `key` not in moment. Default = None. + default (Any, optional): Value to return if `key` is not in moment. Default = None. Returns: - Instruction: moments[key] if key in moments else `default` is returned. + Instruction: moments[key] if key in moments, else `default` is returned. """ return self._moments.get(key, default) diff --git a/src/braket/circuits/operator.py b/src/braket/circuits/operator.py index bd60bb57..6ff7b613 100644 --- a/src/braket/circuits/operator.py +++ b/src/braket/circuits/operator.py @@ -26,8 +26,8 @@ def name(self) -> str: def to_ir(self, *args, **kwargs): """ Converts the operator into the canonical intermediate representation. - If the operator is sent over the wire then this method is called before it is sent. + If the operator is passed in a request, this method is called before it is passed. Args: - target (QubitSet): Target qubits the operator is applied to. + target (QubitSet): Target qubits that the operator is applied to. """ diff --git a/src/braket/circuits/qubit.py b/src/braket/circuits/qubit.py index b7455243..aaba8de4 100644 --- a/src/braket/circuits/qubit.py +++ b/src/braket/circuits/qubit.py @@ -47,7 +47,7 @@ def __str__(self): @staticmethod def new(qubit: QubitInput) -> "Qubit": """ - Helper constructor, if input is a Qubit then it returns back the same value + Helper constructor - if input is a Qubit it returns the same value, else a new Qubit is constructed. Args: diff --git a/src/braket/circuits/qubit_set.py b/src/braket/circuits/qubit_set.py index fa5a32a0..46208eba 100644 --- a/src/braket/circuits/qubit_set.py +++ b/src/braket/circuits/qubit_set.py @@ -21,7 +21,7 @@ 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 @@ -31,7 +31,7 @@ class QubitSet(IndexedSet): def __init__(self, qubits: QubitSetInput = []): """ Args: - qubits (int, Qubit, or iterable of int / Qubit): Qubits to be part of the QubitSet. + qubits (int, Qubit, or iterable of int / Qubit): Qubits to be included in the QubitSet. Examples: >>> qubits = QubitSet([0, 1]) @@ -64,11 +64,11 @@ def _flatten(other): def map(self, mapping: Dict[QubitInput, QubitInput]) -> "QubitSet": """ Creates a new QubitSet where this instance's qubits are mapped to the values in `mapping`. - If this instance contains a qubit not in the `mapping` then that qubit is not modified. + If this instance contains a qubit that is not in the `mapping` that qubit is not modified. Args: mapping (dictionary[int or Qubit, int or Qubit]): A dictionary of qubit mappings to - apply. Key is the qubit in this instance to be targeted and the value is what + apply. Key is the qubit in this instance to target, and the value is what the key will be changed to. Returns: diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index b672f485..bc5c5878 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -20,7 +20,7 @@ class Device(ABC): - """ An abstraction over quantum devices which includes quantum computers and simulators. + """ An abstraction over quantum devices that includes quantum computers and simulators. """ @@ -44,13 +44,16 @@ def run( *args, **kwargs ) -> QuantumTask: - """ Run a quantum task specification (circuit or annealing program) on this quantum device. + """ Run a quantum task specification on this quantum device. A task can be a circuit + or an annealing problem. Args: - task_specification (Union[Circuit, Problem]): Specification of task - (circuit or annealing problem) to run on device. + task_specification (Union[Circuit, Problem]): Specification of a task + to run on device. + location: The location to save the task's results - shots (Optional[int]): The number of times to run the circuit or annealing task + + shots (int): The number of times to run the task on the device. Default is 1_000. Returns: QuantumTask: The QuantumTask tracking task execution on this device @@ -71,7 +74,7 @@ def status(self) -> str: """ Return the status of this Device. Returns: - str: The status of thi Device + str: The status of this Device """ return self._status @@ -80,6 +83,6 @@ def status_reason(self) -> str: """ Return the status reason of this Device. Returns: - str: The status reason of this Device + str: The reason that the device is in the current status """ return self._status_reason diff --git a/src/braket/ipython_utils.py b/src/braket/ipython_utils.py index a223d2b7..c499862a 100644 --- a/src/braket/ipython_utils.py +++ b/src/braket/ipython_utils.py @@ -21,7 +21,7 @@ def running_in_jupyter(): Inspired by https://github.com/ipython/ipython/issues/11694 Returns: - bool: True if running in Jupyter else False. + bool: True if running in Jupyter, else False. """ in_ipython = False in_ipython_kernel = False diff --git a/src/braket/tasks/annealing_quantum_task_result.py b/src/braket/tasks/annealing_quantum_task_result.py index 2ea61f95..585f3585 100644 --- a/src/braket/tasks/annealing_quantum_task_result.py +++ b/src/braket/tasks/annealing_quantum_task_result.py @@ -23,19 +23,18 @@ @dataclass class AnnealingQuantumTaskResult: """ - Result of an annealing 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) - where row is solution, column is value of the variable, 'solution_count' (numpy.ndarray) - - list of number of times the solutions occurred, and 'value' (numpy.ndarray) - - list of the output or energy of the solutions + 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. variable_count (int): the number of variables problem_type (str): the type of problem ('ising' or 'qubo') - task_metadata (Dict[str, Any]): Dictionary of task metadata. TODO: Link boto3 docs. - additional_metadata (Dict[str, Any]): A dictionary of additional metadata - that's device-specific TODO: Link result schema docs + task_metadata (Dict[str, Any]): Dictionary of task metadata. + additional_metadata (Dict[str, Any]): A dictionary of additional device-specific metadata """ record_array: numpy.recarray @@ -51,9 +50,9 @@ def data(self, selected_fields=None, sorted_by="value", reverse=False): Args: selected_fields (List[str], optional, default=None): selected fields to return. Options are 'solution', 'value', and 'solution_count' - sorted_by (str, optional, default='value'): data returned will be sorted by this field. + sorted_by (str, optional, default='value'): Sorts the data by this field. Options are 'solution', 'value', and 'solution_count' - reverse (bool, optional, default=False): whether data returned will be in reverse order + reverse (bool, optional, default=False): If True, returns the data in reverse order. Yields: tuple: data in record_array @@ -133,7 +132,7 @@ def create_record_array( solutions: numpy.ndarray, solution_counts: numpy.ndarray, values: numpy.ndarray ) -> numpy.recarray: """ - Create solutions record for AnnealingQuantumTaskResult + Create a solutions record for AnnealingQuantumTaskResult Args: solutions (numpy.ndarray): row is solution, column is value of the variable diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 95b1d9bc..13575502 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -40,8 +40,8 @@ class GateModelQuantumTaskResult: measurement_probabilities_copied_from_device (bool): flag whether `measurement_probabilities` were copied from device. If false, `measurement_probabilities` are calculated from device data. - task_metadata (Dict[str, Any]): Dictionary of task metadata. TODO: Link boto3 docs. - state_vector (Dict[str, complex]): Dictionary where key is state and value is amplitude. + task_metadata (Dict[str, Any]): Dictionary of task metadata. + state_vector (Dict[str, complex]): Dictionary where Key is state and Value is amplitude. """ measurements: np.ndarray @@ -136,7 +136,7 @@ def from_string(result: str) -> GateModelQuantumTaskResult: Create GateModelQuantumTaskResult from string Args: - result (str): JSON object string, whose keys are GateModelQuantumTaskResult attributes. + result (str): JSON object string, with GateModelQuantumTaskResult attributes as keys. Returns: GateModelQuantumTaskResult: A GateModelQuantumTaskResult based on a string diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py index 6c8e9b8b..626a015a 100644 --- a/src/braket/tasks/quantum_task.py +++ b/src/braket/tasks/quantum_task.py @@ -25,7 +25,7 @@ class QuantumTask(ABC): @property @abstractmethod def id(self) -> str: - """str: The task id.""" + """str: The task ID.""" @abstractmethod def cancel(self) -> None: @@ -39,7 +39,7 @@ def state(self) -> str: def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: """ Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: Get the quantum task result. - Call async_result if you want the result in an async way. + Call async_result if you want the result in an asynchronous way. """ @abstractmethod @@ -51,9 +51,10 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: Get task metadata. Args: - use_cached_value (bool, optional): If true returns the last value retrieved + use_cached_value (bool, optional): If True, uses the value retrieved from the previous + request. Returns: - Dict[str, Any]: The metadata regarding the task If `use_cached_value` is True - then the last value retrieved is returned. + Dict[str, Any]: The metadata regarding the task. If `use_cached_value` is True, + then the value retrieved from the most recent request is used. """ From 64ce97503e89cccc4bc71b7addc1755a1b8af45c Mon Sep 17 00:00:00 2001 From: T-Bone Date: Mon, 2 Mar 2020 16:14:07 -0800 Subject: [PATCH 0048/1165] Adding CODEOWNERS file to automate adding reviewers to PRs. (#43) --- CODEOWNERS | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000..ac08b194 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,7 @@ +# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners + +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, these accounts +# will be requested forreview when someone opens a pull request. +* @floralph + From 90836668d2bd3c849631b5e622de1fb68b1aa817 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Mon, 2 Mar 2020 16:16:15 -0800 Subject: [PATCH 0049/1165] Add logs to AwsQuantumTask (#42) --- README.md | 16 +++++++----- examples/debug_bell.py | 23 +++++++++++++++++ src/braket/aws/aws_quantum_task.py | 41 ++++++++++++++++++------------ 3 files changed, 57 insertions(+), 23 deletions(-) create mode 100644 examples/debug_bell.py diff --git a/README.md b/README.md index bf4fc575..7467051b 100644 --- a/README.md +++ b/README.md @@ -183,14 +183,12 @@ There are currently three simulators available for Amazon Braket. To specify whi - `arn:aws:aqx:::quantum-simulator:aqx:qs3` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples from the state vector but includes the entire state vector. This generates more data, and therefore incurs additional costs for storage of data in Amazon S3. #### To validate your configuration using a Python file -1. Open a text editor. -2. Copy the code sample (above), then paste it into the text editor. -3. Replace the `AWS_ACCOUNT_ID` in the value for `s3_folder` to your 12-digit AWS account ID. If you want to use a different folder in the bucket, change `folder-name` to the name of the folder to create. If the folder already exists it uses the existing folder. Your statement should look similar to the following: - `s3_folder = ("braket-output-123456789012", "folder-name")` -4. Save the file with the name `bellpair.py`. -5. Make sure `braketvirtenv` is activated, and then run the following command in the location where you saved the bellpair.py file to run it: +1. Open a text editor with example file `BRAKET_SDK_ROOT/examples/bell.py`. +1. If desired, modify `folder-name` to the name of the folder to create/use for results in following line: + `s3_folder = (f"braket-output-{aws_account_id}", "folder-name")`. Save the file. +1. Make sure `braketvirtenv` is activated, and then run the following command: ```bash - python bellpair.py + python BRAKET_SDK_ROOT/examples/bell.py ``` You should see a result similar to the following: ```Counter({'11': 522, '00': 478})``` @@ -223,6 +221,10 @@ Choose **Run** to execute the code to confirm that your environment is configure When the job completes, you should see output similar to the following: `Counter({'00': 519, '11': 481})` +#### Debugging logs + +Debugging logs are available for troubleshooting. An example to enable these logs can be found in `BRAKET_SDK_ROOT/examples/debug_bell.py`. This example enables logs of task status updates to be continuously printed to console when a quantum task is created. The logs can also be configured to save to a file or output to another stream. + ## Running a Quantum Algorithm on a Quantum Computer With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The steps to do so are the same as those described to validate your environment. Just replace the example code provided in this document with your own code. diff --git a/examples/debug_bell.py b/examples/debug_bell.py new file mode 100644 index 00000000..d026aeb2 --- /dev/null +++ b/examples/debug_bell.py @@ -0,0 +1,23 @@ +import logging +import sys + +import boto3 +from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns +from braket.circuits import Circuit + +logger = logging.getLogger("newLogger") # create new logger +logger.addHandler(logging.StreamHandler(stream=sys.stdout)) # configure to print to sys.stdout +logger.setLevel(logging.DEBUG) # print to sys.stdout all log messages with level DEBUG or above + +aws_account_id = boto3.client("sts").get_caller_identity()["Account"] + +device = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS1) +s3_folder = (f"braket-output-{aws_account_id}", "folder-name") + +bell = Circuit().h(0).cnot(0, 1) +# pass in logger to device.run, enabling debugging logs to print to console +logger.info( + device.run(bell, s3_folder, poll_timeout_seconds=120, poll_interval_seconds=0.25, logger=logger) + .result() + .measurement_counts +) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index ec154986..7c68c6bf 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -16,6 +16,7 @@ import asyncio import time from functools import singledispatch +from logging import Logger, getLogger from typing import Any, Callable, Dict, Optional, Union from braket.annealing.problem import Problem @@ -24,7 +25,6 @@ from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult, QuantumTask -# TODO: add AnnealingQuantumTaskResult class AwsQuantumTask(QuantumTask): """Amazon Braket implementation of a quantum task. A task can be a circuit or an annealing problem. Currently, only circuits are supported in the Private Beta.""" @@ -61,19 +61,19 @@ def create( device_arn (str): The ARN of the quantum device. task_specification (Union[Circuit, Problem]): The specification of the task - to run on device. + to run on device. 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 task results in. + for index 0 and key for index 1, that specifies the Amazon S3 bucket and folder + to store task results in. shots (int): The number of times to run the task on the device. If the device is a - simulator, this implies the state is sampled N times, where N = `shots`. Default - shots = 1_000. + simulator, this implies the state is sampled N times, where N = `shots`. Default + shots = 1_000. backend_parameters (Dict[str, Any]): Additional parameters to send to the device. - For example, for D-Wave: - >>> backend_parameters = {"dWaveParameters": {"postprocess": "OPTIMIZATION"}} + For example, for D-Wave: + >>> backend_parameters = {"dWaveParameters": {"postprocess": "OPTIMIZATION"}} Returns: AwsQuantumTask: AwsQuantumTask tracking the task execution on the device. @@ -109,6 +109,7 @@ def __init__( results_formatter: Callable[[str], Any], poll_timeout_seconds: int = DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: int = DEFAULT_RESULTS_POLL_INTERVAL, + logger: Logger = getLogger(__name__), ): """ Args: @@ -118,7 +119,9 @@ def __init__( into a results structure (such as `GateModelQuantumTaskResult`) poll_timeout_seconds (int): The polling timeout for result(), default is 120 seconds. poll_interval_seconds (int): The polling interval for result(), default is 0.25 - seconds. + seconds. + logger (Logger): Logger object with which to write logs, such as task statuses + while waiting for task to be in a terminal state. Default is `getLogger(__name__)` """ self._arn: str = arn @@ -126,6 +129,7 @@ def __init__( self._results_formatter = results_formatter self._poll_timeout_seconds = poll_timeout_seconds self._poll_interval_seconds = poll_interval_seconds + self._logger = logger self._metadata: Dict[str, Any] = {} self._result: Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult] = None @@ -163,9 +167,9 @@ def state(self, use_cached_value: bool = False) -> str: The state of the quantum task. Args: use_cached_value (bool, optional): 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. + 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`, @@ -220,17 +224,19 @@ async def _wait_for_completion(self) -> GateModelQuantumTaskResult: Waits for the quantum task to be completed, then returns the result from the S3 bucket. Returns: GateModelQuantumTaskResult: 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. - TERMINAL_STATES` but not `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.TERMINAL_STATES` but not `AwsQuantumTask.RESULTS_READY_STATES`. Note: Timeout and sleep intervals are defined in the constructor fields - `poll_timeout_seconds` and `poll_interval_seconds` respectively. + `poll_timeout_seconds` and `poll_interval_seconds` respectively. """ + self._logger.debug(f"Task {self._arn}: start polling for completion") start_time = time.time() while (time.time() - start_time) < self._poll_timeout_seconds: current_metadata = self.metadata() + self._logger.debug(f"Task {self._arn}: task status {current_metadata['status']}") if current_metadata["status"] in AwsQuantumTask.RESULTS_READY_STATES: result_string = self._aws_session.retrieve_s3_object_body( current_metadata["resultsS3Bucket"], current_metadata["resultsS3ObjectKey"] @@ -244,6 +250,9 @@ async def _wait_for_completion(self) -> GateModelQuantumTaskResult: await asyncio.sleep(self._poll_interval_seconds) # Timed out + self._logger.warning( + f"Task {self._arn}: polling timed out after {time.time()-start_time} secs" + ) self._result = None return None From fb4cc3bde0531cdd6937a84224f15e7693141756 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Tue, 3 Mar 2020 18:37:18 -0800 Subject: [PATCH 0050/1165] Update README.md updated steps to generate the html documentations for the SDK --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f77f559d..6684bab7 100644 --- a/README.md +++ b/README.md @@ -275,11 +275,15 @@ Information to be provided when the next update is available. Coming soon ## Documentation -You can generate the documentation for the SDK. First change directories (`cd`) to position the cursor in the (`doc`) directory. -Then run the following command to generate the HTML documentation files: +You can generate the documentation for the SDK. First change directories (`cd`) to position the cursor in the `braket-python-sdk` directory. Then, run the following command to generate the HTML documentation files: ```bash -make html +tox -e docs +``` + +If you get an error that tox is not installed, run the following command to install it: +```bash +pip install tox ``` To view the generated documentation, open the following file in a browser: From ab63ab5fd81d1432f178d6ec3c3a061adfe62145 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Fri, 6 Mar 2020 15:35:15 -0800 Subject: [PATCH 0051/1165] =?UTF-8?q?Fix=20depth=20and=20ascii=20diagrams?= =?UTF-8?q?=20for=20circuits=20with=20overlapping=20multi-qubi=E2=80=A6=20?= =?UTF-8?q?(#44)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/braket/circuits/ascii_circuit_diagram.py | 99 +++++++++++++++++-- src/braket/circuits/moments.py | 6 +- .../circuits/test_ascii_circuit_diagram.py | 57 +++++++++-- .../braket/circuits/test_moments.py | 7 +- 4 files changed, 149 insertions(+), 20 deletions(-) diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index 38573a2c..b8e008e5 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.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 +from typing import List, Tuple from braket.circuits.circuit_diagram import CircuitDiagram from braket.circuits.gate import Gate @@ -28,7 +28,7 @@ def build_diagram(circuit) -> str: Build an ASCII string circuit diagram. Args: - circuit (Circuit): Circuit to build a diagram of. + circuit (Circuit): Circuit for which to build a diagram. Returns: str: ASCII string circuit diagram. @@ -68,6 +68,43 @@ def build_diagram(circuit) -> str: return "\n".join(lines) + @staticmethod + def _ascii_moment_group_instructions( + instructions: List[Instruction], + ) -> List[Tuple[QubitSet, List[Instruction]]]: + """ + Group instructions in a moment for ASCII diagram + + Args: + instructions (List[Instruction]): list of instructions + + Returns: + List[(QubitSet, List[Instruction])]: list of grouped instructions + """ + groupings = [] + for instr in instructions: + # Can only print Gate operators at the moment + if not isinstance(instr.operator, Gate): + continue + + qubit_range = QubitSet(range(min(instr.target), max(instr.target) + 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(instr) + qubits_added.update(qubit_range) + found_grouping = True + break + + if not found_grouping: + groupings.append((qubit_range, [instr])) + + return groupings + @staticmethod def _ascii_diagram_moment( time: int, circuit_qubits: QubitSet, instructions: List[Instruction] @@ -75,16 +112,62 @@ def _ascii_diagram_moment( """ Return an ASCII string diagram of the circuit at a particular moment in time. + Args: + time (int): time of moment + circuit_qubits (QubitSet): qubits in circuit + instructions (List[Instruction]): list of instructions + Returns: str: An ASCII string diagram for the specified moment in time. """ + + # Group instructions to separate out overlapping multi-qubit gates + groupings = AsciiCircuitDiagram._ascii_moment_group_instructions(instructions) + + column_strs = [ + AsciiCircuitDiagram._ascii_diagram_moment_column(circuit_qubits, grouping[1]) + 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 time width + time_width = len(str(time)) + symbols_width = len(lines[0]) - 1 + if symbols_width < time_width: + diff = time_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(str(time), width=len(lines[0]) - 1) + + return first_line + "\n".join(lines) + + @staticmethod + def _ascii_diagram_moment_column( + circuit_qubits: QubitSet, instructions: List[Instruction] + ) -> str: + """ + Return an ASCII string diagram of the circuit at a particular moment in time for a column. + + Args: + circuit_qubits (QubitSet): qubits in circuit + instructions (List[Instruction]): list of instructions + + 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 instr in instructions: - # Can only print Gate operators at the moment - if not isinstance(instr.operator, Gate): - continue qubits = circuit_qubits.intersection( set(range(min(instr.target), max(instr.target) + 1)) @@ -100,13 +183,13 @@ def _ascii_diagram_moment( else: symbols[qubit] = "|" - # set the margin to be a connector if not on the first qubit + # Set the margin to be a connector if not on the first qubit if qubit != min(instr.target): margins[qubit] = "|" - symbols_width = max([len(symbol) for symbol in symbols.values()] + [len(str(time))]) + symbols_width = max([len(symbol) for symbol in symbols.values()]) - output = "{0:{width}}|\n".format(str(time), width=symbols_width) + output = "" for qubit in circuit_qubits: output += "{0:{width}}\n".format(margins[qubit], width=symbols_width + 1) output += "{0:{fill}{align}{width}}\n".format( diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 495fe3f6..aab9cede 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -138,12 +138,10 @@ def add(self, instructions: Iterable[Instruction]) -> None: self._add(instruction) def _add(self, instruction: Instruction) -> None: - qubit_range = range(min(instruction.target), max(instruction.target) + 1) + qubit_range = instruction.target time = max([self._max_time_for_qubit(qubit) for qubit in qubit_range]) + 1 - # Mark all qubits in the range to avoid another gate being placed in the overlap. - # For example CNOT(0, 5) would draw a line from 0 to 5 and therefore should prevent - # another instruction using those qubits in that time moment. + # Mark all qubits in qubit_range with max_time for qubit in qubit_range: self._max_times[qubit] = max(time, self._max_time_for_qubit(qubit)) 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 5fcc15ca..52ce2fa4 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -18,6 +18,13 @@ def test_empty_circuit(): assert AsciiCircuitDiagram.build_diagram(Circuit()) == "" +def test_one_gate_one_qubit(): + circ = Circuit().h(0) + expected = ("T : |0|", " ", "q0 : -H-", "", "T : |0|") + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_diagram(circ) == expected + + def test_qubit_width(): circ = Circuit().h(0).h(100) expected = ( @@ -43,13 +50,13 @@ def to_ir(self, target): circ = Circuit().h(0).h(1).add_instruction(Instruction(Foo(), 0)) expected = ( - "T : |0|1 |", + "T : |0| 1 |", " ", "q0 : -H-FOO-", " ", "q1 : -H-----", "", - "T : |0|1 |", + "T : |0| 1 |", ) expected = "\n".join(expected) assert AsciiCircuitDiagram.build_diagram(circ) == expected @@ -120,10 +127,48 @@ def test_connector_across_two_qubits(): assert AsciiCircuitDiagram.build_diagram(circ) == expected +def test_overlapping_qubits(): + circ = Circuit().cnot(0, 2).cnot(1, 3).h(0) + expected = ( + "T : | 0 |1|", + " ", + "q0 : -C---H-", + " | ", + "q1 : -|-C---", + " | | ", + "q2 : -X-|---", + " | ", + "q3 : ---X---", + "", + "T : | 0 |1|", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_diagram(circ) == expected + + +def test_overlapping_qubits_angled_gates(): + circ = Circuit().zz([0, 2], 0.15).cnot(1, 3).h(0) + expected = ( + "T : | 0 |1|", + " ", + "q0 : -ZZ(0.15)---H-", + " | ", + "q1 : -|--------C---", + " | | ", + "q2 : -ZZ(0.15)-|---", + " | ", + "q3 : ----------X---", + "", + "T : | 0 |1|", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_diagram(circ) == expected + + def test_connector_across_gt_two_qubits(): circ = Circuit().h(4).cnot(3, 5).h(4).h(2) expected = ( - "T : |0|1|2|", + "T : | 0 |1|", " ", "q2 : -H-----", " ", @@ -133,7 +178,7 @@ def test_connector_across_gt_two_qubits(): " | ", "q5 : ---X---", "", - "T : |0|1|2|", + "T : | 0 |1|", ) expected = "\n".join(expected) assert AsciiCircuitDiagram.build_diagram(circ) == expected @@ -142,7 +187,7 @@ def test_connector_across_gt_two_qubits(): def test_connector_across_non_used_qubits(): circ = Circuit().h(4).cnot(3, 100).h(4).h(101) expected = ( - "T : |0|1|2|", + "T : | 0 |1|", " ", "q3 : ---C---", " | ", @@ -152,7 +197,7 @@ def test_connector_across_non_used_qubits(): " ", "q101 : -H-----", "", - "T : |0|1|2|", + "T : | 0 |1|", ) expected = "\n".join(expected) assert AsciiCircuitDiagram.build_diagram(circ) == expected diff --git a/test/unit_tests/braket/circuits/test_moments.py b/test/unit_tests/braket/circuits/test_moments.py index fc22270c..c3e1be38 100644 --- a/test/unit_tests/braket/circuits/test_moments.py +++ b/test/unit_tests/braket/circuits/test_moments.py @@ -73,8 +73,11 @@ def test_overlaping_qubits(): moments = Moments([h(0), h(0)]) assert moments.depth == 2 - moments.add([cnot(0, 2), h(1)]) - assert moments.depth == 4 + moments.add([cnot(0, 3), h(1)]) + assert moments.depth == 3 + + moments.add([cnot(2, 4)]) + assert moments.depth == 3 def test_qubits(): From 6829662f0cdcdd35087d14ac6208db336bc69e2e Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Fri, 6 Mar 2020 16:56:45 -0800 Subject: [PATCH 0052/1165] Fix 2 control and 2 target gate subroutine parameters (#46) --- src/braket/circuits/gates.py | 65 +++++++++++-------- .../circuits/test_ascii_circuit_diagram.py | 2 +- test/unit_tests/braket/circuits/test_gates.py | 27 ++++++-- 3 files changed, 58 insertions(+), 36 deletions(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index a00659c0..8f4d0034 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -624,11 +624,12 @@ def to_matrix(self) -> np.ndarray: @staticmethod @circuit.subroutine(register=True) - def swap(targets: QubitSet) -> Instruction: + def swap(target1: QubitInput, target2: QubitInput) -> Instruction: """Registers this function into the circuit class. Args: - targets (QubitSet): Target qubit indices. + target1 (Qubit or int): Target qubit 1 index. + target2 (Qubit or int): Target qubit 2 index. Returns: Instruction: Swap instruction. @@ -636,7 +637,7 @@ def swap(targets: QubitSet) -> Instruction: Examples: >>> circ = Circuit().swap(0, 1) """ - return Instruction(Gate.Swap(), target=targets) + return Instruction(Gate.Swap(), target=[target1, target2]) Gate.register_gate(Swap) @@ -664,11 +665,12 @@ def to_matrix(self) -> np.ndarray: @staticmethod @circuit.subroutine(register=True) - def iswap(targets: QubitSet) -> Instruction: + def iswap(target1: QubitInput, target2: QubitInput) -> Instruction: """Registers this function into the circuit class. Args: - targets (QubitSet): Target qubit indices. + target1 (Qubit or int): Target qubit 1 index. + target2 (Qubit or int): Target qubit 2 index. Returns: Instruction: ISwap instruction. @@ -676,7 +678,7 @@ def iswap(targets: QubitSet) -> Instruction: Examples: >>> circ = Circuit().iswap(0, 1) """ - return Instruction(Gate.ISwap(), target=targets) + return Instruction(Gate.ISwap(), target=[target1, target2]) Gate.register_gate(ISwap) @@ -712,11 +714,12 @@ def to_matrix(self) -> np.ndarray: @staticmethod @circuit.subroutine(register=True) - def pswap(targets: QubitSet, angle: float) -> Instruction: + def pswap(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: """Registers this function into the circuit class. Args: - targets (Qubit or int): Target qubit indices. + target1 (Qubit or int): Target qubit 1 index. + target2 (Qubit or int): Target qubit 2 index. Returns: Instruction: PSwap instruction. @@ -724,7 +727,7 @@ def pswap(targets: QubitSet, angle: float) -> Instruction: Examples: >>> circ = Circuit().pswap(0, 1, 0.15) """ - return Instruction(Gate.PSwap(angle), target=targets) + return Instruction(Gate.PSwap(angle), target=[target1, target2]) Gate.register_gate(PSwap) @@ -762,11 +765,12 @@ def to_matrix(self) -> np.ndarray: @staticmethod @circuit.subroutine(register=True) - def xy(targets: QubitSet, angle: float) -> Instruction: + def xy(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: """Registers this function into the circuit class. Args: - targets (Qubit or int): Target qubit indices. + target1 (Qubit or int): Target qubit 1 index. + target2 (Qubit or int): Target qubit 2 index. Returns: Instruction: XY instruction. @@ -774,7 +778,7 @@ def xy(targets: QubitSet, angle: float) -> Instruction: Examples: >>> circ = Circuit().xy(0, 1, 0.15) """ - return Instruction(Gate.XY(angle), target=targets) + return Instruction(Gate.XY(angle), target=[target1, target2]) Gate.register_gate(XY) @@ -1044,11 +1048,12 @@ def to_matrix(self) -> np.ndarray: @staticmethod @circuit.subroutine(register=True) - def xx(targets: QubitSet, angle: float) -> Instruction: + def xx(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: """Registers this function into the circuit class. Args: - targets (Qubit or int): Target qubit indices. + target1 (Qubit or int): Target qubit 1 index. + target2 (Qubit or int): Target qubit 2 index. angle (float): Angle in radians. Returns: @@ -1057,7 +1062,7 @@ def xx(targets: QubitSet, angle: float) -> Instruction: Examples: >>> circ = Circuit().xx(0, 1, 0.15) """ - return Instruction(Gate.XX(angle), target=targets) + return Instruction(Gate.XX(angle), target=[target1, target2]) Gate.register_gate(XX) @@ -1095,11 +1100,12 @@ def to_matrix(self) -> np.ndarray: @staticmethod @circuit.subroutine(register=True) - def yy(targets: QubitSet, angle: float) -> Instruction: + def yy(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: """Registers this function into the circuit class. Args: - targets (Qubit or int): Target qubit indices. + target1 (Qubit or int): Target qubit 1 index. + target2 (Qubit or int): Target qubit 2 index. angle (float): Angle in radians. Returns: @@ -1108,7 +1114,7 @@ def yy(targets: QubitSet, angle: float) -> Instruction: Examples: >>> circ = Circuit().yy(0, 1, 0.15) """ - return Instruction(Gate.YY(angle), target=targets) + return Instruction(Gate.YY(angle), target=[target1, target2]) Gate.register_gate(YY) @@ -1144,11 +1150,12 @@ def to_matrix(self) -> np.ndarray: @staticmethod @circuit.subroutine(register=True) - def zz(targets: QubitSet, angle: float) -> Instruction: + def zz(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: """Registers this function into the circuit class. Args: - targets (Qubit or int): Target qubit indices. + target1 (Qubit or int): Target qubit 1 index. + target2 (Qubit or int): Target qubit 2 index. angle (float): Angle in radians. Returns: @@ -1157,7 +1164,7 @@ def zz(targets: QubitSet, angle: float) -> Instruction: Examples: >>> circ = Circuit().zz(0, 1, 0.15) """ - return Instruction(Gate.ZZ(angle), target=targets) + return Instruction(Gate.ZZ(angle), target=[target1, target2]) Gate.register_gate(ZZ) @@ -1192,20 +1199,21 @@ def to_matrix(self) -> np.ndarray: @staticmethod @circuit.subroutine(register=True) - def ccnot(controls: QubitSet, target: QubitInput) -> Instruction: + def ccnot(control1: QubitInput, control2: QubitInput, target: QubitInput) -> Instruction: """Registers this function into the circuit class. Args: - controls (QubitSet): Control qubit indices. + control1 (Qubit or int): Control qubit 1 index. + control2 (Qubit or int): Control qubit 2 index. target (Qubit or int): Target qubit index. Returns: Instruction: CCNot instruction. Examples: - >>> circ = Circuit().ccnot(controls=[0, 1], target=2) + >>> circ = Circuit().ccnot(0, 1, 2) """ - return Instruction(Gate.CCNot(), target=[controls[0], controls[1], target]) + return Instruction(Gate.CCNot(), target=[control1, control2, target]) Gate.register_gate(CCNot) @@ -1237,12 +1245,13 @@ def to_matrix(self) -> np.ndarray: @staticmethod @circuit.subroutine(register=True) - def cswap(control: QubitInput, targets: QubitSet) -> Instruction: + def cswap(control: QubitInput, target1: QubitInput, target2: QubitInput) -> Instruction: """Registers this function into the circuit class. Args: control (Qubit or int): Control qubit index - targets (QubitSet): Target qubit indices. + target1 (Qubit or int): Target qubit 1 index. + target2 (Qubit or int): Target qubit 2 index. Returns: Instruction: CSwap instruction. @@ -1250,7 +1259,7 @@ def cswap(control: QubitInput, targets: QubitSet) -> Instruction: Examples: >>> circ = Circuit().cswap(0, 1, 2) """ - return Instruction(Gate.CSwap(), target=[control, targets[0], targets[1]]) + return Instruction(Gate.CSwap(), target=[control, target1, target2]) Gate.register_gate(CSwap) 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 52ce2fa4..ba12563d 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -147,7 +147,7 @@ def test_overlapping_qubits(): def test_overlapping_qubits_angled_gates(): - circ = Circuit().zz([0, 2], 0.15).cnot(1, 3).h(0) + circ = Circuit().zz(0, 2, 0.15).cnot(1, 3).h(0) expected = ( "T : | 0 |1|", " ", diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 659d7029..74433dbb 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -115,10 +115,14 @@ def single_target_valid_input(**kwargs): return {"target": 2} -def double_target_valid_input(**kwargs): +def double_target_valid_ir_input(**kwargs): return {"targets": [2, 3]} +def double_target_valid_input(**kwargs): + return {"target1": 2, "target2": 3} + + def angle_valid_input(**kwargs): return {"angle": 0.123} @@ -127,10 +131,14 @@ def single_control_valid_input(**kwargs): return {"control": 0} -def double_control_valid_input(**kwargs): +def double_control_valid_ir_input(**kwargs): return {"controls": [0, 1]} +def double_control_valid_input(**kwargs): + return {"control1": 0, "control2": 1} + + def multi_target_valid_input(**kwargs): return {"targets": [5]} @@ -146,17 +154,22 @@ def two_dimensional_matrix_valid_input(**kwargs): valid_ir_switcher = { "SingleTarget": single_target_valid_input, - "DoubleTarget": double_target_valid_input, + "DoubleTarget": double_target_valid_ir_input, "Angle": angle_valid_input, "SingleControl": single_control_valid_input, - "DoubleControl": double_control_valid_input, + "DoubleControl": double_control_valid_ir_input, "MultiTarget": multi_target_valid_input, "TwoDimensionalMatrix": two_dimensional_matrix_valid_ir_input, } valid_subroutine_switcher = dict( - valid_ir_switcher, **{"TwoDimensionalMatrix": two_dimensional_matrix_valid_input,} + valid_ir_switcher, + **{ + "TwoDimensionalMatrix": two_dimensional_matrix_valid_input, + "DoubleTarget": double_target_valid_input, + "DoubleControl": double_control_valid_input, + } ) @@ -184,13 +197,13 @@ def create_valid_target_input(irsubclasses): if subclass == SingleTarget: qubit_set.extend(list(single_target_valid_input().values())) elif subclass == DoubleTarget: - qubit_set.extend(list(double_target_valid_input().values())) + qubit_set.extend(list(double_target_valid_ir_input().values())) elif subclass == MultiTarget: qubit_set.extend(list(multi_target_valid_input().values())) elif subclass == SingleControl: qubit_set = list(single_control_valid_input().values()) + qubit_set elif subclass == DoubleControl: - qubit_set = list(double_control_valid_input().values()) + qubit_set + qubit_set = list(double_control_valid_ir_input().values()) + qubit_set elif subclass == Angle or subclass == TwoDimensionalMatrix: pass else: From 4c6c8627f6a0de1e0db75403e4f2c6b857795a15 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 6 Mar 2020 16:59:31 -0800 Subject: [PATCH 0053/1165] Create new event loop if doesn't exist (#45) * Fixed a bug where creating a task in a non-main thread would fail due to the asyncio event loop not existing. * Added myself to CODEOWNERS Co-authored-by: caw --- CODEOWNERS | 4 ++-- src/braket/aws/aws_quantum_task.py | 6 ++++++ test/unit_tests/braket/aws/test_aws_quantum_task.py | 13 +++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index ac08b194..940d39da 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -2,6 +2,6 @@ # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, these accounts -# will be requested forreview when someone opens a pull request. +# will be requested for review when someone opens a pull request. * @floralph - +* @speller26 diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 7c68c6bf..44d76f24 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -133,6 +133,12 @@ def __init__( self._metadata: Dict[str, Any] = {} self._result: Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult] = None + try: + asyncio.get_event_loop() + except Exception as e: + self._logger.debug(e) + self._logger.info("No event loop found; creating new event loop") + asyncio.set_event_loop(asyncio.new_event_loop()) self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) @property 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 357617c8..d79cc9c8 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -12,6 +12,8 @@ # language governing permissions and limitations under the License. import asyncio +import threading +import time from unittest.mock import Mock import pytest @@ -299,6 +301,17 @@ def test_from_annealing(aws_session, arn, problem): ) +def test_init_new_thread(aws_session, arn): + tasks_list = [] + threading.Thread(target=_init_and_add_to_list, args=(aws_session, arn, tasks_list)).start() + time.sleep(0.1) + assert len(tasks_list) == 1 + + +def _init_and_add_to_list(aws_session, arn, task_list): + task_list.append(AwsQuantumTask(arn, aws_session, GateModelQuantumTaskResult.from_string)) + + def _assert_create_quantum_task_called_with( aws_session, arn, task_description, ir_type, s3_results_prefix, shots, backend_parameters ): From 5341564ab1888df3b3c9cc5889b7e8a129f53bc0 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Thu, 12 Mar 2020 16:05:36 -0700 Subject: [PATCH 0054/1165] Migrate to V3 API (#47) --- src/braket/aws/aws_session.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index a741a212..f1790139 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -22,9 +22,9 @@ class AwsSession(object): S3DestinationFolder = NamedTuple("S3DestinationFolder", [("bucket", str), ("key", int)]) BRAKET_ENDPOINTS = { - "us-west-1": "https://fdoco1n1x7.execute-api.us-west-1.amazonaws.com/V2", - "us-west-2": "https://xe15dbdvw6.execute-api.us-west-2.amazonaws.com/V2", - "us-east-1": "https://kqjovr0n70.execute-api.us-east-1.amazonaws.com/V2", + "us-west-1": "https://fdoco1n1x7.execute-api.us-west-1.amazonaws.com/V3", + "us-west-2": "https://xe15dbdvw6.execute-api.us-west-2.amazonaws.com/V3", + "us-east-1": "https://kqjovr0n70.execute-api.us-east-1.amazonaws.com/V3", } # similar to sagemaker sdk: From 0c0e57d2013dd2909b48d8b25b2db22155e4a831 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Thu, 12 Mar 2020 17:12:54 -0700 Subject: [PATCH 0055/1165] Update README.md (#48) updated steps to generate the html documentations for the SDK Co-authored-by: randalld-aws <35579021+randalld-aws@users.noreply.github.com> --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7467051b..573dc0ab 100644 --- a/README.md +++ b/README.md @@ -277,11 +277,15 @@ Information to be provided when the next update is available. Coming soon ## Documentation -You can generate the documentation for the SDK. First change directories (`cd`) to position the cursor in the (`doc`) directory. -Then run the following command to generate the HTML documentation files: +You can generate the documentation for the SDK. First change directories (`cd`) to position the cursor in the `braket-python-sdk` directory. Then, run the following command to generate the HTML documentation files: ```bash -make html +tox -e docs +``` + +If you get an error that tox is not installed, run the following command to install it: +```bash +pip install tox ``` To view the generated documentation, open the following file in a browser: From dcfe7542e8468569548be34d1594567272b1598d Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Thu, 12 Mar 2020 17:26:31 -0700 Subject: [PATCH 0056/1165] Update README.md Updating info about how to get the updated version of the SDKs. --- README.md | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 573dc0ab..1cd4e507 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,16 @@ 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. This document describes how to configure your environment to use the Amazon Braket (Private Beta) locally on your computer. It does not include information about how to use Amazon Braket (Private Beta) in the AWS console. +**Getting the latest version** + +Get the latest version of the SDK. If you receive a notice that a new version of the SDK is available, you can update to the latest version. For more information, see [Updating to the latest release](https://github.com/aws/braket-python-sdk/tree/stable/latest#updating-to-the-latest-release). View the [Releases](https://github.com/aws/braket-python-sdk/releases) page for more information. + +**Use the stable/latest branch** + +You should always use the stable/latest branch of this repo, which includes the latest stable version of the SDK. The master branch includes in-progress features and will not work. + +**Providing Feedback and Getting Help** + To provide feedback or request support, please contact the Amazon Braket team at [amazon-braket-preview-support@amazon.com](mailto:amazon-braket-preview-support@amazon.com?subject=Add%20a%20brief%20description%20of%20the%20issue). **Important** @@ -270,8 +280,17 @@ When you want to use it again, you can reactivate it with the same command you u ## Updating to the latest release We will periodically make updates and changes the SDK or the model. When you are notified of a change that requires action on your part, use the following steps to update your environment to the latest version. +### Check the version you have installed +You can view the version of the braket-python-sdk that you have installed by using the following command in the virtual environment: +```bash +pip show braket-sdk +``` +Compare the version displayed in your local environment with the latest version listed in the [Releases](https://github.com/aws/braket-python-sdk/releases) page. If the version listed is higher than your local version, you should update to the latest release. + ### To get the lastest updates -Information to be provided when the next update is available. +Perform the steps described in the [Setting up the Amazon Braket Python SDKs](https://github.com/aws/braket-python-sdk/tree/stable/latest#setting-up-the-amazon-braket-python-sdks) section of this document. The links in that section point to the most recent version of the braket-python-sdk, braket-python-ir, and model file you need to set up the new version of the SDK. + +You can extract the file to the same location you are using and replace the existing files with the updated SDK. This lets you continue to use the same virtual environment. ## Sample Notebooks Coming soon @@ -283,11 +302,6 @@ You can generate the documentation for the SDK. First change directories (`cd`) tox -e docs ``` -If you get an error that tox is not installed, run the following command to install it: -```bash -pip install tox -``` - To view the generated documentation, open the following file in a browser: `BRAKET_SDK_ROOT/build/documentation/html/index.html` From b7a8e7ce2d27250d6297257bdc8b2d13c6f05d82 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Thu, 12 Mar 2020 18:56:22 -0700 Subject: [PATCH 0057/1165] Merging (#49) * Update to README (#20) * Update to README Restructure content to move prereqs to a common section. Copy edit pass for style and language. Formatting changes for hierarchy and organization. * Update readme for private beta Multiple changes throughout to clarify steps to configure one's environment for the beta release. * Update to readme Addressed feedback, corrected a couple of format issues, removed step to create a kernel to simplify. * update to readme Added additional content for using a qpu, incorporated feedback. * restoring section on using a virtenv * update to readme (#23) * update to readme reworking steps to resolve some issues on Mac * Update to update installation steps Changed to remove Conda and download repo zip files instead of configure SSH * update to readme Clean up to remove Conda, misc formatting * update to readme Formatting change * Update README.md * Update README.md addressing feedback from Ava * Update README.md added a note about jobs taking up to 24 hours on IonQ * Update README.md modified descriptions of simulators. * Update README.md Removed step to move the bellpair file to the virtual env. * Update README.md * Update README.md * Update README.md * Update to section on creating resources Included a note in the section about creating resources for Amazon Braket to indicate that the template should be used only once per AWS account. * Update README.md * Add note about using stable branch * Update README.md added a note about how to remain anonymous when interacting with this repository * Update README.md * Add Bell state example (#39) * Added bell example * Changed example in README * Update README.md updated steps to generate the html documentations for the SDK Co-authored-by: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Co-authored-by: Cody Wang --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1cd4e507..63945003 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ 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. This document describes how to configure your environment to use the Amazon Braket (Private Beta) locally on your computer. It does not include information about how to use Amazon Braket (Private Beta) in the AWS console. -**Getting the latest version** +**Getting the latest version** Get the latest version of the SDK. If you receive a notice that a new version of the SDK is available, you can update to the latest version. For more information, see [Updating to the latest release](https://github.com/aws/braket-python-sdk/tree/stable/latest#updating-to-the-latest-release). View the [Releases](https://github.com/aws/braket-python-sdk/releases) page for more information. **Use the stable/latest branch** -You should always use the stable/latest branch of this repo, which includes the latest stable version of the SDK. The master branch includes in-progress features and will not work. +You should always use the stable/latest branch of this repo, which includes the latest stable version of the SDK. The master branch includes in-progress features and will not work. **Providing Feedback and Getting Help** @@ -16,7 +16,7 @@ To provide feedback or request support, please contact the Amazon Braket team at **Important** -If you **Star**, **Watch**, or submit a pull request for this repository, other users that have access to this repository are able to see your user name in the list of watchers. If you want to remain anonymous, you should not Watch or Star this repository, nor post any comments or submit a pull request. +If you **Star**, **Watch**, or submit a pull request for this repository, other users that have access to this repository are able to see your user name in the list of watchers. If you want to remain anonymous, you should not Watch or Star this repository, nor post any comments or submit a pull request. ## Prerequisites Before you begin working with the Amazon Braket SDK, make sure that you've installed or configured the following prerequisites. @@ -83,7 +83,7 @@ pip install awscli ``` #### Configure a profile for the AWS CLI -Configure a CLI 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). +Configure a CLI 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). After you create a profile, use the following command to set the `AWS_PROFILE` so that all future commands can access your AWS account and resources. @@ -113,7 +113,7 @@ Wait until the Status changes to **CREATE_COMPLETE**. You may need to refresh th ## Setting up the Amazon Braket Python SDKs Use the steps in this section to install and configure the Amazon Braket Python SDKs for your environment. You should perform the steps in the order in which they are included in this document. - + ### Download the Amazon Braket GitHub Repositories The easiest way to get the SDKs is to download them directly from the GitHub site. Because the repositories are private during the Private Beta period, an SSH key is required to access the files remotely from a terminal session. If you download them directly from the GitHub site, you can just extract the files to your system or virtual environment without the extra steps of using an SSH key. You need to log in to GitHub using the account that was whitelisted for the Amazon Braket (Private Beta). @@ -188,7 +188,7 @@ The code sample imports the Amazon Braket framework, then defines the execution ### Available Simulators There are currently three simulators available for Amazon Braket. To specify which simulator to use, change the code sample to replace the value for the `AwsQuantumSimulator` to one of the following values: -- `arn:aws:aqx:::quantum-simulator:aqx:qs1` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the state vector and outputs an array of bit strings that appears as though it came from a quantum computer. Does not provide a state vector. +- `arn:aws:aqx:::quantum-simulator:aqx:qs1` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the state vector and outputs an array of bit strings that appears as though it came from a quantum computer. Does not provide a state vector. - `arn:aws:aqx:::quantum-simulator:aqx:qs2` – a tensor network simulator. Provides an approximation of running a job on a quantum computer. - `arn:aws:aqx:::quantum-simulator:aqx:qs3` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples from the state vector but includes the entire state vector. This generates more data, and therefore incurs additional costs for storage of data in Amazon S3. @@ -219,7 +219,7 @@ After you have installed Jupyter, use this command to open a Jupyter notebook so ```bash jupyter notebook ``` -Jupyter opens in a browser window. Choose **New**, and then under **Notebooks**, choose **braket**. +Jupyter opens in a browser window. Choose **New**, and then under **Notebooks**, choose **braket**. **Note** If you are using a Jupyter notebook from an prior installation and did not create a Braket kernel, you will not see braket available for the notebook type. Choose Python3 instead. If you choose Python3, you must have the Braket packages installed globally. @@ -236,7 +236,7 @@ When the job completes, you should see output similar to the following: Debugging logs are available for troubleshooting. An example to enable these logs can be found in `BRAKET_SDK_ROOT/examples/debug_bell.py`. This example enables logs of task status updates to be continuously printed to console when a quantum task is created. The logs can also be configured to save to a file or output to another stream. ## Running a Quantum Algorithm on a Quantum Computer -With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The steps to do so are the same as those described to validate your environment. Just replace the example code provided in this document with your own code. +With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The steps to do so are the same as those described to validate your environment. Just replace the example code provided in this document with your own code. The following example executes the same Bell Pair example described to validate your configuration against a Rigetti quantum computer. ```python @@ -259,7 +259,7 @@ Specify which quantum computer hardware to use by changing the value of the `dev - **D-Wave** Not yet available ### Deactivate the virtual environment -After you are finished using the virtual environment to interact with Amazon Braket, you can deactivate it using the following command. +After you are finished using the virtual environment to interact with Amazon Braket, you can deactivate it using the following command. **To deactivate the virtual environment on Mac or Linux** ```bash @@ -293,7 +293,7 @@ Perform the steps described in the [Setting up the Amazon Braket Python SDKs](ht You can extract the file to the same location you are using and replace the existing files with the updated SDK. This lets you continue to use the same virtual environment. ## Sample Notebooks -Coming soon +Coming soon ## Documentation You can generate the documentation for the SDK. First change directories (`cd`) to position the cursor in the `braket-python-sdk` directory. Then, run the following command to generate the HTML documentation files: From 56705b8bd51e79474058e45e05358ba5678aab6c Mon Sep 17 00:00:00 2001 From: Derek Date: Thu, 12 Mar 2020 19:08:54 -0700 Subject: [PATCH 0058/1165] Revert "Merging (#49)" This reverts commit b7a8e7ce2d27250d6297257bdc8b2d13c6f05d82. --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 63945003..1cd4e507 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ 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. This document describes how to configure your environment to use the Amazon Braket (Private Beta) locally on your computer. It does not include information about how to use Amazon Braket (Private Beta) in the AWS console. -**Getting the latest version** +**Getting the latest version** Get the latest version of the SDK. If you receive a notice that a new version of the SDK is available, you can update to the latest version. For more information, see [Updating to the latest release](https://github.com/aws/braket-python-sdk/tree/stable/latest#updating-to-the-latest-release). View the [Releases](https://github.com/aws/braket-python-sdk/releases) page for more information. **Use the stable/latest branch** -You should always use the stable/latest branch of this repo, which includes the latest stable version of the SDK. The master branch includes in-progress features and will not work. +You should always use the stable/latest branch of this repo, which includes the latest stable version of the SDK. The master branch includes in-progress features and will not work. **Providing Feedback and Getting Help** @@ -16,7 +16,7 @@ To provide feedback or request support, please contact the Amazon Braket team at **Important** -If you **Star**, **Watch**, or submit a pull request for this repository, other users that have access to this repository are able to see your user name in the list of watchers. If you want to remain anonymous, you should not Watch or Star this repository, nor post any comments or submit a pull request. +If you **Star**, **Watch**, or submit a pull request for this repository, other users that have access to this repository are able to see your user name in the list of watchers. If you want to remain anonymous, you should not Watch or Star this repository, nor post any comments or submit a pull request. ## Prerequisites Before you begin working with the Amazon Braket SDK, make sure that you've installed or configured the following prerequisites. @@ -83,7 +83,7 @@ pip install awscli ``` #### Configure a profile for the AWS CLI -Configure a CLI 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). +Configure a CLI 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). After you create a profile, use the following command to set the `AWS_PROFILE` so that all future commands can access your AWS account and resources. @@ -113,7 +113,7 @@ Wait until the Status changes to **CREATE_COMPLETE**. You may need to refresh th ## Setting up the Amazon Braket Python SDKs Use the steps in this section to install and configure the Amazon Braket Python SDKs for your environment. You should perform the steps in the order in which they are included in this document. - + ### Download the Amazon Braket GitHub Repositories The easiest way to get the SDKs is to download them directly from the GitHub site. Because the repositories are private during the Private Beta period, an SSH key is required to access the files remotely from a terminal session. If you download them directly from the GitHub site, you can just extract the files to your system or virtual environment without the extra steps of using an SSH key. You need to log in to GitHub using the account that was whitelisted for the Amazon Braket (Private Beta). @@ -188,7 +188,7 @@ The code sample imports the Amazon Braket framework, then defines the execution ### Available Simulators There are currently three simulators available for Amazon Braket. To specify which simulator to use, change the code sample to replace the value for the `AwsQuantumSimulator` to one of the following values: -- `arn:aws:aqx:::quantum-simulator:aqx:qs1` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the state vector and outputs an array of bit strings that appears as though it came from a quantum computer. Does not provide a state vector. +- `arn:aws:aqx:::quantum-simulator:aqx:qs1` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the state vector and outputs an array of bit strings that appears as though it came from a quantum computer. Does not provide a state vector. - `arn:aws:aqx:::quantum-simulator:aqx:qs2` – a tensor network simulator. Provides an approximation of running a job on a quantum computer. - `arn:aws:aqx:::quantum-simulator:aqx:qs3` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples from the state vector but includes the entire state vector. This generates more data, and therefore incurs additional costs for storage of data in Amazon S3. @@ -219,7 +219,7 @@ After you have installed Jupyter, use this command to open a Jupyter notebook so ```bash jupyter notebook ``` -Jupyter opens in a browser window. Choose **New**, and then under **Notebooks**, choose **braket**. +Jupyter opens in a browser window. Choose **New**, and then under **Notebooks**, choose **braket**. **Note** If you are using a Jupyter notebook from an prior installation and did not create a Braket kernel, you will not see braket available for the notebook type. Choose Python3 instead. If you choose Python3, you must have the Braket packages installed globally. @@ -236,7 +236,7 @@ When the job completes, you should see output similar to the following: Debugging logs are available for troubleshooting. An example to enable these logs can be found in `BRAKET_SDK_ROOT/examples/debug_bell.py`. This example enables logs of task status updates to be continuously printed to console when a quantum task is created. The logs can also be configured to save to a file or output to another stream. ## Running a Quantum Algorithm on a Quantum Computer -With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The steps to do so are the same as those described to validate your environment. Just replace the example code provided in this document with your own code. +With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The steps to do so are the same as those described to validate your environment. Just replace the example code provided in this document with your own code. The following example executes the same Bell Pair example described to validate your configuration against a Rigetti quantum computer. ```python @@ -259,7 +259,7 @@ Specify which quantum computer hardware to use by changing the value of the `dev - **D-Wave** Not yet available ### Deactivate the virtual environment -After you are finished using the virtual environment to interact with Amazon Braket, you can deactivate it using the following command. +After you are finished using the virtual environment to interact with Amazon Braket, you can deactivate it using the following command. **To deactivate the virtual environment on Mac or Linux** ```bash @@ -293,7 +293,7 @@ Perform the steps described in the [Setting up the Amazon Braket Python SDKs](ht You can extract the file to the same location you are using and replace the existing files with the updated SDK. This lets you continue to use the same virtual environment. ## Sample Notebooks -Coming soon +Coming soon ## Documentation You can generate the documentation for the SDK. First change directories (`cd`) to position the cursor in the `braket-python-sdk` directory. Then, run the following command to generate the HTML documentation files: From 6b3ee6f266f4629cf55eec0177bd2688f6e7af09 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Fri, 13 Mar 2020 14:34:36 -0700 Subject: [PATCH 0059/1165] Update README.md (#52) Adding link to Ocean plugin for D-Wave --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 63945003..6ef3dd0b 100644 --- a/README.md +++ b/README.md @@ -256,7 +256,10 @@ print(device.run(bell, s3_folder).result().measurement_counts) Specify which quantum computer hardware to use by changing the value of the `device_arn` to the value for quantum computer to use: - **IonQ** "arn:aws:aqx:::qpu:ionq" (Jobs may take 24 hours to complete on IonQ.) - **Rigetti** "arn:aws:aqx:::qpu:rigetti" -- **D-Wave** Not yet available +- **D-Wave** "arn:aws:aqx:::qpu:d-wave" + +### Using Amazon Braket with D-Wave QPUs +To use Amazon Braket with D-Wave QPUs, you also need to install the [braket-ocean-python-plugin](https://github.com/aws/braket-ocean-python-plugin). Information about how to install the plugin is provided in the [Readme](https://github.com/aws/braket-ocean-python-plugin/blob/master/README.md) for the repo. ### Deactivate the virtual environment After you are finished using the virtual environment to interact with Amazon Braket, you can deactivate it using the following command. From 460ffff48c40cbca0140a546a7b80e4fcce6f2bf Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Fri, 13 Mar 2020 14:52:23 -0700 Subject: [PATCH 0060/1165] Add new version and link to Ocean (#53) --- README.md | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6ef3dd0b..708278b5 100644 --- a/README.md +++ b/README.md @@ -258,8 +258,8 @@ Specify which quantum computer hardware to use by changing the value of the `dev - **Rigetti** "arn:aws:aqx:::qpu:rigetti" - **D-Wave** "arn:aws:aqx:::qpu:d-wave" -### Using Amazon Braket with D-Wave QPUs -To use Amazon Braket with D-Wave QPUs, you also need to install the [braket-ocean-python-plugin](https://github.com/aws/braket-ocean-python-plugin). Information about how to install the plugin is provided in the [Readme](https://github.com/aws/braket-ocean-python-plugin/blob/master/README.md) for the repo. +### Using Amazon Braket with D-Wave QPU +If you want to use [Ocean](https://docs.ocean.dwavesys.com/en/latest/) with the D-Wave QPU, you can install the [braket-ocean-python-plugin](https://github.com/aws/braket-ocean-python-plugin). Information about how to install the plugin is provided in the [README](https://github.com/aws/braket-ocean-python-plugin/blob/master/README.md) for the repo. ### Deactivate the virtual environment After you are finished using the virtual environment to interact with Amazon Braket, you can deactivate it using the following command. diff --git a/setup.py b/setup.py index 0982d067..3008db1b 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="braket-sdk", - version="0.3.1", + version="0.3.2", license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), From 92cfda0000aed33dff8d0109f6d20befe248db8d Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Mon, 16 Mar 2020 13:02:15 -0700 Subject: [PATCH 0061/1165] Update README.md Updates to clarify some configuration steps. --- README.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 708278b5..ea301176 100644 --- a/README.md +++ b/README.md @@ -193,13 +193,17 @@ There are currently three simulators available for Amazon Braket. To specify whi - `arn:aws:aqx:::quantum-simulator:aqx:qs3` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples from the state vector but includes the entire state vector. This generates more data, and therefore incurs additional costs for storage of data in Amazon S3. #### To validate your configuration using a Python file -1. Open a text editor with example file `BRAKET_SDK_ROOT/examples/bell.py`. +1. Open a text editor with example file `../braket-python-sdk/examples/bell.py`. 1. If desired, modify `folder-name` to the name of the folder to create/use for results in following line: `s3_folder = (f"braket-output-{aws_account_id}", "folder-name")`. Save the file. -1. Make sure `braketvirtenv` is activated, and then run the following command: +1. Make sure the virtualenv (`braketvirtenv`) is activated, and then position the cursor in the `/examples` folder of the repo. Assuming you created a virtual environment on your `C:` drive in a folder named `braket`, the cursor should be at the following location: +`c:\braket\braketvirtenv\braket-python-sdk\examples\`. +1. Then use the following command to run the sample: + ```bash - python BRAKET_SDK_ROOT/examples/bell.py + python bell.py ``` + You should see a result similar to the following: ```Counter({'11': 522, '00': 478})``` @@ -223,17 +227,18 @@ Jupyter opens in a browser window. Choose **New**, and then under **Notebooks**, **Note** If you are using a Jupyter notebook from an prior installation and did not create a Braket kernel, you will not see braket available for the notebook type. Choose Python3 instead. If you choose Python3, you must have the Braket packages installed globally. -Copy the code sample (above) into the notebook. Be sure to change the value for the `s3_folder` to replace `AWS_ACCOUNT_ID` with your 12-digit AWS Account ID. You can find your AWS account ID in the AWS console. If you want to use a different folder in the bucket, change `folder-name` to the name of the folder to create. If the folder already exists it uses the existing folder. Your statement should look similar to the following: -`s3_folder = ("braket-output-123456789012", "folder-name")` +Copy the code sample (above) into the notebook. If you want to use a different folder in the bucket, change `folder-name` to the name of the folder to create. If the folder already exists it uses the existing folder. Choose **Run** to execute the code to confirm that your environment is configured correctly. When the job completes, you should see output similar to the following: `Counter({'00': 519, '11': 481})` +**Important** Tasks may not run immediately on the QPU. IonQ runs tasks once every 24 hours. Rigetti tasks run when the QPU is available, with times varying day to day. + #### Debugging logs -Debugging logs are available for troubleshooting. An example to enable these logs can be found in `BRAKET_SDK_ROOT/examples/debug_bell.py`. This example enables logs of task status updates to be continuously printed to console when a quantum task is created. The logs can also be configured to save to a file or output to another stream. +Tasks sent to QPUs don't always run right away. For IonQ, jobs are run once every 24 hours. For Rigetti, tasks are queued and run when the QPU is available, with the time varying day to day. To view task status, you can enable debugging logs. An example of how to enable these logs is included in repo: `../examples/debug_bell.py`. This example enables task logging so that status updates are continuously printed to console after a quantum task is executed. The logs can also be configured to save to a file or output to another stream. You can use the debugging example to get information on the tasks you submit, such as the current status, so that you know when your task completes. ## Running a Quantum Algorithm on a Quantum Computer With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The steps to do so are the same as those described to validate your environment. Just replace the example code provided in this document with your own code. @@ -247,16 +252,16 @@ from braket.aws import AwsQpu, AwsQpuArns aws_account_id = boto3.client("sts").get_caller_identity()["Account"] device = AwsQpu(AwsQpuArns.RIGETTI) -s3_folder = (f"braket-output-{aws_account_id}", "folder-name") +s3_folder = (f"braket-output-{aws_account_id}", "RIGETTI") bell = Circuit().h(0).cnot(0, 1) print(device.run(bell, s3_folder).result().measurement_counts) ``` Specify which quantum computer hardware to use by changing the value of the `device_arn` to the value for quantum computer to use: -- **IonQ** "arn:aws:aqx:::qpu:ionq" (Jobs may take 24 hours to complete on IonQ.) -- **Rigetti** "arn:aws:aqx:::qpu:rigetti" -- **D-Wave** "arn:aws:aqx:::qpu:d-wave" +- **IonQ** "arn:aws:aqx:::qpu:ionq" (Tasks may take 24 hours to complete on IonQ.) +- **Rigetti** "arn:aws:aqx:::qpu:rigetti" (Tasks are run when the QPU is available. Time varies day to day.) +- **D-Wave** "arn:aws:aqx:::qpu:d-wave" (Use for annealing problems.) ### Using Amazon Braket with D-Wave QPU If you want to use [Ocean](https://docs.ocean.dwavesys.com/en/latest/) with the D-Wave QPU, you can install the [braket-ocean-python-plugin](https://github.com/aws/braket-ocean-python-plugin). Information about how to install the plugin is provided in the [README](https://github.com/aws/braket-ocean-python-plugin/blob/master/README.md) for the repo. @@ -306,7 +311,7 @@ tox -e docs ``` To view the generated documentation, open the following file in a browser: -`BRAKET_SDK_ROOT/build/documentation/html/index.html` +../braket-python-sdk/build/documentation/html/index.html` ## Install the SDK for Testing Make sure to install test dependencies first: From 531bf9f3c8a75612693dcb39025762c29634b070 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Mon, 16 Mar 2020 13:03:48 -0700 Subject: [PATCH 0062/1165] Update README.md Updates to some configuration steps. --- README.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 708278b5..ea301176 100644 --- a/README.md +++ b/README.md @@ -193,13 +193,17 @@ There are currently three simulators available for Amazon Braket. To specify whi - `arn:aws:aqx:::quantum-simulator:aqx:qs3` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples from the state vector but includes the entire state vector. This generates more data, and therefore incurs additional costs for storage of data in Amazon S3. #### To validate your configuration using a Python file -1. Open a text editor with example file `BRAKET_SDK_ROOT/examples/bell.py`. +1. Open a text editor with example file `../braket-python-sdk/examples/bell.py`. 1. If desired, modify `folder-name` to the name of the folder to create/use for results in following line: `s3_folder = (f"braket-output-{aws_account_id}", "folder-name")`. Save the file. -1. Make sure `braketvirtenv` is activated, and then run the following command: +1. Make sure the virtualenv (`braketvirtenv`) is activated, and then position the cursor in the `/examples` folder of the repo. Assuming you created a virtual environment on your `C:` drive in a folder named `braket`, the cursor should be at the following location: +`c:\braket\braketvirtenv\braket-python-sdk\examples\`. +1. Then use the following command to run the sample: + ```bash - python BRAKET_SDK_ROOT/examples/bell.py + python bell.py ``` + You should see a result similar to the following: ```Counter({'11': 522, '00': 478})``` @@ -223,17 +227,18 @@ Jupyter opens in a browser window. Choose **New**, and then under **Notebooks**, **Note** If you are using a Jupyter notebook from an prior installation and did not create a Braket kernel, you will not see braket available for the notebook type. Choose Python3 instead. If you choose Python3, you must have the Braket packages installed globally. -Copy the code sample (above) into the notebook. Be sure to change the value for the `s3_folder` to replace `AWS_ACCOUNT_ID` with your 12-digit AWS Account ID. You can find your AWS account ID in the AWS console. If you want to use a different folder in the bucket, change `folder-name` to the name of the folder to create. If the folder already exists it uses the existing folder. Your statement should look similar to the following: -`s3_folder = ("braket-output-123456789012", "folder-name")` +Copy the code sample (above) into the notebook. If you want to use a different folder in the bucket, change `folder-name` to the name of the folder to create. If the folder already exists it uses the existing folder. Choose **Run** to execute the code to confirm that your environment is configured correctly. When the job completes, you should see output similar to the following: `Counter({'00': 519, '11': 481})` +**Important** Tasks may not run immediately on the QPU. IonQ runs tasks once every 24 hours. Rigetti tasks run when the QPU is available, with times varying day to day. + #### Debugging logs -Debugging logs are available for troubleshooting. An example to enable these logs can be found in `BRAKET_SDK_ROOT/examples/debug_bell.py`. This example enables logs of task status updates to be continuously printed to console when a quantum task is created. The logs can also be configured to save to a file or output to another stream. +Tasks sent to QPUs don't always run right away. For IonQ, jobs are run once every 24 hours. For Rigetti, tasks are queued and run when the QPU is available, with the time varying day to day. To view task status, you can enable debugging logs. An example of how to enable these logs is included in repo: `../examples/debug_bell.py`. This example enables task logging so that status updates are continuously printed to console after a quantum task is executed. The logs can also be configured to save to a file or output to another stream. You can use the debugging example to get information on the tasks you submit, such as the current status, so that you know when your task completes. ## Running a Quantum Algorithm on a Quantum Computer With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The steps to do so are the same as those described to validate your environment. Just replace the example code provided in this document with your own code. @@ -247,16 +252,16 @@ from braket.aws import AwsQpu, AwsQpuArns aws_account_id = boto3.client("sts").get_caller_identity()["Account"] device = AwsQpu(AwsQpuArns.RIGETTI) -s3_folder = (f"braket-output-{aws_account_id}", "folder-name") +s3_folder = (f"braket-output-{aws_account_id}", "RIGETTI") bell = Circuit().h(0).cnot(0, 1) print(device.run(bell, s3_folder).result().measurement_counts) ``` Specify which quantum computer hardware to use by changing the value of the `device_arn` to the value for quantum computer to use: -- **IonQ** "arn:aws:aqx:::qpu:ionq" (Jobs may take 24 hours to complete on IonQ.) -- **Rigetti** "arn:aws:aqx:::qpu:rigetti" -- **D-Wave** "arn:aws:aqx:::qpu:d-wave" +- **IonQ** "arn:aws:aqx:::qpu:ionq" (Tasks may take 24 hours to complete on IonQ.) +- **Rigetti** "arn:aws:aqx:::qpu:rigetti" (Tasks are run when the QPU is available. Time varies day to day.) +- **D-Wave** "arn:aws:aqx:::qpu:d-wave" (Use for annealing problems.) ### Using Amazon Braket with D-Wave QPU If you want to use [Ocean](https://docs.ocean.dwavesys.com/en/latest/) with the D-Wave QPU, you can install the [braket-ocean-python-plugin](https://github.com/aws/braket-ocean-python-plugin). Information about how to install the plugin is provided in the [README](https://github.com/aws/braket-ocean-python-plugin/blob/master/README.md) for the repo. @@ -306,7 +311,7 @@ tox -e docs ``` To view the generated documentation, open the following file in a browser: -`BRAKET_SDK_ROOT/build/documentation/html/index.html` +../braket-python-sdk/build/documentation/html/index.html` ## Install the SDK for Testing Make sure to install test dependencies first: From 2af0b35cc82c6f4271892dd2ac9a15cae131b068 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Thu, 19 Mar 2020 14:34:25 -0700 Subject: [PATCH 0063/1165] Update README.md Added link to the built HTML API Reference as another option for accessing the content --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ea301176..42b951ab 100644 --- a/README.md +++ b/README.md @@ -303,8 +303,14 @@ You can extract the file to the same location you are using and replace the exis ## Sample Notebooks Coming soon -## Documentation -You can generate the documentation for the SDK. First change directories (`cd`) to position the cursor in the `braket-python-sdk` directory. Then, run the following command to generate the HTML documentation files: +## Braket Python SDK API Reference Documentation +To view the API Reference for the SDK, either download the .zip file or building it in your local environment. + +**To download the API Reference .zip file** +[Download the .zip file](aws s3 cp s3://braket-external-assets-prod-us-west-2/sdk-docs/built-sdk-documentation.zip braket-sdk-documentation.zip), and then extract the `braket-sdk-documentation.zip` file to your local environment. After you extract the file, open the index.html file in the `SDK Documentation` folder. + +**To generate the API Reference HTML in your local environment** +To generate the HTML, first change directories (`cd`) to position the cursor in the `braket-python-sdk` directory. Then, run the following command to generate the HTML documentation files: ```bash tox -e docs From 7b7df128ed71f8b2ee33292dda0575b86a63de67 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Thu, 19 Mar 2020 14:39:39 -0700 Subject: [PATCH 0064/1165] Update README.md correcting the link to the .zip --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 42b951ab..d6bf898a 100644 --- a/README.md +++ b/README.md @@ -307,9 +307,15 @@ Coming soon To view the API Reference for the SDK, either download the .zip file or building it in your local environment. **To download the API Reference .zip file** -[Download the .zip file](aws s3 cp s3://braket-external-assets-prod-us-west-2/sdk-docs/built-sdk-documentation.zip braket-sdk-documentation.zip), and then extract the `braket-sdk-documentation.zip` file to your local environment. After you extract the file, open the index.html file in the `SDK Documentation` folder. + +Use the following command to download the .zip file +```bash +aws s3 cp s3://braket-external-assets-prod-us-west-2/sdk-docs/built-sdk-documentation.zip braket-sdk-documentation.zip +``` +Then extract the `braket-sdk-documentation.zip` file to your local environment. After you extract the file, open the index.html file in the `SDK Documentation` folder. **To generate the API Reference HTML in your local environment** + To generate the HTML, first change directories (`cd`) to position the cursor in the `braket-python-sdk` directory. Then, run the following command to generate the HTML documentation files: ```bash From 52eddaebdca60c534c62d4b2453a0c4fd888eba6 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Thu, 19 Mar 2020 14:41:41 -0700 Subject: [PATCH 0065/1165] Update README.md adding changes from master for SDK doc download --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ea301176..d6bf898a 100644 --- a/README.md +++ b/README.md @@ -303,8 +303,20 @@ You can extract the file to the same location you are using and replace the exis ## Sample Notebooks Coming soon -## Documentation -You can generate the documentation for the SDK. First change directories (`cd`) to position the cursor in the `braket-python-sdk` directory. Then, run the following command to generate the HTML documentation files: +## Braket Python SDK API Reference Documentation +To view the API Reference for the SDK, either download the .zip file or building it in your local environment. + +**To download the API Reference .zip file** + +Use the following command to download the .zip file +```bash +aws s3 cp s3://braket-external-assets-prod-us-west-2/sdk-docs/built-sdk-documentation.zip braket-sdk-documentation.zip +``` +Then extract the `braket-sdk-documentation.zip` file to your local environment. After you extract the file, open the index.html file in the `SDK Documentation` folder. + +**To generate the API Reference HTML in your local environment** + +To generate the HTML, first change directories (`cd`) to position the cursor in the `braket-python-sdk` directory. Then, run the following command to generate the HTML documentation files: ```bash tox -e docs From 9c96bee7967f16628d1eb2865fcbae81141da8d0 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Thu, 19 Mar 2020 16:27:18 -0700 Subject: [PATCH 0066/1165] Add topology_graph to AwsQpu (#55) --- setup.py | 1 + src/braket/aws/aws_qpu.py | 41 ++++++++++++++++++++++ test/unit_tests/braket/aws/test_aws_qpu.py | 26 ++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/setup.py b/setup.py index 3008db1b..7255efcb 100644 --- a/setup.py +++ b/setup.py @@ -26,6 +26,7 @@ "boto3", "nest-asyncio", "numpy", + "networkx", ], extras_require={ "test": [ diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py index a965557f..959679b2 100644 --- a/src/braket/aws/aws_qpu.py +++ b/src/braket/aws/aws_qpu.py @@ -19,6 +19,7 @@ from braket.aws.aws_session import AwsSession from braket.circuits import Circuit from braket.devices.device import Device +from networkx import Graph, complete_graph, from_edgelist class AwsQpu(Device): @@ -131,6 +132,7 @@ def refresh_metadata(self) -> None: if "annealingModelProperties" in qpu_properties else qpu_properties.get("gateModelProperties") ) + self._topology_graph = self._construct_topology_graph() @property def arn(self) -> str: @@ -143,6 +145,45 @@ def properties(self) -> Dict[str, Any]: """Dict[str, Any]: Return the QPU properties""" return self._properties + @property + def topology_graph(self) -> Graph: + """Graph: topology of QPU as a networkx Graph object + + Examples: + >>> import networkx as nx + >>> device = AwsQpu("arn:aws:aqx:::qpu:rigetti") + >>> nx.draw_kamada_kawai(device.topology_graph, with_labels=True, font_weight="bold") + + >>> topology_subgraph = device.topology_graph.subgraph(range(8)) + >>> nx.draw_kamada_kawai(topology_subgraph, with_labels=True, font_weight="bold") + + >>> print(device.topology_graph.edges) + """ + return self._topology_graph + + def _construct_topology_graph(self) -> Graph: + """ + Construct topology graph. If no such metadata is available, return None. + + Returns: + Graph: topology of QPU as a networkx Graph object + """ + if "connectivity" in self.properties: + adjacency_lists = self.properties["connectivity"]["connectivityGraph"] + edges = [] + for item in adjacency_lists.items(): + i = item[0] + edges.extend([(int(i), int(j)) for j in item[1]]) + if len(edges) == 0: # empty connectivity graph means fully connected + return complete_graph(int(self.properties["qubitCount"])) + else: + return from_edgelist(edges) + elif "couplers" in self.properties: + edges = self.properties["couplers"] + return from_edgelist(edges) + else: + return None + def _aws_session_for_qpu(self, qpu_arn: str, aws_session: AwsSession) -> AwsSession: """ Get an AwsSession for the QPU ARN. QPUs are physically located in specific AWS Regions. diff --git a/test/unit_tests/braket/aws/test_aws_qpu.py b/test/unit_tests/braket/aws/test_aws_qpu.py index f0355de7..be26496c 100644 --- a/test/unit_tests/braket/aws/test_aws_qpu.py +++ b/test/unit_tests/braket/aws/test_aws_qpu.py @@ -13,6 +13,7 @@ from unittest.mock import Mock, patch +import networkx as nx import pytest from braket.aws import AwsQpu, AwsQpuArns from braket.circuits import Circuit @@ -213,6 +214,30 @@ def test_run_with_positional_args_and_kwargs( ) +@pytest.mark.parametrize( + "properties, expected_edges", + [ + ( + {"connectivity": {"connectivityGraph": {"0": ["1"], "1": ["2", "3"]}}}, + [(0, 1), (1, 2), (1, 3)], + ), + ( + {"connectivity": {"connectivityGraph": {}}, "qubitCount": "3"}, + list(nx.complete_graph(3).edges), + ), + ({"couplers": [[0, 1], [1, 2]],}, [(0, 1), (1, 2)]), + ({}, None), + ], +) +def test_construct_topology_graph(qpu, properties, expected_edges): + device = qpu(AwsQpuArns.RIGETTI) + with patch("braket.aws.aws_qpu.AwsQpu.properties", properties): + if expected_edges is None: + assert device._construct_topology_graph() is None + else: + assert list(device._construct_topology_graph().edges) == expected_edges + + def _run_and_assert( aws_quantum_task_mock, qpu, circuit, s3_destination_folder, shots, run_args, run_kwargs ): @@ -241,3 +266,4 @@ def _assert_qpu_fields(qpu, properties_keys, expected_qpu_data): assert qpu.properties[property_name] == expected_qpu_properties.get(property_name) assert qpu.status == expected_qpu_data.get("status") assert qpu.status_reason == expected_qpu_data.get("statusReason") + assert qpu.topology_graph.edges == qpu._construct_topology_graph().edges From 266c8bddad1c997ba82f7a8b5855b84dce3ec306 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 24 Mar 2020 15:20:31 -0600 Subject: [PATCH 0067/1165] Add local simulator support (#57) * Add BraketSimulator class that will eventually be moved to the simulator repo. This is the abstract class that all local simulators should implement. * Added __init__.py for braket.annealing module * Also fixed a couple of malformatted docstrings --- src/braket/annealing/__init__.py | 15 +++ src/braket/aws/aws_quantum_task.py | 27 ++-- src/braket/devices/__init__.py | 1 + src/braket/devices/braket_simulator.py | 59 +++++++++ src/braket/devices/device.py | 2 +- src/braket/devices/local_simulator.py | 119 ++++++++++++++++++ .../tasks/annealing_quantum_task_result.py | 6 +- .../tasks/gate_model_quantum_task_result.py | 8 ++ src/braket/tasks/local_quantum_task.py | 46 +++++++ src/braket/tasks/quantum_task.py | 2 +- .../braket/devices/test_local_simulator.py | 109 ++++++++++++++++ .../braket/tasks/test_local_quantum_task.py | 53 ++++++++ 12 files changed, 431 insertions(+), 16 deletions(-) create mode 100644 src/braket/annealing/__init__.py create mode 100644 src/braket/devices/braket_simulator.py create mode 100644 src/braket/devices/local_simulator.py create mode 100644 src/braket/tasks/local_quantum_task.py create mode 100644 test/unit_tests/braket/devices/test_local_simulator.py create mode 100644 test/unit_tests/braket/tasks/test_local_quantum_task.py diff --git a/src/braket/annealing/__init__.py b/src/braket/annealing/__init__.py new file mode 100644 index 00000000..f4325051 --- /dev/null +++ b/src/braket/annealing/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2019-2019 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. + +# Execute initialization code in circuit module +from braket.annealing.problem import Problem, ProblemType # noqa: F401 diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 44d76f24..34071cd0 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -154,11 +154,12 @@ def cancel(self) -> None: def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: """ Get task metadata defined in Amazon Braket. + Args: use_cached_value (bool, optional): 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. + from the Amazon Braket `GetQuantumTask` operation. If `False`, calls the + `GetQuantumTask` operation to retrieve metadata, 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 @@ -171,11 +172,12 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: def state(self, use_cached_value: bool = False) -> str: """ The state of the quantum task. + Args: use_cached_value (bool, optional): 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. + 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`, @@ -225,14 +227,17 @@ async def _create_future(self) -> asyncio.Task: """ return asyncio.create_task(self._wait_for_completion()) - async def _wait_for_completion(self) -> GateModelQuantumTaskResult: + async def _wait_for_completion( + self, + ) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: """ Waits for the quantum task to be completed, then returns the result from the S3 bucket. Returns: - GateModelQuantumTaskResult: 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.TERMINAL_STATES` but not `AwsQuantumTask.RESULTS_READY_STATES`. + Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: 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.TERMINAL_STATES` but not + `AwsQuantumTask.RESULTS_READY_STATES`. Note: Timeout and sleep intervals are defined in the constructor fields `poll_timeout_seconds` and `poll_interval_seconds` respectively. diff --git a/src/braket/devices/__init__.py b/src/braket/devices/__init__.py index b797522f..891de47f 100644 --- a/src/braket/devices/__init__.py +++ b/src/braket/devices/__init__.py @@ -12,3 +12,4 @@ # language governing permissions and limitations under the License. from braket.devices.device import Device # noqa: F401 +from braket.devices.local_simulator import LocalSimulator # noqa: F401 diff --git a/src/braket/devices/braket_simulator.py b/src/braket/devices/braket_simulator.py new file mode 100644 index 00000000..f5d69cf0 --- /dev/null +++ b/src/braket/devices/braket_simulator.py @@ -0,0 +1,59 @@ +# Copyright 2019-2019 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 abc import ABC, abstractmethod +from typing import Union + +from braket.ir.annealing import Problem +from braket.ir.jaqcd import Program + + +class BraketSimulator(ABC): + """ An abstract simulator that locally runs a quantum task. + + The task can be either a circuit-based program or an annealing task, + specified by the given IR. + + For users creating their own simulator: to register a simulator so the + Braket SDK recognizes its name, the name and class must added as an + entry point for "braket.simulators". This is done by adding an entry to + entry_points in the simulator package's setup.py: + + >>> entry_points = { + >>> "braket.simulators": [ + >>> "backend_name = " + >>> ] + >>> } + """ + + # TODO: Move this class to the local simulator repo and take a dependency on it + # As such, this will not depend on any SDK classes. + + # TODO: Update to use new simulate() method + + @abstractmethod + def run(self, ir: Union[Program, Problem], *args, **kwargs) -> str: + """ Run the task specified by the given IR. + + Extra arguments will contain any additional information necessary to run the task, + such as number of qubits. + + Args: + ir (Union[Program, Problem]): The IR representation of the program + + Returns: + str: A JSON string containing the results of the simulation. + In order to work with braket-python-sdk, the format of the JSON dict should + match that needed by GateModelQuantumTaskResult or AnnealingQuantumTaskResult + in the SDK, depending on the type of task. + """ + raise NotImplementedError() diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index bc5c5878..189da3be 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -49,7 +49,7 @@ def run( Args: task_specification (Union[Circuit, Problem]): Specification of a task - to run on device. + to run on device. location: The location to save the task's results diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py new file mode 100644 index 00000000..935ebf40 --- /dev/null +++ b/src/braket/devices/local_simulator.py @@ -0,0 +1,119 @@ +# Copyright 2019-2019 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 typing import Set, Union + +import pkg_resources + +from braket.annealing.problem import Problem +from braket.circuits import Circuit +from braket.devices.braket_simulator import BraketSimulator +from braket.devices.device import Device +from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult +from braket.tasks.local_quantum_task import LocalQuantumTask + +_simulator_devices = { + entry.name: entry for entry in pkg_resources.iter_entry_points("braket.simulators") +} + + +class LocalSimulator(Device): + """ A simulator meant to run directly on the user's machine. + + This class wraps a BraketSimulator object so that it can be run and returns + results using constructs from the SDK rather than Braket IR. + """ + + def __init__(self, backend: Union[str, BraketSimulator]): + """ + Args: + backend (Union[str, BraketSimulator]): The name of the simulator backend or + the actual simulator instance to use for simulation + """ + delegate = _get_simulator(backend) + super().__init__( + name=delegate.__class__.__name__, + status="AVAILABLE", + status_reason="Local simulator loaded successfully", + ) + self._delegate = delegate + + def run( + self, task_specification: Union[Circuit, Problem], *args, **kwargs, + ) -> LocalQuantumTask: + """ Runs the given task with the wrapped local simulator. + + Args: + task_specification (Union[Circuit, Problem]): + *args: Positional args to pass to the IR simulator + **kwargs: Keyword arguments to pass to the IR simulator + + Returns: + LocalQuantumTask: A LocalQuantumTask object containing the results + of the simulation + + Note: + If running a circuit, the number of qubits will be passed + to the backend as the argument after the circuit itself. + """ + result = _run_internal(task_specification, self._delegate, *args, **kwargs) + return LocalQuantumTask(result) + + @classmethod + def registered_backends(cls) -> Set[str]: + """ Gets the backends that have been registered as entry points + + Returns: + Set[str]: The names of the available backends that can be passed + into LocalSimulator's constructor + """ + return set(_simulator_devices.keys()) + + +@singledispatch +def _get_simulator(simulator): + raise TypeError("Simulator must either be a string or a BraketSimulator instance") + + +@_get_simulator.register +def _(backend_name: str): + if backend_name in _simulator_devices: + device_class = _simulator_devices[backend_name].load() + return device_class() + else: + raise ValueError(f"Only the following devices are available {_simulator_devices.keys()}") + + +@_get_simulator.register +def _(backend_impl: BraketSimulator): + return backend_impl + + +@singledispatch +def _run_internal(task_specification, simulator: BraketSimulator, *args, **kwargs): + raise NotImplementedError("Unsupported task type") + + +@_run_internal.register +def _(circuit: Circuit, simulator: BraketSimulator, *args, **kwargs): + program = circuit.to_ir() + qubits = circuit.qubit_count + result_json = simulator.run(program, qubits, *args, **kwargs) + return GateModelQuantumTaskResult.from_string(result_json) + + +@_run_internal.register +def _(problem: Problem, simulator: BraketSimulator, *args, **kwargs): + ir = problem.to_ir() + result_json = simulator.run(ir, *args, *kwargs) + return AnnealingQuantumTaskResult.from_string(result_json) diff --git a/src/braket/tasks/annealing_quantum_task_result.py b/src/braket/tasks/annealing_quantum_task_result.py index 585f3585..572d3ca3 100644 --- a/src/braket/tasks/annealing_quantum_task_result.py +++ b/src/braket/tasks/annealing_quantum_task_result.py @@ -28,9 +28,9 @@ class AnnealingQuantumTaskResult: Args: record_array (numpy.recarray): numpy array with keys 'solution' (numpy.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. + 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. variable_count (int): the number of variables problem_type (str): the type of problem ('ising' or 'qubo') task_metadata (Dict[str, Any]): Dictionary of task metadata. diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 13575502..ae1629a7 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -140,6 +140,10 @@ def from_string(result: str) -> GateModelQuantumTaskResult: Returns: GateModelQuantumTaskResult: A GateModelQuantumTaskResult based on a string + + Raises: + ValueError: If neither "Measurements" nor "MeasurementProbabilities" is a key + in the result dict """ json_obj = json.loads(result) task_metadata = json_obj["TaskMetadata"] @@ -166,6 +170,10 @@ def from_string(result: str) -> GateModelQuantumTaskResult: measurements_copied_from_device = False m_counts_copied_from_device = False m_probabilities_copied_from_device = True + else: + raise ValueError( + 'One of "Measurements" or "MeasurementProbabilities" must be in the results dict' + ) return GateModelQuantumTaskResult( state_vector=state_vector, task_metadata=task_metadata, diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py new file mode 100644 index 00000000..9c64cad5 --- /dev/null +++ b/src/braket/tasks/local_quantum_task.py @@ -0,0 +1,46 @@ +# Copyright 2019-2019 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 asyncio +import uuid +from typing import Union + +from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult, QuantumTask + + +class LocalQuantumTask(QuantumTask): + """ A task containing the results of a local simulation. + + Since this class is instantiated with the results, cancel() and run_async() are unsupported. + """ + + def __init__(self, result: Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]): + self._id = uuid.uuid4() + self._result = result + + @property + def id(self) -> str: + return str(self._id) + + def cancel(self) -> None: + raise NotImplementedError("Cannot cancel completed local task") + + def state(self) -> str: + return "COMPLETED" + + @property + def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: + return self._result + + def async_result(self) -> asyncio.Task: + # TODO: Allow for asynchronous simulation + raise NotImplementedError("Asynchronous local simulation unsupported") diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py index 626a015a..99b03751 100644 --- a/src/braket/tasks/quantum_task.py +++ b/src/braket/tasks/quantum_task.py @@ -52,7 +52,7 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: Args: use_cached_value (bool, optional): If True, uses the value retrieved from the previous - request. + request. Returns: Dict[str, Any]: The metadata regarding the task. If `use_cached_value` is True, diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py new file mode 100644 index 00000000..3c4f496e --- /dev/null +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -0,0 +1,109 @@ +# Copyright 2019-2019 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 typing import Optional +from unittest.mock import Mock + +import braket.ir as ir +import pytest +from braket.annealing import Problem, ProblemType +from braket.circuits import Circuit +from braket.devices import LocalSimulator, local_simulator +from braket.devices.braket_simulator import BraketSimulator +from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult + +GATE_MODEL_RESULT = json.dumps( + { + "StateVector": {"00": [0.2, 0.2], "01": [0.3, 0.1], "10": [0.1, 0.3], "11": [0.2, 0.2]}, + "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], + "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED"}, + } +) + +ANNEALING_RESULT = json.dumps( + { + "Solutions": [[-1, -1, -1, -1], [1, -1, 1, 1], [1, -1, -1, 1]], + "VariableCount": 4, + "Values": [0.0, 1.0, 2.0], + "SolutionCounts": None, + "ProblemType": "ising", + "Foo": {"Bar": "Baz"}, + "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED", "Shots": 5,}, + } +) + + +class DummyCircuitSimulator(BraketSimulator): + def run( + self, program: ir.jaqcd.Program, qubits: int, shots: Optional[int], *args, **kwargs + ) -> str: + self._shots = shots + self._qubits = qubits + return GATE_MODEL_RESULT + + def assert_shots(self, shots): + assert self._shots == shots + + def assert_qubits(self, qubits): + assert self._qubits == qubits + + +class DummyAnnealingSimulator(BraketSimulator): + def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> str: + return ANNEALING_RESULT + + +mock_entry = Mock() +mock_entry.load.return_value = DummyCircuitSimulator +local_simulator._simulator_devices = {"dummy": mock_entry} + + +def test_load_from_entry_point(): + sim = LocalSimulator("dummy") + task = sim.run(Circuit().h(0).cnot(0, 1), 10) + assert task.result == GateModelQuantumTaskResult.from_string(GATE_MODEL_RESULT) + + +def test_run_gate_model(): + dummy = DummyCircuitSimulator() + sim = LocalSimulator(dummy) + task = sim.run(Circuit().h(0).cnot(0, 1), 10) + dummy.assert_shots(10) + dummy.assert_qubits(2) + assert task.result == GateModelQuantumTaskResult.from_string(GATE_MODEL_RESULT) + + +def test_run_annealing(): + sim = LocalSimulator(DummyAnnealingSimulator()) + task = sim.run(Problem(ProblemType.ISING)) + assert task.result == AnnealingQuantumTaskResult.from_string(ANNEALING_RESULT) + + +def test_registered_backends(): + assert LocalSimulator.registered_backends() == {"dummy"} + + +@pytest.mark.xfail(raises=TypeError) +def test_init_invalid_backend_type(): + LocalSimulator(1234) + + +@pytest.mark.xfail(raises=ValueError) +def test_init_unregistered_backend(): + LocalSimulator("foo") + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_run_unsupported_type(): + sim = LocalSimulator(DummyCircuitSimulator()) + sim.run("I'm unsupported") diff --git a/test/unit_tests/braket/tasks/test_local_quantum_task.py b/test/unit_tests/braket/tasks/test_local_quantum_task.py new file mode 100644 index 00000000..cb9e5275 --- /dev/null +++ b/test/unit_tests/braket/tasks/test_local_quantum_task.py @@ -0,0 +1,53 @@ +# Copyright 2019-2019 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 uuid + +import pytest +from braket.tasks import GateModelQuantumTaskResult +from braket.tasks.local_quantum_task import LocalQuantumTask + +RESULT = GateModelQuantumTaskResult.from_string( + json.dumps( + { + "StateVector": {"00": [0.2, 0.2], "01": [0.3, 0.1], "10": [0.1, 0.3], "11": [0.2, 0.2]}, + "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], + "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED"}, + } + ) +) + +TASK = LocalQuantumTask(RESULT) + + +def test_id(): + # Task ID is valid UUID + uuid.UUID(TASK.id) + + +def test_state(): + assert TASK.state() == "COMPLETED" + + +def test_result(): + assert TASK.result == RESULT + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_cancel(): + TASK.cancel() + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_async(): + TASK.async_result() From aa120e337c773c3e355d28cc826d692280d96368 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 24 Mar 2020 18:19:54 -0600 Subject: [PATCH 0068/1165] Add `from_dict` to results (#58) * This eliminates the need for local simulators to go through an extra ser/de results step * Updated LocalSimulator and BraketSimulator to use this new method * Also improved formatting for a couple of files --- src/braket/aws/aws_qpu.py | 1 + src/braket/devices/braket_simulator.py | 9 ++- src/braket/devices/local_simulator.py | 13 ++-- .../tasks/annealing_quantum_task_result.py | 44 +++++++---- .../tasks/gate_model_quantum_task_result.py | 47 +++++++++-- src/braket/tasks/local_quantum_task.py | 1 + .../test_simulator_quantum_task.py | 1 + .../braket/annealing/test_problem.py | 1 + .../braket/devices/test_local_simulator.py | 53 ++++++------- .../test_annealing_quantum_task_result.py | 34 +++++++- .../test_gate_model_quantum_task_result.py | 78 ++++++++++++++++++- .../braket/tasks/test_local_quantum_task.py | 21 ++--- 12 files changed, 233 insertions(+), 70 deletions(-) diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py index 959679b2..2e8c4912 100644 --- a/src/braket/aws/aws_qpu.py +++ b/src/braket/aws/aws_qpu.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 typing import Any, Dict, Optional, Union import boto3 diff --git a/src/braket/devices/braket_simulator.py b/src/braket/devices/braket_simulator.py index f5d69cf0..434ee01b 100644 --- a/src/braket/devices/braket_simulator.py +++ b/src/braket/devices/braket_simulator.py @@ -10,8 +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 abc import ABC, abstractmethod -from typing import Union +from typing import Any, Dict, Union from braket.ir.annealing import Problem from braket.ir.jaqcd import Program @@ -41,7 +42,7 @@ class BraketSimulator(ABC): # TODO: Update to use new simulate() method @abstractmethod - def run(self, ir: Union[Program, Problem], *args, **kwargs) -> str: + def run(self, ir: Union[Program, Problem], *args, **kwargs) -> Dict[str, Any]: """ Run the task specified by the given IR. Extra arguments will contain any additional information necessary to run the task, @@ -51,9 +52,9 @@ def run(self, ir: Union[Program, Problem], *args, **kwargs) -> str: ir (Union[Program, Problem]): The IR representation of the program Returns: - str: A JSON string containing the results of the simulation. + Dict[str, Any]: A dict containing the results of the simulation. In order to work with braket-python-sdk, the format of the JSON dict should match that needed by GateModelQuantumTaskResult or AnnealingQuantumTaskResult - in the SDK, depending on the type of task. + from the SDK, depending on the type of task. """ raise NotImplementedError() diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 935ebf40..63237ccb 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.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 functools import singledispatch from typing import Set, Union @@ -69,8 +70,8 @@ def run( result = _run_internal(task_specification, self._delegate, *args, **kwargs) return LocalQuantumTask(result) - @classmethod - def registered_backends(cls) -> Set[str]: + @staticmethod + def registered_backends() -> Set[str]: """ Gets the backends that have been registered as entry points Returns: @@ -108,12 +109,12 @@ def _run_internal(task_specification, simulator: BraketSimulator, *args, **kwarg def _(circuit: Circuit, simulator: BraketSimulator, *args, **kwargs): program = circuit.to_ir() qubits = circuit.qubit_count - result_json = simulator.run(program, qubits, *args, **kwargs) - return GateModelQuantumTaskResult.from_string(result_json) + results_dict = simulator.run(program, qubits, *args, **kwargs) + return GateModelQuantumTaskResult.from_dict(results_dict) @_run_internal.register def _(problem: Problem, simulator: BraketSimulator, *args, **kwargs): ir = problem.to_ir() - result_json = simulator.run(ir, *args, *kwargs) - return AnnealingQuantumTaskResult.from_string(result_json) + results_dict = simulator.run(ir, *args, *kwargs) + return AnnealingQuantumTaskResult.from_dict(results_dict) diff --git a/src/braket/tasks/annealing_quantum_task_result.py b/src/braket/tasks/annealing_quantum_task_result.py index 572d3ca3..cc20b23d 100644 --- a/src/braket/tasks/annealing_quantum_task_result.py +++ b/src/braket/tasks/annealing_quantum_task_result.py @@ -91,6 +91,19 @@ def __eq__(self, other) -> bool: return (self.record_array == other.record_array).all() and self_fields == other_fields return NotImplemented + @staticmethod + def from_dict(result: Dict[str, Any]): + """ + Create AnnealingQuantumTaskResult from dict + + Args: + result (Dict[str, Any]): Results dict with AnnealingQuantumTaskResult attributes as keys + + Returns: + AnnealingQuantumTaskResult: An AnnealingQuantumTaskResult based on the given dict + """ + return AnnealingQuantumTaskResult._from_dict_internal(result) + @staticmethod def from_string(result: str) -> AnnealingQuantumTaskResult: """ @@ -100,26 +113,29 @@ def from_string(result: str) -> AnnealingQuantumTaskResult: result (str): JSON object string Returns: - AnnealingQuantumTaskResult: An AnnealingQuantumTaskResult based on a string. + AnnealingQuantumTaskResult: An AnnealingQuantumTaskResult based on the given string """ - json_obj = json.loads(result) - solutions = numpy.asarray(json_obj["Solutions"], dtype=int) - values = numpy.asarray(json_obj["Values"], dtype=float) - if json_obj["SolutionCounts"] is None: + return AnnealingQuantumTaskResult._from_dict_internal(json.loads(result)) + + @classmethod + def _from_dict_internal(cls, result: Dict[str, Any]): + solutions = numpy.asarray(result["Solutions"], dtype=int) + values = numpy.asarray(result["Values"], dtype=float) + if result["SolutionCounts"] is None: solution_counts = numpy.ones(len(solutions), dtype=int) else: - solution_counts = numpy.asarray(json_obj["SolutionCounts"], dtype=int) - record_array = AnnealingQuantumTaskResult.create_record_array( + solution_counts = numpy.asarray(result["SolutionCounts"], dtype=int) + record_array = AnnealingQuantumTaskResult._create_record_array( solutions, solution_counts, values ) - variable_count = json_obj["VariableCount"] - problem_type = json_obj["ProblemType"] - task_metadata = json_obj["TaskMetadata"] + variable_count = result["VariableCount"] + problem_type = result["ProblemType"] + task_metadata = result["TaskMetadata"] additional_metadata = {} - for key in json_obj.keys(): + for key in result.keys(): if key.endswith("Metadata") and key != "TaskMetadata": - additional_metadata[key] = json_obj[key] - return AnnealingQuantumTaskResult( + additional_metadata[key] = result[key] + return cls( record_array=record_array, variable_count=variable_count, problem_type=problem_type, @@ -128,7 +144,7 @@ def from_string(result: str) -> AnnealingQuantumTaskResult: ) @staticmethod - def create_record_array( + def _create_record_array( solutions: numpy.ndarray, solution_counts: numpy.ndarray, values: numpy.ndarray ) -> numpy.recarray: """ diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index ae1629a7..3dbf62ec 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -130,29 +130,60 @@ def measurements_from_measurement_probabilities( measurements_list.extend(individual_measurement_list) return np.asarray(measurements_list, dtype=int) + @staticmethod + def from_dict(result: Dict[str, Any]): + """ + Create GateModelQuantumTaskResult from dict. + + Args: + result (Dict[str, Any]): Results dict with GateModelQuantumTaskResult attributes as keys + + Returns: + GateModelQuantumTaskResult: A GateModelQuantumTaskResult based on the given dict + + Raises: + ValueError: If neither "Measurements" nor "MeasurementProbabilities" is a key + in the result dict + + Note: + For the "StateVector" key, the value should be of type Dict[str, complex]; + each bitstring's amplitude is a Python complex number. + """ + return GateModelQuantumTaskResult._from_dict_internal(result) + @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. Returns: - GateModelQuantumTaskResult: A GateModelQuantumTaskResult based on a string + GateModelQuantumTaskResult: A GateModelQuantumTaskResult based on the given string Raises: ValueError: If neither "Measurements" nor "MeasurementProbabilities" is a key in the result dict + + Note: + For the "StateVector" key, the value should be of type Dict[str, List[float, float]]; + each bitstring's amplitude is represented by a two-element list, with first element + the real component and second element the imaginary component. """ json_obj = json.loads(result) - task_metadata = json_obj["TaskMetadata"] state_vector = json_obj.get("StateVector", None) if state_vector: for state in state_vector: state_vector[state] = complex(*state_vector[state]) - if "Measurements" in json_obj: - measurements = np.asarray(json_obj["Measurements"], dtype=int) + return GateModelQuantumTaskResult._from_dict_internal(json_obj) + + @classmethod + def _from_dict_internal(cls, result: Dict[str, Any]): + task_metadata = result["TaskMetadata"] + state_vector = result.get("StateVector", None) + if "Measurements" in result: + measurements = np.asarray(result["Measurements"], dtype=int) m_counts = GateModelQuantumTaskResult.measurement_counts_from_measurements(measurements) m_probs = GateModelQuantumTaskResult.measurement_probabilities_from_measurement_counts( m_counts @@ -160,9 +191,9 @@ def from_string(result: str) -> GateModelQuantumTaskResult: measurements_copied_from_device = True m_counts_copied_from_device = False m_probabilities_copied_from_device = False - elif "MeasurementProbabilities" in json_obj: + elif "MeasurementProbabilities" in result: shots = task_metadata["Shots"] - m_probs = json_obj["MeasurementProbabilities"] + m_probs = result["MeasurementProbabilities"] measurements = GateModelQuantumTaskResult.measurements_from_measurement_probabilities( m_probs, shots ) @@ -174,7 +205,7 @@ def from_string(result: str) -> GateModelQuantumTaskResult: raise ValueError( 'One of "Measurements" or "MeasurementProbabilities" must be in the results dict' ) - return GateModelQuantumTaskResult( + return cls( state_vector=state_vector, task_metadata=task_metadata, measurements=measurements, diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py index 9c64cad5..c6af7223 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. + import asyncio import uuid from typing import Union diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 2ccd5b7c..71b082e2 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_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. + import math import pytest diff --git a/test/unit_tests/braket/annealing/test_problem.py b/test/unit_tests/braket/annealing/test_problem.py index d8176602..8cd90944 100644 --- a/test/unit_tests/braket/annealing/test_problem.py +++ b/test/unit_tests/braket/annealing/test_problem.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 braket.ir.annealing as ir from braket.annealing.problem import Problem, ProblemType diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 3c4f496e..5066c7f5 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -10,8 +10,8 @@ # 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 typing import Optional + +from typing import Any, Dict, Optional from unittest.mock import Mock import braket.ir as ir @@ -22,31 +22,32 @@ from braket.devices.braket_simulator import BraketSimulator from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult -GATE_MODEL_RESULT = json.dumps( - { - "StateVector": {"00": [0.2, 0.2], "01": [0.3, 0.1], "10": [0.1, 0.3], "11": [0.2, 0.2]}, - "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], - "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED"}, - } -) - -ANNEALING_RESULT = json.dumps( - { - "Solutions": [[-1, -1, -1, -1], [1, -1, 1, 1], [1, -1, -1, 1]], - "VariableCount": 4, - "Values": [0.0, 1.0, 2.0], - "SolutionCounts": None, - "ProblemType": "ising", - "Foo": {"Bar": "Baz"}, - "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED", "Shots": 5,}, - } -) +GATE_MODEL_RESULT = { + "StateVector": { + "00": complex(0.2, 0.2), + "01": complex(0.3, 0.1), + "10": complex(0.1, 0.3), + "11": complex(0.2, 0.2), + }, + "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], + "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED"}, +} + +ANNEALING_RESULT = { + "Solutions": [[-1, -1, -1, -1], [1, -1, 1, 1], [1, -1, -1, 1]], + "VariableCount": 4, + "Values": [0.0, 1.0, 2.0], + "SolutionCounts": None, + "ProblemType": "ising", + "Foo": {"Bar": "Baz"}, + "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED", "Shots": 5,}, +} class DummyCircuitSimulator(BraketSimulator): def run( self, program: ir.jaqcd.Program, qubits: int, shots: Optional[int], *args, **kwargs - ) -> str: + ) -> Dict[str, Any]: self._shots = shots self._qubits = qubits return GATE_MODEL_RESULT @@ -59,7 +60,7 @@ def assert_qubits(self, qubits): class DummyAnnealingSimulator(BraketSimulator): - def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> str: + def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> Dict[str, Any]: return ANNEALING_RESULT @@ -71,7 +72,7 @@ def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> str: def test_load_from_entry_point(): sim = LocalSimulator("dummy") task = sim.run(Circuit().h(0).cnot(0, 1), 10) - assert task.result == GateModelQuantumTaskResult.from_string(GATE_MODEL_RESULT) + assert task.result == GateModelQuantumTaskResult.from_dict(GATE_MODEL_RESULT) def test_run_gate_model(): @@ -80,13 +81,13 @@ def test_run_gate_model(): task = sim.run(Circuit().h(0).cnot(0, 1), 10) dummy.assert_shots(10) dummy.assert_qubits(2) - assert task.result == GateModelQuantumTaskResult.from_string(GATE_MODEL_RESULT) + assert task.result == GateModelQuantumTaskResult.from_dict(GATE_MODEL_RESULT) def test_run_annealing(): sim = LocalSimulator(DummyAnnealingSimulator()) task = sim.run(Problem(ProblemType.ISING)) - assert task.result == AnnealingQuantumTaskResult.from_string(ANNEALING_RESULT) + assert task.result == AnnealingQuantumTaskResult.from_dict(ANNEALING_RESULT) def test_registered_backends(): 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 36f830dd..53fe89b6 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 @@ -110,7 +110,7 @@ def annealing_result( solutions = np.asarray(solutions, dtype=int) values = np.asarray(values, dtype=float) solution_counts = np.asarray(solution_counts, dtype=int) - record_array = AnnealingQuantumTaskResult.create_record_array( + record_array = AnnealingQuantumTaskResult._create_record_array( solutions, solution_counts, values ) return AnnealingQuantumTaskResult( @@ -122,6 +122,30 @@ def annealing_result( ) +def test_from_dict( + result_str_1, + solutions, + values, + solution_counts, + variable_count, + problem_type, + task_metadata, + dwave_metadata, +): + result = AnnealingQuantumTaskResult.from_dict(json.loads(result_str_1)) + solutions = np.asarray(solutions, dtype=int) + values = np.asarray(values, dtype=float) + solution_counts = np.asarray(solution_counts, dtype=int) + assert result.variable_count == variable_count + assert result.problem_type == problem_type + assert result.task_metadata == task_metadata + assert result.additional_metadata == {"DWaveMetadata": dwave_metadata} + np.testing.assert_equal( + result.record_array, + AnnealingQuantumTaskResult._create_record_array(solutions, solution_counts, values), + ) + + def test_from_string( result_str_1, solutions, @@ -142,7 +166,7 @@ def test_from_string( assert result.additional_metadata == {"DWaveMetadata": dwave_metadata} np.testing.assert_equal( result.record_array, - AnnealingQuantumTaskResult.create_record_array(solutions, solution_counts, values), + AnnealingQuantumTaskResult._create_record_array(solutions, solution_counts, values), ) @@ -182,6 +206,12 @@ def test_data_sort_by(annealing_result, solutions, values, solution_counts): assert d[0][2] == solution_counts[min_index] +def test_from_dict_equal_to_from_string(result_str_1): + assert AnnealingQuantumTaskResult.from_dict( + json.loads(result_str_1) + ) == AnnealingQuantumTaskResult.from_string(result_str_1) + + def test_equality(result_str_1, result_str_2): result_1 = AnnealingQuantumTaskResult.from_string(result_str_1) result_2 = AnnealingQuantumTaskResult.from_string(result_str_1) 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 6fcbf8d0..98032a70 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 @@ -20,6 +20,24 @@ from braket.tasks import GateModelQuantumTaskResult +@pytest.fixture +def result_dict_1(): + return { + "StateVector": { + "00": complex(0.2, 0.2), + "01": complex(0.3, 0.1), + "10": complex(0.1, 0.3), + "11": complex(0.2, 0.2), + }, + "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], + "TaskMetadata": { + "Id": "UUID_blah_1", + "Status": "COMPLETED", + "BackendArn": AwsQpuArns.RIGETTI, + }, + } + + @pytest.fixture def result_str_1(): return json.dumps( @@ -79,6 +97,17 @@ def parsed_state_vector(): } +@pytest.fixture +def malformatted_results(): + return { + "TaskMetadata": { + "Id": "UUID_blah_1", + "Status": "COMPLETED", + "BackendArn": AwsQpuArns.RIGETTI, + }, + } + + 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]] @@ -150,7 +179,34 @@ def test_task_metadata(): assert result.task_metadata == task_metadata -def test_from_string_measurements(result_str_1, parsed_state_vector): +def test_from_dict_measurements_state_vector(result_dict_1): + task_result = GateModelQuantumTaskResult.from_dict(result_dict_1) + expected_measurements = np.asarray(result_dict_1["Measurements"], dtype=int) + assert task_result.task_metadata == result_dict_1["TaskMetadata"] + assert task_result.state_vector == result_dict_1["StateVector"] + assert np.array2string(task_result.measurements) == np.array2string(expected_measurements) + assert not task_result.measurement_counts_copied_from_device + assert not task_result.measurement_probabilities_copied_from_device + assert task_result.measurements_copied_from_device + + +def test_from_dict_measurement_probabilities(result_str_3): + result_obj = json.loads(result_str_3) + task_result = GateModelQuantumTaskResult.from_dict(result_obj) + assert task_result.measurement_probabilities == result_obj["MeasurementProbabilities"] + assert task_result.task_metadata == result_obj["TaskMetadata"] + assert task_result.state_vector is None + shots = 100 + measurement_list = [list("011000") for x 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 not task_result.measurement_counts_copied_from_device + assert task_result.measurement_probabilities_copied_from_device + assert not task_result.measurements_copied_from_device + + +def test_from_string_measurements_state_vector(result_str_1, parsed_state_vector): result_obj = json.loads(result_str_1) task_result = GateModelQuantumTaskResult.from_string(result_str_1) expected_measurements = np.asarray(result_obj["Measurements"], dtype=int) @@ -178,6 +234,15 @@ def test_from_string_measurement_probabilities(result_str_3): assert not task_result.measurements_copied_from_device +def test_from_dict_equal_to_from_string(result_dict_1, result_str_1, result_str_3): + assert GateModelQuantumTaskResult.from_dict( + result_dict_1 + ) == GateModelQuantumTaskResult.from_string(result_str_1) + assert GateModelQuantumTaskResult.from_dict( + json.loads(result_str_3) + ) == GateModelQuantumTaskResult.from_string(result_str_3) + + def test_equality(result_str_1, result_str_2): result_1 = GateModelQuantumTaskResult.from_string(result_str_1) result_2 = GateModelQuantumTaskResult.from_string(result_str_1) @@ -188,3 +253,14 @@ def test_equality(result_str_1, result_str_2): assert result_1 is not result_2 assert result_1 != other_result assert result_1 != non_result + + +@pytest.mark.xfail(raises=ValueError) +def test_bad_dict(malformatted_results): + GateModelQuantumTaskResult.from_dict(malformatted_results) + + +@pytest.mark.xfail(raises=ValueError) +def test_bad_string(malformatted_results): + results_string = json.dumps(malformatted_results) + GateModelQuantumTaskResult.from_string(results_string) 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 cb9e5275..0392c9b4 100644 --- a/test/unit_tests/braket/tasks/test_local_quantum_task.py +++ b/test/unit_tests/braket/tasks/test_local_quantum_task.py @@ -10,21 +10,24 @@ # 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 uuid import pytest from braket.tasks import GateModelQuantumTaskResult from braket.tasks.local_quantum_task import LocalQuantumTask -RESULT = GateModelQuantumTaskResult.from_string( - json.dumps( - { - "StateVector": {"00": [0.2, 0.2], "01": [0.3, 0.1], "10": [0.1, 0.3], "11": [0.2, 0.2]}, - "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], - "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED"}, - } - ) +RESULT = GateModelQuantumTaskResult.from_dict( + { + "StateVector": { + "00": complex(0.2, 0.2), + "01": complex(0.3, 0.1), + "10": complex(0.1, 0.3), + "11": complex(0.2, 0.2), + }, + "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], + "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED"}, + } ) TASK = LocalQuantumTask(RESULT) From 3cbe44449b9cf506ae7b03dffe47cc029ed2c165 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 25 Mar 2020 11:56:20 -0600 Subject: [PATCH 0069/1165] Remove SDK references in Instruction.to_ir() (#59) So users working with IR don't see SDK classes like `Qubit`. --- src/braket/circuits/circuit.py | 16 +++++++++------- src/braket/circuits/instruction.py | 14 ++++++++------ src/braket/circuits/qubit.py | 4 +++- src/braket/circuits/qubit_set.py | 4 +++- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 5302c69b..cffc7d0e 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.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 Callable, Dict, Iterable, TypeVar from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram @@ -126,7 +128,7 @@ def add_instruction( instruction: Instruction, target: QubitSetInput = None, target_mapping: Dict[QubitInput, QubitInput] = {}, - ) -> "Circuit": + ) -> Circuit: """ Add an instruction to `self`, returns `self` for chaining ability. @@ -191,10 +193,10 @@ def add_instruction( def add_circuit( self, - circuit: "Circuit", + circuit: Circuit, target: QubitSetInput = None, target_mapping: Dict[QubitInput, QubitInput] = {}, - ) -> "Circuit": + ) -> Circuit: """ Add a `circuit` to self, returns self for chaining ability. This is a composite form of `add_instruction()` since it adds all of the instructions of `circuit` to this circuit. @@ -256,7 +258,7 @@ def add_circuit( return self - def add(self, addable: AddableTypes, *args, **kwargs) -> "Circuit": + def add(self, addable: AddableTypes, *args, **kwargs) -> Circuit: """ Generic add method for adding instruction-like item(s) to self. Any arguments that `add_circuit()` and / or `add_instruction()` supports are supported by this method. @@ -335,7 +337,7 @@ def to_ir(self) -> Program: ir_instructions = [instr.to_ir() for instr in self.instructions] return Program(instructions=ir_instructions) - def _copy(self) -> "Circuit": + def _copy(self) -> Circuit: """ Return a shallow copy of the circuit. @@ -344,10 +346,10 @@ def _copy(self) -> "Circuit": """ return Circuit().add(self.instructions) - def __iadd__(self, addable: AddableTypes) -> "Circuit": + def __iadd__(self, addable: AddableTypes) -> Circuit: return self.add(addable) - def __add__(self, addable: AddableTypes) -> "Circuit": + def __add__(self, addable: AddableTypes) -> Circuit: new = self._copy() new.add(addable) return new diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index eae4872c..57a018b1 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.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 Dict from braket.circuits.operator import Operator @@ -74,11 +76,11 @@ def to_ir(self): Converts the operator into the canonical intermediate representation. If the operator is passed in a request, this method is called before it is passed. """ - return self.operator.to_ir(self.target) + return self._operator.to_ir([int(qubit) for qubit in self._target]) def copy( self, target_mapping: Dict[QubitInput, QubitInput] = {}, target: QubitSetInput = None - ) -> "Instruction": + ) -> Instruction: """ Return a shallow copy of the instruction. @@ -114,14 +116,14 @@ def copy( 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: - return Instruction(self.operator, target) + return Instruction(self._operator, target) else: - return Instruction(self.operator, self.target.map(target_mapping)) + return Instruction(self._operator, self._target.map(target_mapping)) def __repr__(self): - return f"Instruction('operator': {self.operator}, 'target': {self.target})" + return f"Instruction('operator': {self._operator}, 'target': {self._target})" def __eq__(self, other): if isinstance(other, Instruction): - return (self.operator, self.target) == (other.operator, other.target) + return (self._operator, self._target) == (other._operator, other._target) return NotImplemented diff --git a/src/braket/circuits/qubit.py b/src/braket/circuits/qubit.py index aaba8de4..34431a66 100644 --- a/src/braket/circuits/qubit.py +++ b/src/braket/circuits/qubit.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 Union QubitInput = Union["Qubit", int] @@ -45,7 +47,7 @@ def __str__(self): return self.__repr__() @staticmethod - def new(qubit: QubitInput) -> "Qubit": + def new(qubit: QubitInput) -> Qubit: """ Helper constructor - if input is a Qubit it returns the same value, else a new Qubit is constructed. diff --git a/src/braket/circuits/qubit_set.py b/src/braket/circuits/qubit_set.py index 46208eba..66df59a1 100644 --- a/src/braket/circuits/qubit_set.py +++ b/src/braket/circuits/qubit_set.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 Dict, Iterable, Union from boltons.setutils import IndexedSet @@ -61,7 +63,7 @@ def _flatten(other): _qubits = [Qubit.new(qubit) for qubit in _flatten(qubits)] super().__init__(_qubits) - def map(self, mapping: Dict[QubitInput, QubitInput]) -> "QubitSet": + def map(self, mapping: Dict[QubitInput, QubitInput]) -> QubitSet: """ 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. From 075510d8b9089925fd0cdd6ec3a27b8d1d160700 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Wed, 25 Mar 2020 11:32:41 -0700 Subject: [PATCH 0070/1165] removing disclaimer about annealing support (#56) --- src/braket/aws/aws_qpu.py | 2 +- src/braket/aws/aws_quantum_task.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py index 2e8c4912..c00b5246 100644 --- a/src/braket/aws/aws_qpu.py +++ b/src/braket/aws/aws_qpu.py @@ -70,7 +70,7 @@ def run( ) -> AwsQuantumTask: """ Run a quantum task specification on this quantum device. A task can be a circuit or an - annealing problem. Currently, only circuits are supported in the Private Beta. + annealing problem. Args: task_specification (Union[Circuit, Problem]): Specification of task diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 34071cd0..3ee05f3d 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -27,7 +27,7 @@ class AwsQuantumTask(QuantumTask): """Amazon Braket implementation of a quantum task. A task can be a circuit or an annealing - problem. Currently, only circuits are supported in the Private Beta.""" + problem.""" # TODO: Add API documentation that defines these states. Make it clear this is the contract. TERMINAL_STATES = {"COMPLETED", "FAILED", "CANCELLED"} From 0044859a83124c52f3fbe2962cfdaf94a42dc02a Mon Sep 17 00:00:00 2001 From: randalld-aws Date: Thu, 2 Apr 2020 16:24:44 -0700 Subject: [PATCH 0071/1165] Update README to include information about using Braket with PennyLane --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index d6bf898a..ad9f73f4 100644 --- a/README.md +++ b/README.md @@ -266,6 +266,10 @@ Specify which quantum computer hardware to use by changing the value of the `dev ### Using Amazon Braket with D-Wave QPU If you want to use [Ocean](https://docs.ocean.dwavesys.com/en/latest/) with the D-Wave QPU, you can install the [braket-ocean-python-plugin](https://github.com/aws/braket-ocean-python-plugin). Information about how to install the plugin is provided in the [README](https://github.com/aws/braket-ocean-python-plugin/blob/master/README.md) for the repo. +### Using Amazon Braket with PennyLane +To use Amazon Braket with Xanadu [PennyLane](https://pennylane.ai/), install the [Amazon Braket plugin for PennyLane](https://github.com/aws/amazon-braket-pennylane-plugin-python). +Instructions for setting up and using the plugin are provided in the README in the repository. + ### Deactivate the virtual environment After you are finished using the virtual environment to interact with Amazon Braket, you can deactivate it using the following command. From 2bb216214c8fc38231ff94d31747ee0fdb0dde4c Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Fri, 3 Apr 2020 11:36:22 -0700 Subject: [PATCH 0072/1165] Add result type, state vector, amplitude, probability (#60) --- CODEOWNERS | 1 + src/braket/circuits/__init__.py | 2 + src/braket/circuits/circuit.py | 149 ++++++++++++-- src/braket/circuits/gate.py | 5 +- src/braket/circuits/gates.py | 2 - src/braket/circuits/operator.py | 3 +- src/braket/circuits/result_type.py | 124 ++++++++++++ src/braket/circuits/result_types.py | 183 ++++++++++++++++++ src/braket/devices/device.py | 1 - .../braket/circuits/test_circuit.py | 74 ++++++- .../braket/circuits/test_result_type.py | 115 +++++++++++ .../braket/circuits/test_results.py | 80 ++++++++ 12 files changed, 701 insertions(+), 38 deletions(-) create mode 100644 src/braket/circuits/result_type.py create mode 100644 src/braket/circuits/result_types.py create mode 100644 test/unit_tests/braket/circuits/test_result_type.py create mode 100644 test/unit_tests/braket/circuits/test_results.py diff --git a/CODEOWNERS b/CODEOWNERS index 940d39da..101d3669 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -5,3 +5,4 @@ # will be requested for review when someone opens a pull request. * @floralph * @speller26 +* @avawang1 diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py index d3785ff8..2bfee590 100644 --- a/src/braket/circuits/__init__.py +++ b/src/braket/circuits/__init__.py @@ -16,6 +16,7 @@ # Execute initialization code in gates module import braket.circuits.gates as gates # noqa: F401 +import braket.circuits.result_types as result_types # noqa: F401 from braket.circuits.angled_gate import AngledGate # noqa: F401 from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram # noqa: F401 from braket.circuits.circuit import Circuit # noqa: F401 @@ -26,3 +27,4 @@ from braket.circuits.operator import Operator # 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 ResultType # noqa: F401 diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index cffc7d0e..b3392989 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -13,16 +13,19 @@ from __future__ import annotations -from typing import Callable, Dict, Iterable, TypeVar +from typing import Callable, Dict, Iterable, List, TypeVar from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram from braket.circuits.instruction import Instruction from braket.circuits.moments import Moments from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput +from braket.circuits.result_type import ResultType from braket.ir.jaqcd import Program -SubroutineReturn = TypeVar("SubroutineReturn", Iterable[Instruction], Instruction) +SubroutineReturn = TypeVar( + "SubroutineReturn", Iterable[Instruction], Instruction, ResultType, Iterable[ResultType] +) SubroutineCallable = TypeVar("SubroutineCallable", bound=Callable[..., SubroutineReturn]) AddableTypes = TypeVar("AddableTypes", SubroutineReturn, SubroutineCallable) @@ -32,7 +35,14 @@ class Circuit: """ A representation of a quantum circuit that contains the instructions to be performed on a - quantum device. See :mod:`braket.circuits.gates` module for all of the supported instructions. + quantum device and the requested result types. + + See :mod:`braket.circuits.gates` module for all of the supported instructions. + + See :mod:`braket.circuits.result_types` module for all of the supported result types. + + `AddableTypes` are `Instruction`, iterable of `Instruction`, `ResultType`, + iterable of `ResultType`, or `SubroutineCallable` """ @classmethod @@ -42,8 +52,8 @@ def register_subroutine(cls, func: SubroutineCallable) -> None: is the name of `func`. Args: - func (Callable[..., Union[Instruction, Iterable[Instruction]]]): The function of the - subroutine to add to the class. + func (Callable[..., Union[Instruction, Iterable[Instruction], ResultType, + Iterable[ResultType]]): The function of the subroutine to add to the class. Examples: >>> def h_on_all(target): @@ -73,8 +83,8 @@ def method_from_subroutine(self, *args, **kwargs) -> SubroutineReturn: def __init__(self, addable: AddableTypes = None, *args, **kwargs): """ Args: - addable (Instruction, iterable of `Instruction`, or `SubroutineCallable`, optional): The - instruction-like item(s) to add to self. Default = None. + addable (AddableTypes): The item(s) to add to self. + Default = None. *args: Variable length argument list. Supports any arguments that `add()` offers. **kwargs: Arbitrary keyword arguments. Supports any keyword arguments that `add()` offers. @@ -85,6 +95,7 @@ def __init__(self, addable: AddableTypes = None, *args, **kwargs): Examples: >>> circ = Circuit([Instruction(Gate.H(), 4), Instruction(Gate.CNot(), [4, 5])]) >>> circ = Circuit().h(0).cnot(0, 1) + >>> circ = Circuit().h(0).cnot(0, 1).probability([0, 1]) >>> @circuit.subroutine(register=True) >>> def bell_pair(target): @@ -92,8 +103,10 @@ def __init__(self, addable: AddableTypes = None, *args, **kwargs): ... >>> circ = Circuit(bell_pair, [4,5]) >>> circ = Circuit().bell_pair([4,5]) + """ self._moments: Moments = Moments() + self._result_types: Iterable[ResultType] = [] if addable is not None: self.add(addable, *args, **kwargs) @@ -108,6 +121,11 @@ def instructions(self) -> Iterable[Instruction]: """Iterable[Instruction]: Get an `iterable` of instructions in the circuit.""" return self._moments.values() + @property + def result_types(self) -> List[ResultType]: + """List[ResultType]: Get a list of requested result types in the circuit.""" + return self._result_types + @property def moments(self) -> Moments: """Moments: Get the `moments` for this circuit.""" @@ -123,6 +141,74 @@ def qubits(self) -> QubitSet: """QubitSet: Get a copy of the qubits for this circuit.""" return QubitSet(self._moments.qubits) + def add_result_type( + self, + result_type: ResultType, + target: QubitSetInput = None, + target_mapping: Dict[QubitInput, QubitInput] = {}, + ) -> Circuit: + """ + Add a requested result type to `self`, returns `self` for chaining ability. + + Args: + result_type (ResultType): `ResultType` to add into `self`. + target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the + `result_type`. + Default = None. + target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of + qubit mappings to apply to the `result_type.target`. Key is the qubit in + `result_type.target` and the value is what the key will be changed to. Default = {}. + + + Note: target and target_mapping will only be applied to those requested result types with + the attribute `target`. The result_type will be appended to the end of the list of + `circuit.result_types` only if it does not already exist in `circuit.result_types` + + Returns: + Circuit: self + + Raises: + TypeError: If both `target_mapping` and `target` are supplied. + + Examples: + >>> result_type = ResultType.Probability(target=[0, 1]) + >>> circ = Circuit().add_result_type(result_type) + >>> print(circ.result_types[0]) + Probability(target=QubitSet([Qubit(0), Qubit(1)])) + + >>> result_type = ResultType.Probability(target=[0, 1]) + >>> circ = Circuit().add_result_type(result_type, target_mapping={0: 10, 1: 11}) + >>> print(circ.result_types[0]) + Probability(target=QubitSet([Qubit(10), Qubit(11)])) + + >>> result_type = ResultType.Probability(target=[0, 1]) + >>> circ = Circuit().add_result_type(result_type, target=[10, 11]) + >>> print(circ.result_types[0]) + Probability(target=QubitSet([Qubit(10), Qubit(11)])) + + >>> result_type = ResultType.StateVector() + >>> circ = Circuit().add_result_type(result_type) + >>> print(circ.result_types[0]) + StateVector() + """ + if target_mapping and target is not None: + raise TypeError("Only one of 'target_mapping' or 'target' can be supplied.") + + if not target_mapping and not target: + # Nothing has been supplied, add result_type + result_type_to_add = result_type + elif target_mapping: + # Target mapping has been supplied, copy result_type + result_type_to_add = result_type.copy(target_mapping=target_mapping) + else: + # ResultType with target + result_type_to_add = result_type.copy(target=target) + + if result_type_to_add not in self._result_types: + self._result_types.append(result_type_to_add) + + return self + def add_instruction( self, instruction: Instruction, @@ -198,8 +284,7 @@ def add_circuit( target_mapping: Dict[QubitInput, QubitInput] = {}, ) -> Circuit: """ - Add a `circuit` to self, returns self for chaining ability. This is a composite form of - `add_instruction()` since it adds all of the instructions of `circuit` to this circuit. + Add a `circuit` to self, returns self for chaining ability. Args: circuit (Circuit): Circuit to add into self. @@ -224,6 +309,10 @@ def add_circuit( can be resource-intensive. Use `target_mapping` to use a linear runtime to remap the qubits. + Requested result types of the circuit that will be added will be appended to the end + of the list for the existing requested result types. A result type to be added that is + equivalent to an existing requested result type will not be added. + Examples: >>> widget = Circuit().h(0).cnot([0, 1]) >>> circ = Circuit().add_circuit(widget) @@ -256,18 +345,21 @@ def add_circuit( for instruction in circuit.instructions: self.add_instruction(instruction, target_mapping=target_mapping) + for result_type in circuit.result_types: + self.add_result_type(result_type, target_mapping=target_mapping) + return self def add(self, addable: AddableTypes, *args, **kwargs) -> Circuit: """ - Generic add method for adding instruction-like item(s) to self. Any arguments that - `add_circuit()` and / or `add_instruction()` supports are supported by this method. - If adding a subroutine, check with that subroutines documentation to determine what input it + 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 input it allows. Args: - addable (Instruction, iterable of Instruction, or SubroutineCallable, optional): The - instruction-like item(s) to add to self. Default = None. + addable (AddableTypes): The item(s) to add to self. Default = None. *args: Variable length argument list. **kwargs: Arbitrary keyword arguments. @@ -282,8 +374,11 @@ def add(self, addable: AddableTypes, *args, **kwargs) -> Circuit: `add_instruction()` + `add_result_type()` + Examples: >>> circ = Circuit().add([Instruction(Gate.H(), 4), Instruction(Gate.CNot(), [4, 5])]) + >>> circ = Circuit().add([ResultType.StateVector()]) >>> circ = Circuit().h(4).cnot([4, 5]) @@ -304,6 +399,8 @@ def _flatten(addable): for item in _flatten(addable): if isinstance(item, Instruction): self.add_instruction(item, *args, **kwargs) + elif isinstance(item, ResultType): + self.add_result_type(item, *args, **kwargs) elif isinstance(item, Circuit): self.add_circuit(item, *args, **kwargs) elif callable(item): @@ -335,7 +432,8 @@ def to_ir(self) -> Program: (Program): An AWS quantum circuit description program in JSON format. """ ir_instructions = [instr.to_ir() for instr in self.instructions] - return Program(instructions=ir_instructions) + ir_results = [result_type.to_ir() for result_type in self.result_types] + return Program(instructions=ir_instructions, results=ir_results) def _copy(self) -> Circuit: """ @@ -344,7 +442,9 @@ def _copy(self) -> Circuit: Returns: Circuit: A shallow copy of the circuit. """ - return Circuit().add(self.instructions) + copy = Circuit().add(self.instructions) + copy.add(self.result_types) + return copy def __iadd__(self, addable: AddableTypes) -> Circuit: return self.add(addable) @@ -354,21 +454,30 @@ def __add__(self, addable: AddableTypes) -> Circuit: new.add(addable) return new - def __repr__(self): - return f"Circuit('instructions': {list(self.instructions)})" + def __repr__(self) -> str: + if not self.result_types: + return f"Circuit('instructions': {list(self.instructions)})" + else: + return ( + f"Circuit('instructions': {list(self.instructions)}" + + f"result_types': {self.result_types})" + ) def __str__(self): return self.diagram(AsciiCircuitDiagram) def __eq__(self, other): if isinstance(other, Circuit): - return list(self.instructions) == list(other.instructions) + return ( + list(self.instructions) == list(other.instructions) + and self.result_types == self.result_types + ) return NotImplemented def subroutine(register=False): """ - Subroutine is a function that returns instructions or circuits. + Subroutine is a function that returns instructions, result types, or circuits. Args: register (bool, optional): If `True`, adds this subroutine into the `Circuit` class. diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index bb08327b..1418b1f2 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -17,9 +17,6 @@ from braket.circuits.operator import Operator from braket.circuits.qubit_set import QubitSet -# TODO: Add parameters support -# TODO: Add printing / visualization capabilities - class Gate(Operator): """ @@ -131,6 +128,6 @@ def register_gate(cls, gate: "Gate"): """Register a gate implementation by adding it into the Gate class. Args: - gate (Gate): Gate instance to register. + gate (Gate): Gate class to register. """ setattr(cls, gate.__name__, gate) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 8f4d0034..35f77163 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -23,8 +23,6 @@ from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput -# TODO: look into adding angle to diagrams - """ To add a new gate: 1. Implement the class and extend `Gate` diff --git a/src/braket/circuits/operator.py b/src/braket/circuits/operator.py index 6ff7b613..053e6b8a 100644 --- a/src/braket/circuits/operator.py +++ b/src/braket/circuits/operator.py @@ -29,5 +29,6 @@ def to_ir(self, *args, **kwargs): If the operator is passed in a request, this method is called before it is passed. Args: - target (QubitSet): Target qubits that the operator is applied to. + *args: Positional arguments + **kwargs: Keyword arguments """ diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py new file mode 100644 index 00000000..c92b29b7 --- /dev/null +++ b/src/braket/circuits/result_type.py @@ -0,0 +1,124 @@ +# Copyright 2019-2019 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, Dict + +from braket.circuits.qubit import QubitInput +from braket.circuits.qubit_set import QubitSetInput + + +class ResultType: + """ + 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_symbol: str): + """ + Args: + ascii_symbol (str): ASCII string symbol for the result type. This is used when + printing a diagram of circuits. + + Raises: + ValueError: `ascii_symbol` is None + """ + + if ascii_symbol is None: + raise ValueError(f"ascii_symbol must not be None") + + self._ascii_symbol = ascii_symbol + + @property + def ascii_symbol(self) -> str: + """str: Returns the ascii symbol for the requested result type.""" + return self._ascii_symbol + + @property + def name(self) -> str: + """ + Returns the name of the result type + + Returns: + The name of the result type as a string + """ + return self.__class__.__name__ + + def to_ir(self, *args, **kwargs) -> Any: + """Returns IR object of the result type + + Args: + *args: Positional arguments + **kwargs: Keyword arguments + + Returns: + IR object of the result type + """ + raise NotImplementedError("to_ir has not been implemented yet.") + + def copy(self, target_mapping: Dict[QubitInput, QubitInput] = {}, target: QubitSetInput = None): + """ + Return a shallow copy of the result type. + + Note: + If `target_mapping` is specified, then `self.target` is mapped to the specified + qubits. This is useful apply an instruction to a circuit and change the target qubits. + + Args: + target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of + qubit mappings to apply to the target. Key is the qubit in this `target` and the + value is what the key is changed to. Default = {}. + target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the new + instruction. + + Returns: + ResultType: A shallow copy of the result type. + + Raises: + TypeError: If both `target_mapping` and `target` are supplied. + + Examples: + >>> result_type = ResultType.Probabilities(targets=[0]) + >>> new_result_type = result_type.copy() + >>> new_result_type.targets + QubitSet(Qubit(0)) + >>> new_result = result_type.copy(target_mapping={0: 5}) + >>> new_result_type.target + QubitSet(Qubit(5)) + >>> new_result = result_type.copy(target=[5]) + >>> new_result_type.target + QubitSet(Qubit(5)) + """ + copy = self.__copy__() + 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 hasattr(copy, "target"): + copy.target = target + else: + if hasattr(copy, "target"): + copy.target = self._target.map(target_mapping) + return copy + + @classmethod + def register_result_type(cls, result_type: "ResultType"): + """Register a result type implementation by adding it into the ResultType class. + + Args: + result_type (ResultType): ResultType instance to register. + """ + setattr(cls, result_type.__name__, result_type) + + def __repr__(self) -> str: + return f"{self.name}()" diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py new file mode 100644 index 00000000..75bf3f42 --- /dev/null +++ b/src/braket/circuits/result_types.py @@ -0,0 +1,183 @@ +# Copyright 2019-2019 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 List + +import braket.ir.jaqcd as ir +from braket.circuits import circuit +from braket.circuits.qubit_set import QubitSet, QubitSetInput +from braket.circuits.result_type import ResultType + + +""" +To add a new result type: + 1. Implement the class and extend `ResultType` + 2. Add a method with the `@circuit.subroutine(register=True)` decorator. Method name + will be added into the `Circuit` class. This method is the default way + clients add this result type to a circuit. + 3. Register the class with the `ResultType` class via `ResultType.register_result_type()`. +""" + + +class StateVector(ResultType): + """The full state vector as a requested result type.""" + + def __init__(self): + super().__init__(ascii_symbol=["StateVector"]) + + def to_ir(self) -> ir.StateVector: + return ir.StateVector() + + @staticmethod + @circuit.subroutine(register=True) + def state_vector() -> ResultType: + """Registers this function into the circuit class. + + Returns: + ResultType: state vector as a requested result type + + Examples: + >>> circ = Circuit().state_vector() + """ + return ResultType.StateVector() + + def __eq__(self, other) -> bool: + if isinstance(other, StateVector): + return True + return False + + def __copy__(self) -> StateVector: + return type(self)() + + +ResultType.register_result_type(StateVector) + + +class Amplitude(ResultType): + """The amplitude of specified quantum states as a requested result type.""" + + def __init__(self, state: List[str]): + """ + Args: + state (List[str]): list of quantum states as strings with "0" and "1" + + Raises: + ValueError: If state is None or an empty list + + Examples: + >>> ResultType.Amplitude(state=['01', '10']) + """ + super().__init__(ascii_symbol=["Amplitude"]) + if not state: + raise ValueError("A non-empty list of states must be specified e.g. ['01', '10']") + self._state = state + + @property + def state(self) -> List[str]: + return self._state + + def to_ir(self) -> ir.Amplitude: + return ir.Amplitude(states=self.state) + + @staticmethod + @circuit.subroutine(register=True) + def amplitude(state: List[str]) -> ResultType: + """Registers this function into the circuit class. + + Args: + state (List[str]): list of quantum states as strings with "0" and "1" + + Returns: + ResultType: state vector as a requested result type + + Examples: + >>> circ = Circuit().amplitude(state=["01", "10"]) + """ + return ResultType.Amplitude(state=state) + + def __eq__(self, other): + if isinstance(other, Amplitude): + return self.state == other.state + return False + + def __repr__(self): + return f"Amplitude(state={self.state})" + + def __copy__(self): + return type(self)(state=self.state) + + +ResultType.register_result_type(Amplitude) + + +class Probability(ResultType): + """Probability as the requested result type. + + It can be the probability of all states if no targets are specified or the marginal probability + of a restricted set of states if only a subset of all qubits are specified as target.""" + + def __init__(self, target: QubitSetInput = []): + """ + Args: + target (int, Qubit, or iterable of int / Qubit): Target qubits that the result type + is requested for. Default is [], which means all qubits for the circuit. + + Examples: + >>> ResultType.Probability(target=[0, 1]) + """ + super().__init__(ascii_symbol=["Prob"]) + self._target = QubitSet(target) + + @property + def target(self) -> QubitSet: + return self._target + + @target.setter + def target(self, target: QubitSetInput) -> None: + self._target = QubitSet(target) + + def to_ir(self) -> ir.Probability: + return ir.Probability(targets=list(self.target)) + + @staticmethod + @circuit.subroutine(register=True) + def probability(target: QubitSetInput) -> ResultType: + """Registers this function into the circuit class. + + Args: + target (int, Qubit, or iterable of int / Qubit): Target qubits that the result type + is requested for. + + Returns: + ResultType: probability as a requested result type + + Examples: + >>> circ = Circuit().probability(target=[0, 1]) + """ + return ResultType.Probability(target=target) + + def __eq__(self, other) -> bool: + if isinstance(other, Probability): + return self.target == other.target + return False + + def __repr__(self) -> str: + return f"Probability(target={self.target})" + + def __copy__(self) -> Probability: + return type(self)(target=self.target) + + +ResultType.register_result_type(Probability) diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 189da3be..55a76d1d 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -58,7 +58,6 @@ def run( Returns: QuantumTask: The QuantumTask tracking task execution on this device """ - pass @property def name(self) -> str: diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index a57c587b..b014a309 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -22,6 +22,7 @@ Instruction, Moments, QubitSet, + ResultType, circuit, ) @@ -47,27 +48,47 @@ def h_instr(): @pytest.fixture -def bell_pair(): +def prob(): + return ResultType.Probability([0, 1]) + + +@pytest.fixture +def cnot_prob(cnot_instr, prob): + return Circuit().add_result_type(prob).add_instruction(cnot_instr) + + +@pytest.fixture +def bell_pair(prob): return ( Circuit() .add_instruction(Instruction(Gate.H(), 0)) .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_result_type(prob) ) -def test_repr(h): +def test_repr_instructions(h): expected = f"Circuit('instructions': {list(h.instructions)})" assert repr(h) == expected +def test_repr_result_types(cnot_prob): + circuit = cnot_prob + expected = ( + f"Circuit('instructions': {list(circuit.instructions)}" + + f"result_types': {circuit.result_types})" + ) + assert repr(circuit) == expected + + def test_str(h): expected = AsciiCircuitDiagram.build_diagram(h) assert str(h) == expected def test_equality(): - circ_1 = Circuit().h(0) - circ_2 = Circuit().h(0) + circ_1 = Circuit().h(0).probability([0, 1]) + circ_2 = Circuit().h(0).probability([0, 1]) other_circ = Circuit().h(1) non_circ = "non circuit" @@ -77,6 +98,34 @@ def test_equality(): assert circ_1 != non_circ +def test_add_result_type_default(prob): + circ = Circuit().add_result_type(prob) + assert list(circ.result_types) == [prob] + + +def test_add_result_type_with_mapping(prob): + expected = [ResultType.Probability([10, 11])] + circ = Circuit().add_result_type(prob, target_mapping={0: 10, 1: 11}) + assert list(circ.result_types) == expected + + +def test_add_result_type_with_target(prob): + expected = [ResultType.Probability([10, 11])] + circ = Circuit().add_result_type(prob, target=[10, 11]) + assert list(circ.result_types) == expected + + +def test_add_result_type_already_exists(): + expected = [ResultType.StateVector()] + circ = Circuit(expected).add_result_type(expected[0]) + assert list(circ.result_types) == expected + + +@pytest.mark.xfail(raises=TypeError) +def test_add_result_type_with_target_and_mapping(prob): + Circuit().add_result_type(prob, target=[10], target_mapping={0: 10}) + + def test_add_instruction_default(cnot_instr): circ = Circuit().add_instruction(cnot_instr) assert list(circ.instructions) == [cnot_instr] @@ -116,6 +165,7 @@ def test_add_circuit_with_mapping(bell_pair): Circuit() .add_instruction(Instruction(Gate.H(), 10)) .add_instruction(Instruction(Gate.CNot(), [10, 11])) + .add_result_type(ResultType.Probability([10, 11])) ) assert circ == expected @@ -126,6 +176,7 @@ def test_add_circuit_with_target(bell_pair): Circuit() .add_instruction(Instruction(Gate.H(), 10)) .add_instruction(Instruction(Gate.CNot(), [10, 11])) + .add_result_type(ResultType.Probability([10, 11])) ) assert circ == expected @@ -194,7 +245,7 @@ def test_iadd_operator(cnot_instr, h): assert circ == Circuit().add(h).add(cnot_instr).add(h).add(cnot_instr) -def test_add_operator(h, cnot, bell_pair): +def test_add_operator(h, bell_pair): addition = h + bell_pair + h + h expected = Circuit().add(h).add(bell_pair).add(h).add(h) @@ -263,14 +314,17 @@ def h_nested(target): assert circ == expected -def test_ir_empty_instructions(): +def test_ir_empty_instructions_result_types(): circ = Circuit() - assert circ.to_ir() == jaqcd.Program(instructions=[]) + assert circ.to_ir() == jaqcd.Program(instructions=[], results=[]) -def test_ir_non_empty_instructions(): - circ = Circuit().h(0).cnot(0, 1) - expected = jaqcd.Program(instructions=[jaqcd.H(target=0), jaqcd.CNot(control=0, target=1)]) +def test_ir_non_empty_instructions_result_types(): + circ = Circuit().h(0).cnot(0, 1).probability([0, 1]) + expected = jaqcd.Program( + instructions=[jaqcd.H(target=0), jaqcd.CNot(control=0, target=1)], + results=[jaqcd.Probability(targets=[0, 1])], + ) assert circ.to_ir() == expected diff --git a/test/unit_tests/braket/circuits/test_result_type.py b/test/unit_tests/braket/circuits/test_result_type.py new file mode 100644 index 00000000..7d68e58e --- /dev/null +++ b/test/unit_tests/braket/circuits/test_result_type.py @@ -0,0 +1,115 @@ +# Copyright 2019-2019 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 ResultType + + +@pytest.fixture +def result_type(): + return ResultType(ascii_symbol="foo") + + +@pytest.fixture +def prob(): + return ResultType.Probability([0, 1]) + + +@pytest.fixture +def sv(): + return ResultType.StateVector() + + +@pytest.mark.xfail(raises=ValueError) +def test_none_ascii(): + ResultType(ascii_symbol=None) + + +def test_name(result_type): + expected = result_type.__class__.__name__ + assert result_type.name == expected + + +def test_ascii_symbol(): + ascii_symbol = "foo" + result_type = ResultType(ascii_symbol=ascii_symbol) + assert result_type.ascii_symbol == ascii_symbol + + +def test_equality(): + result1 = ResultType.StateVector() + result2 = ResultType.StateVector() + result3 = ResultType.Probability([1]) + result4 = "hi" + assert result1 == result2 + assert result1 != result3 + assert result1 != result4 + + +@pytest.mark.xfail(raises=AttributeError) +def test_ascii_symbol_setter(result_type): + result_type.ascii_symbol = "bar" + + +@pytest.mark.xfail(raises=AttributeError) +def test_name_setter(result_type): + result_type.name = "hi" + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_to_ir_not_implemented_by_default(result_type): + result_type.to_ir(None) + + +def test_register_result(): + class _FooResultType(ResultType): + def __init__(self): + super().__init__(ascii_symbol="foo") + + ResultType.register_result_type(_FooResultType) + assert ResultType._FooResultType().name == _FooResultType().name + + +def test_copy_creates_new_object(prob): + copy = prob.copy() + assert copy == prob + assert copy is not prob + + +def test_copy_with_mapping_target(sv): + target_mapping = {0: 10, 1: 11} + expected = ResultType.StateVector() + assert sv.copy(target_mapping=target_mapping) == expected + + +def test_copy_with_mapping_target_hasattr(prob): + target_mapping = {0: 10, 1: 11} + expected = ResultType.Probability([10, 11]) + assert prob.copy(target_mapping=target_mapping) == expected + + +def test_copy_with_target_hasattr(prob): + target = [10, 11] + expected = ResultType.Probability(target) + assert prob.copy(target=target) == expected + + +def test_copy_with_target(sv): + target = [10, 11] + expected = ResultType.StateVector() + assert sv.copy(target=target) == expected + + +@pytest.mark.xfail(raises=TypeError) +def test_copy_with_target_and_mapping(prob): + prob.copy(target=[10], target_mapping={0: 10}) diff --git a/test/unit_tests/braket/circuits/test_results.py b/test/unit_tests/braket/circuits/test_results.py new file mode 100644 index 00000000..bfe2bd74 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_results.py @@ -0,0 +1,80 @@ +# Copyright 2019-2019 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 braket.ir.jaqcd as ir +import pytest +from braket.circuits import Circuit, ResultType + +testdata = [ + (ResultType.StateVector, "state_vector", ir.StateVector, {}, {}), + (ResultType.Amplitude, "amplitude", ir.Amplitude, {"state": ["0"]}, {"states": ["0"]}), + ( + ResultType.Probability, + "probability", + ir.Probability, + {"target": [0, 1]}, + {"targets": [0, 1]}, + ), +] + + +@pytest.mark.parametrize("testclass,subroutine_name,irclass,input,ir_input", testdata) +def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): + expected = irclass(**ir_input) + actual = testclass(**input).to_ir() + assert actual == expected + + +@pytest.mark.parametrize("testclass,subroutine_name,irclass,input,ir_input", testdata) +def test_result_subroutine(testclass, subroutine_name, irclass, input, ir_input): + subroutine = getattr(Circuit(), subroutine_name) + assert subroutine(**input) == Circuit(testclass(**input)) + + +@pytest.mark.parametrize("testclass,subroutine_name,irclass,input,ir_input", testdata) +def test_result_equality(testclass, subroutine_name, irclass, input, ir_input): + a1 = testclass(**input) + a2 = a1.copy() + assert a1 == a2 + assert a1 is not a2 + + +# Amplitude + + +@pytest.mark.xfail(raises=ValueError) +def test_amplitude_init_value_error(): + ResultType.Amplitude(state=None) + + +def test_amplitude_equality(): + a1 = ResultType.Amplitude(state=["0", "1"]) + a2 = ResultType.Amplitude(state=["0", "1"]) + a3 = ResultType.Amplitude(state=["01", "11", "10"]) + a4 = "hi" + assert a1 == a2 + assert a1 != a3 + assert a1 != a4 + + +# Probability + + +def test_probability_equality(): + a1 = ResultType.Probability([0, 1]) + a2 = ResultType.Probability([0, 1]) + a3 = ResultType.Probability([0, 1, 2]) + a4 = "hi" + assert a1 == a2 + assert a1 != a3 + assert a1 != a4 From 771bb955fea6ebbd15881a151a12dbf3180e9cb1 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 3 Apr 2020 14:21:59 -0600 Subject: [PATCH 0073/1165] Make LocalQuantumTask.result not a property (#63) --- src/braket/tasks/local_quantum_task.py | 1 - test/unit_tests/braket/devices/test_local_simulator.py | 6 +++--- test/unit_tests/braket/tasks/test_local_quantum_task.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py index c6af7223..fddae760 100644 --- a/src/braket/tasks/local_quantum_task.py +++ b/src/braket/tasks/local_quantum_task.py @@ -38,7 +38,6 @@ def cancel(self) -> None: def state(self) -> str: return "COMPLETED" - @property def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: return self._result diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 5066c7f5..cd1c83db 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -72,7 +72,7 @@ def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> Dict[str, Any]: def test_load_from_entry_point(): sim = LocalSimulator("dummy") task = sim.run(Circuit().h(0).cnot(0, 1), 10) - assert task.result == GateModelQuantumTaskResult.from_dict(GATE_MODEL_RESULT) + assert task.result() == GateModelQuantumTaskResult.from_dict(GATE_MODEL_RESULT) def test_run_gate_model(): @@ -81,13 +81,13 @@ def test_run_gate_model(): task = sim.run(Circuit().h(0).cnot(0, 1), 10) dummy.assert_shots(10) dummy.assert_qubits(2) - assert task.result == GateModelQuantumTaskResult.from_dict(GATE_MODEL_RESULT) + assert task.result() == GateModelQuantumTaskResult.from_dict(GATE_MODEL_RESULT) def test_run_annealing(): sim = LocalSimulator(DummyAnnealingSimulator()) task = sim.run(Problem(ProblemType.ISING)) - assert task.result == AnnealingQuantumTaskResult.from_dict(ANNEALING_RESULT) + assert task.result() == AnnealingQuantumTaskResult.from_dict(ANNEALING_RESULT) def test_registered_backends(): 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 0392c9b4..1b0c6ce9 100644 --- a/test/unit_tests/braket/tasks/test_local_quantum_task.py +++ b/test/unit_tests/braket/tasks/test_local_quantum_task.py @@ -43,7 +43,7 @@ def test_state(): def test_result(): - assert TASK.result == RESULT + assert TASK.result() == RESULT @pytest.mark.xfail(raises=NotImplementedError) From 970a36459076d1fb06f5533efe56da690981ffac Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Tue, 7 Apr 2020 16:19:11 -0700 Subject: [PATCH 0074/1165] Add amazon-braket-default-simulator-python (#64) Added amazon-braket-default-simulator-python as an installation requirement. This would allow users to simulate quantum circuits locally using the LocalSimulator interface. --- README.md | 32 +++++++++++++++---- examples/local_bell.py | 7 ++++ setup.py | 4 +++ src/braket/aws/aws_quantum_task.py | 1 - src/braket/devices/local_simulator.py | 23 +++++++++---- src/braket/tasks/local_quantum_task.py | 3 ++ src/braket/tasks/quantum_task.py | 2 ++ .../braket/tasks/test_local_quantum_task.py | 5 +++ 8 files changed, 62 insertions(+), 15 deletions(-) create mode 100644 examples/local_bell.py diff --git a/README.md b/README.md index ad9f73f4..bf9798e1 100644 --- a/README.md +++ b/README.md @@ -119,20 +119,23 @@ The easiest way to get the SDKs is to download them directly from the GitHub sit Use the following links to download the Amazon Braket Python SDK repos: - [braket-python-ir](https://github.com/aws/braket-python-ir/archive/stable/latest.zip) +- [amazon-braket-default-simulator-python](https://github.com/aws/amazon-braket-default-simulator-python/archive/stable/latest.zip) - [braket-python-sdk](https://github.com/aws/braket-python-sdk/archive/stable/latest.zip) ### Extract the SDK .zip files Because the files were downloaded directly from GitHub, the folder in the .zip file includes the name of the branch of the GitHub repo that was downloaded, in this case the `stable/latest` branch. But to use the files in the SDK, we need to rename the folder to the original name. -Note: Make sure you are always using the branch 'stable/latest' and not 'master' for the SDK. 'master' may contain unstable changes. +Note: Make sure you are always using the branch 'stable/latest' and not 'master'. The 'master' branch may contain in-progress changes that result in errors. **To rename the folders in the SDK .zip files** -First, extract the .zip files to a location of your choosing. Then open the location where you extracted the folders to. You can use either the GUI file system tools in your OS, or the command line. You should see 2 folders with the following names: +First, extract the .zip files to a location of your choosing. Then open the location where you extracted the folders to. You can use either the GUI file system tools in your OS, or the command line. You should see 3 folders with the following names: - braket-python-ir-stable-latest +- amazon-braket-default-simulator-python-stable-latest - braket-python-sdk-stable-latest Rename the folders to the following: - braket-python-ir +- amazon-braket-default-simulator-python - braket-python-sdk Then copy the renamed files and paste them into the `braketvirtenv` folder where you created a virtual environment. Your folder structure should look like this: @@ -147,6 +150,10 @@ Use the following commands to install the SDKs in the order that they appear: pip install -e braket-python-ir ``` +```bash +pip install -e amazon-braket-default-simulator-python +``` + ```bash pip install -e braket-python-sdk ``` @@ -190,7 +197,7 @@ The code sample imports the Amazon Braket framework, then defines the execution There are currently three simulators available for Amazon Braket. To specify which simulator to use, change the code sample to replace the value for the `AwsQuantumSimulator` to one of the following values: - `arn:aws:aqx:::quantum-simulator:aqx:qs1` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the state vector and outputs an array of bit strings that appears as though it came from a quantum computer. Does not provide a state vector. - `arn:aws:aqx:::quantum-simulator:aqx:qs2` – a tensor network simulator. Provides an approximation of running a job on a quantum computer. -- `arn:aws:aqx:::quantum-simulator:aqx:qs3` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples from the state vector but includes the entire state vector. This generates more data, and therefore incurs additional costs for storage of data in Amazon S3. +- `arn:aws:aqx:::quantum-simulator:aqx:qs3` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples from the state vector but includes the entire state vector. This generates more data, and therefore incurs additional costs for storage of data in Amazon S3. #### To validate your configuration using a Python file 1. Open a text editor with example file `../braket-python-sdk/examples/bell.py`. @@ -236,6 +243,10 @@ When the job completes, you should see output similar to the following: **Important** Tasks may not run immediately on the QPU. IonQ runs tasks once every 24 hours. Rigetti tasks run when the QPU is available, with times varying day to day. +#### To validate your quantum algorithm locally + +Braket Python SDK comes bundled with an implementation of a quantum simulator that you can run locally. You can use the local simulator to test quantum tasks constructed using the SDK before you submit them to the Amazon Braket service for execution. An example of how to execute the task locally is included in the repo `../examples/local_bell.py`. + #### Debugging logs Tasks sent to QPUs don't always run right away. For IonQ, jobs are run once every 24 hours. For Rigetti, tasks are queued and run when the QPU is available, with the time varying day to day. To view task status, you can enable debugging logs. An example of how to enable these logs is included in repo: `../examples/debug_bell.py`. This example enables task logging so that status updates are continuously printed to console after a quantum task is executed. The logs can also be configured to save to a file or output to another stream. You can use the debugging example to get information on the tasks you submit, such as the current status, so that you know when your task completes. @@ -293,14 +304,21 @@ When you want to use it again, you can reactivate it with the same command you u We will periodically make updates and changes the SDK or the model. When you are notified of a change that requires action on your part, use the following steps to update your environment to the latest version. ### Check the version you have installed -You can view the version of the braket-python-sdk that you have installed by using the following command in the virtual environment: +You can view the version of the braket packages that you have installed by using the following commands in the virtual environment: ```bash +pip show amazon-braket-default-simulator-python +pip show braket-ir pip show braket-sdk ``` -Compare the version displayed in your local environment with the latest version listed in the [Releases](https://github.com/aws/braket-python-sdk/releases) page. If the version listed is higher than your local version, you should update to the latest release. +Compare the version displayed in your local environment with the latest version listed for each of the following release pages: +- [amazon-braket-default-simulator-python](https://github.com/aws/amazon-braket-default-simulator-python/releases) +- [braket-python-ir](https://github.com/aws/braket-python-ir/releases) +- [braket-python-sdk](https://github.com/aws/braket-python-sdk/releases) + +If the version listed is higher than your local version, you should update to the latest release. ### To get the lastest updates -Perform the steps described in the [Setting up the Amazon Braket Python SDKs](https://github.com/aws/braket-python-sdk/tree/stable/latest#setting-up-the-amazon-braket-python-sdks) section of this document. The links in that section point to the most recent version of the braket-python-sdk, braket-python-ir, and model file you need to set up the new version of the SDK. +Perform the steps described in the [Setting up the Amazon Braket Python SDKs](https://github.com/aws/braket-python-sdk/tree/stable/latest#setting-up-the-amazon-braket-python-sdks) section of this document. The links in that section point to the most recent version of the braket-python-sdk, braket-python-ir, amazon-braket-default-simulator-python, and model file you need to set up the new version of the SDK. You can extract the file to the same location you are using and replace the existing files with the updated SDK. This lets you continue to use the same virtual environment. @@ -327,7 +345,7 @@ tox -e docs ``` To view the generated documentation, open the following file in a browser: -../braket-python-sdk/build/documentation/html/index.html` +`../braket-python-sdk/build/documentation/html/index.html` ## Install the SDK for Testing Make sure to install test dependencies first: diff --git a/examples/local_bell.py b/examples/local_bell.py new file mode 100644 index 00000000..f46b4815 --- /dev/null +++ b/examples/local_bell.py @@ -0,0 +1,7 @@ +from braket.circuits import Circuit +from braket.devices import LocalSimulator + +device = LocalSimulator() + +bell = Circuit().h(0).cnot(0, 1) +print(device.run(bell).result().measurement_counts) diff --git a/setup.py b/setup.py index 7255efcb..7cbfb7b3 100644 --- a/setup.py +++ b/setup.py @@ -22,6 +22,10 @@ package_dir={"": "src"}, install_requires=[ "braket-ir @ git+https://github.com/aws/braket-python-ir.git", + ( + "amazon-braket-default-simulator-python @" + "git+https://github.com/aws/amazon-braket-default-simulator-python.git" + ), "boltons", "boto3", "nest-asyncio", diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 3ee05f3d..a99ab756 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -35,7 +35,6 @@ class AwsQuantumTask(QuantumTask): GATE_IR_TYPE = "jaqcd" ANNEALING_IR_TYPE = "annealing" - DEFAULT_SHOTS = 1_000 DEFAULT_RESULTS_POLL_TIMEOUT = 120 DEFAULT_RESULTS_POLL_INTERVAL = 0.25 diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 63237ccb..73fb4bde 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. from functools import singledispatch -from typing import Set, Union +from typing import Optional, Set, Union import pkg_resources @@ -35,11 +35,12 @@ class LocalSimulator(Device): results using constructs from the SDK rather than Braket IR. """ - def __init__(self, backend: Union[str, BraketSimulator]): + def __init__(self, backend: Union[str, BraketSimulator] = "default"): """ Args: backend (Union[str, BraketSimulator]): The name of the simulator backend or - the actual simulator instance to use for simulation + the actual simulator instance to use for simulation. Defaults to the + "default" simulator backend name. """ delegate = _get_simulator(backend) super().__init__( @@ -66,6 +67,11 @@ def run( Note: If running a circuit, the number of qubits will be passed to the backend as the argument after the circuit itself. + + Examples: + >>> circuit = Circuit().h(0).cnot(0, 1) + >>> device = LocalSimulator("default") + >>> device.run(circuit, shots=1000) """ result = _run_internal(task_specification, self._delegate, *args, **kwargs) return LocalQuantumTask(result) @@ -101,20 +107,23 @@ def _(backend_impl: BraketSimulator): @singledispatch -def _run_internal(task_specification, simulator: BraketSimulator, *args, **kwargs): +def _run_internal( + task_specification, simulator: BraketSimulator, shots: Optional[int] = None, *args, **kwargs +): raise NotImplementedError("Unsupported task type") @_run_internal.register -def _(circuit: Circuit, simulator: BraketSimulator, *args, **kwargs): +def _(circuit: Circuit, simulator: BraketSimulator, shots: Optional[int] = None, *args, **kwargs): program = circuit.to_ir() qubits = circuit.qubit_count - results_dict = simulator.run(program, qubits, *args, **kwargs) + shots_count = shots if shots else LocalQuantumTask.DEFAULT_SHOTS + results_dict = simulator.run(program, qubits, shots=shots_count, *args, **kwargs) return GateModelQuantumTaskResult.from_dict(results_dict) @_run_internal.register -def _(problem: Problem, simulator: BraketSimulator, *args, **kwargs): +def _(problem: Problem, simulator: BraketSimulator, shots: Optional[int] = None, *args, **kwargs): ir = problem.to_ir() results_dict = simulator.run(ir, *args, *kwargs) return AnnealingQuantumTaskResult.from_dict(results_dict) diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py index fddae760..2b6f2c6f 100644 --- a/src/braket/tasks/local_quantum_task.py +++ b/src/braket/tasks/local_quantum_task.py @@ -44,3 +44,6 @@ def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult def async_result(self) -> asyncio.Task: # TODO: Allow for asynchronous simulation raise NotImplementedError("Asynchronous local simulation unsupported") + + def __repr__(self) -> str: + return f"LocalQuantumTask('id':{self.id})" diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py index 99b03751..e1e8d787 100644 --- a/src/braket/tasks/quantum_task.py +++ b/src/braket/tasks/quantum_task.py @@ -22,6 +22,8 @@ class QuantumTask(ABC): """An abstraction over a quantum task on a quantum device.""" + DEFAULT_SHOTS = 1_000 + @property @abstractmethod def id(self) -> str: 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 1b0c6ce9..58a17157 100644 --- a/test/unit_tests/braket/tasks/test_local_quantum_task.py +++ b/test/unit_tests/braket/tasks/test_local_quantum_task.py @@ -54,3 +54,8 @@ def test_cancel(): @pytest.mark.xfail(raises=NotImplementedError) def test_async(): TASK.async_result() + + +def test_str(): + expected = "LocalQuantumTask('id':{})".format(TASK.id) + assert str(TASK) == expected From a35dc5f3f12fcedd187b79f21e6dcff6b6ed7586 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Thu, 9 Apr 2020 10:03:08 -0700 Subject: [PATCH 0075/1165] Add observables, result types sample, expectation, and variance (#65) --- src/braket/circuits/__init__.py | 3 + src/braket/circuits/angled_gate.py | 3 + src/braket/circuits/gate.py | 82 +----- src/braket/circuits/gates.py | 18 +- src/braket/circuits/instruction.py | 13 +- src/braket/circuits/observable.py | 60 +++++ src/braket/circuits/observables.py | 233 ++++++++++++++++ src/braket/circuits/operator.py | 2 +- src/braket/circuits/quantum_operator.py | 107 ++++++++ .../circuits/quantum_operator_helpers.py | 74 +++++ src/braket/circuits/qubit_set.py | 7 +- src/braket/circuits/result_types.py | 253 +++++++++++++++++- test/unit_tests/braket/circuits/test_gate.py | 48 +--- .../braket/circuits/test_observable.py | 117 ++++++++ .../braket/circuits/test_observables.py | 124 +++++++++ .../braket/circuits/test_quantum_operator.py | 112 ++++++++ .../circuits/test_quantum_operator_helpers.py | 55 ++++ .../braket/circuits/test_result_types.py | 194 ++++++++++++++ .../braket/circuits/test_results.py | 80 ------ 19 files changed, 1359 insertions(+), 226 deletions(-) create mode 100644 src/braket/circuits/observable.py create mode 100644 src/braket/circuits/observables.py create mode 100644 src/braket/circuits/quantum_operator.py create mode 100644 src/braket/circuits/quantum_operator_helpers.py create mode 100644 test/unit_tests/braket/circuits/test_observable.py create mode 100644 test/unit_tests/braket/circuits/test_observables.py create mode 100644 test/unit_tests/braket/circuits/test_quantum_operator.py create mode 100644 test/unit_tests/braket/circuits/test_quantum_operator_helpers.py create mode 100644 test/unit_tests/braket/circuits/test_result_types.py delete mode 100644 test/unit_tests/braket/circuits/test_results.py diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py index 2bfee590..d049ba44 100644 --- a/src/braket/circuits/__init__.py +++ b/src/braket/circuits/__init__.py @@ -16,6 +16,7 @@ # Execute initialization code in gates module import braket.circuits.gates as gates # noqa: F401 +import braket.circuits.observables as observables # noqa: F401 import braket.circuits.result_types as result_types # noqa: F401 from braket.circuits.angled_gate import AngledGate # noqa: F401 from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram # noqa: F401 @@ -24,7 +25,9 @@ from braket.circuits.gate import Gate # noqa: F401 from braket.circuits.instruction import Instruction # noqa: F401 from braket.circuits.moments import Moments, MomentsKey # noqa: F401 +from braket.circuits.observable import Observable # noqa: F401 from braket.circuits.operator import Operator # 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 ResultType # noqa: F401 diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index bf9545a5..0ab7657f 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -51,3 +51,6 @@ def angle(self) -> float: angle (float): The angle of the gate in radians """ return self._angle + + def __repr__(self): + return f"{self.name}('angle': {self.angle}, 'qubit_count': {self.qubit_count})" diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index 1418b1f2..78508239 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -11,14 +11,13 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import Any, Sequence, Tuple +from typing import Any, Sequence -import numpy as np -from braket.circuits.operator import Operator +from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.qubit_set import QubitSet -class Gate(Operator): +class Gate(QuantumOperator): """ 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 @@ -40,87 +39,24 @@ def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): ValueError: `qubit_count` is less than 1, `ascii_symbols` are None, or `ascii_symbols` length != `qubit_count` """ - - if qubit_count < 1: - raise ValueError(f"qubit_count, {qubit_count}, must be greater than zero") - self._qubit_count = qubit_count - - if ascii_symbols is None: - raise ValueError(f"ascii_symbols must not be None") - - if len(ascii_symbols) != qubit_count: - msg = f"ascii_symbols, {ascii_symbols}, length must equal qubit_count, {qubit_count}" - raise ValueError(msg) - self._ascii_symbols = tuple(ascii_symbols) - - @property - def qubit_count(self) -> int: - """int: Returns number of qubits this gate interacts with.""" - return self._qubit_count - - @property - def ascii_symbols(self) -> Tuple[str]: - """Tuple[str]: Returns the ascii symbols for the gate.""" - return self._ascii_symbols - - @property - def name(self) -> str: - """ - Returns the name of the gate - - Returns: - The name of the gate as a string - """ - return self.__class__.__name__ + super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) def to_ir(self, target: QubitSet) -> Any: - """Returns IR object of gate and target + """Returns IR object of quantum operator and target Args: target (QubitSet): target qubit(s) Returns: - IR object of the gate and target + IR object of the quantum operator and target """ raise NotImplementedError("to_ir has not been implemented yet.") - def to_matrix(self, *args, **kwargs) -> Any: - """Returns a matrix representation of the gate - - Returns: - np.ndarray: A matrix representation of the gate - """ - raise NotImplementedError("to_matrix has not been implemented yet.") - def __eq__(self, other): - if not isinstance(other, Gate): - return NotImplemented - if self.name == other.name: - if hasattr(self, "angle"): - return self.angle == other.angle - return True - else: - return False - - def matrix_equivalence(self, other): - """ - Return if the matrix form of two gates are equivalent - - Args: - other (Gate): Gate instance to compare this gate to - - Returns: - True if matrix forms of this gate and the gate compared to are equivalent - """ - if not isinstance(other, Gate): - return NotImplemented - try: - return np.allclose(self.to_matrix(), other.to_matrix()) - except ValueError: - return False + if isinstance(other, Gate): + return self.name == other.name + return NotImplemented def __repr__(self): - if hasattr(self, "angle"): - return f"{self.name}('angle': {self.angle}, 'qubit_count': {self.qubit_count})" return f"{self.name}('qubit_count': {self.qubit_count})" @classmethod diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 35f77163..91c234b8 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -20,6 +20,10 @@ from braket.circuits.angled_gate import AngledGate from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction +from braket.circuits.quantum_operator_helpers import ( + is_unitary, + verify_quantum_operator_matrix_dimensions, +) from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput @@ -1278,17 +1282,11 @@ class Unitary(Gate): """ def __init__(self, matrix: np.ndarray, display_name: str = "U"): - if len(matrix.shape) != 2 or matrix.shape[0] != matrix.shape[1]: - raise ValueError(f"{matrix} is not a two-dimensional square matrix") - + verify_quantum_operator_matrix_dimensions(matrix) self._matrix = np.array(matrix, dtype=complex) qubit_count = int(np.log2(self._matrix.shape[0])) - if 2 ** qubit_count != self._matrix.shape[0] or qubit_count < 1: - raise ValueError( - f"`matrix` dimension {self._matrix.shape[0]} is not a positive exponent of 2" - ) - if not Unitary._is_unitary(self._matrix): + if not is_unitary(self._matrix): raise ValueError(f"{self._matrix} is not unitary") super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) @@ -1302,10 +1300,6 @@ def to_ir(self, target: QubitSet): matrix=Unitary._transform_matrix_to_ir(self._matrix), ) - @staticmethod - def _is_unitary(matrix: np.ndarray): - return np.allclose(np.eye(len(matrix)), matrix.dot(matrix.T.conj())) - @staticmethod def _transform_matrix_to_ir(matrix: np.ndarray): return [[[element.real, element.imag] for element in row] for row in matrix.tolist()] diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index 57a018b1..b6c2feb6 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -15,23 +15,28 @@ from typing import Dict -from braket.circuits.operator import Operator +from braket.circuits.gate import Gate from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput # TODO: Add parameters support # TODO: Rename to QuantumInstruction, and change Operator to Gate, then rename "target" to "qubits" +# InstructionOperator is a type alias, and it can be expanded to include other operators +InstructionOperator = Gate + class Instruction: """ An instruction is a quantum directive that describes the task to perform on a quantum device. """ - def __init__(self, operator: Operator, target: QubitSetInput): + def __init__(self, operator: InstructionOperator, target: QubitSetInput): """ + InstructionOperator includes objects of type Gate only. + Args: - operator (Operator): Operator for the instruction. + operator (InstructionOperator): Operator for the instruction. target (int, Qubit, or iterable of int / Qubit): Target qubits that the operator is applied to. @@ -57,7 +62,7 @@ def __init__(self, operator: Operator, target: QubitSetInput): self._target = QubitSet(target) @property - def operator(self) -> Operator: + def operator(self) -> InstructionOperator: """Operator: The operator for the instruction, for example, `Gate`.""" return self._operator diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py new file mode 100644 index 00000000..113ad67e --- /dev/null +++ b/src/braket/circuits/observable.py @@ -0,0 +1,60 @@ +# Copyright 2019-2019 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 List, Sequence, Union + +from braket.circuits.quantum_operator import QuantumOperator + + +class Observable(QuantumOperator): + """ + 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. + """ + + def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): + super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + + def to_ir(self) -> List[Union[str, List[List[List[float]]]]]: + """List[Union[str, List[List[List[float]]]]]: Returns the IR + representation for the observable""" + raise NotImplementedError + + @classmethod + def register_observable(cls, observable: Observable): + """Register an observable implementation by adding it into the Observable class. + + Args: + observable (Observable): Observable class to register. + """ + setattr(cls, observable.__name__, observable) + + def __matmul__(self, other) -> Observable.TensorProduct: + if isinstance(other, Observable.TensorProduct): + return other.__rmatmul__(self) + + if isinstance(other, Observable): + return Observable.TensorProduct([self, other]) + + raise ValueError("Can only perform tensor products between observables.") + + def __repr__(self) -> str: + return f"{self.name}('qubit_count': {self.qubit_count})" + + def __eq__(self, other) -> bool: + if isinstance(other, Observable): + return self.name == other.name + return NotImplemented diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py new file mode 100644 index 00000000..81c4df09 --- /dev/null +++ b/src/braket/circuits/observables.py @@ -0,0 +1,233 @@ +# Copyright 2019-2019 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 functools +from typing import List, Tuple + +import numpy as np +from braket.circuits.observable import Observable +from braket.circuits.quantum_operator_helpers import ( + is_hermitian, + verify_quantum_operator_matrix_dimensions, +) + + +class H(Observable): + """Hadamard operation as an observable.""" + + def __init__(self): + """ + Examples: + >>> Observable.I() + """ + super().__init__(qubit_count=1, ascii_symbols=["H"]) + + def to_ir(self) -> List[str]: + return ["h"] + + def to_matrix(self) -> np.ndarray: + return 1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) + + +Observable.register_observable(H) + + +class I(Observable): # noqa: E742, E261 + """Identity operation as an observable.""" + + def __init__(self): + """ + Examples: + >>> Observable.I() + """ + super().__init__(qubit_count=1, ascii_symbols=["I"]) + + def to_ir(self) -> List[str]: + return ["i"] + + def to_matrix(self) -> np.ndarray: + return np.array([[1.0, 0.0], [0.0, 1.0]], dtype=complex) + + +Observable.register_observable(I) + + +class X(Observable): + """Pauli-X operation as an observable.""" + + def __init__(self): + """ + Examples: + >>> Observable.X() + """ + super().__init__(qubit_count=1, ascii_symbols=["X"]) + + def to_ir(self) -> List[str]: + return ["x"] + + def to_matrix(self) -> np.ndarray: + return np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) + + +Observable.register_observable(X) + + +class Y(Observable): + """Pauli-Y operation as an observable.""" + + def __init__(self): + """ + Examples: + >>> Observable.Y() + """ + super().__init__(qubit_count=1, ascii_symbols=["Y"]) + + def to_ir(self) -> List[str]: + return ["y"] + + def to_matrix(self) -> np.ndarray: + return np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) + + +Observable.register_observable(Y) + + +class Z(Observable): + """Pauli-Z operation as an observable.""" + + def __init__(self): + """ + Examples: + >>> Observable.Z() + """ + super().__init__(qubit_count=1, ascii_symbols=["Z"]) + + def to_ir(self) -> List[str]: + return ["z"] + + def to_matrix(self) -> np.ndarray: + return np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) + + +Observable.register_observable(Z) + + +class TensorProduct(Observable): + """Tensor product of observables""" + + def __init__(self, observables: List[Observable]): + """ + Args: + observables (List[Observable]): List of observables for tensor product + + Examples: + >>> t1 = Observable.Y() @ Observable.X() + >>> 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.observables + (Z('qubit_count': 1), Y('qubit_count': 1), X('qubit_count': 1)) + + Note: list of observables for tensor product must be given in the desired order that + the tensor product will be calculated. For `TensorProduct(observables=[ob1, ob2, ob3])`, + the tensor product's matrix will be the result of the tensor product of `ob1`, `ob2`, + `ob3`, or `np.kron(np.kron(ob1.to_matrix(), ob2.to_matrix()), ob3.to_matrix())` + """ + self._observables = tuple(observables) + qubit_count = sum([obs.qubit_count for obs in observables]) + display_name = "@".join([obs.ascii_symbols[0] for obs in observables]) + super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) + + def to_ir(self) -> List[str]: + ir = [] + for obs in self.observables: + ir.extend(obs.to_ir()) + return ir + + @property + def observables(self) -> Tuple[Observable]: + """Tuple[Observable]: observables part of tensor product""" + return self._observables + + def to_matrix(self) -> np.ndarray: + return functools.reduce(np.kron, [obs.to_matrix() for obs in self.observables]) + + def __matmul__(self, other): + if isinstance(other, TensorProduct): + return TensorProduct(list(self.observables) + list(other.observables)) + + if isinstance(other, Observable): + return TensorProduct(list(self.observables) + [other]) + + raise ValueError("Can only perform tensor products between observables.") + + def __rmatmul__(self, other): + if isinstance(other, Observable): + return TensorProduct([other] + list(self.observables)) + + raise ValueError("Can only perform tensor products between observables.") + + def __repr__(self): + return "TensorProduct(" + ", ".join([repr(o) for o in self.observables]) + ")" + + def __eq__(self, other): + return self.matrix_equivalence(other) + + +Observable.register_observable(TensorProduct) + + +class Hermitian(Observable): + """Hermitian matrix as an observable.""" + + def __init__(self, matrix: np.ndarray, display_name: str = "Hermitian"): + """ + Args: + matrix (numpy.ndarray): Hermitian matrix which defines the observable. + display_name (str): Name to be used for an instance of this Hermitian matrix + observable for circuit diagrams. Defaults to `Hermitian`. + + Raises: + ValueError: If `matrix` is not a two-dimensional square matrix, + or has a dimension length which is not a positive exponent of 2, + or is non-hermitian. + + Example: + >>> Observable.Hermitian(matrix=np.array([[0, 1],[1, 0]])) + """ + verify_quantum_operator_matrix_dimensions(matrix) + self._matrix = np.array(matrix, dtype=complex) + qubit_count = int(np.log2(self._matrix.shape[0])) + + if not is_hermitian(self._matrix): + raise ValueError(f"{self._matrix} is not hermitian") + + super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) + + def to_ir(self) -> List[List[List[List[float]]]]: + return [ + [[[element.real, element.imag] for element in row] for row in self._matrix.tolist()] + ] + + def to_matrix(self) -> np.ndarray: + return self._matrix + + def __eq__(self, other) -> bool: + return self.matrix_equivalence(other) + + +Observable.register_observable(Hermitian) diff --git a/src/braket/circuits/operator.py b/src/braket/circuits/operator.py index 053e6b8a..71f0228f 100644 --- a/src/braket/circuits/operator.py +++ b/src/braket/circuits/operator.py @@ -15,7 +15,7 @@ class Operator(ABC): - """A quantum operator is the abstract definition of an operation for a quantum device.""" + """An operator is the abstract definition of an operation for a quantum device.""" @property @abstractmethod diff --git a/src/braket/circuits/quantum_operator.py b/src/braket/circuits/quantum_operator.py new file mode 100644 index 00000000..6cbba841 --- /dev/null +++ b/src/braket/circuits/quantum_operator.py @@ -0,0 +1,107 @@ +# Copyright 2019-2019 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 typing import Any, List, Sequence + +import numpy as np +from braket.circuits.operator import Operator + + +class QuantumOperator(Operator): + """A quantum operator is the definition of a quantum operation for a quantum device.""" + + def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): + """ + Args: + qubit_count (int): Number of qubits this quantum operator interacts with. + ascii_symbols (Sequence[str]): ASCII string symbols for the quantum operator. + These are used when printing a diagram of circuits. + Length must be the same as `qubit_count`, and index ordering is expected + to correlate with target ordering on the instruction. + For instance, if CNOT instruction has the control qubit on the first index and + target qubit on the second index. Then ASCII symbols would have ["C", "X"] to + correlate a symbol with that index. + + Raises: + ValueError: `qubit_count` is less than 1, `ascii_symbols` are None, or + `ascii_symbols` length != `qubit_count` + """ + + if qubit_count < 1: + raise ValueError(f"qubit_count, {qubit_count}, must be greater than zero") + self._qubit_count = qubit_count + + if ascii_symbols is None: + raise ValueError(f"ascii_symbols must not be None") + + if len(ascii_symbols) != qubit_count: + msg = f"ascii_symbols, {ascii_symbols}, length must equal qubit_count, {qubit_count}" + raise ValueError(msg) + self._ascii_symbols = tuple(ascii_symbols) + + @property + def qubit_count(self) -> int: + """int: Returns number of qubits this quantum operator interacts with.""" + return self._qubit_count + + @property + def ascii_symbols(self) -> List[str]: + """List[str]: Returns the ascii symbols for the quantum operator.""" + return self._ascii_symbols + + @property + def name(self) -> str: + """ + Returns the name of the quantum operator + + Returns: + The name of the quantum operator as a string + """ + return self.__class__.__name__ + + def to_ir(self, *args, **kwargs) -> Any: + """Returns IR representation of quantum operator + + Args: + *args: Positional arguments + **kwargs: Keyword arguments + """ + raise NotImplementedError("to_ir has not been implemented yet.") + + def to_matrix(self, *args, **kwargs) -> Any: + """Returns a matrix representation of the quantum operator + + Returns: + np.ndarray: A matrix representation of the quantum operator + """ + raise NotImplementedError("to_matrix has not been implemented yet.") + + def matrix_equivalence(self, other): + """ + Whether the matrix form of two gates are equivalent + + Args: + other (Gate): Gate instance to compare this quantum operator to + + Returns: + bool: If matrix forms of this quantum operator and the other quantum operator + are equivalent + """ + if not isinstance(other, QuantumOperator): + return NotImplemented + try: + return np.allclose(self.to_matrix(), other.to_matrix()) + except ValueError: + return False + + def __repr__(self): + return f"{self.name}('qubit_count': {self.qubit_count})" diff --git a/src/braket/circuits/quantum_operator_helpers.py b/src/braket/circuits/quantum_operator_helpers.py new file mode 100644 index 00000000..4d4b64ab --- /dev/null +++ b/src/braket/circuits/quantum_operator_helpers.py @@ -0,0 +1,74 @@ +# Copyright 2019-2019 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 + + +def verify_quantum_operator_matrix_dimensions(matrix: np.array) -> None: + """ + Verifies matrix is square and matrix dimensions are positive exponents of 2, + raising `ValueError` otherwise. + + Args: + matrix (np.ndarray): matrix to verify + + Raises: + ValueError: If `matrix` is not a two-dimensional square matrix, + or has a dimension length which is not a positive exponent of 2 + """ + if not is_square_matrix(matrix): + raise ValueError(f"{matrix} is not a two-dimensional square matrix") + + matrix = np.array(matrix, dtype=complex) + qubit_count = int(np.log2(matrix.shape[0])) + if 2 ** qubit_count != matrix.shape[0] or qubit_count < 1: + raise ValueError(f"`matrix` dimension {matrix.shape[0]} is not a positive exponent of 2") + + +def is_hermitian(matrix: np.array) -> bool: + """ + Whether matrix is Hermitian + + Args: + matrix (np.ndarray): matrix to verify + + Return: + bool: If matrix is Hermitian + """ + return np.allclose(matrix, matrix.conj().T) + + +def is_square_matrix(matrix: np.array) -> bool: + """ + Whether matrix is square, meaning matrix has two dimensions are both are equivalent + + Args: + matrix (np.ndarray): matrix to verify + + Return: + bool: If matrix is square + """ + return len(matrix.shape) == 2 and matrix.shape[0] == matrix.shape[1] + + +def is_unitary(matrix: np.array) -> bool: + """ + Whether matrix is unitary + + Args: + matrix (np.ndarray): matrix to verify + + Return: + bool: If matrix is unitary + """ + return np.allclose(np.eye(len(matrix)), matrix.dot(matrix.T.conj())) diff --git a/src/braket/circuits/qubit_set.py b/src/braket/circuits/qubit_set.py index 66df59a1..3cb4e77e 100644 --- a/src/braket/circuits/qubit_set.py +++ b/src/braket/circuits/qubit_set.py @@ -30,10 +30,11 @@ class QubitSet(IndexedSet): mutating this object. """ - def __init__(self, qubits: QubitSetInput = []): + def __init__(self, qubits: QubitSetInput = None): """ Args: - qubits (int, Qubit, or iterable of int / Qubit): Qubits to be included in the QubitSet. + qubits (int, Qubit, or iterable of int / Qubit, optional): Qubits to be included in + the QubitSet. Default is None. Examples: >>> qubits = QubitSet([0, 1]) @@ -60,7 +61,7 @@ def _flatten(other): else: yield other - _qubits = [Qubit.new(qubit) for qubit in _flatten(qubits)] + _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: diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 75bf3f42..bb851bd1 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -17,6 +17,7 @@ import braket.ir.jaqcd as ir from braket.circuits import circuit +from braket.circuits.observable import Observable from braket.circuits.qubit_set import QubitSet, QubitSetInput from braket.circuits.result_type import ResultType @@ -128,11 +129,12 @@ class Probability(ResultType): It can be the probability of all states if no targets are specified or the marginal probability of a restricted set of states if only a subset of all qubits are specified as target.""" - def __init__(self, target: QubitSetInput = []): + def __init__(self, target: QubitSetInput = None): """ Args: - target (int, Qubit, or iterable of int / Qubit): Target qubits that the result type - is requested for. Default is [], which means all qubits for the circuit. + target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + result type is requested for. Default is None, which means all qubits for the + circuit. Examples: >>> ResultType.Probability(target=[0, 1]) @@ -149,16 +151,17 @@ def target(self, target: QubitSetInput) -> None: self._target = QubitSet(target) def to_ir(self) -> ir.Probability: - return ir.Probability(targets=list(self.target)) + return ir.Probability(targets=list(self.target)) if self.target else ir.Probability() @staticmethod @circuit.subroutine(register=True) - def probability(target: QubitSetInput) -> ResultType: + def probability(target: QubitSetInput = None) -> ResultType: """Registers this function into the circuit class. Args: - target (int, Qubit, or iterable of int / Qubit): Target qubits that the result type - is requested for. + target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + result type is requested for. Default is None, which means all qubits for the + circuit. Returns: ResultType: probability as a requested result type @@ -181,3 +184,239 @@ def __copy__(self) -> Probability: ResultType.register_result_type(Probability) + + +class ObservableResultType(ResultType): + """ + 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. + + See :mod:`braket.circuits.observables` module for all of the supported observables. + """ + + def __init__(self, ascii_symbol: str, observable: Observable, target: QubitSetInput = None): + """ + Args: + observable (Observable): the observable for the result type + target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + result type is requested for. Default is None, which means the observable must + only operate on 1 qubit and it will be applied to all qubits in parallel + + Raises: + ValueError: If the observable's qubit count and the number of target qubits + are not equal. Or, if target=None and the observable's qubit count is not 1. + """ + super().__init__(ascii_symbol) + 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 self._observable.qubit_count != len(self._target): + raise ValueError( + f"Observable's qubit count and the number of target qubits must be equal" + ) + + @property + def observable(self) -> Observable: + return self._observable + + @property + def target(self) -> QubitSet: + return self._target + + @target.setter + def target(self, target: QubitSetInput) -> None: + self._target = QubitSet(target) + + def __eq__(self, other) -> bool: + if isinstance(other, ObservableResultType): + return self.target == other.target and self.observable == other.observable + return False + + def __repr__(self) -> str: + return f"{self.name}(observable={self.observable}, target={self.target})" + + def __copy__(self) -> Expectation: + return type(self)(observable=self.observable, target=self.target) + + +class Expectation(ObservableResultType): + """Expectation of specified target qubit set and observable as the requested result type. + + 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. + + See :mod:`braket.circuits.observables` module for all of the supported observables. + """ + + def __init__(self, observable: Observable, target: QubitSetInput = None): + """ + Args: + observable (Observable): the observable for the result type + target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + result type is requested for. Default is None, which means the observable must + only operate on 1 qubit and it will be applied to all qubits in parallel + + Raises: + ValueError: If the observable's qubit count and the number of target qubits + are not equal. Or, if target=None and the observable's qubit count is not 1. + + Examples: + >>> ResultType.Expectation(observable=Observable.Z(), target=0) + + >>> tensor_product = Observable.Y() @ Observable.Z() + >>> ResultType.Expectation(observable=tensor_product, target=[0, 1]) + """ + super().__init__(ascii_symbol=["Expectation"], observable=observable, target=target) + + def to_ir(self) -> ir.Expectation: + if self.target: + return ir.Expectation(observable=self.observable.to_ir(), targets=list(self.target)) + else: + return ir.Expectation(observable=self.observable.to_ir()) + + @staticmethod + @circuit.subroutine(register=True) + def expectation(observable: Observable, target: QubitSetInput = None) -> ResultType: + """Registers this function into the circuit class. + + Args: + observable (Observable): the observable for the result type + target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + result type is requested for. Default is None, which means the observable must + only operate on 1 qubit and it will be applied to all qubits in parallel + + Returns: + ResultType: expectation as a requested result type + + Examples: + >>> circ = Circuit().expectation(observable=Observable.Z(), target=0) + """ + return ResultType.Expectation(observable=observable, target=target) + + +ResultType.register_result_type(Expectation) + + +class Sample(ObservableResultType): + """Sample of specified target qubit set and observable as the requested result type. + + 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. + + See :mod:`braket.circuits.observables` module for all of the supported observables. + """ + + def __init__(self, observable: Observable, target: QubitSetInput = None): + """ + Args: + observable (Observable): the observable for the result type + target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + result type is requested for. Default is None, which means the observable must + only operate on 1 qubit and it will be applied to all qubits in parallel + + Raises: + ValueError: If the observable's qubit count and the number of target qubits + are not equal. Or, if target=None and the observable's qubit count is not 1. + + Examples: + >>> ResultType.Sample(observable=Observable.Z(), target=0) + + >>> tensor_product = Observable.Y() @ Observable.Z() + >>> ResultType.Sample(observable=tensor_product, target=[0, 1]) + """ + super().__init__(ascii_symbol=["Sample"], observable=observable, target=target) + + def to_ir(self) -> ir.Sample: + if self.target: + return ir.Sample(observable=self.observable.to_ir(), targets=list(self.target)) + else: + return ir.Sample(observable=self.observable.to_ir()) + + @staticmethod + @circuit.subroutine(register=True) + def sample(observable: Observable, target: QubitSetInput = None) -> ResultType: + """Registers this function into the circuit class. + + Args: + observable (Observable): the observable for the result type + target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + result type is requested for. Default is None, which means the observable must + only operate on 1 qubit and it will be applied to all qubits in parallel + + Returns: + ResultType: sample as a requested result type + + Examples: + >>> circ = Circuit().sample(observable=Observable.Z(), target=0) + """ + return ResultType.Sample(observable=observable, target=target) + + +ResultType.register_result_type(Sample) + + +class Variance(ObservableResultType): + """Variance of specified target qubit set and observable as the requested result type. + + 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. + + See :mod:`braket.circuits.observables` module for all of the supported observables. + """ + + def __init__(self, observable: Observable, target: QubitSetInput = None): + """ + Args: + observable (Observable): the observable for the result type + target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + result type is requested for. Default is None, which means the observable must + only operate on 1 qubit and it will be applied to all qubits in parallel + + Raises: + ValueError: If the observable's qubit count and the number of target qubits + are not equal. Or, if target=None and the observable's qubit count is not 1. + + Examples: + >>> ResultType.Variance(observable=Observable.Z(), target=0) + + >>> tensor_product = Observable.Y() @ Observable.Z() + >>> ResultType.Variance(observable=tensor_product, target=[0, 1]) + """ + super().__init__(ascii_symbol=["Variance"], observable=observable, target=target) + + def to_ir(self) -> ir.Variance: + if self.target: + return ir.Variance(observable=self.observable.to_ir(), targets=list(self.target)) + else: + return ir.Variance(observable=self.observable.to_ir()) + + @staticmethod + @circuit.subroutine(register=True) + def variance(observable: Observable, target: QubitSetInput = None) -> ResultType: + """Registers this function into the circuit class. + + Args: + observable (Observable): the observable for the result type + target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + result type is requested for. Default is None, which means the observable must + only operate on 1 qubit and it will be applied to all qubits in parallel + + Returns: + ResultType: variance as a requested result type + + Examples: + >>> circ = Circuit().variance(observable=Observable.Z(), target=0) + """ + return ResultType.Variance(observable=observable, target=target) + + +ResultType.register_result_type(Variance) diff --git a/test/unit_tests/braket/circuits/test_gate.py b/test/unit_tests/braket/circuits/test_gate.py index 8bab8558..3319e1d3 100644 --- a/test/unit_tests/braket/circuits/test_gate.py +++ b/test/unit_tests/braket/circuits/test_gate.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. import pytest -from braket.circuits import Gate, Operator +from braket.circuits import Gate, QuantumOperator @pytest.fixture @@ -21,51 +21,7 @@ def gate(): def test_is_operator(gate): - assert isinstance(gate, Operator) - - -@pytest.mark.xfail(raises=ValueError) -def test_qubit_count_lt_one(): - Gate(qubit_count=0, ascii_symbols=[]) - - -@pytest.mark.xfail(raises=ValueError) -def test_none_ascii(): - Gate(qubit_count=1, ascii_symbols=None) - - -@pytest.mark.xfail(raises=ValueError) -def test_mismatch_length_ascii(): - Gate(qubit_count=1, ascii_symbols=["foo", "bar"]) - - -def test_name(gate): - expected = gate.__class__.__name__ - assert gate.name == expected - - -def test_getters(): - qubit_count = 2 - ascii_symbols = ("foo", "bar") - gate = Gate(qubit_count=qubit_count, ascii_symbols=ascii_symbols) - - assert gate.qubit_count == qubit_count - assert gate.ascii_symbols == ascii_symbols - - -@pytest.mark.xfail(raises=AttributeError) -def test_qubit_count_setter(gate): - gate.qubit_count = 10 - - -@pytest.mark.xfail(raises=AttributeError) -def test_ascii_symbols_setter(gate): - gate.ascii_symbols = ["foo", "bar"] - - -@pytest.mark.xfail(raises=AttributeError) -def test_name_setter(gate): - gate.name = "hi" + assert isinstance(gate, QuantumOperator) @pytest.mark.xfail(raises=NotImplementedError) diff --git a/test/unit_tests/braket/circuits/test_observable.py b/test/unit_tests/braket/circuits/test_observable.py new file mode 100644 index 00000000..46598447 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_observable.py @@ -0,0 +1,117 @@ +# Copyright 2019-2019 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 Observable, QuantumOperator + + +@pytest.fixture +def observable(): + return Observable(qubit_count=1, ascii_symbols=["foo"]) + + +def test_is_operator(observable): + assert isinstance(observable, QuantumOperator) + + +@pytest.mark.xfail(raises=ValueError) +def test_qubit_count_lt_one(): + Observable(qubit_count=0, ascii_symbols=[]) + + +@pytest.mark.xfail(raises=ValueError) +def test_none_ascii(): + Observable(qubit_count=1, ascii_symbols=None) + + +@pytest.mark.xfail(raises=ValueError) +def test_mismatch_length_ascii(): + Observable(qubit_count=1, ascii_symbols=["foo", "bar"]) + + +def test_name(observable): + expected = observable.__class__.__name__ + assert observable.name == expected + + +def test_getters(): + qubit_count = 2 + ascii_symbols = ("foo", "bar") + observable = Observable(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + + assert observable.qubit_count == qubit_count + assert observable.ascii_symbols == ascii_symbols + + +@pytest.mark.xfail(raises=AttributeError) +def test_qubit_count_setter(observable): + observable.qubit_count = 10 + + +@pytest.mark.xfail(raises=AttributeError) +def test_ascii_symbols_setter(observable): + observable.ascii_symbols = ["foo", "bar"] + + +@pytest.mark.xfail(raises=AttributeError) +def test_name_setter(observable): + observable.name = "hi" + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_to_ir_not_implemented_by_default(observable): + observable.to_ir() + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_to_matrix_not_implemented_by_default(observable): + observable.to_matrix(None) + + +def test_str(observable): + expected = "{}('qubit_count': {})".format(observable.name, observable.qubit_count) + assert str(observable) == expected + + +def test_register_observable(): + class _FooObservable(Observable): + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["foo"]) + + Observable.register_observable(_FooObservable) + assert Observable._FooObservable().name == _FooObservable().name + + +def test_matmul_observable(): + o1 = Observable.I() + o2 = Observable.Z() + o3 = o1 @ o2 + assert isinstance(o3, Observable.TensorProduct) + assert o3.qubit_count == 2 + assert o3.to_ir() == ["i", "z"] + assert o3.ascii_symbols == ("I@Z", "I@Z") + + +@pytest.mark.xfail(raises=ValueError) +def test_matmul_non_observable(): + Observable.I() @ "a" + + +def test_observable_equality(): + o1 = Observable.I() + o2 = Observable.I() + o3 = Observable.Z() + o4 = "a" + assert o1 == o2 + assert o1 != o3 + assert o1 != o4 diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py new file mode 100644 index 00000000..ff6c82ea --- /dev/null +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -0,0 +1,124 @@ +# Copyright 2019-2019 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 Gate, Observable + +testdata = [ + (Observable.I(), Gate.I(), ["i"]), + (Observable.X(), Gate.X(), ["x"]), + (Observable.Y(), Gate.Y(), ["y"]), + (Observable.Z(), Gate.Z(), ["z"]), + (Observable.H(), Gate.H(), ["h"]), +] + +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]])), + (np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])), + (np.array([[0, 1], ["a", 0]])), + (Gate.T().to_matrix()), +] + + +@pytest.mark.parametrize("testobject,gateobject,expected_ir", testdata) +def test_to_ir(testobject, gateobject, expected_ir): + expected = expected_ir + actual = testobject.to_ir() + assert actual == expected + + +@pytest.mark.parametrize("testobject,gateobject,expected_ir", testdata) +def test_gate_equality(testobject, gateobject, expected_ir): + assert testobject.qubit_count == gateobject.qubit_count + assert testobject.ascii_symbols == gateobject.ascii_symbols + assert testobject.matrix_equivalence(gateobject) + + +# Hermitian + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("matrix", invalid_hermitian_matrices) +def test_hermitian_invalid_matrix(matrix): + Observable.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()) + a4 = "hi" + assert a1 == a2 + assert a1 != a3 + assert a1 != a4 + + +def test_hermitian_to_ir(): + matrix = Observable.I().to_matrix() + obs = Observable.Hermitian(matrix=matrix) + assert obs.to_ir() == [[[[1, 0], [0, 0]], [[0, 0], [1, 0]]]] + + +# TensorProduct + + +def test_tensor_product_to_ir(): + t = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.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_matmul_tensor(): + t1 = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) + 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 + assert t3.ascii_symbols == tuple(["Z@I@X@Hermitian@Y"] * 5) + + +def test_tensor_product_matmul_observable(): + t1 = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) + o1 = Observable.I() + t = t1 @ o1 + assert t.to_ir() == ["z", "i", "x", "i"] + assert t.qubit_count == 4 + assert t.ascii_symbols == tuple(["Z@I@X@I"] * 4) + + +@pytest.mark.xfail(raises=ValueError) +def test_tensor_product_value_error(): + Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) @ "a" + + +def test_tensor_product_rmatmul_observable(): + t1 = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) + o1 = Observable.I() + t = o1 @ t1 + assert t.to_ir() == ["i", "z", "i", "x"] + assert t.qubit_count == 4 + assert t.ascii_symbols == tuple(["I@Z@I@X"] * 4) + + +@pytest.mark.xfail(raises=ValueError) +def test_tensor_product_rmatmul_value_error(): + "a" @ Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) diff --git a/test/unit_tests/braket/circuits/test_quantum_operator.py b/test/unit_tests/braket/circuits/test_quantum_operator.py new file mode 100644 index 00000000..22388ea9 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_quantum_operator.py @@ -0,0 +1,112 @@ +# Copyright 2019-2019 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 Operator, QuantumOperator + + +@pytest.fixture +def quantum_operator(): + return QuantumOperator(qubit_count=1, ascii_symbols=["foo"]) + + +def test_is_operator(quantum_operator): + assert isinstance(quantum_operator, Operator) + + +@pytest.mark.xfail(raises=ValueError) +def test_qubit_count_lt_one(): + QuantumOperator(qubit_count=0, ascii_symbols=[]) + + +@pytest.mark.xfail(raises=ValueError) +def test_none_ascii(): + QuantumOperator(qubit_count=1, ascii_symbols=None) + + +@pytest.mark.xfail(raises=ValueError) +def test_mismatch_length_ascii(): + QuantumOperator(qubit_count=1, ascii_symbols=["foo", "bar"]) + + +def test_name(quantum_operator): + expected = quantum_operator.__class__.__name__ + assert quantum_operator.name == expected + + +def test_getters(): + qubit_count = 2 + ascii_symbols = ("foo", "bar") + quantum_operator = QuantumOperator(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + + assert quantum_operator.qubit_count == qubit_count + assert quantum_operator.ascii_symbols == ascii_symbols + + +@pytest.mark.xfail(raises=AttributeError) +def test_qubit_count_setter(quantum_operator): + quantum_operator.qubit_count = 10 + + +@pytest.mark.xfail(raises=AttributeError) +def test_ascii_symbols_setter(quantum_operator): + quantum_operator.ascii_symbols = ["foo", "bar"] + + +@pytest.mark.xfail(raises=AttributeError) +def test_name_setter(quantum_operator): + quantum_operator.name = "hi" + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_to_ir_not_implemented_by_default(quantum_operator): + quantum_operator.to_ir(None) + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_to_matrix_not_implemented_by_default(quantum_operator): + quantum_operator.to_matrix(None) + + +def test_matrix_equivalence(): + class _Foo1(QuantumOperator): + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["foo"]) + + def to_matrix(self): + return np.array([[1.0, 0.0], [0.0, 1.0j]]) + + class _Foo2(QuantumOperator): + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["foo"]) + + def to_matrix(self): + return np.array([[1.0, 0.0], [1.0, 0.0]]) + + quantum_operator1 = _Foo1() + quantum_operator2 = _Foo1() + quantum_operator3 = _Foo2() + assert quantum_operator1.matrix_equivalence(quantum_operator2) + assert not quantum_operator1.matrix_equivalence(quantum_operator3) + + +def test_matrix_equivalence_non_quantum_operator(): + quantum_operator1 = QuantumOperator(qubit_count=1, ascii_symbols=["foo"]) + x = 1 + assert quantum_operator1.matrix_equivalence(x) == NotImplemented + + +def test_str(quantum_operator): + expected = "{}('qubit_count': {})".format(quantum_operator.name, quantum_operator.qubit_count) + assert str(quantum_operator) == expected diff --git a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py new file mode 100644 index 00000000..37f8f363 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py @@ -0,0 +1,55 @@ +import numpy as np +import pytest +from braket.circuits.quantum_operator_helpers import ( + is_hermitian, + is_square_matrix, + is_unitary, + verify_quantum_operator_matrix_dimensions, +) + +valid_unitary_hermitian_matrix = np.array([[0, 1], [1, 0]]) + +invalid_dimension_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]])), + (np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])), +] + +invalid_unitary_matrices = [(np.array([[0, 1], [1, 1]])), (np.array([[1, 2], [3, 4]]))] + +invalid_hermitian_matrices = [(np.array([[1, 0], [0, 1j]])), (np.array([[1, 2], [3, 4]]))] + + +def test_verify_quantum_operator_matrix_dimensions(): + assert verify_quantum_operator_matrix_dimensions(valid_unitary_hermitian_matrix) is None + + +def test_is_unitary_true(): + assert is_unitary(valid_unitary_hermitian_matrix) + + +def test_is_hermitian_true(): + assert is_hermitian(valid_unitary_hermitian_matrix) + + +def test_is_square_matrix(): + assert is_square_matrix(valid_unitary_hermitian_matrix) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("matrix", invalid_dimension_matrices) +def test_verify_quantum_operator_matrix_dimensions_value_error(matrix): + verify_quantum_operator_matrix_dimensions(matrix) + + +@pytest.mark.parametrize("matrix", invalid_unitary_matrices) +def test_is_unitary_false(matrix): + assert not is_unitary(matrix) + + +@pytest.mark.parametrize("matrix", invalid_hermitian_matrices) +def test_is_hermitian_false(matrix): + assert not is_hermitian(matrix) diff --git a/test/unit_tests/braket/circuits/test_result_types.py b/test/unit_tests/braket/circuits/test_result_types.py new file mode 100644 index 00000000..3c4f317b --- /dev/null +++ b/test/unit_tests/braket/circuits/test_result_types.py @@ -0,0 +1,194 @@ +# Copyright 2019-2019 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 braket.ir.jaqcd as ir +import pytest +from braket.circuits import Circuit, Observable, ResultType +from braket.circuits.result_types import ObservableResultType + +testdata = [ + (ResultType.StateVector, "state_vector", ir.StateVector, {}, {}), + (ResultType.Amplitude, "amplitude", ir.Amplitude, {"state": ["0"]}, {"states": ["0"]}), + ( + ResultType.Probability, + "probability", + ir.Probability, + {"target": [0, 1]}, + {"targets": [0, 1]}, + ), + (ResultType.Probability, "probability", ir.Probability, {"target": None}, {},), + ( + ResultType.Expectation, + "expectation", + ir.Expectation, + {"observable": Observable.Z(), "target": [0]}, + {"observable": ["z"], "targets": [0]}, + ), + ( + ResultType.Expectation, + "expectation", + ir.Expectation, + {"observable": Observable.Z() @ Observable.I() @ Observable.X(), "target": [0, 1, 2]}, + {"observable": ["z", "i", "x"], "targets": [0, 1, 2]}, + ), + ( + ResultType.Expectation, + "expectation", + ir.Expectation, + {"observable": Observable.Hermitian(matrix=Observable.I().to_matrix()), "target": [0]}, + {"observable": [[[[1.0, 0], [0, 0]], [[0, 0], [1.0, 0]]]], "targets": [0]}, + ), + ( + ResultType.Expectation, + "expectation", + ir.Expectation, + {"observable": Observable.Hermitian(matrix=Observable.I().to_matrix()), "target": None}, + {"observable": [[[[1.0, 0], [0, 0]], [[0, 0], [1.0, 0]]]]}, + ), + ( + ResultType.Sample, + "sample", + ir.Sample, + {"observable": Observable.Z(), "target": [0]}, + {"observable": ["z"], "targets": [0]}, + ), + ( + ResultType.Sample, + "sample", + ir.Sample, + {"observable": Observable.Z(), "target": None}, + {"observable": ["z"]}, + ), + ( + ResultType.Variance, + "variance", + ir.Variance, + {"observable": Observable.Z(), "target": [0]}, + {"observable": ["z"], "targets": [0]}, + ), + ( + ResultType.Variance, + "variance", + ir.Variance, + {"observable": Observable.Z(), "target": None}, + {"observable": ["z"]}, + ), +] + + +@pytest.mark.parametrize("testclass,subroutine_name,irclass,input,ir_input", testdata) +def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): + expected = irclass(**ir_input) + actual = testclass(**input).to_ir() + assert actual == expected + + +@pytest.mark.parametrize("testclass,subroutine_name,irclass,input,ir_input", testdata) +def test_result_subroutine(testclass, subroutine_name, irclass, input, ir_input): + subroutine = getattr(Circuit(), subroutine_name) + assert subroutine(**input) == Circuit(testclass(**input)) + + +@pytest.mark.parametrize("testclass,subroutine_name,irclass,input,ir_input", testdata) +def test_result_equality(testclass, subroutine_name, irclass, input, ir_input): + a1 = testclass(**input) + a2 = a1.copy() + assert a1 == a2 + assert a1 is not a2 + + +# Amplitude + + +@pytest.mark.xfail(raises=ValueError) +def test_amplitude_init_value_error(): + ResultType.Amplitude(state=None) + + +def test_amplitude_equality(): + a1 = ResultType.Amplitude(state=["0", "1"]) + a2 = ResultType.Amplitude(state=["0", "1"]) + a3 = ResultType.Amplitude(state=["01", "11", "10"]) + a4 = "hi" + assert a1 == a2 + assert a1 != a3 + assert a1 != a4 + + +# Probability + + +def test_probability_equality(): + a1 = ResultType.Probability([0, 1]) + a2 = ResultType.Probability([0, 1]) + a3 = ResultType.Probability([0, 1, 2]) + a4 = "hi" + assert a1 == a2 + assert a1 != a3 + assert a1 != a4 + + +# ObservableResultType + + +@pytest.mark.xfail(raises=ValueError) +def test_expectation_init_value_error_target(): + ObservableResultType(ascii_symbol="Obs", observable=Observable.X() @ Observable.Y(), target=[]) + + +@pytest.mark.xfail(raises=ValueError) +def test_obs_rt_init_value_error_qubit_count(): + ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=[0, 1]) + + +def test_obs_rt_equality(): + a1 = ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=0) + a2 = ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=0) + a3 = ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=1) + a4 = "hi" + assert a1 == a2 + assert a1 != a3 + assert a1 != a4 + + +def test_obs_rt_repr(): + a1 = ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=0) + assert ( + str(a1) + == f"ObservableResultType(observable=X('qubit_count': 1), target=QubitSet([Qubit(0)]))" + ) + + +# Expectation + + +def test_expectation_parent_class(): + assert isinstance( + ResultType.Expectation(observable=Observable.X(), target=0), ObservableResultType + ) + + +# Sample + + +def test_sample_parent_class(): + assert isinstance(ResultType.Sample(observable=Observable.X(), target=0), ObservableResultType) + + +# Variance + + +def test_variance_parent_class(): + assert isinstance( + ResultType.Variance(observable=Observable.X(), target=0), ObservableResultType + ) diff --git a/test/unit_tests/braket/circuits/test_results.py b/test/unit_tests/braket/circuits/test_results.py deleted file mode 100644 index bfe2bd74..00000000 --- a/test/unit_tests/braket/circuits/test_results.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright 2019-2019 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 braket.ir.jaqcd as ir -import pytest -from braket.circuits import Circuit, ResultType - -testdata = [ - (ResultType.StateVector, "state_vector", ir.StateVector, {}, {}), - (ResultType.Amplitude, "amplitude", ir.Amplitude, {"state": ["0"]}, {"states": ["0"]}), - ( - ResultType.Probability, - "probability", - ir.Probability, - {"target": [0, 1]}, - {"targets": [0, 1]}, - ), -] - - -@pytest.mark.parametrize("testclass,subroutine_name,irclass,input,ir_input", testdata) -def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): - expected = irclass(**ir_input) - actual = testclass(**input).to_ir() - assert actual == expected - - -@pytest.mark.parametrize("testclass,subroutine_name,irclass,input,ir_input", testdata) -def test_result_subroutine(testclass, subroutine_name, irclass, input, ir_input): - subroutine = getattr(Circuit(), subroutine_name) - assert subroutine(**input) == Circuit(testclass(**input)) - - -@pytest.mark.parametrize("testclass,subroutine_name,irclass,input,ir_input", testdata) -def test_result_equality(testclass, subroutine_name, irclass, input, ir_input): - a1 = testclass(**input) - a2 = a1.copy() - assert a1 == a2 - assert a1 is not a2 - - -# Amplitude - - -@pytest.mark.xfail(raises=ValueError) -def test_amplitude_init_value_error(): - ResultType.Amplitude(state=None) - - -def test_amplitude_equality(): - a1 = ResultType.Amplitude(state=["0", "1"]) - a2 = ResultType.Amplitude(state=["0", "1"]) - a3 = ResultType.Amplitude(state=["01", "11", "10"]) - a4 = "hi" - assert a1 == a2 - assert a1 != a3 - assert a1 != a4 - - -# Probability - - -def test_probability_equality(): - a1 = ResultType.Probability([0, 1]) - a2 = ResultType.Probability([0, 1]) - a3 = ResultType.Probability([0, 1, 2]) - a4 = "hi" - assert a1 == a2 - assert a1 != a3 - assert a1 != a4 From b72cda8bd7a39cfd5ea8516aaa99d9c8aa740cc5 Mon Sep 17 00:00:00 2001 From: Ava Wang Date: Wed, 8 Apr 2020 15:10:36 -0700 Subject: [PATCH 0076/1165] Reduce required arguments for AwsQuantumTask and fix result caching bug --- setup.py | 2 +- src/braket/aws/aws_quantum_task.py | 124 ++++++++++++------ .../braket/aws/test_aws_quantum_task.py | 88 ++++++++----- 3 files changed, 145 insertions(+), 69 deletions(-) diff --git a/setup.py b/setup.py index 3008db1b..72ada0e1 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="braket-sdk", - version="0.3.2", + version="0.3.4", license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 44d76f24..2efcfdd0 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -17,8 +17,9 @@ import time from functools import singledispatch from logging import Logger, getLogger -from typing import Any, Callable, Dict, Optional, Union +from typing import Any, Dict, Optional, Union +import boto3 from braket.annealing.problem import Problem from braket.aws.aws_session import AwsSession from braket.circuits.circuit import Circuit @@ -27,10 +28,10 @@ class AwsQuantumTask(QuantumTask): """Amazon Braket implementation of a quantum task. A task can be a circuit or an annealing - problem. Currently, only circuits are supported in the Private Beta.""" + problem.""" # TODO: Add API documentation that defines these states. Make it clear this is the contract. - TERMINAL_STATES = {"COMPLETED", "FAILED", "CANCELLED"} + NO_RESULT_TERMINAL_STATES = {"FAILED", "CANCELLED"} RESULTS_READY_STATES = {"COMPLETED"} GATE_IR_TYPE = "jaqcd" @@ -73,7 +74,7 @@ def create( backend_parameters (Dict[str, Any]): Additional parameters to send to the device. For example, for D-Wave: - >>> backend_parameters = {"dWaveParameters": {"postprocess": "OPTIMIZATION"}} + `{"dWaveParameters": {"postprocessingType": "OPTIMIZATION"}}` Returns: AwsQuantumTask: AwsQuantumTask tracking the task execution on the device. @@ -105,8 +106,7 @@ def create( def __init__( self, arn: str, - aws_session: AwsSession, - results_formatter: Callable[[str], Any], + aws_session: AwsSession = None, poll_timeout_seconds: int = DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: int = DEFAULT_RESULTS_POLL_INTERVAL, logger: Logger = getLogger(__name__), @@ -114,19 +114,31 @@ def __init__( """ Args: arn (str): The ARN of the task. - aws_session (AwsSession): The `AwsSession` for connecting to AWS services. - results_formatter (Callable[[str], Any]): A function that deserializes a string - into a results structure (such as `GateModelQuantumTaskResult`) + aws_session (AwsSession, optional): The `AwsSession` for connecting to AWS services. + Default is `None`, in which case an `AwsSession` object will be created with the + region of the task. poll_timeout_seconds (int): The polling timeout for result(), default is 120 seconds. poll_interval_seconds (int): The polling interval for result(), default is 0.25 seconds. logger (Logger): Logger object with which to write logs, such as task statuses while waiting for task to be in a terminal state. Default is `getLogger(__name__)` + + Examples: + >>> task = AwsQuantumTask(arn='task_arn') + >>> task.state() + 'COMPLETED' + >>> result = task.result() + AnnealingQuantumTaskResult(...) + + >>> task = AwsQuantumTask(arn='task_arn', poll_timeout_seconds=300) + >>> result = task.result() + GateModelQuantumTaskResult(...) """ self._arn: str = arn - self._aws_session: AwsSession = aws_session - self._results_formatter = results_formatter + self._aws_session: AwsSession = aws_session or AwsQuantumTask._aws_session_for_task_arn( + task_arn=arn + ) self._poll_timeout_seconds = poll_timeout_seconds self._poll_interval_seconds = poll_interval_seconds self._logger = logger @@ -141,6 +153,18 @@ def __init__( asyncio.set_event_loop(asyncio.new_event_loop()) self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) + @staticmethod + def _aws_session_for_task_arn(task_arn: str) -> AwsSession: + """ + Get an AwsSession for the Task ARN. The AWS session should be in the region of the task. + + Returns: + AwsSession: `AwsSession` object with default `boto_session` in task's region + """ + task_region = task_arn.split(":")[3] + boto_session = boto3.Session(region_name=task_region) + return AwsSession(boto_session=boto_session) + @property def id(self) -> str: """str: The ARN of the quantum task.""" @@ -154,11 +178,12 @@ def cancel(self) -> None: def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: """ Get task metadata defined in Amazon Braket. + Args: use_cached_value (bool, optional): 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. + from the Amazon Braket `GetQuantumTask` operation. If `False`, calls the + `GetQuantumTask` operation to retrieve metadata, 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 @@ -171,11 +196,12 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: def state(self, use_cached_value: bool = False) -> str: """ The state of the quantum task. + Args: use_cached_value (bool, optional): 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. + 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`, @@ -189,7 +215,7 @@ def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult """ Get the quantum task result by polling Amazon Braket to see if the task is completed. Once the task is completed, the result is retrieved from S3 and returned as a - `QuantumTaskResult`. + `GateModelQuantumTaskResult` or `AnnealingQuantumTaskResult` This method is a blocking thread call and synchronously returns a result. Call async_result() if you require an asynchronous invocation. @@ -199,6 +225,7 @@ def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult return asyncio.get_event_loop().run_until_complete(self.async_result()) except asyncio.CancelledError: # Future was cancelled, return whatever is in self._result if anything + self._logger.warning("Task future was cancelled") return self._result def async_result(self) -> asyncio.Task: @@ -206,12 +233,16 @@ def async_result(self) -> asyncio.Task: Get the quantum task result asynchronously. Consecutive calls to this method return the result cached from the most recent request. """ - if ( - self._future.done() - and self.metadata(use_cached_value=True).get("status") - not in AwsQuantumTask.TERMINAL_STATES - ): # Future timed out - self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) + if self._future.done() and self._result is None: # timed out and no result + task_status = self.metadata()["status"] + if task_status in self.NO_RESULT_TERMINAL_STATES: + self._logger.warning( + f"Task is in terminal state {task_status} and no result is available" + ) + # Return done future. Don't restart polling. + return self._future + else: + self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) return self._future async def _create_future(self) -> asyncio.Task: @@ -225,6 +256,25 @@ async def _create_future(self) -> asyncio.Task: """ return asyncio.create_task(self._wait_for_completion()) + def _get_results_formatter( + self, + ) -> Union[GateModelQuantumTaskResult.from_string, AnnealingQuantumTaskResult.from_string]: + """ + Get results formatter based on irType of self.metadata() + + Returns: + Union[GateModelQuantumTaskResult.from_string, AnnealingQuantumTaskResult.from_string]: + function that deserializes a string into a results structure + """ + current_metadata = self.metadata() + ir_type = current_metadata["irType"] + if ir_type == AwsQuantumTask.ANNEALING_IR_TYPE: + return AnnealingQuantumTaskResult.from_string + elif ir_type == AwsQuantumTask.GATE_IR_TYPE: + return GateModelQuantumTaskResult.from_string + else: + raise ValueError("Unknown IR type") + async def _wait_for_completion(self) -> GateModelQuantumTaskResult: """ Waits for the quantum task to be completed, then returns the result from the S3 bucket. @@ -232,7 +282,7 @@ async def _wait_for_completion(self) -> GateModelQuantumTaskResult: GateModelQuantumTaskResult: 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.TERMINAL_STATES` but not `AwsQuantumTask.RESULTS_READY_STATES`. + `AwsQuantumTask.NO_RESULT_TERMINAL_STATES`. Note: Timeout and sleep intervals are defined in the constructor fields `poll_timeout_seconds` and `poll_interval_seconds` respectively. @@ -242,14 +292,18 @@ async def _wait_for_completion(self) -> GateModelQuantumTaskResult: while (time.time() - start_time) < self._poll_timeout_seconds: current_metadata = self.metadata() - self._logger.debug(f"Task {self._arn}: task status {current_metadata['status']}") - if current_metadata["status"] in AwsQuantumTask.RESULTS_READY_STATES: + task_status = current_metadata["status"] + self._logger.debug(f"Task {self._arn}: task status {task_status}") + if task_status in AwsQuantumTask.RESULTS_READY_STATES: result_string = self._aws_session.retrieve_s3_object_body( current_metadata["resultsS3Bucket"], current_metadata["resultsS3ObjectKey"] ) - self._result = self._results_formatter(result_string) + self._result = self._get_results_formatter()(result_string) return self._result - elif current_metadata["status"] in AwsQuantumTask.TERMINAL_STATES: + elif task_status in AwsQuantumTask.NO_RESULT_TERMINAL_STATES: + self._logger.warning( + f"Task is in terminal state {task_status}" + "and no result is available" + ) self._result = None return None else: @@ -257,7 +311,8 @@ async def _wait_for_completion(self) -> GateModelQuantumTaskResult: # Timed out self._logger.warning( - f"Task {self._arn}: polling timed out after {time.time()-start_time} secs" + f"Task {self._arn}: polling for task completion timed out after " + + f"{time.time()-start_time} secs" ) self._result = None return None @@ -302,11 +357,8 @@ def _( "backendParameters": {"gateModelParameters": {"qubitCount": circuit.qubit_count}}, } ) - task_arn = aws_session.create_quantum_task(**create_task_kwargs) - return AwsQuantumTask( - task_arn, aws_session, GateModelQuantumTaskResult.from_string, *args, **kwargs - ) + return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) @_create_internal.register @@ -327,9 +379,7 @@ def _( ) task_arn = aws_session.create_quantum_task(**create_task_kwargs) - return AwsQuantumTask( - task_arn, aws_session, AnnealingQuantumTaskResult.from_string, *args, **kwargs - ) + return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) def _create_common_params( 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 d79cc9c8..c7640d61 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -14,7 +14,7 @@ import asyncio import threading import time -from unittest.mock import Mock +from unittest.mock import Mock, patch import pytest from braket.annealing.problem import Problem, ProblemType @@ -30,27 +30,23 @@ @pytest.fixture def aws_session(): mock = Mock() - _mock_state(mock, "RUNNING") + _mock_metadata(mock, "RUNNING") return mock @pytest.fixture def quantum_task(aws_session): - return AwsQuantumTask("foo:bar:arn", aws_session, lambda x: x, poll_timeout_seconds=2) + return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) @pytest.fixture def circuit_task(aws_session): - return AwsQuantumTask( - "foo:bar:arn", aws_session, GateModelQuantumTaskResult.from_string, poll_timeout_seconds=2 - ) + return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) @pytest.fixture def annealing_task(aws_session): - return AwsQuantumTask( - "foo:bar:arn", aws_session, AnnealingQuantumTaskResult.from_string, poll_timeout_seconds=2 - ) + return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) @pytest.fixture @@ -69,9 +65,9 @@ def problem(): def test_equality(arn, aws_session): - quantum_task_1 = AwsQuantumTask(arn, aws_session, lambda x: x) - quantum_task_2 = AwsQuantumTask(arn, aws_session, lambda x: x) - other_quantum_task = AwsQuantumTask("different:arn", aws_session, lambda x: x) + quantum_task_1 = AwsQuantumTask(arn, aws_session) + quantum_task_2 = AwsQuantumTask(arn, aws_session) + other_quantum_task = AwsQuantumTask("different:arn", aws_session) non_quantum_task = quantum_task_1.id assert quantum_task_1 == quantum_task_2 @@ -90,7 +86,7 @@ def test_hash(quantum_task): def test_id_getter(arn, aws_session): - quantum_task = AwsQuantumTask(arn, aws_session, lambda x: x) + quantum_task = AwsQuantumTask(arn, aws_session) assert quantum_task.id == arn @@ -112,12 +108,12 @@ def test_metadata(quantum_task): def test_state(quantum_task): state_1 = "RUNNING" - _mock_state(quantum_task._aws_session, state_1) + _mock_metadata(quantum_task._aws_session, state_1) assert quantum_task.state() == state_1 quantum_task._aws_session.get_quantum_task.assert_called_with(quantum_task.id) state_2 = "COMPLETED" - _mock_state(quantum_task._aws_session, state_2) + _mock_metadata(quantum_task._aws_session, state_2) assert quantum_task.state(use_cached_value=True) == state_1 @@ -133,7 +129,7 @@ def test_cancel(quantum_task): def test_result_circuit(circuit_task): - _mock_state(circuit_task._aws_session, "COMPLETED") + _mock_metadata(circuit_task._aws_session, "COMPLETED") _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_1) expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) @@ -144,8 +140,15 @@ def test_result_circuit(circuit_task): circuit_task._aws_session.retrieve_s3_object_body.assert_called_with(s3_bucket, s3_object_key) +@pytest.mark.xfail(raises=ValueError) +def test_result_unknown_ir_type(circuit_task): + _mock_metadata(circuit_task._aws_session, "COMPLETED", "unsupported_ir_type") + _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_1) + circuit_task.result() + + def test_result_annealing(annealing_task): - _mock_state(annealing_task._aws_session, "COMPLETED") + _mock_metadata(annealing_task._aws_session, "COMPLETED", "annealing") _mock_s3(annealing_task._aws_session, MockS3.MOCK_S3_RESULT_4) expected = AnnealingQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_4) @@ -157,7 +160,7 @@ def test_result_annealing(annealing_task): def test_result_is_cached(circuit_task): - _mock_state(circuit_task._aws_session, "COMPLETED") + _mock_metadata(circuit_task._aws_session, "COMPLETED") _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_1) circuit_task.result() @@ -172,7 +175,7 @@ def set_result_from_callback(future): nonlocal result_from_callback result_from_callback = future.result() - _mock_state(circuit_task._aws_session, "RUNNING") + _mock_metadata(circuit_task._aws_session, "RUNNING") _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_1) future = circuit_task.async_result() @@ -184,7 +187,7 @@ def set_result_from_callback(future): future.add_done_callback(set_result_from_callback) # via asyncio waiting for result - _mock_state(circuit_task._aws_session, "COMPLETED") + _mock_metadata(circuit_task._aws_session, "COMPLETED") event_loop = asyncio.get_event_loop() result_from_waiting = event_loop.run_until_complete(future) @@ -198,30 +201,40 @@ def set_result_from_callback(future): def test_failed_task(quantum_task): - _mock_state(quantum_task._aws_session, "FAILED") + _mock_metadata(quantum_task._aws_session, "FAILED") _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_1) result = quantum_task.result() assert result is None -def test_timeout(aws_session): - _mock_state(aws_session, "RUNNING") +def test_timeout_completed(aws_session): + _mock_metadata(aws_session, "RUNNING") _mock_s3(aws_session, MockS3.MOCK_S3_RESULT_1) # Setup the poll timing such that the timeout will occur after one API poll quantum_task = AwsQuantumTask( - "foo:bar:arn", - aws_session, - GateModelQuantumTaskResult.from_string, - poll_timeout_seconds=0.5, - poll_interval_seconds=1, + "foo:bar:arn", aws_session, poll_timeout_seconds=0.5, poll_interval_seconds=1, ) assert quantum_task.result() is None - - _mock_state(aws_session, "COMPLETED") + _mock_metadata(aws_session, "COMPLETED") + assert quantum_task.state() == "COMPLETED" assert quantum_task.result() == GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) +def test_timeout_no_result_terminal_state(aws_session): + _mock_metadata(aws_session, "RUNNING") + _mock_s3(aws_session, MockS3.MOCK_S3_RESULT_1) + + # Setup the poll timing such that the timeout will occur after one API poll + quantum_task = AwsQuantumTask( + "foo:bar:arn", aws_session, poll_timeout_seconds=0.5, poll_interval_seconds=1, + ) + assert quantum_task.result() is None + + _mock_metadata(aws_session, "FAILED") + assert quantum_task.result() is None + + @pytest.mark.xfail(raises=ValueError) def test_create_invalid_s3_folder(aws_session, arn, circuit): AwsQuantumTask.create(aws_session, arn, circuit, ("bucket",)) @@ -308,6 +321,18 @@ def test_init_new_thread(aws_session, arn): assert len(tasks_list) == 1 +@patch("braket.aws.aws_quantum_task.boto3.Session") +def test_aws_session_for_task_arn(mock_session): + region = "us-west-2" + arn = f"arn:aws:aqx:{region}:account_id:quantum-task:task_id" + mock_boto_session = Mock() + mock_session.return_value = mock_boto_session + mock_boto_session.region_name = region + aws_session = AwsQuantumTask._aws_session_for_task_arn(arn) + mock_session.assert_called_with(region_name=region) + assert aws_session.boto_session == mock_boto_session + + def _init_and_add_to_list(aws_session, arn, task_list): task_list.append(AwsQuantumTask(arn, aws_session, GateModelQuantumTaskResult.from_string)) @@ -328,11 +353,12 @@ def _assert_create_quantum_task_called_with( ) -def _mock_state(aws_session, state): +def _mock_metadata(aws_session, state, irType="jaqcd"): return_value = { "status": state, "resultsS3Bucket": S3_TARGET.bucket, "resultsS3ObjectKey": S3_TARGET.key, + "irType": irType, } aws_session.get_quantum_task.return_value = return_value From 41b0ae4b236e1aefe742f504716d202033092eb8 Mon Sep 17 00:00:00 2001 From: Ava Wang Date: Fri, 10 Apr 2020 17:08:34 -0700 Subject: [PATCH 0077/1165] Minor changes --- src/braket/aws/aws_quantum_task.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 2aa6ff8e..a456b09e 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -238,8 +238,6 @@ def async_result(self) -> asyncio.Task: self._logger.warning( f"Task is in terminal state {task_status} and no result is available" ) - # Return done future. Don't restart polling. - return self._future else: self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) return self._future @@ -274,13 +272,17 @@ def _get_results_formatter( else: raise ValueError("Unknown IR type") - async def _wait_for_completion(self) -> GateModelQuantumTaskResult: + async def _wait_for_completion( + self, + ) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: """ Waits for the quantum task to be completed, then returns the result from the S3 bucket. + Returns: - GateModelQuantumTaskResult: 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 + Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: 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 @@ -301,7 +303,7 @@ async def _wait_for_completion(self) -> GateModelQuantumTaskResult: return self._result elif task_status in AwsQuantumTask.NO_RESULT_TERMINAL_STATES: self._logger.warning( - f"Task is in terminal state {task_status}" + "and no result is available" + f"Task is in terminal state {task_status} and no result is available" ) self._result = None return None From 0b0d80446d077728fc9f9bae65fddc74a1c29c37 Mon Sep 17 00:00:00 2001 From: Ava Wang Date: Wed, 8 Apr 2020 15:10:36 -0700 Subject: [PATCH 0078/1165] Reduce required arguments for AwsQuantumTask and fix result caching bug --- setup.py | 2 +- src/braket/aws/aws_quantum_task.py | 124 ++++++++++++------ .../braket/aws/test_aws_quantum_task.py | 88 ++++++++----- 3 files changed, 145 insertions(+), 69 deletions(-) diff --git a/setup.py b/setup.py index 3008db1b..72ada0e1 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="braket-sdk", - version="0.3.2", + version="0.3.4", license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 44d76f24..2efcfdd0 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -17,8 +17,9 @@ import time from functools import singledispatch from logging import Logger, getLogger -from typing import Any, Callable, Dict, Optional, Union +from typing import Any, Dict, Optional, Union +import boto3 from braket.annealing.problem import Problem from braket.aws.aws_session import AwsSession from braket.circuits.circuit import Circuit @@ -27,10 +28,10 @@ class AwsQuantumTask(QuantumTask): """Amazon Braket implementation of a quantum task. A task can be a circuit or an annealing - problem. Currently, only circuits are supported in the Private Beta.""" + problem.""" # TODO: Add API documentation that defines these states. Make it clear this is the contract. - TERMINAL_STATES = {"COMPLETED", "FAILED", "CANCELLED"} + NO_RESULT_TERMINAL_STATES = {"FAILED", "CANCELLED"} RESULTS_READY_STATES = {"COMPLETED"} GATE_IR_TYPE = "jaqcd" @@ -73,7 +74,7 @@ def create( backend_parameters (Dict[str, Any]): Additional parameters to send to the device. For example, for D-Wave: - >>> backend_parameters = {"dWaveParameters": {"postprocess": "OPTIMIZATION"}} + `{"dWaveParameters": {"postprocessingType": "OPTIMIZATION"}}` Returns: AwsQuantumTask: AwsQuantumTask tracking the task execution on the device. @@ -105,8 +106,7 @@ def create( def __init__( self, arn: str, - aws_session: AwsSession, - results_formatter: Callable[[str], Any], + aws_session: AwsSession = None, poll_timeout_seconds: int = DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: int = DEFAULT_RESULTS_POLL_INTERVAL, logger: Logger = getLogger(__name__), @@ -114,19 +114,31 @@ def __init__( """ Args: arn (str): The ARN of the task. - aws_session (AwsSession): The `AwsSession` for connecting to AWS services. - results_formatter (Callable[[str], Any]): A function that deserializes a string - into a results structure (such as `GateModelQuantumTaskResult`) + aws_session (AwsSession, optional): The `AwsSession` for connecting to AWS services. + Default is `None`, in which case an `AwsSession` object will be created with the + region of the task. poll_timeout_seconds (int): The polling timeout for result(), default is 120 seconds. poll_interval_seconds (int): The polling interval for result(), default is 0.25 seconds. logger (Logger): Logger object with which to write logs, such as task statuses while waiting for task to be in a terminal state. Default is `getLogger(__name__)` + + Examples: + >>> task = AwsQuantumTask(arn='task_arn') + >>> task.state() + 'COMPLETED' + >>> result = task.result() + AnnealingQuantumTaskResult(...) + + >>> task = AwsQuantumTask(arn='task_arn', poll_timeout_seconds=300) + >>> result = task.result() + GateModelQuantumTaskResult(...) """ self._arn: str = arn - self._aws_session: AwsSession = aws_session - self._results_formatter = results_formatter + self._aws_session: AwsSession = aws_session or AwsQuantumTask._aws_session_for_task_arn( + task_arn=arn + ) self._poll_timeout_seconds = poll_timeout_seconds self._poll_interval_seconds = poll_interval_seconds self._logger = logger @@ -141,6 +153,18 @@ def __init__( asyncio.set_event_loop(asyncio.new_event_loop()) self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) + @staticmethod + def _aws_session_for_task_arn(task_arn: str) -> AwsSession: + """ + Get an AwsSession for the Task ARN. The AWS session should be in the region of the task. + + Returns: + AwsSession: `AwsSession` object with default `boto_session` in task's region + """ + task_region = task_arn.split(":")[3] + boto_session = boto3.Session(region_name=task_region) + return AwsSession(boto_session=boto_session) + @property def id(self) -> str: """str: The ARN of the quantum task.""" @@ -154,11 +178,12 @@ def cancel(self) -> None: def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: """ Get task metadata defined in Amazon Braket. + Args: use_cached_value (bool, optional): 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. + from the Amazon Braket `GetQuantumTask` operation. If `False`, calls the + `GetQuantumTask` operation to retrieve metadata, 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 @@ -171,11 +196,12 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: def state(self, use_cached_value: bool = False) -> str: """ The state of the quantum task. + Args: use_cached_value (bool, optional): 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. + 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`, @@ -189,7 +215,7 @@ def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult """ Get the quantum task result by polling Amazon Braket to see if the task is completed. Once the task is completed, the result is retrieved from S3 and returned as a - `QuantumTaskResult`. + `GateModelQuantumTaskResult` or `AnnealingQuantumTaskResult` This method is a blocking thread call and synchronously returns a result. Call async_result() if you require an asynchronous invocation. @@ -199,6 +225,7 @@ def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult return asyncio.get_event_loop().run_until_complete(self.async_result()) except asyncio.CancelledError: # Future was cancelled, return whatever is in self._result if anything + self._logger.warning("Task future was cancelled") return self._result def async_result(self) -> asyncio.Task: @@ -206,12 +233,16 @@ def async_result(self) -> asyncio.Task: Get the quantum task result asynchronously. Consecutive calls to this method return the result cached from the most recent request. """ - if ( - self._future.done() - and self.metadata(use_cached_value=True).get("status") - not in AwsQuantumTask.TERMINAL_STATES - ): # Future timed out - self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) + if self._future.done() and self._result is None: # timed out and no result + task_status = self.metadata()["status"] + if task_status in self.NO_RESULT_TERMINAL_STATES: + self._logger.warning( + f"Task is in terminal state {task_status} and no result is available" + ) + # Return done future. Don't restart polling. + return self._future + else: + self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) return self._future async def _create_future(self) -> asyncio.Task: @@ -225,6 +256,25 @@ async def _create_future(self) -> asyncio.Task: """ return asyncio.create_task(self._wait_for_completion()) + def _get_results_formatter( + self, + ) -> Union[GateModelQuantumTaskResult.from_string, AnnealingQuantumTaskResult.from_string]: + """ + Get results formatter based on irType of self.metadata() + + Returns: + Union[GateModelQuantumTaskResult.from_string, AnnealingQuantumTaskResult.from_string]: + function that deserializes a string into a results structure + """ + current_metadata = self.metadata() + ir_type = current_metadata["irType"] + if ir_type == AwsQuantumTask.ANNEALING_IR_TYPE: + return AnnealingQuantumTaskResult.from_string + elif ir_type == AwsQuantumTask.GATE_IR_TYPE: + return GateModelQuantumTaskResult.from_string + else: + raise ValueError("Unknown IR type") + async def _wait_for_completion(self) -> GateModelQuantumTaskResult: """ Waits for the quantum task to be completed, then returns the result from the S3 bucket. @@ -232,7 +282,7 @@ async def _wait_for_completion(self) -> GateModelQuantumTaskResult: GateModelQuantumTaskResult: 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.TERMINAL_STATES` but not `AwsQuantumTask.RESULTS_READY_STATES`. + `AwsQuantumTask.NO_RESULT_TERMINAL_STATES`. Note: Timeout and sleep intervals are defined in the constructor fields `poll_timeout_seconds` and `poll_interval_seconds` respectively. @@ -242,14 +292,18 @@ async def _wait_for_completion(self) -> GateModelQuantumTaskResult: while (time.time() - start_time) < self._poll_timeout_seconds: current_metadata = self.metadata() - self._logger.debug(f"Task {self._arn}: task status {current_metadata['status']}") - if current_metadata["status"] in AwsQuantumTask.RESULTS_READY_STATES: + task_status = current_metadata["status"] + self._logger.debug(f"Task {self._arn}: task status {task_status}") + if task_status in AwsQuantumTask.RESULTS_READY_STATES: result_string = self._aws_session.retrieve_s3_object_body( current_metadata["resultsS3Bucket"], current_metadata["resultsS3ObjectKey"] ) - self._result = self._results_formatter(result_string) + self._result = self._get_results_formatter()(result_string) return self._result - elif current_metadata["status"] in AwsQuantumTask.TERMINAL_STATES: + elif task_status in AwsQuantumTask.NO_RESULT_TERMINAL_STATES: + self._logger.warning( + f"Task is in terminal state {task_status}" + "and no result is available" + ) self._result = None return None else: @@ -257,7 +311,8 @@ async def _wait_for_completion(self) -> GateModelQuantumTaskResult: # Timed out self._logger.warning( - f"Task {self._arn}: polling timed out after {time.time()-start_time} secs" + f"Task {self._arn}: polling for task completion timed out after " + + f"{time.time()-start_time} secs" ) self._result = None return None @@ -302,11 +357,8 @@ def _( "backendParameters": {"gateModelParameters": {"qubitCount": circuit.qubit_count}}, } ) - task_arn = aws_session.create_quantum_task(**create_task_kwargs) - return AwsQuantumTask( - task_arn, aws_session, GateModelQuantumTaskResult.from_string, *args, **kwargs - ) + return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) @_create_internal.register @@ -327,9 +379,7 @@ def _( ) task_arn = aws_session.create_quantum_task(**create_task_kwargs) - return AwsQuantumTask( - task_arn, aws_session, AnnealingQuantumTaskResult.from_string, *args, **kwargs - ) + return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) def _create_common_params( 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 d79cc9c8..c7640d61 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -14,7 +14,7 @@ import asyncio import threading import time -from unittest.mock import Mock +from unittest.mock import Mock, patch import pytest from braket.annealing.problem import Problem, ProblemType @@ -30,27 +30,23 @@ @pytest.fixture def aws_session(): mock = Mock() - _mock_state(mock, "RUNNING") + _mock_metadata(mock, "RUNNING") return mock @pytest.fixture def quantum_task(aws_session): - return AwsQuantumTask("foo:bar:arn", aws_session, lambda x: x, poll_timeout_seconds=2) + return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) @pytest.fixture def circuit_task(aws_session): - return AwsQuantumTask( - "foo:bar:arn", aws_session, GateModelQuantumTaskResult.from_string, poll_timeout_seconds=2 - ) + return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) @pytest.fixture def annealing_task(aws_session): - return AwsQuantumTask( - "foo:bar:arn", aws_session, AnnealingQuantumTaskResult.from_string, poll_timeout_seconds=2 - ) + return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) @pytest.fixture @@ -69,9 +65,9 @@ def problem(): def test_equality(arn, aws_session): - quantum_task_1 = AwsQuantumTask(arn, aws_session, lambda x: x) - quantum_task_2 = AwsQuantumTask(arn, aws_session, lambda x: x) - other_quantum_task = AwsQuantumTask("different:arn", aws_session, lambda x: x) + quantum_task_1 = AwsQuantumTask(arn, aws_session) + quantum_task_2 = AwsQuantumTask(arn, aws_session) + other_quantum_task = AwsQuantumTask("different:arn", aws_session) non_quantum_task = quantum_task_1.id assert quantum_task_1 == quantum_task_2 @@ -90,7 +86,7 @@ def test_hash(quantum_task): def test_id_getter(arn, aws_session): - quantum_task = AwsQuantumTask(arn, aws_session, lambda x: x) + quantum_task = AwsQuantumTask(arn, aws_session) assert quantum_task.id == arn @@ -112,12 +108,12 @@ def test_metadata(quantum_task): def test_state(quantum_task): state_1 = "RUNNING" - _mock_state(quantum_task._aws_session, state_1) + _mock_metadata(quantum_task._aws_session, state_1) assert quantum_task.state() == state_1 quantum_task._aws_session.get_quantum_task.assert_called_with(quantum_task.id) state_2 = "COMPLETED" - _mock_state(quantum_task._aws_session, state_2) + _mock_metadata(quantum_task._aws_session, state_2) assert quantum_task.state(use_cached_value=True) == state_1 @@ -133,7 +129,7 @@ def test_cancel(quantum_task): def test_result_circuit(circuit_task): - _mock_state(circuit_task._aws_session, "COMPLETED") + _mock_metadata(circuit_task._aws_session, "COMPLETED") _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_1) expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) @@ -144,8 +140,15 @@ def test_result_circuit(circuit_task): circuit_task._aws_session.retrieve_s3_object_body.assert_called_with(s3_bucket, s3_object_key) +@pytest.mark.xfail(raises=ValueError) +def test_result_unknown_ir_type(circuit_task): + _mock_metadata(circuit_task._aws_session, "COMPLETED", "unsupported_ir_type") + _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_1) + circuit_task.result() + + def test_result_annealing(annealing_task): - _mock_state(annealing_task._aws_session, "COMPLETED") + _mock_metadata(annealing_task._aws_session, "COMPLETED", "annealing") _mock_s3(annealing_task._aws_session, MockS3.MOCK_S3_RESULT_4) expected = AnnealingQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_4) @@ -157,7 +160,7 @@ def test_result_annealing(annealing_task): def test_result_is_cached(circuit_task): - _mock_state(circuit_task._aws_session, "COMPLETED") + _mock_metadata(circuit_task._aws_session, "COMPLETED") _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_1) circuit_task.result() @@ -172,7 +175,7 @@ def set_result_from_callback(future): nonlocal result_from_callback result_from_callback = future.result() - _mock_state(circuit_task._aws_session, "RUNNING") + _mock_metadata(circuit_task._aws_session, "RUNNING") _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_1) future = circuit_task.async_result() @@ -184,7 +187,7 @@ def set_result_from_callback(future): future.add_done_callback(set_result_from_callback) # via asyncio waiting for result - _mock_state(circuit_task._aws_session, "COMPLETED") + _mock_metadata(circuit_task._aws_session, "COMPLETED") event_loop = asyncio.get_event_loop() result_from_waiting = event_loop.run_until_complete(future) @@ -198,30 +201,40 @@ def set_result_from_callback(future): def test_failed_task(quantum_task): - _mock_state(quantum_task._aws_session, "FAILED") + _mock_metadata(quantum_task._aws_session, "FAILED") _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_1) result = quantum_task.result() assert result is None -def test_timeout(aws_session): - _mock_state(aws_session, "RUNNING") +def test_timeout_completed(aws_session): + _mock_metadata(aws_session, "RUNNING") _mock_s3(aws_session, MockS3.MOCK_S3_RESULT_1) # Setup the poll timing such that the timeout will occur after one API poll quantum_task = AwsQuantumTask( - "foo:bar:arn", - aws_session, - GateModelQuantumTaskResult.from_string, - poll_timeout_seconds=0.5, - poll_interval_seconds=1, + "foo:bar:arn", aws_session, poll_timeout_seconds=0.5, poll_interval_seconds=1, ) assert quantum_task.result() is None - - _mock_state(aws_session, "COMPLETED") + _mock_metadata(aws_session, "COMPLETED") + assert quantum_task.state() == "COMPLETED" assert quantum_task.result() == GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) +def test_timeout_no_result_terminal_state(aws_session): + _mock_metadata(aws_session, "RUNNING") + _mock_s3(aws_session, MockS3.MOCK_S3_RESULT_1) + + # Setup the poll timing such that the timeout will occur after one API poll + quantum_task = AwsQuantumTask( + "foo:bar:arn", aws_session, poll_timeout_seconds=0.5, poll_interval_seconds=1, + ) + assert quantum_task.result() is None + + _mock_metadata(aws_session, "FAILED") + assert quantum_task.result() is None + + @pytest.mark.xfail(raises=ValueError) def test_create_invalid_s3_folder(aws_session, arn, circuit): AwsQuantumTask.create(aws_session, arn, circuit, ("bucket",)) @@ -308,6 +321,18 @@ def test_init_new_thread(aws_session, arn): assert len(tasks_list) == 1 +@patch("braket.aws.aws_quantum_task.boto3.Session") +def test_aws_session_for_task_arn(mock_session): + region = "us-west-2" + arn = f"arn:aws:aqx:{region}:account_id:quantum-task:task_id" + mock_boto_session = Mock() + mock_session.return_value = mock_boto_session + mock_boto_session.region_name = region + aws_session = AwsQuantumTask._aws_session_for_task_arn(arn) + mock_session.assert_called_with(region_name=region) + assert aws_session.boto_session == mock_boto_session + + def _init_and_add_to_list(aws_session, arn, task_list): task_list.append(AwsQuantumTask(arn, aws_session, GateModelQuantumTaskResult.from_string)) @@ -328,11 +353,12 @@ def _assert_create_quantum_task_called_with( ) -def _mock_state(aws_session, state): +def _mock_metadata(aws_session, state, irType="jaqcd"): return_value = { "status": state, "resultsS3Bucket": S3_TARGET.bucket, "resultsS3ObjectKey": S3_TARGET.key, + "irType": irType, } aws_session.get_quantum_task.return_value = return_value From bf9d9ec0339505b3962d57593b4b35b9ed534e17 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Tue, 21 Apr 2020 11:06:58 -0700 Subject: [PATCH 0079/1165] Use .construct() for creating IR objects (#67) --- src/braket/circuits/circuit.py | 2 +- src/braket/circuits/gates.py | 64 +++++++++---------- src/braket/circuits/result_types.py | 36 +++++++---- .../braket/circuits/test_result_types.py | 6 ++ 4 files changed, 64 insertions(+), 44 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index b3392989..babc7274 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -433,7 +433,7 @@ def to_ir(self) -> Program: """ ir_instructions = [instr.to_ir() for instr in self.instructions] ir_results = [result_type.to_ir() for result_type in self.result_types] - return Program(instructions=ir_instructions, results=ir_results) + return Program.construct(instructions=ir_instructions, results=ir_results) def _copy(self) -> Circuit: """ diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 91c234b8..bfc1c8cd 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -46,7 +46,7 @@ def __init__(self): super().__init__(qubit_count=1, ascii_symbols=["H"]) def to_ir(self, target: QubitSet): - return ir.H(target=target[0]) + return ir.H.construct(target=target[0]) def to_matrix(self) -> np.ndarray: return 1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) @@ -79,7 +79,7 @@ def __init__(self): super().__init__(qubit_count=1, ascii_symbols=["I"]) def to_ir(self, target: QubitSet): - return ir.I(target=target[0]) + return ir.I.construct(target=target[0]) def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, 1.0]], dtype=complex) @@ -112,7 +112,7 @@ def __init__(self): super().__init__(qubit_count=1, ascii_symbols=["X"]) def to_ir(self, target: QubitSet): - return ir.X(target=target[0]) + return ir.X.construct(target=target[0]) def to_matrix(self) -> np.ndarray: return np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) @@ -145,7 +145,7 @@ def __init__(self): super().__init__(qubit_count=1, ascii_symbols=["Y"]) def to_ir(self, target: QubitSet): - return ir.Y(target=target[0]) + return ir.Y.construct(target=target[0]) def to_matrix(self) -> np.ndarray: return np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) @@ -178,7 +178,7 @@ def __init__(self): super().__init__(qubit_count=1, ascii_symbols=["Z"]) def to_ir(self, target: QubitSet): - return ir.Z(target=target[0]) + return ir.Z.construct(target=target[0]) def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) @@ -211,7 +211,7 @@ def __init__(self): super().__init__(qubit_count=1, ascii_symbols=["S"]) def to_ir(self, target: QubitSet): - return ir.S(target=target[0]) + return ir.S.construct(target=target[0]) def to_matrix(self) -> np.ndarray: @@ -245,7 +245,7 @@ def __init__(self): super().__init__(qubit_count=1, ascii_symbols=["Si"]) def to_ir(self, target: QubitSet): - return ir.Si(target=target[0]) + return ir.Si.construct(target=target[0]) def to_matrix(self) -> np.ndarray: return np.array([[1, 0], [0, -1j]], dtype=complex) @@ -278,7 +278,7 @@ def __init__(self): super().__init__(qubit_count=1, ascii_symbols=["T"]) def to_ir(self, target: QubitSet): - return ir.T(target=target[0]) + return ir.T.construct(target=target[0]) def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, np.exp(1j * np.pi / 4)]], dtype=complex) @@ -311,7 +311,7 @@ def __init__(self): super().__init__(qubit_count=1, ascii_symbols=["Ti"]) def to_ir(self, target: QubitSet): - return ir.Ti(target=target[0]) + return ir.Ti.construct(target=target[0]) def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, np.exp(-1j * np.pi / 4)]], dtype=complex) @@ -344,7 +344,7 @@ def __init__(self): super().__init__(qubit_count=1, ascii_symbols=["V"]) def to_ir(self, target: QubitSet): - return ir.V(target=target[0]) + return ir.V.construct(target=target[0]) def to_matrix(self) -> np.ndarray: return np.array([[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]], dtype=complex) @@ -377,7 +377,7 @@ def __init__(self): super().__init__(qubit_count=1, ascii_symbols=["Vi"]) def to_ir(self, target: QubitSet): - return ir.Vi(target=target[0]) + return ir.Vi.construct(target=target[0]) def to_matrix(self) -> np.ndarray: return np.array(([[0.5 - 0.5j, 0.5 + 0.5j], [0.5 + 0.5j, 0.5 - 0.5j]]), dtype=complex) @@ -417,7 +417,7 @@ def __init__(self, angle: float): super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Rx({:.3g})".format(angle)]) def to_ir(self, target: QubitSet): - return ir.Rx(target=target[0], angle=self.angle) + return ir.Rx.construct(target=target[0], angle=self.angle) def to_matrix(self) -> np.ndarray: cos = np.cos(self.angle / 2) @@ -456,7 +456,7 @@ def __init__(self, angle: float): super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Ry({:.3g})".format(angle)]) def to_ir(self, target: QubitSet): - return ir.Ry(target=target[0], angle=self.angle) + return ir.Ry.construct(target=target[0], angle=self.angle) def to_matrix(self) -> np.ndarray: cos = np.cos(self.angle / 2) @@ -495,7 +495,7 @@ def __init__(self, angle: float): super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Rz({:.3g})".format(angle)]) def to_ir(self, target: QubitSet): - return ir.Rz(target=target[0], angle=self.angle) + return ir.Rz.construct(target=target[0], angle=self.angle) def to_matrix(self) -> np.ndarray: return np.array( @@ -534,7 +534,7 @@ def __init__(self, angle: float): super().__init__(angle=angle, qubit_count=1, ascii_symbols=["PHASE({:.3g})".format(angle)]) def to_ir(self, target: QubitSet): - return ir.PhaseShift(target=target[0], angle=self.angle) + return ir.PhaseShift.construct(target=target[0], angle=self.angle) def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, np.exp(1j * self.angle)]], dtype=complex) @@ -570,7 +570,7 @@ def __init__(self): super().__init__(qubit_count=2, ascii_symbols=["C", "X"]) def to_ir(self, target: QubitSet): - return ir.CNot(control=target[0], target=target[1]) + return ir.CNot.construct(control=target[0], target=target[1]) def to_matrix(self) -> np.ndarray: return np.array( @@ -611,7 +611,7 @@ def __init__(self): super().__init__(qubit_count=2, ascii_symbols=["SWAP", "SWAP"]) def to_ir(self, target: QubitSet): - return ir.Swap(targets=[target[0], target[1]]) + return ir.Swap.construct(targets=[target[0], target[1]]) def to_matrix(self) -> np.ndarray: return np.array( @@ -652,7 +652,7 @@ def __init__(self): super().__init__(qubit_count=2, ascii_symbols=["ISWAP", "ISWAP"]) def to_ir(self, target: QubitSet): - return ir.ISwap(targets=[target[0], target[1]]) + return ir.ISwap.construct(targets=[target[0], target[1]]) def to_matrix(self) -> np.ndarray: return np.array( @@ -701,7 +701,7 @@ def __init__(self, angle: float): ) def to_ir(self, target: QubitSet): - return ir.PSwap(targets=[target[0], target[1]], angle=self.angle) + return ir.PSwap.construct(targets=[target[0], target[1]], angle=self.angle) def to_matrix(self) -> np.ndarray: return np.array( @@ -750,7 +750,7 @@ def __init__(self, angle: float): ) def to_ir(self, target: QubitSet): - return ir.XY(targets=[target[0], target[1]], angle=self.angle) + return ir.XY.construct(targets=[target[0], target[1]], angle=self.angle) def to_matrix(self) -> np.ndarray: cos = np.cos(self.angle / 2) @@ -799,7 +799,7 @@ def __init__(self, angle: float): ) def to_ir(self, target: QubitSet): - return ir.CPhaseShift(control=target[0], target=target[1], angle=self.angle) + return ir.CPhaseShift.construct(control=target[0], target=target[1], angle=self.angle) def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, 1.0, np.exp(1j * self.angle)]) @@ -839,7 +839,7 @@ def __init__(self, angle: float): ) def to_ir(self, target: QubitSet): - return ir.CPhaseShift00(control=target[0], target=target[1], angle=self.angle) + return ir.CPhaseShift00.construct(control=target[0], target=target[1], angle=self.angle) def to_matrix(self) -> np.ndarray: return np.diag([np.exp(1j * self.angle), 1.0, 1.0, 1.0]) @@ -879,7 +879,7 @@ def __init__(self, angle: float): ) def to_ir(self, target: QubitSet): - return ir.CPhaseShift01(control=target[0], target=target[1], angle=self.angle) + return ir.CPhaseShift01.construct(control=target[0], target=target[1], angle=self.angle) def to_matrix(self) -> np.ndarray: return np.diag([1.0, np.exp(1j * self.angle), 1.0, 1.0]) @@ -919,7 +919,7 @@ def __init__(self, angle: float): ) def to_ir(self, target: QubitSet): - return ir.CPhaseShift10(control=target[0], target=target[1], angle=self.angle) + return ir.CPhaseShift10.construct(control=target[0], target=target[1], angle=self.angle) def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, np.exp(1j * self.angle), 1.0]) @@ -953,7 +953,7 @@ def __init__(self): super().__init__(qubit_count=2, ascii_symbols=["C", "Y"]) def to_ir(self, target: QubitSet): - return ir.CY(control=target[0], target=target[1]) + return ir.CY.construct(control=target[0], target=target[1]) def to_matrix(self) -> np.ndarray: return np.array( @@ -994,7 +994,7 @@ def __init__(self): super().__init__(qubit_count=2, ascii_symbols=["C", "Z"]) def to_ir(self, target: QubitSet): - return ir.CZ(control=target[0], target=target[1]) + return ir.CZ.construct(control=target[0], target=target[1]) def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, 1.0, -1.0]) @@ -1035,7 +1035,7 @@ def __init__(self, angle: float): ) def to_ir(self, target: QubitSet): - return ir.XX(targets=[target[0], target[1]], angle=self.angle) + return ir.XX.construct(targets=[target[0], target[1]], angle=self.angle) def to_matrix(self) -> np.ndarray: return (1 / math.sqrt(2)) * np.array( @@ -1085,7 +1085,7 @@ def __init__(self, angle: float): ) def to_ir(self, target: QubitSet): - return ir.YY(targets=[target[0], target[1]], angle=self.angle) + return ir.YY.construct(targets=[target[0], target[1]], angle=self.angle) def to_matrix(self) -> np.ndarray: cos = np.cos(self.angle) @@ -1137,7 +1137,7 @@ def __init__(self, angle: float): ) def to_ir(self, target: QubitSet): - return ir.ZZ(targets=[target[0], target[1]], angle=self.angle) + return ir.ZZ.construct(targets=[target[0], target[1]], angle=self.angle) def to_matrix(self) -> np.ndarray: return np.array( @@ -1182,7 +1182,7 @@ def __init__(self): super().__init__(qubit_count=3, ascii_symbols=["C", "C", "X"]) def to_ir(self, target: QubitSet): - return ir.CCNot(controls=[target[0], target[1]], target=target[2]) + return ir.CCNot.construct(controls=[target[0], target[1]], target=target[2]) def to_matrix(self) -> np.ndarray: return np.array( @@ -1228,7 +1228,7 @@ def __init__(self): super().__init__(qubit_count=3, ascii_symbols=["C", "SWAP", "SWAP"]) def to_ir(self, target: QubitSet): - return ir.CSwap(control=target[0], targets=[target[1], target[2]]) + return ir.CSwap.construct(control=target[0], targets=[target[1], target[2]]) def to_matrix(self) -> np.ndarray: return np.array( @@ -1295,7 +1295,7 @@ def to_matrix(self): return np.array(self._matrix) def to_ir(self, target: QubitSet): - return ir.Unitary( + return ir.Unitary.construct( targets=[qubit for qubit in target], matrix=Unitary._transform_matrix_to_ir(self._matrix), ) diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index bb851bd1..2b3a6ddf 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -13,6 +13,7 @@ from __future__ import annotations +import re from typing import List import braket.ir.jaqcd as ir @@ -39,7 +40,7 @@ def __init__(self): super().__init__(ascii_symbol=["StateVector"]) def to_ir(self) -> ir.StateVector: - return ir.StateVector() + return ir.StateVector.construct() @staticmethod @circuit.subroutine(register=True) @@ -81,8 +82,12 @@ def __init__(self, state: List[str]): >>> ResultType.Amplitude(state=['01', '10']) """ super().__init__(ascii_symbol=["Amplitude"]) - if not state: - raise ValueError("A non-empty list of states must be specified e.g. ['01', '10']") + if not state or not all( + isinstance(amplitude, str) and re.fullmatch("^[01]+$", amplitude) for amplitude in state + ): + raise ValueError( + "A non-empty list of states must be specified in binary encoding e.g. ['01', '10']" + ) self._state = state @property @@ -90,7 +95,7 @@ def state(self) -> List[str]: return self._state def to_ir(self) -> ir.Amplitude: - return ir.Amplitude(states=self.state) + return ir.Amplitude.construct(states=self.state) @staticmethod @circuit.subroutine(register=True) @@ -151,7 +156,10 @@ def target(self, target: QubitSetInput) -> None: self._target = QubitSet(target) def to_ir(self) -> ir.Probability: - return ir.Probability(targets=list(self.target)) if self.target else ir.Probability() + if self.target: + return ir.Probability.construct(targets=list(self.target)) + else: + return ir.Probability.construct() @staticmethod @circuit.subroutine(register=True) @@ -277,9 +285,11 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): def to_ir(self) -> ir.Expectation: if self.target: - return ir.Expectation(observable=self.observable.to_ir(), targets=list(self.target)) + return ir.Expectation.construct( + observable=self.observable.to_ir(), targets=list(self.target) + ) else: - return ir.Expectation(observable=self.observable.to_ir()) + return ir.Expectation.construct(observable=self.observable.to_ir()) @staticmethod @circuit.subroutine(register=True) @@ -336,9 +346,11 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): def to_ir(self) -> ir.Sample: if self.target: - return ir.Sample(observable=self.observable.to_ir(), targets=list(self.target)) + return ir.Sample.construct( + observable=self.observable.to_ir(), targets=list(self.target) + ) else: - return ir.Sample(observable=self.observable.to_ir()) + return ir.Sample.construct(observable=self.observable.to_ir()) @staticmethod @circuit.subroutine(register=True) @@ -395,9 +407,11 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): def to_ir(self) -> ir.Variance: if self.target: - return ir.Variance(observable=self.observable.to_ir(), targets=list(self.target)) + return ir.Variance.construct( + observable=self.observable.to_ir(), targets=list(self.target) + ) else: - return ir.Variance(observable=self.observable.to_ir()) + return ir.Variance.construct(observable=self.observable.to_ir()) @staticmethod @circuit.subroutine(register=True) diff --git a/test/unit_tests/braket/circuits/test_result_types.py b/test/unit_tests/braket/circuits/test_result_types.py index 3c4f317b..99b0584f 100644 --- a/test/unit_tests/braket/circuits/test_result_types.py +++ b/test/unit_tests/braket/circuits/test_result_types.py @@ -115,6 +115,12 @@ def test_amplitude_init_value_error(): ResultType.Amplitude(state=None) +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("state", ((["2", "11"], [1, 0], [0.1, 0], ["", ""]))) +def test_amplitude_init_invalid_state_value_error(state): + ResultType.Amplitude(state=state) + + def test_amplitude_equality(): a1 = ResultType.Amplitude(state=["0", "1"]) a2 = ResultType.Amplitude(state=["0", "1"]) From 5171634eb852d3b5985c3d02765957040d18d789 Mon Sep 17 00:00:00 2001 From: Ava Wang Date: Wed, 22 Apr 2020 13:33:09 -0700 Subject: [PATCH 0080/1165] Update unit tests for numpy release --- test/unit_tests/braket/circuits/test_gates.py | 1 - .../braket/circuits/test_observables.py | 1 - .../circuits/test_quantum_operator_helpers.py | 20 +++++++++++++++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 74433dbb..10469d16 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -107,7 +107,6 @@ (np.array([[0, 1, 2], [2, 3]])), (np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])), (np.array([[0, 1], [1, 1]])), - (np.array([[0, 1], ["a", 0]])), ] diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index ff6c82ea..b91b2da9 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -30,7 +30,6 @@ (np.array([[0, 1], [1, 2], [3, 4]])), (np.array([[0, 1, 2], [2, 3]])), (np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])), - (np.array([[0, 1], ["a", 0]])), (Gate.T().to_matrix()), ] diff --git a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py index 37f8f363..164e6c44 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py @@ -18,9 +18,11 @@ (np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])), ] -invalid_unitary_matrices = [(np.array([[0, 1], [1, 1]])), (np.array([[1, 2], [3, 4]]))] +invalid_unitary_matrices_false = [(np.array([[0, 1], [1, 1]])), (np.array([[1, 2], [3, 4]]))] -invalid_hermitian_matrices = [(np.array([[1, 0], [0, 1j]])), (np.array([[1, 2], [3, 4]]))] +invalid_hermitian_matrices_false = [(np.array([[1, 0], [0, 1j]])), (np.array([[1, 2], [3, 4]]))] + +invalid_matrix_type_error = np.array([[0, 1], ["a", 0]]) def test_verify_quantum_operator_matrix_dimensions(): @@ -45,11 +47,21 @@ def test_verify_quantum_operator_matrix_dimensions_value_error(matrix): verify_quantum_operator_matrix_dimensions(matrix) -@pytest.mark.parametrize("matrix", invalid_unitary_matrices) +@pytest.mark.parametrize("matrix", invalid_unitary_matrices_false) def test_is_unitary_false(matrix): assert not is_unitary(matrix) -@pytest.mark.parametrize("matrix", invalid_hermitian_matrices) +@pytest.mark.parametrize("matrix", invalid_hermitian_matrices_false) def test_is_hermitian_false(matrix): assert not is_hermitian(matrix) + + +@pytest.mark.xfail(raises=Exception) +def test_is_hermitian_type_error(): + is_hermitian(invalid_matrix_type_error) + + +@pytest.mark.xfail(raises=Exception) +def test_is_unitary_type_error(): + is_unitary(invalid_matrix_type_error) From d30ef571a68c8de34d083d928e7e369a456660da Mon Sep 17 00:00:00 2001 From: Ava Wang Date: Wed, 22 Apr 2020 13:40:13 -0700 Subject: [PATCH 0081/1165] Minor change in test names --- .../braket/circuits/test_quantum_operator_helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py index 164e6c44..065f01a6 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py @@ -58,10 +58,10 @@ def test_is_hermitian_false(matrix): @pytest.mark.xfail(raises=Exception) -def test_is_hermitian_type_error(): +def test_is_hermitian_exception(): is_hermitian(invalid_matrix_type_error) @pytest.mark.xfail(raises=Exception) -def test_is_unitary_type_error(): +def test_is_unitary_exception(): is_unitary(invalid_matrix_type_error) From ced7ecee980f2eb8d6c761e77600faded54afda1 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Thu, 23 Apr 2020 13:11:08 -0700 Subject: [PATCH 0082/1165] Add additional tests for amplitude result type (#69) --- test/unit_tests/braket/circuits/test_result_types.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/unit_tests/braket/circuits/test_result_types.py b/test/unit_tests/braket/circuits/test_result_types.py index 99b0584f..8240d9b8 100644 --- a/test/unit_tests/braket/circuits/test_result_types.py +++ b/test/unit_tests/braket/circuits/test_result_types.py @@ -111,12 +111,9 @@ def test_result_equality(testclass, subroutine_name, irclass, input, ir_input): @pytest.mark.xfail(raises=ValueError) -def test_amplitude_init_value_error(): - ResultType.Amplitude(state=None) - - -@pytest.mark.xfail(raises=ValueError) -@pytest.mark.parametrize("state", ((["2", "11"], [1, 0], [0.1, 0], ["", ""]))) +@pytest.mark.parametrize( + "state", ((["2", "11"]), ([1, 0]), ([0.1, 0]), ("-0", "1"), (["", ""]), (None), ([None, None])) +) def test_amplitude_init_invalid_state_value_error(state): ResultType.Amplitude(state=state) From d30e0a8c99060c01b8a7c3d34ac54768782b007d Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Fri, 1 May 2020 16:44:07 -0700 Subject: [PATCH 0083/1165] Add result types to gate model result and make shots=0 default for simulators (#70) --- examples/bell.py | 2 +- src/braket/aws/aws_qpu.py | 7 +- src/braket/aws/aws_quantum_simulator.py | 14 +- src/braket/aws/aws_quantum_task.py | 16 +- src/braket/circuits/circuit_helpers.py | 37 +++++ src/braket/circuits/result_types.py | 26 +++- src/braket/devices/local_simulator.py | 19 ++- .../tasks/gate_model_quantum_task_result.py | 140 ++++++++++++------ src/braket/tasks/local_quantum_task.py | 3 +- src/braket/tasks/quantum_task.py | 2 - .../braket/aws/common_test_utils.py | 9 +- test/unit_tests/braket/aws/test_aws_qpu.py | 9 +- .../braket/aws/test_aws_quantum_simulator.py | 13 -- .../braket/aws/test_aws_quantum_task.py | 36 ++--- .../braket/circuits/test_circuit_helpers.py | 43 ++++++ .../braket/devices/test_local_simulator.py | 23 ++- .../test_gate_model_quantum_task_result.py | 136 +++++++++++------ .../braket/tasks/test_local_quantum_task.py | 15 +- 18 files changed, 377 insertions(+), 173 deletions(-) create mode 100644 src/braket/circuits/circuit_helpers.py create mode 100644 test/unit_tests/braket/circuits/test_circuit_helpers.py diff --git a/examples/bell.py b/examples/bell.py index c18dcac1..400550b5 100644 --- a/examples/bell.py +++ b/examples/bell.py @@ -9,4 +9,4 @@ # https://wikipedia.org/wiki/Bell_state bell = Circuit().h(0).cnot(0, 1) -print(device.run(bell, s3_folder).result().measurement_counts) +print(device.run(bell, s3_folder, shots=100).result().measurement_counts) diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py index c00b5246..dff68c94 100644 --- a/src/braket/aws/aws_qpu.py +++ b/src/braket/aws/aws_qpu.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, Dict, Optional, Union +from typing import Any, Dict, Union import boto3 from braket.annealing.problem import Problem @@ -64,7 +64,7 @@ def run( self, task_specification: Union[Circuit, Problem], s3_destination_folder: AwsSession.S3DestinationFolder, - shots: Optional[int] = None, + shots: int = 1000, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTask: @@ -76,7 +76,8 @@ def run( task_specification (Union[Circuit, Problem]): Specification of task (circuit or annealing problem) to run on device. s3_destination_folder: The S3 location to save the task's results - shots (Optional[int]): The number of times to run the circuit or annealing problem + shots (int, optional): The number of times to run the circuit or annealing problem. + Default is 1000. *aws_quantum_task_args: Variable length positional arguments for `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. **aws_quantum_task_kwargs: Variable length keyword arguments for diff --git a/src/braket/aws/aws_quantum_simulator.py b/src/braket/aws/aws_quantum_simulator.py index 9fcbaf6d..3deee3c5 100644 --- a/src/braket/aws/aws_quantum_simulator.py +++ b/src/braket/aws/aws_quantum_simulator.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, Dict, Optional, Union +from typing import Any, Dict, Union from braket.annealing.problem import Problem from braket.aws.aws_quantum_task import AwsQuantumTask @@ -31,7 +31,7 @@ def __init__(self, arn: str, aws_session=None): """ Args: arn (str): The ARN of the simulator, for example, - "arn:aws:aqx:::quantum-simulator:aqx:qs1". + "arn:aws:aqx:::quantum-simulator:aqx:qs1". aws_session (AwsSession, optional) aws_session: An AWS session object. Default = None. """ super().__init__(name=None, status=None, status_reason=None) @@ -44,7 +44,7 @@ def run( self, task_specification: Union[Circuit, Problem], s3_destination_folder: AwsSession.S3DestinationFolder, - shots: Optional[int] = None, + shots: int = 0, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTask: @@ -55,7 +55,13 @@ def run( task_specification (Union[Circuit, Problem]): Specification of task (circuit or annealing problem) to run on device. s3_destination_folder: The S3 location to save the task's results - shots (Optional[int]): The number of times to run the circuit or annealing problem + shots (int, optional): The number of times to run the circuit or annealing problem. + Default is 0. + For circuits, when `shots=0`, the simulator will support simulator-only + result types, compute the exact results based on the task specification, + and sampling is not supported. + `shots>0` means that the simulator will be treated like a QPU and + only support result types available for a QPU. *aws_quantum_task_args: Variable length positional arguments for `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. **aws_quantum_task_kwargs: Variable length keyword arguments for diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index a456b09e..06224d75 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -17,12 +17,13 @@ import time from functools import singledispatch from logging import Logger, getLogger -from typing import Any, Dict, Optional, Union +from typing import Any, Dict, Union import boto3 from braket.annealing.problem import Problem from braket.aws.aws_session import AwsSession from braket.circuits.circuit import Circuit +from braket.circuits.circuit_helpers import validate_circuit_and_shots from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult, QuantumTask @@ -46,7 +47,7 @@ def create( device_arn: str, task_specification: Union[Circuit, Problem], s3_destination_folder: AwsSession.S3DestinationFolder, - shots: Optional[int] = None, + shots: int, backend_parameters: Dict[str, Any] = None, *args, **kwargs, @@ -68,8 +69,9 @@ def create( to store task results in. shots (int): The number of times to run the task on the device. If the device is a - simulator, this implies the state is sampled N times, where N = `shots`. Default - shots = 1_000. + simulator, this implies the state is sampled N times, where N = `shots`. + `shots=0` is only available on simulators and means that the simulator + will compute the exact results based on the task specification. backend_parameters (Dict[str, Any]): Additional parameters to send to the device. For example, for D-Wave: @@ -77,11 +79,16 @@ def create( Returns: AwsQuantumTask: AwsQuantumTask tracking the task execution on the device. + Note: The following arguments are typically defined via clients of Device. - `task_specification` - `s3_destination_folder` - `shots` + + See Also: + `braket.aws.aws_quantum_simulator.AwsQuantumSimulator.run()` + `braket.aws.aws_qpu.AwsQpu.run()` """ if len(s3_destination_folder) != 2: raise ValueError( @@ -351,6 +358,7 @@ def _( *args, **kwargs, ) -> AwsQuantumTask: + validate_circuit_and_shots(circuit, create_task_kwargs["shots"]) create_task_kwargs.update( { "ir": circuit.to_ir().json(), diff --git a/src/braket/circuits/circuit_helpers.py b/src/braket/circuits/circuit_helpers.py new file mode 100644 index 00000000..b6c134cd --- /dev/null +++ b/src/braket/circuits/circuit_helpers.py @@ -0,0 +1,37 @@ +# Copyright 2019-2019 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, ResultType + + +def validate_circuit_and_shots(circuit: Circuit, shots: int) -> None: + """ + Validates if circuit and shots are correct before running on a device + + Args: + circuit (Circuit): circuit to validate + shots (int): shots to validate + + Raises: + ValueError: If no result types specified for circuit and shots=0. + See `braket.circuit.result_types. Or, if `StateVector` or `Amplitude` + are specified as result types when shots > 0. + """ + if not shots and not circuit.result_types: + raise ValueError( + "No result types specified for circuit and shots=0. See `braket.circuit.result_types`" + ) + elif shots and circuit.result_types: + for rt in circuit.result_types: + if isinstance(rt, ResultType.StateVector) or isinstance(rt, ResultType.Amplitude): + raise ValueError("StateVector or Amplitude cannot be specified when shots>0") diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 2b3a6ddf..457601c3 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -34,7 +34,10 @@ class StateVector(ResultType): - """The full state vector as a requested result type.""" + """ + The full state vector as a requested result type. + This is only available when `shots=0` for simulators. + """ def __init__(self): super().__init__(ascii_symbol=["StateVector"]) @@ -68,7 +71,10 @@ def __copy__(self) -> StateVector: class Amplitude(ResultType): - """The amplitude of specified quantum states as a requested result type.""" + """ + The amplitude of specified quantum states as a requested result type. + This is only available when `shots=0` for simulators. + """ def __init__(self, state: List[str]): """ @@ -129,10 +135,14 @@ def __copy__(self): class Probability(ResultType): - """Probability as the requested result type. + """Probability in the computational basis as the requested result type. It can be the probability of all states if no targets are specified or the marginal probability - of a restricted set of states if only a subset of all qubits are specified as target.""" + of a restricted set of states if only a subset of all qubits are specified as target. + + For `shots>0`, this is calculated by measurements. For `shots=0`, this is supported + only by simulators and represents the exact result. + """ def __init__(self, target: QubitSetInput = None): """ @@ -260,6 +270,9 @@ class Expectation(ObservableResultType): 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. + For `shots>0`, this is calculated by measurements. For `shots=0`, this is supported + only by simulators and represents the exact result. + See :mod:`braket.circuits.observables` module for all of the supported observables. """ @@ -321,6 +334,8 @@ class Sample(ObservableResultType): 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. + This is only available for `shots>0`. + See :mod:`braket.circuits.observables` module for all of the supported observables. """ @@ -382,6 +397,9 @@ class Variance(ObservableResultType): 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. + For `shots>0`, this is calculated by measurements. For `shots=0`, this is supported + only by simulators and represents the exact result. + See :mod:`braket.circuits.observables` module for all of the supported observables. """ diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 73fb4bde..b380800a 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -18,6 +18,7 @@ from braket.annealing.problem import Problem from braket.circuits import Circuit +from braket.circuits.circuit_helpers import validate_circuit_and_shots from braket.devices.braket_simulator import BraketSimulator from braket.devices.device import Device from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult @@ -51,12 +52,16 @@ def __init__(self, backend: Union[str, BraketSimulator] = "default"): self._delegate = delegate def run( - self, task_specification: Union[Circuit, Problem], *args, **kwargs, + self, task_specification: Union[Circuit, Problem], shots: int = 0, *args, **kwargs, ) -> LocalQuantumTask: """ Runs the given task with the wrapped local simulator. Args: task_specification (Union[Circuit, Problem]): + shots (int, optional): 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 task specification. + Sampling is not supported for shots=0. *args: Positional args to pass to the IR simulator **kwargs: Keyword arguments to pass to the IR simulator @@ -73,7 +78,7 @@ def run( >>> device = LocalSimulator("default") >>> device.run(circuit, shots=1000) """ - result = _run_internal(task_specification, self._delegate, *args, **kwargs) + result = _run_internal(task_specification, self._delegate, shots, *args, **kwargs) return LocalQuantumTask(result) @staticmethod @@ -114,16 +119,16 @@ def _run_internal( @_run_internal.register -def _(circuit: Circuit, simulator: BraketSimulator, shots: Optional[int] = None, *args, **kwargs): +def _(circuit: Circuit, simulator: BraketSimulator, shots, *args, **kwargs): + validate_circuit_and_shots(circuit, shots) program = circuit.to_ir() qubits = circuit.qubit_count - shots_count = shots if shots else LocalQuantumTask.DEFAULT_SHOTS - results_dict = simulator.run(program, qubits, shots=shots_count, *args, **kwargs) + results_dict = simulator.run(program, qubits, shots, *args, **kwargs) return GateModelQuantumTaskResult.from_dict(results_dict) @_run_internal.register -def _(problem: Problem, simulator: BraketSimulator, shots: Optional[int] = None, *args, **kwargs): +def _(problem: Problem, simulator: BraketSimulator, shots, *args, **kwargs): ir = problem.to_ir() - results_dict = simulator.run(ir, *args, *kwargs) + results_dict = simulator.run(ir, shots, *args, *kwargs) return AnnealingQuantumTaskResult.from_dict(results_dict) diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 3dbf62ec..e548f1b7 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -15,9 +15,10 @@ import json from dataclasses import dataclass -from typing import Any, Counter, Dict +from typing import Any, Counter, Dict, List import numpy as np +from braket.circuits import ResultType @dataclass @@ -27,40 +28,78 @@ class GateModelQuantumTaskResult: to be initialized by a QuantumTask class. Args: - measurements (numpy.ndarray): 2d array - row is shot, column is qubit. - measurement_counts (Counter): A Counter of measurements. Key is the measurements + task_metadata (Dict[str, Any]): Dictionary of task metadata. task_metadata must have + keys 'Id', 'Shots', 'Ir', and 'IrType'. + result_types (List[Dict[str, Any]], optional): List of dictionaries where each dictionary + has two keys: 'Type' (the result type in IR JSON form) and + 'Value' (the result value for this result type). + Default is None. Currently only available when shots = 0. TODO: update + values (List[Any], optional): The values for result types requested in the circuit. + Default is None. Currently only available when shots = 0. TODO: update + measurements (numpy.ndarray, optional): 2d array - row is shot, column is qubit. + Default is None. Only available when shots > 0. The qubits in `measurements` + are the ones in `GateModelQuantumTaskResult.measured_qubits`. + measured_qubits (List[int], optional): The indices of the measured qubits. Default + is None. Only available when shots > 0. Indicates which qubits are in + `measurements`. + measurement_counts (Counter, optional): A Counter of measurements. Key is the measurements in a big endian binary string. Value is the number of times that measurement occurred. - measurement_probabilities (Dict[str, float]): A dictionary of probabilistic results. + Default is None. Only available when shots > 0. + measurement_probabilities (Dict[str, float], optional): + A dictionary of probabilistic results. Key is the measurements in a big endian binary string. Value is the probability the measurement occurred. - measurements_copied_from_device (bool): flag whether `measurements` were copied from device. - If false, `measurements` are calculated from device data. - measurement_counts_copied_from_device (bool): flag whether `measurement_counts` were copied - from device. If false, `measurement_counts` are calculated from device data. - measurement_probabilities_copied_from_device (bool): flag whether + Default is None. Only available when shots > 0. + measurements_copied_from_device (bool, optional): flag whether `measurements` + were copied from device. If false, `measurements` are calculated from device data. + Default is None. Only available when shots > 0. + measurement_counts_copied_from_device (bool, optional): flag whether `measurement_counts` + were copied from device. If False, `measurement_counts` are calculated from device data. + Default is None. Only available when shots > 0. + measurement_probabilities_copied_from_device (bool, optional): flag whether `measurement_probabilities` were copied from device. If false, - `measurement_probabilities` are calculated from device data. - task_metadata (Dict[str, Any]): Dictionary of task metadata. - state_vector (Dict[str, complex]): Dictionary where Key is state and Value is amplitude. + `measurement_probabilities` are calculated from device data. Default is None. + Only available when shots > 0. """ - measurements: np.ndarray - measurement_counts: Counter - measurement_probabilities: Dict[str, float] - measurements_copied_from_device: bool - measurement_counts_copied_from_device: bool - measurement_probabilities_copied_from_device: bool task_metadata: Dict[str, Any] - state_vector: Dict[str, complex] = None + result_types: List[Dict[str, str]] = None + values: List[Any] = None + measurements: np.ndarray = None + measured_qubits: List[int] = None + measurement_counts: Counter = None + measurement_probabilities: Dict[str, float] = None + measurements_copied_from_device: bool = None + measurement_counts_copied_from_device: bool = None + measurement_probabilities_copied_from_device: bool = None + + def get_value_by_result_type(self, result_type: ResultType) -> Any: + """ + Get value by result type. The result type must have already been + requested in the circuit sent to the device for this task result. + + Args: + result_type (ResultType): result type requested + + Returns: + Any: value of the result corresponding to the result type + + Raises: + ValueError: If result type not found in result. + Result types must be added to circuit before circuit is run on device. + """ + rt_json = result_type.to_ir().json() + for rt in self.result_types: + if rt_json == json.dumps(rt["Type"]): + return rt["Value"] + raise ValueError( + "Result type not found in result. " + + "Result types must be added to circuit before circuit is run on device." + ) def __eq__(self, other) -> bool: - if isinstance(other, GateModelQuantumTaskResult): - # __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 - # elements in the array are equal. - self_fields = (self.task_metadata, self.state_vector) - other_fields = (other.task_metadata, other.state_vector) - return (self.measurements == other.measurements).all() and self_fields == other_fields + if isinstance(other, GateModelQuantumTaskResult) and self.task_metadata.get("Id"): + return self.task_metadata["Id"] == other.task_metadata["Id"] return NotImplemented @staticmethod @@ -143,11 +182,7 @@ def from_dict(result: Dict[str, Any]): Raises: ValueError: If neither "Measurements" nor "MeasurementProbabilities" is a key - in the result dict - - Note: - For the "StateVector" key, the value should be of type Dict[str, complex]; - each bitstring's amplitude is a Python complex number. + in the result dict """ return GateModelQuantumTaskResult._from_dict_internal(result) @@ -164,24 +199,33 @@ def from_string(result: str) -> GateModelQuantumTaskResult: Raises: ValueError: If neither "Measurements" nor "MeasurementProbabilities" is a key - in the result dict - - Note: - For the "StateVector" key, the value should be of type Dict[str, List[float, float]]; - each bitstring's amplitude is represented by a two-element list, with first element - the real component and second element the imaginary component. + in the result dict """ json_obj = json.loads(result) - state_vector = json_obj.get("StateVector", None) - if state_vector: - for state in state_vector: - state_vector[state] = complex(*state_vector[state]) + for result_type in json_obj.get("ResultTypes", []): + type = result_type["Type"]["type"] + if 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]) return GateModelQuantumTaskResult._from_dict_internal(json_obj) @classmethod def _from_dict_internal(cls, result: Dict[str, Any]): + if result["TaskMetadata"]["Shots"] > 0: + return GateModelQuantumTaskResult._from_dict_internal_computational_basis_sampling( + result + ) + else: + return GateModelQuantumTaskResult._from_dict_internal_simulator_only(result) + + @classmethod + def _from_dict_internal_computational_basis_sampling(cls, result: Dict[str, Any]): task_metadata = result["TaskMetadata"] - state_vector = result.get("StateVector", None) + measured_qubits = result.get("MeasuredQubits") if "Measurements" in result: measurements = np.asarray(result["Measurements"], dtype=int) m_counts = GateModelQuantumTaskResult.measurement_counts_from_measurements(measurements) @@ -203,15 +247,23 @@ def _from_dict_internal(cls, result: Dict[str, Any]): m_probabilities_copied_from_device = True else: raise ValueError( - 'One of "Measurements" or "MeasurementProbabilities" must be in the results dict' + 'One of "Measurements" or "MeasurementProbabilities" must be in the result dict' ) + # TODO: add result types for shots > 0 after basis rotation instructions are added to IR return cls( - state_vector=state_vector, task_metadata=task_metadata, measurements=measurements, + measured_qubits=measured_qubits, measurement_counts=m_counts, measurement_probabilities=m_probs, measurements_copied_from_device=measurements_copied_from_device, measurement_counts_copied_from_device=m_counts_copied_from_device, measurement_probabilities_copied_from_device=m_probabilities_copied_from_device, ) + + @classmethod + def _from_dict_internal_simulator_only(cls, result: Dict[str, Any]): + task_metadata = result["TaskMetadata"] + result_types = result["ResultTypes"] + values = [rt["Value"] for rt in result_types] + return cls(task_metadata=task_metadata, result_types=result_types, values=values) diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py index 2b6f2c6f..653cd154 100644 --- a/src/braket/tasks/local_quantum_task.py +++ b/src/braket/tasks/local_quantum_task.py @@ -12,7 +12,6 @@ # language governing permissions and limitations under the License. import asyncio -import uuid from typing import Union from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult, QuantumTask @@ -25,7 +24,7 @@ class LocalQuantumTask(QuantumTask): """ def __init__(self, result: Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]): - self._id = uuid.uuid4() + self._id = result.task_metadata["Id"] self._result = result @property diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py index e1e8d787..99b03751 100644 --- a/src/braket/tasks/quantum_task.py +++ b/src/braket/tasks/quantum_task.py @@ -22,8 +22,6 @@ class QuantumTask(ABC): """An abstraction over a quantum task on a quantum device.""" - DEFAULT_SHOTS = 1_000 - @property @abstractmethod def id(self) -> str: diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 6530fea8..f3a197d2 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -181,8 +181,8 @@ class MockS3: "Id": "UUID_blah_1", "Status": "COMPLETED", "BackendArn": AwsQpuArns.RIGETTI, - "CwLogGroupArn": "blah", - "Program": "....", + "Shots": 1000, + "Ir": "{}", }, } ) @@ -195,8 +195,8 @@ class MockS3: "Id": "UUID_blah_2", "Status": "COMPLETED", "BackendArn": AwsQpuArns.RIGETTI, - "CwLogGroupArn": "blah", - "Program": "....", + "Shots": 1000, + "Ir": "{}", }, } ) @@ -212,6 +212,7 @@ class MockS3: "Modified": 1574140388.6908717, "Shots": 100, "GateModelConfig": {"QubitCount": 6}, + "Ir": "{}", }, "MeasurementProbabilities": {"011000": 0.9999999999999982}, } diff --git a/test/unit_tests/braket/aws/test_aws_qpu.py b/test/unit_tests/braket/aws/test_aws_qpu.py index be26496c..1378bcb7 100644 --- a/test/unit_tests/braket/aws/test_aws_qpu.py +++ b/test/unit_tests/braket/aws/test_aws_qpu.py @@ -194,8 +194,13 @@ def test_run_with_kwargs(aws_quantum_task_mock, qpu, circuit, s3_destination_fol @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_kwargs_no_shots(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): - _run_and_assert( - aws_quantum_task_mock, qpu, circuit, s3_destination_folder, None, [], {"bar": 1, "baz": 2} + task_mock = Mock() + aws_quantum_task_mock.return_value = task_mock + qpu = qpu(AwsQpuArns.RIGETTI) + task = qpu.run(circuit, s3_destination_folder) + assert task == task_mock + aws_quantum_task_mock.assert_called_with( + qpu._aws_session, qpu.arn, circuit, s3_destination_folder, 1000 ) diff --git a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py index ccd4f192..45859b55 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py @@ -104,19 +104,6 @@ def test_run_with_positional_args(aws_quantum_task_mock, simulator, circuit, s3_ @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_kwargs(aws_quantum_task_mock, simulator, circuit, s3_destination_folder): - _run_and_assert( - aws_quantum_task_mock, - simulator, - circuit, - s3_destination_folder, - None, - [], - {"bar": 1, "baz": 2}, - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_kwargs_no_shots(aws_quantum_task_mock, simulator, circuit, s3_destination_folder): _run_and_assert( aws_quantum_task_mock, simulator, 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 c7640d61..623b72cb 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -237,21 +237,22 @@ def test_timeout_no_result_terminal_state(aws_session): @pytest.mark.xfail(raises=ValueError) def test_create_invalid_s3_folder(aws_session, arn, circuit): - AwsQuantumTask.create(aws_session, arn, circuit, ("bucket",)) + AwsQuantumTask.create(aws_session, arn, circuit, ("bucket",), 1000) @pytest.mark.xfail(raises=TypeError) -def test_create_invalid_task_specification(aws_session, arn, circuit): +def test_create_invalid_task_specification(aws_session, arn): mocked_task_arn = "task-arn-1" aws_session.create_quantum_task.return_value = mocked_task_arn - AwsQuantumTask.create(aws_session, arn, "foo", S3_TARGET) + AwsQuantumTask.create(aws_session, arn, "foo", S3_TARGET, 1000) -def test_from_circuit_default_shots(aws_session, arn, circuit): +def test_from_circuit_with_shots(aws_session, arn, circuit): mocked_task_arn = "task-arn-1" aws_session.create_quantum_task.return_value = mocked_task_arn + shots = 53 - task = AwsQuantumTask.create(aws_session, arn, circuit, S3_TARGET) + task = AwsQuantumTask.create(aws_session, arn, circuit, S3_TARGET, shots) assert task == AwsQuantumTask( mocked_task_arn, aws_session, GateModelQuantumTaskResult.from_string ) @@ -262,30 +263,16 @@ def test_from_circuit_default_shots(aws_session, arn, circuit): circuit, AwsQuantumTask.GATE_IR_TYPE, S3_TARGET, - AwsQuantumTask.DEFAULT_SHOTS, + shots, {"gateModelParameters": {"qubitCount": circuit.qubit_count}}, ) -def test_from_circuit_with_shots(aws_session, arn, circuit): +@pytest.mark.xfail(raises=ValueError) +def test_from_circuit_with_shots_value_error(aws_session, arn, circuit): mocked_task_arn = "task-arn-1" aws_session.create_quantum_task.return_value = mocked_task_arn - shots = 53 - - task = AwsQuantumTask.create(aws_session, arn, circuit, S3_TARGET, shots=shots) - assert task == AwsQuantumTask( - mocked_task_arn, aws_session, GateModelQuantumTaskResult.from_string - ) - - _assert_create_quantum_task_called_with( - aws_session, - arn, - circuit, - AwsQuantumTask.GATE_IR_TYPE, - S3_TARGET, - shots, - {"gateModelParameters": {"qubitCount": circuit.qubit_count}}, - ) + AwsQuantumTask.create(aws_session, arn, circuit, S3_TARGET, 0) def test_from_annealing(aws_session, arn, problem): @@ -297,6 +284,7 @@ def test_from_annealing(aws_session, arn, problem): arn, problem, S3_TARGET, + 1000, backend_parameters={"dWaveParameters": {"postprocessingType": "OPTIMIZATION"}}, ) assert task == AwsQuantumTask( @@ -309,7 +297,7 @@ def test_from_annealing(aws_session, arn, problem): problem, AwsQuantumTask.ANNEALING_IR_TYPE, S3_TARGET, - AwsQuantumTask.DEFAULT_SHOTS, + 1000, {"annealingModelParameters": {"dWaveParameters": {"postprocessingType": "OPTIMIZATION"}}}, ) diff --git a/test/unit_tests/braket/circuits/test_circuit_helpers.py b/test/unit_tests/braket/circuits/test_circuit_helpers.py new file mode 100644 index 00000000..2ff39be5 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_circuit_helpers.py @@ -0,0 +1,43 @@ +# Copyright 2019-2019 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.circuit_helpers import validate_circuit_and_shots + + +@pytest.mark.xfail(raises=ValueError) +def test_validate_circuit_and_shots_0_no_results(): + validate_circuit_and_shots(Circuit().h(0), 0) + + +def test_validate_circuit_and_shots_100_no_results(): + assert validate_circuit_and_shots(Circuit().h(0), 100) is None + + +def test_validate_circuit_and_shots_0_results(): + assert validate_circuit_and_shots(Circuit().h(0).state_vector(), 0) is None + + +def test_validate_circuit_and_shots_100_results(): + assert validate_circuit_and_shots(Circuit().h(0).probability(), 100) is None + + +@pytest.mark.xfail(raises=ValueError) +def test_validate_circuit_and_shots_100_result_state_vector(): + validate_circuit_and_shots(Circuit().h(0).state_vector(), 100) + + +@pytest.mark.xfail(raises=ValueError) +def test_validate_circuit_and_shots_100_result_amplitude(): + validate_circuit_and_shots(Circuit().h(0).amplitude(state=["0"]), 100) diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index cd1c83db..adb6113a 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.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. +import json from typing import Any, Dict, Optional from unittest.mock import Mock @@ -23,14 +24,13 @@ from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult GATE_MODEL_RESULT = { - "StateVector": { - "00": complex(0.2, 0.2), - "01": complex(0.3, 0.1), - "10": complex(0.1, 0.3), - "11": complex(0.2, 0.2), - }, "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], - "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED"}, + "TaskMetadata": { + "Id": "UUID_blah_1", + "Status": "COMPLETED", + "Shots": 1000, + "Ir": json.dumps({"results": []}), + }, } ANNEALING_RESULT = { @@ -40,7 +40,7 @@ "SolutionCounts": None, "ProblemType": "ising", "Foo": {"Bar": "Baz"}, - "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED", "Shots": 5,}, + "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED", "Shots": 5}, } @@ -84,6 +84,13 @@ def test_run_gate_model(): assert task.result() == GateModelQuantumTaskResult.from_dict(GATE_MODEL_RESULT) +@pytest.mark.xfail(raises=ValueError) +def test_run_gate_model_value_error(): + dummy = DummyCircuitSimulator() + sim = LocalSimulator(dummy) + sim.run(Circuit().h(0).cnot(0, 1)) + + def test_run_annealing(): sim = LocalSimulator(DummyAnnealingSimulator()) task = sim.run(Problem(ProblemType.ISING)) 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 98032a70..0651696c 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 @@ -11,58 +11,49 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import copy import json from typing import Any, Counter, Dict import numpy as np import pytest from braket.aws.aws_qpu_arns import AwsQpuArns +from braket.circuits import Observable, ResultType from braket.tasks import GateModelQuantumTaskResult @pytest.fixture def result_dict_1(): return { - "StateVector": { - "00": complex(0.2, 0.2), - "01": complex(0.3, 0.1), - "10": complex(0.1, 0.3), - "11": complex(0.2, 0.2), - }, "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], + "MeasuredQubits": [0, 1], "TaskMetadata": { "Id": "UUID_blah_1", "Status": "COMPLETED", "BackendArn": AwsQpuArns.RIGETTI, + "Shots": 1000, + "Ir": json.dumps({"results": []}), }, } @pytest.fixture -def result_str_1(): - return json.dumps( - { - "StateVector": {"00": [0.2, 0.2], "01": [0.3, 0.1], "10": [0.1, 0.3], "11": [0.2, 0.2]}, - "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], - "TaskMetadata": { - "Id": "UUID_blah_1", - "Status": "COMPLETED", - "BackendArn": AwsQpuArns.RIGETTI, - }, - } - ) +def result_str_1(result_dict_1): + return json.dumps(result_dict_1) @pytest.fixture def result_str_2(): return json.dumps( { - "StateVector": {"00": [0.2, 0.2], "01": [0.3, 0.1], "10": [0.1, 0.3], "11": [0.2, 0.2]}, "Measurements": [[0, 0], [0, 0], [0, 0], [1, 1]], + "MeasuredQubits": [0, 1], "TaskMetadata": { "Id": "UUID_blah_2", "Status": "COMPLETED", "BackendArn": AwsQpuArns.RIGETTI, + "Shots": 1000, + "Ir": json.dumps({"results": []}), }, } ) @@ -81,22 +72,55 @@ def result_str_3(): "Modified": 1574140388.6908717, "Shots": 100, "GateModelConfig": {"QubitCount": 6}, + "Ir": json.dumps({"results": []}), }, "MeasurementProbabilities": {"011000": 0.9999999999999982}, + "MeasuredQubits": list(range(6)), } ) @pytest.fixture -def parsed_state_vector(): +def result_dict_4(): return { - "00": complex(0.2, 0.2), - "01": complex(0.3, 0.1), - "10": complex(0.1, 0.3), - "11": complex(0.2, 0.2), + "TaskMetadata": { + "Id": "1231231", + "Shots": 0, + "GateModelConfig": {"QubitCount": 2}, + "Ir": json.dumps({"results": []}), + }, + "ResultTypes": [ + {"Type": {"targets": [0], "type": "probability"}, "Value": np.array([0.5, 0.5])}, + { + "Type": {"type": "statevector"}, + "Value": np.array([complex(0.70710678, 0), 0, 0, complex(0.70710678, 0)]), + }, + {"Type": {"targets": [0], "type": "expectation", "observable": ["y"]}, "Value": 0.0}, + {"Type": {"targets": [0], "type": "variance", "observable": ["y"]}, "Value": 0.1}, + { + "Type": {"type": "amplitude", "states": ["00"]}, + "Value": {"00": complex(0.70710678, 0)}, + }, + ], } +@pytest.fixture +def result_str_4(result_dict_4): + result = copy.deepcopy(result_dict_4) + result["ResultTypes"] = [ + {"Type": {"targets": [0], "type": "probability"}, "Value": [0.5, 0.5]}, + { + "Type": {"type": "statevector"}, + "Value": [(0.70710678, 0), (0, 0), (0, 0), (0.70710678, 0)], + }, + {"Type": {"targets": [0], "type": "expectation", "observable": ["y"]}, "Value": 0.0}, + {"Type": {"targets": [0], "type": "variance", "observable": ["y"]}, "Value": 0.1}, + {"Type": {"type": "amplitude", "states": ["00"]}, "Value": {"00": (0.70710678, 0)}}, + ] + return json.dumps(result) + + @pytest.fixture def malformatted_results(): return { @@ -104,6 +128,8 @@ def malformatted_results(): "Id": "UUID_blah_1", "Status": "COMPLETED", "BackendArn": AwsQpuArns.RIGETTI, + "Shots": 1000, + "Ir": json.dumps({"results": []}), }, } @@ -143,20 +169,6 @@ def test_measurements_from_measurement_probabilities(): assert np.allclose(measurements, expected_results) -def test_state_vector(parsed_state_vector): - result: GateModelQuantumTaskResult = GateModelQuantumTaskResult( - measurements=None, - task_metadata=None, - state_vector=parsed_state_vector, - measurement_counts=None, - measurement_probabilities=None, - measurements_copied_from_device=False, - measurement_counts_copied_from_device=False, - measurement_probabilities_copied_from_device=False, - ) - assert result.state_vector == parsed_state_vector - - def test_task_metadata(): task_metadata: Dict[str, Any] = { "Id": "UUID_blah", @@ -179,15 +191,15 @@ def test_task_metadata(): assert result.task_metadata == task_metadata -def test_from_dict_measurements_state_vector(result_dict_1): +def test_from_dict_measurements(result_dict_1): task_result = GateModelQuantumTaskResult.from_dict(result_dict_1) expected_measurements = np.asarray(result_dict_1["Measurements"], dtype=int) assert task_result.task_metadata == result_dict_1["TaskMetadata"] - assert task_result.state_vector == result_dict_1["StateVector"] assert np.array2string(task_result.measurements) == np.array2string(expected_measurements) assert not task_result.measurement_counts_copied_from_device assert not task_result.measurement_probabilities_copied_from_device assert task_result.measurements_copied_from_device + assert task_result.measured_qubits == result_dict_1["MeasuredQubits"] def test_from_dict_measurement_probabilities(result_str_3): @@ -195,7 +207,6 @@ def test_from_dict_measurement_probabilities(result_str_3): task_result = GateModelQuantumTaskResult.from_dict(result_obj) assert task_result.measurement_probabilities == result_obj["MeasurementProbabilities"] assert task_result.task_metadata == result_obj["TaskMetadata"] - assert task_result.state_vector is None shots = 100 measurement_list = [list("011000") for x in range(shots)] expected_measurements = np.asarray(measurement_list, dtype=int) @@ -204,18 +215,19 @@ def test_from_dict_measurement_probabilities(result_str_3): 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 + assert task_result.measured_qubits == result_obj["MeasuredQubits"] -def test_from_string_measurements_state_vector(result_str_1, parsed_state_vector): +def test_from_string_measurements(result_str_1): result_obj = json.loads(result_str_1) task_result = GateModelQuantumTaskResult.from_string(result_str_1) expected_measurements = np.asarray(result_obj["Measurements"], dtype=int) assert task_result.task_metadata == result_obj["TaskMetadata"] - assert task_result.state_vector == parsed_state_vector assert np.array2string(task_result.measurements) == np.array2string(expected_measurements) assert not task_result.measurement_counts_copied_from_device assert not task_result.measurement_probabilities_copied_from_device assert task_result.measurements_copied_from_device + assert task_result.measured_qubits == result_obj["MeasuredQubits"] def test_from_string_measurement_probabilities(result_str_3): @@ -223,7 +235,6 @@ def test_from_string_measurement_probabilities(result_str_3): task_result = GateModelQuantumTaskResult.from_string(result_str_3) assert task_result.measurement_probabilities == result_obj["MeasurementProbabilities"] assert task_result.task_metadata == result_obj["TaskMetadata"] - assert task_result.state_vector is None shots = 100 measurement_list = [list("011000") for x in range(shots)] expected_measurements = np.asarray(measurement_list, dtype=int) @@ -255,6 +266,43 @@ def test_equality(result_str_1, result_str_2): assert result_1 != non_result +def test_from_string_simulator_only(result_dict_4, result_str_4): + result = GateModelQuantumTaskResult.from_string(result_str_4) + assert len(result.result_types) == len(result_dict_4["ResultTypes"]) + for i in range(len(result.result_types)): + rt = result.result_types[i] + expected_rt = result_dict_4["ResultTypes"][i] + assert rt["Type"] == expected_rt["Type"] + if isinstance(rt["Value"], np.ndarray): + assert np.allclose(rt["Value"], expected_rt["Value"]) + else: + assert rt["Value"] == expected_rt["Value"] + assert result.task_metadata == result.task_metadata + + +def test_get_value_by_result_type(result_dict_4): + result = GateModelQuantumTaskResult.from_dict(result_dict_4) + assert np.allclose( + result.get_value_by_result_type(ResultType.Probability(target=0)), result.values[0] + ) + assert np.allclose(result.get_value_by_result_type(ResultType.StateVector()), result.values[1]) + assert ( + result.get_value_by_result_type(ResultType.Expectation(observable=Observable.Y(), target=0)) + == result.values[2] + ) + assert ( + result.get_value_by_result_type(ResultType.Variance(observable=Observable.Y(), target=0)) + == result.values[3] + ) + assert result.get_value_by_result_type(ResultType.Amplitude(state=["00"])) == result.values[4] + + +@pytest.mark.xfail(raises=ValueError) +def test_get_value_by_result_type_value_error(result_dict_4): + result = GateModelQuantumTaskResult.from_dict(result_dict_4) + result.get_value_by_result_type(ResultType.Probability(target=[0, 1])) + + @pytest.mark.xfail(raises=ValueError) def test_bad_dict(malformatted_results): GateModelQuantumTaskResult.from_dict(malformatted_results) 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 58a17157..6af0b4c8 100644 --- a/test/unit_tests/braket/tasks/test_local_quantum_task.py +++ b/test/unit_tests/braket/tasks/test_local_quantum_task.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. +import json import uuid import pytest @@ -19,14 +20,13 @@ RESULT = GateModelQuantumTaskResult.from_dict( { - "StateVector": { - "00": complex(0.2, 0.2), - "01": complex(0.3, 0.1), - "10": complex(0.1, 0.3), - "11": complex(0.2, 0.2), - }, "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], - "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED"}, + "TaskMetadata": { + "Id": str(uuid.uuid4()), + "Status": "COMPLETED", + "Shots": 2, + "Ir": json.dumps({"results": []}), + }, } ) @@ -43,6 +43,7 @@ def test_state(): def test_result(): + assert RESULT.task_metadata["Id"] == TASK.id assert TASK.result() == RESULT From 5887ab0f5ad7ad2f9b55b9bf5af1b5c28bcfc7f9 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Mon, 4 May 2020 17:18:29 -0700 Subject: [PATCH 0084/1165] Update version to 0.3.5 (#71) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5329a05f..790a78bc 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="braket-sdk", - version="0.3.4", + version="0.3.5", license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), From f85cf63a0879dc8c4831e1d54b3e1611db3a32c9 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 24 Mar 2020 15:20:31 -0600 Subject: [PATCH 0085/1165] Add local simulator support (#57) * Add BraketSimulator class that will eventually be moved to the simulator repo. This is the abstract class that all local simulators should implement. * Added __init__.py for braket.annealing module * Also fixed a couple of malformatted docstrings --- src/braket/annealing/__init__.py | 15 +++ src/braket/devices/__init__.py | 1 + src/braket/devices/braket_simulator.py | 59 +++++++++ src/braket/devices/device.py | 2 +- src/braket/devices/local_simulator.py | 119 ++++++++++++++++++ .../tasks/annealing_quantum_task_result.py | 6 +- .../tasks/gate_model_quantum_task_result.py | 8 ++ src/braket/tasks/local_quantum_task.py | 46 +++++++ src/braket/tasks/quantum_task.py | 2 +- .../braket/devices/test_local_simulator.py | 109 ++++++++++++++++ .../braket/tasks/test_local_quantum_task.py | 53 ++++++++ 11 files changed, 415 insertions(+), 5 deletions(-) create mode 100644 src/braket/annealing/__init__.py create mode 100644 src/braket/devices/braket_simulator.py create mode 100644 src/braket/devices/local_simulator.py create mode 100644 src/braket/tasks/local_quantum_task.py create mode 100644 test/unit_tests/braket/devices/test_local_simulator.py create mode 100644 test/unit_tests/braket/tasks/test_local_quantum_task.py diff --git a/src/braket/annealing/__init__.py b/src/braket/annealing/__init__.py new file mode 100644 index 00000000..f4325051 --- /dev/null +++ b/src/braket/annealing/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2019-2019 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. + +# Execute initialization code in circuit module +from braket.annealing.problem import Problem, ProblemType # noqa: F401 diff --git a/src/braket/devices/__init__.py b/src/braket/devices/__init__.py index b797522f..891de47f 100644 --- a/src/braket/devices/__init__.py +++ b/src/braket/devices/__init__.py @@ -12,3 +12,4 @@ # language governing permissions and limitations under the License. from braket.devices.device import Device # noqa: F401 +from braket.devices.local_simulator import LocalSimulator # noqa: F401 diff --git a/src/braket/devices/braket_simulator.py b/src/braket/devices/braket_simulator.py new file mode 100644 index 00000000..f5d69cf0 --- /dev/null +++ b/src/braket/devices/braket_simulator.py @@ -0,0 +1,59 @@ +# Copyright 2019-2019 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 abc import ABC, abstractmethod +from typing import Union + +from braket.ir.annealing import Problem +from braket.ir.jaqcd import Program + + +class BraketSimulator(ABC): + """ An abstract simulator that locally runs a quantum task. + + The task can be either a circuit-based program or an annealing task, + specified by the given IR. + + For users creating their own simulator: to register a simulator so the + Braket SDK recognizes its name, the name and class must added as an + entry point for "braket.simulators". This is done by adding an entry to + entry_points in the simulator package's setup.py: + + >>> entry_points = { + >>> "braket.simulators": [ + >>> "backend_name = " + >>> ] + >>> } + """ + + # TODO: Move this class to the local simulator repo and take a dependency on it + # As such, this will not depend on any SDK classes. + + # TODO: Update to use new simulate() method + + @abstractmethod + def run(self, ir: Union[Program, Problem], *args, **kwargs) -> str: + """ Run the task specified by the given IR. + + Extra arguments will contain any additional information necessary to run the task, + such as number of qubits. + + Args: + ir (Union[Program, Problem]): The IR representation of the program + + Returns: + str: A JSON string containing the results of the simulation. + In order to work with braket-python-sdk, the format of the JSON dict should + match that needed by GateModelQuantumTaskResult or AnnealingQuantumTaskResult + in the SDK, depending on the type of task. + """ + raise NotImplementedError() diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index bc5c5878..189da3be 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -49,7 +49,7 @@ def run( Args: task_specification (Union[Circuit, Problem]): Specification of a task - to run on device. + to run on device. location: The location to save the task's results diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py new file mode 100644 index 00000000..935ebf40 --- /dev/null +++ b/src/braket/devices/local_simulator.py @@ -0,0 +1,119 @@ +# Copyright 2019-2019 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 typing import Set, Union + +import pkg_resources + +from braket.annealing.problem import Problem +from braket.circuits import Circuit +from braket.devices.braket_simulator import BraketSimulator +from braket.devices.device import Device +from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult +from braket.tasks.local_quantum_task import LocalQuantumTask + +_simulator_devices = { + entry.name: entry for entry in pkg_resources.iter_entry_points("braket.simulators") +} + + +class LocalSimulator(Device): + """ A simulator meant to run directly on the user's machine. + + This class wraps a BraketSimulator object so that it can be run and returns + results using constructs from the SDK rather than Braket IR. + """ + + def __init__(self, backend: Union[str, BraketSimulator]): + """ + Args: + backend (Union[str, BraketSimulator]): The name of the simulator backend or + the actual simulator instance to use for simulation + """ + delegate = _get_simulator(backend) + super().__init__( + name=delegate.__class__.__name__, + status="AVAILABLE", + status_reason="Local simulator loaded successfully", + ) + self._delegate = delegate + + def run( + self, task_specification: Union[Circuit, Problem], *args, **kwargs, + ) -> LocalQuantumTask: + """ Runs the given task with the wrapped local simulator. + + Args: + task_specification (Union[Circuit, Problem]): + *args: Positional args to pass to the IR simulator + **kwargs: Keyword arguments to pass to the IR simulator + + Returns: + LocalQuantumTask: A LocalQuantumTask object containing the results + of the simulation + + Note: + If running a circuit, the number of qubits will be passed + to the backend as the argument after the circuit itself. + """ + result = _run_internal(task_specification, self._delegate, *args, **kwargs) + return LocalQuantumTask(result) + + @classmethod + def registered_backends(cls) -> Set[str]: + """ Gets the backends that have been registered as entry points + + Returns: + Set[str]: The names of the available backends that can be passed + into LocalSimulator's constructor + """ + return set(_simulator_devices.keys()) + + +@singledispatch +def _get_simulator(simulator): + raise TypeError("Simulator must either be a string or a BraketSimulator instance") + + +@_get_simulator.register +def _(backend_name: str): + if backend_name in _simulator_devices: + device_class = _simulator_devices[backend_name].load() + return device_class() + else: + raise ValueError(f"Only the following devices are available {_simulator_devices.keys()}") + + +@_get_simulator.register +def _(backend_impl: BraketSimulator): + return backend_impl + + +@singledispatch +def _run_internal(task_specification, simulator: BraketSimulator, *args, **kwargs): + raise NotImplementedError("Unsupported task type") + + +@_run_internal.register +def _(circuit: Circuit, simulator: BraketSimulator, *args, **kwargs): + program = circuit.to_ir() + qubits = circuit.qubit_count + result_json = simulator.run(program, qubits, *args, **kwargs) + return GateModelQuantumTaskResult.from_string(result_json) + + +@_run_internal.register +def _(problem: Problem, simulator: BraketSimulator, *args, **kwargs): + ir = problem.to_ir() + result_json = simulator.run(ir, *args, *kwargs) + return AnnealingQuantumTaskResult.from_string(result_json) diff --git a/src/braket/tasks/annealing_quantum_task_result.py b/src/braket/tasks/annealing_quantum_task_result.py index 585f3585..572d3ca3 100644 --- a/src/braket/tasks/annealing_quantum_task_result.py +++ b/src/braket/tasks/annealing_quantum_task_result.py @@ -28,9 +28,9 @@ class AnnealingQuantumTaskResult: Args: record_array (numpy.recarray): numpy array with keys 'solution' (numpy.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. + 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. variable_count (int): the number of variables problem_type (str): the type of problem ('ising' or 'qubo') task_metadata (Dict[str, Any]): Dictionary of task metadata. diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 13575502..ae1629a7 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -140,6 +140,10 @@ def from_string(result: str) -> GateModelQuantumTaskResult: Returns: GateModelQuantumTaskResult: A GateModelQuantumTaskResult based on a string + + Raises: + ValueError: If neither "Measurements" nor "MeasurementProbabilities" is a key + in the result dict """ json_obj = json.loads(result) task_metadata = json_obj["TaskMetadata"] @@ -166,6 +170,10 @@ def from_string(result: str) -> GateModelQuantumTaskResult: measurements_copied_from_device = False m_counts_copied_from_device = False m_probabilities_copied_from_device = True + else: + raise ValueError( + 'One of "Measurements" or "MeasurementProbabilities" must be in the results dict' + ) return GateModelQuantumTaskResult( state_vector=state_vector, task_metadata=task_metadata, diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py new file mode 100644 index 00000000..9c64cad5 --- /dev/null +++ b/src/braket/tasks/local_quantum_task.py @@ -0,0 +1,46 @@ +# Copyright 2019-2019 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 asyncio +import uuid +from typing import Union + +from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult, QuantumTask + + +class LocalQuantumTask(QuantumTask): + """ A task containing the results of a local simulation. + + Since this class is instantiated with the results, cancel() and run_async() are unsupported. + """ + + def __init__(self, result: Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]): + self._id = uuid.uuid4() + self._result = result + + @property + def id(self) -> str: + return str(self._id) + + def cancel(self) -> None: + raise NotImplementedError("Cannot cancel completed local task") + + def state(self) -> str: + return "COMPLETED" + + @property + def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: + return self._result + + def async_result(self) -> asyncio.Task: + # TODO: Allow for asynchronous simulation + raise NotImplementedError("Asynchronous local simulation unsupported") diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py index 626a015a..99b03751 100644 --- a/src/braket/tasks/quantum_task.py +++ b/src/braket/tasks/quantum_task.py @@ -52,7 +52,7 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: Args: use_cached_value (bool, optional): If True, uses the value retrieved from the previous - request. + request. Returns: Dict[str, Any]: The metadata regarding the task. If `use_cached_value` is True, diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py new file mode 100644 index 00000000..3c4f496e --- /dev/null +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -0,0 +1,109 @@ +# Copyright 2019-2019 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 typing import Optional +from unittest.mock import Mock + +import braket.ir as ir +import pytest +from braket.annealing import Problem, ProblemType +from braket.circuits import Circuit +from braket.devices import LocalSimulator, local_simulator +from braket.devices.braket_simulator import BraketSimulator +from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult + +GATE_MODEL_RESULT = json.dumps( + { + "StateVector": {"00": [0.2, 0.2], "01": [0.3, 0.1], "10": [0.1, 0.3], "11": [0.2, 0.2]}, + "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], + "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED"}, + } +) + +ANNEALING_RESULT = json.dumps( + { + "Solutions": [[-1, -1, -1, -1], [1, -1, 1, 1], [1, -1, -1, 1]], + "VariableCount": 4, + "Values": [0.0, 1.0, 2.0], + "SolutionCounts": None, + "ProblemType": "ising", + "Foo": {"Bar": "Baz"}, + "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED", "Shots": 5,}, + } +) + + +class DummyCircuitSimulator(BraketSimulator): + def run( + self, program: ir.jaqcd.Program, qubits: int, shots: Optional[int], *args, **kwargs + ) -> str: + self._shots = shots + self._qubits = qubits + return GATE_MODEL_RESULT + + def assert_shots(self, shots): + assert self._shots == shots + + def assert_qubits(self, qubits): + assert self._qubits == qubits + + +class DummyAnnealingSimulator(BraketSimulator): + def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> str: + return ANNEALING_RESULT + + +mock_entry = Mock() +mock_entry.load.return_value = DummyCircuitSimulator +local_simulator._simulator_devices = {"dummy": mock_entry} + + +def test_load_from_entry_point(): + sim = LocalSimulator("dummy") + task = sim.run(Circuit().h(0).cnot(0, 1), 10) + assert task.result == GateModelQuantumTaskResult.from_string(GATE_MODEL_RESULT) + + +def test_run_gate_model(): + dummy = DummyCircuitSimulator() + sim = LocalSimulator(dummy) + task = sim.run(Circuit().h(0).cnot(0, 1), 10) + dummy.assert_shots(10) + dummy.assert_qubits(2) + assert task.result == GateModelQuantumTaskResult.from_string(GATE_MODEL_RESULT) + + +def test_run_annealing(): + sim = LocalSimulator(DummyAnnealingSimulator()) + task = sim.run(Problem(ProblemType.ISING)) + assert task.result == AnnealingQuantumTaskResult.from_string(ANNEALING_RESULT) + + +def test_registered_backends(): + assert LocalSimulator.registered_backends() == {"dummy"} + + +@pytest.mark.xfail(raises=TypeError) +def test_init_invalid_backend_type(): + LocalSimulator(1234) + + +@pytest.mark.xfail(raises=ValueError) +def test_init_unregistered_backend(): + LocalSimulator("foo") + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_run_unsupported_type(): + sim = LocalSimulator(DummyCircuitSimulator()) + sim.run("I'm unsupported") diff --git a/test/unit_tests/braket/tasks/test_local_quantum_task.py b/test/unit_tests/braket/tasks/test_local_quantum_task.py new file mode 100644 index 00000000..cb9e5275 --- /dev/null +++ b/test/unit_tests/braket/tasks/test_local_quantum_task.py @@ -0,0 +1,53 @@ +# Copyright 2019-2019 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 uuid + +import pytest +from braket.tasks import GateModelQuantumTaskResult +from braket.tasks.local_quantum_task import LocalQuantumTask + +RESULT = GateModelQuantumTaskResult.from_string( + json.dumps( + { + "StateVector": {"00": [0.2, 0.2], "01": [0.3, 0.1], "10": [0.1, 0.3], "11": [0.2, 0.2]}, + "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], + "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED"}, + } + ) +) + +TASK = LocalQuantumTask(RESULT) + + +def test_id(): + # Task ID is valid UUID + uuid.UUID(TASK.id) + + +def test_state(): + assert TASK.state() == "COMPLETED" + + +def test_result(): + assert TASK.result == RESULT + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_cancel(): + TASK.cancel() + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_async(): + TASK.async_result() From 54d65c2273334a32a97cfe1ce1449f7abfca0791 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 24 Mar 2020 18:19:54 -0600 Subject: [PATCH 0086/1165] Add `from_dict` to results (#58) * This eliminates the need for local simulators to go through an extra ser/de results step * Updated LocalSimulator and BraketSimulator to use this new method * Also improved formatting for a couple of files --- src/braket/aws/aws_qpu.py | 1 + src/braket/devices/braket_simulator.py | 9 ++- src/braket/devices/local_simulator.py | 13 ++-- .../tasks/annealing_quantum_task_result.py | 44 +++++++---- .../tasks/gate_model_quantum_task_result.py | 47 +++++++++-- src/braket/tasks/local_quantum_task.py | 1 + .../test_simulator_quantum_task.py | 1 + .../braket/annealing/test_problem.py | 1 + .../braket/devices/test_local_simulator.py | 53 ++++++------- .../test_annealing_quantum_task_result.py | 34 +++++++- .../test_gate_model_quantum_task_result.py | 78 ++++++++++++++++++- .../braket/tasks/test_local_quantum_task.py | 21 ++--- 12 files changed, 233 insertions(+), 70 deletions(-) diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py index a965557f..bafe8b7c 100644 --- a/src/braket/aws/aws_qpu.py +++ b/src/braket/aws/aws_qpu.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 typing import Any, Dict, Optional, Union import boto3 diff --git a/src/braket/devices/braket_simulator.py b/src/braket/devices/braket_simulator.py index f5d69cf0..434ee01b 100644 --- a/src/braket/devices/braket_simulator.py +++ b/src/braket/devices/braket_simulator.py @@ -10,8 +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 abc import ABC, abstractmethod -from typing import Union +from typing import Any, Dict, Union from braket.ir.annealing import Problem from braket.ir.jaqcd import Program @@ -41,7 +42,7 @@ class BraketSimulator(ABC): # TODO: Update to use new simulate() method @abstractmethod - def run(self, ir: Union[Program, Problem], *args, **kwargs) -> str: + def run(self, ir: Union[Program, Problem], *args, **kwargs) -> Dict[str, Any]: """ Run the task specified by the given IR. Extra arguments will contain any additional information necessary to run the task, @@ -51,9 +52,9 @@ def run(self, ir: Union[Program, Problem], *args, **kwargs) -> str: ir (Union[Program, Problem]): The IR representation of the program Returns: - str: A JSON string containing the results of the simulation. + Dict[str, Any]: A dict containing the results of the simulation. In order to work with braket-python-sdk, the format of the JSON dict should match that needed by GateModelQuantumTaskResult or AnnealingQuantumTaskResult - in the SDK, depending on the type of task. + from the SDK, depending on the type of task. """ raise NotImplementedError() diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 935ebf40..63237ccb 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.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 functools import singledispatch from typing import Set, Union @@ -69,8 +70,8 @@ def run( result = _run_internal(task_specification, self._delegate, *args, **kwargs) return LocalQuantumTask(result) - @classmethod - def registered_backends(cls) -> Set[str]: + @staticmethod + def registered_backends() -> Set[str]: """ Gets the backends that have been registered as entry points Returns: @@ -108,12 +109,12 @@ def _run_internal(task_specification, simulator: BraketSimulator, *args, **kwarg def _(circuit: Circuit, simulator: BraketSimulator, *args, **kwargs): program = circuit.to_ir() qubits = circuit.qubit_count - result_json = simulator.run(program, qubits, *args, **kwargs) - return GateModelQuantumTaskResult.from_string(result_json) + results_dict = simulator.run(program, qubits, *args, **kwargs) + return GateModelQuantumTaskResult.from_dict(results_dict) @_run_internal.register def _(problem: Problem, simulator: BraketSimulator, *args, **kwargs): ir = problem.to_ir() - result_json = simulator.run(ir, *args, *kwargs) - return AnnealingQuantumTaskResult.from_string(result_json) + results_dict = simulator.run(ir, *args, *kwargs) + return AnnealingQuantumTaskResult.from_dict(results_dict) diff --git a/src/braket/tasks/annealing_quantum_task_result.py b/src/braket/tasks/annealing_quantum_task_result.py index 572d3ca3..cc20b23d 100644 --- a/src/braket/tasks/annealing_quantum_task_result.py +++ b/src/braket/tasks/annealing_quantum_task_result.py @@ -91,6 +91,19 @@ def __eq__(self, other) -> bool: return (self.record_array == other.record_array).all() and self_fields == other_fields return NotImplemented + @staticmethod + def from_dict(result: Dict[str, Any]): + """ + Create AnnealingQuantumTaskResult from dict + + Args: + result (Dict[str, Any]): Results dict with AnnealingQuantumTaskResult attributes as keys + + Returns: + AnnealingQuantumTaskResult: An AnnealingQuantumTaskResult based on the given dict + """ + return AnnealingQuantumTaskResult._from_dict_internal(result) + @staticmethod def from_string(result: str) -> AnnealingQuantumTaskResult: """ @@ -100,26 +113,29 @@ def from_string(result: str) -> AnnealingQuantumTaskResult: result (str): JSON object string Returns: - AnnealingQuantumTaskResult: An AnnealingQuantumTaskResult based on a string. + AnnealingQuantumTaskResult: An AnnealingQuantumTaskResult based on the given string """ - json_obj = json.loads(result) - solutions = numpy.asarray(json_obj["Solutions"], dtype=int) - values = numpy.asarray(json_obj["Values"], dtype=float) - if json_obj["SolutionCounts"] is None: + return AnnealingQuantumTaskResult._from_dict_internal(json.loads(result)) + + @classmethod + def _from_dict_internal(cls, result: Dict[str, Any]): + solutions = numpy.asarray(result["Solutions"], dtype=int) + values = numpy.asarray(result["Values"], dtype=float) + if result["SolutionCounts"] is None: solution_counts = numpy.ones(len(solutions), dtype=int) else: - solution_counts = numpy.asarray(json_obj["SolutionCounts"], dtype=int) - record_array = AnnealingQuantumTaskResult.create_record_array( + solution_counts = numpy.asarray(result["SolutionCounts"], dtype=int) + record_array = AnnealingQuantumTaskResult._create_record_array( solutions, solution_counts, values ) - variable_count = json_obj["VariableCount"] - problem_type = json_obj["ProblemType"] - task_metadata = json_obj["TaskMetadata"] + variable_count = result["VariableCount"] + problem_type = result["ProblemType"] + task_metadata = result["TaskMetadata"] additional_metadata = {} - for key in json_obj.keys(): + for key in result.keys(): if key.endswith("Metadata") and key != "TaskMetadata": - additional_metadata[key] = json_obj[key] - return AnnealingQuantumTaskResult( + additional_metadata[key] = result[key] + return cls( record_array=record_array, variable_count=variable_count, problem_type=problem_type, @@ -128,7 +144,7 @@ def from_string(result: str) -> AnnealingQuantumTaskResult: ) @staticmethod - def create_record_array( + def _create_record_array( solutions: numpy.ndarray, solution_counts: numpy.ndarray, values: numpy.ndarray ) -> numpy.recarray: """ diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index ae1629a7..3dbf62ec 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -130,29 +130,60 @@ def measurements_from_measurement_probabilities( measurements_list.extend(individual_measurement_list) return np.asarray(measurements_list, dtype=int) + @staticmethod + def from_dict(result: Dict[str, Any]): + """ + Create GateModelQuantumTaskResult from dict. + + Args: + result (Dict[str, Any]): Results dict with GateModelQuantumTaskResult attributes as keys + + Returns: + GateModelQuantumTaskResult: A GateModelQuantumTaskResult based on the given dict + + Raises: + ValueError: If neither "Measurements" nor "MeasurementProbabilities" is a key + in the result dict + + Note: + For the "StateVector" key, the value should be of type Dict[str, complex]; + each bitstring's amplitude is a Python complex number. + """ + return GateModelQuantumTaskResult._from_dict_internal(result) + @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. Returns: - GateModelQuantumTaskResult: A GateModelQuantumTaskResult based on a string + GateModelQuantumTaskResult: A GateModelQuantumTaskResult based on the given string Raises: ValueError: If neither "Measurements" nor "MeasurementProbabilities" is a key in the result dict + + Note: + For the "StateVector" key, the value should be of type Dict[str, List[float, float]]; + each bitstring's amplitude is represented by a two-element list, with first element + the real component and second element the imaginary component. """ json_obj = json.loads(result) - task_metadata = json_obj["TaskMetadata"] state_vector = json_obj.get("StateVector", None) if state_vector: for state in state_vector: state_vector[state] = complex(*state_vector[state]) - if "Measurements" in json_obj: - measurements = np.asarray(json_obj["Measurements"], dtype=int) + return GateModelQuantumTaskResult._from_dict_internal(json_obj) + + @classmethod + def _from_dict_internal(cls, result: Dict[str, Any]): + task_metadata = result["TaskMetadata"] + state_vector = result.get("StateVector", None) + if "Measurements" in result: + measurements = np.asarray(result["Measurements"], dtype=int) m_counts = GateModelQuantumTaskResult.measurement_counts_from_measurements(measurements) m_probs = GateModelQuantumTaskResult.measurement_probabilities_from_measurement_counts( m_counts @@ -160,9 +191,9 @@ def from_string(result: str) -> GateModelQuantumTaskResult: measurements_copied_from_device = True m_counts_copied_from_device = False m_probabilities_copied_from_device = False - elif "MeasurementProbabilities" in json_obj: + elif "MeasurementProbabilities" in result: shots = task_metadata["Shots"] - m_probs = json_obj["MeasurementProbabilities"] + m_probs = result["MeasurementProbabilities"] measurements = GateModelQuantumTaskResult.measurements_from_measurement_probabilities( m_probs, shots ) @@ -174,7 +205,7 @@ def from_string(result: str) -> GateModelQuantumTaskResult: raise ValueError( 'One of "Measurements" or "MeasurementProbabilities" must be in the results dict' ) - return GateModelQuantumTaskResult( + return cls( state_vector=state_vector, task_metadata=task_metadata, measurements=measurements, diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py index 9c64cad5..c6af7223 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. + import asyncio import uuid from typing import Union diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 2ccd5b7c..71b082e2 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_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. + import math import pytest diff --git a/test/unit_tests/braket/annealing/test_problem.py b/test/unit_tests/braket/annealing/test_problem.py index d8176602..8cd90944 100644 --- a/test/unit_tests/braket/annealing/test_problem.py +++ b/test/unit_tests/braket/annealing/test_problem.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 braket.ir.annealing as ir from braket.annealing.problem import Problem, ProblemType diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 3c4f496e..5066c7f5 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -10,8 +10,8 @@ # 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 typing import Optional + +from typing import Any, Dict, Optional from unittest.mock import Mock import braket.ir as ir @@ -22,31 +22,32 @@ from braket.devices.braket_simulator import BraketSimulator from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult -GATE_MODEL_RESULT = json.dumps( - { - "StateVector": {"00": [0.2, 0.2], "01": [0.3, 0.1], "10": [0.1, 0.3], "11": [0.2, 0.2]}, - "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], - "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED"}, - } -) - -ANNEALING_RESULT = json.dumps( - { - "Solutions": [[-1, -1, -1, -1], [1, -1, 1, 1], [1, -1, -1, 1]], - "VariableCount": 4, - "Values": [0.0, 1.0, 2.0], - "SolutionCounts": None, - "ProblemType": "ising", - "Foo": {"Bar": "Baz"}, - "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED", "Shots": 5,}, - } -) +GATE_MODEL_RESULT = { + "StateVector": { + "00": complex(0.2, 0.2), + "01": complex(0.3, 0.1), + "10": complex(0.1, 0.3), + "11": complex(0.2, 0.2), + }, + "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], + "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED"}, +} + +ANNEALING_RESULT = { + "Solutions": [[-1, -1, -1, -1], [1, -1, 1, 1], [1, -1, -1, 1]], + "VariableCount": 4, + "Values": [0.0, 1.0, 2.0], + "SolutionCounts": None, + "ProblemType": "ising", + "Foo": {"Bar": "Baz"}, + "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED", "Shots": 5,}, +} class DummyCircuitSimulator(BraketSimulator): def run( self, program: ir.jaqcd.Program, qubits: int, shots: Optional[int], *args, **kwargs - ) -> str: + ) -> Dict[str, Any]: self._shots = shots self._qubits = qubits return GATE_MODEL_RESULT @@ -59,7 +60,7 @@ def assert_qubits(self, qubits): class DummyAnnealingSimulator(BraketSimulator): - def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> str: + def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> Dict[str, Any]: return ANNEALING_RESULT @@ -71,7 +72,7 @@ def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> str: def test_load_from_entry_point(): sim = LocalSimulator("dummy") task = sim.run(Circuit().h(0).cnot(0, 1), 10) - assert task.result == GateModelQuantumTaskResult.from_string(GATE_MODEL_RESULT) + assert task.result == GateModelQuantumTaskResult.from_dict(GATE_MODEL_RESULT) def test_run_gate_model(): @@ -80,13 +81,13 @@ def test_run_gate_model(): task = sim.run(Circuit().h(0).cnot(0, 1), 10) dummy.assert_shots(10) dummy.assert_qubits(2) - assert task.result == GateModelQuantumTaskResult.from_string(GATE_MODEL_RESULT) + assert task.result == GateModelQuantumTaskResult.from_dict(GATE_MODEL_RESULT) def test_run_annealing(): sim = LocalSimulator(DummyAnnealingSimulator()) task = sim.run(Problem(ProblemType.ISING)) - assert task.result == AnnealingQuantumTaskResult.from_string(ANNEALING_RESULT) + assert task.result == AnnealingQuantumTaskResult.from_dict(ANNEALING_RESULT) def test_registered_backends(): 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 36f830dd..53fe89b6 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 @@ -110,7 +110,7 @@ def annealing_result( solutions = np.asarray(solutions, dtype=int) values = np.asarray(values, dtype=float) solution_counts = np.asarray(solution_counts, dtype=int) - record_array = AnnealingQuantumTaskResult.create_record_array( + record_array = AnnealingQuantumTaskResult._create_record_array( solutions, solution_counts, values ) return AnnealingQuantumTaskResult( @@ -122,6 +122,30 @@ def annealing_result( ) +def test_from_dict( + result_str_1, + solutions, + values, + solution_counts, + variable_count, + problem_type, + task_metadata, + dwave_metadata, +): + result = AnnealingQuantumTaskResult.from_dict(json.loads(result_str_1)) + solutions = np.asarray(solutions, dtype=int) + values = np.asarray(values, dtype=float) + solution_counts = np.asarray(solution_counts, dtype=int) + assert result.variable_count == variable_count + assert result.problem_type == problem_type + assert result.task_metadata == task_metadata + assert result.additional_metadata == {"DWaveMetadata": dwave_metadata} + np.testing.assert_equal( + result.record_array, + AnnealingQuantumTaskResult._create_record_array(solutions, solution_counts, values), + ) + + def test_from_string( result_str_1, solutions, @@ -142,7 +166,7 @@ def test_from_string( assert result.additional_metadata == {"DWaveMetadata": dwave_metadata} np.testing.assert_equal( result.record_array, - AnnealingQuantumTaskResult.create_record_array(solutions, solution_counts, values), + AnnealingQuantumTaskResult._create_record_array(solutions, solution_counts, values), ) @@ -182,6 +206,12 @@ def test_data_sort_by(annealing_result, solutions, values, solution_counts): assert d[0][2] == solution_counts[min_index] +def test_from_dict_equal_to_from_string(result_str_1): + assert AnnealingQuantumTaskResult.from_dict( + json.loads(result_str_1) + ) == AnnealingQuantumTaskResult.from_string(result_str_1) + + def test_equality(result_str_1, result_str_2): result_1 = AnnealingQuantumTaskResult.from_string(result_str_1) result_2 = AnnealingQuantumTaskResult.from_string(result_str_1) 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 6fcbf8d0..98032a70 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 @@ -20,6 +20,24 @@ from braket.tasks import GateModelQuantumTaskResult +@pytest.fixture +def result_dict_1(): + return { + "StateVector": { + "00": complex(0.2, 0.2), + "01": complex(0.3, 0.1), + "10": complex(0.1, 0.3), + "11": complex(0.2, 0.2), + }, + "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], + "TaskMetadata": { + "Id": "UUID_blah_1", + "Status": "COMPLETED", + "BackendArn": AwsQpuArns.RIGETTI, + }, + } + + @pytest.fixture def result_str_1(): return json.dumps( @@ -79,6 +97,17 @@ def parsed_state_vector(): } +@pytest.fixture +def malformatted_results(): + return { + "TaskMetadata": { + "Id": "UUID_blah_1", + "Status": "COMPLETED", + "BackendArn": AwsQpuArns.RIGETTI, + }, + } + + 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]] @@ -150,7 +179,34 @@ def test_task_metadata(): assert result.task_metadata == task_metadata -def test_from_string_measurements(result_str_1, parsed_state_vector): +def test_from_dict_measurements_state_vector(result_dict_1): + task_result = GateModelQuantumTaskResult.from_dict(result_dict_1) + expected_measurements = np.asarray(result_dict_1["Measurements"], dtype=int) + assert task_result.task_metadata == result_dict_1["TaskMetadata"] + assert task_result.state_vector == result_dict_1["StateVector"] + assert np.array2string(task_result.measurements) == np.array2string(expected_measurements) + assert not task_result.measurement_counts_copied_from_device + assert not task_result.measurement_probabilities_copied_from_device + assert task_result.measurements_copied_from_device + + +def test_from_dict_measurement_probabilities(result_str_3): + result_obj = json.loads(result_str_3) + task_result = GateModelQuantumTaskResult.from_dict(result_obj) + assert task_result.measurement_probabilities == result_obj["MeasurementProbabilities"] + assert task_result.task_metadata == result_obj["TaskMetadata"] + assert task_result.state_vector is None + shots = 100 + measurement_list = [list("011000") for x 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 not task_result.measurement_counts_copied_from_device + assert task_result.measurement_probabilities_copied_from_device + assert not task_result.measurements_copied_from_device + + +def test_from_string_measurements_state_vector(result_str_1, parsed_state_vector): result_obj = json.loads(result_str_1) task_result = GateModelQuantumTaskResult.from_string(result_str_1) expected_measurements = np.asarray(result_obj["Measurements"], dtype=int) @@ -178,6 +234,15 @@ def test_from_string_measurement_probabilities(result_str_3): assert not task_result.measurements_copied_from_device +def test_from_dict_equal_to_from_string(result_dict_1, result_str_1, result_str_3): + assert GateModelQuantumTaskResult.from_dict( + result_dict_1 + ) == GateModelQuantumTaskResult.from_string(result_str_1) + assert GateModelQuantumTaskResult.from_dict( + json.loads(result_str_3) + ) == GateModelQuantumTaskResult.from_string(result_str_3) + + def test_equality(result_str_1, result_str_2): result_1 = GateModelQuantumTaskResult.from_string(result_str_1) result_2 = GateModelQuantumTaskResult.from_string(result_str_1) @@ -188,3 +253,14 @@ def test_equality(result_str_1, result_str_2): assert result_1 is not result_2 assert result_1 != other_result assert result_1 != non_result + + +@pytest.mark.xfail(raises=ValueError) +def test_bad_dict(malformatted_results): + GateModelQuantumTaskResult.from_dict(malformatted_results) + + +@pytest.mark.xfail(raises=ValueError) +def test_bad_string(malformatted_results): + results_string = json.dumps(malformatted_results) + GateModelQuantumTaskResult.from_string(results_string) 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 cb9e5275..0392c9b4 100644 --- a/test/unit_tests/braket/tasks/test_local_quantum_task.py +++ b/test/unit_tests/braket/tasks/test_local_quantum_task.py @@ -10,21 +10,24 @@ # 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 uuid import pytest from braket.tasks import GateModelQuantumTaskResult from braket.tasks.local_quantum_task import LocalQuantumTask -RESULT = GateModelQuantumTaskResult.from_string( - json.dumps( - { - "StateVector": {"00": [0.2, 0.2], "01": [0.3, 0.1], "10": [0.1, 0.3], "11": [0.2, 0.2]}, - "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], - "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED"}, - } - ) +RESULT = GateModelQuantumTaskResult.from_dict( + { + "StateVector": { + "00": complex(0.2, 0.2), + "01": complex(0.3, 0.1), + "10": complex(0.1, 0.3), + "11": complex(0.2, 0.2), + }, + "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], + "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED"}, + } ) TASK = LocalQuantumTask(RESULT) From 4f1af7075cf65f6c37561bfcec9c204067b29052 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 25 Mar 2020 11:56:20 -0600 Subject: [PATCH 0087/1165] Remove SDK references in Instruction.to_ir() (#59) So users working with IR don't see SDK classes like `Qubit`. --- src/braket/circuits/circuit.py | 16 +++++++++------- src/braket/circuits/instruction.py | 14 ++++++++------ src/braket/circuits/qubit.py | 4 +++- src/braket/circuits/qubit_set.py | 4 +++- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 5302c69b..cffc7d0e 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.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 Callable, Dict, Iterable, TypeVar from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram @@ -126,7 +128,7 @@ def add_instruction( instruction: Instruction, target: QubitSetInput = None, target_mapping: Dict[QubitInput, QubitInput] = {}, - ) -> "Circuit": + ) -> Circuit: """ Add an instruction to `self`, returns `self` for chaining ability. @@ -191,10 +193,10 @@ def add_instruction( def add_circuit( self, - circuit: "Circuit", + circuit: Circuit, target: QubitSetInput = None, target_mapping: Dict[QubitInput, QubitInput] = {}, - ) -> "Circuit": + ) -> Circuit: """ Add a `circuit` to self, returns self for chaining ability. This is a composite form of `add_instruction()` since it adds all of the instructions of `circuit` to this circuit. @@ -256,7 +258,7 @@ def add_circuit( return self - def add(self, addable: AddableTypes, *args, **kwargs) -> "Circuit": + def add(self, addable: AddableTypes, *args, **kwargs) -> Circuit: """ Generic add method for adding instruction-like item(s) to self. Any arguments that `add_circuit()` and / or `add_instruction()` supports are supported by this method. @@ -335,7 +337,7 @@ def to_ir(self) -> Program: ir_instructions = [instr.to_ir() for instr in self.instructions] return Program(instructions=ir_instructions) - def _copy(self) -> "Circuit": + def _copy(self) -> Circuit: """ Return a shallow copy of the circuit. @@ -344,10 +346,10 @@ def _copy(self) -> "Circuit": """ return Circuit().add(self.instructions) - def __iadd__(self, addable: AddableTypes) -> "Circuit": + def __iadd__(self, addable: AddableTypes) -> Circuit: return self.add(addable) - def __add__(self, addable: AddableTypes) -> "Circuit": + def __add__(self, addable: AddableTypes) -> Circuit: new = self._copy() new.add(addable) return new diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index eae4872c..57a018b1 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.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 Dict from braket.circuits.operator import Operator @@ -74,11 +76,11 @@ def to_ir(self): Converts the operator into the canonical intermediate representation. If the operator is passed in a request, this method is called before it is passed. """ - return self.operator.to_ir(self.target) + return self._operator.to_ir([int(qubit) for qubit in self._target]) def copy( self, target_mapping: Dict[QubitInput, QubitInput] = {}, target: QubitSetInput = None - ) -> "Instruction": + ) -> Instruction: """ Return a shallow copy of the instruction. @@ -114,14 +116,14 @@ def copy( 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: - return Instruction(self.operator, target) + return Instruction(self._operator, target) else: - return Instruction(self.operator, self.target.map(target_mapping)) + return Instruction(self._operator, self._target.map(target_mapping)) def __repr__(self): - return f"Instruction('operator': {self.operator}, 'target': {self.target})" + return f"Instruction('operator': {self._operator}, 'target': {self._target})" def __eq__(self, other): if isinstance(other, Instruction): - return (self.operator, self.target) == (other.operator, other.target) + return (self._operator, self._target) == (other._operator, other._target) return NotImplemented diff --git a/src/braket/circuits/qubit.py b/src/braket/circuits/qubit.py index aaba8de4..34431a66 100644 --- a/src/braket/circuits/qubit.py +++ b/src/braket/circuits/qubit.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 Union QubitInput = Union["Qubit", int] @@ -45,7 +47,7 @@ def __str__(self): return self.__repr__() @staticmethod - def new(qubit: QubitInput) -> "Qubit": + def new(qubit: QubitInput) -> Qubit: """ Helper constructor - if input is a Qubit it returns the same value, else a new Qubit is constructed. diff --git a/src/braket/circuits/qubit_set.py b/src/braket/circuits/qubit_set.py index 46208eba..66df59a1 100644 --- a/src/braket/circuits/qubit_set.py +++ b/src/braket/circuits/qubit_set.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 Dict, Iterable, Union from boltons.setutils import IndexedSet @@ -61,7 +63,7 @@ def _flatten(other): _qubits = [Qubit.new(qubit) for qubit in _flatten(qubits)] super().__init__(_qubits) - def map(self, mapping: Dict[QubitInput, QubitInput]) -> "QubitSet": + def map(self, mapping: Dict[QubitInput, QubitInput]) -> QubitSet: """ 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. From 60a8397411499d5351a29f81586ea1465445a858 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Wed, 25 Mar 2020 11:32:41 -0700 Subject: [PATCH 0088/1165] removing disclaimer about annealing support (#56) --- src/braket/aws/aws_qpu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py index bafe8b7c..7011e70a 100644 --- a/src/braket/aws/aws_qpu.py +++ b/src/braket/aws/aws_qpu.py @@ -69,7 +69,7 @@ def run( ) -> AwsQuantumTask: """ Run a quantum task specification on this quantum device. A task can be a circuit or an - annealing problem. Currently, only circuits are supported in the Private Beta. + annealing problem. Args: task_specification (Union[Circuit, Problem]): Specification of task From 78f9a23082bc76d254de348cf18739b0eb859f3f Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 3 Apr 2020 14:21:59 -0600 Subject: [PATCH 0089/1165] Make LocalQuantumTask.result not a property (#63) --- src/braket/tasks/local_quantum_task.py | 1 - test/unit_tests/braket/devices/test_local_simulator.py | 6 +++--- test/unit_tests/braket/tasks/test_local_quantum_task.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py index c6af7223..fddae760 100644 --- a/src/braket/tasks/local_quantum_task.py +++ b/src/braket/tasks/local_quantum_task.py @@ -38,7 +38,6 @@ def cancel(self) -> None: def state(self) -> str: return "COMPLETED" - @property def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: return self._result diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 5066c7f5..cd1c83db 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -72,7 +72,7 @@ def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> Dict[str, Any]: def test_load_from_entry_point(): sim = LocalSimulator("dummy") task = sim.run(Circuit().h(0).cnot(0, 1), 10) - assert task.result == GateModelQuantumTaskResult.from_dict(GATE_MODEL_RESULT) + assert task.result() == GateModelQuantumTaskResult.from_dict(GATE_MODEL_RESULT) def test_run_gate_model(): @@ -81,13 +81,13 @@ def test_run_gate_model(): task = sim.run(Circuit().h(0).cnot(0, 1), 10) dummy.assert_shots(10) dummy.assert_qubits(2) - assert task.result == GateModelQuantumTaskResult.from_dict(GATE_MODEL_RESULT) + assert task.result() == GateModelQuantumTaskResult.from_dict(GATE_MODEL_RESULT) def test_run_annealing(): sim = LocalSimulator(DummyAnnealingSimulator()) task = sim.run(Problem(ProblemType.ISING)) - assert task.result == AnnealingQuantumTaskResult.from_dict(ANNEALING_RESULT) + assert task.result() == AnnealingQuantumTaskResult.from_dict(ANNEALING_RESULT) def test_registered_backends(): 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 0392c9b4..1b0c6ce9 100644 --- a/test/unit_tests/braket/tasks/test_local_quantum_task.py +++ b/test/unit_tests/braket/tasks/test_local_quantum_task.py @@ -43,7 +43,7 @@ def test_state(): def test_result(): - assert TASK.result == RESULT + assert TASK.result() == RESULT @pytest.mark.xfail(raises=NotImplementedError) From e3d9c28da37ee919456dde00af6549f266d44d05 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Tue, 7 Apr 2020 16:19:11 -0700 Subject: [PATCH 0090/1165] Add amazon-braket-default-simulator-python (#64) Added amazon-braket-default-simulator-python as an installation requirement. This would allow users to simulate quantum circuits locally using the LocalSimulator interface. --- README.md | 32 +++++++++++++++---- examples/local_bell.py | 7 ++++ setup.py | 4 +++ src/braket/aws/aws_quantum_task.py | 1 - src/braket/devices/local_simulator.py | 23 +++++++++---- src/braket/tasks/local_quantum_task.py | 3 ++ src/braket/tasks/quantum_task.py | 2 ++ .../braket/tasks/test_local_quantum_task.py | 5 +++ 8 files changed, 62 insertions(+), 15 deletions(-) create mode 100644 examples/local_bell.py diff --git a/README.md b/README.md index ad9f73f4..bf9798e1 100644 --- a/README.md +++ b/README.md @@ -119,20 +119,23 @@ The easiest way to get the SDKs is to download them directly from the GitHub sit Use the following links to download the Amazon Braket Python SDK repos: - [braket-python-ir](https://github.com/aws/braket-python-ir/archive/stable/latest.zip) +- [amazon-braket-default-simulator-python](https://github.com/aws/amazon-braket-default-simulator-python/archive/stable/latest.zip) - [braket-python-sdk](https://github.com/aws/braket-python-sdk/archive/stable/latest.zip) ### Extract the SDK .zip files Because the files were downloaded directly from GitHub, the folder in the .zip file includes the name of the branch of the GitHub repo that was downloaded, in this case the `stable/latest` branch. But to use the files in the SDK, we need to rename the folder to the original name. -Note: Make sure you are always using the branch 'stable/latest' and not 'master' for the SDK. 'master' may contain unstable changes. +Note: Make sure you are always using the branch 'stable/latest' and not 'master'. The 'master' branch may contain in-progress changes that result in errors. **To rename the folders in the SDK .zip files** -First, extract the .zip files to a location of your choosing. Then open the location where you extracted the folders to. You can use either the GUI file system tools in your OS, or the command line. You should see 2 folders with the following names: +First, extract the .zip files to a location of your choosing. Then open the location where you extracted the folders to. You can use either the GUI file system tools in your OS, or the command line. You should see 3 folders with the following names: - braket-python-ir-stable-latest +- amazon-braket-default-simulator-python-stable-latest - braket-python-sdk-stable-latest Rename the folders to the following: - braket-python-ir +- amazon-braket-default-simulator-python - braket-python-sdk Then copy the renamed files and paste them into the `braketvirtenv` folder where you created a virtual environment. Your folder structure should look like this: @@ -147,6 +150,10 @@ Use the following commands to install the SDKs in the order that they appear: pip install -e braket-python-ir ``` +```bash +pip install -e amazon-braket-default-simulator-python +``` + ```bash pip install -e braket-python-sdk ``` @@ -190,7 +197,7 @@ The code sample imports the Amazon Braket framework, then defines the execution There are currently three simulators available for Amazon Braket. To specify which simulator to use, change the code sample to replace the value for the `AwsQuantumSimulator` to one of the following values: - `arn:aws:aqx:::quantum-simulator:aqx:qs1` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the state vector and outputs an array of bit strings that appears as though it came from a quantum computer. Does not provide a state vector. - `arn:aws:aqx:::quantum-simulator:aqx:qs2` – a tensor network simulator. Provides an approximation of running a job on a quantum computer. -- `arn:aws:aqx:::quantum-simulator:aqx:qs3` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples from the state vector but includes the entire state vector. This generates more data, and therefore incurs additional costs for storage of data in Amazon S3. +- `arn:aws:aqx:::quantum-simulator:aqx:qs3` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples from the state vector but includes the entire state vector. This generates more data, and therefore incurs additional costs for storage of data in Amazon S3. #### To validate your configuration using a Python file 1. Open a text editor with example file `../braket-python-sdk/examples/bell.py`. @@ -236,6 +243,10 @@ When the job completes, you should see output similar to the following: **Important** Tasks may not run immediately on the QPU. IonQ runs tasks once every 24 hours. Rigetti tasks run when the QPU is available, with times varying day to day. +#### To validate your quantum algorithm locally + +Braket Python SDK comes bundled with an implementation of a quantum simulator that you can run locally. You can use the local simulator to test quantum tasks constructed using the SDK before you submit them to the Amazon Braket service for execution. An example of how to execute the task locally is included in the repo `../examples/local_bell.py`. + #### Debugging logs Tasks sent to QPUs don't always run right away. For IonQ, jobs are run once every 24 hours. For Rigetti, tasks are queued and run when the QPU is available, with the time varying day to day. To view task status, you can enable debugging logs. An example of how to enable these logs is included in repo: `../examples/debug_bell.py`. This example enables task logging so that status updates are continuously printed to console after a quantum task is executed. The logs can also be configured to save to a file or output to another stream. You can use the debugging example to get information on the tasks you submit, such as the current status, so that you know when your task completes. @@ -293,14 +304,21 @@ When you want to use it again, you can reactivate it with the same command you u We will periodically make updates and changes the SDK or the model. When you are notified of a change that requires action on your part, use the following steps to update your environment to the latest version. ### Check the version you have installed -You can view the version of the braket-python-sdk that you have installed by using the following command in the virtual environment: +You can view the version of the braket packages that you have installed by using the following commands in the virtual environment: ```bash +pip show amazon-braket-default-simulator-python +pip show braket-ir pip show braket-sdk ``` -Compare the version displayed in your local environment with the latest version listed in the [Releases](https://github.com/aws/braket-python-sdk/releases) page. If the version listed is higher than your local version, you should update to the latest release. +Compare the version displayed in your local environment with the latest version listed for each of the following release pages: +- [amazon-braket-default-simulator-python](https://github.com/aws/amazon-braket-default-simulator-python/releases) +- [braket-python-ir](https://github.com/aws/braket-python-ir/releases) +- [braket-python-sdk](https://github.com/aws/braket-python-sdk/releases) + +If the version listed is higher than your local version, you should update to the latest release. ### To get the lastest updates -Perform the steps described in the [Setting up the Amazon Braket Python SDKs](https://github.com/aws/braket-python-sdk/tree/stable/latest#setting-up-the-amazon-braket-python-sdks) section of this document. The links in that section point to the most recent version of the braket-python-sdk, braket-python-ir, and model file you need to set up the new version of the SDK. +Perform the steps described in the [Setting up the Amazon Braket Python SDKs](https://github.com/aws/braket-python-sdk/tree/stable/latest#setting-up-the-amazon-braket-python-sdks) section of this document. The links in that section point to the most recent version of the braket-python-sdk, braket-python-ir, amazon-braket-default-simulator-python, and model file you need to set up the new version of the SDK. You can extract the file to the same location you are using and replace the existing files with the updated SDK. This lets you continue to use the same virtual environment. @@ -327,7 +345,7 @@ tox -e docs ``` To view the generated documentation, open the following file in a browser: -../braket-python-sdk/build/documentation/html/index.html` +`../braket-python-sdk/build/documentation/html/index.html` ## Install the SDK for Testing Make sure to install test dependencies first: diff --git a/examples/local_bell.py b/examples/local_bell.py new file mode 100644 index 00000000..f46b4815 --- /dev/null +++ b/examples/local_bell.py @@ -0,0 +1,7 @@ +from braket.circuits import Circuit +from braket.devices import LocalSimulator + +device = LocalSimulator() + +bell = Circuit().h(0).cnot(0, 1) +print(device.run(bell).result().measurement_counts) diff --git a/setup.py b/setup.py index 72ada0e1..4437b48a 100644 --- a/setup.py +++ b/setup.py @@ -22,6 +22,10 @@ package_dir={"": "src"}, install_requires=[ "braket-ir @ git+https://github.com/aws/braket-python-ir.git", + ( + "amazon-braket-default-simulator-python @" + "git+https://github.com/aws/amazon-braket-default-simulator-python.git" + ), "boltons", "boto3", "nest-asyncio", diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 2efcfdd0..2aa6ff8e 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -36,7 +36,6 @@ class AwsQuantumTask(QuantumTask): GATE_IR_TYPE = "jaqcd" ANNEALING_IR_TYPE = "annealing" - DEFAULT_SHOTS = 1_000 DEFAULT_RESULTS_POLL_TIMEOUT = 120 DEFAULT_RESULTS_POLL_INTERVAL = 0.25 diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 63237ccb..73fb4bde 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. from functools import singledispatch -from typing import Set, Union +from typing import Optional, Set, Union import pkg_resources @@ -35,11 +35,12 @@ class LocalSimulator(Device): results using constructs from the SDK rather than Braket IR. """ - def __init__(self, backend: Union[str, BraketSimulator]): + def __init__(self, backend: Union[str, BraketSimulator] = "default"): """ Args: backend (Union[str, BraketSimulator]): The name of the simulator backend or - the actual simulator instance to use for simulation + the actual simulator instance to use for simulation. Defaults to the + "default" simulator backend name. """ delegate = _get_simulator(backend) super().__init__( @@ -66,6 +67,11 @@ def run( Note: If running a circuit, the number of qubits will be passed to the backend as the argument after the circuit itself. + + Examples: + >>> circuit = Circuit().h(0).cnot(0, 1) + >>> device = LocalSimulator("default") + >>> device.run(circuit, shots=1000) """ result = _run_internal(task_specification, self._delegate, *args, **kwargs) return LocalQuantumTask(result) @@ -101,20 +107,23 @@ def _(backend_impl: BraketSimulator): @singledispatch -def _run_internal(task_specification, simulator: BraketSimulator, *args, **kwargs): +def _run_internal( + task_specification, simulator: BraketSimulator, shots: Optional[int] = None, *args, **kwargs +): raise NotImplementedError("Unsupported task type") @_run_internal.register -def _(circuit: Circuit, simulator: BraketSimulator, *args, **kwargs): +def _(circuit: Circuit, simulator: BraketSimulator, shots: Optional[int] = None, *args, **kwargs): program = circuit.to_ir() qubits = circuit.qubit_count - results_dict = simulator.run(program, qubits, *args, **kwargs) + shots_count = shots if shots else LocalQuantumTask.DEFAULT_SHOTS + results_dict = simulator.run(program, qubits, shots=shots_count, *args, **kwargs) return GateModelQuantumTaskResult.from_dict(results_dict) @_run_internal.register -def _(problem: Problem, simulator: BraketSimulator, *args, **kwargs): +def _(problem: Problem, simulator: BraketSimulator, shots: Optional[int] = None, *args, **kwargs): ir = problem.to_ir() results_dict = simulator.run(ir, *args, *kwargs) return AnnealingQuantumTaskResult.from_dict(results_dict) diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py index fddae760..2b6f2c6f 100644 --- a/src/braket/tasks/local_quantum_task.py +++ b/src/braket/tasks/local_quantum_task.py @@ -44,3 +44,6 @@ def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult def async_result(self) -> asyncio.Task: # TODO: Allow for asynchronous simulation raise NotImplementedError("Asynchronous local simulation unsupported") + + def __repr__(self) -> str: + return f"LocalQuantumTask('id':{self.id})" diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py index 99b03751..e1e8d787 100644 --- a/src/braket/tasks/quantum_task.py +++ b/src/braket/tasks/quantum_task.py @@ -22,6 +22,8 @@ class QuantumTask(ABC): """An abstraction over a quantum task on a quantum device.""" + DEFAULT_SHOTS = 1_000 + @property @abstractmethod def id(self) -> str: 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 1b0c6ce9..58a17157 100644 --- a/test/unit_tests/braket/tasks/test_local_quantum_task.py +++ b/test/unit_tests/braket/tasks/test_local_quantum_task.py @@ -54,3 +54,8 @@ def test_cancel(): @pytest.mark.xfail(raises=NotImplementedError) def test_async(): TASK.async_result() + + +def test_str(): + expected = "LocalQuantumTask('id':{})".format(TASK.id) + assert str(TASK) == expected From d23509e2db5377b54637bb28ef5fb468a32aacce Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Mon, 4 May 2020 17:18:29 -0700 Subject: [PATCH 0091/1165] Update version to 0.3.5 (#71) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4437b48a..a2404870 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="braket-sdk", - version="0.3.4", + version="0.3.5", license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), From 43a693be8b6bee9bdf1cd4a4de9cfb754082a8ad Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Wed, 6 May 2020 10:18:59 -0700 Subject: [PATCH 0092/1165] Add ability to measure in other bases than Pauli-Z (#73) --- src/braket/circuits/__init__.py | 4 +- src/braket/circuits/circuit.py | 64 +++++++++- src/braket/circuits/observable.py | 31 ++++- src/braket/circuits/observables.py | 110 +++++++++++++++++- .../circuits/quantum_operator_helpers.py | 25 ++++ src/braket/circuits/result_type.py | 66 ++++++++++- src/braket/circuits/result_types.py | 61 +--------- .../braket/circuits/test_circuit.py | 89 +++++++++++++- .../braket/circuits/test_observable.py | 26 ++++- .../braket/circuits/test_observables.py | 109 +++++++++++++++-- .../circuits/test_quantum_operator_helpers.py | 49 ++++++++ .../braket/circuits/test_result_type.py | 36 +++++- .../braket/circuits/test_result_types.py | 31 ----- 13 files changed, 582 insertions(+), 119 deletions(-) diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py index d049ba44..ed20ce8d 100644 --- a/src/braket/circuits/__init__.py +++ b/src/braket/circuits/__init__.py @@ -25,9 +25,9 @@ from braket.circuits.gate import Gate # noqa: F401 from braket.circuits.instruction import Instruction # noqa: F401 from braket.circuits.moments import Moments, MomentsKey # noqa: F401 -from braket.circuits.observable import Observable # noqa: F401 +from braket.circuits.observable import Observable, StandardObservable # noqa: F401 from braket.circuits.operator import Operator # 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 ResultType # noqa: F401 +from braket.circuits.result_type import ObservableResultType, ResultType # noqa: F401 diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index babc7274..ed1f4663 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -13,14 +13,15 @@ from __future__ import annotations -from typing import Callable, Dict, Iterable, List, TypeVar +from typing import Callable, Dict, Iterable, List, TypeVar, Union from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram from braket.circuits.instruction import Instruction from braket.circuits.moments import Moments +from braket.circuits.observable import Observable from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput -from braket.circuits.result_type import ResultType +from braket.circuits.result_type import ObservableResultType, ResultType from braket.ir.jaqcd import Program SubroutineReturn = TypeVar( @@ -45,6 +46,8 @@ class Circuit: iterable of `ResultType`, or `SubroutineCallable` """ + _ALL_QUBITS = "ALL" # Flag to indicate all qubits in _qubit_observable_mapping + @classmethod def register_subroutine(cls, func: SubroutineCallable) -> None: """ @@ -106,7 +109,8 @@ def __init__(self, addable: AddableTypes = None, *args, **kwargs): """ self._moments: Moments = Moments() - self._result_types: Iterable[ResultType] = [] + self._result_types: List[ResultType] = [] + self._qubit_observable_mapping: Dict[Union[int, Circuit._ALL_QUBITS], Observable] = {} if addable is not None: self.add(addable, *args, **kwargs) @@ -126,6 +130,28 @@ def result_types(self) -> List[ResultType]: """List[ResultType]: Get a list of requested result types in the circuit.""" return self._result_types + @property + def basis_rotation_instructions(self) -> List[Instruction]: + """List[Instruction]: Get a list of basis rotation instructions in the circuit. + These basis rotation instructions are added if result types are requested for + an observable other than Pauli-Z. + """ + # 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 = [] + observable_return_types = filter( + lambda x: isinstance(x, ObservableResultType), self._result_types + ) + for target, observable in [(obs.target, obs.observable) for obs in observable_return_types]: + for gate in observable.basis_rotation_gates: + if not target: + basis_rotation_instructions.extend( + [Instruction(gate, target) for target in self._moments.qubits] + ) + else: + basis_rotation_instructions.append(Instruction(gate, target)) + return basis_rotation_instructions + @property def moments(self) -> Moments: """Moments: Get the `moments` for this circuit.""" @@ -169,6 +195,9 @@ def add_result_type( Raises: TypeError: If both `target_mapping` and `target` are supplied. + ValueError: If the observable specified for a qubit is different from what is + specified by the result types already added to the circuit. Only one observable + is allowed for a qubit. Examples: >>> result_type = ResultType.Probability(target=[0, 1]) @@ -205,10 +234,28 @@ def add_result_type( result_type_to_add = result_type.copy(target=target) if result_type_to_add not in self._result_types: + self._add_to_qubit_observable_mapping(result_type) self._result_types.append(result_type_to_add) - return self + def _add_to_qubit_observable_mapping(self, result_type: ResultType) -> None: + if isinstance(result_type, ResultType.Probability): + observable = Observable.Z() # computational basis + elif isinstance(result_type, ObservableResultType): + observable = result_type.observable + else: + return + targets = result_type.target if result_type.target else [Circuit._ALL_QUBITS] + all_qubits_observable = self._qubit_observable_mapping.get(Circuit._ALL_QUBITS) + for target in targets: + current_observable = all_qubits_observable or self._qubit_observable_mapping.get(target) + if current_observable and current_observable != observable: + raise ValueError( + f"Existing result type for observable {current_observable} for target {target}" + + f"conflicts with observable {observable} for new result type" + ) + self._qubit_observable_mapping[target] = observable + def add_instruction( self, instruction: Instruction, @@ -433,7 +480,14 @@ def to_ir(self) -> Program: """ ir_instructions = [instr.to_ir() for instr in self.instructions] ir_results = [result_type.to_ir() for result_type in self.result_types] - return Program.construct(instructions=ir_instructions, results=ir_results) + ir_basis_rotation_instructions = [ + instr.to_ir() for instr in self.basis_rotation_instructions + ] + return Program.construct( + instructions=ir_instructions, + results=ir_results, + basis_rotation_instructions=ir_basis_rotation_instructions, + ) def _copy(self) -> Circuit: """ diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index 113ad67e..df499eed 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -12,9 +12,12 @@ # language governing permissions and limitations under the License. from __future__ import annotations -from typing import List, Sequence, Union +from typing import List, Sequence, Tuple, Union +import numpy as np +from braket.circuits.gate import Gate from braket.circuits.quantum_operator import QuantumOperator +from braket.circuits.quantum_operator_helpers import get_pauli_eigenvalues class Observable(QuantumOperator): @@ -33,8 +36,18 @@ def to_ir(self) -> List[Union[str, List[List[List[float]]]]]: representation for the observable""" raise NotImplementedError + @property + def basis_rotation_gates(self) -> Tuple[Gate]: + """Tuple[Gate]: Returns the basis rotation gates for this observable.""" + raise NotImplementedError + + @property + def eigenvalues(self) -> np.ndarray: + """np.ndarray: Returns the eigenvalues of this observable.""" + raise NotImplementedError + @classmethod - def register_observable(cls, observable: Observable): + def register_observable(cls, observable: Observable) -> None: """Register an observable implementation by adding it into the Observable class. Args: @@ -58,3 +71,17 @@ def __eq__(self, other) -> bool: if isinstance(other, Observable): return self.name == other.name return NotImplemented + + +class StandardObservable(Observable): + """ + Class `StandardObservable` to represent a standard quantum observable with + eigenvalues of +/-1, each with a multiplicity of 1. + """ + + def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): + super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + + @property + def eigenvalues(self) -> np.ndarray: + return get_pauli_eigenvalues(self.qubit_count) diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 81c4df09..b344bda6 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -13,17 +13,21 @@ from __future__ import annotations import functools -from typing import List, Tuple +import itertools +import math +from typing import Dict, List, Tuple import numpy as np -from braket.circuits.observable import Observable +from braket.circuits.gate import Gate +from braket.circuits.observable import Observable, StandardObservable from braket.circuits.quantum_operator_helpers import ( + get_pauli_eigenvalues, is_hermitian, verify_quantum_operator_matrix_dimensions, ) -class H(Observable): +class H(StandardObservable): """Hadamard operation as an observable.""" def __init__(self): @@ -39,6 +43,10 @@ def to_ir(self) -> List[str]: def to_matrix(self) -> np.ndarray: return 1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) + @property + def basis_rotation_gates(self) -> Tuple[Gate]: + return tuple([Gate.Ry(-math.pi / 4)]) + Observable.register_observable(H) @@ -59,11 +67,19 @@ def to_ir(self) -> List[str]: def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, 1.0]], dtype=complex) + @property + def basis_rotation_gates(self) -> Tuple[Gate]: + return () + + @property + def eigenvalues(self) -> np.ndarray: + return np.array([1, 1]) + Observable.register_observable(I) -class X(Observable): +class X(StandardObservable): """Pauli-X operation as an observable.""" def __init__(self): @@ -79,11 +95,15 @@ def to_ir(self) -> List[str]: def to_matrix(self) -> np.ndarray: return np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) + @property + def basis_rotation_gates(self) -> Tuple[Gate]: + return tuple([Gate.H()]) + Observable.register_observable(X) -class Y(Observable): +class Y(StandardObservable): """Pauli-Y operation as an observable.""" def __init__(self): @@ -99,11 +119,15 @@ def to_ir(self) -> List[str]: def to_matrix(self) -> np.ndarray: return np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) + @property + def basis_rotation_gates(self) -> Tuple[Gate]: + return tuple([Gate.Z(), Gate.S(), Gate.H()]) + Observable.register_observable(Y) -class Z(Observable): +class Z(StandardObservable): """Pauli-Z operation as an observable.""" def __init__(self): @@ -119,6 +143,10 @@ def to_ir(self) -> List[str]: def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) + @property + def basis_rotation_gates(self) -> Tuple[Gate]: + return () + Observable.register_observable(Z) @@ -151,6 +179,7 @@ def __init__(self, observables: List[Observable]): qubit_count = sum([obs.qubit_count for obs in observables]) display_name = "@".join([obs.ascii_symbols[0] for obs in observables]) super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) + self._eigenvalues = TensorProduct._compute_eigenvalues(self._observables, qubit_count) def to_ir(self) -> List[str]: ir = [] @@ -166,6 +195,17 @@ def observables(self) -> Tuple[Observable]: def to_matrix(self) -> np.ndarray: return functools.reduce(np.kron, [obs.to_matrix() for obs in self.observables]) + @property + def basis_rotation_gates(self) -> Tuple[Gate]: + gates = [] + for obs in self.observables: + gates.extend(obs.basis_rotation_gates) + return tuple(gates) + + @property + def eigenvalues(self): + return self._eigenvalues + def __matmul__(self, other): if isinstance(other, TensorProduct): return TensorProduct(list(self.observables) + list(other.observables)) @@ -187,6 +227,27 @@ def __repr__(self): def __eq__(self, other): return self.matrix_equivalence(other) + @staticmethod + def _compute_eigenvalues(observables: Tuple[Observable], num_qubits: int) -> np.ndarray: + if False in [isinstance(observable, StandardObservable) for observable in observables]: + # Tensor product of observables contains a mixture + # of standard and non-standard observables + eigenvalues = np.array([1]) + for k, g in itertools.groupby(observables, lambda x: isinstance(x, StandardObservable)): + if k: + # Subgroup g contains only standard observables. + eigenvalues = np.kron(eigenvalues, get_pauli_eigenvalues(len(list(g)))) + else: + # Subgroup g contains only non-standard observables. + for nonstandard in g: + # loop through all non-standard observables + eigenvalues = np.kron(eigenvalues, nonstandard.eigenvalues) + else: + eigenvalues = get_pauli_eigenvalues(num_qubits=num_qubits) + + eigenvalues.setflags(write=False) + return eigenvalues + Observable.register_observable(TensorProduct) @@ -194,6 +255,9 @@ def __eq__(self, other): class Hermitian(Observable): """Hermitian matrix as an observable.""" + # Cache of eigenpairs + _eigenpairs = {} + def __init__(self, matrix: np.ndarray, display_name: str = "Hermitian"): """ Args: @@ -229,5 +293,39 @@ def to_matrix(self) -> np.ndarray: def __eq__(self, other) -> bool: return self.matrix_equivalence(other) + @property + def basis_rotation_gates(self) -> Tuple[Gate]: + return tuple([Gate.Unitary(matrix=self._get_eigendecomposition()["eigenvectors_conj_t"])]) + + @property + def eigenvalues(self): + return self._get_eigendecomposition()["eigenvalues"] + + def _get_eigendecomposition(self) -> Dict[str, np.ndarray]: + """ + 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. + + Returns: + Dict[str, np.ndarray]: The keys are "eigenvectors_conj_t", mapping to the + conjugate transpose of a matrix whose columns are the eigenvectors of the matrix, + and "eigenvalues", a list of associated eigenvalues in the order of their + corresponding eigenvectors in the "eigenvectors" matrix. These cached values + are immutable. + """ + mat_key = tuple(self._matrix.flatten().tolist()) + if mat_key not in Hermitian._eigenpairs: + eigenvalues, eigenvectors = np.linalg.eigh(self._matrix) + eigenvalues.setflags(write=False) + eigenvectors_conj_t = eigenvectors.conj().T + eigenvectors_conj_t.setflags(write=False) + Hermitian._eigenpairs[mat_key] = { + "eigenvectors_conj_t": eigenvectors_conj_t, + "eigenvalues": eigenvalues, + } + return Hermitian._eigenpairs[mat_key] + Observable.register_observable(Hermitian) diff --git a/src/braket/circuits/quantum_operator_helpers.py b/src/braket/circuits/quantum_operator_helpers.py index 4d4b64ab..3c50ade7 100644 --- a/src/braket/circuits/quantum_operator_helpers.py +++ b/src/braket/circuits/quantum_operator_helpers.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 functools import lru_cache + import numpy as np @@ -72,3 +74,26 @@ def is_unitary(matrix: np.array) -> bool: bool: If matrix is unitary """ return np.allclose(np.eye(len(matrix)), matrix.dot(matrix.T.conj())) + + +@lru_cache() +def get_pauli_eigenvalues(num_qubits: int) -> np.ndarray: + """ + Get the eigenvalues of Pauli operators and their tensor products as + an immutable Numpy array. + + Args: + num_qubits (int): the number of qubits the operator acts on + + Returns: + np.ndarray: the eigenvalues of a Pauli product operator of the given size + """ + if num_qubits == 1: + 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.setflags(write=False) + return eigs diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index c92b29b7..3d80f751 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -14,8 +14,9 @@ from typing import Any, Dict +from braket.circuits.observable import Observable from braket.circuits.qubit import QubitInput -from braket.circuits.qubit_set import QubitSetInput +from braket.circuits.qubit_set import QubitSet, QubitSetInput class ResultType: @@ -122,3 +123,66 @@ def register_result_type(cls, result_type: "ResultType"): def __repr__(self) -> str: return f"{self.name}()" + + +class ObservableResultType(ResultType): + """ + 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. + + See :mod:`braket.circuits.observables` module for all of the supported observables. + """ + + def __init__(self, ascii_symbol: str, observable: Observable, target: QubitSetInput = None): + """ + Args: + observable (Observable): the observable for the result type + target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + result type is requested for. Default is None, which means the observable must + only operate on 1 qubit and it will be applied to all qubits in parallel + + Raises: + ValueError: If the observable's qubit count and the number of target qubits + are not equal. Or, if target=None and the observable's qubit count is not 1. + """ + super().__init__(ascii_symbol) + 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 self._observable.qubit_count != len(self._target): + raise ValueError( + f"Observable's qubit count and the number of target qubits must be equal" + ) + + @property + def observable(self) -> Observable: + return self._observable + + @property + def target(self) -> QubitSet: + return self._target + + @target.setter + def target(self, target: QubitSetInput) -> None: + self._target = QubitSet(target) + + def __eq__(self, other) -> bool: + if isinstance(other, ObservableResultType): + return ( + self.name == other.name + and self.target == other.target + and self.observable == other.observable + ) + return NotImplemented + + def __repr__(self) -> str: + return f"{self.name}(observable={self.observable}, target={self.target})" + + def __copy__(self) -> ObservableResultType: + return type(self)(observable=self.observable, target=self.target) diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 457601c3..9b6448bb 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -20,7 +20,7 @@ from braket.circuits import circuit from braket.circuits.observable import Observable from braket.circuits.qubit_set import QubitSet, QubitSetInput -from braket.circuits.result_type import ResultType +from braket.circuits.result_type import ObservableResultType, ResultType """ @@ -204,65 +204,6 @@ def __copy__(self) -> Probability: ResultType.register_result_type(Probability) -class ObservableResultType(ResultType): - """ - 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. - - See :mod:`braket.circuits.observables` module for all of the supported observables. - """ - - def __init__(self, ascii_symbol: str, observable: Observable, target: QubitSetInput = None): - """ - Args: - observable (Observable): the observable for the result type - target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the - result type is requested for. Default is None, which means the observable must - only operate on 1 qubit and it will be applied to all qubits in parallel - - Raises: - ValueError: If the observable's qubit count and the number of target qubits - are not equal. Or, if target=None and the observable's qubit count is not 1. - """ - super().__init__(ascii_symbol) - 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 self._observable.qubit_count != len(self._target): - raise ValueError( - f"Observable's qubit count and the number of target qubits must be equal" - ) - - @property - def observable(self) -> Observable: - return self._observable - - @property - def target(self) -> QubitSet: - return self._target - - @target.setter - def target(self, target: QubitSetInput) -> None: - self._target = QubitSet(target) - - def __eq__(self, other) -> bool: - if isinstance(other, ObservableResultType): - return self.target == other.target and self.observable == other.observable - return False - - def __repr__(self) -> str: - return f"{self.name}(observable={self.observable}, target={self.target})" - - def __copy__(self) -> Expectation: - return type(self)(observable=self.observable, target=self.target) - - class Expectation(ObservableResultType): """Expectation of specified target qubit set and observable as the requested result type. diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index b014a309..4dfd7f28 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -14,6 +14,7 @@ from unittest.mock import Mock import braket.ir.jaqcd as jaqcd +import numpy as np import pytest from braket.circuits import ( AsciiCircuitDiagram, @@ -21,6 +22,7 @@ Gate, Instruction, Moments, + Observable, QubitSet, ResultType, circuit, @@ -121,6 +123,51 @@ def test_add_result_type_already_exists(): assert list(circ.result_types) == expected +@pytest.mark.xfail(raises=ValueError) +def test_add_result_type_observable_conflict_target(): + circ = Circuit().add_result_type(ResultType.Probability([0, 1])) + circ.add_result_type(ResultType.Expectation(observable=Observable.Y(), target=0)) + + +@pytest.mark.xfail(raises=ValueError) +def test_add_result_type_observable_conflict_all(): + circ = Circuit().add_result_type(ResultType.Probability()) + circ.add_result_type(ResultType.Expectation(observable=Observable.Y())) + + +@pytest.mark.xfail(raises=ValueError) +def test_add_result_type_observable_conflict_all_target(): + circ = Circuit().add_result_type(ResultType.Probability()) + circ.add_result_type(ResultType.Expectation(observable=Observable.Y(), target=[0, 1])) + + +def test_add_result_type_observable_no_conflict_all_target(): + expected = [ + ResultType.Probability(), + ResultType.Expectation(observable=Observable.Z(), target=[0]), + ] + circ = Circuit(expected) + assert circ.result_types == expected + + +def test_add_result_type_observable_no_conflict_all(): + expected = [ + ResultType.Variance(observable=Observable.Y()), + ResultType.Expectation(observable=Observable.Y()), + ] + circ = Circuit(expected) + assert circ.result_types == expected + + +def test_add_result_type_observable_no_conflict_state_vector_obs_return_value(): + expected = [ + ResultType.StateVector(), + ResultType.Expectation(observable=Observable.Y()), + ] + circ = Circuit(expected) + assert circ.result_types == expected + + @pytest.mark.xfail(raises=TypeError) def test_add_result_type_with_target_and_mapping(prob): Circuit().add_result_type(prob, target=[10], target_mapping={0: 10}) @@ -316,7 +363,9 @@ def h_nested(target): def test_ir_empty_instructions_result_types(): circ = Circuit() - assert circ.to_ir() == jaqcd.Program(instructions=[], results=[]) + assert circ.to_ir() == jaqcd.Program( + instructions=[], results=[], basis_rotation_instructions=[] + ) def test_ir_non_empty_instructions_result_types(): @@ -324,10 +373,48 @@ def test_ir_non_empty_instructions_result_types(): expected = jaqcd.Program( instructions=[jaqcd.H(target=0), jaqcd.CNot(control=0, target=1)], results=[jaqcd.Probability(targets=[0, 1])], + basis_rotation_instructions=[], ) assert circ.to_ir() == expected +def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): + circ = Circuit().h(0).cnot(0, 1).sample(observable=Observable.X(), target=[0]) + expected = jaqcd.Program( + instructions=[jaqcd.H(target=0), jaqcd.CNot(control=0, target=1)], + results=[jaqcd.Sample(observable=["x"], targets=[0])], + basis_rotation_instructions=[jaqcd.H(target=0)], + ) + assert circ.to_ir() == expected + + +def test_basis_rotation_instructions_all(): + circ = Circuit().h(0).cnot(0, 1).sample(observable=Observable.X()) + expected = [ + Instruction(Gate.H(), 0), + Instruction(Gate.H(), 1), + ] + assert circ.basis_rotation_instructions == expected + + +def test_basis_rotation_instructions_target(): + circ = Circuit().h(0).cnot(0, 1).expectation(observable=Observable.X(), target=0) + expected = [Instruction(Gate.H(), 0)] + assert circ.basis_rotation_instructions == expected + + +def test_basis_rotation_instructions_multiple_result_types(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .expectation(observable=Observable.X(), target=0) + .sample(observable=Observable.H(), target=1) + ) + expected = [Instruction(Gate.H(), 0), Instruction(Gate.Ry(-np.pi / 4), 1)] + assert circ.basis_rotation_instructions == expected + + def test_depth_getter(h): assert h.depth is h._moments.depth diff --git a/test/unit_tests/braket/circuits/test_observable.py b/test/unit_tests/braket/circuits/test_observable.py index 46598447..2c3000c5 100644 --- a/test/unit_tests/braket/circuits/test_observable.py +++ b/test/unit_tests/braket/circuits/test_observable.py @@ -11,8 +11,9 @@ # 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 Observable, QuantumOperator +from braket.circuits import Observable, QuantumOperator, StandardObservable @pytest.fixture @@ -20,6 +21,11 @@ def observable(): return Observable(qubit_count=1, ascii_symbols=["foo"]) +@pytest.fixture +def standard_observable(): + return StandardObservable(qubit_count=1, ascii_symbols=["foo"]) + + def test_is_operator(observable): assert isinstance(observable, QuantumOperator) @@ -78,6 +84,16 @@ def test_to_matrix_not_implemented_by_default(observable): observable.to_matrix(None) +@pytest.mark.xfail(raises=NotImplementedError) +def test_basis_rotation_gates_not_implemented_by_default(observable): + observable.basis_rotation_gates + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_eigenvalues_not_implemented_by_default(observable): + observable.eigenvalues + + def test_str(observable): expected = "{}('qubit_count': {})".format(observable.name, observable.qubit_count) assert str(observable) == expected @@ -115,3 +131,11 @@ def test_observable_equality(): assert o1 == o2 assert o1 != o3 assert o1 != o4 + + +def test_standard_observable_subclass_of_observable(standard_observable): + assert isinstance(standard_observable, Observable) + + +def test_standard_observable_eigenvalues(standard_observable): + assert np.allclose(standard_observable.eigenvalues, np.array([1, -1])) diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index b91b2da9..90854ac8 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -11,16 +11,25 @@ # 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 import pytest from braket.circuits import Gate, Observable +from braket.circuits.quantum_operator_helpers import get_pauli_eigenvalues testdata = [ - (Observable.I(), Gate.I(), ["i"]), - (Observable.X(), Gate.X(), ["x"]), - (Observable.Y(), Gate.Y(), ["y"]), - (Observable.Z(), Gate.Z(), ["z"]), - (Observable.H(), Gate.H(), ["h"]), + (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 = [ @@ -34,18 +43,24 @@ ] -@pytest.mark.parametrize("testobject,gateobject,expected_ir", testdata) -def test_to_ir(testobject, gateobject, expected_ir): +@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("testobject,gateobject,expected_ir", testdata) -def test_gate_equality(testobject, gateobject, expected_ir): +@pytest.mark.parametrize( + "testobject,gateobject,expected_ir,basis_rotation_gates,eigenvalues", testdata +) +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) # Hermitian @@ -74,6 +89,51 @@ def test_hermitian_to_ir(): assert obs.to_ir() == [[[[1, 0], [0, 0]], [[0, 0], [1, 0]]]] +@pytest.mark.parametrize( + "matrix,eigenvalues", + [ + (np.array([[1.0, 0.0], [0.0, 1.0]]), np.array([1, 1])), + (np.array([[0, -1j], [1j, 0]]), np.array([-1.0, 1.0])), + (np.array([[1, 1 - 1j], [1 + 1j, -1]]), np.array([-np.sqrt(3), np.sqrt(3)])), + ], +) +def test_hermitian_eigenvalues(matrix, eigenvalues): + assert np.allclose(Observable.Hermitian(matrix=matrix).eigenvalues, eigenvalues) + + +@pytest.mark.parametrize( + "matrix,basis_rotation_matrix", + [ + ( + np.array([[1.0, 0.0], [0.0, 1.0]]), + np.array([[-0.70710678, 0.70710678], [0.70710678, 0.70710678]]).conj().T, + ), + ( + np.array([[0, -1j], [1j, 0]]), + 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], + ] + ), + ), + ], +) +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 expected_unitary.matrix_equivalence(actual_rotation_gates) + + # TensorProduct @@ -121,3 +181,34 @@ def test_tensor_product_rmatmul_observable(): @pytest.mark.xfail(raises=ValueError) def test_tensor_product_rmatmul_value_error(): "a" @ Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) + + +@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])), + ], +) +def test_tensor_product_eigenvalues(observable, eigenvalues): + assert np.allclose(observable.eigenvalues, 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() @ Observable.Z(), + tuple([Gate.H(), Gate.Z(), Gate.S(), Gate.H()]), + ), + ( + Observable.X() @ Observable.Y() @ Observable.I(), + tuple([Gate.H(), Gate.Z(), Gate.S(), Gate.H()]), + ), + (Observable.X() @ Observable.H(), tuple([Gate.H(), Gate.Ry(-np.pi / 4)])), + ], +) +def test_tensor_product_basis_rotation_gates(observable, basis_rotation_gates): + assert observable.basis_rotation_gates == basis_rotation_gates diff --git a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py index 065f01a6..bde19f92 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py @@ -1,6 +1,10 @@ +import functools +import itertools + import numpy as np import pytest from braket.circuits.quantum_operator_helpers import ( + get_pauli_eigenvalues, is_hermitian, is_square_matrix, is_unitary, @@ -24,6 +28,17 @@ invalid_matrix_type_error = np.array([[0, 1], ["a", 0]]) +x_matrix = np.array([[0, 1], [1, 0]]) +y_matrix = np.array([[0, -1j], [1j, 0]]) +z_matrix = np.array([[1, 0], [0, -1]]) +h_matrix = 1 / np.sqrt(2) * np.array([[1, 1], [1, -1]]) + +standard_observables = [x_matrix, y_matrix, z_matrix, h_matrix] + +matrix_pairs = [ + np.kron(x, y) for x, y in list(itertools.product(standard_observables, standard_observables)) +] + def test_verify_quantum_operator_matrix_dimensions(): assert verify_quantum_operator_matrix_dimensions(valid_unitary_hermitian_matrix) is None @@ -65,3 +80,37 @@ def test_is_hermitian_exception(): @pytest.mark.xfail(raises=Exception) def test_is_unitary_exception(): is_unitary(invalid_matrix_type_error) + + +@pytest.mark.parametrize("pauli", standard_observables) +def test_get_pauli_eigenvalues_correct_eigenvalues_one_qubit(pauli): + """Test the get_pauli_eigenvalues function for one qubit""" + assert np.array_equal(get_pauli_eigenvalues(1), np.diag(z_matrix)) + + +@pytest.mark.parametrize("pauli_product", matrix_pairs) +def test_get_pauli_eigenvalues_correct_eigenvalues_two_qubits(pauli_product): + """Test the get_pauli_eigenvalues function for two qubits""" + assert np.array_equal(get_pauli_eigenvalues(2), np.diag(np.kron(z_matrix, z_matrix))) + + +@pytest.mark.parametrize("pauli_product", matrix_pairs) +def test_get_pauli_eigenvalues_correct_eigenvalues_three_qubits(pauli_product): + """Test the get_pauli_eigenvalues function for three qubits""" + assert np.array_equal( + get_pauli_eigenvalues(3), np.diag(np.kron(z_matrix, np.kron(z_matrix, z_matrix))), + ) + + +@pytest.mark.parametrize("depth", list(range(1, 6))) +def test_get_pauli_eigenvalues_cache_usage(depth): + """Test that the right number of cachings have been executed after clearing the cache""" + get_pauli_eigenvalues.cache_clear() + get_pauli_eigenvalues(depth) + assert functools._CacheInfo(depth - 1, depth, 128, depth) == get_pauli_eigenvalues.cache_info() + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("num_qubits", [1, 2]) +def test_get_pauli_eigenvalues_immutable(num_qubits): + get_pauli_eigenvalues(num_qubits)[0] = 100 diff --git a/test/unit_tests/braket/circuits/test_result_type.py b/test/unit_tests/braket/circuits/test_result_type.py index 7d68e58e..e1222b39 100644 --- a/test/unit_tests/braket/circuits/test_result_type.py +++ b/test/unit_tests/braket/circuits/test_result_type.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. import pytest -from braket.circuits import ResultType +from braket.circuits import Observable, ObservableResultType, ResultType @pytest.fixture @@ -113,3 +113,37 @@ def test_copy_with_target(sv): @pytest.mark.xfail(raises=TypeError) def test_copy_with_target_and_mapping(prob): prob.copy(target=[10], target_mapping={0: 10}) + + +# ObservableResultType + + +@pytest.mark.xfail(raises=ValueError) +def test_expectation_init_value_error_target(): + ObservableResultType(ascii_symbol="Obs", observable=Observable.X() @ Observable.Y(), target=[]) + + +@pytest.mark.xfail(raises=ValueError) +def test_obs_rt_init_value_error_qubit_count(): + ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=[0, 1]) + + +def test_obs_rt_equality(): + a1 = ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=0) + a2 = ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=0) + a3 = ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=1) + a4 = "hi" + assert a1 == a2 + assert a1 != a3 + assert a1 != a4 + assert ResultType.Variance(observable=Observable.Y(), target=0) != ResultType.Expectation( + observable=Observable.Y(), target=0 + ) + + +def test_obs_rt_repr(): + a1 = ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=0) + assert ( + str(a1) + == f"ObservableResultType(observable=X('qubit_count': 1), target=QubitSet([Qubit(0)]))" + ) diff --git a/test/unit_tests/braket/circuits/test_result_types.py b/test/unit_tests/braket/circuits/test_result_types.py index 8240d9b8..219783b2 100644 --- a/test/unit_tests/braket/circuits/test_result_types.py +++ b/test/unit_tests/braket/circuits/test_result_types.py @@ -141,37 +141,6 @@ def test_probability_equality(): assert a1 != a4 -# ObservableResultType - - -@pytest.mark.xfail(raises=ValueError) -def test_expectation_init_value_error_target(): - ObservableResultType(ascii_symbol="Obs", observable=Observable.X() @ Observable.Y(), target=[]) - - -@pytest.mark.xfail(raises=ValueError) -def test_obs_rt_init_value_error_qubit_count(): - ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=[0, 1]) - - -def test_obs_rt_equality(): - a1 = ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=0) - a2 = ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=0) - a3 = ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=1) - a4 = "hi" - assert a1 == a2 - assert a1 != a3 - assert a1 != a4 - - -def test_obs_rt_repr(): - a1 = ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=0) - assert ( - str(a1) - == f"ObservableResultType(observable=X('qubit_count': 1), target=QubitSet([Qubit(0)]))" - ) - - # Expectation From 265c412d5c5acc6bf556406021884822bedbc3b7 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Wed, 6 May 2020 12:03:40 -0700 Subject: [PATCH 0093/1165] Remove unused vars in test_quantum_operator_helpers.py (#74) --- .../circuits/test_quantum_operator_helpers.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py index bde19f92..ef22b91d 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py @@ -1,5 +1,4 @@ import functools -import itertools import numpy as np import pytest @@ -28,16 +27,7 @@ invalid_matrix_type_error = np.array([[0, 1], ["a", 0]]) -x_matrix = np.array([[0, 1], [1, 0]]) -y_matrix = np.array([[0, -1j], [1j, 0]]) z_matrix = np.array([[1, 0], [0, -1]]) -h_matrix = 1 / np.sqrt(2) * np.array([[1, 1], [1, -1]]) - -standard_observables = [x_matrix, y_matrix, z_matrix, h_matrix] - -matrix_pairs = [ - np.kron(x, y) for x, y in list(itertools.product(standard_observables, standard_observables)) -] def test_verify_quantum_operator_matrix_dimensions(): @@ -82,20 +72,17 @@ def test_is_unitary_exception(): is_unitary(invalid_matrix_type_error) -@pytest.mark.parametrize("pauli", standard_observables) -def test_get_pauli_eigenvalues_correct_eigenvalues_one_qubit(pauli): +def test_get_pauli_eigenvalues_correct_eigenvalues_one_qubit(): """Test the get_pauli_eigenvalues function for one qubit""" assert np.array_equal(get_pauli_eigenvalues(1), np.diag(z_matrix)) -@pytest.mark.parametrize("pauli_product", matrix_pairs) -def test_get_pauli_eigenvalues_correct_eigenvalues_two_qubits(pauli_product): +def test_get_pauli_eigenvalues_correct_eigenvalues_two_qubits(): """Test the get_pauli_eigenvalues function for two qubits""" assert np.array_equal(get_pauli_eigenvalues(2), np.diag(np.kron(z_matrix, z_matrix))) -@pytest.mark.parametrize("pauli_product", matrix_pairs) -def test_get_pauli_eigenvalues_correct_eigenvalues_three_qubits(pauli_product): +def test_get_pauli_eigenvalues_correct_eigenvalues_three_qubits(): """Test the get_pauli_eigenvalues function for three qubits""" assert np.array_equal( get_pauli_eigenvalues(3), np.diag(np.kron(z_matrix, np.kron(z_matrix, z_matrix))), From 46e571706df9b2870ae036237f2cce6a756c17cb Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Mon, 11 May 2020 12:51:47 -0700 Subject: [PATCH 0094/1165] Calculate result types for shots > 0 (#75) --- src/braket/circuits/observables.py | 41 +++- .../tasks/gate_model_quantum_task_result.py | 175 +++++++++++++++++- .../braket/aws/common_test_utils.py | 5 +- .../braket/circuits/test_observables.py | 47 +++++ .../braket/devices/test_local_simulator.py | 1 + .../test_gate_model_quantum_task_result.py | 149 ++++++++++++++- .../braket/tasks/test_local_quantum_task.py | 1 + 7 files changed, 401 insertions(+), 18 deletions(-) diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index b344bda6..8acba8de 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -15,7 +15,7 @@ import functools import itertools import math -from typing import Dict, List, Tuple +from typing import Dict, List, Tuple, Union import numpy as np from braket.circuits.gate import Gate @@ -329,3 +329,42 @@ def _get_eigendecomposition(self) -> Dict[str, np.ndarray]: Observable.register_observable(Hermitian) + + +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 + observables or a single observable. + + Args: + ir_observable (List[Union[str, List[List[List[float]]]]]): observable as defined in IR + + Return: + Observable: observable object + """ + 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 + + +def _observable_from_ir_list_item(observable: Union[str, List[List[List[float]]]]) -> Observable: + if observable == "i": + return I() + elif observable == "h": + return H() + elif observable == "x": + return X() + elif observable == "y": + return Y() + elif 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}") diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index e548f1b7..598370f3 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -15,10 +15,13 @@ import json from dataclasses import dataclass -from typing import Any, Counter, Dict, List +from typing import Any, Callable, Counter, Dict, List, Optional, TypeVar, Union import numpy as np -from braket.circuits import ResultType +from braket.circuits import Observable, ResultType, StandardObservable +from braket.circuits.observables import observable_from_ir + +T = TypeVar("T") @dataclass @@ -30,12 +33,16 @@ class GateModelQuantumTaskResult: Args: task_metadata (Dict[str, Any]): Dictionary of task metadata. task_metadata must have keys 'Id', 'Shots', 'Ir', and 'IrType'. - result_types (List[Dict[str, Any]], optional): List of dictionaries where each dictionary + result_types (List[Dict[str, Any]]): List of dictionaries where each dictionary has two keys: 'Type' (the result type in IR JSON form) and 'Value' (the result value for this result type). - Default is None. Currently only available when shots = 0. TODO: update - values (List[Any], optional): The values for result types requested in the circuit. - Default is None. Currently only available when shots = 0. TODO: update + This can be an empty list if no result types are specified in the IR. + This is calculated from `measurements` and + the IR of the circuit program when `shots>0`. + values (List[Any]): The values for result types requested in the circuit. + This can be an empty list if no result types are specified in the IR. + This is calculated from `measurements` and + the IR of the circuit program when `shots>0`. measurements (numpy.ndarray, optional): 2d array - row is shot, column is qubit. Default is None. Only available when shots > 0. The qubits in `measurements` are the ones in `GateModelQuantumTaskResult.measured_qubits`. @@ -63,8 +70,8 @@ class GateModelQuantumTaskResult: """ task_metadata: Dict[str, Any] - result_types: List[Dict[str, str]] = None - values: List[Any] = None + result_types: List[Dict[str, str]] + values: List[Any] measurements: np.ndarray = None measured_qubits: List[int] = None measurement_counts: Counter = None @@ -225,7 +232,6 @@ def _from_dict_internal(cls, result: Dict[str, Any]): @classmethod def _from_dict_internal_computational_basis_sampling(cls, result: Dict[str, Any]): task_metadata = result["TaskMetadata"] - measured_qubits = result.get("MeasuredQubits") if "Measurements" in result: measurements = np.asarray(result["Measurements"], dtype=int) m_counts = GateModelQuantumTaskResult.measurement_counts_from_measurements(measurements) @@ -249,9 +255,20 @@ def _from_dict_internal_computational_basis_sampling(cls, result: Dict[str, Any] raise ValueError( 'One of "Measurements" or "MeasurementProbabilities" must be in the result dict' ) - # TODO: add result types for shots > 0 after basis rotation instructions are added to IR + 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" + ) + result_types = GateModelQuantumTaskResult._calculate_result_types( + result["TaskMetadata"]["Ir"], measurements, measured_qubits + ) + values = [rt["Value"] for rt in result_types] return cls( task_metadata=task_metadata, + result_types=result_types, + values=values, measurements=measurements, measured_qubits=measured_qubits, measurement_counts=m_counts, @@ -267,3 +284,141 @@ def _from_dict_internal_simulator_only(cls, result: Dict[str, Any]): result_types = result["ResultTypes"] values = [rt["Value"] for rt in result_types] return cls(task_metadata=task_metadata, result_types=result_types, values=values) + + @staticmethod + def _calculate_result_types( + ir_string: str, measurements: np.ndarray, measured_qubits: List[int] + ) -> List[Dict[str, Any]]: + ir = json.loads(ir_string) + result_types = [] + if not ir.get("results"): + return result_types + for result_type in ir["results"]: + 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 + ) + elif rt_type == "sample": + value = GateModelQuantumTaskResult._calculate_for_targets( + GateModelQuantumTaskResult._samples_from_measurements, + measurements, + measured_qubits, + observable, + targets, + ) + elif rt_type == "variance": + value = GateModelQuantumTaskResult._calculate_for_targets( + GateModelQuantumTaskResult._variance_from_measurements, + measurements, + measured_qubits, + observable, + targets, + ) + elif rt_type == "expectation": + value = GateModelQuantumTaskResult._calculate_for_targets( + GateModelQuantumTaskResult._expectation_from_measurements, + measurements, + measured_qubits, + observable, + targets, + ) + else: + raise ValueError(f"Unknown result type {rt_type}") + result_types.append({"Type": result_type, "Value": value}) + return result_types + + @staticmethod + def _selected_measurements( + measurements: np.ndarray, measured_qubits: List[int], targets: Optional[List[int]] + ) -> 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], + measurements: np.ndarray, + measured_qubits: List[int], + observable: Observable, + targets: List[int], + ) -> Union[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 + ] + + @staticmethod + def _measurements_base_10(measurements: np.ndarray) -> np.ndarray: + # convert samples from a list of 0, 1 integers, to base 10 representation + shots, num_measured_qubits = measurements.shape + unraveled_indices = [2] * num_measured_qubits + return np.ravel_multi_index(measurements.T, unraveled_indices) + + @staticmethod + def _probability_from_measurements( + measurements: np.ndarray, measured_qubits: List[int], targets: Optional[List[int]] + ) -> np.ndarray: + measurements = GateModelQuantumTaskResult._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) + + # count the basis state occurrences, and construct the probability vector + basis_states, counts = np.unique(indices, return_counts=True) + probabilities = np.zeros([2 ** num_measured_qubits], dtype=np.float64) + probabilities[basis_states] = counts / shots + return probabilities + + @staticmethod + def _variance_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.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) + return observable.eigenvalues[indices].real diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index f3a197d2..095c32d9 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -175,8 +175,8 @@ class MockS3: MOCK_S3_RESULT_1 = json.dumps( { - "StateVector": {"00": [0.2, 0.2], "01": [0.3, 0.1], "10": [0.1, 0.3], "11": [0.2, 0.2]}, "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], + "MeasuredQubits": [0, 1], "TaskMetadata": { "Id": "UUID_blah_1", "Status": "COMPLETED", @@ -189,8 +189,8 @@ class MockS3: MOCK_S3_RESULT_2 = json.dumps( { - "StateVector": {"00": [0.2, 0.2], "01": [0.3, 0.1], "10": [0.1, 0.3], "11": [0.2, 0.2]}, "Measurements": [[0, 0], [0, 0], [0, 0], [1, 1]], + "MeasuredQubits": [0, 1], "TaskMetadata": { "Id": "UUID_blah_2", "Status": "COMPLETED", @@ -215,6 +215,7 @@ class MockS3: "Ir": "{}", }, "MeasurementProbabilities": {"011000": 0.9999999999999982}, + "MeasuredQubits": [0, 1], } ) diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index 90854ac8..d5a65c21 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -16,6 +16,7 @@ import numpy as np import pytest from braket.circuits import Gate, Observable +from braket.circuits.observables import observable_from_ir from braket.circuits.quantum_operator_helpers import get_pauli_eigenvalues testdata = [ @@ -63,6 +64,29 @@ def test_gate_equality(testobject, gateobject, expected_ir, basis_rotation_gates assert np.allclose(testobject.eigenvalues, 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( + "testobject,gateobject,expected_ir,basis_rotation_gates,eigenvalues", testdata +) +def test_eigenvalues(testobject, gateobject, expected_ir, basis_rotation_gates, eigenvalues): + assert np.allclose(testobject.eigenvalues, eigenvalues) + + +@pytest.mark.parametrize( + "testobject,gateobject,expected_ir,basis_rotation_gates,eigenvalues", testdata +) +def test_observable_from_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenvalues): + assert testobject == observable_from_ir(expected_ir) + + # Hermitian @@ -134,6 +158,18 @@ def test_hermitian_basis_rotation_gates(matrix, basis_rotation_matrix): assert expected_unitary.matrix_equivalence(actual_rotation_gates) +@pytest.mark.xfail(raises=ValueError) +def test_observable_from_ir_hermitian_value_error(): + ir_observable = [[[[1.0, 0], [0, 1]], [[0.0, 1], [1, 0]]]] + observable_from_ir(ir_observable) + + +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]])) + + # TensorProduct @@ -212,3 +248,14 @@ def test_tensor_product_eigenvalues(observable, eigenvalues): ) def test_tensor_product_basis_rotation_gates(observable, basis_rotation_gates): assert observable.basis_rotation_gates == basis_rotation_gates + + +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"]) + assert expected_observable == actual_observable + + +@pytest.mark.xfail(raises=ValueError) +def test_observable_from_ir_tensor_product_value_error(): + observable_from_ir(["z", "i", "foo"]) diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index adb6113a..b5bb8a9e 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -25,6 +25,7 @@ GATE_MODEL_RESULT = { "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], + "MeasuredQubits": [0, 1], "TaskMetadata": { "Id": "UUID_blah_1", "Status": "COMPLETED", 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 0651696c..df8831be 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 @@ -19,6 +19,7 @@ import pytest from braket.aws.aws_qpu_arns import AwsQpuArns from braket.circuits import Observable, ResultType +from braket.ir import jaqcd from braket.tasks import GateModelQuantumTaskResult @@ -122,7 +123,52 @@ def result_str_4(result_dict_4): @pytest.fixture -def malformatted_results(): +def result_dict_5(): + return { + "TaskMetadata": { + "Id": "1231231", + "Shots": 100, + "GateModelConfig": {"QubitCount": 2}, + "Ir": json.dumps( + { + "results": [ + jaqcd.Probability(targets=[1]).dict(), + jaqcd.Expectation(targets=None, observable=["z"]).dict(), + ] + } + ), + }, + "Measurements": [ + [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], + ], + "MeasuredQubits": [0, 1, 2, 3], + } + + +@pytest.fixture +def malformatted_results_1(): + return { + "TaskMetadata": { + "Id": "UUID_blah_1", + "Status": "COMPLETED", + "BackendArn": AwsQpuArns.RIGETTI, + "Shots": 1000, + "Ir": json.dumps({"results": []}), + } + } + + +@pytest.fixture +def malformatted_results_2(): return { "TaskMetadata": { "Id": "UUID_blah_1", @@ -131,9 +177,41 @@ def malformatted_results(): "Shots": 1000, "Ir": json.dumps({"results": []}), }, + "MeasurementProbabilities": {"011000": 0.9999999999999982}, + "MeasuredQubits": [0], } +test_ir_results = [ + (jaqcd.Probability(targets=[1]), np.array([0.6, 0.4])), + (jaqcd.Probability(targets=[1, 2]), np.array([0.4, 0.2, 0.0, 0.4])), + ( + jaqcd.Probability(targets=None), + np.array([0.1, 0.2, 0.2, 0.0, 0.0, 0.0, 0.0, 0.2, 0.0, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2]), + ), + (jaqcd.Sample(targets=[1], observable=["z"]), np.array([1, -1, 1, 1, -1, -1, 1, -1, 1, 1])), + ( + jaqcd.Sample(targets=[1, 2], observable=["x", "y"]), + np.array([-1, 1, 1, -1, 1, 1, 1, 1, 1, 1]), + ), + ( + jaqcd.Sample(targets=None, observable=["z"]), + [ + np.array([1, -1, -1, 1, -1, 1, 1, 1, 1, 1]), + np.array([1, -1, 1, 1, -1, -1, 1, -1, 1, 1]), + np.array([-1, -1, 1, -1, -1, -1, 1, -1, 1, 1]), + np.array([1, -1, -1, 1, -1, -1, -1, -1, 1, -1]), + ], + ), + (jaqcd.Expectation(targets=[1], observable=["z"]), 0.2), + (jaqcd.Expectation(targets=[1, 2], observable=["z", "y"]), 0.6), + (jaqcd.Expectation(targets=None, observable=["z"]), [0.4, 0.2, -0.2, -0.4]), + (jaqcd.Variance(targets=[1], observable=["z"]), 0.96), + (jaqcd.Variance(targets=[1, 2], observable=["z", "y"]), 0.64), + (jaqcd.Variance(targets=None, observable=["z"]), [0.84, 0.96, 0.96, 0.84]), +] + + 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]] @@ -181,6 +259,8 @@ def test_task_metadata(): } result: GateModelQuantumTaskResult = GateModelQuantumTaskResult( measurements=None, + result_types=[], + values=[], task_metadata=task_metadata, measurement_counts=None, measurement_probabilities=None, @@ -200,6 +280,23 @@ def test_from_dict_measurements(result_dict_1): assert not task_result.measurement_probabilities_copied_from_device assert task_result.measurements_copied_from_device assert task_result.measured_qubits == result_dict_1["MeasuredQubits"] + assert task_result.values == [] + assert task_result.result_types == [] + + +def test_from_dict_result_types(result_dict_5): + task_result = GateModelQuantumTaskResult.from_dict(result_dict_5) + expected_measurements = np.asarray(result_dict_5["Measurements"], dtype=int) + assert task_result.task_metadata == result_dict_5["TaskMetadata"] + assert np.array2string(task_result.measurements) == np.array2string(expected_measurements) + assert task_result.measured_qubits == result_dict_5["MeasuredQubits"] + assert np.allclose(task_result.values[0], np.array([0.6, 0.4])) + assert task_result.values[1] == [0.4, 0.2, -0.2, -0.4] + assert task_result.result_types[0]["Type"] == jaqcd.Probability(targets=[1]).dict() + assert ( + task_result.result_types[1]["Type"] + == jaqcd.Expectation(targets=None, observable=["z"]).dict() + ) def test_from_dict_measurement_probabilities(result_str_3): @@ -304,11 +401,53 @@ def test_get_value_by_result_type_value_error(result_dict_4): @pytest.mark.xfail(raises=ValueError) -def test_bad_dict(malformatted_results): - GateModelQuantumTaskResult.from_dict(malformatted_results) +def test_bad_dict(malformatted_results_1): + GateModelQuantumTaskResult.from_dict(malformatted_results_1) + + +@pytest.mark.xfail(raises=ValueError) +def test_bad_string(malformatted_results_1): + results_string = json.dumps(malformatted_results_1) + GateModelQuantumTaskResult.from_string(results_string) @pytest.mark.xfail(raises=ValueError) -def test_bad_string(malformatted_results): - results_string = json.dumps(malformatted_results) +def test_measurements_measured_qubits_mismatch(malformatted_results_2): + results_string = json.dumps(malformatted_results_2) GateModelQuantumTaskResult.from_string(results_string) + + +@pytest.mark.parametrize("ir_result,expected_result", test_ir_results) +def test_calculate_ir_results(ir_result, expected_result): + ir_string = jaqcd.Program( + 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], + ] + ) + result_types = GateModelQuantumTaskResult._calculate_result_types( + ir_string, measurements, measured_qubits + ) + assert len(result_types) == 1 + assert result_types[0]["Type"] == ir_result.dict() + assert np.allclose(result_types[0]["Value"], expected_result) + + +@pytest.mark.xfail(raises=ValueError) +def test_calculate_ir_results_value_error(): + ir_string = json.dumps({"results": [{"type": "foo"}]}) + measured_qubits = [0] + measurements = np.array([[0]]) + GateModelQuantumTaskResult._calculate_result_types(ir_string, measurements, measured_qubits) 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 6af0b4c8..26765627 100644 --- a/test/unit_tests/braket/tasks/test_local_quantum_task.py +++ b/test/unit_tests/braket/tasks/test_local_quantum_task.py @@ -21,6 +21,7 @@ RESULT = GateModelQuantumTaskResult.from_dict( { "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], + "MeasuredQubits": [0, 1], "TaskMetadata": { "Id": str(uuid.uuid4()), "Status": "COMPLETED", From 2c77bfd904ea3d5e12ce35c6a8f4d092d91be874 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Thu, 14 May 2020 10:17:19 -0700 Subject: [PATCH 0095/1165] Add properties to LocalSimulator (#76) --- src/braket/devices/braket_simulator.py | 6 +++++- src/braket/devices/local_simulator.py | 7 ++++++- .../braket/devices/test_local_simulator.py | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/braket/devices/braket_simulator.py b/src/braket/devices/braket_simulator.py index 434ee01b..74c088ec 100644 --- a/src/braket/devices/braket_simulator.py +++ b/src/braket/devices/braket_simulator.py @@ -57,4 +57,8 @@ def run(self, ir: Union[Program, Problem], *args, **kwargs) -> Dict[str, Any]: match that needed by GateModelQuantumTaskResult or AnnealingQuantumTaskResult from the SDK, depending on the type of task. """ - raise NotImplementedError() + + @property + @abstractmethod + def properties(self) -> Dict[str, Any]: + """ Dict[str, Any]: Properties of the BraketSimulator device. """ diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index b380800a..290655bd 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. from functools import singledispatch -from typing import Optional, Set, Union +from typing import Any, Dict, Optional, Set, Union import pkg_resources @@ -81,6 +81,11 @@ def run( result = _run_internal(task_specification, self._delegate, shots, *args, **kwargs) return LocalQuantumTask(result) + @property + def properties(self) -> Dict[str, Any]: + """ Dict[str, Any]: Properties of the LocalSimulator device """ + return self._delegate.properties + @staticmethod def registered_backends() -> Set[str]: """ Gets the backends that have been registered as entry points diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index b5bb8a9e..0ffe7894 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -53,6 +53,10 @@ def run( self._qubits = qubits return GATE_MODEL_RESULT + @property + def properties(self) -> Dict[str, Any]: + return {"supportedQuantumOperations": ["I", "X"]} + def assert_shots(self, shots): assert self._shots == shots @@ -64,6 +68,10 @@ class DummyAnnealingSimulator(BraketSimulator): def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> Dict[str, Any]: return ANNEALING_RESULT + @property + def properties(self) -> Dict[str, Any]: + return {} + mock_entry = Mock() mock_entry.load.return_value = DummyCircuitSimulator @@ -116,3 +124,9 @@ def test_init_unregistered_backend(): def test_run_unsupported_type(): sim = LocalSimulator(DummyCircuitSimulator()) sim.run("I'm unsupported") + + +def test_properties(): + sim = LocalSimulator(DummyCircuitSimulator()) + expected_properties = {"supportedQuantumOperations": ["I", "X"]} + assert sim.properties == expected_properties From 1921ec167499f744070b7952a70a9d4843be0887 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Fri, 15 May 2020 16:10:11 -0700 Subject: [PATCH 0096/1165] Add result types to ASCII diagram (#77) --- src/braket/circuits/angled_gate.py | 2 +- src/braket/circuits/ascii_circuit_diagram.py | 147 +++++++++++------ src/braket/circuits/circuit.py | 5 +- src/braket/circuits/quantum_operator.py | 2 +- src/braket/circuits/result_type.py | 45 +++--- src/braket/circuits/result_types.py | 25 ++- .../circuits/test_ascii_circuit_diagram.py | 149 +++++++++++++++++- .../braket/circuits/test_circuit.py | 10 +- .../braket/circuits/test_result_type.py | 37 +++-- 9 files changed, 332 insertions(+), 90 deletions(-) diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index 0ab7657f..d14ed519 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -39,7 +39,7 @@ def __init__(self, angle: float, qubit_count: int, ascii_symbols: Sequence[str]) """ super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) if angle is None: - raise ValueError(f"angle must not be None") + raise ValueError("angle must not be None") self._angle = angle @property diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index b8e008e5..462291dd 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -11,12 +11,13 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import List, Tuple +from typing import List, Tuple, Union from braket.circuits.circuit_diagram import CircuitDiagram from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction from braket.circuits.qubit_set import QubitSet +from braket.circuits.result_type import ResultType class AsciiCircuitDiagram(CircuitDiagram): @@ -48,46 +49,66 @@ def build_diagram(circuit) -> str: y_axis_str += "q{0:{width}} : -\n".format(str(int(qubit)), width=y_axis_width) time_slices = circuit.moments.time_slices() + column_strs = [] # Moment columns - moments_strs = [] for time, instructions in time_slices.items(): - moment_str = AsciiCircuitDiagram._ascii_diagram_moment( - time, circuit_qubits, instructions + moment_str = AsciiCircuitDiagram._ascii_diagram_column_set( + str(time), circuit_qubits, instructions + ) + 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 + ) ) - moments_strs.append(moment_str) # Unite strings lines = y_axis_str.split("\n") - for moment_str in moments_strs: - for i, moment_line in enumerate(moment_str.split("\n")): - lines[i] += moment_line + 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]) + # Additional result types line on bottom + if additional_result_types: + lines.append(f"\nAdditional result types: {', '.join(additional_result_types)}") + return "\n".join(lines) @staticmethod - def _ascii_moment_group_instructions( - instructions: List[Instruction], + 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: - instructions (List[Instruction]): list of instructions + circuit_qubits (QubitSet): set of qubits in circuit + items (List[Union[Instruction, ResultType]]): list of instructions or result types Returns: - List[(QubitSet, List[Instruction])]: list of grouped instructions + List[(QubitSet, List[Union[Instruction, ResultType]])]: list of grouped instructions + or result types """ groupings = [] - for instr in instructions: - # Can only print Gate operators at the moment - if not isinstance(instr.operator, Gate): + for item in items: + # Can only print Gate operators for instructions at the moment + if isinstance(item, Instruction) and not isinstance(item.operator, Gate): continue - qubit_range = QubitSet(range(min(instr.target), max(instr.target) + 1)) + if isinstance(item, ResultType) and not item.target: + qubit_range = circuit_qubits + else: + qubit_range = QubitSet(range(min(item.target), max(item.target) + 1)) found_grouping = False for group in groupings: @@ -95,37 +116,58 @@ def _ascii_moment_group_instructions( instr_group = group[1] # Take into account overlapping multi-qubit gates if not qubits_added.intersection(set(qubit_range)): - instr_group.append(instr) + instr_group.append(item) qubits_added.update(qubit_range) found_grouping = True break if not found_grouping: - groupings.append((qubit_range, [instr])) + groupings.append((qubit_range, [item])) return groupings @staticmethod - def _ascii_diagram_moment( - time: int, circuit_qubits: QubitSet, instructions: List[Instruction] + def _categorize_result_types(result_types: List[ResultType]) -> Tuple[List[ResultType]]: + """ + Categorize result types into result types with target and those without. + + Args: + result_types (List[ResultType]): list of result types + + Returns: + Tuple: 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]] ) -> str: """ - Return an ASCII string diagram of the circuit at a particular moment in time. + Return a set of columns in the ASCII string diagram of the circuit for a list of items. Args: - time (int): time of moment + col_title (str): title of column set circuit_qubits (QubitSet): qubits in circuit - instructions (List[Instruction]): list of instructions + items (List[Union[Instruction, ResultType]]): list of instructions or result types Returns: - str: An ASCII string diagram for the specified moment in time. + str: An ASCII string diagram for the column set. """ - # Group instructions to separate out overlapping multi-qubit gates - groupings = AsciiCircuitDiagram._ascii_moment_group_instructions(instructions) + # Group items to separate out overlapping multi-qubit items + groupings = AsciiCircuitDiagram._ascii_group_items(circuit_qubits, items) column_strs = [ - AsciiCircuitDiagram._ascii_diagram_moment_column(circuit_qubits, grouping[1]) + AsciiCircuitDiagram._ascii_diagram_column(circuit_qubits, grouping[1]) for grouping in groupings ] @@ -135,31 +177,31 @@ def _ascii_diagram_moment( for i, moment_line in enumerate(column_str.split("\n")): lines[i] += moment_line - # Adjust for time width - time_width = len(str(time)) + # Adjust for column title width + col_title_width = len(col_title) symbols_width = len(lines[0]) - 1 - if symbols_width < time_width: - diff = time_width - symbols_width + 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(str(time), width=len(lines[0]) - 1) + first_line = "{:^{width}}|\n".format(col_title, width=len(lines[0]) - 1) return first_line + "\n".join(lines) @staticmethod - def _ascii_diagram_moment_column( - circuit_qubits: QubitSet, instructions: List[Instruction] + def _ascii_diagram_column( + circuit_qubits: QubitSet, items: List[Union[Instruction, ResultType]] ) -> str: """ - Return an ASCII string diagram of the circuit at a particular moment in time for a column. + Return a column in the ASCII string diagram of the circuit for a given list of items. Args: circuit_qubits (QubitSet): qubits in circuit - instructions (List[Instruction]): list of instructions + items (List[Union[Instruction, ResultType]]): list of instructions or result types Returns: str: An ASCII string diagram for the specified moment in time for a column. @@ -167,24 +209,35 @@ def _ascii_diagram_moment_column( symbols = {qubit: "-" for qubit in circuit_qubits} margins = {qubit: " " for qubit in circuit_qubits} - for instr in instructions: + for item in items: + if isinstance(item, ResultType) and not item.target: + target_qubits = circuit_qubits + qubits = circuit_qubits + ascii_symbols = [item.ascii_symbols[0]] * len(circuit_qubits) + else: + target_qubits = item.target + qubits = circuit_qubits.intersection( + set(range(min(item.target), max(item.target) + 1)) + ) + ascii_symbols = ( + item.operator.ascii_symbols + if isinstance(item, Instruction) + else item.ascii_symbols + ) - qubits = circuit_qubits.intersection( - set(range(min(instr.target), max(instr.target) + 1)) - ) for qubit in qubits: - # Determine if the qubit is part of the instruction or in the middle of a - # multi qubit gate. - if qubit in instr.target: - instr_qubit_index = [ - index for index, q in enumerate(instr.target) if q == qubit + # 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] - symbols[qubit] = instr.operator.ascii_symbols[instr_qubit_index] + symbols[qubit] = ascii_symbols[item_qubit_index] else: symbols[qubit] = "|" # Set the margin to be a connector if not on the first qubit - if qubit != min(instr.target): + if qubit != min(target_qubits): margins[qubit] = "|" symbols_width = max([len(symbol) for symbol in symbols.values()]) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index ed1f4663..aa502547 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -47,6 +47,7 @@ class Circuit: """ _ALL_QUBITS = "ALL" # Flag to indicate all qubits in _qubit_observable_mapping + _EXISTING_QUBITS = "EXISTING" # Flag to indicate existing qubits in _qubit_observable_mapping @classmethod def register_subroutine(cls, func: SubroutineCallable) -> None: @@ -245,8 +246,9 @@ def _add_to_qubit_observable_mapping(self, result_type: ResultType) -> None: observable = result_type.observable else: return - targets = result_type.target if result_type.target else [Circuit._ALL_QUBITS] + targets = result_type.target if result_type.target else Circuit._EXISTING_QUBITS all_qubits_observable = self._qubit_observable_mapping.get(Circuit._ALL_QUBITS) + for target in targets: current_observable = all_qubits_observable or self._qubit_observable_mapping.get(target) if current_observable and current_observable != observable: @@ -255,6 +257,7 @@ def _add_to_qubit_observable_mapping(self, result_type: ResultType) -> None: + f"conflicts with observable {observable} for new result type" ) self._qubit_observable_mapping[target] = observable + self._qubit_observable_mapping[Circuit._EXISTING_QUBITS] = observable def add_instruction( self, diff --git a/src/braket/circuits/quantum_operator.py b/src/braket/circuits/quantum_operator.py index 6cbba841..6bdefb94 100644 --- a/src/braket/circuits/quantum_operator.py +++ b/src/braket/circuits/quantum_operator.py @@ -41,7 +41,7 @@ def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): self._qubit_count = qubit_count if ascii_symbols is None: - raise ValueError(f"ascii_symbols must not be None") + raise ValueError("ascii_symbols must not be None") if len(ascii_symbols) != qubit_count: msg = f"ascii_symbols, {ascii_symbols}, length must equal qubit_count, {qubit_count}" diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index 3d80f751..8155c587 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. from __future__ import annotations -from typing import Any, Dict +from typing import Any, Dict, List from braket.circuits.observable import Observable from braket.circuits.qubit import QubitInput @@ -26,25 +26,25 @@ class ResultType: the metadata that defines what a requested result type is and what it does. """ - def __init__(self, ascii_symbol: str): + def __init__(self, ascii_symbols: List[str]): """ Args: - ascii_symbol (str): ASCII string symbol for the result type. This is used when + ascii_symbols (List[str]): ASCII string symbols for the result type. This is used when printing a diagram of circuits. Raises: - ValueError: `ascii_symbol` is None + ValueError: `ascii_symbols` is None """ - if ascii_symbol is None: - raise ValueError(f"ascii_symbol must not be None") + if ascii_symbols is None: + raise ValueError("ascii_symbols must not be None") - self._ascii_symbol = ascii_symbol + self._ascii_symbols = ascii_symbols @property - def ascii_symbol(self) -> str: - """str: Returns the ascii symbol for the requested result type.""" - return self._ascii_symbol + def ascii_symbols(self) -> List[str]: + """List[str]: Returns the ascii symbols for the requested result type.""" + return self._ascii_symbols @property def name(self) -> str: @@ -135,7 +135,9 @@ class ObservableResultType(ResultType): See :mod:`braket.circuits.observables` module for all of the supported observables. """ - def __init__(self, ascii_symbol: str, observable: Observable, target: QubitSetInput = None): + def __init__( + self, ascii_symbols: List[str], observable: Observable, target: QubitSetInput = None + ): """ Args: observable (Observable): the observable for the result type @@ -144,10 +146,12 @@ def __init__(self, ascii_symbol: str, observable: Observable, target: QubitSetIn only operate on 1 qubit and it will be applied to all qubits in parallel Raises: - ValueError: If the observable's qubit count and the number of target qubits - are not equal. Or, if target=None and the observable's qubit count is not 1. + ValueError: if target=None and the observable's qubit count is not 1. + Or, if target!=None and the observable's qubit count and the number of target qubits + are not equal. Or, if target!=None and the observable's qubit count and + the number of ascii_symbols are not equal. """ - super().__init__(ascii_symbol) + super().__init__(ascii_symbols) self._observable = observable self._target = QubitSet(target) if not self._target: @@ -155,10 +159,15 @@ def __init__(self, ascii_symbol: str, observable: Observable, target: QubitSetIn raise ValueError( f"Observable {self._observable} must only operate on 1 qubit for target=None" ) - elif self._observable.qubit_count != len(self._target): - raise ValueError( - f"Observable's qubit count and the number of target qubits must be equal" - ) + else: + if self._observable.qubit_count != len(self._target): + raise ValueError( + "Observable's qubit count and the number of target qubits must be equal" + ) + if 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: diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 9b6448bb..320f89d3 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -40,7 +40,7 @@ class StateVector(ResultType): """ def __init__(self): - super().__init__(ascii_symbol=["StateVector"]) + super().__init__(ascii_symbols=["StateVector"]) def to_ir(self) -> ir.StateVector: return ir.StateVector.construct() @@ -87,13 +87,13 @@ def __init__(self, state: List[str]): Examples: >>> ResultType.Amplitude(state=['01', '10']) """ - super().__init__(ascii_symbol=["Amplitude"]) if not state or not all( isinstance(amplitude, str) and re.fullmatch("^[01]+$", amplitude) for amplitude in state ): raise ValueError( "A non-empty list of states must be specified in binary encoding e.g. ['01', '10']" ) + super().__init__(ascii_symbols=[f"Amplitude({','.join(state)})"]) self._state = state @property @@ -154,8 +154,9 @@ def __init__(self, target: QubitSetInput = None): Examples: >>> ResultType.Probability(target=[0, 1]) """ - super().__init__(ascii_symbol=["Prob"]) self._target = QubitSet(target) + ascii_symbols = ["Probability"] * len(self._target) if self._target else ["Probability"] + super().__init__(ascii_symbols=ascii_symbols) @property def target(self) -> QubitSet: @@ -235,7 +236,11 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): >>> tensor_product = Observable.Y() @ Observable.Z() >>> ResultType.Expectation(observable=tensor_product, target=[0, 1]) """ - super().__init__(ascii_symbol=["Expectation"], observable=observable, target=target) + super().__init__( + ascii_symbols=[f"Expectation({obs_ascii})" for obs_ascii in observable.ascii_symbols], + observable=observable, + target=target, + ) def to_ir(self) -> ir.Expectation: if self.target: @@ -298,7 +303,11 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): >>> tensor_product = Observable.Y() @ Observable.Z() >>> ResultType.Sample(observable=tensor_product, target=[0, 1]) """ - super().__init__(ascii_symbol=["Sample"], observable=observable, target=target) + super().__init__( + ascii_symbols=[f"Sample({obs_ascii})" for obs_ascii in observable.ascii_symbols], + observable=observable, + target=target, + ) def to_ir(self) -> ir.Sample: if self.target: @@ -362,7 +371,11 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): >>> tensor_product = Observable.Y() @ Observable.Z() >>> ResultType.Variance(observable=tensor_product, target=[0, 1]) """ - super().__init__(ascii_symbol=["Variance"], observable=observable, target=target) + super().__init__( + ascii_symbols=[f"Variance({obs_ascii})" for obs_ascii in observable.ascii_symbols], + observable=observable, + target=target, + ) def to_ir(self) -> ir.Variance: if self.target: 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 ba12563d..77d40b9f 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.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.circuits import AsciiCircuitDiagram, Circuit, Gate, Instruction, Operator +import numpy as np +from braket.circuits import AsciiCircuitDiagram, Circuit, Gate, Instruction, Observable, Operator def test_empty_circuit(): @@ -226,3 +227,149 @@ def to_ir(self, target): ) expected = "\n".join(expected) assert AsciiCircuitDiagram.build_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|", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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 |", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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)", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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 : -C---H-Variance(Y)----Sample(Y)-", + " | | ", + "q1 : -|-C------------------Sample(Y)-", + " | | | ", + "q2 : -X-|---Expectation(Y)-Sample(Y)-", + " | | ", + "q3 : ---X------------------Sample(Y)-", + "", + "T : | 0 |1| Result Types |", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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 : -C---H-Variance(Y)------------", + " | ", + "q1 : -|-C---Expectation(Hermitian)-", + " | | ", + "q2 : -X-|--------------------------", + " | ", + "q3 : ---X---Expectation(Y)---------", + "", + "T : | 0 |1| Result Types |", + "", + "Additional result types: Amplitude(0001), StateVector", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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 : -C---H-Variance(Y)---------", + " | ", + "q1 : -|-C---Expectation(MyHerm)-", + " | | | ", + "q2 : -X-|---Expectation(MyHerm)-", + " | ", + "q3 : ---X---Expectation(Y)------", + "", + "T : | 0 |1| Result Types |", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_diagram(circ) == expected diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 4dfd7f28..a3cd2584 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -136,11 +136,19 @@ def test_add_result_type_observable_conflict_all(): @pytest.mark.xfail(raises=ValueError) -def test_add_result_type_observable_conflict_all_target(): +def test_add_result_type_observable_conflict_all_target_then_selected_target(): circ = Circuit().add_result_type(ResultType.Probability()) circ.add_result_type(ResultType.Expectation(observable=Observable.Y(), target=[0, 1])) +@pytest.mark.xfail(raises=ValueError) +def test_add_result_type_observable_conflict_selected_target_then_all_target(): + circ = Circuit().add_result_type( + ResultType.Expectation(observable=Observable.Y(), target=[0, 1]) + ) + circ.add_result_type(ResultType.Probability()) + + def test_add_result_type_observable_no_conflict_all_target(): expected = [ ResultType.Probability(), diff --git a/test/unit_tests/braket/circuits/test_result_type.py b/test/unit_tests/braket/circuits/test_result_type.py index e1222b39..58794147 100644 --- a/test/unit_tests/braket/circuits/test_result_type.py +++ b/test/unit_tests/braket/circuits/test_result_type.py @@ -17,7 +17,7 @@ @pytest.fixture def result_type(): - return ResultType(ascii_symbol="foo") + return ResultType(ascii_symbols=["foo"]) @pytest.fixture @@ -32,7 +32,7 @@ def sv(): @pytest.mark.xfail(raises=ValueError) def test_none_ascii(): - ResultType(ascii_symbol=None) + ResultType(ascii_symbols=None) def test_name(result_type): @@ -41,9 +41,9 @@ def test_name(result_type): def test_ascii_symbol(): - ascii_symbol = "foo" - result_type = ResultType(ascii_symbol=ascii_symbol) - assert result_type.ascii_symbol == ascii_symbol + ascii_symbols = ["foo"] + result_type = ResultType(ascii_symbols=ascii_symbols) + assert result_type.ascii_symbols == ascii_symbols def test_equality(): @@ -58,7 +58,7 @@ def test_equality(): @pytest.mark.xfail(raises=AttributeError) def test_ascii_symbol_setter(result_type): - result_type.ascii_symbol = "bar" + result_type.ascii_symbols = ["bar"] @pytest.mark.xfail(raises=AttributeError) @@ -74,7 +74,7 @@ def test_to_ir_not_implemented_by_default(result_type): def test_register_result(): class _FooResultType(ResultType): def __init__(self): - super().__init__(ascii_symbol="foo") + super().__init__(ascii_symbols=["foo"]) ResultType.register_result_type(_FooResultType) assert ResultType._FooResultType().name == _FooResultType().name @@ -120,18 +120,27 @@ def test_copy_with_target_and_mapping(prob): @pytest.mark.xfail(raises=ValueError) def test_expectation_init_value_error_target(): - ObservableResultType(ascii_symbol="Obs", observable=Observable.X() @ Observable.Y(), target=[]) + ObservableResultType( + ascii_symbols=["Obs", "Obs"], observable=Observable.X() @ Observable.Y(), target=[] + ) + + +@pytest.mark.xfail(raises=ValueError) +def test_expectation_init_value_error_ascii_symbols(): + ObservableResultType( + ascii_symbols=["Obs"], observable=Observable.X() @ Observable.Y(), target=[1, 2] + ) @pytest.mark.xfail(raises=ValueError) def test_obs_rt_init_value_error_qubit_count(): - ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=[0, 1]) + ObservableResultType(ascii_symbols=["Obs"], observable=Observable.X(), target=[0, 1]) def test_obs_rt_equality(): - a1 = ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=0) - a2 = ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=0) - a3 = ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=1) + a1 = ObservableResultType(ascii_symbols=["Obs"], observable=Observable.X(), target=0) + a2 = ObservableResultType(ascii_symbols=["Obs"], observable=Observable.X(), target=0) + a3 = ObservableResultType(ascii_symbols=["Obs"], observable=Observable.X(), target=1) a4 = "hi" assert a1 == a2 assert a1 != a3 @@ -142,8 +151,8 @@ def test_obs_rt_equality(): def test_obs_rt_repr(): - a1 = ObservableResultType(ascii_symbol="Obs", observable=Observable.X(), target=0) + a1 = ObservableResultType(ascii_symbols=["Obs"], observable=Observable.X(), target=0) assert ( str(a1) - == f"ObservableResultType(observable=X('qubit_count': 1), target=QubitSet([Qubit(0)]))" + == "ObservableResultType(observable=X('qubit_count': 1), target=QubitSet([Qubit(0)]))" ) From a98c9df19a75cb28d675acc2d5de804686490628 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Mon, 18 May 2020 17:16:11 -0700 Subject: [PATCH 0097/1165] Remove qs2/3 references (#78) --- README.md | 4 +-- src/braket/aws/aws_quantum_simulator_arns.py | 2 -- test/integ_tests/test_device_creation.py | 7 +---- .../test_simulator_quantum_task.py | 31 ++----------------- 4 files changed, 4 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index bf9798e1..3616e1d7 100644 --- a/README.md +++ b/README.md @@ -194,10 +194,8 @@ print(device.run(bell, s3_folder).result().measurement_counts) The code sample imports the Amazon Braket framework, then defines the execution environment as the AWSQuantumSimulator and the device to use. The `s3_folder` statement defines the Amazon S3 bucket for job output and the folder in the bucket to store job output. This folder is created when you run the job. It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the job. ### Available Simulators -There are currently three simulators available for Amazon Braket. To specify which simulator to use, change the code sample to replace the value for the `AwsQuantumSimulator` to one of the following values: +There is currently one AwsQuantumSimulator available: - `arn:aws:aqx:::quantum-simulator:aqx:qs1` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the state vector and outputs an array of bit strings that appears as though it came from a quantum computer. Does not provide a state vector. -- `arn:aws:aqx:::quantum-simulator:aqx:qs2` – a tensor network simulator. Provides an approximation of running a job on a quantum computer. -- `arn:aws:aqx:::quantum-simulator:aqx:qs3` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples from the state vector but includes the entire state vector. This generates more data, and therefore incurs additional costs for storage of data in Amazon S3. #### To validate your configuration using a Python file 1. Open a text editor with example file `../braket-python-sdk/examples/bell.py`. diff --git a/src/braket/aws/aws_quantum_simulator_arns.py b/src/braket/aws/aws_quantum_simulator_arns.py index 3c2c7a36..4e47c3de 100644 --- a/src/braket/aws/aws_quantum_simulator_arns.py +++ b/src/braket/aws/aws_quantum_simulator_arns.py @@ -16,5 +16,3 @@ class AwsQuantumSimulatorArns(str, Enum): QS1 = "arn:aws:aqx:::quantum-simulator:aqx:qs1" - QS2 = "arn:aws:aqx:::quantum-simulator:aqx:qs2" - QS3 = "arn:aws:aqx:::quantum-simulator:aqx:qs3" diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 3d87f048..797f0b1d 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -31,12 +31,7 @@ def test_device_across_regions(aws_session): @pytest.mark.parametrize( - "simulator_arn,simulator_name", - [ - (AwsQuantumSimulatorArns.QS1, "quantum-simulator-1"), - (AwsQuantumSimulatorArns.QS2, "quantum-simulator-2"), - (AwsQuantumSimulatorArns.QS3, "quantum-simulator-3"), - ], + "simulator_arn,simulator_name", [(AwsQuantumSimulatorArns.QS1, "quantum-simulator-1"),], ) def test_simulator_creation(simulator_arn, simulator_name, aws_session): simulator = AwsQuantumSimulator(simulator_arn, aws_session=aws_session) diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 71b082e2..12903470 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -18,9 +18,7 @@ from braket.circuits import Circuit -@pytest.mark.parametrize( - "simulator_arn", [AwsQuantumSimulatorArns.QS1, AwsQuantumSimulatorArns.QS3] -) +@pytest.mark.parametrize("simulator_arn", [AwsQuantumSimulatorArns.QS1]) def test_bell_pair(simulator_arn, aws_session, s3_destination_folder): device = AwsQuantumSimulator(simulator_arn, aws_session) bell = Circuit().h(0).cnot(0, 1) @@ -31,9 +29,7 @@ def test_bell_pair(simulator_arn, aws_session, s3_destination_folder): assert len(result.measurements) == 750 -@pytest.mark.parametrize( - "simulator_arn", [AwsQuantumSimulatorArns.QS1, AwsQuantumSimulatorArns.QS3] -) +@pytest.mark.parametrize("simulator_arn", [AwsQuantumSimulatorArns.QS1]) def test_qubit_ordering(simulator_arn, aws_session, s3_destination_folder): device = AwsQuantumSimulator(simulator_arn, aws_session) @@ -46,26 +42,3 @@ def test_qubit_ordering(simulator_arn, aws_session, s3_destination_folder): state_001 = Circuit().i(0).i(1).x(2) result = device.run(state_001, s3_destination_folder).result() assert result.measurement_counts.most_common(1)[0][0] == "001" - - -def test_state_vector(aws_session, s3_destination_folder): - device = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS3, aws_session) - bell = Circuit().h(0).cnot(0, 1) - state_vector = device.run(bell, s3_destination_folder, shots=1).result().state_vector - assert state_vector["00"] == complex(1 / math.sqrt(2), 0) - assert state_vector["01"] == 0 - assert state_vector["10"] == 0 - assert state_vector["11"] == complex(1 / math.sqrt(2), 0) - - -def test_qs2_quantum_task(aws_session, s3_destination_folder): - device = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS2, aws_session) - - bell = Circuit().h(range(8)) - measurements = device.run(bell, s3_destination_folder, shots=1).result().measurements - - # 1 shot - assert len(measurements) == 1 - - # 8 qubits - assert len(measurements[0]) == 8 From 2e31bc553aaeb09498186038eaf5909145534d8a Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Mon, 18 May 2020 17:38:59 -0700 Subject: [PATCH 0098/1165] Feature/docs update (#79) * Update README example to use poll_timeout_seconds Updated the example algorithm to include poll_timeout_seconds, which lets you increase the default polling time to avoid local timeout errors when QPUs are not immediately available to run a task. * Update to README to correct the description for poll_timout * Update README description of polling_timeout_seconds --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3616e1d7..6b0769c4 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,7 @@ Tasks sent to QPUs don't always run right away. For IonQ, jobs are run once ever ## Running a Quantum Algorithm on a Quantum Computer With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The steps to do so are the same as those described to validate your environment. Just replace the example code provided in this document with your own code. -The following example executes the same Bell Pair example described to validate your configuration against a Rigetti quantum computer. +The following example executes the same Bell Pair example described to validate your configuration against a Rigetti quantum computer. When you execute your task, Braket polls for a result. If it a result is not returned during the default polling time, such as when a QPU is unavailable, a local timeout error is returned. To avoid receiving timeout errors, you can include `poll_timeout_seconds` parameter and specify a longer polling time. In this example, `poll_timeout_seconds=86400` sets the polling time to one day (24 hours). ```python import boto3 from braket.circuits import Circuit @@ -264,7 +264,8 @@ device = AwsQpu(AwsQpuArns.RIGETTI) s3_folder = (f"braket-output-{aws_account_id}", "RIGETTI") bell = Circuit().h(0).cnot(0, 1) -print(device.run(bell, s3_folder).result().measurement_counts) +print(device.run(bell, s3_folder), +poll_timeout_seconds=86400).result().measurement_counts) ``` Specify which quantum computer hardware to use by changing the value of the `device_arn` to the value for quantum computer to use: From 9cc843934505f0db9b3a336e7645eef10f992fb9 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Mon, 18 May 2020 18:08:31 -0700 Subject: [PATCH 0099/1165] Update version to 0.3.6 (#80) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 790a78bc..9a43d5cd 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="braket-sdk", - version="0.3.5", + version="0.3.6", license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), From 59ff88cad09370703ff9409e62a3775ae9a8add8 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Mon, 18 May 2020 17:16:11 -0700 Subject: [PATCH 0100/1165] Remove qs2/3 references (#78) --- README.md | 4 +-- src/braket/aws/aws_quantum_simulator_arns.py | 2 -- test/integ_tests/test_device_creation.py | 7 +---- .../test_simulator_quantum_task.py | 31 ++----------------- 4 files changed, 4 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index bf9798e1..3616e1d7 100644 --- a/README.md +++ b/README.md @@ -194,10 +194,8 @@ print(device.run(bell, s3_folder).result().measurement_counts) The code sample imports the Amazon Braket framework, then defines the execution environment as the AWSQuantumSimulator and the device to use. The `s3_folder` statement defines the Amazon S3 bucket for job output and the folder in the bucket to store job output. This folder is created when you run the job. It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the job. ### Available Simulators -There are currently three simulators available for Amazon Braket. To specify which simulator to use, change the code sample to replace the value for the `AwsQuantumSimulator` to one of the following values: +There is currently one AwsQuantumSimulator available: - `arn:aws:aqx:::quantum-simulator:aqx:qs1` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the state vector and outputs an array of bit strings that appears as though it came from a quantum computer. Does not provide a state vector. -- `arn:aws:aqx:::quantum-simulator:aqx:qs2` – a tensor network simulator. Provides an approximation of running a job on a quantum computer. -- `arn:aws:aqx:::quantum-simulator:aqx:qs3` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples from the state vector but includes the entire state vector. This generates more data, and therefore incurs additional costs for storage of data in Amazon S3. #### To validate your configuration using a Python file 1. Open a text editor with example file `../braket-python-sdk/examples/bell.py`. diff --git a/src/braket/aws/aws_quantum_simulator_arns.py b/src/braket/aws/aws_quantum_simulator_arns.py index 3c2c7a36..4e47c3de 100644 --- a/src/braket/aws/aws_quantum_simulator_arns.py +++ b/src/braket/aws/aws_quantum_simulator_arns.py @@ -16,5 +16,3 @@ class AwsQuantumSimulatorArns(str, Enum): QS1 = "arn:aws:aqx:::quantum-simulator:aqx:qs1" - QS2 = "arn:aws:aqx:::quantum-simulator:aqx:qs2" - QS3 = "arn:aws:aqx:::quantum-simulator:aqx:qs3" diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 3d87f048..797f0b1d 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -31,12 +31,7 @@ def test_device_across_regions(aws_session): @pytest.mark.parametrize( - "simulator_arn,simulator_name", - [ - (AwsQuantumSimulatorArns.QS1, "quantum-simulator-1"), - (AwsQuantumSimulatorArns.QS2, "quantum-simulator-2"), - (AwsQuantumSimulatorArns.QS3, "quantum-simulator-3"), - ], + "simulator_arn,simulator_name", [(AwsQuantumSimulatorArns.QS1, "quantum-simulator-1"),], ) def test_simulator_creation(simulator_arn, simulator_name, aws_session): simulator = AwsQuantumSimulator(simulator_arn, aws_session=aws_session) diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 71b082e2..12903470 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -18,9 +18,7 @@ from braket.circuits import Circuit -@pytest.mark.parametrize( - "simulator_arn", [AwsQuantumSimulatorArns.QS1, AwsQuantumSimulatorArns.QS3] -) +@pytest.mark.parametrize("simulator_arn", [AwsQuantumSimulatorArns.QS1]) def test_bell_pair(simulator_arn, aws_session, s3_destination_folder): device = AwsQuantumSimulator(simulator_arn, aws_session) bell = Circuit().h(0).cnot(0, 1) @@ -31,9 +29,7 @@ def test_bell_pair(simulator_arn, aws_session, s3_destination_folder): assert len(result.measurements) == 750 -@pytest.mark.parametrize( - "simulator_arn", [AwsQuantumSimulatorArns.QS1, AwsQuantumSimulatorArns.QS3] -) +@pytest.mark.parametrize("simulator_arn", [AwsQuantumSimulatorArns.QS1]) def test_qubit_ordering(simulator_arn, aws_session, s3_destination_folder): device = AwsQuantumSimulator(simulator_arn, aws_session) @@ -46,26 +42,3 @@ def test_qubit_ordering(simulator_arn, aws_session, s3_destination_folder): state_001 = Circuit().i(0).i(1).x(2) result = device.run(state_001, s3_destination_folder).result() assert result.measurement_counts.most_common(1)[0][0] == "001" - - -def test_state_vector(aws_session, s3_destination_folder): - device = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS3, aws_session) - bell = Circuit().h(0).cnot(0, 1) - state_vector = device.run(bell, s3_destination_folder, shots=1).result().state_vector - assert state_vector["00"] == complex(1 / math.sqrt(2), 0) - assert state_vector["01"] == 0 - assert state_vector["10"] == 0 - assert state_vector["11"] == complex(1 / math.sqrt(2), 0) - - -def test_qs2_quantum_task(aws_session, s3_destination_folder): - device = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS2, aws_session) - - bell = Circuit().h(range(8)) - measurements = device.run(bell, s3_destination_folder, shots=1).result().measurements - - # 1 shot - assert len(measurements) == 1 - - # 8 qubits - assert len(measurements[0]) == 8 From 40b668e7a514d0fc130721c6a8db26fb7af2edbe Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Mon, 18 May 2020 17:38:59 -0700 Subject: [PATCH 0101/1165] Feature/docs update (#79) * Update README example to use poll_timeout_seconds Updated the example algorithm to include poll_timeout_seconds, which lets you increase the default polling time to avoid local timeout errors when QPUs are not immediately available to run a task. * Update to README to correct the description for poll_timout * Update README description of polling_timeout_seconds --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3616e1d7..6b0769c4 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,7 @@ Tasks sent to QPUs don't always run right away. For IonQ, jobs are run once ever ## Running a Quantum Algorithm on a Quantum Computer With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The steps to do so are the same as those described to validate your environment. Just replace the example code provided in this document with your own code. -The following example executes the same Bell Pair example described to validate your configuration against a Rigetti quantum computer. +The following example executes the same Bell Pair example described to validate your configuration against a Rigetti quantum computer. When you execute your task, Braket polls for a result. If it a result is not returned during the default polling time, such as when a QPU is unavailable, a local timeout error is returned. To avoid receiving timeout errors, you can include `poll_timeout_seconds` parameter and specify a longer polling time. In this example, `poll_timeout_seconds=86400` sets the polling time to one day (24 hours). ```python import boto3 from braket.circuits import Circuit @@ -264,7 +264,8 @@ device = AwsQpu(AwsQpuArns.RIGETTI) s3_folder = (f"braket-output-{aws_account_id}", "RIGETTI") bell = Circuit().h(0).cnot(0, 1) -print(device.run(bell, s3_folder).result().measurement_counts) +print(device.run(bell, s3_folder), +poll_timeout_seconds=86400).result().measurement_counts) ``` Specify which quantum computer hardware to use by changing the value of the `device_arn` to the value for quantum computer to use: From 593e5bb638005e72d9a1dac2dbd129289a5cb90c Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Mon, 18 May 2020 18:08:31 -0700 Subject: [PATCH 0102/1165] Update version to 0.3.6 (#80) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a2404870..a1ece2e3 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="braket-sdk", - version="0.3.5", + version="0.3.6", license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), From 45750af0b594738aa5265695e3907321b43b7743 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Wed, 20 May 2020 14:44:06 -0700 Subject: [PATCH 0103/1165] Updates to the README to clarify the example in the QPU section (#83) * Update README to clarify the example in the QPU section * Update README to clarify the example in the QPU section * Update to README to clarify the code example and QPU availability --- README.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6b0769c4..37e5af67 100644 --- a/README.md +++ b/README.md @@ -250,9 +250,11 @@ Braket Python SDK comes bundled with an implementation of a quantum simulator th Tasks sent to QPUs don't always run right away. For IonQ, jobs are run once every 24 hours. For Rigetti, tasks are queued and run when the QPU is available, with the time varying day to day. To view task status, you can enable debugging logs. An example of how to enable these logs is included in repo: `../examples/debug_bell.py`. This example enables task logging so that status updates are continuously printed to console after a quantum task is executed. The logs can also be configured to save to a file or output to another stream. You can use the debugging example to get information on the tasks you submit, such as the current status, so that you know when your task completes. ## Running a Quantum Algorithm on a Quantum Computer -With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The steps to do so are the same as those described to validate your environment. Just replace the example code provided in this document with your own code. +With Amazon Braket, you can run your quantum circuit on a physical quantum computer. -The following example executes the same Bell Pair example described to validate your configuration against a Rigetti quantum computer. When you execute your task, Braket polls for a result. If it a result is not returned during the default polling time, such as when a QPU is unavailable, a local timeout error is returned. To avoid receiving timeout errors, you can include `poll_timeout_seconds` parameter and specify a longer polling time. In this example, `poll_timeout_seconds=86400` sets the polling time to one day (24 hours). +The following example executes the same Bell Pair example described to validate your configuration on a Rigetti quantum computer. When you execute your task, Amazon Braket polls for a result. If it a result is not returned within the default polling time, such as when a QPU is unavailable, a local timeout error is returned. You can always restart the polling by using `task.result()`. + +However, to avoid receiving timeout errors, you can include `poll_timeout_seconds` parameter and specify a longer polling time. In this example, `poll_timeout_seconds=86400` sets the polling time to one day (24 hours). ```python import boto3 from braket.circuits import Circuit @@ -264,14 +266,14 @@ device = AwsQpu(AwsQpuArns.RIGETTI) s3_folder = (f"braket-output-{aws_account_id}", "RIGETTI") bell = Circuit().h(0).cnot(0, 1) -print(device.run(bell, s3_folder), -poll_timeout_seconds=86400).result().measurement_counts) +task = device.run(bell, s3_folder, poll_timeout_seconds=86400), +print(task.result().measurement_counts) ``` Specify which quantum computer hardware to use by changing the value of the `device_arn` to the value for quantum computer to use: -- **IonQ** "arn:aws:aqx:::qpu:ionq" (Tasks may take 24 hours to complete on IonQ.) -- **Rigetti** "arn:aws:aqx:::qpu:rigetti" (Tasks are run when the QPU is available. Time varies day to day.) -- **D-Wave** "arn:aws:aqx:::qpu:d-wave" (Use for annealing problems.) +- **IonQ** "arn:aws:aqx:::qpu:ionq" (Available 4:00 PM to 8:00 PM ET M-F) +- **Rigetti** "arn:aws:aqx:::qpu:rigetti" (Available 11:00 AM to 1:00 PM ET daily) +- **D-Wave** "arn:aws:aqx:::qpu:d-wave" (Available 24/7. See the next section in this document for more information about using D-Wave.) ### Using Amazon Braket with D-Wave QPU If you want to use [Ocean](https://docs.ocean.dwavesys.com/en/latest/) with the D-Wave QPU, you can install the [braket-ocean-python-plugin](https://github.com/aws/braket-ocean-python-plugin). Information about how to install the plugin is provided in the [README](https://github.com/aws/braket-ocean-python-plugin/blob/master/README.md) for the repo. @@ -386,4 +388,4 @@ tox -e integ-tests -- -k 'your_test' ``` ## License -This project is licensed under the Apache-2.0 License. +This project is licensed under the Apache-2.0 License. \ No newline at end of file From fae3907c58b2faaea4c07cda1804bb4ddab5f95b Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Wed, 20 May 2020 15:50:47 -0700 Subject: [PATCH 0104/1165] Update version to v0.3.7 (#84) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9a43d5cd..237b5df5 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="braket-sdk", - version="0.3.6", + version="0.3.7", license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), From ee5fd036a6210338ecf2444495b38c2d1bdc323c Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Wed, 20 May 2020 14:44:06 -0700 Subject: [PATCH 0105/1165] Updates to the README to clarify the example in the QPU section (#83) * Update README to clarify the example in the QPU section * Update README to clarify the example in the QPU section * Update to README to clarify the code example and QPU availability --- README.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6b0769c4..37e5af67 100644 --- a/README.md +++ b/README.md @@ -250,9 +250,11 @@ Braket Python SDK comes bundled with an implementation of a quantum simulator th Tasks sent to QPUs don't always run right away. For IonQ, jobs are run once every 24 hours. For Rigetti, tasks are queued and run when the QPU is available, with the time varying day to day. To view task status, you can enable debugging logs. An example of how to enable these logs is included in repo: `../examples/debug_bell.py`. This example enables task logging so that status updates are continuously printed to console after a quantum task is executed. The logs can also be configured to save to a file or output to another stream. You can use the debugging example to get information on the tasks you submit, such as the current status, so that you know when your task completes. ## Running a Quantum Algorithm on a Quantum Computer -With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The steps to do so are the same as those described to validate your environment. Just replace the example code provided in this document with your own code. +With Amazon Braket, you can run your quantum circuit on a physical quantum computer. -The following example executes the same Bell Pair example described to validate your configuration against a Rigetti quantum computer. When you execute your task, Braket polls for a result. If it a result is not returned during the default polling time, such as when a QPU is unavailable, a local timeout error is returned. To avoid receiving timeout errors, you can include `poll_timeout_seconds` parameter and specify a longer polling time. In this example, `poll_timeout_seconds=86400` sets the polling time to one day (24 hours). +The following example executes the same Bell Pair example described to validate your configuration on a Rigetti quantum computer. When you execute your task, Amazon Braket polls for a result. If it a result is not returned within the default polling time, such as when a QPU is unavailable, a local timeout error is returned. You can always restart the polling by using `task.result()`. + +However, to avoid receiving timeout errors, you can include `poll_timeout_seconds` parameter and specify a longer polling time. In this example, `poll_timeout_seconds=86400` sets the polling time to one day (24 hours). ```python import boto3 from braket.circuits import Circuit @@ -264,14 +266,14 @@ device = AwsQpu(AwsQpuArns.RIGETTI) s3_folder = (f"braket-output-{aws_account_id}", "RIGETTI") bell = Circuit().h(0).cnot(0, 1) -print(device.run(bell, s3_folder), -poll_timeout_seconds=86400).result().measurement_counts) +task = device.run(bell, s3_folder, poll_timeout_seconds=86400), +print(task.result().measurement_counts) ``` Specify which quantum computer hardware to use by changing the value of the `device_arn` to the value for quantum computer to use: -- **IonQ** "arn:aws:aqx:::qpu:ionq" (Tasks may take 24 hours to complete on IonQ.) -- **Rigetti** "arn:aws:aqx:::qpu:rigetti" (Tasks are run when the QPU is available. Time varies day to day.) -- **D-Wave** "arn:aws:aqx:::qpu:d-wave" (Use for annealing problems.) +- **IonQ** "arn:aws:aqx:::qpu:ionq" (Available 4:00 PM to 8:00 PM ET M-F) +- **Rigetti** "arn:aws:aqx:::qpu:rigetti" (Available 11:00 AM to 1:00 PM ET daily) +- **D-Wave** "arn:aws:aqx:::qpu:d-wave" (Available 24/7. See the next section in this document for more information about using D-Wave.) ### Using Amazon Braket with D-Wave QPU If you want to use [Ocean](https://docs.ocean.dwavesys.com/en/latest/) with the D-Wave QPU, you can install the [braket-ocean-python-plugin](https://github.com/aws/braket-ocean-python-plugin). Information about how to install the plugin is provided in the [README](https://github.com/aws/braket-ocean-python-plugin/blob/master/README.md) for the repo. @@ -386,4 +388,4 @@ tox -e integ-tests -- -k 'your_test' ``` ## License -This project is licensed under the Apache-2.0 License. +This project is licensed under the Apache-2.0 License. \ No newline at end of file From d895f4bc97c7ac6467841514d3995cfabff6a099 Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Wed, 20 May 2020 15:50:47 -0700 Subject: [PATCH 0106/1165] Update version to v0.3.7 (#84) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a1ece2e3..1c893682 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="braket-sdk", - version="0.3.6", + version="0.3.7", license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), From d31831646c4a0262279f209725d9e32f063441ea Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 21 May 2020 11:46:43 -0700 Subject: [PATCH 0107/1165] Add integ tests for local simulator (#86) * Add integ tests for local simulator Also fixed 2 bugs in `Circuit` involving basis rotation gates 1. Fixed the order of gate application when there are no targets 2. Fixed the targets for tensor product observables * Split test_result_types_nonzero_shots into two * Common pieces from AWS and local integ tests Created helper methods used by both the local simulator and AWS simulator integ tests * Add license header to new file --- src/braket/circuits/circuit.py | 39 ++++++-- src/braket/circuits/observable.py | 1 + src/braket/circuits/observables.py | 1 + src/braket/circuits/result_type.py | 1 + src/braket/devices/braket_simulator.py | 64 ------------- src/braket/devices/local_simulator.py | 2 +- test/integ_tests/conftest.py | 16 ++++ test/integ_tests/simulator_assert_utils.py | 26 +++++ test/integ_tests/test_device_creation.py | 2 +- .../test_local_braket_simulator.py | 95 +++++++++++++++++++ .../test_simulator_quantum_task.py | 38 ++++---- .../braket/circuits/test_circuit.py | 25 ++++- .../braket/devices/test_local_simulator.py | 2 +- 13 files changed, 218 insertions(+), 94 deletions(-) delete mode 100644 src/braket/devices/braket_simulator.py create mode 100644 test/integ_tests/simulator_assert_utils.py create mode 100644 test/integ_tests/test_local_braket_simulator.py diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index aa502547..c371a8c5 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -19,6 +19,7 @@ from braket.circuits.instruction import Instruction from braket.circuits.moments import Moments from braket.circuits.observable import Observable +from braket.circuits.observables import TensorProduct from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput from braket.circuits.result_type import ObservableResultType, ResultType @@ -140,19 +141,39 @@ 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 = [] - observable_return_types = filter( - lambda x: isinstance(x, ObservableResultType), self._result_types + observable_return_types = ( + result_type + for result_type in self._result_types + if isinstance(result_type, ObservableResultType) ) - for target, observable in [(obs.target, obs.observable) for obs in observable_return_types]: - for gate in observable.basis_rotation_gates: - if not target: - basis_rotation_instructions.extend( - [Instruction(gate, target) for target in self._moments.qubits] + + for return_type in observable_return_types: + target: List[int] = return_type.target + observable: Observable = return_type.observable + if not target: + # There will be only one result type in observable_return_types, + # and its observable acts on all qubits + for target in self._moments.qubits: + basis_rotation_instructions += Circuit._observable_to_instruction( + observable, target ) - else: - basis_rotation_instructions.append(Instruction(gate, target)) + else: + basis_rotation_instructions += Circuit._observable_to_instruction( + observable, target + ) return basis_rotation_instructions + @staticmethod + def _observable_to_instruction(observable: Observable, targets: List[int]): + if isinstance(observable, TensorProduct): + instructions = [] + for constituent in observable.observables: + target = [targets.pop(0) for _ in range(constituent.qubit_count)] + instructions += Circuit._observable_to_instruction(constituent, target) + return instructions + else: + return [Instruction(gate, targets) for gate in observable.basis_rotation_gates] + @property def moments(self) -> Moments: """Moments: Get the `moments` for this circuit.""" diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index df499eed..0f523ac7 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.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 typing import List, Sequence, Tuple, Union diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 8acba8de..d89e7da1 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.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 functools diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index 8155c587..ee4f429e 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/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. + from __future__ import annotations from typing import Any, Dict, List diff --git a/src/braket/devices/braket_simulator.py b/src/braket/devices/braket_simulator.py deleted file mode 100644 index 74c088ec..00000000 --- a/src/braket/devices/braket_simulator.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2019-2019 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 abc import ABC, abstractmethod -from typing import Any, Dict, Union - -from braket.ir.annealing import Problem -from braket.ir.jaqcd import Program - - -class BraketSimulator(ABC): - """ An abstract simulator that locally runs a quantum task. - - The task can be either a circuit-based program or an annealing task, - specified by the given IR. - - For users creating their own simulator: to register a simulator so the - Braket SDK recognizes its name, the name and class must added as an - entry point for "braket.simulators". This is done by adding an entry to - entry_points in the simulator package's setup.py: - - >>> entry_points = { - >>> "braket.simulators": [ - >>> "backend_name = " - >>> ] - >>> } - """ - - # TODO: Move this class to the local simulator repo and take a dependency on it - # As such, this will not depend on any SDK classes. - - # TODO: Update to use new simulate() method - - @abstractmethod - def run(self, ir: Union[Program, Problem], *args, **kwargs) -> Dict[str, Any]: - """ Run the task specified by the given IR. - - Extra arguments will contain any additional information necessary to run the task, - such as number of qubits. - - Args: - ir (Union[Program, Problem]): The IR representation of the program - - Returns: - Dict[str, Any]: A dict containing the results of the simulation. - In order to work with braket-python-sdk, the format of the JSON dict should - match that needed by GateModelQuantumTaskResult or AnnealingQuantumTaskResult - from the SDK, depending on the type of task. - """ - - @property - @abstractmethod - def properties(self) -> Dict[str, Any]: - """ Dict[str, Any]: Properties of the BraketSimulator device. """ diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 290655bd..46e0ef25 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -19,8 +19,8 @@ from braket.annealing.problem import Problem from braket.circuits import Circuit from braket.circuits.circuit_helpers import validate_circuit_and_shots -from braket.devices.braket_simulator import BraketSimulator from braket.devices.device import Device +from braket.simulator import BraketSimulator from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult from braket.tasks.local_quantum_task import LocalQuantumTask diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py index ab3515e7..8acc6498 100644 --- a/test/integ_tests/conftest.py +++ b/test/integ_tests/conftest.py @@ -17,6 +17,7 @@ import pytest from botocore.exceptions import ClientError from braket.aws.aws_session import AwsSession +from braket.circuits import Circuit @pytest.fixture(scope="session") @@ -70,3 +71,18 @@ 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 bell_state_and_tolerances(): + return Circuit().h(0).cnot(0, 1), {"00": (0.4, 0.6), "11": (0.4, 0.6)} + + +@pytest.fixture(scope="session") +def state_110_and_most_common(): + return Circuit().x(0).x(1).i(2), "110" + + +@pytest.fixture(scope="session") +def state_001_and_most_common(): + return Circuit().i(0).i(1).x(2), "001" diff --git a/test/integ_tests/simulator_assert_utils.py b/test/integ_tests/simulator_assert_utils.py new file mode 100644 index 00000000..98efa845 --- /dev/null +++ b/test/integ_tests/simulator_assert_utils.py @@ -0,0 +1,26 @@ +# Copyright 2019-2019 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 typing import Counter, Dict, Tuple + + +def assert_measurement_probabilities( + probabilities: Dict[str, float], tolerances: Dict[str, Tuple[float, float]] +): + for bitstring in probabilities: + tolerance = tolerances[bitstring] + assert tolerance[0] < probabilities[bitstring] < tolerance[1] + + +def assert_measurement_counts_most_common(measurement_counts: Counter, bitstring: str): + assert measurement_counts.most_common(1)[0][0] == bitstring diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 797f0b1d..73b72e52 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -31,7 +31,7 @@ def test_device_across_regions(aws_session): @pytest.mark.parametrize( - "simulator_arn,simulator_name", [(AwsQuantumSimulatorArns.QS1, "quantum-simulator-1"),], + "simulator_arn,simulator_name", [(AwsQuantumSimulatorArns.QS1, "quantum-simulator-1")], ) def test_simulator_creation(simulator_arn, simulator_name, aws_session): simulator = AwsQuantumSimulator(simulator_arn, aws_session=aws_session) diff --git a/test/integ_tests/test_local_braket_simulator.py b/test/integ_tests/test_local_braket_simulator.py new file mode 100644 index 00000000..482287d5 --- /dev/null +++ b/test/integ_tests/test_local_braket_simulator.py @@ -0,0 +1,95 @@ +# Copyright 2019-2019 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 +from braket.circuits import Circuit, Observable +from braket.circuits.result_types import Expectation, Sample, StateVector +from braket.devices import LocalSimulator +from simulator_assert_utils import ( + assert_measurement_counts_most_common, + assert_measurement_probabilities, +) + +DEVICE = LocalSimulator() +SHOTS = 750 + + +def test_bell_pair(bell_state_and_tolerances): + result = DEVICE.run(bell_state_and_tolerances[0], shots=SHOTS).result() + assert_measurement_probabilities(result.measurement_probabilities, bell_state_and_tolerances[1]) + assert len(result.measurements) == SHOTS + + +def test_qubit_ordering(state_110_and_most_common, state_001_and_most_common): + # |110> should get back value of "110" + result = DEVICE.run(state_110_and_most_common[0], shots=SHOTS).result() + assert_measurement_counts_most_common(result.measurement_counts, state_110_and_most_common[1]) + + # |001> should get back value of "001" + result = DEVICE.run(state_001_and_most_common[0], shots=SHOTS).result() + assert_measurement_counts_most_common(result.measurement_counts, state_001_and_most_common[1]) + + +def test_result_types_no_shots(): + circuit = ( + Circuit() + .h(0) + .cnot(0, 1) + .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + .state_vector() + ) + result = DEVICE.run(circuit).result() + assert len(result.result_types) == 2 + assert np.allclose( + result.get_value_by_result_type( + Expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ), + 1 / np.sqrt(2), + ) + assert np.allclose( + result.get_value_by_result_type(StateVector()), np.array([1, 0, 0, 1]) / np.sqrt(2) + ) + + +def test_result_types_nonzero_shots_expectation(): + circuit_expectation = ( + Circuit() + .h(0) + .cnot(0, 1) + .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ) + result_expectation = DEVICE.run(circuit_expectation, shots=SHOTS).result() + assert len(result_expectation.result_types) == 1 + assert ( + 0.6 + < result_expectation.get_value_by_result_type( + Expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ) + < 0.8 + ) + + +def test_result_types_nonzero_shots_sample(): + circuit_sample = ( + Circuit().h(0).cnot(0, 1).sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ) + result_sample = DEVICE.run(circuit_sample, shots=SHOTS).result() + assert len(result_sample.result_types) == 1 + assert ( + len( + result_sample.get_value_by_result_type( + Sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ) + ) + == SHOTS + ) diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 12903470..1500baea 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -11,34 +11,38 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -import math - import pytest from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns -from braket.circuits import Circuit +from simulator_assert_utils import ( + assert_measurement_counts_most_common, + assert_measurement_probabilities, +) + +SHOTS = 750 @pytest.mark.parametrize("simulator_arn", [AwsQuantumSimulatorArns.QS1]) -def test_bell_pair(simulator_arn, aws_session, s3_destination_folder): +def test_bell_pair(simulator_arn, aws_session, s3_destination_folder, bell_state_and_tolerances): device = AwsQuantumSimulator(simulator_arn, aws_session) - bell = Circuit().h(0).cnot(0, 1) - result = device.run(bell, s3_destination_folder, shots=750).result() - - assert 0.40 < result.measurement_probabilities["00"] < 0.60 - assert 0.40 < result.measurement_probabilities["11"] < 0.60 - assert len(result.measurements) == 750 + result = device.run(bell_state_and_tolerances[0], s3_destination_folder, shots=SHOTS).result() + assert_measurement_probabilities(result.measurement_probabilities, bell_state_and_tolerances[1]) + assert len(result.measurements) == SHOTS @pytest.mark.parametrize("simulator_arn", [AwsQuantumSimulatorArns.QS1]) -def test_qubit_ordering(simulator_arn, aws_session, s3_destination_folder): +def test_qubit_ordering( + simulator_arn, + aws_session, + s3_destination_folder, + state_110_and_most_common, + state_001_and_most_common, +): device = AwsQuantumSimulator(simulator_arn, aws_session) # |110> should get back value of "110" - state_110 = Circuit().x(0).x(1).i(2) - result = device.run(state_110, s3_destination_folder).result() - assert result.measurement_counts.most_common(1)[0][0] == "110" + result = device.run(state_110_and_most_common[0], s3_destination_folder, shots=SHOTS).result() + assert_measurement_counts_most_common(result.measurement_counts, state_110_and_most_common[1]) # |001> should get back value of "001" - state_001 = Circuit().i(0).i(1).x(2) - result = device.run(state_001, s3_destination_folder).result() - assert result.measurement_counts.most_common(1)[0][0] == "001" + result = device.run(state_001_and_most_common[0], s3_destination_folder, shots=SHOTS).result() + assert_measurement_counts_most_common(result.measurement_counts, state_001_and_most_common[1]) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index a3cd2584..52cacd91 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -397,9 +397,13 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): def test_basis_rotation_instructions_all(): - circ = Circuit().h(0).cnot(0, 1).sample(observable=Observable.X()) + circ = Circuit().h(0).cnot(0, 1).sample(observable=Observable.Y()) expected = [ + Instruction(Gate.Z(), 0), + Instruction(Gate.S(), 0), Instruction(Gate.H(), 0), + Instruction(Gate.Z(), 1), + Instruction(Gate.S(), 1), Instruction(Gate.H(), 1), ] assert circ.basis_rotation_instructions == expected @@ -411,6 +415,25 @@ def test_basis_rotation_instructions_target(): assert circ.basis_rotation_instructions == expected +def test_basis_rotation_instructions_tensor_product(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .expectation(observable=Observable.X() @ Observable.Y() @ Observable.Y(), target=[0, 1, 2]) + ) + expected = [ + Instruction(Gate.H(), 0), + Instruction(Gate.Z(), 1), + Instruction(Gate.S(), 1), + Instruction(Gate.H(), 1), + Instruction(Gate.Z(), 2), + Instruction(Gate.S(), 2), + Instruction(Gate.H(), 2), + ] + assert circ.basis_rotation_instructions == expected + + def test_basis_rotation_instructions_multiple_result_types(): circ = ( Circuit() diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 0ffe7894..b8574028 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -20,7 +20,7 @@ from braket.annealing import Problem, ProblemType from braket.circuits import Circuit from braket.devices import LocalSimulator, local_simulator -from braket.devices.braket_simulator import BraketSimulator +from braket.simulator import BraketSimulator from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult GATE_MODEL_RESULT = { From 0f4e8555886ec3ee3c1743bf7f557ce9d45e62a0 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 22 May 2020 11:21:53 -0700 Subject: [PATCH 0108/1165] s/constituent/factor (#87) Because when I first wrote this, I somehow forgot the term for the parts of a product. --- src/braket/circuits/circuit.py | 6 +++--- src/braket/circuits/observables.py | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index c371a8c5..d08f5722 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -167,9 +167,9 @@ def basis_rotation_instructions(self) -> List[Instruction]: def _observable_to_instruction(observable: Observable, targets: List[int]): if isinstance(observable, TensorProduct): instructions = [] - for constituent in observable.observables: - target = [targets.pop(0) for _ in range(constituent.qubit_count)] - instructions += Circuit._observable_to_instruction(constituent, target) + for factor in observable.factors: + target = [targets.pop(0) for _ in range(factor.qubit_count)] + instructions += Circuit._observable_to_instruction(factor, target) return instructions else: return [Instruction(gate, targets) for gate in observable.basis_rotation_gates] diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index d89e7da1..8ddd0ebd 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -168,7 +168,7 @@ def __init__(self, observables: List[Observable]): [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.observables + >>> t2.factors (Z('qubit_count': 1), Y('qubit_count': 1), X('qubit_count': 1)) Note: list of observables for tensor product must be given in the desired order that @@ -184,22 +184,22 @@ def __init__(self, observables: List[Observable]): def to_ir(self) -> List[str]: ir = [] - for obs in self.observables: + for obs in self.factors: ir.extend(obs.to_ir()) return ir @property - def observables(self) -> Tuple[Observable]: - """Tuple[Observable]: observables part of tensor product""" + def factors(self) -> Tuple[Observable]: + """ Tuple[Observable]: The observables that make up this tensor product""" return self._observables def to_matrix(self) -> np.ndarray: - return functools.reduce(np.kron, [obs.to_matrix() for obs in self.observables]) + return functools.reduce(np.kron, [obs.to_matrix() for obs in self.factors]) @property def basis_rotation_gates(self) -> Tuple[Gate]: gates = [] - for obs in self.observables: + for obs in self.factors: gates.extend(obs.basis_rotation_gates) return tuple(gates) @@ -209,21 +209,21 @@ def eigenvalues(self): def __matmul__(self, other): if isinstance(other, TensorProduct): - return TensorProduct(list(self.observables) + list(other.observables)) + return TensorProduct(list(self.factors) + list(other.factors)) if isinstance(other, Observable): - return TensorProduct(list(self.observables) + [other]) + return TensorProduct(list(self.factors) + [other]) raise ValueError("Can only perform tensor products between observables.") def __rmatmul__(self, other): if isinstance(other, Observable): - return TensorProduct([other] + list(self.observables)) + return TensorProduct([other] + list(self.factors)) raise ValueError("Can only perform tensor products between observables.") def __repr__(self): - return "TensorProduct(" + ", ".join([repr(o) for o in self.observables]) + ")" + return "TensorProduct(" + ", ".join([repr(o) for o in self.factors]) + ")" def __eq__(self, other): return self.matrix_equivalence(other) From 4cdbda53897a0da4ce802d9eb443fd9f4050ca23 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 26 May 2020 15:39:25 -0700 Subject: [PATCH 0109/1165] Omit braket/simulator from coverage (#88) --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index b2b0a874..5a54a877 100644 --- a/.coveragerc +++ b/.coveragerc @@ -5,6 +5,7 @@ source = braket omit = **/braket/ir/* + **/braket/simulator/* [paths] source = From b6d215784f7972eaaf3ea63b27c95a32608e2f74 Mon Sep 17 00:00:00 2001 From: brendoyl <65785721+brendoyl@users.noreply.github.com> Date: Fri, 29 May 2020 19:07:09 -0400 Subject: [PATCH 0110/1165] Updated example Bell.py and reflected change in README * Adjusted bell.py and README.md to reflect changes in bell.py * Updated bell.py and README.md to reflect changes in bell.py * Updated bell.py and README to reflect pull request comments * Added another change from comment on pull request * Added shots back in to be set to 100. Adjusted README expected outcome to reflect 100 shots instead of 1000 * Updated to have both expected outcomes be out of 100 shots --- README.md | 11 ++++++----- examples/bell.py | 3 ++- setup.py | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 37e5af67..67237eda 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,8 @@ device = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS1) s3_folder = (f"braket-output-{aws_account_id}", "folder-name") bell = Circuit().h(0).cnot(0, 1) -print(device.run(bell, s3_folder).result().measurement_counts) +task = device.run(bell, s3_folder, shots=100) +print(task.result().measurement_counts) ``` The code sample imports the Amazon Braket framework, then defines the execution environment as the AWSQuantumSimulator and the device to use. The `s3_folder` statement defines the Amazon S3 bucket for job output and the folder in the bucket to store job output. This folder is created when you run the job. It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the job. @@ -210,7 +211,7 @@ There is currently one AwsQuantumSimulator available: ``` You should see a result similar to the following: -```Counter({'11': 522, '00': 478})``` +```Counter({'11': 52, '00': 48})``` #### To validate your configuration using a Jupyter notebook See [Installing the Jupyter Software](https://jupyter.org/install) for information about how to install Jupyter. You can use either JupyterLab or classic Jupyter Notebook. @@ -237,7 +238,7 @@ Copy the code sample (above) into the notebook. If you want to use a different f Choose **Run** to execute the code to confirm that your environment is configured correctly. When the job completes, you should see output similar to the following: -`Counter({'00': 519, '11': 481})` +`Counter({'00': 52, '11': 48})` **Important** Tasks may not run immediately on the QPU. IonQ runs tasks once every 24 hours. Rigetti tasks run when the QPU is available, with times varying day to day. @@ -266,7 +267,7 @@ device = AwsQpu(AwsQpuArns.RIGETTI) s3_folder = (f"braket-output-{aws_account_id}", "RIGETTI") bell = Circuit().h(0).cnot(0, 1) -task = device.run(bell, s3_folder, poll_timeout_seconds=86400), +task = device.run(bell, s3_folder) print(task.result().measurement_counts) ``` @@ -388,4 +389,4 @@ tox -e integ-tests -- -k 'your_test' ``` ## License -This project is licensed under the Apache-2.0 License. \ No newline at end of file +This project is licensed under the Apache-2.0 License. diff --git a/examples/bell.py b/examples/bell.py index 400550b5..5adf6cb9 100644 --- a/examples/bell.py +++ b/examples/bell.py @@ -9,4 +9,5 @@ # https://wikipedia.org/wiki/Bell_state bell = Circuit().h(0).cnot(0, 1) -print(device.run(bell, s3_folder, shots=100).result().measurement_counts) +task = device.run(bell, s3_folder, shots=100) +print(task.result().measurement_counts) diff --git a/setup.py b/setup.py index 237b5df5..c8658424 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="braket-sdk", - version="0.3.7", + version="0.3.8", license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), From 3098ed81bd2a88a9666a86b96c4285aa53d9d825 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 1 Jun 2020 11:56:13 -0700 Subject: [PATCH 0111/1165] Increase default timeouts to 5 days (#90) Also added suggestion to increase timeout upon task timeout. --- README.md | 10 +- src/braket/aws/aws_qpu.py | 14 ++- src/braket/aws/aws_quantum_simulator.py | 14 ++- src/braket/aws/aws_quantum_task.py | 4 +- .../braket/aws/common_test_utils.py | 56 +++++++++++ test/unit_tests/braket/aws/test_aws_qpu.py | 96 ++++++++++++++----- .../braket/aws/test_aws_quantum_simulator.py | 75 +++++++++++---- 7 files changed, 218 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 67237eda..fa88a896 100644 --- a/README.md +++ b/README.md @@ -253,9 +253,8 @@ Tasks sent to QPUs don't always run right away. For IonQ, jobs are run once ever ## Running a Quantum Algorithm on a Quantum Computer With Amazon Braket, you can run your quantum circuit on a physical quantum computer. -The following example executes the same Bell Pair example described to validate your configuration on a Rigetti quantum computer. When you execute your task, Amazon Braket polls for a result. If it a result is not returned within the default polling time, such as when a QPU is unavailable, a local timeout error is returned. You can always restart the polling by using `task.result()`. +The following example executes the same Bell Pair example described to validate your configuration on a Rigetti quantum computer. -However, to avoid receiving timeout errors, you can include `poll_timeout_seconds` parameter and specify a longer polling time. In this example, `poll_timeout_seconds=86400` sets the polling time to one day (24 hours). ```python import boto3 from braket.circuits import Circuit @@ -271,6 +270,13 @@ task = device.run(bell, s3_folder) 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 `AwsQpu.run` (or `AwsQuantumTask`), 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, s3_folder, poll_timeout_seconds=86400) # 1 day +print(task.result().measurement_counts) +``` + Specify which quantum computer hardware to use by changing the value of the `device_arn` to the value for quantum computer to use: - **IonQ** "arn:aws:aqx:::qpu:ionq" (Available 4:00 PM to 8:00 PM ET M-F) - **Rigetti** "arn:aws:aqx:::qpu:rigetti" (Available 11:00 AM to 1:00 PM ET daily) diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py index dff68c94..78bbbe59 100644 --- a/src/braket/aws/aws_qpu.py +++ b/src/braket/aws/aws_qpu.py @@ -36,6 +36,10 @@ class AwsQpu(Device): AwsQpuArns.DWAVE: ["us-west-2"], } + DEFAULT_SHOTS_QPU = 1000 + DEFAULT_RESULTS_POLL_TIMEOUT_QPU = 432000 + DEFAULT_RESULTS_POLL_INTERVAL_QPU = 1 + def __init__(self, arn: str, aws_session=None): """ Args: @@ -64,7 +68,9 @@ def run( self, task_specification: Union[Circuit, Problem], s3_destination_folder: AwsSession.S3DestinationFolder, - shots: int = 1000, + shots: int = DEFAULT_SHOTS_QPU, + poll_timeout_seconds: int = DEFAULT_RESULTS_POLL_TIMEOUT_QPU, + poll_interval_seconds: int = DEFAULT_RESULTS_POLL_INTERVAL_QPU, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTask: @@ -78,6 +84,10 @@ def run( s3_destination_folder: The S3 location to save the task's results shots (int, optional): The number of times to run the circuit or annealing problem. Default is 1000. + poll_timeout_seconds (int): The polling timeout for AwsQuantumTask.result(), in seconds. + Default: 5 days. + poll_interval_seconds (int): The polling interval for AwsQuantumTask.result(), + in seconds. Default: 1 second. *aws_quantum_task_args: Variable length positional arguments for `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. **aws_quantum_task_kwargs: Variable length keyword arguments for @@ -116,6 +126,8 @@ def run( task_specification, s3_destination_folder, shots, + poll_timeout_seconds=poll_timeout_seconds, + poll_interval_seconds=poll_interval_seconds, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) diff --git a/src/braket/aws/aws_quantum_simulator.py b/src/braket/aws/aws_quantum_simulator.py index 3deee3c5..bf5c5a24 100644 --- a/src/braket/aws/aws_quantum_simulator.py +++ b/src/braket/aws/aws_quantum_simulator.py @@ -27,6 +27,10 @@ class AwsQuantumSimulator(Device): and to run a task on the simulator. """ + DEFAULT_SHOTS_SIMULATOR = 0 + DEFAULT_RESULTS_POLL_TIMEOUT_SIMULATOR = 432000 + DEFAULT_RESULTS_POLL_INTERVAL_SIMULATOR = 1 + def __init__(self, arn: str, aws_session=None): """ Args: @@ -44,7 +48,9 @@ def run( self, task_specification: Union[Circuit, Problem], s3_destination_folder: AwsSession.S3DestinationFolder, - shots: int = 0, + shots: int = DEFAULT_SHOTS_SIMULATOR, + poll_timeout_seconds: int = DEFAULT_RESULTS_POLL_TIMEOUT_SIMULATOR, + poll_interval_seconds: int = DEFAULT_RESULTS_POLL_INTERVAL_SIMULATOR, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTask: @@ -62,6 +68,10 @@ def run( and sampling is not supported. `shots>0` means that the simulator will be treated like a QPU and only support result types available for a QPU. + poll_timeout_seconds (int): The polling timeout for AwsQuantumTask.result(), in seconds. + Default: 432000 (5 days). + poll_interval_seconds (int): The polling interval for AwsQuantumTask.result(), + in seconds. Default: 1. *aws_quantum_task_args: Variable length positional arguments for `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. **aws_quantum_task_kwargs: Variable length keyword arguments for @@ -88,6 +98,8 @@ def run( task_specification, s3_destination_folder, shots, + poll_timeout_seconds=poll_timeout_seconds, + poll_interval_seconds=poll_interval_seconds, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 06224d75..25dcc9cc 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -320,7 +320,9 @@ async def _wait_for_completion( # Timed out self._logger.warning( f"Task {self._arn}: polling for task completion timed out after " - + f"{time.time()-start_time} secs" + + 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 diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 095c32d9..08d0f0c2 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import json +from unittest.mock import Mock from braket.aws.aws_qpu_arns import AwsQpuArns from braket.aws.aws_quantum_simulator_arns import AwsQuantumSimulatorArns @@ -252,3 +253,58 @@ class MockS3: }, } ) + + +def run_and_assert( + aws_quantum_task_mock, + device, + default_shots, + default_timeout, + default_poll_interval, + circuit, + s3_destination_folder, + shots, # Treated as positional arg + poll_timeout_seconds, # Treated as positional arg + poll_interval_seconds, # Treated as positional arg + extra_args, + extra_kwargs, +): + task_mock = Mock() + aws_quantum_task_mock.return_value = task_mock + + run_args = [] + if shots is not None: + run_args.append(shots) + if poll_timeout_seconds is not None: + run_args.append(poll_timeout_seconds) + if poll_interval_seconds is not None: + run_args.append(poll_interval_seconds) + run_args += extra_args if extra_args else [] + + run_kwargs = extra_kwargs or {} + + task = device.run(circuit, s3_destination_folder, *run_args, **run_kwargs) + assert task == task_mock + + create_args = [shots if shots is not None else default_shots] + create_args += extra_args if extra_args else [] + + create_kwargs = extra_kwargs or {} + create_kwargs.update( + { + "poll_timeout_seconds": poll_timeout_seconds + if poll_timeout_seconds is not None + else default_timeout, + "poll_interval_seconds": poll_interval_seconds + if poll_interval_seconds is not None + else default_poll_interval, + } + ) + aws_quantum_task_mock.assert_called_with( + device._aws_session, + device.arn, + circuit, + s3_destination_folder, + *create_args, + **create_kwargs + ) diff --git a/test/unit_tests/braket/aws/test_aws_qpu.py b/test/unit_tests/braket/aws/test_aws_qpu.py index 1378bcb7..a1dae9d1 100644 --- a/test/unit_tests/braket/aws/test_aws_qpu.py +++ b/test/unit_tests/braket/aws/test_aws_qpu.py @@ -17,7 +17,7 @@ import pytest from braket.aws import AwsQpu, AwsQpuArns from braket.circuits import Circuit -from common_test_utils import MockDevices +from common_test_utils import MockDevices, run_and_assert @pytest.fixture @@ -31,7 +31,7 @@ def _qpu(arn): @pytest.fixture def s3_destination_folder(): - return ("bucket-foo", "key-bar") + return "bucket-foo", "key-bar" @pytest.fixture @@ -180,27 +180,60 @@ def test_repr(qpu): assert repr(qpu) == expected +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_no_extra(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): + _run_and_assert( + aws_quantum_task_mock, qpu, circuit, s3_destination_folder, + ) + + @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_positional_args(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): - _run_and_assert(aws_quantum_task_mock, qpu, circuit, s3_destination_folder, 1000, ["foo"], {}) + _run_and_assert( + aws_quantum_task_mock, qpu, circuit, s3_destination_folder, 100, 86400, 0.25, ["foo"] + ) @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_kwargs(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): _run_and_assert( - aws_quantum_task_mock, qpu, circuit, s3_destination_folder, 1000, [], {"bar": 1, "baz": 2} + aws_quantum_task_mock, + qpu, + circuit, + s3_destination_folder, + extra_kwargs={"bar": 1, "baz": 2}, ) @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_kwargs_no_shots(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): - task_mock = Mock() - aws_quantum_task_mock.return_value = task_mock - qpu = qpu(AwsQpuArns.RIGETTI) - task = qpu.run(circuit, s3_destination_folder) - assert task == task_mock - aws_quantum_task_mock.assert_called_with( - qpu._aws_session, qpu.arn, circuit, s3_destination_folder, 1000 +def test_run_with_shots(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): + _run_and_assert(aws_quantum_task_mock, qpu, circuit, s3_destination_folder, 100) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_with_shots_kwargs(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): + _run_and_assert( + aws_quantum_task_mock, + qpu, + circuit, + s3_destination_folder, + 100, + extra_kwargs={"bar": 1, "baz": 2}, + ) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_with_shots_poll_timeout_kwargs( + aws_quantum_task_mock, qpu, circuit, s3_destination_folder +): + _run_and_assert( + aws_quantum_task_mock, + qpu, + circuit, + s3_destination_folder, + 100, + 86400, + extra_kwargs={"bar": 1, "baz": 2}, ) @@ -213,7 +246,9 @@ def test_run_with_positional_args_and_kwargs( qpu, circuit, s3_destination_folder, - 1000, + 100, + 86400, + 0.25, ["foo"], {"bar": 1, "baz": 2}, ) @@ -244,20 +279,29 @@ def test_construct_topology_graph(qpu, properties, expected_edges): def _run_and_assert( - aws_quantum_task_mock, qpu, circuit, s3_destination_folder, shots, run_args, run_kwargs + aws_quantum_task_mock, + qpu_factory, + circuit, + s3_destination_folder, + shots=None, # Treated as positional arg + poll_timeout_seconds=None, # Treated as positional arg + poll_interval_seconds=None, # Treated as positional arg + extra_args=None, + extra_kwargs=None, ): - task_mock = Mock() - aws_quantum_task_mock.return_value = task_mock - - qpu = qpu(AwsQpuArns.RIGETTI) - task = ( - qpu.run(circuit, s3_destination_folder, shots, *run_args, **run_kwargs) - if shots - else qpu.run(circuit, s3_destination_folder, *run_args, **run_kwargs) - ) - assert task == task_mock - aws_quantum_task_mock.assert_called_with( - qpu._aws_session, qpu.arn, circuit, s3_destination_folder, shots, *run_args, **run_kwargs + run_and_assert( + aws_quantum_task_mock, + qpu_factory(AwsQpuArns.RIGETTI), + AwsQpu.DEFAULT_SHOTS_QPU, + AwsQpu.DEFAULT_RESULTS_POLL_TIMEOUT_QPU, + AwsQpu.DEFAULT_RESULTS_POLL_INTERVAL_QPU, + circuit, + s3_destination_folder, + shots, + poll_timeout_seconds, + poll_interval_seconds, + extra_args, + extra_kwargs, ) diff --git a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py index 45859b55..7cab8f48 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py @@ -16,7 +16,7 @@ import pytest from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns from braket.circuits import Circuit -from common_test_utils import MockDevices +from common_test_utils import MockDevices, run_and_assert @pytest.fixture @@ -98,20 +98,50 @@ def test_repr(simulator): @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_positional_args(aws_quantum_task_mock, simulator, circuit, s3_destination_folder): _run_and_assert( - aws_quantum_task_mock, simulator, circuit, s3_destination_folder, 1000, ["foo"], {} + aws_quantum_task_mock, simulator, circuit, s3_destination_folder, 1000, 300, 1, ["foo"] ) @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_kwargs(aws_quantum_task_mock, simulator, circuit, s3_destination_folder): + _run_and_assert( + aws_quantum_task_mock, + simulator, + circuit, + s3_destination_folder, + extra_kwargs={"bar": 1, "baz": 2}, + ) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_with_shots(aws_quantum_task_mock, simulator, circuit, s3_destination_folder): + _run_and_assert(aws_quantum_task_mock, simulator, circuit, s3_destination_folder, 1000) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_with_shots_kwargs(aws_quantum_task_mock, simulator, circuit, s3_destination_folder): _run_and_assert( aws_quantum_task_mock, simulator, circuit, s3_destination_folder, 1000, - [], - {"bar": 1, "baz": 2}, + extra_kwargs={"bar": 1, "baz": 2}, + ) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_with_shots_poll_timeout_kwargs( + aws_quantum_task_mock, simulator, circuit, s3_destination_folder +): + _run_and_assert( + aws_quantum_task_mock, + simulator, + circuit, + s3_destination_folder, + 1000, + 300, + extra_kwargs={"bar": 1, "baz": 2}, ) @@ -125,30 +155,35 @@ def test_run_with_positional_args_and_kwargs( circuit, s3_destination_folder, 1000, + 300, + 1, ["foo"], {"bar": 1, "baz": 2}, ) def _run_and_assert( - aws_quantum_task_mock, simulator, circuit, s3_destination_folder, shots, run_args, run_kwargs + aws_quantum_task_mock, + simulator_factory, + circuit, + s3_destination_folder, + shots=None, # Treated as positional arg + poll_timeout_seconds=None, # Treated as positional arg + poll_interval_seconds=None, # Treated as positional arg + extra_args=None, + extra_kwargs=None, ): - task_mock = Mock() - aws_quantum_task_mock.return_value = task_mock - - simulator = simulator(AwsQuantumSimulatorArns.QS1) - task = ( - simulator.run(circuit, s3_destination_folder, shots, *run_args, **run_kwargs) - if shots - else simulator.run(circuit, s3_destination_folder, *run_args, **run_kwargs) - ) - assert task == task_mock - aws_quantum_task_mock.assert_called_with( - simulator._aws_session, - simulator.arn, + run_and_assert( + aws_quantum_task_mock, + simulator_factory(AwsQuantumSimulatorArns.QS1), + AwsQuantumSimulator.DEFAULT_SHOTS_SIMULATOR, + AwsQuantumSimulator.DEFAULT_RESULTS_POLL_TIMEOUT_SIMULATOR, + AwsQuantumSimulator.DEFAULT_RESULTS_POLL_INTERVAL_SIMULATOR, circuit, s3_destination_folder, shots, - *run_args, - **run_kwargs + poll_timeout_seconds, + poll_interval_seconds, + extra_args, + extra_kwargs, ) From 3e132a30c73ed0543f54e6640037f414edf8c21d Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 3 Jun 2020 14:02:22 -0700 Subject: [PATCH 0112/1165] Correct Ising coupling gates (#92) Used reference https://arxiv.org/abs/1707.06356. Also added test case for unitary. --- src/braket/circuits/gates.py | 41 +++++++++++-------- test/unit_tests/braket/circuits/test_gates.py | 7 ++++ 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index bfc1c8cd..ffb282f7 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.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 math from typing import Iterable import braket.ir.jaqcd as ir @@ -738,6 +737,8 @@ def pswap(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction class XY(AngledGate): """XY gate. + Reference: https://arxiv.org/abs/1912.04424v1 + Args: angle (float): angle in radians. """ @@ -1023,6 +1024,8 @@ def cz(control: QubitInput, target: QubitInput) -> Instruction: class XX(AngledGate): """Ising XX coupling gate. + Reference: https://arxiv.org/abs/1707.06356 + Args: angle (float): angle in radians. """ @@ -1038,12 +1041,14 @@ def to_ir(self, target: QubitSet): return ir.XX.construct(targets=[target[0], target[1]], angle=self.angle) def to_matrix(self) -> np.ndarray: - return (1 / math.sqrt(2)) * np.array( + cos = np.cos(self.angle / 2) + isin = 1.0j * np.sin(self.angle / 2) + return np.array( [ - [1.0, 0.0, 0.0, -1.0j * np.exp(1.0j * self.angle)], - [0.0, 1.0, -1.0j, 0.0], - [0.0, -1.0j, 1.0, 0.0], - [-1.0j * np.exp(-1.0j * self.angle), 0.0, 0.0, 1.0], + [cos, 0.0, 0.0, -isin], + [0.0, cos, -isin, 0.0], + [0.0, -isin, cos, 0.0], + [-isin, 0.0, 0.0, cos], ], dtype=complex, ) @@ -1073,6 +1078,8 @@ def xx(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: class YY(AngledGate): """Ising YY coupling gate. + Reference: https://arxiv.org/abs/1707.06356 + Args: angle (float): angle in radians. """ @@ -1088,14 +1095,14 @@ def to_ir(self, target: QubitSet): return ir.YY.construct(targets=[target[0], target[1]], angle=self.angle) def to_matrix(self) -> np.ndarray: - cos = np.cos(self.angle) - sin = np.sin(self.angle) + cos = np.cos(self.angle / 2) + isin = 1.0j * np.sin(self.angle / 2) return np.array( [ - [cos, 0.0, 0.0, 1.0j * sin], - [0.0, cos, -1.0j * sin, 0.0], - [0.0, -1.0j * sin, cos, 0.0], - [1.0j * sin, 0.0, 0.0, cos], + [cos, 0.0, 0.0, isin], + [0.0, cos, -isin, 0.0], + [0.0, -isin, cos, 0.0], + [isin, 0.0, 0.0, cos], ], dtype=complex, ) @@ -1125,6 +1132,8 @@ def yy(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: class ZZ(AngledGate): """Ising ZZ coupling gate. + Reference: https://arxiv.org/abs/1707.06356 + Args: angle (float): angle in radians. """ @@ -1142,10 +1151,10 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.array( [ - [np.exp(1j * (self.angle / 2)), 0.0, 0.0, 0.0], - [0.0, np.exp(-1j * (self.angle / 2)), 0.0, 0.0], - [0.0, 0.0, np.exp(-1j * (self.angle / 2)), 0.0], - [0.0, 0.0, 0.0, np.exp(1j * (self.angle / 2))], + [np.exp(-1j * (self.angle / 2)), 0.0, 0.0, 0.0], + [0.0, np.exp(1j * (self.angle / 2)), 0.0, 0.0], + [0.0, 0.0, np.exp(1j * (self.angle / 2)), 0.0], + [0.0, 0.0, 0.0, np.exp(-1j * (self.angle / 2))], ], dtype=complex, ) diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 10469d16..04cc945d 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -302,3 +302,10 @@ def test_gate_to_matrix(testclass, subroutine_name, irclass, irsubclasses, kwarg @pytest.mark.parametrize("matrix", invalid_unitary_matrices) def test_unitary_invalid_matrix(matrix): Gate.Unitary(matrix=matrix) + + +@pytest.mark.xfail(raises=ValueError) +def test_unitary_matrix_target_size_mismatch(): + Circuit().unitary( + matrix=np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]), targets=[0] + ) From 51f14ff458136a6ab71b49b30fff7f603b4e221b Mon Sep 17 00:00:00 2001 From: randalld-aws <35579021+randalld-aws@users.noreply.github.com> Date: Wed, 3 Jun 2020 14:58:43 -0700 Subject: [PATCH 0113/1165] doc updates for result_types (#91) * doc updates for result_types * Minor corrections to wording. * inadvertent paste error --- src/braket/circuits/observables.py | 17 +++--- src/braket/circuits/result_types.py | 59 ++++++++++--------- .../tasks/gate_model_quantum_task_result.py | 10 ++-- 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 8ddd0ebd..a129fa6e 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -171,10 +171,11 @@ def __init__(self, observables: List[Observable]): >>> t2.factors (Z('qubit_count': 1), Y('qubit_count': 1), X('qubit_count': 1)) - Note: list of observables for tensor product must be given in the desired order that - the tensor product will be calculated. For `TensorProduct(observables=[ob1, ob2, ob3])`, - the tensor product's matrix will be the result of the tensor product of `ob1`, `ob2`, - `ob3`, or `np.kron(np.kron(ob1.to_matrix(), ob2.to_matrix()), ob3.to_matrix())` + Note: You must provide the list of observables for the tensor product to be evaluated + in the order that you want the tensor product to be calculated. + For `TensorProduct(observables=[ob1, ob2, ob3])`, the tensor product's matrix is the + result of the tensor product of `ob1`, `ob2`, `ob3`, or `np.kron(np.kron(ob1.to_matrix(), + ob2.to_matrix()), ob3.to_matrix())`. """ self._observables = tuple(observables) qubit_count = sum([obs.qubit_count for obs in observables]) @@ -190,7 +191,7 @@ def to_ir(self) -> List[str]: @property def factors(self) -> Tuple[Observable]: - """ Tuple[Observable]: The observables that make up this tensor product""" + """ Tuple[Observable]: The observables that comprise this tensor product.""" return self._observables def to_matrix(self) -> np.ndarray: @@ -262,13 +263,13 @@ class Hermitian(Observable): def __init__(self, matrix: np.ndarray, display_name: str = "Hermitian"): """ Args: - matrix (numpy.ndarray): Hermitian matrix which defines the observable. - display_name (str): Name to be used for an instance of this Hermitian matrix + matrix (numpy.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`. Raises: ValueError: If `matrix` is not a two-dimensional square matrix, - or has a dimension length which is not a positive exponent of 2, + or has a dimension length that is not a positive exponent of 2, or is non-hermitian. Example: diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 320f89d3..578fe429 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -27,7 +27,7 @@ To add a new result type: 1. Implement the class and extend `ResultType` 2. Add a method with the `@circuit.subroutine(register=True)` decorator. Method name - will be added into the `Circuit` class. This method is the default way + is added into the `Circuit` class. This method is the default way clients add this result type to a circuit. 3. Register the class with the `ResultType` class via `ResultType.register_result_type()`. """ @@ -36,7 +36,7 @@ class StateVector(ResultType): """ The full state vector as a requested result type. - This is only available when `shots=0` for simulators. + This is available on simulators only when `shots=0`. """ def __init__(self): @@ -72,8 +72,8 @@ def __copy__(self) -> StateVector: class Amplitude(ResultType): """ - The amplitude of specified quantum states as a requested result type. - This is only available when `shots=0` for simulators. + 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]): @@ -137,17 +137,18 @@ def __copy__(self): class Probability(ResultType): """Probability in the computational basis as the requested result type. - It can be the probability of all states if no targets are specified or the marginal probability - of a restricted set of states if only a subset of all qubits are specified as target. + It can be the probability of all states if no targets are specified, or the marginal + probability of a restricted set of states if only a subset of all qubits are specified as + targets. For `shots>0`, this is calculated by measurements. For `shots=0`, this is supported - only by simulators and represents the exact result. + only on simulators and represents the exact result. """ def __init__(self, target: QubitSetInput = None): """ Args: - target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + target (int, Qubit, or iterable of int / Qubit, optional): The target qubits that the result type is requested for. Default is None, which means all qubits for the circuit. @@ -178,7 +179,7 @@ def probability(target: QubitSetInput = None) -> ResultType: """Registers this function into the circuit class. Args: - target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + target (int, Qubit, or iterable of int / Qubit, optional): The target qubits that the result type is requested for. Default is None, which means all qubits for the circuit. @@ -206,10 +207,10 @@ def __copy__(self) -> Probability: class Expectation(ObservableResultType): - """Expectation of specified target qubit set and observable as the requested result type. + """Expectation of the specified target qubit set and observable as the requested result type. - 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 + If no targets are specified, the observable must operate only on 1 qubit and it + is 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. For `shots>0`, this is calculated by measurements. For `shots=0`, this is supported @@ -224,11 +225,11 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): observable (Observable): the observable for the result type target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the result type is requested for. Default is None, which means the observable must - only operate on 1 qubit and it will be applied to all qubits in parallel + operate only on 1 qubit and it is applied to all qubits in parallel. Raises: - ValueError: If the observable's qubit count and the number of target qubits - are not equal. Or, if target=None and the observable's qubit count is not 1. + 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) @@ -259,7 +260,7 @@ def expectation(observable: Observable, target: QubitSetInput = None) -> ResultT observable (Observable): the observable for the result type target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the result type is requested for. Default is None, which means the observable must - only operate on 1 qubit and it will be applied to all qubits in parallel + operate only on 1 qubit and it is applied to all qubits in parallel. Returns: ResultType: expectation as a requested result type @@ -276,9 +277,9 @@ def expectation(observable: Observable, target: QubitSetInput = None) -> ResultT class Sample(ObservableResultType): """Sample of specified target qubit set and observable as the requested result type. - 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. + If no targets are specified, the observable must operate only on 1 qubit and it + is applied to all qubits in parallel. Otherwise, the number of specified targets + must equal the number of qubits the observable can be applied to. This is only available for `shots>0`. @@ -291,11 +292,11 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): observable (Observable): the observable for the result type target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the result type is requested for. Default is None, which means the observable must - only operate on 1 qubit and it will be applied to all qubits in parallel + operate only on 1 qubit and it is applied to all qubits in parallel. Raises: - ValueError: If the observable's qubit count and the number of target qubits - are not equal. Or, if target=None and the observable's qubit count is not 1. + 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) @@ -326,7 +327,7 @@ def sample(observable: Observable, target: QubitSetInput = None) -> ResultType: observable (Observable): the observable for the result type target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the result type is requested for. Default is None, which means the observable must - only operate on 1 qubit and it will be applied to all qubits in parallel + operate only on 1 qubit and it is applied to all qubits in parallel. Returns: ResultType: sample as a requested result type @@ -343,9 +344,9 @@ def sample(observable: Observable, target: QubitSetInput = None) -> ResultType: class Variance(ObservableResultType): """Variance of specified target qubit set and observable as the requested result type. - 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. + If no targets are specified, the observable must operate only on 1 qubit and it + is applied to all qubits in parallel. Otherwise, the number of targets specified + must equal the number of qubits that the observable can be applied to. For `shots>0`, this is calculated by measurements. For `shots=0`, this is supported only by simulators and represents the exact result. @@ -359,11 +360,11 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): observable (Observable): the observable for the result type target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the result type is requested for. Default is None, which means the observable must - only operate on 1 qubit and it will be applied to all qubits in parallel + operate only on 1 qubit and it is applied to all qubits in parallel. Raises: - ValueError: If the observable's qubit count and the number of target qubits - are not equal. Or, if target=None and the observable's qubit count is not 1. + 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) diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 598370f3..09d5405f 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -31,7 +31,7 @@ class GateModelQuantumTaskResult: to be initialized by a QuantumTask class. Args: - task_metadata (Dict[str, Any]): Dictionary of task metadata. task_metadata must have + task_metadata (Dict[str, Any]): Dictionary of task metadata. `task_metadata` must have keys 'Id', 'Shots', 'Ir', and 'IrType'. result_types (List[Dict[str, Any]]): List of dictionaries where each dictionary has two keys: 'Type' (the result type in IR JSON form) and @@ -43,7 +43,7 @@ class GateModelQuantumTaskResult: This can be an empty list if no result types are specified in the IR. This is calculated from `measurements` and the IR of the circuit program when `shots>0`. - measurements (numpy.ndarray, optional): 2d array - row is shot, column is qubit. + measurements (numpy.ndarray, optional): 2d array - row is shot and column is qubit. Default is None. Only available when shots > 0. The qubits in `measurements` are the ones in `GateModelQuantumTaskResult.measured_qubits`. measured_qubits (List[int], optional): The indices of the measured qubits. Default @@ -92,8 +92,8 @@ def get_value_by_result_type(self, result_type: ResultType) -> Any: Any: value of the result corresponding to the result type Raises: - ValueError: If result type not found in result. - Result types must be added to circuit before circuit is run on device. + ValueError: If result type is not found in result. + Result types must be added to the circuit before the circuit is run on a device. """ rt_json = result_type.to_ir().json() for rt in self.result_types: @@ -115,7 +115,7 @@ def measurement_counts_from_measurements(measurements: np.ndarray) -> Counter: Creates measurement counts from measurements Args: - measurements (numpy.ndarray): 2d array - row is shot, column is qubit. + measurements (numpy.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 From c5a1435963b1b9ad6c8683d6b43ef097cc676062 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Fri, 5 Jun 2020 12:26:23 -0700 Subject: [PATCH 0114/1165] Add integ tests for result types for local and aws simulators and example script (#93) --- examples/bell.py | 13 + examples/bell_result_types.py | 62 +++ examples/debug_bell.py | 13 + examples/local_bell.py | 13 + src/braket/circuits/circuit.py | 34 +- src/braket/circuits/observables.py | 12 +- src/braket/circuits/result_types.py | 12 +- test/integ_tests/conftest.py | 16 - .../gate_model_device_testing_utils.py | 401 ++++++++++++++++++ test/integ_tests/simulator_assert_utils.py | 26 -- .../test_local_braket_simulator.py | 147 +++---- .../test_simulator_quantum_task.py | 130 +++++- .../braket/circuits/test_circuit.py | 89 +++- .../braket/circuits/test_observables.py | 7 + .../test_gate_model_quantum_task_result.py | 15 +- 15 files changed, 823 insertions(+), 167 deletions(-) create mode 100644 examples/bell_result_types.py create mode 100644 test/integ_tests/gate_model_device_testing_utils.py delete mode 100644 test/integ_tests/simulator_assert_utils.py diff --git a/examples/bell.py b/examples/bell.py index 5adf6cb9..14348173 100644 --- a/examples/bell.py +++ b/examples/bell.py @@ -1,3 +1,16 @@ +# Copyright 2019-2019 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 boto3 from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns from braket.circuits import Circuit diff --git a/examples/bell_result_types.py b/examples/bell_result_types.py new file mode 100644 index 00000000..6f329c8b --- /dev/null +++ b/examples/bell_result_types.py @@ -0,0 +1,62 @@ +# Copyright 2019-2019 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, Observable +from braket.devices import LocalSimulator + +device = LocalSimulator() + +print("Example for shots=0") +# Result types can be requested in the circuit +# Example of result types for shots=0 +bell = ( + Circuit() + .h(0) + .cnot(0, 1) + .probability(target=[0]) + .expectation(observable=Observable.Z(), target=[1]) + .amplitude(state="00") + .state_vector() +) + +# State vector and amplitude can only be requested when shots=0 for a simulator +# When shots=0 for a simulator, probability, expectation, variance are the exact values, +# not calculated from measurements +# Users cannot request Sample as a result when shots=0 +result = device.run(bell).result() +print("Marginal probability for target 0 in computational basis:", result.values[0]) +print("Expectation of target 1 in the computational basis:", result.values[1]) +print("Amplitude of state 00:", result.values[2]) +print("State vector:", result.values[3]) + +print("\nExample for shots>0") +# Example of result types for shots > 0 +bell = ( + Circuit() + .h(0) + .cnot(0, 1) + .probability() + .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]) +) + +# When shots>0 for a simulator, probability, expectation, variance are calculated from measurements +# Users can request sample as a result when shots > 0 +result = device.run(bell, shots=100).result() +print("Probability of all states in computational basis:", result.values[0]) +print("Expectation of target 0, 1 in the basis of Pauli-Y @ Pauli-X:", result.values[1]) +print("Variance of target 0, 1 in the basis of Pauli-Y @ Pauli-X:", result.values[2]) +print("Samples of target 0, 1 in the basis of Pauli-Y @ Pauli-X:", result.values[3]) + +# Probability, sample, expectation, and variance are also supported for QPU devices diff --git a/examples/debug_bell.py b/examples/debug_bell.py index d026aeb2..1660515a 100644 --- a/examples/debug_bell.py +++ b/examples/debug_bell.py @@ -1,3 +1,16 @@ +# Copyright 2019-2019 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 logging import sys diff --git a/examples/local_bell.py b/examples/local_bell.py index f46b4815..c4eb611f 100644 --- a/examples/local_bell.py +++ b/examples/local_bell.py @@ -1,3 +1,16 @@ +# Copyright 2019-2019 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.devices import LocalSimulator diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index d08f5722..371a707d 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -48,7 +48,6 @@ class Circuit: """ _ALL_QUBITS = "ALL" # Flag to indicate all qubits in _qubit_observable_mapping - _EXISTING_QUBITS = "EXISTING" # Flag to indicate existing qubits in _qubit_observable_mapping @classmethod def register_subroutine(cls, func: SubroutineCallable) -> None: @@ -147,17 +146,20 @@ def basis_rotation_instructions(self) -> List[Instruction]: if isinstance(result_type, ObservableResultType) ) + added_observables_targets = set() for return_type in observable_return_types: - target: List[int] = return_type.target observable: Observable = return_type.observable - if not target: - # There will be only one result type in observable_return_types, - # and its observable acts on all qubits - for target in self._moments.qubits: - basis_rotation_instructions += Circuit._observable_to_instruction( - observable, target - ) - else: + targets: List[List[int]] = [return_type.target] if return_type.target else [ + QubitSet(qubit) for qubit in self._moments.qubits + ] + + for target in targets: + # only add gates for observables and targets that + # have not been processed + str_observables_target = f"{observable}; {target}" + if str_observables_target in added_observables_targets: + continue + added_observables_targets.add(str_observables_target) basis_rotation_instructions += Circuit._observable_to_instruction( observable, target ) @@ -267,7 +269,8 @@ def _add_to_qubit_observable_mapping(self, result_type: ResultType) -> None: observable = result_type.observable else: return - targets = result_type.target if result_type.target else Circuit._EXISTING_QUBITS + + targets = result_type.target or self._qubit_observable_mapping.keys() all_qubits_observable = self._qubit_observable_mapping.get(Circuit._ALL_QUBITS) for target in targets: @@ -275,10 +278,13 @@ def _add_to_qubit_observable_mapping(self, result_type: ResultType) -> None: if current_observable and current_observable != observable: raise ValueError( f"Existing result type for observable {current_observable} for target {target}" - + f"conflicts with observable {observable} for new result type" + f" conflicts with observable {observable} for new result type" ) - self._qubit_observable_mapping[target] = observable - self._qubit_observable_mapping[Circuit._EXISTING_QUBITS] = observable + if result_type.target: + self._qubit_observable_mapping[target] = observable + + if not result_type.target: + self._qubit_observable_mapping[Circuit._ALL_QUBITS] = observable def add_instruction( self, diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index a129fa6e..02f813d4 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -171,10 +171,10 @@ def __init__(self, observables: List[Observable]): >>> t2.factors (Z('qubit_count': 1), Y('qubit_count': 1), X('qubit_count': 1)) - Note: You must provide the list of observables for the tensor product to be evaluated - in the order that you want the tensor product to be calculated. - For `TensorProduct(observables=[ob1, ob2, ob3])`, the tensor product's matrix is the - result of the tensor product of `ob1`, `ob2`, `ob3`, or `np.kron(np.kron(ob1.to_matrix(), + Note: You must provide the list of observables for the tensor product to be evaluated + in the order that you want the tensor product to be calculated. + For `TensorProduct(observables=[ob1, ob2, ob3])`, the tensor product's matrix is the + result of the tensor product of `ob1`, `ob2`, `ob3`, or `np.kron(np.kron(ob1.to_matrix(), ob2.to_matrix()), ob3.to_matrix())`. """ self._observables = tuple(observables) @@ -329,6 +329,10 @@ def _get_eigendecomposition(self) -> Dict[str, np.ndarray]: } return Hermitian._eigenpairs[mat_key] + def __repr__(self): + matrix_str = np.array2string(self.to_matrix()).replace("\n", ",") + return f"{self.name}('qubit_count': {self.qubit_count}, 'matrix': {matrix_str})" + Observable.register_observable(Hermitian) diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 578fe429..6e8556f4 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -137,8 +137,8 @@ def __copy__(self): class Probability(ResultType): """Probability in the computational basis as the requested result type. - It can be the probability of all states if no targets are specified, or the marginal - probability of a restricted set of states if only a subset of all qubits are specified as + It can be the probability of all states if no targets are specified, or the marginal + probability of a restricted set of states if only a subset of all qubits are specified as targets. For `shots>0`, this is calculated by measurements. For `shots=0`, this is supported @@ -228,7 +228,7 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): 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 + 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: @@ -295,7 +295,7 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): 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 + 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: @@ -345,7 +345,7 @@ class Variance(ObservableResultType): """Variance of specified target qubit set and observable as the requested result type. If no targets are specified, the observable must operate only on 1 qubit and it - is applied to all qubits in parallel. Otherwise, the number of targets specified + is applied to all qubits in parallel. Otherwise, the number of targets specified must equal the number of qubits that the observable can be applied to. For `shots>0`, this is calculated by measurements. For `shots=0`, this is supported @@ -363,7 +363,7 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): 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 + 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: diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py index 8acc6498..ab3515e7 100644 --- a/test/integ_tests/conftest.py +++ b/test/integ_tests/conftest.py @@ -17,7 +17,6 @@ import pytest from botocore.exceptions import ClientError from braket.aws.aws_session import AwsSession -from braket.circuits import Circuit @pytest.fixture(scope="session") @@ -71,18 +70,3 @@ 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 bell_state_and_tolerances(): - return Circuit().h(0).cnot(0, 1), {"00": (0.4, 0.6), "11": (0.4, 0.6)} - - -@pytest.fixture(scope="session") -def state_110_and_most_common(): - return Circuit().x(0).x(1).i(2), "110" - - -@pytest.fixture(scope="session") -def state_001_and_most_common(): - return Circuit().i(0).i(1).x(2), "001" diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py new file mode 100644 index 00000000..7500b7a1 --- /dev/null +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -0,0 +1,401 @@ +# Copyright 2019-2019 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 typing import Any, Dict + +import numpy as np +from braket.circuits import Circuit, Observable, ResultType +from braket.circuits.quantum_operator_helpers import get_pauli_eigenvalues +from braket.devices import Device +from braket.tasks import GateModelQuantumTaskResult + + +def get_tol(shots: int) -> Dict[str, float]: + if shots: + return {"atol": 0.05, "rtol": 0.1} + else: + return {"atol": 0.01, "rtol": 0} + + +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() + assert result.measurement_counts.most_common(1)[0][0] == "110" + + # |001> should get back value of "001" + state_001 = Circuit().i(0).i(1).x(2) + result = device.run(state_001, **run_kwargs).result() + assert result.measurement_counts.most_common(1)[0][0] == "001" + + +def no_result_types_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) + result = device.run(bell, **run_kwargs).result() + + assert np.allclose(result.measurement_probabilities["00"], 0.5, **tol) + assert np.allclose(result.measurement_probabilities["11"], 0.5, **tol) + assert len(result.measurements) == shots + + +def result_types_zero_shots_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): + circuit = ( + Circuit() + .h(0) + .cnot(0, 1) + .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + .state_vector() + .amplitude(["01", "10", "00", "11"]) + ) + result = device.run(circuit, **run_kwargs).result() + assert len(result.result_types) == 3 + assert np.allclose( + result.get_value_by_result_type( + ResultType.Expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ), + 1 / np.sqrt(2), + ) + assert np.allclose( + result.get_value_by_result_type(ResultType.StateVector()), + np.array([1, 0, 0, 1]) / np.sqrt(2), + ) + assert result.get_value_by_result_type(ResultType.Amplitude(["01", "10", "00", "11"])) == { + "01": 0j, + "10": 0j, + "00": (1 / np.sqrt(2)), + "11": (1 / np.sqrt(2)), + } + + +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() + result = device.run(circuit, **run_kwargs).result() + assert len(result.result_types) == 1 + assert np.allclose( + result.get_value_by_result_type(ResultType.Probability()), np.array([0.5, 0, 0, 0.5]), **tol + ) + + +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) + result = device.run(circuit, **run_kwargs).result() + assert len(result.result_types) == 1 + assert np.allclose( + result.get_value_by_result_type(ResultType.Probability(target=0)), + np.array([0.5, 0.5]), + **tol + ) + + +def result_types_nonzero_shots_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): + circuit = ( + Circuit() + .h(0) + .cnot(0, 1) + .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + .sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ) + result = device.run(circuit, **run_kwargs).result() + assert len(result.result_types) == 2 + assert ( + 0.6 + < result.get_value_by_result_type( + ResultType.Expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ) + < 0.8 + ) + assert ( + len( + result.get_value_by_result_type( + ResultType.Sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ) + ) + == run_kwargs["shots"] + ) + + +def result_types_hermitian_testing(device: Device, run_kwargs: Dict[str, Any]): + shots = run_kwargs["shots"] + theta = 0.543 + array = np.array([[1, 2j], [-2j, 0]]) + + circuit = ( + Circuit() + .rx(0, theta) + .variance(Observable.Hermitian(array), 0) + .expectation(Observable.Hermitian(array), 0) + ) + if shots: + circuit.add_result_type(ResultType.Sample(Observable.Hermitian(array), 0)) + result = device.run(circuit, **run_kwargs).result() + + expected_mean = 2 * np.sin(theta) + 0.5 * np.cos(theta) + 0.5 + expected_var = 0.25 * (np.sin(theta) - 4 * np.cos(theta)) ** 2 + expected_eigs = np.linalg.eigvalsh(array) + assert_variance_expectation_sample_result( + result, shots, expected_var, expected_mean, expected_eigs + ) + + +def result_types_all_selected_testing(device: Device, run_kwargs: Dict[str, Any]): + shots = run_kwargs["shots"] + theta = 0.543 + array = np.array([[1, 2j], [-2j, 0]]) + + circuit = ( + Circuit() + .rx(0, theta) + .rx(1, theta) + .variance(Observable.Hermitian(array)) + .expectation(Observable.Hermitian(array), 0) + ) + if shots: + circuit.add_result_type(ResultType.Sample(Observable.Hermitian(array), 1)) + result = device.run(circuit, **run_kwargs).result() + + expected_mean = 2 * np.sin(theta) + 0.5 * np.cos(theta) + 0.5 + var = 0.25 * (np.sin(theta) - 4 * np.cos(theta)) ** 2 + expected_var = [var, var] + expected_eigs = np.linalg.eigvalsh(array) + assert_variance_expectation_sample_result( + result, shots, expected_var, expected_mean, expected_eigs + ) + + +def get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, shots) -> Circuit: + circuit = ( + Circuit() + .rx(0, theta) + .rx(1, phi) + .rx(2, varphi) + .cnot(0, 1) + .cnot(1, 2) + .variance(obs, obs_targets) + .expectation(obs, obs_targets) + ) + if shots: + circuit.add_result_type(ResultType.Sample(obs, obs_targets)) + return circuit + + +def assert_variance_expectation_sample_result( + result: GateModelQuantumTaskResult, + shots: int, + expected_var: float, + expected_mean: float, + expected_eigs: np.ndarray, +): + tol = get_tol(shots) + variance = result.values[0] + expectation = result.values[1] + if shots: + samples = result.values[2] + assert np.allclose(sorted(list(set(samples))), sorted(expected_eigs), **tol) + assert np.allclose(np.mean(samples), expected_mean, **tol) + assert np.allclose(np.var(samples), expected_var, **tol) + assert np.allclose(expectation, expected_mean, **tol) + assert np.allclose(variance, expected_var, **tol) + + +def result_types_tensor_x_y_testing(device: Device, run_kwargs: Dict[str, Any]): + shots = run_kwargs["shots"] + theta = 0.432 + phi = 0.123 + varphi = -0.543 + obs = Observable.X() @ Observable.Y() + obs_targets = [0, 2] + circuit = get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, shots) + result = device.run(circuit, **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 + ) + + +def result_types_tensor_z_z_testing(device: Device, run_kwargs: Dict[str, Any]): + shots = run_kwargs["shots"] + theta = 0.432 + phi = 0.123 + varphi = -0.543 + obs = Observable.Z() @ Observable.Z() + obs_targets = [0, 2] + circuit = get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, shots) + result = device.run(circuit, **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 + ) + + +def result_types_tensor_hermitian_hermitian_testing(device: Device, run_kwargs: Dict[str, Any]): + shots = run_kwargs["shots"] + theta = 0.432 + 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], + ] + ) + 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) + result = device.run(circuit, **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 + ) + + +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 + varphi = -0.543 + obs = Observable.Z() @ Observable.H() @ Observable.Y() + obs_targets = [0, 1, 2] + circuit = get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, shots) + + result = device.run(circuit, **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 + ) + + +def result_types_tensor_z_hermitian_testing(device: Device, run_kwargs: Dict[str, Any]): + shots = run_kwargs["shots"] + 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], + ] + ) + 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) + + result = device.run(circuit, **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 + ) + + +def result_types_tensor_y_hermitian_testing(device: Device, run_kwargs: Dict[str, Any]): + shots = run_kwargs["shots"] + 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], + ] + ) + 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) + + result = device.run(circuit, **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 + ) diff --git a/test/integ_tests/simulator_assert_utils.py b/test/integ_tests/simulator_assert_utils.py deleted file mode 100644 index 98efa845..00000000 --- a/test/integ_tests/simulator_assert_utils.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2019-2019 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 typing import Counter, Dict, Tuple - - -def assert_measurement_probabilities( - probabilities: Dict[str, float], tolerances: Dict[str, Tuple[float, float]] -): - for bitstring in probabilities: - tolerance = tolerances[bitstring] - assert tolerance[0] < probabilities[bitstring] < tolerance[1] - - -def assert_measurement_counts_most_common(measurement_counts: Counter, bitstring: str): - assert measurement_counts.most_common(1)[0][0] == bitstring diff --git a/test/integ_tests/test_local_braket_simulator.py b/test/integ_tests/test_local_braket_simulator.py index 482287d5..0e16face 100644 --- a/test/integ_tests/test_local_braket_simulator.py +++ b/test/integ_tests/test_local_braket_simulator.py @@ -11,85 +11,90 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -import numpy as np -from braket.circuits import Circuit, Observable -from braket.circuits.result_types import Expectation, Sample, StateVector +import pytest from braket.devices import LocalSimulator -from simulator_assert_utils import ( - assert_measurement_counts_most_common, - assert_measurement_probabilities, +from gate_model_device_testing_utils import ( + no_result_types_bell_pair_testing, + qubit_ordering_testing, + result_types_all_selected_testing, + result_types_bell_pair_full_probability_testing, + result_types_bell_pair_marginal_probability_testing, + result_types_hermitian_testing, + result_types_nonzero_shots_bell_pair_testing, + result_types_tensor_hermitian_hermitian_testing, + result_types_tensor_x_y_testing, + result_types_tensor_y_hermitian_testing, + result_types_tensor_z_h_y_testing, + result_types_tensor_z_hermitian_testing, + result_types_tensor_z_z_testing, + result_types_zero_shots_bell_pair_testing, ) DEVICE = LocalSimulator() -SHOTS = 750 +SHOTS = 8000 -def test_bell_pair(bell_state_and_tolerances): - result = DEVICE.run(bell_state_and_tolerances[0], shots=SHOTS).result() - assert_measurement_probabilities(result.measurement_probabilities, bell_state_and_tolerances[1]) - assert len(result.measurements) == SHOTS +def test_no_result_types_bell_pair(): + no_result_types_bell_pair_testing(DEVICE, {"shots": SHOTS}) -def test_qubit_ordering(state_110_and_most_common, state_001_and_most_common): - # |110> should get back value of "110" - result = DEVICE.run(state_110_and_most_common[0], shots=SHOTS).result() - assert_measurement_counts_most_common(result.measurement_counts, state_110_and_most_common[1]) - - # |001> should get back value of "001" - result = DEVICE.run(state_001_and_most_common[0], shots=SHOTS).result() - assert_measurement_counts_most_common(result.measurement_counts, state_001_and_most_common[1]) +def test_qubit_ordering(): + qubit_ordering_testing(DEVICE, {"shots": SHOTS}) def test_result_types_no_shots(): - circuit = ( - Circuit() - .h(0) - .cnot(0, 1) - .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) - .state_vector() - ) - result = DEVICE.run(circuit).result() - assert len(result.result_types) == 2 - assert np.allclose( - result.get_value_by_result_type( - Expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) - ), - 1 / np.sqrt(2), - ) - assert np.allclose( - result.get_value_by_result_type(StateVector()), np.array([1, 0, 0, 1]) / np.sqrt(2) - ) - - -def test_result_types_nonzero_shots_expectation(): - circuit_expectation = ( - Circuit() - .h(0) - .cnot(0, 1) - .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) - ) - result_expectation = DEVICE.run(circuit_expectation, shots=SHOTS).result() - assert len(result_expectation.result_types) == 1 - assert ( - 0.6 - < result_expectation.get_value_by_result_type( - Expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) - ) - < 0.8 - ) - - -def test_result_types_nonzero_shots_sample(): - circuit_sample = ( - Circuit().h(0).cnot(0, 1).sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) - ) - result_sample = DEVICE.run(circuit_sample, shots=SHOTS).result() - assert len(result_sample.result_types) == 1 - assert ( - len( - result_sample.get_value_by_result_type( - Sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) - ) - ) - == SHOTS - ) + result_types_zero_shots_bell_pair_testing(DEVICE, {"shots": 0}) + + +def test_result_types_nonzero_shots_bell_pair(): + result_types_nonzero_shots_bell_pair_testing(DEVICE, {"shots": SHOTS}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_bell_pair_full_probability(shots): + result_types_bell_pair_full_probability_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_bell_pair_marginal_probability(shots): + result_types_bell_pair_marginal_probability_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_hermitian(shots): + result_types_hermitian_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_tensor_x_y(shots): + result_types_tensor_x_y_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_tensor_z_z(shots): + result_types_tensor_z_z_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_tensor_z_h_y(shots): + result_types_tensor_z_h_y_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_tensor_z_hermitian(shots): + result_types_tensor_z_hermitian_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_tensor_hermitian_hermitian(shots): + result_types_tensor_hermitian_hermitian_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_tensor_y_hermitian(shots): + result_types_tensor_y_hermitian_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_all_selected(shots): + result_types_all_selected_testing(DEVICE, {"shots": shots}) diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 1500baea..4e84d88b 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -13,36 +13,126 @@ import pytest from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns -from simulator_assert_utils import ( - assert_measurement_counts_most_common, - assert_measurement_probabilities, +from gate_model_device_testing_utils import ( + no_result_types_bell_pair_testing, + qubit_ordering_testing, + result_types_all_selected_testing, + result_types_bell_pair_full_probability_testing, + result_types_bell_pair_marginal_probability_testing, + result_types_hermitian_testing, + result_types_nonzero_shots_bell_pair_testing, + result_types_tensor_hermitian_hermitian_testing, + result_types_tensor_x_y_testing, + result_types_tensor_y_hermitian_testing, + result_types_tensor_z_h_y_testing, + result_types_tensor_z_hermitian_testing, + result_types_tensor_z_z_testing, ) -SHOTS = 750 +SHOTS = 8000 @pytest.mark.parametrize("simulator_arn", [AwsQuantumSimulatorArns.QS1]) -def test_bell_pair(simulator_arn, aws_session, s3_destination_folder, bell_state_and_tolerances): +def test_no_result_types_bell_pair(simulator_arn, aws_session, s3_destination_folder): device = AwsQuantumSimulator(simulator_arn, aws_session) - result = device.run(bell_state_and_tolerances[0], s3_destination_folder, shots=SHOTS).result() - assert_measurement_probabilities(result.measurement_probabilities, bell_state_and_tolerances[1]) - assert len(result.measurements) == SHOTS + no_result_types_bell_pair_testing( + device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} + ) @pytest.mark.parametrize("simulator_arn", [AwsQuantumSimulatorArns.QS1]) -def test_qubit_ordering( - simulator_arn, - aws_session, - s3_destination_folder, - state_110_and_most_common, - state_001_and_most_common, +def test_qubit_ordering(simulator_arn, aws_session, s3_destination_folder): + device = AwsQuantumSimulator(simulator_arn, aws_session) + qubit_ordering_testing(device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder}) + + +@pytest.mark.parametrize("simulator_arn", [AwsQuantumSimulatorArns.QS1]) +def test_result_types_nonzero_shots_bell_pair(simulator_arn, aws_session, s3_destination_folder): + device = AwsQuantumSimulator(simulator_arn, aws_session) + result_types_nonzero_shots_bell_pair_testing( + device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} + ) + + +@pytest.mark.parametrize("simulator_arn", [AwsQuantumSimulatorArns.QS1]) +def test_result_types_bell_pair_full_probability(simulator_arn, aws_session, s3_destination_folder): + device = AwsQuantumSimulator(simulator_arn, aws_session) + result_types_bell_pair_full_probability_testing( + device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} + ) + + +@pytest.mark.parametrize("simulator_arn", [AwsQuantumSimulatorArns.QS1]) +def test_result_types_bell_pair_marginal_probability( + simulator_arn, aws_session, s3_destination_folder ): device = AwsQuantumSimulator(simulator_arn, aws_session) + result_types_bell_pair_marginal_probability_testing( + device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} + ) + + +@pytest.mark.parametrize("simulator_arn,shots", [(AwsQuantumSimulatorArns.QS1, SHOTS)]) +def test_result_types_tensor_x_y(simulator_arn, shots, aws_session, s3_destination_folder): + device = AwsQuantumSimulator(simulator_arn, aws_session) + result_types_tensor_x_y_testing( + device, {"shots": shots, "s3_destination_folder": s3_destination_folder} + ) + + +@pytest.mark.parametrize("simulator_arn,shots", [(AwsQuantumSimulatorArns.QS1, SHOTS)]) +def test_result_types_tensor_z_h_y(simulator_arn, shots, aws_session, s3_destination_folder): + device = AwsQuantumSimulator(simulator_arn, aws_session) + result_types_tensor_z_h_y_testing( + device, {"shots": shots, "s3_destination_folder": s3_destination_folder} + ) - # |110> should get back value of "110" - result = device.run(state_110_and_most_common[0], s3_destination_folder, shots=SHOTS).result() - assert_measurement_counts_most_common(result.measurement_counts, state_110_and_most_common[1]) - # |001> should get back value of "001" - result = device.run(state_001_and_most_common[0], s3_destination_folder, shots=SHOTS).result() - assert_measurement_counts_most_common(result.measurement_counts, state_001_and_most_common[1]) +@pytest.mark.parametrize("simulator_arn,shots", [(AwsQuantumSimulatorArns.QS1, SHOTS)]) +def test_result_types_hermitian(simulator_arn, shots, aws_session, s3_destination_folder): + device = AwsQuantumSimulator(simulator_arn, aws_session) + result_types_hermitian_testing( + device, {"shots": shots, "s3_destination_folder": s3_destination_folder} + ) + + +@pytest.mark.parametrize("simulator_arn,shots", [(AwsQuantumSimulatorArns.QS1, SHOTS)]) +def test_result_types_tensor_z_z(simulator_arn, shots, aws_session, s3_destination_folder): + device = AwsQuantumSimulator(simulator_arn, aws_session) + result_types_tensor_z_z_testing( + device, {"shots": shots, "s3_destination_folder": s3_destination_folder} + ) + + +@pytest.mark.parametrize("simulator_arn,shots", [(AwsQuantumSimulatorArns.QS1, SHOTS)]) +def test_result_types_tensor_hermitian_hermitian( + simulator_arn, shots, aws_session, s3_destination_folder +): + device = AwsQuantumSimulator(simulator_arn, aws_session) + result_types_tensor_hermitian_hermitian_testing( + device, {"shots": shots, "s3_destination_folder": s3_destination_folder} + ) + + +@pytest.mark.parametrize("simulator_arn,shots", [(AwsQuantumSimulatorArns.QS1, SHOTS)]) +def test_result_types_tensor_y_hermitian(simulator_arn, shots, aws_session, s3_destination_folder): + device = AwsQuantumSimulator(simulator_arn, aws_session) + result_types_tensor_y_hermitian_testing( + device, {"shots": shots, "s3_destination_folder": s3_destination_folder} + ) + + +@pytest.mark.parametrize("simulator_arn,shots", [(AwsQuantumSimulatorArns.QS1, SHOTS)]) +def test_result_types_tensor_z_hermitian(simulator_arn, shots, aws_session, s3_destination_folder): + device = AwsQuantumSimulator(simulator_arn, aws_session) + result_types_tensor_z_hermitian_testing( + device, {"shots": shots, "s3_destination_folder": s3_destination_folder} + ) + + +@pytest.mark.parametrize("simulator_arn,shots", [(AwsQuantumSimulatorArns.QS1, SHOTS)]) +def test_result_types_all_selected(simulator_arn, shots, aws_session, s3_destination_folder): + device = AwsQuantumSimulator(simulator_arn, aws_session) + result_types_all_selected_testing( + device, {"shots": shots, "s3_destination_folder": s3_destination_folder} + ) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 52cacd91..191fb126 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -141,6 +141,13 @@ def test_add_result_type_observable_conflict_all_target_then_selected_target(): circ.add_result_type(ResultType.Expectation(observable=Observable.Y(), target=[0, 1])) +@pytest.mark.xfail(raises=ValueError) +def test_add_result_type_observable_conflict_different_selected_targets_then_all_target(): + circ = Circuit().add_result_type(ResultType.Expectation(observable=Observable.Z(), target=[0])) + circ.add_result_type(ResultType.Expectation(observable=Observable.Y(), target=[1])) + circ.add_result_type(ResultType.Expectation(observable=Observable.Y())) + + @pytest.mark.xfail(raises=ValueError) def test_add_result_type_observable_conflict_selected_target_then_all_target(): circ = Circuit().add_result_type( @@ -158,6 +165,15 @@ def test_add_result_type_observable_no_conflict_all_target(): assert circ.result_types == expected +def test_add_result_type_observable_no_conflict_target_all(): + expected = [ + ResultType.Expectation(observable=Observable.Z(), target=[0]), + ResultType.Probability(), + ] + circ = Circuit(expected) + assert circ.result_types == expected + + def test_add_result_type_observable_no_conflict_all(): expected = [ ResultType.Variance(observable=Observable.Y()), @@ -434,7 +450,7 @@ def test_basis_rotation_instructions_tensor_product(): assert circ.basis_rotation_instructions == expected -def test_basis_rotation_instructions_multiple_result_types(): +def test_basis_rotation_instructions_multiple_result_types_different_targets(): circ = ( Circuit() .h(0) @@ -446,6 +462,77 @@ def test_basis_rotation_instructions_multiple_result_types(): assert circ.basis_rotation_instructions == expected +def test_basis_rotation_instructions_multiple_result_types_same_targets(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + .sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) + .variance(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ) + expected = [Instruction(Gate.Ry(-np.pi / 4), 0), Instruction(Gate.H(), 1)] + assert circ.basis_rotation_instructions == expected + + +def test_basis_rotation_instructions_multiple_result_types_all_specified_same_targets(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .expectation(observable=Observable.H()) + .sample(observable=Observable.H(), target=[0]) + ) + expected = [Instruction(Gate.Ry(-np.pi / 4), 0), Instruction(Gate.Ry(-np.pi / 4), 1)] + assert circ.basis_rotation_instructions == expected + + +def test_basis_rotation_instructions_multiple_result_types_specified_all_same_targets(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .sample(observable=Observable.H(), target=[0]) + .expectation(observable=Observable.H()) + ) + expected = [Instruction(Gate.Ry(-np.pi / 4), 0), Instruction(Gate.Ry(-np.pi / 4), 1)] + assert circ.basis_rotation_instructions == expected + + +def test_basis_rotation_instructions_multiple_result_types_same_targets_hermitian(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .sample(observable=Observable.Hermitian(matrix=np.array([[1, 0], [0, -1]])), target=[1]) + .expectation( + observable=Observable.Hermitian(matrix=np.array([[1, 0], [0, -1]])), target=[1] + ) + ) + expected = [Instruction(Gate.Unitary(matrix=np.array([[0, 1], [1, 0]])), target=[1])] + assert circ.basis_rotation_instructions == expected + + +def test_basis_rotation_instructions_multiple_result_types_different_hermitian_targets(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .sample(observable=Observable.Hermitian(matrix=np.array([[1, 0], [0, -1]])), target=[1]) + .expectation(observable=Observable.Hermitian(matrix=np.array([[0, 1], [1, 0]])), target=[0]) + ) + expected = [ + Instruction(Gate.Unitary(matrix=np.array([[0, 1], [1, 0]])), target=[1]), + Instruction( + Gate.Unitary( + matrix=1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) + ), + target=[0], + ), + ] + assert circ.basis_rotation_instructions == expected + + def test_depth_getter(h): assert h.depth is h._moments.depth diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index d5a65c21..e343b903 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -170,6 +170,13 @@ def test_observable_from_ir_hermitian(): assert actual_observable == Observable.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]]))) + == "Hermitian('qubit_count': 1, 'matrix': [[1.+0.j 0.+0.j], [0.+0.j 1.+0.j]])" + ) + + # TensorProduct 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 df8831be..a3cb5e57 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 @@ -133,7 +133,7 @@ def result_dict_5(): { "results": [ jaqcd.Probability(targets=[1]).dict(), - jaqcd.Expectation(targets=None, observable=["z"]).dict(), + jaqcd.Expectation(observable=["z"]).dict(), ] } ), @@ -186,7 +186,7 @@ def malformatted_results_2(): (jaqcd.Probability(targets=[1]), np.array([0.6, 0.4])), (jaqcd.Probability(targets=[1, 2]), np.array([0.4, 0.2, 0.0, 0.4])), ( - jaqcd.Probability(targets=None), + jaqcd.Probability(), np.array([0.1, 0.2, 0.2, 0.0, 0.0, 0.0, 0.0, 0.2, 0.0, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2]), ), (jaqcd.Sample(targets=[1], observable=["z"]), np.array([1, -1, 1, 1, -1, -1, 1, -1, 1, 1])), @@ -195,7 +195,7 @@ def malformatted_results_2(): np.array([-1, 1, 1, -1, 1, 1, 1, 1, 1, 1]), ), ( - jaqcd.Sample(targets=None, observable=["z"]), + jaqcd.Sample(observable=["z"]), [ np.array([1, -1, -1, 1, -1, 1, 1, 1, 1, 1]), np.array([1, -1, 1, 1, -1, -1, 1, -1, 1, 1]), @@ -205,10 +205,10 @@ def malformatted_results_2(): ), (jaqcd.Expectation(targets=[1], observable=["z"]), 0.2), (jaqcd.Expectation(targets=[1, 2], observable=["z", "y"]), 0.6), - (jaqcd.Expectation(targets=None, observable=["z"]), [0.4, 0.2, -0.2, -0.4]), + (jaqcd.Expectation(observable=["z"]), [0.4, 0.2, -0.2, -0.4]), (jaqcd.Variance(targets=[1], observable=["z"]), 0.96), (jaqcd.Variance(targets=[1, 2], observable=["z", "y"]), 0.64), - (jaqcd.Variance(targets=None, observable=["z"]), [0.84, 0.96, 0.96, 0.84]), + (jaqcd.Variance(observable=["z"]), [0.84, 0.96, 0.96, 0.84]), ] @@ -293,10 +293,7 @@ def test_from_dict_result_types(result_dict_5): assert np.allclose(task_result.values[0], np.array([0.6, 0.4])) assert task_result.values[1] == [0.4, 0.2, -0.2, -0.4] assert task_result.result_types[0]["Type"] == jaqcd.Probability(targets=[1]).dict() - assert ( - task_result.result_types[1]["Type"] - == jaqcd.Expectation(targets=None, observable=["z"]).dict() - ) + assert task_result.result_types[1]["Type"] == jaqcd.Expectation(observable=["z"]).dict() def test_from_dict_measurement_probabilities(result_str_3): From 2b808b97dc67075724c1b61599ca59453c6228dc Mon Sep 17 00:00:00 2001 From: Ava Wang Date: Fri, 5 Jun 2020 13:11:25 -0700 Subject: [PATCH 0115/1165] Remove braket_simulator from merging stable/latest --- src/braket/devices/braket_simulator.py | 60 -------------------------- src/braket/tasks/quantum_task.py | 2 - 2 files changed, 62 deletions(-) delete mode 100644 src/braket/devices/braket_simulator.py diff --git a/src/braket/devices/braket_simulator.py b/src/braket/devices/braket_simulator.py deleted file mode 100644 index 434ee01b..00000000 --- a/src/braket/devices/braket_simulator.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright 2019-2019 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 abc import ABC, abstractmethod -from typing import Any, Dict, Union - -from braket.ir.annealing import Problem -from braket.ir.jaqcd import Program - - -class BraketSimulator(ABC): - """ An abstract simulator that locally runs a quantum task. - - The task can be either a circuit-based program or an annealing task, - specified by the given IR. - - For users creating their own simulator: to register a simulator so the - Braket SDK recognizes its name, the name and class must added as an - entry point for "braket.simulators". This is done by adding an entry to - entry_points in the simulator package's setup.py: - - >>> entry_points = { - >>> "braket.simulators": [ - >>> "backend_name = " - >>> ] - >>> } - """ - - # TODO: Move this class to the local simulator repo and take a dependency on it - # As such, this will not depend on any SDK classes. - - # TODO: Update to use new simulate() method - - @abstractmethod - def run(self, ir: Union[Program, Problem], *args, **kwargs) -> Dict[str, Any]: - """ Run the task specified by the given IR. - - Extra arguments will contain any additional information necessary to run the task, - such as number of qubits. - - Args: - ir (Union[Program, Problem]): The IR representation of the program - - Returns: - Dict[str, Any]: A dict containing the results of the simulation. - In order to work with braket-python-sdk, the format of the JSON dict should - match that needed by GateModelQuantumTaskResult or AnnealingQuantumTaskResult - from the SDK, depending on the type of task. - """ - raise NotImplementedError() diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py index e1e8d787..99b03751 100644 --- a/src/braket/tasks/quantum_task.py +++ b/src/braket/tasks/quantum_task.py @@ -22,8 +22,6 @@ class QuantumTask(ABC): """An abstraction over a quantum task on a quantum device.""" - DEFAULT_SHOTS = 1_000 - @property @abstractmethod def id(self) -> str: From b0b1bd7cd3e4c0ea76d3b11ee3ee1f601b838033 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Fri, 5 Jun 2020 13:40:44 -0700 Subject: [PATCH 0116/1165] Update version to 0.4.0 (#95) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c8658424..5d5216b8 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="braket-sdk", - version="0.3.8", + version="0.4.0", license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), From 7b8e2cc3b60a58d9129386009beddfef1939c4f3 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 5 Jun 2020 14:12:34 -0700 Subject: [PATCH 0117/1165] Add version command (#96) Users can now check the version of the SDK by running ```python >>> import braket._sdk as braket_sdk >>> braket_sdk.__version___ 0.4.0 ``` --- README.md | 8 ++++++++ setup.py | 5 ++++- src/braket/_sdk/__init__.py | 14 ++++++++++++++ src/braket/_sdk/_version.py | 18 ++++++++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 src/braket/_sdk/__init__.py create mode 100644 src/braket/_sdk/_version.py diff --git a/README.md b/README.md index 0bc04342..b59305cd 100644 --- a/README.md +++ b/README.md @@ -318,6 +318,14 @@ pip show amazon-braket-default-simulator-python pip show braket-ir pip show braket-sdk ``` + +You can also check your version of `braket-python-sdk` from within Python: + +``` +>>> import braket._sdk as braket_sdk +>>> braket_sdk.__version___ +``` + Compare the version displayed in your local environment with the latest version listed for each of the following release pages: - [amazon-braket-default-simulator-python](https://github.com/aws/amazon-braket-default-simulator-python/releases) - [braket-python-ir](https://github.com/aws/braket-python-ir/releases) diff --git a/setup.py b/setup.py index 5d5216b8..e767ca44 100644 --- a/setup.py +++ b/setup.py @@ -13,9 +13,12 @@ from setuptools import find_namespace_packages, setup +with open("src/braket/_sdk/_version.py") as f: + version = f.readlines()[-1].split()[-1].strip("\"'") + setup( name="braket-sdk", - version="0.4.0", + version=version, license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), diff --git a/src/braket/_sdk/__init__.py b/src/braket/_sdk/__init__.py new file mode 100644 index 00000000..709f2b60 --- /dev/null +++ b/src/braket/_sdk/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2019-2019 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 ._version import __version__ # noqa: F401 diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py new file mode 100644 index 00000000..d2999fd1 --- /dev/null +++ b/src/braket/_sdk/_version.py @@ -0,0 +1,18 @@ +# Copyright 2019-2019 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. + +"""Version information. + Version number (major.minor.patch[-label]) +""" + +__version__ = "0.4.0" From 149dea1e7805d0e9a9877ad69dec5c21a9576a6a Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 5 Jun 2020 14:44:44 -0700 Subject: [PATCH 0118/1165] Remove extra dash in README example (#97) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b59305cd..5f604eb4 100644 --- a/README.md +++ b/README.md @@ -323,7 +323,7 @@ You can also check your version of `braket-python-sdk` from within Python: ``` >>> import braket._sdk as braket_sdk ->>> braket_sdk.__version___ +>>> braket_sdk.__version__ ``` Compare the version displayed in your local environment with the latest version listed for each of the following release pages: From eedb3bf2c28f351818826c0ab2b2183eaded4521 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Fri, 5 Jun 2020 15:53:47 -0700 Subject: [PATCH 0119/1165] Add shots to simulators for examples (#98) --- examples/bell_result_types.py | 8 +++----- examples/debug_bell.py | 9 ++++++++- examples/local_bell.py | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/examples/bell_result_types.py b/examples/bell_result_types.py index 6f329c8b..1767e31e 100644 --- a/examples/bell_result_types.py +++ b/examples/bell_result_types.py @@ -45,7 +45,6 @@ Circuit() .h(0) .cnot(0, 1) - .probability() .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]) @@ -54,9 +53,8 @@ # When shots>0 for a simulator, probability, expectation, variance are calculated from measurements # Users can request sample as a result when shots > 0 result = device.run(bell, shots=100).result() -print("Probability of all states in computational basis:", result.values[0]) -print("Expectation of target 0, 1 in the basis of Pauli-Y @ Pauli-X:", result.values[1]) -print("Variance of target 0, 1 in the basis of Pauli-Y @ Pauli-X:", result.values[2]) -print("Samples of target 0, 1 in the basis of Pauli-Y @ Pauli-X:", result.values[3]) +print("Expectation of target 0, 1 in the basis of Pauli-Y @ Pauli-X:", result.values[0]) +print("Variance of target 0, 1 in the basis of Pauli-Y @ Pauli-X:", result.values[1]) +print("Samples of target 0, 1 in the basis of Pauli-Y @ Pauli-X:", result.values[2]) # Probability, sample, expectation, and variance are also supported for QPU devices diff --git a/examples/debug_bell.py b/examples/debug_bell.py index 1660515a..f1075e5f 100644 --- a/examples/debug_bell.py +++ b/examples/debug_bell.py @@ -30,7 +30,14 @@ bell = Circuit().h(0).cnot(0, 1) # pass in logger to device.run, enabling debugging logs to print to console logger.info( - device.run(bell, s3_folder, poll_timeout_seconds=120, poll_interval_seconds=0.25, logger=logger) + device.run( + bell, + s3_folder, + shots=100, + poll_timeout_seconds=120, + poll_interval_seconds=0.25, + logger=logger, + ) .result() .measurement_counts ) diff --git a/examples/local_bell.py b/examples/local_bell.py index c4eb611f..7b70b1f9 100644 --- a/examples/local_bell.py +++ b/examples/local_bell.py @@ -17,4 +17,4 @@ device = LocalSimulator() bell = Circuit().h(0).cnot(0, 1) -print(device.run(bell).result().measurement_counts) +print(device.run(bell, shots=100).result().measurement_counts) From 1206ec16b327f8bfb6f4440fa107aa2de22c1fff Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 8 Jun 2020 10:08:56 -0700 Subject: [PATCH 0120/1165] CODEOWNERS on one line (#99) Separate line version was wrong: https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners --- CODEOWNERS | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 101d3669..7ce2839a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,6 +3,4 @@ # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, these accounts # will be requested for review when someone opens a pull request. -* @floralph -* @speller26 -* @avawang1 +* @floralph @speller26 @avawang1 From 05c4e318fd187e8259c90f7683b4589938ee6f1c Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 8 Jun 2020 10:27:48 -0700 Subject: [PATCH 0121/1165] More integ test bucket creation exception handling (#100) If a developer has already run the integ tests in one region and then tries to run them from another, the tests will fail with an IllegalLocationConstraintException, which is the error thrown when a user attempts to access an existing bucket from _outside_ the region it lives. This commit adds IllegalLocationConstraintException to the exceptions to ignore. --- test/integ_tests/conftest.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py index ab3515e7..4137fc58 100644 --- a/test/integ_tests/conftest.py +++ b/test/integ_tests/conftest.py @@ -47,7 +47,13 @@ def s3_bucket(s3_resource, boto_session): try: bucket.create(ACL="private", CreateBucketConfiguration={"LocationConstraint": region_name}) except ClientError as e: - if e.response["Error"]["Code"] == "BucketAlreadyOwnedByYou": + code = e.response["Error"]["Code"] + + # Bucket exists in profile region + if code == "BucketAlreadyOwnedByYou": + pass + # Bucket exists in another region + elif code == "IllegalLocationConstraintException" and bucket.creation_date: pass else: raise e From 29bac7f2f15bd643865712fdd370b3e19f290e6c Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Mon, 8 Jun 2020 16:16:07 -0700 Subject: [PATCH 0122/1165] Add supported result types to AwsQuantumSimulator and allow basis_rotation_gates to be run twice on circuit (#101) --- src/braket/_sdk/_version.py | 2 +- src/braket/aws/aws_quantum_simulator.py | 3 ++- src/braket/circuits/circuit.py | 10 +++++----- test/unit_tests/braket/aws/common_test_utils.py | 17 +++++++++++++++++ .../braket/aws/test_aws_quantum_simulator.py | 6 ++++++ test/unit_tests/braket/circuits/test_circuit.py | 14 ++++++++++++++ 6 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d2999fd1..25dd15a4 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.4.0" +__version__ = "0.4.1" diff --git a/src/braket/aws/aws_quantum_simulator.py b/src/braket/aws/aws_quantum_simulator.py index bf5c5a24..db62e62a 100644 --- a/src/braket/aws/aws_quantum_simulator.py +++ b/src/braket/aws/aws_quantum_simulator.py @@ -111,7 +111,8 @@ def refresh_metadata(self) -> None: self._status = simulator_metadata.get("status") self._status_reason = simulator_metadata.get("statusReason") self._properties = { - k: simulator_metadata.get(k) for k in ["supportedQuantumOperations", "qubitCount"] + k: simulator_metadata.get(k) + for k in ["supportedQuantumOperations", "qubitCount", "supportedResultTypes"] } @property diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 371a707d..1428110b 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -149,8 +149,8 @@ def basis_rotation_instructions(self) -> List[Instruction]: added_observables_targets = set() for return_type in observable_return_types: observable: Observable = return_type.observable - targets: List[List[int]] = [return_type.target] if return_type.target else [ - QubitSet(qubit) for qubit in self._moments.qubits + targets: List[List[int]] = [list(return_type.target)] if return_type.target else [ + list([qubit]) for qubit in self._moments.qubits ] for target in targets: @@ -166,15 +166,15 @@ def basis_rotation_instructions(self) -> List[Instruction]: return basis_rotation_instructions @staticmethod - def _observable_to_instruction(observable: Observable, targets: List[int]): + def _observable_to_instruction(observable: Observable, target_list: List[int]): if isinstance(observable, TensorProduct): instructions = [] for factor in observable.factors: - target = [targets.pop(0) for _ in range(factor.qubit_count)] + target = [target_list.pop(0) for _ in range(factor.qubit_count)] instructions += Circuit._observable_to_instruction(factor, target) return instructions else: - return [Instruction(gate, targets) for gate in observable.basis_rotation_gates] + return [Instruction(gate, target_list) for gate in observable.basis_rotation_gates] @property def moments(self) -> Moments: diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 08d0f0c2..89ff292a 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -143,6 +143,15 @@ class MockDevices: "gateModelProperties": { "qubitCount": 23, "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "Toffoli"], + "supportedResultTypes": [ + { + "name": "Sample", + "observables": ["X", "Y", "Z"], + "minShots": 1, + "maxShots": 100, + }, + {"name": "Probability", "minShots": 1, "maxShots": 100,}, + ], } }, "name": "integ_test_simulator", @@ -164,6 +173,14 @@ class MockDevices: "Phase", "CPhase", ], + "supportedResultTypes": [ + { + "name": "Sample", + "observables": ["X", "Y", "Z"], + "minShots": 1, + "maxShots": 100, + } + ], } }, "name": "integ_test_simulator", diff --git a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py index 7cab8f48..86ee623c 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py @@ -50,6 +50,9 @@ def test_simulator_refresh_metadata_success(): assert simulator.properties["supportedQuantumOperations"] == expected_metadata.get( "supportedQuantumOperations" ) + assert simulator.properties["supportedResultTypes"] == expected_metadata.get( + "supportedResultTypes" + ) assert simulator.status == expected_metadata.get("status") assert simulator.status_reason is None @@ -63,6 +66,9 @@ def test_simulator_refresh_metadata_success(): assert simulator.properties["supportedQuantumOperations"] == expected_metadata.get( "supportedQuantumOperations" ) + assert simulator.properties["supportedResultTypes"] == expected_metadata.get( + "supportedResultTypes" + ) assert simulator.status == expected_metadata.get("status") assert simulator.status_reason == expected_metadata.get("statusReason") diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 191fb126..8b979bd0 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -533,6 +533,20 @@ def test_basis_rotation_instructions_multiple_result_types_different_hermitian_t assert circ.basis_rotation_instructions == expected +def test_basis_rotation_instructions_call_twice(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + .sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) + .variance(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ) + expected = [Instruction(Gate.Ry(-np.pi / 4), 0), Instruction(Gate.H(), 1)] + assert circ.basis_rotation_instructions == expected + assert circ.basis_rotation_instructions == expected + + def test_depth_getter(h): assert h.depth is h._moments.depth From 9ca197b9699d0f13000da6c861fe77b3c61b4d95 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 9 Jun 2020 16:55:19 -0700 Subject: [PATCH 0123/1165] Validate compatible IR for LocalSimulator (#102) LocalSimulator now fails fast when the wrong type of task is supplied to a BraketSimulator implementation. --- src/braket/devices/local_simulator.py | 6 +++++- .../braket/devices/test_local_simulator.py | 21 ++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 46e0ef25..cf4a73d4 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -120,11 +120,13 @@ def _(backend_impl: BraketSimulator): def _run_internal( task_specification, simulator: BraketSimulator, shots: Optional[int] = None, *args, **kwargs ): - raise NotImplementedError("Unsupported task type") + raise NotImplementedError(f"Unsupported task type {type(task_specification)}") @_run_internal.register def _(circuit: Circuit, simulator: BraketSimulator, shots, *args, **kwargs): + if "jaqcd" not in simulator.properties["supportedIrTypes"]: + raise NotImplementedError(f"{type(simulator)} does not support qubit gate-based programs") validate_circuit_and_shots(circuit, shots) program = circuit.to_ir() qubits = circuit.qubit_count @@ -134,6 +136,8 @@ def _(circuit: Circuit, simulator: BraketSimulator, shots, *args, **kwargs): @_run_internal.register def _(problem: Problem, simulator: BraketSimulator, shots, *args, **kwargs): + if "annealing" not in simulator.properties["supportedIrTypes"]: + raise NotImplementedError(f"{type(simulator)} does not support quantum annealing problems") ir = problem.to_ir() results_dict = simulator.run(ir, shots, *args, *kwargs) return AnnealingQuantumTaskResult.from_dict(results_dict) diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index b8574028..a73d32d3 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -55,7 +55,7 @@ def run( @property def properties(self) -> Dict[str, Any]: - return {"supportedQuantumOperations": ["I", "X"]} + return {"supportedIrTypes": ["jaqcd"], "supportedQuantumOperations": ["I", "X"]} def assert_shots(self, shots): assert self._shots == shots @@ -70,7 +70,7 @@ def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> Dict[str, Any]: @property def properties(self) -> Dict[str, Any]: - return {} + return {"supportedIrTypes": ["annealing"]} mock_entry = Mock() @@ -126,7 +126,22 @@ def test_run_unsupported_type(): sim.run("I'm unsupported") +@pytest.mark.xfail(raises=NotImplementedError) +def test_run_annealing_unsupported(): + sim = LocalSimulator(DummyCircuitSimulator()) + sim.run(Problem(ProblemType.ISING)) + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_run_qubit_gate_unsupported(): + sim = LocalSimulator(DummyAnnealingSimulator()) + sim.run(Circuit().h(0).cnot(0, 1), 1000) + + def test_properties(): sim = LocalSimulator(DummyCircuitSimulator()) - expected_properties = {"supportedQuantumOperations": ["I", "X"]} + expected_properties = { + "supportedIrTypes": ["jaqcd"], + "supportedQuantumOperations": ["I", "X"], + } assert sim.properties == expected_properties From 76daafcc81746df378dc055eb8b1180f0be1908e Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Mon, 15 Jun 2020 11:17:38 -0700 Subject: [PATCH 0124/1165] Update time windows for IonQ (#103) --- README.md | 2 +- src/braket/_sdk/_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5f604eb4..ce3bcf08 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,7 @@ print(task.result().measurement_counts) ``` Specify which quantum computer hardware to use by changing the value of the `device_arn` to the value for quantum computer to use: -- **IonQ** "arn:aws:aqx:::qpu:ionq" (Available 4:00 PM to 8:00 PM ET M-F) +- **IonQ** "arn:aws:aqx:::qpu:ionq" (Available 9:00 AM to 1:00 PM ET M-F) - **Rigetti** "arn:aws:aqx:::qpu:rigetti" (Available 11:00 AM to 1:00 PM ET daily) - **D-Wave** "arn:aws:aqx:::qpu:d-wave" (Available 24/7. See the next section in this document for more information about using D-Wave.) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 25dd15a4..cec4a1b5 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.4.1" +__version__ = "0.4.2" From de10b495169508978628a038957d7fe3279c467e Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Mon, 15 Jun 2020 15:41:12 -0700 Subject: [PATCH 0125/1165] Add exception if amplitude state is not a list (#104) --- examples/bell_result_types.py | 2 +- src/braket/circuits/result_types.py | 12 +++++++++--- test/unit_tests/braket/circuits/test_result_types.py | 3 ++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/examples/bell_result_types.py b/examples/bell_result_types.py index 1767e31e..715a17fb 100644 --- a/examples/bell_result_types.py +++ b/examples/bell_result_types.py @@ -25,7 +25,7 @@ .cnot(0, 1) .probability(target=[0]) .expectation(observable=Observable.Z(), target=[1]) - .amplitude(state="00") + .amplitude(state=["00"]) .state_vector() ) diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 6e8556f4..c4922815 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -82,13 +82,19 @@ def __init__(self, state: List[str]): state (List[str]): list of quantum states as strings with "0" and "1" Raises: - ValueError: If state is None or an empty list + ValueError: If state is None or an empty list, or + state is not a list of strings of '0' and '1' Examples: >>> ResultType.Amplitude(state=['01', '10']) """ - if not state or not all( - isinstance(amplitude, str) and re.fullmatch("^[01]+$", amplitude) for amplitude in state + if ( + not state + or not isinstance(state, List) + or not all( + isinstance(amplitude, str) and re.fullmatch("^[01]+$", amplitude) + for amplitude in state + ) ): raise ValueError( "A non-empty list of states must be specified in binary encoding e.g. ['01', '10']" diff --git a/test/unit_tests/braket/circuits/test_result_types.py b/test/unit_tests/braket/circuits/test_result_types.py index 219783b2..600ae2bb 100644 --- a/test/unit_tests/braket/circuits/test_result_types.py +++ b/test/unit_tests/braket/circuits/test_result_types.py @@ -112,7 +112,8 @@ def test_result_equality(testclass, subroutine_name, irclass, input, ir_input): @pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize( - "state", ((["2", "11"]), ([1, 0]), ([0.1, 0]), ("-0", "1"), (["", ""]), (None), ([None, None])) + "state", + ((["2", "11"]), ([1, 0]), ([0.1, 0]), ("-0", "1"), (["", ""]), (None), ([None, None]), ("10")), ) def test_amplitude_init_invalid_state_value_error(state): ResultType.Amplitude(state=state) From 1597d131eab91eff8c97ec9dce587283d408273c Mon Sep 17 00:00:00 2001 From: Ravi Kiran Chilakapati Date: Wed, 24 Jun 2020 10:32:45 -0700 Subject: [PATCH 0126/1165] Add retries for some retryable errors when attempting to fetch tasks --- setup.py | 1 + src/braket/aws/aws_session.py | 17 +++++++ .../unit_tests/braket/aws/test_aws_session.py | 49 +++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/setup.py b/setup.py index e767ca44..964ef55b 100644 --- a/setup.py +++ b/setup.py @@ -29,6 +29,7 @@ "amazon-braket-default-simulator-python @" "git+https://github.com/aws/amazon-braket-default-simulator-python.git" ), + "backoff", "boltons", "boto3", "nest-asyncio", diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index f1790139..899e5853 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -13,7 +13,9 @@ from typing import Any, Dict, NamedTuple +import backoff import boto3 +from botocore.exceptions import ClientError class AwsSession(object): @@ -79,6 +81,21 @@ def create_quantum_task(self, **boto3_kwargs) -> str: response = self.braket_client.create_quantum_task(**boto3_kwargs) return response["quantumTaskArn"] + @staticmethod + def _should_giveup(err): + return not ( + isinstance(err, ClientError) + and err.response["Error"]["Code"] + in ["ResourceNotFoundException", "ThrottlingException",] + ) + + @backoff.on_exception( + backoff.expo, + ClientError, + max_tries=3, + jitter=backoff.full_jitter, + giveup=_should_giveup.__func__, + ) def get_quantum_task(self, arn: str) -> Dict[str, Any]: """ Gets the quantum task. diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index 4a281672..ecec2916 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -173,3 +173,52 @@ def test_get_quantum_task(aws_session): assert aws_session.get_quantum_task(arn) == return_value aws_session.braket_client.get_quantum_task.assert_called_with(quantumTaskArn=arn) + + +def test_get_quantum_task_retry(aws_session): + arn = "foo:bar:arn" + return_value = {"quantumTaskArn": arn} + + resource_not_found_response = { + "Error": {"Code": "ResourceNotFoundException", "Message": "unit-test-error",} + } + throttling_response = {"Error": {"Code": "ThrottlingException", "Message": "unit-test-error",}} + + aws_session.braket_client.get_quantum_task.side_effect = [ + ClientError(resource_not_found_response, "unit-test"), + ClientError(throttling_response, "unit-test"), + return_value, + ] + + assert aws_session.get_quantum_task(arn) == return_value + aws_session.braket_client.get_quantum_task.assert_called_with(quantumTaskArn=arn) + aws_session.braket_client.get_quantum_task.call_count == 3 + + +def test_get_quantum_task_fail_after_retries(aws_session): + resource_not_found_response = { + "Error": {"Code": "ResourceNotFoundException", "Message": "unit-test-error",} + } + throttling_response = {"Error": {"Code": "ThrottlingException", "Message": "unit-test-error",}} + + aws_session.braket_client.get_quantum_task.side_effect = [ + ClientError(resource_not_found_response, "unit-test"), + ClientError(throttling_response, "unit-test"), + ClientError(throttling_response, "unit-test"), + ] + + with pytest.raises(ClientError): + aws_session.get_quantum_task("some-arn") + aws_session.braket_client.get_quantum_task.call_count == 3 + + +def test_get_quantum_task_does_not_retry_other_exceptions(aws_session): + exception_response = {"Error": {"Code": "SomeOtherException", "Message": "unit-test-error",}} + + aws_session.braket_client.get_quantum_task.side_effect = [ + ClientError(exception_response, "unit-test"), + ] + + with pytest.raises(ClientError): + aws_session.get_quantum_task("some-arn") + aws_session.braket_client.get_quantum_task.call_count == 1 From 374eb89e326bbd12690cf02fb15f45f2050c7752 Mon Sep 17 00:00:00 2001 From: Cedric Lin Date: Thu, 9 Jul 2020 17:15:48 -0700 Subject: [PATCH 0127/1165] Setting whitelist_externals envconfig setting to "coverage" to stop a warning. --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 414dc56a..8a0dde5f 100644 --- a/tox.ini +++ b/tox.ini @@ -3,6 +3,7 @@ envlist = linters,docs,unit-tests [testenv:unit-tests] basepython = python3.7 +whitelist_externals = coverage # {posargs} contains additional arguments specified when invoking tox. e.g. tox -- -s -k test_foo.py commands = coverage run -m pytest {posargs} From 52f05f7b3ac4618e52895ee2b42576795538f330 Mon Sep 17 00:00:00 2001 From: Ravi Kiran Chilakapati Date: Wed, 15 Jul 2020 06:46:46 -0700 Subject: [PATCH 0128/1165] Run linters without any code changes --- examples/bell.py | 1 + examples/debug_bell.py | 1 + src/braket/aws/aws_qpu.py | 3 ++- src/braket/aws/aws_quantum_task.py | 1 + src/braket/circuits/gates.py | 3 ++- src/braket/circuits/observable.py | 1 + src/braket/circuits/observables.py | 1 + src/braket/circuits/quantum_operator.py | 1 + src/braket/circuits/qubit_set.py | 1 + src/braket/circuits/result_types.py | 1 - src/braket/tasks/gate_model_quantum_task_result.py | 1 + test/integ_tests/conftest.py | 1 + test/integ_tests/gate_model_device_testing_utils.py | 1 + test/integ_tests/test_aws_session_s3.py | 1 + test/integ_tests/test_device_creation.py | 1 + test/integ_tests/test_local_braket_simulator.py | 3 ++- test/integ_tests/test_simulator_quantum_task.py | 3 ++- test/unit_tests/braket/aws/test_aws_qpu.py | 3 ++- test/unit_tests/braket/aws/test_aws_quantum_simulator.py | 3 ++- test/unit_tests/braket/aws/test_aws_quantum_task.py | 3 ++- test/unit_tests/braket/aws/test_aws_session.py | 3 ++- test/unit_tests/braket/circuits/test_angled_gate.py | 1 + test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py | 1 + test/unit_tests/braket/circuits/test_circuit.py | 3 ++- test/unit_tests/braket/circuits/test_circuit_helpers.py | 1 + test/unit_tests/braket/circuits/test_gate.py | 1 + test/unit_tests/braket/circuits/test_gates.py | 3 ++- test/unit_tests/braket/circuits/test_instruction.py | 1 + test/unit_tests/braket/circuits/test_moments.py | 1 + test/unit_tests/braket/circuits/test_observable.py | 1 + test/unit_tests/braket/circuits/test_observables.py | 1 + test/unit_tests/braket/circuits/test_quantum_operator.py | 1 + .../braket/circuits/test_quantum_operator_helpers.py | 1 + test/unit_tests/braket/circuits/test_qubit.py | 1 + test/unit_tests/braket/circuits/test_qubit_set.py | 1 + test/unit_tests/braket/circuits/test_result_type.py | 1 + test/unit_tests/braket/circuits/test_result_types.py | 3 ++- test/unit_tests/braket/devices/test_local_simulator.py | 3 ++- .../braket/tasks/test_annealing_quantum_task_result.py | 1 + .../braket/tasks/test_gate_model_quantum_task_result.py | 1 + test/unit_tests/braket/tasks/test_local_quantum_task.py | 1 + 41 files changed, 52 insertions(+), 13 deletions(-) diff --git a/examples/bell.py b/examples/bell.py index 14348173..45f05456 100644 --- a/examples/bell.py +++ b/examples/bell.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import boto3 + from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns from braket.circuits import Circuit diff --git a/examples/debug_bell.py b/examples/debug_bell.py index f1075e5f..267c1acc 100644 --- a/examples/debug_bell.py +++ b/examples/debug_bell.py @@ -15,6 +15,7 @@ import sys import boto3 + from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns from braket.circuits import Circuit diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py index 78bbbe59..dca18b8f 100644 --- a/src/braket/aws/aws_qpu.py +++ b/src/braket/aws/aws_qpu.py @@ -14,13 +14,14 @@ from typing import Any, Dict, Union import boto3 +from networkx import Graph, complete_graph, from_edgelist + from braket.annealing.problem import Problem from braket.aws.aws_qpu_arns import AwsQpuArns from braket.aws.aws_quantum_task import AwsQuantumTask from braket.aws.aws_session import AwsSession from braket.circuits import Circuit from braket.devices.device import Device -from networkx import Graph, complete_graph, from_edgelist class AwsQpu(Device): diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 25dcc9cc..7e10053d 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -20,6 +20,7 @@ from typing import Any, Dict, Union import boto3 + from braket.annealing.problem import Problem from braket.aws.aws_session import AwsSession from braket.circuits.circuit import Circuit diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index ffb282f7..877a78ab 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -13,8 +13,9 @@ from typing import Iterable -import braket.ir.jaqcd as ir import numpy as np + +import braket.ir.jaqcd as ir from braket.circuits import circuit from braket.circuits.angled_gate import AngledGate from braket.circuits.gate import Gate diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index 0f523ac7..416eb16a 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -16,6 +16,7 @@ from typing import List, Sequence, Tuple, Union import numpy as np + from braket.circuits.gate import Gate from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.quantum_operator_helpers import get_pauli_eigenvalues diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 02f813d4..7cb74800 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -19,6 +19,7 @@ from typing import Dict, List, Tuple, Union import numpy as np + from braket.circuits.gate import Gate from braket.circuits.observable import Observable, StandardObservable from braket.circuits.quantum_operator_helpers import ( diff --git a/src/braket/circuits/quantum_operator.py b/src/braket/circuits/quantum_operator.py index 6bdefb94..46de80c1 100644 --- a/src/braket/circuits/quantum_operator.py +++ b/src/braket/circuits/quantum_operator.py @@ -13,6 +13,7 @@ from typing import Any, List, Sequence import numpy as np + from braket.circuits.operator import Operator diff --git a/src/braket/circuits/qubit_set.py b/src/braket/circuits/qubit_set.py index 3cb4e77e..45b250e2 100644 --- a/src/braket/circuits/qubit_set.py +++ b/src/braket/circuits/qubit_set.py @@ -16,6 +16,7 @@ from typing import Dict, Iterable, Union from boltons.setutils import IndexedSet + from braket.circuits.qubit import Qubit, QubitInput QubitSetInput = Union[QubitInput, Iterable[QubitInput]] diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index c4922815..9a67f17a 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -22,7 +22,6 @@ from braket.circuits.qubit_set import QubitSet, QubitSetInput from braket.circuits.result_type import ObservableResultType, ResultType - """ To add a new result type: 1. Implement the class and extend `ResultType` diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 09d5405f..b3d43279 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -18,6 +18,7 @@ from typing import Any, Callable, Counter, Dict, List, Optional, TypeVar, Union import numpy as np + from braket.circuits import Observable, ResultType, StandardObservable from braket.circuits.observables import observable_from_ir diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py index 4137fc58..7c704ffe 100644 --- a/test/integ_tests/conftest.py +++ b/test/integ_tests/conftest.py @@ -16,6 +16,7 @@ import boto3 import pytest from botocore.exceptions import ClientError + from braket.aws.aws_session import AwsSession diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index 7500b7a1..e2163541 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -14,6 +14,7 @@ from typing import Any, Dict import numpy as np + from braket.circuits import Circuit, Observable, ResultType from braket.circuits.quantum_operator_helpers import get_pauli_eigenvalues from braket.devices import Device diff --git a/test/integ_tests/test_aws_session_s3.py b/test/integ_tests/test_aws_session_s3.py index 3d009c19..54a47a74 100644 --- a/test/integ_tests/test_aws_session_s3.py +++ b/test/integ_tests/test_aws_session_s3.py @@ -14,6 +14,7 @@ import json import pytest + from braket.aws import AwsQpuArns TEST_S3_OBJ_CONTENTS = { diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 73b72e52..216e5544 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import pytest + from braket.aws import AwsQpu, AwsQpuArns, AwsQuantumSimulator, AwsQuantumSimulatorArns diff --git a/test/integ_tests/test_local_braket_simulator.py b/test/integ_tests/test_local_braket_simulator.py index 0e16face..2abda99d 100644 --- a/test/integ_tests/test_local_braket_simulator.py +++ b/test/integ_tests/test_local_braket_simulator.py @@ -12,7 +12,6 @@ # language governing permissions and limitations under the License. import pytest -from braket.devices import LocalSimulator from gate_model_device_testing_utils import ( no_result_types_bell_pair_testing, qubit_ordering_testing, @@ -30,6 +29,8 @@ result_types_zero_shots_bell_pair_testing, ) +from braket.devices import LocalSimulator + DEVICE = LocalSimulator() SHOTS = 8000 diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 4e84d88b..32f3ca24 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -12,7 +12,6 @@ # language governing permissions and limitations under the License. import pytest -from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns from gate_model_device_testing_utils import ( no_result_types_bell_pair_testing, qubit_ordering_testing, @@ -29,6 +28,8 @@ result_types_tensor_z_z_testing, ) +from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns + SHOTS = 8000 diff --git a/test/unit_tests/braket/aws/test_aws_qpu.py b/test/unit_tests/braket/aws/test_aws_qpu.py index a1dae9d1..51b7b6ee 100644 --- a/test/unit_tests/braket/aws/test_aws_qpu.py +++ b/test/unit_tests/braket/aws/test_aws_qpu.py @@ -15,9 +15,10 @@ import networkx as nx import pytest +from common_test_utils import MockDevices, run_and_assert + from braket.aws import AwsQpu, AwsQpuArns from braket.circuits import Circuit -from common_test_utils import MockDevices, run_and_assert @pytest.fixture diff --git a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py index 86ee623c..64bf6a0d 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py @@ -14,9 +14,10 @@ from unittest.mock import Mock, patch import pytest +from common_test_utils import MockDevices, run_and_assert + from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns from braket.circuits import Circuit -from common_test_utils import MockDevices, run_and_assert @pytest.fixture 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 623b72cb..f6472a77 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -17,12 +17,13 @@ from unittest.mock import Mock, patch import pytest +from common_test_utils import MockS3 + from braket.annealing.problem import Problem, ProblemType from braket.aws import AwsQuantumTask from braket.aws.aws_session import AwsSession from braket.circuits import Circuit from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult -from common_test_utils import MockS3 S3_TARGET = AwsSession.S3DestinationFolder("foo", "bar") diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index ecec2916..7010b43c 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -16,9 +16,10 @@ import pytest from botocore.exceptions import ClientError -from braket.aws import AwsQpuArns, AwsQuantumSimulatorArns, AwsSession from common_test_utils import MockDevices +from braket.aws import AwsQpuArns, AwsQuantumSimulatorArns, AwsSession + TEST_S3_OBJ_CONTENTS = { "TaskMetadata": { "Id": "UUID_blah", diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index b429f023..47857fed 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import pytest + from braket.circuits import AngledGate, Gate 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 77d40b9f..c17cdac6 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 + from braket.circuits import AsciiCircuitDiagram, Circuit, Gate, Instruction, Observable, Operator diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 8b979bd0..21cfda61 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -13,9 +13,10 @@ from unittest.mock import Mock -import braket.ir.jaqcd as jaqcd import numpy as np import pytest + +import braket.ir.jaqcd as jaqcd from braket.circuits import ( AsciiCircuitDiagram, Circuit, diff --git a/test/unit_tests/braket/circuits/test_circuit_helpers.py b/test/unit_tests/braket/circuits/test_circuit_helpers.py index 2ff39be5..d6a91515 100644 --- a/test/unit_tests/braket/circuits/test_circuit_helpers.py +++ b/test/unit_tests/braket/circuits/test_circuit_helpers.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import pytest + from braket.circuits import Circuit from braket.circuits.circuit_helpers import validate_circuit_and_shots diff --git a/test/unit_tests/braket/circuits/test_gate.py b/test/unit_tests/braket/circuits/test_gate.py index 3319e1d3..15392647 100644 --- a/test/unit_tests/braket/circuits/test_gate.py +++ b/test/unit_tests/braket/circuits/test_gate.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import pytest + from braket.circuits import Gate, QuantumOperator diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 04cc945d..a62512c9 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -11,9 +11,10 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -import braket.ir.jaqcd as ir import numpy as np import pytest + +import braket.ir.jaqcd as ir from braket.circuits import Circuit, Gate, Instruction, QubitSet from braket.ir.jaqcd.shared_models import ( Angle, diff --git a/test/unit_tests/braket/circuits/test_instruction.py b/test/unit_tests/braket/circuits/test_instruction.py index 136fb175..d120f6cc 100644 --- a/test/unit_tests/braket/circuits/test_instruction.py +++ b/test/unit_tests/braket/circuits/test_instruction.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import pytest + from braket.circuits import Gate, Instruction, Qubit, QubitSet diff --git a/test/unit_tests/braket/circuits/test_moments.py b/test/unit_tests/braket/circuits/test_moments.py index c3e1be38..cedb8643 100644 --- a/test/unit_tests/braket/circuits/test_moments.py +++ b/test/unit_tests/braket/circuits/test_moments.py @@ -14,6 +14,7 @@ from collections import OrderedDict import pytest + from braket.circuits import Gate, Instruction, Moments, MomentsKey, QubitSet diff --git a/test/unit_tests/braket/circuits/test_observable.py b/test/unit_tests/braket/circuits/test_observable.py index 2c3000c5..e5befcb6 100644 --- a/test/unit_tests/braket/circuits/test_observable.py +++ b/test/unit_tests/braket/circuits/test_observable.py @@ -13,6 +13,7 @@ import numpy as np import pytest + from braket.circuits import Observable, QuantumOperator, StandardObservable diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index e343b903..c7dd6f95 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -15,6 +15,7 @@ import numpy as np import pytest + from braket.circuits import Gate, Observable from braket.circuits.observables import observable_from_ir from braket.circuits.quantum_operator_helpers import get_pauli_eigenvalues diff --git a/test/unit_tests/braket/circuits/test_quantum_operator.py b/test/unit_tests/braket/circuits/test_quantum_operator.py index 22388ea9..ed3bedfc 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator.py @@ -13,6 +13,7 @@ import numpy as np import pytest + from braket.circuits import Operator, QuantumOperator diff --git a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py index ef22b91d..8694292d 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py @@ -2,6 +2,7 @@ import numpy as np import pytest + from braket.circuits.quantum_operator_helpers import ( get_pauli_eigenvalues, is_hermitian, diff --git a/test/unit_tests/braket/circuits/test_qubit.py b/test/unit_tests/braket/circuits/test_qubit.py index 7af83a9b..14d70192 100644 --- a/test/unit_tests/braket/circuits/test_qubit.py +++ b/test/unit_tests/braket/circuits/test_qubit.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import pytest + from braket.circuits import Qubit diff --git a/test/unit_tests/braket/circuits/test_qubit_set.py b/test/unit_tests/braket/circuits/test_qubit_set.py index 44563837..9bae63d5 100644 --- a/test/unit_tests/braket/circuits/test_qubit_set.py +++ b/test/unit_tests/braket/circuits/test_qubit_set.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import pytest + from braket.circuits import Qubit, QubitSet diff --git a/test/unit_tests/braket/circuits/test_result_type.py b/test/unit_tests/braket/circuits/test_result_type.py index 58794147..14351a66 100644 --- a/test/unit_tests/braket/circuits/test_result_type.py +++ b/test/unit_tests/braket/circuits/test_result_type.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import pytest + from braket.circuits import Observable, ObservableResultType, ResultType diff --git a/test/unit_tests/braket/circuits/test_result_types.py b/test/unit_tests/braket/circuits/test_result_types.py index 600ae2bb..4d0b5c93 100644 --- a/test/unit_tests/braket/circuits/test_result_types.py +++ b/test/unit_tests/braket/circuits/test_result_types.py @@ -11,8 +11,9 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -import braket.ir.jaqcd as ir import pytest + +import braket.ir.jaqcd as ir from braket.circuits import Circuit, Observable, ResultType from braket.circuits.result_types import ObservableResultType diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index a73d32d3..abbf1295 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -15,8 +15,9 @@ from typing import Any, Dict, Optional from unittest.mock import Mock -import braket.ir as ir import pytest + +import braket.ir as ir from braket.annealing import Problem, ProblemType from braket.circuits import Circuit from braket.devices import LocalSimulator, local_simulator 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 53fe89b6..b9e9b8ad 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 @@ -15,6 +15,7 @@ import numpy as np import pytest + from braket.aws.aws_qpu_arns import AwsQpuArns from braket.tasks import AnnealingQuantumTaskResult 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 a3cb5e57..490a48f1 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 @@ -17,6 +17,7 @@ import numpy as np import pytest + from braket.aws.aws_qpu_arns import AwsQpuArns from braket.circuits import Observable, ResultType from braket.ir import jaqcd 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 26765627..23ce67ae 100644 --- a/test/unit_tests/braket/tasks/test_local_quantum_task.py +++ b/test/unit_tests/braket/tasks/test_local_quantum_task.py @@ -15,6 +15,7 @@ import uuid import pytest + from braket.tasks import GateModelQuantumTaskResult from braket.tasks.local_quantum_task import LocalQuantumTask From 73ae1e1ec33a428bb48d6f3ef280774ece23579e Mon Sep 17 00:00:00 2001 From: rchilaka Date: Wed, 15 Jul 2020 14:01:35 -0700 Subject: [PATCH 0129/1165] Start polling for result of a task only when the customer wants it (#109) Start polling for result of a task only when the customer wants it Today, the SDK starts polling for task results as soon as the task object is created. However, we can delay the polling until the customer actually tries to retrieve the result. There is no impact on result retrieval latency due to this change. We are only postponing the polling process until it's absolutely necessary. --- src/braket/aws/aws_quantum_task.py | 43 +++++++++++++------ .../braket/aws/test_aws_quantum_task.py | 30 ++++++++++++- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 7e10053d..63653ff0 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -152,13 +152,6 @@ def __init__( self._metadata: Dict[str, Any] = {} self._result: Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult] = None - try: - asyncio.get_event_loop() - except Exception as e: - self._logger.debug(e) - self._logger.info("No event loop found; creating new event loop") - asyncio.set_event_loop(asyncio.new_event_loop()) - self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) @staticmethod def _aws_session_for_task_arn(task_arn: str) -> AwsSession: @@ -177,9 +170,17 @@ def id(self) -> str: """str: The ARN of the quantum task.""" return self._arn + def _cancel_future(self) -> None: + """Cancel the future if it exists. Else, create a cancelled future.""" + if hasattr(self, "_future"): + self._future.cancel() + else: + self._future = asyncio.Future() + self._future.cancel() + def cancel(self) -> None: """Cancel the quantum task. This cancels the future and the task in Amazon Braket.""" - self._future.cancel() + self._cancel_future() self._aws_session.cancel_quantum_task(self._arn) def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: @@ -235,12 +236,19 @@ def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult self._logger.warning("Task future was cancelled") return self._result - def async_result(self) -> asyncio.Task: - """ - Get the quantum task result asynchronously. Consecutive calls to this method return - the result cached from the most recent request. - """ - if self._future.done() and self._result is None: # timed out and no result + def _get_future(self): + try: + asyncio.get_event_loop() + except Exception as e: + self._logger.debug(e) + self._logger.info("No event loop found; creating new event loop") + asyncio.set_event_loop(asyncio.new_event_loop()) + + if not hasattr(self, "_future"): + self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) + elif ( + self._future.done() and not self._future.cancelled() and self._result is None + ): # timed out and no result task_status = self.metadata()["status"] if task_status in self.NO_RESULT_TERMINAL_STATES: self._logger.warning( @@ -250,6 +258,13 @@ def async_result(self) -> asyncio.Task: self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) return self._future + def async_result(self) -> asyncio.Task: + """ + 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. 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 f6472a77..205451ae 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -14,7 +14,7 @@ import asyncio import threading import time -from unittest.mock import Mock, patch +from unittest.mock import MagicMock, Mock, patch import pytest from common_test_utils import MockS3 @@ -129,6 +129,34 @@ def test_cancel(quantum_task): quantum_task._aws_session.cancel_quantum_task.assert_called_with(quantum_task.id) +def test_cancel_without_fetching_result(quantum_task): + quantum_task.cancel() + + assert quantum_task.result() is None + assert quantum_task._future.cancelled() + quantum_task._aws_session.cancel_quantum_task.assert_called_with(quantum_task.id) + + +def asyncio_get_event_loop_side_effect(*args, **kwargs): + yield ValueError("unit-test-exception") + mock = MagicMock() + while True: + yield mock + + +@patch("braket.aws.aws_quantum_task.asyncio") +def test_initialize_asyncio_event_loop_if_required(mock_asyncio, quantum_task): + mock_asyncio.get_event_loop.side_effect = asyncio_get_event_loop_side_effect() + mock_asyncio.set_event_loop.return_value = MagicMock() + mock_asyncio.new_event_loop.return_value = MagicMock() + + quantum_task._get_future() + + assert mock_asyncio.get_event_loop.call_count == 2 + assert mock_asyncio.set_event_loop.call_count == 1 + assert mock_asyncio.new_event_loop.call_count == 1 + + def test_result_circuit(circuit_task): _mock_metadata(circuit_task._aws_session, "COMPLETED") _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_1) From e299dda89afeaa03d8526983aabe3ba100c22f34 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 17 Jul 2020 15:11:54 -0400 Subject: [PATCH 0130/1165] Fix missing backticks/spaces that caused None to render incorrectly in the documentation --- src/braket/circuits/angled_gate.py | 2 +- src/braket/circuits/gate.py | 2 +- src/braket/circuits/quantum_operator.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index d14ed519..366b6696 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -35,7 +35,7 @@ def __init__(self, angle: float, qubit_count: int, ascii_symbols: Sequence[str]) Raises: ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or - `ascii_symbols` length != `qubit_count`, or `angle` is`None` + `ascii_symbols` length != `qubit_count`, or `angle` is `None` """ super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) if angle is None: diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index 78508239..08f53245 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -36,7 +36,7 @@ def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): correlate a symbol with that index. Raises: - ValueError: `qubit_count` is less than 1, `ascii_symbols` are None, or + ValueError: `qubit_count` is less than 1, `ascii_symbols` are `None`, or `ascii_symbols` length != `qubit_count` """ super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) diff --git a/src/braket/circuits/quantum_operator.py b/src/braket/circuits/quantum_operator.py index 6bdefb94..11717615 100644 --- a/src/braket/circuits/quantum_operator.py +++ b/src/braket/circuits/quantum_operator.py @@ -32,7 +32,7 @@ def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): correlate a symbol with that index. Raises: - ValueError: `qubit_count` is less than 1, `ascii_symbols` are None, or + ValueError: `qubit_count` is less than 1, `ascii_symbols` are `None`, or `ascii_symbols` length != `qubit_count` """ From d01dcafb8f76580209de32ec2dff515d103c6480 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 17 Jul 2020 15:44:09 -0400 Subject: [PATCH 0131/1165] Add backticks around formula --- src/braket/circuits/gates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 877a78ab..d85ccfcd 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -1322,7 +1322,7 @@ def unitary(targets: QubitSet, matrix: np.ndarray, display_name: str = "U") -> I Args: targets (QubitSet): Target qubits. matrix (numpy.ndarray): Unitary matrix which defines the gate. Matrix should be - compatible with the supplied targets, with 2 ** len(targets) == matrix.shape[0]. + compatible with the supplied targets, with `2 ** len(targets) == matrix.shape[0]`. display_name (str): Name to be used for an instance of this unitary gate for circuit diagrams. Defaults to `U`. From 8b89c63bf0c94def61fcf84532f1b09c5c6a3122 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 17 Jul 2020 15:44:25 -0400 Subject: [PATCH 0132/1165] Add backticks around None and target=None --- src/braket/circuits/result_types.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 9a67f17a..e8b8ad80 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -81,7 +81,7 @@ def __init__(self, state: List[str]): state (List[str]): list of quantum states as strings with "0" and "1" Raises: - ValueError: If state is None or an empty list, or + ValueError: If state is `None` or an empty list, or state is not a list of strings of '0' and '1' Examples: @@ -154,7 +154,7 @@ def __init__(self, target: QubitSetInput = None): """ Args: target (int, Qubit, or iterable of int / Qubit, optional): The target qubits that the - result type is requested for. Default is None, which means all qubits for the + result type is requested for. Default is `None`, which means all qubits for the circuit. Examples: @@ -185,7 +185,7 @@ def probability(target: QubitSetInput = None) -> ResultType: Args: target (int, Qubit, or iterable of int / Qubit, optional): The target qubits that the - result type is requested for. Default is None, which means all qubits for the + result type is requested for. Default is `None`, which means all qubits for the circuit. Returns: @@ -229,12 +229,12 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): Args: observable (Observable): the observable for the result type target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the - result type is requested for. Default is None, which means the observable must + 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. + qubits, or if `target=None` and the observable's qubit count is not 1. Examples: >>> ResultType.Expectation(observable=Observable.Z(), target=0) @@ -264,7 +264,7 @@ def expectation(observable: Observable, target: QubitSetInput = None) -> ResultT Args: observable (Observable): the observable for the result type target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the - result type is requested for. Default is None, which means the observable must + 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. Returns: @@ -296,12 +296,12 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): Args: observable (Observable): the observable for the result type target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the - result type is requested for. Default is None, which means the observable must + 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. + qubits, or if `target=None` and the observable's qubit count is not 1. Examples: >>> ResultType.Sample(observable=Observable.Z(), target=0) @@ -331,7 +331,7 @@ def sample(observable: Observable, target: QubitSetInput = None) -> ResultType: Args: observable (Observable): the observable for the result type target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the - result type is requested for. Default is None, which means the observable must + 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. Returns: @@ -364,12 +364,12 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): Args: observable (Observable): the observable for the result type target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the - result type is requested for. Default is None, which means the observable must + 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. + qubits, or if `target=None` and the observable's qubit count is not 1. Examples: >>> ResultType.Variance(observable=Observable.Z(), target=0) @@ -399,7 +399,7 @@ def variance(observable: Observable, target: QubitSetInput = None) -> ResultType Args: observable (Observable): the observable for the result type target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the - result type is requested for. Default is None, which means the observable must + result type is requested for. Default is `None`, which means the observable must only operate on 1 qubit and it will be applied to all qubits in parallel Returns: From 23b44bb5ca7bac527ca86a5a0f01074913a5ac99 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 17 Jul 2020 16:00:56 -0400 Subject: [PATCH 0133/1165] Backticks for variables and types in circuits.py --- src/braket/circuits/circuit.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 1428110b..96767e4e 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -204,10 +204,10 @@ def add_result_type( result_type (ResultType): `ResultType` to add into `self`. target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the `result_type`. - Default = None. + Default = `None`. target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of qubit mappings to apply to the `result_type.target`. Key is the qubit in - `result_type.target` and the value is what the key will be changed to. Default = {}. + `result_type.target` and the value is what the key will be changed to. Default = `{}`. Note: target and target_mapping will only be applied to those requested result types with @@ -300,10 +300,10 @@ def add_instruction( target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the `instruction`. If a single qubit gate, an instruction is created for every index in `target`. - Default = None. + Default = `None`. target_mapping (dictionary[int or Qubit, int or Qubit], optional): 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 = {}. + `instruction.target` and the value is what the key will be changed to. Default = `{}`. Returns: Circuit: self @@ -368,10 +368,10 @@ def add_circuit( target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the supplied circuit. This is a macro over `target_mapping`; `target` is converted to a `target_mapping` by zipping together a sorted `circuit.qubits` and `target`. - Default = None. + Default = `None`. target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of qubit mappings to apply to the qubits of `circuit.instructions`. Key is the qubit - to map, and the Value is what to change it to. Default = {}. + to map, and the value is what to change it to. Default = `{}`. Returns: Circuit: self @@ -436,7 +436,7 @@ def add(self, addable: AddableTypes, *args, **kwargs) -> Circuit: allows. Args: - addable (AddableTypes): The item(s) to add to self. Default = None. + addable (AddableTypes): The item(s) to add to self. Default = `None`. *args: Variable length argument list. **kwargs: Arbitrary keyword arguments. @@ -493,7 +493,7 @@ def diagram(self, circuit_diagram_class=AsciiCircuitDiagram) -> str: Args: circuit_diagram_class (Class, optional): A `CircuitDiagram` class that builds the - diagram for this circuit. Default = AsciiCircuitDiagram. + diagram for this circuit. Default = `AsciiCircuitDiagram`. Returns: str: An ASCII string circuit diagram. @@ -565,7 +565,7 @@ def subroutine(register=False): Args: register (bool, optional): If `True`, adds this subroutine into the `Circuit` class. - Default = False. + Default = `False`. Examples: >>> @circuit.subroutine(register=True) From a6b6a99fac90794189278d0bc9a9af08e9e01649 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 17 Jul 2020 16:01:25 -0400 Subject: [PATCH 0134/1165] Backticks for types and variables in circuit_helpers.py --- src/braket/circuits/circuit_helpers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/braket/circuits/circuit_helpers.py b/src/braket/circuits/circuit_helpers.py index b6c134cd..5f493208 100644 --- a/src/braket/circuits/circuit_helpers.py +++ b/src/braket/circuits/circuit_helpers.py @@ -23,9 +23,9 @@ def validate_circuit_and_shots(circuit: Circuit, shots: int) -> None: shots (int): shots to validate Raises: - ValueError: If no result types specified for circuit and shots=0. - See `braket.circuit.result_types. Or, if `StateVector` or `Amplitude` - are specified as result types when shots > 0. + ValueError: If no result types specified for circuit and `shots=0`. + See `braket.circuit.result_types`. Or, if `StateVector` or `Amplitude` + are specified as result types when `shots > 0`. """ if not shots and not circuit.result_types: raise ValueError( From a9acf033ba567f8daba1cebf66650e9ff733bf94 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 17 Jul 2020 16:01:50 -0400 Subject: [PATCH 0135/1165] Backticks for types and variables in instruction.py --- src/braket/circuits/instruction.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index b6c2feb6..b1db6400 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -33,7 +33,7 @@ class Instruction: def __init__(self, operator: InstructionOperator, target: QubitSetInput): """ - InstructionOperator includes objects of type Gate only. + InstructionOperator includes objects of type `Gate` only. Args: operator (InstructionOperator): Operator for the instruction. @@ -41,9 +41,9 @@ def __init__(self, operator: InstructionOperator, target: QubitSetInput): applied to. Raises: - ValueError: If `operator` is empty or any integer in `target` does not meet the Qubit - or QubitSet class requirements. - TypeError: If a Qubit class can't be constructed from `target` due to an incorrect + ValueError: If `operator` is empty or any integer in `target` does not meet the `Qubit` + or `QubitSet` class requirements. + TypeError: If a `Qubit` class can't be constructed from `target` due to an incorrect `typing`. Examples: @@ -96,7 +96,7 @@ def copy( Args: target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of qubit mappings to apply to the target. Key is the qubit in this `target` and the - value is what the key is changed to. Default = {}. + value is what the key is changed to. Default = `{}`. target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the new instruction. From 91ef331bb58d1c3b9f0e3f694c43ef949d37e9fd Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 17 Jul 2020 16:02:52 -0400 Subject: [PATCH 0136/1165] Backticks and slight grammar fix for moments.py --- src/braket/circuits/moments.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index aab9cede..5e44cd49 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -132,7 +132,7 @@ def add(self, instructions: Iterable[Instruction]) -> None: Args: instructions (Iterable[Instruction]): Instructions to add to self. The instruction - are added to the max time slice in which the instruction fits. + is added to the max time slice in which the instruction fits. """ for instruction in instructions: self._add(instruction) @@ -174,10 +174,10 @@ def get(self, key: MomentsKey, default=None) -> Instruction: Args: key (MomentsKey): Key of the instruction to fetch. - default (Any, optional): Value to return if `key` is not in moment. Default = None. + default (Any, optional): Value to return if `key` is not in `moments`. Default = `None`. Returns: - Instruction: moments[key] if key in moments, else `default` is returned. + Instruction: `moments[key]` if `key` in `moments`, else `default` is returned. """ return self._moments.get(key, default) From 380319d76334aaf27516f33280de758f78719c0f Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 17 Jul 2020 16:03:45 -0400 Subject: [PATCH 0137/1165] Backticks for class in observable.py --- src/braket/circuits/observable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index 416eb16a..7927c235 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -50,7 +50,7 @@ def eigenvalues(self) -> np.ndarray: @classmethod def register_observable(cls, observable: Observable) -> None: - """Register an observable implementation by adding it into the Observable class. + """Register an observable implementation by adding it into the `Observable` class. Args: observable (Observable): Observable class to register. From d1ad977dd393771727a186757657b65a40c41b6a Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 17 Jul 2020 16:04:51 -0400 Subject: [PATCH 0138/1165] Backticks for qubit.py --- src/braket/circuits/qubit.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/braket/circuits/qubit.py b/src/braket/circuits/qubit.py index 34431a66..8eb8257c 100644 --- a/src/braket/circuits/qubit.py +++ b/src/braket/circuits/qubit.py @@ -49,11 +49,11 @@ def __str__(self): @staticmethod def new(qubit: QubitInput) -> Qubit: """ - Helper constructor - if input is a Qubit it returns the same value, - else a new Qubit is constructed. + Helper constructor - if input is a `Qubit` it returns the same value, + else a new `Qubit` is constructed. Args: - qubit (int or Qubit): Qubit index. If type == Qubit then the `qubit` is returned. + qubit (int or Qubit): `Qubit` index. If `type == Qubit` then the `qubit` is returned. """ if isinstance(qubit, Qubit): From 0467e54f675d6653d243de4c2d9613ee2f360081 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 17 Jul 2020 16:05:54 -0400 Subject: [PATCH 0139/1165] Backticks for variables and types in qubit_set.py --- src/braket/circuits/qubit_set.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/braket/circuits/qubit_set.py b/src/braket/circuits/qubit_set.py index 45b250e2..9e03c527 100644 --- a/src/braket/circuits/qubit_set.py +++ b/src/braket/circuits/qubit_set.py @@ -27,7 +27,7 @@ class QubitSet(IndexedSet): An ordered, unique set of quantum bits. Note: - QubitSet implements __hash__() but is a mutable object, therefore be careful when + QubitSet implements `__hash__()` but is a mutable object, therefore be careful when mutating this object. """ @@ -35,7 +35,7 @@ def __init__(self, qubits: QubitSetInput = None): """ Args: qubits (int, Qubit, or iterable of int / Qubit, optional): Qubits to be included in - the QubitSet. Default is None. + the `QubitSet`. Default is `None`. Examples: >>> qubits = QubitSet([0, 1]) @@ -67,7 +67,7 @@ def _flatten(other): 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: From 14dffea81d41ff25b17358ae85e14652f3695b90 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 17 Jul 2020 16:06:49 -0400 Subject: [PATCH 0140/1165] Backticks for variables and types for result_type.py --- src/braket/circuits/result_type.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index ee4f429e..c9cb2b8b 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -34,7 +34,7 @@ def __init__(self, ascii_symbols: List[str]): printing a diagram of circuits. Raises: - ValueError: `ascii_symbols` is None + ValueError: `ascii_symbols` is `None` """ if ascii_symbols is None: @@ -80,7 +80,7 @@ def copy(self, target_mapping: Dict[QubitInput, QubitInput] = {}, target: QubitS Args: target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of qubit mappings to apply to the target. Key is the qubit in this `target` and the - value is what the key is changed to. Default = {}. + value is what the key is changed to. Default = `{}`. target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the new instruction. @@ -115,10 +115,10 @@ def copy(self, target_mapping: Dict[QubitInput, QubitInput] = {}, target: QubitS @classmethod def register_result_type(cls, result_type: "ResultType"): - """Register a result type implementation by adding it into the ResultType class. + """Register a result type implementation by adding it into the `ResultType` class. Args: - result_type (ResultType): ResultType instance to register. + result_type (ResultType): `ResultType` instance to register. """ setattr(cls, result_type.__name__, result_type) @@ -143,14 +143,14 @@ def __init__( Args: observable (Observable): the observable for the result type target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the - result type is requested for. Default is None, which means the observable must + result type is requested for. Default is `None`, which means the observable must only operate on 1 qubit and it will be applied to all qubits in parallel Raises: ValueError: if target=None and the observable's qubit count is not 1. - Or, if target!=None and the observable's qubit count and the number of target qubits - are not equal. Or, if target!=None and the observable's qubit count and - the number of ascii_symbols are not equal. + Or, if `target!=None` and the observable's qubit count and the number of target qubits + are not equal. Or, if `target!=None` and the observable's qubit count and + the number of `ascii_symbols` are not equal. """ super().__init__(ascii_symbols) self._observable = observable From 4c8477f00c3401f8729a7f586eac06247cbfae14 Mon Sep 17 00:00:00 2001 From: Lin Date: Fri, 17 Jul 2020 19:48:15 -0700 Subject: [PATCH 0141/1165] Remove braket/schema_common from braket-ir from coverage. --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 5a54a877..7ed24b06 100644 --- a/.coveragerc +++ b/.coveragerc @@ -5,6 +5,7 @@ source = braket omit = **/braket/ir/* + **/braket/schema_common/* **/braket/simulator/* [paths] From 7c15851b792a63f167e6e2aebb9885c7b9eed6a6 Mon Sep 17 00:00:00 2001 From: Lin Date: Fri, 17 Jul 2020 19:49:48 -0700 Subject: [PATCH 0142/1165] Reformat comments to stay within the 100-char line length limit. --- src/braket/circuits/circuit.py | 6 ++++-- src/braket/circuits/result_type.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 96767e4e..2b5cacc2 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -207,7 +207,8 @@ def add_result_type( Default = `None`. target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of qubit mappings to apply to the `result_type.target`. Key is the qubit in - `result_type.target` and the value is what the key will be changed to. Default = `{}`. + `result_type.target` and the value is what the key will be changed to. + Default = `{}`. Note: target and target_mapping will only be applied to those requested result types with @@ -303,7 +304,8 @@ def add_instruction( Default = `None`. target_mapping (dictionary[int or Qubit, int or Qubit], optional): 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 = `{}`. + `instruction.target` and the value is what the key will be changed to. + Default = `{}`. Returns: Circuit: self diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index c9cb2b8b..262b025d 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -148,8 +148,8 @@ def __init__( Raises: ValueError: if target=None and the observable's qubit count is not 1. - Or, if `target!=None` and the observable's qubit count and the number of target qubits - are not equal. Or, if `target!=None` and the observable's qubit count and + Or, if `target!=None` and the observable's qubit count and the number of target + qubits are not equal. Or, if `target!=None` and the observable's qubit count and the number of `ascii_symbols` are not equal. """ super().__init__(ascii_symbols) From ab564a3f2afc78e201a17c0c8937a3f56113fff6 Mon Sep 17 00:00:00 2001 From: Lin Date: Mon, 20 Jul 2020 10:40:22 -0700 Subject: [PATCH 0143/1165] Change copy method for the Circuit class to public. --- src/braket/circuits/circuit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 2b5cacc2..a8cc827a 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -521,7 +521,7 @@ def to_ir(self) -> Program: basis_rotation_instructions=ir_basis_rotation_instructions, ) - def _copy(self) -> Circuit: + def copy(self) -> Circuit: """ Return a shallow copy of the circuit. @@ -536,7 +536,7 @@ def __iadd__(self, addable: AddableTypes) -> Circuit: return self.add(addable) def __add__(self, addable: AddableTypes) -> Circuit: - new = self._copy() + new = self.copy() new.add(addable) return new From 8379e9c2a90ddc13ae13090aad555db766d7c8f9 Mon Sep 17 00:00:00 2001 From: Lin Date: Mon, 20 Jul 2020 11:50:26 -0700 Subject: [PATCH 0144/1165] Recreate private copy method for the Circuit class. --- src/braket/circuits/circuit.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index a8cc827a..6705bdfb 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -521,6 +521,11 @@ def to_ir(self) -> Program: basis_rotation_instructions=ir_basis_rotation_instructions, ) + def _copy(self) -> Circuit: + copy = Circuit().add(self.instructions) + copy.add(self.result_types) + return copy + def copy(self) -> Circuit: """ Return a shallow copy of the circuit. @@ -528,15 +533,13 @@ def copy(self) -> Circuit: Returns: Circuit: A shallow copy of the circuit. """ - copy = Circuit().add(self.instructions) - copy.add(self.result_types) - return copy + return self._copy() def __iadd__(self, addable: AddableTypes) -> Circuit: return self.add(addable) def __add__(self, addable: AddableTypes) -> Circuit: - new = self.copy() + new = self._copy() new.add(addable) return new From 05774120f064e72b0bb759492d348ebeb9efc76c Mon Sep 17 00:00:00 2001 From: Lin Date: Mon, 20 Jul 2020 12:20:18 -0700 Subject: [PATCH 0145/1165] Add unit tests for public copy function for Circuit class. --- test/unit_tests/braket/circuits/test_circuit.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 21cfda61..23974e54 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -308,6 +308,21 @@ def test_add_with_circuit_with_target(bell_pair): assert circ == expected +def test_circuit_copy(h, bell_pair, cnot_instr): + original = Circuit().add(h).add(bell_pair).add(cnot_instr) + copy = original.copy() + + assert copy is not original + assert copy == original + + +def test_circuit_copy_with_modification(h, bell_pair, cnot_instr): + original = Circuit().add(h).add(bell_pair) + copy = original.copy().add(cnot_instr) + + assert copy != original + + def test_iadd_operator(cnot_instr, h): circ = Circuit() circ += h From c0916c2ecddd80c371492c0ad205f2a03fd37173 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 22 Jul 2020 15:50:29 -0400 Subject: [PATCH 0146/1165] Fix indenting for ValueError --- src/braket/circuits/result_types.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index e8b8ad80..da2b2d34 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -234,7 +234,7 @@ def __init__(self, observable: Observable, target: QubitSetInput = 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. + qubits, or if `target=None` and the observable's qubit count is not 1. Examples: >>> ResultType.Expectation(observable=Observable.Z(), target=0) @@ -301,7 +301,7 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): 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. + qubits, or if `target=None` and the observable's qubit count is not 1. Examples: >>> ResultType.Sample(observable=Observable.Z(), target=0) @@ -369,7 +369,7 @@ def __init__(self, observable: Observable, target: QubitSetInput = 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. + qubits, or if `target=None` and the observable's qubit count is not 1. Examples: >>> ResultType.Variance(observable=Observable.Z(), target=0) From 538a5d826c8a6114af17e2358da69f1c318b5bff Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Fri, 24 Jul 2020 12:58:16 -0700 Subject: [PATCH 0147/1165] Use new task result schemas (#114) * Use annealing task result schema * Migrate to gate model task result format (#116) --- .coveragerc | 1 + src/braket/devices/local_simulator.py | 8 +- .../tasks/annealing_quantum_task_result.py | 51 ++- .../tasks/gate_model_quantum_task_result.py | 99 +++-- src/braket/tasks/local_quantum_task.py | 2 +- .../braket/aws/common_test_utils.py | 110 ++---- .../braket/aws/test_aws_quantum_task.py | 30 +- .../braket/devices/test_local_simulator.py | 83 ++-- .../test_annealing_quantum_task_result.py | 127 +++--- .../test_gate_model_quantum_task_result.py | 362 +++++++----------- .../braket/tasks/test_local_quantum_task.py | 23 +- 11 files changed, 430 insertions(+), 466 deletions(-) diff --git a/.coveragerc b/.coveragerc index 7ed24b06..81bdfe72 100644 --- a/.coveragerc +++ b/.coveragerc @@ -6,6 +6,7 @@ source = omit = **/braket/ir/* **/braket/schema_common/* + **/braket/task_result/* **/braket/simulator/* [paths] diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index cf4a73d4..c69ad920 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -130,8 +130,8 @@ def _(circuit: Circuit, simulator: BraketSimulator, shots, *args, **kwargs): validate_circuit_and_shots(circuit, shots) program = circuit.to_ir() qubits = circuit.qubit_count - results_dict = simulator.run(program, qubits, shots, *args, **kwargs) - return GateModelQuantumTaskResult.from_dict(results_dict) + results = simulator.run(program, qubits, shots, *args, **kwargs) + return GateModelQuantumTaskResult.from_object(results) @_run_internal.register @@ -139,5 +139,5 @@ def _(problem: Problem, simulator: BraketSimulator, shots, *args, **kwargs): if "annealing" not in simulator.properties["supportedIrTypes"]: raise NotImplementedError(f"{type(simulator)} does not support quantum annealing problems") ir = problem.to_ir() - results_dict = simulator.run(ir, shots, *args, *kwargs) - return AnnealingQuantumTaskResult.from_dict(results_dict) + results = simulator.run(ir, shots, *args, *kwargs) + return AnnealingQuantumTaskResult.from_object(results) diff --git a/src/braket/tasks/annealing_quantum_task_result.py b/src/braket/tasks/annealing_quantum_task_result.py index cc20b23d..5e0731e6 100644 --- a/src/braket/tasks/annealing_quantum_task_result.py +++ b/src/braket/tasks/annealing_quantum_task_result.py @@ -13,12 +13,13 @@ from __future__ import annotations -import json from dataclasses import dataclass -from typing import Any, Dict import numpy +from braket.annealing import ProblemType +from braket.task_result import AdditionalMetadata, AnnealingTaskResult, TaskMetadata + @dataclass class AnnealingQuantumTaskResult: @@ -32,16 +33,16 @@ class AnnealingQuantumTaskResult: the number of times the solutions occurred, and 'value' (numpy.ndarray) the output or energy of the solutions. variable_count (int): the number of variables - problem_type (str): the type of problem ('ising' or 'qubo') - task_metadata (Dict[str, Any]): Dictionary of task metadata. - additional_metadata (Dict[str, Any]): A dictionary of additional device-specific metadata + problem_type (ProblemType): the type of annealing problem + task_metadata (TaskMetadata): Task metadata. + additional_metadata (AdditionalMetadata): Additional metadata about the task """ record_array: numpy.recarray variable_count: int - problem_type: str - task_metadata: Dict[str, Any] - additional_metadata: Dict[str, Any] + problem_type: ProblemType + task_metadata: TaskMetadata + additional_metadata: AdditionalMetadata def data(self, selected_fields=None, sorted_by="value", reverse=False): """ @@ -92,17 +93,18 @@ def __eq__(self, other) -> bool: return NotImplemented @staticmethod - def from_dict(result: Dict[str, Any]): + def from_object(result: AnnealingTaskResult) -> AnnealingQuantumTaskResult: """ - Create AnnealingQuantumTaskResult from dict + Create AnnealingQuantumTaskResult from AnnealingTaskResult object Args: - result (Dict[str, Any]): Results dict with AnnealingQuantumTaskResult attributes as keys + result (AnnealingTaskResult): AnnealingTaskResult object Returns: - AnnealingQuantumTaskResult: An AnnealingQuantumTaskResult based on the given dict + AnnealingQuantumTaskResult: An AnnealingQuantumTaskResult based on the + given result object """ - return AnnealingQuantumTaskResult._from_dict_internal(result) + return AnnealingQuantumTaskResult._from_object(result) @staticmethod def from_string(result: str) -> AnnealingQuantumTaskResult: @@ -115,26 +117,23 @@ def from_string(result: str) -> AnnealingQuantumTaskResult: Returns: AnnealingQuantumTaskResult: An AnnealingQuantumTaskResult based on the given string """ - return AnnealingQuantumTaskResult._from_dict_internal(json.loads(result)) + return AnnealingQuantumTaskResult._from_object(AnnealingTaskResult.parse_raw(result)) @classmethod - def _from_dict_internal(cls, result: Dict[str, Any]): - solutions = numpy.asarray(result["Solutions"], dtype=int) - values = numpy.asarray(result["Values"], dtype=float) - if result["SolutionCounts"] is None: + def _from_object(cls, result: AnnealingTaskResult): + solutions = numpy.asarray(result.solutions, dtype=int) + values = numpy.asarray(result.values, dtype=float) + if result.solutionCounts is None: solution_counts = numpy.ones(len(solutions), dtype=int) else: - solution_counts = numpy.asarray(result["SolutionCounts"], dtype=int) + solution_counts = numpy.asarray(result.solutionCounts, dtype=int) record_array = AnnealingQuantumTaskResult._create_record_array( solutions, solution_counts, values ) - variable_count = result["VariableCount"] - problem_type = result["ProblemType"] - task_metadata = result["TaskMetadata"] - additional_metadata = {} - for key in result.keys(): - if key.endswith("Metadata") and key != "TaskMetadata": - additional_metadata[key] = result[key] + variable_count = result.variableCount + problem_type = ProblemType[result.additionalMetadata.action.type.value] + task_metadata = result.taskMetadata + additional_metadata = result.additionalMetadata return cls( record_array=record_array, variable_count=variable_count, diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index b3d43279..39541514 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -21,6 +21,12 @@ from braket.circuits import Observable, ResultType, StandardObservable from braket.circuits.observables import observable_from_ir +from braket.task_result import ( + AdditionalMetadata, + GateModelTaskResult, + ResultTypeValue, + TaskMetadata, +) T = TypeVar("T") @@ -32,8 +38,8 @@ class GateModelQuantumTaskResult: to be initialized by a QuantumTask class. Args: - task_metadata (Dict[str, Any]): Dictionary of task metadata. `task_metadata` must have - keys 'Id', 'Shots', 'Ir', and 'IrType'. + task_metadata (TaskMetadata): Task metadata. + additional_metadata (AdditionalMetadata): Additional metadata about the task result_types (List[Dict[str, Any]]): List of dictionaries where each dictionary has two keys: 'Type' (the result type in IR JSON form) and 'Value' (the result value for this result type). @@ -70,7 +76,8 @@ class GateModelQuantumTaskResult: Only available when shots > 0. """ - task_metadata: Dict[str, Any] + task_metadata: TaskMetadata + additional_metadata: AdditionalMetadata result_types: List[Dict[str, str]] values: List[Any] measurements: np.ndarray = None @@ -96,18 +103,18 @@ def get_value_by_result_type(self, result_type: ResultType) -> Any: ValueError: If result type is not found in result. Result types must be added to the circuit before the circuit is run on a device. """ - rt_json = result_type.to_ir().json() + rt_ir = result_type.to_ir() for rt in self.result_types: - if rt_json == json.dumps(rt["Type"]): - return rt["Value"] + if rt_ir == rt.type: + return rt.value raise ValueError( "Result type not found in result. " + "Result types must be added to circuit before circuit is run on device." ) def __eq__(self, other) -> bool: - if isinstance(other, GateModelQuantumTaskResult) and self.task_metadata.get("Id"): - return self.task_metadata["Id"] == other.task_metadata["Id"] + if isinstance(other, GateModelQuantumTaskResult): + return self.task_metadata.id == other.task_metadata.id return NotImplemented @staticmethod @@ -178,7 +185,7 @@ def measurements_from_measurement_probabilities( return np.asarray(measurements_list, dtype=int) @staticmethod - def from_dict(result: Dict[str, Any]): + def from_object(result: GateModelQuantumTaskResult): """ Create GateModelQuantumTaskResult from dict. @@ -192,7 +199,7 @@ def from_dict(result: Dict[str, Any]): ValueError: If neither "Measurements" nor "MeasurementProbabilities" is a key in the result dict """ - return GateModelQuantumTaskResult._from_dict_internal(result) + return GateModelQuantumTaskResult._from_object_internal(result) @staticmethod def from_string(result: str) -> GateModelQuantumTaskResult: @@ -209,32 +216,34 @@ def from_string(result: str) -> GateModelQuantumTaskResult: ValueError: If neither "Measurements" nor "MeasurementProbabilities" is a key in the result dict """ - json_obj = json.loads(result) - for result_type in json_obj.get("ResultTypes", []): - type = result_type["Type"]["type"] - if 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]) - return GateModelQuantumTaskResult._from_dict_internal(json_obj) + obj = GateModelTaskResult.parse_raw(result) + if obj.resultTypes: + for result_type in obj.resultTypes: + type = result_type.type.type + if 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]) + return GateModelQuantumTaskResult._from_object_internal(obj) @classmethod - def _from_dict_internal(cls, result: Dict[str, Any]): - if result["TaskMetadata"]["Shots"] > 0: - return GateModelQuantumTaskResult._from_dict_internal_computational_basis_sampling( + def _from_object_internal(cls, result: GateModelTaskResult): + if result.taskMetadata.shots > 0: + return GateModelQuantumTaskResult._from_object_internal_computational_basis_sampling( result ) else: return GateModelQuantumTaskResult._from_dict_internal_simulator_only(result) @classmethod - def _from_dict_internal_computational_basis_sampling(cls, result: Dict[str, Any]): - task_metadata = result["TaskMetadata"] - if "Measurements" in result: - measurements = np.asarray(result["Measurements"], dtype=int) + def _from_object_internal_computational_basis_sampling(cls, result: GateModelTaskResult): + task_metadata = result.taskMetadata + additional_metadata = result.additionalMetadata + if result.measurements: + measurements = np.asarray(result.measurements, dtype=int) m_counts = GateModelQuantumTaskResult.measurement_counts_from_measurements(measurements) m_probs = GateModelQuantumTaskResult.measurement_probabilities_from_measurement_counts( m_counts @@ -242,9 +251,9 @@ def _from_dict_internal_computational_basis_sampling(cls, result: Dict[str, Any] measurements_copied_from_device = True m_counts_copied_from_device = False m_probabilities_copied_from_device = False - elif "MeasurementProbabilities" in result: - shots = task_metadata["Shots"] - m_probs = result["MeasurementProbabilities"] + elif result.measurementProbabilities: + shots = task_metadata.shots + m_probs = result.measurementProbabilities measurements = GateModelQuantumTaskResult.measurements_from_measurement_probabilities( m_probs, shots ) @@ -254,20 +263,22 @@ def _from_dict_internal_computational_basis_sampling(cls, result: Dict[str, Any] m_probabilities_copied_from_device = True else: raise ValueError( - 'One of "Measurements" or "MeasurementProbabilities" must be in the result dict' + 'One of "measurements" or "measurementProbabilities" must be populated in', + " the result obj", ) - measured_qubits = result["MeasuredQubits"] + 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" ) result_types = GateModelQuantumTaskResult._calculate_result_types( - result["TaskMetadata"]["Ir"], measurements, measured_qubits + additional_metadata.action.json(), measurements, measured_qubits ) - values = [rt["Value"] for rt in result_types] + values = [rt.value for rt in result_types] return cls( task_metadata=task_metadata, + additional_metadata=additional_metadata, result_types=result_types, values=values, measurements=measurements, @@ -280,11 +291,17 @@ def _from_dict_internal_computational_basis_sampling(cls, result: Dict[str, Any] ) @classmethod - def _from_dict_internal_simulator_only(cls, result: Dict[str, Any]): - task_metadata = result["TaskMetadata"] - result_types = result["ResultTypes"] - values = [rt["Value"] for rt in result_types] - return cls(task_metadata=task_metadata, result_types=result_types, values=values) + def _from_dict_internal_simulator_only(cls, result: GateModelTaskResult): + task_metadata = result.taskMetadata + additional_metadata = result.additionalMetadata + result_types = result.resultTypes + values = [rt.value for rt in result_types] + return cls( + task_metadata=task_metadata, + additional_metadata=additional_metadata, + result_types=result_types, + values=values, + ) @staticmethod def _calculate_result_types( @@ -329,7 +346,7 @@ def _calculate_result_types( ) else: raise ValueError(f"Unknown result type {rt_type}") - result_types.append({"Type": result_type, "Value": value}) + result_types.append(ResultTypeValue.construct(type=result_type, value=value)) return result_types @staticmethod diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py index 653cd154..b5a8eb0a 100644 --- a/src/braket/tasks/local_quantum_task.py +++ b/src/braket/tasks/local_quantum_task.py @@ -24,7 +24,7 @@ class LocalQuantumTask(QuantumTask): """ def __init__(self, result: Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]): - self._id = result.task_metadata["Id"] + self._id = result.task_metadata.id self._result = result @property diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 89ff292a..c19d96fb 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -191,82 +191,56 @@ class MockDevices: class MockS3: - MOCK_S3_RESULT_1 = json.dumps( + MOCK_S3_RESULT_GATE_MODEL = json.dumps( { - "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], - "MeasuredQubits": [0, 1], - "TaskMetadata": { - "Id": "UUID_blah_1", - "Status": "COMPLETED", - "BackendArn": AwsQpuArns.RIGETTI, - "Shots": 1000, - "Ir": "{}", + "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", }, - } - ) - - MOCK_S3_RESULT_2 = json.dumps( - { - "Measurements": [[0, 0], [0, 0], [0, 0], [1, 1]], - "MeasuredQubits": [0, 1], - "TaskMetadata": { - "Id": "UUID_blah_2", - "Status": "COMPLETED", - "BackendArn": AwsQpuArns.RIGETTI, - "Shots": 1000, - "Ir": "{}", - }, - } - ) - - MOCK_S3_RESULT_3 = json.dumps( - { - "TaskMetadata": { - "Id": "1231231", - "Status": "COMPLETED", - "BackendArn": "test_arn", - "BackendTranslation": "...", - "Created": 1574140385.0697668, - "Modified": 1574140388.6908717, - "Shots": 100, - "GateModelConfig": {"QubitCount": 6}, - "Ir": "{}", + "additionalMetadata": { + "action": { + "braketSchemaHeader": {"name": "braket.ir.jaqcd.program", "version": "1"}, + "instructions": [{"control": 0, "target": 1, "type": "cnot"}], + }, }, - "MeasurementProbabilities": {"011000": 0.9999999999999982}, - "MeasuredQubits": [0, 1], } ) - MOCK_S3_RESULT_4 = json.dumps( + MOCK_S3_RESULT_ANNEALING = json.dumps( { - "Solutions": [[-1, -1, -1, -1], [1, -1, 1, 1], [1, -1, -1, 1]], - "VariableCount": 4, - "Values": [0.0, 1.0, 2.0], - "SolutionCounts": None, - "ProblemType": "ising", - "DWaveMetadata": { - "ActiveVariables": [0], - "Timing": { - "QpuSamplingTime": 1575, - "QpuAnnealTimePerSample": 20, - "QpuReadoutTimePerSample": 274, - "QpuAccessTime": 10917, - "QpuAccessOverheadTime": 3382, - "QpuProgrammingTime": 9342, - "QpuDelayTimePerSample": 21, - "TotalPostProcessingTime": 117, - "PostProcessingOverheadTime": 117, - "TotalRealTime": 10917, - "RunTimeChip": 1575, - "AnnealTimePerRun": 20, - "ReadoutTimePerRun": 274, + "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": AwsQpuArns.DWAVE,}, + "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, + }, }, - }, - "TaskMetadata": { - "Id": "UUID_blah_1", - "Status": "COMPLETED", - "BackendArn": AwsQpuArns.DWAVE, - "Shots": 5, }, } ) 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 205451ae..b9e4e775 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -159,9 +159,9 @@ def test_initialize_asyncio_event_loop_if_required(mock_asyncio, quantum_task): def test_result_circuit(circuit_task): _mock_metadata(circuit_task._aws_session, "COMPLETED") - _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_1) + _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_GATE_MODEL) - expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) + expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_GATE_MODEL) assert circuit_task.result() == expected s3_bucket = circuit_task.metadata()["resultsS3Bucket"] @@ -172,15 +172,15 @@ def test_result_circuit(circuit_task): @pytest.mark.xfail(raises=ValueError) def test_result_unknown_ir_type(circuit_task): _mock_metadata(circuit_task._aws_session, "COMPLETED", "unsupported_ir_type") - _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_1) + _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_GATE_MODEL) circuit_task.result() def test_result_annealing(annealing_task): _mock_metadata(annealing_task._aws_session, "COMPLETED", "annealing") - _mock_s3(annealing_task._aws_session, MockS3.MOCK_S3_RESULT_4) + _mock_s3(annealing_task._aws_session, MockS3.MOCK_S3_RESULT_ANNEALING) - expected = AnnealingQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_4) + expected = AnnealingQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_ANNEALING) assert annealing_task.result() == expected s3_bucket = annealing_task.metadata()["resultsS3Bucket"] @@ -190,11 +190,11 @@ def test_result_annealing(annealing_task): def test_result_is_cached(circuit_task): _mock_metadata(circuit_task._aws_session, "COMPLETED") - _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_1) + _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_GATE_MODEL) circuit_task.result() - _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_2) - expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) + _mock_s3(circuit_task._aws_session, "") + expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_GATE_MODEL) assert circuit_task.result() == expected @@ -205,7 +205,7 @@ def set_result_from_callback(future): result_from_callback = future.result() _mock_metadata(circuit_task._aws_session, "RUNNING") - _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_1) + _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_GATE_MODEL) future = circuit_task.async_result() @@ -223,7 +223,7 @@ def set_result_from_callback(future): # via future.result(). Note that this would fail if the future is not complete. result_from_future = future.result() - expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) + expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_GATE_MODEL) assert result_from_callback == expected assert result_from_waiting == expected assert result_from_future == expected @@ -231,14 +231,14 @@ def set_result_from_callback(future): def test_failed_task(quantum_task): _mock_metadata(quantum_task._aws_session, "FAILED") - _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_1) + _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_GATE_MODEL) result = quantum_task.result() assert result is None def test_timeout_completed(aws_session): _mock_metadata(aws_session, "RUNNING") - _mock_s3(aws_session, MockS3.MOCK_S3_RESULT_1) + _mock_s3(aws_session, MockS3.MOCK_S3_RESULT_GATE_MODEL) # Setup the poll timing such that the timeout will occur after one API poll quantum_task = AwsQuantumTask( @@ -247,12 +247,14 @@ def test_timeout_completed(aws_session): assert quantum_task.result() is None _mock_metadata(aws_session, "COMPLETED") assert quantum_task.state() == "COMPLETED" - assert quantum_task.result() == GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_1) + assert quantum_task.result() == GateModelQuantumTaskResult.from_string( + MockS3.MOCK_S3_RESULT_GATE_MODEL + ) def test_timeout_no_result_terminal_state(aws_session): _mock_metadata(aws_session, "RUNNING") - _mock_s3(aws_session, MockS3.MOCK_S3_RESULT_1) + _mock_s3(aws_session, MockS3.MOCK_S3_RESULT_GATE_MODEL) # Setup the poll timing such that the timeout will occur after one API poll quantum_task = AwsQuantumTask( diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index abbf1295..93d641f1 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.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 json from typing import Any, Dict, Optional from unittest.mock import Mock @@ -22,28 +21,62 @@ from braket.circuits import Circuit from braket.devices import LocalSimulator, local_simulator from braket.simulator import BraketSimulator +from braket.task_result import AnnealingTaskResult, GateModelTaskResult from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult -GATE_MODEL_RESULT = { - "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], - "MeasuredQubits": [0, 1], - "TaskMetadata": { - "Id": "UUID_blah_1", - "Status": "COMPLETED", - "Shots": 1000, - "Ir": json.dumps({"results": []}), - }, -} - -ANNEALING_RESULT = { - "Solutions": [[-1, -1, -1, -1], [1, -1, 1, 1], [1, -1, -1, 1]], - "VariableCount": 4, - "Values": [0.0, 1.0, 2.0], - "SolutionCounts": None, - "ProblemType": "ising", - "Foo": {"Bar": "Baz"}, - "TaskMetadata": {"Id": "UUID_blah_1", "Status": "COMPLETED", "Shots": 5}, -} +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"}], + }, + }, + } +) + +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}, + }, + "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, + }, + }, + }, + } +) class DummyCircuitSimulator(BraketSimulator): @@ -66,7 +99,7 @@ def assert_qubits(self, qubits): class DummyAnnealingSimulator(BraketSimulator): - def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> Dict[str, Any]: + def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> AnnealingTaskResult: return ANNEALING_RESULT @property @@ -82,7 +115,7 @@ def properties(self) -> Dict[str, Any]: def test_load_from_entry_point(): sim = LocalSimulator("dummy") task = sim.run(Circuit().h(0).cnot(0, 1), 10) - assert task.result() == GateModelQuantumTaskResult.from_dict(GATE_MODEL_RESULT) + assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) def test_run_gate_model(): @@ -91,7 +124,7 @@ def test_run_gate_model(): task = sim.run(Circuit().h(0).cnot(0, 1), 10) dummy.assert_shots(10) dummy.assert_qubits(2) - assert task.result() == GateModelQuantumTaskResult.from_dict(GATE_MODEL_RESULT) + assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) @pytest.mark.xfail(raises=ValueError) @@ -104,7 +137,7 @@ def test_run_gate_model_value_error(): def test_run_annealing(): sim = LocalSimulator(DummyAnnealingSimulator()) task = sim.run(Problem(ProblemType.ISING)) - assert task.result() == AnnealingQuantumTaskResult.from_dict(ANNEALING_RESULT) + assert task.result() == AnnealingQuantumTaskResult.from_object(ANNEALING_RESULT) def test_registered_backends(): 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 b9e9b8ad..3c2e9f09 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 @@ -11,12 +11,18 @@ # 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.aws.aws_qpu_arns import AwsQpuArns +from braket.ir.annealing import Problem +from braket.task_result import ( + AdditionalMetadata, + AnnealingTaskResult, + DwaveMetadata, + DwaveTiming, + TaskMetadata, +) from braket.tasks import AnnealingQuantumTaskResult @@ -42,71 +48,82 @@ def solution_counts(): @pytest.fixture def problem_type(): - return "ising" + return "ISING" @pytest.fixture def task_metadata(): - return {"Id": "UUID_blah_1", "Status": "COMPLETED", "BackendArn": AwsQpuArns.DWAVE, "Shots": 5} + return TaskMetadata(**{"id": "task_arn", "deviceId": AwsQpuArns.DWAVE, "shots": 100}) @pytest.fixture def dwave_metadata(): - return { - "ActiveVariables": [0], - "Timing": { - "QpuSamplingTime": 1575, - "QpuAnnealTimePerSample": 20, - "QpuReadoutTimePerSample": 274, - "QpuAccessTime": 10917, - "QpuAccessOverheadTime": 3382, - "QpuProgrammingTime": 9342, - "QpuDelayTimePerSample": 21, - "TotalPostProcessingTime": 117, - "PostProcessingOverheadTime": 117, - "TotalRealTime": 10917, - "RunTimeChip": 1575, - "AnnealTimePerRun": 20, - "ReadoutTimePerRun": 274, - }, - } + return DwaveMetadata( + activeVariables=[0], + timing=DwaveTiming( + qpuSamplingTime=100, + qpuAnnealTimePerSample=20, + qpuReadoutTimePerSample=274, + qpuAccessTime=10917, + qpuAccessOverheadTime=3382, + qpuProgrammingTime=9342, + qpuDelayTimePerSample=21, + totalPostProcessingTime=117, + postProcessingOverheadTime=117, + totalRealTime=10917, + runTimeChip=1575, + annealTimePerRun=20, + readoutTimePerRun=274, + ), + ) + + +@pytest.fixture +def additional_metadata(problem_type, dwave_metadata): + problem = Problem( + type=problem_type, + linear={0: 0.3333, 1: -0.333, 4: -0.333, 5: 0.333}, + quadratic={"0,4": 0.667, "0,5": -1, "1,4": 0.667, "1,5": 0.667}, + ) + return AdditionalMetadata(action=problem, dwaveMetadata=dwave_metadata) @pytest.fixture def result_str_1( - solutions, values, solution_counts, variable_count, problem_type, dwave_metadata, task_metadata + solutions, values, solution_counts, variable_count, task_metadata, additional_metadata ): - return json.dumps( - { - "Solutions": solutions, - "VariableCount": variable_count, - "Values": values, - "SolutionCounts": solution_counts, - "ProblemType": problem_type, - "DWaveMetadata": dwave_metadata, - "TaskMetadata": task_metadata, - } + result = AnnealingTaskResult( + solutions=solutions, + variableCount=variable_count, + values=values, + solutionCounts=solution_counts, + taskMetadata=task_metadata, + additionalMetadata=additional_metadata, ) + return result.json() @pytest.fixture -def result_str_2(solutions, values, variable_count, problem_type, dwave_metadata, task_metadata): - return json.dumps( - { - "Solutions": solutions, - "VariableCount": variable_count, - "Values": values, - "SolutionCounts": None, - "ProblemType": problem_type, - "DWaveMetadata": dwave_metadata, - "TaskMetadata": task_metadata, - } +def result_str_2(solutions, values, variable_count, task_metadata, additional_metadata): + result = AnnealingTaskResult( + solutions=solutions, + variableCount=variable_count, + values=values, + taskMetadata=task_metadata, + additionalMetadata=additional_metadata, ) + return result.json() @pytest.fixture def annealing_result( - solutions, values, solution_counts, variable_count, problem_type, dwave_metadata, task_metadata + solutions, + values, + solution_counts, + variable_count, + problem_type, + additional_metadata, + task_metadata, ): solutions = np.asarray(solutions, dtype=int) values = np.asarray(values, dtype=float) @@ -119,11 +136,11 @@ def annealing_result( variable_count=variable_count, problem_type=problem_type, task_metadata=task_metadata, - additional_metadata={"DWaveMetadata": dwave_metadata}, + additional_metadata=additional_metadata, ) -def test_from_dict( +def test_from_object( result_str_1, solutions, values, @@ -131,16 +148,16 @@ def test_from_dict( variable_count, problem_type, task_metadata, - dwave_metadata, + additional_metadata, ): - result = AnnealingQuantumTaskResult.from_dict(json.loads(result_str_1)) + result = AnnealingQuantumTaskResult.from_object(AnnealingTaskResult.parse_raw(result_str_1)) solutions = np.asarray(solutions, dtype=int) values = np.asarray(values, dtype=float) solution_counts = np.asarray(solution_counts, dtype=int) assert result.variable_count == variable_count assert result.problem_type == problem_type assert result.task_metadata == task_metadata - assert result.additional_metadata == {"DWaveMetadata": dwave_metadata} + assert result.additional_metadata == additional_metadata np.testing.assert_equal( result.record_array, AnnealingQuantumTaskResult._create_record_array(solutions, solution_counts, values), @@ -155,7 +172,7 @@ def test_from_string( variable_count, problem_type, task_metadata, - dwave_metadata, + additional_metadata, ): result = AnnealingQuantumTaskResult.from_string(result_str_1) solutions = np.asarray(solutions, dtype=int) @@ -164,7 +181,7 @@ def test_from_string( assert result.variable_count == variable_count assert result.problem_type == problem_type assert result.task_metadata == task_metadata - assert result.additional_metadata == {"DWaveMetadata": dwave_metadata} + assert result.additional_metadata == additional_metadata np.testing.assert_equal( result.record_array, AnnealingQuantumTaskResult._create_record_array(solutions, solution_counts, values), @@ -207,9 +224,9 @@ def test_data_sort_by(annealing_result, solutions, values, solution_counts): assert d[0][2] == solution_counts[min_index] -def test_from_dict_equal_to_from_string(result_str_1): - assert AnnealingQuantumTaskResult.from_dict( - json.loads(result_str_1) +def test_from_object_equal_to_from_string(result_str_1): + assert AnnealingQuantumTaskResult.from_object( + AnnealingTaskResult.parse_raw(result_str_1) ) == AnnealingQuantumTaskResult.from_string(result_str_1) 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 490a48f1..ff1e279d 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 @@ -11,135 +11,127 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -import copy import json -from typing import Any, Counter, Dict +from typing import Counter import numpy as np import pytest -from braket.aws.aws_qpu_arns import AwsQpuArns from braket.circuits import Observable, ResultType from braket.ir import jaqcd +from braket.task_result import ( + AdditionalMetadata, + GateModelTaskResult, + ResultTypeValue, + TaskMetadata, +) from braket.tasks import GateModelQuantumTaskResult @pytest.fixture -def result_dict_1(): - return { - "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], - "MeasuredQubits": [0, 1], - "TaskMetadata": { - "Id": "UUID_blah_1", - "Status": "COMPLETED", - "BackendArn": AwsQpuArns.RIGETTI, - "Shots": 1000, - "Ir": json.dumps({"results": []}), - }, - } +def task_metadata_shots(): + return TaskMetadata(**{"id": "task_arn", "deviceId": "default", "shots": 100}) @pytest.fixture -def result_str_1(result_dict_1): - return json.dumps(result_dict_1) +def task_metadata_zero_shots(): + return TaskMetadata(**{"id": "task_arn", "deviceId": "default", "shots": 0}) @pytest.fixture -def result_str_2(): - return json.dumps( - { - "Measurements": [[0, 0], [0, 0], [0, 0], [1, 1]], - "MeasuredQubits": [0, 1], - "TaskMetadata": { - "Id": "UUID_blah_2", - "Status": "COMPLETED", - "BackendArn": AwsQpuArns.RIGETTI, - "Shots": 1000, - "Ir": json.dumps({"results": []}), - }, - } - ) +def additional_metadata(): + program = jaqcd.Program(instructions=[jaqcd.CNot(control=0, target=1)]) + return AdditionalMetadata(action=program) @pytest.fixture -def result_str_3(): - return json.dumps( - { - "TaskMetadata": { - "Id": "1231231", - "Status": "COMPLETED", - "BackendArn": "test_arn", - "BackendTranslation": "...", - "Created": 1574140385.0697668, - "Modified": 1574140388.6908717, - "Shots": 100, - "GateModelConfig": {"QubitCount": 6}, - "Ir": json.dumps({"results": []}), - }, - "MeasurementProbabilities": {"011000": 0.9999999999999982}, - "MeasuredQubits": list(range(6)), - } +def result_obj_1(task_metadata_shots, additional_metadata): + return GateModelTaskResult( + measurements=[[0, 0], [0, 1], [0, 1], [0, 1]], + measuredQubits=[0, 1], + taskMetadata=task_metadata_shots, + additionalMetadata=additional_metadata, ) @pytest.fixture -def result_dict_4(): - return { - "TaskMetadata": { - "Id": "1231231", - "Shots": 0, - "GateModelConfig": {"QubitCount": 2}, - "Ir": json.dumps({"results": []}), - }, - "ResultTypes": [ - {"Type": {"targets": [0], "type": "probability"}, "Value": np.array([0.5, 0.5])}, - { - "Type": {"type": "statevector"}, - "Value": np.array([complex(0.70710678, 0), 0, 0, complex(0.70710678, 0)]), - }, - {"Type": {"targets": [0], "type": "expectation", "observable": ["y"]}, "Value": 0.0}, - {"Type": {"targets": [0], "type": "variance", "observable": ["y"]}, "Value": 0.1}, - { - "Type": {"type": "amplitude", "states": ["00"]}, - "Value": {"00": complex(0.70710678, 0)}, - }, - ], - } +def result_str_1(result_obj_1): + return result_obj_1.json() @pytest.fixture -def result_str_4(result_dict_4): - result = copy.deepcopy(result_dict_4) - result["ResultTypes"] = [ - {"Type": {"targets": [0], "type": "probability"}, "Value": [0.5, 0.5]}, - { - "Type": {"type": "statevector"}, - "Value": [(0.70710678, 0), (0, 0), (0, 0), (0.70710678, 0)], - }, - {"Type": {"targets": [0], "type": "expectation", "observable": ["y"]}, "Value": 0.0}, - {"Type": {"targets": [0], "type": "variance", "observable": ["y"]}, "Value": 0.1}, - {"Type": {"type": "amplitude", "states": ["00"]}, "Value": {"00": (0.70710678, 0)}}, - ] - return json.dumps(result) +def result_str_2(result_obj_1): + result_obj_1.taskMetadata.id = "task_arn_2" + return result_obj_1.json() @pytest.fixture -def result_dict_5(): - return { - "TaskMetadata": { - "Id": "1231231", - "Shots": 100, - "GateModelConfig": {"QubitCount": 2}, - "Ir": json.dumps( - { - "results": [ - jaqcd.Probability(targets=[1]).dict(), - jaqcd.Expectation(observable=["z"]).dict(), - ] - } +def result_str_3(task_metadata_shots, additional_metadata): + return GateModelTaskResult( + measurementProbabilities={"011000": 0.9999999999999982}, + measuredQubits=list(range(6)), + taskMetadata=task_metadata_shots, + additionalMetadata=additional_metadata, + ).json() + + +@pytest.fixture +def result_obj_4(task_metadata_zero_shots, additional_metadata): + return GateModelTaskResult( + resultTypes=[ + ResultTypeValue.construct( + type=jaqcd.Probability(targets=[0]), value=np.array([0.5, 0.5]) + ), + ResultTypeValue.construct( + type=jaqcd.StateVector(), + value=np.array([complex(0.70710678, 0), 0, 0, complex(0.70710678, 0)]), ), - }, - "Measurements": [ + ResultTypeValue.construct( + type=jaqcd.Expectation(observable=["y"], targets=[0]), value=0.0 + ), + ResultTypeValue.construct( + type=jaqcd.Variance(observable=["y"], targets=[0]), value=0.1 + ), + ResultTypeValue.construct( + type=jaqcd.Amplitude(states=["00"]), value={"00": complex(0.70710678, 0)} + ), + ], + measuredQubits=list(range(2)), + taskMetadata=task_metadata_zero_shots, + additionalMetadata=additional_metadata, + ) + + +@pytest.fixture +def result_str_4(task_metadata_zero_shots, additional_metadata): + result = GateModelTaskResult( + resultTypes=[ + ResultTypeValue(type=jaqcd.Probability(targets=[0]), value=[0.5, 0.5]), + ResultTypeValue( + type=jaqcd.StateVector(), value=[(0.70710678, 0), (0, 0), (0, 0), (0.70710678, 0)] + ), + ResultTypeValue(type=jaqcd.Expectation(observable=["y"], targets=[0]), value=0.0), + ResultTypeValue(type=jaqcd.Variance(observable=["y"], targets=[0]), value=0.1), + ResultTypeValue(type=jaqcd.Amplitude(states=["00"]), value={"00": (0.70710678, 0)}), + ], + measuredQubits=list(range(2)), + taskMetadata=task_metadata_zero_shots, + additionalMetadata=additional_metadata, + ) + return result.json() + + +@pytest.fixture +def result_obj_5(task_metadata_shots): + return GateModelTaskResult( + taskMetadata=task_metadata_shots, + additionalMetadata=AdditionalMetadata( + action=jaqcd.Program( + instructions=[jaqcd.CNot(control=0, target=1), jaqcd.CNot(control=2, target=3)], + results=[jaqcd.Probability(targets=[1]), jaqcd.Expectation(observable=["z"])], + ) + ), + measurements=[ [0, 0, 1, 0], [1, 1, 1, 1], [1, 0, 0, 1], @@ -151,36 +143,27 @@ def result_dict_5(): [0, 0, 0, 0], [0, 0, 0, 1], ], - "MeasuredQubits": [0, 1, 2, 3], - } + measuredQubits=[0, 1, 2, 3], + ) @pytest.fixture -def malformatted_results_1(): - return { - "TaskMetadata": { - "Id": "UUID_blah_1", - "Status": "COMPLETED", - "BackendArn": AwsQpuArns.RIGETTI, - "Shots": 1000, - "Ir": json.dumps({"results": []}), - } - } +def malformatted_results_1(task_metadata_shots, additional_metadata): + return GateModelTaskResult( + measuredQubits=list(range(6)), + taskMetadata=task_metadata_shots, + additionalMetadata=additional_metadata, + ).json() @pytest.fixture -def malformatted_results_2(): - return { - "TaskMetadata": { - "Id": "UUID_blah_1", - "Status": "COMPLETED", - "BackendArn": AwsQpuArns.RIGETTI, - "Shots": 1000, - "Ir": json.dumps({"results": []}), - }, - "MeasurementProbabilities": {"011000": 0.9999999999999982}, - "MeasuredQubits": [0], - } +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() test_ir_results = [ @@ -248,90 +231,38 @@ def test_measurements_from_measurement_probabilities(): assert np.allclose(measurements, expected_results) -def test_task_metadata(): - task_metadata: Dict[str, Any] = { - "Id": "UUID_blah", - "Status": "COMPLETED", - "BackendArn": "Rigetti_Arn", - "CwLogGroupArn": "blah", - "Program": "....", - "CreatedAt": "02/12/22 21:23", - "UpdatedAt": "02/13/22 21:23", - } - result: GateModelQuantumTaskResult = GateModelQuantumTaskResult( - measurements=None, - result_types=[], - values=[], - task_metadata=task_metadata, - measurement_counts=None, - measurement_probabilities=None, - measurements_copied_from_device=False, - measurement_counts_copied_from_device=False, - measurement_probabilities_copied_from_device=False, - ) - assert result.task_metadata == task_metadata - - -def test_from_dict_measurements(result_dict_1): - task_result = GateModelQuantumTaskResult.from_dict(result_dict_1) - expected_measurements = np.asarray(result_dict_1["Measurements"], dtype=int) - assert task_result.task_metadata == result_dict_1["TaskMetadata"] +def test_from_string_measurements(result_str_1): + result_obj = GateModelTaskResult.parse_raw(result_str_1) + task_result = GateModelQuantumTaskResult.from_string(result_str_1) + expected_measurements = np.asarray(result_obj.measurements, dtype=int) + assert task_result.task_metadata == result_obj.taskMetadata + assert task_result.additional_metadata == result_obj.additionalMetadata assert np.array2string(task_result.measurements) == np.array2string(expected_measurements) assert not task_result.measurement_counts_copied_from_device assert not task_result.measurement_probabilities_copied_from_device assert task_result.measurements_copied_from_device - assert task_result.measured_qubits == result_dict_1["MeasuredQubits"] + assert task_result.measured_qubits == result_obj.measuredQubits assert task_result.values == [] assert task_result.result_types == [] -def test_from_dict_result_types(result_dict_5): - task_result = GateModelQuantumTaskResult.from_dict(result_dict_5) - expected_measurements = np.asarray(result_dict_5["Measurements"], dtype=int) - assert task_result.task_metadata == result_dict_5["TaskMetadata"] +def test_from_object_result_types(result_obj_5): + result_obj = result_obj_5 + task_result = GateModelQuantumTaskResult.from_object(result_obj) + expected_measurements = np.asarray(result_obj.measurements, dtype=int) assert np.array2string(task_result.measurements) == np.array2string(expected_measurements) - assert task_result.measured_qubits == result_dict_5["MeasuredQubits"] assert np.allclose(task_result.values[0], np.array([0.6, 0.4])) assert task_result.values[1] == [0.4, 0.2, -0.2, -0.4] - assert task_result.result_types[0]["Type"] == jaqcd.Probability(targets=[1]).dict() - assert task_result.result_types[1]["Type"] == jaqcd.Expectation(observable=["z"]).dict() - - -def test_from_dict_measurement_probabilities(result_str_3): - result_obj = json.loads(result_str_3) - task_result = GateModelQuantumTaskResult.from_dict(result_obj) - assert task_result.measurement_probabilities == result_obj["MeasurementProbabilities"] - assert task_result.task_metadata == result_obj["TaskMetadata"] - shots = 100 - measurement_list = [list("011000") for x 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 not task_result.measurement_counts_copied_from_device - assert task_result.measurement_probabilities_copied_from_device - assert not task_result.measurements_copied_from_device - assert task_result.measured_qubits == result_obj["MeasuredQubits"] - - -def test_from_string_measurements(result_str_1): - result_obj = json.loads(result_str_1) - task_result = GateModelQuantumTaskResult.from_string(result_str_1) - expected_measurements = np.asarray(result_obj["Measurements"], dtype=int) - assert task_result.task_metadata == result_obj["TaskMetadata"] - assert np.array2string(task_result.measurements) == np.array2string(expected_measurements) - assert not task_result.measurement_counts_copied_from_device - assert not task_result.measurement_probabilities_copied_from_device - assert task_result.measurements_copied_from_device - assert task_result.measured_qubits == result_obj["MeasuredQubits"] + assert task_result.result_types[0].type == jaqcd.Probability(targets=[1]) + assert task_result.result_types[1].type == jaqcd.Expectation(observable=["z"]) def test_from_string_measurement_probabilities(result_str_3): - result_obj = json.loads(result_str_3) + result_obj = GateModelTaskResult.parse_raw(result_str_3) task_result = GateModelQuantumTaskResult.from_string(result_str_3) - assert task_result.measurement_probabilities == result_obj["MeasurementProbabilities"] - assert task_result.task_metadata == result_obj["TaskMetadata"] + assert task_result.measurement_probabilities == result_obj.measurementProbabilities shots = 100 - measurement_list = [list("011000") for x in range(shots)] + 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)]) @@ -340,12 +271,12 @@ def test_from_string_measurement_probabilities(result_str_3): assert not task_result.measurements_copied_from_device -def test_from_dict_equal_to_from_string(result_dict_1, result_str_1, result_str_3): - assert GateModelQuantumTaskResult.from_dict( - result_dict_1 +def test_from_object_equal_to_from_string(result_obj_1, result_str_1, result_str_3): + assert GateModelQuantumTaskResult.from_object( + result_obj_1 ) == GateModelQuantumTaskResult.from_string(result_str_1) - assert GateModelQuantumTaskResult.from_dict( - json.loads(result_str_3) + assert GateModelQuantumTaskResult.from_object( + GateModelTaskResult.parse_raw(result_str_3) ) == GateModelQuantumTaskResult.from_string(result_str_3) @@ -361,22 +292,22 @@ def test_equality(result_str_1, result_str_2): assert result_1 != non_result -def test_from_string_simulator_only(result_dict_4, result_str_4): +def test_from_string_simulator_only(result_obj_4, result_str_4): + result_obj = result_obj_4 result = GateModelQuantumTaskResult.from_string(result_str_4) - assert len(result.result_types) == len(result_dict_4["ResultTypes"]) + assert len(result.result_types) == len(result_obj.resultTypes) for i in range(len(result.result_types)): rt = result.result_types[i] - expected_rt = result_dict_4["ResultTypes"][i] - assert rt["Type"] == expected_rt["Type"] - if isinstance(rt["Value"], np.ndarray): - assert np.allclose(rt["Value"], expected_rt["Value"]) + expected_rt = result_obj.resultTypes[i] + assert rt.type == expected_rt.type + if isinstance(rt.value, np.ndarray): + assert np.allclose(rt.value, expected_rt.value) else: - assert rt["Value"] == expected_rt["Value"] - assert result.task_metadata == result.task_metadata + assert rt.value == expected_rt.value -def test_get_value_by_result_type(result_dict_4): - result = GateModelQuantumTaskResult.from_dict(result_dict_4) +def test_get_value_by_result_type(result_obj_4): + result = GateModelQuantumTaskResult.from_object(result_obj_4) assert np.allclose( result.get_value_by_result_type(ResultType.Probability(target=0)), result.values[0] ) @@ -393,26 +324,19 @@ def test_get_value_by_result_type(result_dict_4): @pytest.mark.xfail(raises=ValueError) -def test_get_value_by_result_type_value_error(result_dict_4): - result = GateModelQuantumTaskResult.from_dict(result_dict_4) +def test_get_value_by_result_type_value_error(result_obj_4): + result = GateModelQuantumTaskResult.from_object(result_obj_4) result.get_value_by_result_type(ResultType.Probability(target=[0, 1])) @pytest.mark.xfail(raises=ValueError) -def test_bad_dict(malformatted_results_1): - GateModelQuantumTaskResult.from_dict(malformatted_results_1) - - -@pytest.mark.xfail(raises=ValueError) -def test_bad_string(malformatted_results_1): - results_string = json.dumps(malformatted_results_1) - GateModelQuantumTaskResult.from_string(results_string) +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): - results_string = json.dumps(malformatted_results_2) - GateModelQuantumTaskResult.from_string(results_string) + GateModelQuantumTaskResult.from_string(malformatted_results_2) @pytest.mark.parametrize("ir_result,expected_result", test_ir_results) @@ -439,8 +363,8 @@ def test_calculate_ir_results(ir_result, expected_result): ir_string, measurements, measured_qubits ) assert len(result_types) == 1 - assert result_types[0]["Type"] == ir_result.dict() - assert np.allclose(result_types[0]["Value"], expected_result) + assert result_types[0].type == ir_result + assert np.allclose(result_types[0].value, expected_result) @pytest.mark.xfail(raises=ValueError) 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 23ce67ae..d04676da 100644 --- a/test/unit_tests/braket/tasks/test_local_quantum_task.py +++ b/test/unit_tests/braket/tasks/test_local_quantum_task.py @@ -11,25 +11,22 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -import json import uuid +import numpy as np import pytest +from braket.task_result import TaskMetadata from braket.tasks import GateModelQuantumTaskResult from braket.tasks.local_quantum_task import LocalQuantumTask -RESULT = GateModelQuantumTaskResult.from_dict( - { - "Measurements": [[0, 0], [0, 1], [0, 1], [0, 1]], - "MeasuredQubits": [0, 1], - "TaskMetadata": { - "Id": str(uuid.uuid4()), - "Status": "COMPLETED", - "Shots": 2, - "Ir": json.dumps({"results": []}), - }, - } +RESULT = GateModelQuantumTaskResult( + 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], + result_types=None, + values=None, ) TASK = LocalQuantumTask(RESULT) @@ -45,7 +42,7 @@ def test_state(): def test_result(): - assert RESULT.task_metadata["Id"] == TASK.id + assert RESULT.task_metadata.id == TASK.id assert TASK.result() == RESULT From 379669807f08df71f0903e8cf3484643908500ef Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 29 Jul 2020 11:01:32 -0700 Subject: [PATCH 0148/1165] Migrate to V4 API (#117) * `ir` and `irType` now live under the `action` key, with the payload now under the `payload` key instead of `ir`, and `irType` being replaced entirely by the header * `backendArn` and `backendParameters` are now `deviceArn` and `deviceParameters`, respectively; additionally, the parameter `backend_arn` has been changed to `device_arn` to reflect the API changes * `resultsS3Bucket`, `resultsS3Prefix` and `resultsS3ObjectKey` are now `outputS3Bucket`, `outputS3KeyPrefix` and `outputS3Directory`, respectively --- src/braket/aws/aws_qpu.py | 2 +- src/braket/aws/aws_quantum_task.py | 73 ++++++++++--------- src/braket/aws/aws_session.py | 6 +- .../tasks/gate_model_quantum_task_result.py | 6 +- .../braket/aws/common_test_utils.py | 8 ++ .../braket/aws/test_aws_quantum_task.py | 50 ++++++------- 6 files changed, 74 insertions(+), 71 deletions(-) diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py index dca18b8f..6e3383f6 100644 --- a/src/braket/aws/aws_qpu.py +++ b/src/braket/aws/aws_qpu.py @@ -114,7 +114,7 @@ def run( >>> ) >>> device = AwsQpu("arn:aws:aqx:::qpu:d-wave") >>> device.run(problem, ("bucket-foo", "key-bar"), - >>> backend_parameters = {"dWaveParameters": {"postprocessingType": "SAMPLING"}}) + >>> device_parameters = {"dWaveParameters": {"postprocessingType": "SAMPLING"}}) See Also: `braket.aws.aws_quantum_task.AwsQuantumTask.create()` diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 63653ff0..f7fe1432 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 json import time from functools import singledispatch from logging import Logger, getLogger @@ -25,6 +26,8 @@ from braket.aws.aws_session import AwsSession from braket.circuits.circuit import Circuit from braket.circuits.circuit_helpers import validate_circuit_and_shots +from braket.schema_common import BraketSchemaBase +from braket.task_result import AnnealingTaskResult, GateModelTaskResult from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult, QuantumTask @@ -41,6 +44,7 @@ class AwsQuantumTask(QuantumTask): DEFAULT_RESULTS_POLL_TIMEOUT = 120 DEFAULT_RESULTS_POLL_INTERVAL = 0.25 + RESULTS_FILENAME = "results.json" @staticmethod def create( @@ -49,7 +53,7 @@ def create( task_specification: Union[Circuit, Problem], s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, - backend_parameters: Dict[str, Any] = None, + device_parameters: Dict[str, Any] = None, *args, **kwargs, ) -> AwsQuantumTask: @@ -74,7 +78,7 @@ def create( `shots=0` is only available on simulators and means that the simulator will compute the exact results based on the task specification. - backend_parameters (Dict[str, Any]): Additional parameters to send to the device. + device_parameters (Dict[str, Any]): Additional parameters to send to the device. For example, for D-Wave: `{"dWaveParameters": {"postprocessingType": "OPTIMIZATION"}}` @@ -105,7 +109,7 @@ def create( task_specification, aws_session, create_task_kwargs, - backend_parameters or {}, + device_parameters or {}, *args, **kwargs, ) @@ -276,25 +280,6 @@ async def _create_future(self) -> asyncio.Task: """ return asyncio.create_task(self._wait_for_completion()) - def _get_results_formatter( - self, - ) -> Union[GateModelQuantumTaskResult.from_string, AnnealingQuantumTaskResult.from_string]: - """ - Get results formatter based on irType of self.metadata() - - Returns: - Union[GateModelQuantumTaskResult.from_string, AnnealingQuantumTaskResult.from_string]: - function that deserializes a string into a results structure - """ - current_metadata = self.metadata() - ir_type = current_metadata["irType"] - if ir_type == AwsQuantumTask.ANNEALING_IR_TYPE: - return AnnealingQuantumTaskResult.from_string - elif ir_type == AwsQuantumTask.GATE_IR_TYPE: - return GateModelQuantumTaskResult.from_string - else: - raise ValueError("Unknown IR type") - async def _wait_for_completion( self, ) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: @@ -320,9 +305,10 @@ async def _wait_for_completion( self._logger.debug(f"Task {self._arn}: task status {task_status}") if task_status in AwsQuantumTask.RESULTS_READY_STATES: result_string = self._aws_session.retrieve_s3_object_body( - current_metadata["resultsS3Bucket"], current_metadata["resultsS3ObjectKey"] + current_metadata["outputS3Bucket"], + current_metadata["outputS3Directory"] + f"/{AwsQuantumTask.RESULTS_FILENAME}", ) - self._result = self._get_results_formatter()(result_string) + self._result = _format_result(BraketSchemaBase.parse_raw_schema(result_string)) return self._result elif task_status in AwsQuantumTask.NO_RESULT_TERMINAL_STATES: self._logger.warning( @@ -360,7 +346,7 @@ def _create_internal( task_specification: Union[Circuit, Problem], aws_session: AwsSession, create_task_kwargs: Dict[str, Any], - backend_parameters: Dict[str, Any], + device_parameters: Dict[str, Any], *args, **kwargs, ) -> AwsQuantumTask: @@ -372,16 +358,17 @@ def _( circuit: Circuit, aws_session: AwsSession, create_task_kwargs: Dict[str, Any], - backend_parameters: Dict[str, Any], + device_parameters: Dict[str, Any], *args, **kwargs, ) -> AwsQuantumTask: validate_circuit_and_shots(circuit, create_task_kwargs["shots"]) create_task_kwargs.update( { - "ir": circuit.to_ir().json(), - "irType": AwsQuantumTask.GATE_IR_TYPE, - "backendParameters": {"gateModelParameters": {"qubitCount": circuit.qubit_count}}, + "action": circuit.to_ir().json(), + "deviceParameters": json.dumps( + {"gateModelParameters": {"qubitCount": circuit.qubit_count}} + ), } ) task_arn = aws_session.create_quantum_task(**create_task_kwargs) @@ -393,15 +380,14 @@ def _( problem: Problem, aws_session: AwsSession, create_task_kwargs: Dict[str, Any], - backend_parameters: Dict[str, Any], + device_parameters: Dict[str, Any], *args, **kwargs, ) -> AwsQuantumTask: create_task_kwargs.update( { - "ir": problem.to_ir().json(), - "irType": AwsQuantumTask.ANNEALING_IR_TYPE, - "backendParameters": {"annealingModelParameters": backend_parameters}, + "action": problem.to_ir().json(), + "deviceParameters": json.dumps({"annealingModelParameters": device_parameters}), } ) @@ -413,8 +399,23 @@ def _create_common_params( device_arn: str, s3_destination_folder: AwsSession.S3DestinationFolder, shots: int ) -> Dict[str, Any]: return { - "backendArn": device_arn, - "resultsS3Bucket": s3_destination_folder[0], - "resultsS3Prefix": s3_destination_folder[1], + "deviceArn": device_arn, + "outputS3Bucket": s3_destination_folder[0], + "outputS3KeyPrefix": s3_destination_folder[1], "shots": shots, } + + +@singledispatch +def _format_result(result): + raise TypeError("Invalid result specification type") + + +@_format_result.register +def _(result: GateModelTaskResult) -> GateModelQuantumTaskResult: + return GateModelQuantumTaskResult.from_object(result) + + +@_format_result.register +def _(result: AnnealingTaskResult) -> AnnealingQuantumTaskResult: + return AnnealingQuantumTaskResult.from_object(result) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 899e5853..ec750517 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -24,9 +24,9 @@ class AwsSession(object): S3DestinationFolder = NamedTuple("S3DestinationFolder", [("bucket", str), ("key", int)]) BRAKET_ENDPOINTS = { - "us-west-1": "https://fdoco1n1x7.execute-api.us-west-1.amazonaws.com/V3", - "us-west-2": "https://xe15dbdvw6.execute-api.us-west-2.amazonaws.com/V3", - "us-east-1": "https://kqjovr0n70.execute-api.us-east-1.amazonaws.com/V3", + "us-west-1": "https://fdoco1n1x7.execute-api.us-west-1.amazonaws.com/V4", + "us-west-2": "https://xe15dbdvw6.execute-api.us-west-2.amazonaws.com/V4", + "us-east-1": "https://kqjovr0n70.execute-api.us-east-1.amazonaws.com/V4", } # similar to sagemaker sdk: diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 39541514..caabb9c5 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -185,12 +185,12 @@ def measurements_from_measurement_probabilities( return np.asarray(measurements_list, dtype=int) @staticmethod - def from_object(result: GateModelQuantumTaskResult): + def from_object(result: GateModelTaskResult): """ - Create GateModelQuantumTaskResult from dict. + Create GateModelQuantumTaskResult from GateModelTaskResult object. Args: - result (Dict[str, Any]): Results dict with GateModelQuantumTaskResult attributes as keys + result (GateModelTaskResult): GateModelTaskResult object Returns: GateModelQuantumTaskResult: A GateModelQuantumTaskResult based on the given dict diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index c19d96fb..7b52ea1e 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -193,6 +193,10 @@ class MockS3: 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": { @@ -212,6 +216,10 @@ class MockS3: 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], 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 b9e4e775..53a7441f 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import asyncio +import json import threading import time from unittest.mock import MagicMock, Mock, patch @@ -164,28 +165,25 @@ def test_result_circuit(circuit_task): expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_GATE_MODEL) assert circuit_task.result() == expected - s3_bucket = circuit_task.metadata()["resultsS3Bucket"] - s3_object_key = circuit_task.metadata()["resultsS3ObjectKey"] - circuit_task._aws_session.retrieve_s3_object_body.assert_called_with(s3_bucket, s3_object_key) - - -@pytest.mark.xfail(raises=ValueError) -def test_result_unknown_ir_type(circuit_task): - _mock_metadata(circuit_task._aws_session, "COMPLETED", "unsupported_ir_type") - _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_GATE_MODEL) - circuit_task.result() + 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", "annealing") + _mock_metadata(annealing_task._aws_session, "COMPLETED") _mock_s3(annealing_task._aws_session, MockS3.MOCK_S3_RESULT_ANNEALING) expected = AnnealingQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_ANNEALING) assert annealing_task.result() == expected - s3_bucket = annealing_task.metadata()["resultsS3Bucket"] - s3_object_key = annealing_task.metadata()["resultsS3ObjectKey"] - annealing_task._aws_session.retrieve_s3_object_body.assert_called_with(s3_bucket, s3_object_key) + s3_bucket = annealing_task.metadata()["outputS3Bucket"] + s3_object_key = annealing_task.metadata()["outputS3Directory"] + annealing_task._aws_session.retrieve_s3_object_body.assert_called_with( + s3_bucket, f"{s3_object_key}/results.json" + ) def test_result_is_cached(circuit_task): @@ -292,7 +290,6 @@ def test_from_circuit_with_shots(aws_session, arn, circuit): aws_session, arn, circuit, - AwsQuantumTask.GATE_IR_TYPE, S3_TARGET, shots, {"gateModelParameters": {"qubitCount": circuit.qubit_count}}, @@ -316,7 +313,7 @@ def test_from_annealing(aws_session, arn, problem): problem, S3_TARGET, 1000, - backend_parameters={"dWaveParameters": {"postprocessingType": "OPTIMIZATION"}}, + device_parameters={"dWaveParameters": {"postprocessingType": "OPTIMIZATION"}}, ) assert task == AwsQuantumTask( mocked_task_arn, aws_session, AnnealingQuantumTaskResult.from_string @@ -326,7 +323,6 @@ def test_from_annealing(aws_session, arn, problem): aws_session, arn, problem, - AwsQuantumTask.ANNEALING_IR_TYPE, S3_TARGET, 1000, {"annealingModelParameters": {"dWaveParameters": {"postprocessingType": "OPTIMIZATION"}}}, @@ -357,27 +353,25 @@ def _init_and_add_to_list(aws_session, arn, task_list): def _assert_create_quantum_task_called_with( - aws_session, arn, task_description, ir_type, s3_results_prefix, shots, backend_parameters + aws_session, arn, task_description, s3_results_prefix, shots, device_parameters ): aws_session.create_quantum_task.assert_called_with( **{ - "backendArn": arn, - "resultsS3Bucket": s3_results_prefix[0], - "resultsS3Prefix": s3_results_prefix[1], - "ir": task_description.to_ir().json(), - "irType": ir_type, - "backendParameters": backend_parameters, + "deviceArn": arn, + "outputS3Bucket": s3_results_prefix[0], + "outputS3KeyPrefix": s3_results_prefix[1], + "action": task_description.to_ir().json(), + "deviceParameters": json.dumps(device_parameters), "shots": shots, } ) -def _mock_metadata(aws_session, state, irType="jaqcd"): +def _mock_metadata(aws_session, state): return_value = { "status": state, - "resultsS3Bucket": S3_TARGET.bucket, - "resultsS3ObjectKey": S3_TARGET.key, - "irType": irType, + "outputS3Bucket": S3_TARGET.bucket, + "outputS3Directory": S3_TARGET.key, } aws_session.get_quantum_task.return_value = return_value From 911a5b8c26bfd6db026516e63970f25e85813bcb Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Thu, 30 Jul 2020 17:43:40 -0700 Subject: [PATCH 0149/1165] Remove hard-coded ARNs and add AwsDevice class (#118) --- README.md | 20 +- examples/bell.py | 4 +- examples/debug_bell.py | 4 +- src/braket/aws/__init__.py | 3 +- src/braket/aws/aws_device.py | 173 ++++++++++++++++++ src/braket/aws/aws_qpu.py | 14 +- src/braket/aws/aws_quantum_simulator.py | 1 + src/braket/aws/aws_quantum_simulator_arns.py | 18 -- test/integ_tests/test_aws_session_s3.py | 14 +- test/integ_tests/test_device_creation.py | 31 ++-- .../test_simulator_quantum_task.py | 55 +++--- .../braket/aws/common_test_utils.py | 22 ++- .../unit_tests/braket/aws/test_aws_device.py | 33 +++- test/unit_tests/braket/aws/test_aws_qpu.py | 51 +++--- .../braket/aws/test_aws_quantum_simulator.py | 16 +- .../unit_tests/braket/aws/test_aws_session.py | 24 +-- .../test_annealing_quantum_task_result.py | 3 +- 17 files changed, 317 insertions(+), 169 deletions(-) create mode 100644 src/braket/aws/aws_device.py delete mode 100644 src/braket/aws/aws_quantum_simulator_arns.py rename src/braket/aws/aws_qpu_arns.py => test/unit_tests/braket/aws/test_aws_device.py (53%) diff --git a/README.md b/README.md index ce3bcf08..29a4716b 100644 --- a/README.md +++ b/README.md @@ -179,12 +179,12 @@ Use the following code sample to validate your environment configuration. ```python import boto3 -from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns +from braket.aws import AwsDevice from braket.circuits import Circuit aws_account_id = boto3.client("sts").get_caller_identity()["Account"] -device = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS1) +device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") s3_folder = (f"braket-output-{aws_account_id}", "folder-name") bell = Circuit().h(0).cnot(0, 1) @@ -195,8 +195,8 @@ print(task.result().measurement_counts) The code sample imports the Amazon Braket framework, then defines the execution environment as the AWSQuantumSimulator and the device to use. The `s3_folder` statement defines the Amazon S3 bucket for job output and the folder in the bucket to store job output. This folder is created when you run the job. It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the job. ### Available Simulators -There is currently one AwsQuantumSimulator available: -- `arn:aws:aqx:::quantum-simulator:aqx:qs1` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the state vector and outputs an array of bit strings that appears as though it came from a quantum computer. Does not provide a state vector. +There is currently one AWS simulator available: +- `arn:aws:braket:::device/quantum-simulator/amazon/sv1` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the state vector and outputs an array of bit strings that appears as though it came from a quantum computer. Does not provide a state vector. #### To validate your configuration using a Python file 1. Open a text editor with example file `../braket-python-sdk/examples/bell.py`. @@ -258,11 +258,11 @@ The following example executes the same Bell Pair example described to validate ```python import boto3 from braket.circuits import Circuit -from braket.aws import AwsQpu, AwsQpuArns +from braket.aws import AwsDevice aws_account_id = boto3.client("sts").get_caller_identity()["Account"] -device = AwsQpu(AwsQpuArns.RIGETTI) +device = AwsDevice("arn:aws:braket:::device/qpu/rigetti/Aspen-8") s3_folder = (f"braket-output-{aws_account_id}", "RIGETTI") bell = Circuit().h(0).cnot(0, 1) @@ -270,7 +270,7 @@ task = device.run(bell, s3_folder) 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 `AwsQpu.run` (or `AwsQuantumTask`), 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()`. +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` (or `AwsQuantumTask`), 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, s3_folder, poll_timeout_seconds=86400) # 1 day @@ -278,9 +278,9 @@ print(task.result().measurement_counts) ``` Specify which quantum computer hardware to use by changing the value of the `device_arn` to the value for quantum computer to use: -- **IonQ** "arn:aws:aqx:::qpu:ionq" (Available 9:00 AM to 1:00 PM ET M-F) -- **Rigetti** "arn:aws:aqx:::qpu:rigetti" (Available 11:00 AM to 1:00 PM ET daily) -- **D-Wave** "arn:aws:aqx:::qpu:d-wave" (Available 24/7. See the next section in this document for more information about using D-Wave.) +- **IonQ** "arn:aws:braket:::device/qpu/ionq/ionQdevice" (Available 9:00 AM to 1:00 PM ET M-F) +- **Rigetti** "arn:aws:braket:::device/qpu/rigetti/Aspen-8" (Available 11:00 AM to 1:00 PM ET daily) +- **D-Wave** "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6" (Available 24/7. See the next section in this document for more information about using D-Wave.) ### Using Amazon Braket with D-Wave QPU If you want to use [Ocean](https://docs.ocean.dwavesys.com/en/latest/) with the D-Wave QPU, you can install the [braket-ocean-python-plugin](https://github.com/aws/braket-ocean-python-plugin). Information about how to install the plugin is provided in the [README](https://github.com/aws/braket-ocean-python-plugin/blob/master/README.md) for the repo. diff --git a/examples/bell.py b/examples/bell.py index 45f05456..69780390 100644 --- a/examples/bell.py +++ b/examples/bell.py @@ -13,12 +13,12 @@ import boto3 -from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns +from braket.aws import AwsDevice from braket.circuits import Circuit aws_account_id = boto3.client("sts").get_caller_identity()["Account"] -device = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS1) +device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") s3_folder = (f"braket-output-{aws_account_id}", "folder-name") # https://wikipedia.org/wiki/Bell_state diff --git a/examples/debug_bell.py b/examples/debug_bell.py index 267c1acc..a1e35b15 100644 --- a/examples/debug_bell.py +++ b/examples/debug_bell.py @@ -16,7 +16,7 @@ import boto3 -from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns +from braket.aws import AwsDevice from braket.circuits import Circuit logger = logging.getLogger("newLogger") # create new logger @@ -25,7 +25,7 @@ aws_account_id = boto3.client("sts").get_caller_identity()["Account"] -device = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS1) +device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") s3_folder = (f"braket-output-{aws_account_id}", "folder-name") bell = Circuit().h(0).cnot(0, 1) diff --git a/src/braket/aws/__init__.py b/src/braket/aws/__init__.py index d351adf0..9d7efd6b 100644 --- a/src/braket/aws/__init__.py +++ b/src/braket/aws/__init__.py @@ -11,9 +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.aws_device import AwsDevice # noqa: F401 from braket.aws.aws_qpu import AwsQpu # noqa: F401 -from braket.aws.aws_qpu_arns import AwsQpuArns # noqa: F401 from braket.aws.aws_quantum_simulator import AwsQuantumSimulator # noqa: F401 -from braket.aws.aws_quantum_simulator_arns import AwsQuantumSimulatorArns # noqa: F401 from braket.aws.aws_quantum_task import AwsQuantumTask # noqa: F401 from braket.aws.aws_session import AwsSession # noqa: F401 diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py new file mode 100644 index 00000000..d040add9 --- /dev/null +++ b/src/braket/aws/aws_device.py @@ -0,0 +1,173 @@ +# Copyright 2019-2019 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 typing import Any, Dict, Union + +from networkx import Graph + +from braket.annealing.problem import Problem +from braket.aws.aws_qpu import AwsQpu +from braket.aws.aws_quantum_simulator import AwsQuantumSimulator +from braket.aws.aws_quantum_task import AwsQuantumTask +from braket.aws.aws_session import AwsSession +from braket.circuits import Circuit +from braket.devices.device import Device + + +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. + """ + + _DUMMY_SHOTS = -1 + DEFAULT_SHOTS_QPU = 1000 + DEFAULT_SHOTS_SIMULATOR = 0 + DEFAULT_RESULTS_POLL_TIMEOUT = 432000 + DEFAULT_RESULTS_POLL_INTERVAL = 1 + + def __init__(self, arn: str, aws_session=None): + """ + Args: + arn (str): The ARN of the device + aws_session (AwsSession, optional) aws_session: An AWS session object. Default = None. + + Raises: + ValueError: If an unknown `arn` is specified. + + Note: + Some devices (QPUs) are physically located in specific AWS Regions. In some cases, + the current `aws_session` connects to a Region other than the Region in which the QPU is + physically located. When this occurs, a cloned `aws_session` is created for the Region + the QPU is located in. + """ + super().__init__(name=None, status=None, status_reason=None) + if "qpu" in arn: + self._device = AwsQpu(arn, aws_session) + else: + self._device = AwsQuantumSimulator(arn, aws_session) + + def run( + self, + task_specification: Union[Circuit, Problem], + s3_destination_folder: AwsSession.S3DestinationFolder, + shots: int = _DUMMY_SHOTS, + poll_timeout_seconds: int = DEFAULT_RESULTS_POLL_TIMEOUT, + poll_interval_seconds: int = DEFAULT_RESULTS_POLL_INTERVAL, + *aws_quantum_task_args, + **aws_quantum_task_kwargs, + ) -> AwsQuantumTask: + """ + Run a quantum task specification on this device. A task can be a circuit or an + annealing problem. + + Args: + task_specification (Union[Circuit, Problem]): Specification of task + (circuit or annealing problem) to run on device. + s3_destination_folder: The S3 location to save the task's results + shots (int, optional): The number of times to run the circuit or annealing problem. + Default is 1000 for QPUs and 0 for simulators. + poll_timeout_seconds (int): The polling timeout for AwsQuantumTask.result(), in seconds. + Default: 5 days. + poll_interval_seconds (int): The polling interval for AwsQuantumTask.result(), + in seconds. Default: 1 second. + *aws_quantum_task_args: Variable length positional arguments for + `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. + **aws_quantum_task_kwargs: Variable length keyword arguments for + `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. + + Returns: + AwsQuantumTask: An AwsQuantumTask that tracks the execution on the device. + + Examples: + >>> circuit = Circuit().h(0).cnot(0, 1) + >>> device = AwsDevice("arn1") + >>> device.run(circuit, ("bucket-foo", "key-bar")) + + >>> circuit = Circuit().h(0).cnot(0, 1) + >>> device = AwsDevice("arn2") + >>> device.run(task_specification=circuit, + >>> s3_destination_folder=("bucket-foo", "key-bar")) + + >>> problem = Problem( + >>> ProblemType.ISING, + >>> linear={1: 3.14}, + >>> quadratic={(1, 2): 10.08}, + >>> ) + >>> device = AwsDevice("arn3") + >>> device.run(problem, ("bucket-foo", "key-bar"), + >>> device_parameters = {"dWaveParameters": {"postprocessingType": "SAMPLING"}}) + + See Also: + `braket.aws.aws_quantum_task.AwsQuantumTask.create()` + """ + if shots == AwsDevice._DUMMY_SHOTS: + if "qpu" in self.arn: + shots = AwsDevice.DEFAULT_SHOTS_QPU + else: + shots = AwsDevice.DEFAULT_SHOTS_SIMULATOR + return self._device.run( + task_specification, + s3_destination_folder, + shots, + poll_timeout_seconds=poll_timeout_seconds, + poll_interval_seconds=poll_interval_seconds, + *aws_quantum_task_args, + **aws_quantum_task_kwargs, + ) + + def refresh_metadata(self) -> None: + """ + Refresh the `AwsDevice` object with the most recent Device metadata. + """ + self._device.refresh_metadata() + + @property + def arn(self) -> str: + """str: Return the ARN of the device""" + return self._device.arn + + @property + # TODO: Add a link to the boto3 docs + def properties(self) -> Dict[str, Any]: + """Dict[str, Any]: Return the device properties""" + return self._device.properties + + @property + def topology_graph(self) -> Graph: + """Graph: topology of device as a networkx Graph object. + Returns None if the topology is not available for the device. + + Examples: + >>> import networkx as nx + >>> device = AwsDevice("arn1") + >>> nx.draw_kamada_kawai(device.topology_graph, with_labels=True, font_weight="bold") + + >>> topology_subgraph = device.topology_graph.subgraph(range(8)) + >>> nx.draw_kamada_kawai(topology_subgraph, with_labels=True, font_weight="bold") + + >>> print(device.topology_graph.edges) + """ + if hasattr(self._device, "topology_graph"): + return self._device.topology_graph + else: + return None + + def __repr__(self): + return "Device('name': {}, 'arn': {})".format(self.name, self.arn) + + def __eq__(self, other): + if isinstance(other, AwsDevice): + return self.arn == other.arn + return NotImplemented diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py index 6e3383f6..f75510df 100644 --- a/src/braket/aws/aws_qpu.py +++ b/src/braket/aws/aws_qpu.py @@ -17,13 +17,13 @@ from networkx import Graph, complete_graph, from_edgelist from braket.annealing.problem import Problem -from braket.aws.aws_qpu_arns import AwsQpuArns from braket.aws.aws_quantum_task import AwsQuantumTask from braket.aws.aws_session import AwsSession from braket.circuits import Circuit from braket.devices.device import Device +# TODO: deprecate class AwsQpu(Device): """ Amazon Braket implementation of a Quantum Processing Unit (QPU). @@ -32,9 +32,9 @@ class AwsQpu(Device): """ QPU_REGIONS = { - AwsQpuArns.RIGETTI: ["us-west-1"], - AwsQpuArns.IONQ: ["us-east-1"], - AwsQpuArns.DWAVE: ["us-west-2"], + "rigetti": ["us-west-1"], + "ionq": ["us-east-1"], + "d-wave": ["us-west-2"], } DEFAULT_SHOTS_QPU = 1000 @@ -206,10 +206,8 @@ def _aws_session_for_qpu(self, qpu_arn: str, aws_session: AwsSession) -> AwsSess See `braket.aws.aws_qpu.AwsQpu.QPU_REGIONS` for the AWS Regions the QPUs are located in. """ - - qpu_regions = AwsQpu.QPU_REGIONS.get(qpu_arn, []) - if not qpu_regions: - raise ValueError(f"Unknown QPU {qpu_arn} was supplied.") + region_key = qpu_arn.split("/")[-2] + qpu_regions = AwsQpu.QPU_REGIONS.get(region_key, []) if aws_session: if aws_session.boto_session.region_name in qpu_regions: diff --git a/src/braket/aws/aws_quantum_simulator.py b/src/braket/aws/aws_quantum_simulator.py index db62e62a..58237077 100644 --- a/src/braket/aws/aws_quantum_simulator.py +++ b/src/braket/aws/aws_quantum_simulator.py @@ -20,6 +20,7 @@ from braket.devices.device import Device +# TODO: deprecate class AwsQuantumSimulator(Device): """ Amazon Braket implementation of a quantum simulator. diff --git a/src/braket/aws/aws_quantum_simulator_arns.py b/src/braket/aws/aws_quantum_simulator_arns.py deleted file mode 100644 index 4e47c3de..00000000 --- a/src/braket/aws/aws_quantum_simulator_arns.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2019-2019 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 enum import Enum - - -class AwsQuantumSimulatorArns(str, Enum): - QS1 = "arn:aws:aqx:::quantum-simulator:aqx:qs1" diff --git a/test/integ_tests/test_aws_session_s3.py b/test/integ_tests/test_aws_session_s3.py index 54a47a74..35d69049 100644 --- a/test/integ_tests/test_aws_session_s3.py +++ b/test/integ_tests/test_aws_session_s3.py @@ -15,19 +15,7 @@ import pytest -from braket.aws import AwsQpuArns - -TEST_S3_OBJ_CONTENTS = { - "TaskMetadata": { - "Id": "UUID_blah", - "Status": "COMPLETED", - "BackendArn": AwsQpuArns.RIGETTI, - "CwLogGroupArn": "blah", - "Program": "....", - "CreatedAt": "02/12/22 21:23", - "UpdatedAt": "02/13/22 21:23", - } -} +TEST_S3_OBJ_CONTENTS = {"TaskMetadata": {"Id": "blah", "Status": "COMPLETED",}} @pytest.fixture() diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 216e5544..4007ddcd 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -13,28 +13,21 @@ import pytest -from braket.aws import AwsQpu, AwsQpuArns, AwsQuantumSimulator, AwsQuantumSimulatorArns +from braket.aws import AwsDevice +DWAVE_ARN = "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6" +RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-8" +IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/ionQdevice" +SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" -@pytest.mark.parametrize( - "qpu_arn,qpu_name", [(AwsQpuArns.RIGETTI, "Rigetti"), (AwsQpuArns.IONQ, "IonQ")] -) -def test_qpu_creation(qpu_arn, qpu_name, aws_session): - qpu = AwsQpu(qpu_arn, aws_session=aws_session) - assert qpu.arn == qpu_arn - assert qpu.name == qpu_name + +@pytest.mark.parametrize("arn", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (SIMULATOR_ARN)]) +def test_qpu_creation(arn, aws_session): + device = AwsDevice(arn, aws_session=aws_session) + assert device.arn == arn def test_device_across_regions(aws_session): # assert QPUs across different regions can be created using the same aws_session - AwsQpu(AwsQpuArns.RIGETTI, aws_session) - AwsQpu(AwsQpuArns.IONQ, aws_session) - - -@pytest.mark.parametrize( - "simulator_arn,simulator_name", [(AwsQuantumSimulatorArns.QS1, "quantum-simulator-1")], -) -def test_simulator_creation(simulator_arn, simulator_name, aws_session): - simulator = AwsQuantumSimulator(simulator_arn, aws_session=aws_session) - assert simulator.arn == simulator_arn - assert simulator.name == simulator_name + AwsDevice(RIGETTI_ARN, aws_session) + AwsDevice(IONQ_ARN, aws_session) diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 32f3ca24..050bccb8 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -28,112 +28,113 @@ result_types_tensor_z_z_testing, ) -from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns +from braket.aws import AwsDevice SHOTS = 8000 +SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" -@pytest.mark.parametrize("simulator_arn", [AwsQuantumSimulatorArns.QS1]) +@pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) def test_no_result_types_bell_pair(simulator_arn, aws_session, s3_destination_folder): - device = AwsQuantumSimulator(simulator_arn, aws_session) + device = AwsDevice(simulator_arn, aws_session) no_result_types_bell_pair_testing( device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} ) -@pytest.mark.parametrize("simulator_arn", [AwsQuantumSimulatorArns.QS1]) +@pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) def test_qubit_ordering(simulator_arn, aws_session, s3_destination_folder): - device = AwsQuantumSimulator(simulator_arn, aws_session) + device = AwsDevice(simulator_arn, aws_session) qubit_ordering_testing(device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder}) -@pytest.mark.parametrize("simulator_arn", [AwsQuantumSimulatorArns.QS1]) +@pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) def test_result_types_nonzero_shots_bell_pair(simulator_arn, aws_session, s3_destination_folder): - device = AwsQuantumSimulator(simulator_arn, aws_session) + device = AwsDevice(simulator_arn, aws_session) result_types_nonzero_shots_bell_pair_testing( device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} ) -@pytest.mark.parametrize("simulator_arn", [AwsQuantumSimulatorArns.QS1]) +@pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) def test_result_types_bell_pair_full_probability(simulator_arn, aws_session, s3_destination_folder): - device = AwsQuantumSimulator(simulator_arn, aws_session) + device = AwsDevice(simulator_arn, aws_session) result_types_bell_pair_full_probability_testing( device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} ) -@pytest.mark.parametrize("simulator_arn", [AwsQuantumSimulatorArns.QS1]) +@pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) def test_result_types_bell_pair_marginal_probability( simulator_arn, aws_session, s3_destination_folder ): - device = AwsQuantumSimulator(simulator_arn, aws_session) + device = AwsDevice(simulator_arn, aws_session) result_types_bell_pair_marginal_probability_testing( device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} ) -@pytest.mark.parametrize("simulator_arn,shots", [(AwsQuantumSimulatorArns.QS1, SHOTS)]) +@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) def test_result_types_tensor_x_y(simulator_arn, shots, aws_session, s3_destination_folder): - device = AwsQuantumSimulator(simulator_arn, aws_session) + device = AwsDevice(simulator_arn, aws_session) result_types_tensor_x_y_testing( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) -@pytest.mark.parametrize("simulator_arn,shots", [(AwsQuantumSimulatorArns.QS1, SHOTS)]) +@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) def test_result_types_tensor_z_h_y(simulator_arn, shots, aws_session, s3_destination_folder): - device = AwsQuantumSimulator(simulator_arn, aws_session) + device = AwsDevice(simulator_arn, aws_session) result_types_tensor_z_h_y_testing( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) -@pytest.mark.parametrize("simulator_arn,shots", [(AwsQuantumSimulatorArns.QS1, SHOTS)]) +@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) def test_result_types_hermitian(simulator_arn, shots, aws_session, s3_destination_folder): - device = AwsQuantumSimulator(simulator_arn, aws_session) + device = AwsDevice(simulator_arn, aws_session) result_types_hermitian_testing( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) -@pytest.mark.parametrize("simulator_arn,shots", [(AwsQuantumSimulatorArns.QS1, SHOTS)]) +@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) def test_result_types_tensor_z_z(simulator_arn, shots, aws_session, s3_destination_folder): - device = AwsQuantumSimulator(simulator_arn, aws_session) + device = AwsDevice(simulator_arn, aws_session) result_types_tensor_z_z_testing( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) -@pytest.mark.parametrize("simulator_arn,shots", [(AwsQuantumSimulatorArns.QS1, SHOTS)]) +@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) def test_result_types_tensor_hermitian_hermitian( simulator_arn, shots, aws_session, s3_destination_folder ): - device = AwsQuantumSimulator(simulator_arn, aws_session) + device = AwsDevice(simulator_arn, aws_session) result_types_tensor_hermitian_hermitian_testing( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) -@pytest.mark.parametrize("simulator_arn,shots", [(AwsQuantumSimulatorArns.QS1, SHOTS)]) +@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) def test_result_types_tensor_y_hermitian(simulator_arn, shots, aws_session, s3_destination_folder): - device = AwsQuantumSimulator(simulator_arn, aws_session) + device = AwsDevice(simulator_arn, aws_session) result_types_tensor_y_hermitian_testing( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) -@pytest.mark.parametrize("simulator_arn,shots", [(AwsQuantumSimulatorArns.QS1, SHOTS)]) +@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) def test_result_types_tensor_z_hermitian(simulator_arn, shots, aws_session, s3_destination_folder): - device = AwsQuantumSimulator(simulator_arn, aws_session) + device = AwsDevice(simulator_arn, aws_session) result_types_tensor_z_hermitian_testing( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) -@pytest.mark.parametrize("simulator_arn,shots", [(AwsQuantumSimulatorArns.QS1, SHOTS)]) +@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) def test_result_types_all_selected(simulator_arn, shots, aws_session, s3_destination_folder): - device = AwsQuantumSimulator(simulator_arn, aws_session) + device = AwsDevice(simulator_arn, aws_session) result_types_all_selected_testing( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 7b52ea1e..ff5f5edb 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -14,14 +14,16 @@ import json from unittest.mock import Mock -from braket.aws.aws_qpu_arns import AwsQpuArns -from braket.aws.aws_quantum_simulator_arns import AwsQuantumSimulatorArns +DWAVE_ARN = "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6" +RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-8" +IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/ionQdevice" +SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" class MockDevices: MOCK_RIGETTI_QPU_1 = { - "arn": AwsQpuArns.RIGETTI, + "arn": RIGETTI_ARN, "properties": { "gateModelProperties": { "qubitCount": 16, @@ -36,7 +38,7 @@ class MockDevices: } MOCK_RIGETTI_QPU_2 = { - "arn": AwsQpuArns.RIGETTI, + "arn": RIGETTI_ARN, "properties": { "gateModelProperties": { "qubitCount": 30, @@ -52,7 +54,7 @@ class MockDevices: } MOCK_DWAVE_QPU_1 = { - "arn": AwsQpuArns.DWAVE, + "arn": DWAVE_ARN, "properties": { "annealingModelProperties": { "dWaveProperties": { @@ -88,7 +90,7 @@ class MockDevices: } MOCK_DWAVE_QPU_2 = { - "arn": AwsQpuArns.DWAVE, + "arn": DWAVE_ARN, "properties": { "annealingModelProperties": { "dWaveProperties": { @@ -125,7 +127,7 @@ class MockDevices: } MOCK_IONQ_QPU = { - "arn": AwsQpuArns.IONQ, + "arn": IONQ_ARN, "properties": { "gateModelProperties": { "qubitCount": 11, @@ -138,7 +140,7 @@ class MockDevices: } MOCK_QS1_SIMULATOR_1 = { - "arn": AwsQuantumSimulatorArns.QS1, + "arn": SIMULATOR_ARN, "properties": { "gateModelProperties": { "qubitCount": 23, @@ -159,7 +161,7 @@ class MockDevices: } MOCK_QS1_SIMULATOR_2 = { - "arn": AwsQuantumSimulatorArns.QS1, + "arn": SIMULATOR_ARN, "properties": { "gateModelProperties": { "qubitCount": 30, @@ -224,7 +226,7 @@ class MockS3: "solutionCounts": [3, 2, 4], "values": [0.0, 1.0, 2.0], "variableCount": 4, - "taskMetadata": {"id": "task_arn", "shots": 100, "deviceId": AwsQpuArns.DWAVE,}, + "taskMetadata": {"id": "task_arn", "shots": 100, "deviceId": DWAVE_ARN,}, "additionalMetadata": { "action": { "type": "ISING", diff --git a/src/braket/aws/aws_qpu_arns.py b/test/unit_tests/braket/aws/test_aws_device.py similarity index 53% rename from src/braket/aws/aws_qpu_arns.py rename to test/unit_tests/braket/aws/test_aws_device.py index 514ad15f..23fec181 100644 --- a/src/braket/aws/aws_qpu_arns.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -11,10 +11,33 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from enum import Enum +from unittest.mock import Mock +import pytest -class AwsQpuArns(str, Enum): - RIGETTI = "arn:aws:aqx:::qpu:rigetti" - IONQ = "arn:aws:aqx:::qpu:ionq" - DWAVE = "arn:aws:aqx:::qpu:d-wave" +from braket.aws import AwsDevice +from braket.circuits import Circuit + + +@pytest.fixture +def arn(): + return "test_arn" + + +@pytest.fixture +def device(arn): + return AwsDevice(arn, Mock()) + + +@pytest.fixture +def s3_destination_folder(): + return "bucket-foo", "key-bar" + + +@pytest.fixture +def circuit(): + return Circuit().h(0) + + +# TODO: Unit tests for AWS device once AwsQpu and AwsQuantumSimulator are deleted +# and we have new V4 *Device APIs diff --git a/test/unit_tests/braket/aws/test_aws_qpu.py b/test/unit_tests/braket/aws/test_aws_qpu.py index 51b7b6ee..920f67f5 100644 --- a/test/unit_tests/braket/aws/test_aws_qpu.py +++ b/test/unit_tests/braket/aws/test_aws_qpu.py @@ -15,11 +15,14 @@ import networkx as nx import pytest -from common_test_utils import MockDevices, run_and_assert +from common_test_utils import DWAVE_ARN, IONQ_ARN, RIGETTI_ARN, MockDevices, run_and_assert -from braket.aws import AwsQpu, AwsQpuArns +from braket.aws import AwsQpu from braket.circuits import Circuit +RIGETTI_REGION_KEY = "rigetti" +IONQ_REGION_KEY = "ionq" + @pytest.fixture def qpu(aws_session): @@ -43,27 +46,22 @@ def circuit(): @pytest.fixture def boto_session(): _boto_session = Mock() - _boto_session.region_name = AwsQpu.QPU_REGIONS[AwsQpuArns.RIGETTI][0] + _boto_session.region_name = AwsQpu.QPU_REGIONS[RIGETTI_REGION_KEY][0] return _boto_session @pytest.fixture def aws_session(): _boto_session = Mock() - _boto_session.region_name = AwsQpu.QPU_REGIONS[AwsQpuArns.RIGETTI][0] + _boto_session.region_name = AwsQpu.QPU_REGIONS[RIGETTI_REGION_KEY][0] _aws_session = Mock() _aws_session.boto_session = _boto_session return _aws_session -@pytest.mark.xfail(raises=ValueError) -def test_unknown_qpu_arn(aws_session): - AwsQpu("foobar", aws_session) - - def test_aws_session_in_qpu_region(aws_session): - arn = AwsQpuArns.RIGETTI - aws_session.boto_session.region_name = AwsQpu.QPU_REGIONS[arn][0] + arn = RIGETTI_ARN + aws_session.boto_session.region_name = AwsQpu.QPU_REGIONS[RIGETTI_REGION_KEY][0] aws_session.get_qpu_metadata.return_value = MockDevices.MOCK_RIGETTI_QPU_1 AwsQpu(arn, aws_session) @@ -75,8 +73,8 @@ def test_aws_session_in_qpu_region(aws_session): def test_aws_session_in_another_qpu_region( boto_session_init, aws_session_init, boto_session, aws_session ): - arn = AwsQpuArns.RIGETTI - region = AwsQpu.QPU_REGIONS.get(arn)[0] + arn = RIGETTI_ARN + region = AwsQpu.QPU_REGIONS.get(RIGETTI_REGION_KEY)[0] boto_session_init.return_value = boto_session aws_session_init.return_value = aws_session @@ -109,8 +107,8 @@ def test_aws_session_in_another_qpu_region( @patch("braket.aws.aws_qpu.AwsSession") @patch("boto3.Session") def test_no_aws_session_supplied(boto_session_init, aws_session_init, boto_session, aws_session): - arn = AwsQpuArns.RIGETTI - region = AwsQpu.QPU_REGIONS.get(arn)[0] + arn = RIGETTI_ARN + region = AwsQpu.QPU_REGIONS.get(RIGETTI_REGION_KEY)[0] boto_session_init.return_value = boto_session aws_session_init.return_value = aws_session @@ -126,13 +124,13 @@ def test_no_aws_session_supplied(boto_session_init, aws_session_init, boto_sessi "qpu_arn, properties_keys, initial_qpu_data, modified_qpu_data", [ ( - AwsQpuArns.RIGETTI, + RIGETTI_ARN, ["gateModelProperties"], MockDevices.MOCK_RIGETTI_QPU_1, MockDevices.MOCK_RIGETTI_QPU_2, ), ( - AwsQpuArns.DWAVE, + DWAVE_ARN, ["annealingModelProperties", "dWaveProperties"], MockDevices.MOCK_DWAVE_QPU_1, MockDevices.MOCK_DWAVE_QPU_2, @@ -142,7 +140,8 @@ def test_no_aws_session_supplied(boto_session_init, aws_session_init, boto_sessi def test_qpu_refresh_metadata_success( aws_session, qpu_arn, properties_keys, initial_qpu_data, modified_qpu_data ): - aws_session.boto_session.region_name = AwsQpu.QPU_REGIONS[qpu_arn][0] + region_key = qpu_arn.split("/")[-2] + aws_session.boto_session.region_name = AwsQpu.QPU_REGIONS[region_key][0] aws_session.get_qpu_metadata.return_value = initial_qpu_data qpu = AwsQpu(qpu_arn, aws_session) _assert_qpu_fields(qpu, properties_keys, initial_qpu_data) @@ -157,16 +156,16 @@ def test_qpu_refresh_metadata_error(aws_session): err_message = "nooo!" aws_session.get_qpu_metadata.side_effect = RuntimeError(err_message) with pytest.raises(RuntimeError) as excinfo: - AwsQpu(AwsQpuArns.RIGETTI, aws_session) + AwsQpu(RIGETTI_ARN, aws_session) assert err_message in str(excinfo.value) def test_equality(qpu, aws_session): - qpu_1 = qpu(AwsQpuArns.RIGETTI) - qpu_2 = qpu(AwsQpuArns.RIGETTI) + qpu_1 = qpu(RIGETTI_ARN) + qpu_2 = qpu(RIGETTI_ARN) aws_session.get_qpu_metadata.return_value = MockDevices.MOCK_IONQ_QPU - aws_session.boto_session.region_name = AwsQpu.QPU_REGIONS[AwsQpuArns.IONQ][0] - other_qpu = AwsQpu(AwsQpuArns.IONQ, aws_session) + aws_session.boto_session.region_name = AwsQpu.QPU_REGIONS[IONQ_REGION_KEY][0] + other_qpu = AwsQpu(IONQ_ARN, aws_session) non_qpu = "HI" assert qpu_1 == qpu_2 @@ -176,7 +175,7 @@ def test_equality(qpu, aws_session): def test_repr(qpu): - qpu = qpu(AwsQpuArns.RIGETTI) + qpu = qpu(RIGETTI_ARN) expected = "QPU('name': {}, 'arn': {})".format(qpu.name, qpu.arn) assert repr(qpu) == expected @@ -271,7 +270,7 @@ def test_run_with_positional_args_and_kwargs( ], ) def test_construct_topology_graph(qpu, properties, expected_edges): - device = qpu(AwsQpuArns.RIGETTI) + device = qpu(RIGETTI_ARN) with patch("braket.aws.aws_qpu.AwsQpu.properties", properties): if expected_edges is None: assert device._construct_topology_graph() is None @@ -292,7 +291,7 @@ def _run_and_assert( ): run_and_assert( aws_quantum_task_mock, - qpu_factory(AwsQpuArns.RIGETTI), + qpu_factory(RIGETTI_ARN), AwsQpu.DEFAULT_SHOTS_QPU, AwsQpu.DEFAULT_RESULTS_POLL_TIMEOUT_QPU, AwsQpu.DEFAULT_RESULTS_POLL_INTERVAL_QPU, diff --git a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py index 64bf6a0d..a71dd67c 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py @@ -14,9 +14,9 @@ from unittest.mock import Mock, patch import pytest -from common_test_utils import MockDevices, run_and_assert +from common_test_utils import SIMULATOR_ARN, MockDevices, run_and_assert -from braket.aws import AwsQuantumSimulator, AwsQuantumSimulatorArns +from braket.aws import AwsQuantumSimulator from braket.circuits import Circuit @@ -44,7 +44,7 @@ def test_simulator_refresh_metadata_success(): mock_session = Mock() expected_metadata = MockDevices.MOCK_QS1_SIMULATOR_1 mock_session.get_simulator_metadata.return_value = expected_metadata - simulator = AwsQuantumSimulator(AwsQuantumSimulatorArns.QS1, mock_session) + simulator = AwsQuantumSimulator(SIMULATOR_ARN, mock_session) assert simulator.arn == expected_metadata.get("arn") assert simulator.name == expected_metadata.get("name") assert simulator.properties["qubitCount"] == expected_metadata.get("qubitCount") @@ -79,13 +79,13 @@ def test_simulator_refresh_metadata_error(): err_message = "nooo!" mock_session.get_simulator_metadata.side_effect = RuntimeError(err_message) with pytest.raises(RuntimeError) as excinfo: - AwsQuantumSimulator(AwsQuantumSimulatorArns.QS1, mock_session) + AwsQuantumSimulator(SIMULATOR_ARN, mock_session) assert err_message in str(excinfo.value) def test_equality(simulator): - simulator_1 = simulator(AwsQuantumSimulatorArns.QS1) - simulator_2 = simulator(AwsQuantumSimulatorArns.QS1) + simulator_1 = simulator(SIMULATOR_ARN) + simulator_2 = simulator(SIMULATOR_ARN) other_simulator = Mock(spec=AwsQuantumSimulator) other_simulator.arn.return_value = "OTHER_ARN" non_simulator = "HI" @@ -97,7 +97,7 @@ def test_equality(simulator): def test_repr(simulator): - simulator = simulator(AwsQuantumSimulatorArns.QS1) + simulator = simulator(SIMULATOR_ARN) expected = "QuantumSimulator('name': {}, 'arn': {})".format(simulator.name, simulator.arn) assert repr(simulator) == expected @@ -182,7 +182,7 @@ def _run_and_assert( ): run_and_assert( aws_quantum_task_mock, - simulator_factory(AwsQuantumSimulatorArns.QS1), + simulator_factory(SIMULATOR_ARN), AwsQuantumSimulator.DEFAULT_SHOTS_SIMULATOR, AwsQuantumSimulator.DEFAULT_RESULTS_POLL_TIMEOUT_SIMULATOR, AwsQuantumSimulator.DEFAULT_RESULTS_POLL_INTERVAL_SIMULATOR, diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index 7010b43c..d5f4ca2b 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -18,19 +18,9 @@ from botocore.exceptions import ClientError from common_test_utils import MockDevices -from braket.aws import AwsQpuArns, AwsQuantumSimulatorArns, AwsSession - -TEST_S3_OBJ_CONTENTS = { - "TaskMetadata": { - "Id": "UUID_blah", - "Status": "COMPLETED", - "BackendArn": AwsQpuArns.RIGETTI, - "CwLogGroupArn": "blah", - "Program": "....", - "CreatedAt": "02/12/22 21:23", - "UpdatedAt": "02/13/22 21:23", - } -} +from braket.aws import AwsSession + +TEST_S3_OBJ_CONTENTS = {"TaskMetadata": {"Id": "blah",}} @pytest.fixture @@ -109,7 +99,7 @@ def test_get_qpu_metadata_success(boto_session): braket_client = Mock() braket_client.describe_qpus.return_value = {"qpus": [MockDevices.MOCK_RIGETTI_QPU_1]} aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) - qpu_metadata = aws_session.get_qpu_metadata(AwsQpuArns.RIGETTI) + qpu_metadata = aws_session.get_qpu_metadata("arn1") assert qpu_metadata == MockDevices.MOCK_RIGETTI_QPU_1 @@ -121,7 +111,7 @@ def test_get_qpu_metadata_client_error(boto_session): {"Error": {"Code": "ValidationException", "Message": "NoSuchQpu"}}, "Operation" ) aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) - aws_session.get_qpu_metadata(AwsQpuArns.RIGETTI) + aws_session.get_qpu_metadata("arn1") def test_get_simulator_metadata_success(boto_session): @@ -130,7 +120,7 @@ def test_get_simulator_metadata_success(boto_session): "quantumSimulators": [MockDevices.MOCK_QS1_SIMULATOR_1] } aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) - simulator_metadata = aws_session.get_simulator_metadata(AwsQuantumSimulatorArns.QS1) + simulator_metadata = aws_session.get_simulator_metadata("arn1") assert simulator_metadata == MockDevices.MOCK_QS1_SIMULATOR_1 @@ -142,7 +132,7 @@ def test_get_simulator_metadata_client_error(boto_session): {"Error": {"Code": "ValidationException", "Message": "NoSuchSimulator"}}, "Operation" ) aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) - aws_session.get_simulator_metadata(AwsQuantumSimulatorArns.QS1) + aws_session.get_simulator_metadata("arn1") def test_cancel_quantum_task(aws_session): 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 3c2e9f09..b8f7d011 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 @@ -14,7 +14,6 @@ import numpy as np import pytest -from braket.aws.aws_qpu_arns import AwsQpuArns from braket.ir.annealing import Problem from braket.task_result import ( AdditionalMetadata, @@ -53,7 +52,7 @@ def problem_type(): @pytest.fixture def task_metadata(): - return TaskMetadata(**{"id": "task_arn", "deviceId": AwsQpuArns.DWAVE, "shots": 100}) + return TaskMetadata(**{"id": "task_arn", "deviceId": "arn1", "shots": 100}) @pytest.fixture From e7df731a5859dbe82552c2e44ce8b5716f7d98bc Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Fri, 31 Jul 2020 16:30:25 -0700 Subject: [PATCH 0150/1165] Modifications per repo and branch name changes (#119) --- README.md | 56 +++++++++++++++++-------------------- doc/Makefile | 2 +- doc/conf.py | 2 +- doc/index.rst | 2 +- setup.py | 6 ++-- src/braket/_sdk/_version.py | 2 +- 6 files changed, 32 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 29a4716b..0a2b1ae8 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,7 @@ The Amazon Braket Python SDK is an open source library that provides a framework **Getting the latest version** -Get the latest version of the SDK. If you receive a notice that a new version of the SDK is available, you can update to the latest version. For more information, see [Updating to the latest release](https://github.com/aws/braket-python-sdk/tree/stable/latest#updating-to-the-latest-release). View the [Releases](https://github.com/aws/braket-python-sdk/releases) page for more information. - -**Use the stable/latest branch** - -You should always use the stable/latest branch of this repo, which includes the latest stable version of the SDK. The master branch includes in-progress features and will not work. +Get the latest version of the SDK. If you receive a notice that a new version of the SDK is available, you can update to the latest version. For more information, see [Updating to the latest release](https://github.com/aws/amazon-braket-sdk-python/tree/main#updating-to-the-latest-release). View the [Releases](https://github.com/aws/amazon-braket-sdk-python/releases) page for more information. **Providing Feedback and Getting Help** @@ -118,36 +114,34 @@ Use the steps in this section to install and configure the Amazon Braket Python The easiest way to get the SDKs is to download them directly from the GitHub site. Because the repositories are private during the Private Beta period, an SSH key is required to access the files remotely from a terminal session. If you download them directly from the GitHub site, you can just extract the files to your system or virtual environment without the extra steps of using an SSH key. You need to log in to GitHub using the account that was whitelisted for the Amazon Braket (Private Beta). Use the following links to download the Amazon Braket Python SDK repos: -- [braket-python-ir](https://github.com/aws/braket-python-ir/archive/stable/latest.zip) -- [amazon-braket-default-simulator-python](https://github.com/aws/amazon-braket-default-simulator-python/archive/stable/latest.zip) -- [braket-python-sdk](https://github.com/aws/braket-python-sdk/archive/stable/latest.zip) +- [amazon-braket-schemas-python](https://github.com/aws/amazon-braket-schemas-python/archive/main.zip) +- [amazon-braket-default-simulator-python](https://github.com/aws/amazon-braket-default-simulator-python/archive/main.zip) +- [amazon-braket-sdk-python](https://github.com/aws/amazon-braket-sdk-python/archive/main.zip) ### Extract the SDK .zip files -Because the files were downloaded directly from GitHub, the folder in the .zip file includes the name of the branch of the GitHub repo that was downloaded, in this case the `stable/latest` branch. But to use the files in the SDK, we need to rename the folder to the original name. - -Note: Make sure you are always using the branch 'stable/latest' and not 'master'. The 'master' branch may contain in-progress changes that result in errors. +Because the files were downloaded directly from GitHub, the folder in the .zip file includes the name of the branch of the GitHub repo that was downloaded, in this case the `main` branch. But to use the files in the SDK, we need to rename the folder to the original name. **To rename the folders in the SDK .zip files** First, extract the .zip files to a location of your choosing. Then open the location where you extracted the folders to. You can use either the GUI file system tools in your OS, or the command line. You should see 3 folders with the following names: -- braket-python-ir-stable-latest -- amazon-braket-default-simulator-python-stable-latest -- braket-python-sdk-stable-latest +- amazon-braket-schemas-python-main +- amazon-braket-default-simulator-python-main +- amazon-braket-sdk-python-main Rename the folders to the following: -- braket-python-ir +- amazon-braket-schemas-python - amazon-braket-default-simulator-python -- braket-python-sdk +- amazon-braket-sdk-python Then copy the renamed files and paste them into the `braketvirtenv` folder where you created a virtual environment. Your folder structure should look like this: ```bash -..\YourFolder\braketvirtenv\braket-python-ir\ +..\YourFolder\braketvirtenv\amazon-braket-schemas-python\ ``` ### Install the SDK packages Use the following commands to install the SDKs in the order that they appear: ```bash -pip install -e braket-python-ir +pip install -e amazon-braket-schemas-python ``` ```bash @@ -155,7 +149,7 @@ pip install -e amazon-braket-default-simulator-python ``` ```bash -pip install -e braket-python-sdk +pip install -e amazon-braket-sdk-python ``` ### Install latest Amazon Braket model in AWS CLI @@ -199,11 +193,11 @@ There is currently one AWS simulator available: - `arn:aws:braket:::device/quantum-simulator/amazon/sv1` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the state vector and outputs an array of bit strings that appears as though it came from a quantum computer. Does not provide a state vector. #### To validate your configuration using a Python file -1. Open a text editor with example file `../braket-python-sdk/examples/bell.py`. +1. Open a text editor with example file `../amazon-braket-sdk-python/examples/bell.py`. 1. If desired, modify `folder-name` to the name of the folder to create/use for results in following line: `s3_folder = (f"braket-output-{aws_account_id}", "folder-name")`. Save the file. 1. Make sure the virtualenv (`braketvirtenv`) is activated, and then position the cursor in the `/examples` folder of the repo. Assuming you created a virtual environment on your `C:` drive in a folder named `braket`, the cursor should be at the following location: -`c:\braket\braketvirtenv\braket-python-sdk\examples\`. +`c:\braket\braketvirtenv\amazon-braket-sdk-python\examples\`. 1. Then use the following command to run the sample: ```bash @@ -314,12 +308,12 @@ We will periodically make updates and changes the SDK or the model. When you are ### Check the version you have installed You can view the version of the braket packages that you have installed by using the following commands in the virtual environment: ```bash -pip show amazon-braket-default-simulator-python -pip show braket-ir -pip show braket-sdk +pip show amazon-braket-default-simulator +pip show amazon-braket-schemas +pip show amazon-braket-sdk ``` -You can also check your version of `braket-python-sdk` from within Python: +You can also check your version of `amazon-braket-sdk-python` from within Python: ``` >>> import braket._sdk as braket_sdk @@ -328,13 +322,13 @@ You can also check your version of `braket-python-sdk` from within Python: Compare the version displayed in your local environment with the latest version listed for each of the following release pages: - [amazon-braket-default-simulator-python](https://github.com/aws/amazon-braket-default-simulator-python/releases) -- [braket-python-ir](https://github.com/aws/braket-python-ir/releases) -- [braket-python-sdk](https://github.com/aws/braket-python-sdk/releases) +- [amazon-braket-schemas-python](https://github.com/aws/amazon-braket-schemas-python/releases) +- [amazon-braket-sdk-python](https://github.com/aws/amazon-braket-sdk-python/releases) If the version listed is higher than your local version, you should update to the latest release. ### To get the lastest updates -Perform the steps described in the [Setting up the Amazon Braket Python SDKs](https://github.com/aws/braket-python-sdk/tree/stable/latest#setting-up-the-amazon-braket-python-sdks) section of this document. The links in that section point to the most recent version of the braket-python-sdk, braket-python-ir, amazon-braket-default-simulator-python, and model file you need to set up the new version of the SDK. +Perform the steps described in the [Setting up the Amazon Braket Python SDKs](https://github.com/aws/amazon-braket-sdk-python/tree/main#setting-up-the-amazon-amazon-braket-sdk-pythons) section of this document. The links in that section point to the most recent version of the amazon-braket-sdk-python, amazon-braket-schemas-python, amazon-braket-default-simulator-python, and model file you need to set up the new version of the SDK. You can extract the file to the same location you are using and replace the existing files with the updated SDK. This lets you continue to use the same virtual environment. @@ -354,19 +348,19 @@ Then extract the `braket-sdk-documentation.zip` file to your local environment. **To generate the API Reference HTML in your local environment** -To generate the HTML, first change directories (`cd`) to position the cursor in the `braket-python-sdk` directory. Then, run the following command to generate the HTML documentation files: +To generate the HTML, first change directories (`cd`) to position the cursor in the `amazon-braket-sdk-python` directory. Then, run the following command to generate the HTML documentation files: ```bash tox -e docs ``` To view the generated documentation, open the following file in a browser: -`../braket-python-sdk/build/documentation/html/index.html` +`../amazon-braket-sdk-python/build/documentation/html/index.html` ## Install the SDK for Testing Make sure to install test dependencies first: ```bash -pip install -e "braket-python-sdk[test]" +pip install -e "amazon-braket-sdk-python[test]" ``` ### Unit Tests diff --git a/doc/Makefile b/doc/Makefile index 9bc8a96e..5f56cc65 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -4,7 +4,7 @@ # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = python -msphinx -SPHINXPROJ = braket-sdk +SPHINXPROJ = amazon-braket-sdk SOURCEDIR = . BUILDDIR = ../build/documentation diff --git a/doc/conf.py b/doc/conf.py index 5281054a..e4dfbbda 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -4,7 +4,7 @@ import pkg_resources # Sphinx configuration below. -project = "braket-sdk" +project = "amazon-braket-sdk" version = pkg_resources.require(project)[0].version release = version copyright = "{}, Amazon.com".format(datetime.datetime.now().year) diff --git a/doc/index.rst b/doc/index.rst index 14b5e812..12bb3c4c 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -3,7 +3,7 @@ Amazon Braket (Private Beta) Python SDK Amazon Braket Python SDK is an open source library for interacting with quantum devices on Amazon Braket (Private Beta). -This documentation provides information about the Amazon Braket Python SDK API. For information about how to configure your environment to use the braket-python-sdk, please see the Readme in the GitHub repo for this project at https://github.com/aws/braket-python-sdk/tree/stable/latest. +This documentation provides information about the Amazon Braket Python SDK API. For information about how to configure your environment to use the braket-python-sdk, please see the README in the GitHub repo for this project at https://github.com/aws/amazon-braket-sdk-python. Indices and tables __________________ diff --git a/setup.py b/setup.py index 964ef55b..5856a161 100644 --- a/setup.py +++ b/setup.py @@ -17,16 +17,16 @@ version = f.readlines()[-1].split()[-1].strip("\"'") setup( - name="braket-sdk", + name="amazon-braket-sdk", version=version, license="Apache License 2.0", python_requires=">= 3.7.2", packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "braket-ir @ git+https://github.com/aws/braket-python-ir.git", + "amazon-braket-schemas @ git+https://github.com/aws/amazon-braket-schemas-python.git", ( - "amazon-braket-default-simulator-python @" + "amazon-braket-default-simulator @ " "git+https://github.com/aws/amazon-braket-default-simulator-python.git" ), "backoff", diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index cec4a1b5..ddd93c06 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.4.2" +__version__ = "0.5.0" From 069dcde62ec2f7c9bf04e4ab1dc121217f4e606c Mon Sep 17 00:00:00 2001 From: licedric <67704428+licedric@users.noreply.github.com> Date: Sun, 2 Aug 2020 14:55:31 +0000 Subject: [PATCH 0151/1165] Fix typo for get_quantum_task function docstring. (#120) Co-authored-by: Lin --- src/braket/aws/aws_session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index ec750517..4f7b25c5 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -101,7 +101,7 @@ def get_quantum_task(self, arn: str) -> Dict[str, Any]: Gets the quantum task. Args: - arn (str): The ARN of the quantum task to cancel. + arn (str): The ARN of the quantum task to get. Returns: Dict[str, Any]: The response from the Amazon Braket `GetQuantumTask` operation. From 47e11ed7e6cc68e7381062434ac1e41690da415b Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Tue, 4 Aug 2020 10:33:44 -0700 Subject: [PATCH 0152/1165] documentation: Add updates to CONTRIBUTING.md (#121) --- CONTRIBUTING.md | 195 ++++++++++++++++++++++++++++++----- README.md | 20 ++-- src/braket/_sdk/_version.py | 2 +- src/braket/aws/aws_device.py | 3 + 4 files changed, 183 insertions(+), 37 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 914e0741..fcbee924 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,55 +7,202 @@ Please read through this document before submitting any issues or pull requests information to effectively respond to your bug report or contribution. -## Reporting Bugs/Feature Requests +## Table of Contents + +* [Report Bugs/Feature Requests](#report-bugsfeature-requests) +* [Contribute via Pull Requests (PRs)](#contribute-via-pull-requests-prs) + * [Pull Down the Code](#pull-down-the-code) + * [Run the Unit Tests](#run-the-unit-tests) + * [Run the Integration Tests](#run-the-integration-tests) + * [Make and Test Your Change](#make-and-test-your-change) + * [Commit Your Change](#commit-your-change) + * [Send a Pull Request](#send-a-pull-request) +* [Documentation Guidelines](#documentation-guidelines) + * [API References (docstrings)](#api-references-docstrings) + * [Build and Test Documentation](#build-and-test-documentation) +* [Find Contributions to Work On](#find-contributions-to-work-on) +* [Code of Conduct](#code-of-conduct) +* [Security Issue Notifications](#security-issue-notifications) +* [Licensing](#licensing) + +## Report Bugs/Feature Requests We welcome you to use the GitHub issue tracker to report bugs or suggest features. -When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already +When filing an issue, please check [existing open](https://github.com/aws/amazon-braket-sdk-python/issues) and [recently closed](https://github.com/aws/amazon-braket-sdk-python/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20) issues to make sure somebody else hasn't already reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: -* A reproducible test case or series of steps -* The version of our code being used -* Any modifications you've made relevant to the bug -* Anything unusual about your environment or deployment +* A reproducible test case or series of steps. +* The version of our code being used. +* Any modifications you've made relevant to the bug. +* A description of your environment or deployment. -## Contributing via Pull Requests -Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: +## Contribute via Pull Requests (PRs) -1. You are working against the latest source on the *master* branch. -2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. -3. You open an issue to discuss any significant work - we would hate for your time to be wasted. +Contributions via pull requests are much appreciated. -To send us a pull request, please: +Before sending us a pull request, please ensure that: -1. Fork the repository. -2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. -3. Ensure local tests pass. -4. Commit to your fork using clear commit messages. -5. Send us a pull request, answering any default questions in the pull request interface. -6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. +* You are working against the latest source on the *main* branch. +* You check the existing open and recently merged pull requests to make sure someone else hasn't already addressed the problem. +* You open an issue to discuss any significant work - we would hate for your time to be wasted. -GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and -[creating a pull request](https://help.github.com/articles/creating-a-pull-request/). +### Pull Down the Code -## Finding contributions to work on -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' issues is a great place to start. +1. If you do not already have one, create a GitHub account by following the prompts at [Join Github](https://github.com/join). +1. Create a fork of this repository on GitHub. You should end up with a fork at `https://github.com//amazon-braket-sdk-python`. + 1. Follow the instructions at [Fork a Repo](https://help.github.com/en/articles/fork-a-repo) to fork a GitHub repository. +1. Clone your fork of the repository: `git clone https://github.com//amazon-braket-sdk-python` where `` is your github username. + + +### Run the Unit Tests + +1. Install tox using `pip install tox` +1. Install coverage using `pip install .[test]` +1. cd into the amazon-braket-sdk-python folder: `cd amazon-braket-sdk-python` or `cd /environment/amazon-braket-sdk-python` +1. Run the following tox command and verify that all unit tests pass: `tox -e unit-tests` + +You can also pass in various pytest arguments `tox -e unit-tests -- your-arguments` to run selected tests. For more information, please see [pytest usage](https://docs.pytest.org/en/stable/usage.html). + + +### Run the Integration Tests + +Run the integration tests to make sure that the system as a whole still works. + +1. Follow the instructions at [Set Up the AWS Command Line Interface (AWS CLI)](https://docs.aws.amazon.com/polly/latest/dg/setup-aws-cli.html). +1. Set the `AWS_PROFILE` information + ```bash + export AWS_PROFILE=Your_Profile_Name + ``` +1. Run the following tox command and verify that integ tests pass: `tox -e integ-tests` + +You can also pass in various pytest arguments `tox -e integ-tests -- your-arguments` to run selected tests. For more information, please see [pytest usage](https://docs.pytest.org/en/stable/usage.html). + +### Make and Test Your Change + +1. Create a new git branch: + ```shell + git checkout -b my-fix-branch main + ``` +1. Make your changes, **including unit tests** and, if appropriate, integration tests. + 1. Include unit tests when you contribute new features or make bug fixes, as they help to: + 1. Prove that your code works correctly. + 1. Guard against future breaking changes to lower the maintenance cost. + 1. Please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. +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). + + +### Commit Your Change + +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: + +``` +feature: support new parameter for `xyz` +fix: fix flake8 errors +documentation: add documentation for `xyz` +``` + +Valid prefixes are listed in the table below. + +| Prefix | Use for... | +|----------------:|:-----------------------------------------------------------------------------------------------| +| `breaking` | Incompatible API changes. | +| `deprecation` | Deprecating an existing API or feature, or removing something that was previously deprecated. | +| `feature` | Adding a new feature. | +| `fix` | Bug fixes. | +| `change` | Any other code change. | +| `documentation` | Documentation changes. | + +Some of the prefixes allow abbreviation ; `break`, `feat`, `depr`, and `doc` are all valid. If you omit a prefix, the commit will be treated as a `change`. + +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 document 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. + + +## Documentation Guidelines + +We use reStructuredText (RST) for most of our documentation. For a quick primer on the syntax, +see [the Sphinx documentation](https://www.sphinx-doc.org/en/main/usage/restructuredtext/basics.html). + +In this repository, we the docstrings create the API reference found on readthedocs. + +Here are some general guidelines to follow when writing either kind of documentation: +* Use present tense. + * 👍 "The device has this property..." + * 👎 "The device will have this property." +* When referring to an AWS product, use its full name in the first invocation. + (This applies only to prose; use what makes sense when it comes to writing code, etc.) + * 👍 "Amazon S3" + * 👎 "s3" +* Provide links to other ReadTheDocs pages, AWS documentation, etc. when helpful. + Try to not duplicate documentation when you can reference it instead. + * Use meaningful text in a link. + + +### API References (docstrings) + +The API references are generated from docstrings. +A docstring is the comment in the source code that describes a module, class, function, or variable. + +```python +def foo(): + """This comment is a docstring for the function foo.""" +``` + +We use [Google-style docstrings](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html). +There should be a docstring for every public module, class, and function. +For functions, make sure your docstring covers all of the arguments, exceptions, and any other relevant information. +When possible, link to classes and functions, e.g. use ":class:~\`braket.aws.AwsSession\`" over just "AwsSession." + +If a parameter of a function has a default value, please note what the default is. +If that default value is `None`, it can also be helpful to explain what happens when the parameter is `None`. +If `**kwargs` is part of the function signature, link to the parent class(es) or method(s) so that the reader knows where to find the available parameters. + +For an example file with docstrings, see [the `circuit` module](https://github.com/aws/amazon-braket-sdk-python/blob/main/src/braket/circuits/circuit.py). + + +### Build and Test Documentation + +To build the Sphinx docs, run the following command in the root repo directory: + +```shell +tox -e docs +``` + +You can then find the generated HTML files in `build/documentation/html`. + + +## Find Contributions to Work On + +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/aws/amazon-braket-sdk-python/labels/help%20wanted) issues is a great place to start. ## Code of Conduct + This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact opensource-codeofconduct@amazon.com with any additional questions or comments. -## Security issue notifications +## Security Issue Notifications + If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. ## Licensing -See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. +See the [LICENSE](https://github.com/aws/amazon-braket-sdk-python/blob/main/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. diff --git a/README.md b/README.md index 0a2b1ae8..7482de11 100644 --- a/README.md +++ b/README.md @@ -357,8 +357,11 @@ tox -e docs To view the generated documentation, open the following file in a browser: `../amazon-braket-sdk-python/build/documentation/html/index.html` -## Install the SDK for Testing -Make sure to install test dependencies first: +## Testing + +This repository has both unit and integration tests. + +To run the tests, make sure to install test dependencies first: ```bash pip install -e "amazon-braket-sdk-python[test]" ``` @@ -368,10 +371,8 @@ pip install -e "amazon-braket-sdk-python[test]" tox -e unit-tests ``` -To run an individual test -``` -tox -e unit-tests -- -k 'your_test' -``` +You can also pass in various pytest arguments `tox -e integ-tests -- your-arguments` to run selected tests. For more information, please see [pytest usage](https://docs.pytest.org/en/stable/usage.html). + To run linters and doc generators and unit tests ```bash @@ -384,17 +385,12 @@ Set the `AWS_PROFILE` information in the Prerequisites section of this document. export AWS_PROFILE=Your_Profile_Name ``` -Create an S3 bucket in the same account as the `AWS_PROFILE` with the following naming convention `braket-sdk-integ-tests-{account_id}`. - Run the tests ```bash tox -e integ-tests ``` -To run an individual test -```bash -tox -e integ-tests -- -k 'your_test' -``` +You can also pass in various pytest arguments `tox -e integ-tests -- your-arguments` to run selected tests. For more information, please see [pytest usage](https://docs.pytest.org/en/stable/usage.html). ## License This project is licensed under the Apache-2.0 License. \ No newline at end of file diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index ddd93c06..ff1d1bc4 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.5.0" +__version__ = "0.5.1" diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index d040add9..970d40d1 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -57,6 +57,9 @@ def __init__(self, arn: str, aws_session=None): self._device = AwsQpu(arn, aws_session) else: self._device = AwsQuantumSimulator(arn, aws_session) + self._name = self._device.name + self._status = self._device.status + self._status_reason = self._device.status_reason def run( self, From 6c1eeb7a8ccae774a27917f8f73e7e4b3853c619 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Tue, 4 Aug 2020 11:08:33 -0700 Subject: [PATCH 0153/1165] documentation: fix typo (#122) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7482de11..30465771 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ Rename the folders to the following: Then copy the renamed files and paste them into the `braketvirtenv` folder where you created a virtual environment. Your folder structure should look like this: ```bash -..\YourFolder\braketvirtenv\amazon-braket-schemas-python\ +..\YourFolder\braketvirtenv\amazon-braket-sdk-python\ ``` ### Install the SDK packages From 5338b3b9fb88a6d63c3f954e060376ec67cbfdf5 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 6 Aug 2020 16:28:23 -0700 Subject: [PATCH 0154/1165] Fix coverage (#123) Added **/braket/device_schema/* to .coveragerc --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 81bdfe72..a5882e46 100644 --- a/.coveragerc +++ b/.coveragerc @@ -5,6 +5,7 @@ source = braket omit = **/braket/ir/* + **/braket/device_schema/* **/braket/schema_common/* **/braket/task_result/* **/braket/simulator/* From 61b1a0993aebef66e65b2ea55f7f8446ca25083d Mon Sep 17 00:00:00 2001 From: Ravi Kiran Chilakapati Date: Thu, 6 Aug 2020 13:03:05 -0700 Subject: [PATCH 0155/1165] Use boto3 supplied regional endpoints --- src/braket/aws/aws_session.py | 16 +++++----------- test/unit_tests/braket/aws/test_aws_session.py | 7 ++----- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 4f7b25c5..76232149 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -23,11 +23,7 @@ class AwsSession(object): S3DestinationFolder = NamedTuple("S3DestinationFolder", [("bucket", str), ("key", int)]) - BRAKET_ENDPOINTS = { - "us-west-1": "https://fdoco1n1x7.execute-api.us-west-1.amazonaws.com/V4", - "us-west-2": "https://xe15dbdvw6.execute-api.us-west-2.amazonaws.com/V4", - "us-east-1": "https://kqjovr0n70.execute-api.us-east-1.amazonaws.com/V4", - } + BRAKET_REGIONS = ["us-east-1", "us-west-1", "us-west-2"] # similar to sagemaker sdk: # https://github.com/aws/sagemaker-python-sdk/blob/master/src/sagemaker/session.py @@ -47,14 +43,12 @@ def __init__(self, boto_session=None, braket_client=None): self.braket_client = braket_client else: region = self.boto_session.region_name - endpoint = AwsSession.BRAKET_ENDPOINTS.get(region, None) - if not endpoint: - supported_regions = list(AwsSession.BRAKET_ENDPOINTS.keys()) + if region not in AwsSession.BRAKET_REGIONS: raise ValueError( - f"No braket endpoint for {region}, supported Regions are {supported_regions}" + f"No braket endpoint for {region}, " + + f"supported Regions are {AwsSession.BRAKET_REGIONS}" ) - - self.braket_client = self.boto_session.client("braket", endpoint_url=endpoint) + self.braket_client = self.boto_session.client("braket") # # Quantum Tasks diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index d5f4ca2b..f62e93f1 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -42,12 +42,9 @@ def test_no_endpoint_for_region(): AwsSession(boto_session=boto_session, braket_client=None) -def test_uses_endpoint_for_region(boto_session): +def test_initializes_boto_client_if_required(boto_session): AwsSession(boto_session=boto_session, braket_client=None) - - boto_session.client.assert_called_with( - "braket", endpoint_url=AwsSession.BRAKET_ENDPOINTS[boto_session.region_name] - ) + boto_session.client.assert_called_with("braket") def test_uses_supplied_braket_client(): From e424afe0fc0487e0441567ebae17614da8217525 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 7 Aug 2020 18:28:15 -0700 Subject: [PATCH 0156/1165] Create pull_request_template.md (#126) Creates a pull request template with checkboxes to ensure contributors have followed all necessary steps. --- .github/pull_request_template.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..d3be7edd --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,22 @@ +*Issue #, if available:* + +*Description of changes:* + +*Testing done:* + +## Merge Checklist + +_Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your pull request._ + +#### General + +- [ ] I have read the [CONTRIBUTING](https://github.com/aws/amazon-braket-sdk-python/blob/main/CONTRIBUTING.md) doc +- [ ] I used the commit message format described in [CONTRIBUTING](https://github.com/aws/amazon-braket-sdk-python/blob/main/CONTRIBUTING.md#commit-your-change) +- [ ] I have updated any necessary documentation, including [READMEs](https://github.com/aws/amazon-braket-sdk-python/blob/main/README.md) and [API docs](https://github.com/aws/amazon-braket-sdk-python#braket-python-sdk-api-reference-documentation) (if appropriate) + +#### Tests + +- [ ] I have added tests that prove my fix is effective or that my feature works (if appropriate) +- [ ] I have checked that my tests are not configured for a specific region or account (if appropriate) + +By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. From 2edcbbdf90374bca893365fc71280870eaa08a87 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Sun, 9 Aug 2020 10:55:53 -0700 Subject: [PATCH 0157/1165] Use GetDevice API and deprecate AwsQpu and AwsQuantumSimulator (#125) --- src/braket/_sdk/_version.py | 2 +- src/braket/aws/__init__.py | 4 +- src/braket/aws/aws_device.py | 138 +++++- src/braket/aws/aws_qpu.py | 234 ---------- src/braket/aws/aws_quantum_simulator.py | 136 ------ src/braket/aws/aws_session.py | 39 +- src/braket/devices/device.py | 26 +- src/braket/devices/local_simulator.py | 15 +- test/integ_tests/test_device_creation.py | 7 +- .../braket/aws/common_test_utils.py | 171 ------- test/unit_tests/braket/aws/test_aws_device.py | 438 +++++++++++++++++- test/unit_tests/braket/aws/test_aws_qpu.py | 318 ------------- .../braket/aws/test_aws_quantum_simulator.py | 196 -------- .../unit_tests/braket/aws/test_aws_session.py | 42 +- .../braket/devices/test_local_simulator.py | 57 ++- 15 files changed, 622 insertions(+), 1201 deletions(-) delete mode 100644 src/braket/aws/aws_qpu.py delete mode 100644 src/braket/aws/aws_quantum_simulator.py delete mode 100644 test/unit_tests/braket/aws/test_aws_qpu.py delete mode 100644 test/unit_tests/braket/aws/test_aws_quantum_simulator.py diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index ff1d1bc4..e784cda2 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.5.1" +__version__ = "0.6.0" diff --git a/src/braket/aws/__init__.py b/src/braket/aws/__init__.py index 9d7efd6b..c99ecde3 100644 --- a/src/braket/aws/__init__.py +++ b/src/braket/aws/__init__.py @@ -11,8 +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.aws_device import AwsDevice # noqa: F401 -from braket.aws.aws_qpu import AwsQpu # noqa: F401 -from braket.aws.aws_quantum_simulator import AwsQuantumSimulator # noqa: F401 +from braket.aws.aws_device import AwsDevice, AwsDeviceType # noqa: F401 from braket.aws.aws_quantum_task import AwsQuantumTask # noqa: F401 from braket.aws.aws_session import AwsSession # noqa: F401 diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 970d40d1..875b5fd5 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -11,17 +11,27 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import Any, Dict, Union +from enum import Enum +from typing import Union -from networkx import Graph +import boto3 +from networkx import Graph, complete_graph, from_edgelist from braket.annealing.problem import Problem -from braket.aws.aws_qpu import AwsQpu -from braket.aws.aws_quantum_simulator import AwsQuantumSimulator from braket.aws.aws_quantum_task import AwsQuantumTask from braket.aws.aws_session import AwsSession from braket.circuits import Circuit +from braket.device_schema import DeviceCapabilities, GateModelQpuParadigmProperties +from braket.device_schema.dwave import DwaveProviderProperties from braket.devices.device import Device +from braket.schema_common import BraketSchemaBase + + +class AwsDeviceType(str, Enum): + """Possible AWS device types""" + + SIMULATOR = "SIMULATOR" + QPU = "QPU" class AwsDevice(Device): @@ -31,6 +41,12 @@ class AwsDevice(Device): device. """ + QPU_REGIONS = { + "rigetti": ["us-west-1"], + "ionq": ["us-east-1"], + "d-wave": ["us-west-2"], + } + _DUMMY_SHOTS = -1 DEFAULT_SHOTS_QPU = 1000 DEFAULT_SHOTS_SIMULATOR = 0 @@ -43,23 +59,22 @@ def __init__(self, arn: str, aws_session=None): arn (str): The ARN of the device aws_session (AwsSession, optional) aws_session: An AWS session object. Default = None. - Raises: - ValueError: If an unknown `arn` is specified. - Note: Some devices (QPUs) are physically located in specific AWS Regions. In some cases, the current `aws_session` connects to a Region other than the Region in which the QPU is physically located. When this occurs, a cloned `aws_session` is created for the Region the QPU is located in. + + See `braket.aws.aws_device.AwsQpu.QPU_REGIONS` for the AWS Regions the QPUs are located + in. """ - super().__init__(name=None, status=None, status_reason=None) - if "qpu" in arn: - self._device = AwsQpu(arn, aws_session) - else: - self._device = AwsQuantumSimulator(arn, aws_session) - self._name = self._device.name - self._status = self._device.status - self._status_reason = self._device.status_reason + super().__init__(name=None, status=None) + self._arn = arn + self._aws_session = AwsDevice._aws_session_for_device(arn, aws_session) + self._properties = None + self._provider_name = None + self._type = None + self.refresh_metadata() def run( self, @@ -120,7 +135,9 @@ def run( shots = AwsDevice.DEFAULT_SHOTS_QPU else: shots = AwsDevice.DEFAULT_SHOTS_SIMULATOR - return self._device.run( + return AwsQuantumTask.create( + self._aws_session, + self._arn, task_specification, s3_destination_folder, shots, @@ -134,18 +151,35 @@ def refresh_metadata(self) -> None: """ Refresh the `AwsDevice` object with the most recent Device metadata. """ - self._device.refresh_metadata() + metadata = self._aws_session.get_device(self._arn) + self._name = metadata.get("deviceName") + self._status = metadata.get("deviceStatus") + self._type = AwsDeviceType(metadata.get("deviceType")) + self._provider_name = metadata.get("providerName") + qpu_properties = metadata.get("deviceCapabilities") + self._properties = BraketSchemaBase.parse_raw_schema(qpu_properties) + self._topology_graph = self._construct_topology_graph() + + @property + def type(self) -> str: + """str: Return the device type""" + return self._type + + @property + def provider_name(self) -> str: + """str: Return the provider name""" + return self._provider_name @property def arn(self) -> str: """str: Return the ARN of the device""" - return self._device.arn + return self._arn @property # TODO: Add a link to the boto3 docs - def properties(self) -> Dict[str, Any]: - """Dict[str, Any]: Return the device properties""" - return self._device.properties + def properties(self) -> DeviceCapabilities: + """DeviceCapabilities: Return the device properties""" + return self._properties @property def topology_graph(self) -> Graph: @@ -162,11 +196,69 @@ def topology_graph(self) -> Graph: >>> print(device.topology_graph.edges) """ - if hasattr(self._device, "topology_graph"): - return self._device.topology_graph + return self._topology_graph + + def _construct_topology_graph(self) -> Graph: + """ + Construct topology graph. If no such metadata is available, return None. + + Returns: + Graph: topology of QPU as a networkx Graph object + """ + if hasattr(self.properties, "paradigm") and isinstance( + self.properties.paradigm, GateModelQpuParadigmProperties + ): + if self.properties.paradigm.connectivity.fullyConnected: + return complete_graph(int(self.properties.paradigm.qubitCount)) + adjacency_lists = self.properties.paradigm.connectivity.connectivityGraph + edges = [] + for item in adjacency_lists.items(): + i = item[0] + edges.extend([(int(i), int(j)) for j in item[1]]) + return from_edgelist(edges) + elif hasattr(self.properties, "provider") and isinstance( + self.properties.provider, DwaveProviderProperties + ): + edges = self.properties.provider.couplers + return from_edgelist(edges) else: return None + @staticmethod + def _aws_session_for_device(device_arn: str, aws_session: AwsSession) -> AwsSession: + """AwsSession: Returns an AwsSession for the device ARN. """ + if "qpu" in device_arn: + return AwsDevice._aws_session_for_qpu(device_arn, aws_session) + else: + return aws_session or AwsSession() + + @staticmethod + def _aws_session_for_qpu(device_arn: str, aws_session: AwsSession) -> AwsSession: + """ + Get an AwsSession for the device ARN. QPUs are physically located in specific AWS Regions. + The AWS sessions should connect to the Region that the QPU is located in. + + See `braket.aws.aws_qpu.AwsDevice.QPU_REGIONS` for the AWS Regions the QPUs are located in. + """ + region_key = device_arn.split("/")[-2] + qpu_regions = AwsDevice.QPU_REGIONS.get(region_key, []) + + if aws_session: + if aws_session.boto_session.region_name in qpu_regions: + return aws_session + else: + creds = aws_session.boto_session.get_credentials() + boto_session = boto3.Session( + aws_access_key_id=creds.access_key, + aws_secret_access_key=creds.secret_key, + aws_session_token=creds.token, + region_name=qpu_regions[0], + ) + return AwsSession(boto_session=boto_session) + else: + boto_session = boto3.Session(region_name=qpu_regions[0]) + return AwsSession(boto_session=boto_session) + def __repr__(self): return "Device('name': {}, 'arn': {})".format(self.name, self.arn) diff --git a/src/braket/aws/aws_qpu.py b/src/braket/aws/aws_qpu.py deleted file mode 100644 index f75510df..00000000 --- a/src/braket/aws/aws_qpu.py +++ /dev/null @@ -1,234 +0,0 @@ -# Copyright 2019-2019 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 typing import Any, Dict, Union - -import boto3 -from networkx import Graph, complete_graph, from_edgelist - -from braket.annealing.problem import Problem -from braket.aws.aws_quantum_task import AwsQuantumTask -from braket.aws.aws_session import AwsSession -from braket.circuits import Circuit -from braket.devices.device import Device - - -# TODO: deprecate -class AwsQpu(Device): - """ - Amazon Braket implementation of a Quantum Processing Unit (QPU). - Use this class to retrieve the latest metadata about the QPU, and to run a quantum task on the - QPU. - """ - - QPU_REGIONS = { - "rigetti": ["us-west-1"], - "ionq": ["us-east-1"], - "d-wave": ["us-west-2"], - } - - DEFAULT_SHOTS_QPU = 1000 - DEFAULT_RESULTS_POLL_TIMEOUT_QPU = 432000 - DEFAULT_RESULTS_POLL_INTERVAL_QPU = 1 - - def __init__(self, arn: str, aws_session=None): - """ - Args: - arn (str): The ARN of the QPU, for example, "arn:aws:aqx:::qpu:ionq" - aws_session (AwsSession, optional) aws_session: An AWS session object. Default = None. - - Raises: - ValueError: If an unknown `arn` is specified. - - Note: - QPUs are physically located in specific AWS Regions. In some cases, the current - `aws_session` connects to a Region other than the Region in which the QPU is - physically located. When this occurs, a cloned `aws_session` is created for the Region - the QPU is located in. - - See `braket.aws.aws_qpu.AwsQpu.QPU_REGIONS` for the AWS Regions the QPUs are located - in. - """ - super().__init__(name=None, status=None, status_reason=None) - self._arn = arn - self._aws_session = self._aws_session_for_qpu(arn, aws_session) - self._properties = None - self.refresh_metadata() - - def run( - self, - task_specification: Union[Circuit, Problem], - s3_destination_folder: AwsSession.S3DestinationFolder, - shots: int = DEFAULT_SHOTS_QPU, - poll_timeout_seconds: int = DEFAULT_RESULTS_POLL_TIMEOUT_QPU, - poll_interval_seconds: int = DEFAULT_RESULTS_POLL_INTERVAL_QPU, - *aws_quantum_task_args, - **aws_quantum_task_kwargs, - ) -> AwsQuantumTask: - """ - Run a quantum task specification on this quantum device. A task can be a circuit or an - annealing problem. - - Args: - task_specification (Union[Circuit, Problem]): Specification of task - (circuit or annealing problem) to run on device. - s3_destination_folder: The S3 location to save the task's results - shots (int, optional): The number of times to run the circuit or annealing problem. - Default is 1000. - poll_timeout_seconds (int): The polling timeout for AwsQuantumTask.result(), in seconds. - Default: 5 days. - poll_interval_seconds (int): The polling interval for AwsQuantumTask.result(), - in seconds. Default: 1 second. - *aws_quantum_task_args: Variable length positional arguments for - `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. - **aws_quantum_task_kwargs: Variable length keyword arguments for - `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. - - Returns: - AwsQuantumTask: An AwsQuantumTask that tracks the execution on the device. - - Examples: - >>> circuit = Circuit().h(0).cnot(0, 1) - >>> device = AwsQpu("arn:aws:aqx:::qpu:rigetti") - >>> device.run(circuit, ("bucket-foo", "key-bar")) - - >>> circuit = Circuit().h(0).cnot(0, 1) - >>> device = AwsQpu("arn:aws:aqx:::qpu:rigetti") - >>> device.run(task_specification=circuit, - >>> s3_destination_folder=("bucket-foo", "key-bar")) - - >>> problem = Problem( - >>> ProblemType.ISING, - >>> linear={1: 3.14}, - >>> quadratic={(1, 2): 10.08}, - >>> ) - >>> device = AwsQpu("arn:aws:aqx:::qpu:d-wave") - >>> device.run(problem, ("bucket-foo", "key-bar"), - >>> device_parameters = {"dWaveParameters": {"postprocessingType": "SAMPLING"}}) - - See Also: - `braket.aws.aws_quantum_task.AwsQuantumTask.create()` - """ - - # TODO: Restrict execution to compatible task types - return AwsQuantumTask.create( - self._aws_session, - self._arn, - task_specification, - s3_destination_folder, - shots, - poll_timeout_seconds=poll_timeout_seconds, - poll_interval_seconds=poll_interval_seconds, - *aws_quantum_task_args, - **aws_quantum_task_kwargs, - ) - - def refresh_metadata(self) -> None: - """ - Refresh the `AwsQpu` object with the most recent QPU metadata. - """ - qpu_metadata = self._aws_session.get_qpu_metadata(self._arn) - self._name = qpu_metadata.get("name") - self._status = qpu_metadata.get("status") - self._status_reason = qpu_metadata.get("statusReason") - qpu_properties = qpu_metadata.get("properties") - self._properties = ( - qpu_properties.get("annealingModelProperties", {}).get("dWaveProperties") - if "annealingModelProperties" in qpu_properties - else qpu_properties.get("gateModelProperties") - ) - self._topology_graph = self._construct_topology_graph() - - @property - def arn(self) -> str: - """str: Return the ARN of the QPU""" - return self._arn - - @property - # TODO: Add a link to the boto3 docs - def properties(self) -> Dict[str, Any]: - """Dict[str, Any]: Return the QPU properties""" - return self._properties - - @property - def topology_graph(self) -> Graph: - """Graph: topology of QPU as a networkx Graph object - - Examples: - >>> import networkx as nx - >>> device = AwsQpu("arn:aws:aqx:::qpu:rigetti") - >>> nx.draw_kamada_kawai(device.topology_graph, with_labels=True, font_weight="bold") - - >>> topology_subgraph = device.topology_graph.subgraph(range(8)) - >>> nx.draw_kamada_kawai(topology_subgraph, with_labels=True, font_weight="bold") - - >>> print(device.topology_graph.edges) - """ - return self._topology_graph - - def _construct_topology_graph(self) -> Graph: - """ - Construct topology graph. If no such metadata is available, return None. - - Returns: - Graph: topology of QPU as a networkx Graph object - """ - if "connectivity" in self.properties: - adjacency_lists = self.properties["connectivity"]["connectivityGraph"] - edges = [] - for item in adjacency_lists.items(): - i = item[0] - edges.extend([(int(i), int(j)) for j in item[1]]) - if len(edges) == 0: # empty connectivity graph means fully connected - return complete_graph(int(self.properties["qubitCount"])) - else: - return from_edgelist(edges) - elif "couplers" in self.properties: - edges = self.properties["couplers"] - return from_edgelist(edges) - else: - return None - - def _aws_session_for_qpu(self, qpu_arn: str, aws_session: AwsSession) -> AwsSession: - """ - Get an AwsSession for the QPU ARN. QPUs are physically located in specific AWS Regions. - The AWS sessions should connect to the Region that the QPU is located in. - - See `braket.aws.aws_qpu.AwsQpu.QPU_REGIONS` for the AWS Regions the QPUs are located in. - """ - region_key = qpu_arn.split("/")[-2] - qpu_regions = AwsQpu.QPU_REGIONS.get(region_key, []) - - if aws_session: - if aws_session.boto_session.region_name in qpu_regions: - return aws_session - else: - creds = aws_session.boto_session.get_credentials() - boto_session = boto3.Session( - aws_access_key_id=creds.access_key, - aws_secret_access_key=creds.secret_key, - aws_session_token=creds.token, - region_name=qpu_regions[0], - ) - return AwsSession(boto_session=boto_session) - else: - boto_session = boto3.Session(region_name=qpu_regions[0]) - return AwsSession(boto_session=boto_session) - - def __repr__(self): - return "QPU('name': {}, 'arn': {})".format(self.name, self.arn) - - def __eq__(self, other): - if isinstance(other, AwsQpu): - return self.arn == other.arn - return NotImplemented diff --git a/src/braket/aws/aws_quantum_simulator.py b/src/braket/aws/aws_quantum_simulator.py deleted file mode 100644 index 58237077..00000000 --- a/src/braket/aws/aws_quantum_simulator.py +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright 2019-2019 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 typing import Any, Dict, Union - -from braket.annealing.problem import Problem -from braket.aws.aws_quantum_task import AwsQuantumTask -from braket.aws.aws_session import AwsSession -from braket.circuits import Circuit -from braket.devices.device import Device - - -# TODO: deprecate -class AwsQuantumSimulator(Device): - """ - Amazon Braket implementation of a quantum simulator. - Use this class to retrieve the latest metadata about the simulator, - and to run a task on the simulator. - """ - - DEFAULT_SHOTS_SIMULATOR = 0 - DEFAULT_RESULTS_POLL_TIMEOUT_SIMULATOR = 432000 - DEFAULT_RESULTS_POLL_INTERVAL_SIMULATOR = 1 - - def __init__(self, arn: str, aws_session=None): - """ - Args: - arn (str): The ARN of the simulator, for example, - "arn:aws:aqx:::quantum-simulator:aqx:qs1". - aws_session (AwsSession, optional) aws_session: An AWS session object. Default = None. - """ - super().__init__(name=None, status=None, status_reason=None) - self._arn = arn - self._aws_session = aws_session or AwsSession() - self._properties: Dict[str, Any] = None - self.refresh_metadata() - - def run( - self, - task_specification: Union[Circuit, Problem], - s3_destination_folder: AwsSession.S3DestinationFolder, - shots: int = DEFAULT_SHOTS_SIMULATOR, - poll_timeout_seconds: int = DEFAULT_RESULTS_POLL_TIMEOUT_SIMULATOR, - poll_interval_seconds: int = DEFAULT_RESULTS_POLL_INTERVAL_SIMULATOR, - *aws_quantum_task_args, - **aws_quantum_task_kwargs, - ) -> AwsQuantumTask: - """ - Run a task on the simulator device. - - Args: - task_specification (Union[Circuit, Problem]): Specification of task - (circuit or annealing problem) to run on device. - s3_destination_folder: The S3 location to save the task's results - shots (int, optional): The number of times to run the circuit or annealing problem. - Default is 0. - For circuits, when `shots=0`, the simulator will support simulator-only - result types, compute the exact results based on the task specification, - and sampling is not supported. - `shots>0` means that the simulator will be treated like a QPU and - only support result types available for a QPU. - poll_timeout_seconds (int): The polling timeout for AwsQuantumTask.result(), in seconds. - Default: 432000 (5 days). - poll_interval_seconds (int): The polling interval for AwsQuantumTask.result(), - in seconds. Default: 1. - *aws_quantum_task_args: Variable length positional arguments for - `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. - **aws_quantum_task_kwargs: Variable length keyword arguments for - `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. - - Returns: - AwsQuantumTask: An AwsQuantumTask that tracks the task execution on the device. - - Examples: - >>> circuit = Circuit().h(0).cnot(0, 1) - >>> device = AwsQuantumSimulator("arn:aws:aqx:::quantum-simulator:aqx:qs1") - >>> device.run(circuit, ("bucket", "key"), shots=1000) - - >>> circuit = Circuit().h(0).cnot(0, 1) - >>> device = AwsQuantumSimulator("arn:aws:aqx:::quantum-simulator:aqx:qs1") - >>> device.run(circuit=circuit, s3_destination_folder=("bucket", "key"), shots=1000) - - See Also: - `braket.aws.aws_quantum_task.AwsQuantumTask.create()` - """ - return AwsQuantumTask.create( - self._aws_session, - self._arn, - task_specification, - s3_destination_folder, - shots, - poll_timeout_seconds=poll_timeout_seconds, - poll_interval_seconds=poll_interval_seconds, - *aws_quantum_task_args, - **aws_quantum_task_kwargs, - ) - - def refresh_metadata(self) -> None: - """Refresh the AwsQuantumSimulator object with the most recent simulator metadata.""" - simulator_metadata = self._aws_session.get_simulator_metadata(self._arn) - self._name = simulator_metadata.get("name") - self._status = simulator_metadata.get("status") - self._status_reason = simulator_metadata.get("statusReason") - self._properties = { - k: simulator_metadata.get(k) - for k in ["supportedQuantumOperations", "qubitCount", "supportedResultTypes"] - } - - @property - def arn(self) -> str: - """str: Returns the ARN of the simulator.""" - return self._arn - - @property - # TODO: Add a link to the boto3 docs - def properties(self) -> Dict[str, Any]: - """Dict[str, Any]: Return the simulator properties""" - return self._properties - - def __repr__(self): - return f"QuantumSimulator('name': {self.name}, 'arn': {self.arn})" - - def __eq__(self, other): - if isinstance(other, AwsQuantumSimulator): - return self.arn == other.arn - return NotImplemented diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 76232149..2ac310ef 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -25,8 +25,6 @@ class AwsSession(object): BRAKET_REGIONS = ["us-east-1", "us-west-1", "us-west-2"] - # similar to sagemaker sdk: - # https://github.com/aws/sagemaker-python-sdk/blob/master/src/sagemaker/session.py def __init__(self, boto_session=None, braket_client=None): """ Args: @@ -117,40 +115,15 @@ def retrieve_s3_object_body(self, s3_bucket: str, s3_object_key: str) -> str: obj = s3.Object(s3_bucket, s3_object_key) return obj.get()["Body"].read().decode("utf-8") - # TODO: add in boto3 exception handling once we have exception types in API - def get_qpu_metadata(self, arn: str) -> Dict[str, Any]: + def get_device(self, arn: str) -> Dict[str, Any]: """ - Calls the Amazon Braket `DescribeQpus` (`describe_qpus`) operation to retrieve - QPU metadata. + Calls the Amazon Braket `get_device` API to + retrieve device metadata Args: - arn (str): The ARN of the QPU to retrieve metadata from + arn (str): The ARN of the device Returns: - Dict[str, Any]: QPU metadata + Dict[str, Any]: Device metadata """ - try: - response = self.braket_client.describe_qpus(qpuArns=[arn]) - qpu_metadata = response.get("qpus")[0] - return qpu_metadata - except Exception as e: - raise e - - # TODO: add in boto3 exception handling once we have exception types in API - def get_simulator_metadata(self, arn: str) -> Dict[str, Any]: - """ - Calls the Amazon Braket `DescribeQuantumSimulators` (`describe_quantum_simulators`) to - retrieve simulator metadata - - Args: - arn (str): The ARN of the simulator to retrieve metadata from - - Returns: - Dict[str, Any]: Simulator metadata - """ - try: - response = self.braket_client.describe_quantum_simulators(quantumSimulatorArns=[arn]) - simulator_metadata = response.get("quantumSimulators")[0] - return simulator_metadata - except Exception as e: - raise e + return self.braket_client.get_device(deviceArn=arn) diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 55a76d1d..4c9395a2 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -24,25 +24,18 @@ class Device(ABC): """ - def __init__(self, name: str, status: str, status_reason: str): + def __init__(self, name: str, status: str): """ Args: - name: Name of quantum device - status: Status of quantum device - status_reason: Status reason of quantum device + name (str): Name of quantum device + status (str): Status of quantum device """ self._name = name self._status = status - self._status_reason = status_reason @abstractmethod def run( - self, - task_specification: Union[Circuit, Problem], - location, - shots: Optional[int], - *args, - **kwargs + self, task_specification: Union[Circuit, Problem], shots: Optional[int], *args, **kwargs ) -> QuantumTask: """ Run a quantum task specification on this quantum device. A task can be a circuit or an annealing problem. @@ -51,8 +44,6 @@ def run( task_specification (Union[Circuit, Problem]): Specification of a task to run on device. - location: The location to save the task's results - shots (int): The number of times to run the task on the device. Default is 1_000. Returns: @@ -76,12 +67,3 @@ def status(self) -> str: str: The status of this Device """ return self._status - - @property - def status_reason(self) -> str: - """ Return the status reason of this Device. - - Returns: - str: The reason that the device is in the current status - """ - return self._status_reason diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index c69ad920..fb0f3277 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -12,13 +12,14 @@ # language governing permissions and limitations under the License. from functools import singledispatch -from typing import Any, Dict, Optional, Set, Union +from typing import Optional, Set, Union import pkg_resources from braket.annealing.problem import Problem from braket.circuits import Circuit from braket.circuits.circuit_helpers import validate_circuit_and_shots +from braket.device_schema import DeviceActionType, DeviceCapabilities from braket.devices.device import Device from braket.simulator import BraketSimulator from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult @@ -45,9 +46,7 @@ def __init__(self, backend: Union[str, BraketSimulator] = "default"): """ delegate = _get_simulator(backend) super().__init__( - name=delegate.__class__.__name__, - status="AVAILABLE", - status_reason="Local simulator loaded successfully", + name=delegate.__class__.__name__, status="AVAILABLE", ) self._delegate = delegate @@ -82,8 +81,8 @@ def run( return LocalQuantumTask(result) @property - def properties(self) -> Dict[str, Any]: - """ Dict[str, Any]: Properties of the LocalSimulator device """ + def properties(self) -> DeviceCapabilities: + """ DeviceCapabilities: Properties of the LocalSimulator device """ return self._delegate.properties @staticmethod @@ -125,7 +124,7 @@ def _run_internal( @_run_internal.register def _(circuit: Circuit, simulator: BraketSimulator, shots, *args, **kwargs): - if "jaqcd" not in simulator.properties["supportedIrTypes"]: + if DeviceActionType.JAQCD not in simulator.properties.action: raise NotImplementedError(f"{type(simulator)} does not support qubit gate-based programs") validate_circuit_and_shots(circuit, shots) program = circuit.to_ir() @@ -136,7 +135,7 @@ def _(circuit: Circuit, simulator: BraketSimulator, shots, *args, **kwargs): @_run_internal.register def _(problem: Problem, simulator: BraketSimulator, shots, *args, **kwargs): - if "annealing" not in simulator.properties["supportedIrTypes"]: + 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) diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 4007ddcd..9e475e24 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -22,9 +22,14 @@ @pytest.mark.parametrize("arn", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (SIMULATOR_ARN)]) -def test_qpu_creation(arn, aws_session): +def test_device_creation(arn, aws_session): device = AwsDevice(arn, aws_session=aws_session) assert device.arn == arn + assert device.name + assert device.status + assert device.type + assert device.provider_name + assert device.properties def test_device_across_regions(aws_session): diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index ff5f5edb..72519b96 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -20,177 +20,6 @@ SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" -class MockDevices: - - MOCK_RIGETTI_QPU_1 = { - "arn": RIGETTI_ARN, - "properties": { - "gateModelProperties": { - "qubitCount": 16, - "connectivity": { - "connectivityGraph": {"0": ["1", "2"], "1": ["0", "2"], "2": ["0", "1"]} - }, - "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "T"], - } - }, - "name": "Rigetti", - "status": "AVAILABLE", - } - - MOCK_RIGETTI_QPU_2 = { - "arn": RIGETTI_ARN, - "properties": { - "gateModelProperties": { - "qubitCount": 30, - "connectivity": { - "connectivityGraph": {"0": ["1", "2"], "1": ["0", "2"], "2": ["0", "1"]} - }, - "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "T", "S"], - } - }, - "name": "Rigetti", - "status": "UNAVAILABLE", - "statusReason": "Under maintenance", - } - - MOCK_DWAVE_QPU_1 = { - "arn": DWAVE_ARN, - "properties": { - "annealingModelProperties": { - "dWaveProperties": { - "activeQubitCount": 2, - "annealingOffsetStep": 2.0, - "annealingOffsetStepPhi0": 4.0, - "annealingOffsetRanges": [[1.34, 5.23], [3.24, 1.44]], - "annealingDurationRange": [3, 5], - "couplers": [[1, 2]], - "defaultAnnealingDuration": 4, - "defaultProgrammingThermalizationDuration": 2, - "defaultReadoutThermalizationDuration": 1, - "extendedJRange": [3.0, 4.0], - "hGainScheduleRange": [2.0, 3.0], - "hRange": [3.4, 5.6], - "jRange": [1.0, 2.0], - "maximumAnnealingSchedulePoints": 3, - "maximumHGainSchedulePoints": 2, - "qubitCount": 3, - "qubits": [0, 2], - "perQubitCouplingRange": [1.0, 3.0], - "programmingThermalizationDurationRange": [1, 2], - "quotaConversionRate": 2.5, - "readoutThermalizationDurationRange": [4, 6], - "shotsRange": [3, 5], - "taskRunDurationRange": [3, 6], - "topology": {"type": "chimera", "topology": [1, 1, 1]}, - } - } - }, - "name": "D-Wave", - "status": "AVAILABLE", - } - - MOCK_DWAVE_QPU_2 = { - "arn": DWAVE_ARN, - "properties": { - "annealingModelProperties": { - "dWaveProperties": { - "activeQubitCount": 3, - "annealingOffsetStep": 2.0, - "annealingOffsetStepPhi0": 4.0, - "annealingOffsetRanges": [[1.34, 5.23], [3.24, 1.44]], - "annealingDurationRange": [3, 5], - "couplers": [[1, 2]], - "defaultAnnealingDuration": 4, - "defaultProgrammingThermalizationDuration": 2, - "defaultReadoutThermalizationDuration": 1, - "extendedJRange": [3.0, 4.0], - "hGainScheduleRange": [2.0, 3.0], - "hRange": [3.4, 5.6], - "jRange": [1.0, 2.0], - "maximumAnnealingSchedulePoints": 3, - "maximumHGainSchedulePoints": 2, - "qubitCount": 3, - "qubits": [0, 1, 2], - "perQubitCouplingRange": [1.0, 3.0], - "programmingThermalizationDurationRange": [1, 2], - "quotaConversionRate": 2.5, - "readoutThermalizationDurationRange": [4, 6], - "shotsRange": [3, 5], - "taskRunDurationRange": [3, 6], - "topology": {"type": "chimera", "topology": [1, 1, 1]}, - } - } - }, - "name": "D-Wave", - "status": "UNAVAILABLE", - "statusReason": "Under maintenance", - } - - MOCK_IONQ_QPU = { - "arn": IONQ_ARN, - "properties": { - "gateModelProperties": { - "qubitCount": 11, - "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "Toffoli"], - } - }, - "name": "IonQ", - "status": "UNAVAILABLE", - "statusReason": "Under maintenance", - } - - MOCK_QS1_SIMULATOR_1 = { - "arn": SIMULATOR_ARN, - "properties": { - "gateModelProperties": { - "qubitCount": 23, - "supportedQuantumOperations": ["CNOT", "H", "RZ", "RY", "RZ", "Toffoli"], - "supportedResultTypes": [ - { - "name": "Sample", - "observables": ["X", "Y", "Z"], - "minShots": 1, - "maxShots": 100, - }, - {"name": "Probability", "minShots": 1, "maxShots": 100,}, - ], - } - }, - "name": "integ_test_simulator", - "status": "AVAILABLE", - } - - MOCK_QS1_SIMULATOR_2 = { - "arn": SIMULATOR_ARN, - "properties": { - "gateModelProperties": { - "qubitCount": 30, - "supportedQuantumOperations": [ - "CNOT", - "H", - "RZ", - "RY", - "RZ", - "Toffoli", - "Phase", - "CPhase", - ], - "supportedResultTypes": [ - { - "name": "Sample", - "observables": ["X", "Y", "Z"], - "minShots": 1, - "maxShots": 100, - } - ], - } - }, - "name": "integ_test_simulator", - "status": "UNAVAILABLE", - "statusReason": "Temporary network issue", - } - - class MockS3: MOCK_S3_RESULT_GATE_MODEL = json.dumps( diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 23fec181..a2b48f11 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -11,22 +11,181 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from unittest.mock import Mock +from unittest.mock import Mock, patch import pytest +from common_test_utils import RIGETTI_ARN, run_and_assert -from braket.aws import AwsDevice +from braket.aws import AwsDevice, AwsDeviceType from braket.circuits import Circuit +from braket.device_schema.dwave import DwaveDeviceCapabilities +from braket.device_schema.rigetti import RigettiDeviceCapabilities +from braket.device_schema.simulators import GateModelSimulatorDeviceCapabilities +MOCK_GATE_MODEL_QPU_CAPABILITIES_1 = RigettiDeviceCapabilities.parse_obj( + { + "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.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": ["H"], + } + }, + "paradigm": { + "qubitCount": 30, + "nativeGateSet": ["ccnot", "cy"], + "connectivity": {"fullyConnected": False, "connectivityGraph": {"1": ["2", "3"]}}, + }, + "deviceParameters": {}, + } +) -@pytest.fixture -def arn(): - return "test_arn" +MOCK_GATE_MODEL_QPU_1 = { + "deviceName": "name1", + "deviceType": "QPU", + "providerName": "provider1", + "deviceStatus": "OFFLINE", + "deviceCapabilities": MOCK_GATE_MODEL_QPU_CAPABILITIES_1.json(), +} + +MOCK_GATE_MODEL_QPU_CAPABILITIES_2 = RigettiDeviceCapabilities.parse_obj( + { + "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.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": ["H"], + } + }, + "paradigm": { + "qubitCount": 30, + "nativeGateSet": ["ccnot", "cy"], + "connectivity": {"fullyConnected": True, "connectivityGraph": {}}, + }, + "deviceParameters": {}, + } +) + +MOCK_GATE_MODEL_QPU_2 = { + "deviceName": "blah", + "deviceType": "QPU", + "providerName": "blahhhh", + "deviceStatus": "OFFLINE", + "deviceCapabilities": MOCK_GATE_MODEL_QPU_CAPABILITIES_2.json(), +} + +MOCK_DWAVE_QPU_CAPABILITIES = DwaveDeviceCapabilities.parse_obj( + { + "braketSchemaHeader": { + "name": "braket.device_schema.dwave.dwave_device_capabilities", + "version": "1", + }, + "provider": { + "annealingOffsetStep": 1.45, + "annealingOffsetStepPhi0": 1.45, + "annealingOffsetRanges": [[1.45, 1.45], [1.45, 1.45]], + "annealingDurationRange": [1, 2, 3], + "couplers": [[1, 2], [1, 2]], + "defaultAnnealingDuration": 1, + "defaultProgrammingThermalizationDuration": 1, + "defaultReadoutThermalizationDuration": 1, + "extendedJRange": [1, 2, 3], + "hGainScheduleRange": [1, 2, 3], + "hRange": [1, 2, 3], + "jRange": [1, 2, 3], + "maximumAnnealingSchedulePoints": 1, + "maximumHGainSchedulePoints": 1, + "perQubitCouplingRange": [1, 2, 3], + "programmingThermalizationDurationRange": [1, 2, 3], + "qubits": [1, 2, 3], + "qubitCount": 1, + "quotaConversionRate": 1, + "readoutThermalizationDurationRange": [1, 2, 3], + "taskRunDurationRange": [1, 2, 3], + "topology": {}, + }, + "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"], + } + }, + "deviceParameters": {}, + } +) + +MOCK_DWAVE_QPU = { + "deviceName": "name3", + "deviceType": "QPU", + "providerName": "provider1", + "deviceStatus": "ONLINE", + "deviceCapabilities": MOCK_DWAVE_QPU_CAPABILITIES.json(), +} + +MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES = GateModelSimulatorDeviceCapabilities.parse_obj( + { + "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.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": ["H"], + } + }, + "paradigm": {"qubitCount": 30}, + "deviceParameters": {}, + } +) + +MOCK_GATE_MODEL_SIMULATOR = { + "deviceName": "name2", + "deviceType": "SIMULATOR", + "providerName": "provider1", + "deviceStatus": "ONLINE", + "deviceCapabilities": MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES.json(), +} + +RIGETTI_REGION_KEY = "rigetti" @pytest.fixture -def device(arn): - return AwsDevice(arn, Mock()) +def arn(): + return "test_arn" @pytest.fixture @@ -39,5 +198,266 @@ def circuit(): return Circuit().h(0) -# TODO: Unit tests for AWS device once AwsQpu and AwsQuantumSimulator are deleted -# and we have new V4 *Device APIs +@pytest.fixture +def boto_session(): + _boto_session = Mock() + _boto_session.region_name = AwsDevice.QPU_REGIONS[RIGETTI_REGION_KEY][0] + return _boto_session + + +@pytest.fixture +def aws_session(): + _boto_session = Mock() + _boto_session.region_name = AwsDevice.QPU_REGIONS[RIGETTI_REGION_KEY][0] + _aws_session = Mock() + _aws_session.boto_session = _boto_session + return _aws_session + + +@pytest.fixture +def device(aws_session): + def _device(arn): + aws_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 + return AwsDevice(arn, aws_session) + + return _device + + +@pytest.mark.parametrize( + "device_capabilities, get_device_data", + [ + (MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES, MOCK_GATE_MODEL_SIMULATOR), + (MOCK_GATE_MODEL_QPU_CAPABILITIES_1, MOCK_GATE_MODEL_QPU_1), + (MOCK_DWAVE_QPU_CAPABILITIES, MOCK_DWAVE_QPU), + ], +) +def test_device_creation(device_capabilities, get_device_data, arn): + mock_session = Mock() + mock_session.get_device.return_value = get_device_data + device = AwsDevice(arn, mock_session) + _assert_device_fields(device, device_capabilities, get_device_data) + + +def test_device_refresh_metadata(arn): + mock_session = Mock() + mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 + device = AwsDevice(arn, mock_session) + _assert_device_fields(device, MOCK_GATE_MODEL_QPU_CAPABILITIES_1, MOCK_GATE_MODEL_QPU_1) + + mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_2 + device.refresh_metadata() + _assert_device_fields(device, MOCK_GATE_MODEL_QPU_CAPABILITIES_2, MOCK_GATE_MODEL_QPU_2) + + +def test_equality(arn): + mock_session = Mock() + mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 + device_1 = AwsDevice(arn, mock_session) + device_2 = AwsDevice(arn, mock_session) + other_device = AwsDevice("foo_bar", mock_session) + non_device = "HI" + + assert device_1 == device_2 + assert device_1 is not device_2 + assert device_1 != other_device + assert device_1 != non_device + + +def test_repr(arn): + mock_session = Mock() + mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 + device = AwsDevice(arn, mock_session) + expected = "Device('name': {}, 'arn': {})".format(device.name, device.arn) + assert repr(device) == expected + + +def test_device_aws_session_in_qpu_region(aws_session): + arn = RIGETTI_ARN + aws_session.boto_session.region_name = AwsDevice.QPU_REGIONS[RIGETTI_REGION_KEY][0] + aws_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 + AwsDevice(arn, aws_session) + + aws_session.get_device.assert_called_with(arn) + + +@patch("braket.aws.aws_device.AwsSession") +@patch("boto3.Session") +def test_aws_session_in_another_qpu_region( + boto_session_init, aws_session_init, boto_session, aws_session +): + arn = RIGETTI_ARN + region = AwsDevice.QPU_REGIONS.get(RIGETTI_REGION_KEY)[0] + + boto_session_init.return_value = boto_session + aws_session_init.return_value = aws_session + aws_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 + + creds = Mock() + creds.access_key = "access key" + creds.secret_key = "secret key" + creds.token = "token" + + different_region_aws_session = Mock() + different_region_aws_session.boto_session.get_credentials.return_value = creds + different_region_aws_session.boto_session.profile_name = "profile name" + different_region_aws_session.boto_session.region_name = "foobar" + + AwsDevice(arn, different_region_aws_session) + + # assert creds, and region were correctly supplied + boto_session_init.assert_called_with( + aws_access_key_id=creds.access_key, + aws_secret_access_key=creds.secret_key, + aws_session_token=creds.token, + region_name=region, + ) + + # assert supplied session, different_region_aws_session, was replaced + aws_session.get_device.assert_called_with(arn) + + +@patch("braket.aws.aws_device.AwsSession") +@patch("boto3.Session") +def test_device_no_aws_session_supplied( + boto_session_init, aws_session_init, boto_session, aws_session +): + arn = RIGETTI_ARN + region = AwsDevice.QPU_REGIONS.get(RIGETTI_REGION_KEY)[0] + + boto_session_init.return_value = boto_session + aws_session_init.return_value = aws_session + aws_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 + + AwsDevice(arn) + + boto_session_init.assert_called_with(region_name=region) + aws_session.get_device.assert_called_with(arn) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +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, + ) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_with_positional_args(aws_quantum_task_mock, device, circuit, s3_destination_folder): + _run_and_assert( + aws_quantum_task_mock, device, circuit, s3_destination_folder, 100, 86400, 0.25, ["foo"] + ) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_with_kwargs(aws_quantum_task_mock, device, circuit, s3_destination_folder): + _run_and_assert( + aws_quantum_task_mock, + device, + circuit, + s3_destination_folder, + extra_kwargs={"bar": 1, "baz": 2}, + ) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_with_shots(aws_quantum_task_mock, device, circuit, s3_destination_folder): + _run_and_assert(aws_quantum_task_mock, device, circuit, s3_destination_folder, 100) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_with_shots_kwargs(aws_quantum_task_mock, device, circuit, s3_destination_folder): + _run_and_assert( + aws_quantum_task_mock, + device, + circuit, + s3_destination_folder, + 100, + extra_kwargs={"bar": 1, "baz": 2}, + ) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_with_qpu_no_shots(aws_quantum_task_mock, device, circuit, s3_destination_folder): + run_and_assert( + aws_quantum_task_mock, + device(RIGETTI_ARN), + AwsDevice.DEFAULT_SHOTS_QPU, + AwsDevice.DEFAULT_RESULTS_POLL_TIMEOUT, + AwsDevice.DEFAULT_RESULTS_POLL_INTERVAL, + circuit, + s3_destination_folder, + None, + None, + None, + None, + None, + ) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_with_shots_poll_timeout_kwargs( + aws_quantum_task_mock, device, circuit, s3_destination_folder +): + _run_and_assert( + aws_quantum_task_mock, + device, + circuit, + s3_destination_folder, + 100, + 86400, + extra_kwargs={"bar": 1, "baz": 2}, + ) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_with_positional_args_and_kwargs( + aws_quantum_task_mock, device, circuit, s3_destination_folder +): + _run_and_assert( + aws_quantum_task_mock, + device, + circuit, + s3_destination_folder, + 100, + 86400, + 0.25, + ["foo"], + {"bar": 1, "baz": 2}, + ) + + +def _run_and_assert( + aws_quantum_task_mock, + device_factory, + circuit, + s3_destination_folder, + shots=None, # Treated as positional arg + poll_timeout_seconds=None, # Treated as positional arg + poll_interval_seconds=None, # Treated as positional arg + extra_args=None, + extra_kwargs=None, +): + run_and_assert( + aws_quantum_task_mock, + device_factory("foo_bar"), + AwsDevice.DEFAULT_SHOTS_SIMULATOR, + AwsDevice.DEFAULT_RESULTS_POLL_TIMEOUT, + AwsDevice.DEFAULT_RESULTS_POLL_INTERVAL, + circuit, + s3_destination_folder, + shots, + poll_timeout_seconds, + poll_interval_seconds, + extra_args, + extra_kwargs, + ) + + +def _assert_device_fields(device, expected_properties, expected_device_data): + assert device.name == expected_device_data.get("deviceName") + assert device.properties == expected_properties + assert device.status == expected_device_data.get("deviceStatus") + assert device.provider_name == expected_device_data.get("providerName") + assert device.type == AwsDeviceType(expected_device_data.get("deviceType")) + if device.topology_graph: + assert device.topology_graph.edges == device._construct_topology_graph().edges diff --git a/test/unit_tests/braket/aws/test_aws_qpu.py b/test/unit_tests/braket/aws/test_aws_qpu.py deleted file mode 100644 index 920f67f5..00000000 --- a/test/unit_tests/braket/aws/test_aws_qpu.py +++ /dev/null @@ -1,318 +0,0 @@ -# Copyright 2019-2019 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 Mock, patch - -import networkx as nx -import pytest -from common_test_utils import DWAVE_ARN, IONQ_ARN, RIGETTI_ARN, MockDevices, run_and_assert - -from braket.aws import AwsQpu -from braket.circuits import Circuit - -RIGETTI_REGION_KEY = "rigetti" -IONQ_REGION_KEY = "ionq" - - -@pytest.fixture -def qpu(aws_session): - def _qpu(arn): - aws_session.get_qpu_metadata.return_value = MockDevices.MOCK_RIGETTI_QPU_1 - return AwsQpu(arn, aws_session) - - return _qpu - - -@pytest.fixture -def s3_destination_folder(): - return "bucket-foo", "key-bar" - - -@pytest.fixture -def circuit(): - return Circuit().h(0) - - -@pytest.fixture -def boto_session(): - _boto_session = Mock() - _boto_session.region_name = AwsQpu.QPU_REGIONS[RIGETTI_REGION_KEY][0] - return _boto_session - - -@pytest.fixture -def aws_session(): - _boto_session = Mock() - _boto_session.region_name = AwsQpu.QPU_REGIONS[RIGETTI_REGION_KEY][0] - _aws_session = Mock() - _aws_session.boto_session = _boto_session - return _aws_session - - -def test_aws_session_in_qpu_region(aws_session): - arn = RIGETTI_ARN - aws_session.boto_session.region_name = AwsQpu.QPU_REGIONS[RIGETTI_REGION_KEY][0] - aws_session.get_qpu_metadata.return_value = MockDevices.MOCK_RIGETTI_QPU_1 - AwsQpu(arn, aws_session) - - aws_session.get_qpu_metadata.assert_called_with(arn) - - -@patch("braket.aws.aws_qpu.AwsSession") -@patch("boto3.Session") -def test_aws_session_in_another_qpu_region( - boto_session_init, aws_session_init, boto_session, aws_session -): - arn = RIGETTI_ARN - region = AwsQpu.QPU_REGIONS.get(RIGETTI_REGION_KEY)[0] - - boto_session_init.return_value = boto_session - aws_session_init.return_value = aws_session - aws_session.get_qpu_metadata.return_value = MockDevices.MOCK_RIGETTI_QPU_1 - - creds = Mock() - creds.access_key = "access key" - creds.secret_key = "secret key" - creds.token = "token" - - different_region_aws_session = Mock() - different_region_aws_session.boto_session.get_credentials.return_value = creds - different_region_aws_session.boto_session.profile_name = "profile name" - different_region_aws_session.boto_session.region_name = "foobar" - - AwsQpu(arn, different_region_aws_session) - - # assert creds, and region were correctly supplied - boto_session_init.assert_called_with( - aws_access_key_id=creds.access_key, - aws_secret_access_key=creds.secret_key, - aws_session_token=creds.token, - region_name=region, - ) - - # assert supplied session, different_region_aws_session, was replaced - aws_session.get_qpu_metadata.assert_called_with(arn) - - -@patch("braket.aws.aws_qpu.AwsSession") -@patch("boto3.Session") -def test_no_aws_session_supplied(boto_session_init, aws_session_init, boto_session, aws_session): - arn = RIGETTI_ARN - region = AwsQpu.QPU_REGIONS.get(RIGETTI_REGION_KEY)[0] - - boto_session_init.return_value = boto_session - aws_session_init.return_value = aws_session - aws_session.get_qpu_metadata.return_value = MockDevices.MOCK_RIGETTI_QPU_1 - - AwsQpu(arn) - - boto_session_init.assert_called_with(region_name=region) - aws_session.get_qpu_metadata.assert_called_with(arn) - - -@pytest.mark.parametrize( - "qpu_arn, properties_keys, initial_qpu_data, modified_qpu_data", - [ - ( - RIGETTI_ARN, - ["gateModelProperties"], - MockDevices.MOCK_RIGETTI_QPU_1, - MockDevices.MOCK_RIGETTI_QPU_2, - ), - ( - DWAVE_ARN, - ["annealingModelProperties", "dWaveProperties"], - MockDevices.MOCK_DWAVE_QPU_1, - MockDevices.MOCK_DWAVE_QPU_2, - ), - ], -) -def test_qpu_refresh_metadata_success( - aws_session, qpu_arn, properties_keys, initial_qpu_data, modified_qpu_data -): - region_key = qpu_arn.split("/")[-2] - aws_session.boto_session.region_name = AwsQpu.QPU_REGIONS[region_key][0] - aws_session.get_qpu_metadata.return_value = initial_qpu_data - qpu = AwsQpu(qpu_arn, aws_session) - _assert_qpu_fields(qpu, properties_keys, initial_qpu_data) - - # describe_qpus now returns new metadata - aws_session.get_qpu_metadata.return_value = modified_qpu_data - qpu.refresh_metadata() - _assert_qpu_fields(qpu, properties_keys, modified_qpu_data) - - -def test_qpu_refresh_metadata_error(aws_session): - err_message = "nooo!" - aws_session.get_qpu_metadata.side_effect = RuntimeError(err_message) - with pytest.raises(RuntimeError) as excinfo: - AwsQpu(RIGETTI_ARN, aws_session) - assert err_message in str(excinfo.value) - - -def test_equality(qpu, aws_session): - qpu_1 = qpu(RIGETTI_ARN) - qpu_2 = qpu(RIGETTI_ARN) - aws_session.get_qpu_metadata.return_value = MockDevices.MOCK_IONQ_QPU - aws_session.boto_session.region_name = AwsQpu.QPU_REGIONS[IONQ_REGION_KEY][0] - other_qpu = AwsQpu(IONQ_ARN, aws_session) - non_qpu = "HI" - - assert qpu_1 == qpu_2 - assert qpu_1 is not qpu_2 - assert qpu_1 != other_qpu - assert qpu_1 != non_qpu - - -def test_repr(qpu): - qpu = qpu(RIGETTI_ARN) - expected = "QPU('name': {}, 'arn': {})".format(qpu.name, qpu.arn) - assert repr(qpu) == expected - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_no_extra(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): - _run_and_assert( - aws_quantum_task_mock, qpu, circuit, s3_destination_folder, - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_positional_args(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): - _run_and_assert( - aws_quantum_task_mock, qpu, circuit, s3_destination_folder, 100, 86400, 0.25, ["foo"] - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_kwargs(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): - _run_and_assert( - aws_quantum_task_mock, - qpu, - circuit, - s3_destination_folder, - extra_kwargs={"bar": 1, "baz": 2}, - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_shots(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): - _run_and_assert(aws_quantum_task_mock, qpu, circuit, s3_destination_folder, 100) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_shots_kwargs(aws_quantum_task_mock, qpu, circuit, s3_destination_folder): - _run_and_assert( - aws_quantum_task_mock, - qpu, - circuit, - s3_destination_folder, - 100, - extra_kwargs={"bar": 1, "baz": 2}, - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_shots_poll_timeout_kwargs( - aws_quantum_task_mock, qpu, circuit, s3_destination_folder -): - _run_and_assert( - aws_quantum_task_mock, - qpu, - circuit, - s3_destination_folder, - 100, - 86400, - extra_kwargs={"bar": 1, "baz": 2}, - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_positional_args_and_kwargs( - aws_quantum_task_mock, qpu, circuit, s3_destination_folder -): - _run_and_assert( - aws_quantum_task_mock, - qpu, - circuit, - s3_destination_folder, - 100, - 86400, - 0.25, - ["foo"], - {"bar": 1, "baz": 2}, - ) - - -@pytest.mark.parametrize( - "properties, expected_edges", - [ - ( - {"connectivity": {"connectivityGraph": {"0": ["1"], "1": ["2", "3"]}}}, - [(0, 1), (1, 2), (1, 3)], - ), - ( - {"connectivity": {"connectivityGraph": {}}, "qubitCount": "3"}, - list(nx.complete_graph(3).edges), - ), - ({"couplers": [[0, 1], [1, 2]],}, [(0, 1), (1, 2)]), - ({}, None), - ], -) -def test_construct_topology_graph(qpu, properties, expected_edges): - device = qpu(RIGETTI_ARN) - with patch("braket.aws.aws_qpu.AwsQpu.properties", properties): - if expected_edges is None: - assert device._construct_topology_graph() is None - else: - assert list(device._construct_topology_graph().edges) == expected_edges - - -def _run_and_assert( - aws_quantum_task_mock, - qpu_factory, - circuit, - s3_destination_folder, - shots=None, # Treated as positional arg - poll_timeout_seconds=None, # Treated as positional arg - poll_interval_seconds=None, # Treated as positional arg - extra_args=None, - extra_kwargs=None, -): - run_and_assert( - aws_quantum_task_mock, - qpu_factory(RIGETTI_ARN), - AwsQpu.DEFAULT_SHOTS_QPU, - AwsQpu.DEFAULT_RESULTS_POLL_TIMEOUT_QPU, - AwsQpu.DEFAULT_RESULTS_POLL_INTERVAL_QPU, - circuit, - s3_destination_folder, - shots, - poll_timeout_seconds, - poll_interval_seconds, - extra_args, - extra_kwargs, - ) - - -def _assert_qpu_fields(qpu, properties_keys, expected_qpu_data): - assert qpu.arn == expected_qpu_data.get("arn") - assert qpu.name == expected_qpu_data.get("name") - expected_qpu_properties = expected_qpu_data.get("properties") - for key in properties_keys: - expected_qpu_properties = expected_qpu_properties.get(key) - for property_name in expected_qpu_properties: - assert qpu.properties[property_name] == expected_qpu_properties.get(property_name) - assert qpu.status == expected_qpu_data.get("status") - assert qpu.status_reason == expected_qpu_data.get("statusReason") - assert qpu.topology_graph.edges == qpu._construct_topology_graph().edges diff --git a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py b/test/unit_tests/braket/aws/test_aws_quantum_simulator.py deleted file mode 100644 index a71dd67c..00000000 --- a/test/unit_tests/braket/aws/test_aws_quantum_simulator.py +++ /dev/null @@ -1,196 +0,0 @@ -# Copyright 2019-2019 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 Mock, patch - -import pytest -from common_test_utils import SIMULATOR_ARN, MockDevices, run_and_assert - -from braket.aws import AwsQuantumSimulator -from braket.circuits import Circuit - - -@pytest.fixture -def simulator(): - def _simulator(arn): - mock_session = Mock() - mock_session.get_simulator_metadata.return_value = MockDevices.MOCK_QS1_SIMULATOR_1 - return AwsQuantumSimulator(arn, mock_session) - - return _simulator - - -@pytest.fixture -def circuit(): - return Circuit().h(0) - - -@pytest.fixture -def s3_destination_folder(): - return ("bucket-foo", "key-bar") - - -def test_simulator_refresh_metadata_success(): - mock_session = Mock() - expected_metadata = MockDevices.MOCK_QS1_SIMULATOR_1 - mock_session.get_simulator_metadata.return_value = expected_metadata - simulator = AwsQuantumSimulator(SIMULATOR_ARN, mock_session) - assert simulator.arn == expected_metadata.get("arn") - assert simulator.name == expected_metadata.get("name") - assert simulator.properties["qubitCount"] == expected_metadata.get("qubitCount") - assert simulator.properties["supportedQuantumOperations"] == expected_metadata.get( - "supportedQuantumOperations" - ) - assert simulator.properties["supportedResultTypes"] == expected_metadata.get( - "supportedResultTypes" - ) - assert simulator.status == expected_metadata.get("status") - assert simulator.status_reason is None - - # describe_simulators now returns new metadata - expected_metadata = MockDevices.MOCK_QS1_SIMULATOR_2 - mock_session.get_simulator_metadata.return_value = expected_metadata - simulator.refresh_metadata() - assert simulator.arn == expected_metadata.get("arn") - assert simulator.name == expected_metadata.get("name") - assert simulator.properties["qubitCount"] == expected_metadata.get("qubitCount") - assert simulator.properties["supportedQuantumOperations"] == expected_metadata.get( - "supportedQuantumOperations" - ) - assert simulator.properties["supportedResultTypes"] == expected_metadata.get( - "supportedResultTypes" - ) - assert simulator.status == expected_metadata.get("status") - assert simulator.status_reason == expected_metadata.get("statusReason") - - -def test_simulator_refresh_metadata_error(): - mock_session = Mock() - err_message = "nooo!" - mock_session.get_simulator_metadata.side_effect = RuntimeError(err_message) - with pytest.raises(RuntimeError) as excinfo: - AwsQuantumSimulator(SIMULATOR_ARN, mock_session) - assert err_message in str(excinfo.value) - - -def test_equality(simulator): - simulator_1 = simulator(SIMULATOR_ARN) - simulator_2 = simulator(SIMULATOR_ARN) - other_simulator = Mock(spec=AwsQuantumSimulator) - other_simulator.arn.return_value = "OTHER_ARN" - non_simulator = "HI" - - assert simulator_1 == simulator_2 - assert simulator_1 is not simulator_2 - assert simulator_1 != other_simulator - assert simulator_1 != non_simulator - - -def test_repr(simulator): - simulator = simulator(SIMULATOR_ARN) - expected = "QuantumSimulator('name': {}, 'arn': {})".format(simulator.name, simulator.arn) - assert repr(simulator) == expected - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_positional_args(aws_quantum_task_mock, simulator, circuit, s3_destination_folder): - _run_and_assert( - aws_quantum_task_mock, simulator, circuit, s3_destination_folder, 1000, 300, 1, ["foo"] - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_kwargs(aws_quantum_task_mock, simulator, circuit, s3_destination_folder): - _run_and_assert( - aws_quantum_task_mock, - simulator, - circuit, - s3_destination_folder, - extra_kwargs={"bar": 1, "baz": 2}, - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_shots(aws_quantum_task_mock, simulator, circuit, s3_destination_folder): - _run_and_assert(aws_quantum_task_mock, simulator, circuit, s3_destination_folder, 1000) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_shots_kwargs(aws_quantum_task_mock, simulator, circuit, s3_destination_folder): - _run_and_assert( - aws_quantum_task_mock, - simulator, - circuit, - s3_destination_folder, - 1000, - extra_kwargs={"bar": 1, "baz": 2}, - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_shots_poll_timeout_kwargs( - aws_quantum_task_mock, simulator, circuit, s3_destination_folder -): - _run_and_assert( - aws_quantum_task_mock, - simulator, - circuit, - s3_destination_folder, - 1000, - 300, - extra_kwargs={"bar": 1, "baz": 2}, - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_positional_args_and_kwargs( - aws_quantum_task_mock, simulator, circuit, s3_destination_folder -): - _run_and_assert( - aws_quantum_task_mock, - simulator, - circuit, - s3_destination_folder, - 1000, - 300, - 1, - ["foo"], - {"bar": 1, "baz": 2}, - ) - - -def _run_and_assert( - aws_quantum_task_mock, - simulator_factory, - circuit, - s3_destination_folder, - shots=None, # Treated as positional arg - poll_timeout_seconds=None, # Treated as positional arg - poll_interval_seconds=None, # Treated as positional arg - extra_args=None, - extra_kwargs=None, -): - run_and_assert( - aws_quantum_task_mock, - simulator_factory(SIMULATOR_ARN), - AwsQuantumSimulator.DEFAULT_SHOTS_SIMULATOR, - AwsQuantumSimulator.DEFAULT_RESULTS_POLL_TIMEOUT_SIMULATOR, - AwsQuantumSimulator.DEFAULT_RESULTS_POLL_INTERVAL_SIMULATOR, - circuit, - s3_destination_folder, - shots, - poll_timeout_seconds, - poll_interval_seconds, - extra_args, - extra_kwargs, - ) diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index f62e93f1..8867e68d 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -16,7 +16,6 @@ import pytest from botocore.exceptions import ClientError -from common_test_utils import MockDevices from braket.aws import AwsSession @@ -92,44 +91,13 @@ def test_retrieve_s3_object_body_client_error(boto_session): aws_session.retrieve_s3_object_body(bucket_name, filename) -def test_get_qpu_metadata_success(boto_session): +def test_get_device(boto_session): braket_client = Mock() - braket_client.describe_qpus.return_value = {"qpus": [MockDevices.MOCK_RIGETTI_QPU_1]} + return_val = {"deviceArn": "arn1", "deviceName": "name1"} + braket_client.get_device.return_value = return_val aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) - qpu_metadata = aws_session.get_qpu_metadata("arn1") - assert qpu_metadata == MockDevices.MOCK_RIGETTI_QPU_1 - - -# TODO: revisit once we actually use boto3 -@pytest.mark.xfail(raises=ClientError) -def test_get_qpu_metadata_client_error(boto_session): - braket_client = Mock() - braket_client.describe_qpus.side_effect = ClientError( - {"Error": {"Code": "ValidationException", "Message": "NoSuchQpu"}}, "Operation" - ) - aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) - aws_session.get_qpu_metadata("arn1") - - -def test_get_simulator_metadata_success(boto_session): - braket_client = Mock() - braket_client.describe_quantum_simulators.return_value = { - "quantumSimulators": [MockDevices.MOCK_QS1_SIMULATOR_1] - } - aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) - simulator_metadata = aws_session.get_simulator_metadata("arn1") - assert simulator_metadata == MockDevices.MOCK_QS1_SIMULATOR_1 - - -# TODO: revisit once we actually use boto3 -@pytest.mark.xfail(raises=ClientError) -def test_get_simulator_metadata_client_error(boto_session): - braket_client = Mock() - braket_client.describe_quantum_simulators.side_effect = ClientError( - {"Error": {"Code": "ValidationException", "Message": "NoSuchSimulator"}}, "Operation" - ) - aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) - aws_session.get_simulator_metadata("arn1") + metadata = aws_session.get_device("arn1") + assert return_val == metadata def test_cancel_quantum_task(aws_session): diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 93d641f1..28c3753e 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -19,6 +19,7 @@ import braket.ir as ir from braket.annealing import Problem, ProblemType from braket.circuits import Circuit +from braket.device_schema import DeviceCapabilities from braket.devices import LocalSimulator, local_simulator from braket.simulator import BraketSimulator from braket.task_result import AnnealingTaskResult, GateModelTaskResult @@ -88,8 +89,28 @@ def run( return GATE_MODEL_RESULT @property - def properties(self) -> Dict[str, Any]: - return {"supportedIrTypes": ["jaqcd"], "supportedQuantumOperations": ["I", "X"]} + 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"], + } + }, + "deviceParameters": {}, + } + ) def assert_shots(self, shots): assert self._shots == shots @@ -103,8 +124,28 @@ def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> AnnealingTaskRe return ANNEALING_RESULT @property - def properties(self) -> Dict[str, Any]: - return {"supportedIrTypes": ["annealing"]} + 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"], + } + }, + "deviceParameters": {}, + } + ) mock_entry = Mock() @@ -173,9 +214,7 @@ def test_run_qubit_gate_unsupported(): def test_properties(): - sim = LocalSimulator(DummyCircuitSimulator()) - expected_properties = { - "supportedIrTypes": ["jaqcd"], - "supportedQuantumOperations": ["I", "X"], - } + dummy = DummyCircuitSimulator() + sim = LocalSimulator(dummy) + expected_properties = dummy.properties assert sim.properties == expected_properties From e2851724270ede8c84e0bd9f1d321a8be09f375e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Derek=20Bolt=F0=9F=90=BE?= Date: Sat, 8 Aug 2020 18:50:13 -0700 Subject: [PATCH 0158/1165] feature: Updated AwsQuantumTask to use new device parameters --- src/braket/aws/aws_device.py | 4 +- src/braket/aws/aws_quantum_task.py | 40 ++++++++++----- test/integ_tests/conftest.py | 2 +- .../braket/aws/test_aws_quantum_task.py | 51 ++++++++++++------- 4 files changed, 66 insertions(+), 31 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 875b5fd5..96023924 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -125,7 +125,9 @@ def run( >>> ) >>> device = AwsDevice("arn3") >>> device.run(problem, ("bucket-foo", "key-bar"), - >>> device_parameters = {"dWaveParameters": {"postprocessingType": "SAMPLING"}}) + >>> device_parameters={ + >>> "providerLevelParameters": {"postprocessingType": "SAMPLING"}} + >>> ) See Also: `braket.aws.aws_quantum_task.AwsQuantumTask.create()` diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index f7fe1432..520c56dc 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -14,7 +14,6 @@ from __future__ import annotations import asyncio -import json import time from functools import singledispatch from logging import Logger, getLogger @@ -26,6 +25,11 @@ from braket.aws.aws_session import AwsSession from braket.circuits.circuit import Circuit from braket.circuits.circuit_helpers import validate_circuit_and_shots +from braket.device_schema import GateModelParameters +from braket.device_schema.dwave import DwaveDeviceParameters +from braket.device_schema.ionq import IonqDeviceParameters +from braket.device_schema.rigetti import RigettiDeviceParameters +from braket.device_schema.simulators import GateModelSimulatorDeviceParameters from braket.schema_common import BraketSchemaBase from braket.task_result import AnnealingTaskResult, GateModelTaskResult from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult, QuantumTask @@ -80,7 +84,7 @@ def create( device_parameters (Dict[str, Any]): Additional parameters to send to the device. For example, for D-Wave: - `{"dWaveParameters": {"postprocessingType": "OPTIMIZATION"}}` + `{"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}}` Returns: AwsQuantumTask: AwsQuantumTask tracking the task execution on the device. @@ -110,6 +114,7 @@ def create( aws_session, create_task_kwargs, device_parameters or {}, + device_arn, *args, **kwargs, ) @@ -346,7 +351,8 @@ def _create_internal( task_specification: Union[Circuit, Problem], aws_session: AwsSession, create_task_kwargs: Dict[str, Any], - device_parameters: Dict[str, Any], + device_parameters: Union[dict, BraketSchemaBase], + device_arn: str, *args, **kwargs, ) -> AwsQuantumTask: @@ -358,18 +364,27 @@ def _( circuit: Circuit, aws_session: AwsSession, create_task_kwargs: Dict[str, Any], - device_parameters: Dict[str, Any], + device_parameters: Union[dict, BraketSchemaBase], + device_arn: str, *args, **kwargs, ) -> AwsQuantumTask: validate_circuit_and_shots(circuit, create_task_kwargs["shots"]) + + # TODO: Update this to use `deviceCapabilities` from Amazon Braket's GetDevice operation + # in order to decide what parameters to build. + paradigm_parameters = GateModelParameters(qubitCount=circuit.qubit_count) + if "ionq" in device_arn: + device_parameters = IonqDeviceParameters(paradigmParameters=paradigm_parameters) + elif "rigetti" in device_arn: + device_parameters = RigettiDeviceParameters(paradigmParameters=paradigm_parameters) + else: # default to use simulator + device_parameters = GateModelSimulatorDeviceParameters( + paradigmParameters=paradigm_parameters + ) + create_task_kwargs.update( - { - "action": circuit.to_ir().json(), - "deviceParameters": json.dumps( - {"gateModelParameters": {"qubitCount": circuit.qubit_count}} - ), - } + {"action": circuit.to_ir().json(), "deviceParameters": device_parameters.json()} ) task_arn = aws_session.create_quantum_task(**create_task_kwargs) return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) @@ -380,14 +395,15 @@ def _( problem: Problem, aws_session: AwsSession, create_task_kwargs: Dict[str, Any], - device_parameters: Dict[str, Any], + device_parameters: Union[dict, DwaveDeviceParameters], + device_arn: str, *args, **kwargs, ) -> AwsQuantumTask: create_task_kwargs.update( { "action": problem.to_ir().json(), - "deviceParameters": json.dumps({"annealingModelParameters": device_parameters}), + "deviceParameters": DwaveDeviceParameters.parse_obj(device_parameters).json(), } ) diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py index 7c704ffe..50618397 100644 --- a/test/integ_tests/conftest.py +++ b/test/integ_tests/conftest.py @@ -42,7 +42,7 @@ def s3_bucket(s3_resource, boto_session): region_name = boto_session.region_name account_id = boto_session.client("sts").get_caller_identity()["Account"] - bucket_name = f"braket-sdk-integ-tests-{account_id}" + bucket_name = f"amazon-braket-sdk-integ-tests-{account_id}" bucket = s3_resource.Bucket(bucket_name) try: 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 53a7441f..c11a867c 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -12,7 +12,6 @@ # language governing permissions and limitations under the License. import asyncio -import json import threading import time from unittest.mock import MagicMock, Mock, patch @@ -24,6 +23,11 @@ from braket.aws import AwsQuantumTask from braket.aws.aws_session import AwsSession from braket.circuits import Circuit +from braket.device_schema import GateModelParameters +from braket.device_schema.dwave import DwaveDeviceParameters +from braket.device_schema.ionq import IonqDeviceParameters +from braket.device_schema.rigetti import RigettiDeviceParameters +from braket.device_schema.simulators import GateModelSimulatorDeviceParameters from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult S3_TARGET = AwsSession.S3DestinationFolder("foo", "bar") @@ -240,7 +244,7 @@ def test_timeout_completed(aws_session): # Setup the poll timing such that the timeout will occur after one API poll quantum_task = AwsQuantumTask( - "foo:bar:arn", aws_session, poll_timeout_seconds=0.5, poll_interval_seconds=1, + "foo:bar:arn", aws_session, poll_timeout_seconds=0.5, poll_interval_seconds=1 ) assert quantum_task.result() is None _mock_metadata(aws_session, "COMPLETED") @@ -256,7 +260,7 @@ def test_timeout_no_result_terminal_state(aws_session): # Setup the poll timing such that the timeout will occur after one API poll quantum_task = AwsQuantumTask( - "foo:bar:arn", aws_session, poll_timeout_seconds=0.5, poll_interval_seconds=1, + "foo:bar:arn", aws_session, poll_timeout_seconds=0.5, poll_interval_seconds=1 ) assert quantum_task.result() is None @@ -276,23 +280,33 @@ def test_create_invalid_task_specification(aws_session, arn): AwsQuantumTask.create(aws_session, arn, "foo", S3_TARGET, 1000) -def test_from_circuit_with_shots(aws_session, arn, circuit): +@pytest.mark.parametrize( + "device_arn,device_parameters_class", + [ + ("device/qpu/ionq", IonqDeviceParameters), + ("device/qpu/rigetti", RigettiDeviceParameters), + ("device/quantum-simulator", GateModelSimulatorDeviceParameters), + ], +) +def test_from_circuit_with_shots(device_arn, device_parameters_class, aws_session, circuit): mocked_task_arn = "task-arn-1" aws_session.create_quantum_task.return_value = mocked_task_arn shots = 53 - task = AwsQuantumTask.create(aws_session, arn, circuit, S3_TARGET, shots) + task = AwsQuantumTask.create(aws_session, device_arn, circuit, S3_TARGET, shots) assert task == AwsQuantumTask( mocked_task_arn, aws_session, GateModelQuantumTaskResult.from_string ) _assert_create_quantum_task_called_with( aws_session, - arn, + device_arn, circuit, S3_TARGET, shots, - {"gateModelParameters": {"qubitCount": circuit.qubit_count}}, + device_parameters_class( + paradigmParameters=GateModelParameters(qubitCount=circuit.qubit_count) + ), ) @@ -303,17 +317,20 @@ def test_from_circuit_with_shots_value_error(aws_session, arn, circuit): AwsQuantumTask.create(aws_session, arn, circuit, S3_TARGET, 0) -def test_from_annealing(aws_session, arn, problem): +@pytest.mark.parametrize( + "device_parameters", + [ + {"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}}, + DwaveDeviceParameters.parse_obj( + {"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}} + ), + ], +) +def test_from_annealing(device_parameters, aws_session, arn, problem): mocked_task_arn = "task-arn-1" aws_session.create_quantum_task.return_value = mocked_task_arn - task = AwsQuantumTask.create( - aws_session, - arn, - problem, - S3_TARGET, - 1000, - device_parameters={"dWaveParameters": {"postprocessingType": "OPTIMIZATION"}}, + aws_session, arn, problem, S3_TARGET, 1000, device_parameters=device_parameters ) assert task == AwsQuantumTask( mocked_task_arn, aws_session, AnnealingQuantumTaskResult.from_string @@ -325,7 +342,7 @@ def test_from_annealing(aws_session, arn, problem): problem, S3_TARGET, 1000, - {"annealingModelParameters": {"dWaveParameters": {"postprocessingType": "OPTIMIZATION"}}}, + DwaveDeviceParameters.parse_obj(device_parameters), ) @@ -361,7 +378,7 @@ def _assert_create_quantum_task_called_with( "outputS3Bucket": s3_results_prefix[0], "outputS3KeyPrefix": s3_results_prefix[1], "action": task_description.to_ir().json(), - "deviceParameters": json.dumps(device_parameters), + "deviceParameters": device_parameters.json(), "shots": shots, } ) From 4a3c1399188de9a7cef576a6798928cd898a2366 Mon Sep 17 00:00:00 2001 From: Lin Date: Mon, 10 Aug 2020 15:51:59 -0700 Subject: [PATCH 0159/1165] change: Set default for shots argument in AwsDevice.run to None instead of -1. --- src/braket/aws/aws_device.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 96023924..c23da3b4 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. from enum import Enum -from typing import Union +from typing import Optional, Union import boto3 from networkx import Graph, complete_graph, from_edgelist @@ -47,7 +47,6 @@ class AwsDevice(Device): "d-wave": ["us-west-2"], } - _DUMMY_SHOTS = -1 DEFAULT_SHOTS_QPU = 1000 DEFAULT_SHOTS_SIMULATOR = 0 DEFAULT_RESULTS_POLL_TIMEOUT = 432000 @@ -80,7 +79,7 @@ def run( self, task_specification: Union[Circuit, Problem], s3_destination_folder: AwsSession.S3DestinationFolder, - shots: int = _DUMMY_SHOTS, + shots: Optional[int] = None, poll_timeout_seconds: int = DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: int = DEFAULT_RESULTS_POLL_INTERVAL, *aws_quantum_task_args, @@ -132,7 +131,7 @@ def run( See Also: `braket.aws.aws_quantum_task.AwsQuantumTask.create()` """ - if shots == AwsDevice._DUMMY_SHOTS: + if shots is None: if "qpu" in self.arn: shots = AwsDevice.DEFAULT_SHOTS_QPU else: From 53ba422b86ed76cd913e0e7d952bf14a768be7dc Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Tue, 11 Aug 2020 12:12:25 -0700 Subject: [PATCH 0160/1165] change: remove extraneous TODOs (#131) --- src/braket/circuits/circuit.py | 2 -- src/braket/circuits/instruction.py | 3 --- src/braket/tasks/__init__.py | 4 +--- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 6705bdfb..a9844b22 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -31,8 +31,6 @@ SubroutineCallable = TypeVar("SubroutineCallable", bound=Callable[..., SubroutineReturn]) AddableTypes = TypeVar("AddableTypes", SubroutineReturn, SubroutineCallable) -# TODO: Add parameterization - class Circuit: """ diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index b1db6400..36c28153 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -19,9 +19,6 @@ from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput -# TODO: Add parameters support -# TODO: Rename to QuantumInstruction, and change Operator to Gate, then rename "target" to "qubits" - # InstructionOperator is a type alias, and it can be expanded to include other operators InstructionOperator = Gate diff --git a/src/braket/tasks/__init__.py b/src/braket/tasks/__init__.py index 0fa1eb25..99dc33f9 100644 --- a/src/braket/tasks/__init__.py +++ b/src/braket/tasks/__init__.py @@ -12,9 +12,7 @@ # language governing permissions and limitations under the License. import braket.ipython_utils as ipython_utils -from braket.tasks.annealing_quantum_task_result import ( # noqa: F401 # TODO: remove - AnnealingQuantumTaskResult, -) +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.quantum_task import QuantumTask # noqa: F401 From 9fba841436b56b94d32b537c44adebbdb735ae82 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 12 Aug 2020 12:21:52 -0700 Subject: [PATCH 0161/1165] fix: multi-target observable target bug (#132) Fixes a bug where a circuit is still valid if two multi-target observables are applied to the same set of target qubits, but the qubits are in different orders; these observables should conflict with one another. --- src/braket/circuits/circuit.py | 11 +++++++++++ test/unit_tests/braket/circuits/test_circuit.py | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index a9844b22..e507ebcd 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -110,6 +110,7 @@ def __init__(self, addable: AddableTypes = None, *args, **kwargs): self._moments: Moments = Moments() self._result_types: List[ResultType] = [] self._qubit_observable_mapping: Dict[Union[int, Circuit._ALL_QUBITS], Observable] = {} + self._qubit_target_mapping: Dict[int, List[int]] = {} if addable is not None: self.add(addable, *args, **kwargs) @@ -274,13 +275,23 @@ def _add_to_qubit_observable_mapping(self, result_type: ResultType) -> None: for target in targets: current_observable = all_qubits_observable or self._qubit_observable_mapping.get(target) + current_target = self._qubit_target_mapping.get(target) if current_observable and current_observable != observable: raise ValueError( f"Existing result type for observable {current_observable} for target {target}" f" conflicts with observable {observable} for new result type" ) + if result_type.target: + # The only way this can happen is if the observables (acting on multiple target + # qubits) and target qubits are the same, but the new target is the wrong order; + if current_target and current_target != targets: + raise ValueError( + f"Target order {current_target} of existing result type with observable" + f" {current_observable} conflicts with order {targets} of new result type" + ) self._qubit_observable_mapping[target] = observable + self._qubit_target_mapping[target] = targets if not result_type.target: self._qubit_observable_mapping[Circuit._ALL_QUBITS] = observable diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 23974e54..8952bfae 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -193,6 +193,15 @@ def test_add_result_type_observable_no_conflict_state_vector_obs_return_value(): assert circ.result_types == expected +@pytest.mark.xfail(raises=ValueError) +def test_add_result_type_same_observable_wrong_target_order(): + Circuit().add_result_type( + ResultType.Expectation(observable=Observable.Y() @ Observable.X(), target=[0, 1]) + ).add_result_type( + ResultType.Variance(observable=Observable.Y() @ Observable.X(), target=[1, 0]) + ) + + @pytest.mark.xfail(raises=TypeError) def test_add_result_type_with_target_and_mapping(prob): Circuit().add_result_type(prob, target=[10], target_mapping={0: 10}) From c13343e0f5b6c7c696c6506f48da346c2d6d2fc6 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Wed, 12 Aug 2020 12:25:20 -0700 Subject: [PATCH 0162/1165] documentation: updating README to remove private beta instructions (#130) * Version bump * documentation: examples updates --- .github/pull_request_template.md | 2 +- CONTRIBUTING.md | 2 +- README.md | 302 +++++-------------------------- doc/index.rst | 4 +- examples/bell.py | 2 +- examples/debug_bell.py | 2 +- src/braket/_sdk/_version.py | 2 +- 7 files changed, 50 insertions(+), 266 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d3be7edd..819c4301 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -12,7 +12,7 @@ _Put an `x` in the boxes that apply. You can also fill these out after creating - [ ] I have read the [CONTRIBUTING](https://github.com/aws/amazon-braket-sdk-python/blob/main/CONTRIBUTING.md) doc - [ ] I used the commit message format described in [CONTRIBUTING](https://github.com/aws/amazon-braket-sdk-python/blob/main/CONTRIBUTING.md#commit-your-change) -- [ ] I have updated any necessary documentation, including [READMEs](https://github.com/aws/amazon-braket-sdk-python/blob/main/README.md) and [API docs](https://github.com/aws/amazon-braket-sdk-python#braket-python-sdk-api-reference-documentation) (if appropriate) +- [ ] I have updated any necessary documentation, including [READMEs](https://github.com/aws/amazon-braket-sdk-python/blob/main/README.md) and [API docs](https://github.com/aws/amazon-braket-sdk-python/blob/main/CONTRIBUTING.md#documentation-guidelines) (if appropriate) #### Tests diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fcbee924..88373f01 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -123,7 +123,7 @@ For the rest of the message, use imperative style and keep things concise but in ### Send a Pull Request -GitHub provides additional document on [Creating a Pull Request](https://help.github.com/articles/creating-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). diff --git a/README.md b/README.md index 30465771..1164c68d 100644 --- a/README.md +++ b/README.md @@ -1,175 +1,58 @@ -**This prerelease documentation is confidential and is provided under the terms of your nondisclosure agreement with Amazon Web Services (AWS) or other agreement governing your receipt of AWS confidential information.** +## Amazon Braket Python SDK -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. This document describes how to configure your environment to use the Amazon Braket (Private Beta) locally on your computer. It does not include information about how to use Amazon Braket (Private Beta) in the AWS console. +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. -**Getting the latest version** - -Get the latest version of the SDK. If you receive a notice that a new version of the SDK is available, you can update to the latest version. For more information, see [Updating to the latest release](https://github.com/aws/amazon-braket-sdk-python/tree/main#updating-to-the-latest-release). View the [Releases](https://github.com/aws/amazon-braket-sdk-python/releases) page for more information. - -**Providing Feedback and Getting Help** - -To provide feedback or request support, please contact the Amazon Braket team at [amazon-braket-preview-support@amazon.com](mailto:amazon-braket-preview-support@amazon.com?subject=Add%20a%20brief%20description%20of%20the%20issue). - -**Important** - -If you **Star**, **Watch**, or submit a pull request for this repository, other users that have access to this repository are able to see your user name in the list of watchers. If you want to remain anonymous, you should not Watch or Star this repository, nor post any comments or submit a pull request. ## Prerequisites Before you begin working with the Amazon Braket SDK, make sure that you've installed or configured the following prerequisites. ### Python 3.7.2 or greater Download and install Python 3.7.2 or greater from [Python.org](https://www.python.org/downloads/). -If you are using Windows, choose **Add Python to environment variables** before you begin the installation. - -### Using a Virtual Environment -If you want to use a virtual environment for interacting with the Amazon Braket SDK, first install virtualenv with the following command: -```bash -pip install virtualenv -``` - -To learn more, see [virtualenv](https://virtualenv.pypa.io/en/stable/installation/). - -On Windows, you should open a new terminal window to install `virtualenv`. If you use a terminal window that was open before you installed Python, the terminal window does not use the Python environment variables needed. - -After you install `virtualenv`, use the following command to create a virtual environment. When you run this command, it creates a folder named `braketvirtenv`, and uses that folder as the root folder of the virtual environment. You should run the command from a location where you want to create the virtual environment. - -**To create a virtual environment** -```bash -virtualenv braketvirtenv -``` - -Then use one of the following options to activate the virtual environment. - -**To activate the virtual environment on Mac and Linux**: -```bash -source braketvirtenv/bin/activate -``` - -**To activate the virtual environment on Windows** -```bash -cd braketvirtenv\scripts -``` - -```bash -activate -``` - -Then run this command to return the cursor to the parent folder -```bash -cd .. -``` ### Git Install Git from https://git-scm.com/downloads. Installation instructions are provided on the download page. -### Access to Amazon Braket (Private Beta) -You can configure your environment for the Amazon Braket Python SDK, but you need to be granted permission to access Amazon Braket (Private Beta) before you can start making code requests to Amazon Braket. If you’ve not received notification that you have been added to the beta, please contact the Amazon Braket team for assistance using this email address: [amazon-braket-preview-support@amazon.com](mailto:amazon-braket-preview-support@amazon.com?subject=Add%20a%20brief%20description%20of%20the%20issue). - ### IAM user or role with required permissions -To perform the steps in this document and to interact with Amazon Braket, use an account with administrator privileges, such as one with the AdministratorAccess policy applied. To learn more about IAM user, roles, and policies, see [Adding and Removing IAM Identity Permissions](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage-attach-detach.html). - -### AWS CLI +As a managed service, Amazon Braket performs operations on your behalf on the AWS hardware that is managed by Amazon Braket. Amazon Braket can perform only operations that the user permits. You can read more about which permissions are necessary in the AWS Documentation. -#### Install and configure the AWS Command Line Interface (CLI) -Install the [AWS CLI](https://github.com/aws/aws-cli#installation) so that you can interact with AWS via the command line. This is required to perform some steps in this document. Instructions to install and configure the CLI are included on the installation page. +The Braket Python SDK should not require any additional permissions aside from what is required for using Braket. However, if you are using an IAM role with a path in it, you should grant permission for iam:GetRole. -#### Use the following command to install the AWS CLI -```bash -pip install awscli -``` +To learn more about IAM user, roles, and policies, see [Adding and Removing IAM Identity Permissions](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage-attach-detach.html). -#### Configure a profile for the AWS CLI -Configure a CLI 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). - -After you create a profile, use the following command to set the `AWS_PROFILE` so that all future commands can access your AWS account and resources. +### Boto3 and setting up AWS credentials -```bash -export AWS_PROFILE=YOUR_PROFILE_NAME -``` +Follow the installation [instructions](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html) for Boto3 and setting up AWS credentials. ### Configure your AWS account with the resources necessary for Amazon Braket -Use the following link to an AWS CloudFormation template to automatically create the resources that Amazon Braket uses or interacts with in your account. - -**Important** - -If you are using IAM roles for multiple users in your organization to access the Amazon Braket Private Beta from the same account, only open the template and create the stack once for the account. Each role in the account then has access to the resources Amazon Braket needs in the account. - -The template creates the following resources: -- An S3 bucket to store job output. The bucket is named `braket-output-AWSaccountId` where AWSAccountId is your account ID. For example, if your AWS account ID is 123456789012, the bucket is named `braket-output-123456789012`. -- IAM roles named AmazonBraketJobExecutionRole, which is used to run jobs, and AQxFullAccess which is used to interact with the AWS resources that Amazon Braket needs. -- An IAM policy, AmazonBraketFullAccess, that includes permission to use Amazon Braket actions, as well as the permissions necessary to access the S3 bucket created. If you want to use a role that does not have admin permissions, you can apply the AmazonBraketFullAccess policy to the user or role you are using to grant the permissions required to use Amazon Braket beta. - -If you are already logged in to AWS when you click the link to open the template, the resources are created in the current account. If you want to use Amazon Braket beta in a different account, first log in using that account. - -[Amazon Braket CloudFormation Stack Creation template](https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https://braket-external-assets-prod-us-west-2.s3-us-west-2.amazonaws.com/templates/braket-resources.yaml&stackName=BraketResources) - -When the template loads, select the **I acknowledge that AWS CloudFormation might create IAM resources with custom names** checkbox, and then choose **Create Stack**. - -Wait until the Status changes to **CREATE_COMPLETE**. You may need to refresh the page to see the current status of the stack creation. - -## Setting up the Amazon Braket Python SDKs -Use the steps in this section to install and configure the Amazon Braket Python SDKs for your environment. You should perform the steps in the order in which they are included in this document. +If you are new to Amazon Braket, onboard to the service and create the resources necessary to use Amazon Braket using the [AWS console](https://aws.amazon.com/braket/). -### Download the Amazon Braket GitHub Repositories -The easiest way to get the SDKs is to download them directly from the GitHub site. Because the repositories are private during the Private Beta period, an SSH key is required to access the files remotely from a terminal session. If you download them directly from the GitHub site, you can just extract the files to your system or virtual environment without the extra steps of using an SSH key. You need to log in to GitHub using the account that was whitelisted for the Amazon Braket (Private Beta). +## Installing the Amazon Braket Python SDK +Use the steps in this section to install and configure the Amazon Braket Python SDK for your environment. -Use the following links to download the Amazon Braket Python SDK repos: -- [amazon-braket-schemas-python](https://github.com/aws/amazon-braket-schemas-python/archive/main.zip) -- [amazon-braket-default-simulator-python](https://github.com/aws/amazon-braket-default-simulator-python/archive/main.zip) -- [amazon-braket-sdk-python](https://github.com/aws/amazon-braket-sdk-python/archive/main.zip) - -### Extract the SDK .zip files -Because the files were downloaded directly from GitHub, the folder in the .zip file includes the name of the branch of the GitHub repo that was downloaded, in this case the `main` branch. But to use the files in the SDK, we need to rename the folder to the original name. - -**To rename the folders in the SDK .zip files** -First, extract the .zip files to a location of your choosing. Then open the location where you extracted the folders to. You can use either the GUI file system tools in your OS, or the command line. You should see 3 folders with the following names: -- amazon-braket-schemas-python-main -- amazon-braket-default-simulator-python-main -- amazon-braket-sdk-python-main - -Rename the folders to the following: -- amazon-braket-schemas-python -- amazon-braket-default-simulator-python -- amazon-braket-sdk-python - -Then copy the renamed files and paste them into the `braketvirtenv` folder where you created a virtual environment. Your folder structure should look like this: -```bash -..\YourFolder\braketvirtenv\amazon-braket-sdk-python\ -``` - -### Install the SDK packages -Use the following commands to install the SDKs in the order that they appear: +You can install from source by cloning this repository and running a pip install command in the root directory of the repository: ```bash -pip install -e amazon-braket-schemas-python -``` - -```bash -pip install -e amazon-braket-default-simulator-python +git clone https://github.com/aws/amazon-braket-sdk-python.git +cd amazon-braket-sdk-python +pip install . ``` +### Check the version you have installed +You can view the version of the amazon-braket-sdk you have installed by using the following command: ```bash -pip install -e amazon-braket-sdk-python +pip show amazon-braket-sdk ``` -### Install latest Amazon Braket model in AWS CLI -Run the following commands to install the Braket model. The file is downloaded to the current location. Position the cursor in the folder where you want to download the file before running the command. +You can also check your version of `amazon-braket-sdk` from within Python: -```bash -aws s3 cp s3://braket-external-assets-prod-us-west-2/models/braket-2019-09-01.normal.json braket-model.json ``` -```bash -aws configure add-model --service-model "file://braket-model.json" --service-name braket +>>> import braket._sdk as braket_sdk +>>> braket_sdk.__version__ ``` -## Validate the Configuration -Your environment should now be configured to successfully use the Braket Python SDK to interact with Amazon Braket. You can use the following example to confirm that your settings are correct. -You can confirm that your environment is correctly configured in either of the following ways: -- Create a Python file -- Use a Jupyter notebook +## Usage -## Code sample for validating your configuration -Use the following code sample to validate your environment configuration. +### Running a circuit on an AWS simulator ```python import boto3 @@ -179,72 +62,28 @@ from braket.circuits import Circuit aws_account_id = boto3.client("sts").get_caller_identity()["Account"] device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") -s3_folder = (f"braket-output-{aws_account_id}", "folder-name") +s3_folder = (f"amazon-braket-output-{aws_account_id}", "folder-name") bell = Circuit().h(0).cnot(0, 1) task = device.run(bell, s3_folder, shots=100) print(task.result().measurement_counts) ``` -The code sample imports the Amazon Braket framework, then defines the execution environment as the AWSQuantumSimulator and the device to use. The `s3_folder` statement defines the Amazon S3 bucket for job output and the folder in the bucket to store job output. This folder is created when you run the job. It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the job. +The code sample imports the Amazon Braket framework, then defines the device to use (the SV1 AWS simulator). The `s3_folder` statement defines the Amazon S3 bucket for the task result and the folder in the bucket to store the task result. This folder is created when you run the task. It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the job. This example can be found in `../examples/bell.py`. ### Available Simulators There is currently one AWS simulator available: - `arn:aws:braket:::device/quantum-simulator/amazon/sv1` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the state vector and outputs an array of bit strings that appears as though it came from a quantum computer. Does not provide a state vector. -#### To validate your configuration using a Python file -1. Open a text editor with example file `../amazon-braket-sdk-python/examples/bell.py`. -1. If desired, modify `folder-name` to the name of the folder to create/use for results in following line: - `s3_folder = (f"braket-output-{aws_account_id}", "folder-name")`. Save the file. -1. Make sure the virtualenv (`braketvirtenv`) is activated, and then position the cursor in the `/examples` folder of the repo. Assuming you created a virtual environment on your `C:` drive in a folder named `braket`, the cursor should be at the following location: -`c:\braket\braketvirtenv\amazon-braket-sdk-python\examples\`. -1. Then use the following command to run the sample: - - ```bash - python bell.py - ``` - -You should see a result similar to the following: -```Counter({'11': 52, '00': 48})``` - -#### To validate your configuration using a Jupyter notebook -See [Installing the Jupyter Software](https://jupyter.org/install) for information about how to install Jupyter. You can use either JupyterLab or classic Jupyter Notebook. - -Run the following commands to install Jupyter and then create a Braket kernel. -```bash -pip install jupyter ipykernel -``` - -```bash -python -m ipykernel install --user --name braket -``` - -After you have installed Jupyter, use this command to open a Jupyter notebook so you can use the SDK within the notebook: -```bash -jupyter notebook -``` -Jupyter opens in a browser window. Choose **New**, and then under **Notebooks**, choose **braket**. - -**Note** If you are using a Jupyter notebook from an prior installation and did not create a Braket kernel, you will not see braket available for the notebook type. Choose Python3 instead. If you choose Python3, you must have the Braket packages installed globally. +### Running a circuit locally -Copy the code sample (above) into the notebook. If you want to use a different folder in the bucket, change `folder-name` to the name of the folder to create. If the folder already exists it uses the existing folder. +The Amazon Braket Python SDK comes bundled with an implementation of a quantum simulator that you can run locally. You can use the local simulator to test quantum tasks constructed using the SDK before you submit them to the Amazon Braket service for execution. An example of how to execute the task locally is included in the repo `../examples/local_bell.py`. -Choose **Run** to execute the code to confirm that your environment is configured correctly. +### Debugging logs -When the job completes, you should see output similar to the following: -`Counter({'00': 52, '11': 48})` +Tasks sent to QPUs don't always run right away. To view task status, you can enable debugging logs. An example of how to enable these logs is included in repo: `../examples/debug_bell.py`. This example enables task logging so that status updates are continuously printed to the terminal after a quantum task is executed. The logs can also be configured to save to a file or output to another stream. You can use the debugging example to get information on the tasks you submit, such as the current status, so that you know when your task completes. -**Important** Tasks may not run immediately on the QPU. IonQ runs tasks once every 24 hours. Rigetti tasks run when the QPU is available, with times varying day to day. - -#### To validate your quantum algorithm locally - -Braket Python SDK comes bundled with an implementation of a quantum simulator that you can run locally. You can use the local simulator to test quantum tasks constructed using the SDK before you submit them to the Amazon Braket service for execution. An example of how to execute the task locally is included in the repo `../examples/local_bell.py`. - -#### Debugging logs - -Tasks sent to QPUs don't always run right away. For IonQ, jobs are run once every 24 hours. For Rigetti, tasks are queued and run when the QPU is available, with the time varying day to day. To view task status, you can enable debugging logs. An example of how to enable these logs is included in repo: `../examples/debug_bell.py`. This example enables task logging so that status updates are continuously printed to console after a quantum task is executed. The logs can also be configured to save to a file or output to another stream. You can use the debugging example to get information on the tasks you submit, such as the current status, so that you know when your task completes. - -## Running a Quantum Algorithm on a Quantum Computer +### Running a Quantum Algorithm on a Quantum Computer With Amazon Braket, you can run your quantum circuit on a physical quantum computer. The following example executes the same Bell Pair example described to validate your configuration on a Rigetti quantum computer. @@ -257,14 +96,14 @@ from braket.aws import AwsDevice aws_account_id = boto3.client("sts").get_caller_identity()["Account"] device = AwsDevice("arn:aws:braket:::device/qpu/rigetti/Aspen-8") -s3_folder = (f"braket-output-{aws_account_id}", "RIGETTI") +s3_folder = (f"amazon-braket-output-{aws_account_id}", "RIGETTI") bell = Circuit().h(0).cnot(0, 1) task = device.run(bell, s3_folder) 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` (or `AwsQuantumTask`), 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()`. +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, s3_folder, poll_timeout_seconds=86400) # 1 day @@ -272,85 +111,26 @@ print(task.result().measurement_counts) ``` Specify which quantum computer hardware to use by changing the value of the `device_arn` to the value for quantum computer to use: -- **IonQ** "arn:aws:braket:::device/qpu/ionq/ionQdevice" (Available 9:00 AM to 1:00 PM ET M-F) -- **Rigetti** "arn:aws:braket:::device/qpu/rigetti/Aspen-8" (Available 11:00 AM to 1:00 PM ET daily) -- **D-Wave** "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6" (Available 24/7. See the next section in this document for more information about using D-Wave.) +- **IonQ** "arn:aws:braket:::device/qpu/ionq/ionQdevice" +- **Rigetti** "arn:aws:braket:::device/qpu/rigetti/Aspen-8" +- **D-Wave** "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6" (See the next section in this document for more information about using D-Wave.) + +**Important** Tasks may not run immediately on the QPU. The QPUs only execute tasks during execution windows. To find their execution windows, please refer to the [AWS console](https://aws.amazon.com/braket/) in the "Devices" tab. ### Using Amazon Braket with D-Wave QPU If you want to use [Ocean](https://docs.ocean.dwavesys.com/en/latest/) with the D-Wave QPU, you can install the [braket-ocean-python-plugin](https://github.com/aws/braket-ocean-python-plugin). Information about how to install the plugin is provided in the [README](https://github.com/aws/braket-ocean-python-plugin/blob/master/README.md) for the repo. -### Using Amazon Braket with PennyLane -To use Amazon Braket with Xanadu [PennyLane](https://pennylane.ai/), install the [Amazon Braket plugin for PennyLane](https://github.com/aws/amazon-braket-pennylane-plugin-python). -Instructions for setting up and using the plugin are provided in the README in the repository. - -### Deactivate the virtual environment -After you are finished using the virtual environment to interact with Amazon Braket, you can deactivate it using the following command. - -**To deactivate the virtual environment on Mac or Linux** -```bash -source braketvirtenv/bin/deactivate -``` - -**To deactivate the virtual environment on Windows** -```bash -cd braketvirtenv\scripts -``` - -```bash -deactivate -``` - -When you want to use it again, you can reactivate it with the same command you used previously. - -## Updating to the latest release -We will periodically make updates and changes the SDK or the model. When you are notified of a change that requires action on your part, use the following steps to update your environment to the latest version. - -### Check the version you have installed -You can view the version of the braket packages that you have installed by using the following commands in the virtual environment: -```bash -pip show amazon-braket-default-simulator -pip show amazon-braket-schemas -pip show amazon-braket-sdk -``` - -You can also check your version of `amazon-braket-sdk-python` from within Python: - -``` ->>> import braket._sdk as braket_sdk ->>> braket_sdk.__version__ -``` - -Compare the version displayed in your local environment with the latest version listed for each of the following release pages: -- [amazon-braket-default-simulator-python](https://github.com/aws/amazon-braket-default-simulator-python/releases) -- [amazon-braket-schemas-python](https://github.com/aws/amazon-braket-schemas-python/releases) -- [amazon-braket-sdk-python](https://github.com/aws/amazon-braket-sdk-python/releases) - -If the version listed is higher than your local version, you should update to the latest release. - -### To get the lastest updates -Perform the steps described in the [Setting up the Amazon Braket Python SDKs](https://github.com/aws/amazon-braket-sdk-python/tree/main#setting-up-the-amazon-amazon-braket-sdk-pythons) section of this document. The links in that section point to the most recent version of the amazon-braket-sdk-python, amazon-braket-schemas-python, amazon-braket-default-simulator-python, and model file you need to set up the new version of the SDK. - -You can extract the file to the same location you are using and replace the existing files with the updated SDK. This lets you continue to use the same virtual environment. - ## Sample Notebooks Coming soon ## Braket Python SDK API Reference Documentation -To view the API Reference for the SDK, either download the .zip file or building it in your local environment. - -**To download the API Reference .zip file** - -Use the following command to download the .zip file -```bash -aws s3 cp s3://braket-external-assets-prod-us-west-2/sdk-docs/built-sdk-documentation.zip braket-sdk-documentation.zip -``` -Then extract the `braket-sdk-documentation.zip` file to your local environment. After you extract the file, open the index.html file in the `SDK Documentation` folder. **To generate the API Reference HTML in your local environment** To generate the HTML, first change directories (`cd`) to position the cursor in the `amazon-braket-sdk-python` directory. Then, run the following command to generate the HTML documentation files: ```bash +pip install tox tox -e docs ``` @@ -380,9 +160,13 @@ tox ``` ### Integration Tests -Set the `AWS_PROFILE` information in the Prerequisites section of this document. + +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). + +After you create a profile, use the following command to set the `AWS_PROFILE` so that all future commands can access your AWS account and resources. + ```bash -export AWS_PROFILE=Your_Profile_Name +export AWS_PROFILE=YOUR_PROFILE_NAME ``` Run the tests diff --git a/doc/index.rst b/doc/index.rst index 12bb3c4c..cfa6e2c0 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,7 +1,7 @@ -Amazon Braket (Private Beta) Python SDK +Amazon Braket Python SDK ======================================= -Amazon Braket Python SDK is an open source library for interacting with quantum devices on Amazon Braket (Private Beta). +Amazon Braket Python SDK is an open source library for interacting with quantum devices on Amazon Braket. This documentation provides information about the Amazon Braket Python SDK API. For information about how to configure your environment to use the braket-python-sdk, please see the README in the GitHub repo for this project at https://github.com/aws/amazon-braket-sdk-python. diff --git a/examples/bell.py b/examples/bell.py index 69780390..43321eee 100644 --- a/examples/bell.py +++ b/examples/bell.py @@ -19,7 +19,7 @@ aws_account_id = boto3.client("sts").get_caller_identity()["Account"] device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") -s3_folder = (f"braket-output-{aws_account_id}", "folder-name") +s3_folder = (f"amazon-braket-output-{aws_account_id}", "folder-name") # https://wikipedia.org/wiki/Bell_state bell = Circuit().h(0).cnot(0, 1) diff --git a/examples/debug_bell.py b/examples/debug_bell.py index a1e35b15..94727551 100644 --- a/examples/debug_bell.py +++ b/examples/debug_bell.py @@ -26,7 +26,7 @@ aws_account_id = boto3.client("sts").get_caller_identity()["Account"] device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") -s3_folder = (f"braket-output-{aws_account_id}", "folder-name") +s3_folder = (f"amazon-braket-output-{aws_account_id}", "folder-name") bell = Circuit().h(0).cnot(0, 1) # pass in logger to device.run, enabling debugging logs to print to console diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index e784cda2..2a47eb0f 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.6.0" +__version__ = "1.0.0" From 369852d8823a8706f0649ff16189217e2b2add5d Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Wed, 12 Aug 2020 13:42:15 -0700 Subject: [PATCH 0163/1165] infra: add issue templates (#133) --- .github/ISSUE_TEMPLATE/bug_report.md | 30 +++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 ++++ .../ISSUE_TEMPLATE/documentation_request.md | 17 +++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 +++++++++++++ 4 files changed, 72 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/documentation_request.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..fdb3bdf3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: File a report to help us reproduce and fix the problem +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To reproduce** +A clear, step-by-step set of instructions to reproduce the bug. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots or logs** +If applicable, add screenshots or logs to help explain your problem. + +**System information** +A description of your system. Please provide: +- **Amazon Braket Python SDK version**: +- **Amazon Braket Python Schemas version**: +- **Amazon Braket Python Default Simulator version**: +- **Python version**: + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..0436a859 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Ask a question + url: https://forums.aws.amazon.com/tags/braket + about: Use AWS Developer Forums to ask and answer questions diff --git a/.github/ISSUE_TEMPLATE/documentation_request.md b/.github/ISSUE_TEMPLATE/documentation_request.md new file mode 100644 index 00000000..b64cd478 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation_request.md @@ -0,0 +1,17 @@ +--- +name: Documentation request +about: Request improved documentation +title: '' +labels: '' +assignees: '' + +--- + +**What did you find confusing? Please describe.** +A clear and concise description of what you found confusing. Ex. I tried to [...] but I didn't understand how to [...] + +**Describe how documentation can be improved** +A clear and concise description of where documentation was lacking and how it can be improved. + +**Additional context** +Add any other context or screenshots about the documentation request here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..da5552c2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest new functionality for this library +title: '' +labels: '' +assignees: '' + +--- + +**Describe the feature you'd like** +A clear and concise description of the functionality you want. + +**How would this feature be used? Please describe.** +A clear and concise description of the use case for this feature. Please provide an example, if possible. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From c4a5c1036ad3e8d4493a566d21708db2822b19aa Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Wed, 12 Aug 2020 17:06:36 -0700 Subject: [PATCH 0164/1165] change: remove braket regions in AwsSession as this is done in boto3 (#134) documentation: fix miscellaneous items in docs and add references --- doc/index.rst | 2 +- src/braket/annealing/problem.py | 6 +++--- src/braket/aws/aws_device.py | 6 +++++- src/braket/aws/aws_quantum_task.py | 3 --- src/braket/aws/aws_session.py | 11 ----------- src/braket/circuits/circuit_diagram.py | 2 +- src/braket/circuits/instruction.py | 2 +- src/braket/circuits/moments.py | 14 +++++++------- src/braket/circuits/observables.py | 2 +- src/braket/circuits/quantum_operator.py | 8 +++++--- src/braket/devices/device.py | 2 +- src/braket/devices/local_simulator.py | 12 ++++++++---- test/unit_tests/braket/aws/test_aws_session.py | 7 ------- 13 files changed, 33 insertions(+), 44 deletions(-) diff --git a/doc/index.rst b/doc/index.rst index cfa6e2c0..73bf9635 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -3,7 +3,7 @@ Amazon Braket Python SDK Amazon Braket Python SDK is an open source library for interacting with quantum devices on Amazon Braket. -This documentation provides information about the Amazon Braket Python SDK API. For information about how to configure your environment to use the braket-python-sdk, please see the README in the GitHub repo for this project at https://github.com/aws/amazon-braket-sdk-python. +This documentation provides information about the Amazon Braket Python SDK library. For information about how to configure your environment to use the amazon-braket-sdk, please see the README in the GitHub repo for this project at https://github.com/aws/amazon-braket-sdk-python. Indices and tables __________________ diff --git a/src/braket/annealing/problem.py b/src/braket/annealing/problem.py index 9d7d2be4..81de48ca 100644 --- a/src/braket/annealing/problem.py +++ b/src/braket/annealing/problem.py @@ -21,7 +21,9 @@ class ProblemType(str, Enum): """ The type of annealing problem. + QUBO: Quadratic Unconstrained Binary Optimization, with values 1 and 0 + ISING: Ising model, with values +/-1 """ @@ -30,9 +32,7 @@ class ProblemType(str, Enum): class Problem: - """ Represents an annealing problem. - - """ + """ Represents an annealing problem.""" def __init__( self, diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index c23da3b4..b3919501 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -179,7 +179,11 @@ def arn(self) -> str: @property # TODO: Add a link to the boto3 docs def properties(self) -> DeviceCapabilities: - """DeviceCapabilities: Return the device properties""" + """DeviceCapabilities: Return the device properties + + Please see `braket.device_schema` in amazon-braket-schemas-python_ + + .. _amazon-braket-schemas-python: https://github.com/aws/amazon-braket-schemas-python""" return self._properties @property diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 520c56dc..6793d13b 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -43,9 +43,6 @@ class AwsQuantumTask(QuantumTask): NO_RESULT_TERMINAL_STATES = {"FAILED", "CANCELLED"} RESULTS_READY_STATES = {"COMPLETED"} - GATE_IR_TYPE = "jaqcd" - ANNEALING_IR_TYPE = "annealing" - DEFAULT_RESULTS_POLL_TIMEOUT = 120 DEFAULT_RESULTS_POLL_INTERVAL = 0.25 RESULTS_FILENAME = "results.json" diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 2ac310ef..44eb68ec 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -23,16 +23,11 @@ class AwsSession(object): S3DestinationFolder = NamedTuple("S3DestinationFolder", [("bucket", str), ("key", int)]) - BRAKET_REGIONS = ["us-east-1", "us-west-1", "us-west-2"] - def __init__(self, boto_session=None, braket_client=None): """ Args: boto_session: A boto3 session object braket_client: A boto3 Braket client - - Raises: - ValueError: If Braket is not available in the Region used for the boto3 session. """ self.boto_session = boto_session or boto3.Session() @@ -40,12 +35,6 @@ def __init__(self, boto_session=None, braket_client=None): if braket_client: self.braket_client = braket_client else: - region = self.boto_session.region_name - if region not in AwsSession.BRAKET_REGIONS: - raise ValueError( - f"No braket endpoint for {region}, " - + f"supported Regions are {AwsSession.BRAKET_REGIONS}" - ) self.braket_client = self.boto_session.client("braket") # diff --git a/src/braket/circuits/circuit_diagram.py b/src/braket/circuits/circuit_diagram.py index 821db691..ad2fd7ef 100644 --- a/src/braket/circuits/circuit_diagram.py +++ b/src/braket/circuits/circuit_diagram.py @@ -28,5 +28,5 @@ def build_diagram(circuit) -> str: Returns: str: String representation for the circuit diagram. - An empty string is returned for an empty circuit. + An empty string is returned for an empty circuit. """ diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index 36c28153..35c55782 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -41,7 +41,7 @@ def __init__(self, operator: InstructionOperator, target: QubitSetInput): ValueError: If `operator` is empty or any integer in `target` does not meet the `Qubit` or `QubitSet` class requirements. TypeError: If a `Qubit` class can't be constructed from `target` due to an incorrect - `typing`. + `typing`. Examples: >>> Instruction(Gate.CNot(), [0, 1]) diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 5e44cd49..c4b154a4 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -38,17 +38,17 @@ class MomentsKey(NamedTuple): class Moments(Mapping[MomentsKey, Instruction]): """ An ordered mapping of `MomentsKey` 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. + 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. This data structure is useful to determine a dependency of instructions, such as - printing or optimizing circuit structure, before sending it to a quantum - device. The original insertion order is preserved and can be retrieved via the `values()` - method. + printing or optimizing circuit structure, before sending it to a quantum + device. The original insertion order is preserved and can be retrieved via the `values()` + method. Args: - instructions (Iterable[Instruction], optional): Instructions to initialize self with. + instructions (Iterable[Instruction], optional): Instructions to initialize self. Default = []. Examples: diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 7cb74800..da663645 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -273,7 +273,7 @@ def __init__(self, matrix: np.ndarray, display_name: str = "Hermitian"): or has a dimension length that is not a positive exponent of 2, or is non-hermitian. - Example: + Examples: >>> Observable.Hermitian(matrix=np.array([[0, 1],[1, 0]])) """ verify_quantum_operator_matrix_dimensions(matrix) diff --git a/src/braket/circuits/quantum_operator.py b/src/braket/circuits/quantum_operator.py index 04710041..8eaab89b 100644 --- a/src/braket/circuits/quantum_operator.py +++ b/src/braket/circuits/quantum_operator.py @@ -10,6 +10,8 @@ # 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, List, Sequence import numpy as np @@ -86,12 +88,12 @@ def to_matrix(self, *args, **kwargs) -> Any: """ raise NotImplementedError("to_matrix has not been implemented yet.") - def matrix_equivalence(self, other): + def matrix_equivalence(self, other: QuantumOperator): """ - Whether the matrix form of two gates are equivalent + Whether the matrix form of two quantum operators are equivalent Args: - other (Gate): Gate instance to compare this quantum operator to + other (QuantumOperator): Quantum operator instance to compare this quantum operator to Returns: bool: If matrix forms of this quantum operator and the other quantum operator diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 4c9395a2..fffaa357 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -44,7 +44,7 @@ def run( task_specification (Union[Circuit, Problem]): Specification of a task to run on device. - shots (int): The number of times to run the task on the device. Default is 1_000. + shots (int): The number of times to run the task on the device. Default is `None`. Returns: QuantumTask: The QuantumTask tracking task execution on this device diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index fb0f3277..d6858f00 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -42,7 +42,7 @@ def __init__(self, backend: Union[str, BraketSimulator] = "default"): 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. + `default` simulator backend name. """ delegate = _get_simulator(backend) super().__init__( @@ -61,8 +61,8 @@ def run( Default is 0, which means that the simulator will compute the exact results based on the task specification. Sampling is not supported for shots=0. - *args: Positional args to pass to the IR simulator - **kwargs: Keyword arguments to pass to the IR simulator + *args: Positional args to pass to the `BraketSimulator` + **kwargs: Keyword arguments to pass to the `BraketSimulator` Returns: LocalQuantumTask: A LocalQuantumTask object containing the results @@ -82,7 +82,11 @@ def run( @property def properties(self) -> DeviceCapabilities: - """ DeviceCapabilities: Properties of the LocalSimulator device """ + """DeviceCapabilities: Return the device properties + + Please see `braket.device_schema` in amazon-braket-schemas-python_ + + .. _amazon-braket-schemas-python: https://github.com/aws/amazon-braket-schemas-python""" return self._delegate.properties @staticmethod diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index 8867e68d..a452d2db 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -34,13 +34,6 @@ def aws_session(boto_session): return AwsSession(boto_session=boto_session, braket_client=Mock()) -@pytest.mark.xfail(raises=ValueError) -def test_no_endpoint_for_region(): - boto_session = Mock() - boto_session.region_name = "foobar" - AwsSession(boto_session=boto_session, braket_client=None) - - def test_initializes_boto_client_if_required(boto_session): AwsSession(boto_session=boto_session, braket_client=None) boto_session.client.assert_called_with("braket") From 3e4f336bd6065d5c57314790ec8d4b9b24f3d321 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 13 Aug 2020 10:22:12 -0700 Subject: [PATCH 0165/1165] documentation: Add readthedocs configuration (#136) Advantages of using a file instead of the dashboard: https://docs.readthedocs.io/en/stable/config-file/index.html --- .gitignore | 1 + .readthedocs.yml | 20 ++++++++++++++++++++ doc/requirements.txt | 2 ++ 3 files changed, 23 insertions(+) create mode 100644 .readthedocs.yml create mode 100644 doc/requirements.txt diff --git a/.gitignore b/.gitignore index 4852cb8f..b554563e 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ __pycache__/ /doc/_apidoc/ /build /venv +/dist diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..d40e9b4f --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,20 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: doc/conf.py + +# Optionally build your docs in additional formats such as PDF +formats: + - pdf + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.7 + install: + - requirements: doc/requirements.txt diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 00000000..cbf1e365 --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,2 @@ +sphinx +sphinx-rtd-theme From 52b3a1e4dc217ba4440acc4dd2620dc9f070beed Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Thu, 13 Aug 2020 11:08:26 -0700 Subject: [PATCH 0166/1165] documentation: update descriptions of SV1 and local simulator in README (#135) --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1164c68d..f2d7e08c 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ To learn more about IAM user, roles, and policies, see [Adding and Removing IAM Follow the installation [instructions](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html) for Boto3 and setting up AWS credentials. ### Configure your AWS account with the resources necessary for Amazon Braket -If you are new to Amazon Braket, onboard to the service and create the resources necessary to use Amazon Braket using the [AWS console](https://aws.amazon.com/braket/). +If you are new to Amazon Braket, onboard to the service and create the resources necessary to use Amazon Braket using the [AWS console](https://console.aws.amazon.com/braket/home ). ## Installing the Amazon Braket Python SDK Use the steps in this section to install and configure the Amazon Braket Python SDK for your environment. @@ -72,12 +72,11 @@ print(task.result().measurement_counts) The code sample imports the Amazon Braket framework, then defines the device to use (the SV1 AWS simulator). The `s3_folder` statement defines the Amazon S3 bucket for the task result and the folder in the bucket to store the task result. This folder is created when you run the task. It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the job. This example can be found in `../examples/bell.py`. ### Available Simulators -There is currently one AWS simulator available: -- `arn:aws:braket:::device/quantum-simulator/amazon/sv1` – a Schrödinger simulator. Simulates exactly running a job on a quantum computer. Limit of 25 qubits. This simulator samples only from the state vector and outputs an array of bit strings that appears as though it came from a quantum computer. Does not provide a state vector. +Amazon Braket provides access to two simulators: a fully managed statevector simulator, SV1, and a local simulator that is part of the Amazon Braket SDK. -### Running a circuit locally +- `arn:aws:braket:::device/quantum-simulator/amazon/sv1` - SV1 is a fully managed, high-performance, state vector simulator. It can simulate circuits of up to 34 qubits and has a maximum runtime of 12h. You should expect a 34-qubit, dense, and square (circuit depth = 34) circuit to take approximately 1-2 hours to complete, depending on the type of gates used and other factors. -The Amazon Braket Python SDK comes bundled with an implementation of a quantum simulator that you can run locally. You can use the local simulator to test quantum tasks constructed using the SDK before you submit them to the Amazon Braket service for execution. An example of how to execute the task locally is included in the repo `../examples/local_bell.py`. +- `LocalSimulator()` - The Amazon Braket Python SDK comes bundled with an implementation of a quantum simulator that you can run locally. The local simulator is well suited for rapid prototyping on small circuits up to 25 qubits, depending on the hardware specifications of your Braket notebook instance or your local environment. An example of how to execute the task locally is included in the repo `../examples/local_bell.py`. ### Debugging logs @@ -115,10 +114,10 @@ Specify which quantum computer hardware to use by changing the value of the `dev - **Rigetti** "arn:aws:braket:::device/qpu/rigetti/Aspen-8" - **D-Wave** "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6" (See the next section in this document for more information about using D-Wave.) -**Important** Tasks may not run immediately on the QPU. The QPUs only execute tasks during execution windows. To find their execution windows, please refer to the [AWS console](https://aws.amazon.com/braket/) in the "Devices" tab. +**Important** Tasks may not run immediately on the QPU. The QPUs only execute tasks during execution windows. To find their execution windows, please refer to the [AWS console](https://console.aws.amazon.com/braket/home) in the "Devices" tab. ### Using Amazon Braket with D-Wave QPU -If you want to use [Ocean](https://docs.ocean.dwavesys.com/en/latest/) with the D-Wave QPU, you can install the [braket-ocean-python-plugin](https://github.com/aws/braket-ocean-python-plugin). Information about how to install the plugin is provided in the [README](https://github.com/aws/braket-ocean-python-plugin/blob/master/README.md) for the repo. +If you want to use [Ocean](https://docs.ocean.dwavesys.com/en/latest/) with the D-Wave QPU, you can install the [amazon-braket-ocean-plugin-python](https://github.com/aws/amazon-braket-ocean-plugin-python). Information about how to install the plugin is provided in the [README](https://github.com/aws/amazon-braket-ocean-plugin-python/blob/master/README.md) for the repo. ## Sample Notebooks Coming soon From 93238a7940c392a6a42b2a21004a02ed075a5d54 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 13 Aug 2020 12:25:57 -0700 Subject: [PATCH 0167/1165] infra: Use public PyPi dependency (#137) --- setup.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 5856a161..656150f3 100644 --- a/setup.py +++ b/setup.py @@ -24,11 +24,7 @@ packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "amazon-braket-schemas @ git+https://github.com/aws/amazon-braket-schemas-python.git", - ( - "amazon-braket-default-simulator @ " - "git+https://github.com/aws/amazon-braket-default-simulator-python.git" - ), + "amazon-braket-default-simulator", "backoff", "boltons", "boto3", From fd129f01d452a306a532be2e180efb1aed092de0 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Thu, 13 Aug 2020 12:55:35 -0700 Subject: [PATCH 0168/1165] documentation: update README with PyPI instructions (#138) --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f2d7e08c..3ed45e4a 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,14 @@ Follow the installation [instructions](https://boto3.amazonaws.com/v1/documentat If you are new to Amazon Braket, onboard to the service and create the resources necessary to use Amazon Braket using the [AWS console](https://console.aws.amazon.com/braket/home ). ## Installing the Amazon Braket Python SDK -Use the steps in this section to install and configure the Amazon Braket Python SDK for your environment. -You can install from source by cloning this repository and running a pip install command in the root directory of the repository: +The Amazon Braket Ocean Plugin can be installed with pip as follows: + +```bash +pip install amazon-braket-sdk +``` + +You can also install from source by cloning this repository and running a pip install command in the root directory of the repository: ```bash git clone https://github.com/aws/amazon-braket-sdk-python.git From ef1091049a092413986343fb36f8d025d2733b21 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Thu, 13 Aug 2020 13:07:37 -0700 Subject: [PATCH 0169/1165] documentation: README update (#139) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ed45e4a..23e63471 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ If you are new to Amazon Braket, onboard to the service and create the resources ## Installing the Amazon Braket Python SDK -The Amazon Braket Ocean Plugin can be installed with pip as follows: +The Amazon Braket Python SDK can be installed with pip as follows: ```bash pip install amazon-braket-sdk From 1dc67e08c9f0a0575f47084ec75e37264547ac05 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 13 Aug 2020 15:07:38 -0700 Subject: [PATCH 0170/1165] infra: Add details to setup.py (#140) So PyPi shows the relevant information --- setup.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/setup.py b/setup.py index 656150f3..fe815724 100644 --- a/setup.py +++ b/setup.py @@ -13,6 +13,9 @@ from setuptools import find_namespace_packages, setup +with open("README.md", "r") as fh: + long_description = fh.read() + with open("src/braket/_sdk/_version.py") as f: version = f.readlines()[-1].split()[-1].strip("\"'") @@ -48,4 +51,21 @@ "tox", ] }, + url="https://github.com/aws/amazon-braket-sdk-python", + author="Amazon Web Services", + description=( + "An open source library for interacting with quantum computing devices on Amazon Braket" + ), + long_description=long_description, + long_description_content_type="text/markdown", + keywords="Amazon AWS Quantum", + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Natural Language :: English", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + ], ) From b2206b812b98af5289a5773eeebdba5ab55989de Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 13 Aug 2020 15:37:52 -0700 Subject: [PATCH 0171/1165] fix: Post-release 1.0.0.post1 (#141) The only way to update a description in PyPi is to upload new files; however, uploading an existing version is prohibited. The recommended way to deal with this is with [post-releases](https://www.python.org/dev/peps/pep-0440/#post-releases). --- 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 2a47eb0f..9bf800c8 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.0.0" +__version__ = "1.0.0.post1" From 488607a5d9deebaa33c65c6c015cfe6a2f438f6f Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Thu, 13 Aug 2020 18:45:15 -0700 Subject: [PATCH 0172/1165] documentation: add readthedocs link to README (#142) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 23e63471..1c0ffcc6 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,8 @@ Coming soon ## Braket Python SDK API Reference Documentation +The API reference, can be found on [Read the Docs](https://amazon-braket-sdk-python.readthedocs.io/en/latest/). + **To generate the API Reference HTML in your local environment** To generate the HTML, first change directories (`cd`) to position the cursor in the `amazon-braket-sdk-python` directory. Then, run the following command to generate the HTML documentation files: From 8900e4c7da996c2df0f33582ce1afa98fb9e4961 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 14 Aug 2020 15:13:52 -0700 Subject: [PATCH 0173/1165] documentation: Get Read the Docs working (#143) Fixed previously failing Read the Docs builds (see aws/amazon-braket-default-simulator-python#46) --- .readthedocs.yml | 2 ++ doc/conf.py | 8 ++++++++ doc/requirements.txt | 1 + setup.py | 1 + tox.ini | 2 +- 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index d40e9b4f..26bc27fa 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -17,4 +17,6 @@ formats: python: version: 3.7 install: + - method: pip + path: . - requirements: doc/requirements.txt diff --git a/doc/conf.py b/doc/conf.py index e4dfbbda..50a6adc1 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -10,6 +10,7 @@ copyright = "{}, Amazon.com".format(datetime.datetime.now().year) extensions = [ + "sphinxcontrib.apidoc", "sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.napoleon", @@ -28,3 +29,10 @@ htmlhelp_basename = "{}doc".format(project) napoleon_use_rtype = False + +apidoc_module_dir = "../src/braket" +apidoc_output_dir = "_apidoc" +apidoc_excluded_paths = ["../test"] +apidoc_separate_modules = True +apidoc_module_first = True +apidoc_extra_args = ["-f", "--implicit-namespaces", "-H", "API Reference"] diff --git a/doc/requirements.txt b/doc/requirements.txt index cbf1e365..bf1b2d65 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,2 +1,3 @@ sphinx sphinx-rtd-theme +sphinxcontrib-apidoc diff --git a/setup.py b/setup.py index fe815724..54cd165c 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,7 @@ "pytest-xdist", "sphinx", "sphinx-rtd-theme", + "sphinxcontrib-apidoc", "tox", ] }, diff --git a/tox.ini b/tox.ini index 8a0dde5f..2fdc9e8a 100644 --- a/tox.ini +++ b/tox.ini @@ -63,8 +63,8 @@ basepython = python3.7 deps = sphinx sphinx-rtd-theme + sphinxcontrib-apidoc commands = - sphinx-apidoc -e -f -M --implicit-namespaces -H "API Reference" -o doc/_apidoc src/braket sphinx-build -E -T -b html doc build/documentation/html [testenv:serve-docs] From 86cac5e359e9fd96e12bfdb765af83c7bb5f30f1 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 17 Aug 2020 13:49:36 -0700 Subject: [PATCH 0174/1165] documentation: Add README badges (#145) --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c0ffcc6..123484fc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ ## Amazon Braket Python SDK -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. +[![Latest Version](https://img.shields.io/pypi/v/amazon-braket-sdk.svg)](https://pypi.python.org/pypi/amazon-braket-sdk) +[![Supported Python Versions](https://img.shields.io/pypi/pyversions/amazon-braket-sdk.svg)](https://pypi.python.org/pypi/amazon-braket-sdk) +[![Code Style: Black](https://img.shields.io/badge/code_style-black-000000.svg)](https://github.com/psf/black) +[![Documentation Status](https://readthedocs.org/projects/amazon-braket-sdk-python/badge/?version=latest)](https://amazon-braket-sdk-python.readthedocs.io/en/latest/?badge=latest) +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. ## Prerequisites Before you begin working with the Amazon Braket SDK, make sure that you've installed or configured the following prerequisites. From c109467b71bc9467c34406656f2a1b3bfb880530 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Mon, 17 Aug 2020 14:52:53 -0700 Subject: [PATCH 0175/1165] fix: Convert amplitude result to a complex number (#144) --- src/braket/aws/aws_quantum_task.py | 1 + .../tasks/gate_model_quantum_task_result.py | 31 ++++++++++----- .../gate_model_device_testing_utils.py | 18 +++++---- .../test_local_braket_simulator.py | 2 +- .../test_simulator_quantum_task.py | 23 +++++++---- .../braket/aws/common_test_utils.py | 38 +++++++++++++++++++ .../braket/aws/test_aws_quantum_task.py | 8 +++- 7 files changed, 94 insertions(+), 27 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 6793d13b..36433313 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -426,6 +426,7 @@ def _format_result(result): @_format_result.register def _(result: GateModelTaskResult) -> GateModelQuantumTaskResult: + GateModelQuantumTaskResult.cast_result_types(result) return GateModelQuantumTaskResult.from_object(result) diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index caabb9c5..464d028b 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -217,16 +217,7 @@ def from_string(result: str) -> GateModelQuantumTaskResult: in the result dict """ obj = GateModelTaskResult.parse_raw(result) - if obj.resultTypes: - for result_type in obj.resultTypes: - type = result_type.type.type - if 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]) + GateModelQuantumTaskResult.cast_result_types(obj) return GateModelQuantumTaskResult._from_object_internal(obj) @classmethod @@ -303,6 +294,26 @@ def _from_dict_internal_simulator_only(cls, result: GateModelTaskResult): values=values, ) + @staticmethod + def cast_result_types(gate_model_task_result: GateModelTaskResult) -> None: + """ + Casts the result types to the types expected by the SDK. + + Args: + gate_model_task_result (GateModelTaskResult): GateModelTaskResult representing the + results. + """ + if gate_model_task_result.resultTypes: + for result_type in gate_model_task_result.resultTypes: + type = result_type.type.type + if 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( ir_string: str, measurements: np.ndarray, measured_qubits: List[int] diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index e2163541..ef42a46b 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -51,27 +51,31 @@ def no_result_types_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any] assert len(result.measurements) == shots -def result_types_zero_shots_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_zero_shots_bell_pair_testing( + device: Device, include_state_vector: bool, run_kwargs: Dict[str, Any] +): circuit = ( Circuit() .h(0) .cnot(0, 1) .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) - .state_vector() .amplitude(["01", "10", "00", "11"]) ) + if include_state_vector: + circuit.state_vector() result = device.run(circuit, **run_kwargs).result() - assert len(result.result_types) == 3 + assert len(result.result_types) == 3 if include_state_vector else 2 assert np.allclose( result.get_value_by_result_type( ResultType.Expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) ), 1 / np.sqrt(2), ) - assert np.allclose( - result.get_value_by_result_type(ResultType.StateVector()), - np.array([1, 0, 0, 1]) / np.sqrt(2), - ) + if include_state_vector: + assert np.allclose( + result.get_value_by_result_type(ResultType.StateVector()), + np.array([1, 0, 0, 1]) / np.sqrt(2), + ) assert result.get_value_by_result_type(ResultType.Amplitude(["01", "10", "00", "11"])) == { "01": 0j, "10": 0j, diff --git a/test/integ_tests/test_local_braket_simulator.py b/test/integ_tests/test_local_braket_simulator.py index 2abda99d..d90d3890 100644 --- a/test/integ_tests/test_local_braket_simulator.py +++ b/test/integ_tests/test_local_braket_simulator.py @@ -44,7 +44,7 @@ def test_qubit_ordering(): def test_result_types_no_shots(): - result_types_zero_shots_bell_pair_testing(DEVICE, {"shots": 0}) + result_types_zero_shots_bell_pair_testing(DEVICE, True, {"shots": 0}) def test_result_types_nonzero_shots_bell_pair(): diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 050bccb8..6e2656fc 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -26,6 +26,7 @@ result_types_tensor_z_h_y_testing, result_types_tensor_z_hermitian_testing, result_types_tensor_z_z_testing, + result_types_zero_shots_bell_pair_testing, ) from braket.aws import AwsDevice @@ -48,6 +49,14 @@ def test_qubit_ordering(simulator_arn, aws_session, s3_destination_folder): qubit_ordering_testing(device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder}) +@pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) +def test_result_types_no_shots(simulator_arn, aws_session, s3_destination_folder): + device = AwsDevice(simulator_arn, aws_session) + result_types_zero_shots_bell_pair_testing( + device, False, {"shots": 0, "s3_destination_folder": s3_destination_folder} + ) + + @pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) def test_result_types_nonzero_shots_bell_pair(simulator_arn, aws_session, s3_destination_folder): device = AwsDevice(simulator_arn, aws_session) @@ -74,7 +83,7 @@ def test_result_types_bell_pair_marginal_probability( ) -@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) +@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS), (SIMULATOR_ARN, 0)]) def test_result_types_tensor_x_y(simulator_arn, shots, aws_session, s3_destination_folder): device = AwsDevice(simulator_arn, aws_session) result_types_tensor_x_y_testing( @@ -82,7 +91,7 @@ def test_result_types_tensor_x_y(simulator_arn, shots, aws_session, s3_destinati ) -@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) +@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS), (SIMULATOR_ARN, 0)]) def test_result_types_tensor_z_h_y(simulator_arn, shots, aws_session, s3_destination_folder): device = AwsDevice(simulator_arn, aws_session) result_types_tensor_z_h_y_testing( @@ -90,7 +99,7 @@ def test_result_types_tensor_z_h_y(simulator_arn, shots, aws_session, s3_destina ) -@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) +@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS), (SIMULATOR_ARN, 0)]) def test_result_types_hermitian(simulator_arn, shots, aws_session, s3_destination_folder): device = AwsDevice(simulator_arn, aws_session) result_types_hermitian_testing( @@ -98,7 +107,7 @@ def test_result_types_hermitian(simulator_arn, shots, aws_session, s3_destinatio ) -@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) +@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS), (SIMULATOR_ARN, 0)]) def test_result_types_tensor_z_z(simulator_arn, shots, aws_session, s3_destination_folder): device = AwsDevice(simulator_arn, aws_session) result_types_tensor_z_z_testing( @@ -106,7 +115,7 @@ def test_result_types_tensor_z_z(simulator_arn, shots, aws_session, s3_destinati ) -@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) +@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS), (SIMULATOR_ARN, 0)]) def test_result_types_tensor_hermitian_hermitian( simulator_arn, shots, aws_session, s3_destination_folder ): @@ -116,7 +125,7 @@ def test_result_types_tensor_hermitian_hermitian( ) -@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) +@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS), (SIMULATOR_ARN, 0)]) def test_result_types_tensor_y_hermitian(simulator_arn, shots, aws_session, s3_destination_folder): device = AwsDevice(simulator_arn, aws_session) result_types_tensor_y_hermitian_testing( @@ -124,7 +133,7 @@ def test_result_types_tensor_y_hermitian(simulator_arn, shots, aws_session, s3_d ) -@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) +@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS), (SIMULATOR_ARN, 0)]) def test_result_types_tensor_z_hermitian(simulator_arn, shots, aws_session, s3_destination_folder): device = AwsDevice(simulator_arn, aws_session) result_types_tensor_z_hermitian_testing( diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 72519b96..f0278cb3 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -45,6 +45,44 @@ class MockS3: } ) + 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, + }, + { + "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": { + "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"}], + }, + }, + } + ) + MOCK_S3_RESULT_ANNEALING = json.dumps( { "braketSchemaHeader": { 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 c11a867c..2a771a0f 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -190,9 +190,13 @@ def test_result_annealing(annealing_task): ) -def test_result_is_cached(circuit_task): +@pytest.mark.parametrize( + "result_string", + [MockS3.MOCK_S3_RESULT_GATE_MODEL, MockS3.MOCK_S3_RESULT_GATE_MODEL_WITH_RESULT_TYPES], +) +def test_result_is_cached(circuit_task, result_string): _mock_metadata(circuit_task._aws_session, "COMPLETED") - _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_GATE_MODEL) + _mock_s3(circuit_task._aws_session, result_string) circuit_task.result() _mock_s3(circuit_task._aws_session, "") From 6e1794dbf991c292a7b3e0ffd368f445f0cd7b55 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Fri, 21 Aug 2020 10:55:32 -0700 Subject: [PATCH 0176/1165] feature: add get_devices to search devices based on criteria (#147) --- src/braket/aws/aws_device.py | 126 ++++++++++-- src/braket/aws/aws_session.py | 45 ++++- test/integ_tests/test_device_creation.py | 24 +++ test/unit_tests/braket/aws/test_aws_device.py | 88 ++++++++- .../unit_tests/braket/aws/test_aws_session.py | 187 +++++++++++++++++- 5 files changed, 441 insertions(+), 29 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index b3919501..78b66edd 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -11,10 +11,13 @@ # 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 enum import Enum -from typing import Optional, Union +from typing import List, Optional, Set, Union import boto3 +from boltons.dictutils import FrozenDict from networkx import Graph, complete_graph, from_edgelist from braket.annealing.problem import Problem @@ -41,18 +44,21 @@ class AwsDevice(Device): device. """ - QPU_REGIONS = { - "rigetti": ["us-west-1"], - "ionq": ["us-east-1"], - "d-wave": ["us-west-2"], - } + DEVICE_REGIONS = FrozenDict( + { + "rigetti": ["us-west-1"], + "ionq": ["us-east-1"], + "d-wave": ["us-west-2"], + "amazon": ["us-west-2", "us-west-1", "us-east-1"], + } + ) DEFAULT_SHOTS_QPU = 1000 DEFAULT_SHOTS_SIMULATOR = 0 DEFAULT_RESULTS_POLL_TIMEOUT = 432000 DEFAULT_RESULTS_POLL_INTERVAL = 1 - def __init__(self, arn: str, aws_session=None): + def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): """ Args: arn (str): The ARN of the device @@ -64,8 +70,8 @@ def __init__(self, arn: str, aws_session=None): physically located. When this occurs, a cloned `aws_session` is created for the Region the QPU is located in. - See `braket.aws.aws_device.AwsQpu.QPU_REGIONS` for the AWS Regions the QPUs are located - in. + See `braket.aws.aws_device.AwsDevice.DEVICE_REGIONS` for the AWS Regions provider + devices are located in. """ super().__init__(name=None, status=None) self._arn = arn @@ -80,8 +86,8 @@ def run( task_specification: Union[Circuit, Problem], s3_destination_folder: AwsSession.S3DestinationFolder, shots: Optional[int] = None, - poll_timeout_seconds: int = DEFAULT_RESULTS_POLL_TIMEOUT, - poll_interval_seconds: int = DEFAULT_RESULTS_POLL_INTERVAL, + poll_timeout_seconds: Optional[int] = DEFAULT_RESULTS_POLL_TIMEOUT, + poll_interval_seconds: Optional[int] = DEFAULT_RESULTS_POLL_INTERVAL, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTask: @@ -230,7 +236,7 @@ def _construct_topology_graph(self) -> Graph: return None @staticmethod - def _aws_session_for_device(device_arn: str, aws_session: AwsSession) -> AwsSession: + def _aws_session_for_device(device_arn: str, aws_session: Optional[AwsSession]) -> AwsSession: """AwsSession: Returns an AwsSession for the device ARN. """ if "qpu" in device_arn: return AwsDevice._aws_session_for_qpu(device_arn, aws_session) @@ -238,18 +244,22 @@ def _aws_session_for_device(device_arn: str, aws_session: AwsSession) -> AwsSess return aws_session or AwsSession() @staticmethod - def _aws_session_for_qpu(device_arn: str, aws_session: AwsSession) -> AwsSession: + def _aws_session_for_qpu(device_arn: str, aws_session: Optional[AwsSession]) -> AwsSession: """ Get an AwsSession for the device ARN. QPUs are physically located in specific AWS Regions. The AWS sessions should connect to the Region that the QPU is located in. - See `braket.aws.aws_qpu.AwsDevice.QPU_REGIONS` for the AWS Regions the QPUs are located in. + See `braket.aws.aws_qpu.AwsDevice.DEVICE_REGIONS` for the + AWS Regions the devices are located in. """ region_key = device_arn.split("/")[-2] - qpu_regions = AwsDevice.QPU_REGIONS.get(region_key, []) + qpu_regions = AwsDevice.DEVICE_REGIONS.get(region_key, []) + return AwsDevice._copy_aws_session(aws_session, qpu_regions) + @staticmethod + def _copy_aws_session(aws_session: Optional[AwsSession], regions: List[str]) -> AwsSession: if aws_session: - if aws_session.boto_session.region_name in qpu_regions: + if aws_session.boto_session.region_name in regions: return aws_session else: creds = aws_session.boto_session.get_credentials() @@ -257,11 +267,11 @@ def _aws_session_for_qpu(device_arn: str, aws_session: AwsSession) -> AwsSession aws_access_key_id=creds.access_key, aws_secret_access_key=creds.secret_key, aws_session_token=creds.token, - region_name=qpu_regions[0], + region_name=regions[0], ) return AwsSession(boto_session=boto_session) else: - boto_session = boto3.Session(region_name=qpu_regions[0]) + boto_session = boto3.Session(region_name=regions[0]) return AwsSession(boto_session=boto_session) def __repr__(self): @@ -271,3 +281,83 @@ def __eq__(self, other): if isinstance(other, AwsDevice): return self.arn == other.arn return NotImplemented + + @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, + 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 + 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']) + + Args: + arns (List[str], optional): device ARN list, default is `None` + names (List[str], optional): device name list, default is `None` + types (List[AwsDeviceType], optional): device type list, default is `None` + statuses (List[str], optional): device status list, default is `None` + provider_names (List[str], optional): provider name list, default is `None` + order_by (str, optional): field to order result by, default is `name`. + Accepted values are ['arn', 'name', 'type', 'provider_name', 'status'] + aws_session (AwsSession, optional) aws_session: An AWS session object. Default = None. + + Returns: + List[AwsDevice]: list of AWS devices + """ + order_by_list = ["arn", "name", "type", "provider_name", "status"] + if order_by not in order_by_list: + raise ValueError(f"order_by '{order_by}' must be in {order_by_list}") + device_regions_set = AwsDevice._get_devices_regions_set(arns, provider_names, types) + results = [] + for region in device_regions_set: + aws_session = AwsDevice._copy_aws_session(aws_session, [region]) + results.extend( + aws_session.search_devices( + arns=arns, + names=names, + types=types, + statuses=statuses, + provider_names=provider_names, + ) + ) + arns = set([result["deviceArn"] for result in results]) + devices = [AwsDevice(arn, aws_session) for arn in arns] + devices.sort(key=lambda x: getattr(x, order_by)) + return devices + + @staticmethod + def _get_devices_regions_set( + arns: Optional[List[str]], + provider_names: Optional[List[str]], + types: Optional[List[AwsDeviceType]], + ) -> Set[str]: + """Get the set of regions to call `SearchDevices` API given filters""" + device_regions_set = set( + AwsDevice.DEVICE_REGIONS[key][0] for key in AwsDevice.DEVICE_REGIONS + ) + if provider_names: + provider_region_set = set() + for provider in provider_names: + for key in AwsDevice.DEVICE_REGIONS: + if key in provider.lower(): + provider_region_set.add(AwsDevice.DEVICE_REGIONS[key][0]) + break + device_regions_set = device_regions_set.intersection(provider_region_set) + if arns: + arns_region_set = set([AwsDevice.DEVICE_REGIONS[arn.split("/")[-2]][0] for arn in arns]) + device_regions_set = device_regions_set.intersection(arns_region_set) + if types and types == [AwsDeviceType.SIMULATOR]: + device_regions_set = device_regions_set.intersection( + [AwsDevice.DEVICE_REGIONS["amazon"][0]] + ) + return device_regions_set diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 44eb68ec..f30c0196 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.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, Dict, NamedTuple +from typing import Any, Dict, List, NamedTuple, Optional import backoff import boto3 @@ -113,6 +113,47 @@ def get_device(self, arn: str) -> Dict[str, Any]: arn (str): The ARN of the device Returns: - Dict[str, Any]: Device metadata + Dict[str, Any]: The response from the Amazon Braket `GetDevice` operation. """ return self.braket_client.get_device(deviceArn=arn) + + 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, + ): + """ + Get devices based on filters. The result is the AND of + all the filters `arns`, `names`, `types`, `statuses`, `provider_names`. + + Args: + arns (List[str], optional): device ARN list, default is `None` + names (List[str], optional): device name list, default is `None` + types (List[str], optional): device type list, default is `None` + statuses (List[str], optional): device status list, default is `None` + provider_names (List[str], optional): provider name list, default is `None` + + Returns: + List[Dict[str, Any]: The response from the Amazon Braket `SearchDevices` operation. + """ + filters = [] + if arns: + filters.append({"name": "deviceArn", "values": arns}) + paginator = self.braket_client.get_paginator("search_devices") + page_iterator = paginator.paginate(filters=filters, PaginationConfig={"MaxItems": 100}) + results = [] + for page in page_iterator: + for result in page["devices"]: + if names and result["deviceName"] not in names: + continue + if types and result["deviceType"] not in types: + continue + if statuses and result["deviceStatus"] not in statuses: + continue + if provider_names and result["providerName"] not in provider_names: + continue + results.append(result) + return results diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 9e475e24..38a3a6e0 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -36,3 +36,27 @@ def test_device_across_regions(aws_session): # assert QPUs across different regions can be created using the same aws_session AwsDevice(RIGETTI_ARN, aws_session) AwsDevice(IONQ_ARN, aws_session) + + +@pytest.mark.parametrize("arn", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (SIMULATOR_ARN)]) +def test_get_devices_arn(arn): + results = AwsDevice.get_devices(arns=[arn]) + assert results[0].arn == arn + + +def test_get_devices_others(): + provider_names = ["Amazon Braket"] + types = ["SIMULATOR"] + statuses = ["ONLINE"] + results = AwsDevice.get_devices(provider_names=provider_names, types=types, statuses=statuses) + assert results + for result in results: + assert result.provider_name in provider_names + assert result.type in types + assert result.status in statuses + + +def test_get_devices_all(): + result_arns = [result.arn for result in AwsDevice.get_devices()] + for arn in [DWAVE_ARN, RIGETTI_ARN, IONQ_ARN, SIMULATOR_ARN]: + assert arn in result_arns diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index a2b48f11..78be6417 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -14,7 +14,7 @@ from unittest.mock import Mock, patch import pytest -from common_test_utils import RIGETTI_ARN, run_and_assert +from common_test_utils import DWAVE_ARN, IONQ_ARN, RIGETTI_ARN, SIMULATOR_ARN, run_and_assert from braket.aws import AwsDevice, AwsDeviceType from braket.circuits import Circuit @@ -51,7 +51,7 @@ ) MOCK_GATE_MODEL_QPU_1 = { - "deviceName": "name1", + "deviceName": "Aspen-8", "deviceType": "QPU", "providerName": "provider1", "deviceStatus": "OFFLINE", @@ -173,7 +173,7 @@ ) MOCK_GATE_MODEL_SIMULATOR = { - "deviceName": "name2", + "deviceName": "SV1", "deviceType": "SIMULATOR", "providerName": "provider1", "deviceStatus": "ONLINE", @@ -201,14 +201,14 @@ def circuit(): @pytest.fixture def boto_session(): _boto_session = Mock() - _boto_session.region_name = AwsDevice.QPU_REGIONS[RIGETTI_REGION_KEY][0] + _boto_session.region_name = AwsDevice.DEVICE_REGIONS[RIGETTI_REGION_KEY][0] return _boto_session @pytest.fixture def aws_session(): _boto_session = Mock() - _boto_session.region_name = AwsDevice.QPU_REGIONS[RIGETTI_REGION_KEY][0] + _boto_session.region_name = AwsDevice.DEVICE_REGIONS[RIGETTI_REGION_KEY][0] _aws_session = Mock() _aws_session.boto_session = _boto_session return _aws_session @@ -273,7 +273,7 @@ def test_repr(arn): def test_device_aws_session_in_qpu_region(aws_session): arn = RIGETTI_ARN - aws_session.boto_session.region_name = AwsDevice.QPU_REGIONS[RIGETTI_REGION_KEY][0] + aws_session.boto_session.region_name = AwsDevice.DEVICE_REGIONS[RIGETTI_REGION_KEY][0] aws_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 AwsDevice(arn, aws_session) @@ -286,7 +286,7 @@ def test_aws_session_in_another_qpu_region( boto_session_init, aws_session_init, boto_session, aws_session ): arn = RIGETTI_ARN - region = AwsDevice.QPU_REGIONS.get(RIGETTI_REGION_KEY)[0] + region = AwsDevice.DEVICE_REGIONS.get(RIGETTI_REGION_KEY)[0] boto_session_init.return_value = boto_session aws_session_init.return_value = aws_session @@ -322,7 +322,7 @@ def test_device_no_aws_session_supplied( boto_session_init, aws_session_init, boto_session, aws_session ): arn = RIGETTI_ARN - region = AwsDevice.QPU_REGIONS.get(RIGETTI_REGION_KEY)[0] + region = AwsDevice.DEVICE_REGIONS.get(RIGETTI_REGION_KEY)[0] boto_session_init.return_value = boto_session aws_session_init.return_value = aws_session @@ -461,3 +461,75 @@ def _assert_device_fields(device, expected_properties, expected_device_data): assert device.type == AwsDeviceType(expected_device_data.get("deviceType")) if device.topology_graph: assert device.topology_graph.edges == device._construct_topology_graph().edges + + +@patch("braket.aws.aws_device.AwsDevice._copy_aws_session") +def test_get_devices(mock_copy_aws_session): + aws_session = Mock() + mock_copy_aws_session.return_value = aws_session + aws_session.search_devices.side_effect = [ + [ + { + "deviceArn": SIMULATOR_ARN, + "deviceName": "SV1", + "deviceType": "SIMULATOR", + "deviceStatus": "ONLINE", + "providerName": "Amazon Braket", + } + ], + [ + { + "deviceArn": RIGETTI_ARN, + "deviceName": "Aspen-8", + "deviceType": "QPU", + "deviceStatus": "ONLINE", + "providerName": "Rigetti", + }, + { + "deviceArn": SIMULATOR_ARN, + "deviceName": "SV1", + "deviceType": "SIMULATOR", + "deviceStatus": "ONLINE", + "providerName": "Amazon Braket", + }, + ], + ] + aws_session.get_device.side_effect = [MOCK_GATE_MODEL_SIMULATOR, MOCK_GATE_MODEL_QPU_1] + results = AwsDevice.get_devices( + arns=[SIMULATOR_ARN, RIGETTI_ARN], + types=["SIMULATOR", "QPU"], + provider_names=["Amazon Braket", "Rigetti"], + statuses=["ONLINE"], + ) + assert [result.name for result in results] == ["Aspen-8", "SV1"] + + +@pytest.mark.xfail(raises=ValueError) +def test_get_devices_invalid_order_by(): + AwsDevice.get_devices(order_by="foo") + + +@pytest.mark.parametrize( + "input,output", + [ + ( + {"arns": None, "types": None, "provider_names": None}, + {"us-west-2", "us-west-1", "us-east-1"}, + ), + ({"arns": None, "types": ["SIMULATOR"], "provider_names": None}, {"us-west-2"}), + ( + {"arns": [RIGETTI_ARN, DWAVE_ARN], "types": ["QPU"], "provider_names": None}, + {"us-west-2", "us-west-1"}, + ), + ( + { + "arns": [RIGETTI_ARN, DWAVE_ARN, IONQ_ARN], + "types": ["QPU"], + "provider_names": ["Rigetti", "Amazon Braket", "IONQ", "FOO"], + }, + {"us-west-2", "us-west-1", "us-east-1"}, + ), + ], +) +def test_get_devices_regions_set(input, output): + assert AwsDevice._get_devices_regions_set(**input) == output diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index a452d2db..728d1e6a 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. import json -from unittest.mock import Mock +from unittest.mock import MagicMock, Mock import pytest from botocore.exceptions import ClientError @@ -171,3 +171,188 @@ def test_get_quantum_task_does_not_retry_other_exceptions(aws_session): with pytest.raises(ClientError): aws_session.get_quantum_task("some-arn") aws_session.braket_client.get_quantum_task.call_count == 1 + + +@pytest.mark.parametrize( + "input,output", + [ + ( + {}, + [ + { + "deviceArn": "arn1", + "deviceName": "name1", + "deviceType": "SIMULATOR", + "deviceStatus": "ONLINE", + "providerName": "pname1", + }, + { + "deviceArn": "arn2", + "deviceName": "name2", + "deviceType": "SIMULATOR", + "deviceStatus": "OFFLINE", + "providerName": "pname1", + }, + { + "deviceArn": "arn3", + "deviceName": "name3", + "deviceType": "QPU", + "deviceStatus": "ONLINE", + "providerName": "pname2", + }, + ], + ), + ( + {"names": ["name1"]}, + [ + { + "deviceArn": "arn1", + "deviceName": "name1", + "deviceType": "SIMULATOR", + "deviceStatus": "ONLINE", + "providerName": "pname1", + }, + ], + ), + ( + {"types": ["SIMULATOR"]}, + [ + { + "deviceArn": "arn1", + "deviceName": "name1", + "deviceType": "SIMULATOR", + "deviceStatus": "ONLINE", + "providerName": "pname1", + }, + { + "deviceArn": "arn2", + "deviceName": "name2", + "deviceType": "SIMULATOR", + "deviceStatus": "OFFLINE", + "providerName": "pname1", + }, + ], + ), + ( + {"statuses": ["ONLINE"]}, + [ + { + "deviceArn": "arn1", + "deviceName": "name1", + "deviceType": "SIMULATOR", + "deviceStatus": "ONLINE", + "providerName": "pname1", + }, + { + "deviceArn": "arn3", + "deviceName": "name3", + "deviceType": "QPU", + "deviceStatus": "ONLINE", + "providerName": "pname2", + }, + ], + ), + ( + {"provider_names": ["pname2"]}, + [ + { + "deviceArn": "arn3", + "deviceName": "name3", + "deviceType": "QPU", + "deviceStatus": "ONLINE", + "providerName": "pname2", + }, + ], + ), + ( + { + "provider_names": ["pname2"], + "types": ["QPU"], + "statuses": ["ONLINE"], + "names": ["name3"], + }, + [ + { + "deviceArn": "arn3", + "deviceName": "name3", + "deviceType": "QPU", + "deviceStatus": "ONLINE", + "providerName": "pname2", + }, + ], + ), + ( + {"provider_names": ["pname1"], "types": ["SIMULATOR"], "statuses": ["ONLINE"],}, + [ + { + "deviceArn": "arn1", + "deviceName": "name1", + "deviceType": "SIMULATOR", + "deviceStatus": "ONLINE", + "providerName": "pname1", + }, + ], + ), + ], +) +def test_search_devices(input, output, aws_session): + return_value = [ + { + "devices": [ + { + "deviceArn": "arn1", + "deviceName": "name1", + "deviceType": "SIMULATOR", + "deviceStatus": "ONLINE", + "providerName": "pname1", + }, + { + "deviceArn": "arn2", + "deviceName": "name2", + "deviceType": "SIMULATOR", + "deviceStatus": "OFFLINE", + "providerName": "pname1", + }, + { + "deviceArn": "arn3", + "deviceName": "name3", + "deviceType": "QPU", + "deviceStatus": "ONLINE", + "providerName": "pname2", + }, + ] + } + ] + mock_paginator = Mock() + mock_iterator = MagicMock() + aws_session.braket_client.get_paginator.return_value = mock_paginator + mock_paginator.paginate.return_value = mock_iterator + mock_iterator.__iter__.return_value = return_value + + assert aws_session.search_devices(**input) == output + + +def test_search_devices_arns(aws_session): + return_value = [ + { + "devices": [ + { + "deviceArn": "arn1", + "deviceName": "name1", + "deviceType": "SIMULATOR", + "deviceStatus": "ONLINE", + "providerName": "pname1", + } + ] + } + ] + mock_paginator = Mock() + mock_iterator = MagicMock() + aws_session.braket_client.get_paginator.return_value = mock_paginator + mock_paginator.paginate.return_value = mock_iterator + mock_iterator.__iter__.return_value = return_value + + assert aws_session.search_devices(arns=["arn1"]) == return_value[0]["devices"] + mock_paginator.paginate.assert_called_with( + filters=[{"name": "deviceArn", "values": ["arn1"]},], PaginationConfig={"MaxItems": 100} + ) From 86e99e68bb07deeb1d32bbe9701182bec2c6d46c Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Tue, 25 Aug 2020 14:12:32 -0700 Subject: [PATCH 0177/1165] infra: add github action for stale issues (#150) --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .../ISSUE_TEMPLATE/documentation_request.md | 2 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- .github/workflows/stale_issue.yml | 47 +++++++++++++++++++ 4 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/stale_issue.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index fdb3bdf3..89f3cdac 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug report about: File a report to help us reproduce and fix the problem title: '' -labels: '' +labels: 'bug' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/documentation_request.md b/.github/ISSUE_TEMPLATE/documentation_request.md index b64cd478..7d339756 100644 --- a/.github/ISSUE_TEMPLATE/documentation_request.md +++ b/.github/ISSUE_TEMPLATE/documentation_request.md @@ -2,7 +2,7 @@ name: Documentation request about: Request improved documentation title: '' -labels: '' +labels: 'documentation' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index da5552c2..6aaa7928 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,7 +2,7 @@ name: Feature request about: Suggest new functionality for this library title: '' -labels: '' +labels: 'feature' assignees: '' --- diff --git a/.github/workflows/stale_issue.yml b/.github/workflows/stale_issue.yml new file mode 100644 index 00000000..e1833b32 --- /dev/null +++ b/.github/workflows/stale_issue.yml @@ -0,0 +1,47 @@ +name: "Close stale issues" + +# Controls when the action will run. +# This is every day at 10am +on: + schedule: + - cron: "0 10 * * *" + +jobs: + cleanup: + runs-on: ubuntu-latest + name: Stale issue job + steps: + - uses: aws-actions/stale-issue-cleanup@v3 + with: + # Setting messages to an empty string will cause the automation to skip + # that category + ancient-issue-message: Greetings! It looks like this issue hasn’t been active in longer than three years. We encourage you to check if this is still an issue in the latest release. Because it has been longer than three years since the last update on this, and in the absence of more information, we will be closing this issue soon. If you find that this is still a problem, please feel free to provide a comment to prevent automatic closure, or if the issue is already closed, please feel free to reopen it. + stale-issue-message: Greetings! It looks like this issue hasn’t been active in longer than a week. We encourage you to check if this is still an issue in the latest release. Because it has been longer than a week since the last update on this, and in the absence of more information, we will be closing this issue soon. If you find that this is still a problem, please feel free to provide a comment or add an upvote to prevent automatic closure, or if the issue is already closed, please feel free to open a new one. + stale-pr-message: Greetings! It looks like this PR hasn’t been active in longer than a week, add a comment or an upvote to prevent automatic closure, or if the issue is already closed, please feel free to open a new one. + + # These labels are required + stale-issue-label: closing-soon + exempt-issue-label: auto-label-exempt + stale-pr-label: closing-soon + exempt-pr-label: pr/needs-review + response-requested-label: response-requested + + # Don't set closed-for-staleness label to skip closing very old issues + # regardless of label + closed-for-staleness-label: closed-for-staleness + + # Issue timing + days-before-stale: 7 + days-before-close: 4 + days-before-ancient: 1095 + + # If you don't want to mark a issue as being ancient based on a + # threshold of "upvotes", you can set this here. An "upvote" is + # the total number of +1, heart, hooray, and rocket reactions + # on an issue. + minimum-upvotes-to-exempt: 1 + + repo-token: ${{ secrets.GITHUB_TOKEN }} + loglevel: DEBUG + # Set dry-run to true to not perform label or close actions. + dry-run: false From 14eafc538936afc26e68d06fae2122d6a3f8fae8 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 25 Aug 2020 19:35:48 -0700 Subject: [PATCH 0178/1165] infra: Automatically publish to PyPi (#148) --- .github/workflows/publish-to-pypi.yml | 26 ++++++++++++++++++++++++++ README.md | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/publish-to-pypi.yml diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml new file mode 100644 index 00000000..fa99300b --- /dev/null +++ b/.github/workflows/publish-to-pypi.yml @@ -0,0 +1,26 @@ +name: Publish distribution to PyPI + +on: + release: + types: [published] + +jobs: + build-and-publish: + name: Build and publish distribution to PyPi + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@main + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + - name: Install wheel + run: python -m pip install --user --upgrade wheel + - name: Install twine + run: python -m pip install --user --upgrade twine + - 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@master + with: + password: ${{ secrets.pypi_token }} diff --git a/README.md b/README.md index 123484fc..14d10818 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## Amazon Braket Python SDK +# Amazon Braket Python SDK [![Latest Version](https://img.shields.io/pypi/v/amazon-braket-sdk.svg)](https://pypi.python.org/pypi/amazon-braket-sdk) [![Supported Python Versions](https://img.shields.io/pypi/pyversions/amazon-braket-sdk.svg)](https://pypi.python.org/pypi/amazon-braket-sdk) From 8cad0b12c835e2f652faf912b5121c286f42bea8 Mon Sep 17 00:00:00 2001 From: Amazon Braket CI Bot <69489165+amazon-braket-ci-bot@users.noreply.github.com> Date: Tue, 1 Sep 2020 12:56:19 -0700 Subject: [PATCH 0179/1165] infra: Update formatting to follow new black rules (#152) --- src/braket/annealing/problem.py | 18 +++---- src/braket/aws/aws_session.py | 5 +- src/braket/circuits/ascii_circuit_diagram.py | 3 +- src/braket/circuits/circuit.py | 8 ++-- src/braket/circuits/observable.py | 2 +- src/braket/devices/device.py | 10 ++-- src/braket/devices/local_simulator.py | 15 ++++-- src/braket/tasks/local_quantum_task.py | 2 +- test/integ_tests/test_aws_session_s3.py | 7 ++- .../braket/aws/common_test_utils.py | 6 ++- test/unit_tests/braket/aws/test_aws_device.py | 23 +++++++-- .../unit_tests/braket/aws/test_aws_session.py | 48 +++++++++++++++---- .../circuits/test_ascii_circuit_diagram.py | 5 +- .../circuits/test_quantum_operator_helpers.py | 3 +- .../braket/circuits/test_result_types.py | 8 +++- .../braket/devices/test_local_simulator.py | 6 ++- 16 files changed, 124 insertions(+), 45 deletions(-) diff --git a/src/braket/annealing/problem.py b/src/braket/annealing/problem.py index 81de48ca..1705e9c9 100644 --- a/src/braket/annealing/problem.py +++ b/src/braket/annealing/problem.py @@ -20,7 +20,7 @@ class ProblemType(str, Enum): - """ The type of annealing problem. + """The type of annealing problem. QUBO: Quadratic Unconstrained Binary Optimization, with values 1 and 0 @@ -63,7 +63,7 @@ def __init__( @property def problem_type(self) -> ProblemType: - """ The type of annealing problem. + """The type of annealing problem. Returns: ProblemType: The type of annealing problem @@ -72,7 +72,7 @@ def problem_type(self) -> ProblemType: @property def linear(self) -> Dict[int, float]: - """ The linear terms of this problem. + """The linear terms of this problem. Returns: Dict[int, float]: The linear terms of this problem, as a map of variable to coefficient @@ -81,7 +81,7 @@ def linear(self) -> Dict[int, float]: @property def quadratic(self) -> Dict[Tuple[int, int], float]: - """ The quadratic terms of this problem. + """The quadratic terms of this problem. Returns: Dict[Tuple[int, int], float]: The quadratic terms of this problem, @@ -90,7 +90,7 @@ def quadratic(self) -> Dict[Tuple[int, int], float]: return self._quadratic def add_linear_term(self, term: int, coefficient: float) -> Problem: - """ Adds a linear term to the problem. + """Adds a linear term to the problem. Args: term (int): The variable of the linear term @@ -103,7 +103,7 @@ def add_linear_term(self, term: int, coefficient: float) -> Problem: return self def add_linear_terms(self, coefficients: Dict[int, float]) -> Problem: - """ Adds linear terms to the problem. + """Adds linear terms to the problem. Args: coefficients (Dict[int, float]): A map of variable to coefficient @@ -115,7 +115,7 @@ def add_linear_terms(self, coefficients: Dict[int, float]) -> Problem: return self def add_quadratic_term(self, term: Tuple[int, int], coefficient: float) -> Problem: - """ Adds a quadratic term to the problem. + """Adds a quadratic term to the problem. Args: term (Tuple[int, int]): The variables of the quadratic term @@ -128,7 +128,7 @@ def add_quadratic_term(self, term: Tuple[int, int], coefficient: float) -> Probl return self def add_quadratic_terms(self, coefficients: Dict[Tuple[int, int], float]) -> Problem: - """ Adds quadratic terms to the problem. + """Adds quadratic terms to the problem. Args: coefficients (Dict[Tuple[int, int], float]): A map of variables to coefficient @@ -140,7 +140,7 @@ def add_quadratic_terms(self, coefficients: Dict[Tuple[int, int], float]) -> Pro return self def to_ir(self): - """ Converts this problem into IR representation. + """Converts this problem into IR representation. Returns: ir.Problem: IR representation of this problem object diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index f30c0196..06968f22 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -67,7 +67,10 @@ def _should_giveup(err): return not ( isinstance(err, ClientError) and err.response["Error"]["Code"] - in ["ResourceNotFoundException", "ThrottlingException",] + in [ + "ResourceNotFoundException", + "ThrottlingException", + ] ) @backoff.on_exception( diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index 462291dd..129969bd 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -86,7 +86,8 @@ def build_diagram(circuit) -> str: @staticmethod def _ascii_group_items( - circuit_qubits: QubitSet, items: List[Union[Instruction, ResultType]], + circuit_qubits: QubitSet, + items: List[Union[Instruction, ResultType]], ) -> List[Tuple[QubitSet, List[Instruction]]]: """ Group instructions in a moment for ASCII diagram diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index e507ebcd..d2e30539 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -148,9 +148,11 @@ def basis_rotation_instructions(self) -> List[Instruction]: added_observables_targets = set() for return_type in observable_return_types: observable: Observable = return_type.observable - targets: List[List[int]] = [list(return_type.target)] if return_type.target else [ - list([qubit]) for qubit in self._moments.qubits - ] + targets: List[List[int]] = ( + [list(return_type.target)] + if return_type.target + else [list([qubit]) for qubit in self._moments.qubits] + ) for target in targets: # only add gates for observables and targets that diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index 7927c235..06e29573 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -35,7 +35,7 @@ def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): def to_ir(self) -> List[Union[str, List[List[List[float]]]]]: """List[Union[str, List[List[List[float]]]]]: Returns the IR - representation for the observable""" + representation for the observable""" raise NotImplementedError @property diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index fffaa357..5c2d8e91 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -20,9 +20,7 @@ class Device(ABC): - """ An abstraction over quantum devices that includes quantum computers and simulators. - - """ + """An abstraction over quantum devices that includes quantum computers and simulators.""" def __init__(self, name: str, status: str): """ @@ -37,7 +35,7 @@ def __init__(self, name: str, status: str): def run( self, task_specification: Union[Circuit, Problem], shots: Optional[int], *args, **kwargs ) -> QuantumTask: - """ Run a quantum task specification on this quantum device. A task can be a circuit + """Run a quantum task specification on this quantum device. A task can be a circuit or an annealing problem. Args: @@ -52,7 +50,7 @@ def run( @property def name(self) -> str: - """ Return the name of this Device. + """Return the name of this Device. Returns: str: The name of this Device @@ -61,7 +59,7 @@ def name(self) -> str: @property def status(self) -> str: - """ Return the status of this Device. + """Return the status of this Device. Returns: str: The status of this Device diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index d6858f00..a3876753 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -31,7 +31,7 @@ class LocalSimulator(Device): - """ A simulator meant to run directly on the user's machine. + """A simulator meant to run directly on the user's machine. This class wraps a BraketSimulator object so that it can be run and returns results using constructs from the SDK rather than Braket IR. @@ -46,14 +46,19 @@ def __init__(self, backend: Union[str, BraketSimulator] = "default"): """ delegate = _get_simulator(backend) super().__init__( - name=delegate.__class__.__name__, status="AVAILABLE", + name=delegate.__class__.__name__, + status="AVAILABLE", ) self._delegate = delegate def run( - self, task_specification: Union[Circuit, Problem], shots: int = 0, *args, **kwargs, + self, + task_specification: Union[Circuit, Problem], + shots: int = 0, + *args, + **kwargs, ) -> LocalQuantumTask: - """ Runs the given task with the wrapped local simulator. + """Runs the given task with the wrapped local simulator. Args: task_specification (Union[Circuit, Problem]): @@ -91,7 +96,7 @@ def properties(self) -> DeviceCapabilities: @staticmethod def registered_backends() -> Set[str]: - """ Gets the backends that have been registered as entry points + """Gets the backends that have been registered as entry points Returns: Set[str]: The names of the available backends that can be passed diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py index b5a8eb0a..adc3213b 100644 --- a/src/braket/tasks/local_quantum_task.py +++ b/src/braket/tasks/local_quantum_task.py @@ -18,7 +18,7 @@ class LocalQuantumTask(QuantumTask): - """ A task containing the results of a local simulation. + """A task containing the results of a local simulation. Since this class is instantiated with the results, cancel() and run_async() are unsupported. """ diff --git a/test/integ_tests/test_aws_session_s3.py b/test/integ_tests/test_aws_session_s3.py index 35d69049..11c85896 100644 --- a/test/integ_tests/test_aws_session_s3.py +++ b/test/integ_tests/test_aws_session_s3.py @@ -15,7 +15,12 @@ import pytest -TEST_S3_OBJ_CONTENTS = {"TaskMetadata": {"Id": "blah", "Status": "COMPLETED",}} +TEST_S3_OBJ_CONTENTS = { + "TaskMetadata": { + "Id": "blah", + "Status": "COMPLETED", + } +} @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 f0278cb3..7644200f 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -93,7 +93,11 @@ class MockS3: "solutionCounts": [3, 2, 4], "values": [0.0, 1.0, 2.0], "variableCount": 4, - "taskMetadata": {"id": "task_arn", "shots": 100, "deviceId": DWAVE_ARN,}, + "taskMetadata": { + "id": "task_arn", + "shots": 100, + "deviceId": DWAVE_ARN, + }, "additionalMetadata": { "action": { "type": "ISING", diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 78be6417..e47da03d 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -30,7 +30,11 @@ }, "service": { "executionWindows": [ - {"executionDay": "Everyday", "windowStartHour": "11:00", "windowEndHour": "12:00",} + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + } ], "shotsRange": [1, 10], }, @@ -66,7 +70,11 @@ }, "service": { "executionWindows": [ - {"executionDay": "Everyday", "windowStartHour": "11:00", "windowEndHour": "12:00",} + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + } ], "shotsRange": [1, 10], }, @@ -156,7 +164,11 @@ }, "service": { "executionWindows": [ - {"executionDay": "Everyday", "windowStartHour": "11:00", "windowEndHour": "12:00",} + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + } ], "shotsRange": [1, 10], }, @@ -337,7 +349,10 @@ def test_device_no_aws_session_supplied( @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") 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, + aws_quantum_task_mock, + device, + circuit, + s3_destination_folder, ) diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index 728d1e6a..4c6f0f1b 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -19,7 +19,11 @@ from braket.aws import AwsSession -TEST_S3_OBJ_CONTENTS = {"TaskMetadata": {"Id": "blah",}} +TEST_S3_OBJ_CONTENTS = { + "TaskMetadata": { + "Id": "blah", + } +} @pytest.fixture @@ -129,9 +133,17 @@ def test_get_quantum_task_retry(aws_session): return_value = {"quantumTaskArn": arn} resource_not_found_response = { - "Error": {"Code": "ResourceNotFoundException", "Message": "unit-test-error",} + "Error": { + "Code": "ResourceNotFoundException", + "Message": "unit-test-error", + } + } + throttling_response = { + "Error": { + "Code": "ThrottlingException", + "Message": "unit-test-error", + } } - throttling_response = {"Error": {"Code": "ThrottlingException", "Message": "unit-test-error",}} aws_session.braket_client.get_quantum_task.side_effect = [ ClientError(resource_not_found_response, "unit-test"), @@ -146,9 +158,17 @@ def test_get_quantum_task_retry(aws_session): def test_get_quantum_task_fail_after_retries(aws_session): resource_not_found_response = { - "Error": {"Code": "ResourceNotFoundException", "Message": "unit-test-error",} + "Error": { + "Code": "ResourceNotFoundException", + "Message": "unit-test-error", + } + } + throttling_response = { + "Error": { + "Code": "ThrottlingException", + "Message": "unit-test-error", + } } - throttling_response = {"Error": {"Code": "ThrottlingException", "Message": "unit-test-error",}} aws_session.braket_client.get_quantum_task.side_effect = [ ClientError(resource_not_found_response, "unit-test"), @@ -162,7 +182,12 @@ def test_get_quantum_task_fail_after_retries(aws_session): def test_get_quantum_task_does_not_retry_other_exceptions(aws_session): - exception_response = {"Error": {"Code": "SomeOtherException", "Message": "unit-test-error",}} + exception_response = { + "Error": { + "Code": "SomeOtherException", + "Message": "unit-test-error", + } + } aws_session.braket_client.get_quantum_task.side_effect = [ ClientError(exception_response, "unit-test"), @@ -282,7 +307,11 @@ def test_get_quantum_task_does_not_retry_other_exceptions(aws_session): ], ), ( - {"provider_names": ["pname1"], "types": ["SIMULATOR"], "statuses": ["ONLINE"],}, + { + "provider_names": ["pname1"], + "types": ["SIMULATOR"], + "statuses": ["ONLINE"], + }, [ { "deviceArn": "arn1", @@ -354,5 +383,8 @@ def test_search_devices_arns(aws_session): assert aws_session.search_devices(arns=["arn1"]) == return_value[0]["devices"] mock_paginator.paginate.assert_called_with( - filters=[{"name": "deviceArn", "values": ["arn1"]},], PaginationConfig={"MaxItems": 100} + filters=[ + {"name": "deviceArn", "values": ["arn1"]}, + ], + PaginationConfig={"MaxItems": 100}, ) 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 c17cdac6..f696c5a3 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -355,7 +355,10 @@ def test_multiple_result_types_with_custom_hermitian_ascii_symbol(): .variance(observable=Observable.Y(), target=0) .expectation(observable=Observable.Y(), target=3) .expectation( - observable=Observable.Hermitian(matrix=herm_matrix, display_name="MyHerm",), + observable=Observable.Hermitian( + matrix=herm_matrix, + display_name="MyHerm", + ), target=[1, 2], ) ) diff --git a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py index 8694292d..a4464648 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py @@ -86,7 +86,8 @@ def test_get_pauli_eigenvalues_correct_eigenvalues_two_qubits(): def test_get_pauli_eigenvalues_correct_eigenvalues_three_qubits(): """Test the get_pauli_eigenvalues function for three qubits""" assert np.array_equal( - get_pauli_eigenvalues(3), np.diag(np.kron(z_matrix, np.kron(z_matrix, z_matrix))), + get_pauli_eigenvalues(3), + np.diag(np.kron(z_matrix, np.kron(z_matrix, z_matrix))), ) diff --git a/test/unit_tests/braket/circuits/test_result_types.py b/test/unit_tests/braket/circuits/test_result_types.py index 4d0b5c93..f523261e 100644 --- a/test/unit_tests/braket/circuits/test_result_types.py +++ b/test/unit_tests/braket/circuits/test_result_types.py @@ -27,7 +27,13 @@ {"target": [0, 1]}, {"targets": [0, 1]}, ), - (ResultType.Probability, "probability", ir.Probability, {"target": None}, {},), + ( + ResultType.Probability, + "probability", + ir.Probability, + {"target": None}, + {}, + ), ( ResultType.Expectation, "expectation", diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 28c3753e..63abd2e4 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -50,7 +50,11 @@ "solutionCounts": [3, 2, 4], "values": [0.0, 1.0, 2.0], "variableCount": 4, - "taskMetadata": {"id": "task_arn", "shots": 100, "deviceId": "device_id",}, + "taskMetadata": { + "id": "task_arn", + "shots": 100, + "deviceId": "device_id", + }, "additionalMetadata": { "action": { "type": "ISING", From 5515fd93f5d52ad13b2e6c827365952c6a78630a Mon Sep 17 00:00:00 2001 From: Cedric Lin <67704428+licedric@users.noreply.github.com> Date: Tue, 1 Sep 2020 20:28:15 +0000 Subject: [PATCH 0180/1165] fix: Call async_result() before calling result() (#151) --- src/braket/aws/aws_quantum_task.py | 3 ++- .../gate_model_device_testing_utils.py | 24 +++++++++++++++++++ .../test_local_braket_simulator.py | 5 ++++ .../test_simulator_quantum_task.py | 9 +++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 36433313..2f118436 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -236,7 +236,8 @@ def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult Consecutive calls to this method return a cached result from the preceding request. """ try: - return asyncio.get_event_loop().run_until_complete(self.async_result()) + async_result = self.async_result() + return asyncio.get_event_loop().run_until_complete(async_result) except asyncio.CancelledError: # Future was cancelled, return whatever is in self._result if anything self._logger.warning("Task future was cancelled") diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index ef42a46b..74f3a344 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -13,6 +13,7 @@ from typing import Any, Dict +import concurrent.futures import numpy as np from braket.circuits import Circuit, Observable, ResultType @@ -404,3 +405,26 @@ def result_types_tensor_y_hermitian_testing(device: Device, run_kwargs: Dict[str assert_variance_expectation_sample_result( result, shots, expected_var, expected_mean, expected_eigs ) + + +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) + + def run_circuit(circuit): + task = device.run(circuit, **run_kwargs) + return task.result() + + futures = [] + num_threads = 2 + + with concurrent.futures.ThreadPoolExecutor() as executor: + for _ in range(num_threads): + future = executor.submit(run_circuit, bell) + futures.append(future) + for future in futures: + result = future.result() + assert np.allclose(result.measurement_probabilities["00"], 0.5, **tol) + assert np.allclose(result.measurement_probabilities["11"], 0.5, **tol) + assert len(result.measurements) == shots diff --git a/test/integ_tests/test_local_braket_simulator.py b/test/integ_tests/test_local_braket_simulator.py index d90d3890..b5da94a7 100644 --- a/test/integ_tests/test_local_braket_simulator.py +++ b/test/integ_tests/test_local_braket_simulator.py @@ -13,6 +13,7 @@ import pytest from gate_model_device_testing_utils import ( + multithreaded_bell_pair_testing, no_result_types_bell_pair_testing, qubit_ordering_testing, result_types_all_selected_testing, @@ -35,6 +36,10 @@ SHOTS = 8000 +def test_multithreaded_bell_pair(): + multithreaded_bell_pair_testing(DEVICE, {"shots": SHOTS}) + + def test_no_result_types_bell_pair(): no_result_types_bell_pair_testing(DEVICE, {"shots": SHOTS}) diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 6e2656fc..994e711f 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -13,6 +13,7 @@ import pytest from gate_model_device_testing_utils import ( + multithreaded_bell_pair_testing, no_result_types_bell_pair_testing, qubit_ordering_testing, result_types_all_selected_testing, @@ -35,6 +36,14 @@ SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" +@pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) +def test_multithreaded_bell_pair(simulator_arn, aws_session, s3_destination_folder): + device = AwsDevice(simulator_arn, aws_session) + multithreaded_bell_pair_testing( + device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} + ) + + @pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) def test_no_result_types_bell_pair(simulator_arn, aws_session, s3_destination_folder): device = AwsDevice(simulator_arn, aws_session) From 964c7fa4971fb2b7fc6f90d452f6f9d6ad36d8a4 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Tue, 8 Sep 2020 11:38:48 -0700 Subject: [PATCH 0181/1165] infra: update version (#153) --- 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 9bf800c8..cc0af6da 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.0.0.post1" +__version__ = "1.1.0" From ceb036e2c8b1585ffcfeeb8a8875e641611089fa Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Tue, 8 Sep 2020 14:07:57 -0700 Subject: [PATCH 0182/1165] infra: modify tol for integ tests (#154) --- test/integ_tests/gate_model_device_testing_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index 74f3a344..0e12286c 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -11,9 +11,9 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import concurrent.futures from typing import Any, Dict -import concurrent.futures import numpy as np from braket.circuits import Circuit, Observable, ResultType @@ -24,7 +24,7 @@ def get_tol(shots: int) -> Dict[str, float]: if shots: - return {"atol": 0.05, "rtol": 0.1} + return {"atol": 0.1, "rtol": 0.15} else: return {"atol": 0.01, "rtol": 0} From 842d761c04ab76b0518b23d7b61e56a5e8b6916e Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Wed, 9 Sep 2020 11:50:24 -0700 Subject: [PATCH 0183/1165] fix: add handling for solution_counts=[] for annealing result (#155) --- src/braket/_sdk/_version.py | 2 +- .../tasks/annealing_quantum_task_result.py | 2 +- .../test_annealing_quantum_task_result.py | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index cc0af6da..4dc8c51b 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.1.0" +__version__ = "1.1.1" diff --git a/src/braket/tasks/annealing_quantum_task_result.py b/src/braket/tasks/annealing_quantum_task_result.py index 5e0731e6..87097aee 100644 --- a/src/braket/tasks/annealing_quantum_task_result.py +++ b/src/braket/tasks/annealing_quantum_task_result.py @@ -123,7 +123,7 @@ def from_string(result: str) -> AnnealingQuantumTaskResult: def _from_object(cls, result: AnnealingTaskResult): solutions = numpy.asarray(result.solutions, dtype=int) values = numpy.asarray(result.values, dtype=float) - if result.solutionCounts is None: + if not result.solutionCounts: solution_counts = numpy.ones(len(solutions), dtype=int) else: solution_counts = numpy.asarray(result.solutionCounts, dtype=int) 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 b8f7d011..5b173891 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 @@ -114,6 +114,19 @@ def result_str_2(solutions, values, variable_count, task_metadata, additional_me return result.json() +@pytest.fixture +def result_str_3(solutions, values, variable_count, task_metadata, additional_metadata): + result = AnnealingTaskResult( + solutionCounts=[], + solutions=solutions, + variableCount=variable_count, + values=values, + taskMetadata=task_metadata, + additionalMetadata=additional_metadata, + ) + return result.json() + + @pytest.fixture def annealing_result( solutions, @@ -192,6 +205,11 @@ def test_from_string_solution_counts_none(result_str_2, solutions): np.testing.assert_equal(result.record_array.solution_count, np.ones(len(solutions), dtype=int)) +def test_from_string_solution_counts_empty_list(result_str_3, solutions): + result = AnnealingQuantumTaskResult.from_string(result_str_3) + np.testing.assert_equal(result.record_array.solution_count, np.ones(len(solutions), dtype=int)) + + def test_data_sort_by_none(annealing_result, solutions, values, solution_counts): d = list(annealing_result.data(sorted_by=None)) for i in range(len(solutions)): From 2ea14b775fc2a28e534dcd6ea7f0ef9fb64c568e Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Wed, 9 Sep 2020 14:47:20 -0700 Subject: [PATCH 0184/1165] infra: Add CHANGELOG.md (#156) --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..93fc92c1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,27 @@ +# Changelog + +## v1.1.1 (2020-09-09) + +### Bug Fixes +* Add handling for solution_counts=[] for annealing result + +## v1.1.0 (2020-09-08) + +### Features +* Add get_devices to search devices based on criteria + +### Bug Fixes +* Call async_result() before calling result() +* Convert amplitude result to a complex number + +## v1.0.0.post1 (2020-08-14) + +### Documentation + +* add readthedocs link to README + +## v1.0.0 (2020-08-13) + +This is the public release of the Amazon Braket Python SDK! + +The Amazon Braket Python SDK is an open source library that provides a framework that you can use to interact with quantum computing devices through Amazon Braket. \ No newline at end of file From 08f101b1c1d4c8995e3820ffe45da25c0e85ce01 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 9 Sep 2020 23:37:11 +0000 Subject: [PATCH 0185/1165] prepare release v1.1.1.post0 --- CHANGELOG.md | 8 +++++++- src/braket/_sdk/_version.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93fc92c1..84f88839 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.1.1.post0 (2020-09-09) + +### Testing and Release Infrastructure + + * Add CHANGELOG.md + ## v1.1.1 (2020-09-09) ### Bug Fixes @@ -24,4 +30,4 @@ This is the public release of the Amazon Braket Python SDK! -The Amazon Braket Python SDK is an open source library that provides a framework that you can use to interact with quantum computing devices through Amazon Braket. \ No newline at end of file +The Amazon Braket Python SDK is an open source library that provides a framework that you can use to interact with quantum computing devices through Amazon Braket. diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 4dc8c51b..72a849ff 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.1.1" +__version__ = "1.1.1.post0" \ No newline at end of file From db7ad3146d97fee147127dbc7c7aaa53f80dad33 Mon Sep 17 00:00:00 2001 From: Amazon Braket CI Bot <69489165+amazon-braket-ci-bot@users.noreply.github.com> Date: Wed, 9 Sep 2020 19:53:00 -0700 Subject: [PATCH 0186/1165] infra: fix black formatting (#157) Co-authored-by: Ava Wang --- 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 72a849ff..0e120505 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.1.1.post0" \ No newline at end of file +__version__ = "1.1.1.post0" From ad062e27b15acae778fa9a7789fff2a5d94c0c73 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 10 Sep 2020 18:05:14 +0000 Subject: [PATCH 0187/1165] prepare release v1.1.1.post1 --- 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 84f88839..5df5679d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.1.1.post1 (2020-09-10) + +### Testing and Release Infrastructure + + * fix black formatting + ## v1.1.1.post0 (2020-09-09) ### Testing and Release Infrastructure diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 0e120505..6d6f4886 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.1.1.post0" +__version__ = "1.1.1.post1" From cc8e87a0aa38216caf577a0789ad3c6dc58d2a45 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 10 Sep 2020 18:05:14 +0000 Subject: [PATCH 0188/1165] update development version to v1.1.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 6d6f4886..3306ce6b 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.1.1.post1" +__version__ = "1.1.2.dev0" From abdc51ad95f40df8dbdc9fa92029d7d35fb91dac Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Tue, 29 Sep 2020 10:05:31 -0700 Subject: [PATCH 0189/1165] documentation: Add D-Wave Advantage_system1 arn (#159) squash/merge to pull this in. new device is live and available with Amazon Braket --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 14d10818..8ef00641 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,9 @@ print(task.result().measurement_counts) Specify which quantum computer hardware to use by changing the value of the `device_arn` to the value for quantum computer to use: - **IonQ** "arn:aws:braket:::device/qpu/ionq/ionQdevice" - **Rigetti** "arn:aws:braket:::device/qpu/rigetti/Aspen-8" -- **D-Wave** "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6" (See the next section in this document for more information about using D-Wave.) +- **D-Wave** (See the next section in this document for more information about using D-Wave.) + - **D-Wave 2000Q_6** "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6" + - **D-Wave Advantage_system1** "arn:aws:braket:::device/qpu/d-wave/Advantage_system1" **Important** Tasks may not run immediately on the QPU. The QPUs only execute tasks during execution windows. To find their execution windows, please refer to the [AWS console](https://console.aws.amazon.com/braket/home) in the "Devices" tab. From 2b975f6b0a42d0e4b6f1182b408cdc4f459db1de Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 29 Sep 2020 18:08:36 +0000 Subject: [PATCH 0190/1165] prepare release v1.1.1.post2 --- 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 5df5679d..58dd9913 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.1.1.post2 (2020-09-29) + +### Documentation Changes + + * Add D-Wave Advantage_system1 arn + ## v1.1.1.post1 (2020-09-10) ### Testing and Release Infrastructure diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 3306ce6b..95ee3925 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.1.2.dev0" +__version__ = "1.1.1.post2" From 1b6aea1fbad6b7bf4dac54cfcc7e5f3cb35353df Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 29 Sep 2020 18:08:36 +0000 Subject: [PATCH 0191/1165] update development version to v1.1.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 95ee3925..3306ce6b 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.1.1.post2" +__version__ = "1.1.2.dev0" From 4e00e218e678d71c9ffd05b8477393af51ea60bf Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Fri, 2 Oct 2020 09:08:53 -0700 Subject: [PATCH 0192/1165] documentation: Update docstring for measurement_counts (#160) --- src/braket/tasks/gate_model_quantum_task_result.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 464d028b..06e3b08a 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -56,9 +56,10 @@ class GateModelQuantumTaskResult: measured_qubits (List[int], optional): The indices of the measured qubits. Default is None. Only available when shots > 0. Indicates which qubits are in `measurements`. - measurement_counts (Counter, optional): A Counter of measurements. Key is the measurements + measurement_counts (Counter, optional): A `Counter` of measurements. Key is the measurements in a big endian binary string. Value is the number of times that measurement occurred. - Default is None. Only available when shots > 0. + Default is None. Only available when shots > 0. Note that the keys in `Counter` are + unordered. measurement_probabilities (Dict[str, float], optional): A dictionary of probabilistic results. Key is the measurements in a big endian binary string. From 2d0f8629b6a2802f0dc289a5f48ff181acbfa19f Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Fri, 2 Oct 2020 09:22:53 -0700 Subject: [PATCH 0193/1165] fix: Add error message for running a circuit without instructions (#161) --- src/braket/circuits/circuit_helpers.py | 8 +++++--- .../unit_tests/braket/circuits/test_circuit_helpers.py | 10 ++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/braket/circuits/circuit_helpers.py b/src/braket/circuits/circuit_helpers.py index 5f493208..d62ca19a 100644 --- a/src/braket/circuits/circuit_helpers.py +++ b/src/braket/circuits/circuit_helpers.py @@ -23,10 +23,12 @@ def validate_circuit_and_shots(circuit: Circuit, shots: int) -> None: shots (int): shots to validate Raises: - ValueError: If no result types specified for circuit and `shots=0`. - See `braket.circuit.result_types`. Or, if `StateVector` or `Amplitude` - are specified as result types when `shots > 0`. + ValueError: If circuit has no instructions. Also, if no result types + specified for circuit and `shots=0`. See `braket.circuit.result_types`. + 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 shots and not circuit.result_types: raise ValueError( "No result types specified for circuit and shots=0. See `braket.circuit.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 d6a91515..4805ce00 100644 --- a/test/unit_tests/braket/circuits/test_circuit_helpers.py +++ b/test/unit_tests/braket/circuits/test_circuit_helpers.py @@ -17,6 +17,16 @@ from braket.circuits.circuit_helpers import validate_circuit_and_shots +@pytest.mark.xfail(raises=ValueError) +def test_validate_circuit_and_shots_no_instructions(): + validate_circuit_and_shots(Circuit(), 100) + + +@pytest.mark.xfail(raises=ValueError) +def test_validate_circuit_and_shots_0_no_instructions(): + validate_circuit_and_shots(Circuit(), 0) + + @pytest.mark.xfail(raises=ValueError) def test_validate_circuit_and_shots_0_no_results(): validate_circuit_and_shots(Circuit().h(0), 0) From b38f0c588aacc60fcc88908835a975032d796c6c Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Fri, 2 Oct 2020 09:24:46 -0700 Subject: [PATCH 0194/1165] fix: Add error for target qubit set size not equal to operator qubit size in instruction (#162) --- src/braket/circuits/instruction.py | 11 ++++++++++- src/braket/circuits/result_type.py | 3 ++- test/unit_tests/braket/circuits/test_instruction.py | 9 +++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index 35c55782..06abd1fe 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -39,7 +39,8 @@ def __init__(self, operator: InstructionOperator, target: QubitSetInput): Raises: ValueError: If `operator` is empty or any integer in `target` does not meet the `Qubit` - or `QubitSet` class requirements. + or `QubitSet` class requirements. Also, if operator qubit count does not equal + the size of the target qubit set. TypeError: If a `Qubit` class can't be constructed from `target` due to an incorrect `typing`. @@ -57,6 +58,14 @@ def __init__(self, operator: InstructionOperator, target: QubitSetInput): raise ValueError("Operator cannot be empty") self._operator = operator self._target = QubitSet(target) + if ( + hasattr(self._operator, "qubit_count") + and len(self._target) != self._operator.qubit_count + ): + raise ValueError( + f"Operator qubit count {self._operator.qubit_count} must be " + f"equal to size of target qubit set {self._target}" + ) @property def operator(self) -> InstructionOperator: diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index 262b025d..2835b304 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -163,7 +163,8 @@ def __init__( else: if self._observable.qubit_count != len(self._target): raise ValueError( - "Observable's qubit count and the number of target qubits must be equal" + f"Observable's qubit count {self._observable.qubit_count} and " + f"the size of the target qubit set {self._target} must be equal" ) if self._observable.qubit_count != len(self.ascii_symbols): raise ValueError( diff --git a/test/unit_tests/braket/circuits/test_instruction.py b/test/unit_tests/braket/circuits/test_instruction.py index d120f6cc..f66fddf7 100644 --- a/test/unit_tests/braket/circuits/test_instruction.py +++ b/test/unit_tests/braket/circuits/test_instruction.py @@ -31,6 +31,11 @@ def test_empty_operator(): Instruction(None, target=0) +@pytest.mark.xfail(raises=ValueError) +def test_non_matching_qubit_set_and_qubit_count(): + Instruction(Gate.CNot, target=[0, 0]) + + def test_init_with_qubits(): target = QubitSet([0, 1]) instr = Instruction(Gate.CNot(), target) @@ -50,9 +55,9 @@ def test_init_with_int(): def test_init_with_sequence(): - target = [0, Qubit(1), 2] + target = [0, Qubit(1)] instr = Instruction(Gate.CNot(), target) - assert instr.target == QubitSet([0, 1, 2]) + assert instr.target == QubitSet([0, 1]) def test_getters(): From f4ccee535a532e0a8b1a61a7b45eada2afe0b084 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 2 Oct 2020 18:09:35 +0000 Subject: [PATCH 0195/1165] prepare release v1.1.2 --- 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 58dd9913..be5561fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.1.2 (2020-10-02) + +### Bug Fixes and Other Changes + + * Add error for target qubit set size not equal to operator qubit size in instruction + * Add error message for running a circuit without instructions + +### Documentation Changes + + * Update docstring for measurement_counts + ## v1.1.1.post2 (2020-09-29) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 3306ce6b..92b566fa 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.1.2.dev0" +__version__ = "1.1.2" From dd7c4c8d398a8cb28b38ca1c035367a08831ab55 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 2 Oct 2020 18:09:35 +0000 Subject: [PATCH 0196/1165] update development version to v1.1.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 92b566fa..94ea4654 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.1.2" +__version__ = "1.1.3.dev0" From 71434b26db458520e9dbfa23dd3af1f1e450a898 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Fri, 2 Oct 2020 16:31:42 -0700 Subject: [PATCH 0197/1165] infra: change bucket creation setup for integ tests (#163) --- test/integ_tests/conftest.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py index 50618397..a4cb2bf2 100644 --- a/test/integ_tests/conftest.py +++ b/test/integ_tests/conftest.py @@ -15,7 +15,6 @@ import boto3 import pytest -from botocore.exceptions import ClientError from braket.aws.aws_session import AwsSession @@ -44,20 +43,8 @@ def s3_bucket(s3_resource, boto_session): account_id = boto_session.client("sts").get_caller_identity()["Account"] bucket_name = f"amazon-braket-sdk-integ-tests-{account_id}" bucket = s3_resource.Bucket(bucket_name) - - try: + if not bucket.creation_date: bucket.create(ACL="private", CreateBucketConfiguration={"LocationConstraint": region_name}) - except ClientError as e: - code = e.response["Error"]["Code"] - - # Bucket exists in profile region - if code == "BucketAlreadyOwnedByYou": - pass - # Bucket exists in another region - elif code == "IllegalLocationConstraintException" and bucket.creation_date: - pass - else: - raise e return bucket_name From 9bc5f50b19d7e7b2e5d459b00f482a6a162f2fcd Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Mon, 5 Oct 2020 15:56:05 -0700 Subject: [PATCH 0198/1165] infra: change check for s3 bucket exists (#164) --- test/integ_tests/conftest.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py index a4cb2bf2..1a3b63c4 100644 --- a/test/integ_tests/conftest.py +++ b/test/integ_tests/conftest.py @@ -15,6 +15,7 @@ import boto3 import pytest +from botocore.exceptions import ClientError from braket.aws.aws_session import AwsSession @@ -36,15 +37,32 @@ def s3_resource(boto_session): @pytest.fixture(scope="session") -def s3_bucket(s3_resource, boto_session): +def s3_client(boto_session): + return boto_session.client("s3") + + +@pytest.fixture(scope="session") +def account_id(boto_session): + return boto_session.client("sts").get_caller_identity()["Account"] + + +@pytest.fixture(scope="session") +def s3_bucket(s3_resource, s3_client, account_id, boto_session): """Create / get S3 bucket for tests""" region_name = boto_session.region_name - account_id = boto_session.client("sts").get_caller_identity()["Account"] - bucket_name = f"amazon-braket-sdk-integ-tests-{account_id}" + bucket_name = f"amazon-braket-ocean-plugin-integ-tests-{account_id}" bucket = s3_resource.Bucket(bucket_name) - if not bucket.creation_date: - bucket.create(ACL="private", CreateBucketConfiguration={"LocationConstraint": region_name}) + + try: + # Determine if bucket exists + s3_client.head_bucket(Bucket=bucket_name) + except ClientError as e: + error_code = e.response["Error"]["Code"] + if error_code == "404": + bucket.create( + ACL="private", CreateBucketConfiguration={"LocationConstraint": region_name} + ) return bucket_name From e58881013dae575ce5e4f7c5c936abede2791bd3 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 5 Oct 2020 23:18:49 +0000 Subject: [PATCH 0199/1165] prepare release v1.1.2.post0 --- 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 be5561fd..6d0aacc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.1.2.post0 (2020-10-05) + +### Testing and Release Infrastructure + + * change check for s3 bucket exists + * change bucket creation setup for integ tests + ## v1.1.2 (2020-10-02) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 94ea4654..0d00201c 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.1.3.dev0" +__version__ = "1.1.2.post0" From 1c7219ac6bfd0934e2a74238c01a1e70e955922f Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 5 Oct 2020 23:18:49 +0000 Subject: [PATCH 0200/1165] update development version to v1.1.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 0d00201c..94ea4654 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.1.2.post0" +__version__ = "1.1.3.dev0" From 461dce93635868c104963d8634a2f17cd19f9d2e Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Wed, 14 Oct 2020 11:03:52 -0700 Subject: [PATCH 0201/1165] documentation: add sample notebooks link (#166) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ef00641..f489b235 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ Specify which quantum computer hardware to use by changing the value of the `dev If you want to use [Ocean](https://docs.ocean.dwavesys.com/en/latest/) with the D-Wave QPU, you can install the [amazon-braket-ocean-plugin-python](https://github.com/aws/amazon-braket-ocean-plugin-python). Information about how to install the plugin is provided in the [README](https://github.com/aws/amazon-braket-ocean-plugin-python/blob/master/README.md) for the repo. ## Sample Notebooks -Coming soon +Sample Jupyter notebooks can be found in the [amazon-braket-examples](https://github.com/aws/amazon-braket-examples/) repo. ## Braket Python SDK API Reference Documentation From 43ca6c971060e2477f054bc61d011b686c7f7669 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 15 Oct 2020 18:08:41 +0000 Subject: [PATCH 0202/1165] prepare release v1.1.2.post1 --- 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 6d0aacc7..613229c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.1.2.post1 (2020-10-15) + +### Documentation Changes + + * add sample notebooks link + ## v1.1.2.post0 (2020-10-05) ### Testing and Release Infrastructure diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 94ea4654..f9c57105 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.1.3.dev0" +__version__ = "1.1.2.post1" From 9fbc39ab2d7bc4fbc32382f4dacccdbf80c4b152 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 15 Oct 2020 18:08:41 +0000 Subject: [PATCH 0203/1165] update development version to v1.1.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 f9c57105..94ea4654 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.1.2.post1" +__version__ = "1.1.3.dev0" From 29ccb5a17dbc05e2a5f8e9e5da23b9410eec1fbe Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Tue, 20 Oct 2020 10:23:42 -0700 Subject: [PATCH 0204/1165] fix: add observable targets not in instructions to circuit qubit count and qubits (#167) --- src/braket/circuits/circuit.py | 17 ++++-- .../gate_model_device_testing_utils.py | 15 +++++ .../test_local_braket_simulator.py | 6 ++ .../test_simulator_quantum_task.py | 11 ++++ .../braket/circuits/test_circuit.py | 58 +++++++++++++++++++ 5 files changed, 102 insertions(+), 5 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index d2e30539..b98d719b 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -111,6 +111,7 @@ def __init__(self, addable: AddableTypes = None, *args, **kwargs): self._result_types: List[ResultType] = [] self._qubit_observable_mapping: Dict[Union[int, Circuit._ALL_QUBITS], Observable] = {} self._qubit_target_mapping: Dict[int, List[int]] = {} + self._qubit_observable_set = set() if addable is not None: self.add(addable, *args, **kwargs) @@ -179,18 +180,19 @@ def _observable_to_instruction(observable: Observable, target_list: List[int]): @property def moments(self) -> Moments: - """Moments: Get the `moments` for this circuit.""" + """Moments: Get the `moments` for this circuit. Note that this includes observables.""" return self._moments @property def qubit_count(self) -> int: - """Get the qubit count for this circuit.""" - return self._moments.qubit_count + """Get the qubit count for this circuit. Note that this includes observables.""" + all_qubits = self._moments.qubits.union(self._qubit_observable_set) + return len(all_qubits) @property def qubits(self) -> QubitSet: """QubitSet: Get a copy of the qubits for this circuit.""" - return QubitSet(self._moments.qubits) + return QubitSet(self._moments.qubits.union(self._qubit_observable_set)) def add_result_type( self, @@ -260,8 +262,9 @@ def add_result_type( result_type_to_add = result_type.copy(target=target) if result_type_to_add not in self._result_types: - self._add_to_qubit_observable_mapping(result_type) + self._add_to_qubit_observable_mapping(result_type_to_add) self._result_types.append(result_type_to_add) + self._add_to_qubit_observable_set(result_type_to_add) return self def _add_to_qubit_observable_mapping(self, result_type: ResultType) -> None: @@ -298,6 +301,10 @@ def _add_to_qubit_observable_mapping(self, result_type: ResultType) -> None: if not result_type.target: self._qubit_observable_mapping[Circuit._ALL_QUBITS] = observable + 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 add_instruction( self, instruction: Instruction, diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index 0e12286c..44767e9e 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -52,6 +52,21 @@ def no_result_types_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any] assert len(result.measurements) == shots +def result_types_observable_not_in_instructions(device: Device, run_kwargs: Dict[str, Any]): + shots = run_kwargs["shots"] + tol = get_tol(shots) + bell = ( + Circuit() + .h(0) + .cnot(0, 1) + .expectation(observable=Observable.X(), target=[2]) + .variance(observable=Observable.Y(), target=[3]) + ) + result = device.run(bell, **run_kwargs).result() + assert np.allclose(result.values[0], 0, **tol) + assert np.allclose(result.values[1], 1, **tol) + + def result_types_zero_shots_bell_pair_testing( device: Device, include_state_vector: bool, run_kwargs: Dict[str, Any] ): diff --git a/test/integ_tests/test_local_braket_simulator.py b/test/integ_tests/test_local_braket_simulator.py index b5da94a7..e00642ff 100644 --- a/test/integ_tests/test_local_braket_simulator.py +++ b/test/integ_tests/test_local_braket_simulator.py @@ -21,6 +21,7 @@ result_types_bell_pair_marginal_probability_testing, result_types_hermitian_testing, result_types_nonzero_shots_bell_pair_testing, + result_types_observable_not_in_instructions, result_types_tensor_hermitian_hermitian_testing, result_types_tensor_x_y_testing, result_types_tensor_y_hermitian_testing, @@ -104,3 +105,8 @@ def test_result_types_tensor_y_hermitian(shots): @pytest.mark.parametrize("shots", [0, SHOTS]) def test_result_types_all_selected(shots): result_types_all_selected_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_observable_not_in_instructions(shots): + result_types_observable_not_in_instructions(DEVICE, {"shots": shots}) diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 994e711f..f34e3076 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -21,6 +21,7 @@ result_types_bell_pair_marginal_probability_testing, result_types_hermitian_testing, result_types_nonzero_shots_bell_pair_testing, + result_types_observable_not_in_instructions, result_types_tensor_hermitian_hermitian_testing, result_types_tensor_x_y_testing, result_types_tensor_y_hermitian_testing, @@ -156,3 +157,13 @@ def test_result_types_all_selected(simulator_arn, shots, aws_session, s3_destina result_types_all_selected_testing( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) + + +@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) +def test_result_types_observable_not_in_instructions( + simulator_arn, shots, aws_session, s3_destination_folder +): + device = AwsDevice(simulator_arn, aws_session) + result_types_observable_not_in_instructions( + device, {"shots": shots, "s3_destination_folder": s3_destination_folder} + ) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 8952bfae..f48d8d75 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -608,6 +608,64 @@ def test_qubit_count_setter(h): h.qubit_count = 1 +@pytest.mark.parametrize( + "circuit,expected_qubit_count", + [ + (Circuit().h(0).h(1).h(2), 3), + ( + Circuit() + .h(0) + .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + .sample(observable=Observable.H() @ Observable.X(), target=[0, 1]), + 2, + ), + ( + Circuit().h(0).probability([1, 2]).state_vector(), + 1, + ), + ( + Circuit() + .h(0) + .variance(observable=Observable.H(), target=1) + .state_vector() + .amplitude(["01"]), + 2, + ), + ], +) +def test_qubit_count(circuit, expected_qubit_count): + assert circuit.qubit_count == expected_qubit_count + + +@pytest.mark.parametrize( + "circuit,expected_qubits", + [ + (Circuit().h(0).h(1).h(2), QubitSet([0, 1, 2])), + ( + Circuit() + .h(0) + .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + .sample(observable=Observable.H() @ Observable.X(), target=[0, 1]), + QubitSet([0, 1]), + ), + ( + Circuit().h(0).probability([1, 2]).state_vector(), + QubitSet([0]), + ), + ( + Circuit() + .h(0) + .variance(observable=Observable.H(), target=1) + .state_vector() + .amplitude(["01"]), + QubitSet([0, 1]), + ), + ], +) +def test_circuit_qubits(circuit, expected_qubits): + assert circuit.qubits == expected_qubits + + def test_qubits_getter(h): assert h.qubits == h._moments.qubits assert h.qubits is not h._moments.qubits From 3b781ef511d1924c3f06f6135ba6e75684397c7b Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 20 Oct 2020 18:08:37 +0000 Subject: [PATCH 0205/1165] prepare release v1.1.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 613229c4..2eb8acdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.1.3 (2020-10-20) + +### Bug Fixes and Other Changes + + * add observable targets not in instructions to circuit qubit count and qubits + ## v1.1.2.post1 (2020-10-15) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 94ea4654..88e3f3e8 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.1.3.dev0" +__version__ = "1.1.3" From 5a1395e9df5eb8ae4975434822418ad3792c6660 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 20 Oct 2020 18:08:37 +0000 Subject: [PATCH 0206/1165] update development version to v1.1.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 88e3f3e8..f9f54e9d 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.1.3" +__version__ = "1.1.4.dev0" From b3defb554def8a5a73ae0fd5adce1a05e6e8d8f7 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Wed, 28 Oct 2020 16:15:11 -0700 Subject: [PATCH 0207/1165] change: Add optimization to only poll during execution window (#169) --- src/braket/aws/aws_quantum_task.py | 76 +++++++- .../braket/aws/test_aws_quantum_task.py | 174 +++++++++++++++++- 2 files changed, 237 insertions(+), 13 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 2f118436..ea828264 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -14,10 +14,12 @@ from __future__ import annotations import asyncio +import json import time +from datetime import datetime from functools import singledispatch from logging import Logger, getLogger -from typing import Any, Dict, Union +from typing import Any, Dict, List, Union import boto3 @@ -25,7 +27,7 @@ from braket.aws.aws_session import AwsSession from braket.circuits.circuit import Circuit from braket.circuits.circuit_helpers import validate_circuit_and_shots -from braket.device_schema import GateModelParameters +from braket.device_schema import DeviceExecutionWindow, ExecutionDay, GateModelParameters from braket.device_schema.dwave import DwaveDeviceParameters from braket.device_schema.ionq import IonqDeviceParameters from braket.device_schema.rigetti import RigettiDeviceParameters @@ -120,8 +122,9 @@ def __init__( self, arn: str, aws_session: AwsSession = None, - poll_timeout_seconds: int = DEFAULT_RESULTS_POLL_TIMEOUT, - poll_interval_seconds: int = DEFAULT_RESULTS_POLL_INTERVAL, + poll_timeout_seconds: float = DEFAULT_RESULTS_POLL_TIMEOUT, + poll_interval_seconds: float = DEFAULT_RESULTS_POLL_INTERVAL, + poll_outside_execution_window: bool = False, logger: Logger = getLogger(__name__), ): """ @@ -130,9 +133,12 @@ def __init__( aws_session (AwsSession, optional): The `AwsSession` for connecting to AWS services. Default is `None`, in which case an `AwsSession` object will be created with the region of the task. - poll_timeout_seconds (int): The polling timeout for result(), default is 120 seconds. - poll_interval_seconds (int): The polling interval for result(), default is 0.25 + poll_timeout_seconds (float): The polling timeout for result(), default is 120 seconds. + poll_interval_seconds (float): The polling interval for result(), default is 0.25 seconds. + poll_outside_execution_window (bool): Whether or not to poll for result() when the + current time is outside of the execution window for the associated device, + default is False. Tasks are expected to only run during the execution window. logger (Logger): Logger object with which to write logs, such as task statuses while waiting for task to be in a terminal state. Default is `getLogger(__name__)` @@ -154,9 +160,12 @@ def __init__( ) self._poll_timeout_seconds = poll_timeout_seconds self._poll_interval_seconds = poll_interval_seconds + self._poll_outside_execution_window = poll_outside_execution_window + self._logger = logger self._metadata: Dict[str, Any] = {} + self._device_execution_windows = None self._result: Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult] = None @staticmethod @@ -298,11 +307,23 @@ async def _wait_for_completion( Note: Timeout and sleep intervals are defined in the constructor fields `poll_timeout_seconds` and `poll_interval_seconds` respectively. + If `poll_outside_execution_window` is set to `False`, it will + not poll the API for the current task status when the current time + is outside of the associated device's execution window. """ self._logger.debug(f"Task {self._arn}: start polling for completion") start_time = time.time() while (time.time() - start_time) < self._poll_timeout_seconds: + is_polling_time = self._is_polling_time() + if not is_polling_time: + self._logger.debug( + f"Task {self._arn}: Current time {datetime.now()} is outside of" + f" associated device's execution windows " + f"{self._get_device_execution_windows()}. Skipping polling for " + f" now." + ) + continue current_metadata = self.metadata() task_status = current_metadata["status"] self._logger.debug(f"Task {self._arn}: task status {task_status}") @@ -332,6 +353,49 @@ async def _wait_for_completion( self._result = None return None + def _is_polling_time(self) -> bool: + """ + Return if it's time to poll for result() + """ + if self._poll_outside_execution_window: + return True + device_execution_windows = self._get_device_execution_windows() + cur_time = datetime.utcnow() + for window in device_execution_windows: + day_no = cur_time.weekday() + day_of_week = cur_time.strftime("%A") + if window.executionDay != ExecutionDay.EVERYDAY: + if window.executionDay == ExecutionDay.WEEKDAYS and day_no >= 5: + continue + if window.executionDay == ExecutionDay.WEEKENDS and day_no < 5: + continue + if ( + window.executionDay not in [ExecutionDay.WEEKENDS, ExecutionDay.WEEKDAYS] + and window.executionDay != day_of_week + ): + continue + if ( + cur_time.time() >= window.windowStartHour + and cur_time.time() <= window.windowEndHour + ): + return True + return False + + def _get_device_execution_windows(self) -> List[DeviceExecutionWindow]: + """Returns the device execution windows""" + if not self._device_execution_windows: + device_arn = self.metadata(use_cached_value=True).get("deviceArn") + if not device_arn: + device_arn = self.metadata(use_cached_value=False).get("deviceArn") + device_capabilities = json.loads( + self._aws_session.get_device(device_arn)["deviceCapabilities"] + ) + self._device_execution_windows = [ + DeviceExecutionWindow.parse_obj(window) + for window in device_capabilities["service"]["executionWindows"] + ] + return self._device_execution_windows + def __repr__(self) -> str: return f"AwsQuantumTask('id':{self.id})" 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 2a771a0f..43c1361f 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -12,8 +12,10 @@ # language governing permissions and limitations under the License. import asyncio +import json import threading import time +from datetime import datetime from unittest.mock import MagicMock, Mock, patch import pytest @@ -23,7 +25,7 @@ from braket.aws import AwsQuantumTask from braket.aws.aws_session import AwsSession from braket.circuits import Circuit -from braket.device_schema import GateModelParameters +from braket.device_schema import DeviceExecutionWindow, GateModelParameters from braket.device_schema.dwave import DwaveDeviceParameters from braket.device_schema.ionq import IonqDeviceParameters from braket.device_schema.rigetti import RigettiDeviceParameters @@ -42,17 +44,23 @@ def aws_session(): @pytest.fixture def quantum_task(aws_session): - return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) + return AwsQuantumTask( + "foo:bar:arn", aws_session, poll_timeout_seconds=2, poll_outside_execution_window=True + ) @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=2, poll_outside_execution_window=True + ) @pytest.fixture def annealing_task(aws_session): - return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) + return AwsQuantumTask( + "foo:bar:arn", aws_session, poll_timeout_seconds=2, poll_outside_execution_window=True + ) @pytest.fixture @@ -101,6 +109,144 @@ def test_no_id_setter(quantum_task): quantum_task.id = 123 +def test_get_device_execution_windows_exists(quantum_task): + mock_windows = ["mock"] + quantum_task._device_execution_windows = mock_windows + assert quantum_task._get_device_execution_windows() == mock_windows + assert not quantum_task._aws_session.get_quantum_task.called + + +def test_get_device_execution_windows_device_arn_exists(quantum_task): + mock_arn = "mock" + window = { + "executionDay": "Everyday", + "windowStartHour": "00:00:00", + "windowEndHour": "23:00:00", + } + quantum_task._metadata = {"deviceArn": mock_arn} + quantum_task._aws_session.get_device.return_value = { + "deviceCapabilities": json.dumps({"service": {"executionWindows": [window]}}) + } + assert quantum_task._get_device_execution_windows() == [DeviceExecutionWindow.parse_obj(window)] + assert not quantum_task._aws_session.get_quantum_task.called + quantum_task._aws_session.get_device.assert_called_with(mock_arn) + + +def test_get_device_execution_windows_not_exists(quantum_task): + mock_arn = "mock" + window = { + "executionDay": "Everyday", + "windowStartHour": "00:00:00", + "windowEndHour": "23:00:00", + } + quantum_task._metadata = {} + quantum_task._aws_session.get_quantum_task.return_value = {"deviceArn": mock_arn} + quantum_task._aws_session.get_device.return_value = { + "deviceCapabilities": json.dumps({"service": {"executionWindows": [window]}}) + } + assert quantum_task._get_device_execution_windows() == [DeviceExecutionWindow.parse_obj(window)] + quantum_task._aws_session.get_quantum_task.assert_called_with(quantum_task.id) + quantum_task._aws_session.get_device.assert_called_with(mock_arn) + + +@pytest.mark.parametrize( + "window,cur_time,expected_val", + [ + ( + { + "executionDay": "Everyday", + "windowStartHour": "00:00:00", + "windowEndHour": "00:01:00", + }, + "10/08/2020 00:00:32", + True, + ), + ( + { + "executionDay": "Everyday", + "windowStartHour": "00:00:00", + "windowEndHour": "00:01:00", + }, + "10/08/2020 00:02:00", + False, + ), + ( + { + "executionDay": "Weekdays", + "windowStartHour": "00:00:00", + "windowEndHour": "00:01:00", + }, + "10/27/2020 00:00:32", + True, + ), + ( + { + "executionDay": "Weekdays", + "windowStartHour": "00:00:00", + "windowEndHour": "00:01:00", + }, + "10/25/2020 00:00:32", + False, + ), + ( + { + "executionDay": "Weekend", + "windowStartHour": "00:00:00", + "windowEndHour": "00:01:00", + }, + "10/27/2020 00:00:32", + False, + ), + ( + { + "executionDay": "Weekend", + "windowStartHour": "00:00:00", + "windowEndHour": "00:01:00", + }, + "10/25/2020 00:00:32", + True, + ), + ( + { + "executionDay": "Tuesday", + "windowStartHour": "00:00:00", + "windowEndHour": "00:01:00", + }, + "10/27/2020 00:00:32", + True, + ), + ( + { + "executionDay": "Monday", + "windowStartHour": "00:00:00", + "windowEndHour": "00:01:00", + }, + "10/27/2020 00:00:32", + False, + ), + ], +) +def test_is_polling_time(window, cur_time, expected_val, quantum_task): + quantum_task._poll_outside_execution_window = False + quantum_task._device_execution_windows = [DeviceExecutionWindow.parse_obj(window)] + with patch("braket.aws.aws_quantum_task.datetime") as mock_datetime: + mock_datetime.utcnow.return_value = datetime.strptime(cur_time, "%m/%d/%Y %H:%M:%S") + assert quantum_task._is_polling_time() == expected_val + + +def test_result_not_polling(quantum_task): + quantum_task._poll_outside_execution_window = False + quantum_task._poll_timeout_seconds = 0.01 + window = { + "executionDay": "Everyday", + "windowStartHour": "00:00:00", + "windowEndHour": "00:00:00", + } + quantum_task._device_execution_windows = [DeviceExecutionWindow.parse_obj(window)] + quantum_task.result() + assert not quantum_task._aws_session.get_quantum_task.called + + def test_metadata(quantum_task): metadata_1 = {"status": "RUNNING"} quantum_task._aws_session.get_quantum_task.return_value = metadata_1 @@ -248,7 +394,11 @@ def test_timeout_completed(aws_session): # Setup the poll timing such that the timeout will occur after one API poll quantum_task = AwsQuantumTask( - "foo:bar:arn", aws_session, poll_timeout_seconds=0.5, poll_interval_seconds=1 + "foo:bar:arn", + aws_session, + poll_timeout_seconds=0.5, + poll_interval_seconds=1, + poll_outside_execution_window=True, ) assert quantum_task.result() is None _mock_metadata(aws_session, "COMPLETED") @@ -264,7 +414,11 @@ def test_timeout_no_result_terminal_state(aws_session): # Setup the poll timing such that the timeout will occur after one API poll quantum_task = AwsQuantumTask( - "foo:bar:arn", aws_session, poll_timeout_seconds=0.5, poll_interval_seconds=1 + "foo:bar:arn", + aws_session, + poll_timeout_seconds=0.5, + poll_interval_seconds=1, + poll_outside_execution_window=True, ) assert quantum_task.result() is None @@ -334,7 +488,13 @@ def test_from_annealing(device_parameters, aws_session, arn, problem): mocked_task_arn = "task-arn-1" aws_session.create_quantum_task.return_value = mocked_task_arn task = AwsQuantumTask.create( - aws_session, arn, problem, S3_TARGET, 1000, device_parameters=device_parameters + aws_session, + arn, + problem, + S3_TARGET, + 1000, + device_parameters=device_parameters, + poll_outside_execution_window=True, ) assert task == AwsQuantumTask( mocked_task_arn, aws_session, AnnealingQuantumTaskResult.from_string From f2003ade03909edee43978b6acd19378e7ef0c70 Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Thu, 29 Oct 2020 09:13:33 -0700 Subject: [PATCH 0208/1165] change: Enable simultaneous measurement of observables with shared factors (#168) --- src/braket/circuits/circuit.py | 116 ++++++++++-------- .../braket/circuits/test_circuit.py | 97 ++++++++++++++- 2 files changed, 163 insertions(+), 50 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index b98d719b..1bdb745c 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -13,7 +13,7 @@ from __future__ import annotations -from typing import Callable, Dict, Iterable, List, TypeVar, Union +from typing import Callable, Dict, Iterable, List, Tuple, TypeVar, Union from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram from braket.circuits.instruction import Instruction @@ -110,7 +110,7 @@ def __init__(self, addable: AddableTypes = None, *args, **kwargs): self._moments: Moments = Moments() self._result_types: List[ResultType] = [] self._qubit_observable_mapping: Dict[Union[int, Circuit._ALL_QUBITS], Observable] = {} - self._qubit_target_mapping: Dict[int, List[int]] = {} + self._qubit_target_mapping: Dict[int, Tuple[int]] = {} self._qubit_observable_set = set() if addable is not None: @@ -140,43 +140,25 @@ 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 = [] - observable_return_types = ( - result_type - for result_type in self._result_types - if isinstance(result_type, ObservableResultType) - ) - - added_observables_targets = set() - for return_type in observable_return_types: - observable: Observable = return_type.observable - targets: List[List[int]] = ( - [list(return_type.target)] - if return_type.target - else [list([qubit]) for qubit in self._moments.qubits] - ) - - for target in targets: - # only add gates for observables and targets that - # have not been processed - str_observables_target = f"{observable}; {target}" - if str_observables_target in added_observables_targets: - continue - added_observables_targets.add(str_observables_target) + all_qubit_observable = self._qubit_observable_mapping.get(Circuit._ALL_QUBITS) + if all_qubit_observable: + for target in self.qubits: basis_rotation_instructions += Circuit._observable_to_instruction( - observable, target + all_qubit_observable, target ) + return basis_rotation_instructions + + target_lists = sorted(list(set(self._qubit_target_mapping.values()))) + for target_list in target_lists: + observable = self._qubit_observable_mapping[target_list[0]] + basis_rotation_instructions += Circuit._observable_to_instruction( + observable, target_list + ) return basis_rotation_instructions @staticmethod def _observable_to_instruction(observable: Observable, target_list: List[int]): - if isinstance(observable, TensorProduct): - instructions = [] - for factor in observable.factors: - target = [target_list.pop(0) for _ in range(factor.qubit_count)] - instructions += Circuit._observable_to_instruction(factor, target) - return instructions - else: - return [Instruction(gate, target_list) for gate in observable.basis_rotation_gates] + return [Instruction(gate, target_list) for gate in observable.basis_rotation_gates] @property def moments(self) -> Moments: @@ -263,8 +245,8 @@ def add_result_type( if result_type_to_add not in self._result_types: self._add_to_qubit_observable_mapping(result_type_to_add) - self._result_types.append(result_type_to_add) self._add_to_qubit_observable_set(result_type_to_add) + self._result_types.append(result_type_to_add) return self def _add_to_qubit_observable_mapping(self, result_type: ResultType) -> None: @@ -274,33 +256,71 @@ def _add_to_qubit_observable_mapping(self, result_type: ResultType) -> None: observable = result_type.observable else: return - - targets = result_type.target or self._qubit_observable_mapping.keys() + targets = result_type.target or list(self._qubit_observable_set) all_qubits_observable = self._qubit_observable_mapping.get(Circuit._ALL_QUBITS) - for target in targets: + for i in range(len(targets)): + target = targets[i] + tensor_product_dict = ( + Circuit._tensor_product_index_dict(observable) + if isinstance(observable, TensorProduct) + else None + ) + new_observable = tensor_product_dict[i][0] if tensor_product_dict else observable current_observable = all_qubits_observable or self._qubit_observable_mapping.get(target) - current_target = self._qubit_target_mapping.get(target) - if current_observable and current_observable != observable: + if current_observable and current_observable != new_observable: raise ValueError( f"Existing result type for observable {current_observable} for target {target}" - f" conflicts with observable {observable} for new result type" + f" conflicts with observable {new_observable} for new result type" ) if result_type.target: - # The only way this can happen is if the observables (acting on multiple target - # qubits) and target qubits are the same, but the new target is the wrong order; - if current_target and current_target != targets: - raise ValueError( - f"Target order {current_target} of existing result type with observable" - f" {current_observable} conflicts with order {targets} of new result type" + if new_observable.qubit_count > 1: + new_targets = ( + tuple( + result_type.target[ + tensor_product_dict[i][1][0] : tensor_product_dict[i][1][1] + ] + ) + if tensor_product_dict + else tuple(result_type.target) ) - self._qubit_observable_mapping[target] = observable - self._qubit_target_mapping[target] = targets + current_target = self._qubit_target_mapping.get(target) + if current_target and current_target != new_targets: + raise ValueError( + f"Target order {current_target} of existing result type with observable" + f" {current_observable} conflicts with order {targets} of new" + " result type" + ) + self._qubit_target_mapping[target] = new_targets + else: + self._qubit_target_mapping[target] = tuple([target]) + self._qubit_observable_mapping[target] = new_observable if not result_type.target: + if all_qubits_observable and all_qubits_observable != observable: + raise ValueError( + f"Existing result type for observable {all_qubits_observable} for all qubits" + f" conflicts with observable {observable} for new result type" + ) self._qubit_observable_mapping[Circuit._ALL_QUBITS] = observable + @staticmethod + def _tensor_product_index_dict(observable: TensorProduct) -> Dict[int, Observable]: + obj_dict = {} + i = 0 + factors = list(observable.factors) + total = factors[0].qubit_count + while factors: + if i >= total: + factors.pop(0) + if factors: + total += factors[0].qubit_count + if factors: + obj_dict[i] = (factors[0], (total - factors[0].qubit_count, total)) + i += 1 + return obj_dict + 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) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index f48d8d75..d59b0d72 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -194,7 +194,7 @@ def test_add_result_type_observable_no_conflict_state_vector_obs_return_value(): @pytest.mark.xfail(raises=ValueError) -def test_add_result_type_same_observable_wrong_target_order(): +def test_add_result_type_same_observable_wrong_target_order_tensor_product(): Circuit().add_result_type( ResultType.Expectation(observable=Observable.Y() @ Observable.X(), target=[0, 1]) ).add_result_type( @@ -202,6 +202,16 @@ def test_add_result_type_same_observable_wrong_target_order(): ) +@pytest.mark.xfail(raises=ValueError) +def test_add_result_type_same_observable_wrong_target_order_hermitian(): + array = np.eye(4) + Circuit().add_result_type( + ResultType.Expectation(observable=Observable.Hermitian(matrix=array), target=[0, 1]) + ).add_result_type( + ResultType.Variance(observable=Observable.Hermitian(matrix=array), target=[1, 0]) + ) + + @pytest.mark.xfail(raises=TypeError) def test_add_result_type_with_target_and_mapping(prob): Circuit().add_result_type(prob, target=[10], target_mapping={0: 10}) @@ -475,6 +485,26 @@ def test_basis_rotation_instructions_tensor_product(): assert circ.basis_rotation_instructions == expected +def test_basis_rotation_instructions_tensor_product_commuting(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .expectation(observable=Observable.X() @ Observable.Y() @ Observable.Y(), target=[0, 1, 2]) + .expectation(observable=Observable.X() @ Observable.Y(), target=[0, 1]) + ) + expected = [ + Instruction(Gate.H(), 0), + Instruction(Gate.Z(), 1), + Instruction(Gate.S(), 1), + Instruction(Gate.H(), 1), + Instruction(Gate.Z(), 2), + Instruction(Gate.S(), 2), + Instruction(Gate.H(), 2), + ] + assert circ.basis_rotation_instructions == expected + + def test_basis_rotation_instructions_multiple_result_types_different_targets(): circ = ( Circuit() @@ -547,13 +577,76 @@ def test_basis_rotation_instructions_multiple_result_types_different_hermitian_t .expectation(observable=Observable.Hermitian(matrix=np.array([[0, 1], [1, 0]])), target=[0]) ) expected = [ - Instruction(Gate.Unitary(matrix=np.array([[0, 1], [1, 0]])), target=[1]), Instruction( Gate.Unitary( matrix=1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) ), target=[0], ), + Instruction(Gate.Unitary(matrix=np.array([[0, 1], [1, 0]])), target=[1]), + ] + assert circ.basis_rotation_instructions == expected + + +def test_basis_rotation_instructions_multiple_result_types_tensor_product_hermitian(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .cnot(1, 2) + .sample( + observable=Observable.Hermitian(matrix=np.array([[1, 0], [0, -1]])) @ Observable.H(), + target=[0, 1], + ) + .variance( + observable=Observable.Hermitian(matrix=np.array([[1, 0], [0, -1]])) @ Observable.H(), + target=[0, 1], + ) + .expectation(observable=Observable.Hermitian(matrix=np.array([[0, 1], [1, 0]])), target=[2]) + ) + expected = [ + Instruction(Gate.Unitary(matrix=np.array([[0, 1], [1, 0]])), target=[0]), + Instruction(Gate.Ry(-np.pi / 4), 1), + Instruction( + Gate.Unitary( + matrix=1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) + ), + target=[2], + ), + ] + assert circ.basis_rotation_instructions == expected + + +def test_basis_rotation_instructions_multiple_result_types_tensor_product_hermitian_qubit_count_2(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .cnot(1, 2) + .sample( + observable=Observable.Hermitian(matrix=np.eye(4)) @ Observable.H(), target=[0, 1, 2] + ) + .variance(observable=Observable.H(), target=[2]) + ) + expected = [ + Instruction(Gate.Unitary(matrix=np.eye(4)), target=[0, 1]), + Instruction(Gate.Ry(-np.pi / 4), 2), + ] + assert circ.basis_rotation_instructions == expected + + +def test_basis_rotation_instructions_multiple_result_types_tensor_product_probability(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .cnot(1, 2) + .probability([0, 1]) + .sample(observable=Observable.Z() @ Observable.Z() @ Observable.H(), target=[0, 1, 2]) + .variance(observable=Observable.H(), target=[2]) + ) + expected = [ + Instruction(Gate.Ry(-np.pi / 4), 2), ] assert circ.basis_rotation_instructions == expected From 339c3a4df007f627c2cb82eec929f4085608d9a2 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 29 Oct 2020 18:08:41 +0000 Subject: [PATCH 0209/1165] prepare release v1.1.4 --- 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 2eb8acdb..7ef78c44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.1.4 (2020-10-29) + +### Bug Fixes and Other Changes + + * Enable simultaneous measurement of observables with shared factors + * Add optimization to only poll during execution window + ## v1.1.3 (2020-10-20) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index f9f54e9d..6189f1d0 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.1.4.dev0" +__version__ = "1.1.4" From 0ce457b7f9918d288025c40be37ae957d0bf6526 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 29 Oct 2020 18:08:41 +0000 Subject: [PATCH 0210/1165] update development version to v1.1.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 6189f1d0..8b202f83 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.1.4" +__version__ = "1.1.5.dev0" From 61f89a035219443dbf5e9c46002c9295791d9adf Mon Sep 17 00:00:00 2001 From: Ava Wang <57644099+avawang1@users.noreply.github.com> Date: Thu, 29 Oct 2020 17:36:59 -0700 Subject: [PATCH 0211/1165] infra: update codeowners (#172) --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 7ce2839a..042801bb 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,4 +3,4 @@ # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, these accounts # will be requested for review when someone opens a pull request. -* @floralph @speller26 @avawang1 +* @floralph @speller26 From ddb83af15ee0f5f1348ec507abc48c25cd6dcf18 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 30 Oct 2020 18:08:34 +0000 Subject: [PATCH 0212/1165] prepare release v1.1.4.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 7ef78c44..05cfeefc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.1.4.post0 (2020-10-30) + +### Testing and Release Infrastructure + + * update codeowners + ## v1.1.4 (2020-10-29) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 8b202f83..3caeab54 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.1.5.dev0" +__version__ = "1.1.4.post0" From 94b1f2ce2ff9917531773aad74af05b394a92b63 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 30 Oct 2020 18:08:34 +0000 Subject: [PATCH 0213/1165] update development version to v1.1.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 3caeab54..8b202f83 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.1.4.post0" +__version__ = "1.1.5.dev0" From dc57bc7757980dc67cc9a11414ac0cf35d56fb3b Mon Sep 17 00:00:00 2001 From: Niveditha <69262004+nivedis1@users.noreply.github.com> Date: Fri, 30 Oct 2020 13:48:03 -0700 Subject: [PATCH 0214/1165] feature: support tags parameter for create method in AwsQuantumTask (#170) * feature: support tags parameter for create method in AwsQuantumTask * Update the documentation for tags --- src/braket/aws/aws_quantum_task.py | 7 +++ .../braket/aws/test_aws_quantum_task.py | 54 +++++++++++++++---- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index ea828264..348b9701 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -57,6 +57,7 @@ def create( s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, device_parameters: Dict[str, Any] = None, + tags: Dict[str, str] = None, *args, **kwargs, ) -> AwsQuantumTask: @@ -85,6 +86,10 @@ def create( For example, for D-Wave: `{"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}}` + tags (Dict[str, str]): Tags, which are Key-Value pairs to add to this quantum task. + An example would be: + `{"state": "washington"}` + Returns: AwsQuantumTask: AwsQuantumTask tracking the task execution on the device. @@ -108,6 +113,8 @@ def create( s3_destination_folder, shots if shots is not None else AwsQuantumTask.DEFAULT_SHOTS, ) + if tags is not None: + create_task_kwargs.update({"tags": tags}) return _create_internal( task_specification, aws_session, 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 43c1361f..5b3abc93 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -510,6 +510,37 @@ def test_from_annealing(device_parameters, aws_session, arn, problem): ) +@pytest.mark.parametrize( + "device_arn,device_parameters_class", + [ + ("device/qpu/ionq", IonqDeviceParameters), + ("device/qpu/rigetti", RigettiDeviceParameters), + ("device/quantum-simulator", GateModelSimulatorDeviceParameters), + ], +) +def test_create_with_tags(device_arn, device_parameters_class, aws_session, circuit): + mocked_task_arn = "task-arn-tags" + aws_session.create_quantum_task.return_value = mocked_task_arn + shots = 53 + tags = {"state": "washington"} + + task = AwsQuantumTask.create(aws_session, device_arn, circuit, S3_TARGET, shots, tags=tags) + assert task == AwsQuantumTask( + mocked_task_arn, aws_session, GateModelQuantumTaskResult.from_string + ) + _assert_create_quantum_task_called_with( + aws_session, + device_arn, + circuit, + S3_TARGET, + shots, + device_parameters_class( + paradigmParameters=GateModelParameters(qubitCount=circuit.qubit_count) + ), + tags, + ) + + def test_init_new_thread(aws_session, arn): tasks_list = [] threading.Thread(target=_init_and_add_to_list, args=(aws_session, arn, tasks_list)).start() @@ -534,18 +565,19 @@ def _init_and_add_to_list(aws_session, arn, task_list): def _assert_create_quantum_task_called_with( - aws_session, arn, task_description, s3_results_prefix, shots, device_parameters + aws_session, arn, task_description, s3_results_prefix, shots, device_parameters, tags=None ): - aws_session.create_quantum_task.assert_called_with( - **{ - "deviceArn": arn, - "outputS3Bucket": s3_results_prefix[0], - "outputS3KeyPrefix": s3_results_prefix[1], - "action": task_description.to_ir().json(), - "deviceParameters": device_parameters.json(), - "shots": shots, - } - ) + test_kwargs = { + "deviceArn": arn, + "outputS3Bucket": s3_results_prefix[0], + "outputS3KeyPrefix": s3_results_prefix[1], + "action": task_description.to_ir().json(), + "deviceParameters": device_parameters.json(), + "shots": shots, + } + if tags is not None: + test_kwargs.update({"tags": tags}) + aws_session.create_quantum_task.assert_called_with(**test_kwargs) def _mock_metadata(aws_session, state): From c65992fb1e2878fea069dd498d250a3846041464 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 2 Nov 2020 18:08:37 +0000 Subject: [PATCH 0215/1165] prepare release v1.2.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 05cfeefc..38f923f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.2.0 (2020-11-02) + +### Features + + * support tags parameter for create method in AwsQuantumTask + ## v1.1.4.post0 (2020-10-30) ### Testing and Release Infrastructure diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 8b202f83..c5f36a3c 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.1.5.dev0" +__version__ = "1.2.0" From 820923b03bacd92143781ae1431278fa0d2d9937 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 2 Nov 2020 18:08:37 +0000 Subject: [PATCH 0216/1165] update development version to v1.2.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 c5f36a3c..52b7b3a8 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.2.0" +__version__ = "1.2.1.dev0" From 67266974c51cfed49613b503378f748d32174b4c Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 20 Nov 2020 11:42:32 -0800 Subject: [PATCH 0217/1165] feature: Add support for batch execution (#173) Also: * Cache task results * Allow AwsSession to be initialized with Config * Unify poll defaults between AwsQuantumTask and AwsDevice * Remove TERMINAL_STATES from AwsQuantumTaskBatch since it's already been added to AwsQuantumTask --- README.md | 12 +- src/braket/aws/__init__.py | 1 + src/braket/aws/aws_device.py | 100 ++++++-- src/braket/aws/aws_quantum_task.py | 55 ++-- src/braket/aws/aws_quantum_task_batch.py | 240 ++++++++++++++++++ src/braket/aws/aws_session.py | 8 +- .../gate_model_device_testing_utils.py | 20 +- .../test_simulator_quantum_task.py | 9 + .../braket/aws/common_test_utils.py | 113 ++++++++- test/unit_tests/braket/aws/test_aws_device.py | 112 +++++++- .../braket/aws/test_aws_quantum_task.py | 17 +- .../braket/aws/test_aws_quantum_task_batch.py | 81 ++++++ .../unit_tests/braket/aws/test_aws_session.py | 18 +- 13 files changed, 711 insertions(+), 75 deletions(-) create mode 100644 src/braket/aws/aws_quantum_task_batch.py create mode 100644 test/unit_tests/braket/aws/test_aws_quantum_task_batch.py diff --git a/README.md b/README.md index f489b235..13d1ba6d 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,16 @@ print(task.result().measurement_counts) The code sample imports the Amazon Braket framework, then defines the device to use (the SV1 AWS simulator). The `s3_folder` statement defines the Amazon S3 bucket for the task result and the folder in the bucket to store the task result. This folder is created when you run the task. It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the job. This example can be found in `../examples/bell.py`. +### Running multiple tasks at once + +Many quantum algorithms need to run multiple independent circuits, and submitting the circuits in parallel can be faster than submitting them one at a time. In particular, parallel task processing provides a significant speed up when using simulator devices. The following example shows how to run a batch of tasks on SV1: + +```python +circuits = [bell for _ in range(5)] +batch = device.run_batch(circuits, s3_folder, shots=100) +print(batch.results()[0].measurement_counts) # The result of the first task in the batch +``` + ### Available Simulators Amazon Braket provides access to two simulators: a fully managed statevector simulator, SV1, and a local simulator that is part of the Amazon Braket SDK. @@ -163,7 +173,7 @@ pip install -e "amazon-braket-sdk-python[test]" tox -e unit-tests ``` -You can also pass in various pytest arguments `tox -e integ-tests -- your-arguments` to run selected tests. For more information, please see [pytest usage](https://docs.pytest.org/en/stable/usage.html). +You can also pass in various pytest arguments `tox -e unit-tests -- your-arguments` to run selected tests. For more information, please see [pytest usage](https://docs.pytest.org/en/stable/usage.html). To run linters and doc generators and unit tests diff --git a/src/braket/aws/__init__.py b/src/braket/aws/__init__.py index c99ecde3..3ad8bac3 100644 --- a/src/braket/aws/__init__.py +++ b/src/braket/aws/__init__.py @@ -13,4 +13,5 @@ from braket.aws.aws_device import AwsDevice, AwsDeviceType # noqa: F401 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 diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 78b66edd..a0f196c7 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -18,10 +18,12 @@ import boto3 from boltons.dictutils import FrozenDict +from botocore.config import Config from networkx import Graph, complete_graph, from_edgelist 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.circuits import Circuit from braket.device_schema import DeviceCapabilities, GateModelQpuParadigmProperties @@ -55,8 +57,6 @@ class AwsDevice(Device): DEFAULT_SHOTS_QPU = 1000 DEFAULT_SHOTS_SIMULATOR = 0 - DEFAULT_RESULTS_POLL_TIMEOUT = 432000 - DEFAULT_RESULTS_POLL_INTERVAL = 1 def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): """ @@ -86,8 +86,8 @@ def run( task_specification: Union[Circuit, Problem], s3_destination_folder: AwsSession.S3DestinationFolder, shots: Optional[int] = None, - poll_timeout_seconds: Optional[int] = DEFAULT_RESULTS_POLL_TIMEOUT, - poll_interval_seconds: Optional[int] = DEFAULT_RESULTS_POLL_INTERVAL, + poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, + poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTask: @@ -96,14 +96,14 @@ def run( annealing problem. Args: - task_specification (Union[Circuit, Problem]): Specification of task + task_specification (Union[Circuit, Problem]): Specification of task (circuit or annealing problem) to run on device. s3_destination_folder: The S3 location to save the task's results shots (int, optional): The number of times to run the circuit or annealing problem. Default is 1000 for QPUs and 0 for simulators. - poll_timeout_seconds (int): The polling timeout for AwsQuantumTask.result(), in seconds. - Default: 5 days. - poll_interval_seconds (int): The polling interval for AwsQuantumTask.result(), + poll_timeout_seconds (float): The polling timeout for AwsQuantumTask.result(), + in seconds. Default: 5 days. + poll_interval_seconds (float): The polling interval for AwsQuantumTask.result(), in seconds. Default: 1 second. *aws_quantum_task_args: Variable length positional arguments for `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. @@ -137,17 +137,66 @@ def run( See Also: `braket.aws.aws_quantum_task.AwsQuantumTask.create()` """ - if shots is None: - if "qpu" in self.arn: - shots = AwsDevice.DEFAULT_SHOTS_QPU - else: - shots = AwsDevice.DEFAULT_SHOTS_SIMULATOR return AwsQuantumTask.create( self._aws_session, self._arn, task_specification, s3_destination_folder, - shots, + shots if shots is not None else self._default_shots, + poll_timeout_seconds=poll_timeout_seconds, + poll_interval_seconds=poll_interval_seconds, + *aws_quantum_task_args, + **aws_quantum_task_kwargs, + ) + + def run_batch( + self, + task_specifications: List[Union[Circuit, Problem]], + s3_destination_folder: AwsSession.S3DestinationFolder, + shots: Optional[int] = None, + max_parallel: int = AwsQuantumTaskBatch.MAX_PARALLEL_DEFAULT, + 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, + *aws_quantum_task_args, + **aws_quantum_task_kwargs, + ) -> AwsQuantumTaskBatch: + """Executes a batch of tasks in parallel + + Args: + task_specifications (List[Union[Circuit, Problem]]): List of circuits + or annealing problems to run on device. + s3_destination_folder: The S3 location to save the tasks' results + shots (int, optional): The number of times to run the circuit or annealing problem. + Default is 1000 for QPUs and 0 for simulators. + max_parallel (int): The maximum number of tasks to run on AWS in parallel. + Batch creation will fail if this value is greater than the maximum allowed + concurrent tasks on the device. Default: 10 + max_connections (int): The maximum number of connections in the Boto3 connection pool. + Also the maximum number of thread pool workers for the batch. Default: 100 + poll_timeout_seconds (float): The polling timeout for AwsQuantumTask.result(), + in seconds. Default: 5 days. + poll_interval_seconds (float): The polling interval for results in seconds. + Default: 1 second. + *aws_quantum_task_args: Variable length positional arguments for + `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. + **aws_quantum_task_kwargs: Variable length keyword arguments for + `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. + + Returns: + AwsQuantumTaskBatch: A batch containing all of the tasks run + + See Also: + `braket.aws.aws_quantum_task_batch.AwsQuantumTaskBatch` + """ + return AwsQuantumTaskBatch( + AwsDevice._copy_aws_session(self._aws_session, max_connections=max_connections), + self._arn, + task_specifications, + s3_destination_folder, + shots if shots is not None else self._default_shots, + max_parallel=max_parallel, + max_workers=max_connections, poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds, *aws_quantum_task_args, @@ -235,6 +284,12 @@ def _construct_topology_graph(self) -> Graph: else: return None + @property + def _default_shots(self): + return ( + AwsDevice.DEFAULT_SHOTS_QPU if "qpu" in self.arn else AwsDevice.DEFAULT_SHOTS_SIMULATOR + ) + @staticmethod def _aws_session_for_device(device_arn: str, aws_session: Optional[AwsSession]) -> AwsSession: """AwsSession: Returns an AwsSession for the device ARN. """ @@ -257,9 +312,16 @@ def _aws_session_for_qpu(device_arn: str, aws_session: Optional[AwsSession]) -> return AwsDevice._copy_aws_session(aws_session, qpu_regions) @staticmethod - def _copy_aws_session(aws_session: Optional[AwsSession], regions: List[str]) -> AwsSession: + def _copy_aws_session( + aws_session: Optional[AwsSession], + regions: List[str] = None, + max_connections: Optional[int] = None, + ) -> AwsSession: + config = Config(max_pool_connections=max_connections) if max_connections else None if aws_session: - if aws_session.boto_session.region_name in regions: + session_region = aws_session.boto_session.region_name + new_regions = regions or [session_region] + if session_region in new_regions and not config: return aws_session else: creds = aws_session.boto_session.get_credentials() @@ -267,12 +329,12 @@ def _copy_aws_session(aws_session: Optional[AwsSession], regions: List[str]) -> aws_access_key_id=creds.access_key, aws_secret_access_key=creds.secret_key, aws_session_token=creds.token, - region_name=regions[0], + region_name=session_region if session_region in new_regions else new_regions[0], ) - return AwsSession(boto_session=boto_session) + return AwsSession(boto_session=boto_session, config=config) else: boto_session = boto3.Session(region_name=regions[0]) - return AwsSession(boto_session=boto_session) + return AwsSession(boto_session=boto_session, config=config) def __repr__(self): return "Device('name': {}, 'arn': {})".format(self.name, self.arn) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 348b9701..6795611f 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -44,9 +44,10 @@ class AwsQuantumTask(QuantumTask): # 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) - DEFAULT_RESULTS_POLL_TIMEOUT = 120 - DEFAULT_RESULTS_POLL_INTERVAL = 0.25 + DEFAULT_RESULTS_POLL_TIMEOUT = 432000 + DEFAULT_RESULTS_POLL_INTERVAL = 1 RESULTS_FILENAME = "results.json" @staticmethod @@ -140,9 +141,8 @@ def __init__( aws_session (AwsSession, optional): The `AwsSession` for connecting to AWS services. Default is `None`, in which case an `AwsSession` object will be created with the region of the task. - poll_timeout_seconds (float): The polling timeout for result(), default is 120 seconds. - poll_interval_seconds (float): The polling interval for result(), default is 0.25 - seconds. + poll_timeout_seconds (float): The polling timeout for result(). Default: 5 days. + poll_interval_seconds (float): The polling interval for result(). Default: 1 second. poll_outside_execution_window (bool): Whether or not to poll for result() when the current time is outside of the execution window for the associated device, default is False. Tasks are expected to only run during the execution window. @@ -239,7 +239,13 @@ def state(self, use_cached_value: bool = False) -> str: See Also: `metadata()` """ - return self.metadata(use_cached_value).get("status") + return self._status(use_cached_value) + + def _status(self, use_cached_value=False): + status = self.metadata(use_cached_value).get("status") + if not use_cached_value and status in self.NO_RESULT_TERMINAL_STATES: + self._logger.warning(f"Task is in terminal state {status} and no result is available") + return status def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: """ @@ -247,10 +253,17 @@ def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult Once the task is completed, the result is retrieved from S3 and returned as a `GateModelQuantumTaskResult` or `AnnealingQuantumTaskResult` - This method is a blocking thread call and synchronously returns a result. Call - async_result() if you require an asynchronous invocation. + This method is a blocking thread call and synchronously returns a result. + Call async_result() if you require an asynchronous invocation. Consecutive calls to this method return a cached result from the preceding request. + + Returns: + Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: The result of the task, + if the task completed successfully; returns None if the task did not complete + successfully or the future timed out. """ + if self._result or self._status(True) in self.NO_RESULT_TERMINAL_STATES: + return self._result try: async_result = self.async_result() return asyncio.get_event_loop().run_until_complete(async_result) @@ -266,19 +279,15 @@ def _get_future(self): self._logger.debug(e) self._logger.info("No event loop found; creating new event loop") asyncio.set_event_loop(asyncio.new_event_loop()) - if not hasattr(self, "_future"): self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) elif ( - self._future.done() and not self._future.cancelled() and self._result is None - ): # timed out and no result - task_status = self.metadata()["status"] - if task_status in self.NO_RESULT_TERMINAL_STATES: - self._logger.warning( - f"Task is in terminal state {task_status} and no result is available" - ) - else: - self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) + self._future.done() + and not self._future.cancelled() + and self._result is None + and self._status() not in self.NO_RESULT_TERMINAL_STATES # timed out and no result + ): + self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) return self._future def async_result(self) -> asyncio.Task: @@ -331,8 +340,11 @@ async def _wait_for_completion( f" now." ) continue - current_metadata = self.metadata() - task_status = current_metadata["status"] + # Used cached metadata if cached status is terminal + current_metadata = self.metadata( + self._status(False) not in AwsQuantumTask.TERMINAL_STATES + ) + task_status = self._status(False) self._logger.debug(f"Task {self._arn}: task status {task_status}") if task_status in AwsQuantumTask.RESULTS_READY_STATES: result_string = self._aws_session.retrieve_s3_object_body( @@ -342,9 +354,6 @@ async def _wait_for_completion( self._result = _format_result(BraketSchemaBase.parse_raw_schema(result_string)) return self._result elif task_status in AwsQuantumTask.NO_RESULT_TERMINAL_STATES: - self._logger.warning( - f"Task is in terminal state {task_status} and no result is available" - ) self._result = None return None else: diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py new file mode 100644 index 00000000..f475d471 --- /dev/null +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -0,0 +1,240 @@ +# Copyright 2019-2019 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 time +from concurrent.futures.thread import ThreadPoolExecutor +from queue import Queue +from typing import List, Set, Union + +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 + + +class AwsQuantumTaskBatch: + """Executes a batch of quantum tasks in parallel. + + Using this class can yield vast speedups over executing tasks sequentially, + and is particularly useful for computations that can be parallelized, + such as calculating quantum gradients or statistics of terms in a Hamiltonian. + + Note: there is no benefit to using this method with QPUs outside of their execution windows, + since results will not be available until the window opens. + """ + + MAX_PARALLEL_DEFAULT = 10 + MAX_CONNECTIONS_DEFAULT = 100 + + def __init__( + self, + aws_session: AwsSession, + device_arn: str, + task_specifications: List[Union[Circuit, Problem]], + s3_destination_folder: AwsSession.S3DestinationFolder, + shots: int, + max_parallel: int = MAX_PARALLEL_DEFAULT, + max_workers: int = MAX_CONNECTIONS_DEFAULT, + poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, + poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, + *aws_quantum_task_args, + **aws_quantum_task_kwargs, + ): + """Creates a batch of quantum tasks. + + Args: + aws_session (AwsSession): AwsSession to connect to AWS with. + device_arn (str): The ARN of the quantum device. + task_specification (Union[Circuit, Problem]): The specification of the task + to run on device. + 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 task results in. + shots (int): The number of times to run the task on the device. If the device is a + simulator, this implies the state is sampled N times, where N = `shots`. + `shots=0` is only available on simulators and means that the simulator + will compute the exact results based on the task specification. + max_parallel (int): The maximum number of tasks to run on AWS in parallel. + Batch creation will fail if this value is greater than the maximum allowed + concurrent tasks on the device. Default: 10 + max_workers (int): The maximum number of thread pool workers. Default: 100 + poll_timeout_seconds (float): The polling timeout for AwsQuantumTask.result(), + in seconds. Default: 5 days. + poll_interval_seconds (float): The polling interval for results in seconds. + Default: 1 second. + *aws_quantum_task_args: Variable length positional arguments for + `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. + **aws_quantum_task_kwargs: Variable length keyword arguments for + `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. + """ + self._tasks = AwsQuantumTaskBatch._execute( + aws_session, + device_arn, + task_specifications, + s3_destination_folder, + shots, + max_parallel, + max_workers, + poll_timeout_seconds, + poll_interval_seconds, + *aws_quantum_task_args, + **aws_quantum_task_kwargs, + ) + self._aws_session = aws_session + self._results = None + self._unsuccessful = set() + + @staticmethod + def _execute( + aws_session, + device_arn, + task_specifications, + s3_destination_folder, + shots, + max_parallel, + max_workers, + poll_timeout_seconds, + poll_interval_seconds, + *args, + **kwargs, + ): + # This list tracks the number of remaining and currently executing tasks + # It also serves as a lock, because appending and popping are atomic + remaining = [0 for _ in task_specifications] + executing = Queue(maxsize=max_parallel) + with ThreadPoolExecutor(max_workers=max_workers) as executor: + task_futures = [ + executor.submit( + AwsQuantumTaskBatch._create_task, + remaining, + executing, + aws_session, + device_arn, + task, + s3_destination_folder, + shots, + poll_timeout_seconds=poll_timeout_seconds, + poll_interval_seconds=poll_interval_seconds, + *args, + **kwargs, + ) + for task in task_specifications + ] + tasks = [future.result() for future in task_futures] + return tasks + + @staticmethod + def _create_task( + remaining, + executing, + aws_session, + device_arn, + task_specification, + s3_destination_folder, + shots, + poll_interval_seconds, + *args, + **kwargs, + ): + executing.put(0) + remaining.pop() + + task = AwsQuantumTask.create( + aws_session, + device_arn, + task_specification, + s3_destination_folder, + shots, + poll_interval_seconds=poll_interval_seconds, + *args, + **kwargs, + ) + + # If the task hits a terminal state before all tasks have been created, + # it can be returned immediately + while remaining: + if task.state() in AwsQuantumTask.TERMINAL_STATES: + executing.get() + break + time.sleep(poll_interval_seconds) + return task + + def results(self, fail_unsuccessful=False, retry=False): + """Retrieves the result of every task in the batch. + + Polling for results happens in parallel; this method returns when all tasks + have reached a terminal state. The result of this method is cached. + + Args: + fail_unsuccessful (bool): If set to True, this method will fail + if any task in the batch is in the FAILED or CANCELLED state. + retry (bool): Whether to refetch the results from S3, + even when results have already been cached. Default: False + + Returns: + List[AwsQuantumTask]: The results of all of the tasks in the batch. + FAILED or CANCELLED tasks will have a result of None + """ + if self._results and not retry: + return list(self._results) + with ThreadPoolExecutor( + max_workers=AwsQuantumTaskBatch.MAX_CONNECTIONS_DEFAULT + ) as executor: + result_futures = [ + executor.submit(AwsQuantumTaskBatch._get_task_result, task, self._unsuccessful) + for task in self._tasks + ] + self._results = [future.result() for future in result_futures] + if fail_unsuccessful and self._unsuccessful: + raise RuntimeError(f"{len(self._unsuccessful)} tasks failed to complete") + return self._results + + @staticmethod + def _get_task_result(task, unsuccessful): + result = task.result() + if not result: + unsuccessful.add(task.id) + return result + + @property + def tasks(self) -> List[AwsQuantumTask]: + """List[AwsQuantumTask]: The tasks in this batch, as a list of AwsQuantumTask objects""" + return list(self._tasks) + + @property + def size(self) -> int: + """int: The number of tasks in the batch""" + return len(self._tasks) + + @property + def unfinished(self) -> Set[str]: + """Set[str]: The IDs of all the tasks in the batch that have yet to complete""" + with ThreadPoolExecutor( + max_workers=AwsQuantumTaskBatch.MAX_CONNECTIONS_DEFAULT + ) 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() + if status not in AwsQuantumTask.TERMINAL_STATES: + unfinished.add(task_id) + if status in AwsQuantumTask.NO_RESULT_TERMINAL_STATES: + self._unsuccessful.add(task_id) + return unfinished + + @property + def unsuccessful(self) -> Set[str]: + """Set[str]: The IDs of all the FAILED and CANCELLED tasks in the batch""" + return set(self._unsuccessful) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 06968f22..328d0873 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -23,19 +23,21 @@ class AwsSession(object): S3DestinationFolder = NamedTuple("S3DestinationFolder", [("bucket", str), ("key", int)]) - def __init__(self, boto_session=None, braket_client=None): + def __init__(self, boto_session=None, braket_client=None, config=None): """ Args: boto_session: A boto3 session object braket_client: A boto3 Braket client + config: A botocore Config object """ self.boto_session = boto_session or boto3.Session() + self._config = config if braket_client: self.braket_client = braket_client else: - self.braket_client = self.boto_session.client("braket") + self.braket_client = self.boto_session.client("braket", config=self._config) # # Quantum Tasks @@ -103,7 +105,7 @@ def retrieve_s3_object_body(self, s3_bucket: str, s3_object_key: str) -> str: Returns: str: The body of the S3 object """ - s3 = self.boto_session.resource("s3") + s3 = self.boto_session.resource("s3", config=self._config) obj = s3.Object(s3_bucket, s3_object_key) return obj.get()["Body"].read().decode("utf-8") diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index 44767e9e..ce351015 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -16,6 +16,7 @@ import numpy as np +from braket.aws import AwsDevice from braket.circuits import Circuit, Observable, ResultType from braket.circuits.quantum_operator_helpers import get_pauli_eigenvalues from braket.devices import Device @@ -23,10 +24,7 @@ def get_tol(shots: int) -> Dict[str, float]: - if shots: - return {"atol": 0.1, "rtol": 0.15} - else: - return {"atol": 0.01, "rtol": 0} + return {"atol": 0.1, "rtol": 0.15} if shots else {"atol": 0.01, "rtol": 0} def qubit_ordering_testing(device: Device, run_kwargs: Dict[str, Any]): @@ -443,3 +441,17 @@ def run_circuit(circuit): assert np.allclose(result.measurement_probabilities["00"], 0.5, **tol) assert np.allclose(result.measurement_probabilities["11"], 0.5, **tol) assert len(result.measurements) == shots + + +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)] + + batch = device.run_batch(circuits, max_parallel=5, **run_kwargs) + results = batch.results() + for result in results: + assert np.allclose(result.measurement_probabilities["00"], 0.5, **tol) + assert np.allclose(result.measurement_probabilities["11"], 0.5, **tol) + assert len(result.measurements) == shots + assert [task.result() for task in batch.tasks] == results diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index f34e3076..3bdd3343 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -13,6 +13,7 @@ import pytest from gate_model_device_testing_utils import ( + batch_bell_pair_testing, multithreaded_bell_pair_testing, no_result_types_bell_pair_testing, qubit_ordering_testing, @@ -167,3 +168,11 @@ def test_result_types_observable_not_in_instructions( result_types_observable_not_in_instructions( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) + + +@pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) +def test_batch_bell_pair(simulator_arn, aws_session, s3_destination_folder): + device = AwsDevice(simulator_arn, aws_session) + batch_bell_pair_testing( + device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} + ) diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 7644200f..7ecf8ffa 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -14,6 +14,8 @@ import json from unittest.mock import Mock +from braket.aws import AwsQuantumTaskBatch + DWAVE_ARN = "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6" RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-8" IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/ionQdevice" @@ -131,7 +133,7 @@ def run_and_assert( aws_quantum_task_mock, device, default_shots, - default_timeout, + default_poll_timeout, default_poll_interval, circuit, s3_destination_folder, @@ -152,31 +154,118 @@ def run_and_assert( if poll_interval_seconds is not None: run_args.append(poll_interval_seconds) run_args += extra_args if extra_args else [] - run_kwargs = extra_kwargs or {} task = device.run(circuit, s3_destination_folder, *run_args, **run_kwargs) assert task == task_mock + create_args, create_kwargs = _create_task_args_and_kwargs( + default_shots, + default_poll_timeout, + default_poll_interval, + shots, + poll_timeout_seconds, + poll_interval_seconds, + extra_args, + extra_kwargs, + ) + + aws_quantum_task_mock.assert_called_with( + device._aws_session, + device.arn, + circuit, + s3_destination_folder, + *create_args, + **create_kwargs + ) + + +def run_batch_and_assert( + aws_quantum_task_mock, + aws_session_mock, + device, + default_shots, + default_poll_timeout, + default_poll_interval, + circuits, + s3_destination_folder, + shots, + max_parallel, + max_connections, + poll_timeout_seconds, + poll_interval_seconds, + extra_args, + extra_kwargs, +): + task_mock = Mock() + task_mock.state.return_value = "COMPLETED" + aws_quantum_task_mock.return_value = task_mock + new_session_mock = Mock() + aws_session_mock.return_value = new_session_mock + + run_args = [] + if shots is not None: + run_args.append(shots) + if max_parallel is not None: + run_args.append(max_parallel) + if max_connections is not None: + run_args.append(max_connections) + if poll_timeout_seconds is not None: + run_args.append(poll_timeout_seconds) + if poll_interval_seconds is not None: + run_args.append(poll_interval_seconds) + run_args += extra_args if extra_args else [] + run_kwargs = extra_kwargs or {} + + batch = device.run_batch(circuits, s3_destination_folder, *run_args, **run_kwargs) + assert batch.tasks == [task_mock for _ in range(len(circuits))] + + create_args, create_kwargs = _create_task_args_and_kwargs( + default_shots, + default_poll_timeout, + default_poll_interval, + shots, + poll_timeout_seconds, + poll_interval_seconds, + extra_args, + extra_kwargs, + ) + + max_pool_connections = max_connections or AwsQuantumTaskBatch.MAX_CONNECTIONS_DEFAULT + + # aws_session_mock.call_args.kwargs syntax is newer than Python 3.7 + assert aws_session_mock.call_args[1]["config"].max_pool_connections == max_pool_connections + aws_quantum_task_mock.assert_called_with( + new_session_mock, + device.arn, + circuits[0], + s3_destination_folder, + *create_args, + **create_kwargs + ) + + +def _create_task_args_and_kwargs( + default_shots, + default_poll_timeout, + default_poll_interval, + shots, + poll_timeout_seconds, + poll_interval_seconds, + extra_args, + extra_kwargs, +): create_args = [shots if shots is not None else default_shots] create_args += extra_args if extra_args else [] - create_kwargs = extra_kwargs or {} create_kwargs.update( { "poll_timeout_seconds": poll_timeout_seconds if poll_timeout_seconds is not None - else default_timeout, + else default_poll_timeout, "poll_interval_seconds": poll_interval_seconds if poll_interval_seconds is not None else default_poll_interval, } ) - aws_quantum_task_mock.assert_called_with( - device._aws_session, - device.arn, - circuit, - s3_destination_folder, - *create_args, - **create_kwargs - ) + 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 e47da03d..69755c39 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -14,9 +14,16 @@ from unittest.mock import Mock, patch import pytest -from common_test_utils import DWAVE_ARN, IONQ_ARN, RIGETTI_ARN, SIMULATOR_ARN, run_and_assert +from common_test_utils import ( + DWAVE_ARN, + IONQ_ARN, + RIGETTI_ARN, + SIMULATOR_ARN, + run_and_assert, + run_batch_and_assert, +) -from braket.aws import AwsDevice, AwsDeviceType +from braket.aws import AwsDevice, AwsDeviceType, AwsQuantumTask from braket.circuits import Circuit from braket.device_schema.dwave import DwaveDeviceCapabilities from braket.device_schema.rigetti import RigettiDeviceCapabilities @@ -221,6 +228,13 @@ def boto_session(): def aws_session(): _boto_session = Mock() _boto_session.region_name = AwsDevice.DEVICE_REGIONS[RIGETTI_REGION_KEY][0] + + creds = Mock() + creds.access_key = "access key" + creds.secret_key = "secret key" + creds.token = "token" + _boto_session.get_credentials.return_value = creds + _aws_session = Mock() _aws_session.boto_session = _boto_session return _aws_session @@ -305,9 +319,9 @@ def test_aws_session_in_another_qpu_region( aws_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 creds = Mock() - creds.access_key = "access key" - creds.secret_key = "secret key" - creds.token = "token" + creds.access_key = "a" + creds.secret_key = "b" + creds.token = "c" different_region_aws_session = Mock() different_region_aws_session.boto_session.get_credentials.return_value = creds @@ -397,8 +411,8 @@ def test_run_with_qpu_no_shots(aws_quantum_task_mock, device, circuit, s3_destin aws_quantum_task_mock, device(RIGETTI_ARN), AwsDevice.DEFAULT_SHOTS_QPU, - AwsDevice.DEFAULT_RESULTS_POLL_TIMEOUT, - AwsDevice.DEFAULT_RESULTS_POLL_INTERVAL, + AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, + AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, circuit, s3_destination_folder, None, @@ -441,6 +455,53 @@ def test_run_with_positional_args_and_kwargs( ) +@patch("braket.aws.aws_device.AwsSession") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_batch_no_extra( + aws_quantum_task_mock, aws_session_mock, device, circuit, s3_destination_folder +): + _run_batch_and_assert( + aws_quantum_task_mock, + aws_session_mock, + device, + [circuit for _ in range(10)], + s3_destination_folder, + ) + + +@patch("braket.aws.aws_device.AwsSession") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_batch_with_shots( + aws_quantum_task_mock, aws_session_mock, device, circuit, s3_destination_folder +): + _run_batch_and_assert( + aws_quantum_task_mock, + aws_session_mock, + device, + [circuit for _ in range(10)], + s3_destination_folder, + 1000, + ) + + +@patch("braket.aws.aws_device.AwsSession") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_batch_with_max_parallel_and_kwargs( + aws_quantum_task_mock, aws_session_mock, device, circuit, s3_destination_folder +): + _run_batch_and_assert( + aws_quantum_task_mock, + aws_session_mock, + device, + [circuit for _ in range(10)], + s3_destination_folder, + 1000, + 20, + 50, + extra_kwargs={"bar": 1, "baz": 2}, + ) + + def _run_and_assert( aws_quantum_task_mock, device_factory, @@ -456,8 +517,8 @@ def _run_and_assert( aws_quantum_task_mock, device_factory("foo_bar"), AwsDevice.DEFAULT_SHOTS_SIMULATOR, - AwsDevice.DEFAULT_RESULTS_POLL_TIMEOUT, - AwsDevice.DEFAULT_RESULTS_POLL_INTERVAL, + AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, + AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, circuit, s3_destination_folder, shots, @@ -468,6 +529,39 @@ def _run_and_assert( ) +def _run_batch_and_assert( + aws_quantum_task_mock, + aws_session_mock, + device_factory, + circuits, + s3_destination_folder, + shots=None, # Treated as positional arg + max_parallel=None, # Treated as positional arg + max_connections=None, # Treated as positional arg + poll_timeout_seconds=None, # Treated as a positional arg + poll_interval_seconds=None, # Treated as positional arg + extra_args=None, + extra_kwargs=None, +): + run_batch_and_assert( + aws_quantum_task_mock, + aws_session_mock, + device_factory("foo_bar"), + AwsDevice.DEFAULT_SHOTS_SIMULATOR, + AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, + AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, + circuits, + s3_destination_folder, + shots, + max_parallel, + max_connections, + poll_timeout_seconds, + poll_interval_seconds, + extra_args, + extra_kwargs, + ) + + def _assert_device_fields(device, expected_properties, expected_device_data): assert device.name == expected_device_data.get("deviceName") assert device.properties == expected_properties 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 5b3abc93..4b814414 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -336,11 +336,26 @@ def test_result_annealing(annealing_task): ) +def test_result_circuit_cached(circuit_task): + _mock_metadata(circuit_task._aws_session, "COMPLETED") + expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_GATE_MODEL) + circuit_task._result = expected + assert circuit_task.result() == expected + assert not circuit_task._aws_session.retrieve_s3_object_body.called + + +def test_no_result(circuit_task): + _mock_metadata(circuit_task._aws_session, "FAILED") + circuit_task._result = None + assert circuit_task.result() is None + assert not circuit_task._aws_session.retrieve_s3_object_body.called + + @pytest.mark.parametrize( "result_string", [MockS3.MOCK_S3_RESULT_GATE_MODEL, MockS3.MOCK_S3_RESULT_GATE_MODEL_WITH_RESULT_TYPES], ) -def test_result_is_cached(circuit_task, result_string): +def test_result_cached_future(circuit_task, result_string): _mock_metadata(circuit_task._aws_session, "COMPLETED") _mock_s3(circuit_task._aws_session, result_string) circuit_task.result() 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 new file mode 100644 index 00000000..672ec7fa --- /dev/null +++ b/test/unit_tests/braket/aws/test_aws_quantum_task_batch.py @@ -0,0 +1,81 @@ +# Copyright 2019-2019 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 random +import uuid +from unittest.mock import Mock, PropertyMock, patch + +import pytest +from common_test_utils import MockS3 + +from braket.aws import AwsQuantumTaskBatch, AwsSession +from braket.circuits import Circuit +from braket.tasks import GateModelQuantumTaskResult + +S3_TARGET = AwsSession.S3DestinationFolder("foo", "bar") + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_creation(mock_create): + task_mock = Mock() + type(task_mock).id = PropertyMock(side_effect=uuid.uuid4) + task_mock.state.return_value = "RUNNING" + mock_create.return_value = task_mock + + batch_size = 10 + batch = AwsQuantumTaskBatch(Mock(), "foo", _circuits(batch_size), S3_TARGET, 1000) + assert batch.size == batch_size + assert batch.tasks == [task_mock for _ in range(batch_size)] + assert len(batch.unfinished) == batch_size + assert not batch.unsuccessful + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_successful(mock_create): + task_mock = Mock() + type(task_mock).id = PropertyMock(side_effect=uuid.uuid4) + task_mock.state.return_value = "COMPLETED" + result = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_GATE_MODEL) + task_mock.result.return_value = result + mock_create.return_value = task_mock + + batch_size = 15 + batch = AwsQuantumTaskBatch(Mock(), "foo", _circuits(batch_size), S3_TARGET, 1000) + assert batch.size == batch_size + assert not batch.unfinished + assert not batch.unsuccessful + assert batch.results() == [result for _ in range(batch_size)] + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_unsuccessful(mock_create): + task_mock = Mock() + task_id = uuid.uuid4() + type(task_mock).id = PropertyMock(return_value=task_id) + task_mock.state.return_value = random.choice(["CANCELLED", "FAILED"]) + task_mock.result.return_value = None + mock_create.return_value = task_mock + + batch = AwsQuantumTaskBatch(Mock(), "foo", [Circuit().h(0).cnot(0, 1)], S3_TARGET, 1000) + assert not batch.unfinished + assert batch.unsuccessful == {task_id} + assert batch.results() == [None] + assert batch.results(fail_unsuccessful=True) == [None] # Result is cached + batch._unsuccessful = set() + with pytest.raises(RuntimeError): + batch.results(fail_unsuccessful=True, retry=True) + assert batch.unsuccessful == {task_id} + + +def _circuits(batch_size): + return [Circuit().h(0).cnot(0, 1) for _ in range(batch_size)] diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index 4c6f0f1b..acd8552e 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -39,8 +39,8 @@ def aws_session(boto_session): def test_initializes_boto_client_if_required(boto_session): - AwsSession(boto_session=boto_session, braket_client=None) - boto_session.client.assert_called_with("braket") + AwsSession(boto_session=boto_session) + boto_session.client.assert_called_with("braket", config=None) def test_uses_supplied_braket_client(): @@ -48,10 +48,15 @@ def test_uses_supplied_braket_client(): boto_session.region_name = "foobar" braket_client = Mock() aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) - assert aws_session.braket_client == braket_client +def test_config(boto_session): + config = Mock() + AwsSession(boto_session=boto_session, config=config) + boto_session.client.assert_called_with("braket", config=config) + + def test_retrieve_s3_object_body_success(boto_session): bucket_name = "braket-integ-test" filename = "tasks/test_task_1.json" @@ -70,6 +75,13 @@ def test_retrieve_s3_object_body_success(boto_session): aws_session = AwsSession(boto_session=boto_session) 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) + + 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) @pytest.mark.xfail(raises=ClientError) From 140a7ee01c404f9e96dd820a2907e6b9bbdf4cbb Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 20 Nov 2020 18:31:19 -0800 Subject: [PATCH 0218/1165] feature: Enable explicit qubit allocation (#171) --- src/braket/aws/aws_device.py | 8 +++- src/braket/aws/aws_quantum_task.py | 23 ++++++++--- .../braket/aws/test_aws_quantum_task.py | 38 ++++++++++++++++++- 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index a0f196c7..7ed35d78 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -78,6 +78,7 @@ def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): self._aws_session = AwsDevice._aws_session_for_device(arn, aws_session) self._properties = None self._provider_name = None + self._topology_graph = None self._type = None self.refresh_metadata() @@ -123,12 +124,17 @@ def run( >>> device.run(task_specification=circuit, >>> s3_destination_folder=("bucket-foo", "key-bar")) + >>> circuit = Circuit().h(0).cnot(0, 1) + >>> device = AwsDevice("arn3") + >>> device.run(task_specification=circuit, + >>> s3_destination_folder=("bucket-foo", "key-bar"), disable_qubit_rewiring=True) + >>> problem = Problem( >>> ProblemType.ISING, >>> linear={1: 3.14}, >>> quadratic={(1, 2): 10.08}, >>> ) - >>> device = AwsDevice("arn3") + >>> device = AwsDevice("arn4") >>> device.run(problem, ("bucket-foo", "key-bar"), >>> device_parameters={ >>> "providerLevelParameters": {"postprocessingType": "SAMPLING"}} diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 6795611f..6d1523ac 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -58,6 +58,7 @@ def create( s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, device_parameters: Dict[str, Any] = None, + disable_qubit_rewiring: bool = False, tags: Dict[str, str] = None, *args, **kwargs, @@ -87,6 +88,12 @@ def create( For example, for D-Wave: `{"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}}` + disable_qubit_rewiring (bool): Whether to run the circuit with the exact qubits chosen, + without any rewiring downstream, if this is supported by the device. + Only applies to digital, gate-based circuits (as opposed to annealing problems). + If ``True``, no qubit rewiring is allowed; if ``False``, qubit rewiring is allowed. + Default: False + tags (Dict[str, str]): Tags, which are Key-Value pairs to add to this quantum task. An example would be: `{"state": "washington"}` @@ -120,8 +127,9 @@ def create( task_specification, aws_session, create_task_kwargs, - device_parameters or {}, device_arn, + device_parameters or {}, + disable_qubit_rewiring, *args, **kwargs, ) @@ -429,8 +437,9 @@ def _create_internal( task_specification: Union[Circuit, Problem], aws_session: AwsSession, create_task_kwargs: Dict[str, Any], - device_parameters: Union[dict, BraketSchemaBase], device_arn: str, + device_parameters: Union[dict, BraketSchemaBase], + disable_qubit_rewiring, *args, **kwargs, ) -> AwsQuantumTask: @@ -442,8 +451,9 @@ def _( circuit: Circuit, aws_session: AwsSession, create_task_kwargs: Dict[str, Any], - device_parameters: Union[dict, BraketSchemaBase], device_arn: str, + device_parameters: Union[dict, BraketSchemaBase], # Not currently used for circuits + disable_qubit_rewiring, *args, **kwargs, ) -> AwsQuantumTask: @@ -451,7 +461,9 @@ def _( # TODO: Update this to use `deviceCapabilities` from Amazon Braket's GetDevice operation # in order to decide what parameters to build. - paradigm_parameters = GateModelParameters(qubitCount=circuit.qubit_count) + paradigm_parameters = GateModelParameters( + qubitCount=circuit.qubit_count, disableQubitRewiring=disable_qubit_rewiring + ) if "ionq" in device_arn: device_parameters = IonqDeviceParameters(paradigmParameters=paradigm_parameters) elif "rigetti" in device_arn: @@ -473,8 +485,9 @@ def _( problem: Problem, aws_session: AwsSession, create_task_kwargs: Dict[str, Any], - device_parameters: Union[dict, DwaveDeviceParameters], device_arn: str, + device_parameters: Union[dict, DwaveDeviceParameters], + disable_qubit_rewiring, *args, **kwargs, ) -> AwsQuantumTask: 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 4b814414..bc6a585c 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -478,7 +478,43 @@ def test_from_circuit_with_shots(device_arn, device_parameters_class, aws_sessio S3_TARGET, shots, device_parameters_class( - paradigmParameters=GateModelParameters(qubitCount=circuit.qubit_count) + paradigmParameters=GateModelParameters( + qubitCount=circuit.qubit_count, disableQubitRewiring=False + ) + ), + ) + + +@pytest.mark.parametrize( + "device_arn,device_parameters_class", + [ + ("device/qpu/rigetti", RigettiDeviceParameters), + ], +) +def test_from_circuit_with_disabled_rewiring( + device_arn, device_parameters_class, aws_session, circuit +): + mocked_task_arn = "task-arn-1" + aws_session.create_quantum_task.return_value = mocked_task_arn + shots = 53 + + task = AwsQuantumTask.create( + aws_session, device_arn, circuit, S3_TARGET, shots, disable_qubit_rewiring=True + ) + assert task == AwsQuantumTask( + mocked_task_arn, aws_session, GateModelQuantumTaskResult.from_string + ) + + _assert_create_quantum_task_called_with( + aws_session, + device_arn, + circuit, + S3_TARGET, + shots, + device_parameters_class( + paradigmParameters=GateModelParameters( + qubitCount=circuit.qubit_count, disableQubitRewiring=True + ) ), ) From ef5e18ea5c88b5ee30d8012c790e06c78da810a8 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Sun, 22 Nov 2020 23:39:32 -0800 Subject: [PATCH 0219/1165] fix: Correctly cache status (#174) --- src/braket/aws/aws_quantum_task.py | 27 ++++++++++++------- .../braket/aws/test_aws_quantum_task.py | 17 ++++++++++-- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 6d1523ac..4a602682 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -219,15 +219,16 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: Args: use_cached_value (bool, optional): 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`. + 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 - retrieved value is used. + 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. """ - if not use_cached_value: + if not use_cached_value or not self._metadata: self._metadata = self._aws_session.get_quantum_task(self._arn) return self._metadata @@ -255,6 +256,13 @@ def _status(self, use_cached_value=False): self._logger.warning(f"Task is in terminal state {status} and no result is available") return status + def _update_status_if_nonterminal(self): + # If metadata has not been populated, the first call to _status will fetch it, + # so the second _status call will no longer need to + metadata_absent = self._metadata is None + cached = self._status(True) + return cached if cached in self.TERMINAL_STATES else self._status(metadata_absent) + def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: """ Get the quantum task result by polling Amazon Braket to see if the task is completed. @@ -293,7 +301,8 @@ def _get_future(self): self._future.done() and not self._future.cancelled() and self._result is None - and self._status() not in self.NO_RESULT_TERMINAL_STATES # timed out and no result + # timed out and no result + and self._update_status_if_nonterminal() not in self.NO_RESULT_TERMINAL_STATES ): self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) return self._future @@ -349,10 +358,8 @@ async def _wait_for_completion( ) continue # Used cached metadata if cached status is terminal - current_metadata = self.metadata( - self._status(False) not in AwsQuantumTask.TERMINAL_STATES - ) - task_status = self._status(False) + task_status = self._update_status_if_nonterminal() + current_metadata = self.metadata(True) self._logger.debug(f"Task {self._arn}: task status {task_status}") if task_status in AwsQuantumTask.RESULTS_READY_STATES: result_string = self._aws_session.retrieve_s3_object_body( 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 bc6a585c..4652ec20 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -235,6 +235,7 @@ def test_is_polling_time(window, cur_time, expected_val, quantum_task): def test_result_not_polling(quantum_task): + quantum_task._metadata = {"a": 0} quantum_task._poll_outside_execution_window = False quantum_task._poll_timeout_seconds = 0.01 window = { @@ -258,6 +259,13 @@ def test_metadata(quantum_task): assert quantum_task.metadata(use_cached_value=True) == metadata_1 +def test_metadata_call_if_none(quantum_task): + metadata_1 = {"status": "RUNNING"} + quantum_task._aws_session.get_quantum_task.return_value = metadata_1 + assert quantum_task.metadata(use_cached_value=True) == metadata_1 + quantum_task._aws_session.get_quantum_task.assert_called_with(quantum_task.id) + + def test_state(quantum_task): state_1 = "RUNNING" _mock_metadata(quantum_task._aws_session, state_1) @@ -421,6 +429,12 @@ def test_timeout_completed(aws_session): assert quantum_task.result() == GateModelQuantumTaskResult.from_string( MockS3.MOCK_S3_RESULT_GATE_MODEL ) + # Cached status is still COMPLETED, so result should be fetched + _mock_metadata(aws_session, "RUNNING") + quantum_task._result = None + assert quantum_task.result() == GateModelQuantumTaskResult.from_string( + MockS3.MOCK_S3_RESULT_GATE_MODEL + ) def test_timeout_no_result_terminal_state(aws_session): @@ -632,12 +646,11 @@ def _assert_create_quantum_task_called_with( def _mock_metadata(aws_session, state): - return_value = { + aws_session.get_quantum_task.return_value = { "status": state, "outputS3Bucket": S3_TARGET.bucket, "outputS3Directory": S3_TARGET.key, } - aws_session.get_quantum_task.return_value = return_value def _mock_s3(aws_session, result): From a9871b539ed87a79227c3864a8152ac9d32f01fb Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 23 Nov 2020 18:09:12 +0000 Subject: [PATCH 0220/1165] prepare release v1.3.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 38f923f4..47101baa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.3.0 (2020-11-23) + +### Features + + * Enable explicit qubit allocation + * Add support for batch execution + +### Bug Fixes and Other Changes + + * Correctly cache status + ## v1.2.0 (2020-11-02) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 52b7b3a8..89366538 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.2.1.dev0" +__version__ = "1.3.0" From 476586daaa9fad72d4cb6c4be260f3d3e1f51c61 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 23 Nov 2020 18:09:12 +0000 Subject: [PATCH 0221/1165] update development version to v1.3.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 89366538..64f1aa87 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.3.0" +__version__ = "1.3.1.dev0" From 386948024d125d94a153112aacc5c365562dc27c Mon Sep 17 00:00:00 2001 From: Cedric Lin <67704428+licedric@users.noreply.github.com> Date: Tue, 24 Nov 2020 20:32:19 +0000 Subject: [PATCH 0222/1165] Fix: Use the user-provided max_workers when fetching batched results (#175) --- src/braket/aws/aws_quantum_task_batch.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index f475d471..c06ba87f 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -96,6 +96,8 @@ def __init__( self._results = None self._unsuccessful = set() + self._max_workers = max_workers + @staticmethod def _execute( aws_session, @@ -189,9 +191,7 @@ def results(self, fail_unsuccessful=False, retry=False): """ if self._results and not retry: return list(self._results) - with ThreadPoolExecutor( - max_workers=AwsQuantumTaskBatch.MAX_CONNECTIONS_DEFAULT - ) as executor: + with ThreadPoolExecutor(max_workers=self._max_workers) as executor: result_futures = [ executor.submit(AwsQuantumTaskBatch._get_task_result, task, self._unsuccessful) for task in self._tasks From 30dcec09ee6aa70d0aac30917f132dbd38bf7b6a Mon Sep 17 00:00:00 2001 From: Cedric Lin <67704428+licedric@users.noreply.github.com> Date: Tue, 24 Nov 2020 22:00:53 +0000 Subject: [PATCH 0223/1165] Fix: Change AwsQuantumTaskBatch.results() to take in the keyword use_cached_value instead of retry. (#176) --- src/braket/aws/aws_quantum_task_batch.py | 8 ++++---- test/unit_tests/braket/aws/test_aws_quantum_task_batch.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index c06ba87f..00d67953 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -173,7 +173,7 @@ def _create_task( time.sleep(poll_interval_seconds) return task - def results(self, fail_unsuccessful=False, retry=False): + def results(self, fail_unsuccessful=False, use_cached_value=True): """Retrieves the result of every task in the batch. Polling for results happens in parallel; this method returns when all tasks @@ -182,14 +182,14 @@ def results(self, fail_unsuccessful=False, retry=False): Args: fail_unsuccessful (bool): If set to True, this method will fail if any task in the batch is in the FAILED or CANCELLED state. - retry (bool): Whether to refetch the results from S3, - even when results have already been cached. Default: False + use_cached_value (bool): If False, will refetch the results from S3, + even when results have already been cached. Default: True Returns: List[AwsQuantumTask]: The results of all of the tasks in the batch. FAILED or CANCELLED tasks will have a result of None """ - if self._results and not retry: + if self._results and use_cached_value: return list(self._results) with ThreadPoolExecutor(max_workers=self._max_workers) as executor: result_futures = [ 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 672ec7fa..03c01a72 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 @@ -73,7 +73,7 @@ def test_unsuccessful(mock_create): assert batch.results(fail_unsuccessful=True) == [None] # Result is cached batch._unsuccessful = set() with pytest.raises(RuntimeError): - batch.results(fail_unsuccessful=True, retry=True) + batch.results(fail_unsuccessful=True, use_cached_value=False) assert batch.unsuccessful == {task_id} From ceffaea33b1a556d746408a5f1426faad47798d9 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 25 Nov 2020 18:08:39 +0000 Subject: [PATCH 0224/1165] prepare release v1.3.1 --- CHANGELOG.md | 2 ++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47101baa..be350897 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## v1.3.1 (2020-11-25) + ## v1.3.0 (2020-11-23) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 64f1aa87..0322ac5d 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.3.1.dev0" +__version__ = "1.3.1" From d5140f959045f5dec83e140c5e69ff5c4d13a9b1 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 25 Nov 2020 18:08:39 +0000 Subject: [PATCH 0225/1165] update development version to v1.3.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 0322ac5d..a15afa60 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.3.1" +__version__ = "1.3.2.dev0" From e2895a8ba95ab74c06e7b13e31caf95f108a35d4 Mon Sep 17 00:00:00 2001 From: Cedric Lin <67704428+licedric@users.noreply.github.com> Date: Thu, 26 Nov 2020 01:07:51 +0000 Subject: [PATCH 0226/1165] feature: Enable retries when retrieving results from AwsQuantumTaskBatch. (#177) * feature: Enable retries when retrieving results from AwsQuantumTaskBatch. * fix: Allow AwsQuantumTaskBatch.result() to fail even when use_cached_value=True * Minor changes --- src/braket/aws/aws_quantum_task_batch.py | 99 +++++++++++++++---- .../braket/aws/test_aws_quantum_task_batch.py | 32 +++++- 2 files changed, 109 insertions(+), 22 deletions(-) diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 00d67953..d61694bc 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -37,6 +37,7 @@ class AwsQuantumTaskBatch: MAX_PARALLEL_DEFAULT = 10 MAX_CONNECTIONS_DEFAULT = 100 + MAX_RETRIES = 3 def __init__( self, @@ -96,7 +97,17 @@ def __init__( self._results = None self._unsuccessful = set() + # Cache execution inputs for retries. + self._device_arn = device_arn + self._task_specifications = task_specifications + self._s3_destination_folder = s3_destination_folder + self._shots = shots + self._max_parallel = max_parallel self._max_workers = max_workers + self._poll_timeout_seconds = poll_timeout_seconds + self._poll_interval_seconds = poll_interval_seconds + self._aws_quantum_task_args = aws_quantum_task_args + self._aws_quantum_task_kwargs = aws_quantum_task_kwargs @staticmethod def _execute( @@ -173,7 +184,7 @@ def _create_task( time.sleep(poll_interval_seconds) return task - def results(self, fail_unsuccessful=False, use_cached_value=True): + def results(self, fail_unsuccessful=False, max_retries=MAX_RETRIES, use_cached_value=True): """Retrieves the result of every task in the batch. Polling for results happens in parallel; this method returns when all tasks @@ -181,32 +192,80 @@ def results(self, fail_unsuccessful=False, use_cached_value=True): Args: fail_unsuccessful (bool): If set to True, this method will fail - if any task in the batch is in the FAILED or CANCELLED state. + if any task in the batch fails to return a result even after + `max_retries` retries. + max_retries (int): Maximum number of times to retry any failed tasks, + i.e. any tasks in the FAILED or CANCELLED state or that didn't + complete within the timeout. Default: 3 use_cached_value (bool): If False, will refetch the results from S3, even when results have already been cached. Default: True Returns: List[AwsQuantumTask]: The results of all of the tasks in the batch. - FAILED or CANCELLED tasks will have a result of None + FAILED, CANCELLED, or timed out tasks will have a result of None """ - if self._results and use_cached_value: - return list(self._results) - with ThreadPoolExecutor(max_workers=self._max_workers) as executor: - result_futures = [ - executor.submit(AwsQuantumTaskBatch._get_task_result, task, self._unsuccessful) - for task in self._tasks - ] - self._results = [future.result() for future in result_futures] + 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 + } + + retries = 0 + while self._unsuccessful and retries < max_retries: + self.retry_unsuccessful_tasks() + retries = retries + 1 + if fail_unsuccessful and self._unsuccessful: - raise RuntimeError(f"{len(self._unsuccessful)} tasks failed to complete") + raise RuntimeError( + f"{len(self._unsuccessful)} tasks failed to complete after {max_retries} retries" + ) return self._results @staticmethod - def _get_task_result(task, unsuccessful): - result = task.result() - if not result: - unsuccessful.add(task.id) - return result + def _retrieve_results(tasks, max_workers): + 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] + + def retry_unsuccessful_tasks(self): + """Retries any tasks in the batch without valid results. + + This method should only be called after results() has been called at least once. + The method will generate new tasks for any failed tasks, so `self.task` and + `self.results()` may return different values after a call to this method. + + Returns: + bool: Whether or not all retried tasks completed successfully. + """ + if not self._results: + raise RuntimeError("results() should be called before attempting to retry") + unsuccessful_indices = [index for index, result in enumerate(self._results) if not result] + # Return early if there's nothing to retry + if not unsuccessful_indices: + return True + retried_tasks = AwsQuantumTaskBatch._execute( + self._aws_session, + self._device_arn, + [self._task_specifications[i] for i in unsuccessful_indices], + self._s3_destination_folder, + self._shots, + self._max_parallel, + self._max_workers, + self._poll_timeout_seconds, + self._poll_interval_seconds, + *self._aws_quantum_task_args, + **self._aws_quantum_task_kwargs, + ) + for index, task in zip(unsuccessful_indices, retried_tasks): + self._tasks[index] = task + + retried_results = AwsQuantumTaskBatch._retrieve_results(retried_tasks, self._max_workers) + for index, result in zip(unsuccessful_indices, retried_results): + self._results[index] = result + self._unsuccessful = { + task.id for task, result in zip(retried_tasks, retried_results) if not result + } + return not self._unsuccessful @property def tasks(self) -> List[AwsQuantumTask]: @@ -221,9 +280,7 @@ def size(self) -> int: @property def unfinished(self) -> Set[str]: """Set[str]: The IDs of all the tasks in the batch that have yet to complete""" - with ThreadPoolExecutor( - max_workers=AwsQuantumTaskBatch.MAX_CONNECTIONS_DEFAULT - ) as executor: + 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: @@ -236,5 +293,5 @@ def unfinished(self) -> Set[str]: @property def unsuccessful(self) -> Set[str]: - """Set[str]: The IDs of all the FAILED and CANCELLED tasks in the batch""" + """Set[str]: The IDs of all the FAILED, CANCELLED, or timed out tasks in the batch.""" return set(self._unsuccessful) 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 03c01a72..4a1b4d68 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 @@ -70,12 +70,42 @@ def test_unsuccessful(mock_create): assert not batch.unfinished assert batch.unsuccessful == {task_id} assert batch.results() == [None] - assert batch.results(fail_unsuccessful=True) == [None] # Result is cached + with pytest.raises(RuntimeError): + assert batch.results(fail_unsuccessful=True) == [None] batch._unsuccessful = set() with pytest.raises(RuntimeError): batch.results(fail_unsuccessful=True, use_cached_value=False) assert batch.unsuccessful == {task_id} +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_retry(mock_create): + bad_task_mock = Mock() + type(bad_task_mock).id = PropertyMock(side_effect=uuid.uuid4) + bad_task_mock.state.return_value = random.choice(["CANCELLED", "FAILED"]) + bad_task_mock.result.return_value = None + + good_task_mock = Mock() + # task id already mocked when setting up bad_task_mock + good_task_mock.state.return_value = "COMPLETED" + result = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_GATE_MODEL) + good_task_mock.result.return_value = result + + mock_create.side_effect = [bad_task_mock, good_task_mock, bad_task_mock, good_task_mock] + + batch = AwsQuantumTaskBatch( + Mock(), "foo", [Circuit().h(0).cnot(0, 1), Circuit().h(1).cnot(0, 1)], S3_TARGET, 1000 + ) + assert not batch.unfinished + assert batch.results(max_retries=0) == [None, result] + + # Retrying should get rid of the failures + assert batch.results(fail_unsuccessful=True, max_retries=3, use_cached_value=False) == [ + result, + result, + ] + assert batch.unsuccessful == set() + + def _circuits(batch_size): return [Circuit().h(0).cnot(0, 1) for _ in range(batch_size)] From 652a59f5ee71d8a68025da687ac41720f5337151 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 26 Nov 2020 18:09:09 +0000 Subject: [PATCH 0227/1165] prepare release v1.4.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 be350897..74542002 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.4.0 (2020-11-26) + +### Features + + * Enable retries when retrieving results from AwsQuantumTaskBatch. + ## v1.3.1 (2020-11-25) ## v1.3.0 (2020-11-23) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index a15afa60..a97c8c2e 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.3.2.dev0" +__version__ = "1.4.0" From 24ee14b50608f1bf0e82d68b9f6452a36c137f0f Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 26 Nov 2020 18:09:09 +0000 Subject: [PATCH 0228/1165] update development version to v1.4.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 a97c8c2e..e6f9b998 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.4.0" +__version__ = "1.4.1.dev0" From 077c86e8c9b0b5eb6d315b8a5f9669e246b9f227 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 2 Dec 2020 16:25:31 -0800 Subject: [PATCH 0229/1165] documentation: Point README to developer guide (#179) Instead of listing available devices, the README now links to the Amazon Braket developer guide. The prevents the need to change the README with each new device. Also changed the bucket used in examples from amazon-braket-output- to amazon-braket-, the correct default bucket name. --- README.md | 17 ++++++----------- examples/bell.py | 2 +- examples/debug_bell.py | 2 +- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 13d1ba6d..7a7e11fb 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ from braket.circuits import Circuit aws_account_id = boto3.client("sts").get_caller_identity()["Account"] device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") -s3_folder = (f"amazon-braket-output-{aws_account_id}", "folder-name") +s3_folder = (f"amazon-braket-{aws_account_id}", "folder-name") bell = Circuit().h(0).cnot(0, 1) task = device.run(bell, s3_folder, shots=100) @@ -91,11 +91,11 @@ print(batch.results()[0].measurement_counts) # The result of the first task in ``` ### Available Simulators -Amazon Braket provides access to two simulators: a fully managed statevector simulator, SV1, and a local simulator that is part of the Amazon Braket SDK. +Amazon Braket provides access to two types of simulators: the fully managed simulator, available through the Amazon Braket service, and the local simulator that is part of the Amazon Braket SDK. -- `arn:aws:braket:::device/quantum-simulator/amazon/sv1` - SV1 is a fully managed, high-performance, state vector simulator. It can simulate circuits of up to 34 qubits and has a maximum runtime of 12h. You should expect a 34-qubit, dense, and square (circuit depth = 34) circuit to take approximately 1-2 hours to complete, depending on the type of gates used and other factors. +- Fully managed simulators offer high-performance circuit simulations. These simulators can handle circuits larger than circuits that run on quantum hardware. For example, the SV1 state vector simulator shown in the previous examples requires approximately 1 or 2 hours to complete a 34-qubit, dense, and square circuit (circuit depth = 34), depending on the type of gates used and other factors. For a list of available simulators and their features, consult the [Amazon Braket Developer Guide](https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html). -- `LocalSimulator()` - The Amazon Braket Python SDK comes bundled with an implementation of a quantum simulator that you can run locally. The local simulator is well suited for rapid prototyping on small circuits up to 25 qubits, depending on the hardware specifications of your Braket notebook instance or your local environment. An example of how to execute the task locally is included in the repo `../examples/local_bell.py`. +- The Amazon Braket Python SDK includes an implementation of a quantum simulator that can run circuits on your local, classic hardware. The local simulator is well suited for rapid prototyping on small circuits up to 25 qubits, depending on the hardware specifications of your Braket notebook instance or your local environment. An example of how to execute the task locally is included in the repository `../examples/local_bell.py`. ### Debugging logs @@ -114,7 +114,7 @@ from braket.aws import AwsDevice aws_account_id = boto3.client("sts").get_caller_identity()["Account"] device = AwsDevice("arn:aws:braket:::device/qpu/rigetti/Aspen-8") -s3_folder = (f"amazon-braket-output-{aws_account_id}", "RIGETTI") +s3_folder = (f"amazon-braket-{aws_account_id}", "RIGETTI") bell = Circuit().h(0).cnot(0, 1) task = device.run(bell, s3_folder) @@ -128,12 +128,7 @@ task = device.run(bell, s3_folder, poll_timeout_seconds=86400) # 1 day print(task.result().measurement_counts) ``` -Specify which quantum computer hardware to use by changing the value of the `device_arn` to the value for quantum computer to use: -- **IonQ** "arn:aws:braket:::device/qpu/ionq/ionQdevice" -- **Rigetti** "arn:aws:braket:::device/qpu/rigetti/Aspen-8" -- **D-Wave** (See the next section in this document for more information about using D-Wave.) - - **D-Wave 2000Q_6** "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6" - - **D-Wave Advantage_system1** "arn:aws:braket:::device/qpu/d-wave/Advantage_system1" +To select a quantum hardware device, specify its ARN as the value of the `device_arn` argument. A list of available quantum devices and their features can be found in the [Amazon Braket Developer Guide](https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html). **Important** Tasks may not run immediately on the QPU. The QPUs only execute tasks during execution windows. To find their execution windows, please refer to the [AWS console](https://console.aws.amazon.com/braket/home) in the "Devices" tab. diff --git a/examples/bell.py b/examples/bell.py index 43321eee..a0101340 100644 --- a/examples/bell.py +++ b/examples/bell.py @@ -19,7 +19,7 @@ aws_account_id = boto3.client("sts").get_caller_identity()["Account"] device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") -s3_folder = (f"amazon-braket-output-{aws_account_id}", "folder-name") +s3_folder = (f"amazon-braket-{aws_account_id}", "folder-name") # https://wikipedia.org/wiki/Bell_state bell = Circuit().h(0).cnot(0, 1) diff --git a/examples/debug_bell.py b/examples/debug_bell.py index 94727551..754e43e6 100644 --- a/examples/debug_bell.py +++ b/examples/debug_bell.py @@ -26,7 +26,7 @@ aws_account_id = boto3.client("sts").get_caller_identity()["Account"] device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") -s3_folder = (f"amazon-braket-output-{aws_account_id}", "folder-name") +s3_folder = (f"amazon-braket-{aws_account_id}", "folder-name") bell = Circuit().h(0).cnot(0, 1) # pass in logger to device.run, enabling debugging logs to print to console From 003cd79f436dbaaf835e0709feb6925e427f74dc Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 3 Dec 2020 18:09:12 +0000 Subject: [PATCH 0230/1165] prepare release v1.4.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 74542002..a0fc7f8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.4.0.post0 (2020-12-03) + +### Documentation Changes + + * Point README to developer guide + ## v1.4.0 (2020-11-26) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index e6f9b998..08123c9e 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.4.1.dev0" +__version__ = "1.4.0.post0" From 77553534aeb237a25f1d2e9294921821bfa52713 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 3 Dec 2020 18:09:12 +0000 Subject: [PATCH 0231/1165] update development version to v1.4.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 08123c9e..e6f9b998 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.4.0.post0" +__version__ = "1.4.1.dev0" From 4c7c3b28e5a17b8f0cddf94377b7734fcbe2ebfc Mon Sep 17 00:00:00 2001 From: Cedric Lin <67704428+licedric@users.noreply.github.com> Date: Thu, 3 Dec 2020 22:43:40 +0000 Subject: [PATCH 0232/1165] Fix: Move defaults for max_parallel in AwsQuantumTaskBatch to AwsDevice (#180) * Fix: Move defaults for max_parallel in AwsQuantumTaskBatch to AwsDevice * Minor fix to docstring --- src/braket/aws/aws_device.py | 12 +++++++++--- src/braket/aws/aws_quantum_task_batch.py | 5 ++--- .../braket/aws/test_aws_quantum_task_batch.py | 19 +++++++++++++++---- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 7ed35d78..dcac07d8 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -58,6 +58,8 @@ class AwsDevice(Device): DEFAULT_SHOTS_QPU = 1000 DEFAULT_SHOTS_SIMULATOR = 0 + DEFAULT_MAX_PARALLEL = 10 + def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): """ Args: @@ -160,7 +162,7 @@ def run_batch( task_specifications: List[Union[Circuit, Problem]], s3_destination_folder: AwsSession.S3DestinationFolder, shots: Optional[int] = None, - max_parallel: int = AwsQuantumTaskBatch.MAX_PARALLEL_DEFAULT, + max_parallel: Optional[int] = 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, @@ -175,7 +177,7 @@ def run_batch( s3_destination_folder: The S3 location to save the tasks' results shots (int, optional): The number of times to run the circuit or annealing problem. Default is 1000 for QPUs and 0 for simulators. - max_parallel (int): The maximum number of tasks to run on AWS in parallel. + max_parallel (int, optional): The maximum number of tasks to run on AWS in parallel. Batch creation will fail if this value is greater than the maximum allowed concurrent tasks on the device. Default: 10 max_connections (int): The maximum number of connections in the Boto3 connection pool. @@ -201,7 +203,7 @@ def run_batch( task_specifications, s3_destination_folder, shots if shots is not None else self._default_shots, - max_parallel=max_parallel, + 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, poll_interval_seconds=poll_interval_seconds, @@ -296,6 +298,10 @@ def _default_shots(self): AwsDevice.DEFAULT_SHOTS_QPU if "qpu" in self.arn else AwsDevice.DEFAULT_SHOTS_SIMULATOR ) + @property + def _default_max_parallel(self): + return AwsDevice.DEFAULT_MAX_PARALLEL + @staticmethod def _aws_session_for_device(device_arn: str, aws_session: Optional[AwsSession]) -> AwsSession: """AwsSession: Returns an AwsSession for the device ARN. """ diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index d61694bc..ada57469 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -35,7 +35,6 @@ class AwsQuantumTaskBatch: since results will not be available until the window opens. """ - MAX_PARALLEL_DEFAULT = 10 MAX_CONNECTIONS_DEFAULT = 100 MAX_RETRIES = 3 @@ -46,7 +45,7 @@ def __init__( task_specifications: List[Union[Circuit, Problem]], s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, - max_parallel: int = MAX_PARALLEL_DEFAULT, + max_parallel: int, max_workers: int = MAX_CONNECTIONS_DEFAULT, poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, @@ -69,7 +68,7 @@ def __init__( will compute the exact results based on the task specification. max_parallel (int): The maximum number of tasks to run on AWS in parallel. Batch creation will fail if this value is greater than the maximum allowed - concurrent tasks on the device. Default: 10 + concurrent tasks on the device. max_workers (int): The maximum number of thread pool workers. Default: 100 poll_timeout_seconds (float): The polling timeout for AwsQuantumTask.result(), in seconds. Default: 5 days. 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 4a1b4d68..707ebd43 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 @@ -33,7 +33,9 @@ def test_creation(mock_create): mock_create.return_value = task_mock batch_size = 10 - batch = AwsQuantumTaskBatch(Mock(), "foo", _circuits(batch_size), S3_TARGET, 1000) + batch = AwsQuantumTaskBatch( + Mock(), "foo", _circuits(batch_size), S3_TARGET, 1000, max_parallel=10 + ) assert batch.size == batch_size assert batch.tasks == [task_mock for _ in range(batch_size)] assert len(batch.unfinished) == batch_size @@ -50,7 +52,9 @@ def test_successful(mock_create): mock_create.return_value = task_mock batch_size = 15 - batch = AwsQuantumTaskBatch(Mock(), "foo", _circuits(batch_size), S3_TARGET, 1000) + batch = AwsQuantumTaskBatch( + Mock(), "foo", _circuits(batch_size), S3_TARGET, 1000, max_parallel=10 + ) assert batch.size == batch_size assert not batch.unfinished assert not batch.unsuccessful @@ -66,7 +70,9 @@ def test_unsuccessful(mock_create): task_mock.result.return_value = None mock_create.return_value = task_mock - batch = AwsQuantumTaskBatch(Mock(), "foo", [Circuit().h(0).cnot(0, 1)], S3_TARGET, 1000) + batch = AwsQuantumTaskBatch( + Mock(), "foo", [Circuit().h(0).cnot(0, 1)], S3_TARGET, 1000, max_parallel=10 + ) assert not batch.unfinished assert batch.unsuccessful == {task_id} assert batch.results() == [None] @@ -94,7 +100,12 @@ def test_retry(mock_create): mock_create.side_effect = [bad_task_mock, good_task_mock, bad_task_mock, good_task_mock] batch = AwsQuantumTaskBatch( - Mock(), "foo", [Circuit().h(0).cnot(0, 1), Circuit().h(1).cnot(0, 1)], S3_TARGET, 1000 + Mock(), + "foo", + [Circuit().h(0).cnot(0, 1), Circuit().h(1).cnot(0, 1)], + S3_TARGET, + 1000, + max_parallel=10, ) assert not batch.unfinished assert batch.results(max_retries=0) == [None, result] From 059034318d10546ea506a058e2c426a55b2274d5 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 3 Dec 2020 15:06:18 -0800 Subject: [PATCH 0233/1165] fix: Correct integ test bucket (#181) --- test/integ_tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py index 1a3b63c4..9096fc84 100644 --- a/test/integ_tests/conftest.py +++ b/test/integ_tests/conftest.py @@ -51,7 +51,7 @@ def s3_bucket(s3_resource, s3_client, account_id, boto_session): """Create / get S3 bucket for tests""" region_name = boto_session.region_name - bucket_name = f"amazon-braket-ocean-plugin-integ-tests-{account_id}" + bucket_name = f"amazon-braket-sdk-integ-tests-{account_id}" bucket = s3_resource.Bucket(bucket_name) try: From be61754820addae41e41a8229f6249fa428f5013 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 4 Dec 2020 18:08:40 +0000 Subject: [PATCH 0234/1165] prepare release v1.4.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 a0fc7f8e..d9e3c3cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.4.1 (2020-12-04) + +### Bug Fixes and Other Changes + + * Correct integ test bucket + ## v1.4.0.post0 (2020-12-03) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index e6f9b998..1fbc4fec 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.4.1.dev0" +__version__ = "1.4.1" From 873e3ca61583b6be8525cb8bb25fe6096274b17d Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 4 Dec 2020 18:08:40 +0000 Subject: [PATCH 0235/1165] update development version to v1.4.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 1fbc4fec..f1845183 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.4.1" +__version__ = "1.4.2.dev0" From b770e78b0f88a2a0d93284862a177a619cee1ebc Mon Sep 17 00:00:00 2001 From: Katharine Hyatt <67932820+kshyatt-aws@users.noreply.github.com> Date: Fri, 4 Dec 2020 16:36:21 -0500 Subject: [PATCH 0236/1165] doc: fix backticks, grammar for aws_device.py (#182) * doc: fix backticks, grammar for aws_device.py * doc: fixed too-long line in docstring --- src/braket/aws/aws_device.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index dcac07d8..75bc96b6 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -64,7 +64,7 @@ def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): """ Args: arn (str): The ARN of the device - aws_session (AwsSession, optional) aws_session: An AWS session object. Default = None. + aws_session (AwsSession, optional): An AWS session object. Default is `None`. Note: Some devices (QPUs) are physically located in specific AWS Regions. In some cases, @@ -101,12 +101,12 @@ def run( Args: task_specification (Union[Circuit, Problem]): Specification of task (circuit or annealing problem) to run on device. - s3_destination_folder: The S3 location to save the task's results + s3_destination_folder: The S3 location to save the task's results to. shots (int, optional): The number of times to run the circuit or annealing problem. Default is 1000 for QPUs and 0 for simulators. - poll_timeout_seconds (float): The polling timeout for AwsQuantumTask.result(), + poll_timeout_seconds (float): The polling timeout for `AwsQuantumTask.result()`, in seconds. Default: 5 days. - poll_interval_seconds (float): The polling interval for AwsQuantumTask.result(), + poll_interval_seconds (float): The polling interval for `AwsQuantumTask.result()`, in seconds. Default: 1 second. *aws_quantum_task_args: Variable length positional arguments for `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. @@ -174,7 +174,7 @@ def run_batch( Args: task_specifications (List[Union[Circuit, Problem]]): List of circuits or annealing problems to run on device. - s3_destination_folder: The S3 location to save the tasks' results + s3_destination_folder: The S3 location to save the tasks' results to. shots (int, optional): The number of times to run the circuit or annealing problem. Default is 1000 for QPUs and 0 for simulators. max_parallel (int, optional): The maximum number of tasks to run on AWS in parallel. @@ -182,7 +182,7 @@ def run_batch( concurrent tasks on the device. Default: 10 max_connections (int): The maximum number of connections in the Boto3 connection pool. Also the maximum number of thread pool workers for the batch. Default: 100 - poll_timeout_seconds (float): The polling timeout for AwsQuantumTask.result(), + poll_timeout_seconds (float): The polling timeout for `AwsQuantumTask.result()`, in seconds. Default: 5 days. poll_interval_seconds (float): The polling interval for results in seconds. Default: 1 second. @@ -251,8 +251,8 @@ def properties(self) -> DeviceCapabilities: @property def topology_graph(self) -> Graph: - """Graph: topology of device as a networkx Graph object. - Returns None if the topology is not available for the device. + """Graph: topology of device as a networkx `Graph` object. + Returns `None` if the topology is not available for the device. Examples: >>> import networkx as nx @@ -268,10 +268,10 @@ def topology_graph(self) -> Graph: def _construct_topology_graph(self) -> Graph: """ - Construct topology graph. If no such metadata is available, return None. + Construct topology graph. If no such metadata is available, return `None`. Returns: - Graph: topology of QPU as a networkx Graph object + Graph: topology of QPU as a networkx `Graph` object. """ if hasattr(self.properties, "paradigm") and isinstance( self.properties.paradigm, GateModelQpuParadigmProperties @@ -383,7 +383,8 @@ def get_devices( provider_names (List[str], optional): provider name list, default is `None` order_by (str, optional): field to order result by, default is `name`. Accepted values are ['arn', 'name', 'type', 'provider_name', 'status'] - aws_session (AwsSession, optional) aws_session: An AWS session object. Default = None. + aws_session (AwsSession, optional) aws_session: An AWS session object. + Default is `None`. Returns: List[AwsDevice]: list of AWS devices From d0a938f33004c03eee6307d131bf004aa5252f9f Mon Sep 17 00:00:00 2001 From: Katharine Hyatt <67932820+kshyatt-aws@users.noreply.github.com> Date: Fri, 4 Dec 2020 16:43:42 -0500 Subject: [PATCH 0237/1165] doc: fix backticks, missing periods in quantum task docs (#183) --- src/braket/aws/aws_quantum_task.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 4a602682..50f26694 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -92,7 +92,7 @@ def create( without any rewiring downstream, if this is supported by the device. Only applies to digital, gate-based circuits (as opposed to annealing problems). If ``True``, no qubit rewiring is allowed; if ``False``, qubit rewiring is allowed. - Default: False + Default: ``False``. tags (Dict[str, str]): Tags, which are Key-Value pairs to add to this quantum task. An example would be: @@ -149,11 +149,11 @@ def __init__( aws_session (AwsSession, optional): The `AwsSession` for connecting to AWS services. Default is `None`, in which case an `AwsSession` object will be created with the region of the task. - poll_timeout_seconds (float): The polling timeout for result(). Default: 5 days. - poll_interval_seconds (float): The polling interval for result(). Default: 1 second. - poll_outside_execution_window (bool): Whether or not to poll for result() when the + poll_timeout_seconds (float): The polling timeout for `result()`. Default: 5 days. + poll_interval_seconds (float): The polling interval for `result()`. Default: 1 second. + poll_outside_execution_window (bool): Whether or not to poll for `result()` when the current time is outside of the execution window for the associated device, - default is False. Tasks are expected to only run during the execution window. + default is `False`. Tasks are expected to only run during the execution window. logger (Logger): Logger object with which to write logs, such as task statuses while waiting for task to be in a terminal state. Default is `getLogger(__name__)` @@ -189,7 +189,7 @@ def _aws_session_for_task_arn(task_arn: str) -> AwsSession: Get an AwsSession for the Task ARN. The AWS session should be in the region of the task. Returns: - AwsSession: `AwsSession` object with default `boto_session` in task's region + AwsSession: `AwsSession` object with default `boto_session` in task's region. """ task_region = task_arn.split(":")[3] boto_session = boto3.Session(region_name=task_region) @@ -270,12 +270,12 @@ def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult `GateModelQuantumTaskResult` or `AnnealingQuantumTaskResult` This method is a blocking thread call and synchronously returns a result. - Call async_result() if you require an asynchronous invocation. + Call `async_result()` if you require an asynchronous invocation. Consecutive calls to this method return a cached result from the preceding request. Returns: Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: The result of the task, - if the task completed successfully; returns None if the task did not complete + if the task completed successfully; returns `None` if the task did not complete successfully or the future timed out. """ if self._result or self._status(True) in self.NO_RESULT_TERMINAL_STATES: @@ -321,7 +321,7 @@ async def _create_future(self) -> asyncio.Task: that contains it. Note that this does not block on the coroutine to finish. Returns: - asyncio.Task: An asyncio Task that contains the _wait_for_completion() coroutine. + asyncio.Task: An asyncio Task that contains the `_wait_for_completion()` coroutine. """ return asyncio.create_task(self._wait_for_completion()) @@ -386,7 +386,7 @@ async def _wait_for_completion( def _is_polling_time(self) -> bool: """ - Return if it's time to poll for result() + Return if it's time to poll for `result()`. """ if self._poll_outside_execution_window: return True From 65923ebbb5bfd42c7769ec24db1a0216a418722d Mon Sep 17 00:00:00 2001 From: Katharine Hyatt <67932820+kshyatt-aws@users.noreply.github.com> Date: Fri, 4 Dec 2020 16:55:27 -0500 Subject: [PATCH 0238/1165] doc: add punctuation to aws_session.py (#184) --- src/braket/aws/aws_session.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 328d0873..549782c0 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -26,9 +26,9 @@ class AwsSession(object): def __init__(self, boto_session=None, braket_client=None, config=None): """ Args: - boto_session: A boto3 session object - braket_client: A boto3 Braket client - config: A botocore Config object + boto_session: A boto3 session object. + braket_client: A boto3 Braket client. + config: A botocore Config object. """ self.boto_session = boto_session or boto3.Session() @@ -96,14 +96,14 @@ def get_quantum_task(self, arn: str) -> Dict[str, Any]: 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 - s3_object_key (str): The S3 object key within the `s3_bucket` + s3_bucket (str): The S3 bucket name. + s3_object_key (str): The S3 object key within the `s3_bucket`. Returns: - str: The body of the S3 object + str: The body of the S3 object. """ s3 = self.boto_session.resource("s3", config=self._config) obj = s3.Object(s3_bucket, s3_object_key) @@ -112,10 +112,10 @@ def retrieve_s3_object_body(self, s3_bucket: str, s3_object_key: str) -> str: def get_device(self, arn: str) -> Dict[str, Any]: """ Calls the Amazon Braket `get_device` API to - retrieve device metadata + retrieve device metadata. Args: - arn (str): The ARN of the device + arn (str): The ARN of the device. Returns: Dict[str, Any]: The response from the Amazon Braket `GetDevice` operation. @@ -135,11 +135,11 @@ def search_devices( all the filters `arns`, `names`, `types`, `statuses`, `provider_names`. Args: - arns (List[str], optional): device ARN list, default is `None` - names (List[str], optional): device name list, default is `None` - types (List[str], optional): device type list, default is `None` - statuses (List[str], optional): device status list, default is `None` - provider_names (List[str], optional): provider name list, default is `None` + arns (List[str], optional): device ARN list, default is `None`. + names (List[str], optional): device name list, default is `None`. + types (List[str], optional): device type list, default is `None`. + statuses (List[str], optional): device status list, default is `None`. + provider_names (List[str], optional): provider name list, default is `None`. Returns: List[Dict[str, Any]: The response from the Amazon Braket `SearchDevices` operation. From b67507a98dfdb9ee0d2fab4fce7984e968ad1c9b Mon Sep 17 00:00:00 2001 From: Katharine Hyatt <67932820+kshyatt-aws@users.noreply.github.com> Date: Fri, 4 Dec 2020 17:02:29 -0500 Subject: [PATCH 0239/1165] doc: backticks for batching tasks (#185) --- src/braket/aws/aws_quantum_task_batch.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index ada57469..76cb3cee 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -70,7 +70,7 @@ def __init__( Batch creation will fail if this value is greater than the maximum allowed concurrent tasks on the device. max_workers (int): The maximum number of thread pool workers. Default: 100 - poll_timeout_seconds (float): The polling timeout for AwsQuantumTask.result(), + poll_timeout_seconds (float): The polling timeout for `AwsQuantumTask.result()`, in seconds. Default: 5 days. poll_interval_seconds (float): The polling interval for results in seconds. Default: 1 second. @@ -190,18 +190,18 @@ def results(self, fail_unsuccessful=False, max_retries=MAX_RETRIES, use_cached_v have reached a terminal state. The result of this method is cached. Args: - fail_unsuccessful (bool): If set to True, this method will fail + fail_unsuccessful (bool): If set to `True`, this method will fail if any task in the batch fails to return a result even after `max_retries` retries. max_retries (int): Maximum number of times to retry any failed tasks, - i.e. any tasks in the FAILED or CANCELLED state or that didn't - complete within the timeout. Default: 3 - use_cached_value (bool): If False, will refetch the results from S3, - even when results have already been cached. Default: True + i.e. any tasks in the `FAILED` or `CANCELLED` state or that didn't + complete within the timeout. Default: 3. + use_cached_value (bool): If `False`, will refetch the results from S3, + even when results have already been cached. Default: `True`. Returns: List[AwsQuantumTask]: The results of all of the tasks in the batch. - FAILED, CANCELLED, or timed out tasks will have a result of None + `FAILED`, `CANCELLED`, or timed out tasks will have a result of None """ if not self._results or not use_cached_value: self._results = AwsQuantumTaskBatch._retrieve_results(self._tasks, self._max_workers) @@ -229,7 +229,7 @@ def _retrieve_results(tasks, max_workers): def retry_unsuccessful_tasks(self): """Retries any tasks in the batch without valid results. - This method should only be called after results() has been called at least once. + This method should only be called after `results()` has been called at least once. The method will generate new tasks for any failed tasks, so `self.task` and `self.results()` may return different values after a call to this method. From 92aaafa273eab5eaa2432dfe685251909e71d7e2 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 4 Dec 2020 15:12:51 -0800 Subject: [PATCH 0240/1165] feature: Always accept identity observable factors (#178) This enables simultaneous measurement of qubit-wise commuting Pauli words. --- README.md | 23 +++++-- src/braket/circuits/circuit.py | 61 ++++++++++++------- .../braket/circuits/test_circuit.py | 32 +++++++++- tox.ini | 19 +++--- 4 files changed, 98 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 7a7e11fb..880bd377 100644 --- a/README.md +++ b/README.md @@ -159,19 +159,29 @@ To view the generated documentation, open the following file in a browser: This repository has both unit and integration tests. To run the tests, make sure to install test dependencies first: + ```bash pip install -e "amazon-braket-sdk-python[test]" ``` ### Unit Tests + +To run the unit tests: + ```bash tox -e unit-tests ``` -You can also pass in various pytest arguments `tox -e unit-tests -- your-arguments` to run selected tests. For more information, please see [pytest usage](https://docs.pytest.org/en/stable/usage.html). +You can also pass in various pytest arguments to run selected tests: + +```bash +tox -e unit-tests -- your-arguments +``` +For more information, please see [pytest usage](https://docs.pytest.org/en/stable/usage.html). + +To run linters and doc generators and unit tests: -To run linters and doc generators and unit tests ```bash tox ``` @@ -186,12 +196,17 @@ After you create a profile, use the following command to set the `AWS_PROFILE` s export AWS_PROFILE=YOUR_PROFILE_NAME ``` -Run the tests +Run the tests: + ```bash tox -e integ-tests ``` -You can also pass in various pytest arguments `tox -e integ-tests -- your-arguments` to run selected tests. For more information, please see [pytest usage](https://docs.pytest.org/en/stable/usage.html). +As with unit tests, you can also pass in various pytest arguments: + +```bash +tox -e integ-tests -- your-arguments +``` ## License This project is licensed under the Apache-2.0 License. \ No newline at end of file diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 1bdb745c..bdcd5410 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -256,9 +256,9 @@ def _add_to_qubit_observable_mapping(self, result_type: ResultType) -> None: observable = result_type.observable else: return + targets = result_type.target or list(self._qubit_observable_set) all_qubits_observable = self._qubit_observable_mapping.get(Circuit._ALL_QUBITS) - for i in range(len(targets)): target = targets[i] tensor_product_dict = ( @@ -268,34 +268,32 @@ def _add_to_qubit_observable_mapping(self, result_type: ResultType) -> None: ) new_observable = tensor_product_dict[i][0] if tensor_product_dict else observable current_observable = all_qubits_observable or self._qubit_observable_mapping.get(target) - if current_observable and current_observable != new_observable: - raise ValueError( - f"Existing result type for observable {current_observable} for target {target}" - f" conflicts with observable {new_observable} for new result type" - ) + + add_observable = Circuit._validate_observable_to_add_for_qubit( + current_observable, new_observable, target + ) if result_type.target: - if new_observable.qubit_count > 1: - new_targets = ( - tuple( - result_type.target[ - tensor_product_dict[i][1][0] : tensor_product_dict[i][1][1] - ] - ) - if tensor_product_dict - else tuple(result_type.target) + new_targets = ( + tuple( + result_type.target[ + tensor_product_dict[i][1][0] : tensor_product_dict[i][1][1] + ] ) + if tensor_product_dict + else tuple(result_type.target) + ) + if add_observable: + self._qubit_target_mapping[target] = new_targets + self._qubit_observable_mapping[target] = new_observable + elif new_observable.qubit_count > 1 and new_observable != Observable.I(): current_target = self._qubit_target_mapping.get(target) if current_target and current_target != new_targets: raise ValueError( - f"Target order {current_target} of existing result type with observable" - f" {current_observable} conflicts with order {targets} of new" - " result type" + f"Target order {current_target} of existing result type with" + f" observable {current_observable} conflicts with order {targets}" + " of new result type" ) - self._qubit_target_mapping[target] = new_targets - else: - self._qubit_target_mapping[target] = tuple([target]) - self._qubit_observable_mapping[target] = new_observable if not result_type.target: if all_qubits_observable and all_qubits_observable != observable: @@ -305,6 +303,25 @@ def _add_to_qubit_observable_mapping(self, result_type: ResultType) -> None: ) self._qubit_observable_mapping[Circuit._ALL_QUBITS] = observable + @staticmethod + def _validate_observable_to_add_for_qubit(current_observable, new_observable, target): + identity = Observable.I() + add_observable = False + if not current_observable or ( + current_observable == identity and new_observable != identity + ): + add_observable = True + elif ( + current_observable != identity + and new_observable != identity + and current_observable != new_observable + ): + raise ValueError( + f"Observable {new_observable} specified for target {target} conflicts with" + + f" existing observable {current_observable} on this target." + ) + return add_observable + @staticmethod def _tensor_product_index_dict(observable: TensorProduct) -> Dict[int, Observable]: obj_dict = {} diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index d59b0d72..41476ec0 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -485,7 +485,7 @@ def test_basis_rotation_instructions_tensor_product(): assert circ.basis_rotation_instructions == expected -def test_basis_rotation_instructions_tensor_product_commuting(): +def test_basis_rotation_instructions_tensor_product_shared_factors(): circ = ( Circuit() .h(0) @@ -505,6 +505,34 @@ def test_basis_rotation_instructions_tensor_product_commuting(): assert circ.basis_rotation_instructions == expected +def test_basis_rotation_instructions_identity(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .cnot(1, 2) + .cnot(2, 3) + .cnot(3, 4) + .expectation(observable=Observable.X(), target=[0]) + .expectation(observable=Observable.I(), target=[2]) + .expectation(observable=Observable.I() @ Observable.Y(), target=[1, 3]) + .expectation(observable=Observable.I(), target=[0]) + .expectation(observable=Observable.X() @ Observable.I(), target=[1, 3]) + .expectation(observable=Observable.Y(), target=[2]) + ) + expected = [ + Instruction(Gate.H(), 0), + Instruction(Gate.H(), 1), + Instruction(Gate.Z(), 2), + Instruction(Gate.S(), 2), + Instruction(Gate.H(), 2), + Instruction(Gate.Z(), 3), + Instruction(Gate.S(), 3), + Instruction(Gate.H(), 3), + ] + assert circ.basis_rotation_instructions == expected + + def test_basis_rotation_instructions_multiple_result_types_different_targets(): circ = ( Circuit() @@ -623,10 +651,12 @@ def test_basis_rotation_instructions_multiple_result_types_tensor_product_hermit .h(0) .cnot(0, 1) .cnot(1, 2) + .expectation(observable=Observable.I(), target=[1]) .sample( observable=Observable.Hermitian(matrix=np.eye(4)) @ Observable.H(), target=[0, 1, 2] ) .variance(observable=Observable.H(), target=[2]) + .expectation(observable=Observable.I(), target=[0]) ) expected = [ Instruction(Gate.Unitary(matrix=np.eye(4)), target=[0, 1]), diff --git a/tox.ini b/tox.ini index 2fdc9e8a..45c68c57 100644 --- a/tox.ini +++ b/tox.ini @@ -2,8 +2,7 @@ envlist = linters,docs,unit-tests [testenv:unit-tests] -basepython = python3.7 -whitelist_externals = coverage +basepython = python3 # {posargs} contains additional arguments specified when invoking tox. e.g. tox -- -s -k test_foo.py commands = coverage run -m pytest {posargs} @@ -13,7 +12,7 @@ commands = extras = test [testenv:integ-tests] -basepython = python3.7 +basepython = python3 # {posargs} contains additional arguments specified when invoking tox. e.g. tox -- -s -k test_foo.py passenv = AWS_PROFILE @@ -22,7 +21,7 @@ commands = extras = test [testenv:linters] -basepython = python3.7 +basepython = python3 skip_install = true deps = {[testenv:isort]deps} @@ -35,7 +34,7 @@ commands = {[testenv:flake8]commands} [testenv:flake8] -basepython = python3.7 +basepython = python3 skip_install = true deps = flake8 @@ -43,7 +42,7 @@ commands = flake8 {posargs} [testenv:isort] -basepython = python3.7 +basepython = python3 skip_install = true deps = isort @@ -51,7 +50,7 @@ commands = isort -rc . {posargs} [testenv:black] -basepython = python3.7 +basepython = python3 skip_install = true deps = black @@ -59,7 +58,7 @@ commands = black ./ {posargs} [testenv:docs] -basepython = python3.7 +basepython = python3 deps = sphinx sphinx-rtd-theme @@ -68,14 +67,14 @@ commands = sphinx-build -E -T -b html doc build/documentation/html [testenv:serve-docs] -basepython = python3.7 +basepython = python3 skip_install = true changedir = build/documentation/html commands = python -m http.server {posargs} [testenv:zip-build] -basepython = python3.7 +basepython = python3 skip_install = true commands = /bin/sh -c 'tar -czvf build_files.tar.gz build/' From 52b0a578a36d26cf9cd6243a8a1fb90f43a53ee9 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 4 Dec 2020 23:23:28 +0000 Subject: [PATCH 0241/1165] prepare release v1.5.0 --- CHANGELOG.md | 13 +++++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9e3c3cc..542e215d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## v1.5.0 (2020-12-04) + +### Features + + * Always accept identity observable factors + +### Documentation Changes + + * backticks for batching tasks + * add punctuation to aws_session.py + * fix backticks, missing periods in quantum task docs + * fix backticks, grammar for aws_device.py + ## v1.4.1 (2020-12-04) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index f1845183..1f84723b 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.4.2.dev0" +__version__ = "1.5.0" From 858c53480e36bc2227de056d4ac265d58776b3bb Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 4 Dec 2020 23:23:28 +0000 Subject: [PATCH 0242/1165] update development version to v1.5.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 1f84723b..d976fa05 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.5.0" +__version__ = "1.5.1.dev0" From bf724236a3fcfdc5bab1a3dddf28e983cbf90038 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 9 Dec 2020 16:52:25 -0800 Subject: [PATCH 0243/1165] fix: Use current region for simulators in get_devices (#187) AwsDevice.get_devices() creates a new AwsSession object for each region that devices can be found and then creates an AwsDevice object for each device found, using the last AwsSession created. However, there is no guarantee that each device even exists in the session's region. This is fine for QPUs, since the correct region is automatically used; this is not the case for simulators, which have been assumed to exist in every region. TN1 does not exist in every region, get_devices() will always fail. This commit fixes the bug by only instantiating simulators that are available in the current region in AwsDevice.get_devices. Also added integ tests for TN1. --- README.md | 4 +- src/braket/aws/aws_device.py | 51 ++++++----- .../gate_model_device_testing_utils.py | 17 ++-- .../test_simulator_quantum_task.py | 52 ++++++------ .../test_tensor_network_simulator.py | 84 +++++++++++++++++++ .../braket/aws/common_test_utils.py | 2 +- test/unit_tests/braket/aws/test_aws_device.py | 68 ++++++++++++--- 7 files changed, 212 insertions(+), 66 deletions(-) create mode 100644 test/integ_tests/test_tensor_network_simulator.py diff --git a/README.md b/README.md index 880bd377..2352c6ac 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ print(batch.results()[0].measurement_counts) # The result of the first task in ``` ### Available Simulators -Amazon Braket provides access to two types of simulators: the fully managed simulator, available through the Amazon Braket service, and the local simulator that is part of the Amazon Braket SDK. +Amazon Braket provides access to two types of simulators: fully managed simulators, available through the Amazon Braket service, and the local simulator that is part of the Amazon Braket SDK. - Fully managed simulators offer high-performance circuit simulations. These simulators can handle circuits larger than circuits that run on quantum hardware. For example, the SV1 state vector simulator shown in the previous examples requires approximately 1 or 2 hours to complete a 34-qubit, dense, and square circuit (circuit depth = 34), depending on the type of gates used and other factors. For a list of available simulators and their features, consult the [Amazon Braket Developer Guide](https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html). @@ -209,4 +209,4 @@ tox -e integ-tests -- your-arguments ``` ## License -This project is licensed under the Apache-2.0 License. \ No newline at end of file +This project is licensed under the Apache-2.0 License. diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 75bc96b6..94c81168 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -57,9 +57,10 @@ class AwsDevice(Device): DEFAULT_SHOTS_QPU = 1000 DEFAULT_SHOTS_SIMULATOR = 0 - DEFAULT_MAX_PARALLEL = 10 + _GET_DEVICES_ORDER_BY_KEYS = frozenset({"arn", "name", "type", "provider_name", "status"}) + def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): """ Args: @@ -304,7 +305,7 @@ def _default_max_parallel(self): @staticmethod def _aws_session_for_device(device_arn: str, aws_session: Optional[AwsSession]) -> AwsSession: - """AwsSession: Returns an AwsSession for the device ARN. """ + """AwsSession: Returns an AwsSession for the device ARN.""" if "qpu" in device_arn: return AwsDevice._aws_session_for_qpu(device_arn, aws_session) else: @@ -345,7 +346,7 @@ def _copy_aws_session( ) return AwsSession(boto_session=boto_session, config=config) else: - boto_session = boto3.Session(region_name=regions[0]) + boto_session = boto3.Session(region_name=regions[0]) if regions else None return AwsSession(boto_session=boto_session, config=config) def __repr__(self): @@ -389,24 +390,41 @@ def get_devices( Returns: List[AwsDevice]: list of AWS devices """ - order_by_list = ["arn", "name", "type", "provider_name", "status"] - if order_by not in order_by_list: - raise ValueError(f"order_by '{order_by}' must be in {order_by_list}") - device_regions_set = AwsDevice._get_devices_regions_set(arns, provider_names, types) - results = [] + 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}" + ) + aws_session = aws_session if aws_session else AwsSession() + types = set(types) if types else {AwsDeviceType.QPU, AwsDeviceType.SIMULATOR} + device_map = {} + device_regions_set = AwsDevice._get_devices_regions_set(arns, provider_names) for region in device_regions_set: - aws_session = AwsDevice._copy_aws_session(aws_session, [region]) - results.extend( - aws_session.search_devices( + session_for_region = AwsDevice._copy_aws_session(aws_session, [region]) + # Require simulators to be instantiated + # in the same region as the AWS session + region_device_types = sorted( + types + if region == aws_session.boto_session.region_name + else types - {AwsDeviceType.SIMULATOR} + ) + region_device_arns = [ + result["deviceArn"] + for result in session_for_region.search_devices( arns=arns, names=names, - types=types, + types=region_device_types, statuses=statuses, 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 + } ) - arns = set([result["deviceArn"] for result in results]) - devices = [AwsDevice(arn, aws_session) for arn in arns] + devices = list(device_map.values()) devices.sort(key=lambda x: getattr(x, order_by)) return devices @@ -414,7 +432,6 @@ def get_devices( def _get_devices_regions_set( arns: Optional[List[str]], provider_names: Optional[List[str]], - types: Optional[List[AwsDeviceType]], ) -> Set[str]: """Get the set of regions to call `SearchDevices` API given filters""" device_regions_set = set( @@ -431,8 +448,4 @@ def _get_devices_regions_set( if arns: arns_region_set = set([AwsDevice.DEVICE_REGIONS[arn.split("/")[-2]][0] for arn in arns]) device_regions_set = device_regions_set.intersection(arns_region_set) - if types and types == [AwsDeviceType.SIMULATOR]: - device_regions_set = device_regions_set.intersection( - [AwsDevice.DEVICE_REGIONS["amazon"][0]] - ) return device_regions_set diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index ce351015..d2621ada 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -39,17 +39,22 @@ def qubit_ordering_testing(device: Device, run_kwargs: Dict[str, Any]): assert result.measurement_counts.most_common(1)[0][0] == "001" -def no_result_types_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): +def no_result_types_testing( + circuit: Circuit, device: Device, run_kwargs: Dict[str, Any], expected: Dict[str, float] +): shots = run_kwargs["shots"] tol = get_tol(shots) - bell = Circuit().h(0).cnot(0, 1) - result = device.run(bell, **run_kwargs).result() - - assert np.allclose(result.measurement_probabilities["00"], 0.5, **tol) - assert np.allclose(result.measurement_probabilities["11"], 0.5, **tol) + result = device.run(circuit, **run_kwargs).result() + probabilities = result.measurement_probabilities + for bitstring in probabilities: + assert np.allclose(probabilities[bitstring], expected[bitstring], **tol) assert len(result.measurements) == shots +def no_result_types_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): + no_result_types_testing(Circuit().h(0).cnot(0, 1), device, run_kwargs, {"00": 0.5, "11": 0.5}) + + def result_types_observable_not_in_instructions(device: Device, run_kwargs: Dict[str, Any]): shots = run_kwargs["shots"] tol = get_tol(shots) diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 3bdd3343..c30d946e 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -35,18 +35,12 @@ from braket.aws import AwsDevice SHOTS = 8000 -SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" +SV1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" +SIMULATOR_ARNS = [SV1_ARN] +ARNS_WITH_SHOTS = [(SV1_ARN, SHOTS), (SV1_ARN, 0)] -@pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) -def test_multithreaded_bell_pair(simulator_arn, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) - multithreaded_bell_pair_testing( - device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} - ) - - -@pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) +@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) no_result_types_bell_pair_testing( @@ -54,13 +48,13 @@ def test_no_result_types_bell_pair(simulator_arn, aws_session, s3_destination_fo ) -@pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) +@pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) def test_qubit_ordering(simulator_arn, aws_session, s3_destination_folder): device = AwsDevice(simulator_arn, aws_session) qubit_ordering_testing(device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder}) -@pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) +@pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) def test_result_types_no_shots(simulator_arn, aws_session, s3_destination_folder): device = AwsDevice(simulator_arn, aws_session) result_types_zero_shots_bell_pair_testing( @@ -68,7 +62,7 @@ def test_result_types_no_shots(simulator_arn, aws_session, s3_destination_folder ) -@pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) +@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) result_types_nonzero_shots_bell_pair_testing( @@ -76,7 +70,7 @@ def test_result_types_nonzero_shots_bell_pair(simulator_arn, aws_session, s3_des ) -@pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) +@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) result_types_bell_pair_full_probability_testing( @@ -84,7 +78,7 @@ def test_result_types_bell_pair_full_probability(simulator_arn, aws_session, s3_ ) -@pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) +@pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) def test_result_types_bell_pair_marginal_probability( simulator_arn, aws_session, s3_destination_folder ): @@ -94,7 +88,7 @@ def test_result_types_bell_pair_marginal_probability( ) -@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS), (SIMULATOR_ARN, 0)]) +@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) result_types_tensor_x_y_testing( @@ -102,7 +96,7 @@ def test_result_types_tensor_x_y(simulator_arn, shots, aws_session, s3_destinati ) -@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS), (SIMULATOR_ARN, 0)]) +@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) result_types_tensor_z_h_y_testing( @@ -110,7 +104,7 @@ def test_result_types_tensor_z_h_y(simulator_arn, shots, aws_session, s3_destina ) -@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS), (SIMULATOR_ARN, 0)]) +@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) result_types_hermitian_testing( @@ -118,7 +112,7 @@ def test_result_types_hermitian(simulator_arn, shots, aws_session, s3_destinatio ) -@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS), (SIMULATOR_ARN, 0)]) +@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) result_types_tensor_z_z_testing( @@ -126,7 +120,7 @@ def test_result_types_tensor_z_z(simulator_arn, shots, aws_session, s3_destinati ) -@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS), (SIMULATOR_ARN, 0)]) +@pytest.mark.parametrize("simulator_arn,shots", ARNS_WITH_SHOTS) def test_result_types_tensor_hermitian_hermitian( simulator_arn, shots, aws_session, s3_destination_folder ): @@ -136,7 +130,7 @@ def test_result_types_tensor_hermitian_hermitian( ) -@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS), (SIMULATOR_ARN, 0)]) +@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) result_types_tensor_y_hermitian_testing( @@ -144,7 +138,7 @@ def test_result_types_tensor_y_hermitian(simulator_arn, shots, aws_session, s3_d ) -@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS), (SIMULATOR_ARN, 0)]) +@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) result_types_tensor_z_hermitian_testing( @@ -152,7 +146,7 @@ def test_result_types_tensor_z_hermitian(simulator_arn, shots, aws_session, s3_d ) -@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) +@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) result_types_all_selected_testing( @@ -160,7 +154,7 @@ def test_result_types_all_selected(simulator_arn, shots, aws_session, s3_destina ) -@pytest.mark.parametrize("simulator_arn,shots", [(SIMULATOR_ARN, SHOTS)]) +@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 ): @@ -170,7 +164,15 @@ def test_result_types_observable_not_in_instructions( ) -@pytest.mark.parametrize("simulator_arn", [SIMULATOR_ARN]) +@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) + 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) batch_bell_pair_testing( diff --git a/test/integ_tests/test_tensor_network_simulator.py b/test/integ_tests/test_tensor_network_simulator.py new file mode 100644 index 00000000..13ebff39 --- /dev/null +++ b/test/integ_tests/test_tensor_network_simulator.py @@ -0,0 +1,84 @@ +# Copyright 2019-2019 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 random + +import pytest +from gate_model_device_testing_utils import no_result_types_testing + +from braket.aws import AwsDevice +from braket.circuits import Circuit + +SHOTS = 1000 +TN1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/tn1" +SIMULATOR_ARNS = [TN1_ARN] + + +@pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) +def test_ghz(simulator_arn, aws_session, s3_destination_folder): + num_qubits = 50 + circuit = _ghz(num_qubits) + device = AwsDevice(simulator_arn, aws_session) + no_result_types_testing( + circuit, + device, + {"shots": SHOTS, "s3_destination_folder": s3_destination_folder}, + {"0" * num_qubits: 0.5, "1" * num_qubits: 0.5}, + ) + + +@pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) +def test_qft_iqft_h(simulator_arn, aws_session, s3_destination_folder): + num_qubits = 24 + h_qubit = random.randint(0, num_qubits) + circuit = _inverse_qft(_qft(Circuit().h(h_qubit), num_qubits), num_qubits) + device = AwsDevice(simulator_arn, aws_session) + no_result_types_testing( + circuit, + device, + {"shots": SHOTS, "s3_destination_folder": s3_destination_folder}, + {"0" * num_qubits: 0.5, "0" * h_qubit + "1" + "0" * (num_qubits - h_qubit - 1): 0.5}, + ) + + +def _ghz(num_qubits): + circuit = Circuit() + circuit.h(0) + for qubit in range(num_qubits - 1): + circuit.cnot(qubit, qubit + 1) + return circuit + + +def _qft(circuit, num_qubits): + for i in range(num_qubits): + circuit.h(i) + for j in range(1, num_qubits - i): + circuit.cphaseshift(i + j, i, math.pi / (2 ** j)) + + for qubit in range(math.floor(num_qubits / 2)): + circuit.swap(qubit, num_qubits - qubit - 1) + + return circuit + + +def _inverse_qft(circuit, num_qubits): + for qubit in range(math.floor(num_qubits / 2)): + circuit.swap(qubit, num_qubits - qubit - 1) + + for i in reversed(range(num_qubits)): + for j in reversed(range(1, num_qubits - i)): + circuit.cphaseshift(i + j, i, -math.pi / (2 ** j)) + circuit.h(i) + + return circuit diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 7ecf8ffa..6bae1c71 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -19,7 +19,7 @@ DWAVE_ARN = "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6" RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-8" IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/ionQdevice" -SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" +SV1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" class MockS3: diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 69755c39..248b5980 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -18,7 +18,7 @@ DWAVE_ARN, IONQ_ARN, RIGETTI_ARN, - SIMULATOR_ARN, + SV1_ARN, run_and_assert, run_batch_and_assert, ) @@ -573,13 +573,13 @@ def _assert_device_fields(device, expected_properties, expected_device_data): @patch("braket.aws.aws_device.AwsDevice._copy_aws_session") -def test_get_devices(mock_copy_aws_session): - aws_session = Mock() - mock_copy_aws_session.return_value = aws_session - aws_session.search_devices.side_effect = [ +def test_get_devices(mock_copy_aws_session, aws_session): + session_for_region = Mock() + mock_copy_aws_session.return_value = session_for_region + session_for_region.search_devices.side_effect = [ [ { - "deviceArn": SIMULATOR_ARN, + "deviceArn": SV1_ARN, "deviceName": "SV1", "deviceType": "SIMULATOR", "deviceStatus": "ONLINE", @@ -595,7 +595,7 @@ def test_get_devices(mock_copy_aws_session): "providerName": "Rigetti", }, { - "deviceArn": SIMULATOR_ARN, + "deviceArn": SV1_ARN, "deviceName": "SV1", "deviceType": "SIMULATOR", "deviceStatus": "ONLINE", @@ -603,16 +603,60 @@ def test_get_devices(mock_copy_aws_session): }, ], ] - aws_session.get_device.side_effect = [MOCK_GATE_MODEL_SIMULATOR, MOCK_GATE_MODEL_QPU_1] + session_for_region.get_device.side_effect = [MOCK_GATE_MODEL_SIMULATOR, MOCK_GATE_MODEL_QPU_1] results = AwsDevice.get_devices( - arns=[SIMULATOR_ARN, RIGETTI_ARN], + arns=[SV1_ARN, RIGETTI_ARN], types=["SIMULATOR", "QPU"], provider_names=["Amazon Braket", "Rigetti"], statuses=["ONLINE"], + aws_session=aws_session, ) assert [result.name for result in results] == ["Aspen-8", "SV1"] +@pytest.mark.parametrize( + "region,types", [("us-west-1", ["QPU", "SIMULATOR"]), ("us-west-2", ["QPU"])] +) +@patch("braket.aws.aws_device.AwsDevice._copy_aws_session") +def test_get_devices_session_regions(mock_copy_aws_session, region, types): + _boto_session = Mock() + _boto_session.region_name = region + _aws_session = Mock() + _aws_session.boto_session = _boto_session + + session_for_region = Mock() + mock_copy_aws_session.return_value = session_for_region + session_for_region.search_devices.return_value = [ + { + "deviceArn": SV1_ARN, + "deviceName": "SV1", + "deviceType": "SIMULATOR", + "deviceStatus": "ONLINE", + "providerName": "Amazon Braket", + } + ] + + session_for_region.get_device.return_value = MOCK_GATE_MODEL_SIMULATOR + arns = [RIGETTI_ARN] + provider_names = ["Rigetti"] + statuses = ["ONLINE"] + + AwsDevice.get_devices( + arns=arns, + types=["SIMULATOR", "QPU"], + provider_names=provider_names, + statuses=["ONLINE"], + aws_session=_aws_session, + ) + session_for_region.search_devices.assert_called_with( + arns=arns, + names=None, + types=types, + statuses=statuses, + provider_names=provider_names, + ) + + @pytest.mark.xfail(raises=ValueError) def test_get_devices_invalid_order_by(): AwsDevice.get_devices(order_by="foo") @@ -622,18 +666,16 @@ def test_get_devices_invalid_order_by(): "input,output", [ ( - {"arns": None, "types": None, "provider_names": None}, + {"arns": None, "provider_names": None}, {"us-west-2", "us-west-1", "us-east-1"}, ), - ({"arns": None, "types": ["SIMULATOR"], "provider_names": None}, {"us-west-2"}), ( - {"arns": [RIGETTI_ARN, DWAVE_ARN], "types": ["QPU"], "provider_names": None}, + {"arns": [RIGETTI_ARN, DWAVE_ARN], "provider_names": None}, {"us-west-2", "us-west-1"}, ), ( { "arns": [RIGETTI_ARN, DWAVE_ARN, IONQ_ARN], - "types": ["QPU"], "provider_names": ["Rigetti", "Amazon Braket", "IONQ", "FOO"], }, {"us-west-2", "us-west-1", "us-east-1"}, From 6e92e638583083628020cc719edebecce5fde137 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 10 Dec 2020 01:10:26 +0000 Subject: [PATCH 0244/1165] prepare release v1.5.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 542e215d..42100ee4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.5.1 (2020-12-10) + +### Bug Fixes and Other Changes + + * Use current region for simulators in get_devices + ## v1.5.0 (2020-12-04) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d976fa05..e22180ce 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.5.1.dev0" +__version__ = "1.5.1" From eca1f144eaca55ce320cb292674b966588bdd84d Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 10 Dec 2020 01:10:26 +0000 Subject: [PATCH 0245/1165] update development version to v1.5.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 e22180ce..42fab534 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.5.1" +__version__ = "1.5.2.dev0" From 5164b39363dd2d8d46152953e066c33789babe8c Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 17 Dec 2020 13:52:36 -0800 Subject: [PATCH 0246/1165] fix: Do not search for simulators in wrong region (#188) Do not call search_devices at all if requesting simulators only and in a different region. --- src/braket/aws/aws_device.py | 53 ++++++++++--------- test/unit_tests/braket/aws/test_aws_device.py | 25 +++++++-- 2 files changed, 51 insertions(+), 27 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 94c81168..16cac582 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -380,6 +380,8 @@ def get_devices( arns (List[str], optional): device ARN list, default is `None` names (List[str], optional): device name list, default is `None` types (List[AwsDeviceType], optional): device type list, default is `None` + QPUs will be searched for all regions and simulators will only be + searched for the region of the current session. statuses (List[str], optional): device status list, default is `None` provider_names (List[str], optional): provider name list, default is `None` order_by (str, optional): field to order result by, default is `name`. @@ -399,31 +401,34 @@ def get_devices( device_map = {} device_regions_set = AwsDevice._get_devices_regions_set(arns, provider_names) for region in device_regions_set: - session_for_region = AwsDevice._copy_aws_session(aws_session, [region]) - # Require simulators to be instantiated - # in the same region as the AWS session - region_device_types = sorted( - types - if region == aws_session.boto_session.region_name - else types - {AwsDeviceType.SIMULATOR} - ) - region_device_arns = [ - result["deviceArn"] - for result in session_for_region.search_devices( - arns=arns, - names=names, - types=region_device_types, - statuses=statuses, - provider_names=provider_names, + session_region = aws_session.boto_session.region_name + # If the region is not the same as the current session's region + # and only simulators are requested, then search_devices will not + # be called at all because simulators are only retrieved for the + # current region + if region == session_region or types != {AwsDeviceType.SIMULATOR}: + session_for_region = AwsDevice._copy_aws_session(aws_session, [region]) + # Simulators are only instantiated in the same region as the AWS session + region_device_types = sorted( + types if region == session_region else types - {AwsDeviceType.SIMULATOR} + ) + region_device_arns = [ + result["deviceArn"] + for result in session_for_region.search_devices( + arns=arns, + names=names, + types=region_device_types, + statuses=statuses, + 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.update( - { - arn: AwsDevice(arn, session_for_region) - for arn in region_device_arns - if arn not in device_map - } - ) devices = list(device_map.values()) devices.sort(key=lambda x: getattr(x, order_by)) return devices diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 248b5980..6356f9ef 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -625,7 +625,6 @@ def test_get_devices_session_regions(mock_copy_aws_session, region, types): _aws_session.boto_session = _boto_session session_for_region = Mock() - mock_copy_aws_session.return_value = session_for_region session_for_region.search_devices.return_value = [ { "deviceArn": SV1_ARN, @@ -635,8 +634,9 @@ def test_get_devices_session_regions(mock_copy_aws_session, region, types): "providerName": "Amazon Braket", } ] - session_for_region.get_device.return_value = MOCK_GATE_MODEL_SIMULATOR + mock_copy_aws_session.return_value = session_for_region + arns = [RIGETTI_ARN] provider_names = ["Rigetti"] statuses = ["ONLINE"] @@ -645,7 +645,7 @@ def test_get_devices_session_regions(mock_copy_aws_session, region, types): arns=arns, types=["SIMULATOR", "QPU"], provider_names=provider_names, - statuses=["ONLINE"], + statuses=statuses, aws_session=_aws_session, ) session_for_region.search_devices.assert_called_with( @@ -657,6 +657,25 @@ def test_get_devices_session_regions(mock_copy_aws_session, region, types): ) +def test_get_devices_simulator_different_region(): + _boto_session = Mock() + _boto_session.region_name = "us-west-2" + _aws_session = Mock() + _aws_session.boto_session = _boto_session + + assert ( + AwsDevice.get_devices( + arns=None, + types=["SIMULATOR"], + # Force get_devices to only look in us-west-1 + provider_names=["Rigetti"], + statuses=None, + aws_session=_aws_session, + ) + == [] + ) + + @pytest.mark.xfail(raises=ValueError) def test_get_devices_invalid_order_by(): AwsDevice.get_devices(order_by="foo") From 8a4de0e9059fcdf48b10877db765ab3d9d546e7a Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 21 Dec 2020 17:17:13 -0800 Subject: [PATCH 0247/1165] fix: Get regions for QPUs instead of providers (#189) Overhauls get_devices logic to filter regions based on ARNs instead of providers. This future-proofs the SDK for QPUs added to new regions. By getting rid of the region lists, this also fixes a bug where the wrong devices would be returned for regions other than us-west-2, which was the first region in the list. --- src/braket/aws/aws_device.py | 118 +++++++++--------- test/unit_tests/braket/aws/test_aws_device.py | 72 ++++++----- 2 files changed, 102 insertions(+), 88 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 16cac582..21405ad8 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -14,7 +14,7 @@ from __future__ import annotations from enum import Enum -from typing import List, Optional, Set, Union +from typing import FrozenSet, List, Optional, Set, Union import boto3 from boltons.dictutils import FrozenDict @@ -46,14 +46,21 @@ class AwsDevice(Device): device. """ - DEVICE_REGIONS = FrozenDict( + SIMULATOR_ARNS = frozenset( { - "rigetti": ["us-west-1"], - "ionq": ["us-east-1"], - "d-wave": ["us-west-2"], - "amazon": ["us-west-2", "us-west-1", "us-east-1"], + "arn:aws:braket:::device/quantum-simulator/amazon/sv1", + "arn:aws:braket:::device/quantum-simulator/amazon/tn1", } ) + QPU_REGIONS = FrozenDict( + { + "arn:aws:braket:::device/qpu/rigetti/Aspen-8": "us-west-1", + "arn:aws:braket:::device/qpu/ionq/ionQdevice": "us-east-1", + "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6": "us-west-2", + "arn:aws:braket:::device/qpu/d-wave/Advantage_system1": "us-west-2", + } + ) + REGIONS = frozenset(QPU_REGIONS.values()) DEFAULT_SHOTS_QPU = 1000 DEFAULT_SHOTS_SIMULATOR = 0 @@ -320,21 +327,19 @@ def _aws_session_for_qpu(device_arn: str, aws_session: Optional[AwsSession]) -> See `braket.aws.aws_qpu.AwsDevice.DEVICE_REGIONS` for the AWS Regions the devices are located in. """ - region_key = device_arn.split("/")[-2] - qpu_regions = AwsDevice.DEVICE_REGIONS.get(region_key, []) - return AwsDevice._copy_aws_session(aws_session, qpu_regions) + return AwsDevice._copy_aws_session(aws_session, AwsDevice.QPU_REGIONS.get(device_arn), None) @staticmethod def _copy_aws_session( aws_session: Optional[AwsSession], - regions: List[str] = None, + region: Optional[str] = None, max_connections: Optional[int] = None, ) -> AwsSession: config = Config(max_pool_connections=max_connections) if max_connections else None if aws_session: session_region = aws_session.boto_session.region_name - new_regions = regions or [session_region] - if session_region in new_regions and not config: + new_region = region or session_region + if session_region == new_region and not config: return aws_session else: creds = aws_session.boto_session.get_credentials() @@ -342,11 +347,11 @@ def _copy_aws_session( aws_access_key_id=creds.access_key, aws_secret_access_key=creds.secret_key, aws_session_token=creds.token, - region_name=session_region if session_region in new_regions else new_regions[0], + region_name=new_region, ) return AwsSession(boto_session=boto_session, config=config) else: - boto_session = boto3.Session(region_name=regions[0]) if regions else None + boto_session = boto3.Session(region_name=region) if region else None return AwsSession(boto_session=boto_session, config=config) def __repr__(self): @@ -397,60 +402,55 @@ def get_devices( f"order_by '{order_by}' must be in {AwsDevice._GET_DEVICES_ORDER_BY_KEYS}" ) aws_session = aws_session if aws_session else AwsSession() - types = set(types) if types else {AwsDeviceType.QPU, AwsDeviceType.SIMULATOR} + types = ( + frozenset(types) if types else frozenset({device_type for device_type in AwsDeviceType}) + ) device_map = {} - device_regions_set = AwsDevice._get_devices_regions_set(arns, provider_names) + session_region = aws_session.boto_session.region_name + device_regions_set = AwsDevice._get_devices_regions_set(types, arns, session_region) for region in device_regions_set: - session_region = aws_session.boto_session.region_name - # If the region is not the same as the current session's region - # and only simulators are requested, then search_devices will not - # be called at all because simulators are only retrieved for the - # current region - if region == session_region or types != {AwsDeviceType.SIMULATOR}: - session_for_region = AwsDevice._copy_aws_session(aws_session, [region]) - # Simulators are only instantiated in the same region as the AWS session - region_device_types = sorted( - types if region == session_region else types - {AwsDeviceType.SIMULATOR} - ) - region_device_arns = [ - result["deviceArn"] - for result in session_for_region.search_devices( - arns=arns, - names=names, - types=region_device_types, - statuses=statuses, - 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 - } + session_for_region = AwsDevice._copy_aws_session(aws_session, region) + # Simulators are only instantiated in the same region as the AWS session + types_for_region = sorted( + types if region == session_region else types - {AwsDeviceType.SIMULATOR} + ) + region_device_arns = [ + result["deviceArn"] + for result in session_for_region.search_devices( + arns=arns, + names=names, + types=types_for_region, + statuses=statuses, + 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 + } + ) devices = list(device_map.values()) devices.sort(key=lambda x: getattr(x, order_by)) return devices @staticmethod def _get_devices_regions_set( - arns: Optional[List[str]], - provider_names: Optional[List[str]], - ) -> Set[str]: + types: Optional[Set[AwsDeviceType]], arns: Optional[List[str]], current_region: str + ) -> FrozenSet[str]: """Get the set of regions to call `SearchDevices` API given filters""" - device_regions_set = set( - AwsDevice.DEVICE_REGIONS[key][0] for key in AwsDevice.DEVICE_REGIONS + device_regions_set = ( + {current_region} if types == {AwsDeviceType.SIMULATOR} else set(AwsDevice.REGIONS) ) - if provider_names: - provider_region_set = set() - for provider in provider_names: - for key in AwsDevice.DEVICE_REGIONS: - if key in provider.lower(): - provider_region_set.add(AwsDevice.DEVICE_REGIONS[key][0]) - break - device_regions_set = device_regions_set.intersection(provider_region_set) if arns: - arns_region_set = set([AwsDevice.DEVICE_REGIONS[arn.split("/")[-2]][0] for arn in arns]) - device_regions_set = device_regions_set.intersection(arns_region_set) - return device_regions_set + arns_region_set = set() + for arn in arns: + if arn in AwsDevice.QPU_REGIONS: + arns_region_set.add(AwsDevice.QPU_REGIONS[arn]) + elif arn in AwsDevice.SIMULATOR_ARNS: + arns_region_set.add(current_region) + else: + arns_region_set.update(AwsDevice.REGIONS) + device_regions_set &= arns_region_set + return frozenset(device_regions_set) diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 6356f9ef..d218e6ae 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -156,7 +156,7 @@ ) MOCK_DWAVE_QPU = { - "deviceName": "name3", + "deviceName": "DW_2000Q_6", "deviceType": "QPU", "providerName": "provider1", "deviceStatus": "ONLINE", @@ -199,8 +199,6 @@ "deviceCapabilities": MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES.json(), } -RIGETTI_REGION_KEY = "rigetti" - @pytest.fixture def arn(): @@ -220,14 +218,14 @@ def circuit(): @pytest.fixture def boto_session(): _boto_session = Mock() - _boto_session.region_name = AwsDevice.DEVICE_REGIONS[RIGETTI_REGION_KEY][0] + _boto_session.region_name = AwsDevice.QPU_REGIONS[RIGETTI_ARN] return _boto_session @pytest.fixture def aws_session(): _boto_session = Mock() - _boto_session.region_name = AwsDevice.DEVICE_REGIONS[RIGETTI_REGION_KEY][0] + _boto_session.region_name = AwsDevice.QPU_REGIONS[RIGETTI_ARN] creds = Mock() creds.access_key = "access key" @@ -299,7 +297,7 @@ def test_repr(arn): def test_device_aws_session_in_qpu_region(aws_session): arn = RIGETTI_ARN - aws_session.boto_session.region_name = AwsDevice.DEVICE_REGIONS[RIGETTI_REGION_KEY][0] + aws_session.boto_session.region_name = AwsDevice.QPU_REGIONS[RIGETTI_ARN] aws_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 AwsDevice(arn, aws_session) @@ -312,7 +310,7 @@ def test_aws_session_in_another_qpu_region( boto_session_init, aws_session_init, boto_session, aws_session ): arn = RIGETTI_ARN - region = AwsDevice.DEVICE_REGIONS.get(RIGETTI_REGION_KEY)[0] + region = AwsDevice.QPU_REGIONS.get(RIGETTI_ARN) boto_session_init.return_value = boto_session aws_session_init.return_value = aws_session @@ -348,7 +346,7 @@ def test_device_no_aws_session_supplied( boto_session_init, aws_session_init, boto_session, aws_session ): arn = RIGETTI_ARN - region = AwsDevice.DEVICE_REGIONS.get(RIGETTI_REGION_KEY)[0] + region = AwsDevice.QPU_REGIONS.get(RIGETTI_ARN) boto_session_init.return_value = boto_session aws_session_init.return_value = aws_session @@ -575,7 +573,6 @@ def _assert_device_fields(device, expected_properties, expected_device_data): @patch("braket.aws.aws_device.AwsDevice._copy_aws_session") def test_get_devices(mock_copy_aws_session, aws_session): session_for_region = Mock() - mock_copy_aws_session.return_value = session_for_region session_for_region.search_devices.side_effect = [ [ { @@ -588,11 +585,11 @@ def test_get_devices(mock_copy_aws_session, aws_session): ], [ { - "deviceArn": RIGETTI_ARN, - "deviceName": "Aspen-8", + "deviceArn": DWAVE_ARN, + "deviceName": "DW_2000Q_6", "deviceType": "QPU", "deviceStatus": "ONLINE", - "providerName": "Rigetti", + "providerName": "D-Wave", }, { "deviceArn": SV1_ARN, @@ -603,15 +600,16 @@ def test_get_devices(mock_copy_aws_session, aws_session): }, ], ] - session_for_region.get_device.side_effect = [MOCK_GATE_MODEL_SIMULATOR, MOCK_GATE_MODEL_QPU_1] + session_for_region.get_device.side_effect = [MOCK_GATE_MODEL_SIMULATOR, MOCK_DWAVE_QPU] + mock_copy_aws_session.return_value = session_for_region results = AwsDevice.get_devices( - arns=[SV1_ARN, RIGETTI_ARN], + arns=[SV1_ARN, DWAVE_ARN], types=["SIMULATOR", "QPU"], - provider_names=["Amazon Braket", "Rigetti"], + provider_names=["Amazon Braket", "D-Wave"], statuses=["ONLINE"], aws_session=aws_session, ) - assert [result.name for result in results] == ["Aspen-8", "SV1"] + assert [result.name for result in results] == ["DW_2000Q_6", "SV1"] @pytest.mark.parametrize( @@ -643,7 +641,7 @@ def test_get_devices_session_regions(mock_copy_aws_session, region, types): AwsDevice.get_devices( arns=arns, - types=["SIMULATOR", "QPU"], + types=None, provider_names=provider_names, statuses=statuses, aws_session=_aws_session, @@ -665,10 +663,9 @@ def test_get_devices_simulator_different_region(): assert ( AwsDevice.get_devices( - arns=None, + arns=[RIGETTI_ARN], types=["SIMULATOR"], - # Force get_devices to only look in us-west-1 - provider_names=["Rigetti"], + provider_names=None, statuses=None, aws_session=_aws_session, ) @@ -685,21 +682,38 @@ def test_get_devices_invalid_order_by(): "input,output", [ ( - {"arns": None, "provider_names": None}, + {"types": None, "arns": None}, {"us-west-2", "us-west-1", "us-east-1"}, ), ( - {"arns": [RIGETTI_ARN, DWAVE_ARN], "provider_names": None}, - {"us-west-2", "us-west-1"}, + {"types": {AwsDeviceType.QPU}, "arns": None}, + {"us-west-2", "us-west-1", "us-east-1"}, ), ( - { - "arns": [RIGETTI_ARN, DWAVE_ARN, IONQ_ARN], - "provider_names": ["Rigetti", "Amazon Braket", "IONQ", "FOO"], - }, - {"us-west-2", "us-west-1", "us-east-1"}, + {"types": {AwsDeviceType.SIMULATOR}, "arns": None}, + {"us-west-2"}, + ), + ( + {"types": {AwsDeviceType.QPU, AwsDeviceType.SIMULATOR}, "arns": None}, + {"us-east-1", "us-west-1", "us-west-2"}, + ), + ( + {"types": None, "arns": [RIGETTI_ARN, IONQ_ARN]}, + {"us-east-1", "us-west-1"}, + ), + ( + {"types": None, "arns": [RIGETTI_ARN, SV1_ARN]}, + {"us-west-1", "us-west-2"}, + ), + ( + {"types": {AwsDeviceType.SIMULATOR}, "arns": [RIGETTI_ARN, IONQ_ARN]}, + set(), + ), + ( + {"types": None, "arns": ["blah"]}, + {"us-east-1", "us-west-1", "us-west-2"}, ), ], ) def test_get_devices_regions_set(input, output): - assert AwsDevice._get_devices_regions_set(**input) == output + assert AwsDevice._get_devices_regions_set(current_region="us-west-2", **input) == output From dda45584b7d4b2f6a5d2a4a481bd203d54c875f3 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 22 Dec 2020 21:13:56 +0000 Subject: [PATCH 0248/1165] prepare release v1.5.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 42100ee4..c88449d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.5.2 (2020-12-22) + +### Bug Fixes and Other Changes + + * Get regions for QPUs instead of providers + * Do not search for simulators in wrong region + ## v1.5.1 (2020-12-10) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 42fab534..2c2ffcff 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.5.2.dev0" +__version__ = "1.5.2" From a7f82e5d6d0c16313c060ceba5472f7de4e0cde3 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 22 Dec 2020 21:13:56 +0000 Subject: [PATCH 0249/1165] update development version to v1.5.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 2c2ffcff..5dcd4a35 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.5.2" +__version__ = "1.5.3.dev0" From aa6589593991149b16553d36ba875ae917623eb7 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 29 Dec 2020 14:11:09 -0800 Subject: [PATCH 0250/1165] infra: Use GitHub Actions for CI (#190) --- .github/workflows/publish-to-pypi.yml | 8 +++--- .github/workflows/python-package.yml | 40 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/python-package.yml diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index fa99300b..d1439d2b 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -9,11 +9,11 @@ jobs: name: Build and publish distribution to PyPi runs-on: ubuntu-latest steps: - - uses: actions/checkout@main - - name: Set up Python 3.7 - uses: actions/setup-python@v1 + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: '3.x' - name: Install wheel run: python -m pip install --user --upgrade wheel - name: Install twine diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 00000000..7ecdd973 --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,40 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python package + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ['3.7', '3.8'] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install --upgrade pip pytest + pip install --upgrade git+https://github.com/aws/amazon-braket-schemas-python@main + pip install --upgrade git+https://github.com/aws/amazon-braket-default-simulator-python@main + pip install -e .[test] + - name: Check code format + run: | + # stop the build if there are Python format errors or undefined names + black --check . + flake8 + - name: Run unit tests + run: | + pytest test/unit_tests From 8dc308deb9403b7944e11137f5782d2e66a59ba9 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 29 Dec 2020 15:43:56 -0800 Subject: [PATCH 0251/1165] infra: Add build badge (#191) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2352c6ac..b07775b9 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,9 @@ [![Latest Version](https://img.shields.io/pypi/v/amazon-braket-sdk.svg)](https://pypi.python.org/pypi/amazon-braket-sdk) [![Supported Python Versions](https://img.shields.io/pypi/pyversions/amazon-braket-sdk.svg)](https://pypi.python.org/pypi/amazon-braket-sdk) +[![Build Status](https://img.shields.io/github/workflow/status/aws/amazon-braket-sdk-python/Python%20package/main?logo=github)](https://github.com/aws/amazon-braket-sdk-python/actions?query=workflow%3A%22Python+package%22) +[![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) [![Code Style: Black](https://img.shields.io/badge/code_style-black-000000.svg)](https://github.com/psf/black) -[![Documentation Status](https://readthedocs.org/projects/amazon-braket-sdk-python/badge/?version=latest)](https://amazon-braket-sdk-python.readthedocs.io/en/latest/?badge=latest) 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 2d36dfc6666399511dff12589de99ee25c1fad18 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 30 Dec 2020 18:11:44 +0000 Subject: [PATCH 0252/1165] prepare release v1.5.2.post0 --- 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 c88449d6..e482ac35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.5.2.post0 (2020-12-30) + +### Testing and Release Infrastructure + + * Add build badge + * Use GitHub Actions for CI + ## v1.5.2 (2020-12-22) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 5dcd4a35..e87c59c2 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.5.3.dev0" +__version__ = "1.5.2.post0" From 2b2b05eee2781b5baf288c9e63cec087d6d7d43c Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 30 Dec 2020 18:11:44 +0000 Subject: [PATCH 0253/1165] update development version to v1.5.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 e87c59c2..5dcd4a35 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.5.2.post0" +__version__ = "1.5.3.dev0" From 3ddde05e03643a867c6b4266d835d00ea57c9d67 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Wed, 30 Dec 2020 11:37:06 -0800 Subject: [PATCH 0254/1165] fix: Update range of random qubit in test_qft_iqft_h (#192) --- test/integ_tests/test_tensor_network_simulator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integ_tests/test_tensor_network_simulator.py b/test/integ_tests/test_tensor_network_simulator.py index 13ebff39..bd793402 100644 --- a/test/integ_tests/test_tensor_network_simulator.py +++ b/test/integ_tests/test_tensor_network_simulator.py @@ -41,7 +41,7 @@ def test_ghz(simulator_arn, aws_session, s3_destination_folder): @pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) def test_qft_iqft_h(simulator_arn, aws_session, s3_destination_folder): num_qubits = 24 - h_qubit = random.randint(0, num_qubits) + h_qubit = random.randint(0, num_qubits - 1) circuit = _inverse_qft(_qft(Circuit().h(h_qubit), num_qubits), num_qubits) device = AwsDevice(simulator_arn, aws_session) no_result_types_testing( From c910f234101fa5cfbc62bd48ea2543d1f4d68891 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 31 Dec 2020 18:12:16 +0000 Subject: [PATCH 0255/1165] prepare release v1.5.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 e482ac35..11ff96c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.5.3 (2020-12-31) + +### Bug Fixes and Other Changes + + * Update range of random qubit in test_qft_iqft_h + ## v1.5.2.post0 (2020-12-30) ### Testing and Release Infrastructure diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 5dcd4a35..d1bbc326 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.5.3.dev0" +__version__ = "1.5.3" From 9a2fbe58cdb0258b850526f1492963377475d90d Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 31 Dec 2020 18:12:16 +0000 Subject: [PATCH 0256/1165] update development version to v1.5.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 d1bbc326..bcf37244 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.5.3" +__version__ = "1.5.4.dev0" From 25bd2a51961313ddbc072835f25fab2e6e4dc968 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Fri, 8 Jan 2021 13:35:22 -0800 Subject: [PATCH 0257/1165] change: update result_types to use hashing (#195) * change: update result_types to use hashing use a dict to hold the result types to speed up checking if a result type is in _result_types. result_types property still interfaces as a list. Python 3.7+ officially guarantees order for dicts, so result_types is expected to be in order also edit gate_model_quantum_task_result to add a dict mapping result type to index, as well as ensuring consistency regarding result type in the implementation Co-authored-by: Aaron Berdy --- src/braket/circuits/circuit.py | 11 +++-- src/braket/circuits/result_type.py | 6 +++ src/braket/circuits/result_types.py | 20 ++++++-- .../tasks/gate_model_quantum_task_result.py | 49 ++++++++++++++----- .../test_gate_model_quantum_task_result.py | 45 +++++++++++++++++ 5 files changed, 111 insertions(+), 20 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index bdcd5410..f506a056 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -108,7 +108,7 @@ def __init__(self, addable: AddableTypes = None, *args, **kwargs): """ self._moments: Moments = Moments() - self._result_types: List[ResultType] = [] + self._result_types: Dict[ResultType] = {} self._qubit_observable_mapping: Dict[Union[int, Circuit._ALL_QUBITS], Observable] = {} self._qubit_target_mapping: Dict[int, Tuple[int]] = {} self._qubit_observable_set = set() @@ -129,7 +129,7 @@ def instructions(self) -> Iterable[Instruction]: @property def result_types(self) -> List[ResultType]: """List[ResultType]: Get a list of requested result types in the circuit.""" - return self._result_types + return list(self._result_types.keys()) @property def basis_rotation_instructions(self) -> List[Instruction]: @@ -197,7 +197,7 @@ def add_result_type( Note: target and target_mapping will only be applied to those requested result types with - the attribute `target`. The result_type will be appended to the end of the list of + the attribute `target`. The result_type will be appended to the end of the dict keys of `circuit.result_types` only if it does not already exist in `circuit.result_types` Returns: @@ -246,7 +246,8 @@ def add_result_type( if result_type_to_add not in self._result_types: self._add_to_qubit_observable_mapping(result_type_to_add) self._add_to_qubit_observable_set(result_type_to_add) - self._result_types.append(result_type_to_add) + # using dict as an ordered set, value is arbitrary + self._result_types[result_type_to_add] = None return self def _add_to_qubit_observable_mapping(self, result_type: ResultType) -> None: @@ -614,7 +615,7 @@ def __eq__(self, other): if isinstance(other, Circuit): return ( list(self.instructions) == list(other.instructions) - and self.result_types == self.result_types + and self.result_types == other.result_types ) return NotImplemented diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index 2835b304..5bb03548 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -125,6 +125,9 @@ def register_result_type(cls, result_type: "ResultType"): def __repr__(self) -> str: return f"{self.name}()" + def __hash__(self) -> int: + return hash(repr(self)) + class ObservableResultType(ResultType): """ @@ -197,3 +200,6 @@ def __repr__(self) -> str: def __copy__(self) -> ObservableResultType: return type(self)(observable=self.observable, target=self.target) + + def __hash__(self) -> int: + return super().__hash__() diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index da2b2d34..74b73b99 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -65,6 +65,11 @@ def __eq__(self, other) -> bool: def __copy__(self) -> StateVector: return type(self)() + # must redefine __hash__ since __eq__ is overwritten + # https://docs.python.org/3/reference/datamodel.html#object.__hash__ + def __hash__(self) -> int: + return super().__hash__() + ResultType.register_result_type(StateVector) @@ -135,6 +140,9 @@ def __repr__(self): def __copy__(self): return type(self)(state=self.state) + def __hash__(self) -> int: + return super().__hash__() + ResultType.register_result_type(Amplitude) @@ -174,7 +182,8 @@ def target(self, target: QubitSetInput) -> None: def to_ir(self) -> ir.Probability: if self.target: - return ir.Probability.construct(targets=list(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() @@ -207,6 +216,9 @@ def __repr__(self) -> str: def __copy__(self) -> Probability: return type(self)(target=self.target) + def __hash__(self) -> int: + return super().__hash__() + ResultType.register_result_type(Probability) @@ -251,7 +263,7 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): def to_ir(self) -> ir.Expectation: if self.target: return ir.Expectation.construct( - observable=self.observable.to_ir(), targets=list(self.target) + observable=self.observable.to_ir(), targets=[int(qubit) for qubit in self.target] ) else: return ir.Expectation.construct(observable=self.observable.to_ir()) @@ -318,7 +330,7 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): def to_ir(self) -> ir.Sample: if self.target: return ir.Sample.construct( - observable=self.observable.to_ir(), targets=list(self.target) + observable=self.observable.to_ir(), targets=[int(qubit) for qubit in self.target] ) else: return ir.Sample.construct(observable=self.observable.to_ir()) @@ -386,7 +398,7 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): def to_ir(self) -> ir.Variance: if self.target: return ir.Variance.construct( - observable=self.observable.to_ir(), targets=list(self.target) + observable=self.observable.to_ir(), targets=[int(qubit) for qubit in self.target] ) else: return ir.Variance.construct(observable=self.observable.to_ir()) diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 06e3b08a..888e86d6 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -21,6 +21,7 @@ from braket.circuits import Observable, ResultType, StandardObservable from braket.circuits.observables import observable_from_ir +from braket.ir.jaqcd import Expectation, Probability, Sample, Variance from braket.task_result import ( AdditionalMetadata, GateModelTaskResult, @@ -31,6 +32,10 @@ T = TypeVar("T") +def result_type_hash(rt_type): + return repr(dict(sorted(dict(rt_type).items(), key=lambda x: x[0]))) + + @dataclass class GateModelQuantumTaskResult: """ @@ -79,8 +84,8 @@ class GateModelQuantumTaskResult: task_metadata: TaskMetadata additional_metadata: AdditionalMetadata - result_types: List[Dict[str, str]] - values: List[Any] + result_types: List[ResultTypeValue] = None + values: List[Any] = None measurements: np.ndarray = None measured_qubits: List[int] = None measurement_counts: Counter = None @@ -89,6 +94,18 @@ class GateModelQuantumTaskResult: measurement_counts_copied_from_device: bool = None measurement_probabilities_copied_from_device: bool = None + _result_types_indices: Dict[str, int] = None + + def __post_init__(self): + if self._result_types_indices is None: + if self.result_types is not None: + self._result_types_indices = dict( + (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 @@ -105,13 +122,15 @@ def get_value_by_result_type(self, result_type: ResultType) -> Any: Result types must be added to the circuit before the circuit is run on a device. """ rt_ir = result_type.to_ir() - for rt in self.result_types: - if rt_ir == rt.type: - return rt.value - raise ValueError( - "Result type not found in result. " - + "Result types must be added to circuit before circuit is run on device." - ) + try: + 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: + raise ValueError( + "Result type not found in result. " + + "Result types must be added to circuit before circuit is run on device." + ) def __eq__(self, other) -> bool: if isinstance(other, GateModelQuantumTaskResult): @@ -318,7 +337,7 @@ def cast_result_types(gate_model_task_result: GateModelTaskResult) -> None: @staticmethod def _calculate_result_types( ir_string: str, measurements: np.ndarray, measured_qubits: List[int] - ) -> List[Dict[str, Any]]: + ) -> List[ResultTypeValue]: ir = json.loads(ir_string) result_types = [] if not ir.get("results"): @@ -332,6 +351,7 @@ def _calculate_result_types( value = GateModelQuantumTaskResult._probability_from_measurements( measurements, measured_qubits, targets ) + casted_result_type = Probability(targets=targets) elif rt_type == "sample": value = GateModelQuantumTaskResult._calculate_for_targets( GateModelQuantumTaskResult._samples_from_measurements, @@ -340,6 +360,7 @@ def _calculate_result_types( observable, targets, ) + casted_result_type = Sample(targets=targets, observable=ir_observable) elif rt_type == "variance": value = GateModelQuantumTaskResult._calculate_for_targets( GateModelQuantumTaskResult._variance_from_measurements, @@ -348,6 +369,7 @@ def _calculate_result_types( observable, targets, ) + casted_result_type = Variance(targets=targets, observable=ir_observable) elif rt_type == "expectation": value = GateModelQuantumTaskResult._calculate_for_targets( GateModelQuantumTaskResult._expectation_from_measurements, @@ -356,9 +378,10 @@ def _calculate_result_types( observable, targets, ) + casted_result_type = Expectation(targets=targets, observable=ir_observable) else: raise ValueError(f"Unknown result type {rt_type}") - result_types.append(ResultTypeValue.construct(type=result_type, value=value)) + result_types.append(ResultTypeValue.construct(type=casted_result_type, value=value)) return result_types @staticmethod @@ -452,3 +475,7 @@ def _samples_from_measurements( # Extract only the columns of the basis samples required based on ``targets``. indices = GateModelQuantumTaskResult._measurements_base_10(measurements) return observable.eigenvalues[indices].real + + @staticmethod + def _result_type_hash(rt_type): + return repr(dict(sorted(dict(rt_type).items(), key=lambda x: x[0]))) 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 ff1e279d..57a9af28 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 @@ -373,3 +373,48 @@ def test_calculate_ir_results_value_error(): measured_qubits = [0] measurements = np.array([[0]]) GateModelQuantumTaskResult._calculate_result_types(ir_string, measurements, measured_qubits) + + +@pytest.mark.parametrize( + "observable_1, observable_2", + [ + ( + jaqcd.Expectation(targets=[1, 2], observable=["x"]), + jaqcd.Expectation(observable=["x"], targets=[1, 2]), + ), + pytest.param( + jaqcd.Expectation(observable=["x"], targets=[1, 2]), + jaqcd.Expectation(observable=["x"], targets=[2, 1]), + marks=pytest.mark.xfail, + ), + pytest.param( + jaqcd.Expectation(observable=["x"], targets=[1, 2]), + jaqcd.Sample(observable=["x"], targets=[2, 1]), + marks=pytest.mark.xfail, + ), + ( + jaqcd.Expectation( + observable=[ + [[[0, 0], [0.512345, 0]], [[0.543215, 0], [0, 0]]], + [[[1, 0], [1, 0]], [[1, 0], [-1, 0]]], + ], + targets=[1, 2], + ), + jaqcd.Expectation( + observable=[ + [[[0, 0], [0.512345, 0]], [[0.543215, 0], [0, 0]]], + [[[1, 0], [1, 0]], [[1, 0], [-1, 0]]], + ], + targets=[1, 2], + ), + ), + ( + jaqcd.Expectation(observable=["y", "z"], targets=[1, 2]), + jaqcd.Expectation(observable=["y", "z"], targets=[1, 2]), + ), + ], +) +def test_hash_result_types(observable_1, observable_2): + assert GateModelQuantumTaskResult._result_type_hash( + observable_1 + ) == GateModelQuantumTaskResult._result_type_hash(observable_2) From 42afd8dc3895cbfc56b78a05fc04c08633e9136c Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Fri, 8 Jan 2021 20:52:17 -0800 Subject: [PATCH 0258/1165] remove window check for polling-- revert to polling at all times (#197) Co-authored-by: Aaron Berdy --- src/braket/aws/aws_quantum_task.py | 67 +------- .../braket/aws/test_aws_quantum_task.py | 158 +----------------- 2 files changed, 6 insertions(+), 219 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 50f26694..277d4cc9 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -14,12 +14,10 @@ from __future__ import annotations import asyncio -import json import time -from datetime import datetime from functools import singledispatch from logging import Logger, getLogger -from typing import Any, Dict, List, Union +from typing import Any, Dict, Union import boto3 @@ -27,7 +25,7 @@ from braket.aws.aws_session import AwsSession from braket.circuits.circuit import Circuit from braket.circuits.circuit_helpers import validate_circuit_and_shots -from braket.device_schema import DeviceExecutionWindow, ExecutionDay, GateModelParameters +from braket.device_schema import GateModelParameters from braket.device_schema.dwave import DwaveDeviceParameters from braket.device_schema.ionq import IonqDeviceParameters from braket.device_schema.rigetti import RigettiDeviceParameters @@ -140,7 +138,6 @@ def __init__( aws_session: AwsSession = None, poll_timeout_seconds: float = DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = DEFAULT_RESULTS_POLL_INTERVAL, - poll_outside_execution_window: bool = False, logger: Logger = getLogger(__name__), ): """ @@ -151,9 +148,6 @@ def __init__( region of the task. poll_timeout_seconds (float): The polling timeout for `result()`. Default: 5 days. poll_interval_seconds (float): The polling interval for `result()`. Default: 1 second. - poll_outside_execution_window (bool): Whether or not to poll for `result()` when the - current time is outside of the execution window for the associated device, - default is `False`. Tasks are expected to only run during the execution window. logger (Logger): Logger object with which to write logs, such as task statuses while waiting for task to be in a terminal state. Default is `getLogger(__name__)` @@ -175,12 +169,10 @@ def __init__( ) self._poll_timeout_seconds = poll_timeout_seconds self._poll_interval_seconds = poll_interval_seconds - self._poll_outside_execution_window = poll_outside_execution_window self._logger = logger self._metadata: Dict[str, Any] = {} - self._device_execution_windows = None self._result: Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult] = None @staticmethod @@ -340,23 +332,11 @@ async def _wait_for_completion( Note: Timeout and sleep intervals are defined in the constructor fields `poll_timeout_seconds` and `poll_interval_seconds` respectively. - If `poll_outside_execution_window` is set to `False`, it will - not poll the API for the current task status when the current time - is outside of the associated device's execution window. """ self._logger.debug(f"Task {self._arn}: start polling for completion") start_time = time.time() while (time.time() - start_time) < self._poll_timeout_seconds: - is_polling_time = self._is_polling_time() - if not is_polling_time: - self._logger.debug( - f"Task {self._arn}: Current time {datetime.now()} is outside of" - f" associated device's execution windows " - f"{self._get_device_execution_windows()}. Skipping polling for " - f" now." - ) - continue # Used cached metadata if cached status is terminal task_status = self._update_status_if_nonterminal() current_metadata = self.metadata(True) @@ -384,49 +364,6 @@ async def _wait_for_completion( self._result = None return None - def _is_polling_time(self) -> bool: - """ - Return if it's time to poll for `result()`. - """ - if self._poll_outside_execution_window: - return True - device_execution_windows = self._get_device_execution_windows() - cur_time = datetime.utcnow() - for window in device_execution_windows: - day_no = cur_time.weekday() - day_of_week = cur_time.strftime("%A") - if window.executionDay != ExecutionDay.EVERYDAY: - if window.executionDay == ExecutionDay.WEEKDAYS and day_no >= 5: - continue - if window.executionDay == ExecutionDay.WEEKENDS and day_no < 5: - continue - if ( - window.executionDay not in [ExecutionDay.WEEKENDS, ExecutionDay.WEEKDAYS] - and window.executionDay != day_of_week - ): - continue - if ( - cur_time.time() >= window.windowStartHour - and cur_time.time() <= window.windowEndHour - ): - return True - return False - - def _get_device_execution_windows(self) -> List[DeviceExecutionWindow]: - """Returns the device execution windows""" - if not self._device_execution_windows: - device_arn = self.metadata(use_cached_value=True).get("deviceArn") - if not device_arn: - device_arn = self.metadata(use_cached_value=False).get("deviceArn") - device_capabilities = json.loads( - self._aws_session.get_device(device_arn)["deviceCapabilities"] - ) - self._device_execution_windows = [ - DeviceExecutionWindow.parse_obj(window) - for window in device_capabilities["service"]["executionWindows"] - ] - return self._device_execution_windows - def __repr__(self) -> str: return f"AwsQuantumTask('id':{self.id})" 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 4652ec20..d88edcc4 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -12,10 +12,8 @@ # language governing permissions and limitations under the License. import asyncio -import json import threading import time -from datetime import datetime from unittest.mock import MagicMock, Mock, patch import pytest @@ -25,7 +23,7 @@ from braket.aws import AwsQuantumTask from braket.aws.aws_session import AwsSession from braket.circuits import Circuit -from braket.device_schema import DeviceExecutionWindow, GateModelParameters +from braket.device_schema import GateModelParameters from braket.device_schema.dwave import DwaveDeviceParameters from braket.device_schema.ionq import IonqDeviceParameters from braket.device_schema.rigetti import RigettiDeviceParameters @@ -44,23 +42,17 @@ def aws_session(): @pytest.fixture def quantum_task(aws_session): - return AwsQuantumTask( - "foo:bar:arn", aws_session, poll_timeout_seconds=2, poll_outside_execution_window=True - ) + return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) @pytest.fixture def circuit_task(aws_session): - return AwsQuantumTask( - "foo:bar:arn", aws_session, poll_timeout_seconds=2, poll_outside_execution_window=True - ) + return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) @pytest.fixture def annealing_task(aws_session): - return AwsQuantumTask( - "foo:bar:arn", aws_session, poll_timeout_seconds=2, poll_outside_execution_window=True - ) + return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) @pytest.fixture @@ -109,145 +101,6 @@ def test_no_id_setter(quantum_task): quantum_task.id = 123 -def test_get_device_execution_windows_exists(quantum_task): - mock_windows = ["mock"] - quantum_task._device_execution_windows = mock_windows - assert quantum_task._get_device_execution_windows() == mock_windows - assert not quantum_task._aws_session.get_quantum_task.called - - -def test_get_device_execution_windows_device_arn_exists(quantum_task): - mock_arn = "mock" - window = { - "executionDay": "Everyday", - "windowStartHour": "00:00:00", - "windowEndHour": "23:00:00", - } - quantum_task._metadata = {"deviceArn": mock_arn} - quantum_task._aws_session.get_device.return_value = { - "deviceCapabilities": json.dumps({"service": {"executionWindows": [window]}}) - } - assert quantum_task._get_device_execution_windows() == [DeviceExecutionWindow.parse_obj(window)] - assert not quantum_task._aws_session.get_quantum_task.called - quantum_task._aws_session.get_device.assert_called_with(mock_arn) - - -def test_get_device_execution_windows_not_exists(quantum_task): - mock_arn = "mock" - window = { - "executionDay": "Everyday", - "windowStartHour": "00:00:00", - "windowEndHour": "23:00:00", - } - quantum_task._metadata = {} - quantum_task._aws_session.get_quantum_task.return_value = {"deviceArn": mock_arn} - quantum_task._aws_session.get_device.return_value = { - "deviceCapabilities": json.dumps({"service": {"executionWindows": [window]}}) - } - assert quantum_task._get_device_execution_windows() == [DeviceExecutionWindow.parse_obj(window)] - quantum_task._aws_session.get_quantum_task.assert_called_with(quantum_task.id) - quantum_task._aws_session.get_device.assert_called_with(mock_arn) - - -@pytest.mark.parametrize( - "window,cur_time,expected_val", - [ - ( - { - "executionDay": "Everyday", - "windowStartHour": "00:00:00", - "windowEndHour": "00:01:00", - }, - "10/08/2020 00:00:32", - True, - ), - ( - { - "executionDay": "Everyday", - "windowStartHour": "00:00:00", - "windowEndHour": "00:01:00", - }, - "10/08/2020 00:02:00", - False, - ), - ( - { - "executionDay": "Weekdays", - "windowStartHour": "00:00:00", - "windowEndHour": "00:01:00", - }, - "10/27/2020 00:00:32", - True, - ), - ( - { - "executionDay": "Weekdays", - "windowStartHour": "00:00:00", - "windowEndHour": "00:01:00", - }, - "10/25/2020 00:00:32", - False, - ), - ( - { - "executionDay": "Weekend", - "windowStartHour": "00:00:00", - "windowEndHour": "00:01:00", - }, - "10/27/2020 00:00:32", - False, - ), - ( - { - "executionDay": "Weekend", - "windowStartHour": "00:00:00", - "windowEndHour": "00:01:00", - }, - "10/25/2020 00:00:32", - True, - ), - ( - { - "executionDay": "Tuesday", - "windowStartHour": "00:00:00", - "windowEndHour": "00:01:00", - }, - "10/27/2020 00:00:32", - True, - ), - ( - { - "executionDay": "Monday", - "windowStartHour": "00:00:00", - "windowEndHour": "00:01:00", - }, - "10/27/2020 00:00:32", - False, - ), - ], -) -def test_is_polling_time(window, cur_time, expected_val, quantum_task): - quantum_task._poll_outside_execution_window = False - quantum_task._device_execution_windows = [DeviceExecutionWindow.parse_obj(window)] - with patch("braket.aws.aws_quantum_task.datetime") as mock_datetime: - mock_datetime.utcnow.return_value = datetime.strptime(cur_time, "%m/%d/%Y %H:%M:%S") - assert quantum_task._is_polling_time() == expected_val - - -def test_result_not_polling(quantum_task): - quantum_task._metadata = {"a": 0} - quantum_task._poll_outside_execution_window = False - quantum_task._poll_timeout_seconds = 0.01 - window = { - "executionDay": "Everyday", - "windowStartHour": "00:00:00", - "windowEndHour": "00:00:00", - } - quantum_task._device_execution_windows = [DeviceExecutionWindow.parse_obj(window)] - quantum_task.result() - assert not quantum_task._aws_session.get_quantum_task.called - - def test_metadata(quantum_task): metadata_1 = {"status": "RUNNING"} quantum_task._aws_session.get_quantum_task.return_value = metadata_1 @@ -421,7 +274,6 @@ def test_timeout_completed(aws_session): aws_session, poll_timeout_seconds=0.5, poll_interval_seconds=1, - poll_outside_execution_window=True, ) assert quantum_task.result() is None _mock_metadata(aws_session, "COMPLETED") @@ -447,7 +299,6 @@ def test_timeout_no_result_terminal_state(aws_session): aws_session, poll_timeout_seconds=0.5, poll_interval_seconds=1, - poll_outside_execution_window=True, ) assert quantum_task.result() is None @@ -559,7 +410,6 @@ def test_from_annealing(device_parameters, aws_session, arn, problem): S3_TARGET, 1000, device_parameters=device_parameters, - poll_outside_execution_window=True, ) assert task == AwsQuantumTask( mocked_task_arn, aws_session, AnnealingQuantumTaskResult.from_string From e31511f7aef27f751ac9dc487eabde8c17f24be8 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 11 Jan 2021 16:16:43 -0800 Subject: [PATCH 0259/1165] infra: Enable Codecov (#198) --- .github/workflows/python-package.yml | 6 ++++-- README.md | 1 + tox.ini | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 7ecdd973..6796121a 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -26,7 +26,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - pip install --upgrade pip pytest + pip install --upgrade pip pip install --upgrade git+https://github.com/aws/amazon-braket-schemas-python@main pip install --upgrade git+https://github.com/aws/amazon-braket-default-simulator-python@main pip install -e .[test] @@ -37,4 +37,6 @@ jobs: flake8 - name: Run unit tests run: | - pytest test/unit_tests + tox -e unit-tests + - name: Upload coverage report to Codecov + uses: codecov/codecov-action@v1 diff --git a/README.md b/README.md index b07775b9..9e807867 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Latest Version](https://img.shields.io/pypi/v/amazon-braket-sdk.svg)](https://pypi.python.org/pypi/amazon-braket-sdk) [![Supported Python Versions](https://img.shields.io/pypi/pyversions/amazon-braket-sdk.svg)](https://pypi.python.org/pypi/amazon-braket-sdk) [![Build Status](https://img.shields.io/github/workflow/status/aws/amazon-braket-sdk-python/Python%20package/main?logo=github)](https://github.com/aws/amazon-braket-sdk-python/actions?query=workflow%3A%22Python+package%22) +[![codecov](https://codecov.io/gh/aws/amazon-braket-sdk-python/branch/main/graph/badge.svg?token=1lsqkZL3Ll)](https://codecov.io/gh/aws/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) [![Code Style: Black](https://img.shields.io/badge/code_style-black-000000.svg)](https://github.com/psf/black) diff --git a/tox.ini b/tox.ini index 45c68c57..b20f4aa1 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,7 @@ commands = coverage combine coverage report coverage html + coverage xml extras = test [testenv:integ-tests] From 2ebb14bf6ec3534af7e1e78be5ed04b8512c17ce Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 12 Jan 2021 23:16:07 +0000 Subject: [PATCH 0260/1165] prepare release v1.5.4 --- 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 11ff96c0..c5fcf151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.5.4 (2021-01-12) + +### Bug Fixes and Other Changes + + * remove window check for polling-- revert to polling at all times + * update result_types to use hashing + +### Testing and Release Infrastructure + + * Enable Codecov + ## v1.5.3 (2020-12-31) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index bcf37244..48600857 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.5.4.dev0" +__version__ = "1.5.4" From 3962154c301339b51e246e8b9d97c651a7b2a84b Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 12 Jan 2021 23:16:07 +0000 Subject: [PATCH 0261/1165] update development version to v1.5.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 48600857..031e1d69 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.5.4" +__version__ = "1.5.5.dev0" From baaccfd47b4ca6a015e6446b580aef3584fede3d Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Thu, 14 Jan 2021 10:08:39 -0800 Subject: [PATCH 0262/1165] fix: get correct event loop for task results after running a batch over multiple threads (#199) --- src/braket/aws/aws_quantum_task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 277d4cc9..8926291e 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -274,7 +274,7 @@ def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult return self._result try: async_result = self.async_result() - return asyncio.get_event_loop().run_until_complete(async_result) + return async_result.get_loop().run_until_complete(async_result) except asyncio.CancelledError: # Future was cancelled, return whatever is in self._result if anything self._logger.warning("Task future was cancelled") From a55790195a93250d35f97641942fc3d8547c6867 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 15 Jan 2021 19:33:07 +0000 Subject: [PATCH 0263/1165] prepare release v1.5.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 c5fcf151..39410cb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.5.5 (2021-01-15) + +### Bug Fixes and Other Changes + + * get correct event loop for task results after running a batch over multiple threads + ## v1.5.4 (2021-01-12) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 031e1d69..26a0acbe 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.5.5.dev0" +__version__ = "1.5.5" From 8218a66134f5efd7a23f6d1b8756387d0cf1e62d Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 15 Jan 2021 19:33:07 +0000 Subject: [PATCH 0264/1165] update development version to v1.5.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 26a0acbe..21b79115 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.5.5" +__version__ = "1.5.6.dev0" From 39ebbcb24619d7386607f098c785aa60d6c113fd Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 20 Jan 2021 13:10:20 -0800 Subject: [PATCH 0265/1165] =?UTF-8?q?fix:=20ensure=20AngledGate=20casts=20?= =?UTF-8?q?its=20angle=20argument=20to=20float=20so=20it=20can=20be?= =?UTF-8?q?=E2=80=A6=20(#201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: ensure AngledGate casts its angle argument to float so it can be serialized to json Co-authored-by: Cody Wang --- src/braket/circuits/angled_gate.py | 2 +- test/unit_tests/braket/circuits/test_angled_gate.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index 366b6696..532974a8 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -40,7 +40,7 @@ def __init__(self, angle: float, qubit_count: int, ascii_symbols: Sequence[str]) super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) if angle is None: raise ValueError("angle must not be None") - self._angle = angle + self._angle = float(angle) # explicit casting in case angle is e.g. np.float32 @property def angle(self) -> float: diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index 47857fed..3bcc822f 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -11,7 +11,11 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import re + +import numpy as np import pytest +from pydantic import BaseModel from braket.circuits import AngledGate, Gate @@ -44,3 +48,11 @@ def test_getters(): @pytest.mark.xfail(raises=AttributeError) def test_angle_setter(angled_gate): angled_gate.angle = 0.14 + + +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)) + assert angle_value == angled_gate.angle From a821b8377f22d4f956aaee6579153ab0b63aa8b5 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 21 Jan 2021 18:11:17 +0000 Subject: [PATCH 0266/1165] prepare release v1.5.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 39410cb9..49a43e65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.5.6 (2021-01-21) + +### Bug Fixes and Other Changes + + * ensure AngledGate casts its angle argument to float so it can be… + ## v1.5.5 (2021-01-15) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 21b79115..8e4c78d1 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.5.6.dev0" +__version__ = "1.5.6" From 481de642b3922a214719e2f7747987865f9e56b9 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 21 Jan 2021 18:11:17 +0000 Subject: [PATCH 0267/1165] update development version to v1.5.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 8e4c78d1..81313dc2 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.5.6" +__version__ = "1.5.7.dev0" From f03547aeaa6798b83cd91f2346d25072daa1b5fd Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 26 Jan 2021 18:15:53 -0800 Subject: [PATCH 0268/1165] change: More scalable eigenvalue calculation (#202) * change: More scalable eigenvalue calculation Currently, whenever an observable is instantiated, _all_ of its eigenvalues are calculated and stored in an array. This means a tensor product on n qubits will instantiate an array of length 2^n, which obviously isn't scalable for large n. This change calculates the eigenvalues only at desired indices. --- src/braket/circuits/observable.py | 31 +++++-- src/braket/circuits/observables.py | 90 +++++++++++++------ src/braket/circuits/quantum_operator.py | 2 +- .../tasks/gate_model_quantum_task_result.py | 4 +- .../braket/circuits/test_observable.py | 7 +- .../braket/circuits/test_observables.py | 31 ++++++- .../test_gate_model_quantum_task_result.py | 2 + 7 files changed, 127 insertions(+), 40 deletions(-) diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index 06e29573..ad9be32b 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -19,7 +19,6 @@ from braket.circuits.gate import Gate from braket.circuits.quantum_operator import QuantumOperator -from braket.circuits.quantum_operator_helpers import get_pauli_eigenvalues class Observable(QuantumOperator): @@ -39,7 +38,7 @@ def to_ir(self) -> List[Union[str, List[List[List[float]]]]]: raise NotImplementedError @property - def basis_rotation_gates(self) -> Tuple[Gate]: + def basis_rotation_gates(self) -> Tuple[Gate, ...]: """Tuple[Gate]: Returns the basis rotation gates for this observable.""" raise NotImplementedError @@ -48,6 +47,20 @@ def eigenvalues(self) -> np.ndarray: """np.ndarray: Returns the eigenvalues of this observable.""" raise NotImplementedError + def eigenvalue(self, index: int) -> float: + """Returns the the eigenvalue of this observable at the given index. + + The eigenvalues are ordered by their corresponding computational basis state + after diagonalization. + + Args: + index: The index of the desired eigenvalue + + Returns: + float: The `index`th eigenvalue of the observable. + """ + raise NotImplementedError + @classmethod def register_observable(cls, observable: Observable) -> None: """Register an observable implementation by adding it into the `Observable` class. @@ -77,13 +90,17 @@ def __eq__(self, other) -> bool: class StandardObservable(Observable): """ - Class `StandardObservable` to represent a standard quantum observable with - eigenvalues of +/-1, each with a multiplicity of 1. + Class `StandardObservable` to represent a Pauli-like quantum observable with + eigenvalues of (+1, -1). """ - def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): - super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + def __init__(self, ascii_symbols: Sequence[str]): + super().__init__(qubit_count=1, ascii_symbols=ascii_symbols) + self._eigenvalues = (1.0, -1.0) # immutable @property def eigenvalues(self) -> np.ndarray: - return get_pauli_eigenvalues(self.qubit_count) + return np.array(self._eigenvalues) + + def eigenvalue(self, index: int) -> float: + return self._eigenvalues[index] diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index da663645..b2264dbb 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -35,9 +35,9 @@ class H(StandardObservable): def __init__(self): """ Examples: - >>> Observable.I() + >>> Observable.H() """ - super().__init__(qubit_count=1, ascii_symbols=["H"]) + super().__init__(ascii_symbols=["H"]) def to_ir(self) -> List[str]: return ["h"] @@ -46,7 +46,7 @@ def to_matrix(self) -> np.ndarray: return 1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) @property - def basis_rotation_gates(self) -> Tuple[Gate]: + def basis_rotation_gates(self) -> Tuple[Gate, ...]: return tuple([Gate.Ry(-math.pi / 4)]) @@ -70,13 +70,16 @@ def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, 1.0]], dtype=complex) @property - def basis_rotation_gates(self) -> Tuple[Gate]: + def basis_rotation_gates(self) -> Tuple[Gate, ...]: return () @property def eigenvalues(self) -> np.ndarray: return np.array([1, 1]) + def eigenvalue(self, index: int) -> float: + return 1.0 + Observable.register_observable(I) @@ -89,7 +92,7 @@ def __init__(self): Examples: >>> Observable.X() """ - super().__init__(qubit_count=1, ascii_symbols=["X"]) + super().__init__(ascii_symbols=["X"]) def to_ir(self) -> List[str]: return ["x"] @@ -98,7 +101,7 @@ def to_matrix(self) -> np.ndarray: return np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) @property - def basis_rotation_gates(self) -> Tuple[Gate]: + def basis_rotation_gates(self) -> Tuple[Gate, ...]: return tuple([Gate.H()]) @@ -113,7 +116,7 @@ def __init__(self): Examples: >>> Observable.Y() """ - super().__init__(qubit_count=1, ascii_symbols=["Y"]) + super().__init__(ascii_symbols=["Y"]) def to_ir(self) -> List[str]: return ["y"] @@ -122,7 +125,7 @@ def to_matrix(self) -> np.ndarray: return np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) @property - def basis_rotation_gates(self) -> Tuple[Gate]: + def basis_rotation_gates(self) -> Tuple[Gate, ...]: return tuple([Gate.Z(), Gate.S(), Gate.H()]) @@ -137,7 +140,7 @@ def __init__(self): Examples: >>> Observable.Z() """ - super().__init__(qubit_count=1, ascii_symbols=["Z"]) + super().__init__(ascii_symbols=["Z"]) def to_ir(self) -> List[str]: return ["z"] @@ -146,7 +149,7 @@ def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) @property - def basis_rotation_gates(self) -> Tuple[Gate]: + def basis_rotation_gates(self) -> Tuple[Gate, ...]: return () @@ -178,11 +181,15 @@ def __init__(self, observables: List[Observable]): result of the tensor product of `ob1`, `ob2`, `ob3`, or `np.kron(np.kron(ob1.to_matrix(), ob2.to_matrix()), ob3.to_matrix())`. """ - self._observables = tuple(observables) + self._factors = tuple(observables) qubit_count = sum([obs.qubit_count for obs in observables]) display_name = "@".join([obs.ascii_symbols[0] for obs in observables]) super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) - self._eigenvalues = TensorProduct._compute_eigenvalues(self._observables, qubit_count) + self._factor_dimensions = tuple( + len(factor.to_matrix()) for factor in reversed(self._factors) + ) + self._eigenvalue_indices = {} + self._all_eigenvalues = None def to_ir(self) -> List[str]: ir = [] @@ -191,15 +198,15 @@ def to_ir(self) -> List[str]: return ir @property - def factors(self) -> Tuple[Observable]: + def factors(self) -> Tuple[Observable, ...]: """ Tuple[Observable]: The observables that comprise this tensor product.""" - return self._observables + return self._factors def to_matrix(self) -> np.ndarray: return functools.reduce(np.kron, [obs.to_matrix() for obs in self.factors]) @property - def basis_rotation_gates(self) -> Tuple[Gate]: + def basis_rotation_gates(self) -> Tuple[Gate, ...]: gates = [] for obs in self.factors: gates.extend(obs.basis_rotation_gates) @@ -207,7 +214,29 @@ def basis_rotation_gates(self) -> Tuple[Gate]: @property def eigenvalues(self): - return self._eigenvalues + if self._all_eigenvalues is None: + self._all_eigenvalues = TensorProduct._compute_eigenvalues( + self._factors, self.qubit_count + ) + return self._all_eigenvalues + + def eigenvalue(self, index: int) -> float: + if index in self._eigenvalue_indices: + return self._eigenvalue_indices[index] + dimension = 2 ** self.qubit_count + if index >= dimension: + raise ValueError( + f"Index {index} requested but observable has at most {dimension} eigenvalues" + ) + # Calculating the eigenvalue amounts to converting the index to a new heterogeneous base + # and multiplying the eigenvalues of each factor at the corresponding digit + product = 1 + quotient = index + 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._eigenvalue_indices[index] def __matmul__(self, other): if isinstance(other, TensorProduct): @@ -278,11 +307,16 @@ def __init__(self, matrix: np.ndarray, display_name: str = "Hermitian"): """ verify_quantum_operator_matrix_dimensions(matrix) self._matrix = np.array(matrix, dtype=complex) - qubit_count = int(np.log2(self._matrix.shape[0])) - if not is_hermitian(self._matrix): raise ValueError(f"{self._matrix} is not hermitian") + qubit_count = int(np.log2(self._matrix.shape[0])) + eigendecomposition = Hermitian._get_eigendecomposition(self._matrix) + self._eigenvalues = eigendecomposition["eigenvalues"] + self._diagonalizing_gates = ( + Gate.Unitary(matrix=eigendecomposition["eigenvectors"].conj().T), + ) + super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) def to_ir(self) -> List[List[List[List[float]]]]: @@ -297,14 +331,18 @@ def __eq__(self, other) -> bool: return self.matrix_equivalence(other) @property - def basis_rotation_gates(self) -> Tuple[Gate]: - return tuple([Gate.Unitary(matrix=self._get_eigendecomposition()["eigenvectors_conj_t"])]) + def basis_rotation_gates(self) -> Tuple[Gate, ...]: + return self._diagonalizing_gates @property def eigenvalues(self): - return self._get_eigendecomposition()["eigenvalues"] + return self._eigenvalues + + def eigenvalue(self, index: int) -> float: + return self._eigenvalues[index] - def _get_eigendecomposition(self) -> Dict[str, np.ndarray]: + @staticmethod + def _get_eigendecomposition(matrix) -> Dict[str, np.ndarray]: """ Decomposes the Hermitian matrix into its eigenvectors and associated eigenvalues. The eigendecomposition is cached so that if another Hermitian observable @@ -318,14 +356,12 @@ def _get_eigendecomposition(self) -> Dict[str, np.ndarray]: corresponding eigenvectors in the "eigenvectors" matrix. These cached values are immutable. """ - mat_key = tuple(self._matrix.flatten().tolist()) + mat_key = tuple(matrix.flatten().tolist()) if mat_key not in Hermitian._eigenpairs: - eigenvalues, eigenvectors = np.linalg.eigh(self._matrix) + eigenvalues, eigenvectors = np.linalg.eigh(matrix) eigenvalues.setflags(write=False) - eigenvectors_conj_t = eigenvectors.conj().T - eigenvectors_conj_t.setflags(write=False) Hermitian._eigenpairs[mat_key] = { - "eigenvectors_conj_t": eigenvectors_conj_t, + "eigenvectors": eigenvectors, "eigenvalues": eigenvalues, } return Hermitian._eigenpairs[mat_key] diff --git a/src/braket/circuits/quantum_operator.py b/src/braket/circuits/quantum_operator.py index 8eaab89b..c4497c9d 100644 --- a/src/braket/circuits/quantum_operator.py +++ b/src/braket/circuits/quantum_operator.py @@ -80,7 +80,7 @@ def to_ir(self, *args, **kwargs) -> Any: """ raise NotImplementedError("to_ir has not been implemented yet.") - def to_matrix(self, *args, **kwargs) -> Any: + def to_matrix(self, *args, **kwargs) -> np.ndarray: """Returns a matrix representation of the quantum operator Returns: diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 888e86d6..4986a063 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -20,7 +20,7 @@ import numpy as np from braket.circuits import Observable, ResultType, StandardObservable -from braket.circuits.observables import observable_from_ir +from braket.circuits.observables import TensorProduct, observable_from_ir from braket.ir.jaqcd import Expectation, Probability, Sample, Variance from braket.task_result import ( AdditionalMetadata, @@ -474,6 +474,8 @@ def _samples_from_measurements( # 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 diff --git a/test/unit_tests/braket/circuits/test_observable.py b/test/unit_tests/braket/circuits/test_observable.py index e5befcb6..00c1270f 100644 --- a/test/unit_tests/braket/circuits/test_observable.py +++ b/test/unit_tests/braket/circuits/test_observable.py @@ -24,7 +24,7 @@ def observable(): @pytest.fixture def standard_observable(): - return StandardObservable(qubit_count=1, ascii_symbols=["foo"]) + return StandardObservable(ascii_symbols=["foo"]) def test_is_operator(observable): @@ -95,6 +95,11 @@ def test_eigenvalues_not_implemented_by_default(observable): observable.eigenvalues +@pytest.mark.xfail(raises=NotImplementedError) +def test_eigenvalue_not_implemented_by_default(observable): + observable.eigenvalue(0) + + def test_str(observable): expected = "{}('qubit_count': {})".format(observable.name, observable.qubit_count) assert str(observable) == expected diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index c7dd6f95..e58fec88 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -78,7 +78,7 @@ def test_basis_rotation_gates( "testobject,gateobject,expected_ir,basis_rotation_gates,eigenvalues", testdata ) def test_eigenvalues(testobject, gateobject, expected_ir, basis_rotation_gates, eigenvalues): - assert np.allclose(testobject.eigenvalues, eigenvalues) + compare_eigenvalues(testobject, eigenvalues) @pytest.mark.parametrize( @@ -123,7 +123,7 @@ def test_hermitian_to_ir(): ], ) def test_hermitian_eigenvalues(matrix, eigenvalues): - assert np.allclose(Observable.Hermitian(matrix=matrix).eigenvalues, eigenvalues) + compare_eigenvalues(Observable.Hermitian(matrix=matrix), eigenvalues) @pytest.mark.parametrize( @@ -208,6 +208,12 @@ def test_tensor_product_matmul_observable(): assert t.ascii_symbols == tuple(["Z@I@X@I"] * 4) +@pytest.mark.xfail(raises=ValueError) +def test_tensor_product_eigenvalue_index_out_of_bounds(): + obs = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) + obs.eigenvalue(8) + + @pytest.mark.xfail(raises=ValueError) def test_tensor_product_value_error(): Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) @ "a" @@ -233,10 +239,21 @@ def test_tensor_product_rmatmul_value_error(): (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])), + ( + Observable.X() + @ Observable.Hermitian( + np.array([[-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + ) + @ Observable.Y(), + np.array([-1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1]), + ), ], ) def test_tensor_product_eigenvalues(observable, eigenvalues): - assert np.allclose(observable.eigenvalues, eigenvalues) + compare_eigenvalues(observable, eigenvalues) + # Test caching + observable._factors = () + compare_eigenvalues(observable, eigenvalues) @pytest.mark.parametrize( @@ -267,3 +284,11 @@ def test_observable_from_ir_tensor_product(): @pytest.mark.xfail(raises=ValueError) def test_observable_from_ir_tensor_product_value_error(): observable_from_ir(["z", "i", "foo"]) + + +def compare_eigenvalues(observable, expected): + assert np.allclose(observable.eigenvalues, expected) + assert np.allclose( + np.array([observable.eigenvalue(i) for i in range(2 ** observable.qubit_count)]), + 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 57a9af28..7abf4ece 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 @@ -188,9 +188,11 @@ def malformatted_results_2(task_metadata_shots, additional_metadata): ], ), (jaqcd.Expectation(targets=[1], observable=["z"]), 0.2), + (jaqcd.Expectation(targets=[1], observable=[[[[-1, 0], [0, 0]], [[0, 0], [1, 0]]]]), -0.2), (jaqcd.Expectation(targets=[1, 2], observable=["z", "y"]), 0.6), (jaqcd.Expectation(observable=["z"]), [0.4, 0.2, -0.2, -0.4]), (jaqcd.Variance(targets=[1], observable=["z"]), 0.96), + (jaqcd.Variance(targets=[1], observable=[[[[-1, 0], [0, 0]], [[0, 0], [1, 0]]]]), 0.96), (jaqcd.Variance(targets=[1, 2], observable=["z", "y"]), 0.64), (jaqcd.Variance(observable=["z"]), [0.84, 0.96, 0.96, 0.84]), ] From 2b1b2f889e960f98989ace0c1cd79fa08049d11e Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 27 Jan 2021 18:11:16 +0000 Subject: [PATCH 0269/1165] prepare release v1.5.7 --- 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 49a43e65..6ae6e243 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.5.7 (2021-01-27) + +### Bug Fixes and Other Changes + + * More scalable eigenvalue calculation + ## v1.5.6 (2021-01-21) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 81313dc2..ddf209f7 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.5.7.dev0" +__version__ = "1.5.7" From b77ffca5d14ee4543788090d3ee5cba25e1fb90c Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 27 Jan 2021 18:11:16 +0000 Subject: [PATCH 0270/1165] update development version to v1.5.8.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 ddf209f7..3bc50d81 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.5.7" +__version__ = "1.5.8.dev0" From 3dcd965d17fae52218ee513a2b9638a4b043a444 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Thu, 28 Jan 2021 14:57:36 -0800 Subject: [PATCH 0271/1165] =?UTF-8?q?fix:=20convert=20measurements=20to=20?= =?UTF-8?q?indices=20without=20allocating=20a=20high-dimens=E2=80=A6=20(#2?= =?UTF-8?q?03)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: convert measurements to indices without allocating a high-dimensional array (also remove branch for result_types_indices not None during initialization) --- .../tasks/gate_model_quantum_task_result.py | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 4986a063..55084709 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -32,10 +32,6 @@ T = TypeVar("T") -def result_type_hash(rt_type): - return repr(dict(sorted(dict(rt_type).items(), key=lambda x: x[0]))) - - @dataclass class GateModelQuantumTaskResult: """ @@ -97,14 +93,13 @@ class GateModelQuantumTaskResult: _result_types_indices: Dict[str, int] = None def __post_init__(self): - if self._result_types_indices is None: - if self.result_types is not None: - self._result_types_indices = dict( - (GateModelQuantumTaskResult._result_type_hash(rt.type), i) - for i, rt in enumerate(self.result_types) - ) - else: - self._result_types_indices = {} + if self.result_types is not None: + self._result_types_indices = dict( + (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: """ @@ -413,9 +408,8 @@ def _calculate_for_targets( @staticmethod def _measurements_base_10(measurements: np.ndarray) -> np.ndarray: # convert samples from a list of 0, 1 integers, to base 10 representation - shots, num_measured_qubits = measurements.shape - unraveled_indices = [2] * num_measured_qubits - return np.ravel_multi_index(measurements.T, unraveled_indices) + two_powers = 2 ** np.arange(measurements.shape[-1])[::-1] # 2^(n-1), ..., 2, 1 + return measurements @ two_powers @staticmethod def _probability_from_measurements( From 6107ea22e9f14948292541b54518af11d4c8cc04 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 28 Jan 2021 15:48:44 -0800 Subject: [PATCH 0272/1165] fix: Remove redundant statement, boost coverage (#204) --- src/braket/circuits/circuit.py | 2 +- src/braket/circuits/observables.py | 4 ++-- test/unit_tests/braket/circuits/test_circuit.py | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index f506a056..8001d10e 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -287,7 +287,7 @@ def _add_to_qubit_observable_mapping(self, result_type: ResultType) -> None: if add_observable: self._qubit_target_mapping[target] = new_targets self._qubit_observable_mapping[target] = new_observable - elif new_observable.qubit_count > 1 and new_observable != Observable.I(): + elif new_observable.qubit_count > 1: current_target = self._qubit_target_mapping.get(target) if current_target and current_target != new_targets: raise ValueError( diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index b2264dbb..9eb5aa49 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -67,7 +67,7 @@ def to_ir(self) -> List[str]: return ["i"] def to_matrix(self) -> np.ndarray: - return np.array([[1.0, 0.0], [0.0, 1.0]], dtype=complex) + return np.eye(2, dtype=complex) @property def basis_rotation_gates(self) -> Tuple[Gate, ...]: @@ -75,7 +75,7 @@ def basis_rotation_gates(self) -> Tuple[Gate, ...]: @property def eigenvalues(self) -> np.ndarray: - return np.array([1, 1]) + return np.ones(2) def eigenvalue(self, index: int) -> float: return 1.0 diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 41476ec0..91dd8c96 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -656,6 +656,7 @@ def test_basis_rotation_instructions_multiple_result_types_tensor_product_hermit observable=Observable.Hermitian(matrix=np.eye(4)) @ Observable.H(), target=[0, 1, 2] ) .variance(observable=Observable.H(), target=[2]) + .variance(observable=Observable.Hermitian(matrix=np.eye(4)), target=[0, 1]) .expectation(observable=Observable.I(), target=[0]) ) expected = [ From a86bc2a3671b96cd3908777f48533dbadece2e0f Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 28 Jan 2021 17:07:26 -0800 Subject: [PATCH 0273/1165] infra: Raise coverage to 100% (#205) --- .../braket/aws/common_test_utils.py | 20 ++++++-------- .../braket/aws/test_aws_quantum_task.py | 26 ++++++++++++++----- .../braket/aws/test_aws_quantum_task_batch.py | 10 +++++++ 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 6bae1c71..3a53d9f6 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -23,6 +23,12 @@ class MockS3: + MOCK_TASK_METADATA = { + "braketSchemaHeader": {"name": "braket.task_result.task_metadata", "version": "1"}, + "id": "task_arn", + "shots": 100, + "deviceId": "default", + } MOCK_S3_RESULT_GATE_MODEL = json.dumps( { @@ -32,12 +38,7 @@ class MockS3: }, "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", - }, + "taskMetadata": MOCK_TASK_METADATA, "additionalMetadata": { "action": { "braketSchemaHeader": {"name": "braket.ir.jaqcd.program", "version": "1"}, @@ -70,12 +71,7 @@ class MockS3: }, }, ], - "taskMetadata": { - "braketSchemaHeader": {"name": "braket.task_result.task_metadata", "version": "1"}, - "id": "task_arn", - "shots": 100, - "deviceId": "default", - }, + "taskMetadata": MOCK_TASK_METADATA, "additionalMetadata": { "action": { "braketSchemaHeader": {"name": "braket.ir.jaqcd.program", "version": "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 d88edcc4..33bbcbb0 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import asyncio +import json import threading import time from unittest.mock import MagicMock, Mock, patch @@ -197,6 +198,13 @@ def test_result_annealing(annealing_task): ) +@pytest.mark.xfail(raises=TypeError) +def test_result_invalid_type(circuit_task): + _mock_metadata(circuit_task._aws_session, "COMPLETED") + _mock_s3(circuit_task._aws_session, json.dumps(MockS3.MOCK_TASK_METADATA)) + circuit_task.result() + + def test_result_circuit_cached(circuit_task): _mock_metadata(circuit_task._aws_session, "COMPLETED") expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_GATE_MODEL) @@ -226,7 +234,14 @@ def test_result_cached_future(circuit_task, result_string): assert circuit_task.result() == expected -def test_async_result(circuit_task): +@pytest.mark.parametrize( + "status, result", + [ + ("COMPLETED", GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_GATE_MODEL)), + ("FAILED", None), + ], +) +def test_async_result(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 @@ -244,17 +259,16 @@ def set_result_from_callback(future): future.add_done_callback(set_result_from_callback) # via asyncio waiting for result - _mock_metadata(circuit_task._aws_session, "COMPLETED") + _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() - expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_GATE_MODEL) - assert result_from_callback == expected - assert result_from_waiting == expected - assert result_from_future == expected + assert result_from_callback == result + assert result_from_waiting == result + assert result_from_future == result def test_failed_task(quantum_task): 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 707ebd43..1dc6313d 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 @@ -117,6 +117,16 @@ def test_retry(mock_create): ] assert batch.unsuccessful == set() + # Don't retry if there's nothing to retry + mock_create.side_effect = [bad_task_mock] + assert batch.retry_unsuccessful_tasks() + assert batch.unsuccessful == set() + + # Error if called before there are any results + batch._results = None + with pytest.raises(RuntimeError): + batch.retry_unsuccessful_tasks() + def _circuits(batch_size): return [Circuit().h(0).cnot(0, 1) for _ in range(batch_size)] From 519b76996e44d202b23728f3248b0a2b5c1cebd2 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 29 Jan 2021 18:13:16 +0000 Subject: [PATCH 0274/1165] prepare release v1.5.8 --- 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 6ae6e243..1f5372bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.5.8 (2021-01-29) + +### Bug Fixes and Other Changes + + * Remove redundant statement, boost coverage + * convert measurements to indices without allocating a high-dimens… + +### Testing and Release Infrastructure + + * Raise coverage to 100% + ## v1.5.7 (2021-01-27) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 3bc50d81..adb738c1 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.5.8.dev0" +__version__ = "1.5.8" From 3a6f87fdb505fab6f39d7612eab265f5292f166c Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 29 Jan 2021 18:13:16 +0000 Subject: [PATCH 0275/1165] update development version to v1.5.9.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 adb738c1..862c1594 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.5.8" +__version__ = "1.5.9.dev0" From ba4cea0217ea96ea825533c6af01f24647bbda90 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 5 Feb 2021 21:16:24 -0800 Subject: [PATCH 0276/1165] fix: Search for unknown QPUs (#207) --- CODEOWNERS | 2 +- src/braket/aws/aws_device.py | 36 ++++++++++++++----- test/unit_tests/braket/aws/test_aws_device.py | 17 +++++++++ 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 042801bb..088560a6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,4 +3,4 @@ # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, these accounts # will be requested for review when someone opens a pull request. -* @floralph @speller26 +* @aws/amazon-braket diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 21405ad8..125f2b09 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -327,7 +327,17 @@ def _aws_session_for_qpu(device_arn: str, aws_session: Optional[AwsSession]) -> See `braket.aws.aws_qpu.AwsDevice.DEVICE_REGIONS` for the AWS Regions the devices are located in. """ - return AwsDevice._copy_aws_session(aws_session, AwsDevice.QPU_REGIONS.get(device_arn), None) + if device_arn in AwsDevice.QPU_REGIONS: + return AwsDevice._copy_aws_session( + aws_session, AwsDevice.QPU_REGIONS.get(device_arn), None + ) + # If the QPU is unknown, search until it is found. + device_sessions = AwsDevice._get_arn_sessions( + [device_arn], None, {AwsDeviceType.QPU}, None, None, aws_session + ) + if device_sessions: + return device_sessions[device_arn] + raise ValueError(f"QPU {device_arn} not found") @staticmethod def _copy_aws_session( @@ -397,15 +407,25 @@ def get_devices( 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}" ) - aws_session = aws_session if aws_session else AwsSession() types = ( frozenset(types) if types else frozenset({device_type for device_type in AwsDeviceType}) ) - device_map = {} + arn_sessions = AwsDevice._get_arn_sessions( + arns, names, types, statuses, provider_names, aws_session + ) + devices = [AwsDevice(arn, arn_sessions[arn]) for arn in arn_sessions] + devices.sort(key=lambda x: getattr(x, order_by)) + return devices + + @staticmethod + def _get_arn_sessions(arns, names, types, statuses, provider_names, aws_session): + aws_session = aws_session if aws_session else AwsSession() + sessions_for_arns = {} session_region = aws_session.boto_session.region_name device_regions_set = AwsDevice._get_devices_regions_set(types, arns, session_region) for region in device_regions_set: @@ -424,16 +444,14 @@ def get_devices( provider_names=provider_names, ) ] - device_map.update( + sessions_for_arns.update( { - arn: AwsDevice(arn, session_for_region) + arn: session_for_region for arn in region_device_arns - if arn not in device_map + if arn not in sessions_for_arns } ) - devices = list(device_map.values()) - devices.sort(key=lambda x: getattr(x, order_by)) - return devices + return sessions_for_arns @staticmethod def _get_devices_regions_set( diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index d218e6ae..18b873db 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -358,6 +358,23 @@ def test_device_no_aws_session_supplied( aws_session.get_device.assert_called_with(arn) +@patch("braket.aws.aws_device.AwsDevice._get_arn_sessions") +def test_device_qpu_unknown(mock_get_arn_sessions): + arn = "arn:aws:braket:::device/qpu/a/b" + mock_session = Mock() + mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 + mock_get_arn_sessions.return_value = {arn: mock_session} + device = AwsDevice(arn) + _assert_device_fields(device, MOCK_GATE_MODEL_QPU_CAPABILITIES_1, MOCK_GATE_MODEL_QPU_1) + + +@pytest.mark.xfail(raises=ValueError) +@patch("braket.aws.aws_device.AwsDevice._get_arn_sessions") +def test_device_not_found(mock_get_arn_sessions): + mock_get_arn_sessions.return_value = {} + AwsDevice("arn:aws:braket:::device/qpu/a/b") + + @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_no_extra(aws_quantum_task_mock, device, circuit, s3_destination_folder): _run_and_assert( From c63d3427f8af7d93c3137f6239c939ec2cf45b6d Mon Sep 17 00:00:00 2001 From: ci Date: Sat, 6 Feb 2021 23:47:03 +0000 Subject: [PATCH 0277/1165] prepare release v1.5.9 --- 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 1f5372bf..724dd91e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.5.9 (2021-02-06) + +### Bug Fixes and Other Changes + + * Search for unknown QPUs + ## v1.5.8 (2021-01-29) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 862c1594..dd9239ca 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.5.9.dev0" +__version__ = "1.5.9" From f8ee9f99db36ac9136adc13a17312c6a8e649a9b Mon Sep 17 00:00:00 2001 From: ci Date: Sat, 6 Feb 2021 23:47:03 +0000 Subject: [PATCH 0278/1165] update development version to v1.5.10.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 dd9239ca..656905c0 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.5.9" +__version__ = "1.5.10.dev0" From 1c86dc8db0eff54ffece2281fd36c378d11d2afc Mon Sep 17 00:00:00 2001 From: Christian Bruun Madsen Date: Sat, 20 Feb 2021 16:22:09 -0700 Subject: [PATCH 0279/1165] documentation: adjust s3_folder naming in README to clarify which bucket to use (#210) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9e807867..b6a488d8 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ from braket.circuits import Circuit aws_account_id = boto3.client("sts").get_caller_identity()["Account"] device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") -s3_folder = (f"amazon-braket-{aws_account_id}", "folder-name") +s3_folder = (f"amazon-braket-Your-Bucket-Name", "folder-name") # Use the S3 bucket you created during onboarding bell = Circuit().h(0).cnot(0, 1) task = device.run(bell, s3_folder, shots=100) @@ -116,7 +116,7 @@ from braket.aws import AwsDevice aws_account_id = boto3.client("sts").get_caller_identity()["Account"] device = AwsDevice("arn:aws:braket:::device/qpu/rigetti/Aspen-8") -s3_folder = (f"amazon-braket-{aws_account_id}", "RIGETTI") +s3_folder = (f"amazon-braket-Your-Bucket-Name", "RIGETTI") # Use the S3 bucket you created during onboarding bell = Circuit().h(0).cnot(0, 1) task = device.run(bell, s3_folder) From 857ef4f94541ada35e635579fa8feede6cba4ee3 Mon Sep 17 00:00:00 2001 From: Christian Bruun Madsen Date: Sat, 20 Feb 2021 23:40:05 -0700 Subject: [PATCH 0280/1165] documentation: remove unneeded calls to sts from the README (#211) --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index b6a488d8..b5f0f5a4 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,6 @@ import boto3 from braket.aws import AwsDevice from braket.circuits import Circuit -aws_account_id = boto3.client("sts").get_caller_identity()["Account"] - device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") s3_folder = (f"amazon-braket-Your-Bucket-Name", "folder-name") # Use the S3 bucket you created during onboarding @@ -113,8 +111,6 @@ import boto3 from braket.circuits import Circuit from braket.aws import AwsDevice -aws_account_id = boto3.client("sts").get_caller_identity()["Account"] - device = AwsDevice("arn:aws:braket:::device/qpu/rigetti/Aspen-8") s3_folder = (f"amazon-braket-Your-Bucket-Name", "RIGETTI") # Use the S3 bucket you created during onboarding From f8a8f05271c86a1a260e9e91ae6ad5010d1cdc79 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 22 Feb 2021 18:14:23 +0000 Subject: [PATCH 0281/1165] prepare release v1.5.9.post0 --- 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 724dd91e..7967994c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.5.9.post0 (2021-02-22) + +### Documentation Changes + + * remove unneeded calls to sts from the README + * adjust s3_folder naming in README to clarify which bucket to use + ## v1.5.9 (2021-02-06) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 656905c0..a38e29d1 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.5.10.dev0" +__version__ = "1.5.9.post0" From 3a16773aae035547fa8e5dec45e2aae5dae4958c Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 22 Feb 2021 18:14:23 +0000 Subject: [PATCH 0282/1165] update development version to v1.5.10.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 a38e29d1..656905c0 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.5.9.post0" +__version__ = "1.5.10.dev0" From 4f92882ab7d8fdf4407676684cf736a1e7c685f7 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 26 Feb 2021 12:03:14 -0800 Subject: [PATCH 0283/1165] change: Remove hardcoded device ARNs (#215) --- src/braket/aws/aws_device.py | 156 +++------ .../braket/aws/common_test_utils.py | 7 +- test/unit_tests/braket/aws/test_aws_device.py | 296 +++++++----------- 3 files changed, 169 insertions(+), 290 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 125f2b09..e14d8918 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -14,10 +14,9 @@ from __future__ import annotations from enum import Enum -from typing import FrozenSet, List, Optional, Set, Union +from typing import List, Optional, Union import boto3 -from boltons.dictutils import FrozenDict from botocore.config import Config from networkx import Graph, complete_graph, from_edgelist @@ -46,21 +45,7 @@ class AwsDevice(Device): device. """ - SIMULATOR_ARNS = frozenset( - { - "arn:aws:braket:::device/quantum-simulator/amazon/sv1", - "arn:aws:braket:::device/quantum-simulator/amazon/tn1", - } - ) - QPU_REGIONS = FrozenDict( - { - "arn:aws:braket:::device/qpu/rigetti/Aspen-8": "us-west-1", - "arn:aws:braket:::device/qpu/ionq/ionQdevice": "us-east-1", - "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6": "us-west-2", - "arn:aws:braket:::device/qpu/d-wave/Advantage_system1": "us-west-2", - } - ) - REGIONS = frozenset(QPU_REGIONS.values()) + REGIONS = ("us-east-1", "us-west-1", "us-west-2") DEFAULT_SHOTS_QPU = 1000 DEFAULT_SHOTS_SIMULATOR = 0 @@ -85,12 +70,11 @@ def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): """ super().__init__(name=None, status=None) self._arn = arn - self._aws_session = AwsDevice._aws_session_for_device(arn, aws_session) self._properties = None self._provider_name = None self._topology_graph = None self._type = None - self.refresh_metadata() + self._aws_session = self._get_session_and_initialize(aws_session or AwsSession()) def run( self, @@ -223,7 +207,28 @@ def refresh_metadata(self) -> None: """ Refresh the `AwsDevice` object with the most recent Device metadata. """ - metadata = self._aws_session.get_device(self._arn) + self._populate_properties(self._aws_session) + + def _get_session_and_initialize(self, session): + current_region = session.boto_session.region_name + try: + self._populate_properties(session) + return session + except Exception: + if "qpu" not in self._arn: + raise ValueError(f"Simulator {self._arn} not found in {current_region}") + # Search remaining regions for QPU + for region in frozenset(AwsDevice.REGIONS) - {current_region}: + region_session = AwsDevice._copy_aws_session(session, region) + try: + self._populate_properties(region_session) + return region_session + except Exception: + pass + raise ValueError(f"QPU {self._arn} not found") + + def _populate_properties(self, session): + metadata = session.get_device(self._arn) self._name = metadata.get("deviceName") self._status = metadata.get("deviceStatus") self._type = AwsDeviceType(metadata.get("deviceType")) @@ -310,59 +315,23 @@ def _default_shots(self): def _default_max_parallel(self): return AwsDevice.DEFAULT_MAX_PARALLEL - @staticmethod - def _aws_session_for_device(device_arn: str, aws_session: Optional[AwsSession]) -> AwsSession: - """AwsSession: Returns an AwsSession for the device ARN.""" - if "qpu" in device_arn: - return AwsDevice._aws_session_for_qpu(device_arn, aws_session) - else: - return aws_session or AwsSession() - - @staticmethod - def _aws_session_for_qpu(device_arn: str, aws_session: Optional[AwsSession]) -> AwsSession: - """ - Get an AwsSession for the device ARN. QPUs are physically located in specific AWS Regions. - The AWS sessions should connect to the Region that the QPU is located in. - - See `braket.aws.aws_qpu.AwsDevice.DEVICE_REGIONS` for the - AWS Regions the devices are located in. - """ - if device_arn in AwsDevice.QPU_REGIONS: - return AwsDevice._copy_aws_session( - aws_session, AwsDevice.QPU_REGIONS.get(device_arn), None - ) - # If the QPU is unknown, search until it is found. - device_sessions = AwsDevice._get_arn_sessions( - [device_arn], None, {AwsDeviceType.QPU}, None, None, aws_session - ) - if device_sessions: - return device_sessions[device_arn] - raise ValueError(f"QPU {device_arn} not found") - @staticmethod def _copy_aws_session( - aws_session: Optional[AwsSession], + aws_session: AwsSession, region: Optional[str] = None, max_connections: Optional[int] = None, ) -> AwsSession: config = Config(max_pool_connections=max_connections) if max_connections else None - if aws_session: - session_region = aws_session.boto_session.region_name - new_region = region or session_region - if session_region == new_region and not config: - return aws_session - else: - creds = aws_session.boto_session.get_credentials() - boto_session = boto3.Session( - aws_access_key_id=creds.access_key, - aws_secret_access_key=creds.secret_key, - aws_session_token=creds.token, - region_name=new_region, - ) - return AwsSession(boto_session=boto_session, config=config) - else: - boto_session = boto3.Session(region_name=region) if region else None - return AwsSession(boto_session=boto_session, config=config) + session_region = aws_session.boto_session.region_name + new_region = region or session_region + creds = aws_session.boto_session.get_credentials() + boto_session = boto3.Session( + aws_access_key_id=creds.access_key, + aws_secret_access_key=creds.secret_key, + aws_session_token=creds.token, + region_name=new_region, + ) + return AwsSession(boto_session=boto_session, config=config) def __repr__(self): return "Device('name': {}, 'arn': {})".format(self.name, self.arn) @@ -415,21 +384,18 @@ def get_devices( types = ( frozenset(types) if types else frozenset({device_type for device_type in AwsDeviceType}) ) - arn_sessions = AwsDevice._get_arn_sessions( - arns, names, types, statuses, provider_names, aws_session - ) - devices = [AwsDevice(arn, arn_sessions[arn]) for arn in arn_sessions] - devices.sort(key=lambda x: getattr(x, order_by)) - return devices - - @staticmethod - def _get_arn_sessions(arns, names, types, statuses, provider_names, aws_session): aws_session = aws_session if aws_session else AwsSession() - sessions_for_arns = {} + device_map = {} session_region = aws_session.boto_session.region_name - device_regions_set = AwsDevice._get_devices_regions_set(types, arns, session_region) - for region in device_regions_set: - session_for_region = AwsDevice._copy_aws_session(aws_session, region) + search_regions = ( + (session_region,) if types == {AwsDeviceType.SIMULATOR} else AwsDevice.REGIONS + ) + for region in search_regions: + session_for_region = ( + aws_session + if region == session_region + else AwsDevice._copy_aws_session(aws_session, region) + ) # Simulators are only instantiated in the same region as the AWS session types_for_region = sorted( types if region == session_region else types - {AwsDeviceType.SIMULATOR} @@ -444,31 +410,13 @@ def _get_arn_sessions(arns, names, types, statuses, provider_names, aws_session) provider_names=provider_names, ) ] - sessions_for_arns.update( + device_map.update( { - arn: session_for_region + arn: AwsDevice(arn, session_for_region) for arn in region_device_arns - if arn not in sessions_for_arns + if arn not in device_map } ) - return sessions_for_arns - - @staticmethod - def _get_devices_regions_set( - types: Optional[Set[AwsDeviceType]], arns: Optional[List[str]], current_region: str - ) -> FrozenSet[str]: - """Get the set of regions to call `SearchDevices` API given filters""" - device_regions_set = ( - {current_region} if types == {AwsDeviceType.SIMULATOR} else set(AwsDevice.REGIONS) - ) - if arns: - arns_region_set = set() - for arn in arns: - if arn in AwsDevice.QPU_REGIONS: - arns_region_set.add(AwsDevice.QPU_REGIONS[arn]) - elif arn in AwsDevice.SIMULATOR_ARNS: - arns_region_set.add(current_region) - else: - arns_region_set.update(AwsDevice.REGIONS) - device_regions_set &= arns_region_set - return frozenset(device_regions_set) + devices = list(device_map.values()) + devices.sort(key=lambda x: getattr(x, order_by)) + return devices diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 3a53d9f6..6980c52f 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -16,10 +16,13 @@ from braket.aws import AwsQuantumTaskBatch -DWAVE_ARN = "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6" -RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-8" +DWAVE_ARN = "arn:aws:braket:::device/qpu/d-wave/Advantage_system1" +RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-9" IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/ionQdevice" SV1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" +TN1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/tn1" + +RIGETTI_REGION = "us-west-1" class MockS3: diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 18b873db..7c1a5d4c 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -18,7 +18,9 @@ DWAVE_ARN, IONQ_ARN, RIGETTI_ARN, + RIGETTI_REGION, SV1_ARN, + TN1_ARN, run_and_assert, run_batch_and_assert, ) @@ -62,7 +64,7 @@ ) MOCK_GATE_MODEL_QPU_1 = { - "deviceName": "Aspen-8", + "deviceName": "Aspen-9", "deviceType": "QPU", "providerName": "provider1", "deviceStatus": "OFFLINE", @@ -102,7 +104,7 @@ ) MOCK_GATE_MODEL_QPU_2 = { - "deviceName": "blah", + "deviceName": "Blah", "deviceType": "QPU", "providerName": "blahhhh", "deviceStatus": "OFFLINE", @@ -156,7 +158,7 @@ ) MOCK_DWAVE_QPU = { - "deviceName": "DW_2000Q_6", + "deviceName": "Advantage_system1.1", "deviceType": "QPU", "providerName": "provider1", "deviceStatus": "ONLINE", @@ -218,14 +220,14 @@ def circuit(): @pytest.fixture def boto_session(): _boto_session = Mock() - _boto_session.region_name = AwsDevice.QPU_REGIONS[RIGETTI_ARN] + _boto_session.region_name = RIGETTI_REGION return _boto_session @pytest.fixture def aws_session(): _boto_session = Mock() - _boto_session.region_name = AwsDevice.QPU_REGIONS[RIGETTI_ARN] + _boto_session.region_name = RIGETTI_REGION creds = Mock() creds.access_key = "access key" @@ -242,6 +244,7 @@ def aws_session(): def device(aws_session): def _device(arn): aws_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 + aws_session.search_devices.return_value = [MOCK_GATE_MODEL_QPU_1] return AwsDevice(arn, aws_session) return _device @@ -255,13 +258,45 @@ def _device(arn): (MOCK_DWAVE_QPU_CAPABILITIES, MOCK_DWAVE_QPU), ], ) -def test_device_creation(device_capabilities, get_device_data, arn): +def test_device_aws_session(device_capabilities, get_device_data, arn): mock_session = Mock() mock_session.get_device.return_value = get_device_data device = AwsDevice(arn, mock_session) _assert_device_fields(device, device_capabilities, get_device_data) +@patch("braket.aws.aws_device.AwsSession") +def test_device_simulator_no_aws_session(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) + _assert_device_fields(device, MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES, MOCK_GATE_MODEL_SIMULATOR) + aws_session.get_device.assert_called_with(arn) + + +@patch("braket.aws.aws_device.AwsDevice._copy_aws_session") +@patch("braket.aws.aws_device.AwsSession") +@pytest.mark.parametrize( + "get_device_side_effect", + [ + [MOCK_GATE_MODEL_QPU_1], + [ValueError(), MOCK_GATE_MODEL_QPU_1], + ], +) +def test_device_qpu_no_aws_session( + aws_session_init, mock_copy_aws_session, get_device_side_effect, aws_session +): + arn = RIGETTI_ARN + mock_session = Mock() + mock_session.get_device.side_effect = get_device_side_effect + aws_session.get_device.side_effect = ValueError() + aws_session_init.return_value = aws_session + mock_copy_aws_session.return_value = mock_session + device = AwsDevice(arn) + _assert_device_fields(device, MOCK_GATE_MODEL_QPU_CAPABILITIES_1, MOCK_GATE_MODEL_QPU_1) + + def test_device_refresh_metadata(arn): mock_session = Mock() mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 @@ -295,84 +330,20 @@ def test_repr(arn): assert repr(device) == expected -def test_device_aws_session_in_qpu_region(aws_session): - arn = RIGETTI_ARN - aws_session.boto_session.region_name = AwsDevice.QPU_REGIONS[RIGETTI_ARN] - aws_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 - AwsDevice(arn, aws_session) - - aws_session.get_device.assert_called_with(arn) - - -@patch("braket.aws.aws_device.AwsSession") -@patch("boto3.Session") -def test_aws_session_in_another_qpu_region( - boto_session_init, aws_session_init, boto_session, aws_session -): - arn = RIGETTI_ARN - region = AwsDevice.QPU_REGIONS.get(RIGETTI_ARN) - - boto_session_init.return_value = boto_session - aws_session_init.return_value = aws_session - aws_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 - - creds = Mock() - creds.access_key = "a" - creds.secret_key = "b" - creds.token = "c" - - different_region_aws_session = Mock() - different_region_aws_session.boto_session.get_credentials.return_value = creds - different_region_aws_session.boto_session.profile_name = "profile name" - different_region_aws_session.boto_session.region_name = "foobar" - - AwsDevice(arn, different_region_aws_session) - - # assert creds, and region were correctly supplied - boto_session_init.assert_called_with( - aws_access_key_id=creds.access_key, - aws_secret_access_key=creds.secret_key, - aws_session_token=creds.token, - region_name=region, - ) - - # assert supplied session, different_region_aws_session, was replaced - aws_session.get_device.assert_called_with(arn) - - -@patch("braket.aws.aws_device.AwsSession") -@patch("boto3.Session") -def test_device_no_aws_session_supplied( - boto_session_init, aws_session_init, boto_session, aws_session -): - arn = RIGETTI_ARN - region = AwsDevice.QPU_REGIONS.get(RIGETTI_ARN) - - boto_session_init.return_value = boto_session - aws_session_init.return_value = aws_session - aws_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 - - AwsDevice(arn) - - boto_session_init.assert_called_with(region_name=region) - aws_session.get_device.assert_called_with(arn) - - -@patch("braket.aws.aws_device.AwsDevice._get_arn_sessions") -def test_device_qpu_unknown(mock_get_arn_sessions): - arn = "arn:aws:braket:::device/qpu/a/b" +@pytest.mark.xfail(raises=ValueError) +def test_device_simulator_not_found(): mock_session = Mock() - mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 - mock_get_arn_sessions.return_value = {arn: mock_session} - device = AwsDevice(arn) - _assert_device_fields(device, MOCK_GATE_MODEL_QPU_CAPABILITIES_1, MOCK_GATE_MODEL_QPU_1) + mock_session.get_device.side_effect = ValueError() + AwsDevice("arn:aws:braket:::device/simulator/a/b", mock_session) @pytest.mark.xfail(raises=ValueError) -@patch("braket.aws.aws_device.AwsDevice._get_arn_sessions") -def test_device_not_found(mock_get_arn_sessions): - mock_get_arn_sessions.return_value = {} - AwsDevice("arn:aws:braket:::device/qpu/a/b") +@patch("braket.aws.aws_device.AwsDevice._copy_aws_session") +def test_device_qpu_not_found(mock_copy_aws_session): + mock_session = Mock() + mock_session.get_device.side_effect = ValueError() + mock_copy_aws_session.return_value = mock_session + AwsDevice("arn:aws:braket:::device/qpu/a/b", mock_session) @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") @@ -589,8 +560,8 @@ def _assert_device_fields(device, expected_properties, expected_device_data): @patch("braket.aws.aws_device.AwsDevice._copy_aws_session") def test_get_devices(mock_copy_aws_session, aws_session): - session_for_region = Mock() - session_for_region.search_devices.side_effect = [ + aws_session.search_devices.side_effect = [ + # us-west-1 [ { "deviceArn": SV1_ARN, @@ -600,14 +571,34 @@ def test_get_devices(mock_copy_aws_session, aws_session): "providerName": "Amazon Braket", } ], + ValueError("should not be reachable"), + ] + aws_session.get_device.side_effect = [ + MOCK_GATE_MODEL_SIMULATOR, + ValueError("should not be reachable"), + ] + session_for_region = Mock() + session_for_region.search_devices.side_effect = [ + # us-east-1 + [ + { + "deviceArn": IONQ_ARN, + "deviceName": "IonQ Device", + "deviceType": "QPU", + "deviceStatus": "ONLINE", + "providerName": "IonQ", + }, + ], + # us-west-2 [ { "deviceArn": DWAVE_ARN, - "deviceName": "DW_2000Q_6", + "deviceName": "Advantage_system1.1", "deviceType": "QPU", "deviceStatus": "ONLINE", "providerName": "D-Wave", }, + # Should not be reached because already instantiated in us-west-1 { "deviceArn": SV1_ARN, "deviceName": "SV1", @@ -616,121 +607,58 @@ def test_get_devices(mock_copy_aws_session, aws_session): "providerName": "Amazon Braket", }, ], + # Only two regions to search outside of current + ValueError("should not be reachable"), + ] + session_for_region.get_device.side_effect = [ + MOCK_DWAVE_QPU, + MOCK_GATE_MODEL_QPU_2, + ValueError("should not be reachable"), ] - session_for_region.get_device.side_effect = [MOCK_GATE_MODEL_SIMULATOR, MOCK_DWAVE_QPU] mock_copy_aws_session.return_value = session_for_region + # Search order: us-east-1, us-west-1, us-west-2 results = AwsDevice.get_devices( - arns=[SV1_ARN, DWAVE_ARN], - types=["SIMULATOR", "QPU"], - provider_names=["Amazon Braket", "D-Wave"], + arns=[SV1_ARN, DWAVE_ARN, IONQ_ARN], + provider_names=["Amazon Braket", "D-Wave", "IonQ"], statuses=["ONLINE"], aws_session=aws_session, ) - assert [result.name for result in results] == ["DW_2000Q_6", "SV1"] + assert [result.name for result in results] == ["Advantage_system1.1", "Blah", "SV1"] -@pytest.mark.parametrize( - "region,types", [("us-west-1", ["QPU", "SIMULATOR"]), ("us-west-2", ["QPU"])] -) @patch("braket.aws.aws_device.AwsDevice._copy_aws_session") -def test_get_devices_session_regions(mock_copy_aws_session, region, types): - _boto_session = Mock() - _boto_session.region_name = region - _aws_session = Mock() - _aws_session.boto_session = _boto_session - - session_for_region = Mock() - session_for_region.search_devices.return_value = [ - { - "deviceArn": SV1_ARN, - "deviceName": "SV1", - "deviceType": "SIMULATOR", - "deviceStatus": "ONLINE", - "providerName": "Amazon Braket", - } +def test_get_devices_simulators_only(mock_copy_aws_session, aws_session): + aws_session.search_devices.side_effect = [ + [ + { + "deviceArn": SV1_ARN, + "deviceName": "SV1", + "deviceType": "SIMULATOR", + "deviceStatus": "ONLINE", + "providerName": "Amazon Braket", + } + ], + ValueError("should not be reachable"), ] - session_for_region.get_device.return_value = MOCK_GATE_MODEL_SIMULATOR + aws_session.get_device.side_effect = [ + MOCK_GATE_MODEL_SIMULATOR, + ValueError("should not be reachable"), + ] + session_for_region = Mock() + session_for_region.search_devices.side_effect = ValueError("should not be reachable") + session_for_region.get_device.side_effect = ValueError("should not be reachable") mock_copy_aws_session.return_value = session_for_region - - arns = [RIGETTI_ARN] - provider_names = ["Rigetti"] - statuses = ["ONLINE"] - - AwsDevice.get_devices( - arns=arns, - types=None, - provider_names=provider_names, - statuses=statuses, - aws_session=_aws_session, - ) - session_for_region.search_devices.assert_called_with( - arns=arns, - names=None, - types=types, - statuses=statuses, - provider_names=provider_names, - ) - - -def test_get_devices_simulator_different_region(): - _boto_session = Mock() - _boto_session.region_name = "us-west-2" - _aws_session = Mock() - _aws_session.boto_session = _boto_session - - assert ( - AwsDevice.get_devices( - arns=[RIGETTI_ARN], - types=["SIMULATOR"], - provider_names=None, - statuses=None, - aws_session=_aws_session, - ) - == [] + results = AwsDevice.get_devices( + arns=[SV1_ARN, TN1_ARN], + types=["SIMULATOR"], + provider_names=["Amazon Braket"], + statuses=["ONLINE"], + aws_session=aws_session, ) + # Only one region should be searched + assert [result.name for result in results] == ["SV1"] @pytest.mark.xfail(raises=ValueError) def test_get_devices_invalid_order_by(): AwsDevice.get_devices(order_by="foo") - - -@pytest.mark.parametrize( - "input,output", - [ - ( - {"types": None, "arns": None}, - {"us-west-2", "us-west-1", "us-east-1"}, - ), - ( - {"types": {AwsDeviceType.QPU}, "arns": None}, - {"us-west-2", "us-west-1", "us-east-1"}, - ), - ( - {"types": {AwsDeviceType.SIMULATOR}, "arns": None}, - {"us-west-2"}, - ), - ( - {"types": {AwsDeviceType.QPU, AwsDeviceType.SIMULATOR}, "arns": None}, - {"us-east-1", "us-west-1", "us-west-2"}, - ), - ( - {"types": None, "arns": [RIGETTI_ARN, IONQ_ARN]}, - {"us-east-1", "us-west-1"}, - ), - ( - {"types": None, "arns": [RIGETTI_ARN, SV1_ARN]}, - {"us-west-1", "us-west-2"}, - ), - ( - {"types": {AwsDeviceType.SIMULATOR}, "arns": [RIGETTI_ARN, IONQ_ARN]}, - set(), - ), - ( - {"types": None, "arns": ["blah"]}, - {"us-east-1", "us-west-1", "us-west-2"}, - ), - ], -) -def test_get_devices_regions_set(input, output): - assert AwsDevice._get_devices_regions_set(current_region="us-west-2", **input) == output From e2d4ac5295c192ec7f10bbe93a734f23592ba1e2 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 26 Feb 2021 13:02:57 -0800 Subject: [PATCH 0284/1165] fix: Unitary equality checks matrix (#217) Before, Unitary.__eq__ only checked that the types were the same. This change makes sure that the actual matrices are also equivalent. --- src/braket/circuits/gates.py | 5 +++++ test/unit_tests/braket/circuits/test_circuit.py | 4 ++-- test/unit_tests/braket/circuits/test_gates.py | 12 ++++++++++++ test/unit_tests/braket/circuits/test_observables.py | 4 ++-- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index d85ccfcd..ae6c273c 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -1310,6 +1310,11 @@ def to_ir(self, target: QubitSet): matrix=Unitary._transform_matrix_to_ir(self._matrix), ) + def __eq__(self, other): + if isinstance(other, Unitary): + return self.matrix_equivalence(other) + return NotImplemented + @staticmethod def _transform_matrix_to_ir(matrix: np.ndarray): return [[[element.real, element.imag] for element in row] for row in matrix.tolist()] diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 91dd8c96..88cfe4c5 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -607,7 +607,7 @@ def test_basis_rotation_instructions_multiple_result_types_different_hermitian_t expected = [ Instruction( Gate.Unitary( - matrix=1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) + matrix=1.0 / np.sqrt(2.0) * np.array([[-1.0, 1.0], [1.0, 1.0]], dtype=complex) ), target=[0], ), @@ -637,7 +637,7 @@ def test_basis_rotation_instructions_multiple_result_types_tensor_product_hermit Instruction(Gate.Ry(-np.pi / 4), 1), Instruction( Gate.Unitary( - matrix=1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) + matrix=1.0 / np.sqrt(2.0) * np.array([[-1.0, 1.0], [1.0, 1.0]], dtype=complex) ), target=[2], ), diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index a62512c9..ac0e346f 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -299,6 +299,18 @@ def test_gate_to_matrix(testclass, subroutine_name, irclass, irsubclasses, kwarg # Additional Unitary gate tests +def test_equality(): + u1 = Gate.Unitary(np.array([[0 + 0j, 1 + 0j], [1 + 0j, 0 + 0j]])) + u2 = Gate.Unitary(np.array([[0, 1], [1, 0]], dtype=np.float32), display_name=["u2"]) + other_gate = Gate.Unitary(np.array([[1, 0], [0, 1]])) + non_gate = "non gate" + + assert u1 == u2 + assert u1 is not u2 + assert u1 != other_gate + assert u1 != non_gate + + @pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("matrix", invalid_unitary_matrices) def test_unitary_invalid_matrix(matrix): diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index e58fec88..274a2b52 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -130,7 +130,7 @@ def test_hermitian_eigenvalues(matrix, eigenvalues): "matrix,basis_rotation_matrix", [ ( - np.array([[1.0, 0.0], [0.0, 1.0]]), + np.array([[0.0, 1.0], [1.0, 0.0]]), np.array([[-0.70710678, 0.70710678], [0.70710678, 0.70710678]]).conj().T, ), ( @@ -156,7 +156,7 @@ 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 expected_unitary.matrix_equivalence(actual_rotation_gates) + assert expected_unitary.matrix_equivalence(actual_rotation_gates[0]) @pytest.mark.xfail(raises=ValueError) From e5ae0e671b5bdc9f95034b2639173c4fe6cd1e40 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 26 Feb 2021 13:08:26 -0800 Subject: [PATCH 0285/1165] fix: AngledGate equality checks angles (#216) Currently, `AngledGate` does not check for angle equality (just type equality) in `__eq__`. This change fixes this. --- src/braket/circuits/angled_gate.py | 6 ++++++ test/unit_tests/braket/circuits/test_angled_gate.py | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index 532974a8..936cbb2e 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.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. +import math from typing import Sequence from braket.circuits.gate import Gate @@ -52,5 +53,10 @@ def angle(self) -> float: """ return self._angle + def __eq__(self, other): + if isinstance(other, AngledGate): + return self.name == other.name and math.isclose(self.angle, other.angle) + return NotImplemented + def __repr__(self): return f"{self.name}('angle': {self.angle}, 'qubit_count': {self.qubit_count})" diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index 3bcc822f..8dc4d88a 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -50,6 +50,17 @@ def test_angle_setter(angled_gate): angled_gate.angle = 0.14 +def test_equality(angled_gate): + gate = AngledGate(angle=0.15, qubit_count=1, ascii_symbols=["foo"]) + other_gate = AngledGate(angle=0.3, qubit_count=1, ascii_symbols=["foo"]) + non_gate = "non gate" + + assert angled_gate == gate + assert angled_gate is not gate + assert angled_gate != other_gate + assert angled_gate != non_gate + + 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() From e309aa0790e2d7d7dd876f1b02995e8649aeee75 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 26 Feb 2021 13:58:47 -0800 Subject: [PATCH 0286/1165] infra: very minor test changes (#218) --- test/unit_tests/braket/circuits/test_angled_gate.py | 2 +- test/unit_tests/braket/circuits/test_gate.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index 8dc4d88a..4e6b6fa1 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -51,7 +51,7 @@ def test_angle_setter(angled_gate): def test_equality(angled_gate): - gate = AngledGate(angle=0.15, qubit_count=1, ascii_symbols=["foo"]) + gate = AngledGate(angle=0.15, qubit_count=1, ascii_symbols=["bar"]) other_gate = AngledGate(angle=0.3, qubit_count=1, ascii_symbols=["foo"]) non_gate = "non gate" diff --git a/test/unit_tests/braket/circuits/test_gate.py b/test/unit_tests/braket/circuits/test_gate.py index 15392647..0c931eb9 100644 --- a/test/unit_tests/braket/circuits/test_gate.py +++ b/test/unit_tests/braket/circuits/test_gate.py @@ -62,7 +62,7 @@ def test_str_angle(): def test_equality(): gate_1 = Gate(qubit_count=1, ascii_symbols=["foo"]) - gate_2 = Gate(qubit_count=1, ascii_symbols=["foo"]) + gate_2 = Gate(qubit_count=1, ascii_symbols=["bar"]) other_gate = Gate.Rx(angle=0.34) non_gate = "non gate" From af420f0d9405a62c3ac5ff35587faae50d9c9bc6 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 26 Feb 2021 14:41:48 -0800 Subject: [PATCH 0287/1165] change: Use np.eye for identity (#219) --- src/braket/circuits/gates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index ae6c273c..a20bc369 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -82,7 +82,7 @@ def to_ir(self, target: QubitSet): return ir.I.construct(target=target[0]) def to_matrix(self) -> np.ndarray: - return np.array([[1.0, 0.0], [0.0, 1.0]], dtype=complex) + return np.eye(2, dtype=complex) @staticmethod @circuit.subroutine(register=True) From 9324caac11533122d332c80a442f8fcf8f53c401 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 2 Mar 2021 12:42:35 -0800 Subject: [PATCH 0288/1165] infra: Use main instead of PyPi for build dependencies (#220) --- .github/workflows/python-package.yml | 5 ++++- tox.ini | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 6796121a..b312ef49 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -37,6 +37,9 @@ jobs: flake8 - name: Run unit tests run: | - tox -e unit-tests + coverage run -m pytest + coverage combine + coverage report + coverage xml - name: Upload coverage report to Codecov uses: codecov/codecov-action@v1 diff --git a/tox.ini b/tox.ini index b20f4aa1..45c68c57 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,6 @@ commands = coverage combine coverage report coverage html - coverage xml extras = test [testenv:integ-tests] From aec9d4b7902d1f90c0656a30316c9e2581515ffe Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 2 Mar 2021 13:12:31 -0800 Subject: [PATCH 0289/1165] fix: Don't return NotImplemented for boolean (#221) `NotImplemented` is a truthy value, so anything relying on the boolean value of `matrix_equivalence` will incorrectly handle non-operators. Also, according to the [Python docs](https://docs.python.org/3/library/constants.html#NotImplemented), it "should not be evaluated in a boolean context." --- src/braket/circuits/quantum_operator.py | 4 ++-- test/unit_tests/braket/circuits/test_gate.py | 3 +-- test/unit_tests/braket/circuits/test_quantum_operator.py | 3 +-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/braket/circuits/quantum_operator.py b/src/braket/circuits/quantum_operator.py index c4497c9d..f05f4821 100644 --- a/src/braket/circuits/quantum_operator.py +++ b/src/braket/circuits/quantum_operator.py @@ -88,7 +88,7 @@ def to_matrix(self, *args, **kwargs) -> np.ndarray: """ raise NotImplementedError("to_matrix has not been implemented yet.") - def matrix_equivalence(self, other: QuantumOperator): + def matrix_equivalence(self, other: QuantumOperator) -> bool: """ Whether the matrix form of two quantum operators are equivalent @@ -100,7 +100,7 @@ def matrix_equivalence(self, other: QuantumOperator): are equivalent """ if not isinstance(other, QuantumOperator): - return NotImplemented + return False try: return np.allclose(self.to_matrix(), other.to_matrix()) except ValueError: diff --git a/test/unit_tests/braket/circuits/test_gate.py b/test/unit_tests/braket/circuits/test_gate.py index 0c931eb9..56e881dd 100644 --- a/test/unit_tests/braket/circuits/test_gate.py +++ b/test/unit_tests/braket/circuits/test_gate.py @@ -45,8 +45,7 @@ def test_matrix_equivalence(): def test_matrix_equivalence_non_gate(): gate1 = Gate.H() - x = 1 - assert gate1.matrix_equivalence(x) == NotImplemented + assert not gate1.matrix_equivalence(1) def test_str(gate): diff --git a/test/unit_tests/braket/circuits/test_quantum_operator.py b/test/unit_tests/braket/circuits/test_quantum_operator.py index ed3bedfc..842fa077 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator.py @@ -104,8 +104,7 @@ def to_matrix(self): def test_matrix_equivalence_non_quantum_operator(): quantum_operator1 = QuantumOperator(qubit_count=1, ascii_symbols=["foo"]) - x = 1 - assert quantum_operator1.matrix_equivalence(x) == NotImplemented + assert not quantum_operator1.matrix_equivalence(1) def test_str(quantum_operator): From 1c9fdca6a55c37c916d6ff3b087b1cfcdc23ee30 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 2 Mar 2021 14:00:33 -0800 Subject: [PATCH 0290/1165] doc: Add note about AWS region in README (#222) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b5f0f5a4..f5da4a0a 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ To learn more about IAM user, roles, and policies, see [Adding and Removing IAM Follow the installation [instructions](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html) for Boto3 and setting up AWS credentials. +**Note:** Make sure that your AWS region is set to one supported by Amazon Braket. You can check this in your AWS configuration file, which is located by default at `~/.aws/config`. + ### Configure your AWS account with the resources necessary for Amazon Braket If you are new to Amazon Braket, onboard to the service and create the resources necessary to use Amazon Braket using the [AWS console](https://console.aws.amazon.com/braket/home ). From 6e6e6dd60c77eaf0378c021a1ade778945f36d7a Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 2 Mar 2021 15:17:23 -0800 Subject: [PATCH 0291/1165] doc: Wording changes (#223) --- src/braket/circuits/gates.py | 8 +++--- src/braket/circuits/observables.py | 4 +-- .../circuits/quantum_operator_helpers.py | 25 ++++++++++++++----- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index a20bc369..44b3101b 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -1287,8 +1287,8 @@ class Unitary(Gate): Raises: ValueError: If `matrix` is not a two-dimensional square matrix, - or has a dimension length which is not a positive exponent of 2, - or is non-unitary. + or has a dimension length that is not a positive power of 2, + or is not unitary. """ def __init__(self, matrix: np.ndarray, display_name: str = "U"): @@ -1336,8 +1336,8 @@ def unitary(targets: QubitSet, matrix: np.ndarray, display_name: str = "U") -> I Raises: ValueError: If `matrix` is not a two-dimensional square matrix, - or has a dimension length which is not compatible with the `targets`, - or is non-unitary, + or has a dimension length that is not compatible with the `targets`, + or is not unitary, Examples: >>> circ = Circuit().unitary(matrix=np.array([[0, 1],[1, 0]]), targets=[0]) diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 9eb5aa49..3e00674b 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -299,8 +299,8 @@ def __init__(self, matrix: np.ndarray, display_name: str = "Hermitian"): Raises: ValueError: If `matrix` is not a two-dimensional square matrix, - or has a dimension length that is not a positive exponent of 2, - or is non-hermitian. + or has a dimension length that is not a positive power of 2, + or is not Hermitian. Examples: >>> Observable.Hermitian(matrix=np.array([[0, 1],[1, 0]])) diff --git a/src/braket/circuits/quantum_operator_helpers.py b/src/braket/circuits/quantum_operator_helpers.py index 3c50ade7..a6df3933 100644 --- a/src/braket/circuits/quantum_operator_helpers.py +++ b/src/braket/circuits/quantum_operator_helpers.py @@ -18,7 +18,7 @@ def verify_quantum_operator_matrix_dimensions(matrix: np.array) -> None: """ - Verifies matrix is square and matrix dimensions are positive exponents of 2, + Verifies matrix is square and matrix dimensions are positive powers of 2, raising `ValueError` otherwise. Args: @@ -26,7 +26,7 @@ def verify_quantum_operator_matrix_dimensions(matrix: np.array) -> None: Raises: ValueError: If `matrix` is not a two-dimensional square matrix, - or has a dimension length which is not a positive exponent of 2 + or has a dimension length that is not a positive power of 2 """ if not is_square_matrix(matrix): raise ValueError(f"{matrix} is not a two-dimensional square matrix") @@ -34,13 +34,19 @@ def verify_quantum_operator_matrix_dimensions(matrix: np.array) -> None: matrix = np.array(matrix, dtype=complex) qubit_count = int(np.log2(matrix.shape[0])) if 2 ** qubit_count != matrix.shape[0] or qubit_count < 1: - raise ValueError(f"`matrix` dimension {matrix.shape[0]} is not a positive exponent of 2") + raise ValueError(f"`matrix` dimension {matrix.shape[0]} is not a positive power of 2") def is_hermitian(matrix: np.array) -> bool: - """ + r""" Whether matrix is Hermitian + A square matrix :math:`U` is Hermitian if + + .. math:: U = U^\dagger + + where :math:`U^\dagger` is the conjugate transpose of :math:`U`. + Args: matrix (np.ndarray): matrix to verify @@ -52,7 +58,7 @@ def is_hermitian(matrix: np.array) -> bool: def is_square_matrix(matrix: np.array) -> bool: """ - Whether matrix is square, meaning matrix has two dimensions are both are equivalent + Whether matrix is square, meaning it has exactly two dimensions and the dimensions are equal Args: matrix (np.ndarray): matrix to verify @@ -64,9 +70,16 @@ def is_square_matrix(matrix: np.array) -> bool: def is_unitary(matrix: np.array) -> bool: - """ + r""" Whether matrix is unitary + A square matrix :math:`U` is unitary if + + .. math:: UU^\dagger = I + + where :math:`U^\dagger` is the conjugate transpose of :math:`U` + and :math:`I` is the identity matrix. + Args: matrix (np.ndarray): matrix to verify From 909d69324554866e6211c7f062f41fac68d065ec Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 3 Mar 2021 18:14:23 +0000 Subject: [PATCH 0292/1165] prepare release v1.5.10 --- CHANGELOG.md | 20 ++++++++++++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7967994c..8b005a16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## v1.5.10 (2021-03-03) + +### Bug Fixes and Other Changes + + * Don't return NotImplemented for boolean + * Use np.eye for identity + * AngledGate equality checks angles + * Unitary equality checks matrix + * Remove hardcoded device ARNs + +### Documentation Changes + + * Wording changes + * Add note about AWS region in README + +### Testing and Release Infrastructure + + * Use main instead of PyPi for build dependencies + * very minor test changes + ## v1.5.9.post0 (2021-02-22) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 656905c0..f18914a6 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.5.10.dev0" +__version__ = "1.5.10" From df4a15c62bc95f3de037c860350f86d6ea05bafb Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 3 Mar 2021 18:14:23 +0000 Subject: [PATCH 0293/1165] update development version to v1.5.11.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 f18914a6..9f3e83ec 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.5.10" +__version__ = "1.5.11.dev0" From 1f8dde93e3fdc85f9a1e9cfe554d1f0c0aaeea4f Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 10 Mar 2021 21:41:45 -0800 Subject: [PATCH 0294/1165] infra: Add Python 3.9 (#224) --- .github/workflows/python-package.yml | 2 +- setup.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index b312ef49..6587e115 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.7', '3.8'] + python-version: [3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 diff --git a/setup.py b/setup.py index 54cd165c..a07ef6f0 100644 --- a/setup.py +++ b/setup.py @@ -68,5 +68,6 @@ "Programming Language :: Python", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", ], ) From 63bdf69e68737529174e6babc1d5c650887ba31d Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 11 Mar 2021 18:13:55 +0000 Subject: [PATCH 0295/1165] prepare release v1.5.10.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 8b005a16..663e3b46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.5.10.post0 (2021-03-11) + +### Testing and Release Infrastructure + + * Add Python 3.9 + ## v1.5.10 (2021-03-03) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 9f3e83ec..73f77f59 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.5.11.dev0" +__version__ = "1.5.10.post0" From 9fea0d9416b404953f89df8265250b4c82b439ea Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 11 Mar 2021 18:13:55 +0000 Subject: [PATCH 0296/1165] update development version to v1.5.11.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 73f77f59..9f3e83ec 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.5.10.post0" +__version__ = "1.5.11.dev0" From 2bf453264e661c86eb26aa306f02a38bd1db5027 Mon Sep 17 00:00:00 2001 From: Eric Battalio Date: Mon, 15 Mar 2021 15:44:27 -0700 Subject: [PATCH 0297/1165] Documentation: Updating getting started, added new content for examples. (#228) * Updated, fixed index * Bumped index depth * Added getting started * Fixed links * Refactor * fix file name * changing API link * changing API link * Reverted, cannot change * Added example templates * Added circuits / algs * fox toc * toc * Tweakindex * More * More * More * Testing * Testing * Testing * Testing * Testing * Testing * First draft * Tweaks * Fixed typos, added missing example * Fixed link, trying out title link * Changed example titles to links * Updated documentation including new examples section Co-authored-by: Christian Bruun Madsen --- doc/examples-adv-circuits-algorithms.rst | 50 ++++++++++++++ doc/examples-braket-features.rst | 38 +++++++++++ doc/examples-hybrid-quantum.rst | 29 ++++++++ doc/examples-ml-pennylane.rst | 47 +++++++++++++ doc/examples-quantum-annealing-dwave.rst | 75 +++++++++++++++++++++ doc/examples-simple-circuits-algorithms.rst | 57 ++++++++++++++++ doc/examples.rst | 18 +++++ doc/getting-started.rst | 40 +++++++++++ doc/index.rst | 48 ++++++++++--- 9 files changed, 393 insertions(+), 9 deletions(-) create mode 100644 doc/examples-adv-circuits-algorithms.rst create mode 100644 doc/examples-braket-features.rst create mode 100644 doc/examples-hybrid-quantum.rst create mode 100644 doc/examples-ml-pennylane.rst create mode 100644 doc/examples-quantum-annealing-dwave.rst create mode 100644 doc/examples-simple-circuits-algorithms.rst create mode 100644 doc/examples.rst create mode 100644 doc/getting-started.rst diff --git a/doc/examples-adv-circuits-algorithms.rst b/doc/examples-adv-circuits-algorithms.rst new file mode 100644 index 00000000..325b791d --- /dev/null +++ b/doc/examples-adv-circuits-algorithms.rst @@ -0,0 +1,50 @@ +################################ +Advanced circuits and algorithms +################################ + +Learn more about working with advanced circuits and algoritms. + +.. 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 +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 +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. + +************************ +`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. diff --git a/doc/examples-braket-features.rst b/doc/examples-braket-features.rst new file mode 100644 index 00000000..6aedd62d --- /dev/null +++ b/doc/examples-braket-features.rst @@ -0,0 +1,38 @@ +################################ +Amazon Braket features +################################ + +Learn more about the indivudal features of Amazon Braket. + +.. toctree:: + :maxdepth: 2 + +************************** +`Getting notifications when a 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 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 +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) programatically, +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. +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-hybrid-quantum.rst b/doc/examples-hybrid-quantum.rst new file mode 100644 index 00000000..0a85f538 --- /dev/null +++ b/doc/examples-hybrid-quantum.rst @@ -0,0 +1,29 @@ +################################ +Hybrid quantum algorithms +################################ + +Learn more about hybrid quantum algorithms. + +.. toctree:: + :maxdepth: 2 + +************************** +`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). + +************************** +`VQE Chemestry `_ +************************** + +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. \ No newline at end of file diff --git a/doc/examples-ml-pennylane.rst b/doc/examples-ml-pennylane.rst new file mode 100644 index 00000000..dc1eab20 --- /dev/null +++ b/doc/examples-ml-pennylane.rst @@ -0,0 +1,47 @@ +################################ +Quantum machine learning and optimization with PennyLane +################################ + +Learn more about how to combine PennyLane with Amazon Braket. + +.. toctree:: + :maxdepth: 2 + +************************** +`Combining PennyLane with Amazon Braket `_ +************************** + +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 +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, +using around 1-2 minutes per iteration. + +************************** +`Quantum chemistry 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, +making optimization more efficient. diff --git a/doc/examples-quantum-annealing-dwave.rst b/doc/examples-quantum-annealing-dwave.rst new file mode 100644 index 00000000..f9739414 --- /dev/null +++ b/doc/examples-quantum-annealing-dwave.rst @@ -0,0 +1,75 @@ +################################ +Quantum annealing with D-Wave +################################ + +Learn more about quantum annealing with D-Wave. + +.. toctree:: + :maxdepth: 2 + +************************** +`Anatomy of annealing with ocean `_ +************************** + +Learn more about the anatomy of quantum annealing with D-Wave on Amazon Braket. + +************************** +` Running large problems with QBSolv `_ +************************** + +This tutorial demonstrates how to solve problems with sizes larger than a +D-Wave device can support, by using a hybrid solver called QBSolv. +QBSolv can decompose large problems into sub-problems, which are solved by the +QPU and a classical Tabu solver, or by the classical solver alone. The results +of the sub-problems then construct the solution to the problem. + +************************** +`Maximum Cut `_ +************************** + +This tutorial solves a small instance of the famous maximum cut (MaxCut) problem using +a D-Wave device on Amazon Braket. The MaxCut problem is one of the most famous NP-hard +problems in combinatorial optimization. Applications can be found in clustering problems +for marketing purposes, or for portfolio optimization problems in finance. + +************************** +`Minimum Vertex `_ +************************** + +This tutorial solves a small instance of the minimum vertex problem +using BraketSampler and the BraketDWaveSampler. BraketDWaveSampler uses D-Wave parameter names +(such as answer_mode). BraketSampler uses parameter names that are consistent +with the rest of the Amazon Braket experience. + +************************** +`Graph partitioning `_ +************************** + +This tutorial solves a small instance of a graph partitioning +problem using a D-Wave device on Amazon Braket. + +************************** +`Factoring `_ +************************** + +This tutorial shows how to solve a constraint satisfaction +problem (CSP) problem, with the example of factoring, using a D-Wave device on +Amazon Braket. Particularly, factoring is expressed as a CSP using Boolean +logic operations, and it is converted to a binary quadratic model that can be +solved by a D-Wave device. + +************************** +`Structural Imbalance `_ +************************** + +This tutorial solves a structural imbalance problem using a D-Wave device +on Amazon Braket. Social networks map relationships between people or organizations +onto graphs. Given a social network as a graph, D-Wave devices can partition the graph +into two colored sets, and show the frustrated edges. + +************************** +`Traveling Salesman Problem `_ +************************** + +This tutorial solves small instances of the famous traveling salesman problem +(TSP) using D-Wave devices on Amazon Braket. diff --git a/doc/examples-simple-circuits-algorithms.rst b/doc/examples-simple-circuits-algorithms.rst new file mode 100644 index 00000000..9527db3e --- /dev/null +++ b/doc/examples-simple-circuits-algorithms.rst @@ -0,0 +1,57 @@ +############################## +Simple circuits and algorithms +############################## + +Learn more about working with advanced circuits and algoritms. + +.. toctree:: + :maxdepth: 2 + +*************** +`Getting started `_ +*************** + +A hello-world tutorial that shows you how to build a simple circuit and run it on a local simulator. + +*************** +`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, +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. + +*************** +`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 +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, +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 +to the receiver (aka Bob), who is then able to decode the full two-bit message. + diff --git a/doc/examples.rst b/doc/examples.rst new file mode 100644 index 00000000..1b9987ac --- /dev/null +++ b/doc/examples.rst @@ -0,0 +1,18 @@ +######## +Examples +######## + +There are several examples available in the Amazon Braket repo: +https://github.com/aws/amazon-braket-examples. + +.. toctree:: + :maxdepth: 2 + + examples-braket-features.rst + examples-simple-circuits-algorithms.rst + examples-adv-circuits-algorithms.rst + examples-hybrid-quantum.rst + examples-ml-pennylane.rst + examples-quantum-annealing-dwave.rst + + \ No newline at end of file diff --git a/doc/getting-started.rst b/doc/getting-started.rst new file mode 100644 index 00000000..efd33bee --- /dev/null +++ b/doc/getting-started.rst @@ -0,0 +1,40 @@ +################################################# +Getting Started with the Amazon Braket Python SDK +################################################# + +It is easy to get started with Amazon Braket Python SDK. You can get +started using an Amazon Braket notebook instance or using +your own environment. + +For more informnation about Amazon Braket, see the full set of documentation +at https://docs.aws.amazon.com/braket/index.html. + +.. toctree:: + :maxdepth: 2 + +************************************************ +Getting started using an Amazon Braket notebook +************************************************ + +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: + +1. `Enable Amazon Braket `_. +2. `Create an Amazon Braket notebook instance `_. +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. + +*********************************** +Getting started in your environment +*********************************** + +You can install the Amazon Braket Python SDK in your environment +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 73bf9635..38dce728 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,14 +1,44 @@ +######################## Amazon Braket Python SDK -======================================= +######################## -Amazon Braket Python SDK is an open source library for interacting with quantum devices on Amazon Braket. +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. For information about how to configure your environment to use the amazon-braket-sdk, please see the README in the GitHub repo for this project at https://github.com/aws/amazon-braket-sdk-python. +This documentation provides information about the Amazon Braket Python SDK library. The project +homepage is in Github https://github.com/aws/amazon-braket-sdk-python. The project +includes SDK source, installation instructions, and other information. -Indices and tables -__________________ +*************** +Getting Started +*************** + +.. toctree:: + :maxdepth: 2 + + getting-started + + +******** +Examples +******** + +Explore Amazon Braket examples. + +.. toctree:: + :maxdepth: 3 + + examples.rst + + +*************** +Python SDK APIs +*************** + +The Amazon Braket Python SDK APIs: + +.. toctree:: + :maxdepth: 2 + + _apidoc/modules -* :doc:`_apidoc/modules` -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` From e86238ce704a27eb004505811ada063512341c53 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 15 Mar 2021 18:13:25 -0700 Subject: [PATCH 0298/1165] doc: Remove STS calls from examples (#225) --- README.md | 4 ++-- examples/bell.py | 8 +++----- examples/debug_bell.py | 8 +++----- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index f5da4a0a..2d336411 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ from braket.aws import AwsDevice from braket.circuits import Circuit device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") -s3_folder = (f"amazon-braket-Your-Bucket-Name", "folder-name") # Use the S3 bucket you created during onboarding +s3_folder = ("amazon-braket-Your-Bucket-Name", "folder-name") # Use the S3 bucket you created during onboarding bell = Circuit().h(0).cnot(0, 1) task = device.run(bell, s3_folder, shots=100) @@ -114,7 +114,7 @@ from braket.circuits import Circuit from braket.aws import AwsDevice device = AwsDevice("arn:aws:braket:::device/qpu/rigetti/Aspen-8") -s3_folder = (f"amazon-braket-Your-Bucket-Name", "RIGETTI") # Use the S3 bucket you created during onboarding +s3_folder = ("amazon-braket-Your-Bucket-Name", "RIGETTI") # Use the S3 bucket you created during onboarding bell = Circuit().h(0).cnot(0, 1) task = device.run(bell, s3_folder) diff --git a/examples/bell.py b/examples/bell.py index a0101340..32039328 100644 --- a/examples/bell.py +++ b/examples/bell.py @@ -11,15 +11,13 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -import boto3 - from braket.aws import AwsDevice from braket.circuits import Circuit -aws_account_id = boto3.client("sts").get_caller_identity()["Account"] - device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") -s3_folder = (f"amazon-braket-{aws_account_id}", "folder-name") + +# Use the S3 bucket you created during onboarding +s3_folder = ("amazon-braket-Your-Bucket-Name", "folder-name") # https://wikipedia.org/wiki/Bell_state bell = Circuit().h(0).cnot(0, 1) diff --git a/examples/debug_bell.py b/examples/debug_bell.py index 754e43e6..4f181375 100644 --- a/examples/debug_bell.py +++ b/examples/debug_bell.py @@ -14,8 +14,6 @@ import logging import sys -import boto3 - from braket.aws import AwsDevice from braket.circuits import Circuit @@ -23,10 +21,10 @@ logger.addHandler(logging.StreamHandler(stream=sys.stdout)) # configure to print to sys.stdout logger.setLevel(logging.DEBUG) # print to sys.stdout all log messages with level DEBUG or above -aws_account_id = boto3.client("sts").get_caller_identity()["Account"] - device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") -s3_folder = (f"amazon-braket-{aws_account_id}", "folder-name") + +# Use the S3 bucket you created during onboarding +s3_folder = ("amazon-braket-Your-Bucket-Name", "folder-name") bell = Circuit().h(0).cnot(0, 1) # pass in logger to device.run, enabling debugging logs to print to console From 5db99d2fb2a6e23d2e18012fca9875411e5117cf Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 16 Mar 2021 18:13:52 +0000 Subject: [PATCH 0299/1165] prepare release v1.5.10.post1 --- 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 663e3b46..efc928e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.5.10.post1 (2021-03-16) + +### Documentation Changes + + * Remove STS calls from examples + ## v1.5.10.post0 (2021-03-11) ### Testing and Release Infrastructure diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 9f3e83ec..3a104cba 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.5.11.dev0" +__version__ = "1.5.10.post1" From e687365ca3382ab87344ccc27b90016481806673 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 16 Mar 2021 18:13:52 +0000 Subject: [PATCH 0300/1165] update development version to v1.5.11.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 3a104cba..9f3e83ec 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.5.10.post1" +__version__ = "1.5.11.dev0" From 9ba26e97928387ed6e945a03a8e31ffe6ee239fe Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 19 Mar 2021 14:28:01 -0700 Subject: [PATCH 0301/1165] infra: Run unit tests for dependent packages (#231) --- .github/workflows/dependent-tests.yml | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/dependent-tests.yml diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml new file mode 100644 index 00000000..9744ad77 --- /dev/null +++ b/.github/workflows/dependent-tests.yml @@ -0,0 +1,38 @@ +name: Dependent tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: [3.7, 3.8, 3.9] + dependent: [amazon-braket-ocean-plugin-python, amazon-braket-pennylane-plugin-python] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install --upgrade pip + pip install --upgrade git+https://github.com/aws/amazon-braket-schemas-python@main + pip install --upgrade git+https://github.com/aws/amazon-braket-default-simulator-python@main + pip install -e . + cd .. + git clone https://github.com/aws/${{ matrix.dependent }}.git + cd ${{ matrix.dependent }} + pip install -e .[test] + - name: Run unit tests + run: | + cd ../${{ matrix.dependent }} + pytest From db655985328a52e1b91c30b6303e171679b0ca8f Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 19 Mar 2021 21:41:01 +0000 Subject: [PATCH 0302/1165] prepare release v1.5.10.post2 --- 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 efc928e0..1732ae63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.5.10.post2 (2021-03-19) + +### Testing and Release Infrastructure + + * Run unit tests for dependent packages + ## v1.5.10.post1 (2021-03-16) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 9f3e83ec..35017b8d 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.5.11.dev0" +__version__ = "1.5.10.post2" From 0938afae7f17b2ab390306d27f282d5b5a188fe4 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 19 Mar 2021 21:41:01 +0000 Subject: [PATCH 0303/1165] update development version to v1.5.11.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 35017b8d..9f3e83ec 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.5.10.post2" +__version__ = "1.5.11.dev0" From f0d89a3e9a98a4224c8a96e846504ed68d79533b Mon Sep 17 00:00:00 2001 From: Viraj Chaudhari <72896239+virajchau@users.noreply.github.com> Date: Sun, 21 Mar 2021 11:52:54 -0700 Subject: [PATCH 0304/1165] Fix broken repository links (#233) --- doc/examples-adv-circuits-algorithms.rst | 8 ++++---- doc/examples-braket-features.rst | 8 ++++---- doc/examples-hybrid-quantum.rst | 6 +++--- doc/examples-ml-pennylane.rst | 8 ++++---- doc/examples-quantum-annealing-dwave.rst | 16 ++++++++-------- doc/examples-simple-circuits-algorithms.rst | 10 +++++----- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/doc/examples-adv-circuits-algorithms.rst b/doc/examples-adv-circuits-algorithms.rst index 325b791d..5cbd924d 100644 --- a/doc/examples-adv-circuits-algorithms.rst +++ b/doc/examples-adv-circuits-algorithms.rst @@ -8,7 +8,7 @@ Learn more about working with advanced circuits and algoritms. :maxdepth: 2 ************************** -`Grover's search algorithm `_ +`Grover's search algorithm `_ ************************** This tutorial provides a step-by-step walkthrough of Grover's quantum algorithm. @@ -18,7 +18,7 @@ gates that are not part of the basic gate set provided by the SDK. A custom gate as a core quantum gate by registering it as a subroutine. ******************************* -`Quantum amplitude amplification `_ +`Quantum amplitude amplification `_ ******************************* This tutorial provides a detailed discussion and implementation of the Quantum Amplitude Amplification (QAA) @@ -30,7 +30,7 @@ quadratic speedup over several classical algorithms. ************************* -`Quantum fourier transform `_ +`Quantum fourier transform `_ ************************* This tutorial provides a detailed implementation of the Quantum Fourier Transform (QFT) and @@ -39,7 +39,7 @@ most famously Shor's algorithm for factoring and the quantum phase estimation (Q for estimating the eigenvalues of a unitary operator. ************************ -`Quantum phase estimation `_ +`Quantum phase estimation `_ ************************ This tutorial provides a detailed implementation of the Quantum Phase Estimation (QPE) diff --git a/doc/examples-braket-features.rst b/doc/examples-braket-features.rst index 6aedd62d..bb6899ca 100644 --- a/doc/examples-braket-features.rst +++ b/doc/examples-braket-features.rst @@ -8,7 +8,7 @@ Learn more about the indivudal features of Amazon Braket. :maxdepth: 2 ************************** -`Getting notifications when a task completes `_ +`Getting notifications when a task completes `_ ************************** This tutorial illustrates how Amazon Braket integrates with Amazon EventBridge for @@ -16,14 +16,14 @@ event-based processing. In the tutorial, you will learn how to configure Amazon and Amazon Eventbridge to receive text notification about task completions on your phone. ************************** -`Allocating Qubits on QPU Devices `_ +`Allocating Qubits on QPU Devices `_ ************************** 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 `_ +`Getting Devices and Checking Device Properties `_ ************************** This example shows how to interact with the Amazon Braket GetDevice API to @@ -31,7 +31,7 @@ retrieve Amazon Braket devices (such as simulators and QPUs) programatically, and how to gain access to their properties. ************************** -`Using the tensor network simulator TN1 `_ +`Using the tensor network simulator TN1 `_ ************************** This notebook introduces the Amazon Braket managed tensor network simulator, TN1. diff --git a/doc/examples-hybrid-quantum.rst b/doc/examples-hybrid-quantum.rst index 0a85f538..135f1487 100644 --- a/doc/examples-hybrid-quantum.rst +++ b/doc/examples-hybrid-quantum.rst @@ -8,21 +8,21 @@ Learn more about hybrid quantum algorithms. :maxdepth: 2 ************************** -`QAOA `_ +`QAOA `_ ************************** This tutorial shows how to (approximately) solve binary combinatorial optimization problems using the Quantum Approximate Optimization Algorithm (QAOA). ************************** -`VQE Transverse Ising `_ +`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). ************************** -`VQE Chemestry `_ +`VQE Chemestry `_ ************************** This tutorial shows how to implement the Variational Quantum Eigensolver (VQE) algorithm in diff --git a/doc/examples-ml-pennylane.rst b/doc/examples-ml-pennylane.rst index dc1eab20..f3f7e4b0 100644 --- a/doc/examples-ml-pennylane.rst +++ b/doc/examples-ml-pennylane.rst @@ -8,14 +8,14 @@ Learn more about how to combine PennyLane with Amazon Braket. :maxdepth: 2 ************************** -`Combining PennyLane with Amazon Braket `_ +`Combining PennyLane with Amazon Braket `_ ************************** 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 `_ +`Computing gradients in parallel with PennyLane-Braket `_ ************************** Learn how to speed up training of quantum circuits by using parallel execution on @@ -26,7 +26,7 @@ local simulator for both executions and gradient calculations. This illustrates parallel capabilities can be combined between PennyLane and SV1. ************************** -`Graph optimization with QAOA `_ +`Graph optimization with QAOA `_ ************************** In this tutorial, you learn how quantum circuit training can be applied to a problem @@ -37,7 +37,7 @@ the Amazon Braket SV1 simulator to speed up gradient calculations and hence trai using around 1-2 minutes per iteration. ************************** -`Quantum chemistry with VQE `_ +`Quantum chemistry with VQE `_ ************************** In this tutorial, you will learn how PennyLane and Amazon Braket can be combined to solve an diff --git a/doc/examples-quantum-annealing-dwave.rst b/doc/examples-quantum-annealing-dwave.rst index f9739414..3b0faaf2 100644 --- a/doc/examples-quantum-annealing-dwave.rst +++ b/doc/examples-quantum-annealing-dwave.rst @@ -8,13 +8,13 @@ Learn more about quantum annealing with D-Wave. :maxdepth: 2 ************************** -`Anatomy of annealing with ocean `_ +`Anatomy of annealing with ocean `_ ************************** Learn more about the anatomy of quantum annealing with D-Wave on Amazon Braket. ************************** -` Running large problems with QBSolv `_ +` Running large problems with QBSolv `_ ************************** This tutorial demonstrates how to solve problems with sizes larger than a @@ -24,7 +24,7 @@ QPU and a classical Tabu solver, or by the classical solver alone. The results of the sub-problems then construct the solution to the problem. ************************** -`Maximum Cut `_ +`Maximum Cut `_ ************************** This tutorial solves a small instance of the famous maximum cut (MaxCut) problem using @@ -33,7 +33,7 @@ problems in combinatorial optimization. Applications can be found in clustering for marketing purposes, or for portfolio optimization problems in finance. ************************** -`Minimum Vertex `_ +`Minimum Vertex `_ ************************** This tutorial solves a small instance of the minimum vertex problem @@ -42,14 +42,14 @@ using BraketSampler and the BraketDWaveSampler. BraketDWaveSampler uses D-Wave p with the rest of the Amazon Braket experience. ************************** -`Graph partitioning `_ +`Graph partitioning `_ ************************** This tutorial solves a small instance of a graph partitioning problem using a D-Wave device on Amazon Braket. ************************** -`Factoring `_ +`Factoring `_ ************************** This tutorial shows how to solve a constraint satisfaction @@ -59,7 +59,7 @@ logic operations, and it is converted to a binary quadratic model that can be solved by a D-Wave device. ************************** -`Structural Imbalance `_ +`Structural Imbalance `_ ************************** This tutorial solves a structural imbalance problem using a D-Wave device @@ -68,7 +68,7 @@ onto graphs. Given a social network as a graph, D-Wave devices can partition the into two colored sets, and show the frustrated edges. ************************** -`Traveling Salesman Problem `_ +`Traveling Salesman Problem `_ ************************** This tutorial solves small instances of the famous traveling salesman problem diff --git a/doc/examples-simple-circuits-algorithms.rst b/doc/examples-simple-circuits-algorithms.rst index 9527db3e..2f7140c2 100644 --- a/doc/examples-simple-circuits-algorithms.rst +++ b/doc/examples-simple-circuits-algorithms.rst @@ -8,13 +8,13 @@ Learn more about working with advanced circuits and algoritms. :maxdepth: 2 *************** -`Getting started `_ +`Getting started `_ *************** A hello-world tutorial that shows you how to build a simple circuit and run it on a local simulator. *************** -`Running quantum circuits on simulators `_ +`Running quantum circuits on simulators `_ *************** This tutorial prepares a paradigmatic example for a multi-qubit entangled state, @@ -25,7 +25,7 @@ protocols it is used as a resource for quantum error correction, quantum communi and quantum metrology. *************** -`Running quantum circuits on QPU devices `_ +`Running quantum circuits on QPU devices `_ *************** This tutorial prepares a maximally-entangled Bell state between two qubits, @@ -35,7 +35,7 @@ we run the circuit on the superconducting machine from Rigetti, and on the ion-t machine provided by IonQ. *************** -`Deep Dive into the anatomy of quantum circuits `_ +`Deep Dive into the anatomy of quantum circuits `_ *************** This tutorial discusses in detail the anatomy of quantum circuits in the Amazon @@ -46,7 +46,7 @@ the circuit on a device of our choice (defining a quantum task) and how to track recover, or cancel a quantum task efficiently. *************** -`Superdense coding `_ +`Superdense coding `_ *************** This tutorial constructs an implementation of the superdense coding protocol using From 9d5e3fffb76a4b0e1091d55551a23dac2099d5c6 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 22 Mar 2021 18:13:52 +0000 Subject: [PATCH 0305/1165] prepare release v1.5.11 --- 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 1732ae63..0c0fdd0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.5.11 (2021-03-22) + +### Bug Fixes and Other Changes + + * Fix broken repository links + ## v1.5.10.post2 (2021-03-19) ### Testing and Release Infrastructure diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 9f3e83ec..e42ebcde 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.5.11.dev0" +__version__ = "1.5.11" From 06bcf5b432f5c3182d6b1c95de7aee9515725cb8 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 22 Mar 2021 18:13:52 +0000 Subject: [PATCH 0306/1165] update development version to v1.5.12.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 e42ebcde..44d6e2ad 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.5.11" +__version__ = "1.5.12.dev0" From f52d9d6a4da49fed862cd82f7c589d153fc7e9ba Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Thu, 25 Mar 2021 09:56:48 -0700 Subject: [PATCH 0307/1165] change: Update user_agent for AwsSession (#227) * change: Update user_agent for AwsSession * Remove # pragma: no cover * Address comments Co-authored-by: Christian Bruun Madsen --- .coveragerc | 1 + src/braket/aws/aws_session.py | 28 ++++++++++++++++ .../unit_tests/braket/aws/test_aws_session.py | 33 ++++++++++++++++++- 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index a5882e46..ed0ee10f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -17,6 +17,7 @@ source = [report] show_missing = True +ignore_errors = True [html] directory = build/coverage diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 549782c0..2969bd9b 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -11,12 +11,16 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import os.path from typing import Any, Dict, List, NamedTuple, Optional import backoff import boto3 from botocore.exceptions import ClientError +import braket._schemas as braket_schemas +import braket._sdk as braket_sdk + class AwsSession(object): """Manage interactions with AWS services.""" @@ -38,6 +42,30 @@ def __init__(self, boto_session=None, braket_client=None, config=None): self.braket_client = braket_client else: self.braket_client = self.boto_session.client("braket", config=self._config) + self._update_user_agent() + + def _update_user_agent(self): + """ + 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"). See: + https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html#botocore.config.Config + """ + + def _notebook_instance_version(): + # TODO: Replace with lifecycle configuration version once we have a way to access those + nbi_metadata_path = "/opt/ml/metadata/resource-metadata.json" + return "0" if os.path.exists(nbi_metadata_path) else "None" + + additional_user_agent_fields = ( + f"BraketSdk/{braket_sdk.__version__} " + f"BraketSchemas/{braket_schemas.__version__} " + f"NotebookInstance/{_notebook_instance_version()}" + ) + + self.braket_client._client_config.user_agent = ( + f"{self.braket_client._client_config.user_agent} {additional_user_agent_fields}" + ) # # Quantum Tasks diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index acd8552e..e3247f7c 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -12,11 +12,13 @@ # language governing permissions and limitations under the License. import json -from unittest.mock import MagicMock, Mock +from unittest.mock import MagicMock, Mock, patch import pytest from botocore.exceptions import ClientError +import braket._schemas as braket_schemas +import braket._sdk as braket_sdk from braket.aws import AwsSession TEST_S3_OBJ_CONTENTS = { @@ -57,6 +59,35 @@ def test_config(boto_session): boto_session.client.assert_called_with("braket", config=config) +@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): + boto_session = Mock() + boto_session.region_name = "foobar" + braket_client = Mock() + braket_client._client_config.user_agent = initial_user_agent + 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) + expected_user_agent = ( + f"{initial_user_agent} 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) + assert aws_session.braket_client._client_config.user_agent == expected_user_agent + + def test_retrieve_s3_object_body_success(boto_session): bucket_name = "braket-integ-test" filename = "tasks/test_task_1.json" From 5f1078869794f934c7bb7ae0fc0fab2d266c6c27 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 25 Mar 2021 18:15:22 +0000 Subject: [PATCH 0308/1165] prepare release v1.5.12 --- 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 0c0fdd0e..dd1004cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.5.12 (2021-03-25) + +### Bug Fixes and Other Changes + + * Update user_agent for AwsSession + ## v1.5.11 (2021-03-22) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 44d6e2ad..267e1484 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.5.12.dev0" +__version__ = "1.5.12" From 8d15914bd33dae7adc5b7860f550bf279011354c Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 25 Mar 2021 18:15:22 +0000 Subject: [PATCH 0309/1165] update development version to v1.5.13.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 267e1484..3e6859d0 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.5.12" +__version__ = "1.5.13.dev0" From 7e73af431fa8f2fcdd3dbb6fd56da754eb645570 Mon Sep 17 00:00:00 2001 From: Cedric Lin <67704428+licedric@users.noreply.github.com> Date: Thu, 25 Mar 2021 13:33:20 -0700 Subject: [PATCH 0310/1165] fix: remove unneeded get_quantum_task calls (#234) --- src/braket/aws/aws_quantum_task.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 8926291e..9471ba7e 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -251,7 +251,7 @@ def _status(self, use_cached_value=False): def _update_status_if_nonterminal(self): # If metadata has not been populated, the first call to _status will fetch it, # so the second _status call will no longer need to - metadata_absent = self._metadata is None + metadata_absent = not self._metadata cached = self._status(True) return cached if cached in self.TERMINAL_STATES else self._status(metadata_absent) @@ -270,7 +270,9 @@ def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult if the task completed successfully; returns `None` if the task did not complete successfully or the future timed out. """ - if self._result or self._status(True) in self.NO_RESULT_TERMINAL_STATES: + if self._result or ( + self._metadata and self._status(True) in self.NO_RESULT_TERMINAL_STATES + ): return self._result try: async_result = self.async_result() From 3ecb389be91016149677a36fc2eaaf7dc63cf193 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Thu, 25 Mar 2021 15:00:48 -0700 Subject: [PATCH 0311/1165] fix: check for task completion before entering async event loop (#226) * fix: check for task completion before entering async event loop --- src/braket/aws/aws_quantum_task.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 9471ba7e..96e2178b 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -274,6 +274,8 @@ def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult self._metadata and self._status(True) in self.NO_RESULT_TERMINAL_STATES ): return self._result + if self._metadata and self._status(True) in self.RESULTS_READY_STATES: + return self._download_result() try: async_result = self.async_result() return async_result.get_loop().run_until_complete(async_result) @@ -289,9 +291,7 @@ def _get_future(self): self._logger.debug(e) self._logger.info("No event loop found; creating new event loop") asyncio.set_event_loop(asyncio.new_event_loop()) - if not hasattr(self, "_future"): - self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) - elif ( + if not hasattr(self, "_future") or ( self._future.done() and not self._future.cancelled() and self._result is None @@ -341,15 +341,9 @@ 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() - current_metadata = self.metadata(True) self._logger.debug(f"Task {self._arn}: task status {task_status}") if task_status in AwsQuantumTask.RESULTS_READY_STATES: - result_string = self._aws_session.retrieve_s3_object_body( - current_metadata["outputS3Bucket"], - current_metadata["outputS3Directory"] + f"/{AwsQuantumTask.RESULTS_FILENAME}", - ) - self._result = _format_result(BraketSchemaBase.parse_raw_schema(result_string)) - return self._result + return self._download_result() elif task_status in AwsQuantumTask.NO_RESULT_TERMINAL_STATES: self._result = None return None @@ -366,6 +360,15 @@ async def _wait_for_completion( self._result = None return None + def _download_result(self): + current_metadata = self.metadata(True) + result_string = self._aws_session.retrieve_s3_object_body( + current_metadata["outputS3Bucket"], + current_metadata["outputS3Directory"] + f"/{AwsQuantumTask.RESULTS_FILENAME}", + ) + self._result = _format_result(BraketSchemaBase.parse_raw_schema(result_string)) + return self._result + def __repr__(self) -> str: return f"AwsQuantumTask('id':{self.id})" From 46eba4fd0c1bcd2f4ff3cf92340103e5fac488f2 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 26 Mar 2021 18:13:48 +0000 Subject: [PATCH 0312/1165] prepare release v1.5.13 --- 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 dd1004cc..45d41738 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.5.13 (2021-03-26) + +### Bug Fixes and Other Changes + + * check for task completion before entering async event loop + * remove unneeded get_quantum_task calls + ## v1.5.12 (2021-03-25) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 3e6859d0..afd4a1b0 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.5.13.dev0" +__version__ = "1.5.13" From ad4d737f0982a1ab8c021c98fe66310a09e79d43 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 26 Mar 2021 18:13:48 +0000 Subject: [PATCH 0313/1165] update development version to v1.5.14.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 afd4a1b0..080a2d29 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.5.13" +__version__ = "1.5.14.dev0" From 85d98a8e4aea46eb0c37cb07497c7a9364a281cc Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Mon, 29 Mar 2021 09:09:43 -0700 Subject: [PATCH 0314/1165] use device data to create device level parameter data when creating a quantum annealing task (#235) --- src/braket/aws/aws_quantum_task.py | 48 +++++++++++++++++-- .../braket/aws/test_aws_quantum_task.py | 42 ++++++++++++++-- 2 files changed, 82 insertions(+), 8 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 96e2178b..261b235d 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -26,7 +26,15 @@ from braket.circuits.circuit import Circuit from braket.circuits.circuit_helpers import validate_circuit_and_shots from braket.device_schema import GateModelParameters -from braket.device_schema.dwave import DwaveDeviceParameters +from braket.device_schema.dwave import ( + Dwave2000QDeviceParameters, + DwaveAdvantageDeviceParameters, + DwaveDeviceParameters, +) +from braket.device_schema.dwave.dwave_device_level_parameters_v1 import ( + Dwave2000QDeviceLevelParameters, + DwaveAdvantageDeviceLevelParameters, +) from braket.device_schema.ionq import IonqDeviceParameters from braket.device_schema.rigetti import RigettiDeviceParameters from braket.device_schema.simulators import GateModelSimulatorDeviceParameters @@ -435,15 +443,22 @@ def _( aws_session: AwsSession, create_task_kwargs: Dict[str, Any], device_arn: str, - device_parameters: Union[dict, DwaveDeviceParameters], + device_parameters: Union[ + # TODO: add tests for initializing w device-specific parameters + dict, + DwaveDeviceParameters, + DwaveAdvantageDeviceParameters, + Dwave2000QDeviceParameters, + ], disable_qubit_rewiring, *args, **kwargs, ) -> AwsQuantumTask: + device_params = _create_device_params(device_parameters, device_arn) create_task_kwargs.update( { "action": problem.to_ir().json(), - "deviceParameters": DwaveDeviceParameters.parse_obj(device_parameters).json(), + "deviceParameters": device_params.json(), } ) @@ -451,6 +466,33 @@ def _( return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) +def _create_device_params(device_params, device_arn): + if type(device_params) is not dict: + device_params = device_params.dict() + + device_level_parameters = device_params.get("deviceLevelParameters", None) or device_params.get( + "providerLevelParameters", None + ) + + if "braketSchemaHeader" in device_level_parameters: + del device_level_parameters["braketSchemaHeader"] + + if device_arn == "arn:aws:braket:::device/qpu/d-wave/Advantage_system1": + device_level_parameters = DwaveAdvantageDeviceLevelParameters.parse_obj( + device_level_parameters + ) + return DwaveAdvantageDeviceParameters(deviceLevelParameters=device_level_parameters) + elif device_arn == "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6": + 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." + ) + + def _create_common_params( device_arn: str, s3_destination_folder: AwsSession.S3DestinationFolder, shots: int ) -> Dict[str, Any]: 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 33bbcbb0..9abab300 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -22,6 +22,7 @@ from braket.annealing.problem import Problem, ProblemType from braket.aws import AwsQuantumTask +from braket.aws.aws_quantum_task import _create_device_params from braket.aws.aws_session import AwsSession from braket.circuits import Circuit from braket.device_schema import GateModelParameters @@ -406,11 +407,42 @@ def test_from_circuit_with_shots_value_error(aws_session, arn, circuit): @pytest.mark.parametrize( - "device_parameters", + "device_parameters,arn", [ - {"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}}, - DwaveDeviceParameters.parse_obj( - {"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}} + ( + {"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}}, + "arn:aws:braket:::device/qpu/d-wave/Advantage_system1", + ), + ( + {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION", "beta": 0.2}}, + "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6", + ), + pytest.param( + {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION", "beta": 0.2}}, + "arn:aws:braket:::device/qpu/d-wave/Advantage_system1", + # this doesn't fail... yet + # marks=pytest.mark.xfail(reason='beta not a valid parameter for Advantage device'), + ), + pytest.param( + {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION", "beta": 0.2}}, + "arn:aws:braket:::device/qpu/d-wave/fake_arn", + marks=pytest.mark.xfail(reason="Bad ARN"), + ), + ( + {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION"}}, + "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6", + ), + ( + DwaveDeviceParameters.parse_obj( + {"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}} + ), + "arn:aws:braket:::device/qpu/d-wave/Advantage_system1", + ), + ( + DwaveDeviceParameters.parse_obj( + {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION"}} + ), + "arn:aws:braket:::device/qpu/d-wave/Advantage_system1", ), ], ) @@ -435,7 +467,7 @@ def test_from_annealing(device_parameters, aws_session, arn, problem): problem, S3_TARGET, 1000, - DwaveDeviceParameters.parse_obj(device_parameters), + _create_device_params(device_parameters, device_arn=arn), ) From 29170c609f90646f8a174f35e5041478fb812225 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Mon, 5 Apr 2021 14:50:52 -0700 Subject: [PATCH 0315/1165] Dwave roll back (#236) * Revert "use device data to create device level parameter data when creating a quantum annealing task" This reverts commit b89757175028627603aed9800bfe6b79f05d9310. From 4deffc6db3ade9c682ec891902ccee66be9f76f8 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Mon, 5 Apr 2021 15:12:21 -0700 Subject: [PATCH 0316/1165] roll back dwave change (#237) --- src/braket/aws/aws_quantum_task.py | 48 ++----------------- .../braket/aws/test_aws_quantum_task.py | 42 ++-------------- 2 files changed, 8 insertions(+), 82 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 261b235d..96e2178b 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -26,15 +26,7 @@ from braket.circuits.circuit import Circuit from braket.circuits.circuit_helpers import validate_circuit_and_shots from braket.device_schema import GateModelParameters -from braket.device_schema.dwave import ( - Dwave2000QDeviceParameters, - DwaveAdvantageDeviceParameters, - DwaveDeviceParameters, -) -from braket.device_schema.dwave.dwave_device_level_parameters_v1 import ( - Dwave2000QDeviceLevelParameters, - DwaveAdvantageDeviceLevelParameters, -) +from braket.device_schema.dwave import DwaveDeviceParameters from braket.device_schema.ionq import IonqDeviceParameters from braket.device_schema.rigetti import RigettiDeviceParameters from braket.device_schema.simulators import GateModelSimulatorDeviceParameters @@ -443,22 +435,15 @@ def _( aws_session: AwsSession, create_task_kwargs: Dict[str, Any], device_arn: str, - device_parameters: Union[ - # TODO: add tests for initializing w device-specific parameters - dict, - DwaveDeviceParameters, - DwaveAdvantageDeviceParameters, - Dwave2000QDeviceParameters, - ], + device_parameters: Union[dict, DwaveDeviceParameters], disable_qubit_rewiring, *args, **kwargs, ) -> AwsQuantumTask: - device_params = _create_device_params(device_parameters, device_arn) create_task_kwargs.update( { "action": problem.to_ir().json(), - "deviceParameters": device_params.json(), + "deviceParameters": DwaveDeviceParameters.parse_obj(device_parameters).json(), } ) @@ -466,33 +451,6 @@ def _( return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) -def _create_device_params(device_params, device_arn): - if type(device_params) is not dict: - device_params = device_params.dict() - - device_level_parameters = device_params.get("deviceLevelParameters", None) or device_params.get( - "providerLevelParameters", None - ) - - if "braketSchemaHeader" in device_level_parameters: - del device_level_parameters["braketSchemaHeader"] - - if device_arn == "arn:aws:braket:::device/qpu/d-wave/Advantage_system1": - device_level_parameters = DwaveAdvantageDeviceLevelParameters.parse_obj( - device_level_parameters - ) - return DwaveAdvantageDeviceParameters(deviceLevelParameters=device_level_parameters) - elif device_arn == "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6": - 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." - ) - - def _create_common_params( device_arn: str, s3_destination_folder: AwsSession.S3DestinationFolder, shots: int ) -> Dict[str, Any]: 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 9abab300..33bbcbb0 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -22,7 +22,6 @@ from braket.annealing.problem import Problem, ProblemType from braket.aws import AwsQuantumTask -from braket.aws.aws_quantum_task import _create_device_params from braket.aws.aws_session import AwsSession from braket.circuits import Circuit from braket.device_schema import GateModelParameters @@ -407,42 +406,11 @@ def test_from_circuit_with_shots_value_error(aws_session, arn, circuit): @pytest.mark.parametrize( - "device_parameters,arn", + "device_parameters", [ - ( - {"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}}, - "arn:aws:braket:::device/qpu/d-wave/Advantage_system1", - ), - ( - {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION", "beta": 0.2}}, - "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6", - ), - pytest.param( - {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION", "beta": 0.2}}, - "arn:aws:braket:::device/qpu/d-wave/Advantage_system1", - # this doesn't fail... yet - # marks=pytest.mark.xfail(reason='beta not a valid parameter for Advantage device'), - ), - pytest.param( - {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION", "beta": 0.2}}, - "arn:aws:braket:::device/qpu/d-wave/fake_arn", - marks=pytest.mark.xfail(reason="Bad ARN"), - ), - ( - {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION"}}, - "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6", - ), - ( - DwaveDeviceParameters.parse_obj( - {"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}} - ), - "arn:aws:braket:::device/qpu/d-wave/Advantage_system1", - ), - ( - DwaveDeviceParameters.parse_obj( - {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION"}} - ), - "arn:aws:braket:::device/qpu/d-wave/Advantage_system1", + {"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}}, + DwaveDeviceParameters.parse_obj( + {"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}} ), ], ) @@ -467,7 +435,7 @@ def test_from_annealing(device_parameters, aws_session, arn, problem): problem, S3_TARGET, 1000, - _create_device_params(device_parameters, device_arn=arn), + DwaveDeviceParameters.parse_obj(device_parameters), ) From da97e73872ed4e9da9e7f3e11c0a3dcd894d07e3 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 7 Apr 2021 18:13:48 +0000 Subject: [PATCH 0317/1165] prepare release v1.5.14 --- 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 45d41738..ef3cd992 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## v1.5.14 (2021-04-07) + +### Bug Fixes and Other Changes + + * roll back dwave change + * Dwave roll back + * use device data to create device level parameter data when creating a quantum annealing task + ## v1.5.13 (2021-03-26) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 080a2d29..2d66a7ff 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.5.14.dev0" +__version__ = "1.5.14" From 31c5a8f4f1e7b9e7752d6376082c540d14c4581e Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 7 Apr 2021 18:13:48 +0000 Subject: [PATCH 0318/1165] update development version to v1.5.15.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 2d66a7ff..05b8e702 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.5.14" +__version__ = "1.5.15.dev0" From 263dd95ac06aa13b61a628c81bdb3bbf5cab0497 Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Thu, 8 Apr 2021 11:57:20 -0700 Subject: [PATCH 0319/1165] fix: stop manually managing waiting treads in quantum task batch requests (#238) --- src/braket/aws/aws_quantum_task_batch.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 76cb3cee..208cadee 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -15,7 +15,6 @@ import time from concurrent.futures.thread import ThreadPoolExecutor -from queue import Queue from typing import List, Set, Union from braket.annealing import Problem @@ -122,16 +121,13 @@ def _execute( *args, **kwargs, ): - # This list tracks the number of remaining and currently executing tasks - # It also serves as a lock, because appending and popping are atomic + max_threads = min(max_parallel, max_workers) remaining = [0 for _ in task_specifications] - executing = Queue(maxsize=max_parallel) - with ThreadPoolExecutor(max_workers=max_workers) as executor: + with ThreadPoolExecutor(max_workers=max_threads) as executor: task_futures = [ executor.submit( AwsQuantumTaskBatch._create_task, remaining, - executing, aws_session, device_arn, task, @@ -150,7 +146,6 @@ def _execute( @staticmethod def _create_task( remaining, - executing, aws_session, device_arn, task_specification, @@ -160,9 +155,6 @@ def _create_task( *args, **kwargs, ): - executing.put(0) - remaining.pop() - task = AwsQuantumTask.create( aws_session, device_arn, @@ -174,11 +166,12 @@ def _create_task( **kwargs, ) + remaining.pop() + # If the task hits a terminal state before all tasks have been created, # it can be returned immediately while remaining: if task.state() in AwsQuantumTask.TERMINAL_STATES: - executing.get() break time.sleep(poll_interval_seconds) return task From efa6a079b41c255b0b766419f5c10e253ce42e0a Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 8 Apr 2021 19:11:26 +0000 Subject: [PATCH 0320/1165] prepare release v1.5.15 --- 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 ef3cd992..27596395 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.5.15 (2021-04-08) + +### Bug Fixes and Other Changes + + * stop manually managing waiting treads in quantum task batch requests + ## v1.5.14 (2021-04-07) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 05b8e702..39294c81 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.5.15.dev0" +__version__ = "1.5.15" From 083edb2a8b5ec7f2b9b3523f4ff084d806bb5af0 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 8 Apr 2021 19:11:26 +0000 Subject: [PATCH 0321/1165] update development version to v1.5.16.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 39294c81..481c4913 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.5.15" +__version__ = "1.5.16.dev0" From 26eb82cdb9751153fb2386b0892b6620e57daa49 Mon Sep 17 00:00:00 2001 From: wrasmuss <6947112+wrasmuss@users.noreply.github.com> Date: Tue, 4 May 2021 17:56:53 -0700 Subject: [PATCH 0322/1165] fix: Added /taskArn to id field in AwsQuantumTask __repr__ (#241) --- src/braket/annealing/problem.py | 2 +- src/braket/aws/aws_quantum_task.py | 2 +- src/braket/circuits/observables.py | 2 +- test/unit_tests/braket/aws/test_aws_quantum_task.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/braket/annealing/problem.py b/src/braket/annealing/problem.py index 1705e9c9..c240fdbc 100644 --- a/src/braket/annealing/problem.py +++ b/src/braket/annealing/problem.py @@ -32,7 +32,7 @@ class ProblemType(str, Enum): class Problem: - """ Represents an annealing problem.""" + """Represents an annealing problem.""" def __init__( self, diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 96e2178b..fd0f148c 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -370,7 +370,7 @@ def _download_result(self): return self._result def __repr__(self) -> str: - return f"AwsQuantumTask('id':{self.id})" + return f"AwsQuantumTask('id/taskArn':'{self.id}')" def __eq__(self, other) -> bool: if isinstance(other, AwsQuantumTask): diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 3e00674b..06a61190 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -199,7 +199,7 @@ def to_ir(self) -> List[str]: @property def factors(self) -> Tuple[Observable, ...]: - """ Tuple[Observable]: The observables that comprise this tensor product.""" + """Tuple[Observable]: The observables that comprise this tensor product.""" return self._factors def to_matrix(self) -> np.ndarray: 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 33bbcbb0..75f1efb7 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -84,7 +84,7 @@ def test_equality(arn, aws_session): def test_str(quantum_task): - expected = "AwsQuantumTask('id':{})".format(quantum_task.id) + expected = "AwsQuantumTask('id/taskArn':'{}')".format(quantum_task.id) assert str(quantum_task) == expected From 83faf7011846f72b8cda9ed538bbce599bd48d38 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 4 May 2021 18:55:37 -0700 Subject: [PATCH 0323/1165] documentation: Fix link, typos, order (#242) --- doc/examples-adv-circuits-algorithms.rst | 2 +- ...e-circuits-algorithms.rst => examples-getting-started.rst} | 4 ++-- doc/examples-hybrid-quantum.rst | 2 +- doc/examples-quantum-annealing-dwave.rst | 2 +- doc/examples.rst | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename doc/{examples-simple-circuits-algorithms.rst => examples-getting-started.rst} (96%) diff --git a/doc/examples-adv-circuits-algorithms.rst b/doc/examples-adv-circuits-algorithms.rst index 5cbd924d..6f9f7699 100644 --- a/doc/examples-adv-circuits-algorithms.rst +++ b/doc/examples-adv-circuits-algorithms.rst @@ -30,7 +30,7 @@ quadratic speedup over several classical algorithms. ************************* -`Quantum fourier transform `_ +`Quantum Fourier transform `_ ************************* This tutorial provides a detailed implementation of the Quantum Fourier Transform (QFT) and diff --git a/doc/examples-simple-circuits-algorithms.rst b/doc/examples-getting-started.rst similarity index 96% rename from doc/examples-simple-circuits-algorithms.rst rename to doc/examples-getting-started.rst index 2f7140c2..e95765f2 100644 --- a/doc/examples-simple-circuits-algorithms.rst +++ b/doc/examples-getting-started.rst @@ -1,8 +1,8 @@ ############################## -Simple circuits and algorithms +Getting started ############################## -Learn more about working with advanced circuits and algoritms. +Get started on Amazon Braket with some introductory examples. .. toctree:: :maxdepth: 2 diff --git a/doc/examples-hybrid-quantum.rst b/doc/examples-hybrid-quantum.rst index 135f1487..34ef0237 100644 --- a/doc/examples-hybrid-quantum.rst +++ b/doc/examples-hybrid-quantum.rst @@ -22,7 +22,7 @@ This tutorial shows how to solve for the ground state of the Transverse Ising Mo using the variational quantum eigenvalue solver (VQE). ************************** -`VQE Chemestry `_ +`VQE Chemistry `_ ************************** This tutorial shows how to implement the Variational Quantum Eigensolver (VQE) algorithm in diff --git a/doc/examples-quantum-annealing-dwave.rst b/doc/examples-quantum-annealing-dwave.rst index 3b0faaf2..7ed2725e 100644 --- a/doc/examples-quantum-annealing-dwave.rst +++ b/doc/examples-quantum-annealing-dwave.rst @@ -14,7 +14,7 @@ Learn more about quantum annealing with D-Wave. Learn more about the anatomy of quantum annealing with D-Wave on Amazon Braket. ************************** -` Running large problems with QBSolv `_ +`Running large problems with QBSolv `_ ************************** This tutorial demonstrates how to solve problems with sizes larger than a diff --git a/doc/examples.rst b/doc/examples.rst index 1b9987ac..e0126c2f 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -8,8 +8,8 @@ https://github.com/aws/amazon-braket-examples. .. toctree:: :maxdepth: 2 + examples-getting-started.rst examples-braket-features.rst - examples-simple-circuits-algorithms.rst examples-adv-circuits-algorithms.rst examples-hybrid-quantum.rst examples-ml-pennylane.rst From afe11813fbf8f44f8f4659b5ff2dd73fd0471064 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 5 May 2021 18:14:19 +0000 Subject: [PATCH 0324/1165] prepare release v1.5.16 --- 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 27596395..48d81546 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.5.16 (2021-05-05) + +### Bug Fixes and Other Changes + + * Added /taskArn to id field in AwsQuantumTask __repr__ + +### Documentation Changes + + * Fix link, typos, order + ## v1.5.15 (2021-04-08) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 481c4913..830befae 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.5.16.dev0" +__version__ = "1.5.16" From dde5df0a7baa0f5df118cd865ab622fcca1da1cb Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 5 May 2021 18:14:19 +0000 Subject: [PATCH 0325/1165] update development version to v1.5.17.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 830befae..3516cd68 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.5.16" +__version__ = "1.5.17.dev0" From f1e2e0a7259ac369ef904a51aa637370f2b76175 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 19 May 2021 18:42:19 -0700 Subject: [PATCH 0326/1165] infra: Use GitHub source for tox tests (#244) --- .github/workflows/python-package.yml | 5 +---- tox.ini | 11 +++++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 6587e115..f9959617 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -37,9 +37,6 @@ jobs: flake8 - name: Run unit tests run: | - coverage run -m pytest - coverage combine - coverage report - coverage xml + tox -e unit-tests - name: Upload coverage report to Codecov uses: codecov/codecov-action@v1 diff --git a/tox.ini b/tox.ini index 45c68c57..03065663 100644 --- a/tox.ini +++ b/tox.ini @@ -4,16 +4,21 @@ envlist = linters,docs,unit-tests [testenv:unit-tests] basepython = python3 # {posargs} contains additional arguments specified when invoking tox. e.g. tox -- -s -k test_foo.py +deps = + {[test-deps]deps} commands = coverage run -m pytest {posargs} coverage combine coverage report coverage html + coverage xml extras = test [testenv:integ-tests] basepython = python3 # {posargs} contains additional arguments specified when invoking tox. e.g. tox -- -s -k test_foo.py +deps = + {[test-deps]deps} passenv = AWS_PROFILE commands = @@ -78,3 +83,9 @@ basepython = python3 skip_install = true commands = /bin/sh -c 'tar -czvf build_files.tar.gz build/' + +[test-deps] +deps = +# If you need to test on a certain branch, add @ after .git + git+https://github.com/aws/amazon-braket-schemas-python.git + git+https://github.com/aws/amazon-braket-default-simulator-python.git From fbd7ed9d739009a757564a1a086afe977eef454b Mon Sep 17 00:00:00 2001 From: Xiaosi Xu <66740305+xiaosi-xu@users.noreply.github.com> Date: Mon, 24 May 2021 20:09:14 +0100 Subject: [PATCH 0327/1165] feature: Noise operators (#212) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Original files for noise simulation * Update circuit.py * Update result_types.py * Update gate_model_device_testing_utils.py * Update result_types.py * Update circuit.py * Update result_types.py * Update gate_model_device_testing_utils.py * fix format * Update setup.py point to the noise simulation branch * Added new noise channels * Added/edited functions to apply noise to gates * fixed bugs and reduced the cyclomatic complexity * Increased code coverage * changed the order of noise channels when using the apply_x_noise methods * added unit test for full coverage * fixed the problem that a single Gate type is not accepted as a target gate * added the input paramter of `target_unitary` to `apply_gate_noise` * several changes(in description) Added the unit test for `target_unitary`; Enabled non-CPTP map (a warning is given); Make sure the sum of probabilities in General Pauli channel is no larger than 1. * several changes (source code) Make sure the time_slice for an empty circuit with noise is no smaller than zero; Gives error if the sum of probabilities in General Pauli channel is larger than 1; Enabled non-CPTP map but a warning is given. * Reverted the change for CPTP map check * Update local_noise_simulation.py * Update setup.py * Update setup.py * fix typo * Update noise_helpers.py * enable reduce density matrix * Kraus operator limit * Update result_types.py * renamed 'GeneralPauli' into 'PauliChannel' * changed the parameter order in generalized_amplitude_damping channel * fix typo in comment * apply the probability limit * Fix simulator name in example (#243) At least on the latest version `Braket-DM` errors. * Update setup.py * documentation: Update to language on simulator for the README.md * To address comments * add class MomentType * corrected indent * Update test_local_noise_simulator.py * Allow Unitary to act on > 2 target qubits * Add specific check for Kraus matrices to have dimensions matching target qubits * Add test for large unitary * formatting * swtich branch selection from setup.py to tox.ini * remove deps from GitHub workflow * Remove references to the noise simulation branch Co-authored-by: Cody Wang Co-authored-by: Katharine Hyatt <67932820+kshyatt-aws@users.noreply.github.com> Co-authored-by: Christian Madsen Co-authored-by: Lin Co-authored-by: ℂ𝓞𝔇𝚈 Co-authored-by: Kshitij Chhabra Co-authored-by: Tim Chen <67017645+yitinche@users.noreply.github.com> --- .github/workflows/python-package.yml | 2 - README.md | 7 +- examples/local_noise_simulation.py | 30 + setup.py | 2 +- src/braket/circuits/__init__.py | 2 + src/braket/circuits/ascii_circuit_diagram.py | 5 +- src/braket/circuits/circuit.py | 308 ++++++- src/braket/circuits/moments.py | 129 ++- src/braket/circuits/noise.py | 384 ++++++++ src/braket/circuits/noise_helpers.py | 304 +++++++ src/braket/circuits/noises.py | 835 ++++++++++++++++++ .../circuits/quantum_operator_helpers.py | 25 +- src/braket/circuits/result_types.py | 72 ++ .../gate_model_device_testing_utils.py | 30 + .../integ_tests/test_local_noise_simulator.py | 108 +++ .../circuits/test_ascii_circuit_diagram.py | 32 + test/unit_tests/braket/circuits/test_gates.py | 8 + .../braket/circuits/test_moments.py | 26 +- test/unit_tests/braket/circuits/test_noise.py | 350 ++++++++ .../braket/circuits/test_noise_helpers.py | 697 +++++++++++++++ .../unit_tests/braket/circuits/test_noises.py | 388 ++++++++ .../circuits/test_quantum_operator_helpers.py | 34 + .../braket/circuits/test_result_type.py | 12 +- .../braket/circuits/test_result_types.py | 8 + tox.ini | 3 +- 25 files changed, 3756 insertions(+), 45 deletions(-) create mode 100644 examples/local_noise_simulation.py create mode 100644 src/braket/circuits/noise.py create mode 100644 src/braket/circuits/noise_helpers.py create mode 100644 src/braket/circuits/noises.py create mode 100644 test/integ_tests/test_local_noise_simulator.py create mode 100644 test/unit_tests/braket/circuits/test_noise.py create mode 100644 test/unit_tests/braket/circuits/test_noise_helpers.py create mode 100644 test/unit_tests/braket/circuits/test_noises.py diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index f9959617..ed3ebdcb 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -27,8 +27,6 @@ jobs: - name: Install dependencies run: | pip install --upgrade pip - pip install --upgrade git+https://github.com/aws/amazon-braket-schemas-python@main - pip install --upgrade git+https://github.com/aws/amazon-braket-default-simulator-python@main pip install -e .[test] - name: Check code format run: | diff --git a/README.md b/README.md index 2d336411..5b1438e2 100644 --- a/README.md +++ b/README.md @@ -93,11 +93,12 @@ print(batch.results()[0].measurement_counts) # The result of the first task in ``` ### Available Simulators -Amazon Braket provides access to two types of simulators: fully managed simulators, available through the Amazon Braket service, and the local simulator that is part of the Amazon Braket SDK. +Amazon Braket provides access to two types of simulators: fully managed simulators, available through the Amazon Braket service, and the local simulators that are part of the Amazon Braket SDK. -- Fully managed simulators offer high-performance circuit simulations. These simulators can handle circuits larger than circuits that run on quantum hardware. For example, the SV1 state vector simulator shown in the previous examples requires approximately 1 or 2 hours to complete a 34-qubit, dense, and square circuit (circuit depth = 34), depending on the type of gates used and other factors. For a list of available simulators and their features, consult the [Amazon Braket Developer Guide](https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html). +- Fully managed simulators offer high-performance circuit simulations. These simulators can handle circuits larger than circuits that run on quantum hardware. For example, the SV1 state vector simulator shown in the previous examples requires approximately 1 or 2 hours to complete a 34-qubit, dense, and square circuit (circuit depth = 34), depending on the type of gates used and other factors. +- The Amazon Braket Python SDK includes an implementation of quantum simulators that can run circuits on your local, classic hardware. For example the braket_sv local simulator is well suited for rapid prototyping on small circuits up to 25 qubits, depending on the hardware specifications of your Braket notebook instance or your local environment. An example of how to execute the task locally is included in the repository `../examples/local_bell.py`. -- The Amazon Braket Python SDK includes an implementation of a quantum simulator that can run circuits on your local, classic hardware. The local simulator is well suited for rapid prototyping on small circuits up to 25 qubits, depending on the hardware specifications of your Braket notebook instance or your local environment. An example of how to execute the task locally is included in the repository `../examples/local_bell.py`. +For a list of available simulators and their features, consult the [Amazon Braket Developer Guide](https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html). ### Debugging logs diff --git a/examples/local_noise_simulation.py b/examples/local_noise_simulation.py new file mode 100644 index 00000000..c6eff078 --- /dev/null +++ b/examples/local_noise_simulation.py @@ -0,0 +1,30 @@ +# Copyright 2019-2019 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, Noise +from braket.devices import LocalSimulator + +device = LocalSimulator("braket_dm") + +circuit = Circuit().x(0).x(1).bit_flip(0, probability=0.1) +print("First example: ") +print(circuit) +print(device.run(circuit, shots=1000).result().measurement_counts) + + +circuit = Circuit().x(0).x(1) +noise = Noise.BitFlip(probability=0.1) +circuit.apply_gate_noise(noise) +print("Second example: ") +print(circuit) +print(device.run(circuit, shots=1000).result().measurement_counts) diff --git a/setup.py b/setup.py index a07ef6f0..a8d1f2d7 100644 --- a/setup.py +++ b/setup.py @@ -32,8 +32,8 @@ "boltons", "boto3", "nest-asyncio", - "numpy", "networkx", + "numpy", ], extras_require={ "test": [ diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py index ed20ce8d..8faa713f 100644 --- a/src/braket/circuits/__init__.py +++ b/src/braket/circuits/__init__.py @@ -16,6 +16,7 @@ # Execute initialization code in gates module import braket.circuits.gates as gates # noqa: F401 +import braket.circuits.noises as noises # noqa: F401 import braket.circuits.observables as observables # noqa: F401 import braket.circuits.result_types as result_types # noqa: F401 from braket.circuits.angled_gate import AngledGate # noqa: F401 @@ -25,6 +26,7 @@ from braket.circuits.gate import Gate # noqa: F401 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.operator import Operator # noqa: F401 from braket.circuits.quantum_operator import QuantumOperator # noqa: F401 diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index 129969bd..978ceb48 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -16,6 +16,7 @@ from braket.circuits.circuit_diagram import CircuitDiagram from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction +from braket.circuits.noise import Noise from braket.circuits.qubit_set import QubitSet from braket.circuits.result_type import ResultType @@ -102,8 +103,8 @@ def _ascii_group_items( """ groupings = [] for item in items: - # Can only print Gate operators for instructions at the moment - if isinstance(item, Instruction) and not isinstance(item.operator, Gate): + # Can only print Gate and Noise operators for instructions at the moment + if isinstance(item, Instruction) and not isinstance(item.operator, (Gate, Noise)): continue if isinstance(item, ResultType) and not item.target: diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 8001d10e..04bec0f5 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -13,11 +13,23 @@ from __future__ import annotations -from typing import Callable, Dict, Iterable, List, Tuple, TypeVar, Union +from typing import Callable, Dict, Iterable, List, Optional, Tuple, Type, TypeVar, Union + +import numpy as np from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram +from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction from braket.circuits.moments import Moments +from braket.circuits.noise import Noise +from braket.circuits.noise_helpers import ( + apply_noise_to_gates, + apply_noise_to_moments, + check_noise_target_gates, + check_noise_target_qubits, + check_noise_target_unitary, + wrap_with_list, +) from braket.circuits.observable import Observable from braket.circuits.observables import TensorProduct from braket.circuits.qubit import QubitInput @@ -124,6 +136,7 @@ def depth(self) -> int: @property def instructions(self) -> Iterable[Instruction]: """Iterable[Instruction]: Get an `iterable` of instructions in the circuit.""" + return self._moments.values() @property @@ -485,13 +498,300 @@ def add_circuit( return self + def apply_gate_noise( + self, + noise: Union[Type[Noise], Iterable[Type[Noise]]], + target_gates: Optional[Union[Type[Gate], Iterable[Type[Gate]]]] = None, + target_unitary: np.ndarray = None, + target_qubits: Optional[QubitSetInput] = None, + ) -> Circuit: + """Apply `noise` to the circuit according to `target_gates`, `target_unitary` and + `target_qubits`. + + For any parameter that is None, that specification is ignored (e.g. if `target_gates` + is None then the noise is applied after every gate in `target_qubits`). + If `target_gates` and `target_qubits` are both None, then `noise` is + applied to every qubit after every gate. + + Noise is either applied to `target_gates` or `target_unitary`, so they cannot be + provided at the same time. + + When `noise.qubit_count` == 1, ie. `noise` is single-qubit, `noise` is added to all + qubits in `target_gates` or `target_unitary` (or to all qubits in `target_qubits` + if `target_gates` is None). + + When `noise.qubit_count` > 1 and `target_gates` is not None, the number of qubits of + any gate in `target_gates` must be the same as `noise.qubit_count`. + + When `noise.qubit_count` > 1, `target_gates` and `target_unitary` is None, noise is + only applied to gates with the same qubit_count in target_qubits. + + Args: + noise (Union[Type[Noise], Iterable[Type[Noise]]]): Noise channel(s) to be applied + to the circuit. + target_gates (Union[Type[Gate], Iterable[Type[Gate]], optional]): Gate class or + List of Gate classes which `noise` is applied to. Default=None. + target_unitary (np.ndarray): matrix of the target unitary gates. Default=None. + target_qubits (Union[QubitSetInput, optional]): Index or indices of qubit(s). + Default=None. + + Returns: + Circuit: self + + Raises: + TypeError: + If `noise` is not Noise type. + If `target_gates` is not a Gate type, Iterable[Gate]. + If `target_unitary` is not a np.ndarray type. + If `target_qubits` has non-integers or negative integers. + IndexError: + If applying noise to an empty circuit. + If `target_qubits` is out of range of circuit.qubits. + ValueError: + If both `target_gates` and `target_unitary` are provided. + 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: + 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. + If no `target_gates` or `target_unitary` exist in `target_qubits` or + in the whole circuit when they are not given. + + Examples: + >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1) + >>> print(circ) + T : |0|1|2| + + q0 : -X-Z-C- + | + q1 : -Y-X-X- + + T : |0|1|2| + + >>> noise = Noise.Depolarizing(probability=0.1) + >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1) + >>> print(circ.apply_gate_noise(noise, target_gates = Gate.X)) + T : | 0 | 1 |2| + + q0 : -X-DEPO(0.1)-Z-----------C- + | + q1 : -Y-----------X-DEPO(0.1)-X- + + T : | 0 | 1 |2| + + >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1) + >>> print(circ.apply_gate_noise(noise, target_qubits = 1)) + T : | 0 | 1 | 2 | + + q0 : -X-----------Z-----------C----------- + | + q1 : -Y-DEPO(0.1)-X-DEPO(0.1)-X-DEPO(0.1)- + + T : | 0 | 1 | 2 | + + >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1) + >>> print(circ.apply_gate_noise(noise, + ... target_gates = [Gate.X,Gate.Y], + ... target_qubits = [0,1]) + ... ) + T : | 0 | 1 |2| + + q0 : -X-DEPO(0.1)-Z-----------C- + | + q1 : -Y-DEPO(0.1)-X-DEPO(0.1)-X- + + T : | 0 | 1 |2| + + """ + # check whether gate noise is applied to an empty circuit + if not self.qubits: + raise IndexError("Gate noise cannot be applied to an empty circuit.") + + # check if target_gates and target_unitary are both given + if (target_unitary is not None) and (target_gates is not None): + raise ValueError("target_unitary and target_gates cannot be input at the same time.") + + # check target_qubits + target_qubits = check_noise_target_qubits(self, target_qubits) + 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.") + + # make noise a list + noise = wrap_with_list(noise) + + # make target_gates a list + if target_gates is not None: + target_gates = wrap_with_list(target_gates) + # remove duplicate items + target_gates = list(dict.fromkeys(target_gates)) + + for noise_channel in noise: + if not isinstance(noise_channel, Noise): + raise TypeError("Noise must be an instance of the Noise class") + # check whether target_gates is valid + if target_gates is not None: + check_noise_target_gates(noise_channel, target_gates) + if target_unitary is not None: + check_noise_target_unitary(noise_channel, target_unitary) + + 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) + + def apply_initialization_noise( + self, + noise: Union[Type[Noise], Iterable[Type[Noise]]], + target_qubits: Optional[QubitSetInput] = None, + ) -> Circuit: + """Apply `noise` at the beginning of the circuit for every qubit (default) or target_qubits`. + + Only when `target_qubits` is given can the noise be applied to an empty circuit. + + When `noise.qubit_count` > 1, the number of qubits in target_qubits must be equal + to `noise.qubit_count`. + + Args: + noise (Union[Type[Noise], Iterable[Type[Noise]]]): Noise channel(s) to be applied + to the circuit. + target_qubits (Union[QubitSetInput, optional]): Index or indices of qubit(s). + Default=None. + + Returns: + Circuit: self + + Raises: + TypeError: + If `noise` is not Noise type. + If `target_qubits` has non-integers or negative integers. + IndexError: + If applying noise to an empty circuit when `target_qubits` is not given. + ValueError: + If `noise.qubit_count` > 1 and the number of qubits in target_qubits is + not the same as `noise.qubit_count`. + + Examples: + >>> 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) + >>> 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() + >>> print(circ.apply_initialization_noise(noise, target_qubits = [0, 1])) + + """ + if (len(self.qubits) == 0) and (target_qubits is None): + raise IndexError( + "target_qubits must be provided in order to apply the initialization noise \ +to an empty circuit." + ) + + target_qubits = check_noise_target_qubits(self, target_qubits) + + # make noise a list + noise = wrap_with_list(noise) + for noise_channel in noise: + if not isinstance(noise_channel, Noise): + raise TypeError("Noise must be an instance of the Noise class") + if noise_channel.qubit_count > 1 and noise_channel.qubit_count != len(target_qubits): + raise ValueError( + "target_qubits needs to be provided for this multi-qubit noise channel, and \ +the number of qubits in target_qubits must be the same as defined by the multi-qubit noise channel." + ) + + return apply_noise_to_moments(self, noise, target_qubits, "initialization") + + def apply_readout_noise( + self, + noise: Union[Type[Noise], Iterable[Type[Noise]]], + target_qubits: Optional[QubitSetInput] = None, + ) -> Circuit: + """Apply `noise` right before measurement in every qubit (default) or target_qubits`. + + Only when `target_qubits` is given can the noise be applied to an empty circuit. + + When `noise.qubit_count` > 1, the number of qubits in target_qubits must be equal + to `noise.qubit_count`. + + Args: + noise (Union[Type[Noise], Iterable[Type[Noise]]]): Noise channel(s) to be applied + to the circuit. + target_qubits (Union[QubitSetInput, optional]): Index or indices of qubit(s). + Default=None. + + Returns: + Circuit: self + + Raises: + TypeError: + If `noise` is not Noise type. + If `target_qubits` has non-integers. + IndexError: + If applying noise to an empty circuit. + ValueError: + If `target_qubits` has negative integers. + If `noise.qubit_count` > 1 and the number of qubits in target_qubits is + not the same as `noise.qubit_count`. + + Examples: + >>> 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) + >>> 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() + >>> print(circ.apply_initialization_noise(noise, target_qubits = [0, 1])) + + """ + if (len(self.qubits) == 0) and (target_qubits is None): + raise IndexError( + "target_qubits must be provided in order to apply the readout noise \ +to an empty circuit." + ) + + if target_qubits is None: + target_qubits = self.qubits + else: + if not isinstance(target_qubits, list): + 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): + raise ValueError("target_qubits must contain only non-negative integers.") + target_qubits = QubitSet(target_qubits) + + # make noise a list + noise = wrap_with_list(noise) + for noise_channel in noise: + if not isinstance(noise_channel, Noise): + raise TypeError("Noise must be an instance of the Noise class") + if noise_channel.qubit_count > 1 and noise_channel.qubit_count != len(target_qubits): + raise ValueError( + "target_qubits needs to be provided for this multi-qubit noise channel, and \ +the number of qubits in target_qubits must be the same as defined by the multi-qubit noise channel." + ) + + 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 `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 input it - allows. + supports are supported by this method. If adding a + subroutine, check with that subroutines documentation to determine what + input it allows. Args: addable (AddableTypes): The item(s) to add to self. Default = `None`. diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index c4b154a4..c9479102 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -11,6 +11,9 @@ # 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 enum import Enum from typing import ( Dict, ItemsView, @@ -24,23 +27,52 @@ ) from braket.circuits.instruction import Instruction +from braket.circuits.noise import Noise from braket.circuits.qubit import Qubit from braket.circuits.qubit_set import QubitSet +class MomentType(str, Enum): + """ + The type of moments. + GATE: a gate + NOISE: a noise channel added directly to the circuit + GATE_NOISE: a gate-based noise channel + INITIALIZATION_NOISE: a initialization noise channel + READOUT_NOISE: a readout noise channel + """ + + GATE = "gate" + NOISE = "noise" + GATE_NOISE = "gate_noise" + INITIALIZATION_NOISE = "initialization_noise" + READOUT_NOISE = "readout_noise" + + class MomentsKey(NamedTuple): - """Key of the Moments mapping.""" + """Key of the Moments mapping. + Args: + time: moment + qubits: qubit set + moment_type: can be GATE, NOISE, or GATE_NOISE which is associated with gates; + and READOUT_NOISE or INITIALIZATION_NOISE. + noise_index: the number of noise channels at the same moment. For gates, this is the + number of gate_noise channels associated with that gate. For all other noise + types, noise_index starts from 0; but for gate noise, it starts from 1. + """ time: int qubits: QubitSet + moment_type: MomentType + noise_index: int class Moments(Mapping[MomentsKey, Instruction]): """ - An ordered mapping of `MomentsKey` 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. + 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. This data structure is useful to determine a dependency of instructions, such as printing or optimizing circuit structure, before sending it to a quantum @@ -119,6 +151,7 @@ def time_slices(self) -> Dict[int, List[Instruction]]: """ time_slices = {} + self.sort_moments() for key, instruction in self._moments.items(): instructions = time_slices.get(key.time, []) instructions.append(instruction) @@ -126,28 +159,89 @@ def time_slices(self) -> Dict[int, List[Instruction]]: return time_slices - def add(self, instructions: Iterable[Instruction]) -> None: + def add(self, instructions: Iterable[Instruction], noise_index: int = 0) -> None: """ Add instructions to self. Args: - instructions (Iterable[Instruction]): Instructions to add to self. The instruction - is added to the max time slice in which the instruction fits. + instructions (Iterable[Instruction]): Instructions to add to self. The instruction is + added to the max time slice in which the instruction fits. """ for instruction in instructions: - self._add(instruction) + self._add(instruction, noise_index) + + def _add(self, instruction: Instruction, noise_index: int = 0) -> None: + + if isinstance(instruction.operator, Noise): + self.add_noise(instruction) + + else: + qubit_range = instruction.target + time = max([self._max_time_for_qubit(qubit) for qubit in qubit_range]) + 1 + + # Mark all qubits in qubit_range with max_time + for qubit in qubit_range: + self._max_times[qubit] = max(time, self._max_time_for_qubit(qubit)) + + self._moments[ + MomentsKey(time, instruction.target, MomentType.GATE, noise_index) + ] = instruction + self._qubits.update(instruction.target) + self._depth = max(self._depth, time + 1) + + def add_noise( + self, instruction: Instruction, input_type: str = "noise", noise_index: int = 0 + ) -> None: - def _add(self, instruction: Instruction) -> None: qubit_range = instruction.target - time = max([self._max_time_for_qubit(qubit) for qubit in qubit_range]) + 1 + time = max(0, *[self._max_time_for_qubit(qubit) for qubit in qubit_range]) + if input_type == MomentType.INITIALIZATION_NOISE: + time = 0 + + while MomentsKey(time, qubit_range, input_type, noise_index) in self._moments: + noise_index = noise_index + 1 - # Mark all qubits in qubit_range with max_time - for qubit in qubit_range: - self._max_times[qubit] = max(time, self._max_time_for_qubit(qubit)) + self._moments[MomentsKey(time, qubit_range, input_type, noise_index)] = instruction + self._qubits.update(qubit_range) - self._moments[MomentsKey(time, instruction.target)] = instruction - self._qubits.update(instruction.target) - self._depth = max(self._depth, time + 1) + def sort_moments(self) -> None: + """ + Make the disordered moments in order. + + 1. Make the readout noise in the end + 2. Make the initialization noise at the beginning + """ + # key for NOISE, GATE and GATE_NOISE + key_noise = [] + # key for INITIALIZATION_NOISE + key_initialization_noise = [] + # key for READOUT_NOISE + key_readout_noise = [] + moment_copy = OrderedDict() + sorted_moment = OrderedDict() + + 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) + else: + key_noise.append(key) + + for key in key_initialization_noise: + sorted_moment[key] = moment_copy[key] + 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) + + for key in key_readout_noise: + sorted_moment[ + MomentsKey(max_time, key.qubits, MomentType.READOUT_NOISE, key.noise_index) + ] = moment_copy[key] + + self._moments = sorted_moment def _max_time_for_qubit(self, qubit: Qubit) -> int: return self._max_times.get(qubit, -1) @@ -166,6 +260,7 @@ def items(self) -> ItemsView[MomentsKey, Instruction]: def values(self) -> ValuesView[Instruction]: """Return a view of self's instructions.""" + self.sort_moments() return self._moments.values() def get(self, key: MomentsKey, default=None) -> Instruction: diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py new file mode 100644 index 00000000..dd5d50b3 --- /dev/null +++ b/src/braket/circuits/noise.py @@ -0,0 +1,384 @@ +# Copyright 2019-2019 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 typing import Any, Sequence + +from braket.circuits.quantum_operator import QuantumOperator +from braket.circuits.qubit_set import QubitSet + + +class Noise(QuantumOperator): + """ + 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 + the metadata that defines what the noise channel is and what it does. + """ + + def __init__( + self, + qubit_count: int, + ascii_symbols: Sequence[str], + ): + """ + Args: + qubit_count (int): Number of qubits this noise channel interacts with. + ascii_symbols (Sequence[str]): ASCII string symbols for this noise channel. These + are used when printing a diagram of circuits. Length must be the same as + `qubit_count`, and index ordering is expected to correlate with target ordering + on the instruction. + + Raises: + ValueError: `qubit_count` is less than 1, `ascii_symbols` are None, or + length of `ascii_symbols` is not equal to `qubit_count` + """ + if qubit_count < 1: + raise ValueError(f"qubit_count, {qubit_count}, must be greater than zero") + self._qubit_count = qubit_count + + if ascii_symbols is None: + raise ValueError("ascii_symbols must not be None") + + if len(ascii_symbols) != qubit_count: + msg = f"ascii_symbols, {ascii_symbols}, length must equal qubit_count, {qubit_count}" + raise ValueError(msg) + self._ascii_symbols = tuple(ascii_symbols) + + @property + def name(self) -> str: + """ + Returns the name of the quantum operator + + Returns: + The name of the quantum operator as a string + """ + return self.__class__.__name__ + + def to_ir(self, target: QubitSet) -> Any: + """Returns IR object of quantum operator and target + + Args: + target (QubitSet): target qubit(s) + Returns: + IR object of the quantum operator and target + """ + raise NotImplementedError("to_ir has not been implemented yet.") + + def to_matrix(self, *args, **kwargs) -> Any: + """Returns a list of matrices defining the Kraus matrices of the noise channel. + + Returns: + Iterable[np.ndarray]: list of matrices defining the Kraus matrices of the noise channel. + """ + raise NotImplementedError("to_matrix has not been implemented yet.") + + def __eq__(self, other): + if isinstance(other, Noise): + return self.name == other.name + return NotImplemented + + def __repr__(self): + return f"{self.name}('qubit_count': {self.qubit_count})" + + @classmethod + def register_noise(cls, noise: "Noise"): + """Register a noise implementation by adding it into the Noise class. + + Args: + noise (Noise): Noise class to register. + """ + setattr(cls, noise.__name__, noise) + + +class SingleProbabilisticNoise(Noise): + """ + Class `SingleProbabilisticNoise` represents the bit/phase flip noise channel on N qubits + parameterized by a single probability. + """ + + def __init__(self, probability: float, qubit_count: int, ascii_symbols: Sequence[str]): + """ + Args: + probability (float): The probability that the noise occurs. + qubit_count (int): The number of qubits to apply noise. + ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when + 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: + ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or + `ascii_symbols` length != `qubit_count`, `probability` is not `float`, + `probability` > 1/2, or `probability` < 0 + """ + super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + + if not isinstance(probability, float): + raise TypeError("probability must be float type") + if not (probability <= 0.5 and probability >= 0.0): + raise ValueError("probability must be a real number in the interval [0,1/2]") + self._probability = probability + + @property + def probability(self) -> float: + """ + Returns: + probability (float): The probability that parameterizes the noise channel. + """ + return self._probability + + def __repr__(self): + return f"{self.name}('probability': {self.probability}, 'qubit_count': {self.qubit_count})" + + +class SingleProbabilisticNoise_34(Noise): + """ + Class `SingleProbabilisticNoise` represents the Depolarizing and TwoQubitDephasing noise + channels parameterized by a single probability. + """ + + def __init__(self, probability: float, qubit_count: int, ascii_symbols: Sequence[str]): + """ + Args: + probability (float): The probability that the noise occurs. + qubit_count (int): The number of qubits to apply noise. + ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when + 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: + ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or + `ascii_symbols` length != `qubit_count`, `probability` is not `float`, + `probability` > 3/4, or `probability` < 0 + """ + super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + + if not isinstance(probability, float): + raise TypeError("probability must be float type") + if not (probability <= 0.75 and probability >= 0.0): + raise ValueError("probability must be a real number in the interval [0,3/4]") + self._probability = probability + + @property + def probability(self) -> float: + """ + Returns: + probability (float): The probability that parameterizes the noise channel. + """ + return self._probability + + def __repr__(self): + return f"{self.name}('probability': {self.probability}, 'qubit_count': {self.qubit_count})" + + +class SingleProbabilisticNoise_1516(Noise): + """ + Class `SingleProbabilisticNoise` represents the TwoQubitDepolarizing noise channel + parameterized by a single probability. + """ + + def __init__(self, probability: float, qubit_count: int, ascii_symbols: Sequence[str]): + """ + Args: + probability (float): The probability that the noise occurs. + qubit_count (int): The number of qubits to apply noise. + ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when + 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: + ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or + `ascii_symbols` length != `qubit_count`, `probability` is not `float`, + `probability` > 15/16, or `probability` < 0 + """ + super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + + if not isinstance(probability, float): + raise TypeError("probability must be float type") + if not (probability <= 0.9375 and probability >= 0.0): + raise ValueError("probability must be a real number in the interval [0,15/16]") + self._probability = probability + + @property + def probability(self) -> float: + """ + Returns: + probability (float): The probability that parameterizes the noise channel. + """ + return self._probability + + def __repr__(self): + return f"{self.name}('probability': {self.probability}, 'qubit_count': {self.qubit_count})" + + +class PauliNoise(Noise): + """ + Class `PauliNoise` represents the general Pauli noise channel on N qubits + parameterized by three probabilities. + """ + + def __init__( + self, + probX: float, + probY: float, + probZ: float, + qubit_count: int, + ascii_symbols: Sequence[str], + ): + """ + Args: + probX [float], probY [float], probZ [float]: The coefficients of the Kraus operators + in the channel. + qubit_count (int): The number of qubits to apply noise. + ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when + 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: + ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or + `ascii_symbols` length != `qubit_count`, `probX` or `probY` or `probZ` + is not `float`, `probX` or `probY` or `probZ` > 1.0, or + `probX` or `probY` or `probZ` < 0.0, or `probX`+`probY`+`probZ` > 1 + """ + super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + + if not isinstance(probX, float): + raise TypeError("probX must be float type") + if not (probX <= 1.0 and probX >= 0.0): + raise ValueError("probX must be a real number in the interval [0,1]") + if not isinstance(probY, float): + raise TypeError("probY must be float type") + if not (probY <= 1.0 and probY >= 0.0): + raise ValueError("probY must be a real number in the interval [0,1]") + if not isinstance(probZ, float): + raise TypeError("probZ must be float type") + if not (probZ <= 1.0 and probZ >= 0.0): + raise ValueError("probZ must be a real number in the interval [0,1]") + if probX + probY + probZ > 1: + raise ValueError("the sum of probX, probY, probZ cannot be larger than 1") + + self._probX = probX + self._probY = probY + self._probZ = probZ + + @property + def probX(self) -> float: + """ + Returns: + probX (float): The probability of a Pauli X error. + """ + return self._probX + + @property + def probY(self) -> float: + """ + Returns: + probY (float): The probability of a Pauli Y error. + """ + return self._probY + + @property + def probZ(self) -> float: + """ + Returns: + probZ (float): The probability of a Pauli Z error. + """ + return self._probZ + + def __repr__(self): + return f"{self.name}('probX': {self.probX}, 'probY': {self.probY}, \ +'probZ': {self.probZ}, 'qubit_count': {self.qubit_count})" + + +class DampingNoise(Noise): + """ + Class `DampingNoise` represents a damping noise channel + on N qubits parameterized by gamma. + """ + + def __init__(self, gamma: float, qubit_count: int, ascii_symbols: Sequence[str]): + """ + Args: + gamma (float): Probability of damping. + qubit_count (int): The number of qubits to apply noise. + ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when + 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: + ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or + `ascii_symbols` length != `qubit_count`, `gamma` is not `float`, + `gamma` > 1.0, or `gamma` < 0.0. + """ + super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + + if not isinstance(gamma, float): + raise TypeError("gamma must be float type") + if not (gamma <= 1.0 and gamma >= 0.0): + raise ValueError("gamma must be a real number in the interval [0,1]") + self._gamma = gamma + + @property + def gamma(self) -> float: + """ + Returns: + gamma (float): Probability of damping. + """ + return self._gamma + + def __repr__(self): + return f"{self.name}('gamma': {self.gamma}, 'qubit_count': {self.qubit_count})" + + +class GeneralizedAmplitudeDampingNoise(DampingNoise): + """ + Class `GeneralizedAmplitudeDampingNoise` represents the generalized amplitude damping + noise channel on N qubits parameterized by gamma and probability. + """ + + def __init__( + self, gamma: float, probability: float, qubit_count: int, ascii_symbols: Sequence[str] + ): + """ + Args: + gamma (float): Probability of damping. + probability (float): Probability of the system being excited by the environment. + qubit_count (int): The number of qubits to apply noise. + ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when + 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: + ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or + `ascii_symbols` length != `qubit_count`, `probability` or `gamma` is not `float`, + `probability` > 1.0, or `probability` < 0.0, `gamma` > 1.0, or `gamma` < 0.0. + """ + super().__init__(gamma=gamma, qubit_count=qubit_count, ascii_symbols=ascii_symbols) + + if not isinstance(probability, float): + raise TypeError("probability must be float type") + if not (probability <= 1.0 and probability >= 0.0): + raise ValueError("probability must be a real number in the interval [0,1]") + self._probability = probability + + @property + def probability(self) -> float: + """ + Returns: + probability (float): Probability of the system being excited by the environment. + """ + return self._probability + + def __repr__(self): + return f"{self.name}('gamma': {self.gamma}, 'probability': {self.probability}, \ +'qubit_count': {self.qubit_count})" diff --git a/src/braket/circuits/noise_helpers.py b/src/braket/circuits/noise_helpers.py new file mode 100644 index 00000000..9db049e7 --- /dev/null +++ b/src/braket/circuits/noise_helpers.py @@ -0,0 +1,304 @@ +# Copyright 2019-2019 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 typing import TYPE_CHECKING, Any, Iterable, Optional, Type, Union + +import numpy as np + +from braket.circuits.gate import Gate +from braket.circuits.instruction import Instruction +from braket.circuits.moments import Moments +from braket.circuits.noise import Noise +from braket.circuits.quantum_operator_helpers import is_unitary +from braket.circuits.qubit_set import QubitSet, QubitSetInput + +if TYPE_CHECKING: # pragma: no cover + from braket.circuits.circuit import Circuit + + +def no_noise_applied_warning(noise_applied: bool): + "Helper function to give a warning is noise is not applied" + if noise_applied is False: + 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." + ) + + +def wrap_with_list(an_item: Any): + "Helper function to make the input parameter a list" + if an_item is not None and not isinstance(an_item, list): + an_item = [an_item] + return an_item + + +def check_noise_target_gates(noise: Noise, target_gates: Iterable[Type[Gate]]): + """Helper function to check + 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 (Union[Type[Gate], Iterable[Type[Gate]]]): Gate class or + List of Gate classes which `noise` is applied to. + """ + + if not all(isinstance(g, type) and issubclass(g, Gate) for g in target_gates): + raise TypeError("All elements in target_gates must be an instance of the Gate class") + + if noise.qubit_count > 1: + for g in target_gates: + if g != Gate.Unitary and g().qubit_count != noise.qubit_count: + raise ValueError( + "The target_targets must be gates that have the same number of \ +qubits as defined by the multi-qubit noise channel." + ) + + +def check_noise_target_unitary(noise: Noise, target_unitary: np.ndarray): + """Helper function to check + 1. whether the input matrix is a np.ndarray type; + 2. whether the target_unitary is a unitary; + + Args: + noise (Noise): A Noise class object to be applied to the circuit. + target_unitary (np.ndarray): matrix of the target unitary gates + """ + + if not isinstance(target_unitary, np.ndarray): + raise TypeError("target_unitary must be a np.ndarray type") + + if not is_unitary(target_unitary): + raise ValueError("target_unitary must be a unitary") + + +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. + Args: + target_qubits (Optional[QubitSetInput] = None): Index or indices of qubit(s). + Returns: + target_qubits: QubitSet + """ + if target_qubits is None: + target_qubits = circuit.qubits + else: + 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): + raise ValueError("target_qubits must contain only non-negative integers.") + + target_qubits = QubitSet(target_qubits) + + return 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. + + When `noise.qubit_count` == 1, `noise` is added to all qubits in `target_qubits`. + + When `noise.qubit_count` > 1, `noise.qubit_count` must be the same as the length of + `target_qubits`. + + Args: + circuit (Circuit): A ciruit where `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. + + Returns: + Circuit: modified circuit. + """ + noise_instructions = [] + 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 + 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") + + # add existing instructions + for moment_key in circuit.moments: + instruction = circuit.moments[moment_key] + # if the instruction is noise instruction + if isinstance(instruction.operator, Noise): + new_moments.add_noise(instruction, moment_key.moment_type, moment_key.noise_index) + # if the instruction is a gate instruction + else: + new_moments.add([instruction], moment_key.noise_index) + + if position == "readout": + for noise in noise_instructions: + new_moments.add_noise(noise, "readout_noise") + + circuit._moments = new_moments + + return circuit + + +def _apply_noise_to_gates_helper( + noise: Iterable[Type[Noise]], + target_qubits: QubitSet, + instruction: Instruction, + noise_index: int, + intersection: QubitSet, + noise_applied: bool, + new_noise_instruction: Iterable, +): + """Helper function to work out the noise instructions to be attached to a gate. + + Args: + noise (Iterable[Type[Noise]]): Noise channel(s) to be applied + to the circuit. + target_qubits (QubitSet): Index or indices of qubits which `noise` is applied to. + instruction (Instruction): Instruction of the gate which `noise` is applied to. + noise_index (int): The number of noise channels applied to the gate. + intersection (QubitSet): Intersection of target_qubits and the qubits associated + with the gate. + noise_applied (bool): Whether noise is applied or not. + new_noise_instruction (Iterable): current new noise instructions to be attached + to the circuit. + + Returns: + new_noise_instruction: A list of noise intructions + 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: + # apply noise to the qubit if it is in target_qubits + 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 + + return new_noise_instruction, noise_index, noise_applied + + +def apply_noise_to_gates( + circuit: Circuit, + noise: Iterable[Type[Noise]], + target_gates: Union[Iterable[Type[Gate]], np.ndarray], + target_qubits: QubitSet, +) -> Circuit: + """Apply noise after target gates in target qubits. + + When `noise.qubit_count` == 1, `noise` is applied to target_qubits after `target_gates`. + + When `noise.qubit_count` > 1, all elements in `target_gates`, if is given, must have + the same number of qubits as `noise.qubit_count`. + + Args: + circuit (Circuit): A ciruit where `noise` is applied to. + noise (Iterable[Type[Noise]]): Noise channel(s) to be applied + to the circuit. + target_gates (Union[Iterable[Type[Gate]], np.ndarray]): List of gates, or a unitary matrix + which `noise` is applied to. + target_qubits (QubitSet): Index or indices of qubits which `noise` is applied to. + + Returns: + Circuit: modified circuit. + + Raises: + 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. + 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 + + for moment_key in circuit.moments: + instruction = circuit.moments[moment_key] + + # add the instruction to new_moments if it is noise instruction + if isinstance(instruction.operator, Noise): + new_moments.add_noise(instruction, moment_key.moment_type, moment_key.noise_index) + + # if the instruction is a gate instruction + else: + new_noise_instruction = [] + noise_index = moment_key.noise_index + if isinstance(target_gates, np.ndarray): + if (instruction.operator.name == "Unitary") and ( + np.array_equiv(instruction.operator._matrix, target_gates) + ): + intersection = list(set(instruction.target) & set(target_qubits)) + ( + new_noise_instruction, + noise_index, + noise_applied, + ) = _apply_noise_to_gates_helper( + noise, + target_qubits, + instruction, + noise_index, + intersection, + noise_applied, + new_noise_instruction, + ) + + elif (target_gates is None) or ( + instruction.operator.name in [g.__name__ for g in target_gates] + ): + intersection = list(set(instruction.target) & set(target_qubits)) + new_noise_instruction, noise_index, noise_applied = _apply_noise_to_gates_helper( + noise, + target_qubits, + instruction, + noise_index, + intersection, + noise_applied, + new_noise_instruction, + ) + + # add the gate and gate noise instructions to new_moments + new_moments.add([instruction], noise_index=noise_index) + for instruction, noise_index in new_noise_instruction: + new_moments.add_noise(instruction, "gate_noise", noise_index) + + no_noise_applied_warning(noise_applied) + circuit._moments = new_moments + return circuit diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py new file mode 100644 index 00000000..fb79437e --- /dev/null +++ b/src/braket/circuits/noises.py @@ -0,0 +1,835 @@ +# Copyright 2019-2019 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 typing import Iterable + +import numpy as np + +import braket.ir.jaqcd as ir +from braket.circuits import circuit +from braket.circuits.instruction import Instruction +from braket.circuits.noise import ( + DampingNoise, + GeneralizedAmplitudeDampingNoise, + Noise, + PauliNoise, + SingleProbabilisticNoise, + SingleProbabilisticNoise_34, + SingleProbabilisticNoise_1516, +) +from braket.circuits.quantum_operator_helpers import ( + is_cptp, + verify_quantum_operator_matrix_dimensions, +) +from braket.circuits.qubit import QubitInput +from braket.circuits.qubit_set import QubitSet, QubitSetInput + +""" +To add a new Noise implementation: + 1. Implement the class and extend `Noise` + 2. Add a method with the `@circuit.subroutine(register=True)` decorator. Method name + will be added into the `Circuit` class. This method is the default way clients add + this noise to a circuit. + 3. Register the class with the `Noise` class via `Noise.register_noise()`. +""" + + +class BitFlip(SingleProbabilisticNoise): + """Bit flip noise channel which transforms a density matrix :math:`\\rho` according to: + + .. math:: \\rho \\Rightarrow (1-p) \\rho + p X \\rho X^{\\dagger} + where + + .. math:: + I = \\left( + \\begin{matrix} + 1 & 0 \\\\ + 0 & 1 + \\end{matrix} + \\right) + + X = \\left( + \\begin{matrix} + 0 & 1 \\\\ + 1 & 0 + \\end{matrix} + \\right) + + p = \\text{probability} + + This noise channel is shown as `BF` in circuit diagrams. + """ + + def __init__(self, probability: float): + super().__init__( + probability=probability, + qubit_count=1, + ascii_symbols=["BF({:.2g})".format(probability)], + ) + + def to_ir(self, target: QubitSet): + return ir.BitFlip.construct(target=target[0], probability=self.probability) + + def to_matrix(self) -> Iterable[np.ndarray]: + K0 = np.sqrt(1 - self.probability) * np.eye(2, dtype=complex) + K1 = np.sqrt(self.probability) * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) + return [K0, K1] + + @staticmethod + @circuit.subroutine(register=True) + def bit_flip(target: QubitSetInput, probability: float) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + probability (float): Probability of bit flipping. + + Returns: + Iterable[Instruction]: `Iterable` of BitFlip instructions. + + Examples: + >>> circ = Circuit().bit_flip(0, probability=0.1) + """ + return [ + Instruction(Noise.BitFlip(probability=probability), target=qubit) + for qubit in QubitSet(target) + ] + + +Noise.register_noise(BitFlip) + + +class PhaseFlip(SingleProbabilisticNoise): + """Phase flip noise channel which transforms a density matrix :math:`\\rho` according to: + + .. math:: \\rho \\Rightarrow (1-p) \\rho + p X \\rho X^{\\dagger} + where + + .. math:: + I = \\left( + \\begin{matrix} + 1 & 0 \\\\ + 0 & 1 + \\end{matrix} + \\right) + + Z = \\left( + \\begin{matrix} + 1 & 0 \\\\ + 0 & -1 + \\end{matrix} + \\right) + + p = \\text{probability} + + This noise channel is shown as `PF` in circuit diagrams. + """ + + def __init__(self, probability: float): + super().__init__( + probability=probability, + qubit_count=1, + ascii_symbols=["PF({:.2g})".format(probability)], + ) + + def to_ir(self, target: QubitSet): + return ir.PhaseFlip.construct(target=target[0], probability=self.probability) + + def to_matrix(self) -> Iterable[np.ndarray]: + K0 = np.sqrt(1 - self.probability) * np.eye(2, dtype=complex) + K1 = np.sqrt(self.probability) * np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) + return [K0, K1] + + @staticmethod + @circuit.subroutine(register=True) + def phase_flip(target: QubitSetInput, probability: float) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + probability (float): Probability of phase flipping. + + Returns: + Iterable[Instruction]: `Iterable` of PhaseFlip instructions. + + Examples: + >>> circ = Circuit().phase_flip(0, probability=0.1) + """ + return [ + Instruction(Noise.PhaseFlip(probability=probability), target=qubit) + for qubit in QubitSet(target) + ] + + +Noise.register_noise(PhaseFlip) + + +class PauliChannel(PauliNoise): + """Pauli noise channel which transforms a density matrix :math:`\\rho` according to: + + .. math:: + \\rho \\Rightarrow (1-probX-probY-probZ) \\rho + + probX X \\rho X^{\\dagger} + + probY Y \\rho Y^{\\dagger} + + probZ Z \\rho Z^{\\dagger} + where + + .. math:: + I = \\left( + \\begin{matrix} + 1 & 0 \\\\ + 0 & 1 + \\end{matrix} + \\right) + + X = \\left( + \\begin{matrix} + 0 & 1 \\\\ + 1 & 0 + \\end{matrix} + \\right) + + Y = \\left( + \\begin{matrix} + 0 & -i \\\\ + i & 0 + \\end{matrix} + \\right) + + Z = \\left( + \\begin{matrix} + 1 & 0 \\\\ + 0 & -1 + \\end{matrix} + \\right) + + This noise channel is shown as `PC` in circuit diagrams. + """ + + def __init__(self, probX: float, probY: float, probZ: float): + super().__init__( + probX=probX, + probY=probY, + probZ=probZ, + qubit_count=1, + ascii_symbols=["PC({:.2g},{:.2g},{:.2g})".format(probX, probY, probZ)], + ) + + def to_ir(self, target: QubitSet): + return ir.PauliChannel.construct( + target=target[0], probX=self.probX, probY=self.probY, probZ=self.probZ + ) + + def to_matrix(self) -> Iterable[np.ndarray]: + K0 = np.sqrt(1 - self.probX - self.probY - self.probZ) * np.eye(2, dtype=complex) + K1 = np.sqrt(self.probX) * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) + K2 = np.sqrt(self.probY) * 1j * np.array([[0.0, -1.0], [1.0, 0.0]], dtype=complex) + K3 = np.sqrt(self.probZ) * np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) + return [K0, K1, K2, K3] + + @staticmethod + @circuit.subroutine(register=True) + def pauli_channel( + target: QubitSetInput, probX: float, probY: float, probZ: float + ) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + probability List[float]: Probabilities for the Pauli X, Y and Z noise + happening in the Kraus channel. + + Returns: + Iterable[Instruction]: `Iterable` of PauliChannel instructions. + + Examples: + >>> circ = Circuit().pauli_channel(0,probX=0.1,probY=0.2,probZ=0.3) + """ + return [ + Instruction(Noise.PauliChannel(probX=probX, probY=probY, probZ=probZ), target=qubit) + for qubit in QubitSet(target) + ] + + +Noise.register_noise(PauliChannel) + + +class Depolarizing(SingleProbabilisticNoise_34): + """Depolarizing noise channel which transforms a density matrix :math:`\\rho` according to: + + .. math:: + \\rho \\Rightarrow (1-p) \\rho + + p/3 X \\rho X^{\\dagger} + + p/3 Y \\rho Y^{\\dagger} + + p/3 Z \\rho Z^{\\dagger} + where + + .. math:: + I = \\left( + \\begin{matrix} + 1 & 0 \\\\ + 0 & 1 + \\end{matrix} + \\right) + + X = \\left( + \\begin{matrix} + 0 & 1 \\\\ + 1 & 0 + \\end{matrix} + \\right) + + Y = \\left( + \\begin{matrix} + 0 & -i \\\\ + i & 0 + \\end{matrix} + \\right) + + Z = \\left( + \\begin{matrix} + 1 & 0 \\\\ + 0 & -1 + \\end{matrix} + \\right) + + p = \\text{probability} + + This noise channel is shown as `DEPO` in circuit diagrams. + """ + + def __init__(self, probability: float): + super().__init__( + probability=probability, + qubit_count=1, + ascii_symbols=["DEPO({:.2g})".format(probability)], + ) + + def to_ir(self, target: QubitSet): + return ir.Depolarizing.construct(target=target[0], probability=self.probability) + + def to_matrix(self) -> Iterable[np.ndarray]: + K0 = np.sqrt(1 - self.probability) * np.eye(2, dtype=complex) + K1 = np.sqrt(self.probability / 3) * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) + K2 = np.sqrt(self.probability / 3) * 1j * np.array([[0.0, -1.0], [1.0, 0.0]], dtype=complex) + K3 = np.sqrt(self.probability / 3) * np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) + return [K0, K1, K2, K3] + + @staticmethod + @circuit.subroutine(register=True) + def depolarizing(target: QubitSetInput, probability: float) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + probability (float): Probability of depolarizing. + + Returns: + Iterable[Instruction]: `Iterable` of Depolarizing instructions. + + Examples: + >>> circ = Circuit().depolarizing(0, probability=0.1) + """ + return [ + Instruction(Noise.Depolarizing(probability=probability), target=qubit) + for qubit in QubitSet(target) + ] + + +Noise.register_noise(Depolarizing) + + +class TwoQubitDepolarizing(SingleProbabilisticNoise_1516): + """Two-Qubit Depolarizing noise channel which transforms a + density matrix :math:`\\rho` according to: + + .. math:: + \\rho \\Rightarrow (1-p) \\rho + p/15 ( + IX \\rho IX^{\\dagger} + IY \\rho IY^{\\dagger} + IZ \\rho IZ^{\\dagger} + + XI \\rho XI^{\\dagger} + XX \\rho XX^{\\dagger} + XY \\rho XY^{\\dagger} + + XZ \\rho XZ^{\\dagger} + YI \\rho YI^{\\dagger} + YX \\rho YX^{\\dagger} + + YY \\rho YY^{\\dagger} + YZ \\rho YZ^{\\dagger} + ZI \\rho ZI^{\\dagger} + + ZX \\rho ZX^{\\dagger} + ZY \\rho ZY^{\\dagger} + ZZ \\rho ZZ^{\\dagger}) + where + + .. math:: + I = \\left( + \\begin{matrix} + 1 & 0 \\\\ + 0 & 1 + \\end{matrix} + \\right) + + X = \\left( + \\begin{matrix} + 0 & 1 \\\\ + 1 & 0 + \\end{matrix} + \\right) + + Y = \\left( + \\begin{matrix} + 0 & -i \\\\ + i & 0 + \\end{matrix} + \\right) + + Z = \\left( + \\begin{matrix} + 1 & 0 \\\\ + 0 & -1 + \\end{matrix} + \\right) + + p = \\text{probability} + + This noise channel is shown as `DEPO` in circuit diagrams. + """ + + def __init__(self, probability: float): + super().__init__( + probability=probability, + qubit_count=2, + ascii_symbols=["DEPO({:.2g})".format(probability)] * 2, + ) + + def to_ir(self, target: QubitSet): + return ir.TwoQubitDepolarizing.construct( + targets=[target[0], target[1]], probability=self.probability + ) + + def to_matrix(self) -> Iterable[np.ndarray]: + + 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) + SZ = np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) + + K_list_single = [SI, SX, SY, SZ] + K_list = [np.kron(i, j) for i in K_list_single for j in K_list_single] + + K_list[0] *= np.sqrt(1 - self._probability) + + K_list[1:] = [np.sqrt(self._probability / 15) * i for i in K_list[1:]] + + return K_list + + @staticmethod + @circuit.subroutine(register=True) + def two_qubit_depolarizing( + target1: QubitInput, target2: QubitInput, probability: float + ) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubits + probability (float): Probability of two-qubit depolarizing. + + Returns: + Iterable[Instruction]: `Iterable` of Depolarizing instructions. + + Examples: + >>> circ = Circuit().two_qubit_depolarizing(0, 1, probability=0.1) + """ + return [ + Instruction( + Noise.TwoQubitDepolarizing(probability=probability), target=[target1, target2] + ) + ] + + +Noise.register_noise(TwoQubitDepolarizing) + + +class TwoQubitDephasing(SingleProbabilisticNoise_34): + """Two-Qubit Dephasing noise channel which transforms a + density matrix :math:`\\rho` according to: + + .. math:: + \\rho \\Rightarrow (1-p) \\rho + p/3 ( + IZ \\rho IZ^{\\dagger} + ZI \\rho ZI^{\\dagger} + ZZ \\rho ZZ^{\\dagger}) + where + + .. math:: + I = \\left( + \\begin{matrix} + 1 & 0 \\\\ + 0 & 1 + \\end{matrix} + \\right) + + Z = \\left( + \\begin{matrix} + 1 & 0 \\\\ + 0 & -1 + \\end{matrix} + \\right) + + p = \\text{probability} + + This noise channel is shown as `DEPH` in circuit diagrams. + """ + + def __init__(self, probability: float): + super().__init__( + probability=probability, + qubit_count=2, + ascii_symbols=["DEPH({:.2g})".format(probability)] * 2, + ) + + def to_ir(self, target: QubitSet): + return ir.TwoQubitDephasing.construct( + targets=[target[0], target[1]], probability=self.probability + ) + + def to_matrix(self) -> Iterable[np.ndarray]: + + SI = np.array([[1.0, 0.0], [0.0, 1.0]], dtype=complex) + SZ = np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) + K0 = np.sqrt(1 - self._probability) * np.kron(SI, SI) + K1 = np.sqrt(self._probability / 3) * np.kron(SI, SZ) + K2 = np.sqrt(self._probability / 3) * np.kron(SZ, SI) + K3 = np.sqrt(self._probability / 3) * np.kron(SZ, SZ) + + return [K0, K1, K2, K3] + + @staticmethod + @circuit.subroutine(register=True) + def two_qubit_dephasing( + target1: QubitInput, target2: QubitInput, probability: float + ) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubits + probability (float): Probability of two-qubit dephasing. + + Returns: + Iterable[Instruction]: `Iterable` of Dephasing instructions. + + Examples: + >>> circ = Circuit().two_qubit_dephasing(0, 1, probability=0.1) + """ + return [ + Instruction(Noise.TwoQubitDephasing(probability=probability), target=[target1, target2]) + ] + + +Noise.register_noise(TwoQubitDephasing) + + +class AmplitudeDamping(DampingNoise): + """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} + where + + .. math:: + E_0 = \\left( + \\begin{matrix} + 1 & 0 \\\\ + 0 & \\sqrt{1-\\gamma} + \\end{matrix} + \\right) + + E_1 = \\left( + \\begin{matrix} + 0 & \\sqrt{\\gamma} \\\\ + 0 & 0 + \\end{matrix} + \\right) + + This noise channel is shown as `AD` in circuit diagrams. + """ + + def __init__(self, gamma: float): + super().__init__( + gamma=gamma, + qubit_count=1, + ascii_symbols=["AD({:.2g})".format(gamma)], + ) + + def to_ir(self, target: QubitSet): + return ir.AmplitudeDamping.construct(target=target[0], gamma=self.gamma) + + def to_matrix(self) -> Iterable[np.ndarray]: + K0 = np.array([[1.0, 0.0], [0.0, np.sqrt(1 - self.gamma)]], dtype=complex) + K1 = np.array([[0.0, np.sqrt(self.gamma)], [0.0, 0.0]], dtype=complex) + return [K0, K1] + + @staticmethod + @circuit.subroutine(register=True) + def amplitude_damping(target: QubitSetInput, gamma: float) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s). + gamma (float): decaying rate of the amplitude damping channel. + + Returns: + Iterable[Instruction]: `Iterable` of AmplitudeDamping instructions. + + Examples: + >>> circ = Circuit().amplitude_damping(0, gamma=0.1) + """ + return [ + Instruction(Noise.AmplitudeDamping(gamma=gamma), target=qubit) + for qubit in QubitSet(target) + ] + + +Noise.register_noise(AmplitudeDamping) + + +class GeneralizedAmplitudeDamping(GeneralizedAmplitudeDampingNoise): + """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} + + E_2 \\rho E_2^{\\dagger} + E_3 \\rho E_3^{\\dagger} + where + + .. math:: + E_0 = \\sqrt(probability)\\left( + \\begin{matrix} + 1 & 0 \\\\ + 0 & \\sqrt{1-\\gamma} + \\end{matrix} + \\right) + + E_1 = \\sqrt(probability)\\left( + \\begin{matrix} + 0 & \\sqrt{\\gamma} \\\\ + 0 & 0 + \\end{matrix} + \\right) + E_2 = \\sqrt(1-probability)\\left( + \\begin{matrix} + \\sqrt{1-\\gamma} & 0 \\\\ + 0 & 1 + \\end{matrix} + \\right) + E_3 = \\sqrt(1-probability)\\left( + \\begin{matrix} + 0 & 0 \\\\ + \\sqrt{\\gamma} & 0 + \\end{matrix} + \\right) + + This noise channel is shown as `GAD` in circuit diagrams. + """ + + def __init__(self, gamma: float, probability: float): + super().__init__( + gamma=gamma, + probability=probability, + qubit_count=1, + ascii_symbols=["GAD({:.2g},{:.2g})".format(gamma, probability)], + ) + + def to_ir(self, target: QubitSet): + return ir.GeneralizedAmplitudeDamping.construct( + target=target[0], gamma=self.gamma, probability=self.probability + ) + + def to_matrix(self) -> Iterable[np.ndarray]: + K0 = np.sqrt(self.probability) * np.array( + [[1.0, 0.0], [0.0, np.sqrt(1 - self.gamma)]], dtype=complex + ) + K1 = np.sqrt(self.probability) * np.array( + [[0.0, np.sqrt(self.gamma)], [0.0, 0.0]], dtype=complex + ) + K2 = np.sqrt(1 - self.probability) * np.array([[np.sqrt(1 - self.gamma), 0.0], [0.0, 1.0]]) + K3 = np.sqrt(1 - self.probability) * np.array([[0.0, 0.0], [np.sqrt(self.gamma), 0.0]]) + return [K0, K1, K2, K3] + + @staticmethod + @circuit.subroutine(register=True) + def generalized_amplitude_damping( + target: QubitSetInput, gamma: float, probability: float + ) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s). + p(float): Probability of the system being excited by the environment. + gamma (float): The damping rate of the amplitude damping channel. + + Returns: + Iterable[Instruction]: `Iterable` of GeneralizedAmplitudeDamping instructions. + + Examples: + >>> circ = Circuit().generalized_amplitude_damping(0, probability = 0.9, gamma=0.1) + """ + return [ + Instruction( + Noise.GeneralizedAmplitudeDamping(gamma=gamma, probability=probability), + target=qubit, + ) + for qubit in QubitSet(target) + ] + + +Noise.register_noise(GeneralizedAmplitudeDamping) + + +class PhaseDamping(DampingNoise): + """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} + where + + .. math:: + E_0 = \\left( + \\begin{matrix} + 1 & 0 \\\\ + 0 & \\sqrt{1-gamma} + \\end{matrix} + \\right) + + E_1 = \\left( + \\begin{matrix} + 0 & 0 \\\\ + 0 & \\sqrt{gamma} + \\end{matrix} + \\right) + + p = \\text{probability} + + This noise channel is shown as `PD` in circuit diagrams. + """ + + def __init__(self, gamma: float): + super().__init__( + gamma=gamma, + qubit_count=1, + ascii_symbols=["PD({:.2g})".format(gamma)], + ) + + def to_ir(self, target: QubitSet): + return ir.PhaseDamping.construct(target=target[0], gamma=self.gamma) + + def to_matrix(self) -> Iterable[np.ndarray]: + K0 = np.array([[1.0, 0.0], [0.0, np.sqrt(1 - self.gamma)]], dtype=complex) + K1 = np.array([[0.0, 0.0], [0.0, np.sqrt(self.gamma)]], dtype=complex) + return [K0, K1] + + @staticmethod + @circuit.subroutine(register=True) + def phase_damping(target: QubitSetInput, gamma: float) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + gamma (float): Probability of phase damping. + + Returns: + Iterable[Instruction]: `Iterable` of PhaseDamping instructions. + + Examples: + >>> circ = Circuit().phase_damping(0, gamma=0.1) + """ + return [ + Instruction(Noise.PhaseDamping(gamma=gamma), target=qubit) for qubit in QubitSet(target) + ] + + +Noise.register_noise(PhaseDamping) + + +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 satisify 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 satisify CPTP condition. + """ + + 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])): + raise ValueError(f"all matrices in {matrices} must have the same shape") + self._matrices = [np.array(matrix, dtype=complex) for matrix in matrices] + qubit_count = int(np.log2(self._matrices[0].shape[0])) + if qubit_count > 2: + raise ValueError("Kraus operators with more than two qubits are not supported.") + if len(matrices) > 2 ** (2 * qubit_count): + raise ValueError("The number of Kraus operators is beyond limit.") + + if not is_cptp(self._matrices): + raise ValueError( + "The input matrices do not define a completely-positive trace-preserving map." + ) + + super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) + + def to_matrix(self) -> Iterable[np.ndarray]: + return self._matrices + + def to_ir(self, target: QubitSet): + return ir.Kraus.construct( + targets=[qubit for qubit in target], + matrices=Kraus._transform_matrix_to_ir(self._matrices), + ) + + @staticmethod + def _transform_matrix_to_ir(matrices: Iterable[np.ndarray]): + serializable = [] + for matrix in matrices: + matrix_as_list = [ + [[element.real, element.imag] for element in row] for row in matrix.tolist() + ] + serializable.append(matrix_as_list) + return serializable + + @staticmethod + @circuit.subroutine(register=True) + def kraus( + targets: QubitSetInput, matrices: Iterable[np.array], display_name: str = "KR" + ) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + targets (Qubit, int, or iterable of Qubit / int): Target qubit(s) + matrices (Iterable[np.array]): Matrices that define a general noise channel. + + Returns: + Iterable[Instruction]: `Iterable` of Kraus instructions. + + Examples: + >>> K0 = np.eye(4) * sqrt(0.9) + >>> K1 = np.kron([[1., 0.],[0., 1.]], [[0., 1.],[1., 0.]]) * sqrt(0.1) + >>> circ = Circuit().kraus(0, matrices=[K0, K1]) + """ + if 2 ** len(targets) != matrices[0].shape[0]: + raise ValueError( + "Dimensions of the supplied Kraus matrices are incompatible with the targets" + ) + + return Instruction( + Noise.Kraus(matrices=matrices, display_name=display_name), target=targets + ) + + +Noise.register_noise(Kraus) diff --git a/src/braket/circuits/quantum_operator_helpers.py b/src/braket/circuits/quantum_operator_helpers.py index a6df3933..76788dda 100644 --- a/src/braket/circuits/quantum_operator_helpers.py +++ b/src/braket/circuits/quantum_operator_helpers.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. from functools import lru_cache +from typing import Iterable import numpy as np @@ -33,6 +34,7 @@ def verify_quantum_operator_matrix_dimensions(matrix: np.array) -> None: matrix = np.array(matrix, dtype=complex) qubit_count = int(np.log2(matrix.shape[0])) + if 2 ** qubit_count != matrix.shape[0] or qubit_count < 1: raise ValueError(f"`matrix` dimension {matrix.shape[0]} is not a positive power of 2") @@ -50,7 +52,7 @@ def is_hermitian(matrix: np.array) -> bool: Args: matrix (np.ndarray): matrix to verify - Return: + Returns: bool: If matrix is Hermitian """ return np.allclose(matrix, matrix.conj().T) @@ -63,7 +65,7 @@ def is_square_matrix(matrix: np.array) -> bool: Args: matrix (np.ndarray): matrix to verify - Return: + Returns: bool: If matrix is square """ return len(matrix.shape) == 2 and matrix.shape[0] == matrix.shape[1] @@ -83,12 +85,29 @@ def is_unitary(matrix: np.array) -> bool: Args: matrix (np.ndarray): matrix to verify - Return: + Returns: bool: If matrix is unitary """ return np.allclose(np.eye(len(matrix)), matrix.dot(matrix.T.conj())) +def is_cptp(matrices: Iterable[np.array]) -> bool: + """ + 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. + + Args: + matrices (Iterable[np.array]): List of matrices representing Kraus operators. + + Returns: + bool: If the matrices define a CPTP map. + """ + E = sum([np.dot(matrix.T.conjugate(), matrix) for matrix in matrices]) + return np.allclose(E, np.eye(*E.shape)) + + @lru_cache() def get_pauli_eigenvalues(num_qubits: int) -> np.ndarray: """ diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 74b73b99..952ff879 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -74,6 +74,78 @@ def __hash__(self) -> int: ResultType.register_result_type(StateVector) +class DensityMatrix(ResultType): + """ + The full density matrix as a requested result type. + This is available on simulators only when `shots=0`. + """ + + def __init__(self, target: QubitSetInput = None): + """ + Args: + target (int, Qubit, or iterable of int / Qubit, optional): The target qubits + of the reduced density matrix. Default is `None`, and the + full density matrix is returned. + + Examples: + >>> ResultType.DensityMatrix(target=[0, 1]) + """ + self._target = QubitSet(target) + ascii_symbols = ["DensityMatrix"] * len(self._target) if self._target else ["DensityMatrix"] + super().__init__(ascii_symbols=ascii_symbols) + + @property + def target(self) -> QubitSet: + return self._target + + @target.setter + def target(self, target: QubitSetInput) -> None: + self._target = QubitSet(target) + + def to_ir(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() + + @staticmethod + @circuit.subroutine(register=True) + def density_matrix(target: QubitSetInput = None) -> ResultType: + """Registers this function into the circuit class. + Args: + target (int, Qubit, or iterable of int / Qubit, optional): The target qubits + of the reduced density matrix. Default is `None`, and the + full density matrix is returned. + + Returns: + ResultType: density matrix as a requested result type + + Examples: + >>> circ = Circuit().density_matrix(target=[0, 1]) + """ + return ResultType.DensityMatrix(target=target) + + def __eq__(self, other) -> bool: + if isinstance(other, DensityMatrix): + return self.target == other.target + return False + + def __repr__(self) -> str: + return f"DensityMatrix(target={self.target})" + + def __copy__(self) -> DensityMatrix: + return type(self)(target=self.target) + + # must redefine __hash__ since __eq__ is overwritten + # https://docs.python.org/3/reference/datamodel.html#object.__hash__ + def __hash__(self) -> int: + return super().__hash__() + + +ResultType.register_result_type(DensityMatrix) + + class Amplitude(ResultType): """ The amplitude of the specified quantum states as a requested result type. diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index d2621ada..e6cde655 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -448,6 +448,36 @@ def run_circuit(circuit): assert len(result.measurements) == shots +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() + result = device.run(circuit, **run_kwargs).result() + assert len(result.result_types) == 1 + assert np.allclose( + result.get_value_by_result_type(ResultType.Probability()), + np.array([0.0, 0.1, 0, 0.9]), + **tol + ) + + +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) + K1 = np.kron(np.array([[0.0, 1.0], [1.0, 0.0]]), np.array([[0.0, 1.0], [1.0, 0.0]])) * np.sqrt( + 0.1 + ) + circuit = Circuit().x(0).x(1).kraus((0, 1), [K0, K1]).probability() + result = device.run(circuit, **run_kwargs).result() + assert len(result.result_types) == 1 + assert np.allclose( + result.get_value_by_result_type(ResultType.Probability()), + np.array([0.1, 0.0, 0, 0.9]), + **tol + ) + + def batch_bell_pair_testing(device: AwsDevice, run_kwargs: Dict[str, Any]): shots = run_kwargs["shots"] tol = get_tol(shots) diff --git a/test/integ_tests/test_local_noise_simulator.py b/test/integ_tests/test_local_noise_simulator.py new file mode 100644 index 00000000..0963fde6 --- /dev/null +++ b/test/integ_tests/test_local_noise_simulator.py @@ -0,0 +1,108 @@ +# Copyright 2019-2019 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 gate_model_device_testing_utils import ( + no_result_types_bell_pair_testing, + noisy_circuit_1qubit_noise_full_probability, + noisy_circuit_2qubit_noise_full_probability, + qubit_ordering_testing, + result_types_all_selected_testing, + result_types_bell_pair_full_probability_testing, + result_types_bell_pair_marginal_probability_testing, + result_types_hermitian_testing, + result_types_nonzero_shots_bell_pair_testing, + result_types_tensor_hermitian_hermitian_testing, + result_types_tensor_x_y_testing, + result_types_tensor_y_hermitian_testing, + result_types_tensor_z_h_y_testing, + result_types_tensor_z_hermitian_testing, + result_types_tensor_z_z_testing, +) + +from braket.devices import LocalSimulator + +DEVICE = LocalSimulator("braket_dm") +SHOTS = 8000 + + +def test_no_result_types_bell_pair(): + no_result_types_bell_pair_testing(DEVICE, {"shots": SHOTS}) + + +def test_qubit_ordering(): + qubit_ordering_testing(DEVICE, {"shots": SHOTS}) + + +def test_result_types_nonzero_shots_bell_pair(): + result_types_nonzero_shots_bell_pair_testing(DEVICE, {"shots": SHOTS}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_bell_pair_full_probability(shots): + result_types_bell_pair_full_probability_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_bell_pair_marginal_probability(shots): + result_types_bell_pair_marginal_probability_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_hermitian(shots): + result_types_hermitian_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_tensor_x_y(shots): + result_types_tensor_x_y_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_tensor_z_z(shots): + result_types_tensor_z_z_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_tensor_z_h_y(shots): + result_types_tensor_z_h_y_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_tensor_z_hermitian(shots): + result_types_tensor_z_hermitian_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_tensor_hermitian_hermitian(shots): + result_types_tensor_hermitian_hermitian_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_tensor_y_hermitian(shots): + result_types_tensor_y_hermitian_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_result_types_all_selected(shots): + result_types_all_selected_testing(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_noisy_circuit_1qubit_noise(shots): + noisy_circuit_1qubit_noise_full_probability(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize("shots", [0, SHOTS]) +def test_noisy_circuit_2qubit_noise(shots): + noisy_circuit_2qubit_noise_full_probability(DEVICE, {"shots": shots}) 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 f696c5a3..48325e6a 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -377,3 +377,35 @@ def test_multiple_result_types_with_custom_hermitian_ascii_symbol(): ) expected = "\n".join(expected) assert AsciiCircuitDiagram.build_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 |", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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 |", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_diagram(circ) == expected diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index ac0e346f..4b5696b9 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -311,6 +311,14 @@ def test_equality(): assert u1 != non_gate +def test_large_unitary(): + matrix = np.eye(16, dtype=np.float32) + # Permute rows of matrix + matrix[[*range(16)]] = matrix[[(i + 1) % 16 for i in range(16)]] + unitary = Gate.Unitary(matrix) + assert unitary.qubit_count == 4 + + @pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("matrix", invalid_unitary_matrices) def test_unitary_invalid_matrix(matrix): diff --git a/test/unit_tests/braket/circuits/test_moments.py b/test/unit_tests/braket/circuits/test_moments.py index cedb8643..12753400 100644 --- a/test/unit_tests/braket/circuits/test_moments.py +++ b/test/unit_tests/braket/circuits/test_moments.py @@ -37,8 +37,8 @@ def test_add(): moments.add([h(0)]) expected = OrderedDict() - expected[MomentsKey(0, QubitSet(0))] = h(0) - expected[MomentsKey(1, QubitSet(0))] = h(0) + expected[MomentsKey(0, QubitSet(0), "gate", 0)] = h(0) + expected[MomentsKey(1, QubitSet(0), "gate", 0)] = h(0) assert OrderedDict(moments) == expected @@ -106,16 +106,20 @@ def test_time_slices(): def test_keys(): moments = Moments([h(0), h(0), h(1)]) - expected = [MomentsKey(0, QubitSet(0)), MomentsKey(1, QubitSet(0)), MomentsKey(0, QubitSet(1))] + expected = [ + MomentsKey(0, QubitSet(0), "gate", 0), + MomentsKey(1, QubitSet(0), "gate", 0), + MomentsKey(0, QubitSet(1), "gate", 0), + ] assert list(moments.keys()) == expected def test_items(): moments = Moments([h(0), h(0), h(1)]) expected = [ - (MomentsKey(0, QubitSet(0)), h(0)), - (MomentsKey(1, QubitSet(0)), h(0)), - (MomentsKey(0, QubitSet(1)), h(1)), + (MomentsKey(0, QubitSet(0), "gate", 0), h(0)), + (MomentsKey(1, QubitSet(0), "gate", 0), h(0)), + (MomentsKey(0, QubitSet(1), "gate", 0), h(1)), ] assert list(moments.items()) == expected @@ -128,15 +132,15 @@ def test_values(): def test_get(): moments = Moments([h(0)]) - unknown_key = MomentsKey(100, QubitSet(100)) - assert moments.get(MomentsKey(0, QubitSet(0))) == h(0) + unknown_key = MomentsKey(100, QubitSet(100), "gate", 0) + assert moments.get(MomentsKey(0, QubitSet(0), "gate", 0)) == h(0) assert moments.get(unknown_key) is None assert moments.get(unknown_key, h(0)) == h(0) def test_getitem(): moments = Moments([h(0)]) - assert moments[MomentsKey(0, QubitSet(0))] == h(0) + assert moments[MomentsKey(0, QubitSet(0), "gate", 0)] == h(0) def test_iter(moments): @@ -150,8 +154,8 @@ def test_len(): def test_contains(): moments = Moments([h(0), h(0)]) - assert MomentsKey(0, QubitSet(0)) in moments - assert MomentsKey(0, QubitSet(100)) not in moments + assert MomentsKey(0, QubitSet(0), "gate", 0) in moments + assert MomentsKey(0, QubitSet(100), "gate", 0) not in moments def test_equals(): diff --git a/test/unit_tests/braket/circuits/test_noise.py b/test/unit_tests/braket/circuits/test_noise.py new file mode 100644 index 00000000..af5ba3e8 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_noise.py @@ -0,0 +1,350 @@ +# Copyright 2019-2019 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 Operator +from braket.circuits.noise import ( + DampingNoise, + GeneralizedAmplitudeDampingNoise, + Noise, + PauliNoise, + SingleProbabilisticNoise, + SingleProbabilisticNoise_34, + SingleProbabilisticNoise_1516, +) + +invalid_data_qubit_count = [(0, ["foo"])] +invalid_data_ascii_symbols = [(1, None)] +invalid_data_ascii_symbols_length = [(2, ["foo", "boo", "braket"])] +invalid_data_prob = [float("nan"), float("inf"), float("-inf"), 0.95, -2.6] +invalid_data_prob_2 = ["a", 1.0 + 1j] +invalid_data_prob_damping = [float("nan"), float("inf"), float("-inf"), 1.5, -2.6] +invalid_data_prob_damping_2 = ["a", 1.0 + 1j] + + +@pytest.fixture +def noise(): + return Noise(qubit_count=1, ascii_symbols=["foo"]) + + +@pytest.fixture +def single_probability_noise(): + return SingleProbabilisticNoise(probability=0.1, qubit_count=1, ascii_symbols=["foo"]) + + +@pytest.fixture +def single_probability_noise_34(): + return SingleProbabilisticNoise_34(probability=0.1, qubit_count=1, ascii_symbols=["foo"]) + + +@pytest.fixture +def single_probability_noise_1516(): + return SingleProbabilisticNoise_1516(probability=0.1, qubit_count=1, ascii_symbols=["foo"]) + + +@pytest.fixture +def pauli_noise(): + return PauliNoise(probX=0.1, probY=0.2, probZ=0.3, qubit_count=1, ascii_symbols=["foo"]) + + +@pytest.fixture +def damping_noise(): + return DampingNoise(gamma=0.2, qubit_count=1, ascii_symbols=["foo"]) + + +@pytest.fixture +def generalized_amplitude_damping_noise(): + return GeneralizedAmplitudeDampingNoise( + gamma=0.2, probability=0.9, qubit_count=1, ascii_symbols=["foo"] + ) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("qubit_count, ascii_symbols", invalid_data_qubit_count) +def test_invalid_data_qubit_count(qubit_count, ascii_symbols): + Noise(qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("qubit_count, ascii_symbols", invalid_data_ascii_symbols) +def test_invalid_data_ascii_symbols(qubit_count, ascii_symbols): + Noise(qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("qubit_count, ascii_symbols", invalid_data_ascii_symbols_length) +def test_invalid_data_ascii_symbols_length(qubit_count, ascii_symbols): + Noise(qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("probability", invalid_data_prob) +def test_invalid_data_single_prob(probability): + qubit_count = 1 + ascii_symbols = ["foo"] + SingleProbabilisticNoise(probability, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("probability", invalid_data_prob) +def test_invalid_data_single_prob_34(probability): + qubit_count = 1 + ascii_symbols = ["foo"] + SingleProbabilisticNoise_34(probability, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("probability", invalid_data_prob) +def test_invalid_data_single_prob_1516(probability): + qubit_count = 1 + ascii_symbols = ["foo"] + SingleProbabilisticNoise_1516(probability, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=TypeError) +@pytest.mark.parametrize("probability", invalid_data_prob_2) +def test_invalid_data_type_single_prob(probability): + qubit_count = 1 + ascii_symbols = ["foo"] + SingleProbabilisticNoise(probability, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=TypeError) +@pytest.mark.parametrize("probability", invalid_data_prob_2) +def test_invalid_data_type_single_prob_34(probability): + qubit_count = 1 + ascii_symbols = ["foo"] + SingleProbabilisticNoise_34(probability, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=TypeError) +@pytest.mark.parametrize("probability", invalid_data_prob_2) +def test_invalid_data_type_single_prob_1516(probability): + qubit_count = 1 + ascii_symbols = ["foo"] + SingleProbabilisticNoise_1516(probability, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("probX", invalid_data_prob) +def test_invalid_data_pauli_probX(probX): + qubit_count = 1 + ascii_symbols = ["foo"] + probY = 0.1 + probZ = 0.1 + PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("probY", invalid_data_prob) +def test_invalid_data_pauli_probY(probY): + qubit_count = 1 + ascii_symbols = ["foo"] + probX = 0.1 + probZ = 0.1 + PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("probZ", invalid_data_prob) +def test_invalid_data_pauli_probZ(probZ): + qubit_count = 1 + ascii_symbols = ["foo"] + probX = 0.1 + probY = 0.1 + PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=TypeError) +@pytest.mark.parametrize("probX", invalid_data_prob_2) +def test_invalid_data_type_pauli_probX(probX): + qubit_count = 1 + ascii_symbols = ["foo"] + probY = 0.1 + probZ = 0.1 + PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=TypeError) +@pytest.mark.parametrize("probY", invalid_data_prob_2) +def test_invalid_data_type_pauli_probY(probY): + qubit_count = 1 + ascii_symbols = ["foo"] + probX = 0.1 + probZ = 0.1 + PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=TypeError) +@pytest.mark.parametrize("probZ", invalid_data_prob_2) +def test_invalid_data_type_pauli_probZ(probZ): + qubit_count = 1 + ascii_symbols = ["foo"] + probX = 0.1 + probY = 0.1 + PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=ValueError) +def test_invalid_data_pauli_sum(): + qubit_count = 1 + ascii_symbols = ["foo"] + probX = 0.1 + probY = 0.1 + probZ = 0.9 + PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("gamma", invalid_data_prob_damping) +def test_invalid_data_damping_prob(gamma): + qubit_count = 1 + ascii_symbols = ["foo"] + DampingNoise(gamma, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("probability", invalid_data_prob_damping) +def test_invalid_data_generalized_amplitude_damping_prob(probability): + qubit_count = 1 + ascii_symbols = ["foo"] + gamma = 0.1 + GeneralizedAmplitudeDampingNoise(gamma, probability, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=TypeError) +@pytest.mark.parametrize("gamma", invalid_data_prob_damping_2) +def test_invalid_data_type_damping_prob(gamma): + qubit_count = 1 + ascii_symbols = ["foo"] + DampingNoise(gamma, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=TypeError) +@pytest.mark.parametrize("probability", invalid_data_prob_damping_2) +def test_invalid_data_type_generalized_amplitude_damping_prob(probability): + qubit_count = 1 + ascii_symbols = ["foo"] + gamma = 0.1 + GeneralizedAmplitudeDampingNoise(gamma, probability, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("gamma", invalid_data_prob_damping) +def test_invalid_data_generalized_amplitude_damping_gamma(gamma): + qubit_count = 1 + ascii_symbols = ["foo"] + probability = 0.1 + GeneralizedAmplitudeDampingNoise(gamma, probability, qubit_count, ascii_symbols) + + +def test_ascii_symbols(noise): + assert noise.ascii_symbols == ("foo",) + + +def test_is_operator(noise): + assert isinstance(noise, Operator) + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_to_ir_not_implemented_by_default(noise): + noise.to_ir(None) + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_to_matrix_not_implemented_by_default(noise): + noise.to_matrix(None) + + +def test_noise_str(noise): + expected = "{}('qubit_count': {})".format(noise.name, noise.qubit_count) + assert str(noise) == expected + + +def test_single_probability_noise_str(single_probability_noise): + expected = "{}('probability': {}, 'qubit_count': {})".format( + single_probability_noise.name, + single_probability_noise.probability, + single_probability_noise.qubit_count, + ) + assert str(single_probability_noise) == expected + + +def test_single_probability_noise_34_str(single_probability_noise_34): + expected = "{}('probability': {}, 'qubit_count': {})".format( + single_probability_noise_34.name, + single_probability_noise_34.probability, + single_probability_noise_34.qubit_count, + ) + assert str(single_probability_noise_34) == expected + + +def test_single_probability_noise_1516_str(single_probability_noise_1516): + expected = "{}('probability': {}, 'qubit_count': {})".format( + single_probability_noise_1516.name, + single_probability_noise_1516.probability, + single_probability_noise_1516.qubit_count, + ) + assert str(single_probability_noise_1516) == expected + + +def test_pauli_noise_str(pauli_noise): + expected = "{}('probX': {}, 'probY': {}, 'probZ': {}, 'qubit_count': {})".format( + pauli_noise.name, + pauli_noise.probX, + pauli_noise.probY, + pauli_noise.probZ, + pauli_noise.qubit_count, + ) + assert str(pauli_noise) == expected + + +def test_damping_noise_str(damping_noise): + expected = "{}('gamma': {}, 'qubit_count': {})".format( + damping_noise.name, + damping_noise.gamma, + damping_noise.qubit_count, + ) + assert str(damping_noise) == expected + + +def test_generalized_amplitude_damping_noise_str(generalized_amplitude_damping_noise): + expected = "{}('gamma': {}, 'probability': {}, 'qubit_count': {})".format( + generalized_amplitude_damping_noise.name, + generalized_amplitude_damping_noise.gamma, + generalized_amplitude_damping_noise.probability, + generalized_amplitude_damping_noise.qubit_count, + ) + assert str(generalized_amplitude_damping_noise) == expected + + +def test_equality(): + noise_1 = Noise(qubit_count=1, ascii_symbols=["foo"]) + noise_2 = Noise(qubit_count=1, ascii_symbols=["foo"]) + other_noise = Noise.AmplitudeDamping(gamma=0.5) + non_noise = "non noise" + + assert noise_1 == noise_2 + assert noise_1 is not noise_2 + assert noise_1 != other_noise + assert noise_1 != non_noise + + +def test_register_noise(): + class _FooNoise(Noise): + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["foo"]) + + Noise.register_noise(_FooNoise) + assert Noise._FooNoise().name == _FooNoise().name diff --git a/test/unit_tests/braket/circuits/test_noise_helpers.py b/test/unit_tests/braket/circuits/test_noise_helpers.py new file mode 100644 index 00000000..5fc0af8e --- /dev/null +++ b/test/unit_tests/braket/circuits/test_noise_helpers.py @@ -0,0 +1,697 @@ +# Copyright 2019-2019 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.circuit import Circuit +from braket.circuits.gate import Gate +from braket.circuits.instruction import Instruction +from braket.circuits.moments import Moments +from braket.circuits.noise import Noise +from braket.circuits.noise_helpers import apply_noise_to_gates, apply_noise_to_moments +from braket.circuits.qubit_set import QubitSet + +invalid_data_noise_type = [Gate.X(), None, 1.5] +invalid_data_target_gates_type = [([-1, "foo"]), ([1.5, None, -1]), "X", ([Gate.X, "CNot"])] +invalid_data_target_qubits_value = [-1] +invalid_data_target_qubits_type = [1.5, "foo", ["foo", 1]] +invalid_data_target_unitary_value = [np.array([[0, 0], [1, 0]])] +invalid_data_target_unitary_type = [[[0, 1], [1, 0]]] + + +@pytest.fixture +def circuit_2qubit(): + return Circuit().x(0).y(1).x(0).x(1).cnot(0, 1) + + +@pytest.fixture +def circuit_2qubit_with_unitary(): + return Circuit().x(0).y(1).x(0).x(1).cnot(0, 1).unitary([0], matrix=np.array([[0, 1], [1, 0]])) + + +@pytest.fixture +def circuit_2qubit_not_dense(): + # there are some qubits and some time that are not occupied by a gate + return Circuit().x(0).y(1).x(0).cnot(0, 1) + + +@pytest.fixture +def circuit_3qubit(): + return Circuit().x(0).y(1).cnot(0, 1).z(2).cz(2, 1).cnot(0, 2).cz(1, 2) + + +@pytest.fixture +def noise_1qubit(): + return Noise.BitFlip(probability=0.1) + + +@pytest.fixture +def noise_1qubit_2(): + return Noise.Depolarizing(probability=0.1) + + +@pytest.fixture +def noise_2qubit(): + E0 = np.sqrt(0.8) * np.eye(4) + E1 = np.sqrt(0.2) * np.kron(np.array([[0, 1], [1, 0]]), np.array([[0, 1], [1, 0]])) + return Noise.Kraus(matrices=[E0, E1]) + + +@pytest.mark.xfail(raises=IndexError) +def test_apply_gate_noise_to_empty_circuit(noise_1qubit): + Circuit().apply_gate_noise(noise_1qubit) + + +@pytest.mark.xfail(raises=IndexError) +def test_apply_initialization_noise_to_empty_circuit(noise_1qubit): + Circuit().apply_initialization_noise(noise_1qubit) + + +@pytest.mark.xfail(raises=IndexError) +def test_apply_readout_noise_to_empty_circuit(noise_1qubit): + Circuit().apply_readout_noise(noise_1qubit) + + +@pytest.mark.xfail(raises=ValueError) +def test_apply_gate_noise_with_target_gates_and_unitary(circuit_2qubit, noise_1qubit): + circuit_2qubit.apply_gate_noise( + noise_1qubit, target_gates=Gate.X, target_unitary=np.array([[0, 1], [1, 0]]) + ) + + +@pytest.mark.xfail(raises=IndexError) +def test_apply_gate_noise_to_outside_qubit_range(circuit_2qubit, noise_1qubit): + circuit_2qubit.apply_gate_noise(noise_1qubit, target_qubits=[0, 1, 2]) + + +@pytest.mark.xfail(raises=TypeError) +@pytest.mark.parametrize("noise", invalid_data_noise_type) +def test_apply_gate_noise_invalid_noise_type(circuit_2qubit, noise): + circuit_2qubit.apply_gate_noise(noise) + + +@pytest.mark.xfail(raises=TypeError) +@pytest.mark.parametrize("noise", invalid_data_noise_type) +def test_apply_initialization_noise_invalid_noise_type(circuit_2qubit, noise): + circuit_2qubit.apply_initialization_noise(noise) + + +@pytest.mark.xfail(raises=TypeError) +@pytest.mark.parametrize("noise", invalid_data_noise_type) +def test_apply_readout_noise_invalid_noise_type(circuit_2qubit, noise): + circuit_2qubit.apply_readout_noise(noise) + + +@pytest.mark.xfail(raises=TypeError) +@pytest.mark.parametrize("target_gates", invalid_data_target_gates_type) +def test_apply_gate_noise_invalid_target_gates_type(circuit_2qubit, noise_1qubit, target_gates): + circuit_2qubit.apply_gate_noise(noise_1qubit, target_gates=target_gates) + + +@pytest.mark.xfail(raises=TypeError) +@pytest.mark.parametrize("target_unitary", invalid_data_target_unitary_type) +def test_apply_gate_noise_invalid_target_unitary_type(circuit_2qubit, noise_1qubit, target_unitary): + circuit_2qubit.apply_gate_noise(noise_1qubit, target_unitary=target_unitary) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("target_unitary", invalid_data_target_unitary_value) +def test_apply_gate_noise_invalid_target_unitary_value( + circuit_2qubit, noise_1qubit, target_unitary +): + circuit_2qubit.apply_gate_noise(noise_1qubit, target_unitary=target_unitary) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_value) +def test_apply_gate_noise_invalid_target_qubits_value(circuit_2qubit, noise_1qubit, target_qubits): + circuit_2qubit.apply_gate_noise(noise_1qubit, target_qubits=target_qubits, target_gates=[]) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_value) +def test_apply_initialization_noise_invalid_target_qubits_value( + circuit_2qubit, noise_1qubit, target_qubits +): + circuit_2qubit.apply_initialization_noise(noise_1qubit, target_qubits=target_qubits) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_value) +def test_apply_readout_noise_invalid_target_qubits_value( + circuit_2qubit, noise_1qubit, target_qubits +): + circuit_2qubit.apply_readout_noise(noise_1qubit, target_qubits=target_qubits) + + +@pytest.mark.xfail(raises=TypeError) +@pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_type) +def test_apply_gate_noise_invalid_target_qubits_type(circuit_2qubit, noise_1qubit, target_qubits): + circuit_2qubit.apply_gate_noise(noise_1qubit, target_qubits=target_qubits) + + +@pytest.mark.xfail(raises=TypeError) +@pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_type) +def test_apply_initialization_noise_invalid_target_qubits_type( + circuit_2qubit, noise_1qubit, target_qubits +): + circuit_2qubit.apply_initialization_noise(noise_1qubit, target_qubits=target_qubits) + + +@pytest.mark.xfail(raises=TypeError) +@pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_type) +def test_apply_readout_noise_invalid_target_qubits_type( + circuit_2qubit, noise_1qubit, target_qubits +): + circuit_2qubit.apply_readout_noise(noise_1qubit, target_qubits=target_qubits) + + +@pytest.mark.xfail(raises=ValueError) +def test_apply_gate_noise_mismatch_qubit_count_with_target_gates(noise_2qubit): + circ = Circuit().cswap(0, 1, 2) + circ.apply_gate_noise(noise_2qubit, target_gates=Gate.CSwap) + + +@pytest.mark.xfail(raises=ValueError) +def test_apply_initialization_noise_mismatch_qubit_count_with_target_qubits(noise_2qubit): + circ = Circuit().cswap(0, 1, 2) + circ.apply_initialization_noise(noise_2qubit, target_qubits=[0, 1, 2]) + + +@pytest.mark.xfail(raises=ValueError) +def test_apply_readout_noise_mismatch_qubit_count_with_target_qubits(noise_2qubit): + circ = Circuit().cswap(0, 1, 2) + circ.apply_readout_noise(noise_2qubit, target_qubits=[0, 1, 2]) + + +def test_apply_gate_noise_1QubitNoise_1(circuit_2qubit, noise_1qubit): + circ = circuit_2qubit.apply_gate_noise( + noise_1qubit, + target_gates=[Gate.X, Gate.Z], + target_qubits=[0, 1], + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(noise_1qubit, 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + ) + + assert circ == expected + + +def test_apply_gate_noise_1QubitNoise2_1(circuit_2qubit, noise_2qubit): + circ = circuit_2qubit.apply_gate_noise( + noise_2qubit, + target_gates=[Gate.CNot], + target_qubits=[0, 1], + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(noise_2qubit, [0, 1])) + ) + + assert circ == expected + + +def test_apply_gate_noise_1QubitNoise_1_unitary(circuit_2qubit_with_unitary, noise_1qubit): + circ = circuit_2qubit_with_unitary.apply_gate_noise( + noise_1qubit, + target_unitary=np.array([[0, 1], [1, 0]]), + target_qubits=[0, 1], + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Gate.Unitary(np.array([[0, 1], [1, 0]]), "U"), 0)) + .add_instruction(Instruction(noise_1qubit, 0)) + ) + + assert circ == expected + + +def test_apply_noise_to_gates_1QubitNoise_1(circuit_2qubit, noise_1qubit): + circ = apply_noise_to_gates( + circuit_2qubit, + [noise_1qubit], + target_gates=[Gate.X], + target_qubits=QubitSet([0, 1]), + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(noise_1qubit, 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + ) + + assert circ == expected + + +def test_apply_noise_to_gates_1QubitNoise_2(circuit_2qubit, noise_1qubit): + circ = apply_noise_to_gates( + circuit_2qubit, + [noise_1qubit], + target_gates=[Gate.X], + target_qubits=QubitSet(0), + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + ) + + assert circ == expected + + +def test_apply_noise_to_gates_2QubitNoise_1(circuit_3qubit, noise_2qubit): + circ = apply_noise_to_gates( + circuit_3qubit, + [noise_2qubit], + target_gates=[Gate.CNot], + target_qubits=QubitSet([0, 1, 2]), + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(noise_2qubit, [0, 1])) + .add_instruction(Instruction(Gate.Z(), 2)) + .add_instruction(Instruction(Gate.CZ(), [2, 1])) + .add_instruction(Instruction(Gate.CNot(), [0, 2])) + .add_instruction(Instruction(noise_2qubit, [0, 2])) + .add_instruction(Instruction(Gate.CZ(), [1, 2])) + ) + + assert circ == expected + + +def test_apply_noise_to_gates_2QubitNoise_2( + circuit_3qubit, noise_2qubit, noise_1qubit, noise_1qubit_2 +): + circ = apply_noise_to_gates( + circuit_3qubit, + [noise_1qubit, noise_2qubit, noise_1qubit_2], + target_gates=[Gate.CZ], + target_qubits=QubitSet([1, 2]), + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Gate.Z(), 2)) + .add_instruction(Instruction(Gate.CZ(), [2, 1])) + .add_instruction(Instruction(noise_1qubit, 1)) + .add_instruction(Instruction(noise_1qubit, 2)) + .add_instruction(Instruction(noise_2qubit, [2, 1])) + .add_instruction(Instruction(noise_1qubit_2, 1)) + .add_instruction(Instruction(noise_1qubit_2, 2)) + .add_instruction(Instruction(Gate.CNot(), [0, 2])) + .add_instruction(Instruction(Gate.CZ(), [1, 2])) + .add_instruction(Instruction(noise_1qubit, 1)) + .add_instruction(Instruction(noise_1qubit, 2)) + .add_instruction(Instruction(noise_2qubit, [1, 2])) + .add_instruction(Instruction(noise_1qubit_2, 1)) + .add_instruction(Instruction(noise_1qubit_2, 2)) + ) + + assert circ == expected + + +def test_apply_noise_to_gates_2QubitNoise_3( + circuit_3qubit, noise_2qubit, noise_1qubit, noise_1qubit_2 +): + circ = apply_noise_to_gates( + circuit_3qubit, + [noise_1qubit, noise_2qubit, noise_1qubit_2], + target_gates=[Gate.Z], + target_qubits=QubitSet([1, 2]), + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Gate.Z(), 2)) + .add_instruction(Instruction(noise_1qubit, 2)) + .add_instruction(Instruction(noise_1qubit_2, 2)) + .add_instruction(Instruction(Gate.CZ(), [2, 1])) + .add_instruction(Instruction(Gate.CNot(), [0, 2])) + .add_instruction(Instruction(Gate.CZ(), [1, 2])) + ) + + assert circ == expected + + +def test_apply_initialization_noise_1QubitNoise_1(circuit_2qubit, noise_1qubit): + circ = circuit_2qubit.apply_initialization_noise( + [noise_1qubit], + target_qubits=[0, 1], + ) + + expected = ( + Circuit() + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(noise_1qubit, 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + ) + + assert circ == expected + + +def test_apply_noise_to_moments_initialization_1QubitNoise_1(circuit_2qubit, noise_1qubit): + circ = apply_noise_to_moments( + circuit_2qubit, + [noise_1qubit], + target_qubits=QubitSet([0, 1]), + position="initialization", + ) + + expected = ( + Circuit() + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(noise_1qubit, 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + ) + + assert circ == expected + + +def test_apply_noise_to_moments_initialization_2QubitNoise_1(circuit_2qubit, noise_2qubit): + circ = apply_noise_to_moments( + circuit_2qubit, + [noise_2qubit], + target_qubits=QubitSet([0, 1]), + position="initialization", + ) + + expected = ( + Circuit() + .add_instruction(Instruction(noise_2qubit, [0, 1])) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + ) + + assert circ == expected + + +def test_apply_noise_to_moments_initialization_2QubitNoise_2( + circuit_2qubit, noise_2qubit, noise_1qubit, noise_1qubit_2 +): + circ = apply_noise_to_moments( + circuit_2qubit, + [noise_1qubit, noise_2qubit, noise_1qubit_2], + target_qubits=QubitSet([0, 1]), + position="initialization", + ) + + expected = ( + Circuit() + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(noise_1qubit, 1)) + .add_instruction(Instruction(noise_2qubit, [0, 1])) + .add_instruction(Instruction(noise_1qubit_2, 0)) + .add_instruction(Instruction(noise_1qubit_2, 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + ) + + assert circ == expected + + +def test_apply_readout_noise_1QubitNoise_1(circuit_2qubit, noise_1qubit): + circ = circuit_2qubit.apply_readout_noise( + [noise_1qubit], + target_qubits=[0, 1], + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(noise_1qubit, 1)) + ) + + assert circ == expected + + +def test_noise_not_applied_1QubitNoise_1(circuit_2qubit, noise_2qubit): + circ = circuit_2qubit.apply_gate_noise( + [noise_2qubit], + target_qubits=[1], + target_gates=[], + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + ) + + assert circ == expected + + +def test_apply_multipe_noise_1QubitNoise_1(circuit_2qubit, noise_1qubit, noise_1qubit_2): + circ = circuit_2qubit.apply_gate_noise(noise_1qubit).apply_readout_noise( + noise_1qubit_2, + target_qubits=[0, 1], + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(noise_1qubit, 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(noise_1qubit, 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(noise_1qubit, 1)) + .add_instruction(Instruction(noise_1qubit_2, 0)) + .add_instruction(Instruction(noise_1qubit_2, 1)) + ) + + assert circ == expected + + +def test_apply_multipe_noise_1QubitNoise_2(circuit_2qubit, noise_1qubit, noise_1qubit_2): + circ = circuit_2qubit.apply_gate_noise(noise_1qubit, target_gates=[Gate.X],).apply_gate_noise( + noise_1qubit_2, + target_qubits=[0], + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(noise_1qubit_2, 0)) + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(noise_1qubit_2, 0)) + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(noise_1qubit, 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(noise_1qubit_2, 0)) + ) + + assert circ == expected + + +def test_apply_noise_to_moments_readout_1QubitNoise_1(circuit_2qubit, noise_1qubit): + circ = apply_noise_to_moments( + circuit_2qubit, + [noise_1qubit], + target_qubits=QubitSet([0, 1]), + position="readout", + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(noise_1qubit, 1)) + ) + + assert circ == expected + + +def test_apply_noise_to_moments_readout_1QubitNoise_3(circuit_2qubit, noise_1qubit, noise_1qubit_2): + circ = apply_noise_to_moments( + circuit_2qubit, + noise=[noise_1qubit, noise_1qubit_2], + target_qubits=QubitSet([0, 1]), + position="readout", + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(noise_1qubit, 1)) + .add_instruction(Instruction(noise_1qubit_2, 0)) + .add_instruction(Instruction(noise_1qubit_2, 1)) + ) + + assert circ == expected + + +def test_apply_noise_to_moments_readout_2QubitNoise_1(circuit_2qubit, noise_2qubit): + circ = apply_noise_to_moments( + circuit_2qubit, + [noise_2qubit], + target_qubits=QubitSet([0, 1]), + position="readout", + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(noise_2qubit, [0, 1])) + ) + + assert circ == expected + + +def test_apply_noise_to_moments_initialization_1QubitNoise_2(circuit_2qubit, noise_1qubit): + circ = apply_noise_to_moments( + circuit_2qubit, + [noise_1qubit], + target_qubits=QubitSet(1), + position="initialization", + ) + + expected = ( + Circuit() + .add_instruction(Instruction(noise_1qubit, 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + ) + + assert circ == expected + + +def test_apply_noise_to_moments_readout_1QubitNoise_2(circuit_2qubit, noise_1qubit): + circ = apply_noise_to_moments( + circuit_2qubit, + [noise_1qubit], + target_qubits=QubitSet(1), + position="readout", + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(noise_1qubit, 1)) + ) + + assert circ == expected + + +def test_apply_noise_to_gates_1QubitNoise_not_dense(circuit_2qubit_not_dense, noise_1qubit): + circ = apply_noise_to_gates( + circuit_2qubit_not_dense, + [noise_1qubit], + target_qubits=QubitSet([0, 1]), + target_gates=None, + ) + + expected_moments = Moments() + expected_moments._add(Instruction(Gate.X(), 0), noise_index=1) + expected_moments.add_noise(Instruction(noise_1qubit, 0), "gate_noise", 1) + expected_moments._add(Instruction(Gate.Y(), 1), noise_index=1) + expected_moments.add_noise(Instruction(noise_1qubit, 1), "gate_noise", 1) + expected_moments._add(Instruction(Gate.X(), 0), noise_index=1) + expected_moments.add_noise(Instruction(noise_1qubit, 0), "gate_noise", 1) + expected_moments._add(Instruction(Gate.CNot(), [0, 1]), noise_index=2) + expected_moments.add_noise(Instruction(noise_1qubit, 0), "gate_noise", 1) + expected_moments.add_noise(Instruction(noise_1qubit, 1), "gate_noise", 2) + + assert circ.moments == expected_moments diff --git a/test/unit_tests/braket/circuits/test_noises.py b/test/unit_tests/braket/circuits/test_noises.py new file mode 100644 index 00000000..765aad8c --- /dev/null +++ b/test/unit_tests/braket/circuits/test_noises.py @@ -0,0 +1,388 @@ +# Copyright 2019-2019 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 + +import braket.ir.jaqcd as ir +from braket.circuits import Circuit, Instruction, Noise, QubitSet +from braket.ir.jaqcd.shared_models import ( + DampingProbability, + DampingSingleProbability, + DoubleControl, + DoubleTarget, + MultiTarget, + SingleControl, + SingleProbability, + SingleProbability_34, + SingleProbability_1516, + SingleTarget, + TripleProbability, + TwoDimensionalMatrixList, +) + +testdata = [ + (Noise.BitFlip, "bit_flip", ir.BitFlip, [SingleTarget, SingleProbability], {}), + (Noise.PhaseFlip, "phase_flip", ir.PhaseFlip, [SingleTarget, SingleProbability], {}), + (Noise.Depolarizing, "depolarizing", ir.Depolarizing, [SingleTarget, SingleProbability_34], {}), + ( + Noise.AmplitudeDamping, + "amplitude_damping", + ir.AmplitudeDamping, + [SingleTarget, DampingProbability], + {}, + ), + ( + Noise.GeneralizedAmplitudeDamping, + "generalized_amplitude_damping", + ir.GeneralizedAmplitudeDamping, + [SingleTarget, DampingProbability, DampingSingleProbability], + {}, + ), + ( + Noise.PhaseDamping, + "phase_damping", + ir.PhaseDamping, + [SingleTarget, DampingProbability], + {}, + ), + ( + Noise.TwoQubitDepolarizing, + "two_qubit_depolarizing", + ir.TwoQubitDepolarizing, + [DoubleTarget, SingleProbability_1516], + {}, + ), + ( + Noise.TwoQubitDephasing, + "two_qubit_dephasing", + ir.TwoQubitDephasing, + [DoubleTarget, SingleProbability_34], + {}, + ), + ( + Noise.PauliChannel, + "pauli_channel", + ir.PauliChannel, + [SingleTarget, TripleProbability], + {}, + ), + ( + Noise.Kraus, + "kraus", + ir.Kraus, + [TwoDimensionalMatrixList, MultiTarget], + {"input_type": complex}, + ), + ( + Noise.Kraus, + "kraus", + ir.Kraus, + [TwoDimensionalMatrixList, MultiTarget], + {"input_type": float}, + ), + ( + Noise.Kraus, + "kraus", + ir.Kraus, + [TwoDimensionalMatrixList, MultiTarget], + {"input_type": int}, + ), +] + + +invalid_kraus_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], [3, 4, 5], [6, 7, 8]])]), + ([np.array([[0, 1], [1, 1]])]), + ([np.array([[1, 0], [0, 1]]), np.array([[0, 1], [1, 0]])]), + ([np.array([[1, 0], [0, 1]]) * np.sqrt(0.5), np.eye(4) * np.sqrt(0.5)]), + ([np.eye(8)]), + ([np.eye(2), np.eye(2), np.eye(2), np.eye(2), np.eye(2)]), +] + + +def single_target_valid_input(**kwargs): + return {"target": 2} + + +def double_target_valid_ir_input(**kwargs): + return {"targets": [2, 3]} + + +def double_target_valid_input(**kwargs): + return {"target1": 2, "target2": 3} + + +def single_probability_valid_input(**kwargs): + return {"probability": 0.1234} + + +def single_probability_34_valid_input(**kwargs): + return {"probability": 0.1234} + + +def single_probability_1516_valid_input(**kwargs): + return {"probability": 0.1234} + + +def damping_single_probability_valid_input(**kwargs): + return {"probability": 0.1234} + + +def damping_probability_valid_input(**kwargs): + return {"gamma": 0.1234} + + +def triple_probability_valid_input(**kwargs): + return {"probX": 0.1234, "probY": 0.1324, "probZ": 0.1423} + + +def single_control_valid_input(**kwargs): + return {"control": 0} + + +def double_control_valid_ir_input(**kwargs): + return {"controls": [0, 1]} + + +def double_control_valid_input(**kwargs): + return {"control1": 0, "control2": 1} + + +def multi_target_valid_input(**kwargs): + return {"targets": [5]} + + +def two_dimensional_matrix_list_valid_ir_input(**kwargs): + return {"matrices": [[[[0, 0], [1, 0]], [[1, 0], [0, 0]]]]} + + +def two_dimensional_matrix_list_valid_input(**kwargs): + input_type = kwargs.get("input_type") + return { + "matrices": [np.array([[input_type(0), input_type(1)], [input_type(1), input_type(0)]])] + } + + +valid_ir_switcher = { + "SingleTarget": single_target_valid_input, + "DoubleTarget": double_target_valid_ir_input, + "SingleProbability": single_probability_valid_input, + "SingleProbability_34": single_probability_34_valid_input, + "SingleProbability_1516": single_probability_1516_valid_input, + "DampingProbability": damping_probability_valid_input, + "DampingSingleProbability": damping_single_probability_valid_input, + "TripleProbability": triple_probability_valid_input, + "SingleControl": single_control_valid_input, + "DoubleControl": double_control_valid_ir_input, + "MultiTarget": multi_target_valid_input, + "TwoDimensionalMatrixList": two_dimensional_matrix_list_valid_ir_input, +} + + +valid_subroutine_switcher = dict( + valid_ir_switcher, + **{ + "TwoDimensionalMatrixList": two_dimensional_matrix_list_valid_input, + "DoubleTarget": double_target_valid_input, + "DoubleControl": double_control_valid_input, + } +) + + +def create_valid_ir_input(irsubclasses): + input = {} + for subclass in irsubclasses: + input.update(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) + ) + 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: + if subclass == SingleTarget: + qubit_set.extend(list(single_target_valid_input().values())) + elif subclass == DoubleTarget: + qubit_set.extend(list(double_target_valid_ir_input().values())) + elif subclass == MultiTarget: + qubit_set.extend(list(multi_target_valid_input().values())) + elif subclass == SingleControl: + 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 + for i in [ + SingleProbability, + SingleProbability_34, + SingleProbability_1516, + DampingSingleProbability, + DampingProbability, + TripleProbability, + TwoDimensionalMatrixList, + ] + ): + pass + else: + raise ValueError("Invalid subclass") + 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()) + if SingleProbability_34 in irsubclasses: + input.update(single_probability_34_valid_input()) + if SingleProbability_1516 in irsubclasses: + input.update(single_probability_1516_valid_input()) + if DampingSingleProbability in irsubclasses: + input.update(damping_single_probability_valid_input()) + if DampingProbability in irsubclasses: + input.update(damping_probability_valid_input()) + if TripleProbability in irsubclasses: + input.update(triple_probability_valid_input()) + if TwoDimensionalMatrixList in irsubclasses: + input.update(two_dimensional_matrix_list_valid_input(**kwargs)) + return input + + +def create_valid_instruction_input(testclass, irsubclasses, **kwargs): + input = create_valid_target_input(irsubclasses) + input["operator"] = testclass(**create_valid_noise_class_input(irsubclasses, **kwargs)) + return input + + +def calculate_qubit_count(irsubclasses): + qubit_count = 0 + for subclass in irsubclasses: + if subclass == SingleTarget: + qubit_count += 1 + elif subclass == DoubleTarget: + qubit_count += 2 + elif subclass == SingleControl: + qubit_count += 1 + elif subclass == DoubleControl: + qubit_count += 2 + elif subclass == MultiTarget: + qubit_count += 3 + elif any( + subclass == i + for i in [ + SingleProbability, + SingleProbability_34, + SingleProbability_1516, + DampingSingleProbability, + DampingProbability, + TripleProbability, + TwoDimensionalMatrixList, + ] + ): + pass + else: + raise ValueError("Invalid subclass") + return qubit_count + + +@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) +def test_ir_noise_level(testclass, subroutine_name, irclass, irsubclasses, kwargs): + expected = irclass(**create_valid_ir_input(irsubclasses)) + actual = testclass(**create_valid_noise_class_input(irsubclasses, **kwargs)).to_ir( + **create_valid_target_input(irsubclasses) + ) + assert actual == expected + + +@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) +def test_ir_instruction_level(testclass, subroutine_name, irclass, irsubclasses, kwargs): + expected = irclass(**create_valid_ir_input(irsubclasses)) + instruction = Instruction(**create_valid_instruction_input(testclass, irsubclasses, **kwargs)) + actual = instruction.to_ir() + assert actual == expected + + +@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) +def test_noise_subroutine(testclass, subroutine_name, irclass, irsubclasses, kwargs): + qubit_count = calculate_qubit_count(irsubclasses) + subroutine = getattr(Circuit(), subroutine_name) + assert subroutine(**create_valid_subroutine_input(irsubclasses, **kwargs)) == Circuit( + Instruction(**create_valid_instruction_input(testclass, irsubclasses, **kwargs)) + ) + 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, + ) + ) + subroutine = getattr(Circuit(), subroutine_name) + subroutine_input = {"target": multi_targets} + if SingleProbability in irsubclasses: + subroutine_input.update(single_probability_valid_input()) + if SingleProbability_34 in irsubclasses: + subroutine_input.update(single_probability_34_valid_input()) + if SingleProbability_1516 in irsubclasses: + subroutine_input.update(single_probability_1516_valid_input()) + if DampingSingleProbability in irsubclasses: + subroutine_input.update(damping_single_probability_valid_input()) + if DampingProbability in irsubclasses: + subroutine_input.update(damping_probability_valid_input()) + if TripleProbability in irsubclasses: + subroutine_input.update(triple_probability_valid_input()) + + circuit1 = subroutine(**subroutine_input) + circuit2 = Circuit(instruction_list) + assert circuit1 == circuit2 + + +@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) +def test_noise_to_matrix(testclass, subroutine_name, irclass, irsubclasses, kwargs): + noise1 = testclass(**create_valid_noise_class_input(irsubclasses, **kwargs)) + noise2 = testclass(**create_valid_noise_class_input(irsubclasses, **kwargs)) + assert all(isinstance(matrix, np.ndarray) for matrix in noise1.to_matrix()) + assert all(np.allclose(m1, m2) for m1, m2 in zip(noise1.to_matrix(), noise2.to_matrix())) + + +# Additional Unitary noise tests + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("matrices", invalid_kraus_matrices) +def test_kraus_invalid_matrix(matrices): + Noise.Kraus(matrices=matrices) + + +@pytest.mark.xfail(raises=ValueError) +def test_kraus_matrix_target_size_mismatch(): + Circuit().kraus( + matrices=[np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])], targets=[0] + ) diff --git a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py index a4464648..7cf798a2 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py @@ -1,3 +1,16 @@ +# Copyright 2019-2019 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 functools import numpy as np @@ -5,6 +18,7 @@ from braket.circuits.quantum_operator_helpers import ( get_pauli_eigenvalues, + is_cptp, is_hermitian, is_square_matrix, is_unitary, @@ -13,6 +27,11 @@ valid_unitary_hermitian_matrix = np.array([[0, 1], [1, 0]]) +valid_CPTP_matrices = [ + np.array([[0, 1], [1, 0]]) / np.sqrt(2), + np.array([[0, 1], [1, 0]]) / np.sqrt(2), +] + invalid_dimension_matrices = [ (np.array([[1]])), (np.array([1])), @@ -26,6 +45,8 @@ invalid_hermitian_matrices_false = [(np.array([[1, 0], [0, 1j]])), (np.array([[1, 2], [3, 4]]))] +invalid_CPTP_matrices_false = [np.array([[1, 0], [0, 1]]), np.array([[0, 1], [1, 0]])] + invalid_matrix_type_error = np.array([[0, 1], ["a", 0]]) z_matrix = np.array([[1, 0], [0, -1]]) @@ -43,6 +64,10 @@ def test_is_hermitian_true(): assert is_hermitian(valid_unitary_hermitian_matrix) +def test_is_cptp_true(): + assert is_cptp(valid_CPTP_matrices) + + def test_is_square_matrix(): assert is_square_matrix(valid_unitary_hermitian_matrix) @@ -63,6 +88,10 @@ def test_is_hermitian_false(matrix): assert not is_hermitian(matrix) +def test_is_cptp_false(): + assert not is_cptp(invalid_CPTP_matrices_false) + + @pytest.mark.xfail(raises=Exception) def test_is_hermitian_exception(): is_hermitian(invalid_matrix_type_error) @@ -73,6 +102,11 @@ def test_is_unitary_exception(): is_unitary(invalid_matrix_type_error) +@pytest.mark.xfail(raises=Exception) +def test_is_cptp_exception(): + is_cptp([invalid_matrix_type_error]) + + def test_get_pauli_eigenvalues_correct_eigenvalues_one_qubit(): """Test the get_pauli_eigenvalues function for one qubit""" assert np.array_equal(get_pauli_eigenvalues(1), np.diag(z_matrix)) diff --git a/test/unit_tests/braket/circuits/test_result_type.py b/test/unit_tests/braket/circuits/test_result_type.py index 14351a66..b0fedebb 100644 --- a/test/unit_tests/braket/circuits/test_result_type.py +++ b/test/unit_tests/braket/circuits/test_result_type.py @@ -47,7 +47,7 @@ def test_ascii_symbol(): assert result_type.ascii_symbols == ascii_symbols -def test_equality(): +def test_equality_statevector(): result1 = ResultType.StateVector() result2 = ResultType.StateVector() result3 = ResultType.Probability([1]) @@ -57,6 +57,16 @@ def test_equality(): assert result1 != result4 +def test_equality_densitymatrix(): + result1 = ResultType.DensityMatrix() + result2 = ResultType.DensityMatrix() + result3 = ResultType.StateVector() + result4 = "foo" + assert result1 == result2 + assert result1 != result3 + assert result1 != result4 + + @pytest.mark.xfail(raises=AttributeError) def test_ascii_symbol_setter(result_type): result_type.ascii_symbols = ["bar"] diff --git a/test/unit_tests/braket/circuits/test_result_types.py b/test/unit_tests/braket/circuits/test_result_types.py index f523261e..e7672e69 100644 --- a/test/unit_tests/braket/circuits/test_result_types.py +++ b/test/unit_tests/braket/circuits/test_result_types.py @@ -19,6 +19,14 @@ testdata = [ (ResultType.StateVector, "state_vector", ir.StateVector, {}, {}), + (ResultType.DensityMatrix, "density_matrix", ir.DensityMatrix, {}, {}), + ( + ResultType.DensityMatrix, + "density_matrix", + ir.DensityMatrix, + {"target": [0, 1]}, + {"targets": [0, 1]}, + ), (ResultType.Amplitude, "amplitude", ir.Amplitude, {"state": ["0"]}, {"states": ["0"]}), ( ResultType.Probability, diff --git a/tox.ini b/tox.ini index 03065663..1ab05fcc 100644 --- a/tox.ini +++ b/tox.ini @@ -65,6 +65,7 @@ commands = [testenv:docs] basepython = python3 deps = + {[test-deps]deps} sphinx sphinx-rtd-theme sphinxcontrib-apidoc @@ -86,6 +87,6 @@ commands = [test-deps] deps = -# If you need to test on a certain branch, add @ after .git + # If you need to test on a certain branch, add @ after .git git+https://github.com/aws/amazon-braket-schemas-python.git git+https://github.com/aws/amazon-braket-default-simulator-python.git From b30c871731e5b678f37e1040b39c550577580d10 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 24 May 2021 19:45:01 +0000 Subject: [PATCH 0328/1165] prepare release v1.6.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 48d81546..173ecedc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.6.0 (2021-05-24) + +### Features + + * Noise operators + +### Testing and Release Infrastructure + + * Use GitHub source for tox tests + ## v1.5.16 (2021-05-05) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 3516cd68..2cd4dfd7 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.5.17.dev0" +__version__ = "1.6.0" From 6a5a49c787ab81237f99c1b0b822eb84d6ce0506 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 24 May 2021 19:45:01 +0000 Subject: [PATCH 0329/1165] update development version to v1.6.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 2cd4dfd7..57ece9b5 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.6.0" +__version__ = "1.6.1.dev0" From 6d0a1da6c445f512c411767178c578d997709690 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Mon, 24 May 2021 13:49:10 -0700 Subject: [PATCH 0330/1165] documentation: Add reference to the noise simulation example notebook (#245) * documentation: Add noise simulation example notebook * Correct reference to Amazon Braket simulator --- doc/examples-braket-features.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/examples-braket-features.rst b/doc/examples-braket-features.rst index bb6899ca..63743214 100644 --- a/doc/examples-braket-features.rst +++ b/doc/examples-braket-features.rst @@ -36,3 +36,12 @@ and how to gain access to their properties. 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. + + +************************** +`Simulating noise on Amazon Braket `_ +************************** + +This notebook provides a detailed overview of noise simulation on Amazon Braket. +You will learn how to define noise channels, apply noise to new or existing circuits, and run those circuits +on the Amazon Braket noise simulators. From 4a546f423c70f92056947a9d9364d2b5c9352d35 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 24 May 2021 21:05:18 +0000 Subject: [PATCH 0331/1165] prepare release v1.6.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 173ecedc..e2d9d545 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.6.0.post0 (2021-05-24) + +### Documentation Changes + + * Add reference to the noise simulation example notebook + ## v1.6.0 (2021-05-24) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 57ece9b5..d308192c 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.6.1.dev0" +__version__ = "1.6.0.post0" From da3f15044a39cb3da50f1e97a67c050a311b359d Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 24 May 2021 21:05:18 +0000 Subject: [PATCH 0332/1165] update development version to v1.6.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 d308192c..57ece9b5 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.6.0.post0" +__version__ = "1.6.1.dev0" From b06e04e03c8889d7da7e36c06fcb57c9749965a9 Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Tue, 25 May 2021 10:27:45 -0700 Subject: [PATCH 0333/1165] fix: copy the boto3 session using the default botocore session (#246) * fix: copy the boto3 session using the default botocore session, unless the customer created the session using explicit credentials. --- src/braket/aws/aws_device.py | 15 ++++---- test/unit_tests/braket/aws/test_aws_device.py | 36 ++++++++++++++++++- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index e14d8918..f44ea4f7 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -325,12 +325,15 @@ def _copy_aws_session( session_region = aws_session.boto_session.region_name new_region = region or session_region creds = aws_session.boto_session.get_credentials() - boto_session = boto3.Session( - aws_access_key_id=creds.access_key, - aws_secret_access_key=creds.secret_key, - aws_session_token=creds.token, - region_name=new_region, - ) + if creds.method == "explicit": + boto_session = boto3.Session( + aws_access_key_id=creds.access_key, + aws_secret_access_key=creds.secret_key, + aws_session_token=creds.token, + region_name=new_region, + ) + else: + boto_session = boto3.Session(region_name=new_region) return AwsSession(boto_session=boto_session, config=config) def __repr__(self): diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 7c1a5d4c..4bbc143f 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -225,7 +225,7 @@ def boto_session(): @pytest.fixture -def aws_session(): +def aws_explicit_session(): _boto_session = Mock() _boto_session.region_name = RIGETTI_REGION @@ -233,6 +233,21 @@ def aws_session(): creds.access_key = "access key" creds.secret_key = "secret key" creds.token = "token" + creds.method = "explicit" + _boto_session.get_credentials.return_value = creds + + _aws_session = Mock() + _aws_session.boto_session = _boto_session + return _aws_session + + +@pytest.fixture +def aws_session(): + _boto_session = Mock() + _boto_session.region_name = RIGETTI_REGION + + creds = Mock() + creds.method = "other" _boto_session.get_credentials.return_value = creds _aws_session = Mock() @@ -275,6 +290,25 @@ def test_device_simulator_no_aws_session(aws_session_init, aws_session): aws_session.get_device.assert_called_with(arn) +@patch("boto3.Session") +def test_copy_session(boto_session_init, aws_session): + boto_session_init.return_value = Mock() + AwsDevice._copy_aws_session(aws_session, RIGETTI_REGION) + boto_session_init.assert_called_with(region_name=RIGETTI_REGION) + + +@patch("boto3.Session") +def test_copy_explicit_session(boto_session_init, aws_explicit_session): + boto_session_init.return_value = Mock() + AwsDevice._copy_aws_session(aws_explicit_session, RIGETTI_REGION) + boto_session_init.assert_called_with( + aws_access_key_id="access key", + aws_secret_access_key="secret key", + aws_session_token="token", + region_name=RIGETTI_REGION, + ) + + @patch("braket.aws.aws_device.AwsDevice._copy_aws_session") @patch("braket.aws.aws_device.AwsSession") @pytest.mark.parametrize( From e84e801269564cbc2c783bc5646890c076196acf Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 25 May 2021 18:14:51 +0000 Subject: [PATCH 0334/1165] prepare release v1.6.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 e2d9d545..f4a508e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.6.1 (2021-05-25) + +### Bug Fixes and Other Changes + + * copy the boto3 session using the default botocore session + ## v1.6.0.post0 (2021-05-24) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 57ece9b5..e504fad6 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.6.1.dev0" +__version__ = "1.6.1" From 27351d49518be22ecb46a64630413ea5cf9fc8fe Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 25 May 2021 18:14:51 +0000 Subject: [PATCH 0335/1165] update development version to v1.6.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 e504fad6..a93a5e4b 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.6.1" +__version__ = "1.6.2.dev0" From 2774c4ed8c028e3bbd94e3a7bf69e3b8f73eda86 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Thu, 27 May 2021 12:21:07 -0700 Subject: [PATCH 0336/1165] exclude null values from device parameters for annealing tasks (#247) --- src/braket/aws/aws_quantum_task.py | 4 +++- test/unit_tests/braket/aws/test_aws_quantum_task.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index fd0f148c..dbda7fdd 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -443,7 +443,9 @@ def _( create_task_kwargs.update( { "action": problem.to_ir().json(), - "deviceParameters": DwaveDeviceParameters.parse_obj(device_parameters).json(), + "deviceParameters": DwaveDeviceParameters.parse_obj(device_parameters).json( + exclude_none=True + ), } ) 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 75f1efb7..229d68d3 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -501,7 +501,7 @@ def _assert_create_quantum_task_called_with( "outputS3Bucket": s3_results_prefix[0], "outputS3KeyPrefix": s3_results_prefix[1], "action": task_description.to_ir().json(), - "deviceParameters": device_parameters.json(), + "deviceParameters": device_parameters.json(exclude_none=True), "shots": shots, } if tags is not None: From bb55cae2ab1f8a137381696bbffc3c124d3fda4d Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 28 May 2021 20:39:23 +0000 Subject: [PATCH 0337/1165] prepare release v1.6.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 f4a508e8..d42ff0e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.6.2 (2021-05-28) + +### Bug Fixes and Other Changes + + * exclude null values from device parameters for annealing tasks + ## v1.6.1 (2021-05-25) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index a93a5e4b..51d420e5 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.6.2.dev0" +__version__ = "1.6.2" From 06572222f0eef94a0a5b313c238660ffee56d7da Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 28 May 2021 20:39:23 +0000 Subject: [PATCH 0338/1165] update development version to v1.6.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 51d420e5..0da1174c 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.6.2" +__version__ = "1.6.3.dev0" From 94d0912518d51ca439fd643da4c7584017c59b7e Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Thu, 3 Jun 2021 14:48:44 -0700 Subject: [PATCH 0339/1165] =?UTF-8?q?use=20device=20data=20to=20create=20d?= =?UTF-8?q?evice=20level=20parameter=20data=20when=20creating=20a=E2=80=A6?= =?UTF-8?q?=20(#240)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * use device data to create device level parameter data when creating a quantum annealing task --- src/braket/aws/aws_quantum_task.py | 52 ++++++++++++++-- .../braket/aws/test_aws_quantum_task.py | 60 +++++++++++++++++-- 2 files changed, 101 insertions(+), 11 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index dbda7fdd..690a9a89 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -26,7 +26,17 @@ from braket.circuits.circuit import Circuit from braket.circuits.circuit_helpers import validate_circuit_and_shots from braket.device_schema import GateModelParameters -from braket.device_schema.dwave import DwaveDeviceParameters +from braket.device_schema.dwave import ( + Dwave2000QDeviceParameters, + DwaveAdvantageDeviceParameters, + DwaveDeviceParameters, +) +from braket.device_schema.dwave.dwave_2000Q_device_level_parameters_v1 import ( + Dwave2000QDeviceLevelParameters, +) +from braket.device_schema.dwave.dwave_advantage_device_level_parameters_v1 import ( + DwaveAdvantageDeviceLevelParameters, +) from braket.device_schema.ionq import IonqDeviceParameters from braket.device_schema.rigetti import RigettiDeviceParameters from braket.device_schema.simulators import GateModelSimulatorDeviceParameters @@ -435,17 +445,21 @@ def _( aws_session: AwsSession, create_task_kwargs: Dict[str, Any], device_arn: str, - device_parameters: Union[dict, DwaveDeviceParameters], + device_parameters: Union[ + dict, + DwaveDeviceParameters, + DwaveAdvantageDeviceParameters, + Dwave2000QDeviceParameters, + ], disable_qubit_rewiring, *args, **kwargs, ) -> AwsQuantumTask: + device_params = _create_annealing_device_params(device_parameters, device_arn) create_task_kwargs.update( { "action": problem.to_ir().json(), - "deviceParameters": DwaveDeviceParameters.parse_obj(device_parameters).json( - exclude_none=True - ), + "deviceParameters": device_params.json(exclude_none=True), } ) @@ -453,6 +467,34 @@ def _( return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) +def _create_annealing_device_params(device_params, device_arn): + if type(device_params) is not dict: + device_params = device_params.dict() + + device_level_parameters = device_params.get("deviceLevelParameters", None) or device_params.get( + "providerLevelParameters", None + ) + + # deleting since it may be the old version + if "braketSchemaHeader" in device_level_parameters: + del device_level_parameters["braketSchemaHeader"] + + if "Advantage" in device_arn: + device_level_parameters = DwaveAdvantageDeviceLevelParameters.parse_obj( + device_level_parameters + ) + return DwaveAdvantageDeviceParameters(deviceLevelParameters=device_level_parameters) + elif "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." + ) + + def _create_common_params( device_arn: str, s3_destination_folder: AwsSession.S3DestinationFolder, shots: int ) -> Dict[str, Any]: 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 229d68d3..30663039 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -22,10 +22,15 @@ from braket.annealing.problem import Problem, ProblemType from braket.aws import AwsQuantumTask +from braket.aws.aws_quantum_task import _create_annealing_device_params from braket.aws.aws_session import AwsSession from braket.circuits import Circuit from braket.device_schema import GateModelParameters -from braket.device_schema.dwave import DwaveDeviceParameters +from braket.device_schema.dwave import ( + Dwave2000QDeviceParameters, + DwaveAdvantageDeviceParameters, + DwaveDeviceParameters, +) from braket.device_schema.ionq import IonqDeviceParameters from braket.device_schema.rigetti import RigettiDeviceParameters from braket.device_schema.simulators import GateModelSimulatorDeviceParameters @@ -406,11 +411,54 @@ def test_from_circuit_with_shots_value_error(aws_session, arn, circuit): @pytest.mark.parametrize( - "device_parameters", + "device_parameters,arn", [ - {"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}}, - DwaveDeviceParameters.parse_obj( - {"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}} + ( + {"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}}, + "arn:aws:braket:::device/qpu/d-wave/Advantage_system1", + ), + ( + {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION", "beta": 0.2}}, + "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6", + ), + pytest.param( + {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION", "beta": 0.2}}, + "arn:aws:braket:::device/qpu/d-wave/Advantage_system1", + # this doesn't fail... yet + # marks=pytest.mark.xfail(reason='beta not a valid parameter for Advantage device'), + ), + pytest.param( + {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION", "beta": 0.2}}, + "arn:aws:braket:::device/qpu/d-wave/fake_arn", + marks=pytest.mark.xfail(reason="Bad ARN"), + ), + ( + {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION"}}, + "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6", + ), + ( + DwaveDeviceParameters.parse_obj( + {"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}} + ), + "arn:aws:braket:::device/qpu/d-wave/Advantage_system1", + ), + ( + DwaveDeviceParameters.parse_obj( + {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION"}} + ), + "arn:aws:braket:::device/qpu/d-wave/Advantage_system1", + ), + ( + DwaveAdvantageDeviceParameters.parse_obj( + {"deviceLevelParameters": {"autoScale": "False"}} + ), + "arn:aws:braket:::device/qpu/d-wave/Advantage_system1", + ), + ( + Dwave2000QDeviceParameters.parse_obj( + {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION"}} + ), + "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6", ), ], ) @@ -435,7 +483,7 @@ def test_from_annealing(device_parameters, aws_session, arn, problem): problem, S3_TARGET, 1000, - DwaveDeviceParameters.parse_obj(device_parameters), + _create_annealing_device_params(device_parameters, device_arn=arn), ) From 5b2fb416f145b7f411ea36d552d7b62b1444ca04 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 4 Jun 2021 18:14:54 +0000 Subject: [PATCH 0340/1165] prepare release v1.6.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 d42ff0e9..4387c417 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.6.3 (2021-06-04) + +### Bug Fixes and Other Changes + + * use device data to create device level parameter data when creating a… + ## v1.6.2 (2021-05-28) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 0da1174c..b7b670b0 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.6.3.dev0" +__version__ = "1.6.3" From df8b38ddd9f8b976987bd816f7b1b561190ad45b Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 4 Jun 2021 18:14:54 +0000 Subject: [PATCH 0341/1165] update development version to v1.6.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 b7b670b0..7b7f002d 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.6.3" +__version__ = "1.6.4.dev0" From ba2a8b790314515ffff1dc87eac08d073f1da185 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 9 Jun 2021 17:19:07 -0700 Subject: [PATCH 0342/1165] fallback on empty dict for device level parameters (#248) * fallback on empty dict for device level parameters --- src/braket/aws/aws_quantum_task.py | 3 ++- test/unit_tests/braket/aws/test_aws_quantum_task.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 690a9a89..2700aa3b 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -471,8 +471,9 @@ def _create_annealing_device_params(device_params, device_arn): if type(device_params) is not dict: device_params = device_params.dict() + # check for device level or provider level parameters device_level_parameters = device_params.get("deviceLevelParameters", None) or device_params.get( - "providerLevelParameters", None + "providerLevelParameters", {} ) # deleting since it may be the old version 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 30663039..db9d1b6a 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -460,6 +460,10 @@ def test_from_circuit_with_shots_value_error(aws_session, arn, circuit): ), "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6", ), + ( + {}, + "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6", + ), ], ) def test_from_annealing(device_parameters, aws_session, arn, problem): From f40fb20038a4af54e4bb4abd70f3a3345af227f0 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 10 Jun 2021 00:54:05 +0000 Subject: [PATCH 0343/1165] prepare release v1.6.4 --- 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 4387c417..65413f84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.6.4 (2021-06-10) + +### Bug Fixes and Other Changes + + * fallback on empty dict for device level parameters + ## v1.6.3 (2021-06-04) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 7b7f002d..9b4bca44 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.6.4.dev0" +__version__ = "1.6.4" From ebd0fce654d060ad10364ae929f282d31a415dbb Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 10 Jun 2021 00:54:05 +0000 Subject: [PATCH 0344/1165] update development version to v1.6.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 9b4bca44..9c064ef8 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.6.4" +__version__ = "1.6.5.dev0" From a24894cca2444334ed0dce2d761a2d6128340b16 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 10 Jun 2021 16:17:27 -0700 Subject: [PATCH 0345/1165] fix: Require qubit indices to be integers (#249) --- src/braket/circuits/qubit.py | 4 +++- test/unit_tests/braket/circuits/test_qubit.py | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/braket/circuits/qubit.py b/src/braket/circuits/qubit.py index 8eb8257c..a1830b2c 100644 --- a/src/braket/circuits/qubit.py +++ b/src/braket/circuits/qubit.py @@ -36,8 +36,10 @@ def __new__(cls, index: int): >>> Qubit(0) >>> Qubit(1) """ + if not isinstance(index, int): + raise TypeError(f"Supplied qubit index, {index}, must be an integer.") if index < 0: - raise ValueError(f"Supplied index, {index}, cannot be less than zero.") + raise ValueError(f"Supplied qubit index, {index}, cannot be less than zero.") return super().__new__(cls, index) def __repr__(self): diff --git a/test/unit_tests/braket/circuits/test_qubit.py b/test/unit_tests/braket/circuits/test_qubit.py index 14d70192..cb2399e7 100644 --- a/test/unit_tests/braket/circuits/test_qubit.py +++ b/test/unit_tests/braket/circuits/test_qubit.py @@ -26,9 +26,10 @@ def test_index_lt_zero(): Qubit(-1) +@pytest.mark.parametrize("qubit_arg", ("not a number", 0.5)) @pytest.mark.xfail(raises=TypeError) -def test_index_non_int(): - Qubit("not a number") +def test_index_non_int(qubit_arg): + Qubit(qubit_arg) def test_index_gte_zero(): From 9bdf26cb7e8a0973fe33e880cf0623189a33fe6b Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 22 Jun 2021 22:50:55 -0700 Subject: [PATCH 0346/1165] fix: Get qubit count without instantiating op (#250) Introduces `fixed_qubit_count` so customers don't need to instantiate a gate or noise operation to see its qubit count. Also fixes a bug that occurs when checking compatibility of gate and noise qubit counts, where the gate is always instantiated with no arguments, which obviously fails for parametrized gates. --- src/braket/circuits/angled_gate.py | 6 +- src/braket/circuits/gate.py | 6 +- src/braket/circuits/gates.py | 268 +++++++++++++----- src/braket/circuits/noise.py | 54 ++-- src/braket/circuits/noise_helpers.py | 12 +- src/braket/circuits/noises.py | 54 +++- src/braket/circuits/quantum_operator.py | 61 +++- test/unit_tests/braket/circuits/test_gates.py | 8 + .../braket/circuits/test_noise_helpers.py | 61 +++- .../unit_tests/braket/circuits/test_noises.py | 8 + .../braket/circuits/test_quantum_operator.py | 21 ++ 11 files changed, 426 insertions(+), 133 deletions(-) diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index 936cbb2e..0c795ca7 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. import math -from typing import Sequence +from typing import Optional, Sequence from braket.circuits.gate import Gate @@ -22,11 +22,11 @@ class AngledGate(Gate): Class `AngledGate` represents a quantum gate that operates on N qubits and an angle. """ - def __init__(self, angle: float, qubit_count: int, ascii_symbols: Sequence[str]): + def __init__(self, angle: float, qubit_count: Optional[int], ascii_symbols: Sequence[str]): """ Args: angle (float): The angle of the gate in radians. - qubit_count (int): The number of qubits that this gate interacts with. + qubit_count (int, optional): The number of qubits that this gate interacts with. ascii_symbols (Sequence[str]): ASCII string symbols for the gate. These are used when 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. diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index 08f53245..310c9b77 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.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, Sequence +from typing import Any, Optional, Sequence from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.qubit_set import QubitSet @@ -24,10 +24,10 @@ class Gate(QuantumOperator): the metadata that defines what a gate is and what it does. """ - def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): + def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): """ Args: - qubit_count (int): Number of qubits this gate interacts with. + qubit_count (int, optional): Number of qubits this gate interacts with. ascii_symbols (Sequence[str]): ASCII string symbols for the gate. These are used when printing a diagram of circuits. Length must be the same as `qubit_count`, and index ordering is expected to correlate with target ordering on the instruction. diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 44b3101b..ab65f61f 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -43,7 +43,7 @@ class H(Gate): """Hadamard gate.""" def __init__(self): - super().__init__(qubit_count=1, ascii_symbols=["H"]) + super().__init__(qubit_count=None, ascii_symbols=["H"]) def to_ir(self, target: QubitSet): return ir.H.construct(target=target[0]) @@ -51,6 +51,10 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return 1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def h(target: QubitSetInput) -> Iterable[Instruction]: @@ -66,7 +70,7 @@ def h(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().h(0) >>> circ = Circuit().h([0, 1, 2]) """ - return [Instruction(Gate.H(), target=qubit) for qubit in QubitSet(target)] + return [Instruction(H(), target=qubit) for qubit in QubitSet(target)] Gate.register_gate(H) @@ -76,7 +80,7 @@ class I(Gate): # noqa: E742, E261 """Identity gate.""" def __init__(self): - super().__init__(qubit_count=1, ascii_symbols=["I"]) + super().__init__(qubit_count=None, ascii_symbols=["I"]) def to_ir(self, target: QubitSet): return ir.I.construct(target=target[0]) @@ -84,6 +88,10 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.eye(2, dtype=complex) + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def i(target: QubitSetInput) -> Iterable[Instruction]: @@ -99,7 +107,7 @@ def i(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().i(0) >>> circ = Circuit().i([0, 1, 2]) """ - return [Instruction(Gate.I(), target=qubit) for qubit in QubitSet(target)] + return [Instruction(I(), target=qubit) for qubit in QubitSet(target)] Gate.register_gate(I) @@ -109,7 +117,7 @@ class X(Gate): """Pauli-X gate.""" def __init__(self): - super().__init__(qubit_count=1, ascii_symbols=["X"]) + super().__init__(qubit_count=None, ascii_symbols=["X"]) def to_ir(self, target: QubitSet): return ir.X.construct(target=target[0]) @@ -117,6 +125,10 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def x(target: QubitSetInput) -> Iterable[Instruction]: @@ -132,7 +144,7 @@ def x(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().x(0) >>> circ = Circuit().x([0, 1, 2]) """ - return [Instruction(Gate.X(), target=qubit) for qubit in QubitSet(target)] + return [Instruction(X(), target=qubit) for qubit in QubitSet(target)] Gate.register_gate(X) @@ -142,7 +154,7 @@ class Y(Gate): """Pauli-Y gate.""" def __init__(self): - super().__init__(qubit_count=1, ascii_symbols=["Y"]) + super().__init__(qubit_count=None, ascii_symbols=["Y"]) def to_ir(self, target: QubitSet): return ir.Y.construct(target=target[0]) @@ -150,6 +162,10 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def y(target: QubitSetInput) -> Iterable[Instruction]: @@ -165,7 +181,7 @@ def y(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().y(0) >>> circ = Circuit().y([0, 1, 2]) """ - return [Instruction(Gate.Y(), target=qubit) for qubit in QubitSet(target)] + return [Instruction(Y(), target=qubit) for qubit in QubitSet(target)] Gate.register_gate(Y) @@ -175,7 +191,7 @@ class Z(Gate): """Pauli-Z gate.""" def __init__(self): - super().__init__(qubit_count=1, ascii_symbols=["Z"]) + super().__init__(qubit_count=None, ascii_symbols=["Z"]) def to_ir(self, target: QubitSet): return ir.Z.construct(target=target[0]) @@ -183,6 +199,10 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def z(target: QubitSetInput) -> Iterable[Instruction]: @@ -198,7 +218,7 @@ def z(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().z(0) >>> circ = Circuit().z([0, 1, 2]) """ - return [Instruction(Gate.Z(), target=qubit) for qubit in QubitSet(target)] + return [Instruction(Z(), target=qubit) for qubit in QubitSet(target)] Gate.register_gate(Z) @@ -208,7 +228,7 @@ class S(Gate): """S gate.""" def __init__(self): - super().__init__(qubit_count=1, ascii_symbols=["S"]) + super().__init__(qubit_count=None, ascii_symbols=["S"]) def to_ir(self, target: QubitSet): return ir.S.construct(target=target[0]) @@ -217,6 +237,10 @@ def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, 1.0j]], dtype=complex) + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def s(target: QubitSetInput) -> Iterable[Instruction]: @@ -232,7 +256,7 @@ def s(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().s(0) >>> circ = Circuit().s([0, 1, 2]) """ - return [Instruction(Gate.S(), target=qubit) for qubit in QubitSet(target)] + return [Instruction(S(), target=qubit) for qubit in QubitSet(target)] Gate.register_gate(S) @@ -242,7 +266,7 @@ class Si(Gate): """Conjugate transpose of S gate.""" def __init__(self): - super().__init__(qubit_count=1, ascii_symbols=["Si"]) + super().__init__(qubit_count=None, ascii_symbols=["Si"]) def to_ir(self, target: QubitSet): return ir.Si.construct(target=target[0]) @@ -250,6 +274,10 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.array([[1, 0], [0, -1j]], dtype=complex) + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def si(target: QubitSetInput) -> Iterable[Instruction]: @@ -265,7 +293,7 @@ def si(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().si(0) >>> circ = Circuit().si([0, 1, 2]) """ - return [Instruction(Gate.Si(), target=qubit) for qubit in QubitSet(target)] + return [Instruction(Si(), target=qubit) for qubit in QubitSet(target)] Gate.register_gate(Si) @@ -275,7 +303,7 @@ class T(Gate): """T gate.""" def __init__(self): - super().__init__(qubit_count=1, ascii_symbols=["T"]) + super().__init__(qubit_count=None, ascii_symbols=["T"]) def to_ir(self, target: QubitSet): return ir.T.construct(target=target[0]) @@ -283,6 +311,10 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, np.exp(1j * np.pi / 4)]], dtype=complex) + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def t(target: QubitSetInput) -> Iterable[Instruction]: @@ -298,7 +330,7 @@ def t(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().t(0) >>> circ = Circuit().t([0, 1, 2]) """ - return [Instruction(Gate.T(), target=qubit) for qubit in QubitSet(target)] + return [Instruction(T(), target=qubit) for qubit in QubitSet(target)] Gate.register_gate(T) @@ -308,7 +340,7 @@ class Ti(Gate): """Conjugate transpose of T gate.""" def __init__(self): - super().__init__(qubit_count=1, ascii_symbols=["Ti"]) + super().__init__(qubit_count=None, ascii_symbols=["Ti"]) def to_ir(self, target: QubitSet): return ir.Ti.construct(target=target[0]) @@ -316,6 +348,10 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, np.exp(-1j * np.pi / 4)]], dtype=complex) + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def ti(target: QubitSetInput) -> Iterable[Instruction]: @@ -331,7 +367,7 @@ def ti(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().ti(0) >>> circ = Circuit().ti([0, 1, 2]) """ - return [Instruction(Gate.Ti(), target=qubit) for qubit in QubitSet(target)] + return [Instruction(Ti(), target=qubit) for qubit in QubitSet(target)] Gate.register_gate(Ti) @@ -341,7 +377,7 @@ class V(Gate): """Square root of not gate.""" def __init__(self): - super().__init__(qubit_count=1, ascii_symbols=["V"]) + super().__init__(qubit_count=None, ascii_symbols=["V"]) def to_ir(self, target: QubitSet): return ir.V.construct(target=target[0]) @@ -349,6 +385,10 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.array([[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]], dtype=complex) + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def v(target: QubitSetInput) -> Iterable[Instruction]: @@ -364,7 +404,7 @@ def v(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().v(0) >>> circ = Circuit().v([0, 1, 2]) """ - return [Instruction(Gate.V(), target=qubit) for qubit in QubitSet(target)] + return [Instruction(V(), target=qubit) for qubit in QubitSet(target)] Gate.register_gate(V) @@ -374,7 +414,7 @@ class Vi(Gate): """Conjugate transpose of square root of not gate.""" def __init__(self): - super().__init__(qubit_count=1, ascii_symbols=["Vi"]) + super().__init__(qubit_count=None, ascii_symbols=["Vi"]) def to_ir(self, target: QubitSet): return ir.Vi.construct(target=target[0]) @@ -382,6 +422,10 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.array(([[0.5 - 0.5j, 0.5 + 0.5j], [0.5 + 0.5j, 0.5 - 0.5j]]), dtype=complex) + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def vi(target: QubitSetInput) -> Iterable[Instruction]: @@ -397,7 +441,7 @@ def vi(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().vi(0) >>> circ = Circuit().vi([0, 1, 2]) """ - return [Instruction(Gate.Vi(), target=qubit) for qubit in QubitSet(target)] + return [Instruction(Vi(), target=qubit) for qubit in QubitSet(target)] Gate.register_gate(Vi) @@ -414,7 +458,7 @@ class Rx(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Rx({:.3g})".format(angle)]) + super().__init__(angle=angle, qubit_count=None, ascii_symbols=["Rx({:.3g})".format(angle)]) def to_ir(self, target: QubitSet): return ir.Rx.construct(target=target[0], angle=self.angle) @@ -424,9 +468,13 @@ def to_matrix(self) -> np.ndarray: sin = np.sin(self.angle / 2) return np.array([[cos, -1j * sin], [-1j * sin, cos]], dtype=complex) + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) - def rx(target: QubitInput, angle: float) -> Instruction: + def rx(target: QubitInput, angle: float) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: @@ -434,12 +482,12 @@ def rx(target: QubitInput, angle: float) -> Instruction: angle (float): Angle in radians. Returns: - Instruction: Rx instruction. + Iterable[Instruction]: Rx instruction. Examples: >>> circ = Circuit().rx(0, 0.15) """ - return [Instruction(Gate.Rx(angle), target=qubit) for qubit in QubitSet(target)] + return [Instruction(Rx(angle), target=qubit) for qubit in QubitSet(target)] Gate.register_gate(Rx) @@ -453,7 +501,7 @@ class Ry(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Ry({:.3g})".format(angle)]) + super().__init__(angle=angle, qubit_count=None, ascii_symbols=["Ry({:.3g})".format(angle)]) def to_ir(self, target: QubitSet): return ir.Ry.construct(target=target[0], angle=self.angle) @@ -463,9 +511,13 @@ def to_matrix(self) -> np.ndarray: sin = np.sin(self.angle / 2) return np.array([[cos, -sin], [+sin, cos]], dtype=complex) + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) - def ry(target: QubitInput, angle: float) -> Instruction: + def ry(target: QubitInput, angle: float) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: @@ -473,12 +525,12 @@ def ry(target: QubitInput, angle: float) -> Instruction: angle (float): Angle in radians. Returns: - Instruction: Ry instruction. + Iterable[Instruction]: Ry instruction. Examples: >>> circ = Circuit().ry(0, 0.15) """ - return [Instruction(Gate.Ry(angle), target=qubit) for qubit in QubitSet(target)] + return [Instruction(Ry(angle), target=qubit) for qubit in QubitSet(target)] Gate.register_gate(Ry) @@ -492,7 +544,7 @@ class Rz(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=1, ascii_symbols=["Rz({:.3g})".format(angle)]) + super().__init__(angle=angle, qubit_count=None, ascii_symbols=["Rz({:.3g})".format(angle)]) def to_ir(self, target: QubitSet): return ir.Rz.construct(target=target[0], angle=self.angle) @@ -502,9 +554,13 @@ def to_matrix(self) -> np.ndarray: [[np.exp(-1j * self.angle / 2), 0], [0, np.exp(1j * self.angle / 2)]], dtype=complex ) + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) - def rz(target: QubitInput, angle: float) -> Instruction: + def rz(target: QubitInput, angle: float) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: @@ -512,12 +568,12 @@ def rz(target: QubitInput, angle: float) -> Instruction: angle (float): Angle in radians. Returns: - Instruction: Rz instruction. + Iterable[Instruction]: Rz instruction. Examples: >>> circ = Circuit().rz(0, 0.15) """ - return [Instruction(Gate.Rz(angle), target=qubit) for qubit in QubitSet(target)] + return [Instruction(Rz(angle), target=qubit) for qubit in QubitSet(target)] Gate.register_gate(Rz) @@ -531,7 +587,9 @@ class PhaseShift(AngledGate): """ def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=1, ascii_symbols=["PHASE({:.3g})".format(angle)]) + super().__init__( + angle=angle, qubit_count=None, ascii_symbols=["PHASE({:.3g})".format(angle)] + ) def to_ir(self, target: QubitSet): return ir.PhaseShift.construct(target=target[0], angle=self.angle) @@ -539,9 +597,13 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, np.exp(1j * self.angle)]], dtype=complex) + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) - def phaseshift(target: QubitInput, angle: float) -> Instruction: + def phaseshift(target: QubitInput, angle: float) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: @@ -549,12 +611,12 @@ def phaseshift(target: QubitInput, angle: float) -> Instruction: angle (float): Angle in radians. Returns: - Instruction: PhaseShift instruction. + Iterable[Instruction]: PhaseShift instruction. Examples: >>> circ = Circuit().phaseshift(0, 0.15) """ - return [Instruction(Gate.PhaseShift(angle), target=qubit) for qubit in QubitSet(target)] + return [Instruction(PhaseShift(angle), target=qubit) for qubit in QubitSet(target)] Gate.register_gate(PhaseShift) @@ -567,7 +629,7 @@ class CNot(Gate): """Controlled NOT gate.""" def __init__(self): - super().__init__(qubit_count=2, ascii_symbols=["C", "X"]) + super().__init__(qubit_count=None, ascii_symbols=["C", "X"]) def to_ir(self, target: QubitSet): return ir.CNot.construct(control=target[0], target=target[1]) @@ -583,6 +645,10 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) + @staticmethod + def fixed_qubit_count() -> int: + return 2 + @staticmethod @circuit.subroutine(register=True) def cnot(control: QubitInput, target: QubitInput) -> Instruction: @@ -598,7 +664,7 @@ def cnot(control: QubitInput, target: QubitInput) -> Instruction: Examples: >>> circ = Circuit().cnot(0, 1) """ - return Instruction(Gate.CNot(), target=[control, target]) + return Instruction(CNot(), target=[control, target]) Gate.register_gate(CNot) @@ -608,7 +674,7 @@ class Swap(Gate): """Swap gate.""" def __init__(self): - super().__init__(qubit_count=2, ascii_symbols=["SWAP", "SWAP"]) + super().__init__(qubit_count=None, ascii_symbols=["SWAP", "SWAP"]) def to_ir(self, target: QubitSet): return ir.Swap.construct(targets=[target[0], target[1]]) @@ -624,6 +690,10 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) + @staticmethod + def fixed_qubit_count() -> int: + return 2 + @staticmethod @circuit.subroutine(register=True) def swap(target1: QubitInput, target2: QubitInput) -> Instruction: @@ -639,7 +709,7 @@ def swap(target1: QubitInput, target2: QubitInput) -> Instruction: Examples: >>> circ = Circuit().swap(0, 1) """ - return Instruction(Gate.Swap(), target=[target1, target2]) + return Instruction(Swap(), target=[target1, target2]) Gate.register_gate(Swap) @@ -649,7 +719,7 @@ class ISwap(Gate): """ISwap gate.""" def __init__(self): - super().__init__(qubit_count=2, ascii_symbols=["ISWAP", "ISWAP"]) + super().__init__(qubit_count=None, ascii_symbols=["ISWAP", "ISWAP"]) def to_ir(self, target: QubitSet): return ir.ISwap.construct(targets=[target[0], target[1]]) @@ -665,6 +735,10 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) + @staticmethod + def fixed_qubit_count() -> int: + return 2 + @staticmethod @circuit.subroutine(register=True) def iswap(target1: QubitInput, target2: QubitInput) -> Instruction: @@ -680,7 +754,7 @@ def iswap(target1: QubitInput, target2: QubitInput) -> Instruction: Examples: >>> circ = Circuit().iswap(0, 1) """ - return Instruction(Gate.ISwap(), target=[target1, target2]) + return Instruction(ISwap(), target=[target1, target2]) Gate.register_gate(ISwap) @@ -696,7 +770,7 @@ class PSwap(AngledGate): def __init__(self, angle: float): super().__init__( angle=angle, - qubit_count=2, + qubit_count=None, ascii_symbols=["PSWAP({:.3g})".format(angle), "PSWAP({:.3g})".format(angle)], ) @@ -714,6 +788,10 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) + @staticmethod + def fixed_qubit_count() -> int: + return 2 + @staticmethod @circuit.subroutine(register=True) def pswap(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: @@ -729,7 +807,7 @@ def pswap(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction Examples: >>> circ = Circuit().pswap(0, 1, 0.15) """ - return Instruction(Gate.PSwap(angle), target=[target1, target2]) + return Instruction(PSwap(angle), target=[target1, target2]) Gate.register_gate(PSwap) @@ -747,7 +825,7 @@ class XY(AngledGate): def __init__(self, angle: float): super().__init__( angle=angle, - qubit_count=2, + qubit_count=None, ascii_symbols=["XY({:.3g})".format(angle), "XY({:.3g})".format(angle)], ) @@ -767,6 +845,10 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) + @staticmethod + def fixed_qubit_count() -> int: + return 2 + @staticmethod @circuit.subroutine(register=True) def xy(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: @@ -782,7 +864,7 @@ def xy(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: Examples: >>> circ = Circuit().xy(0, 1, 0.15) """ - return Instruction(Gate.XY(angle), target=[target1, target2]) + return Instruction(XY(angle), target=[target1, target2]) Gate.register_gate(XY) @@ -797,7 +879,7 @@ class CPhaseShift(AngledGate): def __init__(self, angle: float): super().__init__( - angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE({:.3g})".format(angle)] + angle=angle, qubit_count=None, ascii_symbols=["C", "PHASE({:.3g})".format(angle)] ) def to_ir(self, target: QubitSet): @@ -806,6 +888,10 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, 1.0, np.exp(1j * self.angle)]) + @staticmethod + def fixed_qubit_count() -> int: + return 2 + @staticmethod @circuit.subroutine(register=True) def cphaseshift(control: QubitInput, target: QubitInput, angle: float) -> Instruction: @@ -822,7 +908,7 @@ def cphaseshift(control: QubitInput, target: QubitInput, angle: float) -> Instru Examples: >>> circ = Circuit().cphaseshift(0, 1, 0.15) """ - return Instruction(Gate.CPhaseShift(angle), target=[control, target]) + return Instruction(CPhaseShift(angle), target=[control, target]) Gate.register_gate(CPhaseShift) @@ -837,7 +923,7 @@ class CPhaseShift00(AngledGate): def __init__(self, angle: float): super().__init__( - angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE00({:.3g})".format(angle)] + angle=angle, qubit_count=None, ascii_symbols=["C", "PHASE00({:.3g})".format(angle)] ) def to_ir(self, target: QubitSet): @@ -846,6 +932,10 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.diag([np.exp(1j * self.angle), 1.0, 1.0, 1.0]) + @staticmethod + def fixed_qubit_count() -> int: + return 2 + @staticmethod @circuit.subroutine(register=True) def cphaseshift00(control: QubitInput, target: QubitInput, angle: float) -> Instruction: @@ -862,7 +952,7 @@ def cphaseshift00(control: QubitInput, target: QubitInput, angle: float) -> Inst Examples: >>> circ = Circuit().cphaseshift00(0, 1, 0.15) """ - return Instruction(Gate.CPhaseShift00(angle), target=[control, target]) + return Instruction(CPhaseShift00(angle), target=[control, target]) Gate.register_gate(CPhaseShift00) @@ -877,7 +967,7 @@ class CPhaseShift01(AngledGate): def __init__(self, angle: float): super().__init__( - angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE01({:.3g})".format(angle)] + angle=angle, qubit_count=None, ascii_symbols=["C", "PHASE01({:.3g})".format(angle)] ) def to_ir(self, target: QubitSet): @@ -886,6 +976,10 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.diag([1.0, np.exp(1j * self.angle), 1.0, 1.0]) + @staticmethod + def fixed_qubit_count() -> int: + return 2 + @staticmethod @circuit.subroutine(register=True) def cphaseshift01(control: QubitInput, target: QubitInput, angle: float) -> Instruction: @@ -902,7 +996,7 @@ def cphaseshift01(control: QubitInput, target: QubitInput, angle: float) -> Inst Examples: >>> circ = Circuit().cphaseshift01(0, 1, 0.15) """ - return Instruction(Gate.CPhaseShift01(angle), target=[control, target]) + return Instruction(CPhaseShift01(angle), target=[control, target]) Gate.register_gate(CPhaseShift01) @@ -917,7 +1011,7 @@ class CPhaseShift10(AngledGate): def __init__(self, angle: float): super().__init__( - angle=angle, qubit_count=2, ascii_symbols=["C", "PHASE10({:.3g})".format(angle)] + angle=angle, qubit_count=None, ascii_symbols=["C", "PHASE10({:.3g})".format(angle)] ) def to_ir(self, target: QubitSet): @@ -926,6 +1020,10 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, np.exp(1j * self.angle), 1.0]) + @staticmethod + def fixed_qubit_count() -> int: + return 2 + @staticmethod @circuit.subroutine(register=True) def cphaseshift10(control: QubitInput, target: QubitInput, angle: float) -> Instruction: @@ -942,7 +1040,7 @@ def cphaseshift10(control: QubitInput, target: QubitInput, angle: float) -> Inst Examples: >>> circ = Circuit().cphaseshift10(0, 1, 0.15) """ - return Instruction(Gate.CPhaseShift10(angle), target=[control, target]) + return Instruction(CPhaseShift10(angle), target=[control, target]) Gate.register_gate(CPhaseShift10) @@ -952,7 +1050,7 @@ class CY(Gate): """Controlled Pauli-Y gate.""" def __init__(self): - super().__init__(qubit_count=2, ascii_symbols=["C", "Y"]) + super().__init__(qubit_count=None, ascii_symbols=["C", "Y"]) def to_ir(self, target: QubitSet): return ir.CY.construct(control=target[0], target=target[1]) @@ -968,6 +1066,10 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) + @staticmethod + def fixed_qubit_count() -> int: + return 2 + @staticmethod @circuit.subroutine(register=True) def cy(control: QubitInput, target: QubitInput) -> Instruction: @@ -983,7 +1085,7 @@ def cy(control: QubitInput, target: QubitInput) -> Instruction: Examples: >>> circ = Circuit().cy(0, 1) """ - return Instruction(Gate.CY(), target=[control, target]) + return Instruction(CY(), target=[control, target]) Gate.register_gate(CY) @@ -993,7 +1095,7 @@ class CZ(Gate): """Controlled Pauli-Z gate.""" def __init__(self): - super().__init__(qubit_count=2, ascii_symbols=["C", "Z"]) + super().__init__(qubit_count=None, ascii_symbols=["C", "Z"]) def to_ir(self, target: QubitSet): return ir.CZ.construct(control=target[0], target=target[1]) @@ -1001,6 +1103,10 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, 1.0, -1.0]) + @staticmethod + def fixed_qubit_count() -> int: + return 2 + @staticmethod @circuit.subroutine(register=True) def cz(control: QubitInput, target: QubitInput) -> Instruction: @@ -1016,7 +1122,7 @@ def cz(control: QubitInput, target: QubitInput) -> Instruction: Examples: >>> circ = Circuit().cz(0, 1) """ - return Instruction(Gate.CZ(), target=[control, target]) + return Instruction(CZ(), target=[control, target]) Gate.register_gate(CZ) @@ -1034,7 +1140,7 @@ class XX(AngledGate): def __init__(self, angle: float): super().__init__( angle=angle, - qubit_count=2, + qubit_count=None, ascii_symbols=["XX({:.3g})".format(angle), "XX({:.3g})".format(angle)], ) @@ -1054,6 +1160,10 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) + @staticmethod + def fixed_qubit_count() -> int: + return 2 + @staticmethod @circuit.subroutine(register=True) def xx(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: @@ -1070,7 +1180,7 @@ def xx(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: Examples: >>> circ = Circuit().xx(0, 1, 0.15) """ - return Instruction(Gate.XX(angle), target=[target1, target2]) + return Instruction(XX(angle), target=[target1, target2]) Gate.register_gate(XX) @@ -1088,7 +1198,7 @@ class YY(AngledGate): def __init__(self, angle: float): super().__init__( angle=angle, - qubit_count=2, + qubit_count=None, ascii_symbols=["YY({:.3g})".format(angle), "YY({:.3g})".format(angle)], ) @@ -1108,6 +1218,10 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) + @staticmethod + def fixed_qubit_count() -> int: + return 2 + @staticmethod @circuit.subroutine(register=True) def yy(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: @@ -1124,7 +1238,7 @@ def yy(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: Examples: >>> circ = Circuit().yy(0, 1, 0.15) """ - return Instruction(Gate.YY(angle), target=[target1, target2]) + return Instruction(YY(angle), target=[target1, target2]) Gate.register_gate(YY) @@ -1142,7 +1256,7 @@ class ZZ(AngledGate): def __init__(self, angle: float): super().__init__( angle=angle, - qubit_count=2, + qubit_count=None, ascii_symbols=["ZZ({:.3g})".format(angle), "ZZ({:.3g})".format(angle)], ) @@ -1160,6 +1274,10 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) + @staticmethod + def fixed_qubit_count() -> int: + return 2 + @staticmethod @circuit.subroutine(register=True) def zz(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: @@ -1176,7 +1294,7 @@ def zz(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: Examples: >>> circ = Circuit().zz(0, 1, 0.15) """ - return Instruction(Gate.ZZ(angle), target=[target1, target2]) + return Instruction(ZZ(angle), target=[target1, target2]) Gate.register_gate(ZZ) @@ -1189,7 +1307,7 @@ class CCNot(Gate): """CCNOT gate or Toffoli gate.""" def __init__(self): - super().__init__(qubit_count=3, ascii_symbols=["C", "C", "X"]) + super().__init__(qubit_count=None, ascii_symbols=["C", "C", "X"]) def to_ir(self, target: QubitSet): return ir.CCNot.construct(controls=[target[0], target[1]], target=target[2]) @@ -1209,6 +1327,10 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) + @staticmethod + def fixed_qubit_count() -> int: + return 3 + @staticmethod @circuit.subroutine(register=True) def ccnot(control1: QubitInput, control2: QubitInput, target: QubitInput) -> Instruction: @@ -1225,7 +1347,7 @@ def ccnot(control1: QubitInput, control2: QubitInput, target: QubitInput) -> Ins Examples: >>> circ = Circuit().ccnot(0, 1, 2) """ - return Instruction(Gate.CCNot(), target=[control1, control2, target]) + return Instruction(CCNot(), target=[control1, control2, target]) Gate.register_gate(CCNot) @@ -1235,7 +1357,7 @@ class CSwap(Gate): """Controlled Swap gate.""" def __init__(self): - super().__init__(qubit_count=3, ascii_symbols=["C", "SWAP", "SWAP"]) + super().__init__(qubit_count=None, ascii_symbols=["C", "SWAP", "SWAP"]) def to_ir(self, target: QubitSet): return ir.CSwap.construct(control=target[0], targets=[target[1], target[2]]) @@ -1255,6 +1377,10 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) + @staticmethod + def fixed_qubit_count() -> int: + return 3 + @staticmethod @circuit.subroutine(register=True) def cswap(control: QubitInput, target1: QubitInput, target2: QubitInput) -> Instruction: @@ -1271,7 +1397,7 @@ def cswap(control: QubitInput, target1: QubitInput, target2: QubitInput) -> Inst Examples: >>> circ = Circuit().cswap(0, 1, 2) """ - return Instruction(Gate.CSwap(), target=[control, target1, target2]) + return Instruction(CSwap(), target=[control, target1, target2]) Gate.register_gate(CSwap) @@ -1345,7 +1471,7 @@ def unitary(targets: QubitSet, matrix: np.ndarray, display_name: str = "U") -> I if 2 ** len(targets) != matrix.shape[0]: raise ValueError("Dimensions of the supplied unitary are incompatible with the targets") - return Instruction(Gate.Unitary(matrix, display_name), target=targets) + return Instruction(Unitary(matrix, display_name), target=targets) Gate.register_gate(Unitary) diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py index dd5d50b3..2b5d7f7f 100644 --- a/src/braket/circuits/noise.py +++ b/src/braket/circuits/noise.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, Sequence +from typing import Any, Optional, Sequence from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.qubit_set import QubitSet @@ -26,14 +26,10 @@ class Noise(QuantumOperator): the metadata that defines what the noise channel is and what it does. """ - def __init__( - self, - qubit_count: int, - ascii_symbols: Sequence[str], - ): + def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): """ Args: - qubit_count (int): Number of qubits this noise channel interacts with. + qubit_count (int, optional): Number of qubits this noise channel interacts with. ascii_symbols (Sequence[str]): ASCII string symbols for this noise channel. These are used when printing a diagram of circuits. Length must be the same as `qubit_count`, and index ordering is expected to correlate with target ordering @@ -43,17 +39,7 @@ def __init__( ValueError: `qubit_count` is less than 1, `ascii_symbols` are None, or length of `ascii_symbols` is not equal to `qubit_count` """ - if qubit_count < 1: - raise ValueError(f"qubit_count, {qubit_count}, must be greater than zero") - self._qubit_count = qubit_count - - if ascii_symbols is None: - raise ValueError("ascii_symbols must not be None") - - if len(ascii_symbols) != qubit_count: - msg = f"ascii_symbols, {ascii_symbols}, length must equal qubit_count, {qubit_count}" - raise ValueError(msg) - self._ascii_symbols = tuple(ascii_symbols) + super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) @property def name(self) -> str: @@ -107,11 +93,13 @@ class SingleProbabilisticNoise(Noise): parameterized by a single probability. """ - def __init__(self, probability: float, qubit_count: int, ascii_symbols: Sequence[str]): + def __init__( + self, probability: float, qubit_count: Optional[int], ascii_symbols: Sequence[str] + ): """ Args: probability (float): The probability that the noise occurs. - qubit_count (int): The number of qubits to apply noise. + qubit_count (int, optional): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when 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. @@ -147,11 +135,13 @@ class SingleProbabilisticNoise_34(Noise): channels parameterized by a single probability. """ - def __init__(self, probability: float, qubit_count: int, ascii_symbols: Sequence[str]): + def __init__( + self, probability: float, qubit_count: Optional[int], ascii_symbols: Sequence[str] + ): """ Args: probability (float): The probability that the noise occurs. - qubit_count (int): The number of qubits to apply noise. + qubit_count (int, optional): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when 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. @@ -187,11 +177,13 @@ class SingleProbabilisticNoise_1516(Noise): parameterized by a single probability. """ - def __init__(self, probability: float, qubit_count: int, ascii_symbols: Sequence[str]): + def __init__( + self, probability: float, qubit_count: Optional[int], ascii_symbols: Sequence[str] + ): """ Args: probability (float): The probability that the noise occurs. - qubit_count (int): The number of qubits to apply noise. + qubit_count (int, optional): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when 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. @@ -232,14 +224,14 @@ def __init__( probX: float, probY: float, probZ: float, - qubit_count: int, + qubit_count: Optional[int], ascii_symbols: Sequence[str], ): """ Args: probX [float], probY [float], probZ [float]: The coefficients of the Kraus operators in the channel. - qubit_count (int): The number of qubits to apply noise. + qubit_count (int, optional): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when 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. @@ -306,11 +298,11 @@ class DampingNoise(Noise): on N qubits parameterized by gamma. """ - def __init__(self, gamma: float, qubit_count: int, ascii_symbols: Sequence[str]): + def __init__(self, gamma: float, qubit_count: Optional[int], ascii_symbols: Sequence[str]): """ Args: gamma (float): Probability of damping. - qubit_count (int): The number of qubits to apply noise. + qubit_count (int, optional): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when 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. @@ -347,7 +339,11 @@ class GeneralizedAmplitudeDampingNoise(DampingNoise): """ def __init__( - self, gamma: float, probability: float, qubit_count: int, ascii_symbols: Sequence[str] + self, + gamma: float, + probability: float, + qubit_count: Optional[int], + ascii_symbols: Sequence[str], ): """ Args: diff --git a/src/braket/circuits/noise_helpers.py b/src/braket/circuits/noise_helpers.py index 9db049e7..5f454bb7 100644 --- a/src/braket/circuits/noise_helpers.py +++ b/src/braket/circuits/noise_helpers.py @@ -62,10 +62,16 @@ def check_noise_target_gates(noise: Noise, target_gates: Iterable[Type[Gate]]): if noise.qubit_count > 1: for g in target_gates: - if g != Gate.Unitary and g().qubit_count != noise.qubit_count: + fixed_qubit_count = g.fixed_qubit_count() + if fixed_qubit_count is NotImplemented: raise ValueError( - "The target_targets must be gates that have the same number of \ -qubits as defined by the multi-qubit noise channel." + f"Target gate {g} can be instantiated on a variable number of qubits," + " but noise can only target gates with fixed qubit counts." + ) + if fixed_qubit_count != noise.qubit_count: + raise ValueError( + f"Target gate {g} acts on {fixed_qubit_count} qubits," + f" but {noise} acts on {noise.qubit_count} qubits." ) diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py index fb79437e..71dff491 100644 --- a/src/braket/circuits/noises.py +++ b/src/braket/circuits/noises.py @@ -73,7 +73,7 @@ class BitFlip(SingleProbabilisticNoise): def __init__(self, probability: float): super().__init__( probability=probability, - qubit_count=1, + qubit_count=None, ascii_symbols=["BF({:.2g})".format(probability)], ) @@ -85,6 +85,10 @@ def to_matrix(self) -> Iterable[np.ndarray]: K1 = np.sqrt(self.probability) * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) return [K0, K1] + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def bit_flip(target: QubitSetInput, probability: float) -> Iterable[Instruction]: @@ -138,7 +142,7 @@ class PhaseFlip(SingleProbabilisticNoise): def __init__(self, probability: float): super().__init__( probability=probability, - qubit_count=1, + qubit_count=None, ascii_symbols=["PF({:.2g})".format(probability)], ) @@ -150,6 +154,10 @@ def to_matrix(self) -> Iterable[np.ndarray]: K1 = np.sqrt(self.probability) * np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) return [K0, K1] + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def phase_flip(target: QubitSetInput, probability: float) -> Iterable[Instruction]: @@ -221,7 +229,7 @@ def __init__(self, probX: float, probY: float, probZ: float): probX=probX, probY=probY, probZ=probZ, - qubit_count=1, + qubit_count=None, ascii_symbols=["PC({:.2g},{:.2g},{:.2g})".format(probX, probY, probZ)], ) @@ -237,6 +245,10 @@ def to_matrix(self) -> Iterable[np.ndarray]: K3 = np.sqrt(self.probZ) * np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) return [K0, K1, K2, K3] + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def pauli_channel( @@ -311,7 +323,7 @@ class Depolarizing(SingleProbabilisticNoise_34): def __init__(self, probability: float): super().__init__( probability=probability, - qubit_count=1, + qubit_count=None, ascii_symbols=["DEPO({:.2g})".format(probability)], ) @@ -325,6 +337,10 @@ def to_matrix(self) -> Iterable[np.ndarray]: K3 = np.sqrt(self.probability / 3) * np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) return [K0, K1, K2, K3] + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def depolarizing(target: QubitSetInput, probability: float) -> Iterable[Instruction]: @@ -399,7 +415,7 @@ class TwoQubitDepolarizing(SingleProbabilisticNoise_1516): def __init__(self, probability: float): super().__init__( probability=probability, - qubit_count=2, + qubit_count=None, ascii_symbols=["DEPO({:.2g})".format(probability)] * 2, ) @@ -424,6 +440,10 @@ def to_matrix(self) -> Iterable[np.ndarray]: return K_list + @staticmethod + def fixed_qubit_count() -> int: + return 2 + @staticmethod @circuit.subroutine(register=True) def two_qubit_depolarizing( @@ -483,7 +503,7 @@ class TwoQubitDephasing(SingleProbabilisticNoise_34): def __init__(self, probability: float): super().__init__( probability=probability, - qubit_count=2, + qubit_count=None, ascii_symbols=["DEPH({:.2g})".format(probability)] * 2, ) @@ -503,6 +523,10 @@ def to_matrix(self) -> Iterable[np.ndarray]: return [K0, K1, K2, K3] + @staticmethod + def fixed_qubit_count() -> int: + return 2 + @staticmethod @circuit.subroutine(register=True) def two_qubit_dephasing( @@ -555,7 +579,7 @@ class AmplitudeDamping(DampingNoise): def __init__(self, gamma: float): super().__init__( gamma=gamma, - qubit_count=1, + qubit_count=None, ascii_symbols=["AD({:.2g})".format(gamma)], ) @@ -567,6 +591,10 @@ def to_matrix(self) -> Iterable[np.ndarray]: K1 = np.array([[0.0, np.sqrt(self.gamma)], [0.0, 0.0]], dtype=complex) return [K0, K1] + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def amplitude_damping(target: QubitSetInput, gamma: float) -> Iterable[Instruction]: @@ -633,7 +661,7 @@ def __init__(self, gamma: float, probability: float): super().__init__( gamma=gamma, probability=probability, - qubit_count=1, + qubit_count=None, ascii_symbols=["GAD({:.2g},{:.2g})".format(gamma, probability)], ) @@ -653,6 +681,10 @@ def to_matrix(self) -> Iterable[np.ndarray]: K3 = np.sqrt(1 - self.probability) * np.array([[0.0, 0.0], [np.sqrt(self.gamma), 0.0]]) return [K0, K1, K2, K3] + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def generalized_amplitude_damping( @@ -712,7 +744,7 @@ class PhaseDamping(DampingNoise): def __init__(self, gamma: float): super().__init__( gamma=gamma, - qubit_count=1, + qubit_count=None, ascii_symbols=["PD({:.2g})".format(gamma)], ) @@ -724,6 +756,10 @@ def to_matrix(self) -> Iterable[np.ndarray]: K1 = np.array([[0.0, 0.0], [0.0, np.sqrt(self.gamma)]], dtype=complex) return [K0, K1] + @staticmethod + def fixed_qubit_count() -> int: + return 1 + @staticmethod @circuit.subroutine(register=True) def phase_damping(target: QubitSetInput, gamma: float) -> Iterable[Instruction]: diff --git a/src/braket/circuits/quantum_operator.py b/src/braket/circuits/quantum_operator.py index f05f4821..94c5995d 100644 --- a/src/braket/circuits/quantum_operator.py +++ b/src/braket/circuits/quantum_operator.py @@ -10,9 +10,10 @@ # 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, List, Sequence +from typing import Any, List, Optional, Sequence import numpy as np @@ -22,10 +23,16 @@ class QuantumOperator(Operator): """A quantum operator is the definition of a quantum operation for a quantum device.""" - def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): + def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): """ Args: - qubit_count (int): Number of qubits this quantum operator interacts with. + qubit_count (int, optional): Number of qubits this quantum operator acts on. + If all instances of the operator act on the same number of qubits, this argument + should be ``None``, and ``fixed_qubit_count`` should be implemented to return + the qubit count; if ``fixed_qubit_count`` is implemented and an int is passed in, + it must equal ``fixed_qubit_count``, or instantiation will raise a ValueError. + An int must be passed in if instances can have a varying number of qubits, + in which case ``fixed_qubit_count`` should not be implemented, ascii_symbols (Sequence[str]): ASCII string symbols for the quantum operator. These are used when printing a diagram of circuits. Length must be the same as `qubit_count`, and index ordering is expected @@ -35,25 +42,57 @@ def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): correlate a symbol with that index. Raises: - ValueError: `qubit_count` is less than 1, `ascii_symbols` are `None`, or - `ascii_symbols` length != `qubit_count` + TypeError: `qubit_count` is not an int + ValueError: `qubit_count` is less than 1, `ascii_symbols` are `None`, + ``fixed_qubit_count`` is implemented and and not equal to ``qubit_count``, + or ``len(ascii_symbols) != qubit_count`` """ - if qubit_count < 1: - raise ValueError(f"qubit_count, {qubit_count}, must be greater than zero") - self._qubit_count = qubit_count + fixed_qubit_count = self.fixed_qubit_count() + if fixed_qubit_count is NotImplemented: + self._qubit_count = 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): + raise TypeError(f"qubit_count, {self._qubit_count}, must be an integer") + + if self._qubit_count < 1: + raise ValueError(f"qubit_count, {self._qubit_count}, must be greater than zero") if ascii_symbols is None: raise ValueError("ascii_symbols must not be None") - if len(ascii_symbols) != qubit_count: - msg = f"ascii_symbols, {ascii_symbols}, length must equal qubit_count, {qubit_count}" + if len(ascii_symbols) != self._qubit_count: + msg = ( + f"ascii_symbols, {ascii_symbols}," + f" length must equal qubit_count, {self._qubit_count}" + ) raise ValueError(msg) self._ascii_symbols = tuple(ascii_symbols) + @staticmethod + def fixed_qubit_count() -> int: + """ + 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, + this method returns ``NotImplemented``. + + Returns: + int: The number of qubits this quantum operator acts on. + """ + return NotImplemented + @property def qubit_count(self) -> int: - """int: Returns number of qubits this quantum operator interacts with.""" + """int: The number of qubits this quantum operator acts on.""" return self._qubit_count @property diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 4b5696b9..817e5c4a 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -296,6 +296,14 @@ def test_gate_to_matrix(testclass, subroutine_name, irclass, irsubclasses, kwarg assert gate1.matrix_equivalence(gate2) +@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) +def test_fixed_qubit_count(testclass, subroutine_name, irclass, irsubclasses, kwargs): + fixed_qubit_count = testclass.fixed_qubit_count() + if fixed_qubit_count is not NotImplemented: + gate = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) + assert gate.qubit_count == fixed_qubit_count + + # Additional Unitary gate tests diff --git a/test/unit_tests/braket/circuits/test_noise_helpers.py b/test/unit_tests/braket/circuits/test_noise_helpers.py index 5fc0af8e..9d5bf1c7 100644 --- a/test/unit_tests/braket/circuits/test_noise_helpers.py +++ b/test/unit_tests/braket/circuits/test_noise_helpers.py @@ -23,7 +23,7 @@ from braket.circuits.qubit_set import QubitSet invalid_data_noise_type = [Gate.X(), None, 1.5] -invalid_data_target_gates_type = [([-1, "foo"]), ([1.5, None, -1]), "X", ([Gate.X, "CNot"])] +invalid_data_target_gates_type = [[-1, "foo"], [1.5, None, -1], "X", [Gate.X, "CNot"]] invalid_data_target_qubits_value = [-1] invalid_data_target_qubits_type = [1.5, "foo", ["foo", 1]] invalid_data_target_unitary_value = [np.array([[0, 0], [1, 0]])] @@ -35,6 +35,11 @@ def circuit_2qubit(): return Circuit().x(0).y(1).x(0).x(1).cnot(0, 1) +@pytest.fixture +def circuit_2qubit_parametrized(): + return Circuit().x(0).y(1).x(0).rx(1, np.pi).xy(0, 1, np.pi / 2) + + @pytest.fixture def circuit_2qubit_with_unitary(): return Circuit().x(0).y(1).x(0).x(1).cnot(0, 1).unitary([0], matrix=np.array([[0, 1], [1, 0]])) @@ -177,6 +182,12 @@ def test_apply_readout_noise_invalid_target_qubits_type( circuit_2qubit.apply_readout_noise(noise_1qubit, target_qubits=target_qubits) +@pytest.mark.xfail(raises=ValueError) +def test_apply_gate_noise_fixed_qubit_count_not_implemented(noise_2qubit): + circ = Circuit().unitary([0, 1], matrix=np.eye(4)) + circ.apply_gate_noise(noise_2qubit, target_gates=Gate.Unitary) + + @pytest.mark.xfail(raises=ValueError) def test_apply_gate_noise_mismatch_qubit_count_with_target_gates(noise_2qubit): circ = Circuit().cswap(0, 1, 2) @@ -217,7 +228,29 @@ def test_apply_gate_noise_1QubitNoise_1(circuit_2qubit, noise_1qubit): assert circ == expected -def test_apply_gate_noise_1QubitNoise2_1(circuit_2qubit, noise_2qubit): +def test_apply_gate_noise_1QubitNoise_parametrized(circuit_2qubit_parametrized, noise_1qubit): + circ = circuit_2qubit_parametrized.apply_gate_noise( + noise_1qubit, + target_gates=[Gate.X, Gate.Rx], + target_qubits=[0, 1], + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(noise_1qubit, 0)) + .add_instruction(Instruction(Gate.Rx(np.pi), 1)) + .add_instruction(Instruction(noise_1qubit, 1)) + .add_instruction(Instruction(Gate.XY(np.pi / 2), [0, 1])) + ) + + assert circ == expected + + +def test_apply_gate_noise_2QubitNoise(circuit_2qubit, noise_2qubit): circ = circuit_2qubit.apply_gate_noise( noise_2qubit, target_gates=[Gate.CNot], @@ -237,6 +270,26 @@ def test_apply_gate_noise_1QubitNoise2_1(circuit_2qubit, noise_2qubit): assert circ == expected +def test_apply_gate_noise_2QubitNoise2_parametrized(circuit_2qubit_parametrized, noise_2qubit): + circ = circuit_2qubit_parametrized.apply_gate_noise( + noise_2qubit, + target_gates=[Gate.XY], + target_qubits=[0, 1], + ) + + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Y(), 1)) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.Rx(np.pi), 1)) + .add_instruction(Instruction(Gate.XY(np.pi / 2), [0, 1])) + .add_instruction(Instruction(noise_2qubit, [0, 1])) + ) + + assert circ == expected + + def test_apply_gate_noise_1QubitNoise_1_unitary(circuit_2qubit_with_unitary, noise_1qubit): circ = circuit_2qubit_with_unitary.apply_gate_noise( noise_1qubit, @@ -516,7 +569,7 @@ def test_noise_not_applied_1QubitNoise_1(circuit_2qubit, noise_2qubit): assert circ == expected -def test_apply_multipe_noise_1QubitNoise_1(circuit_2qubit, noise_1qubit, noise_1qubit_2): +def test_apply_multiple_noise_1QubitNoise_1(circuit_2qubit, noise_1qubit, noise_1qubit_2): circ = circuit_2qubit.apply_gate_noise(noise_1qubit).apply_readout_noise( noise_1qubit_2, target_qubits=[0, 1], @@ -542,7 +595,7 @@ def test_apply_multipe_noise_1QubitNoise_1(circuit_2qubit, noise_1qubit, noise_1 assert circ == expected -def test_apply_multipe_noise_1QubitNoise_2(circuit_2qubit, noise_1qubit, noise_1qubit_2): +def test_apply_multiple_noise_1QubitNoise_2(circuit_2qubit, noise_1qubit, noise_1qubit_2): circ = circuit_2qubit.apply_gate_noise(noise_1qubit, target_gates=[Gate.X],).apply_gate_noise( noise_1qubit_2, target_qubits=[0], diff --git a/test/unit_tests/braket/circuits/test_noises.py b/test/unit_tests/braket/circuits/test_noises.py index 765aad8c..fe6adde9 100644 --- a/test/unit_tests/braket/circuits/test_noises.py +++ b/test/unit_tests/braket/circuits/test_noises.py @@ -372,6 +372,14 @@ def test_noise_to_matrix(testclass, subroutine_name, irclass, irsubclasses, kwar assert all(np.allclose(m1, m2) for m1, m2 in zip(noise1.to_matrix(), noise2.to_matrix())) +@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) +def test_fixed_qubit_count(testclass, subroutine_name, irclass, irsubclasses, kwargs): + fixed_qubit_count = testclass.fixed_qubit_count() + if fixed_qubit_count is not NotImplemented: + noise = testclass(**create_valid_noise_class_input(irsubclasses, **kwargs)) + assert noise.qubit_count == fixed_qubit_count + + # Additional Unitary noise tests diff --git a/test/unit_tests/braket/circuits/test_quantum_operator.py b/test/unit_tests/braket/circuits/test_quantum_operator.py index 842fa077..8ba53c68 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator.py @@ -17,6 +17,12 @@ from braket.circuits import Operator, QuantumOperator +class _DummyQuantumOperator(QuantumOperator): + @staticmethod + def fixed_qubit_count(): + return 2 + + @pytest.fixture def quantum_operator(): return QuantumOperator(qubit_count=1, ascii_symbols=["foo"]) @@ -26,6 +32,21 @@ def test_is_operator(quantum_operator): assert isinstance(quantum_operator, Operator) +def test_fixed_qubit_count_implemented(): + operator = _DummyQuantumOperator(qubit_count=None, ascii_symbols=["foo", "bar"]) + assert operator.qubit_count == _DummyQuantumOperator.fixed_qubit_count() + + +@pytest.mark.xfail(raises=ValueError) +def test_qubit_count_fixed_qubit_count_unequal(): + _DummyQuantumOperator(qubit_count=1, ascii_symbols=["foo", "bar"]) + + +@pytest.mark.xfail(raises=TypeError) +def test_qubit_count_not_int(): + QuantumOperator(qubit_count="hello", ascii_symbols=[]) + + @pytest.mark.xfail(raises=ValueError) def test_qubit_count_lt_one(): QuantumOperator(qubit_count=0, ascii_symbols=[]) From 4afe1cbf459910fdafadd8192dfbff9720ae00bf Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 23 Jun 2021 18:17:01 +0000 Subject: [PATCH 0347/1165] prepare release v1.6.5 --- 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 65413f84..10fb2ce4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.6.5 (2021-06-23) + +### Bug Fixes and Other Changes + + * Get qubit count without instantiating op + * Require qubit indices to be integers + ## v1.6.4 (2021-06-10) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 9c064ef8..a3983bdc 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.6.5.dev0" +__version__ = "1.6.5" From d4c6fa66c2d46b95bd718c34103ee2aa6550b828 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 23 Jun 2021 18:17:01 +0000 Subject: [PATCH 0348/1165] update development version to v1.6.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 a3983bdc..b0a0aeea 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.6.5" +__version__ = "1.6.6.dev0" From cd60918b98fdb9d60f25a129cc9a3463df1c719c Mon Sep 17 00:00:00 2001 From: Enrique de la Torre Date: Thu, 24 Jun 2021 23:26:11 +0200 Subject: [PATCH 0349/1165] feature: code Circuit.as_unitary() (#253) * feature: code Circuit.as_unitary() * feature: code Circuit.as_unitary() - UTs: extend with noise * feature: code Circuit.as_unitary() - warn if qubit_count >= 10 * feature: code Circuit.as_unitary() - noise operators raise error * feature: code Circuit.as_unitary() - extend warn if qubit_count >= 10 * feature: code Circuit.as_unitary() - no noise applied raises error * feature: code Circuit.as_unitary() - revert warn if qubit_count >= 10 * feature: code Circuit.as_unitary() - clarify doc. & return [] if empty circuit * feature: code Circuit.as_unitary() - extend tests with result types * feature: code braket.circuits.unitary_calculation.as_unitary() * feature: code Circuit.as_unitary() - implement with braket.circuits.unitary_calculation.calculate_unitary() --- src/braket/circuits/circuit.py | 35 ++ src/braket/circuits/gates.py | 2 +- src/braket/circuits/unitary_calculation.py | 86 ++++ .../braket/circuits/test_circuit.py | 400 ++++++++++++++++++ 4 files changed, 522 insertions(+), 1 deletion(-) create mode 100644 src/braket/circuits/unitary_calculation.py diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 04bec0f5..ef13182b 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -35,6 +35,7 @@ from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput from braket.circuits.result_type import ObservableResultType, ResultType +from braket.circuits.unitary_calculation import calculate_unitary from braket.ir.jaqcd import Program SubroutineReturn = TypeVar( @@ -877,6 +878,40 @@ def to_ir(self) -> Program: basis_rotation_instructions=ir_basis_rotation_instructions, ) + def as_unitary(self) -> np.ndarray: + """ + Returns the unitary matrix representation of the entire circuit. + *Note*: The performance of this method degrades with qubit count. It might be slow for + qubit count > 10. + + Returns: + np.ndarray: A numpy array with shape (2^qubit_count, 2^qubit_count) representing the + circuit as a unitary. *Note*: For an empty circuit, an empty numpy array is + returned (`array([], dtype=complex128)`) + + Raises: + TypeError: If circuit is not composed only of `Gate` instances, + i.e. a circuit with `Noise` operators will raise this error. + + Examples: + >>> circ = Circuit().h(0).cnot(0, 1) + >>> circ.as_unitary() + array([[ 0.70710678+0.j, 0.70710678+0.j, 0. +0.j, + 0. +0.j], + [ 0. +0.j, 0. +0.j, 0.70710678+0.j, + -0.70710678+0.j], + [ 0. +0.j, 0. +0.j, 0.70710678+0.j, + 0.70710678+0.j], + [ 0.70710678+0.j, -0.70710678+0.j, 0. +0.j, + 0. +0.j]]) + """ + qubits = self.qubits + if not qubits: + return np.zeros(0, dtype=complex) + qubit_count = max(qubits) + 1 + + return calculate_unitary(qubit_count, self.instructions) + def _copy(self) -> Circuit: copy = Circuit().add(self.instructions) copy.add(self.result_types) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index ab65f61f..2a058a7b 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -1101,7 +1101,7 @@ def to_ir(self, target: QubitSet): return ir.CZ.construct(control=target[0], target=target[1]) def to_matrix(self) -> np.ndarray: - return np.diag([1.0, 1.0, 1.0, -1.0]) + return np.diag([complex(1.0), 1.0, 1.0, -1.0]) @staticmethod def fixed_qubit_count() -> int: diff --git a/src/braket/circuits/unitary_calculation.py b/src/braket/circuits/unitary_calculation.py new file mode 100644 index 00000000..2714b811 --- /dev/null +++ b/src/braket/circuits/unitary_calculation.py @@ -0,0 +1,86 @@ +# Copyright 2019-2019 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 typing import Iterable + +import numpy as np + +from braket.circuits.gate import Gate +from braket.circuits.instruction import Instruction +from braket.circuits.qubit_set import QubitSet + + +def _einsum_subscripts(targets: QubitSet, qubit_count: int) -> str: + target_count = len(targets) + + gate_left_indexes = list(range(target_count)) + un_left_indexes = list(range(target_count, target_count + qubit_count)) + un_right_indexes = list(range(target_count + qubit_count, target_count + 2 * qubit_count)) + + gate_right_indexes = [un_left_indexes[-1 - target] for target in targets] + + result_left_indexes = un_left_indexes.copy() + for pos, target in enumerate(targets): + result_left_indexes[-1 - target] = gate_left_indexes[pos] + + return ( + gate_left_indexes + gate_right_indexes, + un_left_indexes + un_right_indexes, + result_left_indexes + un_right_indexes, + ) + + +def calculate_unitary(qubit_count: int, instructions: Iterable[Instruction]) -> np.ndarray: + """ + Returns the unitary matrix representation for all the `instructions` with a given + `qubit_count`. + *Note*: The performance of this method degrades with qubit count. It might be slow for + qubit count > 10. + + Args: + qubit_count (int): Total number of qubits, enough for all the `instructions`. + instructions (Iterable[Instruction]): The instructions for which the unitary matrix + will be calculated. + + Returns: + np.ndarray: A numpy array with shape (2^qubit_count, 2^qubit_count) representing the + `instructions` as a unitary. + + Raises: + TypeError: If `instructions` is not composed only of `Gate` instances, + i.e. a circuit with `Noise` operators will raise this error. + """ + unitary = np.eye(2 ** qubit_count, dtype=complex) + un_tensor = np.reshape(unitary, qubit_count * [2, 2]) + + for instr in instructions: + if not isinstance(instr.operator, Gate): + raise TypeError("Only Gate operators are supported to build the unitary") + + matrix = instr.operator.to_matrix() + targets = instr.target + + gate_indexes, un_indexes, result_indexes = _einsum_subscripts(targets, qubit_count) + gate_matrix = np.reshape(matrix, len(targets) * [2, 2]) + + un_tensor = np.einsum( + gate_matrix, + gate_indexes, + un_tensor, + un_indexes, + result_indexes, + dtype=complex, + casting="no", + ) + + return np.reshape(un_tensor, 2 * [2 ** qubit_count]) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 88cfe4c5..130cf2f5 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -27,6 +27,8 @@ QubitSet, ResultType, circuit, + gates, + noise, ) @@ -447,6 +449,404 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): assert circ.to_ir() == expected +def test_as_unitary_empty_instructions_returns_empty_array(): + circ = Circuit() + circ.as_unitary() == [] + + +@pytest.mark.parametrize( + "circuit", + [ + (Circuit().phaseshift(0, 0.15).apply_gate_noise(noise.Noise.BitFlip(probability=0.1))), + (Circuit().cnot(1, 0).apply_gate_noise(noise.Noise.TwoQubitDepolarizing(probability=0.1))), + ( + Circuit() + .x(1) + .i(2) + .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[1]) + ), + ( + Circuit() + .x(1) + .i(2) + .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[2]) + ), + (Circuit().x(1).i(2).apply_gate_noise(noise.Noise.BitFlip(probability=0.1))), + (Circuit().x(1).apply_gate_noise(noise.Noise.BitFlip(probability=0.1)).i(2)), + ( + Circuit() + .y(1) + .z(2) + .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[1]) + ), + ( + Circuit() + .y(1) + .z(2) + .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[2]) + ), + (Circuit().y(1).z(2).apply_gate_noise(noise.Noise.BitFlip(probability=0.1))), + (Circuit().y(1).apply_gate_noise(noise.Noise.BitFlip(probability=0.1)).z(2)), + ( + Circuit() + .cphaseshift(2, 1, 0.15) + .si(3) + .apply_gate_noise( + noise.Noise.TwoQubitDepolarizing(probability=0.1), target_qubits=[1, 2] + ) + ), + ( + Circuit() + .cphaseshift(2, 1, 0.15) + .apply_gate_noise(noise.Noise.TwoQubitDepolarizing(probability=0.1)) + .si(3) + ), + ], +) +@pytest.mark.xfail(raises=TypeError) +def test_as_unitary_noise_raises_error(circuit): + circuit.as_unitary() + + +def test_as_unitary_noise_not_apply_returns_expected_unitary(recwarn): + circuit = ( + Circuit() + .cphaseshift(2, 1, 0.15) + .si(3) + .apply_gate_noise(noise.Noise.TwoQubitDepolarizing(probability=0.1), target_qubits=[1, 3]) + ) + + assert len(recwarn) == 1 + assert str(recwarn[0].message).startswith("Noise is not applied to any gate") + + assert np.allclose( + circuit.as_unitary(), + np.kron(gates.Si().to_matrix(), np.kron(gates.CPhaseShift(0.15).to_matrix(), np.eye(2))), + ) + + +@pytest.mark.parametrize( + "circuit,expected_unitary", + [ + (Circuit().h(0), gates.H().to_matrix()), + (Circuit().h(0).add_result_type(ResultType.Probability(target=[0])), gates.H().to_matrix()), + (Circuit().x(0), gates.X().to_matrix()), + (Circuit().y(0), gates.Y().to_matrix()), + (Circuit().z(0), gates.Z().to_matrix()), + (Circuit().s(0), gates.S().to_matrix()), + (Circuit().si(0), gates.Si().to_matrix()), + (Circuit().t(0), gates.T().to_matrix()), + (Circuit().ti(0), gates.Ti().to_matrix()), + (Circuit().v(0), gates.V().to_matrix()), + (Circuit().vi(0), gates.Vi().to_matrix()), + (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().phaseshift(0, 0.15), gates.PhaseShift(0.15).to_matrix()), + (Circuit().cnot(1, 0), gates.CNot().to_matrix()), + (Circuit().cnot(1, 0).add_result_type(ResultType.StateVector()), gates.CNot().to_matrix()), + (Circuit().swap(1, 0), gates.Swap().to_matrix()), + (Circuit().swap(0, 1), gates.Swap().to_matrix()), + (Circuit().iswap(1, 0), gates.ISwap().to_matrix()), + (Circuit().iswap(0, 1), gates.ISwap().to_matrix()), + (Circuit().pswap(1, 0, 0.15), gates.PSwap(0.15).to_matrix()), + (Circuit().pswap(0, 1, 0.15), gates.PSwap(0.15).to_matrix()), + (Circuit().xy(1, 0, 0.15), gates.XY(0.15).to_matrix()), + (Circuit().xy(0, 1, 0.15), gates.XY(0.15).to_matrix()), + (Circuit().cphaseshift(1, 0, 0.15), gates.CPhaseShift(0.15).to_matrix()), + (Circuit().cphaseshift00(1, 0, 0.15), gates.CPhaseShift00(0.15).to_matrix()), + (Circuit().cphaseshift01(1, 0, 0.15), gates.CPhaseShift01(0.15).to_matrix()), + (Circuit().cphaseshift10(1, 0, 0.15), gates.CPhaseShift10(0.15).to_matrix()), + (Circuit().cy(1, 0), gates.CY().to_matrix()), + (Circuit().cz(1, 0), gates.CZ().to_matrix()), + (Circuit().xx(1, 0, 0.15), gates.XX(0.15).to_matrix()), + (Circuit().yy(1, 0, 0.15), gates.YY(0.15).to_matrix()), + (Circuit().zz(1, 0, 0.15), gates.ZZ(0.15).to_matrix()), + (Circuit().ccnot(2, 1, 0), gates.CCNot().to_matrix()), + ( + Circuit() + .ccnot(2, 1, 0) + .add_result_type(ResultType.Expectation(observable=Observable.Y(), target=[1])), + gates.CCNot().to_matrix(), + ), + (Circuit().ccnot(1, 2, 0), gates.CCNot().to_matrix()), + (Circuit().cswap(2, 1, 0), gates.CSwap().to_matrix()), + (Circuit().cswap(2, 0, 1), gates.CSwap().to_matrix()), + (Circuit().h(1), np.kron(gates.H().to_matrix(), np.eye(2))), + (Circuit().x(1).i(2), np.kron(np.eye(2), np.kron(gates.X().to_matrix(), np.eye(2)))), + ( + Circuit().y(1).z(2), + np.kron(gates.Z().to_matrix(), np.kron(gates.Y().to_matrix(), np.eye(2))), + ), + (Circuit().rx(1, 0.15), np.kron(gates.Rx(0.15).to_matrix(), np.eye(2))), + ( + Circuit().ry(1, 0.15).i(2), + np.kron(np.eye(2), np.kron(gates.Ry(0.15).to_matrix(), np.eye(2))), + ), + ( + Circuit().rz(1, 0.15).s(2), + np.kron(gates.S().to_matrix(), np.kron(gates.Rz(0.15).to_matrix(), np.eye(2))), + ), + (Circuit().pswap(2, 1, 0.15), np.kron(gates.PSwap(0.15).to_matrix(), np.eye(2))), + (Circuit().pswap(1, 2, 0.15), np.kron(gates.PSwap(0.15).to_matrix(), np.eye(2))), + ( + Circuit().xy(2, 1, 0.15).i(3), + np.kron(np.eye(2), np.kron(gates.XY(0.15).to_matrix(), np.eye(2))), + ), + ( + Circuit().xy(1, 2, 0.15).i(3), + np.kron(np.eye(2), np.kron(gates.XY(0.15).to_matrix(), np.eye(2))), + ), + ( + Circuit().cphaseshift(2, 1, 0.15).si(3), + np.kron( + gates.Si().to_matrix(), np.kron(gates.CPhaseShift(0.15).to_matrix(), np.eye(2)) + ), + ), + (Circuit().ccnot(3, 2, 1), np.kron(gates.CCNot().to_matrix(), np.eye(2))), + (Circuit().ccnot(2, 3, 1), np.kron(gates.CCNot().to_matrix(), np.eye(2))), + ( + Circuit().cswap(3, 2, 1).i(4), + np.kron(np.eye(2), np.kron(gates.CSwap().to_matrix(), np.eye(2))), + ), + ( + Circuit().cswap(3, 1, 2).i(4), + np.kron(np.eye(2), np.kron(gates.CSwap().to_matrix(), np.eye(2))), + ), + ( + Circuit().cswap(3, 2, 1).t(4), + np.kron(gates.T().to_matrix(), np.kron(gates.CSwap().to_matrix(), np.eye(2))), + ), + ( + Circuit().cswap(3, 1, 2).t(4), + np.kron(gates.T().to_matrix(), np.kron(gates.CSwap().to_matrix(), np.eye(2))), + ), + (Circuit().h(0).h(0), gates.I().to_matrix()), + (Circuit().h(0).x(0), np.dot(gates.X().to_matrix(), gates.H().to_matrix())), + (Circuit().x(0).h(0), np.dot(gates.H().to_matrix(), gates.X().to_matrix())), + ( + Circuit().y(0).z(1).cnot(1, 0), + np.dot(gates.CNot().to_matrix(), np.kron(gates.Z().to_matrix(), gates.Y().to_matrix())), + ), + ( + Circuit().z(0).y(1).cnot(1, 0), + np.dot(gates.CNot().to_matrix(), np.kron(gates.Y().to_matrix(), gates.Z().to_matrix())), + ), + ( + Circuit().z(0).y(1).cnot(1, 0).cnot(2, 1), + np.dot( + np.dot( + np.dot( + np.kron(gates.CNot().to_matrix(), np.eye(2)), + np.kron(np.eye(2), gates.CNot().to_matrix()), + ), + np.kron(np.kron(np.eye(2), gates.Y().to_matrix()), np.eye(2)), + ), + np.kron(np.eye(4), gates.Z().to_matrix()), + ), + ), + ( + Circuit().z(0).y(1).cnot(1, 0).ccnot(2, 1, 0), + np.dot( + np.dot( + np.dot( + gates.CCNot().to_matrix(), + np.kron(np.eye(2), gates.CNot().to_matrix()), + ), + np.kron(np.kron(np.eye(2), gates.Y().to_matrix()), np.eye(2)), + ), + np.kron(np.eye(4), gates.Z().to_matrix()), + ), + ), + ( + Circuit().cnot(0, 1), + np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + ], + dtype=complex, + ), + ), + ( + Circuit().ccnot(0, 1, 2), + np.array( + [ + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], + ], + dtype=complex, + ), + ), + ( + Circuit().ccnot(1, 0, 2), + np.array( + [ + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], + ], + dtype=complex, + ), + ), + ( + Circuit().ccnot(0, 2, 1), + np.array( + [ + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], + ], + dtype=complex, + ), + ), + ( + Circuit().ccnot(2, 0, 1), + np.array( + [ + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], + ], + dtype=complex, + ), + ), + ( + Circuit().s(0).v(1).cnot(0, 1).cnot(1, 2), + np.dot( + np.dot( + np.dot( + np.kron( + np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + ], + dtype=complex, + ), + np.eye(2), + ), + np.kron( + np.eye(2), + np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + ], + dtype=complex, + ), + ), + ), + np.kron(np.kron(np.eye(2), gates.V().to_matrix()), np.eye(2)), + ), + np.kron(np.eye(4), gates.S().to_matrix()), + ), + ), + ( + Circuit().z(0).y(1).cnot(0, 1).ccnot(0, 1, 2), + np.dot( + np.dot( + np.dot( + np.array( + [ + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], + ], + dtype=complex, + ), + np.kron( + np.eye(2), + np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + ], + dtype=complex, + ), + ), + ), + np.kron(np.kron(np.eye(2), gates.Y().to_matrix()), np.eye(2)), + ), + np.kron(np.eye(4), gates.Z().to_matrix()), + ), + ), + ( + Circuit().z(0).y(1).cnot(0, 1).ccnot(2, 0, 1), + np.dot( + np.dot( + np.dot( + np.array( + [ + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], + ], + dtype=complex, + ), + np.kron( + np.eye(2), + np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + ], + dtype=complex, + ), + ), + ), + np.kron(np.kron(np.eye(2), gates.Y().to_matrix()), np.eye(2)), + ), + np.kron(np.eye(4), gates.Z().to_matrix()), + ), + ), + ], +) +def test_as_unitary_one_gate_returns_expected_unitary(circuit, expected_unitary): + assert np.allclose(circuit.as_unitary(), expected_unitary) + + def test_basis_rotation_instructions_all(): circ = Circuit().h(0).cnot(0, 1).sample(observable=Observable.Y()) expected = [ From d239f92fe7af12bc6ef091273728e29a2bc9a39f Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Thu, 24 Jun 2021 15:55:58 -0700 Subject: [PATCH 0350/1165] allow integral number types that aren't type int (#255) For example, np.int64 --- src/braket/circuits/qubit.py | 3 ++- test/unit_tests/braket/circuits/test_qubit.py | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/braket/circuits/qubit.py b/src/braket/circuits/qubit.py index a1830b2c..63843bc0 100644 --- a/src/braket/circuits/qubit.py +++ b/src/braket/circuits/qubit.py @@ -13,6 +13,7 @@ from __future__ import annotations +import numbers from typing import Union QubitInput = Union["Qubit", int] @@ -36,7 +37,7 @@ def __new__(cls, index: int): >>> Qubit(0) >>> Qubit(1) """ - if not isinstance(index, int): + if not isinstance(index, numbers.Integral): raise TypeError(f"Supplied qubit index, {index}, must be an integer.") if index < 0: raise ValueError(f"Supplied qubit index, {index}, cannot be less than zero.") diff --git a/test/unit_tests/braket/circuits/test_qubit.py b/test/unit_tests/braket/circuits/test_qubit.py index cb2399e7..37c78244 100644 --- a/test/unit_tests/braket/circuits/test_qubit.py +++ b/test/unit_tests/braket/circuits/test_qubit.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. +import numpy as np import pytest from braket.circuits import Qubit @@ -32,9 +33,9 @@ def test_index_non_int(qubit_arg): Qubit(qubit_arg) -def test_index_gte_zero(): - Qubit(0) - Qubit(5) +@pytest.mark.parametrize("qubit_index", (0, 5, np.int64(5))) +def test_index_gte_zero(qubit_index): + Qubit(qubit_index) def test_str(qubit): From 7da4ed635dbaafd8abdd138fda90ec4e25998f50 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 25 Jun 2021 01:42:10 +0000 Subject: [PATCH 0351/1165] prepare release v1.7.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 10fb2ce4..4621551e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.7.0 (2021-06-25) + +### Features + + * code Circuit.as_unitary() + +### Bug Fixes and Other Changes + + * allow integral number types that aren't type int + ## v1.6.5 (2021-06-23) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index b0a0aeea..08cd420e 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.6.6.dev0" +__version__ = "1.7.0" From abfd88ff505b2dd6e9942c0abd5ef27e5eb14926 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 25 Jun 2021 01:42:10 +0000 Subject: [PATCH 0352/1165] update development version to v1.7.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 08cd420e..c3702617 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.7.0" +__version__ = "1.7.1.dev0" From 8711b2ec8d1f16102e54cf416d44c9958140e2e0 Mon Sep 17 00:00:00 2001 From: Jacky Ko <60188942+kjacky@users.noreply.github.com> Date: Fri, 2 Jul 2021 22:51:38 +1000 Subject: [PATCH 0353/1165] Update test_circuit.py --- test/unit_tests/braket/circuits/test_circuit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 130cf2f5..9b0fae66 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -81,7 +81,7 @@ def test_repr_result_types(cnot_prob): circuit = cnot_prob expected = ( f"Circuit('instructions': {list(circuit.instructions)}" - + f"result_types': {circuit.result_types})" + + f", 'result_types': {circuit.result_types})" ) assert repr(circuit) == expected From 008949e1479e377f2748047e33a643f7fcbe9815 Mon Sep 17 00:00:00 2001 From: Jacky Ko <60188942+kjacky@users.noreply.github.com> Date: Sat, 3 Jul 2021 04:55:43 +1000 Subject: [PATCH 0354/1165] fix: Result Type syntax in IR (#254) Co-authored-by: Cody Wang --- src/braket/circuits/circuit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index ef13182b..0baa421e 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -940,7 +940,7 @@ def __repr__(self) -> str: else: return ( f"Circuit('instructions': {list(self.instructions)}" - + f"result_types': {self.result_types})" + + f", 'result_types': {self.result_types})" ) def __str__(self): From 39ee3932bd2fc7aefebcd32bbb73cf531230166f Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 2 Jul 2021 20:32:43 +0000 Subject: [PATCH 0355/1165] prepare release v1.7.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 4621551e..f1f2f9f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.7.1 (2021-07-02) + +### Bug Fixes and Other Changes + + * Result Type syntax in IR + * Update test_circuit.py + ## v1.7.0 (2021-06-25) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index c3702617..cb85c991 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.7.1.dev0" +__version__ = "1.7.1" From 6926c1676dd5b465ef404614a44538c42ee2727d Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 2 Jul 2021 20:32:43 +0000 Subject: [PATCH 0356/1165] update development version to v1.7.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 cb85c991..c40d08a9 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.7.1" +__version__ = "1.7.2.dev0" From 2898252de47646d1489a6a5e746ecf274984dfbc Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 14 Jul 2021 10:06:17 -0700 Subject: [PATCH 0357/1165] change: add json validation for device schema in unit tests (#257) Co-authored-by: Abe Coull Co-authored-by: Cody Wang --- setup.py | 1 + test/unit_tests/braket/aws/test_aws_device.py | 286 ++++++++++-------- 2 files changed, 159 insertions(+), 128 deletions(-) diff --git a/setup.py b/setup.py index a8d1f2d7..e8f8a776 100644 --- a/setup.py +++ b/setup.py @@ -40,6 +40,7 @@ "black", "flake8", "isort", + "jsonschema", "pre-commit", "pylint", "pytest", diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 4bbc143f..c9802d11 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -24,6 +24,7 @@ run_and_assert, run_batch_and_assert, ) +from jsonschema import validate from braket.aws import AwsDevice, AwsDeviceType, AwsQuantumTask from braket.circuits import Circuit @@ -31,38 +32,46 @@ from braket.device_schema.rigetti import RigettiDeviceCapabilities from braket.device_schema.simulators import GateModelSimulatorDeviceCapabilities -MOCK_GATE_MODEL_QPU_CAPABILITIES_1 = RigettiDeviceCapabilities.parse_obj( - { - "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.jaqcd.program": { - "actionType": "braket.ir.jaqcd.program", - "version": ["1"], - "supportedOperations": ["H"], +MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_1 = { + "braketSchemaHeader": { + "name": "braket.device_schema.rigetti.rigetti_device_capabilities", + "version": "1", + }, + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", } - }, - "paradigm": { - "qubitCount": 30, - "nativeGateSet": ["ccnot", "cy"], - "connectivity": {"fullyConnected": False, "connectivityGraph": {"1": ["2", "3"]}}, - }, - "deviceParameters": {}, - } + ], + "shotsRange": [1, 10], + }, + "action": { + "braket.ir.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": ["H"], + } + }, + "paradigm": { + "qubitCount": 30, + "nativeGateSet": ["ccnot", "cy"], + "connectivity": {"fullyConnected": False, "connectivityGraph": {"1": ["2", "3"]}}, + }, + "deviceParameters": {}, +} + + +MOCK_GATE_MODEL_QPU_CAPABILITIES_1 = RigettiDeviceCapabilities.parse_obj( + MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_1 ) + +def test_mock_regetti_schema_1(): + validate(MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_1, RigettiDeviceCapabilities.schema()) + + MOCK_GATE_MODEL_QPU_1 = { "deviceName": "Aspen-9", "deviceType": "QPU", @@ -71,38 +80,45 @@ "deviceCapabilities": MOCK_GATE_MODEL_QPU_CAPABILITIES_1.json(), } -MOCK_GATE_MODEL_QPU_CAPABILITIES_2 = RigettiDeviceCapabilities.parse_obj( - { - "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.jaqcd.program": { - "actionType": "braket.ir.jaqcd.program", - "version": ["1"], - "supportedOperations": ["H"], +MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_2 = { + "braketSchemaHeader": { + "name": "braket.device_schema.rigetti.rigetti_device_capabilities", + "version": "1", + }, + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", } - }, - "paradigm": { - "qubitCount": 30, - "nativeGateSet": ["ccnot", "cy"], - "connectivity": {"fullyConnected": True, "connectivityGraph": {}}, - }, - "deviceParameters": {}, - } + ], + "shotsRange": [1, 10], + }, + "action": { + "braket.ir.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": ["H"], + } + }, + "paradigm": { + "qubitCount": 30, + "nativeGateSet": ["ccnot", "cy"], + "connectivity": {"fullyConnected": True, "connectivityGraph": {}}, + }, + "deviceParameters": {}, +} + +MOCK_GATE_MODEL_QPU_CAPABILITIES_2 = RigettiDeviceCapabilities.parse_obj( + MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_2 ) + +def test_mock_regetti_schema_2(): + validate(MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_2, RigettiDeviceCapabilities.schema()) + + MOCK_GATE_MODEL_QPU_2 = { "deviceName": "Blah", "deviceType": "QPU", @@ -111,51 +127,56 @@ "deviceCapabilities": MOCK_GATE_MODEL_QPU_CAPABILITIES_2.json(), } -MOCK_DWAVE_QPU_CAPABILITIES = DwaveDeviceCapabilities.parse_obj( - { - "braketSchemaHeader": { - "name": "braket.device_schema.dwave.dwave_device_capabilities", - "version": "1", - }, - "provider": { - "annealingOffsetStep": 1.45, - "annealingOffsetStepPhi0": 1.45, - "annealingOffsetRanges": [[1.45, 1.45], [1.45, 1.45]], - "annealingDurationRange": [1, 2, 3], - "couplers": [[1, 2], [1, 2]], - "defaultAnnealingDuration": 1, - "defaultProgrammingThermalizationDuration": 1, - "defaultReadoutThermalizationDuration": 1, - "extendedJRange": [1, 2, 3], - "hGainScheduleRange": [1, 2, 3], - "hRange": [1, 2, 3], - "jRange": [1, 2, 3], - "maximumAnnealingSchedulePoints": 1, - "maximumHGainSchedulePoints": 1, - "perQubitCouplingRange": [1, 2, 3], - "programmingThermalizationDurationRange": [1, 2, 3], - "qubits": [1, 2, 3], - "qubitCount": 1, - "quotaConversionRate": 1, - "readoutThermalizationDurationRange": [1, 2, 3], - "taskRunDurationRange": [1, 2, 3], - "topology": {}, - }, - "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"], - } - }, - "deviceParameters": {}, - } -) +MOCK_DWAVE_QPU_CAPABILITIES_JSON = { + "braketSchemaHeader": { + "name": "braket.device_schema.dwave.dwave_device_capabilities", + "version": "1", + }, + "provider": { + "annealingOffsetStep": 1.45, + "annealingOffsetStepPhi0": 1.45, + "annealingOffsetRanges": [[1.45, 1.45], [1.45, 1.45]], + "annealingDurationRange": [1, 2, 3], + "couplers": [[1, 2], [1, 2]], + "defaultAnnealingDuration": 1, + "defaultProgrammingThermalizationDuration": 1, + "defaultReadoutThermalizationDuration": 1, + "extendedJRange": [1, 2, 3], + "hGainScheduleRange": [1, 2, 3], + "hRange": [1, 2, 3], + "jRange": [1, 2, 3], + "maximumAnnealingSchedulePoints": 1, + "maximumHGainSchedulePoints": 1, + "perQubitCouplingRange": [1, 2, 3], + "programmingThermalizationDurationRange": [1, 2, 3], + "qubits": [1, 2, 3], + "qubitCount": 1, + "quotaConversionRate": 1, + "readoutThermalizationDurationRange": [1, 2, 3], + "taskRunDurationRange": [1, 2, 3], + "topology": {}, + }, + "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"], + } + }, + "deviceParameters": {}, +} + +MOCK_DWAVE_QPU_CAPABILITIES = DwaveDeviceCapabilities.parse_obj(MOCK_DWAVE_QPU_CAPABILITIES_JSON) + + +def test_d_wave_schema(): + validate(MOCK_DWAVE_QPU_CAPABILITIES_JSON, DwaveDeviceCapabilities.schema()) + MOCK_DWAVE_QPU = { "deviceName": "Advantage_system1.1", @@ -165,34 +186,43 @@ "deviceCapabilities": MOCK_DWAVE_QPU_CAPABILITIES.json(), } -MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES = GateModelSimulatorDeviceCapabilities.parse_obj( - { - "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.jaqcd.program": { - "actionType": "braket.ir.jaqcd.program", - "version": ["1"], - "supportedOperations": ["H"], +MOCK_GATE_MODEL_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", } - }, - "paradigm": {"qubitCount": 30}, - "deviceParameters": {}, - } + ], + "shotsRange": [1, 10], + }, + "action": { + "braket.ir.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": ["H"], + } + }, + "paradigm": {"qubitCount": 30}, + "deviceParameters": {}, +} + +MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES = GateModelSimulatorDeviceCapabilities.parse_obj( + MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES_JSON ) + +def test_gate_model_sim_schema(): + validate( + MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES_JSON, GateModelSimulatorDeviceCapabilities.schema() + ) + + MOCK_GATE_MODEL_SIMULATOR = { "deviceName": "SV1", "deviceType": "SIMULATOR", From 7558c775a322b9cfc476f48285fd471e25d66f8c Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 14 Jul 2021 18:14:06 +0000 Subject: [PATCH 0358/1165] prepare release v1.7.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 f1f2f9f6..64c2956a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.7.2 (2021-07-14) + +### Bug Fixes and Other Changes + + * add json validation for device schema in unit tests + ## v1.7.1 (2021-07-02) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index c40d08a9..e3ff02ae 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.7.2.dev0" +__version__ = "1.7.2" From c20489cba65625e4e311c870e5c93de06c915837 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 14 Jul 2021 18:14:06 +0000 Subject: [PATCH 0359/1165] update development version to v1.7.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 e3ff02ae..68124c5b 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.7.2" +__version__ = "1.7.3.dev0" From 23d93f661763e2c26a3927ae1539d4872f354ee1 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 21 Jul 2021 15:19:52 -0700 Subject: [PATCH 0360/1165] change: Add json schema validation for dwave device schemas. (#258) --- .../braket/aws/test_aws_quantum_task.py | 209 +++++++++++++++++- 1 file changed, 198 insertions(+), 11 deletions(-) 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 db9d1b6a..67350379 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,7 @@ import pytest from common_test_utils import MockS3 +from jsonschema import validate from braket.annealing.problem import Problem, ProblemType from braket.aws import AwsQuantumTask @@ -414,53 +415,236 @@ def test_from_circuit_with_shots_value_error(aws_session, arn, circuit): "device_parameters,arn", [ ( - {"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}}, + { + "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", ), ( - {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION", "beta": 0.2}}, + { + "deviceLevelParameters": { + "postprocessingType": "OPTIMIZATION", + "beta": 0.2, + "annealingOffsets": [3.67, 6.123], + "annealingSchedule": [[13.37, 10.08], [3.14, 1.618]], + "annealingDuration": 1, + "autoScale": False, + "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", ), pytest.param( - {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION", "beta": 0.2}}, + { + "deviceLevelParameters": { + "postprocessingType": "OPTIMIZATION", + "beta": 0.2, + "annealingOffsets": [3.67, 6.123], + "annealingSchedule": [[13.37, 10.08], [3.14, 1.618]], + "annealingDuration": 1, + "autoScale": False, + "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", # this doesn't fail... yet # marks=pytest.mark.xfail(reason='beta not a valid parameter for Advantage device'), ), pytest.param( - {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION", "beta": 0.2}}, + { + "deviceLevelParameters": { + "postprocessingType": "OPTIMIZATION", + "beta": 0.2, + "annealingOffsets": [3.67, 6.123], + "annealingSchedule": [[13.37, 10.08], [3.14, 1.618]], + "annealingDuration": 1, + "autoScale": False, + "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/fake_arn", marks=pytest.mark.xfail(reason="Bad ARN"), ), ( - {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION"}}, + { + "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", ), ( DwaveDeviceParameters.parse_obj( - {"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}} + { + "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", ), ( DwaveDeviceParameters.parse_obj( - {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION"}} + { + "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/Advantage_system1", ), ( DwaveAdvantageDeviceParameters.parse_obj( - {"deviceLevelParameters": {"autoScale": "False"}} + { + "deviceLevelParameters": { + "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", ), ( Dwave2000QDeviceParameters.parse_obj( - {"deviceLevelParameters": {"postprocessingType": "OPTIMIZATION"}} + { + "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", ), ( + Dwave2000QDeviceParameters.parse_obj({"deviceLevelParameters": {}}), + "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6", + ), + pytest.param( {}, "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6", ), @@ -480,14 +664,17 @@ def test_from_annealing(device_parameters, aws_session, arn, problem): assert task == AwsQuantumTask( mocked_task_arn, aws_session, AnnealingQuantumTaskResult.from_string ) - + annealing_parameters = _create_annealing_device_params(device_parameters, device_arn=arn) + validate( + json.loads(annealing_parameters.json(exclude_none=True)), annealing_parameters.schema() + ) _assert_create_quantum_task_called_with( aws_session, arn, problem, S3_TARGET, 1000, - _create_annealing_device_params(device_parameters, device_arn=arn), + annealing_parameters, ) From 87c79feb4fc2998cb512afda04fc3dc39991f6e3 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 22 Jul 2021 18:15:08 +0000 Subject: [PATCH 0361/1165] prepare release v1.7.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 64c2956a..82bd9b78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.7.3 (2021-07-22) + +### Bug Fixes and Other Changes + + * Add json schema validation for dwave device schemas. + ## v1.7.2 (2021-07-14) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 68124c5b..4c7f3e75 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.7.3.dev0" +__version__ = "1.7.3" From 9c4f94d8dd3b73453788d0544343e0399d6ea239 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 22 Jul 2021 18:15:08 +0000 Subject: [PATCH 0362/1165] update development version to v1.7.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 4c7f3e75..23d8442d 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.7.3" +__version__ = "1.7.4.dev0" From e167494fb66082c3d1a5728809da08d80da6feae Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Tue, 3 Aug 2021 17:00:20 -0700 Subject: [PATCH 0363/1165] doc: Modify README.md to include update instructions (#264) --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 5b1438e2..27f043cf 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,12 @@ You can also check your version of `amazon-braket-sdk` from within Python: >>> braket_sdk.__version__ ``` +### Updating the Amazon Braket Python SDK +You can update the version of the amazon-braket-sdk you have installed by using the following command: +```bash +pip install amazon-braket-sdk --upgrade --upgrade-strategy eager +``` + ## Usage ### Running a circuit on an AWS simulator From 88f9bb9f3c7ce470e2b92fd2ee169d38b4fb01f2 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 5 Aug 2021 00:19:20 +0000 Subject: [PATCH 0364/1165] prepare release v1.7.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 82bd9b78..7b9e9ae2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.7.3.post0 (2021-08-05) + +### Documentation Changes + + * Modify README.md to include update instructions + ## v1.7.3 (2021-07-22) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 23d8442d..b02785ce 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.7.4.dev0" +__version__ = "1.7.3.post0" From ccb285a5025768e28ab3bfe304e38a3c14d7950d Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 5 Aug 2021 00:19:20 +0000 Subject: [PATCH 0365/1165] update development version to v1.7.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 b02785ce..23d8442d 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.7.3.post0" +__version__ = "1.7.4.dev0" From 74905c286284164d7a1e05c8c0775474dd093b9b Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Fri, 6 Aug 2021 12:00:05 -0700 Subject: [PATCH 0366/1165] fix: Flatten Tensor Products (#263) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Abe Coull Co-authored-by: ℂ𝓞𝔇𝚈 --- src/braket/circuits/observable.py | 3 -- src/braket/circuits/observables.py | 28 +++++++------------ .../braket/circuits/test_observables.py | 15 ++++++---- 3 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index ad9be32b..f3af964e 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -71,9 +71,6 @@ def register_observable(cls, observable: Observable) -> None: setattr(cls, observable.__name__, observable) def __matmul__(self, other) -> Observable.TensorProduct: - if isinstance(other, Observable.TensorProduct): - return other.__rmatmul__(self) - if isinstance(other, Observable): return Observable.TensorProduct([self, other]) diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 06a61190..675c7945 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -181,9 +181,16 @@ def __init__(self, observables: List[Observable]): result of the tensor product of `ob1`, `ob2`, `ob3`, or `np.kron(np.kron(ob1.to_matrix(), ob2.to_matrix()), ob3.to_matrix())`. """ - self._factors = tuple(observables) - qubit_count = sum([obs.qubit_count for obs in observables]) - display_name = "@".join([obs.ascii_symbols[0] for obs in observables]) + flattened_observables = [] + for obs in observables: + if isinstance(obs, TensorProduct): + for nested_obs in obs.factors: + flattened_observables.append(nested_obs) + else: + flattened_observables.append(obs) + self._factors = tuple(flattened_observables) + qubit_count = sum([obs.qubit_count for obs in flattened_observables]) + display_name = "@".join([obs.ascii_symbols[0] for obs in flattened_observables]) super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) self._factor_dimensions = tuple( len(factor.to_matrix()) for factor in reversed(self._factors) @@ -238,21 +245,6 @@ def eigenvalue(self, index: int) -> float: self._eigenvalue_indices[index] = product return self._eigenvalue_indices[index] - def __matmul__(self, other): - if isinstance(other, TensorProduct): - return TensorProduct(list(self.factors) + list(other.factors)) - - if isinstance(other, Observable): - return TensorProduct(list(self.factors) + [other]) - - raise ValueError("Can only perform tensor products between observables.") - - def __rmatmul__(self, other): - if isinstance(other, Observable): - return TensorProduct([other] + list(self.factors)) - - raise ValueError("Can only perform tensor products between observables.") - def __repr__(self): return "TensorProduct(" + ", ".join([repr(o) for o in self.factors]) + ")" diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index 274a2b52..f07fcd3d 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -126,6 +126,16 @@ def test_hermitian_eigenvalues(matrix, eigenvalues): compare_eigenvalues(Observable.Hermitian(matrix=matrix), eigenvalues) +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()] + ) + assert expected == actual + + @pytest.mark.parametrize( "matrix,basis_rotation_matrix", [ @@ -228,11 +238,6 @@ def test_tensor_product_rmatmul_observable(): assert t.ascii_symbols == tuple(["I@Z@I@X"] * 4) -@pytest.mark.xfail(raises=ValueError) -def test_tensor_product_rmatmul_value_error(): - "a" @ Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) - - @pytest.mark.parametrize( "observable,eigenvalues", [ From 8f5da6be048b3a4107b9abb8a85816e01352069c Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 6 Aug 2021 21:25:00 +0000 Subject: [PATCH 0367/1165] prepare release v1.7.4 --- 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 7b9e9ae2..ba013c98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.7.4 (2021-08-06) + +### Bug Fixes and Other Changes + + * Flatten Tensor Products + ## v1.7.3.post0 (2021-08-05) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 23d8442d..a449edbe 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.7.4.dev0" +__version__ = "1.7.4" From 182df73d2d07d4bc9ee4666f4f88dd7e25cf4c98 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 6 Aug 2021 21:25:00 +0000 Subject: [PATCH 0368/1165] update development version to v1.7.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 a449edbe..13ebf00e 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.7.4" +__version__ = "1.7.5.dev0" From c8b148b1bcd32a4af67096f8fb74439e3e1ee6c7 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Tue, 17 Aug 2021 12:21:39 -0700 Subject: [PATCH 0369/1165] fix: Add test for local simulator device names (#266) --- test/integ_tests/test_local_braket_simulator.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/integ_tests/test_local_braket_simulator.py b/test/integ_tests/test_local_braket_simulator.py index e00642ff..c3c62cf4 100644 --- a/test/integ_tests/test_local_braket_simulator.py +++ b/test/integ_tests/test_local_braket_simulator.py @@ -110,3 +110,16 @@ def test_result_types_all_selected(shots): @pytest.mark.parametrize("shots", [0, SHOTS]) def test_result_types_observable_not_in_instructions(shots): result_types_observable_not_in_instructions(DEVICE, {"shots": shots}) + + +@pytest.mark.parametrize( + "backend, device_name", + [ + ("default", "StateVectorSimulator"), + ("braket_sv", "StateVectorSimulator"), + ("braket_dm", "DensityMatrixSimulator"), + ], +) +def test_local_simulator_device_names(backend, device_name): + local_simulator_device = LocalSimulator(backend) + assert local_simulator_device.name == device_name From 0d28a8fa89263daf5d88bc706e79200d8dc091a8 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 17 Aug 2021 18:49:42 -0600 Subject: [PATCH 0370/1165] infra: Update copyright notice (#265) --- bin/apply-header.py | 2 +- examples/bell.py | 2 +- examples/bell_result_types.py | 2 +- examples/debug_bell.py | 2 +- examples/local_bell.py | 2 +- examples/local_noise_simulation.py | 2 +- setup.py | 2 +- src/braket/_sdk/__init__.py | 2 +- src/braket/_sdk/_version.py | 2 +- src/braket/annealing/__init__.py | 2 +- src/braket/annealing/problem.py | 2 +- src/braket/aws/__init__.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 | 2 +- src/braket/circuits/__init__.py | 2 +- src/braket/circuits/angled_gate.py | 2 +- src/braket/circuits/ascii_circuit_diagram.py | 2 +- src/braket/circuits/circuit.py | 2 +- src/braket/circuits/circuit_diagram.py | 2 +- src/braket/circuits/circuit_helpers.py | 2 +- src/braket/circuits/gate.py | 2 +- src/braket/circuits/gates.py | 2 +- src/braket/circuits/instruction.py | 2 +- src/braket/circuits/moments.py | 2 +- src/braket/circuits/noise.py | 2 +- src/braket/circuits/noise_helpers.py | 2 +- src/braket/circuits/noises.py | 2 +- src/braket/circuits/observable.py | 2 +- src/braket/circuits/observables.py | 2 +- src/braket/circuits/operator.py | 2 +- src/braket/circuits/quantum_operator.py | 2 +- src/braket/circuits/quantum_operator_helpers.py | 2 +- src/braket/circuits/qubit.py | 2 +- src/braket/circuits/qubit_set.py | 2 +- src/braket/circuits/result_type.py | 2 +- src/braket/circuits/result_types.py | 2 +- src/braket/circuits/unitary_calculation.py | 2 +- src/braket/devices/__init__.py | 2 +- src/braket/devices/device.py | 2 +- src/braket/devices/local_simulator.py | 2 +- src/braket/ipython_utils.py | 2 +- src/braket/tasks/__init__.py | 2 +- src/braket/tasks/annealing_quantum_task_result.py | 2 +- src/braket/tasks/gate_model_quantum_task_result.py | 2 +- src/braket/tasks/local_quantum_task.py | 2 +- src/braket/tasks/quantum_task.py | 2 +- test/integ_tests/conftest.py | 2 +- test/integ_tests/gate_model_device_testing_utils.py | 2 +- test/integ_tests/test_aws_session_s3.py | 2 +- test/integ_tests/test_device_creation.py | 2 +- test/integ_tests/test_local_braket_simulator.py | 2 +- test/integ_tests/test_local_noise_simulator.py | 2 +- test/integ_tests/test_simulator_quantum_task.py | 2 +- test/integ_tests/test_tensor_network_simulator.py | 2 +- test/unit_tests/braket/annealing/test_problem.py | 2 +- test/unit_tests/braket/aws/common_test_utils.py | 2 +- test/unit_tests/braket/aws/test_aws_device.py | 2 +- test/unit_tests/braket/aws/test_aws_quantum_task.py | 2 +- test/unit_tests/braket/aws/test_aws_quantum_task_batch.py | 2 +- test/unit_tests/braket/aws/test_aws_session.py | 2 +- test/unit_tests/braket/circuits/test_angled_gate.py | 2 +- test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py | 2 +- test/unit_tests/braket/circuits/test_circuit.py | 2 +- test/unit_tests/braket/circuits/test_circuit_helpers.py | 2 +- test/unit_tests/braket/circuits/test_gate.py | 2 +- test/unit_tests/braket/circuits/test_gates.py | 2 +- test/unit_tests/braket/circuits/test_instruction.py | 2 +- test/unit_tests/braket/circuits/test_moments.py | 2 +- test/unit_tests/braket/circuits/test_noise.py | 2 +- test/unit_tests/braket/circuits/test_noise_helpers.py | 2 +- test/unit_tests/braket/circuits/test_noises.py | 2 +- test/unit_tests/braket/circuits/test_observable.py | 2 +- test/unit_tests/braket/circuits/test_observables.py | 2 +- test/unit_tests/braket/circuits/test_quantum_operator.py | 2 +- .../unit_tests/braket/circuits/test_quantum_operator_helpers.py | 2 +- test/unit_tests/braket/circuits/test_qubit.py | 2 +- test/unit_tests/braket/circuits/test_qubit_set.py | 2 +- test/unit_tests/braket/circuits/test_result_type.py | 2 +- test/unit_tests/braket/circuits/test_result_types.py | 2 +- test/unit_tests/braket/devices/test_local_simulator.py | 2 +- .../braket/tasks/test_annealing_quantum_task_result.py | 2 +- .../braket/tasks/test_gate_model_quantum_task_result.py | 2 +- test/unit_tests/braket/tasks/test_local_quantum_task.py | 2 +- test/unit_tests/braket/tasks/test_tasks_init.py | 2 +- test/unit_tests/braket/test_ipython_utils.py | 2 +- 87 files changed, 87 insertions(+), 87 deletions(-) diff --git a/bin/apply-header.py b/bin/apply-header.py index 7e7ffe31..4fed93a4 100644 --- a/bin/apply-header.py +++ b/bin/apply-header.py @@ -8,7 +8,7 @@ Usage: python bin/apply-header.py """ -HEADER = """# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +HEADER = """# 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 diff --git a/examples/bell.py b/examples/bell.py index 32039328..dfa81e52 100644 --- a/examples/bell.py +++ b/examples/bell.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/examples/bell_result_types.py b/examples/bell_result_types.py index 715a17fb..2bcb87b7 100644 --- a/examples/bell_result_types.py +++ b/examples/bell_result_types.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/examples/debug_bell.py b/examples/debug_bell.py index 4f181375..f1ff33fc 100644 --- a/examples/debug_bell.py +++ b/examples/debug_bell.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/examples/local_bell.py b/examples/local_bell.py index 7b70b1f9..c15b1b21 100644 --- a/examples/local_bell.py +++ b/examples/local_bell.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/examples/local_noise_simulation.py b/examples/local_noise_simulation.py index c6eff078..0857bfd0 100644 --- a/examples/local_noise_simulation.py +++ b/examples/local_noise_simulation.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/setup.py b/setup.py index e8f8a776..83ad33df 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/_sdk/__init__.py b/src/braket/_sdk/__init__.py index 709f2b60..b436b998 100644 --- a/src/braket/_sdk/__init__.py +++ b/src/braket/_sdk/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 13ebf00e..72cc4500 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/annealing/__init__.py b/src/braket/annealing/__init__.py index f4325051..7534b9d7 100644 --- a/src/braket/annealing/__init__.py +++ b/src/braket/annealing/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/annealing/problem.py b/src/braket/annealing/problem.py index c240fdbc..c101b938 100644 --- a/src/braket/annealing/problem.py +++ b/src/braket/annealing/problem.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/aws/__init__.py b/src/braket/aws/__init__.py index 3ad8bac3..08217d9a 100644 --- a/src/braket/aws/__init__.py +++ b/src/braket/aws/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index f44ea4f7..0e694870 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 2700aa3b..73eef4b4 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 208cadee..dbf66c8e 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 2969bd9b..b3b5c2aa 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py index 8faa713f..719f1b38 100644 --- a/src/braket/circuits/__init__.py +++ b/src/braket/circuits/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index 0c795ca7..f7a60e32 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index 978ceb48..f386898e 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 0baa421e..71a294a2 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/circuit_diagram.py b/src/braket/circuits/circuit_diagram.py index ad2fd7ef..06425670 100644 --- a/src/braket/circuits/circuit_diagram.py +++ b/src/braket/circuits/circuit_diagram.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/circuit_helpers.py b/src/braket/circuits/circuit_helpers.py index d62ca19a..f57e776c 100644 --- a/src/braket/circuits/circuit_helpers.py +++ b/src/braket/circuits/circuit_helpers.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index 310c9b77..42cac864 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 2a058a7b..096d6488 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index 06abd1fe..4a688dc4 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index c9479102..926193a9 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py index 2b5d7f7f..e5c0dab4 100644 --- a/src/braket/circuits/noise.py +++ b/src/braket/circuits/noise.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/noise_helpers.py b/src/braket/circuits/noise_helpers.py index 5f454bb7..492189f1 100644 --- a/src/braket/circuits/noise_helpers.py +++ b/src/braket/circuits/noise_helpers.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py index 71dff491..1f98e0fd 100644 --- a/src/braket/circuits/noises.py +++ b/src/braket/circuits/noises.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index f3af964e..19c97ff8 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 675c7945..0fc0c9e4 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/operator.py b/src/braket/circuits/operator.py index 71f0228f..7ead4b69 100644 --- a/src/braket/circuits/operator.py +++ b/src/braket/circuits/operator.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/quantum_operator.py b/src/braket/circuits/quantum_operator.py index 94c5995d..47fef1f2 100644 --- a/src/braket/circuits/quantum_operator.py +++ b/src/braket/circuits/quantum_operator.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/quantum_operator_helpers.py b/src/braket/circuits/quantum_operator_helpers.py index 76788dda..5a922100 100644 --- a/src/braket/circuits/quantum_operator_helpers.py +++ b/src/braket/circuits/quantum_operator_helpers.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/qubit.py b/src/braket/circuits/qubit.py index 63843bc0..3ffe1aee 100644 --- a/src/braket/circuits/qubit.py +++ b/src/braket/circuits/qubit.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/qubit_set.py b/src/braket/circuits/qubit_set.py index 9e03c527..2cae36f8 100644 --- a/src/braket/circuits/qubit_set.py +++ b/src/braket/circuits/qubit_set.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index 5bb03548..b846c0df 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 952ff879..e15a4d67 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/circuits/unitary_calculation.py b/src/braket/circuits/unitary_calculation.py index 2714b811..d532e17b 100644 --- a/src/braket/circuits/unitary_calculation.py +++ b/src/braket/circuits/unitary_calculation.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/devices/__init__.py b/src/braket/devices/__init__.py index 891de47f..5750714b 100644 --- a/src/braket/devices/__init__.py +++ b/src/braket/devices/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 5c2d8e91..20103653 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index a3876753..e44391bb 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/ipython_utils.py b/src/braket/ipython_utils.py index c499862a..c1ed5170 100644 --- a/src/braket/ipython_utils.py +++ b/src/braket/ipython_utils.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/tasks/__init__.py b/src/braket/tasks/__init__.py index 99dc33f9..ccf3fc14 100644 --- a/src/braket/tasks/__init__.py +++ b/src/braket/tasks/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/tasks/annealing_quantum_task_result.py b/src/braket/tasks/annealing_quantum_task_result.py index 87097aee..ce812553 100644 --- a/src/braket/tasks/annealing_quantum_task_result.py +++ b/src/braket/tasks/annealing_quantum_task_result.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 55084709..3e4a5714 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py index adc3213b..64edf7d5 100644 --- a/src/braket/tasks/local_quantum_task.py +++ b/src/braket/tasks/local_quantum_task.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py index 99b03751..84c444f0 100644 --- a/src/braket/tasks/quantum_task.py +++ b/src/braket/tasks/quantum_task.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py index 9096fc84..3c9edd2f 100644 --- a/test/integ_tests/conftest.py +++ b/test/integ_tests/conftest.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index e6cde655..6a92099b 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/integ_tests/test_aws_session_s3.py b/test/integ_tests/test_aws_session_s3.py index 11c85896..8b1a48d4 100644 --- a/test/integ_tests/test_aws_session_s3.py +++ b/test/integ_tests/test_aws_session_s3.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 38a3a6e0..a480e12c 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/integ_tests/test_local_braket_simulator.py b/test/integ_tests/test_local_braket_simulator.py index c3c62cf4..ccb66e5f 100644 --- a/test/integ_tests/test_local_braket_simulator.py +++ b/test/integ_tests/test_local_braket_simulator.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/integ_tests/test_local_noise_simulator.py b/test/integ_tests/test_local_noise_simulator.py index 0963fde6..a6e7803a 100644 --- a/test/integ_tests/test_local_noise_simulator.py +++ b/test/integ_tests/test_local_noise_simulator.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index c30d946e..22fec856 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/integ_tests/test_tensor_network_simulator.py b/test/integ_tests/test_tensor_network_simulator.py index bd793402..e96d0cbc 100644 --- a/test/integ_tests/test_tensor_network_simulator.py +++ b/test/integ_tests/test_tensor_network_simulator.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/annealing/test_problem.py b/test/unit_tests/braket/annealing/test_problem.py index 8cd90944..a80863af 100644 --- a/test/unit_tests/braket/annealing/test_problem.py +++ b/test/unit_tests/braket/annealing/test_problem.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 6980c52f..846bf165 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index c9802d11..8c10429f 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 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 67350379..8354207b 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 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 1dc6313d..cac0cef4 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 @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index e3247f7c..8735690c 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index 4e6b6fa1..f8aa27f4 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 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 48325e6a..0a6b1ea2 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 9b0fae66..1b8f43f8 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_circuit_helpers.py b/test/unit_tests/braket/circuits/test_circuit_helpers.py index 4805ce00..5dd627f2 100644 --- a/test/unit_tests/braket/circuits/test_circuit_helpers.py +++ b/test/unit_tests/braket/circuits/test_circuit_helpers.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_gate.py b/test/unit_tests/braket/circuits/test_gate.py index 56e881dd..dd54ef44 100644 --- a/test/unit_tests/braket/circuits/test_gate.py +++ b/test/unit_tests/braket/circuits/test_gate.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 817e5c4a..d91ea3e8 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_instruction.py b/test/unit_tests/braket/circuits/test_instruction.py index f66fddf7..3c4f1c99 100644 --- a/test/unit_tests/braket/circuits/test_instruction.py +++ b/test/unit_tests/braket/circuits/test_instruction.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_moments.py b/test/unit_tests/braket/circuits/test_moments.py index 12753400..d3349fd1 100644 --- a/test/unit_tests/braket/circuits/test_moments.py +++ b/test/unit_tests/braket/circuits/test_moments.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_noise.py b/test/unit_tests/braket/circuits/test_noise.py index af5ba3e8..edab29ac 100644 --- a/test/unit_tests/braket/circuits/test_noise.py +++ b/test/unit_tests/braket/circuits/test_noise.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_noise_helpers.py b/test/unit_tests/braket/circuits/test_noise_helpers.py index 9d5bf1c7..013831e6 100644 --- a/test/unit_tests/braket/circuits/test_noise_helpers.py +++ b/test/unit_tests/braket/circuits/test_noise_helpers.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_noises.py b/test/unit_tests/braket/circuits/test_noises.py index fe6adde9..0de9f0e9 100644 --- a/test/unit_tests/braket/circuits/test_noises.py +++ b/test/unit_tests/braket/circuits/test_noises.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_observable.py b/test/unit_tests/braket/circuits/test_observable.py index 00c1270f..2349e217 100644 --- a/test/unit_tests/braket/circuits/test_observable.py +++ b/test/unit_tests/braket/circuits/test_observable.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index f07fcd3d..7787feb8 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_quantum_operator.py b/test/unit_tests/braket/circuits/test_quantum_operator.py index 8ba53c68..cf2322f9 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py index 7cf798a2..1073d43d 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_qubit.py b/test/unit_tests/braket/circuits/test_qubit.py index 37c78244..7f72722e 100644 --- a/test/unit_tests/braket/circuits/test_qubit.py +++ b/test/unit_tests/braket/circuits/test_qubit.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_qubit_set.py b/test/unit_tests/braket/circuits/test_qubit_set.py index 9bae63d5..4a7eda39 100644 --- a/test/unit_tests/braket/circuits/test_qubit_set.py +++ b/test/unit_tests/braket/circuits/test_qubit_set.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_result_type.py b/test/unit_tests/braket/circuits/test_result_type.py index b0fedebb..0e1d8a0b 100644 --- a/test/unit_tests/braket/circuits/test_result_type.py +++ b/test/unit_tests/braket/circuits/test_result_type.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/circuits/test_result_types.py b/test/unit_tests/braket/circuits/test_result_types.py index e7672e69..c7ee7483 100644 --- a/test/unit_tests/braket/circuits/test_result_types.py +++ b/test/unit_tests/braket/circuits/test_result_types.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 63abd2e4..bf4f6e5a 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 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 5b173891..29d21e58 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 @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 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 7abf4ece..3e8277ac 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 @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 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 d04676da..6b583c60 100644 --- a/test/unit_tests/braket/tasks/test_local_quantum_task.py +++ b/test/unit_tests/braket/tasks/test_local_quantum_task.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/tasks/test_tasks_init.py b/test/unit_tests/braket/tasks/test_tasks_init.py index 65414bc8..610c29bc 100644 --- a/test/unit_tests/braket/tasks/test_tasks_init.py +++ b/test/unit_tests/braket/tasks/test_tasks_init.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 diff --git a/test/unit_tests/braket/test_ipython_utils.py b/test/unit_tests/braket/test_ipython_utils.py index ec1006c9..b275905a 100644 --- a/test/unit_tests/braket/test_ipython_utils.py +++ b/test/unit_tests/braket/test_ipython_utils.py @@ -1,4 +1,4 @@ -# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 From 913e4d4e23a925c92c60dcbe58005b24fd3be803 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Tue, 17 Aug 2021 18:18:48 -0700 Subject: [PATCH 0371/1165] doc: Add documentation for support (#261) --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 27f043cf..87a11467 100644 --- a/README.md +++ b/README.md @@ -215,5 +215,19 @@ As with unit tests, you can also pass in various pytest arguments: tox -e integ-tests -- your-arguments ``` +## Support + +### 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/aws/amazon-braket-sdk-python/issues/). +For issues with the Amazon Braket service in general, please use the [Developer Forum](https://forums.aws.amazon.com/forum.jspa?forumID=370). + +### 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/aws/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. + ## License This project is licensed under the Apache-2.0 License. From 5dd23a4a239849f73e7ed60ac82ef649e18c3b41 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 18 Aug 2021 18:15:13 +0000 Subject: [PATCH 0372/1165] prepare release v1.7.5 --- 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 ba013c98..33fb78d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## v1.7.5 (2021-08-18) + +### Bug Fixes and Other Changes + + * Add test for local simulator device names + +### Documentation Changes + + * Add documentation for support + +### Testing and Release Infrastructure + + * Update copyright notice + ## v1.7.4 (2021-08-06) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 72cc4500..ef773323 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.7.5.dev0" +__version__ = "1.7.5" From 68c5b560f17c8b012c798efb71ea727fad31af54 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 18 Aug 2021 18:15:13 +0000 Subject: [PATCH 0373/1165] update development version to v1.7.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 ef773323..79436eef 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.7.5" +__version__ = "1.7.6.dev0" From 662739b72dbadca0b937d5c1489564ecb066fe41 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 20 Aug 2021 19:29:42 -0600 Subject: [PATCH 0374/1165] feature: Calculate arbitrary observables when `shots=0` (#267) Allows arbitrary observables to be attached to a circuit, allowing, for example, the simultaneous calculation of all terms of a qubit Hamiltonian when `shots=0`. However, running such a circuit with `shots>0`, will throw an exception, as observables must commute to be measured (sampled) simultaneously; in fact, here the observables must be either identical or the identity. --- src/braket/circuits/circuit.py | 127 +++++++++--------- src/braket/circuits/circuit_helpers.py | 9 +- .../gate_model_device_testing_utils.py | 73 +++++++++- .../test_local_braket_simulator.py | 15 +++ .../integ_tests/test_local_noise_simulator.py | 15 +++ .../test_simulator_quantum_task.py | 25 ++++ .../braket/circuits/test_circuit.py | 59 +++++--- .../braket/circuits/test_circuit_helpers.py | 23 +++- 8 files changed, 260 insertions(+), 86 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 71a294a2..4d09c75e 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -123,8 +123,9 @@ def __init__(self, addable: AddableTypes = None, *args, **kwargs): self._moments: Moments = Moments() self._result_types: Dict[ResultType] = {} self._qubit_observable_mapping: Dict[Union[int, Circuit._ALL_QUBITS], Observable] = {} - self._qubit_target_mapping: Dict[int, Tuple[int]] = {} + self._qubit_observable_target_mapping: Dict[int, Tuple[int]] = {} self._qubit_observable_set = set() + self._observables_simultaneously_measurable = True if addable is not None: self.add(addable, *args, **kwargs) @@ -150,6 +151,9 @@ def basis_rotation_instructions(self) -> List[Instruction]: """List[Instruction]: Get a list of basis rotation instructions in the circuit. These basis rotation instructions are added if result types are requested for an observable other than Pauli-Z. + + This only makes sense if all observables are simultaneously measurable; + if not, this method will return an empty list. """ # Note that basis_rotation_instructions can change each time a new instruction # is added to the circuit because `self._moments.qubits` would change @@ -162,7 +166,7 @@ def basis_rotation_instructions(self) -> List[Instruction]: ) return basis_rotation_instructions - target_lists = sorted(list(set(self._qubit_target_mapping.values()))) + target_lists = sorted(set(self._qubit_observable_target_mapping.values())) for target_list in target_lists: observable = self._qubit_observable_mapping[target_list[0]] basis_rotation_instructions += Circuit._observable_to_instruction( @@ -194,7 +198,7 @@ def add_result_type( self, result_type: ResultType, target: QubitSetInput = None, - target_mapping: Dict[QubitInput, QubitInput] = {}, + target_mapping: Dict[QubitInput, QubitInput] = None, ) -> Circuit: """ Add a requested result type to `self`, returns `self` for chaining ability. @@ -207,7 +211,7 @@ def add_result_type( target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of qubit mappings to apply to the `result_type.target`. Key is the qubit in `result_type.target` and the value is what the key will be changed to. - Default = `{}`. + Default = `None`. Note: target and target_mapping will only be applied to those requested result types with @@ -219,9 +223,6 @@ def add_result_type( Raises: TypeError: If both `target_mapping` and `target` are supplied. - ValueError: If the observable specified for a qubit is different from what is - specified by the result types already added to the circuit. Only one observable - is allowed for a qubit. Examples: >>> result_type = ResultType.Probability(target=[0, 1]) @@ -258,87 +259,73 @@ def add_result_type( result_type_to_add = result_type.copy(target=target) if result_type_to_add not in self._result_types: - self._add_to_qubit_observable_mapping(result_type_to_add) + observable = Circuit._extract_observable(result_type_to_add) + if observable and self._observables_simultaneously_measurable: + # Only check if all observables can be simultaneously measured + self._add_to_qubit_observable_mapping(observable, result_type_to_add.target) self._add_to_qubit_observable_set(result_type_to_add) # using dict as an ordered set, value is arbitrary self._result_types[result_type_to_add] = None return self - def _add_to_qubit_observable_mapping(self, result_type: ResultType) -> None: + @staticmethod + def _extract_observable(result_type: ResultType) -> Optional[Observable]: if isinstance(result_type, ResultType.Probability): - observable = Observable.Z() # computational basis + return Observable.Z() # computational basis elif isinstance(result_type, ObservableResultType): - observable = result_type.observable + return result_type.observable else: - return + return None - targets = result_type.target or list(self._qubit_observable_set) + def _add_to_qubit_observable_mapping( + self, observable: Observable, observable_target: QubitSet + ) -> None: + targets = observable_target or list(self._qubit_observable_set) all_qubits_observable = self._qubit_observable_mapping.get(Circuit._ALL_QUBITS) + tensor_product_dict = ( + Circuit._tensor_product_index_dict(observable, observable_target) + if isinstance(observable, TensorProduct) + else None + ) + identity = Observable.I() for i in range(len(targets)): target = targets[i] - tensor_product_dict = ( - Circuit._tensor_product_index_dict(observable) - if isinstance(observable, TensorProduct) - else None - ) new_observable = tensor_product_dict[i][0] if tensor_product_dict else observable current_observable = all_qubits_observable or self._qubit_observable_mapping.get(target) - add_observable = Circuit._validate_observable_to_add_for_qubit( - current_observable, new_observable, target + add_observable = not current_observable or ( + current_observable == identity and new_observable != identity ) - - if result_type.target: + if ( + not add_observable + and current_observable != identity + and new_observable != identity + and current_observable != new_observable + ): + return self._encounter_noncommuting_observable() + + if observable_target: new_targets = ( - tuple( - result_type.target[ - tensor_product_dict[i][1][0] : tensor_product_dict[i][1][1] - ] - ) - if tensor_product_dict - else tuple(result_type.target) + tensor_product_dict[i][1] if tensor_product_dict else tuple(observable_target) ) + if add_observable: - self._qubit_target_mapping[target] = new_targets + self._qubit_observable_target_mapping[target] = new_targets self._qubit_observable_mapping[target] = new_observable elif new_observable.qubit_count > 1: - current_target = self._qubit_target_mapping.get(target) + current_target = self._qubit_observable_target_mapping.get(target) if current_target and current_target != new_targets: - raise ValueError( - f"Target order {current_target} of existing result type with" - f" observable {current_observable} conflicts with order {targets}" - " of new result type" - ) + return self._encounter_noncommuting_observable() - if not result_type.target: + if not observable_target: if all_qubits_observable and all_qubits_observable != observable: - raise ValueError( - f"Existing result type for observable {all_qubits_observable} for all qubits" - f" conflicts with observable {observable} for new result type" - ) + return self._encounter_noncommuting_observable() self._qubit_observable_mapping[Circuit._ALL_QUBITS] = observable @staticmethod - def _validate_observable_to_add_for_qubit(current_observable, new_observable, target): - identity = Observable.I() - add_observable = False - if not current_observable or ( - current_observable == identity and new_observable != identity - ): - add_observable = True - elif ( - current_observable != identity - and new_observable != identity - and current_observable != new_observable - ): - raise ValueError( - f"Observable {new_observable} specified for target {target} conflicts with" - + f" existing observable {current_observable} on this target." - ) - return add_observable - - @staticmethod - def _tensor_product_index_dict(observable: TensorProduct) -> Dict[int, Observable]: + def _tensor_product_index_dict( + observable: TensorProduct, observable_target: QubitSet + ) -> Dict[int, Tuple[Observable, Tuple[int, ...]]]: obj_dict = {} i = 0 factors = list(observable.factors) @@ -349,7 +336,8 @@ def _tensor_product_index_dict(observable: TensorProduct) -> Dict[int, Observabl if factors: total += factors[0].qubit_count if factors: - obj_dict[i] = (factors[0], (total - factors[0].qubit_count, total)) + first = total - factors[0].qubit_count + obj_dict[i] = (factors[0], tuple(observable_target[first:total])) i += 1 return obj_dict @@ -912,6 +900,21 @@ def as_unitary(self) -> np.ndarray: return calculate_unitary(qubit_count, self.instructions) + @property + def observables_simultaneously_measurable(self) -> bool: + """bool: Whether the circuit's observables are simultaneously measurable + + If this is False, then the circuit can only be run when shots = 0, as sampling (shots > 0) + measures the circuit in the observables' shared eigenbasis. + """ + return self._observables_simultaneously_measurable + + def _encounter_noncommuting_observable(self): + self._observables_simultaneously_measurable = False + # No longer simultaneously measurable, so no need to track + self._qubit_observable_mapping.clear() + self._qubit_observable_target_mapping.clear() + def _copy(self) -> Circuit: copy = Circuit().add(self.instructions) copy.add(self.result_types) diff --git a/src/braket/circuits/circuit_helpers.py b/src/braket/circuits/circuit_helpers.py index f57e776c..7aa39b3b 100644 --- a/src/braket/circuits/circuit_helpers.py +++ b/src/braket/circuits/circuit_helpers.py @@ -23,9 +23,10 @@ def validate_circuit_and_shots(circuit: Circuit, shots: int) -> None: shots (int): shots to validate Raises: - ValueError: If circuit has no instructions. Also, if no result types - specified for circuit and `shots=0`. See `braket.circuit.result_types`. - Or, if `StateVector` or `Amplitude` are specified as result types when `shots > 0`. + ValueError: If circuit has no instructions; 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") @@ -34,6 +35,8 @@ def validate_circuit_and_shots(circuit: Circuit, shots: int) -> None: "No result types specified for circuit and shots=0. See `braket.circuit.result_types`" ) elif 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.StateVector) or isinstance(rt, ResultType.Amplitude): raise ValueError("StateVector or Amplitude cannot be specified when shots>0") diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index 6a92099b..9c76fa55 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -214,7 +214,7 @@ def get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, s .expectation(obs, obs_targets) ) if shots: - circuit.add_result_type(ResultType.Sample(obs, obs_targets)) + circuit.sample(obs, obs_targets) return circuit @@ -425,6 +425,77 @@ 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]): + shots = 0 + 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], + ] + ) + obs1 = Observable.X() @ Observable.Y() + obs1_targets = [0, 2] + obs2 = Observable.Z() @ Observable.Z() + obs2_targets = [0, 2] + obs3 = Observable.Y() @ Observable.Hermitian(array) + obs3_targets = [0, 1, 2] + circuit = ( + get_result_types_three_qubit_circuit(theta, phi, varphi, obs1, obs1_targets, shots) + .expectation(obs2, obs2_targets) + .expectation(obs3, obs3_targets) + ) + result = device.run(circuit, **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) + assert np.allclose(result.values[3], expected_mean3) + + +def result_types_noncommuting_flipped_targets_testing(device: Device, run_kwargs: Dict[str, Any]): + circuit = ( + Circuit() + .h(0) + .cnot(0, 1) + .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + .expectation(observable=Observable.H() @ Observable.X(), target=[1, 0]) + ) + result = device.run(circuit, shots=0, **run_kwargs).result() + assert np.allclose(result.values[0], np.sqrt(2) / 2) + assert np.allclose(result.values[1], np.sqrt(2) / 2) + + +def result_types_noncommuting_all(device: Device, run_kwargs: Dict[str, Any]): + array = np.array([[1, 2j], [-2j, 0]]) + circuit = ( + Circuit() + .h(0) + .cnot(0, 1) + .expectation(observable=Observable.Hermitian(array)) + .expectation(observable=Observable.X()) + ) + result = device.run(circuit, shots=0, **run_kwargs).result() + assert np.allclose(result.values[0], [0.5, 0.5]) + assert np.allclose(result.values[1], [0, 0]) + + def multithreaded_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): shots = run_kwargs["shots"] tol = get_tol(shots) diff --git a/test/integ_tests/test_local_braket_simulator.py b/test/integ_tests/test_local_braket_simulator.py index ccb66e5f..9b29e890 100644 --- a/test/integ_tests/test_local_braket_simulator.py +++ b/test/integ_tests/test_local_braket_simulator.py @@ -20,6 +20,9 @@ result_types_bell_pair_full_probability_testing, result_types_bell_pair_marginal_probability_testing, result_types_hermitian_testing, + result_types_noncommuting_all, + result_types_noncommuting_flipped_targets_testing, + result_types_noncommuting_testing, result_types_nonzero_shots_bell_pair_testing, result_types_observable_not_in_instructions, result_types_tensor_hermitian_hermitian_testing, @@ -107,6 +110,18 @@ def test_result_types_all_selected(shots): result_types_all_selected_testing(DEVICE, {"shots": shots}) +def test_result_types_noncommuting(): + result_types_noncommuting_testing(DEVICE, {}) + + +def test_result_types_noncommuting_flipped_targets(): + result_types_noncommuting_flipped_targets_testing(DEVICE, {}) + + +def test_result_types_noncommuting_all(): + result_types_noncommuting_all(DEVICE, {}) + + @pytest.mark.parametrize("shots", [0, SHOTS]) def test_result_types_observable_not_in_instructions(shots): result_types_observable_not_in_instructions(DEVICE, {"shots": shots}) diff --git a/test/integ_tests/test_local_noise_simulator.py b/test/integ_tests/test_local_noise_simulator.py index a6e7803a..2a33cf8b 100644 --- a/test/integ_tests/test_local_noise_simulator.py +++ b/test/integ_tests/test_local_noise_simulator.py @@ -21,6 +21,9 @@ result_types_bell_pair_full_probability_testing, result_types_bell_pair_marginal_probability_testing, result_types_hermitian_testing, + result_types_noncommuting_all, + result_types_noncommuting_flipped_targets_testing, + result_types_noncommuting_testing, result_types_nonzero_shots_bell_pair_testing, result_types_tensor_hermitian_hermitian_testing, result_types_tensor_x_y_testing, @@ -98,6 +101,18 @@ def test_result_types_all_selected(shots): result_types_all_selected_testing(DEVICE, {"shots": shots}) +def test_result_types_noncommuting(): + result_types_noncommuting_testing(DEVICE, {}) + + +def test_result_types_noncommuting_flipped_targets(): + result_types_noncommuting_flipped_targets_testing(DEVICE, {}) + + +def test_result_types_noncommuting_all(): + result_types_noncommuting_all(DEVICE, {}) + + @pytest.mark.parametrize("shots", [0, SHOTS]) def test_noisy_circuit_1qubit_noise(shots): noisy_circuit_1qubit_noise_full_probability(DEVICE, {"shots": shots}) diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 22fec856..2b6cb591 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -21,6 +21,9 @@ result_types_bell_pair_full_probability_testing, result_types_bell_pair_marginal_probability_testing, result_types_hermitian_testing, + result_types_noncommuting_all, + result_types_noncommuting_flipped_targets_testing, + result_types_noncommuting_testing, result_types_nonzero_shots_bell_pair_testing, result_types_observable_not_in_instructions, result_types_tensor_hermitian_hermitian_testing, @@ -154,6 +157,28 @@ def test_result_types_all_selected(simulator_arn, shots, aws_session, s3_destina ) +@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) + 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 +): + device = AwsDevice(simulator_arn, aws_session) + 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) + 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 diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 1b8f43f8..2ca08e26 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -105,58 +105,65 @@ def test_equality(): def test_add_result_type_default(prob): circ = Circuit().add_result_type(prob) + assert circ.observables_simultaneously_measurable assert list(circ.result_types) == [prob] def test_add_result_type_with_mapping(prob): expected = [ResultType.Probability([10, 11])] circ = Circuit().add_result_type(prob, target_mapping={0: 10, 1: 11}) + assert circ.observables_simultaneously_measurable assert list(circ.result_types) == expected def test_add_result_type_with_target(prob): expected = [ResultType.Probability([10, 11])] circ = Circuit().add_result_type(prob, target=[10, 11]) + assert circ.observables_simultaneously_measurable assert list(circ.result_types) == expected def test_add_result_type_already_exists(): expected = [ResultType.StateVector()] circ = Circuit(expected).add_result_type(expected[0]) + assert circ.observables_simultaneously_measurable assert list(circ.result_types) == expected -@pytest.mark.xfail(raises=ValueError) def test_add_result_type_observable_conflict_target(): circ = Circuit().add_result_type(ResultType.Probability([0, 1])) circ.add_result_type(ResultType.Expectation(observable=Observable.Y(), target=0)) + assert not circ.observables_simultaneously_measurable + assert not circ.basis_rotation_instructions -@pytest.mark.xfail(raises=ValueError) def test_add_result_type_observable_conflict_all(): circ = Circuit().add_result_type(ResultType.Probability()) circ.add_result_type(ResultType.Expectation(observable=Observable.Y())) + assert not circ.observables_simultaneously_measurable + assert not circ.basis_rotation_instructions -@pytest.mark.xfail(raises=ValueError) def test_add_result_type_observable_conflict_all_target_then_selected_target(): circ = Circuit().add_result_type(ResultType.Probability()) - circ.add_result_type(ResultType.Expectation(observable=Observable.Y(), target=[0, 1])) + circ.add_result_type(ResultType.Expectation(observable=Observable.Y(), target=[0])) + assert not circ.observables_simultaneously_measurable + assert not circ.basis_rotation_instructions -@pytest.mark.xfail(raises=ValueError) def test_add_result_type_observable_conflict_different_selected_targets_then_all_target(): circ = Circuit().add_result_type(ResultType.Expectation(observable=Observable.Z(), target=[0])) circ.add_result_type(ResultType.Expectation(observable=Observable.Y(), target=[1])) circ.add_result_type(ResultType.Expectation(observable=Observable.Y())) + assert not circ.observables_simultaneously_measurable + assert not circ.basis_rotation_instructions -@pytest.mark.xfail(raises=ValueError) def test_add_result_type_observable_conflict_selected_target_then_all_target(): - circ = Circuit().add_result_type( - ResultType.Expectation(observable=Observable.Y(), target=[0, 1]) - ) + circ = Circuit().add_result_type(ResultType.Expectation(observable=Observable.Y(), target=[1])) circ.add_result_type(ResultType.Probability()) + assert not circ.observables_simultaneously_measurable + assert not circ.basis_rotation_instructions def test_add_result_type_observable_no_conflict_all_target(): @@ -165,6 +172,7 @@ def test_add_result_type_observable_no_conflict_all_target(): ResultType.Expectation(observable=Observable.Z(), target=[0]), ] circ = Circuit(expected) + assert circ.observables_simultaneously_measurable assert circ.result_types == expected @@ -174,6 +182,7 @@ def test_add_result_type_observable_no_conflict_target_all(): ResultType.Probability(), ] circ = Circuit(expected) + assert circ.observables_simultaneously_measurable assert circ.result_types == expected @@ -183,6 +192,7 @@ def test_add_result_type_observable_no_conflict_all(): ResultType.Expectation(observable=Observable.Y()), ] circ = Circuit(expected) + assert circ.observables_simultaneously_measurable assert circ.result_types == expected @@ -192,26 +202,37 @@ def test_add_result_type_observable_no_conflict_state_vector_obs_return_value(): ResultType.Expectation(observable=Observable.Y()), ] circ = Circuit(expected) + assert circ.observables_simultaneously_measurable assert circ.result_types == expected -@pytest.mark.xfail(raises=ValueError) def test_add_result_type_same_observable_wrong_target_order_tensor_product(): - Circuit().add_result_type( - ResultType.Expectation(observable=Observable.Y() @ Observable.X(), target=[0, 1]) - ).add_result_type( - ResultType.Variance(observable=Observable.Y() @ Observable.X(), target=[1, 0]) + circ = ( + Circuit() + .add_result_type( + ResultType.Expectation(observable=Observable.Y() @ Observable.X(), target=[0, 1]) + ) + .add_result_type( + ResultType.Variance(observable=Observable.Y() @ Observable.X(), target=[1, 0]) + ) ) + assert not circ.observables_simultaneously_measurable + assert not circ.basis_rotation_instructions -@pytest.mark.xfail(raises=ValueError) def test_add_result_type_same_observable_wrong_target_order_hermitian(): array = np.eye(4) - Circuit().add_result_type( - ResultType.Expectation(observable=Observable.Hermitian(matrix=array), target=[0, 1]) - ).add_result_type( - ResultType.Variance(observable=Observable.Hermitian(matrix=array), target=[1, 0]) + circ = ( + Circuit() + .add_result_type( + ResultType.Expectation(observable=Observable.Hermitian(matrix=array), target=[0, 1]) + ) + .add_result_type( + ResultType.Variance(observable=Observable.Hermitian(matrix=array), target=[1, 0]) + ) ) + assert not circ.observables_simultaneously_measurable + assert not circ.basis_rotation_instructions @pytest.mark.xfail(raises=TypeError) diff --git a/test/unit_tests/braket/circuits/test_circuit_helpers.py b/test/unit_tests/braket/circuits/test_circuit_helpers.py index 5dd627f2..e9ed831c 100644 --- a/test/unit_tests/braket/circuits/test_circuit_helpers.py +++ b/test/unit_tests/braket/circuits/test_circuit_helpers.py @@ -13,7 +13,7 @@ import pytest -from braket.circuits import Circuit +from braket.circuits import Circuit, observables from braket.circuits.circuit_helpers import validate_circuit_and_shots @@ -52,3 +52,24 @@ def test_validate_circuit_and_shots_100_result_state_vector(): @pytest.mark.xfail(raises=ValueError) def test_validate_circuit_and_shots_100_result_amplitude(): validate_circuit_and_shots(Circuit().h(0).amplitude(state=["0"]), 100) + + +def test_validate_circuit_and_shots_0_noncommuting(): + validate_circuit_and_shots( + Circuit() + .h(0) + .expectation(observables.X() @ observables.Y(), [0, 1]) + .expectation(observables.Y() @ observables.X(), [0, 1]), + 0, + ) + + +@pytest.mark.xfail(raises=ValueError) +def test_validate_circuit_and_shots_100_noncommuting(): + validate_circuit_and_shots( + Circuit() + .h(0) + .expectation(observables.X() @ observables.Y(), [0, 1]) + .expectation(observables.Y() @ observables.X(), [0, 1]), + 100, + ) From cc704fa77d1570729f887a230ef9fc60bcff8df5 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 23 Aug 2021 11:58:36 -0600 Subject: [PATCH 0375/1165] fix: Remove immutable default args (#268) Changed mutable default arguments (i.e. `def func(a={})`) to immutable ones (`def func(a=None)`) to avoid [unexpected bugs](https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments) down the line. --- src/braket/circuits/circuit.py | 8 ++++---- src/braket/circuits/instruction.py | 6 +++--- src/braket/circuits/moments.py | 6 +++--- src/braket/circuits/result_type.py | 11 ++++++----- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 4d09c75e..da7d1b80 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -349,7 +349,7 @@ def add_instruction( self, instruction: Instruction, target: QubitSetInput = None, - target_mapping: Dict[QubitInput, QubitInput] = {}, + target_mapping: Dict[QubitInput, QubitInput] = None, ) -> Circuit: """ Add an instruction to `self`, returns `self` for chaining ability. @@ -363,7 +363,7 @@ def add_instruction( target_mapping (dictionary[int or Qubit, int or Qubit], optional): 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 = `{}`. + Default = `None`. Returns: Circuit: self @@ -418,7 +418,7 @@ def add_circuit( self, circuit: Circuit, target: QubitSetInput = None, - target_mapping: Dict[QubitInput, QubitInput] = {}, + target_mapping: Dict[QubitInput, QubitInput] = None, ) -> Circuit: """ Add a `circuit` to self, returns self for chaining ability. @@ -431,7 +431,7 @@ def add_circuit( Default = `None`. target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of qubit mappings to apply to the qubits of `circuit.instructions`. Key is the qubit - to map, and the value is what to change it to. Default = `{}`. + to map, and the value is what to change it to. Default = `None`. Returns: Circuit: self diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index 4a688dc4..b2dbde4f 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -90,7 +90,7 @@ def to_ir(self): return self._operator.to_ir([int(qubit) for qubit in self._target]) def copy( - self, target_mapping: Dict[QubitInput, QubitInput] = {}, target: QubitSetInput = None + self, target_mapping: Dict[QubitInput, QubitInput] = None, target: QubitSetInput = None ) -> Instruction: """ Return a shallow copy of the instruction. @@ -102,7 +102,7 @@ def copy( Args: target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of qubit mappings to apply to the target. Key is the qubit in this `target` and the - value is what the key is changed to. Default = `{}`. + value is what the key is changed to. Default = `None`. target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the new instruction. @@ -129,7 +129,7 @@ def copy( elif target is not None: return Instruction(self._operator, target) else: - return Instruction(self._operator, self._target.map(target_mapping)) + return Instruction(self._operator, self._target.map(target_mapping or {})) def __repr__(self): return f"Instruction('operator': {self._operator}, 'target': {self._target})" diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 926193a9..008d7d43 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -81,7 +81,7 @@ class Moments(Mapping[MomentsKey, Instruction]): Args: instructions (Iterable[Instruction], optional): Instructions to initialize self. - Default = []. + Default = None. Examples: >>> moments = Moments() @@ -106,13 +106,13 @@ class Moments(Mapping[MomentsKey, Instruction]): Value: Instruction('operator': H, 'target': QubitSet([Qubit(1)])) """ - def __init__(self, instructions: Iterable[Instruction] = []): + def __init__(self, instructions: Iterable[Instruction] = None): self._moments: OrderedDict[MomentsKey, Instruction] = OrderedDict() self._max_times: Dict[Qubit, int] = {} self._qubits = QubitSet() self._depth = 0 - self.add(instructions) + self.add(instructions or []) @property def depth(self) -> int: diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index b846c0df..4088dcdc 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -69,7 +69,9 @@ def to_ir(self, *args, **kwargs) -> Any: """ raise NotImplementedError("to_ir has not been implemented yet.") - def copy(self, target_mapping: Dict[QubitInput, QubitInput] = {}, target: QubitSetInput = None): + def copy( + self, target_mapping: Dict[QubitInput, QubitInput] = None, target: QubitSetInput = None + ): """ Return a shallow copy of the result type. @@ -80,7 +82,7 @@ def copy(self, target_mapping: Dict[QubitInput, QubitInput] = {}, target: QubitS Args: target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of qubit mappings to apply to the target. Key is the qubit in this `target` and the - value is what the key is changed to. Default = `{}`. + value is what the key is changed to. Default = `None`. target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the new instruction. @@ -108,9 +110,8 @@ def copy(self, target_mapping: Dict[QubitInput, QubitInput] = {}, target: QubitS elif target is not None: if hasattr(copy, "target"): copy.target = target - else: - if hasattr(copy, "target"): - copy.target = self._target.map(target_mapping) + elif hasattr(copy, "target"): + copy.target = self._target.map(target_mapping or {}) return copy @classmethod From 5157cc508fe6fc7a68dbd3be3c64d20a7b9cfb77 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 23 Aug 2021 18:14:00 +0000 Subject: [PATCH 0376/1165] prepare release v1.8.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 33fb78d6..585376b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.8.0 (2021-08-23) + +### Features + + * Calculate arbitrary observables when `shots=0` + +### Bug Fixes and Other Changes + + * Remove immutable default args + ## v1.7.5 (2021-08-18) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 79436eef..0303e4a1 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.7.6.dev0" +__version__ = "1.8.0" From ca17c0ad8c38c246e3adf80621211dc145135cf3 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 23 Aug 2021 18:14:00 +0000 Subject: [PATCH 0377/1165] update development version to v1.8.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 0303e4a1..04cf6198 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.8.0" +__version__ = "1.8.1.dev0" From e218487dfa72bcc9ef0265f682822a7d90bc99a0 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 9 Sep 2021 15:15:02 -0600 Subject: [PATCH 0378/1165] feature: Verbatim boxes (#273) Introduces verbatim boxes, which allows users to tell the compiler not to modify specified parts of the circuit. --- src/braket/aws/aws_quantum_task.py | 8 +- src/braket/aws/aws_session.py | 2 +- src/braket/circuits/__init__.py | 17 +- src/braket/circuits/ascii_circuit_diagram.py | 29 ++- src/braket/circuits/circuit.py | 121 ++++++++++-- src/braket/circuits/compiler_directive.py | 51 ++++++ src/braket/circuits/compiler_directives.py | 41 +++++ src/braket/circuits/instruction.py | 28 +-- src/braket/circuits/moments.py | 47 +++-- src/braket/circuits/noise_helpers.py | 6 +- src/braket/circuits/operator.py | 2 +- src/braket/circuits/quantum_operator.py | 6 +- .../braket/aws/test_aws_quantum_task.py | 91 +++++---- .../circuits/test_ascii_circuit_diagram.py | 173 ++++++++++++++++++ .../braket/circuits/test_circuit.py | 78 ++++++++ .../circuits/test_compiler_directive.py | 54 ++++++ .../circuits/test_compiler_directives.py | 38 ++++ .../braket/circuits/test_instruction.py | 2 +- .../braket/circuits/test_quantum_operator.py | 13 +- 19 files changed, 699 insertions(+), 108 deletions(-) create mode 100644 src/braket/circuits/compiler_directive.py create mode 100644 src/braket/circuits/compiler_directives.py create mode 100644 test/unit_tests/braket/circuits/test_compiler_directive.py create mode 100644 test/unit_tests/braket/circuits/test_compiler_directives.py diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 73eef4b4..0d190c2b 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -100,7 +100,9 @@ def create( without any rewiring downstream, if this is supported by the device. Only applies to digital, gate-based circuits (as opposed to annealing problems). If ``True``, no qubit rewiring is allowed; if ``False``, qubit rewiring is allowed. - Default: ``False``. + If the circuit has frozen qubits (``circuit.has_frozen_qubits==True``), then this + must be True, or running will throw an exception. + Default: False tags (Dict[str, str]): Tags, which are Key-Value pairs to add to this quantum task. An example would be: @@ -417,6 +419,10 @@ def _( **kwargs, ) -> AwsQuantumTask: validate_circuit_and_shots(circuit, create_task_kwargs["shots"]) + if circuit.qubits_frozen and not disable_qubit_rewiring: + raise ValueError( + "disable_qubit_rewiring must be True to run circuit with compiler directives" + ) # TODO: Update this to use `deviceCapabilities` from Amazon Braket's GetDevice operation # in order to decide what parameters to build. diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index b3b5c2aa..ec154245 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -25,7 +25,7 @@ class AwsSession(object): """Manage interactions with AWS services.""" - S3DestinationFolder = NamedTuple("S3DestinationFolder", [("bucket", str), ("key", int)]) + S3DestinationFolder = NamedTuple("S3DestinationFolder", [("bucket", str), ("key", str)]) def __init__(self, boto_session=None, braket_client=None, config=None): """ diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py index 719f1b38..94827b00 100644 --- a/src/braket/circuits/__init__.py +++ b/src/braket/circuits/__init__.py @@ -11,18 +11,19 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -# Execute initialization code in circuit module -import braket.circuits.circuit as circuit # noqa: F401 - -# Execute initialization code in gates module -import braket.circuits.gates as gates # noqa: F401 -import braket.circuits.noises as noises # noqa: F401 -import braket.circuits.observables as observables # noqa: F401 -import braket.circuits.result_types as result_types # noqa: F401 +from braket.circuits import ( # noqa: F401 + circuit, + compiler_directives, + gates, + noises, + observables, + result_types, +) from braket.circuits.angled_gate import AngledGate # 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 from braket.circuits.gate import Gate # noqa: F401 from braket.circuits.instruction import Instruction # noqa: F401 from braket.circuits.moments import Moments, MomentsKey # noqa: F401 diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index f386898e..fbf86918 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -14,6 +14,7 @@ from typing import List, Tuple, Union 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.noise import Noise @@ -104,10 +105,14 @@ def _ascii_group_items( 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)): + if isinstance(item, Instruction) and not isinstance( + item.operator, (Gate, Noise, CompilerDirective) + ): continue - if isinstance(item, ResultType) and not item.target: + if (isinstance(item, ResultType) and not item.target) or ( + isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective) + ): qubit_range = circuit_qubits else: qubit_range = QubitSet(range(min(item.target), max(item.target) + 1)) @@ -129,7 +134,9 @@ def _ascii_group_items( return groupings @staticmethod - def _categorize_result_types(result_types: List[ResultType]) -> Tuple[List[ResultType]]: + def _categorize_result_types( + result_types: List[ResultType], + ) -> Tuple[List[str], List[ResultType]]: """ Categorize result types into result types with target and those without. @@ -147,7 +154,7 @@ def _categorize_result_types(result_types: List[ResultType]) -> Tuple[List[Resul target_result_types.append(result_type) else: additional_result_types.extend(result_type.ascii_symbols) - return (additional_result_types, target_result_types) + return additional_result_types, target_result_types @staticmethod def _ascii_diagram_column_set( @@ -216,16 +223,20 @@ def _ascii_diagram_column( target_qubits = circuit_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 + 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 else: target_qubits = item.target qubits = circuit_qubits.intersection( set(range(min(item.target), max(item.target) + 1)) ) - ascii_symbols = ( - item.operator.ascii_symbols - if isinstance(item, Instruction) - else item.ascii_symbols - ) + ascii_symbols = item.ascii_symbols for qubit in qubits: # Determine if the qubit is part of the item or in the middle of a diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index da7d1b80..e04210ef 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -17,6 +17,7 @@ import numpy as np +from braket.circuits import compiler_directives from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction @@ -126,6 +127,7 @@ def __init__(self, addable: AddableTypes = None, *args, **kwargs): self._qubit_observable_target_mapping: Dict[int, Tuple[int]] = {} self._qubit_observable_set = set() self._observables_simultaneously_measurable = True + self._has_compiler_directives = False if addable is not None: self.add(addable, *args, **kwargs) @@ -451,25 +453,28 @@ def add_circuit( equivalent to an existing requested result type will not be added. Examples: - >>> widget = Circuit().h(0).cnot([0, 1]) + >>> widget = Circuit().h(0).cnot(0, 1) >>> circ = Circuit().add_circuit(widget) - >>> print(circ.instructions[0]) + >>> instructions = list(circ.instructions) + >>> print(instructions[0]) Instruction('operator': 'H', 'target': QubitSet(Qubit(0),)) - >>> print(circ.instructions[1]) + >>> print(instructions[1]) Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(0), Qubit(1))) - >>> widget = Circuit().h(0).cnot([0, 1]) + >>> widget = Circuit().h(0).cnot(0, 1) >>> circ = Circuit().add_circuit(widget, target_mapping={0: 10, 1: 11}) - >>> print(circ.instructions[0]) + >>> instructions = list(circ.instructions) + >>> print(instructions[0]) Instruction('operator': 'H', 'target': QubitSet(Qubit(10),)) - >>> print(circ.instructions[1]) + >>> print(instructions[1]) Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(10), Qubit(11))) - >>> widget = Circuit().h(0).cnot([0, 1]) + >>> widget = Circuit().h(0).cnot(0, 1) >>> circ = Circuit().add_circuit(widget, target=[10, 11]) - >>> print(circ.instructions[0]) + >>> instructions = list(circ.instructions) + >>> print(instructions[0]) Instruction('operator': 'H', 'target': QubitSet(Qubit(10),)) - >>> print(circ.instructions[1]) + >>> print(instructions[1]) Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(10), Qubit(11))) """ if target_mapping and target is not None: @@ -487,6 +492,76 @@ def add_circuit( return self + def add_verbatim_box( + self, + verbatim_circuit: Circuit, + target: QubitSetInput = None, + target_mapping: Dict[QubitInput, QubitInput] = None, + ) -> Circuit: + """ + Add a verbatim `circuit` to self, that is, ensures that `circuit` is not modified in any way + by the compiler. + + Args: + verbatim_circuit (Circuit): Circuit to add into self. + target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the + supplied circuit. This is a macro over `target_mapping`; `target` is converted to + a `target_mapping` by zipping together a sorted `circuit.qubits` and `target`. + Default = `None`. + target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of + qubit mappings to apply to the qubits of `circuit.instructions`. Key is the qubit + to map, and the value is what to change it to. Default = `None`. + + Returns: + Circuit: self + + Raises: + TypeError: If both `target_mapping` and `target` are supplied. + ValueError: If `circuit` has result types attached + + Examples: + >>> widget = Circuit().h(0).h(1) + >>> circ = Circuit().add_verbatim_box(widget) + >>> print(list(circ.instructions)) + [Instruction('operator': StartVerbatimBox, 'target': QubitSet([])), + Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(0)])), + Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(1)])), + Instruction('operator': EndVerbatimBox, 'target': QubitSet([]))] + + >>> widget = Circuit().h(0).cnot(0, 1) + >>> circ = Circuit().add_verbatim_box(widget, target_mapping={0: 10, 1: 11}) + >>> print(list(circ.instructions)) + [Instruction('operator': StartVerbatimBox, 'target': QubitSet([])), + Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(10)])), + Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(11)])), + Instruction('operator': EndVerbatimBox, 'target': QubitSet([]))] + + >>> widget = Circuit().h(0).cnot(0, 1) + >>> circ = Circuit().add_verbatim_box(widget, target=[10, 11]) + >>> print(list(circ.instructions)) + [Instruction('operator': StartVerbatimBox, 'target': QubitSet([])), + Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(10)])), + Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(11)])), + Instruction('operator': EndVerbatimBox, 'target': QubitSet([]))] + """ + 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: + keys = sorted(verbatim_circuit.qubits) + values = target + target_mapping = dict(zip(keys, values)) + + if verbatim_circuit.result_types: + raise ValueError("Verbatim subcircuit is not measured and cannot have result types") + + if verbatim_circuit.instructions: + self.add_instruction(Instruction(compiler_directives.StartVerbatimBox())) + for instruction in verbatim_circuit.instructions: + self.add_instruction(instruction, target_mapping=target_mapping) + self.add_instruction(Instruction(compiler_directives.EndVerbatimBox())) + self._has_compiler_directives = True + return self + def apply_gate_noise( self, noise: Union[Type[Noise], Iterable[Type[Noise]]], @@ -678,8 +753,8 @@ def apply_initialization_noise( """ if (len(self.qubits) == 0) and (target_qubits is None): raise IndexError( - "target_qubits must be provided in order to apply the initialization noise \ -to an empty circuit." + "target_qubits must be provided in order to" + " apply the initialization noise to an empty circuit." ) target_qubits = check_noise_target_qubits(self, target_qubits) @@ -691,8 +766,9 @@ def apply_initialization_noise( raise TypeError("Noise must be an instance of the Noise class") if noise_channel.qubit_count > 1 and noise_channel.qubit_count != len(target_qubits): raise ValueError( - "target_qubits needs to be provided for this multi-qubit noise channel, and \ -the number of qubits in target_qubits must be the same as defined by the multi-qubit noise channel." + "target_qubits needs to be provided for this multi-qubit noise channel," + " and the number of qubits in target_qubits must be the same as defined by" + " the multi-qubit noise channel." ) return apply_noise_to_moments(self, noise, target_qubits, "initialization") @@ -746,8 +822,8 @@ def apply_readout_noise( """ if (len(self.qubits) == 0) and (target_qubits is None): raise IndexError( - "target_qubits must be provided in order to apply the readout noise \ -to an empty circuit." + "target_qubits must be provided in order to" + " apply the readout noise to an empty circuit." ) if target_qubits is None: @@ -768,8 +844,9 @@ def apply_readout_noise( raise TypeError("Noise must be an instance of the Noise class") if noise_channel.qubit_count > 1 and noise_channel.qubit_count != len(target_qubits): raise ValueError( - "target_qubits needs to be provided for this multi-qubit noise channel, and \ -the number of qubits in target_qubits must be the same as defined by the multi-qubit noise channel." + "target_qubits needs to be provided for this multi-qubit noise channel," + " and the number of qubits in target_qubits must be the same as defined by" + " the multi-qubit noise channel." ) return apply_noise_to_moments(self, noise, target_qubits, "readout") @@ -900,6 +977,16 @@ def as_unitary(self) -> np.ndarray: return calculate_unitary(qubit_count, self.instructions) + @property + def qubits_frozen(self) -> bool: + """bool: Whether the circuit's qubits are frozen, that is, cannot be remapped. + + This may happen because the circuit contains compiler directives preventing compilation + of a part of the circuit, which consequently means that none of the other qubits can be + rewired either for the program to still make sense. + """ + return self._has_compiler_directives + @property def observables_simultaneously_measurable(self) -> bool: """bool: Whether the circuit's observables are simultaneously measurable diff --git a/src/braket/circuits/compiler_directive.py b/src/braket/circuits/compiler_directive.py new file mode 100644 index 00000000..87b1f818 --- /dev/null +++ b/src/braket/circuits/compiler_directive.py @@ -0,0 +1,51 @@ +# 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 typing import Sequence, Tuple + +from braket.circuits.operator import Operator + + +class CompilerDirective(Operator): + """A directive specifying how the compiler should process a part of the circuit. + + For example, a directive may tell the compiler not to modify some gates in the circuit. + """ + + def __init__(self, ascii_symbols: Sequence[str]): + """ + Args: + ascii_symbols (Sequence[str]): ASCII string symbols for the compiler directiver. + These are used when printing a diagram of circuits. + """ + if ascii_symbols is None: + raise ValueError("ascii_symbols must not be None") + self._ascii_symbols = tuple(ascii_symbols) + + @property + def name(self) -> str: + return self.__class__.__name__ + + @property + def ascii_symbols(self) -> Tuple[str, ...]: + """Tuple[str, ...]: Returns the ascii symbols for the compiler directive.""" + return self._ascii_symbols + + def to_ir(self, *args, **kwargs): + raise NotImplementedError("to_ir has not been implemented yet.") + + def __eq__(self, other): + return isinstance(other, CompilerDirective) and self.name == other.name + + def __repr__(self): + return self.name diff --git a/src/braket/circuits/compiler_directives.py b/src/braket/circuits/compiler_directives.py new file mode 100644 index 00000000..8e0c0dc3 --- /dev/null +++ b/src/braket/circuits/compiler_directives.py @@ -0,0 +1,41 @@ +# 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 braket.ir.jaqcd as ir +from braket.circuits.compiler_directive import CompilerDirective + + +class StartVerbatimBox(CompilerDirective): + """ + Prevents the compiler from modifying any ensuing instructions + until the appearance of a corresponding ``EndVerbatimBox``. + """ + + def __init__(self): + super().__init__(["StartVerbatim"]) + + def to_ir(self, *args, **kwargs): + return ir.StartVerbatimBox.construct() + + +class EndVerbatimBox(CompilerDirective): + """ + Marks the end of a portion of code following a StartVerbatimBox that prevents the enclosed + instructions from being modified by the compiler. + """ + + def __init__(self): + super().__init__(["EndVerbatim"]) + + def to_ir(self, *args, **kwargs): + return ir.EndVerbatimBox.construct() diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index b2dbde4f..5c2d2c9b 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -13,14 +13,15 @@ from __future__ import annotations -from typing import Dict +from typing import Dict, Tuple -from braket.circuits.gate import Gate +from braket.circuits.operator import Operator +from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput # InstructionOperator is a type alias, and it can be expanded to include other operators -InstructionOperator = Gate +InstructionOperator = Operator class Instruction: @@ -28,7 +29,7 @@ class Instruction: An instruction is a quantum directive that describes the task to perform on a quantum device. """ - def __init__(self, operator: InstructionOperator, target: QubitSetInput): + def __init__(self, operator: InstructionOperator, target: QubitSetInput = None): """ InstructionOperator includes objects of type `Gate` only. @@ -56,16 +57,14 @@ def __init__(self, operator: InstructionOperator, target: QubitSetInput): """ if not operator: raise ValueError("Operator cannot be empty") - self._operator = operator - self._target = QubitSet(target) - if ( - hasattr(self._operator, "qubit_count") - and len(self._target) != self._operator.qubit_count - ): + target_set = QubitSet(target) + if isinstance(operator, QuantumOperator) and len(target_set) != operator.qubit_count: raise ValueError( - f"Operator qubit count {self._operator.qubit_count} must be " - f"equal to size of target qubit set {self._target}" + f"Operator qubit count {operator.qubit_count} must be equal to" + f" size of target qubit set {target_set}" ) + self._operator = operator + self._target = target_set @property def operator(self) -> InstructionOperator: @@ -89,6 +88,11 @@ def to_ir(self): """ return self._operator.to_ir([int(qubit) for qubit in self._target]) + @property + def ascii_symbols(self) -> Tuple[str, ...]: + """Tuple[str, ...]: Returns the ascii symbols for the instruction's operator.""" + return self._operator.ascii_symbols + def copy( self, target_mapping: Dict[QubitInput, QubitInput] = None, target: QubitSetInput = None ) -> Instruction: diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 008d7d43..d21907d4 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -26,6 +26,7 @@ ValuesView, ) +from braket.circuits.compiler_directive import CompilerDirective from braket.circuits.instruction import Instruction from braket.circuits.noise import Noise from braket.circuits.qubit import Qubit @@ -40,6 +41,7 @@ class MomentType(str, Enum): GATE_NOISE: a gate-based noise channel 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 """ GATE = "gate" @@ -47,18 +49,18 @@ class MomentType(str, Enum): GATE_NOISE = "gate_noise" INITIALIZATION_NOISE = "initialization_noise" READOUT_NOISE = "readout_noise" + COMPILER_DIRECTIVE = "compiler_directive" class MomentsKey(NamedTuple): """Key of the Moments mapping. Args: - time: moment - qubits: qubit set - moment_type: can be GATE, NOISE, or GATE_NOISE which is associated with gates; - and READOUT_NOISE or INITIALIZATION_NOISE. - noise_index: the number of noise channels at the same moment. For gates, this is the - number of gate_noise channels associated with that gate. For all other noise - types, noise_index starts from 0; but for gate noise, it starts from 1. + time: moment + qubits: qubit set + moment_type: The type of the moment + noise_index: the number of noise channels at the same moment. For gates, this is the + number of gate_noise channels associated with that gate. For all other noise + types, noise_index starts from 0; but for gate noise, it starts from 1. """ time: int @@ -111,6 +113,7 @@ def __init__(self, instructions: Iterable[Instruction] = None): self._max_times: Dict[Qubit, int] = {} self._qubits = QubitSet() self._depth = 0 + self._time_all_qubits = -1 self.add(instructions or []) @@ -171,24 +174,33 @@ def add(self, instructions: Iterable[Instruction], noise_index: int = 0) -> None self._add(instruction, noise_index) def _add(self, instruction: Instruction, noise_index: int = 0) -> None: - - if isinstance(instruction.operator, Noise): + operator = instruction.operator + if isinstance(operator, CompilerDirective): + time = self._update_qubit_times(self._qubits) + self._moments[MomentsKey(time, None, MomentType.COMPILER_DIRECTIVE, 0)] = instruction + self._depth = time + 1 + self._time_all_qubits = time + elif isinstance(operator, Noise): self.add_noise(instruction) - else: qubit_range = instruction.target - time = max([self._max_time_for_qubit(qubit) for qubit in qubit_range]) + 1 - - # Mark all qubits in qubit_range with max_time - for qubit in qubit_range: - self._max_times[qubit] = max(time, self._max_time_for_qubit(qubit)) - + time = self._update_qubit_times(qubit_range) self._moments[ MomentsKey(time, instruction.target, MomentType.GATE, noise_index) ] = instruction self._qubits.update(instruction.target) self._depth = max(self._depth, time + 1) + def _update_qubit_times(self, qubits): + qubit_max_times = [self._max_time_for_qubit(qubit) for qubit in qubits] + [ + self._time_all_qubits + ] + time = max(qubit_max_times) + 1 + # Update time for all specified qubits + for qubit in qubits: + self._max_times[qubit] = time + return time + def add_noise( self, instruction: Instruction, input_type: str = "noise", noise_index: int = 0 ) -> None: @@ -244,6 +256,7 @@ def sort_moments(self) -> None: self._moments = sorted_moment 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) # @@ -290,7 +303,7 @@ def __contains__(self, item): def __eq__(self, other): if isinstance(other, Moments): - return (self._moments) == (other._moments) + return self._moments == other._moments return NotImplemented def __ne__(self, other): diff --git a/src/braket/circuits/noise_helpers.py b/src/braket/circuits/noise_helpers.py index 492189f1..6ccba477 100644 --- a/src/braket/circuits/noise_helpers.py +++ b/src/braket/circuits/noise_helpers.py @@ -33,9 +33,9 @@ def no_noise_applied_warning(noise_applied: bool): "Helper function to give a warning is noise is not applied" if noise_applied is False: 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." + "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." ) diff --git a/src/braket/circuits/operator.py b/src/braket/circuits/operator.py index 7ead4b69..03b94a56 100644 --- a/src/braket/circuits/operator.py +++ b/src/braket/circuits/operator.py @@ -20,7 +20,7 @@ class Operator(ABC): @property @abstractmethod def name(self) -> str: - """str: The operator name.""" + """str: The name of the operator.""" @abstractmethod def to_ir(self, *args, **kwargs): diff --git a/src/braket/circuits/quantum_operator.py b/src/braket/circuits/quantum_operator.py index 47fef1f2..eac1797a 100644 --- a/src/braket/circuits/quantum_operator.py +++ b/src/braket/circuits/quantum_operator.py @@ -13,7 +13,7 @@ from __future__ import annotations -from typing import Any, List, Optional, Sequence +from typing import Any, Optional, Sequence, Tuple import numpy as np @@ -96,8 +96,8 @@ def qubit_count(self) -> int: return self._qubit_count @property - def ascii_symbols(self) -> List[str]: - """List[str]: Returns the ascii symbols for the quantum operator.""" + def ascii_symbols(self) -> Tuple[str, ...]: + """Tuple[str, ...]: Returns the ascii symbols for the quantum operator.""" return self._ascii_symbols @property 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 8354207b..0e36d340 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -39,6 +39,16 @@ S3_TARGET = AwsSession.S3DestinationFolder("foo", "bar") +IONQ_ARN = "device/qpu/ionq" +RIGETTI_ARN = "device/qpu/rigetti" +SIMULATOR_ARN = "device/quantum-simulator" + +DEVICE_PARAMETERS = [ + (IONQ_ARN, IonqDeviceParameters), + (RIGETTI_ARN, RigettiDeviceParameters), + (SIMULATOR_ARN, GateModelSimulatorDeviceParameters), +] + @pytest.fixture def aws_session(): @@ -338,23 +348,14 @@ def test_create_invalid_task_specification(aws_session, arn): AwsQuantumTask.create(aws_session, arn, "foo", S3_TARGET, 1000) -@pytest.mark.parametrize( - "device_arn,device_parameters_class", - [ - ("device/qpu/ionq", IonqDeviceParameters), - ("device/qpu/rigetti", RigettiDeviceParameters), - ("device/quantum-simulator", GateModelSimulatorDeviceParameters), - ], -) +@pytest.mark.parametrize("device_arn,device_parameters_class", DEVICE_PARAMETERS) def test_from_circuit_with_shots(device_arn, device_parameters_class, aws_session, circuit): mocked_task_arn = "task-arn-1" aws_session.create_quantum_task.return_value = mocked_task_arn shots = 53 task = AwsQuantumTask.create(aws_session, device_arn, circuit, S3_TARGET, shots) - assert task == AwsQuantumTask( - mocked_task_arn, aws_session, GateModelQuantumTaskResult.from_string - ) + assert task == AwsQuantumTask(mocked_task_arn, aws_session) _assert_create_quantum_task_called_with( aws_session, @@ -371,10 +372,7 @@ def test_from_circuit_with_shots(device_arn, device_parameters_class, aws_sessio @pytest.mark.parametrize( - "device_arn,device_parameters_class", - [ - ("device/qpu/rigetti", RigettiDeviceParameters), - ], + "device_arn,device_parameters_class", [(RIGETTI_ARN, RigettiDeviceParameters)] ) def test_from_circuit_with_disabled_rewiring( device_arn, device_parameters_class, aws_session, circuit @@ -386,9 +384,7 @@ def test_from_circuit_with_disabled_rewiring( task = AwsQuantumTask.create( aws_session, device_arn, circuit, S3_TARGET, shots, disable_qubit_rewiring=True ) - assert task == AwsQuantumTask( - mocked_task_arn, aws_session, GateModelQuantumTaskResult.from_string - ) + assert task == AwsQuantumTask(mocked_task_arn, aws_session) _assert_create_quantum_task_called_with( aws_session, @@ -404,6 +400,46 @@ def test_from_circuit_with_disabled_rewiring( ) +@pytest.mark.parametrize( + "device_arn,device_parameters_class", [(RIGETTI_ARN, RigettiDeviceParameters)] +) +def test_from_circuit_with_verbatim(device_arn, device_parameters_class, aws_session): + circ = Circuit().add_verbatim_box(Circuit().h(0)) + mocked_task_arn = "task-arn-1" + aws_session.create_quantum_task.return_value = mocked_task_arn + shots = 1337 + + task = AwsQuantumTask.create( + aws_session, + device_arn, + circ, + S3_TARGET, + shots, + disable_qubit_rewiring=True, + ) + assert task == AwsQuantumTask(mocked_task_arn, aws_session) + + _assert_create_quantum_task_called_with( + aws_session, + device_arn, + circ, + S3_TARGET, + shots, + device_parameters_class( + paradigmParameters=GateModelParameters( + qubitCount=circ.qubit_count, disableQubitRewiring=True + ) + ), + ) + + +@pytest.mark.xfail(raises=ValueError) +def test_from_circuit_with_verbatim_qubit_rewiring_not_disabled(aws_session): + circ = Circuit().add_verbatim_box(Circuit().h(0)) + shots = 57 + AwsQuantumTask.create(aws_session, RIGETTI_ARN, circ, S3_TARGET, shots) + + @pytest.mark.xfail(raises=ValueError) def test_from_circuit_with_shots_value_error(aws_session, arn, circuit): mocked_task_arn = "task-arn-1" @@ -661,9 +697,7 @@ def test_from_annealing(device_parameters, aws_session, arn, problem): 1000, device_parameters=device_parameters, ) - assert task == AwsQuantumTask( - mocked_task_arn, aws_session, AnnealingQuantumTaskResult.from_string - ) + assert task == AwsQuantumTask(mocked_task_arn, aws_session) annealing_parameters = _create_annealing_device_params(device_parameters, device_arn=arn) validate( json.loads(annealing_parameters.json(exclude_none=True)), annealing_parameters.schema() @@ -678,14 +712,7 @@ def test_from_annealing(device_parameters, aws_session, arn, problem): ) -@pytest.mark.parametrize( - "device_arn,device_parameters_class", - [ - ("device/qpu/ionq", IonqDeviceParameters), - ("device/qpu/rigetti", RigettiDeviceParameters), - ("device/quantum-simulator", GateModelSimulatorDeviceParameters), - ], -) +@pytest.mark.parametrize("device_arn,device_parameters_class", DEVICE_PARAMETERS) def test_create_with_tags(device_arn, device_parameters_class, aws_session, circuit): mocked_task_arn = "task-arn-tags" aws_session.create_quantum_task.return_value = mocked_task_arn @@ -693,9 +720,7 @@ def test_create_with_tags(device_arn, device_parameters_class, aws_session, circ tags = {"state": "washington"} task = AwsQuantumTask.create(aws_session, device_arn, circuit, S3_TARGET, shots, tags=tags) - assert task == AwsQuantumTask( - mocked_task_arn, aws_session, GateModelQuantumTaskResult.from_string - ) + assert task == AwsQuantumTask(mocked_task_arn, aws_session) _assert_create_quantum_task_called_with( aws_session, device_arn, @@ -729,7 +754,7 @@ def test_aws_session_for_task_arn(mock_session): def _init_and_add_to_list(aws_session, arn, task_list): - task_list.append(AwsQuantumTask(arn, aws_session, GateModelQuantumTaskResult.from_string)) + task_list.append(AwsQuantumTask(arn, aws_session)) def _assert_create_quantum_task_called_with( 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 0a6b1ea2..b485486d 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -205,6 +205,179 @@ def test_connector_across_non_used_qubits(): assert AsciiCircuitDiagram.build_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 |", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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 |", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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|", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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-C-EndVerbatim-", + " | | | ", + "q1 : -*************---X-***********-", + "", + "T : | 0 |1|2| 3 |", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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-C-EndVerbatim-", + " | | | ", + "q1 : ---*************---X-***********-", + "", + "T : |0| 1 |2|3| 4 |", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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-C-EndVerbatim-H-", + " | | | ", + "q1 : -*************---X-***********---", + "", + "T : | 0 |1|2| 3 |4|", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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-C---EndVerbatim-", + " | | | ", + "q1 : -|---------------X-C-|-----------", + " | | | ", + "q2 : -*************-----X-***********-", + "", + "T : | 0 |1|2|3| 4 |", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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-C---EndVerbatim-", + " | | | ", + "q1 : ---|---------------X-C-|-----------", + " | | | ", + "q2 : ---*************-----X-***********-", + "", + "T : |0| 1 |2|3|4| 5 |", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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-C---EndVerbatim-H-", + " | | | ", + "q1 : -|---------------X-C-|-------------", + " | | | ", + "q2 : -*************-----X-***********---", + "", + "T : | 0 |1|2|3| 4 |5|", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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 : ---|---------------|-----------C-", + " | | | ", + "q4 : ---*************---***********-X-", + "", + "T : |0| 1 |2| 3 |4|", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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 : ---C---StartVerbatim---EndVerbatim---", + " | | | ", + "q1 : -H-X-C-|-------------H-|-------------", + " | | | ", + "q2 : -----X-|---------------|-----------C-", + " | | | ", + "q3 : -------*************---***********-X-", + "", + "T : |0|1|2| 3 |4| 5 |6|", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_diagram(circ) == expected + + def test_ignore_non_gates(): class Foo(Operator): @property diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 2ca08e26..de693289 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -27,6 +27,7 @@ QubitSet, ResultType, circuit, + compiler_directives, gates, noise, ) @@ -312,6 +313,83 @@ def test_add_circuit_with_target_and_mapping(h): Circuit().add_circuit(h, target=[10], target_mapping={0: 10}) +def test_add_verbatim_box(): + circ = Circuit().h(0).add_verbatim_box(Circuit().cnot(0, 1)) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(compiler_directives.StartVerbatimBox())) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(compiler_directives.EndVerbatimBox())) + ) + assert circ == expected + + +def test_add_verbatim_box_different_qubits(): + circ = Circuit().h(1).add_verbatim_box(Circuit().h(0)).cnot(3, 4) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 1)) + .add_instruction(Instruction(compiler_directives.StartVerbatimBox())) + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(compiler_directives.EndVerbatimBox())) + .add_instruction(Instruction(Gate.CNot(), [3, 4])) + ) + assert circ == expected + + +def test_add_verbatim_box_no_preceding(): + circ = Circuit().add_verbatim_box(Circuit().h(0)).cnot(2, 3) + expected = ( + Circuit() + .add_instruction(Instruction(compiler_directives.StartVerbatimBox())) + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(compiler_directives.EndVerbatimBox())) + .add_instruction(Instruction(Gate.CNot(), [2, 3])) + ) + assert circ == expected + + +def test_add_verbatim_box_empty(): + circuit = Circuit().add_verbatim_box(Circuit()) + assert circuit == Circuit() + assert not circuit.qubits_frozen + + +def test_add_verbatim_box_with_mapping(cnot): + circ = Circuit().add_verbatim_box(cnot, target_mapping={0: 10, 1: 11}) + expected = ( + Circuit() + .add_instruction(Instruction(compiler_directives.StartVerbatimBox())) + .add_instruction(Instruction(Gate.CNot(), [10, 11])) + .add_instruction(Instruction(compiler_directives.EndVerbatimBox())) + ) + assert circ == expected + + +def test_add_verbatim_box_with_target(cnot): + circ = Circuit().add_verbatim_box(cnot, target=[10, 11]) + expected = ( + Circuit() + .add_instruction(Instruction(compiler_directives.StartVerbatimBox())) + .add_instruction(Instruction(Gate.CNot(), [10, 11])) + .add_instruction(Instruction(compiler_directives.EndVerbatimBox())) + ) + assert circ == expected + + +@pytest.mark.xfail(raises=TypeError) +def test_add_verbatim_box_with_target_and_mapping(h): + Circuit().add_verbatim_box(h, target=[10], target_mapping={0: 10}) + + +@pytest.mark.xfail(raises=ValueError) +def test_add_verbatim_box_result_types(): + Circuit().h(0).add_verbatim_box( + Circuit().cnot(0, 1).expectation(observable=Observable.X(), target=0) + ) + + 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/circuits/test_compiler_directive.py b/test/unit_tests/braket/circuits/test_compiler_directive.py new file mode 100644 index 00000000..4a971e85 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_compiler_directive.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. + +import pytest + +from braket.circuits import Operator +from braket.circuits.compiler_directive import CompilerDirective + + +@pytest.fixture +def ascii_symbols(): + return ["foo"] + + +@pytest.fixture +def compiler_directive(ascii_symbols): + return CompilerDirective(ascii_symbols=ascii_symbols) + + +def test_is_operator(compiler_directive): + assert isinstance(compiler_directive, Operator) + + +def test_ascii_symbols(compiler_directive, ascii_symbols): + assert compiler_directive.ascii_symbols == tuple(ascii_symbols) + + +@pytest.mark.xfail(raises=ValueError) +def test_none_ascii(): + CompilerDirective(ascii_symbols=None) + + +def test_name(compiler_directive): + expected = compiler_directive.__class__.__name__ + assert compiler_directive.name == expected + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_to_ir_not_implemented_by_default(compiler_directive): + compiler_directive.to_ir(None) + + +def test_str(compiler_directive): + assert str(compiler_directive) == compiler_directive.name diff --git a/test/unit_tests/braket/circuits/test_compiler_directives.py b/test/unit_tests/braket/circuits/test_compiler_directives.py new file mode 100644 index 00000000..13de3d00 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_compiler_directives.py @@ -0,0 +1,38 @@ +# 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 braket.ir.jaqcd as ir +from braket.circuits import compiler_directives +from braket.circuits.compiler_directive import CompilerDirective + +testdata = [ + (compiler_directives.StartVerbatimBox, ir.StartVerbatimBox), + (compiler_directives.EndVerbatimBox, ir.EndVerbatimBox), +] + + +@pytest.mark.parametrize("testclass,irclass", testdata) +def test_to_ir(testclass, irclass): + assert testclass().to_ir() == irclass() + + +@pytest.mark.parametrize("testclass,irclass", testdata) +def test_equality(testclass, irclass): + op1 = testclass() + op2 = testclass() + assert op1 == op2 + assert op1 is not op2 + assert op1 != CompilerDirective(ascii_symbols=["foo"]) + assert op1 != "not a directive" diff --git a/test/unit_tests/braket/circuits/test_instruction.py b/test/unit_tests/braket/circuits/test_instruction.py index 3c4f1c99..cef23faf 100644 --- a/test/unit_tests/braket/circuits/test_instruction.py +++ b/test/unit_tests/braket/circuits/test_instruction.py @@ -33,7 +33,7 @@ def test_empty_operator(): @pytest.mark.xfail(raises=ValueError) def test_non_matching_qubit_set_and_qubit_count(): - Instruction(Gate.CNot, target=[0, 0]) + Instruction(Gate.CNot(), target=[0, 0]) def test_init_with_qubits(): diff --git a/test/unit_tests/braket/circuits/test_quantum_operator.py b/test/unit_tests/braket/circuits/test_quantum_operator.py index cf2322f9..11c7441b 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator.py @@ -24,14 +24,23 @@ def fixed_qubit_count(): @pytest.fixture -def quantum_operator(): - return QuantumOperator(qubit_count=1, ascii_symbols=["foo"]) +def ascii_symbols(): + return ["foo"] + + +@pytest.fixture +def quantum_operator(ascii_symbols): + return QuantumOperator(qubit_count=1, ascii_symbols=ascii_symbols) def test_is_operator(quantum_operator): assert isinstance(quantum_operator, Operator) +def test_ascii_symbols(quantum_operator, ascii_symbols): + assert quantum_operator.ascii_symbols == tuple(ascii_symbols) + + def test_fixed_qubit_count_implemented(): operator = _DummyQuantumOperator(qubit_count=None, ascii_symbols=["foo", "bar"]) assert operator.qubit_count == _DummyQuantumOperator.fixed_qubit_count() From 5c9ca726160d223255ccae7337f9d6e43d110f77 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 9 Sep 2021 21:30:16 +0000 Subject: [PATCH 0379/1165] prepare release v1.9.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 585376b3..174e9b6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.9.0 (2021-09-09) + +### Features + + * Verbatim boxes + ## v1.8.0 (2021-08-23) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 04cf6198..2afad28b 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.8.1.dev0" +__version__ = "1.9.0" From c42f1269ff79b877bf484cfbd0c9182a19b8b410 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 9 Sep 2021 21:30:16 +0000 Subject: [PATCH 0380/1165] update development version to v1.9.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 2afad28b..03729ff0 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.9.0" +__version__ = "1.9.1.dev0" From 76abf239fde5ae005be800e87c24521d9f486b1e Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 23 Sep 2021 12:29:32 -0700 Subject: [PATCH 0381/1165] =?UTF-8?q?change:=20Have=20tasks=20that=20are?= =?UTF-8?q?=20failed=20output=20the=20failure=20reason=20from=20tas?= =?UTF-8?q?=E2=80=A6=20(#275)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * change: Have tasks that are failed output the failure reason from task metadata --- src/braket/aws/aws_quantum_task.py | 8 ++++++-- test/unit_tests/braket/aws/test_aws_quantum_task.py | 8 ++++++++ test/unit_tests/braket/circuits/test_gates.py | 2 +- test/unit_tests/braket/circuits/test_observables.py | 2 +- .../braket/circuits/test_quantum_operator_helpers.py | 2 +- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 0d190c2b..2602ecf9 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -255,9 +255,13 @@ def state(self, use_cached_value: bool = False) -> str: return self._status(use_cached_value) def _status(self, use_cached_value=False): - status = self.metadata(use_cached_value).get("status") + metadata = self.metadata(use_cached_value) + status = metadata.get("status") if not use_cached_value and status in self.NO_RESULT_TERMINAL_STATES: - self._logger.warning(f"Task is in terminal state {status} and no result is available") + self._logger.warning(f"Task is in terminal state {status} and no result is available.") + if status == "FAILED": + failure_reason = metadata.get("failureReason", "unknown") + self._logger.warning(f"Task failure reason is: {failure_reason}.") return status def _update_status_if_nonterminal(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 0e36d340..90481bdd 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -146,6 +146,14 @@ def test_state(quantum_task): _mock_metadata(quantum_task._aws_session, state_2) assert quantum_task.state(use_cached_value=True) == state_1 + state_3 = "FAILED" + _mock_metadata(quantum_task._aws_session, state_3) + assert quantum_task.state() == state_3 + + state_4 = "CANCELLED" + _mock_metadata(quantum_task._aws_session, state_4) + assert quantum_task.state() == state_4 + def test_cancel(quantum_task): future = quantum_task.async_result() diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index d91ea3e8..5d575b95 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -105,7 +105,7 @@ (np.array([1])), (np.array([0, 1, 2])), (np.array([[0, 1], [1, 2], [3, 4]])), - (np.array([[0, 1, 2], [2, 3]])), + (np.array([[0, 1, 2], [2, 3]], dtype=object)), (np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])), (np.array([[0, 1], [1, 1]])), ] diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index 7787feb8..2792efbb 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -39,7 +39,7 @@ (np.array([1])), (np.array([0, 1, 2])), (np.array([[0, 1], [1, 2], [3, 4]])), - (np.array([[0, 1, 2], [2, 3]])), + (np.array([[0, 1, 2], [2, 3]], dtype=object)), (np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])), (Gate.T().to_matrix()), ] diff --git a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py index 1073d43d..c76f09a2 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py @@ -37,7 +37,7 @@ (np.array([1])), (np.array([0, 1, 2])), (np.array([[0, 1], [1, 2], [3, 4]])), - (np.array([[0, 1, 2], [2, 3]])), + (np.array([[0, 1, 2], [2, 3]], dtype=object)), (np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])), ] From c6743fc5a584decbb723ae4a07eb7c816fb4b59d Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 24 Sep 2021 18:13:03 +0000 Subject: [PATCH 0382/1165] prepare release v1.9.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 174e9b6a..a24b48aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.9.1 (2021-09-24) + +### Bug Fixes and Other Changes + + * Have tasks that are failed output the failure reason from tas… + ## v1.9.0 (2021-09-09) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 03729ff0..a66fbdc0 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.9.1.dev0" +__version__ = "1.9.1" From fb1c7113a0609f422f8a78d427740635e9dbbb76 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 24 Sep 2021 18:13:03 +0000 Subject: [PATCH 0383/1165] update development version to v1.9.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 a66fbdc0..111f171a 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.9.1" +__version__ = "1.9.2.dev0" From 8f05f5c929129b8c2752c8c5c2381d4ddb63ea30 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Thu, 30 Sep 2021 12:02:36 -0700 Subject: [PATCH 0384/1165] Fix: Pin jsonschema version (#278) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 83ad33df..aae646d2 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ "black", "flake8", "isort", - "jsonschema", + "jsonschema==3.2.0", "pre-commit", "pylint", "pytest", From 8f07211202d88e65197063fb817a3e44b8c4e0e3 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 30 Sep 2021 19:54:15 +0000 Subject: [PATCH 0385/1165] prepare release v1.9.2 --- CHANGELOG.md | 2 ++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a24b48aa..86d959a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## v1.9.2 (2021-09-30) + ## v1.9.1 (2021-09-24) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 111f171a..abfd6c9f 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.9.2.dev0" +__version__ = "1.9.2" From 6427d7e22a95d9273bbbd6fc35a4dd049f02a3ca Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 30 Sep 2021 19:54:15 +0000 Subject: [PATCH 0386/1165] update development version to v1.9.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 abfd6c9f..d9fcc704 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.9.2" +__version__ = "1.9.3.dev0" From d6de65ff0cfceb22aa3fc786b12d04f2e3b938d8 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Fri, 1 Oct 2021 10:39:59 -0700 Subject: [PATCH 0387/1165] fix: rigetti typo (#279) --- test/unit_tests/braket/aws/test_aws_device.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 8c10429f..b4cd59ed 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -68,7 +68,7 @@ ) -def test_mock_regetti_schema_1(): +def test_mock_rigetti_schema_1(): validate(MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_1, RigettiDeviceCapabilities.schema()) @@ -115,7 +115,7 @@ def test_mock_regetti_schema_1(): ) -def test_mock_regetti_schema_2(): +def test_mock_rigetti_schema_2(): validate(MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_2, RigettiDeviceCapabilities.schema()) From 4bd997cf622d5a97bf969a2e07c1cc3b0c996047 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 1 Oct 2021 18:14:36 +0000 Subject: [PATCH 0388/1165] prepare release v1.9.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 86d959a9..5912eeca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.9.3 (2021-10-01) + +### Bug Fixes and Other Changes + + * rigetti typo + ## v1.9.2 (2021-09-30) ## v1.9.1 (2021-09-24) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d9fcc704..c1ecc472 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.9.3.dev0" +__version__ = "1.9.3" From a7d706e242aa7d4f01424a4b07cbd7bdc27b5b79 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 1 Oct 2021 18:14:36 +0000 Subject: [PATCH 0389/1165] update development version to v1.9.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 c1ecc472..755beb05 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.9.3" +__version__ = "1.9.4.dev0" From 6b7c3e93545511a482339555e9ec4f5bfccb2c4c Mon Sep 17 00:00:00 2001 From: Sandra McCann Date: Fri, 1 Oct 2021 16:06:45 -0400 Subject: [PATCH 0390/1165] fixed a spelling nit (#280) --- doc/getting-started.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/getting-started.rst b/doc/getting-started.rst index efd33bee..20525474 100644 --- a/doc/getting-started.rst +++ b/doc/getting-started.rst @@ -6,7 +6,7 @@ It is easy to get started with Amazon Braket Python SDK. You can get started using an Amazon Braket notebook instance or using your own environment. -For more informnation about Amazon Braket, see the full set of documentation +For more information about Amazon Braket, see the full set of documentation at https://docs.aws.amazon.com/braket/index.html. .. toctree:: From e86be68c78cdbad3ff24c7b2e652a41b17f6145c Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 4 Oct 2021 18:12:44 +0000 Subject: [PATCH 0391/1165] prepare release v1.9.4 --- 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 5912eeca..d8a15e4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.9.4 (2021-10-04) + +### Bug Fixes and Other Changes + + * fixed a spelling nit + ## v1.9.3 (2021-10-01) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 755beb05..f77f58ec 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.9.4.dev0" +__version__ = "1.9.4" From d05c05aebe227cbc028cf2dfe778a05419d4c087 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 4 Oct 2021 18:12:44 +0000 Subject: [PATCH 0392/1165] update development version to v1.9.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 f77f58ec..d04b1c3e 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.9.4" +__version__ = "1.9.5.dev0" From 9bc57ce4c1be3d0ae2efbdf3ca36dbaea1b97a4e Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 5 Oct 2021 11:03:19 -0700 Subject: [PATCH 0393/1165] Pin Coverage 5.5 (#281) --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index aae646d2..cd82e27e 100644 --- a/setup.py +++ b/setup.py @@ -31,6 +31,7 @@ "backoff", "boltons", "boto3", + "coverage==5.5", "nest-asyncio", "networkx", "numpy", From 6dc3b0b77948a3825985abacdcfd4db7fa9feb88 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 5 Oct 2021 18:49:58 +0000 Subject: [PATCH 0394/1165] prepare release v1.9.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 d8a15e4f..3f2197d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.9.5 (2021-10-05) + +### Bug Fixes and Other Changes + + * Pin Coverage 5.5 + ## v1.9.4 (2021-10-04) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d04b1c3e..68a43551 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.9.5.dev0" +__version__ = "1.9.5" From 8b845135a337c256dceacfd1409fdf2de681b975 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 5 Oct 2021 18:49:58 +0000 Subject: [PATCH 0395/1165] update development version to v1.9.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 68a43551..4a17f7fa 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.9.5" +__version__ = "1.9.6.dev0" From 01636eab0385fd1ae378b34a592545ea456eaacf Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 3 Nov 2021 12:48:54 -0700 Subject: [PATCH 0396/1165] infra: Pin docutils<0.18 in doc requirements (#283) docutils 0.18 was released on October 26, 2021, and with it came some [breaking changes](https://github.com/readthedocs/readthedocs.org/issues/8616) for sphinx, and in turn [readthedocs builds](https://readthedocs.org/projects/amazon-braket-sdk-python/builds/15168485/). To keep doc builds working, docutils will be constrained to versions below 0.18. --- doc/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/requirements.txt b/doc/requirements.txt index bf1b2d65..6a86be51 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,3 +1,4 @@ +docutils<0.18 sphinx sphinx-rtd-theme sphinxcontrib-apidoc From fc61b703989ee79b551387a9c53bf166188add11 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 4 Nov 2021 18:13:07 +0000 Subject: [PATCH 0397/1165] prepare release v1.9.5.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 3f2197d1..35ecb71b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.9.5.post0 (2021-11-04) + +### Testing and Release Infrastructure + + * Pin docutils<0.18 in doc requirements + ## v1.9.5 (2021-10-05) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 4a17f7fa..48f034d7 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.9.6.dev0" +__version__ = "1.9.5.post0" From 35e3eade19b83c309590b0f967c00e3bfcbefc32 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 4 Nov 2021 18:13:07 +0000 Subject: [PATCH 0398/1165] update development version to v1.9.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 48f034d7..4a17f7fa 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.9.5.post0" +__version__ = "1.9.6.dev0" From 92b90015be335f543de199272a79f8bc73a38af3 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Mon, 29 Nov 2021 10:42:47 -0800 Subject: [PATCH 0399/1165] feature: Add support for jobs (#287) Co-authored-by: Viraj Chaudhari Co-authored-by: Milan Krneta Co-authored-by: Aaron Berdy Co-authored-by: Lin Co-authored-by: Roald Bradley Severtson Co-authored-by: Christian Madsen --- .gitignore | 1 + CHANGELOG.md | 197 ++-- CONTRIBUTING.md | 2 +- MANIFEST.in | 14 + README.md | 19 + doc/examples-hybrid-jobs.rst | 33 + doc/examples.rst | 1 + examples/bell.py | 5 +- examples/debug_bell.py | 4 - examples/job.py | 44 + setup.py | 2 + src/braket/aws/__init__.py | 1 + src/braket/aws/aws_device.py | 79 +- src/braket/aws/aws_quantum_job.py | 561 +++++++++++ src/braket/aws/aws_quantum_task.py | 2 +- src/braket/aws/aws_session.py | 543 ++++++++++- src/braket/jobs/__init__.py | 26 + src/braket/jobs/config.py | 81 ++ src/braket/jobs/data_persistence.py | 136 +++ src/braket/jobs/image_uri_config/base.json | 12 + .../jobs/image_uri_config/pl_pytorch.json | 12 + .../jobs/image_uri_config/pl_tensorflow.json | 12 + src/braket/jobs/image_uris.py | 85 ++ src/braket/jobs/local/__init__.py | 14 + src/braket/jobs/local/local_job.py | 258 +++++ src/braket/jobs/local/local_job_container.py | 245 +++++ .../jobs/local/local_job_container_setup.py | 274 ++++++ src/braket/jobs/logs.py | 231 +++++ src/braket/jobs/metrics.py | 43 + src/braket/jobs/metrics_data/__init__.py | 17 + .../cwl_insights_metrics_fetcher.py | 185 ++++ .../jobs/metrics_data/cwl_metrics_fetcher.py | 164 ++++ src/braket/jobs/metrics_data/definitions.py | 36 + src/braket/jobs/metrics_data/exceptions.py | 18 + .../jobs/metrics_data/log_metrics_parser.py | 198 ++++ src/braket/jobs/quantum_job.py | 186 ++++ src/braket/jobs/quantum_job_creation.py | 409 ++++++++ src/braket/jobs/serialization.py | 67 ++ test/integ_tests/job_test_script.py | 53 + .../test_create_local_quantum_job.py | 151 +++ test/integ_tests/test_create_quantum_job.py | 180 ++++ test/integ_tests/test_device_creation.py | 2 +- .../braket/aws/common_test_utils.py | 39 +- test/unit_tests/braket/aws/test_aws_device.py | 260 +++-- .../braket/aws/test_aws_quantum_job.py | 910 ++++++++++++++++++ .../unit_tests/braket/aws/test_aws_session.py | 894 ++++++++++++++++- .../braket/jobs/local/test_local_job.py | 215 +++++ .../jobs/local/test_local_job_container.py | 364 +++++++ .../local/test_local_job_container_setup.py | 228 +++++ .../test_cwl_insights_metrics_fetcher.py | 105 ++ .../metrics_data/test_cwl_metrics_fetcher.py | 135 +++ .../metrics_data/test_log_metrics_parser.py | 164 ++++ .../braket/jobs/test_data_persistence.py | 274 ++++++ .../unit_tests/braket/jobs/test_image_uris.py | 57 ++ test/unit_tests/braket/jobs/test_metrics.py | 35 + .../braket/jobs/test_quantum_job_creation.py | 636 ++++++++++++ .../braket/jobs/test_serialization.py | 58 ++ 57 files changed, 8704 insertions(+), 273 deletions(-) create mode 100644 MANIFEST.in create mode 100644 doc/examples-hybrid-jobs.rst create mode 100644 examples/job.py create mode 100644 src/braket/aws/aws_quantum_job.py create mode 100644 src/braket/jobs/__init__.py create mode 100644 src/braket/jobs/config.py create mode 100644 src/braket/jobs/data_persistence.py create mode 100644 src/braket/jobs/image_uri_config/base.json create mode 100644 src/braket/jobs/image_uri_config/pl_pytorch.json create mode 100644 src/braket/jobs/image_uri_config/pl_tensorflow.json create mode 100644 src/braket/jobs/image_uris.py create mode 100644 src/braket/jobs/local/__init__.py create mode 100644 src/braket/jobs/local/local_job.py create mode 100644 src/braket/jobs/local/local_job_container.py create mode 100644 src/braket/jobs/local/local_job_container_setup.py create mode 100644 src/braket/jobs/logs.py create mode 100644 src/braket/jobs/metrics.py create mode 100644 src/braket/jobs/metrics_data/__init__.py create mode 100644 src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py create mode 100644 src/braket/jobs/metrics_data/cwl_metrics_fetcher.py create mode 100644 src/braket/jobs/metrics_data/definitions.py create mode 100644 src/braket/jobs/metrics_data/exceptions.py create mode 100644 src/braket/jobs/metrics_data/log_metrics_parser.py create mode 100644 src/braket/jobs/quantum_job.py create mode 100644 src/braket/jobs/quantum_job_creation.py create mode 100644 src/braket/jobs/serialization.py create mode 100644 test/integ_tests/job_test_script.py create mode 100644 test/integ_tests/test_create_local_quantum_job.py create mode 100644 test/integ_tests/test_create_quantum_job.py create mode 100644 test/unit_tests/braket/aws/test_aws_quantum_job.py create mode 100644 test/unit_tests/braket/jobs/local/test_local_job.py create mode 100644 test/unit_tests/braket/jobs/local/test_local_job_container.py create mode 100644 test/unit_tests/braket/jobs/local/test_local_job_container_setup.py create mode 100644 test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py create mode 100644 test/unit_tests/braket/jobs/metrics_data/test_cwl_metrics_fetcher.py create mode 100644 test/unit_tests/braket/jobs/metrics_data/test_log_metrics_parser.py create mode 100644 test/unit_tests/braket/jobs/test_data_persistence.py create mode 100644 test/unit_tests/braket/jobs/test_image_uris.py create mode 100644 test/unit_tests/braket/jobs/test_metrics.py create mode 100644 test/unit_tests/braket/jobs/test_quantum_job_creation.py create mode 100644 test/unit_tests/braket/jobs/test_serialization.py diff --git a/.gitignore b/.gitignore index b554563e..090c26b8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.swp *.idea *.DS_Store +.vscode/* build_files.tar.gz .ycm_extra_conf.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 35ecb71b..5a2e9c06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,19 +10,19 @@ ### Bug Fixes and Other Changes - * Pin Coverage 5.5 +- Pin Coverage 5.5 ## v1.9.4 (2021-10-04) ### Bug Fixes and Other Changes - * fixed a spelling nit +- fixed a spelling nit ## v1.9.3 (2021-10-01) ### Bug Fixes and Other Changes - * rigetti typo +- rigetti typo ## v1.9.2 (2021-09-30) @@ -30,316 +30,316 @@ ### Bug Fixes and Other Changes - * Have tasks that are failed output the failure reason from tas… +- Have tasks that are failed output the failure reason from tas… ## v1.9.0 (2021-09-09) ### Features - * Verbatim boxes +- Verbatim boxes ## v1.8.0 (2021-08-23) ### Features - * Calculate arbitrary observables when `shots=0` +- Calculate arbitrary observables when `shots=0` ### Bug Fixes and Other Changes - * Remove immutable default args +- Remove immutable default args ## v1.7.5 (2021-08-18) ### Bug Fixes and Other Changes - * Add test for local simulator device names +- Add test for local simulator device names ### Documentation Changes - * Add documentation for support +- Add documentation for support ### Testing and Release Infrastructure - * Update copyright notice +- Update copyright notice ## v1.7.4 (2021-08-06) ### Bug Fixes and Other Changes - * Flatten Tensor Products +- Flatten Tensor Products ## v1.7.3.post0 (2021-08-05) ### Documentation Changes - * Modify README.md to include update instructions +- Modify README.md to include update instructions ## v1.7.3 (2021-07-22) ### Bug Fixes and Other Changes - * Add json schema validation for dwave device schemas. +- Add json schema validation for dwave device schemas. ## v1.7.2 (2021-07-14) ### Bug Fixes and Other Changes - * add json validation for device schema in unit tests +- add json validation for device schema in unit tests ## v1.7.1 (2021-07-02) ### Bug Fixes and Other Changes - * Result Type syntax in IR - * Update test_circuit.py +- Result Type syntax in IR +- Update test_circuit.py ## v1.7.0 (2021-06-25) ### Features - * code Circuit.as_unitary() +- code Circuit.as_unitary() ### Bug Fixes and Other Changes - * allow integral number types that aren't type int +- allow integral number types that aren't type int ## v1.6.5 (2021-06-23) ### Bug Fixes and Other Changes - * Get qubit count without instantiating op - * Require qubit indices to be integers +- Get qubit count without instantiating op +- Require qubit indices to be integers ## v1.6.4 (2021-06-10) ### Bug Fixes and Other Changes - * fallback on empty dict for device level parameters +- fallback on empty dict for device level parameters ## v1.6.3 (2021-06-04) ### Bug Fixes and Other Changes - * use device data to create device level parameter data when creating a… +- use device data to create device level parameter data when creating a… ## v1.6.2 (2021-05-28) ### Bug Fixes and Other Changes - * exclude null values from device parameters for annealing tasks +- exclude null values from device parameters for annealing tasks ## v1.6.1 (2021-05-25) ### Bug Fixes and Other Changes - * copy the boto3 session using the default botocore session +- copy the boto3 session using the default botocore session ## v1.6.0.post0 (2021-05-24) ### Documentation Changes - * Add reference to the noise simulation example notebook +- Add reference to the noise simulation example notebook ## v1.6.0 (2021-05-24) ### Features - * Noise operators +- Noise operators ### Testing and Release Infrastructure - * Use GitHub source for tox tests +- Use GitHub source for tox tests ## v1.5.16 (2021-05-05) ### Bug Fixes and Other Changes - * Added /taskArn to id field in AwsQuantumTask __repr__ +- Added /taskArn to id field in AwsQuantumTask **repr** ### Documentation Changes - * Fix link, typos, order +- Fix link, typos, order ## v1.5.15 (2021-04-08) ### Bug Fixes and Other Changes - * stop manually managing waiting treads in quantum task batch requests +- stop manually managing waiting treads in quantum task batch requests ## v1.5.14 (2021-04-07) ### Bug Fixes and Other Changes - * roll back dwave change - * Dwave roll back - * use device data to create device level parameter data when creating a quantum annealing task +- roll back dwave change +- Dwave roll back +- use device data to create device level parameter data when creating a quantum annealing task ## v1.5.13 (2021-03-26) ### Bug Fixes and Other Changes - * check for task completion before entering async event loop - * remove unneeded get_quantum_task calls +- check for task completion before entering async event loop +- remove unneeded get_quantum_task calls ## v1.5.12 (2021-03-25) ### Bug Fixes and Other Changes - * Update user_agent for AwsSession +- Update user_agent for AwsSession ## v1.5.11 (2021-03-22) ### Bug Fixes and Other Changes - * Fix broken repository links +- Fix broken repository links ## v1.5.10.post2 (2021-03-19) ### Testing and Release Infrastructure - * Run unit tests for dependent packages +- Run unit tests for dependent packages ## v1.5.10.post1 (2021-03-16) ### Documentation Changes - * Remove STS calls from examples +- Remove STS calls from examples ## v1.5.10.post0 (2021-03-11) ### Testing and Release Infrastructure - * Add Python 3.9 +- Add Python 3.9 ## v1.5.10 (2021-03-03) ### Bug Fixes and Other Changes - * Don't return NotImplemented for boolean - * Use np.eye for identity - * AngledGate equality checks angles - * Unitary equality checks matrix - * Remove hardcoded device ARNs +- Don't return NotImplemented for boolean +- Use np.eye for identity +- AngledGate equality checks angles +- Unitary equality checks matrix +- Remove hardcoded device ARNs ### Documentation Changes - * Wording changes - * Add note about AWS region in README +- Wording changes +- Add note about AWS region in README ### Testing and Release Infrastructure - * Use main instead of PyPi for build dependencies - * very minor test changes +- Use main instead of PyPi for build dependencies +- very minor test changes ## v1.5.9.post0 (2021-02-22) ### Documentation Changes - * remove unneeded calls to sts from the README - * adjust s3_folder naming in README to clarify which bucket to use +- remove unneeded calls to sts from the README +- adjust s3_folder naming in README to clarify which bucket to use ## v1.5.9 (2021-02-06) ### Bug Fixes and Other Changes - * Search for unknown QPUs +- Search for unknown QPUs ## v1.5.8 (2021-01-29) ### Bug Fixes and Other Changes - * Remove redundant statement, boost coverage - * convert measurements to indices without allocating a high-dimens… +- Remove redundant statement, boost coverage +- convert measurements to indices without allocating a high-dimens… ### Testing and Release Infrastructure - * Raise coverage to 100% +- Raise coverage to 100% ## v1.5.7 (2021-01-27) ### Bug Fixes and Other Changes - * More scalable eigenvalue calculation +- More scalable eigenvalue calculation ## v1.5.6 (2021-01-21) ### Bug Fixes and Other Changes - * ensure AngledGate casts its angle argument to float so it can be… +- ensure AngledGate casts its angle argument to float so it can be… ## v1.5.5 (2021-01-15) ### Bug Fixes and Other Changes - * get correct event loop for task results after running a batch over multiple threads +- get correct event loop for task results after running a batch over multiple threads ## v1.5.4 (2021-01-12) ### Bug Fixes and Other Changes - * remove window check for polling-- revert to polling at all times - * update result_types to use hashing +- remove window check for polling-- revert to polling at all times +- update result_types to use hashing ### Testing and Release Infrastructure - * Enable Codecov +- Enable Codecov ## v1.5.3 (2020-12-31) ### Bug Fixes and Other Changes - * Update range of random qubit in test_qft_iqft_h +- Update range of random qubit in test_qft_iqft_h ## v1.5.2.post0 (2020-12-30) ### Testing and Release Infrastructure - * Add build badge - * Use GitHub Actions for CI +- Add build badge +- Use GitHub Actions for CI ## v1.5.2 (2020-12-22) ### Bug Fixes and Other Changes - * Get regions for QPUs instead of providers - * Do not search for simulators in wrong region +- Get regions for QPUs instead of providers +- Do not search for simulators in wrong region ## v1.5.1 (2020-12-10) ### Bug Fixes and Other Changes - * Use current region for simulators in get_devices +- Use current region for simulators in get_devices ## v1.5.0 (2020-12-04) ### Features - * Always accept identity observable factors +- Always accept identity observable factors ### Documentation Changes - * backticks for batching tasks - * add punctuation to aws_session.py - * fix backticks, missing periods in quantum task docs - * fix backticks, grammar for aws_device.py +- backticks for batching tasks +- add punctuation to aws_session.py +- fix backticks, missing periods in quantum task docs +- fix backticks, grammar for aws_device.py ## v1.4.1 (2020-12-04) ### Bug Fixes and Other Changes - * Correct integ test bucket +- Correct integ test bucket ## v1.4.0.post0 (2020-12-03) ### Documentation Changes - * Point README to developer guide +- Point README to developer guide ## v1.4.0 (2020-11-26) ### Features - * Enable retries when retrieving results from AwsQuantumTaskBatch. +- Enable retries when retrieving results from AwsQuantumTaskBatch. ## v1.3.1 (2020-11-25) @@ -347,99 +347,102 @@ ### Features - * Enable explicit qubit allocation - * Add support for batch execution +- Enable explicit qubit allocation +- Add support for batch execution ### Bug Fixes and Other Changes - * Correctly cache status +- Correctly cache status ## v1.2.0 (2020-11-02) ### Features - * support tags parameter for create method in AwsQuantumTask +- support tags parameter for create method in AwsQuantumTask ## v1.1.4.post0 (2020-10-30) ### Testing and Release Infrastructure - * update codeowners +- update codeowners ## v1.1.4 (2020-10-29) ### Bug Fixes and Other Changes - * Enable simultaneous measurement of observables with shared factors - * Add optimization to only poll during execution window +- Enable simultaneous measurement of observables with shared factors +- Add optimization to only poll during execution window ## v1.1.3 (2020-10-20) ### Bug Fixes and Other Changes - * add observable targets not in instructions to circuit qubit count and qubits +- add observable targets not in instructions to circuit qubit count and qubits ## v1.1.2.post1 (2020-10-15) ### Documentation Changes - * add sample notebooks link +- add sample notebooks link ## v1.1.2.post0 (2020-10-05) ### Testing and Release Infrastructure - * change check for s3 bucket exists - * change bucket creation setup for integ tests +- change check for s3 bucket exists +- change bucket creation setup for integ tests ## v1.1.2 (2020-10-02) ### Bug Fixes and Other Changes - * Add error for target qubit set size not equal to operator qubit size in instruction - * Add error message for running a circuit without instructions +- Add error for target qubit set size not equal to operator qubit size in instruction +- Add error message for running a circuit without instructions ### Documentation Changes - * Update docstring for measurement_counts +- Update docstring for measurement_counts ## v1.1.1.post2 (2020-09-29) ### Documentation Changes - * Add D-Wave Advantage_system1 arn +- Add D-Wave Advantage_system1 arn ## v1.1.1.post1 (2020-09-10) ### Testing and Release Infrastructure - * fix black formatting +- fix black formatting ## v1.1.1.post0 (2020-09-09) ### Testing and Release Infrastructure - * Add CHANGELOG.md +- Add CHANGELOG.md ## v1.1.1 (2020-09-09) ### Bug Fixes -* Add handling for solution_counts=[] for annealing result + +- Add handling for solution_counts=[] for annealing result ## v1.1.0 (2020-09-08) ### Features -* Add get_devices to search devices based on criteria + +- Add get_devices to search devices based on criteria ### Bug Fixes -* Call async_result() before calling result() -* Convert amplitude result to a complex number + +- Call async_result() before calling result() +- Convert amplitude result to a complex number ## v1.0.0.post1 (2020-08-14) ### Documentation -* add readthedocs link to README +- add readthedocs link to README ## v1.0.0 (2020-08-13) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 88373f01..3ae63fc2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,7 +60,7 @@ Before sending us a pull request, please ensure that: ### Run the Unit Tests 1. Install tox using `pip install tox` -1. Install coverage using `pip install .[test]` +1. Install coverage using `pip install '.[test]'` 1. cd into the amazon-braket-sdk-python folder: `cd amazon-braket-sdk-python` or `cd /environment/amazon-braket-sdk-python` 1. Run the following tox command and verify that all unit tests pass: `tox -e unit-tests` diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..e44b0e7b --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,14 @@ +include *.md +include *.yaml +include *.yml +include .coveragerc +include CODEOWNERS +include tox.ini +recursive-include src *.json +recursive-include bin *.py +recursive-include doc *.py +recursive-include doc *.rst +recursive-include doc *.txt +recursive-include doc Makefile +recursive-include examples *.py +recursive-include test *.py diff --git a/README.md b/README.md index 87a11467..dd3b1549 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,24 @@ batch = device.run_batch(circuits, s3_folder, shots=100) print(batch.results()[0].measurement_counts) # The result of the first task in the batch ``` +### Running a hybrid job + +```python +from braket.aws import AwsQuantumJob + +job = AwsQuantumJob.create( + device="arn:aws:braket:::device/quantum-simulator/amazon/sv1", + source_module="job.py", + entry_point="job:run_job", + wait_until_complete=True, +) +print(job.result()) +``` +where `run_job` is a function in the file `job.py`. + + +The code sample imports the Amazon Braket framework, then creates a hybrid job with the entry point being the `run_job` function. The hybrid job creates quantum tasks against the SV1 AWS Simulator. The job runs synchronously, and prints logs until it completes. The complete example can be found in `../examples/job.py`. + ### Available Simulators Amazon Braket provides access to two types of simulators: fully managed simulators, available through the Amazon Braket service, and the local simulators that are part of the Amazon Braket SDK. @@ -202,6 +220,7 @@ After you create a profile, use the following command to set the `AWS_PROFILE` s ```bash export AWS_PROFILE=YOUR_PROFILE_NAME ``` +To run the integration tests for local jobs, you need to have Docker installed and running. To install Docker follow these instructions: [Install Docker](https://docs.docker.com/get-docker/) Run the tests: diff --git a/doc/examples-hybrid-jobs.rst b/doc/examples-hybrid-jobs.rst new file mode 100644 index 00000000..c1b6c6a9 --- /dev/null +++ b/doc/examples-hybrid-jobs.rst @@ -0,0 +1,33 @@ +################################ +Amazon Braket Hybrid Jobs +################################ + +Learn more about hybrid jobs on Amazon Braket. + +.. toctree:: + :maxdepth: 2 + +************************** +`Getting Started `_ +************************** + +This tutorial shows how to run your first Amazon Braket Hybrid Job. + +************************** +`Hyperparameter Tuning `_ +************************** + +This notebook demonstrates a typical quantum machine learning workflow, including uploading data, monitoring training, and tuning hyperparameters. + +************************** +`Using Pennylane with Braket Jobs `_ +************************** + +In this tutorial, we use PennyLane within Amazon Braket Hybrid Jobs to run the Quantum Approximate Optimization Algorithm (QAOA) on a Max-Cut problem. + +************************** +`Bring your own container `_ +************************** + +Amazon Braket has pre-configured containers for executing Amazon Braket Hybrid Jobs, which are sufficient for many use cases involving the Braket SDK and PennyLane. +However, if we want to use custom packages outside the scope of pre-configured containers, we have the ability to supply a custom-built container. In this tutorial, we show how to use Braket Hybrid Jobs to train a quantum machine learning model using BYOC (Bring Your Own Container). diff --git a/doc/examples.rst b/doc/examples.rst index e0126c2f..cc65a5a8 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -14,5 +14,6 @@ https://github.com/aws/amazon-braket-examples. examples-hybrid-quantum.rst examples-ml-pennylane.rst examples-quantum-annealing-dwave.rst + examples-hybrid-jobs.rst \ No newline at end of file diff --git a/examples/bell.py b/examples/bell.py index dfa81e52..7a2c4333 100644 --- a/examples/bell.py +++ b/examples/bell.py @@ -16,10 +16,7 @@ device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") -# Use the S3 bucket you created during onboarding -s3_folder = ("amazon-braket-Your-Bucket-Name", "folder-name") - # https://wikipedia.org/wiki/Bell_state bell = Circuit().h(0).cnot(0, 1) -task = device.run(bell, s3_folder, shots=100) +task = device.run(bell, shots=100) print(task.result().measurement_counts) diff --git a/examples/debug_bell.py b/examples/debug_bell.py index f1ff33fc..cd492e45 100644 --- a/examples/debug_bell.py +++ b/examples/debug_bell.py @@ -23,15 +23,11 @@ device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") -# Use the S3 bucket you created during onboarding -s3_folder = ("amazon-braket-Your-Bucket-Name", "folder-name") - bell = Circuit().h(0).cnot(0, 1) # pass in logger to device.run, enabling debugging logs to print to console logger.info( device.run( bell, - s3_folder, shots=100, poll_timeout_seconds=120, poll_interval_seconds=0.25, diff --git a/examples/job.py b/examples/job.py new file mode 100644 index 00000000..ab9cf8e8 --- /dev/null +++ b/examples/job.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. + +import os + +from braket.aws import AwsDevice, AwsQuantumJob +from braket.circuits import Circuit +from braket.jobs import save_job_result + + +def run_job(): + device = AwsDevice(os.environ.get("AMZN_BRAKET_DEVICE_ARN")) + + bell = Circuit().h(0).cnot(0, 1) + num_tasks = 10 + results = [] + + for i in range(num_tasks): + task = device.run(bell, shots=100) + result = task.result().measurement_counts + results.append(result) + print(f"iter {i}: {result}") + + save_job_result({"results": results}) + + +if __name__ == "__main__": + job = AwsQuantumJob.create( + device="arn:aws:braket:::device/quantum-simulator/amazon/sv1", + source_module="job.py", + entry_point="job:run_job", + wait_until_complete=True, + ) + print(job.result()) diff --git a/setup.py b/setup.py index cd82e27e..c7420189 100644 --- a/setup.py +++ b/setup.py @@ -39,6 +39,7 @@ extras_require={ "test": [ "black", + "botocore", "flake8", "isort", "jsonschema==3.2.0", @@ -54,6 +55,7 @@ "tox", ] }, + include_package_data=True, url="https://github.com/aws/amazon-braket-sdk-python", author="Amazon Web Services", description=( diff --git a/src/braket/aws/__init__.py b/src/braket/aws/__init__.py index 08217d9a..d0b3a341 100644 --- a/src/braket/aws/__init__.py +++ b/src/braket/aws/__init__.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. from braket.aws.aws_device import AwsDevice, AwsDeviceType # noqa: F401 +from braket.aws.aws_quantum_job import AwsQuantumJob # noqa: F401 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 diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 0e694870..59d46179 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -13,11 +13,11 @@ from __future__ import annotations +import os from enum import Enum from typing import List, Optional, Union -import boto3 -from botocore.config import Config +from botocore.errorfactory import ClientError from networkx import Graph, complete_graph, from_edgelist from braket.annealing.problem import Problem @@ -79,7 +79,7 @@ def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): def run( self, task_specification: Union[Circuit, Problem], - s3_destination_folder: AwsSession.S3DestinationFolder, + s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, shots: Optional[int] = None, poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, @@ -93,7 +93,10 @@ def run( Args: task_specification (Union[Circuit, Problem]): Specification of task (circuit or annealing problem) to run on device. - s3_destination_folder: The S3 location to save the task's results to. + s3_destination_folder (AwsSession.S3DestinationFolder, optional): The S3 location to + save the task's results to. Default is `/tasks` if evoked + outside of a Braket Job, `/jobs//tasks` if evoked inside of + a Braket Job. shots (int, optional): The number of times to run the circuit or annealing problem. Default is 1000 for QPUs and 0 for simulators. poll_timeout_seconds (float): The polling timeout for `AwsQuantumTask.result()`, @@ -141,7 +144,13 @@ def run( self._aws_session, self._arn, task_specification, - s3_destination_folder, + s3_destination_folder + or ( + AwsSession.parse_s3_uri(os.environ.get("AMZN_BRAKET_TASK_RESULTS_S3_URI")) + if "AMZN_BRAKET_TASK_RESULTS_S3_URI" in os.environ + else None + ) + or (self._aws_session.default_bucket(), "tasks"), shots if shots is not None else self._default_shots, poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds, @@ -152,7 +161,7 @@ def run( def run_batch( self, task_specifications: List[Union[Circuit, Problem]], - s3_destination_folder: AwsSession.S3DestinationFolder, + s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, shots: Optional[int] = None, max_parallel: Optional[int] = None, max_connections: int = AwsQuantumTaskBatch.MAX_CONNECTIONS_DEFAULT, @@ -166,7 +175,10 @@ def run_batch( Args: task_specifications (List[Union[Circuit, Problem]]): List of circuits or annealing problems to run on device. - s3_destination_folder: The S3 location to save the tasks' results to. + s3_destination_folder (AwsSession.S3DestinationFolder, optional): The S3 location to + save the tasks' results to. Default is `/tasks` if evoked + outside of a Braket Job, `/jobs//tasks` if evoked inside of + a Braket Job. shots (int, optional): The number of times to run the circuit or annealing problem. Default is 1000 for QPUs and 0 for simulators. max_parallel (int, optional): The maximum number of tasks to run on AWS in parallel. @@ -190,10 +202,16 @@ def run_batch( `braket.aws.aws_quantum_task_batch.AwsQuantumTaskBatch` """ return AwsQuantumTaskBatch( - AwsDevice._copy_aws_session(self._aws_session, max_connections=max_connections), + AwsSession.copy_session(self._aws_session, max_connections=max_connections), self._arn, task_specifications, - s3_destination_folder, + s3_destination_folder + or ( + AwsSession.parse_s3_uri(os.environ.get("AMZN_BRAKET_TASK_RESULTS_S3_URI")) + if "AMZN_BRAKET_TASK_RESULTS_S3_URI" in os.environ + else None + ) + or (self._aws_session.default_bucket(), "tasks"), 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, @@ -210,22 +228,26 @@ def refresh_metadata(self) -> None: self._populate_properties(self._aws_session) def _get_session_and_initialize(self, session): - current_region = session.boto_session.region_name + current_region = session.region try: self._populate_properties(session) return session - except Exception: - if "qpu" not in self._arn: - raise ValueError(f"Simulator {self._arn} not found in {current_region}") + 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: + raise e # Search remaining regions for QPU for region in frozenset(AwsDevice.REGIONS) - {current_region}: - region_session = AwsDevice._copy_aws_session(session, region) + region_session = AwsSession.copy_session(session, region) try: self._populate_properties(region_session) return region_session - except Exception: - pass - raise ValueError(f"QPU {self._arn} not found") + except ClientError as e: + if e.response["Error"]["Code"] != "ResourceNotFoundException": + raise e + raise ValueError(f"QPU '{self._arn}' not found") def _populate_properties(self, session): metadata = session.get_device(self._arn) @@ -315,27 +337,6 @@ def _default_shots(self): def _default_max_parallel(self): return AwsDevice.DEFAULT_MAX_PARALLEL - @staticmethod - def _copy_aws_session( - aws_session: AwsSession, - region: Optional[str] = None, - max_connections: Optional[int] = None, - ) -> AwsSession: - config = Config(max_pool_connections=max_connections) if max_connections else None - session_region = aws_session.boto_session.region_name - new_region = region or session_region - creds = aws_session.boto_session.get_credentials() - if creds.method == "explicit": - boto_session = boto3.Session( - aws_access_key_id=creds.access_key, - aws_secret_access_key=creds.secret_key, - aws_session_token=creds.token, - region_name=new_region, - ) - else: - boto_session = boto3.Session(region_name=new_region) - return AwsSession(boto_session=boto_session, config=config) - def __repr__(self): return "Device('name': {}, 'arn': {})".format(self.name, self.arn) @@ -397,7 +398,7 @@ def get_devices( session_for_region = ( aws_session if region == session_region - else AwsDevice._copy_aws_session(aws_session, region) + else AwsSession.copy_session(aws_session, region) ) # Simulators are only instantiated in the same region as the AWS session types_for_region = sorted( diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py new file mode 100644 index 00000000..48f9a698 --- /dev/null +++ b/src/braket/aws/aws_quantum_job.py @@ -0,0 +1,561 @@ +# 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 +import tarfile +import tempfile +import time +from enum import Enum +from logging import Logger, getLogger +from pathlib import Path +from typing import Any, Dict, List, Union + +import boto3 +from botocore.exceptions import ClientError + +from braket.aws import AwsDevice +from braket.aws.aws_session import AwsSession +from braket.jobs import logs +from braket.jobs.config import ( + CheckpointConfig, + InstanceConfig, + OutputDataConfig, + S3DataSourceConfig, + StoppingCondition, +) +from braket.jobs.metrics_data.cwl_insights_metrics_fetcher import CwlInsightsMetricsFetcher + +# TODO: Have added metric file in metrics folder, but have to decide on the name for keep +# for the files, since all those metrics are retrieved from the CW. +from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType +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 AwsQuantumJob(QuantumJob): + """Amazon Braket implementation of a quantum job.""" + + TERMINAL_STATES = {"COMPLETED", "FAILED", "CANCELLED"} + RESULTS_FILENAME = "results.json" + RESULTS_TAR_FILENAME = "model.tar.gz" + LOG_GROUP = "/aws/braket/jobs" + + class LogState(Enum): + TAILING = "tailing" + JOB_COMPLETE = "job_complete" + COMPLETE = "complete" + + @classmethod + def create( + cls, + device: str, + source_module: str, + entry_point: str = None, + image_uri: str = None, + job_name: str = None, + code_location: str = None, + role_arn: str = None, + wait_until_complete: bool = False, + hyperparameters: Dict[str, Any] = None, + input_data: Union[str, Dict, S3DataSourceConfig] = None, + instance_config: InstanceConfig = None, + stopping_condition: StoppingCondition = None, + output_data_config: OutputDataConfig = None, + copy_checkpoints_from_job: str = None, + checkpoint_config: CheckpointConfig = None, + aws_session: AwsSession = None, + tags: Dict[str, str] = None, + logger: Logger = getLogger(__name__), + ) -> AwsQuantumJob: + """Creates a job by invoking the Braket CreateJob API. + + Args: + device (str): ARN for the AWS device which is primarily + accessed for the execution of this job. + + source_module (str): Path (absolute, relative or an S3 URI) to a python module to be + tarred and uploaded. If `source_module` is an S3 URI, it must point to a + tar.gz file. Otherwise, source_module may be a file or directory. + + entry_point (str): A str that specifies the entry point of the job, relative to + the source module. The entry point must be in the format + `importable.module` or `importable.module:callable`. For example, + `source_module.submodule:start_here` indicates the `start_here` function + contained in `source_module.submodule`. If source_module is an S3 URI, + entry point must be given. Default: source_module's name + + image_uri (str): A str that specifies the ECR image to use for executing the job. + `image_uris.retrieve_image()` function may be used for retrieving the ECR image URIs + for the containers supported by Braket. Default = ``. + + job_name (str): A str that specifies the name with which the job is created. + Default: f'{image_uri_type}-{timestamp}'. + + code_location (str): The S3 prefix URI where custom code will be uploaded. + Default: f's3://{default_bucket_name}/jobs/{job_name}/script'. + + role_arn (str): A str providing the IAM role ARN used to execute the + script. Default: IAM role returned by AwsSession's `get_default_jobs_role()`. + + wait_until_complete (bool): `True` if we should wait until the job completes. + This would tail the job logs as it waits. Otherwise `False`. Default: `False`. + + hyperparameters (Dict[str, Any]): Hyperparameters accessible to the job. + The hyperparameters are made accessible as a Dict[str, str] to the job. + For convenience, this accepts other types for keys and values, but `str()` + is called to convert them before being passed on. Default: None. + + input_data (Union[str, S3DataSourceConfig, dict]): Information about the training + data. Dictionary maps channel names to local paths or S3 URIs. Contents found + at any local paths will be uploaded to S3 at + f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local + path, S3 URI, or S3DataSourceConfig is provided, it will be given a default + channel name "input". + Default: {}. + + instance_config (InstanceConfig): Configuration of the instances to be used + to execute the job. Default: InstanceConfig(instanceType='ml.m5.large', + instanceCount=1, volumeSizeInGB=30, volumeKmsKey=None). + + stopping_condition (StoppingCondition): The maximum length of time, in seconds, + and the maximum number of tasks that a job can run before being forcefully stopped. + Default: StoppingCondition(maxRuntimeInSeconds=5 * 24 * 60 * 60). + + output_data_config (OutputDataConfig): Specifies the location for the output of the job. + Default: OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data', + kmsKeyId=None). + + copy_checkpoints_from_job (str): A str that specifies the job ARN whose checkpoint you + want to use in the current job. Specifying this value will copy over the checkpoint + data from `use_checkpoints_from_job`'s checkpoint_config s3Uri to the current job's + checkpoint_config s3Uri, making it available at checkpoint_config.localPath during + the job execution. Default: None + + checkpoint_config (CheckpointConfig): Configuration that specifies the location where + checkpoint data is stored. + Default: CheckpointConfig(localPath='/opt/jobs/checkpoints', + s3Uri=f's3://{default_bucket_name}/jobs/{job_name}/checkpoints'). + + aws_session (AwsSession): AwsSession for connecting to AWS Services. + Default: AwsSession() + + tags (Dict[str, str]): Dict specifying the key-value pairs for tagging this job. + Default: {}. + + logger (Logger): Logger object with which to write logs, such as task statuses + while waiting for task to be in a terminal state. Default is `getLogger(__name__)` + + Returns: + AwsQuantumJob: Job tracking the execution on Amazon Braket. + + Raises: + ValueError: Raises ValueError if the parameters are not valid. + """ + aws_session = AwsQuantumJob._initialize_session(aws_session, device, logger) + + create_job_kwargs = prepare_quantum_job( + device=device, + source_module=source_module, + entry_point=entry_point, + image_uri=image_uri, + job_name=job_name, + code_location=code_location, + role_arn=role_arn, + hyperparameters=hyperparameters, + input_data=input_data, + instance_config=instance_config, + stopping_condition=stopping_condition, + output_data_config=output_data_config, + copy_checkpoints_from_job=copy_checkpoints_from_job, + checkpoint_config=checkpoint_config, + aws_session=aws_session, + tags=tags, + ) + + job_arn = aws_session.create_job(**create_job_kwargs) + job = AwsQuantumJob(job_arn, aws_session) + + if wait_until_complete: + print(f"Initializing Braket Job: {job_arn}") + job.logs(wait=True) + + return job + + def __init__(self, arn: str, aws_session: AwsSession = None): + """ + Args: + arn (str): The ARN of the job. + aws_session (AwsSession, optional): The `AwsSession` for connecting to AWS services. + Default is `None`, in which case an `AwsSession` object will be created with the + region of the job. + """ + self._arn: str = arn + if aws_session: + if not self._is_valid_aws_session_region_for_job_arn(aws_session, arn): + raise ValueError( + "The aws session region does not match the region for the supplied arn." + ) + self._aws_session = aws_session + else: + self._aws_session = AwsQuantumJob._default_session_for_job_arn(arn) + self._metadata = {} + + @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`. + """ + job_region = job_arn.split(":")[3] + return job_region == aws_session.braket_client.meta.region_name + + @staticmethod + def _default_session_for_job_arn(job_arn: str) -> AwsSession: + """Get an AwsSession for the Job ARN. The AWS session should be in the region of the job. + + Args: + job_arn (str): The ARN for the quantum job. + + Returns: + AwsSession: `AwsSession` object with default `boto_session` in job's region. + """ + job_region = job_arn.split(":")[3] + boto_session = boto3.Session(region_name=job_region) + return AwsSession(boto_session=boto_session) + + @property + def arn(self) -> str: + """str: The ARN (Amazon Resource Name) of the quantum job.""" + return self._arn + + @property + def name(self) -> str: + """str: The name of the quantum job.""" + return self._arn.partition("job/")[-1] + + def state(self, use_cached_value: bool = False) -> str: + """The state of the quantum job. + + Args: + use_cached_value (bool, optional): 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: The value of `status` in `metadata()`. This is the value of the `status` key + in the Amazon Braket `GetJob` operation. + + See Also: + `metadata()` + """ + return self.metadata(use_cached_value).get("status") + + def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: + """Display logs for a given job, optionally tailing them until job is complete. + + If the output is a tty or a Jupyter cell, it will be color-coded + based on which instance the log entry is from. + + Args: + wait (bool): `True` to keep looking for new log entries until the job completes; + otherwise `False`. Default: `False`. + + poll_interval_seconds (int): The interval of time, in seconds, between polling for + new log entries and job completion (default: 5). + + Raises: + exceptions.UnexpectedStatusException: If waiting and the training job fails. + """ + # The loop below implements a state machine that alternates between checking the job status + # and reading whatever is available in the logs at this point. Note, that if we were + # called with wait == False, we never check the job status. + # + # If wait == TRUE and job is not completed, the initial state is TAILING + # If wait == FALSE, the initial state is COMPLETE (doesn't matter if the job really is + # complete). + # + # The state table: + # + # STATE ACTIONS CONDITION NEW STATE + # ---------------- ---------------- ----------------- ---------------- + # TAILING Read logs, Pause, Get status Job complete JOB_COMPLETE + # Else TAILING + # JOB_COMPLETE Read logs, Pause Any COMPLETE + # COMPLETE Read logs, Exit N/A + # + # Notes: + # - The JOB_COMPLETE state forces us to do an extra pause and read any items that got to + # Cloudwatch after the job was marked complete. + + job_already_completed = self.state() in AwsQuantumJob.TERMINAL_STATES + log_state = ( + AwsQuantumJob.LogState.TAILING + if wait and not job_already_completed + else AwsQuantumJob.LogState.COMPLETE + ) + + 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 = 1 # currently only support a single instance + has_streams = False + color_wrap = logs.ColorWrap() + + while True: + time.sleep(poll_interval_seconds) + + has_streams = logs.flush_log_streams( + self._aws_session, + log_group, + stream_prefix, + stream_names, + positions, + instance_count, + has_streams, + color_wrap, + ) + + 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: + log_state = AwsQuantumJob.LogState.JOB_COMPLETE + + def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: + """Gets the job metadata defined in Amazon Braket. + + Args: + use_cached_value (bool, optional): 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]: Dict that specifies the job metadata defined in Amazon Braket. + """ + if not use_cached_value or not self._metadata: + self._metadata = self._aws_session.get_job(self._arn) + return self._metadata + + def metrics( + self, + metric_type: MetricType = MetricType.TIMESTAMP, + statistic: MetricStatistic = MetricStatistic.MAX, + ) -> 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. For example, the table: + timestamp energy + 0 0.1 + 1 0.2 + would be represented as: + { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } + values may be integers, floats, strings or None. + + Args: + metric_type (MetricType): The type of metrics to get. Default: MetricType.TIMESTAMP. + + statistic (MetricStatistic): The statistic to determine which metric value to use + when there is a conflict. Default: MetricStatistic.MAX. + + Returns: + Dict[str, List[Union[str, float, int]]] : The metrics data. + """ + 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) + + def cancel(self) -> str: + """Cancels the job. + + Returns: + str: Indicates the status of the job. + + Raises: + ClientError: If there are errors invoking the CancelJob API. + """ + cancellation_response = self._aws_session.cancel_job(self._arn) + return cancellation_response["cancellationStatus"] + + def result( + self, + poll_timeout_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_TIMEOUT, + poll_interval_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_INTERVAL, + ) -> Dict[str, Any]: + """Retrieves the job result persisted using save_job_result() function. + + Args: + poll_timeout_seconds (float): The polling timeout, in seconds, for `result()`. + Default: 10 days. + + poll_interval_seconds (float): The polling interval, in seconds, for `result()`. + Default: 5 seconds. + + + Returns: + Dict[str, Any]: Dict specifying the job results. + + Raises: + RuntimeError: if job is in a FAILED or CANCELLED state. + TimeoutError: if job execution exceeds the polling timeout period. + """ + + with tempfile.TemporaryDirectory() as temp_dir: + job_name = self.metadata(True)["jobName"] + + try: + self.download_result(temp_dir, poll_timeout_seconds, poll_interval_seconds) + except ClientError as e: + if e.response["Error"]["Code"] == "404": + return {} + else: + raise e + return AwsQuantumJob._read_and_deserialize_results(temp_dir, job_name) + + @staticmethod + def _read_and_deserialize_results(temp_dir, job_name): + try: + with open(f"{temp_dir}/{job_name}/{AwsQuantumJob.RESULTS_FILENAME}", "r") as f: + persisted_data = PersistedJobData.parse_raw(f.read()) + deserialized_data = deserialize_values( + persisted_data.dataDictionary, persisted_data.dataFormat + ) + return deserialized_data + except FileNotFoundError: + return {} + + def download_result( + self, + extract_to=None, + poll_timeout_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_TIMEOUT, + poll_interval_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_INTERVAL, + ) -> None: + """Downloads the results from the job output S3 bucket and extracts the tar.gz + bundle to the location specified by `extract_to`. If no location is specified, + the results are extracted to the current directory. + + Args: + extract_to (str): The directory to which the results are extracted. The results + are extracted to a folder titled with the job name within this directory. + Default= `Current working directory`. + + poll_timeout_seconds: (float): The polling timeout, in seconds, for `download_result()`. + Default: 10 days. + + poll_interval_seconds: (float): The polling interval, in seconds, for + `download_result()`.Default: 5 seconds. + + Raises: + RuntimeError: if job is in a FAILED or CANCELLED state. + TimeoutError: if job execution exceeds the polling timeout period. + """ + + extract_to = extract_to or Path.cwd() + + timeout_time = time.time() + poll_timeout_seconds + job_response = self.metadata(True) + + while time.time() < timeout_time: + job_response = self.metadata(True) + job_state = self.state() + + if job_state in AwsQuantumJob.TERMINAL_STATES: + output_s3_path = job_response["outputDataConfig"]["s3Path"] + output_s3_uri = f"{output_s3_path}/output/model.tar.gz" + 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) + + raise TimeoutError( + f"{job_response['jobName']}: Polling for job completion " + f"timed out after {poll_timeout_seconds} seconds." + ) + + def _attempt_results_download(self, output_bucket_uri, output_s3_path): + try: + self._aws_session.download_from_s3( + 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: + raise e + + @staticmethod + def _extract_tar_file(extract_path): + with tarfile.open(AwsQuantumJob.RESULTS_TAR_FILENAME, "r:gz") as tar: + tar.extractall(extract_path) + + def __repr__(self) -> str: + return f"AwsQuantumJob('arn':'{self.arn}')" + + def __eq__(self, other) -> bool: + if isinstance(other, AwsQuantumJob): + return self.arn == other.arn + return False + + def __hash__(self) -> int: + return hash(self.arn) + + @staticmethod + def _initialize_session(session_value, device, logger): + aws_session = session_value or AwsSession() + current_region = aws_session.region + + try: + 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 '{current_region}'") + else: + raise e + + return AwsQuantumJob._find_device_session(aws_session, device, current_region, logger) + + @staticmethod + def _find_device_session(aws_session, device, original_region, logger): + for region in frozenset(AwsDevice.REGIONS) - {original_region}: + device_session = aws_session.copy_session(region=region) + try: + device_session.get_device(device) + 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 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 2602ecf9..7b7293b2 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -391,7 +391,7 @@ def __repr__(self) -> str: def __eq__(self, other) -> bool: if isinstance(other, AwsQuantumTask): return self.id == other.id - return NotImplemented + return False def __hash__(self) -> int: return hash(self.id) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index ec154245..2d7cf3a2 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -11,11 +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 itertools +import os import os.path +import re +from pathlib import Path from typing import Any, Dict, List, NamedTuple, Optional import backoff import boto3 +from botocore.config import Config from botocore.exceptions import ClientError import braket._schemas as braket_schemas @@ -27,22 +34,82 @@ class AwsSession(object): S3DestinationFolder = NamedTuple("S3DestinationFolder", [("bucket", str), ("key", str)]) - def __init__(self, boto_session=None, braket_client=None, config=None): + def __init__(self, boto_session=None, braket_client=None, config=None, default_bucket=None): """ Args: boto_session: A boto3 session object. braket_client: A boto3 Braket client. config: A botocore Config object. """ + if ( + boto_session + and braket_client + and boto_session.region_name != braket_client.meta.region_name + ): + raise ValueError( + "Boto Session region and Braket Client region must match and currently " + f"they do not: Boto Session region is '{boto_session.region_name}', but " + f"Braket Client region is '{braket_client.meta.region_name}'." + ) - self.boto_session = boto_session or boto3.Session() self._config = config if braket_client: + self.boto_session = boto_session or boto3.Session( + region_name=braket_client.meta.region_name + ) self.braket_client = braket_client else: + self.boto_session = boto_session or boto3.Session() self.braket_client = self.boto_session.client("braket", config=self._config) + 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") + + self._iam = None + self._s3 = None + self._sts = None + self._logs = None + self._ecr = None + + @property + def region(self): + return self.boto_session.region_name + + @property + def account_id(self): + return self.sts_client.get_caller_identity()["Account"] + + @property + def iam_client(self): + if not self._iam: + self._iam = self.boto_session.client("iam", region_name=self.region) + return self._iam + + @property + def s3_client(self): + if not self._s3: + self._s3 = self.boto_session.client("s3", region_name=self.region) + return self._s3 + + @property + def sts_client(self): + if not self._sts: + self._sts = self.boto_session.client("sts", region_name=self.region) + return self._sts + + @property + def logs_client(self): + if not self._logs: + self._logs = self.boto_session.client("logs", region_name=self.region) + return self._logs + + @property + def ecr_client(self): + if not self._ecr: + self._ecr = self.boto_session.client("ecr", region_name=self.region) + return self._ecr def _update_user_agent(self): """ @@ -89,9 +156,26 @@ def create_quantum_task(self, **boto3_kwargs) -> str: Returns: str: The ARN of the quantum task. """ + # Add job token to request, if available. + job_token = os.getenv("AMZN_BRAKET_JOB_TOKEN") + if job_token: + boto3_kwargs.update({"jobToken": job_token}) response = self.braket_client.create_quantum_task(**boto3_kwargs) return response["quantumTaskArn"] + def create_job(self, **boto3_kwargs) -> str: + """ + Create a quantum job. + + Args: + **boto3_kwargs: Keyword arguments for the Amazon Braket `CreateJob` operation. + + Returns: + str: The ARN of the job. + """ + response = self.braket_client.create_job(**boto3_kwargs) + return response["jobArn"] + @staticmethod def _should_giveup(err): return not ( @@ -122,6 +206,60 @@ def get_quantum_task(self, arn: str) -> Dict[str, Any]: """ return self.braket_client.get_quantum_task(quantumTaskArn=arn) + def get_default_jobs_role(self) -> str: + """ + Returns the role ARN for the default jobs role created in the Amazon Braket Console. + It will pick the first role it finds with the `RoleName` prefix + `AmazonBraketJobsExecutionRole`. + + Returns: + (str): The ARN for the default IAM role for jobs execution created in the Amazon + Braket console. + + Raises: + RuntimeError: If no roles can be found with the prefix `AmazonBraketJobsExecutionRole`. + """ + roles_paginator = self.iam_client.get_paginator("list_roles") + for page in roles_paginator.paginate(): + for role in page.get("Roles", []): + if role["RoleName"].startswith("AmazonBraketJobsExecutionRole"): + return role["Arn"] + raise RuntimeError( + "No default jobs roles found. Please create a role using the " + "Amazon Braket console or supply a custom role." + ) + + @backoff.on_exception( + backoff.expo, + ClientError, + max_tries=3, + jitter=backoff.full_jitter, + giveup=_should_giveup.__func__, + ) + def get_job(self, arn: str) -> Dict[str, Any]: + """ + Gets the quantum job. + + Args: + arn (str): The ARN of the quantum job to get. + + Returns: + Dict[str, Any]: The response from the Amazon Braket `GetQuantumJob` operation. + """ + return self.braket_client.get_job(jobArn=arn) + + def cancel_job(self, arn: str) -> Dict[str, Any]: + """ + Cancel the quantum job. + + Args: + arn (str): The ARN of the quantum job to cancel. + + Returns: + Dict[str, Any]: The response from the Amazon Braket `CancelJob` operation. + """ + 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. @@ -137,6 +275,251 @@ def retrieve_s3_object_body(self, s3_bucket: str, s3_object_key: str) -> str: obj = s3.Object(s3_bucket, s3_object_key) return obj.get()["Body"].read().decode("utf-8") + def upload_to_s3(self, filename: str, s3_uri: str) -> None: + """ + Upload file to S3 + + Args: + filename (str): local file to be uploaded. + s3_uri (str): The S3 URI where the file will be uploaded. + + Returns: + None + """ + bucket, key = self.parse_s3_uri(s3_uri) + self.s3_client.upload_file(filename, bucket, key) + + def upload_local_data(self, local_prefix: str, s3_prefix: str): + """ + 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 + beginning with local_prefix will be uploaded. + s3_prefix (str): the corresponding S3 prefix that will replace the local prefix + when the data is uploaded. This will be an S3 URI and should include the bucket + (i.e. 's3://my-bucket/my/prefix-') + + For example, local_prefix = "input", s3_prefix = "s3://my-bucket/dir/input" will upload: + * 'input.csv' to 's3://my-bucket/dir/input.csv' + * 'input-2.csv' to 's3://my-bucket/dir/input-2.csv' + * 'input/data.txt' to 's3://my-bucket/dir/input/data.txt' + * 'input-dir/data.csv' to 's3://my-bucket/dir/input-dir/data.csv' + but will not upload: + * 'my-input.csv' + * 'my-dir/input.csv' + To match all files within the directory "input" and upload them into + "s3://my-bucket/input", provide local_prefix = "input/" and + s3_prefix = "s3://my-bucket/input/" + """ + # support absolute paths + if Path(local_prefix).is_absolute(): + base_dir = Path(Path(local_prefix).anchor) + relative_prefix = str(Path(local_prefix).relative_to(base_dir)) + else: + base_dir = Path() + relative_prefix = str(local_prefix) + for file in itertools.chain( + # files that match the prefix + base_dir.glob(f"{relative_prefix}*"), + # files inside of directories that match the prefix + base_dir.glob(f"{relative_prefix}*/**/*"), + ): + if file.is_file(): + s3_uri = str(file.as_posix()).replace(str(Path(local_prefix).as_posix()), s3_prefix) + self.upload_to_s3(str(file), s3_uri) + + def download_from_s3(self, s3_uri: str, filename: str) -> None: + """ + Download file from S3 + + Args: + s3_uri (str): The S3 uri from where the file will be downloaded. + filename (str): filename to save the file to. + + Returns: + None + """ + bucket, key = self.parse_s3_uri(s3_uri) + 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 + destination URIs are the same. + + Args: + source_s3_uri (str): S3 URI pointing to the object to be copied. + destination_s3_uri (str): S3 URI where the object will be copied to. + """ + if source_s3_uri == destination_s3_uri: + return + + source_bucket, source_key = self.parse_s3_uri(source_s3_uri) + destination_bucket, destination_key = self.parse_s3_uri(destination_s3_uri) + + self.s3_client.copy( + { + "Bucket": source_bucket, + "Key": source_key, + }, + destination_bucket, + destination_key, + ) + + 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 + 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. + + Args: + source_s3_path (str): S3 URI pointing to the directory to be copied. + destination_s3_path (str): S3 URI where the contents of the source_s3_path + directory will be copied to. + """ + if source_s3_path == destination_s3_path: + return + + source_bucket, source_prefix = AwsSession.parse_s3_uri(source_s3_path) + destination_bucket, destination_prefix = AwsSession.parse_s3_uri(destination_s3_path) + + source_keys = self.list_keys(source_bucket, source_prefix) + + for key in source_keys: + self.s3_client.copy( + { + "Bucket": source_bucket, + "Key": key, + }, + destination_bucket, + key.replace(source_prefix, destination_prefix, 1), + ) + + def list_keys(self, bucket: str, prefix: str) -> List[str]: + """ + Lists keys matching prefix in bucket. + + Args: + bucket (str): Bucket to be queried. + prefix (str): The S3 path prefix to be matched + + Returns: + List[str]: A list of all keys matching the prefix in + the bucket. + """ + list_objects = self.s3_client.list_objects_v2( + Bucket=bucket, + Prefix=prefix, + ) + keys = [obj["Key"] for obj in list_objects["Contents"]] + while list_objects["IsTruncated"]: + list_objects = self.s3_client.list_objects_v2( + Bucket=bucket, + Prefix=prefix, + ContinuationToken=list_objects["NextContinuationToken"], + ) + keys += [obj["Key"] for obj in list_objects["Contents"]] + return keys + + def default_bucket(self): + """ + 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 Job (if evoked inside of a Braket Job), or a default value of + "amazon-braket--. Except in the case of a user- + specified bucket name, this method will create the default bucket if it does not + exist. + + Returns: + str: Name of the default bucket. + """ + if self._default_bucket: + return self._default_bucket + default_bucket = f"amazon-braket-{self.region}-{self.account_id}" + + self._create_s3_bucket_if_it_does_not_exist(bucket_name=default_bucket, region=self.region) + + self._default_bucket = default_bucket + return self._default_bucket + + def _create_s3_bucket_if_it_does_not_exist(self, bucket_name, region): + """Creates an S3 Bucket if it does not exist. + Also swallows a few common exceptions that indicate that the bucket already exists or + that it is being created. + + Args: + bucket_name (str): Name of the S3 bucket to be created. + region (str): The region in which to create the bucket. + + Raises: + botocore.exceptions.ClientError: If S3 throws an unexpected exception during bucket + creation. + If the exception is due to the bucket already existing or + already being created, no exception is raised. + """ + try: + if region == "us-east-1": + # 'us-east-1' cannot be specified because it is the default region: + # https://github.com/boto/boto3/issues/125 + self.s3_client.create_bucket(Bucket=bucket_name) + else: + self.s3_client.create_bucket( + Bucket=bucket_name, CreateBucketConfiguration={"LocationConstraint": region} + ) + self.s3_client.put_public_access_block( + Bucket=bucket_name, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": True, + "IgnorePublicAcls": True, + "BlockPublicPolicy": True, + "RestrictPublicBuckets": True, + }, + ) + self.s3_client.put_bucket_policy( + Bucket=bucket_name, + Policy=f"""{{ + "Version": "2012-10-17", + "Statement": [ + {{ + "Effect": "Allow", + "Principal": {{ + "Service": [ + "braket.amazonaws.com" + ] + }}, + "Action": "s3:*", + "Resource": [ + "arn:aws:s3:::{bucket_name}", + "arn:aws:s3:::{bucket_name}/*" + ] + }} + ] + }}""", + ) + except ClientError as e: + error_code = e.response["Error"]["Code"] + message = e.response["Error"]["Message"] + + if error_code == "BucketAlreadyOwnedByYou": + pass + elif error_code == "BucketAlreadyExists": + raise ValueError( + f"Provided default bucket '{bucket_name}' already exists " + 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 + def get_device(self, arn: str) -> Dict[str, Any]: """ Calls the Amazon Braket `get_device` API to @@ -190,3 +573,159 @@ def search_devices( continue results.append(result) return results + + @staticmethod + def is_s3_uri(string: str): + try: + AwsSession.parse_s3_uri(string) + except ValueError: + return False + return True + + @staticmethod + def parse_s3_uri(s3_uri: str) -> (str, str): + """ + Parse S3 URI to get bucket and key + + Args: + s3_uri (str): S3 URI. + + Returns: + (str, str): Bucket and Key tuple. + + Raises: + ValueError: Raises a ValueError if the provided string is not + a valid S3 URI. + """ + 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 + ) + assert s3_uri_match + 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}") + + @staticmethod + def construct_s3_uri(bucket: str, *dirs: str): + """ + Args: + bucket (str): S3 URI. + *dirs (str): directories to be appended in the resulting S3 URI + + Returns: + str: S3 URI + + Raises: + ValueError: Raises a ValueError if the provided arguments are not + valid to generate an S3 URI + """ + if not dirs: + raise ValueError(f"Not a valid S3 location: s3://{bucket}") + return f"s3://{bucket}/{'/'.join(dirs)}" + + def describe_log_streams( + self, + log_group: str, + log_stream_prefix: str, + limit: int = None, + next_token: Optional[str] = None, + ) -> Dict[str, Any]: + """ + Describes CloudWatch log streams in a log group with a given prefix. + + Args: + log_group (str): Name of the log group. + log_stream_prefix (str): Prefix for log streams to include. + limit (int, optional): Limit for number of log streams returned. + default is 50. + next_token (optional, str): The token for the next set of items to return. + Would have been received in a previous call. + + Returns: + dict: Dicionary containing logStreams and nextToken + """ + log_stream_args = { + "logGroupName": log_group, + "logStreamNamePrefix": log_stream_prefix, + "orderBy": "LogStreamName", + } + + if limit: + log_stream_args.update({"limit": limit}) + + if next_token: + log_stream_args.update({"nextToken": next_token}) + + return self.logs_client.describe_log_streams(**log_stream_args) + + def get_log_events( + self, + log_group: str, + log_stream: str, + start_time: int, + start_from_head: bool = True, + next_token: Optional[str] = None, + ) -> Dict[str, Any]: + """ + Gets CloudWatch log events from a given log stream. + + Args: + log_group (str): Name of the log group. + log_stream (str): Name of the log stream. + start_time (int): Timestamp that indicates a start time to include log events. + start_from_head (bool): Bool indicating to return oldest events first. default + is True. + next_token (optional, str): The token for the next set of items to return. + Would have been received in a previous call. + + Returns: + dict: Dicionary containing events, nextForwardToken, and nextBackwardToken + """ + log_events_args = { + "logGroupName": log_group, + "logStreamName": log_stream, + "startTime": start_time, + "startFromHead": start_from_head, + } + + if next_token: + log_events_args.update({"nextToken": next_token}) + + return self.logs_client.get_log_events(**log_events_args) + + def copy_session( + self, + region: Optional[str] = None, + max_connections: Optional[int] = None, + ) -> AwsSession: + """ + Creates a new AwsSession based on the region. + + Args: + region (str): Name of the region. Default = `None`. + max_connections (int): The maximum number of connections in the + Boto3 connection pool. Default = `None`. + + Returns: + AwsSession: based on the region and boto config parameters. + """ + 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 + creds = self.boto_session.get_credentials() + default_bucket = self._default_bucket if self._custom_default_bucket else None + if creds.method == "explicit": + boto_session = boto3.Session( + aws_access_key_id=creds.access_key, + aws_secret_access_key=creds.secret_key, + aws_session_token=creds.token, + region_name=new_region, + ) + else: + boto_session = boto3.Session(region_name=new_region) + return AwsSession(boto_session=boto_session, config=config, default_bucket=default_bucket) diff --git a/src/braket/jobs/__init__.py b/src/braket/jobs/__init__.py new file mode 100644 index 00000000..e58ba16e --- /dev/null +++ b/src/braket/jobs/__init__.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.jobs.config import ( # noqa: F401 + CheckpointConfig, + InstanceConfig, + OutputDataConfig, + S3DataSourceConfig, + StoppingCondition, +) +from braket.jobs.data_persistence import ( # noqa: F401 + load_job_checkpoint, + save_job_checkpoint, + save_job_result, +) +from braket.jobs.image_uris import Framework, retrieve_image # noqa: F401 diff --git a/src/braket/jobs/config.py b/src/braket/jobs/config.py new file mode 100644 index 00000000..bbe1f2cb --- /dev/null +++ b/src/braket/jobs/config.py @@ -0,0 +1,81 @@ +# 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 dataclasses import dataclass +from typing import Optional + + +@dataclass +class CheckpointConfig: + """Configuration that specifies the location where checkpoint data is stored.""" + + localPath: str = "/opt/jobs/checkpoints" + s3Uri: Optional[str] = None + + +@dataclass +class InstanceConfig: + """Configuration of the instances used to execute the job.""" + + instanceType: str = "ml.m5.large" + volumeSizeInGb: int = 30 + + +@dataclass +class OutputDataConfig: + """Configuration that specifies the location for the output of the job.""" + + s3Path: Optional[str] = None + kmsKeyId = None + + +@dataclass +class StoppingCondition: + """Conditions that specify when the job should be forcefully stopped.""" + + maxRuntimeInSeconds: int = 5 * 24 * 60 * 60 + + +@dataclass +class DeviceConfig: + device: str + + +class S3DataSourceConfig: + """ + Data source for data that lives on S3 + Attributes: + config (dict[str, dict]): config passed to the Braket API + """ + + def __init__( + self, + s3_data, + content_type=None, + ): + """Create a definition for input data used by a Braket job. + + Args: + s3_data (str): Defines the location of s3 data to train on. + content_type (str): MIME type of the input data (default: None). + """ + self.config = { + "dataSource": { + "s3DataSource": { + "s3Uri": s3_data, + } + } + } + + if content_type is not None: + self.config["contentType"] = content_type diff --git a/src/braket/jobs/data_persistence.py b/src/braket/jobs/data_persistence.py new file mode 100644 index 00000000..f969a598 --- /dev/null +++ b/src/braket/jobs/data_persistence.py @@ -0,0 +1,136 @@ +# 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 typing import Any, Dict + +from braket.jobs.serialization import deserialize_values, serialize_values +from braket.jobs_data import PersistedJobData, PersistedJobDataFormat + + +def save_job_checkpoint( + checkpoint_data: Dict[str, Any], + 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 + `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`. + + Note: This function for storing the checkpoints is only for use inside the job container + as it writes data to directories and references env variables set in the containers. + + + Args: + checkpoint_data (Dict[str, Any]): Dict that specifies the checkpoint data to be persisted. + checkpoint_file_suffix (str): str that specifies the file suffix to be used for + the checkpoint filename. The resulting filename + `f"{job_name}(_{checkpoint_file_suffix}).json"` is used to save the checkpoints. + Default: "" + data_format (PersistedJobDataFormat): 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: + ValueError: If the supplied `checkpoint_data` is `None` or empty. + """ + if not checkpoint_data: + raise ValueError("The checkpoint_data argument cannot be empty.") + checkpoint_directory = os.environ["AMZN_BRAKET_CHECKPOINT_DIR"] + job_name = os.environ["AMZN_BRAKET_JOB_NAME"] + checkpoint_file_path = ( + f"{checkpoint_directory}/{job_name}_{checkpoint_file_suffix}.json" + if checkpoint_file_suffix + else f"{checkpoint_directory}/{job_name}.json" + ) + with open(checkpoint_file_path, "w") 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()) + + +def load_job_checkpoint(job_name: str, checkpoint_file_suffix: str = "") -> Dict[str, Any]: + """ + 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. + + Note: This function for loading job checkpoints is only for use inside the job container + as it writes data to directories and references env variables set in the containers. + + + Args: + job_name (str): str that specifies the name of the job whose checkpoints + are to be loaded. + checkpoint_file_suffix (str): str specifying the file suffix that is used to + locate the checkpoint file to load. The resulting file name + `f"{job_name}(_{checkpoint_file_suffix}).json"` is used to locate the + checkpoint file. Default: "" + + Returns: + Dict[str, Any]: Dict that contains the checkpoint data persisted in the checkpoint file. + + Raises: + FileNotFoundError: If the file `f"{job_name}(_{checkpoint_file_suffix})"` could not be found + in the directory specified by the container environment variable `CHECKPOINT_DIR`. + ValueError: If the data stored in the checkpoint file can't be deserialized (possibly due to + corruption). + """ + checkpoint_directory = os.environ["AMZN_BRAKET_CHECKPOINT_DIR"] + checkpoint_file_path = ( + f"{checkpoint_directory}/{job_name}_{checkpoint_file_suffix}.json" + if checkpoint_file_suffix + else f"{checkpoint_directory}/{job_name}.json" + ) + with open(checkpoint_file_path, "r") as f: + persisted_data = PersistedJobData.parse_raw(f.read()) + deserialized_data = deserialize_values( + persisted_data.dataDictionary, persisted_data.dataFormat + ) + return deserialized_data + + +def save_job_result( + result_data: Dict[str, Any], + data_format: PersistedJobDataFormat = PersistedJobDataFormat.PLAINTEXT, +) -> None: + """ + Saves the `result_data` to the local output directory that is specified by the container + environment variable `OUTPUT_DIR`, with the filename 'results.json'. The `result_data` + values are serialized to the specified `data_format`. + + Note: This function for storing the results is only for use inside the job container + as it writes data to directories and references env variables set in the containers. + + + Args: + result_data (Dict[str, Any]): Dict that specifies the result data to be persisted. + data_format (PersistedJobDataFormat): 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: + ValueError: If the supplied `result_data` is `None` or empty. + """ + if not result_data: + raise ValueError("The result_data argument cannot be empty.") + result_directory = os.environ["AMZN_BRAKET_JOB_RESULTS_DIR"] + result_path = f"{result_directory}/results.json" + with open(result_path, "w") as f: + serialized_data = serialize_values(result_data or {}, data_format) + persisted_data = PersistedJobData(dataDictionary=serialized_data, dataFormat=data_format) + f.write(persisted_data.json()) diff --git a/src/braket/jobs/image_uri_config/base.json b/src/braket/jobs/image_uri_config/base.json new file mode 100644 index 00000000..b941f5dc --- /dev/null +++ b/src/braket/jobs/image_uri_config/base.json @@ -0,0 +1,12 @@ +{ + "versions": { + "1.0": { + "registries": { + "us-east-1": "292282985366", + "us-west-1": "292282985366", + "us-west-2": "292282985366" + }, + "repository": "amazon-braket-base-jobs" + } + } +} diff --git a/src/braket/jobs/image_uri_config/pl_pytorch.json b/src/braket/jobs/image_uri_config/pl_pytorch.json new file mode 100644 index 00000000..d5b0943f --- /dev/null +++ b/src/braket/jobs/image_uri_config/pl_pytorch.json @@ -0,0 +1,12 @@ +{ + "versions": { + "1.8.1": { + "registries": { + "us-east-1": "292282985366", + "us-west-1": "292282985366", + "us-west-2": "292282985366" + }, + "repository": "amazon-braket-pytorch-jobs" + } + } +} diff --git a/src/braket/jobs/image_uri_config/pl_tensorflow.json b/src/braket/jobs/image_uri_config/pl_tensorflow.json new file mode 100644 index 00000000..758b2107 --- /dev/null +++ b/src/braket/jobs/image_uri_config/pl_tensorflow.json @@ -0,0 +1,12 @@ +{ + "versions": { + "2.4.1": { + "registries": { + "us-east-1": "292282985366", + "us-west-1": "292282985366", + "us-west-2": "292282985366" + }, + "repository": "amazon-braket-tensorflow-jobs" + } + } +} diff --git a/src/braket/jobs/image_uris.py b/src/braket/jobs/image_uris.py new file mode 100644 index 00000000..0a9f27bb --- /dev/null +++ b/src/braket/jobs/image_uris.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 json +import os +from enum import Enum +from typing import Dict + + +class Framework(str, Enum): + """Supported Frameworks for pre-built containers""" + + BASE = "BASE" + PL_TENSORFLOW = "PL_TENSORFLOW" + PL_PYTORCH = "PL_PYTORCH" + + +def retrieve_image(framework: Framework, region: str): + """Retrieves the ECR URI for the Docker image matching the specified arguments. + + Args: + framework (str): The name of the framework. + region (str): The AWS region for the Docker image. + + Returns: + str: The ECR URI for the corresponding Amazon Braket Docker image. + + Raises: + ValueError: If any of the supplied values are invalid or the combination of inputs + specified is not supported. + """ + # Validate framework + framework = Framework(framework) + config = _config_for_framework(framework) + framework_version = max(version for version in config["versions"]) + version_config = config["versions"][framework_version] + registry = _registry_for_region(version_config, region) + tag = f"{version_config['repository']}:{framework_version}-cpu-py37-ubuntu18.04" + return f"{registry}.dkr.ecr.{region}.amazonaws.com/{tag}" + + +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. + """ + 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: + """Retrieves the registry for the specified region from the configuration. + + Args: + config (Dict[str, str]): Dict containing the framework configuration. + region (str): str that specifies the region for which the registry is retrieved. + + Returns: + str: str that specifies the registry for the supplied region. + + Raises: + ValueError: If the supplied region is invalid or not supported. + """ + registry_config = config["registries"] + if region not in registry_config: + raise ValueError( + f"Unsupported region: {region}. You may need to upgrade your SDK version for newer " + f"regions. Supported region(s): {list(registry_config.keys())}" + ) + return registry_config[region] diff --git a/src/braket/jobs/local/__init__.py b/src/braket/jobs/local/__init__.py new file mode 100644 index 00000000..6fe353f7 --- /dev/null +++ b/src/braket/jobs/local/__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.jobs.local.local_job import LocalQuantumJob # noqa: F401 diff --git a/src/braket/jobs/local/local_job.py b/src/braket/jobs/local/local_job.py new file mode 100644 index 00000000..8b739426 --- /dev/null +++ b/src/braket/jobs/local/local_job.py @@ -0,0 +1,258 @@ +# 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 time +from typing import Any, Dict, List, Union + +from braket.aws.aws_session import AwsSession +from braket.jobs.config import CheckpointConfig, OutputDataConfig, S3DataSourceConfig +from braket.jobs.image_uris import Framework, retrieve_image +from braket.jobs.local.local_job_container import _LocalJobContainer +from braket.jobs.local.local_job_container_setup import setup_container +from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType +from braket.jobs.metrics_data.log_metrics_parser import LogMetricsParser +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): + """Amazon Braket implementation of a quantum job that runs locally.""" + + @classmethod + def create( + cls, + device: str, + source_module: str, + entry_point: str = None, + image_uri: str = None, + job_name: str = None, + code_location: str = None, + role_arn: str = None, + hyperparameters: Dict[str, Any] = None, + input_data: Union[str, Dict, S3DataSourceConfig] = None, + output_data_config: OutputDataConfig = None, + checkpoint_config: CheckpointConfig = None, + aws_session: AwsSession = None, + ) -> LocalQuantumJob: + """Creates and runs job by setting up and running the customer script in a local + docker container. + + Args: + device (str): ARN for the AWS device which is primarily + accessed for the execution of this job. + + source_module (str): Path (absolute, relative or an S3 URI) to a python module to be + tarred and uploaded. If `source_module` is an S3 URI, it must point to a + tar.gz file. Otherwise, source_module may be a file or directory. + + entry_point (str): A str that specifies the entry point of the job, relative to + the source module. The entry point must be in the format + `importable.module` or `importable.module:callable`. For example, + `source_module.submodule:start_here` indicates the `start_here` function + contained in `source_module.submodule`. If source_module is an S3 URI, + entry point must be given. Default: source_module's name + + image_uri (str): A str that specifies the ECR image to use for executing the job. + `image_uris.retrieve_image()` function may be used for retrieving the ECR image URIs + for the containers supported by Braket. Default = ``. + + job_name (str): A str that specifies the name with which the job is created. + Default: f'{image_uri_type}-{timestamp}'. + + code_location (str): The S3 prefix URI where custom code will be uploaded. + Default: f's3://{default_bucket_name}/jobs/{job_name}/script'. + + role_arn (str): This field is currently not used for local jobs. Local jobs will use + the current role's credentials. This may be subject to change. + + hyperparameters (Dict[str, Any]): Hyperparameters accessible to the job. + The hyperparameters are made accessible as a Dict[str, str] to the job. + For convenience, this accepts other types for keys and values, but `str()` + is called to convert them before being passed on. Default: None. + + input_data (Union[str, S3DataSourceConfig, dict]): Information about the training + data. Dictionary maps channel names to local paths or S3 URIs. Contents found + at any local paths will be uploaded to S3 at + f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local + path, S3 URI, or S3DataSourceConfig is provided, it will be given a default + channel name "input". + Default: {}. + + output_data_config (OutputDataConfig): Specifies the location for the output of the job. + Default: OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data', + kmsKeyId=None). + + checkpoint_config (CheckpointConfig): Configuration that specifies the location where + checkpoint data is stored. + Default: CheckpointConfig(localPath='/opt/jobs/checkpoints', + s3Uri=f's3://{default_bucket_name}/jobs/{job_name}/checkpoints'). + + aws_session (AwsSession): AwsSession for connecting to AWS Services. + Default: AwsSession() + + Returns: + LocalQuantumJob: The representation of a local Braket Job. + """ + create_job_kwargs = prepare_quantum_job( + device=device, + source_module=source_module, + entry_point=entry_point, + image_uri=image_uri, + job_name=job_name, + code_location=code_location, + role_arn=role_arn, + hyperparameters=hyperparameters, + input_data=input_data, + output_data_config=output_data_config, + checkpoint_config=checkpoint_config, + aws_session=aws_session, + ) + + job_name = create_job_kwargs["jobName"] + if os.path.isdir(job_name): + raise ValueError( + f"A local directory called {job_name} already exists. " + f"Please use a different job name." + ) + + session = aws_session or AwsSession() + algorithm_specification = create_job_kwargs["algorithmSpecification"] + if "containerImage" in algorithm_specification: + image_uri = algorithm_specification["containerImage"]["uri"] + else: + image_uri = retrieve_image(Framework.BASE, session.region) + + with _LocalJobContainer(image_uri) as container: + 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: + log_file.write(container.run_log) + if "checkpointConfig" in create_job_kwargs: + checkpoint_config = create_job_kwargs["checkpointConfig"] + if "localPath" in checkpoint_config: + checkpoint_path = checkpoint_config["localPath"] + container.copy_from(checkpoint_path, os.path.join(job_name, "checkpoints")) + run_log = container.run_log + return LocalQuantumJob(f"local:job/{job_name}", run_log) + + def __init__(self, arn: str, run_log: str = None): + """ + Args: + arn (str): The ARN of the job. + run_log (str, Optional): The container output log of running the job with the given arn. + """ + if not arn.startswith("local:job/"): + raise ValueError(f"Arn {arn} is not a valid local job arn") + self._arn = arn + self._run_log = run_log + self._name = arn.partition("job/")[-1] + if not run_log and not os.path.isdir(self.name): + raise ValueError(f"Unable to find local job results for {self.name}") + + @property + def arn(self) -> str: + """str: The ARN (Amazon Resource Name) of the quantum job.""" + return self._arn + + @property + def name(self) -> str: + """str: The name of the quantum job.""" + return self._name + + @property + def run_log(self) -> str: + """str: The container output log from running the job.""" + if not self._run_log: + try: + with open(os.path.join(self.name, "log.txt"), "r") 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}.") + return self._run_log + + def state(self, use_cached_value: bool = False) -> str: + """The state of the quantum job.""" + return "COMPLETED" + + def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: + """When running the quantum job in local mode, the metadata is not available.""" + pass + + def cancel(self) -> str: + """When running the quantum job in local mode, the cancelling a running is not possible.""" + pass + + def download_result( + self, + extract_to=None, + poll_timeout_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_TIMEOUT, + poll_interval_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_INTERVAL, + ) -> None: + """When running the quantum job in local mode, results are automatically stored locally.""" + pass + + def result( + self, + poll_timeout_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_TIMEOUT, + poll_interval_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_INTERVAL, + ) -> Dict[str, Any]: + """Retrieves the job result persisted using save_job_result() function.""" + try: + with open(os.path.join(self.name, "results.json"), "r") as f: + persisted_data = PersistedJobData.parse_raw(f.read()) + deserialized_data = deserialize_values( + 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}.") + + def metrics( + self, + metric_type: MetricType = MetricType.TIMESTAMP, + statistic: MetricStatistic = MetricStatistic.MAX, + ) -> 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. For example, the table: + timestamp energy + 0 0.1 + 1 0.2 + would be represented as: + { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } + values may be integers, floats, strings or None. + + Args: + metric_type (MetricType): The type of metrics to get. Default: MetricType.TIMESTAMP. + + statistic (MetricStatistic): The statistic to determine which metric value to use + when there is a conflict. Default: MetricStatistic.MAX. + + Returns: + Dict[str, List[Union[str, float, int]]] : The metrics data. + """ + parser = LogMetricsParser() + current_time = str(time.time()) + for line in self.run_log.splitlines(): + if line.startswith("Metrics -"): + parser.parse_log_message(current_time, line) + return parser.get_parsed_metrics(metric_type, statistic) + + def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: + """Display container logs for a given job""" + return print(self.run_log) diff --git a/src/braket/jobs/local/local_job_container.py b/src/braket/jobs/local/local_job_container.py new file mode 100644 index 00000000..fc76e8d2 --- /dev/null +++ b/src/braket/jobs/local/local_job_container.py @@ -0,0 +1,245 @@ +# 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 base64 +import re +import subprocess +from logging import Logger, getLogger +from pathlib import Path +from typing import Dict, List + +from braket.aws.aws_session import AwsSession + + +class _LocalJobContainer(object): + """Uses docker CLI to run Braket Jobs on a local docker container.""" + + ECR_URI_PATTERN = r"^((\d+)\.dkr\.ecr\.([^.]+)\.[^/]*)/([^:]*):(.*)$" + CONTAINER_CODE_PATH = "/opt/ml/code/" + + def __init__( + self, image_uri: str, aws_session: AwsSession = None, logger: Logger = getLogger(__name__) + ): + """Represents and provides functions for interacting with a Braket Jobs docker 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, Optional): AwsSession for connecting to AWS Services. + Default: AwsSession() + logger (Logger): Logger object with which to write logs. + Default: `getLogger(__name__)` + """ + self._aws_session = aws_session or AwsSession() + self.image_uri = image_uri + self.run_log = None + self._container_name = None + self._logger = logger + + def __enter__(self): + """Creates and starts the local docker container.""" + self._container_name = self._start_container(self.image_uri) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Stops and removes the local docker container.""" + self._end_session() + + @staticmethod + 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 + their values. + Returns: + List[str]: The list of parameters to use when running a job that will include the + 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]}") + return env_list + + @staticmethod + 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. + + Returns: + (str): The UTF-8 encoded output of running the command. + """ + output = subprocess.check_output(command) + return output.decode("utf-8").strip() + + def _login_to_ecr(self, account_id: str, ecr_url: str) -> None: + """Logs in docker to an ECR repository using the client AWS credentials. + + Args: + account_id(str): The customer account ID. + ecr_url(str): The URL of the ECR repo to log into. + """ + ecr_client = self._aws_session.ecr_client + authorization_data_result = ecr_client.get_authorization_token(registryIds=[account_id]) + if not authorization_data_result: + raise ValueError( + "Unable to get permissions to access to log in to docker. " + "Please pull down the container before proceeding." + ) + authorization_data = authorization_data_result["authorizationData"][0] + raw_token = base64.b64decode(authorization_data["authorizationToken"]) + token = raw_token.decode("utf-8").strip("AWS:") + subprocess.run(["docker", "login", "-u", "AWS", "-p", token, ecr_url]) + + def _pull_image(self, image_uri: str) -> None: + """Pulls an image from ECR. + + Args: + image_uri(str): The URI of the ECR image to pull. + """ + ecr_pattern = re.compile(self.ECR_URI_PATTERN) + ecr_pattern_match = ecr_pattern.match(image_uri) + if not ecr_pattern_match: + raise ValueError( + f"The URL {image_uri} is not available locally and does not seem to " + f"be a valid AWS ECR URL." + "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) + 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]) + + def _start_container(self, image_uri: str) -> str: + """Runs a docker container in a busy loop so that it will accept further commands. The + call to this function must be matched with end_session to stop the container. + + Args: + image_uri(str): The URI of the ECR image to run. + + Returns: + (str): The name of the running container, which can be used to execute further commands. + """ + image_name = self._check_output_formatted(["docker", "images", "-q", image_uri]) + if not image_name: + self._pull_image(image_uri) + image_name = self._check_output_formatted(["docker", "images", "-q", image_uri]) + if not image_name: + raise ValueError( + f"The URL {image_uri} is not available locally and can not be pulled from ECR." + " Please pull down the container before proceeding." + ) + 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. + + Args: + dir_path(str): The directory path to create. + + Raises: + subprocess.CalledProcessError: If unable to make the directory. + """ + try: + 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 + + def copy_to(self, source: str, destination: str) -> None: + """Copies a local file or directory to the container. + + Args: + source(str): The local file or directory to copy. + destination(str): The path to the file or directory where the source should be copied. + + Raises: + subprocess.CalledProcessError: If unable to copy. + """ + dirname = str(Path(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}"] + ) + except subprocess.CalledProcessError as e: + output = e.output.decode("utf-8").strip() + self._logger.error(output) + raise e + + def copy_from(self, source: str, destination: str) -> None: + """Copies a file or directory from the container locally. + + Args: + source(str): The container file or directory to copy. + destination(str): The path to the file or directory where the source should be copied. + + Raises: + subprocess.CalledProcessError: If unable to copy. + """ + try: + 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 + + def run_local_job(self, environment_variables: Dict[str, str]) -> None: + """Runs a Braket job in a local container. + + Args: + environment_variables (Dict[str, str]): The environment variables to make available + as part of running the job. + """ + 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. " + "The specified container is not setup to run Braket Jobs. " + "Please see setup instructions for creating your own containers." + ) + + 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) + + try: + self.run_log = self._check_output_formatted(command) + print(self.run_log) + except subprocess.CalledProcessError as e: + self.run_log = e.output.decode("utf-8").strip() + self._logger.error(self.run_log) + + def _end_session(self): + """Stops and removes the local container.""" + subprocess.run(["docker", "stop", self._container_name]) diff --git a/src/braket/jobs/local/local_job_container_setup.py b/src/braket/jobs/local/local_job_container_setup.py new file mode 100644 index 00000000..0f7eac02 --- /dev/null +++ b/src/braket/jobs/local/local_job_container_setup.py @@ -0,0 +1,274 @@ +# 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 tempfile +from logging import Logger, getLogger +from pathlib import Path +from typing import Any, Dict, Iterable + +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]: + """Sets up a container with prerequisites for running a Braket Job. The prerequisites are + based on the options the customer has chosen for the job. Similarly, any environment variables + that are needed during runtime will be returned by this function. + + Args: + container(_LocalJobContainer): The container that will run the braket job. + aws_session (AwsSession): AwsSession for connecting to AWS Services. + **creation_kwargs: Keyword arguments for the boto3 Amazon Braket `CreateJob` operation. + + Returns: + (Dict[str, str]): A dictionary of environment variables that reflect Braket Jobs options + requested by the customer. + """ + 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.update( + _get_env_script_mode_config(creation_kwargs["algorithmSpecification"]["scriptModeConfig"]) + ) + run_environment_variables.update(_get_env_additional_lib()) + run_environment_variables.update(_get_env_default_vars(aws_session, **creation_kwargs)) + if _copy_hyperparameters(container, **creation_kwargs): + run_environment_variables.update(_get_env_hyperparameters()) + if _copy_input_data_list(container, aws_session, **creation_kwargs): + run_environment_variables.update(_get_env_input_data()) + return run_environment_variables + + +def _create_expected_paths(container: _LocalJobContainer, **creation_kwargs) -> None: + """Creates the basic paths required for Braket Jobs to run. + + Args: + container(_LocalJobContainer): The container that will run the braket job. + **creation_kwargs: Keyword arguments for the boto3 Amazon Braket `CreateJob` operation. + """ + container.makedir("/opt/ml/model") + container.makedir(creation_kwargs["checkpointConfig"]["localPath"]) + + +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. + + Args: + aws_session (AwsSession): AwsSession for connecting to AWS Services. + 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 + to the running container. + """ + credentials = aws_session.boto_session.get_credentials() + if credentials.token is None: + logger.info("Using the long-lived AWS credentials found in session") + return { + "AWS_ACCESS_KEY_ID": str(credentials.access_key), + "AWS_SECRET_ACCESS_KEY": str(credentials.secret_key), + } + logger.warning( + "Using the short-lived AWS credentials found in session. They might expire while running." + ) + return { + "AWS_ACCESS_KEY_ID": str(credentials.access_key), + "AWS_SECRET_ACCESS_KEY": str(credentials.secret_key), + "AWS_SESSION_TOKEN": str(credentials.token), + } + + +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 + parameters for running a Braket Job. + + Returns: + (Dict[str, str]): The set of key/value pairs that should be added as environment variables + to the running container. + """ + result = { + "AMZN_BRAKET_SCRIPT_S3_URI": script_mode_config["s3Uri"], + "AMZN_BRAKET_SCRIPT_ENTRY_POINT": script_mode_config["entryPoint"], + } + if "compressionType" in script_mode_config: + result["AMZN_BRAKET_SCRIPT_COMPRESSION_TYPE"] = script_mode_config["compressionType"] + return result + + +def _get_env_additional_lib() -> Dict[str, str]: + """For preview, we have some libraries that are not available publicly (yet). The container + will install these libraries if we set this env variable. + + Returns: + (Dict[str, str]): The set of key/value pairs that should be added as environment variables + to the running container. + """ + return { + "AMZN_BRAKET_IMAGE_SETUP_SCRIPT": "s3://amazon-braket-external-assets-preview-us-west-2/" + "HybridJobsAccess/scripts/setup-container.sh", + } + + +def _get_env_default_vars(aws_session: AwsSession, **creation_kwargs) -> 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. + + Returns: + (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"] + bucket, location = AwsSession.parse_s3_uri(creation_kwargs["outputDataConfig"]["s3Path"]) + return { + "AWS_DEFAULT_REGION": aws_session.region, + "AMZN_BRAKET_JOB_NAME": job_name, + "AMZN_BRAKET_DEVICE_ARN": creation_kwargs["deviceConfig"]["device"], + "AMZN_BRAKET_JOB_RESULTS_DIR": "/opt/braket/model", + "AMZN_BRAKET_CHECKPOINT_DIR": creation_kwargs["checkpointConfig"]["localPath"], + "AMZN_BRAKET_OUT_S3_BUCKET": bucket, + "AMZN_BRAKET_TASK_RESULTS_S3_URI": f"s3://{bucket}/jobs/{job_name}/tasks", + "AMZN_BRAKET_JOB_RESULTS_S3_PATH": str(Path(location, job_name, "output").as_posix()), + } + + +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 job. + + Returns: + (Dict[str, str]): The set of key/value pairs that should be added as environment variables + to the running container. + """ + return { + "AMZN_BRAKET_HP_FILE": "/opt/braket/input/config/hyperparameters.json", + } + + +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 job. + + Returns: + (Dict[str, str]): The set of key/value pairs that should be added as environment variables + to the running container. + """ + return { + "AMZN_BRAKET_INPUT_DIR": "/opt/braket/input/data", + } + + +def _copy_hyperparameters(container: _LocalJobContainer, **creation_kwargs) -> 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: Keyword arguments for the boto3 Amazon Braket `CreateJob` operation. + + Returns: + (bool): True if any hyperparameters were copied to the container. + """ + if "hyperParameters" not in creation_kwargs: + return False + 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: + json.dump(hyperparameters, write_file) + container.copy_to(str(file_path), "/opt/ml/input/config/hyperparameters.json") + return True + + +def _download_input_data( + aws_session: AwsSession, + download_dir: str, + input_data: Dict[str, Any], +) -> None: + """Downloads input data for a 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 + running a Braket Job. + """ + # 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 + # directory with the same name as the channel. This behavior is the same + # whether or not s3 prefix ends with a "/". Moreover, if s3 prefix ends + # with a "/", this is certainly the behavior to expect, since it can only + # match a directory. + # If s3 prefix matches any files exactly, or matches as a prefix of any + # files or directories, then all files and directories matching s3 prefix + # will be copied into a directory with the same name as the channel. + channel_name = input_data["channelName"] + s3_uri_prefix = input_data["dataSource"]["s3DataSource"]["s3Uri"] + bucket, prefix = AwsSession.parse_s3_uri(s3_uri_prefix) + s3_keys = aws_session.list_keys(bucket, prefix) + top_level = prefix if _is_dir(prefix, s3_keys) else str(Path(prefix).parent) + 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}") + for s3_key in s3_keys: + relative_key = Path(s3_key).relative_to(top_level) + download_path = Path(download_dir, channel_name, relative_key) + if not s3_key.endswith("/"): + download_path.parent.mkdir(parents=True, exist_ok=True) + aws_session.download_from_s3( + AwsSession.construct_s3_uri(bucket, s3_key), str(download_path) + ) + found_item = True + if not found_item: + raise RuntimeError(f"No data found for channel '{channel_name}'") + + +def _is_dir(prefix: str, keys: Iterable[str]) -> bool: + """determine whether the prefix refers to a directory""" + if prefix.endswith("/"): + return True + return all(key.startswith(f"{prefix}/") for key in keys) + + +def _copy_input_data_list( + container: _LocalJobContainer, aws_session: AwsSession, **creation_kwargs +) -> bool: + """If the input data list is not empty, this function will download the input files and + store them in the container. + + Args: + container(_LocalJobContainer): The container to save input data to. + aws_session (AwsSession): AwsSession for connecting to AWS Services. + **creation_kwargs: Keyword arguments for the boto3 Amazon Braket `CreateJob` operation. + + Returns: + (bool): True if any input data was copied to the container. + """ + if "inputDataConfig" not in creation_kwargs: + return False + + input_data_list = creation_kwargs["inputDataConfig"] + with tempfile.TemporaryDirectory() as temp_dir: + for input_data in input_data_list: + _download_input_data(aws_session, temp_dir, input_data) + container.copy_to(temp_dir, "/opt/ml/input/data/") + return bool(input_data_list) diff --git a/src/braket/jobs/logs.py b/src/braket/jobs/logs.py new file mode 100644 index 00000000..7f5ff65d --- /dev/null +++ b/src/braket/jobs/logs.py @@ -0,0 +1,231 @@ +# 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 collections +import os +import sys + +############################################################################## +# +# Support for reading logs +# +############################################################################## +from typing import Dict, List + +from botocore.exceptions import ClientError + + +class ColorWrap(object): + """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] + + def __init__(self, force=False): + """Initialize the class. + + Args: + force (bool): If True, the render output is colorized wherever the + output is. Default: False. + """ + self.colorize = force or sys.stdout.isatty() or os.environ.get("JPY_PARENT_PID", None) + + def __call__(self, index, s): + """Prints the string, colorized or not, depending on the environment. + + Args: + index (int): The instance number. + s (str): The string to print. + """ + if self.colorize: + self._color_wrap(index, s) + else: + print(s) + + def _color_wrap(self, index, s): + """Prints the string in a color determined by the index. + + Args: + index (int): The instance number. + s (str): The string to print (color-wrapped). + """ + 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, log_group, streams, positions): + """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. + + Args: + aws_session (AwsSession): The AwsSession for interfacing with CloudWatch. + + log_group (str): The name of the log group. + + streams (list of str): A list of the log stream names. The the stream number is + the position of the stream in this list. + + positions: (list of Positions): A list of (timestamp, skip) pairs which represent + the last record read from each stream. + + Yields: + A tuple of (stream number, cloudwatch log event). + """ + event_iters = [ + log_stream(aws_session, log_group, s, positions[s].timestamp, positions[s].skip) + for s in streams + ] + events = [] + for s in event_iters: + try: + events.append(next(s)) + except StopIteration: + events.append(None) + + while any(events): + i = events.index(min(events, key=lambda x: x["timestamp"] if x else float("inf"))) + yield i, events[i] + try: + events[i] = next(event_iters[i]) + except StopIteration: + events[i] = None + + +def log_stream(aws_session, log_group, stream_name, start_time=0, skip=0): + """A generator for log items in a single stream. + This yields all the items that are available at the current moment. + + Args: + aws_session (AwsSession): The AwsSession for interfacing with CloudWatch. + + log_group (str): The name of the log group. + + stream_name (str): The name of the specific stream. + + start_time (int): The time stamp value to start reading the logs from. Default: 0. + + skip (int): The number of log entries to skip at the start. Default: 0 (This is for + when there are multiple entries at the same timestamp.) + + Yields: + 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 + while event_count > 0: + response = aws_session.get_log_events( + log_group, + stream_name, + start_time, + start_from_head=True, + next_token=next_token, + ) + next_token = response["nextForwardToken"] + events = response["events"] + event_count = len(events) + if event_count > skip: + events = events[skip:] + skip = 0 + else: + skip = skip - event_count + events = [] + for ev in events: + yield ev + + +def flush_log_streams( + aws_session, + log_group: str, + stream_prefix: str, + stream_names: List[str], + positions: Dict[str, Position], + stream_count: int, + has_streams: bool, + color_wrap: ColorWrap, +): + """Flushes log streams to stdout. + + Args: + 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 + 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 of Positions): 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. + has_streams (bool): Whether the function has already been called once all streams have + 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. + + Yields: + A tuple of (stream number, cloudwatch log event). + """ + if len(stream_names) < stream_count: + # Log streams are created whenever a container starts writing to stdout/err, + # so this list may be dynamic until we have a stream for every instance. + try: + streams = aws_session.describe_log_streams( + log_group, + stream_prefix, + limit=stream_count, + ) + # stream_names = [...] wouldn't modify the list by reference. + new_streams = [ + s["logStreamName"] + for s in streams["logStreams"] + 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] + ) + 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 + # errors thrown about that until logging begins. + err = e.response.get("Error", {}) + if err.get("Code") != "ResourceNotFoundException": + raise + + if len(stream_names) > 0: + if not has_streams: + print() + has_streams = True + for idx, event in multi_stream_iter(aws_session, log_group, stream_names, positions): + color_wrap(idx, event["message"]) + ts, count = positions[stream_names[idx]] + if event["timestamp"] == ts: + positions[stream_names[idx]] = Position(timestamp=ts, skip=count + 1) + else: + positions[stream_names[idx]] = Position(timestamp=event["timestamp"], skip=1) + else: + print(".", end="", flush=True) + return has_streams diff --git a/src/braket/jobs/metrics.py b/src/braket/jobs/metrics.py new file mode 100644 index 00000000..cd862628 --- /dev/null +++ b/src/braket/jobs/metrics.py @@ -0,0 +1,43 @@ +# 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 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, +) -> None: + """ + Records Braket Job metrics. + + Args: + metric_name (str) : The name 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. + + 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};"] + if iteration_number is not None: + metric_list.append(f" iteration_number={iteration_number};") + metric_line = "".join(metric_list) + print(metric_line) diff --git a/src/braket/jobs/metrics_data/__init__.py b/src/braket/jobs/metrics_data/__init__.py new file mode 100644 index 00000000..273b6800 --- /dev/null +++ b/src/braket/jobs/metrics_data/__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.jobs.metrics_data.cwl_metrics_fetcher import CwlMetricsFetcher # noqa: F401 +from braket.jobs.metrics_data.definitions import MetricPeriod, MetricStatistic # noqa: F401 +from braket.jobs.metrics_data.exceptions import MetricsRetrievalError # noqa: F401 +from braket.jobs.metrics_data.log_metrics_parser import LogMetricsParser # noqa: F401 diff --git a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py new file mode 100644 index 00000000..251d17de --- /dev/null +++ b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py @@ -0,0 +1,185 @@ +# 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 time +from logging import Logger, getLogger +from typing import Any, Dict, List, Optional, Union + +from braket.aws.aws_session import AwsSession +from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType +from braket.jobs.metrics_data.exceptions import MetricsRetrievalError +from braket.jobs.metrics_data.log_metrics_parser import LogMetricsParser + + +class CwlInsightsMetricsFetcher(object): + LOG_GROUP_NAME = "/aws/braket/jobs" + QUERY_DEFAULT_JOB_DURATION = 3 * 60 * 60 + + def __init__( + self, + aws_session: AwsSession, + poll_timeout_seconds: float = 10, + poll_interval_seconds: float = 1, + logger: Logger = getLogger(__name__), + ): + """ + Args: + aws_session (AwsSession): AwsSession to connect to AWS with. + poll_timeout_seconds (float): The polling timeout for retrieving the metrics, + in seconds. Default: 10 seconds. + poll_interval_seconds (float): The interval of time, in seconds, between polling + for results. Default: 1 second. + logger (Logger): Logger object with which to write logs, such as task statuses + while waiting for a task to be in a terminal state. Default is `getLogger(__name__)` + """ + self._poll_timeout_seconds = poll_timeout_seconds + self._poll_interval_seconds = poll_interval_seconds + self._logger = logger + 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]: + """ + 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. + + Returns: + 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. + + Args: + query_id (str): CloudWatch Insights query ID. + + Returns: + List[Any]: The results from CloudWatch insights 'GetQueryResults' operation. + """ + timeout_time = time.time() + self._poll_timeout_seconds + 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"]: + raise MetricsRetrievalError(f"Query {query_id} failed with status {query_status}.") + elif query_status == "Complete": + return response["results"] + else: + time.sleep(self._poll_interval_seconds) + 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 + to 'all_metrics' along with the timestamp for the entry. + + Args: + 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. + parser (LogMetricsParser) : The CWL metrics parser. + """ + message = self._get_element_from_log_line("@message", result_entry) + if message: + timestamp = self._get_element_from_log_line("@timestamp", result_entry) + 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. + + Args: + 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. + """ + parser = LogMetricsParser() + for result in results: + self._parse_log_line(result, parser) + return parser.get_parsed_metrics(metric_type, statistic) + + def get_metrics_for_job( + self, + job_name: str, + metric_type: MetricType = MetricType.TIMESTAMP, + statistic: MetricStatistic = MetricStatistic.MAX, + job_start_time: int = None, + job_end_time: int = None, + ) -> Dict[str, List[Union[str, float, int]]]: + """ + Synchronously retrieves all the algorithm metrics logged by a given Job. + + Args: + job_name (str): The name of the Job. The name must be exact to ensure only the relevant + metrics are retrieved. + metric_type (MetricType): The type of metrics to get. Default is MetricType.TIMESTAMP. + statistic (MetricStatistic): The statistic to determine which metric value to use + when there is a conflict. Default is MetricStatistic.MAX. + job_start_time (int): The time when the job started. + Default: 3 hours before job_end_time. + job_end_time (int): If the job is complete, this should be the time at which the + job finished. Default: current time. + + Returns: + 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. + For example, the table: + timestamp energy + 0 0.1 + 1 0.2 + would be represented as: + { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } + The values may be integers, floats, strings or None. + """ + 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 job name needs to be unique to prevent jobs with similar names from being conflated. + query = ( + f"fields @timestamp, @message " + f"| filter @logStream like /^{job_name}\\// " + f"| filter @message like /^Metrics - /" + ) + + response = self._logs_client.start_query( + logGroupName=self.LOG_GROUP_NAME, + startTime=query_start_time, + endTime=query_end_time, + queryString=query, + limit=10000, + ) + + query_id = response["queryId"] + + results = self._get_metrics_results_sync(query_id) + + return self._parse_log_query_results(results, metric_type, statistic) diff --git a/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py new file mode 100644 index 00000000..63237616 --- /dev/null +++ b/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py @@ -0,0 +1,164 @@ +# 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 time +from logging import Logger, getLogger +from typing import Dict, List, 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): + LOG_GROUP_NAME = "/aws/braket/jobs" + + def __init__( + self, + aws_session: AwsSession, + poll_timeout_seconds: float = 10, + logger: Logger = getLogger(__name__), + ): + """ + Args: + aws_session (AwsSession): AwsSession to connect to AWS with. + poll_timeout_seconds (float): The polling timeout for retrieving the metrics, + in seconds. Default: 10 seconds. + logger (Logger): Logger object with which to write logs, such as task statuses + while waiting for task to be in a terminal state. Default is `getLogger(__name__)` + """ + self._poll_timeout_seconds = poll_timeout_seconds + self._logger = logger + self._logs_client = aws_session.logs_client + + @staticmethod + def _is_metrics_message(message): + """ + Returns true if a given message is designated as containing Metrics. + + Args: + message (str): The message to check. + + Returns: + True if the given message is designated as containing Metrics; False otherwise. + """ + if message: + return "Metrics -" in message + return False + + def _parse_metrics_from_log_stream( + self, + stream_name: str, + timeout_time: float, + parser: LogMetricsParser, + ) -> None: + """ + Synchronously retrieves the algorithm metrics logged in a given job log stream. + + Args: + stream_name (str): The name of the log stream. + timeout_time (float) : We stop getting metrics if the current time is beyond + the timeout time. + parser (LogMetricsParser) : The CWL metrics parser. + + Returns: + None + """ + kwargs = { + "logGroupName": self.LOG_GROUP_NAME, + "logStreamName": stream_name, + "startFromHead": True, + "limit": 10000, + } + + previous_token = None + while time.time() < timeout_time: + response = self._logs_client.get_log_events(**kwargs) + for event in response.get("events"): + message = event.get("message") + if self._is_metrics_message(message): + parser.parse_log_message(event.get("timestamp"), message) + next_token = response.get("nextForwardToken") + if not next_token or next_token == previous_token: + return + previous_token = next_token + 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 job. + + Args: + job_name (str): The name of the 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 job. + """ + kwargs = { + "logGroupName": self.LOG_GROUP_NAME, + "logStreamNamePrefix": job_name + "/algo-", + } + log_streams = [] + while time.time() < timeout_time: + response = self._logs_client.describe_log_streams(**kwargs) + streams = response.get("logStreams") + if streams: + for stream in streams: + name = stream.get("logStreamName") + if name: + log_streams.append(name) + next_token = response.get("nextToken") + if not next_token: + return log_streams + kwargs["nextToken"] = next_token + self._logger.warning("Timed out waiting for all metrics. Data may be incomplete.") + return log_streams + + def get_metrics_for_job( + self, + 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 Job. + + Args: + job_name (str): The name of the Job. The name must be exact to ensure only the relevant + metrics are retrieved. + metric_type (MetricType): The type of metrics to get. Default is MetricType.TIMESTAMP. + statistic (MetricStatistic): The statistic to determine which metric value to use + when there is a conflict. Default is MetricStatistic.MAX. + + Returns: + 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. + For example, the table: + timestamp energy + 0 0.1 + 1 0.2 + would be represented as: + { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } + values may be integers, floats, strings or None. + """ + timeout_time = time.time() + self._poll_timeout_seconds + + parser = LogMetricsParser() + + log_streams = self._get_log_streams_for_job(job_name, timeout_time) + for log_stream in log_streams: + self._parse_metrics_from_log_stream(log_stream, timeout_time, parser) + + return parser.get_parsed_metrics(metric_type, statistic) diff --git a/src/braket/jobs/metrics_data/definitions.py b/src/braket/jobs/metrics_data/definitions.py new file mode 100644 index 00000000..a3e77a78 --- /dev/null +++ b/src/braket/jobs/metrics_data/definitions.py @@ -0,0 +1,36 @@ +# 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 enum import Enum, unique + + +@unique +class MetricPeriod(Enum): + """Period over which the cloudwatch metric is aggregated.""" + + ONE_MINUTE: int = 60 + + +@unique +class MetricStatistic(Enum): + """Metric data aggregation to use over the specified period.""" + + MIN: str = "Min" + MAX: str = "Max" + + +@unique +class MetricType(Enum): + """Metric type.""" + + TIMESTAMP: str = "Timestamp" + ITERATION_NUMBER: str = "IterationNumber" diff --git a/src/braket/jobs/metrics_data/exceptions.py b/src/braket/jobs/metrics_data/exceptions.py new file mode 100644 index 00000000..677a3a44 --- /dev/null +++ b/src/braket/jobs/metrics_data/exceptions.py @@ -0,0 +1,18 @@ +# 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. + + +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 new file mode 100644 index 00000000..76ef319b --- /dev/null +++ b/src/braket/jobs/metrics_data/log_metrics_parser.py @@ -0,0 +1,198 @@ +# 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 logging import Logger, getLogger +from typing import Dict, Iterator, List, Optional, Tuple, 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 + convenient format. + """ + + METRICS_DEFINITIONS = re.compile(r"(\w+)\s*=\s*([^;]+)\s*;") + TIMESTAMP = "timestamp" + ITERATION_NUMBER = "iteration_number" + + def __init__( + self, + logger: Logger = getLogger(__name__), + ): + self._logger = logger + self.all_metrics = [] + + @staticmethod + def _get_value( + current_value: Optional[Union[str, float, int]], + new_value: Union[str, float, int], + statistic: MetricStatistic, + ) -> Union[str, float, int]: + """ + Gets the value based on a statistic. + + Args: + current_value (Optional[Union[str, float, int]]): The current value. + + new_value: (Union[str, float, int]) The new value. + + statistic (MetricStatistic): The statistic to determine which value to use. + + Returns: + Union[str, float, int]: the value. + """ + if current_value is None: + return new_value + if statistic == MetricStatistic.MAX: + 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]]: + """ + 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 + is in the format { : }. This implies that multiple metrics + with the same name are deduped to the last instance of that metric. + """ + metrics = {} + for match in all_matches: + subgroup = match.groups() + value = subgroup[1] + try: + metrics[subgroup[0]] = float(value) + except ValueError: + self._logger.warning(f"Unable to convert value {value} to a float.") + 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 + on that line. The timestamp is also added to match the corresponding values. + + Args: + timestamp (str): A formatted string representing the timestamp for any found metrics. + + message (str): A log line from a log. + """ + if not message: + return + all_matches = self.METRICS_DEFINITIONS.finditer(message) + parsed_metrics = self._get_metrics_from_log_line_matches(all_matches) + if not parsed_metrics: + return + if timestamp and self.TIMESTAMP not in parsed_metrics: + parsed_metrics[self.TIMESTAMP] = timestamp + self.all_metrics.append(parsed_metrics) + + def get_columns_and_pivot_indices( + self, pivot: str + ) -> Tuple[Dict[str, List[Union[str, float, int]]], Dict[int, int]]: + """ + Parses the metrics to find all the metrics that have the pivot column. The values of + the pivot column are assigned a row index, so that all metrics with the same pivot value + 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[Any]], Dict[int, int]]: + The Dict[str, List[Any]] the result table with all the metrics values initialized + to None + The Dict[int, int] is the list of pivot indices, where the value of a pivot column + is mapped to a row index. + """ + row_count = 0 + pivot_indices: dict[int, int] = {} + table: dict[str, list[Optional[Union[str, float, int]]]] = {} + for metric in self.all_metrics: + if pivot in metric: + if metric[pivot] not in pivot_indices: + pivot_indices[metric[pivot]] = row_count + row_count += 1 + for column_name in metric: + table[column_name] = [None] + for column_name in table: + table[column_name] = [None] * row_count + return table, 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 + are not included in the results. Metrics that have the same value in the pivot column + are returned in the same row. If the a metric has multiple values for the pivot value, + the statistic is used to determine which value is returned. + For example, for the metrics: + "iteration_number" : 0, "metricA" : 2, "metricB" : 1, + "iteration_number" : 0, "metricA" : 1, + "no_pivot_column" : 0, "metricA" : 0, + "iteration_number" : 1, "metricA" : 2, + + The result with iteration_number as the pivot, statistic of MIN the result will be: + iteration_number metricA metricB + 0 1 1 + 1 2 None + + Args: + pivot (str): The name of the pivot column. Must be TIMESTAMP or ITERATION_NUMBER. + statistic (MetricStatistic): The statistic to determine which value to use. + + Returns: + 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: + if pivot in metric: + row = pivot_indices[metric[pivot]] + for column_name in metric: + table[column_name][row] = self._get_value( + table[column_name][row], metric[column_name], statistic + ) + return table + + 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 + containing the values in each row. For example, the table: + timestamp energy + 0 0.1 + 1 0.2 + would be represented as: + { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } + values may be integers, floats, strings or None. + + Args: + 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. + """ + if metric_type == MetricType.ITERATION_NUMBER: + return self.get_metric_data_with_pivot(self.ITERATION_NUMBER, statistic) + return self.get_metric_data_with_pivot(self.TIMESTAMP, statistic) diff --git a/src/braket/jobs/quantum_job.py b/src/braket/jobs/quantum_job.py new file mode 100644 index 00000000..d17fa637 --- /dev/null +++ b/src/braket/jobs/quantum_job.py @@ -0,0 +1,186 @@ +# 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 abc import ABC, abstractmethod +from typing import Any, Dict, List + +from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType + + +class QuantumJob(ABC): + DEFAULT_RESULTS_POLL_TIMEOUT = 864000 + DEFAULT_RESULTS_POLL_INTERVAL = 5 + + @property + @abstractmethod + def arn(self) -> str: + """str: The ARN (Amazon Resource Name) of the quantum job.""" + + @property + @abstractmethod + def name(self) -> str: + """str: The name of the quantum job.""" + + @abstractmethod + def state(self, use_cached_value: bool = False) -> str: + """The state of the quantum job. + + Args: + use_cached_value (bool, optional): 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: The value of `status` in `metadata()`. This is the value of the `status` key + in the Amazon Braket `GetJob` operation. + + See Also: + `metadata()` + """ + + @abstractmethod + def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: + """Display logs for a given job, optionally tailing them until job is complete. + + If the output is a tty or a Jupyter cell, it will be color-coded + based on which instance the log entry is from. + + Args: + wait (bool): `True` to keep looking for new log entries until the job completes; + otherwise `False`. Default: `False`. + + poll_interval_seconds (int): The interval of time, in seconds, between polling for + new log entries and job completion (default: 5). + + Raises: + RuntimeError: If waiting and the job fails. + """ + # The loop below implements a state machine that alternates between checking the job status + # and reading whatever is available in the logs at this point. Note, that if we were + # called with wait == False, we never check the job status. + # + # If wait == TRUE and job is not completed, the initial state is TAILING + # If wait == FALSE, the initial state is COMPLETE (doesn't matter if the job really is + # complete). + # + # The state table: + # + # STATE ACTIONS CONDITION NEW STATE + # ---------------- ---------------- ----------------- ---------------- + # TAILING Read logs, Pause, Get status Job complete JOB_COMPLETE + # Else TAILING + # JOB_COMPLETE Read logs, Pause Any COMPLETE + # COMPLETE Read logs, Exit N/A + # + # Notes: + # - The JOB_COMPLETE state forces us to do an extra pause and read any items that got to + # Cloudwatch after the job was marked complete. + + @abstractmethod + def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: + """Gets the job metadata defined in Amazon Braket. + + Args: + use_cached_value (bool, optional): 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]: Dict that specifies the job metadata defined in Amazon Braket. + """ + + @abstractmethod + def metrics( + self, + metric_type: MetricType = MetricType.TIMESTAMP, + statistic: MetricStatistic = MetricStatistic.MAX, + ) -> 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. For example, the table: + timestamp energy + 0 0.1 + 1 0.2 + would be represented as: + { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } + values may be integers, floats, strings or None. + + Args: + metric_type (MetricType): The type of metrics to get. Default: MetricType.TIMESTAMP. + + statistic (MetricStatistic): The statistic to determine which metric value to use + when there is a conflict. Default: MetricStatistic.MAX. + + Returns: + Dict[str, List[Union[str, float, int]]] : The metrics data. + """ + + @abstractmethod + def cancel(self) -> str: + """Cancels the job. + + Returns: + str: Indicates the status of the job. + + Raises: + ClientError: If there are errors invoking the CancelJob API. + """ + + @abstractmethod + def result( + self, + poll_timeout_seconds: float = DEFAULT_RESULTS_POLL_TIMEOUT, + poll_interval_seconds: float = DEFAULT_RESULTS_POLL_INTERVAL, + ) -> Dict[str, Any]: + """Retrieves the job result persisted using save_job_result() function. + + Args: + poll_timeout_seconds (float): The polling timeout, in seconds, for `result()`. + Default: 10 days. + + poll_interval_seconds (float): The polling interval, in seconds, for `result()`. + Default: 5 seconds. + + + Returns: + Dict[str, Any]: Dict specifying the job results. + + Raises: + RuntimeError: if job is in a FAILED or CANCELLED state. + TimeoutError: if job execution exceeds the polling timeout period. + """ + + @abstractmethod + def download_result( + self, + extract_to=None, + poll_timeout_seconds: float = DEFAULT_RESULTS_POLL_TIMEOUT, + poll_interval_seconds: float = DEFAULT_RESULTS_POLL_INTERVAL, + ) -> None: + """Downloads the results from the job output S3 bucket and extracts the tar.gz + bundle to the location specified by `extract_to`. If no location is specified, + the results are extracted to the current directory. + + Args: + extract_to (str): The directory to which the results are extracted. The results + are extracted to a folder titled with the job name within this directory. + Default= `Current working directory`. + + poll_timeout_seconds: (float): The polling timeout, in seconds, for `download_result()`. + Default: 10 days. + + poll_interval_seconds: (float): The polling interval, in seconds, for + `download_result()`.Default: 5 seconds. + + Raises: + RuntimeError: if job is in a FAILED or CANCELLED state. + TimeoutError: if job execution exceeds the polling timeout period. + """ diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py new file mode 100644 index 00000000..db159ecc --- /dev/null +++ b/src/braket/jobs/quantum_job_creation.py @@ -0,0 +1,409 @@ +# 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 importlib.util +import re +import sys +import tarfile +import tempfile +import time +from dataclasses import asdict +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple, Union + +from braket.aws.aws_session import AwsSession +from braket.jobs.config import ( + CheckpointConfig, + DeviceConfig, + InstanceConfig, + OutputDataConfig, + S3DataSourceConfig, + StoppingCondition, +) + + +def prepare_quantum_job( + device: str, + source_module: str, + entry_point: str = None, + image_uri: str = None, + job_name: str = None, + code_location: str = None, + role_arn: str = None, + hyperparameters: Dict[str, Any] = None, + input_data: Union[str, Dict, S3DataSourceConfig] = None, + instance_config: InstanceConfig = None, + stopping_condition: StoppingCondition = None, + output_data_config: OutputDataConfig = None, + copy_checkpoints_from_job: str = None, + checkpoint_config: CheckpointConfig = None, + aws_session: AwsSession = None, + tags: Dict[str, str] = None, +): + """Creates a job by invoking the Braket CreateJob API. + + Args: + device (str): ARN for the AWS device which is primarily + accessed for the execution of this job. + + source_module (str): Path (absolute, relative or an S3 URI) to a python module to be + tarred and uploaded. If `source_module` is an S3 URI, it must point to a + tar.gz file. Otherwise, source_module may be a file or directory. + + entry_point (str): A str that specifies the entry point of the job, relative to + the source module. The entry point must be in the format + `importable.module` or `importable.module:callable`. For example, + `source_module.submodule:start_here` indicates the `start_here` function + contained in `source_module.submodule`. If source_module is an S3 URI, + entry point must be given. Default: source_module's name + + image_uri (str): A str that specifies the ECR image to use for executing the job. + `image_uris.retrieve_image()` function may be used for retrieving the ECR image URIs + for the containers supported by Braket. Default = ``. + + job_name (str): A str that specifies the name with which the job is created. + Default: f'{image_uri_type}-{timestamp}'. + + code_location (str): The S3 prefix URI where custom code will be uploaded. + Default: f's3://{default_bucket_name}/jobs/{job_name}/script'. + + role_arn (str): A str providing the IAM role ARN used to execute the + script. Default: IAM role returned by AwsSession's `get_default_jobs_role()`. + + hyperparameters (Dict[str, Any]): Hyperparameters accessible to the job. + The hyperparameters are made accessible as a Dict[str, str] to the job. + For convenience, this accepts other types for keys and values, but `str()` + is called to convert them before being passed on. Default: None. + + input_data (Union[str, S3DataSourceConfig, dict]): Information about the training + data. Dictionary maps channel names to local paths or S3 URIs. Contents found + at any local paths will be uploaded to S3 at + f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local + path, S3 URI, or S3DataSourceConfig is provided, it will be given a default + channel name "input". + Default: {}. + + instance_config (InstanceConfig): Configuration of the instances to be used + to execute the job. Default: InstanceConfig(instanceType='ml.m5.large', + instanceCount=1, volumeSizeInGB=30, volumeKmsKey=None). + + stopping_condition (StoppingCondition): The maximum length of time, in seconds, + and the maximum number of tasks that a job can run before being forcefully stopped. + Default: StoppingCondition(maxRuntimeInSeconds=5 * 24 * 60 * 60). + + output_data_config (OutputDataConfig): Specifies the location for the output of the job. + Default: OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data', + kmsKeyId=None). + + copy_checkpoints_from_job (str): A str that specifies the job ARN whose checkpoint you + want to use in the current job. Specifying this value will copy over the checkpoint + data from `use_checkpoints_from_job`'s checkpoint_config s3Uri to the current job's + checkpoint_config s3Uri, making it available at checkpoint_config.localPath during + the job execution. Default: None + + checkpoint_config (CheckpointConfig): Configuration that specifies the location where + checkpoint data is stored. + Default: CheckpointConfig(localPath='/opt/jobs/checkpoints', + s3Uri=f's3://{default_bucket_name}/jobs/{job_name}/checkpoints'). + + aws_session (AwsSession): AwsSession for connecting to AWS Services. + Default: AwsSession() + + tags (Dict[str, str]): Dict specifying the key-value pairs for tagging this job. + Default: {}. + + Returns: + AwsQuantumJob: Job tracking the execution on Amazon Braket. + + Raises: + ValueError: Raises ValueError if the parameters are not valid. + """ + param_datatype_map = { + "instance_config": (instance_config, InstanceConfig), + "stopping_condition": (stopping_condition, StoppingCondition), + "output_data_config": (output_data_config, OutputDataConfig), + "checkpoint_config": (checkpoint_config, CheckpointConfig), + } + + _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) + role_arn = role_arn or aws_session.get_default_jobs_role() + hyperparameters = hyperparameters or {} + 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) + instance_config = instance_config or InstanceConfig() + stopping_condition = stopping_condition or StoppingCondition() + output_data_config = output_data_config or OutputDataConfig() + checkpoint_config = checkpoint_config or CheckpointConfig() + code_location = code_location or AwsSession.construct_s3_uri( + default_bucket, + "jobs", + job_name, + "script", + ) + if AwsSession.is_s3_uri(source_module): + _process_s3_source_module(source_module, entry_point, aws_session, code_location) + else: + # if entry point is None, it will be set to default here + entry_point = _process_local_source_module( + source_module, entry_point, aws_session, code_location + ) + algorithm_specification = { + "scriptModeConfig": { + "entryPoint": entry_point, + "s3Uri": f"{code_location}/source.tar.gz", + "compressionType": "GZIP", + } + } + if image_uri: + algorithm_specification["containerImage"] = {"uri": image_uri} + if not output_data_config.s3Path: + output_data_config.s3Path = AwsSession.construct_s3_uri( + default_bucket, + "jobs", + job_name, + "data", + ) + if not checkpoint_config.s3Uri: + checkpoint_config.s3Uri = AwsSession.construct_s3_uri( + default_bucket, + "jobs", + job_name, + "checkpoints", + ) + if copy_checkpoints_from_job: + checkpoints_to_copy = aws_session.get_job(copy_checkpoints_from_job)["checkpointConfig"][ + "s3Uri" + ] + aws_session.copy_s3_directory(checkpoints_to_copy, checkpoint_config.s3Uri) + + create_job_kwargs = { + "jobName": job_name, + "roleArn": role_arn, + "algorithmSpecification": algorithm_specification, + "inputDataConfig": input_data_list, + "instanceConfig": asdict(instance_config), + "outputDataConfig": asdict(output_data_config), + "checkpointConfig": asdict(checkpoint_config), + "deviceConfig": asdict(device_config), + "hyperParameters": hyperparameters, + "stoppingCondition": asdict(stopping_condition), + "tags": tags, + } + + return create_job_kwargs + + +def _generate_default_job_name(image_uri: Optional[str]) -> str: + """ + Generate default job name using the image uri and a timestamp + Args: + image_uri (str, optional): URI for the image container. + + Returns: + str: Job name. + """ + if not image_uri: + job_type = "-default" + else: + job_type_match = re.search("/amazon-braket-(.*)-jobs:", image_uri) or re.search( + "/amazon-braket-([^:/]*)", image_uri + ) + job_type = f"-{job_type_match.groups()[0]}" if job_type_match else "" + + return f"braket-job{job_type}-{time.time() * 1000:.0f}" + + +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 + provided. + + Args: + source_module (str): S3 URI pointing to the tarred source module. + entry_point (str): Entry point for the job. + 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. + """ + if entry_point is None: + raise ValueError("If source_module is an S3 URI, entry_point must be provided.") + if not source_module.lower().endswith(".tar.gz"): + raise ValueError( + "If source_module is an S3 URI, it must point to a tar.gz file. " + f"Not a valid S3 URI for parameter `source_module`: {source_module}" + ) + aws_session.copy_s3_object(source_module, f"{code_location}/source.tar.gz") + + +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 + 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. + aws_session (AwsSession): AwsSession for uploading tarred source module. + code_location (str): S3 URI pointing to the location where the code will + be uploaded to. + + Returns: + str: Entry point. + """ + 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}") + + entry_point = entry_point or abs_path_source_module.stem + _validate_entry_point(abs_path_source_module, entry_point) + _tar_and_upload_to_code_location(abs_path_source_module, aws_session, code_location) + return entry_point + + +def _validate_entry_point(source_module_path: Path, entry_point: str) -> None: + """ + 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. + """ + importable, _, _method = entry_point.partition(":") + sys.path.append(str(source_module_path.parent)) + try: + # second argument allows relative imports + module = importlib.util.find_spec(importable, source_module_path.stem) + assert module is not None + # if entry point is nested (ie contains '.'), parent modules are imported + except (ModuleNotFoundError, AssertionError): + raise ValueError(f"Entry point module was not found: {importable}") + finally: + sys.path.pop() + + +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. + + Args: + source_module_path (Path): Path to source module. + aws_session (AwsSession): AwsSession for uploading source module. + code_location (str): S3 URI pointing to the location where the tarred + source module will be uploaded to. + """ + with tempfile.TemporaryDirectory() as temp_dir: + with tarfile.open(f"{temp_dir}/source.tar.gz", "w:gz", dereference=True) as tar: + tar.add(source_module_path, arcname=source_module_path.name) + aws_session.upload_to_s3(f"{temp_dir}/source.tar.gz", f"{code_location}/source.tar.gz") + + +def _validate_params(dict_arr: Dict[str, Tuple[any, any]]) -> None: + """ + 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. + """ + for parameter_name, value_tuple in dict_arr.items(): + user_input, expected_datatype = value_tuple + + if user_input and not isinstance(user_input, expected_datatype): + raise ValueError( + f"'{parameter_name}' should be of '{expected_datatype}' " + f"but user provided {type(user_input)}." + ) + + +def _process_input_data( + input_data: Union[str, Dict, S3DataSourceConfig], job_name: str, aws_session: AwsSession +) -> List[Dict[str, Any]]: + """ + Convert input data into a list of dicts compatible with the Braket API. + Args: + input_data (Union[str, Dict, S3DataSourceConfig]): Either a channel definition or a + dictionary mapping channel names to channel definitions, where a channel definition + can be an S3DataSourceConfig or a str corresponding to a local prefix or S3 prefix. + job_name (str): Job name. + aws_session (AwsSession): AwsSession for possibly uploading local data. + + Returns: + List[Dict[str, Any]]: A list of channel configs. + """ + if not isinstance(input_data, dict): + 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) + return _convert_input_to_config(input_data) + + +def _process_channel( + location: str, job_name: str, aws_session: AwsSession, channel_name: str +) -> S3DataSourceConfig: + """ + Convert a location to an S3DataSourceConfig, uploading local data to S3, if necessary. + Args: + location (str): Local prefix or S3 prefix. + job_name (str): Job name. + aws_session (AwsSession): AwsSession to be used for uploading local data. + channel_name (str): Name of the channel. + + Returns: + S3DataSourceConfig: S3DataSourceConfig for the 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/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.upload_local_data(location, s3_prefix) + return S3DataSourceConfig(s3_prefix) + + +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 + configs compatible with the Braket API. + + Args: + input_data (Dict[str, S3DataSourceConfig]): A dictionary mapping channel names to + S3DataSourceConfig objects. + + Returns: + List[Dict[str, Any]]: A list of channel configs. + """ + return [ + { + "channelName": channel_name, + **data_config.config, + } + for channel_name, data_config in input_data.items() + ] diff --git a/src/braket/jobs/serialization.py b/src/braket/jobs/serialization.py new file mode 100644 index 00000000..f8c854d0 --- /dev/null +++ b/src/braket/jobs/serialization.py @@ -0,0 +1,67 @@ +# 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 codecs +import pickle +from typing import Any, Dict + +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`. + + Args: + 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 + the specified `data_format`. + """ + return ( + { + k: codecs.encode(pickle.dumps(v, protocol=4), "base64").decode() + for k, v in data_dictionary.items() + } + if data_format == PersistedJobDataFormat.PICKLED_V4 + else data_dictionary + ) + + +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`. + + Args: + 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 + the specified `data_format` to plaintext. + """ + return ( + {k: pickle.loads(codecs.decode(v.encode(), "base64")) for k, v in data_dictionary.items()} + if data_format == PersistedJobDataFormat.PICKLED_V4 + else data_dictionary + ) diff --git a/test/integ_tests/job_test_script.py b/test/integ_tests/job_test_script.py new file mode 100644 index 00000000..7071ffd6 --- /dev/null +++ b/test/integ_tests/job_test_script.py @@ -0,0 +1,53 @@ +# 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 + +from braket.aws import AwsDevice +from braket.circuits import Circuit +from braket.jobs import save_job_checkpoint, save_job_result +from braket.jobs_data import PersistedJobDataFormat + + +def start_here(): + hp_file = os.environ["AMZN_BRAKET_HP_FILE"] + with open(hp_file, "r") as f: + hyperparameters = json.load(f) + + if hyperparameters["test_case"] == "completed": + completed_job_script() + else: + failed_job_script() + + +def failed_job_script(): + print("Test job started!!!!!") + assert 0 + + +def completed_job_script(): + print("Test job started!!!!!") + + # Use the device declared in the Orchestration Script + device = AwsDevice(os.environ["AMZN_BRAKET_DEVICE_ARN"]) + + bell = Circuit().h(0).cnot(0, 1) + for count in range(5): + task = device.run(bell, shots=100) + print(task.result().measurement_counts) + save_job_result({"converged": True, "energy": -0.2}) + save_job_checkpoint({"some_data": "abc"}, checkpoint_file_suffix="plain_data") + save_job_checkpoint({"some_data": "abc"}, data_format=PersistedJobDataFormat.PICKLED_V4) + + print("Test job completed!!!!!") diff --git a/test/integ_tests/test_create_local_quantum_job.py b/test/integ_tests/test_create_local_quantum_job.py new file mode 100644 index 00000000..838a0132 --- /dev/null +++ b/test/integ_tests/test_create_local_quantum_job.py @@ -0,0 +1,151 @@ +# 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 re +import tempfile +from pathlib import Path + +import pytest + +from braket.jobs.local import LocalQuantumJob + + +def test_completed_local_job(aws_session, capsys): + """Asserts the job is completed with the respective files and folders for logs, + results and checkpoints. Validate the results are what we expect. Also, + assert that logs contains all the necessary steps for setup and running + the job is displayed to the user. + """ + absolute_source_module = str(Path("test/integ_tests/job_test_script.py").resolve()) + current_dir = Path.cwd() + + with tempfile.TemporaryDirectory() as temp_dir: + os.chdir(temp_dir) + job = LocalQuantumJob.create( + "arn:aws:braket:::device/quantum-simulator/amazon/sv1", + source_module=absolute_source_module, + entry_point="job_test_script:start_here", + hyperparameters={"test_case": "completed"}, + aws_session=aws_session, + ) + + job_name = job.name + pattern = f"^local:job/{job_name}$" + re.match(pattern=pattern, string=job.arn) + + assert job.state() == "COMPLETED" + assert Path(job_name).is_dir() + + # Check results match the expectations. + assert Path(f"{job_name}/results.json").exists() + assert job.result() == {"converged": True, "energy": -0.2} + + # Validate checkpoint files and data + assert Path(f"{job_name}/checkpoints/{job_name}.json").exists() + assert Path(f"{job_name}/checkpoints/{job_name}_plain_data.json").exists() + + for file_name, expected_data in [ + ( + f"{job_name}/checkpoints/{job_name}_plain_data.json", + { + "braketSchemaHeader": { + "name": "braket.jobs_data.persisted_job_data", + "version": "1", + }, + "dataDictionary": {"some_data": "abc"}, + "dataFormat": "plaintext", + }, + ), + ( + f"{job_name}/checkpoints/{job_name}.json", + { + "braketSchemaHeader": { + "name": "braket.jobs_data.persisted_job_data", + "version": "1", + }, + "dataDictionary": {"some_data": "gASVBwAAAAAAAACMA2FiY5Qu\n"}, + "dataFormat": "pickled_v4", + }, + ), + ]: + with open(file_name, "r") as f: + assert json.loads(f.read()) == expected_data + + # Capture logs + assert Path(f"{job_name}/log.txt").exists() + job.logs() + log_data, errors = capsys.readouterr() + + logs_to_validate = [ + "Beginning Setup", + "Running Code As Process", + "Test job started!!!!!", + "Test job completed!!!!!", + "Code Run Finished", + ] + + for data in logs_to_validate: + assert data in log_data + + os.chdir(current_dir) + + +def test_failed_local_job(aws_session, capsys): + """Asserts the job is failed with the output, checkpoints not created in bucket + and only logs are populated. Validate the calling result function raises + the ValueError. Also, check if the logs displays the required error message. + """ + absolute_source_module = str(Path("test/integ_tests/job_test_script.py").resolve()) + current_dir = Path.cwd() + + with tempfile.TemporaryDirectory() as temp_dir: + os.chdir(temp_dir) + job = LocalQuantumJob.create( + "arn:aws:braket:::device/quantum-simulator/amazon/sv1", + source_module=absolute_source_module, + entry_point="job_test_script:start_here", + hyperparameters={"test_case": "failed"}, + aws_session=aws_session, + ) + + job_name = job.name + pattern = f"^local:job/{job_name}$" + re.match(pattern=pattern, string=job.arn) + + assert Path(job_name).is_dir() + + # Check no files are populated in checkpoints folder. + assert not any(Path(f"{job_name}/checkpoints").iterdir()) + + # Check results match the expectations. + error_message = f"Unable to find results in the local job directory {job_name}." + with pytest.raises(ValueError, match=error_message): + job.result() + + assert Path(f"{job_name}/log.txt").exists() + job.logs() + log_data, errors = capsys.readouterr() + + logs_to_validate = [ + "Beginning Setup", + "Running Code As Process", + "Test job started!!!!!", + "Code Run Finished", + ] + + for data in logs_to_validate: + assert data in log_data + + os.chdir(current_dir) diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py new file mode 100644 index 00000000..b88405e6 --- /dev/null +++ b/test/integ_tests/test_create_quantum_job.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. + +import json +import os.path +import re +import tempfile +from pathlib import Path + +from braket.aws.aws_quantum_job import AwsQuantumJob + + +def test_failed_quantum_job(aws_session, capsys): + """Asserts the job is failed with the output, checkpoints, + 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_name = job.name + pattern = f"^arn:aws:braket:{aws_session.region}:\\d12:job/{job_name}$" + 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. + keys = aws_session.list_keys( + bucket=f"amazon-braket-{aws_session.region}-{aws_session.account_id}", + prefix=f"jobs/{job_name}", + ) + assert keys == [f"jobs/{job_name}/script/source.tar.gz"] + + # no results saved + assert job.result() == {} + + job.logs() + log_data, errors = capsys.readouterr() + assert errors == "" + logs_to_validate = [ + "Invoking script with the following command:", + "/usr/local/bin/python3.7 braket_container.py", + "Running Code As Process", + "Test job started!!!!!", + "AssertionError", + "Code Run Finished", + '"user_entry_point": "braket_container.py"', + ] + + for data in logs_to_validate: + assert data in log_data + + assert job.metadata()["failureReason"] == ( + "AlgorithmError: Job at job_test_script:start_here exited with exit code: 1" + ) + + +def test_completed_quantum_job(aws_session, capsys): + """Asserts the job is completed with the output, checkpoints, tasks and + script folder created in S3 for respective 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 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_name = job.name + pattern = f"^arn:aws:braket:{aws_session.region}:\\d12:job/{job_name}$" + 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. + s3_bucket = f"amazon-braket-{aws_session.region}-{aws_session.account_id}" + keys = aws_session.list_keys( + bucket=s3_bucket, + prefix=f"jobs/{job_name}", + ) + 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", + ]: + assert any(re.match(expected_key, key) for key in 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", + { + "braketSchemaHeader": { + "name": "braket.jobs_data.persisted_job_data", + "version": "1", + }, + "dataDictionary": {"some_data": "abc"}, + "dataFormat": "plaintext", + }, + ), + ( + f"jobs/{job_name}/checkpoints/{job_name}.json", + { + "braketSchemaHeader": { + "name": "braket.jobs_data.persisted_job_data", + "version": "1", + }, + "dataDictionary": {"some_data": "gASVBwAAAAAAAACMA2FiY5Qu\n"}, + "dataFormat": "pickled_v4", + }, + ), + ]: + assert ( + json.loads( + aws_session.retrieve_s3_object_body(s3_bucket=s3_bucket, s3_object_key=s3_key) + ) + == 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) + 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} + os.chdir(current_dir) + + # Check the logs and validate it contains required output. + job.logs(wait=True) + log_data, errors = capsys.readouterr() + assert errors == "" + logs_to_validate = [ + "Invoking script with the following command:", + "/usr/local/bin/python3.7 braket_container.py", + "Running Code As Process", + "Test job started!!!!!", + "Test job completed!!!!!", + "Code Run Finished", + '"user_entry_point": "braket_container.py"', + "Reporting training SUCCESS", + ] + + for data in logs_to_validate: + assert data in log_data diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index a480e12c..63b7a371 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -16,7 +16,7 @@ from braket.aws import AwsDevice DWAVE_ARN = "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6" -RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-8" +RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-10" IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/ionQdevice" SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 846bf165..8eaaa367 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-9" +RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-10" IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/ionQdevice" SV1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" TN1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/tn1" @@ -131,11 +131,12 @@ class MockS3: def run_and_assert( aws_quantum_task_mock, device, + default_s3_folder, default_shots, default_poll_timeout, default_poll_interval, circuit, - s3_destination_folder, + s3_destination_folder, # Treated as positional arg shots, # Treated as positional arg poll_timeout_seconds, # Treated as positional arg poll_interval_seconds, # Treated as positional arg @@ -146,6 +147,8 @@ def run_and_assert( aws_quantum_task_mock.return_value = task_mock run_args = [] + if s3_destination_folder is not None: + run_args.append(s3_destination_folder) if shots is not None: run_args.append(shots) if poll_timeout_seconds is not None: @@ -155,13 +158,15 @@ def run_and_assert( run_args += extra_args if extra_args else [] run_kwargs = extra_kwargs or {} - task = device.run(circuit, s3_destination_folder, *run_args, **run_kwargs) + task = device.run(circuit, *run_args, **run_kwargs) assert task == task_mock create_args, create_kwargs = _create_task_args_and_kwargs( + default_s3_folder, default_shots, default_poll_timeout, default_poll_interval, + s3_destination_folder, shots, poll_timeout_seconds, poll_interval_seconds, @@ -170,12 +175,7 @@ def run_and_assert( ) aws_quantum_task_mock.assert_called_with( - device._aws_session, - device.arn, - circuit, - s3_destination_folder, - *create_args, - **create_kwargs + device._aws_session, device.arn, circuit, *create_args, **create_kwargs ) @@ -183,6 +183,7 @@ def run_batch_and_assert( aws_quantum_task_mock, aws_session_mock, device, + default_s3_folder, default_shots, default_poll_timeout, default_poll_interval, @@ -203,6 +204,8 @@ def run_batch_and_assert( aws_session_mock.return_value = new_session_mock run_args = [] + if s3_destination_folder is not None: + run_args.append(s3_destination_folder) if shots is not None: run_args.append(shots) if max_parallel is not None: @@ -216,13 +219,15 @@ def run_batch_and_assert( run_args += extra_args if extra_args else [] run_kwargs = extra_kwargs or {} - batch = device.run_batch(circuits, s3_destination_folder, *run_args, **run_kwargs) + batch = device.run_batch(circuits, *run_args, **run_kwargs) assert batch.tasks == [task_mock for _ in range(len(circuits))] create_args, create_kwargs = _create_task_args_and_kwargs( + default_s3_folder, default_shots, default_poll_timeout, default_poll_interval, + s3_destination_folder, shots, poll_timeout_seconds, poll_interval_seconds, @@ -235,26 +240,26 @@ def run_batch_and_assert( # aws_session_mock.call_args.kwargs syntax is newer than Python 3.7 assert aws_session_mock.call_args[1]["config"].max_pool_connections == max_pool_connections aws_quantum_task_mock.assert_called_with( - new_session_mock, - device.arn, - circuits[0], - s3_destination_folder, - *create_args, - **create_kwargs + new_session_mock, device.arn, circuits[0], *create_args, **create_kwargs ) def _create_task_args_and_kwargs( + default_s3_folder, default_shots, default_poll_timeout, default_poll_interval, + s3_folder, shots, poll_timeout_seconds, poll_interval_seconds, extra_args, extra_kwargs, ): - create_args = [shots if shots is not None else default_shots] + create_args = [ + 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_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 b4cd59ed..911ad932 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -10,10 +10,11 @@ # 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 Mock, patch import pytest +from botocore.exceptions import ClientError from common_test_utils import ( DWAVE_ARN, IONQ_ARN, @@ -73,7 +74,7 @@ def test_mock_rigetti_schema_1(): MOCK_GATE_MODEL_QPU_1 = { - "deviceName": "Aspen-9", + "deviceName": "Aspen-10", "deviceType": "QPU", "providerName": "provider1", "deviceStatus": "OFFLINE", @@ -231,6 +232,11 @@ def test_gate_model_sim_schema(): "deviceCapabilities": MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES.json(), } +MOCK_DEFAULT_S3_DESTINATION_FOLDER = ( + "amazon-braket-us-test-1-00000000", + "tasks", +) + @pytest.fixture def arn(): @@ -254,23 +260,6 @@ def boto_session(): return _boto_session -@pytest.fixture -def aws_explicit_session(): - _boto_session = Mock() - _boto_session.region_name = RIGETTI_REGION - - creds = Mock() - creds.access_key = "access key" - creds.secret_key = "secret key" - creds.token = "token" - creds.method = "explicit" - _boto_session.get_credentials.return_value = creds - - _aws_session = Mock() - _aws_session.boto_session = _boto_session - return _aws_session - - @pytest.fixture def aws_session(): _boto_session = Mock() @@ -282,6 +271,11 @@ def aws_session(): _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 @@ -320,43 +314,41 @@ def test_device_simulator_no_aws_session(aws_session_init, aws_session): aws_session.get_device.assert_called_with(arn) -@patch("boto3.Session") -def test_copy_session(boto_session_init, aws_session): - boto_session_init.return_value = Mock() - AwsDevice._copy_aws_session(aws_session, RIGETTI_REGION) - boto_session_init.assert_called_with(region_name=RIGETTI_REGION) - - -@patch("boto3.Session") -def test_copy_explicit_session(boto_session_init, aws_explicit_session): - boto_session_init.return_value = Mock() - AwsDevice._copy_aws_session(aws_explicit_session, RIGETTI_REGION) - boto_session_init.assert_called_with( - aws_access_key_id="access key", - aws_secret_access_key="secret key", - aws_session_token="token", - region_name=RIGETTI_REGION, - ) - - -@patch("braket.aws.aws_device.AwsDevice._copy_aws_session") +@patch("braket.aws.aws_device.AwsSession.copy_session") @patch("braket.aws.aws_device.AwsSession") @pytest.mark.parametrize( "get_device_side_effect", [ [MOCK_GATE_MODEL_QPU_1], - [ValueError(), MOCK_GATE_MODEL_QPU_1], + [ + ClientError( + { + "Error": { + "Code": "ResourceNotFoundException", + } + }, + "getDevice", + ), + MOCK_GATE_MODEL_QPU_1, + ], ], ) def test_device_qpu_no_aws_session( - aws_session_init, mock_copy_aws_session, get_device_side_effect, aws_session + aws_session_init, mock_copy_session, get_device_side_effect, aws_session ): arn = RIGETTI_ARN mock_session = Mock() mock_session.get_device.side_effect = get_device_side_effect - aws_session.get_device.side_effect = ValueError() + aws_session.get_device.side_effect = ClientError( + { + "Error": { + "Code": "ResourceNotFoundException", + } + }, + "getDevice", + ) aws_session_init.return_value = aws_session - mock_copy_aws_session.return_value = mock_session + mock_copy_session.return_value = mock_session device = AwsDevice(arn) _assert_device_fields(device, MOCK_GATE_MODEL_QPU_CAPABILITIES_1, MOCK_GATE_MODEL_QPU_1) @@ -394,29 +386,118 @@ def test_repr(arn): assert repr(device) == expected -@pytest.mark.xfail(raises=ValueError) def test_device_simulator_not_found(): mock_session = Mock() - mock_session.get_device.side_effect = ValueError() - AwsDevice("arn:aws:braket:::device/simulator/a/b", mock_session) + mock_session.region = "test-region-1" + mock_session.get_device.side_effect = ClientError( + { + "Error": { + "Code": "ResourceNotFoundException", + "Message": ( + "Braket device 'arn:aws:braket:::device/quantum-simulator/amazon/tn1' " + "not found in us-west-1. You can find a list of all supported device " + "ARNs and the regions in which they are available in the documentation: " + "https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html" + ), + } + }, + "getDevice", + ) + simulator_not_found = ( + "Simulator 'arn:aws:braket:::device/simulator/a/b' not found in 'test-region-1'" + ) + with pytest.raises(ValueError, match=simulator_not_found): + AwsDevice("arn:aws:braket:::device/simulator/a/b", mock_session) -@pytest.mark.xfail(raises=ValueError) -@patch("braket.aws.aws_device.AwsDevice._copy_aws_session") -def test_device_qpu_not_found(mock_copy_aws_session): +@patch("braket.aws.aws_device.AwsSession.copy_session") +def test_device_qpu_not_found(mock_copy_session): + mock_session = Mock() + mock_session.get_device.side_effect = ClientError( + { + "Error": { + "Code": "ResourceNotFoundException", + "Message": ( + "Braket device 'arn:aws:braket:::device/quantum-simulator/amazon/tn1' " + "not found in us-west-1. You can find a list of all supported device " + "ARNs and the regions in which they are available in the documentation: " + "https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html" + ), + } + }, + "getDevice", + ) + mock_copy_session.return_value = mock_session + qpu_not_found = "QPU 'arn:aws:braket:::device/qpu/a/b' not found" + with pytest.raises(ValueError, match=qpu_not_found): + AwsDevice("arn:aws:braket:::device/qpu/a/b", mock_session) + + +@patch("braket.aws.aws_device.AwsSession.copy_session") +def test_device_qpu_exception(mock_copy_session): + mock_session = Mock() + mock_session.get_device.side_effect = ( + ClientError( + { + "Error": { + "Code": "ResourceNotFoundException", + "Message": ( + "Braket device 'arn:aws:braket:::device/quantum-simulator/amazon/tn1' " + "not found in us-west-1. You can find a list of all supported device " + "ARNs and the regions in which they are available in the documentation: " + "https://docs.aws.amazon.com/braket/latest/developerguide/braket-" + "devices.html" + ), + } + }, + "getDevice", + ), + ClientError( + { + "Error": { + "Code": "OtherException", + "Message": "Some other message", + } + }, + "getDevice", + ), + ) + mock_copy_session.return_value = mock_session + qpu_exception = ( + "An error occurred \\(OtherException\\) when calling the " + "getDevice operation: Some other message" + ) + with pytest.raises(ClientError, match=qpu_exception): + AwsDevice("arn:aws:braket:::device/qpu/a/b", mock_session) + + +@patch("braket.aws.aws_device.AwsSession.copy_session") +def test_device_non_qpu_region_error(mock_copy_session): mock_session = Mock() - mock_session.get_device.side_effect = ValueError() - mock_copy_aws_session.return_value = mock_session - AwsDevice("arn:aws:braket:::device/qpu/a/b", mock_session) + mock_session.get_device.side_effect = ClientError( + { + "Error": { + "Code": "ExpiredTokenError", + "Message": ("Some other error that isn't ResourceNotFoundException"), + } + }, + "getDevice", + ) + mock_copy_session.return_value = mock_session + expired_token = ( + "An error occurred \\(ExpiredTokenError\\) when calling the getDevice operation: " + "Some other error that isn't ResourceNotFoundException" + ) + with pytest.raises(ClientError, match=expired_token): + AwsDevice("arn:aws:braket:::device/qpu/a/b", mock_session) @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_no_extra(aws_quantum_task_mock, device, circuit, s3_destination_folder): +def test_run_no_extra(aws_quantum_task_mock, device, circuit): _run_and_assert( aws_quantum_task_mock, device, circuit, - s3_destination_folder, ) @@ -460,6 +541,7 @@ def test_run_with_qpu_no_shots(aws_quantum_task_mock, device, circuit, s3_destin run_and_assert( aws_quantum_task_mock, device(RIGETTI_ARN), + MOCK_DEFAULT_S3_DESTINATION_FOLDER, AwsDevice.DEFAULT_SHOTS_QPU, AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, @@ -473,6 +555,27 @@ def test_run_with_qpu_no_shots(aws_quantum_task_mock, device, circuit, s3_destin ) +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_default_bucket_not_called(aws_quantum_task_mock, device, circuit, s3_destination_folder): + device = device(RIGETTI_ARN) + run_and_assert( + aws_quantum_task_mock, + device, + MOCK_DEFAULT_S3_DESTINATION_FOLDER, + AwsDevice.DEFAULT_SHOTS_QPU, + AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, + AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, + circuit, + s3_destination_folder, + None, + None, + None, + None, + None, + ) + device._aws_session.default_bucket.assert_not_called() + + @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_shots_poll_timeout_kwargs( aws_quantum_task_mock, device, circuit, s3_destination_folder @@ -505,21 +608,28 @@ def test_run_with_positional_args_and_kwargs( ) -@patch("braket.aws.aws_device.AwsSession") +@patch.dict( + os.environ, + {"AMZN_BRAKET_TASK_RESULTS_S3_URI": "s3://env_bucket/env/path"}, +) @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_batch_no_extra( - aws_quantum_task_mock, aws_session_mock, device, circuit, s3_destination_folder -): +def test_run_env_variables(aws_quantum_task_mock, device, circuit): + device("foo:bar").run(circuit) + assert aws_quantum_task_mock.call_args_list[0][0][3] == ("env_bucket", "env/path") + + +@patch("braket.aws.aws_session.AwsSession") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_batch_no_extra(aws_quantum_task_mock, aws_session_mock, device, circuit): _run_batch_and_assert( aws_quantum_task_mock, aws_session_mock, device, [circuit for _ in range(10)], - s3_destination_folder, ) -@patch("braket.aws.aws_device.AwsSession") +@patch("braket.aws.aws_session.AwsSession") @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_batch_with_shots( aws_quantum_task_mock, aws_session_mock, device, circuit, s3_destination_folder @@ -534,7 +644,7 @@ def test_run_batch_with_shots( ) -@patch("braket.aws.aws_device.AwsSession") +@patch("braket.aws.aws_session.AwsSession") @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_batch_with_max_parallel_and_kwargs( aws_quantum_task_mock, aws_session_mock, device, circuit, s3_destination_folder @@ -552,11 +662,21 @@ def test_run_batch_with_max_parallel_and_kwargs( ) +@patch.dict( + os.environ, + {"AMZN_BRAKET_TASK_RESULTS_S3_URI": "s3://env_bucket/env/path"}, +) +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_batch_env_variables(aws_quantum_task_mock, device, circuit): + device("foo:bar").run_batch([circuit]) + assert aws_quantum_task_mock.call_args_list[0][0][3] == ("env_bucket", "env/path") + + def _run_and_assert( aws_quantum_task_mock, device_factory, circuit, - s3_destination_folder, + s3_destination_folder=None, # Treated as positional arg shots=None, # Treated as positional arg poll_timeout_seconds=None, # Treated as positional arg poll_interval_seconds=None, # Treated as positional arg @@ -566,6 +686,7 @@ def _run_and_assert( run_and_assert( aws_quantum_task_mock, device_factory("foo_bar"), + MOCK_DEFAULT_S3_DESTINATION_FOLDER, AwsDevice.DEFAULT_SHOTS_SIMULATOR, AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, @@ -584,7 +705,7 @@ def _run_batch_and_assert( aws_session_mock, device_factory, circuits, - s3_destination_folder, + s3_destination_folder=None, # Treated as positional arg shots=None, # Treated as positional arg max_parallel=None, # Treated as positional arg max_connections=None, # Treated as positional arg @@ -597,6 +718,7 @@ def _run_batch_and_assert( aws_quantum_task_mock, aws_session_mock, device_factory("foo_bar"), + MOCK_DEFAULT_S3_DESTINATION_FOLDER, AwsDevice.DEFAULT_SHOTS_SIMULATOR, AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, @@ -622,8 +744,8 @@ def _assert_device_fields(device, expected_properties, expected_device_data): assert device.topology_graph.edges == device._construct_topology_graph().edges -@patch("braket.aws.aws_device.AwsDevice._copy_aws_session") -def test_get_devices(mock_copy_aws_session, aws_session): +@patch("braket.aws.aws_device.AwsSession.copy_session") +def test_get_devices(mock_copy_session, aws_session): aws_session.search_devices.side_effect = [ # us-west-1 [ @@ -679,7 +801,7 @@ def test_get_devices(mock_copy_aws_session, aws_session): MOCK_GATE_MODEL_QPU_2, ValueError("should not be reachable"), ] - mock_copy_aws_session.return_value = session_for_region + mock_copy_session.return_value = session_for_region # Search order: us-east-1, us-west-1, us-west-2 results = AwsDevice.get_devices( arns=[SV1_ARN, DWAVE_ARN, IONQ_ARN], @@ -690,8 +812,8 @@ def test_get_devices(mock_copy_aws_session, aws_session): assert [result.name for result in results] == ["Advantage_system1.1", "Blah", "SV1"] -@patch("braket.aws.aws_device.AwsDevice._copy_aws_session") -def test_get_devices_simulators_only(mock_copy_aws_session, aws_session): +@patch("braket.aws.aws_device.AwsSession.copy_session") +def test_get_devices_simulators_only(mock_copy_session, aws_session): aws_session.search_devices.side_effect = [ [ { @@ -711,7 +833,7 @@ def test_get_devices_simulators_only(mock_copy_aws_session, aws_session): session_for_region = Mock() session_for_region.search_devices.side_effect = ValueError("should not be reachable") session_for_region.get_device.side_effect = ValueError("should not be reachable") - mock_copy_aws_session.return_value = session_for_region + mock_copy_session.return_value = session_for_region results = AwsDevice.get_devices( arns=[SV1_ARN, TN1_ARN], types=["SIMULATOR"], diff --git a/test/unit_tests/braket/aws/test_aws_quantum_job.py b/test/unit_tests/braket/aws/test_aws_quantum_job.py new file mode 100644 index 00000000..2265fd3c --- /dev/null +++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py @@ -0,0 +1,910 @@ +# 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 datetime +import json +import logging +import os +import tarfile +import tempfile +from unittest.mock import Mock, patch + +import pytest +from botocore.exceptions import ClientError + +from braket.aws import AwsQuantumJob, AwsSession + + +@pytest.fixture +def aws_session(quantum_job_arn, job_region): + _aws_session = Mock(spec=AwsSession) + _aws_session.create_job.return_value = quantum_job_arn + _aws_session.default_bucket.return_value = "default-bucket-name" + _aws_session.get_default_jobs_role.return_value = "default-role-arn" + _aws_session.construct_s3_uri.side_effect = ( + lambda bucket, *dirs: f"s3://{bucket}/{'/'.join(dirs)}" + ) + + def fake_copy_session(region): + _aws_session.region = region + return _aws_session + + _aws_session.copy_session.side_effect = fake_copy_session + _aws_session.list_keys.return_value = ["job-path/output/model.tar.gz"] + _aws_session.region = "us-test-1" + + _braket_client_mock = Mock(meta=Mock(region_name=job_region)) + _aws_session.braket_client = _braket_client_mock + return _aws_session + + +@pytest.fixture +def generate_get_job_response(): + def _get_job_response(**kwargs): + response = { + "ResponseMetadata": { + "RequestId": "d223b1a0-ee5c-4c75-afa7-3c29d5338b62", + "HTTPStatusCode": 200, + }, + "algorithmSpecification": { + "scriptModeConfig": { + "entryPoint": "my_file:start_here", + "s3Uri": "s3://amazon-braket-jobs/job-path/my_file.py", + } + }, + "checkpointConfig": { + "localPath": "/opt/omega/checkpoints", + "s3Uri": "s3://amazon-braket-jobs/job-path/checkpoints", + }, + "createdAt": datetime.datetime(2021, 6, 28, 21, 4, 51), + "deviceConfig": { + "device": "arn:aws:braket:::device/qpu/rigetti/Aspen-10", + }, + "hyperParameters": { + "foo": "bar", + }, + "inputDataConfig": [ + { + "channelName": "training_input", + "dataSource": { + "s3DataSource": { + "s3Uri": "s3://amazon-braket-jobs/job-path/input", + } + }, + } + ], + "instanceConfig": { + "instanceCount": 1, + "instanceType": "ml.m5.large", + "volumeSizeInGb": 1, + }, + "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"}, + "roleArn": "arn:aws:iam::875981177017:role/AmazonBraketJobRole", + "status": "RUNNING", + "stoppingCondition": {"maxRuntimeInSeconds": 1200}, + } + response.update(kwargs) + + return response + + return _get_job_response + + +@pytest.fixture +def generate_cancel_job_response(): + def _cancel_job_response(**kwargs): + response = { + "ResponseMetadata": { + "RequestId": "857b0893-2073-4ad6-b828-744af8400dfe", + "HTTPStatusCode": 200, + }, + "cancellationStatus": "CANCELLING", + "jobArn": "arn:aws:braket:us-west-2:875981177017:job/job-test-20210628140446", + } + response.update(kwargs) + return response + + return _cancel_job_response + + +@pytest.fixture +def quantum_job_name(): + return "job-test-20210628140446" + + +@pytest.fixture +def job_region(): + return "us-west-2" + + +@pytest.fixture +def quantum_job_arn(quantum_job_name, job_region): + return f"arn:aws:braket:{job_region}:875981177017:job/{quantum_job_name}" + + +@pytest.fixture +def quantum_job(quantum_job_arn, aws_session): + return AwsQuantumJob(quantum_job_arn, aws_session) + + +def test_equality(quantum_job_arn, aws_session, job_region): + new_aws_session = Mock(braket_client=Mock(meta=Mock(region_name=job_region))) + quantum_job_1 = AwsQuantumJob(quantum_job_arn, aws_session) + quantum_job_2 = AwsQuantumJob(quantum_job_arn, aws_session) + quantum_job_3 = AwsQuantumJob(quantum_job_arn, new_aws_session) + other_quantum_job = AwsQuantumJob( + "arn:aws:braket:us-west-2:875981177017:job/other-job", aws_session + ) + non_quantum_job = quantum_job_1.arn + + assert quantum_job_1 == quantum_job_2 + assert quantum_job_1 == quantum_job_3 + assert quantum_job_1 is not quantum_job_2 + assert quantum_job_1 is not quantum_job_3 + assert quantum_job_1 is quantum_job_1 + assert quantum_job_1 != other_quantum_job + assert quantum_job_1 != non_quantum_job + + +def test_hash(quantum_job): + assert hash(quantum_job) == hash(quantum_job.arn) + + +@pytest.mark.parametrize( + "arn, expected_region", + [ + ("arn:aws:braket:us-west-2:875981177017:job/job-name", "us-west-2"), + ("arn:aws:braket:us-west-1:1234567890:job/job-name", "us-west-1"), + ], +) +@patch("braket.aws.aws_quantum_job.boto3.Session") +@patch("braket.aws.aws_quantum_job.AwsSession") +def test_quantum_job_constructor_default_session( + aws_session_mock, mock_session, arn, expected_region +): + mock_boto_session = Mock() + aws_session_mock.return_value = Mock() + mock_session.return_value = mock_boto_session + job = AwsQuantumJob(arn) + mock_session.assert_called_with(region_name=expected_region) + aws_session_mock.assert_called_with(boto_session=mock_boto_session) + assert job.arn == arn + assert job._aws_session == aws_session_mock.return_value + + +@pytest.mark.xfail(raises=ValueError) +def test_quantum_job_constructor_invalid_region(aws_session): + arn = "arn:aws:braket:unknown-region:875981177017:job/quantum_job_name" + AwsQuantumJob(arn, aws_session) + + +@patch("braket.aws.aws_quantum_job.boto3.Session") +def test_quantum_job_constructor_explicit_session(mock_session, quantum_job_arn, job_region): + aws_session_mock = Mock(braket_client=Mock(meta=Mock(region_name=job_region))) + job = AwsQuantumJob(quantum_job_arn, aws_session_mock) + assert job._aws_session == aws_session_mock + assert job.arn == quantum_job_arn + mock_session.assert_not_called() + + +def test_metadata(quantum_job, aws_session, generate_get_job_response, quantum_job_arn): + get_job_response_running = generate_get_job_response(status="RUNNING") + aws_session.get_job.return_value = get_job_response_running + assert quantum_job.metadata() == get_job_response_running + aws_session.get_job.assert_called_with(quantum_job_arn) + + get_job_response_completed = generate_get_job_response(status="COMPLETED") + aws_session.get_job.return_value = get_job_response_completed + assert quantum_job.metadata() == get_job_response_completed + aws_session.get_job.assert_called_with(quantum_job_arn) + assert aws_session.get_job.call_count == 2 + + +def test_metadata_caching(quantum_job, aws_session, generate_get_job_response, quantum_job_arn): + get_job_response_running = generate_get_job_response(status="RUNNING") + aws_session.get_job.return_value = get_job_response_running + assert quantum_job.metadata(True) == get_job_response_running + + get_job_response_completed = generate_get_job_response(status="COMPLETED") + aws_session.get_job.return_value = get_job_response_completed + assert quantum_job.metadata(True) == get_job_response_running + aws_session.get_job.assert_called_with(quantum_job_arn) + assert aws_session.get_job.call_count == 1 + + +def test_state(quantum_job, aws_session, generate_get_job_response, quantum_job_arn): + state_1 = "RUNNING" + get_job_response_running = generate_get_job_response(status=state_1) + aws_session.get_job.return_value = get_job_response_running + assert quantum_job.state() == state_1 + aws_session.get_job.assert_called_with(quantum_job_arn) + + state_2 = "COMPLETED" + get_job_response_completed = generate_get_job_response(status=state_2) + aws_session.get_job.return_value = get_job_response_completed + assert quantum_job.state() == state_2 + aws_session.get_job.assert_called_with(quantum_job_arn) + assert aws_session.get_job.call_count == 2 + + +def test_state_caching(quantum_job, aws_session, generate_get_job_response, quantum_job_arn): + state_1 = "RUNNING" + get_job_response_running = generate_get_job_response(status=state_1) + aws_session.get_job.return_value = get_job_response_running + assert quantum_job.state(True) == state_1 + + state_2 = "COMPLETED" + get_job_response_completed = generate_get_job_response(status=state_2) + aws_session.get_job.return_value = get_job_response_completed + assert quantum_job.state(True) == state_1 + aws_session.get_job.assert_called_with(quantum_job_arn) + assert aws_session.get_job.call_count == 1 + + +@pytest.fixture() +def result_setup(quantum_job_name): + with tempfile.TemporaryDirectory() as temp_dir: + os.chdir(temp_dir) + file_path = "results.json" + + 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", + } + ) + ) + + with tarfile.open("model.tar.gz", "w:gz") as tar: + tar.add(file_path, arcname=os.path.basename(file_path)) + + yield + + result_dir = f"{os.getcwd()}/{quantum_job_name}" + + if os.path.exists(result_dir): + os.remove(f"{result_dir}/results.json") + os.rmdir(f"{result_dir}/") + + if os.path.isfile("model.tar.gz"): + os.remove("model.tar.gz") + + os.chdir("..") + + +@pytest.mark.parametrize("state", AwsQuantumJob.TERMINAL_STATES) +def test_results_when_job_is_completed( + quantum_job, aws_session, generate_get_job_response, result_setup, state +): + expected_saved_data = {"converged": True, "energy": -0.2} + + get_job_response_completed = generate_get_job_response(status=state) + quantum_job._aws_session.get_job.return_value = get_job_response_completed + actual_data = quantum_job.result() + + job_metadata = quantum_job.metadata(True) + s3_path = job_metadata["outputDataConfig"]["s3Path"] + + output_bucket_uri = f"{s3_path}/output/model.tar.gz" + quantum_job._aws_session.download_from_s3.assert_called_with( + s3_uri=output_bucket_uri, filename="model.tar.gz" + ) + assert actual_data == expected_saved_data + + +def test_download_result_when_job_is_running( + quantum_job, aws_session, generate_get_job_response, result_setup +): + poll_timeout_seconds, poll_interval_seconds, state = 1, 0.5, "RUNNING" + get_job_response_completed = generate_get_job_response(status=state) + aws_session.get_job.return_value = get_job_response_completed + job_metadata = quantum_job.metadata(True) + + with pytest.raises( + TimeoutError, + match=f"{job_metadata['jobName']}: Polling for job completion " + f"timed out after {poll_timeout_seconds} seconds.", + ): + quantum_job.download_result( + poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds + ) + + +def test_download_result_when_extract_path_not_provided( + quantum_job, generate_get_job_response, aws_session, result_setup +): + state = "COMPLETED" + expected_saved_data = {"converged": True, "energy": -0.2} + get_job_response_completed = generate_get_job_response(status=state) + quantum_job._aws_session.get_job.return_value = get_job_response_completed + job_metadata = quantum_job.metadata(True) + job_name = job_metadata["jobName"] + quantum_job.download_result() + + with open(f"{job_name}/results.json", "r") as file: + actual_data = json.loads(file.read())["dataDictionary"] + assert expected_saved_data == actual_data + + +def test_download_result_when_extract_path_provided( + quantum_job, generate_get_job_response, aws_session, result_setup +): + expected_saved_data = {"converged": True, "energy": -0.2} + state = "COMPLETED" + get_job_response_completed = generate_get_job_response(status=state) + aws_session.get_job.return_value = get_job_response_completed + job_metadata = quantum_job.metadata(True) + job_name = job_metadata["jobName"] + + with tempfile.TemporaryDirectory() as temp_dir: + quantum_job.download_result(temp_dir) + + with open(f"{temp_dir}/{job_name}/results.json", "r") as file: + actual_data = json.loads(file.read())["dataDictionary"] + assert expected_saved_data == actual_data + + +def test_empty_dict_returned_when_result_not_saved( + quantum_job, generate_get_job_response, aws_session +): + state = "COMPLETED" + get_job_response_completed = generate_get_job_response(status=state) + aws_session.get_job.return_value = get_job_response_completed + + exception_response = { + "Error": { + "Code": "404", + "Message": "Not Found", + } + } + quantum_job._aws_session.download_from_s3 = Mock( + side_effect=ClientError(exception_response, "HeadObject") + ) + assert quantum_job.result() == {} + + +def test_results_not_in_s3_for_download(quantum_job, generate_get_job_response, aws_session): + state = "COMPLETED" + get_job_response_completed = generate_get_job_response(status=state) + aws_session.get_job.return_value = get_job_response_completed + job_metadata = quantum_job.metadata(True) + output_s3_path = job_metadata["outputDataConfig"]["s3Path"] + + error_message = f"Error retrieving results, could not find results at '{output_s3_path}" + + exception_response = { + "Error": { + "Code": "404", + "Message": "Not Found", + } + } + quantum_job._aws_session.download_from_s3 = Mock( + side_effect=ClientError(exception_response, "HeadObject") + ) + with pytest.raises(ClientError, match=error_message): + quantum_job.download_result() + + +def test_results_raises_error_for_non_404_errors( + quantum_job, generate_get_job_response, aws_session +): + state = "COMPLETED" + get_job_response_completed = generate_get_job_response(status=state) + aws_session.get_job.return_value = get_job_response_completed + + error = "An error occurred \\(402\\) when calling the SomeObject operation: Something" + + exception_response = { + "Error": { + "Code": "402", + "Message": "Something", + } + } + quantum_job._aws_session.download_from_s3 = Mock( + side_effect=ClientError(exception_response, "SomeObject") + ) + with pytest.raises(ClientError, match=error): + quantum_job.result() + + +@patch("braket.aws.aws_quantum_job.AwsQuantumJob.download_result") +def test_results_json_file_not_in_tar( + result_download, quantum_job, aws_session, generate_get_job_response +): + state = "COMPLETED" + get_job_response_completed = generate_get_job_response(status=state) + quantum_job._aws_session.get_job.return_value = get_job_response_completed + assert quantum_job.result() == {} + + +@pytest.fixture +def entry_point(): + return "test-source-module.entry_point:func" + + +@pytest.fixture +def bucket(): + return "braket-region-id" + + +@pytest.fixture( + params=[ + None, + "aws.location/custom-jobs:tag.1.2.3", + "other.uri/custom-name:tag", + "other-custom-format.com", + ] +) +def image_uri(request): + return request.param + + +@pytest.fixture(params=["given_job_name", "default_job_name"]) +def job_name(request): + if request.param == "given_job_name": + return "test-job-name" + + +@pytest.fixture +def s3_prefix(job_name): + return f"{job_name}/non-default" + + +@pytest.fixture(params=["local_source", "s3_source"]) +def source_module(request, bucket, s3_prefix): + if request.param == "local_source": + return "test-source-module" + elif request.param == "s3_source": + return AwsSession.construct_s3_uri(bucket, "test-source-prefix", "source.tar.gz") + + +@pytest.fixture +def role_arn(): + return "arn:aws:iam::0000000000:role/AmazonBraketInternalSLR" + + +@pytest.fixture +def device_arn(): + return "arn:aws:braket:::device/qpu/test/device-name" + + +@pytest.fixture +def prepare_job_args(aws_session): + return { + "device": Mock(), + "source_module": Mock(), + "entry_point": Mock(), + "image_uri": Mock(), + "job_name": Mock(), + "code_location": Mock(), + "role_arn": Mock(), + "hyperparameters": Mock(), + "input_data": Mock(), + "instance_config": Mock(), + "stopping_condition": Mock(), + "output_data_config": Mock(), + "copy_checkpoints_from_job": Mock(), + "checkpoint_config": Mock(), + "aws_session": aws_session, + "tags": Mock(), + } + + +def test_str(quantum_job): + expected = f"AwsQuantumJob('arn':'{quantum_job.arn}')" + assert str(quantum_job) == expected + + +def test_arn(quantum_job_arn, aws_session): + quantum_job = AwsQuantumJob(quantum_job_arn, aws_session) + assert quantum_job.arn == quantum_job_arn + + +def test_name(quantum_job_arn, quantum_job_name, aws_session): + quantum_job = AwsQuantumJob(quantum_job_arn, aws_session) + assert quantum_job.name == quantum_job_name + + +@pytest.mark.xfail(raises=AttributeError) +def test_no_arn_setter(quantum_job): + quantum_job.arn = 123 + + +@pytest.mark.parametrize("wait_until_complete", [True, False]) +@patch("braket.aws.aws_quantum_job.AwsQuantumJob.logs") +@patch("braket.aws.aws_quantum_job.prepare_quantum_job") +def test_create_job( + mock_prepare_quantum_job, + mock_logs, + aws_session, + prepare_job_args, + quantum_job_arn, + wait_until_complete, +): + test_response_args = {"testArgs": "MyTestArg"} + mock_prepare_quantum_job.return_value = test_response_args + job = AwsQuantumJob.create(wait_until_complete=wait_until_complete, **prepare_job_args) + mock_prepare_quantum_job.assert_called_with(**prepare_job_args) + aws_session.create_job.assert_called_with(**test_response_args) + if wait_until_complete: + mock_logs.assert_called_once() + else: + mock_logs.assert_not_called() + assert job.arn == quantum_job_arn + + +def test_create_fake_arg(): + unexpected_kwarg = "create\\(\\) got an unexpected keyword argument 'fake_arg'" + with pytest.raises(TypeError, match=unexpected_kwarg): + AwsQuantumJob.create( + device="device", + source_module="source", + fake_arg="fake_value", + ) + + +def test_cancel_job(quantum_job_arn, aws_session, generate_cancel_job_response): + cancellation_status = "CANCELLING" + aws_session.cancel_job.return_value = generate_cancel_job_response( + cancellationStatus=cancellation_status + ) + quantum_job = AwsQuantumJob(quantum_job_arn, aws_session) + status = quantum_job.cancel() + aws_session.cancel_job.assert_called_with(quantum_job_arn) + assert status == cancellation_status + + +@pytest.mark.xfail(raises=ClientError) +def test_cancel_job_surfaces_exception(quantum_job, aws_session): + exception_response = { + "Error": { + "Code": "ValidationException", + "Message": "unit-test-error", + } + } + aws_session.cancel_job.side_effect = ClientError(exception_response, "cancel_job") + quantum_job.cancel() + + +@pytest.mark.parametrize( + "generate_get_job_response_kwargs", + [ + { + "status": "RUNNING", + }, + { + "status": "COMPLETED", + }, + { + "status": "COMPLETED", + "startedAt": datetime.datetime(2021, 1, 1, 1, 0, 0, 0), + }, + {"status": "COMPLETED", "endedAt": datetime.datetime(2021, 1, 1, 1, 0, 0, 0)}, + { + "status": "COMPLETED", + "startedAt": datetime.datetime(2021, 1, 1, 1, 0, 0, 0), + "endedAt": datetime.datetime(2021, 1, 1, 1, 0, 0, 0), + }, + ], +) +@patch( + "braket.jobs.metrics_data.cwl_insights_metrics_fetcher." + "CwlInsightsMetricsFetcher.get_metrics_for_job" +) +def test_metrics( + metrics_fetcher_mock, + quantum_job, + aws_session, + generate_get_job_response, + generate_get_job_response_kwargs, +): + get_job_response_running = generate_get_job_response(**generate_get_job_response_kwargs) + aws_session.get_job.return_value = get_job_response_running + + expected_metrics = {"Test": [1]} + metrics_fetcher_mock.return_value = expected_metrics + metrics = quantum_job.metrics() + assert metrics == expected_metrics + + +@pytest.fixture +def log_stream_responses(): + return ( + ClientError( + { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "This shouldn't get raised...", + } + }, + "DescribeLogStreams", + ), + {"logStreams": []}, + {"logStreams": [{"logStreamName": "stream-1"}]}, + ) + + +@pytest.fixture +def log_events_responses(): + return ( + {"nextForwardToken": None, "events": [{"timestamp": 1, "message": "hi there #1"}]}, + {"nextForwardToken": None, "events": []}, + { + "nextForwardToken": None, + "events": [ + {"timestamp": 1, "message": "hi there #1"}, + {"timestamp": 2, "message": "hi there #2"}, + ], + }, + {"nextForwardToken": None, "events": []}, + { + "nextForwardToken": None, + "events": [ + {"timestamp": 2, "message": "hi there #2"}, + {"timestamp": 2, "message": "hi there #2a"}, + {"timestamp": 3, "message": "hi there #3"}, + ], + }, + {"nextForwardToken": None, "events": []}, + ) + + +def test_logs( + quantum_job, + generate_get_job_response, + log_events_responses, + log_stream_responses, + 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"), + ) + 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( + ( + "..", + "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, + generate_get_job_response, + log_events_responses, + log_stream_responses, + capsys, +): + quantum_job._aws_session.get_job.side_effect = ( + generate_get_job_response(status="RUNNING", instanceConfig={"instanceCount": 2}), + generate_get_job_response(status="RUNNING"), + generate_get_job_response(status="RUNNING"), + generate_get_job_response(status="RUNNING"), + generate_get_job_response(status="COMPLETED"), + ) + log_stream_responses[-1]["logStreams"].append({"logStreamName": "stream-2"}) + quantum_job._aws_session.describe_log_streams.side_effect = log_stream_responses + + event_counts = { + "stream-1": 0, + "stream-2": 0, + } + + def get_log_events(log_group, log_stream, start_time, start_from_head, next_token): + log_events_dict = { + "stream-1": log_events_responses, + "stream-2": log_events_responses, + } + log_events_dict["stream-1"] += ( + { + "nextForwardToken": None, + "events": [], + }, + { + "nextForwardToken": None, + "events": [], + }, + ) + log_events_dict["stream-2"] += ( + { + "nextForwardToken": None, + "events": [ + {"timestamp": 3, "message": "hi there #3"}, + {"timestamp": 4, "message": "hi there #4"}, + ], + }, + { + "nextForwardToken": None, + "events": [], + }, + ) + event_counts[log_stream] += 1 + return log_events_dict[log_stream][event_counts[log_stream]] + + quantum_job._aws_session.get_log_events.side_effect = get_log_events + + 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", + "", + ) + ) + + +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="COMPLETED"), + ) + quantum_job._aws_session.describe_log_streams.side_effect = ( + ClientError( + { + "Error": { + "Code": "UnknownCode", + "Message": "Some error message", + } + }, + "DescribeLogStreams", + ), + ) + + with pytest.raises(ClientError, match="Some error message"): + quantum_job.logs(wait=True, poll_interval_seconds=0) + + +def test_initialize_session_for_valid_device(device_arn, aws_session, caplog): + first_region = aws_session.region + logger = logging.getLogger(__name__) + + aws_session.get_device.side_effect = [ + ClientError( + { + "Error": { + "Code": "ResourceNotFoundException", + } + }, + "getDevice", + ), + ClientError( + { + "Error": { + "Code": "ResourceNotFoundException", + } + }, + "getDevice", + ), + device_arn, + ] + + caplog.set_level(logging.INFO) + AwsQuantumJob._initialize_session(aws_session, device_arn, logger) + + assert f"Changed session region from '{first_region}' to '{aws_session.region}'" in caplog.text + + +def test_initialize_session_for_invalid_device(aws_session, device_arn): + logger = logging.getLogger(__name__) + aws_session.get_device.side_effect = ClientError( + { + "Error": { + "Code": "ResourceNotFoundException", + } + }, + "getDevice", + ) + + device_not_found = "QPU 'arn:aws:braket:::device/qpu/test/device-name' not found." + with pytest.raises(ValueError, match=device_not_found): + AwsQuantumJob._initialize_session(aws_session, device_arn, logger) + + +def test_no_region_routing_simulator(aws_session): + logger = logging.getLogger(__name__) + + aws_session.get_device.side_effect = ClientError( + { + "Error": { + "Code": "ResourceNotFoundException", + } + }, + "getDevice", + ) + + device_arn = "arn:aws:braket:::device/simulator/test/device-name" + device_not_found = f"Simulator '{device_arn}' not found in 'us-test-1'" + with pytest.raises(ValueError, match=device_not_found): + AwsQuantumJob._initialize_session(aws_session, device_arn, logger) + + +def test_exception_in_credentials_session_region(device_arn, aws_session): + logger = logging.getLogger(__name__) + + aws_session.get_device.side_effect = ClientError( + { + "Error": { + "Code": "SomeOtherErrorMessage", + } + }, + "getDevice", + ) + + error_message = ( + "An error occurred \\(SomeOtherErrorMessage\\) " + "when calling the getDevice operation: Unknown" + ) + with pytest.raises(ClientError, match=error_message): + AwsQuantumJob._initialize_session(aws_session, device_arn, logger) + + +def test_exceptions_in_all_device_regions(device_arn, aws_session): + logger = logging.getLogger(__name__) + + aws_session.get_device.side_effect = [ + ClientError( + { + "Error": { + "Code": "ResourceNotFoundException", + } + }, + "getDevice", + ), + ClientError( + { + "Error": { + "Code": "SomeOtherErrorMessage", + } + }, + "getDevice", + ), + ] + + error_message = ( + "An error occurred \\(SomeOtherErrorMessage\\) " + "when calling the getDevice operation: Unknown" + ) + with pytest.raises(ClientError, match=error_message): + AwsQuantumJob._initialize_session(aws_session, device_arn, logger) diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index 8735690c..5032eed2 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -12,10 +12,16 @@ # language governing permissions and limitations under the License. import json +import os +import tempfile +import time +from pathlib import Path from unittest.mock import MagicMock, Mock, patch +import boto3 import pytest from botocore.exceptions import ClientError +from botocore.stub import Stubber import braket._schemas as braket_schemas import braket._sdk as braket_sdk @@ -36,19 +42,118 @@ def boto_session(): @pytest.fixture -def aws_session(boto_session): - return AwsSession(boto_session=boto_session, braket_client=Mock()) +def braket_client(): + _braket_client = Mock() + _braket_client.meta.region_name = "us-west-2" + return _braket_client + + +@pytest.fixture +def aws_session(boto_session, braket_client, account_id): + _aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) + + _aws_session._sts = Mock() + _aws_session._sts.get_caller_identity.return_value = { + "Account": account_id, + } + + _aws_session._s3 = Mock() + return _aws_session + + +@pytest.fixture +def aws_explicit_session(): + _boto_session = Mock() + _boto_session.region_name = "us-test-1" + + creds = Mock() + creds.access_key = "access key" + creds.secret_key = "secret key" + creds.token = "token" + creds.method = "explicit" + _boto_session.get_credentials.return_value = creds + + _aws_session = Mock() + _aws_session.boto_session = _boto_session + _aws_session._default_bucket = "amazon-braket-us-test-1-00000000" + _aws_session.default_bucket.return_value = _aws_session._default_bucket + _aws_session._custom_default_bucket = False + _aws_session.account_id = "00000000" + _aws_session.region = "us-test-1" + return _aws_session + + +@pytest.fixture +def account_id(): + return "000000000" + + +@pytest.fixture +def job_role_name(): + return "AmazonBraketJobsExecutionRole-134534514345" + + +@pytest.fixture +def job_role_arn(job_role_name): + return f"arn:aws:iam::0000000000:role/{job_role_name}" + + +@pytest.fixture +def get_job_response(): + return { + "algorithmSpecification": { + "scriptModeConfig": { + "entryPoint": "my_file:start_here", + "s3Uri": "s3://amazon-braket-jobs/job-path/my_file.py", + } + }, + "checkpointConfig": { + "localPath": "/opt/omega/checkpoints", + "s3Uri": "s3://amazon-braket-jobs/job-path/checkpoints", + }, + "instanceConfig": { + "instanceCount": 1, + "instanceType": "ml.m5.large", + "volumeSizeInGb": 1, + }, + "jobArn": "arn:aws:braket:us-west-2:875981177017:job/job-test-20210628140446", + "jobName": "job-test-20210628140446", + "outputDataConfig": {"s3Path": "s3://amazon-braket-jobs/job-path/output"}, + "roleArn": "arn:aws:iam::875981177017:role/AmazonBraketJobRole", + "status": "RUNNING", + } + + +@pytest.fixture +def resource_not_found_response(): + return { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "unit-test-error", + } + } + + +@pytest.fixture +def throttling_response(): + return { + "Error": { + "Code": "ThrottlingException", + "Message": "unit-test-error", + } + } def test_initializes_boto_client_if_required(boto_session): AwsSession(boto_session=boto_session) - boto_session.client.assert_called_with("braket", config=None) + boto_session.client.assert_any_call("braket", config=None) -def test_uses_supplied_braket_client(): +def test_user_supplied_braket_client(): boto_session = Mock() boto_session.region_name = "foobar" braket_client = Mock() + braket_client.meta.region_name = "foobar" aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) assert aws_session.braket_client == braket_client @@ -56,7 +161,85 @@ def test_uses_supplied_braket_client(): def test_config(boto_session): config = Mock() AwsSession(boto_session=boto_session, config=config) - boto_session.client.assert_called_with("braket", config=config) + boto_session.client.assert_any_call("braket", config=config) + + +def test_region(): + boto_region = "boto-region" + braket_region = "braket-region" + + boto_session = Mock() + boto_session.region_name = boto_region + braket_client = Mock() + braket_client.meta.region_name = braket_region + + assert ( + AwsSession( + boto_session=boto_session, + ).region + == boto_region + ) + + assert ( + AwsSession( + braket_client=braket_client, + ).region + == braket_region + ) + + regions_must_match = ( + "Boto Session region and Braket Client region must match and currently " + "they do not: Boto Session region is 'boto-region', but " + "Braket Client region is 'braket-region'." + ) + with pytest.raises(ValueError, match=regions_must_match): + AwsSession( + boto_session=boto_session, + braket_client=braket_client, + ) + + +def test_iam(aws_session): + aws_session._iam = Mock() + assert aws_session.iam_client + aws_session.boto_session.client.assert_not_called() + aws_session._iam = None + assert aws_session.iam_client + aws_session.boto_session.client.assert_called_with("iam", region_name="us-west-2") + + +def test_s3(aws_session): + assert aws_session.s3_client + aws_session.boto_session.client.assert_not_called() + aws_session._s3 = None + assert aws_session.s3_client + aws_session.boto_session.client.assert_called_with("s3", region_name="us-west-2") + + +def test_sts(aws_session): + assert aws_session.sts_client + aws_session.boto_session.client.assert_not_called() + aws_session._sts = None + assert aws_session.sts_client + aws_session.boto_session.client.assert_called_with("sts", region_name="us-west-2") + + +def test_logs(aws_session): + aws_session._logs = Mock() + assert aws_session.logs_client + aws_session.boto_session.client.assert_not_called() + aws_session._logs = None + assert aws_session.logs_client + aws_session.boto_session.client.assert_called_with("logs", region_name="us-west-2") + + +def test_ecr(aws_session): + aws_session._ecr = Mock() + assert aws_session.ecr_client + aws_session.boto_session.client.assert_not_called() + aws_session._ecr = None + assert aws_session.ecr_client + aws_session.boto_session.client.assert_called_with("ecr", region_name="us-west-2") @patch("os.path.exists") @@ -75,6 +258,7 @@ def test_populates_user_agent(os_path_exists_mock, metadata_file_exists, initial boto_session = Mock() boto_session.region_name = "foobar" braket_client = Mock() + braket_client.meta.region_name = "foobar" braket_client._client_config.user_agent = initial_user_agent nbi_metadata_path = "/opt/ml/metadata/resource-metadata.json" os_path_exists_mock.return_value = metadata_file_exists @@ -131,8 +315,7 @@ def test_retrieve_s3_object_body_client_error(boto_session): aws_session.retrieve_s3_object_body(bucket_name, filename) -def test_get_device(boto_session): - braket_client = Mock() +def test_get_device(boto_session, braket_client): return_val = {"deviceArn": "arn1", "deviceName": "name1"} braket_client.get_device.return_value = return_val aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) @@ -155,13 +338,30 @@ def test_create_quantum_task(aws_session): kwargs = { "backendArn": "arn:aws:us-west-2:abc:xyz:abc", "cwLogGroupArn": "arn:aws:us-west-2:abc:xyz:abc", - "destinationUrl": "http://s3-us-west-2.amazonaws.com/task-output-derebolt-1/output.json", + "destinationUrl": "http://s3-us-west-2.amazonaws.com/task-output-bar-1/output.json", "program": {"ir": '{"instructions":[]}', "qubitCount": 4}, } assert aws_session.create_quantum_task(**kwargs) == arn aws_session.braket_client.create_quantum_task.assert_called_with(**kwargs) +def test_create_quantum_task_with_job_token(aws_session): + arn = "arn:aws:braket:us-west-2:1234567890:task/task-name" + job_token = "arn:aws:braket:us-west-2:1234567890:job/job-name" + aws_session.braket_client.create_quantum_task.return_value = {"quantumTaskArn": arn} + + kwargs = { + "backendArn": "arn:aws:us-west-2:abc:xyz:abc", + "cwLogGroupArn": "arn:aws:us-west-2:abc:xyz:abc", + "destinationUrl": "http://s3-us-west-2.amazonaws.com/task-output-foo-1/output.json", + "program": {"ir": '{"instructions":[]}', "qubitCount": 4}, + } + with patch.dict(os.environ, {"AMZN_BRAKET_JOB_TOKEN": job_token}): + assert aws_session.create_quantum_task(**kwargs) == arn + kwargs.update({"jobToken": job_token}) + aws_session.braket_client.create_quantum_task.assert_called_with(**kwargs) + + def test_get_quantum_task(aws_session): arn = "foo:bar:arn" return_value = {"quantumTaskArn": arn} @@ -171,23 +371,10 @@ def test_get_quantum_task(aws_session): aws_session.braket_client.get_quantum_task.assert_called_with(quantumTaskArn=arn) -def test_get_quantum_task_retry(aws_session): +def test_get_quantum_task_retry(aws_session, throttling_response, resource_not_found_response): arn = "foo:bar:arn" return_value = {"quantumTaskArn": arn} - resource_not_found_response = { - "Error": { - "Code": "ResourceNotFoundException", - "Message": "unit-test-error", - } - } - throttling_response = { - "Error": { - "Code": "ThrottlingException", - "Message": "unit-test-error", - } - } - aws_session.braket_client.get_quantum_task.side_effect = [ ClientError(resource_not_found_response, "unit-test"), ClientError(throttling_response, "unit-test"), @@ -196,35 +383,81 @@ def test_get_quantum_task_retry(aws_session): assert aws_session.get_quantum_task(arn) == return_value aws_session.braket_client.get_quantum_task.assert_called_with(quantumTaskArn=arn) - aws_session.braket_client.get_quantum_task.call_count == 3 + assert aws_session.braket_client.get_quantum_task.call_count == 3 -def test_get_quantum_task_fail_after_retries(aws_session): - resource_not_found_response = { - "Error": { - "Code": "ResourceNotFoundException", - "Message": "unit-test-error", - } - } - throttling_response = { +def test_get_quantum_task_fail_after_retries( + aws_session, throttling_response, resource_not_found_response +): + aws_session.braket_client.get_quantum_task.side_effect = [ + ClientError(resource_not_found_response, "unit-test"), + ClientError(throttling_response, "unit-test"), + ClientError(throttling_response, "unit-test"), + ] + + with pytest.raises(ClientError): + aws_session.get_quantum_task("some-arn") + assert aws_session.braket_client.get_quantum_task.call_count == 3 + + +def test_get_quantum_task_does_not_retry_other_exceptions(aws_session): + exception_response = { "Error": { - "Code": "ThrottlingException", + "Code": "SomeOtherException", "Message": "unit-test-error", } } aws_session.braket_client.get_quantum_task.side_effect = [ + ClientError(exception_response, "unit-test"), + ] + + with pytest.raises(ClientError): + aws_session.get_quantum_task("some-arn") + assert aws_session.braket_client.get_quantum_task.call_count == 1 + + +def test_get_job(aws_session, get_job_response): + arn = "arn:aws:braket:us-west-2:1234567890:job/job-name" + aws_session.braket_client.get_job.return_value = get_job_response + + assert aws_session.get_job(arn) == get_job_response + aws_session.braket_client.get_job.assert_called_with(jobArn=arn) + + +def test_get_job_retry( + aws_session, get_job_response, throttling_response, resource_not_found_response +): + arn = "arn:aws:braket:us-west-2:1234567890:job/job-name" + + aws_session.braket_client.get_job.side_effect = [ + ClientError(resource_not_found_response, "unit-test"), + ClientError(throttling_response, "unit-test"), + get_job_response, + ] + + assert aws_session.get_job(arn) == get_job_response + aws_session.braket_client.get_job.assert_called_with(jobArn=arn) + assert aws_session.braket_client.get_job.call_count == 3 + + +def test_get_job_fail_after_retries(aws_session, throttling_response, resource_not_found_response): + arn = "arn:aws:braket:us-west-2:1234567890:job/job-name" + + aws_session.braket_client.get_job.side_effect = [ ClientError(resource_not_found_response, "unit-test"), ClientError(throttling_response, "unit-test"), ClientError(throttling_response, "unit-test"), ] with pytest.raises(ClientError): - aws_session.get_quantum_task("some-arn") - aws_session.braket_client.get_quantum_task.call_count == 3 + aws_session.get_job(arn) + aws_session.braket_client.get_job.assert_called_with(jobArn=arn) + assert aws_session.braket_client.get_job.call_count == 3 -def test_get_quantum_task_does_not_retry_other_exceptions(aws_session): +def test_get_job_does_not_retry_other_exceptions(aws_session): + arn = "arn:aws:braket:us-west-2:1234567890:job/job-name" exception_response = { "Error": { "Code": "SomeOtherException", @@ -232,13 +465,60 @@ def test_get_quantum_task_does_not_retry_other_exceptions(aws_session): } } - aws_session.braket_client.get_quantum_task.side_effect = [ + aws_session.braket_client.get_job.side_effect = [ ClientError(exception_response, "unit-test"), ] with pytest.raises(ClientError): - aws_session.get_quantum_task("some-arn") - aws_session.braket_client.get_quantum_task.call_count == 1 + aws_session.get_job(arn) + aws_session.braket_client.get_job.assert_called_with(jobArn=arn) + assert aws_session.braket_client.get_job.call_count == 1 + + +def test_cancel_job(aws_session): + arn = "arn:aws:braket:us-west-2:1234567890:job/job-name" + cancel_job_response = { + "ResponseMetadata": { + "RequestId": "857b0893-2073-4ad6-b828-744af8400dfe", + "HTTPStatusCode": 200, + }, + "cancellationStatus": "CANCELLING", + "jobArn": "arn:aws:braket:us-west-2:1234567890:job/job-name", + } + aws_session.braket_client.cancel_job.return_value = cancel_job_response + + assert aws_session.cancel_job(arn) == cancel_job_response + aws_session.braket_client.cancel_job.assert_called_with(jobArn=arn) + + +@pytest.mark.parametrize( + "exception_type", + [ + "ResourceNotFoundException", + "ValidationException", + "AccessDeniedException", + "ThrottlingException", + "InternalServiceException", + "ConflictException", + ], +) +def test_cancel_job_surfaces_errors(exception_type, aws_session): + arn = "arn:aws:braket:us-west-2:1234567890:job/job-name" + exception_response = { + "Error": { + "Code": "SomeOtherException", + "Message": "unit-test-error", + } + } + + aws_session.braket_client.cancel_job.side_effect = [ + ClientError(exception_response, "unit-test"), + ] + + with pytest.raises(ClientError): + aws_session.cancel_job(arn) + aws_session.braket_client.cancel_job.assert_called_with(jobArn=arn) + assert aws_session.braket_client.cancel_job.call_count == 1 @pytest.mark.parametrize( @@ -431,3 +711,541 @@ def test_search_devices_arns(aws_session): ], PaginationConfig={"MaxItems": 100}, ) + + +def test_create_job(aws_session): + arn = "foo:bar:arn" + aws_session.braket_client.create_job.return_value = {"jobArn": arn} + + kwargs = { + "jobName": "job-name", + "roleArn": "role-arn", + "algorithmSpecification": { + "scriptModeConfig": { + "entryPoint": "entry-point", + "s3Uri": "s3-uri", + "compressionType": "GZIP", + } + }, + } + assert aws_session.create_job(**kwargs) == arn + aws_session.braket_client.create_job.assert_called_with(**kwargs) + + +@pytest.mark.parametrize( + "string, valid", + ( + ("s3://bucket/key", True), + ("S3://bucket/key", True), + ("https://bucket-name-123.s3.us-west-2.amazonaws.com/key/with/dirs", True), + ("https://bucket-name-123.S3.us-west-2.amazonaws.com/key/with/dirs", True), + ("https://bucket-name-123.S3.us-west-2.amazonaws.com/", False), + ("https://bucket-name-123.S3.us-west-2.amazonaws.com", False), + ("https://S3.us-west-2.amazonaws.com", False), + ("s3://bucket/", False), + ("s3://bucket", False), + ("s3://////", False), + ("http://bucket/key", False), + ("bucket/key", False), + ), +) +def test_is_s3_uri(string, valid): + assert AwsSession.is_s3_uri(string) == valid + + +@pytest.mark.parametrize( + "uri, bucket, key", + ( + ( + "s3://bucket-name-123/key/with/multiple/dirs", + "bucket-name-123", + "key/with/multiple/dirs", + ), + ( + "s3://bucket-name-123/key-with_one.dirs", + "bucket-name-123", + "key-with_one.dirs", + ), + ( + "https://bucket-name-123.s3.us-west-2.amazonaws.com/key/with/dirs", + "bucket-name-123", + "key/with/dirs", + ), + ( + "https://bucket-name-123.S3.us-west-2.amazonaws.com/key/with/dirs", + "bucket-name-123", + "key/with/dirs", + ), + ), +) +def test_parse_s3_uri(uri, bucket, key): + assert bucket, key == AwsSession.parse_s3_uri(uri) + + +@pytest.mark.parametrize( + "uri", + ( + "s3://bucket.name-123/key-with_one.dirs", + "http://bucket-name-123/key/with/multiple/dirs", + "bucket-name-123/key/with/multiple/dirs", + "s3://bucket-name-123/", + "s3://bucket-name-123", + ), +) +def test_parse_s3_uri_invalid(uri): + with pytest.raises(ValueError, match=f"Not a valid S3 uri: {uri}"): + AwsSession.parse_s3_uri(uri) + + +@pytest.mark.parametrize( + "bucket, dirs", + [ + ("bucket", ("d1", "d2", "d3")), + ("bucket-123-braket", ("dir",)), + pytest.param( + "braket", + (), + marks=pytest.mark.xfail(raises=ValueError, strict=True), + ), + ], +) +def test_construct_s3_uri(bucket, dirs): + parsed_bucket, parsed_key = AwsSession.parse_s3_uri(AwsSession.construct_s3_uri(bucket, *dirs)) + assert parsed_bucket == bucket + assert parsed_key == "/".join(dirs) + + +def test_get_default_jobs_role(aws_session, job_role_arn, job_role_name): + iam_client = boto3.client("iam") + with Stubber(iam_client) as stub: + stub.add_response( + "list_roles", + { + "Roles": [ + { + "Arn": "arn:aws:iam::0000000000:role/nonJobsRole", + "RoleName": "nonJobsRole", + "Path": "/", + "RoleId": "nonJobsRole-213453451345-431513", + "CreateDate": time.time(), + } + ] + * 100, + "IsTruncated": True, + "Marker": "resp-marker", + }, + ) + stub.add_response( + "list_roles", + { + "Roles": [ + { + "Arn": job_role_arn, + "RoleName": job_role_name, + "Path": "/", + "RoleId": f"{job_role_name}-213453451345-431513", + "CreateDate": time.time(), + } + ], + "IsTruncated": False, + }, + {"Marker": "resp-marker"}, + ) + aws_session._iam = iam_client + assert aws_session.get_default_jobs_role() == job_role_arn + + +def test_get_default_jobs_role_not_found(aws_session, job_role_arn, job_role_name): + iam_client = boto3.client("iam") + with Stubber(iam_client) as stub: + stub.add_response( + "list_roles", + { + "Roles": [ + { + "Arn": "arn:aws:iam::0000000000:role/nonJobsRole", + "RoleName": "nonJobsRole", + "Path": "/", + "RoleId": "nonJobsRole-213453451345-431513", + "CreateDate": time.time(), + } + ] + * 100, + "IsTruncated": True, + "Marker": "resp-marker", + }, + ) + stub.add_response( + "list_roles", + { + "Roles": [ + { + "Arn": "arn:aws:iam::0000000000:role/nonJobsRole2", + "RoleName": "nonJobsRole2", + "Path": "/", + "RoleId": "nonJobsRole2-213453451345-431513", + "CreateDate": time.time(), + } + ], + "IsTruncated": False, + }, + {"Marker": "resp-marker"}, + ) + aws_session._iam = iam_client + with pytest.raises(RuntimeError): + aws_session.get_default_jobs_role() + + +def test_upload_to_s3(aws_session): + filename = "file.txt" + s3_uri = "s3://bucket-123/key" + bucket, key = "bucket-123", "key" + aws_session.upload_to_s3(filename, s3_uri) + aws_session._s3.upload_file.assert_called_with(filename, bucket, key) + + +def test_upload_local_data(aws_session): + with tempfile.TemporaryDirectory() as temp_dir: + os.chdir(temp_dir) + + Path("input-dir", "pref-dir", "sub-pref-dir").mkdir(parents=True) + Path("input-dir", "not-pref-dir").mkdir() + + # these should all get uploaded + Path("input-dir", "pref-dir", "sub-pref-dir", "very-nested.txt").touch() + Path("input-dir", "pref-dir", "nested.txt").touch() + Path("input-dir", "pref.txt").touch() + Path("input-dir", "pref-and-more.txt").touch() + + # these should not + Path("input-dir", "false-pref.txt").touch() + Path("input-dir", "not-pref-dir", "pref-fake.txt").touch() + + aws_session.upload_to_s3 = Mock() + aws_session.upload_local_data("input-dir/pref", "s3://bucket/pref") + call_args = {args for args, kwargs in aws_session.upload_to_s3.call_args_list} + assert call_args == { + ( + str(Path("input-dir", "pref-dir", "sub-pref-dir", "very-nested.txt")), + "s3://bucket/pref-dir/sub-pref-dir/very-nested.txt", + ), + (str(Path("input-dir", "pref-dir", "nested.txt")), "s3://bucket/pref-dir/nested.txt"), + (str(Path("input-dir", "pref.txt")), "s3://bucket/pref.txt"), + (str(Path("input-dir", "pref-and-more.txt")), "s3://bucket/pref-and-more.txt"), + } + os.chdir("..") + + +def test_upload_local_data_absolute(aws_session): + with tempfile.TemporaryDirectory() 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() + + # these should all get uploaded + Path(temp_dir, "input-dir", "pref-dir", "sub-pref-dir", "very-nested.txt").touch() + Path(temp_dir, "input-dir", "pref-dir", "nested.txt").touch() + Path(temp_dir, "input-dir", "pref.txt").touch() + Path(temp_dir, "input-dir", "pref-and-more.txt").touch() + + # these should not + Path(temp_dir, "input-dir", "false-pref.txt").touch() + Path(temp_dir, "input-dir", "not-pref-dir", "pref-fake.txt").touch() + + aws_session.upload_to_s3 = Mock() + aws_session.upload_local_data(str(Path(temp_dir, "input-dir", "pref")), "s3://bucket/pref") + call_args = {args for args, kwargs in aws_session.upload_to_s3.call_args_list} + assert call_args == { + ( + str(Path(temp_dir, "input-dir", "pref-dir", "sub-pref-dir", "very-nested.txt")), + "s3://bucket/pref-dir/sub-pref-dir/very-nested.txt", + ), + ( + str(Path(temp_dir, "input-dir", "pref-dir", "nested.txt")), + "s3://bucket/pref-dir/nested.txt", + ), + (str(Path(temp_dir, "input-dir", "pref.txt")), "s3://bucket/pref.txt"), + ( + str(Path(temp_dir, "input-dir", "pref-and-more.txt")), + "s3://bucket/pref-and-more.txt", + ), + } + + +def test_download_from_s3(aws_session): + filename = "model.tar.gz" + s3_uri = ( + "s3://amazon-braket-jobs/job-path/output/" + "BraketJob-875981177017-job-test-20210628140446/output/model.tar.gz" + ) + bucket, key = ( + "amazon-braket-jobs", + "job-path/output/BraketJob-875981177017-job-test-20210628140446/output/model.tar.gz", + ) + aws_session.download_from_s3(s3_uri, filename) + aws_session._s3.download_file.assert_called_with(bucket, key, filename) + + +def test_copy_identical_s3(aws_session): + s3_uri = "s3://bucket/key" + aws_session.copy_s3_object(s3_uri, s3_uri) + aws_session.boto_session.client.return_value.copy.assert_not_called() + + +def test_copy_s3(aws_session): + source_s3_uri = "s3://here/now" + dest_s3_uri = "s3://there/then" + source_bucket, source_key = AwsSession.parse_s3_uri(source_s3_uri) + dest_bucket, dest_key = AwsSession.parse_s3_uri(dest_s3_uri) + aws_session.copy_s3_object(source_s3_uri, dest_s3_uri) + aws_session._s3.copy.assert_called_with( + { + "Bucket": source_bucket, + "Key": source_key, + }, + dest_bucket, + dest_key, + ) + + +def test_copy_identical_s3_directory(aws_session): + s3_uri = "s3://bucket/prefix/" + aws_session.copy_s3_directory(s3_uri, s3_uri) + aws_session.boto_session.client.return_value.copy.assert_not_called() + + +def test_copy_s3_directory(aws_session): + aws_session.list_keys = Mock(return_value=[f"now/key-{i}" for i in range(5)]) + source_s3_uri = "s3://here/now" + dest_s3_uri = "s3://there/then" + aws_session.copy_s3_directory(source_s3_uri, dest_s3_uri) + for i in range(5): + aws_session.s3_client.copy.assert_any_call( + { + "Bucket": "here", + "Key": f"now/key-{i}", + }, + "there", + f"then/key-{i}", + ) + + +def test_list_keys(aws_session): + bucket, prefix = "bucket", "prefix" + aws_session.s3_client.list_objects_v2.side_effect = [ + { + "IsTruncated": True, + "Contents": [ + {"Key": "copy-test/copy.txt"}, + {"Key": "copy-test/copy2.txt"}, + ], + "NextContinuationToken": "next-continuation-token", + }, + { + "IsTruncated": False, + "Contents": [ + {"Key": "copy-test/nested/double-nested/double-nested.txt"}, + {"Key": "copy-test/nested/nested.txt"}, + ], + }, + ] + keys = aws_session.list_keys(bucket, prefix) + assert keys == [ + "copy-test/copy.txt", + "copy-test/copy2.txt", + "copy-test/nested/double-nested/double-nested.txt", + "copy-test/nested/nested.txt", + ] + + +def test_default_bucket(aws_session, account_id): + region = "test-region-0" + aws_session.boto_session.region_name = region + assert aws_session.default_bucket() == f"amazon-braket-{region}-{account_id}" + + +def test_default_bucket_given(aws_session): + default_bucket = "default_bucket" + aws_session._default_bucket = default_bucket + assert aws_session.default_bucket() == default_bucket + aws_session._s3.create_bucket.assert_not_called() + + +@patch.dict("os.environ", {"AMZN_BRAKET_OUT_S3_BUCKET": "default_bucket_env"}) +def test_default_bucket_env_variable(boto_session, braket_client): + aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) + assert aws_session.default_bucket() == "default_bucket_env" + + +@pytest.mark.parametrize( + "region", + ( + "test-region-0", + "us-east-1", + ), +) +def test_create_s3_bucket_if_it_does_not_exist(aws_session, region, account_id): + bucket = f"amazon-braket-{region}-{account_id}" + aws_session._create_s3_bucket_if_it_does_not_exist(bucket, region) + kwargs = { + "Bucket": bucket, + "CreateBucketConfiguration": { + "LocationConstraint": region, + }, + } + if region == "us-east-1": + del kwargs["CreateBucketConfiguration"] + aws_session._s3.create_bucket.assert_called_with(**kwargs) + aws_session._s3.put_public_access_block.assert_called_with( + Bucket=bucket, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": True, + "IgnorePublicAcls": True, + "BlockPublicPolicy": True, + "RestrictPublicBuckets": True, + }, + ) + + +@pytest.mark.parametrize( + "error", + ( + ClientError( + { + "Error": { + "Code": "BucketAlreadyOwnedByYou", + "Message": "Your previous request to create the named bucket succeeded " + "and you already own it.", + } + }, + "CreateBucket", + ), + ClientError( + { + "Error": { + "Code": "OperationAborted", + "Message": "A conflicting conditional operation is currently in progress " + "against this resource. Please try again.", + } + }, + "CreateBucket", + ), + pytest.param( + ClientError( + { + "Error": { + "Code": "OtherCode", + "Message": "This should fail properly.", + } + }, + "CreateBucket", + ), + marks=pytest.mark.xfail(raises=ClientError, strict=True), + ), + ), +) +def test_create_s3_bucket_if_it_does_not_exist_error(aws_session, error, account_id): + region = "test-region-0" + bucket = f"amazon-braket-{region}-{account_id}" + aws_session._s3.create_bucket.side_effect = error + aws_session._create_s3_bucket_if_it_does_not_exist(bucket, region) + + +@pytest.mark.xfail(raises=ValueError) +def test_bucket_already_exists_for_another_account(aws_session): + exception_response = { + "Error": { + "Code": "BucketAlreadyExists", + "Message": "This should fail properly.", + } + } + bucket_name, region = "some-bucket-123", "test-region" + aws_session._s3.create_bucket.side_effect = ClientError(exception_response, "CreateBucket") + aws_session._create_s3_bucket_if_it_does_not_exist(bucket_name, region) + + +@pytest.mark.parametrize( + "limit, next_token", + ( + (None, None), + (10, None), + (None, "next-token"), + (10, "next-token"), + ), +) +def test_describe_log_streams(aws_session, limit, next_token): + aws_session._logs = Mock() + + log_group = "log_group" + log_stream_prefix = "log_stream_prefix" + + describe_log_stream_args = { + "logGroupName": log_group, + "logStreamNamePrefix": log_stream_prefix, + "orderBy": "LogStreamName", + } + + if limit: + describe_log_stream_args.update({"limit": limit}) + + if next_token: + describe_log_stream_args.update({"nextToken": next_token}) + + aws_session.describe_log_streams(log_group, log_stream_prefix, limit, next_token) + + aws_session._logs.describe_log_streams.assert_called_with(**describe_log_stream_args) + + +@pytest.mark.parametrize( + "next_token", + (None, "next-token"), +) +def test_get_log_events(aws_session, next_token): + aws_session._logs = Mock() + + log_group = "log_group" + log_stream_name = "log_stream_name" + start_time = "timestamp" + start_from_head = True + + log_events_args = { + "logGroupName": log_group, + "logStreamName": log_stream_name, + "startTime": start_time, + "startFromHead": start_from_head, + } + + if next_token: + log_events_args.update({"nextToken": next_token}) + + aws_session.get_log_events(log_group, log_stream_name, start_time, start_from_head, next_token) + + aws_session._logs.get_log_events.assert_called_with(**log_events_args) + + +@patch("boto3.Session") +def test_copy_session(boto_session_init, aws_session): + boto_session_init.return_value = Mock() + copied_session = AwsSession.copy_session(aws_session, "us-west-2") + boto_session_init.assert_called_with(region_name="us-west-2") + assert copied_session._default_bucket is None + + +@patch("boto3.Session") +def test_copy_explicit_session(boto_session_init, aws_explicit_session): + boto_session_init.return_value = Mock() + AwsSession.copy_session(aws_explicit_session, "us-west-2") + boto_session_init.assert_called_with( + aws_access_key_id="access key", + aws_secret_access_key="secret key", + aws_session_token="token", + region_name="us-west-2", + ) + + +@patch("boto3.Session") +def test_copy_session_custom_default_bucket(mock_boto, aws_session): + mock_boto.return_value.region_name = "us-test-1" + aws_session._default_bucket = "my-own-default" + aws_session._custom_default_bucket = True + copied_session = AwsSession.copy_session(aws_session) + assert copied_session._default_bucket == "my-own-default" diff --git a/test/unit_tests/braket/jobs/local/test_local_job.py b/test/unit_tests/braket/jobs/local/test_local_job.py new file mode 100644 index 00000000..185b059e --- /dev/null +++ b/test/unit_tests/braket/jobs/local/test_local_job.py @@ -0,0 +1,215 @@ +# 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, mock_open, patch + +import pytest + +from braket.jobs.local.local_job import LocalQuantumJob + + +@pytest.fixture +def aws_session(): + _aws_session = Mock() + return _aws_session + + +@pytest.fixture +def job_results(): + return {"dataFormat": "plaintext", "dataDictionary": {"some_results": {"excellent": "here"}}} + + +@pytest.fixture +def run_log(): + test_log = ( + "This is a multi-line log.\n" + "This is the next line.\n" + "Metrics - timestamp=1633027264.5406773; Cost=-4.034; iteration_number=0;\n" + "Metrics - timestamp=1633027288.6284382; Cost=-3.957; iteration_number=1;\n" + ) + return test_log + + +@pytest.fixture +def test_envs(): + return {"Test": "Env"} + + +@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": {}, + } + ), + ], +) +@patch("braket.jobs.local.local_job.prepare_quantum_job") +@patch("braket.jobs.local.local_job.retrieve_image") +@patch("braket.jobs.local.local_job.setup_container") +@patch("braket.jobs.local.local_job._LocalJobContainer") +@patch("os.path.isdir") +def test_create( + mock_dir, + mock_container, + mock_setup, + mock_retrieve_image, + mock_prepare_job, + aws_session, + creation_kwargs, + job_results, + run_log, + test_envs, +): + with patch("builtins.open", mock_open()) as file_open: + mock_dir.return_value = False + mock_prepare_job.return_value = creation_kwargs + + mock_container_open = mock_container.return_value.__enter__.return_value + mock_container_open.run_log = run_log + file_read = file_open() + file_read.read.return_value = json.dumps(job_results) + mock_setup.return_value = test_envs + + job = LocalQuantumJob.create( + device=Mock(), + source_module=Mock(), + entry_point=Mock(), + image_uri=Mock(), + job_name=Mock(), + code_location=Mock(), + role_arn=Mock(), + hyperparameters=Mock(), + input_data=Mock(), + output_data_config=Mock(), + checkpoint_config=Mock(), + aws_session=aws_session, + ) + assert job.name == "Test-Job-Name" + assert job.arn == "local:job/Test-Job-Name" + assert job.state() == "COMPLETED" + assert job.run_log == run_log + assert job.metadata() is None + assert job.cancel() is None + assert job.download_result() is None + assert job.logs() is None + assert job.result() == job_results["dataDictionary"] + assert job.metrics() == { + "Cost": [-4.034, -3.957], + "iteration_number": [0.0, 1.0], + "timestamp": [1633027264.5406773, 1633027288.6284382], + } + mock_setup.assert_called_with(mock_container_open, aws_session, **creation_kwargs) + mock_container_open.run_local_job.assert_called_with(test_envs) + + +def test_create_invalid_arg(): + unexpected_kwarg = "create\\(\\) got an unexpected keyword argument 'wait_until_complete'" + with pytest.raises(TypeError, match=unexpected_kwarg): + LocalQuantumJob.create( + device="device", + source_module="source", + wait_until_complete=True, + ) + + +@patch("os.path.isdir") +def test_read_runlog_file(mock_dir): + mock_dir.return_value = True + with patch("builtins.open", mock_open()) as file_open: + file_read = file_open() + file_read.read.return_value = "Test Log" + job = LocalQuantumJob("local:job/Fake-Job") + assert job.run_log == "Test Log" + + +@patch("braket.jobs.local.local_job.prepare_quantum_job") +@patch("os.path.isdir") +def test_create_existing_job(mock_dir, mock_prepare_job, aws_session): + mock_dir.return_value = True + mock_prepare_job.return_value = { + "jobName": "Test-Job-Name", + "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, + "checkpointConfig": {"localPath": "test/local/path/"}, + } + dir_already_exists = ( + "A local directory called Test-Job-Name already exists. Please use a different job name." + ) + with pytest.raises(ValueError, match=dir_already_exists): + LocalQuantumJob.create( + device=Mock(), + source_module=Mock(), + entry_point=Mock(), + image_uri=Mock(), + job_name=Mock(), + code_location=Mock(), + role_arn=Mock(), + hyperparameters=Mock(), + input_data=Mock(), + output_data_config=Mock(), + checkpoint_config=Mock(), + aws_session=aws_session, + ) + + +def test_invalid_arn(): + invalid_arn = "Arn Invalid-Arn is not a valid local job arn" + with pytest.raises(ValueError, match=invalid_arn): + LocalQuantumJob("Invalid-Arn") + + +def test_missing_job_dir(): + missing_dir = "Unable to find local job results for Missing-Dir" + with pytest.raises(ValueError, match=missing_dir): + LocalQuantumJob("local:job/Missing-Dir") + + +@patch("os.path.isdir") +def test_missing_runlog_file(mock_dir): + mock_dir.return_value = True + job = LocalQuantumJob("local:job/Fake-Dir") + no_file = "Unable to find logs in the local job directory Fake-Dir." + with pytest.raises(ValueError, match=no_file): + job.run_log + + +@patch("os.path.isdir") +def test_missing_results_file(mock_dir): + mock_dir.return_value = True + job = LocalQuantumJob("local:job/Fake-Dir") + no_results = "Unable to find results in the local job directory Fake-Dir." + with pytest.raises(ValueError, match=no_results): + job.result() 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 new file mode 100644 index 00000000..34b7a385 --- /dev/null +++ b/test/unit_tests/braket/jobs/local/test_local_job_container.py @@ -0,0 +1,364 @@ +# 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 base64 +import subprocess +from pathlib import Path +from unittest.mock import Mock, patch + +import pytest + +from braket.jobs.local.local_job_container import _LocalJobContainer + + +@pytest.fixture +def repo_uri(): + return "012345678901.dkr.ecr.us-west-2.amazonaws.com" + + +@pytest.fixture +def image_uri(repo_uri): + return f"{repo_uri}/my-repo:my-tag" + + +@pytest.fixture +def aws_session(): + _aws_session = Mock() + return _aws_session + + +@patch("subprocess.check_output") +@patch("subprocess.run") +def test_start_and_stop(mock_run, mock_check_output, image_uri, aws_session): + local_image_name = "LocalImageName" + running_container_name = "RunningContainer" + mock_check_output.side_effect = [ + str.encode(local_image_name), + str.encode(running_container_name), + ] + 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"] + ) + assert mock_check_output.call_count == 2 + mock_run.assert_any_call(["docker", "stop", running_container_name]) + assert mock_run.call_count == 1 + + +@patch("subprocess.check_output") +@patch("subprocess.run") +def test_pull_container(mock_run, mock_check_output, repo_uri, image_uri, aws_session): + local_image_name = "LocalImageName" + running_container_name = "RunningContainer" + test_token = "Test Token" + mock_check_output.side_effect = [ + str.encode(""), + str.encode(local_image_name), + str.encode(running_container_name), + ] + aws_session.ecr_client.get_authorization_token.return_value = { + "authorizationData": [{"authorizationToken": base64.b64encode(str.encode(test_token))}] + } + 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"] + ) + assert mock_check_output.call_count == 3 + mock_run.assert_any_call(["docker", "login", "-u", "AWS", "-p", test_token, repo_uri]) + mock_run.assert_any_call(["docker", "pull", image_uri]) + mock_run.assert_any_call(["docker", "stop", running_container_name]) + assert mock_run.call_count == 3 + + +@patch("subprocess.check_output") +@patch("subprocess.run") +def test_run_job_success(mock_run, mock_check_output, repo_uri, image_uri, aws_session): + local_image_name = "LocalImageName" + running_container_name = "RunningContainer" + env_variables = { + "ENV0": "VALUE0", + "ENV1": "VALUE1", + } + run_program_name = "Run Program Name" + expected_run_output = "Expected Run Output" + mock_check_output.side_effect = [ + str.encode(local_image_name), + str.encode(running_container_name), + str.encode(run_program_name), + str.encode(expected_run_output), + ] + with _LocalJobContainer(image_uri, aws_session) as container: + container.run_local_job(env_variables) + run_output = container.run_log + assert run_output == expected_run_output + 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", + "exec", + "-w", + "/opt/ml/code/", + "-e", + "ENV0=VALUE0", + "-e", + "ENV1=VALUE1", + running_container_name, + "python", + run_program_name, + ] + ) + assert mock_check_output.call_count == 4 + mock_run.assert_any_call(["docker", "stop", running_container_name]) + assert mock_run.call_count == 1 + + +@patch("subprocess.check_output") +@patch("subprocess.run") +def test_customer_script_fails(mock_run, mock_check_output, repo_uri, image_uri, aws_session): + local_image_name = "LocalImageName" + running_container_name = "RunningContainer" + env_variables = { + "ENV0": "VALUE0", + "ENV1": "VALUE1", + } + run_program_name = "Run Program Name" + expected_error_output = "Expected Error Output" + mock_check_output.side_effect = [ + str.encode(local_image_name), + str.encode(running_container_name), + str.encode(run_program_name), + subprocess.CalledProcessError("Test Error", "test", str.encode(expected_error_output)), + ] + with _LocalJobContainer(image_uri, aws_session) as container: + container.run_local_job(env_variables) + run_output = container.run_log + assert run_output == expected_error_output + assert mock_check_output.call_count == 4 + mock_run.assert_any_call(["docker", "stop", running_container_name]) + assert mock_run.call_count == 1 + + +@patch("subprocess.check_output") +@patch("subprocess.run") +def test_make_dir(mock_run, mock_check_output, repo_uri, image_uri, aws_session): + local_image_name = "LocalImageName" + running_container_name = "RunningContainer" + test_dir_path = "/test/dir/path" + mock_check_output.side_effect = [ + str.encode(local_image_name), + str.encode(running_container_name), + str.encode(""), + ] + 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] + ) + assert mock_check_output.call_count == 3 + mock_run.assert_any_call(["docker", "stop", running_container_name]) + assert mock_run.call_count == 1 + + +@patch("subprocess.check_output") +@patch("subprocess.run") +def test_copy_to(mock_run, mock_check_output, repo_uri, image_uri, aws_session): + local_image_name = "LocalImageName" + running_container_name = "RunningContainer" + source_path = str(Path("test", "source", "dir", "path", "srcfile.txt")) + dest_path = str(Path("test", "dest", "dir", "path", "dstfile.txt")) + mock_check_output.side_effect = [ + str.encode(local_image_name), + str.encode(running_container_name), + str.encode(""), + str.encode(""), + ] + 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(Path("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 + + +@patch("subprocess.check_output") +@patch("subprocess.run") +def test_copy_from(mock_run, mock_check_output, repo_uri, image_uri, aws_session): + local_image_name = "LocalImageName" + running_container_name = "RunningContainer" + source_path = "/test/source/dir/path/srcfile.txt" + dest_path = "/test/dest/dir/path/dstfile.txt" + mock_check_output.side_effect = [ + str.encode(local_image_name), + str.encode(running_container_name), + str.encode(""), + str.encode(""), + ] + 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] + ) + assert mock_check_output.call_count == 3 + mock_run.assert_any_call(["docker", "stop", running_container_name]) + assert mock_run.call_count == 1 + + +@patch("subprocess.check_output") +@patch("subprocess.run") +@pytest.mark.xfail(raises=ValueError) +def test_run_fails_no_program(mock_run, mock_check_output, repo_uri, image_uri, aws_session): + local_image_name = "LocalImageName" + running_container_name = "RunningContainer" + env_variables = { + "ENV0": "VALUE0", + "ENV1": "VALUE1", + } + mock_check_output.side_effect = [ + str.encode(local_image_name), + str.encode(running_container_name), + str.encode(""), + ] + with _LocalJobContainer(image_uri, aws_session) as container: + container.run_local_job(env_variables) + + +@patch("subprocess.check_output") +@patch("subprocess.run") +@pytest.mark.xfail(raises=subprocess.CalledProcessError) +def test_make_dir_fails(mock_run, mock_check_output, repo_uri, image_uri, aws_session): + local_image_name = "LocalImageName" + running_container_name = "RunningContainer" + test_dir_path = "/test/dir/path" + mock_check_output.side_effect = [ + str.encode(local_image_name), + str.encode(running_container_name), + subprocess.CalledProcessError("Test Error", "test", str.encode("test output")), + ] + with _LocalJobContainer(image_uri, aws_session) as container: + container.makedir(test_dir_path) + + +@patch("subprocess.check_output") +@patch("subprocess.run") +@pytest.mark.xfail(raises=subprocess.CalledProcessError) +def test_copy_to_fails(mock_run, mock_check_output, repo_uri, image_uri, aws_session): + local_image_name = "LocalImageName" + running_container_name = "RunningContainer" + source_path = "/test/source/dir/path/srcfile.txt" + dest_path = "/test/dest/dir/path/dstfile.txt" + mock_check_output.side_effect = [ + str.encode(local_image_name), + str.encode(running_container_name), + subprocess.CalledProcessError("Test Error", "test", str.encode("test output")), + ] + with _LocalJobContainer(image_uri, aws_session) as container: + container.copy_to(source_path, dest_path) + + +@patch("subprocess.check_output") +@patch("subprocess.run") +@pytest.mark.xfail(raises=subprocess.CalledProcessError) +def test_copy_from_fails(mock_run, mock_check_output, repo_uri, image_uri, aws_session): + local_image_name = "LocalImageName" + running_container_name = "RunningContainer" + source_path = "/test/source/dir/path/srcfile.txt" + dest_path = "/test/dest/dir/path/dstfile.txt" + mock_check_output.side_effect = [ + str.encode(local_image_name), + str.encode(running_container_name), + subprocess.CalledProcessError("Test Error", "test", str.encode("test output")), + ] + with _LocalJobContainer(image_uri, aws_session) as container: + container.copy_from(source_path, dest_path) + + +@patch("subprocess.check_output") +@patch("subprocess.run") +@pytest.mark.xfail(raises=ValueError) +def test_pull_fails_no_auth(mock_run, mock_check_output, repo_uri, image_uri, aws_session): + local_image_name = "LocalImageName" + running_container_name = "RunningContainer" + mock_check_output.side_effect = [ + str.encode(""), + str.encode(local_image_name), + str.encode(running_container_name), + ] + aws_session.ecr_client.get_authorization_token.return_value = {} + with _LocalJobContainer(image_uri, aws_session): + pass + + +@patch("subprocess.check_output") +@patch("subprocess.run") +@pytest.mark.xfail(raises=ValueError) +def test_pull_fails_invalid_uri(mock_run, mock_check_output, aws_session): + local_image_name = "LocalImageName" + running_container_name = "RunningContainer" + mock_check_output.side_effect = [ + str.encode(""), + str.encode(local_image_name), + str.encode(running_container_name), + ] + aws_session.ecr_client.get_authorization_token.return_value = {} + with _LocalJobContainer("TestURI", aws_session): + pass + + +@patch("subprocess.check_output") +@patch("subprocess.run") +@pytest.mark.xfail(raises=ValueError) +def test_pull_fails_unknown_reason(mock_run, mock_check_output, repo_uri, image_uri, aws_session): + test_token = "Test Token" + mock_check_output.side_effect = [ + str.encode(""), + str.encode(""), + ] + aws_session.ecr_client.get_authorization_token.return_value = { + "authorizationData": [{"authorizationToken": base64.b64encode(str.encode(test_token))}] + } + with _LocalJobContainer(image_uri, aws_session): + pass 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 new file mode 100644 index 00000000..93e114ef --- /dev/null +++ b/test/unit_tests/braket/jobs/local/test_local_job_container_setup.py @@ -0,0 +1,228 @@ +# 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 pathlib import Path +from unittest.mock import Mock, mock_open, patch + +import pytest + +from braket.jobs.local.local_job_container_setup import setup_container + + +@pytest.fixture +def aws_session(): + _aws_session = Mock() + _aws_session.boto_session.get_credentials.return_value.access_key = "Test Access Key" + _aws_session.boto_session.get_credentials.return_value.secret_key = "Test Secret Key" + _aws_session.boto_session.get_credentials.return_value.token = None + _aws_session.region = "Test Region" + _aws_session.list_keys.side_effect = lambda bucket, prefix: [ + key + for key in [ + "input-dir/", + "input-dir/file-1.txt", + "input-dir/file-2.txt", + ] + if key.startswith(prefix) + ] + return _aws_session + + +@pytest.fixture +def container(): + _container = Mock() + return _container + + +@pytest.fixture +def creation_kwargs(): + return { + "algorithmSpecification": { + "scriptModeConfig": { + "entryPoint": "my_file:start_here", + "s3Uri": "s3://amazon-braket-jobs/job-path/my_file.py", + } + }, + "checkpointConfig": { + "localPath": "/opt/omega/checkpoints", + "s3Uri": "s3://amazon-braket-jobs/job-path/checkpoints", + }, + "outputDataConfig": {"s3Path": "s3://test_bucket/test_location/"}, + "deviceConfig": {"device": "test device ARN"}, + "jobName": "Test-Job-Name", + "roleArn": "arn:aws:iam::875981177017:role/AmazonBraketJobRole", + } + + +@pytest.fixture +def compressed_script_mode_config(): + return { + "scriptModeConfig": { + "entryPoint": "my_file:start_here", + "s3Uri": "s3://amazon-braket-jobs/job-path/my_archive.gzip", + "compressionType": "gzip", + } + } + + +@pytest.fixture +def expected_envs(): + return { + "AMZN_BRAKET_CHECKPOINT_DIR": "/opt/omega/checkpoints", + "AMZN_BRAKET_DEVICE_ARN": "test device ARN", + "AMZN_BRAKET_IMAGE_SETUP_SCRIPT": "s3://amazon-braket-external-assets-preview-us-west-2/" + "HybridJobsAccess/scripts/setup-container.sh", + "AMZN_BRAKET_JOB_NAME": "Test-Job-Name", + "AMZN_BRAKET_JOB_RESULTS_DIR": "/opt/braket/model", + "AMZN_BRAKET_JOB_RESULTS_S3_PATH": "test_location/Test-Job-Name/output", + "AMZN_BRAKET_OUT_S3_BUCKET": "test_bucket", + "AMZN_BRAKET_SCRIPT_ENTRY_POINT": "my_file:start_here", + "AMZN_BRAKET_SCRIPT_S3_URI": "s3://amazon-braket-jobs/job-path/my_file.py", + "AMZN_BRAKET_TASK_RESULTS_S3_URI": "s3://test_bucket/jobs/Test-Job-Name/tasks", + "AWS_ACCESS_KEY_ID": "Test Access Key", + "AWS_DEFAULT_REGION": "Test Region", + "AWS_SECRET_ACCESS_KEY": "Test Secret Key", + } + + +@pytest.fixture +def input_data_config(): + return [ + # s3 prefix is a single file + { + "channelName": "single-file", + "dataSource": {"s3DataSource": {"s3Uri": "s3://input_bucket/input-dir/file-1.txt"}}, + }, + # s3 prefix is a directory no slash + { + "channelName": "directory-no-slash", + "dataSource": {"s3DataSource": {"s3Uri": "s3://input_bucket/input-dir"}}, + }, + # s3 prefix is a directory with slash + { + "channelName": "directory-slash", + "dataSource": {"s3DataSource": {"s3Uri": "s3://input_bucket/input-dir/"}}, + }, + # s3 prefix is a prefix for a directory + { + "channelName": "directory-prefix", + "dataSource": {"s3DataSource": {"s3Uri": "s3://input_bucket/input"}}, + }, + # s3 prefix is a prefix for multiple files + { + "channelName": "files-prefix", + "dataSource": {"s3DataSource": {"s3Uri": "s3://input_bucket/input-dir/file"}}, + }, + ] + + +def test_basic_setup(container, aws_session, creation_kwargs, expected_envs): + aws_session.parse_s3_uri.return_value = ["test_bucket", "test_location"] + envs = setup_container(container, aws_session, **creation_kwargs) + assert envs == expected_envs + container.makedir.assert_any_call("/opt/ml/model") + container.makedir.assert_any_call(expected_envs["AMZN_BRAKET_CHECKPOINT_DIR"]) + assert container.makedir.call_count == 2 + + +def test_compressed_script_mode( + container, aws_session, creation_kwargs, expected_envs, compressed_script_mode_config +): + creation_kwargs["algorithmSpecification"] = compressed_script_mode_config + expected_envs["AMZN_BRAKET_SCRIPT_S3_URI"] = "s3://amazon-braket-jobs/job-path/my_archive.gzip" + expected_envs["AMZN_BRAKET_SCRIPT_COMPRESSION_TYPE"] = "gzip" + aws_session.parse_s3_uri.return_value = ["test_bucket", "test_location"] + envs = setup_container(container, aws_session, **creation_kwargs) + assert envs == expected_envs + container.makedir.assert_any_call("/opt/ml/model") + container.makedir.assert_any_call(expected_envs["AMZN_BRAKET_CHECKPOINT_DIR"]) + assert container.makedir.call_count == 2 + + +@patch("json.dump") +@patch("tempfile.TemporaryDirectory") +def test_hyperparameters(tempfile, json, container, aws_session, creation_kwargs, expected_envs): + with patch("builtins.open", mock_open()): + tempfile.return_value.__enter__.return_value = "temporaryDir" + creation_kwargs["hyperParameters"] = {"test": "hyper"} + expected_envs["AMZN_BRAKET_HP_FILE"] = "/opt/braket/input/config/hyperparameters.json" + aws_session.parse_s3_uri.return_value = ["test_bucket", "test_location"] + envs = setup_container(container, aws_session, **creation_kwargs) + assert envs == expected_envs + container.makedir.assert_any_call("/opt/ml/model") + container.makedir.assert_any_call(expected_envs["AMZN_BRAKET_CHECKPOINT_DIR"]) + assert container.makedir.call_count == 2 + container.copy_to.assert_called_with( + os.path.join("temporaryDir", "hyperparameters.json"), + "/opt/ml/input/config/hyperparameters.json", + ) + + +def test_input(container, aws_session, creation_kwargs, input_data_config): + creation_kwargs.update({"inputDataConfig": input_data_config}) + setup_container(container, aws_session, **creation_kwargs) + download_locations = [call[0][1] for call in aws_session.download_from_s3.call_args_list] + expected_downloads = [ + Path("single-file", "file-1.txt"), + Path("directory-no-slash", "file-1.txt"), + Path("directory-no-slash", "file-2.txt"), + Path("directory-slash", "file-1.txt"), + Path("directory-slash", "file-2.txt"), + Path("directory-prefix", "input-dir", "file-1.txt"), + Path("directory-prefix", "input-dir", "file-2.txt"), + Path("files-prefix", "file-1.txt"), + Path("files-prefix", "file-2.txt"), + ] + + for download, expected_download in zip(download_locations, expected_downloads): + assert download.endswith(str(expected_download)) + + +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"}}, + } + ) + 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): + setup_container(container, aws_session, **creation_kwargs) + + +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"}}, + } + ) + 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): + setup_container(container, aws_session, **creation_kwargs) + + +def test_temporary_credentials(container, aws_session, creation_kwargs, expected_envs): + aws_session.boto_session.get_credentials.return_value.token = "Test Token" + expected_envs["AWS_SESSION_TOKEN"] = "Test Token" + aws_session.parse_s3_uri.return_value = ["test_bucket", "test_location"] + envs = setup_container(container, aws_session, **creation_kwargs) + assert envs == expected_envs + container.makedir.assert_any_call("/opt/ml/model") + container.makedir.assert_any_call(expected_envs["AMZN_BRAKET_CHECKPOINT_DIR"]) + assert container.makedir.call_count == 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 new file mode 100644 index 00000000..f0d42f52 --- /dev/null +++ b/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py @@ -0,0 +1,105 @@ +# 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 Mock, call, patch + +import pytest + +from braket.jobs.metrics_data import MetricsRetrievalError +from braket.jobs.metrics_data.cwl_insights_metrics_fetcher import CwlInsightsMetricsFetcher + + +@pytest.fixture +def aws_session(): + _aws_session = Mock() + return _aws_session + + +EXAMPLE_METRICS_LOG_LINES = [ + [ + {"field": "@timestamp", "value": "Test timestamp 0"}, + {"field": "@message", "value": "Test value 0"}, + ], + [ + {"field": "@timestamp", "value": "Test timestamp 1"}, + {"field": "@message", "value": "Test value 1"}, + ], + [ + {"field": "@timestamp", "value": "Test timestamp 2"}, + ], + [ + {"field": "@message", "value": "Test value 3"}, + ], + [], +] + +EXPECTED_CALL_LIST = [ + call("Test timestamp 0", "Test value 0"), + call("Test timestamp 1", "Test value 1"), + call(None, "Test value 3"), +] + + +@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(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) + 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\\//" + " | 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 + + logs_client_mock.start_query.return_value = {"queryId": "test"} + logs_client_mock.get_query_results.return_value = {"status": "Queued"} + + fetcher = CwlInsightsMetricsFetcher(aws_session, 0.1, 0.2) + result = fetcher.get_metrics_for_job("test_job") + logs_client_mock.get_query_results.assert_called() + assert result == {} + + +@pytest.mark.xfail(raises=MetricsRetrievalError) +def test_get_all_metrics_failed(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": "Failed"} + + fetcher = CwlInsightsMetricsFetcher(aws_session) + fetcher.get_metrics_for_job("test_job") 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 new file mode 100644 index 00000000..fdaff840 --- /dev/null +++ b/test/unit_tests/braket/jobs/metrics_data/test_cwl_metrics_fetcher.py @@ -0,0 +1,135 @@ +# 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 Mock, call, patch + +import pytest + +from braket.jobs.metrics_data.cwl_metrics_fetcher import CwlMetricsFetcher + + +@pytest.fixture +def aws_session(): + _aws_session = Mock() + return _aws_session + + +EXAMPLE_METRICS_LOG_LINES = [ + { + "timestamp": "Test timestamp 0", + "message": "Metrics - Test value 0", + }, + { + "timestamp": "Test timestamp 1", + "message": "Metrics - Test value 1", + }, + { + "timestamp": "Test timestamp 2", + }, + { + "message": "Metrics - Test value 3", + }, + { + # This metrics fetcher will filter out log line that don't have a "Metrics -" tag. + "message": "No prefix, Test value 4", + }, +] + +EXPECTED_CALL_LIST = [ + call("Test timestamp 0", "Metrics - Test value 0"), + call("Test timestamp 1", "Metrics - Test value 1"), + call(None, "Metrics - Test value 3"), +] + + +@patch("braket.jobs.metrics_data.cwl_metrics_fetcher.LogMetricsParser.get_parsed_metrics") +@patch("braket.jobs.metrics_data.cwl_metrics_fetcher.LogMetricsParser.parse_log_message") +def test_get_all_metrics_complete_results(mock_add_metrics, mock_get_metrics, aws_session): + logs_client_mock = Mock() + aws_session.logs_client = logs_client_mock + + logs_client_mock.describe_log_streams.return_value = { + "logStreams": [{"logStreamName": "stream name"}, {}] + } + logs_client_mock.get_log_events.return_value = { + "events": EXAMPLE_METRICS_LOG_LINES, + "nextForwardToken": None, + } + expected_result = {"Test": [0]} + mock_get_metrics.return_value = expected_result + + fetcher = CwlMetricsFetcher(aws_session) + result = fetcher.get_metrics_for_job("test_job") + assert mock_add_metrics.call_args_list == EXPECTED_CALL_LIST + assert result == expected_result + + +@patch("braket.jobs.metrics_data.cwl_metrics_fetcher.LogMetricsParser.parse_log_message") +def test_get_log_streams_timeout(mock_add_metrics, aws_session): + logs_client_mock = Mock() + aws_session.logs_client = logs_client_mock + + logs_client_mock.describe_log_streams.return_value = { + "logStreams": [{"logStreamName": "stream name"}], + "nextToken": "forever", + } + logs_client_mock.get_log_events.return_value = { + "events": EXAMPLE_METRICS_LOG_LINES, + } + + fetcher = CwlMetricsFetcher(aws_session, 0.1) + result = fetcher.get_metrics_for_job("test_job") + mock_add_metrics.assert_not_called() + assert result == {} + + +@patch("braket.jobs.metrics_data.cwl_metrics_fetcher.LogMetricsParser.parse_log_message") +def test_get_no_streams_returned(mock_add_metrics, aws_session): + logs_client_mock = Mock() + aws_session.logs_client = logs_client_mock + + logs_client_mock.describe_log_streams.return_value = {} + + fetcher = CwlMetricsFetcher(aws_session) + result = fetcher.get_metrics_for_job("test_job") + logs_client_mock.describe_log_streams.assert_called() + mock_add_metrics.assert_not_called() + assert result == {} + + +@patch("braket.jobs.metrics_data.cwl_metrics_fetcher.LogMetricsParser.get_parsed_metrics") +@patch("braket.jobs.metrics_data.cwl_metrics_fetcher.LogMetricsParser.parse_log_message") +def test_get_metrics_timeout(mock_add_metrics, mock_get_metrics, aws_session): + logs_client_mock = Mock() + aws_session.logs_client = logs_client_mock + + logs_client_mock.describe_log_streams.return_value = { + "logStreams": [{"logStreamName": "stream name"}] + } + logs_client_mock.get_log_events.side_effect = get_log_events_forever + expected_result = {"Test": [0]} + mock_get_metrics.return_value = expected_result + + fetcher = CwlMetricsFetcher(aws_session, 0.1) + result = fetcher.get_metrics_for_job("test_job") + logs_client_mock.get_log_events.assert_called() + mock_add_metrics.assert_called() + assert result == expected_result + + +def get_log_events_forever(*args, **kwargs): + next_token = "1" + token = kwargs.get("nextToken") + if token and token == "1": + next_token = "2" + return {"events": EXAMPLE_METRICS_LOG_LINES, "nextForwardToken": next_token} diff --git a/test/unit_tests/braket/jobs/metrics_data/test_log_metrics_parser.py b/test/unit_tests/braket/jobs/metrics_data/test_log_metrics_parser.py new file mode 100644 index 00000000..f3edadb0 --- /dev/null +++ b/test/unit_tests/braket/jobs/metrics_data/test_log_metrics_parser.py @@ -0,0 +1,164 @@ +# 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.jobs.metrics_data import LogMetricsParser +from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType + +MALFORMED_METRICS_LOG_LINES = [ + {"timestamp": "Test timestamp 0", "message": ""}, + {"timestamp": "Test timestamp 1", "message": "No semicolon metric0=2.0"}, + {"timestamp": "Test timestamp 2", "message": "metric0=not_a_number;"}, + {"timestamp": "Test timestamp 3", "message": "also not a number metric0=2 . 0;"}, + {"timestamp": "Test timestamp 3", "message": "metric0=;"}, + {"timestamp": "Test timestamp 3", "message": "metric0= ;"}, + {"timestamp": "Test timestamp 4"}, + {"unknown": "Unknown"}, +] + +SIMPLE_METRICS_LOG_LINES = [ + # This is a standard line of what our metrics may look like + { + "timestamp": "Test timestamp 0", + "message": "Metrics - metric0=0.0; metric1=1.0; metric2=2.0 ;", + }, + # This line overwrites the timestamp by having it output in the metrics. + { + "timestamp": "Test timestamp 1", + "message": "Metrics - timestamp=1628019160; metric0=0.1; metric2= 2.1;", + }, + # This line adds metric3 that won't have values for any other timestamp + { + "timestamp": "Test timestamp 2", + "message": "Metrics - metric0=0.2; metric1=1.2; metric2= 2.2 ; metric3=0.2;", + }, + # This line adds metrics expressed as exponents + { + "timestamp": "Test timestamp 3", + "message": "Metrics - metric0=-0.4; metric1=3.14e-22; metric2=3.14E22;", + }, +] + +SIMPLE_METRICS_RESULT = { + "timestamp": [ + "Test timestamp 0", + 1628019160, + "Test timestamp 2", + "Test timestamp 3", + ], + "metric0": [0.0, 0.1, 0.2, -0.4], + "metric1": [1.0, None, 1.2, 3.14e-22], + "metric2": [2.0, 2.1, 2.2, 3.14e22], + "metric3": [None, None, 0.2, None], +} + +# This will test how metrics are combined when the multiple metrics have the same timestamp +SINGLE_TIMESTAMP_METRICS_LOG_LINES = [ + {"timestamp": "Test timestamp 0", "message": "Metrics - metric0=0.0;"}, + {"timestamp": "Test timestamp 0", "message": "Metrics - metric0=0.1; metric1=1.1;"}, + {"timestamp": "Test timestamp 0", "message": "Metrics - metric0=0.2; metric2=2.8;"}, + {"timestamp": "Test timestamp 0", "message": "Metrics - metric0=0.3; metric1=1.3;"}, + {"timestamp": "Test timestamp 0", "message": "Metrics - metric1=1.4; metric2=2.4;"}, + { + "timestamp": "Test timestamp 0", + "message": "Metrics - metric0=0.5; metric1=1.5; metric2=2.5;", + }, + {"timestamp": "Test timestamp 0", "message": "Metrics - metric1=0.6; metric0=0.6;"}, +] + + +ITERATION_AND_TIMESTAMPS_LOG_LINES = [ + {"timestamp": "Test timestamp 0", "message": "Metrics - iteration_number=0; metric0=0.0;"}, + { + "timestamp": "Test timestamp 1", + "message": "Metrics - metric0=0.1; metric1=1.1; iteration_number=0;", + }, + {"timestamp": "Test timestamp 2", "message": "Metrics - metric0=0.2; metric2=2.8;"}, + {"timestamp": "Test timestamp 3", "message": "Metrics - metric0=0.3; metric1=1.3;"}, + { + "timestamp": "Test timestamp 4", + "message": "Metrics - metric1=1.4; metric2=2.4; iteration_number=0;", + }, + { + "timestamp": "Test timestamp 5", + "message": "Metrics - metric0=0.5; metric1=1.5; metric2=2.5;", + }, + { + "timestamp": "Test timestamp 6", + "message": "Metrics - metric1=0.6; iteration_number=0; metric0=0.6;", + }, +] + + +SINGLE_TIMESTAMP_MAX_RESULTS = { + "timestamp": ["Test timestamp 0"], + "metric0": [0.6], + "metric1": [1.5], + "metric2": [2.8], +} + +SINGLE_TIMESTAMP_MIN_RESULTS = { + "timestamp": ["Test timestamp 0"], + "metric0": [0.0], + "metric1": [0.6], + "metric2": [2.4], +} + +ITERATION_NUMBER_MAX_RESULTS = { + "iteration_number": [0], + "timestamp": ["Test timestamp 6"], + "metric0": [0.6], + "metric1": [1.4], + "metric2": [2.4], +} + + +@pytest.mark.parametrize( + "log_events, metric_type, metric_stat, metrics_results", + [ + ([], MetricType.TIMESTAMP, MetricStatistic.MAX, {}), + (MALFORMED_METRICS_LOG_LINES, MetricType.TIMESTAMP, MetricStatistic.MAX, {}), + ( + SIMPLE_METRICS_LOG_LINES, + MetricType.TIMESTAMP, + MetricStatistic.MAX, + SIMPLE_METRICS_RESULT, + ), + ( + SINGLE_TIMESTAMP_METRICS_LOG_LINES, + MetricType.TIMESTAMP, + MetricStatistic.MAX, + SINGLE_TIMESTAMP_MAX_RESULTS, + ), + ( + SINGLE_TIMESTAMP_METRICS_LOG_LINES, + MetricType.TIMESTAMP, + MetricStatistic.MIN, + SINGLE_TIMESTAMP_MIN_RESULTS, + ), + ( + ITERATION_AND_TIMESTAMPS_LOG_LINES, + MetricType.ITERATION_NUMBER, + MetricStatistic.MAX, + ITERATION_NUMBER_MAX_RESULTS, + ), + # TODO: https://app.asana.com/0/1199668788990775/1200502190825620 + # We should also test some real-world data, once we have it. + ], +) +def test_get_all_metrics_complete_results(log_events, metric_type, metric_stat, metrics_results): + parser = LogMetricsParser() + for log_event in log_events: + parser.parse_log_message(log_event.get("timestamp"), log_event.get("message")) + assert parser.get_parsed_metrics(metric_type, metric_stat) == metrics_results diff --git a/test/unit_tests/braket/jobs/test_data_persistence.py b/test/unit_tests/braket/jobs/test_data_persistence.py new file mode 100644 index 00000000..d40d2c20 --- /dev/null +++ b/test/unit_tests/braket/jobs/test_data_persistence.py @@ -0,0 +1,274 @@ +# 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 +from dataclasses import dataclass +from unittest.mock import patch + +import numpy as np +import pytest + +from braket.jobs.data_persistence import load_job_checkpoint, save_job_checkpoint, save_job_result +from braket.jobs_data import PersistedJobDataFormat + + +@pytest.mark.parametrize( + "job_name, file_suffix, data_format, checkpoint_data, expected_saved_data", + [ + ( + "job_plaintext_simple_dict", + "", + 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", + } + ), + ), + ( + "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", + } + ), + ), + ], +) +def test_save_job_checkpoint( + job_name, file_suffix, data_format, checkpoint_data, expected_saved_data +): + with tempfile.TemporaryDirectory() as tmp_dir: + with patch.dict( + os.environ, {"AMZN_BRAKET_CHECKPOINT_DIR": tmp_dir, "AMZN_BRAKET_JOB_NAME": job_name} + ): + save_job_checkpoint(checkpoint_data, file_suffix, data_format) + + expected_file_location = ( + f"{tmp_dir}/{job_name}_{file_suffix}.json" + if file_suffix + else f"{tmp_dir}/{job_name}.json" + ) + with open(expected_file_location, "r") as expected_file: + assert expected_file.read() == expected_saved_data + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("checkpoint_data", [{}, None]) +def test_save_job_checkpoint_raises_error_empty_data(checkpoint_data): + job_name = "foo" + with tempfile.TemporaryDirectory() as tmp_dir: + with patch.dict( + os.environ, {"AMZN_BRAKET_CHECKPOINT_DIR": tmp_dir, "AMZN_BRAKET_JOB_NAME": job_name} + ): + save_job_checkpoint(checkpoint_data) + + +@pytest.mark.parametrize( + "job_name, file_suffix, data_format, saved_data, expected_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", + } + ), + {"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", + } + ), + {"converged": True, "energy": -0.2}, + ), + ], +) +def test_load_job_checkpoint( + job_name, file_suffix, data_format, saved_data, expected_checkpoint_data +): + with tempfile.TemporaryDirectory() as tmp_dir: + file_path = ( + f"{tmp_dir}/{job_name}_{file_suffix}.json" + if file_suffix + else f"{tmp_dir}/{job_name}.json" + ) + with open(file_path, "w") as f: + f.write(saved_data) + + with patch.dict( + os.environ, {"AMZN_BRAKET_CHECKPOINT_DIR": tmp_dir, "AMZN_BRAKET_JOB_NAME": job_name} + ): + loaded_data = load_job_checkpoint(job_name, file_suffix) + assert loaded_data == expected_checkpoint_data + + +@pytest.mark.xfail(raises=FileNotFoundError) +def test_load_job_checkpoint_raises_error_file_not_exists(): + job_name = "old_job" + file_suffix = "correct_suffix" + with tempfile.TemporaryDirectory() as tmp_dir: + file_path = f"{tmp_dir}/{job_name}_{file_suffix}.json" + with open(file_path, "w") as _: + pass + + with patch.dict( + os.environ, {"AMZN_BRAKET_CHECKPOINT_DIR": tmp_dir, "AMZN_BRAKET_JOB_NAME": job_name} + ): + load_job_checkpoint(job_name, "wrong_suffix") + + +@pytest.mark.xfail(raises=ValueError) +def test_load_job_checkpoint_raises_error_corrupted_data(): + job_name = "old_job_corrupted_data" + file_suffix = "foo" + with tempfile.TemporaryDirectory() as tmp_dir: + 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", + } + ) + ) + + with patch.dict( + os.environ, {"AMZN_BRAKET_CHECKPOINT_DIR": tmp_dir, "AMZN_BRAKET_JOB_NAME": job_name} + ): + load_job_checkpoint(job_name, file_suffix) + + +@dataclass +class CustomClassToPersist: + float_val: float + str_val: str + bool_val: bool + + +def test_save_and_load_job_checkpoint(): + with tempfile.TemporaryDirectory() as tmp_dir: + job_name = "job_name_1" + data = { + "np_array": np.array([1]), + "custom_class": CustomClassToPersist(3.4, "str", True), + "none_value": None, + "nested_dict": {"a": {"b": False}}, + } + with patch.dict( + os.environ, {"AMZN_BRAKET_CHECKPOINT_DIR": tmp_dir, "AMZN_BRAKET_JOB_NAME": job_name} + ): + save_job_checkpoint(data, data_format=PersistedJobDataFormat.PICKLED_V4) + retrieved = load_job_checkpoint(job_name) + assert retrieved == data + + +@pytest.mark.parametrize( + "data_format, result_data, expected_saved_data", + [ + ( + 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", + } + ), + ), + ( + 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", + } + ), + ), + ], +) +def test_save_job_result(data_format, result_data, expected_saved_data): + with tempfile.TemporaryDirectory() as tmp_dir: + with patch.dict(os.environ, {"AMZN_BRAKET_JOB_RESULTS_DIR": tmp_dir}): + save_job_result(result_data, data_format) + + expected_file_location = f"{tmp_dir}/results.json" + with open(expected_file_location, "r") as expected_file: + assert expected_file.read() == expected_saved_data + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("result_data", [{}, None]) +def test_save_job_result_raises_error_empty_data(result_data): + with tempfile.TemporaryDirectory() as tmp_dir: + with patch.dict(os.environ, {"AMZN_BRAKET_CHECKPOINT_DIR": tmp_dir}): + save_job_result(result_data) diff --git a/test/unit_tests/braket/jobs/test_image_uris.py b/test/unit_tests/braket/jobs/test_image_uris.py new file mode 100644 index 00000000..ab608c5a --- /dev/null +++ b/test/unit_tests/braket/jobs/test_image_uris.py @@ -0,0 +1,57 @@ +# 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.jobs.image_uris import Framework, retrieve_image + + +@pytest.mark.parametrize( + "region, framework, expected_uri", + [ + ( + "us-west-1", + Framework.BASE, + "292282985366.dkr.ecr.us-west-1.amazonaws.com/" + "amazon-braket-base-jobs:1.0-cpu-py37-ubuntu18.04", + ), + ( + "us-east-1", + Framework.PL_TENSORFLOW, + "292282985366.dkr.ecr.us-east-1.amazonaws.com/amazon-braket-tensorflow-jobs:" + "2.4.1-cpu-py37-ubuntu18.04", + ), + ( + "us-west-2", + Framework.PL_PYTORCH, + "292282985366.dkr.ecr.us-west-2.amazonaws.com/" + "amazon-braket-pytorch-jobs:1.8.1-cpu-py37-ubuntu18.04", + ), + ], +) +def test_retrieve_image_default_version(region, framework, expected_uri): + assert retrieve_image(framework, region) == expected_uri + + +@pytest.mark.parametrize( + "region, framework", + [ + ("eu-west-1", Framework.BASE), + (None, Framework.BASE), + ("us-west-1", None), + ("us-west-1", "foo"), + ], +) +@pytest.mark.xfail(raises=ValueError) +def test_retrieve_image_incorrect_input(region, framework): + retrieve_image(framework, region) diff --git a/test/unit_tests/braket/jobs/test_metrics.py b/test/unit_tests/braket/jobs/test_metrics.py new file mode 100644 index 00000000..1670cee3 --- /dev/null +++ b/test/unit_tests/braket/jobs/test_metrics.py @@ -0,0 +1,35 @@ +from unittest.mock import patch + +import pytest + +from braket.jobs.metrics import log_metric + + +@pytest.mark.parametrize( + "test_value, test_timestamp, test_iteration, result_string", + [ + # Happy case + (0.1, 1, 2, "Metrics - timestamp=1; TestName=0.1; iteration_number=2;"), + # We handle exponent values + (3.14e-22, 1, 2, "Metrics - timestamp=1; TestName=3.14e-22; iteration_number=2;"), + # When iteration number is not provided, we don't print it + (5, 1, None, "Metrics - timestamp=1; TestName=5;"), + # When iteration number is 0, we do print it + (5, 1, 0, "Metrics - timestamp=1; TestName=5; iteration_number=0;"), + # When timestamp is not provided, we use time.time() + (-3.14, None, 2, "Metrics - timestamp=time_mocked; TestName=-3.14; iteration_number=2;"), + ], +) +@patch("time.time") +@patch("builtins.print") +def test_log_metric( + print_mock, time_mock, test_value, test_timestamp, test_iteration, result_string +): + time_mock.return_value = "time_mocked" + log_metric( + metric_name="TestName", + value=test_value, + timestamp=test_timestamp, + iteration_number=test_iteration, + ) + print_mock.assert_called_with(result_string) diff --git a/test/unit_tests/braket/jobs/test_quantum_job_creation.py b/test/unit_tests/braket/jobs/test_quantum_job_creation.py new file mode 100644 index 00000000..824cc33c --- /dev/null +++ b/test/unit_tests/braket/jobs/test_quantum_job_creation.py @@ -0,0 +1,636 @@ +# 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 datetime +import tempfile +import time +from collections import defaultdict +from dataclasses import asdict +from pathlib import Path +from unittest.mock import Mock, patch + +import pytest + +from braket.aws import AwsSession +from braket.jobs.config import ( + CheckpointConfig, + InstanceConfig, + OutputDataConfig, + S3DataSourceConfig, + StoppingCondition, +) +from braket.jobs.quantum_job_creation import ( + _generate_default_job_name, + _process_input_data, + _process_local_source_module, + _process_s3_source_module, + _tar_and_upload_to_code_location, + _validate_entry_point, + prepare_quantum_job, +) + + +@pytest.fixture +def aws_session(): + _aws_session = Mock(spec=AwsSession) + _aws_session.default_bucket.return_value = "default-bucket-name" + _aws_session.get_default_jobs_role.return_value = "default-role-arn" + return _aws_session + + +@pytest.fixture +def entry_point(): + return "test-source-dir.entry_point:func" + + +@pytest.fixture +def bucket(): + return "braket-region-id" + + +@pytest.fixture +def tags(): + return {"tag-key": "tag-value"} + + +@pytest.fixture( + params=[ + None, + "aws.location/amazon-braket-custom-jobs:tag.1.2.3", + "other.uri/amazon-braket-custom-name:tag", + "other.uri/custom-non-managed:tag", + "other-custom-format.com", + ] +) +def image_uri(request): + return request.param + + +@pytest.fixture(params=["given_job_name", "default_job_name"]) +def job_name(request): + if request.param == "given_job_name": + return "test-job-name" + + +@pytest.fixture +def s3_prefix(job_name): + return f"{job_name}/non-default" + + +@pytest.fixture(params=["local_source", "s3_source"]) +def source_module(request, bucket): + if request.param == "local_source": + return "test-source-module" + elif request.param == "s3_source": + return AwsSession.construct_s3_uri(bucket, "test-source-prefix", "source.tar.gz") + + +@pytest.fixture +def code_location(bucket, s3_prefix): + return AwsSession.construct_s3_uri(bucket, s3_prefix, "script") + + +@pytest.fixture +def role_arn(): + return "arn:aws:iam::0000000000:role/AmazonBraketInternalSLR" + + +@pytest.fixture +def device(): + return "arn:aws:braket:::device/qpu/test/device-name" + + +@pytest.fixture +def hyperparameters(): + return { + "param": "value", + "other-param": 100, + } + + +@pytest.fixture(params=["dict", "local"]) +def input_data(request, bucket): + if request.param == "dict": + return { + "s3_input": f"s3://{bucket}/data/prefix", + "local_input": "local/prefix", + "config_input": S3DataSourceConfig(f"s3://{bucket}/config/prefix"), + } + elif request.param == "local": + return "local/prefix" + + +@pytest.fixture +def instance_config(): + return InstanceConfig( + instanceType="ml.m5.large", + volumeSizeInGb=1, + ) + + +@pytest.fixture +def stopping_condition(): + return StoppingCondition( + maxRuntimeInSeconds=1200, + ) + + +@pytest.fixture +def output_data_config(bucket, s3_prefix): + return OutputDataConfig( + s3Path=AwsSession.construct_s3_uri(bucket, s3_prefix, "output"), + ) + + +@pytest.fixture +def checkpoint_config(bucket, s3_prefix): + return CheckpointConfig( + localPath="/opt/omega/checkpoints", + s3Uri=AwsSession.construct_s3_uri(bucket, s3_prefix, "checkpoints"), + ) + + +@pytest.fixture +def generate_get_job_response(): + def _get_job_response(**kwargs): + response = { + "ResponseMetadata": { + "RequestId": "d223b1a0-ee5c-4c75-afa7-3c29d5338b62", + "HTTPStatusCode": 200, + }, + "algorithmSpecification": { + "scriptModeConfig": { + "entryPoint": "my_file:start_here", + "s3Uri": "s3://amazon-braket-jobs/job-path/my_file.py", + } + }, + "checkpointConfig": { + "localPath": "/opt/omega/checkpoints", + "s3Uri": "s3://amazon-braket-jobs/job-path/checkpoints", + }, + "createdAt": datetime.datetime(2021, 6, 28, 21, 4, 51), + "deviceConfig": { + "device": "arn:aws:braket:::device/qpu/rigetti/Aspen-10", + }, + "hyperParameters": { + "foo": "bar", + }, + "inputDataConfig": [ + { + "channelName": "training_input", + "dataSource": { + "s3DataSource": { + "s3Uri": "s3://amazon-braket-jobs/job-path/input", + } + }, + } + ], + "instanceConfig": { + "instanceType": "ml.m5.large", + "volumeSizeInGb": 1, + }, + "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"}, + "roleArn": "arn:aws:iam::875981177017:role/AmazonBraketJobRole", + "status": "RUNNING", + "stoppingCondition": {"maxRuntimeInSeconds": 1200}, + } + response.update(kwargs) + + return response + + return _get_job_response + + +@pytest.fixture(params=["fixtures", "defaults", "nones"]) +def create_job_args( + request, + aws_session, + entry_point, + image_uri, + source_module, + job_name, + code_location, + role_arn, + device, + hyperparameters, + input_data, + instance_config, + stopping_condition, + output_data_config, + checkpoint_config, + tags, +): + if request.param == "fixtures": + return dict( + (key, value) + for key, value in { + "device": device, + "source_module": source_module, + "entry_point": entry_point, + "image_uri": image_uri, + "job_name": job_name, + "code_location": code_location, + "role_arn": role_arn, + "hyperparameters": hyperparameters, + "input_data": input_data, + "instance_config": instance_config, + "stopping_condition": stopping_condition, + "output_data_config": output_data_config, + "checkpoint_config": checkpoint_config, + "aws_session": aws_session, + "tags": tags, + }.items() + if value is not None + ) + elif request.param == "defaults": + return { + "device": device, + "source_module": source_module, + "entry_point": entry_point, + "aws_session": aws_session, + } + elif request.param == "nones": + return defaultdict( + lambda: None, + device=device, + source_module=source_module, + entry_point=entry_point, + aws_session=aws_session, + ) + + +@patch("tarfile.TarFile.add") +@patch("importlib.util.find_spec") +@patch("braket.jobs.quantum_job_creation.Path") +@patch("time.time") +def test_create_job( + mock_time, + mock_path, + mock_findspec, + mock_tarfile, + aws_session, + source_module, + create_job_args, +): + mock_path.return_value.resolve.return_value.parent = "parent_dir" + mock_path.return_value.resolve.return_value.stem = source_module + mock_path.return_value.name = "file_name" + mock_time.return_value = datetime.datetime.now().timestamp() + expected_kwargs = _translate_creation_args(create_job_args) + result_kwargs = prepare_quantum_job(**create_job_args) + assert expected_kwargs == result_kwargs + + +def _translate_creation_args(create_job_args): + aws_session = create_job_args["aws_session"] + create_job_args = defaultdict(lambda: None, **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() + code_location = create_job_args["code_location"] or AwsSession.construct_s3_uri( + default_bucket, "jobs", job_name, "script" + ) + role_arn = create_job_args["role_arn"] or aws_session.get_default_jobs_role() + device = create_job_args["device"] + hyperparameters = create_job_args["hyperparameters"] or {} + input_data = create_job_args["input_data"] or {} + instance_config = create_job_args["instance_config"] or InstanceConfig() + output_data_config = create_job_args["output_data_config"] or OutputDataConfig( + s3Path=AwsSession.construct_s3_uri(default_bucket, "jobs", job_name, "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") + ) + entry_point = create_job_args["entry_point"] + source_module = create_job_args["source_module"] + if not AwsSession.is_s3_uri(source_module): + entry_point = entry_point or Path(source_module).stem + algorithm_specification = { + "scriptModeConfig": { + "entryPoint": entry_point, + "s3Uri": f"{code_location}/source.tar.gz", + "compressionType": "GZIP", + } + } + if image_uri: + algorithm_specification["containerImage"] = {"uri": image_uri} + tags = create_job_args.get("tags", {}) + + test_kwargs = { + "jobName": job_name, + "roleArn": role_arn, + "algorithmSpecification": algorithm_specification, + "inputDataConfig": _process_input_data(input_data, job_name, aws_session), + "instanceConfig": asdict(instance_config), + "outputDataConfig": asdict(output_data_config), + "checkpointConfig": asdict(checkpoint_config), + "deviceConfig": {"device": device}, + "hyperParameters": hyperparameters, + "stoppingCondition": asdict(stopping_condition), + "tags": tags, + } + + return test_kwargs + + +@patch("time.time") +def test_generate_default_job_name(mock_time, image_uri): + job_type_mapping = { + None: "-default", + "aws.location/amazon-braket-custom-jobs:tag.1.2.3": "-custom", + "other.uri/amazon-braket-custom-name:tag": "-custom-name", + "other.uri/custom-non-managed:tag": "", + "other-custom-format.com": "", + } + job_type = job_type_mapping[image_uri] + mock_time.return_value = datetime.datetime.now().timestamp() + assert _generate_default_job_name(image_uri) == f"braket-job{job_type}-{time.time() * 1000:.0f}" + + +@pytest.mark.parametrize( + "source_module", + ( + "s3://bucket/source_module.tar.gz", + "s3://bucket/SOURCE_MODULE.TAR.GZ", + ), +) +def test_process_s3_source_module(source_module, aws_session): + _process_s3_source_module(source_module, "entry_point", aws_session, "code_location") + aws_session.copy_s3_object.assert_called_with(source_module, "code_location/source.tar.gz") + + +def test_process_s3_source_module_not_tar_gz(aws_session): + must_be_tar_gz = ( + "If source_module is an S3 URI, it must point to a tar.gz file. " + "Not a valid S3 URI for parameter `source_module`: s3://bucket/source_module" + ) + with pytest.raises(ValueError, match=must_be_tar_gz): + _process_s3_source_module( + "s3://bucket/source_module", "entry_point", aws_session, "code_location" + ) + + +def test_process_s3_source_module_no_entry_point(aws_session): + entry_point_required = "If source_module is an S3 URI, entry_point must be provided." + with pytest.raises(ValueError, match=entry_point_required): + _process_s3_source_module("s3://bucket/source_module", None, aws_session, "code_location") + + +@patch("braket.jobs.quantum_job_creation._tar_and_upload_to_code_location") +@patch("braket.jobs.quantum_job_creation._validate_entry_point") +def test_process_local_source_module(validate_mock, tar_and_upload_mock, aws_session): + with tempfile.TemporaryDirectory() as temp_dir: + source_module = Path(temp_dir, "source_module") + source_module.touch() + + _process_local_source_module( + str(source_module), "entry_point", aws_session, "code_location" + ) + + source_module_abs_path = Path(temp_dir, "source_module").resolve() + validate_mock.assert_called_with(source_module_abs_path, "entry_point") + tar_and_upload_mock.assert_called_with(source_module_abs_path, aws_session, "code_location") + + +def test_process_local_source_module_not_found(aws_session): + with tempfile.TemporaryDirectory() as temp_dir: + source_module = str(Path(temp_dir, "source_module").as_posix()) + source_module_not_found = f"Source module not found: {source_module}" + with pytest.raises(ValueError, match=source_module_not_found): + _process_local_source_module(source_module, "entry_point", aws_session, "code_location") + + +def test_validate_entry_point_default_file(): + with tempfile.TemporaryDirectory() as temp_dir: + source_module_path = Path(temp_dir, "source_module.py") + source_module_path.touch() + # import source_module + _validate_entry_point(source_module_path, "source_module") + # from source_module import func + _validate_entry_point(source_module_path, "source_module:func") + # import . + _validate_entry_point(source_module_path, ".") + # from . import func + _validate_entry_point(source_module_path, ".:func") + + +def test_validate_entry_point_default_directory(): + with tempfile.TemporaryDirectory() as temp_dir: + source_module_path = Path(temp_dir, "source_module") + source_module_path.mkdir() + # import source_module + _validate_entry_point(source_module_path, "source_module") + # from source_module import func + _validate_entry_point(source_module_path, "source_module:func") + # import . + _validate_entry_point(source_module_path, ".") + # from . import func + _validate_entry_point(source_module_path, ".:func") + + +def test_validate_entry_point_submodule_file(): + with tempfile.TemporaryDirectory() as temp_dir: + source_module_path = Path(temp_dir, "source_module") + source_module_path.mkdir() + Path(source_module_path, "submodule.py").touch() + # from source_module import submodule + _validate_entry_point(source_module_path, "source_module.submodule") + # from source_module.submodule import func + _validate_entry_point(source_module_path, "source_module.submodule:func") + # from . import submodule + _validate_entry_point(source_module_path, ".submodule") + # from .submodule import func + _validate_entry_point(source_module_path, ".submodule:func") + + +def test_validate_entry_point_submodule_init(): + with tempfile.TemporaryDirectory() as temp_dir: + source_module_path = Path(temp_dir, "source_module") + source_module_path.mkdir() + Path(source_module_path, "submodule.py").touch() + with open(str(Path(source_module_path, "__init__.py")), "w") as f: + f.write("from . import submodule as renamed") + # from source_module import renamed + _validate_entry_point(source_module_path, "source_module:renamed") + # from . import renamed + _validate_entry_point(source_module_path, ".:renamed") + + +def test_validate_entry_point_source_module_not_found(): + with tempfile.TemporaryDirectory() as temp_dir: + source_module_path = Path(temp_dir, "source_module") + source_module_path.mkdir() + Path(source_module_path, "submodule.py").touch() + + # catches ModuleNotFoundError + module_not_found = "Entry point module was not found: fake_source_module.submodule" + with pytest.raises(ValueError, match=module_not_found): + _validate_entry_point(source_module_path, "fake_source_module.submodule") + + # catches AssertionError for module is not None + submodule_not_found = "Entry point module was not found: source_module.fake_submodule" + with pytest.raises(ValueError, match=submodule_not_found): + _validate_entry_point(source_module_path, "source_module.fake_submodule") + + +@patch("tarfile.TarFile.add") +def test_tar_and_upload_to_code_location(mock_tar_add, aws_session): + with tempfile.TemporaryDirectory() as temp_dir: + source_module_path = Path(temp_dir, "source_module") + source_module_path.mkdir() + _tar_and_upload_to_code_location(source_module_path, aws_session, "code_location") + mock_tar_add.assert_called_with(source_module_path, arcname="source_module") + local, s3 = aws_session.upload_to_s3.call_args_list[0][0] + assert local.endswith("source.tar.gz") + assert s3 == "code_location/source.tar.gz" + + +@patch("braket.jobs.quantum_job_creation._process_local_source_module") +@patch("braket.jobs.quantum_job_creation._validate_entry_point") +@patch("braket.jobs.quantum_job_creation._validate_params") +def test_copy_checkpoints( + mock_validate_input, + mock_validate_entry_point, + mock_process_local_source, + aws_session, + entry_point, + device, + checkpoint_config, + generate_get_job_response, +): + other_checkpoint_uri = "s3://amazon-braket-jobs/job-path/checkpoints" + aws_session.get_job.return_value = generate_get_job_response( + checkpointConfig={ + "s3Uri": other_checkpoint_uri, + } + ) + prepare_quantum_job( + device=device, + source_module="source_module", + entry_point=entry_point, + copy_checkpoints_from_job="other-job-arn", + checkpoint_config=checkpoint_config, + aws_session=aws_session, + ) + aws_session.copy_s3_directory.assert_called_with(other_checkpoint_uri, checkpoint_config.s3Uri) + + +def test_invalid_input_parameters(entry_point, aws_session): + error_message = ( + "'instance_config' should be of '' " + "but user provided ." + ) + with pytest.raises(ValueError, match=error_message): + prepare_quantum_job( + aws_session=aws_session, + entry_point=entry_point, + device="arn:aws:braket:::device/quantum-simulator/amazon/sv1", + source_module="alpha_test_job", + hyperparameters={ + "param-1": "first parameter", + "param-2": "second param", + }, + instance_config=2, + ) + + +@pytest.mark.parametrize( + "input_data, input_data_configs", + ( + ( + "local/prefix", + [ + { + "channelName": "input", + "dataSource": { + "s3DataSource": { + "s3Uri": "s3://default-bucket-name/jobs/job-name/data/input/prefix", + }, + }, + } + ], + ), + ( + "s3://my-bucket/my/prefix-", + [ + { + "channelName": "input", + "dataSource": { + "s3DataSource": { + "s3Uri": "s3://my-bucket/my/prefix-", + }, + }, + } + ], + ), + ( + S3DataSourceConfig( + "s3://my-bucket/my/manifest.json", + content_type="text/csv", + ), + [ + { + "channelName": "input", + "dataSource": { + "s3DataSource": { + "s3Uri": "s3://my-bucket/my/manifest.json", + }, + }, + "contentType": "text/csv", + } + ], + ), + ( + { + "local-input": "local/prefix", + "s3-input": "s3://my-bucket/my/prefix-", + "config-input": S3DataSourceConfig( + "s3://my-bucket/my/manifest.json", + ), + }, + [ + { + "channelName": "local-input", + "dataSource": { + "s3DataSource": { + "s3Uri": "s3://default-bucket-name/jobs/job-name/" + "data/local-input/prefix", + }, + }, + }, + { + "channelName": "s3-input", + "dataSource": { + "s3DataSource": { + "s3Uri": "s3://my-bucket/my/prefix-", + }, + }, + }, + { + "channelName": "config-input", + "dataSource": { + "s3DataSource": { + "s3Uri": "s3://my-bucket/my/manifest.json", + }, + }, + }, + ], + ), + ), +) +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 diff --git a/test/unit_tests/braket/jobs/test_serialization.py b/test/unit_tests/braket/jobs/test_serialization.py new file mode 100644 index 00000000..bbd1c623 --- /dev/null +++ b/test/unit_tests/braket/jobs/test_serialization.py @@ -0,0 +1,58 @@ +# 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.jobs.serialization import deserialize_values, serialize_values +from braket.jobs_data import PersistedJobDataFormat + + +@pytest.mark.parametrize( + "data_format, submitted_data, expected_serialized_data", + [ + ( + PersistedJobDataFormat.PLAINTEXT, + {"converged": True, "energy": -0.2}, + {"converged": True, "energy": -0.2}, + ), + ( + PersistedJobDataFormat.PICKLED_V4, + {"converged": True, "energy": -0.2}, + {"converged": "gASILg==\n", "energy": "gASVCgAAAAAAAABHv8mZmZmZmZou\n"}, + ), + ], +) +def test_job_serialize_data(data_format, submitted_data, expected_serialized_data): + serialized_data = serialize_values(submitted_data, data_format) + assert serialized_data == expected_serialized_data + + +@pytest.mark.parametrize( + "data_format, submitted_data, expected_deserialized_data", + [ + ( + PersistedJobDataFormat.PLAINTEXT, + {"converged": True, "energy": -0.2}, + {"converged": True, "energy": -0.2}, + ), + ( + PersistedJobDataFormat.PICKLED_V4, + {"converged": "gASILg==\n", "energy": "gASVCgAAAAAAAABHv8mZmZmZmZou\n"}, + {"converged": True, "energy": -0.2}, + ), + ], +) +def test_job_deserialize_data(data_format, submitted_data, expected_deserialized_data): + deserialized_data = deserialize_values(submitted_data, data_format) + assert deserialized_data == expected_deserialized_data From a59548da9fa704912819a3c3e850fc6344cdadf0 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Mon, 29 Nov 2021 11:21:21 -0800 Subject: [PATCH 0400/1165] fix: Skip jobs integration tests (#288) --- test/integ_tests/test_create_local_quantum_job.py | 2 ++ test/integ_tests/test_create_quantum_job.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/test/integ_tests/test_create_local_quantum_job.py b/test/integ_tests/test_create_local_quantum_job.py index 838a0132..a2c8f9e6 100644 --- a/test/integ_tests/test_create_local_quantum_job.py +++ b/test/integ_tests/test_create_local_quantum_job.py @@ -22,6 +22,7 @@ from braket.jobs.local import LocalQuantumJob +@pytest.mark.skip() def test_completed_local_job(aws_session, capsys): """Asserts the job is completed with the respective files and folders for logs, results and checkpoints. Validate the results are what we expect. Also, @@ -102,6 +103,7 @@ def test_completed_local_job(aws_session, capsys): os.chdir(current_dir) +@pytest.mark.skip() def test_failed_local_job(aws_session, capsys): """Asserts the job is failed with the output, checkpoints not created in bucket and only logs are populated. Validate the calling result function raises diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py index b88405e6..dc8e7615 100644 --- a/test/integ_tests/test_create_quantum_job.py +++ b/test/integ_tests/test_create_quantum_job.py @@ -17,9 +17,12 @@ import tempfile from pathlib import Path +import pytest + from braket.aws.aws_quantum_job import AwsQuantumJob +@pytest.mark.skip() def test_failed_quantum_job(aws_session, capsys): """Asserts the job is failed with the output, checkpoints, tasks not created in bucket and only input is uploaded to s3. Validate the @@ -75,6 +78,7 @@ def test_failed_quantum_job(aws_session, capsys): ) +@pytest.mark.skip() def test_completed_quantum_job(aws_session, capsys): """Asserts the job is completed with the output, checkpoints, tasks and script folder created in S3 for respective job. Validate the results are From 3c71b4a8e0c38a5dee997c40b52f9f33876c5d43 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 29 Nov 2021 19:35:47 +0000 Subject: [PATCH 0401/1165] prepare release v1.10.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 5a2e9c06..b2332336 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.10.0 (2021-11-29) + +### Features + + * Add support for jobs + +### Bug Fixes and Other Changes + + * Skip jobs integration tests + ## v1.9.5.post0 (2021-11-04) ### Testing and Release Infrastructure diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 4a17f7fa..8ec7a8bd 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.9.6.dev0" +__version__ = "1.10.0" From 2343581ec00e852ee582720e0c103b80f2f150d5 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 29 Nov 2021 19:35:48 +0000 Subject: [PATCH 0402/1165] update development version to v1.10.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 8ec7a8bd..b84f6ebc 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.10.0" +__version__ = "1.10.1.dev0" From 20edaf80f5ba72fec98cb42da4b1fa17631fc857 Mon Sep 17 00:00:00 2001 From: Jacob Feldman Date: Wed, 1 Dec 2021 15:15:33 -0800 Subject: [PATCH 0403/1165] feature: Adding integration tests for DM1 (#286) * feature: Adding integration tests for DM1 * Moved many_layers to test_quantum_task * formatting changes only Co-authored-by: Cody Wang --- .../gate_model_device_testing_utils.py | 53 +++++++++++++++---- .../test_density_matrix_simulator.py | 42 +++++++++++++++ .../test_simulator_quantum_task.py | 40 ++++++++++++-- 3 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 test/integ_tests/test_density_matrix_simulator.py diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index 9c76fa55..19994355 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -12,12 +12,13 @@ # language governing permissions and limitations under the License. import concurrent.futures +import math from typing import Any, Dict import numpy as np from braket.aws import AwsDevice -from braket.circuits import Circuit, Observable, ResultType +from braket.circuits import Circuit, Gate, Instruction, Observable, ResultType from braket.circuits.quantum_operator_helpers import get_pauli_eigenvalues from braket.devices import Device from braket.tasks import GateModelQuantumTaskResult @@ -71,15 +72,19 @@ 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] + device: Device, + include_state_vector: bool, + run_kwargs: Dict[str, Any], + include_amplitude: bool = True, ): circuit = ( Circuit() .h(0) .cnot(0, 1) .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) - .amplitude(["01", "10", "00", "11"]) ) + if include_amplitude: + circuit.amplitude(["01", "10", "00", "11"]) if include_state_vector: circuit.state_vector() result = device.run(circuit, **run_kwargs).result() @@ -95,12 +100,13 @@ def result_types_zero_shots_bell_pair_testing( result.get_value_by_result_type(ResultType.StateVector()), np.array([1, 0, 0, 1]) / np.sqrt(2), ) - assert result.get_value_by_result_type(ResultType.Amplitude(["01", "10", "00", "11"])) == { - "01": 0j, - "10": 0j, - "00": (1 / np.sqrt(2)), - "11": (1 / np.sqrt(2)), - } + if include_amplitude: + assert result.get_value_by_result_type(ResultType.Amplitude(["01", "10", "00", "11"])) == { + "01": 0j, + "10": 0j, + "00": (1 / np.sqrt(2)), + "11": (1 / np.sqrt(2)), + } def result_types_bell_pair_full_probability_testing(device: Device, run_kwargs: Dict[str, Any]): @@ -561,3 +567,32 @@ def batch_bell_pair_testing(device: AwsDevice, run_kwargs: Dict[str, Any]): assert np.allclose(result.measurement_probabilities["11"], 0.5, **tol) assert len(result.measurements) == shots assert [task.result() for task in batch.tasks] == results + + +def many_layers(n_qubits: int, n_layers: int) -> Circuit: + """ + Function to return circuit with many layers. + + :param int n_qubits: number of qubits + :param int n_layers: number of layers + :return: Constructed easy circuit + :rtype: Circuit + """ + qubits = range(n_qubits) + circuit = Circuit() # instantiate circuit object + for q in range(n_qubits): + circuit.h(q) + for layer in range(n_layers): + if (layer + 1) % 100 != 0: + for qubit in range(len(qubits)): + angle = np.random.uniform(0, 2 * math.pi) + gate = np.random.choice( + [Gate.Rx(angle), Gate.Ry(angle), Gate.Rz(angle), Gate.H()], 1, replace=True + )[0] + circuit.add_instruction(Instruction(gate, qubit)) + else: + for q in range(0, n_qubits, 2): + circuit.cnot(q, q + 1) + for q in range(1, n_qubits - 1, 2): + circuit.cnot(q, q + 1) + return circuit diff --git a/test/integ_tests/test_density_matrix_simulator.py b/test/integ_tests/test_density_matrix_simulator.py new file mode 100644 index 00000000..71ead742 --- /dev/null +++ b/test/integ_tests/test_density_matrix_simulator.py @@ -0,0 +1,42 @@ +import math + +import pytest +from gate_model_device_testing_utils import get_tol + +from braket.aws import AwsDevice +from braket.circuits import Circuit, Noise, Observable + +SHOTS = 1000 +DM1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/dm1" +SIMULATOR_ARNS = [DM1_ARN] + + +@pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) +def test_mixed_states(simulator_arn, aws_session, s3_destination_folder): + num_qubits = 10 + circuit = _mixed_states(num_qubits) + device = AwsDevice(simulator_arn, aws_session) + + tol = get_tol(SHOTS) + result = device.run(circuit, shots=SHOTS, s3_destination_folder=s3_destination_folder).result() + probabilities = result.measurement_probabilities + probability_sum = 0 + for bitstring in probabilities: + assert probabilities[bitstring] >= 0 + probability_sum += probabilities[bitstring] + assert math.isclose(probability_sum, 1, rel_tol=tol["rtol"], abs_tol=tol["atol"]) + assert len(result.measurements) == SHOTS + + +def _mixed_states(n_qubits: int) -> Circuit: + noise = Noise.PhaseFlip(probability=0.2) + circ = Circuit() + for qubit in range(0, n_qubits - 2, 3): + circ.x(qubit).y(qubit + 1).cnot(qubit, qubit + 2).x(qubit + 1).z(qubit + 2) + circ.apply_gate_noise(noise, target_qubits=[qubit, qubit + 2]) + + # attach the result types + circ.probability() + circ.expectation(observable=Observable.Z(), target=0) + + return circ diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 2b6cb591..99be2102 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -11,9 +11,13 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import math + import pytest from gate_model_device_testing_utils import ( batch_bell_pair_testing, + get_tol, + many_layers, multithreaded_bell_pair_testing, no_result_types_bell_pair_testing, qubit_ordering_testing, @@ -39,8 +43,9 @@ SHOTS = 8000 SV1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" -SIMULATOR_ARNS = [SV1_ARN] -ARNS_WITH_SHOTS = [(SV1_ARN, SHOTS), (SV1_ARN, 0)] +DM1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/dm1" +SIMULATOR_ARNS = [SV1_ARN, DM1_ARN] +ARNS_WITH_SHOTS = [(SV1_ARN, SHOTS), (SV1_ARN, 0), (DM1_ARN, SHOTS), (DM1_ARN, 0)] @pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) @@ -57,11 +62,18 @@ def test_qubit_ordering(simulator_arn, aws_session, s3_destination_folder): qubit_ordering_testing(device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder}) -@pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -def test_result_types_no_shots(simulator_arn, aws_session, s3_destination_folder): +@pytest.mark.parametrize( + "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 +): device = AwsDevice(simulator_arn, aws_session) result_types_zero_shots_bell_pair_testing( - device, False, {"shots": 0, "s3_destination_folder": s3_destination_folder} + device, + False, + {"shots": 0, "s3_destination_folder": s3_destination_folder}, + include_amplitude, ) @@ -203,3 +215,21 @@ def test_batch_bell_pair(simulator_arn, aws_session, s3_destination_folder): batch_bell_pair_testing( device, {"shots": SHOTS, "s3_destination_folder": 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): + num_qubits = 10 + circuit = many_layers(num_qubits, num_layers) + device = AwsDevice(simulator_arn, aws_session) + + tol = get_tol(SHOTS) + result = device.run(circuit, shots=SHOTS, s3_destination_folder=s3_destination_folder).result() + probabilities = result.measurement_probabilities + probability_sum = 0 + for bitstring in probabilities: + assert probabilities[bitstring] >= 0 + probability_sum += probabilities[bitstring] + assert math.isclose(probability_sum, 1, rel_tol=tol["rtol"], abs_tol=tol["atol"]) + assert len(result.measurements) == SHOTS From 53c11f3f1768384d5bb44df3a170694216033226 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 2 Dec 2021 18:13:33 +0000 Subject: [PATCH 0404/1165] prepare release v1.11.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 b2332336..f489bd52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.11.0 (2021-12-02) + +### Features + + * Adding integration tests for DM1 + ## v1.10.0 (2021-11-29) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index b84f6ebc..6ad8bf3b 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.10.1.dev0" +__version__ = "1.11.0" From 2575bb02d6e3cc606791b2594114330ae0b02de1 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 2 Dec 2021 18:13:33 +0000 Subject: [PATCH 0405/1165] update development version to v1.11.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 6ad8bf3b..7b1da559 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.11.0" +__version__ = "1.11.1.dev0" From 6a836e762e892aaa0419bcdbcc7763fe3181f0d3 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 8 Dec 2021 13:18:21 -0800 Subject: [PATCH 0406/1165] fix: remove extraneous reference from local job container setup (#292) --- .../jobs/local/local_job_container_setup.py | 15 --------------- .../jobs/local/test_local_job_container_setup.py | 2 -- 2 files changed, 17 deletions(-) diff --git a/src/braket/jobs/local/local_job_container_setup.py b/src/braket/jobs/local/local_job_container_setup.py index 0f7eac02..0adc9042 100644 --- a/src/braket/jobs/local/local_job_container_setup.py +++ b/src/braket/jobs/local/local_job_container_setup.py @@ -44,7 +44,6 @@ def setup_container( run_environment_variables.update( _get_env_script_mode_config(creation_kwargs["algorithmSpecification"]["scriptModeConfig"]) ) - run_environment_variables.update(_get_env_additional_lib()) run_environment_variables.update(_get_env_default_vars(aws_session, **creation_kwargs)) if _copy_hyperparameters(container, **creation_kwargs): run_environment_variables.update(_get_env_hyperparameters()) @@ -113,20 +112,6 @@ def _get_env_script_mode_config(script_mode_config: Dict[str, str]) -> Dict[str, return result -def _get_env_additional_lib() -> Dict[str, str]: - """For preview, we have some libraries that are not available publicly (yet). The container - will install these libraries if we set this env variable. - - Returns: - (Dict[str, str]): The set of key/value pairs that should be added as environment variables - to the running container. - """ - return { - "AMZN_BRAKET_IMAGE_SETUP_SCRIPT": "s3://amazon-braket-external-assets-preview-us-west-2/" - "HybridJobsAccess/scripts/setup-container.sh", - } - - def _get_env_default_vars(aws_session: AwsSession, **creation_kwargs) -> 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. 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 93e114ef..51199055 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 @@ -81,8 +81,6 @@ def expected_envs(): return { "AMZN_BRAKET_CHECKPOINT_DIR": "/opt/omega/checkpoints", "AMZN_BRAKET_DEVICE_ARN": "test device ARN", - "AMZN_BRAKET_IMAGE_SETUP_SCRIPT": "s3://amazon-braket-external-assets-preview-us-west-2/" - "HybridJobsAccess/scripts/setup-container.sh", "AMZN_BRAKET_JOB_NAME": "Test-Job-Name", "AMZN_BRAKET_JOB_RESULTS_DIR": "/opt/braket/model", "AMZN_BRAKET_JOB_RESULTS_S3_PATH": "test_location/Test-Job-Name/output", From d817ed14cc82b47e8615f0727ce4900cd0a7432d Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 9 Dec 2021 18:12:29 +0000 Subject: [PATCH 0407/1165] prepare release v1.11.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 f489bd52..bcdf5612 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.11.1 (2021-12-09) + +### Bug Fixes and Other Changes + + * remove extraneous reference from local job container setup + ## v1.11.0 (2021-12-02) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 7b1da559..f9bc49a0 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.11.1.dev0" +__version__ = "1.11.1" From d9baf46566925af17ed8664a32882b9a79235cbd Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 9 Dec 2021 18:12:29 +0000 Subject: [PATCH 0408/1165] update development version to v1.11.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 f9bc49a0..e708eff3 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.11.1" +__version__ = "1.11.2.dev0" From 11ade6896a0e449a383713ce2fc1f74a06c463e9 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Mon, 24 Jan 2022 12:29:32 -0800 Subject: [PATCH 0409/1165] feature: optimize IAM role retrieval (#299) --- src/braket/aws/aws_session.py | 7 ++++--- test/unit_tests/braket/aws/test_aws_session.py | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 2d7cf3a2..65117be5 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -210,17 +210,18 @@ def get_default_jobs_role(self) -> str: """ Returns the role ARN for the default jobs role created in the Amazon Braket Console. It will pick the first role it finds with the `RoleName` prefix - `AmazonBraketJobsExecutionRole`. + `AmazonBraketJobsExecutionRole` with a `PathPrefix` of `/service-role/`. Returns: (str): The ARN for the default IAM role for jobs execution created in the Amazon Braket console. Raises: - RuntimeError: If no roles can be found with the prefix `AmazonBraketJobsExecutionRole`. + RuntimeError: If no roles can be found with the prefix + `/service-role/AmazonBraketJobsExecutionRole`. """ roles_paginator = self.iam_client.get_paginator("list_roles") - for page in roles_paginator.paginate(): + for page in roles_paginator.paginate(PathPrefix="/service-role/"): for role in page.get("Roles", []): if role["RoleName"].startswith("AmazonBraketJobsExecutionRole"): return role["Arn"] diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index 5032eed2..b51a4c05 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -834,6 +834,7 @@ def test_get_default_jobs_role(aws_session, job_role_arn, job_role_name): "IsTruncated": True, "Marker": "resp-marker", }, + {"PathPrefix": "/service-role/"}, ) stub.add_response( "list_roles", @@ -849,7 +850,7 @@ def test_get_default_jobs_role(aws_session, job_role_arn, job_role_name): ], "IsTruncated": False, }, - {"Marker": "resp-marker"}, + {"Marker": "resp-marker", "PathPrefix": "/service-role/"}, ) aws_session._iam = iam_client assert aws_session.get_default_jobs_role() == job_role_arn @@ -874,6 +875,7 @@ def test_get_default_jobs_role_not_found(aws_session, job_role_arn, job_role_nam "IsTruncated": True, "Marker": "resp-marker", }, + {"PathPrefix": "/service-role/"}, ) stub.add_response( "list_roles", @@ -889,7 +891,7 @@ def test_get_default_jobs_role_not_found(aws_session, job_role_arn, job_role_nam ], "IsTruncated": False, }, - {"Marker": "resp-marker"}, + {"Marker": "resp-marker", "PathPrefix": "/service-role/"}, ) aws_session._iam = iam_client with pytest.raises(RuntimeError): From aa3e4ae5da1e3e9cf8b8bb0ebf6b10fd13f13e56 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Mon, 24 Jan 2022 12:47:20 -0800 Subject: [PATCH 0410/1165] fix: Enable jobs integration tests (#289) --- test/integ_tests/test_create_local_quantum_job.py | 2 -- test/integ_tests/test_create_quantum_job.py | 4 ---- 2 files changed, 6 deletions(-) diff --git a/test/integ_tests/test_create_local_quantum_job.py b/test/integ_tests/test_create_local_quantum_job.py index a2c8f9e6..838a0132 100644 --- a/test/integ_tests/test_create_local_quantum_job.py +++ b/test/integ_tests/test_create_local_quantum_job.py @@ -22,7 +22,6 @@ from braket.jobs.local import LocalQuantumJob -@pytest.mark.skip() def test_completed_local_job(aws_session, capsys): """Asserts the job is completed with the respective files and folders for logs, results and checkpoints. Validate the results are what we expect. Also, @@ -103,7 +102,6 @@ def test_completed_local_job(aws_session, capsys): os.chdir(current_dir) -@pytest.mark.skip() def test_failed_local_job(aws_session, capsys): """Asserts the job is failed with the output, checkpoints not created in bucket and only logs are populated. Validate the calling result function raises diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py index dc8e7615..b88405e6 100644 --- a/test/integ_tests/test_create_quantum_job.py +++ b/test/integ_tests/test_create_quantum_job.py @@ -17,12 +17,9 @@ import tempfile from pathlib import Path -import pytest - from braket.aws.aws_quantum_job import AwsQuantumJob -@pytest.mark.skip() def test_failed_quantum_job(aws_session, capsys): """Asserts the job is failed with the output, checkpoints, tasks not created in bucket and only input is uploaded to s3. Validate the @@ -78,7 +75,6 @@ def test_failed_quantum_job(aws_session, capsys): ) -@pytest.mark.skip() def test_completed_quantum_job(aws_session, capsys): """Asserts the job is completed with the output, checkpoints, tasks and script folder created in S3 for respective job. Validate the results are From 9ce521d273f7b84ecc51077341792b92c9074ac0 Mon Sep 17 00:00:00 2001 From: Mark Sweat <37444469+surfkansas@users.noreply.github.com> Date: Mon, 24 Jan 2022 14:58:26 -0600 Subject: [PATCH 0411/1165] feature: Added is_available property to AwsDevice (#290) Added is_available property to AwsDevice that parses the availability window and current device status to return a boolean flag if the device is currently available. --- src/braket/aws/aws_device.py | 60 +++++++- test/unit_tests/braket/aws/test_aws_device.py | 137 ++++++++++++++++++ 2 files changed, 196 insertions(+), 1 deletion(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 59d46179..44862279 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -14,6 +14,7 @@ from __future__ import annotations import os +from datetime import datetime from enum import Enum from typing import List, Optional, Union @@ -25,7 +26,7 @@ from braket.aws.aws_quantum_task_batch import AwsQuantumTaskBatch from braket.aws.aws_session import AwsSession from braket.circuits import Circuit -from braket.device_schema import DeviceCapabilities, GateModelQpuParadigmProperties +from braket.device_schema import DeviceCapabilities, ExecutionDay, GateModelQpuParadigmProperties from braket.device_schema.dwave import DwaveProviderProperties from braket.devices.device import Device from braket.schema_common import BraketSchemaBase @@ -274,6 +275,63 @@ def arn(self) -> str: """str: Return the ARN of the device""" return self._arn + @property + def is_available(self) -> bool: + """bool: Return if the device is currently available""" + if self.status != "ONLINE": + return False + + is_available_result = False + + current_datetime_utc = datetime.utcnow() + for execution_window in self.properties.service.executionWindows: + weekday = current_datetime_utc.weekday() + current_time_utc = current_datetime_utc.time().replace(microsecond=0) + + if ( + execution_window.windowEndHour < execution_window.windowStartHour + and current_time_utc < execution_window.windowEndHour + ): + weekday = (weekday - 1) % 7 + + matched_day = execution_window.executionDay == ExecutionDay.EVERYDAY + matched_day = matched_day or ( + execution_window.executionDay == ExecutionDay.WEEKDAYS and weekday < 5 + ) + matched_day = matched_day or ( + execution_window.executionDay == ExecutionDay.WEEKENDS and weekday > 4 + ) + ordered_days = ( + ExecutionDay.MONDAY, + ExecutionDay.TUESDAY, + ExecutionDay.WEDNESDAY, + ExecutionDay.THURSDAY, + ExecutionDay.FRIDAY, + ExecutionDay.SATURDAY, + ExecutionDay.SUNDAY, + ) + matched_day = matched_day or ( + execution_window.executionDay in ordered_days + and ordered_days.index(execution_window.executionDay) == weekday + ) + + matched_time = ( + execution_window.windowStartHour < execution_window.windowEndHour + and execution_window.windowStartHour + <= current_time_utc + <= execution_window.windowEndHour + ) or ( + execution_window.windowEndHour < execution_window.windowStartHour + and ( + current_time_utc >= execution_window.windowStartHour + or current_time_utc <= execution_window.windowEndHour + ) + ) + + is_available_result = is_available_result or (matched_day and matched_time) + + return is_available_result + @property # TODO: Add a link to the boto3 docs def properties(self) -> DeviceCapabilities: diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 911ad932..d78fc59e 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -10,7 +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. +import json import os +from datetime import datetime from unittest.mock import Mock, patch import pytest @@ -29,6 +31,7 @@ from braket.aws import AwsDevice, AwsDeviceType, AwsQuantumTask from braket.circuits import Circuit +from braket.device_schema.device_execution_window import DeviceExecutionWindow from braket.device_schema.dwave import DwaveDeviceCapabilities from braket.device_schema.rigetti import RigettiDeviceCapabilities from braket.device_schema.simulators import GateModelSimulatorDeviceCapabilities @@ -848,3 +851,137 @@ def test_get_devices_simulators_only(mock_copy_session, aws_session): @pytest.mark.xfail(raises=ValueError) def test_get_devices_invalid_order_by(): AwsDevice.get_devices(order_by="foo") + + +@patch("braket.aws.aws_device.datetime") +def test_get_device_availability(mock_utc_now): + class Expando(object): + pass + + class MockDevice(AwsDevice): + 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, + } + ) + ) + ) + self._properties.service.executionWindows = execution_windows + + test_sets = ( + { + "test_devices": ( + ("always_on_device", MockDevice("ONLINE", ("Everyday", "00:00", "23:59:59"))), + ("offline_device", MockDevice("OFFLINE", ("Everyday", "00:00", "23:59:59"))), + ("retired_device", MockDevice("RETIRED", ("Everyday", "00:00", "23:59:59"))), + ("missing_schedule_device", MockDevice("ONLINE")), + ), + "test_items": ( + (datetime(2021, 12, 6, 10, 0, 0), (1, 0, 0, 0)), + (datetime(2021, 12, 7, 10, 0, 0), (1, 0, 0, 0)), + (datetime(2021, 12, 8, 10, 0, 0), (1, 0, 0, 0)), + (datetime(2021, 12, 9, 10, 0, 0), (1, 0, 0, 0)), + (datetime(2021, 12, 10, 10, 0, 0), (1, 0, 0, 0)), + (datetime(2021, 12, 11, 10, 0, 0), (1, 0, 0, 0)), + (datetime(2021, 12, 12, 10, 0, 0), (1, 0, 0, 0)), + ), + }, + { + "test_devices": ( + ("midday_everyday_device", MockDevice("ONLINE", ("Everyday", "07:00", "17:00"))), + ("midday_weekday_device", MockDevice("ONLINE", ("Weekdays", "07:00", "17:00"))), + ("midday_weekend_device", MockDevice("ONLINE", ("Weekend", "07:00", "17:00"))), + ("evening_everyday_device", MockDevice("ONLINE", ("Everyday", "17:00", "07:00"))), + ("evening_weekday_device", MockDevice("ONLINE", ("Weekdays", "17:00", "07:00"))), + ("evening_weekend_device", MockDevice("ONLINE", ("Weekend", "17:00", "07:00"))), + ), + "test_items": ( + (datetime(2021, 12, 6, 5, 0, 0), (0, 0, 0, 1, 0, 1)), + (datetime(2021, 12, 6, 10, 0, 0), (1, 1, 0, 0, 0, 0)), + (datetime(2021, 12, 6, 20, 0, 0), (0, 0, 0, 1, 1, 0)), + (datetime(2021, 12, 7, 5, 0, 0), (0, 0, 0, 1, 1, 0)), + (datetime(2021, 12, 7, 10, 0, 0), (1, 1, 0, 0, 0, 0)), + (datetime(2021, 12, 7, 20, 0, 0), (0, 0, 0, 1, 1, 0)), + (datetime(2021, 12, 8, 5, 0, 0), (0, 0, 0, 1, 1, 0)), + (datetime(2021, 12, 8, 10, 0, 0), (1, 1, 0, 0, 0, 0)), + (datetime(2021, 12, 8, 20, 0, 0), (0, 0, 0, 1, 1, 0)), + (datetime(2021, 12, 9, 5, 0, 0), (0, 0, 0, 1, 1, 0)), + (datetime(2021, 12, 9, 10, 0, 0), (1, 1, 0, 0, 0, 0)), + (datetime(2021, 12, 9, 20, 0, 0), (0, 0, 0, 1, 1, 0)), + (datetime(2021, 12, 10, 5, 0, 0), (0, 0, 0, 1, 1, 0)), + (datetime(2021, 12, 10, 10, 0, 0), (1, 1, 0, 0, 0, 0)), + (datetime(2021, 12, 10, 20, 0, 0), (0, 0, 0, 1, 1, 0)), + (datetime(2021, 12, 11, 5, 0, 0), (0, 0, 0, 1, 1, 0)), + (datetime(2021, 12, 11, 10, 0, 0), (1, 0, 1, 0, 0, 0)), + (datetime(2021, 12, 11, 20, 0, 0), (0, 0, 0, 1, 0, 1)), + (datetime(2021, 12, 12, 5, 0, 0), (0, 0, 0, 1, 0, 1)), + (datetime(2021, 12, 12, 10, 0, 0), (1, 0, 1, 0, 0, 0)), + (datetime(2021, 12, 12, 20, 0, 0), (0, 0, 0, 1, 0, 1)), + ), + }, + { + "test_devices": ( + ("monday_device", MockDevice("ONLINE", ("Monday", "07:00", "17:00"))), + ("tuesday_device", MockDevice("ONLINE", ("Tuesday", "07:00", "17:00"))), + ("wednesday_device", MockDevice("ONLINE", ("Wednesday", "07:00", "17:00"))), + ("thursday_device", MockDevice("ONLINE", ("Thursday", "07:00", "17:00"))), + ("friday_device", MockDevice("ONLINE", ("Friday", "07:00", "17:00"))), + ("saturday_device", MockDevice("ONLINE", ("Saturday", "07:00", "17:00"))), + ("sunday_device", MockDevice("ONLINE", ("Sunday", "07:00", "17:00"))), + ( + "monday_friday_device", + MockDevice( + "ONLINE", ("Monday", "07:00", "17:00"), ("Friday", "07:00", "17:00") + ), + ), + ), + "test_items": ( + (datetime(2021, 12, 6, 5, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), + (datetime(2021, 12, 6, 10, 0, 0), (1, 0, 0, 0, 0, 0, 0, 1)), + (datetime(2021, 12, 6, 20, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), + (datetime(2021, 12, 7, 5, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), + (datetime(2021, 12, 7, 10, 0, 0), (0, 1, 0, 0, 0, 0, 0, 0)), + (datetime(2021, 12, 7, 20, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), + (datetime(2021, 12, 8, 5, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), + (datetime(2021, 12, 8, 10, 0, 0), (0, 0, 1, 0, 0, 0, 0, 0)), + (datetime(2021, 12, 8, 20, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), + (datetime(2021, 12, 9, 5, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), + (datetime(2021, 12, 9, 10, 0, 0), (0, 0, 0, 1, 0, 0, 0, 0)), + (datetime(2021, 12, 9, 20, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), + (datetime(2021, 12, 10, 5, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), + (datetime(2021, 12, 10, 10, 0, 0), (0, 0, 0, 0, 1, 0, 0, 1)), + (datetime(2021, 12, 10, 20, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), + (datetime(2021, 12, 11, 5, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), + (datetime(2021, 12, 11, 10, 0, 0), (0, 0, 0, 0, 0, 1, 0, 0)), + (datetime(2021, 12, 11, 20, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), + (datetime(2021, 12, 12, 5, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), + (datetime(2021, 12, 12, 10, 0, 0), (0, 0, 0, 0, 0, 0, 1, 0)), + (datetime(2021, 12, 12, 20, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), + ), + }, + ) + + 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 + + # flake8: noqa: C501 + for i in range(len(test_item[1])): + device_name = test_set["test_devices"][i][0] + device = test_set["test_devices"][i][1] + 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}" From 24b84269d25ca154aa59d05571a159b521769b5f Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 25 Jan 2022 18:21:14 +0000 Subject: [PATCH 0412/1165] prepare release v1.12.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 bcdf5612..b2b92382 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.12.0 (2022-01-25) + +### Features + + * Added is_available property to AwsDevice + * optimize IAM role retrieval + +### Bug Fixes and Other Changes + + * Enable jobs integration tests + ## v1.11.1 (2021-12-09) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index e708eff3..3af19862 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.11.2.dev0" +__version__ = "1.12.0" From 21466b39a816505fb8615a1f24b113531d741371 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 25 Jan 2022 18:21:14 +0000 Subject: [PATCH 0413/1165] update development version to v1.12.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 3af19862..72829d8e 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.12.0" +__version__ = "1.12.1.dev0" From 560739e2333e33f3a6cdd1f0f538f9a392f0d154 Mon Sep 17 00:00:00 2001 From: Mark C Date: Wed, 26 Jan 2022 22:04:29 +0000 Subject: [PATCH 0414/1165] feature: added controlled-sqrt-not gate (#297) * feature: added controlled-sqrt-not gate This makes certain circuits, like CHSH, more straightforward. This commit works in line with the following branches (also committed, separately): https://github.com/unprovable/amazon-braket-schemas-python.git@ctrl-v-gate https://github.com/unprovable/amazon-braket-default-simulator-python.git@ctrl-v-gate * fix: ran tox linters * fix: reverted tox.ini Co-authored-by: Mark C Co-authored-by: Mark C Co-authored-by: Cody Wang --- src/braket/circuits/gates.py | 45 +++++++++++++++++++ test/unit_tests/braket/circuits/test_gates.py | 1 + tox.ini | 2 +- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 096d6488..f7c31af6 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -1046,6 +1046,51 @@ def cphaseshift10(control: QubitInput, target: QubitInput, angle: float) -> Inst Gate.register_gate(CPhaseShift10) +class CV(Gate): + """Controlled Sqrt of NOT gate.""" + + def __init__(self): + super().__init__(qubit_count=None, ascii_symbols=["C", "V"]) + + def to_ir(self, target: QubitSet): + return ir.CV.construct(control=target[0], target=target[1]) + + def to_matrix(self) -> np.ndarray: + return np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 0.5 + 0.5j, 0.5 - 0.5j], # if the control bit, then apply the V gate + [0.0, 0.0, 0.5 - 0.5j, 0.5 + 0.5j], # which is the sqrt(NOT) gate. + ], + dtype=complex, + ) + + @staticmethod + def fixed_qubit_count() -> int: + return 2 + + @staticmethod + @circuit.subroutine(register=True) + def cv(control: QubitInput, target: QubitInput) -> Instruction: + """Registers this function into the circuit class. + + Args: + control (Qubit or int): Control qubit index. + target (Qubit or int): Target qubit index. + + Returns: + Instruction: CV instruction. + + Examples: + >>> circ = Circuit().cv(0, 1) + """ + return Instruction(CV(), target=[control, target]) + + +Gate.register_gate(CV) + + class CY(Gate): """Controlled Pauli-Y gate.""" diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 5d575b95..fbb35c6c 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -42,6 +42,7 @@ (Gate.Ry, "ry", ir.Ry, [SingleTarget, Angle], {}), (Gate.Rz, "rz", ir.Rz, [SingleTarget, Angle], {}), (Gate.CNot, "cnot", ir.CNot, [SingleTarget, SingleControl], {}), + (Gate.CV, "cv", ir.CV, [SingleTarget, SingleControl], {}), (Gate.CCNot, "ccnot", ir.CCNot, [SingleTarget, DoubleControl], {}), (Gate.Swap, "swap", ir.Swap, [DoubleTarget], {}), (Gate.CSwap, "cswap", ir.CSwap, [SingleControl, DoubleTarget], {}), diff --git a/tox.ini b/tox.ini index 1ab05fcc..d6945f94 100644 --- a/tox.ini +++ b/tox.ini @@ -89,4 +89,4 @@ commands = deps = # If you need to test on a certain branch, add @ after .git git+https://github.com/aws/amazon-braket-schemas-python.git - git+https://github.com/aws/amazon-braket-default-simulator-python.git + git+https://github.com/aws/amazon-braket-default-simulator-python.git \ No newline at end of file From 88840b40d357917c81e312d890db70b446f87b3c Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 27 Jan 2022 18:21:43 +0000 Subject: [PATCH 0415/1165] prepare release v1.13.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 b2b92382..940818da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.13.0 (2022-01-27) + +### Features + + * added controlled-sqrt-not gate + ## v1.12.0 (2022-01-25) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 72829d8e..8cccef36 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.12.1.dev0" +__version__ = "1.13.0" From 0967f974a4e4ccea9f4c8293be30973b3e915653 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 27 Jan 2022 18:21:43 +0000 Subject: [PATCH 0416/1165] update development version to v1.13.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 8cccef36..62a7f280 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.13.0" +__version__ = "1.13.1.dev0" From 0ed70c76eea989788539ae560d394123a5eadc8a Mon Sep 17 00:00:00 2001 From: mbeach-aws <85963088+mbeach-aws@users.noreply.github.com> Date: Tue, 1 Feb 2022 16:44:26 -0500 Subject: [PATCH 0417/1165] feature: adding TwoQubitPauliChannel (#300) --- src/braket/circuits/noise.py | 104 ++++++++++++- src/braket/circuits/noises.py | 137 +++++++++++++++++- src/braket/circuits/observables.py | 2 +- .../circuits/quantum_operator_helpers.py | 2 +- src/braket/circuits/unitary_calculation.py | 4 +- .../tasks/gate_model_quantum_task_result.py | 2 +- .../test_tensor_network_simulator.py | 4 +- test/unit_tests/braket/circuits/test_noise.py | 47 ++++++ .../unit_tests/braket/circuits/test_noises.py | 51 +++++++ .../braket/circuits/test_observables.py | 2 +- 10 files changed, 338 insertions(+), 17 deletions(-) diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py index e5c0dab4..303080aa 100644 --- a/src/braket/circuits/noise.py +++ b/src/braket/circuits/noise.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, Sequence +from typing import Any, Dict, Optional, Sequence from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.qubit_set import QubitSet @@ -121,7 +121,7 @@ def __init__( def probability(self) -> float: """ Returns: - probability (float): The probability that parameterizes the noise channel. + probability (float): The probability that parametrizes the noise channel. """ return self._probability @@ -163,7 +163,7 @@ def __init__( def probability(self) -> float: """ Returns: - probability (float): The probability that parameterizes the noise channel. + probability (float): The probability that parametrizes the noise channel. """ return self._probability @@ -205,7 +205,7 @@ def __init__( def probability(self) -> float: """ Returns: - probability (float): The probability that parameterizes the noise channel. + probability (float): The probability that parametrizes the noise channel. """ return self._probability @@ -213,10 +213,102 @@ def __repr__(self): return f"{self.name}('probability': {self.probability}, 'qubit_count': {self.qubit_count})" +class MultiQubitPauliNoise(Noise): + """ + Class `MultiQubitPauliNoise` represents a general multi-qubit Pauli channel, + parameterized by up to 4**N - 1 probabilities. + """ + + _allowed_substrings = {"I", "X", "Y", "Z"} + + def __init__( + self, + probabilities: Dict[str, float], + qubit_count: Optional[int], + ascii_symbols: Sequence[str], + ): + """[summary] + + Args: + probabilities (Dict[str, float]): A dictionary with Pauli string as the keys, + and the probabilities as values, i.e. {"XX": 0.1. "IZ": 0.2}. + qubit_count (Optional[int]): The number of qubits the Pauli noise acts on. + ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when + 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: + ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or + `ascii_symbols` length != `qubit_count`. Also if `probabilities` are not `float`s, + any `probabilities` > 1, or `probabilities` < 0, or if the sum of all + probabilities is > 1, + or if "II" is specified as a Pauli string. + Also if any Pauli string contains invalid strings. + Also if the length of probabilities is greater than 4**qubit_count. + 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 + + if not probabilities: + raise ValueError("Pauli dictionary must not be empty.") + + identity = self.qubit_count * "I" + if identity in probabilities: + raise ValueError( + f"{identity} is not allowed as a key. Please enter only non-identity Pauli strings." + ) + + for pauli_string, prob in probabilities.items(): + if not isinstance(pauli_string, str): + raise TypeError(f"Type of {pauli_string} was not a string.") + if len(pauli_string) != self.qubit_count: + raise ValueError( + ( + "Length of each Pauli string must be equal to number of qubits. " + f"{pauli_string} had length {len(pauli_string)} instead of length {self.qubit_count}." # noqa + ) + ) + if not isinstance(prob, float): + raise TypeError( + ( + "Probabilities must be a float type. " + f"The probability for {pauli_string} was of type {type(prob)}." + ) + ) + if not set(pauli_string) <= self._allowed_substrings: + raise ValueError( + ( + "Strings must be Pauli strings consisting of only [I, X, Y, Z]. " + f"Received {pauli_string}." + ) + ) + if prob < 0.0 or prob > 1.0: + raise ValueError( + ( + "Individual probabilities must be real numbers in the interval [0, 1]. " + f"Probability for {pauli_string} was {prob}." + ) + ) + total_prob = sum(probabilities.values()) + if total_prob > 1.0 or total_prob < 0.0: + raise ValueError( + ( + "Total probability must be a real number in the interval [0, 1]. " + f"Total probability was {total_prob}." + ) + ) + + def __repr__(self): + return f"{self.name}('probabilities' : {self.probabilities}, 'qubit_count': {self.qubit_count})" # noqa + + class PauliNoise(Noise): """ - Class `PauliNoise` represents the general Pauli noise channel on N qubits - parameterized by three probabilities. + Class `PauliNoise` represents the a single-qubit Pauli noise channel + acting on one qubit. It is parameterized by three probabilities. """ def __init__( diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py index 1f98e0fd..7d79fbaa 100644 --- a/src/braket/circuits/noises.py +++ b/src/braket/circuits/noises.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 typing import Iterable +import itertools +from typing import Dict, Iterable import numpy as np @@ -21,6 +22,7 @@ from braket.circuits.noise import ( DampingNoise, GeneralizedAmplitudeDampingNoise, + MultiQubitPauliNoise, Noise, PauliNoise, SingleProbabilisticNoise, @@ -552,6 +554,135 @@ def two_qubit_dephasing( Noise.register_noise(TwoQubitDephasing) +class TwoQubitPauliChannel(MultiQubitPauliNoise): + """Two-Qubit Pauli noise channel which transforms a + density matrix :math:`\\rho` according to: + + .. math:: + \\rho \\Rightarrow (1-p) \\rho + + p_{IX} IX \\rho IX^{\\dagger} + + p_{IY} IY \\rho IY^{\\dagger} + + p_{IZ} IZ \\rho IZ^{\\dagger} + + p_{XI} XI \\rho XI^{\\dagger} + + p_{XX} XX \\rho XX^{\\dagger} + + p_{XY} XY \\rho XY^{\\dagger} + + p_{XZ} XZ \\rho XZ^{\\dagger} + + p_{YI} YI \\rho YI^{\\dagger} + + p_{YX} YX \\rho YX^{\\dagger} + + p_{YY} YY \\rho YY^{\\dagger} + + p_{YZ} YZ \\rho YZ^{\\dagger} + + p_{ZI} ZI \\rho ZI^{\\dagger} + + p_{ZX} ZX \\rho ZX^{\\dagger} + + p_{ZY} ZY \\rho ZY^{\\dagger} + + p_{ZZ} ZZ \\rho ZZ^{\\dagger}) + where + + .. math:: + I = \\left( + \\begin{matrix} + 1 & 0 \\\\ + 0 & 1 + \\end{matrix} + \\right) + + X = \\left( + \\begin{matrix} + 0 & 1 \\\\ + 1 & 0 + \\end{matrix} + \\right) + + Y = \\left( + \\begin{matrix} + 0 & -i \\\\ + i & 0 + \\end{matrix} + \\right) + + Z = \\left( + \\begin{matrix} + 1 & 0 \\\\ + 0 & -1 + \\end{matrix} + \\right) + + p = \\text{sum of all probabilities} + + This noise channel is shown as `PC_2({"pauli_string": probability})` in circuit diagrams. + """ + + _paulis = { + "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] + + def __init__(self, probabilities: Dict[str, float]): + super().__init__( + probabilities=probabilities, + qubit_count=None, + ascii_symbols=[ + f"PC2({probabilities})", + f"PC2({probabilities})", + ], + ) + + total_prob = sum(self.probabilities.values()) + + K_list = [np.sqrt(1 - total_prob) * np.identity(4)] # "II" element + for pstring in self._names_list[1:]: # ignore "II" + if pstring in self.probabilities: + mat = np.sqrt(self.probabilities[pstring]) * np.kron( + self._paulis[pstring[0]], self._paulis[pstring[1]] + ) + K_list.append(mat) + else: + K_list.append(np.zeros((4, 4))) + self._matrix = K_list + + def to_ir(self, target: QubitSet): + return ir.MultiQubitPauliChannel.construct( + targets=[target[0], target[1]], probabilities=self.probabilities + ) + + def to_matrix(self) -> Iterable[np.ndarray]: + return self._matrix + + @staticmethod + def fixed_qubit_count() -> int: + return 2 + + @staticmethod + @circuit.subroutine(register=True) + def two_qubit_pauli_channel( + target1: QubitInput, target2: QubitInput, probabilities: Dict[str, float] + ) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit, int, or iterable of Qubit / int): Target qubits + probability (float): Probability of two-qubit Pauli channel. + + Returns: + Iterable[Instruction]: `Iterable` of Depolarizing instructions. + + Examples: + >>> circ = Circuit().two_qubit_pauli_channel(0, 1, {"XX": 0.1}) + """ + return [ + Instruction( + Noise.TwoQubitPauliChannel(probabilities=probabilities), + target=[target1, target2], + ) + ] + + +Noise.register_noise(TwoQubitPauliChannel) + + class AmplitudeDamping(DampingNoise): """AmplitudeDamping noise channel which transforms a density matrix :math:`\\rho` according to: @@ -789,7 +920,7 @@ class Kraus(Noise): Args: matrices (Iterable[np.array]): A list of matrices that define a noise - channel. These matrices need to satisify the requirement of CPTP map. + 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`. @@ -797,7 +928,7 @@ class Kraus(Noise): 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 satisify CPTP condition. + or the `matrices` do not satisfy CPTP condition. """ def __init__(self, matrices: Iterable[np.ndarray], display_name: str = "KR"): diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 0fc0c9e4..62b523f0 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -230,7 +230,7 @@ def eigenvalues(self): def eigenvalue(self, index: int) -> float: if index in self._eigenvalue_indices: return self._eigenvalue_indices[index] - dimension = 2 ** self.qubit_count + dimension = 2**self.qubit_count if index >= dimension: raise ValueError( f"Index {index} requested but observable has at most {dimension} eigenvalues" diff --git a/src/braket/circuits/quantum_operator_helpers.py b/src/braket/circuits/quantum_operator_helpers.py index 5a922100..b61a3184 100644 --- a/src/braket/circuits/quantum_operator_helpers.py +++ b/src/braket/circuits/quantum_operator_helpers.py @@ -35,7 +35,7 @@ def verify_quantum_operator_matrix_dimensions(matrix: np.array) -> None: matrix = np.array(matrix, dtype=complex) qubit_count = int(np.log2(matrix.shape[0])) - if 2 ** qubit_count != matrix.shape[0] or qubit_count < 1: + if 2**qubit_count != matrix.shape[0] or qubit_count < 1: raise ValueError(f"`matrix` dimension {matrix.shape[0]} is not a positive power of 2") diff --git a/src/braket/circuits/unitary_calculation.py b/src/braket/circuits/unitary_calculation.py index d532e17b..18ddba6b 100644 --- a/src/braket/circuits/unitary_calculation.py +++ b/src/braket/circuits/unitary_calculation.py @@ -60,7 +60,7 @@ def calculate_unitary(qubit_count: int, instructions: Iterable[Instruction]) -> TypeError: If `instructions` is not composed only of `Gate` instances, i.e. a circuit with `Noise` operators will raise this error. """ - unitary = np.eye(2 ** qubit_count, dtype=complex) + unitary = np.eye(2**qubit_count, dtype=complex) un_tensor = np.reshape(unitary, qubit_count * [2, 2]) for instr in instructions: @@ -83,4 +83,4 @@ def calculate_unitary(qubit_count: int, instructions: Iterable[Instruction]) -> casting="no", ) - return np.reshape(un_tensor, 2 * [2 ** qubit_count]) + return np.reshape(un_tensor, 2 * [2**qubit_count]) diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 3e4a5714..b001f01a 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -424,7 +424,7 @@ def _probability_from_measurements( # count the basis state occurrences, and construct the probability vector basis_states, counts = np.unique(indices, return_counts=True) - probabilities = np.zeros([2 ** num_measured_qubits], dtype=np.float64) + probabilities = np.zeros([2**num_measured_qubits], dtype=np.float64) probabilities[basis_states] = counts / shots return probabilities diff --git a/test/integ_tests/test_tensor_network_simulator.py b/test/integ_tests/test_tensor_network_simulator.py index e96d0cbc..e14f8f73 100644 --- a/test/integ_tests/test_tensor_network_simulator.py +++ b/test/integ_tests/test_tensor_network_simulator.py @@ -64,7 +64,7 @@ def _qft(circuit, num_qubits): for i in range(num_qubits): circuit.h(i) for j in range(1, num_qubits - i): - circuit.cphaseshift(i + j, i, math.pi / (2 ** j)) + circuit.cphaseshift(i + j, i, math.pi / (2**j)) for qubit in range(math.floor(num_qubits / 2)): circuit.swap(qubit, num_qubits - qubit - 1) @@ -78,7 +78,7 @@ def _inverse_qft(circuit, num_qubits): for i in reversed(range(num_qubits)): for j in reversed(range(1, num_qubits - i)): - circuit.cphaseshift(i + j, i, -math.pi / (2 ** j)) + circuit.cphaseshift(i + j, i, -math.pi / (2**j)) circuit.h(i) return circuit diff --git a/test/unit_tests/braket/circuits/test_noise.py b/test/unit_tests/braket/circuits/test_noise.py index edab29ac..0c367bbc 100644 --- a/test/unit_tests/braket/circuits/test_noise.py +++ b/test/unit_tests/braket/circuits/test_noise.py @@ -17,6 +17,7 @@ from braket.circuits.noise import ( DampingNoise, GeneralizedAmplitudeDampingNoise, + MultiQubitPauliNoise, Noise, PauliNoise, SingleProbabilisticNoise, @@ -348,3 +349,49 @@ def __init__(self): Noise.register_noise(_FooNoise) assert Noise._FooNoise().name == _FooNoise().name + + +@pytest.mark.parametrize( + "probs, qubit_count, ascii_symbols", [({"X": 0.1}, 1, ["PC"]), ({"XX": 0.1}, 2, ["PC2", "PC2"])] +) +def test_multi_qubit_noise(probs, qubit_count, ascii_symbols): + MultiQubitPauliNoise(probs, qubit_count, ascii_symbols) + + +@pytest.mark.xfail(raises=ValueError) +class TestMultiQubitNoise: + qubit_count = 1 + ascii_symbols = ["PC2"] + + def test_non_empty(self): + MultiQubitPauliNoise({}, self.qubit_count, self.ascii_symbols) + + def test_non_identity(self): + MultiQubitPauliNoise({"I": 0.1}, self.qubit_count, self.ascii_symbols) + + def test_non_equal_length_paulis(self): + MultiQubitPauliNoise({"X": 0.1, "XY": 0.1}, 1, self.ascii_symbols) + MultiQubitPauliNoise({"X": 0.1, "Y": 0.1}, 2, ["PC2", "PC2"]) + + def test_prob_over_one(self): + MultiQubitPauliNoise({"X": 0.9, "Y": 0.9}, 1, self.ascii_symbols) + MultiQubitPauliNoise({"XX": 0.9, "YY": 0.9}, 1, self.ascii_symbols) + + def test_prob_under_one(self): + MultiQubitPauliNoise({"X": -0.6, "Y": -0.9}, 1, self.ascii_symbols) + MultiQubitPauliNoise({"XX": -0.9, "YY": -0.9}, 2, ["PC2", "PC2"]) + + def test_non_pauli_string(self): + MultiQubitPauliNoise({"T": 0.1}, 1, self.ascii_symbols) + + def test_individual_probs(self): + MultiQubitPauliNoise({"X": -0.1}, 1, self.ascii_symbols) + MultiQubitPauliNoise({"X": 1.1}, 1, self.ascii_symbols) + + @pytest.mark.xfail(raises=TypeError) + def test_keys_strings(self): + MultiQubitPauliNoise({1: 1.1}, 1, self.ascii_symbols) + + @pytest.mark.xfail(raises=TypeError) + def test_values_floats(self): + MultiQubitPauliNoise({"X": "str"}, 1, self.ascii_symbols) diff --git a/test/unit_tests/braket/circuits/test_noises.py b/test/unit_tests/braket/circuits/test_noises.py index 0de9f0e9..37e420ab 100644 --- a/test/unit_tests/braket/circuits/test_noises.py +++ b/test/unit_tests/braket/circuits/test_noises.py @@ -21,6 +21,7 @@ DampingSingleProbability, DoubleControl, DoubleTarget, + MultiProbability, MultiTarget, SingleControl, SingleProbability, @@ -70,6 +71,13 @@ [DoubleTarget, SingleProbability_34], {}, ), + ( + Noise.TwoQubitPauliChannel, + "two_qubit_pauli_channel", + ir.MultiQubitPauliChannel, + [DoubleTarget, MultiProbability], + {}, + ), ( Noise.PauliChannel, "pauli_channel", @@ -178,6 +186,14 @@ def two_dimensional_matrix_list_valid_input(**kwargs): } +def multi_probability_valid_input(**kwargs): + return {"probabilities": {"XX": 0.1}} + + +def multi_probability_invalid_input(**kwargs): + return {"probabilities": {"XX": 1.1}} + + valid_ir_switcher = { "SingleTarget": single_target_valid_input, "DoubleTarget": double_target_valid_ir_input, @@ -187,6 +203,7 @@ def two_dimensional_matrix_list_valid_input(**kwargs): "DampingProbability": damping_probability_valid_input, "DampingSingleProbability": damping_single_probability_valid_input, "TripleProbability": triple_probability_valid_input, + "MultiProbability": multi_probability_valid_input, "SingleControl": single_control_valid_input, "DoubleControl": double_control_valid_ir_input, "MultiTarget": multi_target_valid_input, @@ -245,6 +262,7 @@ def create_valid_target_input(irsubclasses): DampingProbability, TripleProbability, TwoDimensionalMatrixList, + MultiProbability, ] ): pass @@ -268,6 +286,8 @@ def create_valid_noise_class_input(irsubclasses, **kwargs): input.update(damping_probability_valid_input()) if TripleProbability in irsubclasses: input.update(triple_probability_valid_input()) + if MultiProbability in irsubclasses: + input.update(multi_probability_valid_input()) if TwoDimensionalMatrixList in irsubclasses: input.update(two_dimensional_matrix_list_valid_input(**kwargs)) return input @@ -301,6 +321,7 @@ def calculate_qubit_count(irsubclasses): DampingSingleProbability, DampingProbability, TripleProbability, + MultiProbability, TwoDimensionalMatrixList, ] ): @@ -358,6 +379,8 @@ def test_noise_subroutine(testclass, subroutine_name, irclass, irsubclasses, kwa subroutine_input.update(damping_probability_valid_input()) if TripleProbability in irsubclasses: subroutine_input.update(triple_probability_valid_input()) + if MultiProbability in irsubclasses: + subroutine_input.update(multi_probability_valid_input()) circuit1 = subroutine(**subroutine_input) circuit2 = Circuit(instruction_list) @@ -394,3 +417,31 @@ def test_kraus_matrix_target_size_mismatch(): Circuit().kraus( matrices=[np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])], targets=[0] ) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize( + "probs", + [ + {"X": -0.1}, + {"XY": 1.1}, + {"TX": 0.1}, + {"X": 0.5, "Y": 0.6}, + {"X": 0.1, "YY": 0.2}, + {"II": 0.9, "XX": 0.1}, + ], +) +def test_invalid_values_pauli_channel_two_qubit(probs): + Noise.TwoQubitPauliChannel(probs) + + +@pytest.mark.parametrize( + "probs", + [ + {"XY": 0.1}, + {"XX": 0.1, "ZZ": 0.2}, + ], +) +def test_valid_values_pauli_channel_two_qubit(probs): + noise = Noise.TwoQubitPauliChannel(probs) + assert len(noise.to_matrix()) == 16 diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index 2792efbb..fbe72eb8 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -294,6 +294,6 @@ def test_observable_from_ir_tensor_product_value_error(): def compare_eigenvalues(observable, expected): assert np.allclose(observable.eigenvalues, expected) assert np.allclose( - np.array([observable.eigenvalue(i) for i in range(2 ** observable.qubit_count)]), + np.array([observable.eigenvalue(i) for i in range(2**observable.qubit_count)]), expected, ) From 7b70bed4f5dbdba8cf832adc06de7f90993cf546 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 2 Feb 2022 18:21:50 +0000 Subject: [PATCH 0418/1165] prepare release v1.14.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 940818da..b75c6ebb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.14.0 (2022-02-02) + +### Features + + * adding TwoQubitPauliChannel + ## v1.13.0 (2022-01-27) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 62a7f280..2ea1eab8 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.13.1.dev0" +__version__ = "1.14.0" From 8019cd2f334df98dc552dfc884c2713eb4b6e06c Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 2 Feb 2022 18:21:50 +0000 Subject: [PATCH 0419/1165] update development version to v1.14.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 2ea1eab8..9855c1a6 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.14.0" +__version__ = "1.14.1.dev0" From bab6d4b610d8e38605138df9021f5a07296824cf Mon Sep 17 00:00:00 2001 From: Yiheng Duan <98372300+duanyh12@users.noreply.github.com> Date: Thu, 10 Feb 2022 10:39:27 -0800 Subject: [PATCH 0420/1165] documentation: fix documentation on environment variable to match the code. (#302) --- src/braket/jobs/data_persistence.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/braket/jobs/data_persistence.py b/src/braket/jobs/data_persistence.py index f969a598..d41f4d27 100644 --- a/src/braket/jobs/data_persistence.py +++ b/src/braket/jobs/data_persistence.py @@ -110,8 +110,8 @@ def save_job_result( ) -> None: """ Saves the `result_data` to the local output directory that is specified by the container - environment variable `OUTPUT_DIR`, with the filename 'results.json'. The `result_data` - values are serialized to the specified `data_format`. + environment variable `AMZN_BRAKET_JOB_RESULTS_DIR`, with the filename 'results.json'. + The `result_data` values are serialized to the specified `data_format`. Note: This function for storing the results is only for use inside the job container as it writes data to directories and references env variables set in the containers. From c67c3ccdcfc07840403776f9f99a9faae9846c87 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 11 Feb 2022 18:21:21 +0000 Subject: [PATCH 0421/1165] prepare release v1.14.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 b75c6ebb..8f769be9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.14.0.post0 (2022-02-11) + +### Documentation Changes + + * fix documentation on environment variable to match the code. + ## v1.14.0 (2022-02-02) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 9855c1a6..6ad711e8 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.14.1.dev0" +__version__ = "1.14.0.post0" From 8d272573fab7ee4642660c3d6d931fd6a83c419e Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 11 Feb 2022 18:21:21 +0000 Subject: [PATCH 0422/1165] update development version to v1.14.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 6ad711e8..9855c1a6 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.14.0.post0" +__version__ = "1.14.1.dev0" From b018dfc2bce6cb64e1a799c9667458d81d69dfd6 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Tue, 15 Feb 2022 11:10:49 -0800 Subject: [PATCH 0423/1165] feat: Update region switching for regional device arns (#169) (#303) --- src/braket/aws/aws_device.py | 23 +++++ src/braket/aws/aws_quantum_job.py | 29 +++++- test/unit_tests/braket/aws/test_aws_device.py | 88 ++++++++++++++++-- .../braket/aws/test_aws_quantum_job.py | 93 +++++++++++++++++-- 4 files changed, 210 insertions(+), 23 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 44862279..431b9bd4 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -229,6 +229,29 @@ def refresh_metadata(self) -> None: self._populate_properties(self._aws_session) def _get_session_and_initialize(self, session): + device_region = self._arn.split(":")[3] + return ( + self._get_regional_device_session(session) + if device_region + else self._get_non_regional_device_session(session) + ) + + def _get_regional_device_session(self, session): + device_region = self._arn.split(":")[3] + region_session = ( + session + if session.region == device_region + else AwsSession.copy_session(session, device_region) + ) + 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 + + def _get_non_regional_device_session(self, session): current_region = session.region try: self._populate_properties(session) diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index 48f9a698..8a3f7ccb 100644 --- a/src/braket/aws/aws_quantum_job.py +++ b/src/braket/aws/aws_quantum_job.py @@ -531,22 +531,41 @@ def __hash__(self) -> int: @staticmethod def _initialize_session(session_value, device, logger): aws_session = session_value or AwsSession() + device_region = device.split(":")[3] + return ( + AwsQuantumJob._initialize_regional_device_session(aws_session, device, logger) + if device_region + else AwsQuantumJob._initialize_non_regional_device_session(aws_session, device, logger) + ) + + @staticmethod + def _initialize_regional_device_session(aws_session, device, logger): + device_region = device.split(":")[3] current_region = aws_session.region + if current_region != device_region: + aws_session = aws_session.copy_session(region=device_region) + 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 + @staticmethod + def _initialize_non_regional_device_session(aws_session, device, logger): + original_region = aws_session.region try: 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 '{current_region}'") + raise ValueError(f"Simulator '{device}' not found in '{original_region}'") else: raise e - return AwsQuantumJob._find_device_session(aws_session, device, current_region, logger) - - @staticmethod - def _find_device_session(aws_session, device, original_region, logger): for region in frozenset(AwsDevice.REGIONS) - {original_region}: device_session = aws_session.copy_session(region=region) try: diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index d78fc59e..f86e4fa8 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -241,9 +241,14 @@ def test_gate_model_sim_schema(): ) -@pytest.fixture -def arn(): - return "test_arn" +@pytest.fixture( + params=[ + "arn:aws:braket:us-west-1::device/quantum-simulator/amazon/sim", + "arn:aws:braket:::device/quantum-simulator/amazon/sim", + ] +) +def arn(request): + return request.param @pytest.fixture @@ -303,6 +308,7 @@ def _device(arn): def test_device_aws_session(device_capabilities, get_device_data, arn): mock_session = Mock() mock_session.get_device.return_value = get_device_data + mock_session.region = RIGETTI_REGION device = AwsDevice(arn, mock_session) _assert_device_fields(device, device_capabilities, get_device_data) @@ -356,9 +362,69 @@ def test_device_qpu_no_aws_session( _assert_device_fields(device, MOCK_GATE_MODEL_QPU_CAPABILITIES_1, MOCK_GATE_MODEL_QPU_1) +@patch("braket.aws.aws_device.AwsSession.copy_session") +@patch("braket.aws.aws_device.AwsSession") +def test_regional_device_region_switch(aws_session_init, mock_copy_session, aws_session): + device_region = "device-region" + arn = f"arn:aws:braket:{device_region}::device/quantum-simulator/amazon/sim" + aws_session_init.return_value = aws_session + mock_session = Mock() + mock_session.get_device.return_value = MOCK_GATE_MODEL_SIMULATOR + mock_copy_session.return_value = mock_session + device = AwsDevice(arn) + aws_session.get_device.assert_not_called() + mock_copy_session.assert_called_once() + mock_copy_session.assert_called_with(aws_session, device_region) + _assert_device_fields(device, MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES, MOCK_GATE_MODEL_SIMULATOR) + + +@patch("braket.aws.aws_device.AwsSession") +@pytest.mark.parametrize( + "get_device_side_effect, expected_exception", + [ + ( + [ + ClientError( + { + "Error": { + "Code": "ResourceNotFoundException", + } + }, + "getDevice", + ) + ], + ValueError, + ), + ( + [ + ClientError( + { + "Error": { + "Code": "ThrottlingException", + } + }, + "getDevice", + ) + ], + ClientError, + ), + ], +) +def test_regional_device_raises_error( + aws_session_init, get_device_side_effect, expected_exception, aws_session +): + arn = "arn:aws:braket:us-west-1::device/quantum-simulator/amazon/sim" + aws_session.get_device.side_effect = get_device_side_effect + aws_session_init.return_value = aws_session + with pytest.raises(expected_exception): + AwsDevice(arn) + aws_session.get_device.assert_called_once() + + def test_device_refresh_metadata(arn): mock_session = Mock() mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 + mock_session.region = RIGETTI_REGION device = AwsDevice(arn, mock_session) _assert_device_fields(device, MOCK_GATE_MODEL_QPU_CAPABILITIES_1, MOCK_GATE_MODEL_QPU_1) @@ -370,9 +436,10 @@ def test_device_refresh_metadata(arn): def test_equality(arn): mock_session = Mock() mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 + mock_session.region = RIGETTI_REGION device_1 = AwsDevice(arn, mock_session) device_2 = AwsDevice(arn, mock_session) - other_device = AwsDevice("foo_bar", mock_session) + other_device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/bar", mock_session) non_device = "HI" assert device_1 == device_2 @@ -384,6 +451,7 @@ def test_equality(arn): def test_repr(arn): mock_session = Mock() 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) assert repr(device) == expected @@ -616,8 +684,8 @@ def test_run_with_positional_args_and_kwargs( {"AMZN_BRAKET_TASK_RESULTS_S3_URI": "s3://env_bucket/env/path"}, ) @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_env_variables(aws_quantum_task_mock, device, circuit): - device("foo:bar").run(circuit) +def test_run_env_variables(aws_quantum_task_mock, device, circuit, arn): + device(arn).run(circuit) assert aws_quantum_task_mock.call_args_list[0][0][3] == ("env_bucket", "env/path") @@ -670,8 +738,8 @@ def test_run_batch_with_max_parallel_and_kwargs( {"AMZN_BRAKET_TASK_RESULTS_S3_URI": "s3://env_bucket/env/path"}, ) @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_batch_env_variables(aws_quantum_task_mock, device, circuit): - device("foo:bar").run_batch([circuit]) +def test_run_batch_env_variables(aws_quantum_task_mock, device, circuit, arn): + device(arn).run_batch([circuit]) assert aws_quantum_task_mock.call_args_list[0][0][3] == ("env_bucket", "env/path") @@ -688,7 +756,7 @@ def _run_and_assert( ): run_and_assert( aws_quantum_task_mock, - device_factory("foo_bar"), + device_factory("arn:aws:braket:::device/quantum-simulator/amazon/sim"), MOCK_DEFAULT_S3_DESTINATION_FOLDER, AwsDevice.DEFAULT_SHOTS_SIMULATOR, AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, @@ -720,7 +788,7 @@ def _run_batch_and_assert( run_batch_and_assert( aws_quantum_task_mock, aws_session_mock, - device_factory("foo_bar"), + device_factory("arn:aws:braket:::device/quantum-simulator/amazon/sim"), MOCK_DEFAULT_S3_DESTINATION_FOLDER, AwsDevice.DEFAULT_SHOTS_SIMULATOR, AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, 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 2265fd3c..22bd6405 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_job.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py @@ -481,15 +481,20 @@ def role_arn(): return "arn:aws:iam::0000000000:role/AmazonBraketInternalSLR" -@pytest.fixture -def device_arn(): - return "arn:aws:braket:::device/qpu/test/device-name" +@pytest.fixture( + params=[ + "arn:aws:braket:us-test-1::device/qpu/test/device-name", + "arn:aws:braket:::device/qpu/test/device-name", + ] +) +def device_arn(request): + return request.param @pytest.fixture -def prepare_job_args(aws_session): +def prepare_job_args(aws_session, device_arn): return { - "device": Mock(), + "device": device_arn, "source_module": Mock(), "entry_point": Mock(), "image_uri": Mock(), @@ -796,7 +801,8 @@ def test_logs_error(quantum_job, generate_get_job_response, capsys): quantum_job.logs(wait=True, poll_interval_seconds=0) -def test_initialize_session_for_valid_device(device_arn, aws_session, caplog): +def test_initialize_session_for_valid_non_regional_device(aws_session, caplog): + device_arn = "arn:aws:braket:::device/qpu/test/device-name" first_region = aws_session.region logger = logging.getLogger(__name__) @@ -826,6 +832,76 @@ def test_initialize_session_for_valid_device(device_arn, aws_session, caplog): assert f"Changed session region from '{first_region}' to '{aws_session.region}'" in caplog.text +def test_initialize_session_for_valid_regional_device(aws_session, caplog): + device_arn = f"arn:aws:braket:{aws_session.region}::device/qpu/test/device-name" + logger = logging.getLogger(__name__) + aws_session.get_device.return_value = device_arn + caplog.set_level(logging.INFO) + AwsQuantumJob._initialize_session(aws_session, device_arn, logger) + assert not caplog.text + + +@pytest.mark.parametrize( + "get_device_side_effect, expected_exception", + [ + ( + [ + ClientError( + { + "Error": { + "Code": "ResourceNotFoundException", + } + }, + "getDevice", + ) + ], + ValueError, + ), + ( + [ + ClientError( + { + "Error": { + "Code": "ThrottlingException", + } + }, + "getDevice", + ) + ], + ClientError, + ), + ], +) +def test_regional_device_raises_error( + get_device_side_effect, expected_exception, aws_session, caplog +): + device_arn = f"arn:aws:braket:{aws_session.region}::device/qpu/test/device-name" + aws_session.get_device.side_effect = get_device_side_effect + logger = logging.getLogger(__name__) + caplog.set_level(logging.INFO) + with pytest.raises(expected_exception): + AwsQuantumJob._initialize_session(aws_session, device_arn, logger) + aws_session.get_device.assert_called_with(device_arn) + assert not caplog.text + + +def test_regional_device_switches(aws_session, caplog): + original_region = aws_session.region + device_region = "us-east-1" + device_arn = f"arn:aws:braket:{device_region}::device/qpu/test/device-name" + mock_session = Mock() + mock_session.get_device.side_effect = device_arn + aws_session.copy_session.side_effect = [mock_session] + logger = logging.getLogger(__name__) + caplog.set_level(logging.INFO) + + assert mock_session == AwsQuantumJob._initialize_session(aws_session, device_arn, logger) + + aws_session.copy_session.assert_called_with(region=device_region) + mock_session.get_device.assert_called_with(device_arn) + assert f"Changed session region from '{original_region}' to '{device_region}'" in caplog.text + + def test_initialize_session_for_invalid_device(aws_session, device_arn): logger = logging.getLogger(__name__) aws_session.get_device.side_effect = ClientError( @@ -837,7 +913,7 @@ def test_initialize_session_for_invalid_device(aws_session, device_arn): "getDevice", ) - device_not_found = "QPU 'arn:aws:braket:::device/qpu/test/device-name' not found." + device_not_found = f"'{device_arn}' not found." with pytest.raises(ValueError, match=device_not_found): AwsQuantumJob._initialize_session(aws_session, device_arn, logger) @@ -880,7 +956,8 @@ def test_exception_in_credentials_session_region(device_arn, aws_session): AwsQuantumJob._initialize_session(aws_session, device_arn, logger) -def test_exceptions_in_all_device_regions(device_arn, aws_session): +def test_exceptions_in_all_device_regions(aws_session): + device_arn = "arn:aws:braket:::device/qpu/test/device-name" logger = logging.getLogger(__name__) aws_session.get_device.side_effect = [ From 12002ad3d6c3cb59fc76f170eccb0e1eb0c6384f Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 15 Feb 2022 20:29:15 +0000 Subject: [PATCH 0424/1165] prepare release v1.15.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 8f769be9..672de8f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.15.0 (2022-02-15) + +### Features + + * Update region switching for regional device arns (#169) + ## v1.14.0.post0 (2022-02-11) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 9855c1a6..eb8ccf7c 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.14.1.dev0" +__version__ = "1.15.0" From 69acaf54237ecbee14b5b5f0549fa28e32eba83b Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 15 Feb 2022 20:29:15 +0000 Subject: [PATCH 0425/1165] update development version to v1.15.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 eb8ccf7c..e264b40e 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.15.0" +__version__ = "1.15.1.dev0" From a04f2e3dd16c05c47bc2141d2eb092dc7f33fc0c Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Sun, 27 Feb 2022 12:36:41 -0800 Subject: [PATCH 0426/1165] change: Oqc release (#305) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Viraj Chaudhari Co-authored-by: Milan Krneta Co-authored-by: Aaron Berdy Co-authored-by: Viraj Chaudhari Co-authored-by: Milan Krneta Co-authored-by: Aaron Berdy Co-authored-by: Lin Co-authored-by: Roald Bradley Severtson Co-authored-by: Christian Madsen Co-authored-by: Jacob Feldman Co-authored-by: mbeach-aws <85963088+mbeach-aws@users.noreply.github.com> Co-authored-by: Yiheng Duan <98372300+duanyh12@users.noreply.github.com> Co-authored-by: ℂ𝓞𝔇𝚈 Co-authored-by: Cody Wang Co-authored-by: Aaron Berdy Co-authored-by: Kshitij Chhabra Co-authored-by: Abe Coull Co-authored-by: Lin Co-authored-by: Roald Bradley Severtson Co-authored-by: Jeff Heckey Co-authored-by: Christian Madsen Co-authored-by: mbeach-aws Co-authored-by: Kshitij Chhabra Co-authored-by: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com> Co-authored-by: Jacob Feldman Co-authored-by: mbeach-aws <85963088+mbeach-aws@users.noreply.github.com> --- .github/workflows/dependent-tests.yml | 4 +- src/braket/aws/aws_quantum_task.py | 3 ++ src/braket/circuits/circuit_helpers.py | 4 ++ src/braket/circuits/gates.py | 44 +++++++++++++++++++ test/integ_tests/test_device_creation.py | 12 +++-- .../braket/aws/common_test_utils.py | 1 + .../braket/aws/test_aws_quantum_task.py | 3 ++ .../braket/circuits/test_circuit_helpers.py | 22 +++++++++- test/unit_tests/braket/circuits/test_gates.py | 1 + 9 files changed, 88 insertions(+), 6 deletions(-) diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index 9744ad77..e9da969b 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -25,8 +25,8 @@ jobs: - name: Install dependencies run: | pip install --upgrade pip - pip install --upgrade git+https://github.com/aws/amazon-braket-schemas-python@main - pip install --upgrade git+https://github.com/aws/amazon-braket-default-simulator-python@main + pip install --upgrade git+https://github.com/aws/amazon-braket-schemas-python.git@main + pip install --upgrade git+https://github.com/aws/amazon-braket-default-simulator-python.git@main pip install -e . cd .. git clone https://github.com/aws/${{ matrix.dependent }}.git diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 7b7293b2..ee06dccd 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -38,6 +38,7 @@ DwaveAdvantageDeviceLevelParameters, ) from braket.device_schema.ionq import IonqDeviceParameters +from braket.device_schema.oqc import OqcDeviceParameters from braket.device_schema.rigetti import RigettiDeviceParameters from braket.device_schema.simulators import GateModelSimulatorDeviceParameters from braket.schema_common import BraketSchemaBase @@ -437,6 +438,8 @@ def _( device_parameters = IonqDeviceParameters(paradigmParameters=paradigm_parameters) elif "rigetti" in device_arn: device_parameters = RigettiDeviceParameters(paradigmParameters=paradigm_parameters) + elif "oqc" in device_arn: + device_parameters = OqcDeviceParameters(paradigmParameters=paradigm_parameters) else: # default to use simulator device_parameters = GateModelSimulatorDeviceParameters( paradigmParameters=paradigm_parameters diff --git a/src/braket/circuits/circuit_helpers.py b/src/braket/circuits/circuit_helpers.py index 7aa39b3b..53319ac5 100644 --- a/src/braket/circuits/circuit_helpers.py +++ b/src/braket/circuits/circuit_helpers.py @@ -40,3 +40,7 @@ def validate_circuit_and_shots(circuit: Circuit, shots: int) -> None: for rt in circuit.result_types: if isinstance(rt, ResultType.StateVector) or isinstance(rt, ResultType.Amplitude): 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 + if num_qubits > 40: + raise ValueError("Probability target must be less than or equal to 40 qubits.") diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index f7c31af6..22afc532 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -1173,6 +1173,50 @@ def cz(control: QubitInput, target: QubitInput) -> Instruction: Gate.register_gate(CZ) +class ECR(Gate): + """An echoed RZX(pi/2) gate.""" + + def __init__(self): + super().__init__(qubit_count=None, ascii_symbols=["ECR", "ECR"]) + + def to_ir(self, target: QubitSet): + return ir.ECR.construct(targets=[target[0], target[1]]) + + def to_matrix(self) -> np.ndarray: + return ( + 1 + / np.sqrt(2) + * np.array( + [[0, 1, 0, 1.0j], [1, 0, -1.0j, 0], [0, 1.0j, 0, 1], [-1.0j, 0, 1, 0]], + dtype=complex, + ) + ) + + @staticmethod + def fixed_qubit_count() -> int: + return 2 + + @staticmethod + @circuit.subroutine(register=True) + def ecr(target1: QubitInput, target2: QubitInput) -> Instruction: + """Registers this function into the circuit class. + + Args: + target1 (Qubit or int): Target qubit 1 index. + target2 (Qubit or int): Target qubit 2 index. + + Returns: + Instruction: ECR instruction. + + Examples: + >>> circ = Circuit().ecr(0, 1) + """ + return Instruction(ECR(), target=[target1, target2]) + + +Gate.register_gate(ECR) + + class XX(AngledGate): """Ising XX coupling gate. diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 63b7a371..f1ca875c 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -19,9 +19,12 @@ RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-10" IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/ionQdevice" 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", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (SIMULATOR_ARN)]) +@pytest.mark.parametrize( + "arn", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (OQC_ARN), (SIMULATOR_ARN)] +) def test_device_creation(arn, aws_session): device = AwsDevice(arn, aws_session=aws_session) assert device.arn == arn @@ -36,9 +39,12 @@ def test_device_across_regions(aws_session): # 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) -@pytest.mark.parametrize("arn", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (SIMULATOR_ARN)]) +@pytest.mark.parametrize( + "arn", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (OQC_ARN), (SIMULATOR_ARN)] +) def test_get_devices_arn(arn): results = AwsDevice.get_devices(arns=[arn]) assert results[0].arn == arn @@ -58,5 +64,5 @@ def test_get_devices_others(): def test_get_devices_all(): result_arns = [result.arn for result in AwsDevice.get_devices()] - for arn in [DWAVE_ARN, RIGETTI_ARN, IONQ_ARN, SIMULATOR_ARN]: + for arn in [DWAVE_ARN, RIGETTI_ARN, IONQ_ARN, SIMULATOR_ARN, OQC_ARN]: assert arn in result_arns diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 8eaaa367..9ea1e2b1 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -19,6 +19,7 @@ DWAVE_ARN = "arn:aws:braket:::device/qpu/d-wave/Advantage_system1" RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-10" IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/ionQdevice" +OQC_ARN = "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" SV1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" TN1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/tn1" 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 90481bdd..bcc517d4 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 @@ DwaveDeviceParameters, ) from braket.device_schema.ionq import IonqDeviceParameters +from braket.device_schema.oqc import OqcDeviceParameters from braket.device_schema.rigetti import RigettiDeviceParameters from braket.device_schema.simulators import GateModelSimulatorDeviceParameters from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult @@ -41,11 +42,13 @@ IONQ_ARN = "device/qpu/ionq" RIGETTI_ARN = "device/qpu/rigetti" +OQC_ARN = "device/qpu/oqc" SIMULATOR_ARN = "device/quantum-simulator" DEVICE_PARAMETERS = [ (IONQ_ARN, IonqDeviceParameters), (RIGETTI_ARN, RigettiDeviceParameters), + (OQC_ARN, OqcDeviceParameters), (SIMULATOR_ARN, GateModelSimulatorDeviceParameters), ] diff --git a/test/unit_tests/braket/circuits/test_circuit_helpers.py b/test/unit_tests/braket/circuits/test_circuit_helpers.py index e9ed831c..84efd585 100644 --- a/test/unit_tests/braket/circuits/test_circuit_helpers.py +++ b/test/unit_tests/braket/circuits/test_circuit_helpers.py @@ -13,7 +13,7 @@ import pytest -from braket.circuits import Circuit, observables +from braket.circuits import Circuit, Observable, observables from braket.circuits.circuit_helpers import validate_circuit_and_shots @@ -44,6 +44,15 @@ def test_validate_circuit_and_shots_100_results(): assert validate_circuit_and_shots(Circuit().h(0).probability(), 100) is None +def test_validate_circuit_and_shots_100_results_mixed_result(): + assert ( + validate_circuit_and_shots( + Circuit().h(0).expectation(observable=Observable.Z(), target=0), 100 + ) + is None + ) + + @pytest.mark.xfail(raises=ValueError) def test_validate_circuit_and_shots_100_result_state_vector(): validate_circuit_and_shots(Circuit().h(0).state_vector(), 100) @@ -73,3 +82,14 @@ def test_validate_circuit_and_shots_100_noncommuting(): .expectation(observables.Y() @ observables.X(), [0, 1]), 100, ) + + +def test_probability_limit(): + circ = Circuit() + for i in range(50): + circ.h(i) + circ.probability() + + too_many_qubits = "Probability target must be less than or equal to 40 qubits." + with pytest.raises(ValueError, match=too_many_qubits): + validate_circuit_and_shots(circ, 100) diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index fbb35c6c..3bc18fb0 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -74,6 +74,7 @@ ), (Gate.CY, "cy", ir.CY, [SingleTarget, SingleControl], {}), (Gate.CZ, "cz", ir.CZ, [SingleTarget, SingleControl], {}), + (Gate.ECR, "ecr", ir.ECR, [DoubleTarget], {}), (Gate.XX, "xx", ir.XX, [DoubleTarget, Angle], {}), (Gate.YY, "yy", ir.YY, [DoubleTarget, Angle], {}), (Gate.ZZ, "zz", ir.ZZ, [DoubleTarget, Angle], {}), From a89c2a5b1ecf79397fb094687c4b9a4b0958a265 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Sun, 27 Feb 2022 13:52:50 -0800 Subject: [PATCH 0427/1165] feat: LHR region configuration (#306) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Viraj Chaudhari Co-authored-by: Milan Krneta Co-authored-by: Aaron Berdy Co-authored-by: Kshitij Chhabra Co-authored-by: ℂ𝓞𝔇𝚈 Co-authored-by: Cody Wang Co-authored-by: Abe Coull Co-authored-by: Lin Co-authored-by: Roald Bradley Severtson Co-authored-by: Jeff Heckey Co-authored-by: Christian Madsen Co-authored-by: mbeach-aws Co-authored-by: Jacob Feldman --- src/braket/aws/aws_device.py | 20 +++---- 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/test_device_creation.py | 12 ++--- test/unit_tests/braket/aws/test_aws_device.py | 53 ++++++++++++++++--- 6 files changed, 66 insertions(+), 28 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 431b9bd4..98a5e787 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -19,7 +19,7 @@ from typing import List, Optional, Union from botocore.errorfactory import ClientError -from networkx import Graph, complete_graph, from_edgelist +from networkx import DiGraph, complete_graph, from_edgelist from braket.annealing.problem import Problem from braket.aws.aws_quantum_task import AwsQuantumTask @@ -46,7 +46,7 @@ class AwsDevice(Device): device. """ - REGIONS = ("us-east-1", "us-west-1", "us-west-2") + REGIONS = ("us-east-1", "us-west-1", "us-west-2", "eu-west-2") DEFAULT_SHOTS_QPU = 1000 DEFAULT_SHOTS_SIMULATOR = 0 @@ -366,8 +366,8 @@ def properties(self) -> DeviceCapabilities: return self._properties @property - def topology_graph(self) -> Graph: - """Graph: topology of device as a networkx `Graph` object. + def topology_graph(self) -> DiGraph: + """DiGraph: topology of device as a networkx `DiGraph` object. Returns `None` if the topology is not available for the device. Examples: @@ -382,29 +382,31 @@ def topology_graph(self) -> Graph: """ return self._topology_graph - def _construct_topology_graph(self) -> Graph: + def _construct_topology_graph(self) -> DiGraph: """ Construct topology graph. If no such metadata is available, return `None`. Returns: - Graph: topology of QPU as a networkx `Graph` object. + DiGraph: topology of QPU as a networkx `DiGraph` object. """ if hasattr(self.properties, "paradigm") and isinstance( self.properties.paradigm, GateModelQpuParadigmProperties ): if self.properties.paradigm.connectivity.fullyConnected: - return complete_graph(int(self.properties.paradigm.qubitCount)) + return complete_graph( + int(self.properties.paradigm.qubitCount), create_using=DiGraph() + ) adjacency_lists = self.properties.paradigm.connectivity.connectivityGraph edges = [] for item in adjacency_lists.items(): i = item[0] edges.extend([(int(i), int(j)) for j in item[1]]) - return from_edgelist(edges) + return from_edgelist(edges, create_using=DiGraph()) elif hasattr(self.properties, "provider") and isinstance( self.properties.provider, DwaveProviderProperties ): edges = self.properties.provider.couplers - return from_edgelist(edges) + return from_edgelist(edges, create_using=DiGraph()) else: return None diff --git a/src/braket/jobs/image_uri_config/base.json b/src/braket/jobs/image_uri_config/base.json index b941f5dc..d3bc8064 100644 --- a/src/braket/jobs/image_uri_config/base.json +++ b/src/braket/jobs/image_uri_config/base.json @@ -4,7 +4,8 @@ "registries": { "us-east-1": "292282985366", "us-west-1": "292282985366", - "us-west-2": "292282985366" + "us-west-2": "292282985366", + "eu-west-2": "292282985366" }, "repository": "amazon-braket-base-jobs" } diff --git a/src/braket/jobs/image_uri_config/pl_pytorch.json b/src/braket/jobs/image_uri_config/pl_pytorch.json index d5b0943f..384848cb 100644 --- a/src/braket/jobs/image_uri_config/pl_pytorch.json +++ b/src/braket/jobs/image_uri_config/pl_pytorch.json @@ -4,7 +4,8 @@ "registries": { "us-east-1": "292282985366", "us-west-1": "292282985366", - "us-west-2": "292282985366" + "us-west-2": "292282985366", + "eu-west-2": "292282985366" }, "repository": "amazon-braket-pytorch-jobs" } diff --git a/src/braket/jobs/image_uri_config/pl_tensorflow.json b/src/braket/jobs/image_uri_config/pl_tensorflow.json index 758b2107..9a339c5d 100644 --- a/src/braket/jobs/image_uri_config/pl_tensorflow.json +++ b/src/braket/jobs/image_uri_config/pl_tensorflow.json @@ -4,7 +4,8 @@ "registries": { "us-east-1": "292282985366", "us-west-1": "292282985366", - "us-west-2": "292282985366" + "us-west-2": "292282985366", + "eu-west-2": "292282985366" }, "repository": "amazon-braket-tensorflow-jobs" } diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index f1ca875c..63b7a371 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -19,12 +19,9 @@ RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-10" IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/ionQdevice" 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", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (OQC_ARN), (SIMULATOR_ARN)] -) +@pytest.mark.parametrize("arn", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (SIMULATOR_ARN)]) def test_device_creation(arn, aws_session): device = AwsDevice(arn, aws_session=aws_session) assert device.arn == arn @@ -39,12 +36,9 @@ def test_device_across_regions(aws_session): # 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) -@pytest.mark.parametrize( - "arn", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (OQC_ARN), (SIMULATOR_ARN)] -) +@pytest.mark.parametrize("arn", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (SIMULATOR_ARN)]) def test_get_devices_arn(arn): results = AwsDevice.get_devices(arns=[arn]) assert results[0].arn == arn @@ -64,5 +58,5 @@ def test_get_devices_others(): def test_get_devices_all(): result_arns = [result.arn for result in AwsDevice.get_devices()] - for arn in [DWAVE_ARN, RIGETTI_ARN, IONQ_ARN, SIMULATOR_ARN, OQC_ARN]: + for arn in [DWAVE_ARN, RIGETTI_ARN, IONQ_ARN, SIMULATOR_ARN]: assert arn in result_arns diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index f86e4fa8..334de799 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -15,11 +15,13 @@ from datetime import datetime from unittest.mock import Mock, patch +import networkx as nx import pytest from botocore.exceptions import ClientError from common_test_utils import ( DWAVE_ARN, IONQ_ARN, + OQC_ARN, RIGETTI_ARN, RIGETTI_REGION, SV1_ARN, @@ -131,6 +133,14 @@ def test_mock_rigetti_schema_2(): "deviceCapabilities": MOCK_GATE_MODEL_QPU_CAPABILITIES_2.json(), } +MOCK_GATE_MODEL_QPU_3 = { + "deviceName": "Lucy", + "deviceType": "QPU", + "providerName": "OQC", + "deviceStatus": "OFFLINE", + "deviceCapabilities": MOCK_GATE_MODEL_QPU_CAPABILITIES_1.json(), +} + MOCK_DWAVE_QPU_CAPABILITIES_JSON = { "braketSchemaHeader": { "name": "braket.device_schema.dwave.dwave_device_capabilities", @@ -141,7 +151,7 @@ def test_mock_rigetti_schema_2(): "annealingOffsetStepPhi0": 1.45, "annealingOffsetRanges": [[1.45, 1.45], [1.45, 1.45]], "annealingDurationRange": [1, 2, 3], - "couplers": [[1, 2], [1, 2]], + "couplers": [[1, 2], [2, 3]], "defaultAnnealingDuration": 1, "defaultProgrammingThermalizationDuration": 1, "defaultReadoutThermalizationDuration": 1, @@ -608,10 +618,11 @@ def test_run_with_shots_kwargs(aws_quantum_task_mock, device, circuit, s3_destin @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_qpu_no_shots(aws_quantum_task_mock, device, circuit, s3_destination_folder): +def test_default_bucket_not_called(aws_quantum_task_mock, device, circuit, s3_destination_folder): + device = device(RIGETTI_ARN) run_and_assert( aws_quantum_task_mock, - device(RIGETTI_ARN), + device, MOCK_DEFAULT_S3_DESTINATION_FOLDER, AwsDevice.DEFAULT_SHOTS_QPU, AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, @@ -624,6 +635,7 @@ def test_run_with_qpu_no_shots(aws_quantum_task_mock, device, circuit, s3_destin None, None, ) + device._aws_session.default_bucket.assert_not_called() @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") @@ -864,23 +876,34 @@ def test_get_devices(mock_copy_session, aws_session): "providerName": "Amazon Braket", }, ], + # eu-west-2 + [ + { + "deviceArn": OQC_ARN, + "deviceName": "Lucy", + "deviceType": "QPU", + "deviceStatus": "ONLINE", + "providerName": "OQC", + } + ], # Only two regions to search outside of current ValueError("should not be reachable"), ] session_for_region.get_device.side_effect = [ MOCK_DWAVE_QPU, MOCK_GATE_MODEL_QPU_2, + MOCK_GATE_MODEL_QPU_3, ValueError("should not be reachable"), ] mock_copy_session.return_value = session_for_region - # Search order: us-east-1, us-west-1, us-west-2 + # Search order: us-east-1, us-west-1, us-west-2, eu-west-2 results = AwsDevice.get_devices( - arns=[SV1_ARN, DWAVE_ARN, IONQ_ARN], - provider_names=["Amazon Braket", "D-Wave", "IonQ"], + arns=[SV1_ARN, DWAVE_ARN, IONQ_ARN, OQC_ARN], + provider_names=["Amazon Braket", "D-Wave", "IonQ", "OQC"], statuses=["ONLINE"], aws_session=aws_session, ) - assert [result.name for result in results] == ["Advantage_system1.1", "Blah", "SV1"] + assert [result.name for result in results] == ["Advantage_system1.1", "Blah", "Lucy", "SV1"] @patch("braket.aws.aws_device.AwsSession.copy_session") @@ -1053,3 +1076,19 @@ def __init__(self, status, *execution_window_args): assert ( expected == actual ), f"device_name: {device_name}, test_date: {test_date}, expected: {expected}, actual: {actual}" + + +@pytest.mark.parametrize( + "get_device_data, expected_graph", + [ + (MOCK_GATE_MODEL_QPU_1, nx.DiGraph([(1, 2), (1, 3)])), + (MOCK_GATE_MODEL_QPU_2, nx.complete_graph(30, nx.DiGraph())), + (MOCK_DWAVE_QPU, nx.DiGraph([(1, 2), (2, 3)])), + ], +) +def test_device_topology_graph_data(get_device_data, expected_graph, arn): + mock_session = Mock() + mock_session.get_device.return_value = get_device_data + mock_session.region = RIGETTI_REGION + device = AwsDevice(arn, mock_session) + assert nx.is_isomorphic(device.topology_graph, expected_graph) From 8e77abae832c35157a85b22f16b8df334104c9ab Mon Sep 17 00:00:00 2001 From: ci Date: Sun, 27 Feb 2022 22:13:49 +0000 Subject: [PATCH 0428/1165] prepare release v1.16.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 672de8f5..f3378f05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.16.0 (2022-02-27) + +### Features + + * LHR region configuration + +### Bug Fixes and Other Changes + + * Oqc release + ## v1.15.0 (2022-02-15) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index e264b40e..b9de1318 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.15.1.dev0" +__version__ = "1.16.0" From f8ec495aed173694f796846ab4c2f9ece14e18be Mon Sep 17 00:00:00 2001 From: ci Date: Sun, 27 Feb 2022 22:13:49 +0000 Subject: [PATCH 0429/1165] update development version to v1.16.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 b9de1318..0cb71802 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.16.0" +__version__ = "1.16.1.dev0" From f7984de94f2031cb204ee83716f7783213e81818 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 1 Mar 2022 10:17:25 -0800 Subject: [PATCH 0430/1165] fix: Add the OQC ARN to the integ tests (#307) * fix: Add the OQC ARN to the integ tests --- test/integ_tests/test_device_creation.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 63b7a371..f1ca875c 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -19,9 +19,12 @@ RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-10" IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/ionQdevice" 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", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (SIMULATOR_ARN)]) +@pytest.mark.parametrize( + "arn", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (OQC_ARN), (SIMULATOR_ARN)] +) def test_device_creation(arn, aws_session): device = AwsDevice(arn, aws_session=aws_session) assert device.arn == arn @@ -36,9 +39,12 @@ def test_device_across_regions(aws_session): # 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) -@pytest.mark.parametrize("arn", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (SIMULATOR_ARN)]) +@pytest.mark.parametrize( + "arn", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (OQC_ARN), (SIMULATOR_ARN)] +) def test_get_devices_arn(arn): results = AwsDevice.get_devices(arns=[arn]) assert results[0].arn == arn @@ -58,5 +64,5 @@ def test_get_devices_others(): def test_get_devices_all(): result_arns = [result.arn for result in AwsDevice.get_devices()] - for arn in [DWAVE_ARN, RIGETTI_ARN, IONQ_ARN, SIMULATOR_ARN]: + for arn in [DWAVE_ARN, RIGETTI_ARN, IONQ_ARN, SIMULATOR_ARN, OQC_ARN]: assert arn in result_arns From 7da84ce2f8ad6ab34fa08377b3f5743db59cde86 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 1 Mar 2022 21:08:47 +0000 Subject: [PATCH 0431/1165] prepare release v1.16.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 f3378f05..e8f9ed5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.16.1 (2022-03-01) + +### Bug Fixes and Other Changes + + * Add the OQC ARN to the integ tests + ## v1.16.0 (2022-02-27) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 0cb71802..4026ffb0 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.16.1.dev0" +__version__ = "1.16.1" From 1272e5343a699f796655f1f782806387f7409ddb Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 1 Mar 2022 21:08:47 +0000 Subject: [PATCH 0432/1165] update development version to v1.16.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 4026ffb0..cca275b9 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.16.1" +__version__ = "1.16.2.dev0" From 2af708148140f6979c677b62f927943ed5b91a09 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 1 Mar 2022 14:01:28 -0800 Subject: [PATCH 0433/1165] feature: Add parameterized circuits (#291) * feature: Add parameterized circuits Co-authored-by: Abe Coull Co-authored-by: Cody Wang --- setup.py | 1 + src/braket/aws/aws_quantum_task.py | 5 + src/braket/aws/aws_quantum_task_batch.py | 6 + src/braket/circuits/__init__.py | 3 + src/braket/circuits/angled_gate.py | 57 ++- src/braket/circuits/ascii_circuit_diagram.py | 4 + src/braket/circuits/circuit.py | 141 ++++++- src/braket/circuits/free_parameter.py | 85 ++++ .../circuits/free_parameter_expression.py | 136 +++++++ src/braket/circuits/gate.py | 2 +- src/braket/circuits/gates.py | 381 +++++++++++++++--- src/braket/circuits/parameterizable.py | 44 ++ test/unit_tests/braket/aws/test_aws_device.py | 66 ++- .../braket/circuits/test_angled_gate.py | 41 +- .../circuits/test_ascii_circuit_diagram.py | 70 +++- .../braket/circuits/test_circuit.py | 288 +++++++++++++ .../braket/circuits/test_free_parameter.py | 63 +++ .../test_free_parameter_expression.py | 126 ++++++ test/unit_tests/braket/circuits/test_gates.py | 46 ++- 19 files changed, 1494 insertions(+), 71 deletions(-) create mode 100644 src/braket/circuits/free_parameter.py create mode 100644 src/braket/circuits/free_parameter_expression.py create mode 100644 src/braket/circuits/parameterizable.py create mode 100644 test/unit_tests/braket/circuits/test_free_parameter.py create mode 100644 test/unit_tests/braket/circuits/test_free_parameter_expression.py diff --git a/setup.py b/setup.py index c7420189..5abd002d 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,7 @@ "nest-asyncio", "networkx", "numpy", + "sympy", ], extras_require={ "test": [ diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index ee06dccd..8f728e08 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -134,6 +134,11 @@ def create( ) if tags is not None: create_task_kwargs.update({"tags": tags}) + if isinstance(task_specification, Circuit) and task_specification.parameters: + raise ValueError( + f"Cannot execute circuit with unbound parameters: " + f"{task_specification.parameters}" + ) return _create_internal( task_specification, aws_session, diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index dbf66c8e..95cea721 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -121,6 +121,12 @@ def _execute( *args, **kwargs, ): + for task_specification in task_specifications: + if isinstance(task_specification, Circuit) and task_specification.parameters: + raise ValueError( + f"Cannot execute circuit with unbound parameters: " + f"{task_specification.parameters}" + ) max_threads = min(max_parallel, max_workers) remaining = [0 for _ in task_specifications] with ThreadPoolExecutor(max_workers=max_threads) as executor: diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py index 94827b00..c3f8570e 100644 --- a/src/braket/circuits/__init__.py +++ b/src/braket/circuits/__init__.py @@ -24,12 +24,15 @@ 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 +from braket.circuits.free_parameter import FreeParameter # noqa: F401 +from braket.circuits.free_parameter_expression import FreeParameterExpression # noqa: F401 from braket.circuits.gate import Gate # noqa: F401 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.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 diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index f7a60e32..f28cf4f3 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -12,20 +12,28 @@ # language governing permissions and limitations under the License. import math -from typing import Optional, Sequence +from typing import List, Optional, Sequence, Union +from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.gate import Gate +from braket.circuits.parameterizable import Parameterizable -class AngledGate(Gate): +class AngledGate(Gate, Parameterizable): """ Class `AngledGate` represents a quantum gate that operates on N qubits and an angle. """ - def __init__(self, angle: float, qubit_count: Optional[int], ascii_symbols: Sequence[str]): + def __init__( + self, + angle: Union[FreeParameterExpression, float], + qubit_count: Optional[int], + ascii_symbols: Sequence[str], + ): """ Args: - angle (float): The angle of the gate in radians. + angle (Union[FreeParameterExpression, float]): The angle of the gate in radians + or expression representation. qubit_count (int, optional): The number of qubits that this gate interacts with. ascii_symbols (Sequence[str]): ASCII string symbols for the gate. These are used when printing a diagram of a circuit. The length must be the same as `qubit_count`, and @@ -41,22 +49,51 @@ def __init__(self, angle: float, qubit_count: Optional[int], ascii_symbols: Sequ super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) if angle is None: raise ValueError("angle must not be None") - self._angle = float(angle) # explicit casting in case angle is e.g. np.float32 + if isinstance(angle, FreeParameterExpression): + self._parameters = [angle] + else: + self._parameters = [float(angle)] # explicit casting in case angle is e.g. np.float32 @property - def angle(self) -> float: + def parameters(self) -> List[Union[FreeParameterExpression, float]]: + """ + Returns the free parameters associated with the object. + + Returns: + Union[FreeParameterExpression,, float]: Returns the free parameters or fixed value + associated with the object. + """ + return self._parameters + + @property + def angle(self) -> Union[FreeParameterExpression, float]: """ Returns the angle for the gate Returns: - angle (float): The angle of the gate in radians + Union[FreeParameterExpression, float]: The angle of the gate in radians + """ + return self._parameters[0] + + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Raises: + NotImplementedError: Subclasses should implement this function. """ - return self._angle + raise NotImplementedError def __eq__(self, other): if isinstance(other, AngledGate): - return self.name == other.name and math.isclose(self.angle, other.angle) - return NotImplemented + if isinstance(self.angle, FreeParameterExpression): + return self.name == other.name and self.angle == other.angle + else: + return self.name == other.name and math.isclose(self.angle, other.angle) + return False def __repr__(self): return f"{self.name}('angle': {self.angle}, 'qubit_count': {self.qubit_count})" diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index fbf86918..4926de9c 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -84,6 +84,10 @@ def build_diagram(circuit) -> str: 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(f"\nUnassigned parameters: {circuit.parameters}.") + return "\n".join(lines) @staticmethod diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index e04210ef..e169a11c 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -13,12 +13,15 @@ from __future__ import annotations -from typing import Callable, Dict, Iterable, List, Optional, Tuple, Type, TypeVar, Union +from numbers import Number +from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar, Union import numpy as np 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 from braket.circuits.instruction import Instruction from braket.circuits.moments import Moments @@ -33,6 +36,7 @@ ) from braket.circuits.observable import Observable from braket.circuits.observables import TensorProduct +from braket.circuits.parameterizable import Parameterizable from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput from braket.circuits.result_type import ObservableResultType, ResultType @@ -126,6 +130,7 @@ def __init__(self, addable: AddableTypes = None, *args, **kwargs): self._qubit_observable_mapping: Dict[Union[int, Circuit._ALL_QUBITS], Observable] = {} self._qubit_observable_target_mapping: Dict[int, Tuple[int]] = {} self._qubit_observable_set = set() + self._parameters = set() self._observables_simultaneously_measurable = True self._has_compiler_directives = False @@ -196,6 +201,16 @@ def qubits(self) -> QubitSet: """QubitSet: Get a copy of the qubits for this circuit.""" return QubitSet(self._moments.qubits.union(self._qubit_observable_set)) + @property + def parameters(self) -> Set[FreeParameter]: + """ + Gets a set of the parameters in the Circuit. + + Returns: + Set[FreeParameter]: The `FreeParameters` in the Circuit. + """ + return self._parameters + def add_result_type( self, result_type: ResultType, @@ -412,10 +427,32 @@ def add_instruction( # non single qubit operator with target, add instruction with target instructions_to_add = [instruction.copy(target=target)] + if self._check_for_params(instruction): + for param in instruction.operator.parameters: + free_params = param.expression.free_symbols + for parameter in free_params: + self._parameters.add(FreeParameter(parameter.name)) self._moments.add(instructions_to_add) return self + def _check_for_params(self, instruction: Instruction) -> bool: + """ + This checks for free parameters in an :class:{Instruction}. Checks children classes of + :class:{Parameterizable}. + + Args: + instruction (Instruction): The instruction to check for a + :class:{FreeParameterExpression}. + + Returns: + bool: Whether an object is parameterized. + """ + return issubclass(type(instruction.operator), Parameterizable) and any( + issubclass(type(param), FreeParameterExpression) + for param in instruction.operator.parameters + ) + def add_circuit( self, circuit: Circuit, @@ -773,6 +810,88 @@ 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 + share the same name, all the parameters of that name will be set to the mapped value. + + Args: + param_values (Dict[str, Number]): A mapping of FreeParameter names + to a value to assign to them. + strict (bool, optional): If True, raises a ValueError if none of the FreeParameters + in param_values appear in the circuit. False by default." + + Returns: + Circuit: Returns a circuit with all present parameters fixed to their respective + values. + """ + if strict: + self._validate_parameters(param_values) + return self._use_parameter_value(param_values) + + def _validate_parameters(self, parameter_values: Dict[str, Number]): + """ + This runs a check to see 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 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)) + for param in parameter_values: + if param not in parameter_strings: + 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. + + Args: + param_values (Dict[str, Number]): A mapping of FreeParameter names + to a value to assign to them. + + Returns: + Circuit: A Circuit with specified parameters swapped for their + values. + + """ + fixed_circ = Circuit() + for val in param_values.values(): + self._validate_parameter_value(val) + for instruction in self.instructions: + if self._check_for_params(instruction): + fixed_circ.add( + Instruction( + instruction.operator.bind_values(**param_values), target=instruction.target + ) + ) + else: + fixed_circ.add(instruction) + fixed_circ.add(self.result_types) + return fixed_circ + + @staticmethod + def _validate_parameter_value(val): + """ + Validates the value being used is a Number. + + Args: + val: The value be verified. + + Raises: + ValueError: If the value is not a Number + """ + if not isinstance(val, Number): + raise ValueError( + f"Parameters can only be assigned numeric values. " f"Invalid inputs: {val}" + ) + def apply_readout_noise( self, noise: Union[Type[Noise], Iterable[Type[Noise]]], @@ -1044,6 +1163,26 @@ def __eq__(self, other): ) return NotImplemented + def __call__(self, arg=None, **kwargs) -> Circuit: + """ + Implements the call function to easily make a bound Circuit. + + Args: + arg: A value to bind to all parameters. Defaults to None and + can be overridden if the parameter is in kwargs. + **kwargs: the named parameters to have their value bound. + + Returns: + Circuit: A circuit with the specified parameters bound. + """ + param_values = dict() + if arg is not None: + for param in self.parameters: + param_values[str(param)] = arg + for key, val in kwargs.items(): + param_values[str(key)] = val + return self.make_bound_circuit(param_values) + def subroutine(register=False): """ diff --git a/src/braket/circuits/free_parameter.py b/src/braket/circuits/free_parameter.py new file mode 100644 index 00000000..f3989609 --- /dev/null +++ b/src/braket/circuits/free_parameter.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. + +from numbers import Number +from typing import Dict + +from sympy import Symbol + +from braket.circuits.free_parameter_expression import FreeParameterExpression + + +class FreeParameter(FreeParameterExpression): + """ + 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 + for a numerical value later on. Circuits with FreeParameters present will NOT run. Values must + be substituted prior to execution. + """ + + def __init__(self, name: str): + """ + Initializes a new :class:'FreeParameter' object. + + Args: + name (str): Name of the :class:'FreeParameter'. Can be a unicode value. + + Examples: + >>> param1 = FreeParameter("theta") + >>> param1 = FreeParameter("\u03B8") + """ + self._name = Symbol(name) + super().__init__(expression=self._name) + + @property + def name(self) -> str: + """ + str: Name of this parameter. + """ + return self._name.name + + def subs(self, parameter_values: Dict[str, Number]): + """ + Substitutes a value in if the parameter exists within the mapping. + + Args: + parameter_values (Dict[str, Number]): A mapping of parameter to its + corresponding value. + + Returns: 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 + + def __str__(self): + return str(self.name) + + def __hash__(self) -> int: + return hash(tuple(self.name)) + + def __eq__(self, other): + if isinstance(other, FreeParameter): + return self._name == other._name + return False + + def __repr__(self): + """ + The representation of the :class:'FreeParameter'. + + Returns: + The name of the class:'FreeParameter' to represent the class. + """ + return self.name diff --git a/src/braket/circuits/free_parameter_expression.py b/src/braket/circuits/free_parameter_expression.py new file mode 100644 index 00000000..a5dbcf17 --- /dev/null +++ b/src/braket/circuits/free_parameter_expression.py @@ -0,0 +1,136 @@ +# 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 numbers import Number +from typing import Dict + +from sympy import Expr, sympify + + +class FreeParameterExpression: + """ + Class 'FreeParameterExpression' + + Objects that can take a parameter all inherit from :class:'Parameterizable'. + FreeParametersExpressions can hold FreeParameters that can later be + swapped out for a number. Circuits with FreeParameters present will NOT run. Values must + be substituted prior to execution. + """ + + def __init__(self, expression): + """ + 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. + + Args: + expression: The expression to use. + + Examples: + >>> expression_1 = FreeParameter("theta") * FreeParameter("alpha") + >>> expression_2 = 1 + FreeParameter("beta") + 2 * FreeParameter("alpha") + """ + if isinstance(expression, FreeParameterExpression): + self._expression = expression.expression + elif isinstance(expression, (Number, Expr)): + self._expression = expression + else: + raise NotImplementedError + + @property + def expression(self): + """ + Returns: + The expression for the FreeParameterExpression. + """ + return self._expression + + def subs(self, parameter_values: Dict[str, Number]): + """ + Similar to a substitution in Sympy. Parameters are swapped for corresponding values or + expressions from the dictionary. + + Args: + parameter_values (Dict[str, Number]): A mapping of parameters to their corresponding + values to be assigned. + + Returns: A numerical value if there are no symbols left in the expression otherwise + returns a new FreeParameterExpression. + """ + new_parameter_values = dict() + for key, val in parameter_values.items(): + if issubclass(type(key), FreeParameterExpression): + new_parameter_values[key.expression] = val + else: + new_parameter_values[key] = val + + subbed_expr = self._expression.subs(new_parameter_values) + if subbed_expr.is_Number: + return subbed_expr + else: + return FreeParameterExpression(subbed_expr) + + def __add__(self, other): + if issubclass(type(other), FreeParameterExpression): + return FreeParameterExpression(self.expression + other.expression) + else: + return FreeParameterExpression(self.expression + other) + + def __radd__(self, other): + return FreeParameterExpression(other + self.expression) + + def __sub__(self, other): + if issubclass(type(other), FreeParameterExpression): + return FreeParameterExpression(self.expression - other.expression) + else: + return FreeParameterExpression(self.expression - other) + + def __rsub__(self, other): + return FreeParameterExpression(other - self.expression) + + def __mul__(self, other): + if issubclass(type(other), FreeParameterExpression): + return FreeParameterExpression(self.expression * other.expression) + else: + return FreeParameterExpression(self.expression * other) + + def __rmul__(self, other): + return FreeParameterExpression(other * self.expression) + + def __pow__(self, other, modulo=None): + if issubclass(type(other), FreeParameterExpression): + return FreeParameterExpression(self.expression**other.expression) + else: + return FreeParameterExpression(self.expression**other) + + def __rpow__(self, other): + return FreeParameterExpression(other**self.expression) + + def __neg__(self): + return FreeParameterExpression(-1 * self.expression) + + def __eq__(self, other): + if isinstance(other, FreeParameterExpression): + return sympify(self.expression).equals(sympify(other.expression)) + return False + + def __repr__(self) -> str: + """ + The representation of the :class:'FreeParameterExpression'. + + Returns: + The expression of the class:'FreeParameterExpression' to represent the class. + """ + return repr(self.expression) diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index 42cac864..b8a897c5 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -54,7 +54,7 @@ def to_ir(self, target: QubitSet) -> Any: def __eq__(self, other): if isinstance(other, Gate): return self.name == other.name - return NotImplemented + return False def __repr__(self): return f"{self.name}('qubit_count': {self.qubit_count})" diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 22afc532..994831c5 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -11,13 +11,16 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import Iterable +from typing import Iterable, Union import numpy as np +from sympy import Float import braket.ir.jaqcd as ir from braket.circuits import circuit from braket.circuits.angled_gate import AngledGate +from braket.circuits.free_parameter import FreeParameter +from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction from braket.circuits.quantum_operator_helpers import ( @@ -454,11 +457,15 @@ class Rx(AngledGate): """X-axis rotation gate. Args: - angle (float): angle in radians. + angle (Union[FreeParameter, float]): angle in radians. """ - def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=None, ascii_symbols=["Rx({:.3g})".format(angle)]) + def __init__(self, angle: Union[FreeParameter, float]): + super().__init__( + angle=angle, + qubit_count=None, + ascii_symbols=[angled_ascii_characters("Rx", angle)], + ) def to_ir(self, target: QubitSet): return ir.Rx.construct(target=target[0], angle=self.angle) @@ -472,14 +479,28 @@ def to_matrix(self) -> np.ndarray: def fixed_qubit_count() -> int: return 1 + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Gate.Rx: A new Gate of the same type with the requested + parameters bound. + + """ + return get_angle(self, **kwargs) + @staticmethod @circuit.subroutine(register=True) - def rx(target: QubitInput, angle: float) -> Iterable[Instruction]: + def rx(target: QubitInput, angle: Union[FreeParameter, float]) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (Qubit or int): Target qubit index. - angle (float): Angle in radians. + angle (Union[FreeParameter, float]): Angle in radians. Returns: Iterable[Instruction]: Rx instruction. @@ -497,11 +518,15 @@ class Ry(AngledGate): """Y-axis rotation gate. Args: - angle (float): angle in radians. + angle (Union[FreeParameter, float]): angle in radians. """ - def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=None, ascii_symbols=["Ry({:.3g})".format(angle)]) + def __init__(self, angle: Union[FreeParameter, float]): + super().__init__( + angle=angle, + qubit_count=None, + ascii_symbols=[angled_ascii_characters("Ry", angle)], + ) def to_ir(self, target: QubitSet): return ir.Ry.construct(target=target[0], angle=self.angle) @@ -515,14 +540,28 @@ def to_matrix(self) -> np.ndarray: def fixed_qubit_count() -> int: return 1 + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Gate.Ry: A new Gate of the same type with the requested + parameters bound. + + """ + return get_angle(self, **kwargs) + @staticmethod @circuit.subroutine(register=True) - def ry(target: QubitInput, angle: float) -> Iterable[Instruction]: + def ry(target: QubitInput, angle: Union[FreeParameter, float]) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (Qubit or int): Target qubit index. - angle (float): Angle in radians. + angle (Union[FreeParameter, float]): Angle in radians. Returns: Iterable[Instruction]: Ry instruction. @@ -540,11 +579,15 @@ class Rz(AngledGate): """Z-axis rotation gate. Args: - angle (float): angle in radians. + angle (Union[FreeParameter, float]): angle in radians. """ - def __init__(self, angle: float): - super().__init__(angle=angle, qubit_count=None, ascii_symbols=["Rz({:.3g})".format(angle)]) + def __init__(self, angle: Union[FreeParameter, float]): + super().__init__( + angle=angle, + qubit_count=None, + ascii_symbols=[angled_ascii_characters("Rz", angle)], + ) def to_ir(self, target: QubitSet): return ir.Rz.construct(target=target[0], angle=self.angle) @@ -554,18 +597,32 @@ def to_matrix(self) -> np.ndarray: [[np.exp(-1j * self.angle / 2), 0], [0, np.exp(1j * self.angle / 2)]], dtype=complex ) + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Gate.Rz: A new Gate of the same type with the requested + parameters bound. + + """ + return get_angle(self, **kwargs) + @staticmethod def fixed_qubit_count() -> int: return 1 @staticmethod @circuit.subroutine(register=True) - def rz(target: QubitInput, angle: float) -> Iterable[Instruction]: + def rz(target: QubitInput, angle: Union[FreeParameter, float]) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (Qubit or int): Target qubit index. - angle (float): Angle in radians. + angle (Union[FreeParameter, float]): angle in radians. Returns: Iterable[Instruction]: Rz instruction. @@ -583,12 +640,14 @@ class PhaseShift(AngledGate): """Phase shift gate. Args: - angle (float): angle in radians. + angle (Union[FreeParameter, float]): angle in radians. """ def __init__(self, angle: float): super().__init__( - angle=angle, qubit_count=None, ascii_symbols=["PHASE({:.3g})".format(angle)] + angle=angle, + qubit_count=None, + ascii_symbols=[angled_ascii_characters("PHASE", angle)], ) def to_ir(self, target: QubitSet): @@ -597,18 +656,32 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, np.exp(1j * self.angle)]], dtype=complex) + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Gate.PhaseShift: A new Gate of the same type with the requested + parameters bound. + + """ + return get_angle(self, **kwargs) + @staticmethod def fixed_qubit_count() -> int: return 1 @staticmethod @circuit.subroutine(register=True) - def phaseshift(target: QubitInput, angle: float) -> Iterable[Instruction]: + def phaseshift(target: QubitInput, angle: Union[FreeParameter, float]) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (Qubit or int): Target qubit index. - angle (float): Angle in radians. + angle (Union[FreeParameter, float]): angle in radians. Returns: Iterable[Instruction]: PhaseShift instruction. @@ -764,14 +837,17 @@ class PSwap(AngledGate): """PSwap gate. Args: - angle (float): angle in radians. + angle (Union[FreeParameter, float]): angle in radians. """ def __init__(self, angle: float): super().__init__( angle=angle, qubit_count=None, - ascii_symbols=["PSWAP({:.3g})".format(angle), "PSWAP({:.3g})".format(angle)], + ascii_symbols=[ + angled_ascii_characters("PSWAP", angle), + angled_ascii_characters("PSWAP", angle), + ], ) def to_ir(self, target: QubitSet): @@ -788,6 +864,20 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + kwargs: The parameters that are being assigned. + + Returns: + Gate.PSwap: A new Gate of the same type with the requested + parameters bound. + + """ + return get_angle(self, **kwargs) + @staticmethod def fixed_qubit_count() -> int: return 2 @@ -800,6 +890,7 @@ def pswap(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction Args: target1 (Qubit or int): Target qubit 1 index. target2 (Qubit or int): Target qubit 2 index. + angle (Union[FreeParameter, float]): angle in radians. Returns: Instruction: PSwap instruction. @@ -819,14 +910,17 @@ class XY(AngledGate): Reference: https://arxiv.org/abs/1912.04424v1 Args: - angle (float): angle in radians. + angle (Union[FreeParameter, float]): angle in radians. """ def __init__(self, angle: float): super().__init__( angle=angle, qubit_count=None, - ascii_symbols=["XY({:.3g})".format(angle), "XY({:.3g})".format(angle)], + ascii_symbols=[ + angled_ascii_characters("XY", angle), + angled_ascii_characters("XY", angle), + ], ) def to_ir(self, target: QubitSet): @@ -845,18 +939,35 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + kwargs: The parameters that are being assigned. + + Returns: + Gate.XY: A new Gate of the same type with the requested + parameters bound. + + """ + return get_angle(self, **kwargs) + @staticmethod def fixed_qubit_count() -> int: return 2 @staticmethod @circuit.subroutine(register=True) - def xy(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: + def xy( + target1: QubitInput, target2: QubitInput, angle: Union[FreeParameter, float] + ) -> Instruction: """Registers this function into the circuit class. Args: target1 (Qubit or int): Target qubit 1 index. target2 (Qubit or int): Target qubit 2 index. + angle (Union[FreeParameter, float]): angle in radians. Returns: Instruction: XY instruction. @@ -874,12 +985,14 @@ class CPhaseShift(AngledGate): """Controlled phase shift gate. Args: - angle (float): angle in radians. + angle (Union[FreeParameter, float]): angle in radians. """ def __init__(self, angle: float): super().__init__( - angle=angle, qubit_count=None, ascii_symbols=["C", "PHASE({:.3g})".format(angle)] + angle=angle, + qubit_count=None, + ascii_symbols=["C", angled_ascii_characters("PHASE", angle)], ) def to_ir(self, target: QubitSet): @@ -888,19 +1001,35 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, 1.0, np.exp(1j * self.angle)]) + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Gate.CPhaseShift: A new Gate of the same type with the requested + parameters bound. + + """ + return get_angle(self, **kwargs) + @staticmethod def fixed_qubit_count() -> int: return 2 @staticmethod @circuit.subroutine(register=True) - def cphaseshift(control: QubitInput, target: QubitInput, angle: float) -> Instruction: + def cphaseshift( + control: QubitInput, target: QubitInput, angle: Union[FreeParameter, float] + ) -> Instruction: """Registers this function into the circuit class. Args: control (Qubit or int): Control qubit index. target (Qubit or int): Target qubit index. - angle (float): Angle in radians. + angle (Union[FreeParameter, float]): angle in radians. Returns: Instruction: CPhaseShift instruction. @@ -918,12 +1047,14 @@ class CPhaseShift00(AngledGate): """Controlled phase shift gate for phasing the \\|00> state. Args: - angle (float): angle in radians. + angle (Union[FreeParameter, float]): angle in radians. """ def __init__(self, angle: float): super().__init__( - angle=angle, qubit_count=None, ascii_symbols=["C", "PHASE00({:.3g})".format(angle)] + angle=angle, + qubit_count=None, + ascii_symbols=["C", angled_ascii_characters("PHASE00", angle)], ) def to_ir(self, target: QubitSet): @@ -932,19 +1063,35 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.diag([np.exp(1j * self.angle), 1.0, 1.0, 1.0]) + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Gate.CPhaseShift00: A new Gate of the same type with the requested + parameters bound. + + """ + return get_angle(self, **kwargs) + @staticmethod def fixed_qubit_count() -> int: return 2 @staticmethod @circuit.subroutine(register=True) - def cphaseshift00(control: QubitInput, target: QubitInput, angle: float) -> Instruction: + def cphaseshift00( + control: QubitInput, target: QubitInput, angle: Union[FreeParameter, float] + ) -> Instruction: """Registers this function into the circuit class. Args: control (Qubit or int): Control qubit index. target (Qubit or int): Target qubit index. - angle (float): Angle in radians. + angle (Union[FreeParameter, float]): angle in radians. Returns: Instruction: CPhaseShift00 instruction. @@ -962,12 +1109,14 @@ class CPhaseShift01(AngledGate): """Controlled phase shift gate for phasing the \\|01> state. Args: - angle (float): angle in radians. + angle (Union[FreeParameter, float]): angle in radians. """ def __init__(self, angle: float): super().__init__( - angle=angle, qubit_count=None, ascii_symbols=["C", "PHASE01({:.3g})".format(angle)] + angle=angle, + qubit_count=None, + ascii_symbols=["C", angled_ascii_characters("PHASE01", angle)], ) def to_ir(self, target: QubitSet): @@ -976,19 +1125,35 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.diag([1.0, np.exp(1j * self.angle), 1.0, 1.0]) + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Gate.CPhaseShift01: A new Gate of the same type with the requested + parameters bound. + + """ + return get_angle(self, **kwargs) + @staticmethod def fixed_qubit_count() -> int: return 2 @staticmethod @circuit.subroutine(register=True) - def cphaseshift01(control: QubitInput, target: QubitInput, angle: float) -> Instruction: + def cphaseshift01( + control: QubitInput, target: QubitInput, angle: Union[FreeParameter, float] + ) -> Instruction: """Registers this function into the circuit class. Args: control (Qubit or int): Control qubit index. target (Qubit or int): Target qubit index. - angle (float): Angle in radians. + angle (Union[FreeParameter, float]): angle in radians. Returns: Instruction: CPhaseShift01 instruction. @@ -1006,12 +1171,14 @@ class CPhaseShift10(AngledGate): """Controlled phase shift gate for phasing the \\|10> state. Args: - angle (float): angle in radians. + angle (Union[FreeParameter, float]): angle in radians. """ def __init__(self, angle: float): super().__init__( - angle=angle, qubit_count=None, ascii_symbols=["C", "PHASE10({:.3g})".format(angle)] + angle=angle, + qubit_count=None, + ascii_symbols=["C", angled_ascii_characters("PHASE10", angle)], ) def to_ir(self, target: QubitSet): @@ -1020,19 +1187,35 @@ def to_ir(self, target: QubitSet): def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, np.exp(1j * self.angle), 1.0]) + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Gate.CPhaseShift10: A new Gate of the same type with the requested + parameters bound. + + """ + return get_angle(self, **kwargs) + @staticmethod def fixed_qubit_count() -> int: return 2 @staticmethod @circuit.subroutine(register=True) - def cphaseshift10(control: QubitInput, target: QubitInput, angle: float) -> Instruction: + def cphaseshift10( + control: QubitInput, target: QubitInput, angle: Union[FreeParameter, float] + ) -> Instruction: """Registers this function into the circuit class. Args: control (Qubit or int): Control qubit index. target (Qubit or int): Target qubit index. - angle (float): Angle in radians. + angle (Union[FreeParameter, float]): angle in radians. Returns: Instruction: CPhaseShift10 instruction. @@ -1223,14 +1406,17 @@ class XX(AngledGate): Reference: https://arxiv.org/abs/1707.06356 Args: - angle (float): angle in radians. + angle (Union[FreeParameter, float]): angle in radians. """ def __init__(self, angle: float): super().__init__( angle=angle, qubit_count=None, - ascii_symbols=["XX({:.3g})".format(angle), "XX({:.3g})".format(angle)], + ascii_symbols=[ + angled_ascii_characters("XX", angle), + angled_ascii_characters("XX", angle), + ], ) def to_ir(self, target: QubitSet): @@ -1249,19 +1435,35 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Gate.XX: A new Gate of the same type with the requested + parameters bound. + + """ + return get_angle(self, **kwargs) + @staticmethod def fixed_qubit_count() -> int: return 2 @staticmethod @circuit.subroutine(register=True) - def xx(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: + def xx( + target1: QubitInput, target2: QubitInput, angle: Union[FreeParameter, float] + ) -> Instruction: """Registers this function into the circuit class. Args: target1 (Qubit or int): Target qubit 1 index. target2 (Qubit or int): Target qubit 2 index. - angle (float): Angle in radians. + angle (Union[FreeParameter, float]): angle in radians. Returns: Instruction: XX instruction. @@ -1281,14 +1483,17 @@ class YY(AngledGate): Reference: https://arxiv.org/abs/1707.06356 Args: - angle (float): angle in radians. + angle (Union[FreeParameter, float]): angle in radians. """ def __init__(self, angle: float): super().__init__( angle=angle, qubit_count=None, - ascii_symbols=["YY({:.3g})".format(angle), "YY({:.3g})".format(angle)], + ascii_symbols=[ + angled_ascii_characters("YY", angle), + angled_ascii_characters("YY", angle), + ], ) def to_ir(self, target: QubitSet): @@ -1307,19 +1512,35 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Gate.YY: A new Gate of the same type with the requested + parameters bound. + + """ + return get_angle(self, **kwargs) + @staticmethod def fixed_qubit_count() -> int: return 2 @staticmethod @circuit.subroutine(register=True) - def yy(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: + def yy( + target1: QubitInput, target2: QubitInput, angle: Union[FreeParameter, float] + ) -> Instruction: """Registers this function into the circuit class. Args: target1 (Qubit or int): Target qubit 1 index. target2 (Qubit or int): Target qubit 2 index. - angle (float): Angle in radians. + angle (Union[FreeParameter, float]): angle in radians. Returns: Instruction: YY instruction. @@ -1339,14 +1560,17 @@ class ZZ(AngledGate): Reference: https://arxiv.org/abs/1707.06356 Args: - angle (float): angle in radians. + angle (Union[FreeParameter, float]): angle in radians. """ def __init__(self, angle: float): super().__init__( angle=angle, qubit_count=None, - ascii_symbols=["ZZ({:.3g})".format(angle), "ZZ({:.3g})".format(angle)], + ascii_symbols=[ + angled_ascii_characters("ZZ", angle), + angled_ascii_characters("ZZ", angle), + ], ) def to_ir(self, target: QubitSet): @@ -1363,19 +1587,35 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Gate.ZZ: A new Gate of the same type with the requested + parameters bound. + + """ + return get_angle(self, **kwargs) + @staticmethod def fixed_qubit_count() -> int: return 2 @staticmethod @circuit.subroutine(register=True) - def zz(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: + def zz( + target1: QubitInput, target2: QubitInput, angle: Union[FreeParameter, float] + ) -> Instruction: """Registers this function into the circuit class. Args: target1 (Qubit or int): Target qubit 1 index. target2 (Qubit or int): Target qubit 2 index. - angle (float): Angle in radians. + angle (Union[FreeParameter, float]): angle in radians. Returns: Instruction: ZZ instruction. @@ -1528,7 +1768,7 @@ def to_ir(self, target: QubitSet): def __eq__(self, other): if isinstance(other, Unitary): return self.matrix_equivalence(other) - return NotImplemented + return False @staticmethod def _transform_matrix_to_ir(matrix: np.ndarray): @@ -1564,3 +1804,36 @@ def unitary(targets: QubitSet, matrix: np.ndarray, display_name: str = "U") -> I Gate.register_gate(Unitary) + + +def angled_ascii_characters(gate: str, angle: Union[FreeParameter, float]) -> str: + """ + Generates a formatted ascii representation of an angled gate. + + Args: + gate (str): The name of the gate. + angle (Union[FreeParameter, float]): The angle for the gate. + + Returns: + str: Returns the ascii representation for an angled gate. + + """ + return f'{gate}({angle:{".2f" if isinstance(angle, (float, Float)) else ""}})' + + +def get_angle(self, **kwargs): + """ + Gets the angle with all values substituted in that are requested. + + Args: + self: The subclass of AngledGate for which the angle is being obtained. + **kwargs: The named parameters that are being filled for a particular gate. + + Returns: + A new gate of the type of the AngledGate originally used with all angles updated. + + """ + new_angle = ( + self.angle.subs(kwargs) if isinstance(self.angle, FreeParameterExpression) else self.angle + ) + return type(self)(angle=new_angle) diff --git a/src/braket/circuits/parameterizable.py b/src/braket/circuits/parameterizable.py new file mode 100644 index 00000000..4032cdd3 --- /dev/null +++ b/src/braket/circuits/parameterizable.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 abc import ABC, abstractmethod +from typing import List, Union + +from braket.circuits.free_parameter import FreeParameter +from braket.circuits.free_parameter_expression import FreeParameterExpression + + +class Parameterizable(ABC): + """ + A parameterized object is the abstract definition + of an object that can take in FreeParameters. + """ + + @property + @abstractmethod + def parameters(self) -> List[Union[FreeParameterExpression, FreeParameter, float]]: + """ + Returns the free parameters associated with the object. + """ + + @abstractmethod + def bind_values(self, **kwargs): + """ + Takes in parameters and returns an object with specified parameters + replaced with their values. + + Args: + **kwargs: The named parameters and the corresponding values. + """ diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 334de799..e166e5ba 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -31,8 +31,8 @@ ) from jsonschema import validate -from braket.aws import AwsDevice, AwsDeviceType, AwsQuantumTask -from braket.circuits import Circuit +from braket.aws import AwsDevice, AwsDeviceType, AwsQuantumTask, AwsQuantumTaskBatch +from braket.circuits import Circuit, FreeParameter from braket.device_schema.device_execution_window import DeviceExecutionWindow from braket.device_schema.dwave import DwaveDeviceCapabilities from braket.device_schema.rigetti import RigettiDeviceCapabilities @@ -251,6 +251,36 @@ def test_gate_model_sim_schema(): ) +@pytest.fixture +def parameterized_quantum_task(aws_session, s3_destination_folder): + theta = FreeParameter("theta") + circ = Circuit().ry(angle=theta, target=0) + return AwsQuantumTask.create( + device_arn="arn:aws:braket:::device/quantum-simulator/amazon/sv1", + aws_session=aws_session, + poll_timeout_seconds=2, + task_specification=circ, + shots=10, + s3_destination_folder=s3_destination_folder, + ) + + +@pytest.fixture +def parameterized_quantum_task_batch(aws_session, s3_destination_folder): + theta = FreeParameter("theta") + circ_1 = Circuit().ry(angle=3, target=0) + circ_2 = Circuit().ry(angle=theta, target=0) + return AwsQuantumTaskBatch( + device_arn="arn:aws:braket:::device/quantum-simulator/amazon/sv1", + aws_session=aws_session, + poll_timeout_seconds=2, + task_specifications=[circ_1, circ_2], + shots=1, + s3_destination_folder=s3_destination_folder, + max_parallel=100, + ) + + @pytest.fixture( params=[ "arn:aws:braket:us-west-1::device/quantum-simulator/amazon/sim", @@ -582,6 +612,38 @@ def test_run_no_extra(aws_quantum_task_mock, device, circuit): ) +@pytest.mark.xfail(raises=ValueError) +def test_run_param_circuit(parameterized_quantum_task, device, s3_destination_folder): + theta = FreeParameter("theta") + circ = Circuit().ry(angle=theta, target=0) + _run_and_assert( + parameterized_quantum_task, + device, + circ, + s3_destination_folder, + shots=10, + ) + + +@pytest.mark.xfail(raises=ValueError) +def test_run_batch_param_circuit( + parameterized_quantum_task_batch, aws_session, device, s3_destination_folder +): + theta = FreeParameter("theta") + circ_1 = Circuit().ry(angle=3, target=0) + circ_2 = Circuit().ry(angle=theta, target=0) + circuits = [circ_1, circ_2] + + _run_batch_and_assert( + parameterized_quantum_task_batch, + aws_session, + device, + circuits, + s3_destination_folder, + shots=10, + ) + + @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_positional_args(aws_quantum_task_mock, device, circuit, s3_destination_folder): _run_and_assert( diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index f8aa27f4..1946e236 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -17,7 +17,7 @@ import pytest from pydantic import BaseModel -from braket.circuits import AngledGate, Gate +from braket.circuits import AngledGate, FreeParameter, FreeParameterExpression, Gate @pytest.fixture @@ -61,6 +61,45 @@ def test_equality(angled_gate): assert angled_gate != non_gate +def test_symbolic_equality(): + symbol1 = FreeParameter("theta") + symbol2 = FreeParameter("phi") + symbol3 = FreeParameter("theta") + gate1 = AngledGate(angle=symbol1, qubit_count=1, ascii_symbols=["bar"]) + gate2 = AngledGate(angle=symbol1, qubit_count=1, ascii_symbols=["bar"]) + gate3 = AngledGate(angle=symbol3, qubit_count=1, ascii_symbols=["bar"]) + other_gate = AngledGate(angle=symbol2, qubit_count=1, ascii_symbols=["foo"]) + + assert gate1 == gate2 + assert gate1 == gate3 + assert gate1 is not gate2 + assert gate1 != other_gate + + +def test_mixed_angle_equality(): + symbol1 = FreeParameter("theta") + gate1 = AngledGate(angle=symbol1, qubit_count=1, ascii_symbols=["bar"]) + gate2 = AngledGate(angle=0.15, qubit_count=1, ascii_symbols=["foo"]) + + assert gate1 != gate2 + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_bind_values(): + theta = FreeParameter("theta") + gate = AngledGate(angle=theta, qubit_count=1, ascii_symbols=["bar"]) + gate.bind_values(theta=1) + + +def test_angled_gate_with_expr(): + expr = FreeParameterExpression(FreeParameter("theta") + 1) + new_expr = expr.subs({"theta": 1}) + gate = AngledGate(angle=new_expr, qubit_count=1, ascii_symbols=["bar"]) + expected = AngledGate(angle=2, qubit_count=1, ascii_symbols=["bar"]) + + assert gate == expected + + 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() 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 b485486d..643c4d9a 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -13,7 +13,15 @@ import numpy as np -from braket.circuits import AsciiCircuitDiagram, Circuit, Gate, Instruction, Observable, Operator +from braket.circuits import ( + AsciiCircuitDiagram, + Circuit, + FreeParameter, + Gate, + Instruction, + Observable, + Operator, +) def test_empty_circuit(): @@ -27,6 +35,66 @@ def test_one_gate_one_qubit(): assert AsciiCircuitDiagram.build_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 |") + expected = "\n".join(expected) + + assert AsciiCircuitDiagram.build_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}.", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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: {θ}.", + ) + expected = "\n".join(expected) + assert AsciiCircuitDiagram.build_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 |", + ) + expected = "\n".join(expected) + print(AsciiCircuitDiagram.build_diagram(new_circ)) + assert AsciiCircuitDiagram.build_diagram(new_circ) == expected + + def test_qubit_width(): circ = Circuit().h(0).h(100) expected = ( diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index de693289..dd19e3a3 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -20,6 +20,7 @@ from braket.circuits import ( AsciiCircuitDiagram, Circuit, + FreeParameter, Gate, Instruction, Moments, @@ -104,6 +105,64 @@ def test_equality(): assert circ_1 != non_circ +def test_call(): + alpha = FreeParameter("alpha") + theta = FreeParameter("theta") + circ = Circuit().h(0).rx(angle=theta, target=1).ry(angle=alpha, target=0) + new_circ = circ(theta=1, alpha=0) + expected = Circuit().h(0).rx(angle=1, target=1).ry(angle=0, target=0) + assert new_circ == expected and not new_circ.parameters + + +def test_call_with_result_type(prob): + alpha = FreeParameter("alpha") + theta = FreeParameter("theta") + circ = Circuit().h(0).rx(angle=theta, target=1).ry(angle=alpha, target=0).add_result_type(prob) + new_circ = circ(theta=1, alpha=0) + expected = Circuit().h(0).rx(angle=1, target=1).ry(angle=0, target=0).add_result_type(prob) + + assert new_circ == expected and not new_circ.parameters + assert new_circ.observables_simultaneously_measurable + assert list(new_circ.result_types) == [prob] + + +def test_call_one_param_not_bound(): + alpha = FreeParameter("alpha") + theta = FreeParameter("theta") + 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) + + assert new_circ == expected_circ and new_circ.parameters == expected_parameters + + +def test_call_with_default_parameter_val(): + alpha = FreeParameter("alpha") + beta = FreeParameter("beta") + theta = FreeParameter("theta") + gamma = FreeParameter("gamma") + circ = ( + Circuit() + .h(0) + .rx(angle=theta, target=1) + .ry(angle=alpha, target=0) + .ry(angle=beta, target=2) + .rx(angle=gamma, target=1) + ) + new_circ = circ(np.pi, theta=1, alpha=0) + expected = ( + Circuit() + .h(0) + .rx(angle=1, target=1) + .ry(angle=0, target=0) + .ry(angle=np.pi, target=2) + .rx(angle=np.pi, target=1) + ) + assert new_circ == expected and not new_circ.parameters + + def test_add_result_type_default(prob): circ = Circuit().add_result_type(prob) assert circ.observables_simultaneously_measurable @@ -607,6 +666,13 @@ def test_as_unitary_noise_raises_error(circuit): circuit.as_unitary() +@pytest.mark.xfail(raises=TypeError) +def test_as_unitary_parameterized(): + theta = FreeParameter("theta") + circ = Circuit().rx(angle=theta, target=0) + assert np.allclose(circ.as_unitary()) + + def test_as_unitary_noise_not_apply_returns_expected_unitary(recwarn): circuit = ( Circuit() @@ -946,6 +1012,26 @@ def test_as_unitary_one_gate_returns_expected_unitary(circuit, expected_unitary) assert np.allclose(circuit.as_unitary(), expected_unitary) +def test_circuit_with_symbol(): + theta = FreeParameter("theta") + + circ = ( + Circuit() + .ry(angle=theta, target=0) + .ry(angle=theta, target=1) + .ry(angle=theta, target=2) + .ry(angle=theta, target=3) + ) + expected = ( + Circuit() + .ry(angle=theta, target=0) + .ry(angle=theta, target=1) + .ry(angle=theta, target=2) + .ry(angle=theta, target=3) + ) + assert circ == expected + + def test_basis_rotation_instructions_all(): circ = Circuit().h(0).cnot(0, 1).sample(observable=Observable.Y()) expected = [ @@ -1306,3 +1392,205 @@ def test_diagram(h): assert h.diagram(mock_diagram) == expected mock_diagram.build_diagram.assert_called_with(h) + + +def test_add_parameterized_check_true(): + theta = FreeParameter("theta") + circ = ( + Circuit() + .ry(angle=theta, target=0) + .ry(angle=theta, target=1) + .ry(angle=theta, target=2) + .ry(angle=theta, target=3) + ) + expected = set() + expected.add(theta) + + assert circ.parameters == expected + + +def test_add_parameterized_instr_parameterized_circ_check_true(): + theta = FreeParameter("theta") + alpha = FreeParameter("alpha") + 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) + + assert circ.parameters == expected + + +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) + + assert circ.parameters == expected + + +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) + + assert circ.parameters == expected + + +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) + + assert circ.parameters == expected + + +@pytest.mark.parametrize( + "input_circ", + [ + (Circuit().ry(angle=1, target=0).ry(angle=2, target=1)), + (Circuit().ry(angle=1, target=0).add_circuit(Circuit().ry(angle=2, target=0))), + ], +) +def test_parameterized_check_false(input_circ): + circ = input_circ + expected = 0 + + assert len(circ.parameters) == expected + + +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) + + assert circ.parameters == expected + + +def test_no_parameters(): + circ = Circuit().ry(angle=0.12, target=0).ry(angle=0.25, target=1).ry(angle=0.6, target=2) + expected = set() + + assert circ.parameters == expected + + +def test_make_bound_circuit_strict(): + theta = FreeParameter("theta") + input_val = np.pi + circ = Circuit().ry(angle=theta, target=0).ry(angle=theta, target=1).ry(angle=theta, target=2) + circ_new = circ.make_bound_circuit({"theta": input_val}, strict=True) + expected = ( + Circuit().ry(angle=np.pi, target=0).ry(angle=np.pi, target=1).ry(angle=np.pi, target=2) + ) + + assert circ_new == expected + + +def test_make_bound_circuit_strict_false(): + input_val = np.pi + theta = FreeParameter("theta") + param_superset = {"theta": input_val, "alpha": input_val, "beta": input_val} + circ = Circuit().ry(angle=theta, target=0).ry(angle=theta, target=1).ry(angle=theta, target=2) + circ_new = circ.make_bound_circuit(param_superset) + expected = ( + Circuit().ry(angle=np.pi, target=0).ry(angle=np.pi, target=1).ry(angle=np.pi, target=2) + ) + + assert circ_new == expected + + +def test_make_bound_circuit_multiple(): + theta = FreeParameter("theta") + alpha = FreeParameter("alpha") + input_val = np.pi + circ = Circuit().ry(angle=theta, target=0).ry(angle=theta, target=1).ry(angle=alpha, target=2) + circ_new = circ.make_bound_circuit({"theta": input_val, "alpha": input_val}) + expected = ( + Circuit().ry(angle=np.pi, target=0).ry(angle=np.pi, target=1).ry(angle=np.pi, target=2) + ) + + assert circ_new == expected + + +def test_make_bound_circuit_partial_bind(): + theta = FreeParameter("theta") + alpha = FreeParameter("alpha") + input_val = np.pi + circ = Circuit().ry(angle=theta, target=0).ry(angle=theta, target=1).ry(angle=alpha, target=2) + circ_new = circ.make_bound_circuit({"theta": input_val}) + 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) + + assert circ_new == expected_circ and circ_new.parameters == expected_parameters + + +@pytest.mark.xfail(raises=ValueError) +def test_make_bound_circuit_non_existent_param(): + theta = FreeParameter("theta") + input_val = np.pi + circ = Circuit().ry(angle=theta, target=0).ry(angle=theta, target=1).ry(angle=theta, target=2) + circ.make_bound_circuit({"alpha": input_val}, strict=True) + + +@pytest.mark.xfail(raises=ValueError) +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) + circ.make_bound_circuit({"theta": input_val}) + + +def test_circuit_with_expr(): + theta = FreeParameter("theta") + alpha = FreeParameter("alpha") + circ = ( + Circuit() + .ry(angle=theta * 2 + theta, target=0) + .rx(angle=(alpha + theta + 2 * alpha * theta), target=2) + .rz(angle=theta, target=1) + ) + circ.add_instruction(Instruction(Gate.Ry(alpha), 3)) + + new_circ = circ(theta=1, alpha=np.pi) + expected = ( + Circuit() + .ry(angle=3, target=0) + .rx(angle=(3 * np.pi + 1), target=2) + .rz(angle=1, target=1) + .ry(angle=np.pi, target=3) + ) + + assert new_circ == expected + + +def test_circuit_with_expr_not_fully_bound(): + theta = FreeParameter("theta") + alpha = FreeParameter("alpha") + circ = ( + Circuit() + .ry(angle=theta * 2 + theta, target=0) + .rx(angle=(alpha + theta + 2 * alpha * theta), target=2) + .rz(angle=theta, target=1) + ) + circ.add_instruction(Instruction(Gate.Ry(alpha), 3)) + + new_circ = circ(theta=1) + expected = ( + Circuit() + .ry(angle=3, target=0) + .rx(angle=(3 * alpha + 1), target=2) + .rz(angle=1, target=1) + .ry(angle=alpha, target=3) + ) + assert new_circ == expected diff --git a/test/unit_tests/braket/circuits/test_free_parameter.py b/test/unit_tests/braket/circuits/test_free_parameter.py new file mode 100644 index 00000000..c1698035 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_free_parameter.py @@ -0,0 +1,63 @@ +# 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 FreeParameter + + +@pytest.fixture +def free_parameter(): + return FreeParameter("theta") + + +@pytest.mark.xfail(raises=TypeError) +def test_bad_input(): + FreeParameter(6) + + +def test_is_free_param(free_parameter): + assert isinstance(free_parameter, FreeParameter) + + +def test_equality(): + param_1 = FreeParameter("theta") + param_2 = FreeParameter("theta") + other_param = FreeParameter("phi") + non_param = "non circuit" + + assert param_1 == param_2 + assert param_1 is not param_2 + assert param_1 != other_param + assert param_1 != non_param + + +def test_str(free_parameter): + expected = "theta" + assert str(free_parameter) == expected + + +def test_hash(free_parameter): + assert hash(free_parameter) == hash(tuple(free_parameter.name)) + + +def test_rep(free_parameter): + assert repr(free_parameter) == free_parameter.name + + +def test_sub_successful(free_parameter): + assert free_parameter.subs({"theta": 1}) == 1 + + +def test_sub_wrong_param(free_parameter): + assert free_parameter.subs({"alpha": 1}) == FreeParameter("theta") diff --git a/test/unit_tests/braket/circuits/test_free_parameter_expression.py b/test/unit_tests/braket/circuits/test_free_parameter_expression.py new file mode 100644 index 00000000..14ce3fe5 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_free_parameter_expression.py @@ -0,0 +1,126 @@ +# 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 FreeParameter, FreeParameterExpression + + +@pytest.fixture +def free_parameter_expression(): + return FreeParameterExpression(FreeParameter("theta") - 1) + + +def test_is_free_param_expr(free_parameter_expression): + assert isinstance(free_parameter_expression, FreeParameterExpression) + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_constructor_bad_input(): + FreeParameterExpression("theta") + + +def test_equality(): + expr_1 = FreeParameterExpression(FreeParameter("theta") + 1) + expr_2 = FreeParameterExpression(FreeParameter("theta") + 1) + other_expr = FreeParameterExpression(FreeParameter("theta")) + non_expr = "non circuit" + + assert expr_1 == expr_2 + assert expr_1 is not expr_2 + assert expr_1 != other_expr + assert expr_1 != non_expr + + +def test_commutativity(): + add_1 = 1 + FreeParameterExpression(FreeParameter("theta")) + add_2 = FreeParameterExpression(FreeParameter("theta")) + 1 + mul_1 = FreeParameterExpression(FreeParameter("theta") * 1) + mul_2 = FreeParameterExpression(1 * FreeParameter("theta")) + + assert add_1 == add_2 + assert mul_1 == mul_2 + + +def test_add(): + add_expr = FreeParameter("theta") + FreeParameter("theta") + expected = FreeParameterExpression(2 * FreeParameter("theta")) + + assert add_expr == expected + + +def test_sub(): + sub_expr = FreeParameter("theta") - FreeParameter("alpha") + 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 + + +def test_mul(): + mul_expr = FreeParameter("theta") * FreeParameter("alpha") * 2 * FreeParameter("theta") + expected = FreeParameterExpression(FreeParameter("theta") ** 2 * FreeParameter("alpha") * 2) + assert mul_expr == expected + + +def test_pow(): + mul_expr = FreeParameter("theta") ** FreeParameter("alpha") * 2 + expected = FreeParameterExpression(FreeParameter("theta") ** FreeParameter("alpha") * 2) + assert mul_expr == expected + + +def test_pow_constant(): + mul_expr = FreeParameter("theta") ** 2 + expected = FreeParameterExpression(FreeParameter("theta") ** 2) + assert mul_expr == expected + + +def test_r_pow(): + mul_expr = 2 ** FreeParameter("theta") + expected = FreeParameterExpression(2 ** FreeParameter("theta")) + assert mul_expr == expected + + +def test_neg(): + expr = FreeParameter("theta") * FreeParameter("alpha") * 2 + expected_expr = -FreeParameter("theta") * -FreeParameter("alpha") * -2 + assert -expr == expected_expr and -(-expr) == expr + + +def test_sub_string(): + theta = FreeParameter("theta") + expr = theta + 1 + assert expr.subs({"theta": 1}) == 2 + + +def test_sub_free_parameter(): + theta = FreeParameter("theta") + expr = theta + 1 + param_values = {theta: 1} + assert expr.subs(param_values) == 2 + + +def test_sub_return_expression(): + expr = FreeParameter("theta") + 1 + FreeParameter("alpha") + subbed_expr = expr.subs({"alpha": 1}) + expected = FreeParameter("theta") + 2 + + assert subbed_expr == expected diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 3bc18fb0..100fa41c 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -15,7 +15,7 @@ import pytest import braket.ir.jaqcd as ir -from braket.circuits import Circuit, Gate, Instruction, QubitSet +from braket.circuits import Circuit, FreeParameter, Gate, Instruction, QubitSet from braket.ir.jaqcd.shared_models import ( Angle, DoubleControl, @@ -102,6 +102,23 @@ ] +parameterizable_gates = [ + Gate.Rx, + Gate.Ry, + Gate.Rz, + Gate.PhaseShift, + Gate.PSwap, + Gate.XX, + Gate.XY, + Gate.YY, + Gate.ZZ, + Gate.CPhaseShift, + Gate.CPhaseShift00, + Gate.CPhaseShift01, + Gate.CPhaseShift10, +] + + invalid_unitary_matrices = [ (np.array([[1]])), (np.array([1])), @@ -321,6 +338,19 @@ def test_equality(): assert u1 != non_gate +def test_free_param_equality(): + param1 = FreeParameter("theta") + param2 = FreeParameter("phi") + rx1 = Gate.Rx(param1) + rx2 = Gate.Rx(param1) + other_gate = Gate.Rx(param2) + + assert rx1 == rx2 + assert rx1 is not rx2 + assert rx1 != other_gate + assert rx1 != param1 + + def test_large_unitary(): matrix = np.eye(16, dtype=np.float32) # Permute rows of matrix @@ -329,6 +359,20 @@ def test_large_unitary(): assert unitary.qubit_count == 4 +@pytest.mark.parametrize("gate", parameterizable_gates) +def test_bind_values(gate): + theta = FreeParameter("theta") + param_gate = gate(theta) + new_gate = param_gate.bind_values(theta=1) + expected = gate(1) + + assert ( + type(new_gate.angle) == float + and type(new_gate) == type(param_gate) + and new_gate == expected + ) + + @pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("matrix", invalid_unitary_matrices) def test_unitary_invalid_matrix(matrix): From f5b89a929127932ec86bfc487670073602873086 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 2 Mar 2022 18:21:49 +0000 Subject: [PATCH 0434/1165] prepare release v1.17.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 e8f9ed5b..e272c9da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.17.0 (2022-03-02) + +### Features + + * Add parameterized circuits + ## v1.16.1 (2022-03-01) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index cca275b9..1d82eb09 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.16.2.dev0" +__version__ = "1.17.0" From 29a8b633184ae3f312d2cdacbc9a46f3abb4edb6 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 2 Mar 2022 18:21:49 +0000 Subject: [PATCH 0435/1165] update development version to v1.17.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 1d82eb09..7b340217 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.17.0" +__version__ = "1.17.1.dev0" From 2b894fc679e70c6a3c3545ca0221d1f49e0b69db Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 7 Mar 2022 11:59:15 -0800 Subject: [PATCH 0436/1165] feature: Add support for running OpenQASM programs (#310) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Aaron Berdy Co-authored-by: Abe Coull Co-authored-by: Cedric Lin <67704428+licedric@users.noreply.github.com> Co-authored-by: Christian Madsen Co-authored-by: Cody Wang Co-authored-by: Jacob Feldman Co-authored-by: Jeff Heckey Co-authored-by: Kshitij Chhabra Co-authored-by: Lin Co-authored-by: Milan <30416311+krneta@users.noreply.github.com> Co-authored-by: Milan Krneta Co-authored-by: Roald Bradley Severtson Co-authored-by: Viraj Chaudhari Co-authored-by: Yiheng Duan <98372300+duanyh12@users.noreply.github.com> Co-authored-by: mbeach-aws Co-authored-by: ℂ𝓞𝔇𝚈 --- src/braket/aws/aws_device.py | 5 +- src/braket/aws/aws_quantum_task.py | 21 +++++- src/braket/aws/aws_quantum_task_batch.py | 3 +- .../tasks/gate_model_quantum_task_result.py | 8 +- .../gate_model_device_testing_utils.py | 75 ++++++++++++++++++- .../test_simulator_quantum_task.py | 26 +++++++ .../test_tensor_network_simulator.py | 10 ++- test/unit_tests/braket/aws/test_aws_device.py | 23 ++++-- .../braket/aws/test_aws_quantum_task.py | 37 +++++++-- .../test_gate_model_quantum_task_result.py | 63 ++++++++++++++++ 10 files changed, 246 insertions(+), 25 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 98a5e787..0e1e3f81 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -29,6 +29,7 @@ from braket.device_schema import DeviceCapabilities, ExecutionDay, GateModelQpuParadigmProperties from braket.device_schema.dwave import DwaveProviderProperties from braket.devices.device import Device +from braket.ir.openqasm import Program as OpenQasmProgram from braket.schema_common import BraketSchemaBase @@ -79,7 +80,7 @@ def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): def run( self, - task_specification: Union[Circuit, Problem], + task_specification: Union[Circuit, Problem, OpenQasmProgram], s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, shots: Optional[int] = None, poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, @@ -161,7 +162,7 @@ def run( def run_batch( self, - task_specifications: List[Union[Circuit, Problem]], + task_specifications: List[Union[Circuit, Problem, OpenQasmProgram]], s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, shots: Optional[int] = None, max_parallel: Optional[int] = None, diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 8f728e08..ee77bfb4 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -41,6 +41,7 @@ from braket.device_schema.oqc import OqcDeviceParameters from braket.device_schema.rigetti import RigettiDeviceParameters from braket.device_schema.simulators import GateModelSimulatorDeviceParameters +from braket.ir.openqasm import Program as OpenQasmProgram from braket.schema_common import BraketSchemaBase from braket.task_result import AnnealingTaskResult, GateModelTaskResult from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult, QuantumTask @@ -63,7 +64,7 @@ class AwsQuantumTask(QuantumTask): def create( aws_session: AwsSession, device_arn: str, - task_specification: Union[Circuit, Problem], + task_specification: Union[Circuit, Problem, OpenQasmProgram], s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, device_parameters: Dict[str, Any] = None, @@ -417,6 +418,22 @@ def _create_internal( raise TypeError("Invalid task specification type") +@_create_internal.register +def _( + open_qasm_program: OpenQasmProgram, + aws_session: AwsSession, + create_task_kwargs: Dict[str, Any], + device_arn: str, + _device_parameters: Union[dict, BraketSchemaBase], # Not currently used for OpenQasmProgram + _disable_qubit_rewiring, + *args, + **kwargs, +) -> AwsQuantumTask: + create_task_kwargs.update({"action": open_qasm_program.json()}) + task_arn = aws_session.create_quantum_task(**create_task_kwargs) + return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) + + @_create_internal.register def _( circuit: Circuit, @@ -469,7 +486,7 @@ def _( DwaveAdvantageDeviceParameters, Dwave2000QDeviceParameters, ], - disable_qubit_rewiring, + _, *args, **kwargs, ) -> AwsQuantumTask: diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 95cea721..848dd8bc 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -21,6 +21,7 @@ from braket.aws.aws_quantum_task import AwsQuantumTask from braket.aws.aws_session import AwsSession from braket.circuits import Circuit +from braket.ir.openqasm import Program as OpenQasmProgram class AwsQuantumTaskBatch: @@ -41,7 +42,7 @@ def __init__( self, aws_session: AwsSession, device_arn: str, - task_specifications: List[Union[Circuit, Problem]], + task_specifications: List[Union[Circuit, Problem, OpenQasmProgram]], s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, max_parallel: int, diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index b001f01a..757c718e 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -278,8 +278,12 @@ def _from_object_internal_computational_basis_sampling(cls, result: GateModelTas f"Measured qubits {measured_qubits} is not equivalent to number of qubits " + f"{measurements.shape[1]} in measurements" ) - result_types = GateModelQuantumTaskResult._calculate_result_types( - additional_metadata.action.json(), measurements, measured_qubits + result_types = ( + result.resultTypes + if result.resultTypes + else GateModelQuantumTaskResult._calculate_result_types( + additional_metadata.action.json(), measurements, measured_qubits + ) ) values = [rt.value for rt in result_types] return cls( diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index 19994355..fbbecac0 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 +from typing import Any, Dict, Union import numpy as np @@ -21,6 +21,7 @@ from braket.circuits import Circuit, Gate, Instruction, Observable, ResultType from braket.circuits.quantum_operator_helpers import get_pauli_eigenvalues from braket.devices import Device +from braket.ir.openqasm import Program as OpenQasmProgram from braket.tasks import GateModelQuantumTaskResult @@ -41,11 +42,14 @@ def qubit_ordering_testing(device: Device, run_kwargs: Dict[str, Any]): def no_result_types_testing( - circuit: Circuit, device: Device, run_kwargs: Dict[str, Any], expected: Dict[str, float] + program: Union[Circuit, OpenQasmProgram], + device: Device, + run_kwargs: Dict[str, Any], + expected: Dict[str, float], ): shots = run_kwargs["shots"] tol = get_tol(shots) - result = device.run(circuit, **run_kwargs).result() + result = device.run(program, **run_kwargs).result() probabilities = result.measurement_probabilities for bitstring in probabilities: assert np.allclose(probabilities[bitstring], expected[bitstring], **tol) @@ -569,6 +573,71 @@ 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]): + openqasm_string = ( + "OPENQASM 3;" + "qubit[2] q;" + "bit[2] c;" + "h q[0];" + "cnot q[0], q[1];" + "c[0] = measure q[0];" + "c[1] = measure q[1];" + ) + no_result_types_testing( + OpenQasmProgram(source=openqasm_string), device, run_kwargs, {"00": 0.5, "11": 0.5} + ) + + +def openqasm_noisy_circuit_1qubit_noise_full_probability( + device: Device, run_kwargs: Dict[str, Any] +): + shots = run_kwargs["shots"] + tol = get_tol(shots) + openqasm_string = ( + "OPENQASM 3;" + "qubit[2] q;" + "x q[0];" + "x q[1];" + "#pragma braket noise bit_flip(0.1) q[0]" + "#pragma braket result probability q[0], q[1]" + ) + result = device.run(OpenQasmProgram(source=openqasm_string), **run_kwargs).result() + assert len(result.result_types) == 1 + assert np.allclose( + result.get_value_by_result_type(ResultType.Probability(target=[0, 1])), + np.array([0.0, 0.1, 0, 0.9]), + **tol + ) + + +def openqasm_result_types_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): + openqasm_string = ( + "OPENQASM 3;" + "qubit[2] q;" + "h q[0];" + "cnot q[0], q[1];" + "#pragma braket result expectation h(q[0]) @ x(q[1])" + "#pragma braket result sample h(q[0]) @ x(q[1])" + ) + result = device.run(OpenQasmProgram(source=openqasm_string), **run_kwargs).result() + assert len(result.result_types) == 2 + assert ( + 0.6 + < result.get_value_by_result_type( + ResultType.Expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ) + < 0.8 + ) + assert ( + len( + result.get_value_by_result_type( + ResultType.Sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ) + ) + == run_kwargs["shots"] + ) + + def many_layers(n_qubits: int, n_layers: int) -> Circuit: """ Function to return circuit with many layers. diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 99be2102..50dd35e6 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -16,10 +16,13 @@ import pytest from gate_model_device_testing_utils import ( batch_bell_pair_testing, + bell_pair_openqasm_testing, get_tol, many_layers, multithreaded_bell_pair_testing, no_result_types_bell_pair_testing, + openqasm_noisy_circuit_1qubit_noise_full_probability, + openqasm_result_types_bell_pair_testing, qubit_ordering_testing, result_types_all_selected_testing, result_types_bell_pair_full_probability_testing, @@ -217,6 +220,29 @@ def test_batch_bell_pair(simulator_arn, aws_session, 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) + 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) + 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): + 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} + ) + + @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): diff --git a/test/integ_tests/test_tensor_network_simulator.py b/test/integ_tests/test_tensor_network_simulator.py index e14f8f73..093781b6 100644 --- a/test/integ_tests/test_tensor_network_simulator.py +++ b/test/integ_tests/test_tensor_network_simulator.py @@ -15,7 +15,7 @@ import random import pytest -from gate_model_device_testing_utils import no_result_types_testing +from gate_model_device_testing_utils import bell_pair_openqasm_testing, no_result_types_testing from braket.aws import AwsDevice from braket.circuits import Circuit @@ -52,6 +52,14 @@ def test_qft_iqft_h(simulator_arn, aws_session, 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) + bell_pair_openqasm_testing( + device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} + ) + + def _ghz(num_qubits): circuit = Circuit() circuit.h(0) diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index e166e5ba..4ac896c1 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -37,6 +37,7 @@ from braket.device_schema.dwave import DwaveDeviceCapabilities from braket.device_schema.rigetti import RigettiDeviceCapabilities from braket.device_schema.simulators import GateModelSimulatorDeviceCapabilities +from braket.ir.openqasm import Program as OpenQasmProgram MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_1 = { "braketSchemaHeader": { @@ -297,15 +298,18 @@ def s3_destination_folder(): @pytest.fixture -def circuit(): +def bell_circuit(): return Circuit().h(0) @pytest.fixture -def boto_session(): - _boto_session = Mock() - _boto_session.region_name = RIGETTI_REGION - return _boto_session +def openqasm_program(): + return OpenQasmProgram(source="OPENQASM 3.0; h $0;") + + +@pytest.fixture(params=["bell_circuit", "openqasm_program"]) +def circuit(request): + return request.getfixturevalue(request.param) @pytest.fixture @@ -647,7 +651,14 @@ def test_run_batch_param_circuit( @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_positional_args(aws_quantum_task_mock, device, circuit, s3_destination_folder): _run_and_assert( - aws_quantum_task_mock, device, circuit, s3_destination_folder, 100, 86400, 0.25, ["foo"] + aws_quantum_task_mock, + device, + circuit, + s3_destination_folder, + 100, + 86400, + 0.25, + ["foo"], ) 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 bcc517d4..6b1c44be 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -36,6 +36,7 @@ from braket.device_schema.oqc import OqcDeviceParameters from braket.device_schema.rigetti import RigettiDeviceParameters from braket.device_schema.simulators import GateModelSimulatorDeviceParameters +from braket.ir.openqasm import Program as OpenQasmProgram from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult S3_TARGET = AwsSession.S3DestinationFolder("foo", "bar") @@ -90,6 +91,11 @@ def problem(): return Problem(ProblemType.ISING, linear={1: 3.14}, quadratic={(1, 2): 10.08}) +@pytest.fixture +def openqasm_program(): + return OpenQasmProgram(source="OPENQASM 3.0; h $0;") + + def test_equality(arn, aws_session): quantum_task_1 = AwsQuantumTask(arn, aws_session) quantum_task_2 = AwsQuantumTask(arn, aws_session) @@ -359,6 +365,20 @@ def test_create_invalid_task_specification(aws_session, arn): AwsQuantumTask.create(aws_session, arn, "foo", S3_TARGET, 1000) +def test_create_openqasm_program(aws_session, arn, openqasm_program): + aws_session.create_quantum_task.return_value = arn + shots = 21 + AwsQuantumTask.create(aws_session, SIMULATOR_ARN, openqasm_program, S3_TARGET, shots) + + _assert_create_quantum_task_called_with( + aws_session, + SIMULATOR_ARN, + openqasm_program.json(), + S3_TARGET, + shots, + ) + + @pytest.mark.parametrize("device_arn,device_parameters_class", DEVICE_PARAMETERS) def test_from_circuit_with_shots(device_arn, device_parameters_class, aws_session, circuit): mocked_task_arn = "task-arn-1" @@ -371,7 +391,7 @@ def test_from_circuit_with_shots(device_arn, device_parameters_class, aws_sessio _assert_create_quantum_task_called_with( aws_session, device_arn, - circuit, + circuit.to_ir().json(), S3_TARGET, shots, device_parameters_class( @@ -400,7 +420,7 @@ def test_from_circuit_with_disabled_rewiring( _assert_create_quantum_task_called_with( aws_session, device_arn, - circuit, + circuit.to_ir().json(), S3_TARGET, shots, device_parameters_class( @@ -433,7 +453,7 @@ def test_from_circuit_with_verbatim(device_arn, device_parameters_class, aws_ses _assert_create_quantum_task_called_with( aws_session, device_arn, - circ, + circ.to_ir().json(), S3_TARGET, shots, device_parameters_class( @@ -716,7 +736,7 @@ def test_from_annealing(device_parameters, aws_session, arn, problem): _assert_create_quantum_task_called_with( aws_session, arn, - problem, + problem.to_ir().json(), S3_TARGET, 1000, annealing_parameters, @@ -735,7 +755,7 @@ def test_create_with_tags(device_arn, device_parameters_class, aws_session, circ _assert_create_quantum_task_called_with( aws_session, device_arn, - circuit, + circuit.to_ir().json(), S3_TARGET, shots, device_parameters_class( @@ -769,16 +789,17 @@ def _init_and_add_to_list(aws_session, arn, task_list): def _assert_create_quantum_task_called_with( - aws_session, arn, task_description, s3_results_prefix, shots, device_parameters, tags=None + aws_session, arn, task_description, s3_results_prefix, shots, device_parameters=None, tags=None ): test_kwargs = { "deviceArn": arn, "outputS3Bucket": s3_results_prefix[0], "outputS3KeyPrefix": s3_results_prefix[1], - "action": task_description.to_ir().json(), - "deviceParameters": device_parameters.json(exclude_none=True), + "action": task_description, "shots": shots, } + if device_parameters is not None: + test_kwargs.update({"deviceParameters": device_parameters.json(exclude_none=True)}) if tags is not None: test_kwargs.update({"tags": tags}) aws_session.create_quantum_task.assert_called_with(**test_kwargs) 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 3e8277ac..903ce706 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 @@ -13,6 +13,7 @@ import json from typing import Counter +from unittest.mock import patch import numpy as np import pytest @@ -420,3 +421,65 @@ def test_hash_result_types(observable_1, observable_2): assert GateModelQuantumTaskResult._result_type_hash( observable_1 ) == GateModelQuantumTaskResult._result_type_hash(observable_2) + + +@patch( + "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": { + "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": 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, + }, + }, + } + ) + res = GateModelQuantumTaskResult.from_string(result_str) + assert ( + res.get_value_by_result_type(ResultType.Variance(observable=Observable.Z(), target=[0])) + == 12 + ) + calculate_result_types_mocked.assert_not_called() From fec06265c4db298f35beae6a0904ded06451f34f Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 7 Mar 2022 20:22:02 +0000 Subject: [PATCH 0437/1165] prepare release v1.18.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 e272c9da..4ba7c321 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.18.0 (2022-03-07) + +### Features + + * Add support for running OpenQASM programs + ## v1.17.0 (2022-03-02) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 7b340217..1c9e9c8b 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.17.1.dev0" +__version__ = "1.18.0" From 1d951d0b76bad43285df866fa61ddeb3196b9b7b Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 7 Mar 2022 20:22:02 +0000 Subject: [PATCH 0438/1165] update development version to v1.18.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 1c9e9c8b..3343e046 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.18.0" +__version__ = "1.18.1.dev0" From 8d7d738fa456e34c11eeb033cadb23c971b24197 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 5 Apr 2022 14:29:45 -0700 Subject: [PATCH 0439/1165] documentation: Specify DEVICE_REGIONS docs. (#321) *documentation: Specify DEVICE_REGIONS docs. Co-authored-by: Abe Coull Co-authored-by: Cody Wang --- src/braket/aws/aws_device.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 0e1e3f81..dfe71752 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -67,8 +67,9 @@ def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): physically located. When this occurs, a cloned `aws_session` is created for the Region the QPU is located in. - See `braket.aws.aws_device.AwsDevice.DEVICE_REGIONS` for the AWS Regions provider - devices are located in. + See `braket.aws.aws_device.AwsDevice.REGIONS` for the AWS regions provider + devices are located in across the AWS Braket service. + This is not a device specific tuple. """ super().__init__(name=None, status=None) self._arn = arn From 75315a313baeab769e82490cb2512aace283462a Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 6 Apr 2022 18:24:19 +0000 Subject: [PATCH 0440/1165] prepare release v1.18.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 4ba7c321..67947b82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.18.0.post0 (2022-04-06) + +### Documentation Changes + + * Specify DEVICE_REGIONS docs. + ## v1.18.0 (2022-03-07) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 3343e046..22afa18a 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.18.1.dev0" +__version__ = "1.18.0.post0" From 0a4baa7f2b9c526ebc4133a3c50cc968694a7ed2 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 6 Apr 2022 18:24:19 +0000 Subject: [PATCH 0441/1165] update development version to v1.18.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 22afa18a..3343e046 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.18.0.post0" +__version__ = "1.18.1.dev0" From 9a36d7f7efa67174929b445146918261ce022991 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Wed, 13 Apr 2022 15:59:25 -0700 Subject: [PATCH 0442/1165] fix: Run github workflows on feature branches (#323) Co-authored-by: Aaron Berdy --- .github/workflows/dependent-tests.yml | 8 ++++++-- .github/workflows/python-package.yml | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index e9da969b..afa7777e 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -2,9 +2,13 @@ name: Dependent tests on: push: - branches: [ main ] + branches: + - main + - feature/** pull_request: - branches: [ main ] + branches: + - main + - feature/** jobs: build: diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index ed3ebdcb..e6b1ad85 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -5,9 +5,13 @@ name: Python package on: push: - branches: [ main ] + branches: + - main + - feature/** pull_request: - branches: [ main ] + branches: + - main + - feature/** jobs: build: From de83740c7ec8af15f3fe490185bd40eb86a5d818 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 13 Apr 2022 17:20:23 -0700 Subject: [PATCH 0443/1165] fix: add exception handling to local job test (#326) --- .../test_create_local_quantum_job.py | 210 +++++++++--------- 1 file changed, 106 insertions(+), 104 deletions(-) diff --git a/test/integ_tests/test_create_local_quantum_job.py b/test/integ_tests/test_create_local_quantum_job.py index 838a0132..02ae41da 100644 --- a/test/integ_tests/test_create_local_quantum_job.py +++ b/test/integ_tests/test_create_local_quantum_job.py @@ -32,74 +32,75 @@ def test_completed_local_job(aws_session, capsys): current_dir = Path.cwd() with tempfile.TemporaryDirectory() as temp_dir: - os.chdir(temp_dir) - job = LocalQuantumJob.create( - "arn:aws:braket:::device/quantum-simulator/amazon/sv1", - source_module=absolute_source_module, - entry_point="job_test_script:start_here", - hyperparameters={"test_case": "completed"}, - aws_session=aws_session, - ) - - job_name = job.name - pattern = f"^local:job/{job_name}$" - re.match(pattern=pattern, string=job.arn) - - assert job.state() == "COMPLETED" - assert Path(job_name).is_dir() - - # Check results match the expectations. - assert Path(f"{job_name}/results.json").exists() - assert job.result() == {"converged": True, "energy": -0.2} - - # Validate checkpoint files and data - assert Path(f"{job_name}/checkpoints/{job_name}.json").exists() - assert Path(f"{job_name}/checkpoints/{job_name}_plain_data.json").exists() - - for file_name, expected_data in [ - ( - f"{job_name}/checkpoints/{job_name}_plain_data.json", - { - "braketSchemaHeader": { - "name": "braket.jobs_data.persisted_job_data", - "version": "1", + try: + os.chdir(temp_dir) + job = LocalQuantumJob.create( + "arn:aws:braket:::device/quantum-simulator/amazon/sv1", + source_module=absolute_source_module, + entry_point="job_test_script:start_here", + hyperparameters={"test_case": "completed"}, + aws_session=aws_session, + ) + + job_name = job.name + pattern = f"^local:job/{job_name}$" + re.match(pattern=pattern, string=job.arn) + + assert job.state() == "COMPLETED" + assert Path(job_name).is_dir() + + # Check results match the expectations. + assert Path(f"{job_name}/results.json").exists() + assert job.result() == {"converged": True, "energy": -0.2} + + # Validate checkpoint files and data + assert Path(f"{job_name}/checkpoints/{job_name}.json").exists() + assert Path(f"{job_name}/checkpoints/{job_name}_plain_data.json").exists() + + for file_name, expected_data in [ + ( + f"{job_name}/checkpoints/{job_name}_plain_data.json", + { + "braketSchemaHeader": { + "name": "braket.jobs_data.persisted_job_data", + "version": "1", + }, + "dataDictionary": {"some_data": "abc"}, + "dataFormat": "plaintext", }, - "dataDictionary": {"some_data": "abc"}, - "dataFormat": "plaintext", - }, - ), - ( - f"{job_name}/checkpoints/{job_name}.json", - { - "braketSchemaHeader": { - "name": "braket.jobs_data.persisted_job_data", - "version": "1", + ), + ( + f"{job_name}/checkpoints/{job_name}.json", + { + "braketSchemaHeader": { + "name": "braket.jobs_data.persisted_job_data", + "version": "1", + }, + "dataDictionary": {"some_data": "gASVBwAAAAAAAACMA2FiY5Qu\n"}, + "dataFormat": "pickled_v4", }, - "dataDictionary": {"some_data": "gASVBwAAAAAAAACMA2FiY5Qu\n"}, - "dataFormat": "pickled_v4", - }, - ), - ]: - with open(file_name, "r") as f: - assert json.loads(f.read()) == expected_data - - # Capture logs - assert Path(f"{job_name}/log.txt").exists() - job.logs() - log_data, errors = capsys.readouterr() - - logs_to_validate = [ - "Beginning Setup", - "Running Code As Process", - "Test job started!!!!!", - "Test job completed!!!!!", - "Code Run Finished", - ] - - for data in logs_to_validate: - assert data in log_data - - os.chdir(current_dir) + ), + ]: + with open(file_name, "r") as f: + assert json.loads(f.read()) == expected_data + + # Capture logs + assert Path(f"{job_name}/log.txt").exists() + job.logs() + log_data, errors = capsys.readouterr() + + logs_to_validate = [ + "Beginning Setup", + "Running Code As Process", + "Test job started!!!!!", + "Test job completed!!!!!", + "Code Run Finished", + ] + + for data in logs_to_validate: + assert data in log_data + finally: + os.chdir(current_dir) def test_failed_local_job(aws_session, capsys): @@ -111,41 +112,42 @@ def test_failed_local_job(aws_session, capsys): current_dir = Path.cwd() with tempfile.TemporaryDirectory() as temp_dir: - os.chdir(temp_dir) - job = LocalQuantumJob.create( - "arn:aws:braket:::device/quantum-simulator/amazon/sv1", - source_module=absolute_source_module, - entry_point="job_test_script:start_here", - hyperparameters={"test_case": "failed"}, - aws_session=aws_session, - ) - - job_name = job.name - pattern = f"^local:job/{job_name}$" - re.match(pattern=pattern, string=job.arn) - - assert Path(job_name).is_dir() - - # Check no files are populated in checkpoints folder. - assert not any(Path(f"{job_name}/checkpoints").iterdir()) - - # Check results match the expectations. - error_message = f"Unable to find results in the local job directory {job_name}." - with pytest.raises(ValueError, match=error_message): - job.result() - - assert Path(f"{job_name}/log.txt").exists() - job.logs() - log_data, errors = capsys.readouterr() - - logs_to_validate = [ - "Beginning Setup", - "Running Code As Process", - "Test job started!!!!!", - "Code Run Finished", - ] - - for data in logs_to_validate: - assert data in log_data - - os.chdir(current_dir) + try: + os.chdir(temp_dir) + job = LocalQuantumJob.create( + "arn:aws:braket:::device/quantum-simulator/amazon/sv1", + source_module=absolute_source_module, + entry_point="job_test_script:start_here", + hyperparameters={"test_case": "failed"}, + aws_session=aws_session, + ) + + job_name = job.name + pattern = f"^local:job/{job_name}$" + re.match(pattern=pattern, string=job.arn) + + assert Path(job_name).is_dir() + + # Check no files are populated in checkpoints folder. + assert not any(Path(f"{job_name}/checkpoints").iterdir()) + + # Check results match the expectations. + error_message = f"Unable to find results in the local job directory {job_name}." + with pytest.raises(ValueError, match=error_message): + job.result() + + assert Path(f"{job_name}/log.txt").exists() + job.logs() + log_data, errors = capsys.readouterr() + + logs_to_validate = [ + "Beginning Setup", + "Running Code As Process", + "Test job started!!!!!", + "Code Run Finished", + ] + + for data in logs_to_validate: + assert data in log_data + finally: + os.chdir(current_dir) From e19cfb3fdb86bb6a0a3085c521ea6e3dfde59022 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 14 Apr 2022 18:22:18 +0000 Subject: [PATCH 0444/1165] prepare release v1.18.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 67947b82..c6eef8e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.18.1 (2022-04-14) + +### Bug Fixes and Other Changes + + * add exception handling to local job test + * Run github workflows on feature branches + ## v1.18.0.post0 (2022-04-06) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 3343e046..d4c81f10 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.18.1.dev0" +__version__ = "1.18.1" From 0ff801af38dec76029c6853ef348430b3cfb702d Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 14 Apr 2022 18:22:18 +0000 Subject: [PATCH 0445/1165] update development version to v1.18.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 d4c81f10..2c722f94 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.18.1" +__version__ = "1.18.2.dev0" From 3028ad76efcc00be1c8e26b8116f0f3247d2ef16 Mon Sep 17 00:00:00 2001 From: Mitch Massey <90290638+mtbmitchm@users.noreply.github.com> Date: Fri, 15 Apr 2022 14:28:20 -0600 Subject: [PATCH 0446/1165] fix: stringify hyperparameters automatically (#333) * fix: stringify hyperparameters automatically * fix: stringify hyperparameters in prepare_quantum_job * fix: removed extra whitespace --- src/braket/jobs/quantum_job_creation.py | 1 + test/unit_tests/braket/jobs/test_quantum_job_creation.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py index db159ecc..093bfce7 100644 --- a/src/braket/jobs/quantum_job_creation.py +++ b/src/braket/jobs/quantum_job_creation.py @@ -142,6 +142,7 @@ def prepare_quantum_job( job_name = job_name or _generate_default_job_name(image_uri) role_arn = role_arn or 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() 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 824cc33c..48fe575c 100644 --- a/test/unit_tests/braket/jobs/test_quantum_job_creation.py +++ b/test/unit_tests/braket/jobs/test_quantum_job_creation.py @@ -305,6 +305,7 @@ def _translate_creation_args(create_job_args): role_arn = create_job_args["role_arn"] or aws_session.get_default_jobs_role() device = create_job_args["device"] hyperparameters = create_job_args["hyperparameters"] or {} + hyperparameters = {str(key): str(value) for key, value in hyperparameters.items()} input_data = create_job_args["input_data"] or {} instance_config = create_job_args["instance_config"] or InstanceConfig() output_data_config = create_job_args["output_data_config"] or OutputDataConfig( From e81ef88e6cce263b5e48c36a5eb0d96ff4cc8687 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 18 Apr 2022 18:22:10 +0000 Subject: [PATCH 0447/1165] prepare release v1.18.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 c6eef8e5..4f45ada7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.18.2 (2022-04-18) + +### Bug Fixes and Other Changes + + * stringify hyperparameters automatically + ## v1.18.1 (2022-04-14) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 2c722f94..addfe98d 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.18.2.dev0" +__version__ = "1.18.2" From b2bb0863957a1ebc45ef65a7e69d2846e8c3f986 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 18 Apr 2022 18:22:10 +0000 Subject: [PATCH 0448/1165] update development version to v1.18.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 addfe98d..742c6a9e 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.18.2" +__version__ = "1.18.3.dev0" From f0529d079be00b4772966fe3af12ee0eeff7fbe7 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Mon, 18 Apr 2022 13:38:08 -0700 Subject: [PATCH 0449/1165] fix: add device arn error handling for badly formed ARNs (#324) --- src/braket/aws/aws_device.py | 14 ++++++++++++-- src/braket/aws/aws_quantum_job.py | 4 ++-- test/unit_tests/braket/aws/test_aws_quantum_job.py | 11 +++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index dfe71752..a032c540 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -231,7 +231,7 @@ def refresh_metadata(self) -> None: self._populate_properties(self._aws_session) def _get_session_and_initialize(self, session): - device_region = self._arn.split(":")[3] + device_region = AwsDevice.get_device_region(self._arn) return ( self._get_regional_device_session(session) if device_region @@ -239,7 +239,7 @@ def _get_session_and_initialize(self, session): ) def _get_regional_device_session(self, session): - device_region = self._arn.split(":")[3] + device_region = AwsDevice.get_device_region(self._arn) region_session = ( session if session.region == device_region @@ -509,3 +509,13 @@ def get_devices( devices = list(device_map.values()) devices.sort(key=lambda x: getattr(x, order_by)) return devices + + @staticmethod + def get_device_region(device_arn: str) -> str: + try: + return device_arn.split(":")[3] + except IndexError: + 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'" + ) diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index 8a3f7ccb..f5d1e48a 100644 --- a/src/braket/aws/aws_quantum_job.py +++ b/src/braket/aws/aws_quantum_job.py @@ -531,7 +531,7 @@ def __hash__(self) -> int: @staticmethod def _initialize_session(session_value, device, logger): aws_session = session_value or AwsSession() - device_region = device.split(":")[3] + device_region = AwsDevice.get_device_region(device) return ( AwsQuantumJob._initialize_regional_device_session(aws_session, device, logger) if device_region @@ -540,7 +540,7 @@ def _initialize_session(session_value, device, logger): @staticmethod def _initialize_regional_device_session(aws_session, device, logger): - device_region = device.split(":")[3] + device_region = AwsDevice.get_device_region(device) current_region = aws_session.region if current_region != device_region: aws_session = aws_session.copy_session(region=device_region) 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 22bd6405..b7111410 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_job.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py @@ -985,3 +985,14 @@ def test_exceptions_in_all_device_regions(aws_session): ) with pytest.raises(ClientError, match=error_message): AwsQuantumJob._initialize_session(aws_session, device_arn, logger) + + +def test_bad_arn_format(aws_session): + logger = logging.getLogger(__name__) + device_not_found = ( + "Device ARN is not a valid format: bad-arn-format. For valid Braket ARNs, " + "see 'https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html'" + ) + + with pytest.raises(ValueError, match=device_not_found): + AwsQuantumJob._initialize_session(aws_session, "bad-arn-format", logger) From 14d97dcaa20f4a6b2f2813727879e67ab9083606 Mon Sep 17 00:00:00 2001 From: rchilaka Date: Mon, 18 Apr 2022 13:53:08 -0700 Subject: [PATCH 0450/1165] fix: align ECR gate definition with OQC (#327) --- src/braket/circuits/gates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 994831c5..2593dde5 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -1370,7 +1370,7 @@ def to_matrix(self) -> np.ndarray: 1 / np.sqrt(2) * np.array( - [[0, 1, 0, 1.0j], [1, 0, -1.0j, 0], [0, 1.0j, 0, 1], [-1.0j, 0, 1, 0]], + [[0, 0, 1, 1.0j], [0, 0, 1.0j, 1], [1, -1.0j, 0, 0], [-1.0j, 1, 0, 0]], dtype=complex, ) ) From f25a8344ffcf3d0b69841f29b263fc6e37cace4a Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Tue, 19 Apr 2022 10:46:33 -0700 Subject: [PATCH 0451/1165] deprecation: use to_unitary rather than as_unitary. (#325) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Make as_unitary consistent with Braket Brings circuit unitary calculation in line with Braket convention: 1. Qubit order should big-endian, so the underlying state vector is ordered q0, q1, q2; consistency is ensured by using the multiply_matrix function in the default simulator 2. Unused qubits should be ignored; a circuit on qubits 3 and 5 should have a 4x4 unitary Co-authored-by: ℂ𝓞𝔇𝚈 --- src/braket/circuits/circuit.py | 53 ++- src/braket/circuits/unitary_calculation.py | 44 +++ .../braket/circuits/test_circuit.py | 373 ++++++++++++++++++ 3 files changed, 467 insertions(+), 3 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index e169a11c..f421af2d 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -13,6 +13,7 @@ from __future__ import annotations +import warnings from numbers import Number from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar, Union @@ -40,7 +41,7 @@ from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput from braket.circuits.result_type import ObservableResultType, ResultType -from braket.circuits.unitary_calculation import calculate_unitary +from braket.circuits.unitary_calculation import calculate_unitary, calculate_unitary_big_endian from braket.ir.jaqcd import Program SubroutineReturn = TypeVar( @@ -1063,8 +1064,8 @@ def to_ir(self) -> Program: ) def as_unitary(self) -> np.ndarray: - """ - Returns the unitary matrix representation of the entire circuit. + r""" + Returns the unitary matrix representation, in little endian format, of the entire circuit. *Note*: The performance of this method degrades with qubit count. It might be slow for qubit count > 10. @@ -1073,6 +1074,12 @@ def as_unitary(self) -> np.ndarray: circuit as a unitary. *Note*: For an empty circuit, an empty numpy array is returned (`array([], dtype=complex128)`) + Warnings: + This method has been deprecated, please use to_unitary() instead. + The unitary returned by this method is *little-endian*; the first qubit in the circuit + is the _least_ significant. For example, a circuit `Circuit().h(0).x(1)` will yield the + unitary :math:`X(1) \otimes H(0)`. + Raises: TypeError: If circuit is not composed only of `Gate` instances, i.e. a circuit with `Noise` operators will raise this error. @@ -1089,6 +1096,12 @@ def as_unitary(self) -> np.ndarray: [ 0.70710678+0.j, -0.70710678+0.j, 0. +0.j, 0. +0.j]]) """ + warnings.warn( + "Matrix returned will have qubits in little-endian order; " + "This method has been deprecated. Please use to_unitary() instead.", + category=DeprecationWarning, + ) + qubits = self.qubits if not qubits: return np.zeros(0, dtype=complex) @@ -1096,6 +1109,40 @@ def as_unitary(self) -> np.ndarray: return calculate_unitary(qubit_count, self.instructions) + def to_unitary(self) -> np.ndarray: + """ + Returns the unitary matrix representation of the entire circuit. + + Note: + The performance of this method degrades with qubit count. It might be slow for + `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)`) + + Raises: + TypeError: If circuit is not composed only of `Gate` instances, + i.e. a circuit with `Noise` operators will raise this error. + + Examples: + >>> circ = Circuit().h(0).cnot(0, 1) + >>> circ.to_unitary() + array([[ 0.70710678+0.j, 0. +0.j, 0.70710678+0.j, + 0. +0.j], + [ 0. +0.j, 0.70710678+0.j, 0. +0.j, + 0.70710678+0.j], + [ 0. +0.j, 0.70710678+0.j, 0. +0.j, + -0.70710678+0.j], + [ 0.70710678+0.j, 0. +0.j, -0.70710678+0.j, + 0. +0.j]]) + """ + qubits = self.qubits + if not qubits: + return np.zeros(0, dtype=complex) + return calculate_unitary_big_endian(self.instructions, qubits) + @property def qubits_frozen(self) -> bool: """bool: Whether the circuit's qubits are frozen, that is, cannot be remapped. diff --git a/src/braket/circuits/unitary_calculation.py b/src/braket/circuits/unitary_calculation.py index 18ddba6b..d6ae047b 100644 --- a/src/braket/circuits/unitary_calculation.py +++ b/src/braket/circuits/unitary_calculation.py @@ -18,6 +18,7 @@ from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction from braket.circuits.qubit_set import QubitSet +from braket.default_simulator.linalg_utils import multiply_matrix def _einsum_subscripts(targets: QubitSet, qubit_count: int) -> str: @@ -84,3 +85,46 @@ def calculate_unitary(qubit_count: int, instructions: Iterable[Instruction]) -> ) return np.reshape(un_tensor, 2 * [2**qubit_count]) + + +def calculate_unitary_big_endian( + instructions: Iterable[Instruction], qubits: QubitSet +) -> np.ndarray: + """ + Returns the unitary matrix representation for all the `instructions` on qubits `qubits`. + + Note: + The performance of this method degrades with qubit count. It might be slow for + `len(qubits)` > 10. + + Args: + instructions (Iterable[Instruction]): The instructions for which the unitary matrix + will be calculated. + qubits (QubitSet, optional): The actual qubits used by the instructions. + + Returns: + np.ndarray: A numpy array with shape (2^qubit_count, 2^qubit_count) representing the + `instructions` as a unitary matrix. + + Raises: + TypeError: If `instructions` is not composed only of `Gate` instances, + i.e. a circuit with `Noise` operators will raise this error. + """ + qubits_sorted = sorted(qubits) + qubit_count = len(qubits_sorted) + # Map qubits to contiguous indices starting from 0 + index_substitutions = {qubits_sorted[i]: i for i in range(qubit_count)} + rank = 2**qubit_count + # Initialize identity unitary as type (rank, rank) tensor + unitary = np.eye(rank).reshape([2] * 2 * qubit_count) + + for instruction in instructions: + if not isinstance(instruction.operator, Gate): + raise TypeError("Only Gate operators are supported to build the unitary") + unitary = multiply_matrix( + unitary, + instruction.operator.to_matrix(), + tuple(index_substitutions[qubit] for qubit in instruction.target), + ) + + return unitary.reshape(rank, rank) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index dd19e3a3..7538d2a1 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -1012,6 +1012,379 @@ def test_as_unitary_one_gate_returns_expected_unitary(circuit, expected_unitary) assert np.allclose(circuit.as_unitary(), expected_unitary) +def test_to_unitary_empty_instructions_returns_empty_array(): + circ = Circuit() + circ.to_unitary() == [] + + +@pytest.mark.parametrize( + "circuit", + [ + (Circuit().phaseshift(0, 0.15).apply_gate_noise(noise.Noise.BitFlip(probability=0.1))), + (Circuit().cnot(1, 0).apply_gate_noise(noise.Noise.TwoQubitDepolarizing(probability=0.1))), + ( + Circuit() + .x(1) + .i(2) + .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[1]) + ), + ( + Circuit() + .x(1) + .i(2) + .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[2]) + ), + (Circuit().x(1).i(2).apply_gate_noise(noise.Noise.BitFlip(probability=0.1))), + (Circuit().x(1).apply_gate_noise(noise.Noise.BitFlip(probability=0.1)).i(2)), + ( + Circuit() + .y(1) + .z(2) + .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[1]) + ), + ( + Circuit() + .y(1) + .z(2) + .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[2]) + ), + (Circuit().y(1).z(2).apply_gate_noise(noise.Noise.BitFlip(probability=0.1))), + (Circuit().y(1).apply_gate_noise(noise.Noise.BitFlip(probability=0.1)).z(2)), + ( + Circuit() + .cphaseshift(2, 1, 0.15) + .si(3) + .apply_gate_noise( + noise.Noise.TwoQubitDepolarizing(probability=0.1), target_qubits=[1, 2] + ) + ), + ( + Circuit() + .cphaseshift(2, 1, 0.15) + .apply_gate_noise(noise.Noise.TwoQubitDepolarizing(probability=0.1)) + .si(3) + ), + ], +) +@pytest.mark.xfail(raises=TypeError) +def test_to_unitary_noise_raises_error(circuit): + circuit.to_unitary() + + +@pytest.mark.xfail(raises=TypeError) +def test_to_unitary_parameterized(): + theta = FreeParameter("theta") + circ = Circuit().rx(angle=theta, target=0) + assert np.allclose(circ.to_unitary()) + + +def test_to_unitary_noise_not_apply_returns_expected_unitary(recwarn): + circuit = ( + Circuit() + .cphaseshift(1, 2, 0.15) + .si(3) + .apply_gate_noise(noise.Noise.TwoQubitDepolarizing(probability=0.1), target_qubits=[1, 3]) + ) + + assert len(recwarn) == 1 + assert str(recwarn[0].message).startswith("Noise is not applied to any gate") + + assert np.allclose( + circuit.to_unitary(), + np.kron(gates.CPhaseShift(0.15).to_matrix(), gates.Si().to_matrix()), + ) + + +@pytest.mark.parametrize( + "circuit,expected_unitary", + [ + (Circuit().h(0), gates.H().to_matrix()), + (Circuit().h(0).add_result_type(ResultType.Probability(target=[0])), gates.H().to_matrix()), + (Circuit().h(1), gates.H().to_matrix()), + (Circuit().h(2), gates.H().to_matrix()), + (Circuit().x(0), gates.X().to_matrix()), + (Circuit().y(0), gates.Y().to_matrix()), + (Circuit().z(0), gates.Z().to_matrix()), + (Circuit().s(0), gates.S().to_matrix()), + (Circuit().si(0), gates.Si().to_matrix()), + (Circuit().t(0), gates.T().to_matrix()), + (Circuit().ti(0), gates.Ti().to_matrix()), + (Circuit().v(0), gates.V().to_matrix()), + (Circuit().vi(0), gates.Vi().to_matrix()), + (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().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()), + (Circuit().cnot(2, 4), gates.CNot().to_matrix()), + (Circuit().swap(0, 1), gates.Swap().to_matrix()), + (Circuit().swap(1, 0), gates.Swap().to_matrix()), + (Circuit().iswap(0, 1), gates.ISwap().to_matrix()), + (Circuit().iswap(1, 0), gates.ISwap().to_matrix()), + (Circuit().pswap(0, 1, 0.15), gates.PSwap(0.15).to_matrix()), + (Circuit().pswap(1, 0, 0.15), gates.PSwap(0.15).to_matrix()), + (Circuit().xy(0, 1, 0.15), gates.XY(0.15).to_matrix()), + (Circuit().xy(1, 0, 0.15), gates.XY(0.15).to_matrix()), + (Circuit().cphaseshift(0, 1, 0.15), gates.CPhaseShift(0.15).to_matrix()), + (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().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()), + (Circuit().yy(0, 1, 0.15), gates.YY(0.15).to_matrix()), + (Circuit().zz(0, 1, 0.15), gates.ZZ(0.15).to_matrix()), + (Circuit().ccnot(0, 1, 2), gates.CCNot().to_matrix()), + ( + Circuit() + .ccnot(0, 1, 2) + .add_result_type(ResultType.Expectation(observable=Observable.Y(), target=[1])), + gates.CCNot().to_matrix(), + ), + (Circuit().ccnot(0, 1, 2), gates.CCNot().to_matrix()), + (Circuit().cswap(0, 1, 2), gates.CSwap().to_matrix()), + (Circuit().cswap(0, 2, 1), gates.CSwap().to_matrix()), + (Circuit().h(1), gates.H().to_matrix()), + (Circuit().x(1).i(2), np.kron(gates.X().to_matrix(), np.eye(2))), + (Circuit().y(1).z(2), np.kron(gates.Y().to_matrix(), gates.Z().to_matrix())), + (Circuit().rx(1, 0.15), gates.Rx(0.15).to_matrix()), + (Circuit().ry(1, 0.15).i(2), np.kron(gates.Ry(0.15).to_matrix(), np.eye(2))), + (Circuit().rz(1, 0.15).s(2), np.kron(gates.Rz(0.15).to_matrix(), gates.S().to_matrix())), + (Circuit().pswap(1, 2, 0.15), gates.PSwap(0.15).to_matrix()), + (Circuit().pswap(2, 1, 0.15), gates.PSwap(0.15).to_matrix()), + (Circuit().xy(1, 2, 0.15).i(3), np.kron(gates.XY(0.15).to_matrix(), np.eye(2))), + (Circuit().xy(2, 1, 0.15).i(3), np.kron(gates.XY(0.15).to_matrix(), np.eye(2))), + ( + Circuit().cphaseshift(1, 2, 0.15).si(3), + np.kron(gates.CPhaseShift(0.15).to_matrix(), gates.Si().to_matrix()), + ), + (Circuit().ccnot(1, 2, 3), gates.CCNot().to_matrix()), + (Circuit().ccnot(2, 1, 3), gates.CCNot().to_matrix()), + (Circuit().cswap(1, 2, 3).i(4), np.kron(gates.CSwap().to_matrix(), np.eye(2))), + (Circuit().cswap(1, 3, 2).i(4), np.kron(gates.CSwap().to_matrix(), np.eye(2))), + (Circuit().cswap(1, 2, 3).t(4), np.kron(gates.CSwap().to_matrix(), gates.T().to_matrix())), + (Circuit().cswap(1, 3, 2).t(4), np.kron(gates.CSwap().to_matrix(), gates.T().to_matrix())), + (Circuit().h(0).h(0), gates.I().to_matrix()), + (Circuit().h(0).x(0), np.dot(gates.X().to_matrix(), gates.H().to_matrix())), + (Circuit().x(0).h(0), np.dot(gates.H().to_matrix(), gates.X().to_matrix())), + ( + Circuit().y(0).z(1).cnot(0, 1), + np.dot(gates.CNot().to_matrix(), np.kron(gates.Y().to_matrix(), gates.Z().to_matrix())), + ), + ( + Circuit().z(0).y(1).cnot(0, 1), + np.dot(gates.CNot().to_matrix(), np.kron(gates.Z().to_matrix(), gates.Y().to_matrix())), + ), + ( + Circuit().y(0).z(1).cnot(0, 1).cnot(1, 2), + np.dot( + np.dot( + np.kron(np.eye(2), gates.CNot().to_matrix()), + np.kron(gates.CNot().to_matrix(), np.eye(2)), + ), + np.kron(np.kron(gates.Y().to_matrix(), gates.Z().to_matrix()), np.eye(2)), + ), + ), + ( + Circuit().z(0).y(1).cnot(0, 1).ccnot(0, 1, 2), + np.dot( + np.dot( + gates.CCNot().to_matrix(), + np.kron(gates.CNot().to_matrix(), np.eye(2)), + ), + np.kron(np.kron(gates.Z().to_matrix(), gates.Y().to_matrix()), np.eye(2)), + ), + ), + ( + Circuit().cnot(1, 0), + np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + ], + dtype=complex, + ), + ), + ( + Circuit().ccnot(1, 2, 0), + np.array( + [ + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], + ], + dtype=complex, + ), + ), + ( + Circuit().ccnot(2, 1, 0), + np.array( + [ + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], + ], + dtype=complex, + ), + ), + ( + Circuit().ccnot(0, 2, 1), + np.array( + [ + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], + ], + dtype=complex, + ), + ), + ( + Circuit().ccnot(2, 0, 1), + np.array( + [ + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], + ], + dtype=complex, + ), + ), + ( + Circuit().s(0).v(1).cnot(0, 1).cnot(2, 1), + np.dot( + np.dot( + np.dot( + np.kron( + np.eye(2), + np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + ], + dtype=complex, + ), + ), + np.kron( + np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0], + ], + dtype=complex, + ), + np.eye(2), + ), + ), + np.kron(np.kron(np.eye(2), gates.V().to_matrix()), np.eye(2)), + ), + np.kron(gates.S().to_matrix(), np.eye(4)), + ), + ), + ( + Circuit().z(0).y(1).cnot(1, 0).ccnot(2, 1, 0), + np.dot( + np.dot( + np.dot( + np.array( + [ + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], + ], + dtype=complex, + ), + np.kron( + np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + ], + dtype=complex, + ), + np.eye(2), + ), + ), + np.kron(np.kron(np.eye(2), gates.Y().to_matrix()), np.eye(2)), + ), + np.kron(gates.Z().to_matrix(), np.eye(4)), + ), + ), + ( + Circuit().z(0).y(1).cnot(1, 0).ccnot(2, 0, 1), + np.dot( + np.dot( + np.dot( + np.array( + [ + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], + ], + dtype=complex, + ), + np.kron( + np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + ], + dtype=complex, + ), + np.eye(2), + ), + ), + np.kron(np.kron(np.eye(2), gates.Y().to_matrix()), np.eye(2)), + ), + np.kron(gates.Z().to_matrix(), np.eye(4)), + ), + ), + ], +) +def test_to_matrix_one_gate_returns_expected_unitary(circuit, expected_unitary): + assert np.allclose(circuit.to_unitary(), expected_unitary) + + def test_circuit_with_symbol(): theta = FreeParameter("theta") From 06e1cb74a21372e10d88ca8e48a614177768138b Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 19 Apr 2022 18:23:15 +0000 Subject: [PATCH 0452/1165] prepare release v1.19.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 4f45ada7..4d04f1d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.19.0 (2022-04-19) + +### Deprecations and Removals + + * use to_unitary rather than as_unitary. + +### Bug Fixes and Other Changes + + * align ECR gate definition with OQC + * add device arn error handling for badly formed ARNs + ## v1.18.2 (2022-04-18) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 742c6a9e..b6fa58d2 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.18.3.dev0" +__version__ = "1.19.0" From be3b6399280266d2bd8d62d53365a207b278ed8c Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 19 Apr 2022 18:23:15 +0000 Subject: [PATCH 0453/1165] update development version to v1.19.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 b6fa58d2..f3e65864 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.19.0" +__version__ = "1.19.1.dev0" From 65d299c493b795a5e53f7ddedfd16c2184895545 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 4 May 2022 13:56:41 -0700 Subject: [PATCH 0454/1165] feature: support local simulators for jobs (#309) Co-authored-by: Stephen Face <60493521+shpface@users.noreply.github.com> Co-authored-by: Stephen Face Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> Co-authored-by: Abe Coull --- src/braket/aws/aws_quantum_job.py | 19 ++++++-- src/braket/jobs/config.py | 1 + .../jobs/image_uri_config/pl_pytorch.json | 2 +- src/braket/jobs/image_uris.py | 8 +++- src/braket/jobs/local/local_job.py | 7 ++- .../cwl_insights_metrics_fetcher.py | 2 +- .../jobs/metrics_data/log_metrics_parser.py | 43 ++++++++++++------- src/braket/jobs/quantum_job_creation.py | 11 +++++ .../braket/aws/test_aws_quantum_job.py | 29 ++++++++++--- .../test_cwl_insights_metrics_fetcher.py | 2 +- .../metrics_data/test_log_metrics_parser.py | 40 +++++++++++++++++ .../unit_tests/braket/jobs/test_image_uris.py | 4 +- .../braket/jobs/test_quantum_job_creation.py | 20 +++++++++ 13 files changed, 154 insertions(+), 34 deletions(-) diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index f5d1e48a..8d0b7870 100644 --- a/src/braket/aws/aws_quantum_job.py +++ b/src/braket/aws/aws_quantum_job.py @@ -73,6 +73,7 @@ def create( hyperparameters: Dict[str, Any] = None, input_data: Union[str, Dict, S3DataSourceConfig] = None, instance_config: InstanceConfig = None, + distribution: str = None, stopping_condition: StoppingCondition = None, output_data_config: OutputDataConfig = None, copy_checkpoints_from_job: str = None, @@ -84,8 +85,11 @@ def create( """Creates a job by invoking the Braket CreateJob API. Args: - device (str): ARN for the AWS device which is primarily - accessed for the execution of this job. + device (str): ARN for the AWS device which is primarily accessed for the execution + of this job. Alternatively, a string of the format "local:/" + for using a local simulator for the job. This string will be available as the + environment variable `AMZN_BRAKET_DEVICE_ARN` inside the job container when + using a Braket container. source_module (str): Path (absolute, relative or an S3 URI) to a python module to be tarred and uploaded. If `source_module` is an S3 URI, it must point to a @@ -129,7 +133,11 @@ def create( instance_config (InstanceConfig): Configuration of the instances to be used to execute the job. Default: InstanceConfig(instanceType='ml.m5.large', - instanceCount=1, volumeSizeInGB=30, volumeKmsKey=None). + instanceCount=1, volumeSizeInGB=30). + + distribution (str): A str that specifies how the job should be distributed. If set to + "data_parallel", the hyperparameters for the job will be set to use data parallelism + features for PyTorch or TensorFlow. Default: None. stopping_condition (StoppingCondition): The maximum length of time, in seconds, and the maximum number of tasks that a job can run before being forcefully stopped. @@ -178,6 +186,7 @@ def create( hyperparameters=hyperparameters, input_data=input_data, instance_config=instance_config, + distribution=distribution, stopping_condition=stopping_condition, output_data_config=output_data_config, copy_checkpoints_from_job=copy_checkpoints_from_job, @@ -311,7 +320,7 @@ def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: 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 = 1 # currently only support a single instance + instance_count = self.metadata(use_cached_value=True)["instanceConfig"]["instanceCount"] has_streams = False color_wrap = logs.ColorWrap() @@ -531,6 +540,8 @@ def __hash__(self) -> int: @staticmethod def _initialize_session(session_value, device, logger): aws_session = session_value or AwsSession() + if device.startswith("local:"): + return aws_session device_region = AwsDevice.get_device_region(device) return ( AwsQuantumJob._initialize_regional_device_session(aws_session, device, logger) diff --git a/src/braket/jobs/config.py b/src/braket/jobs/config.py index bbe1f2cb..28b7c05d 100644 --- a/src/braket/jobs/config.py +++ b/src/braket/jobs/config.py @@ -29,6 +29,7 @@ class InstanceConfig: instanceType: str = "ml.m5.large" volumeSizeInGb: int = 30 + instanceCount: int = 1 @dataclass diff --git a/src/braket/jobs/image_uri_config/pl_pytorch.json b/src/braket/jobs/image_uri_config/pl_pytorch.json index 384848cb..50197b1c 100644 --- a/src/braket/jobs/image_uri_config/pl_pytorch.json +++ b/src/braket/jobs/image_uri_config/pl_pytorch.json @@ -1,6 +1,6 @@ { "versions": { - "1.8.1": { + "1.9.1": { "registries": { "us-east-1": "292282985366", "us-west-1": "292282985366", diff --git a/src/braket/jobs/image_uris.py b/src/braket/jobs/image_uris.py index 0a9f27bb..4996a7dd 100644 --- a/src/braket/jobs/image_uris.py +++ b/src/braket/jobs/image_uris.py @@ -45,7 +45,13 @@ def retrieve_image(framework: Framework, region: str): framework_version = max(version for version in config["versions"]) version_config = config["versions"][framework_version] registry = _registry_for_region(version_config, region) - tag = f"{version_config['repository']}:{framework_version}-cpu-py37-ubuntu18.04" + tag = "" + if framework == Framework.PL_TENSORFLOW: + tag = f"{version_config['repository']}:{framework_version}-gpu-py37-cu110-ubuntu18.04" + elif framework == Framework.PL_PYTORCH: + tag = f"{version_config['repository']}:{framework_version}-gpu-py38-cu111-ubuntu20.04" + else: + tag = f"{version_config['repository']}:{framework_version}-cpu-py37-ubuntu18.04" return f"{registry}.dkr.ecr.{region}.amazonaws.com/{tag}" diff --git a/src/braket/jobs/local/local_job.py b/src/braket/jobs/local/local_job.py index 8b739426..b240658c 100644 --- a/src/braket/jobs/local/local_job.py +++ b/src/braket/jobs/local/local_job.py @@ -53,8 +53,11 @@ def create( docker container. Args: - device (str): ARN for the AWS device which is primarily - accessed for the execution of this job. + device (str): ARN for the AWS device which is primarily accessed for the execution + of this job. Alternatively, a string of the format "local:/" + for using a local simulator for the job. This string will be available as the + environment variable `AMZN_BRAKET_DEVICE_ARN` inside the job container when + using a Braket container. source_module (str): Path (absolute, relative or an S3 URI) to a python module to be tarred and uploaded. If `source_module` is an S3 URI, it must point to a 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 251d17de..c40d7057 100644 --- a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py @@ -167,7 +167,7 @@ def get_metrics_for_job( query = ( f"fields @timestamp, @message " f"| filter @logStream like /^{job_name}\\// " - f"| filter @message like /^Metrics - /" + f"| filter @message like /Metrics - /" ) response = self._logs_client.start_query( diff --git a/src/braket/jobs/metrics_data/log_metrics_parser.py b/src/braket/jobs/metrics_data/log_metrics_parser.py index 76ef319b..d0faacb5 100644 --- a/src/braket/jobs/metrics_data/log_metrics_parser.py +++ b/src/braket/jobs/metrics_data/log_metrics_parser.py @@ -27,6 +27,8 @@ class LogMetricsParser(object): METRICS_DEFINITIONS = re.compile(r"(\w+)\s*=\s*([^;]+)\s*;") TIMESTAMP = "timestamp" ITERATION_NUMBER = "iteration_number" + NODE_ID = "node_id" + NODE_TAG = re.compile(r"^\[([^\]]*)\]") def __init__( self, @@ -102,32 +104,37 @@ 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: + parsed_metrics[self.NODE_ID] = node_match.group(1) self.all_metrics.append(parsed_metrics) def get_columns_and_pivot_indices( self, pivot: str - ) -> Tuple[Dict[str, List[Union[str, float, int]]], Dict[int, int]]: + ) -> 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 assigned a row index, so that all metrics with the same pivot value - are stored in the same row. + 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[Any]], Dict[int, int]]: - The Dict[str, List[Any]] the result table with all the metrics values initialized + Tuple[Dict[str, List[Any]], Dict[Tuple[int, str], int]]: + The Dict[str, List[Any]] is the result table with all the metrics values initialized to None - The Dict[int, int] is the list of pivot indices, where the value of a pivot column - is mapped to a row index. + 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 pivot_indices: dict[int, int] = {} table: dict[str, list[Optional[Union[str, float, int]]]] = {} for metric in self.all_metrics: if pivot in metric: - if metric[pivot] not in pivot_indices: - pivot_indices[metric[pivot]] = row_count + # If no node_id is present, pair pivot value with None for the key. + metric_pivot = (metric[pivot], metric.get(self.NODE_ID)) + if metric_pivot not in pivot_indices: + pivot_indices[metric_pivot] = row_count row_count += 1 for column_name in metric: table[column_name] = [None] @@ -141,18 +148,21 @@ def get_metric_data_with_pivot( """ 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 - are returned in the same row. If the a metric has multiple values for the pivot value, - the statistic is used to determine which value is returned. + 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 + to determine which value is returned. For example, for the metrics: "iteration_number" : 0, "metricA" : 2, "metricB" : 1, "iteration_number" : 0, "metricA" : 1, "no_pivot_column" : 0, "metricA" : 0, "iteration_number" : 1, "metricA" : 2, + "iteration_number" : 1, "node_id" : "nodeB", "metricB" : 0, The result with iteration_number as the pivot, statistic of MIN the result will be: - iteration_number metricA metricB - 0 1 1 - 1 2 None + iteration_number node_id metricA metricB + 0 None 1 1 + 1 None 2 None + 1 nodeB None 0 Args: pivot (str): The name of the pivot column. Must be TIMESTAMP or ITERATION_NUMBER. @@ -164,7 +174,8 @@ def get_metric_data_with_pivot( table, pivot_indices = self.get_columns_and_pivot_indices(pivot) for metric in self.all_metrics: if pivot in metric: - row = pivot_indices[metric[pivot]] + metric_pivot = (metric[pivot], metric.get(self.NODE_ID)) + row = pivot_indices[metric_pivot] for column_name in metric: table[column_name][row] = self._get_value( table[column_name][row], metric[column_name], statistic diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py index 093bfce7..d5292c77 100644 --- a/src/braket/jobs/quantum_job_creation.py +++ b/src/braket/jobs/quantum_job_creation.py @@ -44,6 +44,7 @@ def prepare_quantum_job( hyperparameters: Dict[str, Any] = None, input_data: Union[str, Dict, S3DataSourceConfig] = None, instance_config: InstanceConfig = None, + distribution: str = None, stopping_condition: StoppingCondition = None, output_data_config: OutputDataConfig = None, copy_checkpoints_from_job: str = None, @@ -98,6 +99,10 @@ def prepare_quantum_job( to execute the job. Default: InstanceConfig(instanceType='ml.m5.large', instanceCount=1, volumeSizeInGB=30, volumeKmsKey=None). + distribution (str): A str that specifies how the job should be distributed. If set to + "data_parallel", the hyperparameters for the job will be set to use data parallelism + features for PyTorch or TensorFlow. Default: None. + stopping_condition (StoppingCondition): The maximum length of time, in seconds, and the maximum number of tasks that a job can run before being forcefully stopped. Default: StoppingCondition(maxRuntimeInSeconds=5 * 24 * 60 * 60). @@ -192,6 +197,12 @@ def prepare_quantum_job( "s3Uri" ] aws_session.copy_s3_directory(checkpoints_to_copy, checkpoint_config.s3Uri) + if distribution == "data_parallel": + distributed_hyperparams = { + "sagemaker_distributed_dataparallel_enabled": "true", + "sagemaker_instance_type": instance_config.instanceType, + } + hyperparameters.update(distributed_hyperparams) create_job_kwargs = { "jobName": job_name, 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 b7111410..bbb1ad79 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_job.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py @@ -15,6 +15,7 @@ import json import logging import os +import re import tarfile import tempfile from unittest.mock import Mock, patch @@ -184,10 +185,11 @@ def test_quantum_job_constructor_default_session( assert job._aws_session == aws_session_mock.return_value -@pytest.mark.xfail(raises=ValueError) def test_quantum_job_constructor_invalid_region(aws_session): + region_mismatch = "The aws session region does not match the region for the supplied arn." arn = "arn:aws:braket:unknown-region:875981177017:job/quantum_job_name" - AwsQuantumJob(arn, aws_session) + with pytest.raises(ValueError, match=region_mismatch): + AwsQuantumJob(arn, aws_session) @patch("braket.aws.aws_quantum_job.boto3.Session") @@ -504,6 +506,7 @@ def prepare_job_args(aws_session, device_arn): "hyperparameters": Mock(), "input_data": Mock(), "instance_config": Mock(), + "distribution": Mock(), "stopping_condition": Mock(), "output_data_config": Mock(), "copy_checkpoints_from_job": Mock(), @@ -528,9 +531,9 @@ def test_name(quantum_job_arn, quantum_job_name, aws_session): assert quantum_job.name == quantum_job_name -@pytest.mark.xfail(raises=AttributeError) def test_no_arn_setter(quantum_job): - quantum_job.arn = 123 + with pytest.raises(AttributeError, match="can't set attribute"): + quantum_job.arn = 123 @pytest.mark.parametrize("wait_until_complete", [True, False]) @@ -577,7 +580,6 @@ def test_cancel_job(quantum_job_arn, aws_session, generate_cancel_job_response): assert status == cancellation_status -@pytest.mark.xfail(raises=ClientError) def test_cancel_job_surfaces_exception(quantum_job, aws_session): exception_response = { "Error": { @@ -585,8 +587,13 @@ def test_cancel_job_surfaces_exception(quantum_job, aws_session): "Message": "unit-test-error", } } + error_string = re.escape( + "An error occurred (ValidationException) when calling the " + "cancel_job operation: unit-test-error" + ) aws_session.cancel_job.side_effect = ClientError(exception_response, "cancel_job") - quantum_job.cancel() + with pytest.raises(ClientError, match=error_string): + quantum_job.cancel() @pytest.mark.parametrize( @@ -987,6 +994,16 @@ def test_exceptions_in_all_device_regions(aws_session): AwsQuantumJob._initialize_session(aws_session, device_arn, logger) +@patch("braket.aws.aws_quantum_job.AwsSession") +def test_initialize_session_local_device(mock_new_session, aws_session): + logger = logging.getLogger(__name__) + device = "local:provider.device.name" + # don't change a provided AwsSession + assert AwsQuantumJob._initialize_session(aws_session, device, logger) == aws_session + # otherwise, create an AwsSession with the profile defaults + assert AwsQuantumJob._initialize_session(None, device, logger) == mock_new_session() + + def test_bad_arn_format(aws_session): logger = logging.getLogger(__name__) device_not_found = ( 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 f0d42f52..17027cec 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 @@ -73,7 +73,7 @@ def test_get_all_metrics_complete_results(mock_add_metrics, mock_get_metrics, aw startTime=1, endTime=2, queryString="fields @timestamp, @message | filter @logStream like /^test_job\\//" - " | filter @message like /^Metrics - /", + " | filter @message like /Metrics - /", limit=10000, ) assert mock_add_metrics.call_args_list == EXPECTED_CALL_LIST diff --git a/test/unit_tests/braket/jobs/metrics_data/test_log_metrics_parser.py b/test/unit_tests/braket/jobs/metrics_data/test_log_metrics_parser.py index f3edadb0..4d9ca9dc 100644 --- a/test/unit_tests/braket/jobs/metrics_data/test_log_metrics_parser.py +++ b/test/unit_tests/braket/jobs/metrics_data/test_log_metrics_parser.py @@ -63,6 +63,40 @@ "metric3": [None, None, 0.2, None], } +MULTINODE_METRICS_LOG_LINES = [ + { + "timestamp": "Test timestamp 0", + "message": "[nodeA]:Metrics - metric0=1.0;", + }, + # This line logs the same metric from a different node. + { + "timestamp": "Test timestamp 0", + "message": "[nodeB]:Metrics - metric0=2.0;", + }, + # This line also logs a metric unique to one node. + { + "timestamp": "Test timestamp 1", + "message": "[nodeA]:Metrics - metricA=3.0;", + }, + # This line logs a metric without a node tag. + { + "timestamp": "Test timestamp 1", + "message": "Metrics - metric0=0.0;", + }, +] + +MULTINODES_METRICS_RESULT = { + "timestamp": ["Test timestamp 0", "Test timestamp 0", "Test timestamp 1", "Test timestamp 1"], + "node_id": [ + "nodeA", + "nodeB", + "nodeA", + None, + ], + "metric0": [1.0, 2.0, None, 0.0], + "metricA": [None, None, 3.0, None], +} + # This will test how metrics are combined when the multiple metrics have the same timestamp SINGLE_TIMESTAMP_METRICS_LOG_LINES = [ {"timestamp": "Test timestamp 0", "message": "Metrics - metric0=0.0;"}, @@ -135,6 +169,12 @@ MetricStatistic.MAX, SIMPLE_METRICS_RESULT, ), + ( + MULTINODE_METRICS_LOG_LINES, + MetricType.TIMESTAMP, + MetricStatistic.MAX, + MULTINODES_METRICS_RESULT, + ), ( SINGLE_TIMESTAMP_METRICS_LOG_LINES, MetricType.TIMESTAMP, diff --git a/test/unit_tests/braket/jobs/test_image_uris.py b/test/unit_tests/braket/jobs/test_image_uris.py index ab608c5a..9f44c7ef 100644 --- a/test/unit_tests/braket/jobs/test_image_uris.py +++ b/test/unit_tests/braket/jobs/test_image_uris.py @@ -29,13 +29,13 @@ "us-east-1", Framework.PL_TENSORFLOW, "292282985366.dkr.ecr.us-east-1.amazonaws.com/amazon-braket-tensorflow-jobs:" - "2.4.1-cpu-py37-ubuntu18.04", + "2.4.1-gpu-py37-cu110-ubuntu18.04", ), ( "us-west-2", Framework.PL_PYTORCH, "292282985366.dkr.ecr.us-west-2.amazonaws.com/" - "amazon-braket-pytorch-jobs:1.8.1-cpu-py37-ubuntu18.04", + "amazon-braket-pytorch-jobs:1.9.1-gpu-py38-cu111-ubuntu20.04", ), ], ) 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 48fe575c..ec2d556f 100644 --- a/test/unit_tests/braket/jobs/test_quantum_job_creation.py +++ b/test/unit_tests/braket/jobs/test_quantum_job_creation.py @@ -138,6 +138,18 @@ def instance_config(): ) +@pytest.fixture(params=[False, True]) +def data_parallel(request): + return request.param + + +@pytest.fixture +def distribution(data_parallel): + if data_parallel: + return "data_parallel" + return None + + @pytest.fixture def stopping_condition(): return StoppingCondition( @@ -227,6 +239,7 @@ def create_job_args( hyperparameters, input_data, instance_config, + distribution, stopping_condition, output_data_config, checkpoint_config, @@ -246,6 +259,7 @@ def create_job_args( "hyperparameters": hyperparameters, "input_data": input_data, "instance_config": instance_config, + "distribution": distribution, "stopping_condition": stopping_condition, "output_data_config": output_data_config, "checkpoint_config": checkpoint_config, @@ -308,6 +322,12 @@ def _translate_creation_args(create_job_args): hyperparameters = {str(key): str(value) for key, value in hyperparameters.items()} input_data = create_job_args["input_data"] or {} instance_config = create_job_args["instance_config"] or InstanceConfig() + if create_job_args["distribution"] == "data_parallel": + distributed_hyperparams = { + "sagemaker_distributed_dataparallel_enabled": "true", + "sagemaker_instance_type": instance_config.instanceType, + } + 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") ) From 5d8c182f704f901a4c290bb3f1fefc62a2c98c11 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 4 May 2022 21:22:13 +0000 Subject: [PATCH 0455/1165] prepare release v1.20.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 4d04f1d9..30e25959 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.20.0 (2022-05-04) + +### Features + + * support local simulators for jobs + ## v1.19.0 (2022-04-19) ### Deprecations and Removals diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index f3e65864..72f0d1d4 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.19.1.dev0" +__version__ = "1.20.0" From 614029a9f2a0b991d0eed1643438e63a4a83ef16 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 4 May 2022 21:22:13 +0000 Subject: [PATCH 0456/1165] update development version to v1.20.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 72f0d1d4..7ddec429 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.20.0" +__version__ = "1.20.1.dev0" From 46b647320632366babb7b1553f504e886b9cc418 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 9 May 2022 13:25:21 -0700 Subject: [PATCH 0457/1165] feature: Gate and Circuit inversion (#311) Introduces the ability to take adjoints of Gates, gate Instructions and noiseless Circuits. --- src/braket/circuits/angled_gate.py | 11 ++ src/braket/circuits/circuit.py | 32 ++++-- src/braket/circuits/compiler_directive.py | 15 +++ src/braket/circuits/compiler_directives.py | 6 + src/braket/circuits/gate.py | 27 ++++- src/braket/circuits/gates.py | 67 ++++++++++- src/braket/circuits/instruction.py | 24 +++- .../braket/circuits/test_angled_gate.py | 12 ++ .../circuits/test_ascii_circuit_diagram.py | 105 ++++++------------ .../braket/circuits/test_circuit.py | 42 +++++-- .../circuits/test_compiler_directive.py | 5 + .../circuits/test_compiler_directives.py | 17 ++- test/unit_tests/braket/circuits/test_gate.py | 9 +- test/unit_tests/braket/circuits/test_gates.py | 11 ++ .../braket/circuits/test_instruction.py | 18 ++- 15 files changed, 293 insertions(+), 108 deletions(-) diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index f28cf4f3..754ca803 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.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. +import copy import math from typing import List, Optional, Sequence, Union @@ -87,6 +88,16 @@ def bind_values(self, **kwargs): """ raise NotImplementedError + def adjoint(self) -> List[Gate]: + """Returns the adjoint of this gate as a singleton list. + + Returns: + List[Gate]: A list containing the gate with negated angle. + """ + new = copy.copy(self) + new._parameters = [-angle for angle in self._parameters] + return [new] + def __eq__(self, other): if isinstance(other, AngledGate): if isinstance(self.angle, FreeParameterExpression): diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index f421af2d..0a12d8a9 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -144,10 +144,9 @@ def depth(self) -> int: return self._moments.depth @property - def instructions(self) -> Iterable[Instruction]: + def instructions(self) -> List[Instruction]: """Iterable[Instruction]: Get an `iterable` of instructions in the circuit.""" - - return self._moments.values() + return list(self._moments.values()) @property def result_types(self) -> List[ResultType]: @@ -1031,6 +1030,22 @@ def _flatten(addable): return self + def adjoint(self) -> Circuit: + """Returns the adjoint of this circuit. + + This is the adjoint of every instruction of the circuit, in reverse order. Result types, + and consequently basis rotations will stay in the same order at the end of the circuit. + + Returns: + Circuit: The adjoint of the circuit. + """ + circ = Circuit() + for instr in reversed(self.instructions): + circ.add(instr.adjoint()) + for result_type in self._result_types: + circ.add_result_type(result_type) + return circ + def diagram(self, circuit_diagram_class=AsciiCircuitDiagram) -> str: """ Get a diagram for the current circuit. @@ -1050,9 +1065,9 @@ def to_ir(self) -> Program: If the circuit is sent over the wire, this method is called before it is sent. Returns: - (Program): An AWS quantum circuit description program in JSON format. + Program: A Braket quantum circuit description program in JSON format. """ - ir_instructions = [instr.to_ir() for instr in self.instructions] + ir_instructions = [instruction.to_ir() for instruction in self.instructions] ir_results = [result_type.to_ir() for result_type in self.result_types] ir_basis_rotation_instructions = [ instr.to_ir() for instr in self.basis_rotation_instructions @@ -1192,10 +1207,10 @@ def __add__(self, addable: AddableTypes) -> Circuit: def __repr__(self) -> str: if not self.result_types: - return f"Circuit('instructions': {list(self.instructions)})" + return f"Circuit('instructions': {self.instructions})" else: return ( - f"Circuit('instructions': {list(self.instructions)}" + f"Circuit('instructions': {self.instructions}" + f", 'result_types': {self.result_types})" ) @@ -1205,8 +1220,7 @@ def __str__(self): def __eq__(self, other): if isinstance(other, Circuit): return ( - list(self.instructions) == list(other.instructions) - and self.result_types == other.result_types + self.instructions == other.instructions and self.result_types == other.result_types ) return NotImplemented diff --git a/src/braket/circuits/compiler_directive.py b/src/braket/circuits/compiler_directive.py index 87b1f818..f6b7a936 100644 --- a/src/braket/circuits/compiler_directive.py +++ b/src/braket/circuits/compiler_directive.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 Sequence, Tuple from braket.circuits.operator import Operator @@ -41,6 +43,19 @@ def ascii_symbols(self) -> Tuple[str, ...]: """Tuple[str, ...]: Returns the ascii symbols for the compiler directive.""" return self._ascii_symbols + def counterpart(self) -> CompilerDirective: + """Returns the "opposite" counterpart to this compiler directive. + + For example, the counterpart of a directive that starts a box + is the directive that ends the box. + + Returns: + CompilerDirective: The counterpart compiler directive + """ + raise NotImplementedError( + f"Compiler directive {self.name} does not have counterpart implemented" + ) + def to_ir(self, *args, **kwargs): raise NotImplementedError("to_ir has not been implemented yet.") diff --git a/src/braket/circuits/compiler_directives.py b/src/braket/circuits/compiler_directives.py index 8e0c0dc3..d17a2e38 100644 --- a/src/braket/circuits/compiler_directives.py +++ b/src/braket/circuits/compiler_directives.py @@ -24,6 +24,9 @@ class StartVerbatimBox(CompilerDirective): def __init__(self): super().__init__(["StartVerbatim"]) + def counterpart(self) -> CompilerDirective: + return EndVerbatimBox() + def to_ir(self, *args, **kwargs): return ir.StartVerbatimBox.construct() @@ -37,5 +40,8 @@ class EndVerbatimBox(CompilerDirective): def __init__(self): super().__init__(["EndVerbatim"]) + def counterpart(self) -> CompilerDirective: + return StartVerbatimBox() + def to_ir(self, *args, **kwargs): return ir.EndVerbatimBox.construct() diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index b8a897c5..bb9c32c8 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -11,7 +11,9 @@ # 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, Sequence +from __future__ import annotations + +from typing import Any, List, Optional, Sequence, Tuple from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.qubit_set import QubitSet @@ -41,6 +43,16 @@ def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): """ super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + def adjoint(self) -> List[Gate]: + """Returns a list of gates that implement the adjoint of this gate. + + This is a list because some gates do not have an inverse defined by a single existing gate. + + Returns: + List[Gate]: The gates comprising the adjoint of this gate. + """ + raise NotImplementedError(f"Gate {self.name} does not have adjoint implemented") + def to_ir(self, target: QubitSet) -> Any: """Returns IR object of quantum operator and target @@ -51,16 +63,19 @@ def to_ir(self, target: QubitSet) -> Any: """ raise NotImplementedError("to_ir has not been implemented yet.") + @property + def ascii_symbols(self) -> Tuple[str, ...]: + """Tuple[str, ...]: Returns the ascii symbols for the quantum operator.""" + return self._ascii_symbols + def __eq__(self, other): - if isinstance(other, Gate): - return self.name == other.name - return False + return isinstance(other, Gate) and self.name == other.name def __repr__(self): - return f"{self.name}('qubit_count': {self.qubit_count})" + return f"{self.name}('qubit_count': {self._qubit_count})" @classmethod - def register_gate(cls, gate: "Gate"): + def register_gate(cls, gate: Gate): """Register a gate implementation by adding it into the Gate class. Args: diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 2593dde5..ad13b822 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.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 Iterable, Union +from typing import Iterable, List, Union import numpy as np from sympy import Float @@ -39,6 +39,7 @@ 3. Register the class with the `Gate` class via `Gate.register_gate()`. """ + # Single qubit gates # @@ -48,6 +49,9 @@ class H(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["H"]) + def adjoint(self) -> List[Gate]: + return [H()] + def to_ir(self, target: QubitSet): return ir.H.construct(target=target[0]) @@ -85,6 +89,9 @@ class I(Gate): # noqa: E742, E261 def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["I"]) + def adjoint(self) -> List[Gate]: + return [I()] + def to_ir(self, target: QubitSet): return ir.I.construct(target=target[0]) @@ -122,6 +129,9 @@ class X(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["X"]) + def adjoint(self) -> List[Gate]: + return [X()] + def to_ir(self, target: QubitSet): return ir.X.construct(target=target[0]) @@ -159,6 +169,9 @@ class Y(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Y"]) + def adjoint(self) -> List[Gate]: + return [Y()] + def to_ir(self, target: QubitSet): return ir.Y.construct(target=target[0]) @@ -196,6 +209,9 @@ class Z(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Z"]) + def adjoint(self) -> List[Gate]: + return [Z()] + def to_ir(self, target: QubitSet): return ir.Z.construct(target=target[0]) @@ -233,11 +249,13 @@ class S(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["S"]) + def adjoint(self) -> List[Gate]: + return [Si()] + def to_ir(self, target: QubitSet): return ir.S.construct(target=target[0]) def to_matrix(self) -> np.ndarray: - return np.array([[1.0, 0.0], [0.0, 1.0j]], dtype=complex) @staticmethod @@ -271,6 +289,9 @@ class Si(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Si"]) + def adjoint(self) -> List[Gate]: + return [S()] + def to_ir(self, target: QubitSet): return ir.Si.construct(target=target[0]) @@ -308,6 +329,9 @@ class T(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["T"]) + def adjoint(self) -> List[Gate]: + return [Ti()] + def to_ir(self, target: QubitSet): return ir.T.construct(target=target[0]) @@ -345,6 +369,9 @@ class Ti(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Ti"]) + def adjoint(self) -> List[Gate]: + return [T()] + def to_ir(self, target: QubitSet): return ir.Ti.construct(target=target[0]) @@ -382,6 +409,9 @@ class V(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["V"]) + def adjoint(self) -> List[Gate]: + return [Vi()] + def to_ir(self, target: QubitSet): return ir.V.construct(target=target[0]) @@ -419,6 +449,9 @@ class Vi(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Vi"]) + def adjoint(self) -> List[Gate]: + return [V()] + def to_ir(self, target: QubitSet): return ir.Vi.construct(target=target[0]) @@ -704,6 +737,9 @@ class CNot(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "X"]) + def adjoint(self) -> List[Gate]: + return [CNot()] + def to_ir(self, target: QubitSet): return ir.CNot.construct(control=target[0], target=target[1]) @@ -749,6 +785,9 @@ class Swap(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["SWAP", "SWAP"]) + def adjoint(self) -> List[Gate]: + return [Swap()] + def to_ir(self, target: QubitSet): return ir.Swap.construct(targets=[target[0], target[1]]) @@ -794,6 +833,9 @@ class ISwap(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["ISWAP", "ISWAP"]) + def adjoint(self) -> List[Gate]: + return [self, self, self] + def to_ir(self, target: QubitSet): return ir.ISwap.construct(targets=[target[0], target[1]]) @@ -1235,6 +1277,9 @@ class CV(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "V"]) + def adjoint(self) -> List[Gate]: + return [self, self, self] + def to_ir(self, target: QubitSet): return ir.CV.construct(control=target[0], target=target[1]) @@ -1280,6 +1325,9 @@ class CY(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "Y"]) + def adjoint(self) -> List[Gate]: + return [CY()] + def to_ir(self, target: QubitSet): return ir.CY.construct(control=target[0], target=target[1]) @@ -1325,6 +1373,9 @@ class CZ(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "Z"]) + def adjoint(self) -> List[Gate]: + return [CZ()] + def to_ir(self, target: QubitSet): return ir.CZ.construct(control=target[0], target=target[1]) @@ -1362,6 +1413,9 @@ class ECR(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["ECR", "ECR"]) + def adjoint(self) -> List[Gate]: + return [ECR()] + def to_ir(self, target: QubitSet): return ir.ECR.construct(targets=[target[0], target[1]]) @@ -1638,6 +1692,9 @@ class CCNot(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "C", "X"]) + def adjoint(self) -> List[Gate]: + return [CCNot()] + def to_ir(self, target: QubitSet): return ir.CCNot.construct(controls=[target[0], target[1]], target=target[2]) @@ -1688,6 +1745,9 @@ class CSwap(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "SWAP", "SWAP"]) + def adjoint(self) -> List[Gate]: + return [CSwap()] + def to_ir(self, target: QubitSet): return ir.CSwap.construct(control=target[0], targets=[target[1], target[2]]) @@ -1759,6 +1819,9 @@ def __init__(self, matrix: np.ndarray, display_name: str = "U"): def to_matrix(self): return np.array(self._matrix) + def adjoint(self) -> List[Gate]: + return [Unitary(self._matrix.conj().T, display_name=f"({self.ascii_symbols})^†")] + def to_ir(self, target: QubitSet): return ir.Unitary.construct( targets=[qubit for qubit in target], diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index 5c2d2c9b..4e6a0ba8 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -13,8 +13,10 @@ from __future__ import annotations -from typing import Dict, Tuple +from typing import Dict, List, Tuple +from braket.circuits.compiler_directive import CompilerDirective +from braket.circuits.gate import Gate from braket.circuits.operator import Operator from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.qubit import QubitInput @@ -31,7 +33,7 @@ class Instruction: def __init__(self, operator: InstructionOperator, target: QubitSetInput = None): """ - InstructionOperator includes objects of type `Gate` only. + InstructionOperator includes objects of type `Gate` and `Noise` only. Args: operator (InstructionOperator): Operator for the instruction. @@ -81,6 +83,24 @@ def target(self) -> QubitSet: """ return self._target + def adjoint(self) -> List[Instruction]: + """Returns a list of Instructions implementing adjoint of this instruction's own operator + + This operation only works on Gate operators and compiler directives. + + Returns: + List[Instruction]: A list of new instructions that comprise the adjoint of this operator + + Raises: + NotImplementedError: If `operator` is not of type `Gate` or `CompilerDirective` + """ + operator = self._operator + if isinstance(operator, Gate): + return [Instruction(gate, self._target) for gate in operator.adjoint()] + elif isinstance(operator, CompilerDirective): + return [Instruction(operator.counterpart(), self._target)] + raise NotImplementedError(f"Adjoint not supported for {operator}") + def to_ir(self): """ Converts the operator into the canonical intermediate representation. diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index 1946e236..f6953db4 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -50,6 +50,18 @@ def test_angle_setter(angled_gate): angled_gate.angle = 0.14 +def test_adjoint(angled_gate): + assert angled_gate.adjoint() == [AngledGate(angle=-0.15, qubit_count=1, ascii_symbols=["foo"])] + + +def test_adjoint_parameterized(): + theta = FreeParameter("theta") + angled_gate = AngledGate(angle=(theta + 1), qubit_count=1, ascii_symbols=["foo"]) + assert angled_gate.adjoint() == [ + AngledGate(angle=-(theta + 1), qubit_count=1, ascii_symbols=["foo"]) + ] + + def test_equality(angled_gate): gate = AngledGate(angle=0.15, qubit_count=1, ascii_symbols=["bar"]) other_gate = AngledGate(angle=0.3, qubit_count=1, ascii_symbols=["foo"]) 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 643c4d9a..61c3ad87 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -31,17 +31,14 @@ def test_empty_circuit(): def test_one_gate_one_qubit(): circ = Circuit().h(0) expected = ("T : |0|", " ", "q0 : -H-", "", "T : |0|") - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _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 |") - expected = "\n".join(expected) - - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_one_gate_one_qubit_rotation_with_parameter(): @@ -57,8 +54,7 @@ def test_one_gate_one_qubit_rotation_with_parameter(): "", "Unassigned parameters: {theta}.", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_one_gate_one_qubit_rotation_with_unicode(): @@ -74,8 +70,7 @@ def test_one_gate_one_qubit_rotation_with_unicode(): "", "Unassigned parameters: {θ}.", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_one_gate_one_qubit_rotation_with_parameter_assigned(): @@ -90,9 +85,7 @@ def test_one_gate_one_qubit_rotation_with_parameter_assigned(): "", "T : | 0 |", ) - expected = "\n".join(expected) - print(AsciiCircuitDiagram.build_diagram(new_circ)) - assert AsciiCircuitDiagram.build_diagram(new_circ) == expected + _assert_correct_diagram(new_circ, expected) def test_qubit_width(): @@ -106,8 +99,7 @@ def test_qubit_width(): "", "T : |0|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_gate_width(): @@ -128,8 +120,7 @@ def to_ir(self, target): "", "T : |0| 1 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_time_width(): @@ -174,8 +165,7 @@ def test_time_width(): "", "T : |0|1|2|3|4|5|6|7|8|9|10|11|12|13|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_connector_across_two_qubits(): @@ -193,8 +183,7 @@ def test_connector_across_two_qubits(): "", "T : |0|1|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_overlapping_qubits(): @@ -212,8 +201,7 @@ def test_overlapping_qubits(): "", "T : | 0 |1|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_overlapping_qubits_angled_gates(): @@ -231,8 +219,7 @@ def test_overlapping_qubits_angled_gates(): "", "T : | 0 |1|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_connector_across_gt_two_qubits(): @@ -250,8 +237,7 @@ def test_connector_across_gt_two_qubits(): "", "T : | 0 |1|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_connector_across_non_used_qubits(): @@ -269,8 +255,7 @@ def test_connector_across_non_used_qubits(): "", "T : | 0 |1|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_1q_no_preceding(): @@ -282,8 +267,7 @@ def test_verbatim_1q_no_preceding(): "", "T : | 0 |1| 2 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_1q_preceding(): @@ -295,8 +279,7 @@ def test_verbatim_1q_preceding(): "", "T : |0| 1 |2| 3 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_1q_following(): @@ -308,8 +291,7 @@ def test_verbatim_1q_following(): "", "T : | 0 |1| 2 |3|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_2q_no_preceding(): @@ -323,8 +305,7 @@ def test_verbatim_2q_no_preceding(): "", "T : | 0 |1|2| 3 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_2q_preceding(): @@ -338,8 +319,7 @@ def test_verbatim_2q_preceding(): "", "T : |0| 1 |2|3| 4 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_2q_following(): @@ -353,8 +333,7 @@ def test_verbatim_2q_following(): "", "T : | 0 |1|2| 3 |4|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_3q_no_preceding(): @@ -370,8 +349,7 @@ def test_verbatim_3q_no_preceding(): "", "T : | 0 |1|2|3| 4 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_3q_preceding(): @@ -387,8 +365,7 @@ def test_verbatim_3q_preceding(): "", "T : |0| 1 |2|3|4| 5 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_3q_following(): @@ -404,8 +381,7 @@ def test_verbatim_3q_following(): "", "T : | 0 |1|2|3| 4 |5|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_different_qubits(): @@ -423,8 +399,7 @@ def test_verbatim_different_qubits(): "", "T : |0| 1 |2| 3 |4|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_verbatim_qubset_qubits(): @@ -442,8 +417,7 @@ def test_verbatim_qubset_qubits(): "", "T : |0|1|2| 3 |4| 5 |6|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_ignore_non_gates(): @@ -467,8 +441,7 @@ def to_ir(self, target): "", "T : |0|1|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_result_types_target_none(): @@ -482,8 +455,7 @@ def test_result_types_target_none(): "", "T : |0|Result Types|", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_result_types_target_some(): @@ -505,8 +477,7 @@ def test_result_types_target_some(): "", "T : |0| Result Types |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_additional_result_types(): @@ -524,8 +495,7 @@ def test_additional_result_types(): "", "Additional result types: StateVector, Amplitude(110,001)", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_multiple_result_types(): @@ -551,8 +521,7 @@ def test_multiple_result_types(): "", "T : | 0 |1| Result Types |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_multiple_result_types_with_state_vector_amplitude(): @@ -582,8 +551,7 @@ def test_multiple_result_types_with_state_vector_amplitude(): "", "Additional result types: Amplitude(0001), StateVector", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_multiple_result_types_with_custom_hermitian_ascii_symbol(): @@ -616,8 +584,7 @@ def test_multiple_result_types_with_custom_hermitian_ascii_symbol(): "", "T : | 0 |1| Result Types |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_noise_1qubit(): @@ -631,8 +598,7 @@ def test_noise_1qubit(): "", "T : | 0 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) def test_noise_2qubit(): @@ -648,5 +614,8 @@ def test_noise_2qubit(): "", "T : | 0 |", ) - expected = "\n".join(expected) - assert AsciiCircuitDiagram.build_diagram(circ) == expected + _assert_correct_diagram(circ, expected) + + +def _assert_correct_diagram(circ, expected): + assert AsciiCircuitDiagram.build_diagram(circ) == "\n".join(expected) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 7538d2a1..5deb820a 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -75,14 +75,14 @@ def bell_pair(prob): def test_repr_instructions(h): - expected = f"Circuit('instructions': {list(h.instructions)})" + expected = f"Circuit('instructions': {h.instructions})" assert repr(h) == expected def test_repr_result_types(cnot_prob): circuit = cnot_prob expected = ( - f"Circuit('instructions': {list(circuit.instructions)}" + f"Circuit('instructions': {circuit.instructions}" + f", 'result_types': {circuit.result_types})" ) assert repr(circuit) == expected @@ -123,7 +123,7 @@ def test_call_with_result_type(prob): assert new_circ == expected and not new_circ.parameters assert new_circ.observables_simultaneously_measurable - assert list(new_circ.result_types) == [prob] + assert new_circ.result_types == [prob] def test_call_one_param_not_bound(): @@ -166,28 +166,28 @@ def test_call_with_default_parameter_val(): def test_add_result_type_default(prob): circ = Circuit().add_result_type(prob) assert circ.observables_simultaneously_measurable - assert list(circ.result_types) == [prob] + assert circ.result_types == [prob] def test_add_result_type_with_mapping(prob): expected = [ResultType.Probability([10, 11])] circ = Circuit().add_result_type(prob, target_mapping={0: 10, 1: 11}) assert circ.observables_simultaneously_measurable - assert list(circ.result_types) == expected + assert circ.result_types == expected def test_add_result_type_with_target(prob): expected = [ResultType.Probability([10, 11])] circ = Circuit().add_result_type(prob, target=[10, 11]) assert circ.observables_simultaneously_measurable - assert list(circ.result_types) == expected + assert circ.result_types == expected def test_add_result_type_already_exists(): expected = [ResultType.StateVector()] circ = Circuit(expected).add_result_type(expected[0]) assert circ.observables_simultaneously_measurable - assert list(circ.result_types) == expected + assert circ.result_types == expected def test_add_result_type_observable_conflict_target(): @@ -302,19 +302,19 @@ def test_add_result_type_with_target_and_mapping(prob): def test_add_instruction_default(cnot_instr): circ = Circuit().add_instruction(cnot_instr) - assert list(circ.instructions) == [cnot_instr] + assert circ.instructions == [cnot_instr] def test_add_instruction_with_mapping(cnot_instr): expected = [Instruction(Gate.CNot(), [10, 11])] circ = Circuit().add_instruction(cnot_instr, target_mapping={0: 10, 1: 11}) - assert list(circ.instructions) == expected + assert circ.instructions == expected def test_add_instruction_with_target(cnot_instr): expected = [Instruction(Gate.CNot(), [10, 11])] circ = Circuit().add_instruction(cnot_instr, target=[10, 11]) - assert list(circ.instructions) == expected + assert circ.instructions == expected def test_add_multiple_single_qubit_instruction(h_instr): @@ -487,6 +487,24 @@ def test_add_with_circuit_with_target(bell_pair): assert circ == expected +def test_adjoint(): + circ = Circuit().s(0).add_verbatim_box(Circuit().rz(0, 0.123)).expectation(Observable.X(), 0) + expected = Circuit() + expected.add_verbatim_box(Circuit().rz(0, -0.123)) + expected.si(0) + expected.expectation(Observable.X(), 0) + actual = circ.adjoint() + assert actual == expected + assert circ == expected.adjoint() + assert circ == actual.adjoint() + + +def test_adjoint_subcircuit_free_parameter(): + circ = Circuit().h(0).add_circuit(Circuit().s(0).rz(0, FreeParameter("theta")).adjoint()).x(0) + expected = Circuit().h(0).rz(0, -FreeParameter("theta")).si(0).x(0) + assert circ == expected + + def test_circuit_copy(h, bell_pair, cnot_instr): original = Circuit().add(h).add(bell_pair).add(cnot_instr) copy = original.copy() @@ -1664,12 +1682,12 @@ def test_depth_setter(h): def test_instructions_getter(h): - assert list(h.instructions) == list(h._moments.values()) + assert h.instructions == list(h._moments.values()) @pytest.mark.xfail(raises=AttributeError) def test_instructions_setter(h, h_instr): - h.instructions = iter([h_instr]) + h.instructions = [h_instr] def test_moments_getter(h): diff --git a/test/unit_tests/braket/circuits/test_compiler_directive.py b/test/unit_tests/braket/circuits/test_compiler_directive.py index 4a971e85..bd1f2d95 100644 --- a/test/unit_tests/braket/circuits/test_compiler_directive.py +++ b/test/unit_tests/braket/circuits/test_compiler_directive.py @@ -45,6 +45,11 @@ def test_name(compiler_directive): assert compiler_directive.name == expected +@pytest.mark.xfail(raises=NotImplementedError) +def test_counterpart_not_implemented_by_default(compiler_directive): + compiler_directive.counterpart() + + @pytest.mark.xfail(raises=NotImplementedError) def test_to_ir_not_implemented_by_default(compiler_directive): compiler_directive.to_ir(None) diff --git a/test/unit_tests/braket/circuits/test_compiler_directives.py b/test/unit_tests/braket/circuits/test_compiler_directives.py index 13de3d00..80e485f8 100644 --- a/test/unit_tests/braket/circuits/test_compiler_directives.py +++ b/test/unit_tests/braket/circuits/test_compiler_directives.py @@ -18,18 +18,23 @@ from braket.circuits.compiler_directive import CompilerDirective testdata = [ - (compiler_directives.StartVerbatimBox, ir.StartVerbatimBox), - (compiler_directives.EndVerbatimBox, ir.EndVerbatimBox), + (compiler_directives.StartVerbatimBox, ir.StartVerbatimBox, compiler_directives.EndVerbatimBox), + (compiler_directives.EndVerbatimBox, ir.EndVerbatimBox, compiler_directives.StartVerbatimBox), ] -@pytest.mark.parametrize("testclass,irclass", testdata) -def test_to_ir(testclass, irclass): +@pytest.mark.parametrize("testclass,irclass,counterpart", testdata) +def test_counterpart(testclass, irclass, counterpart): + assert testclass().counterpart() == counterpart() + + +@pytest.mark.parametrize("testclass,irclass,counterpart", testdata) +def test_to_ir(testclass, irclass, counterpart): assert testclass().to_ir() == irclass() -@pytest.mark.parametrize("testclass,irclass", testdata) -def test_equality(testclass, irclass): +@pytest.mark.parametrize("testclass,irclass,counterpart", testdata) +def test_equality(testclass, irclass, counterpart): op1 = testclass() op2 = testclass() assert op1 == op2 diff --git a/test/unit_tests/braket/circuits/test_gate.py b/test/unit_tests/braket/circuits/test_gate.py index dd54ef44..8dd4297c 100644 --- a/test/unit_tests/braket/circuits/test_gate.py +++ b/test/unit_tests/braket/circuits/test_gate.py @@ -25,6 +25,11 @@ def test_is_operator(gate): assert isinstance(gate, QuantumOperator) +@pytest.mark.xfail(raises=NotImplementedError) +def test_adjoint_not_implemented_by_default(gate): + gate.adjoint() + + @pytest.mark.xfail(raises=NotImplementedError) def test_to_ir_not_implemented_by_default(gate): gate.to_ir(None) @@ -49,13 +54,13 @@ def test_matrix_equivalence_non_gate(): def test_str(gate): - expected = "{}('qubit_count': {})".format(gate.name, gate.qubit_count) + expected = f"{gate.name}('qubit_count': {gate.qubit_count})" assert str(gate) == expected def test_str_angle(): gate = Gate.Rx(0.5) - expected = "{}('angle': {}, 'qubit_count': {})".format(gate.name, gate.angle, gate.qubit_count) + expected = f"{gate.name}('angle': {gate.angle}, 'qubit_count': {gate.qubit_count})" assert str(gate) == expected diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 100fa41c..ed4bfa84 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.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. +import functools + import numpy as np import pytest @@ -307,6 +309,15 @@ def test_gate_subroutine(testclass, subroutine_name, irclass, irsubclasses, kwar assert subroutine(**subroutine_input) == Circuit(instruction_list) +@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)) + matrices = [elem.to_matrix() for elem in gate.adjoint()] + matrices.append(gate.to_matrix()) + identity = np.eye(2**gate.qubit_count) + assert np.isclose(functools.reduce(lambda a, b: a @ b, matrices), identity).all() + + @pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) def test_gate_to_matrix(testclass, subroutine_name, irclass, irsubclasses, kwargs): gate1 = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) diff --git a/test/unit_tests/braket/circuits/test_instruction.py b/test/unit_tests/braket/circuits/test_instruction.py index cef23faf..b0e40345 100644 --- a/test/unit_tests/braket/circuits/test_instruction.py +++ b/test/unit_tests/braket/circuits/test_instruction.py @@ -13,7 +13,7 @@ import pytest -from braket.circuits import Gate, Instruction, Qubit, QubitSet +from braket.circuits import Gate, Instruction, Noise, Qubit, QubitSet, compiler_directives @pytest.fixture @@ -79,6 +79,22 @@ def test_target_setter(instr): instr.target = QubitSet(0) +def test_adjoint_gate(): + instr = Instruction(Gate.S(), 0) + adj = instr.adjoint() + assert adj == [Instruction(Gate.Si(), 0)] + + +def test_adjoint_compiler_directive(): + instr = Instruction(compiler_directives.StartVerbatimBox()).adjoint() + assert instr == [Instruction(compiler_directives.EndVerbatimBox())] + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_adjoint_unsupported(): + Instruction(Noise.BitFlip(0.1), 0).adjoint() + + def test_str(instr): expected = "Instruction('operator': {}, 'target': {})".format(instr.operator, instr.target) assert str(instr) == expected From cad97b482a92d9403f26458f52884a23703c23f0 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 10 May 2022 18:24:19 +0000 Subject: [PATCH 0458/1165] prepare release v1.21.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 30e25959..25f82319 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.21.0 (2022-05-10) + +### Features + + * Gate and Circuit inversion + ## v1.20.0 (2022-05-04) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 7ddec429..553c4488 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.20.1.dev0" +__version__ = "1.21.0" From f45bf39344d225af30bdcaaf183866f691eb25c4 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 10 May 2022 18:24:19 +0000 Subject: [PATCH 0459/1165] update development version to v1.21.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 553c4488..e6804978 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.21.0" +__version__ = "1.21.1.dev0" From 09592f0abbb21382dcfecdd3ef4197117a6fe6bc Mon Sep 17 00:00:00 2001 From: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com> Date: Mon, 16 May 2022 18:44:27 -0700 Subject: [PATCH 0460/1165] fix: broken links for examples (#340) --- doc/examples-getting-started.rst | 10 +++++----- doc/examples-ml-pennylane.rst | 8 ++++---- doc/examples-quantum-annealing-dwave.rst | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/examples-getting-started.rst b/doc/examples-getting-started.rst index e95765f2..e68e4134 100644 --- a/doc/examples-getting-started.rst +++ b/doc/examples-getting-started.rst @@ -8,13 +8,13 @@ Get started on Amazon Braket with some introductory examples. :maxdepth: 2 *************** -`Getting started `_ +`Getting started `_ *************** A hello-world tutorial that shows you how to build a simple circuit and run it on a local simulator. *************** -`Running quantum circuits on simulators `_ +`Running quantum circuits on simulators `_ *************** This tutorial prepares a paradigmatic example for a multi-qubit entangled state, @@ -25,7 +25,7 @@ protocols it is used as a resource for quantum error correction, quantum communi and quantum metrology. *************** -`Running quantum circuits on QPU devices `_ +`Running quantum circuits on QPU devices `_ *************** This tutorial prepares a maximally-entangled Bell state between two qubits, @@ -35,7 +35,7 @@ we run the circuit on the superconducting machine from Rigetti, and on the ion-t machine provided by IonQ. *************** -`Deep Dive into the anatomy of quantum circuits `_ +`Deep Dive into the anatomy of quantum circuits `_ *************** This tutorial discusses in detail the anatomy of quantum circuits in the Amazon @@ -46,7 +46,7 @@ the circuit on a device of our choice (defining a quantum task) and how to track recover, or cancel a quantum task efficiently. *************** -`Superdense coding `_ +`Superdense coding `_ *************** This tutorial constructs an implementation of the superdense coding protocol using diff --git a/doc/examples-ml-pennylane.rst b/doc/examples-ml-pennylane.rst index f3f7e4b0..0a3ec922 100644 --- a/doc/examples-ml-pennylane.rst +++ b/doc/examples-ml-pennylane.rst @@ -8,14 +8,14 @@ Learn more about how to combine PennyLane with Amazon Braket. :maxdepth: 2 ************************** -`Combining PennyLane with Amazon Braket `_ +`Combining PennyLane with Amazon Braket `_ ************************** 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 `_ +`Computing gradients in parallel with PennyLane-Braket `_ ************************** Learn how to speed up training of quantum circuits by using parallel execution on @@ -26,7 +26,7 @@ local simulator for both executions and gradient calculations. This illustrates parallel capabilities can be combined between PennyLane and SV1. ************************** -`Graph optimization with QAOA `_ +`Graph optimization with QAOA `_ ************************** In this tutorial, you learn how quantum circuit training can be applied to a problem @@ -37,7 +37,7 @@ the Amazon Braket SV1 simulator to speed up gradient calculations and hence trai using around 1-2 minutes per iteration. ************************** -`Quantum chemistry with VQE `_ +`Quantum chemistry with VQE `_ ************************** In this tutorial, you will learn how PennyLane and Amazon Braket can be combined to solve an diff --git a/doc/examples-quantum-annealing-dwave.rst b/doc/examples-quantum-annealing-dwave.rst index 7ed2725e..64f8c7ed 100644 --- a/doc/examples-quantum-annealing-dwave.rst +++ b/doc/examples-quantum-annealing-dwave.rst @@ -8,7 +8,7 @@ Learn more about quantum annealing with D-Wave. :maxdepth: 2 ************************** -`Anatomy of annealing with ocean `_ +`Anatomy of annealing with ocean `_ ************************** Learn more about the anatomy of quantum annealing with D-Wave on Amazon Braket. From bc130e35cce5e837ba8aa5bc529f7a0b35872b2e Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 17 May 2022 18:25:40 +0000 Subject: [PATCH 0461/1165] prepare release v1.21.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 25f82319..92afbdd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.21.1 (2022-05-17) + +### Bug Fixes and Other Changes + + * broken links for examples + ## v1.21.0 (2022-05-10) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index e6804978..f9cef79a 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.21.1.dev0" +__version__ = "1.21.1" From 25162bdd237a2553fec0c1f1fd5e4e7cf6978887 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 17 May 2022 18:25:40 +0000 Subject: [PATCH 0462/1165] update development version to v1.21.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 f9cef79a..140307d1 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.21.1" +__version__ = "1.21.2.dev0" From 3d90064adf4b1b53d635ef46b398b9d2fc5980b1 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 18 May 2022 10:37:06 -0700 Subject: [PATCH 0463/1165] feature: Noise models (#313) Introducing NoiseModels, which encapsulate a collection of noise channels to be applied to a given circuit according to some criteria. Co-authored-by: Milan <30416311+krneta@users.noreply.github.com> Co-authored-by: mbeach-aws <85963088+mbeach-aws@users.noreply.github.com> --- .coveragerc | 6 + src/braket/circuits/angled_gate.py | 12 +- src/braket/circuits/ascii_circuit_diagram.py | 5 +- src/braket/circuits/free_parameter.py | 12 + src/braket/circuits/gates.py | 259 ++----- src/braket/circuits/noise.py | 645 +++++++++++++----- src/braket/circuits/noise_model/__init__.py | 30 + .../circuit_instruction_criteria.py | 56 ++ src/braket/circuits/noise_model/criteria.py | 114 ++++ .../noise_model/criteria_input_parsing.py | 90 +++ .../circuits/noise_model/gate_criteria.py | 142 ++++ .../noise_model/initialization_criteria.py | 36 + .../circuits/noise_model/noise_model.py | 403 +++++++++++ .../noise_model/observable_criteria.py | 156 +++++ .../qubit_initialization_criteria.py | 109 +++ .../noise_model/result_type_criteria.py | 32 + .../noise_model/unitary_gate_criteria.py | 122 ++++ src/braket/circuits/noises.py | 415 ++++++++++- src/braket/circuits/parameterizable.py | 7 +- .../circuits/noise/test_gate_criteria.py | 209 ++++++ .../braket/circuits/noise/test_noise_model.py | 490 +++++++++++++ .../noise/test_observable_criteria.py | 191 ++++++ .../test_qubit_initialization_criteria.py | 112 +++ .../noise/test_unitary_gate_criteria.py | 207 ++++++ .../circuits/test_ascii_circuit_diagram.py | 37 +- test/unit_tests/braket/circuits/test_noise.py | 221 +++--- .../unit_tests/braket/circuits/test_noises.py | 87 +++ 27 files changed, 3734 insertions(+), 471 deletions(-) create mode 100644 src/braket/circuits/noise_model/__init__.py create mode 100644 src/braket/circuits/noise_model/circuit_instruction_criteria.py create mode 100644 src/braket/circuits/noise_model/criteria.py create mode 100644 src/braket/circuits/noise_model/criteria_input_parsing.py create mode 100644 src/braket/circuits/noise_model/gate_criteria.py create mode 100644 src/braket/circuits/noise_model/initialization_criteria.py create mode 100644 src/braket/circuits/noise_model/noise_model.py create mode 100644 src/braket/circuits/noise_model/observable_criteria.py create mode 100644 src/braket/circuits/noise_model/qubit_initialization_criteria.py create mode 100644 src/braket/circuits/noise_model/result_type_criteria.py create mode 100644 src/braket/circuits/noise_model/unitary_gate_criteria.py create mode 100644 test/unit_tests/braket/circuits/noise/test_gate_criteria.py create mode 100644 test/unit_tests/braket/circuits/noise/test_noise_model.py create mode 100644 test/unit_tests/braket/circuits/noise/test_observable_criteria.py create mode 100644 test/unit_tests/braket/circuits/noise/test_qubit_initialization_criteria.py create mode 100644 test/unit_tests/braket/circuits/noise/test_unitary_gate_criteria.py diff --git a/.coveragerc b/.coveragerc index ed0ee10f..22227400 100644 --- a/.coveragerc +++ b/.coveragerc @@ -18,6 +18,12 @@ source = [report] show_missing = True ignore_errors = True +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain if tests don't hit defensive assertion code: + raise NotImplementedError [html] directory = build/coverage diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index 754ca803..343234d2 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.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 copy import math from typing import List, Optional, Sequence, Union @@ -58,10 +60,11 @@ def __init__( @property def parameters(self) -> List[Union[FreeParameterExpression, float]]: """ - Returns the free parameters associated with the object. + Returns the parameters associated with the object, either unbound free parameters or + bound values. Returns: - Union[FreeParameterExpression,, float]: Returns the free parameters or fixed value + List[Union[FreeParameterExpression, float]]: The free parameters or fixed value associated with the object. """ return self._parameters @@ -76,13 +79,16 @@ def angle(self) -> Union[FreeParameterExpression, float]: """ return self._parameters[0] - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> AngledGate: """ Takes in parameters and attempts to assign them to values. Args: **kwargs: The parameters that are being assigned. + Returns: + AngledGate: A new Gate of the same type with the requested parameters bound. + Raises: NotImplementedError: Subclasses should implement this function. """ diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index 4926de9c..a72b10be 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -86,7 +86,10 @@ def build_diagram(circuit) -> str: # A list of parameters in the circuit to the currently assigned values. if circuit.parameters: - lines.append(f"\nUnassigned parameters: {circuit.parameters}.") + lines.append( + "\nUnassigned parameters: " + f"{sorted(circuit.parameters, key=lambda param: param.name)}." + ) return "\n".join(lines) diff --git a/src/braket/circuits/free_parameter.py b/src/braket/circuits/free_parameter.py index f3989609..406fdc84 100644 --- a/src/braket/circuits/free_parameter.py +++ b/src/braket/circuits/free_parameter.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 numbers import Number from typing import Dict @@ -83,3 +85,13 @@ def __repr__(self): The name of the class:'FreeParameter' to represent the class. """ return self.name + + def to_dict(self) -> dict: + return { + "__class__": self.__class__.__name__, + "name": self.name, + } + + @classmethod + def from_dict(cls, parameter: dict) -> FreeParameter: + return FreeParameter(parameter["name"]) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index ad13b822..8a1a9768 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -19,7 +19,6 @@ import braket.ir.jaqcd as ir from braket.circuits import circuit from braket.circuits.angled_gate import AngledGate -from braket.circuits.free_parameter import FreeParameter from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction @@ -490,10 +489,10 @@ class Rx(AngledGate): """X-axis rotation gate. Args: - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameter, float]): + def __init__(self, angle: Union[FreeParameterExpression, float]): super().__init__( angle=angle, qubit_count=None, @@ -513,27 +512,18 @@ def fixed_qubit_count() -> int: return 1 def bind_values(self, **kwargs): - """ - Takes in parameters and attempts to assign them to values. - - Args: - **kwargs: The parameters that are being assigned. - - Returns: - Gate.Rx: A new Gate of the same type with the requested - parameters bound. - - """ return get_angle(self, **kwargs) @staticmethod @circuit.subroutine(register=True) - def rx(target: QubitInput, angle: Union[FreeParameter, float]) -> Iterable[Instruction]: + def rx( + target: QubitInput, angle: Union[FreeParameterExpression, float] + ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (Qubit or int): Target qubit index. - angle (Union[FreeParameter, float]): Angle in radians. + angle (Union[FreeParameterExpression, float]): Angle in radians. Returns: Iterable[Instruction]: Rx instruction. @@ -551,10 +541,10 @@ class Ry(AngledGate): """Y-axis rotation gate. Args: - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameter, float]): + def __init__(self, angle: Union[FreeParameterExpression, float]): super().__init__( angle=angle, qubit_count=None, @@ -574,27 +564,18 @@ def fixed_qubit_count() -> int: return 1 def bind_values(self, **kwargs): - """ - Takes in parameters and attempts to assign them to values. - - Args: - **kwargs: The parameters that are being assigned. - - Returns: - Gate.Ry: A new Gate of the same type with the requested - parameters bound. - - """ return get_angle(self, **kwargs) @staticmethod @circuit.subroutine(register=True) - def ry(target: QubitInput, angle: Union[FreeParameter, float]) -> Iterable[Instruction]: + def ry( + target: QubitInput, angle: Union[FreeParameterExpression, float] + ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (Qubit or int): Target qubit index. - angle (Union[FreeParameter, float]): Angle in radians. + angle (Union[FreeParameterExpression, float]): Angle in radians. Returns: Iterable[Instruction]: Ry instruction. @@ -612,10 +593,10 @@ class Rz(AngledGate): """Z-axis rotation gate. Args: - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameter, float]): + def __init__(self, angle: Union[FreeParameterExpression, float]): super().__init__( angle=angle, qubit_count=None, @@ -631,17 +612,6 @@ def to_matrix(self) -> np.ndarray: ) def bind_values(self, **kwargs): - """ - Takes in parameters and attempts to assign them to values. - - Args: - **kwargs: The parameters that are being assigned. - - Returns: - Gate.Rz: A new Gate of the same type with the requested - parameters bound. - - """ return get_angle(self, **kwargs) @staticmethod @@ -650,12 +620,14 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def rz(target: QubitInput, angle: Union[FreeParameter, float]) -> Iterable[Instruction]: + def rz( + target: QubitInput, angle: Union[FreeParameterExpression, float] + ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (Qubit or int): Target qubit index. - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. Returns: Iterable[Instruction]: Rz instruction. @@ -673,10 +645,10 @@ class PhaseShift(AngledGate): """Phase shift gate. Args: - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: float): + def __init__(self, angle: Union[FreeParameterExpression, float]): super().__init__( angle=angle, qubit_count=None, @@ -690,17 +662,6 @@ def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, np.exp(1j * self.angle)]], dtype=complex) def bind_values(self, **kwargs): - """ - Takes in parameters and attempts to assign them to values. - - Args: - **kwargs: The parameters that are being assigned. - - Returns: - Gate.PhaseShift: A new Gate of the same type with the requested - parameters bound. - - """ return get_angle(self, **kwargs) @staticmethod @@ -709,12 +670,14 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def phaseshift(target: QubitInput, angle: Union[FreeParameter, float]) -> Iterable[Instruction]: + def phaseshift( + target: QubitInput, angle: Union[FreeParameterExpression, float] + ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (Qubit or int): Target qubit index. - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. Returns: Iterable[Instruction]: PhaseShift instruction. @@ -879,10 +842,10 @@ class PSwap(AngledGate): """PSwap gate. Args: - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: float): + def __init__(self, angle: Union[FreeParameterExpression, float]): super().__init__( angle=angle, qubit_count=None, @@ -907,17 +870,6 @@ def to_matrix(self) -> np.ndarray: ) def bind_values(self, **kwargs): - """ - Takes in parameters and attempts to assign them to values. - - Args: - kwargs: The parameters that are being assigned. - - Returns: - Gate.PSwap: A new Gate of the same type with the requested - parameters bound. - - """ return get_angle(self, **kwargs) @staticmethod @@ -932,7 +884,7 @@ def pswap(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction Args: target1 (Qubit or int): Target qubit 1 index. target2 (Qubit or int): Target qubit 2 index. - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. Returns: Instruction: PSwap instruction. @@ -952,10 +904,10 @@ class XY(AngledGate): Reference: https://arxiv.org/abs/1912.04424v1 Args: - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: float): + def __init__(self, angle: Union[FreeParameterExpression, float]): super().__init__( angle=angle, qubit_count=None, @@ -982,17 +934,6 @@ def to_matrix(self) -> np.ndarray: ) def bind_values(self, **kwargs): - """ - Takes in parameters and attempts to assign them to values. - - Args: - kwargs: The parameters that are being assigned. - - Returns: - Gate.XY: A new Gate of the same type with the requested - parameters bound. - - """ return get_angle(self, **kwargs) @staticmethod @@ -1002,14 +943,14 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def xy( - target1: QubitInput, target2: QubitInput, angle: Union[FreeParameter, float] + target1: QubitInput, target2: QubitInput, angle: Union[FreeParameterExpression, float] ) -> Instruction: """Registers this function into the circuit class. Args: target1 (Qubit or int): Target qubit 1 index. target2 (Qubit or int): Target qubit 2 index. - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. Returns: Instruction: XY instruction. @@ -1027,10 +968,10 @@ class CPhaseShift(AngledGate): """Controlled phase shift gate. Args: - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: float): + def __init__(self, angle: Union[FreeParameterExpression, float]): super().__init__( angle=angle, qubit_count=None, @@ -1044,17 +985,6 @@ def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, 1.0, np.exp(1j * self.angle)]) def bind_values(self, **kwargs): - """ - Takes in parameters and attempts to assign them to values. - - Args: - **kwargs: The parameters that are being assigned. - - Returns: - Gate.CPhaseShift: A new Gate of the same type with the requested - parameters bound. - - """ return get_angle(self, **kwargs) @staticmethod @@ -1064,14 +994,14 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def cphaseshift( - control: QubitInput, target: QubitInput, angle: Union[FreeParameter, float] + control: QubitInput, target: QubitInput, angle: Union[FreeParameterExpression, float] ) -> Instruction: """Registers this function into the circuit class. Args: control (Qubit or int): Control qubit index. target (Qubit or int): Target qubit index. - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. Returns: Instruction: CPhaseShift instruction. @@ -1089,10 +1019,10 @@ class CPhaseShift00(AngledGate): """Controlled phase shift gate for phasing the \\|00> state. Args: - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: float): + def __init__(self, angle: Union[FreeParameterExpression, float]): super().__init__( angle=angle, qubit_count=None, @@ -1106,17 +1036,6 @@ def to_matrix(self) -> np.ndarray: return np.diag([np.exp(1j * self.angle), 1.0, 1.0, 1.0]) def bind_values(self, **kwargs): - """ - Takes in parameters and attempts to assign them to values. - - Args: - **kwargs: The parameters that are being assigned. - - Returns: - Gate.CPhaseShift00: A new Gate of the same type with the requested - parameters bound. - - """ return get_angle(self, **kwargs) @staticmethod @@ -1126,14 +1045,14 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def cphaseshift00( - control: QubitInput, target: QubitInput, angle: Union[FreeParameter, float] + control: QubitInput, target: QubitInput, angle: Union[FreeParameterExpression, float] ) -> Instruction: """Registers this function into the circuit class. Args: control (Qubit or int): Control qubit index. target (Qubit or int): Target qubit index. - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. Returns: Instruction: CPhaseShift00 instruction. @@ -1151,10 +1070,10 @@ class CPhaseShift01(AngledGate): """Controlled phase shift gate for phasing the \\|01> state. Args: - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: float): + def __init__(self, angle: Union[FreeParameterExpression, float]): super().__init__( angle=angle, qubit_count=None, @@ -1168,17 +1087,6 @@ def to_matrix(self) -> np.ndarray: return np.diag([1.0, np.exp(1j * self.angle), 1.0, 1.0]) def bind_values(self, **kwargs): - """ - Takes in parameters and attempts to assign them to values. - - Args: - **kwargs: The parameters that are being assigned. - - Returns: - Gate.CPhaseShift01: A new Gate of the same type with the requested - parameters bound. - - """ return get_angle(self, **kwargs) @staticmethod @@ -1188,14 +1096,14 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def cphaseshift01( - control: QubitInput, target: QubitInput, angle: Union[FreeParameter, float] + control: QubitInput, target: QubitInput, angle: Union[FreeParameterExpression, float] ) -> Instruction: """Registers this function into the circuit class. Args: control (Qubit or int): Control qubit index. target (Qubit or int): Target qubit index. - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. Returns: Instruction: CPhaseShift01 instruction. @@ -1213,10 +1121,10 @@ class CPhaseShift10(AngledGate): """Controlled phase shift gate for phasing the \\|10> state. Args: - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: float): + def __init__(self, angle: Union[FreeParameterExpression, float]): super().__init__( angle=angle, qubit_count=None, @@ -1230,17 +1138,6 @@ def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, np.exp(1j * self.angle), 1.0]) def bind_values(self, **kwargs): - """ - Takes in parameters and attempts to assign them to values. - - Args: - **kwargs: The parameters that are being assigned. - - Returns: - Gate.CPhaseShift10: A new Gate of the same type with the requested - parameters bound. - - """ return get_angle(self, **kwargs) @staticmethod @@ -1250,14 +1147,14 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def cphaseshift10( - control: QubitInput, target: QubitInput, angle: Union[FreeParameter, float] + control: QubitInput, target: QubitInput, angle: Union[FreeParameterExpression, float] ) -> Instruction: """Registers this function into the circuit class. Args: control (Qubit or int): Control qubit index. target (Qubit or int): Target qubit index. - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. Returns: Instruction: CPhaseShift10 instruction. @@ -1460,10 +1357,10 @@ class XX(AngledGate): Reference: https://arxiv.org/abs/1707.06356 Args: - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: float): + def __init__(self, angle: Union[FreeParameterExpression, float]): super().__init__( angle=angle, qubit_count=None, @@ -1490,17 +1387,6 @@ def to_matrix(self) -> np.ndarray: ) def bind_values(self, **kwargs): - """ - Takes in parameters and attempts to assign them to values. - - Args: - **kwargs: The parameters that are being assigned. - - Returns: - Gate.XX: A new Gate of the same type with the requested - parameters bound. - - """ return get_angle(self, **kwargs) @staticmethod @@ -1510,14 +1396,14 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def xx( - target1: QubitInput, target2: QubitInput, angle: Union[FreeParameter, float] + target1: QubitInput, target2: QubitInput, angle: Union[FreeParameterExpression, float] ) -> Instruction: """Registers this function into the circuit class. Args: target1 (Qubit or int): Target qubit 1 index. target2 (Qubit or int): Target qubit 2 index. - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. Returns: Instruction: XX instruction. @@ -1537,10 +1423,10 @@ class YY(AngledGate): Reference: https://arxiv.org/abs/1707.06356 Args: - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: float): + def __init__(self, angle: Union[FreeParameterExpression, float]): super().__init__( angle=angle, qubit_count=None, @@ -1567,17 +1453,6 @@ def to_matrix(self) -> np.ndarray: ) def bind_values(self, **kwargs): - """ - Takes in parameters and attempts to assign them to values. - - Args: - **kwargs: The parameters that are being assigned. - - Returns: - Gate.YY: A new Gate of the same type with the requested - parameters bound. - - """ return get_angle(self, **kwargs) @staticmethod @@ -1587,14 +1462,14 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def yy( - target1: QubitInput, target2: QubitInput, angle: Union[FreeParameter, float] + target1: QubitInput, target2: QubitInput, angle: Union[FreeParameterExpression, float] ) -> Instruction: """Registers this function into the circuit class. Args: target1 (Qubit or int): Target qubit 1 index. target2 (Qubit or int): Target qubit 2 index. - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. Returns: Instruction: YY instruction. @@ -1614,10 +1489,10 @@ class ZZ(AngledGate): Reference: https://arxiv.org/abs/1707.06356 Args: - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: float): + def __init__(self, angle: Union[FreeParameterExpression, float]): super().__init__( angle=angle, qubit_count=None, @@ -1642,17 +1517,6 @@ def to_matrix(self) -> np.ndarray: ) def bind_values(self, **kwargs): - """ - Takes in parameters and attempts to assign them to values. - - Args: - **kwargs: The parameters that are being assigned. - - Returns: - Gate.ZZ: A new Gate of the same type with the requested - parameters bound. - - """ return get_angle(self, **kwargs) @staticmethod @@ -1662,14 +1526,14 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def zz( - target1: QubitInput, target2: QubitInput, angle: Union[FreeParameter, float] + target1: QubitInput, target2: QubitInput, angle: Union[FreeParameterExpression, float] ) -> Instruction: """Registers this function into the circuit class. Args: target1 (Qubit or int): Target qubit 1 index. target2 (Qubit or int): Target qubit 2 index. - angle (Union[FreeParameter, float]): angle in radians. + angle (Union[FreeParameterExpression, float]): angle in radians. Returns: Instruction: ZZ instruction. @@ -1869,13 +1733,13 @@ def unitary(targets: QubitSet, matrix: np.ndarray, display_name: str = "U") -> I Gate.register_gate(Unitary) -def angled_ascii_characters(gate: str, angle: Union[FreeParameter, float]) -> str: +def angled_ascii_characters(gate: str, angle: Union[FreeParameterExpression, float]) -> str: """ Generates a formatted ascii representation of an angled gate. Args: gate (str): The name of the gate. - angle (Union[FreeParameter, float]): The angle for the gate. + angle (Union[FreeParameterExpression, float]): The angle for the gate. Returns: str: Returns the ascii representation for an angled gate. @@ -1894,7 +1758,6 @@ def get_angle(self, **kwargs): Returns: A new gate of the type of the AngledGate originally used with all angles updated. - """ new_angle = ( self.angle.subs(kwargs) if isinstance(self.angle, FreeParameterExpression) else self.angle diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py index 303080aa..582e8e91 100644 --- a/src/braket/circuits/noise.py +++ b/src/braket/circuits/noise.py @@ -11,8 +11,13 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import Any, Dict, Optional, Sequence +from __future__ import annotations +from typing import Any, Dict, List, Optional, Sequence, Union + +from braket.circuits.free_parameter import FreeParameter +from braket.circuits.free_parameter_expression import FreeParameterExpression +from braket.circuits.parameterizable import Parameterizable from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.qubit_set import QubitSet @@ -72,13 +77,21 @@ def to_matrix(self, *args, **kwargs) -> Any: def __eq__(self, other): if isinstance(other, Noise): return self.name == other.name - return NotImplemented + return False def __repr__(self): return f"{self.name}('qubit_count': {self.qubit_count})" @classmethod - def register_noise(cls, noise: "Noise"): + def from_dict(cls, noise: dict) -> Noise: + if "__class__" in noise: + noise_name = noise["__class__"] + noise_cls = getattr(cls, noise_name) + return noise_cls.from_dict(noise) + raise NotImplementedError + + @classmethod + def register_noise(cls, noise: Noise): """Register a noise implementation by adding it into the Noise class. Args: @@ -87,34 +100,38 @@ def register_noise(cls, noise: "Noise"): setattr(cls, noise.__name__, noise) -class SingleProbabilisticNoise(Noise): +class SingleProbabilisticNoise(Noise, Parameterizable): """ Class `SingleProbabilisticNoise` represents the bit/phase flip noise channel on N qubits parameterized by a single probability. """ def __init__( - self, probability: float, qubit_count: Optional[int], ascii_symbols: Sequence[str] + self, + probability: Union[FreeParameterExpression, float], + qubit_count: Optional[int], + ascii_symbols: Sequence[str], + max_probability: float = 0.5, ): """ Args: - probability (float): The probability that the noise occurs. + probability (Union[FreeParameterExpression, float]): The probability that the + noise occurs. qubit_count (int, optional): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when 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. + max_probability (float): Maximum allowed probability of the noise channel. Default: 0.5 Raises: ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or - `ascii_symbols` length != `qubit_count`, `probability` is not `float`, - `probability` > 1/2, or `probability` < 0 + `ascii_symbols` length != `qubit_count`, `probability` is not `float` or + `FreeParameterExpression`, `probability` > 1/2, or `probability` < 0 """ super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) - if not isinstance(probability, float): - raise TypeError("probability must be float type") - if not (probability <= 0.5 and probability >= 0.0): - raise ValueError("probability must be a real number in the interval [0,1/2]") + if not isinstance(probability, FreeParameterExpression): + _validate_param_value(probability, "probability", max_probability) self._probability = probability @property @@ -128,19 +145,74 @@ def probability(self) -> float: def __repr__(self): return f"{self.name}('probability': {self.probability}, 'qubit_count': {self.qubit_count})" + def __str__(self): + return f"{self.name}({self.probability})" + + @property + def parameters(self) -> List[Union[FreeParameterExpression, float]]: + """ + Returns the parameters associated with the object, either unbound free parameter expressions + or bound values. + + Returns: + List[Union[FreeParameterExpression, float]]: The free parameter expressions + or fixed values associated with the object. + """ + return [self._probability] + + def __eq__(self, other): + if isinstance(other, type(self)): + 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. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + SingleProbabilisticNoise: A new Noise object of the same type with the requested + parameters bound. + + Raises: + NotImplementedError: Subclasses should implement this function. + """ + raise NotImplementedError + + def to_dict(self) -> dict: + """ + 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. + """ + return { + "__class__": self.__class__.__name__, + "probability": _parameter_to_dict(self.probability), + "qubit_count": self.qubit_count, + "ascii_symbols": self.ascii_symbols, + } + -class SingleProbabilisticNoise_34(Noise): +class SingleProbabilisticNoise_34(SingleProbabilisticNoise): """ Class `SingleProbabilisticNoise` represents the Depolarizing and TwoQubitDephasing noise channels parameterized by a single probability. """ def __init__( - self, probability: float, qubit_count: Optional[int], ascii_symbols: Sequence[str] + self, + probability: Union[FreeParameterExpression, float], + qubit_count: Optional[int], + ascii_symbols: Sequence[str], ): """ Args: - probability (float): The probability that the noise occurs. + probability (Union[FreeParameterExpression, float]): The probability that the + noise occurs. qubit_count (int, optional): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when printing a diagram of a circuit. The length must be the same as `qubit_count`, and @@ -148,41 +220,33 @@ def __init__( Raises: ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or - `ascii_symbols` length != `qubit_count`, `probability` is not `float`, - `probability` > 3/4, or `probability` < 0 - """ - super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) - - if not isinstance(probability, float): - raise TypeError("probability must be float type") - if not (probability <= 0.75 and probability >= 0.0): - raise ValueError("probability must be a real number in the interval [0,3/4]") - self._probability = probability - - @property - def probability(self) -> float: - """ - Returns: - probability (float): The probability that parametrizes the noise channel. + `ascii_symbols` length != `qubit_count`, `probability` is not `float` or + `FreeParameterExpression`, `probability` > 3/4, or `probability` < 0 """ - return self._probability - - def __repr__(self): - return f"{self.name}('probability': {self.probability}, 'qubit_count': {self.qubit_count})" + super().__init__( + probability=probability, + qubit_count=qubit_count, + ascii_symbols=ascii_symbols, + max_probability=0.75, + ) -class SingleProbabilisticNoise_1516(Noise): +class SingleProbabilisticNoise_1516(SingleProbabilisticNoise): """ Class `SingleProbabilisticNoise` represents the TwoQubitDepolarizing noise channel parameterized by a single probability. """ def __init__( - self, probability: float, qubit_count: Optional[int], ascii_symbols: Sequence[str] + self, + probability: Union[FreeParameterExpression, float], + qubit_count: Optional[int], + ascii_symbols: Sequence[str], ): """ Args: - probability (float): The probability that the noise occurs. + probability (Union[FreeParameterExpression, float]): The probability that the + noise occurs. qubit_count (int, optional): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when printing a diagram of a circuit. The length must be the same as `qubit_count`, and @@ -190,30 +254,18 @@ def __init__( Raises: ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or - `ascii_symbols` length != `qubit_count`, `probability` is not `float`, - `probability` > 15/16, or `probability` < 0 - """ - super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) - - if not isinstance(probability, float): - raise TypeError("probability must be float type") - if not (probability <= 0.9375 and probability >= 0.0): - raise ValueError("probability must be a real number in the interval [0,15/16]") - self._probability = probability - - @property - def probability(self) -> float: - """ - Returns: - probability (float): The probability that parametrizes the noise channel. + `ascii_symbols` length != `qubit_count`, `probability` is not `float` or + `FreeParameterExpression`, `probability` > 15/16, or `probability` < 0 """ - return self._probability - - def __repr__(self): - return f"{self.name}('probability': {self.probability}, 'qubit_count': {self.qubit_count})" + super().__init__( + probability=probability, + qubit_count=qubit_count, + ascii_symbols=ascii_symbols, + max_probability=0.9375, + ) -class MultiQubitPauliNoise(Noise): +class MultiQubitPauliNoise(Noise, Parameterizable): """ Class `MultiQubitPauliNoise` represents a general multi-qubit Pauli channel, parameterized by up to 4**N - 1 probabilities. @@ -223,34 +275,37 @@ class MultiQubitPauliNoise(Noise): def __init__( self, - probabilities: Dict[str, float], + probabilities: Dict[str, Union[FreeParameterExpression, float]], qubit_count: Optional[int], ascii_symbols: Sequence[str], ): """[summary] Args: - probabilities (Dict[str, float]): A dictionary with Pauli string as the keys, - and the probabilities as values, i.e. {"XX": 0.1. "IZ": 0.2}. + probabilities (Dict[str, Union[FreeParameterExpression, float]]): A dictionary with + Pauli strings as keys and the probabilities as values, i.e. {"XX": 0.1. "IZ": 0.2}. qubit_count (Optional[int]): The number of qubits the Pauli noise acts on. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when 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: - ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or - `ascii_symbols` length != `qubit_count`. Also if `probabilities` are not `float`s, - any `probabilities` > 1, or `probabilities` < 0, or if the sum of all - probabilities is > 1, - or if "II" is specified as a Pauli string. - Also if any Pauli string contains invalid strings. - Also if the length of probabilities is greater than 4**qubit_count. + ValueError: If + `qubit_count` < 1, + `ascii_symbols` is `None`, + `ascii_symbols` length != `qubit_count`, + `probabilities` are not `float`s or FreeParameterExpressions, + any of `probabilities` > 1 or `probabilities` < 0, + the sum of all probabilities is > 1, + if "II" is specified as a Pauli string, + if any Pauli string contains invalid strings, + or if the length of probabilities is greater than 4**qubit_count. 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 + self._probabilities = probabilities if not probabilities: raise ValueError("Pauli dictionary must not be empty.") @@ -261,39 +316,15 @@ def __init__( f"{identity} is not allowed as a key. Please enter only non-identity Pauli strings." ) + total_prob = 0 for pauli_string, prob in probabilities.items(): - if not isinstance(pauli_string, str): - raise TypeError(f"Type of {pauli_string} was not a string.") - if len(pauli_string) != self.qubit_count: - raise ValueError( - ( - "Length of each Pauli string must be equal to number of qubits. " - f"{pauli_string} had length {len(pauli_string)} instead of length {self.qubit_count}." # noqa - ) - ) - if not isinstance(prob, float): - raise TypeError( - ( - "Probabilities must be a float type. " - f"The probability for {pauli_string} was of type {type(prob)}." - ) - ) - if not set(pauli_string) <= self._allowed_substrings: - raise ValueError( - ( - "Strings must be Pauli strings consisting of only [I, X, Y, Z]. " - f"Received {pauli_string}." - ) - ) - if prob < 0.0 or prob > 1.0: - raise ValueError( - ( - "Individual probabilities must be real numbers in the interval [0, 1]. " - f"Probability for {pauli_string} was {prob}." - ) - ) - total_prob = sum(probabilities.values()) - if total_prob > 1.0 or total_prob < 0.0: + MultiQubitPauliNoise._validate_pauli_string( + pauli_string, self.qubit_count, self._allowed_substrings + ) + if not isinstance(prob, FreeParameterExpression): + _validate_param_value(prob, f"probability for {pauli_string}") + 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]. " @@ -301,11 +332,98 @@ def __init__( ) ) + @classmethod + def _validate_pauli_string(cls, pauli_str, qubit_count, allowed_substrings): + if not isinstance(pauli_str, str): + 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}." + ) + ) + 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}." + ) + ) + def __repr__(self): - return f"{self.name}('probabilities' : {self.probabilities}, 'qubit_count': {self.qubit_count})" # noqa + return ( + f"{self.name}('probabilities' : {self._probabilities}, " + f"'qubit_count': {self.qubit_count})" + ) + + def __str__(self): + return f"{self.name}({self._probabilities})" + + def __eq__(self, other): + if isinstance(other, type(self)): + return self.name == other.name and self._probabilities == other._probabilities + return False + + @property + def probabilities(self) -> Dict[str, float]: + """ + Dict[str, float]: A map of a Pauli string to its corresponding probability. + """ + return self._probabilities + + @property + def parameters(self) -> List[Union[FreeParameterExpression, float]]: + """ + 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`. + + Returns: + List[Union[FreeParameterExpression, float]]: The free parameter expressions + or fixed values associated with the object. + """ + return [ + self._probabilities[pauli_string] for pauli_string in sorted(self._probabilities.keys()) + ] + + def bind_values(self, **kwargs) -> MultiQubitPauliNoise: + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + MultiQubitPauliNoise: A new Noise object of the same type with the requested + parameters bound. + + Raises: + NotImplementedError: Subclasses should implement this function. + """ + raise NotImplementedError + + def to_dict(self) -> dict: + """ + 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() + for pauli_string, prob in self._probabilities.items(): + probabilities[pauli_string] = _parameter_to_dict(prob) + return { + "__class__": self.__class__.__name__, + "probabilities": probabilities, + "qubit_count": self.qubit_count, + "ascii_symbols": self.ascii_symbols, + } -class PauliNoise(Noise): +class PauliNoise(Noise, Parameterizable): """ Class `PauliNoise` represents the a single-qubit Pauli noise channel acting on one qubit. It is parameterized by three probabilities. @@ -313,15 +431,19 @@ class PauliNoise(Noise): def __init__( self, - probX: float, - probY: float, - probZ: float, + probX: Union[FreeParameterExpression, float], + probY: Union[FreeParameterExpression, float], + probZ: Union[FreeParameterExpression, float], qubit_count: Optional[int], ascii_symbols: Sequence[str], ): """ Args: - probX [float], probY [float], probZ [float]: The coefficients of the Kraus operators + probX Union[FreeParameterExpression, float]: The X coefficient of the Kraus operators + in the channel. + probY Union[FreeParameterExpression, float]: The Y coefficient of the Kraus operators + in the channel. + probZ Union[FreeParameterExpression, float]: The Z coefficient of the Kraus operators in the channel. qubit_count (int, optional): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when @@ -331,85 +453,160 @@ def __init__( Raises: ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or `ascii_symbols` length != `qubit_count`, `probX` or `probY` or `probZ` - is not `float`, `probX` or `probY` or `probZ` > 1.0, or + is not `float` or FreeParameterExpression, `probX` or `probY` or `probZ` > 1.0, or `probX` or `probY` or `probZ` < 0.0, or `probX`+`probY`+`probZ` > 1 """ super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) - if not isinstance(probX, float): - raise TypeError("probX must be float type") - if not (probX <= 1.0 and probX >= 0.0): - raise ValueError("probX must be a real number in the interval [0,1]") - if not isinstance(probY, float): - raise TypeError("probY must be float type") - if not (probY <= 1.0 and probY >= 0.0): - raise ValueError("probY must be a real number in the interval [0,1]") - if not isinstance(probZ, float): - raise TypeError("probZ must be float type") - if not (probZ <= 1.0 and probZ >= 0.0): - raise ValueError("probZ must be a real number in the interval [0,1]") - if probX + probY + probZ > 1: + total = 0 + total += PauliNoise._get_param_float(probX, "probX") + total += PauliNoise._get_param_float(probY, "probY") + total += PauliNoise._get_param_float(probZ, "probZ") + if total > 1: raise ValueError("the sum of probX, probY, probZ cannot be larger than 1") + self._parameters = [probX, probY, probZ] + + @staticmethod + def _get_param_float(param: Union[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. + + Args: + param (Union[FreeParameterExpression, float]): The probability to validate + param_name (str): The name of the probability parameter - self._probX = probX - self._probY = probY - self._probZ = probZ + Returns: + float: The value of the parameter, or 0 if it is a free parameter expression + """ + if isinstance(param, FreeParameterExpression): + return 0 + else: + _validate_param_value(param, param_name) + return float(param) @property - def probX(self) -> float: + def probX(self) -> Union[FreeParameterExpression, float]: """ Returns: - probX (float): The probability of a Pauli X error. + Union[FreeParameterExpression, float]: The probability of a Pauli X error. """ - return self._probX + return self._parameters[0] @property - def probY(self) -> float: + def probY(self) -> Union[FreeParameterExpression, float]: """ Returns: - probY (float): The probability of a Pauli Y error. + Union[FreeParameterExpression, float]: The probability of a Pauli Y error. """ - return self._probY + return self._parameters[1] @property - def probZ(self) -> float: + def probZ(self) -> Union[FreeParameterExpression, float]: """ Returns: - probZ (float): The probability of a Pauli Z error. + Union[FreeParameterExpression, float]: The probability of a Pauli Z error. """ - return self._probZ + return self._parameters[2] def __repr__(self): - return f"{self.name}('probX': {self.probX}, 'probY': {self.probY}, \ -'probZ': {self.probZ}, 'qubit_count': {self.qubit_count})" + return ( + f"{self.name}(" + f"'probX': {self._parameters[0]}, " + f"'probY': {self._parameters[1]}, " + f"'probZ': {self._parameters[2]}, " + f"'qubit_count': {self.qubit_count}" + f")" + ) + + 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)): + 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. + + Parameters are in the order [probX, probY, probZ] + + Returns: + List[Union[FreeParameterExpression, float]]: The free parameter expressions + or fixed values associated with the object. + """ + return self._parameters + + def bind_values(self, **kwargs) -> PauliNoise: + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + PauliNoise: A new Noise object of the same type with the requested + parameters bound. + + Raises: + NotImplementedError: Subclasses should implement this function. + """ + raise NotImplementedError + + def to_dict(self) -> dict: + """ + 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. + """ + return { + "__class__": self.__class__.__name__, + "probX": _parameter_to_dict(self.probX), + "probY": _parameter_to_dict(self.probY), + "probZ": _parameter_to_dict(self.probZ), + "qubit_count": self.qubit_count, + "ascii_symbols": self.ascii_symbols, + } -class DampingNoise(Noise): +class DampingNoise(Noise, Parameterizable): """ Class `DampingNoise` represents a damping noise channel on N qubits parameterized by gamma. """ - def __init__(self, gamma: float, qubit_count: Optional[int], ascii_symbols: Sequence[str]): + def __init__( + self, + gamma: Union[FreeParameterExpression, float], + qubit_count: Optional[int], + ascii_symbols: Sequence[str], + ): """ Args: - gamma (float): Probability of damping. + gamma (Union[FreeParameterExpression, float]): Probability of damping. qubit_count (int, optional): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when 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: - ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or - `ascii_symbols` length != `qubit_count`, `gamma` is not `float`, - `gamma` > 1.0, or `gamma` < 0.0. + ValueError: If + `qubit_count` < 1, + `ascii_symbols` is `None`, + `len(ascii_symbols)` != `qubit_count`, + `gamma` is not `float` or `FreeParameterExpression`, + or `gamma` > 1.0 or `gamma` < 0.0. """ super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) - if not isinstance(gamma, float): - raise TypeError("gamma must be float type") - if not (gamma <= 1.0 and gamma >= 0.0): - raise ValueError("gamma must be a real number in the interval [0,1]") + if not isinstance(gamma, FreeParameterExpression): + _validate_param_value(gamma, "gamma") self._gamma = gamma @property @@ -423,6 +620,57 @@ def gamma(self) -> float: def __repr__(self): return f"{self.name}('gamma': {self.gamma}, 'qubit_count': {self.qubit_count})" + def __str__(self): + return f"{self.name}({self.gamma})" + + @property + def parameters(self) -> List[Union[FreeParameterExpression, float]]: + """ + Returns the parameters associated with the object, either unbound free parameter expressions + or bound values. + + Returns: + List[Union[FreeParameterExpression, float]]: The free parameter expressions + or fixed values associated with the object. + """ + return [self._gamma] + + def __eq__(self, other): + if isinstance(other, type(self)): + 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. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + DampingNoise: A new Noise object of the same type with the requested + parameters bound. + + Raises: + NotImplementedError: Subclasses should implement this function. + """ + raise NotImplementedError + + def to_dict(self) -> dict: + """ + 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. + """ + return { + "__class__": self.__class__.__name__, + "gamma": _parameter_to_dict(self.gamma), + "qubit_count": self.qubit_count, + "ascii_symbols": self.ascii_symbols, + } + class GeneralizedAmplitudeDampingNoise(DampingNoise): """ @@ -432,31 +680,34 @@ class GeneralizedAmplitudeDampingNoise(DampingNoise): def __init__( self, - gamma: float, - probability: float, + gamma: Union[FreeParameterExpression, float], + probability: Union[FreeParameterExpression, float], qubit_count: Optional[int], ascii_symbols: Sequence[str], ): """ Args: - gamma (float): Probability of damping. - probability (float): Probability of the system being excited by the environment. + gamma (Union[FreeParameterExpression, float]): Probability of damping. + probability (Union[FreeParameterExpression, float]): Probability of the system being + excited by the environment. qubit_count (int): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when 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: - ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or - `ascii_symbols` length != `qubit_count`, `probability` or `gamma` is not `float`, - `probability` > 1.0, or `probability` < 0.0, `gamma` > 1.0, or `gamma` < 0.0. + ValueError: If + `qubit_count` < 1, + `ascii_symbols` is `None`, + `len(ascii_symbols)` != `qubit_count`, + `probability` or `gamma` is not `float` or `FreeParameterExpression`, + `probability` > 1.0 or `probability` < 0.0, + or `gamma` > 1.0 or `gamma` < 0.0. """ super().__init__(gamma=gamma, qubit_count=qubit_count, ascii_symbols=ascii_symbols) - if not isinstance(probability, float): - raise TypeError("probability must be float type") - if not (probability <= 1.0 and probability >= 0.0): - raise ValueError("probability must be a real number in the interval [0,1]") + if not isinstance(probability, FreeParameterExpression): + _validate_param_value(probability, "probability") self._probability = probability @property @@ -468,5 +719,81 @@ def probability(self) -> float: return self._probability def __repr__(self): - return f"{self.name}('gamma': {self.gamma}, 'probability': {self.probability}, \ -'qubit_count': {self.qubit_count})" + return ( + f"{self.name}('gamma': {self.gamma}, " + f"'probability': {self.probability}, " + f"'qubit_count': {self.qubit_count})" + ) + + def __str__(self): + return f"{self.name}({self.gamma}, {self.probability})" + + @property + def parameters(self) -> List[Union[FreeParameterExpression, float]]: + """ + Returns the parameters associated with the object, either unbound free parameter expressions + or bound values. + + Parameters are in the order [gamma, probability] + + Returns: + List[Union[FreeParameterExpression, float]]: The free parameter expressions + or fixed values associated with the object. + """ + return [self.gamma, self.probability] + + def __eq__(self, other): + if isinstance(other, type(self)): + return ( + self.name == other.name + and self.gamma == other.gamma + and self.probability == other.probability + ) + return False + + def to_dict(self) -> dict: + """ + 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. + """ + return { + "__class__": self.__class__.__name__, + "gamma": _parameter_to_dict(self.gamma), + "probability": _parameter_to_dict(self.probability), + "qubit_count": self.qubit_count, + "ascii_symbols": self.ascii_symbols, + } + + +def _validate_param_value( + parameter: Union[FreeParameterExpression, float], param_name: str, maximum: float = 1.0 +) -> None: + """Validates the value of a given parameter. + + Args: + parameter (Union[FreeParameterExpression, float]): The parameter to validate + param_name (str): The name of the parameter + maximum (float): The maximum value of the parameter. Default: 1.0 + """ + if not isinstance(parameter, float): + raise TypeError(f"{param_name} must be float type") + if not (maximum >= parameter >= 0.0): + 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]: + """Converts a parameter to a dictionary if it's a FreeParameter, otherwise returns the float. + + Args: + parameter(Union[FreeParameter, float]): The parameter to convert. + + Returns: + A dictionary representation of a FreeParameter if the parameter is a FreeParameter, + otherwise returns the float. + """ + if isinstance(parameter, FreeParameter): + return parameter.to_dict() + return parameter diff --git a/src/braket/circuits/noise_model/__init__.py b/src/braket/circuits/noise_model/__init__.py new file mode 100644 index 00000000..717e5057 --- /dev/null +++ b/src/braket/circuits/noise_model/__init__.py @@ -0,0 +1,30 @@ +# 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.noise_model.circuit_instruction_criteria import ( # noqa: F401 + CircuitInstructionCriteria, +) +from braket.circuits.noise_model.criteria import ( # noqa: F401 + Criteria, + CriteriaKey, + CriteriaKeyResult, +) +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.observable_criteria import ObservableCriteria # noqa: F401 +from braket.circuits.noise_model.qubit_initialization_criteria import ( # noqa: F401 + QubitInitializationCriteria, +) +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/circuit_instruction_criteria.py b/src/braket/circuits/noise_model/circuit_instruction_criteria.py new file mode 100644 index 00000000..2e592885 --- /dev/null +++ b/src/braket/circuits/noise_model/circuit_instruction_criteria.py @@ -0,0 +1,56 @@ +# 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 abc import abstractmethod +from typing import Optional, Set, Tuple, Union + +from braket.circuits.instruction import Instruction +from braket.circuits.noise_model.criteria import Criteria +from braket.circuits.qubit_set import QubitSetInput + + +class CircuitInstructionCriteria(Criteria): + """Criteria that implement these methods may be used to determine gate noise.""" + + @abstractmethod + def instruction_matches(self, instruction: Instruction) -> bool: + """Returns True if an Instruction matches the criteria. + + Args: + instruction (Instruction): An Instruction to match. + + Returns: + bool: True if an Instruction matches the criteria. + """ + raise NotImplementedError + + @staticmethod + 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. + + Args: + qubits (Optional[Set[Union[int, Tuple[int]]]]): The qubits provided to the criteria. + target (QubitSetInput): Targets of an instruction. + + Returns: + bool: True if the provided target should be matched by the given 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 diff --git a/src/braket/circuits/noise_model/criteria.py b/src/braket/circuits/noise_model/criteria.py new file mode 100644 index 00000000..acbaf072 --- /dev/null +++ b/src/braket/circuits/noise_model/criteria.py @@ -0,0 +1,114 @@ +# 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 enum import Enum +from typing import Any, Iterable, Set, Type, Union + + +class CriteriaKey(str, Enum): + """ + Specifies the types of keys that a criteria may use to match an instruction, observable, etc. + """ + + QUBIT = "QUBIT" + GATE = "GATE" + UNITARY_GATE = "UNITARY_GATE" + OBSERVABLE = "OBSERVABLE" + + +class CriteriaKeyResult(str, Enum): + """ + The get_keys() method may return this enum instead of actual keys for + a given criteria key type. + """ + + ALL = "ALL" + + +class Criteria(ABC): + """Represents conditions that need to be met for a noise to apply to a circuit.""" + + @abstractmethod + def applicable_key_types(self) -> Iterable[CriteriaKey]: + """Returns the relevant set of keys for the Criteria + + This informs what the Criteria operates on and can be used to optimize + which Criteria is relevant. + + Returns: + Iterable[CriteriaKey]: The relevant set of keys for the Criteria. + """ + raise NotImplementedError + + @abstractmethod + def get_keys(self, key_type: CriteriaKey) -> Union[CriteriaKeyResult, Set[Any]]: + """Returns a set of key for a given key type. + + Args: + key_type (CriteriaKey): The criteria key type. + + Returns: + Union[CriteriaKeyResult, Set[Any]]: Returns a set of keys for a key type. The + actual returned keys will depend on the CriteriaKey. If the provided key type + is not relevant the returned list will be empty. If the provided key type is + relevant for all possible inputs, the string CriteriaKeyResult.ALL will be returned. + """ + raise NotImplementedError + + def __eq__(self, other: Criteria): + if not isinstance(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 + + @abstractmethod + def to_dict(self) -> dict: + """Converts this Criteria object into a dict representation + + Returns: + dict: A dictionary object representing the Criteria. + """ + raise NotImplementedError + + @classmethod + def from_dict(cls, criteria: dict) -> Criteria: + """ + 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. + + Returns: + Criteria: An object of this class that corresponds to the passed in dictionary. + """ + if "__class__" in criteria: + criteria_name = criteria["__class__"] + criteria_cls = getattr(Criteria, criteria_name) + return criteria_cls.from_dict(criteria) + raise NotImplementedError + + @classmethod + def register_criteria(cls, criteria: Type[Criteria]): + """Register a criteria implementation by adding it into the Criteria class. + + Args: + criteria (Type[Criteria]): Criteria class to register. + """ + setattr(cls, criteria.__name__, criteria) diff --git a/src/braket/circuits/noise_model/criteria_input_parsing.py b/src/braket/circuits/noise_model/criteria_input_parsing.py new file mode 100644 index 00000000..193525f3 --- /dev/null +++ b/src/braket/circuits/noise_model/criteria_input_parsing.py @@ -0,0 +1,90 @@ +# 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 typing import Iterable, Optional, Set, Tuple, Union + +from braket.circuits.quantum_operator import QuantumOperator +from braket.circuits.qubit_set import QubitSetInput + + +def parse_operator_input( + operators: Union[QuantumOperator, Iterable[QuantumOperator]] +) -> Optional[Set[QuantumOperator]]: + """ + Processes the quantum operator input to __init__ to validate and return a set of + QuantumOperators. + + Args: + operators (Union[QuantumOperator, Iterable[QuantumOperator]]): QuantumOperator input. + + Returns: + Optional[Set[QuantumOperator]]: The set of relevant QuantumOperators or None if none + is specified. + + Throws: + ValueError: If no quantum operator are provided, if the quantum operator don't all operate + on the same number of qubits. + """ + if not operators: + return None + if not isinstance(operators, Iterable): + return {operators} + fixed_qubit_counts = {operator.fixed_qubit_count() for operator in operators} + if len(fixed_qubit_counts) != 1: + raise ValueError("All operators in a criteria must operate on the same number of qubits.") + return set(operators) + + +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. + + Args: + qubits (Optional[QubitSetInput]): Qubit input. + expected_qubit_count (Optional[int]): The expected number of qubits that the input + gates operates on. If the value is non-zero, this method will validate that the + expected qubit count matches the actual qubit count. Default is 0. + + Returns: + Optional[Set[Union[int, Tuple[int]]]]: The set of qubit targets, or None if no qubits + are specified. + """ + if qubits is None: + return None + if not isinstance(qubits, Iterable): + return {int(qubits)} + if len(qubits) == 0: + return None + types = {type(item) for item in qubits} + if len(types) != 1: + raise TypeError("Qubit targets must be all the same type.") + qubit_count = ( + expected_qubit_count if expected_qubit_count is not None and expected_qubit_count > 0 else 1 + ) + if isinstance(qubits[0], Iterable): + qubit_count = ( + expected_qubit_count + if expected_qubit_count is not None and expected_qubit_count > 0 + else len(qubits[0]) + ) + target_set_all_same = all(len(item) == qubit_count for item in qubits) + if not target_set_all_same: + raise ValueError(f"Qubits must all target {qubit_count}-qubit operations.") + 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) diff --git a/src/braket/circuits/noise_model/gate_criteria.py b/src/braket/circuits/noise_model/gate_criteria.py new file mode 100644 index 00000000..3505391d --- /dev/null +++ b/src/braket/circuits/noise_model/gate_criteria.py @@ -0,0 +1,142 @@ +# 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 typing import Any, Iterable, Optional, Set, Union + +from braket.circuits.gate import Gate +from braket.circuits.instruction import Instruction +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_operator_input, + parse_qubit_input, +) +from braket.circuits.qubit_set import QubitSetInput + + +class GateCriteria(CircuitInstructionCriteria): + """This class models noise Criteria based on named Braket SDK Gates.""" + + def __init__( + self, + gates: Optional[Union[Gate, Iterable[Gate]]] = None, + qubits: Optional[QubitSetInput] = None, + ): + """ + 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 + must have the same fixed_qubit_count(). Optional. If gates are not provided + this matcher will match on all gates. + qubits (Optional[QubitSetInput]): A set of relevant qubits. If no qubits + are provided, all (possible) qubits are considered to be relevant. + + Raises: + ValueError: If the gates don't all operate on the same number of qubits, or if + qubits are not valid targets for the provided gates. + """ + self._gates = parse_operator_input(gates) + expected_qubit_count = next(iter(self._gates)).fixed_qubit_count() if self._gates else 0 + self._qubits = parse_qubit_input(qubits, expected_qubit_count) + + def __str__(self): + gate_names = {gate.__name__ for gate in self._gates} if self._gates is not None else None + return f"{self.__class__.__name__}({gate_names}, {self._qubits})" + + def __repr__(self): + gate_names = {gate.__name__ for gate in self._gates} if self._gates is not None else None + return f"{self.__class__.__name__}(gates={gate_names}, qubits={self._qubits})" + + def applicable_key_types(self) -> Iterable[CriteriaKey]: + """ + Returns: + Iterable[CriteriaKey]: This Criteria operates on Gates and Qubits. + """ + return [CriteriaKey.QUBIT, CriteriaKey.GATE] + + 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: + GATE will return a set of Gate classes that are relevant to this Criteria. + 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 list. + """ + if key_type == CriteriaKey.GATE: + return CriteriaKeyResult.ALL if self._gates is None else self._gates + 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 + gates = [gate.__name__ for gate in self._gates] if self._gates is not None else None + return { + "__class__": self.__class__.__name__, + "gates": gates, + "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 operator is one of the Gates provided in the constructor and + the target is a qubit (or set of qubits) provided in the constructor. + If gates were not provided in the constructor, then this method will accept any Gate. + If qubits were not provided in the constructor, then this method will accept any + Instruction target. + """ + if isinstance(instruction, Iterable): + return False + if not isinstance(instruction.operator, Gate): + return False + if self._gates is not None and type(instruction.operator) not in self._gates: + 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 GateCriteria. + + Returns: + Criteria: A deserialized GateCriteria represented by the passed in + serialized data. + """ + gates = ( + [getattr(Gate, name) for name in criteria["gates"]] + if criteria["gates"] is not None + else None + ) + return GateCriteria(gates, criteria["qubits"]) + + +Criteria.register_criteria(GateCriteria) diff --git a/src/braket/circuits/noise_model/initialization_criteria.py b/src/braket/circuits/noise_model/initialization_criteria.py new file mode 100644 index 00000000..6229573b --- /dev/null +++ b/src/braket/circuits/noise_model/initialization_criteria.py @@ -0,0 +1,36 @@ +# 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 abc import abstractmethod + +from braket.circuits.noise_model.criteria import Criteria +from braket.circuits.qubit_set import QubitSetInput + + +class InitializationCriteria(Criteria): + """ + 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. + + Args: + qubits (QubitSetInput): A qubit or set of qubits that may match the criteria. + + Returns: + QubitSetInput: The subset of passed qubits that match the criteria. + """ + raise NotImplementedError diff --git a/src/braket/circuits/noise_model/noise_model.py b/src/braket/circuits/noise_model/noise_model.py new file mode 100644 index 00000000..16ead6cb --- /dev/null +++ b/src/braket/circuits/noise_model/noise_model.py @@ -0,0 +1,403 @@ +# 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 defaultdict +from dataclasses import dataclass +from typing import List, Optional, Type + +from braket.circuits.circuit import Circuit +from braket.circuits.gate import Gate +from braket.circuits.instruction import Instruction +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.result_type_criteria import ResultTypeCriteria +from braket.circuits.qubit_set import QubitSetInput +from braket.circuits.result_types import ObservableResultType + + +@dataclass +class NoiseModelInstruction: + """Represents a single instruction for a Noise Model.""" + + noise: Noise + criteria: Criteria + + def __init__(self, noise: Noise, criteria: Criteria): + if not isinstance(noise, Noise): + raise ValueError(f"{noise} must be a Noise type.") + if not isinstance(criteria, Criteria): + raise ValueError(f"{criteria} must be a Criteria type.") + self.noise = noise + self.criteria = criteria + + def __repr__(self): + return f"NoiseModelInstruction(noise={self.noise}, criteria={self.criteria})" + + def __str__(self): + return f"{self.noise}, {self.criteria}" + + def to_dict(self) -> dict: + """Converts this object to a dictionary.""" + return {"noise": self.noise.to_dict(), "criteria": self.criteria.to_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. + + Args: + noise_model_item (dict): A dictionary representation of an object of this class. + + Returns: + NoiseModelInstruction: An object of this class that corresponds + to the passed in dictionary. + """ + return NoiseModelInstruction( + noise=Noise.from_dict(noise_model_item["noise"]), + criteria=Criteria.from_dict(noise_model_item["criteria"]), + ) + + +@dataclass +class NoiseModelInstructions: + """Represents the instructions in a noise model, seperated by type.""" + + initialization_noise: List[NoiseModelInstruction] + gate_noise: List[NoiseModelInstruction] + readout_noise: List[NoiseModelInstruction] + + +class NoiseModel: + """ + 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 + a phase flip. + """ + + def __init__(self, instructions: List[NoiseModelInstruction] = None): + self._instructions = instructions or [] + + def __repr__(self): + return str({"instructions": list(self._instructions)}) + + def __str__(self): + instructions = self.get_instructions_by_type() + all_strings = [] + all_strings.extend( + NoiseModel._items_to_string("Initialization Noise:", instructions.initialization_noise) + ) + all_strings.extend(NoiseModel._items_to_string("Gate Noise:", instructions.gate_noise)) + all_strings.extend( + NoiseModel._items_to_string("Readout Noise:", instructions.readout_noise) + ) + return "\n".join(all_strings) + + @property + def instructions(self) -> List[NoiseModelInstruction]: + """ + List all the noise in the NoiseModel. + + Returns: + List[NoiseModelInstruction]: The noise model instructions. + """ + return self._instructions + + def add_noise(self, noise: Noise, criteria: Criteria) -> NoiseModel: + """Modifies the NoiseModel to add noise with a given Criteria. + + Args: + noise (Noise): The noise to add. + criteria (Criteria): The criteria that determines when the noise will be applied. + + Returns: + NoiseModel: This NoiseModel object. + """ + return self._add_instruction(NoiseModelInstruction(noise, criteria)) + + def insert_noise(self, index: int, noise: Noise, criteria: Criteria) -> NoiseModel: + """Modifies the NoiseModel to insert a noise with a given Criteria at particular position. + + Args: + index (int): The index at which to insert. + noise (Noise): The noise to insert. + criteria (Criteria): The criteria that determines when the noise will be applied. + + Returns: + NoiseModel: This NoiseModel object. + """ + self._instructions.insert(index, NoiseModelInstruction(noise, criteria)) + return self + + def _add_instruction(self, instruction: NoiseModelInstruction) -> NoiseModel: + """Modifies the NoiseModel to add noise with a given Criteria. + + Args: + instruction (NoiseModelInstruction): The noise model instruction to add. + + Returns: + NoiseModel: This NoiseModel object. + """ + self._instructions.append(instruction) + return self + + def remove_noise(self, index: int) -> NoiseModel: + """ + Removes the noise and corresponding criteria from the NoiseModel at the given index. + + Args: + index (int): The index of the instruction to remove. + + Returns: + NoiseModel: This NoiseModel object. + + Throws: + IndexError: If the provided index is not less than the number of noise (as returned + from items()). + """ + self._instructions.pop(index) + return self + + def get_instructions_by_type(self) -> NoiseModelInstructions: + """Returns the noise in this model by noise type. + Returns: + NoiseModelInstructions: The noise model instructions. + """ + init_noise = [] + gate_noise = [] + readout_noise = [] + for item in self._instructions: + if isinstance(item.criteria, InitializationCriteria): + init_noise.append(item) + elif isinstance(item.criteria, CircuitInstructionCriteria): + gate_noise.append(item) + elif isinstance(item.criteria, ResultTypeCriteria): + readout_noise.append(item) + return NoiseModelInstructions( + initialization_noise=init_noise, + gate_noise=gate_noise, + readout_noise=readout_noise, + ) + + def from_filter( + self, + qubit: Optional[QubitSetInput] = None, + 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 + specified, the returned NoiseModel will be the same as this one. + + Args: + qubit (Optional[QubitSetInput]): The qubit to filter. Default is None. + If not None, the returned NoiseModel will only have Noise that might be applicable + to the passed qubit (or qubit list, if the criteria acts on a multi-qubit Gate). + gate (Optional[Gate]): The gate to filter. Default is None. If not None, + the returned NoiseModel will only have Noise that might be applicable + to the passed Gate. + noise (Optional[Type[Noise]]): The noise class to filter. Default is None. + If not None, the returned NoiseModel will only have noise that is of the same + class as the given noise class. + + Returns: + NoiseModel: A noise model containing Noise and Criteria that are applicable for + the given filter. + """ + new_model = NoiseModel() + for item in self._instructions: + if noise is not None and not isinstance(item.noise, noise): + continue + if gate is not None: + gate_keys = item.criteria.get_keys(CriteriaKey.GATE) + if gate_keys != CriteriaKeyResult.ALL and gate not in gate_keys: + continue + if qubit is not None: + qubit_keys = item.criteria.get_keys(CriteriaKey.QUBIT) + if qubit_keys != CriteriaKeyResult.ALL and qubit not in qubit_keys: + continue + new_model.add_noise(item.noise, item.criteria) + 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` + 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. + + Args: + circuit (Circuit): a circuit to apply `noise` to. + + Returns: + Circuit: A new circuit that's a `noisy` version of the passed in circuit. + """ + instructions = self.get_instructions_by_type() + new_circuit = NoiseModel._apply_gate_noise(circuit, instructions.gate_noise) + new_circuit = NoiseModel._apply_init_noise(new_circuit, instructions.initialization_noise) + return NoiseModel._apply_readout_noise(new_circuit, instructions.readout_noise) + + def to_dict(self) -> dict: + """Converts this object to a dictionary.""" + return {"instructions": [item.to_dict() for item in self._instructions]} + + @classmethod + def _apply_gate_noise( + cls, + 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 + circuit. + + Args: + circuit (Circuit): a circuit to apply `noise` to. + gate_noise_instructions (List[NoiseModelInstruction]): a list of gate noise + instructions to apply to the circuit. + + Returns: + Circuit: A new circuit that's a `noisy` version of the passed in circuit. The targets + set will be populated with the list of targets in the new circuit. + """ + 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)) + for result_type in circuit.result_types: + new_circuit.add_result_type(result_type) + return new_circuit + + @classmethod + def _apply_init_noise( + cls, + circuit: Circuit, + init_noise_instructions: List[NoiseModelInstruction], + ) -> 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. + init_noise_instructions (List[NoiseModelInstruction]): A list of initialization noise + model instructions. + + Returns: + Circuit: The passed in circuit, with the initialization noise applied. + """ + if not init_noise_instructions: + return circuit + for item in init_noise_instructions: + qubits = item.criteria.qubit_intersection(circuit.qubits) + if len(qubits) > 0: + circuit.apply_initialization_noise(item.noise, list(qubits)) + return circuit + + @classmethod + def _apply_readout_noise( + cls, + circuit: Circuit, + readout_noise_instructions: List[NoiseModelInstruction], + ) -> 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. + readout_noise_instructions (List[NoiseModelInstruction]): The list of readout noise + to apply. + + Returns: + Circuit: The passed in circuit, with the readout noise applied. + """ + if not readout_noise_instructions or not circuit.result_types: + return circuit + return _apply_noise_on_observable_result_types(circuit, readout_noise_instructions) + + @classmethod + def _items_to_string( + cls, instructions_title: str, instructions: List[NoiseModelInstruction] + ) -> List[str]: + """ + Creates a string representation of a list of instructions. + + Args: + instructions_title (str): The title for this list of instructions. + instructions (List[NoiseModelInstruction]): A list of instructions. + + Returns: + List[str]: A list of string representations of the passed instructions. + """ + results = [] + if len(instructions) > 0: + results.append(instructions_title) + for item in instructions: + results.append(f" {item}") + return results + + @classmethod + def from_dict(cls, noise_dict: dict) -> NoiseModel: + """ + 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. + + Returns: + NoiseModel: An object of this class that corresponds to the passed in dictionary. + """ + model = NoiseModel() + all( + model._add_instruction(NoiseModelInstruction.from_dict(item)) + for item in noise_dict["instructions"] + ) + return model + + +def _apply_noise_on_observable_result_types( + circuit: Circuit, readout_noise_instructions: List[NoiseModelInstruction] +) -> Circuit: + """Applies readout noise based on the observable result types in the circuit. Each applicable + Noise will be applied only once to a target in the ObservableResultType. + + Args: + circuit (Circuit): The circuit to apply the readout noise to. + + Returns: + Circuit: The passed in circuit, with the readout noise applied. + """ + result_types = circuit.result_types + noise_to_apply = defaultdict(set) + for result_type in 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): + for target_qubit in target_qubits: + noise_to_apply[target_qubit].add(item_index) + for qubit in noise_to_apply: + for noise_item_index in noise_to_apply[qubit]: + item = readout_noise_instructions[noise_item_index] + circuit.apply_readout_noise(item.noise, qubit) + return circuit diff --git a/src/braket/circuits/noise_model/observable_criteria.py b/src/braket/circuits/noise_model/observable_criteria.py new file mode 100644 index 00000000..e6affc94 --- /dev/null +++ b/src/braket/circuits/noise_model/observable_criteria.py @@ -0,0 +1,156 @@ +# 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 typing import Any, Iterable, Optional, Set, Union + +from braket.circuits.noise_model.criteria import Criteria, CriteriaKey, CriteriaKeyResult +from braket.circuits.noise_model.criteria_input_parsing import ( + parse_operator_input, + parse_qubit_input, +) +from braket.circuits.noise_model.result_type_criteria import ResultTypeCriteria +from braket.circuits.observable import Observable +from braket.circuits.qubit_set import QubitSetInput +from braket.circuits.result_type import ObservableResultType, ResultType + + +class ObservableCriteria(ResultTypeCriteria): + """This class models noise Criteria based on the Braket SDK Observable classes.""" + + def __init__( + self, + observables: Optional[Union[Observable, Iterable[Observable]]] = None, + qubits: Optional[QubitSetInput] = None, + ): + """ + Creates Observable-based Criteria. See instruction_matches() for more details. + + Args: + observables (Optional[Union[Observable, Iterable[Observable]]]): A set of relevant + Observables. Observables must only operate on a single qubit. Optional. If + observables are not specified, this criteria will match on any observable. + qubits (Optional[QubitSetInput]): A set of relevant qubits. If no qubits + are provided, all (possible) qubits are considered to be relevant. + + Throws: + ValueError: If the operators operate on more than one qubit. + """ + self._observables = parse_operator_input(observables) + self._qubits = parse_qubit_input(qubits, 1) + + def __str__(self): + observables_names = ( + {observable.__name__ for observable in self._observables} + if self._observables is not None + else None + ) + return f"{self.__class__.__name__}({observables_names}, {self._qubits})" + + def __repr__(self): + observables_names = ( + {observable.__name__ for observable in self._observables} + if self._observables is not None + else None + ) + return f"{self.__class__.__name__}(observables={observables_names}, qubits={self._qubits})" + + def applicable_key_types(self) -> Iterable[CriteriaKey]: + """ + Returns: + Iterable[CriteriaKey]: This Criteria operates on Observables and Qubits. + """ + return [CriteriaKey.OBSERVABLE, 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: + OBSERVABLE will return a set of Observable classes that are relevant to this Criteria, + or CriteriaKeyResult.ALL if the Criteria is relevant for all (possible) observables. + 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.OBSERVABLE: + return CriteriaKeyResult.ALL if self._observables is None else set(self._observables) + 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. + """ + observables = ( + [observable.__name__ for observable in self._observables] + if self._observables is not None + else None + ) + qubits = list(self._qubits) if self._qubits is not None else None + return { + "__class__": self.__class__.__name__, + "observables": observables, + "qubits": qubits, + } + + def result_type_matches(self, result_type: ResultType) -> bool: + """Returns true if a result type matches the criteria. + + 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. + If observables were not provided in the constructor, then this method will accept any + Observable. + If qubits were not provided in the constructor, then this method will accept any + result type target. + """ + if not isinstance(result_type, ObservableResultType): + return False + if self._observables is not None and type(result_type.observable) not in self._observables: + return False + if self._qubits is None: + return True + target = list(result_type.target) + if not target: + return True + return target[0] in self._qubits + + @classmethod + def from_dict(cls, criteria: dict) -> Criteria: + """Deserializes a dictionary into a Criteria object. + + Args: + criteria (dict): A dictionary representation of a GateCriteria. + + Returns: + Criteria: A deserialized GateCriteria represented by the passed in + serialized data. + """ + observables = ( + [getattr(Observable, name) for name in criteria["observables"]] + if criteria["observables"] is not None + else None + ) + return ObservableCriteria(observables, criteria["qubits"]) + + +Criteria.register_criteria(ObservableCriteria) diff --git a/src/braket/circuits/noise_model/qubit_initialization_criteria.py b/src/braket/circuits/noise_model/qubit_initialization_criteria.py new file mode 100644 index 00000000..0646845c --- /dev/null +++ b/src/braket/circuits/noise_model/qubit_initialization_criteria.py @@ -0,0 +1,109 @@ +# 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 typing import Any, Iterable, Optional, Set, Union + +from braket.circuits.noise_model.criteria import Criteria, CriteriaKey, CriteriaKeyResult +from braket.circuits.noise_model.criteria_input_parsing import parse_qubit_input +from braket.circuits.noise_model.initialization_criteria import InitializationCriteria +from braket.circuits.qubit_set import QubitSet, QubitSetInput + + +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. + + 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) + + 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: + Iterable[CriteriaKey]: This Criteria operates on Qubits, but is valid for all Gates. + """ + 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 Critera, 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: + if self._qubits is None: + return CriteriaKeyResult.ALL + return 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 qubit_intersection(self, qubits: QubitSetInput) -> QubitSetInput: + """ + Returns subset of passed qubits that match the criteria. + + Args: + qubits (QubitSetInput): A qubit or set of qubits that may match the criteria. + + Returns: + QubitSetInput: The subset of passed qubits that match the criteria. + """ + target_qubit = QubitSet(qubits) + if self._qubits is None: + return target_qubit + return self._qubits.intersection(target_qubit) + + @classmethod + def from_dict(cls, criteria: dict) -> Criteria: + """ + Deserializes a dictionary into a Criteria object. + + Args: + criteria (dict): A dictionary representation of a QubitCriteria. + + Returns: + Criteria: A deserialized QubitCriteria represented by the passed in + serialized data. + """ + return QubitInitializationCriteria(criteria["qubits"]) + + +Criteria.register_criteria(QubitInitializationCriteria) diff --git a/src/braket/circuits/noise_model/result_type_criteria.py b/src/braket/circuits/noise_model/result_type_criteria.py new file mode 100644 index 00000000..77c8d0d6 --- /dev/null +++ b/src/braket/circuits/noise_model/result_type_criteria.py @@ -0,0 +1,32 @@ +# 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 abc import abstractmethod + +from braket.circuits.noise_model.criteria import Criteria +from braket.circuits.result_type import ResultType + + +class ResultTypeCriteria(Criteria): + """Criteria that implement these methods may be used to determine readout noise.""" + + @abstractmethod + def result_type_matches(self, result_type: ResultType) -> bool: + """Returns true if a result type matches the criteria. + + Args: + result_type (ResultType): A result type or list of result types to match. + Returns: + bool: True if the result type matches the criteria. + """ + raise NotImplementedError diff --git a/src/braket/circuits/noise_model/unitary_gate_criteria.py b/src/braket/circuits/noise_model/unitary_gate_criteria.py new file mode 100644 index 00000000..c8729298 --- /dev/null +++ b/src/braket/circuits/noise_model/unitary_gate_criteria.py @@ -0,0 +1,122 @@ +# 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 typing import Any, Iterable, Optional, Set, Union + +from braket.circuits.gates import Unitary +from braket.circuits.instruction import Instruction +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.circuits.qubit_set import QubitSetInput + + +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. + + 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: + ValueError: If unitary is not a Unitary type. + """ + if not isinstance(unitary, Unitary): + raise TypeError("unitary must be a Unitary type") + self._unitary = unitary + self._qubits = parse_qubit_input(qubits) + + def __str__(self): + return f"{self.__class__.__name__}(unitary={self._unitary}, qubits={self._qubits})" + + def __repr__(self): + return f"{self.__class__.__name__}(unitary={self._unitary}, qubits={self._qubits})" + + def applicable_key_types(self) -> Iterable[CriteriaKey]: + """ + Returns: + Iterable[CriteriaKey]: This Criteria operates on unitary gates and Qubits. + """ + return [CriteriaKey.QUBIT, CriteriaKey.UNITARY_GATE] + + 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: + UNITARY_GATE will return a set containing the bytes of the unitary matrix representing + the unitary gate. + 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 list. + """ + if key_type == CriteriaKey.UNITARY_GATE: + return {self._unitary.to_matrix().tobytes()} + 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__, + "unitary": self._unitary, + "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 operator is one of the Unitary gates provided in the + constructor 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 ignore + the Instruction target. + """ + if isinstance(instruction, Iterable): + return False + if instruction.operator != self._unitary: + 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 UnitaryGateCriteria. + + Returns: + Criteria: A deserialized UnitaryGateCriteria represented by the passed in + serialized data. + """ + return UnitaryGateCriteria(criteria["unitary"], criteria["qubits"]) + + +Criteria.register_criteria(UnitaryGateCriteria) diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py index 7d79fbaa..6b58df90 100644 --- a/src/braket/circuits/noises.py +++ b/src/braket/circuits/noises.py @@ -12,12 +12,14 @@ # language governing permissions and limitations under the License. import itertools -from typing import Dict, Iterable +from typing import Dict, Iterable, List, Union 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 from braket.circuits.instruction import Instruction from braket.circuits.noise import ( DampingNoise, @@ -72,11 +74,11 @@ class BitFlip(SingleProbabilisticNoise): This noise channel is shown as `BF` in circuit diagrams. """ - def __init__(self, probability: float): + def __init__(self, probability: Union[FreeParameterExpression, float]): super().__init__( probability=probability, qubit_count=None, - ascii_symbols=["BF({:.2g})".format(probability)], + ascii_symbols=[_ascii_representation("BF", [probability])], ) def to_ir(self, target: QubitSet): @@ -111,6 +113,32 @@ def bit_flip(target: QubitSetInput, probability: float) -> Iterable[Instruction] for qubit in QubitSet(target) ] + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Noise: A new Noise object of the same type with the requested + parameters bound. + """ + return BitFlip(probability=_substitute_value(self._probability, **kwargs)) + + @classmethod + def from_dict(cls, noise: dict) -> Noise: + """ + Converts a dictionary representation of this class into this class. + + Args: + noise(dict): The dictionary representation of this noise. + + Returns: + Noise: A Noise object that represents the passed in dictionary. + """ + return BitFlip(probability=_parameter_from_dict(noise["probability"])) + Noise.register_noise(BitFlip) @@ -141,11 +169,11 @@ class PhaseFlip(SingleProbabilisticNoise): This noise channel is shown as `PF` in circuit diagrams. """ - def __init__(self, probability: float): + def __init__(self, probability: Union[FreeParameterExpression, float]): super().__init__( probability=probability, qubit_count=None, - ascii_symbols=["PF({:.2g})".format(probability)], + ascii_symbols=[_ascii_representation("PF", [probability])], ) def to_ir(self, target: QubitSet): @@ -180,6 +208,32 @@ def phase_flip(target: QubitSetInput, probability: float) -> Iterable[Instructio for qubit in QubitSet(target) ] + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Noise: A new Noise object of the same type with the requested + parameters bound. + """ + return PhaseFlip(probability=_substitute_value(self._probability, **kwargs)) + + @classmethod + def from_dict(cls, noise: dict) -> Noise: + """ + Converts a dictionary representation of this class into this class. + + Args: + noise(dict): The dictionary representation of this noise. + + Returns: + Noise: A Noise object that represents the passed in dictionary. + """ + return PhaseFlip(probability=_parameter_from_dict(noise["probability"])) + Noise.register_noise(PhaseFlip) @@ -226,13 +280,18 @@ class PauliChannel(PauliNoise): This noise channel is shown as `PC` in circuit diagrams. """ - def __init__(self, probX: float, probY: float, probZ: float): + def __init__( + self, + probX: Union[FreeParameterExpression, float], + probY: Union[FreeParameterExpression, float], + probZ: Union[FreeParameterExpression, float], + ): super().__init__( probX=probX, probY=probY, probZ=probZ, qubit_count=None, - ascii_symbols=["PC({:.2g},{:.2g},{:.2g})".format(probX, probY, probZ)], + ascii_symbols=[_ascii_representation("PC", [probX, probY, probZ])], ) def to_ir(self, target: QubitSet): @@ -274,6 +333,40 @@ def pauli_channel( for qubit in QubitSet(target) ] + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Gate.Rx: A new Gate of the same type with the requested + parameters bound. + """ + probX = _substitute_value(self.probX, **kwargs) + probY = _substitute_value(self.probY, **kwargs) + probZ = _substitute_value(self.probZ, **kwargs) + + return PauliChannel(probX=probX, probY=probY, probZ=probZ) + + @classmethod + def from_dict(cls, noise: dict) -> Noise: + """ + Converts a dictionary representation of this class into this class. + + Args: + noise(dict): The dictionary representation of this noise. + + Returns: + Noise: A Noise object that represents the passed in dictionary. + """ + return PauliChannel( + probX=_parameter_from_dict(noise["probX"]), + probY=_parameter_from_dict(noise["probY"]), + probZ=_parameter_from_dict(noise["probZ"]), + ) + Noise.register_noise(PauliChannel) @@ -322,11 +415,11 @@ class Depolarizing(SingleProbabilisticNoise_34): This noise channel is shown as `DEPO` in circuit diagrams. """ - def __init__(self, probability: float): + def __init__(self, probability: Union[FreeParameterExpression, float]): super().__init__( probability=probability, qubit_count=None, - ascii_symbols=["DEPO({:.2g})".format(probability)], + ascii_symbols=[_ascii_representation("DEPO", [probability])], ) def to_ir(self, target: QubitSet): @@ -363,6 +456,33 @@ def depolarizing(target: QubitSetInput, probability: float) -> Iterable[Instruct for qubit in QubitSet(target) ] + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Noise: A new Noise object of the same type with the requested + parameters bound. + + """ + return Depolarizing(probability=_substitute_value(self._probability, **kwargs)) + + @classmethod + def from_dict(cls, noise: dict) -> Noise: + """ + Converts a dictionary representation of this class into this class. + + Args: + noise(dict): The dictionary representation of this noise. + + Returns: + Noise: A Noise object that represents the passed in dictionary. + """ + return Depolarizing(probability=_parameter_from_dict(noise["probability"])) + Noise.register_noise(Depolarizing) @@ -414,11 +534,11 @@ class TwoQubitDepolarizing(SingleProbabilisticNoise_1516): This noise channel is shown as `DEPO` in circuit diagrams. """ - def __init__(self, probability: float): + def __init__(self, probability: Union[FreeParameterExpression, float]): super().__init__( probability=probability, qubit_count=None, - ascii_symbols=["DEPO({:.2g})".format(probability)] * 2, + ascii_symbols=[_ascii_representation("DEPO", [probability])] * 2, ) def to_ir(self, target: QubitSet): @@ -469,6 +589,33 @@ def two_qubit_depolarizing( ) ] + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Noise: A new Noise object of the same type with the requested + parameters bound. + + """ + return TwoQubitDepolarizing(probability=_substitute_value(self._probability, **kwargs)) + + @classmethod + def from_dict(cls, noise: dict) -> Noise: + """ + Converts a dictionary representation of this class into this class. + + Args: + noise(dict): The dictionary representation of this noise. + + Returns: + Noise: A Noise object that represents the passed in dictionary. + """ + return TwoQubitDepolarizing(probability=_parameter_from_dict(noise["probability"])) + Noise.register_noise(TwoQubitDepolarizing) @@ -502,11 +649,11 @@ class TwoQubitDephasing(SingleProbabilisticNoise_34): This noise channel is shown as `DEPH` in circuit diagrams. """ - def __init__(self, probability: float): + def __init__(self, probability: Union[FreeParameterExpression, float]): super().__init__( probability=probability, qubit_count=None, - ascii_symbols=["DEPH({:.2g})".format(probability)] * 2, + ascii_symbols=[_ascii_representation("DEPH", [probability])] * 2, ) def to_ir(self, target: QubitSet): @@ -550,6 +697,33 @@ def two_qubit_dephasing( Instruction(Noise.TwoQubitDephasing(probability=probability), target=[target1, target2]) ] + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Noise: A new Noise object of the same type with the requested + parameters bound. + + """ + return TwoQubitDephasing(probability=_substitute_value(self._probability, **kwargs)) + + @classmethod + def from_dict(cls, noise: dict) -> Noise: + """ + Converts a dictionary representation of this class into this class. + + Args: + noise(dict): The dictionary representation of this noise. + + Returns: + Noise: A Noise object that represents the passed in dictionary. + """ + return TwoQubitDephasing(probability=_parameter_from_dict(noise["probability"])) + Noise.register_noise(TwoQubitDephasing) @@ -629,26 +803,27 @@ def __init__(self, probabilities: Dict[str, float]): f"PC2({probabilities})", ], ) + self._matrix = None - total_prob = sum(self.probabilities.values()) + def to_ir(self, target: QubitSet): + return ir.MultiQubitPauliChannel.construct( + targets=[target[0], target[1]], probabilities=self._probabilities + ) + def to_matrix(self) -> Iterable[np.ndarray]: + if self._matrix is not None: + return self._matrix + total_prob = sum(self._probabilities.values()) K_list = [np.sqrt(1 - total_prob) * np.identity(4)] # "II" element for pstring in self._names_list[1:]: # ignore "II" - if pstring in self.probabilities: - mat = np.sqrt(self.probabilities[pstring]) * np.kron( + if pstring in self._probabilities: + mat = np.sqrt(self._probabilities[pstring]) * np.kron( self._paulis[pstring[0]], self._paulis[pstring[1]] ) K_list.append(mat) else: K_list.append(np.zeros((4, 4))) self._matrix = K_list - - def to_ir(self, target: QubitSet): - return ir.MultiQubitPauliChannel.construct( - targets=[target[0], target[1]], probabilities=self.probabilities - ) - - def to_matrix(self) -> Iterable[np.ndarray]: return self._matrix @staticmethod @@ -679,6 +854,40 @@ def two_qubit_pauli_channel( ) ] + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Noise: A new Noise object of the same type with the requested + parameters bound. + + """ + probabilities = { + pauli_string: _substitute_value(prob, **kwargs) + for pauli_string, prob in self._probabilities.items() + } + return TwoQubitPauliChannel(probabilities=probabilities) + + @classmethod + def from_dict(cls, noise: dict) -> Noise: + """ + Converts a dictionary representation of this class into this class. + + Args: + noise(dict): The dictionary representation of this noise. + + Returns: + Noise: A Noise object that represents the passed in dictionary. + """ + probabilities = dict() + for pauli_string, prob in noise["probabilities"].items(): + probabilities[pauli_string] = _parameter_from_dict(prob) + return TwoQubitPauliChannel(probabilities=probabilities) + Noise.register_noise(TwoQubitPauliChannel) @@ -707,11 +916,11 @@ class AmplitudeDamping(DampingNoise): This noise channel is shown as `AD` in circuit diagrams. """ - def __init__(self, gamma: float): + def __init__(self, gamma: Union[FreeParameterExpression, float]): super().__init__( gamma=gamma, qubit_count=None, - ascii_symbols=["AD({:.2g})".format(gamma)], + ascii_symbols=[_ascii_representation("AD", [gamma])], ) def to_ir(self, target: QubitSet): @@ -746,6 +955,33 @@ def amplitude_damping(target: QubitSetInput, gamma: float) -> Iterable[Instructi for qubit in QubitSet(target) ] + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Noise: A new Noise object of the same type with the requested + parameters bound. + + """ + return AmplitudeDamping(gamma=_substitute_value(self._gamma, **kwargs)) + + @classmethod + def from_dict(cls, noise: dict) -> Noise: + """ + Converts a dictionary representation of this class into this class. + + Args: + noise(dict): The dictionary representation of this noise. + + Returns: + Noise: A Noise object that represents the passed in dictionary. + """ + return AmplitudeDamping(gamma=_parameter_from_dict(noise["gamma"])) + Noise.register_noise(AmplitudeDamping) @@ -788,12 +1024,16 @@ class GeneralizedAmplitudeDamping(GeneralizedAmplitudeDampingNoise): This noise channel is shown as `GAD` in circuit diagrams. """ - def __init__(self, gamma: float, probability: float): + def __init__( + self, + gamma: Union[FreeParameterExpression, float], + probability: Union[FreeParameterExpression, float], + ): super().__init__( gamma=gamma, probability=probability, qubit_count=None, - ascii_symbols=["GAD({:.2g},{:.2g})".format(gamma, probability)], + ascii_symbols=[_ascii_representation("GAD", [gamma, probability])], ) def to_ir(self, target: QubitSet): @@ -842,6 +1082,38 @@ def generalized_amplitude_damping( for qubit in QubitSet(target) ] + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Noise: A new Noise object of the same type with the requested + parameters bound. + + """ + gamma = _substitute_value(self._gamma, **kwargs) + probability = _substitute_value(self._probability, **kwargs) + return GeneralizedAmplitudeDamping(gamma=gamma, probability=probability) + + @classmethod + def from_dict(cls, noise: dict) -> Noise: + """ + Converts a dictionary representation of this class into this class. + + Args: + noise(dict): The dictionary representation of this noise. + + Returns: + Noise: A Noise object that represents the passed in dictionary. + """ + return GeneralizedAmplitudeDamping( + gamma=_parameter_from_dict(noise["gamma"]), + probability=_parameter_from_dict(noise["probability"]), + ) + Noise.register_noise(GeneralizedAmplitudeDamping) @@ -872,11 +1144,11 @@ class PhaseDamping(DampingNoise): This noise channel is shown as `PD` in circuit diagrams. """ - def __init__(self, gamma: float): + def __init__(self, gamma: Union[FreeParameterExpression, float]): super().__init__( gamma=gamma, qubit_count=None, - ascii_symbols=["PD({:.2g})".format(gamma)], + ascii_symbols=[_ascii_representation("PD", [gamma])], ) def to_ir(self, target: QubitSet): @@ -910,6 +1182,33 @@ def phase_damping(target: QubitSetInput, gamma: float) -> Iterable[Instruction]: Instruction(Noise.PhaseDamping(gamma=gamma), target=qubit) for qubit in QubitSet(target) ] + def bind_values(self, **kwargs): + """ + Takes in parameters and attempts to assign them to values. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + Noise: A new Noise object of the same type with the requested + parameters bound. + + """ + return PhaseDamping(gamma=_substitute_value(self._gamma, **kwargs)) + + @classmethod + def from_dict(cls, noise: dict) -> Noise: + """ + Converts a dictionary representation of this class into this class. + + Args: + noise(dict): The dictionary representation of this noise. + + Returns: + Noise: A Noise object that represents the passed in dictionary. + """ + return PhaseDamping(gamma=_parameter_from_dict(noise["gamma"])) + Noise.register_noise(PhaseDamping) @@ -938,6 +1237,7 @@ def __init__(self, matrices: Iterable[np.ndarray], display_name: str = "KR"): if not 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 qubit_count = int(np.log2(self._matrices[0].shape[0])) if qubit_count > 2: raise ValueError("Kraus operators with more than two qubits are not supported.") @@ -998,5 +1298,62 @@ def kraus( Noise.Kraus(matrices=matrices, display_name=display_name), target=targets ) + def to_dict(self) -> dict: + raise NotImplementedError + + @classmethod + def from_dict(cls, noise: dict) -> Noise: + """ + Converts a dictionary representation of this class into this class. + + Args: + noise(dict): The dictionary representation of this noise. + + Returns: + Noise: A Noise object that represents the passed in dictionary. + """ + raise NotImplementedError + Noise.register_noise(Kraus) + + +def _ascii_representation( + noise: str, parameters: List[Union[FreeParameterExpression, float]] +) -> str: + """ + Generates a formatted ascii representation of a noise. + + Args: + noise (str): The name of the noise. + parameters (List[Union[FreeParameterExpression, float]]): The parameters to the noise. + + Returns: + str: The ascii representation of the noise. + """ + param_list = [] + for param in parameters: + param_list.append( + str(param) if isinstance(param, FreeParameterExpression) else "{:.2g}".format(param) + ) + param_str = ",".join(param_list) + return f"{noise}({param_str})" + + +def _substitute_value(expr, **kwargs): + return expr.subs(kwargs) if isinstance(expr, FreeParameterExpression) else expr + + +def _parameter_from_dict(parameter: Union[dict, float]) -> Union[FreeParameter, float]: + """Converts a parameter from a dictionary if it's a FreeParameter, otherwise returns the float. + + Args: + parameter(Union[dict, float]): The parameter to convert. + + Returns: + A FreeParameter representing the parameter, if the parameter is a dictionary, + otherwise returns the float. + """ + if isinstance(parameter, dict): + return FreeParameter.from_dict(parameter) + return parameter diff --git a/src/braket/circuits/parameterizable.py b/src/braket/circuits/parameterizable.py index 4032cdd3..550b48cc 100644 --- a/src/braket/circuits/parameterizable.py +++ b/src/braket/circuits/parameterizable.py @@ -22,15 +22,16 @@ class Parameterizable(ABC): """ - A parameterized object is the abstract definition - of an object that can take in FreeParameters. + A parameterized object is the abstract definition of an object + that can take in FreeParameterExpressions. """ @property @abstractmethod def parameters(self) -> List[Union[FreeParameterExpression, FreeParameter, float]]: """ - Returns the free parameters associated with the object. + Returns the parameters associated with the object, either unbound free parameter expressions + or bound values. The order of the parameters is determined by the subclass. """ @abstractmethod diff --git a/test/unit_tests/braket/circuits/noise/test_gate_criteria.py b/test/unit_tests/braket/circuits/noise/test_gate_criteria.py new file mode 100644 index 00000000..3bd4f054 --- /dev/null +++ b/test/unit_tests/braket/circuits/noise/test_gate_criteria.py @@ -0,0 +1,209 @@ +# 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 Gate, Instruction, Observable +from braket.circuits.noise_model import ( + CriteriaKey, + CriteriaKeyResult, + GateCriteria, + ObservableCriteria, +) + + +@pytest.mark.parametrize( + "gates, qubits", + [ + (None, range(3)), + (None, None), + (Gate.H, range(3)), + ([Gate.H], range(3)), + ([Gate.I, Gate.H], range(3)), + (Gate.H, 1), + ([Gate.H], [1]), + ([Gate.I, Gate.H], [1, 0]), + (Gate.H, None), + ], +) +def test_happy_case(gates, qubits): + criteria = GateCriteria(gates=gates, qubits=qubits) + assert criteria.applicable_key_types() == [CriteriaKey.QUBIT, CriteriaKey.GATE] + if gates is None: + assert CriteriaKeyResult.ALL == criteria.get_keys(CriteriaKey.GATE) + else: + assert Gate.H in criteria.get_keys(CriteriaKey.GATE) + if qubits is None: + assert CriteriaKeyResult.ALL == criteria.get_keys(CriteriaKey.QUBIT) + else: + assert 1 in criteria.get_keys(CriteriaKey.QUBIT) + + +@pytest.mark.parametrize( + "gates, qubits", + [ + (None, range(3)), + (None, None), + (Gate.H, range(3)), + ([Gate.H], 1), + ([Gate.H], [1]), + ([Gate.H], [[1]]), + ([Gate.CNot], [0, 1]), + ([Gate.CNot], [[1, 2]]), + ([Gate.CNot], [[3, 4], [5, 6]]), + ([Gate.I, Gate.H], range(3)), + ([Gate.I, Gate.H], None), + ], +) +def test_serialization(gates, qubits): + test_criteria = GateCriteria(gates=gates, qubits=qubits) + serialized_criteria = test_criteria.to_dict() + assert serialized_criteria["__class__"] == "GateCriteria" + assert "gates" in serialized_criteria + assert "qubits" in serialized_criteria + deserialized_criteria = GateCriteria.from_dict(serialized_criteria) + assert test_criteria == deserialized_criteria + + +@pytest.mark.parametrize( + "gates, qubits, matching_instructions, non_matching_instructions", + [ + (None, 1, [Instruction(Gate.H(), 1)], [Instruction(Gate.CNot(), [0, 1])]), + ( + None, + None, + [Instruction(Gate.H(), 1), Instruction(Gate.CNot(), [0, 1])], + [Instruction(Observable.X, 0)], + ), + ( + Gate.H, + range(3), + [Instruction(Gate.H(), 0), Instruction(Gate.H(), 1)], + [ + Instruction(Gate.H(), 3), + Instruction(Gate.I(), 1), + [Instruction(Gate.H(), 0)], + ], + ), + (Gate.H, 1, [Instruction(Gate.H(), 1)], [Instruction(Gate.H(), 0)]), + ([Gate.H], [1], [Instruction(Gate.H(), 1)], [Instruction(Gate.H(), 0)]), + ( + [Gate.CNot], + [0, 1], + [Instruction(Gate.CNot(), [0, 1])], + [Instruction(Gate.CNot(), [1, 0])], + ), + ( + [Gate.CNot], + [[0, 1], [1, 2]], + [Instruction(Gate.CNot(), [0, 1]), Instruction(Gate.CNot(), [1, 2])], + [Instruction(Gate.CNot(), [1, 0])], + ), + ( + [Gate.I, Gate.H], + range(3), + [Instruction(Gate.H(), 0), Instruction(Gate.I(), 2)], + [Instruction(Gate.H(), 3), Instruction(Gate.X(), 1)], + ), + ( + Gate.H, + None, + [Instruction(Gate.H(), 0), Instruction(Gate.H(), 10)], + [Instruction(Gate.I(), 1), Instruction(Gate.X(), 3)], + ), + ( + Gate.H, + [], + [Instruction(Gate.H(), 0), Instruction(Gate.H(), 10)], + [Instruction(Gate.I(), 1), Instruction(Gate.X(), 3)], + ), + ], +) +def test_matcher(gates, qubits, matching_instructions, non_matching_instructions): + criteria = GateCriteria(gates=gates, qubits=qubits) + for instruction in matching_instructions: + assert criteria.instruction_matches(instruction) + for instruction in non_matching_instructions: + assert not criteria.instruction_matches(instruction) + + +@pytest.mark.parametrize( + "gates, qubits", + [ + (Gate.CNot, [[0, 1], 2]), + ], +) +@pytest.mark.xfail(raises=TypeError) +def test_invalid_type_params(gates, qubits): + GateCriteria(gates=gates, qubits=qubits) + + +@pytest.mark.parametrize( + "gates, qubits", + [ + ([Gate.H, Gate.CNot], 1), + (Gate.CNot, [[0, 1], [2]]), + ], +) +@pytest.mark.xfail(raises=ValueError) +def test_invalid_value_params(gates, qubits): + GateCriteria(gates=gates, qubits=qubits) + + +def test_representation(): + criteria = GateCriteria(gates=[Gate.I], qubits=0) + str_representation = criteria.__repr__() + assert str_representation == "GateCriteria(gates={'I'}, qubits={0})" + + +def test_string(): + criteria = GateCriteria(gates=[Gate.I], qubits=0) + str_representation = criteria.__str__() + assert str_representation == "GateCriteria({'I'}, {0})" + + +def test_get_keys_for_unknown_keytypes(): + criteria = GateCriteria(gates=[Gate.I], qubits=0) + result = criteria.get_keys(CriteriaKey.UNITARY_GATE) + assert len(result) == 0 + + +@pytest.mark.parametrize( + "criteria0, criteria1", + [ + (GateCriteria(Gate.H, range(3)), GateCriteria(Gate.H, range(3))), + (GateCriteria(Gate.H, [0, 1, 2]), GateCriteria(Gate.H, range(3))), + (GateCriteria(Gate.H, [1, 2, 0]), GateCriteria(Gate.H, range(3))), + (GateCriteria(Gate.H), GateCriteria(Gate.H, None)), + ( + GateCriteria([Gate.H, Gate.I], range(3)), + GateCriteria([Gate.I, Gate.H], range(3)), + ), + ], +) +def test_equal_criteria(criteria0, criteria1): + assert criteria0 == criteria1 + + +@pytest.mark.parametrize( + "criteria0, criteria1", + [ + (GateCriteria(Gate.H, range(3)), GateCriteria(Gate.I, range(3))), + (GateCriteria(Gate.H, [1, 2, 3]), GateCriteria(Gate.H, range(3))), + (GateCriteria(Gate.H), GateCriteria(Gate.H, range(3))), + (GateCriteria(Gate.H, range(3)), float(3)), + (GateCriteria(Gate.H, range(3)), ObservableCriteria(Observable.X, range(3))), + ], +) +def test_not_equal_criteria(criteria0, criteria1): + assert criteria0 != criteria1 diff --git a/test/unit_tests/braket/circuits/noise/test_noise_model.py b/test/unit_tests/braket/circuits/noise/test_noise_model.py new file mode 100644 index 00000000..bf5974e4 --- /dev/null +++ b/test/unit_tests/braket/circuits/noise/test_noise_model.py @@ -0,0 +1,490 @@ +# 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 Mock + +import pytest + +from braket.circuits import Circuit, Gate, Noise, Observable +from braket.circuits.gates import Unitary +from braket.circuits.noise_model import ( + CircuitInstructionCriteria, + Criteria, + GateCriteria, + NoiseModel, + ObservableCriteria, + QubitInitializationCriteria, + UnitaryGateCriteria, +) +from braket.circuits.noises import BitFlip, Depolarizing, PauliChannel, TwoQubitDepolarizing + + +def h_unitary(): + return Unitary(Gate.H().to_matrix()) + + +@pytest.fixture +def default_noise_model(): + noise_list = [] + criteria_list = [] + noise_model = NoiseModel() + for i in range(3): + noise = Mock(spec=Noise) + criteria = Mock(spec=CircuitInstructionCriteria) + criteria.instruction_matches.return_value = i % 2 == 0 + noise_model.add_noise(noise, criteria) + noise_list.append(noise) + criteria_list.append(criteria) + return noise_model, noise_list, criteria_list + + +def test_simple_add_noise(): + noise_model = NoiseModel() + assert len(noise_model.instructions) == 0 + mock_noise = Mock(spec=Noise) + mock_criteria = Mock(spec=CircuitInstructionCriteria) + noise_model.add_noise(mock_noise, mock_criteria) + noise_list = noise_model.instructions + assert len(noise_list) == 1 + assert mock_noise == noise_list[0].noise + assert mock_criteria == noise_list[0].criteria + + +def test_insert_noise(default_noise_model): + noise_model, noise_list, criteria_list = default_noise_model + listed_noise = noise_model.instructions + assert len(listed_noise) == 3 + + mock_noise = Mock(spec=Noise) + mock_criteria = Mock(spec=CircuitInstructionCriteria) + + noise_model.insert_noise(1, mock_noise, mock_criteria) + + listed_noise = noise_model.instructions + assert len(listed_noise) == 4 + assert listed_noise[0].noise == noise_list[0] + assert listed_noise[0].criteria == criteria_list[0] + assert listed_noise[1].noise == mock_noise + assert listed_noise[1].criteria == mock_criteria + assert listed_noise[2].noise == noise_list[1] + assert listed_noise[2].criteria == criteria_list[1] + assert listed_noise[3].noise == noise_list[2] + assert listed_noise[3].criteria == criteria_list[2] + + +def test_remove_noise(default_noise_model): + noise_model, noise_list, _ = default_noise_model + + listed_noise = noise_model.instructions + assert len(listed_noise) == 3 + + noise_model.remove_noise(1) + + listed_noise = noise_model.instructions + assert len(listed_noise) == 2 + assert listed_noise[0].noise == noise_list[0] + assert listed_noise[1].noise == noise_list[2] + + +@pytest.mark.parametrize( + "gate, qubit, noise, noise_type, expected_length", + [ + (None, None, None, None, 6), + (None, None, PauliChannel, None, 2), + (Gate.H, None, None, None, 3), + (Gate.CNot, None, None, None, 2), + (None, 0, None, None, 5), + # (None, (0, 1), None, None, 1), # TODO: I'm not sure of the best way to fix this. + (Gate.CNot, (0, 1), None, None, 1), + (Gate.CNot, (1, 0), None, None, 0), + (Gate.H, 2, None, None, 1), + (Gate.H, None, Depolarizing, None, 2), + ], +) +def test_filter(gate, qubit, noise, noise_type, expected_length): + noise_model = ( + NoiseModel() + .add_noise(PauliChannel(0.01, 0.02, 0.03), GateCriteria(Gate.I, [0, 1])) + .add_noise(Depolarizing(0.04), GateCriteria(Gate.H)) + .add_noise(Depolarizing(0.05), GateCriteria(Gate.CNot, [0, 1])) + .add_noise(PauliChannel(0.06, 0.07, 0.08), GateCriteria(Gate.H, [0, 1])) + .add_noise(Depolarizing(0.09), GateCriteria(None, 0)) + .add_noise(Depolarizing(0.10), QubitInitializationCriteria([0, 1])) + ) + result_model = noise_model.from_filter(qubit=qubit, gate=gate, noise=noise) + assert len(result_model.instructions) == expected_length + + +@pytest.mark.parametrize( + "noise_types, expected_string", + [ + ([], ""), + ([Criteria], ""), + ( + [GateCriteria], + "Gate Noise:\n my_noise 0, my_criteria 0\n my_noise 1, my_criteria 1", + ), + ( + [Criteria, GateCriteria, ObservableCriteria, QubitInitializationCriteria], + "Initialization Noise:\n my_noise 0, my_criteria 0\n my_noise 1, my_criteria 1\n" + "Gate Noise:\n my_noise 0, my_criteria 0\n my_noise 1, my_criteria 1\n" + "Readout Noise:\n my_noise 0, my_criteria 0\n my_noise 1, my_criteria 1", + ), + ], +) +def test_str_representation(noise_types, expected_string): + noise_model = NoiseModel() + + for noise_type in noise_types: + for index in range(2): + mock_noise = Mock(spec=Noise) + mock_noise.__str__ = Mock(return_value=f"my_noise {index}") + mock_criteria = Mock(spec=noise_type) + mock_criteria.__str__ = Mock(return_value=f"my_criteria {index}") + noise_model.add_noise(mock_noise, mock_criteria) + + str_representation = noise_model.__str__() + assert str_representation == expected_string + + +def test_repr_representation(): + noise_model = NoiseModel() + mock_noise = Mock(spec=Noise) + mock_noise.__repr__ = Mock(return_value="n") + mock_criteria = Mock(spec=Criteria) + mock_criteria.__repr__ = Mock(return_value="c") + noise_model.add_noise(mock_noise, mock_criteria) + str_representation = noise_model.__repr__() + assert str_representation == "{'instructions': [NoiseModelInstruction(noise=n, criteria=c)]}" + + +def test_serialization(): + noise_model = ( + NoiseModel() + .add_noise(PauliChannel(0.01, 0.02, 0.03), GateCriteria(Gate.I, [0, 1])) + .add_noise(Depolarizing(0.04), GateCriteria(Gate.H)) + .add_noise(Depolarizing(0.05), GateCriteria(Gate.CNot, [0, 1])) + .add_noise(PauliChannel(0.06, 0.07, 0.08), GateCriteria(Gate.H, [0, 1])) + ) + serialized_model = noise_model.to_dict() + deserialized_model = NoiseModel.from_dict(serialized_model) + assert len(deserialized_model.instructions) == len(noise_model.instructions) + for index, deserialized_item in enumerate(deserialized_model.instructions): + assert noise_model.instructions[index].noise == deserialized_item.noise + assert noise_model.instructions[index].criteria == deserialized_item.criteria + assert deserialized_model is not None + + +def test_apply(): + noise_model = ( + NoiseModel() + .add_noise(PauliChannel(0.01, 0.02, 0.03), GateCriteria(Gate.I, [0, 1])) + .add_noise(Depolarizing(0.04), GateCriteria(Gate.H)) + .add_noise(TwoQubitDepolarizing(0.05), GateCriteria(Gate.CNot, [0, 1])) + .add_noise(PauliChannel(0.06, 0.07, 0.08), GateCriteria(Gate.H, [0, 1])) + .add_noise(Depolarizing(0.10), UnitaryGateCriteria(h_unitary(), 0)) + .add_noise(Depolarizing(0.06), ObservableCriteria(Observable.Z, 0)) + .add_noise(Depolarizing(0.09), QubitInitializationCriteria(0)) + ) + layer1 = Circuit().h(0).cnot(0, 1).sample(Observable.Z(), 0) + layer2 = Circuit().unitary([0], h_unitary().to_matrix()) + circuit = layer1 + layer2 + noisy_circuit_from_circuit = noise_model.apply(circuit) + expected_circuit = ( + Circuit() + .depolarizing(0, 0.09) + .h(0) + .depolarizing(0, 0.04) + .pauli_channel(0, 0.06, 0.07, 0.08) + .cnot(0, 1) + .two_qubit_depolarizing(0, 1, 0.05) + .unitary([0], h_unitary().to_matrix()) + .depolarizing(0, 0.10) + .apply_readout_noise(Depolarizing(0.06), 0) + .sample(Observable.Z(), 0) + ) + assert noisy_circuit_from_circuit == expected_circuit + + +def test_apply_in_order(): + noise_model = ( + NoiseModel() + .add_noise(Depolarizing(0.01), GateCriteria(Gate.H)) + .add_noise(Depolarizing(0.02), GateCriteria(Gate.H)) + ) + circuit = Circuit().h(0) + noisy_circuit = noise_model.apply(circuit) + expected_circuit = circuit.apply_gate_noise([Depolarizing(0.01), Depolarizing(0.02)]) + assert noisy_circuit == expected_circuit + + +@pytest.mark.parametrize( + "noise_model, input_circuit, expected_circuit", + [ + ( + # model with noise on H(0) + NoiseModel().add_noise(Depolarizing(0.01), GateCriteria(Gate.H, 0)), + # input circuit has an H gate on qubit 0 + Circuit().h(0).cnot(0, 1), + # expected circuit has noise on qubit 0 + Circuit().h(0).depolarizing(0, 0.01).cnot(0, 1), + ), + ( + # model with noise on H(0) + NoiseModel().add_noise(Depolarizing(0.01), GateCriteria(Gate.H, 0)), + # input circuit has two H gates on qubit 0 + Circuit().h(0).h(0).cnot(0, 1), + # expected circuit has noise on qubit 0 + Circuit().h(0).depolarizing(0, 0.01).h(0).depolarizing(0, 0.01).cnot(0, 1), + ), + ( + # model with noise on all gates, on qubits 0, 1 + NoiseModel().add_noise(Depolarizing(0.01), GateCriteria(None, [0, 1])), + # input circuit + Circuit().h(0).h(1).cnot(0, 1), + # expected circuit has noise on qubit 0 + Circuit().h(0).depolarizing(0, 0.01).h(1).depolarizing(1, 0.01).cnot(0, 1), + ), + ( + # model with noise on all gates, on qubits [0, 1] + NoiseModel().add_noise(Depolarizing(0.01), GateCriteria(None, [[0, 1]])), + # input circuit + Circuit().h(0).h(1).cnot(0, 1), + # expected circuit has noise on the CNot gate + Circuit().h(0).h(1).cnot(0, 1).depolarizing(0, 0.01).depolarizing(1, 0.01), + ), + ( + # model with noise on all gates, on qubits [0, 1] + NoiseModel().add_noise(TwoQubitDepolarizing(0.01), GateCriteria(None, [[0, 1]])), + # input circuit + Circuit().h(0).h(1).cnot(0, 1), + # expected circuit has noise on the CNot gate + Circuit().h(0).h(1).cnot(0, 1).two_qubit_depolarizing(0, 1, 0.01), + ), + ( + # model with noise on a unitary H(0) + NoiseModel().add_noise(Depolarizing(0.01), UnitaryGateCriteria(h_unitary(), 0)), + # input circuit has a unitary H gate on qubit 0 + Circuit().unitary([0], h_unitary().to_matrix()).cnot(0, 1), + # expected circuit has noise on qubit 0 + Circuit().unitary([0], h_unitary().to_matrix()).depolarizing(0, 0.01).cnot(0, 1), + ), + ], +) +def test_gate_noise(noise_model, input_circuit, expected_circuit): + result_circuit = noise_model.apply(input_circuit) + assert result_circuit == expected_circuit + + +@pytest.mark.parametrize( + "noise_model, input_circuit, expected_circuit", + [ + ( + # model + NoiseModel().add_noise(Depolarizing(0.01), QubitInitializationCriteria()), + # input circuit + Circuit().h(0).cnot(0, 1), + # expected circuit has noise on both qubits + Circuit().depolarizing(0, 0.01).depolarizing(1, 0.01).h(0).cnot(0, 1), + ), + ( + # model + NoiseModel().add_noise(Depolarizing(0.01), QubitInitializationCriteria(range(4))), + # input circuit + Circuit().h(0).cnot(0, 1), + # expected circuit has noise on both qubits + Circuit().depolarizing(0, 0.01).depolarizing(1, 0.01).h(0).cnot(0, 1), + ), + ( + # model only specifies noise on one qubit + NoiseModel().add_noise(Depolarizing(0.01), QubitInitializationCriteria(1)), + # input circuit + Circuit().h(0).cnot(0, 1), + # expected circuit has noise on one qubit + Circuit().depolarizing(1, 0.01).h(0).cnot(0, 1), + ), + ( + # model only specifies noise on an unrelated qubit + NoiseModel().add_noise(Depolarizing(0.01), QubitInitializationCriteria(2)), + # input circuit + Circuit().h(0).cnot(0, 1), + # expected circuit has no noise applied + Circuit().h(0).cnot(0, 1), + ), + ( + # model does not specify initialization noise + NoiseModel().add_noise(Depolarizing(0.01), GateCriteria(Gate.X, 2)), + # input circuit + Circuit().h(0).cnot(0, 1), + # expected circuit has no noise applied + Circuit().h(0).cnot(0, 1), + ), + ], +) +def test_apply_initialization_noise(noise_model, input_circuit, expected_circuit): + result_circuit = noise_model.apply(input_circuit) + assert result_circuit == expected_circuit + + +@pytest.mark.parametrize( + "noise_model, input_circuit, expected_circuit", + [ + ( + # model + NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(Observable.Z)), + # input circuit has no explicit observables + Circuit().h(0).cnot(0, 1), + # expected circuit has no noise applied + Circuit().h(0).cnot(0, 1), + ), + ( + # model has observable criteria only on one qubit + NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(Observable.Z, 0)), + # input circuit has no explicit observables + Circuit().h(0).cnot(0, 1), + # expected circuit has no noise applied + Circuit().h(0).cnot(0, 1), + ), + ( + # model + NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(Observable.Z)), + # input circuit has explicit explicit observables + Circuit().h(0).cnot(0, 1).sample(Observable.Z(), 0), + # expected circuit has noise applied + Circuit().h(0).cnot(0, 1).depolarizing(0, 0.01).sample(Observable.Z(), 0), + ), + ( + # model + NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(Observable.X, 0)), + # input circuit doesn't contain observable X + Circuit().h(0).cnot(0, 1), + # expected circuit has no change. + Circuit().h(0).cnot(0, 1), + ), + ( + # model + NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(Observable.X, 0)), + # input circuit contains observable X + Circuit().h(0).cnot(0, 1).sample(Observable.X(), 0), + # expected circuit noise applied. + Circuit().h(0).cnot(0, 1).depolarizing(0, 0.01).sample(Observable.X(), 0), + ), + ( + # model only has an observable on Z + NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(Observable.Z, 0)), + # input circuit contains observable X + Circuit().h(0).cnot(0, 1).sample(Observable.X(), 0), + # expected circuit has no change + Circuit().h(0).cnot(0, 1).sample(Observable.X(), 0), + ), + ( + # model uses qubit criteria + NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(None, None)), + # input circuit doesn't contain observables + Circuit().h(0).cnot(0, 1), + # expected circuit has no noise applied + Circuit().h(0).cnot(0, 1), + ), + ( + # model uses qubit criteria on non-related qubits + NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(None, [2, 3])), + # input circuit doesn't contain observables + Circuit().h(0).cnot(0, 1), + # expected circuit has no change + Circuit().h(0).cnot(0, 1), + ), + ( + # model uses qubit criteria + NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(None, [0, 1])), + # input circuit contains observable X + Circuit().h(0).cnot(0, 1).sample(Observable.X(), 0), + # expected circuit noise applied. + Circuit().h(0).cnot(0, 1).depolarizing(0, 0.01).sample(Observable.X(), 0), + ), + ( + # model uses observable and qubit criteria + NoiseModel() + .add_noise(Depolarizing(0.01), ObservableCriteria(Observable.X, 0)) + .add_noise(Depolarizing(0.02), ObservableCriteria(None, [0, 1])), + # input circuit contains observable X + Circuit().h(0).cnot(0, 1).sample(Observable.X(), 0), + # expected circuit noise applied. + Circuit() + .h(0) + .cnot(0, 1) + .depolarizing(0, 0.01) + .depolarizing(0, 0.02) + .sample(Observable.X(), 0), + ), + ( + # model uses observable criteria with any observable/qubit. + NoiseModel().add_noise(BitFlip(0.01), ObservableCriteria(None, None)), + # input circuit contains many different types of result types for qubit 0 + Circuit() + .h(0) + .cnot(0, 1) + .probability(target=[0, 1]) + .probability(target=0) + .expectation(observable=Observable.Z(), target=0) + .sample(observable=Observable.X(), target=0) + .variance(observable=Observable.Z(), target=0), + # expected circuit only applies BitFlip once to qubit 0 + Circuit() + .h(0) + .cnot(0, 1) + .probability(target=[0, 1]) + .probability(target=0) + .expectation(observable=Observable.Z(), target=0) + .sample(observable=Observable.X(), target=0) + .variance(observable=Observable.Z(), target=0) + .apply_readout_noise(BitFlip(0.01), 0), + ), + ( + # model uses observable criteria with any observable/qubit. + NoiseModel().add_noise(BitFlip(0.01), ObservableCriteria(None, None)), + # input circuit only has a probability result type + Circuit().h(0).cnot(0, 1).probability(target=[0, 1]).probability(target=0), + # expected circuit has no noise applied + Circuit().h(0).cnot(0, 1).probability(target=[0, 1]).probability(target=0), + ), + ], +) +def test_apply_readout_noise(noise_model, input_circuit, expected_circuit): + result_circuit = noise_model.apply(input_circuit) + assert result_circuit == expected_circuit + + +@pytest.mark.xfail(raises=IndexError) +def test_remove_noise_at_invalid_index(): + noise_model = NoiseModel() + noise_model.remove_noise(index=0) + 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)) + + +@pytest.mark.xfail(raises=ValueError) +def test_add_invalid_criteria(): + noise_model = NoiseModel() + 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([]) diff --git a/test/unit_tests/braket/circuits/noise/test_observable_criteria.py b/test/unit_tests/braket/circuits/noise/test_observable_criteria.py new file mode 100644 index 00000000..406a3c7a --- /dev/null +++ b/test/unit_tests/braket/circuits/noise/test_observable_criteria.py @@ -0,0 +1,191 @@ +# 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 Observable, ResultType +from braket.circuits.noise_model import CriteriaKey, CriteriaKeyResult, ObservableCriteria + + +@pytest.mark.parametrize( + "observables, qubits", + [ + (None, range(3)), + (None, None), + (Observable.X, range(3)), + ([Observable.X], range(3)), + ([Observable.X, Observable.Y], range(3)), + (Observable.X, 1), + ([Observable.X], [1]), + ([Observable.X, Observable.Y], [1, 0]), + (Observable.X, None), + ], +) +def test_happy_case(observables, qubits): + criteria = ObservableCriteria(observables=observables, qubits=qubits) + assert criteria.applicable_key_types() == [ + CriteriaKey.OBSERVABLE, + CriteriaKey.QUBIT, + ] + if observables is None: + assert CriteriaKeyResult.ALL == criteria.get_keys(CriteriaKey.OBSERVABLE) + else: + assert Observable.X in criteria.get_keys(CriteriaKey.OBSERVABLE) + if qubits is None: + assert CriteriaKeyResult.ALL == criteria.get_keys(CriteriaKey.QUBIT) + else: + assert 1 in criteria.get_keys(CriteriaKey.QUBIT) + + +@pytest.mark.parametrize( + "observables, qubits", + [ + (None, range(3)), + (None, None), + (Observable.X, range(3)), + ([Observable.X], 1), + ([Observable.X], [1]), + ([Observable.X], [[1]]), + ([Observable.X, Observable.Y], range(3)), + ([Observable.X, Observable.Y], None), + ], +) +def test_serialization(observables, qubits): + test_criteria = ObservableCriteria(observables=observables, qubits=qubits) + serialized_criteria = test_criteria.to_dict() + assert serialized_criteria["__class__"] == "ObservableCriteria" + assert "observables" in serialized_criteria + assert "qubits" in serialized_criteria + deserialized_criteria = ObservableCriteria.from_dict(serialized_criteria) + assert test_criteria == deserialized_criteria + + +@pytest.mark.parametrize( + "observables, qubits, matching_result_type, non_matching_result_type", + [ + ( + None, + range(3), + [ + ResultType.Sample(Observable.X(), 0), + ResultType.Sample(Observable.Z(), 2), + ], + [ResultType.Sample(Observable.X(), 4)], + ), + ( + None, + None, + [ + ResultType.Sample(Observable.X(), 0), + ResultType.Sample(Observable.Z(), 10), + ], + [ResultType.Probability(0)], + ), + ( + Observable.X, + range(3), + [ + ResultType.Sample(Observable.X(), 0), + ResultType.Sample(Observable.X(), 1), + ResultType.Sample(Observable.X()), + ], + [ + ResultType.Sample(Observable.X(), 3), + ResultType.Sample(Observable.Y(), 1), + ], + ), + ( + Observable.X, + 1, + [ResultType.Sample(Observable.X(), 1)], + [ResultType.Sample(Observable.X(), 0)], + ), + ( + [Observable.X], + [1], + [ResultType.Sample(Observable.X(), 1)], + [ResultType.Sample(Observable.X(), 0)], + ), + ( + [Observable.X, Observable.Y], + range(3), + [ + ResultType.Sample(Observable.X(), 0), + ResultType.Sample(Observable.Y(), 2), + ], + [ + ResultType.Sample(Observable.X(), 3), + ResultType.Sample(Observable.Z(), 1), + ], + ), + ( + Observable.X, + None, + [ + ResultType.Sample(Observable.X(), 0), + ResultType.Sample(Observable.X(), 10), + ], + [ + ResultType.Sample(Observable.Y(), 1), + ResultType.Sample(Observable.Z(), 3), + ], + ), + ( + Observable.X, + [], + [ + ResultType.Sample(Observable.X(), 0), + ResultType.Sample(Observable.X(), 10), + ], + [ + ResultType.Sample(Observable.Y(), 1), + ResultType.Sample(Observable.Z(), 3), + ], + ), + ], +) +def test_matcher(observables, qubits, matching_result_type, non_matching_result_type): + criteria = ObservableCriteria(observables=observables, qubits=qubits) + for result_type in matching_result_type: + assert criteria.result_type_matches(result_type) + for instruction in non_matching_result_type: + assert not criteria.result_type_matches(instruction) + + +@pytest.mark.parametrize( + "observables, qubits", + [ + ([Observable.X], [[0, 1]]), + ], +) +@pytest.mark.xfail(raises=ValueError) +def test_invalid_params(observables, qubits): + ObservableCriteria(observables=observables, qubits=qubits) + + +def test_representation(): + criteria = ObservableCriteria(observables=[Observable.X], qubits=0) + str_representation = criteria.__repr__() + assert str_representation == "ObservableCriteria(observables={'X'}, qubits={0})" + + +def test_string(): + criteria = ObservableCriteria(observables=[Observable.X], qubits=0) + str_representation = criteria.__str__() + assert str_representation == "ObservableCriteria({'X'}, {0})" + + +def test_get_keys_for_unknown_keytypes(): + criteria = ObservableCriteria(observables=[Observable.X], qubits=0) + result = criteria.get_keys(CriteriaKey.UNITARY_GATE) + assert len(result) == 0 diff --git a/test/unit_tests/braket/circuits/noise/test_qubit_initialization_criteria.py b/test/unit_tests/braket/circuits/noise/test_qubit_initialization_criteria.py new file mode 100644 index 00000000..b61a8484 --- /dev/null +++ b/test/unit_tests/braket/circuits/noise/test_qubit_initialization_criteria.py @@ -0,0 +1,112 @@ +# 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.noise_model import CriteriaKey, CriteriaKeyResult, QubitInitializationCriteria + + +@pytest.mark.parametrize( + "qubits", + [1, range(3), None, [1], [[1]]], +) +def test_happy_case(qubits): + criteria = QubitInitializationCriteria(qubits=qubits) + assert criteria.applicable_key_types() == [CriteriaKey.QUBIT] + if qubits is None: + assert CriteriaKeyResult.ALL == criteria.get_keys(CriteriaKey.QUBIT) + else: + assert 1 in criteria.get_keys(CriteriaKey.QUBIT) + + +@pytest.mark.parametrize( + "qubits", + [ + 1, + range(3), + [1], + [[1]], + [0, 1], + [[1, 2]], + [[3, 4], [5, 6]], + None, + ], +) +def test_serialization(qubits): + test_criteria = QubitInitializationCriteria(qubits=qubits) + serialized_criteria = test_criteria.to_dict() + assert serialized_criteria["__class__"] == "QubitInitializationCriteria" + assert "qubits" in serialized_criteria + deserialized_criteria = QubitInitializationCriteria.from_dict(serialized_criteria) + assert test_criteria == deserialized_criteria + + +@pytest.mark.parametrize( + "qubits, input_qubits, expected_result", + [ + ( + range(3), + [0, 1, 2, [2], [[2]], 4, [5], [[6]], [0, 1]], + {0, 1, 2}, + ), + ( + None, + [0, 1, 2, [2], [[2]], 4, [5], [[6]], [0, 1]], + {0, 1, 2, 4, 5, 6}, + ), + ], +) +def test_matcher(qubits, input_qubits, expected_result): + criteria = QubitInitializationCriteria(qubits=qubits) + result = criteria.qubit_intersection(input_qubits) + assert result == expected_result + + +@pytest.mark.parametrize( + "qubits", + [ + ([[0, 1], 2]), + ], +) +@pytest.mark.xfail(raises=TypeError) +def test_invalid_param_types(qubits): + QubitInitializationCriteria(qubits=qubits) + + +@pytest.mark.parametrize( + "qubits", + [ + ([[0, 1], [2]]), + ], +) +@pytest.mark.xfail(raises=ValueError) +def test_invalid_params(qubits): + QubitInitializationCriteria(qubits=qubits) + + +def test_representation(): + criteria = QubitInitializationCriteria(qubits=range(4)) + str_representation = criteria.__repr__() + assert str_representation == "QubitInitializationCriteria(qubits={0, 1, 2, 3})" + + +def test_string(): + criteria = QubitInitializationCriteria(qubits=range(4)) + str_representation = criteria.__str__() + assert str_representation == "QubitInitializationCriteria({0, 1, 2, 3})" + + +def test_get_keys_for_unknown_keytypes(): + criteria = QubitInitializationCriteria(qubits=0) + result = criteria.get_keys(CriteriaKey.UNITARY_GATE) + assert len(result) == 0 diff --git a/test/unit_tests/braket/circuits/noise/test_unitary_gate_criteria.py b/test/unit_tests/braket/circuits/noise/test_unitary_gate_criteria.py new file mode 100644 index 00000000..8fb4636c --- /dev/null +++ b/test/unit_tests/braket/circuits/noise/test_unitary_gate_criteria.py @@ -0,0 +1,207 @@ +# 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 Gate, Instruction, Observable +from braket.circuits.gates import Unitary +from braket.circuits.noise_model import ( + CriteriaKey, + CriteriaKeyResult, + ObservableCriteria, + UnitaryGateCriteria, +) + + +def h_unitary(): + return Unitary(Gate.H().to_matrix()) + + +def i_unitary(): + return Unitary(Gate.I().to_matrix()) + + +def x_unitary(): + return Unitary(Gate.X().to_matrix()) + + +def cnot_unitary(): + return Unitary(Gate.CNot().to_matrix()) + + +@pytest.mark.parametrize( + "unitary, qubits", + [ + (h_unitary(), range(3)), + (h_unitary(), 1), + (h_unitary(), None), + ], +) +def test_happy_case(unitary, qubits): + criteria = UnitaryGateCriteria(unitary=unitary, qubits=qubits) + assert criteria.applicable_key_types() == [ + CriteriaKey.QUBIT, + CriteriaKey.UNITARY_GATE, + ] + assert h_unitary().to_matrix().tobytes() in criteria.get_keys(CriteriaKey.UNITARY_GATE) + if qubits is None: + assert CriteriaKeyResult.ALL == criteria.get_keys(CriteriaKey.QUBIT) + else: + assert 1 in criteria.get_keys(CriteriaKey.QUBIT) + + +def test_serialization(): + test_criteria = UnitaryGateCriteria(unitary=h_unitary(), qubits=range(3)) + serialized_criteria = test_criteria.to_dict() + assert serialized_criteria["__class__"] == "UnitaryGateCriteria" + assert "unitary" in serialized_criteria + assert "qubits" in serialized_criteria + deserialized_criteria = UnitaryGateCriteria.from_dict(serialized_criteria) + assert test_criteria == deserialized_criteria + + +@pytest.mark.parametrize( + "unitary, qubits, matching_instructions, non_matching_instructions", + [ + ( + h_unitary(), + range(3), + [ + Instruction(h_unitary(), 0), + Instruction(h_unitary(), 1), + ], + [ + Instruction(Gate.H, 3), + Instruction(h_unitary(), 3), + Instruction(i_unitary(), 1), + [Instruction(h_unitary(), 0)], + ], + ), + ( + cnot_unitary(), + [[0, 1]], + [ + Instruction(cnot_unitary(), [0, 1]), + ], + [ + Instruction(Gate.CNot(), [0, 1]), + ], + ), + ( + h_unitary(), + 1, + [Instruction(h_unitary(), 1)], + [Instruction(h_unitary(), 0)], + ), + ( + h_unitary(), + None, + [Instruction(h_unitary(), 0), Instruction(h_unitary(), 10)], + [Instruction(i_unitary(), 1), Instruction(x_unitary(), 3)], + ), + ( + h_unitary(), + [], + [Instruction(h_unitary(), 0), Instruction(h_unitary(), 10)], + [Instruction(i_unitary(), 1), Instruction(x_unitary(), 3)], + ), + ], +) +def test_matcher(unitary, qubits, matching_instructions, non_matching_instructions): + criteria = UnitaryGateCriteria(unitary=unitary, qubits=qubits) + for instruction in matching_instructions: + assert criteria.instruction_matches(instruction) + for instruction in non_matching_instructions: + assert not criteria.instruction_matches(instruction) + + +@pytest.mark.parametrize( + "unitary, qubits", + [ + (None, 1), + ([], 1), + ([h_unitary()], 1), + (np.zeros(15, dtype=int), 1), + ], +) +@pytest.mark.xfail(raises=TypeError) +def test_invalid_params(unitary, qubits): + UnitaryGateCriteria(unitary=unitary, qubits=qubits) + + +def test_representation(): + criteria = UnitaryGateCriteria(unitary=h_unitary(), qubits=0) + str_representation = criteria.__repr__() + assert ( + str_representation == "UnitaryGateCriteria(unitary=Unitary('qubit_count': 1), qubits={0})" + ) # noqa + + +def test_string(): + criteria = UnitaryGateCriteria(unitary=h_unitary(), qubits=0) + str_representation = criteria.__str__() + assert ( + str_representation == "UnitaryGateCriteria(unitary=Unitary('qubit_count': 1), qubits={0})" + ) # noqa + + +def test_get_keys_for_unknown_keytypes(): + criteria = UnitaryGateCriteria(unitary=h_unitary(), qubits=0) + result = criteria.get_keys(CriteriaKey.GATE) + assert len(result) == 0 + + +@pytest.mark.parametrize( + "criteria0, criteria1", + [ + ( + UnitaryGateCriteria(h_unitary(), range(3)), + UnitaryGateCriteria(h_unitary(), range(3)), + ), + ( + UnitaryGateCriteria(h_unitary(), [0, 1, 2]), + UnitaryGateCriteria(h_unitary(), range(3)), + ), + ( + UnitaryGateCriteria(h_unitary(), [1, 2, 0]), + UnitaryGateCriteria(h_unitary(), range(3)), + ), + (UnitaryGateCriteria(h_unitary()), UnitaryGateCriteria(h_unitary(), None)), + ], +) +def test_equal_criteria(criteria0, criteria1): + assert criteria0 == criteria1 + + +@pytest.mark.parametrize( + "criteria0, criteria1", + [ + ( + UnitaryGateCriteria(h_unitary(), range(3)), + UnitaryGateCriteria(i_unitary(), range(3)), + ), + ( + UnitaryGateCriteria(h_unitary(), [1, 2, 3]), + UnitaryGateCriteria(h_unitary(), range(3)), + ), + (UnitaryGateCriteria(h_unitary()), UnitaryGateCriteria(h_unitary(), range(3))), + (UnitaryGateCriteria(h_unitary(), range(3)), float(3)), + ( + UnitaryGateCriteria(h_unitary(), range(3)), + ObservableCriteria(Observable.X, range(3)), + ), + ], +) +def test_not_equal_criteria(criteria0, criteria1): + assert criteria0 != criteria1 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 61c3ad87..bda725fa 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -52,7 +52,7 @@ def test_one_gate_one_qubit_rotation_with_parameter(): "", "T : | 0 |", "", - "Unassigned parameters: {theta}.", + "Unassigned parameters: [theta].", ) _assert_correct_diagram(circ, expected) @@ -68,7 +68,7 @@ def test_one_gate_one_qubit_rotation_with_unicode(): "", "T : | 0 |", "", - "Unassigned parameters: {θ}.", + "Unassigned parameters: [θ].", ) _assert_correct_diagram(circ, expected) @@ -617,5 +617,38 @@ def test_noise_2qubit(): _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 _assert_correct_diagram(circ, expected): assert AsciiCircuitDiagram.build_diagram(circ) == "\n".join(expected) diff --git a/test/unit_tests/braket/circuits/test_noise.py b/test/unit_tests/braket/circuits/test_noise.py index 0c367bbc..bf5eb5e6 100644 --- a/test/unit_tests/braket/circuits/test_noise.py +++ b/test/unit_tests/braket/circuits/test_noise.py @@ -11,9 +11,12 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import json + import pytest from braket.circuits import Operator +from braket.circuits.free_parameter import FreeParameter from braket.circuits.noise import ( DampingNoise, GeneralizedAmplitudeDampingNoise, @@ -35,7 +38,7 @@ @pytest.fixture -def noise(): +def base_noise(): return Noise(qubit_count=1, ascii_symbols=["foo"]) @@ -250,96 +253,129 @@ def test_invalid_data_generalized_amplitude_damping_gamma(gamma): GeneralizedAmplitudeDampingNoise(gamma, probability, qubit_count, ascii_symbols) -def test_ascii_symbols(noise): - assert noise.ascii_symbols == ("foo",) +def test_ascii_symbols(base_noise): + assert base_noise.ascii_symbols == ("foo",) -def test_is_operator(noise): - assert isinstance(noise, Operator) +def test_is_operator(base_noise): + assert isinstance(base_noise, Operator) @pytest.mark.xfail(raises=NotImplementedError) -def test_to_ir_not_implemented_by_default(noise): - noise.to_ir(None) +def test_to_ir_not_implemented_by_default(base_noise): + base_noise.to_ir(None) @pytest.mark.xfail(raises=NotImplementedError) -def test_to_matrix_not_implemented_by_default(noise): - noise.to_matrix(None) - +def test_to_matrix_not_implemented_by_default(base_noise): + base_noise.to_matrix(None) -def test_noise_str(noise): - expected = "{}('qubit_count': {})".format(noise.name, noise.qubit_count) - assert str(noise) == expected - - -def test_single_probability_noise_str(single_probability_noise): - expected = "{}('probability': {}, 'qubit_count': {})".format( - single_probability_noise.name, - single_probability_noise.probability, - single_probability_noise.qubit_count, - ) - assert str(single_probability_noise) == expected - - -def test_single_probability_noise_34_str(single_probability_noise_34): - expected = "{}('probability': {}, 'qubit_count': {})".format( - single_probability_noise_34.name, - single_probability_noise_34.probability, - single_probability_noise_34.qubit_count, - ) - assert str(single_probability_noise_34) == expected - - -def test_single_probability_noise_1516_str(single_probability_noise_1516): - expected = "{}('probability': {}, 'qubit_count': {})".format( - single_probability_noise_1516.name, - single_probability_noise_1516.probability, - single_probability_noise_1516.qubit_count, - ) - assert str(single_probability_noise_1516) == expected +@pytest.mark.xfail(raises=NotImplementedError) +def test_invalid_deserializatoin(): + Noise.from_dict({}) -def test_pauli_noise_str(pauli_noise): - expected = "{}('probX': {}, 'probY': {}, 'probZ': {}, 'qubit_count': {})".format( - pauli_noise.name, - pauli_noise.probX, - pauli_noise.probY, - pauli_noise.probZ, - pauli_noise.qubit_count, - ) - assert str(pauli_noise) == expected +@pytest.mark.parametrize( + "noise, expected_string, expected_repr", + [ + (Noise(1, ["foo"]), "Noise('qubit_count': 1)", "Noise('qubit_count': 1)"), + ( + SingleProbabilisticNoise(0.1, 1, ["foo"]), + "SingleProbabilisticNoise(0.1)", + "SingleProbabilisticNoise('probability': 0.1, 'qubit_count': 1)", + ), + ( + DampingNoise(0.1, 1, ["foo"]), + "DampingNoise(0.1)", + "DampingNoise('gamma': 0.1, 'qubit_count': 1)", + ), + ( + GeneralizedAmplitudeDampingNoise(0.1, 0.2, 1, ["foo"]), + "GeneralizedAmplitudeDampingNoise(0.1, 0.2)", + "GeneralizedAmplitudeDampingNoise('gamma': 0.1, 'probability': 0.2, 'qubit_count': 1)", + ), + ( + PauliNoise(0.1, 0.2, 0.3, 1, ["foo"]), + "PauliNoise(0.1, 0.2, 0.3)", + "PauliNoise('probX': 0.1, 'probY': 0.2, 'probZ': 0.3, 'qubit_count': 1)", + ), + ( + MultiQubitPauliNoise({"X": 0.2}, 1, ["foo"]), + "MultiQubitPauliNoise({'X': 0.2})", + "MultiQubitPauliNoise('probabilities' : {'X': 0.2}, 'qubit_count': 1)", + ), + ], +) +def test_noise_str_repr(noise, expected_string, expected_repr): + assert str(noise) == expected_string + assert repr(noise) == expected_repr -def test_damping_noise_str(damping_noise): - expected = "{}('gamma': {}, 'qubit_count': {})".format( - damping_noise.name, - damping_noise.gamma, - damping_noise.qubit_count, - ) - assert str(damping_noise) == expected +@pytest.mark.parametrize( + "noise", + [ + SingleProbabilisticNoise(0.1, 1, ["foo"]), + DampingNoise(0.1, 1, ["foo"]), + GeneralizedAmplitudeDampingNoise(0.1, 0.2, 1, ["foo"]), + PauliNoise(0.1, 0.2, 0.3, 1, ["foo"]), + MultiQubitPauliNoise({"X": 0.2}, 1, ["foo"]), + ], +) +def test_noise_serialization(noise): + representation = noise.to_dict() + assert isinstance(representation, dict) + serialized = json.dumps(representation) + assert isinstance(serialized, str) -def test_generalized_amplitude_damping_noise_str(generalized_amplitude_damping_noise): - expected = "{}('gamma': {}, 'probability': {}, 'qubit_count': {})".format( - generalized_amplitude_damping_noise.name, - generalized_amplitude_damping_noise.gamma, - generalized_amplitude_damping_noise.probability, - generalized_amplitude_damping_noise.qubit_count, - ) - assert str(generalized_amplitude_damping_noise) == expected +@pytest.mark.parametrize( + "noise, equal_noise, unequal_noise, param_noise", + [ + ( + SingleProbabilisticNoise(0.1, 1, ["foo"]), + SingleProbabilisticNoise(0.1, 1, ["foo"]), + SingleProbabilisticNoise(0.2, 1, ["foo"]), + SingleProbabilisticNoise(FreeParameter("alpha"), 1, ["foo"]), + ), + ( + DampingNoise(0.1, 1, ["foo"]), + DampingNoise(0.1, 1, ["foo"]), + DampingNoise(0.2, 1, ["foo"]), + DampingNoise(FreeParameter("alpha"), 1, ["foo"]), + ), + ( + GeneralizedAmplitudeDampingNoise(0.1, 0.2, 1, ["foo"]), + GeneralizedAmplitudeDampingNoise(0.1, 0.2, 1, ["foo"]), + GeneralizedAmplitudeDampingNoise(0.2, 0.2, 1, ["foo"]), + GeneralizedAmplitudeDampingNoise(FreeParameter("alpha"), 0.2, 1, ["foo"]), + ), + ( + PauliNoise(0.1, 0.2, 0.3, 1, ["foo"]), + PauliNoise(0.1, 0.2, 0.3, 1, ["foo"]), + PauliNoise(0.2, 0.2, 0.3, 1, ["foo"]), + PauliNoise(FreeParameter("x"), FreeParameter("y"), FreeParameter("z"), 1, ["foo"]), + ), + ( + MultiQubitPauliNoise({"X": 0.2}, 1, ["foo"]), + MultiQubitPauliNoise({"X": 0.2}, 1, ["foo"]), + MultiQubitPauliNoise({"X": 0.3}, 1, ["foo"]), + MultiQubitPauliNoise({"X": FreeParameter("alpha")}, 1, ["foo"]), + ), + ], +) +def test_noise_equality(noise, equal_noise, unequal_noise, param_noise): + assert noise == noise + assert noise is noise + assert noise == equal_noise + assert noise is not equal_noise + assert noise != unequal_noise + assert noise != param_noise + assert noise != Noise(qubit_count=1, ascii_symbols=["foo"]) -def test_equality(): - noise_1 = Noise(qubit_count=1, ascii_symbols=["foo"]) - noise_2 = Noise(qubit_count=1, ascii_symbols=["foo"]) - other_noise = Noise.AmplitudeDamping(gamma=0.5) - non_noise = "non noise" - assert noise_1 == noise_2 - assert noise_1 is not noise_2 - assert noise_1 != other_noise - assert noise_1 != non_noise +def test_noise_base_not_equal_to_different_type(): + assert Noise(qubit_count=1, ascii_symbols=["foo"]) != "foo" def test_register_noise(): @@ -352,14 +388,47 @@ def __init__(self): @pytest.mark.parametrize( - "probs, qubit_count, ascii_symbols", [({"X": 0.1}, 1, ["PC"]), ({"XX": 0.1}, 2, ["PC2", "PC2"])] + "noise_class, params", + [ + (SingleProbabilisticNoise, {"probability": 0.6}), + (SingleProbabilisticNoise, {"probability": -0.1}), + (SingleProbabilisticNoise_34, {"probability": 0.76}), + (SingleProbabilisticNoise_34, {"probability": -0.1}), + (SingleProbabilisticNoise_1516, {"probability": 0.93755}), + (SingleProbabilisticNoise_1516, {"probability": -0.1}), + (MultiQubitPauliNoise, {"probabilities": {"X": 0.4, "Y": 0.7}}), + (MultiQubitPauliNoise, {"probabilities": {"X": 0.4, "Y": -0.7}}), + (PauliNoise, {"probX": 0.5, "probY": 0.5, "probZ": 0.5}), + (PauliNoise, {"probX": -0.1, "probY": 0, "probZ": 0}), + (DampingNoise, {"gamma": -0.1}), + (DampingNoise, {"gamma": 1.1}), + (GeneralizedAmplitudeDampingNoise, {"gamma": 0.1, "probability": -0.2}), + (GeneralizedAmplitudeDampingNoise, {"gamma": 0.1, "probability": 1.2}), + ], +) +@pytest.mark.xfail(raises=ValueError) +def test_invalid_values(noise_class, params): + noise_class(**params, qubit_count=1, ascii_symbols=["foo"]) + + +@pytest.mark.parametrize( + "probs, qubit_count, ascii_symbols", + [ + ({"X": 0.1}, 1, ["PC"]), + ({"XXY": 0.1}, 3, ["PC3", "PC3", "PC3"]), + ({"YX": 0.1, "IZ": 0.2}, 2, ["PC2", "PC2"]), + ], ) def test_multi_qubit_noise(probs, qubit_count, ascii_symbols): - MultiQubitPauliNoise(probs, qubit_count, ascii_symbols) + noise = MultiQubitPauliNoise(probs, qubit_count, ascii_symbols) + assert noise.probabilities == probs + assert noise.qubit_count == qubit_count + assert noise.ascii_symbols == tuple(ascii_symbols) + assert noise.parameters == [probs[key] for key in sorted(probs.keys())] @pytest.mark.xfail(raises=ValueError) -class TestMultiQubitNoise: +class TestInvalidMultiQubitNoise: qubit_count = 1 ascii_symbols = ["PC2"] diff --git a/test/unit_tests/braket/circuits/test_noises.py b/test/unit_tests/braket/circuits/test_noises.py index 37e420ab..afc4a215 100644 --- a/test/unit_tests/braket/circuits/test_noises.py +++ b/test/unit_tests/braket/circuits/test_noises.py @@ -11,11 +11,14 @@ # 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 import braket.ir.jaqcd as ir from braket.circuits import Circuit, Instruction, Noise, QubitSet +from braket.circuits.free_parameter import FreeParameter from braket.ir.jaqcd.shared_models import ( DampingProbability, DampingSingleProbability, @@ -403,6 +406,90 @@ def test_fixed_qubit_count(testclass, subroutine_name, irclass, irsubclasses, kw assert noise.qubit_count == fixed_qubit_count +@pytest.mark.parametrize( + "parameterized_noise", + [ + (Noise.BitFlip(0.1)), + (Noise.BitFlip(FreeParameter("alpha"))), + (Noise.PhaseFlip(0.1)), + (Noise.PhaseFlip(FreeParameter("alpha"))), + (Noise.Depolarizing(0.1)), + (Noise.Depolarizing(FreeParameter("alpha"))), + (Noise.AmplitudeDamping(0.1)), + (Noise.AmplitudeDamping(FreeParameter("alpha"))), + (Noise.GeneralizedAmplitudeDamping(0.1, 0.2)), + (Noise.GeneralizedAmplitudeDamping(FreeParameter("alpha"), FreeParameter("beta"))), + (Noise.PhaseDamping(0.1)), + (Noise.PhaseDamping(FreeParameter("alpha"))), + (Noise.TwoQubitDepolarizing(0.1)), + (Noise.TwoQubitDepolarizing(FreeParameter("alpha"))), + (Noise.TwoQubitDephasing(0.1)), + (Noise.TwoQubitDephasing(FreeParameter("alpha"))), + (Noise.TwoQubitPauliChannel({"XX": 0.1, "YY": 0.2})), + (Noise.TwoQubitPauliChannel({"XX": FreeParameter("x"), "YY": FreeParameter("y")})), + (Noise.PauliChannel(0.1, 0.2, 0.3)), + (Noise.PauliChannel(FreeParameter("x"), FreeParameter("y"), FreeParameter("z"))), + ], +) +def test_serialization(parameterized_noise): + serialized = parameterized_noise.to_dict() + serialized_str = json.dumps(serialized) + deserialized_dict = json.loads(serialized_str) + deserialized = Noise.from_dict(deserialized_dict) + assert deserialized == parameterized_noise + + +@pytest.mark.parametrize( + "parameterized_noise, params, expected_noise", + [ + (Noise.BitFlip(FreeParameter("alpha")), {"alpha": 0.1}, Noise.BitFlip(0.1)), + (Noise.PhaseFlip(FreeParameter("alpha")), {"alpha": 0.1}, Noise.PhaseFlip(0.1)), + (Noise.Depolarizing(FreeParameter("alpha")), {"alpha": 0.1}, Noise.Depolarizing(0.1)), + ( + Noise.AmplitudeDamping(FreeParameter("alpha")), + {"alpha": 0.1}, + Noise.AmplitudeDamping(0.1), + ), + ( + Noise.GeneralizedAmplitudeDamping(FreeParameter("alpha"), FreeParameter("beta")), + {"alpha": 0.1}, + Noise.GeneralizedAmplitudeDamping(0.1, FreeParameter("beta")), + ), + (Noise.PhaseDamping(FreeParameter("alpha")), {"alpha": 0.1}, Noise.PhaseDamping(0.1)), + ( + Noise.TwoQubitDepolarizing(FreeParameter("alpha")), + {"alpha": 0.1}, + Noise.TwoQubitDepolarizing(0.1), + ), + ( + Noise.TwoQubitDephasing(FreeParameter("alpha")), + {"alpha": 0.1}, + Noise.TwoQubitDephasing(0.1), + ), + ( + Noise.TwoQubitPauliChannel({"XX": FreeParameter("x"), "YY": FreeParameter("y")}), + {"x": 0.1}, + Noise.TwoQubitPauliChannel({"XX": 0.1, "YY": FreeParameter("y")}), + ), + ( + Noise.PauliChannel(FreeParameter("x"), FreeParameter("y"), FreeParameter("z")), + {"x": 0.1, "z": 0.2}, + Noise.PauliChannel(0.1, FreeParameter("y"), 0.2), + ), + ], +) +def test_parameter_binding(parameterized_noise, params, expected_noise): + result_noise = parameterized_noise.bind_values(**params) + assert result_noise == expected_noise + + +def test_parameterized_noise(): + noise = Noise.PauliChannel(FreeParameter("a"), 0.2, FreeParameter("b")) + assert noise.probX == FreeParameter("a") + assert noise.probY == 0.2 + assert noise.probZ == FreeParameter("b") + + # Additional Unitary noise tests From a4f12d12256257954ac25412532b59178f576413 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 18 May 2022 18:24:22 +0000 Subject: [PATCH 0464/1165] prepare release v1.22.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 92afbdd1..45cb3d41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.22.0 (2022-05-18) + +### Features + + * Noise models + ## v1.21.1 (2022-05-17) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 140307d1..0eb9baed 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.21.2.dev0" +__version__ = "1.22.0" From ba694784e64d2aa9efa702f3a22862b88c5a0a61 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 18 May 2022 18:24:22 +0000 Subject: [PATCH 0465/1165] update development version to v1.22.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 0eb9baed..0382647a 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.22.0" +__version__ = "1.22.1.dev0" From 556f0d6bba9d63ef28e9ca7c5761f0c3546cdedd Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 18 May 2022 15:18:13 -0700 Subject: [PATCH 0466/1165] feat: allow user to set region+endpoint through env variables (#349) --- src/braket/aws/aws_session.py | 8 ++++++-- test/unit_tests/braket/aws/test_aws_session.py | 9 +++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 65117be5..a4eb6c36 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -60,8 +60,12 @@ def __init__(self, boto_session=None, braket_client=None, config=None, default_b ) self.braket_client = braket_client else: - self.boto_session = boto_session or boto3.Session() - self.braket_client = self.boto_session.client("braket", config=self._config) + self.boto_session = boto_session or boto3.Session( + region_name=os.environ.get("AWS_REGION") + ) + 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) diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index b51a4c05..016cbbdc 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -146,7 +146,7 @@ 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) + boto_session.client.assert_any_call("braket", config=None, endpoint_url=None) def test_user_supplied_braket_client(): @@ -158,10 +158,15 @@ def test_user_supplied_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) - boto_session.client.assert_any_call("braket", config=config) + boto_session.client.assert_any_call( + "braket", + config=config, + endpoint_url="some-endpoint", + ) def test_region(): From b613c316cbf9e19465f8f679ee9c82ab7d55d00c Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Thu, 19 May 2022 11:05:03 -0700 Subject: [PATCH 0467/1165] feat: allow job role to be set via env variable (#352) Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> --- src/braket/jobs/quantum_job_creation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py index d5292c77..bffb335a 100644 --- a/src/braket/jobs/quantum_job_creation.py +++ b/src/braket/jobs/quantum_job_creation.py @@ -13,6 +13,7 @@ from __future__ import annotations import importlib.util +import os import re import sys import tarfile @@ -145,7 +146,7 @@ def prepare_quantum_job( aws_session = aws_session or AwsSession() device_config = DeviceConfig(device) job_name = job_name or _generate_default_job_name(image_uri) - role_arn = role_arn or aws_session.get_default_jobs_role() + 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 {} From b64284efd5d74c5db603533f93504b42a59137e7 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 19 May 2022 20:20:22 +0000 Subject: [PATCH 0468/1165] prepare release v1.23.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 45cb3d41..49a13043 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.23.0 (2022-05-19) + +### Features + + * allow job role to be set via env variable + * allow user to set region+endpoint through env variables + ## v1.22.0 (2022-05-18) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 0382647a..237e385a 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.22.1.dev0" +__version__ = "1.23.0" From c28837ac271b7e5393bb0f40d4696c287a5b2476 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 19 May 2022 20:20:22 +0000 Subject: [PATCH 0469/1165] update development version to v1.23.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 237e385a..c202eff8 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.23.0" +__version__ = "1.23.1.dev0" From 7467ab5b864bba665e047ced397a5be33f01ace3 Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Fri, 20 May 2022 13:04:55 -0700 Subject: [PATCH 0470/1165] fix: removing validation for disable_qubit_rewiring (#357) Removing validation for disable_qubit_rewiring and letting the service handle it. As a result of this change, customers will no longer get a ValueError when providing the incorrect verbatim/disable_qubit_rewiring combination - instead they will get a ValidationException from the service call. --- src/braket/aws/aws_quantum_task.py | 7 ------- .../braket/aws/test_aws_quantum_task.py | 18 +++++++----------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index ee77bfb4..5d9fc394 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -102,8 +102,6 @@ def create( without any rewiring downstream, if this is supported by the device. Only applies to digital, gate-based circuits (as opposed to annealing problems). If ``True``, no qubit rewiring is allowed; if ``False``, qubit rewiring is allowed. - If the circuit has frozen qubits (``circuit.has_frozen_qubits==True``), then this - must be True, or running will throw an exception. Default: False tags (Dict[str, str]): Tags, which are Key-Value pairs to add to this quantum task. @@ -446,11 +444,6 @@ def _( **kwargs, ) -> AwsQuantumTask: validate_circuit_and_shots(circuit, create_task_kwargs["shots"]) - if circuit.qubits_frozen and not disable_qubit_rewiring: - raise ValueError( - "disable_qubit_rewiring must be True to run circuit with compiler directives" - ) - # TODO: Update this to use `deviceCapabilities` from Amazon Braket's GetDevice operation # in order to decide what parameters to build. paradigm_parameters = GateModelParameters( 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 6b1c44be..093c0236 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -432,9 +432,12 @@ def test_from_circuit_with_disabled_rewiring( @pytest.mark.parametrize( - "device_arn,device_parameters_class", [(RIGETTI_ARN, RigettiDeviceParameters)] + "device_arn,device_parameters_class, disable_qubit_rewiring", + [(RIGETTI_ARN, RigettiDeviceParameters, True), (RIGETTI_ARN, RigettiDeviceParameters, False)], ) -def test_from_circuit_with_verbatim(device_arn, device_parameters_class, aws_session): +def test_from_circuit_with_verbatim( + device_arn, device_parameters_class, disable_qubit_rewiring, aws_session +): circ = Circuit().add_verbatim_box(Circuit().h(0)) mocked_task_arn = "task-arn-1" aws_session.create_quantum_task.return_value = mocked_task_arn @@ -446,7 +449,7 @@ def test_from_circuit_with_verbatim(device_arn, device_parameters_class, aws_ses circ, S3_TARGET, shots, - disable_qubit_rewiring=True, + disable_qubit_rewiring=disable_qubit_rewiring, ) assert task == AwsQuantumTask(mocked_task_arn, aws_session) @@ -458,19 +461,12 @@ def test_from_circuit_with_verbatim(device_arn, device_parameters_class, aws_ses shots, device_parameters_class( paradigmParameters=GateModelParameters( - qubitCount=circ.qubit_count, disableQubitRewiring=True + qubitCount=circ.qubit_count, disableQubitRewiring=disable_qubit_rewiring ) ), ) -@pytest.mark.xfail(raises=ValueError) -def test_from_circuit_with_verbatim_qubit_rewiring_not_disabled(aws_session): - circ = Circuit().add_verbatim_box(Circuit().h(0)) - shots = 57 - AwsQuantumTask.create(aws_session, RIGETTI_ARN, circ, S3_TARGET, shots) - - @pytest.mark.xfail(raises=ValueError) def test_from_circuit_with_shots_value_error(aws_session, arn, circuit): mocked_task_arn = "task-arn-1" From 4f20ce62bb5c24888f34f898aee1a6fc755181a3 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 20 May 2022 20:32:38 +0000 Subject: [PATCH 0471/1165] prepare release v1.23.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 49a13043..6e8d3846 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.23.1 (2022-05-20) + +### Bug Fixes and Other Changes + + * removing validation for disable_qubit_rewiring + ## v1.23.0 (2022-05-19) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index c202eff8..b354d03d 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.23.1.dev0" +__version__ = "1.23.1" From 9ee936a1d5eb3562f71369eb933db4ca1f2772a4 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 20 May 2022 20:32:38 +0000 Subject: [PATCH 0472/1165] update development version to v1.23.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 b354d03d..6e3c25de 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.23.1" +__version__ = "1.23.2.dev0" From 23a7ebdaded68bab64a654aecb4c50235a07b6a5 Mon Sep 17 00:00:00 2001 From: Or Ostrovsky <93072774+orclassiq@users.noreply.github.com> Date: Tue, 24 May 2022 00:29:20 +0300 Subject: [PATCH 0473/1165] change: pin coverage dependency only for test extra (#304) Move coverage from an install requirement to a testing requirement --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5abd002d..14491ab4 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,6 @@ "backoff", "boltons", "boto3", - "coverage==5.5", "nest-asyncio", "networkx", "numpy", @@ -41,6 +40,7 @@ "test": [ "black", "botocore", + "coverage==5.5", "flake8", "isort", "jsonschema==3.2.0", From 8e067d5d762be41585fd9af49476d56c8d1bc8b3 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 24 May 2022 18:24:23 +0000 Subject: [PATCH 0474/1165] prepare release v1.23.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 6e8d3846..3dd717ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.23.2 (2022-05-24) + +### Bug Fixes and Other Changes + + * pin coverage dependency only for test extra + ## v1.23.1 (2022-05-20) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 6e3c25de..5ac1694a 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.23.2.dev0" +__version__ = "1.23.2" From c2538a4008d77277c89eb97801739abdf2d74568 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 24 May 2022 18:24:23 +0000 Subject: [PATCH 0475/1165] update development version to v1.23.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 5ac1694a..1639ff2a 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.23.2" +__version__ = "1.23.3.dev0" From 6fffba77d7c225f0a49f890fe5121438c3784c59 Mon Sep 17 00:00:00 2001 From: Stephen Face <60493521+shpface@users.noreply.github.com> Date: Wed, 1 Jun 2022 20:03:25 -0700 Subject: [PATCH 0476/1165] feat: Add support for photonic computations (#376) --- src/braket/aws/aws_device.py | 13 +-- src/braket/aws/aws_quantum_task.py | 53 ++++++++++-- src/braket/aws/aws_quantum_task_batch.py | 7 +- src/braket/tasks/__init__.py | 3 + src/braket/tasks/local_quantum_task.py | 20 ++++- .../photonic_model_quantum_task_result.py | 66 ++++++++++++++ src/braket/tasks/quantum_task.py | 7 +- .../braket/aws/common_test_utils.py | 24 ++++++ .../braket/aws/test_aws_quantum_task.py | 46 +++++++++- ...test_photonic_model_quantum_task_result.py | 86 +++++++++++++++++++ 10 files changed, 302 insertions(+), 23 deletions(-) create mode 100644 src/braket/tasks/photonic_model_quantum_task_result.py create mode 100644 test/unit_tests/braket/tasks/test_photonic_model_quantum_task_result.py diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index a032c540..fb438074 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -29,6 +29,7 @@ from braket.device_schema import DeviceCapabilities, ExecutionDay, GateModelQpuParadigmProperties from braket.device_schema.dwave import DwaveProviderProperties from braket.devices.device import Device +from braket.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQasmProgram from braket.schema_common import BraketSchemaBase @@ -81,7 +82,7 @@ def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): def run( self, - task_specification: Union[Circuit, Problem, OpenQasmProgram], + task_specification: Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram], s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, shots: Optional[int] = None, poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, @@ -94,8 +95,8 @@ def run( annealing problem. Args: - task_specification (Union[Circuit, Problem]): Specification of task - (circuit or annealing problem) to run on device. + task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]): + Specification of task (circuit or annealing problem or program) to run on device. s3_destination_folder (AwsSession.S3DestinationFolder, optional): The S3 location to save the task's results to. Default is `/tasks` if evoked outside of a Braket Job, `/jobs//tasks` if evoked inside of @@ -163,7 +164,7 @@ def run( def run_batch( self, - task_specifications: List[Union[Circuit, Problem, OpenQasmProgram]], + task_specifications: List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]], s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, shots: Optional[int] = None, max_parallel: Optional[int] = None, @@ -176,8 +177,8 @@ def run_batch( """Executes a batch of tasks in parallel Args: - task_specifications (List[Union[Circuit, Problem]]): List of circuits - or annealing problems to run on device. + task_specifications (List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]]): + List of circuits or annealing problems to run on device. s3_destination_folder (AwsSession.S3DestinationFolder, optional): The S3 location to save the tasks' results to. Default is `/tasks` if evoked outside of a Braket Job, `/jobs//tasks` if evoked inside of diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 5d9fc394..d9ac8f5c 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -41,10 +41,16 @@ from braket.device_schema.oqc import OqcDeviceParameters from braket.device_schema.rigetti import RigettiDeviceParameters 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.schema_common import BraketSchemaBase -from braket.task_result import AnnealingTaskResult, GateModelTaskResult -from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult, QuantumTask +from braket.task_result import AnnealingTaskResult, GateModelTaskResult, PhotonicModelTaskResult +from braket.tasks import ( + AnnealingQuantumTaskResult, + GateModelQuantumTaskResult, + PhotonicModelQuantumTaskResult, + QuantumTask, +) class AwsQuantumTask(QuantumTask): @@ -64,7 +70,7 @@ class AwsQuantumTask(QuantumTask): def create( aws_session: AwsSession, device_arn: str, - task_specification: Union[Circuit, Problem, OpenQasmProgram], + task_specification: Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram], s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, device_parameters: Dict[str, Any] = None, @@ -82,8 +88,8 @@ def create( device_arn (str): The ARN of the quantum device. - task_specification (Union[Circuit, Problem]): The specification of the task - to run on device. + task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]): + The specification of the task to run on device. s3_destination_folder (AwsSession.S3DestinationFolder): NamedTuple, with bucket for index 0 and key for index 1, that specifies the Amazon S3 bucket and folder @@ -190,7 +196,9 @@ def __init__( self._logger = logger self._metadata: Dict[str, Any] = {} - self._result: Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult] = None + self._result: Union[ + GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult + ] = None @staticmethod def _aws_session_for_task_arn(task_arn: str) -> AwsSession: @@ -276,7 +284,11 @@ def _update_status_if_nonterminal(self): cached = self._status(True) return cached if cached in self.TERMINAL_STATES else self._status(metadata_absent) - def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: + def result( + self, + ) -> Union[ + GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult + ]: """ Get the quantum task result by polling Amazon Braket to see if the task is completed. Once the task is completed, the result is retrieved from S3 and returned as a @@ -342,7 +354,9 @@ async def _create_future(self) -> asyncio.Task: async def _wait_for_completion( self, - ) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: + ) -> Union[ + GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult + ]: """ Waits for the quantum task to be completed, then returns the result from the S3 bucket. @@ -404,7 +418,7 @@ def __hash__(self) -> int: @singledispatch def _create_internal( - task_specification: Union[Circuit, Problem], + task_specification: Union[Circuit, Problem, BlackbirdProgram], aws_session: AwsSession, create_task_kwargs: Dict[str, Any], device_arn: str, @@ -432,6 +446,22 @@ def _( 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, + _device_parameters: Union[dict, BraketSchemaBase], + _disable_qubit_rewiring, + *args, + **kwargs, +) -> AwsQuantumTask: + create_task_kwargs.update({"action": blackbird_program.json()}) + task_arn = aws_session.create_quantum_task(**create_task_kwargs) + return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) + + @_create_internal.register def _( circuit: Circuit, @@ -549,3 +579,8 @@ def _(result: GateModelTaskResult) -> GateModelQuantumTaskResult: @_format_result.register def _(result: AnnealingTaskResult) -> AnnealingQuantumTaskResult: return AnnealingQuantumTaskResult.from_object(result) + + +@_format_result.register +def _(result: PhotonicModelTaskResult) -> PhotonicModelQuantumTaskResult: + return PhotonicModelQuantumTaskResult.from_object(result) diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 848dd8bc..d45ee3e3 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -21,6 +21,7 @@ from braket.aws.aws_quantum_task import AwsQuantumTask from braket.aws.aws_session import AwsSession from braket.circuits import Circuit +from braket.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQasmProgram @@ -42,7 +43,7 @@ def __init__( self, aws_session: AwsSession, device_arn: str, - task_specifications: List[Union[Circuit, Problem, OpenQasmProgram]], + task_specifications: List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]], s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, max_parallel: int, @@ -57,8 +58,8 @@ def __init__( Args: aws_session (AwsSession): AwsSession to connect to AWS with. device_arn (str): The ARN of the quantum device. - task_specification (Union[Circuit, Problem]): The specification of the task - to run on device. + task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]): + The specification of the task to run on device. 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 task results in. diff --git a/src/braket/tasks/__init__.py b/src/braket/tasks/__init__.py index ccf3fc14..648851e3 100644 --- a/src/braket/tasks/__init__.py +++ b/src/braket/tasks/__init__.py @@ -14,6 +14,9 @@ import braket.ipython_utils as ipython_utils 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.quantum_task import QuantumTask # noqa: F401 # Apply nest_asyncio if currently running within Jupyter. This ensures anything that uses diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py index 64edf7d5..f9d76999 100644 --- a/src/braket/tasks/local_quantum_task.py +++ b/src/braket/tasks/local_quantum_task.py @@ -14,7 +14,12 @@ import asyncio from typing import Union -from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult, QuantumTask +from braket.tasks import ( + AnnealingQuantumTaskResult, + GateModelQuantumTaskResult, + PhotonicModelQuantumTaskResult, + QuantumTask, +) class LocalQuantumTask(QuantumTask): @@ -23,7 +28,12 @@ class LocalQuantumTask(QuantumTask): Since this class is instantiated with the results, cancel() and run_async() are unsupported. """ - def __init__(self, result: Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]): + def __init__( + self, + result: Union[ + GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult + ], + ): self._id = result.task_metadata.id self._result = result @@ -37,7 +47,11 @@ def cancel(self) -> None: def state(self) -> str: return "COMPLETED" - def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: + def result( + self, + ) -> Union[ + GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult + ]: return self._result def async_result(self) -> asyncio.Task: diff --git a/src/braket/tasks/photonic_model_quantum_task_result.py b/src/braket/tasks/photonic_model_quantum_task_result.py new file mode 100644 index 00000000..85358455 --- /dev/null +++ b/src/braket/tasks/photonic_model_quantum_task_result.py @@ -0,0 +1,66 @@ +# 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 dataclasses import dataclass + +import numpy as np + +from braket.task_result import AdditionalMetadata, PhotonicModelTaskResult, TaskMetadata + + +@dataclass +class PhotonicModelQuantumTaskResult: + task_metadata: TaskMetadata + additional_metadata: AdditionalMetadata + measurements: np.ndarray = None + + def __eq__(self, other) -> bool: + if isinstance(other, PhotonicModelQuantumTaskResult): + return self.task_metadata.id == other.task_metadata.id + return NotImplemented + + @staticmethod + def from_object(result: PhotonicModelTaskResult): + """ + Create PhotonicModelQuantumTaskResult from PhotonicModelTaskResult object. + + Args: + result (PhotonicModelTaskResult): PhotonicModelTaskResult object + + Returns: + PhotonicModelQuantumTaskResult: A PhotonicModelQuantumTaskResult based on the given dict + + Raises: + ValueError: If "measurements" is not a key in the result dict + """ + return PhotonicModelQuantumTaskResult._from_object_internal(result) + + @staticmethod + def from_string(result: str): + return PhotonicModelQuantumTaskResult._from_object_internal( + PhotonicModelTaskResult.parse_raw(result) + ) + + @classmethod + def _from_object_internal(cls, result: PhotonicModelTaskResult): + task_metadata = result.taskMetadata + additional_metadata = result.additionalMetadata + if result.measurements is not None: + measurements = np.asarray(result.measurements, dtype=int) + else: + measurements = None + return cls( + task_metadata=task_metadata, + additional_metadata=additional_metadata, + measurements=measurements, + ) diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py index 84c444f0..0c40c783 100644 --- a/src/braket/tasks/quantum_task.py +++ b/src/braket/tasks/quantum_task.py @@ -17,6 +17,7 @@ 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 QuantumTask(ABC): @@ -36,7 +37,11 @@ def state(self) -> str: """str: State of the quantum task""" @abstractmethod - def result(self) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: + def result( + self, + ) -> Union[ + GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult + ]: """ Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: Get the quantum task result. Call async_result if you want the result in an asynchronous way. diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 9ea1e2b1..16532bac 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -22,6 +22,7 @@ OQC_ARN = "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" SV1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" TN1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/tn1" +XANADU_ARN = "arn:aws:braket:us-east-1::device/qpu/xanadu/Borealis" RIGETTI_REGION = "us-west-1" @@ -128,6 +129,29 @@ class MockS3: } ) + 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]", + }, + "xanaduMetadata": { + "compiledProgram": "DECLARE ro BIT[2];", + }, + }, + } + ) + def run_and_assert( aws_quantum_task_mock, 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 093c0236..8b8e5b4b 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -36,8 +36,13 @@ from braket.device_schema.oqc import OqcDeviceParameters from braket.device_schema.rigetti import RigettiDeviceParameters 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.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult +from braket.tasks import ( + AnnealingQuantumTaskResult, + GateModelQuantumTaskResult, + PhotonicModelQuantumTaskResult, +) S3_TARGET = AwsSession.S3DestinationFolder("foo", "bar") @@ -45,6 +50,7 @@ RIGETTI_ARN = "device/qpu/rigetti" OQC_ARN = "device/qpu/oqc" SIMULATOR_ARN = "device/quantum-simulator" +XANADU_ARN = "device/qpu/xanadu" DEVICE_PARAMETERS = [ (IONQ_ARN, IonqDeviceParameters), @@ -76,6 +82,11 @@ def annealing_task(aws_session): return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) +@pytest.fixture +def photonic_model_task(aws_session): + return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) + + @pytest.fixture def arn(): return "foo:bar:arn" @@ -96,6 +107,11 @@ def openqasm_program(): return OpenQasmProgram(source="OPENQASM 3.0; h $0;") +@pytest.fixture +def blackbird_program(): + return BlackbirdProgram(source="Vac | q[0]") + + def test_equality(arn, aws_session): quantum_task_1 = AwsQuantumTask(arn, aws_session) quantum_task_2 = AwsQuantumTask(arn, aws_session) @@ -231,6 +247,20 @@ def test_result_annealing(annealing_task): ) +def test_result_photonic_model(photonic_model_task): + _mock_metadata(photonic_model_task._aws_session, "COMPLETED") + _mock_s3(photonic_model_task._aws_session, MockS3.MOCK_S3_RESULT_PHOTONIC_MODEL) + + expected = PhotonicModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_PHOTONIC_MODEL) + assert photonic_model_task.result() == expected + + s3_bucket = photonic_model_task.metadata()["outputS3Bucket"] + s3_object_key = photonic_model_task.metadata()["outputS3Directory"] + photonic_model_task._aws_session.retrieve_s3_object_body.assert_called_with( + s3_bucket, f"{s3_object_key}/results.json" + ) + + @pytest.mark.xfail(raises=TypeError) def test_result_invalid_type(circuit_task): _mock_metadata(circuit_task._aws_session, "COMPLETED") @@ -379,6 +409,20 @@ def test_create_openqasm_program(aws_session, arn, openqasm_program): ) +def test_create_blackbird_program(aws_session, arn, blackbird_program): + aws_session.create_quantum_task.return_value = arn + shots = 21 + AwsQuantumTask.create(aws_session, XANADU_ARN, blackbird_program, S3_TARGET, shots) + + _assert_create_quantum_task_called_with( + aws_session, + XANADU_ARN, + blackbird_program.json(), + S3_TARGET, + shots, + ) + + @pytest.mark.parametrize("device_arn,device_parameters_class", DEVICE_PARAMETERS) def test_from_circuit_with_shots(device_arn, device_parameters_class, aws_session, circuit): mocked_task_arn = "task-arn-1" diff --git a/test/unit_tests/braket/tasks/test_photonic_model_quantum_task_result.py b/test/unit_tests/braket/tasks/test_photonic_model_quantum_task_result.py new file mode 100644 index 00000000..55617a1f --- /dev/null +++ b/test/unit_tests/braket/tasks/test_photonic_model_quantum_task_result.py @@ -0,0 +1,86 @@ +# 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.ir.blackbird import Program as BlackbirdProgram +from braket.task_result import ( + AdditionalMetadata, + PhotonicModelTaskResult, + TaskMetadata, + XanaduMetadata, +) +from braket.tasks import PhotonicModelQuantumTaskResult + + +@pytest.fixture +def task_metadata(): + return TaskMetadata(**{"id": "task_arn", "deviceId": "arn1", "shots": 100}) + + +@pytest.fixture +def xanadu_metadata(): + return XanaduMetadata(compiledProgram="DECLARE ro BIT[2];") + + +@pytest.fixture +def blackbird_program(): + return BlackbirdProgram(source="Vac | q[0]") + + +@pytest.fixture +def additional_metadata(blackbird_program, xanadu_metadata): + return AdditionalMetadata(action=blackbird_program, xanaduMetadata=xanadu_metadata) + + +@pytest.fixture +def measurements(): + return [[[1, 2, 3, 4]], [[4, 3, 2, 1]], [[0, 0, 0, 0]]] + + +@pytest.fixture +def result_1(measurements, task_metadata, additional_metadata): + return PhotonicModelTaskResult( + measurements=measurements, + taskMetadata=task_metadata, + additionalMetadata=additional_metadata, + ) + + +@pytest.fixture +def result_1_str(result_1): + return result_1.json() + + +@pytest.fixture +def empty_result(task_metadata, additional_metadata): + task_metadata.id = "empty_arn" + return PhotonicModelTaskResult( + taskMetadata=task_metadata, additionalMetadata=additional_metadata + ) + + +def test_from_object_equals_from_string(result_1, result_1_str): + assert PhotonicModelQuantumTaskResult.from_object( + result_1 + ) == PhotonicModelQuantumTaskResult.from_string(result_1_str) + + +def test_equality(result_1, empty_result): + quantum_result1 = PhotonicModelQuantumTaskResult.from_object(result_1) + quantum_empty = PhotonicModelQuantumTaskResult.from_object(empty_result) + non_result = "not a quantum task result" + + assert quantum_result1 == quantum_result1 + assert quantum_result1 != quantum_empty + assert quantum_result1 != non_result From 0eb92538f83e4752c231d28c349c39a4e1c5f10d Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 2 Jun 2022 03:26:37 +0000 Subject: [PATCH 0477/1165] prepare release v1.24.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 3dd717ab..2f8f207b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.24.0 (2022-06-02) + +### Features + + * Add support for photonic computations + ## v1.23.2 (2022-05-24) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 1639ff2a..b8a7a47b 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.23.3.dev0" +__version__ = "1.24.0" From 18908a90b71eb33efc276d88a866c8073a061fc9 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 2 Jun 2022 03:26:37 +0000 Subject: [PATCH 0478/1165] update development version to v1.24.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 b8a7a47b..279481ce 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.24.0" +__version__ = "1.24.1.dev0" From df3fd688a128e0e43a3df8ec1d5fb4911b2a45ba Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Tue, 7 Jun 2022 17:34:39 -0700 Subject: [PATCH 0479/1165] feat: Add method for updating the user agent for braket client (#379) * feat: Add method for updating the user agent for braket client * reformat --- src/braket/aws/aws_device.py | 4 ++++ src/braket/aws/aws_session.py | 22 ++++++++++++++++++- test/unit_tests/braket/aws/test_aws_device.py | 1 + .../unit_tests/braket/aws/test_aws_session.py | 9 ++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index fb438074..9902c847 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -296,6 +296,10 @@ def provider_name(self) -> str: """str: Return the provider name""" return self._provider_name + @property + def aws_session(self) -> AwsSession: + return self._aws_session + @property def arn(self) -> str: """str: Return the ARN of the device""" diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index a4eb6c36..df5c23bb 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -138,6 +138,19 @@ def _notebook_instance_version(): f"{self.braket_client._client_config.user_agent} {additional_user_agent_fields}" ) + 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 + header. This method is typically only relevant for libraries integrating with the + Amazon Braket SDK. + + Args: + user_agent (str): The user_agent value to append to the header. + """ + existing_user_agent = self.braket_client._client_config.user_agent + if user_agent not in existing_user_agent: + self.braket_client._client_config.user_agent = f"{existing_user_agent} {user_agent}" + # # Quantum Tasks # @@ -733,4 +746,11 @@ def copy_session( ) else: boto_session = boto3.Session(region_name=new_region) - return AwsSession(boto_session=boto_session, config=config, default_bucket=default_bucket) + copied_session = AwsSession( + boto_session=boto_session, config=config, default_bucket=default_bucket + ) + # Preserve user_agent information + copied_session.braket_client._client_config.user_agent = ( + self.braket_client._client_config.user_agent + ) + return copied_session diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 4ac896c1..3b9b3654 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -355,6 +355,7 @@ def test_device_aws_session(device_capabilities, get_device_data, arn): mock_session.region = RIGETTI_REGION device = AwsDevice(arn, mock_session) _assert_device_fields(device, device_capabilities, get_device_data) + assert device.aws_session == mock_session @patch("braket.aws.aws_device.AwsSession") diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index 016cbbdc..a0a5ce0e 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -1232,8 +1232,10 @@ 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_client._client_config.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") + assert copied_session.braket_client._client_config.user_agent == "foo/bar" assert copied_session._default_bucket is None @@ -1256,3 +1258,10 @@ def test_copy_session_custom_default_bucket(mock_boto, aws_session): aws_session._custom_default_bucket = True copied_session = AwsSession.copy_session(aws_session) assert copied_session._default_bucket == "my-own-default" + + +def test_add_braket_user_agent(aws_session): + user_agent = "newAgent/1.0" + aws_session.add_braket_user_agent(user_agent) + aws_session.add_braket_user_agent(user_agent) + assert aws_session.braket_client._client_config.user_agent.count(user_agent) == 1 From a18106c7092005266301940f5ec7b4a8afed37ba Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 8 Jun 2022 18:28:03 +0000 Subject: [PATCH 0480/1165] prepare release v1.25.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 2f8f207b..99db985b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.25.0 (2022-06-08) + +### Features + + * Add method for updating the user agent for braket client + ## v1.24.0 (2022-06-02) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 279481ce..5bdaca65 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.24.1.dev0" +__version__ = "1.25.0" From af9a7fb6bd0dff694b07c566426ba97674a567c2 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 8 Jun 2022 18:28:03 +0000 Subject: [PATCH 0481/1165] update development version to v1.25.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 5bdaca65..ef63be00 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.25.0" +__version__ = "1.25.1.dev0" From d3a71d79d789b52e037e7a8ad78d544ccb22a77e Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 15 Jun 2022 16:09:59 -0700 Subject: [PATCH 0482/1165] fix: change failureReason string check to let test pass (#380) --- test/integ_tests/test_create_quantum_job.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py index b88405e6..4eccaa63 100644 --- a/test/integ_tests/test_create_quantum_job.py +++ b/test/integ_tests/test_create_quantum_job.py @@ -70,8 +70,8 @@ def test_failed_quantum_job(aws_session, capsys): for data in logs_to_validate: assert data in log_data - assert job.metadata()["failureReason"] == ( - "AlgorithmError: Job at job_test_script:start_here exited with exit code: 1" + assert job.metadata()["failureReason"].startswith( + "AlgorithmError: Job at job_test_script:start_here" ) From 3a7b55a135fa52be40eed607db2276c625c1120b Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 16 Jun 2022 18:00:43 +0000 Subject: [PATCH 0483/1165] prepare release v1.25.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 99db985b..2ca6089c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.25.1 (2022-06-16) + +### Bug Fixes and Other Changes + + * change failureReason string check to let test pass + ## v1.25.0 (2022-06-08) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index ef63be00..891027e2 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.25.1.dev0" +__version__ = "1.25.1" From 2a76cc6de58a1f42f57f16ced768356c26a50920 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 16 Jun 2022 18:00:43 +0000 Subject: [PATCH 0484/1165] update development version to v1.25.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 891027e2..82a6cea6 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.25.1" +__version__ = "1.25.2.dev0" From 2140084d1af2bfb176872c408abfa56f8e365fb9 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Thu, 16 Jun 2022 19:05:31 -0700 Subject: [PATCH 0485/1165] documentation: remove s3 references from README (#381) --- README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index dd3b1549..f1d6c9f6 100644 --- a/README.md +++ b/README.md @@ -79,14 +79,13 @@ from braket.aws import AwsDevice from braket.circuits import Circuit device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") -s3_folder = ("amazon-braket-Your-Bucket-Name", "folder-name") # Use the S3 bucket you created during onboarding bell = Circuit().h(0).cnot(0, 1) -task = device.run(bell, s3_folder, shots=100) +task = device.run(bell, shots=100) print(task.result().measurement_counts) ``` -The code sample imports the Amazon Braket framework, then defines the device to use (the SV1 AWS simulator). The `s3_folder` statement defines the Amazon S3 bucket for the task result and the folder in the bucket to store the task result. This folder is created when you run the task. It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the job. This example can be found in `../examples/bell.py`. +The code sample imports the Amazon Braket framework, then defines the device to use (the SV1 AWS simulator). It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the job. This example can be found in `../examples/bell.py`. ### Running multiple tasks at once @@ -94,7 +93,7 @@ Many quantum algorithms need to run multiple independent circuits, and submittin ```python circuits = [bell for _ in range(5)] -batch = device.run_batch(circuits, s3_folder, shots=100) +batch = device.run_batch(circuits, shots=100) print(batch.results()[0].measurement_counts) # The result of the first task in the batch ``` @@ -139,17 +138,16 @@ from braket.circuits import Circuit from braket.aws import AwsDevice device = AwsDevice("arn:aws:braket:::device/qpu/rigetti/Aspen-8") -s3_folder = ("amazon-braket-Your-Bucket-Name", "RIGETTI") # Use the S3 bucket you created during onboarding bell = Circuit().h(0).cnot(0, 1) -task = device.run(bell, s3_folder) +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, s3_folder, poll_timeout_seconds=86400) # 1 day +task = device.run(bell, poll_timeout_seconds=86400) # 1 day print(task.result().measurement_counts) ``` From dab7e8ad379eb0237f2d152cd6203a2a9002ff48 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 17 Jun 2022 18:27:32 +0000 Subject: [PATCH 0486/1165] prepare release v1.25.1.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 2ca6089c..96f53be0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.25.1.post0 (2022-06-17) + +### Documentation Changes + + * remove s3 references from README + ## v1.25.1 (2022-06-16) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 82a6cea6..e463b445 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.25.2.dev0" +__version__ = "1.25.1.post0" From 3a275e0867f67b19fb5160c2eb2f7e4676e7babf Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 17 Jun 2022 18:27:32 +0000 Subject: [PATCH 0487/1165] update development version to v1.25.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 e463b445..82a6cea6 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.25.1.post0" +__version__ = "1.25.2.dev0" From 1ba287c8c6f21e0331701a20257cb60f62c6e337 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 20 Jun 2022 11:32:25 -0700 Subject: [PATCH 0488/1165] fix: Set the range for amazon-braket-schemas to >= 1.10.0 for the latest device schemas needed. (#382) --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 14491ab4..8074143d 100644 --- a/setup.py +++ b/setup.py @@ -28,6 +28,7 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-default-simulator", + "amazon-braket-schemas>=1.10.0", "backoff", "boltons", "boto3", From 51ac03a99cbeee3c6db2b571fe83868adbb67a60 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 22 Jun 2022 21:19:10 +0000 Subject: [PATCH 0489/1165] prepare release v1.25.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 96f53be0..703d184b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.25.2 (2022-06-22) + +### Bug Fixes and Other Changes + + * Set the range for amazon-braket-schemas to >= 1.10.0 for the latest device schemas needed. + ## v1.25.1.post0 (2022-06-17) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 82a6cea6..895a2d75 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.25.2.dev0" +__version__ = "1.25.2" From abd9ea1556cb0cc360349abb42dd48ffffaea666 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 22 Jun 2022 21:19:10 +0000 Subject: [PATCH 0490/1165] update development version to v1.25.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 895a2d75..6d825650 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.25.2" +__version__ = "1.25.3.dev0" From 61088813e9ae7c6b52654ed2890ddbb0cd8db314 Mon Sep 17 00:00:00 2001 From: Stephen Face <60493521+shpface@users.noreply.github.com> Date: Mon, 18 Jul 2022 13:53:28 -0700 Subject: [PATCH 0491/1165] feature: SDK Cost Tracker (#404) --- src/braket/aws/aws_quantum_task.py | 10 + src/braket/aws/aws_session.py | 24 +- src/braket/tracking/__init__.py | 14 + src/braket/tracking/pricing.py | 64 ++++ src/braket/tracking/tracker.py | 284 ++++++++++++++++++ src/braket/tracking/tracking_context.py | 39 +++ src/braket/tracking/tracking_events.py | 40 +++ test/integ_tests/test_cost_tracking.py | 130 ++++++++ .../unit_tests/braket/aws/test_aws_session.py | 26 +- .../braket/tracking/test_pricing.py | 37 +++ .../braket/tracking/test_tracker.py | 195 ++++++++++++ .../braket/tracking/test_tracking_context.py | 44 +++ 12 files changed, 902 insertions(+), 5 deletions(-) create mode 100644 src/braket/tracking/__init__.py create mode 100644 src/braket/tracking/pricing.py create mode 100644 src/braket/tracking/tracker.py create mode 100644 src/braket/tracking/tracking_context.py create mode 100644 src/braket/tracking/tracking_events.py create mode 100644 test/integ_tests/test_cost_tracking.py create mode 100644 test/unit_tests/braket/tracking/test_pricing.py create mode 100644 test/unit_tests/braket/tracking/test_tracker.py create mode 100644 test/unit_tests/braket/tracking/test_tracking_context.py diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index d9ac8f5c..1691068c 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -51,6 +51,8 @@ PhotonicModelQuantumTaskResult, QuantumTask, ) +from braket.tracking.tracking_context import broadcast_event +from braket.tracking.tracking_events import _TaskCompletionEvent class AwsQuantumTask(QuantumTask): @@ -402,6 +404,14 @@ def _download_result(self): current_metadata["outputS3Directory"] + f"/{AwsQuantumTask.RESULTS_FILENAME}", ) self._result = _format_result(BraketSchemaBase.parse_raw_schema(result_string)) + task_event = {"arn": self.id, "status": self.state(), "execution_duration": None} + try: + task_event[ + "execution_duration" + ] = self._result.additional_metadata.simulatorMetadata.executionDuration + except AttributeError: + pass + broadcast_event(_TaskCompletionEvent(**task_event)) return self._result def __repr__(self) -> str: diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index df5c23bb..623f9ba1 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -27,6 +27,8 @@ 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 class AwsSession(object): @@ -70,6 +72,9 @@ def __init__(self, boto_session=None, braket_client=None, config=None, default_b 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") + self.braket_client.meta.events.register( + "before-sign.braket.CreateQuantumTask", self._add_cost_tracker_count_handler + ) self._iam = None self._s3 = None @@ -151,6 +156,10 @@ def add_braket_user_agent(self, user_agent: str) -> None: if user_agent not in existing_user_agent: self.braket_client._client_config.user_agent = f"{existing_user_agent} {user_agent}" + @staticmethod + def _add_cost_tracker_count_handler(request, **kwargs) -> None: + request.headers.add_header("Braket-Trackers", str(len(active_trackers()))) + # # Quantum Tasks # @@ -161,7 +170,8 @@ def cancel_quantum_task(self, arn: str) -> None: Args: arn (str): The ARN of the quantum task to cancel. """ - self.braket_client.cancel_quantum_task(quantumTaskArn=arn) + response = self.braket_client.cancel_quantum_task(quantumTaskArn=arn) + broadcast_event(_TaskStatusEvent(arn=arn, status=response["cancellationStatus"])) def create_quantum_task(self, **boto3_kwargs) -> str: """ @@ -178,6 +188,14 @@ def create_quantum_task(self, **boto3_kwargs) -> str: if job_token: boto3_kwargs.update({"jobToken": job_token}) response = self.braket_client.create_quantum_task(**boto3_kwargs) + broadcast_event( + _TaskCreationEvent( + arn=response["quantumTaskArn"], + shots=boto3_kwargs["shots"], + is_job_task=(job_token is not None), + device=boto3_kwargs["deviceArn"], + ) + ) return response["quantumTaskArn"] def create_job(self, **boto3_kwargs) -> str: @@ -221,7 +239,9 @@ def get_quantum_task(self, arn: str) -> Dict[str, Any]: Returns: Dict[str, Any]: The response from the Amazon Braket `GetQuantumTask` operation. """ - return self.braket_client.get_quantum_task(quantumTaskArn=arn) + response = self.braket_client.get_quantum_task(quantumTaskArn=arn) + broadcast_event(_TaskStatusEvent(arn=response["quantumTaskArn"], status=response["status"])) + return response def get_default_jobs_role(self) -> str: """ diff --git a/src/braket/tracking/__init__.py b/src/braket/tracking/__init__.py new file mode 100644 index 00000000..652111f2 --- /dev/null +++ b/src/braket/tracking/__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.tracking.tracker import Tracker # noqa: F401 diff --git a/src/braket/tracking/pricing.py b/src/braket/tracking/pricing.py new file mode 100644 index 00000000..25ea9982 --- /dev/null +++ b/src/braket/tracking/pricing.py @@ -0,0 +1,64 @@ +# 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 csv +import io +from functools import lru_cache +from typing import Dict, List + +import urllib3 + + +class Pricing: + def __init__(self): + self._price_list = [] + + def get_prices(self): + # Using AWS Pricing Bulk API. Format for the response is described at + # https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/reading-an-offer.html + + http = urllib3.PoolManager() + price_url = ( + "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBraket/current/index.csv" + ) + response = http.request( + "GET", + price_url, + preload_content=False, + ) + response.auto_close = False + + text_response = io.TextIOWrapper(response) + + # Data starts on line 6 + # + # > The first five rows of the CSV are the metadata for the offer file. The sixth row has + # > all the column names for the products and their attributes... + # https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/reading-an-offer.html#csv + for _ in range(5): + text_response.readline() + self._price_list = list(csv.DictReader(text_response)) + + @lru_cache() + def price_search(self, **kwargs) -> List[Dict[str, str]]: + if not self._price_list: + self.get_prices() + return [ + entry for entry in self._price_list if all(entry[k] == v for k, v in kwargs.items()) + ] + + +_pricing = Pricing() +price_search = _pricing.price_search diff --git a/src/braket/tracking/tracker.py b/src/braket/tracking/tracker.py new file mode 100644 index 00000000..ffc9a695 --- /dev/null +++ b/src/braket/tracking/tracker.py @@ -0,0 +1,284 @@ +# 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 datetime import timedelta +from decimal import Decimal +from functools import singledispatch +from typing import Any, Dict, List + +from braket.tracking.pricing import price_search +from braket.tracking.tracking_context import deregister_tracker, register_tracker +from braket.tracking.tracking_events import ( + _TaskCompletionEvent, + _TaskCreationEvent, + _TaskStatusEvent, +) + +MIN_SIMULATOR_DURATION = timedelta(milliseconds=3000) + + +class Tracker: + """ + Amazon Braket cost tracker. + Use this class to track costs incurred from quantum tasks on Amazon Braket. + """ + + def __init__(self): + self._resources = {} # Key = quantum_task_arn + + def __enter__(self): + register_tracker(self) + return self + + def __exit__(self, *args): + deregister_tracker(self) + + def start(self): + """ + Start tracking resources with this tracker. + """ + return self.__enter__() + + def stop(self): + """ + Stop tracking resources with this tracker. + """ + return self.__exit__() + + def receive_event(self, event): + _recieve_internal(event, self._resources) + + def tracked_resources(self) -> List[str]: + """ + Resources tracked by this tracker. + + Returns: + List[str]: The list of task ids for tasks tracked by this tracker. + """ + 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. + + 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 + charges. Estimated charges do not factor in any discounts or credits, and you may + experience additional charges based on your use of other services such as + Amazon Elastic Compute Cloud (Amazon EC2). + + Returns: + Decimal: The estimated total cost in USD + """ + 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) + return total_cost + + def simulator_tasks_cost(self) -> Decimal: + """ + Estimate cost of all quantum tasks tracked by this tracker using Braket simulator 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 + charges. Estimated charges do not factor in any discounts or credits, and you may + experience additional charges based on your use of other services such as + Amazon Elastic Compute Cloud (Amazon EC2). + + Returns: + Decimal: The estimated total cost in USD + """ + 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) + return total_cost + + def quantum_tasks_statistics(self) -> Dict[str, Dict[str, Any]]: + """ + 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 + a dictionary sumarizing the 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 tasks on simulator devices, the summary + also includes the duration of the simulation. + + Example: + >>> tracker.quantum_tasks_statistics() + {'qpu_device_foo': + {'shots' : 1000, + 'tasks' : { 'COMPLETED' : 4, + 'QUEUED' : 1 }, + }, + 'simulator_device_bar': + {'shots' : 1000 + 'tasks' : { 'COMPLETED' : 2, + 'CREATED' : 1}, + 'execution_duration' : datetime.timedelta(seconds=5, microseconds=654321), + 'billed_execution_duration' : datetime.timedelta(seconds=6, microseconds=123456)}} + """ + stats = {} + for _, details in self._resources.items(): + + device_stats = stats.get(details["device"], {}) + + shots = device_stats.get("shots", 0) + details["shots"] + device_stats["shots"] = shots + + task_states = device_stats.get("tasks", {}) + task_states[details["status"]] = task_states.get(details["status"], 0) + 1 + device_stats["tasks"] = task_states + + if "execution_duration" in details: + duration = ( + device_stats.get("execution_duration", timedelta(0)) + + details["execution_duration"] + ) + billed_duration = ( + device_stats.get("billed_execution_duration", timedelta(0)) + + details["billed_duration"] + ) + + device_stats["execution_duration"] = duration + device_stats["billed_execution_duration"] = billed_duration + + stats.setdefault(details["device"], {}).update(device_stats) + + return stats + + +def _get_qpu_task_cost(task_arn: str, details: dict) -> Decimal: + if details["status"] in ["FAILED", "CANCELLED"]: + return Decimal(0) + task_region = task_arn.split(":")[3] + + search_dict = {"Region Code": task_region} + + device_name = details["device"].split("/")[-1] + device_name = device_name[0].upper() + device_name[1:] + if "2000Q" in device_name: + device_name = "2000Q" + elif "Advantage_system" in device_name: + device_name = "Advantage_system" + + if details["job_task"]: + search_dict["Device Name"] = device_name + shot_product_family = "Braket Managed Jobs QPU Task Shot" + task_product_family = "Braket Managed Jobs QPU Task" + else: + search_dict["DeviceName"] = device_name # The difference in spelling is intentional + shot_product_family = "Quantum Task-Shot" + task_product_family = "Quantum Task" + + search_dict["Product Family"] = shot_product_family + shot_prices = price_search(**search_dict) + if len(shot_prices) != 1: + raise ValueError(f"Found {len(shot_prices)} products matching {search_dict}") + + search_dict["Product Family"] = task_product_family + task_prices = price_search(**search_dict) + if len(task_prices) != 1: + raise ValueError(f"Found {len(task_prices)} products matching {search_dict}") + + shot_price = shot_prices[0] + task_price = task_prices[0] + + for price in [shot_price, task_price]: + if price["Currency"] != "USD": + raise ValueError(f"Expected USD, found {price['Currency']}") + + shot_cost = Decimal(shot_price["PricePerUnit"]) * details["shots"] + task_cost = Decimal(task_price["PricePerUnit"]) * 1 + + return shot_cost + task_cost + + +def _get_simulator_task_cost(task_arn: str, details: dict) -> Decimal: + if not details.get("billed_duration"): + return Decimal(0) + task_region = task_arn.split(":")[3] + + device_name = details["device"].split("/")[-1].upper() + + if details["job_task"]: + product_family = "Braket Managed Jobs Simulator Task" + operation = "Managed-Jobs" + else: + product_family = "Simulator Task" + operation = "CompleteTask" + if details["status"] == "FAILED" and device_name == "TN1": + # Rehersal step of TN1 can fail and charges still apply. + operation = "FailedTask" + + search_dict = { + "Region Code": task_region, + "Version": device_name, + "Product Family": product_family, + "operation": operation, + } + + duration_prices = price_search(**search_dict) + + if len(duration_prices) != 1: + raise ValueError(f"Found {len(duration_prices)} products matching {search_dict}") + + duration_price = duration_prices[0] + + if duration_price["Currency"] != "USD": + raise ValueError(f"Expected USD, found {duration_price['Currency']}") + + duration_cost = ( + Decimal(duration_price["PricePerUnit"]) + * Decimal(details["billed_duration"] / timedelta(milliseconds=1)) + / Decimal(timedelta(**{duration_price["Unit"]: 1}) / timedelta(milliseconds=1)) + ) + + return duration_cost + + +@singledispatch +def _recieve_internal(event, resources): + raise ValueError(f"Event type {type(event)} is not supported") + + +@_recieve_internal.register +def _(event: _TaskCreationEvent, resources: dict): + resources[event.arn] = { + "shots": event.shots, + "device": event.device, + "status": "CREATED", + "job_task": event.is_job_task, + } + + +@_recieve_internal.register +def _(event: _TaskStatusEvent, resources: dict): + # 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 _(event: _TaskCompletionEvent, resources: dict): + # 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 + if event.execution_duration: + duration = timedelta(milliseconds=event.execution_duration) + resources[event.arn]["execution_duration"] = duration + resources[event.arn]["billed_duration"] = max(duration, MIN_SIMULATOR_DURATION) diff --git a/src/braket/tracking/tracking_context.py b/src/braket/tracking/tracking_context.py new file mode 100644 index 00000000..772d92a0 --- /dev/null +++ b/src/braket/tracking/tracking_context.py @@ -0,0 +1,39 @@ +# 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 + + +class TrackingContext: + def __init__(self): + self._trackers = set() + + def register_tracker(self, tracker: Tracker): # noqa F821 + self._trackers.add(tracker) + + def deregister_tracker(self, tracker: Tracker): # noqa F821 + self._trackers.remove(tracker) + + def broadcast_event(self, event: _TrackingEvent): # noqa F821 + for tracker in self._trackers: + tracker.receive_event(event) + + def active_trackers(self): + return self._trackers + + +_tracking_context = TrackingContext() +register_tracker = _tracking_context.register_tracker +deregister_tracker = _tracking_context.deregister_tracker +broadcast_event = _tracking_context.broadcast_event +active_trackers = _tracking_context.active_trackers diff --git a/src/braket/tracking/tracking_events.py b/src/braket/tracking/tracking_events.py new file mode 100644 index 00000000..6f37183c --- /dev/null +++ b/src/braket/tracking/tracking_events.py @@ -0,0 +1,40 @@ +# 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 dataclasses import dataclass +from typing import Optional + + +@dataclass +class _TrackingEvent: + arn: str + + +@dataclass +class _TaskCreationEvent(_TrackingEvent): + shots: int + is_job_task: bool + device: str + + +@dataclass +class _TaskCompletionEvent(_TrackingEvent): + execution_duration: Optional[float] + status: str + + +@dataclass +class _TaskStatusEvent(_TrackingEvent): + status: str diff --git a/test/integ_tests/test_cost_tracking.py b/test/integ_tests/test_cost_tracking.py new file mode 100644 index 00000000..d7683f6e --- /dev/null +++ b/test/integ_tests/test_cost_tracking.py @@ -0,0 +1,130 @@ +# 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 datetime import timedelta + +import boto3 + +from braket.annealing import Problem, ProblemType +from braket.aws import AwsDevice, AwsSession +from braket.circuits import Circuit +from braket.tracking import Tracker +from braket.tracking.tracker import MIN_SIMULATOR_DURATION + + +def test_qpu_tracking(): + problem = Problem(ProblemType("QUBO"), linear={35: 1}, quadratic={(35, 36): -1}) + device = AwsDevice("arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6") + other_device = AwsDevice("arn:aws:braket:us-west-2::device/qpu/d-wave/Advantage_system6") + + device.run(problem, shots=50).result() + with Tracker() as t: + device.run(problem, shots=100).result() + other_device.run(problem, shots=400).result() + t_partial_cost = t.qpu_tasks_cost() + assert t_partial_cost > 0 + with Tracker() as s: + task = device.run(problem, shots=200) + assert s.quantum_tasks_statistics() == { + "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6": { + "shots": 200, + "tasks": {"CREATED": 1}, + } + } + task.result() + + assert s.simulator_tasks_cost() == 0 + assert t.simulator_tasks_cost() == 0 + + assert s.quantum_tasks_statistics() == { + "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6": {"shots": 200, "tasks": {"COMPLETED": 1}} + } + assert t.quantum_tasks_statistics() == { + "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6": {"shots": 300, "tasks": {"COMPLETED": 2}}, + "arn:aws:braket:us-west-2::device/qpu/d-wave/Advantage_system6": { + "shots": 400, + "tasks": {"COMPLETED": 1}, + }, + } + + assert s.qpu_tasks_cost() > 0 + assert t.qpu_tasks_cost() > s.qpu_tasks_cost() + assert t.qpu_tasks_cost() > t_partial_cost + + circuit = Circuit().h(0) + with Tracker() as t: + AwsDevice("arn:aws:braket:::device/qpu/ionq/ionQdevice").run(circuit, shots=10) + AwsDevice("arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy").run(circuit, shots=10) + AwsDevice("arn:aws:braket:::device/qpu/rigetti/Aspen-11").run(circuit, shots=10) + + assert t.qpu_tasks_cost() > 0 + + +def test_simulator_tracking(): + circuit = Circuit().h(0).cnot(0, 1) + device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") + + with Tracker() as t: + task0 = device.run(circuit, shots=100) + task1 = device.run(circuit, shots=100) + assert t.quantum_tasks_statistics() == { + "arn:aws:braket:::device/quantum-simulator/amazon/sv1": { + "shots": 200, + "tasks": {"CREATED": 2}, + } + } + task0.result() + task1.result() + + device.run(circuit, shots=100).cancel() + + quantum_stats = t.quantum_tasks_statistics()[device.arn] + assert quantum_stats["shots"] == 300 + assert quantum_stats["tasks"] == {"COMPLETED": 2, "CANCELLING": 1} + assert quantum_stats["execution_duration"] > timedelta(0) + assert quantum_stats["billed_execution_duration"] >= quantum_stats["execution_duration"] + assert quantum_stats["billed_execution_duration"] >= 2 * MIN_SIMULATOR_DURATION + + assert t.qpu_tasks_cost() == 0 + assert t.simulator_tasks_cost() > 0 + + +def test_all_devices_price_search(): + devices = AwsDevice.get_devices(statuses=["ONLINE", "OFFLINE"]) + + tasks = {} + for region in AwsDevice.REGIONS: + s = AwsSession(boto3.Session(region_name=region)) + for device in devices: + 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 + + assert t.qpu_tasks_cost() + t.simulator_tasks_cost() > 0 diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index a0a5ce0e..babfcf2f 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -277,6 +277,17 @@ def test_populates_user_agent(os_path_exists_mock, metadata_file_exists, initial assert aws_session.braket_client._client_config.user_agent == expected_user_agent +@patch("braket.aws.aws_session.active_trackers") +def test_add_cost_tracker_count(active_trackers_mock, aws_session): + request = Mock() + active_trackers_mock.return_value = {"A tracker"} + aws_session._add_cost_tracker_count_handler(request) + request.headers.add_header.assert_called_with("Braket-Trackers", "1") + active_trackers_mock.return_value = {} + aws_session._add_cost_tracker_count_handler(request) + request.headers.add_header.assert_called_with("Braket-Trackers", "0") + + def test_retrieve_s3_object_body_success(boto_session): bucket_name = "braket-integ-test" filename = "tasks/test_task_1.json" @@ -330,7 +341,10 @@ def test_get_device(boto_session, braket_client): def test_cancel_quantum_task(aws_session): arn = "foo:bar:arn" - aws_session.braket_client.cancel_quantum_task.return_value = {"quantumTaskArn": arn} + aws_session.braket_client.cancel_quantum_task.return_value = { + "quantumTaskArn": arn, + "cancellationStatus": "CANCELLING OR CANCELLED", + } assert aws_session.cancel_quantum_task(arn) is None aws_session.braket_client.cancel_quantum_task.assert_called_with(quantumTaskArn=arn) @@ -345,6 +359,8 @@ def test_create_quantum_task(aws_session): "cwLogGroupArn": "arn:aws:us-west-2:abc:xyz:abc", "destinationUrl": "http://s3-us-west-2.amazonaws.com/task-output-bar-1/output.json", "program": {"ir": '{"instructions":[]}', "qubitCount": 4}, + "shots": 1, + "deviceArn": "foo:bar:device_arn", } assert aws_session.create_quantum_task(**kwargs) == arn aws_session.braket_client.create_quantum_task.assert_called_with(**kwargs) @@ -360,6 +376,8 @@ def test_create_quantum_task_with_job_token(aws_session): "cwLogGroupArn": "arn:aws:us-west-2:abc:xyz:abc", "destinationUrl": "http://s3-us-west-2.amazonaws.com/task-output-foo-1/output.json", "program": {"ir": '{"instructions":[]}', "qubitCount": 4}, + "shots": 1, + "deviceArn": "foo:bar:device_arn", } with patch.dict(os.environ, {"AMZN_BRAKET_JOB_TOKEN": job_token}): assert aws_session.create_quantum_task(**kwargs) == arn @@ -369,7 +387,8 @@ def test_create_quantum_task_with_job_token(aws_session): def test_get_quantum_task(aws_session): arn = "foo:bar:arn" - return_value = {"quantumTaskArn": arn} + status = "STATUS" + return_value = {"quantumTaskArn": arn, "status": status} aws_session.braket_client.get_quantum_task.return_value = return_value assert aws_session.get_quantum_task(arn) == return_value @@ -378,7 +397,8 @@ def test_get_quantum_task(aws_session): def test_get_quantum_task_retry(aws_session, throttling_response, resource_not_found_response): arn = "foo:bar:arn" - return_value = {"quantumTaskArn": arn} + status = "STATUS" + return_value = {"quantumTaskArn": arn, "status": status} aws_session.braket_client.get_quantum_task.side_effect = [ ClientError(resource_not_found_response, "unit-test"), diff --git a/test/unit_tests/braket/tracking/test_pricing.py b/test/unit_tests/braket/tracking/test_pricing.py new file mode 100644 index 00000000..dcdf6450 --- /dev/null +++ b/test/unit_tests/braket/tracking/test_pricing.py @@ -0,0 +1,37 @@ +# 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 io +from unittest.mock import patch + +from braket.tracking.pricing import Pricing + + +@patch("urllib3.PoolManager") +def test_search_prices(mock_http): + mock_http().request.return_value = io.BytesIO( + b"""line1 +line2 +line3 +line4 +line5 +A,B +1,1 +1,2 +""" + ) + pricer = Pricing() + assert pricer.price_search(A="0") == [] + assert pricer.price_search(A="1", B="1") == [{"A": "1", "B": "1"}] + assert pricer.price_search(A="1") == [{"A": "1", "B": "1"}, {"A": "1", "B": "2"}] diff --git a/test/unit_tests/braket/tracking/test_tracker.py b/test/unit_tests/braket/tracking/test_tracker.py new file mode 100644 index 00000000..fc660b80 --- /dev/null +++ b/test/unit_tests/braket/tracking/test_tracker.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 datetime import timedelta +from decimal import Decimal +from unittest.mock import patch + +import pytest + +from braket.tracking.tracker import Tracker +from braket.tracking.tracking_context import active_trackers +from braket.tracking.tracking_events import ( + _TaskCompletionEvent, + _TaskCreationEvent, + _TaskStatusEvent, +) + + +@pytest.fixture() +def empty_tracker(): + with Tracker() as _tracker: + yield _tracker + + +def test_tracker_enter_exit(): + x = len(active_trackers()) + with Tracker() as t: + assert len(active_trackers()) == 1 + x + with Tracker() as s: + assert len(active_trackers()) == 2 + x + assert s in active_trackers() + assert t in active_trackers() + assert len(active_trackers()) == x + + +def test_tracker_start_stop(): + t = Tracker() + assert t not in active_trackers() + t.start() + assert t in active_trackers() + t.stop() + assert t not in active_trackers() + + +def test_receive_fake_event(empty_tracker): + event = "NOT AN EVENT" + with pytest.raises(ValueError): + empty_tracker.receive_event(event) + + +CREATE_EVENTS = [ + _TaskCreationEvent(arn="task1:::region", shots=100, is_job_task=True, device="qpu/foo"), + _TaskCreationEvent(arn="task2:::region", shots=100, is_job_task=False, device="qpu/foo"), + _TaskCreationEvent( + arn="job_sim_task:::region", shots=0, is_job_task=True, device="simulator/bar" + ), + _TaskCreationEvent( + arn="notjob_sim_task:::region", shots=0, is_job_task=False, device="simulator/bar" + ), + _TaskCreationEvent( + arn="task_fail:::region", shots=0, is_job_task=False, device="simulator/tn1" + ), + _TaskCreationEvent( + arn="task_cancel:::region", shots=0, is_job_task=False, device="simulator/baz" + ), + _TaskCreationEvent( + arn="2000qtask:::region", shots=100, is_job_task=False, device="qpu/2000Qxyz" + ), + _TaskCreationEvent( + arn="adv_task:::region", shots=100, is_job_task=False, device="qpu/Advantage_system123" + ), + _TaskCreationEvent( + arn="unfinished_sim_task:::region", shots=1000, is_job_task=False, device="simulator/bar" + ), + _TaskCreationEvent( + arn="no_price:::region", shots=1000, is_job_task=False, device="something_else" + ), +] + +GET_EVENTS = [ + _TaskStatusEvent(arn="untracked_task:::region", status="FOO"), + _TaskStatusEvent(arn="task1:::region", status="BAR"), + _TaskStatusEvent(arn="task2:::region", status="FAILED"), +] +COMPLETE_EVENTS = [ + _TaskCompletionEvent(arn="untracked_task:::region", execution_duration=999999, status="BAR"), + _TaskCompletionEvent(arn="task1:::region", execution_duration=None, status="COMPLETED"), + _TaskCompletionEvent(arn="job_sim_task:::region", execution_duration=123, status="COMPLETED"), + _TaskCompletionEvent( + arn="notjob_sim_task:::region", execution_duration=1729, status="COMPLETED" + ), + _TaskCompletionEvent(arn="task_fail:::region", execution_duration=12345, status="FAILED"), + _TaskCompletionEvent(arn="task_cancel:::region", execution_duration=None, status="CANCELLED"), +] + + +@pytest.fixture +def create_tracker(empty_tracker): + for e in CREATE_EVENTS: + empty_tracker.receive_event(e) + return empty_tracker + + +@pytest.fixture +def get_tracker(create_tracker): + for e in GET_EVENTS: + create_tracker.receive_event(e) + return create_tracker + + +@pytest.fixture +def completed_tracker(get_tracker): + for e in COMPLETE_EVENTS: + get_tracker.receive_event(e) + return get_tracker + + +def test_tracked_resources(create_tracker): + assert len(CREATE_EVENTS) == len(create_tracker.tracked_resources()) + + +def mock_qpu_price(**kwargs): + if "Shot" in kwargs["Product Family"]: + return [{"PricePerUnit": "0.001", "Currency": "USD"}] + else: + return [{"PricePerUnit": "1.0", "Currency": "USD"}] + + +@patch("braket.tracking.tracker.price_search") +def test_qpu_task_cost(price_mock, completed_tracker): + price_mock.side_effect = mock_qpu_price + cost = completed_tracker.qpu_tasks_cost() + assert cost == Decimal("3.3") + + price_mock.side_effect = [[]] + with pytest.raises(ValueError, match="Found 0 products"): + completed_tracker.qpu_tasks_cost() + + price_mock.side_effect = [[{}], [{}, {}]] + with pytest.raises(ValueError, match="Found 2 products"): + completed_tracker.qpu_tasks_cost() + + price_mock.side_effect = [[{"Currency": "BAD"}], [{"Currency": "BAD"}]] + with pytest.raises(ValueError, match="Expected USD"): + completed_tracker.qpu_tasks_cost() + + +@patch("braket.tracking.tracker.price_search") +def test_simulator_task_cost(price_mock, completed_tracker): + price_mock.return_value = [{"PricePerUnit": "6.0", "Currency": "USD", "Unit": "minutes"}] + cost = completed_tracker.simulator_tasks_cost() + expected = Decimal("0.0001") * (3000 + 3000 + 12345) + assert cost == expected + + price_mock.return_value = [] + with pytest.raises(ValueError, match="Found 0 products"): + completed_tracker.simulator_tasks_cost() + + price_mock.return_value = [{"Currency": "BAD"}] + with pytest.raises(ValueError, match="Expected USD"): + completed_tracker.simulator_tasks_cost() + + +def test_quantum_task_statistics(completed_tracker): + stats = completed_tracker.quantum_tasks_statistics() + expected = { + "qpu/foo": {"shots": 200, "tasks": {"COMPLETED": 1, "FAILED": 1}}, + "simulator/bar": { + "shots": 1000, + "tasks": {"COMPLETED": 2, "CREATED": 1}, + "execution_duration": timedelta(seconds=1, microseconds=852000), + "billed_execution_duration": timedelta(seconds=6), + }, + "simulator/tn1": { + "shots": 0, + "tasks": {"FAILED": 1}, + "execution_duration": timedelta(seconds=12, microseconds=345000), + "billed_execution_duration": timedelta(seconds=12, microseconds=345000), + }, + "simulator/baz": {"shots": 0, "tasks": {"CANCELLED": 1}}, + "qpu/2000Qxyz": {"shots": 100, "tasks": {"CREATED": 1}}, + "qpu/Advantage_system123": {"shots": 100, "tasks": {"CREATED": 1}}, + "something_else": {"shots": 1000, "tasks": {"CREATED": 1}}, + } + assert stats == expected diff --git a/test/unit_tests/braket/tracking/test_tracking_context.py b/test/unit_tests/braket/tracking/test_tracking_context.py new file mode 100644 index 00000000..2963bbd0 --- /dev/null +++ b/test/unit_tests/braket/tracking/test_tracking_context.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 unittest.mock import Mock + +from braket.tracking.tracking_context import ( + active_trackers, + broadcast_event, + deregister_tracker, + register_tracker, +) + + +def test_tracking_context(): + assert active_trackers() == set() + + +def test_register_deregister_tracker(): + register_tracker("foo") + assert active_trackers() == {"foo"} + register_tracker("bar") + register_tracker("bar") + assert active_trackers() == {"foo", "bar"} + deregister_tracker("foo") + assert active_trackers() == {"bar"} + deregister_tracker("bar") + + +def test_broadcast_event(): + tracker = Mock() + register_tracker(tracker) + broadcast_event("EVENT") + tracker.receive_event.assert_called_with("EVENT") + deregister_tracker(tracker) From 7ad4f86705b37fc92bd74d3e05e4b17c6e7e63d4 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 18 Jul 2022 21:14:56 +0000 Subject: [PATCH 0492/1165] prepare release v1.26.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 703d184b..eda382cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.26.0 (2022-07-18) + +### Features + + * SDK Cost Tracker + ## v1.25.2 (2022-06-22) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 6d825650..2691f5a5 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.25.3.dev0" +__version__ = "1.26.0" From 190417b317e7fc12418e947ec07fe76c1e7a4a53 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 18 Jul 2022 21:14:56 +0000 Subject: [PATCH 0493/1165] update development version to v1.26.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 2691f5a5..b2d313ee 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.26.0" +__version__ = "1.26.1.dev0" From 3da1528f73f793b10fecbe0650bebc77476915ef Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 19 Jul 2022 09:07:01 -0700 Subject: [PATCH 0494/1165] =?UTF-8?q?fix:=20Lazily=20parse=20schemas=20for?= =?UTF-8?q?=20devices=20so=20getDevice=20calls=20do=20not=20rely=20?= =?UTF-8?q?=E2=80=A6=20(#401)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Lazily parse schemas for devices so getDevice calls do not rely on the device. --- src/braket/aws/aws_device.py | 8 +++----- test/unit_tests/braket/aws/test_aws_device.py | 7 ++++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 9902c847..4609e631 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -282,9 +282,7 @@ def _populate_properties(self, session): self._status = metadata.get("deviceStatus") self._type = AwsDeviceType(metadata.get("deviceType")) self._provider_name = metadata.get("providerName") - qpu_properties = metadata.get("deviceCapabilities") - self._properties = BraketSchemaBase.parse_raw_schema(qpu_properties) - self._topology_graph = self._construct_topology_graph() + self._properties = metadata.get("deviceCapabilities") @property def type(self) -> str: @@ -370,7 +368,7 @@ 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""" - return self._properties + return BraketSchemaBase.parse_raw_schema(self._properties) @property def topology_graph(self) -> DiGraph: @@ -387,7 +385,7 @@ def topology_graph(self) -> DiGraph: >>> print(device.topology_graph.edges) """ - return self._topology_graph + return self._construct_topology_graph() def _construct_topology_graph(self) -> DiGraph: """ diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 3b9b3654..28f74928 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -13,7 +13,7 @@ import json import os from datetime import datetime -from unittest.mock import Mock, patch +from unittest.mock import Mock, PropertyMock, patch import networkx as nx import pytest @@ -1145,6 +1145,11 @@ def __init__(self, status, *execution_window_args): for i in range(len(test_item[1])): device_name = test_set["test_devices"][i][0] device = test_set["test_devices"][i][1] + type(device).properties = PropertyMock(return_value=Expando()) + type(device).properties.service = PropertyMock(return_value=Expando()) + device.properties.service.executionWindows = ( + device._properties.service.executionWindows + ) expected = bool(test_item[1][i]) actual = device.is_available assert ( From 8f2ae49596417c1148af29627989048764a1757d Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 19 Jul 2022 18:19:49 +0000 Subject: [PATCH 0495/1165] prepare release v1.26.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 eda382cb..1bccfe32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.26.1 (2022-07-19) + +### Bug Fixes and Other Changes + + * Lazily parse schemas for devices so getDevice calls do not rely … + ## v1.26.0 (2022-07-18) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index b2d313ee..e7e9b1f7 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.26.1.dev0" +__version__ = "1.26.1" From d10fe1ec90f9f80da6bb96d38802531675256950 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 19 Jul 2022 18:19:49 +0000 Subject: [PATCH 0496/1165] update development version to v1.26.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 e7e9b1f7..13184806 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.26.1" +__version__ = "1.26.2.dev0" From b40e1c10cee7e0888ad79a97161e40ceffe62c72 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Wed, 20 Jul 2022 13:53:12 -0700 Subject: [PATCH 0497/1165] docs: Update README to include guidance for integrations (#407) --- CONTRIBUTING.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3ae63fc2..7bc3046e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,6 +21,7 @@ information to effectively respond to your bug report or contribution. * [API References (docstrings)](#api-references-docstrings) * [Build and Test Documentation](#build-and-test-documentation) * [Find Contributions to Work On](#find-contributions-to-work-on) +* [Building Integrations](#building-integrations) * [Code of Conduct](#code-of-conduct) * [Security Issue Notifications](#security-issue-notifications) * [Licensing](#licensing) @@ -188,6 +189,10 @@ 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/aws/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/aws/amazon-braket-pennylane-plugin-python), [Strawberryfields](https://github.com/aws/amazon-braket-strawberryfields-plugin-python) and [DWave's Ocean library](https://github.com/aws/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/aws/amazon-braket-pennylane-plugin-python/commit/ccee35604afc2b04d83ee9103eccb2821a4256cb). ## Code of Conduct From 2e151bcbae9aba0650922cb4f12a7b682de0194d Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 21 Jul 2022 18:20:15 +0000 Subject: [PATCH 0498/1165] prepare release v1.26.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 1bccfe32..5eee23ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.26.2 (2022-07-21) + +### Bug Fixes and Other Changes + + * docs: Update README to include guidance for integrations + ## v1.26.1 (2022-07-19) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 13184806..b68c8028 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.26.2.dev0" +__version__ = "1.26.2" From 03a35e8655e84bac42dab5eb1f544c8121574528 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 21 Jul 2022 18:20:15 +0000 Subject: [PATCH 0499/1165] update development version to v1.26.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 b68c8028..042093e1 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.26.2" +__version__ = "1.26.3.dev0" From 7cdd82a7e6f645d9ef16a7e3f7d849e8d5691c30 Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Tue, 26 Jul 2022 09:28:27 -0700 Subject: [PATCH 0500/1165] feature: provide easy mechanism to update the local container when running local job. (#408) --- src/braket/jobs/local/local_job.py | 9 +- src/braket/jobs/local/local_job_container.py | 21 ++++- .../jobs/local/test_local_job_container.py | 82 +++++++++++++++++-- 3 files changed, 99 insertions(+), 13 deletions(-) diff --git a/src/braket/jobs/local/local_job.py b/src/braket/jobs/local/local_job.py index b240658c..cc3e1d85 100644 --- a/src/braket/jobs/local/local_job.py +++ b/src/braket/jobs/local/local_job.py @@ -48,6 +48,7 @@ def create( output_data_config: OutputDataConfig = None, checkpoint_config: CheckpointConfig = None, aws_session: AwsSession = None, + local_container_update: bool = True, ) -> LocalQuantumJob: """Creates and runs job by setting up and running the customer script in a local docker container. @@ -108,6 +109,10 @@ def create( aws_session (AwsSession): AwsSession for connecting to AWS Services. Default: AwsSession() + local_container_update (bool): Perform an update, if available, from ECR to the local + container image. Optional. + Default: True. + Returns: LocalQuantumJob: The representation of a local Braket Job. """ @@ -140,7 +145,9 @@ def create( else: image_uri = retrieve_image(Framework.BASE, session.region) - with _LocalJobContainer(image_uri) as container: + with _LocalJobContainer( + image_uri=image_uri, force_update=local_container_update + ) as container: env_variables = setup_container(container, session, **create_job_kwargs) container.run_local_job(env_variables) container.copy_from("/opt/ml/model", job_name) diff --git a/src/braket/jobs/local/local_job_container.py b/src/braket/jobs/local/local_job_container.py index fc76e8d2..796966f1 100644 --- a/src/braket/jobs/local/local_job_container.py +++ b/src/braket/jobs/local/local_job_container.py @@ -27,7 +27,11 @@ class _LocalJobContainer(object): CONTAINER_CODE_PATH = "/opt/ml/code/" def __init__( - self, image_uri: str, aws_session: AwsSession = None, logger: Logger = getLogger(__name__) + self, + image_uri: str, + aws_session: AwsSession = None, + logger: Logger = getLogger(__name__), + force_update: bool = False, ): """Represents and provides functions for interacting with a Braket Jobs docker container. @@ -38,16 +42,19 @@ 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. + Default: False """ self._aws_session = aws_session or AwsSession() self.image_uri = image_uri self.run_log = None self._container_name = None self._logger = logger + self._force_update = force_update def __enter__(self): """Creates and starts the local docker container.""" - self._container_name = self._start_container(self.image_uri) + self._container_name = self._start_container(self.image_uri, self._force_update) return self def __exit__(self, exc_type, exc_val, exc_tb): @@ -127,12 +134,13 @@ def _pull_image(self, image_uri: str) -> None: self._logger.warning("Pulling docker container image. This may take a while.") subprocess.run(["docker", "pull", image_uri]) - def _start_container(self, image_uri: str) -> str: + def _start_container(self, image_uri: str, force_update: bool) -> str: """Runs a docker container in a busy loop so that it will accept further commands. The call to this function must be matched with end_session to stop the container. Args: 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. Returns: (str): The name of the running container, which can be used to execute further commands. @@ -146,6 +154,13 @@ def _start_container(self, image_uri: str) -> str: f"The URL {image_uri} is not available locally and can not be pulled from ECR." " Please pull down the container before proceeding." ) + elif force_update: + try: + self._pull_image(image_uri) + image_name = self._check_output_formatted(["docker", "images", "-q", image_uri]) + 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"] ) 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 34b7a385..ad061cc0 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 @@ -57,33 +57,97 @@ def test_start_and_stop(mock_run, mock_check_output, image_uri, aws_session): assert mock_run.call_count == 1 +@pytest.mark.parametrize( + "forced_update, check_output, local_image_name", + [ + ( + False, + [ + str.encode(""), # This means that the container image does not exist locally. + str.encode("LocalImageName"), + str.encode("RunningContainer"), + ], + "LocalImageName", + ), + ( + True, + [ + str.encode(""), # This means that the container image does not exist locally. + str.encode("LocalImageName"), + str.encode("RunningContainer"), + ], + "LocalImageName", + ), + ( + True, # When force update is true, we'll pull containers, even if they exist locally. + [ + str.encode("PreUpdateName"), # This means that the container image exists locally. + str.encode("PostUpdateName"), # This means that the container image exists locally. + str.encode("RunningContainer"), + ], + "PostUpdateName", + ), + ], +) @patch("subprocess.check_output") @patch("subprocess.run") -def test_pull_container(mock_run, mock_check_output, repo_uri, image_uri, aws_session): - local_image_name = "LocalImageName" +def test_pull_container( + mock_run, + mock_check_output, + repo_uri, + image_uri, + aws_session, + forced_update, + check_output, + local_image_name, +): running_container_name = "RunningContainer" test_token = "Test Token" - mock_check_output.side_effect = [ - str.encode(""), - str.encode(local_image_name), - str.encode(running_container_name), - ] + mock_check_output.side_effect = check_output aws_session.ecr_client.get_authorization_token.return_value = { "authorizationData": [{"authorizationToken": base64.b64encode(str.encode(test_token))}] } - with _LocalJobContainer(image_uri, aws_session): + with _LocalJobContainer( + image_uri=image_uri, aws_session=aws_session, force_update=forced_update + ): 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"] ) - assert mock_check_output.call_count == 3 + 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]) mock_run.assert_any_call(["docker", "stop", running_container_name]) assert mock_run.call_count == 3 +@patch("subprocess.check_output") +@patch("subprocess.run") +def test_pull_container_forced_update_invalid_name( + mock_run, mock_check_output, repo_uri, aws_session +): + local_image_name = "LocalImageName" + running_container_name = "RunningContainer" + mock_logger = Mock() + mock_check_output.side_effect = [ + str.encode(local_image_name), + str.encode(running_container_name), + ] + with _LocalJobContainer( + image_uri=local_image_name, aws_session=aws_session, force_update=True, logger=mock_logger + ): + 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"] + ) + assert mock_check_output.call_count == 2 + mock_run.assert_any_call(["docker", "stop", running_container_name]) + assert mock_run.call_count == 1 + + @patch("subprocess.check_output") @patch("subprocess.run") def test_run_job_success(mock_run, mock_check_output, repo_uri, image_uri, aws_session): From d339ce2649d4c42e6a2134e4008ea2f2290bfdbc Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 26 Jul 2022 23:42:14 +0000 Subject: [PATCH 0501/1165] prepare release v1.27.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 5eee23ab..e0405843 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.27.0 (2022-07-26) + +### Features + + * provide easy mechanism to update the local container when running local job. + ## v1.26.2 (2022-07-21) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 042093e1..db4984c1 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.26.3.dev0" +__version__ = "1.27.0" From 3745b730c58bc637ffc5d696310bb10112c968fc Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 26 Jul 2022 23:42:14 +0000 Subject: [PATCH 0502/1165] update development version to v1.27.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 db4984c1..c1680a94 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.27.0" +__version__ = "1.27.1.dev0" From 9794c4f23db9af0b945bfb8fe35a86b449e1e88f Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Fri, 29 Jul 2022 11:03:00 -0700 Subject: [PATCH 0503/1165] fix: customer script errors not shown when local jobs run from a notebook. (#414) --- src/braket/jobs/local/local_job.py | 13 ++- src/braket/jobs/local/local_job_container.py | 15 +-- .../braket/jobs/local/test_local_job.py | 75 ++++++++++++--- .../jobs/local/test_local_job_container.py | 92 +++++++++++++++---- 4 files changed, 158 insertions(+), 37 deletions(-) diff --git a/src/braket/jobs/local/local_job.py b/src/braket/jobs/local/local_job.py index cc3e1d85..7c8dacff 100644 --- a/src/braket/jobs/local/local_job.py +++ b/src/braket/jobs/local/local_job.py @@ -152,14 +152,21 @@ def create( 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: - log_file.write(container.run_log) + if isinstance(container.run_result, Exception): + log_file.write("\n--- Errors ---\n") + log_file.write(container.run_result) + else: + if container.run_result.stdout: + log_file.write(container.run_result.stdout.decode("utf-8").strip()) + if container.run_result.stderr: + log_file.write("\n--- Errors ---\n") + log_file.write(container.run_result.stderr.decode("utf-8").strip()) if "checkpointConfig" in create_job_kwargs: checkpoint_config = create_job_kwargs["checkpointConfig"] if "localPath" in checkpoint_config: checkpoint_path = checkpoint_config["localPath"] container.copy_from(checkpoint_path, os.path.join(job_name, "checkpoints")) - run_log = container.run_log - return LocalQuantumJob(f"local:job/{job_name}", run_log) + return LocalQuantumJob(f"local:job/{job_name}") def __init__(self, arn: str, run_log: str = None): """ diff --git a/src/braket/jobs/local/local_job_container.py b/src/braket/jobs/local/local_job_container.py index 796966f1..bbed0c72 100644 --- a/src/braket/jobs/local/local_job_container.py +++ b/src/braket/jobs/local/local_job_container.py @@ -47,7 +47,7 @@ def __init__( """ self._aws_session = aws_session or AwsSession() self.image_uri = image_uri - self.run_log = None + self.run_result = None self._container_name = None self._logger = logger self._force_update = force_update @@ -249,11 +249,14 @@ def run_local_job(self, environment_variables: Dict[str, str]) -> None: command.append(start_program_name) try: - self.run_log = self._check_output_formatted(command) - print(self.run_log) - except subprocess.CalledProcessError as e: - self.run_log = e.output.decode("utf-8").strip() - self._logger.error(self.run_log) + self.run_result = subprocess.run(command, capture_output=True) + if self.run_result.stdout: + print(self.run_result.stdout.decode("utf-8").strip()) + if self.run_result.stderr: + self._logger.error(self.run_result.stderr.decode("utf-8").strip()) + except Exception as e: + self.run_result = e + self._logger.error(e) def _end_session(self): """Stops and removes the local container.""" 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 185b059e..5b41ea18 100644 --- a/test/unit_tests/braket/jobs/local/test_local_job.py +++ b/test/unit_tests/braket/jobs/local/test_local_job.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import json +from subprocess import CompletedProcess from unittest.mock import Mock, mock_open, patch import pytest @@ -46,34 +47,58 @@ def test_envs(): return {"Test": "Env"} +def success_result(): + return CompletedProcess(None, 0, str.encode("Test Output"), str.encode("Test Error")) + + @pytest.mark.parametrize( - "creation_kwargs", + "creation_kwargs, run_result", [ ( { "jobName": "Test-Job-Name", "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, "checkpointConfig": {"localPath": "test/local/path/"}, - } + }, + success_result(), ), ( { "jobName": "Test-Job-Name", "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, "checkpointConfig": {}, - } + }, + success_result(), ), ( { "jobName": "Test-Job-Name", "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, - } + }, + success_result(), ), ( { "jobName": "Test-Job-Name", "algorithmSpecification": {}, - } + }, + success_result(), + ), + ( + { + "jobName": "Test-Job-Name", + "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, + "checkpointConfig": {"localPath": "test/local/path/"}, + }, + CompletedProcess(None, 0, None, None), + ), + ( + { + "jobName": "Test-Job-Name", + "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, + "checkpointConfig": {"localPath": "test/local/path/"}, + }, + Exception("Test Exception"), ), ], ) @@ -90,16 +115,16 @@ def test_create( mock_prepare_job, aws_session, creation_kwargs, + run_result, job_results, - run_log, test_envs, ): with patch("builtins.open", mock_open()) as file_open: - mock_dir.return_value = False + mock_dir.side_effect = [False, True] mock_prepare_job.return_value = creation_kwargs mock_container_open = mock_container.return_value.__enter__.return_value - mock_container_open.run_log = run_log + mock_container_open.run_result = run_result file_read = file_open() file_read.read.return_value = json.dumps(job_results) mock_setup.return_value = test_envs @@ -121,19 +146,47 @@ def test_create( assert job.name == "Test-Job-Name" assert job.arn == "local:job/Test-Job-Name" assert job.state() == "COMPLETED" - assert job.run_log == run_log assert job.metadata() is None assert job.cancel() is None assert job.download_result() is None assert job.logs() is None assert job.result() == job_results["dataDictionary"] + mock_setup.assert_called_with(mock_container_open, aws_session, **creation_kwargs) + mock_container_open.run_local_job.assert_called_with(test_envs) + if isinstance(run_result, Exception): + assert file_read.write.call_count == 2 + file_read.write.assert_called_with(run_result) + elif run_result.stdout or run_result.stderr: + assert file_read.write.call_count == 3 + file_read.write.assert_any_call("Test Output") + file_read.write.assert_any_call("Test Error") + else: + file_read.write.assert_not_called() + + +@patch("os.path.isdir") +def test_run_log(mock_dir, run_log): + mock_dir.return_value = True + with patch("builtins.open", mock_open()) as file_open: + file_read = file_open() + file_read.read.return_value = run_log + job = LocalQuantumJob("local:job/Test-Job-Name") + assert job.run_log == run_log assert job.metrics() == { "Cost": [-4.034, -3.957], "iteration_number": [0.0, 1.0], "timestamp": [1633027264.5406773, 1633027288.6284382], } - mock_setup.assert_called_with(mock_container_open, aws_session, **creation_kwargs) - mock_container_open.run_local_job.assert_called_with(test_envs) + + +def test_parse_run_log(run_log): + job = LocalQuantumJob("local:job/Test-Job-Name", run_log) + assert job.run_log == run_log + assert job.metrics() == { + "Cost": [-4.034, -3.957], + "iteration_number": [0.0, 1.0], + "timestamp": [1633027264.5406773, 1633027288.6284382], + } def test_create_invalid_arg(): 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 ad061cc0..3f6487a0 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 @@ -14,6 +14,7 @@ import base64 import subprocess from pathlib import Path +from subprocess import CompletedProcess from unittest.mock import Mock, patch import pytest @@ -37,6 +38,11 @@ def aws_session(): return _aws_session +@pytest.fixture +def run_result(): + return CompletedProcess(None, 0, None, None) + + @patch("subprocess.check_output") @patch("subprocess.run") def test_start_and_stop(mock_run, mock_check_output, image_uri, aws_session): @@ -150,7 +156,7 @@ def test_pull_container_forced_update_invalid_name( @patch("subprocess.check_output") @patch("subprocess.run") -def test_run_job_success(mock_run, mock_check_output, repo_uri, image_uri, aws_session): +def test_run_job_success(mock_run, mock_check_output, repo_uri, image_uri, aws_session, run_result): local_image_name = "LocalImageName" running_container_name = "RunningContainer" env_variables = { @@ -158,17 +164,15 @@ def test_run_job_success(mock_run, mock_check_output, repo_uri, image_uri, aws_s "ENV1": "VALUE1", } run_program_name = "Run Program Name" - expected_run_output = "Expected Run Output" mock_check_output.side_effect = [ str.encode(local_image_name), str.encode(running_container_name), str.encode(run_program_name), - str.encode(expected_run_output), ] + mock_run.side_effect = [run_result, None] with _LocalJobContainer(image_uri, aws_session) as container: container.run_local_job(env_variables) - run_output = container.run_log - assert run_output == expected_run_output + assert container.run_result == run_result 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"] @@ -176,7 +180,8 @@ def test_run_job_success(mock_run, mock_check_output, repo_uri, image_uri, aws_s mock_check_output.assert_any_call( ["docker", "exec", running_container_name, "printenv", "SAGEMAKER_PROGRAM"] ) - mock_check_output.assert_any_call( + assert mock_check_output.call_count == 3 + mock_run.assert_any_call( [ "docker", "exec", @@ -189,16 +194,17 @@ def test_run_job_success(mock_run, mock_check_output, repo_uri, image_uri, aws_s running_container_name, "python", run_program_name, - ] + ], + capture_output=True, ) - assert mock_check_output.call_count == 4 mock_run.assert_any_call(["docker", "stop", running_container_name]) - assert mock_run.call_count == 1 + assert mock_run.call_count == 2 @patch("subprocess.check_output") @patch("subprocess.run") -def test_customer_script_fails(mock_run, mock_check_output, repo_uri, image_uri, aws_session): +def test_run_customer_script_fails(mock_run, mock_check_output, repo_uri, image_uri, aws_session): + mock_logger = Mock() local_image_name = "LocalImageName" running_container_name = "RunningContainer" env_variables = { @@ -206,20 +212,72 @@ def test_customer_script_fails(mock_run, mock_check_output, repo_uri, image_uri, "ENV1": "VALUE1", } run_program_name = "Run Program Name" - expected_error_output = "Expected Error Output" mock_check_output.side_effect = [ str.encode(local_image_name), str.encode(running_container_name), str.encode(run_program_name), - subprocess.CalledProcessError("Test Error", "test", str.encode(expected_error_output)), ] - with _LocalJobContainer(image_uri, aws_session) as container: + expected_exception = Exception("Test Error") + mock_run.side_effect = [expected_exception, None] + with _LocalJobContainer(image_uri, aws_session, mock_logger) as container: container.run_local_job(env_variables) - run_output = container.run_log - assert run_output == expected_error_output - assert mock_check_output.call_count == 4 + assert container.run_result == expected_exception + assert mock_check_output.call_count == 3 mock_run.assert_any_call(["docker", "stop", running_container_name]) - assert mock_run.call_count == 1 + assert mock_run.call_count == 2 + mock_logger.error.assert_called_with(expected_exception) + + +@patch("subprocess.check_output") +@patch("subprocess.run") +def test_customer_script_check_output( + mock_run, mock_check_output, repo_uri, image_uri, aws_session +): + mock_logger = Mock() + local_image_name = "LocalImageName" + running_container_name = "RunningContainer" + env_variables = { + "ENV0": "VALUE0", + "ENV1": "VALUE1", + } + run_program_name = "Run Program Name" + mock_check_output.side_effect = [ + str.encode(local_image_name), + str.encode(running_container_name), + str.encode(run_program_name), + ] + run_result = CompletedProcess(None, 0, str.encode("Test Output"), str.encode("Test Error")) + mock_run.side_effect = [run_result, None] + with _LocalJobContainer(image_uri, aws_session, mock_logger) as container: + container.run_local_job(env_variables) + assert container.run_result == run_result + 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"] + ) + assert mock_check_output.call_count == 3 + mock_run.assert_any_call( + [ + "docker", + "exec", + "-w", + "/opt/ml/code/", + "-e", + "ENV0=VALUE0", + "-e", + "ENV1=VALUE1", + running_container_name, + "python", + run_program_name, + ], + capture_output=True, + ) + mock_run.assert_any_call(["docker", "stop", running_container_name]) + assert mock_run.call_count == 2 + mock_logger.error.assert_called_with("Test Error") @patch("subprocess.check_output") From 0c520f89a1c04ab639b5ecc329bc7a999a9b651a Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 29 Jul 2022 23:01:00 +0000 Subject: [PATCH 0504/1165] prepare release v1.27.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 e0405843..01ca94ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.27.1 (2022-07-29) + +### Bug Fixes and Other Changes + + * customer script errors not shown when local jobs run from a notebook. + ## v1.27.0 (2022-07-26) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index c1680a94..11b61a6d 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.27.1.dev0" +__version__ = "1.27.1" From bdd19ac146105dcb3162de69309f03f23544b70f Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 29 Jul 2022 23:01:00 +0000 Subject: [PATCH 0505/1165] update development version to v1.27.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 11b61a6d..186f8f7b 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.27.1" +__version__ = "1.27.2.dev0" From 0cf2f59fa24ab82031ece126a54fca5752cfbc4e Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Thu, 4 Aug 2022 17:56:27 -0700 Subject: [PATCH 0506/1165] feature: OpenQASM default IR and OpenQASM Local Simulator (#418) BDK now uses OpenQASM 3.0 as the default IR for Circuits. This support is made possible by the new local simulator support for OpenQASM 3.0 programs. The local simulators (braket_sv, braket_dm) now support OpenQASM 3.0, including advanced language features not currently supported elsewhere in the service. Check out [Simulating_Advanced_OpenQASM_Programs_with_the_Local_Simulator.ipynb](https://github.com/aws/amazon-braket-examples/blob/main/examples/braket_features/Simulating_Advanced_OpenQASM_Programs_with_the_Local_Simulator.ipynb) to see examples! Co-authored-by: Kshitij Chhabra Co-authored-by: Synthia <93848130+synthia2080@users.noreply.github.com> Co-authored-by: valegogu <104395310+valegogu@users.noreply.github.com> Co-authored-by: Megha <57037661+mshenoy87@users.noreply.github.com> Co-authored-by: toniraggs <105249045+toniraggs@users.noreply.github.com> Co-authored-by: kfox808 Co-authored-by: Kira Fox Co-authored-by: Megha Shenoy --- setup.py | 4 +- src/braket/aws/aws_quantum_task.py | 28 +- src/braket/circuits/circuit.py | 103 +++- src/braket/circuits/compiler_directive.py | 46 +- src/braket/circuits/compiler_directives.py | 10 +- src/braket/circuits/gate.py | 67 ++- src/braket/circuits/gates.py | 325 ++++++++-- src/braket/circuits/instruction.py | 20 +- src/braket/circuits/noise.py | 65 +- src/braket/circuits/noises.py | 120 +++- src/braket/circuits/observable.py | 69 ++- src/braket/circuits/observables.py | 97 ++- src/braket/circuits/result_type.py | 53 +- src/braket/circuits/result_types.py | 62 +- src/braket/circuits/serialization.py | 56 ++ src/braket/devices/local_simulator.py | 31 +- .../tasks/gate_model_quantum_task_result.py | 25 +- .../gate_model_device_testing_utils.py | 556 ++++++++++-------- .../test_local_braket_simulator.py | 60 +- .../integ_tests/test_local_noise_simulator.py | 54 +- .../braket/aws/test_aws_quantum_task.py | 40 +- .../braket/circuits/test_circuit.py | 124 ++++ .../circuits/test_compiler_directive.py | 22 +- .../circuits/test_compiler_directives.py | 30 +- test/unit_tests/braket/circuits/test_gate.py | 27 +- test/unit_tests/braket/circuits/test_gates.py | 488 ++++++++++++++- .../braket/circuits/test_instruction.py | 15 +- test/unit_tests/braket/circuits/test_noise.py | 24 + .../unit_tests/braket/circuits/test_noises.py | 191 ++++++ .../braket/circuits/test_observable.py | 25 +- .../braket/circuits/test_observables.py | 169 ++++++ .../braket/circuits/test_result_type.py | 29 +- .../braket/circuits/test_result_types.py | 87 +++ .../braket/devices/test_local_simulator.py | 122 +++- .../test_gate_model_quantum_task_result.py | 37 +- tox.ini | 3 +- 36 files changed, 2851 insertions(+), 433 deletions(-) create mode 100644 src/braket/circuits/serialization.py diff --git a/setup.py b/setup.py index 8074143d..ecbc19d5 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-default-simulator", - "amazon-braket-schemas>=1.10.0", + "amazon-braket-schemas>=1.10.1", + "amazon-braket-default-simulator>=1.7.1", "backoff", "boltons", "boto3", diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 1691068c..88b00e5a 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -23,8 +23,15 @@ from braket.annealing.problem import Problem from braket.aws.aws_session import AwsSession +from braket.circuits import Instruction from braket.circuits.circuit import Circuit from braket.circuits.circuit_helpers import validate_circuit_and_shots +from braket.circuits.compiler_directives import StartVerbatimBox +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + QubitReferenceType, +) from braket.device_schema import GateModelParameters from braket.device_schema.dwave import ( Dwave2000QDeviceParameters, @@ -451,6 +458,10 @@ def _( *args, **kwargs, ) -> AwsQuantumTask: + if open_qasm_program.inputs is not None: + raise ValueError( + "OpenQASM Program inputs are only currently supported in the LocalSimulator." + ) create_task_kwargs.update({"action": open_qasm_program.json()}) task_arn = aws_session.create_quantum_task(**create_task_kwargs) return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) @@ -500,8 +511,23 @@ def _( paradigmParameters=paradigm_parameters ) + qubit_reference_type = QubitReferenceType.VIRTUAL + + if disable_qubit_rewiring or Instruction(StartVerbatimBox()) in circuit.instructions: + qubit_reference_type = QubitReferenceType.PHYSICAL + + serialization_properties = OpenQASMSerializationProperties( + qubit_reference_type=qubit_reference_type + ) + create_task_kwargs.update( - {"action": circuit.to_ir().json(), "deviceParameters": device_parameters.json()} + { + "action": circuit.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + ).json(), + "deviceParameters": device_parameters.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 0a12d8a9..92764469 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -41,8 +41,15 @@ from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput from braket.circuits.result_type import ObservableResultType, ResultType +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + QubitReferenceType, + SerializationProperties, +) from braket.circuits.unitary_calculation import calculate_unitary, calculate_unitary_big_endian -from braket.ir.jaqcd import Program +from braket.ir.jaqcd import Program as JaqcdProgram +from braket.ir.openqasm import Program as OpenQasmProgram SubroutineReturn = TypeVar( "SubroutineReturn", Iterable[Instruction], Instruction, ResultType, Iterable[ResultType] @@ -747,7 +754,8 @@ def apply_initialization_noise( noise: Union[Type[Noise], Iterable[Type[Noise]]], target_qubits: Optional[QubitSetInput] = None, ) -> Circuit: - """Apply `noise` at the beginning of the circuit for every qubit (default) or target_qubits`. + """Apply `noise` at the beginning of the circuit for every qubit (default) or + target_qubits`. Only when `target_qubits` is given can the noise be applied to an empty circuit. @@ -1059,25 +1067,104 @@ def diagram(self, circuit_diagram_class=AsciiCircuitDiagram) -> str: """ return circuit_diagram_class.build_diagram(self) - def to_ir(self) -> Program: + def to_ir( + self, + ir_type: IRType = IRType.JAQCD, + serialization_properties: SerializationProperties = None, + ) -> Union[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. + Args: + ir_type (IRType): The IRType to use for converting the circuit object to its + IR representation. + serialization_properties (SerializationProperties): 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: - Program: A Braket quantum circuit description program in JSON format. + (Union[OpenQasmProgram, JaqcdProgram]): A representation of the circuit in the + `ir_type` format. + + Raises: + ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization + properties don't correspond to the `ir_type`. """ - ir_instructions = [instruction.to_ir() for instruction in self.instructions] - ir_results = [result_type.to_ir() for result_type in self.result_types] + if ir_type == IRType.JAQCD: + return self._to_jaqcd() + elif ir_type == IRType.OPENQASM: + if serialization_properties and not isinstance( + serialization_properties, OpenQASMSerializationProperties + ): + raise ValueError( + "serialization_properties must be of type OpenQASMSerializationProperties " + "for IRType.OPENQASM." + ) + return self._to_openqasm(serialization_properties or OpenQASMSerializationProperties()) + else: + raise ValueError(f"Supplied ir_type {ir_type} is not supported.") + + def _to_jaqcd(self) -> JaqcdProgram: + jaqcd_ir_type = IRType.JAQCD + ir_instructions = [instr.to_ir(ir_type=jaqcd_ir_type) for instr in self.instructions] + ir_results = [result_type.to_ir(ir_type=jaqcd_ir_type) for result_type in self.result_types] ir_basis_rotation_instructions = [ - instr.to_ir() for instr in self.basis_rotation_instructions + instr.to_ir(ir_type=jaqcd_ir_type) for instr in self.basis_rotation_instructions ] - return Program.construct( + return JaqcdProgram.construct( instructions=ir_instructions, results=ir_results, basis_rotation_instructions=ir_basis_rotation_instructions, ) + def _to_openqasm( + self, serialization_properties: OpenQASMSerializationProperties + ) -> OpenQasmProgram: + ir_instructions = self._create_openqasm_header(serialization_properties) + 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 + ] + ) + + 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 + ] + ) + else: + for idx, qubit in enumerate(self.qubits): + qubit_target = serialization_properties.format_target(int(qubit)) + ir_instructions.append(f"b[{idx}] = measure {qubit_target};") + + return OpenQasmProgram.construct(source="\n".join(ir_instructions)) + + def _create_openqasm_header( + self, serialization_properties: OpenQASMSerializationProperties + ) -> List[str]: + ir_instructions = ["OPENQASM 3.0;"] + if not self.result_types: + ir_instructions.append(f"bit[{self.qubit_count}] b;") + + if serialization_properties.qubit_reference_type == QubitReferenceType.VIRTUAL: + total_qubits = max(self.qubits).real + 1 + ir_instructions.append(f"qubit[{total_qubits}] q;") + elif serialization_properties.qubit_reference_type != QubitReferenceType.PHYSICAL: + raise ValueError( + f"Invalid qubit_reference_type " + f"{serialization_properties.qubit_reference_type} supplied." + ) + return ir_instructions + def as_unitary(self) -> np.ndarray: r""" Returns the unitary matrix representation, in little endian format, of the entire circuit. diff --git a/src/braket/circuits/compiler_directive.py b/src/braket/circuits/compiler_directive.py index f6b7a936..d28a1d63 100644 --- a/src/braket/circuits/compiler_directive.py +++ b/src/braket/circuits/compiler_directive.py @@ -13,9 +13,11 @@ from __future__ import annotations -from typing import Sequence, Tuple +from typing import Any, Sequence, Tuple from braket.circuits.operator import Operator +from braket.circuits.qubit_set import QubitSet +from braket.circuits.serialization import IRType, SerializationProperties class CompilerDirective(Operator): @@ -43,6 +45,45 @@ def ascii_symbols(self) -> Tuple[str, ...]: """Tuple[str, ...]: Returns the ascii symbols for the compiler directive.""" return self._ascii_symbols + def to_ir( + self, + target: QubitSet = None, + ir_type: IRType = IRType.JAQCD, + serialization_properties: SerializationProperties = None, + **kwargs, + ): + """Returns IR object of the compiler directive. + + Args: + target (QubitSet): target qubit(s). Defaults to None + ir_type(IRType) : The IRType to use for converting the compiler directive object to its + IR representation. Defaults to IRType.JAQCD. + serialization_properties (SerializationProperties): 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. + **kwargs: Keyword arguments + + Returns: + IR object of the compiler directive. + + 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() + else: + raise ValueError(f"Supplied ir_type {ir_type} is not supported.") + + def _to_jaqcd(self) -> Any: + """Returns the JAQCD representation of the compiler directive.""" + raise NotImplementedError("to_jaqcd has not been implemented yet.") + + def _to_openqasm(self) -> str: + """Returns the openqasm string representation of the compiler directive.""" + raise NotImplementedError("to_openqasm has not been implemented yet.") + def counterpart(self) -> CompilerDirective: """Returns the "opposite" counterpart to this compiler directive. @@ -56,9 +97,6 @@ def counterpart(self) -> CompilerDirective: f"Compiler directive {self.name} does not have counterpart implemented" ) - def to_ir(self, *args, **kwargs): - raise NotImplementedError("to_ir has not been implemented yet.") - def __eq__(self, other): return isinstance(other, CompilerDirective) and self.name == other.name diff --git a/src/braket/circuits/compiler_directives.py b/src/braket/circuits/compiler_directives.py index d17a2e38..36d770de 100644 --- a/src/braket/circuits/compiler_directives.py +++ b/src/braket/circuits/compiler_directives.py @@ -27,9 +27,12 @@ def __init__(self): def counterpart(self) -> CompilerDirective: return EndVerbatimBox() - def to_ir(self, *args, **kwargs): + def _to_jaqcd(self, *args, **kwargs): return ir.StartVerbatimBox.construct() + def _to_openqasm(self) -> str: + return "#pragma braket verbatim\nbox{" + class EndVerbatimBox(CompilerDirective): """ @@ -43,5 +46,8 @@ def __init__(self): def counterpart(self) -> CompilerDirective: return StartVerbatimBox() - def to_ir(self, *args, **kwargs): + def _to_jaqcd(self, *args, **kwargs): return ir.EndVerbatimBox.construct() + + def _to_openqasm(self) -> str: + return "}" diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index bb9c32c8..c5842b16 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -17,6 +17,11 @@ from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.qubit_set import QubitSet +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + SerializationProperties, +) class Gate(QuantumOperator): @@ -53,15 +58,71 @@ def adjoint(self) -> List[Gate]: """ raise NotImplementedError(f"Gate {self.name} does not have adjoint implemented") - def to_ir(self, target: QubitSet) -> Any: + def to_ir( + self, + target: QubitSet, + ir_type: IRType = IRType.JAQCD, + serialization_properties: SerializationProperties = None, + ) -> Any: """Returns IR object of quantum operator and target Args: - target (QubitSet): target qubit(s) + target (QubitSet): target qubit(s). + ir_type(IRType) : The IRType to use for converting the gate object to its + IR representation. Defaults to IRType.JAQCD. + serialization_properties (SerializationProperties): 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: IR object of the quantum operator and target + + Raises: + ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization + properties don't correspond to the `ir_type`. + """ + if ir_type == IRType.JAQCD: + return self._to_jaqcd(target) + elif ir_type == IRType.OPENQASM: + if serialization_properties and not isinstance( + serialization_properties, OpenQASMSerializationProperties + ): + raise ValueError( + "serialization_properties must be of type OpenQASMSerializationProperties " + "for IRType.OPENQASM." + ) + return self._to_openqasm( + target, serialization_properties or OpenQASMSerializationProperties() + ) + else: + 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. + + Args: + target (QubitSet): target qubit(s). + + Returns: + Any: JAQCD object representing the gate. + """ + raise NotImplementedError("to_jaqcd has not been implemented yet.") + + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties + ) -> str: + """ + Returns the openqasm string representation of the gate. + + Args: + target (QubitSet): target qubit(s). + serialization_properties (OpenQASMSerializationProperties): The serialization properties + to use while serializing the object to the IR representation. + + Returns: + str: Representing the openqasm representation of the gate. """ - raise NotImplementedError("to_ir has not been implemented yet.") + raise NotImplementedError("to_openqasm has not been implemented yet.") @property def ascii_symbols(self) -> Tuple[str, ...]: diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 8a1a9768..39d3e607 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -28,6 +28,7 @@ ) from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput +from braket.circuits.serialization import OpenQASMSerializationProperties """ To add a new gate: @@ -51,9 +52,15 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [H()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.H.construct(target=target[0]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return f"h {target_qubit};" + def to_matrix(self) -> np.ndarray: return 1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) @@ -91,9 +98,15 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [I()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.I.construct(target=target[0]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return f"i {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.eye(2, dtype=complex) @@ -131,9 +144,15 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [X()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.X.construct(target=target[0]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return f"x {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) @@ -171,9 +190,15 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [Y()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.Y.construct(target=target[0]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ) -> str: + target_qubit = serialization_properties.format_target(int(target[0])) + return f"y {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) @@ -211,9 +236,15 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [Z()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.Z.construct(target=target[0]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ) -> str: + target_qubit = serialization_properties.format_target(int(target[0])) + return f"z {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) @@ -251,9 +282,15 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [Si()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.S.construct(target=target[0]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ) -> str: + target_qubit = serialization_properties.format_target(int(target[0])) + return f"s {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, 1.0j]], dtype=complex) @@ -291,9 +328,15 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [S()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.Si.construct(target=target[0]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ) -> str: + target_qubit = serialization_properties.format_target(int(target[0])) + return f"si {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.array([[1, 0], [0, -1j]], dtype=complex) @@ -331,9 +374,15 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [Ti()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.T.construct(target=target[0]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return f"t {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, np.exp(1j * np.pi / 4)]], dtype=complex) @@ -371,9 +420,15 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [T()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.Ti.construct(target=target[0]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return f"ti {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, np.exp(-1j * np.pi / 4)]], dtype=complex) @@ -411,9 +466,15 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [Vi()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.V.construct(target=target[0]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return f"v {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.array([[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]], dtype=complex) @@ -451,9 +512,15 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [V()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.Vi.construct(target=target[0]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return f"vi {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.array(([[0.5 - 0.5j, 0.5 + 0.5j], [0.5 + 0.5j, 0.5 - 0.5j]]), dtype=complex) @@ -499,9 +566,15 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=[angled_ascii_characters("Rx", angle)], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet, **kwargs): return ir.Rx.construct(target=target[0], angle=self.angle) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return f"rx({self.angle}) {target_qubit};" + def to_matrix(self) -> np.ndarray: cos = np.cos(self.angle / 2) sin = np.sin(self.angle / 2) @@ -551,9 +624,15 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=[angled_ascii_characters("Ry", angle)], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.Ry.construct(target=target[0], angle=self.angle) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return f"ry({self.angle}) {target_qubit};" + def to_matrix(self) -> np.ndarray: cos = np.cos(self.angle / 2) sin = np.sin(self.angle / 2) @@ -603,9 +682,15 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=[angled_ascii_characters("Rz", angle)], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.Rz.construct(target=target[0], angle=self.angle) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return f"rz({self.angle}) {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.array( [[np.exp(-1j * self.angle / 2), 0], [0, np.exp(1j * self.angle / 2)]], dtype=complex @@ -655,9 +740,16 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=[angled_ascii_characters("PHASE", angle)], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.PhaseShift.construct(target=target[0], angle=self.angle) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit = serialization_properties.format_target(int(target[0])) + # alternatively, "ctrl @ phase({self.angle}) {target_qubit};" + return f"phaseshift({self.angle}) {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, np.exp(1j * self.angle)]], dtype=complex) @@ -703,9 +795,16 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [CNot()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.CNot.construct(control=target[0], target=target[1]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + control_qubit = serialization_properties.format_target(int(target[0])) + target_qubit = serialization_properties.format_target(int(target[1])) + return f"cnot {control_qubit}, {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.array( [ @@ -751,9 +850,16 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [Swap()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.Swap.construct(targets=[target[0], target[1]]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit_0 = serialization_properties.format_target(int(target[0])) + target_qubit_1 = serialization_properties.format_target(int(target[1])) + return f"swap {target_qubit_0}, {target_qubit_1};" + def to_matrix(self) -> np.ndarray: return np.array( [ @@ -799,9 +905,16 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [self, self, self] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.ISwap.construct(targets=[target[0], target[1]]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit_0 = serialization_properties.format_target(int(target[0])) + target_qubit_1 = serialization_properties.format_target(int(target[1])) + return f"iswap {target_qubit_0}, {target_qubit_1};" + def to_matrix(self) -> np.ndarray: return np.array( [ @@ -855,9 +968,16 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.PSwap.construct(targets=[target[0], target[1]], angle=self.angle) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit_0 = serialization_properties.format_target(int(target[0])) + target_qubit_1 = serialization_properties.format_target(int(target[1])) + return f"pswap({self.angle}) {target_qubit_0}, {target_qubit_1};" + def to_matrix(self) -> np.ndarray: return np.array( [ @@ -917,9 +1037,16 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.XY.construct(targets=[target[0], target[1]], angle=self.angle) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit_1 = serialization_properties.format_target(int(target[0])) + target_qubit_2 = serialization_properties.format_target(int(target[1])) + return f"xy({self.angle}) {target_qubit_1}, {target_qubit_2};" + def to_matrix(self) -> np.ndarray: cos = np.cos(self.angle / 2) sin = np.sin(self.angle / 2) @@ -978,9 +1105,16 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=["C", angled_ascii_characters("PHASE", angle)], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.CPhaseShift.construct(control=target[0], target=target[1], angle=self.angle) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + control_qubit = serialization_properties.format_target(int(target[0])) + target_qubit = serialization_properties.format_target(int(target[1])) + return f"cphaseshift({self.angle}) {control_qubit}, {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, 1.0, np.exp(1j * self.angle)]) @@ -1029,9 +1163,16 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=["C", angled_ascii_characters("PHASE00", angle)], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.CPhaseShift00.construct(control=target[0], target=target[1], angle=self.angle) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + control_qubit = serialization_properties.format_target(int(target[0])) + target_qubit = serialization_properties.format_target(int(target[1])) + return f"cphaseshift00({self.angle}) {control_qubit}, {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.diag([np.exp(1j * self.angle), 1.0, 1.0, 1.0]) @@ -1080,9 +1221,16 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=["C", angled_ascii_characters("PHASE01", angle)], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.CPhaseShift01.construct(control=target[0], target=target[1], angle=self.angle) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + control_qubit = serialization_properties.format_target(int(target[0])) + target_qubit = serialization_properties.format_target(int(target[1])) + return f"cphaseshift01({self.angle}) {control_qubit}, {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.diag([1.0, np.exp(1j * self.angle), 1.0, 1.0]) @@ -1131,9 +1279,16 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=["C", angled_ascii_characters("PHASE10", angle)], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.CPhaseShift10.construct(control=target[0], target=target[1], angle=self.angle) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + control_qubit = serialization_properties.format_target(int(target[0])) + target_qubit = serialization_properties.format_target(int(target[1])) + return f"cphaseshift10({self.angle}) {control_qubit}, {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, np.exp(1j * self.angle), 1.0]) @@ -1177,9 +1332,16 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [self, self, self] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.CV.construct(control=target[0], target=target[1]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + control_qubit = serialization_properties.format_target(int(target[0])) + target_qubit = serialization_properties.format_target(int(target[1])) + return f"cv {control_qubit}, {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.array( [ @@ -1225,9 +1387,16 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [CY()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.CY.construct(control=target[0], target=target[1]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit = serialization_properties.format_target(int(target[1])) + control_qubit = serialization_properties.format_target(int(target[0])) + return f"cy {control_qubit}, {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.array( [ @@ -1273,9 +1442,16 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [CZ()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.CZ.construct(control=target[0], target=target[1]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit = serialization_properties.format_target(int(target[1])) + control_qubit = serialization_properties.format_target(int(target[0])) + return f"cz {control_qubit}, {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.diag([complex(1.0), 1.0, 1.0, -1.0]) @@ -1313,9 +1489,16 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [ECR()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.ECR.construct(targets=[target[0], target[1]]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit_0 = serialization_properties.format_target(int(target[0])) + target_qubit_1 = serialization_properties.format_target(int(target[1])) + return f"ecr {target_qubit_0}, {target_qubit_1};" + def to_matrix(self) -> np.ndarray: return ( 1 @@ -1370,9 +1553,16 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.XX.construct(targets=[target[0], target[1]], angle=self.angle) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit_1 = serialization_properties.format_target(int(target[0])) + target_qubit_2 = serialization_properties.format_target(int(target[1])) + return f"xx({self.angle}) {target_qubit_1}, {target_qubit_2};" + def to_matrix(self) -> np.ndarray: cos = np.cos(self.angle / 2) isin = 1.0j * np.sin(self.angle / 2) @@ -1436,9 +1626,16 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.YY.construct(targets=[target[0], target[1]], angle=self.angle) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit_1 = serialization_properties.format_target(int(target[0])) + target_qubit_2 = serialization_properties.format_target(int(target[1])) + return f"yy({self.angle}) {target_qubit_1}, {target_qubit_2};" + def to_matrix(self) -> np.ndarray: cos = np.cos(self.angle / 2) isin = 1.0j * np.sin(self.angle / 2) @@ -1502,9 +1699,16 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.ZZ.construct(targets=[target[0], target[1]], angle=self.angle) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit_1 = serialization_properties.format_target(int(target[0])) + target_qubit_2 = serialization_properties.format_target(int(target[1])) + return f"zz({self.angle}) {target_qubit_1}, {target_qubit_2};" + def to_matrix(self) -> np.ndarray: return np.array( [ @@ -1559,9 +1763,17 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [CCNot()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.CCNot.construct(controls=[target[0], target[1]], target=target[2]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + control_qubit_0 = serialization_properties.format_target(int(target[0])) + control_qubit_1 = serialization_properties.format_target(int(target[1])) + target_qubit = serialization_properties.format_target(int(target[2])) + return f"ccnot {control_qubit_0}, {control_qubit_1}, {target_qubit};" + def to_matrix(self) -> np.ndarray: return np.array( [ @@ -1612,9 +1824,18 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [CSwap()] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.CSwap.construct(control=target[0], targets=[target[1], target[2]]) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + control_qubit = serialization_properties.format_target(int(target[0])) + target_qubit_0 = serialization_properties.format_target(int(target[1])) + target_qubit_1 = serialization_properties.format_target(int(target[2])) + + return f"cswap {control_qubit}, {target_qubit_0}, {target_qubit_1};" + def to_matrix(self) -> np.ndarray: return np.array( [ @@ -1686,12 +1907,25 @@ def to_matrix(self): def adjoint(self) -> List[Gate]: return [Unitary(self._matrix.conj().T, display_name=f"({self.ascii_symbols})^†")] - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.Unitary.construct( targets=[qubit for qubit in target], matrix=Unitary._transform_matrix_to_ir(self._matrix), ) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + qubits = [serialization_properties.format_target(int(qubit)) for qubit in target] + formatted_matrix = np.array2string( + self._matrix, + separator=", ", + formatter={"all": lambda x: format_complex(x)}, + threshold=float("inf"), + ).replace("\n", "") + + return f"#pragma braket unitary({formatted_matrix}) {', '.join(qubits)}" + def __eq__(self, other): if isinstance(other, Unitary): return self.matrix_equivalence(other) @@ -1763,3 +1997,26 @@ def get_angle(self, **kwargs): self.angle.subs(kwargs) if isinstance(self.angle, FreeParameterExpression) else self.angle ) return type(self)(angle=new_angle) + + +def format_complex(number: complex) -> str: + """ + Format a complex number into + im to be consumed by the braket unitary pragma + + Args: + number (complex): A complex number. + + Returns: + 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: + return f"{number.real}" + else: + if number.imag: + return f"{number.imag}im" + else: + return "0" diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index 4e6a0ba8..623d48d8 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -21,6 +21,7 @@ from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput +from braket.circuits.serialization import IRType, SerializationProperties # InstructionOperator is a type alias, and it can be expanded to include other operators InstructionOperator = Operator @@ -101,12 +102,27 @@ def adjoint(self) -> List[Instruction]: return [Instruction(operator.counterpart(), self._target)] raise NotImplementedError(f"Adjoint not supported for {operator}") - def to_ir(self): + def to_ir( + self, + ir_type: IRType = IRType.JAQCD, + serialization_properties: SerializationProperties = None, + ): """ 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: + ir_type(IRType) : The IRType to use for converting the instruction object to its + IR representation. + serialization_properties (SerializationProperties): 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. """ - return self._operator.to_ir([int(qubit) for qubit in self._target]) + return self._operator.to_ir( + [int(qubit) for qubit in self._target], + ir_type=ir_type, + serialization_properties=serialization_properties, + ) @property def ascii_symbols(self) -> Tuple[str, ...]: diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py index 582e8e91..0bb3bce7 100644 --- a/src/braket/circuits/noise.py +++ b/src/braket/circuits/noise.py @@ -20,6 +20,11 @@ from braket.circuits.parameterizable import Parameterizable from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.qubit_set import QubitSet +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + SerializationProperties, +) class Noise(QuantumOperator): @@ -56,15 +61,71 @@ def name(self) -> str: """ return self.__class__.__name__ - def to_ir(self, target: QubitSet) -> Any: + def to_ir( + self, + target: QubitSet, + ir_type: IRType = IRType.JAQCD, + serialization_properties: SerializationProperties = None, + ) -> Any: """Returns IR object of quantum operator and target Args: target (QubitSet): target qubit(s) + ir_type(IRType) : The IRType to use for converting the noise object to its + IR representation. Defaults to IRType.JAQCD. + serialization_properties (SerializationProperties): 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: IR object of the quantum operator and target + + Raises: + ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization + properties don't correspond to the `ir_type`. + """ + if ir_type == IRType.JAQCD: + return self._to_jaqcd(target) + elif ir_type == IRType.OPENQASM: + if serialization_properties and not isinstance( + serialization_properties, OpenQASMSerializationProperties + ): + raise ValueError( + "serialization_properties must be of type OpenQASMSerializationProperties " + "for IRType.OPENQASM." + ) + return self._to_openqasm( + target, serialization_properties or OpenQASMSerializationProperties() + ) + else: + 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. + + Args: + target (QubitSet): target qubit(s). + + Returns: + Any: JAQCD object representing the noise. + """ + raise NotImplementedError("to_jaqcd has not been implemented yet.") + + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties + ) -> str: + """ + Returns the openqasm string representation of the noise. + + Args: + target (QubitSet): target qubit(s). + serialization_properties (OpenQASMSerializationProperties): The serialization properties + to use while serializing the object to the IR representation. + + Returns: + str: Representing the openqasm representation of the noise. """ - raise NotImplementedError("to_ir has not been implemented yet.") + raise NotImplementedError("to_openqasm has not been implemented yet.") def to_matrix(self, *args, **kwargs) -> Any: """Returns a list of matrices defining the Kraus matrices of the noise channel. diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py index 6b58df90..f5612d44 100644 --- a/src/braket/circuits/noises.py +++ b/src/braket/circuits/noises.py @@ -20,6 +20,7 @@ from braket.circuits import circuit from braket.circuits.free_parameter import FreeParameter from braket.circuits.free_parameter_expression import FreeParameterExpression +from braket.circuits.gates import format_complex from braket.circuits.instruction import Instruction from braket.circuits.noise import ( DampingNoise, @@ -37,6 +38,7 @@ ) from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput +from braket.circuits.serialization import OpenQASMSerializationProperties """ To add a new Noise implementation: @@ -81,9 +83,15 @@ def __init__(self, probability: Union[FreeParameterExpression, float]): ascii_symbols=[_ascii_representation("BF", [probability])], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.BitFlip.construct(target=target[0], probability=self.probability) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return f"#pragma braket noise bit_flip({self.probability}) {target_qubit}" + def to_matrix(self) -> Iterable[np.ndarray]: K0 = np.sqrt(1 - self.probability) * np.eye(2, dtype=complex) K1 = np.sqrt(self.probability) * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) @@ -176,9 +184,15 @@ def __init__(self, probability: Union[FreeParameterExpression, float]): ascii_symbols=[_ascii_representation("PF", [probability])], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.PhaseFlip.construct(target=target[0], probability=self.probability) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return f"#pragma braket noise phase_flip({self.probability}) {target_qubit}" + def to_matrix(self) -> Iterable[np.ndarray]: K0 = np.sqrt(1 - self.probability) * np.eye(2, dtype=complex) K1 = np.sqrt(self.probability) * np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) @@ -294,11 +308,20 @@ def __init__( ascii_symbols=[_ascii_representation("PC", [probX, probY, probZ])], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.PauliChannel.construct( target=target[0], probX=self.probX, probY=self.probY, probZ=self.probZ ) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return ( + f"#pragma braket noise pauli_channel" + f"({self.probX}, {self.probY}, {self.probZ}) {target_qubit}" + ) + def to_matrix(self) -> Iterable[np.ndarray]: K0 = np.sqrt(1 - self.probX - self.probY - self.probZ) * np.eye(2, dtype=complex) K1 = np.sqrt(self.probX) * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) @@ -326,7 +349,7 @@ def pauli_channel( Iterable[Instruction]: `Iterable` of PauliChannel instructions. Examples: - >>> circ = Circuit().pauli_channel(0,probX=0.1,probY=0.2,probZ=0.3) + >>> circ = Circuit().pauli_channel(0, probX=0.1, probY=0.2, probZ=0.3) """ return [ Instruction(Noise.PauliChannel(probX=probX, probY=probY, probZ=probZ), target=qubit) @@ -422,9 +445,15 @@ def __init__(self, probability: Union[FreeParameterExpression, float]): ascii_symbols=[_ascii_representation("DEPO", [probability])], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.Depolarizing.construct(target=target[0], probability=self.probability) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return f"#pragma braket noise depolarizing({self.probability}) {target_qubit}" + def to_matrix(self) -> Iterable[np.ndarray]: K0 = np.sqrt(1 - self.probability) * np.eye(2, dtype=complex) K1 = np.sqrt(self.probability / 3) * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) @@ -541,11 +570,21 @@ def __init__(self, probability: Union[FreeParameterExpression, float]): ascii_symbols=[_ascii_representation("DEPO", [probability])] * 2, ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.TwoQubitDepolarizing.construct( targets=[target[0], target[1]], probability=self.probability ) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties + ): + target_qubit_0 = serialization_properties.format_target(int(target[0])) + target_qubit_1 = serialization_properties.format_target(int(target[1])) + return ( + f"#pragma braket noise two_qubit_depolarizing({self.probability}) " + f"{target_qubit_0}, {target_qubit_1}" + ) + def to_matrix(self) -> Iterable[np.ndarray]: SI = np.array([[1.0, 0.0], [0.0, 1.0]], dtype=complex) @@ -656,11 +695,21 @@ def __init__(self, probability: Union[FreeParameterExpression, float]): ascii_symbols=[_ascii_representation("DEPH", [probability])] * 2, ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.TwoQubitDephasing.construct( targets=[target[0], target[1]], probability=self.probability ) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties + ): + target_qubit_0 = serialization_properties.format_target(int(target[0])) + target_qubit_1 = serialization_properties.format_target(int(target[1])) + return ( + f"#pragma braket noise two_qubit_dephasing({self.probability}) " + f"{target_qubit_0}, {target_qubit_1}" + ) + def to_matrix(self) -> Iterable[np.ndarray]: SI = np.array([[1.0, 0.0], [0.0, 1.0]], dtype=complex) @@ -805,11 +854,6 @@ def __init__(self, probabilities: Dict[str, float]): ) self._matrix = None - def to_ir(self, target: QubitSet): - return ir.MultiQubitPauliChannel.construct( - targets=[target[0], target[1]], probabilities=self._probabilities - ) - def to_matrix(self) -> Iterable[np.ndarray]: if self._matrix is not None: return self._matrix @@ -826,6 +870,11 @@ def to_matrix(self) -> Iterable[np.ndarray]: self._matrix = K_list return self._matrix + def _to_jaqcd(self, target: QubitSet): + return ir.MultiQubitPauliChannel.construct( + targets=[target[0], target[1]], probabilities=self.probabilities + ) + @staticmethod def fixed_qubit_count() -> int: return 2 @@ -923,9 +972,15 @@ def __init__(self, gamma: Union[FreeParameterExpression, float]): ascii_symbols=[_ascii_representation("AD", [gamma])], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.AmplitudeDamping.construct(target=target[0], gamma=self.gamma) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return f"#pragma braket noise amplitude_damping({self.gamma}) {target_qubit}" + def to_matrix(self) -> Iterable[np.ndarray]: K0 = np.array([[1.0, 0.0], [0.0, np.sqrt(1 - self.gamma)]], dtype=complex) K1 = np.array([[0.0, np.sqrt(self.gamma)], [0.0, 0.0]], dtype=complex) @@ -1036,11 +1091,20 @@ def __init__( ascii_symbols=[_ascii_representation("GAD", [gamma, probability])], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.GeneralizedAmplitudeDamping.construct( target=target[0], gamma=self.gamma, probability=self.probability ) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return ( + "#pragma braket noise generalized_amplitude_damping(" + f"{self.gamma}, {self.probability}) {target_qubit}" + ) + def to_matrix(self) -> Iterable[np.ndarray]: K0 = np.sqrt(self.probability) * np.array( [[1.0, 0.0], [0.0, np.sqrt(1 - self.gamma)]], dtype=complex @@ -1072,7 +1136,7 @@ def generalized_amplitude_damping( Iterable[Instruction]: `Iterable` of GeneralizedAmplitudeDamping instructions. Examples: - >>> circ = Circuit().generalized_amplitude_damping(0, probability = 0.9, gamma=0.1) + >>> circ = Circuit().generalized_amplitude_damping(0, gamma=0.1, probability = 0.9) """ return [ Instruction( @@ -1151,9 +1215,15 @@ def __init__(self, gamma: Union[FreeParameterExpression, float]): ascii_symbols=[_ascii_representation("PD", [gamma])], ) - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.PhaseDamping.construct(target=target[0], gamma=self.gamma) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return f"#pragma braket noise phase_damping({self.gamma}) {target_qubit}" + def to_matrix(self) -> Iterable[np.ndarray]: K0 = np.array([[1.0, 0.0], [0.0, np.sqrt(1 - self.gamma)]], dtype=complex) K1 = np.array([[0.0, 0.0], [0.0, np.sqrt(self.gamma)]], dtype=complex) @@ -1254,12 +1324,28 @@ def __init__(self, matrices: Iterable[np.ndarray], display_name: str = "KR"): def to_matrix(self) -> Iterable[np.ndarray]: return self._matrices - def to_ir(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet): return ir.Kraus.construct( targets=[qubit for qubit in target], matrices=Kraus._transform_matrix_to_ir(self._matrices), ) + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties + ): + matrix_list = ", ".join( + np.array2string( + matrix, + separator=", ", + formatter={"all": lambda x: format_complex(x)}, + ).replace("\n", "") + for matrix in self._matrices + ) + qubit_list = ", ".join( + serialization_properties.format_target(int(qubit)) for qubit in target + ) + return f"#pragma braket noise kraus({matrix_list}) {qubit_list}" + @staticmethod def _transform_matrix_to_ir(matrices: Iterable[np.ndarray]): serializable = [] diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index 19c97ff8..53b6d84e 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -19,6 +19,12 @@ from braket.circuits.gate import Gate from braket.circuits.quantum_operator import QuantumOperator +from braket.circuits.qubit_set import QubitSet +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + SerializationProperties, +) class Observable(QuantumOperator): @@ -32,10 +38,65 @@ class Observable(QuantumOperator): def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) - def to_ir(self) -> List[Union[str, List[List[List[float]]]]]: - """List[Union[str, List[List[List[float]]]]]: Returns the IR - representation for the observable""" - raise NotImplementedError + def to_ir( + self, + target: QubitSet = None, + ir_type: IRType = IRType.JAQCD, + serialization_properties: SerializationProperties = None, + ) -> Union[str, List[Union[str, List[List[List[float]]]]]]: + """Returns the IR representation for the observable + + Args: + target (QubitSet): 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): 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: + Union[str, List[Union[str, List[List[List[float]]]]]]: The IR representation for + the observable. + + Raises: + ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization + properties don't correspond to the `ir_type`. + """ + if ir_type == IRType.JAQCD: + return self._to_jaqcd() + elif ir_type == IRType.OPENQASM: + if serialization_properties and not isinstance( + serialization_properties, OpenQASMSerializationProperties + ): + raise ValueError( + "serialization_properties must be of type OpenQASMSerializationProperties " + "for IRType.OPENQASM." + ) + return self._to_openqasm( + serialization_properties or OpenQASMSerializationProperties(), target + ) + else: + raise ValueError(f"Supplied ir_type {ir_type} is not supported.") + + def _to_jaqcd(self) -> List[Union[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 + ) -> 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): 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 basis_rotation_gates(self) -> Tuple[Gate, ...]: diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 62b523f0..631c1a62 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -27,6 +27,8 @@ is_hermitian, verify_quantum_operator_matrix_dimensions, ) +from braket.circuits.qubit_set import QubitSet +from braket.circuits.serialization import IRType, OpenQASMSerializationProperties class H(StandardObservable): @@ -39,9 +41,18 @@ def __init__(self): """ super().__init__(ascii_symbols=["H"]) - def to_ir(self) -> List[str]: + def _to_jaqcd(self) -> List[str]: return ["h"] + def _to_openqasm( + self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None + ) -> str: + if target: + qubit_target = serialization_properties.format_target(int(target[0])) + return f"h({qubit_target})" + else: + return "h all" + def to_matrix(self) -> np.ndarray: return 1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) @@ -63,9 +74,18 @@ def __init__(self): """ super().__init__(qubit_count=1, ascii_symbols=["I"]) - def to_ir(self) -> List[str]: + def _to_jaqcd(self) -> List[str]: return ["i"] + def _to_openqasm( + self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None + ) -> str: + if target: + qubit_target = serialization_properties.format_target(int(target[0])) + return f"i({qubit_target})" + else: + return "i all" + def to_matrix(self) -> np.ndarray: return np.eye(2, dtype=complex) @@ -94,9 +114,18 @@ def __init__(self): """ super().__init__(ascii_symbols=["X"]) - def to_ir(self) -> List[str]: + def _to_jaqcd(self) -> List[str]: return ["x"] + def _to_openqasm( + self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None + ) -> str: + if target: + qubit_target = serialization_properties.format_target(int(target[0])) + return f"x({qubit_target})" + else: + return "x all" + def to_matrix(self) -> np.ndarray: return np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) @@ -118,9 +147,18 @@ def __init__(self): """ super().__init__(ascii_symbols=["Y"]) - def to_ir(self) -> List[str]: + def _to_jaqcd(self) -> List[str]: return ["y"] + def _to_openqasm( + self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None + ) -> str: + if target: + qubit_target = serialization_properties.format_target(int(target[0])) + return f"y({qubit_target})" + else: + return "y all" + def to_matrix(self) -> np.ndarray: return np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) @@ -142,9 +180,18 @@ def __init__(self): """ super().__init__(ascii_symbols=["Z"]) - def to_ir(self) -> List[str]: + def _to_jaqcd(self) -> List[str]: return ["z"] + def _to_openqasm( + self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None + ) -> str: + if target: + qubit_target = serialization_properties.format_target(int(target[0])) + return f"z({qubit_target})" + else: + return "z all" + def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) @@ -198,12 +245,31 @@ def __init__(self, observables: List[Observable]): self._eigenvalue_indices = {} self._all_eigenvalues = None - def to_ir(self) -> List[str]: + def _to_jaqcd(self) -> List[str]: ir = [] for obs in self.factors: ir.extend(obs.to_ir()) return ir + def _to_openqasm( + self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None + ) -> str: + factors = [] + use_qubits = iter(target) + for obs in self._factors: + obs_target = QubitSet() + num_qubits = int(np.log2(obs.to_matrix().shape[0])) + for _ in range(num_qubits): + obs_target.add(next(use_qubits)) + factors.append( + obs.to_ir( + target=obs_target, + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + ) + ) + return " @ ".join(factors) + @property def factors(self) -> Tuple[Observable, ...]: """Tuple[Observable]: The observables that comprise this tensor product.""" @@ -311,11 +377,28 @@ def __init__(self, matrix: np.ndarray, display_name: str = "Hermitian"): super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) - def to_ir(self) -> List[List[List[List[float]]]]: + def _to_jaqcd(self) -> List[List[List[List[float]]]]: return [ [[[element.real, element.imag] for element in row] for row in self._matrix.tolist()] ] + def _to_openqasm( + self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None + ) -> str: + if target: + qubit_target = ", ".join( + [serialization_properties.format_target(int(t)) for t in target] + ) + return f"hermitian({self._serialized_matrix_openqasm_matrix()}) {qubit_target}" + else: + return f"hermitian({self._serialized_matrix_openqasm_matrix()}) all" + + def _serialized_matrix_openqasm_matrix(self): + serialized = str([[f"{complex(elem)}" for elem in row] for row in self._matrix.tolist()]) + for replacements in [("(", ""), (")", ""), ("'", ""), ("j", "im")]: + serialized = serialized.replace(replacements[0], replacements[1]) + return serialized + def to_matrix(self) -> np.ndarray: return self._matrix diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index 4088dcdc..26bddbb2 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -18,6 +18,11 @@ from braket.circuits.observable import Observable from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + SerializationProperties, +) class ResultType: @@ -57,17 +62,59 @@ def name(self) -> str: """ return self.__class__.__name__ - def to_ir(self, *args, **kwargs) -> Any: + def to_ir( + self, + ir_type: IRType = IRType.JAQCD, + serialization_properties: SerializationProperties = None, + **kwargs, + ) -> Any: """Returns IR object of the result type Args: - *args: Positional arguments + ir_type(IRType) : The IRType to use for converting the result type object to its + IR representation. Defaults to IRType.JAQCD. + serialization_properties (SerializationProperties): 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. **kwargs: Keyword arguments Returns: IR object of the result type + + Raises: + ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization + properties don't correspond to the `ir_type`. + """ + if ir_type == IRType.JAQCD: + return self._to_jaqcd() + elif ir_type == IRType.OPENQASM: + if serialization_properties and not isinstance( + serialization_properties, OpenQASMSerializationProperties + ): + raise ValueError( + "serialization_properties must be of type OpenQASMSerializationProperties " + "for IRType.OPENQASM." + ) + return self._to_openqasm(serialization_properties or OpenQASMSerializationProperties()) + else: + raise ValueError(f"Supplied ir_type {ir_type} is not supported.") + + def _to_jaqcd(self) -> Any: + """Returns the JAQCD representation of the result type.""" + 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. + + Args: + serialization_properties (OpenQASMSerializationProperties): The serialization properties + to use while serializing the object to the IR representation. + + Returns: + str: Representing the openqasm representation of the result type. """ - raise NotImplementedError("to_ir has not been implemented yet.") + raise NotImplementedError("to_openqasm has not been implemented yet.") def copy( self, target_mapping: Dict[QubitInput, QubitInput] = None, target: QubitSetInput = None diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index e15a4d67..dd92503b 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -21,6 +21,7 @@ from braket.circuits.observable import Observable from braket.circuits.qubit_set import QubitSet, QubitSetInput from braket.circuits.result_type import ObservableResultType, ResultType +from braket.circuits.serialization import IRType, OpenQASMSerializationProperties """ To add a new result type: @@ -41,9 +42,12 @@ class StateVector(ResultType): def __init__(self): super().__init__(ascii_symbols=["StateVector"]) - def to_ir(self) -> ir.StateVector: + def _to_jaqcd(self) -> ir.StateVector: return ir.StateVector.construct() + def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: + return "#pragma braket result state_vector" + @staticmethod @circuit.subroutine(register=True) def state_vector() -> ResultType: @@ -102,13 +106,21 @@ def target(self) -> QubitSet: def target(self, target: QubitSetInput) -> None: self._target = QubitSet(target) - def to_ir(self) -> ir.DensityMatrix: + 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() + def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: + if not self.target: + return "#pragma braket result density_matrix" + targets = ", ".join( + serialization_properties.format_target(int(target)) for target in self.target + ) + return f"#pragma braket result density_matrix {targets}" + @staticmethod @circuit.subroutine(register=True) def density_matrix(target: QubitSetInput = None) -> ResultType: @@ -182,9 +194,13 @@ def __init__(self, state: List[str]): def state(self) -> List[str]: return self._state - def to_ir(self) -> ir.Amplitude: + def _to_jaqcd(self) -> ir.Amplitude: return ir.Amplitude.construct(states=self.state) + def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: + states = ", ".join(f'"{state}"' for state in self.state) + return f"#pragma braket result amplitude {states}" + @staticmethod @circuit.subroutine(register=True) def amplitude(state: List[str]) -> ResultType: @@ -252,13 +268,21 @@ def target(self) -> QubitSet: def target(self, target: QubitSetInput) -> None: self._target = QubitSet(target) - def to_ir(self) -> ir.Probability: + 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() + def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: + if not self.target: + return "#pragma braket result probability all" + targets = ", ".join( + serialization_properties.format_target(int(target)) for target in self.target + ) + return f"#pragma braket result probability {targets}" + @staticmethod @circuit.subroutine(register=True) def probability(target: QubitSetInput = None) -> ResultType: @@ -332,7 +356,7 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): target=target, ) - def to_ir(self) -> ir.Expectation: + def _to_jaqcd(self) -> ir.Expectation: if self.target: return ir.Expectation.construct( observable=self.observable.to_ir(), targets=[int(qubit) for qubit in self.target] @@ -340,6 +364,14 @@ def to_ir(self) -> ir.Expectation: else: return ir.Expectation.construct(observable=self.observable.to_ir()) + def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: + observable_ir = self.observable.to_ir( + target=self.target, + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + ) + return f"#pragma braket result expectation {observable_ir}" + @staticmethod @circuit.subroutine(register=True) def expectation(observable: Observable, target: QubitSetInput = None) -> ResultType: @@ -399,7 +431,7 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): target=target, ) - def to_ir(self) -> ir.Sample: + def _to_jaqcd(self) -> ir.Sample: if self.target: return ir.Sample.construct( observable=self.observable.to_ir(), targets=[int(qubit) for qubit in self.target] @@ -407,6 +439,14 @@ def to_ir(self) -> ir.Sample: else: return ir.Sample.construct(observable=self.observable.to_ir()) + def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: + observable_ir = self.observable.to_ir( + target=self.target, + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + ) + return f"#pragma braket result sample {observable_ir}" + @staticmethod @circuit.subroutine(register=True) def sample(observable: Observable, target: QubitSetInput = None) -> ResultType: @@ -467,7 +507,7 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): target=target, ) - def to_ir(self) -> ir.Variance: + def _to_jaqcd(self) -> ir.Variance: if self.target: return ir.Variance.construct( observable=self.observable.to_ir(), targets=[int(qubit) for qubit in self.target] @@ -475,6 +515,14 @@ def to_ir(self) -> ir.Variance: else: return ir.Variance.construct(observable=self.observable.to_ir()) + def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: + observable_ir = self.observable.to_ir( + target=self.target, + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + ) + return f"#pragma braket result variance {observable_ir}" + @staticmethod @circuit.subroutine(register=True) def variance(observable: Observable, target: QubitSetInput = None) -> ResultType: diff --git a/src/braket/circuits/serialization.py b/src/braket/circuits/serialization.py new file mode 100644 index 00000000..d50e4fed --- /dev/null +++ b/src/braket/circuits/serialization.py @@ -0,0 +1,56 @@ +# 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 dataclasses import dataclass +from enum import Enum + + +class IRType(str, Enum): + """Defines the available IRTypes for circuit serialization.""" + + OPENQASM = "OPENQASM" + JAQCD = "JAQCD" + + +class QubitReferenceType(str, Enum): + """ + Defines how qubits should be referenced in the generated OpenQASM string. + See https://qiskit.github.io/openqasm/language/types.html#quantum-types + for details. + """ + + VIRTUAL = "VIRTUAL" + PHYSICAL = "PHYSICAL" + + +@dataclass +class OpenQASMSerializationProperties: + """ + Properties for serializing a circuit to OpenQASM. + + qubit_reference_type (QubitReferenceType): determines whether to use + logical qubits or physical qubits (q[i] vs $i). + """ + + qubit_reference_type: QubitReferenceType = QubitReferenceType.VIRTUAL + + def format_target(self, target: int) -> str: + qubit_reference_format = ( + "q[{}]" if self.qubit_reference_type == QubitReferenceType.VIRTUAL else "${}" + ) + return qubit_reference_format.format(target) + + +# Type alias to refer to possible serialization properties. Can be expanded once +# new properties are added. +SerializationProperties = OpenQASMSerializationProperties diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index e44391bb..f84a0696 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -19,8 +19,10 @@ from braket.annealing.problem import Problem from braket.circuits import Circuit from braket.circuits.circuit_helpers import validate_circuit_and_shots +from braket.circuits.serialization import IRType from braket.device_schema import DeviceActionType, DeviceCapabilities from braket.devices.device import Device +from braket.ir.openqasm import Program from braket.simulator import BraketSimulator from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult from braket.tasks.local_quantum_task import LocalQuantumTask @@ -53,7 +55,7 @@ def __init__(self, backend: Union[str, BraketSimulator] = "default"): def run( self, - task_specification: Union[Circuit, Problem], + task_specification: Union[Circuit, Problem, Program], shots: int = 0, *args, **kwargs, @@ -133,13 +135,18 @@ def _run_internal( @_run_internal.register def _(circuit: Circuit, simulator: BraketSimulator, shots, *args, **kwargs): - if DeviceActionType.JAQCD not in simulator.properties.action: - raise NotImplementedError(f"{type(simulator)} does not support qubit gate-based programs") - validate_circuit_and_shots(circuit, shots) - program = circuit.to_ir() - qubits = circuit.qubit_count - results = simulator.run(program, qubits, shots, *args, **kwargs) - return GateModelQuantumTaskResult.from_object(results) + if DeviceActionType.OPENQASM in simulator.properties.action: + validate_circuit_and_shots(circuit, shots) + program = circuit.to_ir(ir_type=IRType.OPENQASM) + results = simulator.run(program, shots, *args, **kwargs) + return GateModelQuantumTaskResult.from_object(results) + 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) + raise NotImplementedError(f"{type(simulator)} does not support qubit gate-based programs") @_run_internal.register @@ -149,3 +156,11 @@ def _(problem: Problem, simulator: BraketSimulator, shots, *args, **kwargs): ir = problem.to_ir() results = simulator.run(ir, shots, *args, *kwargs) return AnnealingQuantumTaskResult.from_object(results) + + +@_run_internal.register +def _(program: Program, simulator: BraketSimulator, shots, *args, **kwargs): + if DeviceActionType.OPENQASM not in simulator.properties.action: + raise NotImplementedError(f"{type(simulator)} does not support OpenQASM programs") + results = simulator.run(program, shots, *args, **kwargs) + return GateModelQuantumTaskResult.from_object(results) diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 757c718e..6c59ed58 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -14,8 +14,9 @@ from __future__ import annotations import json +from collections import Counter from dataclasses import dataclass -from typing import Any, Callable, Counter, Dict, List, Optional, TypeVar, Union +from typing import Any, Callable, Dict, List, Optional, TypeVar, Union import numpy as np @@ -278,13 +279,25 @@ def _from_object_internal_computational_basis_sampling(cls, result: GateModelTas f"Measured qubits {measured_qubits} is not equivalent to number of qubits " + f"{measurements.shape[1]} in measurements" ) - result_types = ( - result.resultTypes - if result.resultTypes - else GateModelQuantumTaskResult._calculate_result_types( + 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 + # specifies result types inline and parsing result types is more involved + # (ie. may involve dereferencing logical qubits at runtime), so the parsed + # result type specifications need to be returned, even if not calculated + # during simulation. + if not isinstance(result.resultTypes[0], ResultTypeValue): + result_types = GateModelQuantumTaskResult._calculate_result_types( + json.dumps({"results": [rt.dict() for rt in result.resultTypes]}), + measurements, + measured_qubits, + ) + else: + result_types = result.resultTypes + else: + result_types = GateModelQuantumTaskResult._calculate_result_types( additional_metadata.action.json(), measurements, measured_qubits ) - ) values = [rt.value for rt in result_types] return cls( task_metadata=task_metadata, diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index fbbecac0..6ccd6f05 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -20,6 +20,7 @@ from braket.aws import AwsDevice from braket.circuits import Circuit, Gate, Instruction, Observable, ResultType from braket.circuits.quantum_operator_helpers import get_pauli_eigenvalues +from braket.circuits.serialization import IRType from braket.devices import Device from braket.ir.openqasm import Program as OpenQasmProgram from braket.tasks import GateModelQuantumTaskResult @@ -34,11 +35,17 @@ def qubit_ordering_testing(device: Device, run_kwargs: Dict[str, Any]): state_110 = Circuit().x(0).x(1).i(2) result = device.run(state_110, **run_kwargs).result() assert result.measurement_counts.most_common(1)[0][0] == "110" + state_110_qasm = state_110.to_ir(ir_type=IRType.OPENQASM) + result = device.run(state_110_qasm, **run_kwargs).result() + assert result.measurement_counts.most_common(1)[0][0] == "110" # |001> should get back value of "001" state_001 = Circuit().i(0).i(1).x(2) result = device.run(state_001, **run_kwargs).result() assert result.measurement_counts.most_common(1)[0][0] == "001" + state_001_qasm = state_001.to_ir(ir_type=IRType.OPENQASM) + result = device.run(state_001_qasm, **run_kwargs).result() + assert result.measurement_counts.most_common(1)[0][0] == "001" def no_result_types_testing( @@ -57,7 +64,10 @@ def no_result_types_testing( def no_result_types_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): - no_result_types_testing(Circuit().h(0).cnot(0, 1), device, run_kwargs, {"00": 0.5, "11": 0.5}) + 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]): @@ -70,9 +80,11 @@ def result_types_observable_not_in_instructions(device: Device, run_kwargs: Dict .expectation(observable=Observable.X(), target=[2]) .variance(observable=Observable.Y(), target=[3]) ) - result = device.run(bell, **run_kwargs).result() - assert np.allclose(result.values[0], 0, **tol) - assert np.allclose(result.values[1], 1, **tol) + bell_qasm = bell.to_ir(ir_type=IRType.OPENQASM) + for task in (bell, bell_qasm): + result = device.run(task, **run_kwargs).result() + assert np.allclose(result.values[0], 0, **tol) + assert np.allclose(result.values[1], 1, **tol) def result_types_zero_shots_bell_pair_testing( @@ -91,50 +103,59 @@ def result_types_zero_shots_bell_pair_testing( circuit.amplitude(["01", "10", "00", "11"]) if include_state_vector: circuit.state_vector() - result = device.run(circuit, **run_kwargs).result() - assert len(result.result_types) == 3 if include_state_vector else 2 - assert np.allclose( - result.get_value_by_result_type( - ResultType.Expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) - ), - 1 / np.sqrt(2), - ) - if include_state_vector: + tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) + for task in tasks: + result = device.run(task, **run_kwargs).result() + assert len(result.result_types) == 3 if include_state_vector else 2 assert np.allclose( - result.get_value_by_result_type(ResultType.StateVector()), - np.array([1, 0, 0, 1]) / np.sqrt(2), + result.get_value_by_result_type( + ResultType.Expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ), + 1 / np.sqrt(2), ) - if include_amplitude: - assert result.get_value_by_result_type(ResultType.Amplitude(["01", "10", "00", "11"])) == { - "01": 0j, - "10": 0j, - "00": (1 / np.sqrt(2)), - "11": (1 / np.sqrt(2)), - } + if include_state_vector: + assert np.allclose( + result.get_value_by_result_type(ResultType.StateVector()), + np.array([1, 0, 0, 1]) / np.sqrt(2), + ) + if include_amplitude: + amplitude = result.get_value_by_result_type( + ResultType.Amplitude(["01", "10", "00", "11"]) + ) + assert np.isclose(amplitude["01"], 0) + assert np.isclose(amplitude["10"], 0) + assert np.isclose(amplitude["00"], 1 / np.sqrt(2)) + assert np.isclose(amplitude["11"], 1 / np.sqrt(2)) 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() - result = device.run(circuit, **run_kwargs).result() - assert len(result.result_types) == 1 - assert np.allclose( - result.get_value_by_result_type(ResultType.Probability()), np.array([0.5, 0, 0, 0.5]), **tol - ) + tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) + for task in tasks: + result = device.run(task, **run_kwargs).result() + assert len(result.result_types) == 1 + assert np.allclose( + result.get_value_by_result_type(ResultType.Probability()), + np.array([0.5, 0, 0, 0.5]), + **tol + ) 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) - result = device.run(circuit, **run_kwargs).result() - assert len(result.result_types) == 1 - assert np.allclose( - result.get_value_by_result_type(ResultType.Probability(target=0)), - np.array([0.5, 0.5]), - **tol - ) + tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) + for task in tasks: + result = device.run(task, **run_kwargs).result() + assert len(result.result_types) == 1 + assert np.allclose( + result.get_value_by_result_type(ResultType.Probability(target=0)), + np.array([0.5, 0.5]), + **tol + ) def result_types_nonzero_shots_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): @@ -145,26 +166,30 @@ def result_types_nonzero_shots_bell_pair_testing(device: Device, run_kwargs: Dic .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) .sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) ) - result = device.run(circuit, **run_kwargs).result() - assert len(result.result_types) == 2 - assert ( - 0.6 - < result.get_value_by_result_type( - ResultType.Expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) + for task in tasks: + result = device.run(task, **run_kwargs).result() + assert len(result.result_types) == 2 + assert ( + 0.6 + < result.get_value_by_result_type( + ResultType.Expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ) + < 0.8 ) - < 0.8 - ) - assert ( - len( - result.get_value_by_result_type( - ResultType.Sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) + assert ( + len( + result.get_value_by_result_type( + ResultType.Sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ) ) + == run_kwargs["shots"] ) - == run_kwargs["shots"] - ) -def result_types_hermitian_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_hermitian_testing( + device: Device, run_kwargs: Dict[str, Any], test_program: bool = True +): shots = run_kwargs["shots"] theta = 0.543 array = np.array([[1, 2j], [-2j, 0]]) @@ -177,17 +202,21 @@ def result_types_hermitian_testing(device: Device, run_kwargs: Dict[str, Any]): ) if shots: circuit.add_result_type(ResultType.Sample(Observable.Hermitian(array), 0)) - result = device.run(circuit, **run_kwargs).result() - - expected_mean = 2 * np.sin(theta) + 0.5 * np.cos(theta) + 0.5 - expected_var = 0.25 * (np.sin(theta) - 4 * np.cos(theta)) ** 2 - expected_eigs = np.linalg.eigvalsh(array) - assert_variance_expectation_sample_result( - result, shots, expected_var, expected_mean, expected_eigs - ) + tasks = (circuit,) if not test_program else (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) + for task in tasks: + result = device.run(task, **run_kwargs).result() + + expected_mean = 2 * np.sin(theta) + 0.5 * np.cos(theta) + 0.5 + expected_var = 0.25 * (np.sin(theta) - 4 * np.cos(theta)) ** 2 + expected_eigs = np.linalg.eigvalsh(array) + assert_variance_expectation_sample_result( + result, shots, expected_var, expected_mean, expected_eigs + ) -def result_types_all_selected_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_all_selected_testing( + device: Device, run_kwargs: Dict[str, Any], test_program: bool = True +): shots = run_kwargs["shots"] theta = 0.543 array = np.array([[1, 2j], [-2j, 0]]) @@ -201,15 +230,19 @@ def result_types_all_selected_testing(device: Device, run_kwargs: Dict[str, Any] ) if shots: circuit.add_result_type(ResultType.Sample(Observable.Hermitian(array), 1)) - result = device.run(circuit, **run_kwargs).result() - - expected_mean = 2 * np.sin(theta) + 0.5 * np.cos(theta) + 0.5 - var = 0.25 * (np.sin(theta) - 4 * np.cos(theta)) ** 2 - expected_var = [var, var] - expected_eigs = np.linalg.eigvalsh(array) - assert_variance_expectation_sample_result( - result, shots, expected_var, expected_mean, expected_eigs - ) + + tasks = (circuit,) if not test_program else (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) + + for task in tasks: + result = device.run(task, **run_kwargs).result() + + expected_mean = 2 * np.sin(theta) + 0.5 * np.cos(theta) + 0.5 + var = 0.25 * (np.sin(theta) - 4 * np.cos(theta)) ** 2 + expected_var = [var, var] + expected_eigs = np.linalg.eigvalsh(array) + assert_variance_expectation_sample_result( + result, shots, expected_var, expected_mean, expected_eigs + ) def get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, shots) -> Circuit: @@ -255,22 +288,24 @@ def result_types_tensor_x_y_testing(device: Device, run_kwargs: Dict[str, Any]): obs = Observable.X() @ Observable.Y() obs_targets = [0, 2] circuit = get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, shots) - result = device.run(circuit, **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 - ) + 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 + ) def result_types_tensor_z_z_testing(device: Device, run_kwargs: Dict[str, Any]): @@ -281,15 +316,17 @@ def result_types_tensor_z_z_testing(device: Device, run_kwargs: Dict[str, Any]): obs = Observable.Z() @ Observable.Z() obs_targets = [0, 2] circuit = get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, shots) - result = device.run(circuit, **run_kwargs).result() + 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) + 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 - ) + assert_variance_expectation_sample_result( + result, shots, expected_var, expected_mean, expected_eigs + ) def result_types_tensor_hermitian_hermitian_testing(device: Device, run_kwargs: Dict[str, Any]): @@ -309,15 +346,17 @@ def result_types_tensor_hermitian_hermitian_testing(device: Device, run_kwargs: 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) - result = device.run(circuit, **run_kwargs).result() + 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]) + 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 - ) + assert_variance_expectation_sample_result( + result, shots, expected_var, expected_mean, expected_eigs + ) def result_types_tensor_z_h_y_testing(device: Device, run_kwargs: Dict[str, Any]): @@ -328,20 +367,23 @@ def result_types_tensor_z_h_y_testing(device: Device, run_kwargs: Dict[str, Any] obs = Observable.Z() @ Observable.H() @ Observable.Y() obs_targets = [0, 1, 2] 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() - result = device.run(circuit, **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 - ) + 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 + ) def result_types_tensor_z_hermitian_testing(device: Device, run_kwargs: Dict[str, Any]): @@ -360,51 +402,52 @@ def result_types_tensor_z_hermitian_testing(device: Device, run_kwargs: Dict[str 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) - - result = device.run(circuit, **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) - ) + 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) * ( - 15 - + 8 * np.cos(varphi) - - 11 * np.cos(2 * varphi) - + 42 * np.sin(varphi) - + 3 * np.sin(2 * varphi) + 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 + ) / 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 - ) + 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 + ) def result_types_tensor_y_hermitian_testing(device: Device, run_kwargs: Dict[str, Any]): @@ -423,16 +466,17 @@ def result_types_tensor_y_hermitian_testing(device: Device, run_kwargs: Dict[str 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) - - result = device.run(circuit, **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 - ) + 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 + ) def result_types_noncommuting_testing(device: Device, run_kwargs: Dict[str, Any]): @@ -459,24 +503,26 @@ def result_types_noncommuting_testing(device: Device, run_kwargs: Dict[str, Any] .expectation(obs2, obs2_targets) .expectation(obs3, obs3_targets) ) - result = device.run(circuit, **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) - assert np.allclose(result.values[3], expected_mean3) + tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) + 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) + assert np.allclose(result.values[3], expected_mean3) def result_types_noncommuting_flipped_targets_testing(device: Device, run_kwargs: Dict[str, Any]): @@ -487,9 +533,11 @@ def result_types_noncommuting_flipped_targets_testing(device: Device, run_kwargs .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) .expectation(observable=Observable.H() @ Observable.X(), target=[1, 0]) ) - result = device.run(circuit, shots=0, **run_kwargs).result() - assert np.allclose(result.values[0], np.sqrt(2) / 2) - assert np.allclose(result.values[1], np.sqrt(2) / 2) + tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) + for task in tasks: + result = device.run(task, shots=0, **run_kwargs).result() + assert np.allclose(result.values[0], np.sqrt(2) / 2) + assert np.allclose(result.values[1], np.sqrt(2) / 2) def result_types_noncommuting_all(device: Device, run_kwargs: Dict[str, Any]): @@ -501,9 +549,11 @@ def result_types_noncommuting_all(device: Device, run_kwargs: Dict[str, Any]): .expectation(observable=Observable.Hermitian(array)) .expectation(observable=Observable.X()) ) - result = device.run(circuit, shots=0, **run_kwargs).result() - assert np.allclose(result.values[0], [0.5, 0.5]) - assert np.allclose(result.values[1], [0, 0]) + tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) + for task in tasks: + result = device.run(task, shots=0, **run_kwargs).result() + assert np.allclose(result.values[0], [0.5, 0.5]) + assert np.allclose(result.values[1], [0, 0]) def multithreaded_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): @@ -518,28 +568,32 @@ def run_circuit(circuit): futures = [] num_threads = 2 - with concurrent.futures.ThreadPoolExecutor() as executor: - for _ in range(num_threads): - future = executor.submit(run_circuit, bell) - futures.append(future) - for future in futures: - result = future.result() - assert np.allclose(result.measurement_probabilities["00"], 0.5, **tol) - assert np.allclose(result.measurement_probabilities["11"], 0.5, **tol) - assert len(result.measurements) == shots + tasks = (bell, bell.to_ir(ir_type=IRType.OPENQASM)) + for task in tasks: + with concurrent.futures.ThreadPoolExecutor() as executor: + for _ in range(num_threads): + future = executor.submit(run_circuit, task) + futures.append(future) + for future in futures: + result = future.result() + assert np.allclose(result.measurement_probabilities["00"], 0.5, **tol) + assert np.allclose(result.measurement_probabilities["11"], 0.5, **tol) + assert len(result.measurements) == shots 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() - result = device.run(circuit, **run_kwargs).result() - assert len(result.result_types) == 1 - assert np.allclose( - result.get_value_by_result_type(ResultType.Probability()), - np.array([0.0, 0.1, 0, 0.9]), - **tol - ) + tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) + for task in tasks: + result = device.run(task, **run_kwargs).result() + assert len(result.result_types) == 1 + assert np.allclose( + result.get_value_by_result_type(ResultType.Probability()), + np.array([0.0, 0.1, 0, 0.9]), + **tol + ) def noisy_circuit_2qubit_noise_full_probability(device: Device, run_kwargs: Dict[str, Any]): @@ -550,27 +604,30 @@ def noisy_circuit_2qubit_noise_full_probability(device: Device, run_kwargs: Dict 0.1 ) circuit = Circuit().x(0).x(1).kraus((0, 1), [K0, K1]).probability() - result = device.run(circuit, **run_kwargs).result() - assert len(result.result_types) == 1 - assert np.allclose( - result.get_value_by_result_type(ResultType.Probability()), - np.array([0.1, 0.0, 0, 0.9]), - **tol - ) + tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) + for task in tasks: + result = device.run(task, **run_kwargs).result() + assert len(result.result_types) == 1 + assert np.allclose( + result.get_value_by_result_type(ResultType.Probability()), + np.array([0.1, 0.0, 0, 0.9]), + **tol + ) 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)] - - batch = device.run_batch(circuits, max_parallel=5, **run_kwargs) - results = batch.results() - for result in results: - assert np.allclose(result.measurement_probabilities["00"], 0.5, **tol) - assert np.allclose(result.measurement_probabilities["11"], 0.5, **tol) - assert len(result.measurements) == shots - assert [task.result() for task in batch.tasks] == results + tasks_list = (circuits, [circuit.to_ir(ir_type=IRType.OPENQASM) for circuit in circuits]) + for tasks in tasks_list: + batch = device.run_batch(tasks, max_parallel=5, **run_kwargs) + results = batch.results() + for result in results: + assert np.allclose(result.measurement_probabilities["00"], 0.5, **tol) + assert np.allclose(result.measurement_probabilities["11"], 0.5, **tol) + assert len(result.measurements) == shots + assert [task.result() for task in batch.tasks] == results def bell_pair_openqasm_testing(device: AwsDevice, run_kwargs: Dict[str, Any]): @@ -583,9 +640,12 @@ def bell_pair_openqasm_testing(device: AwsDevice, run_kwargs: Dict[str, Any]): "c[0] = measure q[0];" "c[1] = measure q[1];" ) - no_result_types_testing( - OpenQasmProgram(source=openqasm_string), device, run_kwargs, {"00": 0.5, "11": 0.5} - ) + hardcoded_openqasm = OpenQasmProgram(source=openqasm_string) + circuit = Circuit().h(0).cnot(0, 1) + generated_openqasm = circuit.to_ir(ir_type=IRType.OPENQASM) + + for program in hardcoded_openqasm, generated_openqasm: + no_result_types_testing(program, device, run_kwargs, {"00": 0.5, "11": 0.5}) def openqasm_noisy_circuit_1qubit_noise_full_probability( @@ -601,13 +661,18 @@ def openqasm_noisy_circuit_1qubit_noise_full_probability( "#pragma braket noise bit_flip(0.1) q[0]" "#pragma braket result probability q[0], q[1]" ) - result = device.run(OpenQasmProgram(source=openqasm_string), **run_kwargs).result() - assert len(result.result_types) == 1 - assert np.allclose( - result.get_value_by_result_type(ResultType.Probability(target=[0, 1])), - np.array([0.0, 0.1, 0, 0.9]), - **tol - ) + hardcoded_openqasm = OpenQasmProgram(source=openqasm_string) + circuit = Circuit().x(0).x(1).bit_flip(0, 0.1).probability([0, 1]) + generated_openqasm = circuit.to_ir(ir_type=IRType.OPENQASM) + + for program in hardcoded_openqasm, generated_openqasm: + result = device.run(program, **run_kwargs).result() + assert len(result.result_types) == 1 + assert np.allclose( + result.get_value_by_result_type(ResultType.Probability(target=[0, 1])), + np.array([0.0, 0.1, 0, 0.9]), + **tol + ) def openqasm_result_types_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): @@ -619,23 +684,34 @@ def openqasm_result_types_bell_pair_testing(device: Device, run_kwargs: Dict[str "#pragma braket result expectation h(q[0]) @ x(q[1])" "#pragma braket result sample h(q[0]) @ x(q[1])" ) - result = device.run(OpenQasmProgram(source=openqasm_string), **run_kwargs).result() - assert len(result.result_types) == 2 - assert ( - 0.6 - < result.get_value_by_result_type( - ResultType.Expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + hardcoded_openqasm = OpenQasmProgram(source=openqasm_string) + circuit = ( + Circuit() + .h(0) + .cnot(0, 1) + .expectation(Observable.H() @ Observable.X(), (0, 1)) + .sample(Observable.H() @ Observable.X(), (0, 1)) + ) + generated_openqasm = circuit.to_ir(ir_type=IRType.OPENQASM) + + for program in hardcoded_openqasm, generated_openqasm: + result = device.run(program, **run_kwargs).result() + assert len(result.result_types) == 2 + assert ( + 0.6 + < result.get_value_by_result_type( + ResultType.Expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ) + < 0.8 ) - < 0.8 - ) - assert ( - len( - result.get_value_by_result_type( - ResultType.Sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) + assert ( + len( + result.get_value_by_result_type( + ResultType.Sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) + ) ) + == run_kwargs["shots"] ) - == run_kwargs["shots"] - ) def many_layers(n_qubits: int, n_layers: int) -> Circuit: diff --git a/test/integ_tests/test_local_braket_simulator.py b/test/integ_tests/test_local_braket_simulator.py index 9b29e890..4b3b93cd 100644 --- a/test/integ_tests/test_local_braket_simulator.py +++ b/test/integ_tests/test_local_braket_simulator.py @@ -40,91 +40,110 @@ SHOTS = 8000 -def test_multithreaded_bell_pair(): +def test_multithreaded_bell_pair(caplog): multithreaded_bell_pair_testing(DEVICE, {"shots": SHOTS}) + assert not caplog.text -def test_no_result_types_bell_pair(): +def test_no_result_types_bell_pair(caplog): no_result_types_bell_pair_testing(DEVICE, {"shots": SHOTS}) + assert not caplog.text -def test_qubit_ordering(): +def test_qubit_ordering(caplog): qubit_ordering_testing(DEVICE, {"shots": SHOTS}) + assert not caplog.text -def test_result_types_no_shots(): +def test_result_types_no_shots(caplog): result_types_zero_shots_bell_pair_testing(DEVICE, True, {"shots": 0}) + assert not caplog.text -def test_result_types_nonzero_shots_bell_pair(): +def test_result_types_nonzero_shots_bell_pair(caplog): result_types_nonzero_shots_bell_pair_testing(DEVICE, {"shots": SHOTS}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_bell_pair_full_probability(shots): +def test_result_types_bell_pair_full_probability(shots, caplog): result_types_bell_pair_full_probability_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_bell_pair_marginal_probability(shots): +def test_result_types_bell_pair_marginal_probability(shots, caplog): result_types_bell_pair_marginal_probability_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_hermitian(shots): +def test_result_types_hermitian(shots, caplog): result_types_hermitian_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_x_y(shots): +def test_result_types_tensor_x_y(shots, caplog): result_types_tensor_x_y_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_z_z(shots): +def test_result_types_tensor_z_z(shots, caplog): result_types_tensor_z_z_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_z_h_y(shots): +def test_result_types_tensor_z_h_y(shots, caplog): result_types_tensor_z_h_y_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_z_hermitian(shots): +def test_result_types_tensor_z_hermitian(shots, caplog): result_types_tensor_z_hermitian_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_hermitian_hermitian(shots): +def test_result_types_tensor_hermitian_hermitian(shots, caplog): result_types_tensor_hermitian_hermitian_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_y_hermitian(shots): +def test_result_types_tensor_y_hermitian(shots, caplog): result_types_tensor_y_hermitian_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_all_selected(shots): +def test_result_types_all_selected(shots, caplog): result_types_all_selected_testing(DEVICE, {"shots": shots}) + assert not caplog.text -def test_result_types_noncommuting(): +def test_result_types_noncommuting(caplog): result_types_noncommuting_testing(DEVICE, {}) + assert not caplog.text -def test_result_types_noncommuting_flipped_targets(): +def test_result_types_noncommuting_flipped_targets(caplog): result_types_noncommuting_flipped_targets_testing(DEVICE, {}) + assert not caplog.text -def test_result_types_noncommuting_all(): +def test_result_types_noncommuting_all(caplog): result_types_noncommuting_all(DEVICE, {}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_observable_not_in_instructions(shots): +def test_result_types_observable_not_in_instructions(shots, caplog): result_types_observable_not_in_instructions(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize( @@ -135,6 +154,7 @@ def test_result_types_observable_not_in_instructions(shots): ("braket_dm", "DensityMatrixSimulator"), ], ) -def test_local_simulator_device_names(backend, device_name): +def test_local_simulator_device_names(backend, device_name, caplog): local_simulator_device = LocalSimulator(backend) assert local_simulator_device.name == device_name + assert not caplog.text diff --git a/test/integ_tests/test_local_noise_simulator.py b/test/integ_tests/test_local_noise_simulator.py index 2a33cf8b..26753d2c 100644 --- a/test/integ_tests/test_local_noise_simulator.py +++ b/test/integ_tests/test_local_noise_simulator.py @@ -39,85 +39,103 @@ SHOTS = 8000 -def test_no_result_types_bell_pair(): +def test_no_result_types_bell_pair(caplog): no_result_types_bell_pair_testing(DEVICE, {"shots": SHOTS}) + assert not caplog.text -def test_qubit_ordering(): +def test_qubit_ordering(caplog): qubit_ordering_testing(DEVICE, {"shots": SHOTS}) + assert not caplog.text -def test_result_types_nonzero_shots_bell_pair(): +def test_result_types_nonzero_shots_bell_pair(caplog): result_types_nonzero_shots_bell_pair_testing(DEVICE, {"shots": SHOTS}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_bell_pair_full_probability(shots): +def test_result_types_bell_pair_full_probability(shots, caplog): result_types_bell_pair_full_probability_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_bell_pair_marginal_probability(shots): +def test_result_types_bell_pair_marginal_probability(shots, caplog): result_types_bell_pair_marginal_probability_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_hermitian(shots): +def test_result_types_hermitian(shots, caplog): result_types_hermitian_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_x_y(shots): +def test_result_types_tensor_x_y(shots, caplog): result_types_tensor_x_y_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_z_z(shots): +def test_result_types_tensor_z_z(shots, caplog): result_types_tensor_z_z_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_z_h_y(shots): +def test_result_types_tensor_z_h_y(shots, caplog): result_types_tensor_z_h_y_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_z_hermitian(shots): +def test_result_types_tensor_z_hermitian(shots, caplog): result_types_tensor_z_hermitian_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_hermitian_hermitian(shots): +def test_result_types_tensor_hermitian_hermitian(shots, caplog): result_types_tensor_hermitian_hermitian_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_y_hermitian(shots): +def test_result_types_tensor_y_hermitian(shots, caplog): result_types_tensor_y_hermitian_testing(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_all_selected(shots): +def test_result_types_all_selected(shots, caplog): result_types_all_selected_testing(DEVICE, {"shots": shots}) + assert not caplog.text -def test_result_types_noncommuting(): +def test_result_types_noncommuting(caplog): result_types_noncommuting_testing(DEVICE, {}) + assert not caplog.text -def test_result_types_noncommuting_flipped_targets(): +def test_result_types_noncommuting_flipped_targets(caplog): result_types_noncommuting_flipped_targets_testing(DEVICE, {}) + assert not caplog.text -def test_result_types_noncommuting_all(): +def test_result_types_noncommuting_all(caplog): result_types_noncommuting_all(DEVICE, {}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_noisy_circuit_1qubit_noise(shots): +def test_noisy_circuit_1qubit_noise(shots, caplog): noisy_circuit_1qubit_noise_full_probability(DEVICE, {"shots": shots}) + assert not caplog.text @pytest.mark.parametrize("shots", [0, SHOTS]) -def test_noisy_circuit_2qubit_noise(shots): +def test_noisy_circuit_2qubit_noise(shots, caplog): noisy_circuit_2qubit_noise_full_probability(DEVICE, {"shots": shots}) + assert not caplog.text 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 8b8e5b4b..5f9fd868 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -26,6 +26,11 @@ from braket.aws.aws_quantum_task import _create_annealing_device_params from braket.aws.aws_session import AwsSession from braket.circuits import Circuit +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + QubitReferenceType, +) from braket.device_schema import GateModelParameters from braket.device_schema.dwave import ( Dwave2000QDeviceParameters, @@ -435,7 +440,7 @@ def test_from_circuit_with_shots(device_arn, device_parameters_class, aws_sessio _assert_create_quantum_task_called_with( aws_session, device_arn, - circuit.to_ir().json(), + circuit.to_ir(ir_type=IRType.OPENQASM).json(), S3_TARGET, shots, device_parameters_class( @@ -464,7 +469,10 @@ def test_from_circuit_with_disabled_rewiring( _assert_create_quantum_task_called_with( aws_session, device_arn, - circuit.to_ir().json(), + circuit.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=OpenQASMSerializationProperties(QubitReferenceType.PHYSICAL), + ).json(), S3_TARGET, shots, device_parameters_class( @@ -497,10 +505,17 @@ def test_from_circuit_with_verbatim( ) assert task == AwsQuantumTask(mocked_task_arn, aws_session) + serialization_properties = OpenQASMSerializationProperties( + qubit_reference_type=QubitReferenceType.PHYSICAL + ) + _assert_create_quantum_task_called_with( aws_session, device_arn, - circ.to_ir().json(), + circ.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + ).json(), S3_TARGET, shots, device_parameters_class( @@ -795,7 +810,7 @@ def test_create_with_tags(device_arn, device_parameters_class, aws_session, circ _assert_create_quantum_task_called_with( aws_session, device_arn, - circuit.to_ir().json(), + circuit.to_ir(ir_type=IRType.OPENQASM).json(), S3_TARGET, shots, device_parameters_class( @@ -855,3 +870,20 @@ def _mock_metadata(aws_session, state): def _mock_s3(aws_session, result): aws_session.retrieve_s3_object_body.return_value = result + + +def test_no_program_inputs(aws_session): + openqasm_program = OpenQasmProgram( + source=""" + qubit q; + h q; + """, + inputs={"x": 1}, + ) + aws_session.create_quantum_task.return_value = arn + shots = 21 + only_for_local_sim = ( + "OpenQASM Program inputs are only currently supported in the LocalSimulator." + ) + with pytest.raises(ValueError, match=only_for_local_sim): + AwsQuantumTask.create(aws_session, SIMULATOR_ARN, openqasm_program, S3_TARGET, shots) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 5deb820a..b987550e 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -32,6 +32,12 @@ gates, noise, ) +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + QubitReferenceType, +) +from braket.ir.openqasm import Program as OpenQasmProgram @pytest.fixture @@ -625,6 +631,124 @@ 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).rx(1, 0.3), + OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), + OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "rx(0.15) q[0];", + "rx(0.3) q[1];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ] + ) + ), + ), + ( + Circuit().rx(0, 0.15).rx(4, 0.3), + OpenQASMSerializationProperties(QubitReferenceType.PHYSICAL), + OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[2] b;", + "rx(0.15) $0;", + "rx(0.3) $4;", + "b[0] = measure $0;", + "b[1] = measure $4;", + ] + ) + ), + ), + ( + Circuit() + .rx(0, 0.15) + .add_verbatim_box(Circuit().rx(4, 0.3)) + .expectation(observable=Observable.I()), + OpenQASMSerializationProperties(QubitReferenceType.PHYSICAL), + OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "rx(0.15) $0;", + "#pragma braket verbatim", + "box{", + "rx(0.3) $4;", + "}", + "#pragma braket result expectation i all", + ] + ) + ), + ), + ( + Circuit() + .rx(0, 0.15) + .rx(4, 0.3) + .bit_flip(3, probability=0.2) + .expectation(observable=Observable.I(), target=0), + None, + OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "qubit[5] q;", + "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])", + ] + ) + ), + ), + ], +) +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( + "ir_type, serialization_properties, expected_exception, expected_message", + [ + ( + "invalid-ir-type", + OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), + ValueError, + "Supplied ir_type invalid-ir-type is not supported.", + ), + ( + IRType.OPENQASM, + OpenQASMSerializationProperties("invalid-qubit-reference-type"), + ValueError, + "Invalid qubit_reference_type invalid-qubit-reference-type supplied.", + ), + ( + IRType.OPENQASM, + "invalid-serialization-properties", + ValueError, + "serialization_properties must be of type OpenQASMSerializationProperties " + "for IRType.OPENQASM.", + ), + ], +) +def test_circuit_to_ir_invalid_inputs( + ir_type, serialization_properties, expected_exception, expected_message +): + circuit = Circuit().h(0).cnot(0, 1) + with pytest.raises(expected_exception) as exc: + circuit.to_ir(ir_type, serialization_properties=serialization_properties) + assert exc.value.args[0] == expected_message + + def test_as_unitary_empty_instructions_returns_empty_array(): circ = Circuit() circ.as_unitary() == [] diff --git a/test/unit_tests/braket/circuits/test_compiler_directive.py b/test/unit_tests/braket/circuits/test_compiler_directive.py index bd1f2d95..a6a5c29e 100644 --- a/test/unit_tests/braket/circuits/test_compiler_directive.py +++ b/test/unit_tests/braket/circuits/test_compiler_directive.py @@ -15,6 +15,7 @@ from braket.circuits import Operator from braket.circuits.compiler_directive import CompilerDirective +from braket.circuits.serialization import IRType @pytest.fixture @@ -50,10 +51,21 @@ def test_counterpart_not_implemented_by_default(compiler_directive): compiler_directive.counterpart() -@pytest.mark.xfail(raises=NotImplementedError) -def test_to_ir_not_implemented_by_default(compiler_directive): - compiler_directive.to_ir(None) - - def test_str(compiler_directive): assert str(compiler_directive) == compiler_directive.name + + +@pytest.mark.parametrize( + "ir_type, serialization_properties, expected_exception, expected_message", + [ + (IRType.JAQCD, None, NotImplementedError, "to_jaqcd has not been implemented yet."), + (IRType.OPENQASM, None, NotImplementedError, "to_openqasm has not been implemented yet."), + ("invalid-ir-type", None, ValueError, "Supplied ir_type invalid-ir-type is not supported."), + ], +) +def test_compiler_directive_to_ir( + ir_type, serialization_properties, expected_exception, expected_message, compiler_directive +): + with pytest.raises(expected_exception) as exc: + compiler_directive.to_ir(0, ir_type, serialization_properties=serialization_properties) + assert exc.value.args[0] == expected_message diff --git a/test/unit_tests/braket/circuits/test_compiler_directives.py b/test/unit_tests/braket/circuits/test_compiler_directives.py index 80e485f8..db0b7061 100644 --- a/test/unit_tests/braket/circuits/test_compiler_directives.py +++ b/test/unit_tests/braket/circuits/test_compiler_directives.py @@ -16,25 +16,37 @@ import braket.ir.jaqcd as ir from braket.circuits import compiler_directives from braket.circuits.compiler_directive import CompilerDirective +from braket.circuits.serialization import IRType testdata = [ - (compiler_directives.StartVerbatimBox, ir.StartVerbatimBox, compiler_directives.EndVerbatimBox), - (compiler_directives.EndVerbatimBox, ir.EndVerbatimBox, compiler_directives.StartVerbatimBox), + ( + 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,counterpart", testdata) -def test_counterpart(testclass, irclass, counterpart): +@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,counterpart", testdata) -def test_to_ir(testclass, irclass, counterpart): - assert testclass().to_ir() == irclass() +@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,counterpart", testdata) -def test_equality(testclass, irclass, counterpart): +@pytest.mark.parametrize("testclass,irclass,openqasm_str,counterpart", testdata) +def test_equality(testclass, irclass, openqasm_str, counterpart): op1 = testclass() op2 = testclass() assert op1 == op2 diff --git a/test/unit_tests/braket/circuits/test_gate.py b/test/unit_tests/braket/circuits/test_gate.py index 8dd4297c..a9f1acbc 100644 --- a/test/unit_tests/braket/circuits/test_gate.py +++ b/test/unit_tests/braket/circuits/test_gate.py @@ -14,6 +14,7 @@ import pytest from braket.circuits import Gate, QuantumOperator +from braket.circuits.serialization import IRType @pytest.fixture @@ -30,11 +31,6 @@ def test_adjoint_not_implemented_by_default(gate): gate.adjoint() -@pytest.mark.xfail(raises=NotImplementedError) -def test_to_ir_not_implemented_by_default(gate): - gate.to_ir(None) - - @pytest.mark.xfail(raises=NotImplementedError) def test_to_matrix_not_implemented_by_default(gate): gate.to_matrix(None) @@ -83,3 +79,24 @@ def __init__(self): Gate.register_gate(_FooGate) assert Gate._FooGate().name == _FooGate().name + + +@pytest.mark.parametrize( + "ir_type, serialization_properties, expected_exception, expected_message", + [ + (IRType.JAQCD, None, NotImplementedError, "to_jaqcd has not been implemented yet."), + (IRType.OPENQASM, None, NotImplementedError, "to_openqasm has not been implemented yet."), + ("invalid-ir-type", None, ValueError, "Supplied ir_type invalid-ir-type is not supported."), + ( + IRType.OPENQASM, + "invalid-property-type", + ValueError, + "serialization_properties must be of type OpenQASMSerializationProperties for " + "IRType.OPENQASM.", + ), + ], +) +def test_gate_to_ir(ir_type, serialization_properties, expected_exception, expected_message, gate): + with pytest.raises(expected_exception) as exc: + gate.to_ir(0, ir_type, serialization_properties=serialization_properties) + assert exc.value.args[0] == expected_message diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index ed4bfa84..db476fbd 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -18,6 +18,11 @@ import braket.ir.jaqcd as ir from braket.circuits import Circuit, FreeParameter, Gate, Instruction, QubitSet +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + QubitReferenceType, +) from braket.ir.jaqcd.shared_models import ( Angle, DoubleControl, @@ -103,7 +108,6 @@ ), ] - parameterizable_gates = [ Gate.Rx, Gate.Ry, @@ -120,7 +124,6 @@ Gate.CPhaseShift10, ] - invalid_unitary_matrices = [ (np.array([[1]])), (np.array([1])), @@ -183,7 +186,6 @@ def two_dimensional_matrix_valid_input(**kwargs): "TwoDimensionalMatrix": two_dimensional_matrix_valid_ir_input, } - valid_subroutine_switcher = dict( valid_ir_switcher, **{ @@ -277,6 +279,486 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs assert actual == expected +@pytest.mark.parametrize( + "gate, target, serialization_properties, expected_ir", + [ + ( + Gate.Rx(angle=0.17), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "rx(0.17) q[4];", + ), + ( + Gate.Rx(angle=0.17), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "rx(0.17) $4;", + ), + ( + Gate.X(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "x q[4];", + ), + ( + Gate.X(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "x $4;", + ), + ( + Gate.Z(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "z q[4];", + ), + ( + Gate.Z(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "z $4;", + ), + ( + Gate.Y(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "y q[4];", + ), + ( + Gate.Y(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "y $4;", + ), + ( + Gate.H(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "h q[4];", + ), + ( + Gate.H(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "h $4;", + ), + ( + Gate.Ry(angle=0.17), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "ry(0.17) q[4];", + ), + ( + Gate.Ry(angle=0.17), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "ry(0.17) $4;", + ), + ( + Gate.ZZ(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "zz(0.17) q[4], q[5];", + ), + ( + Gate.ZZ(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "zz(0.17) $4, $5;", + ), + ( + Gate.I(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "i q[4];", + ), + ( + Gate.I(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "i $4;", + ), + ( + Gate.V(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "v q[4];", + ), + ( + Gate.V(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "v $4;", + ), + ( + Gate.CY(), + [0, 1], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "cy q[0], q[1];", + ), + ( + Gate.CY(), + [0, 1], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "cy $0, $1;", + ), + ( + Gate.Rz(angle=0.17), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "rz(0.17) q[4];", + ), + ( + Gate.Rz(angle=0.17), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "rz(0.17) $4;", + ), + ( + Gate.XX(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "xx(0.17) q[4], q[5];", + ), + ( + Gate.XX(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "xx(0.17) $4, $5;", + ), + ( + Gate.T(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "t q[4];", + ), + ( + Gate.T(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "t $4;", + ), + ( + Gate.CZ(), + [0, 1], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "cz $0, $1;", + ), + ( + Gate.CZ(), + [0, 1], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "cz q[0], q[1];", + ), + ( + Gate.YY(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "yy(0.17) q[4], q[5];", + ), + ( + Gate.YY(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "yy(0.17) $4, $5;", + ), + ( + Gate.XY(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "xy(0.17) q[4], q[5];", + ), + ( + Gate.XY(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "xy(0.17) $4, $5;", + ), + ( + Gate.ISwap(), + [0, 1], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "iswap $0, $1;", + ), + ( + Gate.ISwap(), + [0, 1], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "iswap q[0], q[1];", + ), + ( + Gate.Swap(), + [0, 1], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "swap $0, $1;", + ), + ( + Gate.Swap(), + [0, 1], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "swap q[0], q[1];", + ), + ( + Gate.ECR(), + [0, 1], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "ecr $0, $1;", + ), + ( + Gate.ECR(), + [0, 1], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "ecr q[0], q[1];", + ), + ( + Gate.CV(), + [0, 1], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "cv $0, $1;", + ), + ( + Gate.CV(), + [0, 1], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "cv q[0], q[1];", + ), + ( + Gate.Vi(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "vi q[4];", + ), + ( + Gate.Vi(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "vi $4;", + ), + ( + Gate.CSwap(), + [0, 1, 2], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "cswap q[0], q[1], q[2];", + ), + ( + Gate.CSwap(), + [0, 1, 2], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "cswap $0, $1, $2;", + ), + ( + Gate.CPhaseShift01(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "cphaseshift01(0.17) q[4], q[5];", + ), + ( + Gate.CPhaseShift01(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "cphaseshift01(0.17) $4, $5;", + ), + ( + Gate.CPhaseShift00(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "cphaseshift00(0.17) q[4], q[5];", + ), + ( + Gate.CPhaseShift00(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "cphaseshift00(0.17) $4, $5;", + ), + ( + Gate.CPhaseShift(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "cphaseshift(0.17) q[4], q[5];", + ), + ( + Gate.CPhaseShift(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "cphaseshift(0.17) $4, $5;", + ), + ( + Gate.S(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "s q[4];", + ), + ( + Gate.S(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "s $4;", + ), + ( + Gate.Si(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "si q[4];", + ), + ( + Gate.Si(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "si $4;", + ), + ( + Gate.Ti(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "ti q[4];", + ), + ( + Gate.Ti(), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "ti $4;", + ), + ( + Gate.PhaseShift(angle=0.17), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "phaseshift(0.17) q[4];", + ), + ( + Gate.PhaseShift(angle=0.17), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "phaseshift(0.17) $4;", + ), + ( + Gate.CNot(), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "cnot q[4], q[5];", + ), + ( + Gate.CNot(), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "cnot $4, $5;", + ), + ( + Gate.PSwap(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "pswap(0.17) q[4], q[5];", + ), + ( + Gate.PSwap(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "pswap(0.17) $4, $5;", + ), + ( + Gate.CPhaseShift10(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "cphaseshift10(0.17) q[4], q[5];", + ), + ( + Gate.CPhaseShift10(angle=0.17), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "cphaseshift10(0.17) $4, $5;", + ), + ( + Gate.CCNot(), + [4, 5, 6], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "ccnot q[4], q[5], q[6];", + ), + ( + Gate.CCNot(), + [4, 5, 6], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "ccnot $4, $5, $6;", + ), + ( + Gate.Unitary(Gate.CCNot().to_matrix()), + [4, 5, 6], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket unitary([" + "[1.0, 0, 0, 0, 0, 0, 0, 0], " + "[0, 1.0, 0, 0, 0, 0, 0, 0], " + "[0, 0, 1.0, 0, 0, 0, 0, 0], " + "[0, 0, 0, 1.0, 0, 0, 0, 0], " + "[0, 0, 0, 0, 1.0, 0, 0, 0], " + "[0, 0, 0, 0, 0, 1.0, 0, 0], " + "[0, 0, 0, 0, 0, 0, 0, 1.0], " + "[0, 0, 0, 0, 0, 0, 1.0, 0]" + "]) q[4], q[5], q[6]", + ), + ( + Gate.Unitary(Gate.CCNot().to_matrix()), + [4, 5, 6], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "#pragma braket unitary([" + "[1.0, 0, 0, 0, 0, 0, 0, 0], " + "[0, 1.0, 0, 0, 0, 0, 0, 0], " + "[0, 0, 1.0, 0, 0, 0, 0, 0], " + "[0, 0, 0, 1.0, 0, 0, 0, 0], " + "[0, 0, 0, 0, 1.0, 0, 0, 0], " + "[0, 0, 0, 0, 0, 1.0, 0, 0], " + "[0, 0, 0, 0, 0, 0, 0, 1.0], " + "[0, 0, 0, 0, 0, 0, 1.0, 0]" + "]) $4, $5, $6", + ), + ( + Gate.Unitary(np.round(Gate.ECR().to_matrix(), 8)), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket unitary([" + "[0, 0, 0.70710678, 0.70710678im], " + "[0, 0, 0.70710678im, 0.70710678], " + "[0.70710678, -0.70710678im, 0, 0], " + "[-0.70710678im, 0.70710678, 0, 0]" + "]) q[4], q[5]", + ), + ( + Gate.Unitary(np.round(Gate.ECR().to_matrix(), 8)), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "#pragma braket unitary([" + "[0, 0, 0.70710678, 0.70710678im], " + "[0, 0, 0.70710678im, 0.70710678], " + "[0.70710678, -0.70710678im, 0, 0], " + "[-0.70710678im, 0.70710678, 0, 0]" + "]) $4, $5", + ), + ( + Gate.Unitary(np.round(Gate.T().to_matrix(), 8)), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket unitary([[1.0, 0], [0, 0.70710678 + 0.70710678im]]) q[4]", + ), + ( + Gate.Unitary(np.round(Gate.T().to_matrix(), 8)), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "#pragma braket unitary([[1.0, 0], [0, 0.70710678 + 0.70710678im]]) $4", + ), + ( + Gate.Unitary(np.array([[1.0, 0], [0, 0.70710678 - 0.70710678j]])), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket unitary([[1.0, 0], [0, 0.70710678 - 0.70710678im]]) q[4]", + ), + ], +) +def test_gate_to_ir_openqasm(gate, target, serialization_properties, expected_ir): + assert ( + gate.to_ir( + target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties + ) + == expected_ir + ) + + @pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) def test_ir_instruction_level(testclass, subroutine_name, irclass, irsubclasses, kwargs): expected = irclass(**create_valid_ir_input(irsubclasses)) diff --git a/test/unit_tests/braket/circuits/test_instruction.py b/test/unit_tests/braket/circuits/test_instruction.py index b0e40345..3cb12a22 100644 --- a/test/unit_tests/braket/circuits/test_instruction.py +++ b/test/unit_tests/braket/circuits/test_instruction.py @@ -14,6 +14,11 @@ import pytest from braket.circuits import Gate, Instruction, Noise, Qubit, QubitSet, compiler_directives +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + QubitReferenceType, +) @pytest.fixture @@ -115,17 +120,23 @@ def test_equality(): def test_to_ir(): expected_target = QubitSet([0, 1]) expected_ir = "foo bar value" + expected_ir_type = IRType.OPENQASM + expected_serialization_properties = OpenQASMSerializationProperties( + qubit_reference_type=QubitReferenceType.PHYSICAL + ) class FooGate(Gate): def __init__(self): super().__init__(qubit_count=2, ascii_symbols=["foo", "bar"]) - def to_ir(self, target): + def to_ir(self, target, ir_type, serialization_properties): assert target == expected_target + assert ir_type == expected_ir_type + assert serialization_properties == expected_serialization_properties return expected_ir instr = Instruction(FooGate(), expected_target) - assert instr.to_ir() == expected_ir + assert instr.to_ir(expected_ir_type, expected_serialization_properties) == expected_ir def test_copy_creates_new_object(instr): diff --git a/test/unit_tests/braket/circuits/test_noise.py b/test/unit_tests/braket/circuits/test_noise.py index bf5eb5e6..587ec0f1 100644 --- a/test/unit_tests/braket/circuits/test_noise.py +++ b/test/unit_tests/braket/circuits/test_noise.py @@ -27,6 +27,7 @@ SingleProbabilisticNoise_34, SingleProbabilisticNoise_1516, ) +from braket.circuits.serialization import IRType invalid_data_qubit_count = [(0, ["foo"])] invalid_data_ascii_symbols = [(1, None)] @@ -266,6 +267,29 @@ def test_to_ir_not_implemented_by_default(base_noise): base_noise.to_ir(None) +@pytest.mark.parametrize( + "ir_type, serialization_properties, expected_exception, expected_message", + [ + (IRType.JAQCD, None, NotImplementedError, "to_jaqcd has not been implemented yet."), + (IRType.OPENQASM, None, NotImplementedError, "to_openqasm has not been implemented yet."), + ("invalid-ir-type", None, ValueError, "Supplied ir_type invalid-ir-type is not supported."), + ( + IRType.OPENQASM, + "invalid-serialization-properties", + ValueError, + "serialization_properties must be of type OpenQASMSerializationProperties for " + "IRType.OPENQASM.", + ), + ], +) +def test_noise_to_ir( + ir_type, serialization_properties, expected_exception, expected_message, base_noise +): + with pytest.raises(expected_exception) as exc: + base_noise.to_ir(0, ir_type, serialization_properties=serialization_properties) + assert exc.value.args[0] == expected_message + + @pytest.mark.xfail(raises=NotImplementedError) def test_to_matrix_not_implemented_by_default(base_noise): base_noise.to_matrix(None) diff --git a/test/unit_tests/braket/circuits/test_noises.py b/test/unit_tests/braket/circuits/test_noises.py index afc4a215..758aafdf 100644 --- a/test/unit_tests/braket/circuits/test_noises.py +++ b/test/unit_tests/braket/circuits/test_noises.py @@ -19,6 +19,11 @@ import braket.ir.jaqcd as ir from braket.circuits import Circuit, Instruction, Noise, QubitSet from braket.circuits.free_parameter import FreeParameter +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + QubitReferenceType, +) from braket.ir.jaqcd.shared_models import ( DampingProbability, DampingSingleProbability, @@ -532,3 +537,189 @@ def test_invalid_values_pauli_channel_two_qubit(probs): def test_valid_values_pauli_channel_two_qubit(probs): noise = Noise.TwoQubitPauliChannel(probs) assert len(noise.to_matrix()) == 16 + + +@pytest.mark.parametrize( + "noise, serialization_properties, target, expected_ir", + [ + ( + Noise.BitFlip(0.5), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3], + "#pragma braket noise bit_flip(0.5) q[3]", + ), + ( + Noise.BitFlip(0.5), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3], + "#pragma braket noise bit_flip(0.5) $3", + ), + ( + Noise.PhaseFlip(0.5), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3], + "#pragma braket noise phase_flip(0.5) q[3]", + ), + ( + Noise.PhaseFlip(0.5), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3], + "#pragma braket noise phase_flip(0.5) $3", + ), + ( + Noise.PauliChannel(0.1, 0.2, 0.3), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3], + "#pragma braket noise pauli_channel(0.1, 0.2, 0.3) q[3]", + ), + ( + Noise.PauliChannel(0.1, 0.2, 0.3), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3], + "#pragma braket noise pauli_channel(0.1, 0.2, 0.3) $3", + ), + ( + Noise.Depolarizing(0.5), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3], + "#pragma braket noise depolarizing(0.5) q[3]", + ), + ( + Noise.Depolarizing(0.5), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3], + "#pragma braket noise depolarizing(0.5) $3", + ), + ( + Noise.TwoQubitDepolarizing(0.5), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3, 5], + "#pragma braket noise two_qubit_depolarizing(0.5) q[3], q[5]", + ), + ( + Noise.TwoQubitDepolarizing(0.5), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3, 5], + "#pragma braket noise two_qubit_depolarizing(0.5) $3, $5", + ), + ( + Noise.TwoQubitDephasing(0.5), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3, 5], + "#pragma braket noise two_qubit_dephasing(0.5) q[3], q[5]", + ), + ( + Noise.TwoQubitDephasing(0.5), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3, 5], + "#pragma braket noise two_qubit_dephasing(0.5) $3, $5", + ), + ( + Noise.AmplitudeDamping(0.5), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3], + "#pragma braket noise amplitude_damping(0.5) q[3]", + ), + ( + Noise.AmplitudeDamping(0.5), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3], + "#pragma braket noise amplitude_damping(0.5) $3", + ), + ( + Noise.GeneralizedAmplitudeDamping(0.5, 0.1), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3], + "#pragma braket noise generalized_amplitude_damping(0.5, 0.1) q[3]", + ), + ( + Noise.GeneralizedAmplitudeDamping(0.5, 0.1), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3], + "#pragma braket noise generalized_amplitude_damping(0.5, 0.1) $3", + ), + ( + Noise.PhaseDamping(0.5), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3], + "#pragma braket noise phase_damping(0.5) q[3]", + ), + ( + Noise.PhaseDamping(0.5), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3], + "#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), + ] + ), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3, 5], + "#pragma braket noise kraus([" + "[0.9486832980505138, 0, 0, 0], " + "[0, 0.9486832980505138, 0, 0], " + "[0, 0, 0.9486832980505138, 0], " + "[0, 0, 0, 0.9486832980505138]], [" + "[0, 0.31622776601683794, 0, 0], " + "[0.31622776601683794, 0, 0, 0], " + "[0, 0, 0, 0.31622776601683794], " + "[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), + ] + ), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3, 5], + "#pragma braket noise kraus([" + "[0.9486832980505138, 0, 0, 0], " + "[0, 0.9486832980505138, 0, 0], " + "[0, 0, 0.9486832980505138, 0], " + "[0, 0, 0, 0.9486832980505138]], [" + "[0, 0.31622776601683794, 0, 0], " + "[0.31622776601683794, 0, 0, 0], " + "[0, 0, 0, 0.31622776601683794], " + "[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]]), + ] + ), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3], + "#pragma braket noise kraus([" + "[0.9486833im, 0], [0, 0.9486833im]], [" + "[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]]), + ] + ), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3], + "#pragma braket noise kraus([" + "[0.9486833im, 0], [0, 0.9486833im]], [" + "[0, 0.31622777], [0.31622777, 0]]) $3", + ), + ], +) +def test_noise_to_ir_openqasm(noise, serialization_properties, target, expected_ir): + assert ( + noise.to_ir( + target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties + ) + == expected_ir + ) diff --git a/test/unit_tests/braket/circuits/test_observable.py b/test/unit_tests/braket/circuits/test_observable.py index 2349e217..21e3cf00 100644 --- a/test/unit_tests/braket/circuits/test_observable.py +++ b/test/unit_tests/braket/circuits/test_observable.py @@ -15,6 +15,7 @@ import pytest from braket.circuits import Observable, QuantumOperator, StandardObservable +from braket.circuits.serialization import IRType @pytest.fixture @@ -75,9 +76,27 @@ def test_name_setter(observable): observable.name = "hi" -@pytest.mark.xfail(raises=NotImplementedError) -def test_to_ir_not_implemented_by_default(observable): - observable.to_ir() +@pytest.mark.parametrize( + "ir_type, serialization_properties, expected_exception, expected_message", + [ + (IRType.JAQCD, None, NotImplementedError, "to_jaqcd has not been implemented yet."), + (IRType.OPENQASM, None, NotImplementedError, "to_openqasm has not been implemented yet."), + ("invalid-ir-type", None, ValueError, "Supplied ir_type invalid-ir-type is not supported."), + ( + IRType.OPENQASM, + "invalid-property-type", + ValueError, + "serialization_properties must be of type OpenQASMSerializationProperties for " + "IRType.OPENQASM.", + ), + ], +) +def test_observable_to_ir( + ir_type, serialization_properties, expected_exception, expected_message, observable +): + with pytest.raises(expected_exception) as exc: + observable.to_ir(0, ir_type, serialization_properties=serialization_properties) + assert exc.value.args[0] == expected_message @pytest.mark.xfail(raises=NotImplementedError) diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index fbe72eb8..49e643e5 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -19,6 +19,11 @@ from braket.circuits import Gate, Observable from braket.circuits.observables import observable_from_ir from braket.circuits.quantum_operator_helpers import get_pauli_eigenvalues +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + QubitReferenceType, +) testdata = [ (Observable.I(), Gate.I(), ["i"], (), np.array([1, 1])), @@ -54,6 +59,170 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv assert actual == expected +@pytest.mark.parametrize( + "observable, serialization_properties, target, expected_ir", + [ + ( + Observable.I(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3], + "i(q[3])", + ), + ( + Observable.I(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3], + "i($3)", + ), + ( + Observable.I(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + None, + "i all", + ), + ( + Observable.X(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3], + "x(q[3])", + ), + ( + Observable.X(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3], + "x($3)", + ), + ( + Observable.X(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + None, + "x all", + ), + ( + Observable.Y(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3], + "y(q[3])", + ), + ( + Observable.Y(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3], + "y($3)", + ), + ( + Observable.Y(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + None, + "y all", + ), + ( + Observable.Z(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3], + "z(q[3])", + ), + ( + Observable.Z(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3], + "z($3)", + ), + ( + Observable.Z(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + None, + "z all", + ), + ( + Observable.H(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3], + "h(q[3])", + ), + ( + Observable.H(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3], + "h($3)", + ), + ( + Observable.H(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + None, + "h all", + ), + ( + Observable.Hermitian(np.eye(4)), + 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)), + 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)), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + None, + "hermitian([[1+0im, 0im], [0im, 1+0im]]) all", + ), + ( + Observable.H() @ Observable.Z(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3, 0], + "h(q[3]) @ z(q[0])", + ), + ( + Observable.H() @ Observable.Z(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3, 0], + "h($3) @ z($0)", + ), + ( + Observable.H() @ Observable.Z() @ Observable.I(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3, 0, 1], + "h(q[3]) @ z(q[0]) @ i(q[1])", + ), + ( + Observable.H() @ Observable.Z() @ Observable.I(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3, 0, 1], + "h($3) @ z($0) @ i($1)", + ), + ( + Observable.Hermitian(np.eye(4)) @ Observable.I(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + [3, 0, 1], + "hermitian([[1+0im, 0im, 0im, 0im], [0im, 1+0im, 0im, 0im], " + "[0im, 0im, 1+0im, 0im], [0im, 0im, 0im, 1+0im]]) q[3], q[0]" + " @ i(q[1])", + ), + ( + Observable.I() @ Observable.Hermitian(np.eye(4)), + 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", + ), + ], +) +def test_observables_to_ir_openqasm(observable, serialization_properties, target, expected_ir): + assert ( + observable.to_ir( + target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties + ) + == expected_ir + ) + + @pytest.mark.parametrize( "testobject,gateobject,expected_ir,basis_rotation_gates,eigenvalues", testdata ) diff --git a/test/unit_tests/braket/circuits/test_result_type.py b/test/unit_tests/braket/circuits/test_result_type.py index 0e1d8a0b..ced44b39 100644 --- a/test/unit_tests/braket/circuits/test_result_type.py +++ b/test/unit_tests/braket/circuits/test_result_type.py @@ -14,6 +14,7 @@ import pytest from braket.circuits import Observable, ObservableResultType, ResultType +from braket.circuits.serialization import IRType @pytest.fixture @@ -77,11 +78,6 @@ def test_name_setter(result_type): result_type.name = "hi" -@pytest.mark.xfail(raises=NotImplementedError) -def test_to_ir_not_implemented_by_default(result_type): - result_type.to_ir(None) - - def test_register_result(): class _FooResultType(ResultType): def __init__(self): @@ -167,3 +163,26 @@ def test_obs_rt_repr(): str(a1) == "ObservableResultType(observable=X('qubit_count': 1), target=QubitSet([Qubit(0)]))" ) + + +@pytest.mark.parametrize( + "ir_type, serialization_properties, expected_exception, expected_message", + [ + (IRType.JAQCD, None, NotImplementedError, "to_jaqcd has not been implemented yet."), + (IRType.OPENQASM, None, NotImplementedError, "to_openqasm has not been implemented yet."), + ("invalid-ir-type", None, ValueError, "Supplied ir_type invalid-ir-type is not supported."), + ( + IRType.OPENQASM, + "invalid-serialization-properties", + ValueError, + "serialization_properties must be of type OpenQASMSerializationProperties for " + "IRType.OPENQASM.", + ), + ], +) +def test_result_type_to_ir( + ir_type, serialization_properties, expected_exception, expected_message, result_type +): + with pytest.raises(expected_exception) as exc: + result_type.to_ir(ir_type, serialization_properties=serialization_properties) + assert exc.value.args[0] == expected_message diff --git a/test/unit_tests/braket/circuits/test_result_types.py b/test/unit_tests/braket/circuits/test_result_types.py index c7ee7483..b298a046 100644 --- a/test/unit_tests/braket/circuits/test_result_types.py +++ b/test/unit_tests/braket/circuits/test_result_types.py @@ -16,6 +16,11 @@ import braket.ir.jaqcd as ir from braket.circuits import Circuit, Observable, ResultType from braket.circuits.result_types import ObservableResultType +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + QubitReferenceType, +) testdata = [ (ResultType.StateVector, "state_vector", ir.StateVector, {}, {}), @@ -108,6 +113,88 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): assert actual == expected +@pytest.mark.parametrize( + "result_type, serialization_properties, expected_ir", + [ + ( + ResultType.Expectation(Observable.I(), target=0), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result expectation i(q[0])", + ), + ( + ResultType.Expectation(Observable.I()), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result expectation i all", + ), + ( + ResultType.StateVector(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result state_vector", + ), + ( + ResultType.DensityMatrix(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result density_matrix", + ), + ( + ResultType.DensityMatrix([0, 2]), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result density_matrix q[0], q[2]", + ), + ( + ResultType.DensityMatrix(0), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "#pragma braket result density_matrix $0", + ), + ( + ResultType.Amplitude(["01", "10"]), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + '#pragma braket result amplitude "01", "10"', + ), + ( + ResultType.Probability(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result probability all", + ), + ( + ResultType.Probability([0, 2]), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result probability q[0], q[2]", + ), + ( + ResultType.Probability(0), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "#pragma braket result probability $0", + ), + ( + ResultType.Sample(Observable.I(), target=0), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result sample i(q[0])", + ), + ( + ResultType.Sample(Observable.I()), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result sample i all", + ), + ( + ResultType.Variance(Observable.I(), target=0), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result variance i(q[0])", + ), + ( + ResultType.Variance(Observable.I()), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result variance i all", + ), + ], +) +def test_result_to_ir_openqasm(result_type, serialization_properties, expected_ir): + assert ( + result_type.to_ir(IRType.OPENQASM, serialization_properties=serialization_properties) + == expected_ir + ) + + @pytest.mark.parametrize("testclass,subroutine_name,irclass,input,ir_input", testdata) def test_result_subroutine(testclass, subroutine_name, irclass, input, ir_input): subroutine = getattr(Circuit(), subroutine_name) diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index bf4f6e5a..5159d08b 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -21,6 +21,7 @@ from braket.circuits import Circuit from braket.device_schema import DeviceCapabilities from braket.devices import LocalSimulator, local_simulator +from braket.ir.openqasm import Program from braket.simulator import BraketSimulator from braket.task_result import AnnealingTaskResult, GateModelTaskResult from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult @@ -107,10 +108,49 @@ def properties(self) -> DeviceCapabilities: "shotsRange": [1, 10], }, "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "version": ["1"], + }, "braket.ir.jaqcd.program": { "actionType": "braket.ir.jaqcd.program", "version": ["1"], - } + }, + }, + "deviceParameters": {}, + } + ) + + +class DummyJaqcdSimulator(BraketSimulator): + def run( + self, program: ir.jaqcd.Program, qubits: int, shots: Optional[int], *args, **kwargs + ) -> Dict[str, Any]: + if not isinstance(program, ir.jaqcd.Program): + raise TypeError("Not a Jaqcd program") + self._shots = shots + self._qubits = qubits + return GATE_MODEL_RESULT + + @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"], + }, }, "deviceParameters": {}, } @@ -123,6 +163,40 @@ def assert_qubits(self, qubits): assert self._qubits == qubits +class DummyProgramSimulator(BraketSimulator): + def run( + self, + openqasm_ir: Program, + shots: int = 0, + batch_size: int = 1, + ) -> GateModelTaskResult: + return GATE_MODEL_RESULT + + @property + def properties(self) -> DeviceCapabilities: + return 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"], + } + }, + "deviceParameters": {}, + } + ) + + class DummyAnnealingSimulator(BraketSimulator): def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> AnnealingTaskResult: return ANNEALING_RESULT @@ -152,19 +226,34 @@ def properties(self) -> DeviceCapabilities: ) -mock_entry = Mock() -mock_entry.load.return_value = DummyCircuitSimulator -local_simulator._simulator_devices = {"dummy": mock_entry} +mock_circuit_entry = Mock() +mock_program_entry = Mock() +mock_jaqcd_entry = Mock() +mock_circuit_entry.load.return_value = DummyCircuitSimulator +mock_program_entry.load.return_value = DummyProgramSimulator +mock_jaqcd_entry.load.return_value = DummyJaqcdSimulator +local_simulator._simulator_devices = { + "dummy": mock_circuit_entry, + "dummy_oq3": mock_program_entry, + "dummy_jaqcd": mock_jaqcd_entry, +} def test_load_from_entry_point(): - sim = LocalSimulator("dummy") + sim = LocalSimulator("dummy_oq3") task = sim.run(Circuit().h(0).cnot(0, 1), 10) assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) def test_run_gate_model(): - dummy = DummyCircuitSimulator() + dummy = DummyProgramSimulator() + sim = LocalSimulator(dummy) + task = sim.run(Circuit().h(0).cnot(0, 1), 10) + assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) + + +def test_run_jaqcd_only(): + dummy = DummyJaqcdSimulator() sim = LocalSimulator(dummy) task = sim.run(Circuit().h(0).cnot(0, 1), 10) dummy.assert_shots(10) @@ -172,6 +261,25 @@ def test_run_gate_model(): assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) +def test_run_program_model(): + dummy = DummyProgramSimulator() + sim = LocalSimulator(dummy) + task = sim.run( + Program( + 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() @@ -186,7 +294,7 @@ def test_run_annealing(): def test_registered_backends(): - assert LocalSimulator.registered_backends() == {"dummy"} + assert LocalSimulator.registered_backends() == {"dummy", "dummy_oq3", "dummy_jaqcd"} @pytest.mark.xfail(raises=TypeError) 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 903ce706..3b06b8d7 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 @@ -19,7 +19,7 @@ import pytest from braket.circuits import Observable, ResultType -from braket.ir import jaqcd +from braket.ir import jaqcd, openqasm from braket.task_result import ( AdditionalMetadata, GateModelTaskResult, @@ -45,6 +45,22 @@ def additional_metadata(): return AdditionalMetadata(action=program) +@pytest.fixture +def additional_metadata_openqasm(): + program = openqasm.Program( + source=""" + qubit[2] q; + bit[2] c; + + h q[0]; + cnot q[0], q[1]; + + c = measure q; + """ + ) + return AdditionalMetadata(action=program) + + @pytest.fixture def result_obj_1(task_metadata_shots, additional_metadata): return GateModelTaskResult( @@ -167,6 +183,25 @@ def malformatted_results_2(task_metadata_shots, additional_metadata): ).json() +@pytest.fixture +def openqasm_result_obj_shots(task_metadata_shots, additional_metadata_openqasm): + return GateModelTaskResult.construct( + measurements=[[0, 0], [0, 1], [0, 1], [0, 1]], + measuredQubits=[0, 1], + taskMetadata=task_metadata_shots, + additionalMetadata=additional_metadata_openqasm, + resultTypes=[jaqcd.Probability()], + ) + + +def test_openqasm_shots_calculate_result_types(openqasm_result_obj_shots): + result = GateModelQuantumTaskResult._from_object_internal_computational_basis_sampling( + openqasm_result_obj_shots + ) + assert result.result_types[0].type == jaqcd.Probability() + assert np.array_equal(result.result_types[0].value, [0.25, 0.75, 0, 0]) + + test_ir_results = [ (jaqcd.Probability(targets=[1]), np.array([0.6, 0.4])), (jaqcd.Probability(targets=[1, 2]), np.array([0.4, 0.2, 0.0, 0.4])), diff --git a/tox.ini b/tox.ini index d6945f94..fac55f02 100644 --- a/tox.ini +++ b/tox.ini @@ -21,6 +21,7 @@ deps = {[test-deps]deps} passenv = AWS_PROFILE + BRAKET_ENDPOINT commands = pytest test/integ_tests {posargs} extras = test @@ -89,4 +90,4 @@ commands = deps = # If you need to test on a certain branch, add @ after .git git+https://github.com/aws/amazon-braket-schemas-python.git - git+https://github.com/aws/amazon-braket-default-simulator-python.git \ No newline at end of file + git+https://github.com/aws/amazon-braket-default-simulator-python.git From 90fcc98c6d79ba13bb0d849dd972fafb0d681767 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Thu, 4 Aug 2022 19:12:52 -0700 Subject: [PATCH 0507/1165] fix: handle -0 edge case in result type hash (#421) --- src/braket/tasks/gate_model_quantum_task_result.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 6c59ed58..c77a6d10 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -491,4 +491,8 @@ def _samples_from_measurements( @staticmethod def _result_type_hash(rt_type): + if hasattr(rt_type, "observable") and isinstance(rt_type.observable, list): + as_array = np.array(rt_type.observable) + replaced_neg_0 = np.where(as_array == 0, 0, as_array) + rt_type.observable = replaced_neg_0.tolist() return repr(dict(sorted(dict(rt_type).items(), key=lambda x: x[0]))) From 1c2d06889813044906f6515eceb92be56fd22bc2 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Thu, 4 Aug 2022 21:11:06 -0700 Subject: [PATCH 0508/1165] fix: update simulator version (#422) * fix: update simulator version * Update .gitignore Co-authored-by: Cody Wang --- .gitignore | 1 + setup.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 090c26b8..d91f4d30 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *# *.swp *.idea +*.iml *.DS_Store .vscode/* build_files.tar.gz diff --git a/setup.py b/setup.py index ecbc19d5..a1093e4e 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.10.1", - "amazon-braket-default-simulator>=1.7.1", + "amazon-braket-default-simulator>=1.7.2", "backoff", "boltons", "boto3", From 6dcdffbb438fd7efebb572ffa343eb6024dcb043 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 5 Aug 2022 04:36:11 +0000 Subject: [PATCH 0509/1165] prepare release v1.28.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 01ca94ec..09ac563d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.28.0 (2022-08-05) + +### Features + + * OpenQASM default IR and OpenQASM Local Simulator + +### Bug Fixes and Other Changes + + * update simulator version + * handle -0 edge case in result type hash + ## v1.27.1 (2022-07-29) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 186f8f7b..ef0a0721 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.27.2.dev0" +__version__ = "1.28.0" From 06de3fe05879b4ca552e7135472a86cc4eb7687d Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 5 Aug 2022 04:36:11 +0000 Subject: [PATCH 0510/1165] update development version to v1.28.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 ef0a0721..769e4c9e 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.28.0" +__version__ = "1.28.1.dev0" From bb3c78ba35640ba3732ebfaa58ad749ccdc98b93 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Fri, 5 Aug 2022 09:10:35 -0700 Subject: [PATCH 0511/1165] fix: fix future warning (#423) --- src/braket/tasks/gate_model_quantum_task_result.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index c77a6d10..6a30a289 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -492,7 +492,12 @@ def _samples_from_measurements( @staticmethod def _result_type_hash(rt_type): if hasattr(rt_type, "observable") and isinstance(rt_type.observable, list): - as_array = np.array(rt_type.observable) - replaced_neg_0 = np.where(as_array == 0, 0, as_array) - rt_type.observable = replaced_neg_0.tolist() + rt_type.observable = GateModelQuantumTaskResult._replace_neg_zero(rt_type.observable) return repr(dict(sorted(dict(rt_type).items(), key=lambda x: x[0]))) + + @staticmethod + def _replace_neg_zero(observable_matrix): + 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 From e50abbe903fcd40d5bd842737a7bd4ff69a15c5b Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 5 Aug 2022 16:35:40 +0000 Subject: [PATCH 0512/1165] prepare release v1.28.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 09ac563d..3d7f08ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.28.1 (2022-08-05) + +### Bug Fixes and Other Changes + + * fix future warning + ## v1.28.0 (2022-08-05) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 769e4c9e..3b6b117d 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.28.1.dev0" +__version__ = "1.28.1" From 17fc233ac894b4858897bc24127ddfefeac6be1c Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 5 Aug 2022 16:35:40 +0000 Subject: [PATCH 0513/1165] update development version to v1.28.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 3b6b117d..b098d39f 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.28.1" +__version__ = "1.28.2.dev0" From e625a28a2988c35f9810042cf2bd8e9b1fe485eb Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 9 Aug 2022 15:36:58 -0600 Subject: [PATCH 0514/1165] feature: Pauli strings (#315) Introduces the PauliString class, which can be used, among other things, to generate observables and eigenstate circuits. This class lives in the newly-created `braket.quantum_information` module. --- src/braket/circuits/gate.py | 6 +- src/braket/circuits/noise.py | 4 +- src/braket/circuits/observable.py | 4 +- src/braket/circuits/result_type.py | 6 +- src/braket/quantum_information/__init__.py | 14 ++ .../quantum_information/pauli_string.py | 201 ++++++++++++++++++ .../quantum_information/test_pauli_string.py | 142 +++++++++++++ .../test_gate_model_quantum_task_result.py | 2 +- 8 files changed, 368 insertions(+), 11 deletions(-) create mode 100644 src/braket/quantum_information/__init__.py create mode 100644 src/braket/quantum_information/pauli_string.py create mode 100644 test/unit_tests/braket/quantum_information/test_pauli_string.py diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index c5842b16..071c5ea2 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -13,7 +13,7 @@ from __future__ import annotations -from typing import Any, List, Optional, Sequence, Tuple +from typing import Any, List, Optional, Sequence, Tuple, Type from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.qubit_set import QubitSet @@ -136,10 +136,10 @@ def __repr__(self): return f"{self.name}('qubit_count': {self._qubit_count})" @classmethod - def register_gate(cls, gate: Gate): + def register_gate(cls, gate: Type[Gate]): """Register a gate implementation by adding it into the Gate class. Args: - gate (Gate): Gate class to register. + gate (Type[Gate]): Gate class to register. """ setattr(cls, gate.__name__, gate) diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py index 0bb3bce7..36768048 100644 --- a/src/braket/circuits/noise.py +++ b/src/braket/circuits/noise.py @@ -13,7 +13,7 @@ from __future__ import annotations -from typing import Any, Dict, List, Optional, Sequence, Union +from typing import Any, Dict, List, Optional, Sequence, Type, Union from braket.circuits.free_parameter import FreeParameter from braket.circuits.free_parameter_expression import FreeParameterExpression @@ -152,7 +152,7 @@ def from_dict(cls, noise: dict) -> Noise: raise NotImplementedError @classmethod - def register_noise(cls, noise: Noise): + def register_noise(cls, noise: Type[Noise]): """Register a noise implementation by adding it into the Noise class. Args: diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index 53b6d84e..3dc16f51 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -109,13 +109,13 @@ def eigenvalues(self) -> np.ndarray: raise NotImplementedError def eigenvalue(self, index: int) -> float: - """Returns the the eigenvalue of this observable at the given index. + """Returns the eigenvalue of this observable at the given index. The eigenvalues are ordered by their corresponding computational basis state after diagonalization. Args: - index: The index of the desired eigenvalue + index (int): The index of the desired eigenvalue Returns: float: The `index`th eigenvalue of the observable. diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index 26bddbb2..b0e096fd 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, Dict, List +from typing import Any, Dict, List, Type from braket.circuits.observable import Observable from braket.circuits.qubit import QubitInput @@ -162,11 +162,11 @@ def copy( return copy @classmethod - def register_result_type(cls, result_type: "ResultType"): + def register_result_type(cls, result_type: Type[ResultType]): """Register a result type implementation by adding it into the `ResultType` class. Args: - result_type (ResultType): `ResultType` instance to register. + result_type (Type[ResultType]): `ResultType` class to register. """ setattr(cls, result_type.__name__, result_type) diff --git a/src/braket/quantum_information/__init__.py b/src/braket/quantum_information/__init__.py new file mode 100644 index 00000000..647d0667 --- /dev/null +++ b/src/braket/quantum_information/__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.quantum_information.pauli_string import PauliString # noqa: F401 diff --git a/src/braket/quantum_information/pauli_string.py b/src/braket/quantum_information/pauli_string.py new file mode 100644 index 00000000..bea88be9 --- /dev/null +++ b/src/braket/quantum_information/pauli_string.py @@ -0,0 +1,201 @@ +# 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 itertools +from typing import List, Optional, Tuple, Union + +from braket.circuits.circuit import Circuit +from braket.circuits.observables import TensorProduct, X, Y, Z + +_IDENTITY = "I" +_PAULI_X = "X" +_PAULI_Y = "Y" +_PAULI_Z = "Z" +_PAULI_INDICES = {_IDENTITY: 0, _PAULI_X: 1, _PAULI_Y: 2, _PAULI_Z: 3} +_PAULI_OBSERVABLES = {_PAULI_X: X(), _PAULI_Y: Y(), _PAULI_Z: Z()} +_SIGN_MAP = {"+": 1, "-": -1} + + +class PauliString: + """ + A lightweight representation of a Pauli string with its phase. + """ + + def __init__(self, pauli_string: Union[str, 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 + """ + if not pauli_string: + raise ValueError("pauli_string must not be empty") + if isinstance(pauli_string, PauliString): + self._phase = pauli_string._phase + self._qubit_count = pauli_string._qubit_count + self._nontrivial = pauli_string._nontrivial + elif isinstance(pauli_string, str): + self._phase, factors_str = PauliString._split(pauli_string) + self._qubit_count = len(factors_str) + self._nontrivial = { + i: factors_str[i] for i in range(len(factors_str)) if factors_str[i] != "I" + } + else: + raise TypeError(f"Pauli word {pauli_string} must be of type {PauliString} or {str}") + + @property + def phase(self) -> int: + """int: The phase of the Pauli string. + + Can be one of +/-1 + """ + return self._phase + + @property + 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: + """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. + + Returns: + TensorProduct: The tensor product of the unsigned factors in the Pauli string. + """ + 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. + + The number of substrings is equal to :math:`\binom{n}{w}`, where :math`n` is the number of + nontrivial (non-identity) factors in the Pauli string and :math`w` is `weight`. + + Args: + weight (int): The number of non-identity factors in the substrings. + + Returns: + Tuple[PauliString, ...]: A tuple of weight-n Pauli substrings. + """ + substrings = [] + for indices in itertools.combinations(self._nontrivial, weight): + factors = [ + self._nontrivial[qubit] + if qubit in set(indices).intersection(self._nontrivial) + else "I" + for qubit in range(self._qubit_count) + ] + substrings.append( + PauliString(f"{PauliString._phase_to_str(self._phase)}{''.join(factors)}") + ) + return tuple(substrings) + + def eigenstate(self, signs: Optional[Union[str, List[int], Tuple[int, ...]]] = 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 + Pauli operator. For example, a Pauli string +XYZ and signs ++- has factors +X, +Y and -Z, + with the corresponding qubits in states |+⟩, |i⟩ and |1⟩ respectively (the global phase of + the Pauli string is ignored). + + Args: + signs (Union[str, List[int], Tuple[int, ...]], optional): The sign of each factor of the + eigenstate, specified either as a string of "+" and "_", or as a list or tuple of + +/-1. The length of signs must be equal to the length of the Pauli string. If not + specified, it is assumed to be all +. Default: None. + + Returns: + Circuit: A circuit that prepares the desired eigenstate of the Pauli string. + + Raises: + ValueError: If the length of signs is not equal to that of the Pauli string or the signs + are invalid. + """ + qubit_count = self._qubit_count + if not signs: + signs = "+" * qubit_count + elif len(signs) != qubit_count: + raise ValueError( + f"signs must be the same length of the Pauli string ({qubit_count}), " + f"but was {len(signs)}" + ) + signs_tup = ( + tuple(_SIGN_MAP.get(sign) for sign in signs) if isinstance(signs, str) else tuple(signs) + ) + if not set(signs_tup) <= {1, -1}: + raise ValueError(f"signs must be +/-1, got {signs}") + return self._generate_eigenstate_circuit(signs_tup) + + def __eq__(self, other): + if isinstance(other, PauliString): + return ( + self._phase == other._phase + and self._nontrivial == other._nontrivial + and self._qubit_count == other._qubit_count + ) + return False + + def __getitem__(self, item): + if item >= self._qubit_count: + raise IndexError(item) + return _PAULI_INDICES[self._nontrivial.get(item, "I")] + + 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) + ] + return f"{PauliString._phase_to_str(self._phase)}{''.join(factors)}" + + @staticmethod + def _split(pauli_word: str) -> Tuple[int, str]: + index = 0 + phase = 1 + if pauli_word[index] in {"+", "-"}: + phase *= int(f"{pauli_word[index]}1") + index += 1 + unsigned = pauli_word[index:] + if not unsigned: + raise ValueError("Pauli string cannot be empty") + if set(unsigned) - _PAULI_INDICES.keys(): + raise ValueError(f"{pauli_word} is not a valid Pauli string") + return phase, unsigned + + @staticmethod + def _phase_to_str(phase: int) -> str: + return "+" if phase > 0 else "-" + + def _generate_eigenstate_circuit(self, signs: Tuple[int, ...]) -> Circuit: + circ = Circuit() + for qubit in range(len(signs)): + state = signs[qubit] * self[qubit] + if state == -3: + circ.x(qubit) + elif state == 1: + circ.h(qubit) + elif state == -1: + circ.x(qubit).h(qubit) + elif state == 2: + circ.h(qubit).s(qubit) + elif state == -2: + circ.h(qubit).si(qubit) + return circ diff --git a/test/unit_tests/braket/quantum_information/test_pauli_string.py b/test/unit_tests/braket/quantum_information/test_pauli_string.py new file mode 100644 index 00000000..e9f2d8ad --- /dev/null +++ b/test/unit_tests/braket/quantum_information/test_pauli_string.py @@ -0,0 +1,142 @@ +# 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 functools +import itertools +import math + +import numpy as np +import pytest + +from braket.circuits import gates +from braket.circuits.observables import X, Y, Z +from braket.quantum_information import PauliString + +ORDER = ["I", "X", "Y", "Z"] +PAULI_INDEX_MATRICES = { + 0: gates.I().to_matrix(), + 1: gates.X().to_matrix(), + 2: gates.Y().to_matrix(), + 3: gates.Z().to_matrix(), +} +SIGN_MAP = {"+": 1, "-": -1} + + +@pytest.mark.parametrize( + "pauli_string, string, phase, observable", + [ + ("+XZ", "+XZ", 1, X() @ Z()), + ("-ZXY", "-ZXY", -1, Z() @ X() @ Y()), + ("YIX", "+YIX", 1, Y() @ X()), + (PauliString("-ZYXI"), "-ZYXI", -1, Z() @ Y() @ X()), + ], +) +def test_happy_case(pauli_string, string, phase, observable): + instance = PauliString(pauli_string) + assert str(instance) == string + assert instance.phase == phase + pauli_list = list(str(pauli_string)) + if pauli_list[0] in {"-", "+"}: + pauli_list.pop(0) + stripped = "".join(pauli_list) + assert len(instance) == len(stripped) + assert len(instance) == instance.qubit_count + for i in range(len(instance)): + assert ORDER[instance[i]] == stripped[i] + assert instance == PauliString(pauli_string) + assert instance == PauliString(instance) + assert instance.to_unsigned_observable() == observable + + +@pytest.mark.parametrize( + "other", ["foo", PauliString("+XYZ"), PauliString("-XI"), PauliString("-XYZI")] +) +def test_not_equal(other): + assert PauliString("-XYZ") != other + + +@pytest.mark.xfail(raises=ValueError) +def test_none(): + PauliString(None) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("invalid_string", ["XAY", "-BYZ", "+", "-", "xyz", "", None]) +def test_invalid_string(invalid_string): + PauliString(invalid_string) + + +@pytest.mark.xfail(raises=TypeError) +def test_invalid_type(): + PauliString(1234) + + +@pytest.mark.xfail(raises=IndexError) +@pytest.mark.parametrize("string", ["XZY", "-YIIXZ", "+IXIYIZ"]) +def test_index_out_of_bounds(string): + PauliString(string)[len(string)] + + +@pytest.mark.parametrize( + "string,weight", + # Make phase explicit for test simplicity + list(itertools.product(["-ZYX", "-IXIIXYZ", "+ZXYXY"], [1, 2, 3])), +) +def test_weight_n_substrings(string, weight): + pauli_string = PauliString(string) + qubit_count = pauli_string.qubit_count + nontrivial = [qubit for qubit in range(qubit_count) if pauli_string[qubit]] + substrings = [] + for indices in itertools.combinations(nontrivial, weight): + factors = [string[qubit + 1] if qubit in indices else "I" for qubit in range(qubit_count)] + substrings.append(PauliString(f"{string[0]}{''.join(factors)}")) + actual = pauli_string.weight_n_substrings(weight) + assert actual == tuple(substrings) + assert len(actual) == n_choose_k(len(nontrivial), weight) + + +def n_choose_k(n, k): + m = min(k, n - k) + return functools.reduce(lambda x, y: x * y, range(m + 1, n + 1)) // (math.factorial(n - m)) + + +@pytest.mark.parametrize( + "string,signs", + list(itertools.product(["X", "Y", "Z"], ["+", "-"])) + + [("ZIY", "+--"), ("YIXIZ", [1, 1, -1, -1, 1]), ("XYZ", (-1, -1, -1)), ("XZIY", None)], +) +def test_eigenstate(string, signs): + pauli_string = PauliString(string) + circuit = pauli_string.eigenstate(signs) + for qubit in range(len(string)): + circuit.i(qubit) + initial_state = np.zeros(2 ** len(pauli_string)) + initial_state[0] = 1 + state = circuit.to_unitary() @ initial_state + + if not signs: + signs = [1] * len(string) + signs_list = [SIGN_MAP[sign] for sign in signs] if isinstance(signs, str) else signs + positive = [signs_list[i] for i in range(len(string)) if signs_list[i] < 0 and string[i] != "I"] + total_sign = 1 if len(positive) % 2 == 0 else -1 + pauli_matrix = functools.reduce( + np.kron, [PAULI_INDEX_MATRICES[pauli] for pauli in pauli_string] + ) + actual = total_sign * pauli_matrix @ state + assert np.allclose(actual, state) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize("sign", ["+ab", "--+-", [+2, -1, -1], (-1, 1j, 1)]) +def test_eigenstate_invalid_signs(sign): + PauliString("XYZ").eigenstate(sign) 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 3b06b8d7..7a9ae3a2 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 @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. import json -from typing import Counter +from collections import Counter from unittest.mock import patch import numpy as np From ab7b0b36c74ce1fa5fd3e8ae2c02d917a1f2ed54 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 9 Aug 2022 16:14:40 -0600 Subject: [PATCH 0515/1165] infra: Add SF plugin to dependent tests (#424) --- .github/workflows/dependent-tests.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index afa7777e..6c5d8efe 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -18,7 +18,10 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python-version: [3.7, 3.8, 3.9] - dependent: [amazon-braket-ocean-plugin-python, amazon-braket-pennylane-plugin-python] + dependent: + - amazon-braket-ocean-plugin-python + - amazon-braket-pennylane-plugin-python + - amazon-braket-strawberryfields-plugin-python steps: - uses: actions/checkout@v2 From 8e39075f5e67f60f5d566b8d8b0ff7aeffd0dd77 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 9 Aug 2022 17:21:19 -0600 Subject: [PATCH 0516/1165] infra: Don't run tests on push to feature branches (#425) --- .github/workflows/dependent-tests.yml | 1 - .github/workflows/python-package.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index 6c5d8efe..0daf6c3c 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -4,7 +4,6 @@ on: push: branches: - main - - feature/** pull_request: branches: - main diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index e6b1ad85..83abf0fb 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -7,7 +7,6 @@ on: push: branches: - main - - feature/** pull_request: branches: - main From f4b2bc1fc89320f1749b129e08a4caf2f9e590f4 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 10 Aug 2022 18:23:32 +0000 Subject: [PATCH 0517/1165] prepare release v1.29.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 3d7f08ec..9f562ce6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.29.0 (2022-08-10) + +### Features + + * Pauli strings + +### Testing and Release Infrastructure + + * Don't run tests on push to feature branches + * Add SF plugin to dependent tests + ## v1.28.1 (2022-08-05) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index b098d39f..3af4f21d 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.28.2.dev0" +__version__ = "1.29.0" From 52406ad35438b3cfe56d072c0769a9e00b7bca43 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 10 Aug 2022 18:23:32 +0000 Subject: [PATCH 0518/1165] update development version to v1.29.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 3af4f21d..bd47b22a 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.29.0" +__version__ = "1.29.1.dev0" From aac129df184dd89268d5f4a9fcc4eb6839048d85 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Fri, 12 Aug 2022 14:47:19 -0700 Subject: [PATCH 0519/1165] infra: Avoid mutation of fixtures (#427) --- .../braket/tasks/test_photonic_model_quantum_task_result.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/unit_tests/braket/tasks/test_photonic_model_quantum_task_result.py b/test/unit_tests/braket/tasks/test_photonic_model_quantum_task_result.py index 55617a1f..c3110b8e 100644 --- a/test/unit_tests/braket/tasks/test_photonic_model_quantum_task_result.py +++ b/test/unit_tests/braket/tasks/test_photonic_model_quantum_task_result.py @@ -64,9 +64,10 @@ def result_1_str(result_1): @pytest.fixture def empty_result(task_metadata, additional_metadata): - task_metadata.id = "empty_arn" + updated_metadata = task_metadata.copy() + updated_metadata.id = "empty_arn" return PhotonicModelTaskResult( - taskMetadata=task_metadata, additionalMetadata=additional_metadata + taskMetadata=updated_metadata, additionalMetadata=additional_metadata ) From e41ad0dc8f7fed9896f64a940c420981dfc159e8 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 17 Aug 2022 18:25:04 +0000 Subject: [PATCH 0520/1165] prepare release v1.29.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 9f562ce6..bedf6a8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.29.0.post0 (2022-08-17) + +### Testing and Release Infrastructure + + * Avoid mutation of fixtures + ## v1.29.0 (2022-08-10) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index bd47b22a..7ec103a5 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.29.1.dev0" +__version__ = "1.29.0.post0" From e6c5a5f88b775a320fef7ec9de30034f751a3d08 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 17 Aug 2022 18:25:04 +0000 Subject: [PATCH 0521/1165] update development version to v1.29.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 7ec103a5..bd47b22a 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.29.0.post0" +__version__ = "1.29.1.dev0" From c25100ce3bb60916b29e5cd131696ba392aa6873 Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Thu, 18 Aug 2022 13:32:48 -0700 Subject: [PATCH 0522/1165] fix: updating test cost tracking integ test to use M2. (#428) --- 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 d7683f6e..a9db2d75 100644 --- a/test/integ_tests/test_cost_tracking.py +++ b/test/integ_tests/test_cost_tracking.py @@ -65,7 +65,7 @@ def test_qpu_tracking(): with Tracker() as t: AwsDevice("arn:aws:braket:::device/qpu/ionq/ionQdevice").run(circuit, shots=10) AwsDevice("arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy").run(circuit, shots=10) - AwsDevice("arn:aws:braket:::device/qpu/rigetti/Aspen-11").run(circuit, shots=10) + AwsDevice("arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-2").run(circuit, shots=10) assert t.qpu_tasks_cost() > 0 From 4a966269d88f535fb030c6bed7bfa43e6d4a6f81 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 18 Aug 2022 21:01:55 +0000 Subject: [PATCH 0523/1165] prepare release v1.29.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 bedf6a8a..9b6cf140 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.29.1 (2022-08-18) + +### Bug Fixes and Other Changes + + * updating test cost tracking integ test to use M2. + ## v1.29.0.post0 (2022-08-17) ### Testing and Release Infrastructure diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index bd47b22a..e3ae1bde 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.29.1.dev0" +__version__ = "1.29.1" From fc6dd12b380560e77ab23606b4615ba90fe36772 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 18 Aug 2022 21:01:55 +0000 Subject: [PATCH 0524/1165] update development version to v1.29.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 e3ae1bde..7ebd0875 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.29.1" +__version__ = "1.29.2.dev0" From 8e5da5f1780323f7c9dc01b173a6393640162225 Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Wed, 24 Aug 2022 11:56:34 -0700 Subject: [PATCH 0525/1165] fix: Updating documentation and type hints. (#429) --- src/braket/annealing/problem.py | 8 +- src/braket/aws/aws_device.py | 58 ++-- src/braket/aws/aws_quantum_job.py | 38 +-- src/braket/aws/aws_quantum_task.py | 56 ++-- src/braket/aws/aws_quantum_task_batch.py | 62 ++-- src/braket/aws/aws_session.py | 156 +++++---- src/braket/circuits/angled_gate.py | 8 +- src/braket/circuits/ascii_circuit_diagram.py | 13 +- src/braket/circuits/circuit.py | 115 +++---- src/braket/circuits/compiler_directive.py | 5 +- src/braket/circuits/compiler_directives.py | 6 +- src/braket/circuits/free_parameter.py | 16 +- .../circuits/free_parameter_expression.py | 27 +- src/braket/circuits/gate.py | 6 +- src/braket/circuits/gates.py | 298 ++++++++++-------- src/braket/circuits/instruction.py | 15 +- src/braket/circuits/moments.py | 24 +- src/braket/circuits/noise.py | 83 ++--- src/braket/circuits/noise_helpers.py | 44 ++- src/braket/circuits/noise_model/criteria.py | 2 +- .../circuits/noise_model/noise_model.py | 2 + src/braket/circuits/noises.py | 203 ++++++------ src/braket/circuits/observable.py | 10 +- src/braket/circuits/observables.py | 42 ++- src/braket/circuits/operator.py | 13 +- src/braket/circuits/parameterizable.py | 17 +- src/braket/circuits/quantum_operator.py | 13 +- .../circuits/quantum_operator_helpers.py | 24 +- src/braket/circuits/qubit.py | 5 +- src/braket/circuits/qubit_set.py | 9 +- src/braket/circuits/result_type.py | 22 +- src/braket/circuits/result_types.py | 28 +- src/braket/circuits/serialization.py | 7 + src/braket/circuits/unitary_calculation.py | 4 +- src/braket/devices/device.py | 4 +- src/braket/devices/local_simulator.py | 22 +- src/braket/ipython_utils.py | 2 +- src/braket/jobs/data_persistence.py | 2 +- src/braket/jobs/image_uris.py | 4 +- src/braket/jobs/local/local_job.py | 94 ++++-- src/braket/jobs/local/local_job_container.py | 12 +- .../jobs/local/local_job_container_setup.py | 37 ++- src/braket/jobs/logs.py | 45 ++- .../cwl_insights_metrics_fetcher.py | 21 +- .../jobs/metrics_data/cwl_metrics_fetcher.py | 26 +- .../jobs/metrics_data/log_metrics_parser.py | 30 +- src/braket/jobs/quantum_job.py | 38 ++- src/braket/jobs/quantum_job_creation.py | 8 +- .../quantum_information/pauli_string.py | 2 +- .../tasks/annealing_quantum_task_result.py | 22 +- .../tasks/gate_model_quantum_task_result.py | 32 +- src/braket/tasks/local_quantum_task.py | 5 + .../photonic_model_quantum_task_result.py | 10 +- src/braket/tasks/quantum_task.py | 27 +- src/braket/tracking/pricing.py | 7 +- src/braket/tracking/tracker.py | 36 ++- src/braket/tracking/tracking_context.py | 20 +- 57 files changed, 1138 insertions(+), 807 deletions(-) diff --git a/src/braket/annealing/problem.py b/src/braket/annealing/problem.py index c101b938..4959df52 100644 --- a/src/braket/annealing/problem.py +++ b/src/braket/annealing/problem.py @@ -85,7 +85,7 @@ def quadratic(self) -> Dict[Tuple[int, int], float]: Returns: Dict[Tuple[int, int], float]: The quadratic terms of this problem, - as a map of variables to coefficient + as a map of variables to coefficient """ return self._quadratic @@ -119,7 +119,7 @@ def add_quadratic_term(self, term: Tuple[int, int], coefficient: float) -> Probl Args: term (Tuple[int, int]): The variables of the quadratic term - coefficient (flost): The coefficient of the quadratic term + coefficient (float): The coefficient of the quadratic term Returns: Problem: This problem object @@ -139,11 +139,11 @@ def add_quadratic_terms(self, coefficients: Dict[Tuple[int, int], float]) -> Pro self._quadratic.update(coefficients) return self - def to_ir(self): + def to_ir(self) -> Problem: """Converts this problem into IR representation. Returns: - ir.Problem: IR representation of this problem object + Problem: IR representation of this problem object """ return ir.Problem( type=ir.ProblemType[self._problem_type.value], diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 4609e631..570016cf 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -60,7 +60,7 @@ def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): """ Args: arn (str): The ARN of the device - aws_session (AwsSession, optional): An AWS session object. Default is `None`. + aws_session (Optional[AwsSession]): An AWS session object. Default is `None`. Note: Some devices (QPUs) are physically located in specific AWS Regions. In some cases, @@ -97,20 +97,16 @@ def run( Args: task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]): Specification of task (circuit or annealing problem or program) to run on device. - s3_destination_folder (AwsSession.S3DestinationFolder, optional): The S3 location to + s3_destination_folder (Optional[S3DestinationFolder]): The S3 location to save the task's results to. Default is `/tasks` if evoked outside of a Braket Job, `/jobs//tasks` if evoked inside of a Braket Job. - shots (int, optional): The number of times to run the circuit or annealing problem. + shots (Optional[int]): The number of times to run the circuit or annealing problem. Default is 1000 for QPUs and 0 for simulators. poll_timeout_seconds (float): The polling timeout for `AwsQuantumTask.result()`, in seconds. Default: 5 days. poll_interval_seconds (float): The polling interval for `AwsQuantumTask.result()`, in seconds. Default: 1 second. - *aws_quantum_task_args: Variable length positional arguments for - `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. - **aws_quantum_task_kwargs: Variable length keyword arguments for - `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. Returns: AwsQuantumTask: An AwsQuantumTask that tracks the execution on the device. @@ -179,13 +175,13 @@ def run_batch( Args: task_specifications (List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]]): List of circuits or annealing problems to run on device. - s3_destination_folder (AwsSession.S3DestinationFolder, optional): The S3 location to + s3_destination_folder (Optional[S3DestinationFolder]): The S3 location to save the tasks' results to. Default is `/tasks` if evoked outside of a Braket Job, `/jobs//tasks` if evoked inside of a Braket Job. - shots (int, optional): The number of times to run the circuit or annealing problem. + shots (Optional[int]): The number of times to run the circuit or annealing problem. Default is 1000 for QPUs and 0 for simulators. - max_parallel (int, optional): The maximum number of tasks to run on AWS in parallel. + max_parallel (Optional[int]): The maximum number of tasks to run on AWS in parallel. Batch creation will fail if this value is greater than the maximum allowed concurrent tasks on the device. Default: 10 max_connections (int): The maximum number of connections in the Boto3 connection pool. @@ -194,10 +190,6 @@ def run_batch( in seconds. Default: 5 days. poll_interval_seconds (float): The polling interval for results in seconds. Default: 1 second. - *aws_quantum_task_args: Variable length positional arguments for - `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. - **aws_quantum_task_kwargs: Variable length keyword arguments for - `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. Returns: AwsQuantumTaskBatch: A batch containing all of the tasks run @@ -231,7 +223,7 @@ def refresh_metadata(self) -> None: """ self._populate_properties(self._aws_session) - def _get_session_and_initialize(self, session): + def _get_session_and_initialize(self, session: AwsSession) -> AwsSession: device_region = AwsDevice.get_device_region(self._arn) return ( self._get_regional_device_session(session) @@ -239,7 +231,7 @@ def _get_session_and_initialize(self, session): else self._get_non_regional_device_session(session) ) - def _get_regional_device_session(self, session): + def _get_regional_device_session(self, session: AwsSession) -> AwsSession: device_region = AwsDevice.get_device_region(self._arn) region_session = ( session @@ -254,7 +246,7 @@ def _get_regional_device_session(self, session): "Code" ] == "ResourceNotFoundException" else e - def _get_non_regional_device_session(self, session): + def _get_non_regional_device_session(self, session: AwsSession) -> AwsSession: current_region = session.region try: self._populate_properties(session) @@ -276,7 +268,7 @@ def _get_non_regional_device_session(self, session): raise e raise ValueError(f"QPU '{self._arn}' not found") - def _populate_properties(self, session): + def _populate_properties(self, session: AwsSession) -> None: metadata = session.get_device(self._arn) self._name = metadata.get("deviceName") self._status = metadata.get("deviceStatus") @@ -305,7 +297,10 @@ def arn(self) -> str: @property def is_available(self) -> bool: - """bool: Return if the device is currently available""" + """Returns true if the device is currently available. + Returns: + bool: Return if the device is currently available. + """ if self.status != "ONLINE": return False @@ -416,13 +411,13 @@ def _construct_topology_graph(self) -> DiGraph: return None @property - def _default_shots(self): + def _default_shots(self) -> int: return ( AwsDevice.DEFAULT_SHOTS_QPU if "qpu" in self.arn else AwsDevice.DEFAULT_SHOTS_SIMULATOR ) @property - def _default_max_parallel(self): + def _default_max_parallel(self) -> int: return AwsDevice.DEFAULT_MAX_PARALLEL def __repr__(self): @@ -453,16 +448,16 @@ def get_devices( >>> AwsDevice.get_devices(types=['SIMULATOR']) Args: - arns (List[str], optional): device ARN list, default is `None` - names (List[str], optional): device name list, default is `None` - types (List[AwsDeviceType], optional): device type list, default is `None` + arns (Optional[List[str]]): device ARN list, default is `None` + names (Optional[List[str]]): device name list, default is `None` + types (Optional[List[AwsDeviceType]]): device type list, default is `None` QPUs will be searched for all regions and simulators will only be searched for the region of the current session. - statuses (List[str], optional): device status list, default is `None` - provider_names (List[str], optional): provider name list, default is `None` - order_by (str, optional): field to order result by, default is `name`. + statuses (Optional[List[str]]): device status list, default is `None` + provider_names (Optional[List[str]]): provider name list, default is `None` + order_by (str): field to order result by, default is `name`. Accepted values are ['arn', 'name', 'type', 'provider_name', 'status'] - aws_session (AwsSession, optional) aws_session: An AWS session object. + aws_session (Optional[AwsSession]): An AWS session object. Default is `None`. Returns: @@ -515,6 +510,13 @@ def get_devices( @staticmethod def get_device_region(device_arn: str) -> str: + """Gets the region from a device arn. + Args: + device_arn (str): The device ARN. + + Returns: + str: the region of the ARN. + """ try: return device_arn.split(":")[3] except IndexError: diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index 8d0b7870..6b0c181d 100644 --- a/src/braket/aws/aws_quantum_job.py +++ b/src/braket/aws/aws_quantum_job.py @@ -123,7 +123,7 @@ def create( For convenience, this accepts other types for keys and values, but `str()` is called to convert them before being passed on. Default: None. - input_data (Union[str, S3DataSourceConfig, dict]): Information about the training + input_data (Union[str, Dict, S3DataSourceConfig]): Information about the training data. Dictionary maps channel names to local paths or S3 URIs. Contents found at any local paths will be uploaded to S3 at f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local @@ -208,7 +208,7 @@ def __init__(self, arn: str, aws_session: AwsSession = None): """ Args: arn (str): The ARN of the job. - aws_session (AwsSession, optional): The `AwsSession` for connecting to AWS services. + aws_session (AwsSession): The `AwsSession` for connecting to AWS services. Default is `None`, in which case an `AwsSession` object will be created with the region of the job. """ @@ -259,7 +259,7 @@ def state(self, use_cached_value: bool = False) -> str: """The state of the quantum job. Args: - use_cached_value (bool, optional): If `True`, uses the value most recently retrieved + 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`. @@ -350,7 +350,7 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: """Gets the job metadata defined in Amazon Braket. Args: - use_cached_value (bool, optional): If `True`, uses the value most recently retrieved + 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`. @@ -382,7 +382,7 @@ def metrics( when there is a conflict. Default: MetricStatistic.MAX. Returns: - Dict[str, List[Union[str, float, int]]] : The metrics data. + Dict[str, List[Any]] : The metrics data. """ fetcher = CwlInsightsMetricsFetcher(self._aws_session) metadata = self.metadata(True) @@ -417,11 +417,9 @@ def result( Args: poll_timeout_seconds (float): The polling timeout, in seconds, for `result()`. Default: 10 days. - poll_interval_seconds (float): The polling interval, in seconds, for `result()`. Default: 5 seconds. - Returns: Dict[str, Any]: Dict specifying the job results. @@ -443,7 +441,7 @@ def result( return AwsQuantumJob._read_and_deserialize_results(temp_dir, job_name) @staticmethod - def _read_and_deserialize_results(temp_dir, job_name): + def _read_and_deserialize_results(temp_dir: str, job_name: str) -> Dict[str, Any]: try: with open(f"{temp_dir}/{job_name}/{AwsQuantumJob.RESULTS_FILENAME}", "r") as f: persisted_data = PersistedJobData.parse_raw(f.read()) @@ -456,7 +454,7 @@ def _read_and_deserialize_results(temp_dir, job_name): def download_result( self, - extract_to=None, + extract_to: str = None, poll_timeout_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_INTERVAL, ) -> None: @@ -468,11 +466,9 @@ def download_result( extract_to (str): The directory to which the results are extracted. The results are extracted to a folder titled with the job name within this directory. Default= `Current working directory`. - - poll_timeout_seconds: (float): The polling timeout, in seconds, for `download_result()`. + poll_timeout_seconds (float): The polling timeout, in seconds, for `download_result()`. Default: 10 days. - - poll_interval_seconds: (float): The polling interval, in seconds, for + poll_interval_seconds (float): The polling interval, in seconds, for `download_result()`.Default: 5 seconds. Raises: @@ -503,7 +499,7 @@ def download_result( f"timed out after {poll_timeout_seconds} seconds." ) - def _attempt_results_download(self, output_bucket_uri, output_s3_path): + def _attempt_results_download(self, output_bucket_uri: str, output_s3_path: str) -> None: try: self._aws_session.download_from_s3( s3_uri=output_bucket_uri, filename=AwsQuantumJob.RESULTS_TAR_FILENAME @@ -522,7 +518,7 @@ def _attempt_results_download(self, output_bucket_uri, output_s3_path): raise e @staticmethod - def _extract_tar_file(extract_path): + def _extract_tar_file(extract_path: str) -> None: with tarfile.open(AwsQuantumJob.RESULTS_TAR_FILENAME, "r:gz") as tar: tar.extractall(extract_path) @@ -538,7 +534,9 @@ def __hash__(self) -> int: return hash(self.arn) @staticmethod - def _initialize_session(session_value, device, logger): + def _initialize_session( + session_value: AwsSession, device: AwsDevice, logger: Logger + ) -> AwsSession: aws_session = session_value or AwsSession() if device.startswith("local:"): return aws_session @@ -550,7 +548,9 @@ def _initialize_session(session_value, device, logger): ) @staticmethod - def _initialize_regional_device_session(aws_session, device, logger): + def _initialize_regional_device_session( + aws_session: AwsSession, device: AwsDevice, logger: Logger + ) -> AwsSession: device_region = AwsDevice.get_device_region(device) current_region = aws_session.region if current_region != device_region: @@ -565,7 +565,9 @@ def _initialize_regional_device_session(aws_session, device, logger): ] == "ResourceNotFoundException" else e @staticmethod - def _initialize_non_regional_device_session(aws_session, device, logger): + def _initialize_non_regional_device_session( + aws_session: AwsSession, device: AwsDevice, logger: Logger + ) -> AwsSession: original_region = aws_session.region try: aws_session.get_device(device) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 88b00e5a..8c21d7d0 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -97,8 +97,8 @@ def create( device_arn (str): The ARN of the quantum device. - task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]): - The specification of the task to run on device. + task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]): The + specification of the task to run on device. 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 +175,7 @@ def __init__( """ Args: arn (str): The ARN of the task. - aws_session (AwsSession, optional): The `AwsSession` for connecting to AWS services. + aws_session (AwsSession): The `AwsSession` for connecting to AWS services. Default is `None`, in which case an `AwsSession` object will be created with the region of the task. poll_timeout_seconds (float): The polling timeout for `result()`. Default: 5 days. @@ -244,7 +244,7 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: Get task metadata defined in Amazon Braket. Args: - use_cached_value (bool, optional): If `True`, uses the value most recently retrieved + 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`. @@ -263,7 +263,7 @@ def state(self, use_cached_value: bool = False) -> str: The state of the quantum task. Args: - use_cached_value (bool, optional): If `True`, uses the value most recently retrieved + 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`. @@ -276,7 +276,7 @@ def state(self, use_cached_value: bool = False) -> str: """ return self._status(use_cached_value) - def _status(self, use_cached_value=False): + def _status(self, use_cached_value: bool = False) -> str: metadata = self.metadata(use_cached_value) status = metadata.get("status") if not use_cached_value and status in self.NO_RESULT_TERMINAL_STATES: @@ -286,7 +286,7 @@ def _status(self, use_cached_value=False): self._logger.warning(f"Task failure reason is: {failure_reason}.") return status - def _update_status_if_nonterminal(self): + def _update_status_if_nonterminal(self) -> str: # If metadata has not been populated, the first call to _status will fetch it, # so the second _status call will no longer need to metadata_absent = not self._metadata @@ -308,9 +308,9 @@ def result( Consecutive calls to this method return a cached result from the preceding request. Returns: - Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: The result of the task, - if the task completed successfully; returns `None` if the task did not complete - successfully or the future timed out. + Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]: # noqa + The result of the task, if the task completed successfully; returns `None` if the 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 @@ -326,7 +326,7 @@ def result( self._logger.warning("Task future was cancelled") return self._result - def _get_future(self): + def _get_future(self) -> asyncio.Future: try: asyncio.get_event_loop() except Exception as e: @@ -404,7 +404,11 @@ async def _wait_for_completion( self._result = None return None - def _download_result(self): + def _download_result( + self, + ) -> Union[ + GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult + ]: current_metadata = self.metadata(True) result_string = self._aws_session.retrieve_s3_object_body( current_metadata["outputS3Bucket"], @@ -440,7 +444,7 @@ def _create_internal( create_task_kwargs: Dict[str, Any], device_arn: str, device_parameters: Union[dict, BraketSchemaBase], - disable_qubit_rewiring, + disable_qubit_rewiring: bool, *args, **kwargs, ) -> AwsQuantumTask: @@ -454,7 +458,7 @@ def _( create_task_kwargs: Dict[str, Any], device_arn: str, _device_parameters: Union[dict, BraketSchemaBase], # Not currently used for OpenQasmProgram - _disable_qubit_rewiring, + _disable_qubit_rewiring: bool, *args, **kwargs, ) -> AwsQuantumTask: @@ -474,7 +478,7 @@ def _( create_task_kwargs: Dict[str, any], device_arn: str, _device_parameters: Union[dict, BraketSchemaBase], - _disable_qubit_rewiring, + _disable_qubit_rewiring: bool, *args, **kwargs, ) -> AwsQuantumTask: @@ -490,7 +494,7 @@ def _( create_task_kwargs: Dict[str, Any], device_arn: str, device_parameters: Union[dict, BraketSchemaBase], # Not currently used for circuits - disable_qubit_rewiring, + disable_qubit_rewiring: bool, *args, **kwargs, ) -> AwsQuantumTask: @@ -561,7 +565,21 @@ def _( return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) -def _create_annealing_device_params(device_params, device_arn): +def _create_annealing_device_params( + device_params: Dict[str, Any], device_arn: str +) -> Union[DwaveAdvantageDeviceParameters, Dwave2000QDeviceParameters]: + """Gets Annealing Device Parameters. + + Args: + device_params (Dict[str, Any]): Additional parameters for the device. + For example, for D-Wave: + `{"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}}` + device_arn (str): The ARN of the quantum device. + + Returns: + Union[DwaveAdvantageDeviceParameters, Dwave2000QDeviceParameters]: The device parameters. + + """ if type(device_params) is not dict: device_params = device_params.dict() @@ -602,7 +620,9 @@ def _create_common_params( @singledispatch -def _format_result(result): +def _format_result( + 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 d45ee3e3..5f20a999 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -58,7 +58,7 @@ def __init__( Args: aws_session (AwsSession): AwsSession to connect to AWS with. device_arn (str): The ARN of the quantum device. - task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]): + task_specifications (List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]]): The specification of the task to run on device. s3_destination_folder (AwsSession.S3DestinationFolder): NamedTuple, with bucket for index 0 and key for index 1, that specifies the Amazon S3 bucket and folder @@ -75,10 +75,6 @@ def __init__( in seconds. Default: 5 days. poll_interval_seconds (float): The polling interval for results in seconds. Default: 1 second. - *aws_quantum_task_args: Variable length positional arguments for - `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. - **aws_quantum_task_kwargs: Variable length keyword arguments for - `braket.aws.aws_quantum_task.AwsQuantumTask.create()`. """ self._tasks = AwsQuantumTaskBatch._execute( aws_session, @@ -111,18 +107,18 @@ def __init__( @staticmethod def _execute( - aws_session, - device_arn, - task_specifications, - s3_destination_folder, - shots, - max_parallel, - max_workers, - poll_timeout_seconds, - poll_interval_seconds, + aws_session: AwsSession, + device_arn: str, + task_specifications: List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]], + s3_destination_folder: AwsSession.S3DestinationFolder, + shots: int, + max_parallel: int, + max_workers: int = MAX_CONNECTIONS_DEFAULT, + poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, + poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, *args, **kwargs, - ): + ) -> List[AwsQuantumTask]: for task_specification in task_specifications: if isinstance(task_specification, Circuit) and task_specification.parameters: raise ValueError( @@ -153,20 +149,20 @@ def _execute( @staticmethod def _create_task( - remaining, - aws_session, - device_arn, - task_specification, - s3_destination_folder, - shots, - poll_interval_seconds, + remaining: List[int], + aws_session: AwsSession, + device_arn: str, + task_specifications: List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]], + s3_destination_folder: AwsSession.S3DestinationFolder, + shots: int, + poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, *args, **kwargs, - ): + ) -> AwsQuantumTask: task = AwsQuantumTask.create( aws_session, device_arn, - task_specification, + task_specifications, s3_destination_folder, shots, poll_interval_seconds=poll_interval_seconds, @@ -184,7 +180,12 @@ def _create_task( time.sleep(poll_interval_seconds) return task - def results(self, fail_unsuccessful=False, max_retries=MAX_RETRIES, use_cached_value=True): + def results( + self, + fail_unsuccessful: bool = False, + max_retries: int = MAX_RETRIES, + use_cached_value: bool = True, + ) -> List[AwsQuantumTask]: """Retrieves the result of every task in the batch. Polling for results happens in parallel; this method returns when all tasks @@ -202,7 +203,7 @@ def results(self, fail_unsuccessful=False, max_retries=MAX_RETRIES, use_cached_v Returns: List[AwsQuantumTask]: The results of all of the tasks in the batch. - `FAILED`, `CANCELLED`, or timed out tasks will have a result of None + `FAILED`, `CANCELLED`, or timed out tasks will have a result of None """ if not self._results or not use_cached_value: self._results = AwsQuantumTaskBatch._retrieve_results(self._tasks, self._max_workers) @@ -222,12 +223,12 @@ def results(self, fail_unsuccessful=False, max_retries=MAX_RETRIES, use_cached_v return self._results @staticmethod - def _retrieve_results(tasks, max_workers): + def _retrieve_results(tasks: List[AwsQuantumTask], max_workers: int) -> List[AwsQuantumTask]: 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] - def retry_unsuccessful_tasks(self): + def retry_unsuccessful_tasks(self) -> bool: """Retries any tasks in the batch without valid results. This method should only be called after `results()` has been called at least once. @@ -279,7 +280,10 @@ def size(self) -> int: @property def unfinished(self) -> Set[str]: - """Set[str]: The IDs of all the tasks in the batch that have yet to complete""" + """Gets all the IDs of all the tasks in teh batch that have yet to complete. + Returns: + Set[str]: The IDs of all the tasks in the batch that have yet to complete. + """ with ThreadPoolExecutor(max_workers=self._max_workers) as executor: status_futures = {task.id: executor.submit(task.state) for task in self._tasks} unfinished = set() diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 623f9ba1..caf3ba07 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -18,10 +18,11 @@ import os.path import re from pathlib import Path -from typing import Any, Dict, List, NamedTuple, Optional +from typing import Any, Dict, List, NamedTuple, Optional, Tuple import backoff import boto3 +from botocore import client from botocore.config import Config from botocore.exceptions import ClientError @@ -36,12 +37,19 @@ class AwsSession(object): S3DestinationFolder = NamedTuple("S3DestinationFolder", [("bucket", str), ("key", str)]) - def __init__(self, boto_session=None, braket_client=None, config=None, default_bucket=None): + def __init__( + self, + boto_session: boto3.Session = None, + braket_client: client = None, + config: Config = None, + default_bucket: str = None, + ): """ Args: - boto_session: A boto3 session object. - braket_client: A boto3 Braket client. - config: A botocore Config object. + boto_session (Session): A boto3 session object. + braket_client (client): A boto3 Braket client. + config (Config): A botocore Config object. + default_bucket (str): The name of the default bucket of the AWS Session. """ if ( boto_session @@ -83,44 +91,69 @@ def __init__(self, boto_session=None, braket_client=None, config=None, default_b self._ecr = None @property - def region(self): + def region(self) -> str: return self.boto_session.region_name @property - def account_id(self): + def account_id(self) -> str: return self.sts_client.get_caller_identity()["Account"] @property - def iam_client(self): + def iam_client(self) -> client: + """Gets the IAM client. + + Returns: + client: The IAM Client. + """ if not self._iam: self._iam = self.boto_session.client("iam", region_name=self.region) return self._iam @property - def s3_client(self): + def s3_client(self) -> client: + """Gets the S3 client. + + Returns: + client: The S3 Client. + """ if not self._s3: self._s3 = self.boto_session.client("s3", region_name=self.region) return self._s3 @property - def sts_client(self): + def sts_client(self) -> client: + """Gets the STS client. + + Returns: + client: The STS Client. + """ if not self._sts: self._sts = self.boto_session.client("sts", region_name=self.region) return self._sts @property - def logs_client(self): + def logs_client(self) -> client: + """Gets the CloudWatch logs client. + + Returns: + client: The CloudWatch logs Client. + """ if not self._logs: self._logs = self.boto_session.client("logs", region_name=self.region) return self._logs @property - def ecr_client(self): + def ecr_client(self) -> client: + """Gets the ECR client. + + Returns: + client: The ECR Client. + """ if not self._ecr: self._ecr = self.boto_session.client("ecr", region_name=self.region) return self._ecr - def _update_user_agent(self): + def _update_user_agent(self) -> None: """ 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 @@ -128,7 +161,7 @@ def _update_user_agent(self): https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html#botocore.config.Config """ - def _notebook_instance_version(): + def _notebook_instance_version() -> str: # TODO: Replace with lifecycle configuration version once we have a way to access those nbi_metadata_path = "/opt/ml/metadata/resource-metadata.json" return "0" if os.path.exists(nbi_metadata_path) else "None" @@ -212,7 +245,7 @@ def create_job(self, **boto3_kwargs) -> str: return response["jobArn"] @staticmethod - def _should_giveup(err): + def _should_giveup(err: Exception) -> bool: return not ( isinstance(err, ClientError) and err.response["Error"]["Code"] @@ -250,7 +283,7 @@ def get_default_jobs_role(self) -> str: `AmazonBraketJobsExecutionRole` with a `PathPrefix` of `/service-role/`. Returns: - (str): The ARN for the default IAM role for jobs execution created in the Amazon + str: The ARN for the default IAM role for jobs execution created in the Amazon Braket console. Raises: @@ -314,20 +347,16 @@ def retrieve_s3_object_body(self, s3_bucket: str, s3_object_key: str) -> str: return obj.get()["Body"].read().decode("utf-8") def upload_to_s3(self, filename: str, s3_uri: str) -> None: - """ - Upload file to S3 + """Upload file to S3. Args: filename (str): local file to be uploaded. s3_uri (str): The S3 URI where the file will be uploaded. - - Returns: - None """ bucket, key = self.parse_s3_uri(s3_uri) self.s3_client.upload_file(filename, bucket, key) - def upload_local_data(self, local_prefix: str, s3_prefix: str): + def upload_local_data(self, local_prefix: str, s3_prefix: str) -> None: """ Upload local data matching a prefix to a corresponding location in S3 @@ -338,17 +367,18 @@ def upload_local_data(self, local_prefix: str, s3_prefix: str): when the data is uploaded. This will be an S3 URI and should include the bucket (i.e. 's3://my-bucket/my/prefix-') - For example, local_prefix = "input", s3_prefix = "s3://my-bucket/dir/input" will upload: - * 'input.csv' to 's3://my-bucket/dir/input.csv' - * 'input-2.csv' to 's3://my-bucket/dir/input-2.csv' - * 'input/data.txt' to 's3://my-bucket/dir/input/data.txt' - * 'input-dir/data.csv' to 's3://my-bucket/dir/input-dir/data.csv' - but will not upload: - * 'my-input.csv' - * 'my-dir/input.csv' - To match all files within the directory "input" and upload them into - "s3://my-bucket/input", provide local_prefix = "input/" and - s3_prefix = "s3://my-bucket/input/" + Example: + local_prefix = "input", s3_prefix = "s3://my-bucket/dir/input" will upload: + * 'input.csv' to 's3://my-bucket/dir/input.csv' + * 'input-2.csv' to 's3://my-bucket/dir/input-2.csv' + * 'input/data.txt' to 's3://my-bucket/dir/input/data.txt' + * 'input-dir/data.csv' to 's3://my-bucket/dir/input-dir/data.csv' + but will not upload: + * 'my-input.csv' + * 'my-dir/input.csv' + To match all files within the directory "input" and upload them into + "s3://my-bucket/input", provide local_prefix = "input/" and + s3_prefix = "s3://my-bucket/input/" """ # support absolute paths if Path(local_prefix).is_absolute(): @@ -374,9 +404,6 @@ def download_from_s3(self, s3_uri: str, filename: str) -> None: Args: s3_uri (str): The S3 uri from where the file will be downloaded. filename (str): filename to save the file to. - - Returns: - None """ bucket, key = self.parse_s3_uri(s3_uri) self.s3_client.download_file(bucket, key, filename) @@ -415,7 +442,7 @@ def copy_s3_directory(self, source_s3_path: str, destination_s3_path: str) -> No Args: source_s3_path (str): S3 URI pointing to the directory to be copied. destination_s3_path (str): S3 URI where the contents of the source_s3_path - directory will be copied to. + directory will be copied to. """ if source_s3_path == destination_s3_path: return @@ -461,7 +488,7 @@ def list_keys(self, bucket: str, prefix: str) -> List[str]: keys += [obj["Key"] for obj in list_objects["Contents"]] return keys - def default_bucket(self): + def default_bucket(self) -> str: """ 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 @@ -483,7 +510,7 @@ def default_bucket(self): self._default_bucket = default_bucket return self._default_bucket - def _create_s3_bucket_if_it_does_not_exist(self, bucket_name, region): + def _create_s3_bucket_if_it_does_not_exist(self, bucket_name: str, region: str) -> None: """Creates an S3 Bucket if it does not exist. Also swallows a few common exceptions that indicate that the bucket already exists or that it is being created. @@ -560,8 +587,7 @@ def _create_s3_bucket_if_it_does_not_exist(self, bucket_name, region): 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. @@ -578,20 +604,20 @@ def search_devices( types: Optional[List[str]] = None, 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 all the filters `arns`, `names`, `types`, `statuses`, `provider_names`. Args: - arns (List[str], optional): device ARN list, default is `None`. - names (List[str], optional): device name list, default is `None`. - types (List[str], optional): device type list, default is `None`. - statuses (List[str], optional): device status list, default is `None`. - provider_names (List[str], optional): provider name list, default is `None`. + arns (Optional[List[str]]): device ARN list, default is `None`. + names (Optional[List[str]]): device name list, default is `None`. + types (Optional[List[str]]): device type list, default is `None`. + statuses (Optional[List[str]]): device status list, default is `None`. + provider_names (Optional[List[str]]): provider name list, default is `None`. Returns: - List[Dict[str, Any]: The response from the Amazon Braket `SearchDevices` operation. + List[Dict[str, Any]]: The response from the Amazon Braket `SearchDevices` operation. """ filters = [] if arns: @@ -613,7 +639,14 @@ def search_devices( return results @staticmethod - def is_s3_uri(string: str): + def is_s3_uri(string: str) -> bool: + """Determines if a given string is an S3 URI. + Args: + string (str): the string to check. + + Returns: + bool: Returns True if the given string is an S3 URI. + """ try: AwsSession.parse_s3_uri(string) except ValueError: @@ -621,7 +654,7 @@ def is_s3_uri(string: str): return True @staticmethod - def parse_s3_uri(s3_uri: str) -> (str, str): + def parse_s3_uri(s3_uri: str) -> Tuple[str, str]: """ Parse S3 URI to get bucket and key @@ -629,7 +662,7 @@ def parse_s3_uri(s3_uri: str) -> (str, str): s3_uri (str): S3 URI. Returns: - (str, str): Bucket and Key tuple. + Tuple[str, str]: Bucket and Key tuple. Raises: ValueError: Raises a ValueError if the provided string is not @@ -649,8 +682,9 @@ def parse_s3_uri(s3_uri: str) -> (str, str): raise ValueError(f"Not a valid S3 uri: {s3_uri}") @staticmethod - def construct_s3_uri(bucket: str, *dirs: str): - """ + def construct_s3_uri(bucket: str, *dirs: str) -> str: + """Create an S3 URI given a bucket and path. + Args: bucket (str): S3 URI. *dirs (str): directories to be appended in the resulting S3 URI @@ -679,13 +713,13 @@ def describe_log_streams( Args: log_group (str): Name of the log group. log_stream_prefix (str): Prefix for log streams to include. - limit (int, optional): Limit for number of log streams returned. + limit (int): Limit for number of log streams returned. default is 50. - next_token (optional, str): The token for the next set of items to return. + next_token (Optional[str]): The token for the next set of items to return. Would have been received in a previous call. Returns: - dict: Dicionary containing logStreams and nextToken + Dict[str, Any]: Dicionary containing logStreams and nextToken """ log_stream_args = { "logGroupName": log_group, @@ -718,11 +752,11 @@ def get_log_events( start_time (int): Timestamp that indicates a start time to include log events. start_from_head (bool): Bool indicating to return oldest events first. default is True. - next_token (optional, str): The token for the next set of items to return. + next_token (Optional[str]): The token for the next set of items to return. Would have been received in a previous call. Returns: - dict: Dicionary containing events, nextForwardToken, and nextBackwardToken + Dict[str, Any]: Dicionary containing events, nextForwardToken, and nextBackwardToken """ log_events_args = { "logGroupName": log_group, @@ -745,9 +779,9 @@ def copy_session( Creates a new AwsSession based on the region. Args: - region (str): Name of the region. Default = `None`. - max_connections (int): The maximum number of connections in the - Boto3 connection pool. Default = `None`. + region (Optional[str]): Name of the region. Default = `None`. + max_connections (Optional[int]): The maximum number of connections in the + Boto3 connection pool. Default = `None`. Returns: AwsSession: based on the region and boto config parameters. diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index 343234d2..29f24d63 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -37,7 +37,7 @@ def __init__( Args: angle (Union[FreeParameterExpression, float]): The angle of the gate in radians or expression representation. - qubit_count (int, optional): The number of qubits that this gate interacts with. + qubit_count (Optional[int]): The number of qubits that this gate interacts with. ascii_symbols (Sequence[str]): ASCII string symbols for the gate. These are used when 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. @@ -80,11 +80,7 @@ def angle(self) -> Union[FreeParameterExpression, float]: return self._parameters[0] def bind_values(self, **kwargs) -> AngledGate: - """ - Takes in parameters and attempts to assign them to values. - - Args: - **kwargs: The parameters that are being assigned. + """Takes in parameters and attempts to assign them to values. Returns: AngledGate: A new Gate of the same type with the requested parameters bound. diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index a72b10be..9438358a 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.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 + from typing import List, Tuple, 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 @@ -26,7 +29,7 @@ class AsciiCircuitDiagram(CircuitDiagram): """Builds ASCII string circuit diagrams.""" @staticmethod - def build_diagram(circuit) -> str: + def build_diagram(circuit: cir.Circuit) -> str: """ Build an ASCII string circuit diagram. @@ -106,8 +109,7 @@ def _ascii_group_items( items (List[Union[Instruction, ResultType]]): list of instructions or result types Returns: - List[(QubitSet, List[Union[Instruction, ResultType]])]: list of grouped instructions - or result types + List[Tuple[QubitSet, List[Instruction]]]: list of grouped instructions or result types. """ groupings = [] for item in items: @@ -151,8 +153,9 @@ def _categorize_result_types( result_types (List[ResultType]): list of result types Returns: - Tuple: first element is a list of result types without `target` attribute; - second element is a list of result types with `target` attribute + 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 = [] diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 92764469..7f4f08b3 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -15,7 +15,7 @@ import warnings from numbers import Number -from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar, Union +from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar, Union import numpy as np @@ -80,8 +80,7 @@ def register_subroutine(cls, func: SubroutineCallable) -> None: is the name of `func`. Args: - func (Callable[..., Union[Instruction, Iterable[Instruction], ResultType, - Iterable[ResultType]]): The function of the subroutine to add to the class. + func (SubroutineCallable): The function of the subroutine to add to the class. Examples: >>> def h_on_all(target): @@ -113,9 +112,6 @@ def __init__(self, addable: AddableTypes = None, *args, **kwargs): Args: addable (AddableTypes): The item(s) to add to self. Default = None. - *args: Variable length argument list. Supports any arguments that `add()` offers. - **kwargs: Arbitrary keyword arguments. Supports any keyword arguments that `add()` - offers. Raises: TypeError: If `addable` is an unsupported type. @@ -162,12 +158,15 @@ def result_types(self) -> List[ResultType]: @property def basis_rotation_instructions(self) -> List[Instruction]: - """List[Instruction]: Get a list of basis rotation instructions in the circuit. - These basis rotation instructions are added if result types are requested for - an observable other than Pauli-Z. + """Gets a list of basis rotation instructions. - This only makes sense if all observables are simultaneously measurable; - if not, this method will return an empty list. + Returns: + List[Instruction]: Get a list of basis rotation instructions in the circuit. + These basis rotation instructions are added if result types are requested for + an observable other than Pauli-Z. + + This only makes sense if all observables are simultaneously measurable; + if not, this method will return an empty list. """ # Note that basis_rotation_instructions can change each time a new instruction # is added to the circuit because `self._moments.qubits` would change @@ -189,7 +188,9 @@ def basis_rotation_instructions(self) -> List[Instruction]: return basis_rotation_instructions @staticmethod - def _observable_to_instruction(observable: Observable, target_list: List[int]): + def _observable_to_instruction( + observable: Observable, target_list: List[int] + ) -> List[Instruction]: return [Instruction(gate, target_list) for gate in observable.basis_rotation_gates] @property @@ -199,7 +200,10 @@ def moments(self) -> Moments: @property def qubit_count(self) -> int: - """Get the qubit count for this circuit. Note that this includes observables.""" + """Get the qubit count for this circuit. Note that this includes observables. + Returns: + int: The qubit count for this circuit. + """ all_qubits = self._moments.qubits.union(self._qubit_observable_set) return len(all_qubits) @@ -229,22 +233,23 @@ def add_result_type( Args: result_type (ResultType): `ResultType` to add into `self`. - target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the + target (QubitSetInput): Target qubits for the `result_type`. Default = `None`. - target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of + target_mapping (Dict[QubitInput, QubitInput]): A dictionary of qubit mappings to apply to the `result_type.target`. Key is the qubit in `result_type.target` and the value is what the key will be changed to. Default = `None`. - Note: target and target_mapping will only be applied to those requested result types with - the attribute `target`. The result_type will be appended to the end of the dict keys of - `circuit.result_types` only if it does not already exist in `circuit.result_types` - Returns: Circuit: self + Note: + Target and target_mapping will only be applied to those requested result types with + the attribute `target`. The result_type will be appended to the end of the dict keys of + `circuit.result_types` only if it does not already exist in `circuit.result_types` + Raises: TypeError: If both `target_mapping` and `target` are supplied. @@ -380,11 +385,11 @@ def add_instruction( Args: instruction (Instruction): `Instruction` to add into `self`. - target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the + target (QubitSetInput): Target qubits for the `instruction`. If a single qubit gate, an instruction is created for every index in `target`. Default = `None`. - target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of + target_mapping (Dict[QubitInput, QubitInput]): 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`. @@ -450,7 +455,7 @@ def _check_for_params(self, instruction: Instruction) -> bool: Args: instruction (Instruction): The instruction to check for a - :class:{FreeParameterExpression}. + :class:{FreeParameterExpression}. Returns: bool: Whether an object is parameterized. @@ -471,11 +476,11 @@ def add_circuit( Args: circuit (Circuit): Circuit to add into self. - target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the + target (QubitSetInput): Target qubits for the supplied circuit. This is a macro over `target_mapping`; `target` is converted to a `target_mapping` by zipping together a sorted `circuit.qubits` and `target`. Default = `None`. - target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of + target_mapping (Dict[QubitInput, QubitInput]): A dictionary of qubit mappings to apply to the qubits of `circuit.instructions`. Key is the qubit to map, and the value is what to change it to. Default = `None`. @@ -548,11 +553,11 @@ def add_verbatim_box( Args: verbatim_circuit (Circuit): Circuit to add into self. - target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the + target (QubitSetInput): Target qubits for the supplied circuit. This is a macro over `target_mapping`; `target` is converted to a `target_mapping` by zipping together a sorted `circuit.qubits` and `target`. Default = `None`. - target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of + target_mapping (Dict[QubitInput, QubitInput]): A dictionary of qubit mappings to apply to the qubits of `circuit.instructions`. Key is the qubit to map, and the value is what to change it to. Default = `None`. @@ -636,11 +641,11 @@ def apply_gate_noise( Args: noise (Union[Type[Noise], Iterable[Type[Noise]]]): Noise channel(s) to be applied - to the circuit. - target_gates (Union[Type[Gate], Iterable[Type[Gate]], optional]): Gate class or + to the circuit. + target_gates (Optional[Union[Type[Gate], Iterable[Type[Gate]]]]): Gate class or List of Gate classes which `noise` is applied to. Default=None. - target_unitary (np.ndarray): matrix of the target unitary gates. Default=None. - target_qubits (Union[QubitSetInput, optional]): Index or indices of qubit(s). + target_unitary (ndarray): matrix of the target unitary gates. Default=None. + target_qubits (Optional[QubitSetInput]): Index or indices of qubit(s). Default=None. Returns: @@ -764,8 +769,8 @@ def apply_initialization_noise( Args: noise (Union[Type[Noise], Iterable[Type[Noise]]]): Noise channel(s) to be applied - to the circuit. - target_qubits (Union[QubitSetInput, optional]): Index or indices of qubit(s). + to the circuit. + target_qubits (Optional[QubitSetInput]): Index or indices of qubit(s). Default=None. Returns: @@ -826,7 +831,7 @@ def make_bound_circuit(self, param_values: Dict[str, Number], strict: bool = Fal Args: param_values (Dict[str, Number]): A mapping of FreeParameter names to a value to assign to them. - strict (bool, optional): If True, raises a ValueError if none of the FreeParameters + strict (bool): If True, raises a ValueError if none of the FreeParameters in param_values appear in the circuit. False by default." Returns: @@ -837,7 +842,7 @@ def make_bound_circuit(self, param_values: Dict[str, Number], strict: bool = Fal self._validate_parameters(param_values) return self._use_parameter_value(param_values) - def _validate_parameters(self, parameter_values: Dict[str, Number]): + def _validate_parameters(self, parameter_values: Dict[str, Number]) -> None: """ This runs a check to see that the parameters are in the Circuit. @@ -885,12 +890,12 @@ def _use_parameter_value(self, param_values: Dict[str, Number]) -> Circuit: return fixed_circ @staticmethod - def _validate_parameter_value(val): + def _validate_parameter_value(val: Any) -> None: """ Validates the value being used is a Number. Args: - val: The value be verified. + val (Any): The value be verified. Raises: ValueError: If the value is not a Number @@ -914,8 +919,8 @@ def apply_readout_noise( Args: noise (Union[Type[Noise], Iterable[Type[Noise]]]): Noise channel(s) to be applied - to the circuit. - target_qubits (Union[QubitSetInput, optional]): Index or indices of qubit(s). + to the circuit. + target_qubits (Optional[QubitSetInput]): Index or indices of qubit(s). Default=None. Returns: @@ -988,8 +993,6 @@ def add(self, addable: AddableTypes, *args, **kwargs) -> Circuit: Args: addable (AddableTypes): The item(s) to add to self. Default = `None`. - *args: Variable length argument list. - **kwargs: Arbitrary keyword arguments. Returns: Circuit: self @@ -1017,7 +1020,7 @@ def add(self, addable: AddableTypes, *args, **kwargs) -> Circuit: >>> circ = Circuit().add(bell_pair, [4,5]) """ - def _flatten(addable): + def _flatten(addable: Union[Iterable, AddableTypes]) -> AddableTypes: if isinstance(addable, Iterable): for item in addable: yield from _flatten(item) @@ -1054,12 +1057,12 @@ def adjoint(self) -> Circuit: circ.add_result_type(result_type) return circ - def diagram(self, circuit_diagram_class=AsciiCircuitDiagram) -> str: + def diagram(self, circuit_diagram_class: Type = AsciiCircuitDiagram) -> str: """ Get a diagram for the current circuit. Args: - circuit_diagram_class (Class, optional): A `CircuitDiagram` class that builds the + circuit_diagram_class (Type): A `CircuitDiagram` class that builds the diagram for this circuit. Default = `AsciiCircuitDiagram`. Returns: @@ -1084,7 +1087,7 @@ def to_ir( supplied must correspond to the supplied `ir_type`. Defaults to None. Returns: - (Union[OpenQasmProgram, JaqcdProgram]): A representation of the circuit in the + Union[OpenQasmProgram, JaqcdProgram]: A representation of the circuit in the `ir_type` format. Raises: @@ -1172,9 +1175,9 @@ def as_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. *Note*: For an empty circuit, an empty numpy array is - returned (`array([], dtype=complex128)`) + ndarray: A numpy array with shape (2^qubit_count, 2^qubit_count) representing the + circuit as a unitary. *Note*: For an empty circuit, an empty numpy array is + returned (`array([], dtype=complex128)`) Warnings: This method has been deprecated, please use to_unitary() instead. @@ -1264,7 +1267,7 @@ def observables_simultaneously_measurable(self) -> bool: """ return self._observables_simultaneously_measurable - def _encounter_noncommuting_observable(self): + def _encounter_noncommuting_observable(self) -> None: self._observables_simultaneously_measurable = False # No longer simultaneously measurable, so no need to track self._qubit_observable_mapping.clear() @@ -1311,14 +1314,13 @@ def __eq__(self, other): ) return NotImplemented - def __call__(self, arg=None, **kwargs) -> Circuit: + def __call__(self, arg: Any = None, **kwargs) -> Circuit: """ Implements the call function to easily make a bound Circuit. Args: - arg: A value to bind to all parameters. Defaults to None and + arg (Any): A value to bind to all parameters. Defaults to None and can be overridden if the parameter is in kwargs. - **kwargs: the named parameters to have their value bound. Returns: Circuit: A circuit with the specified parameters bound. @@ -1332,14 +1334,17 @@ def __call__(self, arg=None, **kwargs) -> Circuit: return self.make_bound_circuit(param_values) -def subroutine(register=False): +def subroutine(register: bool = False) -> Callable: """ Subroutine is a function that returns instructions, result types, or circuits. Args: - register (bool, optional): If `True`, adds this subroutine into the `Circuit` class. + register (bool): If `True`, adds this subroutine into the `Circuit` class. Default = `False`. + Returns: + Callable: The subroutine function. + Examples: >>> @circuit.subroutine(register=True) >>> def bell_circuit(): @@ -1353,9 +1358,9 @@ def subroutine(register=False): Instruction('operator': 'H', 'target': QubitSet(Qubit(1),)) """ - def subroutine_function_wrapper(func: Callable[..., SubroutineReturn]) -> SubroutineReturn: + def _subroutine_function_wrapper(func: Callable[..., SubroutineReturn]) -> SubroutineReturn: if register: Circuit.register_subroutine(func) return func - return subroutine_function_wrapper + return _subroutine_function_wrapper diff --git a/src/braket/circuits/compiler_directive.py b/src/braket/circuits/compiler_directive.py index d28a1d63..cd40a596 100644 --- a/src/braket/circuits/compiler_directive.py +++ b/src/braket/circuits/compiler_directive.py @@ -51,7 +51,7 @@ def to_ir( ir_type: IRType = IRType.JAQCD, serialization_properties: SerializationProperties = None, **kwargs, - ): + ) -> Any: """Returns IR object of the compiler directive. Args: @@ -61,10 +61,9 @@ def to_ir( serialization_properties (SerializationProperties): 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. - **kwargs: Keyword arguments Returns: - IR object of the compiler directive. + Any: IR object of the compiler directive. Raises: ValueError: If the supplied `ir_type` is not supported. diff --git a/src/braket/circuits/compiler_directives.py b/src/braket/circuits/compiler_directives.py index 36d770de..2533537f 100644 --- a/src/braket/circuits/compiler_directives.py +++ b/src/braket/circuits/compiler_directives.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 typing import Any + import braket.ir.jaqcd as ir from braket.circuits.compiler_directive import CompilerDirective @@ -27,7 +29,7 @@ def __init__(self): def counterpart(self) -> CompilerDirective: return EndVerbatimBox() - def _to_jaqcd(self, *args, **kwargs): + def _to_jaqcd(self, *args, **kwargs) -> Any: return ir.StartVerbatimBox.construct() def _to_openqasm(self) -> str: @@ -46,7 +48,7 @@ def __init__(self): def counterpart(self) -> CompilerDirective: return StartVerbatimBox() - def _to_jaqcd(self, *args, **kwargs): + def _to_jaqcd(self, *args, **kwargs) -> Any: return ir.EndVerbatimBox.construct() def _to_openqasm(self) -> str: diff --git a/src/braket/circuits/free_parameter.py b/src/braket/circuits/free_parameter.py index 406fdc84..69030e57 100644 --- a/src/braket/circuits/free_parameter.py +++ b/src/braket/circuits/free_parameter.py @@ -14,7 +14,7 @@ from __future__ import annotations from numbers import Number -from typing import Dict +from typing import Dict, Union from sympy import Symbol @@ -52,17 +52,17 @@ def name(self) -> str: """ return self._name.name - def subs(self, parameter_values: Dict[str, Number]): + def subs(self, parameter_values: Dict[str, Number]) -> Union[FreeParameter, Number]: """ Substitutes a value in if the parameter exists within the mapping. Args: parameter_values (Dict[str, Number]): A mapping of parameter to its - corresponding value. - - Returns: The substituted value if this parameter is in parameter_values, - otherwise returns self + corresponding value. + Returns: + 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 @@ -77,12 +77,12 @@ def __eq__(self, other): return self._name == other._name return False - def __repr__(self): + def __repr__(self) -> str: """ The representation of the :class:'FreeParameter'. Returns: - The name of the class:'FreeParameter' to represent the class. + str: The name of the class:'FreeParameter' to represent the class. """ return self.name diff --git a/src/braket/circuits/free_parameter_expression.py b/src/braket/circuits/free_parameter_expression.py index a5dbcf17..d4062b64 100644 --- a/src/braket/circuits/free_parameter_expression.py +++ b/src/braket/circuits/free_parameter_expression.py @@ -10,10 +10,10 @@ # 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 -from typing import Dict +from typing import Dict, Union from sympy import Expr, sympify @@ -28,7 +28,7 @@ class FreeParameterExpression: be substituted prior to execution. """ - def __init__(self, expression): + def __init__(self, expression: Union[FreeParameterExpression, Number, Expr]): """ Initializes a FreeParameterExpression. Best practice is to initialize using FreeParameters and Numbers. Not meant to be initialized directly. @@ -36,7 +36,7 @@ def __init__(self, expression): Below are examples of how FreeParameterExpressions should be made. Args: - expression: The expression to use. + expression (Union[FreeParameterExpression, Number, Expr]): The expression to use. Examples: >>> expression_1 = FreeParameter("theta") * FreeParameter("alpha") @@ -50,24 +50,27 @@ def __init__(self, expression): raise NotImplementedError @property - def expression(self): - """ + def expression(self) -> Union[Number, Expr]: + """Gets the expression. Returns: - The expression for the FreeParameterExpression. + Union[Number, Expr]: The expression for the FreeParameterExpression. """ return self._expression - def subs(self, parameter_values: Dict[str, Number]): + def subs( + self, parameter_values: Dict[str, Number] + ) -> Union[FreeParameterExpression, Number, Expr]: """ Similar to a substitution in Sympy. Parameters are swapped for corresponding values or expressions from the dictionary. Args: parameter_values (Dict[str, Number]): A mapping of parameters to their corresponding - values to be assigned. + values to be assigned. - Returns: A numerical value if there are no symbols left in the expression otherwise - returns a new FreeParameterExpression. + Returns: + 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() for key, val in parameter_values.items(): @@ -131,6 +134,6 @@ def __repr__(self) -> str: The representation of the :class:'FreeParameterExpression'. Returns: - The expression of the class:'FreeParameterExpression' to represent the class. + str: The expression of the class:'FreeParameterExpression' to represent the class. """ return repr(self.expression) diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index 071c5ea2..d8cb302f 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -34,7 +34,7 @@ class Gate(QuantumOperator): def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): """ Args: - qubit_count (int, optional): Number of qubits this gate interacts with. + 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 printing a diagram of circuits. Length must be the same as `qubit_count`, and index ordering is expected to correlate with target ordering on the instruction. @@ -74,7 +74,7 @@ def to_ir( while serializing the object to the IR representation. The serialization properties supplied must correspond to the supplied `ir_type`. Defaults to None. Returns: - IR object of the quantum operator and target + Any: IR object of the quantum operator and target Raises: ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization @@ -136,7 +136,7 @@ def __repr__(self): return f"{self.name}('qubit_count': {self._qubit_count})" @classmethod - def register_gate(cls, gate: Type[Gate]): + def register_gate(cls, gate: Type[Gate]) -> None: """Register a gate implementation by adding it into the Gate class. Args: diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 39d3e607..cbfe8b71 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.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 Iterable, List, Union +from typing import Any, Iterable, List, Union import numpy as np from sympy import Float @@ -52,12 +52,12 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [H()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.H.construct(target=target[0]) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return f"h {target_qubit};" @@ -74,7 +74,7 @@ def h(target: QubitSetInput) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + target (QubitSetInput): Target qubit(s) Returns: Iterable[Instruction]: `Iterable` of H instructions. @@ -98,12 +98,12 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [I()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.I.construct(target=target[0]) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return f"i {target_qubit};" @@ -120,7 +120,7 @@ def i(target: QubitSetInput) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + target (QubitSetInput): Target qubit(s) Returns: Iterable[Instruction]: `Iterable` of I instructions. @@ -144,12 +144,12 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [X()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.X.construct(target=target[0]) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return f"x {target_qubit};" @@ -166,7 +166,7 @@ def x(target: QubitSetInput) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + target (QubitSetInput): Target qubit(s) Returns: Iterable[Instruction]: `Iterable` of X instructions. @@ -190,7 +190,7 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [Y()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Y.construct(target=target[0]) def _to_openqasm( @@ -212,7 +212,7 @@ def y(target: QubitSetInput) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + target (QubitSetInput): Target qubit(s) Returns: Iterable[Instruction]: `Iterable` of Y instructions. @@ -236,7 +236,7 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [Z()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Z.construct(target=target[0]) def _to_openqasm( @@ -258,7 +258,7 @@ def z(target: QubitSetInput) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + target (QubitSetInput): Target qubit(s) Returns: Iterable[Instruction]: `Iterable` of Z instructions. @@ -282,7 +282,7 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [Si()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.S.construct(target=target[0]) def _to_openqasm( @@ -304,7 +304,7 @@ def s(target: QubitSetInput) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + target (QubitSetInput): Target qubit(s) Returns: Iterable[Instruction]: `Iterable` of S instructions. @@ -328,7 +328,7 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [S()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Si.construct(target=target[0]) def _to_openqasm( @@ -350,7 +350,7 @@ def si(target: QubitSetInput) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + target (QubitSetInput): Target qubit(s) Returns: Iterable[Instruction]: Iterable of Si instructions. @@ -374,12 +374,12 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [Ti()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.T.construct(target=target[0]) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return f"t {target_qubit};" @@ -396,7 +396,7 @@ def t(target: QubitSetInput) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + target (QubitSetInput): Target qubit(s) Returns: Iterable[Instruction]: `Iterable` of T instructions. @@ -420,12 +420,12 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [T()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Ti.construct(target=target[0]) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return f"ti {target_qubit};" @@ -442,7 +442,7 @@ def ti(target: QubitSetInput) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + target (QubitSetInput): Target qubit(s) Returns: Iterable[Instruction]: `Iterable` of Ti instructions. @@ -466,12 +466,12 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [Vi()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.V.construct(target=target[0]) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return f"v {target_qubit};" @@ -488,7 +488,7 @@ def v(target: QubitSetInput) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + target (QubitSetInput): Target qubit(s) Returns: Iterable[Instruction]: `Iterable` of V instructions. @@ -512,12 +512,12 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [V()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Vi.construct(target=target[0]) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return f"vi {target_qubit};" @@ -534,7 +534,7 @@ def vi(target: QubitSetInput) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + target (QubitSetInput): Target qubit(s) Returns: Iterable[Instruction]: `Iterable` of Vi instructions. @@ -566,16 +566,20 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=[angled_ascii_characters("Rx", angle)], ) - def _to_jaqcd(self, target: QubitSet, **kwargs): + def _to_jaqcd(self, target: QubitSet, **kwargs) -> Any: return ir.Rx.construct(target=target[0], angle=self.angle) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return f"rx({self.angle}) {target_qubit};" def to_matrix(self) -> np.ndarray: + """Returns a matrix representation of this gate. + Returns: + ndarray: The matrix representation of this gate. + """ cos = np.cos(self.angle / 2) sin = np.sin(self.angle / 2) return np.array([[cos, -1j * sin], [-1j * sin, cos]], dtype=complex) @@ -584,7 +588,7 @@ def to_matrix(self) -> np.ndarray: def fixed_qubit_count() -> int: return 1 - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> AngledGate: return get_angle(self, **kwargs) @staticmethod @@ -595,7 +599,7 @@ def rx( """Registers this function into the circuit class. Args: - target (Qubit or int): Target qubit index. + target (QubitInput): Target qubit index. angle (Union[FreeParameterExpression, float]): Angle in radians. Returns: @@ -624,16 +628,20 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=[angled_ascii_characters("Ry", angle)], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Ry.construct(target=target[0], angle=self.angle) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return f"ry({self.angle}) {target_qubit};" def to_matrix(self) -> np.ndarray: + """Returns a matrix representation of this gate. + Returns: + ndarray: The matrix representation of this gate. + """ cos = np.cos(self.angle / 2) sin = np.sin(self.angle / 2) return np.array([[cos, -sin], [+sin, cos]], dtype=complex) @@ -642,7 +650,7 @@ def to_matrix(self) -> np.ndarray: def fixed_qubit_count() -> int: return 1 - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> AngledGate: return get_angle(self, **kwargs) @staticmethod @@ -653,7 +661,7 @@ def ry( """Registers this function into the circuit class. Args: - target (Qubit or int): Target qubit index. + target (QubitInput): Target qubit index. angle (Union[FreeParameterExpression, float]): Angle in radians. Returns: @@ -682,12 +690,12 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=[angled_ascii_characters("Rz", angle)], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Rz.construct(target=target[0], angle=self.angle) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return f"rz({self.angle}) {target_qubit};" @@ -696,7 +704,7 @@ def to_matrix(self) -> np.ndarray: [[np.exp(-1j * self.angle / 2), 0], [0, np.exp(1j * self.angle / 2)]], dtype=complex ) - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> AngledGate: return get_angle(self, **kwargs) @staticmethod @@ -711,7 +719,7 @@ def rz( """Registers this function into the circuit class. Args: - target (Qubit or int): Target qubit index. + target (QubitInput): Target qubit index. angle (Union[FreeParameterExpression, float]): angle in radians. Returns: @@ -740,12 +748,12 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=[angled_ascii_characters("PHASE", angle)], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.PhaseShift.construct(target=target[0], angle=self.angle) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) # alternatively, "ctrl @ phase({self.angle}) {target_qubit};" return f"phaseshift({self.angle}) {target_qubit};" @@ -753,7 +761,7 @@ def _to_openqasm( def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, np.exp(1j * self.angle)]], dtype=complex) - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> AngledGate: return get_angle(self, **kwargs) @staticmethod @@ -768,7 +776,7 @@ def phaseshift( """Registers this function into the circuit class. Args: - target (Qubit or int): Target qubit index. + target (QubitInput): Target qubit index. angle (Union[FreeParameterExpression, float]): angle in radians. Returns: @@ -795,12 +803,12 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [CNot()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CNot.construct(control=target[0], target=target[1]) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: control_qubit = serialization_properties.format_target(int(target[0])) target_qubit = serialization_properties.format_target(int(target[1])) return f"cnot {control_qubit}, {target_qubit};" @@ -826,8 +834,8 @@ def cnot(control: QubitInput, target: QubitInput) -> Instruction: """Registers this function into the circuit class. Args: - control (Qubit or int): Control qubit index. - target (Qubit or int): Target qubit index. + control (QubitInput): Control qubit index. + target (QubitInput): Target qubit index. Returns: Instruction: CNot instruction. @@ -850,12 +858,12 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [Swap()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Swap.construct(targets=[target[0], target[1]]) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit_0 = serialization_properties.format_target(int(target[0])) target_qubit_1 = serialization_properties.format_target(int(target[1])) return f"swap {target_qubit_0}, {target_qubit_1};" @@ -881,8 +889,8 @@ def swap(target1: QubitInput, target2: QubitInput) -> Instruction: """Registers this function into the circuit class. Args: - target1 (Qubit or int): Target qubit 1 index. - target2 (Qubit or int): Target qubit 2 index. + target1 (QubitInput): Target qubit 1 index. + target2 (QubitInput): Target qubit 2 index. Returns: Instruction: Swap instruction. @@ -905,12 +913,12 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [self, self, self] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.ISwap.construct(targets=[target[0], target[1]]) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit_0 = serialization_properties.format_target(int(target[0])) target_qubit_1 = serialization_properties.format_target(int(target[1])) return f"iswap {target_qubit_0}, {target_qubit_1};" @@ -936,8 +944,8 @@ def iswap(target1: QubitInput, target2: QubitInput) -> Instruction: """Registers this function into the circuit class. Args: - target1 (Qubit or int): Target qubit 1 index. - target2 (Qubit or int): Target qubit 2 index. + target1 (QubitInput): Target qubit 1 index. + target2 (QubitInput): Target qubit 2 index. Returns: Instruction: ISwap instruction. @@ -968,12 +976,12 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.PSwap.construct(targets=[target[0], target[1]], angle=self.angle) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit_0 = serialization_properties.format_target(int(target[0])) target_qubit_1 = serialization_properties.format_target(int(target[1])) return f"pswap({self.angle}) {target_qubit_0}, {target_qubit_1};" @@ -989,7 +997,7 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> AngledGate: return get_angle(self, **kwargs) @staticmethod @@ -998,12 +1006,14 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def pswap(target1: QubitInput, target2: QubitInput, angle: float) -> Instruction: + def pswap( + target1: QubitInput, target2: QubitInput, angle: Union[FreeParameterExpression, float] + ) -> Instruction: """Registers this function into the circuit class. Args: - target1 (Qubit or int): Target qubit 1 index. - target2 (Qubit or int): Target qubit 2 index. + target1 (QubitInput): Target qubit 1 index. + target2 (QubitInput): Target qubit 2 index. angle (Union[FreeParameterExpression, float]): angle in radians. Returns: @@ -1037,17 +1047,21 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.XY.construct(targets=[target[0], target[1]], angle=self.angle) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit_1 = serialization_properties.format_target(int(target[0])) target_qubit_2 = serialization_properties.format_target(int(target[1])) return f"xy({self.angle}) {target_qubit_1}, {target_qubit_2};" def to_matrix(self) -> np.ndarray: + """Returns a matrix representation of this gate. + Returns: + ndarray: The matrix representation of this gate. + """ cos = np.cos(self.angle / 2) sin = np.sin(self.angle / 2) return np.array( @@ -1060,7 +1074,7 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> AngledGate: return get_angle(self, **kwargs) @staticmethod @@ -1075,8 +1089,8 @@ def xy( """Registers this function into the circuit class. Args: - target1 (Qubit or int): Target qubit 1 index. - target2 (Qubit or int): Target qubit 2 index. + target1 (QubitInput): Target qubit 1 index. + target2 (QubitInput): Target qubit 2 index. angle (Union[FreeParameterExpression, float]): angle in radians. Returns: @@ -1105,12 +1119,12 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=["C", angled_ascii_characters("PHASE", angle)], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CPhaseShift.construct(control=target[0], target=target[1], angle=self.angle) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: control_qubit = serialization_properties.format_target(int(target[0])) target_qubit = serialization_properties.format_target(int(target[1])) return f"cphaseshift({self.angle}) {control_qubit}, {target_qubit};" @@ -1118,7 +1132,7 @@ def _to_openqasm( def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, 1.0, np.exp(1j * self.angle)]) - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> AngledGate: return get_angle(self, **kwargs) @staticmethod @@ -1133,8 +1147,8 @@ def cphaseshift( """Registers this function into the circuit class. Args: - control (Qubit or int): Control qubit index. - target (Qubit or int): Target qubit index. + control (QubitInput): Control qubit index. + target (QubitInput): Target qubit index. angle (Union[FreeParameterExpression, float]): angle in radians. Returns: @@ -1163,12 +1177,12 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=["C", angled_ascii_characters("PHASE00", angle)], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CPhaseShift00.construct(control=target[0], target=target[1], angle=self.angle) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: control_qubit = serialization_properties.format_target(int(target[0])) target_qubit = serialization_properties.format_target(int(target[1])) return f"cphaseshift00({self.angle}) {control_qubit}, {target_qubit};" @@ -1176,7 +1190,7 @@ def _to_openqasm( def to_matrix(self) -> np.ndarray: return np.diag([np.exp(1j * self.angle), 1.0, 1.0, 1.0]) - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> AngledGate: return get_angle(self, **kwargs) @staticmethod @@ -1191,8 +1205,8 @@ def cphaseshift00( """Registers this function into the circuit class. Args: - control (Qubit or int): Control qubit index. - target (Qubit or int): Target qubit index. + control (QubitInput): Control qubit index. + target (QubitInput): Target qubit index. angle (Union[FreeParameterExpression, float]): angle in radians. Returns: @@ -1221,12 +1235,12 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=["C", angled_ascii_characters("PHASE01", angle)], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CPhaseShift01.construct(control=target[0], target=target[1], angle=self.angle) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: control_qubit = serialization_properties.format_target(int(target[0])) target_qubit = serialization_properties.format_target(int(target[1])) return f"cphaseshift01({self.angle}) {control_qubit}, {target_qubit};" @@ -1234,7 +1248,7 @@ def _to_openqasm( def to_matrix(self) -> np.ndarray: return np.diag([1.0, np.exp(1j * self.angle), 1.0, 1.0]) - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> AngledGate: return get_angle(self, **kwargs) @staticmethod @@ -1249,8 +1263,8 @@ def cphaseshift01( """Registers this function into the circuit class. Args: - control (Qubit or int): Control qubit index. - target (Qubit or int): Target qubit index. + control (QubitInput): Control qubit index. + target (QubitInput): Target qubit index. angle (Union[FreeParameterExpression, float]): angle in radians. Returns: @@ -1279,12 +1293,12 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=["C", angled_ascii_characters("PHASE10", angle)], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CPhaseShift10.construct(control=target[0], target=target[1], angle=self.angle) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: control_qubit = serialization_properties.format_target(int(target[0])) target_qubit = serialization_properties.format_target(int(target[1])) return f"cphaseshift10({self.angle}) {control_qubit}, {target_qubit};" @@ -1292,7 +1306,7 @@ def _to_openqasm( def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, np.exp(1j * self.angle), 1.0]) - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> AngledGate: return get_angle(self, **kwargs) @staticmethod @@ -1307,8 +1321,8 @@ def cphaseshift10( """Registers this function into the circuit class. Args: - control (Qubit or int): Control qubit index. - target (Qubit or int): Target qubit index. + control (QubitInput): Control qubit index. + target (QubitInput): Target qubit index. angle (Union[FreeParameterExpression, float]): angle in radians. Returns: @@ -1332,12 +1346,12 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [self, self, self] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CV.construct(control=target[0], target=target[1]) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: control_qubit = serialization_properties.format_target(int(target[0])) target_qubit = serialization_properties.format_target(int(target[1])) return f"cv {control_qubit}, {target_qubit};" @@ -1363,8 +1377,8 @@ def cv(control: QubitInput, target: QubitInput) -> Instruction: """Registers this function into the circuit class. Args: - control (Qubit or int): Control qubit index. - target (Qubit or int): Target qubit index. + control (QubitInput): Control qubit index. + target (QubitInput): Target qubit index. Returns: Instruction: CV instruction. @@ -1387,12 +1401,12 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [CY()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CY.construct(control=target[0], target=target[1]) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[1])) control_qubit = serialization_properties.format_target(int(target[0])) return f"cy {control_qubit}, {target_qubit};" @@ -1418,8 +1432,8 @@ def cy(control: QubitInput, target: QubitInput) -> Instruction: """Registers this function into the circuit class. Args: - control (Qubit or int): Control qubit index. - target (Qubit or int): Target qubit index. + control (QubitInput): Control qubit index. + target (QubitInput): Target qubit index. Returns: Instruction: CY instruction. @@ -1442,12 +1456,12 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [CZ()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CZ.construct(control=target[0], target=target[1]) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[1])) control_qubit = serialization_properties.format_target(int(target[0])) return f"cz {control_qubit}, {target_qubit};" @@ -1465,8 +1479,8 @@ def cz(control: QubitInput, target: QubitInput) -> Instruction: """Registers this function into the circuit class. Args: - control (Qubit or int): Control qubit index. - target (Qubit or int): Target qubit index. + control (QubitInput): Control qubit index. + target (QubitInput): Target qubit index. Returns: Instruction: CZ instruction. @@ -1489,12 +1503,12 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [ECR()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.ECR.construct(targets=[target[0], target[1]]) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit_0 = serialization_properties.format_target(int(target[0])) target_qubit_1 = serialization_properties.format_target(int(target[1])) return f"ecr {target_qubit_0}, {target_qubit_1};" @@ -1519,8 +1533,8 @@ def ecr(target1: QubitInput, target2: QubitInput) -> Instruction: """Registers this function into the circuit class. Args: - target1 (Qubit or int): Target qubit 1 index. - target2 (Qubit or int): Target qubit 2 index. + target1 (QubitInput): Target qubit 1 index. + target2 (QubitInput): Target qubit 2 index. Returns: Instruction: ECR instruction. @@ -1553,17 +1567,21 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.XX.construct(targets=[target[0], target[1]], angle=self.angle) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit_1 = serialization_properties.format_target(int(target[0])) target_qubit_2 = serialization_properties.format_target(int(target[1])) return f"xx({self.angle}) {target_qubit_1}, {target_qubit_2};" def to_matrix(self) -> np.ndarray: + """Returns a matrix representation of this gate. + Returns: + ndarray: The matrix representation of this gate. + """ cos = np.cos(self.angle / 2) isin = 1.0j * np.sin(self.angle / 2) return np.array( @@ -1576,7 +1594,7 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> AngledGate: return get_angle(self, **kwargs) @staticmethod @@ -1591,8 +1609,8 @@ def xx( """Registers this function into the circuit class. Args: - target1 (Qubit or int): Target qubit 1 index. - target2 (Qubit or int): Target qubit 2 index. + target1 (QubitInput): Target qubit 1 index. + target2 (QubitInput): Target qubit 2 index. angle (Union[FreeParameterExpression, float]): angle in radians. Returns: @@ -1626,17 +1644,21 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.YY.construct(targets=[target[0], target[1]], angle=self.angle) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit_1 = serialization_properties.format_target(int(target[0])) target_qubit_2 = serialization_properties.format_target(int(target[1])) return f"yy({self.angle}) {target_qubit_1}, {target_qubit_2};" def to_matrix(self) -> np.ndarray: + """Returns a matrix representation of this gate. + Returns: + ndarray: The matrix representation of this gate. + """ cos = np.cos(self.angle / 2) isin = 1.0j * np.sin(self.angle / 2) return np.array( @@ -1649,7 +1671,7 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> AngledGate: return get_angle(self, **kwargs) @staticmethod @@ -1664,8 +1686,8 @@ def yy( """Registers this function into the circuit class. Args: - target1 (Qubit or int): Target qubit 1 index. - target2 (Qubit or int): Target qubit 2 index. + target1 (QubitInput): Target qubit 1 index. + target2 (QubitInput): Target qubit 2 index. angle (Union[FreeParameterExpression, float]): angle in radians. Returns: @@ -1699,12 +1721,12 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.ZZ.construct(targets=[target[0], target[1]], angle=self.angle) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit_1 = serialization_properties.format_target(int(target[0])) target_qubit_2 = serialization_properties.format_target(int(target[1])) return f"zz({self.angle}) {target_qubit_1}, {target_qubit_2};" @@ -1720,7 +1742,7 @@ def to_matrix(self) -> np.ndarray: dtype=complex, ) - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> AngledGate: return get_angle(self, **kwargs) @staticmethod @@ -1735,8 +1757,8 @@ def zz( """Registers this function into the circuit class. Args: - target1 (Qubit or int): Target qubit 1 index. - target2 (Qubit or int): Target qubit 2 index. + target1 (QubitInput): Target qubit 1 index. + target2 (QubitInput): Target qubit 2 index. angle (Union[FreeParameterExpression, float]): angle in radians. Returns: @@ -1763,12 +1785,12 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [CCNot()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CCNot.construct(controls=[target[0], target[1]], target=target[2]) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: control_qubit_0 = serialization_properties.format_target(int(target[0])) control_qubit_1 = serialization_properties.format_target(int(target[1])) target_qubit = serialization_properties.format_target(int(target[2])) @@ -1799,9 +1821,9 @@ def ccnot(control1: QubitInput, control2: QubitInput, target: QubitInput) -> Ins """Registers this function into the circuit class. Args: - control1 (Qubit or int): Control qubit 1 index. - control2 (Qubit or int): Control qubit 2 index. - target (Qubit or int): Target qubit index. + control1 (QubitInput): Control qubit 1 index. + control2 (QubitInput): Control qubit 2 index. + target (QubitInput): Target qubit index. Returns: Instruction: CCNot instruction. @@ -1824,12 +1846,12 @@ def __init__(self): def adjoint(self) -> List[Gate]: return [CSwap()] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CSwap.construct(control=target[0], targets=[target[1], target[2]]) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: control_qubit = serialization_properties.format_target(int(target[0])) target_qubit_0 = serialization_properties.format_target(int(target[1])) target_qubit_1 = serialization_properties.format_target(int(target[2])) @@ -1861,9 +1883,9 @@ def cswap(control: QubitInput, target1: QubitInput, target2: QubitInput) -> Inst """Registers this function into the circuit class. Args: - control (Qubit or int): Control qubit index - target1 (Qubit or int): Target qubit 1 index. - target2 (Qubit or int): Target qubit 2 index. + control (QubitInput): Control qubit index + target1 (QubitInput): Target qubit 1 index. + target2 (QubitInput): Target qubit 2 index. Returns: Instruction: CSwap instruction. @@ -1901,13 +1923,13 @@ def __init__(self, matrix: np.ndarray, display_name: str = "U"): super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) - def to_matrix(self): + def to_matrix(self) -> np.ndarray: return np.array(self._matrix) def adjoint(self) -> List[Gate]: return [Unitary(self._matrix.conj().T, display_name=f"({self.ascii_symbols})^†")] - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Unitary.construct( targets=[qubit for qubit in target], matrix=Unitary._transform_matrix_to_ir(self._matrix), @@ -1915,7 +1937,7 @@ def _to_jaqcd(self, target: QubitSet): def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: qubits = [serialization_properties.format_target(int(qubit)) for qubit in target] formatted_matrix = np.array2string( self._matrix, @@ -1932,7 +1954,7 @@ def __eq__(self, other): return False @staticmethod - def _transform_matrix_to_ir(matrix: np.ndarray): + def _transform_matrix_to_ir(matrix: np.ndarray) -> List: return [[[element.real, element.imag] for element in row] for row in matrix.tolist()] @staticmethod @@ -1982,16 +2004,16 @@ def angled_ascii_characters(gate: str, angle: Union[FreeParameterExpression, flo return f'{gate}({angle:{".2f" if isinstance(angle, (float, Float)) else ""}})' -def get_angle(self, **kwargs): +def get_angle(self: AngledGate, **kwargs) -> AngledGate: """ Gets the angle with all values substituted in that are requested. Args: - self: The subclass of AngledGate for which the angle is being obtained. - **kwargs: The named parameters that are being filled for a particular gate. + self (AngledGate): The subclass of AngledGate for which the angle is being obtained. Returns: - A new gate of the type of the AngledGate originally used with all angles updated. + AngledGate: A new gate of the type of the AngledGate originally used with all + angles updated. """ new_angle = ( self.angle.subs(kwargs) if isinstance(self.angle, FreeParameterExpression) else self.angle diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index 623d48d8..d0ceb286 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -13,7 +13,7 @@ from __future__ import annotations -from typing import Dict, List, Tuple +from typing import Any, Dict, List, Tuple from braket.circuits.compiler_directive import CompilerDirective from braket.circuits.gate import Gate @@ -38,8 +38,7 @@ def __init__(self, operator: InstructionOperator, target: QubitSetInput = None): Args: operator (InstructionOperator): Operator for the instruction. - target (int, Qubit, or iterable of int / Qubit): Target qubits that the operator is - applied to. + target (QubitSetInput): Target qubits that the operator is applied to. Default is None. Raises: ValueError: If `operator` is empty or any integer in `target` does not meet the `Qubit` @@ -106,7 +105,7 @@ def to_ir( self, ir_type: IRType = IRType.JAQCD, serialization_properties: SerializationProperties = None, - ): + ) -> Any: """ Converts the operator into the canonical intermediate representation. If the operator is passed in a request, this method is called before it is passed. @@ -117,6 +116,9 @@ def to_ir( serialization_properties (SerializationProperties): 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 instruction. """ return self._operator.to_ir( [int(qubit) for qubit in self._target], @@ -140,11 +142,10 @@ def copy( qubits. This is useful apply an instruction to a circuit and change the target qubits. Args: - target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of + target_mapping (Dict[QubitInput, QubitInput]): A dictionary of qubit mappings to apply to the target. Key is the qubit in this `target` and the value is what the key is changed to. Default = `None`. - target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the new - instruction. + target (QubitSetInput): Target qubits for the new instruction. Default is None. Returns: Instruction: A shallow copy of the instruction. diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index d21907d4..2ed6dfbd 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -15,6 +15,7 @@ from enum import Enum from typing import ( + Any, Dict, ItemsView, Iterable, @@ -169,6 +170,9 @@ def add(self, instructions: Iterable[Instruction], noise_index: int = 0) -> None Args: instructions (Iterable[Instruction]): Instructions to add to self. The instruction is added to the max time slice in which the instruction fits. + noise_index (int): the number of noise channels at the same moment. For gates, this + is the number of gate_noise channels associated with that gate. For all other noise + types, noise_index starts from 0; but for gate noise, it starts from 1. """ for instruction in instructions: self._add(instruction, noise_index) @@ -191,7 +195,7 @@ def _add(self, instruction: Instruction, noise_index: int = 0) -> None: self._qubits.update(instruction.target) self._depth = max(self._depth, time + 1) - def _update_qubit_times(self, 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 ] @@ -204,7 +208,14 @@ def _update_qubit_times(self, qubits): 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. + noise_index (int): The number of noise channels at the same moment. For gates, this + is the number of gate_noise channels associated with that gate. For all other noise + types, noise_index starts from 0; but for gate noise, it starts from 1. + """ qubit_range = instruction.target time = max(0, *[self._max_time_for_qubit(qubit) for qubit in qubit_range]) if input_type == MomentType.INITIALIZATION_NOISE: @@ -272,17 +283,20 @@ def items(self) -> ItemsView[MomentsKey, Instruction]: return self._moments.items() def values(self) -> ValuesView[Instruction]: - """Return a view of self's instructions.""" + """Return a view of self's instructions. + Returns: + ValuesView[Instruction]: The (in-order) instructions. + """ self.sort_moments() return self._moments.values() - def get(self, key: MomentsKey, default=None) -> Instruction: + def get(self, key: MomentsKey, default: Any = None) -> Instruction: """ Get the instruction in self by key. Args: key (MomentsKey): Key of the instruction to fetch. - default (Any, optional): Value to return if `key` is not in `moments`. Default = `None`. + default (Any): Value to return if `key` is not in `moments`. Default = `None`. Returns: Instruction: `moments[key]` if `key` in `moments`, else `default` is returned. diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py index 36768048..5abf19f6 100644 --- a/src/braket/circuits/noise.py +++ b/src/braket/circuits/noise.py @@ -13,7 +13,9 @@ from __future__ import annotations -from typing import Any, Dict, List, Optional, Sequence, Type, Union +from typing import Any, Dict, Iterable, List, Optional, Sequence, Set, Type, Union + +import numpy as np from braket.circuits.free_parameter import FreeParameter from braket.circuits.free_parameter_expression import FreeParameterExpression @@ -39,7 +41,7 @@ class Noise(QuantumOperator): def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): """ Args: - qubit_count (int, optional): Number of qubits this noise channel interacts with. + qubit_count (Optional[int]): Number of qubits this noise channel interacts with. ascii_symbols (Sequence[str]): ASCII string symbols for this noise channel. These are used when printing a diagram of circuits. Length must be the same as `qubit_count`, and index ordering is expected to correlate with target ordering @@ -57,7 +59,7 @@ def name(self) -> str: Returns the name of the quantum operator Returns: - The name of the quantum operator as a string + str: The name of the quantum operator as a string """ return self.__class__.__name__ @@ -77,7 +79,7 @@ def to_ir( while serializing the object to the IR representation. The serialization properties supplied must correspond to the supplied `ir_type`. Defaults to None. Returns: - IR object of the quantum operator and target + Any: IR object of the quantum operator and target Raises: ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization @@ -127,11 +129,11 @@ def _to_openqasm( """ raise NotImplementedError("to_openqasm has not been implemented yet.") - def to_matrix(self, *args, **kwargs) -> Any: + def to_matrix(self, *args, **kwargs) -> Iterable[np.ndarray]: """Returns a list of matrices defining the Kraus matrices of the noise channel. Returns: - Iterable[np.ndarray]: list of matrices defining the Kraus matrices of the noise channel. + Iterable[ndarray]: list of matrices defining the Kraus matrices of the noise channel. """ raise NotImplementedError("to_matrix has not been implemented yet.") @@ -145,6 +147,15 @@ 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. + + Args: + noise (dict): A dictionary representation of an object of this class. + + Returns: + Noise: An object of this class that corresponds to the passed in dictionary. + """ if "__class__" in noise: noise_name = noise["__class__"] noise_cls = getattr(cls, noise_name) @@ -152,11 +163,10 @@ def from_dict(cls, noise: dict) -> Noise: raise NotImplementedError @classmethod - def register_noise(cls, noise: Type[Noise]): + def register_noise(cls, noise: Type[Noise]) -> None: """Register a noise implementation by adding it into the Noise class. - Args: - noise (Noise): Noise class to register. + noise (Type[Noise]): Noise class to register. """ setattr(cls, noise.__name__, noise) @@ -178,7 +188,7 @@ def __init__( Args: probability (Union[FreeParameterExpression, float]): The probability that the noise occurs. - qubit_count (int, optional): The number of qubits to apply noise. + qubit_count (Optional[int]): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when 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. @@ -197,9 +207,9 @@ def __init__( @property def probability(self) -> float: - """ + """The probability that parametrizes the noise channel. Returns: - probability (float): The probability that parametrizes the noise channel. + float: The probability that parametrizes the noise channel. """ return self._probability @@ -230,9 +240,6 @@ def bind_values(self, **kwargs) -> SingleProbabilisticNoise: """ Takes in parameters and attempts to assign them to values. - Args: - **kwargs: The parameters that are being assigned. - Returns: SingleProbabilisticNoise: A new Noise object of the same type with the requested parameters bound. @@ -274,7 +281,7 @@ def __init__( Args: probability (Union[FreeParameterExpression, float]): The probability that the noise occurs. - qubit_count (int, optional): The number of qubits to apply noise. + qubit_count (Optional[int]): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when 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. @@ -308,7 +315,7 @@ def __init__( Args: probability (Union[FreeParameterExpression, float]): The probability that the noise occurs. - qubit_count (int, optional): The number of qubits to apply noise. + qubit_count (Optional[int]): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when 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. @@ -394,7 +401,9 @@ def __init__( ) @classmethod - def _validate_pauli_string(cls, pauli_str, qubit_count, allowed_substrings): + def _validate_pauli_string( + cls, pauli_str: str, qubit_count: int, allowed_substrings: Set[str] + ) -> None: if not isinstance(pauli_str, str): raise TypeError(f"Type of {pauli_str} was not a string.") if len(pauli_str) != qubit_count: @@ -428,8 +437,9 @@ def __eq__(self, other): @property def probabilities(self) -> Dict[str, float]: - """ - Dict[str, float]: A map of a Pauli string to its corresponding probability. + """A map of a Pauli string to its corresponding probability. + Returns: + Dict[str, float]: A map of a Pauli string to its corresponding probability. """ return self._probabilities @@ -453,9 +463,6 @@ def bind_values(self, **kwargs) -> MultiQubitPauliNoise: """ Takes in parameters and attempts to assign them to values. - Args: - **kwargs: The parameters that are being assigned. - Returns: MultiQubitPauliNoise: A new Noise object of the same type with the requested parameters bound. @@ -500,13 +507,13 @@ def __init__( ): """ Args: - probX Union[FreeParameterExpression, float]: The X coefficient of the Kraus operators + probX (Union[FreeParameterExpression, float]): The X coefficient of the Kraus operators in the channel. - probY Union[FreeParameterExpression, float]: The Y coefficient of the Kraus operators + probY (Union[FreeParameterExpression, float]): The Y coefficient of the Kraus operators in the channel. - probZ Union[FreeParameterExpression, float]: The Z coefficient of the Kraus operators + probZ (Union[FreeParameterExpression, float]): The Z coefficient of the Kraus operators in the channel. - qubit_count (int, optional): The number of qubits to apply noise. + qubit_count (Optional[int]): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when 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. @@ -606,9 +613,6 @@ def bind_values(self, **kwargs) -> PauliNoise: """ Takes in parameters and attempts to assign them to values. - Args: - **kwargs: The parameters that are being assigned. - Returns: PauliNoise: A new Noise object of the same type with the requested parameters bound. @@ -651,7 +655,7 @@ def __init__( """ Args: gamma (Union[FreeParameterExpression, float]): Probability of damping. - qubit_count (int, optional): The number of qubits to apply noise. + qubit_count (Optional[int]): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when 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. @@ -672,9 +676,9 @@ def __init__( @property def gamma(self) -> float: - """ + """Probability of damping. Returns: - gamma (float): Probability of damping. + float: Probability of damping. """ return self._gamma @@ -705,9 +709,6 @@ def bind_values(self, **kwargs) -> DampingNoise: """ Takes in parameters and attempts to assign them to values. - Args: - **kwargs: The parameters that are being assigned. - Returns: DampingNoise: A new Noise object of the same type with the requested parameters bound. @@ -751,7 +752,7 @@ def __init__( gamma (Union[FreeParameterExpression, float]): Probability of damping. probability (Union[FreeParameterExpression, float]): Probability of the system being excited by the environment. - qubit_count (int): The number of qubits to apply noise. + qubit_count (Optional[int]): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when 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. @@ -773,9 +774,9 @@ def __init__( @property def probability(self) -> float: - """ + """Probability of the system being excited by the environment. Returns: - probability (float): Probability of the system being excited by the environment. + float: Probability of the system being excited by the environment. """ return self._probability @@ -852,8 +853,8 @@ def _parameter_to_dict(parameter: Union[FreeParameter, float]) -> Union[dict, fl parameter(Union[FreeParameter, float]): The parameter to convert. Returns: - A dictionary representation of a FreeParameter if the parameter is a FreeParameter, - otherwise returns the float. + Union[dict, float]: A dictionary representation of a FreeParameter if the parameter + is a FreeParameter, otherwise returns the float. """ if isinstance(parameter, FreeParameter): return parameter.to_dict() diff --git a/src/braket/circuits/noise_helpers.py b/src/braket/circuits/noise_helpers.py index 6ccba477..29ade8cf 100644 --- a/src/braket/circuits/noise_helpers.py +++ b/src/braket/circuits/noise_helpers.py @@ -14,7 +14,7 @@ from __future__ import annotations import warnings -from typing import TYPE_CHECKING, Any, Iterable, Optional, Type, Union +from typing import TYPE_CHECKING, Any, Iterable, List, Optional, Tuple, Type, Union import numpy as np @@ -29,8 +29,11 @@ from braket.circuits.circuit import Circuit -def no_noise_applied_warning(noise_applied: bool): - "Helper function to give a warning is noise is not applied" +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. + """ if noise_applied is False: warnings.warn( "Noise is not applied to any gate, as there is no eligible gate in the circuit" @@ -39,24 +42,29 @@ def no_noise_applied_warning(noise_applied: bool): ) -def wrap_with_list(an_item: Any): - "Helper function to make the input parameter a list" +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. + + Returns: + List[Any]: The item wrapped in a list. + """ if an_item is not None and not isinstance(an_item, list): an_item = [an_item] return an_item -def check_noise_target_gates(noise: Noise, target_gates: Iterable[Type[Gate]]): +def check_noise_target_gates(noise: Noise, target_gates: Iterable[Type[Gate]]) -> None: """Helper function to check 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 (Union[Type[Gate], Iterable[Type[Gate]]]): Gate class or + target_gates (Iterable[Type[Gate]]): Gate class or List of Gate classes which `noise` is applied to. """ - if not all(isinstance(g, type) and issubclass(g, Gate) for g in target_gates): raise TypeError("All elements in target_gates must be an instance of the Gate class") @@ -75,14 +83,14 @@ def check_noise_target_gates(noise: Noise, target_gates: Iterable[Type[Gate]]): ) -def check_noise_target_unitary(noise: Noise, target_unitary: np.ndarray): +def check_noise_target_unitary(noise: Noise, target_unitary: np.ndarray) -> None: """Helper function to check 1. whether the input matrix is a np.ndarray type; 2. whether the target_unitary is a unitary; Args: noise (Noise): A Noise class object to be applied to the circuit. - target_unitary (np.ndarray): matrix of the target unitary gates + target_unitary (ndarray): matrix of the target unitary gates """ if not isinstance(target_unitary, np.ndarray): @@ -98,9 +106,10 @@ def check_noise_target_qubits( """ Helper function to check whether all the target_qubits are positive integers. Args: - target_qubits (Optional[QubitSetInput] = None): Index or indices of qubit(s). + circuit (Circuit): A ciruit where `noise` is to be checked. + target_qubits (Optional[QubitSetInput]): Index or indices of qubit(s). Returns: - target_qubits: QubitSet + QubitSet: The target qubits. """ if target_qubits is None: target_qubits = circuit.qubits @@ -132,6 +141,8 @@ def apply_noise_to_moments( 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. + position (str): The position to add the noise to. May be 'initialization' or + 'readout_noise'. Returns: Circuit: modified circuit. @@ -177,7 +188,7 @@ def _apply_noise_to_gates_helper( intersection: QubitSet, noise_applied: bool, new_noise_instruction: Iterable, -): +) -> Tuple[Iterable[Instruction], int, bool]: """Helper function to work out the noise instructions to be attached to a gate. Args: @@ -186,13 +197,14 @@ def _apply_noise_to_gates_helper( target_qubits (QubitSet): Index or indices of qubits which `noise` is applied to. instruction (Instruction): Instruction of the gate which `noise` is applied to. noise_index (int): The number of noise channels applied to the gate. - intersection (QubitSet): Intersection of target_qubits and the qubits associated + intersection (QubitSet): Intersection of target_qubits and the qubits associated with the gate. noise_applied (bool): Whether noise is applied or not. - new_noise_instruction (Iterable): current new noise instructions to be attached + new_noise_instruction (Iterable): current new noise instructions to be attached to the circuit. Returns: + Tuple[Iterable[Instruction], int, bool]: A tuple of three values: new_noise_instruction: A list of noise intructions noise_index: The number of noise channels applied to the gate noise_applied: Whether noise is applied or not @@ -237,7 +249,7 @@ def apply_noise_to_gates( circuit (Circuit): A ciruit where `noise` is applied to. noise (Iterable[Type[Noise]]): Noise channel(s) to be applied to the circuit. - target_gates (Union[Iterable[Type[Gate]], np.ndarray]): List of gates, or a unitary matrix + target_gates (Union[Iterable[Type[Gate]], ndarray]): List of gates, or a unitary matrix which `noise` is applied to. target_qubits (QubitSet): Index or indices of qubits which `noise` is applied to. diff --git a/src/braket/circuits/noise_model/criteria.py b/src/braket/circuits/noise_model/criteria.py index acbaf072..539bf382 100644 --- a/src/braket/circuits/noise_model/criteria.py +++ b/src/braket/circuits/noise_model/criteria.py @@ -105,7 +105,7 @@ def from_dict(cls, criteria: dict) -> Criteria: raise NotImplementedError @classmethod - def register_criteria(cls, criteria: Type[Criteria]): + def register_criteria(cls, criteria: Type[Criteria]) -> None: """Register a criteria implementation by adding it into the Criteria class. Args: diff --git a/src/braket/circuits/noise_model/noise_model.py b/src/braket/circuits/noise_model/noise_model.py index 16ead6cb..33791bd1 100644 --- a/src/braket/circuits/noise_model/noise_model.py +++ b/src/braket/circuits/noise_model/noise_model.py @@ -383,6 +383,8 @@ def _apply_noise_on_observable_result_types( 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 applied. diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py index f5612d44..b778d5b0 100644 --- a/src/braket/circuits/noises.py +++ b/src/braket/circuits/noises.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. import itertools -from typing import Dict, Iterable, List, Union +from typing import Any, Dict, Iterable, List, Union import numpy as np @@ -83,16 +83,20 @@ def __init__(self, probability: Union[FreeParameterExpression, float]): ascii_symbols=[_ascii_representation("BF", [probability])], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.BitFlip.construct(target=target[0], probability=self.probability) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return f"#pragma braket noise bit_flip({self.probability}) {target_qubit}" 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. + """ K0 = np.sqrt(1 - self.probability) * np.eye(2, dtype=complex) K1 = np.sqrt(self.probability) * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) return [K0, K1] @@ -107,7 +111,7 @@ def bit_flip(target: QubitSetInput, probability: float) -> Iterable[Instruction] """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + target (QubitSetInput): Target qubit(s) probability (float): Probability of bit flipping. Returns: @@ -121,13 +125,10 @@ def bit_flip(target: QubitSetInput, probability: float) -> Iterable[Instruction] for qubit in QubitSet(target) ] - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> Noise: """ Takes in parameters and attempts to assign them to values. - Args: - **kwargs: The parameters that are being assigned. - Returns: Noise: A new Noise object of the same type with the requested parameters bound. @@ -184,16 +185,20 @@ def __init__(self, probability: Union[FreeParameterExpression, float]): ascii_symbols=[_ascii_representation("PF", [probability])], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.PhaseFlip.construct(target=target[0], probability=self.probability) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return f"#pragma braket noise phase_flip({self.probability}) {target_qubit}" 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. + """ K0 = np.sqrt(1 - self.probability) * np.eye(2, dtype=complex) K1 = np.sqrt(self.probability) * np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) return [K0, K1] @@ -208,7 +213,7 @@ def phase_flip(target: QubitSetInput, probability: float) -> Iterable[Instructio """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + target (QubitSetInput): Target qubit(s) probability (float): Probability of phase flipping. Returns: @@ -222,13 +227,10 @@ def phase_flip(target: QubitSetInput, probability: float) -> Iterable[Instructio for qubit in QubitSet(target) ] - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> Noise: """ Takes in parameters and attempts to assign them to values. - Args: - **kwargs: The parameters that are being assigned. - Returns: Noise: A new Noise object of the same type with the requested parameters bound. @@ -300,6 +302,12 @@ def __init__( probY: Union[FreeParameterExpression, float], probZ: Union[FreeParameterExpression, float], ): + """Creates PauliChannel noise. + Args: + probX (Union[FreeParameterExpression, float]): X rotation probability. + probY (Union[FreeParameterExpression, float]): Y rotation probability. + probZ (Union[FreeParameterExpression, float]): Z rotation probability. + """ super().__init__( probX=probX, probY=probY, @@ -308,14 +316,14 @@ def __init__( ascii_symbols=[_ascii_representation("PC", [probX, probY, probZ])], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.PauliChannel.construct( target=target[0], probX=self.probX, probY=self.probY, probZ=self.probZ ) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return ( f"#pragma braket noise pauli_channel" @@ -323,6 +331,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. + """ K0 = np.sqrt(1 - self.probX - self.probY - self.probZ) * np.eye(2, dtype=complex) K1 = np.sqrt(self.probX) * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) K2 = np.sqrt(self.probY) * 1j * np.array([[0.0, -1.0], [1.0, 0.0]], dtype=complex) @@ -341,9 +353,12 @@ def pauli_channel( """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + target (QubitSetInput): Target qubit(s) probability List[float]: Probabilities for the Pauli X, Y and Z noise happening in the Kraus channel. + probX (float): X rotation probability. + probY (float): Y rotation probability. + probZ (float): Z rotation probability. Returns: Iterable[Instruction]: `Iterable` of PauliChannel instructions. @@ -356,15 +371,12 @@ def pauli_channel( for qubit in QubitSet(target) ] - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> Noise: """ Takes in parameters and attempts to assign them to values. - Args: - **kwargs: The parameters that are being assigned. - Returns: - Gate.Rx: A new Gate of the same type with the requested + Noise: A new Noise object of the same type with the requested parameters bound. """ probX = _substitute_value(self.probX, **kwargs) @@ -445,16 +457,20 @@ def __init__(self, probability: Union[FreeParameterExpression, float]): ascii_symbols=[_ascii_representation("DEPO", [probability])], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Depolarizing.construct(target=target[0], probability=self.probability) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return f"#pragma braket noise depolarizing({self.probability}) {target_qubit}" 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. + """ K0 = np.sqrt(1 - self.probability) * np.eye(2, dtype=complex) K1 = np.sqrt(self.probability / 3) * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) K2 = np.sqrt(self.probability / 3) * 1j * np.array([[0.0, -1.0], [1.0, 0.0]], dtype=complex) @@ -471,7 +487,7 @@ def depolarizing(target: QubitSetInput, probability: float) -> Iterable[Instruct """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + target (QubitSetInput): Target qubit(s) probability (float): Probability of depolarizing. Returns: @@ -485,17 +501,13 @@ def depolarizing(target: QubitSetInput, probability: float) -> Iterable[Instruct for qubit in QubitSet(target) ] - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> Noise: """ Takes in parameters and attempts to assign them to values. - Args: - **kwargs: The parameters that are being assigned. - Returns: Noise: A new Noise object of the same type with the requested parameters bound. - """ return Depolarizing(probability=_substitute_value(self._probability, **kwargs)) @@ -570,14 +582,14 @@ def __init__(self, probability: Union[FreeParameterExpression, float]): ascii_symbols=[_ascii_representation("DEPO", [probability])] * 2, ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.TwoQubitDepolarizing.construct( targets=[target[0], target[1]], probability=self.probability ) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ): + ) -> str: target_qubit_0 = serialization_properties.format_target(int(target[0])) target_qubit_1 = serialization_properties.format_target(int(target[1])) return ( @@ -586,6 +598,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) @@ -613,7 +629,8 @@ def two_qubit_depolarizing( """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubits + target1 (QubitInput): Target qubit 1. + target2 (QubitInput): Target qubit 2. probability (float): Probability of two-qubit depolarizing. Returns: @@ -628,17 +645,13 @@ def two_qubit_depolarizing( ) ] - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> Noise: """ Takes in parameters and attempts to assign them to values. - Args: - **kwargs: The parameters that are being assigned. - Returns: Noise: A new Noise object of the same type with the requested parameters bound. - """ return TwoQubitDepolarizing(probability=_substitute_value(self._probability, **kwargs)) @@ -695,14 +708,14 @@ def __init__(self, probability: Union[FreeParameterExpression, float]): ascii_symbols=[_ascii_representation("DEPH", [probability])] * 2, ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.TwoQubitDephasing.construct( targets=[target[0], target[1]], probability=self.probability ) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ): + ) -> str: target_qubit_0 = serialization_properties.format_target(int(target[0])) target_qubit_1 = serialization_properties.format_target(int(target[1])) return ( @@ -711,7 +724,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) SZ = np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) K0 = np.sqrt(1 - self._probability) * np.kron(SI, SI) @@ -733,7 +749,8 @@ def two_qubit_dephasing( """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubits + target1 (QubitInput): Target qubit 1. + target2 (QubitInput): Target qubit 2. probability (float): Probability of two-qubit dephasing. Returns: @@ -746,17 +763,13 @@ def two_qubit_dephasing( Instruction(Noise.TwoQubitDephasing(probability=probability), target=[target1, target2]) ] - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> Noise: """ Takes in parameters and attempts to assign them to values. - Args: - **kwargs: The parameters that are being assigned. - Returns: Noise: A new Noise object of the same type with the requested parameters bound. - """ return TwoQubitDephasing(probability=_substitute_value(self._probability, **kwargs)) @@ -855,6 +868,10 @@ def __init__(self, probabilities: Dict[str, float]): self._matrix = None 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. + """ if self._matrix is not None: return self._matrix total_prob = sum(self._probabilities.values()) @@ -870,7 +887,7 @@ def to_matrix(self) -> Iterable[np.ndarray]: self._matrix = K_list return self._matrix - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.MultiQubitPauliChannel.construct( targets=[target[0], target[1]], probabilities=self.probabilities ) @@ -887,8 +904,9 @@ def two_qubit_pauli_channel( """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubits - probability (float): Probability of two-qubit Pauli channel. + target1 (QubitInput): Target qubit 1. + target2 (QubitInput): Target qubit 2. + probabilities (Dict[str, float]): Probability of two-qubit Pauli channel. Returns: Iterable[Instruction]: `Iterable` of Depolarizing instructions. @@ -903,17 +921,13 @@ def two_qubit_pauli_channel( ) ] - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> Noise: """ Takes in parameters and attempts to assign them to values. - Args: - **kwargs: The parameters that are being assigned. - Returns: Noise: A new Noise object of the same type with the requested parameters bound. - """ probabilities = { pauli_string: _substitute_value(prob, **kwargs) @@ -972,16 +986,20 @@ def __init__(self, gamma: Union[FreeParameterExpression, float]): ascii_symbols=[_ascii_representation("AD", [gamma])], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.AmplitudeDamping.construct(target=target[0], gamma=self.gamma) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return f"#pragma braket noise amplitude_damping({self.gamma}) {target_qubit}" 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. + """ K0 = np.array([[1.0, 0.0], [0.0, np.sqrt(1 - self.gamma)]], dtype=complex) K1 = np.array([[0.0, np.sqrt(self.gamma)], [0.0, 0.0]], dtype=complex) return [K0, K1] @@ -996,7 +1014,7 @@ def amplitude_damping(target: QubitSetInput, gamma: float) -> Iterable[Instructi """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s). + target (QubitSetInput): Target qubit(s). gamma (float): decaying rate of the amplitude damping channel. Returns: @@ -1010,17 +1028,13 @@ def amplitude_damping(target: QubitSetInput, gamma: float) -> Iterable[Instructi for qubit in QubitSet(target) ] - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> Noise: """ Takes in parameters and attempts to assign them to values. - Args: - **kwargs: The parameters that are being assigned. - Returns: Noise: A new Noise object of the same type with the requested parameters bound. - """ return AmplitudeDamping(gamma=_substitute_value(self._gamma, **kwargs)) @@ -1091,14 +1105,14 @@ def __init__( ascii_symbols=[_ascii_representation("GAD", [gamma, probability])], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.GeneralizedAmplitudeDamping.construct( target=target[0], gamma=self.gamma, probability=self.probability ) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return ( "#pragma braket noise generalized_amplitude_damping(" @@ -1106,6 +1120,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. + """ K0 = np.sqrt(self.probability) * np.array( [[1.0, 0.0], [0.0, np.sqrt(1 - self.gamma)]], dtype=complex ) @@ -1128,9 +1146,9 @@ def generalized_amplitude_damping( """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s). - p(float): Probability of the system being excited by the environment. + target (QubitSetInput): Target qubit(s). gamma (float): The damping rate of the amplitude damping channel. + probability(float): Probability of the system being excited by the environment. Returns: Iterable[Instruction]: `Iterable` of GeneralizedAmplitudeDamping instructions. @@ -1146,17 +1164,13 @@ def generalized_amplitude_damping( for qubit in QubitSet(target) ] - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> Noise: """ Takes in parameters and attempts to assign them to values. - Args: - **kwargs: The parameters that are being assigned. - Returns: Noise: A new Noise object of the same type with the requested parameters bound. - """ gamma = _substitute_value(self._gamma, **kwargs) probability = _substitute_value(self._probability, **kwargs) @@ -1215,16 +1229,20 @@ def __init__(self, gamma: Union[FreeParameterExpression, float]): ascii_symbols=[_ascii_representation("PD", [gamma])], ) - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.PhaseDamping.construct(target=target[0], gamma=self.gamma) def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return f"#pragma braket noise phase_damping({self.gamma}) {target_qubit}" 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. + """ K0 = np.array([[1.0, 0.0], [0.0, np.sqrt(1 - self.gamma)]], dtype=complex) K1 = np.array([[0.0, 0.0], [0.0, np.sqrt(self.gamma)]], dtype=complex) return [K0, K1] @@ -1239,7 +1257,7 @@ def phase_damping(target: QubitSetInput, gamma: float) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (Qubit, int, or iterable of Qubit / int): Target qubit(s) + target (QubitSetInput): Target qubit(s) gamma (float): Probability of phase damping. Returns: @@ -1252,17 +1270,13 @@ def phase_damping(target: QubitSetInput, gamma: float) -> Iterable[Instruction]: Instruction(Noise.PhaseDamping(gamma=gamma), target=qubit) for qubit in QubitSet(target) ] - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> Noise: """ Takes in parameters and attempts to assign them to values. - Args: - **kwargs: The parameters that are being assigned. - Returns: Noise: A new Noise object of the same type with the requested parameters bound. - """ return PhaseDamping(gamma=_substitute_value(self._gamma, **kwargs)) @@ -1322,9 +1336,13 @@ def __init__(self, matrices: Iterable[np.ndarray], display_name: str = "KR"): super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) 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. + """ return self._matrices - def _to_jaqcd(self, target: QubitSet): + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Kraus.construct( targets=[qubit for qubit in target], matrices=Kraus._transform_matrix_to_ir(self._matrices), @@ -1332,7 +1350,7 @@ def _to_jaqcd(self, target: QubitSet): def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ): + ) -> str: matrix_list = ", ".join( np.array2string( matrix, @@ -1347,7 +1365,7 @@ def _to_openqasm( return f"#pragma braket noise kraus({matrix_list}) {qubit_list}" @staticmethod - def _transform_matrix_to_ir(matrices: Iterable[np.ndarray]): + def _transform_matrix_to_ir(matrices: Iterable[np.ndarray]) -> List: serializable = [] for matrix in matrices: matrix_as_list = [ @@ -1364,8 +1382,9 @@ def kraus( """Registers this function into the circuit class. Args: - targets (Qubit, int, or iterable of Qubit / int): Target qubit(s) - matrices (Iterable[np.array]): Matrices that define a general noise channel. + targets (QubitSetInput): Target qubit(s) + matrices (Iterable[array]): Matrices that define a general noise channel. + display_name (str): The display name. Returns: Iterable[Instruction]: `Iterable` of Kraus instructions. @@ -1385,6 +1404,12 @@ def kraus( ) def to_dict(self) -> dict: + """ + Converts this object into a dictionary representation. Not implemented at this time. + + Returns: + dict: Not implemented at this time.. + """ raise NotImplementedError @classmethod @@ -1426,7 +1451,7 @@ def _ascii_representation( return f"{noise}({param_str})" -def _substitute_value(expr, **kwargs): +def _substitute_value(expr: float, **kwargs) -> Union[FreeParameterExpression, float]: return expr.subs(kwargs) if isinstance(expr, FreeParameterExpression) else expr @@ -1437,8 +1462,8 @@ def _parameter_from_dict(parameter: Union[dict, float]) -> Union[FreeParameter, parameter(Union[dict, float]): The parameter to convert. Returns: - A FreeParameter representing the parameter, if the parameter is a dictionary, - otherwise returns the float. + Union[FreeParameter, float]: A FreeParameter representing the parameter, if the parameter + is a dictionary, otherwise returns the float. """ if isinstance(parameter, dict): return FreeParameter.from_dict(parameter) diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index 3dc16f51..bb9d5f2a 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -100,12 +100,18 @@ def _to_openqasm( @property def basis_rotation_gates(self) -> Tuple[Gate, ...]: - """Tuple[Gate]: Returns the basis rotation gates for this observable.""" + """Returns the basis rotation gates for this observable. + Returns: + Tuple[Gate, ...]: The basis rotation gates for this observable. + """ raise NotImplementedError @property def eigenvalues(self) -> np.ndarray: - """np.ndarray: Returns the eigenvalues of this observable.""" + """Returns the eigenvalues of this observable. + Returns: + ndarray: The eigenvalues of this observable. + """ raise NotImplementedError def eigenvalue(self, index: int) -> float: diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 631c1a62..0af9d90e 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -95,6 +95,10 @@ 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. + """ return np.ones(2) def eigenvalue(self, index: int) -> float: @@ -280,13 +284,21 @@ 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. + """ gates = [] for obs in self.factors: gates.extend(obs.basis_rotation_gates) return tuple(gates) @property - def eigenvalues(self): + def eigenvalues(self) -> np.ndarray: + """Returns the eigenvalues of this observable. + Returns: + np.ndarray: The eigenvalues of this observable. + """ if self._all_eigenvalues is None: self._all_eigenvalues = TensorProduct._compute_eigenvalues( self._factors, self.qubit_count @@ -294,6 +306,17 @@ def eigenvalues(self): return self._all_eigenvalues def eigenvalue(self, index: int) -> float: + """Returns the eigenvalue of this observable at the given index. + + The eigenvalues are ordered by their corresponding computational basis state + after diagonalization. + + Args: + index (int): The index of the desired eigenvalue + + Returns: + float: The `index`th eigenvalue of the observable. + """ if index in self._eigenvalue_indices: return self._eigenvalue_indices[index] dimension = 2**self.qubit_count @@ -393,7 +416,7 @@ def _to_openqasm( else: return f"hermitian({self._serialized_matrix_openqasm_matrix()}) all" - def _serialized_matrix_openqasm_matrix(self): + def _serialized_matrix_openqasm_matrix(self) -> str: serialized = str([[f"{complex(elem)}" for elem in row] for row in self._matrix.tolist()]) for replacements in [("(", ""), (")", ""), ("'", ""), ("j", "im")]: serialized = serialized.replace(replacements[0], replacements[1]) @@ -410,22 +433,29 @@ def basis_rotation_gates(self) -> Tuple[Gate, ...]: return self._diagonalizing_gates @property - def eigenvalues(self): + def eigenvalues(self) -> np.ndarray: + """Returns the eigenvalues of this observable. + Returns: + np.ndarray: The eigenvalues of this observable. + """ return self._eigenvalues def eigenvalue(self, index: int) -> float: return self._eigenvalues[index] @staticmethod - def _get_eigendecomposition(matrix) -> Dict[str, np.ndarray]: + def _get_eigendecomposition(matrix: np.ndarray) -> Dict[str, np.ndarray]: """ 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. + Args: + matrix (ndarray): The Hermitian matrix. + Returns: - Dict[str, np.ndarray]: The keys are "eigenvectors_conj_t", mapping to the + Dict[str, ndarray]: The keys are "eigenvectors_conj_t", mapping to the conjugate transpose of a matrix whose columns are the eigenvectors of the matrix, and "eigenvalues", a list of associated eigenvalues in the order of their corresponding eigenvectors in the "eigenvectors" matrix. These cached values @@ -457,7 +487,7 @@ def observable_from_ir(ir_observable: List[Union[str, List[List[List[float]]]]]) Args: ir_observable (List[Union[str, List[List[List[float]]]]]): observable as defined in IR - Return: + Returns: Observable: observable object """ if len(ir_observable) == 1: diff --git a/src/braket/circuits/operator.py b/src/braket/circuits/operator.py index 03b94a56..06e72c8a 100644 --- a/src/braket/circuits/operator.py +++ b/src/braket/circuits/operator.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. from abc import ABC, abstractmethod +from typing import Any class Operator(ABC): @@ -20,15 +21,17 @@ class Operator(ABC): @property @abstractmethod def name(self) -> str: - """str: The name of the operator.""" + """The name of the operator. + Returns: + str: The name of the operator. + """ @abstractmethod - def to_ir(self, *args, **kwargs): + def to_ir(self, *args, **kwargs) -> Any: """ 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: - *args: Positional arguments - **kwargs: Keyword arguments + Returns: + Any: The the canonical intermediate representation of the operator. """ diff --git a/src/braket/circuits/parameterizable.py b/src/braket/circuits/parameterizable.py index 550b48cc..10f9e854 100644 --- a/src/braket/circuits/parameterizable.py +++ b/src/braket/circuits/parameterizable.py @@ -14,7 +14,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import List, Union +from typing import Any, List, Union from braket.circuits.free_parameter import FreeParameter from braket.circuits.free_parameter_expression import FreeParameterExpression @@ -29,17 +29,20 @@ class Parameterizable(ABC): @property @abstractmethod def parameters(self) -> List[Union[FreeParameterExpression, FreeParameter, float]]: - """ - Returns the parameters associated with the object, either unbound free parameter expressions - or bound values. The order of the parameters is determined by the subclass. + """Get the parameters. + + Returns: + List[Union[FreeParameterExpression, FreeParameter, float]]: The parameters associated + with the object, either unbound free parameter expressions or bound values. The order + of the parameters is determined by the subclass. """ @abstractmethod - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> Any: """ Takes in parameters and returns an object with specified parameters replaced with their values. - Args: - **kwargs: The named parameters and the corresponding values. + Returns: + Any: The result object will depend on the implementation of the object being bound. """ diff --git a/src/braket/circuits/quantum_operator.py b/src/braket/circuits/quantum_operator.py index eac1797a..846573c6 100644 --- a/src/braket/circuits/quantum_operator.py +++ b/src/braket/circuits/quantum_operator.py @@ -26,7 +26,7 @@ class QuantumOperator(Operator): def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): """ Args: - qubit_count (int, optional): Number of qubits this quantum operator acts on. + 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 should be ``None``, and ``fixed_qubit_count`` should be implemented to return the qubit count; if ``fixed_qubit_count`` is implemented and an int is passed in, @@ -106,16 +106,15 @@ def name(self) -> str: Returns the name of the quantum operator Returns: - The name of the quantum operator as a string + str: The name of the quantum operator as a string """ return self.__class__.__name__ def to_ir(self, *args, **kwargs) -> Any: - """Returns IR representation of quantum operator + """Returns IR representation of quantum operator. - Args: - *args: Positional arguments - **kwargs: Keyword arguments + Returns: + Any: The the canonical intermediate representation of the operator. """ raise NotImplementedError("to_ir has not been implemented yet.") @@ -123,7 +122,7 @@ def to_matrix(self, *args, **kwargs) -> np.ndarray: """Returns a matrix representation of the quantum operator Returns: - np.ndarray: A matrix representation of the quantum operator + ndarray: A matrix representation of the quantum operator """ raise NotImplementedError("to_matrix has not been implemented yet.") diff --git a/src/braket/circuits/quantum_operator_helpers.py b/src/braket/circuits/quantum_operator_helpers.py index b61a3184..cb6da9e8 100644 --- a/src/braket/circuits/quantum_operator_helpers.py +++ b/src/braket/circuits/quantum_operator_helpers.py @@ -17,13 +17,13 @@ import numpy as np -def verify_quantum_operator_matrix_dimensions(matrix: np.array) -> None: +def verify_quantum_operator_matrix_dimensions(matrix: np.ndarray) -> None: """ Verifies matrix is square and matrix dimensions are positive powers of 2, raising `ValueError` otherwise. Args: - matrix (np.ndarray): matrix to verify + matrix (ndarray): matrix to verify Raises: ValueError: If `matrix` is not a two-dimensional square matrix, @@ -39,7 +39,7 @@ def verify_quantum_operator_matrix_dimensions(matrix: np.array) -> None: raise ValueError(f"`matrix` dimension {matrix.shape[0]} is not a positive power of 2") -def is_hermitian(matrix: np.array) -> bool: +def is_hermitian(matrix: np.ndarray) -> bool: r""" Whether matrix is Hermitian @@ -50,7 +50,7 @@ def is_hermitian(matrix: np.array) -> bool: where :math:`U^\dagger` is the conjugate transpose of :math:`U`. Args: - matrix (np.ndarray): matrix to verify + matrix (ndarray): matrix to verify Returns: bool: If matrix is Hermitian @@ -58,12 +58,12 @@ def is_hermitian(matrix: np.array) -> bool: return np.allclose(matrix, matrix.conj().T) -def is_square_matrix(matrix: np.array) -> bool: +def is_square_matrix(matrix: np.ndarray) -> bool: """ Whether matrix is square, meaning it has exactly two dimensions and the dimensions are equal Args: - matrix (np.ndarray): matrix to verify + matrix (ndarray): matrix to verify Returns: bool: If matrix is square @@ -71,7 +71,7 @@ def is_square_matrix(matrix: np.array) -> bool: return len(matrix.shape) == 2 and matrix.shape[0] == matrix.shape[1] -def is_unitary(matrix: np.array) -> bool: +def is_unitary(matrix: np.ndarray) -> bool: r""" Whether matrix is unitary @@ -83,7 +83,7 @@ def is_unitary(matrix: np.array) -> bool: and :math:`I` is the identity matrix. Args: - matrix (np.ndarray): matrix to verify + matrix (ndarray): matrix to verify Returns: bool: If matrix is unitary @@ -91,7 +91,7 @@ def is_unitary(matrix: np.array) -> bool: return np.allclose(np.eye(len(matrix)), matrix.dot(matrix.T.conj())) -def is_cptp(matrices: Iterable[np.array]) -> bool: +def is_cptp(matrices: Iterable[np.ndarray]) -> bool: """ Whether a transformation defined by these matrics as Kraus operators is a completely positive trace preserving (CPTP) map. This is the requirement for @@ -99,7 +99,7 @@ def is_cptp(matrices: Iterable[np.array]) -> bool: Reference: Section 8.2.3 in Nielsen & Chuang (2010) 10th edition. Args: - matrices (Iterable[np.array]): List of matrices representing Kraus operators. + matrices (Iterable[ndarray]): List of matrices representing Kraus operators. Returns: bool: If the matrices define a CPTP map. @@ -112,13 +112,13 @@ def is_cptp(matrices: Iterable[np.array]) -> bool: def get_pauli_eigenvalues(num_qubits: int) -> np.ndarray: """ Get the eigenvalues of Pauli operators and their tensor products as - an immutable Numpy array. + an immutable Numpy ndarray. Args: num_qubits (int): the number of qubits the operator acts on Returns: - np.ndarray: the eigenvalues of a Pauli product operator of the given size + 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/qubit.py b/src/braket/circuits/qubit.py index 3ffe1aee..479a453e 100644 --- a/src/braket/circuits/qubit.py +++ b/src/braket/circuits/qubit.py @@ -56,7 +56,10 @@ def new(qubit: QubitInput) -> Qubit: else a new `Qubit` is constructed. Args: - qubit (int or Qubit): `Qubit` index. If `type == Qubit` then the `qubit` is returned. + qubit (QubitInput): `Qubit` index. If `type == Qubit` then the `qubit` is returned. + + Returns: + Qubit: The qubit. """ if isinstance(qubit, Qubit): diff --git a/src/braket/circuits/qubit_set.py b/src/braket/circuits/qubit_set.py index 2cae36f8..155ca548 100644 --- a/src/braket/circuits/qubit_set.py +++ b/src/braket/circuits/qubit_set.py @@ -13,7 +13,7 @@ from __future__ import annotations -from typing import Dict, Iterable, Union +from typing import Any, Dict, Iterable, Union from boltons.setutils import IndexedSet @@ -34,8 +34,7 @@ class QubitSet(IndexedSet): def __init__(self, qubits: QubitSetInput = None): """ Args: - qubits (int, Qubit, or iterable of int / Qubit, optional): Qubits to be included in - the `QubitSet`. Default is `None`. + qubits (QubitSetInput): Qubits to be included in the `QubitSet`. Default is `None`. Examples: >>> qubits = QubitSet([0, 1]) @@ -55,7 +54,7 @@ def __init__(self, qubits: QubitSetInput = None): Qubit(3) """ - def _flatten(other): + def _flatten(other: Any) -> Any: if isinstance(other, Iterable) and not isinstance(other, str): for item in other: yield from _flatten(item) @@ -71,7 +70,7 @@ def map(self, mapping: Dict[QubitInput, QubitInput]) -> QubitSet: If this instance contains a qubit that is not in the `mapping` that qubit is not modified. Args: - mapping (dictionary[int or Qubit, int or Qubit]): A dictionary of qubit mappings to + mapping (Dict[QubitInput, QubitInput]): A dictionary of qubit mappings to apply. Key is the qubit in this instance to target, and the value is what the key will be changed to. diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index b0e096fd..392cbbe1 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -58,7 +58,7 @@ def name(self) -> str: Returns the name of the result type Returns: - The name of the result type as a string + str: The name of the result type as a string """ return self.__class__.__name__ @@ -76,10 +76,9 @@ def to_ir( serialization_properties (SerializationProperties): 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. - **kwargs: Keyword arguments Returns: - IR object of the result type + Any: IR object of the result type Raises: ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization @@ -118,7 +117,7 @@ def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties def copy( self, target_mapping: Dict[QubitInput, QubitInput] = None, target: QubitSetInput = None - ): + ) -> ResultType: """ Return a shallow copy of the result type. @@ -127,11 +126,10 @@ def copy( qubits. This is useful apply an instruction to a circuit and change the target qubits. Args: - target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of + target_mapping (Dict[QubitInput, QubitInput]): A dictionary of qubit mappings to apply to the target. Key is the qubit in this `target` and the value is what the key is changed to. Default = `None`. - target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the new - instruction. + target (QubitSetInput): Target qubits for the new instruction. Returns: ResultType: A shallow copy of the result type. @@ -162,7 +160,7 @@ def copy( return copy @classmethod - def register_result_type(cls, result_type: Type[ResultType]): + def register_result_type(cls, result_type: Type[ResultType]) -> None: """Register a result type implementation by adding it into the `ResultType` class. Args: @@ -192,8 +190,10 @@ def __init__( ): """ Args: + ascii_symbols (List[str]): ASCII string symbols for the result type. This is used when + printing a diagram of circuits. observable (Observable): the observable for the result type - target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + target (QubitSetInput): Target qubits that the result type is requested for. Default is `None`, which means the observable must only operate on 1 qubit and it will be applied to all qubits in parallel @@ -232,6 +232,10 @@ 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: diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index dd92503b..10594d3d 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -87,7 +87,7 @@ class DensityMatrix(ResultType): def __init__(self, target: QubitSetInput = None): """ Args: - target (int, Qubit, or iterable of int / Qubit, optional): The target qubits + target (QubitSetInput): The target qubits of the reduced density matrix. Default is `None`, and the full density matrix is returned. @@ -104,6 +104,10 @@ def target(self) -> QubitSet: @target.setter def target(self, target: QubitSetInput) -> None: + """Sets the target qubit set. + Args: + target (QubitSetInput): The target qubit set. + """ self._target = QubitSet(target) def _to_jaqcd(self) -> ir.DensityMatrix: @@ -126,7 +130,7 @@ def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties def density_matrix(target: QubitSetInput = None) -> ResultType: """Registers this function into the circuit class. Args: - target (int, Qubit, or iterable of int / Qubit, optional): The target qubits + target (QubitSetInput): The target qubits of the reduced density matrix. Default is `None`, and the full density matrix is returned. @@ -249,7 +253,7 @@ class Probability(ResultType): def __init__(self, target: QubitSetInput = None): """ Args: - target (int, Qubit, or iterable of int / Qubit, optional): The target qubits that the + target (QubitSetInput): The target qubits that the result type is requested for. Default is `None`, which means all qubits for the circuit. @@ -266,6 +270,10 @@ def target(self) -> QubitSet: @target.setter def target(self, target: QubitSetInput) -> None: + """Sets the target qubit set. + Args: + target (QubitSetInput): The target qubit set. + """ self._target = QubitSet(target) def _to_jaqcd(self) -> ir.Probability: @@ -289,7 +297,7 @@ def probability(target: QubitSetInput = None) -> ResultType: """Registers this function into the circuit class. Args: - target (int, Qubit, or iterable of int / Qubit, optional): The target qubits that the + target (QubitSetInput): The target qubits that the result type is requested for. Default is `None`, which means all qubits for the circuit. @@ -336,7 +344,7 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): """ Args: observable (Observable): the observable for the result type - target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + target (QubitSetInput): 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. @@ -379,7 +387,7 @@ def expectation(observable: Observable, target: QubitSetInput = None) -> ResultT Args: observable (Observable): the observable for the result type - target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + target (QubitSetInput): 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. @@ -411,7 +419,7 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): """ Args: observable (Observable): the observable for the result type - target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + target (QubitSetInput): 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. @@ -454,7 +462,7 @@ def sample(observable: Observable, target: QubitSetInput = None) -> ResultType: Args: observable (Observable): the observable for the result type - target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + target (QubitSetInput): 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. @@ -487,7 +495,7 @@ def __init__(self, observable: Observable, target: QubitSetInput = None): """ Args: observable (Observable): the observable for the result type - target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + target (QubitSetInput): 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. @@ -530,7 +538,7 @@ def variance(observable: Observable, target: QubitSetInput = None) -> ResultType Args: observable (Observable): the observable for the result type - target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the + target (QubitSetInput): Target qubits that the result type is requested for. Default is `None`, which means the observable must only operate on 1 qubit and it will be applied to all qubits in parallel diff --git a/src/braket/circuits/serialization.py b/src/braket/circuits/serialization.py index d50e4fed..1e0826e8 100644 --- a/src/braket/circuits/serialization.py +++ b/src/braket/circuits/serialization.py @@ -45,6 +45,13 @@ class OpenQASMSerializationProperties: qubit_reference_type: QubitReferenceType = QubitReferenceType.VIRTUAL def format_target(self, target: int) -> str: + """Format a target qubit to the appropriate OpenQASM representation. + Args: + target (int): The target qubit. + + Returns: + str: The OpenQASM representation of the target qubit. + """ qubit_reference_format = ( "q[{}]" if self.qubit_reference_type == QubitReferenceType.VIRTUAL else "${}" ) diff --git a/src/braket/circuits/unitary_calculation.py b/src/braket/circuits/unitary_calculation.py index d6ae047b..8538aea2 100644 --- a/src/braket/circuits/unitary_calculation.py +++ b/src/braket/circuits/unitary_calculation.py @@ -55,7 +55,7 @@ def calculate_unitary(qubit_count: int, instructions: Iterable[Instruction]) -> Returns: np.ndarray: A numpy array with shape (2^qubit_count, 2^qubit_count) representing the - `instructions` as a unitary. + `instructions` as a unitary. Raises: TypeError: If `instructions` is not composed only of `Gate` instances, @@ -100,7 +100,7 @@ def calculate_unitary_big_endian( Args: instructions (Iterable[Instruction]): The instructions for which the unitary matrix will be calculated. - qubits (QubitSet, optional): The actual qubits used by the instructions. + qubits (QubitSet): The actual qubits used by the instructions. Returns: np.ndarray: A numpy array with shape (2^qubit_count, 2^qubit_count) representing the diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 20103653..c21d52a5 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -41,8 +41,8 @@ def run( Args: task_specification (Union[Circuit, Problem]): Specification of a task to run on device. - - shots (int): The number of times to run the task on the device. Default is `None`. + shots (Optional[int]): The number of times to run the task on the device. + Default is `None`. Returns: QuantumTask: The QuantumTask tracking task execution on this device diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index f84a0696..fbff6174 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -63,13 +63,11 @@ def run( """Runs the given task with the wrapped local simulator. Args: - task_specification (Union[Circuit, Problem]): - shots (int, optional): The number of times to run the circuit or annealing problem. + task_specification (Union[Circuit, Problem, Program]): The 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 task specification. Sampling is not supported for shots=0. - *args: Positional args to pass to the `BraketSimulator` - **kwargs: Keyword arguments to pass to the `BraketSimulator` Returns: LocalQuantumTask: A LocalQuantumTask object containing the results @@ -108,7 +106,7 @@ def registered_backends() -> Set[str]: @singledispatch -def _get_simulator(simulator): +def _get_simulator(simulator: Union[str, BraketSimulator]) -> LocalSimulator: raise TypeError("Simulator must either be a string or a BraketSimulator instance") @@ -128,13 +126,17 @@ def _(backend_impl: BraketSimulator): @singledispatch def _run_internal( - task_specification, simulator: BraketSimulator, shots: Optional[int] = None, *args, **kwargs -): + task_specification: Union[Circuit, Problem, Program], + simulator: BraketSimulator, + shots: Optional[int] = None, + *args, + **kwargs, +) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: raise NotImplementedError(f"Unsupported task type {type(task_specification)}") @_run_internal.register -def _(circuit: Circuit, simulator: BraketSimulator, shots, *args, **kwargs): +def _(circuit: Circuit, simulator: BraketSimulator, shots: Optional[int] = None, *args, **kwargs): if DeviceActionType.OPENQASM in simulator.properties.action: validate_circuit_and_shots(circuit, shots) program = circuit.to_ir(ir_type=IRType.OPENQASM) @@ -150,7 +152,7 @@ def _(circuit: Circuit, simulator: BraketSimulator, shots, *args, **kwargs): @_run_internal.register -def _(problem: Problem, simulator: BraketSimulator, shots, *args, **kwargs): +def _(problem: Problem, simulator: BraketSimulator, shots: Optional[int] = None, *args, **kwargs): if DeviceActionType.ANNEALING not in simulator.properties.action: raise NotImplementedError(f"{type(simulator)} does not support quantum annealing problems") ir = problem.to_ir() @@ -159,7 +161,7 @@ def _(problem: Problem, simulator: BraketSimulator, shots, *args, **kwargs): @_run_internal.register -def _(program: Program, simulator: BraketSimulator, shots, *args, **kwargs): +def _(program: Program, simulator: BraketSimulator, shots: Optional[int] = None, *args, **kwargs): if DeviceActionType.OPENQASM not in simulator.properties.action: raise NotImplementedError(f"{type(simulator)} does not support OpenQASM programs") results = simulator.run(program, shots, *args, **kwargs) diff --git a/src/braket/ipython_utils.py b/src/braket/ipython_utils.py index c1ed5170..20100d94 100644 --- a/src/braket/ipython_utils.py +++ b/src/braket/ipython_utils.py @@ -14,7 +14,7 @@ import sys -def running_in_jupyter(): +def running_in_jupyter() -> bool: """ Determine if running within Jupyter. diff --git a/src/braket/jobs/data_persistence.py b/src/braket/jobs/data_persistence.py index d41f4d27..5bd44adb 100644 --- a/src/braket/jobs/data_persistence.py +++ b/src/braket/jobs/data_persistence.py @@ -39,7 +39,7 @@ def save_job_checkpoint( checkpoint_file_suffix (str): str that specifies the file suffix to be used for the checkpoint filename. The resulting filename `f"{job_name}(_{checkpoint_file_suffix}).json"` is used to save the checkpoints. - Default: "" + Default: "" data_format (PersistedJobDataFormat): The data format used to serialize the values. Note that for `PICKLED` data formats, the values are base64 encoded after serialization. Default: PersistedJobDataFormat.PLAINTEXT diff --git a/src/braket/jobs/image_uris.py b/src/braket/jobs/image_uris.py index 4996a7dd..e8e5ebef 100644 --- a/src/braket/jobs/image_uris.py +++ b/src/braket/jobs/image_uris.py @@ -25,11 +25,11 @@ class Framework(str, Enum): PL_PYTORCH = "PL_PYTORCH" -def retrieve_image(framework: Framework, region: str): +def retrieve_image(framework: Framework, region: str) -> str: """Retrieves the ECR URI for the Docker image matching the specified arguments. Args: - framework (str): The name of the framework. + framework (Framework): The name of the framework. region (str): The AWS region for the Docker image. Returns: diff --git a/src/braket/jobs/local/local_job.py b/src/braket/jobs/local/local_job.py index 7c8dacff..89705103 100644 --- a/src/braket/jobs/local/local_job.py +++ b/src/braket/jobs/local/local_job.py @@ -51,9 +51,9 @@ def create( local_container_update: bool = True, ) -> LocalQuantumJob: """Creates and runs job by setting up and running the customer script in a local - docker container. + docker container. - Args: + Args: device (str): ARN for the AWS device which is primarily accessed for the execution of this job. Alternatively, a string of the format "local:/" for using a local simulator for the job. This string will be available as the @@ -89,7 +89,7 @@ def create( For convenience, this accepts other types for keys and values, but `str()` is called to convert them before being passed on. Default: None. - input_data (Union[str, S3DataSourceConfig, dict]): Information about the training + input_data (Union[str, Dict, S3DataSourceConfig]): Information about the training data. Dictionary maps channel names to local paths or S3 URIs. Contents found at any local paths will be uploaded to S3 at f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local @@ -172,7 +172,7 @@ def __init__(self, arn: str, run_log: str = None): """ Args: arn (str): The ARN of the job. - run_log (str, Optional): The container output log of running the job with the given arn. + run_log (str): The container output log of running the job with the given arn. """ if not arn.startswith("local:job/"): raise ValueError(f"Arn {arn} is not a valid local job arn") @@ -194,7 +194,11 @@ def name(self) -> str: @property def run_log(self) -> str: - """str: The container output log from running the job.""" + """Gets the run output log from running the job. + + Returns: + str: The container output log from running the job. + """ if not self._run_log: try: with open(os.path.join(self.name, "log.txt"), "r") as log_file: @@ -204,24 +208,53 @@ def run_log(self) -> str: return self._run_log def state(self, use_cached_value: bool = False) -> str: - """The state of the quantum job.""" + """The state of the quantum 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". + """ return "COMPLETED" def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: - """When running the quantum job in local mode, the metadata is not available.""" + """When running the quantum 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 + """ pass def cancel(self) -> str: - """When running the quantum job in local mode, the cancelling a running is not possible.""" + """When running the quantum job in local mode, the cancelling a running is not possible. + Returns: + str: None + """ pass def download_result( self, - extract_to=None, + extract_to: str = None, poll_timeout_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_INTERVAL, ) -> None: - """When running the quantum job in local mode, results are automatically stored locally.""" + """When running the quantum job in local mode, results are automatically stored locally. + + Args: + extract_to (str): The directory to which the results are extracted. The results + are extracted to a folder titled with the job name within this directory. + Default= `Current working directory`. + poll_timeout_seconds (float): The polling timeout, in seconds, for `result()`. + Default: 10 days. + poll_interval_seconds (float): The polling interval, in seconds, for `result()`. + Default: 5 seconds. + """ pass def result( @@ -229,7 +262,17 @@ 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 job result persisted using save_job_result() function.""" + """Retrieves the job result persisted using save_job_result() function. + + Args: + poll_timeout_seconds (float): The polling timeout, in seconds, for `result()`. + Default: 10 days. + poll_interval_seconds (float): The polling interval, in seconds, for `result()`. + Default: 5 seconds. + + Returns: + Dict[str, Any]: Dict specifying the job results. + """ try: with open(os.path.join(self.name, "results.json"), "r") as f: persisted_data = PersistedJobData.parse_raw(f.read()) @@ -246,22 +289,23 @@ def metrics( statistic: MetricStatistic = MetricStatistic.MAX, ) -> 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. For example, the table: - timestamp energy - 0 0.1 - 1 0.2 - would be represented as: - { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } - values may be integers, floats, strings or None. + containing the values in each row. Args: metric_type (MetricType): The type of metrics to get. Default: MetricType.TIMESTAMP. - statistic (MetricStatistic): The statistic to determine which metric value to use when there is a conflict. Default: MetricStatistic.MAX. + Example: + timestamp energy + 0 0.1 + 1 0.2 + would be represented as: + { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } + values may be integers, floats, strings or None. + Returns: - Dict[str, List[Union[str, float, int]]] : The metrics data. + Dict[str, List[Any]]: The metrics data. """ parser = LogMetricsParser() current_time = str(time.time()) @@ -271,5 +315,13 @@ def metrics( return parser.get_parsed_metrics(metric_type, statistic) def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: - """Display container logs for a given job""" + """Display container logs for a given job + + Args: + wait (bool): `True` to keep looking for new log entries until the job completes; + otherwise `False`. Default: `False`. + poll_interval_seconds (int): The interval of time, in seconds, between polling for + new log entries and job completion (default: 5). + + """ return print(self.run_log) diff --git a/src/braket/jobs/local/local_job_container.py b/src/braket/jobs/local/local_job_container.py index bbed0c72..289e83ff 100644 --- a/src/braket/jobs/local/local_job_container.py +++ b/src/braket/jobs/local/local_job_container.py @@ -38,7 +38,7 @@ def __init__( 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, Optional): AwsSession for connecting to AWS Services. + aws_session (AwsSession): AwsSession for connecting to AWS Services. Default: AwsSession() logger (Logger): Logger object with which to write logs. Default: `getLogger(__name__)` @@ -69,7 +69,7 @@ def _envs_to_list(environment_variables: Dict[str, str]) -> List[str]: Args: environment_variables (Dict[str, str]): A dictionary of environment variables and - their values. + their values. Returns: List[str]: The list of parameters to use when running a job that will include the provided environment variables as part of the runtime. @@ -89,7 +89,7 @@ def _check_output_formatted(command: List[str]) -> str: command(List[str]): The command to run. Returns: - (str): The UTF-8 encoded output of running the command. + str: The UTF-8 encoded output of running the command. """ output = subprocess.check_output(command) return output.decode("utf-8").strip() @@ -143,7 +143,7 @@ def _start_container(self, image_uri: str, force_update: bool) -> str: force_update(bool): Do a docker pull, even if the image is local, in order to update. Returns: - (str): The name of the running container, which can be used to execute further commands. + str: The name of the running container, which can be used to execute further commands. """ image_name = self._check_output_formatted(["docker", "images", "-q", image_uri]) if not image_name: @@ -230,7 +230,7 @@ def run_local_job(self, environment_variables: Dict[str, str]) -> None: Args: environment_variables (Dict[str, str]): The environment variables to make available - as part of running the job. + as part of running the job. """ start_program_name = self._check_output_formatted( ["docker", "exec", self._container_name, "printenv", "SAGEMAKER_PROGRAM"] @@ -258,6 +258,6 @@ def run_local_job(self, environment_variables: Dict[str, str]) -> None: self.run_result = e self._logger.error(e) - def _end_session(self): + def _end_session(self) -> None: """Stops and removes the local container.""" subprocess.run(["docker", "stop", self._container_name]) diff --git a/src/braket/jobs/local/local_job_container_setup.py b/src/braket/jobs/local/local_job_container_setup.py index 0adc9042..6372957e 100644 --- a/src/braket/jobs/local/local_job_container_setup.py +++ b/src/braket/jobs/local/local_job_container_setup.py @@ -31,10 +31,9 @@ def setup_container( Args: container(_LocalJobContainer): The container that will run the braket job. aws_session (AwsSession): AwsSession for connecting to AWS Services. - **creation_kwargs: Keyword arguments for the boto3 Amazon Braket `CreateJob` operation. Returns: - (Dict[str, str]): A dictionary of environment variables that reflect Braket Jobs options + Dict[str, str]: A dictionary of environment variables that reflect Braket Jobs options requested by the customer. """ logger = getLogger(__name__) @@ -57,7 +56,6 @@ def _create_expected_paths(container: _LocalJobContainer, **creation_kwargs) -> Args: container(_LocalJobContainer): The container that will run the braket job. - **creation_kwargs: Keyword arguments for the boto3 Amazon Braket `CreateJob` operation. """ container.makedir("/opt/ml/model") container.makedir(creation_kwargs["checkpointConfig"]["localPath"]) @@ -72,7 +70,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() @@ -97,10 +95,10 @@ def _get_env_script_mode_config(script_mode_config: Dict[str, str]) -> Dict[str, Args: script_mode_config (Dict[str, str]): The values for scriptModeConfig in the boto3 input - parameters for running a Braket Job. + parameters for running a Braket 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 = { @@ -116,8 +114,11 @@ def _get_env_default_vars(aws_session: AwsSession, **creation_kwargs) -> Dict[st """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. + 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"] @@ -139,7 +140,7 @@ def _get_env_hyperparameters() -> Dict[str, str]: provided hyperpameters to the 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 { @@ -152,7 +153,7 @@ def _get_env_input_data() -> Dict[str, str]: provided input data to the 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 { @@ -166,10 +167,9 @@ def _copy_hyperparameters(container: _LocalJobContainer, **creation_kwargs) -> b Args: container(_LocalJobContainer): The container to save hyperparameters to. - **creation_kwargs: Keyword arguments for the boto3 Amazon Braket `CreateJob` operation. Returns: - (bool): True if any hyperparameters were copied to the container. + bool: True if any hyperparameters were copied to the container. """ if "hyperParameters" not in creation_kwargs: return False @@ -228,7 +228,15 @@ def _download_input_data( def _is_dir(prefix: str, keys: Iterable[str]) -> bool: - """determine whether the prefix refers to a directory""" + """Determine whether the prefix refers to a directory. + + Args: + prefix (str): The prefix to check. + keys (Iterable[str]): The set of paths to check. + + Returns: + bool: True if the prefix refers to a directory. + """ if prefix.endswith("/"): return True return all(key.startswith(f"{prefix}/") for key in keys) @@ -241,12 +249,11 @@ def _copy_input_data_list( store them in the container. Args: - container(_LocalJobContainer): The container to save input data to. + container (_LocalJobContainer): The container to save input data to. aws_session (AwsSession): AwsSession for connecting to AWS Services. - **creation_kwargs: Keyword arguments for the boto3 Amazon Braket `CreateJob` operation. Returns: - (bool): True if any input data was copied to the container. + bool: True if any input data was copied to the container. """ if "inputDataConfig" not in creation_kwargs: return False diff --git a/src/braket/jobs/logs.py b/src/braket/jobs/logs.py index 7f5ff65d..e0f54458 100644 --- a/src/braket/jobs/logs.py +++ b/src/braket/jobs/logs.py @@ -20,10 +20,12 @@ # Support for reading logs # ############################################################################## -from typing import Dict, List +from typing import Dict, List, Tuple from botocore.exceptions import ClientError +from braket.aws.aws_session import AwsSession + class ColorWrap(object): """A callable that prints text in a different color depending on the instance. @@ -55,7 +57,7 @@ def __call__(self, index, s): else: print(s) - def _color_wrap(self, index, s): + def _color_wrap(self, index: int, s: str) -> None: """Prints the string in a color determined by the index. Args: @@ -70,24 +72,23 @@ def _color_wrap(self, index, s): Position = collections.namedtuple("Position", ["timestamp", "skip"]) -def multi_stream_iter(aws_session, log_group, streams, positions): +def multi_stream_iter( + aws_session: AwsSession, log_group: str, streams: List[str], positions: Dict[str, Position] +) -> 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. Args: aws_session (AwsSession): The AwsSession for interfacing with CloudWatch. - log_group (str): The name of the log group. - - streams (list of 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: (list of Positions): 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: - A tuple of (stream number, cloudwatch log event). + 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) @@ -109,27 +110,25 @@ def multi_stream_iter(aws_session, log_group, streams, positions): events[i] = None -def log_stream(aws_session, log_group, stream_name, start_time=0, skip=0): +def log_stream( + aws_session: AwsSession, log_group: str, stream_name: str, start_time: int = 0, skip: int = 0 +) -> Dict: """A generator for log items in a single stream. This yields all the items that are available at the current moment. Args: aws_session (AwsSession): The AwsSession for interfacing with CloudWatch. - log_group (str): The name of the log group. - stream_name (str): The name of the specific stream. - start_time (int): The time stamp value to start reading the logs from. Default: 0. - skip (int): The number of log entries to skip at the start. Default: 0 (This is for when there are multiple entries at the same timestamp.) Yields: - 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. + 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 @@ -157,7 +156,7 @@ def log_stream(aws_session, log_group, stream_name, start_time=0, skip=0): def flush_log_streams( - aws_session, + aws_session: AwsSession, log_group: str, stream_prefix: str, stream_names: List[str], @@ -165,7 +164,7 @@ def flush_log_streams( stream_count: int, has_streams: bool, color_wrap: ColorWrap, -): +) -> bool: """Flushes log streams to stdout. Args: @@ -176,7 +175,7 @@ def flush_log_streams( 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 of Positions): 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. @@ -185,8 +184,8 @@ def flush_log_streams( color_wrap (ColorWrap): An instance of ColorWrap to potentially color-wrap print statements from different streams. - Yields: - A tuple of (stream number, cloudwatch log event). + Returns: + bool: Returns 'True' if any streams have been flushed. """ if len(stream_names) < stream_count: # Log streams are created whenever a container starts writing to stdout/err, 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 c40d7057..bbcc4f3c 100644 --- a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py @@ -117,7 +117,7 @@ def _parse_log_query_results( 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. + when there is a conflict. Returns: Dict[str, List[Union[str, float, int]]] : The metrics data. @@ -143,7 +143,7 @@ def get_metrics_for_job( metrics are retrieved. metric_type (MetricType): The type of metrics to get. Default is MetricType.TIMESTAMP. statistic (MetricStatistic): The statistic to determine which metric value to use - when there is a conflict. Default is MetricStatistic.MAX. + when there is a conflict. Default is MetricStatistic.MAX. job_start_time (int): The time when the job started. Default: 3 hours before job_end_time. job_end_time (int): If the job is complete, this should be the time at which the @@ -151,14 +151,15 @@ def get_metrics_for_job( Returns: 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. - For example, the table: - timestamp energy - 0 0.1 - 1 0.2 - would be represented as: - { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } - The values may be integers, floats, strings or None. + are the column names and the values are a list containing the values in each row. + + Example: + timestamp energy + 0 0.1 + 1 0.2 + would be represented as: + { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } + The values may be integers, floats, strings or None. """ 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 diff --git a/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py index 63237616..c7db59da 100644 --- a/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py @@ -42,7 +42,7 @@ def __init__( self._logs_client = aws_session.logs_client @staticmethod - def _is_metrics_message(message): + def _is_metrics_message(message: str) -> bool: """ Returns true if a given message is designated as containing Metrics. @@ -50,7 +50,7 @@ def _is_metrics_message(message): message (str): The message to check. Returns: - True if the given message is designated as containing Metrics; False otherwise. + bool: True if the given message is designated as containing Metrics; False otherwise. """ if message: return "Metrics -" in message @@ -70,9 +70,6 @@ def _parse_metrics_from_log_stream( timeout_time (float) : We stop getting metrics if the current time is beyond the timeout time. parser (LogMetricsParser) : The CWL metrics parser. - - Returns: - None """ kwargs = { "logGroupName": self.LOG_GROUP_NAME, @@ -140,18 +137,19 @@ def get_metrics_for_job( metrics are retrieved. metric_type (MetricType): The type of metrics to get. Default is MetricType.TIMESTAMP. statistic (MetricStatistic): The statistic to determine which metric value to use - when there is a conflict. Default is MetricStatistic.MAX. + when there is a conflict. Default is MetricStatistic.MAX. Returns: 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. - For example, the table: - timestamp energy - 0 0.1 - 1 0.2 - would be represented as: - { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } - values may be integers, floats, strings or None. + are the column names and the values are a list containing the values in each row. + + Example: + timestamp energy + 0 0.1 + 1 0.2 + would be represented as: + { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } + values may be integers, floats, strings or None. """ timeout_time = time.time() + self._poll_timeout_seconds diff --git a/src/braket/jobs/metrics_data/log_metrics_parser.py b/src/braket/jobs/metrics_data/log_metrics_parser.py index d0faacb5..7187486c 100644 --- a/src/braket/jobs/metrics_data/log_metrics_parser.py +++ b/src/braket/jobs/metrics_data/log_metrics_parser.py @@ -49,7 +49,7 @@ def _get_value( Args: current_value (Optional[Union[str, float, int]]): The current value. - new_value: (Union[str, float, int]) The new value. + new_value (Union[str, float, int]): The new value. statistic (MetricStatistic): The statistic to determine which value to use. @@ -120,11 +120,11 @@ 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[Any]], Dict[Tuple[int, str], int]]: - 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 - pivot column and node_id is mapped to a row index. + 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 + pivot column and node_id is mapped to a row index. """ row_count = 0 pivot_indices: dict[int, int] = {} @@ -187,22 +187,24 @@ def get_parsed_metrics( ) -> 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. For example, the table: - timestamp energy - 0 0.1 - 1 0.2 - would be represented as: - { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } - values may be integers, floats, strings or None. + containing the values in each row. Args: 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. + when there is a conflict. Returns: Dict[str, List[Union[str, float, int]]] : The metrics data. + + Example: + timestamp energy + 0 0.1 + 1 0.2 + would be represented as: + { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } + values may be integers, floats, strings or None. """ if metric_type == MetricType.ITERATION_NUMBER: return self.get_metric_data_with_pivot(self.ITERATION_NUMBER, statistic) diff --git a/src/braket/jobs/quantum_job.py b/src/braket/jobs/quantum_job.py index d17fa637..f99fe18d 100644 --- a/src/braket/jobs/quantum_job.py +++ b/src/braket/jobs/quantum_job.py @@ -23,19 +23,25 @@ class QuantumJob(ABC): @property @abstractmethod def arn(self) -> str: - """str: The ARN (Amazon Resource Name) of the quantum job.""" + """The ARN (Amazon Resource Name) of the quantum job. + Returns: + str: The ARN (Amazon Resource Name) of the quantum job. + """ @property @abstractmethod def name(self) -> str: - """str: The name of the quantum job.""" + """The name of the quantum job. + Returns: + str: The name of the quantum job. + """ @abstractmethod def state(self, use_cached_value: bool = False) -> str: """The state of the quantum job. Args: - use_cached_value (bool, optional): If `True`, uses the value most recently retrieved + 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`. @@ -90,7 +96,7 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: """Gets the job metadata defined in Amazon Braket. Args: - use_cached_value (bool, optional): If `True`, uses the value most recently retrieved + 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`. @@ -105,13 +111,7 @@ def metrics( statistic: MetricStatistic = MetricStatistic.MAX, ) -> 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. For example, the table: - timestamp energy - 0 0.1 - 1 0.2 - would be represented as: - { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } - values may be integers, floats, strings or None. + containing the values in each row. Args: metric_type (MetricType): The type of metrics to get. Default: MetricType.TIMESTAMP. @@ -120,7 +120,15 @@ def metrics( when there is a conflict. Default: MetricStatistic.MAX. Returns: - Dict[str, List[Union[str, float, int]]] : The metrics data. + Dict[str, List[Any]] : The metrics data. + + Example: + timestamp energy + 0 0.1 + 1 0.2 + would be represented as: + { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } + values may be integers, floats, strings or None. """ @abstractmethod @@ -161,7 +169,7 @@ def result( @abstractmethod def download_result( self, - extract_to=None, + extract_to: str = None, poll_timeout_seconds: float = DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = DEFAULT_RESULTS_POLL_INTERVAL, ) -> None: @@ -174,10 +182,10 @@ def download_result( are extracted to a folder titled with the job name within this directory. Default= `Current working directory`. - poll_timeout_seconds: (float): The polling timeout, in seconds, for `download_result()`. + poll_timeout_seconds (float): The polling timeout, in seconds, for `download_result()`. Default: 10 days. - poll_interval_seconds: (float): The polling interval, in seconds, for + poll_interval_seconds (float): The polling interval, in seconds, for `download_result()`.Default: 5 seconds. Raises: diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py index bffb335a..3556cd78 100644 --- a/src/braket/jobs/quantum_job_creation.py +++ b/src/braket/jobs/quantum_job_creation.py @@ -52,7 +52,7 @@ def prepare_quantum_job( checkpoint_config: CheckpointConfig = None, aws_session: AwsSession = None, tags: Dict[str, str] = None, -): +) -> Dict: """Creates a job by invoking the Braket CreateJob API. Args: @@ -88,7 +88,7 @@ def prepare_quantum_job( For convenience, this accepts other types for keys and values, but `str()` is called to convert them before being passed on. Default: None. - input_data (Union[str, S3DataSourceConfig, dict]): Information about the training + input_data (Union[str, Dict, S3DataSourceConfig]): Information about the training data. Dictionary maps channel names to local paths or S3 URIs. Contents found at any local paths will be uploaded to S3 at f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local @@ -130,7 +130,7 @@ def prepare_quantum_job( Default: {}. Returns: - AwsQuantumJob: Job tracking the execution on Amazon Braket. + Dict: Job tracking the execution on Amazon Braket. Raises: ValueError: Raises ValueError if the parameters are not valid. @@ -226,7 +226,7 @@ def _generate_default_job_name(image_uri: Optional[str]) -> str: """ Generate default job name using the image uri and a timestamp Args: - image_uri (str, optional): URI for the image container. + image_uri (Optional[str]): URI for the image container. Returns: str: Job name. diff --git a/src/braket/quantum_information/pauli_string.py b/src/braket/quantum_information/pauli_string.py index bea88be9..f248a2b3 100644 --- a/src/braket/quantum_information/pauli_string.py +++ b/src/braket/quantum_information/pauli_string.py @@ -115,7 +115,7 @@ def eigenstate(self, signs: Optional[Union[str, List[int], Tuple[int, ...]]] = N the Pauli string is ignored). Args: - signs (Union[str, List[int], Tuple[int, ...]], optional): The sign of each factor of the + signs (Optional[Union[str, List[int], Tuple[int, ...]]]): The sign of each factor of the eigenstate, specified either as a string of "+" and "_", or as a list or tuple of +/-1. The length of signs must be equal to the length of the Pauli string. If not specified, it is assumed to be all +. Default: None. diff --git a/src/braket/tasks/annealing_quantum_task_result.py b/src/braket/tasks/annealing_quantum_task_result.py index ce812553..82828942 100644 --- a/src/braket/tasks/annealing_quantum_task_result.py +++ b/src/braket/tasks/annealing_quantum_task_result.py @@ -14,6 +14,7 @@ from __future__ import annotations from dataclasses import dataclass +from typing import List, Optional, Tuple import numpy @@ -44,19 +45,24 @@ class AnnealingQuantumTaskResult: task_metadata: TaskMetadata additional_metadata: AdditionalMetadata - def data(self, selected_fields=None, sorted_by="value", reverse=False): + def data( + self, + selected_fields: Optional[List[str]] = None, + sorted_by: str = "value", + reverse: bool = False, + ) -> Tuple: """ Iterate over the data in record_array Args: - selected_fields (List[str], optional, default=None): selected fields to return. - Options are 'solution', 'value', and 'solution_count' - sorted_by (str, optional, default='value'): Sorts the data by this field. - Options are 'solution', 'value', and 'solution_count' - reverse (bool, optional, default=False): If True, returns the data in reverse order. + 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 + Tuple: data in record_array """ if selected_fields is None: selected_fields = ["solution", "value", "solution_count"] @@ -120,7 +126,7 @@ def from_string(result: str) -> AnnealingQuantumTaskResult: return AnnealingQuantumTaskResult._from_object(AnnealingTaskResult.parse_raw(result)) @classmethod - def _from_object(cls, result: AnnealingTaskResult): + def _from_object(cls, result: AnnealingTaskResult) -> AnnealingQuantumTaskResult: solutions = numpy.asarray(result.solutions, dtype=int) values = numpy.asarray(result.values, dtype=float) if not result.solutionCounts: diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 6a30a289..04b201e3 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -139,11 +139,11 @@ def measurement_counts_from_measurements(measurements: np.ndarray) -> Counter: Creates measurement counts from measurements Args: - measurements (numpy.ndarray): 2d array - row is shot and column is qubit. + measurements (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. + string. Value is the number of times that measurement occurred. """ bitstrings = [] for j in range(len(measurements)): @@ -164,7 +164,7 @@ def measurement_probabilities_from_measurement_counts( 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. + in a big endian binary string. Value is the probability the measurement occurred. """ measurement_probabilities = {} shots = sum(measurement_counts.values()) @@ -178,18 +178,18 @@ 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. Key is the measurements in a big endian binary string. Value is the probability the measurement occurred. - shots (int): number of iterations on device + shots (int): number of iterations on device. 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. + ndarray: A dictionary of probabilistic results. + Key is the measurements in a big endian binary string. + Value is the probability the measurement occurred. """ measurements_list = [] for bitstring in measurement_probabilities: @@ -201,7 +201,7 @@ def measurements_from_measurement_probabilities( return np.asarray(measurements_list, dtype=int) @staticmethod - def from_object(result: GateModelTaskResult): + def from_object(result: GateModelTaskResult) -> GateModelQuantumTaskResult: """ Create GateModelQuantumTaskResult from GateModelTaskResult object. @@ -237,7 +237,7 @@ def from_string(result: str) -> GateModelQuantumTaskResult: return GateModelQuantumTaskResult._from_object_internal(obj) @classmethod - def _from_object_internal(cls, result: GateModelTaskResult): + def _from_object_internal(cls, result: GateModelTaskResult) -> GateModelQuantumTaskResult: if result.taskMetadata.shots > 0: return GateModelQuantumTaskResult._from_object_internal_computational_basis_sampling( result @@ -246,7 +246,9 @@ def _from_object_internal(cls, result: GateModelTaskResult): return GateModelQuantumTaskResult._from_dict_internal_simulator_only(result) @classmethod - def _from_object_internal_computational_basis_sampling(cls, result: GateModelTaskResult): + def _from_object_internal_computational_basis_sampling( + cls, result: GateModelTaskResult + ) -> GateModelQuantumTaskResult: task_metadata = result.taskMetadata additional_metadata = result.additionalMetadata if result.measurements: @@ -314,7 +316,9 @@ def _from_object_internal_computational_basis_sampling(cls, result: GateModelTas ) @classmethod - def _from_dict_internal_simulator_only(cls, result: GateModelTaskResult): + def _from_dict_internal_simulator_only( + cls, result: GateModelTaskResult + ) -> GateModelQuantumTaskResult: task_metadata = result.taskMetadata additional_metadata = result.additionalMetadata result_types = result.resultTypes @@ -490,13 +494,13 @@ def _samples_from_measurements( return observable.eigenvalues[indices].real @staticmethod - def _result_type_hash(rt_type): + 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]))) @staticmethod - def _replace_neg_zero(observable_matrix): + def _replace_neg_zero(observable_matrix: Union[list, int]) -> Union[list, int]: if isinstance(observable_matrix, list): return [GateModelQuantumTaskResult._replace_neg_zero(x) for x in observable_matrix] else: diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py index f9d76999..5b079f63 100644 --- a/src/braket/tasks/local_quantum_task.py +++ b/src/braket/tasks/local_quantum_task.py @@ -42,6 +42,7 @@ def id(self) -> str: return str(self._id) def cancel(self) -> None: + """Cancel the quantum task.""" raise NotImplementedError("Cannot cancel completed local task") def state(self) -> str: @@ -55,6 +56,10 @@ def result( return self._result def async_result(self) -> asyncio.Task: + """Get the quantum task result asynchronously. + Returns: + 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 85358455..4d2fa020 100644 --- a/src/braket/tasks/photonic_model_quantum_task_result.py +++ b/src/braket/tasks/photonic_model_quantum_task_result.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 dataclasses import dataclass import numpy as np @@ -30,7 +32,7 @@ def __eq__(self, other) -> bool: return NotImplemented @staticmethod - def from_object(result: PhotonicModelTaskResult): + def from_object(result: PhotonicModelTaskResult) -> PhotonicModelQuantumTaskResult: """ Create PhotonicModelQuantumTaskResult from PhotonicModelTaskResult object. @@ -46,13 +48,15 @@ def from_object(result: PhotonicModelTaskResult): return PhotonicModelQuantumTaskResult._from_object_internal(result) @staticmethod - def from_string(result: str): + def from_string(result: str) -> PhotonicModelQuantumTaskResult: return PhotonicModelQuantumTaskResult._from_object_internal( PhotonicModelTaskResult.parse_raw(result) ) @classmethod - def _from_object_internal(cls, result: PhotonicModelTaskResult): + def _from_object_internal( + cls, result: PhotonicModelTaskResult + ) -> PhotonicModelQuantumTaskResult: task_metadata = result.taskMetadata additional_metadata = result.additionalMetadata if result.measurements is not None: diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py index 0c40c783..ccefc7f5 100644 --- a/src/braket/tasks/quantum_task.py +++ b/src/braket/tasks/quantum_task.py @@ -26,7 +26,10 @@ class QuantumTask(ABC): @property @abstractmethod def id(self) -> str: - """str: The task ID.""" + """Get the task ID. + Returns: + str: The task ID. + """ @abstractmethod def cancel(self) -> None: @@ -34,7 +37,10 @@ def cancel(self) -> None: @abstractmethod def state(self) -> str: - """str: State of the quantum task""" + """Get the state of the quantum task. + Returns: + str: State of the quantum task. + """ @abstractmethod def result( @@ -42,22 +48,27 @@ def result( ) -> Union[ GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult ]: - """ - Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: Get the quantum task result. - Call async_result if you want the result in an asynchronous way. + """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 + asynchronous way. """ @abstractmethod def async_result(self) -> asyncio.Task: - """asyncio.Task: Get the quantum task result asynchronously.""" + """Get the quantum task result asynchronously. + Returns: + Task: Get the quantum task result asynchronously. + """ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: """ Get task metadata. Args: - use_cached_value (bool, optional): If True, uses the value retrieved from the previous - request. + use_cached_value (bool): If True, uses the value retrieved from the previous + request. Default is False. Returns: Dict[str, Any]: The metadata regarding the task. If `use_cached_value` is True, diff --git a/src/braket/tracking/pricing.py b/src/braket/tracking/pricing.py index 25ea9982..f2c89ac2 100644 --- a/src/braket/tracking/pricing.py +++ b/src/braket/tracking/pricing.py @@ -25,7 +25,8 @@ class Pricing: def __init__(self): self._price_list = [] - def get_prices(self): + def get_prices(self) -> None: + """Retrieves the price list.""" # Using AWS Pricing Bulk API. Format for the response is described at # https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/reading-an-offer.html @@ -53,6 +54,10 @@ def get_prices(self): @lru_cache() def price_search(self, **kwargs) -> List[Dict[str, str]]: + """Searches the price list for a given set of parameters. + Returns: + List[Dict[str, str]]: The price list. + """ if not self._price_list: self.get_prices() return [ diff --git a/src/braket/tracking/tracker.py b/src/braket/tracking/tracker.py index ffc9a695..59bac823 100644 --- a/src/braket/tracking/tracker.py +++ b/src/braket/tracking/tracker.py @@ -45,19 +45,25 @@ def __enter__(self): def __exit__(self, *args): deregister_tracker(self) - def start(self): - """ - Start tracking resources with this tracker. + def start(self) -> Tracker: + """Start tracking resources with this tracker. + Returns: + Tracker: self. """ return self.__enter__() - def stop(self): - """ - Stop tracking resources with this tracker. + def stop(self) -> Tracker: + """Stop tracking resources with this tracker. + Returns: + Tracker: self. """ return self.__exit__() - def receive_event(self, event): + def receive_event(self, event: _TaskCreationEvent) -> None: + """Process a Tack Creation Event. + Args: + event (_TaskCreationEvent): The event to process. + """ _recieve_internal(event, self._resources) def tracked_resources(self) -> List[str]: @@ -113,10 +119,10 @@ 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 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 tasks on simulator devices, the summary - also includes the duration of the simulation. + a dictionary sumarizing the 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 tasks on simulator devices, the summary + also includes the duration of the simulation. Example: >>> tracker.quantum_tasks_statistics() @@ -252,12 +258,12 @@ def _get_simulator_task_cost(task_arn: str, details: dict) -> Decimal: @singledispatch -def _recieve_internal(event, resources): +def _recieve_internal(event: _TaskCreationEvent, resources: dict) -> None: raise ValueError(f"Event type {type(event)} is not supported") @_recieve_internal.register -def _(event: _TaskCreationEvent, resources: dict): +def _(event: _TaskCreationEvent, resources: dict) -> None: resources[event.arn] = { "shots": event.shots, "device": event.device, @@ -267,14 +273,14 @@ def _(event: _TaskCreationEvent, resources: dict): @_recieve_internal.register -def _(event: _TaskStatusEvent, resources: dict): +def _(event: _TaskStatusEvent, resources: dict) -> None: # 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 _(event: _TaskCompletionEvent, resources: dict): +def _(event: _TaskCompletionEvent, resources: dict) -> None: # 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 diff --git a/src/braket/tracking/tracking_context.py b/src/braket/tracking/tracking_context.py index 772d92a0..128af025 100644 --- a/src/braket/tracking/tracking_context.py +++ b/src/braket/tracking/tracking_context.py @@ -18,17 +18,29 @@ class TrackingContext: def __init__(self): self._trackers = set() - def register_tracker(self, tracker: Tracker): # noqa F821 + def register_tracker(self, tracker: Tracker) -> None: # noqa F821 + """Registers a tracker. + Args: + tracker (Tracker): The tracker. + """ self._trackers.add(tracker) - def deregister_tracker(self, tracker: Tracker): # noqa F821 + def deregister_tracker(self, tracker: Tracker) -> None: # noqa F821 + """Deregisters a tracker. + Args: + tracker (Tracker): The tracker. + """ self._trackers.remove(tracker) - def broadcast_event(self, event: _TrackingEvent): # 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): + def active_trackers(self) -> None: return self._trackers From e97f23d66f0cbee23be8ddf58d7e3636a4005522 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 25 Aug 2022 18:27:17 +0000 Subject: [PATCH 0526/1165] prepare release v1.29.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 9b6cf140..9145ad40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.29.2 (2022-08-25) + +### Bug Fixes and Other Changes + + * Updating documentation and type hints. + ## v1.29.1 (2022-08-18) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 7ebd0875..fce563fc 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.29.2.dev0" +__version__ = "1.29.2" From 792986de68dd1fbca88a043157ac9340cf50304f Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 25 Aug 2022 18:27:17 +0000 Subject: [PATCH 0527/1165] update development version to v1.29.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 fce563fc..5170d97c 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.29.2" +__version__ = "1.29.3.dev0" From 528b4d25c4e25dbfbcf03e838b25978811d22deb Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Fri, 2 Sep 2022 10:12:00 -0700 Subject: [PATCH 0528/1165] fix: making local jobs stream output. (#434) --- src/braket/jobs/local/local_job.py | 13 +-- src/braket/jobs/local/local_job_container.py | 34 ++++-- .../braket/jobs/local/test_local_job.py | 75 ++---------- .../jobs/local/test_local_job_container.py | 108 ++++++++++-------- 4 files changed, 99 insertions(+), 131 deletions(-) diff --git a/src/braket/jobs/local/local_job.py b/src/braket/jobs/local/local_job.py index 89705103..6be15720 100644 --- a/src/braket/jobs/local/local_job.py +++ b/src/braket/jobs/local/local_job.py @@ -152,21 +152,14 @@ def create( 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: - if isinstance(container.run_result, Exception): - log_file.write("\n--- Errors ---\n") - log_file.write(container.run_result) - else: - if container.run_result.stdout: - log_file.write(container.run_result.stdout.decode("utf-8").strip()) - if container.run_result.stderr: - log_file.write("\n--- Errors ---\n") - log_file.write(container.run_result.stderr.decode("utf-8").strip()) + log_file.write(container.run_log) if "checkpointConfig" in create_job_kwargs: checkpoint_config = create_job_kwargs["checkpointConfig"] if "localPath" in checkpoint_config: checkpoint_path = checkpoint_config["localPath"] container.copy_from(checkpoint_path, os.path.join(job_name, "checkpoints")) - return LocalQuantumJob(f"local:job/{job_name}") + run_log = container.run_log + return LocalQuantumJob(f"local:job/{job_name}", run_log) def __init__(self, arn: str, run_log: str = None): """ diff --git a/src/braket/jobs/local/local_job_container.py b/src/braket/jobs/local/local_job_container.py index 289e83ff..9483dd0d 100644 --- a/src/braket/jobs/local/local_job_container.py +++ b/src/braket/jobs/local/local_job_container.py @@ -47,7 +47,7 @@ def __init__( """ self._aws_session = aws_session or AwsSession() self.image_uri = image_uri - self.run_result = None + self.run_log = None self._container_name = None self._logger = logger self._force_update = force_update @@ -225,7 +225,10 @@ def copy_from(self, source: str, destination: str) -> None: self._logger.error(output) raise e - def run_local_job(self, environment_variables: Dict[str, str]) -> None: + def run_local_job( + self, + environment_variables: Dict[str, str], + ) -> None: """Runs a Braket job in a local container. Args: @@ -249,15 +252,30 @@ def run_local_job(self, environment_variables: Dict[str, str]) -> None: command.append(start_program_name) try: - self.run_result = subprocess.run(command, capture_output=True) - if self.run_result.stdout: - print(self.run_result.stdout.decode("utf-8").strip()) - if self.run_result.stderr: - self._logger.error(self.run_result.stderr.decode("utf-8").strip()) + process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + self.run_log = _stream_output(process) except Exception as e: - self.run_result = e + self.run_log = e self._logger.error(e) def _end_session(self) -> None: """Stops and removes the local container.""" subprocess.run(["docker", "stop", self._container_name]) + + +def _stream_output(process: subprocess.Popen) -> str: + exit_code = None + run_log = "" + + while exit_code is None: + stdout = process.stdout.readline().decode("utf-8") + print(stdout, end="") + run_log += stdout + exit_code = process.poll() + + if exit_code != 0: + error_message = f"Process exited with code: {exit_code}" + print(error_message) + run_log += error_message + + return run_log 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 5b41ea18..185b059e 100644 --- a/test/unit_tests/braket/jobs/local/test_local_job.py +++ b/test/unit_tests/braket/jobs/local/test_local_job.py @@ -12,7 +12,6 @@ # language governing permissions and limitations under the License. import json -from subprocess import CompletedProcess from unittest.mock import Mock, mock_open, patch import pytest @@ -47,58 +46,34 @@ def test_envs(): return {"Test": "Env"} -def success_result(): - return CompletedProcess(None, 0, str.encode("Test Output"), str.encode("Test Error")) - - @pytest.mark.parametrize( - "creation_kwargs, run_result", + "creation_kwargs", [ ( { "jobName": "Test-Job-Name", "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, "checkpointConfig": {"localPath": "test/local/path/"}, - }, - success_result(), + } ), ( { "jobName": "Test-Job-Name", "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, "checkpointConfig": {}, - }, - success_result(), + } ), ( { "jobName": "Test-Job-Name", "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, - }, - success_result(), + } ), ( { "jobName": "Test-Job-Name", "algorithmSpecification": {}, - }, - success_result(), - ), - ( - { - "jobName": "Test-Job-Name", - "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, - "checkpointConfig": {"localPath": "test/local/path/"}, - }, - CompletedProcess(None, 0, None, None), - ), - ( - { - "jobName": "Test-Job-Name", - "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, - "checkpointConfig": {"localPath": "test/local/path/"}, - }, - Exception("Test Exception"), + } ), ], ) @@ -115,16 +90,16 @@ def test_create( mock_prepare_job, aws_session, creation_kwargs, - run_result, job_results, + run_log, test_envs, ): with patch("builtins.open", mock_open()) as file_open: - mock_dir.side_effect = [False, True] + mock_dir.return_value = False mock_prepare_job.return_value = creation_kwargs mock_container_open = mock_container.return_value.__enter__.return_value - mock_container_open.run_result = run_result + mock_container_open.run_log = run_log file_read = file_open() file_read.read.return_value = json.dumps(job_results) mock_setup.return_value = test_envs @@ -146,47 +121,19 @@ def test_create( assert job.name == "Test-Job-Name" assert job.arn == "local:job/Test-Job-Name" assert job.state() == "COMPLETED" + assert job.run_log == run_log assert job.metadata() is None assert job.cancel() is None assert job.download_result() is None assert job.logs() is None assert job.result() == job_results["dataDictionary"] - mock_setup.assert_called_with(mock_container_open, aws_session, **creation_kwargs) - mock_container_open.run_local_job.assert_called_with(test_envs) - if isinstance(run_result, Exception): - assert file_read.write.call_count == 2 - file_read.write.assert_called_with(run_result) - elif run_result.stdout or run_result.stderr: - assert file_read.write.call_count == 3 - file_read.write.assert_any_call("Test Output") - file_read.write.assert_any_call("Test Error") - else: - file_read.write.assert_not_called() - - -@patch("os.path.isdir") -def test_run_log(mock_dir, run_log): - mock_dir.return_value = True - with patch("builtins.open", mock_open()) as file_open: - file_read = file_open() - file_read.read.return_value = run_log - job = LocalQuantumJob("local:job/Test-Job-Name") - assert job.run_log == run_log assert job.metrics() == { "Cost": [-4.034, -3.957], "iteration_number": [0.0, 1.0], "timestamp": [1633027264.5406773, 1633027288.6284382], } - - -def test_parse_run_log(run_log): - job = LocalQuantumJob("local:job/Test-Job-Name", run_log) - assert job.run_log == run_log - assert job.metrics() == { - "Cost": [-4.034, -3.957], - "iteration_number": [0.0, 1.0], - "timestamp": [1633027264.5406773, 1633027288.6284382], - } + mock_setup.assert_called_with(mock_container_open, aws_session, **creation_kwargs) + mock_container_open.run_local_job.assert_called_with(test_envs) def test_create_invalid_arg(): 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 3f6487a0..7a1a767d 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 @@ -14,7 +14,6 @@ import base64 import subprocess from pathlib import Path -from subprocess import CompletedProcess from unittest.mock import Mock, patch import pytest @@ -39,8 +38,15 @@ def aws_session(): @pytest.fixture -def run_result(): - return CompletedProcess(None, 0, None, None) +def popen_result(): + mock = Mock() + mock.stdout.readline.side_effect = [ + str.encode("this\n"), + str.encode("is a\n"), + str.encode("test\n"), + ] + mock.poll.side_effect = [None, None, 0] + return mock @patch("subprocess.check_output") @@ -156,7 +162,10 @@ def test_pull_container_forced_update_invalid_name( @patch("subprocess.check_output") @patch("subprocess.run") -def test_run_job_success(mock_run, mock_check_output, repo_uri, image_uri, aws_session, run_result): +@patch("subprocess.Popen") +def test_run_job_success( + mock_popen, mock_run, mock_check_output, repo_uri, image_uri, aws_session, popen_result +): local_image_name = "LocalImageName" running_container_name = "RunningContainer" env_variables = { @@ -169,10 +178,10 @@ def test_run_job_success(mock_run, mock_check_output, repo_uri, image_uri, aws_s str.encode(running_container_name), str.encode(run_program_name), ] - mock_run.side_effect = [run_result, None] + mock_popen.return_value = popen_result with _LocalJobContainer(image_uri, aws_session) as container: container.run_local_job(env_variables) - assert container.run_result == run_result + 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"] @@ -181,7 +190,7 @@ def test_run_job_success(mock_run, mock_check_output, repo_uri, image_uri, aws_s ["docker", "exec", running_container_name, "printenv", "SAGEMAKER_PROGRAM"] ) assert mock_check_output.call_count == 3 - mock_run.assert_any_call( + mock_popen.assert_called_with( [ "docker", "exec", @@ -195,45 +204,18 @@ def test_run_job_success(mock_run, mock_check_output, repo_uri, image_uri, aws_s "python", run_program_name, ], - capture_output=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, ) - mock_run.assert_any_call(["docker", "stop", running_container_name]) - assert mock_run.call_count == 2 + mock_run.assert_called_with(["docker", "stop", running_container_name]) @patch("subprocess.check_output") @patch("subprocess.run") -def test_run_customer_script_fails(mock_run, mock_check_output, repo_uri, image_uri, aws_session): - mock_logger = Mock() - local_image_name = "LocalImageName" - running_container_name = "RunningContainer" - env_variables = { - "ENV0": "VALUE0", - "ENV1": "VALUE1", - } - run_program_name = "Run Program Name" - mock_check_output.side_effect = [ - str.encode(local_image_name), - str.encode(running_container_name), - str.encode(run_program_name), - ] - expected_exception = Exception("Test Error") - mock_run.side_effect = [expected_exception, None] - with _LocalJobContainer(image_uri, aws_session, mock_logger) as container: - container.run_local_job(env_variables) - assert container.run_result == expected_exception - assert mock_check_output.call_count == 3 - mock_run.assert_any_call(["docker", "stop", running_container_name]) - assert mock_run.call_count == 2 - mock_logger.error.assert_called_with(expected_exception) - - -@patch("subprocess.check_output") -@patch("subprocess.run") -def test_customer_script_check_output( - mock_run, mock_check_output, repo_uri, image_uri, aws_session +@patch("subprocess.Popen") +def test_run_customer_script_fails( + mock_popen, mock_run, mock_check_output, repo_uri, image_uri, aws_session, popen_result ): - mock_logger = Mock() local_image_name = "LocalImageName" running_container_name = "RunningContainer" env_variables = { @@ -246,11 +228,11 @@ def test_customer_script_check_output( str.encode(running_container_name), str.encode(run_program_name), ] - run_result = CompletedProcess(None, 0, str.encode("Test Output"), str.encode("Test Error")) - mock_run.side_effect = [run_result, None] - with _LocalJobContainer(image_uri, aws_session, mock_logger) as container: + popen_result.poll.side_effect = [None, None, 400] + mock_popen.return_value = popen_result + with _LocalJobContainer(image_uri, aws_session) as container: container.run_local_job(env_variables) - assert container.run_result == run_result + 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"] @@ -259,7 +241,7 @@ def test_customer_script_check_output( ["docker", "exec", running_container_name, "printenv", "SAGEMAKER_PROGRAM"] ) assert mock_check_output.call_count == 3 - mock_run.assert_any_call( + mock_popen.assert_called_with( [ "docker", "exec", @@ -273,11 +255,39 @@ def test_customer_script_check_output( "python", run_program_name, ], - capture_output=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, ) - mock_run.assert_any_call(["docker", "stop", running_container_name]) - assert mock_run.call_count == 2 - mock_logger.error.assert_called_with("Test Error") + mock_run.assert_called_with(["docker", "stop", running_container_name]) + + +@patch("subprocess.check_output") +@patch("subprocess.run") +@patch("subprocess.Popen") +def test_running_throws_exception( + mock_popen, mock_run, mock_check_output, repo_uri, image_uri, aws_session, popen_result +): + mock_logger = Mock() + local_image_name = "LocalImageName" + running_container_name = "RunningContainer" + env_variables = { + "ENV0": "VALUE0", + "ENV1": "VALUE1", + } + run_program_name = "Run Program Name" + mock_check_output.side_effect = [ + str.encode(local_image_name), + str.encode(running_container_name), + str.encode(run_program_name), + ] + expected_exception = Exception("Test Error") + mock_popen.side_effect = [expected_exception, None] + with _LocalJobContainer(image_uri, aws_session, mock_logger) as container: + container.run_local_job(env_variables) + 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) @patch("subprocess.check_output") From 1675f98a5ff9e7eb677a6373759eac95070a6ead Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 5 Sep 2022 16:23:51 +0000 Subject: [PATCH 0529/1165] prepare release v1.29.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 9145ad40..f1e902c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.29.3 (2022-09-05) + +### Bug Fixes and Other Changes + + * making local jobs stream output. + ## v1.29.2 (2022-08-25) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 5170d97c..82f721f6 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.29.3.dev0" +__version__ = "1.29.3" From 276dac3dfdcfe0b7c35e56c06be34da9e2d6ec18 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 5 Sep 2022 16:23:51 +0000 Subject: [PATCH 0530/1165] update development version to v1.29.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 82f721f6..ee136313 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.29.3" +__version__ = "1.29.4.dev0" From cddb0fcd7358652de23f67f5e9fee44cdf0c9b1c Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 8 Sep 2022 09:45:17 -0600 Subject: [PATCH 0531/1165] fix: Simultaneous measurement of identity on all qubits (#441) --- src/braket/circuits/circuit.py | 2 +- test/unit_tests/braket/circuits/test_circuit.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 7f4f08b3..8db5f3f0 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -346,7 +346,7 @@ def _add_to_qubit_observable_mapping( if current_target and current_target != new_targets: return self._encounter_noncommuting_observable() - if not observable_target: + if not observable_target and observable != identity: if all_qubits_observable and all_qubits_observable != observable: return self._encounter_noncommuting_observable() self._qubit_observable_mapping[Circuit._ALL_QUBITS] = observable diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index b987550e..8c885f53 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -262,6 +262,17 @@ def test_add_result_type_observable_no_conflict_all(): assert circ.result_types == expected +def test_add_result_type_observable_no_conflict_all_identity(): + expected = [ + ResultType.Variance(observable=Observable.Y()), + ResultType.Expectation(observable=Observable.I()), + ResultType.Expectation(observable=Observable.Y()), + ] + circ = Circuit(expected) + assert circ.observables_simultaneously_measurable + assert circ.result_types == expected + + def test_add_result_type_observable_no_conflict_state_vector_obs_return_value(): expected = [ ResultType.StateVector(), From 88573909bfbfc9ef9f7a1759519ca15d2eb86a8d Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 8 Sep 2022 16:25:55 +0000 Subject: [PATCH 0532/1165] prepare release v1.29.4 --- 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 f1e902c7..2e385ff4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.29.4 (2022-09-08) + +### Bug Fixes and Other Changes + + * Simultaneous measurement of identity on all qubits + ## v1.29.3 (2022-09-05) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index ee136313..c7aa5f29 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.29.4.dev0" +__version__ = "1.29.4" From 1ea8cb6a3b112cb86959cbe4b85b8d71e6fd3580 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 8 Sep 2022 16:25:55 +0000 Subject: [PATCH 0533/1165] update development version to v1.29.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 c7aa5f29..fcb6c403 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.29.4" +__version__ = "1.29.5.dev0" From a84dee9ecef0d83b16fe7cdfc4556ed4cb142cc5 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Fri, 16 Sep 2022 10:44:41 -0700 Subject: [PATCH 0534/1165] feature: IonQ native gates (#442) Co-authored-by: Cody Wang --- setup.py | 2 +- src/braket/circuits/angled_gate.py | 123 +++++++++ src/braket/circuits/gate.py | 2 +- src/braket/circuits/gates.py | 242 +++++++++++++++++- .../braket/circuits/test_angled_gate.py | 40 +++ test/unit_tests/braket/circuits/test_gate.py | 2 +- test/unit_tests/braket/circuits/test_gates.py | 108 ++++++-- 7 files changed, 492 insertions(+), 27 deletions(-) diff --git a/setup.py b/setup.py index a1093e4e..7e9e3450 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.10.1", - "amazon-braket-default-simulator>=1.7.2", + "amazon-braket-default-simulator>=1.8.1", "backoff", "boltons", "boto3", diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index 29f24d63..b81052ea 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -15,6 +15,7 @@ import copy import math +from functools import singledispatch from typing import List, Optional, Sequence, Union from braket.circuits.free_parameter_expression import FreeParameterExpression @@ -110,3 +111,125 @@ def __eq__(self, other): def __repr__(self): return f"{self.name}('angle': {self.angle}, 'qubit_count': {self.qubit_count})" + + +class DoubleAngledGate(Gate, Parameterizable): + """ + Class `DoubleAngledGate` represents a quantum gate that operates on N qubits and two angles. + """ + + def __init__( + self, + angle_1: Union[FreeParameterExpression, float], + angle_2: Union[FreeParameterExpression, float], + qubit_count: Optional[int], + ascii_symbols: Sequence[str], + ): + """ + 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. + qubit_count (int, optional): The number of qubits that this gate interacts with. + ascii_symbols (Sequence[str]): ASCII string symbols for the gate. These are used when + 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. + For instance, if a CNOT instruction has the control qubit on the first index and + target qubit on the second index, the ASCII symbols should have `["C", "X"]` to + correlate a symbol with that index. + + Raises: + ValueError: If `qubit_count` is less than 1, `ascii_symbols` are `None`, or + `ascii_symbols` length != `qubit_count`, or `angle_1` or `angle_2` is `None` + """ + super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + if angle_1 is None or angle_2 is None: + raise ValueError("angles must not be None") + self._parameters = [ + ( + angle + if isinstance(angle, FreeParameterExpression) + else float(angle) # explicit casting in case angle is e.g. np.float32 + ) + for angle in (angle_1, angle_2) + ] + + @property + def parameters(self) -> List[Union[FreeParameterExpression, float]]: + """ + Returns the parameters associated with the object, either unbound free parameters or + bound values. + + Returns: + List[Union[FreeParameterExpression, float]]: The free parameters or fixed value + associated with the object. + """ + return self._parameters + + @property + def angle_1(self) -> Union[FreeParameterExpression, float]: + """ + Returns the first angle for the gate + + Returns: + Union[FreeParameterExpression, float]: The first angle of the gate in radians + """ + return self._parameters[0] + + @property + def angle_2(self) -> Union[FreeParameterExpression, float]: + """ + Returns the second angle for 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. + + Args: + **kwargs: The parameters that are being assigned. + + Returns: + AngledGate: A new Gate of the same type with the requested parameters bound. + + Raises: + NotImplementedError: Subclasses should implement this function. + """ + raise NotImplementedError + + def adjoint(self) -> List[Gate]: + """Returns the adjoint of this gate as a singleton list. + + Returns: + List[Gate]: A list containing the gate with negated angle. + """ + raise NotImplementedError + + def __eq__(self, other): + return ( + isinstance(other, DoubleAngledGate) + and self.name == other.name + and _angles_equal(self.angle_1, other.angle_1) + and _angles_equal(self.angle_2, other.angle_2) + ) + + def __repr__(self): + return ( + f"{self.name}('angles': ({self.angle_1}, {self.angle_2}), " + f"'qubit_count': {self.qubit_count})" + ) + + +@singledispatch +def _angles_equal(angle_1, angle_2): + return math.isclose(angle_1, angle_2) + + +@_angles_equal.register +def _(angle_1: FreeParameterExpression, angle_2): + return angle_1 == angle_2 diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index d8cb302f..5fb19bcb 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -106,7 +106,7 @@ def _to_jaqcd(self, target: QubitSet) -> Any: Returns: Any: JAQCD object representing the gate. """ - raise NotImplementedError("to_jaqcd has not been implemented yet.") + raise NotImplementedError("to_jaqcd is not implemented.") def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index cbfe8b71..0f076a29 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -18,7 +18,7 @@ import braket.ir.jaqcd as ir from braket.circuits import circuit -from braket.circuits.angled_gate import AngledGate +from braket.circuits.angled_gate import AngledGate, DoubleAngledGate from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction @@ -1899,6 +1899,201 @@ def cswap(control: QubitInput, target1: QubitInput, target2: QubitInput) -> Inst Gate.register_gate(CSwap) +class GPi(AngledGate): + """IonQ GPi gate. + + Args: + angle (Union[FreeParameterExpression, float]): angle in radians. + """ + + def __init__(self, angle: Union[FreeParameterExpression, float]): + super().__init__( + angle=angle, + qubit_count=None, + ascii_symbols=[angled_ascii_characters("GPi", angle)], + ) + + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return f"gpi({self.angle}) {target_qubit};" + + def to_matrix(self) -> np.ndarray: + return np.array( + [ + [0, np.exp(-1j * self.angle)], + [np.exp(1j * self.angle), 0], + ] + ) + + def adjoint(self) -> List[Gate]: + return [GPi(self.angle)] + + @staticmethod + def fixed_qubit_count() -> int: + return 1 + + def bind_values(self, **kwargs): + return get_angle(self, **kwargs) + + @staticmethod + @circuit.subroutine(register=True) + def gpi( + target: QubitInput, angle: Union[FreeParameterExpression, float] + ) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit or int): Target qubit index. + angle (Union[FreeParameterExpression, float]): Angle in radians. + + Returns: + Iterable[Instruction]: GPi instruction. + + Examples: + >>> circ = Circuit().gpi(0, 0.15) + """ + return [Instruction(GPi(angle), target=qubit) for qubit in QubitSet(target)] + + +Gate.register_gate(GPi) + + +class GPi2(AngledGate): + """IonQ GPi2 gate. + + Args: + angle (Union[FreeParameterExpression, float]): angle in radians. + """ + + def __init__(self, angle: Union[FreeParameterExpression, float]): + super().__init__( + angle=angle, + qubit_count=None, + ascii_symbols=[angled_ascii_characters("GPi2", angle)], + ) + + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit = serialization_properties.format_target(int(target[0])) + return f"gpi2({self.angle}) {target_qubit};" + + 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) + + def adjoint(self) -> List[Gate]: + return [GPi2(self.angle + np.pi)] + + @staticmethod + def fixed_qubit_count() -> int: + return 1 + + def bind_values(self, **kwargs): + return get_angle(self, **kwargs) + + @staticmethod + @circuit.subroutine(register=True) + def gpi2( + target: QubitInput, angle: Union[FreeParameterExpression, float] + ) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit or int): Target qubit index. + angle (Union[FreeParameterExpression, float]): Angle in radians. + + Returns: + Iterable[Instruction]: GPi2 instruction. + + Examples: + >>> circ = Circuit().gpi2(0, 0.15) + """ + return [Instruction(GPi2(angle), target=qubit) for qubit in QubitSet(target)] + + +Gate.register_gate(GPi2) + + +class MS(DoubleAngledGate): + """IonQ Mølmer-Sørenson gate. + + Args: + angle_1 (Union[FreeParameterExpression, float]): angle in radians. + angle_2 (Union[FreeParameterExpression, float]): angle in radians. + """ + + 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=[_double_angled_ascii_characters("MS", angle_1, angle_2)] * 2, + ) + + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ): + target_qubit_1 = serialization_properties.format_target(int(target[0])) + target_qubit_2 = serialization_properties.format_target(int(target[1])) + return f"ms({self.angle_1}, {self.angle_2}) {target_qubit_1}, {target_qubit_2};" + + def to_matrix(self) -> np.ndarray: + return np.array( + [ + [1, 0, 0, -1j * np.exp(-1j * (self.angle_1 + self.angle_2))], + [0, 1, -1j * np.exp(-1j * (self.angle_1 - self.angle_2)), 0], + [0, -1j * np.exp(1j * (self.angle_1 - self.angle_2)), 1, 0], + [-1j * np.exp(1j * (self.angle_1 + self.angle_2)), 0, 0, 1], + ] + ) / np.sqrt(2) + + def adjoint(self) -> List[Gate]: + return [MS(self.angle_1 + np.pi, self.angle_2)] + + @staticmethod + def fixed_qubit_count() -> int: + return 2 + + def bind_values(self, **kwargs): + return _get_angles(self, **kwargs) + + @staticmethod + @circuit.subroutine(register=True) + def ms( + target1: QubitInput, + target2: QubitInput, + angle_1: Union[FreeParameterExpression, float], + angle_2: Union[FreeParameterExpression, float], + ) -> Iterable[Instruction]: + """Registers this function into the circuit class. + + Args: + target (Qubit or int): Target qubit index. + angle (Union[FreeParameterExpression, float]): Angle in radians. + + Returns: + Iterable[Instruction]: MS instruction. + + Examples: + >>> circ = Circuit().ms(0, 1, 0.15, 0.34) + """ + return [Instruction(MS(angle_1, angle_2), target=[target1, target2])] + + +Gate.register_gate(MS) + + class Unitary(Gate): """Arbitrary unitary gate @@ -2004,6 +2199,29 @@ def angled_ascii_characters(gate: str, angle: Union[FreeParameterExpression, flo return f'{gate}({angle:{".2f" if isinstance(angle, (float, Float)) else ""}})' +def _double_angled_ascii_characters( + gate: str, + angle_1: Union[FreeParameterExpression, float], + angle_2: Union[FreeParameterExpression, float], +) -> str: + """ + Generates a formatted ascii representation of an angled gate. + + Args: + gate (str): The name of the gate. + angle (Union[FreeParameterExpression, float]): The angle for the gate. + + Returns: + str: Returns the ascii representation for an angled gate. + + """ + return ( + f"{gate}(" + f'{angle_1:{".2f" if isinstance(angle_1, (float, Float)) else ""}}, ' + f'{angle_2:{".2f" if isinstance(angle_2, (float, Float)) else ""}})' + ) + + def get_angle(self: AngledGate, **kwargs) -> AngledGate: """ Gets the angle with all values substituted in that are requested. @@ -2021,6 +2239,28 @@ def get_angle(self: AngledGate, **kwargs) -> AngledGate: return type(self)(angle=new_angle) +def _get_angles(self, **kwargs): + """ + Gets the angle with all values substituted in that are requested. + + Args: + self: The subclass of AngledGate for which the angle is being obtained. + **kwargs: The named parameters that are being filled for a particular gate. + + Returns: + A new gate of the type of the AngledGate originally used with all angles updated. + """ + new_angles = [ + ( + getattr(self, angle).subs(kwargs) + if isinstance(getattr(self, angle), FreeParameterExpression) + else getattr(self, angle) + ) + for angle in ("angle_1", "angle_2") + ] + return type(self)(angle_1=new_angles[0], angle_2=new_angles[1]) + + def format_complex(number: complex) -> str: """ Format a complex number into + im to be consumed by the braket unitary pragma diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index f6953db4..80535cb1 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -18,6 +18,7 @@ from pydantic import BaseModel from braket.circuits import AngledGate, FreeParameter, FreeParameterExpression, Gate +from braket.circuits.angled_gate import DoubleAngledGate @pytest.fixture @@ -118,3 +119,42 @@ def test_np_float_angle_json(): match = re.match(r'\{"target": \[0], "angle": (\d*\.?\d*)}', angled_gate_json) angle_value = float(match.group(1)) assert angle_value == angled_gate.angle + + +def test_double_angle_is_none(): + with pytest.raises(ValueError, match="angles must not be None"): + DoubleAngledGate(qubit_count=1, ascii_symbols=["foo"], angle_1=None, angle_2=1) + + +def test_double_angle_equality(): + gate = DoubleAngledGate(angle_1=0.15, angle_2=3, qubit_count=1, ascii_symbols=["bar"]) + equal_gate = DoubleAngledGate(angle_1=0.15, angle_2=3, qubit_count=1, ascii_symbols=["bar"]) + other_gate = AngledGate(angle=0.3, qubit_count=1, ascii_symbols=["foo"]) + non_gate = "non gate" + + assert equal_gate == gate + assert equal_gate is not gate + assert gate != other_gate + assert gate != non_gate + + +def test_double_angle_symbolic_equality(): + symbol1 = FreeParameter("theta") + symbol2 = FreeParameter("phi") + symbol3 = FreeParameter("theta") + gate1 = DoubleAngledGate(angle_1=symbol1, angle_2=1, qubit_count=1, ascii_symbols=["bar"]) + gate2 = DoubleAngledGate(angle_1=symbol1, angle_2=1, qubit_count=1, ascii_symbols=["bar"]) + gate3 = DoubleAngledGate(angle_1=symbol3, angle_2=1, qubit_count=1, ascii_symbols=["bar"]) + other_gate = DoubleAngledGate(angle_1=symbol2, angle_2=1, qubit_count=1, ascii_symbols=["foo"]) + + assert gate1 == gate2 + assert gate1 == gate3 + assert gate1 is not gate2 + assert gate1 != other_gate + + +def test_double_angle_repr(): + assert ( + repr(DoubleAngledGate(qubit_count=1, ascii_symbols=["foo"], angle_1=1, angle_2=2)) + == "DoubleAngledGate('angles': (1.0, 2.0), 'qubit_count': 1)" + ) diff --git a/test/unit_tests/braket/circuits/test_gate.py b/test/unit_tests/braket/circuits/test_gate.py index a9f1acbc..98e300ff 100644 --- a/test/unit_tests/braket/circuits/test_gate.py +++ b/test/unit_tests/braket/circuits/test_gate.py @@ -84,7 +84,7 @@ def __init__(self): @pytest.mark.parametrize( "ir_type, serialization_properties, expected_exception, expected_message", [ - (IRType.JAQCD, None, NotImplementedError, "to_jaqcd has not been implemented yet."), + (IRType.JAQCD, None, NotImplementedError, "to_jaqcd is not implemented."), (IRType.OPENQASM, None, NotImplementedError, "to_openqasm has not been implemented yet."), ("invalid-ir-type", None, ValueError, "Supplied ir_type invalid-ir-type is not supported."), ( diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index db476fbd..0ea9cd24 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -33,6 +33,11 @@ TwoDimensionalMatrix, ) + +class DoubleAngle: + pass + + testdata = [ (Gate.H, "h", ir.H, [SingleTarget], {}), (Gate.I, "i", ir.I, [SingleTarget], {}), @@ -85,6 +90,9 @@ (Gate.XX, "xx", ir.XX, [DoubleTarget, Angle], {}), (Gate.YY, "yy", ir.YY, [DoubleTarget, Angle], {}), (Gate.ZZ, "zz", ir.ZZ, [DoubleTarget, Angle], {}), + (Gate.GPi, "gpi", None, [SingleTarget, Angle], {}), + (Gate.GPi2, "gpi2", None, [SingleTarget, Angle], {}), + (Gate.MS, "ms", None, [DoubleTarget, DoubleAngle], {}), ( Gate.Unitary, "unitary", @@ -122,6 +130,9 @@ Gate.CPhaseShift00, Gate.CPhaseShift01, Gate.CPhaseShift10, + Gate.GPi, + Gate.GPi2, + Gate.MS, ] invalid_unitary_matrices = [ @@ -151,6 +162,10 @@ def angle_valid_input(**kwargs): return {"angle": 0.123} +def double_angle_valid_input(**kwargs): + return {"angle_1": 0.123, "angle_2": 4.567} + + def single_control_valid_input(**kwargs): return {"control": 0} @@ -180,6 +195,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, "SingleControl": single_control_valid_input, "DoubleControl": double_control_valid_ir_input, "MultiTarget": multi_target_valid_input, @@ -192,7 +208,7 @@ def two_dimensional_matrix_valid_input(**kwargs): "TwoDimensionalMatrix": two_dimensional_matrix_valid_input, "DoubleTarget": double_target_valid_input, "DoubleControl": double_control_valid_input, - } + }, ) @@ -227,7 +243,7 @@ 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 subclass == Angle or subclass == TwoDimensionalMatrix: + elif subclass in (Angle, TwoDimensionalMatrix, DoubleAngle): pass else: raise ValueError("Invalid subclass") @@ -239,6 +255,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 TwoDimensionalMatrix in irsubclasses: input.update(two_dimensional_matrix_valid_input(**kwargs)) return input @@ -263,7 +281,7 @@ def calculate_qubit_count(irsubclasses): qubit_count += 2 elif subclass == MultiTarget: qubit_count += 3 - elif subclass == Angle or subclass == TwoDimensionalMatrix: + elif subclass in (Angle, TwoDimensionalMatrix, DoubleAngle): pass else: raise ValueError("Invalid subclass") @@ -272,11 +290,12 @@ def calculate_qubit_count(irsubclasses): @pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs): - expected = irclass(**create_valid_ir_input(irsubclasses)) - actual = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)).to_ir( - **create_valid_target_input(irsubclasses) - ) - assert actual == expected + if irclass is not None: + expected = irclass(**create_valid_ir_input(irsubclasses)) + actual = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)).to_ir( + **create_valid_target_input(irsubclasses) + ) + assert actual == expected @pytest.mark.parametrize( @@ -748,6 +767,42 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), "#pragma braket unitary([[1.0, 0], [0, 0.70710678 - 0.70710678im]]) q[4]", ), + ( + Gate.GPi(angle=0.17), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "gpi(0.17) q[4];", + ), + ( + Gate.GPi(angle=0.17), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "gpi(0.17) $4;", + ), + ( + Gate.GPi2(angle=0.17), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "gpi2(0.17) q[4];", + ), + ( + Gate.GPi2(angle=0.17), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "gpi2(0.17) $4;", + ), + ( + Gate.MS(angle_1=0.17, angle_2=3.45), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "ms(0.17, 3.45) q[4], q[5];", + ), + ( + Gate.MS(angle_1=0.17, angle_2=3.45), + [4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "ms(0.17, 3.45) $4, $5;", + ), ], ) def test_gate_to_ir_openqasm(gate, target, serialization_properties, expected_ir): @@ -761,10 +816,13 @@ def test_gate_to_ir_openqasm(gate, target, serialization_properties, expected_ir @pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) def test_ir_instruction_level(testclass, subroutine_name, irclass, irsubclasses, kwargs): - expected = irclass(**create_valid_ir_input(irsubclasses)) - instruction = Instruction(**create_valid_instruction_input(testclass, irsubclasses, **kwargs)) - actual = instruction.to_ir() - assert actual == expected + if irclass is not None: + expected = irclass(**create_valid_ir_input(irsubclasses)) + instruction = Instruction( + **create_valid_instruction_input(testclass, irsubclasses, **kwargs) + ) + actual = instruction.to_ir() + assert actual == expected @pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) @@ -797,7 +855,7 @@ def test_gate_adjoint_expansion_correct(testclass, subroutine_name, irclass, irs matrices = [elem.to_matrix() for elem in gate.adjoint()] matrices.append(gate.to_matrix()) identity = np.eye(2**gate.qubit_count) - assert np.isclose(functools.reduce(lambda a, b: a @ b, matrices), identity).all() + assert np.allclose(functools.reduce(lambda a, b: a @ b, matrices), identity) @pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) @@ -854,16 +912,20 @@ def test_large_unitary(): @pytest.mark.parametrize("gate", parameterizable_gates) def test_bind_values(gate): - theta = FreeParameter("theta") - param_gate = gate(theta) - new_gate = param_gate.bind_values(theta=1) - expected = gate(1) - - assert ( - type(new_gate.angle) == float - and type(new_gate) == type(param_gate) - and new_gate == expected - ) + double_angled = gate.__name__ in ("MS",) + num_params = 2 if double_angled else 1 + thetas = [FreeParameter(f"theta_{i}") for i in range(num_params)] + mapping = dict((f"theta_{i}", i) for i in range(num_params)) + param_gate = gate(*thetas) + new_gate = param_gate.bind_values(**mapping) + expected = gate(*range(num_params)) + + assert type(new_gate) == type(param_gate) and new_gate == expected + if double_angled: + for angle in new_gate.angle_1, new_gate.angle_2: + assert type(angle) == float + else: + assert type(new_gate.angle) == float @pytest.mark.xfail(raises=ValueError) From 26288753a7edd5eb8b34e409ee4d9503ad14dacc Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 16 Sep 2022 18:11:14 +0000 Subject: [PATCH 0535/1165] prepare release v1.30.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 2e385ff4..81d12793 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.30.0 (2022-09-16) + +### Features + + * IonQ native gates + ## v1.29.4 (2022-09-08) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index fcb6c403..8b340b48 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.29.5.dev0" +__version__ = "1.30.0" From 792b376452e9bf2f5816941533e447609b38a513 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 16 Sep 2022 18:11:14 +0000 Subject: [PATCH 0536/1165] update development version to v1.30.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 8b340b48..d879458b 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.30.0" +__version__ = "1.30.1.dev0" From 294d1773aa25528512a435a71aad8b722ee190e8 Mon Sep 17 00:00:00 2001 From: Marciano Moreno Date: Mon, 19 Sep 2022 11:28:41 -0500 Subject: [PATCH 0537/1165] fix: update paths within docker image to posix (#444) --- src/braket/jobs/local/local_job_container.py | 4 ++-- .../braket/jobs/local/test_local_job_container.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/braket/jobs/local/local_job_container.py b/src/braket/jobs/local/local_job_container.py index 9483dd0d..d9f1c119 100644 --- a/src/braket/jobs/local/local_job_container.py +++ b/src/braket/jobs/local/local_job_container.py @@ -14,7 +14,7 @@ import re import subprocess from logging import Logger, getLogger -from pathlib import Path +from pathlib import PurePosixPath from typing import Dict, List from braket.aws.aws_session import AwsSession @@ -193,7 +193,7 @@ def copy_to(self, source: str, destination: str) -> None: Raises: subprocess.CalledProcessError: If unable to copy. """ - dirname = str(Path(destination).parent) + dirname = str(PurePosixPath(destination).parent) try: subprocess.check_output( ["docker", "exec", self._container_name, "mkdir", "-p", dirname] 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 7a1a767d..47efceef 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 @@ -13,7 +13,7 @@ import base64 import subprocess -from pathlib import Path +from pathlib import Path, PurePosixPath from unittest.mock import Mock, patch import pytest @@ -321,7 +321,7 @@ def test_copy_to(mock_run, mock_check_output, repo_uri, image_uri, aws_session): local_image_name = "LocalImageName" running_container_name = "RunningContainer" source_path = str(Path("test", "source", "dir", "path", "srcfile.txt")) - dest_path = str(Path("test", "dest", "dir", "path", "dstfile.txt")) + dest_path = str(PurePosixPath("test", "dest", "dir", "path", "dstfile.txt")) mock_check_output.side_effect = [ str.encode(local_image_name), str.encode(running_container_name), @@ -341,7 +341,7 @@ def test_copy_to(mock_run, mock_check_output, repo_uri, image_uri, aws_session): running_container_name, "mkdir", "-p", - str(Path("test", "dest", "dir", "path")), + str(PurePosixPath("test", "dest", "dir", "path")), ] ) mock_check_output.assert_any_call( From e2f2b8dcac42457918f8a186b7a9da292e515bff Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 20 Sep 2022 16:36:18 +0000 Subject: [PATCH 0538/1165] prepare release v1.30.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 81d12793..4ad95440 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.30.1 (2022-09-20) + +### Bug Fixes and Other Changes + + * update paths within docker image to posix + ## v1.30.0 (2022-09-16) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d879458b..80625f25 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.30.1.dev0" +__version__ = "1.30.1" From 736c4b9468d73cffdcac871adef4dacfcd526616 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 20 Sep 2022 16:36:18 +0000 Subject: [PATCH 0539/1165] update development version to v1.30.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 80625f25..ee2fe7e6 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.30.1" +__version__ = "1.30.2.dev0" From 60fc15b5415c5a628cbd1ba0308f3979e3299260 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 21 Sep 2022 11:33:55 -0700 Subject: [PATCH 0540/1165] fix: copy profile name (#446) --- src/braket/aws/aws_session.py | 6 ++++- test/unit_tests/braket/aws/test_aws_device.py | 25 ++++++++++++++++--- .../unit_tests/braket/aws/test_aws_session.py | 8 +++++- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index caf3ba07..26fda29f 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -797,9 +797,13 @@ def copy_session( aws_secret_access_key=creds.secret_key, aws_session_token=creds.token, region_name=new_region, + profile_name=self.boto_session.profile_name, ) else: - boto_session = boto3.Session(region_name=new_region) + boto_session = boto3.Session( + region_name=new_region, + profile_name=self.boto_session.profile_name, + ) copied_session = AwsSession( boto_session=boto_session, config=config, default_bucket=default_bucket ) diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 28f74928..b2974fb3 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -316,6 +316,7 @@ def circuit(request): def aws_session(): _boto_session = Mock() _boto_session.region_name = RIGETTI_REGION + _boto_session.profile_name = "test-profile" creds = Mock() creds.method = "other" @@ -775,9 +776,12 @@ def test_run_env_variables(aws_quantum_task_mock, device, circuit, arn): assert aws_quantum_task_mock.call_args_list[0][0][3] == ("env_bucket", "env/path") +@patch("braket.aws.aws_session.boto3.Session") @patch("braket.aws.aws_session.AwsSession") @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_batch_no_extra(aws_quantum_task_mock, aws_session_mock, device, circuit): +def test_run_batch_no_extra( + aws_quantum_task_mock, aws_session_mock, boto_session_mock, device, circuit +): _run_batch_and_assert( aws_quantum_task_mock, aws_session_mock, @@ -786,10 +790,16 @@ def test_run_batch_no_extra(aws_quantum_task_mock, aws_session_mock, device, cir ) +@patch("braket.aws.aws_session.boto3.Session") @patch("braket.aws.aws_session.AwsSession") @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_batch_with_shots( - aws_quantum_task_mock, aws_session_mock, device, circuit, s3_destination_folder + aws_quantum_task_mock, + aws_session_mock, + boto_session_mock, + device, + circuit, + s3_destination_folder, ): _run_batch_and_assert( aws_quantum_task_mock, @@ -801,10 +811,16 @@ def test_run_batch_with_shots( ) +@patch("braket.aws.aws_session.boto3.Session") @patch("braket.aws.aws_session.AwsSession") @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_batch_with_max_parallel_and_kwargs( - aws_quantum_task_mock, aws_session_mock, device, circuit, s3_destination_folder + aws_quantum_task_mock, + aws_session_mock, + boto_session_mock, + device, + circuit, + s3_destination_folder, ): _run_batch_and_assert( aws_quantum_task_mock, @@ -819,12 +835,13 @@ def test_run_batch_with_max_parallel_and_kwargs( ) +@patch("braket.aws.aws_session.boto3.Session") @patch.dict( os.environ, {"AMZN_BRAKET_TASK_RESULTS_S3_URI": "s3://env_bucket/env/path"}, ) @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_batch_env_variables(aws_quantum_task_mock, device, circuit, arn): +def test_run_batch_env_variables(aws_quantum_task_mock, boto_session_mock, device, circuit, arn): device(arn).run_batch([circuit]) assert aws_quantum_task_mock.call_args_list[0][0][3] == ("env_bucket", "env/path") diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index babfcf2f..3c82e671 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -38,6 +38,7 @@ def boto_session(): _boto_session = Mock() _boto_session.region_name = "us-west-2" + _boto_session.profile_name = "test-profile" return _boto_session @@ -65,6 +66,7 @@ def aws_session(boto_session, braket_client, account_id): def aws_explicit_session(): _boto_session = Mock() _boto_session.region_name = "us-test-1" + _boto_session.profile_name = "test-profile" creds = Mock() creds.access_key = "access key" @@ -1254,7 +1256,10 @@ def test_copy_session(boto_session_init, aws_session): boto_session_init.return_value = Mock() aws_session.braket_client._client_config.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") + boto_session_init.assert_called_with( + region_name="us-west-2", + profile_name="test-profile", + ) assert copied_session.braket_client._client_config.user_agent == "foo/bar" assert copied_session._default_bucket is None @@ -1268,6 +1273,7 @@ def test_copy_explicit_session(boto_session_init, aws_explicit_session): aws_secret_access_key="secret key", aws_session_token="token", region_name="us-west-2", + profile_name="test-profile", ) From 6f7e373ce2f657cc8d5a36e96b71f10ee6fae5a3 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 22 Sep 2022 16:26:28 +0000 Subject: [PATCH 0541/1165] prepare release v1.30.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 4ad95440..847b5929 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.30.2 (2022-09-22) + +### Bug Fixes and Other Changes + + * copy profile name + ## v1.30.1 (2022-09-20) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index ee2fe7e6..9d0dd3bc 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.30.2.dev0" +__version__ = "1.30.2" From 6fafa0f311203c3c0697dd59a4bb3cf240da655d Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 22 Sep 2022 16:26:28 +0000 Subject: [PATCH 0542/1165] update development version to v1.30.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 9d0dd3bc..57f56dc8 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.30.2" +__version__ = "1.30.3.dev0" From 68e317dd471924b1eddc8cd4317e14d048789cc0 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Mon, 26 Sep 2022 10:02:29 -0700 Subject: [PATCH 0543/1165] feature: support inputs in the device interface (#450) --- src/braket/circuits/circuit.py | 4 ++- src/braket/devices/device.py | 12 +++++-- src/braket/devices/local_simulator.py | 18 ++++++++-- .../braket/circuits/test_circuit.py | 31 +++++++++++++--- .../braket/devices/test_local_simulator.py | 35 +++++++++++++++++-- 5 files changed, 88 insertions(+), 12 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 8db5f3f0..dd25fd4b 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -1149,12 +1149,14 @@ def _to_openqasm( qubit_target = serialization_properties.format_target(int(qubit)) ir_instructions.append(f"b[{idx}] = measure {qubit_target};") - return OpenQasmProgram.construct(source="\n".join(ir_instructions)) + return OpenQasmProgram.construct(source="\n".join(ir_instructions), inputs={}) def _create_openqasm_header( self, serialization_properties: OpenQASMSerializationProperties ) -> List[str]: ir_instructions = ["OPENQASM 3.0;"] + 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;") diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index c21d52a5..9f818d56 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. from abc import ABC, abstractmethod -from typing import Optional, Union +from typing import Dict, Optional, Union from braket.annealing.problem import Problem from braket.circuits import Circuit @@ -33,7 +33,12 @@ def __init__(self, name: str, status: str): @abstractmethod def run( - self, task_specification: Union[Circuit, Problem], shots: Optional[int], *args, **kwargs + self, + task_specification: Union[Circuit, Problem], + shots: Optional[int], + inputs: Optional[Dict[str, float]], + *args, + **kwargs ) -> QuantumTask: """Run a quantum task specification on this quantum device. A task can be a circuit or an annealing problem. @@ -43,6 +48,9 @@ def run( to run on device. shots (Optional[int]): The number of times to run the task on the device. Default is `None`. + 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: {}. Returns: QuantumTask: The QuantumTask tracking task execution on this device diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index fbff6174..712bf1be 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. from functools import singledispatch -from typing import Optional, Set, Union +from typing import Dict, Optional, Set, Union import pkg_resources @@ -57,6 +57,7 @@ def run( self, task_specification: Union[Circuit, Problem, Program], shots: int = 0, + inputs: Optional[Dict[str, float]] = None, *args, **kwargs, ) -> LocalQuantumTask: @@ -68,6 +69,9 @@ def run( Default is 0, which means that the simulator will compute the exact results based on the task specification. Sampling is not supported for shots=0. + 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. + Default: {}. Returns: LocalQuantumTask: A LocalQuantumTask object containing the results @@ -82,7 +86,7 @@ def run( >>> device = LocalSimulator("default") >>> device.run(circuit, shots=1000) """ - result = _run_internal(task_specification, self._delegate, shots, *args, **kwargs) + result = _run_internal(task_specification, self._delegate, shots, inputs, *args, **kwargs) return LocalQuantumTask(result) @property @@ -136,10 +140,18 @@ def _run_internal( @_run_internal.register -def _(circuit: Circuit, simulator: BraketSimulator, shots: Optional[int] = None, *args, **kwargs): +def _( + circuit: Circuit, + simulator: BraketSimulator, + shots: Optional[int] = None, + inputs: Optional[Dict[str, float]] = None, + *args, + **kwargs, +): 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) elif DeviceActionType.JAQCD in simulator.properties.action: diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 8c885f53..9632dd50 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -659,7 +659,8 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "b[0] = measure q[0];", "b[1] = measure q[1];", ] - ) + ), + inputs={}, ), ), ( @@ -675,7 +676,8 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "b[0] = measure $0;", "b[1] = measure $4;", ] - ) + ), + inputs={}, ), ), ( @@ -695,7 +697,8 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "}", "#pragma braket result expectation i all", ] - ) + ), + inputs={}, ), ), ( @@ -715,7 +718,27 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "#pragma braket noise bit_flip(0.2) q[3]", "#pragma braket result expectation i(q[0])", ] - ) + ), + inputs={}, + ), + ), + ( + 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;", + "rx(0.15) q[0];", + "rx(theta) q[1];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ] + ), + inputs={}, ), ), ], diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 5159d08b..08883efb 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 @@ import braket.ir as ir from braket.annealing import Problem, ProblemType -from braket.circuits import Circuit +from braket.circuits import Circuit, FreeParameter from braket.device_schema import DeviceCapabilities from braket.devices import LocalSimulator, local_simulator from braket.ir.openqasm import Program @@ -87,7 +87,13 @@ class DummyCircuitSimulator(BraketSimulator): def run( - self, program: ir.jaqcd.Program, qubits: int, shots: Optional[int], *args, **kwargs + self, + program: ir.jaqcd.Program, + qubits: int, + shots: Optional[int], + inputs: Optional[Dict[str, float]], + *args, + **kwargs ) -> Dict[str, Any]: self._shots = shots self._qubits = qubits @@ -252,6 +258,31 @@ def test_run_gate_model(): assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) +def test_run_gate_model_inputs(): + dummy = DummyProgramSimulator() + dummy.run = Mock(return_value=GATE_MODEL_RESULT) + sim = LocalSimulator(dummy) + circuit = Circuit().rx(0, FreeParameter("theta")) + 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];", + ) + ), + inputs={"theta": 2}, + ), + 10, + ) + assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) + + def test_run_jaqcd_only(): dummy = DummyJaqcdSimulator() sim = LocalSimulator(dummy) From 1427c895259c835ac808702838c4f8ba6c4c4e59 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Mon, 26 Sep 2022 11:08:54 -0700 Subject: [PATCH 0544/1165] fix: don't provide profile name for default profile (#454) --- src/braket/aws/aws_session.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 26fda29f..b966d4aa 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -791,18 +791,20 @@ def copy_session( new_region = region or session_region 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 + profile_name = profile_name if profile_name != "default" else None if creds.method == "explicit": boto_session = boto3.Session( aws_access_key_id=creds.access_key, aws_secret_access_key=creds.secret_key, aws_session_token=creds.token, region_name=new_region, - profile_name=self.boto_session.profile_name, + profile_name=profile_name, ) else: boto_session = boto3.Session( region_name=new_region, - profile_name=self.boto_session.profile_name, + profile_name=profile_name, ) copied_session = AwsSession( boto_session=boto_session, config=config, default_bucket=default_bucket From 748c789d5e0534a3b5e5080a0cf92f2a5102dbad Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Mon, 26 Sep 2022 13:38:11 -0700 Subject: [PATCH 0545/1165] fix: add missing case for input handling (#455) --- src/braket/aws/aws_quantum_task.py | 2 +- src/braket/devices/local_simulator.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 8c21d7d0..2b142c02 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -462,7 +462,7 @@ def _( *args, **kwargs, ) -> AwsQuantumTask: - if open_qasm_program.inputs is not None: + if open_qasm_program.inputs: raise ValueError( "OpenQASM Program inputs are only currently supported in the LocalSimulator." ) diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 712bf1be..07c4ccf1 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -173,8 +173,17 @@ def _(problem: Problem, simulator: BraketSimulator, shots: Optional[int] = None, @_run_internal.register -def _(program: Program, simulator: BraketSimulator, shots: Optional[int] = None, *args, **kwargs): +def _( + program: Program, + simulator: BraketSimulator, + shots: Optional[int] = None, + inputs: Optional[Dict[str, float]] = None, + *args, + **kwargs, +): if DeviceActionType.OPENQASM not in simulator.properties.action: raise NotImplementedError(f"{type(simulator)} does not support OpenQASM programs") + program.inputs = program.inputs or {} + program.inputs.update(inputs or {}) results = simulator.run(program, shots, *args, **kwargs) return GateModelQuantumTaskResult.from_object(results) From 0342fef9b77b2a5eae6beb0300d0a11bf9ed914b Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 26 Sep 2022 21:15:46 +0000 Subject: [PATCH 0546/1165] prepare release v1.31.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 847b5929..bc95e5de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.31.0 (2022-09-26) + +### Features + + * support inputs in the device interface + +### Bug Fixes and Other Changes + + * add missing case for input handling + * don't provide profile name for default profile + ## v1.30.2 (2022-09-22) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 57f56dc8..f1131457 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.30.3.dev0" +__version__ = "1.31.0" From bead4a8df1bcd059cf377560f9ebbaad00f9925e Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 26 Sep 2022 21:15:46 +0000 Subject: [PATCH 0547/1165] update development version to v1.31.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 f1131457..24709395 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.31.0" +__version__ = "1.31.1.dev0" From d9c4ce1f8f5cb45f9dd9b1edda9661bbd4dc9992 Mon Sep 17 00:00:00 2001 From: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com> Date: Wed, 12 Oct 2022 09:10:13 -0700 Subject: [PATCH 0548/1165] fix: update inputs on program's copy (#459) --- src/braket/devices/local_simulator.py | 11 +++++++--- .../braket/devices/test_local_simulator.py | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 07c4ccf1..07fc8398 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -70,7 +70,7 @@ def run( results based on the task specification. Sampling is not supported for shots=0. 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. + IR. If the IR supports inputs, the inputs will be updated with this value. Default: {}. Returns: @@ -183,7 +183,12 @@ def _( ): if DeviceActionType.OPENQASM not in simulator.properties.action: raise NotImplementedError(f"{type(simulator)} does not support OpenQASM programs") - program.inputs = program.inputs or {} - program.inputs.update(inputs or {}) + if inputs: + inputs_copy = program.inputs.copy() if program.inputs is not None else {} + inputs_copy.update(inputs) + program = Program( + source=program.source, + inputs=inputs_copy, + ) results = simulator.run(program, shots, *args, **kwargs) return GateModelQuantumTaskResult.from_object(results) diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 08883efb..2d6d8335 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -283,6 +283,28 @@ def test_run_gate_model_inputs(): assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) +def test_run_program_model_inputs(): + dummy = DummyProgramSimulator() + dummy.run = Mock(return_value=GATE_MODEL_RESULT) + sim = LocalSimulator(dummy) + inputs = {"theta": 2} + source_string = ( + "OPENQASM 3.0;", + "input float theta;", + "bit[1] b;", + "qubit[1] q;", + "rx(theta) q[0];", + "b[0] = measure q[0];", + ) + program = Program.construct(source="\n".join(source_string), inputs=inputs) + update_inputs = {"beta": 3} + 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) + assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) + + def test_run_jaqcd_only(): dummy = DummyJaqcdSimulator() sim = LocalSimulator(dummy) From b0334e9f1a3dce94a74e74405664fce211b90cfb Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 12 Oct 2022 17:27:10 +0000 Subject: [PATCH 0549/1165] prepare release v1.31.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 bc95e5de..9c6a2e97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.31.1 (2022-10-12) + +### Bug Fixes and Other Changes + + * update inputs on program's copy + ## v1.31.0 (2022-09-26) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 24709395..8527a6be 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.31.1.dev0" +__version__ = "1.31.1" From d4a00c95007422e0ec8a23fc5ccf226211d19d33 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 12 Oct 2022 17:27:10 +0000 Subject: [PATCH 0550/1165] update development version to v1.31.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 8527a6be..a2766db3 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.31.1" +__version__ = "1.31.2.dev0" From 59817d31352258b236af9081dd57c85e78a1ac9b Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Thu, 20 Oct 2022 13:19:22 -0700 Subject: [PATCH 0551/1165] feature: Add support for pulse control (#464) Co-authored-by: Aaron Berdy Co-authored-by: Milan <30416311+krneta@users.noreply.github.com> Co-authored-by: Cody Wang --- setup.py | 7 +- src/braket/aws/aws_device.py | 62 +- src/braket/aws/aws_quantum_task.py | 28 +- src/braket/circuits/circuit.py | 33 + src/braket/circuits/free_parameter.py | 85 +-- .../circuits/free_parameter_expression.py | 127 +--- src/braket/circuits/gates.py | 91 +++ src/braket/circuits/parameterizable.py | 36 +- src/braket/parametric/__init__.py | 17 + src/braket/parametric/free_parameter.py | 97 +++ .../parametric/free_parameter_expression.py | 149 ++++ src/braket/parametric/parameterizable.py | 48 ++ src/braket/pulse/__init__.py | 22 + src/braket/pulse/ast/approximation_parser.py | 478 ++++++++++++ src/braket/pulse/ast/free_parameters.py | 52 ++ src/braket/pulse/ast/qasm_parser.py | 50 ++ src/braket/pulse/ast/qasm_transformer.py | 52 ++ src/braket/pulse/frame.py | 80 ++ src/braket/pulse/port.py | 52 ++ src/braket/pulse/pulse_sequence.py | 346 +++++++++ src/braket/pulse/pulse_sequence_trace.py | 39 + src/braket/pulse/waveforms.py | 404 ++++++++++ src/braket/timings/time_series.py | 143 ++++ test/integ_tests/test_device_creation.py | 10 +- test/integ_tests/test_pulse.py | 277 +++++++ test/unit_tests/braket/aws/test_aws_device.py | 129 ++++ .../braket/aws/test_aws_quantum_task.py | 79 ++ .../circuits/test_ascii_circuit_diagram.py | 35 + .../braket/circuits/test_circuit.py | 220 ++++++ test/unit_tests/braket/circuits/test_gates.py | 71 ++ .../test_free_parameter.py | 2 +- .../test_free_parameter_expression.py | 23 +- .../pulse/ast/test_approximation_parser.py | 692 ++++++++++++++++++ test/unit_tests/braket/pulse/test_frame.py | 72 ++ test/unit_tests/braket/pulse/test_port.py | 61 ++ .../braket/pulse/test_pulse_sequence.py | 334 +++++++++ .../unit_tests/braket/pulse/test_waveforms.py | 248 +++++++ .../braket/timings/test_time_series.py | 100 +++ 38 files changed, 4594 insertions(+), 257 deletions(-) create mode 100644 src/braket/parametric/__init__.py create mode 100644 src/braket/parametric/free_parameter.py create mode 100644 src/braket/parametric/free_parameter_expression.py create mode 100644 src/braket/parametric/parameterizable.py create mode 100644 src/braket/pulse/__init__.py create mode 100644 src/braket/pulse/ast/approximation_parser.py create mode 100644 src/braket/pulse/ast/free_parameters.py create mode 100644 src/braket/pulse/ast/qasm_parser.py create mode 100644 src/braket/pulse/ast/qasm_transformer.py create mode 100644 src/braket/pulse/frame.py create mode 100644 src/braket/pulse/port.py create mode 100644 src/braket/pulse/pulse_sequence.py create mode 100644 src/braket/pulse/pulse_sequence_trace.py create mode 100644 src/braket/pulse/waveforms.py create mode 100644 src/braket/timings/time_series.py create mode 100644 test/integ_tests/test_pulse.py rename test/unit_tests/braket/{circuits => parametric}/test_free_parameter.py (97%) rename test/unit_tests/braket/{circuits => parametric}/test_free_parameter_expression.py (81%) create mode 100644 test/unit_tests/braket/pulse/ast/test_approximation_parser.py create mode 100644 test/unit_tests/braket/pulse/test_frame.py create mode 100644 test/unit_tests/braket/pulse/test_port.py create mode 100644 test/unit_tests/braket/pulse/test_pulse_sequence.py create mode 100644 test/unit_tests/braket/pulse/test_waveforms.py create mode 100644 test/unit_tests/braket/timings/test_time_series.py diff --git a/setup.py b/setup.py index 7e9e3450..1596c61d 100644 --- a/setup.py +++ b/setup.py @@ -27,14 +27,17 @@ packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "amazon-braket-schemas>=1.10.1", - "amazon-braket-default-simulator>=1.8.1", + "amazon-braket-schemas>=1.11.0", + "amazon-braket-default-simulator>=1.9.0", + "oqpy==0.1.0", "backoff", "boltons", "boto3", "nest-asyncio", "networkx", "numpy", + "openpulse", + "openqasm3", "sympy", ], extras_require={ diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 570016cf..c196e91c 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -13,10 +13,11 @@ from __future__ import annotations +import json import os from datetime import datetime from enum import Enum -from typing import List, Optional, Union +from typing import Dict, List, Optional, Union from botocore.errorfactory import ClientError from networkx import DiGraph, complete_graph, from_edgelist @@ -28,9 +29,13 @@ from braket.circuits import Circuit 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, +) from braket.devices.device import Device from braket.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQasmProgram +from braket.pulse import Frame, Port, PulseSequence from braket.schema_common import BraketSchemaBase @@ -79,10 +84,14 @@ def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): self._topology_graph = None self._type = None self._aws_session = self._get_session_and_initialize(aws_session or AwsSession()) + self._ports = None + self._frames = None def run( self, - task_specification: Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram], + task_specification: Union[ + Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence + ], s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, shots: Optional[int] = None, poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, @@ -95,7 +104,7 @@ def run( annealing problem. Args: - task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]): + task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence]): # noqa Specification of task (circuit or annealing problem or program) to run on device. s3_destination_folder (Optional[S3DestinationFolder]): The S3 location to save the task's results to. Default is `/tasks` if evoked @@ -160,7 +169,9 @@ def run( def run_batch( self, - task_specifications: List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]], + task_specifications: List[ + Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence] + ], s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, shots: Optional[int] = None, max_parallel: Optional[int] = None, @@ -173,7 +184,7 @@ def run_batch( """Executes a batch of tasks in parallel Args: - task_specifications (List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]]): + task_specifications (List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence]]): # noqa List of circuits or annealing problems to run on device. s3_destination_folder (Optional[S3DestinationFolder]): The S3 location to save the tasks' results to. Default is `/tasks` if evoked @@ -275,6 +286,8 @@ def _populate_properties(self, session: AwsSession) -> None: self._type = AwsDeviceType(metadata.get("deviceType")) self._provider_name = metadata.get("providerName") self._properties = metadata.get("deviceCapabilities") + self._frames = None + self._ports = None @property def type(self) -> str: @@ -428,6 +441,20 @@ def __eq__(self, other): return self.arn == other.arn return NotImplemented + @property + def frames(self) -> Dict[str, Frame]: + """Returns a Dict mapping frame ids to the frame objects for predefined frames + for this device.""" + self._update_pulse_properties() + return self._frames or dict() + + @property + def ports(self) -> Dict[str, Port]: + """Returns a Dict mapping port ids to the port objects for predefined ports + for this device.""" + self._update_pulse_properties() + return self._ports or dict() + @staticmethod def get_devices( arns: Optional[List[str]] = None, @@ -508,6 +535,31 @@ def get_devices( devices.sort(key=lambda x: getattr(x, order_by)) return devices + def _update_pulse_properties(self) -> None: + if hasattr(self.properties, "pulse") and isinstance( + self.properties.pulse, PulseDeviceActionProperties + ): + if self._ports is None: + self._ports = dict() + 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() + 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: """Gets the region from a device arn. diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 2b142c02..169f4ccd 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -27,6 +27,7 @@ from braket.circuits.circuit import Circuit 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, @@ -50,6 +51,7 @@ 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.pulse.pulse_sequence import PulseSequence from braket.schema_common import BraketSchemaBase from braket.task_result import AnnealingTaskResult, GateModelTaskResult, PhotonicModelTaskResult from braket.tasks import ( @@ -79,7 +81,9 @@ class AwsQuantumTask(QuantumTask): def create( aws_session: AwsSession, device_arn: str, - task_specification: Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram], + task_specification: Union[ + Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence + ], s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, device_parameters: Dict[str, Any] = None, @@ -451,6 +455,22 @@ def _create_internal( raise TypeError("Invalid task specification type") +@_create_internal.register +def _( + pulse_sequence: PulseSequence, + aws_session: AwsSession, + create_task_kwargs: Dict[str, Any], + device_arn: str, + _device_parameters: Union[dict, BraketSchemaBase], # Not currently used for OpenQasmProgram + _disable_qubit_rewiring: bool, + *args, + **kwargs, +) -> AwsQuantumTask: + create_task_kwargs.update({"action": OpenQasmProgram(source=pulse_sequence.to_ir()).json()}) + task_arn = aws_session.create_quantum_task(**create_task_kwargs) + return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) + + @_create_internal.register def _( open_qasm_program: OpenQasmProgram, @@ -517,7 +537,11 @@ def _( qubit_reference_type = QubitReferenceType.VIRTUAL - if disable_qubit_rewiring or Instruction(StartVerbatimBox()) in circuit.instructions: + if ( + disable_qubit_rewiring + or Instruction(StartVerbatimBox()) in circuit.instructions + or any(isinstance(instruction.operator, PulseGate) for instruction in circuit.instructions) + ): qubit_reference_type = QubitReferenceType.PHYSICAL serialization_properties = OpenQASMSerializationProperties( diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index dd25fd4b..df06bbae 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -18,6 +18,7 @@ from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar, Union import numpy as np +from oqpy import Program as OqpyProgram from braket.circuits import compiler_directives from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram @@ -50,6 +51,8 @@ from braket.circuits.unitary_calculation import calculate_unitary, calculate_unitary_big_endian from braket.ir.jaqcd import Program as JaqcdProgram from braket.ir.openqasm import Program as OpenQasmProgram +from braket.pulse.ast.qasm_parser import ast_to_qasm +from braket.pulse.pulse_sequence import _validate_uniqueness SubroutineReturn = TypeVar( "SubroutineReturn", Iterable[Instruction], Instruction, ResultType, Iterable[ResultType] @@ -1168,8 +1171,38 @@ def _create_openqasm_header( f"Invalid qubit_reference_type " f"{serialization_properties.qubit_reference_type} supplied." ) + + frame_wf_declarations = self._generate_frame_wf_declarations() + if frame_wf_declarations: + ir_instructions.append(frame_wf_declarations) return ir_instructions + def _generate_frame_wf_declarations(self) -> Optional[str]: + frames = {} + waveforms = {} + from braket.circuits.gates import PulseGate + + for instruction in self.instructions: + if isinstance(instruction.operator, PulseGate): + for frame in instruction.operator.pulse_sequence._frames.values(): + _validate_uniqueness(frames, frame) + frames[frame.id] = frame + for waveform in instruction.operator.pulse_sequence._waveforms.values(): + _validate_uniqueness(waveforms, waveform) + waveforms[waveform.id] = waveform + + # 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: + program = OqpyProgram(None) + for f in declarable_frames: + program.declare(f._to_oqpy_expression()) + for wf in waveforms.values(): + program.declare(wf._to_oqpy_expression()) + ast = program.to_ast(encal=True, include_externs=False) + return ast_to_qasm(ast) + return None + def as_unitary(self) -> np.ndarray: r""" Returns the unitary matrix representation, in little endian format, of the entire circuit. diff --git a/src/braket/circuits/free_parameter.py b/src/braket/circuits/free_parameter.py index 69030e57..4b9015db 100644 --- a/src/braket/circuits/free_parameter.py +++ b/src/braket/circuits/free_parameter.py @@ -11,87 +11,4 @@ # 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 -from typing import Dict, Union - -from sympy import Symbol - -from braket.circuits.free_parameter_expression import FreeParameterExpression - - -class FreeParameter(FreeParameterExpression): - """ - 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 - for a numerical value later on. Circuits with FreeParameters present will NOT run. Values must - be substituted prior to execution. - """ - - def __init__(self, name: str): - """ - Initializes a new :class:'FreeParameter' object. - - Args: - name (str): Name of the :class:'FreeParameter'. Can be a unicode value. - - Examples: - >>> param1 = FreeParameter("theta") - >>> param1 = FreeParameter("\u03B8") - """ - self._name = Symbol(name) - super().__init__(expression=self._name) - - @property - def name(self) -> str: - """ - 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. - - Args: - parameter_values (Dict[str, Number]): A mapping of parameter to its - corresponding value. - - Returns: - 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 - - def __str__(self): - return str(self.name) - - def __hash__(self) -> int: - return hash(tuple(self.name)) - - def __eq__(self, other): - if isinstance(other, FreeParameter): - return self._name == other._name - return False - - def __repr__(self) -> str: - """ - The representation of the :class:'FreeParameter'. - - Returns: - str: The name of the class:'FreeParameter' to represent the class. - """ - return self.name - - def to_dict(self) -> dict: - return { - "__class__": self.__class__.__name__, - "name": self.name, - } - - @classmethod - def from_dict(cls, parameter: dict) -> FreeParameter: - return FreeParameter(parameter["name"]) +from braket.parametric.free_parameter import FreeParameter # noqa: F401 diff --git a/src/braket/circuits/free_parameter_expression.py b/src/braket/circuits/free_parameter_expression.py index d4062b64..0ba88340 100644 --- a/src/braket/circuits/free_parameter_expression.py +++ b/src/braket/circuits/free_parameter_expression.py @@ -10,130 +10,5 @@ # 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 -from typing import Dict, Union - -from sympy import Expr, sympify - - -class FreeParameterExpression: - """ - Class 'FreeParameterExpression' - - Objects that can take a parameter all inherit from :class:'Parameterizable'. - FreeParametersExpressions can hold FreeParameters that can later be - swapped out for a number. Circuits with FreeParameters present will NOT run. Values must - be substituted prior to execution. - """ - - def __init__(self, expression: Union[FreeParameterExpression, Number, Expr]): - """ - 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. - - Args: - expression (Union[FreeParameterExpression, Number, Expr]): The expression to use. - - Examples: - >>> expression_1 = FreeParameter("theta") * FreeParameter("alpha") - >>> expression_2 = 1 + FreeParameter("beta") + 2 * FreeParameter("alpha") - """ - if isinstance(expression, FreeParameterExpression): - self._expression = expression.expression - elif isinstance(expression, (Number, Expr)): - self._expression = expression - else: - raise NotImplementedError - - @property - def expression(self) -> Union[Number, Expr]: - """Gets the expression. - Returns: - Union[Number, Expr]: The expression for the FreeParameterExpression. - """ - return self._expression - - def subs( - self, parameter_values: Dict[str, Number] - ) -> Union[FreeParameterExpression, Number, Expr]: - """ - Similar to a substitution in Sympy. Parameters are swapped for corresponding values or - expressions from the dictionary. - - Args: - parameter_values (Dict[str, Number]): A mapping of parameters to their corresponding - values to be assigned. - - Returns: - 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() - for key, val in parameter_values.items(): - if issubclass(type(key), FreeParameterExpression): - new_parameter_values[key.expression] = val - else: - new_parameter_values[key] = val - - subbed_expr = self._expression.subs(new_parameter_values) - if subbed_expr.is_Number: - return subbed_expr - else: - return FreeParameterExpression(subbed_expr) - - def __add__(self, other): - if issubclass(type(other), FreeParameterExpression): - return FreeParameterExpression(self.expression + other.expression) - else: - return FreeParameterExpression(self.expression + other) - - def __radd__(self, other): - return FreeParameterExpression(other + self.expression) - - def __sub__(self, other): - if issubclass(type(other), FreeParameterExpression): - return FreeParameterExpression(self.expression - other.expression) - else: - return FreeParameterExpression(self.expression - other) - - def __rsub__(self, other): - return FreeParameterExpression(other - self.expression) - - def __mul__(self, other): - if issubclass(type(other), FreeParameterExpression): - return FreeParameterExpression(self.expression * other.expression) - else: - return FreeParameterExpression(self.expression * other) - - def __rmul__(self, other): - return FreeParameterExpression(other * self.expression) - - def __pow__(self, other, modulo=None): - if issubclass(type(other), FreeParameterExpression): - return FreeParameterExpression(self.expression**other.expression) - else: - return FreeParameterExpression(self.expression**other) - - def __rpow__(self, other): - return FreeParameterExpression(other**self.expression) - - def __neg__(self): - return FreeParameterExpression(-1 * self.expression) - - def __eq__(self, other): - if isinstance(other, FreeParameterExpression): - return sympify(self.expression).equals(sympify(other.expression)) - return False - - def __repr__(self) -> str: - """ - The representation of the :class:'FreeParameterExpression'. - - Returns: - str: The expression of the class:'FreeParameterExpression' to represent the class. - """ - return repr(self.expression) +from braket.parametric.free_parameter_expression import FreeParameterExpression # noqa: F401 diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 0f076a29..dfe2cfc5 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -11,17 +11,23 @@ # 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 copy import deepcopy from typing import Any, Iterable, List, Union import numpy as np +from oqpy import Program from sympy import Float import braket.ir.jaqcd as ir from braket.circuits import circuit from braket.circuits.angled_gate import AngledGate, DoubleAngledGate +from braket.circuits.free_parameter import FreeParameter from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction +from braket.circuits.parameterizable import Parameterizable from braket.circuits.quantum_operator_helpers import ( is_unitary, verify_quantum_operator_matrix_dimensions, @@ -29,6 +35,8 @@ from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput from braket.circuits.serialization import OpenQASMSerializationProperties +from braket.pulse.ast.qasm_parser import ast_to_qasm +from braket.pulse.pulse_sequence import PulseSequence """ To add a new gate: @@ -2184,6 +2192,89 @@ def unitary(targets: QubitSet, matrix: np.ndarray, display_name: str = "U") -> I Gate.register_gate(Unitary) +class PulseGate(Gate, Parameterizable): + """Arbitrary pulse gate which provides the ability to embed custom pulse sequences + within circuits. + + Args: + pulse_sequence (PulseSequence): PulseSequence to embed within the circuit. + qubit_count (int): The number of qubits this pulse gate operates on. + display_name (str): Name to be used for an instance of this pulse gate + for circuit diagrams. Defaults to `PG`. + """ + + def __init__(self, pulse_sequence: PulseSequence, qubit_count: int, display_name: str = "PG"): + if pulse_sequence._capture_v0_count > 0: + raise ValueError( + "The supplied pulse sequence contains capture instructions which " + "can not be embedded in a PulseGate." + ) + self._pulse_sequence = deepcopy(pulse_sequence) + super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) + + @property + def pulse_sequence(self) -> PulseSequence: + """PulseSequence: The underlying PulseSequence of this gate.""" + return self._pulse_sequence + + @property + def parameters(self) -> List[FreeParameter]: + """Returns the list of `FreeParameter`s associated with the gate.""" + return list(self._pulse_sequence.parameters) + + def bind_values(self, **kwargs) -> PulseGate: + """Takes in parameters and returns an object with specified parameters + replaced with their values. + + Returns: + PulseGate: A copy of this gate with the requested parameters bound. + """ + new_pulse_sequence = self._pulse_sequence.make_bound_pulse_sequence(kwargs) + return PulseGate(new_pulse_sequence, self.qubit_count, self.ascii_symbols[0]) + + def to_matrix(self) -> np.ndarray: + raise NotImplementedError("PulseGate does not support conversion to a matrix.") + + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs + ) -> str: + new_program = Program(None) + new_program += self._pulse_sequence._program + # Suppress declaration of frame and waveform vars as they have already been declared + for v in list(new_program.undeclared_vars.values()): + new_program._mark_var_declared(v) + return ast_to_qasm(new_program.to_ast(include_externs=False, encal=True)) + + @staticmethod + @circuit.subroutine(register=True) + def pulse_gate( + targets: QubitSet, pulse_sequence: PulseSequence, display_name: str = "PG" + ) -> Instruction: + """Arbitrary pulse gate which provides the ability to embed custom pulse sequences + within circuits. + + Args: + targets (QubitSet): Target qubits. Note: These are only for representational purposes. + The actual targets are determined by the frames used in the pulse sequence. + pulse_sequence (PulseSequence): PulseSequence to embed within the circuit. + display_name (str): Name to be used for an instance of this pulse gate + for circuit diagrams. Defaults to `PG`. + + Returns: + Instruction: Pulse gate instruction. + + Examples: + >>> pulse_seq = PulseSequence().set_frequency(frame, frequency).... + >>> circ = Circuit().pulse_gate(pulse_sequence=pulse_seq, targets=[0]) + """ + return Instruction( + PulseGate(pulse_sequence, len(QubitSet(targets)), display_name), target=targets + ) + + +Gate.register_gate(PulseGate) + + def angled_ascii_characters(gate: str, angle: Union[FreeParameterExpression, float]) -> str: """ Generates a formatted ascii representation of an angled gate. diff --git a/src/braket/circuits/parameterizable.py b/src/braket/circuits/parameterizable.py index 10f9e854..a4a9925b 100644 --- a/src/braket/circuits/parameterizable.py +++ b/src/braket/circuits/parameterizable.py @@ -11,38 +11,4 @@ # 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 typing import Any, List, Union - -from braket.circuits.free_parameter import FreeParameter -from braket.circuits.free_parameter_expression import FreeParameterExpression - - -class Parameterizable(ABC): - """ - A parameterized object is the abstract definition of an object - that can take in FreeParameterExpressions. - """ - - @property - @abstractmethod - def parameters(self) -> List[Union[FreeParameterExpression, FreeParameter, float]]: - """Get the parameters. - - Returns: - List[Union[FreeParameterExpression, FreeParameter, float]]: The parameters associated - with the object, either unbound free parameter expressions or bound values. The order - of the parameters is determined by the subclass. - """ - - @abstractmethod - def bind_values(self, **kwargs) -> Any: - """ - Takes in parameters and returns an object with specified parameters - replaced with their values. - - Returns: - Any: The result object will depend on the implementation of the object being bound. - """ +from braket.parametric.parameterizable import Parameterizable # noqa: F401 diff --git a/src/braket/parametric/__init__.py b/src/braket/parametric/__init__.py new file mode 100644 index 00000000..054f0af7 --- /dev/null +++ b/src/braket/parametric/__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.parametric.free_parameter import FreeParameter # noqa: F401 +from braket.parametric.free_parameter_expression import FreeParameterExpression # noqa: F401 +from braket.parametric.parameterizable import Parameterizable # noqa: F401 diff --git a/src/braket/parametric/free_parameter.py b/src/braket/parametric/free_parameter.py new file mode 100644 index 00000000..eadb6b14 --- /dev/null +++ b/src/braket/parametric/free_parameter.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 __future__ import annotations + +from numbers import Number +from typing import Dict, Union + +from sympy import Symbol + +from braket.parametric.free_parameter_expression import FreeParameterExpression + + +class FreeParameter(FreeParameterExpression): + """ + 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 + for a numerical value later on. Circuits with FreeParameters present will NOT run. Values must + be substituted prior to execution. + """ + + def __init__(self, name: str): + """ + Initializes a new :class:'FreeParameter' object. + + Args: + name (str): Name of the :class:'FreeParameter'. Can be a unicode value. + + Examples: + >>> param1 = FreeParameter("theta") + >>> param1 = FreeParameter("\u03B8") + """ + self._name = Symbol(name) + super().__init__(expression=self._name) + + @property + def name(self) -> str: + """ + 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. + + Args: + parameter_values (Dict[str, Number]): A mapping of parameter to its + corresponding value. + + Returns: + 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 + + def __str__(self): + return str(self.name) + + def __hash__(self) -> int: + return hash(tuple(self.name)) + + def __eq__(self, other): + if isinstance(other, FreeParameter): + return self._name == other._name + return False + + def __repr__(self) -> str: + """ + The representation of the :class:'FreeParameter'. + + Returns: + str: The name of the class:'FreeParameter' to represent the class. + """ + return self.name + + def to_dict(self) -> dict: + return { + "__class__": self.__class__.__name__, + "name": self.name, + } + + @classmethod + def from_dict(cls, parameter: dict) -> FreeParameter: + return FreeParameter(parameter["name"]) diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py new file mode 100644 index 00000000..3ccaf4e0 --- /dev/null +++ b/src/braket/parametric/free_parameter_expression.py @@ -0,0 +1,149 @@ +# 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 +from typing import Any, Dict, Union + +from sympy import Expr, Float, sympify + + +class FreeParameterExpression: + """ + Class 'FreeParameterExpression' + + Objects that can take a parameter all inherit from :class:'Parameterizable'. + FreeParametersExpressions can hold FreeParameters that can later be + swapped out for a number. Circuits or PulseSequences with FreeParameters + present will NOT run. Values must be substituted prior to execution. + """ + + def __init__(self, expression: Union[FreeParameterExpression, Number, Expr]): + """ + 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. + + Args: + expression (Union[FreeParameterExpression, Number, Expr]): The expression to use. + + Examples: + >>> expression_1 = FreeParameter("theta") * FreeParameter("alpha") + >>> expression_2 = 1 + FreeParameter("beta") + 2 * FreeParameter("alpha") + """ + if isinstance(expression, FreeParameterExpression): + self._expression = expression.expression + elif isinstance(expression, (Number, Expr)): + self._expression = expression + else: + raise NotImplementedError + + @property + def expression(self) -> Union[Number, Expr]: + """Gets the expression. + Returns: + Union[Number, Expr]: The expression for the FreeParameterExpression. + """ + return self._expression + + def subs( + self, parameter_values: Dict[str, Number] + ) -> Union[FreeParameterExpression, Number, Expr]: + """ + Similar to a substitution in Sympy. Parameters are swapped for corresponding values or + expressions from the dictionary. + + Args: + parameter_values (Dict[str, Number]): A mapping of parameters to their corresponding + values to be assigned. + + Returns: + 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() + for key, val in parameter_values.items(): + if issubclass(type(key), FreeParameterExpression): + new_parameter_values[key.expression] = val + else: + new_parameter_values[key] = val + + subbed_expr = self._expression.subs(new_parameter_values) + if subbed_expr.is_Number: + return subbed_expr + else: + return FreeParameterExpression(subbed_expr) + + def __add__(self, other): + if issubclass(type(other), FreeParameterExpression): + return FreeParameterExpression(self.expression + other.expression) + else: + return FreeParameterExpression(self.expression + other) + + def __radd__(self, other): + return FreeParameterExpression(other + self.expression) + + def __sub__(self, other): + if issubclass(type(other), FreeParameterExpression): + return FreeParameterExpression(self.expression - other.expression) + else: + return FreeParameterExpression(self.expression - other) + + def __rsub__(self, other): + return FreeParameterExpression(other - self.expression) + + def __mul__(self, other): + if issubclass(type(other), FreeParameterExpression): + return FreeParameterExpression(self.expression * other.expression) + else: + return FreeParameterExpression(self.expression * other) + + def __rmul__(self, other): + return FreeParameterExpression(other * self.expression) + + def __pow__(self, other, modulo=None): + if issubclass(type(other), FreeParameterExpression): + return FreeParameterExpression(self.expression**other.expression) + else: + return FreeParameterExpression(self.expression**other) + + def __rpow__(self, other): + return FreeParameterExpression(other**self.expression) + + def __neg__(self): + return FreeParameterExpression(-1 * self.expression) + + def __eq__(self, other): + if isinstance(other, FreeParameterExpression): + return sympify(self.expression).equals(sympify(other.expression)) + return False + + def __repr__(self) -> str: + """ + The representation of the :class:'FreeParameterExpression'. + + Returns: + str: The expression of the class:'FreeParameterExpression' to represent the class. + """ + return repr(self.expression) + + +def subs_if_free_parameter(parameter: Any, **kwargs) -> Any: + if isinstance(parameter, FreeParameterExpression): + substituted = parameter.subs(kwargs) + if isinstance(substituted, Float): + substituted = float(substituted) + return substituted + return parameter diff --git a/src/braket/parametric/parameterizable.py b/src/braket/parametric/parameterizable.py new file mode 100644 index 00000000..45c7561f --- /dev/null +++ b/src/braket/parametric/parameterizable.py @@ -0,0 +1,48 @@ +# 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 typing import Any, List, Union + +from braket.parametric.free_parameter import FreeParameter +from braket.parametric.free_parameter_expression import FreeParameterExpression + + +class Parameterizable(ABC): + """ + A parameterized object is the abstract definition of an object + that can take in FreeParameterExpressions. + """ + + @property + @abstractmethod + def parameters(self) -> List[Union[FreeParameterExpression, FreeParameter, float]]: + """Get the parameters. + + Returns: + List[Union[FreeParameterExpression, FreeParameter, float]]: The parameters associated + with the object, either unbound free parameter expressions or bound values. The order + of the parameters is determined by the subclass. + """ + + @abstractmethod + def bind_values(self, **kwargs) -> Any: + """ + Takes in parameters and returns an object with specified parameters + replaced with their values. + + Returns: + Any: The result object will depend on the implementation of the object being bound. + """ diff --git a/src/braket/pulse/__init__.py b/src/braket/pulse/__init__.py new file mode 100644 index 00000000..01ef6689 --- /dev/null +++ b/src/braket/pulse/__init__.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. + +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, + GaussianWaveform, +) diff --git a/src/braket/pulse/ast/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py new file mode 100644 index 00000000..bde359c5 --- /dev/null +++ b/src/braket/pulse/ast/approximation_parser.py @@ -0,0 +1,478 @@ +# 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 import defaultdict +from dataclasses import dataclass +from typing import Any, Dict, KeysView, List, Optional, Union + +import numpy as np +from openpulse import ast +from openqasm3.visitor import QASMVisitor +from oqpy import Program + +from braket.pulse.frame import Frame +from braket.pulse.waveforms import ( + ConstantWaveform, + DragGaussianWaveform, + GaussianWaveform, + Waveform, +) +from braket.timings.time_series import TimeSeries + + +@dataclass +class _FrameState: + dt: float + frequency: float = 0 + phase: float = 0 + current_time: float = 0 + amplitude: float = 0 + scale: float = 1 + + +@dataclass +class _ParseState: + variables: dict + frame_data: Dict[str, _FrameState] + + +class _ApproximationParser(QASMVisitor[_ParseState]): + """Walk the AST and build the output signal amplitude, frequency and phases + for each channel.""" + + TIME_UNIT_TO_EXP = {"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)) + self.visit(program.to_ast(include_externs=False), context) + + 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. + """ + return super().visit(node, context) + + def _get_frame_parameters( + self, parameters: List[ast.Expression], context: _ParseState + ) -> Union[KeysView, List[str]]: + frame_ids = [] + for expression in parameters: + frame_ids.append(self.visit(expression, context)) + return frame_ids + + def _delay_frame(self, frame_id: str, to_delay_time: float, context: _ParseState) -> None: + frame_data = context.frame_data[frame_id] + if to_delay_time >= frame_data.current_time + frame_data.dt: + start_time = frame_data.current_time + self.amplitudes[frame_id].put(start_time, 0) + self.frequencies[frame_id].put(start_time, frame_data.frequency) + self.phases[frame_id].put(start_time, frame_data.phase) + if to_delay_time >= frame_data.current_time + (2 * frame_data.dt): + end_time = to_delay_time - frame_data.dt + self.amplitudes[frame_id].put(end_time, 0) + self.frequencies[frame_id].put(end_time, frame_data.frequency) + self.phases[frame_id].put(end_time, frame_data.phase) + context.frame_data[frame_id].current_time = to_delay_time + + 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. + """ + for statement in node.statements: + self.visit(statement, context) + + 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. + """ + return self.visit(node.expression, context) # need to check + + def visit_ClassicalDeclaration( + self, node: ast.ClassicalDeclaration, context: _ParseState + ) -> 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. + """ + identifier = self.visit(node.identifier, context) + if type(node.type) == ast.WaveformType: + context.variables[identifier] = self.visit(node.init_expression, context) + elif type(node.type) == ast.FrameType: + pass + elif type(node.type) == ast.PortType: + pass + else: + # Only Classical type? + # for instance: type(node.type) == ast.IntType: + raise NotImplementedError + # context.variables[identifier] = self.visit(node.init_expression, context) + + def visit_DelayInstruction(self, node: ast.DelayInstruction, context: _ParseState) -> None: + """Visit a Delay Instruction. + node.duration, node.qubits + delay[100ns] $0; + Args: + node (ast.DelayInstruction): The classical declaration. + context (_ParseState): The parse state context. + """ + duration = self.visit(node.duration, context) + frames = self._get_frame_parameters(node.qubits, context) + for frame_id in frames: + frame_data = context.frame_data[frame_id] + self._delay_frame(frame_id, frame_data.current_time + duration, context) + + def visit_QuantumBarrier(self, node: ast.QuantumBarrier, context: _ParseState) -> None: + """Visit a Quantum Barrier. + barrier $0; + barrier; + barrier frame, frame1; + Args: + node (ast.QuantumBarrier): The quantum barrier. + context (_ParseState): The parse state context. + """ + frames = self._get_frame_parameters(node.qubits, context) + 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]) + # 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) + barrier_time = _ceil_approx(max_time / lcm) * lcm + for frame_id in frames: + self._delay_frame(frame_id, barrier_time, context) + + def visit_FunctionCall(self, node: ast.FunctionCall, context: _ParseState) -> Any: + """Visit a Quantum Barrier. + node.name, node.arguments + f(args,arg2) + Args: + node (ast.FunctionCall): The function call. + context (_ParseState): The parse state context. + """ + func_name = node.name.name + return getattr(self, func_name)(node, context) + + def visit_Identifier(self, node: ast.Identifier, context: _ParseState) -> Any: + """Visit Identifier. + node.name + x + Args: + node (ast.Identifier): The identifier. + context (_ParseState): The parse state context. + """ + if node.name in context.variables: + return context.variables[node.name] + else: + return node.name + + def visit_UnaryExpression(self, node: ast.UnaryExpression, context: _ParseState) -> bool: + """Visit Unary Expression. + node.op, node.expression + ~ ! - + Args: + node (ast.UnaryExpression): The unary expression. + context (_ParseState): The parse state context. + """ + # context.print(node.op.name) + if node.op == ast.UnaryOperator["-"]: + return -1 * self.visit(node.expression, context) + elif node.op == ast.UnaryOperator["!"]: + return not self.visit(node.expression, context) + elif node.op == ast.UnaryOperator["~"]: + return ~self.visit(node.expression, context) + else: + raise NotImplementedError + + # flake8: noqa: C901 + def visit_BinaryExpression(self, node: ast.BinaryExpression, context: _ParseState) -> Any: + """Visit Binary Expression. + node.lhs, node.rhs, node.op + 1+2 + a.b + > < >= <= == != && || | ^ & << >> + - * / % ** . + Args: + node (ast.BinaryExpression): The binary expression. + context (_ParseState): The parse state context. + """ + lhs = self.visit(node.lhs, context) + rhs = self.visit(node.rhs, context) + + op = ast.BinaryOperator + + if node.op == op["+"]: + return lhs + rhs + elif node.op == op["-"]: + return lhs - rhs + elif node.op == op["*"]: + return lhs * rhs + elif node.op == op["/"]: + return lhs / rhs + elif node.op == op["%"]: + return lhs % rhs + elif node.op == op["**"]: + return lhs**rhs + elif node.op == op[">"]: + return lhs > rhs + elif node.op == op["<"]: + return lhs < rhs + elif node.op == op[">="]: + return lhs >= rhs + elif node.op == op["<="]: + return lhs <= rhs + elif node.op == op["=="]: + return lhs == rhs + elif node.op == op["!="]: + return lhs != rhs + elif node.op == op["&&"]: + return lhs and rhs + elif node.op == op["||"]: + return lhs or rhs + elif node.op == op["|"]: + return lhs | rhs + elif node.op == op["^"]: + return lhs ^ rhs + elif node.op == op["&"]: + return lhs & rhs + elif node.op == op["<<"]: + return lhs << rhs + elif node.op == op[">>"]: + return lhs >> rhs + else: + # Need more + # if node.op == ast.BinaryOperator["."]: + # What to do? + raise NotImplementedError + + def visit_ArrayLiteral(self, node: ast.ArrayLiteral, context: _ParseState) -> Any: + """Visit Array Literal. + node.values + {1,2,4} + Args: + node (ast.ArrayLiteral): The array literal. + context (_ParseState): The parse state context. + """ + return [self.visit(e, context) for e in node.values] + + def visit_IntegerLiteral(self, node: ast.IntegerLiteral, context: _ParseState) -> Any: + """Visit Integer Literal. + node.value + 1 + Args: + node (ast.IntegerLiteral): The integer literal. + context (_ParseState): The parse state context. + """ + return int(node.value) + + def visit_ImaginaryLiteral(self, node: ast.ImaginaryLiteral, context: _ParseState) -> Any: + """Visit Imaginary Number Literal. + node.value + 1.3im + Args: + node (ast.visit_ImaginaryLiteral): The imaginary number literal. + context (_ParseState): The parse state context. + """ + return complex(node.value * 1j) + + def visit_FloatLiteral(self, node: ast.FloatLiteral, context: _ParseState) -> Any: + """Visit Float Literal. + node.value + 1.1 + Args: + node (ast.FloatLiteral): The float literal. + context (_ParseState): The parse state context. + """ + return float(node.value) + + def visit_BooleanLiteral(self, node: ast.BooleanLiteral, context: _ParseState) -> Any: + """Visit Boolean Literal. + node.value + true + Args: + node (ast.BooleanLiteral): The boolean literal. + context (_ParseState): The parse state context. + """ + return True if node.value else False + + def visit_DurationLiteral(self, node: ast.DurationLiteral, context: _ParseState) -> Any: + """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. + """ + if node.unit.name not in self.TIME_UNIT_TO_EXP: + raise ValueError(f"Unexpected duration specified: {node.unit.name}:{node.unit.value}") + multiplier = 10 ** (-3 * self.TIME_UNIT_TO_EXP[node.unit.name]) + return multiplier * node.value + + # The following are function call declarations supported by the parser. + + 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. + """ + frame = self.visit(node.arguments[0], context) + value = self.visit(node.arguments[1], context) + context.frame_data[frame].frequency = value + + 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. + """ + frame = self.visit(node.arguments[0], context) + value = self.visit(node.arguments[1], context) + context.frame_data[frame].frequency += value + + 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. + """ + frame = self.visit(node.arguments[0], context) + value = self.visit(node.arguments[1], context) + context.frame_data[frame].phase = value + + 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. + """ + frame = self.visit(node.arguments[0], context) + value = self.visit(node.arguments[1], context) + context.frame_data[frame].phase += value + + 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. + """ + frame = self.visit(node.arguments[0], context) + value = self.visit(node.arguments[1], context) + context.frame_data[frame].scale = value + + 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. + """ + pass + + 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. + """ + 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)): + amps = self.visit(node.arguments[1], context) + if isinstance(amps, Waveform): + amps = amps.sample(context.frame_data[frame_id].dt) + else: + raise NotImplementedError + frame_data = context.frame_data[frame_id] + for value in amps: + self.amplitudes[frame_id].put( + frame_data.current_time, complex(frame_data.scale * value) + ) + self.frequencies[frame_id].put(frame_data.current_time, frame_data.frequency) + self.phases[frame_id].put(frame_data.current_time, frame_data.phase) + frame_data.current_time += frame_data.dt + + 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. + """ + args = [self.visit(arg, context) for arg in node.arguments] + return ConstantWaveform(*args) + + 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. + """ + args = [self.visit(arg, context) for arg in node.arguments] + return GaussianWaveform(*args) + + 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. + """ + args = [self.visit(arg, context) for arg in node.arguments] + return DragGaussianWaveform(*args) + + +def _init_frame_data(frames: Dict[str, Frame]) -> Dict[str, _FrameState]: + frame_states = dict() + for frameId, frame in frames.items(): + frame_states[frameId] = _FrameState(frame.port.dt, frame.frequency, frame.phase) + return frame_states + + +def _lcm_floats(*dts: List[float]) -> float: + """Return the least common multiple of time increments of a list of frames + A time increment is the inverse of the corresponding sample rate which is considered + an integer LCM of rational numbers is lcm = (LCM of numerators) / (GCD of denominators) + Hence the LCM of dts is 1/gcd([sample rates]) + + Args: + *dts (List[float]): list of time resolutions + """ + + sample_rates = [round(1 / dt) for dt in dts] + res_gcd = sample_rates[0] + for sr in sample_rates[1:]: + res_gcd = np.gcd(res_gcd, sr) + return 1 / res_gcd + + +def _ceil_approx(number: float) -> int: + return int(number) + 1 if abs(number - int(number)) > 0.001 else int(number) diff --git a/src/braket/pulse/ast/free_parameters.py b/src/braket/pulse/ast/free_parameters.py new file mode 100644 index 00000000..922f0fb0 --- /dev/null +++ b/src/braket/pulse/ast/free_parameters.py @@ -0,0 +1,52 @@ +# 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 typing import Dict + +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 + + +class _FreeParameterTransformer(QASMTransformer): + """Walk the AST and evaluate FreeParameterExpressions.""" + + def __init__(self, param_values: Dict[str, float]): + self.param_values = param_values + super().__init__() + + def visit__FreeParameterExpressionIdentifier(self, identifier: ast.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): + duration = duration_literal.value + if not isinstance(duration, FreeParameterExpression): + return duration_literal + return DurationLiteral(duration.subs(self.param_values), duration_literal.unit) diff --git a/src/braket/pulse/ast/qasm_parser.py b/src/braket/pulse/ast/qasm_parser.py new file mode 100644 index 00000000..e192242e --- /dev/null +++ b/src/braket/pulse/ast/qasm_parser.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. +import io + +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.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def visit__FreeParameterExpressionIdentifier( + self, node: ast.Identifier, context: PrinterState + ) -> None: + self.stream.write(str(node.expression.expression)) + + def visit_DurationLiteral(self, node: DurationLiteral, context: PrinterState): + duration = node.value + if isinstance(duration, FreeParameterExpression): + self.stream.write(f"({duration.expression}){node.unit.name}") + else: + super().visit_DurationLiteral(node, context) + + def visit_ClassicalDeclaration(self, node: ast.ClassicalDeclaration, context: PrinterState): + # Skip port declarations in output + if not isinstance(node.type, ast.PortType): + super().visit_ClassicalDeclaration(node, context) + + +def ast_to_qasm(ast: ast.Program) -> str: + out = io.StringIO() + _PulsePrinter(out, indent=" ").visit(ast) + return out.getvalue().strip() diff --git a/src/braket/pulse/ast/qasm_transformer.py b/src/braket/pulse/ast/qasm_transformer.py new file mode 100644 index 00000000..1e3a7433 --- /dev/null +++ b/src/braket/pulse/ast/qasm_transformer.py @@ -0,0 +1,52 @@ +# 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 typing import Optional + +from openpulse import ast +from openqasm3.visitor import QASMTransformer + + +class _IRQASMTransformer(QASMTransformer): + """ + 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. + """ + + def __init__(self, register_identifier: Optional[str] = None): + self._register_identifier = register_identifier + self._capture_v0_count = 0 + super().__init__() + + def visit_ExpressionStatement(self, expression_statement: ast.ExpressionStatement): + if ( + isinstance(expression_statement.expression, ast.FunctionCall) + and expression_statement.expression.name.name == "capture_v0" + and 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 diff --git a/src/braket/pulse/frame.py b/src/braket/pulse/frame.py new file mode 100644 index 00000000..aab243a4 --- /dev/null +++ b/src/braket/pulse/frame.py @@ -0,0 +1,80 @@ +# 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 typing import Any, Dict, Optional + +from oqpy import FrameVar as OQFrame +from oqpy.base import OQPyExpression + +from braket.pulse.port import Port + + +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. + """ + + def __init__( + self, + frame_id: str, + port: Port, + frequency: float, + phase: float = 0, + is_predefined: bool = False, + properties: Optional[Dict[str, Any]] = None, + ): + """ + Args: + frame_id (str): str identifying a unique frame. + port (Port): port that this frame is attached to. + frequency (float): frequency to which this frame should be initialized. + phase (float): phase to which this frame should be initialized. Defaults to 0. + is_predefined (bool): bool indicating whether this is a predefined frame on + the device. Defaults to False. + properties (Optional[Dict[str, Any]]): Dict containing properties of this frame. + Defaults to None. + """ + self._frame_id = frame_id + self.port = port + self.frequency = frequency + self.phase = phase + self.is_predefined = is_predefined + self.properties = properties + + @property + def id(self) -> str: + """Returns a str indicating the frame id.""" + return self._frame_id + + def __eq__(self, other) -> bool: + return ( + ( + (self.id == other.id) + and (self.port == other.port) + and math.isclose(self.frequency, other.frequency) + and math.isclose(self.phase, other.phase) + ) + if isinstance(other, Frame) + else False + ) + + def _to_oqpy_expression(self) -> OQPyExpression: + return OQFrame( + port=self.port._to_oqpy_expression(), + frequency=self.frequency, + phase=self.phase, + name=self.id, + needs_declaration=not self.is_predefined, + ) diff --git a/src/braket/pulse/port.py b/src/braket/pulse/port.py new file mode 100644 index 00000000..37383687 --- /dev/null +++ b/src/braket/pulse/port.py @@ -0,0 +1,52 @@ +# 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 typing import Any, Dict, Optional + +from oqpy import PortVar +from oqpy.base import OQPyExpression + + +class Port: + """ + 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): + """ + 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. + properties (Optional[Dict[str, Any]]): Dict containing properties of + this port. Defaults to None. + """ + self._port_id = port_id + self._dt = dt + self.properties = properties + + @property + def id(self) -> str: + """Returns a str indicating the port id.""" + return self._port_id + + @property + 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: + return self.id == other.id if isinstance(other, Port) else False + + def _to_oqpy_expression(self) -> OQPyExpression: + return PortVar(name=self.id) diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py new file mode 100644 index 00000000..258120cd --- /dev/null +++ b/src/braket/pulse/pulse_sequence.py @@ -0,0 +1,346 @@ +# 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 copy import deepcopy +from typing import Any, Dict, List, Set, Union + +from openpulse import ast +from oqpy import BitVar, 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.qasm_parser import ast_to_qasm +from braket.pulse.ast.qasm_transformer import _IRQASMTransformer +from braket.pulse.frame import Frame +from braket.pulse.pulse_sequence_trace import PulseSequenceTrace +from braket.pulse.waveforms import Waveform + + +class PulseSequence: + """ + A representation of a collection of instructions to be performed on a quantum device + and the requested results. + """ + + def __init__(self): + self._capture_v0_count = 0 + self._program = Program() + self._frames = {} + self._waveforms = {} + self._free_parameters = set() + + def to_time_trace(self) -> PulseSequenceTrace: + """Generate an approximate trace of the amplitude, frequency, phase for each frame + contained in the PulseSequence, under the action of the instructions contained in + the pulse sequence. + + Returns: + PulseSequenceTrace: The approximation information with each attribute + (amplitude, frequency and phase) mapping a str (frame id) to a TimeSeries + (containing the time evolution of that attribute). + """ + parser = _ApproximationParser(deepcopy(self._program), self._frames) + return PulseSequenceTrace( + amplitudes=parser.amplitudes, frequencies=parser.frequencies, phases=parser.phases + ) + + @property + def parameters(self) -> Set[FreeParameter]: + """Returns the set of `FreeParameter`s in the PulseSequence.""" + return self._free_parameters.copy() + + 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. + + Args: + frame (Frame): Frame for which the frequency needs to be set. + frequency (Union[float, FreeParameterExpression]): frequency value to set + for the specified frame. + + Returns: + PulseSequence: self, with the instruction added. + """ + + _validate_uniqueness(self._frames, frame) + self._program.set_frequency(frame=frame, freq=self._format_parameter_ast(frequency)) + self._frames[frame.id] = frame + return self + + 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. + + Args: + frame (Frame): Frame for which the frequency needs to be shifted. + frequency (Union[float, FreeParameterExpression]): frequency value by which to shift + the frequency for the specified frame. + + Returns: + PulseSequence: self, with the instruction added. + """ + _validate_uniqueness(self._frames, frame) + self._program.shift_frequency(frame=frame, freq=self._format_parameter_ast(frequency)) + self._frames[frame.id] = frame + return self + + 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. + + Args: + frame (Frame): Frame for which the frequency needs to be set. + phase (Union[float, FreeParameterExpression]): phase value to set + for the specified frame. + + Returns: + PulseSequence: self, with the instruction added. + """ + _validate_uniqueness(self._frames, frame) + self._program.set_phase(frame=frame, phase=self._format_parameter_ast(phase)) + self._frames[frame.id] = frame + return self + + 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. + + Args: + frame (Frame): Frame for which the phase needs to be shifted. + phase (Union[float, FreeParameterExpression]): phase value by which to shift + the phase for the specified frame. + + Returns: + PulseSequence: self, with the instruction added. + """ + _validate_uniqueness(self._frames, frame) + self._program.shift_phase(frame=frame, phase=self._format_parameter_ast(phase)) + self._frames[frame.id] = frame + return self + + 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. + + Args: + frame (Frame): Frame for which the scale needs to be set. + scale (Union[float, FreeParameterExpression]): scale value to set + on the specified frame. + + Returns: + PulseSequence: self, with the instruction added. + """ + _validate_uniqueness(self._frames, frame) + self._program.set_scale(frame=frame, scale=self._format_parameter_ast(scale)) + self._frames[frame.id] = frame + return self + + def delay( + self, frames: Union[Frame, List[Frame]], duration: Union[float, FreeParameterExpression] + ) -> PulseSequence: + """ + Adds an instruction to advance the frame clock by the specified `duration` value. + + Args: + frames (Union[Frame, List[Frame]]): 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. + """ + if not isinstance(frames, list): + frames = [frames] + if isinstance(duration, FreeParameterExpression): + for p in duration.expression.free_symbols: + self._free_parameters.add(FreeParameter(p.name)) + duration = OQDurationLiteral(duration) + _validate_uniqueness(self._frames, frames) + self._program.delay(time=duration, qubits_or_frames=frames) + for frame in frames: + self._frames[frame.id] = frame + return self + + def barrier(self, frames: List[Frame]) -> PulseSequence: + """ + Adds an instruction to align the frame clocks to the latest time across all the specified + frames. + + Args: + frames (List[Frame]): Frames across which the frame clocks need to be aligned. + + Returns: + PulseSequence: self, with the instruction added. + """ + _validate_uniqueness(self._frames, frames) + self._program.barrier(qubits_or_frames=frames) + for frame in frames: + self._frames[frame.id] = frame + return self + + def play(self, frame: Frame, waveform: Waveform) -> PulseSequence: + """ + Adds an instruction to play the specified waveform on the supplied frame. + + Args: + frame (Frame): Frame on which the specified waveform signal would be output. + waveform (Waveform): Waveform envelope specifying the signal to output on the + specified frame. + """ + _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._frames[frame.id] = frame + self._waveforms[waveform.id] = waveform + return self + + def capture_v0(self, frame: Frame) -> PulseSequence: + """ + Adds an instruction to capture the bit output from measuring the specified frame. + + Args: + frame (Frame): Frame on which the capture operation needs + to be performed. + + Returns: + PulseSequence: self, with the instruction added. + """ + _validate_uniqueness(self._frames, frame) + self._program.function_call("capture_v0", [frame]) + self._capture_v0_count += 1 + self._frames[frame.id] = frame + 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 + share the same name, all the parameters of that name will be set to the mapped value. + + Args: + param_values (Dict[str, Number]): A mapping of FreeParameter names + to a value to assign to them. + + Returns: + PulseSequence: Returns a PulseSequence with all present parameters fixed to + their respective values. + """ + 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_program = Program() + new_program.declared_vars = program.declared_vars + new_program.undeclared_vars = program.undeclared_vars + for x in new_tree.statements: + new_program._add_statement(x) + + new_pulse_sequence = PulseSequence() + new_pulse_sequence._program = new_program + new_pulse_sequence._frames = deepcopy(self._frames) + new_pulse_sequence._waveforms = { + wf.id: wf.bind_values(**param_values) if isinstance(wf, Parameterizable) else wf + for wf in deepcopy(self._waveforms).values() + } + + # Update waveforms to bind values + for v in new_program.undeclared_vars: + if v in self._waveforms: + new_program.undeclared_vars[v] = new_pulse_sequence._waveforms[ + v + ]._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] + ) + + return new_pulse_sequence + + def to_ir(self) -> str: + """Returns a str representing the OpenPulse program encoding the PulseSequence.""" + + program = deepcopy(self._program) + if self._capture_v0_count: + register_identifier = "psb" + program.declare( + BitVar[self._capture_v0_count](name=register_identifier), to_beginning=True + ) + tree = program.to_ast(encal=True, include_externs=False) + tree = _IRQASMTransformer(register_identifier).visit(tree) + else: + tree = program.to_ast(encal=True, include_externs=False) + return ast_to_qasm(tree) + + def _format_parameter_ast(self, parameter): + if isinstance(parameter, FreeParameterExpression): + for p in parameter.expression.free_symbols: + self._free_parameters.add(FreeParameter(p.name)) + return _FreeParameterExpressionIdentifier(parameter) + return parameter + + def __call__(self, arg: Any = None, **kwargs) -> PulseSequence: + """ + Implements the call function to easily make a bound PulseSequence. + + Args: + arg (Any): A value to bind to all parameters. Defaults to None and + can be overridden if the parameter is in kwargs. + + Returns: + PulseSequence: A pulse sequence with the specified parameters bound. + """ + param_values = dict() + if arg is not None: + for param in self.parameters: + param_values[str(param)] = arg + for key, val in kwargs.items(): + param_values[str(key)] = val + return self.make_bound_pulse_sequence(param_values) + + +def _validate_uniqueness( + mapping: Dict[str, Any], values: Union[Frame, Waveform, List[Frame], List[Waveform]] +): + if not isinstance(values, list): + values = [values] + + for value in values: + if value.id in mapping and mapping[value.id] != value: + raise ValueError( + f"{value.id} has already been used for defining {mapping[value.id]} " + f"which differs from {value}" + ) diff --git a/src/braket/pulse/pulse_sequence_trace.py b/src/braket/pulse/pulse_sequence_trace.py new file mode 100644 index 00000000..9b4f5593 --- /dev/null +++ b/src/braket/pulse/pulse_sequence_trace.py @@ -0,0 +1,39 @@ +# 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 dataclasses import dataclass +from typing import Dict + +from braket.timings.time_series import TimeSeries + + +@dataclass +class PulseSequenceTrace: + """This class encapsulates the data representing the PulseSequence execution. It contains + the trace of amplitude, frequency and phase information for each frame in the + PulseSequence. + + Args: + amplitudes (dict): A dictionary of frame ID to a TimeSeries of complex values specifying + the waveform amplitude. + frequencies (dict):A dictionary of frame ID to a TimeSeries of float values specifying + the waveform frequency. + phases (dict):A dictionary of frame ID to a TimeSeries of float values specifying + the waveform phase. + """ + + amplitudes: Dict[str, TimeSeries] + frequencies: Dict[str, TimeSeries] + phases: Dict[str, TimeSeries] diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py new file mode 100644 index 00000000..9c62cc77 --- /dev/null +++ b/src/braket/pulse/waveforms.py @@ -0,0 +1,404 @@ +# 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 random +import string +from abc import ABC, abstractmethod +from typing import List, Optional, Union + +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 ( + FreeParameterExpression, + subs_if_free_parameter, +) +from braket.parametric.parameterizable import Parameterizable +from braket.pulse.ast.free_parameters import _FreeParameterExpressionIdentifier + + +class Waveform(ABC): + """ + 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 + time at which the signal is captured. See https://openqasm.com/language/openpulse.html#waveforms + for more details. + """ + + @abstractmethod + def _to_oqpy_expression(self) -> OQPyExpression: + """Returns an OQPyExpression defining this waveform.""" + + @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. + """ + + +class ArbitraryWaveform(Waveform): + """An arbitrary waveform with amplitudes at each timestep explicitly specified using + an array.""" + + def __init__(self, amplitudes: List[complex], id: Optional[str] = None): + """ + Args: + amplitudes (List[complex]): Array of complex values specifying the + waveform amplitude at each timestep. The timestep is determined by the sampling rate + of the frame to which waveform is applied to. + id (Optional[str]): The identifier used for declaring this waveform. A random string of + ascii characters is assigned by default. + """ + self.amplitudes = amplitudes + self.id = id or _make_identifier_name() + + def __eq__(self, other): + return isinstance(other, ArbitraryWaveform) and (self.amplitudes, self.id) == ( + other.amplitudes, + other.id, + ) + + def _to_oqpy_expression(self) -> OQPyExpression: + """Returns an OQPyExpression defining this waveform. + Returns: + OQPyExpression: The OQPyExpression. + """ + return WaveformVar(init_expression=self.amplitudes, 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: + ndarray: The sample amplitudes for this waveform. + """ + raise NotImplementedError + + +class ConstantWaveform(Waveform, Parameterizable): + """A constant waveform which holds the supplied `iq` value as its amplitude for the + specified length.""" + + def __init__( + self, length: Union[float, FreeParameterExpression], iq: complex, id: Optional[str] = None + ): + """ + Args: + length (Union[float, FreeParameterExpression]): Value (in seconds) + specifying the duration of the waveform. + iq (complex): complex value specifying the amplitude of the waveform. + id (Optional[str]): The identifier used for declaring this waveform. A random string of + ascii characters is assigned by default. + """ + self.length = length + self.iq = iq + self.id = id or _make_identifier_name() + + @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] + + def bind_values(self, **kwargs) -> ConstantWaveform: + """Takes in parameters and returns an object with specified parameters + replaced with their values. + + Returns: + ConstantWaveform: A copy of this waveform with the requested parameters bound. + """ + constructor_kwargs = { + "length": subs_if_free_parameter(self.length, **kwargs), + "iq": self.iq, + "id": self.id, + } + return ConstantWaveform(**constructor_kwargs) + + def __eq__(self, other): + return isinstance(other, ConstantWaveform) and (self.length, self.iq, self.id) == ( + other.length, + other.iq, + other.id, + ) + + def _to_oqpy_expression(self) -> OQPyExpression: + """Returns an OQPyExpression defining this waveform. + Returns: + OQPyExpression: The OQPyExpression. + """ + constant_generator = declare_waveform_generator( + "constant", [("length", duration), ("iq", complex128)] + ) + return WaveformVar( + init_expression=constant_generator(_map_to_oqpy_type(self.length, True), self.iq), + 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: + ndarray: The sample amplitudes for this waveform. + """ + # 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 + + +class DragGaussianWaveform(Waveform, Parameterizable): + """A gaussian waveform with an additional gaussian derivative component and lifting applied.""" + + def __init__( + self, + length: Union[float, FreeParameterExpression], + sigma: Union[float, FreeParameterExpression], + beta: Union[float, FreeParameterExpression], + amplitude: Union[float, FreeParameterExpression] = 1, + zero_at_edges: bool = False, + id: Optional[str] = None, + ): + """ + Args: + length (Union[float, FreeParameterExpression]): Value (in seconds) + specifying the duration of the waveform. + sigma (Union[float, FreeParameterExpression]): A measure (in seconds) of + how wide or narrow the Gaussian peak is. + beta (Union[float, FreeParameterExpression]): The correction amplitude. + amplitude (Union[float, FreeParameterExpression]): The amplitude of the + waveform envelope. Defaults to 1. + zero_at_edges (bool): bool specifying whether the waveform amplitude is clipped to + zero 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.sigma = sigma + self.beta = beta + self.amplitude = amplitude + self.zero_at_edges = zero_at_edges + self.id = id or _make_identifier_name() + + @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.sigma, self.beta, self.amplitude] + + def bind_values(self, **kwargs) -> DragGaussianWaveform: + """Takes in parameters and returns an object with specified parameters + replaced with their values. + + Returns: + ConstantWaveform: A copy of this waveform with the requested parameters bound. + """ + constructor_kwargs = { + "length": subs_if_free_parameter(self.length, **kwargs), + "sigma": subs_if_free_parameter(self.sigma, **kwargs), + "beta": subs_if_free_parameter(self.beta, **kwargs), + "amplitude": subs_if_free_parameter(self.amplitude, **kwargs), + "zero_at_edges": self.zero_at_edges, + "id": self.id, + } + return DragGaussianWaveform(**constructor_kwargs) + + def __eq__(self, other): + return isinstance(other, DragGaussianWaveform) and ( + self.length, + self.sigma, + self.beta, + self.amplitude, + self.zero_at_edges, + self.id, + ) == (other.length, other.sigma, other.beta, other.amplitude, other.zero_at_edges, other.id) + + def _to_oqpy_expression(self) -> OQPyExpression: + """Returns an OQPyExpression defining this waveform. + Returns: + OQPyExpression: The OQPyExpression. + """ + drag_gaussian_generator = declare_waveform_generator( + "drag_gaussian", + [ + ("length", duration), + ("sigma", duration), + ("beta", float64), + ("amplitude", float64), + ("zero_at_edges", bool_), + ], + ) + 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.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: + ndarray: The sample amplitudes for this waveform. + """ + sample_range = np.arange(0, self.length, dt) + t0 = self.length / 2 + zero_at_edges_int = int(self.zero_at_edges) + samples = ( + (1 - (1.0j * self.beta * ((sample_range - t0) / self.sigma**2))) + * ( + 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 + + +class GaussianWaveform(Waveform, Parameterizable): + """A waveform with amplitudes following a gaussian distribution for the specified parameters.""" + + def __init__( + self, + length: Union[float, FreeParameterExpression], + sigma: Union[float, FreeParameterExpression], + amplitude: Union[float, FreeParameterExpression] = 1, + zero_at_edges: bool = False, + id: Optional[str] = None, + ): + """ + Args: + length (Union[float, FreeParameterExpression]): Value (in seconds) specifying the + duration of the waveform. + sigma (Union[float, FreeParameterExpression]): A measure (in seconds) of how wide + or narrow the Gaussian peak is. + amplitude (Union[float, FreeParameterExpression]): The amplitude of the waveform + envelope. Defaults to 1. + zero_at_edges (bool): bool specifying whether the waveform amplitude is clipped to + zero 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.sigma = sigma + self.amplitude = amplitude + self.zero_at_edges = zero_at_edges + self.id = id or _make_identifier_name() + + @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.sigma, self.amplitude] + + def bind_values(self, **kwargs) -> GaussianWaveform: + """Takes in parameters and returns an object with specified parameters + replaced with their values. + + Returns: + ConstantWaveform: A copy of this waveform with the requested parameters bound. + """ + constructor_kwargs = { + "length": subs_if_free_parameter(self.length, **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 GaussianWaveform(**constructor_kwargs) + + def __eq__(self, other): + return isinstance(other, GaussianWaveform) and ( + self.length, + self.sigma, + self.amplitude, + self.zero_at_edges, + self.id, + ) == (other.length, 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. + """ + gaussian_generator = declare_waveform_generator( + "gaussian", + [ + ("length", duration), + ("sigma", duration), + ("amplitude", float64), + ("zero_at_edges", bool_), + ], + ) + 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.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: + ndarray: The sample amplitudes for this waveform. + """ + sample_range = np.arange(0, self.length, dt) + t0 = self.length / 2 + zero_at_edges_int = int(self.zero_at_edges) + samples = ( + 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 + + +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 diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py new file mode 100644 index 00000000..e4f4d852 --- /dev/null +++ b/src/braket/timings/time_series.py @@ -0,0 +1,143 @@ +# 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 OrderedDict +from dataclasses import dataclass +from decimal import Decimal +from numbers import Number +from typing import Iterator, List + + +@dataclass +class TimeSeriesItem: + time: Number + value: Number + + +class TimeSeries: + def __init__(self): + self._series = OrderedDict() + self._sorted = True + self._largest_time = -1 + + def put( + self, + time: Number, + value: Number, + ) -> TimeSeries: + """Puts a value to the time series at the given time. A value passed to an existing + time will overwrite the current value. + + Args: + time (Number): The time of the value. + value (Number): The value to add to the time series. + + Returns: + TimeSeries: returns self (to allow for chaining). + """ + if time in self._series: + self._series[time] = TimeSeriesItem(time, value) + elif time > self._largest_time: + self._series[time] = TimeSeriesItem(time, value) + self._largest_time = time + else: + self._series[time] = TimeSeriesItem(time, value) + self._sorted = False + return self + + def times(self) -> List[Number]: + """Returns the times in the time series. + + Returns: + List[Number]: The times in the time series. + """ + self._ensure_sorted() + return [item.time for item in self._series.values()] + + def values(self) -> List[Number]: + """Returns the values in the time series. + + Returns: + List[Number]: The values in the time series. + """ + self._ensure_sorted() + return [item.value for item in self._series.values()] + + def __iter__(self) -> Iterator: + self._ensure_sorted() + return self._series.values().__iter__() + + def __len__(self): + return self._series.values().__len__() + + def _ensure_sorted(self) -> None: + if not self._sorted: + self._series = OrderedDict(sorted(self._series.items())) + self._sorted = True + + def discretize(self, time_resolution: Decimal, value_resolution: 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 + + 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, + ) + return discretized_ts + + +# 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) + 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. + + + Args: + first (TimeSeries): A time series. + second (TimeSeries): A time series. + tolerance (Number): The tolerance value. + + Returns: + bool: True if the times and values in two time series are all within (less than) + a given tolerance range. If the time series are not the same size, this function + will return False. + """ + if len(first) != len(second): + return False + if len(first) == 0: + return True + first_times = first.times() + second_times = second.times() + first_values = first.values() + second_values = second.values() + for index in range(len(first)): + if abs(first_times[index] - second_times[index]) >= tolerance: + return False + if abs(first_values[index] - second_values[index]) >= tolerance: + return False + return True diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index f1ca875c..2edfb3d6 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -20,10 +20,11 @@ IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/ionQdevice" 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-2-pulse" @pytest.mark.parametrize( - "arn", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (OQC_ARN), (SIMULATOR_ARN)] + "arn", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (OQC_ARN), (SIMULATOR_ARN), (PULSE_ARN)] ) def test_device_creation(arn, aws_session): device = AwsDevice(arn, aws_session=aws_session) @@ -35,6 +36,13 @@ def test_device_creation(arn, aws_session): assert device.properties +@pytest.mark.parametrize("arn", [(PULSE_ARN)]) +def test_device_pulse_properties(arn, aws_session): + device = AwsDevice(arn, aws_session=aws_session) + assert device.ports + assert device.frames + + def test_device_across_regions(aws_session): # assert QPUs across different regions can be created using the same aws_session AwsDevice(RIGETTI_ARN, aws_session) diff --git a/test/integ_tests/test_pulse.py b/test/integ_tests/test_pulse.py new file mode 100644 index 00000000..a77910b8 --- /dev/null +++ b/test/integ_tests/test_pulse.py @@ -0,0 +1,277 @@ +import numpy as np +import pytest + +from braket.aws import AwsDevice, AwsQuantumTask +from braket.circuits import Circuit +from braket.parametric import FreeParameter +from braket.pulse import ArbitraryWaveform, PulseSequence + + +@pytest.fixture +def device(): + return AwsDevice("arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-2") + + +@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, + ] + ) + + +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( + q0: str, + q1: str, + shift_phases_q0: float, + shift_phases_q1: float, + 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] + + dt = device.properties.pulse.ports[q0_q1_cz_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) + .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 + + +def test_pulse_bell(arbitrary_waveform, device): + a, b, = ( + 10, + 113, + ) # 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) + + bell_pair_with_gates = Circuit().h(a).h(b).cz(a, b).h(b) + bell_pair_with_pulses_unbound = ( + h_gate(a) + h_gate(b) + Circuit().pulse_gate([a, b], cz) + h_gate(b) + ) + bell_pair_with_pulses = bell_pair_with_pulses_unbound(theta_0=p0, theta_1=p1) + + num_shots = 1000 + gate_task = device.run(bell_pair_with_gates, shots=num_shots, disable_qubit_rewiring=True) + pulse_task = device.run(bell_pair_with_pulses, shots=num_shots) + + if not device.is_available: + try: + assert gate_task.state not in AwsQuantumTask.TERMINAL_STATES + assert pulse_task.state not in AwsQuantumTask.TERMINAL_STATES + finally: + gate_task.cancel() + pulse_task.cancel() + return + + gate_measurements = gate_task.result().measurement_counts + pulse_measurements = pulse_task.result().measurement_counts + + # 1-smoothing to avoid nans for 0 counts + observed = ( + np.array([pulse_measurements.get(state, 1) for state in ("00", "01", "10", "11")]) + / num_shots + ) + expected = ( + np.array([gate_measurements.get(state, 1) for state in ("00", "01", "10", "11")]) + / num_shots + ) + chi_squared = np.sum((observed - expected) ** 2 / expected) + assert chi_squared < 10 # adjust this threshold if test is flaky + + +def test_pulse_sequence(arbitrary_waveform, device): + a, b, = ( + 10, + 113, + ) # 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_with_pulses_unbound = cz_pulse(a, b, theta_0, theta_1, a_b_cz_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) + .capture_v0(q0_readout_frame) + .capture_v0(q1_readout_frame) + ) + cz_with_gates = Circuit().cz(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) + + if not device.is_available: + try: + assert gate_task.state not in AwsQuantumTask.TERMINAL_STATES + assert pulse_task.state not in AwsQuantumTask.TERMINAL_STATES + finally: + gate_task.cancel() + pulse_task.cancel() + return + + gate_measurements = gate_task.result().measurement_counts + pulse_measurements = pulse_task.result().measurement_counts + + # 1-smoothing to avoid nans for 0 counts + observed = ( + np.array([pulse_measurements.get(state, 1) for state in ("00", "01", "10", "11")]) + / num_shots + ) + expected = ( + np.array([gate_measurements.get(state, 1) for state in ("00", "01", "10", "11")]) + / num_shots + ) + chi_squared = np.sum((observed - expected) ** 2 / expected) + assert chi_squared < 10 # adjust this threshold if test is flaky diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index b2974fb3..b781cd68 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -35,9 +35,13 @@ from braket.circuits import Circuit, FreeParameter from braket.device_schema.device_execution_window import DeviceExecutionWindow from braket.device_schema.dwave import DwaveDeviceCapabilities +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, +) from braket.device_schema.rigetti import RigettiDeviceCapabilities from braket.device_schema.simulators import GateModelSimulatorDeviceCapabilities from braket.ir.openqasm import Program as OpenQasmProgram +from braket.pulse import Frame, Port MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_1 = { "braketSchemaHeader": { @@ -479,6 +483,129 @@ def test_device_refresh_metadata(arn): _assert_device_fields(device, MOCK_GATE_MODEL_QPU_CAPABILITIES_2, MOCK_GATE_MODEL_QPU_2) +MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 = { + "braketSchemaHeader": { + "name": "braket.device_schema.pulse.pulse_device_action_properties", + "version": "1", + }, + "supportedQhpTemplateWaveforms": {}, + "supportedFunctions": {}, + "ports": { + "q0_ff": { + "portId": "q0_ff", + "direction": "tx", + "portType": "ff", + "dt": 1e-09, + "qubitMappings": None, + "centerFrequencies": [375000000.0], + "qhpSpecificProperties": None, + } + }, + "frames": { + "q0_q1_cphase_frame": { + "frameId": "q0_q1_cphase_frame", + "portId": "q0_ff", + "frequency": 4276236.85736918, + "centerFrequency": 375000000.0, + "phase": 1.0, + "associatedGate": "cphase", + "qubitMappings": [0, 1], + "qhpSpecificProperties": None, + } + }, +} + + +MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_2 = { + "braketSchemaHeader": { + "name": "braket.device_schema.pulse.pulse_device_action_properties", + "version": "1", + }, + "supportedQhpTemplateWaveforms": {}, + "supportedFunctions": {}, + "ports": { + "q0_ff": { + "portId": "q0_ff", + "direction": "tx", + "portType": "ff", + "dt": 1e-09, + "qubitMappings": None, + "centerFrequencies": [375000000.0], + "qhpSpecificProperties": None, + } + }, +} + + +def get_pulse_model(capabilities_json): + device_json = { + "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.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": ["H"], + } + }, + "paradigm": { + "qubitCount": 30, + "nativeGateSet": ["ccnot", "cy"], + "connectivity": {"fullyConnected": False, "connectivityGraph": {"1": ["2", "3"]}}, + }, + "deviceParameters": {}, + "pulse": capabilities_json, + } + device_obj = RigettiDeviceCapabilities.parse_obj(device_json) + return { + "deviceName": "M-2-Pulse", + "deviceType": "QPU", + "providerName": "provider1", + "deviceStatus": "OFFLINE", + "deviceCapabilities": device_obj.json(), + } + + +@pytest.mark.parametrize( + "pulse_device_capabilities", + [ + MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1, + MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_2, + ], +) +def test_device_pulse_metadata(pulse_device_capabilities): + mock_session = Mock() + mock_session.get_device.return_value = get_pulse_model(pulse_device_capabilities) + mock_session.region = RIGETTI_REGION + device = AwsDevice("arn:aws:braket:us-west-1::TestName", mock_session) + assert device.ports == {"q0_ff": Port("q0_ff", 1e-9)} + port = device.ports["q0_ff"] + assert port.properties == pulse_device_capabilities["ports"]["q0_ff"] + if "frames" in pulse_device_capabilities: + assert device.frames == { + "q0_q1_cphase_frame": Frame( + "q0_q1_cphase_frame", Port("q0_ff", 1e-9), 4276236.85736918, 1.0 + ) + } + frame = device.frames["q0_q1_cphase_frame"] + assert frame.is_predefined is True + assert frame.properties == pulse_device_capabilities["frames"]["q0_q1_cphase_frame"] + else: + assert device.frames == {} + + def test_equality(arn): mock_session = Mock() mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 @@ -916,6 +1043,8 @@ def _assert_device_fields(device, expected_properties, expected_device_data): assert device.type == AwsDeviceType(expected_device_data.get("deviceType")) if device.topology_graph: assert device.topology_graph.edges == device._construct_topology_graph().edges + assert device.frames == {} + assert device.ports == {} @patch("braket.aws.aws_device.AwsSession.copy_session") 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 5f9fd868..9fe2008c 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -26,6 +26,7 @@ from braket.aws.aws_quantum_task import _create_annealing_device_params from braket.aws.aws_session import AwsSession from braket.circuits import Circuit +from braket.circuits.gates import PulseGate from braket.circuits.serialization import ( IRType, OpenQASMSerializationProperties, @@ -43,6 +44,7 @@ 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.pulse import Frame, Port, PulseSequence from braket.tasks import ( AnnealingQuantumTaskResult, GateModelQuantumTaskResult, @@ -117,6 +119,25 @@ def blackbird_program(): return BlackbirdProgram(source="Vac | q[0]") +@pytest.fixture +def pulse_sequence(): + return PulseSequence().set_frequency( + Frame( + frame_id="predefined_frame_1", + frequency=2e9, + port=Port(port_id="device_port_x0", dt=1e-9, properties={}), + phase=0, + is_predefined=True, + ), + 6e6, + ) + + +@pytest.fixture +def pulse_gate(pulse_sequence): + return PulseGate(pulse_sequence, 1, "my_PG") + + def test_equality(arn, aws_session): quantum_task_1 = AwsQuantumTask(arn, aws_session) quantum_task_2 = AwsQuantumTask(arn, aws_session) @@ -428,6 +449,64 @@ def test_create_blackbird_program(aws_session, arn, blackbird_program): ) +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_program = OpenQasmProgram(source=expected_openqasm) + + aws_session.create_quantum_task.return_value = arn + AwsQuantumTask.create(aws_session, SIMULATOR_ARN, pulse_sequence, S3_TARGET, 10) + + _assert_create_quantum_task_called_with( + aws_session, + SIMULATOR_ARN, + expected_program.json(), + S3_TARGET, + 10, + ) + + +@pytest.mark.parametrize("device_arn,device_parameters_class", DEVICE_PARAMETERS) +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_program = OpenQasmProgram(source=expected_openqasm, inputs={}) + + aws_session.create_quantum_task.return_value = arn + AwsQuantumTask.create(aws_session, device_arn, pulse_gate_circuit, S3_TARGET, 10) + + _assert_create_quantum_task_called_with( + aws_session, + device_arn, + expected_program.json(), + S3_TARGET, + 10, + device_parameters_class( + paradigmParameters=GateModelParameters( + qubitCount=pulse_gate_circuit.qubit_count, disableQubitRewiring=False + ) + ), + ) + + @pytest.mark.parametrize("device_arn,device_parameters_class", DEVICE_PARAMETERS) def test_from_circuit_with_shots(device_arn, device_parameters_class, aws_session, circuit): mocked_task_arn = "task-arn-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 bda725fa..468b08bf 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -22,6 +22,7 @@ Observable, Operator, ) +from braket.pulse import Frame, Port, PulseSequence def test_empty_circuit(): @@ -650,5 +651,39 @@ def test_noise_multi_probabilities_with_parameter(): _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 _assert_correct_diagram(circ, expected): assert AsciiCircuitDiagram.build_diagram(circ) == "\n".join(expected) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 9632dd50..8df9a394 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -38,6 +38,7 @@ QubitReferenceType, ) from braket.ir.openqasm import Program as OpenQasmProgram +from braket.pulse import DragGaussianWaveform, Frame, GaussianWaveform, Port, PulseSequence @pytest.fixture @@ -80,6 +81,30 @@ def bell_pair(prob): ) +@pytest.fixture +def port(): + return Port(port_id="device_port_x0", dt=1e-9, properties={}) + + +@pytest.fixture +def predefined_frame_1(port): + return Frame( + frame_id="predefined_frame_1", frequency=2e9, port=port, phase=0, is_predefined=True + ) + + +@pytest.fixture +def user_defined_frame(port): + return Frame( + frame_id="user_defined_frame_0", + port=port, + frequency=1e7, + phase=3.14, + is_predefined=False, + properties={"associatedGate": "rz"}, + ) + + def test_repr_instructions(h): expected = f"Circuit('instructions': {h.instructions})" assert repr(h) == expected @@ -2143,3 +2168,198 @@ def test_circuit_with_expr_not_fully_bound(): .ry(angle=alpha, target=3) ) assert new_circ == expected + + +def test_pulse_circuit_to_openqasm(predefined_frame_1, user_defined_frame): + pulse_sequence_1 = ( + PulseSequence() + .set_frequency(predefined_frame_1, 3e9) + .play(predefined_frame_1, GaussianWaveform(length=1e-3, sigma=0.7, id="gauss_wf")) + .play( + predefined_frame_1, + DragGaussianWaveform(length=3e-3, sigma=0.4, beta=0.2, id="drag_gauss_wf"), + ) + ) + + pulse_sequence_2 = ( + PulseSequence() + .set_frequency(predefined_frame_1, 3e9) + .play(user_defined_frame, GaussianWaveform(length=1e-3, sigma=0.7, id="gauss_wf")) + .play( + predefined_frame_1, + DragGaussianWaveform(length=3e-3, sigma=0.4, beta=0.2, id="drag_gauss_wf_2"), + ) + ) + + circuit = ( + Circuit().h(0).pulse_gate(0, pulse_sequence_1).x(1).pulse_gate(1, pulse_sequence_2).h(1) + ) + + pulse_sequence_2.play( + user_defined_frame, GaussianWaveform(length=1e-3, sigma=0.7, id="gauss_wf_ignore") + ) + + assert circuit.to_ir( + ir_type=IRType.OPENQASM, + 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(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);", + "}", + "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): + pulse_sequence_1 = ( + PulseSequence() + .set_frequency(predefined_frame_1, 3e9) + .play(predefined_frame_1, GaussianWaveform(length=1e-3, sigma=0.7, id="gauss_wf")) + ) + + pulse_sequence_2 = ( + PulseSequence() + .set_frequency(predefined_frame_1, 3e9) + .play(user_defined_frame, GaussianWaveform(length=1e-3, sigma=0.3, id="gauss_wf")) + ) + + circuit = ( + Circuit().h(0).pulse_gate(0, pulse_sequence_1).x(1).pulse_gate(1, pulse_sequence_2).h(1) + ) + + with pytest.raises(ValueError): + circuit.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=OpenQASMSerializationProperties( + qubit_reference_type=QubitReferenceType.PHYSICAL + ), + ) + + +def test_pulse_circuit_conflicting_frame(user_defined_frame): + user_defined_frame_x = Frame( + user_defined_frame.id, + Port("wrong_port", 1e-9), + user_defined_frame.frequency, + user_defined_frame.phase, + ) + pulse_sequence_user_defined_frame_x = ( + PulseSequence() + .set_frequency(user_defined_frame_x, 3e9) + .play(user_defined_frame_x, GaussianWaveform(length=1e-3, sigma=0.7, id="gauss_wf")) + ) + + pulse_sequence_user_defined_frame = ( + PulseSequence() + .set_frequency(user_defined_frame, 3e9) + .play(user_defined_frame, GaussianWaveform(length=1e-3, sigma=0.7, id="gauss_wf")) + ) + + circuit = ( + Circuit() + .h(0) + .pulse_gate(0, pulse_sequence_user_defined_frame) + .x(1) + .pulse_gate(1, pulse_sequence_user_defined_frame_x) + .h(1) + ) + + with pytest.raises(ValueError): + circuit.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=OpenQASMSerializationProperties( + qubit_reference_type=QubitReferenceType.PHYSICAL + ), + ) + + +def test_parametrized_pulse_circuit(user_defined_frame): + frequency_parameter = FreeParameter("frequency") + length = FreeParameter("length") + theta = FreeParameter("theta") + pulse_sequence = ( + PulseSequence() + .set_frequency(user_defined_frame, frequency_parameter) + .play(user_defined_frame, GaussianWaveform(length=length, sigma=0.7, id="gauss_wf")) + ) + + circuit = ( + Circuit().rx(angle=theta, target=0).pulse_gate(pulse_sequence=pulse_sequence, targets=1) + ) + + assert circuit.parameters == set([frequency_parameter, length, theta]) + + bound_half = circuit(theta=0.5, length=1e-5) + assert bound_half.to_ir( + ir_type=IRType.OPENQASM, + 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(10000.0ns, 700000000.0ns, 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) + + assert bound.to_ir( + ir_type=IRType.OPENQASM, + 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(10000.0ns, 700000000.0ns, 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;", + ] + ) diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 0ea9cd24..5ec40626 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -32,6 +32,7 @@ SingleTarget, TwoDimensionalMatrix, ) +from braket.pulse import ArbitraryWaveform, Frame, Port, PulseSequence class DoubleAngle: @@ -767,6 +768,18 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), "#pragma braket unitary([[1.0, 0], [0, 0.70710678 - 0.70710678im]]) q[4]", ), + ( + Gate.PulseGate( + PulseSequence().play( + Frame("user_frame", Port("device_port_x", 1e-9), 1e9), + ArbitraryWaveform([1, 2], "arb_wf"), + ), + 1, + ), + [0], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "\n".join(["cal {", " play(user_frame, arb_wf);", "}"]), + ), ( Gate.GPi(angle=0.17), [4], @@ -928,6 +941,53 @@ def test_bind_values(gate): assert type(new_gate.angle) == float +def test_bind_values_pulse_gate(): + qubit_count = 1 + 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")), + qubit_count, + ) + + def to_ir(pulse_gate): + return pulse_gate.to_ir(range(pulse_gate.qubit_count), IRType.OPENQASM) + + a = 3 + 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, b + 3);", + " delay[(1000000000.0*c)ns] user_frame;", + "}", + ] + ) + + assert a_bound_ir == to_ir( + Gate.PulseGate(gate.pulse_sequence.make_bound_pulse_sequence({"a": a}), qubit_count) + ) + assert a_bound_ir != to_ir(gate) + + c = 4e-6 + ac_bound = a_bound.bind_values(c=c) + ac_bound_ir = to_ir(ac_bound) + assert ac_bound_ir == to_ir( + Gate.PulseGate(a_bound.pulse_sequence.make_bound_pulse_sequence({"c": c}), qubit_count) + ) + assert ac_bound_ir != a_bound_ir + + +@pytest.mark.xfail(raises=ValueError) +def test_pulse_gate_capture_throws(): + Circuit().pulse_gate( + 0, PulseSequence().capture_v0(Frame("user_frame", Port("device_port_x", dt=1e-9), 1e9)) + ) + + @pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("matrix", invalid_unitary_matrices) def test_unitary_invalid_matrix(matrix): @@ -939,3 +999,14 @@ def test_unitary_matrix_target_size_mismatch(): Circuit().unitary( matrix=np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]), targets=[0] ) + + +@pytest.mark.xfail(raises=NotImplementedError) +def test_pulse_gate_to_matrix(): + Gate.PulseGate( + PulseSequence().play( + Frame("user_frame", Port("device_port_x", 1e-9), 1e9), + ArbitraryWaveform([1, 2], "arb_wf"), + ), + 1, + ).to_matrix() diff --git a/test/unit_tests/braket/circuits/test_free_parameter.py b/test/unit_tests/braket/parametric/test_free_parameter.py similarity index 97% rename from test/unit_tests/braket/circuits/test_free_parameter.py rename to test/unit_tests/braket/parametric/test_free_parameter.py index c1698035..816bc0ce 100644 --- a/test/unit_tests/braket/circuits/test_free_parameter.py +++ b/test/unit_tests/braket/parametric/test_free_parameter.py @@ -13,7 +13,7 @@ import pytest -from braket.circuits import FreeParameter +from braket.parametric import FreeParameter @pytest.fixture diff --git a/test/unit_tests/braket/circuits/test_free_parameter_expression.py b/test/unit_tests/braket/parametric/test_free_parameter_expression.py similarity index 81% rename from test/unit_tests/braket/circuits/test_free_parameter_expression.py rename to test/unit_tests/braket/parametric/test_free_parameter_expression.py index 14ce3fe5..89692091 100644 --- a/test/unit_tests/braket/circuits/test_free_parameter_expression.py +++ b/test/unit_tests/braket/parametric/test_free_parameter_expression.py @@ -13,7 +13,8 @@ import pytest -from braket.circuits import FreeParameter, FreeParameterExpression +from braket.parametric import FreeParameter, FreeParameterExpression +from braket.parametric.free_parameter_expression import subs_if_free_parameter @pytest.fixture @@ -124,3 +125,23 @@ def test_sub_return_expression(): expected = FreeParameter("theta") + 2 assert subbed_expr == expected + + +@pytest.mark.parametrize( + "param, kwargs, expected_value, expected_type", + [ + (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), + ( + FreeParameter("a") + 2 * FreeParameter("x"), + {"a": 0.4, "b": 0.4}, + 0.4 + 2 * FreeParameter("x"), + FreeParameterExpression, + ), + ], +) +def test_subs_if_free_parameter(param, kwargs, expected_value, expected_type): + value = subs_if_free_parameter(param, **kwargs) + assert value == expected_value + assert type(value) == expected_type diff --git a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py new file mode 100644 index 00000000..27b1b735 --- /dev/null +++ b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py @@ -0,0 +1,692 @@ +# 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 typing import List, Union +from unittest.mock import Mock + +import numpy as np +import pytest +from openpulse import ast +from oqpy import IntVar + +from braket.pulse import ArbitraryWaveform, ConstantWaveform, DragGaussianWaveform, GaussianWaveform +from braket.pulse.ast.approximation_parser import _ApproximationParser +from braket.pulse.frame import Frame +from braket.pulse.port import Port +from braket.pulse.pulse_sequence import PulseSequence +from braket.timings.time_series import TimeSeries, _all_close + + +@pytest.fixture +def port(): + return Port(port_id="device_port_x0", dt=1e-9, properties={}) + + +def test_bare_pulsequence(): + pulse_seq = PulseSequence() + results = pulse_seq.to_time_trace() + verify_results(results, {}, {}, {}) + + +def test_delay(port): + frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) + pulse_seq = PulseSequence().delay(frame, 3e-9) + # 3 datapoints for delay + + expected_amplitudes = {"frame1": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries()} + expected_phases = {"frame1": TimeSeries()} + + # 2 datapoints for first delay + expected_amplitudes["frame1"].put(0, 0).put(2e-9, 0) + expected_frequencies["frame1"].put(0, 1e8).put(2e-9, 1e8) + expected_phases["frame1"].put(0, 0).put(2e-9, 0) + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) + + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + + +def test_predefined_frame(port): + frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=True) + pulse_seq = PulseSequence().delay(frame, 3e-9) + # 3 datapoints for delay + + expected_amplitudes = {"frame1": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries()} + expected_phases = {"frame1": TimeSeries()} + + # 2 datapoints for first delay + expected_amplitudes["frame1"].put(0, 0).put(2e-9, 0) + expected_frequencies["frame1"].put(0, 1e8).put(2e-9, 1e8) + expected_phases["frame1"].put(0, 0).put(2e-9, 0) + + for statement in pulse_seq._program._state.body: + assert type(statement) != ast.FrameType + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) + + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + + +def test_set_shift_phase(port): + frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) + pulse_seq = ( + PulseSequence() + .set_phase(frame, 1) + .delay(frame, 2e-9) + .shift_phase(frame, 2) + .delay(frame, 5e-9) + .set_phase(frame, 0) + ) + expected_amplitudes = {"frame1": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries()} + expected_phases = {"frame1": TimeSeries()} + + # 2 datapoints for first delay + # Parser does not like delay 2ns + expected_amplitudes["frame1"].put(0, 0).put(1e-9, 0) + expected_frequencies["frame1"].put(0, 1e8).put(1e-9, 1e8) + expected_phases["frame1"].put(0, 1).put(1e-9, 1) + + # set_shift_phase should be instantaneous (result on current or next datapoint?) + # 5 dt for second delay + # shift_phase adds 2 to the phase of the last point -> 3 + expected_amplitudes["frame1"].put(2e-9, 0).put(6e-9, 0) + expected_frequencies["frame1"].put(2e-9, 1e8).put(6e-9, 1e8) + expected_phases["frame1"].put(2e-9, 3).put(6e-9, 3) + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) + + 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 = ( + PulseSequence() + .set_frequency(frame, 2e8) + .delay(frame, 20e-9) + .shift_frequency(frame, -0.1e8) + .delay(frame, 10e-9) + ) + # 2 datapoints for delay + # set_shift_phase should be instantaneous (result on current or next datapoint?) + # shift_phase adds 2 to the phase of the last point -> 3 + + expected_amplitudes = {"frame1": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries()} + expected_phases = {"frame1": TimeSeries()} + + # 2 datapoints for first delay + expected_amplitudes["frame1"].put(0, 0).put(19e-9, 0) + expected_frequencies["frame1"].put(0, 2e8).put(19e-9, 2e8) + expected_phases["frame1"].put(0, 0).put(19e-9, 0) + + # set_shift_phase should be instantaneous (result on current or next datapoint?) + # 5 dt for second delay + # shift_phase adds 2 to the phase of the last point -> 3 + expected_amplitudes["frame1"].put(20e-9, 0).put(29e-9, 0) + expected_frequencies["frame1"].put(20e-9, 1.9e8).put(29e-9, 1.9e8) + expected_phases["frame1"].put(20e-9, 0).put(29e-9, 0) + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) + + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + + +def test_play_arbitrary_waveforms(port): + frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) + my_arb_wf = ArbitraryWaveform([0.4 + 0.1j, -0.8 + 0.1j, 1 + 0.2j]) + pulse_seq = PulseSequence().play(frame, my_arb_wf).capture_v0(frame) + # 3 datapoints for arb wf play + + expected_amplitudes = {"frame1": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries()} + expected_phases = {"frame1": TimeSeries()} + + times = np.arange(0, 4e-9, port.dt) + for t, v in zip(times, [0.4 + 0.1j, -0.8 + 0.1j, 1 + 0.2j]): + 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(frame)) + + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + + +def test_play_literal(port): + frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) + pulse_seq = PulseSequence() + pulse_seq._program.play(frame=frame, waveform=[0.4 + 0.1j, -0.8 + 0.1j, 1 + 0.2j]) + # 3 datapoints for arb wf play + + expected_amplitudes = {"frame1": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries()} + expected_phases = {"frame1": TimeSeries()} + + times = np.arange(0, 4e-9, port.dt) + for t, v in zip(times, [0.4 + 0.1j, -0.8 + 0.1j, 1 + 0.2j]): + 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(frame)) + + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + + +def test_classical_variable_declaration(port): + frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) + pulse_seq = PulseSequence() + i = IntVar(5, "i") + pulse_seq._program.increment(i, 1) + + with pytest.raises(NotImplementedError): + _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) + + +def test_play_constant_waveforms(port): + frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) + length = 6e-9 + pulse_seq = PulseSequence().play(frame, ConstantWaveform(length, 0.75)) + + expected_amplitudes = {"frame1": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries()} + expected_phases = {"frame1": TimeSeries()} + + times = np.arange(0, length, port.dt) + values = 0.75 * np.ones_like(times) + 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(frame)) + + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + + +def test_set_scale(port): + frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) + pulse_seq = ( + PulseSequence() + .play(frame, ConstantWaveform(10e-9, 0.66)) + .set_scale(frame, 0.5) + .play(frame, ConstantWaveform(20e-9, 0.66)) + ) + + expected_amplitudes = {"frame1": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries()} + expected_phases = {"frame1": TimeSeries()} + + times = np.arange(0, 10e-9, port.dt) + values = 0.66 * np.ones_like(times) + 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) + + shift_time = 10e-9 + times = np.arange(0, 20e-9, port.dt) + values = 0.33 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["frame1"].put(shift_time + t, v) + expected_frequencies["frame1"].put(shift_time + t, 1e8) + expected_phases["frame1"].put(shift_time + t, 0) + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) + + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + + +def test_play_gaussian_waveforms(port): + frame1 = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) + + # First gaussian with zero_at_edges=False + gaussian_wf_ZaE_False = GaussianWaveform( + length=1e-8, sigma=1.69e-9, amplitude=1.0, zero_at_edges=False + ) + pulse_seq = PulseSequence().play(frame1, gaussian_wf_ZaE_False) + # X datapoints... + + times = np.arange(0, 1e-8, port.dt) + values = np.array( + [ + complex(0.012568049266111367), + complex(0.06074792381889125), + complex(0.20688853998666168), + complex(0.49645839613814063), + complex(0.839403382587157), + complex(1.0), + complex(0.839403382587157), + complex(0.49645839613814063), + complex(0.20688853998666168), + complex(0.06074792381889125), + ], + 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) + + # Add a delay between the two waveforms + pulse_seq.delay(frame1, 2e-8) + expected_amplitudes["frame1"].put(1e-8, 0).put(29e-9, 0) + expected_frequencies["frame1"].put(1e-8, 1e8).put(29e-9, 1e8) + expected_phases["frame1"].put(1e-8, 0).put(29e-9, 0) + + # Second gaussian with zero_at_edges=True + gaussian_wf_ZaE_True = GaussianWaveform( + length=1e-8, sigma=1.69e-9, amplitude=1.0, zero_at_edges=True + ) + pulse_seq.play(frame1, gaussian_wf_ZaE_True) + # X datapoints... + + times = np.arange(0, 1e-8, port.dt) + values = np.array( + [ + 0.0 + 0.0j, + complex(0.04879311), + complex(0.1967938), + complex(0.49004931), + complex(0.83735931), + complex(1.0), + complex(0.83735931), + complex(0.49004931), + complex(0.1967938), + complex(0.04879311), + ], + dtype=np.complex128, + ) + + shift_time = 30e-9 + for t, v in zip(times, values): + expected_amplitudes["frame1"].put(t + shift_time, v) + expected_frequencies["frame1"].put(t + shift_time, 1e8) + expected_phases["frame1"].put(t + shift_time, 0) + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame1)) + + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + + +def test_play_drag_gaussian_waveforms(port): + frame1 = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) + + expected_amplitudes = {"frame1": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries()} + expected_phases = {"frame1": TimeSeries()} + + drag_gaussian_wf_ZaE_False = DragGaussianWaveform( + length=1e-8, sigma=1.69e-9, amplitude=1.0, beta=1e-9, zero_at_edges=False + ) + drag_gaussian_wf_ZaE_True = DragGaussianWaveform( + length=1e-8, sigma=1.69e-9, amplitude=1.0, beta=1e-9, zero_at_edges=True + ) + pulse_seq = ( + PulseSequence() + .play(frame1, drag_gaussian_wf_ZaE_False) + .delay(frame1, 2e-8) + .play(frame1, drag_gaussian_wf_ZaE_True) + ) + + # First DRAG_Gaussian with zero_at_edges=False + times = np.arange(0, 1e-8, port.dt) + values = np.array( + [ + complex(0.012568049266111367, 0.02200211698839566), + complex(0.06074792381889125, 0.08507814687005531), + complex(0.20688853998666168, 0.21731228597037397), + complex(0.49645839613814063, 0.3476477687322857), + complex(0.839403382587157, 0.29389845684225235), + complex(1.0, 0.0), + complex(0.839403382587157, -0.29389845684225235), + complex(0.49645839613814063, -0.3476477687322857), + complex(0.20688853998666168, -0.21731228597037397), + complex(0.06074792381889125, -0.08507814687005531), + ], + dtype=np.complex128, + ) + + 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) + + # Add a delay between the two waveforms + shift_time = 10e-9 + expected_amplitudes["frame1"].put(shift_time + 0, 0).put(shift_time + 19e-9, 0) + expected_frequencies["frame1"].put(shift_time + 0, 1e8).put(shift_time + 19e-9, 1e8) + expected_phases["frame1"].put(shift_time + 0, 0).put(shift_time + 19e-9, 0) + + # Second DRAG_Gaussian with zero_at_edges=True + times = np.arange(0, 1e-8, port.dt) + values = np.array( + [ + 0.0 + 0.0j, + 0.04879311 + 0.06833529j, + 0.1967938 + 0.20670894j, + 0.49004931 + 0.34315977j, + 0.83735931 + 0.29318277j, + 1.0 + 0.0j, + 0.83735931 - 0.29318277j, + 0.49004931 - 0.34315977j, + 0.1967938 - 0.20670894j, + 0.04879311 - 0.06833529j, + ], + dtype=np.complex128, + ) + + shift_time = 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) + expected_phases["frame1"].put(t + shift_time, 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) + pulse_seq = ( + PulseSequence() + .play(frame1, ConstantWaveform(12e-9, 0.75)) # Inst1 + .barrier([frame1, frame2]) # Inst2 + .play(frame1, ConstantWaveform(16e-9, 1)) # Inst3 + .play(frame2, ConstantWaveform(8e-9, -1)) # Inst4 + ) + + expected_amplitudes = {"frame1": TimeSeries(), "frame2": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries(), "frame2": TimeSeries()} + expected_phases = {"frame1": TimeSeries(), "frame2": TimeSeries()} + + # Inst1 + shift_time_frame1 = 0 + pulse_length = 12e-9 + times = np.arange(0, pulse_length, port.dt) + values = 0.75 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) + expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) + expected_phases["frame1"].put(shift_time_frame1 + t, 0) + + # Inst2 + # Delay frame2 from 0ns to 11ns + shift_time_frame2 = 0 + + expected_amplitudes["frame2"].put(0, 0).put(11e-9, 0) + expected_frequencies["frame2"].put(0, 1e8).put(11e-9, 1e8) + expected_phases["frame2"].put(0, 0).put(11e-9, 0) + + # Inst3 + shift_time_frame1 = shift_time_frame1 + pulse_length + pulse_length = 16e-9 + times = np.arange(0, pulse_length, port.dt) + values = 1 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) + expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) + expected_phases["frame1"].put(shift_time_frame1 + t, 0) + + # Inst4 + shift_time_frame2 = shift_time_frame1 + pulse_length = 8e-9 + times = np.arange(0, pulse_length, port.dt) + values = -1 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["frame2"].put(shift_time_frame2 + t, v) + expected_frequencies["frame2"].put(shift_time_frame2 + t, 1e8) + expected_phases["frame2"].put(shift_time_frame2 + t, 0) + + # # Pad frame2 + # shift_time_frame2 = shift_time_frame2 + pulse_length + # last_time_frame1 = expected_amplitudes["frame1"].times()[-1] + + # # Do we need to pad frame1? + # expected_amplitudes["frame1"].put(last_time_frame1 + port.dt, 0) + # expected_frequencies["frame1"].put(last_time_frame1 + port.dt, 1e8) + # expected_phases["frame1"].put(last_time_frame1 + port.dt, 0) + + # expected_amplitudes["frame2"].put(shift_time_frame2, 0).put( + # last_time_frame1 + port.dt, 0 + # ) + # expected_frequencies["frame2"].put(shift_time_frame2, 1e8).put( + # last_time_frame1 + port.dt, 1e8 + # ) + # expected_phases["frame2"].put(shift_time_frame2, 0).put(last_time_frame1 + port.dt, 0) + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) + + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + # Array retrievable with + # x=np.arange(0, ts.times()+1e-9, 1e-9) + # np.interp(x, ts.times(), ts.values()) # See last cells of SDK Testing.ipynb + + +def test_barrier_different_dt(port): + port1 = Port(port_id="device_port_x1", dt=5e-9, properties={}) + port2 = Port(port_id="device_port_x2", dt=4e-9, properties={}) + frame1 = Frame(frame_id="frame1", port=port1, frequency=1e8, phase=0, is_predefined=False) + frame2 = Frame(frame_id="frame2", port=port2, frequency=1e8, phase=0, is_predefined=False) + pulse_seq = ( + PulseSequence() + .play(frame1, ConstantWaveform(25e-9, 0.75)) # Inst 1 + .barrier([frame1, frame2]) # Inst 2 + .play(frame1, ConstantWaveform(40e-9, 1)) # Inst 3 + .play(frame2, ConstantWaveform(28e-9, -1)) # Inst 4 + ) + + expected_amplitudes = {"frame1": TimeSeries(), "frame2": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries(), "frame2": TimeSeries()} + expected_phases = {"frame1": TimeSeries(), "frame2": TimeSeries()} + + # Inst1 + shift_time_frame1 = 0 + pulse_length = 25e-9 + times = np.arange(0, pulse_length, port1.dt) + values = 0.75 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) + expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) + expected_phases["frame1"].put(shift_time_frame1 + t, 0) + + # Inst2 + # barrier at 35ns (first point coinciding with frame2: np.ceil(max(20,0)/np.lcm(dt1=5,dt2=7))*np.lcm(dt1=5,dt2=7)) # noqa + # reset shift_time_frame1 = shift_time_frame2 = 35 + shift_time_frame1 = shift_time_frame2 = 40e-9 + + latest_time_frame1 = expected_amplitudes["frame1"].times()[-1] + expected_amplitudes["frame1"].put(latest_time_frame1 + port1.dt, 0).put( + shift_time_frame1 - port1.dt, 0 + ) + expected_frequencies["frame1"].put(latest_time_frame1 + port1.dt, 1e8).put( + shift_time_frame1 - port1.dt, 1e8 + ) + expected_phases["frame1"].put(latest_time_frame1 + port1.dt, 0).put( + shift_time_frame1 - port1.dt, 0 + ) + + expected_amplitudes["frame2"].put(0, 0).put(shift_time_frame2 - port2.dt, 0) + expected_frequencies["frame2"].put(0, 1e8).put(shift_time_frame2 - port2.dt, 1e8) + expected_phases["frame2"].put(0, 0).put(shift_time_frame2 - port2.dt, 0) + + # Inst3 + pulse_length = 40e-9 + times = np.arange(0, pulse_length, port1.dt) + values = 1 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) + expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) + expected_phases["frame1"].put(shift_time_frame1 + t, 0) + + # Inst4 + pulse_length = 28e-9 + times = np.arange(0, pulse_length, port2.dt) + values = -1 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["frame2"].put(shift_time_frame2 + t, v) + expected_frequencies["frame2"].put(shift_time_frame2 + t, 1e8) + expected_phases["frame2"].put(shift_time_frame2 + t, 0) + + # # Pad frame2 + # shift_time_frame2 = shift_time_frame2 + pulse_length + # last_time = 80e-9 + # expected_amplitudes["frame2"].put(shift_time_frame2, 0).put(last_time, 0) + # expected_frequencies["frame2"].put(shift_time_frame2, 1e8).put(last_time, 1e8) + # expected_phases["frame2"].put(shift_time_frame2, 0).put(last_time, 0) + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) + + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + + +def test_pad_different_dt(port): + port1 = Port(port_id="device_port_x1", dt=5e-9, properties={}) + port2 = Port(port_id="device_port_x2", dt=4e-9, properties={}) + frame1 = Frame(frame_id="frame1", port=port1, frequency=1e8, phase=0, is_predefined=False) + frame2 = Frame(frame_id="frame2", port=port2, frequency=1e8, phase=0, is_predefined=False) + + pulse_seq = ( + PulseSequence() + .play(frame1, ConstantWaveform(20e-9, 0.75)) + .play(frame2, ConstantWaveform(28e-9, -1)) + ) + + expected_amplitudes = {"frame1": TimeSeries(), "frame2": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries(), "frame2": TimeSeries()} + expected_phases = {"frame1": TimeSeries(), "frame2": TimeSeries()} + + # Inst1 + shift_time_frame1 = 0 + pulse_length = 20e-9 + times = np.arange(0, pulse_length, port1.dt) + values = 0.75 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) + expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) + expected_phases["frame1"].put(shift_time_frame1 + t, 0) + + # Inst2 + shift_time_frame2 = 0 + pulse_length = 28e-9 + times = np.arange(0, pulse_length, port2.dt) + values = -1 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["frame2"].put(shift_time_frame2 + t, v) + expected_frequencies["frame2"].put(shift_time_frame2 + t, 1e8) + expected_phases["frame2"].put(shift_time_frame2 + t, 0) + + # # Pad all frames to end up + # last_time_frame1 = expected_amplitudes["frame1"].times()[-1] + # last_time_frame2 = expected_amplitudes["frame2"].times()[-1] + + # last_time = 40e-9 + # expected_amplitudes["frame1"].put(last_time_frame1 + port1.dt, 0).put(last_time, 0) + # expected_frequencies["frame1"].put(last_time_frame1 + port1.dt, 1e8).put(last_time, 1e8) # noqa + # expected_phases["frame1"].put(last_time_frame1 + port1.dt, 0).put(last_time, 0) + # expected_amplitudes["frame2"].put(last_time_frame2 + port2.dt, 0).put(last_time, 0) + # expected_frequencies["frame2"].put(last_time_frame2 + port2.dt, 1e8).put(last_time, 1e8) # noqa + # expected_phases["frame2"].put(last_time_frame2 + port2.dt, 0).put(last_time, 0) + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) + + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + + +@pytest.mark.parametrize( + "literal_type, lhs, operator, rhs, expected_result", + [ + (ast.FloatLiteral, 3, "+", 2, 5), + (ast.FloatLiteral, 3, "-", 2, 1), + (ast.FloatLiteral, 3, "*", 2, 6), + (ast.FloatLiteral, 3, "/", 2, 1.5), + (ast.FloatLiteral, 3, "%", 2, 1), + (ast.FloatLiteral, 3, "**", 2, 9), + (ast.FloatLiteral, 3, "<", 2, False), + (ast.FloatLiteral, 3, ">", 2, True), + (ast.FloatLiteral, 3, ">=", 2, True), + (ast.FloatLiteral, 3, "<=", 2, False), + (ast.FloatLiteral, 3, "==", 2, False), + (ast.FloatLiteral, 3, "!=", 2, True), + (ast.BooleanLiteral, True, "&&", False, False), + (ast.BooleanLiteral, True, "||", False, True), + (ast.BooleanLiteral, True, "|", False, True), + (ast.BooleanLiteral, True, "^", False, True), + (ast.BooleanLiteral, True, "&", False, False), + (ast.IntegerLiteral, 3, "<<", 2, 12), + (ast.IntegerLiteral, 3, ">>", 2, 0), + ], +) +def test_binary_operations(literal_type, lhs, operator, rhs, expected_result): + expression = ast.BinaryExpression( + lhs=literal_type(lhs), + op=ast.BinaryOperator[operator], + rhs=literal_type(rhs), + ) + pulse_seq = PulseSequence() + parser = _ApproximationParser(program=pulse_seq._program, frames={}) + result = parser.visit_BinaryExpression(expression, Mock()) + assert result == expected_result + + +@pytest.mark.parametrize( + "literal_type, operator, literal, expected_result", + [ + (ast.FloatLiteral, "-", 3, -3), + (ast.BooleanLiteral, "!", True, False), + (ast.IntegerLiteral, "~", 3, ~3), + ], +) +def test_unary_operations(literal_type, operator, literal, expected_result): + expression = ast.UnaryExpression( + op=ast.UnaryOperator[operator], expression=literal_type(literal) + ) + pulse_seq = PulseSequence() + parser = _ApproximationParser(program=pulse_seq._program, frames={}) + result = parser.visit_UnaryExpression(expression, Mock()) + assert result == expected_result + + +@pytest.mark.xfail(raises=ValueError) +def test_duration_literal(): + literal = Mock() + pulse_seq = PulseSequence() + parser = _ApproximationParser(program=pulse_seq._program, frames={}) + parser.visit_DurationLiteral(literal, Mock()) + + +def verify_results(results, expected_amplitudes, expected_frequencies, expected_phases): + for frame_id in expected_amplitudes.keys(): + assert _all_close(results.amplitudes[frame_id], expected_amplitudes[frame_id]) + assert _all_close(results.frequencies[frame_id], expected_frequencies[frame_id]) + assert _all_close(results.phases[frame_id], expected_phases[frame_id]) + + +def to_dict(frames: Union[Frame, List]): + if not isinstance(frames, List): + frames = [frames] + frame_dict = dict() + for frame in frames: + frame_dict[frame.id] = frame + return frame_dict diff --git a/test/unit_tests/braket/pulse/test_frame.py b/test/unit_tests/braket/pulse/test_frame.py new file mode 100644 index 00000000..b70fe7ee --- /dev/null +++ b/test/unit_tests/braket/pulse/test_frame.py @@ -0,0 +1,72 @@ +# 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 oqpy import FrameVar as OQpyFrame +from oqpy import PortVar +from oqpy.base import expr_matches + +from braket.pulse import Frame, Port + + +@pytest.fixture +def port(): + return Port("test_port_ff", dt=1e-9) + + +@pytest.fixture +def frame_id(): + return "test_frame_rf" + + +def test_frame_no_properties(frame_id, port): + frame = Frame(frame_id, port, 1e6, is_predefined=True) + assert frame.id == frame_id + assert frame.port == port + assert frame.frequency == 1e6 + assert frame.phase == 0 + assert frame.is_predefined + assert frame.properties is None + + +def test_frame_to_oqpy_expression(port, frame_id): + frequency = 4e7 + phase = 1.57 + frame = Frame(frame_id, port, frequency, phase, True, {"dummy_property": "foo"}) + expected_expression = OQpyFrame( + port=PortVar(port.id), + frequency=frequency, + phase=phase, + name=frame_id, + needs_declaration=False, + ) + oq_expression = frame._to_oqpy_expression() + assert expr_matches(oq_expression, expected_expression) + + +def test_frame_equality(frame_id, port): + f = Frame(frame_id, port, 1e4, 0.57) + uneqs = [ + Frame("wrong_id", port, f.frequency, f.phase, {"foo": "bar"}), + Frame(f.id, Port("foo", dt=1e-9), f.frequency, f.phase), + Frame(f.id, f.port, 1e5, f.phase), + Frame(f.id, f.port, f.frequency, 0.23), + ] + eqs = [ + Frame(f.id, f.port, f.frequency, f.phase, {"a": "b"}), + Frame(f.id, f.port, f.frequency, f.phase), + ] + for x in uneqs: + assert f != x + for x in eqs: + assert f == x diff --git a/test/unit_tests/braket/pulse/test_port.py b/test/unit_tests/braket/pulse/test_port.py new file mode 100644 index 00000000..f307ea87 --- /dev/null +++ b/test/unit_tests/braket/pulse/test_port.py @@ -0,0 +1,61 @@ +# 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 oqpy import PortVar +from oqpy.base import expr_matches + +from braket.pulse import Port + + +@pytest.fixture +def port_id(): + return "test_port_ff" + + +@pytest.fixture +def port_time_resolution(): + return 1e-9 + + +@pytest.fixture +def port_properties(): + return {"dt": 1e-6, "direction": "tx"} + + +@pytest.fixture +def port(port_id, port_time_resolution, port_properties): + return Port(port_id, port_time_resolution, port_properties) + + +def test_port_no_properties(port_id, port_time_resolution): + port = Port(port_id, port_time_resolution) + assert port.id == port_id + assert port.dt == port_time_resolution + assert port.properties is None + + +def test_port_to_oqpy_expression(port, port_id): + expected_expression = PortVar(port_id) + assert expr_matches(port._to_oqpy_expression(), expected_expression) + + +def test_port_equality(port, port_time_resolution): + p2 = Port(port.id, port_time_resolution) + p3 = Port("random_id", port_time_resolution, properties=port.properties) + p4 = Port(port.id, port_time_resolution, properties={"random_property": "foo"}) + p5 = Port(port.id, 0) + assert port == p2 + assert port == p4 + assert port == p5 + assert port != p3 diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py new file mode 100644 index 00000000..657d5762 --- /dev/null +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -0,0 +1,334 @@ +# 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 FreeParameter +from braket.pulse import ( + ArbitraryWaveform, + ConstantWaveform, + DragGaussianWaveform, + Frame, + GaussianWaveform, + Port, + PulseSequence, +) + + +@pytest.fixture +def port(): + return Port(port_id="device_port_x0", dt=1e-9, properties={}) + + +@pytest.fixture +def predefined_frame_1(port): + return Frame( + frame_id="predefined_frame_1", frequency=2e9, port=port, phase=0, is_predefined=True + ) + + +@pytest.fixture +def predefined_frame_2(port): + return Frame(frame_id="predefined_frame_2", frequency=1e6, port=port, is_predefined=True) + + +@pytest.fixture +def user_defined_frame(port): + return Frame( + frame_id="user_defined_frame_0", + port=port, + frequency=1e7, + phase=3.14, + is_predefined=False, + properties={"associatedGate": "rz"}, + ) + + +@pytest.fixture +def conflicting_user_defined_frame(): + return Frame( + frame_id="user_defined_frame_0", + port=Port("wrong_port", dt=1e-9), + frequency=1e7, + phase=3.14, + is_predefined=False, + properties={"associatedGate": "rz"}, + ) + + +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);", + "}", + ] + ) + assert pulse_sequence.to_ir() == expected_str + + +def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined_frame_2): + param = FreeParameter("a") + 2 * FreeParameter("b") + pulse_sequence = ( + PulseSequence() + .set_frequency(predefined_frame_1, param) + .shift_frequency(predefined_frame_1, param) + .set_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) + .delay(predefined_frame_1, param) + .delay(predefined_frame_1, 1e-3) + .barrier([predefined_frame_1, predefined_frame_2]) + .play( + predefined_frame_1, + GaussianWaveform( + length=FreeParameter("length_g"), sigma=FreeParameter("sigma_g"), id="gauss_wf" + ), + ) + .play( + predefined_frame_2, + DragGaussianWaveform( + length=FreeParameter("length_dg"), + sigma=FreeParameter("sigma_dg"), + beta=0.2, + id="drag_gauss_wf", + ), + ) + .play( + predefined_frame_1, + ConstantWaveform( + length=FreeParameter("length_c"), iq=complex(2, 0.3), id="constant_wf" + ), + ) + .play( + predefined_frame_2, + ArbitraryWaveform([complex(1, 0.4), 0, 0.3, complex(0.1, 0.2)], id="arb_wf"), + ) + .capture_v0(predefined_frame_2) + ) + expected_str_unbound = "\n".join( + [ + "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);", + " 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);", + " 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;", + " 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);", + " psb[1] = capture_v0(predefined_frame_2);", + "}", + ] + ) + 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"), + ] + ) + 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} + ) + b_bound_call = pulse_sequence(b=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;", + "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);", + " 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);", + " 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;", + " 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);", + " 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 == set([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 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);", + " 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;", + " 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);", + " 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 + + +@pytest.mark.parametrize( + "method_name, method_kwargs", + [ + ("set_frequency", {"frequency": 1e4}), + ("shift_frequency", {"frequency": 2e4}), + ("set_phase", {"phase": 0.3}), + ("shift_phase", {"phase": 0.2}), + ("set_scale", {"scale": 0.7}), + ("delay", {"duration": 1e-5}), + ("barrier", None), + ("play", {"waveform": ConstantWaveform(1e-3, complex(1, 2), "wf_id")}), + ], +) +def test_pulse_sequence_conflicting_frames( + user_defined_frame, conflicting_user_defined_frame, method_name, method_kwargs +): + ps = PulseSequence().shift_frequency(user_defined_frame, 1e2) + method = getattr(ps, method_name) + + with pytest.raises(ValueError): + method(conflicting_user_defined_frame, **method_kwargs) if method_kwargs else method( + conflicting_user_defined_frame + ) + + +def test_pulse_sequence_conflicting_wf(user_defined_frame): + wf = ConstantWaveform(1e-3, complex(1, 2), "wf_id") + conflicting_wf = ConstantWaveform(1e-4, complex(1, 2), "wf_id") + ps = PulseSequence().play(user_defined_frame, wf) + + with pytest.raises(ValueError): + ps.play(user_defined_frame, conflicting_wf) + + +def test_pulse_sequence_to_time_trace_program_mutation(user_defined_frame): + wf = ConstantWaveform(1e-3, complex(1, 2), "wf_id") + ps = PulseSequence().play(user_defined_frame, wf) + initial_vars = dict(ps._program.undeclared_vars) + ps.to_time_trace() + assert initial_vars == ps._program.undeclared_vars + + +def test_pulse_sequence_to_ir(predefined_frame_1, predefined_frame_2): + pulse_sequence = ( + PulseSequence() + .set_frequency(predefined_frame_1, 3e9) + .shift_frequency(predefined_frame_1, 1e9) + .set_phase(predefined_frame_1, -0.5) + .shift_phase(predefined_frame_1, 0.1) + .set_scale(predefined_frame_1, 0.25) + .capture_v0(predefined_frame_1) + .delay([predefined_frame_1, predefined_frame_2], 2e-9) + .delay(predefined_frame_1, 1e-6) + .barrier([predefined_frame_1, predefined_frame_2]) + .play(predefined_frame_1, GaussianWaveform(length=1e-3, sigma=0.7, id="gauss_wf")) + .play( + predefined_frame_2, + DragGaussianWaveform(length=3e-3, sigma=0.4, beta=0.2, id="drag_gauss_wf"), + ) + .play( + predefined_frame_1, + ConstantWaveform(length=4e-3, iq=complex(2, 0.3), id="constant_wf"), + ) + .play( + predefined_frame_2, + ArbitraryWaveform([complex(1, 0.4), 0, 0.3, complex(0.1, 0.2)], id="arb_wf"), + ) + .capture_v0(predefined_frame_2) + ) + expected_str = "\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 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);", + " 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[1000.0ns] 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);", + " psb[1] = capture_v0(predefined_frame_2);", + "}", + ] + ) + assert pulse_sequence.to_ir() == expected_str diff --git a/test/unit_tests/braket/pulse/test_waveforms.py b/test/unit_tests/braket/pulse/test_waveforms.py new file mode 100644 index 00000000..198bcd10 --- /dev/null +++ b/test/unit_tests/braket/pulse/test_waveforms.py @@ -0,0 +1,248 @@ +# 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 re +from copy import deepcopy + +from oqpy import Program + +from braket.circuits.free_parameter import FreeParameter +from braket.pulse import ArbitraryWaveform, ConstantWaveform, DragGaussianWaveform, GaussianWaveform +from braket.pulse.ast.qasm_parser import ast_to_qasm + + +def test_arbitrary_waveform(): + amps = [complex(1, 2), complex(0.3, -1), 0, 4.2] + id = "arb_wf_x" + wf = ArbitraryWaveform(amps, id) + assert wf.amplitudes == amps + assert wf.id == id + oq_exp = wf._to_oqpy_expression() + assert oq_exp.init_expression == amps + assert oq_exp.name == wf.id + + +def test_arbitrary_waveform_default_params(): + amps = [1, 4, 5] + wf = ArbitraryWaveform(amps) + assert wf.amplitudes == amps + assert re.match(r"[A-Za-z]{10}", wf.id) + + +def test_arbitrary_wf_eq(): + wf = ArbitraryWaveform([1, 4, 5], "wf_x") + wf_2 = ArbitraryWaveform(wf.amplitudes, wf.id) + assert wf_2 == wf + for att in ["amplitudes", "id"]: + wfc = deepcopy(wf_2) + setattr(wfc, att, "wrong_value") + assert wf != wfc + + +def test_constant_waveform(): + length = 4e-3 + iq = 4 + id = "const_wf_x" + wf = ConstantWaveform(length, iq, id) + assert wf.length == length + assert wf.iq == iq + assert wf.id == id + + _assert_wf_qasm(wf, "waveform const_wf_x = constant(4000000.0ns, 4);") + + +def test_constant_waveform_default_params(): + amps = [1, 4, 5] + wf = ArbitraryWaveform(amps) + assert wf.amplitudes == amps + assert re.match(r"[A-Za-z]{10}", wf.id) + + +def test_constant_wf_eq(): + wf = ConstantWaveform(4e-3, complex(2, 3), "wf_c") + wf_2 = ConstantWaveform(wf.length, wf.iq, wf.id) + assert wf_2 == wf + for att in ["length", "iq", "id"]: + wfc = deepcopy(wf_2) + setattr(wfc, att, "wrong_value") + assert wf != wfc + + +def test_constant_wf_free_params(): + wf = ConstantWaveform( + FreeParameter("length_v") + FreeParameter("length_w"), iq=complex(2, -3), id="const_wf" + ) + 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);", + ) + + 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);") + + +def test_drag_gaussian_waveform(): + 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) + assert wf.id == id + assert wf.zero_at_edges == zero_at_edges + assert wf.amplitude == amplitude + assert wf.beta == beta + 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);" + ) + + +def test_drag_gaussian_waveform_default_params(): + length = 4e-9 + sigma = 0.3 + beta = 0.6 + wf = DragGaussianWaveform(length, sigma, beta) + assert re.match(r"[A-Za-z]{10}", wf.id) + assert wf.zero_at_edges is False + assert wf.amplitude == 1 + assert wf.beta == beta + assert wf.sigma == sigma + assert wf.length == length + + +def test_drag_gaussian_wf_eq(): + wf = DragGaussianWaveform(4e-3, 0.3, 0.2, 0.7, True, "wf_dg") + wf_2 = DragGaussianWaveform(wf.length, wf.sigma, wf.beta, wf.amplitude, wf.zero_at_edges, wf.id) + assert wf_2 == wf + for att in ["length", "sigma", "beta", "amplitude", "zero_at_edges", "id"]: + wfc = deepcopy(wf_2) + setattr(wfc, att, "wrong_value") + 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"), + FreeParameter("sigma_a") + FreeParameter("sigma_b"), + FreeParameter("beta_y"), + FreeParameter("amp_z"), + id="d_gauss_wf", + ) + assert wf.parameters == [ + FreeParameter("length_v"), + FreeParameter("sigma_a") + FreeParameter("sigma_b"), + FreeParameter("beta_y"), + FreeParameter("amp_z"), + ] + _assert_wf_qasm( + wf, + "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);", + ) + + wf_2 = wf.bind_values(length_v=0.6, sigma_a=0.4) + assert wf_2.parameters == [ + 0.6, + 0.4 + FreeParameter("sigma_b"), + FreeParameter("beta_y"), + FreeParameter("amp_z"), + ] + _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);", + ) + + 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);" + ) + + +def test_gaussian_waveform_default_params(): + length = 4e-9 + sigma = 0.3 + wf = GaussianWaveform(length, sigma) + assert re.match(r"[A-Za-z]{10}", wf.id) + assert wf.zero_at_edges is False + assert wf.amplitude == 1 + assert wf.sigma == sigma + assert wf.length == length + + +def test_gaussian_wf_eq(): + wf = GaussianWaveform(4e-3, 0.3, 0.7, True, "wf_dg") + wf_2 = GaussianWaveform(wf.length, wf.sigma, wf.amplitude, wf.zero_at_edges, wf.id) + assert wf_2 == wf + for att in ["length", "sigma", "amplitude", "zero_at_edges", "id"]: + wfc = deepcopy(wf_2) + setattr(wfc, att, "wrong_value") + assert wf != wfc + + +def test_gaussian_wf_free_params(): + wf = GaussianWaveform( + FreeParameter("length_v"), FreeParameter("sigma_x"), FreeParameter("amp_z"), id="gauss_wf" + ) + assert wf.parameters == [ + FreeParameter("length_v"), + FreeParameter("sigma_x"), + FreeParameter("amp_z"), + ] + _assert_wf_qasm( + wf, + "waveform gauss_wf = gaussian((1000000000.0*length_v)ns, (1000000000.0*sigma_x)ns, " + "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_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);") + + +def _assert_wf_qasm(waveform, expected_qasm): + p = Program(None) + p.declare(waveform._to_oqpy_expression()) + assert ast_to_qasm(p.to_ast(include_externs=False)) == expected_qasm diff --git a/test/unit_tests/braket/timings/test_time_series.py b/test/unit_tests/braket/timings/test_time_series.py new file mode 100644 index 00000000..8f322b5f --- /dev/null +++ b/test/unit_tests/braket/timings/test_time_series.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. + +from decimal import Decimal + +import pytest + +from braket.timings.time_series import TimeSeries, _all_close + + +@pytest.fixture +def default_values(): + return [(2700, 25.1327), (300, 25.1327), (600, 15.1327), (Decimal(0.3), Decimal(0.4))] + + +@pytest.fixture +def default_time_series(default_values): + time_series = TimeSeries() + for value in default_values: + time_series.put(value[0], value[1]) + return time_series + + +def test_add_chaining(): + time_series = ( + TimeSeries() + .put(time=0, value=0) + .put(300, 25.1327) + .put(2700, 25.1327) + .put(3000, 0) + .put(2700, 25.1327) + ) + assert len(time_series) == 4 + + +def test_iteration_sorted(default_values, default_time_series): + sorted_returned_values = [(item.time, item.value) for item in default_time_series] + assert sorted_returned_values == sorted(default_values) + + +def test_get_sorted(default_values, default_time_series): + sorted_values = sorted(default_values) + assert default_time_series.times() == [item[0] for item in sorted_values] + assert default_time_series.values() == [item[1] for item in sorted_values] + + +@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")]), + ], +) +def test_discretize_times(default_time_series, time_res, expected_times): + value_res = Decimal("1") + assert expected_times == default_time_series.discretize(time_res, value_res).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 + (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")]), + (Decimal(100), [Decimal("0"), Decimal("0"), Decimal("0"), Decimal("0")]), + ], +) +def test_discretize_values(default_time_series, value_res, expected_values): + time_res = Decimal("0.1") + assert expected_values == default_time_series.discretize(time_res, value_res).values() + + +@pytest.mark.parametrize( + "first_series, second_series, expected_result", + [ + (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), + ], +) +def test_all_close(first_series, second_series, expected_result): + result = _all_close(first_series, second_series) + assert result == expected_result From ec112be6b15827339cca07862ab02d308af4aa90 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 20 Oct 2022 20:47:46 +0000 Subject: [PATCH 0552/1165] prepare release v1.32.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 9c6a2e97..0a7391c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.32.0 (2022-10-20) + +### Features + + * Add support for pulse control + ## v1.31.1 (2022-10-12) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index a2766db3..7058e40d 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.31.2.dev0" +__version__ = "1.32.0" From 17a9df18612ba67744754ae7641200ed7891a550 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 20 Oct 2022 20:47:46 +0000 Subject: [PATCH 0553/1165] update development version to v1.32.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 7058e40d..f20130ee 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.32.0" +__version__ = "1.32.1.dev0" From d0ca36bb62a21ec9af159f0b2afdce299f9d0753 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Thu, 20 Oct 2022 17:51:10 -0700 Subject: [PATCH 0554/1165] fix: require boto containing latest API changes (#462) [boto3 v1.22.3](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst#1223) contains Braket API changes allowing multiple instances --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1596c61d..7fe568bf 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ "oqpy==0.1.0", "backoff", "boltons", - "boto3", + "boto3>=1.22.3", "nest-asyncio", "networkx", "numpy", From f7683c1042336753e589bb6d4e363fccae19fd54 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 24 Oct 2022 16:26:24 +0000 Subject: [PATCH 0555/1165] prepare release v1.32.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 0a7391c0..4396f90f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.32.1 (2022-10-24) + +### Bug Fixes and Other Changes + + * require boto containing latest API changes + ## v1.32.0 (2022-10-20) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index f20130ee..2e6623ac 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.32.1.dev0" +__version__ = "1.32.1" From e7ad0632d7e65fe0c91712255e581b352f755dac Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 24 Oct 2022 16:26:24 +0000 Subject: [PATCH 0556/1165] update development version to v1.32.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 2e6623ac..85c71845 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.32.1" +__version__ = "1.32.2.dev0" From 7ac544164bb27197694cc919db82b44b91f5748b Mon Sep 17 00:00:00 2001 From: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com> Date: Mon, 24 Oct 2022 17:40:42 -0700 Subject: [PATCH 0557/1165] doc: update FreeParameter class with example (#460) * doc: update FreeParameter class with example * address suggestions --- src/braket/parametric/free_parameter.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/braket/parametric/free_parameter.py b/src/braket/parametric/free_parameter.py index eadb6b14..3ac4cdd8 100644 --- a/src/braket/parametric/free_parameter.py +++ b/src/braket/parametric/free_parameter.py @@ -27,8 +27,15 @@ class FreeParameter(FreeParameterExpression): 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 - for a numerical value later on. Circuits with FreeParameters present will NOT run. Values must - be substituted prior to execution. + for a numerical value later on. Circuits with FreeParameters must have all the inputs + provided at execution or substituted prior to execution. + + Examples: + >>> alpha, beta = FreeParameter("alpha"), FreeParameter("beta") + >>> circuit = Circuit().rx(target=0, angle=alpha).ry(target=1, angle=beta) + >>> circuit = circuit(alpha=0.3) + >>> device = LocalSimulator() + >>> device.run(circuit, inputs={'beta': 0.5} shots=10) """ def __init__(self, name: str): From 53876010c78b23b39c656b5e2abbaff06be07bcd Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 25 Oct 2022 16:26:22 +0000 Subject: [PATCH 0558/1165] prepare release v1.32.1.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 4396f90f..1e14a522 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.32.1.post0 (2022-10-25) + +### Documentation Changes + + * update FreeParameter class with example + ## v1.32.1 (2022-10-24) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 85c71845..9a4add86 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.32.2.dev0" +__version__ = "1.32.1.post0" From 7cdd146a36b2097f80eaab8de2ebfb66c9ad4ac4 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 25 Oct 2022 16:26:22 +0000 Subject: [PATCH 0559/1165] update development version to v1.32.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 9a4add86..85c71845 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.32.1.post0" +__version__ = "1.32.2.dev0" From 014d8ed3fdbf78e19969fc2c2b250cd8a735ac4f Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Mon, 31 Oct 2022 19:23:38 -0700 Subject: [PATCH 0560/1165] feature: Support analog Hamiltonian simulations (#467) Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> Co-authored-by: Stephen Face <60493521+shpface@users.noreply.github.com> Co-authored-by: Kshitij Chhabra Co-authored-by: Aaron Berdy Co-authored-by: Cody Wang Co-authored-by: Peter Komar Co-authored-by: Mao Lin --- setup.py | 4 +- src/braket/ahs/__init__.py | 0 .../ahs/analog_hamiltonian_simulation.py | 157 ++++++++++++ src/braket/ahs/atom_arrangement.py | 124 +++++++++ src/braket/ahs/discretization_types.py | 27 ++ src/braket/ahs/driving_field.py | 112 ++++++++ src/braket/ahs/field.py | 76 ++++++ src/braket/ahs/hamiltonian.py | 60 +++++ src/braket/ahs/pattern.py | 49 ++++ src/braket/ahs/shifting_field.py | 78 ++++++ src/braket/aws/aws_device.py | 21 +- src/braket/aws/aws_quantum_task.py | 43 +++- src/braket/aws/aws_quantum_task_batch.py | 7 +- src/braket/devices/local_simulator.py | 28 +- src/braket/tasks/__init__.py | 4 + ...iltonian_simulation_quantum_task_result.py | 107 ++++++++ .../test_local_braket_simulator.py | 1 + .../ahs/test_analog_hamiltonian_simulation.py | 241 ++++++++++++++++++ .../braket/ahs/test_atom_arrangement.py | 115 +++++++++ .../braket/ahs/test_driving_field.py | 112 ++++++++ test/unit_tests/braket/ahs/test_field.py | 106 ++++++++ .../unit_tests/braket/ahs/test_hamiltonian.py | 45 ++++ test/unit_tests/braket/ahs/test_pattern.py | 94 +++++++ .../braket/ahs/test_shifting_field.py | 82 ++++++ .../braket/aws/common_test_utils.py | 34 +++ .../braket/aws/test_aws_quantum_task.py | 39 +++ .../braket/devices/test_local_simulator.py | 59 +++++ ...alog_hamiltonian_simulation_task_result.py | 192 ++++++++++++++ 28 files changed, 2002 insertions(+), 15 deletions(-) create mode 100644 src/braket/ahs/__init__.py create mode 100644 src/braket/ahs/analog_hamiltonian_simulation.py create mode 100644 src/braket/ahs/atom_arrangement.py create mode 100644 src/braket/ahs/discretization_types.py create mode 100644 src/braket/ahs/driving_field.py create mode 100644 src/braket/ahs/field.py create mode 100644 src/braket/ahs/hamiltonian.py create mode 100644 src/braket/ahs/pattern.py create mode 100644 src/braket/ahs/shifting_field.py create mode 100644 src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py create mode 100644 test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py create mode 100644 test/unit_tests/braket/ahs/test_atom_arrangement.py create mode 100644 test/unit_tests/braket/ahs/test_driving_field.py create mode 100644 test/unit_tests/braket/ahs/test_field.py create mode 100644 test/unit_tests/braket/ahs/test_hamiltonian.py create mode 100644 test/unit_tests/braket/ahs/test_pattern.py create mode 100644 test/unit_tests/braket/ahs/test_shifting_field.py create mode 100644 test/unit_tests/braket/tasks/test_analog_hamiltonian_simulation_task_result.py diff --git a/setup.py b/setup.py index 7fe568bf..145f0f3b 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.11.0", - "amazon-braket-default-simulator>=1.9.0", + "amazon-braket-schemas>=1.12.0", + "amazon-braket-default-simulator>=1.10.0", "oqpy==0.1.0", "backoff", "boltons", diff --git a/src/braket/ahs/__init__.py b/src/braket/ahs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/braket/ahs/analog_hamiltonian_simulation.py b/src/braket/ahs/analog_hamiltonian_simulation.py new file mode 100644 index 00000000..f13f96bf --- /dev/null +++ b/src/braket/ahs/analog_hamiltonian_simulation.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 import defaultdict +from functools import singledispatch +from typing import Tuple + +import braket.ir.ahs as ir +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.shifting_field import ShiftingField +from braket.device_schema import DeviceActionType + + +class AnalogHamiltonianSimulation: + SHIFTING_FIELDS_PROPERTY = "shifting_fields" + DRIVING_FIELDS_PROPERTY = "driving_fields" + + def __init__(self, register: AtomArrangement, hamiltonian: Hamiltonian) -> None: + """Creates an AnalogHamiltonianSimulation with a given setup, and terms. + + Args: + register (AtomArrangement): The initial atom arrangement for the simulation. + hamiltonian (Hamiltonian): The hamiltonian to simulate. + """ + self._register = register + self._hamiltonian = hamiltonian + + @property + def register(self) -> AtomArrangement: + """AtomArrangement: The initial atom arrangement for the simulation.""" + return self._register + + @property + def hamiltonian(self) -> Hamiltonian: + """Hamiltonian: The hamiltonian to simulate.""" + return self._hamiltonian + + def to_ir(self) -> ir.Program: + """Converts the Analog Hamiltonian Simulation into the canonical intermediate + representation. + + Returns: + Program: A representation of the circuit in the IR format. + """ + return ir.Program( + setup=ir.Setup(ahs_register=self._register_to_ir()), + hamiltonian=self._hamiltonian_to_ir(), + ) + + def _register_to_ir(self) -> ir.AtomArrangement: + return ir.AtomArrangement( + sites=[site.coordinate for site in self.register], + filling=[1 if site.site_type == SiteType.FILLED else 0 for site in self.register], + ) + + def _hamiltonian_to_ir(self) -> ir.Hamiltonian: + terms = defaultdict(list) + for term in self.hamiltonian.terms: + term_type, term_ir = _get_term_ir(term) + terms[term_type].append(term_ir) + return ir.Hamiltonian( + drivingFields=terms[AnalogHamiltonianSimulation.DRIVING_FIELDS_PROPERTY], + shiftingFields=terms[AnalogHamiltonianSimulation.SHIFTING_FIELDS_PROPERTY], + ) + + def discretize(self, device) -> AnalogHamiltonianSimulation: + """Creates a new AnalogHamiltonianSimulation with all numerical values represented + as Decimal objects with fixed precision based on the capabilities of the device. + + Args: + device (AwsDevice): The device for which to discretize the program. + + Returns: + AnalogHamiltonianSimulation: A discretized version of this program. + + Raises: + DiscretizeError: 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 + ): + raise DiscretizationError( + f"AwsDevice {device} does not accept {required_action_schema} action schema." + ) + + properties = DiscretizationProperties( + device.properties.paradigm.lattice, device.properties.paradigm.rydberg + ) + discretized_register = self.register.discretize(properties) + discretized_hamiltonian = self.hamiltonian.discretize(properties) + return AnalogHamiltonianSimulation( + register=discretized_register, hamiltonian=discretized_hamiltonian + ) + + +@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: ShiftingField) -> Tuple[str, ir.ShiftingField]: + return AnalogHamiltonianSimulation.SHIFTING_FIELDS_PROPERTY, ir.ShiftingField( + 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", + ), + ) diff --git a/src/braket/ahs/atom_arrangement.py b/src/braket/ahs/atom_arrangement.py new file mode 100644 index 00000000..da0dd13a --- /dev/null +++ b/src/braket/ahs/atom_arrangement.py @@ -0,0 +1,124 @@ +# 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 dataclasses import dataclass +from decimal import Decimal +from enum import Enum +from numbers import Number +from typing import Iterator, List, Tuple, Union + +import numpy as np + +from braket.ahs.discretization_types import DiscretizationError, DiscretizationProperties + + +class SiteType(Enum): + VACANT = "Vacant" + FILLED = "Filled" + + +@dataclass +class AtomArrangementItem: + """Represents an item (coordinate and metadata) in an atom arrangement.""" + + coordinate: Tuple[Number, Number] + site_type: SiteType + + def _validate_coordinate(self): + if len(self.coordinate) != 2: + raise ValueError(f"{self.coordinate} must be of length 2") + for idx, num in enumerate(self.coordinate): + if not isinstance(num, Number): + raise TypeError(f"{num} at position {idx} must be a number") + + def _validate_site_type(self): + allowed_site_types = {SiteType.FILLED, SiteType.VACANT} + if self.site_type not in allowed_site_types: + raise ValueError(f"{self.site_type} must be one of {allowed_site_types}") + + def __post_init__(self): + self._validate_coordinate() + self._validate_site_type() + + +class AtomArrangement: + def __init__(self): + """Represents a set of coordinates that can be used as a register to an + AnalogHamiltonianSimulation. + """ + self._sites = [] + + def add( + self, + coordinate: Union[Tuple[Number, Number], np.ndarray], + site_type: SiteType = SiteType.FILLED, + ) -> AtomArrangement: + """Add a coordinate to the atom arrangement. + + Args: + coordinate (Union[Tuple[Number, Number], np.ndarray]): The coordinate of the + 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). + """ + self._sites.append(AtomArrangementItem(tuple(coordinate), site_type)) + return self + + def coordinate_list(self, coordinate_index: Number) -> List[Number]: + """Returns all the coordinates at the given index. + + Args: + coordinate_index (Number): The index to get for each coordinate. + + Returns: + List[Number]:The list of coordinates at the given index. + + Example: + To get a list of all x-coordinates: coordinate_list(0) + To get a list of all y-coordinates: coordinate_list(1) + """ + return [site.coordinate[coordinate_index] for site in self._sites] + + def __iter__(self) -> Iterator: + return self._sites.__iter__() + + def __len__(self): + return self._sites.__len__() + + def discretize(self, properties: DiscretizationProperties) -> AtomArrangement: + """Creates a discretized version of the atom arrangement, + rounding all site coordinates to the closest multiple of the + resolution. The types of the sites are unchanged. + + Args: + properties (DiscretizationProperties): Capabilities of a device that represent the + resolution with which the device can implement the parameters. + + Returns: + AtomArrangement: A new discretized atom arrangement. + """ + try: + position_res = properties.lattice.geometry.positionResolution + discretized_arrangement = AtomArrangement() + for site in self._sites: + new_coordinates = tuple( + (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}") diff --git a/src/braket/ahs/discretization_types.py b/src/braket/ahs/discretization_types.py new file mode 100644 index 00000000..43405b9a --- /dev/null +++ b/src/braket/ahs/discretization_types.py @@ -0,0 +1,27 @@ +from dataclasses import dataclass +from typing import Any + + +class DiscretizationError(Exception): + """Raised if the discretization of the numerical values of the AHS program fails.""" + + pass + + +@dataclass +class DiscretizationProperties: + """Capabilities of a device that represent the resolution with which the device can + implement the parameters. + + lattice (Any): configuration values for discretization of the lattice geometry, + including the position resolution. + rydberg (Any): configuration values for discretization of Rydberg fields. + + Examples: + lattice.geometry.positionResolution = Decimal("1E-7") + rydberg.rydbergGlobal.timeResolution = Decimal("1E-9") + rydberg.rydbergGlobal.phaseResolution = Decimal("5E-7") + """ + + lattice: Any + rydberg: Any diff --git a/src/braket/ahs/driving_field.py b/src/braket/ahs/driving_field.py new file mode 100644 index 00000000..e64f0327 --- /dev/null +++ b/src/braket/ahs/driving_field.py @@ -0,0 +1,112 @@ +# 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 List, Union + +from braket.ahs.discretization_types import DiscretizationProperties +from braket.ahs.field import Field +from braket.ahs.hamiltonian import Hamiltonian +from braket.timings.time_series import TimeSeries + + +class DrivingField(Hamiltonian): + def __init__( + self, + amplitude: Union[Field, TimeSeries], + phase: Union[Field, TimeSeries], + detuning: Union[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 + in an AnalogHamiltonianSimulation, defined by the formula + + .. math:: + H_{drive} (t) := \frac{\Omega(t)}{2} e^{i \phi(t)} \left( + \sum_k |g_k \rangle \langle r_k| + |r_k \rangle \langle g_k| + \right) - \Delta(t) \sum_k{| r_k \rangle \langle r_k |} + + where + + :math:`\Omega(t)` is the global Rabi frequency in rad/s, + + :math:`\phi(t)` is the global phase in rad/s, + + :math:`\Delta(t)` is the global detuning in rad/s, + + :math:`|g_k \rangle` is the ground state of atom :math:`k`, + + :math:`|r_k \rangle` is the Rydberg state of atom :math:`k`. + + with the sum :math:`\sum_k` taken over all target atoms. + + Args: + amplitude (Union[Field, TimeSeries]): global amplitude (:math:`\Omega(t)`). + Time is in s, and value is in rad/s. + phase (Union[Field, TimeSeries]): global phase (:math:`\phi(t)`). + Time is in s, and value is in rad/s. + detuning (Union[Field, TimeSeries]): global detuning (:math:`\Delta(t)`). + Time is in s, and value is in rad/s. + """ + super().__init__() + self._amplitude = amplitude if isinstance(amplitude, Field) else Field(amplitude) + self._phase = phase if isinstance(phase, Field) else Field(phase) + self._detuning = detuning if isinstance(detuning, Field) else Field(detuning) + + @property + def terms(self) -> List[Hamiltonian]: + return [self] + + @property + def amplitude(self) -> Field: + r"""Field: The global amplitude (:math:`\Omega(t)`). Time is in s, and value is in rad/s.""" + return self._amplitude + + @property + def phase(self) -> Field: + r"""Field: The global phase (:math:`\phi(t)`). Time is in s, and value is in rad/s.""" + return self._phase + + @property + def detuning(self) -> Field: + r"""Field: global detuning (:math:`\Delta(t)`). Time is in s, and value is in rad/s.""" + return self._detuning + + def discretize(self, properties: DiscretizationProperties) -> DrivingField: + """Creates a discretized version of the Hamiltonian. + + Args: + properties (DiscretizationProperties): Capabilities of a device that represent the + resolution with which the device can implement the parameters. + + Returns: + DrivingField: A new discretized DrivingField. + """ + driving_parameters = properties.rydberg.rydbergGlobal + time_resolution = driving_parameters.timeResolution + discretized_amplitude = self.amplitude.discretize( + time_resolution=time_resolution, + value_resolution=driving_parameters.rabiFrequencyResolution, + ) + discretized_phase = self.phase.discretize( + time_resolution=time_resolution, + value_resolution=driving_parameters.phaseResolution, + ) + discretized_detuning = self.detuning.discretize( + time_resolution=time_resolution, + value_resolution=driving_parameters.detuningResolution, + ) + 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 new file mode 100644 index 00000000..1f0a2c12 --- /dev/null +++ b/src/braket/ahs/field.py @@ -0,0 +1,76 @@ +# 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 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 + + +class Field: + def __init__(self, time_series: TimeSeries, pattern: Optional[Pattern] = None) -> None: + """A space and time dependent parameter of a program. + + Args: + time_series (TimeSeries): The time series representing this field. + pattern (Optional[Pattern]): The local pattern of real numbers. + """ + self._time_series = time_series + self._pattern = pattern + + @property + def time_series(self) -> TimeSeries: + """TimeSeries: The time series representing this field.""" + return self._time_series + + @property + def pattern(self) -> Optional[Pattern]: + """Optional[Pattern]: The local pattern of real numbers.""" + return self._pattern + + def discretize( + self, + time_resolution: Decimal, + value_resolution: Decimal, + pattern_resolution: Optional[Decimal] = None, + ) -> Field: + """Creates a discretized version of the field, + where time, value and pattern are rounded to the + closest multiple of their corresponding resolutions. + + Args: + time_resolution (Decimal): Time resolution + value_resolution (Decimal): Value resolution + pattern_resolution (Decimal or None): 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/hamiltonian.py b/src/braket/ahs/hamiltonian.py new file mode 100644 index 00000000..83b80e69 --- /dev/null +++ b/src/braket/ahs/hamiltonian.py @@ -0,0 +1,60 @@ +# 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 List, Optional + +from braket.ahs.discretization_types import DiscretizationProperties + + +class Hamiltonian: + def __init__(self, terms: Optional[List[Hamiltonian]] = None): + r"""A Hamiltonian representing a system to be simulated. + + A Hamiltonian :math:`H` may be expressed as a sum of multiple terms + + .. math:: + H = \sum_i H_i + """ + self._terms = terms or [] + + @property + def terms(self) -> List[Hamiltonian]: + """List[Hamiltonian]: The list of terms in this Hamiltonian.""" + return self._terms + + def discretize(self, properties: DiscretizationProperties) -> Hamiltonian: + """Creates a discretized version of the Hamiltonian. + + Args: + properties (DiscretizationProperties): Capabilities of a device that represent the + resolution with which the device can implement the parameters. + + Returns: + Hamiltonian: A new discretized Hamiltonian. + """ + terms = [term.discretize(properties) for term in self.terms] + return Hamiltonian(terms=terms) + + def __iadd__(self, other: Hamiltonian) -> Hamiltonian: + if type(self) is not Hamiltonian: + raise ValueError(f"Unable to modify Hamiltonian of type {type(self)}") + self._terms.extend(other.terms) + return self + + def __add__(self, other: Hamiltonian) -> Hamiltonian: + terms = [] + terms.extend(self.terms) + terms.extend(other.terms) + return Hamiltonian(terms) diff --git a/src/braket/ahs/pattern.py b/src/braket/ahs/pattern.py new file mode 100644 index 00000000..ebd1eafc --- /dev/null +++ b/src/braket/ahs/pattern.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 decimal import Decimal +from numbers import Number +from typing import List + + +class Pattern: + def __init__(self, series: List[Number]): + """Represents the spatial dependence of a Field. + + Args: + series (List[Number]): A series of numbers representing the the local + pattern of real numbers. + """ + self._series = series + + @property + def series(self) -> List[Number]: + """List[Number]: A series of numbers representing the local + pattern of real numbers.""" + return self._series + + def discretize(self, resolution: 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 + + Returns: + Pattern: The new discretized pattern + """ + discretized_series = [round(Decimal(num) / resolution) * resolution for num in self.series] + return Pattern(series=discretized_series) diff --git a/src/braket/ahs/shifting_field.py b/src/braket/ahs/shifting_field.py new file mode 100644 index 00000000..2801a2a9 --- /dev/null +++ b/src/braket/ahs/shifting_field.py @@ -0,0 +1,78 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +from __future__ import annotations + +from typing import List + +from braket.ahs.discretization_types import DiscretizationProperties +from braket.ahs.field import Field +from braket.ahs.hamiltonian import Hamiltonian + + +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 + + 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) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index c196e91c..a5456bee 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -22,6 +22,7 @@ from botocore.errorfactory import ClientError 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 @@ -90,7 +91,12 @@ def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): def run( self, task_specification: Union[ - Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence + Circuit, + Problem, + OpenQasmProgram, + BlackbirdProgram, + PulseSequence, + AnalogHamiltonianSimulation, ], s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, shots: Optional[int] = None, @@ -104,7 +110,7 @@ def run( annealing problem. Args: - task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence]): # noqa + task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation]): # noqa Specification of task (circuit or annealing problem or program) to run on device. s3_destination_folder (Optional[S3DestinationFolder]): The S3 location to save the task's results to. Default is `/tasks` if evoked @@ -170,7 +176,14 @@ def run( def run_batch( self, task_specifications: List[ - Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence] + Union[ + Circuit, + Problem, + OpenQasmProgram, + BlackbirdProgram, + PulseSequence, + AnalogHamiltonianSimulation, + ] ], s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, shots: Optional[int] = None, @@ -184,7 +197,7 @@ def run_batch( """Executes a batch of tasks in parallel Args: - task_specifications (List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence]]): # noqa + task_specifications (List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation]]): # noqa List of circuits or annealing problems to run on device. s3_destination_folder (Optional[S3DestinationFolder]): The S3 location to save the tasks' results to. Default is `/tasks` if evoked diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 169f4ccd..d32c9084 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -21,6 +21,7 @@ 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.circuits import Instruction @@ -53,8 +54,14 @@ 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 AnnealingTaskResult, GateModelTaskResult, PhotonicModelTaskResult +from braket.task_result import ( + AnalogHamiltonianSimulationTaskResult, + AnnealingTaskResult, + GateModelTaskResult, + PhotonicModelTaskResult, +) from braket.tasks import ( + AnalogHamiltonianSimulationQuantumTaskResult, AnnealingQuantumTaskResult, GateModelQuantumTaskResult, PhotonicModelQuantumTaskResult, @@ -82,7 +89,12 @@ def create( aws_session: AwsSession, device_arn: str, task_specification: Union[ - Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence + Circuit, + Problem, + OpenQasmProgram, + BlackbirdProgram, + PulseSequence, + AnalogHamiltonianSimulation, ], s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, @@ -101,8 +113,8 @@ def create( device_arn (str): The ARN of the quantum device. - task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]): The - specification of the task to run on device. + task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation]): # noqa + The specification of the task to run on device. s3_destination_folder (AwsSession.S3DestinationFolder): NamedTuple, with bucket for index 0 and key for index 1, that specifies the Amazon S3 bucket and folder @@ -589,6 +601,22 @@ def _( return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) +@_create_internal.register +def _( + analog_hamiltonian_simulation: AnalogHamiltonianSimulation, + aws_session: AwsSession, + create_task_kwargs: Dict[str, Any], + device_arn: str, + device_parameters: dict, + _, + *args, + **kwargs, +) -> AwsQuantumTask: + create_task_kwargs.update({"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) + + def _create_annealing_device_params( device_params: Dict[str, Any], device_arn: str ) -> Union[DwaveAdvantageDeviceParameters, Dwave2000QDeviceParameters]: @@ -664,3 +692,10 @@ def _(result: AnnealingTaskResult) -> AnnealingQuantumTaskResult: @_format_result.register def _(result: PhotonicModelTaskResult) -> PhotonicModelQuantumTaskResult: return PhotonicModelQuantumTaskResult.from_object(result) + + +@_format_result.register +def _( + result: AnalogHamiltonianSimulationTaskResult, +) -> 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 5f20a999..3f6965b9 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -17,6 +17,7 @@ from concurrent.futures.thread import ThreadPoolExecutor from typing import List, Set, Union +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 @@ -43,7 +44,9 @@ def __init__( self, aws_session: AwsSession, device_arn: str, - task_specifications: List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]], + task_specifications: List[ + Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation] + ], s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, max_parallel: int, @@ -58,7 +61,7 @@ def __init__( Args: aws_session (AwsSession): AwsSession to connect to AWS with. device_arn (str): The ARN of the quantum device. - task_specifications (List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]]): + task_specifications (List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation]]): # noqa The specification of the task to run on device. s3_destination_folder (AwsSession.S3DestinationFolder): NamedTuple, with bucket for index 0 and key for index 1, that specifies the Amazon S3 bucket and folder diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 07fc8398..3a751f0a 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -16,6 +16,7 @@ import pkg_resources +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 @@ -25,6 +26,9 @@ from braket.ir.openqasm import Program from braket.simulator import BraketSimulator 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 _simulator_devices = { @@ -55,7 +59,7 @@ def __init__(self, backend: Union[str, BraketSimulator] = "default"): def run( self, - task_specification: Union[Circuit, Problem, Program], + task_specification: Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], shots: int = 0, inputs: Optional[Dict[str, float]] = None, *args, @@ -86,7 +90,9 @@ def run( >>> device = LocalSimulator("default") >>> device.run(circuit, shots=1000) """ - result = _run_internal(task_specification, self._delegate, shots, inputs, *args, **kwargs) + result = _run_internal( + task_specification, self._delegate, shots, inputs=inputs, *args, **kwargs + ) return LocalQuantumTask(result) @property @@ -130,7 +136,7 @@ def _(backend_impl: BraketSimulator): @singledispatch def _run_internal( - task_specification: Union[Circuit, Problem, Program], + task_specification: Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], simulator: BraketSimulator, shots: Optional[int] = None, *args, @@ -192,3 +198,19 @@ def _( ) results = simulator.run(program, shots, *args, **kwargs) return GateModelQuantumTaskResult.from_object(results) + + +@_run_internal.register +def _( + program: AnalogHamiltonianSimulation, + simulator: BraketSimulator, + shots: Optional[int] = None, + *args, + **kwargs, +): + 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) diff --git a/src/braket/tasks/__init__.py b/src/braket/tasks/__init__.py index 648851e3..d81df6ce 100644 --- a/src/braket/tasks/__init__.py +++ b/src/braket/tasks/__init__.py @@ -12,6 +12,10 @@ # language governing permissions and limitations under the License. import braket.ipython_utils as ipython_utils +from braket.tasks.analog_hamiltonian_simulation_quantum_task_result import ( # noqa: F401 + AnalogHamiltonianSimulationQuantumTaskResult, + AnalogHamiltonianSimulationShotStatus, +) 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 diff --git a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py new file mode 100644 index 00000000..f1efc82e --- /dev/null +++ b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py @@ -0,0 +1,107 @@ +# 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 dataclasses import dataclass +from enum import Enum +from typing import List + +import numpy as np + +from braket.task_result import AnalogHamiltonianSimulationTaskResult, TaskMetadata + + +class AnalogHamiltonianSimulationShotStatus(str, Enum): + SUCCESS = "Success" + PARTIAL_SUCCESS = "Partial Success" + FAILURE = "Failure" + + +@dataclass +class ShotResult: + status: AnalogHamiltonianSimulationShotStatus + pre_sequence: np.ndarray = None + post_sequence: np.ndarray = None + + def __eq__(self, other) -> bool: + if isinstance(other, ShotResult): + return ( + self.status == other.status + and _equal_sequences(self.pre_sequence, other.pre_sequence) + and _equal_sequences(self.post_sequence, other.post_sequence) + ) + return NotImplemented + + +@dataclass +class AnalogHamiltonianSimulationQuantumTaskResult: + task_metadata: TaskMetadata + measurements: List[ShotResult] = None + + def __eq__(self, other) -> bool: + if isinstance(other, AnalogHamiltonianSimulationQuantumTaskResult): + return ( + self.task_metadata.id == other.task_metadata.id + and self.measurements == other.measurements + ) + return NotImplemented + + @staticmethod + def from_object( + result: AnalogHamiltonianSimulationTaskResult, + ) -> AnalogHamiltonianSimulationQuantumTaskResult: + return AnalogHamiltonianSimulationQuantumTaskResult._from_object_internal(result) + + @staticmethod + def from_string(result: str) -> AnalogHamiltonianSimulationQuantumTaskResult: + return AnalogHamiltonianSimulationQuantumTaskResult._from_object_internal( + AnalogHamiltonianSimulationTaskResult.parse_raw(result) + ) + + @classmethod + def _from_object_internal( + cls, result: AnalogHamiltonianSimulationTaskResult + ) -> AnalogHamiltonianSimulationQuantumTaskResult: + if result.measurements is not None: + measurements = AnalogHamiltonianSimulationQuantumTaskResult._get_measurements(result) + else: + measurements = None + return cls( + task_metadata=result.taskMetadata, + measurements=measurements, + ) + + @classmethod + def _get_measurements(cls, result: AnalogHamiltonianSimulationTaskResult) -> List[ShotResult]: + measurements = [] + for measurement in result.measurements: + status = AnalogHamiltonianSimulationShotStatus(measurement.shotMetadata.shotStatus) + if measurement.shotResult.preSequence: + pre_sequence = np.asarray(measurement.shotResult.preSequence, dtype=int) + else: + pre_sequence = None + if measurement.shotResult.postSequence: + post_sequence = np.asarray(measurement.shotResult.postSequence, dtype=int) + else: + post_sequence = None + measurements.append(ShotResult(status, pre_sequence, post_sequence)) + return measurements + + +def _equal_sequences(sequence0, sequence1) -> bool: + if sequence0 is None and sequence1 is None: + return True + if sequence0 is None or sequence1 is None: + return False + return np.allclose(sequence0, sequence1) diff --git a/test/integ_tests/test_local_braket_simulator.py b/test/integ_tests/test_local_braket_simulator.py index 4b3b93cd..ff1b3755 100644 --- a/test/integ_tests/test_local_braket_simulator.py +++ b/test/integ_tests/test_local_braket_simulator.py @@ -152,6 +152,7 @@ def test_result_types_observable_not_in_instructions(shots, caplog): ("default", "StateVectorSimulator"), ("braket_sv", "StateVectorSimulator"), ("braket_dm", "DensityMatrixSimulator"), + ("braket_ahs", "RydbergAtomSimulator"), ], ) def test_local_simulator_device_names(backend, device_name, caplog): diff --git a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py new file mode 100644 index 00000000..bdc3e92f --- /dev/null +++ b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py @@ -0,0 +1,241 @@ +# 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 decimal import Decimal +from unittest.mock import Mock + +import numpy as np +import pytest + +from braket.ahs.analog_hamiltonian_simulation import ( + AnalogHamiltonianSimulation, + AtomArrangement, + DiscretizationError, + DrivingField, + ShiftingField, + SiteType, +) +from braket.ahs.atom_arrangement import AtomArrangementItem +from braket.ahs.field import Field +from braket.ahs.pattern import Pattern +from braket.ir.ahs.program_v1 import Program +from braket.timings.time_series import TimeSeries + + +@pytest.fixture +def register(): + return ( + AtomArrangement() + .add((0.0, 0.0)) + .add((0.0, 3.0e-6)) + .add((0.0, 6.0e-6)) + .add((3.0e-6, 0.0)) + .add((3.0e-6, 3.0e-6)) + .add((3.0e-6, 3.0e-6), SiteType.VACANT) + .add((3.0e-6, 6.0e-6), SiteType.VACANT) + ) + + +@pytest.fixture +def driving_field(): + return DrivingField( + TimeSeries().put(0.0, 0.0).put(3.0e-7, 2.51327e7).put(2.7e-6, 2.51327e7).put(3.0e-6, 0.0), + TimeSeries().put(0.0, 0).put(3.0e-6, 0), + TimeSeries() + .put(0.0, -1.25664e8) + .put(3.0e-7, -1.25664e8) + .put(2.7e-6, 1.25664e8) + .put(3.0e-6, 1.25664e8), + ) + + +@pytest.fixture +def shifting_field(): + return ShiftingField( + 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]), + ) + ) + + +def test_create(): + mock0 = Mock() + mock1 = Mock() + ahs = AnalogHamiltonianSimulation(register=mock0, hamiltonian=mock1) + assert mock0 == ahs.register + assert mock1 == ahs.hamiltonian + + +def test_to_ir(register, driving_field, shifting_field): + hamiltonian = driving_field + shifting_field + ahs = AnalogHamiltonianSimulation(register=register, hamiltonian=hamiltonian) + problem = ahs.to_ir() + assert Program.parse_raw(problem.json()) == problem + assert problem == Program.parse_raw_schema(problem.json()) + + +def test_to_ir_empty(): + hamiltonian = Mock() + hamiltonian.terms = [] + ahs = AnalogHamiltonianSimulation(register=AtomArrangement(), hamiltonian=hamiltonian) + problem = ahs.to_ir() + assert Program.parse_raw(problem.json()) == problem + assert problem == Program.parse_raw_schema(problem.json()) + + +@pytest.mark.xfail(raises=TypeError) +def test_to_ir_invalid_hamiltonian(register): + hamiltonian = Mock() + hamiltonian.terms = [Mock()] + ahs = AnalogHamiltonianSimulation(register=register, hamiltonian=hamiltonian) + ahs.to_ir() + + +@pytest.mark.xfail(raises=DiscretizationError) +def test_invalid_action(): + action = Mock() + action.actionType = "not-a-valid-AHS-action" + device = Mock() + device.properties.action = {"braket.ir.ahs.program": action} + + AnalogHamiltonianSimulation(register=Mock(), hamiltonian=Mock()).discretize(device) + + +@pytest.mark.xfail(raises=DiscretizationError) +def test_invalid_action_name(): + action = Mock() + action.actionType = "braket.ir.ahs.program" + device = Mock() + device.properties.action = {"not-a-valid-AHS-action": action} + + AnalogHamiltonianSimulation(register=Mock(), hamiltonian=Mock()).discretize(device) + + +def test_discretize(register, driving_field, shifting_field): + hamiltonian = driving_field + shifting_field + ahs = AnalogHamiltonianSimulation(register=register, hamiltonian=hamiltonian) + + action = Mock() + action.actionType = "braket.ir.ahs.program" + + device = Mock() + device.properties.action = {"braket.ir.ahs.program": action} + + device.properties.paradigm.lattice.geometry.positionResolution = Decimal("1E-7") + + device.properties.paradigm.rydberg.rydbergGlobal.timeResolution = Decimal("1E-9") + device.properties.paradigm.rydberg.rydbergGlobal.rabiFrequencyResolution = Decimal("400") + device.properties.paradigm.rydberg.rydbergGlobal.detuningResolution = Decimal("0.2") + 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() + discretized_json = json.loads(discretized_ir.json()) + assert discretized_json["setup"]["ahs_register"] == { + "filling": [1, 1, 1, 1, 1, 0, 0], + "sites": [ + ["0E-7", "0E-7"], + ["0E-7", "0.0000030"], + ["0E-7", "0.0000060"], + ["0.0000030", "0E-7"], + ["0.0000030", "0.0000030"], + ["0.0000030", "0.0000030"], + ["0.0000030", "0.0000060"], + ], + } + assert discretized_json["hamiltonian"]["drivingFields"][0]["amplitude"] == { + "pattern": "uniform", + "time_series": { + "times": ["0E-9", "3.00E-7", "0.000002700", "0.000003000"], + "values": ["0", "25132800", "25132800", "0"], + }, + } + assert discretized_json["hamiltonian"]["drivingFields"][0]["phase"] == { + "pattern": "uniform", + "time_series": {"times": ["0E-9", "0.000003000"], "values": ["0E-7", "0E-7"]}, + } + assert discretized_json["hamiltonian"]["drivingFields"][0]["detuning"] == { + "pattern": "uniform", + "time_series": { + "times": ["0E-9", "3.00E-7", "0.000002700", "0.000003000"], + "values": ["-125664000.0", "-125664000.0", "125664000.0", "125664000.0"], + }, + } + assert discretized_json["hamiltonian"]["shiftingFields"][0]["magnitude"] == { + "pattern": ["0.50", "1.00", "0.50", "0.50", "0.50", "0.50"], + "time_series": { + "times": ["0E-9", "0.000003000"], + "values": ["-125664000.0", "125664000.0"], + }, + } + + +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], + ] + ) + register = AtomArrangement() + for site in sites: + register.add(site) + + ahs = AnalogHamiltonianSimulation(register=register, hamiltonian=hamiltonian) + sites_in_ir = ahs.to_ir().setup.ahs_register.sites + expected_sites_in_ir = [ + [Decimal("0.0"), Decimal("0.0")], + [Decimal("0.0"), Decimal("1e-6")], + [Decimal("1e-6"), Decimal("2e-6")], + ] + + assert sites_in_ir == expected_sites_in_ir + + +@pytest.mark.xfail(raises=ValueError) +def test_site_validation_wrong_length(): + register = AtomArrangement() + register.add(np.array([0.0, 1e-6, -1e-6])) + + +@pytest.mark.xfail(raises=TypeError) +def test_site_validation_non_number(): + register = AtomArrangement() + register.add( + [ + "not-a-number", + [ + "also-not-a-number", + ], + ] + ) + + +@pytest.mark.xfail(raises=TypeError) +def test_site_validation_not_a_tuple(): + AtomArrangementItem(None, SiteType.FILLED) + + +@pytest.mark.xfail(raises=ValueError) +def test_site_validation_invalid_site_type(): + register = AtomArrangement() + register.add([0.0, 0.0], "not-a-valid-site-type") diff --git a/test/unit_tests/braket/ahs/test_atom_arrangement.py b/test/unit_tests/braket/ahs/test_atom_arrangement.py new file mode 100644 index 00000000..42545854 --- /dev/null +++ b/test/unit_tests/braket/ahs/test_atom_arrangement.py @@ -0,0 +1,115 @@ +# 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 +from unittest.mock import Mock + +import pytest + +from braket.ahs.analog_hamiltonian_simulation import DiscretizationError +from braket.ahs.atom_arrangement import AtomArrangement, SiteType + + +@pytest.fixture +def default_atom_arrangement(): + atom_arrangement = ( + AtomArrangement() + .add((0, 3.1e-6), SiteType.FILLED) + .add((0, 5.99e-6)) + .add((3.001e-6, 0)) + .add((3e-6, 3e-6)) + .add((-3.01e-6, 6.5e-6)) + ) + return atom_arrangement + + +def test_add_chaining(): + atom_arrangement = ( + AtomArrangement() + .add(coordinate=(0, 0), site_type=SiteType.FILLED) + .add((0, 3), SiteType.FILLED) + .add((0, 6)) + .add((3, 0)) + .add((3, 3)) + .add((3, 6)) + .add((3, 0)) + ) + assert len(atom_arrangement) == 7 + + +def test_iteration(): + values = [(0, 0), (0.1, 0.2), (Decimal(0.3), Decimal(0.4))] + atom_arrangement = AtomArrangement() + for value in values: + atom_arrangement.add(value) + returned_values = [] + for site in atom_arrangement: + returned_values.append(site.coordinate) + assert values == returned_values + + +def test_coordinate_list(): + values = [(0, 0), (0.1, 0.2), (Decimal(0.3), Decimal(0.4))] + atom_arrangement = AtomArrangement() + for value in values: + atom_arrangement.add(value) + for coord_index in range(2): + coords = atom_arrangement.coordinate_list(coord_index) + assert coords == [value[coord_index] for value in values] + + +@pytest.mark.parametrize( + "position_res, expected_x, expected_y", + [ + # default x: [0, 0, 3.001e-6, 3e-6, -3.01e-6] + # default y: [3.1e-6, 5.99e-6, 0, 3e-6, 6.5e-6] + ( + Decimal("1e-6"), + [Decimal("0"), Decimal("0"), Decimal("3e-6"), Decimal("3e-6"), Decimal("-3e-6")], + [Decimal("3e-6"), Decimal("6e-6"), Decimal("0"), Decimal("3e-6"), Decimal("6e-6")], + ), + ( + Decimal("1e-7"), + [Decimal("0"), Decimal("0"), Decimal("3e-6"), Decimal("3e-6"), Decimal("-3e-6")], + [Decimal("3.1e-6"), Decimal("6e-6"), Decimal("0"), Decimal("3e-6"), Decimal("6.5e-6")], + ), + ( + Decimal("2e-7"), + [Decimal("0"), Decimal("0"), Decimal("3e-6"), Decimal("3e-6"), Decimal("-3e-6")], + [Decimal("3e-6"), Decimal("6e-6"), Decimal("0"), Decimal("3e-6"), Decimal("6.4e-6")], + ), + ( + Decimal("1e-8"), + [Decimal("0"), Decimal("0"), Decimal("3e-6"), Decimal("3e-6"), Decimal("-3.01e-6")], + [ + Decimal("3.1e-6"), + Decimal("5.99e-6"), + Decimal("0"), + Decimal("3e-6"), + Decimal("6.5e-6"), + ], + ), + ], +) +def test_discretize(default_atom_arrangement, position_res, expected_x, expected_y): + properties = Mock() + properties.lattice.geometry.positionResolution = position_res + actual = default_atom_arrangement.discretize(properties) + assert expected_x == actual.coordinate_list(0) + assert expected_y == actual.coordinate_list(1) + + +@pytest.mark.xfail(raises=DiscretizationError) +def test_invalid_discretization_properties(default_atom_arrangement): + properties = "not-a-valid-discretization-property" + default_atom_arrangement.discretize(properties) diff --git a/test/unit_tests/braket/ahs/test_driving_field.py b/test/unit_tests/braket/ahs/test_driving_field.py new file mode 100644 index 00000000..8472a7e1 --- /dev/null +++ b/test/unit_tests/braket/ahs/test_driving_field.py @@ -0,0 +1,112 @@ +# 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 Mock + +import pytest + +from braket.ahs.driving_field import DrivingField +from braket.ahs.field import Field +from braket.ahs.hamiltonian import Hamiltonian +from braket.timings.time_series import TimeSeries + + +@pytest.fixture +def default_driving_field(): + return DrivingField(Mock(spec=Field), Mock(spec=Field), Mock(spec=Field)) + + +def test_create(): + mock0 = Mock(spec=Field) + mock1 = Mock(spec=Field) + mock2 = Mock(spec=Field) + field = DrivingField(amplitude=mock0, phase=mock1, detuning=mock2) + assert mock0 == field.amplitude + assert mock1 == field.phase + assert mock2 == field.detuning + + +def test_create_non_field(): + mock0 = Mock(spec=TimeSeries) + mock1 = Mock(spec=TimeSeries) + mock2 = Mock(spec=TimeSeries) + field = DrivingField(amplitude=mock0, phase=mock1, detuning=mock2) + assert mock0 == field.amplitude.time_series + assert mock1 == field.phase.time_series + assert mock2 == field.detuning.time_series + + +def test_add_hamiltonian(default_driving_field): + expected = [default_driving_field, Mock(), Mock(), Mock()] + result = expected[0] + Hamiltonian([expected[1], expected[2], expected[3]]) + assert result.terms == expected + + +def test_add_to_hamiltonian(default_driving_field): + expected = [Mock(), Mock(), Mock(), default_driving_field] + result = Hamiltonian([expected[0], expected[1], expected[2]]) + expected[3] + assert result.terms == expected + + +def test_add_to_other(): + field0 = DrivingField(Mock(spec=Field), Mock(spec=Field), Mock(spec=Field)) + field1 = DrivingField(Mock(spec=Field), Mock(spec=Field), Mock(spec=Field)) + result = field0 + field1 + assert type(result) is Hamiltonian + assert result.terms == [field0, field1] + + +def test_add_to_self(default_driving_field): + result = default_driving_field + default_driving_field + assert type(result) is Hamiltonian + assert result.terms == [default_driving_field, default_driving_field] + + +def test_iadd_to_other(default_driving_field): + expected = [Mock(), Mock(), Mock(), default_driving_field] + other = Hamiltonian([expected[0], expected[1], expected[2]]) + other += expected[3] + assert other.terms == expected + + +def test_discretize(): + amplitude_mock = Mock(spec=Field) + amplitude_mock.discretize.return_value = Mock(spec=Field) + phase_mock = Mock(spec=Field) + phase_mock.discretize.return_value = Mock(spec=Field) + detuning_mock = Mock(spec=Field) + detuning_mock.discretize.return_value = Mock(spec=Field) + mock_properties = Mock() + field = DrivingField(amplitude=amplitude_mock, phase=phase_mock, detuning=detuning_mock) + discretized_field = field.discretize(mock_properties) + amplitude_mock.discretize.assert_called_with( + time_resolution=mock_properties.rydberg.rydbergGlobal.timeResolution, + value_resolution=mock_properties.rydberg.rydbergGlobal.rabiFrequencyResolution, + ) + phase_mock.discretize.assert_called_with( + time_resolution=mock_properties.rydberg.rydbergGlobal.timeResolution, + value_resolution=mock_properties.rydberg.rydbergGlobal.phaseResolution, + ) + detuning_mock.discretize.assert_called_with( + time_resolution=mock_properties.rydberg.rydbergGlobal.timeResolution, + value_resolution=mock_properties.rydberg.rydbergGlobal.detuningResolution, + ) + assert field is not discretized_field + assert discretized_field.amplitude == amplitude_mock.discretize.return_value + assert discretized_field.phase == phase_mock.discretize.return_value + assert discretized_field.detuning == detuning_mock.discretize.return_value + + +@pytest.mark.xfail(raises=ValueError) +def test_iadd_to_itself(default_driving_field): + default_driving_field += Hamiltonian(Mock()) diff --git a/test/unit_tests/braket/ahs/test_field.py b/test/unit_tests/braket/ahs/test_field.py new file mode 100644 index 00000000..4212ba33 --- /dev/null +++ b/test/unit_tests/braket/ahs/test_field.py @@ -0,0 +1,106 @@ +# 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 +from unittest.mock import Mock + +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 + + +@pytest.fixture +def default_time_series(): + default_values = [(2700, 25.1327), (300, 25.1327), (600, 15.1327), (Decimal(0.3), Decimal(0.4))] + time_series = TimeSeries() + for value in default_values: + time_series.put(value[0], value[1]) + return time_series + + +@pytest.fixture +def default_pattern(): + return Pattern(series=[0, 0.1, 1, 0.5, 0.2, 0.001, 1e-10]) + + +@pytest.fixture +def default_field(default_time_series, default_pattern): + return Field(time_series=default_time_series, pattern=default_pattern) + + +@pytest.fixture +def default_uniform_field(default_time_series): + return Field(time_series=default_time_series) + + +def test_create(): + mock0 = Mock() + mock1 = Mock() + field = Field(time_series=mock0, pattern=mock1) + assert mock0 == field.time_series + assert mock1 == field.pattern + + +@pytest.mark.parametrize( + "time_res, value_res, pattern_res", + [ + (Decimal("0.1"), Decimal("10"), Decimal("0.5")), + (Decimal("10"), Decimal("20"), Decimal("0.1")), + (Decimal("100"), Decimal("0.1"), Decimal("1")), + ], +) +def test_discretize( + default_time_series, default_pattern, default_field, time_res, value_res, pattern_res +): + expected = Field( + time_series=default_time_series.discretize(time_res, value_res), + pattern=default_pattern.discretize(pattern_res), + ) + actual = default_field.discretize(time_res, value_res, pattern_res) + assert 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("0.1"), Decimal("10"), Decimal("0.5")), + (Decimal("10"), Decimal("20"), None), + (Decimal("100"), Decimal("0.1"), Decimal("1")), + ], +) +def test_uniform_field( + default_time_series, default_uniform_field, time_res, value_res, pattern_res +): + expected = Field(time_series=default_time_series.discretize(time_res, value_res)) + actual = default_uniform_field.discretize(time_res, value_res, pattern_res) + assert ( + (expected.pattern is None) and (actual.pattern is None) + ) 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_hamiltonian.py b/test/unit_tests/braket/ahs/test_hamiltonian.py new file mode 100644 index 00000000..e7a3b308 --- /dev/null +++ b/test/unit_tests/braket/ahs/test_hamiltonian.py @@ -0,0 +1,45 @@ +# 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 Mock + +from braket.ahs.hamiltonian import Hamiltonian + + +def test_create(): + hamiltonian = Hamiltonian() + assert hamiltonian.terms == [] + + +def test_add(): + mocks = [Mock(), Mock(), Mock(), Mock()] + result = Hamiltonian([mocks[0], mocks[1], mocks[2]]) + Hamiltonian([mocks[3]]) + assert result.terms == mocks + + +def test_iadd(): + mocks = [Mock(), Mock(), Mock(), Mock()] + hamiltonian = Hamiltonian([mocks[0]]) + hamiltonian += Hamiltonian([mocks[1], mocks[2], mocks[3]]) + assert hamiltonian.terms == mocks + + +def test_discretize(): + mocks = [Mock(), Mock(), Mock()] + mock_properties = Mock() + hamiltonian = Hamiltonian(mocks) + discretized_hamiltonian = hamiltonian.discretize(mock_properties) + for index in range(len(mocks)): + mocks[index].discretize.assert_called_with(mock_properties) + assert discretized_hamiltonian.terms[index] == mocks[index].discretize.return_value + assert hamiltonian is not discretized_hamiltonian diff --git a/test/unit_tests/braket/ahs/test_pattern.py b/test/unit_tests/braket/ahs/test_pattern.py new file mode 100644 index 00000000..d84f3a92 --- /dev/null +++ b/test/unit_tests/braket/ahs/test_pattern.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 decimal import Decimal + +import pytest + +from braket.ahs.pattern import Pattern + + +@pytest.fixture +def default_values(): + return [0, 0.1, 1, 0.5, 0.2, 0.001, 1e-10] + + +@pytest.fixture +def default_pattern(default_values): + return Pattern(series=default_values) + + +def test_create(): + expected_series = [1, 2, Decimal(3.1)] + pattern = Pattern(expected_series) + assert expected_series == pattern.series + + +@pytest.mark.parametrize( + "res, expected_series", + [ + # default pattern: [0, 0.1, 1, 0.5, 0.2, 0.001, 1e-10] + ( + Decimal("0.001"), + [ + Decimal("0"), + Decimal("0.1"), + Decimal("1"), + Decimal("0.5"), + Decimal("0.2"), + Decimal("0.001"), + Decimal("0"), + ], + ), + ( + Decimal("0.1"), + [ + Decimal("0"), + Decimal("0.1"), + Decimal("1"), + Decimal("0.5"), + Decimal("0.2"), + Decimal("0"), + Decimal("0"), + ], + ), + ( + Decimal("0.5"), + [ + Decimal("0"), + Decimal("0"), + Decimal("1"), + Decimal("0.5"), + Decimal("0"), + Decimal("0"), + Decimal("0"), + ], + ), + ( + Decimal("0.9"), + [ + Decimal("0"), + Decimal("0"), + Decimal("0.9"), + Decimal("0.9"), + Decimal("0"), + Decimal("0"), + Decimal("0"), + ], + ), + ], +) +def test_discretize(default_pattern, res, expected_series): + print(default_pattern.series) + print(res, default_pattern.discretize(res).series) + assert expected_series == default_pattern.discretize(res).series diff --git a/test/unit_tests/braket/ahs/test_shifting_field.py b/test/unit_tests/braket/ahs/test_shifting_field.py new file mode 100644 index 00000000..3af466ba --- /dev/null +++ b/test/unit_tests/braket/ahs/test_shifting_field.py @@ -0,0 +1,82 @@ +# 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 Mock + +import pytest + +from braket.ahs.hamiltonian import Hamiltonian +from braket.ahs.shifting_field import ShiftingField + + +@pytest.fixture +def default_shifting_field(): + return ShiftingField(Mock()) + + +def test_create(): + mock0 = Mock() + field = ShiftingField(magnitude=mock0) + assert mock0 == field.magnitude + + +def test_add_hamiltonian(default_shifting_field): + expected = [default_shifting_field, 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] + 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()) + 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 + assert type(result) is Hamiltonian + assert result.terms == [default_shifting_field, default_shifting_field] + + +def test_iadd_to_other(default_shifting_field): + expected = [Mock(), Mock(), Mock(), default_shifting_field] + other = Hamiltonian([expected[0], expected[1], expected[2]]) + other += expected[3] + assert other.terms == expected + + +def test_discretize(): + magnitude_mock = Mock() + mock_properties = Mock() + field = ShiftingField(magnitude=magnitude_mock) + 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 + + +@pytest.mark.xfail(raises=ValueError) +def test_iadd_to_itself(default_shifting_field): + default_shifting_field += Hamiltonian(Mock()) diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 16532bac..d4788cb8 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -152,6 +152,40 @@ class MockS3: } ) + 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], + }, + }, + { + "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( aws_quantum_task_mock, 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 9fe2008c..b30d093c 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -21,6 +21,7 @@ from common_test_utils import MockS3 from jsonschema import validate +from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing.problem import Problem, ProblemType from braket.aws import AwsQuantumTask from braket.aws.aws_quantum_task import _create_annealing_device_params @@ -46,6 +47,7 @@ from braket.ir.openqasm import Program as OpenQasmProgram from braket.pulse import Frame, Port, PulseSequence from braket.tasks import ( + AnalogHamiltonianSimulationQuantumTaskResult, AnnealingQuantumTaskResult, GateModelQuantumTaskResult, PhotonicModelQuantumTaskResult, @@ -138,6 +140,13 @@ def pulse_gate(pulse_sequence): return PulseGate(pulse_sequence, 1, "my_PG") +@pytest.fixture +def ahs_problem(): + mock = Mock(spec=AnalogHamiltonianSimulation) + mock.to_ir.return_value.json.return_value = "Test AHS Problem" + return mock + + def test_equality(arn, aws_session): quantum_task_1 = AwsQuantumTask(arn, aws_session) quantum_task_2 = AwsQuantumTask(arn, aws_session) @@ -287,6 +296,22 @@ def test_result_photonic_model(photonic_model_task): ) +def test_result_analog_hamiltonian_simulation(quantum_task): + _mock_metadata(quantum_task._aws_session, "COMPLETED") + _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_ANALOG_HAMILTONIAN_SIMULTION) + + expected = AnalogHamiltonianSimulationQuantumTaskResult.from_string( + MockS3.MOCK_S3_RESULT_ANALOG_HAMILTONIAN_SIMULTION + ) + assert quantum_task.result() == expected + + s3_bucket = quantum_task.metadata()["outputS3Bucket"] + s3_object_key = quantum_task.metadata()["outputS3Directory"] + quantum_task._aws_session.retrieve_s3_object_body.assert_called_with( + s3_bucket, f"{s3_object_key}/results.json" + ) + + @pytest.mark.xfail(raises=TypeError) def test_result_invalid_type(circuit_task): _mock_metadata(circuit_task._aws_session, "COMPLETED") @@ -449,6 +474,20 @@ def test_create_blackbird_program(aws_session, arn, blackbird_program): ) +def test_create_ahs_problem(aws_session, arn, ahs_problem): + aws_session.create_quantum_task.return_value = arn + shots = 21 + AwsQuantumTask.create(aws_session, SIMULATOR_ARN, ahs_problem, S3_TARGET, shots) + + _assert_create_quantum_task_called_with( + aws_session, + SIMULATOR_ARN, + ahs_problem.to_ir().json(), + S3_TARGET, + shots, + ) + + def test_create_pulse_sequence(aws_session, arn, pulse_sequence): expected_openqasm = "\n".join( [ diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 2d6d8335..dd6692aa 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -15,8 +15,12 @@ from unittest.mock import Mock import pytest +from pydantic import create_model # This is temporary for defining properties below import braket.ir as ir +from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation +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 @@ -24,7 +28,13 @@ from braket.ir.openqasm import Program from braket.simulator import BraketSimulator from braket.task_result import AnnealingTaskResult, GateModelTaskResult +from braket.task_result.analog_hamiltonian_simulation_task_result_v1 import ( + AnalogHamiltonianSimulationTaskResult, +) from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult +from braket.tasks.analog_hamiltonian_simulation_quantum_task_result import ( + AnalogHamiltonianSimulationQuantumTaskResult, +) GATE_MODEL_RESULT = GateModelTaskResult( **{ @@ -84,6 +94,16 @@ } ) +AHS_RESULT = AnalogHamiltonianSimulationTaskResult( + **{ + "taskMetadata": { + "id": "rydberg", + "shots": 100, + "deviceId": "rydbergLocalSimulator", + }, + } +) + class DummyCircuitSimulator(BraketSimulator): def run( @@ -232,6 +252,35 @@ def properties(self) -> DeviceCapabilities: ) +class DummyRydbergSimulator(BraketSimulator): + def run( + self, program: AnalogHamiltonianSimulation, *args, **kwargs + ) -> AnalogHamiltonianSimulationTaskResult: + return AHS_RESULT + + @property + def properties(self) -> DeviceCapabilities: + properties = { + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "00:00", + "windowEndHour": "23:59:59", + } + ], + "shotsRange": [0, 10], + }, + "action": {"braket.ir.ahs.program": {}}, + } + + RydbergSimulatorDeviceCapabilities = create_model( + "RydbergSimulatorDeviceCapabilities", **properties + ) + + return RydbergSimulatorDeviceCapabilities.parse_obj(properties) + + mock_circuit_entry = Mock() mock_program_entry = Mock() mock_jaqcd_entry = Mock() @@ -244,6 +293,10 @@ def properties(self) -> DeviceCapabilities: "dummy_jaqcd": mock_jaqcd_entry, } +mock_ahs_program = AnalogHamiltonianSimulation( + register=AtomArrangement(), hamiltonian=Hamiltonian() +) + def test_load_from_entry_point(): sim = LocalSimulator("dummy_oq3") @@ -346,6 +399,12 @@ def test_run_annealing(): assert task.result() == AnnealingQuantumTaskResult.from_object(ANNEALING_RESULT) +def test_run_ahs(): + sim = LocalSimulator(DummyRydbergSimulator()) + task = sim.run(mock_ahs_program) + assert task.result() == AnalogHamiltonianSimulationQuantumTaskResult.from_object(AHS_RESULT) + + def test_registered_backends(): assert LocalSimulator.registered_backends() == {"dummy", "dummy_oq3", "dummy_jaqcd"} 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 new file mode 100644 index 00000000..c977a0b3 --- /dev/null +++ b/test/unit_tests/braket/tasks/test_analog_hamiltonian_simulation_task_result.py @@ -0,0 +1,192 @@ +# 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.task_result import ( + AnalogHamiltonianSimulationShotMeasurement, + AnalogHamiltonianSimulationShotMetadata, + AnalogHamiltonianSimulationShotResult, + AnalogHamiltonianSimulationTaskResult, + TaskMetadata, +) +from braket.tasks import ( + AnalogHamiltonianSimulationQuantumTaskResult, + AnalogHamiltonianSimulationShotStatus, +) +from braket.tasks.analog_hamiltonian_simulation_quantum_task_result import ShotResult + + +@pytest.fixture +def task_metadata(): + return TaskMetadata(**{"id": "task_arn", "deviceId": "arn1", "shots": 100}) + + +@pytest.fixture +def success_measurement(): + return AnalogHamiltonianSimulationShotMeasurement( + shotMetadata=AnalogHamiltonianSimulationShotMetadata(shotStatus="Success"), + shotResult=AnalogHamiltonianSimulationShotResult( + preSequence=[1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], + postSequence=[0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0], + ), + ) + + +@pytest.fixture +def partial_success_measurement(): + return AnalogHamiltonianSimulationShotMeasurement( + shotMetadata=AnalogHamiltonianSimulationShotMetadata(shotStatus="Partial Success"), + shotResult=AnalogHamiltonianSimulationShotResult( + preSequence=[1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], postSequence=None + ), + ) + + +@pytest.fixture +def error_measurement(): + return AnalogHamiltonianSimulationShotMeasurement( + shotMetadata=AnalogHamiltonianSimulationShotMetadata(shotStatus="Failure"), + shotResult=AnalogHamiltonianSimulationShotResult(preSequence=None, postSequence=None), + ) + + +@pytest.fixture +def measurements(success_measurement, partial_success_measurement, error_measurement): + return [success_measurement, partial_success_measurement, error_measurement] + + +@pytest.fixture +def result_str_1(task_metadata, measurements): + result = AnalogHamiltonianSimulationTaskResult( + taskMetadata=task_metadata, + measurements=measurements, + ) + return result.json() + + +@pytest.fixture +def result_str_2(task_metadata, measurements): + result = AnalogHamiltonianSimulationTaskResult( + taskMetadata=task_metadata, + measurements=None, + ) + return result.json() + + +def validate_result_from_str_1(result): + assert len(result.measurements) == 3 + assert result.measurements[0].status == AnalogHamiltonianSimulationShotStatus.SUCCESS + np.testing.assert_equal(result.measurements[0].pre_sequence, [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1]) + np.testing.assert_equal(result.measurements[0].post_sequence, [0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0]) + assert result.measurements[1].status == AnalogHamiltonianSimulationShotStatus.PARTIAL_SUCCESS + np.testing.assert_equal(result.measurements[1].pre_sequence, [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1]) + assert result.measurements[1].post_sequence is None + assert result.measurements[2].status == AnalogHamiltonianSimulationShotStatus.FAILURE + assert result.measurements[2].pre_sequence is None + assert result.measurements[2].post_sequence is None + + +def test_from_object(result_str_1, task_metadata): + result = AnalogHamiltonianSimulationQuantumTaskResult.from_object( + AnalogHamiltonianSimulationTaskResult.parse_raw(result_str_1) + ) + assert result.task_metadata == task_metadata + validate_result_from_str_1(result) + + +def test_from_string(result_str_1, task_metadata): + result = AnalogHamiltonianSimulationQuantumTaskResult.from_string(result_str_1) + assert result.task_metadata == task_metadata + validate_result_from_str_1(result) + + +def test_from_object_equal_to_from_string(result_str_1): + assert AnalogHamiltonianSimulationQuantumTaskResult.from_object( + AnalogHamiltonianSimulationTaskResult.parse_raw(result_str_1) + ) == AnalogHamiltonianSimulationQuantumTaskResult.from_string(result_str_1) + + +def test_equality(task_metadata, result_str_1, result_str_2): + result_1 = AnalogHamiltonianSimulationQuantumTaskResult.from_string(result_str_1) + result_2 = AnalogHamiltonianSimulationQuantumTaskResult.from_string(result_str_1) + other_result = AnalogHamiltonianSimulationQuantumTaskResult.from_string(result_str_2) + non_result = "not a quantum task result" + + assert result_1 == result_2 + assert result_1 is not result_2 + assert result_1 != other_result + assert result_1 != AnalogHamiltonianSimulationQuantumTaskResult( + task_metadata=task_metadata, + measurements=[result_1.measurements[1], result_1.measurements[0]], + ) + assert result_1 != non_result + + +@pytest.mark.parametrize( + "shot0, shot1", + [ + ( + ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [], []), + ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [], []), + ), + ( + ShotResult(AnalogHamiltonianSimulationShotStatus.FAILURE, [1], [2]), + ShotResult(AnalogHamiltonianSimulationShotStatus.FAILURE, [1], [2]), + ), + ( + ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, None, None), + ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, None, None), + ), + ( + ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, None, [1, 2]), + ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, None, [1, 2]), + ), + ( + ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [1, 2], None), + ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [1, 2], None), + ), + ], +) +def test_shot_result_equals(shot0, shot1): + assert shot0 == shot1 + + +@pytest.mark.parametrize( + "shot0, shot1", + [ + ( + ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [], []), + ShotResult(AnalogHamiltonianSimulationShotStatus.FAILURE, [], []), + ), + ( + ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [1], [2]), + ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [2], [1]), + ), + ( + ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [1], [2]), + ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, None, [2]), + ), + ( + ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [1], None), + ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [1], [2]), + ), + ( + ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [1], None), + "not a shot", + ), + ], +) +def test_shot_result_not_equals(shot0, shot1): + assert shot0 != shot1 From f67863d79c741825e7640fe2eb27b0cf5423dca1 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 1 Nov 2022 02:49:16 +0000 Subject: [PATCH 0561/1165] prepare release v1.33.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 1e14a522..e801d253 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.33.0 (2022-11-01) + +### Features + + * Support analog Hamiltonian simulations + ## v1.32.1.post0 (2022-10-25) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 85c71845..c4ebc49e 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.32.2.dev0" +__version__ = "1.33.0" From 569d84c08fd5fee41de4d77eb223b06c1d287044 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 1 Nov 2022 02:49:16 +0000 Subject: [PATCH 0562/1165] update development version to v1.33.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 c4ebc49e..8c20f55c 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.33.0" +__version__ = "1.33.1.dev0" From 5ede3e2dea60884332200e8c89093fa3fe60934e Mon Sep 17 00:00:00 2001 From: Michael Abel <115323439+michaab@users.noreply.github.com> Date: Tue, 1 Nov 2022 13:50:03 -0600 Subject: [PATCH 0563/1165] documentation: update example notebook links (#471) --- doc/examples-adv-circuits-algorithms.rst | 6 +++--- doc/examples-hybrid-jobs.rst | 4 ++-- doc/examples-ml-pennylane.rst | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/examples-adv-circuits-algorithms.rst b/doc/examples-adv-circuits-algorithms.rst index 6f9f7699..38c4472d 100644 --- a/doc/examples-adv-circuits-algorithms.rst +++ b/doc/examples-adv-circuits-algorithms.rst @@ -18,7 +18,7 @@ gates that are not part of the basic gate set provided by the SDK. A custom gate as a core quantum gate by registering it as a subroutine. ******************************* -`Quantum amplitude amplification `_ +`Quantum amplitude amplification `_ ******************************* This tutorial provides a detailed discussion and implementation of the Quantum Amplitude Amplification (QAA) @@ -30,7 +30,7 @@ quadratic speedup over several classical algorithms. ************************* -`Quantum Fourier transform `_ +`Quantum Fourier transform `_ ************************* This tutorial provides a detailed implementation of the Quantum Fourier Transform (QFT) and @@ -39,7 +39,7 @@ most famously Shor's algorithm for factoring and the quantum phase estimation (Q for estimating the eigenvalues of a unitary operator. ************************ -`Quantum phase estimation `_ +`Quantum phase estimation `_ ************************ This tutorial provides a detailed implementation of the Quantum Phase Estimation (QPE) diff --git a/doc/examples-hybrid-jobs.rst b/doc/examples-hybrid-jobs.rst index c1b6c6a9..9c905343 100644 --- a/doc/examples-hybrid-jobs.rst +++ b/doc/examples-hybrid-jobs.rst @@ -8,13 +8,13 @@ Learn more about hybrid jobs on Amazon Braket. :maxdepth: 2 ************************** -`Getting Started `_ +`Creating your first Hybrid Job `_ ************************** This tutorial shows how to run your first Amazon Braket Hybrid Job. ************************** -`Hyperparameter Tuning `_ +`Quantum machine learning in Amazon Braket Hybrid Jobs `_ ************************** This notebook demonstrates a typical quantum machine learning workflow, including uploading data, monitoring training, and tuning hyperparameters. diff --git a/doc/examples-ml-pennylane.rst b/doc/examples-ml-pennylane.rst index 0a3ec922..349541fc 100644 --- a/doc/examples-ml-pennylane.rst +++ b/doc/examples-ml-pennylane.rst @@ -37,7 +37,7 @@ the Amazon Braket SV1 simulator to speed up gradient calculations and hence trai using around 1-2 minutes per iteration. ************************** -`Quantum chemistry with VQE `_ +`Hydrogen Molecule geometry with VQE `_ ************************** In this tutorial, you will learn how PennyLane and Amazon Braket can be combined to solve an From ecaa428944dae9e62e00cc7596a343f68eeacc7e Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 2 Nov 2022 21:49:22 +0000 Subject: [PATCH 0564/1165] prepare release v1.33.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 e801d253..e75ba0ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.33.0.post0 (2022-11-02) + +### Documentation Changes + + * update example notebook links + ## v1.33.0 (2022-11-01) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 8c20f55c..35bf19ce 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.33.1.dev0" +__version__ = "1.33.0.post0" From 548cbfff9dc4cbf554a72d675016df852894cb5d Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 2 Nov 2022 21:49:22 +0000 Subject: [PATCH 0565/1165] update development version to v1.33.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 35bf19ce..8c20f55c 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.33.0.post0" +__version__ = "1.33.1.dev0" From 5ba10fa9cd40c7abd6d0b3a8697d6bb1c85a71c1 Mon Sep 17 00:00:00 2001 From: nate stemen Date: Mon, 7 Nov 2022 23:48:56 -0800 Subject: [PATCH 0566/1165] fix: bump oqpy version (#469) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 145f0f3b..2dac3451 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ install_requires=[ "amazon-braket-schemas>=1.12.0", "amazon-braket-default-simulator>=1.10.0", - "oqpy==0.1.0", + "oqpy==0.1.1", "backoff", "boltons", "boto3>=1.22.3", From 47c7bf162dc13af4335c96c19bbd5efa31666804 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 8 Nov 2022 18:22:50 +0000 Subject: [PATCH 0567/1165] prepare release v1.33.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 e75ba0ce..10665ece 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.33.1 (2022-11-08) + +### Bug Fixes and Other Changes + + * bump oqpy version + ## v1.33.0.post0 (2022-11-02) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 8c20f55c..9f18c001 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.33.1.dev0" +__version__ = "1.33.1" From dc26e437079a755d0114608657f380293db03221 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 8 Nov 2022 18:22:50 +0000 Subject: [PATCH 0568/1165] update development version to v1.33.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 9f18c001..44b88c00 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.33.1" +__version__ = "1.33.2.dev0" From 12510587a2489f54882a2afe92b6616835da15e9 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Wed, 9 Nov 2022 18:38:24 -0800 Subject: [PATCH 0569/1165] fix: Reference code from the current commit for dependent tests (#472) --- .github/scripts/update_dependency.py | 34 +++++++++++++++++++++++++++ .github/workflows/dependent-tests.yml | 2 ++ 2 files changed, 36 insertions(+) create mode 100644 .github/scripts/update_dependency.py diff --git a/.github/scripts/update_dependency.py b/.github/scripts/update_dependency.py new file mode 100644 index 00000000..ec1be302 --- /dev/null +++ b/.github/scripts/update_dependency.py @@ -0,0 +1,34 @@ +# 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 fileinput +from pathlib import Path + +# Here we replace the `amazon-braket-sdk` dependency to point to the file system; otherwise +# pip will install them separately, allowing it to override the version of +# any mutual dependencies with the sdk. By pointing to the file system, pip will be +# forced to reconcile the dependencies in setup.py with the dependencies of the sdk, +# and raise an error if there are conflicts. While we can't do this for every upstream +# dependency, we can do this for the ones we own to make sure that when the sdk updates +# its dependencies, these upstream github repos will not be impacted. + +package = "amazon-braket-sdk" +path = Path.cwd().parent.resolve() + +for line in fileinput.input("setup.py", inplace=True): + # Update the amazon-braket-sdk dependency in setup.py to use the local path. This + # would help catch conflicts during the installation process. + replaced_line = ( + line if package not in line else f'"{package} @ file://{path}/{package}-python",\n' + ) + print(replaced_line, end="") diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index 0daf6c3c..3b132db6 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -37,6 +37,8 @@ jobs: cd .. git clone https://github.com/aws/${{ matrix.dependent }}.git cd ${{ matrix.dependent }} + # Update the amazon-braket-sdk dependency to reference the current commit + python ${GITHUB_WORKSPACE}/.github/scripts/update_dependency.py pip install -e .[test] - name: Run unit tests run: | From 4ecda0ba3af456ed39fca5b2d721c59302a06923 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 10 Nov 2022 16:26:31 +0000 Subject: [PATCH 0570/1165] prepare release v1.33.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 10665ece..a7d27392 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.33.2 (2022-11-10) + +### Bug Fixes and Other Changes + + * Reference code from the current commit for dependent tests + ## v1.33.1 (2022-11-08) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 44b88c00..5bd870ff 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.33.2.dev0" +__version__ = "1.33.2" From e2262a40e36a81f50818a220626c98ec3c4664b2 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 10 Nov 2022 16:26:31 +0000 Subject: [PATCH 0571/1165] update development version to v1.33.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 5bd870ff..bddb1fa4 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.33.2" +__version__ = "1.33.3.dev0" From 56319cc11b3a4930a4cf1c4a142774514155ccb0 Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Thu, 10 Nov 2022 11:58:53 -0800 Subject: [PATCH 0572/1165] feature: adding Braket checkstyle checks. (#474) --- .../ahs/analog_hamiltonian_simulation.py | 2 +- src/braket/ahs/atom_arrangement.py | 8 +-- src/braket/ahs/field.py | 2 +- src/braket/aws/aws_session.py | 2 +- src/braket/circuits/angled_gate.py | 8 +-- src/braket/circuits/circuit_diagram.py | 5 +- src/braket/circuits/gates.py | 49 ++++++++++--------- src/braket/devices/local_simulator.py | 3 +- .../parametric/free_parameter_expression.py | 8 +++ src/braket/pulse/ast/free_parameters.py | 23 +++++++-- src/braket/pulse/ast/qasm_parser.py | 35 ++++++++++++- src/braket/pulse/ast/qasm_transformer.py | 10 +++- src/braket/pulse/pulse_sequence.py | 19 ++++--- src/braket/pulse/waveforms.py | 4 +- ...iltonian_simulation_quantum_task_result.py | 2 +- tox.ini | 2 + 16 files changed, 132 insertions(+), 50 deletions(-) diff --git a/src/braket/ahs/analog_hamiltonian_simulation.py b/src/braket/ahs/analog_hamiltonian_simulation.py index f13f96bf..80c4f2f5 100644 --- a/src/braket/ahs/analog_hamiltonian_simulation.py +++ b/src/braket/ahs/analog_hamiltonian_simulation.py @@ -78,7 +78,7 @@ def _hamiltonian_to_ir(self) -> ir.Hamiltonian: shiftingFields=terms[AnalogHamiltonianSimulation.SHIFTING_FIELDS_PROPERTY], ) - def discretize(self, device) -> AnalogHamiltonianSimulation: + def discretize(self, device) -> AnalogHamiltonianSimulation: # noqa """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 da0dd13a..70fc64b0 100644 --- a/src/braket/ahs/atom_arrangement.py +++ b/src/braket/ahs/atom_arrangement.py @@ -36,19 +36,19 @@ class AtomArrangementItem: coordinate: Tuple[Number, Number] site_type: SiteType - def _validate_coordinate(self): + def _validate_coordinate(self) -> None: if len(self.coordinate) != 2: raise ValueError(f"{self.coordinate} must be of length 2") for idx, num in enumerate(self.coordinate): if not isinstance(num, Number): raise TypeError(f"{num} at position {idx} must be a number") - def _validate_site_type(self): + def _validate_site_type(self) -> None: allowed_site_types = {SiteType.FILLED, SiteType.VACANT} if self.site_type not in allowed_site_types: raise ValueError(f"{self.site_type} must be one of {allowed_site_types}") - def __post_init__(self): + def __post_init__(self) -> None: self._validate_coordinate() self._validate_site_type() @@ -68,7 +68,7 @@ def add( """Add a coordinate to the atom arrangement. Args: - coordinate (Union[Tuple[Number, Number], np.ndarray]): The coordinate of the + coordinate (Union[Tuple[Number, Number], ndarray]): The coordinate of the 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. diff --git a/src/braket/ahs/field.py b/src/braket/ahs/field.py index 1f0a2c12..9a473fd9 100644 --- a/src/braket/ahs/field.py +++ b/src/braket/ahs/field.py @@ -55,7 +55,7 @@ def discretize( Args: time_resolution (Decimal): Time resolution value_resolution (Decimal): Value resolution - pattern_resolution (Decimal or None): Pattern resolution + pattern_resolution (Optional[Decimal]): Pattern resolution Returns: Field: A new discretized field. diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index b966d4aa..8233223a 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -190,7 +190,7 @@ def add_braket_user_agent(self, user_agent: str) -> None: self.braket_client._client_config.user_agent = f"{existing_user_agent} {user_agent}" @staticmethod - def _add_cost_tracker_count_handler(request, **kwargs) -> None: + def _add_cost_tracker_count_handler(request: Any, **kwargs) -> None: request.headers.add_header("Braket-Trackers", str(len(active_trackers()))) # diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index b81052ea..a5b71b9f 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -131,7 +131,7 @@ def __init__( radians or expression representation. angle_2 (Union[FreeParameterExpression, float]): The second angle of the gate in radians or expression representation. - qubit_count (int, optional): The number of qubits that this gate interacts with. + qubit_count (Optional[int]): The number of qubits that this gate interacts with. ascii_symbols (Sequence[str]): ASCII string symbols for the gate. These are used when 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. @@ -226,10 +226,12 @@ def __repr__(self): @singledispatch -def _angles_equal(angle_1, angle_2): +def _angles_equal( + angle_1: Union[FreeParameterExpression, float], angle_2: Union[FreeParameterExpression, float] +) -> bool: return math.isclose(angle_1, angle_2) @_angles_equal.register -def _(angle_1: FreeParameterExpression, angle_2): +def _(angle_1: FreeParameterExpression, angle_2: FreeParameterExpression): return angle_1 == angle_2 diff --git a/src/braket/circuits/circuit_diagram.py b/src/braket/circuits/circuit_diagram.py index 06425670..ee09d73f 100644 --- a/src/braket/circuits/circuit_diagram.py +++ b/src/braket/circuits/circuit_diagram.py @@ -10,16 +10,19 @@ # 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 +import braket.circuits.circuit as cir + class CircuitDiagram(ABC): """A class that builds circuit diagrams.""" @staticmethod @abstractmethod - def build_diagram(circuit) -> str: + def build_diagram(circuit: cir.Circuit) -> str: """ Build a diagram for the specified `circuit`. diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index dfe2cfc5..b5dca4bd 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -1923,7 +1923,7 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return f"gpi({self.angle}) {target_qubit};" @@ -1942,7 +1942,7 @@ def adjoint(self) -> List[Gate]: def fixed_qubit_count() -> int: return 1 - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> GPi: return get_angle(self, **kwargs) @staticmethod @@ -1953,7 +1953,7 @@ def gpi( """Registers this function into the circuit class. Args: - target (Qubit or int): Target qubit index. + target (QubitInput): Target qubit index. angle (Union[FreeParameterExpression, float]): Angle in radians. Returns: @@ -1984,7 +1984,7 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit = serialization_properties.format_target(int(target[0])) return f"gpi2({self.angle}) {target_qubit};" @@ -2003,7 +2003,7 @@ def adjoint(self) -> List[Gate]: def fixed_qubit_count() -> int: return 1 - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> GPi2: return get_angle(self, **kwargs) @staticmethod @@ -2014,7 +2014,7 @@ def gpi2( """Registers this function into the circuit class. Args: - target (Qubit or int): Target qubit index. + target (QubitInput): Target qubit index. angle (Union[FreeParameterExpression, float]): Angle in radians. Returns: @@ -2051,7 +2051,7 @@ def __init__( def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ): + ) -> str: target_qubit_1 = serialization_properties.format_target(int(target[0])) target_qubit_2 = serialization_properties.format_target(int(target[1])) return f"ms({self.angle_1}, {self.angle_2}) {target_qubit_1}, {target_qubit_2};" @@ -2073,7 +2073,7 @@ def adjoint(self) -> List[Gate]: def fixed_qubit_count() -> int: return 2 - def bind_values(self, **kwargs): + def bind_values(self, **kwargs) -> MS: return _get_angles(self, **kwargs) @staticmethod @@ -2087,8 +2087,10 @@ def ms( """Registers this function into the circuit class. Args: - target (Qubit or int): Target qubit index. - angle (Union[FreeParameterExpression, float]): Angle in radians. + target1 (QubitInput): Target qubit 1 index. + target2 (QubitInput): Target qubit 2 index. + angle_1 (Union[FreeParameterExpression, float]): angle in radians. + angle_2 (Union[FreeParameterExpression, float]): angle in radians. Returns: Iterable[Instruction]: MS instruction. @@ -2300,7 +2302,8 @@ def _double_angled_ascii_characters( Args: gate (str): The name of the gate. - angle (Union[FreeParameterExpression, float]): The angle for the gate. + angle_1 (Union[FreeParameterExpression, float]): angle in radians. + angle_2 (Union[FreeParameterExpression, float]): angle in radians. Returns: str: Returns the ascii representation for an angled gate. @@ -2313,43 +2316,45 @@ def _double_angled_ascii_characters( ) -def get_angle(self: AngledGate, **kwargs) -> AngledGate: +def get_angle(gate: AngledGate, **kwargs) -> AngledGate: """ Gets the angle with all values substituted in that are requested. Args: - self (AngledGate): The subclass of AngledGate for which the angle is being obtained. + gate (AngledGate): The subclass of AngledGate for which the angle is being obtained. Returns: AngledGate: A new gate of the type of the AngledGate originally used with all angles updated. """ new_angle = ( - self.angle.subs(kwargs) if isinstance(self.angle, FreeParameterExpression) else self.angle + gate.angle.subs(kwargs) if isinstance(gate.angle, FreeParameterExpression) else gate.angle ) - return type(self)(angle=new_angle) + return type(gate)(angle=new_angle) -def _get_angles(self, **kwargs): +def _get_angles(gate: DoubleAngledGate, **kwargs) -> DoubleAngledGate: """ Gets the angle with all values substituted in that are requested. Args: - self: The subclass of AngledGate for which the angle is being obtained. + gate (DoubleAngledGate): The subclass of DoubleAngledGate for which the angle is being + obtained. **kwargs: The named parameters that are being filled for a particular gate. Returns: - A new gate of the type of the AngledGate originally used with all angles updated. + DoubleAngledGate: A new gate of the type of the AngledGate originally used with all angles + updated. """ new_angles = [ ( - getattr(self, angle).subs(kwargs) - if isinstance(getattr(self, angle), FreeParameterExpression) - else getattr(self, angle) + getattr(gate, angle).subs(kwargs) + if isinstance(getattr(gate, angle), FreeParameterExpression) + else getattr(gate, angle) ) for angle in ("angle_1", "angle_2") ] - return type(self)(angle_1=new_angles[0], angle_2=new_angles[1]) + return type(gate)(angle_1=new_angles[0], angle_2=new_angles[1]) def format_complex(number: complex) -> str: diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 3a751f0a..a58dfba6 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -68,7 +68,8 @@ def run( """Runs the given task with the wrapped local simulator. Args: - task_specification (Union[Circuit, Problem, Program]): The task specification. + task_specification (Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]): The + 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 task specification. diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index 3ccaf4e0..eb1c045c 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -141,6 +141,14 @@ def __repr__(self) -> str: def subs_if_free_parameter(parameter: Any, **kwargs) -> Any: + """Substitute a free parameter with the given kwargs, if any. + Args: + parameter (Any): The parameter. + **kwargs: The kwargs to use to substitute. + + Returns: + Any: The substituted parameters. + """ if isinstance(parameter, FreeParameterExpression): substituted = parameter.subs(kwargs) if isinstance(substituted, Float): diff --git a/src/braket/pulse/ast/free_parameters.py b/src/braket/pulse/ast/free_parameters.py index 922f0fb0..6cfc36d0 100644 --- a/src/braket/pulse/ast/free_parameters.py +++ b/src/braket/pulse/ast/free_parameters.py @@ -10,7 +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 typing import Dict +from typing import Dict, Union from openpulse import ast from openqasm3.ast import DurationLiteral @@ -38,14 +38,31 @@ def __init__(self, param_values: Dict[str, float]): self.param_values = param_values super().__init__() - def visit__FreeParameterExpressionIdentifier(self, identifier: ast.Identifier): + def visit__FreeParameterExpressionIdentifier( + self, identifier: ast.Identifier + ) -> Union[_FreeParameterExpressionIdentifier, ast.FloatLiteral]: + """Visit a FreeParameterExpressionIdentifier. + Args: + identifier (Identifier): The identifier. + + Returns: + Union[_FreeParameterExpressionIdentifier, FloatLiteral]: The transformed expression. + """ 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): + def visit_DurationLiteral(self, duration_literal: DurationLiteral) -> DurationLiteral: + """Visit Duration Literal. + node.value, node.unit (node.unit.name, node.unit.value) + 1 + Args: + duration_literal (DurationLiteral): The duration literal. + Returns: + DurationLiteral: The transformed duration literal. + """ duration = duration_literal.value if not isinstance(duration, FreeParameterExpression): return duration_literal diff --git a/src/braket/pulse/ast/qasm_parser.py b/src/braket/pulse/ast/qasm_parser.py index e192242e..8e6f94de 100644 --- a/src/braket/pulse/ast/qasm_parser.py +++ b/src/braket/pulse/ast/qasm_parser.py @@ -29,22 +29,53 @@ def __init__(self, *args, **kwargs): def visit__FreeParameterExpressionIdentifier( self, node: ast.Identifier, context: PrinterState ) -> None: + """Visit a FreeParameterExpressionIdentifier. + 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): + 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) - def visit_ClassicalDeclaration(self, node: ast.ClassicalDeclaration, context: PrinterState): + def visit_ClassicalDeclaration( + self, node: ast.ClassicalDeclaration, context: PrinterState + ) -> 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 (PrinterState): The printer state context. + """ # Skip port declarations in output if not isinstance(node.type, ast.PortType): super().visit_ClassicalDeclaration(node, context) def ast_to_qasm(ast: ast.Program) -> str: + """Converts an AST program to OpenQASM + + Args: + ast (Program): The AST program. + + Returns: + str: a str representing the OpenPulse program encoding the program. + """ out = io.StringIO() _PulsePrinter(out, indent=" ").visit(ast) return out.getvalue().strip() diff --git a/src/braket/pulse/ast/qasm_transformer.py b/src/braket/pulse/ast/qasm_transformer.py index 1e3a7433..ae4cccad 100644 --- a/src/braket/pulse/ast/qasm_transformer.py +++ b/src/braket/pulse/ast/qasm_transformer.py @@ -10,7 +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 typing import Optional +from typing import Any, Optional from openpulse import ast from openqasm3.visitor import QASMTransformer @@ -29,7 +29,13 @@ def __init__(self, register_identifier: Optional[str] = None): self._capture_v0_count = 0 super().__init__() - def visit_ExpressionStatement(self, expression_statement: ast.ExpressionStatement): + 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. + """ if ( isinstance(expression_statement.expression, ast.FunctionCall) and expression_statement.expression.name.name == "capture_v0" diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index 258120cd..58d2cce9 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -208,13 +208,15 @@ def barrier(self, frames: List[Frame]) -> PulseSequence: return self def play(self, frame: Frame, waveform: Waveform) -> PulseSequence: - """ - Adds an instruction to play the specified waveform on the supplied frame. + """Adds an instruction to play the specified waveform on the supplied frame. Args: frame (Frame): Frame on which the specified waveform signal would be output. waveform (Waveform): Waveform envelope specifying the signal to output on the specified frame. + + Returns: + PulseSequence: returns self. """ _validate_uniqueness(self._frames, frame) _validate_uniqueness(self._waveforms, waveform) @@ -251,7 +253,7 @@ def make_bound_pulse_sequence(self, param_values: Dict[str, float]) -> PulseSequ share the same name, all the parameters of that name will be set to the mapped value. Args: - param_values (Dict[str, Number]): A mapping of FreeParameter names + param_values (Dict[str, float]): A mapping of FreeParameter names to a value to assign to them. Returns: @@ -291,8 +293,11 @@ def make_bound_pulse_sequence(self, param_values: Dict[str, float]) -> PulseSequ return new_pulse_sequence def to_ir(self) -> str: - """Returns a str representing the OpenPulse program encoding the PulseSequence.""" + """Converts this OpenPulse problem into IR representation. + Returns: + str: a str representing the OpenPulse program encoding the PulseSequence. + """ program = deepcopy(self._program) if self._capture_v0_count: register_identifier = "psb" @@ -305,7 +310,9 @@ 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): + def _format_parameter_ast( + self, parameter: Union[float, FreeParameterExpression] + ) -> Union[float, _FreeParameterExpressionIdentifier]: if isinstance(parameter, FreeParameterExpression): for p in parameter.expression.free_symbols: self._free_parameters.add(FreeParameter(p.name)) @@ -334,7 +341,7 @@ def __call__(self, arg: Any = None, **kwargs) -> PulseSequence: def _validate_uniqueness( mapping: Dict[str, Any], values: Union[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 9c62cc77..76f7fcf9 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -211,7 +211,7 @@ def bind_values(self, **kwargs) -> DragGaussianWaveform: replaced with their values. Returns: - ConstantWaveform: A copy of this waveform with the requested parameters bound. + DragGaussianWaveform: A copy of this waveform with the requested parameters bound. """ constructor_kwargs = { "length": subs_if_free_parameter(self.length, **kwargs), @@ -324,7 +324,7 @@ def bind_values(self, **kwargs) -> GaussianWaveform: replaced with their values. Returns: - ConstantWaveform: A copy of this waveform with the requested parameters bound. + GaussianWaveform: A copy of this waveform with the requested parameters bound. """ constructor_kwargs = { "length": subs_if_free_parameter(self.length, **kwargs), 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 f1efc82e..09cb5061 100644 --- a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py +++ b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py @@ -99,7 +99,7 @@ def _get_measurements(cls, result: AnalogHamiltonianSimulationTaskResult) -> Lis return measurements -def _equal_sequences(sequence0, sequence1) -> bool: +def _equal_sequences(sequence0: np.ndarray, sequence1: np.ndarray) -> bool: if sequence0 is None and sequence1 is None: return True if sequence0 is None or sequence1 is None: diff --git a/tox.ini b/tox.ini index fac55f02..4db891d3 100644 --- a/tox.ini +++ b/tox.ini @@ -44,8 +44,10 @@ basepython = python3 skip_install = true deps = flake8 + git+https://github.com/aws/amazon-braket-build-tools.git commands = flake8 {posargs} + flake8 --enable-extensions=BCS src [testenv:isort] basepython = python3 From 6eeef94a102fa303c67fa28f94cc04a7083239d1 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 14 Nov 2022 16:25:18 +0000 Subject: [PATCH 0573/1165] prepare release v1.34.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 a7d27392..30110399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.34.0 (2022-11-14) + +### Features + + * adding Braket checkstyle checks. + ## v1.33.2 (2022-11-10) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index bddb1fa4..d605d6e1 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.33.3.dev0" +__version__ = "1.34.0" From ca5b08dada4839ca31c012ff50aa20b656fd1879 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 14 Nov 2022 16:25:18 +0000 Subject: [PATCH 0574/1165] update development version to v1.34.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 d605d6e1..d222eca6 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.34.0" +__version__ = "1.34.1.dev0" From 8c156e489d7254d366f7afc566c57b84af22ceca Mon Sep 17 00:00:00 2001 From: Lauren Capelluto <107005333+laurencap@users.noreply.github.com> Date: Mon, 14 Nov 2022 16:31:20 -0500 Subject: [PATCH 0575/1165] fix: update import path in error message (#476) There is no braket.circuit module; update text to braket.circuits. --- src/braket/circuits/circuit_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/circuits/circuit_helpers.py b/src/braket/circuits/circuit_helpers.py index 53319ac5..44e9bd57 100644 --- a/src/braket/circuits/circuit_helpers.py +++ b/src/braket/circuits/circuit_helpers.py @@ -32,7 +32,7 @@ def validate_circuit_and_shots(circuit: Circuit, shots: int) -> None: raise ValueError("Circuit must have instructions 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.circuit.result_types`" + "No result types specified for circuit and shots=0. See `braket.circuits.result_types`" ) elif shots and circuit.result_types: if not circuit.observables_simultaneously_measurable: From 79fe36f82655c36546a137fbf8301ca042414eb6 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 15 Nov 2022 16:25:13 +0000 Subject: [PATCH 0576/1165] prepare release v1.34.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 30110399..69970581 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.34.1 (2022-11-15) + +### Bug Fixes and Other Changes + + * update import path in error message + ## v1.34.0 (2022-11-14) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d222eca6..fa9ec09b 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.34.1.dev0" +__version__ = "1.34.1" From f1bab39da2062abee3853c83a20fe0bae8756757 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 15 Nov 2022 16:25:13 +0000 Subject: [PATCH 0577/1165] update development version to v1.34.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 fa9ec09b..e34dc1d5 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.34.1" +__version__ = "1.34.2.dev0" From 3dae20c91902a1ab6196da13d533c8ad58615d3a Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Wed, 16 Nov 2022 17:53:56 -0500 Subject: [PATCH 0578/1165] fix: Plot the phase between 0 and 2*pi (#468) --- src/braket/pulse/ast/approximation_parser.py | 17 +++++----- .../pulse/ast/test_approximation_parser.py | 31 +++++++++++++++++++ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/braket/pulse/ast/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py index bde359c5..51d5851a 100644 --- a/src/braket/pulse/ast/approximation_parser.py +++ b/src/braket/pulse/ast/approximation_parser.py @@ -129,10 +129,7 @@ def visit_ClassicalDeclaration( elif type(node.type) == ast.PortType: pass else: - # Only Classical type? - # for instance: type(node.type) == ast.IntType: raise NotImplementedError - # context.variables[identifier] = self.visit(node.init_expression, context) def visit_DelayInstruction(self, node: ast.DelayInstruction, context: _ParseState) -> None: """Visit a Delay Instruction. @@ -199,7 +196,6 @@ def visit_UnaryExpression(self, node: ast.UnaryExpression, context: _ParseState) node (ast.UnaryExpression): The unary expression. context (_ParseState): The parse state context. """ - # context.print(node.op.name) if node.op == ast.UnaryOperator["-"]: return -1 * self.visit(node.expression, context) elif node.op == ast.UnaryOperator["!"]: @@ -264,9 +260,6 @@ def visit_BinaryExpression(self, node: ast.BinaryExpression, context: _ParseStat elif node.op == op[">>"]: return lhs >> rhs else: - # Need more - # if node.op == ast.BinaryOperator["."]: - # What to do? raise NotImplementedError def visit_ArrayLiteral(self, node: ast.ArrayLiteral, context: _ParseState) -> Any: @@ -362,7 +355,7 @@ def set_phase(self, node: ast.FunctionCall, context: _ParseState) -> None: """ frame = self.visit(node.arguments[0], context) value = self.visit(node.arguments[1], context) - context.frame_data[frame].phase = value + context.frame_data[frame].phase = value % (2 * np.pi) def shift_phase(self, node: ast.FunctionCall, context: _ParseState) -> None: """A 'shift_phase' Function call. @@ -373,6 +366,7 @@ def shift_phase(self, node: ast.FunctionCall, context: _ParseState) -> None: frame = self.visit(node.arguments[0], context) value = self.visit(node.arguments[1], context) context.frame_data[frame].phase += value + context.frame_data[frame].phase %= 2 * np.pi def set_scale(self, node: ast.FunctionCall, context: _ParseState) -> None: """A 'set_scale' Function call. @@ -453,14 +447,17 @@ def drag_gaussian(self, node: ast.FunctionCall, context: _ParseState) -> Wavefor def _init_frame_data(frames: Dict[str, Frame]) -> Dict[str, _FrameState]: frame_states = dict() for frameId, frame in frames.items(): - frame_states[frameId] = _FrameState(frame.port.dt, frame.frequency, frame.phase) + frame_states[frameId] = _FrameState( + frame.port.dt, frame.frequency, frame.phase % (2 * np.pi) + ) return frame_states def _lcm_floats(*dts: List[float]) -> float: """Return the least common multiple of time increments of a list of frames A time increment is the inverse of the corresponding sample rate which is considered - an integer LCM of rational numbers is lcm = (LCM of numerators) / (GCD of denominators) + an integer. + LCM of rational numbers is lcm = (LCM of numerators) / (GCD of denominators) Hence the LCM of dts is 1/gcd([sample rates]) Args: 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 27b1b735..1521875c 100644 --- a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py +++ b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py @@ -111,6 +111,37 @@ def test_set_shift_phase(port): verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) +def test_set_shift_phase_beyond_2_pi(port): + frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) + pulse_seq = ( + PulseSequence() + .set_phase(frame, 5 * np.pi / 2) + .delay(frame, 2e-9) + .shift_phase(frame, -np.pi) + .delay(frame, 5e-9) + .set_phase(frame, 0) + ) + expected_amplitudes = {"frame1": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries()} + expected_phases = {"frame1": TimeSeries()} + + # 2 datapoints for first delay + # 5pi/2 is reduced to pi/2 + expected_amplitudes["frame1"].put(0, 0).put(1e-9, 0) + expected_frequencies["frame1"].put(0, 1e8).put(1e-9, 1e8) + expected_phases["frame1"].put(0, np.pi / 2).put(1e-9, np.pi / 2) + + # set_shift_phase should be instantaneous (result on current or next datapoint?) + # shift_phase adds -pi to the phase of the last point -> 3pi/2 + expected_amplitudes["frame1"].put(2e-9, 0).put(6e-9, 0) + expected_frequencies["frame1"].put(2e-9, 1e8).put(6e-9, 1e8) + expected_phases["frame1"].put(2e-9, 3 * np.pi / 2).put(6e-9, 3 * np.pi / 2) + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) + + 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 = ( From 221745fe3c67cfa77ff9d772239ce7814f509258 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 17 Nov 2022 16:27:56 +0000 Subject: [PATCH 0579/1165] prepare release v1.34.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 69970581..5a049f43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.34.2 (2022-11-17) + +### Bug Fixes and Other Changes + + * Plot the phase between 0 and 2*pi + ## v1.34.1 (2022-11-15) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index e34dc1d5..05663be5 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.34.2.dev0" +__version__ = "1.34.2" From 4f8482ec5c271a43d6192775176ee2ce117ec778 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 17 Nov 2022 16:27:56 +0000 Subject: [PATCH 0580/1165] update development version to v1.34.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 05663be5..ad6e97dc 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.34.2" +__version__ = "1.34.3.dev0" From e35920d82e9a9163255fd872033d0380d88124c8 Mon Sep 17 00:00:00 2001 From: Stephen Face <60493521+shpface@users.noreply.github.com> Date: Thu, 17 Nov 2022 09:15:52 -0800 Subject: [PATCH 0581/1165] fix: remove d-wave content (#477) * update doc links to annealing examples * Remove D-Wave from README * Remove D-Wave from integ_tests * remove D-Wave from AwsQuantumTask comments Co-authored-by: Cody Wang --- README.md | 3 - doc/examples-quantum-annealing-dwave.rst | 75 ------------------------ doc/examples.rst | 1 - src/braket/aws/aws_quantum_task.py | 4 -- test/integ_tests/test_cost_tracking.py | 39 ------------ test/integ_tests/test_device_creation.py | 9 +-- 6 files changed, 3 insertions(+), 128 deletions(-) delete mode 100644 doc/examples-quantum-annealing-dwave.rst diff --git a/README.md b/README.md index f1d6c9f6..7e415ac4 100644 --- a/README.md +++ b/README.md @@ -155,9 +155,6 @@ To select a quantum hardware device, specify its ARN as the value of the `device **Important** Tasks may not run immediately on the QPU. The QPUs only execute tasks during execution windows. To find their execution windows, please refer to the [AWS console](https://console.aws.amazon.com/braket/home) in the "Devices" tab. -### Using Amazon Braket with D-Wave QPU -If you want to use [Ocean](https://docs.ocean.dwavesys.com/en/latest/) with the D-Wave QPU, you can install the [amazon-braket-ocean-plugin-python](https://github.com/aws/amazon-braket-ocean-plugin-python). Information about how to install the plugin is provided in the [README](https://github.com/aws/amazon-braket-ocean-plugin-python/blob/master/README.md) for the repo. - ## Sample Notebooks Sample Jupyter notebooks can be found in the [amazon-braket-examples](https://github.com/aws/amazon-braket-examples/) repo. diff --git a/doc/examples-quantum-annealing-dwave.rst b/doc/examples-quantum-annealing-dwave.rst deleted file mode 100644 index 64f8c7ed..00000000 --- a/doc/examples-quantum-annealing-dwave.rst +++ /dev/null @@ -1,75 +0,0 @@ -################################ -Quantum annealing with D-Wave -################################ - -Learn more about quantum annealing with D-Wave. - -.. toctree:: - :maxdepth: 2 - -************************** -`Anatomy of annealing with ocean `_ -************************** - -Learn more about the anatomy of quantum annealing with D-Wave on Amazon Braket. - -************************** -`Running large problems with QBSolv `_ -************************** - -This tutorial demonstrates how to solve problems with sizes larger than a -D-Wave device can support, by using a hybrid solver called QBSolv. -QBSolv can decompose large problems into sub-problems, which are solved by the -QPU and a classical Tabu solver, or by the classical solver alone. The results -of the sub-problems then construct the solution to the problem. - -************************** -`Maximum Cut `_ -************************** - -This tutorial solves a small instance of the famous maximum cut (MaxCut) problem using -a D-Wave device on Amazon Braket. The MaxCut problem is one of the most famous NP-hard -problems in combinatorial optimization. Applications can be found in clustering problems -for marketing purposes, or for portfolio optimization problems in finance. - -************************** -`Minimum Vertex `_ -************************** - -This tutorial solves a small instance of the minimum vertex problem -using BraketSampler and the BraketDWaveSampler. BraketDWaveSampler uses D-Wave parameter names -(such as answer_mode). BraketSampler uses parameter names that are consistent -with the rest of the Amazon Braket experience. - -************************** -`Graph partitioning `_ -************************** - -This tutorial solves a small instance of a graph partitioning -problem using a D-Wave device on Amazon Braket. - -************************** -`Factoring `_ -************************** - -This tutorial shows how to solve a constraint satisfaction -problem (CSP) problem, with the example of factoring, using a D-Wave device on -Amazon Braket. Particularly, factoring is expressed as a CSP using Boolean -logic operations, and it is converted to a binary quadratic model that can be -solved by a D-Wave device. - -************************** -`Structural Imbalance `_ -************************** - -This tutorial solves a structural imbalance problem using a D-Wave device -on Amazon Braket. Social networks map relationships between people or organizations -onto graphs. Given a social network as a graph, D-Wave devices can partition the graph -into two colored sets, and show the frustrated edges. - -************************** -`Traveling Salesman Problem `_ -************************** - -This tutorial solves small instances of the famous traveling salesman problem -(TSP) using D-Wave devices on Amazon Braket. diff --git a/doc/examples.rst b/doc/examples.rst index cc65a5a8..e607920a 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -13,7 +13,6 @@ https://github.com/aws/amazon-braket-examples. examples-adv-circuits-algorithms.rst examples-hybrid-quantum.rst examples-ml-pennylane.rst - examples-quantum-annealing-dwave.rst examples-hybrid-jobs.rst \ No newline at end of file diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index d32c9084..003fd77d 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -126,8 +126,6 @@ def create( will compute the exact results based on the task specification. device_parameters (Dict[str, Any]): Additional parameters to send to the device. - For example, for D-Wave: - `{"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}}` disable_qubit_rewiring (bool): Whether to run the circuit with the exact qubits chosen, without any rewiring downstream, if this is supported by the device. @@ -624,8 +622,6 @@ def _create_annealing_device_params( Args: device_params (Dict[str, Any]): Additional parameters for the device. - For example, for D-Wave: - `{"providerLevelParameters": {"postprocessingType": "OPTIMIZATION"}}` device_arn (str): The ARN of the quantum device. Returns: diff --git a/test/integ_tests/test_cost_tracking.py b/test/integ_tests/test_cost_tracking.py index a9db2d75..7732a3aa 100644 --- a/test/integ_tests/test_cost_tracking.py +++ b/test/integ_tests/test_cost_tracking.py @@ -15,7 +15,6 @@ import boto3 -from braket.annealing import Problem, ProblemType from braket.aws import AwsDevice, AwsSession from braket.circuits import Circuit from braket.tracking import Tracker @@ -23,44 +22,6 @@ def test_qpu_tracking(): - problem = Problem(ProblemType("QUBO"), linear={35: 1}, quadratic={(35, 36): -1}) - device = AwsDevice("arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6") - other_device = AwsDevice("arn:aws:braket:us-west-2::device/qpu/d-wave/Advantage_system6") - - device.run(problem, shots=50).result() - with Tracker() as t: - device.run(problem, shots=100).result() - other_device.run(problem, shots=400).result() - t_partial_cost = t.qpu_tasks_cost() - assert t_partial_cost > 0 - with Tracker() as s: - task = device.run(problem, shots=200) - assert s.quantum_tasks_statistics() == { - "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6": { - "shots": 200, - "tasks": {"CREATED": 1}, - } - } - task.result() - - assert s.simulator_tasks_cost() == 0 - assert t.simulator_tasks_cost() == 0 - - assert s.quantum_tasks_statistics() == { - "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6": {"shots": 200, "tasks": {"COMPLETED": 1}} - } - assert t.quantum_tasks_statistics() == { - "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6": {"shots": 300, "tasks": {"COMPLETED": 2}}, - "arn:aws:braket:us-west-2::device/qpu/d-wave/Advantage_system6": { - "shots": 400, - "tasks": {"COMPLETED": 1}, - }, - } - - assert s.qpu_tasks_cost() > 0 - assert t.qpu_tasks_cost() > s.qpu_tasks_cost() - assert t.qpu_tasks_cost() > t_partial_cost - circuit = Circuit().h(0) with Tracker() as t: AwsDevice("arn:aws:braket:::device/qpu/ionq/ionQdevice").run(circuit, shots=10) diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 2edfb3d6..9a55bb56 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -15,7 +15,6 @@ from braket.aws import AwsDevice -DWAVE_ARN = "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6" RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-10" IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/ionQdevice" SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" @@ -24,7 +23,7 @@ @pytest.mark.parametrize( - "arn", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (OQC_ARN), (SIMULATOR_ARN), (PULSE_ARN)] + "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) @@ -50,9 +49,7 @@ def test_device_across_regions(aws_session): AwsDevice(OQC_ARN, aws_session) -@pytest.mark.parametrize( - "arn", [(RIGETTI_ARN), (IONQ_ARN), (DWAVE_ARN), (OQC_ARN), (SIMULATOR_ARN)] -) +@pytest.mark.parametrize("arn", [(RIGETTI_ARN), (IONQ_ARN), (OQC_ARN), (SIMULATOR_ARN)]) def test_get_devices_arn(arn): results = AwsDevice.get_devices(arns=[arn]) assert results[0].arn == arn @@ -72,5 +69,5 @@ def test_get_devices_others(): def test_get_devices_all(): result_arns = [result.arn for result in AwsDevice.get_devices()] - for arn in [DWAVE_ARN, RIGETTI_ARN, IONQ_ARN, SIMULATOR_ARN, OQC_ARN]: + for arn in [RIGETTI_ARN, IONQ_ARN, SIMULATOR_ARN, OQC_ARN]: assert arn in result_arns From 0a378974bf1a19f285e87241d29dbf754a15db0f Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 17 Nov 2022 17:43:37 +0000 Subject: [PATCH 0582/1165] prepare release v1.34.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 5a049f43..cf0e8012 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.34.3 (2022-11-17) + +### Bug Fixes and Other Changes + + * remove d-wave content + ## v1.34.2 (2022-11-17) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index ad6e97dc..7cd105ce 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.34.3.dev0" +__version__ = "1.34.3" From 3808529821be80b9f971e211b15ccad44a4cb26c Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 17 Nov 2022 17:43:37 +0000 Subject: [PATCH 0583/1165] update development version to v1.34.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 7cd105ce..fce3a457 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.34.3" +__version__ = "1.34.4.dev0" From 7a1c02f92b603e07010c9bb19ecfca2d08b9eacf Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 17 Nov 2022 15:35:35 -0800 Subject: [PATCH 0584/1165] infra: Remove Ocean plugin from dependent tests (#478) --- .github/workflows/dependent-tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index 3b132db6..c6289539 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -18,7 +18,6 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] python-version: [3.7, 3.8, 3.9] dependent: - - amazon-braket-ocean-plugin-python - amazon-braket-pennylane-plugin-python - amazon-braket-strawberryfields-plugin-python From 3fb4ad58ba854282da39bd8e6bd48ed9a9ad0999 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 21 Nov 2022 20:56:43 +0000 Subject: [PATCH 0585/1165] prepare release v1.34.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 cf0e8012..7be67e67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.34.3.post0 (2022-11-21) + +### Testing and Release Infrastructure + + * Remove Ocean plugin from dependent tests + ## v1.34.3 (2022-11-17) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index fce3a457..bea6571f 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.34.4.dev0" +__version__ = "1.34.3.post0" From 2d9c45dadec785743247f21aad273059f426f530 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 21 Nov 2022 20:56:43 +0000 Subject: [PATCH 0586/1165] update development version to v1.34.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 bea6571f..fce3a457 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.34.3.post0" +__version__ = "1.34.4.dev0" From 41084705e9e3da4767f48138bfe64efd330fd305 Mon Sep 17 00:00:00 2001 From: nate stemen Date: Tue, 6 Dec 2022 09:06:19 -0800 Subject: [PATCH 0587/1165] fix: loosen oqpy requirement (#479) --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2dac3451..3fde7540 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ install_requires=[ "amazon-braket-schemas>=1.12.0", "amazon-braket-default-simulator>=1.10.0", - "oqpy==0.1.1", + "oqpy~=0.1.1", "backoff", "boltons", "boto3>=1.22.3", @@ -45,7 +45,7 @@ "black", "botocore", "coverage==5.5", - "flake8", + "flake8<=5.0.4", "isort", "jsonschema==3.2.0", "pre-commit", From 04df52b53d5728328e27483936631d2d67d891d7 Mon Sep 17 00:00:00 2001 From: tichrisl137 <101154780+tichrisl137@users.noreply.github.com> Date: Tue, 6 Dec 2022 09:20:53 -0800 Subject: [PATCH 0588/1165] docs: Update examples-getting-started.rst (#475) --- doc/examples-getting-started.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/examples-getting-started.rst b/doc/examples-getting-started.rst index e68e4134..d3e9dadb 100644 --- a/doc/examples-getting-started.rst +++ b/doc/examples-getting-started.rst @@ -24,6 +24,8 @@ It is often used as a performance benchmark for today's hardware. In many quantu 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. + *************** `Running quantum circuits on QPU devices `_ *************** From 44d2d487902f227d709d96e40d774882cf99c9ba Mon Sep 17 00:00:00 2001 From: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com> Date: Wed, 7 Dec 2022 08:01:03 -0800 Subject: [PATCH 0589/1165] feature: adjoint gradient (#484) Co-authored-by: Aaron Berdy Co-authored-by: Jacob Feldman Co-authored-by: Katharine Hyatt <67932820+kshyatt-aws@users.noreply.github.com> Co-authored-by: Milan <30416311+krneta@users.noreply.github.com> Co-authored-by: Cody Wang Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> Co-authored-by: Stephen Face <60493521+shpface@users.noreply.github.com> Co-authored-by: Kshitij Chhabra Co-authored-by: Peter Komar Co-authored-by: Mao Lin Co-authored-by: Michael Abel <115323439+michaab@users.noreply.github.com> --- CHANGELOG.md | 114 +++--- setup.cfg | 9 +- setup.py | 4 +- src/braket/aws/aws_device.py | 31 +- src/braket/aws/aws_quantum_task.py | 74 +++- src/braket/aws/aws_quantum_task_batch.py | 88 ++++- src/braket/circuits/ascii_circuit_diagram.py | 14 +- src/braket/circuits/circuit.py | 20 +- src/braket/circuits/observable.py | 47 ++- src/braket/circuits/observables.py | 227 +++++++++-- src/braket/circuits/result_type.py | 80 +++- src/braket/circuits/result_types.py | 110 +++++- test/integ_tests/test_adjoint_gradient.py | 175 +++++++++ .../braket/aws/common_test_utils.py | 15 +- test/unit_tests/braket/aws/test_aws_device.py | 360 ++++++++++++++---- .../braket/aws/test_aws_quantum_task.py | 90 ++++- .../circuits/test_ascii_circuit_diagram.py | 51 +++ .../braket/circuits/test_observable.py | 50 +++ .../braket/circuits/test_observables.py | 250 ++++++++++++ .../braket/circuits/test_result_type.py | 63 +++ .../braket/circuits/test_result_types.py | 123 +++++- tox.ini | 2 +- 22 files changed, 1767 insertions(+), 230 deletions(-) create mode 100644 test/integ_tests/test_adjoint_gradient.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 7be67e67..a23c8cb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,301 +93,301 @@ ### Bug Fixes and Other Changes - * copy profile name +- copy profile name ## v1.30.1 (2022-09-20) ### Bug Fixes and Other Changes - * update paths within docker image to posix +- update paths within docker image to posix ## v1.30.0 (2022-09-16) ### Features - * IonQ native gates +- IonQ native gates ## v1.29.4 (2022-09-08) ### Bug Fixes and Other Changes - * Simultaneous measurement of identity on all qubits +- Simultaneous measurement of identity on all qubits ## v1.29.3 (2022-09-05) ### Bug Fixes and Other Changes - * making local jobs stream output. +- making local jobs stream output. ## v1.29.2 (2022-08-25) ### Bug Fixes and Other Changes - * Updating documentation and type hints. +- Updating documentation and type hints. ## v1.29.1 (2022-08-18) ### Bug Fixes and Other Changes - * updating test cost tracking integ test to use M2. +- updating test cost tracking integ test to use M2. ## v1.29.0.post0 (2022-08-17) ### Testing and Release Infrastructure - * Avoid mutation of fixtures +- Avoid mutation of fixtures ## v1.29.0 (2022-08-10) ### Features - * Pauli strings +- Pauli strings ### Testing and Release Infrastructure - * Don't run tests on push to feature branches - * Add SF plugin to dependent tests +- Don't run tests on push to feature branches +- Add SF plugin to dependent tests ## v1.28.1 (2022-08-05) ### Bug Fixes and Other Changes - * fix future warning +- fix future warning ## v1.28.0 (2022-08-05) ### Features - * OpenQASM default IR and OpenQASM Local Simulator +- OpenQASM default IR and OpenQASM Local Simulator ### Bug Fixes and Other Changes - * update simulator version - * handle -0 edge case in result type hash +- update simulator version +- handle -0 edge case in result type hash ## v1.27.1 (2022-07-29) ### Bug Fixes and Other Changes - * customer script errors not shown when local jobs run from a notebook. +- customer script errors not shown when local jobs run from a notebook. ## v1.27.0 (2022-07-26) ### Features - * provide easy mechanism to update the local container when running local job. +- provide easy mechanism to update the local container when running local job. ## v1.26.2 (2022-07-21) ### Bug Fixes and Other Changes - * docs: Update README to include guidance for integrations +- docs: Update README to include guidance for integrations ## v1.26.1 (2022-07-19) ### Bug Fixes and Other Changes - * Lazily parse schemas for devices so getDevice calls do not rely … +- Lazily parse schemas for devices so getDevice calls do not rely … ## v1.26.0 (2022-07-18) ### Features - * SDK Cost Tracker +- SDK Cost Tracker ## v1.25.2 (2022-06-22) ### Bug Fixes and Other Changes - * Set the range for amazon-braket-schemas to >= 1.10.0 for the latest device schemas needed. +- Set the range for amazon-braket-schemas to >= 1.10.0 for the latest device schemas needed. ## v1.25.1.post0 (2022-06-17) ### Documentation Changes - * remove s3 references from README +- remove s3 references from README ## v1.25.1 (2022-06-16) ### Bug Fixes and Other Changes - * change failureReason string check to let test pass +- change failureReason string check to let test pass ## v1.25.0 (2022-06-08) ### Features - * Add method for updating the user agent for braket client +- Add method for updating the user agent for braket client ## v1.24.0 (2022-06-02) ### Features - * Add support for photonic computations +- Add support for photonic computations ## v1.23.2 (2022-05-24) ### Bug Fixes and Other Changes - * pin coverage dependency only for test extra +- pin coverage dependency only for test extra ## v1.23.1 (2022-05-20) ### Bug Fixes and Other Changes - * removing validation for disable_qubit_rewiring +- removing validation for disable_qubit_rewiring ## v1.23.0 (2022-05-19) ### Features - * allow job role to be set via env variable - * allow user to set region+endpoint through env variables +- allow job role to be set via env variable +- allow user to set region+endpoint through env variables ## v1.22.0 (2022-05-18) ### Features - * Noise models +- Noise models ## v1.21.1 (2022-05-17) ### Bug Fixes and Other Changes - * broken links for examples +- broken links for examples ## v1.21.0 (2022-05-10) ### Features - * Gate and Circuit inversion +- Gate and Circuit inversion ## v1.20.0 (2022-05-04) ### Features - * support local simulators for jobs +- support local simulators for jobs ## v1.19.0 (2022-04-19) ### Deprecations and Removals - * use to_unitary rather than as_unitary. +- use to_unitary rather than as_unitary. ### Bug Fixes and Other Changes - * align ECR gate definition with OQC - * add device arn error handling for badly formed ARNs +- align ECR gate definition with OQC +- add device arn error handling for badly formed ARNs ## v1.18.2 (2022-04-18) ### Bug Fixes and Other Changes - * stringify hyperparameters automatically +- stringify hyperparameters automatically ## v1.18.1 (2022-04-14) ### Bug Fixes and Other Changes - * add exception handling to local job test - * Run github workflows on feature branches +- add exception handling to local job test +- Run github workflows on feature branches ## v1.18.0.post0 (2022-04-06) ### Documentation Changes - * Specify DEVICE_REGIONS docs. +- Specify DEVICE_REGIONS docs. ## v1.18.0 (2022-03-07) ### Features - * Add support for running OpenQASM programs +- Add support for running OpenQASM programs ## v1.17.0 (2022-03-02) ### Features - * Add parameterized circuits +- Add parameterized circuits ## v1.16.1 (2022-03-01) ### Bug Fixes and Other Changes - * Add the OQC ARN to the integ tests +- Add the OQC ARN to the integ tests ## v1.16.0 (2022-02-27) ### Features - * LHR region configuration +- LHR region configuration ### Bug Fixes and Other Changes - * Oqc release +- Oqc release ## v1.15.0 (2022-02-15) ### Features - * Update region switching for regional device arns (#169) +- Update region switching for regional device arns (#169) ## v1.14.0.post0 (2022-02-11) ### Documentation Changes - * fix documentation on environment variable to match the code. +- fix documentation on environment variable to match the code. ## v1.14.0 (2022-02-02) ### Features - * adding TwoQubitPauliChannel +- adding TwoQubitPauliChannel ## v1.13.0 (2022-01-27) ### Features - * added controlled-sqrt-not gate +- added controlled-sqrt-not gate ## v1.12.0 (2022-01-25) ### Features - * Added is_available property to AwsDevice - * optimize IAM role retrieval +- Added is_available property to AwsDevice +- optimize IAM role retrieval ### Bug Fixes and Other Changes - * Enable jobs integration tests +- Enable jobs integration tests ## v1.11.1 (2021-12-09) ### Bug Fixes and Other Changes - * remove extraneous reference from local job container setup +- remove extraneous reference from local job container setup ## v1.11.0 (2021-12-02) ### Features - * Adding integration tests for DM1 +- Adding integration tests for DM1 ## v1.10.0 (2021-11-29) ### Features - * Add support for jobs +- Add support for jobs ### Bug Fixes and Other Changes - * Skip jobs integration tests +- Skip jobs integration tests ## v1.9.5.post0 (2021-11-04) ### Testing and Release Infrastructure - * Pin docutils<0.18 in doc requirements +- Pin docutils<0.18 in doc requirements ## v1.9.5 (2021-10-05) diff --git a/setup.cfg b/setup.cfg index 8d5e6ff2..6dd9b7de 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,9 +14,12 @@ include_trailing_comma = true [flake8] ignore = - E203, # not pep8, black adds whitespace before ':' - E231, # not pep8, https://www.python.org/dev/peps/pep-0008/#pet-peeves - W503, # not pep8, black adds line break before binary operator + # 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, max_line_length = 100 max-complexity = 10 exclude = diff --git a/setup.py b/setup.py index 3fde7540..bf2f5b4c 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.12.0", - "amazon-braket-default-simulator>=1.10.0", + "amazon-braket-schemas>=1.14.0", + "amazon-braket-default-simulator>=1.11.0", "oqpy~=0.1.1", "backoff", "boltons", diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index a5456bee..9bd4d9c7 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -102,6 +102,7 @@ def run( shots: Optional[int] = None, poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, + inputs: Optional[Dict[str, float]] = None, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTask: @@ -122,6 +123,9 @@ def run( in seconds. Default: 5 days. poll_interval_seconds (float): The polling interval for `AwsQuantumTask.result()`, in seconds. Default: 1 second. + 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: {}. Returns: AwsQuantumTask: An AwsQuantumTask that tracks the execution on the device. @@ -169,13 +173,14 @@ def run( shots if shots is not None else self._default_shots, poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds, + inputs=inputs, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) def run_batch( self, - task_specifications: List[ + task_specifications: Union[ Union[ Circuit, Problem, @@ -183,7 +188,17 @@ def run_batch( BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation, - ] + ], + List[ + Union[ + Circuit, + Problem, + OpenQasmProgram, + BlackbirdProgram, + PulseSequence, + AnalogHamiltonianSimulation, + ] + ], ], s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, shots: Optional[int] = None, @@ -191,14 +206,18 @@ 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, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTaskBatch: """Executes a batch of tasks in parallel Args: - task_specifications (List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation]]): # noqa - List of circuits or annealing problems to run on device. + task_specifications (Union[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, + PulseSequence, AnalogHamiltonianSimulation], List[Union[ Circuit, + Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence, + AnalogHamiltonianSimulation]]]): Single instance or list of circuits, annealing + problems, pulse sequences, or photonics program to run on device. s3_destination_folder (Optional[S3DestinationFolder]): The S3 location to save the tasks' results to. Default is `/tasks` if evoked outside of a Braket Job, `/jobs//tasks` if evoked inside of @@ -214,6 +233,9 @@ def run_batch( in seconds. Default: 5 days. poll_interval_seconds (float): The polling interval for results in seconds. Default: 1 second. + 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: {}. Returns: AwsQuantumTaskBatch: A batch containing all of the tasks run @@ -237,6 +259,7 @@ def run_batch( max_workers=max_connections, poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds, + inputs=inputs, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 003fd77d..5c3bc6af 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -51,7 +51,7 @@ from braket.device_schema.rigetti import RigettiDeviceParameters 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 Program as OpenQASMProgram from braket.pulse.pulse_sequence import PulseSequence from braket.schema_common import BraketSchemaBase from braket.task_result import ( @@ -91,7 +91,7 @@ def create( task_specification: Union[ Circuit, Problem, - OpenQasmProgram, + OpenQASMProgram, BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation, @@ -101,6 +101,7 @@ def create( device_parameters: Dict[str, Any] = None, disable_qubit_rewiring: bool = False, tags: Dict[str, str] = None, + inputs: Dict[str, float] = None, *args, **kwargs, ) -> AwsQuantumTask: @@ -113,8 +114,9 @@ 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 task to run on device. + task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, + PulseSequence, AnalogHamiltonianSimulation]): The specification of the task to run + on device. s3_destination_folder (AwsSession.S3DestinationFolder): NamedTuple, with bucket for index 0 and key for index 1, that specifies the Amazon S3 bucket and folder @@ -137,6 +139,10 @@ def create( An example would be: `{"state": "washington"}` + 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: {}. + Returns: AwsQuantumTask: AwsQuantumTask tracking the task execution on the device. @@ -160,13 +166,19 @@ def create( s3_destination_folder, shots if shots is not None else AwsQuantumTask.DEFAULT_SHOTS, ) + if tags is not None: create_task_kwargs.update({"tags": tags}) - if isinstance(task_specification, Circuit) and task_specification.parameters: - raise ValueError( - f"Cannot execute circuit with unbound parameters: " - f"{task_specification.parameters}" - ) + inputs = inputs or {} + + 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: + raise ValueError( + f"Cannot execute circuit with unbound parameters: " f"{unbounded_parameters}" + ) + return _create_internal( task_specification, aws_session, @@ -174,6 +186,7 @@ def create( device_arn, device_parameters or {}, disable_qubit_rewiring, + inputs, *args, **kwargs, ) @@ -411,7 +424,7 @@ async def _wait_for_completion( # 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; " + + 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." ) @@ -459,6 +472,7 @@ def _create_internal( device_arn: str, device_parameters: Union[dict, BraketSchemaBase], disable_qubit_rewiring: bool, + inputs: Dict[str, float], *args, **kwargs, ) -> AwsQuantumTask: @@ -473,30 +487,35 @@ def _( device_arn: str, _device_parameters: Union[dict, BraketSchemaBase], # Not currently used for OpenQasmProgram _disable_qubit_rewiring: bool, + inputs: Dict[str, float], *args, **kwargs, ) -> AwsQuantumTask: - create_task_kwargs.update({"action": OpenQasmProgram(source=pulse_sequence.to_ir()).json()}) + create_task_kwargs.update({"action": OpenQASMProgram(source=pulse_sequence.to_ir()).json()}) task_arn = aws_session.create_quantum_task(**create_task_kwargs) return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) @_create_internal.register def _( - open_qasm_program: OpenQasmProgram, + openqasm_program: OpenQASMProgram, aws_session: AwsSession, create_task_kwargs: Dict[str, Any], device_arn: str, - _device_parameters: Union[dict, BraketSchemaBase], # Not currently used for OpenQasmProgram + _device_parameters: Union[dict, BraketSchemaBase], # Not currently used for OpenQASMProgram _disable_qubit_rewiring: bool, + inputs: Dict[str, float], *args, **kwargs, ) -> AwsQuantumTask: - if open_qasm_program.inputs: - raise ValueError( - "OpenQASM Program inputs are only currently supported in the LocalSimulator." + if inputs: + inputs_copy = openqasm_program.inputs.copy() if openqasm_program.inputs is not None else {} + inputs_copy.update(inputs) + openqasm_program = OpenQASMProgram( + source=openqasm_program.source, + inputs=inputs_copy, ) - create_task_kwargs.update({"action": open_qasm_program.json()}) + 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) @@ -509,6 +528,7 @@ def _( device_arn: str, _device_parameters: Union[dict, BraketSchemaBase], _disable_qubit_rewiring: bool, + inputs: Dict[str, float], *args, **kwargs, ) -> AwsQuantumTask: @@ -525,6 +545,7 @@ def _( device_arn: str, device_parameters: Union[dict, BraketSchemaBase], # Not currently used for circuits disable_qubit_rewiring: bool, + inputs: Dict[str, float], *args, **kwargs, ) -> AwsQuantumTask: @@ -558,12 +579,21 @@ def _( qubit_reference_type=qubit_reference_type ) + openqasm_program = circuit.to_ir( + ir_type=IRType.OPENQASM, serialization_properties=serialization_properties + ) + + if inputs: + inputs_copy = openqasm_program.inputs.copy() if openqasm_program.inputs is not None else {} + inputs_copy.update(inputs) + openqasm_program = OpenQASMProgram( + source=openqasm_program.source, + inputs=inputs_copy, + ) + create_task_kwargs.update( { - "action": circuit.to_ir( - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - ).json(), + "action": openqasm_program.json(), "deviceParameters": device_parameters.json(), } ) @@ -584,6 +614,7 @@ def _( Dwave2000QDeviceParameters, ], _, + inputs: Dict[str, float], *args, **kwargs, ) -> AwsQuantumTask: @@ -607,6 +638,7 @@ def _( device_arn: str, device_parameters: dict, _, + inputs: Dict[str, float], *args, **kwargs, ) -> AwsQuantumTask: diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 3f6965b9..b2c5e47c 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -15,7 +15,8 @@ import time from concurrent.futures.thread import ThreadPoolExecutor -from typing import List, Set, Union +from itertools import repeat +from typing import Dict, List, Set, Union from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing import Problem @@ -44,8 +45,13 @@ def __init__( self, aws_session: AwsSession, device_arn: str, - task_specifications: List[ - Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation] + task_specifications: Union[ + Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation], + List[ + Union[ + Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation + ] + ], ], s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, @@ -53,6 +59,7 @@ 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, *aws_quantum_task_args, **aws_quantum_task_kwargs, ): @@ -61,8 +68,13 @@ def __init__( Args: aws_session (AwsSession): AwsSession to connect to AWS with. device_arn (str): The ARN of the quantum device. - task_specifications (List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation]]): # noqa - The specification of the task to run on device. + task_specifications ( + Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, + AnalogHamiltonianSimulation], List[Union[Circuit, + Problem, OpenQasmProgram, BlackbirdProgram, + AnalogHamiltonianSimulation]]): Single instance or list of circuits, annealing + problems, pulse sequences, or photonics program as specification of task + to run on device. 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 task results in. @@ -78,6 +90,9 @@ def __init__( in seconds. Default: 5 days. poll_interval_seconds (float): The polling interval for results in seconds. Default: 1 second. + inputs ([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: {}. """ self._tasks = AwsQuantumTaskBatch._execute( aws_session, @@ -89,6 +104,7 @@ def __init__( max_workers, poll_timeout_seconds, poll_interval_seconds, + inputs, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) @@ -105,6 +121,7 @@ def __init__( self._max_workers = max_workers self._poll_timeout_seconds = poll_timeout_seconds self._poll_interval_seconds = poll_interval_seconds + self._inputs = inputs self._aws_quantum_task_args = aws_quantum_task_args self._aws_quantum_task_kwargs = aws_quantum_task_kwargs @@ -112,24 +129,62 @@ def __init__( def _execute( aws_session: AwsSession, device_arn: str, - task_specifications: List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]], + task_specifications: Union[ + Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation], + List[ + Union[ + Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation + ] + ], + ], s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, max_parallel: int, 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, *args, **kwargs, ) -> List[AwsQuantumTask]: - for task_specification in task_specifications: - if isinstance(task_specification, Circuit) and task_specification.parameters: + inputs = inputs or {} + + single_task = isinstance( + task_specifications, + (Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation), + ) + single_input = isinstance(inputs, dict) + + if not single_task and not single_input: + if len(task_specifications) != len(inputs): raise ValueError( - f"Cannot execute circuit with unbound parameters: " - f"{task_specification.parameters}" + "Multiple inputs and task specifications must " "be equal in number." ) + if single_task: + task_specifications = repeat(task_specifications) + + if single_input: + inputs = repeat(inputs) + + 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) + + 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: + raise ValueError( + f"Cannot execute circuit with unbound parameters: " + f"{unbounded_parameters}" + ) + max_threads = min(max_parallel, max_workers) - remaining = [0 for _ in task_specifications] + remaining = [0 for _ in tasks_and_inputs] with ThreadPoolExecutor(max_workers=max_threads) as executor: task_futures = [ executor.submit( @@ -142,10 +197,11 @@ def _execute( shots, poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds, + inputs=input_map, *args, **kwargs, ) - for task in task_specifications + for task, input_map in tasks_and_inputs ] tasks = [future.result() for future in task_futures] return tasks @@ -155,20 +211,24 @@ def _create_task( remaining: List[int], aws_session: AwsSession, device_arn: str, - task_specifications: List[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram]], + task_specification: Union[ + 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, *args, **kwargs, ) -> AwsQuantumTask: task = AwsQuantumTask.create( aws_session, device_arn, - task_specifications, + task_specification, s3_destination_folder, shots, poll_interval_seconds=poll_interval_seconds, + inputs=inputs, *args, **kwargs, ) diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index 9438358a..af0b098c 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -13,6 +13,7 @@ from __future__ import annotations +from functools import reduce from typing import List, Tuple, Union import braket.circuits.circuit as cir @@ -124,7 +125,11 @@ def _ascii_group_items( ): qubit_range = circuit_qubits else: - qubit_range = QubitSet(range(min(item.target), max(item.target) + 1)) + if isinstance(item.target, list): + target = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) + else: + target = item.target + qubit_range = QubitSet(range(min(target), max(target) + 1)) found_grouping = False for group in groupings: @@ -242,9 +247,12 @@ def _ascii_diagram_column( after = ["|"] * (num_after - 1) + ([marker] if num_after else []) ascii_symbols = [ascii_symbol] + after else: - target_qubits = item.target + if isinstance(item.target, list): + target_qubits = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) + else: + target_qubits = item.target qubits = circuit_qubits.intersection( - set(range(min(item.target), max(item.target) + 1)) + set(range(min(target_qubits), max(target_qubits) + 1)) ) ascii_symbols = item.ascii_symbols diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index df06bbae..ee7ce3d3 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -41,7 +41,11 @@ from braket.circuits.parameterizable import Parameterizable from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput -from braket.circuits.result_type import ObservableResultType, ResultType +from braket.circuits.result_type import ( + ObservableParameterResultType, + ObservableResultType, + ResultType, +) from braket.circuits.serialization import ( IRType, OpenQASMSerializationProperties, @@ -292,9 +296,21 @@ def add_result_type( if result_type_to_add not in self._result_types: observable = Circuit._extract_observable(result_type_to_add) - if observable and self._observables_simultaneously_measurable: + # 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. + supports_basis_rotation_instructions = not isinstance( + result_type_to_add, ObservableParameterResultType + ) + if ( + observable + and self._observables_simultaneously_measurable + and supports_basis_rotation_instructions + ): # Only check if all observables can be simultaneously measured self._add_to_qubit_observable_mapping(observable, result_type_to_add.target) + self._add_to_qubit_observable_set(result_type_to_add) # using dict as an ordered set, value is arbitrary self._result_types[result_type_to_add] = None diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index bb9d5f2a..cbd4c8b5 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -13,6 +13,8 @@ from __future__ import annotations +import numbers +from copy import deepcopy from typing import List, Sequence, Tuple, Union import numpy as np @@ -37,6 +39,10 @@ class Observable(QuantumOperator): def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + self._coef = 1 + + def _unscaled(self): + return Observable(qubit_count=self.qubit_count, ascii_symbols=self.ascii_symbols) def to_ir( self, @@ -98,6 +104,10 @@ def _to_openqasm( """ raise NotImplementedError("to_openqasm has not been implemented yet.") + @property + def coefficient(self): + return self._coef + @property def basis_rotation_gates(self) -> Tuple[Gate, ...]: """Returns the basis rotation gates for this observable. @@ -143,6 +153,29 @@ def __matmul__(self, other) -> Observable.TensorProduct: raise ValueError("Can only perform tensor products between observables.") + def __mul__(self, other) -> Observable: + """Scalar multiplication""" + if isinstance(other, numbers.Number): + observable_copy = deepcopy(self) + observable_copy._coef *= other + return observable_copy + raise TypeError("Observable coefficients must be numbers.") + + def __rmul__(self, other) -> Observable: + return self * other + + def __add__(self, other): + if not isinstance(other, Observable): + raise ValueError("Can only perform addition between observables.") + + return Observable.Sum([self, other]) + + def __sub__(self, other): + if not isinstance(other, Observable): + raise ValueError("Can only perform subtraction between observables.") + + return self + (-1 * other) + def __repr__(self) -> str: return f"{self.name}('qubit_count': {self.qubit_count})" @@ -162,9 +195,19 @@ def __init__(self, ascii_symbols: Sequence[str]): super().__init__(qubit_count=1, ascii_symbols=ascii_symbols) self._eigenvalues = (1.0, -1.0) # immutable + def _unscaled(self): + return StandardObservable(ascii_symbols=self.ascii_symbols) + @property def eigenvalues(self) -> np.ndarray: - return np.array(self._eigenvalues) + return self.coefficient * np.array(self._eigenvalues) def eigenvalue(self, index: int) -> float: - return self._eigenvalues[index] + return self.coefficient * self._eigenvalues[index] + + @property + def ascii_symbols(self) -> Tuple[str, ...]: + return tuple( + f"{self.coefficient if self.coefficient != 1 else ''}{ascii_symbol}" + for ascii_symbol in self._ascii_symbols + ) diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 0af9d90e..4a5b7942 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -16,6 +16,8 @@ import functools import itertools import math +import numbers +from copy import deepcopy from typing import Dict, List, Tuple, Union import numpy as np @@ -41,20 +43,28 @@ def __init__(self): """ super().__init__(ascii_symbols=["H"]) + def _unscaled(self): + return H() + def _to_jaqcd(self) -> List[str]: + if self.coefficient != 1: + raise ValueError("Observable coefficients not supported with Jaqcd") return ["h"] 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"h({qubit_target})" + return f"{coef_prefix}h({qubit_target})" else: - return "h all" + return f"{coef_prefix}h all" def to_matrix(self) -> np.ndarray: - return 1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) + return self.coefficient * ( + 1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) + ) @property def basis_rotation_gates(self) -> Tuple[Gate, ...]: @@ -74,20 +84,26 @@ def __init__(self): """ super().__init__(qubit_count=1, ascii_symbols=["I"]) + def _unscaled(self): + return I() + def _to_jaqcd(self) -> List[str]: + if self.coefficient != 1: + raise ValueError("Observable coefficients not supported with Jaqcd") return ["i"] 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"i({qubit_target})" + return f"{coef_prefix}i({qubit_target})" else: - return "i all" + return f"{coef_prefix}i all" def to_matrix(self) -> np.ndarray: - return np.eye(2, dtype=complex) + return self.coefficient * np.eye(2, dtype=complex) @property def basis_rotation_gates(self) -> Tuple[Gate, ...]: @@ -99,10 +115,10 @@ def eigenvalues(self) -> np.ndarray: Returns: np.ndarray: The eigenvalues of this observable. """ - return np.ones(2) + return self.coefficient * np.ones(2) def eigenvalue(self, index: int) -> float: - return 1.0 + return self.coefficient * 1.0 Observable.register_observable(I) @@ -118,20 +134,26 @@ def __init__(self): """ super().__init__(ascii_symbols=["X"]) + def _unscaled(self): + return X() + def _to_jaqcd(self) -> List[str]: + if self.coefficient != 1: + raise ValueError("Observable coefficients not supported with Jaqcd") return ["x"] 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"x({qubit_target})" + return f"{coef_prefix}x({qubit_target})" else: - return "x all" + return f"{coef_prefix}x all" def to_matrix(self) -> np.ndarray: - return np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) + return self.coefficient * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) @property def basis_rotation_gates(self) -> Tuple[Gate, ...]: @@ -151,20 +173,26 @@ def __init__(self): """ super().__init__(ascii_symbols=["Y"]) + def _unscaled(self): + return Y() + def _to_jaqcd(self) -> List[str]: + if self.coefficient != 1: + raise ValueError("Observable coefficients not supported with Jaqcd") return ["y"] 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"y({qubit_target})" + return f"{coef_prefix}y({qubit_target})" else: - return "y all" + return f"{coef_prefix}y all" def to_matrix(self) -> np.ndarray: - return np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) + return self.coefficient * np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) @property def basis_rotation_gates(self) -> Tuple[Gate, ...]: @@ -184,20 +212,26 @@ def __init__(self): """ super().__init__(ascii_symbols=["Z"]) + def _unscaled(self): + return Z() + def _to_jaqcd(self) -> List[str]: + if self.coefficient != 1: + raise ValueError("Observable coefficients not supported with Jaqcd") return ["z"] 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"z({qubit_target})" + return f"{coef_prefix}z({qubit_target})" else: - return "z all" + return f"{coef_prefix}z all" def to_matrix(self) -> np.ndarray: - return np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) + return self.coefficient * np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) @property def basis_rotation_gates(self) -> Tuple[Gate, ...]: @@ -237,19 +271,45 @@ def __init__(self, observables: List[Observable]): if isinstance(obs, TensorProduct): for nested_obs in obs.factors: flattened_observables.append(nested_obs) + # 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) - self._factors = tuple(flattened_observables) qubit_count = sum([obs.qubit_count for obs in flattened_observables]) - display_name = "@".join([obs.ascii_symbols[0] 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) + display_name = ( + 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) + self._coef = coefficient + self._factors = unscaled_factors self._factor_dimensions = tuple( len(factor.to_matrix()) for factor in reversed(self._factors) ) self._eigenvalue_indices = {} self._all_eigenvalues = None + @property + def ascii_symbols(self) -> Tuple[str, ...]: + return tuple( + f"{self.coefficient if self.coefficient != 1 else ''}" + f"{'@'.join([obs.ascii_symbols[0] for obs in self.factors])}" + for _ in range(self.qubit_count) + ) + + def _unscaled(self): + copied = TensorProduct(observables=self.factors) + copied._coef = 1 + return copied + def _to_jaqcd(self) -> List[str]: + if self.coefficient != 1: + raise ValueError("Observable coefficients not supported with Jaqcd") ir = [] for obs in self.factors: ir.extend(obs.to_ir()) @@ -258,6 +318,7 @@ def _to_jaqcd(self) -> List[str]: def _to_openqasm( self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None ) -> str: + coef_prefix = f"{self.coefficient} * " if self.coefficient != 1 else "" factors = [] use_qubits = iter(target) for obs in self._factors: @@ -272,7 +333,7 @@ def _to_openqasm( serialization_properties=serialization_properties, ) ) - return " @ ".join(factors) + return f"{coef_prefix}{' @ '.join(factors)}" @property def factors(self) -> Tuple[Observable, ...]: @@ -280,7 +341,9 @@ def factors(self) -> Tuple[Observable, ...]: return self._factors def to_matrix(self) -> np.ndarray: - return functools.reduce(np.kron, [obs.to_matrix() for obs in self.factors]) + return self.coefficient * functools.reduce( + np.kron, [obs.to_matrix() for obs in self.factors] + ) @property def basis_rotation_gates(self) -> Tuple[Gate, ...]: @@ -303,7 +366,7 @@ def eigenvalues(self) -> np.ndarray: self._all_eigenvalues = TensorProduct._compute_eigenvalues( self._factors, self.qubit_count ) - return self._all_eigenvalues + return self.coefficient * self._all_eigenvalues def eigenvalue(self, index: int) -> float: """Returns the eigenvalue of this observable at the given index. @@ -332,7 +395,7 @@ def eigenvalue(self, index: int) -> float: quotient, remainder = divmod(quotient, self._factor_dimensions[i]) product *= self._factors[-i - 1].eigenvalue(remainder) self._eigenvalue_indices[index] = product - return self._eigenvalue_indices[index] + return self.coefficient * self._eigenvalue_indices[index] def __repr__(self): return "TensorProduct(" + ", ".join([repr(o) for o in self.factors]) + ")" @@ -365,6 +428,109 @@ def _compute_eigenvalues(observables: Tuple[Observable], num_qubits: int) -> np. Observable.register_observable(TensorProduct) +class Sum(Observable): + """Sum of observables""" + + def __init__(self, observables: List[Observable]): + """ + Args: + observables (List[Observable]): List of observables for Sum + + Examples: + >>> t1 = -3 * Observable.Y() + 2 * Observable.X() + Sum(X('qubit_count': 1), Y('qubit_count': 1)) + >>> t1.summands + (X('qubit_count': 1), Y('qubit_count': 1)) + """ + flattened_observables = [] + for obs in observables: + if isinstance(obs, Sum): + for nested_obs in obs.summands: + flattened_observables.append(nested_obs) + else: + flattened_observables.append(obs) + + self._summands = tuple(flattened_observables) + qubit_count = sum(obs.qubit_count for obs in flattened_observables) + display_name = "+".join([obs.ascii_symbols[0] for obs in flattened_observables]) + super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) + + @property + def ascii_symbols(self) -> Tuple[str, ...]: + return tuple( + "+".join([obs.ascii_symbols[0] for obs in self.summands]).replace("+-", "-") + for _ in range(self.qubit_count) + ) + + def __mul__(self, other) -> Observable: + """Scalar multiplication""" + if isinstance(other, numbers.Number): + sum_copy = deepcopy(self) + for i, obs in enumerate(sum_copy.summands): + sum_copy._summands[i]._coef *= other + return sum_copy + raise TypeError("Observable coefficients must be numbers.") + + def _to_jaqcd(self) -> List[str]: + raise NotImplementedError("Sum Observable is not supported in Jaqcd") + + def _to_openqasm( + self, + serialization_properties: OpenQASMSerializationProperties, + target: List[QubitSet] = None, + ) -> str: + if len(self.summands) != len(target): + 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)): + if term.qubit_count != len(term_target): + raise ValueError( + f"Invalid target for term {i} of Sum. " + f"Expected {term.qubit_count} targets, got {len(term_target)}" + ) + return " + ".join( + obs.to_ir( + target=term_target, + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + ) + for obs, term_target in zip(self.summands, target) + ).replace("+ -", "- ") + + @property + def summands(self) -> Tuple[Observable, ...]: + """Tuple[Observable]: The observables that comprise this sum.""" + return self._summands + + def to_matrix(self) -> np.ndarray: + raise NotImplementedError("Matrix operation is not supported for Sum") + + @property + def basis_rotation_gates(self) -> Tuple[Gate, ...]: + raise NotImplementedError("Basis rotation calculation not supported for Sum") + + @property + def eigenvalues(self) -> np.ndarray: + raise NotImplementedError("Eigenvalue calculation not supported for Sum") + + def eigenvalue(self, index: int) -> float: + raise NotImplementedError("Eigenvalue calculation not supported for Sum") + + def __repr__(self): + return "Sum(" + ", ".join([repr(o) for o in self.summands]) + ")" + + def __eq__(self, other): + return repr(self) == repr(other) + + @staticmethod + def _compute_eigenvalues(observables: Tuple[Observable], num_qubits: int) -> np.ndarray: + raise NotImplementedError("Eigenvalue calculation not supported for Sum") + + +Observable.register_observable(Sum) + + class Hermitian(Observable): """Hermitian matrix as an observable.""" @@ -400,7 +566,12 @@ def __init__(self, matrix: np.ndarray, display_name: str = "Hermitian"): super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) + def _unscaled(self): + return Hermitian(matrix=self._matrix, display_name=self.ascii_symbols[0]) + def _to_jaqcd(self) -> List[List[List[List[float]]]]: + if self.coefficient != 1: + raise ValueError("Observable coefficients not supported with Jaqcd") return [ [[[element.real, element.imag] for element in row] for row in self._matrix.tolist()] ] @@ -408,13 +579,17 @@ def _to_jaqcd(self) -> List[List[List[List[float]]]]: 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 = ", ".join( [serialization_properties.format_target(int(t)) for t in target] ) - return f"hermitian({self._serialized_matrix_openqasm_matrix()}) {qubit_target}" + return ( + f"{coef_prefix}" + f"hermitian({self._serialized_matrix_openqasm_matrix()}) {qubit_target}" + ) else: - return f"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()]) @@ -423,7 +598,7 @@ def _serialized_matrix_openqasm_matrix(self) -> str: return serialized def to_matrix(self) -> np.ndarray: - return self._matrix + return self.coefficient * self._matrix def __eq__(self, other) -> bool: return self.matrix_equivalence(other) diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index 392cbbe1..c0152d43 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -13,9 +13,11 @@ from __future__ import annotations -from typing import Any, Dict, List, Type +from typing import Any, Dict, List, Type, Union +from braket.circuits.free_parameter import FreeParameter from braket.circuits.observable import Observable +from braket.circuits.observables import Sum from braket.circuits.qubit import QubitInput from braket.circuits.qubit_set import QubitSet, QubitSetInput from braket.circuits.serialization import ( @@ -212,7 +214,20 @@ def __init__( f"Observable {self._observable} must only operate on 1 qubit for target=None" ) else: - if self._observable.qubit_count != len(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." + ) + 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" @@ -255,3 +270,64 @@ def __copy__(self) -> ObservableResultType: def __hash__(self) -> int: return super().__hash__() + + +class ObservableParameterResultType(ObservableResultType): + """ + 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. + If no parameters are specified, observable will be applied to all the free parameters. + + See :mod:`braket.circuits.observables` module for all of the supported observables. + """ + + def __init__( + self, + ascii_symbols: List[str], + observable: Observable, + target: QubitSetInput = None, + parameters: List[Union[str, FreeParameter]] = None, + ): + super().__init__(ascii_symbols, observable, target) + + self._parameters = ( + [(param.name if isinstance(param, FreeParameter) else param) for param in parameters] + if parameters + else parameters + ) + + """ + Args: + ascii_symbols (List[str]): ASCII string symbols for the result type. This is used when + printing a diagram of circuits. + observable (Observable): the observable for the result type. + target (QubitSetInput): Target qubits that the result type is requested for. + Default is `None`, which means the observable must only operate on 1 + qubit and it will be applied to all qubits in parallel. + parameters (List[Union[str, FreeParameter]]): List of string inputs or + FreeParameter objects. These inputs will be used as parameters for + gradient calculation. Default: `all`. + + Raises: + ValueError: if target=None and the observable's qubit count is not 1. + Or, if `target!=None` and the observable's qubit count and the number of target + qubits are not equal. Or, if `target!=None` and the observable's qubit count and + the number of `ascii_symbols` are not equal. + """ + + @property + def parameters(self) -> List[str]: + return self._parameters + + def __repr__(self) -> str: + return ( + f"{self.name}(observable={self.observable}, target={self.target}, " + f"parameters={self.parameters})" + ) + + def __copy__(self) -> ObservableResultType: + return type(self)( + observable=self.observable, target=self.target, parameters=self.parameters + ) diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 10594d3d..5e4bc974 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -14,13 +14,18 @@ from __future__ import annotations import re -from typing import List +from typing import List, 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.qubit_set import QubitSet, QubitSetInput -from braket.circuits.result_type import ObservableResultType, ResultType +from braket.circuits.result_type import ( + ObservableParameterResultType, + ObservableResultType, + ResultType, +) from braket.circuits.serialization import IRType, OpenQASMSerializationProperties """ @@ -162,6 +167,107 @@ def __hash__(self) -> int: ResultType.register_result_type(DensityMatrix) +class AdjointGradient(ObservableParameterResultType): + """ + The gradient of the expectation value of the provided observable, applied to target, + with respect to the given parameter. + """ + + def __init__( + self, + observable: Observable, + target: List[QubitSetInput] = None, + parameters: List[Union[str, FreeParameter]] = None, + ): + """ + Args: + observable (Observable): The expectation value of this observable is the function + against which parameters in the gradient are differentiated. + target (List[QubitSetInput]): Target qubits that the result type is requested for. + Each term in the target list should have the same number of qubits as the + corresponding term in the observable. Default is `None`, which means the + observable must operate only on 1 qubit and it is applied to all qubits + in parallel. + parameters (List[Union[str, FreeParameter]]): The free parameters in the circuit to + differentiate with respect to. Default: `all`. + + 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.AdjointGradient(observable=Observable.Z(), + target=0, parameters=["alpha", "beta"]) + + >>> tensor_product = Observable.Y() @ Observable.Z() + >>> hamiltonian = Observable.Y() @ Observable.Z() + Observable.H() + >>> ResultType.AdjointGradient( + >>> observable=tensor_product, + >>> target=[[0, 1], [2]], + >>> parameters=["alpha", "beta"], + >>> ) + """ + + super().__init__( + ascii_symbols=[ + f"AdjointGradient({obs_ascii})" for obs_ascii in observable.ascii_symbols + ], + observable=observable, + target=target, + parameters=parameters, + ) + + def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: + observable_ir = self.observable.to_ir( + target=self.target, + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + ) + + pragma_parameters = ", ".join(self.parameters) if self.parameters else "all" + + return ( + f"#pragma braket result adjoint_gradient " + f"expectation({observable_ir}) {pragma_parameters}" + ) + + @staticmethod + @circuit.subroutine(register=True) + def adjoint_gradient( + observable: Observable, + target: List[QubitSetInput] = None, + parameters: List[Union[str, FreeParameter]] = None, + ) -> ResultType: + """Registers this function into the circuit class. + + Args: + observable (Observable): The expectation value of this observable is the function + against which parameters in the gradient are differentiated. + target (List[QubitSetInput]): Target qubits that the result type is requested for. + Each term in the target list should have the same number of qubits as the + corresponding term in the observable. Default is `None`, which means the + observable must operate only on 1 qubit and it is applied to all qubits + in parallel. + parameters (List[Union[str, FreeParameter]]): The free parameters in the circuit to + differentiate with respect to. Default: `all`. + + Returns: + ResultType: gradient computed via adjoint differentiation as a requested result type + + 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] + >>> ) + """ + return ResultType.AdjointGradient( + observable=observable, target=target, parameters=parameters + ) + + +ResultType.register_result_type(AdjointGradient) + + class Amplitude(ResultType): """ The amplitude of the specified quantum states as a requested result type. diff --git a/test/integ_tests/test_adjoint_gradient.py b/test/integ_tests/test_adjoint_gradient.py new file mode 100644 index 00000000..99ab7dfe --- /dev/null +++ b/test/integ_tests/test_adjoint_gradient.py @@ -0,0 +1,175 @@ +# 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.aws import AwsQuantumTask, AwsQuantumTaskBatch +from braket.circuits import Circuit, Observable +from braket.parametric import FreeParameter + + +@pytest.fixture +def sv1_device(): + return "arn:aws:braket:::device/quantum-simulator/amazon/sv1" + + +def test_adjoint_gradient_quantum_task_with_nested_targets( + aws_session, s3_destination_folder, sv1_device +): + theta = FreeParameter("theta") + inputs = {"theta": 0.2} + circ = ( + Circuit() + .rx(0, theta) + .adjoint_gradient( + observable=(-2 * Observable.Y()) @ (3 * Observable.I()) + + 0.75 * Observable.Y() @ Observable.Z(), + target=[[0, 1], [2, 3]], + parameters=["theta"], + ) + ) + + expected_openqasm = ( + "OPENQASM 3.0;\n" + "input float theta;\n" + "qubit[4] q;\n" + "rx(theta) q[0];\n" + "#pragma braket result adjoint_gradient expectation(-6 * y(q[0]) @ i(q[1]) + 0.75 * " + "y(q[2]) @ z(q[3])) theta" + ) + + gradient_task = AwsQuantumTask.create( + aws_session=aws_session, + task_specification=circ, + s3_destination_folder=s3_destination_folder, + device_arn=sv1_device, + shots=0, + inputs=inputs, + ) + + assert gradient_task.result().additional_metadata.action.source == expected_openqasm + assert gradient_task.result().values == [ + {"expectation": 1.1920159847703675, "gradient": {"theta": 5.880399467047451}} + ] + assert gradient_task.result().result_types[0].type.observable == "-6 * y @ i + 0.75 * y @ z" + assert gradient_task.result().additional_metadata.action.inputs == inputs + + +def test_adjoint_gradient_with_standard_observable_terms( + aws_session, s3_destination_folder, sv1_device +): + theta = FreeParameter("theta") + inputs = {"theta": 0.2} + circ = ( + Circuit() + .rx(0, theta) + .adjoint_gradient( + observable=(2 * Observable.X()) + (3 * Observable.Y()) - Observable.Z(), + target=[[0], [1], [2]], + parameters=["theta"], + ) + ) + + expected_openqasm = ( + "OPENQASM 3.0;\n" + "input float theta;\n" + "qubit[3] q;\n" + "rx(theta) q[0];\n" + "#pragma braket result adjoint_gradient expectation(2 * x(q[0]) + 3 * y(q[1]) " + "- 1 * z(q[2])) theta" + ) + + gradient_task = AwsQuantumTask.create( + aws_session=aws_session, + task_specification=circ, + s3_destination_folder=s3_destination_folder, + device_arn=sv1_device, + shots=0, + inputs=inputs, + ) + + assert gradient_task.result().additional_metadata.action.source == expected_openqasm + assert gradient_task.result().values == [{"expectation": -1.0, "gradient": {"theta": 0.0}}] + assert gradient_task.result().result_types[0].type.observable == "2 * x + 3 * y - 1 * z" + assert gradient_task.result().additional_metadata.action.inputs == inputs + + +def test_adjoint_gradient_with_batch_circuits(aws_session, s3_destination_folder, sv1_device): + theta = FreeParameter("theta") + + inputs = {"theta": 0.2} + circ_1 = ( + Circuit() + .rx(0, theta) + .adjoint_gradient( + observable=(2 * Observable.Y()) @ (3 * Observable.I()), + target=[0, 1], + parameters=["theta"], + ) + ) + circ_2 = ( + Circuit() + .rx(0, theta) + .adjoint_gradient( + observable=(-2 * Observable.Y()) @ (3 * Observable.I()) + + 0.75 * Observable.Y() @ Observable.Z(), + target=[[0, 1], [0, 1]], + parameters=["theta"], + ) + ) + + expected_openqasm = [ + ( + "OPENQASM 3.0;\n" + "input float theta;\n" + "qubit[2] q;\n" + "rx(theta) q[0];\n" + "#pragma braket result adjoint_gradient expectation(6 * y(q[0]) @ i(q[1])) theta" + ), + ( + "OPENQASM 3.0;\n" + "input float theta;\n" + "qubit[2] q;\n" + "rx(theta) q[0];\n" + "#pragma braket result adjoint_gradient expectation(-6 * y(q[0]) @ i(q[1]) + 0.75 * " + "y(q[0]) @ z(q[1])) theta" + ), + ] + + expected_result_values = [ + [{"expectation": -1.1920159847703675, "gradient": {"theta": -5.880399467047451}}], + [{"expectation": 1.0430139866740715, "gradient": {"theta": 5.145349533666519}}], + ] + expected_observables = ["6 * y @ i", "-6 * y @ i + 0.75 * y @ z"] + + gradient_batch_tasks = AwsQuantumTaskBatch( + aws_session=aws_session, + device_arn=sv1_device, + task_specifications=[circ_1, circ_2], + shots=0, + max_parallel=1, + s3_destination_folder=s3_destination_folder, + inputs=inputs, + ) + + for i in range(2): + assert ( + gradient_batch_tasks.tasks[i].result().additional_metadata.action.source + == expected_openqasm[i] + ) + assert gradient_batch_tasks.tasks[i].result().values == expected_result_values[i] + assert ( + gradient_batch_tasks.tasks[i].result().result_types[0].type.observable + == expected_observables[i] + ) + assert gradient_batch_tasks.tasks[i].result().additional_metadata.action.inputs == inputs diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index d4788cb8..47e85b5d 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -199,6 +199,7 @@ def run_and_assert( shots, # Treated as positional arg poll_timeout_seconds, # Treated as positional arg poll_interval_seconds, # Treated as positional arg + inputs, # Treated as positional arg extra_args, extra_kwargs, ): @@ -214,6 +215,8 @@ def run_and_assert( run_args.append(poll_timeout_seconds) if poll_interval_seconds is not None: run_args.append(poll_interval_seconds) + if inputs is not None: + run_args.append(inputs) run_args += extra_args if extra_args else [] run_kwargs = extra_kwargs or {} @@ -229,6 +232,7 @@ def run_and_assert( shots, poll_timeout_seconds, poll_interval_seconds, + inputs, extra_args, extra_kwargs, ) @@ -253,6 +257,7 @@ def run_batch_and_assert( max_connections, poll_timeout_seconds, poll_interval_seconds, + inputs, extra_args, extra_kwargs, ): @@ -275,6 +280,8 @@ def run_batch_and_assert( run_args.append(poll_timeout_seconds) if poll_interval_seconds is not None: run_args.append(poll_interval_seconds) + if inputs is not None: + run_args.append(inputs) run_args += extra_args if extra_args else [] run_kwargs = extra_kwargs or {} @@ -290,6 +297,7 @@ def run_batch_and_assert( shots, poll_timeout_seconds, poll_interval_seconds, + inputs, extra_args, extra_kwargs, ) @@ -298,9 +306,12 @@ def run_batch_and_assert( # aws_session_mock.call_args.kwargs syntax is newer than Python 3.7 assert aws_session_mock.call_args[1]["config"].max_pool_connections == max_pool_connections - aws_quantum_task_mock.assert_called_with( + aws_quantum_task_mock.assert_any_call( new_session_mock, device.arn, circuits[0], *create_args, **create_kwargs ) + aws_quantum_task_mock.assert_any_call( + new_session_mock, device.arn, circuits[1], *create_args, **create_kwargs + ) def _create_task_args_and_kwargs( @@ -312,6 +323,7 @@ def _create_task_args_and_kwargs( shots, poll_timeout_seconds, poll_interval_seconds, + inputs, extra_args, extra_kwargs, ): @@ -329,6 +341,7 @@ def _create_task_args_and_kwargs( "poll_interval_seconds": poll_interval_seconds if poll_interval_seconds is not None else default_poll_interval, + "inputs": inputs, } ) 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 b781cd68..ef1baed7 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -73,7 +73,6 @@ "deviceParameters": {}, } - MOCK_GATE_MODEL_QPU_CAPABILITIES_1 = RigettiDeviceCapabilities.parse_obj( MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_1 ) @@ -256,36 +255,6 @@ def test_gate_model_sim_schema(): ) -@pytest.fixture -def parameterized_quantum_task(aws_session, s3_destination_folder): - theta = FreeParameter("theta") - circ = Circuit().ry(angle=theta, target=0) - return AwsQuantumTask.create( - device_arn="arn:aws:braket:::device/quantum-simulator/amazon/sv1", - aws_session=aws_session, - poll_timeout_seconds=2, - task_specification=circ, - shots=10, - s3_destination_folder=s3_destination_folder, - ) - - -@pytest.fixture -def parameterized_quantum_task_batch(aws_session, s3_destination_folder): - theta = FreeParameter("theta") - circ_1 = Circuit().ry(angle=3, target=0) - circ_2 = Circuit().ry(angle=theta, target=0) - return AwsQuantumTaskBatch( - device_arn="arn:aws:braket:::device/quantum-simulator/amazon/sv1", - aws_session=aws_session, - poll_timeout_seconds=2, - task_specifications=[circ_1, circ_2], - shots=1, - s3_destination_folder=s3_destination_folder, - max_parallel=100, - ) - - @pytest.fixture( params=[ "arn:aws:braket:us-west-1::device/quantum-simulator/amazon/sim", @@ -316,6 +285,19 @@ def circuit(request): return request.getfixturevalue(request.param) +@pytest.fixture +def multiple_circuit_inputs(): + theta = FreeParameter("theta") + beta = FreeParameter("beta") + return Circuit().ry(angle=theta, target=0).rx(angle=beta, target=1) + + +@pytest.fixture() +def single_circuit_input(): + theta = FreeParameter("theta") + return Circuit().ry(angle=theta, target=0) + + @pytest.fixture def aws_session(): _boto_session = Mock() @@ -745,38 +727,273 @@ def test_run_no_extra(aws_quantum_task_mock, device, circuit): ) -@pytest.mark.xfail(raises=ValueError) -def test_run_param_circuit(parameterized_quantum_task, device, s3_destination_folder): - theta = FreeParameter("theta") - circ = Circuit().ry(angle=theta, target=0) +@patch("braket.aws.aws_quantum_task.AwsQuantumTask") +def test_run_param_circuit_with_no_inputs( + aws_quantum_task_mock, single_circuit_input, device, s3_destination_folder +): + cannot_execute_with_unbound = "Cannot execute circuit with unbound parameters: {'theta'}" + + with pytest.raises(ValueError, match=cannot_execute_with_unbound): + _run_and_assert( + aws_quantum_task_mock, + device, + single_circuit_input, + s3_destination_folder, + 10, + 86400, + 0.25, + {}, + ) + + +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_param_circuit_with_inputs( + aws_quantum_task_mock, single_circuit_input, device, s3_destination_folder +): + inputs = {"theta": 0.2} + _run_and_assert( - parameterized_quantum_task, + aws_quantum_task_mock, device, - circ, + single_circuit_input, s3_destination_folder, - shots=10, + 10, + 86400, + 0.25, + inputs, ) -@pytest.mark.xfail(raises=ValueError) -def test_run_batch_param_circuit( - parameterized_quantum_task_batch, aws_session, device, s3_destination_folder +@patch("braket.aws.aws_session.boto3.Session") +@patch("braket.aws.aws_session.AwsSession") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_param_circuit_with_inputs_batch_task( + aws_quantum_task_mock, + aws_session_mock, + boto_session_mock, + single_circuit_input, + device, + s3_destination_folder, ): - theta = FreeParameter("theta") - circ_1 = Circuit().ry(angle=3, target=0) - circ_2 = Circuit().ry(angle=theta, target=0) - circuits = [circ_1, circ_2] + inputs = {"theta": 0.2} + circ_1 = Circuit().rx(angle=0.2, target=0) + circuits = [circ_1, single_circuit_input] _run_batch_and_assert( - parameterized_quantum_task_batch, - aws_session, + aws_quantum_task_mock, + aws_session_mock, device, circuits, s3_destination_folder, - shots=10, + 10, + 20, + 50, + 43200, + 0.25, + inputs, ) +@patch("braket.aws.aws_quantum_task.AwsQuantumTask") +def test_run_param_circuit_with_invalid_input( + aws_quantum_task_mock, single_circuit_input, device, s3_destination_folder +): + inputs = {"beta": 0.2} + cannot_execute_with_unbound = "Cannot execute circuit with unbound parameters: {'theta'}" + with pytest.raises(ValueError, match=cannot_execute_with_unbound): + _run_and_assert( + aws_quantum_task_mock, + device, + single_circuit_input, + s3_destination_folder, + 10, + 86400, + 0.25, + inputs, + ) + + +@patch("braket.aws.aws_session.boto3.Session") +@patch("braket.aws.aws_session.AwsSession") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_batch_param_circuit_with_no_inputs( + aws_quantum_task_mock, + aws_session_mock, + boto_session_mock, + single_circuit_input, + device, + s3_destination_folder, +): + circ_1 = Circuit().ry(angle=3, target=0) + circuits = [circ_1, single_circuit_input] + + cannot_execute_with_unbound = "Cannot execute circuit with unbound parameters: {'theta'}" + + with pytest.raises(ValueError, match=cannot_execute_with_unbound): + _run_batch_and_assert( + aws_quantum_task_mock, + aws_session_mock, + device, + circuits, + s3_destination_folder, + 1000, + 20, + 50, + 43200, + 0.25, + {}, + ) + + +@patch("braket.aws.aws_session.boto3.Session") +@patch("braket.aws.aws_session.AwsSession") +@patch("braket.aws.aws_quantum_task_batch.AwsQuantumTask.create") +def test_run_multi_param_batch_circuit_with_input( + aws_quantum_task_mock, + aws_session_mock, + boto_session_mock, + multiple_circuit_inputs, + device, + s3_destination_folder, +): + inputs = {"beta": 0.2} + circ_1 = Circuit().ry(angle=3, target=0) + circuits = [circ_1, multiple_circuit_inputs] + + cannot_execute_with_unbound = "Cannot execute circuit with unbound parameters: {'theta'}" + with pytest.raises(ValueError, match=cannot_execute_with_unbound): + _run_batch_and_assert( + aws_quantum_task_mock, + aws_session_mock, + device, + circuits, + s3_destination_folder, + 1000, + 20, + 50, + 43200, + 0.25, + inputs, + ) + + +@patch("braket.aws.aws_session.boto3.Session") +@patch("braket.aws.aws_session.AwsSession") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_param_batch_circuit_with_invalid_input( + aws_quantum_task_mock, + aws_session_mock, + boto_session_mock, + single_circuit_input, + aws_session, + device, + s3_destination_folder, +): + inputs = {"beta": 0.2} + circ_1 = Circuit().ry(angle=3, target=0) + circuits = [circ_1, single_circuit_input] + cannot_execute_with_unbound = "Cannot execute circuit with unbound parameters: {'theta'}" + with pytest.raises(ValueError, match=cannot_execute_with_unbound): + _run_batch_and_assert( + aws_quantum_task_mock, + aws_session_mock, + device, + circuits, + s3_destination_folder, + 1000, + 20, + 50, + 43200, + 0.25, + inputs, + ) + + +@patch("braket.aws.aws_session.boto3.Session") +@patch("braket.aws.aws_session.AwsSession") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_batch_circuit_with_task_and_input_mismatch( + aws_quantum_task_mock, + aws_session_mock, + boto_session_mock, + single_circuit_input, + openqasm_program, + device, + s3_destination_folder, +): + 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." + + with pytest.raises(ValueError, match=wrong_number_of_inputs): + _run_batch_and_assert( + aws_quantum_task_mock, + aws_session_mock, + device, + task_specifications, + s3_destination_folder, + 1000, + 20, + 50, + 43200, + 0.25, + inputs, + ) + + +@patch("braket.aws.aws_session.boto3.Session") +@patch("braket.aws.aws_session.AwsSession") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_multiple_task_multiple_batch_inputs_invalid_config( + aws_quantum_task_mock, aws_session_mock, boto_session_mock, device, s3_destination_folder +): + theta = FreeParameter("theta") + multiple_task = [Circuit().rx(angle=theta, target=1)] * 2 + multiple_inputs = [{"theta": 0.2}, {"beta": 0.3}] + cannot_execute_with_unbound = "Cannot execute circuit with unbound parameters: {'theta'}" + with pytest.raises(ValueError, match=cannot_execute_with_unbound): + _run_batch_and_assert( + aws_quantum_task_mock, + aws_session_mock, + device, + multiple_task, + s3_destination_folder, + 1000, + 20, + 50, + 43200, + 0.25, + multiple_inputs, + ) + + +@patch("braket.aws.aws_session.boto3.Session") +@patch("braket.aws.aws_session.AwsSession") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_single_task_single_input_batch_missing_input( + aws_quantum_task_mock, aws_session_mock, boto_session_mock, device, s3_destination_folder +): + theta = FreeParameter("theta") + task = Circuit().rx(angle=theta, target=0) + inputs = {"beta": 0.2} + cannot_execute_with_unbound = "Cannot execute circuit with unbound parameters: {'theta'}" + with pytest.raises(ValueError, match=cannot_execute_with_unbound): + _run_batch_and_assert( + aws_quantum_task_mock, + aws_session_mock, + device, + task, + s3_destination_folder, + 1000, + 20, + 50, + 43200, + 0.25, + inputs, + ) + + @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_positional_args(aws_quantum_task_mock, device, circuit, s3_destination_folder): _run_and_assert( @@ -836,26 +1053,6 @@ def test_default_bucket_not_called(aws_quantum_task_mock, device, circuit, s3_de None, None, None, - ) - device._aws_session.default_bucket.assert_not_called() - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_default_bucket_not_called(aws_quantum_task_mock, device, circuit, s3_destination_folder): - device = device(RIGETTI_ARN) - run_and_assert( - aws_quantum_task_mock, - device, - MOCK_DEFAULT_S3_DESTINATION_FOLDER, - AwsDevice.DEFAULT_SHOTS_QPU, - AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, - AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, - circuit, - s3_destination_folder, - None, - None, - None, - None, None, ) device._aws_session.default_bucket.assert_not_called() @@ -888,6 +1085,7 @@ def test_run_with_positional_args_and_kwargs( 100, 86400, 0.25, + {}, ["foo"], {"bar": 1, "baz": 2}, ) @@ -907,13 +1105,25 @@ def test_run_env_variables(aws_quantum_task_mock, device, circuit, arn): @patch("braket.aws.aws_session.AwsSession") @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_batch_no_extra( - aws_quantum_task_mock, aws_session_mock, boto_session_mock, device, circuit + aws_quantum_task_mock, + aws_session_mock, + boto_session_mock, + device, + circuit, + s3_destination_folder, ): _run_batch_and_assert( aws_quantum_task_mock, aws_session_mock, device, [circuit for _ in range(10)], + s3_destination_folder, + 1000, + 20, + 50, + 43200, + 0.25, + {}, ) @@ -935,6 +1145,11 @@ def test_run_batch_with_shots( [circuit for _ in range(10)], s3_destination_folder, 1000, + 20, + 50, + 43200, + 0.25, + {}, ) @@ -958,6 +1173,9 @@ def test_run_batch_with_max_parallel_and_kwargs( 1000, 20, 50, + 43200, + 0.25, + inputs={"theta": 0.2}, extra_kwargs={"bar": 1, "baz": 2}, ) @@ -981,6 +1199,7 @@ def _run_and_assert( shots=None, # Treated as positional arg poll_timeout_seconds=None, # Treated as positional arg poll_interval_seconds=None, # Treated as positional arg + inputs=None, # Treated as positional arg extra_args=None, extra_kwargs=None, ): @@ -996,6 +1215,7 @@ def _run_and_assert( shots, poll_timeout_seconds, poll_interval_seconds, + inputs, extra_args, extra_kwargs, ) @@ -1012,6 +1232,7 @@ def _run_batch_and_assert( max_connections=None, # Treated as positional arg poll_timeout_seconds=None, # Treated as a positional arg poll_interval_seconds=None, # Treated as positional arg + inputs=None, # Treated as positional arg extra_args=None, extra_kwargs=None, ): @@ -1030,6 +1251,7 @@ def _run_batch_and_assert( max_connections, poll_timeout_seconds, poll_interval_seconds, + inputs, extra_args, extra_kwargs, ) 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 b30d093c..3adb1fe7 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -44,7 +44,7 @@ from braket.device_schema.rigetti import RigettiDeviceParameters 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 Program as OpenQASMProgram from braket.pulse import Frame, Port, PulseSequence from braket.tasks import ( AnalogHamiltonianSimulationQuantumTaskResult, @@ -113,7 +113,7 @@ def problem(): @pytest.fixture def openqasm_program(): - return OpenQasmProgram(source="OPENQASM 3.0; h $0;") + return OpenQASMProgram(source="OPENQASM 3.0; h $0;") @pytest.fixture @@ -497,7 +497,7 @@ def test_create_pulse_sequence(aws_session, arn, pulse_sequence): "}", ] ) - expected_program = OpenQasmProgram(source=expected_openqasm) + expected_program = OpenQASMProgram(source=expected_openqasm) aws_session.create_quantum_task.return_value = arn AwsQuantumTask.create(aws_session, SIMULATOR_ARN, pulse_sequence, S3_TARGET, 10) @@ -527,7 +527,8 @@ def test_create_pulse_gate_circuit( "b[1] = measure $1;", ) ) - expected_program = OpenQasmProgram(source=expected_openqasm, inputs={}) + + expected_program = OpenQASMProgram(source=expected_openqasm, inputs={}) aws_session.create_quantum_task.return_value = arn AwsQuantumTask.create(aws_session, device_arn, pulse_gate_circuit, S3_TARGET, 10) @@ -554,11 +555,40 @@ def test_from_circuit_with_shots(device_arn, device_parameters_class, aws_sessio task = AwsQuantumTask.create(aws_session, device_arn, circuit, S3_TARGET, shots) assert task == AwsQuantumTask(mocked_task_arn, aws_session) + program = circuit.to_ir(ir_type=IRType.OPENQASM) + assert program.inputs == {} _assert_create_quantum_task_called_with( aws_session, device_arn, - circuit.to_ir(ir_type=IRType.OPENQASM).json(), + program.json(), + S3_TARGET, + shots, + device_parameters_class( + paradigmParameters=GateModelParameters( + qubitCount=circuit.qubit_count, disableQubitRewiring=False + ) + ), + ) + + +@pytest.mark.parametrize("device_arn,device_parameters_class", DEVICE_PARAMETERS) +def test_from_circuit_with_input_params(device_arn, device_parameters_class, aws_session, circuit): + mocked_task_arn = "task-arn-1" + aws_session.create_quantum_task.return_value = mocked_task_arn + shots = 53 + inputs = {"beta": 3} + + task = AwsQuantumTask.create(aws_session, device_arn, circuit, S3_TARGET, shots, inputs=inputs) + assert task == AwsQuantumTask(mocked_task_arn, aws_session) + program = circuit.to_ir(ir_type=IRType.OPENQASM) + assert program.inputs == {} + program.inputs.update(inputs) + + _assert_create_quantum_task_called_with( + aws_session, + device_arn, + program.json(), S3_TARGET, shots, device_parameters_class( @@ -962,7 +992,13 @@ def _init_and_add_to_list(aws_session, arn, task_list): def _assert_create_quantum_task_called_with( - aws_session, arn, task_description, s3_results_prefix, shots, device_parameters=None, tags=None + aws_session, + arn, + task_description, + s3_results_prefix, + shots, + device_parameters=None, + tags=None, ): test_kwargs = { "deviceArn": arn, @@ -971,6 +1007,7 @@ def _assert_create_quantum_task_called_with( "action": task_description, "shots": shots, } + if device_parameters is not None: test_kwargs.update({"deviceParameters": device_parameters.json(exclude_none=True)}) if tags is not None: @@ -990,18 +1027,35 @@ def _mock_s3(aws_session, result): aws_session.retrieve_s3_object_body.return_value = result -def test_no_program_inputs(aws_session): - openqasm_program = OpenQasmProgram( - source=""" - qubit q; - h q; - """, - inputs={"x": 1}, - ) +@pytest.mark.parametrize("source_input", [{}, {"gamma": 0.15}, None]) +@pytest.mark.parametrize("device_arn", DEVICE_PARAMETERS[0]) +def test_program_inputs(aws_session, device_arn, source_input): + bell_qasm = """ + OPENQASM 3; + input float theta; + qubit[2] q; + bit[2] c; + h q[0]; + cnot q[0], q[1]; + c = measure q; + """ + openqasm_program = OpenQASMProgram(source=bell_qasm, inputs=source_input) aws_session.create_quantum_task.return_value = arn shots = 21 - only_for_local_sim = ( - "OpenQASM Program inputs are only currently supported in the LocalSimulator." + inputs = {"theta": 0.2} + AwsQuantumTask.create( + aws_session, device_arn, openqasm_program, S3_TARGET, shots, inputs=inputs + ) + + assert openqasm_program.inputs == source_input + inputs_copy = openqasm_program.inputs.copy() if openqasm_program.inputs is not None else {} + inputs_copy.update(inputs) + openqasm_program = OpenQASMProgram(source=openqasm_program.source, inputs=inputs_copy) + + _assert_create_quantum_task_called_with( + aws_session, + device_arn, + openqasm_program.json(), + S3_TARGET, + shots, ) - with pytest.raises(ValueError, match=only_for_local_sim): - AwsQuantumTask.create(aws_session, SIMULATOR_ARN, openqasm_program, S3_TARGET, shots) 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 468b08bf..37cfc3b8 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -687,3 +687,54 @@ def test_pulse_gate_multi_qubit_circuit(): def _assert_correct_diagram(circ, expected): assert AsciiCircuitDiagram.build_diagram(circ) == "\n".join(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(-6Y@I-0.75Y@Z)-", + " | ", + "q1 : -H-Expectation(-6Y@I-0.75Y@Z)-", + "", + "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-C-Rx(theta)-AdjointGradient(8e-05Z+48X@Y)-", + " | | ", + "q1 : ---X-----------AdjointGradient(8e-05Z+48X@Y)-", + " | ", + "q2 : ---------------AdjointGradient(8e-05Z+48X@Y)-", + "", + "T : |0|1| 2 | Result Types |", + "", + "Unassigned parameters: [theta].", + ) + _assert_correct_diagram(circ, expected) diff --git a/test/unit_tests/braket/circuits/test_observable.py b/test/unit_tests/braket/circuits/test_observable.py index 21e3cf00..d732a0d7 100644 --- a/test/unit_tests/braket/circuits/test_observable.py +++ b/test/unit_tests/braket/circuits/test_observable.py @@ -28,6 +28,16 @@ def standard_observable(): return StandardObservable(ascii_symbols=["foo"]) +@pytest.fixture +def unscaled_observable(observable): + return observable._unscaled() + + +@pytest.fixture +def unscaled_standard_observable(standard_observable): + return standard_observable._unscaled() + + def test_is_operator(observable): assert isinstance(observable, QuantumOperator) @@ -122,6 +132,7 @@ def test_eigenvalue_not_implemented_by_default(observable): def test_str(observable): expected = "{}('qubit_count': {})".format(observable.name, observable.qubit_count) assert str(observable) == expected + assert observable.coefficient == 1 def test_register_observable(): @@ -162,5 +173,44 @@ def test_standard_observable_subclass_of_observable(standard_observable): assert isinstance(standard_observable, Observable) +def test_unscaled_standard_observable_subclass_of_observable(unscaled_standard_observable): + assert isinstance(unscaled_standard_observable, Observable) + + def test_standard_observable_eigenvalues(standard_observable): assert np.allclose(standard_observable.eigenvalues, np.array([1, -1])) + + +def test_unscaled_standard_observable_eigenvalues(unscaled_standard_observable): + assert np.allclose(unscaled_standard_observable.eigenvalues, np.array([1, -1])) + + +def test_observable_coeffs(observable): + observable = 2 * observable + assert observable.coefficient == 2 + unscaled_observable = observable._unscaled() + assert unscaled_observable.coefficient == 1 + assert isinstance(unscaled_observable, 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): + 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): + 2 * observable - parameter + + +def test_sum_observable_with_subtraction(): + obs1 = 6 * Observable.X() + obs2 = -4 * Observable.Y() + result = obs1 - obs2 + assert isinstance(result, Observable.Sum) + assert result.qubit_count == 2 + assert result.ascii_symbols == ("6X+4Y", "6X+4Y") diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index 49e643e5..22c0ab9f 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -14,6 +14,7 @@ import math import numpy as np +import numpy.testing as npt import pytest from braket.circuits import Gate, Observable @@ -212,6 +213,74 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv "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()), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3], + "6 * z($3)", + ), + ( + (2 * Observable.I()) @ (2 * Observable.Hermitian(np.eye(4))), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [3, 0, 1], + "4 * 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", + ), + ( + Observable.Z() + 2 * Observable.H(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [[3], [4]], + "z($3) + 2 * h($4)", + ), + ( + 3 * (Observable.H() + 2 * Observable.X()), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [[3], [0]], + "3 * h($3) + 6 * x($0)", + ), + ( + 3 * (Observable.H() + 2 * Observable.H()), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [[3], [3]], + "3 * h($3) + 6 * h($3)", + ), + ( + 3 * (Observable.H() + 2 * Observable.H()), + 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(), + 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(), + 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()))), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [[0], [1, 2]], + "8 * z($0) + 48 * x($1) @ y($2)", + ), ], ) def test_observables_to_ir_openqasm(observable, serialization_properties, target, expected_ir): @@ -223,6 +292,100 @@ def test_observables_to_ir_openqasm(observable, serialization_properties, target ) +@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()]), + ], +) +def test_observable_coef_jaqcd(observable): + coef_not_supported_with_jaqcd = "Observable coefficients not supported with Jaqcd" + with pytest.raises(ValueError, match=coef_not_supported_with_jaqcd): + observable.to_ir(target=0, ir_type=IRType.JAQCD) + + +@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()), + ], +) +def test_invalid_scalar_multiplication(expression, observable): + with pytest.raises(TypeError, match="Observable coefficients must be numbers."): + expression * observable + + +@pytest.mark.parametrize( + "observable, matrix", + [ + ( + (-3 * Observable.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(), + np.array([[3.0 + 0.0j, 0.0 + 0.0j], [0.0 + 0.0j, -3.0 + 0.0j]]), + ), + ( + (2 * Observable.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(), + np.array([[0.0 + 0.0j, 1.2 + 0.0j], [1.2 + 0.0j, 0.0 + 0.0j]]), + ), + ( + (1e-2 * Observable.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([[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(), + np.array( + [ + [1.41421356 + 0.0j, 1.41421356 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j], + [1.41421356 + 0.0j, -1.41421356 + 0.0j, 0.0 + 0.0j, -0.0 + 0.0j], + [0.0 + 0.0j, 0.0 + 0.0j, -1.41421356 + 0.0j, -1.41421356 + 0.0j], + [0.0 + 0.0j, -0.0 + 0.0j, -1.41421356 + 0.0j, 1.41421356 + 0.0j], + ], + ), + ), + ], +) +def test_valid_scaled_matrix(observable, matrix): + npt.assert_allclose(observable, matrix) + + +@pytest.mark.parametrize( + "observable, eigenvalue", + [ + (-2 * Observable.I().eigenvalues, np.array([-2.0, -2.0])), + ( + 3e-2 * Observable.Hermitian(matrix=np.array([[0, 1], [1, 0]])).eigenvalues, + np.array([-0.03, 0.03]), + ), + ], +) +def test_valid_scaled_eigenvalues(observable, eigenvalue): + npt.assert_allclose(observable, eigenvalue) + + @pytest.mark.parametrize( "testobject,gateobject,expected_ir,basis_rotation_gates,eigenvalues", testdata ) @@ -466,3 +629,90 @@ def compare_eigenvalues(observable, expected): np.array([observable.eigenvalue(i) for i in range(2**observable.qubit_count)]), 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()]) + + +# Sum of observables + + +@pytest.mark.parametrize( + "observable,basis_rotation_gates", + [ + (Observable.X() + Observable.Y(), tuple([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" + with pytest.raises(NotImplementedError, match=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 + + +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() + + +def test_invalid_targets_config_for_sum_obs(): + observable, serialization_properties = ( + 2 * Observable.X() @ Observable.Y() + 0.75 * Observable.Y() @ Observable.Z(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + ) + target = [[0, 1]] + + target_len_mismatch_for_sum_terms = "Invalid target of length 1 for Sum with 2 terms" + + with pytest.raises(ValueError, match=target_len_mismatch_for_sum_terms): + observable.to_ir( + target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties + ) + + +def test_sum_obs_str(): + assert ( + str(Observable.Sum([2 * Observable.X() + 3 * Observable.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()]) + assert t1 == t2 + assert t2 != t3 + assert t1 != t3 + assert t3 == t4 + + +def test_invalid_target_length_for_sum_obs_term(): + observable, serialization_properties = ( + 2 * Observable.Y() + 0.75 * Observable.Y() @ Observable.Z(), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + ) + target = [[0, 1], [0, 1]] + + invalid_target_len_for_term = "Invalid target for term 0 of Sum. Expected 1 targets, got 2" + + with pytest.raises(ValueError, match=invalid_target_len_for_term): + observable.to_ir( + target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties + ) + + +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() diff --git a/test/unit_tests/braket/circuits/test_result_type.py b/test/unit_tests/braket/circuits/test_result_type.py index ced44b39..2f734a7c 100644 --- a/test/unit_tests/braket/circuits/test_result_type.py +++ b/test/unit_tests/braket/circuits/test_result_type.py @@ -10,10 +10,13 @@ # 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 braket.circuits import Observable, ObservableResultType, ResultType +from braket.circuits.free_parameter import FreeParameter +from braket.circuits.result_type import ObservableParameterResultType from braket.circuits.serialization import IRType @@ -186,3 +189,63 @@ def test_result_type_to_ir( with pytest.raises(expected_exception) as exc: result_type.to_ir(ir_type, serialization_properties=serialization_properties) assert exc.value.args[0] == expected_message + + +# Observable Result Type with Params + + +def test_expectation_init_value_error_target_adjoint_gradient(): + tensor_operation_error = re.escape( + "Observable TensorProduct(X('qubit_count': 1), " + "Y('qubit_count': 1)) must only operate on 1 qubit for target=None" + ) + with pytest.raises(ValueError, match=tensor_operation_error): + ObservableParameterResultType( + ascii_symbols=["Obs", "Obs"], + observable=Observable.X() @ Observable.Y(), + target=[], + parameters=["alpha"], + ) + + +def test_expectation_init_value_error_ascii_symbols_adjoint_gradient(): + ascii_and_obs_qubit_count_mismatch = ( + "Observable's qubit count and the number of ASCII symbols must be equal" + ) + with pytest.raises(ValueError, match=ascii_and_obs_qubit_count_mismatch): + ObservableParameterResultType( + ascii_symbols=["Obs"], + observable=Observable.X() @ Observable.Y(), + target=[1, 2], + parameters=[], + ) + + +def test_obs_rt_init_value_error_qubit_count_adjoint_gradient(): + obs_and_target_count_mismatch = re.escape( + "Observable's qubit count 1 and the size of the target " + "qubit set QubitSet([Qubit(0), Qubit(1)]) must be equal" + ) + with pytest.raises(ValueError, match=obs_and_target_count_mismatch): + ObservableParameterResultType( + ascii_symbols=["Obs"], observable=Observable.X(), target=[0, 1] + ) + + +def test_valid_result_type_for__adjoint_gradient(): + ObservableParameterResultType( + ascii_symbols=["Obs", "Obs"], + observable=Observable.X() @ Observable.Y(), + target=[0, 1], + parameters=["alpha", FreeParameter("beta")], + ) + + +def test_obs_rt_repr_adjoint_gradient(): + a1 = ObservableParameterResultType( + ascii_symbols=["Obs"], observable=Observable.X(), target=0, parameters=["alpha"] + ) + assert ( + str(a1) == "ObservableParameterResultType(observable=X('qubit_count': 1), " + "target=QubitSet([Qubit(0)]), parameters=['alpha'])" + ) diff --git a/test/unit_tests/braket/circuits/test_result_types.py b/test/unit_tests/braket/circuits/test_result_types.py index b298a046..2d51fb68 100644 --- a/test/unit_tests/braket/circuits/test_result_types.py +++ b/test/unit_tests/braket/circuits/test_result_types.py @@ -15,6 +15,8 @@ import braket.ir.jaqcd as ir from braket.circuits import Circuit, Observable, ResultType +from braket.circuits.free_parameter import FreeParameter +from braket.circuits.result_type import ObservableParameterResultType from braket.circuits.result_types import ObservableResultType from braket.circuits.serialization import ( IRType, @@ -103,14 +105,48 @@ {"observable": Observable.Z(), "target": None}, {"observable": ["z"]}, ), + ( + ResultType.AdjointGradient, + "adjoint_gradient", + ir.AdjointGradient, + {"observable": Observable.Z(), "target": None, "parameters": None}, + {"observable": ["z"]}, + ), + ( + ResultType.AdjointGradient, + "adjoint_gradient", + ir.AdjointGradient, + {"observable": Observable.Z(), "target": [0], "parameters": ["alpha", "beta"]}, + {"observable": ["z"], "targets": [0], "parameters": ["alpha", FreeParameter("beta")]}, + ), + ( + ResultType.AdjointGradient, + "adjoint_gradient", + ir.AdjointGradient, + { + "observable": Observable.Hermitian(matrix=Observable.Z().to_matrix()), + "target": [0], + "parameters": ["alpha", "beta"], + }, + { + "observable": [[[[1.0, 0], [0, 0]], [[0, 0], [-1.0, 0]]]], + "targets": [0], + "parameters": ["alpha", "beta"], + }, + ), ] @pytest.mark.parametrize("testclass,subroutine_name,irclass,input,ir_input", testdata) def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): - expected = irclass(**ir_input) - actual = testclass(**input).to_ir() - assert actual == expected + if testclass == ResultType.AdjointGradient: + jaqcd_not_implemented = "to_jaqcd has not been implemented yet." + with pytest.raises(NotImplementedError, match=jaqcd_not_implemented): + testclass(**input).to_ir() + else: + expected = irclass(**ir_input) + actual = testclass(**input).to_ir() + assert actual == expected @pytest.mark.parametrize( @@ -186,6 +222,56 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), "#pragma braket result variance i all", ), + ( + ResultType.AdjointGradient(Observable.I(), target=0, parameters=["alpha", "beta"]), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result adjoint_gradient expectation(i(q[0])) alpha, beta", + ), + ( + ResultType.AdjointGradient(Observable.I(), target=0, parameters=["alpha"]), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result adjoint_gradient expectation(i(q[0])) alpha", + ), + ( + ResultType.AdjointGradient( + Observable.H() @ Observable.I(), + target=[0, 1], + parameters=[FreeParameter("alpha"), "beta", FreeParameter("gamma")], + ), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result adjoint_gradient expectation(h(q[0]) @ i(q[1])) " + "alpha, beta, gamma", + ), + ( + ResultType.AdjointGradient( + Observable.H() @ Observable.I() + 2 * Observable.Z(), + target=[[0, 1], [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.I(), target=0, parameters=[]), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result adjoint_gradient expectation(i(q[0])) all", + ), + ( + ResultType.AdjointGradient( + Observable.X() @ Observable.Y(), target=[0, 1], parameters=[] + ), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result adjoint_gradient expectation(x(q[0]) @ y(q[1])) all", + ), + ( + ResultType.AdjointGradient( + Observable.Hermitian(matrix=Observable.I().to_matrix()), target=0, parameters=[] + ), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result adjoint_gradient expectation(hermitian([[1+0im, 0im], " + "[0im, 1+0im]]) q[0]) all", + ), ], ) def test_result_to_ir_openqasm(result_type, serialization_properties, expected_ir): @@ -267,3 +353,34 @@ def test_variance_parent_class(): assert isinstance( ResultType.Variance(observable=Observable.X(), target=0), ObservableResultType ) + + +# AdjointGradient + + +def test_adjoint_gradient_parent_class(): + assert isinstance( + ResultType.AdjointGradient( + observable=Observable.X(), target=0, parameters=["alpha", FreeParameter("beta")] + ), + ObservableParameterResultType, + ) + + +@pytest.mark.parametrize( + "target", + ( + [[0], [0, 1], [2]], + [[0, 1], [0, 1]], + ), +) +def test_incorrect_target_adjoint_gradient(target): + match = ( + "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." + ) + with pytest.raises(ValueError, match=match): + ResultType.AdjointGradient( + 2 * Observable.Z() + 3 * Observable.X() @ Observable.Y(), + target, + ) diff --git a/tox.ini b/tox.ini index 4db891d3..9c4e3cd5 100644 --- a/tox.ini +++ b/tox.ini @@ -47,7 +47,7 @@ deps = git+https://github.com/aws/amazon-braket-build-tools.git commands = flake8 {posargs} - flake8 --enable-extensions=BCS src + ; flake8 --enable-extensions=BCS src [testenv:isort] basepython = python3 From c64eed0047782ee8ea1d5db74a0dca5f518f41a2 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 7 Dec 2022 16:26:43 +0000 Subject: [PATCH 0590/1165] prepare release v1.35.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 a23c8cb4..a05131ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.35.0 (2022-12-07) + +### Features + + * adjoint gradient + +### Bug Fixes and Other Changes + + * docs: Update examples-getting-started.rst + * loosen oqpy requirement + ## v1.34.3.post0 (2022-11-21) ### Testing and Release Infrastructure diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index fce3a457..c9b43ce2 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.34.4.dev0" +__version__ = "1.35.0" From 8573557e1a45e991c7fb98627334281f11fe61e9 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 7 Dec 2022 16:26:43 +0000 Subject: [PATCH 0591/1165] update development version to v1.35.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 c9b43ce2..8da2c8dd 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.35.0" +__version__ = "1.35.1.dev0" From 9a9d385b01dfd3df4b14eeb1faab6532e45faf23 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Thu, 8 Dec 2022 12:57:13 -0800 Subject: [PATCH 0592/1165] fix: Hamiltonian ascii simplification (#486) --- src/braket/circuits/observables.py | 5 ++--- src/braket/circuits/result_type.py | 2 +- src/braket/circuits/result_types.py | 11 ++++++++--- test/unit_tests/braket/circuits/test_observable.py | 4 ++-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 4a5b7942..18ca4916 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -431,7 +431,7 @@ def _compute_eigenvalues(observables: Tuple[Observable], num_qubits: int) -> np. class Sum(Observable): """Sum of observables""" - def __init__(self, observables: List[Observable]): + def __init__(self, observables: List[Observable], display_name: str = "Hamiltonian"): """ Args: observables (List[Observable]): List of observables for Sum @@ -451,8 +451,7 @@ def __init__(self, observables: List[Observable]): flattened_observables.append(obs) self._summands = tuple(flattened_observables) - qubit_count = sum(obs.qubit_count for obs in flattened_observables) - display_name = "+".join([obs.ascii_symbols[0] for obs in flattened_observables]) + 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) @property diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index c0152d43..d711e273 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -232,7 +232,7 @@ def __init__( f"Observable's qubit count {self._observable.qubit_count} and " f"the size of the target qubit set {self._target} must be equal" ) - if self._observable.qubit_count != len(self.ascii_symbols): + elif self._observable.qubit_count != len(self.ascii_symbols): raise ValueError( "Observable's qubit count and the number of ASCII symbols must be equal" ) diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 5e4bc974..d6a15c48 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -14,12 +14,14 @@ from __future__ import annotations import re +from functools import reduce from typing import List, 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.qubit_set import QubitSet, QubitSetInput from braket.circuits.result_type import ( ObservableParameterResultType, @@ -208,10 +210,13 @@ def __init__( >>> ) """ + if isinstance(observable, Sum): + target_qubits = reduce(QubitSet.union, map(QubitSet, target), QubitSet()) + else: + target_qubits = QubitSet(target) + super().__init__( - ascii_symbols=[ - f"AdjointGradient({obs_ascii})" for obs_ascii in observable.ascii_symbols - ], + ascii_symbols=[f"AdjointGradient({observable.ascii_symbols[0]})"] * len(target_qubits), observable=observable, target=target, parameters=parameters, diff --git a/test/unit_tests/braket/circuits/test_observable.py b/test/unit_tests/braket/circuits/test_observable.py index d732a0d7..e2762531 100644 --- a/test/unit_tests/braket/circuits/test_observable.py +++ b/test/unit_tests/braket/circuits/test_observable.py @@ -212,5 +212,5 @@ def test_sum_observable_with_subtraction(): obs2 = -4 * Observable.Y() result = obs1 - obs2 assert isinstance(result, Observable.Sum) - assert result.qubit_count == 2 - assert result.ascii_symbols == ("6X+4Y", "6X+4Y") + assert result.qubit_count == 1 + assert result.ascii_symbols == ("6X+4Y",) From dd1e6daa8b1688471cd345edbeb4f66df9d1399c Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 8 Dec 2022 21:25:00 +0000 Subject: [PATCH 0593/1165] prepare release v1.35.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 a05131ca..b8be2308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.35.1 (2022-12-08) + +### Bug Fixes and Other Changes + + * Hamiltonian ascii simplification + ## v1.35.0 (2022-12-07) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 8da2c8dd..9f0a449b 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.35.1.dev0" +__version__ = "1.35.1" From fb4e7a351c691076632ba0c201d2e34554fa0def Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 8 Dec 2022 21:25:00 +0000 Subject: [PATCH 0594/1165] update development version to v1.35.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 9f0a449b..7a9e3e59 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.35.1" +__version__ = "1.35.2.dev0" From c082566df2547abe94b26dc10ce159d5dda41258 Mon Sep 17 00:00:00 2001 From: Stephen Face <60493521+shpface@users.noreply.github.com> Date: Tue, 13 Dec 2022 10:17:07 -0800 Subject: [PATCH 0595/1165] fix: abort batch task submission on interrupt (#480) * fix: abort batch task submission on interrupt * fix config for flake v6 * clarify run_batch abort comment * fix testing and handling of earlier exceptions * add windows specific logic to fix run_batch test * skip abort run_batch unit test on windows --- src/braket/aws/aws_quantum_task_batch.py | 94 +++++++++++++------ .../braket/aws/test_aws_quantum_task_batch.py | 53 +++++++++++ 2 files changed, 116 insertions(+), 31 deletions(-) diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index b2c5e47c..05c5853d 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 Dict, List, Set, Union +from typing import Dict, List, Set, Tuple, Union from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing import Problem @@ -126,9 +126,7 @@ def __init__( self._aws_quantum_task_kwargs = aws_quantum_task_kwargs @staticmethod - def _execute( - aws_session: AwsSession, - device_arn: str, + def _tasks_and_inputs( task_specifications: Union[ Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation], List[ @@ -137,16 +135,13 @@ def _execute( ] ], ], - s3_destination_folder: AwsSession.S3DestinationFolder, - shots: int, - max_parallel: int, - 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, - *args, - **kwargs, - ) -> List[AwsQuantumTask]: + ) -> List[ + Tuple[ + Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation], + Dict[str, float], + ] + ]: inputs = inputs or {} single_task = isinstance( @@ -183,26 +178,63 @@ def _execute( f"{unbounded_parameters}" ) + return tasks_and_inputs + + @staticmethod + def _execute( + aws_session: AwsSession, + device_arn: str, + task_specifications: Union[ + Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation], + List[ + Union[ + Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation + ] + ], + ], + s3_destination_folder: AwsSession.S3DestinationFolder, + shots: int, + max_parallel: int, + 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, + *args, + **kwargs, + ) -> List[AwsQuantumTask]: + tasks_and_inputs = AwsQuantumTaskBatch._tasks_and_inputs(task_specifications, inputs) max_threads = min(max_parallel, max_workers) remaining = [0 for _ in tasks_and_inputs] - with ThreadPoolExecutor(max_workers=max_threads) as executor: - task_futures = [ - executor.submit( - AwsQuantumTaskBatch._create_task, - remaining, - aws_session, - device_arn, - task, - s3_destination_folder, - shots, - poll_timeout_seconds=poll_timeout_seconds, - poll_interval_seconds=poll_interval_seconds, - inputs=input_map, - *args, - **kwargs, - ) - for task, input_map in tasks_and_inputs - ] + try: + with ThreadPoolExecutor(max_workers=max_threads) as executor: + task_futures = [ + executor.submit( + AwsQuantumTaskBatch._create_task, + remaining, + aws_session, + device_arn, + task, + s3_destination_folder, + shots, + poll_timeout_seconds=poll_timeout_seconds, + poll_interval_seconds=poll_interval_seconds, + inputs=input_map, + *args, + **kwargs, + ) + for task, input_map in tasks_and_inputs + ] + except KeyboardInterrupt: + # If an exception is thrown before the thread pool has finished, + # clean up the tasks which have not yet been created before reraising it. + if "task_futures" in locals(): + for future in task_futures: + future.cancel() + + # Signal to the workers that there is no mork work to do + remaining.clear() + + raise tasks = [future.result() for future in task_futures] return tasks 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 cac0cef4..fff0cdc7 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 @@ -11,7 +11,11 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import os import random +import signal +import sys +import time import uuid from unittest.mock import Mock, PropertyMock, patch @@ -128,5 +132,54 @@ def test_retry(mock_create): batch.retry_unsuccessful_tasks() +@pytest.mark.skipif( + sys.platform == "win32", reason="Sending signals to test interrupt does not work on windows" +) +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_abort(mock_create): + batch_size = 10 + num_workers = 2 + task_mock = Mock() + task_mock.state.side_effect = ["COMPLETED", "RUNNING"] * batch_size + + counter = 0 + + def create_effect(*args, **kwargs): + nonlocal counter + counter = counter + 1 + if counter == 4: + os.kill(os.getpid(), signal.SIGINT) + return task_mock + + mock_create.side_effect = create_effect + + with pytest.raises(KeyboardInterrupt): + AwsQuantumTaskBatch( + Mock(), + "foo", + _circuits(batch_size), + S3_TARGET, + 1000, + max_parallel=num_workers, + poll_interval_seconds=0.1, + ) + + num_created = mock_create.call_count + time.sleep(1) # delay to check no new tasks are created in the background + assert mock_create.call_count == num_created + + +@patch("concurrent.futures.ThreadPoolExecutor.submit") +def test_early_abort(mock_submit): + batch_size = 10 + num_workers = 2 + mock_submit.side_effect = [Mock(), KeyboardInterrupt()] + + with pytest.raises(KeyboardInterrupt): + AwsQuantumTaskBatch( + Mock(), "foo", _circuits(batch_size), S3_TARGET, 1000, max_parallel=num_workers + ) + + def _circuits(batch_size): return [Circuit().h(0).cnot(0, 1) for _ in range(batch_size)] From 76c6d7564594b75883948b17d2e49f139596b2e6 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Tue, 13 Dec 2022 13:46:53 -0800 Subject: [PATCH 0596/1165] fix: remove ascii_characters dynamic property for sum observables (#488) --- src/braket/circuits/observables.py | 7 ----- .../circuits/test_ascii_circuit_diagram.py | 28 +++++++++---------- .../braket/circuits/test_observable.py | 2 +- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 18ca4916..0588fa63 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -454,13 +454,6 @@ 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) - @property - def ascii_symbols(self) -> Tuple[str, ...]: - return tuple( - "+".join([obs.ascii_symbols[0] for obs in self.summands]).replace("+-", "-") - for _ in range(self.qubit_count) - ) - def __mul__(self, other) -> Observable: """Scalar multiplication""" if isinstance(other, numbers.Number): 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 37cfc3b8..8fade296 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -702,13 +702,13 @@ def test_circuit_with_nested_target_list(): ) expected = ( - "T : |0| Result Types |", - " ", - "q0 : -H-Expectation(-6Y@I-0.75Y@Z)-", - " | ", - "q1 : -H-Expectation(-6Y@I-0.75Y@Z)-", + "T : |0| Result Types |", + " ", + "q0 : -H-Expectation(Hamiltonian)-", + " | ", + "q1 : -H-Expectation(Hamiltonian)-", "", - "T : |0| Result Types |", + "T : |0| Result Types |", ) _assert_correct_diagram(circ, expected) @@ -725,15 +725,15 @@ def test_hamiltonian(): ) ) expected = ( - "T : |0|1| 2 | Result Types |", - " ", - "q0 : -H-C-Rx(theta)-AdjointGradient(8e-05Z+48X@Y)-", - " | | ", - "q1 : ---X-----------AdjointGradient(8e-05Z+48X@Y)-", - " | ", - "q2 : ---------------AdjointGradient(8e-05Z+48X@Y)-", + "T : |0|1| 2 | Result Types |", + " ", + "q0 : -H-C-Rx(theta)-AdjointGradient(Hamiltonian)-", + " | | ", + "q1 : ---X-----------AdjointGradient(Hamiltonian)-", + " | ", + "q2 : ---------------AdjointGradient(Hamiltonian)-", "", - "T : |0|1| 2 | Result Types |", + "T : |0|1| 2 | Result Types |", "", "Unassigned parameters: [theta].", ) diff --git a/test/unit_tests/braket/circuits/test_observable.py b/test/unit_tests/braket/circuits/test_observable.py index e2762531..150e2d8f 100644 --- a/test/unit_tests/braket/circuits/test_observable.py +++ b/test/unit_tests/braket/circuits/test_observable.py @@ -213,4 +213,4 @@ def test_sum_observable_with_subtraction(): result = obs1 - obs2 assert isinstance(result, Observable.Sum) assert result.qubit_count == 1 - assert result.ascii_symbols == ("6X+4Y",) + assert np.array_equal(result.summands, (6 * Observable.X(), -4 * Observable.Y())) From 1c868c4691d68ba1b76d156d15ff5a9675965e22 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 14 Dec 2022 20:19:54 -0800 Subject: [PATCH 0597/1165] fix: update ascii symbols for angled gate adjoint (#492) --- src/braket/circuits/angled_gate.py | 88 ++++++++++++++++++ src/braket/circuits/gates.py | 90 ++----------------- .../braket/circuits/test_angled_gate.py | 13 +++ 3 files changed, 109 insertions(+), 82 deletions(-) diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index a5b71b9f..ec2b3da8 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -18,6 +18,8 @@ from functools import singledispatch from typing import List, Optional, Sequence, Union +from sympy import Float + from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.gate import Gate from braket.circuits.parameterizable import Parameterizable @@ -97,8 +99,14 @@ def adjoint(self) -> List[Gate]: Returns: List[Gate]: A list containing the gate with negated angle. """ + gate_ascii_name_index = self.ascii_symbols[0].find("(") + gate_ascii_name = self.ascii_symbols[0][:gate_ascii_name_index] + new_ascii_symbols = [ + angled_ascii_characters(gate_ascii_name, -self.angle) + ] * self.qubit_count new = copy.copy(self) new._parameters = [-angle for angle in self._parameters] + new._ascii_symbols = new_ascii_symbols return [new] def __eq__(self, other): @@ -235,3 +243,83 @@ def _angles_equal( @_angles_equal.register def _(angle_1: FreeParameterExpression, angle_2: FreeParameterExpression): return angle_1 == angle_2 + + +def angled_ascii_characters(gate: str, angle: Union[FreeParameterExpression, float]) -> str: + """ + Generates a formatted ascii representation of an angled gate. + + Args: + gate (str): The name of the gate. + angle (Union[FreeParameterExpression, float]): The angle for the gate. + + Returns: + str: Returns the ascii representation for an angled gate. + + """ + return f'{gate}({angle:{".2f" if isinstance(angle, (float, Float)) else ""}})' + + +def _double_angled_ascii_characters( + gate: str, + angle_1: Union[FreeParameterExpression, float], + angle_2: Union[FreeParameterExpression, float], +) -> str: + """ + Generates a formatted ascii representation of an angled gate. + + Args: + gate (str): The name of the gate. + angle_1 (Union[FreeParameterExpression, float]): angle in radians. + angle_2 (Union[FreeParameterExpression, float]): angle in radians. + + Returns: + str: Returns the ascii representation for an angled gate. + + """ + return ( + f"{gate}(" + f'{angle_1:{".2f" if isinstance(angle_1, (float, Float)) else ""}}, ' + f'{angle_2:{".2f" if isinstance(angle_2, (float, Float)) else ""}})' + ) + + +def get_angle(gate: AngledGate, **kwargs) -> 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. + + Returns: + AngledGate: A new gate of the type of the AngledGate originally used with all + angles updated. + """ + new_angle = ( + gate.angle.subs(kwargs) if isinstance(gate.angle, FreeParameterExpression) else gate.angle + ) + return type(gate)(angle=new_angle) + + +def _get_angles(gate: DoubleAngledGate, **kwargs) -> DoubleAngledGate: + """ + Gets the angle with all values substituted in that are requested. + + Args: + gate (DoubleAngledGate): The subclass of DoubleAngledGate for which the angle is being + obtained. + **kwargs: The named parameters that are being filled for a particular gate. + + Returns: + DoubleAngledGate: A new gate of the type of the AngledGate originally used with all angles + updated. + """ + new_angles = [ + ( + getattr(gate, angle).subs(kwargs) + if isinstance(getattr(gate, angle), FreeParameterExpression) + else getattr(gate, angle) + ) + for angle in ("angle_1", "angle_2") + ] + return type(gate)(angle_1=new_angles[0], angle_2=new_angles[1]) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index b5dca4bd..cea3dfbb 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -18,11 +18,17 @@ import numpy as np from oqpy import Program -from sympy import Float import braket.ir.jaqcd as ir from braket.circuits import circuit -from braket.circuits.angled_gate import AngledGate, DoubleAngledGate +from braket.circuits.angled_gate import ( + AngledGate, + DoubleAngledGate, + _double_angled_ascii_characters, + _get_angles, + angled_ascii_characters, + get_angle, +) from braket.circuits.free_parameter import FreeParameter from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.gate import Gate @@ -2277,86 +2283,6 @@ def pulse_gate( Gate.register_gate(PulseGate) -def angled_ascii_characters(gate: str, angle: Union[FreeParameterExpression, float]) -> str: - """ - Generates a formatted ascii representation of an angled gate. - - Args: - gate (str): The name of the gate. - angle (Union[FreeParameterExpression, float]): The angle for the gate. - - Returns: - str: Returns the ascii representation for an angled gate. - - """ - return f'{gate}({angle:{".2f" if isinstance(angle, (float, Float)) else ""}})' - - -def _double_angled_ascii_characters( - gate: str, - angle_1: Union[FreeParameterExpression, float], - angle_2: Union[FreeParameterExpression, float], -) -> str: - """ - Generates a formatted ascii representation of an angled gate. - - Args: - gate (str): The name of the gate. - angle_1 (Union[FreeParameterExpression, float]): angle in radians. - angle_2 (Union[FreeParameterExpression, float]): angle in radians. - - Returns: - str: Returns the ascii representation for an angled gate. - - """ - return ( - f"{gate}(" - f'{angle_1:{".2f" if isinstance(angle_1, (float, Float)) else ""}}, ' - f'{angle_2:{".2f" if isinstance(angle_2, (float, Float)) else ""}})' - ) - - -def get_angle(gate: AngledGate, **kwargs) -> 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. - - Returns: - AngledGate: A new gate of the type of the AngledGate originally used with all - angles updated. - """ - new_angle = ( - gate.angle.subs(kwargs) if isinstance(gate.angle, FreeParameterExpression) else gate.angle - ) - return type(gate)(angle=new_angle) - - -def _get_angles(gate: DoubleAngledGate, **kwargs) -> DoubleAngledGate: - """ - Gets the angle with all values substituted in that are requested. - - Args: - gate (DoubleAngledGate): The subclass of DoubleAngledGate for which the angle is being - obtained. - **kwargs: The named parameters that are being filled for a particular gate. - - Returns: - DoubleAngledGate: A new gate of the type of the AngledGate originally used with all angles - updated. - """ - new_angles = [ - ( - getattr(gate, angle).subs(kwargs) - if isinstance(getattr(gate, angle), FreeParameterExpression) - else getattr(gate, angle) - ) - for angle in ("angle_1", "angle_2") - ] - return type(gate)(angle_1=new_angles[0], angle_2=new_angles[1]) - - def format_complex(number: complex) -> str: """ Format a complex number into + im to be consumed by the braket unitary pragma diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index 80535cb1..ebf18532 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -97,6 +97,19 @@ def test_mixed_angle_equality(): assert gate1 != gate2 +def test_angle_adjoint(): + symbol1 = FreeParameter("theta") + gate1 = AngledGate(angle=symbol1, qubit_count=1, ascii_symbols=["bar(theta)"]) + gate2 = AngledGate(angle=0.15, qubit_count=1, ascii_symbols=["foo(0.15)"]) + + gate1_adj = gate1.adjoint() + gate2_adj = gate2.adjoint() + + assert len(gate1_adj) == len(gate2_adj) == 1 + assert np.array_equal(gate1_adj[0].ascii_symbols, ["bar(-theta)"]) + assert np.array_equal(gate2_adj[0].ascii_symbols, ["foo(-0.15)"]) + + @pytest.mark.xfail(raises=NotImplementedError) def test_bind_values(): theta = FreeParameter("theta") From 63da01b16b34a89640b3e72ace69e6a17285c20e Mon Sep 17 00:00:00 2001 From: Stephen Face <60493521+shpface@users.noreply.github.com> Date: Thu, 15 Dec 2022 16:09:46 -0800 Subject: [PATCH 0598/1165] fix: remove OS signaling from run_batch unit test (#491) --- .../braket/aws/test_aws_quantum_task_batch.py | 30 ++----------------- 1 file changed, 3 insertions(+), 27 deletions(-) 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 fff0cdc7..5802db11 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 @@ -11,11 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -import os import random -import signal -import sys -import time import uuid from unittest.mock import Mock, PropertyMock, patch @@ -132,26 +128,11 @@ def test_retry(mock_create): batch.retry_unsuccessful_tasks() -@pytest.mark.skipif( - sys.platform == "win32", reason="Sending signals to test interrupt does not work on windows" -) -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_abort(mock_create): +@patch("braket.aws.aws_quantum_task_batch.ThreadPoolExecutor") +def test_abort(mock_threadpool): batch_size = 10 num_workers = 2 - task_mock = Mock() - task_mock.state.side_effect = ["COMPLETED", "RUNNING"] * batch_size - - counter = 0 - - def create_effect(*args, **kwargs): - nonlocal counter - counter = counter + 1 - if counter == 4: - os.kill(os.getpid(), signal.SIGINT) - return task_mock - - mock_create.side_effect = create_effect + mock_threadpool().__exit__.side_effect = KeyboardInterrupt() with pytest.raises(KeyboardInterrupt): AwsQuantumTaskBatch( @@ -161,13 +142,8 @@ def create_effect(*args, **kwargs): S3_TARGET, 1000, max_parallel=num_workers, - poll_interval_seconds=0.1, ) - num_created = mock_create.call_count - time.sleep(1) # delay to check no new tasks are created in the background - assert mock_create.call_count == num_created - @patch("concurrent.futures.ThreadPoolExecutor.submit") def test_early_abort(mock_submit): From 3c6eac3097ca9f998d60e6938a20f086cf46eb1d Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Fri, 16 Dec 2022 12:12:59 -0800 Subject: [PATCH 0599/1165] fix: specify all for density matrix target (#483) --- src/braket/circuits/result_types.py | 2 +- test/integ_tests/test_density_matrix_simulator.py | 12 ++++++++++++ test/unit_tests/braket/circuits/test_result_types.py | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index d6a15c48..d73c7fbd 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -126,7 +126,7 @@ def _to_jaqcd(self) -> ir.DensityMatrix: def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: if not self.target: - return "#pragma braket result density_matrix" + return "#pragma braket result density_matrix all" targets = ", ".join( serialization_properties.format_target(int(target)) for target in self.target ) diff --git a/test/integ_tests/test_density_matrix_simulator.py b/test/integ_tests/test_density_matrix_simulator.py index 71ead742..220a94ec 100644 --- a/test/integ_tests/test_density_matrix_simulator.py +++ b/test/integ_tests/test_density_matrix_simulator.py @@ -2,6 +2,7 @@ import pytest from gate_model_device_testing_utils import get_tol +import numpy as np from braket.aws import AwsDevice from braket.circuits import Circuit, Noise, Observable @@ -40,3 +41,14 @@ def _mixed_states(n_qubits: int) -> Circuit: circ.expectation(observable=Observable.Z(), target=0) return circ + + +@pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) +def test_density_matrix_result_type(simulator_arn): + circ = Circuit().bit_flip(0, 0.2).density_matrix() + device = AwsDevice(simulator_arn) + density_matrix_result = device.run(circ).result().result_types[0].value + assert np.allclose( + density_matrix_result, + [[[0.8, 0.0], [0.0, 0.0]], [[0.0, 0.0], [0.2, 0.0]]], + ) diff --git a/test/unit_tests/braket/circuits/test_result_types.py b/test/unit_tests/braket/circuits/test_result_types.py index 2d51fb68..501d1f8b 100644 --- a/test/unit_tests/braket/circuits/test_result_types.py +++ b/test/unit_tests/braket/circuits/test_result_types.py @@ -170,7 +170,7 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): ( ResultType.DensityMatrix(), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result density_matrix", + "#pragma braket result density_matrix all", ), ( ResultType.DensityMatrix([0, 2]), From 810465a0227e0cbb609adf64561fad417c28365e Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 4 Jan 2023 01:47:08 +0000 Subject: [PATCH 0600/1165] prepare release v1.35.2 --- 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 b8be2308..722efda7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.35.2 (2023-01-04) + +### Bug Fixes and Other Changes + + * specify all for density matrix target + * remove OS signaling from run_batch unit test + * update ascii symbols for angled gate adjoint + * remove ascii_characters dynamic property for sum observables + * abort batch task submission on interrupt + ## v1.35.1 (2022-12-08) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 7a9e3e59..91957eb9 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.35.2.dev0" +__version__ = "1.35.2" From cf3404508ac3c3433955e3e1dea17101a6d71eeb Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 4 Jan 2023 01:47:08 +0000 Subject: [PATCH 0601/1165] update development version to v1.35.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 91957eb9..027a924c 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.35.2" +__version__ = "1.35.3.dev0" From 3b264b96ba0c3775f5aa6da1419478665999b672 Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Tue, 17 Jan 2023 13:46:43 -0800 Subject: [PATCH 0602/1165] update: updating for Aspen-M3 --- test/integ_tests/test_cost_tracking.py | 15 +++++++++++---- test/integ_tests/test_density_matrix_simulator.py | 2 +- test/integ_tests/test_device_creation.py | 2 +- test/integ_tests/test_pulse.py | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/test/integ_tests/test_cost_tracking.py b/test/integ_tests/test_cost_tracking.py index 7732a3aa..97cd70e8 100644 --- a/test/integ_tests/test_cost_tracking.py +++ b/test/integ_tests/test_cost_tracking.py @@ -14,6 +14,7 @@ from datetime import timedelta import boto3 +import pytest from braket.aws import AwsDevice, AwsSession from braket.circuits import Circuit @@ -21,12 +22,18 @@ from braket.tracking.tracker import MIN_SIMULATOR_DURATION -def test_qpu_tracking(): +@pytest.mark.parametrize( + "qpu", + [ + "arn:aws:braket:::device/qpu/ionq/ionQdevice", + "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy", + "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3", + ], +) +def test_qpu_tracking(qpu): circuit = Circuit().h(0) with Tracker() as t: - AwsDevice("arn:aws:braket:::device/qpu/ionq/ionQdevice").run(circuit, shots=10) - AwsDevice("arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy").run(circuit, shots=10) - AwsDevice("arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-2").run(circuit, shots=10) + AwsDevice(qpu).run(circuit, shots=10) assert t.qpu_tasks_cost() > 0 diff --git a/test/integ_tests/test_density_matrix_simulator.py b/test/integ_tests/test_density_matrix_simulator.py index 220a94ec..b377bdeb 100644 --- a/test/integ_tests/test_density_matrix_simulator.py +++ b/test/integ_tests/test_density_matrix_simulator.py @@ -1,8 +1,8 @@ import math +import numpy as np import pytest from gate_model_device_testing_utils import get_tol -import numpy as np from braket.aws import AwsDevice from braket.circuits import Circuit, Noise, Observable diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 9a55bb56..677e7ed9 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -19,7 +19,7 @@ IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/ionQdevice" 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-2-pulse" +PULSE_ARN = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" @pytest.mark.parametrize( diff --git a/test/integ_tests/test_pulse.py b/test/integ_tests/test_pulse.py index a77910b8..ff894d48 100644 --- a/test/integ_tests/test_pulse.py +++ b/test/integ_tests/test_pulse.py @@ -9,7 +9,7 @@ @pytest.fixture def device(): - return AwsDevice("arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-2") + return AwsDevice("arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3") @pytest.fixture From f62e234e5f5fe04f574c39802340bfb1c23ae915 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 17 Jan 2023 23:37:32 +0000 Subject: [PATCH 0603/1165] prepare release v1.35.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 722efda7..21787799 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.35.3 (2023-01-17) + +### Bug Fixes and Other Changes + + * update: updating for Aspen-M3 + ## v1.35.2 (2023-01-04) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 027a924c..ef6eb7cc 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.35.3.dev0" +__version__ = "1.35.3" From efd407db5d6114d190e06093df4de05d691e9fb8 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 17 Jan 2023 23:37:32 +0000 Subject: [PATCH 0604/1165] update development version to v1.35.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 ef6eb7cc..96863365 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.35.3" +__version__ = "1.35.4.dev0" From 54d19c3e47cf7080527e3a35245d9162de9a0504 Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Tue, 7 Feb 2023 13:02:26 -0800 Subject: [PATCH 0605/1165] fix: copy session with env and profile. (#497) --- src/braket/aws/aws_session.py | 2 ++ src/braket/circuits/noises.py | 1 - src/braket/tracking/tracker.py | 1 - test/integ_tests/test_pulse.py | 10 +++++-- .../unit_tests/braket/aws/test_aws_session.py | 29 +++++++++++++++++++ .../braket/circuits/test_noise_helpers.py | 5 +++- 6 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 8233223a..4b0a8eca 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -801,6 +801,8 @@ def copy_session( region_name=new_region, profile_name=profile_name, ) + elif creds.method == "env": + boto_session = boto3.Session(region_name=new_region) else: boto_session = boto3.Session( region_name=new_region, diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py index b778d5b0..7b01ce58 100644 --- a/src/braket/circuits/noises.py +++ b/src/braket/circuits/noises.py @@ -1315,7 +1315,6 @@ class Kraus(Noise): """ 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])): diff --git a/src/braket/tracking/tracker.py b/src/braket/tracking/tracker.py index 59bac823..0ff2d352 100644 --- a/src/braket/tracking/tracker.py +++ b/src/braket/tracking/tracker.py @@ -140,7 +140,6 @@ def quantum_tasks_statistics(self) -> Dict[str, Dict[str, Any]]: """ stats = {} for _, details in self._resources.items(): - device_stats = stats.get(details["device"], {}) shots = device_stats.get("shots", 0) + details["shots"] diff --git a/test/integ_tests/test_pulse.py b/test/integ_tests/test_pulse.py index ff894d48..3b4694e4 100644 --- a/test/integ_tests/test_pulse.py +++ b/test/integ_tests/test_pulse.py @@ -184,7 +184,10 @@ def cz_pulse( def test_pulse_bell(arbitrary_waveform, device): - a, b, = ( + ( + a, + b, + ) = ( 10, 113, ) # qubits used @@ -229,7 +232,10 @@ def test_pulse_bell(arbitrary_waveform, device): def test_pulse_sequence(arbitrary_waveform, device): - a, b, = ( + ( + a, + b, + ) = ( 10, 113, ) # qubits used diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index 3c82e671..c0938d38 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -85,6 +85,26 @@ def aws_explicit_session(): return _aws_session +@pytest.fixture +def aws_env_session(): + _boto_session = Mock() + _boto_session.region_name = "us-test-1" + _boto_session.profile_name = "test-profile" + + creds = Mock() + creds.method = "env" + _boto_session.get_credentials.return_value = creds + + _aws_session = Mock() + _aws_session.boto_session = _boto_session + _aws_session._default_bucket = "amazon-braket-us-test-1-00000000" + _aws_session.default_bucket.return_value = _aws_session._default_bucket + _aws_session._custom_default_bucket = False + _aws_session.account_id = "00000000" + _aws_session.region = "us-test-1" + return _aws_session + + @pytest.fixture def account_id(): return "000000000" @@ -1277,6 +1297,15 @@ def test_copy_explicit_session(boto_session_init, aws_explicit_session): ) +@patch("boto3.Session") +def test_copy_env_session(boto_session_init, aws_env_session): + boto_session_init.return_value = Mock() + AwsSession.copy_session(aws_env_session, "us-west-2") + boto_session_init.assert_called_with( + region_name="us-west-2", + ) + + @patch("boto3.Session") def test_copy_session_custom_default_bucket(mock_boto, aws_session): mock_boto.return_value.region_name = "us-test-1" diff --git a/test/unit_tests/braket/circuits/test_noise_helpers.py b/test/unit_tests/braket/circuits/test_noise_helpers.py index 013831e6..6d9d2a81 100644 --- a/test/unit_tests/braket/circuits/test_noise_helpers.py +++ b/test/unit_tests/braket/circuits/test_noise_helpers.py @@ -596,7 +596,10 @@ def test_apply_multiple_noise_1QubitNoise_1(circuit_2qubit, noise_1qubit, noise_ def test_apply_multiple_noise_1QubitNoise_2(circuit_2qubit, noise_1qubit, noise_1qubit_2): - circ = circuit_2qubit.apply_gate_noise(noise_1qubit, target_gates=[Gate.X],).apply_gate_noise( + circ = circuit_2qubit.apply_gate_noise( + noise_1qubit, + target_gates=[Gate.X], + ).apply_gate_noise( noise_1qubit_2, target_qubits=[0], ) From b2d0ecbb782965bed3ec2ce6b17d08449e8d5ac7 Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Wed, 8 Feb 2023 09:02:52 -0800 Subject: [PATCH 0606/1165] update: adding build for python 3.10 (#485) --- .github/workflows/dependent-tests.yml | 2 +- .github/workflows/python-package.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index c6289539..89092dde 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.7, 3.8, 3.9] + python-version: ["3.7", "3.8", "3.9", "3.10"] dependent: - amazon-braket-pennylane-plugin-python - amazon-braket-strawberryfields-plugin-python diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 83abf0fb..923e06ed 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.7, 3.8, 3.9] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v2 From f7e4824c33582ae26fd145dda22bad6dd0408804 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 9 Feb 2023 17:49:53 +0000 Subject: [PATCH 0607/1165] prepare release v1.35.4 --- 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 21787799..94191804 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.35.4 (2023-02-09) + +### Bug Fixes and Other Changes + + * update: adding build for python 3.10 + * copy session with env and profile. + ## v1.35.3 (2023-01-17) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 96863365..d5eb3292 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.35.4.dev0" +__version__ = "1.35.4" From c88ad8d125fee134ed98026c0e22f2fa6f843ea7 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 9 Feb 2023 17:49:53 +0000 Subject: [PATCH 0608/1165] update development version to v1.35.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 d5eb3292..034a1d0f 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.35.4" +__version__ = "1.35.5.dev0" From 58e5a88125a2158cc3ee2c87aa344017f4345aa8 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 13 Feb 2023 10:53:07 -0800 Subject: [PATCH 0609/1165] infra: update github workflows for node12 retirement * infra: update github workflows for node12 retirement --- .github/workflows/dependent-tests.yml | 4 ++-- .github/workflows/publish-to-pypi.yml | 4 ++-- .github/workflows/python-package.yml | 6 +++--- README.md | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index 89092dde..96c00368 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -22,9 +22,9 @@ jobs: - amazon-braket-strawberryfields-plugin-python steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 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 d1439d2b..ea0c5702 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -9,9 +9,9 @@ jobs: name: Build and publish distribution to PyPi runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.x' - name: Install wheel diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 923e06ed..6089fa88 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -22,9 +22,9 @@ jobs: python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -40,4 +40,4 @@ jobs: run: | tox -e unit-tests - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 diff --git a/README.md b/README.md index 7e415ac4..98b8e89f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Latest Version](https://img.shields.io/pypi/v/amazon-braket-sdk.svg)](https://pypi.python.org/pypi/amazon-braket-sdk) [![Supported Python Versions](https://img.shields.io/pypi/pyversions/amazon-braket-sdk.svg)](https://pypi.python.org/pypi/amazon-braket-sdk) -[![Build Status](https://img.shields.io/github/workflow/status/aws/amazon-braket-sdk-python/Python%20package/main?logo=github)](https://github.com/aws/amazon-braket-sdk-python/actions?query=workflow%3A%22Python+package%22) +[![Build status](https://github.com/aws/amazon-braket-sdk-python/actions/workflows/python-package.yml/badge.svg?branch=main)](https://github.com/aws/amazon-braket-sdk-python/actions/workflows/python-package.yml) [![codecov](https://codecov.io/gh/aws/amazon-braket-sdk-python/branch/main/graph/badge.svg?token=1lsqkZL3Ll)](https://codecov.io/gh/aws/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) [![Code Style: Black](https://img.shields.io/badge/code_style-black-000000.svg)](https://github.com/psf/black) From 242d5a617531144963316ce92a1aa3be2048ee71 Mon Sep 17 00:00:00 2001 From: Stephen Face <60493521+shpface@users.noreply.github.com> Date: Mon, 13 Feb 2023 18:10:27 -0800 Subject: [PATCH 0610/1165] doc: Add note to call result to estimate sim tasks (#501) --- src/braket/tracking/tracker.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/braket/tracking/tracker.py b/src/braket/tracking/tracker.py index 0ff2d352..b2a6f173 100644 --- a/src/braket/tracking/tracker.py +++ b/src/braket/tracking/tracker.py @@ -98,6 +98,10 @@ def simulator_tasks_cost(self) -> Decimal: """ Estimate cost of all quantum tasks tracked by this tracker using Braket simulator devices. + Note: The cost of a simulator task is not available until after the results for the task + have been fetched. Call `result()` on an `AwsQuantumTask` before estimating its cost + to ensure that the simulator usage is included in the cost estimate. + 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 charges. Estimated charges do not factor in any discounts or credits, and you may From ecbe2c73fdc47f5194315847174b9c1808bc769d Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 15 Feb 2023 16:24:01 +0000 Subject: [PATCH 0611/1165] prepare release v1.35.4.post0 --- 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 94191804..bd7f4f01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.35.4.post0 (2023-02-15) + +### Documentation Changes + + * Add note to call result to estimate sim tasks + +### Testing and Release Infrastructure + + * update github workflows for node12 retirement + ## v1.35.4 (2023-02-09) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 034a1d0f..e854bccf 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.35.5.dev0" +__version__ = "1.35.4.post0" From d05ac1e00894b8e4064ed1884304bffddd0f57a2 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 15 Feb 2023 16:24:01 +0000 Subject: [PATCH 0612/1165] update development version to v1.35.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 e854bccf..034a1d0f 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.35.4.post0" +__version__ = "1.35.5.dev0" From 1f7a08bb4cc67f16e6b8a8939e500023b0f67202 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 15 Feb 2023 10:30:55 -0800 Subject: [PATCH 0613/1165] infra: change publish to pypi wf to use release/v1 (#502) https://github.com/pypa/gh-action-pypi-publish#-master-branch-sunset- --- .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 ea0c5702..c6171c41 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -21,6 +21,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@master + uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.pypi_token }} From a8974e35b251e6056b7c4184f0949bb544451c21 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 15 Feb 2023 13:11:05 -0800 Subject: [PATCH 0614/1165] infra: add dependabot updates for GH actions (#503) * infra: add dependabot updates for GH actions Co-authored-by: Abe Coull --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..bf7c9851 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# Set update schedule for GitHub Actions + +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" + From 630755580a521dabecdf45c716295506422e9103 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Feb 2023 13:28:06 -0800 Subject: [PATCH 0615/1165] build(deps): bump aws-actions/stale-issue-cleanup from 3 to 6 (#504) Bumps [aws-actions/stale-issue-cleanup](https://github.com/aws-actions/stale-issue-cleanup) from 3 to 6. - [Release notes](https://github.com/aws-actions/stale-issue-cleanup/releases) - [Commits](https://github.com/aws-actions/stale-issue-cleanup/compare/v3...v6) --- updated-dependencies: - dependency-name: aws-actions/stale-issue-cleanup 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/stale_issue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale_issue.yml b/.github/workflows/stale_issue.yml index e1833b32..482c7084 100644 --- a/.github/workflows/stale_issue.yml +++ b/.github/workflows/stale_issue.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest name: Stale issue job steps: - - uses: aws-actions/stale-issue-cleanup@v3 + - uses: aws-actions/stale-issue-cleanup@v6 with: # Setting messages to an empty string will cause the automation to skip # that category From c07eb1be3f496161b7eb2dbe8fe4e147ebe7cda5 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 15 Feb 2023 13:44:29 -0800 Subject: [PATCH 0616/1165] fix: Prevent float-FreeParameter comparison (#500) --- src/braket/circuits/angled_gate.py | 13 ++++++------- test/unit_tests/braket/circuits/test_angled_gate.py | 1 + 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index ec2b3da8..6dbccba0 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -110,12 +110,11 @@ def adjoint(self) -> List[Gate]: return [new] def __eq__(self, other): - if isinstance(other, AngledGate): - if isinstance(self.angle, FreeParameterExpression): - return self.name == other.name and self.angle == other.angle - else: - return self.name == other.name and math.isclose(self.angle, other.angle) - return False + return ( + isinstance(other, AngledGate) + and self.name == other.name + and _angles_equal(self.angle, other.angle) + ) def __repr__(self): return f"{self.name}('angle': {self.angle}, 'qubit_count': {self.qubit_count})" @@ -237,7 +236,7 @@ def __repr__(self): def _angles_equal( angle_1: Union[FreeParameterExpression, float], angle_2: Union[FreeParameterExpression, float] ) -> bool: - return math.isclose(angle_1, angle_2) + return isinstance(angle_2, float) and math.isclose(angle_1, angle_2) @_angles_equal.register diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index ebf18532..0e46c1a3 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -95,6 +95,7 @@ def test_mixed_angle_equality(): gate2 = AngledGate(angle=0.15, qubit_count=1, ascii_symbols=["foo"]) assert gate1 != gate2 + assert gate2 != gate1 def test_angle_adjoint(): From cc5a232cc49a5057d5cac23eebae6bfa4a354730 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 15 Feb 2023 13:57:47 -0800 Subject: [PATCH 0617/1165] doc: Remove black badge (#505) It doesn't contain any useful information --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 98b8e89f..24106c5c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ [![Build status](https://github.com/aws/amazon-braket-sdk-python/actions/workflows/python-package.yml/badge.svg?branch=main)](https://github.com/aws/amazon-braket-sdk-python/actions/workflows/python-package.yml) [![codecov](https://codecov.io/gh/aws/amazon-braket-sdk-python/branch/main/graph/badge.svg?token=1lsqkZL3Ll)](https://codecov.io/gh/aws/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) -[![Code Style: Black](https://img.shields.io/badge/code_style-black-000000.svg)](https://github.com/psf/black) 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 620391f4f69afca75ff36e8831e6c2268c65d7bf Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 16 Feb 2023 16:24:03 +0000 Subject: [PATCH 0618/1165] prepare release v1.35.5 --- CHANGELOG.md | 16 ++++++++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd7f4f01..437bf955 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## v1.35.5 (2023-02-16) + +### Bug Fixes and Other Changes + + * Prevent float-FreeParameter comparison + * build(deps): bump aws-actions/stale-issue-cleanup from 3 to 6 + +### Documentation Changes + + * Remove black badge + +### Testing and Release Infrastructure + + * add dependabot updates for GH actions + * change publish to pypi wf to use release/v1 + ## v1.35.4.post0 (2023-02-15) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 034a1d0f..2eeffc6e 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.35.5.dev0" +__version__ = "1.35.5" From 105433edb22ad2ccb50afbb72fd74ccb8d8d63b9 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 16 Feb 2023 16:24:03 +0000 Subject: [PATCH 0619/1165] update development version to v1.35.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 2eeffc6e..31dc7070 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.35.5" +__version__ = "1.35.6.dev0" From 076adf37979f9495f8217f1ef359cd1431d19c82 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 1 Mar 2023 17:38:45 -0800 Subject: [PATCH 0620/1165] deprecation: deprecate python 3.7 (#511) Co-authored-by: Abe Coull --- .github/workflows/dependent-tests.yml | 2 +- .github/workflows/python-package.yml | 2 +- .readthedocs.yml | 2 +- setup.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index 96c00368..aba90639 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10"] dependent: - amazon-braket-pennylane-plugin-python - amazon-braket-strawberryfields-plugin-python diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 6089fa88..2423e160 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v3 diff --git a/.readthedocs.yml b/.readthedocs.yml index 26bc27fa..a52ab5c2 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -15,7 +15,7 @@ formats: # Optionally set the version of Python and requirements required to build your docs python: - version: 3.7 + version: 3.8 install: - method: pip path: . diff --git a/setup.py b/setup.py index bf2f5b4c..5d400190 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.7.2", + python_requires=">= 3.8.2", packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ @@ -75,8 +75,8 @@ "Natural Language :: English", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", ], ) From f9a798647f100d08718f00a9e683d4ddb0862f48 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 1 Mar 2023 19:01:56 -0800 Subject: [PATCH 0621/1165] fix: Include setuptools in requirements (#507) --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 5d400190..e2400f53 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ "amazon-braket-schemas>=1.14.0", "amazon-braket-default-simulator>=1.11.0", "oqpy~=0.1.1", + "setuptools", "backoff", "boltons", "boto3>=1.22.3", From eb202a21e66dc941843742e3162947c389deb472 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 3 Mar 2023 09:10:39 -0800 Subject: [PATCH 0622/1165] Take advantage of some nice Python 3.8 features (#512) --- src/braket/devices/local_simulator.py | 204 +++++++++--------- src/braket/tracking/tracker.py | 68 +++--- .../quantum_information/test_pauli_string.py | 7 +- 3 files changed, 136 insertions(+), 143 deletions(-) diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index a58dfba6..0398d517 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -11,7 +11,9 @@ # 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 + +from functools import singledispatchmethod from typing import Dict, Optional, Set, Union import pkg_resources @@ -50,7 +52,7 @@ def __init__(self, backend: Union[str, BraketSimulator] = "default"): the actual simulator instance to use for simulation. Defaults to the `default` simulator backend name. """ - delegate = _get_simulator(backend) + delegate = self._get_simulator(backend) super().__init__( name=delegate.__class__.__name__, status="AVAILABLE", @@ -91,9 +93,7 @@ def run( >>> device = LocalSimulator("default") >>> device.run(circuit, shots=1000) """ - result = _run_internal( - task_specification, self._delegate, shots, inputs=inputs, *args, **kwargs - ) + result = self._run_internal(task_specification, shots, inputs=inputs, *args, **kwargs) return LocalQuantumTask(result) @property @@ -115,103 +115,103 @@ def registered_backends() -> Set[str]: """ return set(_simulator_devices.keys()) + @singledispatchmethod + def _get_simulator(self, simulator: Union[str, BraketSimulator]) -> LocalSimulator: + raise TypeError("Simulator must either be a string or a BraketSimulator instance") + + @_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: + raise ValueError( + f"Only the following devices are available {_simulator_devices.keys()}" + ) + + @_get_simulator.register + def _(self, backend_impl: BraketSimulator): + return backend_impl + + @singledispatchmethod + def _run_internal( + self, + task_specification: Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], + shots: Optional[int] = None, + *args, + **kwargs, + ) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: + raise NotImplementedError(f"Unsupported task type {type(task_specification)}") -@singledispatch -def _get_simulator(simulator: Union[str, BraketSimulator]) -> LocalSimulator: - raise TypeError("Simulator must either be a string or a BraketSimulator instance") - - -@_get_simulator.register -def _(backend_name: str): - if backend_name in _simulator_devices: - device_class = _simulator_devices[backend_name].load() - return device_class() - else: - raise ValueError(f"Only the following devices are available {_simulator_devices.keys()}") - - -@_get_simulator.register -def _(backend_impl: BraketSimulator): - return backend_impl - - -@singledispatch -def _run_internal( - task_specification: Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], - simulator: BraketSimulator, - shots: Optional[int] = None, - *args, - **kwargs, -) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: - raise NotImplementedError(f"Unsupported task type {type(task_specification)}") - - -@_run_internal.register -def _( - circuit: Circuit, - simulator: BraketSimulator, - shots: Optional[int] = None, - inputs: Optional[Dict[str, float]] = None, - *args, - **kwargs, -): - 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 {}) + @_run_internal.register + def _( + self, + circuit: Circuit, + shots: Optional[int] = None, + inputs: Optional[Dict[str, float]] = None, + *args, + **kwargs, + ): + 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) + 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) + 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: Program, + shots: Optional[int] = None, + inputs: Optional[Dict[str, float]] = None, + *args, + **kwargs, + ): + simulator = self._delegate + if DeviceActionType.OPENQASM not in simulator.properties.action: + raise NotImplementedError(f"{type(simulator)} does not support OpenQASM programs") + if inputs: + inputs_copy = program.inputs.copy() if program.inputs is not None else {} + inputs_copy.update(inputs) + program = Program( + source=program.source, + inputs=inputs_copy, + ) results = simulator.run(program, shots, *args, **kwargs) return GateModelQuantumTaskResult.from_object(results) - 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) - raise NotImplementedError(f"{type(simulator)} does not support qubit gate-based programs") - - -@_run_internal.register -def _(problem: Problem, simulator: BraketSimulator, shots: Optional[int] = None, *args, **kwargs): - 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 _( - program: Program, - simulator: BraketSimulator, - shots: Optional[int] = None, - inputs: Optional[Dict[str, float]] = None, - *args, - **kwargs, -): - if DeviceActionType.OPENQASM not in simulator.properties.action: - raise NotImplementedError(f"{type(simulator)} does not support OpenQASM programs") - if inputs: - inputs_copy = program.inputs.copy() if program.inputs is not None else {} - inputs_copy.update(inputs) - program = Program( - source=program.source, - inputs=inputs_copy, - ) - results = simulator.run(program, shots, *args, **kwargs) - return GateModelQuantumTaskResult.from_object(results) - - -@_run_internal.register -def _( - program: AnalogHamiltonianSimulation, - simulator: BraketSimulator, - shots: Optional[int] = None, - *args, - **kwargs, -): - 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) + + @_run_internal.register + def _( + self, + program: AnalogHamiltonianSimulation, + shots: Optional[int] = None, + *args, + **kwargs, + ): + 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) diff --git a/src/braket/tracking/tracker.py b/src/braket/tracking/tracker.py index b2a6f173..e902e170 100644 --- a/src/braket/tracking/tracker.py +++ b/src/braket/tracking/tracker.py @@ -15,7 +15,7 @@ from datetime import timedelta from decimal import Decimal -from functools import singledispatch +from functools import singledispatchmethod from typing import Any, Dict, List from braket.tracking.pricing import price_search @@ -64,7 +64,7 @@ def receive_event(self, event: _TaskCreationEvent) -> None: Args: event (_TaskCreationEvent): The event to process. """ - _recieve_internal(event, self._resources) + self._recieve_internal(event) def tracked_resources(self) -> List[str]: """ @@ -170,6 +170,37 @@ 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: + 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 + if event.execution_duration: + duration = timedelta(milliseconds=event.execution_duration) + resources[event.arn]["execution_duration"] = duration + resources[event.arn]["billed_duration"] = max(duration, MIN_SIMULATOR_DURATION) + def _get_qpu_task_cost(task_arn: str, details: dict) -> Decimal: if details["status"] in ["FAILED", "CANCELLED"]: @@ -258,36 +289,3 @@ def _get_simulator_task_cost(task_arn: str, details: dict) -> Decimal: ) return duration_cost - - -@singledispatch -def _recieve_internal(event: _TaskCreationEvent, resources: dict) -> None: - raise ValueError(f"Event type {type(event)} is not supported") - - -@_recieve_internal.register -def _(event: _TaskCreationEvent, resources: dict) -> None: - resources[event.arn] = { - "shots": event.shots, - "device": event.device, - "status": "CREATED", - "job_task": event.is_job_task, - } - - -@_recieve_internal.register -def _(event: _TaskStatusEvent, resources: dict) -> None: - # 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 _(event: _TaskCompletionEvent, resources: dict) -> None: - # 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 - if event.execution_duration: - duration = timedelta(milliseconds=event.execution_duration) - resources[event.arn]["execution_duration"] = duration - resources[event.arn]["billed_duration"] = max(duration, MIN_SIMULATOR_DURATION) 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 e9f2d8ad..4876bbaf 100644 --- a/test/unit_tests/braket/quantum_information/test_pauli_string.py +++ b/test/unit_tests/braket/quantum_information/test_pauli_string.py @@ -102,12 +102,7 @@ def test_weight_n_substrings(string, weight): substrings.append(PauliString(f"{string[0]}{''.join(factors)}")) actual = pauli_string.weight_n_substrings(weight) assert actual == tuple(substrings) - assert len(actual) == n_choose_k(len(nontrivial), weight) - - -def n_choose_k(n, k): - m = min(k, n - k) - return functools.reduce(lambda x, y: x * y, range(m + 1, n + 1)) // (math.factorial(n - m)) + assert len(actual) == math.comb(len(nontrivial), weight) @pytest.mark.parametrize( From e854b27b9a4b051a827deb9811a3f9a785533e15 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 3 Mar 2023 17:32:44 +0000 Subject: [PATCH 0623/1165] prepare release v1.36.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 437bf955..36618637 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.36.0 (2023-03-03) + +### Deprecations and Removals + + * deprecate python 3.7 + +### Bug Fixes and Other Changes + + * Take advantage of some nice Python 3.8 features + * Include setuptools in requirements + ## v1.35.5 (2023-02-16) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 31dc7070..0268824f 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.35.6.dev0" +__version__ = "1.36.0" From d989b7b3d39ed6d6e728ce1ee97140b62986b387 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 3 Mar 2023 17:32:44 +0000 Subject: [PATCH 0624/1165] update development version to v1.36.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 0268824f..9d7a34f0 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.36.0" +__version__ = "1.36.1.dev0" From 1aa8e92c0e1f018696a692bbdd49ce51a9b19eab Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Mon, 6 Mar 2023 18:23:41 -0800 Subject: [PATCH 0625/1165] test: make simulator tracking integ test more robust (#513) --- test/integ_tests/test_cost_tracking.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/integ_tests/test_cost_tracking.py b/test/integ_tests/test_cost_tracking.py index 97cd70e8..d0481438 100644 --- a/test/integ_tests/test_cost_tracking.py +++ b/test/integ_tests/test_cost_tracking.py @@ -14,6 +14,7 @@ from datetime import timedelta import boto3 +from botocore.exceptions import ClientError import pytest from braket.aws import AwsDevice, AwsSession @@ -54,7 +55,13 @@ def test_simulator_tracking(): task0.result() task1.result() - device.run(circuit, shots=100).cancel() + try: + device.run(circuit, shots=100).cancel() + except ClientError as e: + if not e.response["Error"]["Message"].startswith( + "Amazon Braket cannot cancel a quantum task in the COMPLETED status" + ): + raise e quantum_stats = t.quantum_tasks_statistics()[device.arn] assert quantum_stats["shots"] == 300 From 7c43efd067c8818e18df7cf3204f98714aa86c83 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 7 Mar 2023 16:21:23 +0000 Subject: [PATCH 0626/1165] prepare release v1.36.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 36618637..cdeaa345 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.36.1 (2023-03-07) + +### Bug Fixes and Other Changes + + * test: make simulator tracking integ test more robust + ## v1.36.0 (2023-03-03) ### Deprecations and Removals diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 9d7a34f0..bad96663 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.36.1.dev0" +__version__ = "1.36.1" From 90dbe7efc31a8d2fc4d63a5eacbb4b9335f03035 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 7 Mar 2023 16:21:23 +0000 Subject: [PATCH 0627/1165] update development version to v1.36.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 bad96663..0e3ad44b 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.36.1" +__version__ = "1.36.2.dev0" From 3747c872bf5ad5d8c807fa5d84bdc786aa55645c Mon Sep 17 00:00:00 2001 From: Stephen Face <60493521+shpface@users.noreply.github.com> Date: Mon, 13 Mar 2023 11:00:41 -0700 Subject: [PATCH 0628/1165] change: Look for price offer url in env variable (#510) --- src/braket/tracking/pricing.py | 6 +++-- .../braket/tracking/test_pricing.py | 25 +++++++++++++++---- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/braket/tracking/pricing.py b/src/braket/tracking/pricing.py index f2c89ac2..fb4b9469 100644 --- a/src/braket/tracking/pricing.py +++ b/src/braket/tracking/pricing.py @@ -15,6 +15,7 @@ import csv import io +import os from functools import lru_cache from typing import Dict, List @@ -31,8 +32,9 @@ def get_prices(self) -> None: # https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/reading-an-offer.html http = urllib3.PoolManager() - price_url = ( - "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBraket/current/index.csv" + 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 ) response = http.request( "GET", diff --git a/test/unit_tests/braket/tracking/test_pricing.py b/test/unit_tests/braket/tracking/test_pricing.py index dcdf6450..0d6b4348 100644 --- a/test/unit_tests/braket/tracking/test_pricing.py +++ b/test/unit_tests/braket/tracking/test_pricing.py @@ -15,13 +15,16 @@ import io from unittest.mock import patch +import pytest + from braket.tracking.pricing import Pricing -@patch("urllib3.PoolManager") -def test_search_prices(mock_http): - mock_http().request.return_value = io.BytesIO( - b"""line1 +@pytest.fixture +def mock_http(): + with patch("urllib3.PoolManager") as http_mock: + http_mock().request.return_value = io.BytesIO( + b"""line1 line2 line3 line4 @@ -30,8 +33,20 @@ def test_search_prices(mock_http): 1,1 1,2 """ - ) + ) + yield http_mock() + + +def test_search_prices(mock_http): pricer = Pricing() assert pricer.price_search(A="0") == [] assert pricer.price_search(A="1", B="1") == [{"A": "1", "B": "1"}] assert pricer.price_search(A="1") == [{"A": "1", "B": "1"}, {"A": "1", "B": "2"}] + + +@patch.dict("os.environ", {"BRAKET_PRICE_OFFERS_URL": "https://myurl"}) +def test_price_offer_env_var(mock_http): + pricer = Pricing() + pricer.get_prices() + + mock_http.request.assert_called_with("GET", "https://myurl", preload_content=False) From 96d256a22d8a6469ed9251911ed81b0d6cde93b5 Mon Sep 17 00:00:00 2001 From: Stephen Face <60493521+shpface@users.noreply.github.com> Date: Mon, 13 Mar 2023 20:07:20 -0700 Subject: [PATCH 0629/1165] fix: update job test for python3.8 (#514) --- test/integ_tests/test_create_quantum_job.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py index 4eccaa63..74303279 100644 --- a/test/integ_tests/test_create_quantum_job.py +++ b/test/integ_tests/test_create_quantum_job.py @@ -59,7 +59,7 @@ def test_failed_quantum_job(aws_session, capsys): assert errors == "" logs_to_validate = [ "Invoking script with the following command:", - "/usr/local/bin/python3.7 braket_container.py", + "/usr/local/bin/python3.8 braket_container.py", "Running Code As Process", "Test job started!!!!!", "AssertionError", @@ -167,7 +167,7 @@ def test_completed_quantum_job(aws_session, capsys): assert errors == "" logs_to_validate = [ "Invoking script with the following command:", - "/usr/local/bin/python3.7 braket_container.py", + "/usr/local/bin/python3.8 braket_container.py", "Running Code As Process", "Test job started!!!!!", "Test job completed!!!!!", From 09344d2c236803f2260c379b79618016b05e91fe Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 14 Mar 2023 16:22:16 +0000 Subject: [PATCH 0630/1165] prepare release v1.36.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 cdeaa345..e67afcbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.36.2 (2023-03-14) + +### Bug Fixes and Other Changes + + * update job test for python3.8 + * Look for price offer url in env variable + ## v1.36.1 (2023-03-07) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 0e3ad44b..e739c50a 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.36.2.dev0" +__version__ = "1.36.2" From 6ce92ffa6f82a182ddda2f7df6faba52310a43ff Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 14 Mar 2023 16:22:16 +0000 Subject: [PATCH 0631/1165] update development version to v1.36.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 e739c50a..69610888 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.36.2" +__version__ = "1.36.3.dev0" From c0f80936fe4d9f1bb3ccad4d3ee2bd650ecad944 Mon Sep 17 00:00:00 2001 From: Christian Bruun Madsen Date: Wed, 15 Mar 2023 12:53:05 -0600 Subject: [PATCH 0632/1165] documentation: update README information (#516) documentation: update README's supported Python versions and support info. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 24106c5c..eea4e659 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,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.7.2 or greater -Download and install Python 3.7.2 or greater from [Python.org](https://www.python.org/downloads/). +### Python 3.8 or greater +Download and install Python 3.8 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. @@ -234,7 +234,7 @@ tox -e integ-tests -- your-arguments 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/aws/amazon-braket-sdk-python/issues/). -For issues with the Amazon Braket service in general, please use the [Developer Forum](https://forums.aws.amazon.com/forum.jspa?forumID=370). +For other issues or general questions, please ask on the [Quantum Computing Stack Exchange](https://quantumcomputing.stackexchange.com/questions/ask) and add the tag amazon-braket. ### Feedback and Feature Requests From 8a06d45d7aa3fa88c43dbdf5183a948b053f1ace Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 16 Mar 2023 16:22:26 +0000 Subject: [PATCH 0633/1165] prepare release v1.36.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 e67afcbd..151033d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.36.2.post0 (2023-03-16) + +### Documentation Changes + + * update README information + ## v1.36.2 (2023-03-14) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 69610888..e5198d5b 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.36.3.dev0" +__version__ = "1.36.2.post0" From 3a482140760337a93500fae428cccec6cdf7edc2 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 16 Mar 2023 16:22:26 +0000 Subject: [PATCH 0634/1165] update development version to v1.36.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 e5198d5b..69610888 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.36.2.post0" +__version__ = "1.36.3.dev0" From c0cafe12f3e81c924745a09e774d21eb3593faed Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Mon, 20 Mar 2023 15:04:10 -0400 Subject: [PATCH 0635/1165] fix: add DoubleAngledGate as exported symbol (#517) Co-authored-by: Cody Wang --- src/braket/circuits/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py index c3f8570e..a6e185d9 100644 --- a/src/braket/circuits/__init__.py +++ b/src/braket/circuits/__init__.py @@ -19,7 +19,7 @@ observables, result_types, ) -from braket.circuits.angled_gate import AngledGate # noqa: F401 +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 ef7876df8a75d98c71833217d77ac060e0714ffb Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Mon, 20 Mar 2023 15:32:38 -0400 Subject: [PATCH 0636/1165] fix: ignore CompilerDirective when calculating unitary (#518) * fix: ignore CompilerDirective when calculating unitary * fix: whitespace formatting fix * fix: add test for as_unitary case --------- Co-authored-by: Cody Wang --- src/braket/circuits/unitary_calculation.py | 10 ++++++++++ test/unit_tests/braket/circuits/test_circuit.py | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/braket/circuits/unitary_calculation.py b/src/braket/circuits/unitary_calculation.py index 8538aea2..e5b93e11 100644 --- a/src/braket/circuits/unitary_calculation.py +++ b/src/braket/circuits/unitary_calculation.py @@ -15,6 +15,7 @@ import numpy as np +from braket.circuits.compiler_directive import CompilerDirective from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction from braket.circuits.qubit_set import QubitSet @@ -60,11 +61,16 @@ def calculate_unitary(qubit_count: int, instructions: Iterable[Instruction]) -> Raises: TypeError: If `instructions` is not composed only of `Gate` instances, i.e. a circuit with `Noise` operators will raise this error. + Any `CompilerDirective` instructions will be ignored, as these should + not affect the unitary representation of the circuit. """ unitary = np.eye(2**qubit_count, dtype=complex) un_tensor = np.reshape(unitary, qubit_count * [2, 2]) for instr in instructions: + if isinstance(instr.operator, CompilerDirective): + continue + if not isinstance(instr.operator, Gate): raise TypeError("Only Gate operators are supported to build the unitary") @@ -109,6 +115,8 @@ def calculate_unitary_big_endian( Raises: TypeError: If `instructions` is not composed only of `Gate` instances, i.e. a circuit with `Noise` operators will raise this error. + Any `CompilerDirective` instructions will be ignored, as these should + not affect the unitary representation of the circuit. """ qubits_sorted = sorted(qubits) qubit_count = len(qubits_sorted) @@ -119,6 +127,8 @@ def calculate_unitary_big_endian( unitary = np.eye(rank).reshape([2] * 2 * qubit_count) for instruction in instructions: + if isinstance(instruction.operator, CompilerDirective): + continue if not isinstance(instruction.operator, Gate): raise TypeError("Only Gate operators are supported to build the unitary") unitary = multiply_matrix( diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 8df9a394..f191f11e 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -891,6 +891,14 @@ def test_as_unitary_noise_not_apply_returns_expected_unitary(recwarn): ) +def test_as_unitary_with_compiler_directives_returns_expected_unitary(): + circuit = Circuit().add_verbatim_box(Circuit().cphaseshift(2, 1, 0.15).si(3)) + assert np.allclose( + circuit.as_unitary(), + np.kron(gates.Si().to_matrix(), np.kron(gates.CPhaseShift(0.15).to_matrix(), np.eye(2))), + ) + + @pytest.mark.parametrize( "circuit,expected_unitary", [ @@ -1296,6 +1304,14 @@ def test_to_unitary_noise_not_apply_returns_expected_unitary(recwarn): ) +def test_to_unitary_with_compiler_directives_returns_expected_unitary(): + circuit = Circuit().add_verbatim_box(Circuit().cphaseshift(1, 2, 0.15).si(3)) + assert np.allclose( + circuit.to_unitary(), + np.kron(gates.CPhaseShift(0.15).to_matrix(), gates.Si().to_matrix()), + ) + + @pytest.mark.parametrize( "circuit,expected_unitary", [ From 4b401966509a5a320afccc16ee60b4090b5f4296 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 21 Mar 2023 16:22:53 +0000 Subject: [PATCH 0637/1165] prepare release v1.36.3 --- 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 151033d1..7c9a75b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.36.3 (2023-03-21) + +### Bug Fixes and Other Changes + + * ignore CompilerDirective when calculating unitary + * add DoubleAngledGate as exported symbol + ## v1.36.2.post0 (2023-03-16) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 69610888..4d91b54d 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.36.3.dev0" +__version__ = "1.36.3" From 90a0c82e6c5ea4b05e10266110b547c5ec10fb29 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 21 Mar 2023 16:22:53 +0000 Subject: [PATCH 0638/1165] update development version to v1.36.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 4d91b54d..67638735 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.36.3" +__version__ = "1.36.4.dev0" From 95fe07c556c523dcceea511ce1ffd60d89cbb1af Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 23 Mar 2023 13:40:20 -0700 Subject: [PATCH 0639/1165] Export from ahs and timings modules (#520) --- src/braket/ahs/__init__.py | 21 +++++++++++++++++++++ src/braket/ahs/discretization_types.py | 13 +++++++++++++ src/braket/timings/__init__.py | 14 ++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 src/braket/timings/__init__.py diff --git a/src/braket/ahs/__init__.py b/src/braket/ahs/__init__.py index e69de29b..8a9fd266 100644 --- a/src/braket/ahs/__init__.py +++ b/src/braket/ahs/__init__.py @@ -0,0 +1,21 @@ +# 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.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation # noqa: F401 +from braket.ahs.atom_arrangement import AtomArrangement, AtomArrangementItem, SiteType # 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 +from braket.ahs.hamiltonian import Hamiltonian # 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/discretization_types.py b/src/braket/ahs/discretization_types.py index 43405b9a..5a00fc8b 100644 --- a/src/braket/ahs/discretization_types.py +++ b/src/braket/ahs/discretization_types.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 dataclasses import dataclass from typing import Any diff --git a/src/braket/timings/__init__.py b/src/braket/timings/__init__.py new file mode 100644 index 00000000..15b6c5dc --- /dev/null +++ b/src/braket/timings/__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.timings.time_series import TimeSeries, TimeSeriesItem # noqa: F401 From 8264b42ed66c881156edb8208d8be322d467cb6c Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 27 Mar 2023 16:21:06 +0000 Subject: [PATCH 0640/1165] prepare release v1.36.4 --- 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 7c9a75b4..ea636ecb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.36.4 (2023-03-27) + +### Bug Fixes and Other Changes + + * Export from ahs and timings modules + ## v1.36.3 (2023-03-21) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 67638735..36166215 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.36.4.dev0" +__version__ = "1.36.4" From e59dc918f5e8784606028b774d7c587a00d89369 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 27 Mar 2023 16:21:06 +0000 Subject: [PATCH 0641/1165] update development version to v1.36.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 36166215..7efce6ec 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.36.4" +__version__ = "1.36.5.dev0" From 100c60920172a3346c5ee0a81e731966d2594585 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Sat, 1 Apr 2023 14:59:52 +0900 Subject: [PATCH 0642/1165] fix: typo in noise_model.py (#521) seperated -> separated --- src/braket/circuits/noise_model/noise_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/circuits/noise_model/noise_model.py b/src/braket/circuits/noise_model/noise_model.py index 33791bd1..76cdffa2 100644 --- a/src/braket/circuits/noise_model/noise_model.py +++ b/src/braket/circuits/noise_model/noise_model.py @@ -74,7 +74,7 @@ def from_dict(cls, noise_model_item: dict) -> NoiseModelInstruction: @dataclass class NoiseModelInstructions: - """Represents the instructions in a noise model, seperated by type.""" + """Represents the instructions in a noise model, separated by type.""" initialization_noise: List[NoiseModelInstruction] gate_noise: List[NoiseModelInstruction] From 884958d2dca336bf03d8721b6e9284b846d9bb2e Mon Sep 17 00:00:00 2001 From: Angela Guo Date: Mon, 3 Apr 2023 13:03:52 -0700 Subject: [PATCH 0643/1165] fix: support adding a single instruction to moments (#522) * fix: support adding a single instruction to moments * Update src/braket/circuits/moments.py Co-authored-by: Cody Wang * Update src/braket/circuits/moments.py Co-authored-by: Cody Wang --------- Co-authored-by: Cody Wang --- src/braket/circuits/moments.py | 13 +++++++++---- test/unit_tests/braket/circuits/test_moments.py | 9 +++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 2ed6dfbd..5221e26f 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -24,6 +24,7 @@ Mapping, NamedTuple, OrderedDict, + Union, ValuesView, ) @@ -163,17 +164,21 @@ def time_slices(self) -> Dict[int, List[Instruction]]: return time_slices - def add(self, instructions: Iterable[Instruction], noise_index: int = 0) -> None: + def add( + self, instructions: Union[Iterable[Instruction], Instruction], noise_index: int = 0 + ) -> None: """ - Add instructions to self. + Add one or more instructions to self. Args: - instructions (Iterable[Instruction]): Instructions to add to self. The instruction is - added to the max time slice in which the instruction fits. + instructions (Union[Iterable[Instruction], Instruction]): Instructions to add to self. + The instruction is added to the max time slice in which the instruction fits. noise_index (int): the number of noise channels at the same moment. For gates, this is the number of gate_noise channels associated with that gate. For all other noise types, noise_index starts from 0; but for gate noise, it starts from 1. """ + if isinstance(instructions, Instruction): + instructions = [instructions] for instruction in instructions: self._add(instruction, noise_index) diff --git a/test/unit_tests/braket/circuits/test_moments.py b/test/unit_tests/braket/circuits/test_moments.py index d3349fd1..53697321 100644 --- a/test/unit_tests/braket/circuits/test_moments.py +++ b/test/unit_tests/braket/circuits/test_moments.py @@ -42,6 +42,15 @@ def test_add(): assert OrderedDict(moments) == expected +def test_add_single_insturction(): + moments = Moments() + moments.add(h(0)) + + expected = OrderedDict() + expected[MomentsKey(0, QubitSet(0), "gate", 0)] = h(0) + assert OrderedDict(moments) == expected + + def test_default_constructor(): moments = Moments() assert OrderedDict(moments) == OrderedDict() From 0b18969da3762b73bfaceba42fb3414c1bb54abf Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 3 Apr 2023 22:03:12 +0000 Subject: [PATCH 0644/1165] prepare release v1.36.5 --- 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 ea636ecb..3e3fe9fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.36.5 (2023-04-03) + +### Bug Fixes and Other Changes + + * support adding a single instruction to moments + * typo in noise_model.py + ## v1.36.4 (2023-03-27) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 7efce6ec..025eee23 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.36.5.dev0" +__version__ = "1.36.5" From 1d5356707b4d938ba1487865390dd239c8dc7743 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 3 Apr 2023 22:03:12 +0000 Subject: [PATCH 0645/1165] update development version to v1.36.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 025eee23..35537dc2 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.36.5" +__version__ = "1.36.6.dev0" From 5d340e0254913638d25783cbd9d200680a3cf1ed Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 6 Apr 2023 11:52:33 -0700 Subject: [PATCH 0646/1165] feat: upgrade container URIs for python 3.9 (#525) * feat: upgrade container URIs for python 3.9 --- src/braket/jobs/image_uri_config/pl_pytorch.json | 2 +- src/braket/jobs/image_uri_config/pl_tensorflow.json | 2 +- src/braket/jobs/image_uris.py | 6 +++--- test/integ_tests/test_create_quantum_job.py | 4 ++-- test/unit_tests/braket/jobs/test_image_uris.py | 6 +++--- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/braket/jobs/image_uri_config/pl_pytorch.json b/src/braket/jobs/image_uri_config/pl_pytorch.json index 50197b1c..21143b38 100644 --- a/src/braket/jobs/image_uri_config/pl_pytorch.json +++ b/src/braket/jobs/image_uri_config/pl_pytorch.json @@ -1,6 +1,6 @@ { "versions": { - "1.9.1": { + "1.13.1": { "registries": { "us-east-1": "292282985366", "us-west-1": "292282985366", diff --git a/src/braket/jobs/image_uri_config/pl_tensorflow.json b/src/braket/jobs/image_uri_config/pl_tensorflow.json index 9a339c5d..058d28a8 100644 --- a/src/braket/jobs/image_uri_config/pl_tensorflow.json +++ b/src/braket/jobs/image_uri_config/pl_tensorflow.json @@ -1,6 +1,6 @@ { "versions": { - "2.4.1": { + "2.11.0": { "registries": { "us-east-1": "292282985366", "us-west-1": "292282985366", diff --git a/src/braket/jobs/image_uris.py b/src/braket/jobs/image_uris.py index e8e5ebef..df2ef278 100644 --- a/src/braket/jobs/image_uris.py +++ b/src/braket/jobs/image_uris.py @@ -47,11 +47,11 @@ def retrieve_image(framework: Framework, region: str) -> str: registry = _registry_for_region(version_config, region) tag = "" if framework == Framework.PL_TENSORFLOW: - tag = f"{version_config['repository']}:{framework_version}-gpu-py37-cu110-ubuntu18.04" + tag = f"{version_config['repository']}:{framework_version}-gpu-py39-cu112-ubuntu20.04" elif framework == Framework.PL_PYTORCH: - tag = f"{version_config['repository']}:{framework_version}-gpu-py38-cu111-ubuntu20.04" + tag = f"{version_config['repository']}:{framework_version}-gpu-py39-cu117-ubuntu20.04" else: - tag = f"{version_config['repository']}:{framework_version}-cpu-py37-ubuntu18.04" + tag = f"{version_config['repository']}:{framework_version}-cpu-py39-ubuntu22.04" return f"{registry}.dkr.ecr.{region}.amazonaws.com/{tag}" diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py index 74303279..a6a1e34f 100644 --- a/test/integ_tests/test_create_quantum_job.py +++ b/test/integ_tests/test_create_quantum_job.py @@ -59,7 +59,7 @@ def test_failed_quantum_job(aws_session, capsys): assert errors == "" logs_to_validate = [ "Invoking script with the following command:", - "/usr/local/bin/python3.8 braket_container.py", + "/usr/local/bin/python3.9 braket_container.py", "Running Code As Process", "Test job started!!!!!", "AssertionError", @@ -167,7 +167,7 @@ def test_completed_quantum_job(aws_session, capsys): assert errors == "" logs_to_validate = [ "Invoking script with the following command:", - "/usr/local/bin/python3.8 braket_container.py", + "/usr/local/bin/python3.9 braket_container.py", "Running Code As Process", "Test job started!!!!!", "Test job completed!!!!!", diff --git a/test/unit_tests/braket/jobs/test_image_uris.py b/test/unit_tests/braket/jobs/test_image_uris.py index 9f44c7ef..e6b2469e 100644 --- a/test/unit_tests/braket/jobs/test_image_uris.py +++ b/test/unit_tests/braket/jobs/test_image_uris.py @@ -23,19 +23,19 @@ "us-west-1", Framework.BASE, "292282985366.dkr.ecr.us-west-1.amazonaws.com/" - "amazon-braket-base-jobs:1.0-cpu-py37-ubuntu18.04", + "amazon-braket-base-jobs:1.0-cpu-py39-ubuntu22.04", ), ( "us-east-1", Framework.PL_TENSORFLOW, "292282985366.dkr.ecr.us-east-1.amazonaws.com/amazon-braket-tensorflow-jobs:" - "2.4.1-gpu-py37-cu110-ubuntu18.04", + "2.11.0-gpu-py39-cu112-ubuntu20.04", ), ( "us-west-2", Framework.PL_PYTORCH, "292282985366.dkr.ecr.us-west-2.amazonaws.com/" - "amazon-braket-pytorch-jobs:1.9.1-gpu-py38-cu111-ubuntu20.04", + "amazon-braket-pytorch-jobs:1.13.1-gpu-py39-cu117-ubuntu20.04", ), ], ) From 78b8659630b8a4af86fd64b6fc78673d35712506 Mon Sep 17 00:00:00 2001 From: ykharkov <112575584+ykharkov@users.noreply.github.com> Date: Fri, 7 Apr 2023 15:22:38 -0400 Subject: [PATCH 0647/1165] feat: Introduce AHS-related utils from examples repo (#508) * Move AHS-related utils from amazon-braket-examples repo to sdk + add tests * fix identation * Add rabi pulse method * Add counter import * import math lib * black formatting * Fix formatting and imports * Update rabi_pulse to return TimeSeries * flake8 formatting * flake8 * flake8 * docstring formatting, math formula * Updated PR: code refactoring, enhance tests * black formatting * black formatting * formatting fix * formatting fix * Add periodic_signal to TimeSeries * Check result status in get_counts, avg_density * Update tests: get_counts, get_avg_density * Replace asserts by error msg * formatting * Handling empty list in concatenate_list * Refactor + small fixes * Incorporating PR feedback: xfail test, empty concat list * cleanup unused variables * Group staticmethods together * Change RuntimeWarning to logging.warning * flake8 * fix unused variable * Use deepcopy instead of shallow copy * Use deepcopy instead of shallow copy * remove deepcopy * remove math import * Fix avg_density test + remove rabi_pulse * black * Format fix * remove import * add test coverage * black * Update src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py Co-authored-by: Peter Komar * Update src/braket/timings/time_series.py Co-authored-by: Peter Komar * Minor updates: times in periodic_signal, simplify concatenate * Modify periodic_signal function * Update src/braket/ahs/shifting_field.py Co-authored-by: Peter Komar * Update src/braket/timings/time_series.py Co-authored-by: Peter Komar * Typing: import Union * Update src/braket/ahs/driving_field.py Co-authored-by: Peter Komar * Add test coverage: stitch * black * test coverage * test coverage * Update src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py Co-authored-by: Peter Komar * Update src/braket/timings/time_series.py Co-authored-by: Peter Komar * Update get_avg_density * black * formatting * Add stitch method to ShiftingField and DrivingField * Remove redundancy: concatenate_list * Remove concatenate and concatenate_list methods from DrivingField and ShiftingField classes to avoid redundancy * test coverage * minor improvements: formatting * Formatting * Formatting * black * docstring formatting: identation * formatting * formatting * Add more detailed docstrings + add Enum boundary condition * Update concat tests * formatting * Update docstring --------- Co-authored-by: Cody Wang Co-authored-by: Peter Komar --- src/braket/ahs/driving_field.py | 66 +++++- src/braket/ahs/shifting_field.py | 51 +++++ ...iltonian_simulation_quantum_task_result.py | 53 ++++- src/braket/timings/time_series.py | 191 +++++++++++++++++- test/integ_tests/test_cost_tracking.py | 2 +- .../braket/ahs/test_driving_field.py | 65 +++++- .../braket/ahs/test_shifting_field.py | 59 ++++++ ...alog_hamiltonian_simulation_task_result.py | 65 +++++- .../braket/timings/test_time_series.py | 135 ++++++++++++- 9 files changed, 677 insertions(+), 10 deletions(-) mode change 100644 => 100755 test/unit_tests/braket/timings/test_time_series.py diff --git a/src/braket/ahs/driving_field.py b/src/braket/ahs/driving_field.py index e64f0327..4bc4f1e2 100644 --- a/src/braket/ahs/driving_field.py +++ b/src/braket/ahs/driving_field.py @@ -18,7 +18,7 @@ from braket.ahs.discretization_types import DiscretizationProperties from braket.ahs.field import Field from braket.ahs.hamiltonian import Hamiltonian -from braket.timings.time_series import TimeSeries +from braket.timings.time_series import StitchBoundaryCondition, TimeSeries class DrivingField(Hamiltonian): @@ -83,6 +83,34 @@ def detuning(self) -> Field: r"""Field: global detuning (:math:`\Delta(t)`). Time is in s, and value is in rad/s.""" return self._detuning + def stitch( + self, other: DrivingField, boundary: StitchBoundaryCondition = StitchBoundaryCondition.MEAN + ) -> DrivingField: + """Stitches two driving fields based on TimeSeries.stitch method. + The time points of the second DrivingField are shifted such that the first time point of + the second DrifingField coincides with the last time point of the first DrivingField. + The boundary point value is handled according to StitchBoundaryCondition argument value. + + Args: + other (DrivingField): 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. + + 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) + + return DrivingField(amplitude=amplitude, detuning=detuning, phase=phase) + def discretize(self, properties: DiscretizationProperties) -> DrivingField: """Creates a discretized version of the Hamiltonian. @@ -110,3 +138,39 @@ def discretize(self, properties: DiscretizationProperties) -> DrivingField: return DrivingField( amplitude=discretized_amplitude, phase=discretized_phase, detuning=discretized_detuning ) + + @staticmethod + def from_lists( + times: List[float], amplitudes: List[float], detunings: List[float], phases: List[float] + ) -> DrivingField: + """ + 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. + + Args: + times (List[float]): The time points of the driving field + amplitudes (List[float]): The values of the amplitude + detunings (List[float]): The values of the detuning + phases (List[float]): The values of the phase + """ + if not (len(times) == len(amplitudes) == len(detunings) == len(phases)): + raise ValueError( + f"The lengths of the lists for times({len(times)}), amplitudes({len(amplitudes)}),\ + detunings({len(detunings)}) and phases({len(phases)}) are not equal" + ) + + amplitude = TimeSeries() + detuning = TimeSeries() + phase = TimeSeries() + + for t, amplitude_value, detuning_value, phase_value in zip( + times, amplitudes, detunings, phases + ): + amplitude.put(t, amplitude_value) + detuning.put(t, detuning_value) + phase.put(t, phase_value) + + drive = DrivingField(amplitude=amplitude, detuning=detuning, phase=phase) + + return drive diff --git a/src/braket/ahs/shifting_field.py b/src/braket/ahs/shifting_field.py index 2801a2a9..6759e119 100644 --- a/src/braket/ahs/shifting_field.py +++ b/src/braket/ahs/shifting_field.py @@ -18,6 +18,8 @@ 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): @@ -59,6 +61,55 @@ def magnitude(self) -> Field: 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 + + 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. + + Returns: + ShiftingField: The stitched ShiftingField object. + """ + 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. 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 09cb5061..86bbb6f0 100644 --- a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py +++ b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py @@ -13,9 +13,10 @@ from __future__ import annotations +from collections import Counter from dataclasses import dataclass from enum import Enum -from typing import List +from typing import Dict, List import numpy as np @@ -98,6 +99,56 @@ def _get_measurements(cls, result: AnalogHamiltonianSimulationTaskResult) -> Lis measurements.append(ShotResult(status, pre_sequence, post_sequence)) return measurements + def get_counts(self) -> Dict[str, int]: + """Aggregate state counts from AHS shot results. + + 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. + + Notes: We use the following convention to denote the state of an atom (site): + e: empty site + r: Rydberg state atom + g: ground state atom + """ + + state_counts = Counter() + states = ["e", "r", "g"] + for shot in self.measurements: + if shot.status == AnalogHamiltonianSimulationShotStatus.SUCCESS: + pre = shot.pre_sequence + 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) + ] + state = "".join(map(lambda s_idx: states[s_idx], state_idx)) + state_counts.update((state,)) + + return dict(state_counts) + + def get_avg_density(self) -> np.ndarray: + """Get the average Rydberg state densities from the result + + Returns: + ndarray (float): The average densities from the result + """ + + counts = self.get_counts() + + N_ryd, N_ground = [], [] + for shot, count in counts.items(): + N_ryd.append([count if s == "r" else 0 for s in shot]) + N_ground.append([count if s == "g" else 0 for s in shot]) + + 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 + def _equal_sequences(sequence0: np.ndarray, sequence1: np.ndarray) -> bool: if sequence0 is None and sequence1 is None: diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py index e4f4d852..6790434e 100644 --- a/src/braket/timings/time_series.py +++ b/src/braket/timings/time_series.py @@ -16,8 +16,9 @@ from collections import OrderedDict from dataclasses import dataclass from decimal import Decimal +from enum import Enum from numbers import Number -from typing import Iterator, List +from typing import Iterator, List, Union @dataclass @@ -26,6 +27,12 @@ class TimeSeriesItem: value: Number +class StitchBoundaryCondition(str, Enum): + MEAN = "mean" + LEFT = "left" + RIGHT = "right" + + class TimeSeries: def __init__(self): self._series = OrderedDict() @@ -87,6 +94,164 @@ def _ensure_sorted(self) -> None: self._series = OrderedDict(sorted(self._series.items())) self._sorted = True + @staticmethod + def from_lists(times: List[float], values: List[float]) -> TimeSeries: + """Create a time series from the list of time and value points + + Args: + times (List[float]): list of time points + values (List[float]): list of value points + + Returns: + TimeSeries: time series constructed from lists + """ + if len(times) != len(values): + raise ValueError( + f"The lengths of the times({len(times)})\ + and values({len(values)}) lists are not equal." + ) + + ts = TimeSeries() + for t, v in zip(times, values): + ts.put(t, v) + return ts + + @staticmethod + def constant_like(times: Union[List[float], TimeSeries], constant: float = 0.0) -> TimeSeries: + """Obtain a constant time series given the list of time points and the constant values + + Args: + times (List[float]): list of time points + + Returns: + TimeSeries: A constant time series + """ + ts = TimeSeries() + for t in times: + ts.put(t, constant) + return ts + + def concatenate(self, other: TimeSeries) -> TimeSeries: + """Concatenates two time series ino to a single time series. + The time points in the final time series are obtained by concatenating + two lists of time points from the first and the second time series. + Similarly, the values in the final time series is a concatenated list + of the values in the first and the second time series. + + Args: + other (TimeSeries): The second time series to be concatenated + + Returns: + TimeSeries: The concatenated time series. + + 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. + + Example: + time_series_1 = TimeSeries.from_lists(times=[0, 0.1], values=[1, 2]) + time_series_2 = TimeSeries.from_lists(times=[0.2, 0.3], values=[4, 5]) + + concat_ts = time_series_1.concatenate(time_series_2) + + Result: + 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( + "The time points in the first TimeSeries must be strictly smaller \ + then the time points in the second 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): + new_time_series.put(t, v) + + return new_time_series + + def stitch( + self, other: TimeSeries, boundary: StitchBoundaryCondition = StitchBoundaryCondition.MEAN + ) -> TimeSeries: + """Stitch two time series to a single time series. The time points of the + second time series are shifted such that the first time point of the second series + coincides with the last time point of the first series. + The boundary point value is handled according to StitchBoundaryCondition argument value. + + Args: + other (TimeSeries): The second time series 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. + + Returns: + TimeSeries: The stitched time series. + + 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 len(self.times()) == 0: + return TimeSeries.from_lists(times=other.times(), values=other.values()) + if len(other.times()) == 0: + return TimeSeries.from_lists(times=self.times(), values=self.values()) + + new_time_series = TimeSeries() + left_t, right_t = self.times()[-1], other.times()[0] + other_times = [t - right_t + left_t for t in other.times()] + new_times = self.times() + other_times[1:] + + left, right = self.values()[-1], other.values()[0] + if boundary == StitchBoundaryCondition.MEAN: + bndry_val = 0.5 * sum([left, right]) + elif boundary == StitchBoundaryCondition.LEFT: + bndry_val = left + elif boundary == StitchBoundaryCondition.RIGHT: + bndry_val = right + else: + raise ValueError( + f"Boundary handler value {boundary} is not allowed. \ + Possible options are: 'mean', 'left', 'right'." + ) + + 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) + + return new_time_series + def discretize(self, time_resolution: Decimal, value_resolution: Decimal) -> TimeSeries: """Creates a discretized version of the time series, rounding all times and values to the closest multiple of the @@ -107,6 +272,29 @@ def discretize(self, time_resolution: Decimal, value_resolution: Decimal) -> Tim ) return discretized_ts + @staticmethod + def periodic_signal(times: List[float], values: List[float], num_repeat: int = 1): + """Create a periodic time series by repeating the same block multiple times. + + Args: + times (float): List of time points in a single block + values (float): Values for the time series in a single block + num_repeat (int): Number of block repeatitions + + 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") + new_time_series = TimeSeries() + + repeating_block = TimeSeries.from_lists(times=times, values=values) + for index in range(num_repeat): + new_time_series = new_time_series.stitch(repeating_block) + + return new_time_series + # TODO: Verify if this belongs here. def _all_close(first: TimeSeries, second: TimeSeries, tolerance: Number = 1e-7) -> bool: @@ -116,7 +304,6 @@ def _all_close(first: TimeSeries, second: TimeSeries, tolerance: Number = 1e-7) subtracted from each-other, support getting the absolute value, and can be compared against the tolerance. - Args: first (TimeSeries): A time series. second (TimeSeries): A time series. diff --git a/test/integ_tests/test_cost_tracking.py b/test/integ_tests/test_cost_tracking.py index d0481438..757e8d79 100644 --- a/test/integ_tests/test_cost_tracking.py +++ b/test/integ_tests/test_cost_tracking.py @@ -14,8 +14,8 @@ from datetime import timedelta import boto3 -from botocore.exceptions import ClientError import pytest +from botocore.exceptions import ClientError from braket.aws import AwsDevice, AwsSession from braket.circuits import Circuit diff --git a/test/unit_tests/braket/ahs/test_driving_field.py b/test/unit_tests/braket/ahs/test_driving_field.py index 8472a7e1..1597c765 100644 --- a/test/unit_tests/braket/ahs/test_driving_field.py +++ b/test/unit_tests/braket/ahs/test_driving_field.py @@ -13,12 +13,13 @@ from unittest.mock import Mock +import numpy as np import pytest from braket.ahs.driving_field import DrivingField from braket.ahs.field import Field from braket.ahs.hamiltonian import Hamiltonian -from braket.timings.time_series import TimeSeries +from braket.timings.time_series import StitchBoundaryCondition, TimeSeries @pytest.fixture @@ -107,6 +108,68 @@ def test_discretize(): assert discretized_field.detuning == detuning_mock.discretize.return_value +def test_from_lists(): + times = [0, 0.1, 0.2] + amplitudes = [0.5, 0.8, 0.9] + detunings = [0.3, 0.7, 0.6] + phases = [0.2, 0.4, 0.6] + + dr_field = DrivingField.from_lists(times, amplitudes, detunings, phases) + assert dr_field.amplitude.time_series.values() == amplitudes + assert dr_field.detuning.time_series.values() == detunings + assert dr_field.phase.time_series.values() == phases + + assert dr_field.amplitude.time_series.times() == times + assert dr_field.detuning.time_series.times() == times + assert dr_field.phase.time_series.times() == times + + +@pytest.mark.xfail(raises=ValueError) +def test_from_lists_not_eq_length(): + times = [0, 0.1] + amplitudes = [0.5, 0.8, 0.9] + detunings = [0.3, 0.7, 0.6] + phases = [0.2, 0.4, 0.6] + + DrivingField.from_lists(times, amplitudes, detunings, phases) + + +def test_stitch(): + dr_field_1 = DrivingField.from_lists( + times=[0, 0.1, 0.2], + amplitudes=[1, 2, 3.5], + detunings=[1.2, 3.4, 5.6], + phases=[2.1, 4.2, 1.3], + ) + dr_field_2 = DrivingField.from_lists( + times=[0.4, 0.5, 0.6], + amplitudes=[0.11, 0.22, 0.35], + detunings=[1.12, 3.14, 5.16], + phases=[2.11, 4.12, 1.13], + ) + + new_dr = dr_field_1.stitch(dr_field_2, boundary=StitchBoundaryCondition.RIGHT) + new_times = new_dr.amplitude.time_series.times() + + amplitudes_1 = dr_field_1.amplitude.time_series.values() + amplitudes_2 = dr_field_2.amplitude.time_series.values() + new_amplitudes = new_dr.amplitude.time_series.values() + + detunings_1 = dr_field_1.detuning.time_series.values() + detunings_2 = dr_field_2.detuning.time_series.values() + new_detunings = new_dr.detuning.time_series.values() + + phases_1 = dr_field_1.phase.time_series.values() + phases_2 = dr_field_2.phase.time_series.values() + new_phases = new_dr.phase.time_series.values() + + expected_times = [0, 0.1, 0.2, 0.3, 0.4] + np.testing.assert_almost_equal(new_times, expected_times) + np.testing.assert_almost_equal(new_amplitudes, amplitudes_1[:-1] + amplitudes_2) + np.testing.assert_almost_equal(new_detunings, detunings_1[:-1] + detunings_2) + np.testing.assert_almost_equal(new_phases, phases_1[:-1] + phases_2) + + @pytest.mark.xfail(raises=ValueError) def test_iadd_to_itself(default_driving_field): default_driving_field += Hamiltonian(Mock()) diff --git a/test/unit_tests/braket/ahs/test_shifting_field.py b/test/unit_tests/braket/ahs/test_shifting_field.py index 3af466ba..0249b875 100644 --- a/test/unit_tests/braket/ahs/test_shifting_field.py +++ b/test/unit_tests/braket/ahs/test_shifting_field.py @@ -17,6 +17,7 @@ from braket.ahs.hamiltonian import Hamiltonian from braket.ahs.shifting_field import ShiftingField +from braket.timings.time_series import StitchBoundaryCondition @pytest.fixture @@ -63,6 +64,64 @@ def test_iadd_to_other(default_shifting_field): assert other.terms == expected +def test_from_lists(): + times = [0, 0.1, 0.2, 0.3] + 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) + assert sh_field.magnitude.time_series.values() == glob_amplitude + assert sh_field.magnitude.pattern.series == pattern + + assert sh_field.magnitude.time_series.times() == times + + +@pytest.mark.xfail(raises=ValueError) +def test_from_lists_not_eq_length(): + times = [0, 0.1, 0.2] + 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) + + +def test_stitch(): + times_1 = [0, 0.1, 0.2, 0.3] + glob_amplitude_1 = [0.5, 0.8, 0.9, 1.0] + pattern_1 = [0.3, 0.7, 0.6, -0.5, 0, 1.6] + + times_2 = [0, 0.1, 0.2, 0.3] + 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) + + new_sh_field = sh_field_1.stitch(sh_field_2, boundary=StitchBoundaryCondition.LEFT) + + expected_times = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6] + expected_amplitude = glob_amplitude_1 + glob_amplitude_2[1:] + assert new_sh_field.magnitude.time_series.times() == expected_times + assert new_sh_field.magnitude.time_series.values() == expected_amplitude + assert new_sh_field.magnitude.pattern == sh_field_1.magnitude.pattern + + +@pytest.mark.xfail(raises=ValueError) +def test_stitch_not_eq_pattern(): + times_1 = [0, 0.1, 0.2, 0.3] + glob_amplitude_1 = [0.5, 0.8, 0.9, 1.0] + pattern_1 = [0.3, 0.7, 0.6, -0.5, 0, 1.6] + + times_2 = [0.4, 0.5, 0.6, 0.7] + 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.stitch(sh_field_2) + + def test_discretize(): magnitude_mock = Mock() mock_properties = 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 c977a0b3..3d60843d 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 @@ -55,7 +55,7 @@ def partial_success_measurement(): @pytest.fixture -def error_measurement(): +def failed_measurement(): return AnalogHamiltonianSimulationShotMeasurement( shotMetadata=AnalogHamiltonianSimulationShotMetadata(shotStatus="Failure"), shotResult=AnalogHamiltonianSimulationShotResult(preSequence=None, postSequence=None), @@ -63,8 +63,30 @@ def error_measurement(): @pytest.fixture -def measurements(success_measurement, partial_success_measurement, error_measurement): - return [success_measurement, partial_success_measurement, error_measurement] +def success_measurement_extended(): + return AnalogHamiltonianSimulationShotMeasurement( + shotMetadata=AnalogHamiltonianSimulationShotMetadata(shotStatus="Success"), + shotResult=AnalogHamiltonianSimulationShotResult( + preSequence=[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + postSequence=[1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1], + ), + ) + + +@pytest.fixture +def measurements(success_measurement, partial_success_measurement, failed_measurement): + return [success_measurement, partial_success_measurement, failed_measurement] + + +@pytest.fixture +def measurements_extended( + success_measurement, + success_measurement_extended, +): + return [ + success_measurement, + success_measurement_extended, + ] @pytest.fixture @@ -85,6 +107,15 @@ def result_str_2(task_metadata, measurements): return result.json() +@pytest.fixture +def result_str_3(task_metadata, measurements_extended): + result = AnalogHamiltonianSimulationTaskResult( + taskMetadata=task_metadata, + measurements=measurements_extended, + ) + return result.json() + + def validate_result_from_str_1(result): assert len(result.measurements) == 3 assert result.measurements[0].status == AnalogHamiltonianSimulationShotStatus.SUCCESS @@ -134,6 +165,34 @@ def test_equality(task_metadata, result_str_1, result_str_2): assert result_1 != non_result +def test_get_counts(result_str_3): + result = AnalogHamiltonianSimulationQuantumTaskResult.from_string(result_str_3) + + counts = result.get_counts() + # Partial Success and Failure result status are mapped to counts = None + expected_counts = {"rrrgeggrrgr": 1, "grggrgrrrrg": 1} + assert counts == expected_counts + + +def test_get_counts_failed_task(task_metadata): + measurement = ShotResult(AnalogHamiltonianSimulationShotStatus.FAILURE, [], []) + result = AnalogHamiltonianSimulationQuantumTaskResult( + task_metadata=task_metadata, measurements=[measurement] + ) + + counts = result.get_counts() + expected_counts = {} + assert counts == expected_counts + + +def test_avg_density(result_str_3): + result = AnalogHamiltonianSimulationQuantumTaskResult.from_string(result_str_3) + + density = result.get_avg_density() + expected_density = [0.5, 1.0, 0.5, 0.0, 1.0, 0.0, 0.5, 1.0, 1.0, 0.5, 0.5] + np.testing.assert_almost_equal(density, expected_density) + + @pytest.mark.parametrize( "shot0, shot1", [ diff --git a/test/unit_tests/braket/timings/test_time_series.py b/test/unit_tests/braket/timings/test_time_series.py old mode 100644 new mode 100755 index 8f322b5f..8c3a6f28 --- a/test/unit_tests/braket/timings/test_time_series.py +++ b/test/unit_tests/braket/timings/test_time_series.py @@ -15,7 +15,7 @@ import pytest -from braket.timings.time_series import TimeSeries, _all_close +from braket.timings.time_series import StitchBoundaryCondition, TimeSeries, _all_close @pytest.fixture @@ -54,6 +54,139 @@ def test_get_sorted(default_values, default_time_series): assert default_time_series.values() == [item[1] for item in sorted_values] +def test_constant_like(): + times = list(range(10)) + constant_ts = TimeSeries.constant_like(times, constant=3.14) + assert times == constant_ts.times() + assert constant_ts.values() == [3.14] * len(times) + + +def test_periodic_signal(): + times = list(range(4)) + values = [0, 1, 3, 0] + new_ts = TimeSeries.periodic_signal(times=times, values=values, num_repeat=3) + expected_times = list(range(10)) + expected_values = [0, 1, 3, 0, 1, 3, 0, 1, 3, 0] + + assert new_ts.times() == expected_times + assert new_ts.values() == expected_values + + +@pytest.mark.xfail(raises=ValueError) +def test_periodic_signal_not_eq_length(): + times = list(range(5)) + values = [0.5, 1, 1, 0] + TimeSeries.periodic_signal(times=times, values=values, num_repeat=3) + + +def test_concatenate(): + times_1 = list(range(4)) + values_1 = [0.5, 1, 1, 0] + time_series_1 = TimeSeries.from_lists(times=times_1, values=values_1) + + times_2 = list(range(4, 8)) + values_2 = [-0.5, -1, -1, 0] + time_series_2 = TimeSeries.from_lists(times=times_2, values=values_2) + + new_ts = time_series_1.concatenate(time_series_2) + + assert new_ts.times() == times_1 + times_2 + assert new_ts.values() == values_1 + values_2 + + new_ts = time_series_1.concatenate(TimeSeries()) + assert new_ts.times() == times_1 + assert new_ts.values() == values_1 + + new_ts = TimeSeries().concatenate(time_series_1) + assert new_ts.times() == times_1 + assert new_ts.values() == values_1 + + new_ts = TimeSeries().concatenate(TimeSeries()) + assert new_ts.times() == [] + assert new_ts.values() == [] + + +@pytest.mark.xfail(raises=ValueError) +def test_concatenate_not_ordered(): + times_1 = list(range(4)) + values_1 = [0.5, 1, 1, 0] + time_series_1 = TimeSeries.from_lists(times=times_1, values=values_1) + + times_2 = list(range(4)) + values_2 = [-0.5, -1, -1, 0] + time_series_2 = TimeSeries.from_lists(times=times_2, values=values_2) + + time_series_1.concatenate(time_series_2) + + +def test_from_lists(): + times = list(range(4)) + values = [0.5, 1, 1, 0] + ts = TimeSeries.from_lists(times=times, values=values) + assert ts.times() == times + assert ts.values() == values + + +@pytest.mark.xfail(raises=ValueError) +def test_from_lists_not_equal_size(): + times = list(range(4)) + values = [0.5, 1, 1] + TimeSeries.from_lists(times=times, values=values) + + +def test_stitch(): + times_1 = list(range(4)) + values_1 = [0.5, 1, 1, 0] + time_series_1 = TimeSeries.from_lists(times=times_1, values=values_1) + + times_2 = list(range(4)) + values_2 = [-0.5, -1, -1, 0] + time_series_2 = TimeSeries.from_lists(times=times_2, values=values_2) + + new_ts_mean = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.MEAN) + new_ts_left = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.LEFT) + new_ts_right = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.RIGHT) + + excepted_times = list(range(7)) + assert new_ts_mean.times() == excepted_times + assert new_ts_left.times() == excepted_times + assert new_ts_right.times() == excepted_times + + assert new_ts_mean.values() == [0.5, 1, 1, -0.25, -1, -1, 0] + assert new_ts_left.values() == [0.5, 1, 1, 0, -1, -1, 0] + assert new_ts_right.values() == [0.5, 1, 1, -0.5, -1, -1, 0] + + +def test_stitch_empty_ts(): + times = list(range(4)) + values = [0.5, 1, 1, 0] + time_series = TimeSeries.from_lists(times=times, values=values) + new_ts = time_series.stitch(TimeSeries()) + assert new_ts.times() == times + assert new_ts.values() == values + + new_ts = TimeSeries().stitch(TimeSeries()) + assert new_ts.times() == [] + assert new_ts.values() == [] + + new_ts = TimeSeries().stitch(time_series) + assert new_ts.times() == time_series.times() + assert new_ts.values() == time_series.values() + + +@pytest.mark.xfail(raises=ValueError) +def test_stitch_wrong_bndry_value(): + times_1 = list(range(4)) + values_1 = [0.5, 1, 1, 0] + time_series_1 = TimeSeries.from_lists(times=times_1, values=values_1) + + times_2 = list(range(4)) + values_2 = [-0.5, -1, -1, 0] + time_series_2 = TimeSeries.from_lists(times=times_2, values=values_2) + + time_series_1.stitch(time_series_2, boundary="average") + + @pytest.mark.parametrize( "time_res, expected_times", [ From 688ee6bfbd2697e4fd34b32cb66153180e5e4c61 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 7 Apr 2023 12:34:14 -0700 Subject: [PATCH 0648/1165] Use device-specific poll interval if provided (#528) --- src/braket/aws/aws_device.py | 31 +++++++++++++------ test/unit_tests/braket/aws/test_aws_device.py | 30 ++++++++++++++++++ 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 9bd4d9c7..4782421a 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -82,7 +82,7 @@ def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): self._arn = arn self._properties = None self._provider_name = None - self._topology_graph = None + self._poll_interval_seconds = None self._type = None self._aws_session = self._get_session_and_initialize(aws_session or AwsSession()) self._ports = None @@ -101,7 +101,7 @@ def run( s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, shots: Optional[int] = None, poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, - poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, + poll_interval_seconds: Optional[float] = None, inputs: Optional[Dict[str, float]] = None, *aws_quantum_task_args, **aws_quantum_task_kwargs, @@ -122,7 +122,8 @@ def run( poll_timeout_seconds (float): The polling timeout for `AwsQuantumTask.result()`, in seconds. Default: 5 days. poll_interval_seconds (float): The polling interval for `AwsQuantumTask.result()`, - in seconds. Default: 1 second. + in seconds. Defaults to the ``getTaskPollIntervalMillis`` value specified in + ``self.properties.service`` (divided by 1000) if provided, otherwise 1 second. 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: {}. @@ -172,7 +173,7 @@ def run( or (self._aws_session.default_bucket(), "tasks"), shots if shots is not None else self._default_shots, poll_timeout_seconds=poll_timeout_seconds, - poll_interval_seconds=poll_interval_seconds, + poll_interval_seconds=poll_interval_seconds or self._poll_interval_seconds, inputs=inputs, *aws_quantum_task_args, **aws_quantum_task_kwargs, @@ -231,8 +232,9 @@ def run_batch( Also the maximum number of thread pool workers for the batch. Default: 100 poll_timeout_seconds (float): The polling timeout for `AwsQuantumTask.result()`, in seconds. Default: 5 days. - poll_interval_seconds (float): The polling interval for results in seconds. - Default: 1 second. + 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[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: {}. @@ -258,7 +260,7 @@ def run_batch( 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, - poll_interval_seconds=poll_interval_seconds, + poll_interval_seconds=poll_interval_seconds or self._poll_interval_seconds, inputs=inputs, *aws_quantum_task_args, **aws_quantum_task_kwargs, @@ -321,7 +323,14 @@ 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 = metadata.get("deviceCapabilities") + 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 + ) + self._topology_graph = None self._frames = None self._ports = None @@ -412,7 +421,7 @@ 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""" - return BraketSchemaBase.parse_raw_schema(self._properties) + return self._properties @property def topology_graph(self) -> DiGraph: @@ -429,7 +438,9 @@ def topology_graph(self) -> DiGraph: >>> print(device.topology_graph.edges) """ - return self._construct_topology_graph() + if not self._topology_graph: + self._topology_graph = self._construct_topology_graph() + return self._topology_graph def _construct_topology_graph(self) -> DiGraph: """ diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index ef1baed7..a4674e07 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -1058,6 +1058,33 @@ def test_default_bucket_not_called(aws_quantum_task_mock, device, circuit, s3_de device._aws_session.default_bucket.assert_not_called() +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_device_poll_interval_kwargs( + aws_quantum_task_mock, aws_session, circuit, s3_destination_folder +): + poll_interval_seconds = 200 + capabilities = MOCK_GATE_MODEL_QPU_CAPABILITIES_1 + capabilities.service.getTaskPollIntervalMillis = poll_interval_seconds + properties = { + "deviceName": "Aspen-10", + "deviceType": "QPU", + "providerName": "provider1", + "deviceStatus": "OFFLINE", + "deviceCapabilities": capabilities.json(), + } + aws_session.get_device.return_value = properties + _run_and_assert( + aws_quantum_task_mock, + lambda arn: AwsDevice(arn, aws_session), + circuit, + s3_destination_folder, + 100, + 86400, + poll_interval_seconds / 1000.0, + extra_kwargs={"bar": 1, "baz": 2}, + ) + + @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") def test_run_with_shots_poll_timeout_kwargs( aws_quantum_task_mock, device, circuit, s3_destination_folder @@ -1539,3 +1566,6 @@ def test_device_topology_graph_data(get_device_data, expected_graph, arn): mock_session.region = RIGETTI_REGION device = AwsDevice(arn, mock_session) assert nx.is_isomorphic(device.topology_graph, expected_graph) + new_val = "new_val" + device._topology_graph = new_val + assert device.topology_graph == new_val From a88567a6477f25ed1468476e557fe4ad8e4bc8f9 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 11 Apr 2023 10:12:56 -0700 Subject: [PATCH 0649/1165] fix: correct the python version in the container integ tests to the correct one (#531) fix: correct the python version in the container integ tests to the correct one --- test/integ_tests/test_create_quantum_job.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py index a6a1e34f..74303279 100644 --- a/test/integ_tests/test_create_quantum_job.py +++ b/test/integ_tests/test_create_quantum_job.py @@ -59,7 +59,7 @@ def test_failed_quantum_job(aws_session, capsys): assert errors == "" logs_to_validate = [ "Invoking script with the following command:", - "/usr/local/bin/python3.9 braket_container.py", + "/usr/local/bin/python3.8 braket_container.py", "Running Code As Process", "Test job started!!!!!", "AssertionError", @@ -167,7 +167,7 @@ def test_completed_quantum_job(aws_session, capsys): assert errors == "" logs_to_validate = [ "Invoking script with the following command:", - "/usr/local/bin/python3.9 braket_container.py", + "/usr/local/bin/python3.8 braket_container.py", "Running Code As Process", "Test job started!!!!!", "Test job completed!!!!!", From c48dcfe97f1084dac0986c0a33cfb1abf5122fe1 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 11 Apr 2023 17:38:42 +0000 Subject: [PATCH 0650/1165] prepare release v1.37.0 --- CHANGELOG.md | 12 ++++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e3fe9fb..47102370 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## v1.37.0 (2023-04-11) + +### Features + + * Introduce AHS-related utils from examples repo + * upgrade container URIs for python 3.9 + +### Bug Fixes and Other Changes + + * correct the python version in the container integ tests to the correct one + * Use device-specific poll interval if provided + ## v1.36.5 (2023-04-03) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 35537dc2..eef48f9e 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.36.6.dev0" +__version__ = "1.37.0" From b0e6980dc7427cabfb086a2bc6598635d916ee2e Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 11 Apr 2023 17:38:42 +0000 Subject: [PATCH 0651/1165] update development version to v1.37.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 eef48f9e..c26e3964 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.37.0" +__version__ = "1.37.1.dev0" From c5cf3cbf1026dcdf54018539ee5ae86ebf65f5fc Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 20 Apr 2023 15:52:15 -0700 Subject: [PATCH 0652/1165] infra: speed up unit testing by automatically parallelizing the CPU workers for test runs * infra: speed up unit testing by automatically parallelizing the CPU workers for test runs --- setup.cfg | 5 +++++ setup.py | 1 - tox.ini | 6 +----- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/setup.cfg b/setup.cfg index 6dd9b7de..bc84fcb8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,7 +4,12 @@ test=pytest [tool:pytest] xfail_strict = true addopts = + --cov-report term-missing + --cov-report html + --cov-report xml + --cov=braket --verbose + -n auto testpaths = test/unit_tests [isort] diff --git a/setup.py b/setup.py index e2400f53..fb47decc 100644 --- a/setup.py +++ b/setup.py @@ -45,7 +45,6 @@ "test": [ "black", "botocore", - "coverage==5.5", "flake8<=5.0.4", "isort", "jsonschema==3.2.0", diff --git a/tox.ini b/tox.ini index 9c4e3cd5..02e61530 100644 --- a/tox.ini +++ b/tox.ini @@ -7,11 +7,7 @@ basepython = python3 deps = {[test-deps]deps} commands = - coverage run -m pytest {posargs} - coverage combine - coverage report - coverage html - coverage xml + pytest {posargs} extras = test [testenv:integ-tests] From fd244421fa4096124cee9bb067cea03092e625f4 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 24 Apr 2023 15:22:13 -0700 Subject: [PATCH 0653/1165] test: order terminal states for quantum jobs (#536) --- src/braket/aws/aws_quantum_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index 6b0c181d..d5a0341c 100644 --- a/src/braket/aws/aws_quantum_job.py +++ b/src/braket/aws/aws_quantum_job.py @@ -49,7 +49,7 @@ class AwsQuantumJob(QuantumJob): """Amazon Braket implementation of a quantum job.""" - TERMINAL_STATES = {"COMPLETED", "FAILED", "CANCELLED"} + TERMINAL_STATES = {"CANCELLED", "COMPLETED", "FAILED"} RESULTS_FILENAME = "results.json" RESULTS_TAR_FILENAME = "model.tar.gz" LOG_GROUP = "/aws/braket/jobs" From e188b9aef905ffb941b2d538718d413c230e8f63 Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Tue, 25 Apr 2023 09:56:31 -0700 Subject: [PATCH 0654/1165] fix: Mock task creation against QPUs for tracker (#532) --- test/integ_tests/test_cost_tracking.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/integ_tests/test_cost_tracking.py b/test/integ_tests/test_cost_tracking.py index 757e8d79..8f3d7a4b 100644 --- a/test/integ_tests/test_cost_tracking.py +++ b/test/integ_tests/test_cost_tracking.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. +import unittest.mock as mock from datetime import timedelta import boto3 @@ -34,7 +35,17 @@ def test_qpu_tracking(qpu): circuit = Circuit().h(0) with Tracker() as t: - AwsDevice(qpu).run(circuit, shots=10) + device = AwsDevice(qpu) + # Mock out task creation against the service + device._aws_session.braket_client.create_quantum_task = mock.Mock( + return_value={ + "quantumTaskArn": ( + f"arn:aws:braket:{device._aws_session.region}" + ":1234567890:quantum-task/e9e6bd31-5ba3-4027-948d-93c5f12e2942" + ) + } + ) + device.run(circuit, shots=10) assert t.qpu_tasks_cost() > 0 From 00f8834ffce57cff85b16951674e8a25360dee6b Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 25 Apr 2023 11:26:56 -0700 Subject: [PATCH 0655/1165] test: fix tox parallel issues with unsorted sets (#537) * test: fix tox parallel issues with unsorted sets --- setup.cfg | 8 ++------ test/unit_tests/braket/aws/test_aws_quantum_job.py | 2 +- tox.ini | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/setup.cfg b/setup.cfg index bc84fcb8..ffe3a264 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,13 +3,9 @@ test=pytest [tool:pytest] xfail_strict = true +# https://pytest-xdist.readthedocs.io/en/latest/known-limitations.html addopts = - --cov-report term-missing - --cov-report html - --cov-report xml - --cov=braket - --verbose - -n auto + --verbose -n auto testpaths = test/unit_tests [isort] 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 bbb1ad79..846088bf 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_job.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py @@ -292,7 +292,7 @@ def result_setup(quantum_job_name): os.chdir("..") -@pytest.mark.parametrize("state", AwsQuantumJob.TERMINAL_STATES) +@pytest.mark.parametrize("state", sorted(AwsQuantumJob.TERMINAL_STATES)) def test_results_when_job_is_completed( quantum_job, aws_session, generate_get_job_response, result_setup, state ): diff --git a/tox.ini b/tox.ini index 02e61530..4f63f4b2 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ basepython = python3 deps = {[test-deps]deps} commands = - pytest {posargs} + pytest {posargs} --cov-report term-missing --cov-report html --cov-report xml --cov=braket extras = test [testenv:integ-tests] From 0e70621f73ffc91187bab4dd5660d783151a547a Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 25 Apr 2023 19:10:34 +0000 Subject: [PATCH 0656/1165] prepare release v1.37.1 --- CHANGELOG.md | 12 ++++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47102370..97158541 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## v1.37.1 (2023-04-25) + +### Bug Fixes and Other Changes + + * test: fix tox parallel issues with unsorted sets + * Mock task creation against QPUs for tracker + * test: order terminal states for quantum jobs + +### Testing and Release Infrastructure + + * speed up unit testing by automatically parallelizing the CPU workers for test runs + ## v1.37.0 (2023-04-11) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index c26e3964..955b4abf 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.37.1.dev0" +__version__ = "1.37.1" From d8dd4fe68d5de73c7da1b7c0423bf407af308b0b Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 25 Apr 2023 19:10:34 +0000 Subject: [PATCH 0657/1165] update development version to v1.37.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 955b4abf..91fc3a65 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.37.1" +__version__ = "1.37.2.dev0" From bcc0e4c71b9ddff0757469a3367d0922cb2e724b Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Fri, 28 Apr 2023 09:59:57 -0700 Subject: [PATCH 0658/1165] feat: add tagging for python 3.10 images (#538) Co-authored-by: Abe Coull --- src/braket/jobs/image_uri_config/pl_pytorch.json | 2 +- src/braket/jobs/image_uri_config/pl_tensorflow.json | 2 +- src/braket/jobs/image_uris.py | 6 +++--- test/unit_tests/braket/jobs/test_image_uris.py | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/braket/jobs/image_uri_config/pl_pytorch.json b/src/braket/jobs/image_uri_config/pl_pytorch.json index 21143b38..2391dc25 100644 --- a/src/braket/jobs/image_uri_config/pl_pytorch.json +++ b/src/braket/jobs/image_uri_config/pl_pytorch.json @@ -1,6 +1,6 @@ { "versions": { - "1.13.1": { + "2.0.0": { "registries": { "us-east-1": "292282985366", "us-west-1": "292282985366", diff --git a/src/braket/jobs/image_uri_config/pl_tensorflow.json b/src/braket/jobs/image_uri_config/pl_tensorflow.json index 058d28a8..84436d66 100644 --- a/src/braket/jobs/image_uri_config/pl_tensorflow.json +++ b/src/braket/jobs/image_uri_config/pl_tensorflow.json @@ -1,6 +1,6 @@ { "versions": { - "2.11.0": { + "2.12.0": { "registries": { "us-east-1": "292282985366", "us-west-1": "292282985366", diff --git a/src/braket/jobs/image_uris.py b/src/braket/jobs/image_uris.py index df2ef278..4e58e8dd 100644 --- a/src/braket/jobs/image_uris.py +++ b/src/braket/jobs/image_uris.py @@ -47,11 +47,11 @@ def retrieve_image(framework: Framework, region: str) -> str: registry = _registry_for_region(version_config, region) tag = "" if framework == Framework.PL_TENSORFLOW: - tag = f"{version_config['repository']}:{framework_version}-gpu-py39-cu112-ubuntu20.04" + tag = f"{version_config['repository']}:{framework_version}-gpu-py310-cu118-ubuntu20.04" elif framework == Framework.PL_PYTORCH: - tag = f"{version_config['repository']}:{framework_version}-gpu-py39-cu117-ubuntu20.04" + tag = f"{version_config['repository']}:{framework_version}-gpu-py310-cu118-ubuntu20.04" else: - tag = f"{version_config['repository']}:{framework_version}-cpu-py39-ubuntu22.04" + tag = f"{version_config['repository']}:{framework_version}-cpu-py310-ubuntu22.04" return f"{registry}.dkr.ecr.{region}.amazonaws.com/{tag}" diff --git a/test/unit_tests/braket/jobs/test_image_uris.py b/test/unit_tests/braket/jobs/test_image_uris.py index e6b2469e..7450346c 100644 --- a/test/unit_tests/braket/jobs/test_image_uris.py +++ b/test/unit_tests/braket/jobs/test_image_uris.py @@ -23,19 +23,19 @@ "us-west-1", Framework.BASE, "292282985366.dkr.ecr.us-west-1.amazonaws.com/" - "amazon-braket-base-jobs:1.0-cpu-py39-ubuntu22.04", + "amazon-braket-base-jobs:1.0-cpu-py310-ubuntu22.04", ), ( "us-east-1", Framework.PL_TENSORFLOW, "292282985366.dkr.ecr.us-east-1.amazonaws.com/amazon-braket-tensorflow-jobs:" - "2.11.0-gpu-py39-cu112-ubuntu20.04", + "2.12.0-gpu-py310-cu118-ubuntu20.04", ), ( "us-west-2", Framework.PL_PYTORCH, "292282985366.dkr.ecr.us-west-2.amazonaws.com/" - "amazon-braket-pytorch-jobs:1.13.1-gpu-py39-cu117-ubuntu20.04", + "amazon-braket-pytorch-jobs:2.0.0-gpu-py310-cu118-ubuntu20.04", ), ], ) From c6bcd26bd496038894c11c89544dc19b69fd1e12 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 1 May 2023 16:14:46 +0000 Subject: [PATCH 0659/1165] prepare release v1.38.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 97158541..3da80544 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.38.0 (2023-05-01) + +### Features + + * add tagging for python 3.10 images + ## v1.37.1 (2023-04-25) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 91fc3a65..73cf1ebb 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.37.2.dev0" +__version__ = "1.38.0" From 98f07b59f82e11cc26acf9b8774f393d9ef406e4 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 1 May 2023 16:14:46 +0000 Subject: [PATCH 0660/1165] update development version to v1.38.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 73cf1ebb..9afb9b4a 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.38.0" +__version__ = "1.38.1.dev0" From e0b634b4dc1bb17ff36809d79fa1eeee12e17236 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 10 May 2023 11:21:04 -0700 Subject: [PATCH 0661/1165] fix: hardcode the language used by Sphinx instead of falling back on the default (#544) Co-authored-by: Abe Coull Co-authored-by: Cody Wang --- doc/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/conf.py b/doc/conf.py index 50a6adc1..23e9abdc 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -28,6 +28,8 @@ html_theme = "sphinx_rtd_theme" htmlhelp_basename = "{}doc".format(project) +language = "en" + napoleon_use_rtype = False apidoc_module_dir = "../src/braket" From e96aeb3a92b1eca51705a52b3989fc14e1227ff5 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 11 May 2023 16:13:27 +0000 Subject: [PATCH 0662/1165] prepare release v1.38.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 3da80544..c88c290a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.38.1 (2023-05-11) + +### Bug Fixes and Other Changes + + * hardcode the language used by Sphinx instead of falling back on the default + ## v1.38.0 (2023-05-01) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 9afb9b4a..511a066d 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.38.1.dev0" +__version__ = "1.38.1" From 236adab877603f62be64aade2e23ee479b239ebe Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 11 May 2023 16:13:27 +0000 Subject: [PATCH 0663/1165] update development version to v1.38.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 511a066d..30e59b9f 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.38.1" +__version__ = "1.38.2.dev0" From dce09758f4218f440d8bc54e3e45ed52fab6d7b5 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 15 May 2023 09:45:16 -0700 Subject: [PATCH 0664/1165] docs: add a linter to check proper rst formatting and fix up incorrect docs (#545) * docs: add a linter to check proper rst formatting and fix up incorrect docs --------- Co-authored-by: Abe Coull --- doc/examples-adv-circuits-algorithms.rst | 16 ++++++------- doc/examples-braket-features.rst | 20 ++++++++-------- doc/examples-getting-started.rst | 20 ++++++++-------- doc/examples-hybrid-jobs.rst | 16 ++++++------- doc/examples-hybrid-quantum.rst | 14 +++++------ doc/examples-ml-pennylane.rst | 20 ++++++++-------- setup.cfg | 6 +++++ setup.py | 1 + src/braket/ahs/discretization_types.py | 4 ++-- src/braket/ahs/driving_field.py | 10 ++++---- src/braket/ahs/shifting_field.py | 10 ++++---- src/braket/aws/aws_session.py | 23 +++++++++++-------- src/braket/circuits/angled_gate.py | 5 ++-- src/braket/circuits/circuit.py | 1 + src/braket/circuits/gates.py | 5 ++-- src/braket/circuits/noises.py | 10 ++++++++ src/braket/circuits/observable.py | 2 +- src/braket/circuits/observables.py | 2 +- .../parametric/free_parameter_expression.py | 2 +- src/braket/pulse/pulse_sequence.py | 2 +- .../quantum_information/pauli_string.py | 4 ++-- src/braket/timings/time_series.py | 19 +++++++++------ tox.ini | 1 + 23 files changed, 121 insertions(+), 92 deletions(-) diff --git a/doc/examples-adv-circuits-algorithms.rst b/doc/examples-adv-circuits-algorithms.rst index 38c4472d..ea99a475 100644 --- a/doc/examples-adv-circuits-algorithms.rst +++ b/doc/examples-adv-circuits-algorithms.rst @@ -7,9 +7,9 @@ Learn more about working with advanced circuits and algoritms. .. 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 @@ -17,9 +17,9 @@ 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 @@ -29,18 +29,18 @@ target states in a given search space. In a quantum computer, QAA can be used to 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. -************************ +*********************************************************************************************************************************************************************************** `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 diff --git a/doc/examples-braket-features.rst b/doc/examples-braket-features.rst index 63743214..cbb81fb6 100644 --- a/doc/examples-braket-features.rst +++ b/doc/examples-braket-features.rst @@ -7,40 +7,40 @@ Learn more about the indivudal features of Amazon Braket. .. toctree:: :maxdepth: 2 -************************** +******************************************************************************************************************************************************************************************************************************* `Getting notifications when a 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 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 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) programatically, 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. You will learn about how TN1 works, how to use it, and which problems are best suited to run on TN1. -************************** +*************************************************************************************************************************************************************** `Simulating noise on Amazon Braket `_ -************************** +*************************************************************************************************************************************************************** This notebook provides a detailed overview of noise simulation on Amazon Braket. You will learn how to define noise channels, apply noise to new or existing circuits, and run those circuits diff --git a/doc/examples-getting-started.rst b/doc/examples-getting-started.rst index d3e9dadb..9523320c 100644 --- a/doc/examples-getting-started.rst +++ b/doc/examples-getting-started.rst @@ -7,15 +7,15 @@ Get started on Amazon Braket with some introductory examples. .. toctree:: :maxdepth: 2 -*************** +*********************************************************************************************************************************************** `Getting started `_ -*************** +*********************************************************************************************************************************************** A hello-world tutorial that shows you how to build a simple circuit and run it on a local simulator. -*************** +******************************************************************************************************************************************************************************************************************** `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). @@ -26,9 +26,9 @@ and quantum metrology. **Note:** When a circuit is ran using a simulator, customers are required to use contiguous qubits/indices. -*************** +*********************************************************************************************************************************************************************************************************************** `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 @@ -36,9 +36,9 @@ 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 @@ -47,9 +47,9 @@ 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 diff --git a/doc/examples-hybrid-jobs.rst b/doc/examples-hybrid-jobs.rst index 9c905343..88d4e8f6 100644 --- a/doc/examples-hybrid-jobs.rst +++ b/doc/examples-hybrid-jobs.rst @@ -7,27 +7,27 @@ Learn more about hybrid jobs on Amazon Braket. .. toctree:: :maxdepth: 2 -************************** +************************************************************************************************************************************************************************************** `Creating your first Hybrid Job `_ -************************** +************************************************************************************************************************************************************************************** This tutorial shows how to run your first Amazon Braket Hybrid Job. -************************** +*********************************************************************************************************************************************************************************************************************************************************** `Quantum machine learning in Amazon Braket Hybrid Jobs `_ -************************** +*********************************************************************************************************************************************************************************************************************************************************** This notebook demonstrates a typical quantum machine learning workflow, including uploading data, monitoring training, and tuning hyperparameters. -************************** +******************************************************************************************************************************************************************************************** `Using Pennylane with Braket Jobs `_ -************************** +******************************************************************************************************************************************************************************************** In this tutorial, we use PennyLane within Amazon Braket Hybrid Jobs to run the Quantum Approximate Optimization Algorithm (QAOA) on a Max-Cut problem. -************************** +******************************************************************************************************************************************************************** `Bring your own container `_ -************************** +******************************************************************************************************************************************************************** Amazon Braket has pre-configured containers for executing Amazon Braket Hybrid Jobs, which are sufficient for many use cases involving the Braket SDK and PennyLane. However, if we want to use custom packages outside the scope of pre-configured containers, we have the ability to supply a custom-built container. In this tutorial, we show how to use Braket Hybrid Jobs to train a quantum machine learning model using BYOC (Bring Your Own Container). diff --git a/doc/examples-hybrid-quantum.rst b/doc/examples-hybrid-quantum.rst index 34ef0237..b30083af 100644 --- a/doc/examples-hybrid-quantum.rst +++ b/doc/examples-hybrid-quantum.rst @@ -7,23 +7,23 @@ Learn more about hybrid quantum algorithms. .. toctree:: :maxdepth: 2 -************************** +*************************************************************************************************************************** `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). -************************** +****************************************************************************************************************************************************** `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. \ No newline at end of file +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 349541fc..dae85912 100644 --- a/doc/examples-ml-pennylane.rst +++ b/doc/examples-ml-pennylane.rst @@ -1,22 +1,22 @@ -################################ +######################################################## Quantum machine learning and optimization with PennyLane -################################ +######################################################## Learn more about how to combine PennyLane with Amazon Braket. .. toctree:: :maxdepth: 2 -************************** +**************************************************************************************************************************************************************** `Combining PennyLane with Amazon Braket `_ -************************** +**************************************************************************************************************************************************************** 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 @@ -25,9 +25,9 @@ The tutorial benchmarks SV1 against a local simulator, showing that SV1 outperfo 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 @@ -36,9 +36,9 @@ then extends to a more difficult 20-node graph and uses the parallel capabilitie 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 diff --git a/setup.cfg b/setup.cfg index ffe3a264..432fec80 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,6 +21,9 @@ ignore = 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 = @@ -30,3 +33,6 @@ exclude = bin build venv +rst-roles = + # Python programming language: + py:func,py:mod,mod diff --git a/setup.py b/setup.py index fb47decc..3826fca5 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ "black", "botocore", "flake8<=5.0.4", + "flake8-rst-docstrings", "isort", "jsonschema==3.2.0", "pre-commit", diff --git a/src/braket/ahs/discretization_types.py b/src/braket/ahs/discretization_types.py index 5a00fc8b..c7df1fcf 100644 --- a/src/braket/ahs/discretization_types.py +++ b/src/braket/ahs/discretization_types.py @@ -26,9 +26,9 @@ class DiscretizationProperties: """Capabilities of a device that represent the resolution with which the device can implement the parameters. - lattice (Any): configuration values for discretization of the lattice geometry, + :parameter lattice (Any): configuration values for discretization of the lattice geometry, including the position resolution. - rydberg (Any): configuration values for discretization of Rydberg fields. + :parameter rydberg (Any): configuration values for discretization of Rydberg fields. Examples: lattice.geometry.positionResolution = Decimal("1E-7") diff --git a/src/braket/ahs/driving_field.py b/src/braket/ahs/driving_field.py index 4bc4f1e2..44fb73d4 100644 --- a/src/braket/ahs/driving_field.py +++ b/src/braket/ahs/driving_field.py @@ -95,11 +95,11 @@ def stitch( other (DrivingField): 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. + * "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. Returns: DrivingField: The stitched DrivingField object. diff --git a/src/braket/ahs/shifting_field.py b/src/braket/ahs/shifting_field.py index 6759e119..f1c9f9eb 100644 --- a/src/braket/ahs/shifting_field.py +++ b/src/braket/ahs/shifting_field.py @@ -95,11 +95,11 @@ def stitch( 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. + * "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. Returns: ShiftingField: The stitched ShiftingField object. diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 4b0a8eca..406ccbf1 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -211,7 +211,8 @@ def create_quantum_task(self, **boto3_kwargs) -> str: Create a quantum task. Args: - **boto3_kwargs: Keyword arguments for the Amazon Braket `CreateQuantumTask` operation. + ``**boto3_kwargs``: Keyword arguments for the Amazon Braket `CreateQuantumTask` + operation. Returns: str: The ARN of the quantum task. @@ -236,7 +237,7 @@ def create_job(self, **boto3_kwargs) -> str: Create a quantum 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 job. @@ -369,13 +370,15 @@ def upload_local_data(self, local_prefix: str, s3_prefix: str) -> None: Example: local_prefix = "input", s3_prefix = "s3://my-bucket/dir/input" will upload: - * 'input.csv' to 's3://my-bucket/dir/input.csv' - * 'input-2.csv' to 's3://my-bucket/dir/input-2.csv' - * 'input/data.txt' to 's3://my-bucket/dir/input/data.txt' - * 'input-dir/data.csv' to 's3://my-bucket/dir/input-dir/data.csv' - but will not upload: - * 'my-input.csv' - * 'my-dir/input.csv' + + - 'input.csv' to 's3://my-bucket/dir/input.csv' + - 'input-2.csv' to 's3://my-bucket/dir/input-2.csv' + - 'input/data.txt' to 's3://my-bucket/dir/input/data.txt' + - 'input-dir/data.csv' to 's3://my-bucket/dir/input-dir/data.csv' + but will not upload: + - 'my-input.csv' + - 'my-dir/input.csv' + To match all files within the directory "input" and upload them into "s3://my-bucket/input", provide local_prefix = "input/" and s3_prefix = "s3://my-bucket/input/" @@ -687,7 +690,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 diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index 6dbccba0..6a19ac91 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -199,7 +199,7 @@ def bind_values(self, **kwargs) -> AngledGate: Takes in parameters and attempts to assign them to values. Args: - **kwargs: The parameters that are being assigned. + ``**kwargs``: The parameters that are being assigned. Returns: AngledGate: A new Gate of the same type with the requested parameters bound. @@ -289,6 +289,7 @@ def get_angle(gate: AngledGate, **kwargs) -> AngledGate: 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. Returns: AngledGate: A new gate of the type of the AngledGate originally used with all @@ -307,7 +308,7 @@ def _get_angles(gate: DoubleAngledGate, **kwargs) -> DoubleAngledGate: Args: gate (DoubleAngledGate): The subclass of DoubleAngledGate for which the angle is being obtained. - **kwargs: The named parameters that are being filled for a particular gate. + ``**kwargs``: The named parameters that are being filled for a particular gate. Returns: DoubleAngledGate: A new gate of the type of the AngledGate originally used with all angles diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index ee7ce3d3..d526bdb4 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -692,6 +692,7 @@ def apply_gate_noise( in the whole circuit when they are not given. Examples: + :: >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1) >>> print(circ) T : |0|1|2| diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index cea3dfbb..26d296e5 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -2227,11 +2227,12 @@ def pulse_sequence(self) -> PulseSequence: @property def parameters(self) -> List[FreeParameter]: - """Returns the list of `FreeParameter`s associated with the gate.""" + """Returns the list of `FreeParameter` s associated with the gate.""" 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: diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py index 7b01ce58..0f9b8c08 100644 --- a/src/braket/circuits/noises.py +++ b/src/braket/circuits/noises.py @@ -54,6 +54,7 @@ class BitFlip(SingleProbabilisticNoise): """Bit flip noise channel which transforms a density matrix :math:`\\rho` according to: .. math:: \\rho \\Rightarrow (1-p) \\rho + p X \\rho X^{\\dagger} + where .. math:: @@ -156,6 +157,7 @@ class PhaseFlip(SingleProbabilisticNoise): """Phase flip noise channel which transforms a density matrix :math:`\\rho` according to: .. math:: \\rho \\Rightarrow (1-p) \\rho + p X \\rho X^{\\dagger} + where .. math:: @@ -262,6 +264,7 @@ class PauliChannel(PauliNoise): + probX X \\rho X^{\\dagger} + probY Y \\rho Y^{\\dagger} + probZ Z \\rho Z^{\\dagger} + where .. math:: @@ -414,6 +417,7 @@ class Depolarizing(SingleProbabilisticNoise_34): + p/3 X \\rho X^{\\dagger} + p/3 Y \\rho Y^{\\dagger} + p/3 Z \\rho Z^{\\dagger} + where .. math:: @@ -539,6 +543,7 @@ class TwoQubitDepolarizing(SingleProbabilisticNoise_1516): + XZ \\rho XZ^{\\dagger} + YI \\rho YI^{\\dagger} + YX \\rho YX^{\\dagger} + YY \\rho YY^{\\dagger} + YZ \\rho YZ^{\\dagger} + ZI \\rho ZI^{\\dagger} + ZX \\rho ZX^{\\dagger} + ZY \\rho ZY^{\\dagger} + ZZ \\rho ZZ^{\\dagger}) + where .. math:: @@ -679,6 +684,7 @@ class TwoQubitDephasing(SingleProbabilisticNoise_34): .. math:: \\rho \\Rightarrow (1-p) \\rho + p/3 ( IZ \\rho IZ^{\\dagger} + ZI \\rho ZI^{\\dagger} + ZZ \\rho ZZ^{\\dagger}) + where .. math:: @@ -811,6 +817,7 @@ class TwoQubitPauliChannel(MultiQubitPauliNoise): p_{ZX} ZX \\rho ZX^{\\dagger} + p_{ZY} ZY \\rho ZY^{\\dagger} + p_{ZZ} ZZ \\rho ZZ^{\\dagger}) + where .. math:: @@ -959,6 +966,7 @@ class AmplitudeDamping(DampingNoise): """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} + where .. math:: @@ -1061,6 +1069,7 @@ class GeneralizedAmplitudeDamping(GeneralizedAmplitudeDampingNoise): .. math:: \\rho \\Rightarrow E_0 \\rho E_0^{\\dagger} + E_1 \\rho E_1^{\\dagger} + E_2 \\rho E_2^{\\dagger} + E_3 \\rho E_3^{\\dagger} + where .. math:: @@ -1200,6 +1209,7 @@ class PhaseDamping(DampingNoise): """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} + where .. math:: diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index cbd4c8b5..db50f823 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -134,7 +134,7 @@ def eigenvalue(self, index: int) -> float: index (int): The index of the desired eigenvalue Returns: - float: The `index`th eigenvalue of the observable. + float: The `index` th eigenvalue of the observable. """ raise NotImplementedError diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 0588fa63..0cd37ed6 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -378,7 +378,7 @@ def eigenvalue(self, index: int) -> float: index (int): The index of the desired eigenvalue Returns: - float: The `index`th eigenvalue of the observable. + float: The `index` th eigenvalue of the observable. """ if index in self._eigenvalue_indices: return self._eigenvalue_indices[index] diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index eb1c045c..120e7ffa 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -144,7 +144,7 @@ def subs_if_free_parameter(parameter: Any, **kwargs) -> Any: """Substitute a free parameter with the given kwargs, if any. Args: parameter (Any): The parameter. - **kwargs: The kwargs to use to substitute. + ``**kwargs``: The kwargs to use to substitute. Returns: Any: The substituted parameters. diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index 58d2cce9..50370061 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -65,7 +65,7 @@ def to_time_trace(self) -> PulseSequenceTrace: @property def parameters(self) -> Set[FreeParameter]: - """Returns the set of `FreeParameter`s in the PulseSequence.""" + """Returns the set of `FreeParameter` s in the PulseSequence.""" return self._free_parameters.copy() def set_frequency( diff --git a/src/braket/quantum_information/pauli_string.py b/src/braket/quantum_information/pauli_string.py index f248a2b3..2c9e7ea0 100644 --- a/src/braket/quantum_information/pauli_string.py +++ b/src/braket/quantum_information/pauli_string.py @@ -111,8 +111,8 @@ def eigenstate(self, signs: Optional[Union[str, List[int], Tuple[int, ...]]] = N The resulting eigenstate has each qubit in the +1 eigenstate of its corresponding signed Pauli operator. For example, a Pauli string +XYZ and signs ++- has factors +X, +Y and -Z, - with the corresponding qubits in states |+⟩, |i⟩ and |1⟩ respectively (the global phase of - the Pauli string is ignored). + with the corresponding qubits in states `|+⟩` , `|i⟩` , and `|1⟩` respectively (the global + phase of the Pauli string is ignored). Args: signs (Optional[Union[str, List[int], Tuple[int, ...]]]): The sign of each factor of the diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py index 6790434e..a3acc42d 100644 --- a/src/braket/timings/time_series.py +++ b/src/braket/timings/time_series.py @@ -150,6 +150,7 @@ def concatenate(self, other: TimeSeries) -> TimeSeries: are at earler times then the time points in the second TimeSeries. Example: + :: time_series_1 = TimeSeries.from_lists(times=[0, 0.1], values=[1, 2]) time_series_2 = TimeSeries.from_lists(times=[0.2, 0.3], values=[4, 5]) @@ -185,18 +186,20 @@ def stitch( Args: other (TimeSeries): The second time series 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. + 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. Returns: TimeSeries: The stitched time series. 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]) @@ -207,6 +210,7 @@ def stitch( stitch_ts.values() = [1, 3, 5] Example (StitchBoundaryCondition.LEFT): + :: stitch_ts = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.LEFT) Result: @@ -214,6 +218,7 @@ def stitch( stitch_ts.values() = [1, 2, 5] Example (StitchBoundaryCondition.RIGHT): + :: stitch_ts = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.RIGHT) Result: diff --git a/tox.ini b/tox.ini index 4f63f4b2..700d4550 100644 --- a/tox.ini +++ b/tox.ini @@ -40,6 +40,7 @@ basepython = python3 skip_install = true deps = flake8 + flake8-rst-docstrings git+https://github.com/aws/amazon-braket-build-tools.git commands = flake8 {posargs} From f02f531a579697b82b7f5a392673676f75888fda Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 16 May 2023 16:13:44 +0000 Subject: [PATCH 0665/1165] prepare release v1.38.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 c88c290a..dbc2f1bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.38.2 (2023-05-16) + +### Bug Fixes and Other Changes + + * docs: add a linter to check proper rst formatting and fix up incorrect docs + ## v1.38.1 (2023-05-11) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 30e59b9f..1850be20 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.38.2.dev0" +__version__ = "1.38.2" From 7ba88b4159b7526727166331873911633bf123c0 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 16 May 2023 16:13:44 +0000 Subject: [PATCH 0666/1165] update development version to v1.38.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 1850be20..dbf2d93b 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.38.2" +__version__ = "1.38.3.dev0" From fdf0e7b44e41d4ac7a2a2e161c266a742d5500ef Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 16 May 2023 11:55:44 -0600 Subject: [PATCH 0667/1165] fix: Remove `exclude_none` from device params (#549) --- src/braket/aws/aws_quantum_task.py | 2 +- test/unit_tests/braket/aws/test_aws_quantum_task.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 5c3bc6af..ea4b41ba 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -622,7 +622,7 @@ def _( create_task_kwargs.update( { "action": problem.to_ir().json(), - "deviceParameters": device_params.json(exclude_none=True), + "deviceParameters": device_params.json(), } ) 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 3adb1fe7..a5b1b218 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -1009,7 +1009,7 @@ 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.update({"deviceParameters": device_parameters.json()}) if tags is not None: test_kwargs.update({"tags": tags}) aws_session.create_quantum_task.assert_called_with(**test_kwargs) From 9f3f59b9f53605f2042da8c89d3e7e169ce25df5 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 16 May 2023 18:16:56 +0000 Subject: [PATCH 0668/1165] prepare release v1.38.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 dbc2f1bc..69c37643 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.38.3 (2023-05-16) + +### Bug Fixes and Other Changes + + * Remove `exclude_none` from device params + ## v1.38.2 (2023-05-16) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index dbf2d93b..e319e2f7 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.38.3.dev0" +__version__ = "1.38.3" From 637efac29fdeb3df4f5312e93568cfff89f6af06 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 16 May 2023 18:16:56 +0000 Subject: [PATCH 0669/1165] update development version to v1.38.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 e319e2f7..4155ce40 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.38.3" +__version__ = "1.38.4.dev0" From a83c493489a7ebd1b46d1232be5259466a4ca240 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 16 May 2023 13:01:11 -0600 Subject: [PATCH 0670/1165] feat: Introduce error mitigation (#547) --- src/braket/aws/aws_device.py | 10 +- src/braket/aws/aws_quantum_task.py | 55 ++++++++--- src/braket/error_mitigation/__init__.py | 15 +++ src/braket/error_mitigation/debias.py | 26 +++++ .../error_mitigation/error_mitigation.py | 26 +++++ .../braket/aws/test_aws_quantum_task.py | 95 +++++++++++++++++-- 6 files changed, 200 insertions(+), 27 deletions(-) create mode 100644 src/braket/error_mitigation/__init__.py create mode 100644 src/braket/error_mitigation/debias.py create mode 100644 src/braket/error_mitigation/error_mitigation.py diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 4782421a..fca61ee6 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -114,9 +114,8 @@ def run( task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation]): # noqa Specification of task (circuit or annealing problem or program) to run on device. s3_destination_folder (Optional[S3DestinationFolder]): The S3 location to - save the task's results to. Default is `/tasks` if evoked - outside of a Braket Job, `/jobs//tasks` if evoked inside of - a Braket Job. + save the task's results to. Default is `/tasks` if evoked outside a + Braket Job, `/jobs//tasks` if evoked inside a Braket Job. shots (Optional[int]): The number of times to run the circuit or annealing problem. Default is 1000 for QPUs and 0 for simulators. poll_timeout_seconds (float): The polling timeout for `AwsQuantumTask.result()`, @@ -220,9 +219,8 @@ def run_batch( AnalogHamiltonianSimulation]]]): Single instance or list of circuits, annealing problems, pulse sequences, or photonics program to run on device. s3_destination_folder (Optional[S3DestinationFolder]): The S3 location to - save the tasks' results to. Default is `/tasks` if evoked - outside of a Braket Job, `/jobs//tasks` if evoked inside of - a Braket Job. + save the tasks' results to. Default is `/tasks` if evoked outside a + Braket Job, `/jobs//tasks` if evoked inside a Braket Job. shots (Optional[int]): The number of times to run the circuit or annealing problem. Default is 1000 for QPUs and 0 for simulators. max_parallel (Optional[int]): The maximum number of tasks to run on AWS in parallel. diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index ea4b41ba..56f9b202 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -50,6 +50,7 @@ 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 @@ -502,7 +503,7 @@ def _( aws_session: AwsSession, create_task_kwargs: Dict[str, Any], device_arn: str, - _device_parameters: Union[dict, BraketSchemaBase], # Not currently used for OpenQASMProgram + device_parameters: Union[dict, BraketSchemaBase], _disable_qubit_rewiring: bool, inputs: Dict[str, float], *args, @@ -516,6 +517,20 @@ def _( inputs=inputs_copy, ) create_task_kwargs.update({"action": openqasm_program.json()}) + if device_parameters: + final_device_parameters = ( + _circuit_device_params_from_dict( + device_parameters, + device_arn, + GateModelParameters(qubitCount=0), # qubitCount unused + ) + if type(device_parameters) is dict + else device_parameters + ) + create_task_kwargs.update( + {"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) @@ -543,7 +558,7 @@ def _( aws_session: AwsSession, create_task_kwargs: Dict[str, Any], device_arn: str, - device_parameters: Union[dict, BraketSchemaBase], # Not currently used for circuits + device_parameters: Union[dict, BraketSchemaBase], disable_qubit_rewiring: bool, inputs: Dict[str, float], *args, @@ -555,16 +570,11 @@ def _( paradigm_parameters = GateModelParameters( qubitCount=circuit.qubit_count, disableQubitRewiring=disable_qubit_rewiring ) - if "ionq" in device_arn: - device_parameters = IonqDeviceParameters(paradigmParameters=paradigm_parameters) - elif "rigetti" in device_arn: - device_parameters = RigettiDeviceParameters(paradigmParameters=paradigm_parameters) - elif "oqc" in device_arn: - device_parameters = OqcDeviceParameters(paradigmParameters=paradigm_parameters) - else: # default to use simulator - device_parameters = GateModelSimulatorDeviceParameters( - paradigmParameters=paradigm_parameters - ) + final_device_parameters = ( + _circuit_device_params_from_dict(device_parameters or {}, device_arn, paradigm_parameters) + if type(device_parameters) is dict + else device_parameters + ) qubit_reference_type = QubitReferenceType.VIRTUAL @@ -594,7 +604,7 @@ def _( create_task_kwargs.update( { "action": openqasm_program.json(), - "deviceParameters": device_parameters.json(), + "deviceParameters": final_device_parameters.json(exclude_none=True), } ) task_arn = aws_session.create_quantum_task(**create_task_kwargs) @@ -622,7 +632,7 @@ def _( create_task_kwargs.update( { "action": problem.to_ir().json(), - "deviceParameters": device_params.json(), + "deviceParameters": device_params.json(exclude_none=True), } ) @@ -647,6 +657,23 @@ def _( return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) +def _circuit_device_params_from_dict(device_parameters, device_arn, paradigm_parameters): + if "errorMitigation" in device_parameters: + error_migitation = device_parameters["errorMitigation"] + device_parameters["errorMitigation"] = ( + error_migitation.serialize() + if isinstance(error_migitation, ErrorMitigation) + else error_migitation + ) + if "ionq" in device_arn: + return IonqDeviceParameters(paradigmParameters=paradigm_parameters, **device_parameters) + if "rigetti" in device_arn: + return RigettiDeviceParameters(paradigmParameters=paradigm_parameters) + if "oqc" in device_arn: + return OqcDeviceParameters(paradigmParameters=paradigm_parameters) + return GateModelSimulatorDeviceParameters(paradigmParameters=paradigm_parameters) + + def _create_annealing_device_params( device_params: Dict[str, Any], device_arn: str ) -> Union[DwaveAdvantageDeviceParameters, Dwave2000QDeviceParameters]: diff --git a/src/braket/error_mitigation/__init__.py b/src/braket/error_mitigation/__init__.py new file mode 100644 index 00000000..5121cf10 --- /dev/null +++ b/src/braket/error_mitigation/__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.error_mitigation.debias import Debias # noqa: F401 +from braket.error_mitigation.error_mitigation import ErrorMitigation # noqa: F401 diff --git a/src/braket/error_mitigation/debias.py b/src/braket/error_mitigation/debias.py new file mode 100644 index 00000000..154094a0 --- /dev/null +++ b/src/braket/error_mitigation/debias.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 typing import List + +from braket.device_schema import error_mitigation +from braket.error_mitigation.error_mitigation import ErrorMitigation + + +class Debias(ErrorMitigation): + """ + 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 new file mode 100644 index 00000000..caa51e31 --- /dev/null +++ b/src/braket/error_mitigation/error_mitigation.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 typing import List + +from braket.device_schema import error_mitigation + + +class ErrorMitigation: + def serialize(self) -> List[error_mitigation.ErrorMitigationScheme]: + """ + Returns: + List[error_mitigation.ErrorMitigationScheme]: A list of service-readable error + mitigation scheme descriptions + """ + raise NotImplementedError("serialize is not implemented.") 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 a5b1b218..de8ead78 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -33,7 +33,7 @@ OpenQASMSerializationProperties, QubitReferenceType, ) -from braket.device_schema import GateModelParameters +from braket.device_schema import GateModelParameters, error_mitigation from braket.device_schema.dwave import ( Dwave2000QDeviceParameters, DwaveAdvantageDeviceParameters, @@ -43,6 +43,7 @@ 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.debias import Debias from braket.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQASMProgram from braket.pulse import Frame, Port, PulseSequence @@ -140,6 +141,11 @@ def pulse_gate(pulse_sequence): return PulseGate(pulse_sequence, 1, "my_PG") +@pytest.fixture +def em(): + return Debias() + + @pytest.fixture def ahs_problem(): mock = Mock(spec=AnalogHamiltonianSimulation) @@ -460,6 +466,54 @@ def test_create_openqasm_program(aws_session, arn, openqasm_program): ) +def test_create_openqasm_program_em(aws_session, arn, openqasm_program, em): + aws_session.create_quantum_task.return_value = arn + shots = 21 + AwsQuantumTask.create( + aws_session, + IONQ_ARN, + openqasm_program, + S3_TARGET, + shots, + device_parameters={"errorMitigation": em}, + ) + _assert_create_quantum_task_called_with( + aws_session, + IONQ_ARN, + openqasm_program.json(), + S3_TARGET, + shots, + device_parameters=IonqDeviceParameters( + paradigmParameters=GateModelParameters(qubitCount=0), + errorMitigation=[error_mitigation.Debias()], + ), + ) + + +def test_create_openqasm_program_em_serialized(aws_session, arn, openqasm_program, em): + aws_session.create_quantum_task.return_value = arn + shots = 21 + AwsQuantumTask.create( + aws_session, + IONQ_ARN, + openqasm_program, + S3_TARGET, + shots, + device_parameters={"errorMitigation": em.serialize()}, + ) + _assert_create_quantum_task_called_with( + aws_session, + IONQ_ARN, + openqasm_program.json(), + S3_TARGET, + shots, + device_parameters=IonqDeviceParameters( + paradigmParameters=GateModelParameters(qubitCount=0), + errorMitigation=[error_mitigation.Debias()], + ), + ) + + def test_create_blackbird_program(aws_session, arn, blackbird_program): aws_session.create_quantum_task.return_value = arn shots = 21 @@ -548,7 +602,7 @@ def test_create_pulse_gate_circuit( @pytest.mark.parametrize("device_arn,device_parameters_class", DEVICE_PARAMETERS) -def test_from_circuit_with_shots(device_arn, device_parameters_class, aws_session, circuit): +def test_create_circuit_with_shots(device_arn, device_parameters_class, aws_session, circuit): mocked_task_arn = "task-arn-1" aws_session.create_quantum_task.return_value = mocked_task_arn shots = 53 @@ -572,8 +626,35 @@ def test_from_circuit_with_shots(device_arn, device_parameters_class, aws_sessio ) +def test_create_circuit_em(aws_session, circuit, em): + mocked_task_arn = "task-arn-1" + aws_session.create_quantum_task.return_value = mocked_task_arn + shots = 53 + + task = AwsQuantumTask.create( + aws_session, IONQ_ARN, circuit, S3_TARGET, shots, device_parameters={"errorMitigation": em} + ) + assert task == AwsQuantumTask(mocked_task_arn, aws_session) + program = circuit.to_ir(ir_type=IRType.OPENQASM) + assert program.inputs == {} + + _assert_create_quantum_task_called_with( + aws_session, + IONQ_ARN, + program.json(), + S3_TARGET, + shots, + IonqDeviceParameters( + paradigmParameters=GateModelParameters(qubitCount=circuit.qubit_count), + errorMitigation=[error_mitigation.Debias()], + ), + ) + + @pytest.mark.parametrize("device_arn,device_parameters_class", DEVICE_PARAMETERS) -def test_from_circuit_with_input_params(device_arn, device_parameters_class, aws_session, circuit): +def test_create_circuit_with_input_params( + device_arn, device_parameters_class, aws_session, circuit +): mocked_task_arn = "task-arn-1" aws_session.create_quantum_task.return_value = mocked_task_arn shots = 53 @@ -602,7 +683,7 @@ def test_from_circuit_with_input_params(device_arn, device_parameters_class, aws @pytest.mark.parametrize( "device_arn,device_parameters_class", [(RIGETTI_ARN, RigettiDeviceParameters)] ) -def test_from_circuit_with_disabled_rewiring( +def test_create_circuit_with_disabled_rewiring( device_arn, device_parameters_class, aws_session, circuit ): mocked_task_arn = "task-arn-1" @@ -635,7 +716,7 @@ def test_from_circuit_with_disabled_rewiring( "device_arn,device_parameters_class, disable_qubit_rewiring", [(RIGETTI_ARN, RigettiDeviceParameters, True), (RIGETTI_ARN, RigettiDeviceParameters, False)], ) -def test_from_circuit_with_verbatim( +def test_create_circuit_with_verbatim( device_arn, device_parameters_class, disable_qubit_rewiring, aws_session ): circ = Circuit().add_verbatim_box(Circuit().h(0)) @@ -675,7 +756,7 @@ def test_from_circuit_with_verbatim( @pytest.mark.xfail(raises=ValueError) -def test_from_circuit_with_shots_value_error(aws_session, arn, circuit): +def test_create_circuit_with_shots_value_error(aws_session, arn, circuit): mocked_task_arn = "task-arn-1" aws_session.create_quantum_task.return_value = mocked_task_arn AwsQuantumTask.create(aws_session, arn, circuit, S3_TARGET, 0) @@ -1009,7 +1090,7 @@ def _assert_create_quantum_task_called_with( } if device_parameters is not None: - test_kwargs.update({"deviceParameters": device_parameters.json()}) + test_kwargs.update({"deviceParameters": device_parameters.json(exclude_none=True)}) if tags is not None: test_kwargs.update({"tags": tags}) aws_session.create_quantum_task.assert_called_with(**test_kwargs) From 3a86d2ca06cb9b1e32370cb568aead5a273db0e2 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 16 May 2023 19:15:04 +0000 Subject: [PATCH 0671/1165] prepare release v1.39.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 69c37643..da33efc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.39.0 (2023-05-16) + +### Features + + * Introduce error mitigation + ## v1.38.3 (2023-05-16) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 4155ce40..b62491b0 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.38.4.dev0" +__version__ = "1.39.0" From 413b2a3ff9c36ccb9b58177f887a09be7adc06e6 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 16 May 2023 19:15:04 +0000 Subject: [PATCH 0672/1165] update development version to v1.39.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 b62491b0..4870d2b3 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.39.0" +__version__ = "1.39.1.dev0" From 07581043401f930c0e119c11017fc9d446517cc1 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 17 May 2023 10:11:50 -0700 Subject: [PATCH 0673/1165] fix: making kms key optional (#542) Co-authored-by: Stephen Face <60493521+shpface@users.noreply.github.com> Co-authored-by: Aaron Berdy --- src/braket/jobs/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/jobs/config.py b/src/braket/jobs/config.py index 28b7c05d..de24ed78 100644 --- a/src/braket/jobs/config.py +++ b/src/braket/jobs/config.py @@ -37,7 +37,7 @@ class OutputDataConfig: """Configuration that specifies the location for the output of the job.""" s3Path: Optional[str] = None - kmsKeyId = None + kmsKeyId: Optional[str] = None @dataclass From d5a2a9b276757745635c02df76e642405cd9ce55 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 18 May 2023 11:18:26 -0600 Subject: [PATCH 0674/1165] test: Rename ionQdevice to Harmony in tests (#551) --- test/integ_tests/test_cost_tracking.py | 2 +- test/integ_tests/test_device_creation.py | 2 +- test/unit_tests/braket/aws/common_test_utils.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integ_tests/test_cost_tracking.py b/test/integ_tests/test_cost_tracking.py index 8f3d7a4b..60ddc1b5 100644 --- a/test/integ_tests/test_cost_tracking.py +++ b/test/integ_tests/test_cost_tracking.py @@ -27,7 +27,7 @@ @pytest.mark.parametrize( "qpu", [ - "arn:aws:braket:::device/qpu/ionq/ionQdevice", + "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony", "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy", "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3", ], diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 677e7ed9..36cb6ec5 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -16,7 +16,7 @@ from braket.aws import AwsDevice RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-10" -IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/ionQdevice" +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" PULSE_ARN = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 47e85b5d..cd7d76c3 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -18,7 +18,7 @@ DWAVE_ARN = "arn:aws:braket:::device/qpu/d-wave/Advantage_system1" RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-10" -IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/ionQdevice" +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" TN1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/tn1" From 1814e8e47a93680935eb841d0f042c2c644294da Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 18 May 2023 11:26:41 -0600 Subject: [PATCH 0675/1165] infra: twine check action (#540) Checks whether long description will render correctly on PyPI. --- .github/workflows/twine-check.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/twine-check.yml diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml new file mode 100644 index 00000000..51026ba2 --- /dev/null +++ b/.github/workflows/twine-check.yml @@ -0,0 +1,29 @@ +name: Check long description for PyPI + +on: + push: + branches: + - main + pull_request: + branches: + - main + - feature/** + +jobs: + twine-check: + name: Check long description + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + - name: Install wheel + run: python -m pip install --user --upgrade wheel + - name: Install twine + run: python -m pip install --user --upgrade twine + - name: Build a binary wheel and a source tarball + run: python setup.py sdist bdist_wheel + - name: Check that long description will render correctly on PyPI. + run: twine check dist/* From 77ecddb7f9aecf664e49f1e0e91b35bd2ef759b6 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Thu, 18 May 2023 11:48:18 -0700 Subject: [PATCH 0676/1165] fix: exclude default none for kms (#552) --- src/braket/jobs/quantum_job_creation.py | 6 +++++- test/unit_tests/braket/jobs/test_quantum_job_creation.py | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py index 3556cd78..c26d9e9e 100644 --- a/src/braket/jobs/quantum_job_creation.py +++ b/src/braket/jobs/quantum_job_creation.py @@ -211,7 +211,7 @@ def prepare_quantum_job( "algorithmSpecification": algorithm_specification, "inputDataConfig": input_data_list, "instanceConfig": asdict(instance_config), - "outputDataConfig": asdict(output_data_config), + "outputDataConfig": asdict(output_data_config, dict_factory=_exclude_nones_factory), "checkpointConfig": asdict(checkpoint_config), "deviceConfig": asdict(device_config), "hyperParameters": hyperparameters, @@ -420,3 +420,7 @@ def _convert_input_to_config(input_data: Dict[str, S3DataSourceConfig]) -> List[ } for channel_name, data_config in input_data.items() ] + + +def _exclude_nones_factory(items: List[Tuple]) -> Dict: + return {k: v for k, v in items if v is not None} 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 ec2d556f..842117c0 100644 --- a/test/unit_tests/braket/jobs/test_quantum_job_creation.py +++ b/test/unit_tests/braket/jobs/test_quantum_job_creation.py @@ -30,6 +30,7 @@ StoppingCondition, ) from braket.jobs.quantum_job_creation import ( + _exclude_nones_factory, _generate_default_job_name, _process_input_data, _process_local_source_module, @@ -356,7 +357,7 @@ def _translate_creation_args(create_job_args): "algorithmSpecification": algorithm_specification, "inputDataConfig": _process_input_data(input_data, job_name, aws_session), "instanceConfig": asdict(instance_config), - "outputDataConfig": asdict(output_data_config), + "outputDataConfig": asdict(output_data_config, dict_factory=_exclude_nones_factory), "checkpointConfig": asdict(checkpoint_config), "deviceConfig": {"device": device}, "hyperParameters": hyperparameters, From e8c5ee854ecfb78faa4dcd5f8412264b161886d6 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 18 May 2023 19:03:37 +0000 Subject: [PATCH 0677/1165] prepare release v1.39.1 --- CHANGELOG.md | 12 ++++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da33efc2..d034ec94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## v1.39.1 (2023-05-18) + +### Bug Fixes and Other Changes + + * exclude default none for kms + * test: Rename ionQdevice to Harmony in tests + * making kms key optional + +### Testing and Release Infrastructure + + * twine check action + ## v1.39.0 (2023-05-16) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 4870d2b3..09c4679e 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.39.1.dev0" +__version__ = "1.39.1" From ee4860401f4b48aeb9feefc7021fa7699d479433 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 18 May 2023 19:03:37 +0000 Subject: [PATCH 0678/1165] update development version to v1.39.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 09c4679e..de54143d 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.39.1" +__version__ = "1.39.2.dev0" From c02c43723a5ca1988e86e2050842a34bc56afec6 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 24 May 2023 12:05:06 -0700 Subject: [PATCH 0679/1165] test: add flaky tag (#539) --- test/integ_tests/test_simulator_quantum_task.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 50dd35e6..7b9e7d20 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -44,7 +44,9 @@ from braket.aws import AwsDevice -SHOTS = 8000 +# 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 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] From 4ca476d872e308ae45475ca8dc86200ef555cd98 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 24 May 2023 16:00:03 -0700 Subject: [PATCH 0680/1165] infra: remove flag that is no longer needed for isort (#560) https://pycqa.github.io/isort/docs/upgrade_guides/5.0.0.html#-recursive-or-rc As of 5.0.0, this is not needed anymore --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 700d4550..43d8acb4 100644 --- a/tox.ini +++ b/tox.ini @@ -52,7 +52,7 @@ skip_install = true deps = isort commands = - isort -rc . {posargs} + isort . {posargs} [testenv:black] basepython = python3 From 19b86a5795d9ce039f50ffa37ea0d10e7afce1ca Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Thu, 25 May 2023 14:05:31 -0700 Subject: [PATCH 0681/1165] feat: gate modifiers (#562) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gate modifiers! Three new keyword args for gates: `control`, `control_state`, and `power`. To add a controlled version of a gate to a circuit, add the keyword arg `control=control_target`, where `control_target` is a qubit index or list of qubit indices. By default, this controls on all qubits in `control_target` being in the `|1⟩` state, but you can use `control_state` to choose a custom state to control off of instead. `control_state` can be a sequence of `0`s and `1`s, a binary string, or an integer. To add a gate raised to a power to a circuit, add the keyword arg `power=p` where `p` is any real number. Here is an example of creating and printing a circuit with control and power modifiers: ``` from braket.circuits import Circuit # create a bell circuit with a controlled x gate my_circuit = Circuit().h(0).x(control=0, target=1) # add a multi-controlled Ry gate of angle .13 my_circuit.ry(angle=.13, target=2, control=(0, 1)) # add a 1/5 root of X gate my_circuit.x(0, power=1/5) print(my_circuit) # output """ T : |0|1| 2 | 3 | q0 : -H-C-C--------(X^0.2)- | | q1 : ---X-C---------------- | q2 : -----Ry(0.13)--------- T : |0|1| 2 | 3 | """ ``` --- setup.py | 4 +- src/braket/circuits/ascii_circuit_diagram.py | 37 +- src/braket/circuits/basis_state.py | 67 + src/braket/circuits/circuit.py | 7 +- src/braket/circuits/gate.py | 81 +- src/braket/circuits/gates.py | 1269 ++++++++++++----- src/braket/circuits/instruction.py | 147 +- src/braket/circuits/moments.py | 8 +- .../circuits/test_ascii_circuit_diagram.py | 56 +- .../braket/circuits/test_basis_state.py | 56 + .../braket/circuits/test_circuit.py | 66 + test/unit_tests/braket/circuits/test_gate.py | 31 +- test/unit_tests/braket/circuits/test_gates.py | 139 ++ .../braket/circuits/test_instruction.py | 51 +- 14 files changed, 1648 insertions(+), 371 deletions(-) create mode 100644 src/braket/circuits/basis_state.py create mode 100644 test/unit_tests/braket/circuits/test_basis_state.py diff --git a/setup.py b/setup.py index 3826fca5..e04d5b55 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.14.0", - "amazon-braket-default-simulator>=1.11.0", + "amazon-braket-schemas>=1.17.0", + "amazon-braket-default-simulator>=1.14.0", "oqpy~=0.1.1", "setuptools", "backoff", diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index af0b098c..f43f37a3 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -129,7 +129,9 @@ def _ascii_group_items( target = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) else: target = item.target - qubit_range = QubitSet(range(min(target), max(target) + 1)) + 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: @@ -236,10 +238,14 @@ def _ascii_diagram_column( 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) @@ -251,9 +257,10 @@ def _ascii_diagram_column( target_qubits = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) else: target_qubits = item.target - qubits = circuit_qubits.intersection( - set(range(min(target_qubits), max(target_qubits) + 1)) - ) + control_qubits = getattr(item, "control", QubitSet()) + 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: @@ -263,12 +270,30 @@ def _ascii_diagram_column( item_qubit_index = [ index for index, q in enumerate(target_qubits) if q == qubit ][0] - symbols[qubit] = ascii_symbols[item_qubit_index] + 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" else: symbols[qubit] = "|" # Set the margin to be a connector if not on the first qubit - if qubit != min(target_qubits): + if qubit != min(target_and_control): margins[qubit] = "|" symbols_width = max([len(symbol) for symbol in symbols.values()]) diff --git a/src/braket/circuits/basis_state.py b/src/braket/circuits/basis_state.py new file mode 100644 index 00000000..8c17802c --- /dev/null +++ b/src/braket/circuits/basis_state.py @@ -0,0 +1,67 @@ +from functools import singledispatch +from typing import List, Optional, Union + +import numpy as np + + +class BasisState: + def __init__(self, state: "BasisStateInput", size: Optional[int] = None): + self.state = _as_tuple(state, size) + + @property + def size(self): + return len(self.state) + + @property + def as_tuple(self): + return self.state + + @property + def as_int(self): + return 2 ** np.arange(self.size)[::-1] @ self.state + + @property + def as_string(self): + return "".join(map(str, self.state)) + + def __len__(self): + return len(self.state) + + def __iter__(self): + return iter(self.state) + + +BasisStateInput = Union[int, List[int], str, BasisState] + + +@singledispatch +def _as_tuple(state: BasisStateInput, size: int): + 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) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index d526bdb4..66ae24ba 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -1165,7 +1165,12 @@ def _to_openqasm( ] ) else: - for idx, qubit in enumerate(self.qubits): + qubits = ( + sorted(self.qubits) + if serialization_properties.qubit_reference_type == QubitReferenceType.VIRTUAL + else self.qubits + ) + for idx, qubit in enumerate(qubits): qubit_target = serialization_properties.format_target(int(qubit)) ir_instructions.append(f"b[{idx}] = measure {qubit_target};") diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index 5fb19bcb..cc918ae6 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -13,8 +13,10 @@ from __future__ import annotations +from itertools import groupby from typing import Any, List, Optional, Sequence, Tuple, Type +from braket.circuits.basis_state import BasisState, BasisStateInput from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.qubit_set import QubitSet from braket.circuits.serialization import ( @@ -46,8 +48,13 @@ 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 super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + @property + def _qasm_name(self): + raise NotImplementedError() + def adjoint(self) -> List[Gate]: """Returns a list of gates that implement the adjoint of this gate. @@ -63,6 +70,10 @@ def to_ir( target: QubitSet, ir_type: IRType = IRType.JAQCD, serialization_properties: SerializationProperties = None, + *, + control: Optional[QubitSet] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, ) -> Any: """Returns IR object of quantum operator and target @@ -73,14 +84,28 @@ def to_ir( serialization_properties (SerializationProperties): 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. + control (Optional[QubitSet]): Control qubit(s). Only supported for OpenQASM. + 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: Any: IR object of the quantum operator and target Raises: ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization properties don't correspond to the `ir_type`. + ValueError: If gate modifiers are supplied with `ir_type` Jaqcd. """ if ir_type == IRType.JAQCD: + 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 serialization_properties and not isinstance( @@ -91,7 +116,11 @@ def to_ir( "for IRType.OPENQASM." ) return self._to_openqasm( - target, serialization_properties or OpenQASMSerializationProperties() + target, + serialization_properties or OpenQASMSerializationProperties(), + control=control, + control_state=control_state, + power=power, ) else: raise ValueError(f"Supplied ir_type {ir_type} is not supported.") @@ -109,7 +138,13 @@ def _to_jaqcd(self, target: QubitSet) -> Any: raise NotImplementedError("to_jaqcd is not implemented.") def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties + self, + target: QubitSet, + serialization_properties: OpenQASMSerializationProperties, + *, + control: Optional[QubitSet] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, ) -> str: """ Returns the openqasm string representation of the gate. @@ -118,11 +153,51 @@ def _to_openqasm( target (QubitSet): target qubit(s). serialization_properties (OpenQASMSerializationProperties): The serialization properties to use while serializing the object to the IR representation. + control (Optional[QubitSet]): 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: str: Representing the openqasm representation of the gate. """ - raise NotImplementedError("to_openqasm has not been implemented yet.") + target_qubits = [serialization_properties.format_target(int(qubit)) for qubit in target] + if control: + control_qubits = [ + serialization_properties.format_target(int(qubit)) for qubit in control + ] + control_state = (1,) * len(control) if control_state is None else control_state + control_basis_state = BasisState(control_state, len(control)) + control_modifiers = [] + 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})" + ] + control_modifiers.append("") + qubits = control_qubits + target_qubits + control_prefix = " @ ".join(control_modifiers) + else: + qubits = target_qubits + control_prefix = "" + inv_prefix = "inv @ " if power and power < 0 else "" + power_prefix = f"pow({abs_power}) @ " if (abs_power := abs(power)) != 1 else "" + param_string = ( + f"({', '.join(map(str, self.parameters))})" if hasattr(self, "parameters") else "" + ) + + return ( + f"{inv_prefix}{power_prefix}{control_prefix}" + f"{self._qasm_name}{param_string} {', '.join(qubits)};" + ) @property def ascii_symbols(self) -> Tuple[str, ...]: diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 26d296e5..331bc2ee 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -14,7 +14,7 @@ from __future__ import annotations from copy import deepcopy -from typing import Any, Iterable, List, Union +from typing import Any, Iterable, List, Optional, Union import numpy as np from oqpy import Program @@ -29,6 +29,7 @@ angled_ascii_characters, get_angle, ) +from braket.circuits.basis_state import BasisStateInput from braket.circuits.free_parameter import FreeParameter from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.gate import Gate @@ -63,18 +64,16 @@ class H(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["H"]) + @property + def _qasm_name(self): + return "h" + def adjoint(self) -> List[Gate]: return [H()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.H.construct(target=target[0]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"h {target_qubit};" - def to_matrix(self) -> np.ndarray: return 1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) @@ -84,11 +83,27 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def h(target: QubitSetInput) -> Iterable[Instruction]: + def h( + target: QubitSetInput, + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (QubitSetInput): Target qubit(s) + 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]: `Iterable` of H instructions. @@ -97,7 +112,12 @@ def h(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().h(0) >>> circ = Circuit().h([0, 1, 2]) """ - return [Instruction(H(), target=qubit) for qubit in QubitSet(target)] + return [ + Instruction( + H(), target=qubit, control=control, control_state=control_state, power=power + ) + for qubit in QubitSet(target) + ] Gate.register_gate(H) @@ -109,18 +129,16 @@ class I(Gate): # noqa: E742, E261 def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["I"]) + @property + def _qasm_name(self): + return "i" + def adjoint(self) -> List[Gate]: return [I()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.I.construct(target=target[0]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"i {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.eye(2, dtype=complex) @@ -130,11 +148,27 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def i(target: QubitSetInput) -> Iterable[Instruction]: + def i( + target: QubitSetInput, + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (QubitSetInput): Target qubit(s) + 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]: `Iterable` of I instructions. @@ -143,7 +177,12 @@ def i(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().i(0) >>> circ = Circuit().i([0, 1, 2]) """ - return [Instruction(I(), target=qubit) for qubit in QubitSet(target)] + return [ + Instruction( + I(), target=qubit, control=control, control_state=control_state, power=power + ) + for qubit in QubitSet(target) + ] Gate.register_gate(I) @@ -155,18 +194,16 @@ class X(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["X"]) + @property + def _qasm_name(self): + return "x" + def adjoint(self) -> List[Gate]: return [X()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.X.construct(target=target[0]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"x {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) @@ -176,11 +213,27 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def x(target: QubitSetInput) -> Iterable[Instruction]: + def x( + target: QubitSetInput, + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (QubitSetInput): Target qubit(s) + 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]: `Iterable` of X instructions. @@ -189,7 +242,12 @@ def x(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().x(0) >>> circ = Circuit().x([0, 1, 2]) """ - return [Instruction(X(), target=qubit) for qubit in QubitSet(target)] + return [ + Instruction( + X(), target=qubit, control=control, control_state=control_state, power=power + ) + for qubit in QubitSet(target) + ] Gate.register_gate(X) @@ -201,18 +259,16 @@ class Y(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Y"]) + @property + def _qasm_name(self): + return "y" + def adjoint(self) -> List[Gate]: return [Y()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Y.construct(target=target[0]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"y {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) @@ -222,11 +278,27 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def y(target: QubitSetInput) -> Iterable[Instruction]: + def y( + target: QubitSetInput, + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (QubitSetInput): Target qubit(s) + 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]: `Iterable` of Y instructions. @@ -235,7 +307,12 @@ def y(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().y(0) >>> circ = Circuit().y([0, 1, 2]) """ - return [Instruction(Y(), target=qubit) for qubit in QubitSet(target)] + return [ + Instruction( + Y(), target=qubit, control=control, control_state=control_state, power=power + ) + for qubit in QubitSet(target) + ] Gate.register_gate(Y) @@ -247,18 +324,16 @@ class Z(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Z"]) + @property + def _qasm_name(self): + return "z" + def adjoint(self) -> List[Gate]: return [Z()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Z.construct(target=target[0]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"z {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) @@ -268,11 +343,27 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def z(target: QubitSetInput) -> Iterable[Instruction]: + def z( + target: QubitSetInput, + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (QubitSetInput): Target qubit(s) + 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]: `Iterable` of Z instructions. @@ -281,7 +372,12 @@ def z(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().z(0) >>> circ = Circuit().z([0, 1, 2]) """ - return [Instruction(Z(), target=qubit) for qubit in QubitSet(target)] + return [ + Instruction( + Z(), target=qubit, control=control, control_state=control_state, power=power + ) + for qubit in QubitSet(target) + ] Gate.register_gate(Z) @@ -293,18 +389,16 @@ class S(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["S"]) + @property + def _qasm_name(self): + return "s" + def adjoint(self) -> List[Gate]: return [Si()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.S.construct(target=target[0]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"s {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, 1.0j]], dtype=complex) @@ -314,11 +408,27 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def s(target: QubitSetInput) -> Iterable[Instruction]: + def s( + target: QubitSetInput, + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (QubitSetInput): Target qubit(s) + 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]: `Iterable` of S instructions. @@ -327,7 +437,12 @@ def s(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().s(0) >>> circ = Circuit().s([0, 1, 2]) """ - return [Instruction(S(), target=qubit) for qubit in QubitSet(target)] + return [ + Instruction( + S(), target=qubit, control=control, control_state=control_state, power=power + ) + for qubit in QubitSet(target) + ] Gate.register_gate(S) @@ -339,18 +454,16 @@ class Si(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Si"]) + @property + def _qasm_name(self): + return "si" + def adjoint(self) -> List[Gate]: return [S()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Si.construct(target=target[0]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"si {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.array([[1, 0], [0, -1j]], dtype=complex) @@ -360,11 +473,27 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def si(target: QubitSetInput) -> Iterable[Instruction]: + def si( + target: QubitSetInput, + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (QubitSetInput): Target qubit(s) + 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]: Iterable of Si instructions. @@ -373,7 +502,12 @@ def si(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().si(0) >>> circ = Circuit().si([0, 1, 2]) """ - return [Instruction(Si(), target=qubit) for qubit in QubitSet(target)] + return [ + Instruction( + Si(), target=qubit, control=control, control_state=control_state, power=power + ) + for qubit in QubitSet(target) + ] Gate.register_gate(Si) @@ -385,18 +519,16 @@ class T(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["T"]) + @property + def _qasm_name(self): + return "t" + def adjoint(self) -> List[Gate]: return [Ti()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.T.construct(target=target[0]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"t {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, np.exp(1j * np.pi / 4)]], dtype=complex) @@ -406,11 +538,27 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def t(target: QubitSetInput) -> Iterable[Instruction]: + def t( + target: QubitSetInput, + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (QubitSetInput): Target qubit(s) + 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]: `Iterable` of T instructions. @@ -419,7 +567,12 @@ def t(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().t(0) >>> circ = Circuit().t([0, 1, 2]) """ - return [Instruction(T(), target=qubit) for qubit in QubitSet(target)] + return [ + Instruction( + T(), target=qubit, control=control, control_state=control_state, power=power + ) + for qubit in QubitSet(target) + ] Gate.register_gate(T) @@ -431,18 +584,16 @@ class Ti(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Ti"]) + @property + def _qasm_name(self): + return "ti" + def adjoint(self) -> List[Gate]: return [T()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Ti.construct(target=target[0]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"ti {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, np.exp(-1j * np.pi / 4)]], dtype=complex) @@ -452,11 +603,27 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def ti(target: QubitSetInput) -> Iterable[Instruction]: + def ti( + target: QubitSetInput, + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (QubitSetInput): Target qubit(s) + 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]: `Iterable` of Ti instructions. @@ -465,7 +632,12 @@ def ti(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().ti(0) >>> circ = Circuit().ti([0, 1, 2]) """ - return [Instruction(Ti(), target=qubit) for qubit in QubitSet(target)] + return [ + Instruction( + Ti(), target=qubit, control=control, control_state=control_state, power=power + ) + for qubit in QubitSet(target) + ] Gate.register_gate(Ti) @@ -477,18 +649,16 @@ class V(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["V"]) + @property + def _qasm_name(self): + return "v" + def adjoint(self) -> List[Gate]: return [Vi()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.V.construct(target=target[0]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"v {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.array([[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]], dtype=complex) @@ -498,11 +668,27 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def v(target: QubitSetInput) -> Iterable[Instruction]: + def v( + target: QubitSetInput, + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (QubitSetInput): Target qubit(s) + 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]: `Iterable` of V instructions. @@ -511,7 +697,12 @@ def v(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().v(0) >>> circ = Circuit().v([0, 1, 2]) """ - return [Instruction(V(), target=qubit) for qubit in QubitSet(target)] + return [ + Instruction( + V(), target=qubit, control=control, control_state=control_state, power=power + ) + for qubit in QubitSet(target) + ] Gate.register_gate(V) @@ -523,18 +714,16 @@ class Vi(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Vi"]) + @property + def _qasm_name(self): + return "vi" + def adjoint(self) -> List[Gate]: return [V()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Vi.construct(target=target[0]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"vi {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.array(([[0.5 - 0.5j, 0.5 + 0.5j], [0.5 + 0.5j, 0.5 - 0.5j]]), dtype=complex) @@ -544,11 +733,27 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def vi(target: QubitSetInput) -> Iterable[Instruction]: + def vi( + target: QubitSetInput, + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: target (QubitSetInput): Target qubit(s) + 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]: `Iterable` of Vi instructions. @@ -557,7 +762,12 @@ def vi(target: QubitSetInput) -> Iterable[Instruction]: >>> circ = Circuit().vi(0) >>> circ = Circuit().vi([0, 1, 2]) """ - return [Instruction(Vi(), target=qubit) for qubit in QubitSet(target)] + return [ + Instruction( + Vi(), target=qubit, control=control, control_state=control_state, power=power + ) + for qubit in QubitSet(target) + ] Gate.register_gate(Vi) @@ -580,15 +790,13 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=[angled_ascii_characters("Rx", angle)], ) + @property + def _qasm_name(self): + return "rx" + def _to_jaqcd(self, target: QubitSet, **kwargs) -> Any: return ir.Rx.construct(target=target[0], angle=self.angle) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"rx({self.angle}) {target_qubit};" - def to_matrix(self) -> np.ndarray: """Returns a matrix representation of this gate. Returns: @@ -608,13 +816,28 @@ def bind_values(self, **kwargs) -> AngledGate: @staticmethod @circuit.subroutine(register=True) def rx( - target: QubitInput, angle: Union[FreeParameterExpression, float] + target: QubitSetInput, + angle: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (QubitInput): Target qubit index. + target (QubitSetInput): Target qubit(s). angle (Union[FreeParameterExpression, float]): 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]: Rx instruction. @@ -622,7 +845,12 @@ def rx( Examples: >>> circ = Circuit().rx(0, 0.15) """ - return [Instruction(Rx(angle), target=qubit) for qubit in QubitSet(target)] + return [ + Instruction( + Rx(angle), target=qubit, control=control, control_state=control_state, power=power + ) + for qubit in QubitSet(target) + ] Gate.register_gate(Rx) @@ -642,15 +870,13 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=[angled_ascii_characters("Ry", angle)], ) + @property + def _qasm_name(self): + return "ry" + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Ry.construct(target=target[0], angle=self.angle) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"ry({self.angle}) {target_qubit};" - def to_matrix(self) -> np.ndarray: """Returns a matrix representation of this gate. Returns: @@ -670,21 +896,41 @@ def bind_values(self, **kwargs) -> AngledGate: @staticmethod @circuit.subroutine(register=True) def ry( - target: QubitInput, angle: Union[FreeParameterExpression, float] + target: QubitSetInput, + angle: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (QubitInput): Target qubit index. + target (QubitSetInput): Target qubit(s). angle (Union[FreeParameterExpression, float]): 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]: Ry instruction. + Iterable[Instruction]: Rx instruction. Examples: >>> circ = Circuit().ry(0, 0.15) """ - return [Instruction(Ry(angle), target=qubit) for qubit in QubitSet(target)] + return [ + Instruction( + Ry(angle), target=qubit, control=control, control_state=control_state, power=power + ) + for qubit in QubitSet(target) + ] Gate.register_gate(Ry) @@ -704,15 +950,13 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=[angled_ascii_characters("Rz", angle)], ) + @property + def _qasm_name(self): + return "rz" + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Rz.construct(target=target[0], angle=self.angle) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"rz({self.angle}) {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.array( [[np.exp(-1j * self.angle / 2), 0], [0, np.exp(1j * self.angle / 2)]], dtype=complex @@ -728,21 +972,41 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def rz( - target: QubitInput, angle: Union[FreeParameterExpression, float] + target: QubitSetInput, + angle: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (QubitInput): Target qubit index. - angle (Union[FreeParameterExpression, float]): angle in radians. + target (QubitSetInput): Target qubit(s). + angle (Union[FreeParameterExpression, float]): 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]: Rz instruction. + Iterable[Instruction]: Rx instruction. Examples: >>> circ = Circuit().rz(0, 0.15) """ - return [Instruction(Rz(angle), target=qubit) for qubit in QubitSet(target)] + return [ + Instruction( + Rz(angle), target=qubit, control=control, control_state=control_state, power=power + ) + for qubit in QubitSet(target) + ] Gate.register_gate(Rz) @@ -762,16 +1026,13 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=[angled_ascii_characters("PHASE", angle)], ) + @property + def _qasm_name(self): + return "phaseshift" + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.PhaseShift.construct(target=target[0], angle=self.angle) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - # alternatively, "ctrl @ phase({self.angle}) {target_qubit};" - return f"phaseshift({self.angle}) {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.array([[1.0, 0.0], [0.0, np.exp(1j * self.angle)]], dtype=complex) @@ -785,13 +1046,28 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def phaseshift( - target: QubitInput, angle: Union[FreeParameterExpression, float] + target: QubitSetInput, + angle: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (QubitInput): Target qubit index. + target (QubitSetInput): Target qubit(s). angle (Union[FreeParameterExpression, float]): 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]: PhaseShift instruction. @@ -799,7 +1075,16 @@ def phaseshift( Examples: >>> circ = Circuit().phaseshift(0, 0.15) """ - return [Instruction(PhaseShift(angle), target=qubit) for qubit in QubitSet(target)] + return [ + Instruction( + PhaseShift(angle), + target=qubit, + control=control, + control_state=control_state, + power=power, + ) + for qubit in QubitSet(target) + ] Gate.register_gate(PhaseShift) @@ -814,19 +1099,16 @@ class CNot(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "X"]) + @property + def _qasm_name(self): + return "cnot" + def adjoint(self) -> List[Gate]: return [CNot()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CNot.construct(control=target[0], target=target[1]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - control_qubit = serialization_properties.format_target(int(target[0])) - target_qubit = serialization_properties.format_target(int(target[1])) - return f"cnot {control_qubit}, {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.array( [ @@ -844,12 +1126,16 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def cnot(control: QubitInput, target: QubitInput) -> Instruction: + def cnot(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruction: """Registers this function into the circuit class. Args: - control (QubitInput): Control qubit index. + control (QubitSetInput): Control qubit(s). The last control qubit + is absorbed into the target of the instruction. target (QubitInput): Target qubit index. + 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: CNot instruction. @@ -857,7 +1143,11 @@ def cnot(control: QubitInput, target: QubitInput) -> Instruction: Examples: >>> circ = Circuit().cnot(0, 1) """ - return Instruction(CNot(), target=[control, target]) + control_qubits = QubitSet(control) + absorbed_control = control_qubits.pop() + return Instruction( + CNot(), target=[absorbed_control, target], control=control_qubits, power=power + ) Gate.register_gate(CNot) @@ -869,19 +1159,16 @@ class Swap(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["SWAP", "SWAP"]) + @property + def _qasm_name(self): + return "swap" + def adjoint(self) -> List[Gate]: return [Swap()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Swap.construct(targets=[target[0], target[1]]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit_0 = serialization_properties.format_target(int(target[0])) - target_qubit_1 = serialization_properties.format_target(int(target[1])) - return f"swap {target_qubit_0}, {target_qubit_1};" - def to_matrix(self) -> np.ndarray: return np.array( [ @@ -899,12 +1186,29 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def swap(target1: QubitInput, target2: QubitInput) -> Instruction: + def swap( + target1: QubitInput, + target2: QubitInput, + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Instruction: """Registers this function into the circuit class. Args: target1 (QubitInput): Target qubit 1 index. target2 (QubitInput): Target qubit 2 index. + 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: Swap instruction. @@ -912,7 +1216,13 @@ def swap(target1: QubitInput, target2: QubitInput) -> Instruction: Examples: >>> circ = Circuit().swap(0, 1) """ - return Instruction(Swap(), target=[target1, target2]) + return Instruction( + Swap(), + target=[target1, target2], + control=control, + control_state=control_state, + power=power, + ) Gate.register_gate(Swap) @@ -924,19 +1234,16 @@ class ISwap(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["ISWAP", "ISWAP"]) + @property + def _qasm_name(self): + return "iswap" + def adjoint(self) -> List[Gate]: return [self, self, self] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.ISwap.construct(targets=[target[0], target[1]]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit_0 = serialization_properties.format_target(int(target[0])) - target_qubit_1 = serialization_properties.format_target(int(target[1])) - return f"iswap {target_qubit_0}, {target_qubit_1};" - def to_matrix(self) -> np.ndarray: return np.array( [ @@ -954,12 +1261,29 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def iswap(target1: QubitInput, target2: QubitInput) -> Instruction: + def iswap( + target1: QubitInput, + target2: QubitInput, + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Instruction: """Registers this function into the circuit class. Args: target1 (QubitInput): Target qubit 1 index. target2 (QubitInput): Target qubit 2 index. + 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: ISwap instruction. @@ -967,7 +1291,13 @@ def iswap(target1: QubitInput, target2: QubitInput) -> Instruction: Examples: >>> circ = Circuit().iswap(0, 1) """ - return Instruction(ISwap(), target=[target1, target2]) + return Instruction( + ISwap(), + target=[target1, target2], + control=control, + control_state=control_state, + power=power, + ) Gate.register_gate(ISwap) @@ -990,16 +1320,13 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ], ) + @property + def _qasm_name(self): + return "pswap" + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.PSwap.construct(targets=[target[0], target[1]], angle=self.angle) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit_0 = serialization_properties.format_target(int(target[0])) - target_qubit_1 = serialization_properties.format_target(int(target[1])) - return f"pswap({self.angle}) {target_qubit_0}, {target_qubit_1};" - def to_matrix(self) -> np.ndarray: return np.array( [ @@ -1021,7 +1348,13 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def pswap( - target1: QubitInput, target2: QubitInput, angle: Union[FreeParameterExpression, float] + target1: QubitInput, + target2: QubitInput, + angle: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, ) -> Instruction: """Registers this function into the circuit class. @@ -1029,6 +1362,16 @@ def pswap( target1 (QubitInput): Target qubit 1 index. target2 (QubitInput): Target qubit 2 index. angle (Union[FreeParameterExpression, float]): 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: Instruction: PSwap instruction. @@ -1036,7 +1379,13 @@ def pswap( Examples: >>> circ = Circuit().pswap(0, 1, 0.15) """ - return Instruction(PSwap(angle), target=[target1, target2]) + return Instruction( + PSwap(angle), + target=[target1, target2], + control=control, + control_state=control_state, + power=power, + ) Gate.register_gate(PSwap) @@ -1061,16 +1410,13 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ], ) + @property + def _qasm_name(self): + return "xy" + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.XY.construct(targets=[target[0], target[1]], angle=self.angle) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit_1 = serialization_properties.format_target(int(target[0])) - target_qubit_2 = serialization_properties.format_target(int(target[1])) - return f"xy({self.angle}) {target_qubit_1}, {target_qubit_2};" - def to_matrix(self) -> np.ndarray: """Returns a matrix representation of this gate. Returns: @@ -1098,7 +1444,13 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def xy( - target1: QubitInput, target2: QubitInput, angle: Union[FreeParameterExpression, float] + target1: QubitInput, + target2: QubitInput, + angle: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, ) -> Instruction: """Registers this function into the circuit class. @@ -1106,6 +1458,16 @@ def xy( target1 (QubitInput): Target qubit 1 index. target2 (QubitInput): Target qubit 2 index. angle (Union[FreeParameterExpression, float]): 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: Instruction: XY instruction. @@ -1113,7 +1475,13 @@ def xy( Examples: >>> circ = Circuit().xy(0, 1, 0.15) """ - return Instruction(XY(angle), target=[target1, target2]) + return Instruction( + XY(angle), + target=[target1, target2], + control=control, + control_state=control_state, + power=power, + ) Gate.register_gate(XY) @@ -1133,16 +1501,13 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=["C", angled_ascii_characters("PHASE", angle)], ) + @property + def _qasm_name(self): + return "cphaseshift" + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CPhaseShift.construct(control=target[0], target=target[1], angle=self.angle) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - control_qubit = serialization_properties.format_target(int(target[0])) - target_qubit = serialization_properties.format_target(int(target[1])) - return f"cphaseshift({self.angle}) {control_qubit}, {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, 1.0, np.exp(1j * self.angle)]) @@ -1156,14 +1521,21 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def cphaseshift( - control: QubitInput, target: QubitInput, angle: Union[FreeParameterExpression, float] + control: QubitSetInput, + target: QubitInput, + angle: Union[FreeParameterExpression, float], + power: float = 1, ) -> Instruction: """Registers this function into the circuit class. Args: - control (QubitInput): Control qubit index. + control (QubitSetInput): Control qubit(s). The last control qubit + is absorbed into the target of the instruction. target (QubitInput): Target qubit index. angle (Union[FreeParameterExpression, float]): angle in radians. + 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: CPhaseShift instruction. @@ -1171,7 +1543,14 @@ def cphaseshift( Examples: >>> circ = Circuit().cphaseshift(0, 1, 0.15) """ - return Instruction(CPhaseShift(angle), target=[control, target]) + control_qubits = QubitSet(control) + absorbed_control = control_qubits.pop() + return Instruction( + CPhaseShift(angle), + target=[absorbed_control, target], + control=control_qubits, + power=power, + ) Gate.register_gate(CPhaseShift) @@ -1191,16 +1570,13 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=["C", angled_ascii_characters("PHASE00", angle)], ) + @property + def _qasm_name(self): + return "cphaseshift00" + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CPhaseShift00.construct(control=target[0], target=target[1], angle=self.angle) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - control_qubit = serialization_properties.format_target(int(target[0])) - target_qubit = serialization_properties.format_target(int(target[1])) - return f"cphaseshift00({self.angle}) {control_qubit}, {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.diag([np.exp(1j * self.angle), 1.0, 1.0, 1.0]) @@ -1214,14 +1590,21 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def cphaseshift00( - control: QubitInput, target: QubitInput, angle: Union[FreeParameterExpression, float] + control: QubitSetInput, + target: QubitInput, + angle: Union[FreeParameterExpression, float], + power: float = 1, ) -> Instruction: """Registers this function into the circuit class. Args: - control (QubitInput): Control qubit index. + control (QubitSetInput): Control qubit(s). The last control qubit + is absorbed into the target of the instruction. target (QubitInput): Target qubit index. angle (Union[FreeParameterExpression, float]): angle in radians. + 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: CPhaseShift00 instruction. @@ -1229,7 +1612,14 @@ def cphaseshift00( Examples: >>> circ = Circuit().cphaseshift00(0, 1, 0.15) """ - return Instruction(CPhaseShift00(angle), target=[control, target]) + control_qubits = QubitSet(control) + absorbed_control = control_qubits.pop() + return Instruction( + CPhaseShift00(angle), + target=[absorbed_control, target], + control=control_qubits, + power=power, + ) Gate.register_gate(CPhaseShift00) @@ -1249,16 +1639,13 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=["C", angled_ascii_characters("PHASE01", angle)], ) + @property + def _qasm_name(self): + return "cphaseshift01" + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CPhaseShift01.construct(control=target[0], target=target[1], angle=self.angle) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - control_qubit = serialization_properties.format_target(int(target[0])) - target_qubit = serialization_properties.format_target(int(target[1])) - return f"cphaseshift01({self.angle}) {control_qubit}, {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.diag([1.0, np.exp(1j * self.angle), 1.0, 1.0]) @@ -1272,14 +1659,21 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def cphaseshift01( - control: QubitInput, target: QubitInput, angle: Union[FreeParameterExpression, float] + control: QubitSetInput, + target: QubitInput, + angle: Union[FreeParameterExpression, float], + power: float = 1, ) -> Instruction: """Registers this function into the circuit class. Args: - control (QubitInput): Control qubit index. + control (QubitSetInput): Control qubit(s). The last control qubit + is absorbed into the target of the instruction. target (QubitInput): Target qubit index. angle (Union[FreeParameterExpression, float]): angle in radians. + 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: CPhaseShift01 instruction. @@ -1287,7 +1681,14 @@ def cphaseshift01( Examples: >>> circ = Circuit().cphaseshift01(0, 1, 0.15) """ - return Instruction(CPhaseShift01(angle), target=[control, target]) + control_qubits = QubitSet(control) + absorbed_control = control_qubits.pop() + return Instruction( + CPhaseShift01(angle), + target=[absorbed_control, target], + control=control_qubits, + power=power, + ) Gate.register_gate(CPhaseShift01) @@ -1307,16 +1708,13 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=["C", angled_ascii_characters("PHASE10", angle)], ) + @property + def _qasm_name(self): + return "cphaseshift10" + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CPhaseShift10.construct(control=target[0], target=target[1], angle=self.angle) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - control_qubit = serialization_properties.format_target(int(target[0])) - target_qubit = serialization_properties.format_target(int(target[1])) - return f"cphaseshift10({self.angle}) {control_qubit}, {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.diag([1.0, 1.0, np.exp(1j * self.angle), 1.0]) @@ -1330,14 +1728,21 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def cphaseshift10( - control: QubitInput, target: QubitInput, angle: Union[FreeParameterExpression, float] + control: QubitSetInput, + target: QubitInput, + angle: Union[FreeParameterExpression, float], + power: float = 1, ) -> Instruction: """Registers this function into the circuit class. Args: - control (QubitInput): Control qubit index. + control (QubitSetInput): Control qubit(s). The last control qubit + is absorbed into the target of the instruction. target (QubitInput): Target qubit index. angle (Union[FreeParameterExpression, float]): angle in radians. + 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: CPhaseShift10 instruction. @@ -1345,7 +1750,14 @@ def cphaseshift10( Examples: >>> circ = Circuit().cphaseshift10(0, 1, 0.15) """ - return Instruction(CPhaseShift10(angle), target=[control, target]) + control_qubits = QubitSet(control) + absorbed_control = control_qubits.pop() + return Instruction( + CPhaseShift10(angle), + target=[absorbed_control, target], + control=control_qubits, + power=power, + ) Gate.register_gate(CPhaseShift10) @@ -1357,19 +1769,16 @@ class CV(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "V"]) + @property + def _qasm_name(self): + return "cv" + def adjoint(self) -> List[Gate]: return [self, self, self] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CV.construct(control=target[0], target=target[1]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - control_qubit = serialization_properties.format_target(int(target[0])) - target_qubit = serialization_properties.format_target(int(target[1])) - return f"cv {control_qubit}, {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.array( [ @@ -1387,12 +1796,16 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def cv(control: QubitInput, target: QubitInput) -> Instruction: + def cv(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruction: """Registers this function into the circuit class. Args: - control (QubitInput): Control qubit index. + control (QubitSetInput): Control qubit(s). The last control qubit + is absorbed into the target of the instruction. target (QubitInput): Target qubit index. + 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: CV instruction. @@ -1400,7 +1813,11 @@ def cv(control: QubitInput, target: QubitInput) -> Instruction: Examples: >>> circ = Circuit().cv(0, 1) """ - return Instruction(CV(), target=[control, target]) + control_qubits = QubitSet(control) + absorbed_control = control_qubits.pop() + return Instruction( + CV(), target=[absorbed_control, target], control=control_qubits, power=power + ) Gate.register_gate(CV) @@ -1412,19 +1829,16 @@ class CY(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "Y"]) + @property + def _qasm_name(self): + return "cy" + def adjoint(self) -> List[Gate]: return [CY()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CY.construct(control=target[0], target=target[1]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[1])) - control_qubit = serialization_properties.format_target(int(target[0])) - return f"cy {control_qubit}, {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.array( [ @@ -1442,12 +1856,16 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def cy(control: QubitInput, target: QubitInput) -> Instruction: + def cy(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruction: """Registers this function into the circuit class. Args: - control (QubitInput): Control qubit index. + control (QubitSetInput): Control qubit(s). The last control qubit + is absorbed into the target of the instruction. target (QubitInput): Target qubit index. + 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: CY instruction. @@ -1455,7 +1873,11 @@ def cy(control: QubitInput, target: QubitInput) -> Instruction: Examples: >>> circ = Circuit().cy(0, 1) """ - return Instruction(CY(), target=[control, target]) + control_qubits = QubitSet(control) + absorbed_control = control_qubits.pop() + return Instruction( + CY(), target=[absorbed_control, target], control=control_qubits, power=power + ) Gate.register_gate(CY) @@ -1467,19 +1889,16 @@ class CZ(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "Z"]) + @property + def _qasm_name(self): + return "cz" + def adjoint(self) -> List[Gate]: return [CZ()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CZ.construct(control=target[0], target=target[1]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[1])) - control_qubit = serialization_properties.format_target(int(target[0])) - return f"cz {control_qubit}, {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.diag([complex(1.0), 1.0, 1.0, -1.0]) @@ -1489,12 +1908,16 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def cz(control: QubitInput, target: QubitInput) -> Instruction: + def cz(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruction: """Registers this function into the circuit class. Args: - control (QubitInput): Control qubit index. + control (QubitSetInput): Control qubit(s). The last control qubit + is absorbed into the target of the instruction. target (QubitInput): Target qubit index. + 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: CZ instruction. @@ -1502,7 +1925,11 @@ def cz(control: QubitInput, target: QubitInput) -> Instruction: Examples: >>> circ = Circuit().cz(0, 1) """ - return Instruction(CZ(), target=[control, target]) + control_qubits = QubitSet(control) + absorbed_control = control_qubits.pop() + return Instruction( + CZ(), target=[absorbed_control, target], control=control_qubits, power=power + ) Gate.register_gate(CZ) @@ -1514,19 +1941,16 @@ class ECR(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["ECR", "ECR"]) + @property + def _qasm_name(self): + return "ecr" + def adjoint(self) -> List[Gate]: return [ECR()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.ECR.construct(targets=[target[0], target[1]]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit_0 = serialization_properties.format_target(int(target[0])) - target_qubit_1 = serialization_properties.format_target(int(target[1])) - return f"ecr {target_qubit_0}, {target_qubit_1};" - def to_matrix(self) -> np.ndarray: return ( 1 @@ -1543,12 +1967,29 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def ecr(target1: QubitInput, target2: QubitInput) -> Instruction: + def ecr( + target1: QubitInput, + target2: QubitInput, + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Instruction: """Registers this function into the circuit class. Args: target1 (QubitInput): Target qubit 1 index. target2 (QubitInput): Target qubit 2 index. + 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: ECR instruction. @@ -1556,7 +1997,13 @@ def ecr(target1: QubitInput, target2: QubitInput) -> Instruction: Examples: >>> circ = Circuit().ecr(0, 1) """ - return Instruction(ECR(), target=[target1, target2]) + return Instruction( + ECR(), + target=[target1, target2], + control=control, + control_state=control_state, + power=power, + ) Gate.register_gate(ECR) @@ -1581,16 +2028,13 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ], ) + @property + def _qasm_name(self): + return "xx" + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.XX.construct(targets=[target[0], target[1]], angle=self.angle) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit_1 = serialization_properties.format_target(int(target[0])) - target_qubit_2 = serialization_properties.format_target(int(target[1])) - return f"xx({self.angle}) {target_qubit_1}, {target_qubit_2};" - def to_matrix(self) -> np.ndarray: """Returns a matrix representation of this gate. Returns: @@ -1618,7 +2062,13 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def xx( - target1: QubitInput, target2: QubitInput, angle: Union[FreeParameterExpression, float] + target1: QubitInput, + target2: QubitInput, + angle: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, ) -> Instruction: """Registers this function into the circuit class. @@ -1626,6 +2076,16 @@ def xx( target1 (QubitInput): Target qubit 1 index. target2 (QubitInput): Target qubit 2 index. angle (Union[FreeParameterExpression, float]): 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: Instruction: XX instruction. @@ -1633,7 +2093,13 @@ def xx( Examples: >>> circ = Circuit().xx(0, 1, 0.15) """ - return Instruction(XX(angle), target=[target1, target2]) + return Instruction( + XX(angle), + target=[target1, target2], + control=control, + control_state=control_state, + power=power, + ) Gate.register_gate(XX) @@ -1658,16 +2124,13 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ], ) + @property + def _qasm_name(self): + return "yy" + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.YY.construct(targets=[target[0], target[1]], angle=self.angle) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit_1 = serialization_properties.format_target(int(target[0])) - target_qubit_2 = serialization_properties.format_target(int(target[1])) - return f"yy({self.angle}) {target_qubit_1}, {target_qubit_2};" - def to_matrix(self) -> np.ndarray: """Returns a matrix representation of this gate. Returns: @@ -1695,7 +2158,13 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def yy( - target1: QubitInput, target2: QubitInput, angle: Union[FreeParameterExpression, float] + target1: QubitInput, + target2: QubitInput, + angle: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, ) -> Instruction: """Registers this function into the circuit class. @@ -1703,6 +2172,16 @@ def yy( target1 (QubitInput): Target qubit 1 index. target2 (QubitInput): Target qubit 2 index. angle (Union[FreeParameterExpression, float]): 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: Instruction: YY instruction. @@ -1710,7 +2189,13 @@ def yy( Examples: >>> circ = Circuit().yy(0, 1, 0.15) """ - return Instruction(YY(angle), target=[target1, target2]) + return Instruction( + YY(angle), + target=[target1, target2], + control=control, + control_state=control_state, + power=power, + ) Gate.register_gate(YY) @@ -1735,16 +2220,13 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ], ) + @property + def _qasm_name(self): + return "zz" + def _to_jaqcd(self, target: QubitSet) -> Any: return ir.ZZ.construct(targets=[target[0], target[1]], angle=self.angle) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit_1 = serialization_properties.format_target(int(target[0])) - target_qubit_2 = serialization_properties.format_target(int(target[1])) - return f"zz({self.angle}) {target_qubit_1}, {target_qubit_2};" - def to_matrix(self) -> np.ndarray: return np.array( [ @@ -1766,7 +2248,13 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def zz( - target1: QubitInput, target2: QubitInput, angle: Union[FreeParameterExpression, float] + target1: QubitInput, + target2: QubitInput, + angle: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, ) -> Instruction: """Registers this function into the circuit class. @@ -1774,6 +2262,16 @@ def zz( target1 (QubitInput): Target qubit 1 index. target2 (QubitInput): Target qubit 2 index. angle (Union[FreeParameterExpression, float]): 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: Instruction: ZZ instruction. @@ -1781,7 +2279,13 @@ def zz( Examples: >>> circ = Circuit().zz(0, 1, 0.15) """ - return Instruction(ZZ(angle), target=[target1, target2]) + return Instruction( + ZZ(angle), + target=[target1, target2], + control=control, + control_state=control_state, + power=power, + ) Gate.register_gate(ZZ) @@ -1796,20 +2300,16 @@ class CCNot(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "C", "X"]) + @property + def _qasm_name(self): + return "ccnot" + def adjoint(self) -> List[Gate]: return [CCNot()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CCNot.construct(controls=[target[0], target[1]], target=target[2]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - control_qubit_0 = serialization_properties.format_target(int(target[0])) - control_qubit_1 = serialization_properties.format_target(int(target[1])) - target_qubit = serialization_properties.format_target(int(target[2])) - return f"ccnot {control_qubit_0}, {control_qubit_1}, {target_qubit};" - def to_matrix(self) -> np.ndarray: return np.array( [ @@ -1831,13 +2331,33 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def ccnot(control1: QubitInput, control2: QubitInput, target: QubitInput) -> Instruction: + def ccnot( + control1: QubitInput, + control2: QubitInput, + target: QubitInput, + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Instruction: """Registers this function into the circuit class. Args: control1 (QubitInput): Control qubit 1 index. control2 (QubitInput): Control qubit 2 index. target (QubitInput): Target qubit index. + control (Optional[QubitSetInput]): Control qubit(s), in addition to + control1 and control2. 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. Control state only applies to control qubits specified with + the control argument, not control1 and control2. 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: CCNot instruction. @@ -1845,7 +2365,13 @@ def ccnot(control1: QubitInput, control2: QubitInput, target: QubitInput) -> Ins Examples: >>> circ = Circuit().ccnot(0, 1, 2) """ - return Instruction(CCNot(), target=[control1, control2, target]) + return Instruction( + CCNot(), + target=[control1, control2, target], + control=control, + control_state=control_state, + power=power, + ) Gate.register_gate(CCNot) @@ -1857,21 +2383,16 @@ class CSwap(Gate): def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "SWAP", "SWAP"]) + @property + def _qasm_name(self): + return "cswap" + def adjoint(self) -> List[Gate]: return [CSwap()] def _to_jaqcd(self, target: QubitSet) -> Any: return ir.CSwap.construct(control=target[0], targets=[target[1], target[2]]) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - control_qubit = serialization_properties.format_target(int(target[0])) - target_qubit_0 = serialization_properties.format_target(int(target[1])) - target_qubit_1 = serialization_properties.format_target(int(target[2])) - - return f"cswap {control_qubit}, {target_qubit_0}, {target_qubit_1};" - def to_matrix(self) -> np.ndarray: return np.array( [ @@ -1893,13 +2414,22 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) - def cswap(control: QubitInput, target1: QubitInput, target2: QubitInput) -> Instruction: + def cswap( + control: QubitSetInput, + target1: QubitInput, + target2: QubitInput, + power: float = 1, + ) -> Instruction: """Registers this function into the circuit class. Args: - control (QubitInput): Control qubit index + control (QubitSetInput): Control qubit(s). The last control qubit + is absorbed into the target of the instruction. target1 (QubitInput): Target qubit 1 index. target2 (QubitInput): Target qubit 2 index. + 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: CSwap instruction. @@ -1907,7 +2437,14 @@ def cswap(control: QubitInput, target1: QubitInput, target2: QubitInput) -> Inst Examples: >>> circ = Circuit().cswap(0, 1, 2) """ - return Instruction(CSwap(), target=[control, target1, target2]) + control_qubits = QubitSet(control) + absorbed_control = control_qubits.pop() + return Instruction( + CSwap(), + target=[absorbed_control, target1, target2], + control=control_qubits, + power=power, + ) Gate.register_gate(CSwap) @@ -1927,11 +2464,9 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=[angled_ascii_characters("GPi", angle)], ) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"gpi({self.angle}) {target_qubit};" + @property + def _qasm_name(self): + return "gpi" def to_matrix(self) -> np.ndarray: return np.array( @@ -1954,13 +2489,28 @@ def bind_values(self, **kwargs) -> GPi: @staticmethod @circuit.subroutine(register=True) def gpi( - target: QubitInput, angle: Union[FreeParameterExpression, float] + target: QubitSetInput, + angle: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (QubitInput): Target qubit index. + target (QubitSetInput): Target qubit(s). angle (Union[FreeParameterExpression, float]): 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]: GPi instruction. @@ -1968,7 +2518,12 @@ def gpi( Examples: >>> circ = Circuit().gpi(0, 0.15) """ - return [Instruction(GPi(angle), target=qubit) for qubit in QubitSet(target)] + return [ + Instruction( + GPi(angle), target=qubit, control=control, control_state=control_state, power=power + ) + for qubit in QubitSet(target) + ] Gate.register_gate(GPi) @@ -1988,11 +2543,9 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ascii_symbols=[angled_ascii_characters("GPi2", angle)], ) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"gpi2({self.angle}) {target_qubit};" + @property + def _qasm_name(self): + return "gpi2" def to_matrix(self) -> np.ndarray: return np.array( @@ -2015,13 +2568,28 @@ def bind_values(self, **kwargs) -> GPi2: @staticmethod @circuit.subroutine(register=True) def gpi2( - target: QubitInput, angle: Union[FreeParameterExpression, float] + target: QubitSetInput, + angle: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, ) -> Iterable[Instruction]: """Registers this function into the circuit class. Args: - target (QubitInput): Target qubit index. + target (QubitSetInput): Target qubit(s). angle (Union[FreeParameterExpression, float]): 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]: GPi2 instruction. @@ -2029,7 +2597,12 @@ def gpi2( Examples: >>> circ = Circuit().gpi2(0, 0.15) """ - return [Instruction(GPi2(angle), target=qubit) for qubit in QubitSet(target)] + return [ + Instruction( + GPi2(angle), target=qubit, control=control, control_state=control_state, power=power + ) + for qubit in QubitSet(target) + ] Gate.register_gate(GPi2) @@ -2055,12 +2628,9 @@ def __init__( ascii_symbols=[_double_angled_ascii_characters("MS", angle_1, angle_2)] * 2, ) - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - target_qubit_1 = serialization_properties.format_target(int(target[0])) - target_qubit_2 = serialization_properties.format_target(int(target[1])) - return f"ms({self.angle_1}, {self.angle_2}) {target_qubit_1}, {target_qubit_2};" + @property + def _qasm_name(self): + return "ms" def to_matrix(self) -> np.ndarray: return np.array( @@ -2089,6 +2659,10 @@ def ms( target2: QubitInput, angle_1: Union[FreeParameterExpression, float], angle_2: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, ) -> Iterable[Instruction]: """Registers this function into the circuit class. @@ -2097,6 +2671,16 @@ def ms( target2 (QubitInput): Target qubit 2 index. angle_1 (Union[FreeParameterExpression, float]): angle in radians. angle_2 (Union[FreeParameterExpression, float]): 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]: MS instruction. @@ -2104,7 +2688,15 @@ def ms( Examples: >>> circ = Circuit().ms(0, 1, 0.15, 0.34) """ - return [Instruction(MS(angle_1, angle_2), target=[target1, target2])] + return [ + Instruction( + MS(angle_1, angle_2), + target=[target1, target2], + control=control, + control_state=control_state, + power=power, + ) + ] Gate.register_gate(MS) @@ -2191,6 +2783,7 @@ def unitary(targets: QubitSet, matrix: np.ndarray, display_name: str = "U") -> I Examples: >>> circ = Circuit().unitary(matrix=np.array([[0, 1],[1, 0]]), targets=[0]) """ + # todo: handle controlled unitary if 2 ** len(targets) != matrix.shape[0]: raise ValueError("Dimensions of the supplied unitary are incompatible with the targets") @@ -2257,7 +2850,13 @@ def _to_openqasm( @staticmethod @circuit.subroutine(register=True) def pulse_gate( - targets: QubitSet, pulse_sequence: PulseSequence, display_name: str = "PG" + targets: QubitSet, + pulse_sequence: PulseSequence, + display_name: str = "PG", + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, ) -> Instruction: """Arbitrary pulse gate which provides the ability to embed custom pulse sequences within circuits. @@ -2268,6 +2867,16 @@ def pulse_gate( pulse_sequence (PulseSequence): PulseSequence to embed within the circuit. display_name (str): Name to be used for an instance of this pulse gate for circuit diagrams. Defaults to `PG`. + 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: Pulse gate instruction. @@ -2277,7 +2886,11 @@ def pulse_gate( >>> circ = Circuit().pulse_gate(pulse_sequence=pulse_seq, targets=[0]) """ return Instruction( - PulseGate(pulse_sequence, len(QubitSet(targets)), display_name), target=targets + PulseGate(pulse_sequence, len(QubitSet(targets)), display_name), + target=targets, + control=control, + control_state=control_state, + power=power, ) diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index d0ceb286..0b1cca60 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -13,8 +13,9 @@ from __future__ import annotations -from typing import Any, Dict, List, Tuple +from typing import Any, Dict, List, Optional, Tuple +from braket.circuits.basis_state import BasisState, BasisStateInput from braket.circuits.compiler_directive import CompilerDirective from braket.circuits.gate import Gate from braket.circuits.operator import Operator @@ -32,13 +33,32 @@ class Instruction: An instruction is a quantum directive that describes the task to perform on a quantum device. """ - def __init__(self, operator: InstructionOperator, target: QubitSetInput = None): + def __init__( + self, + operator: InstructionOperator, + target: QubitSetInput = None, + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ): """ InstructionOperator includes objects of type `Gate` and `Noise` only. Args: operator (InstructionOperator): Operator for the instruction. target (QubitSetInput): Target qubits that the operator is applied to. Default is None. + control (QubitSetInput): Target qubits that the operator is controlled on. + Default is 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. Raises: ValueError: If `operator` is empty or any integer in `target` does not meet the `Qubit` @@ -56,10 +76,17 @@ def __init__(self, operator: InstructionOperator, target: QubitSetInput = None): Instruction('operator': H, 'target': QubitSet(Qubit(0),)) >>> instr = Instruction(Gate.Rx(0.12), 0) Instruction('operator': Rx, 'target': QubitSet(Qubit(0),)) + >>> instr = Instruction(Gate.Rx(0.12, control=1), 0) + Instruction( + 'operator': Rx, + 'target': QubitSet(Qubit(0),), + 'control': QubitSet(Qubit(1),), + ) """ if not operator: raise ValueError("Operator cannot be empty") target_set = QubitSet(target) + control_set = QubitSet(control) if isinstance(operator, QuantumOperator) and len(target_set) != operator.qubit_count: raise ValueError( f"Operator qubit count {operator.qubit_count} must be equal to" @@ -67,6 +94,12 @@ def __init__(self, operator: InstructionOperator, target: QubitSetInput = None): ) self._operator = operator self._target = target_set + self._control = control_set + self._control_state = BasisState( + (1,) * len(control_set) if control_state is None else control_state, + len(control_set), + ) + self._power = power @property def operator(self) -> InstructionOperator: @@ -77,12 +110,30 @@ def operator(self) -> InstructionOperator: def target(self) -> QubitSet: """ QubitSet: Target qubits that the operator is applied to. - - Note: - Don't mutate this property, any mutations can have unexpected consequences. """ return self._target + @property + def control(self) -> QubitSet: + """ + 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. + """ + return self._control_state + + @property + def power(self) -> float: + """ + float: Power that the operator is raised to. + """ + return self._power + def adjoint(self) -> List[Instruction]: """Returns a list of Instructions implementing adjoint of this instruction's own operator @@ -96,7 +147,16 @@ def adjoint(self) -> List[Instruction]: """ operator = self._operator if isinstance(operator, Gate): - return [Instruction(gate, self._target) for gate in operator.adjoint()] + return [ + Instruction( + gate, + self._target, + control=self._control, + control_state=self._control_state, + power=self._power, + ) + for gate in operator.adjoint() + ] elif isinstance(operator, CompilerDirective): return [Instruction(operator.counterpart(), self._target)] raise NotImplementedError(f"Adjoint not supported for {operator}") @@ -120,10 +180,17 @@ def to_ir( Returns: Any: IR object of the instruction. """ + kwargs = {} + if self.control: + kwargs["control"] = self.control + kwargs["control_state"] = self.control_state + if self.power != 1: + kwargs["power"] = self.power return self._operator.to_ir( [int(qubit) for qubit in self._target], ir_type=ir_type, serialization_properties=serialization_properties, + **kwargs, ) @property @@ -132,7 +199,13 @@ def ascii_symbols(self) -> Tuple[str, ...]: return self._operator.ascii_symbols def copy( - self, target_mapping: Dict[QubitInput, QubitInput] = None, target: QubitSetInput = None + self, + target_mapping: Dict[QubitInput, QubitInput] = None, + target: QubitSetInput = None, + control_mapping: Dict[QubitInput, QubitInput] = None, + control: QubitSetInput = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, ) -> Instruction: """ Return a shallow copy of the instruction. @@ -140,12 +213,26 @@ def copy( Note: If `target_mapping` is specified, then `self.target` is mapped to the specified qubits. This is useful apply an instruction to a circuit and change the target qubits. + Same relationship holds for `control_mapping`. Args: target_mapping (Dict[QubitInput, QubitInput]): A dictionary of qubit mappings to apply to the target. Key is the qubit in this `target` and the value is what the key is changed to. Default = `None`. target (QubitSetInput): Target qubits for the new instruction. Default is None. + control_mapping (Dict[QubitInput, QubitInput]): A dictionary of + qubit mappings to apply to the control. Key is the qubit in this `control` and the + value is what the key is changed to. Default = `None`. + control (QubitSetInput): Control qubits for the new instruction. Default is 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: A shallow copy of the instruction. @@ -167,15 +254,49 @@ def copy( """ 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: - return Instruction(self._operator, target) - else: - return Instruction(self._operator, self._target.map(target_mapping or {})) + if control_mapping and control is not None: + raise TypeError("Only 'control_mapping' or 'control' can be supplied, but not both.") + + new_target = self._target.map(target_mapping or {}) if target is None else target + new_control = self._control.map(control_mapping or {}) if control is None else control + new_control_state = self._control_state if control_state is None else control_state + + return Instruction( + self._operator, + new_target, + control=new_control, + control_state=new_control_state, + power=power, + ) def __repr__(self): - return f"Instruction('operator': {self._operator}, 'target': {self._target})" + return ( + f"Instruction('operator': {self._operator}, " + f"'target': {self._target}, " + f"'control': {self._control}, " + f"'control_state': {self._control_state.as_tuple}, " + f"'power': {self.power})" + ) def __eq__(self, other): if isinstance(other, Instruction): - return (self._operator, self._target) == (other._operator, other._target) + return ( + self._operator, + self._target, + self._control, + self._control_state, + self._power, + ) == ( + other._operator, + other._target, + self._control, + self._control_state, + self._power, + ) return NotImplemented + + def __pow__(self, power, modulo=None): + new_power = self.power * power + if modulo is not None: + new_power %= modulo + return self.copy(power=new_power) diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 5221e26f..f1387d02 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -192,12 +192,10 @@ def _add(self, instruction: Instruction, noise_index: int = 0) -> None: elif isinstance(operator, Noise): self.add_noise(instruction) else: - qubit_range = instruction.target + qubit_range = instruction.target.union(instruction.control) time = self._update_qubit_times(qubit_range) - self._moments[ - MomentsKey(time, instruction.target, MomentType.GATE, noise_index) - ] = instruction - self._qubits.update(instruction.target) + self._moments[MomentsKey(time, qubit_range, MomentType.GATE, noise_index)] = instruction + self._qubits.update(qubit_range) self._depth = max(self._depth, time + 1) def _update_qubit_times(self, qubits: QubitSet) -> int: 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 8fade296..3f3268f8 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -187,8 +187,26 @@ def test_connector_across_two_qubits(): _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 : -C-H-", + " | ", + "q4 : -C-H-", + " | ", + "q5 : -X-H-", + "", + "T : |0|1|", + ) + _assert_correct_diagram(circ, expected) + + def test_overlapping_qubits(): - circ = Circuit().cnot(0, 2).cnot(1, 3).h(0) + circ = Circuit().cnot(0, 2).x(control=1, target=3).h(0) expected = ( "T : | 0 |1|", " ", @@ -206,7 +224,7 @@ def test_overlapping_qubits(): def test_overlapping_qubits_angled_gates(): - circ = Circuit().zz(0, 2, 0.15).cnot(1, 3).h(0) + circ = Circuit().zz(0, 2, 0.15).x(control=1, target=3).h(0) expected = ( "T : | 0 |1|", " ", @@ -224,7 +242,7 @@ def test_overlapping_qubits_angled_gates(): def test_connector_across_gt_two_qubits(): - circ = Circuit().h(4).cnot(3, 5).h(4).h(2) + circ = Circuit().h(4).x(control=3, target=5).h(4).h(2) expected = ( "T : | 0 |1|", " ", @@ -738,3 +756,35 @@ def test_hamiltonian(): "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, 2), control=0, power=4)) + expected = ( + "T : | 0 | 1 | 2 | 3 | 4 |", + " ", + "q0 : -H---------(FOO^-1)-C-------C-------C-------", + " | | | ", + "q1 : -(H^0)--------------(FOO^2)-C-------(FOO^4)-", + " | | ", + "q2 : -(H^-3.14)------------------(FOO^3)-(FOO^4)-", + "", + "T : | 0 | 1 | 2 | 3 | 4 |", + ) + _assert_correct_diagram(circ, expected) diff --git a/test/unit_tests/braket/circuits/test_basis_state.py b/test/unit_tests/braket/circuits/test_basis_state.py new file mode 100644 index 00000000..023494fa --- /dev/null +++ b/test/unit_tests/braket/circuits/test_basis_state.py @@ -0,0 +1,56 @@ +import pytest + +from braket.circuits.basis_state import BasisState + + +@pytest.mark.parametrize( + "basis_state_input, size, as_tuple, as_int, as_string", + ( + ( + [1, 0, 1], + None, + (1, 0, 1), + 5, + "101", + ), + ( + [1, 0, 1], + 5, + (0, 0, 1, 0, 1), + 5, + "00101", + ), + ( + "1", + 3, + (0, 0, 1), + 1, + "001", + ), + ( + "101", + None, + (1, 0, 1), + 5, + "101", + ), + ( + 5, + None, + (1, 0, 1), + 5, + "101", + ), + ( + 5, + 4, + (0, 1, 0, 1), + 5, + "0101", + ), + ), +) +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 diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index f191f11e..5e534999 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -766,6 +766,72 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): inputs={}, ), ), + ( + Circuit() + .rx(0, 0.15, control=2, control_state=0) + .rx(1, 0.3, control=[2, 3]) + .cnot(target=0, control=[2, 3, 4]), + OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), + 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];", + ] + ), + inputs={}, + ), + ), + ( + 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;", + "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), + OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), + 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];", + ] + ), + inputs={}, + ), + ), ], ) def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): diff --git a/test/unit_tests/braket/circuits/test_gate.py b/test/unit_tests/braket/circuits/test_gate.py index 98e300ff..600a108f 100644 --- a/test/unit_tests/braket/circuits/test_gate.py +++ b/test/unit_tests/braket/circuits/test_gate.py @@ -13,7 +13,7 @@ import pytest -from braket.circuits import Gate, QuantumOperator +from braket.circuits import Gate, QuantumOperator, QubitSet from braket.circuits.serialization import IRType @@ -82,21 +82,38 @@ def __init__(self): @pytest.mark.parametrize( - "ir_type, serialization_properties, expected_exception, expected_message", + "ir_type, serialization_properties, expected_exception, expected_message, control", [ - (IRType.JAQCD, None, NotImplementedError, "to_jaqcd is not implemented."), - (IRType.OPENQASM, None, NotImplementedError, "to_openqasm has not been implemented yet."), - ("invalid-ir-type", None, ValueError, "Supplied ir_type invalid-ir-type is not supported."), + (IRType.JAQCD, None, NotImplementedError, "to_jaqcd is not implemented.", None), + ( + IRType.JAQCD, + None, + ValueError, + "Gate modifiers are not supported with Jaqcd.", + QubitSet(0), + ), + ( + "invalid-ir-type", + None, + ValueError, + "Supplied ir_type invalid-ir-type is not supported.", + None, + ), ( IRType.OPENQASM, "invalid-property-type", ValueError, "serialization_properties must be of type OpenQASMSerializationProperties for " "IRType.OPENQASM.", + None, ), ], ) -def test_gate_to_ir(ir_type, serialization_properties, expected_exception, expected_message, gate): +def test_gate_to_ir( + ir_type, serialization_properties, expected_exception, expected_message, gate, control +): with pytest.raises(expected_exception) as exc: - gate.to_ir(0, ir_type, serialization_properties=serialization_properties) + gate.to_ir( + QubitSet(0), ir_type, serialization_properties=serialization_properties, control=control + ) assert exc.value.args[0] == expected_message diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 5ec40626..89ce7fb5 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -1010,3 +1010,142 @@ def test_pulse_gate_to_matrix(): ), 1, ).to_matrix() + + +@pytest.mark.parametrize( + "gate, target, control, control_state, expected_ir", + ( + (Gate.H(), QubitSet(0), QubitSet(1), None, "ctrl @ h q[1], q[0];"), + (Gate.H(), QubitSet(0), QubitSet([1, 2]), None, "ctrl(2) @ h q[1], q[2], q[0];"), + (Gate.Ry(angle=1.23), QubitSet(0), QubitSet([2]), None, "ctrl @ ry(1.23) q[2], q[0];"), + ( + Gate.MS(angle_1=0.17, angle_2=3.45), + QubitSet(0), + QubitSet([1, 2]), + None, + "ctrl(2) @ ms(0.17, 3.45) q[1], q[2], q[0];", + ), + ( + Gate.CCNot(), + QubitSet([0, 1, 2]), + QubitSet([3, 4]), + None, + "ctrl(2) @ ccnot q[3], q[4], q[0], q[1], q[2];", + ), + ( + Gate.Z(), + QubitSet([0]), + QubitSet([1, 2, 3, 4, 5, 6, 7]), + [1, 1, 1, 0, 0, 1, 0], + "ctrl(3) @ negctrl(2) @ ctrl @ negctrl @ " + "z q[1], q[2], q[3], q[4], q[5], q[6], q[7], q[0];", + ), + ( + Gate.Z(), + QubitSet([0]), + QubitSet([1, 2, 3, 4, 5, 6, 7]), + "1110010", + "ctrl(3) @ negctrl(2) @ ctrl @ negctrl @ " + "z q[1], q[2], q[3], q[4], q[5], q[6], q[7], q[0];", + ), + ( + Gate.Z(), + QubitSet([0]), + QubitSet([1, 2, 3, 4, 5, 6, 7]), + 114, + "ctrl(3) @ negctrl(2) @ ctrl @ negctrl @ " + "z q[1], q[2], q[3], q[4], q[5], q[6], q[7], q[0];", + ), + ( + Gate.Z(), + QubitSet([0]), + QubitSet([1, 2, 3]), + [1, 0], + "negctrl @ ctrl @ negctrl @ z q[1], q[2], q[3], q[0];", + ), + ( + Gate.Z(), + QubitSet([0]), + QubitSet([1, 2, 3]), + "10", + "negctrl @ ctrl @ negctrl @ z q[1], q[2], q[3], q[0];", + ), + ), +) +def test_gate_control(gate, target, control, control_state, expected_ir): + serialization_properties = OpenQASMSerializationProperties( + qubit_reference_type=QubitReferenceType.VIRTUAL + ) + assert ( + gate.to_ir( + target, + control=control, + control_state=control_state, + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + ) + == expected_ir + ) + + +@pytest.mark.parametrize( + "control, control_state, error_string", + ( + ( + [0, 1], + [1, 0, 1], + "State value represents a binary sequence of length greater " + "than the specified number of qubits.", + ), + ( + [0, 1], + "101", + "State value represents a binary sequence of length greater " + "than the specified number of qubits.", + ), + ( + [0, 1], + 5, + "State value represents a binary sequence of length greater " + "than the specified number of qubits.", + ), + ), +) +def test_gate_control_invalid_state(control, control_state, error_string): + with pytest.raises(ValueError, match=error_string): + Gate.X().to_ir( + target=[0], + control=control, + control_state=control_state, + ir_type=IRType.OPENQASM, + serialization_properties=OpenQASMSerializationProperties( + qubit_reference_type=QubitReferenceType.VIRTUAL + ), + ) + + +@pytest.mark.parametrize( + "gate, target, power, expected_ir", + ( + (Gate.H(), QubitSet(0), 2, "pow(2) @ h q[0];"), + (Gate.H(), QubitSet(0), 2.0, "pow(2.0) @ h q[0];"), + (Gate.H(), QubitSet(0), 2.5, "pow(2.5) @ h q[0];"), + (Gate.H(), QubitSet(0), 0, "pow(0) @ h q[0];"), + (Gate.H(), QubitSet(0), -2, "inv @ pow(2) @ h q[0];"), + (Gate.H(), QubitSet(0), -2.0, "inv @ pow(2.0) @ h q[0];"), + (Gate.H(), QubitSet(0), -2.5, "inv @ pow(2.5) @ h q[0];"), + ), +) +def test_gate_power(gate, target, power, expected_ir): + serialization_properties = OpenQASMSerializationProperties( + qubit_reference_type=QubitReferenceType.VIRTUAL + ) + assert ( + gate.to_ir( + target, + power=power, + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + ) + == expected_ir + ) diff --git a/test/unit_tests/braket/circuits/test_instruction.py b/test/unit_tests/braket/circuits/test_instruction.py index 3cb12a22..3b8f2fc9 100644 --- a/test/unit_tests/braket/circuits/test_instruction.py +++ b/test/unit_tests/braket/circuits/test_instruction.py @@ -31,6 +31,11 @@ def cnot(): return Instruction(Gate.CNot(), [0, 1]) +@pytest.fixture +def ccry(): + return Instruction(Gate.Ry(1.23), 0, control=[1, 2]) + + @pytest.mark.xfail(raises=ValueError) def test_empty_operator(): Instruction(None, target=0) @@ -101,7 +106,16 @@ def test_adjoint_unsupported(): def test_str(instr): - expected = "Instruction('operator': {}, 'target': {})".format(instr.operator, instr.target) + expected = ( + "Instruction('operator': {}, 'target': {}, " + "'control': {}, 'control_state': {}, 'power': {})" + ).format( + instr.operator, + instr.target, + instr.control, + instr.control_state.as_tuple, + instr.power, + ) assert str(instr) == expected @@ -151,12 +165,43 @@ def test_copy_with_mapping(cnot): assert cnot.copy(target_mapping=target_mapping) == expected +def test_copy_with_control_mapping(ccry): + control_mapping = {1: 10, 2: 11} + expected = Instruction(Gate.Ry(1.23), target=1, control=[10, 11]) + assert ccry.copy(target=1, control_mapping=control_mapping) == expected + + def test_copy_with_target(cnot): target = [10, 11] expected = Instruction(Gate.CNot(), target) assert cnot.copy(target=target) == expected -@pytest.mark.xfail(raises=TypeError) +def test_copy_with_control(ccry): + control = [10, 11] + expected = Instruction(Gate.Ry(1.23), 3, control=control) + assert ccry.copy(target=3, control=control) == expected + + def test_copy_with_target_and_mapping(instr): - instr.copy(target=[10], target_mapping={0: 10}) + cant_do_both = "Only 'target_mapping' or 'target' can be supplied, but not both." + with pytest.raises(TypeError, match=cant_do_both): + instr.copy(target=[10], target_mapping={0: 10}) + + +def test_copy_with_control_target_and_mapping(instr): + cant_do_both = "Only 'control_mapping' or 'control' can be supplied, but not both." + with pytest.raises(TypeError, match=cant_do_both): + instr.copy(target=[10], control=[10], control_mapping={0: 10}) + + +def test_pow(instr): + assert instr.power == 1 + cubed = instr**3 + assert instr.power == 1 + assert cubed.power == 3 + then_squared = cubed**2 + assert cubed.power == 3 + assert then_squared.power == 6 + modded = then_squared.__pow__(6, 5) + assert modded.power == 1 From 94096de968ab59f935e15135d11d3bad572cae4b Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 25 May 2023 21:33:03 +0000 Subject: [PATCH 0682/1165] prepare release v1.40.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 d034ec94..61fd1ec3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.40.0 (2023-05-25) + +### Features + + * gate modifiers + ## v1.39.1 (2023-05-18) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index de54143d..583550a9 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.39.2.dev0" +__version__ = "1.40.0" From 85d55f570f88ac7fe6f2b3ca5742e64ebb0b7948 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 25 May 2023 21:33:03 +0000 Subject: [PATCH 0683/1165] update development version to v1.40.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 583550a9..6f021fc0 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.40.0" +__version__ = "1.40.1.dev0" From 1a57c2f9ea31bb479639ba7a30289fa9104a0c60 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 26 May 2023 13:59:28 -0600 Subject: [PATCH 0684/1165] infra: Don't run dependent tests and twine check on push (#563) --- .github/workflows/dependent-tests.yml | 3 --- .github/workflows/twine-check.yml | 3 --- 2 files changed, 6 deletions(-) diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index aba90639..f5e8edd9 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -1,9 +1,6 @@ name: Dependent tests on: - push: - branches: - - main pull_request: branches: - main diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index 51026ba2..a6035204 100644 --- a/.github/workflows/twine-check.yml +++ b/.github/workflows/twine-check.yml @@ -1,9 +1,6 @@ name: Check long description for PyPI on: - push: - branches: - - main pull_request: branches: - main From faf1c819d3ce791ccce48ef3ceac553133c99dba Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 26 May 2023 18:45:42 -0600 Subject: [PATCH 0685/1165] infra: Dedicated code format check workflow (#567) --- .github/workflows/check-format.yml | 28 ++++++++++++++++++++++++++++ .github/workflows/python-package.yml | 6 ------ 2 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/check-format.yml diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml new file mode 100644 index 00000000..db1b9a2e --- /dev/null +++ b/.github/workflows/check-format.yml @@ -0,0 +1,28 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Check code format + +on: + pull_request: + branches: + - main + - feature/** + +jobs: + check-code-format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + - name: Install dependencies + run: | + pip install --upgrade pip + pip install -e .[test] + - name: Run code format checks + run: | + black --check . + flake8 diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 2423e160..b195b44e 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -14,7 +14,6 @@ on: jobs: build: - runs-on: ${{ matrix.os }} strategy: matrix: @@ -31,11 +30,6 @@ jobs: run: | pip install --upgrade pip pip install -e .[test] - - name: Check code format - run: | - # stop the build if there are Python format errors or undefined names - black --check . - flake8 - name: Run unit tests run: | tox -e unit-tests From 84040022f69139b86972f8b905703b6b36aa0c56 Mon Sep 17 00:00:00 2001 From: Phillip Weinberg Date: Tue, 30 May 2023 18:01:53 -0400 Subject: [PATCH 0686/1165] feature: AHS ir valid input for `LocalSimulator` (#565) * feature: AHS ir valid input for `LocalSimulator` * change: Rename 'AnalogHamiltonianProgram' -> 'ProgramAHS' * change: adding test for local simulator with ahs ir as input. * change: reformat with black. * change: manual reformatting `test_local_simulator.py`. * change: reformat files to pass lint * Update src/braket/devices/local_simulator.py * change: rename `ProgramAHS` to `AHSProfram` * change: fixing doc string. --- src/braket/devices/local_simulator.py | 30 +++++++++++++++---- .../braket/devices/test_local_simulator.py | 3 ++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 0398d517..d357f0e9 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -25,6 +25,7 @@ from braket.circuits.serialization import IRType 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.simulator import BraketSimulator from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult @@ -70,15 +71,16 @@ def run( """Runs the given task with the wrapped local simulator. Args: - task_specification (Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]): The - task specification. + task_specification, + (Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, AHSProgram]): + The 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 task specification. Sampling is not supported for shots=0. 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: {}. + IR. If the IR supports inputs, the inputs will be updated with this + value. Default: {}. Returns: LocalQuantumTask: A LocalQuantumTask object containing the results @@ -136,7 +138,9 @@ def _(self, backend_impl: BraketSimulator): @singledispatchmethod def _run_internal( self, - task_specification: Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], + task_specification: Union[ + Circuit, Problem, Program, AnalogHamiltonianSimulation, AHSProgram + ], shots: Optional[int] = None, *args, **kwargs, @@ -215,3 +219,19 @@ def _( ) results = simulator.run(program.to_ir(), shots, *args, **kwargs) return AnalogHamiltonianSimulationQuantumTaskResult.from_object(results) + + @_run_internal.register + def _( + self, + program: AHSProgram, + shots: Optional[int] = None, + *args, + **kwargs, + ): + 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) diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index dd6692aa..a0f7285b 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -404,6 +404,9 @@ def test_run_ahs(): task = sim.run(mock_ahs_program) assert task.result() == AnalogHamiltonianSimulationQuantumTaskResult.from_object(AHS_RESULT) + task = sim.run(mock_ahs_program.to_ir()) + assert task.result() == AnalogHamiltonianSimulationQuantumTaskResult.from_object(AHS_RESULT) + def test_registered_backends(): assert LocalSimulator.registered_backends() == {"dummy", "dummy_oq3", "dummy_jaqcd"} From 36fa25c7eb2e7585e8295debf9fa45d0aee9ab37 Mon Sep 17 00:00:00 2001 From: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com> Date: Tue, 30 May 2023 15:28:43 -0700 Subject: [PATCH 0687/1165] fix: re-enable & update content as per BCS (#546) * fix: re-enable & update content as per BCS * fix return docstring * fix examples and other formatting * fix additional things * fix missing backticks * handling new issues * more fixes * more fixes * merge from main more fixes * minor fix --- src/braket/ahs/driving_field.py | 13 ++-- src/braket/ahs/shifting_field.py | 37 ++++++++-- src/braket/aws/aws_device.py | 21 +++--- src/braket/aws/aws_quantum_task.py | 11 +-- src/braket/aws/aws_quantum_task_batch.py | 13 ++-- src/braket/aws/aws_session.py | 4 +- src/braket/circuits/basis_state.py | 12 ++-- src/braket/circuits/gate.py | 2 +- src/braket/circuits/gates.py | 72 +++++++++---------- src/braket/circuits/instruction.py | 4 +- src/braket/circuits/observable.py | 10 ++- src/braket/circuits/observables.py | 16 +++-- src/braket/devices/local_simulator.py | 5 +- .../error_mitigation/error_mitigation.py | 4 +- ...iltonian_simulation_quantum_task_result.py | 13 ++-- src/braket/timings/time_series.py | 32 ++++----- tox.ini | 2 +- 17 files changed, 152 insertions(+), 119 deletions(-) diff --git a/src/braket/ahs/driving_field.py b/src/braket/ahs/driving_field.py index 44fb73d4..45914063 100644 --- a/src/braket/ahs/driving_field.py +++ b/src/braket/ahs/driving_field.py @@ -94,12 +94,12 @@ def stitch( Args: other (DrivingField): 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. + - "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. Returns: DrivingField: The stitched DrivingField object. @@ -153,6 +153,9 @@ def from_lists( amplitudes (List[float]): The values of the amplitude detunings (List[float]): The values of the detuning phases (List[float]): The values of the phase + + Returns: + DrivingField: DrivingField Hamiltonian. """ if not (len(times) == len(amplitudes) == len(detunings) == len(phases)): raise ValueError( diff --git a/src/braket/ahs/shifting_field.py b/src/braket/ahs/shifting_field.py index f1c9f9eb..bd3f59da 100644 --- a/src/braket/ahs/shifting_field.py +++ b/src/braket/ahs/shifting_field.py @@ -94,15 +94,42 @@ def stitch( 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. + - "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. 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.") diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index fca61ee6..51fd40ce 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -120,7 +120,7 @@ def run( Default is 1000 for QPUs and 0 for simulators. poll_timeout_seconds (float): The polling timeout for `AwsQuantumTask.result()`, in seconds. Default: 5 days. - poll_interval_seconds (float): The polling interval for `AwsQuantumTask.result()`, + poll_interval_seconds (Optional[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[Dict[str, float]]): Inputs to be passed along with the @@ -213,11 +213,9 @@ def run_batch( """Executes a batch of tasks in parallel Args: - task_specifications (Union[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, - PulseSequence, AnalogHamiltonianSimulation], List[Union[ Circuit, - Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence, - AnalogHamiltonianSimulation]]]): Single instance or list of circuits, annealing - problems, pulse sequences, or photonics program to run on device. + 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. s3_destination_folder (Optional[S3DestinationFolder]): The S3 location to save the tasks' results to. Default is `/tasks` if evoked outside a Braket Job, `/jobs//tasks` if evoked inside a Braket Job. @@ -233,9 +231,9 @@ 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[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: {}. + 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: {}. Returns: AwsQuantumTaskBatch: A batch containing all of the tasks run @@ -424,7 +422,6 @@ def properties(self) -> DeviceCapabilities: @property def topology_graph(self) -> DiGraph: """DiGraph: topology of device as a networkx `DiGraph` object. - Returns `None` if the topology is not available for the device. Examples: >>> import networkx as nx @@ -435,6 +432,10 @@ def topology_graph(self) -> DiGraph: >>> nx.draw_kamada_kawai(topology_subgraph, with_labels=True, font_weight="bold") >>> print(device.topology_graph.edges) + + Returns: + DiGraph: topology of QPU as a networkx `DiGraph` object. `None` if the topology + is not available for the device. """ if not self._topology_graph: self._topology_graph = self._construct_topology_graph() diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 56f9b202..7c65c57f 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -115,9 +115,8 @@ def create( device_arn (str): The ARN of the quantum device. - task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, - PulseSequence, AnalogHamiltonianSimulation]): The specification of the task to run - on device. + task_specification (Union[Circuit, Problem, OpenQASMProgram, BlackbirdProgram,PulseSequence, AnalogHamiltonianSimulation]): # noqa + The specification of the task to run on device. s3_destination_folder (AwsSession.S3DestinationFolder): NamedTuple, with bucket for index 0 and key for index 1, that specifies the Amazon S3 bucket and folder @@ -140,7 +139,7 @@ def create( An example would be: `{"state": "washington"}` - inputs (Optional[Dict[str, float]]): Inputs to be passed along with the + inputs (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: {}. @@ -657,7 +656,9 @@ def _( return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) -def _circuit_device_params_from_dict(device_parameters, device_arn, paradigm_parameters): +def _circuit_device_params_from_dict( + device_parameters: dict, device_arn: str, paradigm_parameters: GateModelParameters +) -> GateModelSimulatorDeviceParameters: if "errorMitigation" in device_parameters: error_migitation = device_parameters["errorMitigation"] device_parameters["errorMitigation"] = ( diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 05c5853d..72d2ab93 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -68,11 +68,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[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, - AnalogHamiltonianSimulation], List[Union[Circuit, - Problem, OpenQasmProgram, BlackbirdProgram, - AnalogHamiltonianSimulation]]): Single instance or list of circuits, annealing + 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 task to run on device. s3_destination_folder (AwsSession.S3DestinationFolder): NamedTuple, with bucket @@ -90,9 +87,9 @@ def __init__( in seconds. Default: 5 days. poll_interval_seconds (float): The polling interval for results in seconds. Default: 1 second. - inputs ([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: {}. + inputs (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: {}. """ self._tasks = AwsQuantumTaskBatch._execute( aws_session, diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 406ccbf1..7ce1de27 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -212,7 +212,7 @@ def create_quantum_task(self, **boto3_kwargs) -> str: Args: ``**boto3_kwargs``: Keyword arguments for the Amazon Braket `CreateQuantumTask` - operation. + operation. Returns: str: The ARN of the quantum task. @@ -690,7 +690,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 diff --git a/src/braket/circuits/basis_state.py b/src/braket/circuits/basis_state.py index 8c17802c..0d32712b 100644 --- a/src/braket/circuits/basis_state.py +++ b/src/braket/circuits/basis_state.py @@ -9,22 +9,22 @@ def __init__(self, state: "BasisStateInput", size: Optional[int] = None): self.state = _as_tuple(state, size) @property - def size(self): + def size(self) -> int: return len(self.state) @property - def as_tuple(self): + def as_tuple(self) -> tuple: return self.state @property - def as_int(self): + def as_int(self) -> int: return 2 ** np.arange(self.size)[::-1] @ self.state @property - def as_string(self): + def as_string(self) -> str: return "".join(map(str, self.state)) - def __len__(self): + def __len__(self) -> int: return len(self.state) def __iter__(self): @@ -35,7 +35,7 @@ def __iter__(self): @singledispatch -def _as_tuple(state: BasisStateInput, size: int): +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( diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index cc918ae6..eaf9675f 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -52,7 +52,7 @@ def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) @property - def _qasm_name(self): + def _qasm_name(self) -> NotImplementedError: raise NotImplementedError() def adjoint(self) -> List[Gate]: diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 331bc2ee..02688b71 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -65,7 +65,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["H"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "h" def adjoint(self) -> List[Gate]: @@ -130,7 +130,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["I"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "i" def adjoint(self) -> List[Gate]: @@ -195,7 +195,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["X"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "x" def adjoint(self) -> List[Gate]: @@ -260,7 +260,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Y"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "y" def adjoint(self) -> List[Gate]: @@ -325,7 +325,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Z"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "z" def adjoint(self) -> List[Gate]: @@ -390,7 +390,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["S"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "s" def adjoint(self) -> List[Gate]: @@ -455,7 +455,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Si"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "si" def adjoint(self) -> List[Gate]: @@ -520,7 +520,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["T"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "t" def adjoint(self) -> List[Gate]: @@ -585,7 +585,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Ti"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "ti" def adjoint(self) -> List[Gate]: @@ -650,7 +650,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["V"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "v" def adjoint(self) -> List[Gate]: @@ -715,7 +715,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Vi"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "vi" def adjoint(self) -> List[Gate]: @@ -791,7 +791,7 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "rx" def _to_jaqcd(self, target: QubitSet, **kwargs) -> Any: @@ -871,7 +871,7 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "ry" def _to_jaqcd(self, target: QubitSet) -> Any: @@ -951,7 +951,7 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "rz" def _to_jaqcd(self, target: QubitSet) -> Any: @@ -1027,7 +1027,7 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "phaseshift" def _to_jaqcd(self, target: QubitSet) -> Any: @@ -1100,7 +1100,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "X"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "cnot" def adjoint(self) -> List[Gate]: @@ -1160,7 +1160,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["SWAP", "SWAP"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "swap" def adjoint(self) -> List[Gate]: @@ -1235,7 +1235,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["ISWAP", "ISWAP"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "iswap" def adjoint(self) -> List[Gate]: @@ -1321,7 +1321,7 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "pswap" def _to_jaqcd(self, target: QubitSet) -> Any: @@ -1411,7 +1411,7 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "xy" def _to_jaqcd(self, target: QubitSet) -> Any: @@ -1502,7 +1502,7 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "cphaseshift" def _to_jaqcd(self, target: QubitSet) -> Any: @@ -1571,7 +1571,7 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "cphaseshift00" def _to_jaqcd(self, target: QubitSet) -> Any: @@ -1640,7 +1640,7 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "cphaseshift01" def _to_jaqcd(self, target: QubitSet) -> Any: @@ -1709,7 +1709,7 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "cphaseshift10" def _to_jaqcd(self, target: QubitSet) -> Any: @@ -1770,7 +1770,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "V"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "cv" def adjoint(self) -> List[Gate]: @@ -1830,7 +1830,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "Y"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "cy" def adjoint(self) -> List[Gate]: @@ -1890,7 +1890,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "Z"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "cz" def adjoint(self) -> List[Gate]: @@ -1942,7 +1942,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["ECR", "ECR"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "ecr" def adjoint(self) -> List[Gate]: @@ -2029,7 +2029,7 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "xx" def _to_jaqcd(self, target: QubitSet) -> Any: @@ -2125,7 +2125,7 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "yy" def _to_jaqcd(self, target: QubitSet) -> Any: @@ -2221,7 +2221,7 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "zz" def _to_jaqcd(self, target: QubitSet) -> Any: @@ -2301,7 +2301,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "C", "X"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "ccnot" def adjoint(self) -> List[Gate]: @@ -2384,7 +2384,7 @@ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "SWAP", "SWAP"]) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "cswap" def adjoint(self) -> List[Gate]: @@ -2465,7 +2465,7 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "gpi" def to_matrix(self) -> np.ndarray: @@ -2544,7 +2544,7 @@ def __init__(self, angle: Union[FreeParameterExpression, float]): ) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "gpi2" def to_matrix(self) -> np.ndarray: @@ -2629,7 +2629,7 @@ def __init__( ) @property - def _qasm_name(self): + def _qasm_name(self) -> str: return "ms" def to_matrix(self) -> np.ndarray: diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index 0b1cca60..cde92183 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -41,14 +41,14 @@ def __init__( control: Optional[QubitSetInput] = None, control_state: Optional[BasisStateInput] = None, power: float = 1, - ): + ) -> Instruction: """ InstructionOperator includes objects of type `Gate` and `Noise` only. Args: operator (InstructionOperator): Operator for the instruction. target (QubitSetInput): Target qubits that the operator is applied to. Default is None. - control (QubitSetInput): Target qubits that the operator is controlled on. + control (Optional[QubitSetInput]): Target qubits that the operator is controlled on. Default is 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 diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index db50f823..c63304c9 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -41,7 +41,7 @@ def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) self._coef = 1 - def _unscaled(self): + def _unscaled(self) -> Observable: return Observable(qubit_count=self.qubit_count, ascii_symbols=self.ascii_symbols) def to_ir( @@ -105,7 +105,11 @@ def _to_openqasm( raise NotImplementedError("to_openqasm has not been implemented yet.") @property - def coefficient(self): + def coefficient(self) -> int: + """ + Returns: + int: coefficient value of the observable. + """ return self._coef @property @@ -195,7 +199,7 @@ def __init__(self, ascii_symbols: Sequence[str]): super().__init__(qubit_count=1, ascii_symbols=ascii_symbols) self._eigenvalues = (1.0, -1.0) # immutable - def _unscaled(self): + def _unscaled(self) -> StandardObservable: return StandardObservable(ascii_symbols=self.ascii_symbols) @property diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 0cd37ed6..b2717023 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -43,7 +43,7 @@ def __init__(self): """ super().__init__(ascii_symbols=["H"]) - def _unscaled(self): + def _unscaled(self) -> StandardObservable: return H() def _to_jaqcd(self) -> List[str]: @@ -84,7 +84,7 @@ def __init__(self): """ super().__init__(qubit_count=1, ascii_symbols=["I"]) - def _unscaled(self): + def _unscaled(self) -> Observable: return I() def _to_jaqcd(self) -> List[str]: @@ -134,7 +134,7 @@ def __init__(self): """ super().__init__(ascii_symbols=["X"]) - def _unscaled(self): + def _unscaled(self) -> StandardObservable: return X() def _to_jaqcd(self) -> List[str]: @@ -173,7 +173,7 @@ def __init__(self): """ super().__init__(ascii_symbols=["Y"]) - def _unscaled(self): + def _unscaled(self) -> StandardObservable: return Y() def _to_jaqcd(self) -> List[str]: @@ -212,7 +212,7 @@ def __init__(self): """ super().__init__(ascii_symbols=["Z"]) - def _unscaled(self): + def _unscaled(self) -> StandardObservable: return Z() def _to_jaqcd(self) -> List[str]: @@ -302,7 +302,7 @@ def ascii_symbols(self) -> Tuple[str, ...]: for _ in range(self.qubit_count) ) - def _unscaled(self): + def _unscaled(self) -> Observable: copied = TensorProduct(observables=self.factors) copied._coef = 1 return copied @@ -435,6 +435,8 @@ def __init__(self, observables: List[Observable], display_name: str = "Hamiltoni """ Args: observables (List[Observable]): List of observables for Sum + display_name (str): Name to use for an instance of this Sum + observable for circuit diagrams. Defaults to `Hamiltonian`. Examples: >>> t1 = -3 * Observable.Y() + 2 * Observable.X() @@ -558,7 +560,7 @@ def __init__(self, matrix: np.ndarray, display_name: str = "Hermitian"): super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) - def _unscaled(self): + def _unscaled(self) -> Observable: return Hermitian(matrix=self._matrix, display_name=self.ascii_symbols[0]) def _to_jaqcd(self) -> List[List[List[List[float]]]]: diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index d357f0e9..34379227 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -71,9 +71,8 @@ def run( """Runs the given task with the wrapped local simulator. Args: - task_specification, - (Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, AHSProgram]): - The task specification. + task_specification (Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]): + The 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 task specification. diff --git a/src/braket/error_mitigation/error_mitigation.py b/src/braket/error_mitigation/error_mitigation.py index caa51e31..c03fbf6d 100644 --- a/src/braket/error_mitigation/error_mitigation.py +++ b/src/braket/error_mitigation/error_mitigation.py @@ -20,7 +20,7 @@ class ErrorMitigation: def serialize(self) -> List[error_mitigation.ErrorMitigationScheme]: """ Returns: - List[error_mitigation.ErrorMitigationScheme]: A list of service-readable error - mitigation scheme descriptions + List[ErrorMitigationScheme]: A list of service-readable error + mitigation scheme descriptions. """ raise NotImplementedError("serialize is not implemented.") 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 86bbb6f0..37346fbc 100644 --- a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py +++ b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py @@ -102,15 +102,16 @@ def _get_measurements(cls, result: AnalogHamiltonianSimulationTaskResult) -> Lis def get_counts(self) -> Dict[str, int]: """Aggregate state counts from AHS shot results. + Notes: + We use the following convention to denote the state of an atom (site). + e: empty site + r: Rydberg state atom + g: ground state atom + 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. - - Notes: We use the following convention to denote the state of an atom (site): - e: empty site - r: Rydberg state atom - g: ground state atom """ state_counts = Counter() @@ -132,7 +133,7 @@ def get_avg_density(self) -> np.ndarray: """Get the average Rydberg state densities from the result Returns: - ndarray (float): The average densities from the result + ndarray: The average densities from the result """ counts = self.get_counts() diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py index a3acc42d..cfc87141 100644 --- a/src/braket/timings/time_series.py +++ b/src/braket/timings/time_series.py @@ -121,7 +121,8 @@ def constant_like(times: Union[List[float], TimeSeries], constant: float = 0.0) """Obtain a constant time series given the list of time points and the constant values Args: - times (List[float]): list of time points + times (Union[List[float], TimeSeries]): list of time points + constant (float): constant value Returns: TimeSeries: A constant time series @@ -140,15 +141,14 @@ def concatenate(self, other: TimeSeries) -> TimeSeries: Args: other (TimeSeries): The second time series to be concatenated + 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. Returns: TimeSeries: The concatenated time series. - 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. - Example: :: time_series_1 = TimeSeries.from_lists(times=[0, 0.1], values=[1, 2]) @@ -186,14 +186,13 @@ def stitch( Args: other (TimeSeries): The second time series to be stitched with. - boundary (StitchBoundaryCondition): `{"mean", "left", "right"}`. Boundary point handler. - Possible options are + boundary (StitchBoundaryCondition): {"mean", "left", "right"}. Boundary point handler. - - "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. + 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. Returns: TimeSeries: The stitched time series. @@ -224,7 +223,6 @@ def stitch( Result: stitch_ts.times() = [0, 0.1, 0.3] stitch_ts.values() = [1, 4, 5] - """ if len(self.times()) == 0: @@ -278,12 +276,12 @@ def discretize(self, time_resolution: Decimal, value_resolution: Decimal) -> Tim return discretized_ts @staticmethod - def periodic_signal(times: List[float], values: List[float], num_repeat: int = 1): + def periodic_signal(times: List[float], values: List[float], num_repeat: int = 1) -> TimeSeries: """Create a periodic time series by repeating the same block multiple times. Args: - times (float): List of time points in a single block - values (float): Values for the time series in a single block + 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 Returns: diff --git a/tox.ini b/tox.ini index 43d8acb4..ba688287 100644 --- a/tox.ini +++ b/tox.ini @@ -44,7 +44,7 @@ deps = git+https://github.com/aws/amazon-braket-build-tools.git commands = flake8 {posargs} - ; flake8 --enable-extensions=BCS src + flake8 --enable-extensions=BCS src [testenv:isort] basepython = python3 From 46c54b730e35cad0f310723a23f6e53c05224774 Mon Sep 17 00:00:00 2001 From: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com> Date: Tue, 30 May 2023 15:40:58 -0700 Subject: [PATCH 0688/1165] infra: add bcs checks to workflow (#569) --- .github/workflows/check-format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index db1b9a2e..51a56630 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -25,4 +25,4 @@ jobs: - name: Run code format checks run: | black --check . - flake8 + flake8 --enable-extensions=BCS src From e40c439f98014c93634e3f884c33c9a986decd98 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 30 May 2023 23:46:43 +0000 Subject: [PATCH 0689/1165] prepare release v1.41.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 61fd1ec3..dcfc340b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.41.0 (2023-05-30) + +### Features + + * AHS ir valid input for `LocalSimulator` + +### Bug Fixes and Other Changes + + * re-enable & update content as per BCS + ## v1.40.0 (2023-05-25) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 6f021fc0..3cab2121 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.40.1.dev0" +__version__ = "1.41.0" From a93dba42a3dfd04c55afe3815fa76b44b20b9cc7 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 30 May 2023 23:46:43 +0000 Subject: [PATCH 0690/1165] update development version to v1.41.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 3cab2121..b17191a3 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.41.0" +__version__ = "1.41.1.dev0" From a0dec3209359f0c7b4fb8d05d62b4391678de1e5 Mon Sep 17 00:00:00 2001 From: Davide Gessa Date: Thu, 1 Jun 2023 18:37:47 +0200 Subject: [PATCH 0691/1165] feature: support for the run_batch() method on LocalSimulator (#566) * implementation * fix run_batch with none inputs * fix pickle errors * add integration test * fix: type signatures and docstring fix: local simulator run_batch input validation and default parameters fix: local simulator linting * fix: test for local_simulator * fix: test coverage for local simulator run_batch * fix: local simulator test linting * fix: fix flake8 errors fix: fix memory leak on LocalSimulator run_batch change: improve test coverage for run_batch * fix: add test for local run_batch coverage --- src/braket/aws/aws_quantum_task_batch.py | 3 +- src/braket/devices/device.py | 35 ++++++- src/braket/devices/local_simulator.py | 94 ++++++++++++++++++- src/braket/tasks/__init__.py | 1 + src/braket/tasks/local_quantum_task_batch.py | 49 ++++++++++ src/braket/tasks/quantum_task_batch.py | 37 ++++++++ .../braket/devices/test_local_simulator.py | 81 ++++++++++++++++ .../tasks/test_local_quantum_task_batch.py | 40 ++++++++ 8 files changed, 336 insertions(+), 4 deletions(-) create mode 100644 src/braket/tasks/local_quantum_task_batch.py create mode 100644 src/braket/tasks/quantum_task_batch.py create mode 100644 test/unit_tests/braket/tasks/test_local_quantum_task_batch.py diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 72d2ab93..b0cd3a38 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -25,9 +25,10 @@ from braket.circuits import Circuit from braket.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQasmProgram +from braket.tasks.quantum_task_batch import QuantumTaskBatch -class AwsQuantumTaskBatch: +class AwsQuantumTaskBatch(QuantumTaskBatch): """Executes a batch of quantum tasks in parallel. Using this class can yield vast speedups over executing tasks sequentially, diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 9f818d56..7223ff6b 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -12,11 +12,12 @@ # language governing permissions and limitations under the License. from abc import ABC, abstractmethod -from typing import Dict, Optional, Union +from typing import Dict, List, Optional, Union from braket.annealing.problem import Problem from braket.circuits import Circuit from braket.tasks.quantum_task import QuantumTask +from braket.tasks.quantum_task_batch import QuantumTaskBatch class Device(ABC): @@ -44,7 +45,7 @@ def run( or an annealing problem. Args: - task_specification (Union[Circuit, Problem]): Specification of a task + task_specification (Union[Circuit, Problem]): Specification of a task to run on device. shots (Optional[int]): The number of times to run the task on the device. Default is `None`. @@ -56,6 +57,36 @@ def run( QuantumTask: The QuantumTask tracking task execution on this device """ + @abstractmethod + def run_batch( + self, + task_specifications: Union[ + Union[Circuit, Problem], + List[Union[Circuit, Problem]], + ], + shots: Optional[int], + max_parallel: Optional[int], + inputs: Optional[Union[Dict[str, float], List[Dict[str, float]]]], + *args, + **kwargs + ) -> QuantumTaskBatch: + """Executes a batch of tasks in parallel + + Args: + task_specifications (Union[Union[Circuit, Problem], List[Union[Circuit, Problem]]]): + 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 tasks to run in parallel. + Batch creation will fail if this value is greater than the maximum allowed + concurrent tasks on the device. + 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. + + Returns: + QuantumTaskBatch: A batch containing all of the tasks run + """ + @property def name(self) -> str: """Return the name of this Device. diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 34379227..6d254c81 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -14,7 +14,10 @@ from __future__ import annotations from functools import singledispatchmethod -from typing import Dict, Optional, Set, Union +from itertools import repeat +from multiprocessing import Pool +from os import cpu_count +from typing import Dict, List, Optional, Set, Union import pkg_resources @@ -33,6 +36,7 @@ AnalogHamiltonianSimulationQuantumTaskResult, ) 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") @@ -97,6 +101,83 @@ def run( result = self._run_internal(task_specification, shots, inputs=inputs, *args, **kwargs) return LocalQuantumTask(result) + def run_batch( + self, + task_specifications: Union[ + Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], + List[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]], + ], + shots: Optional[int] = 0, + max_parallel: Optional[int] = None, + inputs: Optional[Union[Dict[str, float], List[Dict[str, float]]]] = None, + *args, + **kwargs, + ) -> LocalQuantumTaskBatch: + """Executes a batch of tasks in parallel + + Args: + task_specifications (Union[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], List[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]]]): # noqa + Single instance or list of task specification. + shots (Optional[int]): The number of times to run the task. + Default: 0. + max_parallel (Optional[int]): The maximum number of tasks to run in parallel. Default + is the number of CPU. + 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: {}. + + Returns: + LocalQuantumTaskBatch: A batch containing all of the tasks run + + See Also: + `braket.tasks.local_quantum_task_batch.LocalQuantumTaskBatch` + """ + inputs = inputs or {} + + if not max_parallel: + max_parallel = cpu_count() + + single_task = isinstance( + task_specifications, + (Circuit, Program, Problem, AnalogHamiltonianSimulation), + ) + + 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 single_task: + task_specifications = repeat(task_specifications) + + if single_input: + inputs = repeat(inputs) + + tasks_and_inputs = zip(task_specifications, inputs) + + if single_task and single_input: + tasks_and_inputs = [next(tasks_and_inputs)] + else: + tasks_and_inputs = list(tasks_and_inputs) + + 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: + raise ValueError( + f"Cannot execute circuit with unbound parameters: " + f"{unbounded_parameters}" + ) + + 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) + @property def properties(self) -> DeviceCapabilities: """DeviceCapabilities: Return the device properties @@ -116,6 +197,17 @@ def registered_backends() -> Set[str]: """ return set(_simulator_devices.keys()) + def _run_internal_wrap( + self, + task_specification: Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], + shots: Optional[int] = None, + inputs: Optional[Dict[str, float]] = None, + *args, + **kwargs, + ) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: + """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: raise TypeError("Simulator must either be a string or a BraketSimulator instance") diff --git a/src/braket/tasks/__init__.py b/src/braket/tasks/__init__.py index d81df6ce..bb6cc6e7 100644 --- a/src/braket/tasks/__init__.py +++ b/src/braket/tasks/__init__.py @@ -22,6 +22,7 @@ PhotonicModelQuantumTaskResult, ) from braket.tasks.quantum_task import QuantumTask # noqa: F401 +from braket.tasks.quantum_task_batch import QuantumTaskBatch # noqa: F401 # Apply nest_asyncio if currently running within Jupyter. This ensures anything that uses # asyncio will run in Jupyter without any issues. diff --git a/src/braket/tasks/local_quantum_task_batch.py b/src/braket/tasks/local_quantum_task_batch.py new file mode 100644 index 00000000..6e36246e --- /dev/null +++ b/src/braket/tasks/local_quantum_task_batch.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 typing import List, Union + +from braket.tasks import ( + AnnealingQuantumTaskResult, + GateModelQuantumTaskResult, + PhotonicModelQuantumTaskResult, + QuantumTaskBatch, +) + + +class LocalQuantumTaskBatch(QuantumTaskBatch): + """Executes a batch of quantum tasks in parallel. + + Since this class is instantiated with the results, cancel() and run_async() are unsupported. + """ + + def __init__( + self, + results: List[ + Union[ + GateModelQuantumTaskResult, + AnnealingQuantumTaskResult, + PhotonicModelQuantumTaskResult, + ] + ], + ): + self._results = results + + def results( + self, + ) -> List[ + Union[ + GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult + ] + ]: + return self._results diff --git a/src/braket/tasks/quantum_task_batch.py b/src/braket/tasks/quantum_task_batch.py new file mode 100644 index 00000000..721afd7c --- /dev/null +++ b/src/braket/tasks/quantum_task_batch.py @@ -0,0 +1,37 @@ +# 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 abc import ABC, abstractmethod +from typing import List, Union + +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 QuantumTaskBatch(ABC): + """An abstraction over a quantum task batch on a quantum device.""" + + @abstractmethod + def results( + self, + ) -> List[ + Union[ + GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult + ] + ]: + """Get the quantum task results. + Returns: + List[Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]]:: # noqa + Get the quantum task results. + """ diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index a0f7285b..08f2a19c 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -311,6 +311,87 @@ def test_run_gate_model(): assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) +def test_batch_circuit(): + dummy = DummyProgramSimulator() + theta = FreeParameter("theta") + task = Circuit().rx(angle=theta, target=0) + device = LocalSimulator(dummy) + num_tasks = 10 + circuits = [task for _ in range(num_tasks)] + inputs = [{"theta": i} for i in range(num_tasks)] + batch = device.run_batch(circuits, inputs=inputs, shots=10) + assert len(batch.results()) == num_tasks + for x in batch.results(): + assert x == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) + + +def test_batch_with_max_parallel(): + dummy = DummyProgramSimulator() + task = Circuit().h(0).cnot(0, 1) + device = LocalSimulator(dummy) + num_tasks = 10 + circuits = [task for _ in range(num_tasks)] + batch = device.run_batch(circuits, shots=10, max_parallel=2) + assert len(batch.results()) == num_tasks + for x in batch.results(): + assert x == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) + + +def test_batch_with_annealing_problems(): + dummy = DummyAnnealingSimulator() + problem = Problem(ProblemType.ISING) + device = LocalSimulator(dummy) + num_tasks = 10 + problems = [problem for _ in range(num_tasks)] + batch = device.run_batch(problems, shots=10) + assert len(batch.results()) == num_tasks + for x in batch.results(): + assert x == AnnealingQuantumTaskResult.from_object(ANNEALING_RESULT) + + +def test_batch_circuit_without_inputs(): + dummy = DummyProgramSimulator() + bell = Circuit().h(0).cnot(0, 1) + device = LocalSimulator(dummy) + num_tasks = 10 + circuits = [bell for _ in range(num_tasks)] + batch = device.run_batch(circuits, shots=10) + assert len(batch.results()) == num_tasks + for x in batch.results(): + assert x == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) + + +def test_batch_circuit_with_unbound_parameters(): + dummy = DummyProgramSimulator() + device = LocalSimulator(dummy) + theta = FreeParameter("theta") + task = Circuit().rx(angle=theta, target=0) + inputs = {"beta": 0.2} + cannot_execute_with_unbound = "Cannot execute circuit with unbound parameters: {'theta'}" + with pytest.raises(ValueError, match=cannot_execute_with_unbound): + device.run_batch(task, inputs=inputs, shots=10) + + +def test_batch_circuit_with_single_task(): + dummy = DummyProgramSimulator() + bell = Circuit().h(0).cnot(0, 1) + device = LocalSimulator(dummy) + batch = device.run_batch(bell, shots=10) + assert len(batch.results()) == 1 + assert batch.results()[0] == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) + + +def test_batch_circuit_with_task_and_input_mismatch(): + dummy = DummyProgramSimulator() + bell = Circuit().h(0).cnot(0, 1) + device = LocalSimulator(dummy) + num_tasks = 10 + circuits = [bell for _ in range(num_tasks)] + inputs = [{} for _ in range(num_tasks - 1)] + with pytest.raises(ValueError): + device.run_batch(circuits, inputs=inputs, shots=10) + + def test_run_gate_model_inputs(): dummy = DummyProgramSimulator() dummy.run = Mock(return_value=GATE_MODEL_RESULT) 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 new file mode 100644 index 00000000..3c7c523b --- /dev/null +++ b/test/unit_tests/braket/tasks/test_local_quantum_task_batch.py @@ -0,0 +1,40 @@ +# 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 uuid + +import numpy as np + +from braket.task_result import TaskMetadata +from braket.tasks import GateModelQuantumTaskResult +from braket.tasks.local_quantum_task_batch import LocalQuantumTaskBatch + +RESULTS = [ + GateModelQuantumTaskResult( + 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], + result_types=None, + values=None, + ) +] + +TASK_BATCH = LocalQuantumTaskBatch(RESULTS) + + +def test_results(): + assert TASK_BATCH.results() == RESULTS + assert len(TASK_BATCH.results()) == 1 From ae348694a5ec3a1a20f1093ae20ecb3c142fc1d2 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 5 Jun 2023 16:14:38 +0000 Subject: [PATCH 0692/1165] prepare release v1.42.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 dcfc340b..d0a091e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.42.0 (2023-06-05) + +### Features + + * support for the run_batch() method on LocalSimulator + ## v1.41.0 (2023-05-30) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index b17191a3..7d78120b 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.41.1.dev0" +__version__ = "1.42.0" From 2559c341495920c9e3090a384757f5dde965c328 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 5 Jun 2023 16:14:38 +0000 Subject: [PATCH 0693/1165] update development version to v1.42.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 7d78120b..69331a7d 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.42.0" +__version__ = "1.42.1.dev0" From 59013e086c81df6998929267cda2e6ab65a7a8bb Mon Sep 17 00:00:00 2001 From: Katharine Hyatt <67932820+kshyatt-aws@users.noreply.github.com> Date: Tue, 6 Jun 2023 15:32:40 -0400 Subject: [PATCH 0694/1165] change: Add more information to docstring about job name requirements (#571) --- src/braket/jobs/quantum_job_creation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py index c26d9e9e..b6f1a41e 100644 --- a/src/braket/jobs/quantum_job_creation.py +++ b/src/braket/jobs/quantum_job_creation.py @@ -74,7 +74,8 @@ def prepare_quantum_job( `image_uris.retrieve_image()` function may be used for retrieving the ECR image URIs for the containers supported by Braket. Default = ``. - job_name (str): A str that specifies the name with which the job is created. + job_name (str): A str that specifies the name with which the job is created. The job + name must be between 0 and 50 characters long and cannot contain underscores. Default: f'{image_uri_type}-{timestamp}'. code_location (str): The S3 prefix URI where custom code will be uploaded. From 9a8b33c0a9773662332a744f05115821d61e6465 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 7 Jun 2023 16:14:21 +0000 Subject: [PATCH 0695/1165] prepare release v1.42.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 d0a091e2..3d375dfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.42.1 (2023-06-07) + +### Bug Fixes and Other Changes + + * Add more information to docstring about job name requirements + ## v1.42.0 (2023-06-05) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 69331a7d..2d7d71e2 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.42.1.dev0" +__version__ = "1.42.1" From 6a70cccf24cc17e1e7d59a04c8cf4e5fdba68b7d Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 7 Jun 2023 16:14:21 +0000 Subject: [PATCH 0696/1165] update development version to v1.42.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 2d7d71e2..79e25b18 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.42.1" +__version__ = "1.42.2.dev0" From ea50ed5d73b7a7a34809ff0aa944b297d2f37174 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Tue, 20 Jun 2023 09:04:06 -0400 Subject: [PATCH 0697/1165] fix: pulse plotting with barriers that have no argument (#577) * fix: increase pulse plotting test accuracy * fix: pulse plot with barriers without argument --- src/braket/pulse/ast/approximation_parser.py | 3 + .../pulse/ast/test_approximation_parser.py | 160 +++++++++--------- 2 files changed, 81 insertions(+), 82 deletions(-) diff --git a/src/braket/pulse/ast/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py index 51d5851a..68a36b45 100644 --- a/src/braket/pulse/ast/approximation_parser.py +++ b/src/braket/pulse/ast/approximation_parser.py @@ -155,6 +155,9 @@ def visit_QuantumBarrier(self, node: ast.QuantumBarrier, context: _ParseState) - context (_ParseState): The parse state context. """ frames = self._get_frame_parameters(node.qubits, context) + if len(frames) == 0: + # 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]) # All frames are delayed till the first multiple of the LCM([port.dts]) 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 1521875c..2f83730f 100644 --- a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py +++ b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py @@ -41,13 +41,11 @@ def test_bare_pulsequence(): def test_delay(port): frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) pulse_seq = PulseSequence().delay(frame, 3e-9) - # 3 datapoints for delay expected_amplitudes = {"frame1": TimeSeries()} expected_frequencies = {"frame1": TimeSeries()} expected_phases = {"frame1": TimeSeries()} - # 2 datapoints for first delay expected_amplitudes["frame1"].put(0, 0).put(2e-9, 0) expected_frequencies["frame1"].put(0, 1e8).put(2e-9, 1e8) expected_phases["frame1"].put(0, 0).put(2e-9, 0) @@ -60,13 +58,11 @@ def test_delay(port): def test_predefined_frame(port): frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=True) pulse_seq = PulseSequence().delay(frame, 3e-9) - # 3 datapoints for delay expected_amplitudes = {"frame1": TimeSeries()} expected_frequencies = {"frame1": TimeSeries()} expected_phases = {"frame1": TimeSeries()} - # 2 datapoints for first delay expected_amplitudes["frame1"].put(0, 0).put(2e-9, 0) expected_frequencies["frame1"].put(0, 1e8).put(2e-9, 1e8) expected_phases["frame1"].put(0, 0).put(2e-9, 0) @@ -93,15 +89,10 @@ def test_set_shift_phase(port): expected_frequencies = {"frame1": TimeSeries()} expected_phases = {"frame1": TimeSeries()} - # 2 datapoints for first delay - # Parser does not like delay 2ns expected_amplitudes["frame1"].put(0, 0).put(1e-9, 0) expected_frequencies["frame1"].put(0, 1e8).put(1e-9, 1e8) expected_phases["frame1"].put(0, 1).put(1e-9, 1) - # set_shift_phase should be instantaneous (result on current or next datapoint?) - # 5 dt for second delay - # shift_phase adds 2 to the phase of the last point -> 3 expected_amplitudes["frame1"].put(2e-9, 0).put(6e-9, 0) expected_frequencies["frame1"].put(2e-9, 1e8).put(6e-9, 1e8) expected_phases["frame1"].put(2e-9, 3).put(6e-9, 3) @@ -125,13 +116,11 @@ def test_set_shift_phase_beyond_2_pi(port): expected_frequencies = {"frame1": TimeSeries()} expected_phases = {"frame1": TimeSeries()} - # 2 datapoints for first delay # 5pi/2 is reduced to pi/2 expected_amplitudes["frame1"].put(0, 0).put(1e-9, 0) expected_frequencies["frame1"].put(0, 1e8).put(1e-9, 1e8) expected_phases["frame1"].put(0, np.pi / 2).put(1e-9, np.pi / 2) - # set_shift_phase should be instantaneous (result on current or next datapoint?) # shift_phase adds -pi to the phase of the last point -> 3pi/2 expected_amplitudes["frame1"].put(2e-9, 0).put(6e-9, 0) expected_frequencies["frame1"].put(2e-9, 1e8).put(6e-9, 1e8) @@ -151,22 +140,15 @@ def test_set_shift_frequency(port): .shift_frequency(frame, -0.1e8) .delay(frame, 10e-9) ) - # 2 datapoints for delay - # set_shift_phase should be instantaneous (result on current or next datapoint?) - # shift_phase adds 2 to the phase of the last point -> 3 expected_amplitudes = {"frame1": TimeSeries()} expected_frequencies = {"frame1": TimeSeries()} expected_phases = {"frame1": TimeSeries()} - # 2 datapoints for first delay expected_amplitudes["frame1"].put(0, 0).put(19e-9, 0) expected_frequencies["frame1"].put(0, 2e8).put(19e-9, 2e8) expected_phases["frame1"].put(0, 0).put(19e-9, 0) - # set_shift_phase should be instantaneous (result on current or next datapoint?) - # 5 dt for second delay - # shift_phase adds 2 to the phase of the last point -> 3 expected_amplitudes["frame1"].put(20e-9, 0).put(29e-9, 0) expected_frequencies["frame1"].put(20e-9, 1.9e8).put(29e-9, 1.9e8) expected_phases["frame1"].put(20e-9, 0).put(29e-9, 0) @@ -180,7 +162,6 @@ def test_play_arbitrary_waveforms(port): frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) my_arb_wf = ArbitraryWaveform([0.4 + 0.1j, -0.8 + 0.1j, 1 + 0.2j]) pulse_seq = PulseSequence().play(frame, my_arb_wf).capture_v0(frame) - # 3 datapoints for arb wf play expected_amplitudes = {"frame1": TimeSeries()} expected_frequencies = {"frame1": TimeSeries()} @@ -201,7 +182,6 @@ def test_play_literal(port): frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) pulse_seq = PulseSequence() pulse_seq._program.play(frame=frame, waveform=[0.4 + 0.1j, -0.8 + 0.1j, 1 + 0.2j]) - # 3 datapoints for arb wf play expected_amplitudes = {"frame1": TimeSeries()} expected_frequencies = {"frame1": TimeSeries()} @@ -290,7 +270,6 @@ def test_play_gaussian_waveforms(port): length=1e-8, sigma=1.69e-9, amplitude=1.0, zero_at_edges=False ) pulse_seq = PulseSequence().play(frame1, gaussian_wf_ZaE_False) - # X datapoints... times = np.arange(0, 1e-8, port.dt) values = np.array( @@ -329,21 +308,20 @@ def test_play_gaussian_waveforms(port): length=1e-8, sigma=1.69e-9, amplitude=1.0, zero_at_edges=True ) pulse_seq.play(frame1, gaussian_wf_ZaE_True) - # X datapoints... times = np.arange(0, 1e-8, port.dt) values = np.array( [ - 0.0 + 0.0j, - complex(0.04879311), - complex(0.1967938), - complex(0.49004931), - complex(0.83735931), + complex(0.0), + complex(0.04879310874736347), + complex(0.1967938049565093), + complex(0.49004931075238933), + complex(0.8373593063365198), complex(1.0), - complex(0.83735931), - complex(0.49004931), - complex(0.1967938), - complex(0.04879311), + complex(0.8373593063365196), + complex(0.49004931075238906), + complex(0.1967938049565092), + complex(0.048793108747363416), ], dtype=np.complex128, ) @@ -412,16 +390,16 @@ def test_play_drag_gaussian_waveforms(port): times = np.arange(0, 1e-8, port.dt) values = np.array( [ - 0.0 + 0.0j, - 0.04879311 + 0.06833529j, - 0.1967938 + 0.20670894j, - 0.49004931 + 0.34315977j, - 0.83735931 + 0.29318277j, - 1.0 + 0.0j, - 0.83735931 - 0.29318277j, - 0.49004931 - 0.34315977j, - 0.1967938 - 0.20670894j, - 0.04879311 - 0.06833529j, + complex(0.0, 0.0), + complex(0.04879310874736347, 0.0683352946288484), + complex(0.1967938049565093, 0.20670894396888342), + complex(0.49004931075238933, 0.34315977084303023), + complex(0.8373593063365198, 0.2931827689284408), + complex(1.0, 0), + complex(0.8373593063365196, -0.293182768928441), + complex(0.49004931075238906, -0.34315977084303023), + complex(0.1967938049565092, -0.20670894396888334), + complex(0.048793108747363416, -0.06833529462884834), ], dtype=np.complex128, ) @@ -490,29 +468,67 @@ def test_barrier_same_dt(port): expected_frequencies["frame2"].put(shift_time_frame2 + t, 1e8) expected_phases["frame2"].put(shift_time_frame2 + t, 0) - # # Pad frame2 - # shift_time_frame2 = shift_time_frame2 + pulse_length - # last_time_frame1 = expected_amplitudes["frame1"].times()[-1] + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) + + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + + +def test_barrier_no_args(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) + pulse_seq = ( + PulseSequence() + .play(frame1, ConstantWaveform(12e-9, 0.75)) # Inst1 + .barrier([]) # Inst2 + .play(frame1, ConstantWaveform(16e-9, 1)) # Inst3 + .play(frame2, ConstantWaveform(8e-9, -1)) # Inst4 + ) + + expected_amplitudes = {"frame1": TimeSeries(), "frame2": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries(), "frame2": TimeSeries()} + expected_phases = {"frame1": TimeSeries(), "frame2": TimeSeries()} + + # Inst1 + shift_time_frame1 = 0 + pulse_length = 12e-9 + times = np.arange(0, pulse_length, port.dt) + values = 0.75 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) + expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) + expected_phases["frame1"].put(shift_time_frame1 + t, 0) + + # Inst2 + # Delay frame2 from 0ns to 11ns + shift_time_frame2 = 0 + + expected_amplitudes["frame2"].put(0, 0).put(11e-9, 0) + expected_frequencies["frame2"].put(0, 1e8).put(11e-9, 1e8) + expected_phases["frame2"].put(0, 0).put(11e-9, 0) - # # Do we need to pad frame1? - # expected_amplitudes["frame1"].put(last_time_frame1 + port.dt, 0) - # expected_frequencies["frame1"].put(last_time_frame1 + port.dt, 1e8) - # expected_phases["frame1"].put(last_time_frame1 + port.dt, 0) + # Inst3 + shift_time_frame1 = shift_time_frame1 + pulse_length + pulse_length = 16e-9 + times = np.arange(0, pulse_length, port.dt) + values = 1 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) + expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) + expected_phases["frame1"].put(shift_time_frame1 + t, 0) - # expected_amplitudes["frame2"].put(shift_time_frame2, 0).put( - # last_time_frame1 + port.dt, 0 - # ) - # expected_frequencies["frame2"].put(shift_time_frame2, 1e8).put( - # last_time_frame1 + port.dt, 1e8 - # ) - # expected_phases["frame2"].put(shift_time_frame2, 0).put(last_time_frame1 + port.dt, 0) + # Inst4 + shift_time_frame2 = shift_time_frame1 + pulse_length = 8e-9 + times = np.arange(0, pulse_length, port.dt) + values = -1 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["frame2"].put(shift_time_frame2 + t, v) + expected_frequencies["frame2"].put(shift_time_frame2 + t, 1e8) + expected_phases["frame2"].put(shift_time_frame2 + t, 0) parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - # Array retrievable with - # x=np.arange(0, ts.times()+1e-9, 1e-9) - # np.interp(x, ts.times(), ts.values()) # See last cells of SDK Testing.ipynb def test_barrier_different_dt(port): @@ -543,8 +559,7 @@ def test_barrier_different_dt(port): expected_phases["frame1"].put(shift_time_frame1 + t, 0) # Inst2 - # barrier at 35ns (first point coinciding with frame2: np.ceil(max(20,0)/np.lcm(dt1=5,dt2=7))*np.lcm(dt1=5,dt2=7)) # noqa - # reset shift_time_frame1 = shift_time_frame2 = 35 + # barrier at 40ns (first point coinciding with frame2 (every 20ns)) shift_time_frame1 = shift_time_frame2 = 40e-9 latest_time_frame1 = expected_amplitudes["frame1"].times()[-1] @@ -580,13 +595,6 @@ def test_barrier_different_dt(port): expected_frequencies["frame2"].put(shift_time_frame2 + t, 1e8) expected_phases["frame2"].put(shift_time_frame2 + t, 0) - # # Pad frame2 - # shift_time_frame2 = shift_time_frame2 + pulse_length - # last_time = 80e-9 - # expected_amplitudes["frame2"].put(shift_time_frame2, 0).put(last_time, 0) - # expected_frequencies["frame2"].put(shift_time_frame2, 1e8).put(last_time, 1e8) - # expected_phases["frame2"].put(shift_time_frame2, 0).put(last_time, 0) - parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) @@ -628,18 +636,6 @@ def test_pad_different_dt(port): expected_frequencies["frame2"].put(shift_time_frame2 + t, 1e8) expected_phases["frame2"].put(shift_time_frame2 + t, 0) - # # Pad all frames to end up - # last_time_frame1 = expected_amplitudes["frame1"].times()[-1] - # last_time_frame2 = expected_amplitudes["frame2"].times()[-1] - - # last_time = 40e-9 - # expected_amplitudes["frame1"].put(last_time_frame1 + port1.dt, 0).put(last_time, 0) - # expected_frequencies["frame1"].put(last_time_frame1 + port1.dt, 1e8).put(last_time, 1e8) # noqa - # expected_phases["frame1"].put(last_time_frame1 + port1.dt, 0).put(last_time, 0) - # expected_amplitudes["frame2"].put(last_time_frame2 + port2.dt, 0).put(last_time, 0) - # expected_frequencies["frame2"].put(last_time_frame2 + port2.dt, 1e8).put(last_time, 1e8) # noqa - # expected_phases["frame2"].put(last_time_frame2 + port2.dt, 0).put(last_time, 0) - parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) @@ -709,9 +705,9 @@ def test_duration_literal(): def verify_results(results, expected_amplitudes, expected_frequencies, expected_phases): for frame_id in expected_amplitudes.keys(): - assert _all_close(results.amplitudes[frame_id], expected_amplitudes[frame_id]) - assert _all_close(results.frequencies[frame_id], expected_frequencies[frame_id]) - assert _all_close(results.phases[frame_id], expected_phases[frame_id]) + assert _all_close(results.amplitudes[frame_id], expected_amplitudes[frame_id], 1e-10) + assert _all_close(results.frequencies[frame_id], expected_frequencies[frame_id], 1e-10) + assert _all_close(results.phases[frame_id], expected_phases[frame_id], 1e-10) def to_dict(frames: Union[Frame, List]): From 512ab9398990a7860067297e2e45da5fd497970e Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 20 Jun 2023 16:53:57 +0000 Subject: [PATCH 0698/1165] prepare release v1.42.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 3d375dfe..87e5ff78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.42.2 (2023-06-20) + +### Bug Fixes and Other Changes + + * pulse plotting with barriers that have no argument + ## v1.42.1 (2023-06-07) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 79e25b18..b27143b5 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.42.2.dev0" +__version__ = "1.42.2" From 8dcbcd00786030c1455c2a27d93bd7cde378e96b Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 20 Jun 2023 16:53:57 +0000 Subject: [PATCH 0699/1165] update development version to v1.42.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 b27143b5..d6b9e29a 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.42.2" +__version__ = "1.42.3.dev0" From a6093b488ec236717004cf7a67e23a0659c2eca2 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 20 Jun 2023 15:34:27 -0700 Subject: [PATCH 0700/1165] infra: cleanup the tox env between runs (#576) Co-authored-by: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com> --- tox.ini | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ba688287..b0eace76 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,10 @@ [tox] -envlist = linters,docs,unit-tests +envlist = clean,linters,docs,unit-tests + +[testenv:clean] +deps = coverage +skip_install = true +commands = coverage erase [testenv:unit-tests] basepython = python3 From cfc8cda910c7e79cbf01b83b37aa5137f16b76cf Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 21 Jun 2023 13:48:24 -0700 Subject: [PATCH 0701/1165] infra: stop testing against the strawberryfields plugin (#579) --- .github/workflows/dependent-tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index f5e8edd9..5f89b8ae 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -16,7 +16,6 @@ jobs: python-version: ["3.8", "3.9", "3.10"] dependent: - amazon-braket-pennylane-plugin-python - - amazon-braket-strawberryfields-plugin-python steps: - uses: actions/checkout@v3 From 15e089b74b4e947b3f678aa4d36750de9a8e4b02 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:35:46 -0700 Subject: [PATCH 0702/1165] feat: add support for Python 3.11 (#572) * feat: add support for Python 3.11 --------- Co-authored-by: Abe Coull --- .coveragerc | 7 ++++--- .github/workflows/dependent-tests.yml | 2 +- .github/workflows/python-package.yml | 2 +- setup.py | 1 + src/braket/devices/local_simulator.py | 2 +- test/unit_tests/braket/aws/test_aws_quantum_job.py | 3 ++- tox.ini | 3 ++- 7 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.coveragerc b/.coveragerc index 22227400..79545ce2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,18 +2,19 @@ parallel = True branch = True source = - braket + src omit = **/braket/ir/* **/braket/device_schema/* **/braket/schema_common/* **/braket/task_result/* **/braket/simulator/* + */site-packages/braket/* [paths] source = - src/braket - .tox/*/lib/python*/site-packages/braket + src + */site-packages/braket [report] show_missing = True diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index 5f89b8ae..d6e247d4 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11"] dependent: - amazon-braket-pennylane-plugin-python diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index b195b44e..bb07324e 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 diff --git a/setup.py b/setup.py index e04d5b55..9ba270d9 100644 --- a/setup.py +++ b/setup.py @@ -79,5 +79,6 @@ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ], ) diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 6d254c81..8c97193a 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -204,7 +204,7 @@ def _run_internal_wrap( inputs: Optional[Dict[str, float]] = None, *args, **kwargs, - ) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: + ) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: # pragma: no cover """Wraps _run_interal for pickle dump""" return self._run_internal(task_specification, shots, inputs=inputs, *args, **kwargs) 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 846088bf..cbc535a2 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_job.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py @@ -532,7 +532,8 @@ def test_name(quantum_job_arn, quantum_job_name, aws_session): def test_no_arn_setter(quantum_job): - with pytest.raises(AttributeError, match="can't set attribute"): + # Python 3.11 error output differs from Python 3.10 <= + with pytest.raises(AttributeError): quantum_job.arn = 123 diff --git a/tox.ini b/tox.ini index b0eace76..af1fd909 100644 --- a/tox.ini +++ b/tox.ini @@ -7,12 +7,13 @@ skip_install = true commands = coverage erase [testenv:unit-tests] +usedevelop=True basepython = python3 # {posargs} contains additional arguments specified when invoking tox. e.g. tox -- -s -k test_foo.py deps = {[test-deps]deps} commands = - pytest {posargs} --cov-report term-missing --cov-report html --cov-report xml --cov=braket + pytest {posargs} --cov=braket --cov-report term-missing --cov-report html --cov-report xml --cov-append extras = test [testenv:integ-tests] From c2456de504e46fcd30a8275272efe8e8430946fb Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 22 Jun 2023 20:58:08 +0000 Subject: [PATCH 0703/1165] prepare release v1.43.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 87e5ff78..ae085ff7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.43.0 (2023-06-22) + +### Features + + * add support for Python 3.11 + ## v1.42.2 (2023-06-20) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d6b9e29a..b3885aac 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.42.3.dev0" +__version__ = "1.43.0" From 56fbb354e25275f133fcd4848444e10a9beae515 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 22 Jun 2023 20:58:08 +0000 Subject: [PATCH 0704/1165] update development version to v1.43.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 b3885aac..aa097ee1 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.43.0" +__version__ = "1.43.1.dev0" From eed59ff40e59075c2ffca7a03ac2db7730892506 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Sun, 25 Jun 2023 21:46:05 -0700 Subject: [PATCH 0705/1165] feat: add support for qubits in pulse delay and barrier (#578) * feat: add support for qubits in pulse delay and barrier --------- Co-authored-by: Abe Coull --- src/braket/pulse/pulse_sequence.py | 44 ++++++++++++------- .../braket/pulse/test_pulse_sequence.py | 13 ++++-- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index 50370061..f51531fe 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -17,9 +17,10 @@ from typing import Any, Dict, List, Set, Union from openpulse import ast -from oqpy import BitVar, Program +from oqpy import BitVar, PhysicalQubits, Program from oqpy.timing import OQDurationLiteral +from braket.circuits.qubit_set import QubitSet from braket.parametric.free_parameter import FreeParameter from braket.parametric.free_parameter_expression import FreeParameterExpression from braket.parametric.parameterizable import Parameterizable @@ -165,46 +166,57 @@ def set_scale( return self def delay( - self, frames: Union[Frame, List[Frame]], duration: Union[float, FreeParameterExpression] + self, + 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. Args: - frames (Union[Frame, List[Frame]]): Frame(s) on which the delay needs to be introduced. + 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. """ - if not isinstance(frames, list): - frames = [frames] if isinstance(duration, FreeParameterExpression): for p in duration.expression.free_symbols: self._free_parameters.add(FreeParameter(p.name)) duration = OQDurationLiteral(duration) - _validate_uniqueness(self._frames, frames) - self._program.delay(time=duration, qubits_or_frames=frames) - for frame in frames: - self._frames[frame.id] = frame + if not isinstance(qubits_or_frames, QubitSet): + if not isinstance(qubits_or_frames, list): + qubits_or_frames = [qubits_or_frames] + _validate_uniqueness(self._frames, qubits_or_frames) + self._program.delay(time=duration, qubits_or_frames=qubits_or_frames) + for frame in qubits_or_frames: + self._frames[frame.id] = frame + else: + physical_qubits = list(PhysicalQubits[int(x)] for x in qubits_or_frames) + self._program.delay(time=duration, qubits_or_frames=physical_qubits) return self - def barrier(self, frames: List[Frame]) -> PulseSequence: + 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 frames. Args: - frames (List[Frame]): Frames across which the frame clocks need to be aligned. + qubits_or_frames (Union[List[Frame], QubitSet]): Qubits or frames which the delay + needs to be introduced. Returns: PulseSequence: self, with the instruction added. """ - _validate_uniqueness(self._frames, frames) - self._program.barrier(qubits_or_frames=frames) - for frame in frames: - self._frames[frame.id] = frame + if not isinstance(qubits_or_frames, QubitSet): + _validate_uniqueness(self._frames, qubits_or_frames) + self._program.barrier(qubits_or_frames=qubits_or_frames) + for frame in qubits_or_frames: + self._frames[frame.id] = frame + else: + physical_qubits = list(PhysicalQubits[int(x)] for x in qubits_or_frames) + self._program.barrier(qubits_or_frames=physical_qubits) return self def play(self, frame: Frame, waveform: Waveform) -> PulseSequence: diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py index 657d5762..064ab302 100644 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -13,7 +13,7 @@ import pytest -from braket.circuits import FreeParameter +from braket.circuits import FreeParameter, QubitSet from braket.pulse import ( ArbitraryWaveform, ConstantWaveform, @@ -255,9 +255,10 @@ def test_pulse_sequence_conflicting_frames( method = getattr(ps, method_name) with pytest.raises(ValueError): - method(conflicting_user_defined_frame, **method_kwargs) if method_kwargs else method( - conflicting_user_defined_frame - ) + if method_kwargs: + method(conflicting_user_defined_frame, **method_kwargs) + else: + method(conflicting_user_defined_frame) def test_pulse_sequence_conflicting_wf(user_defined_frame): @@ -288,6 +289,8 @@ def test_pulse_sequence_to_ir(predefined_frame_1, predefined_frame_2): .capture_v0(predefined_frame_1) .delay([predefined_frame_1, predefined_frame_2], 2e-9) .delay(predefined_frame_1, 1e-6) + .delay(QubitSet(0), 1e-3) + .barrier(QubitSet([0, 1])) .barrier([predefined_frame_1, predefined_frame_2]) .play(predefined_frame_1, GaussianWaveform(length=1e-3, sigma=0.7, id="gauss_wf")) .play( @@ -322,6 +325,8 @@ def test_pulse_sequence_to_ir(predefined_frame_1, predefined_frame_2): " 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;", + " barrier $0, $1;", " barrier predefined_frame_1, predefined_frame_2;", " play(predefined_frame_1, gauss_wf);", " play(predefined_frame_2, drag_gauss_wf);", From 683edf55f37e14ca8127e15c0f334b1c77bbc67f Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 26 Jun 2023 16:27:45 +0000 Subject: [PATCH 0706/1165] prepare release v1.44.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 ae085ff7..393e00ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.44.0 (2023-06-26) + +### Features + + * add support for qubits in pulse delay and barrier + ## v1.43.0 (2023-06-22) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index aa097ee1..e8c70daa 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.43.1.dev0" +__version__ = "1.44.0" From 699717d712fd751f82eb8ee10d351bb8183465a8 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 26 Jun 2023 16:27:45 +0000 Subject: [PATCH 0707/1165] update development version to v1.44.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 e8c70daa..50e29a95 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.44.0" +__version__ = "1.44.1.dev0" From 38440dc19ba50204ccda71a543e29e477cb96b1c Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Tue, 27 Jun 2023 10:19:55 -0700 Subject: [PATCH 0708/1165] feat: enum for device arns (#575) Co-authored-by: Cody Wang --- examples/bell.py | 3 +- examples/debug_bell.py | 3 +- examples/job.py | 3 +- src/braket/devices/__init__.py | 1 + src/braket/devices/devices.py | 58 ++++++++++++++++++++++++ test/integ_tests/test_device_creation.py | 43 ++++++++++++++++++ 6 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 src/braket/devices/devices.py diff --git a/examples/bell.py b/examples/bell.py index 7a2c4333..e42b3b01 100644 --- a/examples/bell.py +++ b/examples/bell.py @@ -13,8 +13,9 @@ from braket.aws import AwsDevice from braket.circuits import Circuit +from braket.devices import Devices -device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") +device = AwsDevice(Devices.Amazon.SV1) # https://wikipedia.org/wiki/Bell_state bell = Circuit().h(0).cnot(0, 1) diff --git a/examples/debug_bell.py b/examples/debug_bell.py index cd492e45..00d68a45 100644 --- a/examples/debug_bell.py +++ b/examples/debug_bell.py @@ -16,12 +16,13 @@ from braket.aws import AwsDevice from braket.circuits import Circuit +from braket.devices import Devices logger = logging.getLogger("newLogger") # create new logger logger.addHandler(logging.StreamHandler(stream=sys.stdout)) # configure to print to sys.stdout logger.setLevel(logging.DEBUG) # print to sys.stdout all log messages with level DEBUG or above -device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") +device = AwsDevice(Devices.Amazon.SV1) bell = Circuit().h(0).cnot(0, 1) # pass in logger to device.run, enabling debugging logs to print to console diff --git a/examples/job.py b/examples/job.py index ab9cf8e8..fdb2cec6 100644 --- a/examples/job.py +++ b/examples/job.py @@ -15,6 +15,7 @@ from braket.aws import AwsDevice, AwsQuantumJob from braket.circuits import Circuit +from braket.devices import Devices from braket.jobs import save_job_result @@ -36,7 +37,7 @@ def run_job(): if __name__ == "__main__": job = AwsQuantumJob.create( - device="arn:aws:braket:::device/quantum-simulator/amazon/sv1", + device=Devices.Amazon.SV1, source_module="job.py", entry_point="job:run_job", wait_until_complete=True, diff --git a/src/braket/devices/__init__.py b/src/braket/devices/__init__.py index 5750714b..1c45599a 100644 --- a/src/braket/devices/__init__.py +++ b/src/braket/devices/__init__.py @@ -12,4 +12,5 @@ # language governing permissions and limitations under the License. from braket.devices.device import Device # noqa: F401 +from braket.devices.devices import Devices # noqa: F401 from braket.devices.local_simulator import LocalSimulator # noqa: F401 diff --git a/src/braket/devices/devices.py b/src/braket/devices/devices.py new file mode 100644 index 00000000..e179bdfd --- /dev/null +++ b/src/braket/devices/devices.py @@ -0,0 +1,58 @@ +# 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 enum import Enum + + +class Devices: + class _Amazon(str, Enum): + SV1 = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" + TN1 = "arn:aws:braket:::device/quantum-simulator/amazon/tn1" + DM1 = "arn:aws:braket:::device/quantum-simulator/amazon/dm1" + + class _DWave(str, Enum): + _Advantage1 = "arn:aws:braket:::device/qpu/d-wave/Advantage_system1" + _Advantage3 = "arn:aws:braket:::device/qpu/d-wave/Advantage_system3" + _Advantage4 = "arn:aws:braket:::device/qpu/d-wave/Advantage_system4" + _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 _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" + + class _OQC(str, Enum): + 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" + + class _Rigetti(str, Enum): + _Aspen8 = "arn:aws:braket:::device/qpu/rigetti/Aspen-8" + _Aspen9 = "arn:aws:braket:::device/qpu/rigetti/Aspen-9" + _Aspen10 = "arn:aws:braket:::device/qpu/rigetti/Aspen-10" + _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" + + class _Xanadu(str, Enum): + _Borealis = "arn:aws:braket:us-east-1::device/qpu/xanadu/Borealis" + + Amazon = _Amazon + # DWave = _DWave + IonQ = _IonQ + 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 36cb6ec5..c12b899f 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -14,6 +14,7 @@ import pytest from braket.aws import AwsDevice +from braket.devices import Devices RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-10" IONQ_ARN = "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" @@ -71,3 +72,45 @@ def test_get_devices_all(): result_arns = [result.arn for result in AwsDevice.get_devices()] for arn in [RIGETTI_ARN, IONQ_ARN, SIMULATOR_ARN, OQC_ARN]: assert arn in result_arns + + +def test_device_enum(): + provider_name_to_enum_map = { + "Amazon Braket": "Amazon", + "D-Wave Systems": "_DWave", + "IonQ": "IonQ", + "Oxford": "OQC", + "QuEra": "QuEra", + "Rigetti": "Rigetti", + "Xanadu": "_Xanadu", + } + device_name_to_enum_map = { + "SV1": "SV1", + "TN1": "TN1", + "dm1": "DM1", + "Advantage_system1.1": "_Advantage1", + "Advantage_system3.2": "_Advantage3", + "Advantage_system4.1": "_Advantage4", + "Advantage_system6.1": "_Advantage6", + "DW_2000Q_6": "_DW2000Q6", + "Harmony": "Harmony", + "Aria 1": "Aria1", + "Lucy": "Lucy", + "Aquila": "Aquila", + "Aspen-8": "_Aspen8", + "Aspen-9": "_Aspen9", + "Aspen-10": "_Aspen10", + "Aspen-11": "_Aspen11", + "Aspen-M-1": "_AspenM1", + "Aspen-M-2": "_AspenM2", + "Aspen-M-3": "AspenM3", + "Borealis": "_Borealis", + } + for device in AwsDevice.get_devices(): + assert ( + getattr( + getattr(Devices, provider_name_to_enum_map[device.provider_name]), + device_name_to_enum_map[device.name], + ) + == device.arn + ) From ba9131500fc327dfface5cc7ce56f5e57fc26a21 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 28 Jun 2023 00:19:02 +0100 Subject: [PATCH 0709/1165] infra: Only run Codecov check once (#585) --- .github/workflows/python-package.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index bb07324e..50a6de00 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -35,3 +35,4 @@ jobs: tox -e unit-tests - name: Upload coverage report to Codecov uses: codecov/codecov-action@v3 + if: ${{ matrix.os }} == "ubuntu-latest" && ${{ matrix.python-version }} == "3.11" From 2f1be1b05efd617bbdce7f4286160b263c9c2555 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 28 Jun 2023 01:18:40 +0100 Subject: [PATCH 0710/1165] infra: Use final strategy index for Codecov run (#586) --- .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 50a6de00..fe10ad51 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -35,4 +35,4 @@ jobs: tox -e unit-tests - name: Upload coverage report to Codecov uses: codecov/codecov-action@v3 - if: ${{ matrix.os }} == "ubuntu-latest" && ${{ matrix.python-version }} == "3.11" + if: ${{ strategy.job-index }} == 0 From ce8f55b6b35448caee58176fe15ebd9c054aae9f Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 28 Jun 2023 16:13:01 +0000 Subject: [PATCH 0711/1165] prepare release v1.45.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 393e00ce..347a3524 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.45.0 (2023-06-28) + +### Features + + * enum for device arns + ## v1.44.0 (2023-06-26) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 50e29a95..c3109f40 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.44.1.dev0" +__version__ = "1.45.0" From 5fbfa535cdbee3a161333c878f7df228eae74d1e Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 28 Jun 2023 16:13:01 +0000 Subject: [PATCH 0712/1165] update development version to v1.45.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 c3109f40..df1778b1 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.45.0" +__version__ = "1.45.1.dev0" From 6166ba31ac5cd1189074b01ebf6098afcf71757e Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 28 Jun 2023 09:57:11 -0700 Subject: [PATCH 0713/1165] feat: add string support for FreeParameterExpressions (#582) * feat: add string support for FreeParameterExpressions --- .../parametric/free_parameter_expression.py | 39 +++++++++++++++++-- .../test_free_parameter_expression.py | 25 +++++++++++- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index 120e7ffa..9059cdb5 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -13,10 +13,11 @@ from __future__ import annotations +import ast from numbers import Number from typing import Any, Dict, Union -from sympy import Expr, Float, sympify +from sympy import Expr, Float, Symbol, sympify class FreeParameterExpression: @@ -29,7 +30,7 @@ class FreeParameterExpression: present will NOT run. Values must be substituted prior to execution. """ - def __init__(self, expression: Union[FreeParameterExpression, Number, Expr]): + def __init__(self, expression: Union[FreeParameterExpression, Number, Expr, str]): """ Initializes a FreeParameterExpression. Best practice is to initialize using FreeParameters and Numbers. Not meant to be initialized directly. @@ -37,16 +38,25 @@ def __init__(self, expression: Union[FreeParameterExpression, Number, Expr]): Below are examples of how FreeParameterExpressions should be made. Args: - expression (Union[FreeParameterExpression, Number, Expr]): The expression to use. + expression (Union[FreeParameterExpression, Number, Expr, str]): The expression to use. Examples: >>> expression_1 = FreeParameter("theta") * FreeParameter("alpha") >>> expression_2 = 1 + FreeParameter("beta") + 2 * FreeParameter("alpha") """ + self._operations = { + ast.Add: self.__add__, + ast.Sub: self.__sub__, + ast.Mult: self.__mul__, + ast.Pow: self.__pow__, + ast.USub: self.__neg__, + } if isinstance(expression, FreeParameterExpression): self._expression = expression.expression elif isinstance(expression, (Number, Expr)): self._expression = expression + elif isinstance(expression, str): + self._expression = self._parse_string_expression(expression) else: raise NotImplementedError @@ -81,11 +91,32 @@ def subs( new_parameter_values[key] = val subbed_expr = self._expression.subs(new_parameter_values) - if subbed_expr.is_Number: + if isinstance(subbed_expr, Number): return subbed_expr else: return FreeParameterExpression(subbed_expr) + 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): + return FreeParameterExpression(node.n) + elif isinstance(node, ast.Name): + return FreeParameterExpression(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)}") + 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(): + 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}") + def __add__(self, other): 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 89692091..735c3d3d 100644 --- a/test/unit_tests/braket/parametric/test_free_parameter_expression.py +++ b/test/unit_tests/braket/parametric/test_free_parameter_expression.py @@ -28,7 +28,7 @@ def test_is_free_param_expr(free_parameter_expression): @pytest.mark.xfail(raises=NotImplementedError) def test_constructor_bad_input(): - FreeParameterExpression("theta") + FreeParameterExpression(["test"]) def test_equality(): @@ -43,6 +43,29 @@ def test_equality(): assert expr_1 != non_expr +def test_equality_str(): + expr_1 = FreeParameterExpression("-theta+2*theta") + expr_2 = FreeParameterExpression(-FreeParameter("theta") + 2 * FreeParameter("theta")) + param_values = {"theta": 1} + assert expr_1 == expr_2 + assert expr_1.subs(param_values) == expr_2.subs(param_values) + + +@pytest.mark.xfail(raises=ValueError) +def test_unsupported_bin_op_str(): + FreeParameterExpression("theta/1") + + +@pytest.mark.xfail(raises=ValueError) +def test_unsupported_un_op_str(): + FreeParameterExpression("~theta") + + +@pytest.mark.xfail(raises=ValueError) +def test_unsupported_node_str(): + FreeParameterExpression("theta , 1") + + def test_commutativity(): add_1 = 1 + FreeParameterExpression(FreeParameter("theta")) add_2 = FreeParameterExpression(FreeParameter("theta")) + 1 From 15f354294693e340b9b3d8d24186a974875e4a9e Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 29 Jun 2023 16:13:28 +0000 Subject: [PATCH 0714/1165] prepare release v1.46.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 347a3524..95448831 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.46.0 (2023-06-29) + +### Features + + * add string support for FreeParameterExpressions + ## v1.45.0 (2023-06-28) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index df1778b1..35278e0e 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.45.1.dev0" +__version__ = "1.46.0" From f1ef00ad2461efd01fe842058ed70a8b1bd0b322 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 29 Jun 2023 16:13:28 +0000 Subject: [PATCH 0715/1165] update development version to v1.46.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 35278e0e..16c076ed 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.46.0" +__version__ = "1.46.1.dev0" From 0e112bd967421d8e37da9be83a6b64bbac54b9e3 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Thu, 29 Jun 2023 17:48:13 -0700 Subject: [PATCH 0716/1165] fix: mixed free parameters and floats (#590) --- src/braket/circuits/circuit.py | 7 ++++--- test/unit_tests/braket/circuits/test_circuit.py | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 66ae24ba..b7f770d7 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -460,9 +460,10 @@ def add_instruction( if self._check_for_params(instruction): for param in instruction.operator.parameters: - free_params = param.expression.free_symbols - for parameter in free_params: - self._parameters.add(FreeParameter(parameter.name)) + if isinstance(param, FreeParameterExpression): + free_params = param.expression.free_symbols + for parameter in free_params: + self._parameters.add(FreeParameter(parameter.name)) self._moments.add(instructions_to_add) return self diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 5e534999..298eb773 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -2445,3 +2445,7 @@ def test_parametrized_pulse_circuit(user_defined_frame): "b[1] = measure $1;", ] ) + + +def test_free_param_float_mix(): + Circuit().ms(0, 1, 0.1, FreeParameter("theta")) From aa4e004d25a127d952ae510db10293cb6b8204a8 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Fri, 30 Jun 2023 13:21:05 -0700 Subject: [PATCH 0717/1165] feat: add optional third angle to MS gate (#570) --- setup.py | 2 +- src/braket/circuits/angled_gate.py | 155 ++++++++++++++++-- src/braket/circuits/gates.py | 47 ++++-- .../braket/circuits/test_angled_gate.py | 22 ++- test/unit_tests/braket/circuits/test_gates.py | 34 ++-- 5 files changed, 215 insertions(+), 45 deletions(-) diff --git a/setup.py b/setup.py index 9ba270d9..c71f8cc9 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.17.0", - "amazon-braket-default-simulator>=1.14.0", + "amazon-braket-default-simulator>=1.15.0", "oqpy~=0.1.1", "setuptools", "backoff", diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index 6a19ac91..c40dce17 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -232,6 +232,133 @@ def __repr__(self): ) +class TripleAngledGate(Gate, Parameterizable): + """ + Class `TripleAngledGate` represents a quantum gate that operates on N qubits and three angles. + """ + + def __init__( + self, + angle_1: Union[FreeParameterExpression, float], + angle_2: Union[FreeParameterExpression, float], + angle_3: Union[FreeParameterExpression, float], + qubit_count: Optional[int], + ascii_symbols: Sequence[str], + ): + """ + 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. + angle_3 (Union[FreeParameterExpression, float]): The third angle of the gate in + radians or expression representation. + qubit_count (Optional[int]): The number of qubits that this gate interacts with. + ascii_symbols (Sequence[str]): ASCII string symbols for the gate. These are used when + 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. + For instance, if a CNOT instruction has the control qubit on the first index and + target qubit on the second index, the ASCII symbols should have `["C", "X"]` to + correlate a symbol with that index. + + Raises: + ValueError: If `qubit_count` is less than 1, `ascii_symbols` are `None`, or + `ascii_symbols` length != `qubit_count`, or `angle_1` or `angle_2` or `angle_3` + is `None` + """ + super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + if angle_1 is None or angle_2 is None or angle_3 is None: + raise ValueError("angles must not be None") + self._parameters = [ + ( + angle + if isinstance(angle, FreeParameterExpression) + else float(angle) # explicit casting in case angle is e.g. np.float32 + ) + for angle in (angle_1, angle_2, angle_3) + ] + + @property + def parameters(self) -> List[Union[FreeParameterExpression, float]]: + """ + Returns the parameters associated with the object, either unbound free parameters or + bound values. + + Returns: + List[Union[FreeParameterExpression, float]]: The free parameters or fixed value + associated with the object. + """ + return self._parameters + + @property + def angle_1(self) -> Union[FreeParameterExpression, float]: + """ + Returns the first angle for the gate + + Returns: + Union[FreeParameterExpression, float]: The first angle of the gate in radians + """ + return self._parameters[0] + + @property + def angle_2(self) -> Union[FreeParameterExpression, float]: + """ + Returns the second angle for the gate + + Returns: + Union[FreeParameterExpression, float]: The second angle of the gate in radians + """ + return self._parameters[1] + + @property + def angle_3(self) -> Union[FreeParameterExpression, float]: + """ + Returns the second angle for 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. + + Args: + ``**kwargs``: The parameters that are being assigned. + + Returns: + AngledGate: A new Gate of the same type with the requested parameters bound. + + Raises: + NotImplementedError: Subclasses should implement this function. + """ + raise NotImplementedError + + def adjoint(self) -> List[Gate]: + """Returns the adjoint of this gate as a singleton list. + + Returns: + List[Gate]: A list containing the gate with negated angle. + """ + raise NotImplementedError + + def __eq__(self, other): + return ( + isinstance(other, TripleAngledGate) + and self.name == other.name + and _angles_equal(self.angle_1, other.angle_1) + and _angles_equal(self.angle_2, other.angle_2) + and _angles_equal(self.angle_3, other.angle_3) + ) + + def __repr__(self): + return ( + f"{self.name}('angles': ({self.angle_1}, {self.angle_2}, {self.angle_3}), " + f"'qubit_count': {self.qubit_count})" + ) + + @singledispatch def _angles_equal( angle_1: Union[FreeParameterExpression, float], angle_2: Union[FreeParameterExpression, float] @@ -259,28 +386,26 @@ def angled_ascii_characters(gate: str, angle: Union[FreeParameterExpression, flo return f'{gate}({angle:{".2f" if isinstance(angle, (float, Float)) else ""}})' -def _double_angled_ascii_characters( +def _multi_angled_ascii_characters( gate: str, - angle_1: Union[FreeParameterExpression, float], - angle_2: Union[FreeParameterExpression, float], + *angles: Union[FreeParameterExpression, float], ) -> str: """ Generates a formatted ascii representation of an angled gate. Args: gate (str): The name of the gate. - angle_1 (Union[FreeParameterExpression, float]): angle in radians. - angle_2 (Union[FreeParameterExpression, float]): angle in radians. + angles (Union[FreeParameterExpression, float]): angles in radians. Returns: str: Returns the ascii representation for an angled gate. """ - return ( - f"{gate}(" - f'{angle_1:{".2f" if isinstance(angle_1, (float, Float)) else ""}}, ' - f'{angle_2:{".2f" if isinstance(angle_2, (float, Float)) else ""}})' - ) + + def format_string(angle): + return ".2f" if isinstance(angle, (float, Float)) else "" + + return f"{gate}({', '.join(f'{angle:{format_string(angle)}}' for angle in angles)})" def get_angle(gate: AngledGate, **kwargs) -> AngledGate: @@ -301,17 +426,17 @@ def get_angle(gate: AngledGate, **kwargs) -> AngledGate: return type(gate)(angle=new_angle) -def _get_angles(gate: DoubleAngledGate, **kwargs) -> DoubleAngledGate: +def _get_angles(gate: TripleAngledGate, **kwargs) -> TripleAngledGate: """ Gets the angle with all values substituted in that are requested. Args: - gate (DoubleAngledGate): The subclass of DoubleAngledGate for which the angle is being + 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. Returns: - DoubleAngledGate: A new gate of the type of the AngledGate originally used with all angles + TripleAngledGate: A new gate of the type of the AngledGate originally used with all angles updated. """ new_angles = [ @@ -320,6 +445,6 @@ def _get_angles(gate: DoubleAngledGate, **kwargs) -> DoubleAngledGate: if isinstance(getattr(gate, angle), FreeParameterExpression) else getattr(gate, angle) ) - for angle in ("angle_1", "angle_2") + for angle in ("angle_1", "angle_2", "angle_3") ] - return type(gate)(angle_1=new_angles[0], angle_2=new_angles[1]) + return type(gate)(angle_1=new_angles[0], angle_2=new_angles[1], angle_3=new_angles[2]) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 02688b71..a37e3208 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -23,9 +23,9 @@ from braket.circuits import circuit from braket.circuits.angled_gate import ( AngledGate, - DoubleAngledGate, - _double_angled_ascii_characters, + TripleAngledGate, _get_angles, + _multi_angled_ascii_characters, angled_ascii_characters, get_angle, ) @@ -2608,24 +2608,27 @@ def gpi2( Gate.register_gate(GPi2) -class MS(DoubleAngledGate): +class MS(TripleAngledGate): """IonQ Mølmer-Sørenson gate. Args: angle_1 (Union[FreeParameterExpression, float]): angle in radians. angle_2 (Union[FreeParameterExpression, float]): angle in radians. + angle_3 (Union[FreeParameterExpression, float]): angle in radians. """ def __init__( self, angle_1: Union[FreeParameterExpression, float], angle_2: Union[FreeParameterExpression, float], + angle_3: Union[FreeParameterExpression, float] = np.pi / 2, ): super().__init__( angle_1=angle_1, angle_2=angle_2, + angle_3=angle_3, qubit_count=None, - ascii_symbols=[_double_angled_ascii_characters("MS", angle_1, angle_2)] * 2, + ascii_symbols=[_multi_angled_ascii_characters("MS", angle_1, angle_2, angle_3)] * 2, ) @property @@ -2635,15 +2638,35 @@ def _qasm_name(self) -> str: def to_matrix(self) -> np.ndarray: return np.array( [ - [1, 0, 0, -1j * np.exp(-1j * (self.angle_1 + self.angle_2))], - [0, 1, -1j * np.exp(-1j * (self.angle_1 - self.angle_2)), 0], - [0, -1j * np.exp(1j * (self.angle_1 - self.angle_2)), 1, 0], - [-1j * np.exp(1j * (self.angle_1 + self.angle_2)), 0, 0, 1], + [ + 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.sqrt(2) + ) def adjoint(self) -> List[Gate]: - return [MS(self.angle_1 + np.pi, self.angle_2)] + return [MS(self.angle_1 + np.pi, self.angle_2, self.angle_3)] @staticmethod def fixed_qubit_count() -> int: @@ -2659,6 +2682,7 @@ def ms( target2: QubitInput, angle_1: Union[FreeParameterExpression, float], angle_2: Union[FreeParameterExpression, float], + angle_3: Union[FreeParameterExpression, float] = np.pi / 2, *, control: Optional[QubitSetInput] = None, control_state: Optional[BasisStateInput] = None, @@ -2671,6 +2695,7 @@ def ms( target2 (QubitInput): Target qubit 2 index. angle_1 (Union[FreeParameterExpression, float]): angle in radians. angle_2 (Union[FreeParameterExpression, float]): angle in radians. + angle_3 (Union[FreeParameterExpression, float]): 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 @@ -2690,7 +2715,7 @@ def ms( """ return [ Instruction( - MS(angle_1, angle_2), + MS(angle_1, angle_2, angle_3), target=[target1, target2], control=control, control_state=control_state, diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index 0e46c1a3..8dde92f2 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -18,7 +18,7 @@ from pydantic import BaseModel from braket.circuits import AngledGate, FreeParameter, FreeParameterExpression, Gate -from braket.circuits.angled_gate import DoubleAngledGate +from braket.circuits.angled_gate import DoubleAngledGate, TripleAngledGate @pytest.fixture @@ -140,6 +140,11 @@ def test_double_angle_is_none(): DoubleAngledGate(qubit_count=1, ascii_symbols=["foo"], angle_1=None, angle_2=1) +def test_triple_angle_is_none(): + with pytest.raises(ValueError, match="angles must not be None"): + TripleAngledGate(qubit_count=1, ascii_symbols=["foo"], angle_1=None, angle_2=1, angle_3=2) + + def test_double_angle_equality(): gate = DoubleAngledGate(angle_1=0.15, angle_2=3, qubit_count=1, ascii_symbols=["bar"]) equal_gate = DoubleAngledGate(angle_1=0.15, angle_2=3, qubit_count=1, ascii_symbols=["bar"]) @@ -172,3 +177,18 @@ def test_double_angle_repr(): repr(DoubleAngledGate(qubit_count=1, ascii_symbols=["foo"], angle_1=1, angle_2=2)) == "DoubleAngledGate('angles': (1.0, 2.0), 'qubit_count': 1)" ) + + +def test_triple_angle_repr(): + assert ( + repr( + TripleAngledGate(qubit_count=1, ascii_symbols=["foo"], angle_1=1, angle_2=2, angle_3=3) + ) + == "TripleAngledGate('angles': (1.0, 2.0, 3.0), 'qubit_count': 1)" + ) + + +def test_double_angle_parameters(): + assert DoubleAngledGate( + qubit_count=1, ascii_symbols=["foo"], angle_1=1, angle_2=2 + ).parameters == [1, 2] diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 89ce7fb5..90dc7d5a 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -35,7 +35,7 @@ from braket.pulse import ArbitraryWaveform, Frame, Port, PulseSequence -class DoubleAngle: +class TripleAngle: pass @@ -93,7 +93,7 @@ class DoubleAngle: (Gate.ZZ, "zz", ir.ZZ, [DoubleTarget, Angle], {}), (Gate.GPi, "gpi", None, [SingleTarget, Angle], {}), (Gate.GPi2, "gpi2", None, [SingleTarget, Angle], {}), - (Gate.MS, "ms", None, [DoubleTarget, DoubleAngle], {}), + (Gate.MS, "ms", None, [DoubleTarget, TripleAngle], {}), ( Gate.Unitary, "unitary", @@ -163,8 +163,8 @@ def angle_valid_input(**kwargs): return {"angle": 0.123} -def double_angle_valid_input(**kwargs): - return {"angle_1": 0.123, "angle_2": 4.567} +def triple_angle_valid_input(**kwargs): + return {"angle_1": 0.123, "angle_2": 4.567, "angle_3": 8.910} def single_control_valid_input(**kwargs): @@ -196,7 +196,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, "DoubleControl": double_control_valid_ir_input, "MultiTarget": multi_target_valid_input, @@ -244,7 +244,7 @@ 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 subclass in (Angle, TwoDimensionalMatrix, DoubleAngle): + elif subclass in (Angle, TwoDimensionalMatrix, TripleAngle): pass else: raise ValueError("Invalid subclass") @@ -256,8 +256,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: input.update(two_dimensional_matrix_valid_input(**kwargs)) return input @@ -282,7 +282,7 @@ def calculate_qubit_count(irsubclasses): qubit_count += 2 elif subclass == MultiTarget: qubit_count += 3 - elif subclass in (Angle, TwoDimensionalMatrix, DoubleAngle): + elif subclass in (Angle, TwoDimensionalMatrix, TripleAngle): pass else: raise ValueError("Invalid subclass") @@ -808,13 +808,13 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.MS(angle_1=0.17, angle_2=3.45), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "ms(0.17, 3.45) q[4], q[5];", + f"ms(0.17, 3.45, {np.pi / 2}) q[4], q[5];", ), ( Gate.MS(angle_1=0.17, angle_2=3.45), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "ms(0.17, 3.45) $4, $5;", + f"ms(0.17, 3.45, {np.pi / 2}) $4, $5;", ), ], ) @@ -925,17 +925,17 @@ def test_large_unitary(): @pytest.mark.parametrize("gate", parameterizable_gates) def test_bind_values(gate): - double_angled = gate.__name__ in ("MS",) - num_params = 2 if double_angled else 1 + triple_angled = gate.__name__ in ("MS",) + num_params = 3 if triple_angled else 1 thetas = [FreeParameter(f"theta_{i}") for i in range(num_params)] - mapping = dict((f"theta_{i}", i) for i in range(num_params)) + mapping = {f"theta_{i}": i for i in range(num_params)} param_gate = gate(*thetas) new_gate = param_gate.bind_values(**mapping) expected = gate(*range(num_params)) assert type(new_gate) == type(param_gate) and new_gate == expected - if double_angled: - for angle in new_gate.angle_1, new_gate.angle_2: + if triple_angled: + for angle in new_gate.angle_1, new_gate.angle_2, new_gate.angle_3: assert type(angle) == float else: assert type(new_gate.angle) == float @@ -1023,7 +1023,7 @@ def test_pulse_gate_to_matrix(): QubitSet(0), QubitSet([1, 2]), None, - "ctrl(2) @ ms(0.17, 3.45) q[1], q[2], q[0];", + f"ctrl(2) @ ms(0.17, 3.45, {np.pi / 2}) q[1], q[2], q[0];", ), ( Gate.CCNot(), From 74fefcd3e9f50de8e45342d61750f35a7a6e8af3 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 30 Jun 2023 20:35:13 +0000 Subject: [PATCH 0718/1165] prepare release v1.47.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 95448831..91bc4499 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.47.0 (2023-06-30) + +### Features + + * add optional third angle to MS gate + +### Bug Fixes and Other Changes + + * mixed free parameters and floats + ## v1.46.0 (2023-06-29) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 16c076ed..a959bb59 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.46.1.dev0" +__version__ = "1.47.0" From 0aa3b11878c532a98d28ee0da9da75a7a7559ad7 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 30 Jun 2023 20:35:13 +0000 Subject: [PATCH 0719/1165] update development version to v1.47.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 a959bb59..10f3c763 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.47.0" +__version__ = "1.47.1.dev0" From 41144327f62c9db3c6d60dead0f638945a7f0de4 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 5 Jul 2023 12:03:13 -0700 Subject: [PATCH 0720/1165] feature: autoqasm (#589) AutoQASM is an experimental module offering a new quantum-imperative programming experience embedded in Python for developing quantum programs. Co-authored-by: Lauren Capelluto <107005333+laurencap@users.noreply.github.com> Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Co-authored-by: Tim (Yi-Ting) --- .github/workflows/check-format.yml | 4 +- setup.cfg | 2 + setup.py | 9 +- src/braket/devices/device.py | 11 +- src/braket/devices/local_simulator.py | 42 +- src/braket/experimental/__init__.py | 18 + src/braket/experimental/autoqasm/README.md | 137 ++ src/braket/experimental/autoqasm/__init__.py | 59 + .../experimental/autoqasm/api/__init__.py | 16 + src/braket/experimental/autoqasm/api/api.py | 402 +++++ .../autoqasm/autograph/ATTRIBUTION.md | 263 +++ .../autoqasm/autograph/__init__.py | 55 + .../autoqasm/autograph/converters/__init__.py | 28 + .../autoqasm/autograph/converters/asserts.py | 48 + .../autograph/converters/break_statements.py | 185 ++ .../autograph/converters/call_trees.py | 221 +++ .../converters/conditional_expressions.py | 46 + .../converters/continue_statements.py | 164 ++ .../autograph/converters/control_flow.py | 413 +++++ .../converters/control_flow_deprecated_py2.py | 635 +++++++ .../autograph/converters/directives.py | 177 ++ .../autograph/converters/functions.py | 134 ++ .../converters/list_comprehensions.py | 78 + .../autoqasm/autograph/converters/lists.py | 239 +++ .../converters/logical_expressions.py | 131 ++ .../autograph/converters/return_statements.py | 402 +++++ .../autoqasm/autograph/converters/slices.py | 83 + .../autograph/converters/variables.py | 97 + .../autoqasm/autograph/core/ag_ctx.py | 103 ++ .../autoqasm/autograph/core/config.py | 61 + .../autoqasm/autograph/core/config_lib.py | 61 + .../autoqasm/autograph/core/converter.py | 314 ++++ .../autograph/core/function_wrappers.py | 95 + .../core/unsupported_features_checker.py | 57 + .../autoqasm/autograph/impl/api_core.py | 161 ++ .../autoqasm/autograph/lang/directives.py | 91 + .../autoqasm/autograph/logging/ag_logging.py | 142 ++ .../autoqasm/autograph/operators/variables.py | 104 ++ .../autograph/operators/variables_test.py | 51 + .../autoqasm/autograph/pyct/__init__.py | 16 + .../autoqasm/autograph/pyct/anno.py | 174 ++ .../autoqasm/autograph/pyct/anno_test.py | 80 + .../autoqasm/autograph/pyct/ast_util.py | 344 ++++ .../autoqasm/autograph/pyct/ast_util_test.py | 246 +++ .../autoqasm/autograph/pyct/cache.py | 93 + .../autoqasm/autograph/pyct/cache_test.py | 75 + .../autoqasm/autograph/pyct/cfg.py | 971 ++++++++++ .../autoqasm/autograph/pyct/cfg_test.py | 1602 +++++++++++++++++ .../pyct/common_transformers/__init__.py | 0 .../autograph/pyct/common_transformers/anf.py | 620 +++++++ .../pyct/common_transformers/anf_test.py | 518 ++++++ .../autoqasm/autograph/pyct/error_utils.py | 230 +++ .../autograph/pyct/error_utils_test.py | 126 ++ .../autoqasm/autograph/pyct/errors.py | 27 + .../autoqasm/autograph/pyct/gast_util.py | 74 + .../autoqasm/autograph/pyct/inspect_utils.py | 321 ++++ .../autograph/pyct/inspect_utils_test.py | 612 +++++++ .../autograph/pyct/inspect_utils_test.sh | 19 + .../autoqasm/autograph/pyct/loader.py | 102 ++ .../autoqasm/autograph/pyct/loader_test.py | 123 ++ .../autoqasm/autograph/pyct/naming.py | 53 + .../autoqasm/autograph/pyct/naming_test.py | 44 + .../autoqasm/autograph/pyct/origin_info.py | 296 +++ .../autograph/pyct/origin_info_test.py | 268 +++ .../autoqasm/autograph/pyct/parser.py | 396 ++++ .../autoqasm/autograph/pyct/parser_test.py | 385 ++++ .../autoqasm/autograph/pyct/pretty_printer.py | 130 ++ .../autograph/pyct/pretty_printer_test.py | 57 + .../autoqasm/autograph/pyct/qual_names.py | 266 +++ .../autograph/pyct/qual_names_test.py | 261 +++ .../pyct/static_analysis/__init__.py | 28 + .../pyct/static_analysis/activity.py | 706 ++++++++ .../pyct/static_analysis/activity_test.py | 868 +++++++++ .../autograph/pyct/static_analysis/annos.py | 55 + .../pyct/static_analysis/liveness.py | 220 +++ .../pyct/static_analysis/liveness_py3_test.py | 30 + .../pyct/static_analysis/liveness_test.py | 575 ++++++ .../static_analysis/reaching_definitions.py | 288 +++ .../reaching_definitions_py3_test.py | 92 + .../reaching_definitions_test.py | 526 ++++++ .../pyct/static_analysis/reaching_fndefs.py | 178 ++ .../static_analysis/reaching_fndefs_test.py | 54 + .../pyct/static_analysis/type_inference.py | 624 +++++++ .../static_analysis/type_inference_test.py | 942 ++++++++++ .../autoqasm/autograph/pyct/templates.py | 290 +++ .../autoqasm/autograph/pyct/templates_test.py | 338 ++++ .../pyct/testing/basic_definitions.py | 65 + .../autograph/pyct/testing/codegen.py | 230 +++ .../autograph/pyct/testing/decorators.py | 46 + .../autoqasm/autograph/pyct/transformer.py | 538 ++++++ .../autograph/pyct/transformer_test.py | 364 ++++ .../autoqasm/autograph/pyct/transpiler.py | 495 +++++ .../autograph/pyct/transpiler_test.py | 240 +++ .../autograph/tf_utils/tf_decorator.py | 361 ++++ .../autoqasm/autograph/tf_utils/tf_export.py | 420 +++++ .../autoqasm/autograph/tf_utils/tf_stack.py | 168 ++ .../autograph/tf_utils/traceback_utils.py | 157 ++ src/braket/experimental/autoqasm/constants.py | 37 + .../autoqasm/converters/__init__.py | 19 + .../autoqasm/converters/assignments.py | 72 + src/braket/experimental/autoqasm/errors.py | 35 + .../experimental/autoqasm/gates/__init__.py | 17 + .../experimental/autoqasm/gates/gates.py | 62 + .../autoqasm/gates/measurements.py | 62 + .../experimental/autoqasm/gates/qubits.py | 95 + .../autoqasm/operators/__init__.py | 54 + .../autoqasm/operators/assignments.py | 123 ++ .../operators/conditional_expressions.py | 63 + .../autoqasm/operators/control_flow.py | 171 ++ .../autoqasm/operators/data_structures.py | 29 + .../autoqasm/operators/logical.py | 52 + .../experimental/autoqasm/operators/slices.py | 104 ++ .../experimental/autoqasm/program/__init__.py | 27 + .../experimental/autoqasm/program/pragmas.py | 52 + .../experimental/autoqasm/program/program.py | 246 +++ .../autoqasm/transpiler/__init__.py | 17 + .../autoqasm/transpiler/transpiler.py | 380 ++++ .../experimental/autoqasm/types/__init__.py | 19 + .../experimental/autoqasm/types/py_to_oqpy.py | 179 ++ .../experimental/autoqasm/types/types.py | 51 + .../braket/experimental/autoqasm/conftest.py | 71 + .../experimental/autoqasm/mock_transpiler.py | 56 + .../braket/experimental/autoqasm/test_api.py | 798 ++++++++ .../experimental/autoqasm/test_aq_gates.py | 52 + .../experimental/autoqasm/test_converters.py | 59 + .../experimental/autoqasm/test_operators.py | 546 ++++++ .../experimental/autoqasm/test_pragmas.py | 38 + .../experimental/autoqasm/test_program.py | 62 + .../experimental/autoqasm/test_types.py | 43 + tox.ini | 4 +- 130 files changed, 26441 insertions(+), 20 deletions(-) create mode 100644 src/braket/experimental/__init__.py create mode 100644 src/braket/experimental/autoqasm/README.md create mode 100644 src/braket/experimental/autoqasm/__init__.py create mode 100644 src/braket/experimental/autoqasm/api/__init__.py create mode 100644 src/braket/experimental/autoqasm/api/api.py create mode 100644 src/braket/experimental/autoqasm/autograph/ATTRIBUTION.md create mode 100644 src/braket/experimental/autoqasm/autograph/__init__.py create mode 100644 src/braket/experimental/autoqasm/autograph/converters/__init__.py create mode 100644 src/braket/experimental/autoqasm/autograph/converters/asserts.py create mode 100644 src/braket/experimental/autoqasm/autograph/converters/break_statements.py create mode 100644 src/braket/experimental/autoqasm/autograph/converters/call_trees.py create mode 100644 src/braket/experimental/autoqasm/autograph/converters/conditional_expressions.py create mode 100644 src/braket/experimental/autoqasm/autograph/converters/continue_statements.py create mode 100644 src/braket/experimental/autoqasm/autograph/converters/control_flow.py create mode 100644 src/braket/experimental/autoqasm/autograph/converters/control_flow_deprecated_py2.py create mode 100644 src/braket/experimental/autoqasm/autograph/converters/directives.py create mode 100644 src/braket/experimental/autoqasm/autograph/converters/functions.py create mode 100644 src/braket/experimental/autoqasm/autograph/converters/list_comprehensions.py create mode 100644 src/braket/experimental/autoqasm/autograph/converters/lists.py create mode 100644 src/braket/experimental/autoqasm/autograph/converters/logical_expressions.py create mode 100644 src/braket/experimental/autoqasm/autograph/converters/return_statements.py create mode 100644 src/braket/experimental/autoqasm/autograph/converters/slices.py create mode 100644 src/braket/experimental/autoqasm/autograph/converters/variables.py create mode 100644 src/braket/experimental/autoqasm/autograph/core/ag_ctx.py create mode 100644 src/braket/experimental/autoqasm/autograph/core/config.py create mode 100644 src/braket/experimental/autoqasm/autograph/core/config_lib.py create mode 100644 src/braket/experimental/autoqasm/autograph/core/converter.py create mode 100644 src/braket/experimental/autoqasm/autograph/core/function_wrappers.py create mode 100644 src/braket/experimental/autoqasm/autograph/core/unsupported_features_checker.py create mode 100644 src/braket/experimental/autoqasm/autograph/impl/api_core.py create mode 100644 src/braket/experimental/autoqasm/autograph/lang/directives.py create mode 100644 src/braket/experimental/autoqasm/autograph/logging/ag_logging.py create mode 100644 src/braket/experimental/autoqasm/autograph/operators/variables.py create mode 100644 src/braket/experimental/autoqasm/autograph/operators/variables_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/__init__.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/anno.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/anno_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/ast_util.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/ast_util_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/cache.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/cache_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/cfg.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/cfg_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/common_transformers/__init__.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/common_transformers/anf.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/common_transformers/anf_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/error_utils.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/error_utils_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/errors.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/gast_util.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/inspect_utils.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/inspect_utils_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/inspect_utils_test.sh create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/loader.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/loader_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/naming.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/naming_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/origin_info.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/origin_info_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/parser.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/parser_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/pretty_printer.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/pretty_printer_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/qual_names.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/qual_names_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/__init__.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/activity.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/activity_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/annos.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness_py3_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions_py3_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_fndefs.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_fndefs_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/type_inference.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/type_inference_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/templates.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/templates_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/testing/basic_definitions.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/testing/codegen.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/testing/decorators.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/transformer.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/transformer_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/transpiler.py create mode 100644 src/braket/experimental/autoqasm/autograph/pyct/transpiler_test.py create mode 100644 src/braket/experimental/autoqasm/autograph/tf_utils/tf_decorator.py create mode 100644 src/braket/experimental/autoqasm/autograph/tf_utils/tf_export.py create mode 100644 src/braket/experimental/autoqasm/autograph/tf_utils/tf_stack.py create mode 100644 src/braket/experimental/autoqasm/autograph/tf_utils/traceback_utils.py create mode 100644 src/braket/experimental/autoqasm/constants.py create mode 100644 src/braket/experimental/autoqasm/converters/__init__.py create mode 100644 src/braket/experimental/autoqasm/converters/assignments.py create mode 100644 src/braket/experimental/autoqasm/errors.py create mode 100644 src/braket/experimental/autoqasm/gates/__init__.py create mode 100644 src/braket/experimental/autoqasm/gates/gates.py create mode 100644 src/braket/experimental/autoqasm/gates/measurements.py create mode 100644 src/braket/experimental/autoqasm/gates/qubits.py create mode 100644 src/braket/experimental/autoqasm/operators/__init__.py create mode 100644 src/braket/experimental/autoqasm/operators/assignments.py create mode 100644 src/braket/experimental/autoqasm/operators/conditional_expressions.py create mode 100644 src/braket/experimental/autoqasm/operators/control_flow.py create mode 100644 src/braket/experimental/autoqasm/operators/data_structures.py create mode 100644 src/braket/experimental/autoqasm/operators/logical.py create mode 100644 src/braket/experimental/autoqasm/operators/slices.py create mode 100644 src/braket/experimental/autoqasm/program/__init__.py create mode 100644 src/braket/experimental/autoqasm/program/pragmas.py create mode 100644 src/braket/experimental/autoqasm/program/program.py create mode 100644 src/braket/experimental/autoqasm/transpiler/__init__.py create mode 100644 src/braket/experimental/autoqasm/transpiler/transpiler.py create mode 100644 src/braket/experimental/autoqasm/types/__init__.py create mode 100644 src/braket/experimental/autoqasm/types/py_to_oqpy.py create mode 100644 src/braket/experimental/autoqasm/types/types.py create mode 100644 test/unit_tests/braket/experimental/autoqasm/conftest.py create mode 100644 test/unit_tests/braket/experimental/autoqasm/mock_transpiler.py create mode 100644 test/unit_tests/braket/experimental/autoqasm/test_api.py create mode 100644 test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py create mode 100644 test/unit_tests/braket/experimental/autoqasm/test_converters.py create mode 100644 test/unit_tests/braket/experimental/autoqasm/test_operators.py create mode 100644 test/unit_tests/braket/experimental/autoqasm/test_pragmas.py create mode 100644 test/unit_tests/braket/experimental/autoqasm/test_program.py create mode 100644 test/unit_tests/braket/experimental/autoqasm/test_types.py diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index 51a56630..90d9021d 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -24,5 +24,5 @@ jobs: pip install -e .[test] - name: Run code format checks run: | - black --check . - flake8 --enable-extensions=BCS src + black --check . --extend-exclude=src/braket/experimental/autoqasm/autograph + flake8 --enable-extensions=BCS src/braket/experimental/autoqasm diff --git a/setup.cfg b/setup.cfg index 432fec80..f5bc4d24 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,6 +12,7 @@ testpaths = test/unit_tests line_length = 100 multi_line_output = 3 include_trailing_comma = true +skip_glob = src/braket/experimental/autoqasm/autograph/* [flake8] ignore = @@ -32,6 +33,7 @@ exclude = .git bin build + src/braket/experimental/autoqasm/autograph venv rst-roles = # Python programming language: diff --git a/setup.py b/setup.py index c71f8cc9..7c97788c 100644 --- a/setup.py +++ b/setup.py @@ -28,8 +28,8 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.17.0", - "amazon-braket-default-simulator>=1.15.0", - "oqpy~=0.1.1", + "amazon-braket-default-simulator @ git+https://github.com/aws/amazon-braket-default-simulator-python.git@9b0a2a7c6a9b8a580ddc04f3d1a048dc47fac374#egg=amazon-braket-default-simulator", # mcm-sim branch # noqa E501 + "oqpy @ git+https://github.com/ajberdy/oqpy.git@7e5885af6193009265c8195dad7553db02bdfd96#egg=oqpy", # qubit-array branch # noqa E501 "setuptools", "backoff", "boltons", @@ -40,6 +40,9 @@ "openpulse", "openqasm3", "sympy", + "astunparse", + "gast", + "termcolor", ], extras_require={ "test": [ @@ -59,7 +62,7 @@ "sphinx-rtd-theme", "sphinxcontrib-apidoc", "tox", - ] + ], }, include_package_data=True, url="https://github.com/aws/amazon-braket-sdk-python", diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 7223ff6b..11623e31 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -14,6 +14,7 @@ from abc import ABC, abstractmethod from typing import Dict, List, Optional, Union +import braket.experimental.autoqasm as aq from braket.annealing.problem import Problem from braket.circuits import Circuit from braket.tasks.quantum_task import QuantumTask @@ -35,7 +36,7 @@ def __init__(self, name: str, status: str): @abstractmethod def run( self, - task_specification: Union[Circuit, Problem], + task_specification: Union[Circuit, Problem, aq.Program], shots: Optional[int], inputs: Optional[Dict[str, float]], *args, @@ -45,7 +46,7 @@ def run( or an annealing problem. Args: - task_specification (Union[Circuit, Problem]): Specification of a task + task_specification (Union[Circuit, Problem, Program]): Specification of a task to run on device. shots (Optional[int]): The number of times to run the task on the device. Default is `None`. @@ -61,8 +62,8 @@ def run( def run_batch( self, task_specifications: Union[ - Union[Circuit, Problem], - List[Union[Circuit, Problem]], + Union[Circuit, Problem, aq.Program], + List[Union[Circuit, Problem, aq.Program]], ], shots: Optional[int], max_parallel: Optional[int], @@ -73,7 +74,7 @@ def run_batch( """Executes a batch of tasks in parallel Args: - task_specifications (Union[Union[Circuit, Problem], List[Union[Circuit, Problem]]]): + task_specifications (Union[Union[Circuit, Problem, Program], List[Union[Circuit, Problem, Program]]]): # noqa E501 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 tasks to run in parallel. diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 8c97193a..9efb10f7 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -21,6 +21,7 @@ import pkg_resources +import braket.experimental.autoqasm as aq from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing.problem import Problem from braket.circuits import Circuit @@ -31,6 +32,7 @@ from braket.ir.ahs import Program as AHSProgram from braket.ir.openqasm import Program from braket.simulator import BraketSimulator +from braket.task_result import AdditionalMetadata, TaskMetadata from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult from braket.tasks.analog_hamiltonian_simulation_quantum_task_result import ( AnalogHamiltonianSimulationQuantumTaskResult, @@ -66,7 +68,9 @@ def __init__(self, backend: Union[str, BraketSimulator] = "default"): def run( self, - task_specification: Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], + task_specification: Union[ + Circuit, Problem, Program, AnalogHamiltonianSimulation, aq.Program + ], shots: int = 0, inputs: Optional[Dict[str, float]] = None, *args, @@ -75,7 +79,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, Program, AnalogHamiltonianSimulation, Program]): # noqa E501 The 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 @@ -104,8 +108,8 @@ def run( def run_batch( self, task_specifications: Union[ - Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], - List[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]], + Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, aq.Program], + List[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, aq.Program]], ], shots: Optional[int] = 0, max_parallel: Optional[int] = None, @@ -116,7 +120,7 @@ def run_batch( """Executes a batch of 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, Program], List[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, Program]]]): # noqa E501 Single instance or list of task specification. shots (Optional[int]): The number of times to run the task. Default: 0. @@ -199,12 +203,14 @@ def registered_backends() -> Set[str]: def _run_internal_wrap( self, - task_specification: Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], + task_specification: Union[ + Circuit, Problem, Program, AnalogHamiltonianSimulation, aq.Program + ], shots: Optional[int] = None, inputs: Optional[Dict[str, float]] = None, *args, **kwargs, - ) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: # pragma: no cover + ) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: """Wraps _run_interal for pickle dump""" return self._run_internal(task_specification, shots, inputs=inputs, *args, **kwargs) @@ -230,7 +236,7 @@ def _(self, backend_impl: BraketSimulator): def _run_internal( self, task_specification: Union[ - Circuit, Problem, Program, AnalogHamiltonianSimulation, AHSProgram + Circuit, Problem, Program, AnalogHamiltonianSimulation, AHSProgram, aq.Program ], shots: Optional[int] = None, *args, @@ -295,6 +301,26 @@ def _( results = simulator.run(program, shots, *args, **kwargs) return GateModelQuantumTaskResult.from_object(results) + @_run_internal.register + def _( + self, + program: aq.Program, + shots: Optional[int] = None, + inputs: Optional[Dict[str, float]] = None, + *args, + **kwargs, + ): + simulator = self._delegate + if DeviceActionType.OPENQASM not in simulator.properties.action: + raise NotImplementedError(f"{type(simulator)} does not support OpenQASM programs") + program = Program(source=program.to_ir(ir_type=IRType.OPENQASM)) + results = simulator.run(program, shots, *args, **kwargs) + return GateModelQuantumTaskResult( + task_metadata=TaskMetadata.construct(id=""), + additional_metadata=AdditionalMetadata.construct(), + measurements=results, + ) + @_run_internal.register def _( self, diff --git a/src/braket/experimental/__init__.py b/src/braket/experimental/__init__.py new file mode 100644 index 00000000..89ff77e6 --- /dev/null +++ b/src/braket/experimental/__init__.py @@ -0,0 +1,18 @@ +# 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. + +"""This module implements experimental features of the Amazon Braket SDK. +Features in this module may be changed, removed, or deprecated without notice. +""" + +from . import autoqasm # noqa: F401 diff --git a/src/braket/experimental/autoqasm/README.md b/src/braket/experimental/autoqasm/README.md new file mode 100644 index 00000000..ce909d2d --- /dev/null +++ b/src/braket/experimental/autoqasm/README.md @@ -0,0 +1,137 @@ +# AutoQASM + +**AutoQASM is not an officially supported AWS product.** + +This experimental module offers a new quantum-imperative programming experience embedded in Python +for developing quantum programs. + +All of the code in the `experimental` module is _experimental_ software. We may change, remove, or +deprecate parts of the AutoQASM API without notice. The name AutoQASM is a working title and is +also subject to change. The name is inspired by the +[AutoGraph module of TensorFlow](https://www.tensorflow.org/api_docs/python/tf/autograph), +which we have used as a foundation for this project. + +For a fully supported quantum developer experience, +please continue to use the rest of the Amazon Braket Python SDK by following +[these instructions](https://github.com/aws/amazon-braket-sdk-python#installing-the-amazon-braket-python-sdk). +If you are interested in our active development efforts, and you are not +afraid of a few bugs, please keep on reading! + +## Installation + +AutoQASM is an experimental module and is not yet part of the released Amazon Braket SDK. +To use AutoQASM, you'll need to install directly from the `feature/autoqasm` branch: +``` +git clone https://github.com/aws/amazon-braket-sdk-python.git +cd amazon-braket-sdk-python +git checkout feature/autoqasm +pip install -e . +``` + +## Quick start + +In this section, we will show how to get started with AutoQASM. AutoQASM allows you to build +quantum programs with a simplified syntax and run the programs on the service. It uses the circuit +model programming paradigm that is also used in the Amazon Braket SDK. + +First, import the following modules and functions: +``` +import braket.experimental.autoqasm as aq +from braket.experimental.autoqasm.gates import h, cnot, measure +``` + +To create a quantum program using the AutoQASM experience, you decorate a function with `@aq.function`. +This allows AutoQASM to hook into the program definition and generate an output format that is accepted +by quantum devices. + +For instance, we can create a Bell state like so: +``` +# A program that generates a maximally entangled state +@aq.function +def bell_state() -> None: + h(0) + cnot(0, 1) +``` + +You can view the output format, which is OpenQASM, by running `bell_state().to_ir()`. + +AutoQASM enables users to use more complicated program constructs with a compact and readable +structure. We can demonstrate this with an active reset program: +``` +# A program that actively resets a qubit back to the ground state +@aq.function +def reset(qubit: int, num_repeats: int) -> None: + for repeats in aq.range(num_repeats): + if measure(qubit): + x(qubit) +``` + +Now that the program takes inputs, you must pass those inputs when you call your function. +In this case, the qubits are indexed by variable, rather than by integer literals, so we +must additionally pass the `num_qubits` keyword argument to AutoQASM. +``` +my_reset_program = reset(qubit=0, num_repeats=3, num_qubits=1) +``` + +AutoQASM can support nested subroutines and complex control flow. You can use the Python runtime +and quantum runtime side-by-side. For the moment, we support only a few quantum operations such as +`h`, `x`, `cnot`, and `measure`. There are rough edges at the moment, but we're actively smoothing +them out! + +The Amazon Braket local simulator supports AutoQASM programs as input. +Let's simulate the `my_reset_program` with the `mcm=True` argument, +which enables simulation of mid-circuit measurements: + +``` +from braket.devices.local_simulator import LocalSimulator + +device = LocalSimulator() +task = device.run(my_reset_program, shots=100, mcm=True) +result = task.result() +``` + +For more example usage of AutoQASM, visit the [example notebooks](../../../../examples/autoqasm). + +## Architecture + +AutoQASM is built on top of the `autograph` component of TensorFlow. A quantum program is +written as a Python function which includes an `@aq.function` decorator. When calling this +decorated function, the user’s Python function is converted into a transformed Python function +by `autograph`. This transformed function is then executed to produce an AutoQASM `Program` +object which can be simulated and/or serialized to OpenQASM. + +The conversion process allows AutoQASM to provide custom handling for native Python control +flow keywords such as `if`, `for`, and `while` and to preserve this control flow in the resulting +quantum program in order to realize functionality such as classical feedback on mid-circuit +measurement, efficient representation of loops, and modularity of subroutine definitions. + +## Plans + +The AutoQASM project is undergoing rapid development. +The current status and future plans are tracked in +the [AutoQASM GitHub project](https://github.com/orgs/amazon-braket/projects/2/). + +## Contributing and sharing feedback + +We welcome feature requests, bug reports, or +general feedback, which you can share with us by +[opening up an issue](https://github.com/aws/amazon-braket-sdk-python/issues/new/choose). We also +welcome pull requests, examples, and documentation -- please open an issue describing your work +when you get started, or comment on an existing issue with your intentions. Pull requests should be +targeted to the feature/autoqasm branch of the https://github.com/aws/amazon-braket-sdk-python +repository. For more details on contributing to the Amazon Braket SDK, please read the +[contributing guidelines](../../../../CONTRIBUTING.md). + +For questions, you can get help via the Quantum Technologies section of +[AWS RePost](https://repost.aws/topics/TAxin6L9GYR5a3NElq8AHIqQ/quantum-technologies). +Please tag your question with "Amazon Braket" and mention AutoQASM in the question title. + +## Tests + +To run only AutoQASM tests (and skip the rest of the unit tests), run: +``` +tox -e unit-tests -- test/unit_test/braket/experimental/autoqasm +``` + +Note that you may first need to run `pip install -e .[test]`. More information on running tests +can be found in the [top-level README](../../../../README.md). diff --git a/src/braket/experimental/autoqasm/__init__.py b/src/braket/experimental/autoqasm/__init__.py new file mode 100644 index 00000000..5e7d1fc6 --- /dev/null +++ b/src/braket/experimental/autoqasm/__init__.py @@ -0,0 +1,59 @@ +# 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. + +"""AutoQASM provides a native Python programming experience for building complex quantum programs +and running them on simulators and quantum hardware using Amazon Braket. + +The basic usage of AutoQASM is as follows: + + import braket.experimental.autoqasm as aq + from braket.experimental.autoqasm.gates import h, cnot, measure + + @aq.function + def my_program(): + h(0) + cnot(0, 1) + result = measure([0, 1]) + return result + + program = my_program() + print(program.to_ir()) + +The Python code above outputs the following OpenQASM program: + + OPENQASM 3.0; + qubit[2] __qubits__; + h __qubits__[0]; + cnot __qubits__[0], __qubits__[1]; + bit[2] result; + result[0] = measure __qubits__[0]; + result[1] = measure __qubits__[1]; +""" + +from oqpy import BitVar # noqa: F401 +from oqpy import FloatVar # noqa: F401 +from oqpy import IntVar # noqa: F401 + +from . import api, constants, gates, operators, types # noqa: F401 +from .api import function # noqa: F401 +from .gates import QubitIdentifier # noqa: F401 +from .program import ( # noqa: F401 + Program, + ProgramConversionContext, + Verbatim, + build_program, + get_program_conversion_context, + in_active_program_conversion_context, +) +from .transpiler import transpiler # noqa: F401 +from .types import qasm_range as range # noqa: F401 diff --git a/src/braket/experimental/autoqasm/api/__init__.py b/src/braket/experimental/autoqasm/api/__init__.py new file mode 100644 index 00000000..564d4dcc --- /dev/null +++ b/src/braket/experimental/autoqasm/api/__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. + +"""This module implements the decorator API for generating programs using AutoQASM.""" + +from .api import function # noqa: F401 diff --git a/src/braket/experimental/autoqasm/api/api.py b/src/braket/experimental/autoqasm/api/api.py new file mode 100644 index 00000000..4916770f --- /dev/null +++ b/src/braket/experimental/autoqasm/api/api.py @@ -0,0 +1,402 @@ +# 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 copy +import functools +import inspect +from types import FunctionType +from typing import Any, Callable, Dict, List, Optional, Tuple + +import openqasm3.ast as qasm_ast +import oqpy.base + +import braket.experimental.autoqasm.constants as aq_constants +import braket.experimental.autoqasm.program as aq_program +import braket.experimental.autoqasm.transpiler as aq_transpiler +import braket.experimental.autoqasm.types as aq_types +from braket.experimental.autoqasm import errors +from braket.experimental.autoqasm.autograph.core import ag_ctx, converter +from braket.experimental.autoqasm.autograph.impl.api_core import ( + autograph_artifact, + is_autograph_artifact, +) +from braket.experimental.autoqasm.autograph.tf_utils import tf_decorator + + +def function(f: Callable) -> Callable[[Any], aq_program.Program]: + """Decorator that converts a function into a callable that returns + a Program object containing the quantum program. + + The decorator re-converts the target function whenever the decorated + function is called, and a new Program object is returned. + + Args: + f (Callable): The target function to be converted which represents + the entry point of the quantum program. + + Returns: + Callable[[Any], Program]: A callable which returns the converted + quantum program when called. + """ + if is_autograph_artifact(f): + return f + + # Update documentation with user configuration + if f.__doc__ is None: + f.__doc__ = "" + f.__doc__ += f""" + +Keyword Args: + {aq_program.ProgramOptions.NUM_QUBITS.value} (int): Configuration to set the total number of + qubits to declare in the program. +""" + + f_wrapper = f + decorators, f = tf_decorator.unwrap(f) + + wrapper_factory = convert_wrapper( + recursive=False, + optional_features=( + converter.Feature.LISTS, + converter.Feature.EQUALITY_OPERATORS, + ), + ) + wrapper = wrapper_factory(f) + + if decorators: + wrapper = tf_decorator.rewrap(f_wrapper, f, wrapper) + + return autograph_artifact(wrapper) + + +def convert_wrapper( + recursive: bool = False, + optional_features: Optional[Tuple[converter.Feature]] = None, + user_requested: bool = True, + conversion_ctx: Optional[ag_ctx.ControlStatusCtx] = ag_ctx.NullCtx(), +) -> Callable: + """Generates a factory which does the conversion of a function into a callable + that returns a Program object containing the quantum program. + + Args: + recursive (bool): whether to recursively convert any functions or classes + that the converted function may use. Defaults to False. + optional_features (Optional[Tuple[Feature]]): allows toggling + optional or experimental features. When set to None, only the core features are + enabled. Defaults to None. + user_requested (bool): whether this is a function that the user explicitly + asked to be converted. See ConversionOptions.user_requested. Defaults to True. + conversion_ctx (Optional[ControlStatusCtx]): the Autograph context in + which `f` is used. Defaults to ag_ctx.NullCtx(). + + Returns: + Callable: a decorator that converts the given function into an equivalent + function that uses AutoQASM operations. + """ + + def _decorator(f: Callable) -> Callable[[Any], aq_program.Program]: + """Decorator implementation.""" + + def _wrapper(*args, **kwargs) -> aq_program.Program: + """Wrapper that calls the converted version of f.""" + options = converter.ConversionOptions( + recursive=recursive, + user_requested=user_requested, + optional_features=optional_features, + ) + return _convert(f, conversion_ctx, options, args, kwargs) + + if inspect.isfunction(f) or inspect.ismethod(f): + _wrapper = functools.update_wrapper(_wrapper, f) + + decorated_wrapper = tf_decorator.make_decorator(f, _wrapper) + return autograph_artifact(decorated_wrapper) + + return _decorator + + +def _convert( + f: Callable, + conversion_ctx: ag_ctx.ControlStatusCtx, + options: converter.ConversionOptions, + args: List[Any], + kwargs: Dict[str, Any], +) -> aq_program.Program: + """Convert the initial callable `f` into a full AutoQASM program `program`. + + This function adds error handling around `_convert_program_as_subroutine` + and `_convert_program_as_main`, where the conversion logic itself lives. + + Args: + f (Callable): The function to be converted. + conversion_ctx (ControlStatusCtx): the Autograph context in which `f` is used. + options (converter.ConversionOptions): Converter options. + args (List[Any]): Arguments passed to the program when called. + kwargs (Dict[str, Any]): Keyword arguments passed to the program when called. + + Returns: + Program: The converted program. + """ + # User-supplied kwargs need to be processed and removed + # _before_ the program is processed + user_config = _process_user_config(kwargs) + + # We will convert this function as a subroutine if we are already inside an + # existing conversion process (i.e., this is a subroutine call). + convert_as_subroutine = aq_program.in_active_program_conversion_context() + + with aq_program.build_program(user_config) as program_conversion_context: + try: + with conversion_ctx: + if convert_as_subroutine: + _convert_program_as_subroutine( + f, program_conversion_context, options, args, kwargs + ) + else: + _convert_program_as_main(f, program_conversion_context, options, args, kwargs) + except Exception as e: + if isinstance(e, errors.AutoQasmError): + raise + elif hasattr(e, "ag_error_metadata"): + raise e.ag_error_metadata.to_exception(e) + else: + raise + + return program_conversion_context.make_program() + + +def _process_user_config(kwargs: Dict[str, Any]) -> aq_program.UserConfig: + """ + Process the user supplied kwargs and return a standardized user config dictionary. + + Args: + kwargs (Dict[str, Any]): Keyword arguments passed to the program when called. + + Returns: + UserConfig: Processed user-config keyword arguments. + """ + return aq_program.UserConfig( + num_qubits=kwargs.pop(aq_program.ProgramOptions.NUM_QUBITS.value, None) + ) + + +def _convert_program_as_main( + f: Callable, + program_conversion_context: aq_program.ProgramConversionContext, + options: converter.ConversionOptions, + args: List[Any], + kwargs: Dict[str, Any], +) -> None: + """Convert the initial callable `f` into a full AutoQASM program `program`. + Puts the contents of `f` at the global level of the program, rather than + putting it into a subroutine as done in `_convert_program_as_subroutine`. + + Some program pre- and post-processing occurs here, such as adding a qubit + declaration and adding the subroutine invocation at the top level. + + Args: + f (Callable): The function to be converted. + program_conversion_context (ProgramConversionContext): The program being converted. + options (converter.ConversionOptions): Converter options. + args (List[Any]): Arguments passed to the program when called. + kwargs (Dict[str, Any]): Keyword arguments passed to the program when called. + """ + # Process the program + aq_transpiler.converted_call(f, args, kwargs, options=options) + + # Modify program to add qubit declaration if necessary + _add_qubit_declaration(program_conversion_context) + + +def _convert_program_as_subroutine( + f: Callable, + program_conversion_context: aq_program.ProgramConversionContext, + options: converter.ConversionOptions, + args: List[Any], + kwargs: Dict[str, Any], +) -> None: + """Convert the initial callable `f` into a full AutoQASM program `program`. + The contents of `f` are converted into a subroutine in the program. + + Some program pre- and post-processing occurs here, such as adding a qubit + declaration and adding the subroutine invocation at the top level. + + Args: + f (Callable): The function to be converted. + program_conversion_context (ProgramConversionContext): The program being converted. + options (converter.ConversionOptions): Converter options. + args (List[Any]): Arguments passed to the program when called. + kwargs (Dict[str, Any]): Keyword arguments passed to the program when called. + """ + oqpy_program = program_conversion_context.get_oqpy_program() + + if f not in program_conversion_context.subroutines_processing: + # Mark that we are starting to process this function to short-circuit recursion + program_conversion_context.subroutines_processing.add(f) + + # Convert the function via autograph into an oqpy subroutine + # NOTE: Process a clone of the function so that we don't modify the original object + oqpy_sub = oqpy.subroutine(_wrap_for_oqpy_subroutine(_clone_function(f), options)) + + # Process the program + subroutine_function_call = oqpy_sub(oqpy_program, *args, **kwargs) + + # Mark that we are finished processing this function + program_conversion_context.subroutines_processing.remove(f) + else: + # Convert the function via autograph into an oqpy subroutine + # NOTE: Recursive call; process a dummy version of the function instead + oqpy_sub = oqpy.subroutine(_wrap_for_oqpy_subroutine(_dummy_function(f), options)) + + # Process the program + subroutine_function_call = oqpy_sub(oqpy_program, *args, **kwargs) + + # Add the subroutine invocation to the program + return_instance = _make_return_instance(f, program_conversion_context) + if return_instance is not None: + return_variable = aq_types.wrap_value(return_instance) + oqpy_program.set(return_variable, subroutine_function_call) + else: + function_call = subroutine_function_call.to_ast(oqpy_program) + oqpy_program._add_statement(qasm_ast.ExpressionStatement(function_call)) + + # Add the subroutine definition to the root-level program if necessary + root_oqpy_program = program_conversion_context.oqpy_program_stack[0] + subroutine_name = subroutine_function_call.identifier.name + if ( + subroutine_name not in root_oqpy_program.subroutines + and subroutine_function_call.subroutine_decl is not None + ): + root_oqpy_program._add_subroutine(subroutine_name, subroutine_function_call.subroutine_decl) + + +def _make_return_instance( + f: Callable, program_conversion_context: aq_program.ProgramConversionContext +) -> Any: + annotations = f.__annotations__ + return_type = annotations["return"] if "return" in annotations else None + + return_instance = None + if return_type and issubclass(return_type, oqpy.base.Var): + return_instance = return_type(name=program_conversion_context.next_var_name(return_type)) + elif return_type: + return_instance = return_type() + + return return_instance + + +def _add_qubit_declaration(program_conversion_context: aq_program.ProgramConversionContext) -> None: + """Modify the program to include a global qubit register declaration. + + The number of qubits declared is pulled from either the user config (supplied explicitly + via kwargs when calling the program) or an attempt is made to dynamically determine the total + number of qubits used by inspecting the program. + + Args: + program_conversion_context (ProgramConversionContext): The program conversion context. + """ + root_oqpy_program = program_conversion_context.oqpy_program_stack[0] + + # Return early if the qubit register is already declared + if aq_constants.QUBIT_REGISTER in root_oqpy_program.declared_vars: + return + + # Declare the global qubit register if necessary + user_specified_num_qubits = program_conversion_context.get_declared_qubits() + + if user_specified_num_qubits is not None: + # User-supplied qubit count + root_oqpy_program.declare( + [oqpy.QubitArray(aq_constants.QUBIT_REGISTER, user_specified_num_qubits)], + to_beginning=True, + ) + + else: + # Qubit count from program inspection + qubits = program_conversion_context.qubits + max_qubit_index = qubits[-1] if len(qubits) else None + if max_qubit_index is not None: + root_oqpy_program.declare( + [oqpy.QubitArray(aq_constants.QUBIT_REGISTER, max_qubit_index + 1)], + to_beginning=True, + ) + + +def _clone_function(f_source: Callable) -> Callable: + f_clone = FunctionType( + copy.deepcopy(f_source.__code__), + copy.copy(f_source.__globals__), + copy.deepcopy(f_source.__name__), + copy.deepcopy(f_source.__defaults__), + copy.deepcopy(f_source.__closure__), + ) + setattr(f_clone, "__signature__", copy.deepcopy(inspect.signature(f_source))) + setattr(f_clone, "__annotations__", copy.deepcopy(f_source.__annotations__)) + return f_clone + + +def _dummy_function(f_source: Callable) -> Callable: + return_instance = _make_return_instance(f_source, aq_program.get_program_conversion_context()) + + def f_dummy(*args, **kwargs) -> Any: + return return_instance + + f_dummy.__name__ = copy.deepcopy(f_source.__name__) + f_dummy.__defaults__ = copy.deepcopy(f_source.__defaults__) + setattr(f_dummy, "__signature__", copy.deepcopy(inspect.signature(f_source))) + setattr(f_dummy, "__annotations__", copy.deepcopy(f_source.__annotations__)) + return f_dummy + + +def _wrap_for_oqpy_subroutine(f: Callable, options: converter.ConversionOptions) -> Callable: + """Wraps the given function into a callable expected by oqpy.subroutine. + + oqpy.subroutine requires that the first argument be of type `oqpy.Program`, + which represents the nested Program object which will be built up while + executing the subroutine. + + Args: + f (Callable): The function to be wrapped. + options (converter.ConversionOptions): Converter options. + + Returns: + Callable: The modified function for use with oqpy.subroutine. + """ + + @functools.wraps(f) + def _func(*args, **kwargs) -> Any: + inner_program = args[0] + with aq_program.get_program_conversion_context().push_oqpy_program(inner_program): + return aq_transpiler.converted_call(f, args[1:], kwargs, options=options) + + # Replace the function signature with a new signature where the first + # argument is of type `oqpy.Program`. + sig = inspect.signature(_func) + first_param = inspect.Parameter( + name="inner_program", + kind=inspect._ParameterKind.POSITIONAL_OR_KEYWORD, + annotation=oqpy.Program, + ) + _func.__annotations__[first_param.name] = first_param.annotation + + new_params = [first_param] + for param in sig.parameters.values(): + new_param = inspect.Parameter( + name=param.name, kind=param.kind, annotation=aq_types.map_type(param.annotation) + ) + new_params.append(new_param) + _func.__annotations__[new_param.name] = new_param.annotation + + _func.__signature__ = sig.replace(parameters=new_params) + return _func diff --git a/src/braket/experimental/autoqasm/autograph/ATTRIBUTION.md b/src/braket/experimental/autoqasm/autograph/ATTRIBUTION.md new file mode 100644 index 00000000..a2dbd1cd --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/ATTRIBUTION.md @@ -0,0 +1,263 @@ +# autograph + +The contents of this folder are copied with modification from the TensorFlow project under the Apache 2.0 license. + +** tensorflow; version 2.12.0 -- https://github.com/tensorflow/tensorflow/ + +--- + +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License 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. + +## Some of TensorFlow's code is derived from Caffe, which is subject to the following copyright notice: + +COPYRIGHT + +All contributions by the University of California: + +Copyright (c) 2014, The Regents of the University of California (Regents) +All rights reserved. + +All other contributions: + +Copyright (c) 2014, the respective contributors +All rights reserved. + +Caffe uses a shared copyright model: each contributor holds copyright over +their contributions to Caffe. The project versioning records all such +contribution and copyright details. If a contributor wants to further mark +their specific copyright on a particular contribution, they should indicate +their copyright solely in the commit message of the change when it is +committed. + +LICENSE + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +CONTRIBUTION AGREEMENT + +By contributing to the BVLC/caffe repository through pull-request, comment, +or otherwise, the contributor releases their content to the +license and copyright terms herein. + +## For TensorFlow see also this required NOTICE: + +Copyright 2016 The TensorFlow Authors. All Rights Reserved. diff --git a/src/braket/experimental/autoqasm/autograph/__init__.py b/src/braket/experimental/autoqasm/autograph/__init__.py new file mode 100644 index 00000000..186d2fc8 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/__init__.py @@ -0,0 +1,55 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Conversion of eager-style Python into TensorFlow graph code. + +NOTE: In TensorFlow 2.0, AutoGraph is automatically applied when using +`tf.function`. This module contains lower-level APIs for advanced use. + +AutoGraph transforms a subset of Python which operates on TensorFlow objects +into equivalent TensorFlow graph code. When executing the graph, it has the same +effect as if you ran the original code in eager mode. +Python code which doesn't operate on TensorFlow objects remains functionally +unchanged, but keep in mind that `tf.function` only executes such code at trace +time, and generally will not be consistent with eager execution. + +For more information, see the +[AutoGraph reference documentation](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/autograph/g3doc/reference/index.md), +and the [tf.function guide](https://www.tensorflow.org/guide/function#autograph_transformations). +""" + +# TODO(mdan): Bring only the relevant symbols to the top level. +from braket.experimental.autoqasm.autograph import logging +from braket.experimental.autoqasm.autograph.core.converter import ConversionOptions +from braket.experimental.autoqasm.autograph.core.converter import Feature +from braket.experimental.autoqasm.autograph.impl.api_core import AutoGraphError +from braket.experimental.autoqasm.autograph.impl.api_core import StackTraceMapper +from braket.experimental.autoqasm.autograph.lang.directives import set_element_type +from braket.experimental.autoqasm.autograph.lang.directives import set_loop_options +from braket.experimental.autoqasm.autograph.logging import ag_logging + +# TODO(mdan): Revisit this list once we finalize the generated code mechanism. +_allowed_symbols = [ + # Main API + 'AutoGraphError', + 'ConversionOptions', + 'Feature', + 'StackTraceMapper', + # Python language "extensions" + 'set_element_type', + 'set_loop_options', + # Logging + 'logging', + 'ag_logging', +] diff --git a/src/braket/experimental/autoqasm/autograph/converters/__init__.py b/src/braket/experimental/autoqasm/autograph/converters/__init__.py new file mode 100644 index 00000000..7037b3ce --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/converters/__init__.py @@ -0,0 +1,28 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Code converters used by Autograph.""" + +# Naming conventions: +# * each converter should specialize on a single idiom; be consistent with +# the Python reference for naming +# * all converters inherit core.converter.Base +# * module names describe the idiom that the converter covers, plural +# * the converter class is named consistent with the module, singular and +# includes the word Transformer +# +# Example: +# +# lists.py +# class ListTransformer(converter.Base) diff --git a/src/braket/experimental/autoqasm/autograph/converters/asserts.py b/src/braket/experimental/autoqasm/autograph/converters/asserts.py new file mode 100644 index 00000000..3ea9bb49 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/converters/asserts.py @@ -0,0 +1,48 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Converts assert statements to their corresponding TF calls.""" + +import gast + +from braket.experimental.autoqasm.autograph.core import converter +from braket.experimental.autoqasm.autograph.pyct import templates + + +class AssertTransformer(converter.Base): + """Transforms Assert nodes to Call so they can be handled as functions.""" + + def visit_Assert(self, node): + self.generic_visit(node) + + # Note: The lone tf.Assert call will be wrapped with control_dependencies + # by side_effect_guards. + template = """ + ag__.assert_stmt(test, lambda: msg) + """ + + if node.msg is None: + return templates.replace( + template, + test=node.test, + msg=gast.Constant('Assertion error', kind=None)) + elif isinstance(node.msg, gast.Constant): + return templates.replace(template, test=node.test, msg=node.msg) + else: + raise NotImplementedError('can only convert string messages for now.') + + +def transform(node, ctx): + node = AssertTransformer(ctx).visit(node) + return node diff --git a/src/braket/experimental/autoqasm/autograph/converters/break_statements.py b/src/braket/experimental/autoqasm/autograph/converters/break_statements.py new file mode 100644 index 00000000..96945df6 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/converters/break_statements.py @@ -0,0 +1,185 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Lowers break statements to conditionals.""" + +from braket.experimental.autoqasm.autograph.core import converter +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import qual_names +from braket.experimental.autoqasm.autograph.pyct import templates +from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity +from braket.experimental.autoqasm.autograph.pyct.static_analysis.annos import NodeAnno + + +class _Break(object): + + def __init__(self): + self.used = False + self.control_var_name = None + + def __repr__(self): + return 'used: %s, var: %s' % (self.used, self.control_var_name) + + +class BreakTransformer(converter.Base): + """Canonicalizes break statements into additional conditionals.""" + + def visit_Break(self, node): + self.state[_Break].used = True + var_name = self.state[_Break].control_var_name + # TODO(mdan): This will fail when expanded inside a top-level else block. + template = """ + var_name = True + continue + """ + return templates.replace(template, var_name=var_name) + + def _guard_if_present(self, block, var_name): + """Prevents the block from executing if var_name is set.""" + if not block: + return block + + template = """ + if not var_name: + block + """ + node = templates.replace( + template, + var_name=var_name, + block=block) + return node + + def _process_body(self, nodes, break_var): + self.state[_Break].enter() + self.state[_Break].control_var_name = break_var + nodes = self.visit_block(nodes) + break_used = self.state[_Break].used + self.state[_Break].exit() + return nodes, break_used + + def visit_While(self, node): + original_node = node + scope = anno.getanno(node, NodeAnno.BODY_SCOPE) + break_var = self.ctx.namer.new_symbol('break_', scope.referenced) + + node.test = self.visit(node.test) + node.body, break_used = self._process_body(node.body, break_var) + # A break in the else clause applies to the containing scope. + node.orelse = self.visit_block(node.orelse) + + if not break_used: + template = """ + while test: + body + orelse + """ + node = templates.replace( + template, test=node.test, body=node.body, orelse=node.orelse) + + new_while_node = node[0] + anno.copyanno(original_node, new_while_node, anno.Basic.DIRECTIVES) + + return node + + # Python's else clause only triggers if the loop exited cleanly (e.g. + # break did not trigger). + guarded_orelse = self._guard_if_present(node.orelse, break_var) + + template = """ + var_name = False + while not var_name and test: + body + orelse + """ + node = templates.replace( + template, + var_name=break_var, + test=node.test, + body=node.body, + orelse=guarded_orelse) + + new_while_node = node[1] + anno.copyanno(original_node, new_while_node, anno.Basic.DIRECTIVES) + + return node + + def visit_For(self, node): + original_node = node + scope = anno.getanno(node, NodeAnno.BODY_SCOPE) + break_var = self.ctx.namer.new_symbol('break_', scope.referenced) + + node.target = self.visit(node.target) + node.iter = self.visit(node.iter) + node.body, break_used = self._process_body(node.body, break_var) + # A break in the else clause applies to the containing scope. + node.orelse = self.visit_block(node.orelse) + + if not break_used: + template = """ + for target in iter_: + body + orelse + """ + node = templates.replace( + template, + iter_=node.iter, + target=node.target, + body=node.body, + orelse=node.orelse) + + new_for_node = node[0] + anno.copyanno(original_node, new_for_node, anno.Basic.EXTRA_LOOP_TEST) + anno.copyanno(original_node, new_for_node, anno.Basic.DIRECTIVES) + + return node + + # Python's else clause only triggers if the loop exited cleanly (e.g. + # break did not trigger). + guarded_orelse = self._guard_if_present(node.orelse, break_var) + extra_test = templates.replace_as_expression( + 'not var_name', var_name=break_var) + + # The extra test is hidden in the AST, which will confuse the static + # analysis. To mitigate that, we insert a no-op statement that ensures + # the control variable is marked as used. + # TODO(mdan): Use a marker instead, e.g. ag__.condition_loop_on(var_name) + template = """ + var_name = False + for target in iter_: + (var_name,) + body + orelse + """ + node = templates.replace( + template, + var_name=break_var, + iter_=node.iter, + target=node.target, + body=node.body, + orelse=guarded_orelse) + + new_for_node = node[1] + anno.setanno(new_for_node, anno.Basic.EXTRA_LOOP_TEST, extra_test) + anno.copyanno(original_node, new_for_node, anno.Basic.DIRECTIVES) + + return node + + +def transform(node, ctx): + node = qual_names.resolve(node) + node = activity.resolve(node, ctx, None) + + transformer = BreakTransformer(ctx) + node = transformer.visit(node) + return node diff --git a/src/braket/experimental/autoqasm/autograph/converters/call_trees.py b/src/braket/experimental/autoqasm/autograph/converters/call_trees.py new file mode 100644 index 00000000..d2ae58e5 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/converters/call_trees.py @@ -0,0 +1,221 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Handles function calls, by generating compiled function names and calls. + +Note: this transformer does not rename the top level object being converted; +that is the caller's responsibility. + +Requires function_scopes. +""" + +import gast + +from braket.experimental.autoqasm.autograph.core import converter +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import qual_names +from braket.experimental.autoqasm.autograph.pyct import templates +from braket.experimental.autoqasm.autograph.logging import ag_logging + + +# TODO(mdan): Rename to FunctionCallsTransformer. + + +class _Function(object): + + no_root = True + + def __init__(self): + self.context_name = None + + +set_trace_warned = False + + +class _ArgTemplateBuilder(object): + """Constructs a tuple representing the positional arguments in a call. + + Example (yes, it's legal Python 3): + + f(*args1, b, *args2, c, d) -> args1 + (b,) + args2 + (c, d) + """ + + def __init__(self): + self._arg_accumulator = [] + self._argspec = [] + self._finalized = False + + def _consume_args(self): + if self._arg_accumulator: + self._argspec.append( + gast.Tuple(elts=self._arg_accumulator, ctx=gast.Load())) + self._arg_accumulator = [] + + def add_arg(self, a): + self._arg_accumulator.append(a) + + def add_stararg(self, a): + self._consume_args() + self._argspec.append( + gast.Call( + gast.Name( + 'tuple', ctx=gast.Load(), annotation=None, type_comment=None), + args=[a], + keywords=())) + + def finalize(self): + self._consume_args() + self._finalized = True + + def to_ast(self): + assert self._finalized + if self._argspec: + result = self._argspec[0] + for i in range(1, len(self._argspec)): + result = gast.BinOp(result, gast.Add(), self._argspec[i]) + return result + return gast.Tuple([], gast.Load()) + + +class CallTreeTransformer(converter.Base): + """Transforms the call tree by renaming transformed symbols.""" + + def visit_Lambda(self, node): + if not anno.hasanno(node, 'function_context_name'): + # Lambda functions created during the conversion process have no + # context manager. + return self.generic_visit(node) + with self.state[_Function] as fn_scope: + fn_scope.context_name = anno.getanno(node, 'function_context_name') + return self.generic_visit(node) + + def visit_FunctionDef(self, node): + # Decorators and arg defaults are part of the outer scope. + node.decorator_list = self.visit_block(node.decorator_list) + node.args.defaults = self.visit_block(node.args.defaults) + for i, d in enumerate(node.args.kw_defaults): + if d is not None: + node.args.kw_defaults[i] = self.visit(d) + with self.state[_Function] as fn_scope: + # Note: if the conversion process ever creates helper functions, this + # assumption will no longer hold. + assert anno.hasanno(node, 'function_context_name'), ( + 'The function_scopes converter always creates a scope for functions.') + fn_scope.context_name = anno.getanno(node, 'function_context_name') + node.body = self.visit_block(node.body) + if node.returns: + node.returns = self.visit(node.returns) + return node + + def visit_With(self, node): + # Context manager calls (in node.items) are not converted. + node.body = self.visit_block(node.body) + return node + + def _args_to_tuple(self, node): + """Ties together all positional and *arg arguments in a single tuple.""" + # TODO(mdan): We could rewrite this to just a call to tuple(). Maybe better? + # For example for + # f(a, b, *args) + # instead of writing: + # (a, b) + args + # just write this? + # tuple(a, b, *args) + builder = _ArgTemplateBuilder() + for a in node.args: + if isinstance(a, gast.Starred): + builder.add_stararg(a.value) + else: + builder.add_arg(a) + builder.finalize() + return builder.to_ast() + + def _kwargs_to_dict(self, node): + """Ties together all keyword and **kwarg arguments in a single dict.""" + if node.keywords: + return gast.Call( + gast.Name( + 'dict', ctx=gast.Load(), annotation=None, type_comment=None), + args=(), + keywords=node.keywords) + else: + return parser.parse_expression('None') + + def visit_Call(self, node): + full_name = str(anno.getanno(node.func, anno.Basic.QN, default='')) + function_context_name = self.state[_Function].context_name + node = self.generic_visit(node) + + # TODO(mdan): Refactor converted_call as a 'Call' operator. + + # Calls to the internal 'ag__' module are never converted (though their + # arguments might be). + if full_name.startswith('ag__.'): + return node + + # Calls to the function context manager (inserted by function_scopes) are + # also safe. + if full_name.startswith(function_context_name + '.'): + return node + + # Calls to pdb.set_trace or ipdb.set_trace are never converted. We don't use + # the normal mechanisms to bypass these literals because they are sensitive + # to the frame they are being called from. + # TODO(mdan): Generalize this to a "static allowlist" config. + if full_name in ('pdb.set_trace', 'ipdb.set_trace', 'breakpoint'): + global set_trace_warned + if not set_trace_warned: + # TODO(mdan): Update and shorten once available on tensorflow.org. + ag_logging.warning( + 'Detected `pdb.set_trace()` in user code. The code' + ' generated by AutoGraph is not optimized for step-by-step' + ' debugging. See https://github.com/tensorflow/tensorflow/' + 'blob/master/tensorflow/python/autograph/g3doc/reference/' + 'debugging.md.') + set_trace_warned = True + return node + + if (full_name == 'print' and + not self.ctx.user.options.uses(converter.Feature.BUILTIN_FUNCTIONS)): + return node + + template = """ + ag__.converted_call(func, args, kwargs, function_ctx) + """ + new_call = templates.replace_as_expression( + template, + func=node.func, + args=self._args_to_tuple(node), + kwargs=self._kwargs_to_dict(node), + function_ctx=function_context_name) + + return new_call + + +def transform(node, ctx): + """Transform function call to the compiled counterparts. + + Args: + node: AST + ctx: EntityContext + Returns: + A tuple (node, new_names): + node: The transformed AST + new_names: set(string), containing any newly-generated names + """ + node = qual_names.resolve(node) + + node = CallTreeTransformer(ctx).visit(node) + return node diff --git a/src/braket/experimental/autoqasm/autograph/converters/conditional_expressions.py b/src/braket/experimental/autoqasm/autograph/converters/conditional_expressions.py new file mode 100644 index 00000000..cfd1337c --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/converters/conditional_expressions.py @@ -0,0 +1,46 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Converts the ternary conditional operator.""" + +import gast + +from braket.experimental.autoqasm.autograph.core import converter +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import templates + + +class ConditionalExpressionTransformer(converter.Base): + """Converts conditional expressions to functional form.""" + + def visit_IfExp(self, node): + template = ''' + ag__.if_exp( + test, + lambda: true_expr, + lambda: false_expr, + expr_repr) + ''' + expr_repr = parser.unparse(node.test, include_encoding_marker=False).strip() + return templates.replace_as_expression( + template, + test=node.test, + true_expr=node.body, + false_expr=node.orelse, + expr_repr=gast.Constant(expr_repr, kind=None)) + + +def transform(node, ctx): + node = ConditionalExpressionTransformer(ctx).visit(node) + return node diff --git a/src/braket/experimental/autoqasm/autograph/converters/continue_statements.py b/src/braket/experimental/autoqasm/autograph/converters/continue_statements.py new file mode 100644 index 00000000..0dce9fb2 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/converters/continue_statements.py @@ -0,0 +1,164 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Canonicalizes continue statements by de-sugaring into a control boolean.""" + +from braket.experimental.autoqasm.autograph.core import converter +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import qual_names +from braket.experimental.autoqasm.autograph.pyct import templates +from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity +from braket.experimental.autoqasm.autograph.pyct.static_analysis.annos import NodeAnno + + +class _Continue(object): + + def __init__(self): + self.used = False + self.control_var_name = None + + def __repr__(self): + return '<_Continue(used: {}, var: {})>'.format(self.used, + self.control_var_name) + + +class _Block(object): + """Tracks information about lexical blocks as they are visited in the AST. + + Mainly, this object tracks the creation of block guards that replace + `continue` statements (e.g. `if not continue_:`). + + Attributes: + create_guard_current: bool, whether to create a guard for the current + statement. + create_guard_next: bool, whether to create a guard for the next + statement. + is_loop_type: bool, whether this block is the body of a loop. + """ + + def __init__(self): + self.is_loop_type = False + self.create_guard_current = False + self.create_guard_next = False + + +class ContinueCanonicalizationTransformer(converter.Base): + """Canonicalizes continue statements into additional conditionals.""" + + def visit_Continue(self, node): + self.state[_Continue].used = True + for block in reversed(self.state[_Block].stack): + # See ContinueCanonicalizationTest.test_multiple_continues for an example + # it's necessary to create guards for all enclosing affected blocks, not + # just that of the current block. + block.create_guard_next = True + if block.is_loop_type: + # continue only affects the innermost loop + break + template = """ + var_name = True + """ + return templates.replace( + template, var_name=self.state[_Continue].control_var_name) + + def _postprocess_statement(self, node): + if self.state[_Continue].used: + block = self.state[_Block] + should_wrap_current = block.create_guard_current + # After processing propagate whether to guard the next statement + block.create_guard_current = block.create_guard_next + block.create_guard_next = False + if should_wrap_current: + template = """ + if not var_name: + original_node + """ + cond, = templates.replace( + template, + var_name=self.state[_Continue].control_var_name, + original_node=node) + return cond, cond.body + return node, None + + def _visit_loop_body(self, node, nodes): + self.state[_Continue].enter() + self.state[_Block].enter() + self.state[_Block].is_loop_type = True + scope = anno.getanno(node, NodeAnno.BODY_SCOPE) + continue_var = self.ctx.namer.new_symbol('continue_', scope.referenced) + self.state[_Continue].control_var_name = continue_var + + nodes = self.visit_block(nodes, after_visit=self._postprocess_statement) + + if self.state[_Continue].used: + template = """ + var_name = False + """ + control_var_init = templates.replace(template, var_name=continue_var) + nodes = control_var_init + nodes + + self.state[_Block].exit() + self.state[_Continue].exit() + return nodes + + def _visit_non_loop_body(self, nodes): + self.state[_Block].enter() + nodes = self.visit_block(nodes, after_visit=self._postprocess_statement) + self.state[_Block].exit() + return nodes + + def visit_While(self, node): + node.test = self.visit(node.test) + node.body = self._visit_loop_body(node, node.body) + # A continue in the else clause applies to the containing scope. + node.orelse = self._visit_non_loop_body(node.orelse) + return node + + def visit_For(self, node): + node.target = self.generic_visit(node.target) + node.iter = self.generic_visit(node.iter) + node.body = self._visit_loop_body(node, node.body) + # A continue in the else clause applies to the containing scope. + node.orelse = self._visit_non_loop_body(node.orelse) + return node + + def visit_If(self, node): + node.body = self._visit_non_loop_body(node.body) + node.orelse = self._visit_non_loop_body(node.orelse) + return node + + def visit_With(self, node): + node.items = self.visit_block(node.items) + node.body = self._visit_non_loop_body(node.body) + return node + + def visit_Try(self, node): + node.body = self._visit_non_loop_body(node.body) + node.orelse = self._visit_non_loop_body(node.orelse) + # In Python 3.8 and later continue is allowed in finally blocks + node.finalbody = self._visit_non_loop_body(node.finalbody) + node.handlers = self.visit_block(node.handlers) + return node + + def visit_ExceptHandler(self, node): + node.body = self._visit_non_loop_body(node.body) + return node + + +def transform(node, ctx): + node = qual_names.resolve(node) + node = activity.resolve(node, ctx, None) + + node = ContinueCanonicalizationTransformer(ctx).visit(node) + return node diff --git a/src/braket/experimental/autoqasm/autograph/converters/control_flow.py b/src/braket/experimental/autoqasm/autograph/converters/control_flow.py new file mode 100644 index 00000000..4bb1b9b3 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/converters/control_flow.py @@ -0,0 +1,413 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Handles control flow statements: while, for, if.""" + +import gast + +from braket.experimental.autoqasm.autograph.core import converter +from braket.experimental.autoqasm.autograph.lang import directives +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import cfg +from braket.experimental.autoqasm.autograph.pyct import origin_info +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import qual_names +from braket.experimental.autoqasm.autograph.pyct import templates +from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity +from braket.experimental.autoqasm.autograph.pyct.static_analysis import annos +from braket.experimental.autoqasm.autograph.pyct.static_analysis import liveness +from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_definitions +from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_fndefs + + +class _Function(object): + + scope = None + + +class ControlFlowTransformer(converter.Base): + """Transforms control flow structures like loops an conditionals.""" + + def visit_Lambda(self, node): + with self.state[_Function] as fn: + fn.scope = anno.getanno(node, anno.Static.SCOPE) + return self.generic_visit(node) + + def visit_FunctionDef(self, node): + with self.state[_Function] as fn: + fn.scope = anno.getanno(node, annos.NodeAnno.BODY_SCOPE) + return self.generic_visit(node) + + def _create_nonlocal_declarations(self, vars_): + vars_ = set(vars_) + results = [] + global_vars = self.state[_Function].scope.globals & vars_ + + if global_vars: + results.append(gast.Global([str(v) for v in global_vars])) + + nonlocal_vars = [ + v for v in vars_ if not v.is_composite() and v not in global_vars] + if nonlocal_vars: + results.append(gast.Nonlocal([str(v) for v in nonlocal_vars])) + + return results + + def _create_state_functions( + self, block_vars, nonlocal_declarations, getter_name, setter_name): + if not block_vars: + template = """ + def getter_name(): + return () + def setter_name(block_vars): + pass + """ + return templates.replace( + template, getter_name=getter_name, setter_name=setter_name) + + guarded_block_vars = [] + for v in block_vars: + if v.is_simple(): + guarded_block_vars.append(v) + else: + guarded_block_vars.append( + templates.replace_as_expression( + 'ag__.ldu(lambda: var_, name)', + var_=v, + name=gast.Constant(str(v), kind=None))) + + template = """ + def getter_name(): + return guarded_state_vars, + def setter_name(vars_): + nonlocal_declarations + state_vars, = vars_ + """ + return templates.replace( + template, + nonlocal_declarations=nonlocal_declarations, + getter_name=getter_name, + guarded_state_vars=guarded_block_vars, + setter_name=setter_name, + state_vars=tuple(block_vars)) + + def _create_loop_options(self, node): + if not anno.hasanno(node, anno.Basic.DIRECTIVES): + return gast.Dict([], []) + + loop_directives = anno.getanno(node, anno.Basic.DIRECTIVES) + if directives.set_loop_options not in loop_directives: + return gast.Dict([], []) + + opts_dict = loop_directives[directives.set_loop_options] + str_keys, values = zip(*opts_dict.items()) + keys = [gast.Constant(s, kind=None) for s in str_keys] + values = list(values) # ast and gast don't play well with tuples. + return gast.Dict(keys, values) + + def _create_undefined_assigns(self, undefined_symbols): + assignments = [] + for s in undefined_symbols: + template = ''' + var = ag__.Undefined(symbol_name) + ''' + assignments += templates.replace( + template, + var=s, + symbol_name=gast.Constant(s.ssf(), kind=None)) + return assignments + + def _get_block_basic_vars(self, modified, live_in, live_out): + nonlocals = self.state[_Function].scope.nonlocals + basic_scope_vars = [] + for s in modified: + if s.is_composite(): + # TODO(mdan): Raise an error when this happens for a TF scope. + continue + # Variables not live into or out of the scope are considered local to the + # scope. + if s in live_in or s in live_out or s in nonlocals: + basic_scope_vars.append(s) + continue + return frozenset(basic_scope_vars) + + def _get_block_composite_vars(self, modified, live_in): + # The scope variables corresponding to composite symbols (e.g. `self.x`). + composite_scope_vars = [] + for s in modified: + if not s.is_composite(): + continue + # Mutations made to objects created inside the scope will appear as writes + # to composite symbols. Because these mutations appear as modifications + # made to composite symbols, we check whether the composite's parent is + # actually live into the scope. + # Example: + # while cond: + # x = Foo() + # x.foo = 2 * x.foo # x.foo is live into the scope, but x is not. + # + # Note that some parents might not be symbols - for example, in x['foo'], + # 'foo' is a parent, but it's a literal, not a symbol. We don't check the + # liveness of literals. + support_set_symbols = tuple( + sss for sss in s.support_set if sss.is_symbol()) + if not all(sss in live_in for sss in support_set_symbols): + continue + composite_scope_vars.append(s) + return frozenset(composite_scope_vars) + + def _get_block_vars(self, node, modified): + """Determines the variables affected inside a control flow statement.""" + defined_in = anno.getanno(node, anno.Static.DEFINED_VARS_IN) + live_in = anno.getanno(node, anno.Static.LIVE_VARS_IN) + live_out = anno.getanno(node, anno.Static.LIVE_VARS_OUT) + fn_scope = self.state[_Function].scope + + basic_scope_vars = self._get_block_basic_vars( + modified, + live_in, + live_out) + composite_scope_vars = self._get_block_composite_vars(modified, live_in) + scope_vars = tuple(basic_scope_vars | composite_scope_vars) + + # Variables that are modified inside the scope, but not defined + # before entering it. Only simple variables must be defined. The + # composite ones will be implicitly checked at runtime. + possibly_undefined = ( + modified - defined_in - fn_scope.globals - fn_scope.nonlocals) + undefined = tuple(v for v in possibly_undefined if not v.is_composite()) + + # Variables that are modified inside the scope, and depend on values outside + # it. + input_only = basic_scope_vars & live_in - live_out + + # Place the outputs first, then sort lexicographically. + scope_vars = sorted(scope_vars, key=lambda v: (v in input_only, v)) + nouts = len(scope_vars) - len(input_only) + + return scope_vars, undefined, nouts + + def visit_If(self, node): + node = self.generic_visit(node) + body_scope = anno.getanno(node, annos.NodeAnno.BODY_SCOPE) + orelse_scope = anno.getanno(node, annos.NodeAnno.ORELSE_SCOPE) + + cond_vars, undefined, nouts = self._get_block_vars( + node, body_scope.bound | orelse_scope.bound) + + undefined_assigns = self._create_undefined_assigns(undefined) + + nonlocal_declarations = self._create_nonlocal_declarations(cond_vars) + + reserved = body_scope.referenced | orelse_scope.referenced + state_getter_name = self.ctx.namer.new_symbol('get_state', reserved) + state_setter_name = self.ctx.namer.new_symbol('set_state', reserved) + state_functions = self._create_state_functions( + cond_vars, nonlocal_declarations, state_getter_name, state_setter_name) + + orelse_body = node.orelse + if not orelse_body: + orelse_body = [gast.Pass()] + + template = """ + state_functions + def body_name(): + nonlocal_declarations + body + def orelse_name(): + nonlocal_declarations + orelse + undefined_assigns + ag__.if_stmt( + test, + body_name, + orelse_name, + state_getter_name, + state_setter_name, + (symbol_names,), + nouts) + """ + new_nodes = templates.replace( + template, + body=node.body, + body_name=self.ctx.namer.new_symbol('if_body', reserved), + orelse=orelse_body, + orelse_name=self.ctx.namer.new_symbol('else_body', reserved), + nonlocal_declarations=nonlocal_declarations, + nouts=gast.Constant(nouts, kind=None), + state_functions=state_functions, + state_getter_name=state_getter_name, + state_setter_name=state_setter_name, + symbol_names=tuple(gast.Constant(str(s), kind=None) for s in cond_vars), + test=node.test, + undefined_assigns=undefined_assigns) + origin_info.copy_origin(node, new_nodes[-1]) + return new_nodes + + def visit_While(self, node): + node = self.generic_visit(node) + body_scope = anno.getanno(node, annos.NodeAnno.BODY_SCOPE) + + loop_vars, undefined, _ = self._get_block_vars(node, body_scope.bound) + + undefined_assigns = self._create_undefined_assigns(undefined) + + nonlocal_declarations = self._create_nonlocal_declarations(loop_vars) + + reserved = body_scope.referenced + state_getter_name = self.ctx.namer.new_symbol('get_state', reserved) + state_setter_name = self.ctx.namer.new_symbol('set_state', reserved) + state_functions = self._create_state_functions( + loop_vars, nonlocal_declarations, state_getter_name, state_setter_name) + + opts = self._create_loop_options(node) + + template = """ + state_functions + def body_name(): + nonlocal_declarations + body + def test_name(): + return test + undefined_assigns + ag__.while_stmt( + test_name, + body_name, + state_getter_name, + state_setter_name, + (symbol_names,), + opts) + """ + new_nodes = templates.replace( + template, + body=node.body, + body_name=self.ctx.namer.new_symbol('loop_body', reserved), + nonlocal_declarations=nonlocal_declarations, + opts=opts, + state_functions=state_functions, + state_getter_name=state_getter_name, + state_setter_name=state_setter_name, + symbol_names=tuple(gast.Constant(str(s), kind=None) for s in loop_vars), + test=node.test, + test_name=self.ctx.namer.new_symbol('loop_test', reserved), + undefined_assigns=undefined_assigns) + origin_info.copy_origin(node, new_nodes[-1]) + return new_nodes + + def visit_For(self, node): + node = self.generic_visit(node) + body_scope = anno.getanno(node, annos.NodeAnno.BODY_SCOPE) + iter_scope = anno.getanno(node, annos.NodeAnno.ITERATE_SCOPE) + + loop_vars, undefined, _ = self._get_block_vars( + node, body_scope.bound | iter_scope.bound) + + undefined_assigns = self._create_undefined_assigns(undefined) + + nonlocal_declarations = self._create_nonlocal_declarations(loop_vars) + + reserved = body_scope.referenced | iter_scope.referenced + state_getter_name = self.ctx.namer.new_symbol('get_state', reserved) + state_setter_name = self.ctx.namer.new_symbol('set_state', reserved) + state_functions = self._create_state_functions( + loop_vars, nonlocal_declarations, state_getter_name, state_setter_name) + + opts = self._create_loop_options(node) + opts.keys.append(gast.Constant('iterate_names', kind=None)) + opts.values.append(gast.Constant( + parser.unparse(node.target, include_encoding_marker=False), kind=None)) + + if anno.hasanno(node, anno.Basic.EXTRA_LOOP_TEST): + extra_test = anno.getanno(node, anno.Basic.EXTRA_LOOP_TEST) + extra_test_name = self.ctx.namer.new_symbol( + 'extra_test', reserved) + template = """ + def extra_test_name(): + nonlocal_declarations + return extra_test_expr + """ + extra_test_function = templates.replace( + template, + extra_test_expr=extra_test, + extra_test_name=extra_test_name, + loop_vars=loop_vars, + nonlocal_declarations=nonlocal_declarations) + else: + extra_test_name = parser.parse_expression('None') + extra_test_function = [] + + # iterate_arg_name holds a single arg with the iterates, which may be a + # tuple. + iterate_arg_name = self.ctx.namer.new_symbol('itr', reserved) + template = """ + iterates = iterate_arg_name + """ + iterate_expansion = templates.replace( + template, iterate_arg_name=iterate_arg_name, iterates=node.target) + origin_info.copy_origin(node, iterate_expansion) + + template = """ + state_functions + def body_name(iterate_arg_name): + nonlocal_declarations + iterate_expansion + body + extra_test_function + undefined_assigns + ag__.for_stmt( + iterated, + extra_test_name, + body_name, + state_getter_name, + state_setter_name, + (symbol_names,), + opts) + """ + new_nodes = templates.replace( + template, + body=node.body, + body_name=self.ctx.namer.new_symbol('loop_body', reserved), + extra_test_function=extra_test_function, + extra_test_name=extra_test_name, + iterate_arg_name=iterate_arg_name, + iterate_expansion=iterate_expansion, + iterated=node.iter, + nonlocal_declarations=nonlocal_declarations, + opts=opts, + symbol_names=tuple(gast.Constant(str(s), kind=None) for s in loop_vars), + state_functions=state_functions, + state_getter_name=state_getter_name, + state_setter_name=state_setter_name, + undefined_assigns=undefined_assigns) + origin_info.copy_origin(node, new_nodes[-1]) + return new_nodes + + +class AnnotatedDef(reaching_definitions.Definition): + + def __init__(self): + super(AnnotatedDef, self).__init__() + self.directives = {} + + +def transform(node, ctx): + graphs = cfg.build(node) + node = qual_names.resolve(node) + node = activity.resolve(node, ctx, None) + node = reaching_definitions.resolve(node, ctx, graphs) + node = reaching_fndefs.resolve(node, ctx, graphs) + node = liveness.resolve(node, ctx, graphs) + + node = ControlFlowTransformer(ctx).visit(node) + return node diff --git a/src/braket/experimental/autoqasm/autograph/converters/control_flow_deprecated_py2.py b/src/braket/experimental/autoqasm/autograph/converters/control_flow_deprecated_py2.py new file mode 100644 index 00000000..575d64da --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/converters/control_flow_deprecated_py2.py @@ -0,0 +1,635 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Handles control flow statements: while, for, if. + +Python 2 compatibility version. Not maintained. +""" + +import gast + +from braket.experimental.autoqasm.autograph.core import converter +from braket.experimental.autoqasm.autograph.lang import directives +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import ast_util +from braket.experimental.autoqasm.autograph.pyct import cfg +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import qual_names +from braket.experimental.autoqasm.autograph.pyct import templates +from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity +from braket.experimental.autoqasm.autograph.pyct.static_analysis import annos +from braket.experimental.autoqasm.autograph.pyct.static_analysis import liveness +from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_definitions +from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_fndefs + + +# TODO(mdan): Refactor functions to make them smaller. + + +class ControlFlowTransformer(converter.Base): + """Transforms control flow structures like loops an conditionals.""" + + def _create_cond_branch(self, body_name, aliased_orig_names, + aliased_new_names, body, returns): + if len(returns) == 1: + template = """ + return retval + """ + return_stmt = templates.replace(template, retval=returns[0]) + else: + template = """ + return (retvals,) + """ + return_stmt = templates.replace(template, retvals=returns) + + if aliased_orig_names: + alias_declarations = [] + for new_name, old_name in zip(aliased_new_names, aliased_orig_names): + template = """ + try: + aliased_new_name = aliased_orig_name + except NameError: + aliased_new_name = ag__.Undefined(symbol_name) + """ + + alias_declarations.extend( + templates.replace( + template, + aliased_new_name=new_name, + aliased_orig_name=old_name, + symbol_name=gast.Constant(str(old_name), kind=None))) + + template = """ + def body_name(): + alias_declarations + body + return_stmt + """ + return templates.replace( + template, + alias_declarations=alias_declarations, + body_name=body_name, + body=body, + return_stmt=return_stmt) + else: + template = """ + def body_name(): + body + return_stmt + """ + return templates.replace( + template, body_name=body_name, body=body, return_stmt=return_stmt) + + def _create_cond_expr(self, results, test, body_name, orelse_name, + state_getter_name, state_setter_name, + basic_symbol_names, composite_symbol_names): + if results is not None: + template = """ + results = ag__.if_stmt(test, body_name, orelse_name, + state_getter_name, state_setter_name, + (basic_symbol_names,), + (composite_symbol_names,)) + """ + return templates.replace( + template, + test=test, + results=results, + body_name=body_name, + orelse_name=orelse_name, + state_getter_name=state_getter_name, + state_setter_name=state_setter_name, + basic_symbol_names=basic_symbol_names, + composite_symbol_names=composite_symbol_names) + else: + template = """ + ag__.if_stmt(test, body_name, orelse_name, getter_name, setter_name, + (basic_symbol_names,), (composite_symbol_names,)) + """ + return templates.replace( + template, + test=test, + body_name=body_name, + orelse_name=orelse_name, + getter_name=state_getter_name, + setter_name=state_setter_name, + basic_symbol_names=basic_symbol_names, + composite_symbol_names=composite_symbol_names) + + def _fmt_symbols(self, symbol_set): + if not symbol_set: + return 'no variables' + return ', '.join(map(str, symbol_set)) + + def _determine_aliased_symbols(self, scope, node_defined_in): + modified_live = scope.modified & node_defined_in + # Composite symbols are handled elsewhere see _create_state_functions + return {s for s in modified_live if not s.is_composite()} + + def _create_state_functions(self, composites, state_getter_name, + state_setter_name): + + if composites: + composite_tuple = tuple(composites) + + template = """ + def state_getter_name(): + return composite_tuple, + def state_setter_name(vals): + composite_tuple, = vals + """ + node = templates.replace( + template, + state_getter_name=state_getter_name, + state_setter_name=state_setter_name, + composite_tuple=composite_tuple) + else: + template = """ + def state_getter_name(): + return () + def state_setter_name(_): + pass + """ + node = templates.replace( + template, + state_getter_name=state_getter_name, + state_setter_name=state_setter_name) + + return node + + def _create_loop_options(self, node): + if not anno.hasanno(node, anno.Basic.DIRECTIVES): + return gast.Dict([], []) + + loop_directives = anno.getanno(node, anno.Basic.DIRECTIVES) + if directives.set_loop_options not in loop_directives: + return gast.Dict([], []) + + opts_dict = loop_directives[directives.set_loop_options] + str_keys, values = zip(*opts_dict.items()) + keys = [gast.Constant(s, kind=None) for s in str_keys] + values = list(values) # ast and gast don't play well with tuples. + return gast.Dict(keys, values) + + def _create_undefined_assigns(self, undefined_symbols): + assignments = [] + for s in undefined_symbols: + template = ''' + var = ag__.Undefined(symbol_name) + ''' + assignments += templates.replace( + template, + var=s, + symbol_name=gast.Constant(s.ssf(), kind=None)) + return assignments + + def visit_If(self, node): + body_scope = anno.getanno(node, annos.NodeAnno.BODY_SCOPE) + orelse_scope = anno.getanno(node, annos.NodeAnno.ORELSE_SCOPE) + defined_in = anno.getanno(node, anno.Static.DEFINED_VARS_IN) + live_out = anno.getanno(node, anno.Static.LIVE_VARS_OUT) + + # Note: this information needs to be extracted before the body conversion + # that happens in the call to generic_visit below, because the conversion + # generates nodes that lack static analysis annotations. + need_alias_in_body = self._determine_aliased_symbols( + body_scope, defined_in) + need_alias_in_orelse = self._determine_aliased_symbols( + orelse_scope, defined_in) + + node = self.generic_visit(node) + + modified_in_cond = body_scope.modified | orelse_scope.modified + returned_from_cond = set() + composites = set() + for s in modified_in_cond: + if s in live_out and not s.is_composite(): + returned_from_cond.add(s) + if s.is_composite(): + # Special treatment for compound objects, always return them. + # This allows special handling within the if_stmt itself. + # For example, in TensorFlow we need to restore the state of composite + # symbols to ensure that only effects from the executed branch are seen. + composites.add(s) + + created_in_body = body_scope.modified & returned_from_cond - defined_in + created_in_orelse = orelse_scope.modified & returned_from_cond - defined_in + + basic_created_in_body = tuple( + s for s in created_in_body if not s.is_composite()) + basic_created_in_orelse = tuple( + s for s in created_in_orelse if not s.is_composite()) + + # These variables are defined only in a single branch. This is fine in + # Python so we pass them through. Another backend, e.g. Tensorflow, may need + # to handle these cases specially or throw an Error. + possibly_undefined = (set(basic_created_in_body) ^ + set(basic_created_in_orelse)) + + # Alias the closure variables inside the conditional functions, to allow + # the functions access to the respective variables. + # We will alias variables independently for body and orelse scope, + # because different branches might write different variables. + aliased_body_orig_names = tuple(need_alias_in_body) + aliased_orelse_orig_names = tuple(need_alias_in_orelse) + aliased_body_new_names = tuple( + self.ctx.namer.new_symbol(s.ssf(), body_scope.referenced) + for s in aliased_body_orig_names) + aliased_orelse_new_names = tuple( + self.ctx.namer.new_symbol(s.ssf(), orelse_scope.referenced) + for s in aliased_orelse_orig_names) + + alias_body_map = dict(zip(aliased_body_orig_names, aliased_body_new_names)) + alias_orelse_map = dict( + zip(aliased_orelse_orig_names, aliased_orelse_new_names)) + + node_body = ast_util.rename_symbols(node.body, alias_body_map) + node_orelse = ast_util.rename_symbols(node.orelse, alias_orelse_map) + + cond_var_name = self.ctx.namer.new_symbol('cond', body_scope.referenced) + body_name = self.ctx.namer.new_symbol('if_true', body_scope.referenced) + orelse_name = self.ctx.namer.new_symbol('if_false', orelse_scope.referenced) + all_referenced = body_scope.referenced | orelse_scope.referenced + state_getter_name = self.ctx.namer.new_symbol('get_state', all_referenced) + state_setter_name = self.ctx.namer.new_symbol('set_state', all_referenced) + + returned_from_cond = tuple(returned_from_cond) + composites = tuple(composites) + + if returned_from_cond: + if len(returned_from_cond) == 1: + cond_results = returned_from_cond[0] + else: + cond_results = gast.Tuple([s.ast() for s in returned_from_cond], None) + + returned_from_body = tuple( + alias_body_map[s] if s in need_alias_in_body else s + for s in returned_from_cond) + returned_from_orelse = tuple( + alias_orelse_map[s] if s in need_alias_in_orelse else s + for s in returned_from_cond) + + else: + # When the cond would return no value, we leave the cond called without + # results. That in turn should trigger the side effect guards. The + # branch functions will return a dummy value that ensures cond + # actually has some return value as well. + cond_results = None + # TODO(mdan): Replace with None once side_effect_guards is retired. + returned_from_body = (templates.replace_as_expression( + 'ag__.match_staging_level(1, cond_var_name)', + cond_var_name=cond_var_name),) + returned_from_orelse = (templates.replace_as_expression( + 'ag__.match_staging_level(1, cond_var_name)', + cond_var_name=cond_var_name),) + + cond_assign = self.create_assignment(cond_var_name, node.test) + body_def = self._create_cond_branch( + body_name, + aliased_orig_names=aliased_body_orig_names, + aliased_new_names=aliased_body_new_names, + body=node_body, + returns=returned_from_body) + orelse_def = self._create_cond_branch( + orelse_name, + aliased_orig_names=aliased_orelse_orig_names, + aliased_new_names=aliased_orelse_new_names, + body=node_orelse, + returns=returned_from_orelse) + undefined_assigns = self._create_undefined_assigns(possibly_undefined) + composite_defs = self._create_state_functions( + composites, state_getter_name, state_setter_name) + + basic_symbol_names = tuple( + gast.Constant(str(symbol), kind=None) for symbol in returned_from_cond) + composite_symbol_names = tuple( + gast.Constant(str(symbol), kind=None) for symbol in composites) + + cond_expr = self._create_cond_expr(cond_results, cond_var_name, body_name, + orelse_name, state_getter_name, + state_setter_name, basic_symbol_names, + composite_symbol_names) + + if_ast = ( + undefined_assigns + composite_defs + body_def + orelse_def + + cond_assign + cond_expr) + return if_ast + + def _get_basic_loop_vars(self, modified_symbols, live_in, live_out): + # The loop variables corresponding to simple symbols (e.g. `x`). + basic_loop_vars = [] + for s in modified_symbols: + if s.is_composite(): + # TODO(mdan): Raise an error when this happens for a TF loop. + continue + # Variables not live into or out of the loop are considered local to the + # loop. + if s not in live_in and s not in live_out: + continue + basic_loop_vars.append(s) + return frozenset(basic_loop_vars) + + def _get_composite_loop_vars(self, modified_symbols, live_in): + # The loop variables corresponding to composite symbols (e.g. `self.x`). + composite_loop_vars = [] + for s in modified_symbols: + if not s.is_composite(): + continue + # Mutations made to objects created inside the loop will appear as writes + # to composite symbols. Because these mutations appear as modifications + # made to composite symbols, we check whether the composite's parent is + # actually live into the loop. + # Example: + # while cond: + # x = Foo() + # x.foo = 2 * x.foo # x.foo is live into the loop, but x is not. + # + # Note that some parents might not be symbols - for example, in x['foo'], + # 'foo' is a parent, but it's a literal, not a symbol. We don't check the + # liveness of literals. + support_set_symbols = tuple( + sss for sss in s.support_set if sss.is_symbol()) + if not all(sss in live_in for sss in support_set_symbols): + continue + composite_loop_vars.append(s) + return frozenset(composite_loop_vars) + + def _get_loop_vars(self, node, modified_symbols): + body_scope = anno.getanno(node, annos.NodeAnno.BODY_SCOPE) + defined_in = anno.getanno(node, anno.Static.DEFINED_VARS_IN) + live_in = anno.getanno(node, anno.Static.LIVE_VARS_IN) + live_out = anno.getanno(node, anno.Static.LIVE_VARS_OUT) + reserved_symbols = body_scope.referenced + + basic_loop_vars = self._get_basic_loop_vars( + modified_symbols, live_in, live_out) + composite_loop_vars = self._get_composite_loop_vars( + modified_symbols, live_in) + + # Variable that are used or defined inside the loop, but not defined + # before entering the loop. Only simple variables must be defined. The + # composite ones will be implicitly checked at runtime. + undefined_lives = basic_loop_vars - defined_in + + return (basic_loop_vars, composite_loop_vars, reserved_symbols, + undefined_lives) + + def _loop_var_constructs(self, basic_loop_vars): + loop_vars = tuple(basic_loop_vars) + loop_vars_ast_tuple = gast.Tuple([n.ast() for n in loop_vars], None) + + if len(loop_vars) == 1: + loop_vars = loop_vars[0] + + return loop_vars, loop_vars_ast_tuple + + def visit_While(self, node): + node = self.generic_visit(node) + + (basic_loop_vars, composite_loop_vars, reserved_symbols, + possibly_undefs) = self._get_loop_vars( + node, + anno.getanno(node, annos.NodeAnno.BODY_SCOPE).modified) + loop_vars, loop_vars_ast_tuple = self._loop_var_constructs( + basic_loop_vars) + + state_getter_name = self.ctx.namer.new_symbol('get_state', reserved_symbols) + state_setter_name = self.ctx.namer.new_symbol('set_state', reserved_symbols) + state_functions = self._create_state_functions( + composite_loop_vars, state_getter_name, state_setter_name) + + basic_symbol_names = tuple( + gast.Constant(str(symbol), kind=None) for symbol in basic_loop_vars) + composite_symbol_names = tuple( + gast.Constant(str(symbol), kind=None) for symbol in composite_loop_vars) + + opts = self._create_loop_options(node) + + # TODO(mdan): Use a single template. + # If the body and test functions took a single tuple for loop_vars, instead + # of *loop_vars, then a single template could be used. + if loop_vars: + template = """ + state_functions + def body_name(loop_vars): + body + return loop_vars, + def test_name(loop_vars): + return test + loop_vars_ast_tuple = ag__.while_stmt( + test_name, + body_name, + state_getter_name, + state_setter_name, + (loop_vars,), + (basic_symbol_names,), + (composite_symbol_names,), + opts) + """ + node = templates.replace( + template, + loop_vars=loop_vars, + loop_vars_ast_tuple=loop_vars_ast_tuple, + test_name=self.ctx.namer.new_symbol('loop_test', reserved_symbols), + test=node.test, + body_name=self.ctx.namer.new_symbol('loop_body', reserved_symbols), + body=node.body, + state_functions=state_functions, + state_getter_name=state_getter_name, + state_setter_name=state_setter_name, + basic_symbol_names=basic_symbol_names, + composite_symbol_names=composite_symbol_names, + opts=opts) + else: + template = """ + state_functions + def body_name(): + body + return () + def test_name(): + return test + ag__.while_stmt( + test_name, + body_name, + state_getter_name, + state_setter_name, + (), + (), + (composite_symbol_names,), + opts) + """ + node = templates.replace( + template, + test_name=self.ctx.namer.new_symbol('loop_test', reserved_symbols), + test=node.test, + body_name=self.ctx.namer.new_symbol('loop_body', reserved_symbols), + body=node.body, + state_functions=state_functions, + state_getter_name=state_getter_name, + state_setter_name=state_setter_name, + composite_symbol_names=composite_symbol_names, + opts=opts) + + undefined_assigns = self._create_undefined_assigns(possibly_undefs) + return undefined_assigns + node + + def visit_For(self, node): + node = self.generic_visit(node) + + (basic_loop_vars, composite_loop_vars, + reserved_symbols, possibly_undefs) = self._get_loop_vars( + node, (anno.getanno(node, annos.NodeAnno.BODY_SCOPE).modified + | anno.getanno(node, annos.NodeAnno.ITERATE_SCOPE).modified)) + loop_vars, loop_vars_ast_tuple = self._loop_var_constructs( + basic_loop_vars) + body_name = self.ctx.namer.new_symbol('loop_body', reserved_symbols) + + state_getter_name = self.ctx.namer.new_symbol('get_state', reserved_symbols) + state_setter_name = self.ctx.namer.new_symbol('set_state', reserved_symbols) + state_functions = self._create_state_functions( + composite_loop_vars, state_getter_name, state_setter_name) + + if anno.hasanno(node, anno.Basic.EXTRA_LOOP_TEST): + extra_test = anno.getanno(node, anno.Basic.EXTRA_LOOP_TEST) + extra_test_name = self.ctx.namer.new_symbol( + 'extra_test', reserved_symbols) + template = """ + def extra_test_name(loop_vars): + return extra_test_expr + """ + extra_test_function = templates.replace( + template, + extra_test_name=extra_test_name, + loop_vars=loop_vars, + extra_test_expr=extra_test) + else: + extra_test_name = parser.parse_expression('None') + extra_test_function = [] + + # Workaround for PEP-3113 + # iterates_var holds a single variable with the iterates, which may be a + # tuple. + iterates_var_name = self.ctx.namer.new_symbol( + 'iterates', reserved_symbols) + template = """ + iterates = iterates_var_name + """ + iterate_expansion = templates.replace( + template, + iterates=node.target, + iterates_var_name=iterates_var_name) + + undefined_assigns = self._create_undefined_assigns(possibly_undefs) + + basic_symbol_names = tuple( + gast.Constant(str(symbol), kind=None) for symbol in basic_loop_vars) + composite_symbol_names = tuple( + gast.Constant(str(symbol), kind=None) for symbol in composite_loop_vars) + + opts = self._create_loop_options(node) + + # TODO(mdan): Use a single template. + # If the body and test functions took a single tuple for loop_vars, instead + # of *loop_vars, then a single template could be used. + if loop_vars: + template = """ + undefined_assigns + state_functions + def body_name(iterates_var_name, loop_vars): + iterate_expansion + body + return loop_vars, + extra_test_function + loop_vars_ast_tuple = ag__.for_stmt( + iter_, + extra_test_name, + body_name, + state_getter_name, + state_setter_name, + (loop_vars,), + (basic_symbol_names,), + (composite_symbol_names,), + opts) + """ + return templates.replace( + template, + undefined_assigns=undefined_assigns, + loop_vars=loop_vars, + loop_vars_ast_tuple=loop_vars_ast_tuple, + iter_=node.iter, + iterate_expansion=iterate_expansion, + iterates_var_name=iterates_var_name, + extra_test_name=extra_test_name, + extra_test_function=extra_test_function, + body_name=body_name, + body=node.body, + state_functions=state_functions, + state_getter_name=state_getter_name, + state_setter_name=state_setter_name, + basic_symbol_names=basic_symbol_names, + composite_symbol_names=composite_symbol_names, + opts=opts) + else: + template = """ + undefined_assigns + state_functions + def body_name(iterates_var_name): + iterate_expansion + body + return () + extra_test_function + ag__.for_stmt( + iter_, + extra_test_name, + body_name, + state_getter_name, + state_setter_name, + (), + (), + (composite_symbol_names,), + opts) + """ + return templates.replace( + template, + undefined_assigns=undefined_assigns, + iter_=node.iter, + iterate_expansion=iterate_expansion, + iterates_var_name=iterates_var_name, + extra_test_name=extra_test_name, + extra_test_function=extra_test_function, + body_name=body_name, + body=node.body, + state_functions=state_functions, + state_getter_name=state_getter_name, + state_setter_name=state_setter_name, + composite_symbol_names=composite_symbol_names, + opts=opts) + + +class AnnotatedDef(reaching_definitions.Definition): + + def __init__(self): + super(AnnotatedDef, self).__init__() + self.directives = {} + + +def transform(node, ctx): + graphs = cfg.build(node) + node = qual_names.resolve(node) + node = activity.resolve(node, ctx, None) + node = reaching_definitions.resolve(node, ctx, graphs) + node = reaching_fndefs.resolve(node, ctx, graphs) + node = liveness.resolve(node, ctx, graphs) + + node = ControlFlowTransformer(ctx).visit(node) + return node diff --git a/src/braket/experimental/autoqasm/autograph/converters/directives.py b/src/braket/experimental/autoqasm/autograph/converters/directives.py new file mode 100644 index 00000000..f68440b6 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/converters/directives.py @@ -0,0 +1,177 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Handles directives. + +This converter removes the directive functions from the code and moves the +information they specify into AST annotations. It is a specialized form of +static analysis, one that is specific to AutoGraph. + +Note that this requires that the actual directive functions are static - that +is, they do not change at runtime. So if you do something like this: + + tf.autograph.set_loop_options = + +Then the directive will may no longer be recognized. Furthermore, if the +converted function is cached, such an action may be irreversible. +""" + +import inspect + +import gast + +from braket.experimental.autoqasm.autograph.core import converter +from braket.experimental.autoqasm.autograph.lang import directives +from braket.experimental.autoqasm.autograph.pyct import anno +import inspect as tf_inspect + + +STATIC_VALUE = 'static_value' +"""Used for AST annotations, see visit_Name.""" + + +class _LoopScope(object): + + def __init__(self): + self.ast_node = None + self.statements_visited = 0 + + +def _map_args(call_node, function): + """Maps AST call nodes to the actual function's arguments. + + Args: + call_node: ast.Call + function: Callable[..., Any], the actual function matching call_node + Returns: + Dict[Text, ast.AST], mapping each of the function's argument names to + the respective AST node. + Raises: + ValueError: if the default arguments are not correctly set + """ + args = call_node.args + kwds = {kwd.arg: kwd.value for kwd in call_node.keywords} + call_args = tf_inspect.getcallargs(function, *args, **kwds) + + # Keyword arguments not specified in kwds will be mapped to their defaults, + # which are Python values. Since we don't currently have a way to transform + # those into AST references, we simply remove them. By convention, directives + # use UNSPECIFIED as default value for optional arguments. No other + # defaults should be present. + unexpected_defaults = [] + for k in call_args: + if (k not in kwds + and call_args[k] not in args + and call_args[k] is not directives.UNSPECIFIED): + unexpected_defaults.append(k) + if unexpected_defaults: + raise ValueError('Unexpected keyword argument values, %s, for function %s' + % (zip(unexpected_defaults, + [call_args[k] for k in unexpected_defaults]), + function)) + return {k: v for k, v in call_args.items() if v is not directives.UNSPECIFIED} + + +class DirectivesTransformer(converter.Base): + """Parses compiler directives and converts them into AST annotations.""" + + def _process_symbol_directive(self, call_node, directive): + if len(call_node.args) < 1: + raise ValueError('"%s" requires a positional first argument' + ' as the target' % directive.__name__) + target = call_node.args[0] + defs = anno.getanno(target, anno.Static.ORIG_DEFINITIONS) + for def_ in defs: + def_.directives[directive] = _map_args(call_node, directive) + return call_node + + def _process_statement_directive(self, call_node, directive): + if self.state[_LoopScope].statements_visited > 1: + raise ValueError( + '"%s" must be the first statement in the loop block' % ( + directive.__name__)) + if self.state[_LoopScope].level < 2: + raise ValueError( + '"%s" must be used inside a statement' % directive.__name__) + target = self.state[_LoopScope].ast_node + node_anno = anno.getanno(target, anno.Basic.DIRECTIVES, {}) + node_anno[directive] = _map_args(call_node, directive) + anno.setanno(target, anno.Basic.DIRECTIVES, node_anno) + return call_node + + def visit_Name(self, node): + node = self.generic_visit(node) + if isinstance(node.ctx, gast.Load): + defs = anno.getanno(node, anno.Static.DEFINITIONS, ()) + is_defined = bool(defs) + if not is_defined and node.id in self.ctx.info.namespace: + anno.setanno(node, STATIC_VALUE, self.ctx.info.namespace[node.id]) + return node + + def visit_Attribute(self, node): + node = self.generic_visit(node) + parent_val = anno.getanno(node.value, STATIC_VALUE, default=None) + if parent_val is not None and inspect.ismodule(parent_val): + if hasattr(parent_val, node.attr): + anno.setanno(node, STATIC_VALUE, getattr(parent_val, node.attr)) + return node + + def visit_Assign(self, node): + self.state[_LoopScope].statements_visited += 1 + return self.generic_visit(node) + + def visit_AugAssign(self, node): + self.state[_LoopScope].statements_visited += 1 + return self.generic_visit(node) + + def visit_Expr(self, node): + self.state[_LoopScope].statements_visited += 1 + node = self.generic_visit(node) + if isinstance(node.value, gast.Call): + call_node = node.value + static_val = anno.getanno(call_node.func, STATIC_VALUE, default=None) + if static_val is not None: + # Note: directive calls are not output in the generated code, hence + # the removal from the code by returning None. + + if static_val is directives.set_element_type: + self._process_symbol_directive(call_node, static_val) + return None + elif static_val is directives.set_loop_options: + self._process_statement_directive(call_node, static_val) + return None + return node + + # TODO(mdan): This will be insufficient for other control flow. + # That means that if we ever have a directive that affects things other than + # loops, we'll need support for parallel scopes, or have multiple converters. + def _track_and_visit_loop(self, node): + self.state[_LoopScope].enter() + self.state[_LoopScope].ast_node = node + node = self.generic_visit(node) + # Edge case: a loop with just one directive statement would become empty. + if not node.body: + node.body = [gast.Pass()] + self.state[_LoopScope].exit() + return node + + def visit_While(self, node): + return self._track_and_visit_loop(node) + + def visit_For(self, node): + return self._track_and_visit_loop(node) + + +def transform(node, ctx): + return DirectivesTransformer(ctx).visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/converters/functions.py b/src/braket/experimental/autoqasm/autograph/converters/functions.py new file mode 100644 index 00000000..92bbb89d --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/converters/functions.py @@ -0,0 +1,134 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Converts function definitions and lambdas by adding necessary boilerplate.""" + +import gast + +from braket.experimental.autoqasm.autograph.core import converter +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import qual_names +from braket.experimental.autoqasm.autograph.pyct import templates +from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity +from braket.experimental.autoqasm.autograph.pyct.static_analysis import annos + + +class _Function(object): + + def __init__(self): + self.context_name = None + + +class FunctionTransformer(converter.Base): + """Wraps function bodies around autograph-specific boilerplate.""" + + def _function_scope_options(self, fn_scope): + """Returns the options with which to create function scopes.""" + # Top-level function receive the options that were directly requested. + # All others receive the options corresponding to a recursive conversion. + # Note: this mainly controls the user_requested flag, which is important + # primarily because the FunctionScope context also creates a + # ControlStatusCtx(autograph=ENABLED) when user_requested is True. See + # function_wrappers.py. + if fn_scope.level == 2: + return self.ctx.user.options + return self.ctx.user.options.call_options() + + def visit_Lambda(self, node): + with self.state[_Function] as fn_scope: + node = self.generic_visit(node) + + # TODO(mdan): Fix the tests so that we can always add this decorator. + if fn_scope.level > 2: + return templates.replace_as_expression( + 'ag__.autograph_artifact(l)', l=node) + + scope = anno.getanno(node, anno.Static.SCOPE) + function_context_name = self.ctx.namer.new_symbol('lscope', + scope.referenced) + fn_scope.context_name = function_context_name + anno.setanno(node, 'function_context_name', function_context_name) + + template = """ + ag__.with_function_scope( + lambda function_context: body, function_context_name, options) + """ + node.body = templates.replace_as_expression( + template, + options=self._function_scope_options(fn_scope).to_ast(), + function_context=function_context_name, + function_context_name=gast.Constant(function_context_name, kind=None), + body=node.body) + + return node + + def visit_FunctionDef(self, node): + with self.state[_Function] as fn_scope: + scope = anno.getanno(node, annos.NodeAnno.BODY_SCOPE) + + function_context_name = self.ctx.namer.new_symbol('fscope', + scope.referenced) + fn_scope.context_name = function_context_name + anno.setanno(node, 'function_context_name', function_context_name) + + node = self.generic_visit(node) + + if fn_scope.level <= 2: + # Top-level functions lose their decorator because the conversion is + # always just-in-time and by the time it happens the decorators are + # already set to be applied. + node.decorator_list = [] + else: + # TODO(mdan): Fix the tests so that we can always add this decorator. + # Inner functions are converted already, so we insert a decorator to + # prevent double conversion. Double conversion would work too, but this + # saves the overhead. + node.decorator_list.append( + parser.parse_expression('ag__.autograph_artifact')) + + docstring_node = None + if node.body: + first_statement = node.body[0] + if (isinstance(first_statement, gast.Expr) and + isinstance(first_statement.value, gast.Constant)): + docstring_node = first_statement + node.body = node.body[1:] + + template = """ + with ag__.FunctionScope( + function_name, context_name, options) as function_context: + body + """ + wrapped_body = templates.replace( + template, + function_name=gast.Constant(node.name, kind=None), + context_name=gast.Constant(function_context_name, kind=None), + options=self._function_scope_options(fn_scope).to_ast(), + function_context=function_context_name, + body=node.body) + + if docstring_node is not None: + wrapped_body = [docstring_node] + wrapped_body + + node.body = wrapped_body + + return node + + +def transform(node, ctx): + node = qual_names.resolve(node) + node = activity.resolve(node, ctx, None) + + return FunctionTransformer(ctx).visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/converters/list_comprehensions.py b/src/braket/experimental/autoqasm/autograph/converters/list_comprehensions.py new file mode 100644 index 00000000..e57af119 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/converters/list_comprehensions.py @@ -0,0 +1,78 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Lowers list comprehensions into for and if statements. + +Example: + + result = [x * x for x in xs] + +becomes + + result = [] + for x in xs: + elt = x * x + result.append(elt) +""" + +import gast + +from braket.experimental.autoqasm.autograph.core import converter +from braket.experimental.autoqasm.autograph.pyct import templates + + +# TODO(mdan): This should covert directly to operator calls. + + +class ListCompTransformer(converter.Base): + """Lowers list comprehensions into standard control flow.""" + + def visit_Assign(self, node): + if not isinstance(node.value, gast.ListComp): + return self.generic_visit(node) + if len(node.targets) > 1: + raise NotImplementedError('multiple assignments') + + target, = node.targets + list_comp_node = node.value + + template = """ + target = [] + """ + initialization = templates.replace(template, target=target) + + template = """ + target.append(elt) + """ + body = templates.replace(template, target=target, elt=list_comp_node.elt) + + for gen in reversed(list_comp_node.generators): + for gen_if in reversed(gen.ifs): + template = """ + if test: + body + """ + body = templates.replace(template, test=gen_if, body=body) + template = """ + for target in iter_: + body + """ + body = templates.replace( + template, iter_=gen.iter, target=gen.target, body=body) + + return initialization + body + + +def transform(node, ctx): + return ListCompTransformer(ctx).visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/converters/lists.py b/src/braket/experimental/autoqasm/autograph/converters/lists.py new file mode 100644 index 00000000..86c3ad47 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/converters/lists.py @@ -0,0 +1,239 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Converter for list operations. + +This includes converting Python lists to TensorArray/TensorList. +""" + +# TODO(mdan): Elaborate the logic here. +# TODO(mdan): Does it even make sense to attempt to try to use TAs? +# The current rule (always convert to TensorArray) is naive and insufficient. +# In general, a better mechanism could look like: +# * convert to TensorList by default +# * leave as Python list if the user explicitly forbids it +# * convert to TensorArray only when complete write once behavior can be +# guaranteed (e.g. list comprehensions) + +import gast + +from braket.experimental.autoqasm.autograph.core import converter +from braket.experimental.autoqasm.autograph.lang import directives +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import qual_names +from braket.experimental.autoqasm.autograph.pyct import templates +from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity +from braket.experimental.autoqasm.autograph.pyct.static_analysis.annos import NodeAnno + + +class _Statement(object): + + def __init__(self): + self.pop_uses = None + + +class ListTransformer(converter.Base): + """Converts lists and related operations to their TF counterpart.""" + + def visit_List(self, node): + node = self.generic_visit(node) + template = """ + ag__.new_list(elements) + """ + return templates.replace_as_expression(template, elements=node) + + def _replace_append_call(self, node): + assert len(node.args) == 1 + assert isinstance(node.func, gast.Attribute) + template = """ + target = ag__.list_append(target, element) + """ + return templates.replace( + template, + target=node.func.value, + element=node.args[0]) + + def _replace_pop_call(self, node): + # Expressions that use pop() are converted to a statement + expression. + # + # For example: + # + # print(target.pop()) + # + # ... is converted to: + # + # target, target_pop = ag__.list_pop(target) + # print(target_pop) + # + # Here, we just generate the variable name and swap it in, + # and _generate_pop_operation will handle the rest. + # + # Multiple uses of pop() are allowed: + # + # print(tartget.pop(), target.pop()) + # print(tartget.pop().pop()) + # + assert isinstance(node.func, gast.Attribute) + scope = anno.getanno(node, NodeAnno.ARGS_SCOPE) + target_node = node.func.value + + # Attempt to use a related name if one exists. Otherwise use something + # generic. + if anno.hasanno(target_node, anno.Basic.QN): + target_name = anno.getanno(target_node, anno.Basic.QN).ssf() + else: + target_name = 'list_' + pop_var_name = self.ctx.namer.new_symbol(target_name, scope.referenced) + + stmt = self.state[_Statement] + if stmt.pop_uses is None: + stmt.pop_uses = [] + stmt.pop_uses.append((node, pop_var_name)) + + return templates.replace_as_expression('var_name', var_name=pop_var_name) + + def _replace_stack_call(self, node): + assert len(node.args) == 1 + dtype = self.get_definition_directive( + node.args[0], + directives.set_element_type, + 'dtype', + default=templates.replace_as_expression('None')) + template = """ + ag__.list_stack( + target, + opts=ag__.ListStackOpts( + element_dtype=dtype, + original_call=orig_call)) + """ + return templates.replace_as_expression( + template, + dtype=dtype, + target=node.args[0], + orig_call=node.func) + + def visit_Call(self, node): + node = self.generic_visit(node) + + # TODO(mdan): This is insufficient if target is a function argument. + # In the case of function arguments, we need to add the list to the + # function's return value, because it is being modified. + # TODO(mdan): Checking just the name is brittle, can it be improved? + if isinstance(node.func, gast.Attribute): + func_name = node.func.attr + if func_name == 'append' and (len(node.args) == 1): + node = self._replace_append_call(node) + elif func_name == 'pop' and (len(node.args) <= 1): + node = self._replace_pop_call(node) + elif (func_name == 'stack' and (len(node.args) == 1) and + (not node.keywords or node.keywords[0].arg == 'strict')): + # This avoids false positives with keyword args. + # TODO(mdan): handle kwargs properly. + node = self._replace_stack_call(node) + + return node + + def _generate_pop_operation(self, original_call_node, pop_var_name): + assert isinstance(original_call_node.func, gast.Attribute) + + if original_call_node.args: + pop_element = original_call_node.args[0] + else: + pop_element = parser.parse_expression('None') + + # The call will be something like "target.pop()", and the dtype is hooked to + # target, hence the func.value. + # TODO(mdan): For lists of lists, this won't work. + # The reason why it won't work is because it's unclear how to annotate + # the list as a "list of lists with a certain element type" when using + # operations like `l.pop().pop()`. + dtype = self.get_definition_directive( + original_call_node.func.value, + directives.set_element_type, + 'dtype', + default=templates.replace_as_expression('None')) + shape = self.get_definition_directive( + original_call_node.func.value, + directives.set_element_type, + 'shape', + default=templates.replace_as_expression('None')) + + template = """ + target, pop_var_name = ag__.list_pop( + target, element, + opts=ag__.ListPopOpts(element_dtype=dtype, element_shape=shape)) + """ + return templates.replace( + template, + target=original_call_node.func.value, + pop_var_name=pop_var_name, + element=pop_element, + dtype=dtype, + shape=shape) + + def _postprocess_statement(self, node): + """Inserts any separate pop() calls that node may use.""" + pop_uses = self.state[_Statement].pop_uses + if pop_uses: + replacements = [] + for original_call_node, pop_var_name in pop_uses: + replacements.extend( + self._generate_pop_operation(original_call_node, pop_var_name)) + replacements.append(node) + node = replacements + self.state[_Statement].exit() + return node, None + + def _visit_and_process_block(self, block): + return self.visit_block( + block, + before_visit=self.state[_Statement].enter, + after_visit=self._postprocess_statement) + + def visit_FunctionDef(self, node): + node.args = self.generic_visit(node.args) + node.decorator_list = self.visit_block(node.decorator_list) + node.body = self._visit_and_process_block(node.body) + return node + + def visit_For(self, node): + node.target = self.visit(node.target) + node.body = self._visit_and_process_block(node.body) + node.orelse = self._visit_and_process_block(node.orelse) + return node + + def visit_While(self, node): + node.test = self.visit(node.test) + node.body = self._visit_and_process_block(node.body) + node.orelse = self._visit_and_process_block(node.orelse) + return node + + def visit_If(self, node): + node.test = self.visit(node.test) + node.body = self._visit_and_process_block(node.body) + node.orelse = self._visit_and_process_block(node.orelse) + return node + + def visit_With(self, node): + node.items = self.visit_block(node.items) + node.body = self._visit_and_process_block(node.body) + return node + + +def transform(node, ctx): + node = qual_names.resolve(node) + node = activity.resolve(node, ctx, None) + + return ListTransformer(ctx).visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/converters/logical_expressions.py b/src/braket/experimental/autoqasm/autograph/converters/logical_expressions.py new file mode 100644 index 00000000..b9592085 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/converters/logical_expressions.py @@ -0,0 +1,131 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Converter for logical expressions, e.g. `a and b -> tf.logical_and(a, b)`.""" + +import gast + +from braket.experimental.autoqasm.autograph.core import converter +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import templates + +# TODO(mdan): Properly extract boolean ops according to lazy eval rules. +# Note that this isn't completely safe either, because tensors may have control +# dependencies. +# Note that for loops that should be done after the loop was converted to +# tf.while_loop so that the expanded conditionals are properly scoped. + +# Used to signal that an operand is safe for non-lazy evaluation. +SAFE_BOOLEAN_OPERAND = 'SAFE_BOOLEAN_OPERAND' + + +LOGICAL_OPERATORS = { + gast.And: 'ag__.and_', + gast.Not: 'ag__.not_', + gast.Or: 'ag__.or_', +} + +EQUALITY_OPERATORS = { + gast.Eq: 'ag__.eq', + gast.NotEq: 'ag__.not_eq', +} + + +class LogicalExpressionTransformer(converter.Base): + """Converts logical expressions to corresponding TF calls.""" + + def _overload_of(self, operator): + op_type = type(operator) + if op_type in LOGICAL_OPERATORS: + return LOGICAL_OPERATORS[op_type] + if self.ctx.user.options.uses(converter.Feature.EQUALITY_OPERATORS): + if op_type in EQUALITY_OPERATORS: + return EQUALITY_OPERATORS[op_type] + return None + + def _as_lambda(self, expr): + return templates.replace_as_expression('lambda: expr', expr=expr) + + def _as_binary_function(self, func_name, arg1, arg2): + return templates.replace_as_expression( + 'func_name(arg1, arg2)', + func_name=parser.parse_expression(func_name), + arg1=arg1, + arg2=arg2) + + def _as_binary_operation(self, op, arg1, arg2): + template = templates.replace_as_expression( + 'arg1 is arg2', # Note: `is` will be replaced with `op` below. + arg1=arg1, + arg2=arg2) + template.ops[0] = op + return template + + def _as_unary_function(self, func_name, arg): + return templates.replace_as_expression( + 'func_name(arg)', func_name=parser.parse_expression(func_name), arg=arg) + + def _process_binop(self, op, left, right): + overload = self._overload_of(op) + if overload is None: + return self._as_binary_operation(op, left, right) + return self._as_binary_function(overload, left, right) + + def visit_Compare(self, node): + node = self.generic_visit(node) + + ops_and_comps = list(zip(node.ops, node.comparators)) + left = node.left + + # Repeated comparisons are converted to conjunctions: + # a < b < c -> a < b and b < c + op_tree = None + while ops_and_comps: + op, right = ops_and_comps.pop(0) + binary_comparison = self._process_binop(op, left, right) + if op_tree is not None: + op_tree = self._as_binary_function('ag__.and_', + self._as_lambda(op_tree), + self._as_lambda(binary_comparison)) + else: + op_tree = binary_comparison + left = right + + assert op_tree is not None + return op_tree + + def visit_UnaryOp(self, node): + node = self.generic_visit(node) + + overload = self._overload_of(node.op) + if overload is None: + return node + + return self._as_unary_function(overload, node.operand) + + def visit_BoolOp(self, node): + node = self.generic_visit(node) + node_values = node.values + right = node.values.pop() + while node_values: + left = node_values.pop() + right = self._as_binary_function( + self._overload_of(node.op), self._as_lambda(left), + self._as_lambda(right)) + return right + + +def transform(node, ctx): + transformer = LogicalExpressionTransformer(ctx) + return transformer.visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/converters/return_statements.py b/src/braket/experimental/autoqasm/autograph/converters/return_statements.py new file mode 100644 index 00000000..42501ca3 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/converters/return_statements.py @@ -0,0 +1,402 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Canonicalizes functions with multiple returns to use just one.""" + +import gast + +from braket.experimental.autoqasm.autograph.core import converter +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import qual_names +from braket.experimental.autoqasm.autograph.pyct import templates +from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity +from braket.experimental.autoqasm.autograph.pyct.static_analysis.annos import NodeAnno + + +BODY_DEFINITELY_RETURNS = 'BODY_DEFINITELY_RETURNS' +ORELSE_DEFINITELY_RETURNS = 'ORELSE_DEFINITELY_RETURNS' +STMT_DEFINITELY_RETURNS = 'STMT_DEFINITELY_RETURNS' + + +class _RewriteBlock(object): + + def __init__(self): + self.definitely_returns = False + + +class ConditionalReturnRewriter(converter.Base): + """Rewrites a pattern where it's unobvious that all paths return a value. + + This rewrite allows avoiding intermediate None return values. + + The following pattern: + + if cond: + + return + else: + + + + is converted to: + + if cond: + + return + else: + + + + and vice-versa (if the else returns, subsequent statements are moved under the + if branch). + """ + + def visit_Return(self, node): + self.state[_RewriteBlock].definitely_returns = True + return node + + def _postprocess_statement(self, node): + # If the node definitely returns (e.g. it's a with statement with a + # return statement in it), then the current block also definitely returns. + if anno.getanno(node, STMT_DEFINITELY_RETURNS, default=False): + self.state[_RewriteBlock].definitely_returns = True + + # The special case: collapse a typical conditional return pattern into + # a single conditional with possibly returns on both branches. This + # reduces the use of None return values, which don't work with TF + # conditionals. + if (isinstance(node, gast.If) + and anno.getanno(node, BODY_DEFINITELY_RETURNS, default=False)): + return node, node.orelse + elif (isinstance(node, gast.If) + and anno.getanno(node, ORELSE_DEFINITELY_RETURNS, default=False)): + return node, node.body + + return node, None + + def _visit_statement_block(self, node, nodes): + self.state[_RewriteBlock].enter() + new_nodes = self.visit_block(nodes, after_visit=self._postprocess_statement) + block_definitely_returns = self.state[_RewriteBlock].definitely_returns + self.state[_RewriteBlock].exit() + return new_nodes, block_definitely_returns + + def visit_While(self, node): + node.test = self.visit(node.test) + node.body, _ = self._visit_statement_block(node, node.body) + node.orelse, _ = self._visit_statement_block(node, node.orelse) + return node + + def visit_For(self, node): + node.iter = self.visit(node.iter) + node.target = self.visit(node.target) + node.body, _ = self._visit_statement_block(node, node.body) + node.orelse, _ = self._visit_statement_block(node, node.orelse) + return node + + def visit_With(self, node): + node.items = self.visit_block(node.items) + node.body, definitely_returns = self._visit_statement_block(node, node.body) + if definitely_returns: + anno.setanno(node, STMT_DEFINITELY_RETURNS, True) + return node + + def visit_Try(self, node): + # We could decide whether a 'try' DEFINITELY_RETURNS based on its components + # It is not clear whether we want to do anything with this given + # a 'try' is likely to throw an exception in some circumstances. + node.body, _ = self._visit_statement_block(node, node.body) + node.orelse, _ = self._visit_statement_block(node, node.orelse) + node.finalbody, _ = self._visit_statement_block(node, node.finalbody) + node.handlers = self.visit_block(node.handlers) + return node + + def visit_ExceptHandler(self, node): + # To determine whether `try` DEFINITELY_RETURNS we need to revisit this. + node.body, _ = self._visit_statement_block(node, node.body) + return node + + def visit_If(self, node): + node.test = self.visit(node.test) + + node.body, body_definitely_returns = self._visit_statement_block( + node, node.body) + if body_definitely_returns: + anno.setanno(node, BODY_DEFINITELY_RETURNS, True) + + node.orelse, orelse_definitely_returns = self._visit_statement_block( + node, node.orelse) + if orelse_definitely_returns: + anno.setanno(node, ORELSE_DEFINITELY_RETURNS, True) + + if body_definitely_returns and orelse_definitely_returns: + self.state[_RewriteBlock].definitely_returns = True + + return node + + def visit_FunctionDef(self, node): + node.args = self.visit(node.args) + node.body, _ = self._visit_statement_block(node, node.body) + return node + + +class _Block(object): + + def __init__(self): + self.is_function = False + self.return_used = False + self.create_guard_next = False + self.create_guard_now = False + + def __repr__(self): + return 'used: {}'.format( + self.return_used) + + +class _Function(object): + + def __init__(self): + self.do_return_var_name = None + self.retval_var_name = None + + def __repr__(self): + return 'return control: {}, return value: {}'.format( + self.do_return_var_name, self.retval_var_name) + + +class ReturnStatementsTransformer(converter.Base): + """Lowers return statements into variables and conditionals. + + Specifically, the following pattern: + + + return val + + + is converted to: + + do_return = False + retval = None + + + + do_return = True + retval = val + + if not do_return: + + + return retval + + The conversion adjusts loops as well: + + + while cond: + + return retval + + is converted to: + + + while not do_return and cond: + + do_return = True + retval = val + """ + + def __init__(self, ctx, allow_missing_return): + super(ReturnStatementsTransformer, self).__init__(ctx) + self.allow_missing_return = allow_missing_return + + def visit_Return(self, node): + for block in reversed(self.state[_Block].stack): + block.return_used = True + block.create_guard_next = True + if block.is_function: + break + + retval = node.value if node.value else parser.parse_expression('None') + + # Note: If `return raises, then the return is aborted. + # The try-catch below ensures the variables remain consistent in that case. + template = """ + try: + do_return_var_name = True + retval_var_name = retval + except: + do_return_var_name = False + raise + """ + node = templates.replace( + template, + do_return_var_name=self.state[_Function].do_return_var_name, + retval_var_name=self.state[_Function].retval_var_name, + retval=retval) + + return node + + def _postprocess_statement(self, node): + if not self.state[_Block].return_used: + return node, None + + state = self.state[_Block] + if state.create_guard_now: + template = """ + if not do_return_var_name: + original_node + """ + cond, = templates.replace( + template, + do_return_var_name=self.state[_Function].do_return_var_name, + original_node=node) + node, block = cond, cond.body + else: + node, block = node, None + + state.create_guard_now = state.create_guard_next + state.create_guard_next = False + + return node, block + + def _visit_statement_block(self, node, nodes): + self.state[_Block].enter() + nodes = self.visit_block(nodes, after_visit=self._postprocess_statement) + self.state[_Block].exit() + return nodes + + def visit_While(self, node): + node.test = self.visit(node.test) + + # Add the check for return to the loop condition. + node.body = self._visit_statement_block(node, node.body) + if self.state[_Block].return_used: + node.test = templates.replace_as_expression( + 'not control_var and test', + test=node.test, + control_var=self.state[_Function].do_return_var_name) + + node.orelse = self._visit_statement_block(node, node.orelse) + return node + + def visit_For(self, node): + node.iter = self.visit(node.iter) + node.target = self.visit(node.target) + + # Add the check for return to the loop condition. + node.body = self._visit_statement_block(node, node.body) + if self.state[_Block].return_used: + extra_test = anno.getanno(node, anno.Basic.EXTRA_LOOP_TEST, default=None) + if extra_test is not None: + extra_test = templates.replace_as_expression( + 'not control_var and extra_test', + extra_test=extra_test, + control_var=self.state[_Function].do_return_var_name) + else: + extra_test = templates.replace_as_expression( + 'not control_var', + control_var=self.state[_Function].do_return_var_name) + anno.setanno(node, anno.Basic.EXTRA_LOOP_TEST, extra_test) + + node.orelse = self._visit_statement_block(node, node.orelse) + return node + + def visit_With(self, node): + node.items = self.visit_block(node.items) + node.body = self._visit_statement_block(node, node.body) + return node + + def visit_Try(self, node): + node.body = self._visit_statement_block(node, node.body) + node.orelse = self._visit_statement_block(node, node.orelse) + node.finalbody = self._visit_statement_block(node, node.finalbody) + node.handlers = self.visit_block(node.handlers) + return node + + def visit_ExceptHandler(self, node): + node.body = self._visit_statement_block(node, node.body) + return node + + def visit_If(self, node): + node.test = self.visit(node.test) + node.body = self._visit_statement_block(node, node.body) + node.orelse = self._visit_statement_block(node, node.orelse) + return node + + def visit_FunctionDef(self, node): + with self.state[_Function] as fn: + with self.state[_Block] as block: + block.is_function = True + + scope = anno.getanno(node, NodeAnno.BODY_SCOPE) + do_return_var_name = self.ctx.namer.new_symbol('do_return', + scope.referenced) + retval_var_name = self.ctx.namer.new_symbol('retval_', scope.referenced) + fn.do_return_var_name = do_return_var_name + fn.retval_var_name = retval_var_name + + node.body = self._visit_statement_block(node, node.body) + + if block.return_used: + + if self.allow_missing_return: + # The function would have a single `with` node that wraps the + # entire body. If the function had a docstring, the body has two + # nodes, with the `with` as the second node. + wrapper_node = node.body[-1] + assert isinstance(wrapper_node, gast.With), ( + 'This transformer requires the functions converter.') + + template = """ + do_return_var_name = False + retval_var_name = ag__.UndefinedReturnValue() + body + return function_context.ret(retval_var_name, do_return_var_name) + """ + + wrapper_node.body = templates.replace( + template, + body=wrapper_node.body, + do_return_var_name=do_return_var_name, + function_context=anno.getanno(node, 'function_context_name'), + retval_var_name=retval_var_name) + else: + template = """ + body + return retval_var_name + """ + node.body = templates.replace( + template, + body=node.body, + do_return_var_name=do_return_var_name, + retval_var_name=retval_var_name) + + return node + + +def transform(node, ctx, default_to_null_return=True): + """Ensure a function has only a single return, at the end.""" + node = qual_names.resolve(node) + node = activity.resolve(node, ctx, None) + + # Note: Technically, these two could be merged into a single walk, but + # keeping them separate helps with readability. + node = ConditionalReturnRewriter(ctx).visit(node) + + node = qual_names.resolve(node) + node = activity.resolve(node, ctx, None) + transformer = ReturnStatementsTransformer( + ctx, allow_missing_return=default_to_null_return) + node = transformer.visit(node) + return node diff --git a/src/braket/experimental/autoqasm/autograph/converters/slices.py b/src/braket/experimental/autoqasm/autograph/converters/slices.py new file mode 100644 index 00000000..48823e6e --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/converters/slices.py @@ -0,0 +1,83 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Converter for slice operations.""" + +import gast + +from braket.experimental.autoqasm.autograph.core import converter +from braket.experimental.autoqasm.autograph.lang import directives +from braket.experimental.autoqasm.autograph.pyct import templates + + +class SliceTransformer(converter.Base): + """Converts slicing operations to their TF counterpart. + + Currently, relying on the default slice operator that Tensor uses is + insufficient, because TensorArray and tensor lists use dedicated index read + and write functions. + """ + + def _process_single_assignment(self, target, value): + if not isinstance(target, gast.Subscript): + return None + s = target.slice + if isinstance(s, (gast.Tuple, gast.Slice)): + return None + + template = """ + target = ag__.set_item(target, key, item) + """ + return templates.replace( + template, target=target.value, key=target.slice, item=value) + + def visit_Assign(self, node): + node = self.generic_visit(node) + # TODO(mdan): Support unpackings and multiple assignments. + if len(node.targets) != 1: + raise NotImplementedError('multiple assignment') + replacement = self._process_single_assignment(node.targets[0], node.value) + if replacement is not None: + return replacement + return node + + def visit_Subscript(self, node): + node = self.generic_visit(node) + s = node.slice + if isinstance(s, (gast.Tuple, gast.Slice)): + return node + + if not isinstance(node.ctx, gast.Load): + # Index writes are handled at a higher level, one at which the rvalue is + # also available. + return node + + dtype = self.get_definition_directive( + node.value, + directives.set_element_type, + 'dtype', + default=templates.replace_as_expression('None')) + + template = """ + ag__.get_item( + target, + key, + opts=ag__.GetItemOpts(element_dtype=dtype)) + """ + return templates.replace_as_expression( + template, target=node.value, key=s, dtype=dtype) + + +def transform(node, ctx): + return SliceTransformer(ctx).visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/converters/variables.py b/src/braket/experimental/autoqasm/autograph/converters/variables.py new file mode 100644 index 00000000..cab01dc6 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/converters/variables.py @@ -0,0 +1,97 @@ +# Copyright 2020 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Overloads all variable read operations.""" + +import gast + +from braket.experimental.autoqasm.autograph.core import converter +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import templates + + +class VariableAccessTransformer(converter.Base): + """Rewrites basic symbol reads. + + This transformer rewrites variable reads with a "read" operator which allows + tracking activity. + + Example: + + For a basic statement: + + a = b + c + + This is translated to: + + a = ld(b) + ld(c) + + Augmented assignment operations also introduce a `ld` operator: + + a += b + + The assignment target also receives an operator to properly represent the + read: + + a = ld(a) + a += ld(b) + """ + + def visit_Name(self, node): + # Only the loads which existed in the original code are overloaded. + if not anno.hasanno(node, anno.Static.ORIG_DEFINITIONS): + return node + if isinstance(node.ctx, gast.Load): + node = templates.replace_as_expression('ag__.ld(var_)', var_=node) + return node + + def visit_Delete(self, node): + node = self.generic_visit(node) + + rewrite_targets = [] + for tgt in node.targets: + # Don't rewrite composites like `del a[0]`. + if isinstance(tgt, gast.Name): + rewrite_targets.append(tgt) + + if not rewrite_targets: + return node + + results = [] + for tgt in rewrite_targets: + template = """ + var_ = ag__.Undefined(var_name) + """ + results.extend(templates.replace( + template, var_=tgt, var_name=gast.Constant(tgt.id, kind=None))) + remaining_targets = [n for n in node.targets if n not in rewrite_targets] + if remaining_targets: + results.append(gast.Delete(targets=remaining_targets)) + + return results + + def visit_AugAssign(self, node): + if isinstance(node.target, gast.Name): + template = """ + var_ = ag__.ld(var_) + original + """ + node = templates.replace(template, var_=node.target, original=node) + else: + node = self.generic_visit(node) + return node + + +def transform(node, ctx): + return VariableAccessTransformer(ctx).visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/core/ag_ctx.py b/src/braket/experimental/autoqasm/autograph/core/ag_ctx.py new file mode 100644 index 00000000..1211ddf2 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/core/ag_ctx.py @@ -0,0 +1,103 @@ +# Copyright 2019 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Thread-local context managers for AutoGraph.""" + +import enum +import inspect +import threading + +from braket.experimental.autoqasm.autograph.logging import ag_logging + + +stacks = threading.local() + + +def _control_ctx(): + if not hasattr(stacks, 'control_status'): + stacks.control_status = [_default_control_status_ctx()] + return stacks.control_status + + +def control_status_ctx(): + """Returns the current control context for autograph. + + This method is useful when calling `tf.__internal__.autograph.tf_convert`, + The context will be used by tf_convert to determine whether it should convert + the input function. See the sample usage like below: + + ``` + def foo(func): + return tf.__internal__.autograph.tf_convert( + input_fn, ctx=tf.__internal__.autograph.control_status_ctx())() + ``` + + Returns: + The current control context of autograph. + """ + ret = _control_ctx()[-1] + return ret + + +class Status(enum.Enum): + UNSPECIFIED = 0 + ENABLED = 1 + DISABLED = 2 + + +class ControlStatusCtx(object): + """A context that tracks whether autograph is enabled by the user.""" + + def __init__(self, status, options=None): + self.status = status + self.options = options + + def __enter__(self): + _control_ctx().append(self) + return self + + def __repr__(self): + return '{}[status={}, options={}]'.format( + self.__class__.__name__, self.status, self.options) + + def __exit__(self, unused_type, unused_value, unused_traceback): + assert _control_ctx()[-1] is self + _control_ctx().pop() + + +class NullCtx(object): + """Helper substitute for contextlib.nullcontext.""" + + def __enter__(self): + pass + + def __exit__(self, unused_type, unused_value, unused_traceback): + pass + + +def _default_control_status_ctx(): + return ControlStatusCtx(status=Status.UNSPECIFIED) + + +INSPECT_SOURCE_SUPPORTED = True +try: + inspect.getsource(ag_logging.log) +except OSError: + INSPECT_SOURCE_SUPPORTED = False + ag_logging.warning( + 'AutoGraph is not available in this environment: functions lack code' + ' information. This is typical of some environments like the interactive' + ' Python shell. See' + ' https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/autograph/g3doc/reference/limitations.md#access-to-source-code' + ' for more information.') diff --git a/src/braket/experimental/autoqasm/autograph/core/config.py b/src/braket/experimental/autoqasm/autograph/core/config.py new file mode 100644 index 00000000..8d9d338b --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/core/config.py @@ -0,0 +1,61 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Global configuration.""" + +from braket.experimental.autoqasm.autograph.core import config_lib + +Action = config_lib.Action +Convert = config_lib.Convert +DoNotConvert = config_lib.DoNotConvert + + +# This list is evaluated in order and stops at the first rule that tests True +# for a definitely_convert of definitely_bypass call. +CONVERSION_RULES = ( + # Known packages + Convert('tensorflow.python.training.experimental'), + + # Builtin modules + DoNotConvert('collections'), + DoNotConvert('copy'), + DoNotConvert('cProfile'), + DoNotConvert('inspect'), + DoNotConvert('ipdb'), + DoNotConvert('linecache'), + DoNotConvert('mock'), + DoNotConvert('pathlib'), + DoNotConvert('pdb'), + DoNotConvert('posixpath'), + DoNotConvert('pstats'), + DoNotConvert('re'), + DoNotConvert('threading'), + DoNotConvert('urllib'), + + # Known libraries + DoNotConvert('matplotlib'), + DoNotConvert('numpy'), + DoNotConvert('pandas'), + DoNotConvert('tensorflow'), + DoNotConvert('PIL'), + DoNotConvert('absl.logging'), + + # TODO(b/133417201): Remove. + DoNotConvert('tensorflow_probability'), + + # TODO(b/133842282): Remove. + DoNotConvert('tensorflow_datasets.core'), + + DoNotConvert('keras'), +) diff --git a/src/braket/experimental/autoqasm/autograph/core/config_lib.py b/src/braket/experimental/autoqasm/autograph/core/config_lib.py new file mode 100644 index 00000000..9065bf36 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/core/config_lib.py @@ -0,0 +1,61 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Global configuration support.""" + +import enum + + +# TODO(mdan): For better performance, allow each rule to take a set names. + + +class Rule(object): + """Base class for conversion rules.""" + + def __init__(self, module_prefix): + self._prefix = module_prefix + + def matches(self, module_name): + return (module_name.startswith(self._prefix + '.') or + module_name == self._prefix) + + +class Action(enum.Enum): + NONE = 0 + CONVERT = 1 + DO_NOT_CONVERT = 2 + + +class DoNotConvert(Rule): + """Indicates that this module should be not converted.""" + + def __str__(self): + return 'DoNotConvert rule for {}'.format(self._prefix) + + def get_action(self, module): + if self.matches(module.__name__): + return Action.DO_NOT_CONVERT + return Action.NONE + + +class Convert(Rule): + """Indicates that this module should be converted.""" + + def __str__(self): + return 'Convert rule for {}'.format(self._prefix) + + def get_action(self, module): + if self.matches(module.__name__): + return Action.CONVERT + return Action.NONE diff --git a/src/braket/experimental/autoqasm/autograph/core/converter.py b/src/braket/experimental/autoqasm/autograph/core/converter.py new file mode 100644 index 00000000..2d928dea --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/core/converter.py @@ -0,0 +1,314 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Converter construction support. + +This module contains a base class for all converters, as well as supporting +structures. These structures are referred to as contexts. + +The class hierarchy is as follows: + + + [extends] converter.Base + [extends] transformer.Base + [extends] gast.nodeTransformer + [uses] transformer.SourceInfo + [uses] converter.EntityContext + [uses] converter.ProgramContext + [uses] transformer.SourceInfo + +converter.Base is a specialization of transformer.Base for AutoGraph. It's a +very lightweight subclass that adds a `ctx` attribute holding the corresponding +EntityContext object (see below). Note that converters are not reusable, and +`visit` will raise an error if called more than once. + +converter.EntityContext contains mutable state associated with an entity that +the converter processes. + +converter.ProgramContext contains mutable state across related entities. For +example, when converting several functions that call one another, the +ProgramContext should be shared across these entities. + +Below is the overall flow at conversion: + + program_ctx = ProgramContext(, , ...) + while : + entity, source_info = + entity_ctx = EntityContext(program_ctx, source_info) + for : + converter = ConverterClass(entity_ctx) + + # May update entity_ctx and program_ctx + entity = converter.visit(entity) + + + +Note that pyct contains a small number of transformers used for static analysis. +These implement transformer.Base, rather than converter.Base, to avoid a +dependency on AutoGraph. +""" + +import enum + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import ast_util +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import templates +from braket.experimental.autoqasm.autograph.pyct import transformer + +# TODO(mdan): These contexts can be refactored into first class objects. +# For example, we could define Program and Entity abstractions that hold on +# to the actual entity and have conversion methods. + +# TODO(mdan): Add a test specific to this converter. + + +class Feature(enum.Enum): + """This enumeration represents optional conversion options. + + These conversion options are experimental. They are subject to change without + notice and offer no guarantees. + + _Example Usage_ + + ```python + optionals= tf.autograph.experimental.Feature.EQUALITY_OPERATORS + @tf.function(experimental_autograph_options=optionals) + def f(i): + if i == 0: # EQUALITY_OPERATORS allows the use of == here. + tf.print('i is zero') + ``` + + Attributes: + ALL: Enable all features. + AUTO_CONTROL_DEPS: Insert of control dependencies in the generated code. + ASSERT_STATEMENTS: Convert Tensor-dependent assert statements to tf.Assert. + BUILTIN_FUNCTIONS: Convert builtin functions applied to Tensors to + their TF counterparts. + EQUALITY_OPERATORS: Whether to convert the equality operator ('==') to + tf.math.equal. + LISTS: Convert list idioms, like initializers, slices, append, etc. + NAME_SCOPES: Insert name scopes that name ops according to context, like the + function they were defined in. + """ + + ALL = 'ALL' + + AUTO_CONTROL_DEPS = 'AUTO_CONTROL_DEPS' + ASSERT_STATEMENTS = 'ASSERT_STATEMENTS' + BUILTIN_FUNCTIONS = 'BUILTIN_FUNCTIONS' + EQUALITY_OPERATORS = 'EQUALITY_OPERATORS' + LISTS = 'LISTS' + NAME_SCOPES = 'NAME_SCOPES' + + @classmethod + def all(cls): + """Returns a tuple that enables all options.""" + return tuple(cls.__members__.values()) + + @classmethod + def all_but(cls, exclude): + """Returns a tuple that enables all but the excluded options.""" + if not isinstance(exclude, (list, tuple, set)): + exclude = (exclude,) + return tuple(set(cls.all()) - set(exclude) - {cls.ALL}) + + +STANDARD_OPTIONS = None # Forward definition. + + +class ConversionOptions(object): + """Immutable container for global conversion flags. + + Attributes: + recursive: bool, whether to recursively convert any user functions or + classes that the converted function may use. + user_requested: bool, whether the conversion was explicitly requested by + the user, as opposed to being performed as a result of other logic. This + value always auto-resets to False in child conversions. + optional_features: Union[Feature, Set[Feature]], controls the use of + optional features in the conversion process. See Feature for available + options. + """ + + def __init__(self, + recursive=False, + user_requested=False, + internal_convert_user_code=True, + optional_features=Feature.ALL): + self.recursive = recursive + self.user_requested = user_requested + # TODO(mdan): Rename to conversion_recursion_depth? + self.internal_convert_user_code = internal_convert_user_code + + if optional_features is None: + optional_features = () + elif isinstance(optional_features, Feature): + optional_features = (optional_features,) + optional_features = frozenset(optional_features) + self.optional_features = optional_features + + def as_tuple(self): + return (self.recursive, self.user_requested, + self.internal_convert_user_code, self.optional_features) + + def __hash__(self): + return hash(self.as_tuple()) + + def __eq__(self, other): + assert isinstance(other, ConversionOptions) + return self.as_tuple() == other.as_tuple() + + def __str__(self): + return 'ConversionOptions[{}]' + + def uses(self, feature): + return (Feature.ALL in self.optional_features or + feature in self.optional_features) + + def call_options(self): + """Returns the corresponding options to be used for recursive conversion.""" + return ConversionOptions( + recursive=self.recursive, + user_requested=False, + internal_convert_user_code=self.recursive, + optional_features=self.optional_features) + + def to_ast(self): + """Returns a representation of this object as an AST node. + + The AST node encodes a constructor that would create an object with the + same contents. + + Returns: + ast.Node + """ + if self == STANDARD_OPTIONS: + return parser.parse_expression('ag__.STD') + + template = """ + ag__.ConversionOptions( + recursive=recursive_val, + user_requested=user_requested_val, + optional_features=optional_features_val, + internal_convert_user_code=internal_convert_user_code_val) + """ + + def list_of_features(values): + return parser.parse_expression('({})'.format(', '.join( + 'ag__.{}'.format(str(v)) for v in values))) + + expr_ast = templates.replace( + template, + recursive_val=parser.parse_expression(str(self.recursive)), + user_requested_val=parser.parse_expression(str(self.user_requested)), + internal_convert_user_code_val=parser.parse_expression( + str(self.internal_convert_user_code)), + optional_features_val=list_of_features(self.optional_features)) + return expr_ast[0].value + + +STANDARD_OPTIONS = ConversionOptions( + recursive=True, + user_requested=False, + internal_convert_user_code=True, + optional_features=None) + + +class ProgramContext(object): + """ProgramContext keeps track of converting function hierarchies. + + Attributes: + options: ConversionOptions + autograph_module: Deprecated. Do not use. + """ + + def __init__(self, options, autograph_module=None): + self.options = options + self.autograph_module = autograph_module + + +class Base(transformer.Base): + """All converters should inherit from this class. + + Attributes: + ctx: EntityContext + """ + + def __init__(self, ctx): + super(Base, self).__init__(ctx) + + self._used = False + self._ast_depth = 0 + + def get_definition_directive(self, node, directive, arg, default): + """Returns the unique directive argument for a symbol. + + See lang/directives.py for details on directives. + + Example: + # Given a directive in the code: + ag.foo_directive(bar, baz=1) + + # One can write for an AST node Name(id='bar'): + get_definition_directive(node, ag.foo_directive, 'baz') + + Args: + node: ast.AST, the node representing the symbol for which the directive + argument is needed. + directive: Callable[..., Any], the directive to search. + arg: str, the directive argument to return. + default: Any + + Raises: + ValueError: if conflicting annotations have been found + """ + defs = anno.getanno(node, anno.Static.ORIG_DEFINITIONS, ()) + if not defs: + return default + + arg_values_found = [] + for def_ in defs: + if (directive in def_.directives and arg in def_.directives[directive]): + arg_values_found.append(def_.directives[directive][arg]) + + if not arg_values_found: + return default + + if len(arg_values_found) == 1: + return arg_values_found[0] + + # If multiple annotations reach the symbol, they must all match. If they do, + # return any of them. + first_value = arg_values_found[0] + for other_value in arg_values_found[1:]: + if not ast_util.matches(first_value, other_value): + qn = anno.getanno(node, anno.Basic.QN) + raise ValueError( + '%s has ambiguous annotations for %s(%s): %s, %s' % + (qn, directive.__name__, arg, parser.unparse(other_value).strip(), + parser.unparse(first_value).strip())) + return first_value + + def visit(self, node): + if not self._ast_depth: + if self._used: + raise ValueError('converter objects cannot be reused') + self._used = True + + self._ast_depth += 1 + try: + return super(Base, self).visit(node) + finally: + self._ast_depth -= 1 diff --git a/src/braket/experimental/autoqasm/autograph/core/function_wrappers.py b/src/braket/experimental/autoqasm/autograph/core/function_wrappers.py new file mode 100644 index 00000000..1d0bb198 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/core/function_wrappers.py @@ -0,0 +1,95 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Support for wrapping converted functions bodies with auxiliary logic.""" + +from braket.experimental.autoqasm.autograph.core import ag_ctx +from braket.experimental.autoqasm.autograph.core import converter +from braket.experimental.autoqasm.autograph.operators import variables + + +# TODO(mdan): Move this into operators - it represents a function definition. + + +class FunctionScope(object): + """Context manager that wraps the body of a converted function. + + This context manager handles various operations related to the scope of a + function: + * optional TF name scopes - these name scopes match the name of the + function, for easy visualization in tensorBoard; + * optional automatic control dependencies - this adds the same mechanism + for control dependencies that is used by `@tf.function`; it can be + optionally enabled when using `tf.autograph.to_graph`; + * tracking of autograph conversion state (whether it's enabled by the user, + conversion options; + """ + + def __init__(self, function_name, scope_name, options): + self.name = scope_name + self.options = options + + if options.user_requested: + self.autograph_ctx = ag_ctx.ControlStatusCtx(ag_ctx.Status.ENABLED, + options) + self.callopts = options.call_options() + + use_name_scope = options.uses(converter.Feature.NAME_SCOPES) + self.use_name_scope = use_name_scope + if use_name_scope: + raise NotImplementedError("name_scopes is TensorFlow-specific") + + use_auto_deps = self.options.uses(converter.Feature.AUTO_CONTROL_DEPS) + self.use_auto_deps = use_auto_deps + if use_auto_deps: + raise NotImplementedError("auto_control_deps is TensorFlow-specific") + + def _sanitize(self, name): + """See https://www.tensorflow.org/api_docs/python/tf/Graph#name_scope.""" + # TensorFlow doesn't like leading underscores at the top level. + if name and name.startswith('_'): + name = 'fn' + name + return name + + def __enter__(self): + if self.options.user_requested: + self.autograph_ctx.__enter__() + if self.use_name_scope: + self.name_scope.__enter__() + if self.use_auto_deps: + self.autodeps_scope.__enter__() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if self.options.user_requested: + self.autograph_ctx.__exit__(exc_type, exc_val, exc_tb) + if self.use_name_scope: + self.name_scope.__exit__(exc_type, exc_val, exc_tb) + if self.use_auto_deps: + self.autodeps_scope.__exit__(exc_type, exc_val, exc_tb) + + def ret(self, value, did_return): + """Marks a value as returned from the function guarded by the scope.""" + del did_return + + if isinstance(value, variables.UndefinedReturnValue): + return None + + return value + + +def with_function_scope(thunk, scope_name, options): + """Inline version of the FunctionScope context manager.""" + with FunctionScope('lambda_', scope_name, options) as scope: + return thunk(scope) diff --git a/src/braket/experimental/autoqasm/autograph/core/unsupported_features_checker.py b/src/braket/experimental/autoqasm/autograph/core/unsupported_features_checker.py new file mode 100644 index 00000000..3e047f33 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/core/unsupported_features_checker.py @@ -0,0 +1,57 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Checkers for detecting unsupported Python features.""" + +import gast + +from braket.experimental.autoqasm.autograph.pyct import errors + + +class UnsupportedFeaturesChecker(gast.NodeVisitor): + """Quick check for Python features we know we don't support. + + Any features detected will cause AutoGraph to not compile a function. + """ + + def visit_Attribute(self, node): + if (node.attr is not None + and node.attr.startswith('__') and not node.attr.endswith('__')): + raise errors.UnsupportedLanguageElementError( + 'mangled names are not yet supported') + self.generic_visit(node) + + def visit_For(self, node): + if node.orelse: + raise errors.UnsupportedLanguageElementError( + 'for/else statement not yet supported') + self.generic_visit(node) + + def visit_While(self, node): + if node.orelse: + raise errors.UnsupportedLanguageElementError( + 'while/else statement not yet supported') + self.generic_visit(node) + + # These checks could potentially be replaced with inspect.isgeneratorfunction + # to avoid a getsource/parse/ast-walk round trip. + def visit_Yield(self, node): + raise errors.UnsupportedLanguageElementError('generators are not supported') + + def visit_YieldFrom(self, node): + raise errors.UnsupportedLanguageElementError('generators are not supported') + + +def verify(node): + UnsupportedFeaturesChecker().visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/impl/api_core.py b/src/braket/experimental/autoqasm/autograph/impl/api_core.py new file mode 100644 index 00000000..1092619a --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/impl/api_core.py @@ -0,0 +1,161 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""This module contains the user- and codegen-facing API for AutoGraph.""" + +import inspect +import os +import sys +import traceback + +from braket.experimental.autoqasm.autograph.pyct import error_utils +from braket.experimental.autoqasm.autograph.pyct import errors +from braket.experimental.autoqasm.autograph.pyct import origin_info +from braket.experimental.autoqasm.autograph.logging import ag_logging as logging +from braket.experimental.autoqasm.autograph.tf_utils import tf_stack +import inspect as tf_inspect + + +def is_autograph_strict_conversion_mode(): + return int(os.environ.get('AUTOGRAPH_STRICT_CONVERSION', '0')) > 0 + + +def _log_callargs(f, args, kwargs): + """Logging helper.""" + logging.log(2, 'Defaults of %s : %s', f, f.__defaults__) + logging.log(2, 'KW defaults of %s : %s', f, f.__kwdefaults__) + + if kwargs is not None: + callargs = tf_inspect.getcallargs(f, *args, **kwargs) + else: + callargs = tf_inspect.getcallargs(f, *args) + + formatted_callargs = '\n'.join( + ' {}: {}'.format(k, v) for k, v in callargs.items()) + logging.log(2, 'Calling %s with\n%s\n', f, formatted_callargs) + + +# +# Error handling +# + + +# TODO(mdan): Export this symbol. +class AutoGraphError(errors.PyCTError): + """Base class for all AutoGraph exceptions.""" + pass + + +class ConversionError(AutoGraphError): + """Raised during the conversion process.""" + pass + + +class StagingError(AutoGraphError): + """Raised during the staging (i.e. Python execution) of converted code.""" + pass + + +class _ErrorMetadata(error_utils.ErrorMetadataBase): + """AutoGraph-specific error metadata. See base class.""" + + def create_exception(self, source_error): + preferred_type = type(source_error) + if preferred_type in (errors.PyCTError, AutoGraphError, ConversionError, + StagingError): + return preferred_type(self.get_message()) + + exc = super(_ErrorMetadata, self).create_exception(source_error) + if exc is not None: + return exc + + # Note: While changing an error's message property to change the message it + # displays will probably work a lot of times, there is no standard way in + # Python to do that. The safest way is therefore to create a new exception. + # For user defined exceptions, we could define an interface that allowed + # them to work under this mechanism. + return StagingError(self.get_message()) + + +def _attach_error_metadata(e, f): + """Augments an error with the metadata necessary for rewrite.""" + if hasattr(e, 'ag_pass_through'): + return + + metadata = getattr(e, 'ag_error_metadata', None) + source_map = f.ag_source_map + + if metadata is None: + logging.log(1, 'Caught error in user callable %s', f, exc_info=True) + message = '{}: {}'.format(e.__class__.__name__, e) + else: + message = None + + cause_tb = traceback.extract_tb(sys.exc_info()[2])[1:] + + e.ag_error_metadata = _ErrorMetadata(cause_tb, metadata, message, source_map, + __file__) + + +class StackTraceMapper(tf_stack.StackTraceMapper): + """Remaps generated code to code it originated from.""" + + def __init__(self, converted_fn): + super().__init__() + self._source_map = converted_fn.ag_source_map + # This may be called repeatedly: once on entry, by the superclass, then by + # each child context manager. + self._cached_map = None + + def get_effective_source_map(self): + if self._cached_map is not None: + return self._cached_map + + parent_map = self.parent.get_effective_source_map() + + effective_source_map = {} + for loc, origin in self._source_map.items(): + effective_source_map[(loc.filename, loc.lineno)] = (origin.loc.filename, + origin.loc.lineno, + origin.function_name) + + for key, value in parent_map.items(): + filename, lineno, _ = value + value_loc = origin_info.LineLocation(filename=filename, lineno=lineno) + if value_loc in self._source_map: + origin = self._source_map[value_loc] + effective_source_map[key] = (origin.loc.filename, origin.loc.lineno, + origin.function_name) + else: + effective_source_map[key] = value + + self._cached_map = effective_source_map + return effective_source_map + + +# +# Generated code support +# + + +def autograph_artifact(entity, extras=None): + if inspect.ismethod(entity): + setattr(entity.__func__, 'autograph_info__', extras) + else: + setattr(entity, 'autograph_info__', extras) + return entity + + +def is_autograph_artifact(entity): + return hasattr(entity, 'autograph_info__') diff --git a/src/braket/experimental/autoqasm/autograph/lang/directives.py b/src/braket/experimental/autoqasm/autograph/lang/directives.py new file mode 100644 index 00000000..8072d008 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/lang/directives.py @@ -0,0 +1,91 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Directives are special no-op functions that serve as compilation markers. + +They provide static information like type hints, compilation and TensorFlow +overrides. + +These serve as annotations in the compiled code, allowing the user some control +over the compilation process. They have no functional role at runtime. +""" + +UNSPECIFIED = object() + + +def set_element_type(entity, dtype, shape=UNSPECIFIED): + """Indicates that the entity is expected hold items of specified type/shape. + + The staged TensorFlow ops will reflect and assert this data type. Ignored + otherwise. + + Args: + entity: The entity to annotate. + dtype: TensorFlow dtype value to assert for entity. + shape: Optional shape to assert for entity. + """ + del entity + del dtype + del shape + + +def set_loop_options( + parallel_iterations=UNSPECIFIED, + swap_memory=UNSPECIFIED, + maximum_iterations=UNSPECIFIED, + shape_invariants=UNSPECIFIED): + """Specifies additional arguments to be passed to the enclosing while_loop. + + The parameters apply to and only to the immediately enclosing loop. It only + has effect if the loop is staged as a TF while_loop; otherwise the parameters + have no effect. + + Usage: + + >>> @tf.function(autograph=True) + ... def f(): + ... n = 0 + ... for i in tf.range(10): + ... tf.autograph.experimental.set_loop_options(maximum_iterations=3) + ... n += 1 + ... return n + + >>> @tf.function(autograph=True) + ... def f(): + ... v = tf.constant((0,)) + ... for i in tf.range(3): + ... tf.autograph.experimental.set_loop_options( + ... shape_invariants=[(v, tf.TensorShape([None]))] + ... ) + ... v = tf.concat((v, [i]), 0) + ... return v + + Also see tf.while_loop. + + Args: + parallel_iterations: The maximum number of iterations allowed to run in + parallel at any given time. Note that this does not guarantee parallel + execution. + swap_memory: Whether to store intermediate values needed for + gradients on the CPU instead of GPU. + maximum_iterations: Allows limiting the total number of iterations executed + by the loop. + shape_invariants: Allows controlling the argument with the same name passed + to tf.while_loop. Unlike tf.while_loop, this is a list of + `(tensor, shape)` pairs. + """ + del parallel_iterations + del swap_memory + del maximum_iterations + del shape_invariants diff --git a/src/braket/experimental/autoqasm/autograph/logging/ag_logging.py b/src/braket/experimental/autoqasm/autograph/logging/ag_logging.py new file mode 100644 index 00000000..36a13a7e --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/logging/ag_logging.py @@ -0,0 +1,142 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Logging and debugging utilities.""" + +import os +import sys +import traceback + +# TODO(mdan): Use a custom logger class. +import logging + +VERBOSITY_VAR_NAME = 'AUTOGRAPH_VERBOSITY' +DEFAULT_VERBOSITY = 0 + +verbosity_level = None # vlog-like. Takes precedence over the env variable. +echo_log_to_stdout = False + +# In interactive Python, logging echo is enabled by default. +if hasattr(sys, 'ps1') or hasattr(sys, 'ps2'): + echo_log_to_stdout = True + + +def set_verbosity(level, alsologtostdout=False): + """Sets the AutoGraph verbosity level. + + _Debug logging in AutoGraph_ + + More verbose logging is useful to enable when filing bug reports or doing + more in-depth debugging. + + There are two means to control the logging verbosity: + + * The `set_verbosity` function + + * The `AUTOGRAPH_VERBOSITY` environment variable + + `set_verbosity` takes precedence over the environment variable. + + For example: + + ```python + import os + import tensorflow as tf + + os.environ['AUTOGRAPH_VERBOSITY'] = '5' + # Verbosity is now 5 + + tf.autograph.set_verbosity(0) + # Verbosity is now 0 + + os.environ['AUTOGRAPH_VERBOSITY'] = '1' + # No effect, because set_verbosity was already called. + ``` + + Logs entries are output to [absl](https://abseil.io)'s + [default output](https://abseil.io/docs/python/guides/logging), + with `INFO` level. + Logs can be mirrored to stdout by using the `alsologtostdout` argument. + Mirroring is enabled by default when Python runs in interactive mode. + + Args: + level: int, the verbosity level; larger values specify increased verbosity; + 0 means no logging. When reporting bugs, it is recommended to set this + value to a larger number, like 10. + alsologtostdout: bool, whether to also output log messages to `sys.stdout`. + """ + global verbosity_level + global echo_log_to_stdout + verbosity_level = level + echo_log_to_stdout = alsologtostdout + + +def trace(*args): + """Traces argument information at compilation time. + + `trace` is useful when debugging, and it always executes during the tracing + phase, that is, when the TF graph is constructed. + + _Example usage_ + + ```python + import tensorflow as tf + + for i in tf.range(10): + tf.autograph.trace(i) + # Output: + ``` + + Args: + *args: Arguments to print to `sys.stdout`. + """ + print(*args) + + +def get_verbosity(): + global verbosity_level + if verbosity_level is not None: + return verbosity_level + return int(os.getenv(VERBOSITY_VAR_NAME, DEFAULT_VERBOSITY)) + + +def has_verbosity(level): + return get_verbosity() >= level + + +def _output_to_stdout(msg, *args, **kwargs): + print(msg % args) + if kwargs.get('exc_info', False): + traceback.print_exc() + + +def error(level, msg, *args, **kwargs): + if has_verbosity(level): + logging.error(msg, *args, **kwargs) + if echo_log_to_stdout: + _output_to_stdout('ERROR: ' + msg, *args, **kwargs) + + +def log(level, msg, *args, **kwargs): + if has_verbosity(level): + logging.info(msg, *args, **kwargs) + if echo_log_to_stdout: + _output_to_stdout(msg, *args, **kwargs) + + +def warning(msg, *args, **kwargs): + logging.warning(msg, *args, **kwargs) + if echo_log_to_stdout: + _output_to_stdout('WARNING: ' + msg, *args, **kwargs) + sys.stdout.flush() diff --git a/src/braket/experimental/autoqasm/autograph/operators/variables.py b/src/braket/experimental/autoqasm/autograph/operators/variables.py new file mode 100644 index 00000000..115c4470 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/operators/variables.py @@ -0,0 +1,104 @@ +# Copyright 2019 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Utilities used to capture Python idioms.""" + + +def ld(v): + """Load variable operator.""" + if isinstance(v, Undefined): + return v.read() + return v + + +def ldu(load_v, name): + """Load variable operator that returns Undefined when failing to evaluate. + + Note: the name ("load or return undefined") is abbreviated to minimize + the amount of clutter in generated code. + + This variant of `ld` is useful when loading symbols that may be undefined at + runtime, such as composite symbols, and whether they are defined or not cannot + be determined statically. For example `d['a']` is undefined when `d` is an + empty dict. + + Args: + load_v: Lambda that executes the actual read. + name: Human-readable name of the symbol being read. + Returns: + Either the value of the symbol, or Undefined, if the symbol is not fully + defined. + """ + try: + # TODO(mdan): Use locals()/globals() here. + return load_v() + except (KeyError, AttributeError, NameError): + return Undefined(name) + + +class Undefined(object): + """Represents an undefined symbol in Python. + + This is used to reify undefined symbols, which is required to use the + functional form of loops. + Example: + + while n > 0: + n = n - 1 + s = n + return s # Runtime error if n == 0 + + This is valid Python code and will not result in an error as long as n + is positive. The use of this class is to stay as close to Python semantics + as possible for staged code of this nature. + + Converted version of the above showing the possible usage of this class: + + s = Undefined('s') + init_state = (s,) + s = while_loop(cond, body, init_state) + return s # s is an instance of Undefined if the loop never runs + + Attributes: + symbol_name: Text, identifier for the undefined symbol + """ + + __slots__ = ('symbol_name',) + + def __init__(self, symbol_name): + self.symbol_name = symbol_name + + def read(self): + raise UnboundLocalError("'{}' is used before assignment".format( + self.symbol_name)) + + def __repr__(self): + return self.symbol_name + + def __getattribute__(self, name): + try: + # If it's an existing attribute, return it. + return object.__getattribute__(self, name) + except AttributeError: + # Otherwise return Undefined. + return self + + def __getitem__(self, i): + return self + + +# TODO(mdan): Refactor as a RetVal object, aggregating the value and do_return. +class UndefinedReturnValue(object): + """Represents a return value that is undefined.""" + pass diff --git a/src/braket/experimental/autoqasm/autograph/operators/variables_test.py b/src/braket/experimental/autoqasm/autograph/operators/variables_test.py new file mode 100644 index 00000000..d7856571 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/operators/variables_test.py @@ -0,0 +1,51 @@ +# Copyright 2019 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for python_lang_utils module.""" + +from braket.experimental.autoqasm.autograph.operators import variables +from tensorflow.python.platform import test + + +class SpecialValuesTest(test.TestCase): + + def test_undefined(self): + undefined_symbol = variables.Undefined('name') + undefined_symbol2 = variables.Undefined('name') + + self.assertEqual(undefined_symbol.symbol_name, 'name') + self.assertEqual(undefined_symbol2.symbol_name, 'name') + self.assertNotEqual(undefined_symbol, undefined_symbol2) + + def test_undefined_operations(self): + undefined_symbol = variables.Undefined('name') + + self.assertIsInstance(undefined_symbol.foo, variables.Undefined) + self.assertIsInstance(undefined_symbol[0], variables.Undefined) + self.assertNotIsInstance(undefined_symbol.__class__, variables.Undefined) + + def test_read(self): + self.assertEqual(variables.ld(1), 1) + o = object() + self.assertEqual(variables.ld(o), o) + + self.assertIsNone(variables.ld(None)) + + def test_read_undefined(self): + with self.assertRaisesRegex(UnboundLocalError, 'used before assignment'): + variables.ld(variables.Undefined('a')) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/__init__.py b/src/braket/experimental/autoqasm/autograph/pyct/__init__.py new file mode 100644 index 00000000..9202a5ce --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Python source code transformation library.""" + diff --git a/src/braket/experimental/autoqasm/autograph/pyct/anno.py b/src/braket/experimental/autoqasm/autograph/pyct/anno.py new file mode 100644 index 00000000..59ff7035 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/anno.py @@ -0,0 +1,174 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""AST node annotation support. + +Adapted from Tangent. +""" + +import enum + +# pylint:disable=g-bad-import-order + +import gast +# pylint:enable=g-bad-import-order + + +# TODO(mdan): Shorten the names. +# These names are heavily used, and anno.blaa +# TODO(mdan): Replace the attr-dict mechanism with a more typed solution. + + +class NoValue(enum.Enum): + """Base class for different types of AST annotations.""" + + def of(self, node, default=None): + return getanno(node, self, default=default) + + def add_to(self, node, value): + setanno(node, self, value) + + def exists(self, node): + return hasanno(node, self) + + def __repr__(self): + return str(self.name) + + +class Basic(NoValue): + """Container for basic annotation keys. + + The enum values are used strictly for documentation purposes. + """ + + QN = 'Qualified name, as it appeared in the code. See qual_names.py.' + SKIP_PROCESSING = ( + 'This node should be preserved as is and not processed any further.') + INDENT_BLOCK_REMAINDER = ( + 'When a node is annotated with this, the remainder of the block should' + ' be indented below it. The annotation contains a tuple' + ' (new_body, name_map), where `new_body` is the new indented block and' + ' `name_map` allows renaming symbols.') + ORIGIN = ('Information about the source code that converted code originated' + ' from. See origin_information.py.') + DIRECTIVES = ('User directives associated with a statement or a variable.' + ' Typically, they affect the immediately-enclosing statement.') + + EXTRA_LOOP_TEST = ( + 'A special annotation containing additional test code to be executed in' + ' for loops.') + + +class Static(NoValue): + """Container for static analysis annotation keys. + + The enum values are used strictly for documentation purposes. + """ + + # Symbols + # These flags are boolean. + IS_PARAM = 'Symbol is a parameter to the function being analyzed.' + + # Scopes + # Scopes are represented by objects of type activity.Scope. + SCOPE = 'The scope for the annotated node. See activity.py.' + # TODO(mdan): Drop these in favor of accessing the child's SCOPE. + ARGS_SCOPE = 'The scope for the argument list of a function call.' + COND_SCOPE = 'The scope for the test node of a conditional statement.' + BODY_SCOPE = ( + 'The scope for the main body of a statement (True branch for if ' + 'statements, main body for loops).') + ORELSE_SCOPE = ( + 'The scope for the orelse body of a statement (False branch for if ' + 'statements, orelse body for loops).') + + # Static analysis annotations. + DEFINITIONS = ( + 'Reaching definition information. See reaching_definitions.py.') + ORIG_DEFINITIONS = ( + 'The value of DEFINITIONS that applied to the original code before any' + ' conversion.') + DEFINED_FNS_IN = ( + 'Local function definitions that may exist when exiting the node. See' + ' reaching_fndefs.py') + DEFINED_VARS_IN = ( + 'Symbols defined when entering the node. See reaching_definitions.py.') + LIVE_VARS_OUT = ('Symbols live when exiting the node. See liveness.py.') + LIVE_VARS_IN = ('Symbols live when entering the node. See liveness.py.') + TYPES = 'Static type information. See type_inference.py.' + CLOSURE_TYPES = 'Types of closure symbols at each detected call site.' + VALUE = 'Static value information. See type_inference.py.' + + +FAIL = object() + + +def keys(node, field_name='___pyct_anno'): + if not hasattr(node, field_name): + return frozenset() + return frozenset(getattr(node, field_name).keys()) + + +def getanno(node, key, default=FAIL, field_name='___pyct_anno'): + if (default is FAIL or (hasattr(node, field_name) and + (key in getattr(node, field_name)))): + return getattr(node, field_name)[key] + return default + + +def hasanno(node, key, field_name='___pyct_anno'): + return hasattr(node, field_name) and key in getattr(node, field_name) + + +def setanno(node, key, value, field_name='___pyct_anno'): + annotations = getattr(node, field_name, {}) + setattr(node, field_name, annotations) + annotations[key] = value + + # So that the annotations survive gast_to_ast() and ast_to_gast() + if field_name not in node._fields: + node._fields += (field_name,) + + +def delanno(node, key, field_name='___pyct_anno'): + annotations = getattr(node, field_name) + del annotations[key] + if not annotations: + delattr(node, field_name) + node._fields = tuple(f for f in node._fields if f != field_name) + + +def copyanno(from_node, to_node, key, field_name='___pyct_anno'): + if hasanno(from_node, key, field_name=field_name): + setanno( + to_node, + key, + getanno(from_node, key, field_name=field_name), + field_name=field_name) + + +def dup(node, copy_map, field_name='___pyct_anno'): + """Recursively copies annotations in an AST tree. + + Args: + node: ast.AST + copy_map: Dict[Hashable, Hashable], maps a source anno key to a destination + key. All annotations with the source key will be copied to identical + annotations with the destination key. + field_name: str + """ + for n in gast.walk(node): + for k in copy_map: + if hasanno(n, k, field_name): + setanno(n, copy_map[k], getanno(n, k, field_name), field_name) diff --git a/src/braket/experimental/autoqasm/autograph/pyct/anno_test.py b/src/braket/experimental/autoqasm/autograph/pyct/anno_test.py new file mode 100644 index 00000000..77fefdff --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/anno_test.py @@ -0,0 +1,80 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for anno module.""" + +import ast + +from braket.experimental.autoqasm.autograph.pyct import anno +from tensorflow.python.platform import test + + +# TODO(mdan): Consider strong types instead of primitives. + + +class AnnoTest(test.TestCase): + + def test_basic(self): + node = ast.Name() + + self.assertEqual(anno.keys(node), set()) + self.assertFalse(anno.hasanno(node, 'foo')) + with self.assertRaises(AttributeError): + anno.getanno(node, 'foo') + + anno.setanno(node, 'foo', 3) + + self.assertEqual(anno.keys(node), {'foo'}) + self.assertTrue(anno.hasanno(node, 'foo')) + self.assertEqual(anno.getanno(node, 'foo'), 3) + self.assertEqual(anno.getanno(node, 'bar', default=7), 7) + + anno.delanno(node, 'foo') + + self.assertEqual(anno.keys(node), set()) + self.assertFalse(anno.hasanno(node, 'foo')) + with self.assertRaises(AttributeError): + anno.getanno(node, 'foo') + self.assertIsNone(anno.getanno(node, 'foo', default=None)) + + def test_copy(self): + node_1 = ast.Name() + anno.setanno(node_1, 'foo', 3) + + node_2 = ast.Name() + anno.copyanno(node_1, node_2, 'foo') + anno.copyanno(node_1, node_2, 'bar') + + self.assertTrue(anno.hasanno(node_2, 'foo')) + self.assertFalse(anno.hasanno(node_2, 'bar')) + + def test_duplicate(self): + node = ast.If( + test=ast.Num(1), + body=[ast.Expr(ast.Name('bar', ast.Load()))], + orelse=[]) + anno.setanno(node, 'spam', 1) + anno.setanno(node, 'ham', 1) + anno.setanno(node.body[0], 'ham', 1) + + anno.dup(node, {'spam': 'eggs'}) + + self.assertTrue(anno.hasanno(node, 'spam')) + self.assertTrue(anno.hasanno(node, 'ham')) + self.assertTrue(anno.hasanno(node, 'eggs')) + self.assertFalse(anno.hasanno(node.body[0], 'eggs')) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/ast_util.py b/src/braket/experimental/autoqasm/autograph/pyct/ast_util.py new file mode 100644 index 00000000..2b0aa2fb --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/ast_util.py @@ -0,0 +1,344 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""AST manipulation utilities.""" + +import ast + +import gast + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import qual_names + + +class CleanCopier(object): + """NodeTransformer-like visitor that copies an AST.""" + + def __init__(self, preserve_annos): + super(CleanCopier, self).__init__() + self.preserve_annos = preserve_annos + + def copy(self, node): + """Returns a deep copy of node (excluding some fields, see copy_clean).""" + + if isinstance(node, list): + return [self.copy(n) for n in node] + elif isinstance(node, tuple): + return tuple(self.copy(n) for n in node) + elif not isinstance(node, (gast.AST, ast.AST)): + # Assuming everything that's not an AST, list or tuple is a value type + # and may simply be assigned. + return node + + assert isinstance(node, (gast.AST, ast.AST)) + + new_fields = {} + for f in node._fields: + if not f.startswith('__') and hasattr(node, f): + new_fields[f] = self.copy(getattr(node, f)) + new_node = type(node)(**new_fields) + + if self.preserve_annos: + for k in self.preserve_annos: + anno.copyanno(node, new_node, k) + return new_node + + +def copy_clean(node, preserve_annos=None): + """Creates a deep copy of an AST. + + The copy will not include fields that are prefixed by '__', with the + exception of user-specified annotations. + + Args: + node: ast.AST + preserve_annos: Optional[Set[Hashable]], annotation keys to include in the + copy + Returns: + ast.AST + """ + return CleanCopier(preserve_annos).copy(node) + + +class SymbolRenamer(gast.NodeTransformer): + """Transformer that can rename symbols to a simple names.""" + + def __init__(self, name_map): + self.name_map = name_map + + def _process_name_node(self, node): + qn = anno.getanno(node, anno.Basic.QN) + if qn in self.name_map: + new_node = gast.Name( + str(self.name_map[qn]), + ctx=node.ctx, + annotation=None, + type_comment=None) + # All annotations get carried over. + for k in anno.keys(node): + anno.copyanno(node, new_node, k) + return new_node + return self.generic_visit(node) + + def _process_list_of_strings(self, names): + for i in range(len(names)): + qn = qual_names.QN(names[i]) + if qn in self.name_map: + names[i] = str(self.name_map[qn]) + return names + + def visit_Nonlocal(self, node): + node.names = self._process_list_of_strings(node.names) + return node + + def visit_Global(self, node): + node.names = self._process_list_of_strings(node.names) + return node + + def visit_Name(self, node): + return self._process_name_node(node) + + def visit_Attribute(self, node): + if anno.hasanno(node, anno.Basic.QN): + return self._process_name_node(node) + # Renaming attributes is not supported. + return self.generic_visit(node) + + def visit_FunctionDef(self, node): + qn = qual_names.QN(node.name) + if qn in self.name_map: + node.name = str(self.name_map[qn]) + return self.generic_visit(node) + + +def rename_symbols(node, name_map): + """Renames symbols in an AST. Requires qual_names annotations.""" + renamer = SymbolRenamer(name_map) + if isinstance(node, list): + return [renamer.visit(n) for n in node] + elif isinstance(node, tuple): + return tuple(renamer.visit(n) for n in node) + return renamer.visit(node) + + +def keywords_to_dict(keywords): + """Converts a list of ast.keyword objects to a dict.""" + keys = [] + values = [] + for kw in keywords: + keys.append(gast.Constant(kw.arg, kind=None)) + values.append(kw.value) + return gast.Dict(keys=keys, values=values) + + +class PatternMatcher(gast.NodeVisitor): + """Matches a node against a pattern represented by a node.""" + + def __init__(self, pattern): + self.pattern = pattern + self.pattern_stack = [] + self.matches = True + + def compare_and_visit(self, node, pattern): + self.pattern_stack.append(self.pattern) + self.pattern = pattern + self.generic_visit(node) + self.pattern = self.pattern_stack.pop() + + def no_match(self): + self.matches = False + return False + + def is_wildcard(self, p): + if isinstance(p, (list, tuple)) and len(p) == 1: + p, = p + if isinstance(p, gast.Name) and p.id == '_': + return True + if p == '_': + return True + return False + + def generic_visit(self, node): + if not self.matches: + return + + pattern = self.pattern + for f in node._fields: + if f.startswith('__'): + continue + + if not hasattr(node, f): + if hasattr(pattern, f) and getattr(pattern, f): + return self.no_match() + else: + continue + if not hasattr(pattern, f): + return self.no_match() + + v = getattr(node, f) + p = getattr(pattern, f) + + if self.is_wildcard(p): + continue + if isinstance(v, (list, tuple)): + if not isinstance(p, (list, tuple)) or len(v) != len(p): + return self.no_match() + for v_item, p_item in zip(v, p): + self.compare_and_visit(v_item, p_item) + elif isinstance(v, (gast.AST, ast.AST)): + if not isinstance(v, type(p)) and not isinstance(p, type(v)): + return self.no_match() + self.compare_and_visit(v, p) + else: + # Assume everything else is a value type. + if v != p: + return self.no_match() + + +def matches(node, pattern): + """Basic pattern matcher for AST. + + The pattern may contain wildcards represented by the symbol '_'. A node + matches a pattern if for every node in the tree, either there is a node of + the same type in pattern, or a Name node with id='_'. + + Args: + node: ast.AST + pattern: ast.AST + Returns: + bool + """ + if isinstance(pattern, str): + pattern = parser.parse_str(pattern) + + matcher = PatternMatcher(pattern) + matcher.visit(node) + return matcher.matches + + +# TODO(mdan): Once we have error tracing, we may be able to just go to SSA. +def apply_to_single_assignments(targets, values, apply_fn): + """Applies a function to each individual assignment. + + This function can process a possibly-unpacked (e.g. a, b = c, d) assignment. + It tries to break down the unpacking if possible. In effect, it has the same + effect as passing the assigned values in SSA form to apply_fn. + + Examples: + + The following will result in apply_fn(a, c), apply_fn(b, d): + + a, b = c, d + + The following will result in apply_fn(a, c[0]), apply_fn(b, c[1]): + + a, b = c + + The following will result in apply_fn(a, (b, c)): + + a = b, c + + It uses the visitor pattern to allow subclasses to process single + assignments individually. + + Args: + targets: Union[List[ast.AST, ...], Tuple[ast.AST, ...], ast.AST, should be + used with the targets field of an ast.Assign node + values: ast.AST + apply_fn: Callable[[ast.AST, ast.AST], None], called with the + respective nodes of each single assignment + """ + if not isinstance(targets, (list, tuple)): + targets = (targets,) + for target in targets: + if isinstance(target, (gast.Tuple, gast.List)): + for i in range(len(target.elts)): + target_el = target.elts[i] + if isinstance(values, (gast.Tuple, gast.List)): + value_el = values.elts[i] + else: + idx = parser.parse_expression(str(i)) + value_el = gast.Subscript(values, idx, ctx=gast.Load()) + apply_to_single_assignments(target_el, value_el, apply_fn) + else: + apply_fn(target, values) + + +def parallel_walk(node, other): + """Walks two ASTs in parallel. + + The two trees must have identical structure. + + Args: + node: Union[ast.AST, Iterable[ast.AST]] + other: Union[ast.AST, Iterable[ast.AST]] + Yields: + Tuple[ast.AST, ast.AST] + Raises: + ValueError: if the two trees don't have identical structure. + """ + if isinstance(node, (list, tuple)): + node_stack = list(node) + else: + node_stack = [node] + + if isinstance(other, (list, tuple)): + other_stack = list(other) + else: + other_stack = [other] + + while node_stack and other_stack: + assert len(node_stack) == len(other_stack) + n = node_stack.pop() + o = other_stack.pop() + + if ((not isinstance(n, (ast.AST, gast.AST, str)) and n is not None) or + (not isinstance(o, (ast.AST, gast.AST, str)) and n is not None) or + n.__class__.__name__ != o.__class__.__name__): + raise ValueError('inconsistent nodes: {} ({}) and {} ({})'.format( + n, n.__class__.__name__, o, o.__class__.__name__)) + + yield n, o + + if isinstance(n, str): + assert isinstance(o, str), 'The check above should have ensured this' + continue + if n is None: + assert o is None, 'The check above should have ensured this' + continue + + for f in n._fields: + n_child = getattr(n, f, None) + o_child = getattr(o, f, None) + if f.startswith('__') or n_child is None or o_child is None: + continue + + if isinstance(n_child, (list, tuple)): + if (not isinstance(o_child, (list, tuple)) or + len(n_child) != len(o_child)): + raise ValueError( + 'inconsistent values for field {}: {} and {}'.format( + f, n_child, o_child)) + node_stack.extend(n_child) + other_stack.extend(o_child) + + elif isinstance(n_child, (gast.AST, ast.AST)): + node_stack.append(n_child) + other_stack.append(o_child) + + elif n_child != o_child: + raise ValueError( + 'inconsistent values for field {}: {} and {}'.format( + f, n_child, o_child)) diff --git a/src/braket/experimental/autoqasm/autograph/pyct/ast_util_test.py b/src/braket/experimental/autoqasm/autograph/pyct/ast_util_test.py new file mode 100644 index 00000000..7ced51c1 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/ast_util_test.py @@ -0,0 +1,246 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for ast_util module.""" + +import ast +import collections +import textwrap + +import gast + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import ast_util +from braket.experimental.autoqasm.autograph.pyct import loader +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import pretty_printer +from braket.experimental.autoqasm.autograph.pyct import qual_names +from tensorflow.python.platform import test + + +class AstUtilTest(test.TestCase): + + def assertAstMatches(self, actual_node, expected_node_src): + expected_node = gast.parse('({})'.format(expected_node_src)).body[0] + msg = 'AST did not match expected:\n{}\nActual:\n{}'.format( + pretty_printer.fmt(expected_node), + pretty_printer.fmt(actual_node)) + self.assertTrue(ast_util.matches(actual_node, expected_node), msg) + + def setUp(self): + super(AstUtilTest, self).setUp() + self._invocation_counts = collections.defaultdict(lambda: 0) + + def test_rename_symbols_basic(self): + node = parser.parse('a + b') + node = qual_names.resolve(node) + + node = ast_util.rename_symbols( + node, {qual_names.QN('a'): qual_names.QN('renamed_a')}) + source = parser.unparse(node, include_encoding_marker=False) + expected_node_src = 'renamed_a + b' + + self.assertIsInstance(node.value.left.id, str) + self.assertAstMatches(node, source) + self.assertAstMatches(node, expected_node_src) + + def test_rename_symbols_attributes(self): + node = parser.parse('b.c = b.c.d') + node = qual_names.resolve(node) + + node = ast_util.rename_symbols( + node, {qual_names.from_str('b.c'): qual_names.QN('renamed_b_c')}) + + source = parser.unparse(node, include_encoding_marker=False) + self.assertEqual(source.strip(), 'renamed_b_c = renamed_b_c.d') + + def test_rename_symbols_nonlocal(self): + node = parser.parse('nonlocal a, b, c') + node = qual_names.resolve(node) + + node = ast_util.rename_symbols( + node, {qual_names.from_str('b'): qual_names.QN('renamed_b')}) + + source = parser.unparse(node, include_encoding_marker=False) + self.assertEqual(source.strip(), 'nonlocal a, renamed_b, c') + + def test_rename_symbols_global(self): + node = parser.parse('global a, b, c') + node = qual_names.resolve(node) + + node = ast_util.rename_symbols( + node, {qual_names.from_str('b'): qual_names.QN('renamed_b')}) + + source = parser.unparse(node, include_encoding_marker=False) + self.assertEqual(source.strip(), 'global a, renamed_b, c') + + def test_rename_symbols_annotations(self): + node = parser.parse('a[i]') + node = qual_names.resolve(node) + anno.setanno(node, 'foo', 'bar') + orig_anno = anno.getanno(node, 'foo') + + node = ast_util.rename_symbols(node, + {qual_names.QN('a'): qual_names.QN('b')}) + + self.assertIs(anno.getanno(node, 'foo'), orig_anno) + + def test_rename_symbols_function(self): + node = parser.parse('def f():\n pass') + node = ast_util.rename_symbols(node, + {qual_names.QN('f'): qual_names.QN('f1')}) + + source = parser.unparse(node, include_encoding_marker=False) + self.assertEqual(source.strip(), 'def f1():\n pass') + + def test_copy_clean(self): + node = parser.parse( + textwrap.dedent(""" + def f(a): + return a + 1 + """)) + setattr(node, '__foo', 'bar') + new_node = ast_util.copy_clean(node) + self.assertIsNot(new_node, node) + self.assertFalse(hasattr(new_node, '__foo')) + + def test_copy_clean_preserves_annotations(self): + node = parser.parse( + textwrap.dedent(""" + def f(a): + return a + 1 + """)) + anno.setanno(node, 'foo', 'bar') + anno.setanno(node, 'baz', 1) + new_node = ast_util.copy_clean(node, preserve_annos={'foo'}) + self.assertEqual(anno.getanno(new_node, 'foo'), 'bar') + self.assertFalse(anno.hasanno(new_node, 'baz')) + + def test_keywords_to_dict(self): + keywords = parser.parse_expression('f(a=b, c=1, d=\'e\')').keywords + d = ast_util.keywords_to_dict(keywords) + # Make sure we generate a usable dict node by attaching it to a variable and + # compiling everything. + node = parser.parse('def f(b): pass') + node.body.append(ast.Return(d)) + result, _, _ = loader.load_ast(node) + self.assertDictEqual(result.f(3), {'a': 3, 'c': 1, 'd': 'e'}) + + def assertMatch(self, target_str, pattern_str): + node = parser.parse_expression(target_str) + pattern = parser.parse_expression(pattern_str) + self.assertTrue(ast_util.matches(node, pattern)) + + def assertNoMatch(self, target_str, pattern_str): + node = parser.parse_expression(target_str) + pattern = parser.parse_expression(pattern_str) + self.assertFalse(ast_util.matches(node, pattern)) + + def test_matches_symbols(self): + self.assertMatch('foo', '_') + self.assertNoMatch('foo()', '_') + self.assertMatch('foo + bar', 'foo + _') + self.assertNoMatch('bar + bar', 'foo + _') + self.assertNoMatch('foo - bar', 'foo + _') + + def test_matches_function_args(self): + self.assertMatch('super(Foo, self).__init__(arg1, arg2)', + 'super(_).__init__(_)') + self.assertMatch('super().__init__()', 'super(_).__init__(_)') + self.assertNoMatch('super(Foo, self).bar(arg1, arg2)', + 'super(_).__init__(_)') + self.assertMatch('super(Foo, self).__init__()', 'super(Foo, _).__init__(_)') + self.assertNoMatch('super(Foo, self).__init__()', + 'super(Bar, _).__init__(_)') + + def _mock_apply_fn(self, target, source): + target = parser.unparse(target, include_encoding_marker=False) + source = parser.unparse(source, include_encoding_marker=False) + self._invocation_counts[(target.strip(), source.strip())] += 1 + + def test_apply_to_single_assignments_dynamic_unpack(self): + node = parser.parse('a, b, c = d') + ast_util.apply_to_single_assignments(node.targets, node.value, + self._mock_apply_fn) + self.assertDictEqual(self._invocation_counts, { + ('a', 'd[0]'): 1, + ('b', 'd[1]'): 1, + ('c', 'd[2]'): 1, + }) + + def test_apply_to_single_assignments_static_unpack(self): + node = parser.parse('a, b, c = d, e, f') + ast_util.apply_to_single_assignments(node.targets, node.value, + self._mock_apply_fn) + self.assertDictEqual(self._invocation_counts, { + ('a', 'd'): 1, + ('b', 'e'): 1, + ('c', 'f'): 1, + }) + + def test_parallel_walk(self): + src = """ + def f(a): + return a + 1 + """ + node = parser.parse(textwrap.dedent(src)) + for child_a, child_b in ast_util.parallel_walk(node, node): + self.assertEqual(child_a, child_b) + + def test_parallel_walk_string_leaves(self): + src = """ + def f(a): + global g + """ + node = parser.parse(textwrap.dedent(src)) + for child_a, child_b in ast_util.parallel_walk(node, node): + self.assertEqual(child_a, child_b) + + def test_parallel_walk_inconsistent_trees(self): + node_1 = parser.parse( + textwrap.dedent(""" + def f(a): + return a + 1 + """)) + node_2 = parser.parse( + textwrap.dedent(""" + def f(a): + return a + (a * 2) + """)) + node_3 = parser.parse( + textwrap.dedent(""" + def f(a): + return a + 2 + """)) + with self.assertRaises(ValueError): + for _ in ast_util.parallel_walk(node_1, node_2): + pass + # There is not particular reason to reject trees that differ only in the + # value of a constant. + # TODO(mdan): This should probably be allowed. + with self.assertRaises(ValueError): + for _ in ast_util.parallel_walk(node_1, node_3): + pass + + def assertLambdaNodes(self, matching_nodes, expected_bodies): + self.assertEqual(len(matching_nodes), len(expected_bodies)) + for node in matching_nodes: + self.assertIsInstance(node, gast.Lambda) + self.assertIn( + parser.unparse(node.body, include_encoding_marker=False).strip(), + expected_bodies) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/cache.py b/src/braket/experimental/autoqasm/autograph/pyct/cache.py new file mode 100644 index 00000000..2d125e68 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/cache.py @@ -0,0 +1,93 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Caching utilities.""" + +import inspect +import weakref + + +# TODO(mdan): Add a garbage collection hook for cleaning up modules. +class _TransformedFnCache(object): + """Generic hierarchical cache for transformed functions. + + The keys are soft references (i.e. they are discarded when the key is + destroyed) created from the source function by `_get_key`. The subkeys are + strong references and can be any value. Typically they identify different + kinds of transformation. + """ + + __slots__ = ('_cache',) + + def __init__(self): + self._cache = weakref.WeakKeyDictionary() + + def _get_key(self, entity): + raise NotImplementedError('subclasses must override') + + def has(self, entity, subkey): + key = self._get_key(entity) + parent = self._cache.get(key, None) + if parent is None: + return False + return subkey in parent + + def __getitem__(self, entity): + key = self._get_key(entity) + parent = self._cache.get(key, None) + if parent is None: + # The bucket is initialized to support this usage: + # cache[key][subkey] = value + self._cache[key] = parent = {} + return parent + + def __len__(self): + return len(self._cache) + + +class CodeObjectCache(_TransformedFnCache): + """A function cache based on code objects. + + Code objects are good proxies for the source code of a function. + + This cache efficiently handles functions that share code objects, such as + functions defined in a loop, bound methods, etc. + + The cache falls back to the function object, if it doesn't have a code object. + """ + + def _get_key(self, entity): + if hasattr(entity, '__code__'): + return entity.__code__ + else: + return entity + + +class UnboundInstanceCache(_TransformedFnCache): + """A function cache based on unbound function objects. + + Using the function for the cache key allows efficient handling of object + methods. + + Unlike the _CodeObjectCache, this discriminates between different functions + even if they have the same code. This is needed for decorators that may + masquerade as another function. + """ + + def _get_key(self, entity): + if inspect.ismethod(entity): + return entity.__func__ + return entity + + diff --git a/src/braket/experimental/autoqasm/autograph/pyct/cache_test.py b/src/braket/experimental/autoqasm/autograph/pyct/cache_test.py new file mode 100644 index 00000000..91252c17 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/cache_test.py @@ -0,0 +1,75 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for cache module.""" + +from braket.experimental.autoqasm.autograph.pyct import cache +from tensorflow.python.platform import test + + +class CacheTest(test.TestCase): + + def test_code_object_cache(self): + + def factory(x): + def test_fn(): + return x + 1 + return test_fn + + c = cache.CodeObjectCache() + + f1 = factory(1) + dummy = object() + + c[f1][1] = dummy + + self.assertTrue(c.has(f1, 1)) + self.assertFalse(c.has(f1, 2)) + self.assertIs(c[f1][1], dummy) + self.assertEqual(len(c), 1) + + f2 = factory(2) + + self.assertTrue(c.has(f2, 1)) + self.assertIs(c[f2][1], dummy) + self.assertEqual(len(c), 1) + + def test_unbound_instance_cache(self): + + class TestClass(object): + + def method(self): + pass + + c = cache.UnboundInstanceCache() + + o1 = TestClass() + dummy = object() + + c[o1.method][1] = dummy + + self.assertTrue(c.has(o1.method, 1)) + self.assertFalse(c.has(o1.method, 2)) + self.assertIs(c[o1.method][1], dummy) + self.assertEqual(len(c), 1) + + o2 = TestClass() + + self.assertTrue(c.has(o2.method, 1)) + self.assertIs(c[o2.method][1], dummy) + self.assertEqual(len(c), 1) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/cfg.py b/src/braket/experimental/autoqasm/autograph/pyct/cfg.py new file mode 100644 index 00000000..97e54b4d --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/cfg.py @@ -0,0 +1,971 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Control flow graph (CFG) structure for Python AST representation. + +The CFG is a digraph with edges representing valid control flow. Each +node is associated with exactly one AST node, but not all AST nodes may have +a corresponding CFG counterpart. + +Once built, the CFG itself is immutable, but the values it holds need not be; +they are usually annotated with information extracted by walking the graph. + +Tip: Use `Graph.as_dot` to visualize the CFG using any DOT viewer. + +Note: the CFG tries to include all code paths that MAY be taken, with a single +notable exception: + * function calls do not generate edges corresponding to exceptions they may + raise (i.e. a function call in the middle of a block does not return or jump + to any except or finally block) +TODO(mdan): Consider adding the edges above. They'd only add ~O(n) edges. +TODO(mdan): Alternatively, consider adding an edge from try to all its excepts. +""" + +# TODO(mdan): The notion of 'statements' below is inaccurate. +# They should rather be called 'block statements', because they include +# statements that may have a body, e.g. if and while. + +import collections +import enum +import weakref + +import astunparse +import gast + +from braket.experimental.autoqasm.autograph.pyct import anno + + +class Node(object): + """A node in the CFG. + + Although new instances of this class are mutable, the objects that a user + finds in the CFG are typically not. + + The nodes represent edges in the CFG graph, and maintain pointers to allow + efficient walking in both forward and reverse order. The following property + holds for all nodes: "child in node.next" iff "node in child.prev". + + Attributes: + next: FrozenSet[Node, ...], the nodes that follow this node, in control flow + order + prev: FrozenSet[Node, ...], the nodes that precede this node, in reverse + control flow order + ast_node: ast.AST, the AST node corresponding to this CFG node + """ + + def __init__(self, next_, prev, ast_node): + self.next = next_ + self.prev = prev + self.ast_node = ast_node + + def freeze(self): + self.next = frozenset(self.next) + # Assumption: All CFG nodes have identical life spans, because the graph + # owns them. Nodes should never be used outside the context of an existing + # graph. + self.prev = weakref.WeakSet(self.prev) + + def __repr__(self): + if isinstance(self.ast_node, gast.FunctionDef): + return 'def %s' % self.ast_node.name + elif isinstance(self.ast_node, gast.ClassDef): + return 'class %s' % self.ast_node.name + elif isinstance(self.ast_node, gast.withitem): + # TODO(xjun): remove use of astunparse + return astunparse.unparse(self.ast_node.context_expr).strip() + return astunparse.unparse(self.ast_node).strip() + + +class Graph( + collections.namedtuple( + 'Graph', + ['entry', 'exit', 'error', 'index', 'stmt_prev', 'stmt_next'])): + """A Control Flow Graph. + + The CFG maintains an index to allow looking up a CFG node by the AST node to + which it is associated. The index can also be enumerated in top-down, depth + first order. + + Walking the graph in forward or reverse order is supported by double + parent-child links. + + Note: the error nodes are not wired to their corresponding finally guards, + because these are shared, and wiring them would create a reverse path from + normal control flow into the error nodes, which we want to avoid. + + The graph also maintains edges corresponding to higher level statements + like for-else loops. A node is considered successor of a statement if there + is an edge from a node that is lexically a child of that statement to a node + that is not. Statement predecessors are analogously defined. + + Attributes: + entry: Node, the entry node + exit: FrozenSet[Node, ...], the exit nodes + error: FrozenSet[Node, ...], nodes that exit due to an explicitly raised + error (errors propagated from function calls are not accounted) + index: Dict[ast.Node, Node], mapping AST nodes to the respective CFG node + stmt_prev: Dict[ast.Node, FrozenSet[Node, ...]], mapping statement AST nodes + to their predecessor CFG nodes + stmt_next: Dict[ast.Node, FrozenSet[Node, ...]], mapping statement AST nodes + to their successor CFG nodes + """ + + def __repr__(self): + return self.as_dot() + + def as_dot(self): + """Print CFG in DOT format.""" + result = 'digraph CFG {\n' + for node in self.index.values(): + result += ' %s [label="%s"];\n' % (id(node), node) + for node in self.index.values(): + for next_ in node.next: + result += ' %s -> %s;\n' % (id(node), id(next_)) + result += '}' + return result + + +class _WalkMode(enum.Enum): + FORWARD = 1 + REVERSE = 2 + + +# TODO(mdan): Rename to DataFlowAnalyzer. +# TODO(mdan): Consider specializations that use gen/kill/transfer abstractions. +class GraphVisitor(object): + """Base class for a CFG visitors. + + This implementation is not thread safe. + + The visitor has some facilities to simplify dataflow analyses. In particular, + it allows revisiting the nodes at the decision of the subclass. This can be + used to visit the graph until the state reaches a fixed point. + + For more details on dataflow analysis, see + https://www.seas.harvard.edu/courses/cs252/2011sp/slides/Lec02-Dataflow.pdf + + Note: the literature generally suggests visiting successor nodes only when the + state of the current node changed, regardless of whether that successor has + ever been visited. This implementation visits every successor at least once. + + Attributes: + graph: Graph + in_: Dict[Node, Any], stores node-keyed state during a visit + out: Dict[Node, Any], stores node-keyed state during a visit + """ + + def __init__(self, graph): + self.graph = graph + self.reset() + + def init_state(self, node): + """State initialization function. + + Optional to overload. + + An in/out state slot will be created for each node in the graph. Subclasses + must overload this to control what that is initialized to. + + Args: + node: Node + """ + raise NotImplementedError('Subclasses must implement this.') + + # TODO(mdan): Rename to flow? + def visit_node(self, node): + """Visitor function. + + Args: + node: Node + + Returns: + bool, whether the node should be revisited; subclasses can visit every + reachable node exactly once by always returning False + """ + raise NotImplementedError('Subclasses must implement this.') + + def reset(self): + self.in_ = { + node: self.init_state(node) for node in self.graph.index.values() + } + self.out = { + node: self.init_state(node) for node in self.graph.index.values() + } + + def can_ignore(self, node): + """Returns True if the node can safely be assumed not to touch variables.""" + ast_node = node.ast_node + if anno.hasanno(ast_node, anno.Basic.SKIP_PROCESSING): + return True + return isinstance(ast_node, + (gast.Break, gast.Continue, gast.Raise, gast.Pass)) + + def _visit_internal(self, mode): + """Visits the CFG, breadth-first.""" + assert mode in (_WalkMode.FORWARD, _WalkMode.REVERSE) + if mode == _WalkMode.FORWARD: + open_ = [self.graph.entry] + elif mode == _WalkMode.REVERSE: + open_ = list(self.graph.exit) + closed = set() + + while open_: + node = open_.pop(0) + closed.add(node) + + should_revisit = self.visit_node(node) + + if mode == _WalkMode.FORWARD: + children = node.next + elif mode == _WalkMode.REVERSE: + children = node.prev + + for next_ in children: + if should_revisit or next_ not in closed: + open_.append(next_) + + def visit_forward(self): + self._visit_internal(_WalkMode.FORWARD) + + def visit_reverse(self): + self._visit_internal(_WalkMode.REVERSE) + + +class GraphBuilder(object): + """Builder that constructs a CFG from a given AST. + + This GraphBuilder facilitates constructing the DAG that forms the CFG when + nodes + are supplied in lexical order (i.e., top-down, depth first). Under these + conditions, it supports building patterns found in typical structured + programs. + + This builder ignores the flow generated by exceptions, which are assumed to + always be catastrophic and present purely for diagnostic purposes (e.g. to + print debug information). Statements like raise and try/catch sections are + allowed and will generate control flow edges, but ordinary statements are + assumed not to raise exceptions. + + Finally sections are also correctly interleaved between break/continue/return + nodes and their subsequent statements. + + Important concepts: + * nodes - nodes refer to CFG nodes; AST nodes are qualified explicitly + * leaf set - since the graph is constructed gradually, a leaf set maintains + the CFG nodes that will precede the node that the builder expects to + receive next; when an ordinary node is added, it is connected to the + existing leaves and it in turn becomes the new leaf + * jump nodes - nodes that should generate edges other than what + ordinary nodes would; these correspond to break, continue and return + statements + * sections - logical delimiters for subgraphs that require special + edges; there are various types of nodes, each admitting various + types of jump nodes; sections are identified by their corresponding AST + node + """ + + # TODO(mdan): Perhaps detail this in a markdown doc. + # TODO(mdan): Add exception support. + + def __init__(self, parent_ast_node): + self.reset() + self.parent = parent_ast_node + + def reset(self): + """Resets the state of this factory.""" + self.head = None + self.errors = set() + self.node_index = {} + + # TODO(mdan): Too many primitives. Use classes. + self.leaves = set() + + # Note: This mechanism requires that nodes are added in lexical order (top + # to bottom, depth first). + self.active_stmts = set() + self.owners = {} # type: Set[any] + self.forward_edges = set() # type: Tuple[Node, Node] # (from, to) + + self.finally_sections = {} + # Dict values represent (entry, exits) + self.finally_section_subgraphs = { + } # type: Dict[ast.AST, Tuple[Node, Set[Node]]] + # Whether the guard section can be reached from the statement that precedes + # it. + self.finally_section_has_direct_flow = {} + # Finally sections that await their first node. + self.pending_finally_sections = set() + + # Exit jumps keyed by the section they affect. + self.exits = {} + + # The entry of loop sections, keyed by the section. + self.section_entry = {} + # Continue jumps keyed by the section they affect. + self.continues = {} + + # Raise jumps keyed by the except section guarding them. + self.raises = {} + + # The entry of conditional sections, keyed by the section. + self.cond_entry = {} + # Lists of leaf nodes corresponding to each branch in the section. + self.cond_leaves = {} + + def _connect_nodes(self, first, second): + """Connects nodes to signify that control flows from first to second. + + Args: + first: Union[Set[Node, ...], Node] + second: Node + """ + if isinstance(first, Node): + first.next.add(second) + second.prev.add(first) + self.forward_edges.add((first, second)) + else: + for node in first: + self._connect_nodes(node, second) + + def _add_new_node(self, ast_node): + """Grows the graph by adding a CFG node following the current leaves.""" + if ast_node in self.node_index: + raise ValueError('%s added twice' % ast_node) + # Assumption: All CFG nodes have identical life spans, because the graph + # owns them. Nodes should never be used outside the context of an existing + # graph. + node = Node(next_=set(), prev=weakref.WeakSet(), ast_node=ast_node) + self.node_index[ast_node] = node + self.owners[node] = frozenset(self.active_stmts) + + if self.head is None: + self.head = node + + for leaf in self.leaves: + self._connect_nodes(leaf, node) + + # If any finally section awaits its first node, populate it. + for section_id in self.pending_finally_sections: + self.finally_section_subgraphs[section_id][0] = node + self.pending_finally_sections = set() + + return node + + def begin_statement(self, stmt): + """Marks the beginning of a statement. + + Args: + stmt: Hashable, a key by which the statement can be identified in the + CFG's stmt_prev and stmt_next attributes + """ + self.active_stmts.add(stmt) + + def end_statement(self, stmt): + """Marks the end of a statement. + + Args: + stmt: Hashable, a key by which the statement can be identified in the + CFG's stmt_prev and stmt_next attributes; must match a key previously + passed to begin_statement. + """ + self.active_stmts.remove(stmt) + + def add_ordinary_node(self, ast_node): + """Grows the graph by adding an ordinary CFG node. + + Ordinary nodes are followed by the next node, in lexical order, that is, + they become the new leaf set. + + Args: + ast_node: ast.AST + + Returns: + Node + """ + node = self._add_new_node(ast_node) + self.leaves = set((node,)) + return node + + def _add_jump_node(self, ast_node, guards): + """Grows the graph by adding a jump node. + + Jump nodes are added to the current leaf set, and the leaf set becomes + empty. If the jump node is the last in a cond section, then it may be added + back to the leaf set by a separate mechanism. + + Args: + ast_node: ast.AST + guards: Tuple[ast.AST, ...], the finally sections active for this node + + Returns: + Node + """ + node = self._add_new_node(ast_node) + self.leaves = set() + # The guards themselves may not yet be complete, and will be wired later. + self.finally_sections[node] = guards + return node + + def _connect_jump_to_finally_sections(self, node): + """Connects a jump node to the finally sections protecting it.""" + cursor = set((node,)) + if node not in self.finally_sections: + return cursor + for guard_section_id in self.finally_sections[node]: + guard_begin, guard_ends = self.finally_section_subgraphs[guard_section_id] + self._connect_nodes(cursor, guard_begin) + cursor = guard_ends + del self.finally_sections[node] + # TODO(mdan): Should garbage-collect finally_section_subgraphs. + return cursor + + def add_exit_node(self, ast_node, section_id, guards): + """Grows the graph by adding an exit node. + + This node becomes an exit for the current section. + + Args: + ast_node: ast.AST + section_id: Hashable, the node for which ast_node should be considered to + be an exit node + guards: Tuple[ast.AST, ...], the finally sections that guard ast_node + + Returns: + Node + """ + node = self._add_jump_node(ast_node, guards) + self.exits[section_id].add(node) + return node + + def add_continue_node(self, ast_node, section_id, guards): + """Grows the graph by adding a reentry node. + + This node causes control flow to go back to the loop section's entry. + + Args: + ast_node: ast.AST + section_id: Hashable, the node for which ast_node should be considered to + be an exit node + guards: Tuple[ast.AST, ...], the finally sections that guard ast_node + """ + node = self._add_jump_node(ast_node, guards) + self.continues[section_id].add(node) + + def connect_raise_node(self, node, except_guards): + """Adds extra connection between a raise node and containing except guards. + + The node is a graph node, not an ast node. + + Args: + node: Node + except_guards: Tuple[ast.AST, ...], the except sections that guard node + """ + for guard in except_guards: + if guard in self.raises: + self.raises[guard].append(node) + else: + self.raises[guard] = [node] + + def enter_section(self, section_id): + """Enters a regular section. + + Regular sections admit exit jumps, which end the section. + + Args: + section_id: Hashable, the same node that will be used in calls to the + ast_node arg passed to add_exit_node + """ + assert section_id not in self.exits + self.exits[section_id] = set() + + def exit_section(self, section_id): + """Exits a regular section.""" + + # Exits are jump nodes, which may be protected. + for exit_ in self.exits[section_id]: + self.leaves |= self._connect_jump_to_finally_sections(exit_) + + del self.exits[section_id] + + def enter_loop_section(self, section_id, entry_node): + """Enters a loop section. + + Loop sections define an entry node. The end of the section always flows back + to the entry node. These admit continue jump nodes which also flow to the + entry node. + + Args: + section_id: Hashable, the same node that will be used in calls to the + ast_node arg passed to add_continue_node + entry_node: ast.AST, the entry node into the loop (e.g. the test node for + while loops) + """ + assert section_id not in self.section_entry + assert section_id not in self.continues + self.continues[section_id] = set() + node = self.add_ordinary_node(entry_node) + self.section_entry[section_id] = node + + def exit_loop_section(self, section_id): + """Exits a loop section.""" + self._connect_nodes(self.leaves, self.section_entry[section_id]) + + # continues are jump nodes, which may be protected. + for reentry in self.continues[section_id]: + guard_ends = self._connect_jump_to_finally_sections(reentry) + self._connect_nodes(guard_ends, self.section_entry[section_id]) + + # Loop nodes always loop back. + self.leaves = set((self.section_entry[section_id],)) + + del self.continues[section_id] + del self.section_entry[section_id] + + def enter_cond_section(self, section_id): + """Enters a conditional section. + + Conditional sections define an entry node, and one or more branches. + + Args: + section_id: Hashable, the same node that will be used in calls to the + section_id arg passed to new_cond_branch + """ + + assert section_id not in self.cond_entry + assert section_id not in self.cond_leaves + self.cond_leaves[section_id] = [] + + def new_cond_branch(self, section_id): + """Begins a new branch in a cond section.""" + assert section_id in self.cond_leaves + + if section_id in self.cond_entry: + # Subsequent splits move back to the split point, and memorize the + # current leaves. + self.cond_leaves[section_id].append(self.leaves) + self.leaves = self.cond_entry[section_id] + else: + # If this is the first time we split a section, just remember the split + # point. + self.cond_entry[section_id] = self.leaves + + def exit_cond_section(self, section_id): + """Exits a conditional section.""" + for split in self.cond_leaves[section_id]: + self.leaves |= split + del self.cond_entry[section_id] + del self.cond_leaves[section_id] + + def enter_except_section(self, section_id): + """Enters an except section.""" + if section_id in self.raises: + self.leaves.update(self.raises[section_id]) + + def enter_finally_section(self, section_id): + """Enters a finally section.""" + # TODO(mdan): This, not the caller, should track the active sections. + self.finally_section_subgraphs[section_id] = [None, None] + if self.leaves: + self.finally_section_has_direct_flow[section_id] = True + else: + self.finally_section_has_direct_flow[section_id] = False + self.pending_finally_sections.add(section_id) + + def exit_finally_section(self, section_id): + """Exits a finally section.""" + assert section_id not in self.pending_finally_sections, 'Empty finally?' + self.finally_section_subgraphs[section_id][1] = self.leaves + # If the guard can only be reached by a jump, then it will not flow + # into the statement that follows it. + if not self.finally_section_has_direct_flow[section_id]: + self.leaves = set() + del self.finally_section_has_direct_flow[section_id] + + def build(self): + """Returns the CFG accumulated so far and resets the builder. + + Returns: + Graph + """ + # Freeze the nodes. + for node in self.node_index.values(): + node.freeze() + + # Build the statement edges. + stmt_next = {} + stmt_prev = {} + + for node in self.node_index.values(): + for stmt in self.owners[node]: + if stmt not in stmt_prev: + stmt_prev[stmt] = set() + if stmt not in stmt_next: + stmt_next[stmt] = set() + + for first, second in self.forward_edges: + stmts_exited = self.owners[first] - self.owners[second] + for stmt in stmts_exited: + stmt_next[stmt].add(second) + stmts_entered = self.owners[second] - self.owners[first] + for stmt in stmts_entered: + stmt_prev[stmt].add(first) + for stmt in stmt_next: + stmt_next[stmt] = frozenset(stmt_next[stmt]) + for stmt in stmt_prev: + stmt_prev[stmt] = frozenset(stmt_prev[stmt]) + + # Construct the final graph object. + result = Graph( + entry=self.head, + exit=self.leaves, + error=self.errors, + index=self.node_index, + stmt_prev=stmt_prev, + stmt_next=stmt_next) + + # Reset the state. + self.reset() + + return result + + +class AstToCfg(gast.NodeVisitor): + """Converts an AST to CFGs. + + A separate CFG will be constructed for each function. + """ + + def __init__(self): + super(AstToCfg, self).__init__() + + self.builder_stack = [] + self.builder = None + self.cfgs = {} + + self.lexical_scopes = [] + + def _enter_lexical_scope(self, node): + self.lexical_scopes.append(node) + + def _exit_lexical_scope(self, node): + leaving_node = self.lexical_scopes.pop() + assert node == leaving_node + + def _get_enclosing_finally_scopes(self, stop_at): + included = [] + for node in reversed(self.lexical_scopes): + if isinstance(node, gast.Try) and node.finalbody: + included.append(node) + if isinstance(node, stop_at): + return node, included + return None, included + + def _get_enclosing_except_scopes(self, stop_at): + included = [] + for node in reversed(self.lexical_scopes): + if isinstance(node, gast.Try) and node.handlers: + included.extend(node.handlers) + if isinstance(node, stop_at): + break + return included + + def _process_basic_statement(self, node): + self.generic_visit(node) + self.builder.add_ordinary_node(node) + + def _process_exit_statement(self, + node, + exits_nodes_of_type, + may_exit_via_except=False): + self.generic_visit(node) + # Note: this is safe because we process functions separately. + try_node, guards = self._get_enclosing_finally_scopes(exits_nodes_of_type) + assert try_node is not None, '{} that is not enclosed by any of {}'.format( + node, exits_nodes_of_type) + + node = self.builder.add_exit_node(node, try_node, guards) + + if may_exit_via_except: + except_guards = self._get_enclosing_except_scopes(exits_nodes_of_type) + self.builder.connect_raise_node(node, except_guards) + + def _process_continue_statement(self, node, *loops_to_nodes_of_type): + # Note: this is safe because we process functions separately. + try_node, guards = self._get_enclosing_finally_scopes( + tuple(loops_to_nodes_of_type)) + if try_node is None: + raise ValueError('%s that is not enclosed by any of %s' % + (node, loops_to_nodes_of_type)) + self.builder.add_continue_node(node, try_node, guards) + + def visit_ClassDef(self, node): + # We also keep the ClassDef node in the CFG, since it technically is a + # statement. + # For example, this is legal and allows executing user code: + # + # class Foo(bar()): + # pass + # + # It also has a scope: + # + # class Bar(object): + # a = 1 + if self.builder is None: + self.generic_visit(node) + return + + self.builder.add_ordinary_node(node) + + self.builder_stack.append(self.builder) + self.builder = GraphBuilder(node) + self._enter_lexical_scope(node) + + self._process_basic_statement(node) + + self._exit_lexical_scope(node) + # TODO(mdan): Track the CFG local to the class definition as well? + self.builder = self.builder_stack.pop() + + def _process_function_def(self, node, is_lambda): + # The function body is stored in a separate graph, because function + # definitions have effects very different from function calls. + if self.builder is not None: + self.builder.add_ordinary_node(node) + + self.builder_stack.append(self.builder) + self.builder = GraphBuilder(node) + + self._enter_lexical_scope(node) + self.builder.enter_section(node) + + self._process_basic_statement(node.args) + if is_lambda: + self._process_exit_statement(node.body, (gast.Lambda,)) + else: + for stmt in node.body: + self.visit(stmt) + + self.builder.exit_section(node) + self._exit_lexical_scope(node) + + self.cfgs[node] = self.builder.build() + self.builder = self.builder_stack.pop() + + def visit_FunctionDef(self, node): + self._process_function_def(node, is_lambda=False) + + def visit_Lambda(self, node): + self._process_function_def(node, is_lambda=True) + + def visit_Return(self, node): + self._process_exit_statement(node, (gast.FunctionDef,)) + + def visit_Import(self, node): + self._process_basic_statement(node) + + def visit_ImportFrom(self, node): + self._process_basic_statement(node) + + def visit_Expr(self, node): + self._process_basic_statement(node) + + def visit_Assign(self, node): + self._process_basic_statement(node) + + def visit_AnnAssign(self, node): + self._process_basic_statement(node) + + def visit_AugAssign(self, node): + self._process_basic_statement(node) + + def visit_Pass(self, node): + self._process_basic_statement(node) + + def visit_Global(self, node): + self._process_basic_statement(node) + + def visit_Nonlocal(self, node): + self._process_basic_statement(node) + + def visit_Print(self, node): + self._process_basic_statement(node) + + def visit_Raise(self, node): + self._process_exit_statement( + node, (gast.FunctionDef,), may_exit_via_except=True) + self.builder.errors.add(node) + + def visit_Assert(self, node): + # Ignoring the effect of exceptions. + self._process_basic_statement(node) + + def visit_Delete(self, node): + self._process_basic_statement(node) + + def visit_If(self, node): + # No need to track ifs as lexical scopes, for now. + # Lexical scopes are generally tracked in order to be able to resolve the + # targets of jump statements like break/continue/etc. Since there is no + # statement that can interrupt a conditional, we don't need to track their + # lexical scope. That may change in the future. + self.builder.begin_statement(node) + + self.builder.enter_cond_section(node) + self._process_basic_statement(node.test) + + self.builder.new_cond_branch(node) + for stmt in node.body: + self.visit(stmt) + + self.builder.new_cond_branch(node) + for stmt in node.orelse: + self.visit(stmt) + + self.builder.exit_cond_section(node) + self.builder.end_statement(node) + + def visit_While(self, node): + self.builder.begin_statement(node) + self._enter_lexical_scope(node) + + self.builder.enter_section(node) + + self.generic_visit(node.test) + self.builder.enter_loop_section(node, node.test) + for stmt in node.body: + self.visit(stmt) + self.builder.exit_loop_section(node) + + # Note: although the orelse is technically part of the loop node, + # the statements inside it don't affect the loop itself. For example, a + # break in the loop's orelse will not affect the loop itself. + self._exit_lexical_scope(node) + + for stmt in node.orelse: + self.visit(stmt) + + self.builder.exit_section(node) + self.builder.end_statement(node) + + def visit_For(self, node): + self.builder.begin_statement(node) + self._enter_lexical_scope(node) + + self.builder.enter_section(node) + + # Note: Strictly speaking, this should be node.target + node.iter. + # However, the activity analysis accounts for this inconsistency, + # so dataflow analysis produces the correct values. + self.generic_visit(node.iter) + self.builder.enter_loop_section(node, node.iter) + # Also include the "extra loop test" annotation, to capture things like the + # control variable for return and break in for loops. + if anno.hasanno(node, anno.Basic.EXTRA_LOOP_TEST): + self._process_basic_statement( + anno.getanno(node, anno.Basic.EXTRA_LOOP_TEST)) + for stmt in node.body: + self.visit(stmt) + self.builder.exit_loop_section(node) + + # Note: although the orelse is technically part of the loop node, + # they don't count as loop bodies. For example, a break in the loop's + # orelse will affect the parent loop, not the current one. + self._exit_lexical_scope(node) + + for stmt in node.orelse: + self.visit(stmt) + + self.builder.exit_section(node) + self.builder.end_statement(node) + + def visit_Break(self, node): + self._process_exit_statement(node, ( + gast.While, + gast.For, + )) + + def visit_Continue(self, node): + self._process_continue_statement(node, ( + gast.While, + gast.For, + )) + + def visit_ExceptHandler(self, node): + self.builder.begin_statement(node) + self.builder.enter_except_section(node) + + if node.type is not None: + self.visit(node.type) + if node.name is not None: + self.visit(node.name) + + for stmt in node.body: + self.visit(stmt) + + self.builder.end_statement(node) + + def visit_Try(self, node): + self.builder.begin_statement(node) + self._enter_lexical_scope(node) + + # Note: the current simplification is that the try block fully executes + # regardless of whether an exception triggers or not. This is consistent + # with blocks free of try/except, which also don't account for the + # possibility of an exception being raised mid-block. + + for stmt in node.body: + self.visit(stmt) + # The orelse is an optional continuation of the body. + if node.orelse: + block_representative = node.orelse[0] + self.builder.enter_cond_section(block_representative) + self.builder.new_cond_branch(block_representative) + for stmt in node.orelse: + self.visit(stmt) + self.builder.new_cond_branch(block_representative) + self.builder.exit_cond_section(block_representative) + + self._exit_lexical_scope(node) + + if node.handlers: + # Using node would be inconsistent. Using the first handler node is also + # inconsistent, but less so. + block_representative = node.handlers[0] + self.builder.enter_cond_section(block_representative) + for block in node.handlers: + self.builder.new_cond_branch(block_representative) + self.visit(block) + self.builder.new_cond_branch(block_representative) + self.builder.exit_cond_section(block_representative) + + if node.finalbody: + self.builder.enter_finally_section(node) + for stmt in node.finalbody: + self.visit(stmt) + self.builder.exit_finally_section(node) + + self.builder.end_statement(node) + + def visit_With(self, node): + # TODO(mdan): Mark the context manager's exit call as exit guard. + for item in node.items: + self._process_basic_statement(item) + for stmt in node.body: + self.visit(stmt) + + +def build(node): + visitor = AstToCfg() + visitor.visit(node) + return visitor.cfgs diff --git a/src/braket/experimental/autoqasm/autograph/pyct/cfg_test.py b/src/braket/experimental/autoqasm/autograph/pyct/cfg_test.py new file mode 100644 index 00000000..5533e44c --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/cfg_test.py @@ -0,0 +1,1602 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for cfg module.""" + +import gast + +from braket.experimental.autoqasm.autograph.pyct import cfg +from braket.experimental.autoqasm.autograph.pyct import parser +from tensorflow.python.platform import test + + +class CountingVisitor(cfg.GraphVisitor): + + def __init__(self, graph): + super(CountingVisitor, self).__init__(graph) + self.counts = {} + + def init_state(self, _): + return None + + def visit_node(self, node): + self.counts[node.ast_node] = self.counts.get(node.ast_node, 0) + 1 + return False # visit only once + + +class GraphVisitorTest(test.TestCase): + + def _build_cfg(self, fn): + node, _ = parser.parse_entity(fn, future_features=()) + cfgs = cfg.build(node) + return cfgs, node + + def test_basic_coverage_forward(self): + + def test_fn(a): + while a > 0: + a = 1 + break + return a # pylint:disable=unreachable + a = 2 + + graphs, node = self._build_cfg(test_fn) + graph, = graphs.values() + visitor = CountingVisitor(graph) + visitor.visit_forward() + + self.assertEqual(visitor.counts[node.args], 1) + self.assertEqual(visitor.counts[node.body[0].test], 1) + self.assertEqual(visitor.counts[node.body[0].body[0]], 1) + self.assertEqual(visitor.counts[node.body[0].body[1]], 1) + # The return node should be unreachable in forward direction. + self.assertNotIn(node.body[0].body[2], visitor.counts) + self.assertEqual(visitor.counts[node.body[1]], 1) + + def test_basic_coverage_reverse(self): + + def test_fn(a): + while a > 0: + a = 1 + break + return a # pylint:disable=unreachable + a = 2 + + graphs, node = self._build_cfg(test_fn) + graph, = graphs.values() + visitor = CountingVisitor(graph) + visitor.visit_reverse() + + self.assertEqual(visitor.counts[node.args], 1) + self.assertEqual(visitor.counts[node.body[0].test], 1) + self.assertEqual(visitor.counts[node.body[0].body[0]], 1) + self.assertEqual(visitor.counts[node.body[0].body[1]], 1) + self.assertEqual(visitor.counts[node.body[0].body[2]], 1) + self.assertEqual(visitor.counts[node.body[1]], 1) + + +class AstToCfgTest(test.TestCase): + + def _build_cfg(self, fn): + node, _ = parser.parse_entity(fn, future_features=()) + cfgs = cfg.build(node) + return cfgs + + def _repr_set(self, node_set): + return frozenset(repr(n) for n in node_set) + + def _as_set(self, elements): + if elements is None: + return frozenset() + elif isinstance(elements, str): + return frozenset((elements,)) + else: + return frozenset(elements) + + def assertGraphMatches(self, graph, edges): + """Tests whether the CFG contains the specified edges.""" + for prev, node_repr, next_ in edges: + matched = False + for cfg_node in graph.index.values(): + if repr(cfg_node) == node_repr: + if (self._as_set(prev) == frozenset(map(repr, cfg_node.prev)) and + self._as_set(next_) == frozenset(map(repr, cfg_node.next))): + matched = True + break + if not matched: + self.fail( + 'match failed for node "%s" in graph:\n%s' % (node_repr, graph)) + + def assertGraphEnds(self, graph, entry_repr, exit_reprs): + """Tests whether the CFG has the specified entry and exits.""" + self.assertEqual(repr(graph.entry), entry_repr) + self.assertSetEqual(frozenset(map(repr, graph.exit)), frozenset(exit_reprs)) + + def assertStatementEdges(self, graph, edges): + """Tests whether the CFG contains the specified statement edges.""" + for prev_node_reprs, node_repr, next_node_reprs in edges: + matched = False + partial_matches = [] + self.assertSetEqual( + frozenset(graph.stmt_next.keys()), frozenset(graph.stmt_prev.keys())) + for stmt_ast_node in graph.stmt_next: + ast_repr = '%s:%s' % (stmt_ast_node.__class__.__name__, + stmt_ast_node.lineno) + if ast_repr == node_repr: + actual_next = frozenset(map(repr, graph.stmt_next[stmt_ast_node])) + actual_prev = frozenset(map(repr, graph.stmt_prev[stmt_ast_node])) + partial_matches.append((actual_prev, node_repr, actual_next)) + if (self._as_set(prev_node_reprs) == actual_prev and + self._as_set(next_node_reprs) == actual_next): + matched = True + break + if not matched: + self.fail('edges mismatch for %s: %s' % (node_repr, partial_matches)) + + def test_straightline(self): + + def test_fn(a): + a += 1 + a = 2 + a = 3 + return + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (None, 'a', 'a += 1'), + ('a += 1', 'a = 2', 'a = 3'), + ('a = 2', 'a = 3', 'return'), + ('a = 3', 'return', None), + ), + ) + self.assertGraphEnds(graph, 'a', ('return',)) + + def test_straightline_no_return(self): + + def test_fn(a, b): + a = b + 1 + a += max(a) + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (None, 'a, b', 'a = (b + 1)'), + ('a = (b + 1)', 'a += max(a)', None), + ), + ) + self.assertGraphEnds(graph, 'a, b', ('a += max(a)',)) + + def test_unreachable_code(self): + + def test_fn(a): + return + a += 1 # pylint:disable=unreachable + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (None, 'a', 'return'), + ('a', 'return', None), + (None, 'a += 1', None), + ), + ) + self.assertGraphEnds(graph, 'a', ('return', 'a += 1')) + + def test_if_straightline(self): + + def test_fn(a): + if a > 0: + a = 1 + else: + a += -1 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (None, 'a', '(a > 0)'), + ('(a > 0)', 'a = 1', None), + ('(a > 0)', 'a += (- 1)', None), + ), + ) + self.assertStatementEdges( + graph, + (('a', 'If:2', None),), + ) + self.assertGraphEnds(graph, 'a', ('a = 1', 'a += (- 1)')) + + def test_branch_nested(self): + + def test_fn(a): + if a > 0: + if a > 1: + a = 1 + else: + a = 2 + else: + if a > 2: + a = 3 + else: + a = 4 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (None, 'a', '(a > 0)'), + ('a', '(a > 0)', ('(a > 1)', '(a > 2)')), + ('(a > 0)', '(a > 1)', ('a = 1', 'a = 2')), + ('(a > 1)', 'a = 1', None), + ('(a > 1)', 'a = 2', None), + ('(a > 0)', '(a > 2)', ('a = 3', 'a = 4')), + ('(a > 2)', 'a = 3', None), + ('(a > 2)', 'a = 4', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'If:2', None), + ('(a > 0)', 'If:3', None), + ('(a > 0)', 'If:8', None), + ), + ) + self.assertGraphEnds(graph, 'a', ('a = 1', 'a = 2', 'a = 3', 'a = 4')) + + def test_branch_straightline_unbalanced(self): + + def test_fn(a): + if a > 0: + a = 1 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (None, 'a', '(a > 0)'), + ('a', '(a > 0)', 'a = 1'), + ('(a > 0)', 'a = 1', None), + ), + ) + self.assertStatementEdges( + graph, + (('a', 'If:2', None),), + ) + self.assertGraphEnds(graph, 'a', ('(a > 0)', 'a = 1')) + + def test_branch_return(self): + + def test_fn(a): + if a > 0: + return + else: + a = 1 + a = 2 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', '(a > 0)', ('return', 'a = 1')), + ('(a > 0)', 'a = 1', 'a = 2'), + ('(a > 0)', 'return', None), + ('a = 1', 'a = 2', None), + ), + ) + self.assertStatementEdges( + graph, + (('a', 'If:2', 'a = 2'),), + ) + self.assertGraphEnds(graph, 'a', ('a = 2', 'return')) + + def test_branch_raise(self): + + def test_fn(a): + if a > 0: + raise a + else: + a = 1 + a = 2 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', '(a > 0)', ('raise a', 'a = 1')), + ('(a > 0)', 'a = 1', 'a = 2'), + ('(a > 0)', 'raise a', None), + ('a = 1', 'a = 2', None), + ), + ) + self.assertStatementEdges( + graph, + (('a', 'If:2', 'a = 2'),), + ) + self.assertGraphEnds(graph, 'a', ('a = 2', 'raise a')) + + def test_branch_return_minimal(self): + + def test_fn(a): + if a > 0: + return + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', '(a > 0)', 'return'), + ('(a > 0)', 'return', None), + ), + ) + self.assertStatementEdges( + graph, + (('a', 'If:2', None),), + ) + self.assertGraphEnds(graph, 'a', ('(a > 0)', 'return')) + + def test_while_straightline(self): + + def test_fn(a): + while a > 0: + a = 1 + a = 2 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (('a', 'a = 1'), '(a > 0)', ('a = 1', 'a = 2')), + ('(a > 0)', 'a = 1', '(a > 0)'), + ('(a > 0)', 'a = 2', None), + ), + ) + self.assertStatementEdges( + graph, + (('a', 'While:2', 'a = 2'),), + ) + self.assertGraphEnds(graph, 'a', ('a = 2',)) + + def test_while_else_straightline(self): + + def test_fn(a): + while a > 0: + a = 1 + else: # pylint:disable=useless-else-on-loop + a = 2 + a = 3 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (('a', 'a = 1'), '(a > 0)', ('a = 1', 'a = 2')), + ('(a > 0)', 'a = 1', '(a > 0)'), + ('(a > 0)', 'a = 2', 'a = 3'), + ('a = 2', 'a = 3', None), + ), + ) + self.assertStatementEdges( + graph, + (('a', 'While:2', 'a = 3'),), + ) + self.assertGraphEnds(graph, 'a', ('a = 3',)) + + def test_while_else_continue(self): + + def test_fn(a): + while a > 0: + if a > 1: + continue + else: + a = 0 + a = 1 + else: # pylint:disable=useless-else-on-loop + a = 2 + a = 3 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (('a', 'continue', 'a = 1'), '(a > 0)', ('(a > 1)', 'a = 2')), + ('(a > 0)', '(a > 1)', ('continue', 'a = 0')), + ('(a > 1)', 'continue', '(a > 0)'), + ('a = 0', 'a = 1', '(a > 0)'), + ('(a > 0)', 'a = 2', 'a = 3'), + ('a = 2', 'a = 3', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'While:2', 'a = 3'), + ('(a > 0)', 'If:3', ('a = 1', '(a > 0)')), + ), + ) + self.assertGraphEnds(graph, 'a', ('a = 3',)) + + def test_while_else_break(self): + + def test_fn(a): + while a > 0: + if a > 1: + break + a = 1 + else: + a = 2 + a = 3 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (('a', 'a = 1'), '(a > 0)', ('(a > 1)', 'a = 2')), + ('(a > 0)', '(a > 1)', ('break', 'a = 1')), + ('(a > 1)', 'break', 'a = 3'), + ('(a > 1)', 'a = 1', '(a > 0)'), + ('(a > 0)', 'a = 2', 'a = 3'), + (('break', 'a = 2'), 'a = 3', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'While:2', 'a = 3'), + ('(a > 0)', 'If:3', ('a = 1', 'a = 3')), + ), + ) + self.assertGraphEnds(graph, 'a', ('a = 3',)) + + def test_while_else_return(self): + + def test_fn(a): + while a > 0: + if a > 1: + return + a = 1 + else: # pylint:disable=useless-else-on-loop + a = 2 + a = 3 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (('a', 'a = 1'), '(a > 0)', ('(a > 1)', 'a = 2')), + ('(a > 0)', '(a > 1)', ('return', 'a = 1')), + ('(a > 1)', 'return', None), + ('(a > 1)', 'a = 1', '(a > 0)'), + ('(a > 0)', 'a = 2', 'a = 3'), + ('a = 2', 'a = 3', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'While:2', 'a = 3'), + ('(a > 0)', 'If:3', 'a = 1'), + ), + ) + self.assertGraphEnds(graph, 'a', ('a = 3', 'return')) + + def test_while_nested_straightline(self): + + def test_fn(a): + while a > 0: + while a > 1: + a = 1 + a = 2 + a = 3 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (('a', 'a = 2'), '(a > 0)', ('(a > 1)', 'a = 3')), + (('(a > 0)', 'a = 1'), '(a > 1)', ('a = 1', 'a = 2')), + ('(a > 1)', 'a = 1', '(a > 1)'), + ('(a > 1)', 'a = 2', '(a > 0)'), + ('(a > 0)', 'a = 3', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'While:2', 'a = 3'), + ('(a > 0)', 'While:3', 'a = 2'), + ), + ) + self.assertGraphEnds(graph, 'a', ('a = 3',)) + + def test_while_nested_continue(self): + + def test_fn(a): + while a > 0: + while a > 1: + if a > 3: + continue + a = 1 + a = 2 + a = 3 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (('a', 'a = 2'), '(a > 0)', ('(a > 1)', 'a = 3')), + (('(a > 0)', 'continue', 'a = 1'), '(a > 1)', ('(a > 3)', 'a = 2')), + ('(a > 1)', '(a > 3)', ('continue', 'a = 1')), + ('(a > 3)', 'continue', '(a > 1)'), + ('(a > 3)', 'a = 1', '(a > 1)'), + ('(a > 1)', 'a = 2', '(a > 0)'), + ('(a > 0)', 'a = 3', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'While:2', 'a = 3'), + ('(a > 0)', 'While:3', 'a = 2'), + ('(a > 1)', 'If:4', ('a = 1', '(a > 1)')), + ), + ) + self.assertGraphEnds(graph, 'a', ('a = 3',)) + + def test_while_nested_break(self): + + def test_fn(a): + while a > 0: + while a > 1: + if a > 2: + break + a = 1 + a = 2 + a = 3 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches(graph, ( + (('a', 'a = 2'), '(a > 0)', ('(a > 1)', 'a = 3')), + (('(a > 0)', 'a = 1'), '(a > 1)', ('(a > 2)', 'a = 2')), + ('(a > 1)', '(a > 2)', ('break', 'a = 1')), + ('(a > 2)', 'break', 'a = 2'), + ('(a > 2)', 'a = 1', '(a > 1)'), + (('(a > 1)', 'break'), 'a = 2', '(a > 0)'), + ('(a > 0)', 'a = 3', None), + )) + self.assertStatementEdges( + graph, + ( + ('a', 'While:2', 'a = 3'), + ('(a > 0)', 'While:3', 'a = 2'), + ('(a > 1)', 'If:4', ('a = 1', 'a = 2')), + ), + ) + self.assertGraphEnds(graph, 'a', ('a = 3',)) + + def test_for_straightline(self): + + def test_fn(a): + for a in range(0, a): + a = 1 + a = 2 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (('a', 'a = 1'), 'range(0, a)', ('a = 1', 'a = 2')), + ('range(0, a)', 'a = 1', 'range(0, a)'), + ('range(0, a)', 'a = 2', None), + ), + ) + self.assertStatementEdges( + graph, + (('a', 'For:2', 'a = 2'),), + ) + self.assertGraphEnds(graph, 'a', ('a = 2',)) + + def test_for_else_straightline(self): + + def test_fn(a): + for a in range(0, a): + a = 1 + else: # pylint:disable=useless-else-on-loop + a = 2 + a = 3 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (('a', 'a = 1'), 'range(0, a)', ('a = 1', 'a = 2')), + ('range(0, a)', 'a = 1', 'range(0, a)'), + ('range(0, a)', 'a = 2', 'a = 3'), + ('a = 2', 'a = 3', None), + ), + ) + self.assertStatementEdges( + graph, + (('a', 'For:2', 'a = 3'),), + ) + self.assertGraphEnds(graph, 'a', ('a = 3',)) + + def test_for_else_continue(self): + + def test_fn(a): + for a in range(0, a): + if a > 1: + continue + else: + a = 0 + a = 1 + else: # pylint:disable=useless-else-on-loop + a = 2 + a = 3 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (('a', 'continue', 'a = 1'), 'range(0, a)', ('(a > 1)', 'a = 2')), + ('range(0, a)', '(a > 1)', ('continue', 'a = 0')), + ('(a > 1)', 'continue', 'range(0, a)'), + ('(a > 1)', 'a = 0', 'a = 1'), + ('a = 0', 'a = 1', 'range(0, a)'), + ('range(0, a)', 'a = 2', 'a = 3'), + ('a = 2', 'a = 3', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'For:2', 'a = 3'), + ('range(0, a)', 'If:3', ('a = 1', 'range(0, a)')), + ), + ) + self.assertGraphEnds(graph, 'a', ('a = 3',)) + + def test_for_else_break(self): + + def test_fn(a): + for a in range(0, a): + if a > 1: + break + a = 1 + else: + a = 2 + a = 3 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (('a', 'a = 1'), 'range(0, a)', ('(a > 1)', 'a = 2')), + ('range(0, a)', '(a > 1)', ('break', 'a = 1')), + ('(a > 1)', 'break', 'a = 3'), + ('(a > 1)', 'a = 1', 'range(0, a)'), + ('range(0, a)', 'a = 2', 'a = 3'), + (('break', 'a = 2'), 'a = 3', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'For:2', 'a = 3'), + ('range(0, a)', 'If:3', ('a = 1', 'a = 3')), + ), + ) + self.assertGraphEnds(graph, 'a', ('a = 3',)) + + def test_for_else_return(self): + + def test_fn(a): + for a in range(0, a): + if a > 1: + return + a = 1 + else: # pylint:disable=useless-else-on-loop + a = 2 + a = 3 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (('a', 'a = 1'), 'range(0, a)', ('(a > 1)', 'a = 2')), + ('range(0, a)', '(a > 1)', ('return', 'a = 1')), + ('(a > 1)', 'return', None), + ('(a > 1)', 'a = 1', 'range(0, a)'), + ('range(0, a)', 'a = 2', 'a = 3'), + ('a = 2', 'a = 3', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'For:2', 'a = 3'), + ('range(0, a)', 'If:3', 'a = 1'), + ), + ) + self.assertGraphEnds(graph, 'a', ('a = 3', 'return')) + + def test_for_nested_straightline(self): + + def test_fn(a): + for a in range(0, a): + for b in range(1, a): + b += 1 + a = 2 + a = 3 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (('a', 'a = 2'), 'range(0, a)', ('range(1, a)', 'a = 3')), + (('range(0, a)', 'b += 1'), 'range(1, a)', ('b += 1', 'a = 2')), + ('range(1, a)', 'b += 1', 'range(1, a)'), + ('range(1, a)', 'a = 2', 'range(0, a)'), + ('range(0, a)', 'a = 3', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'For:2', 'a = 3'), + ('range(0, a)', 'For:3', 'a = 2'), + ), + ) + self.assertGraphEnds(graph, 'a', ('a = 3',)) + + def test_for_nested_continue(self): + + def test_fn(a): + for a in range(0, a): + for b in range(1, a): + if a > 3: + continue + b += 1 + a = 2 + a = 3 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (('a', 'a = 2'), 'range(0, a)', ('range(1, a)', 'a = 3')), + (('range(0, a)', 'continue', 'b += 1'), 'range(1, a)', + ('(a > 3)', 'a = 2')), + ('range(1, a)', '(a > 3)', ('continue', 'b += 1')), + ('(a > 3)', 'continue', 'range(1, a)'), + ('(a > 3)', 'b += 1', 'range(1, a)'), + ('range(1, a)', 'a = 2', 'range(0, a)'), + ('range(0, a)', 'a = 3', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'For:2', 'a = 3'), + ('range(0, a)', 'For:3', 'a = 2'), + ('range(1, a)', 'If:4', ('b += 1', 'range(1, a)')), + ), + ) + self.assertGraphEnds(graph, 'a', ('a = 3',)) + + def test_for_nested_break(self): + + def test_fn(a): + for a in range(0, a): + for b in range(1, a): + if a > 2: + break + b += 1 + a = 2 + a = 3 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (('a', 'a = 2'), 'range(0, a)', ('range(1, a)', 'a = 3')), + (('range(0, a)', 'b += 1'), 'range(1, a)', ('(a > 2)', 'a = 2')), + ('range(1, a)', '(a > 2)', ('break', 'b += 1')), + ('(a > 2)', 'break', 'a = 2'), + ('(a > 2)', 'b += 1', 'range(1, a)'), + (('range(1, a)', 'break'), 'a = 2', 'range(0, a)'), + ('range(0, a)', 'a = 3', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'For:2', 'a = 3'), + ('range(0, a)', 'For:3', 'a = 2'), + ('range(1, a)', 'If:4', ('b += 1', 'a = 2')), + ), + ) + self.assertGraphEnds(graph, 'a', ('a = 3',)) + + def test_complex(self): + + def test_fn(a): + b = 0 + while a > 0: + for b in range(0, a): + if a > 2: + break + if a > 3: + if a > 4: + continue + else: + max(a) + break + b += 1 + else: # for b in range(0, a): + return a + a = 2 + for a in range(1, a): + return b + a = 3 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (('b = 0', 'a = 2'), '(a > 0)', ('range(0, a)', 'range(1, a)')), + ( + ('(a > 0)', 'continue', 'b += 1'), + 'range(0, a)', + ('(a > 2)', 'return a'), + ), + ('range(0, a)', '(a > 2)', ('(a > 3)', 'break')), + ('(a > 2)', 'break', 'a = 2'), + ('(a > 2)', '(a > 3)', ('(a > 4)', 'b += 1')), + ('(a > 3)', '(a > 4)', ('continue', 'max(a)')), + ('(a > 4)', 'max(a)', 'break'), + ('max(a)', 'break', 'a = 2'), + ('(a > 4)', 'continue', 'range(0, a)'), + ('(a > 3)', 'b += 1', 'range(0, a)'), + ('range(0, a)', 'return a', None), + ('break', 'a = 2', '(a > 0)'), + ('(a > 0)', 'range(1, a)', ('return b', 'a = 3')), + ('range(1, a)', 'return b', None), + ('range(1, a)', 'a = 3', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('b = 0', 'While:3', 'range(1, a)'), + ('(a > 0)', 'For:4', 'a = 2'), + ('range(0, a)', 'If:5', ('(a > 3)', 'a = 2')), + ('(a > 2)', 'If:7', ('b += 1', 'a = 2', 'range(0, a)')), + ('(a > 3)', 'If:8', ('a = 2', 'range(0, a)')), + ('(a > 0)', 'For:17', 'a = 3'), + ), + ) + self.assertGraphEnds(graph, 'a', ('a = 3', 'return a', 'return b')) + + def test_finally_straightline(self): + + def test_fn(a): + try: + a += 1 + finally: + a = 2 + a = 3 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', 'a += 1', 'a = 2'), + ('a += 1', 'a = 2', 'a = 3'), + ('a = 2', 'a = 3', None), + ), + ) + self.assertGraphEnds(graph, 'a', ('a = 3',)) + + def test_return_finally(self): + + def test_fn(a): + try: + return a + finally: + a = 1 + a = 2 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', 'return a', 'a = 1'), + ('return a', 'a = 1', None), + (None, 'a = 2', None), + ), + ) + # Note, `a = 1` executes after `return a`. + self.assertGraphEnds(graph, 'a', ('a = 2', 'a = 1')) + + def test_break_finally(self): + + def test_fn(a): + while a > 0: + try: + break + finally: + a = 1 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', '(a > 0)', 'break'), + ('(a > 0)', 'break', 'a = 1'), + ('break', 'a = 1', None), + ), + ) + self.assertGraphEnds(graph, 'a', ('(a > 0)', 'a = 1')) + + def test_continue_finally(self): + + def test_fn(a): + while a > 0: + try: + continue + finally: + a = 1 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + (('a', 'a = 1'), '(a > 0)', 'continue'), + ('(a > 0)', 'continue', 'a = 1'), + ('continue', 'a = 1', '(a > 0)'), + ), + ) + self.assertGraphEnds(graph, 'a', ('(a > 0)',)) + + def test_with_straightline(self): + + def test_fn(a): + with max(a) as b: + a = 0 + return b + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', 'max(a)', 'a = 0'), + ('max(a)', 'a = 0', 'return b'), + ('a = 0', 'return b', None), + ), + ) + self.assertGraphEnds(graph, 'a', ('return b',)) + + def test_lambda_basic(self): + + def test_fn(a): + a = lambda b: a + b + return a + + graphs = self._build_cfg(test_fn) + for k, v in graphs.items(): + if isinstance(k, gast.Lambda): + lam_graph = v + else: + fn_graph = v + + self.assertGraphMatches( + fn_graph, + ( + ('a', '(lambda b: (a + b))', 'a = (lambda b: (a + b))'), + ('(lambda b: (a + b))', 'a = (lambda b: (a + b))', 'return a'), + ('a = (lambda b: (a + b))', 'return a', None), + ), + ) + self.assertGraphEnds(fn_graph, 'a', ('return a',)) + self.assertGraphMatches( + lam_graph, + ( + ('b', '(a + b)', None), + ), + ) + self.assertGraphEnds(lam_graph, 'b', ('(a + b)',)) + + def test_lambda_in_return(self): + + def test_fn(a): + return lambda b: a + b + + graphs = self._build_cfg(test_fn) + for k, v in graphs.items(): + if isinstance(k, gast.Lambda): + lam_graph = v + else: + fn_graph = v + + self.assertGraphMatches( + fn_graph, + ( + ('a', '(lambda b: (a + b))', 'return (lambda b: (a + b))'), + ('(lambda b: (a + b))', 'return (lambda b: (a + b))', None), + ), + ) + self.assertGraphEnds(fn_graph, 'a', ('return (lambda b: (a + b))',)) + self.assertGraphMatches( + lam_graph, + ( + ('b', '(a + b)', None), + ), + ) + self.assertGraphEnds(lam_graph, 'b', ('(a + b)',)) + + def test_lambda_in_while_loop_test(self): + + def test_fn(a): + while (lambda b: a + b)(a): + pass + + graphs = self._build_cfg(test_fn) + for k, v in graphs.items(): + if isinstance(k, gast.Lambda): + lam_graph = v + else: + fn_graph = v + + self.assertGraphMatches( + fn_graph, + ( + ('a', '(lambda b: (a + b))', '(lambda b: (a + b))(a)'), + (('(lambda b: (a + b))', 'pass'), '(lambda b: (a + b))(a)', 'pass'), + ('(lambda b: (a + b))(a)', 'pass', '(lambda b: (a + b))(a)'), + ), + ) + self.assertGraphEnds(fn_graph, 'a', ('(lambda b: (a + b))(a)',)) + self.assertGraphMatches( + lam_graph, + ( + ('b', '(a + b)', None), + ), + ) + self.assertGraphEnds(lam_graph, 'b', ('(a + b)',)) + + def test_lambda_in_for_loop_test(self): + + def test_fn(a): + for _ in (lambda b: a + b)(a): + pass + + graphs = self._build_cfg(test_fn) + for k, v in graphs.items(): + if isinstance(k, gast.Lambda): + lam_graph = v + else: + fn_graph = v + + self.assertGraphMatches( + fn_graph, + ( + ('a', '(lambda b: (a + b))', '(lambda b: (a + b))(a)'), + (('(lambda b: (a + b))', 'pass'), '(lambda b: (a + b))(a)', 'pass'), + ('(lambda b: (a + b))(a)', 'pass', '(lambda b: (a + b))(a)'), + ), + ) + self.assertGraphEnds(fn_graph, 'a', ('(lambda b: (a + b))(a)',)) + self.assertGraphMatches( + lam_graph, + ( + ('b', '(a + b)', None), + ), + ) + self.assertGraphEnds(lam_graph, 'b', ('(a + b)',)) + + def test_pass(self): + + def test_fn(a): # pylint:disable=unused-argument + pass + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', 'pass', None), + ), + ) + self.assertGraphEnds(graph, 'a', ('pass',)) + + def test_try_finally(self): + + def test_fn(a): + try: + a = 1 + finally: + a = 2 + return a + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', 'a = 1', 'a = 2'), + ('a = 1', 'a = 2', 'return a'), + ('a = 2', 'return a', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'Try:2', 'return a'), + ), + ) + self.assertGraphEnds(graph, 'a', ('return a',)) + + def test_try_except_single_bare(self): + + def test_fn(a): + try: + a = 1 + a = 2 + except: # pylint:disable=bare-except + a = 3 + return a + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', 'a = 1', 'a = 2'), + ('a = 2', 'a = 3', 'return a'), + (('a = 2', 'a = 3'), 'return a', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'Try:2', 'return a'), + ('a = 2', 'ExceptHandler:5', 'return a'), + ), + ) + self.assertGraphEnds(graph, 'a', ('return a',)) + + def test_try_except_single(self): + + def test_fn(a): + try: + a = 1 + a = 2 + except Exception1: # pylint:disable=undefined-variable + a = 3 + return a + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', 'a = 1', 'a = 2'), + ('a = 2', 'a = 3', 'return a'), + (('a = 2', 'a = 3'), 'return a', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'Try:2', 'return a'), + ('a = 2', 'ExceptHandler:5', 'return a'), + ), + ) + self.assertGraphEnds(graph, 'a', ('return a',)) + + def test_try_except_single_aliased(self): + + def test_fn(a): + try: + a = 1 + except Exception1 as e: # pylint:disable=undefined-variable,unused-variable + a = 2 + return a + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', 'a = 1', ('a = 2', 'return a')), + (('a = 1', 'a = 2'), 'return a', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'Try:2', 'return a'), + ('a = 1', 'ExceptHandler:4', 'return a'), + ), + ) + self.assertGraphEnds(graph, 'a', ('return a',)) + + def test_try_except_single_tuple_aliased(self): + + def test_fn(a): + try: + a = 1 + except (Exception1, Exception2) as e: # pylint:disable=undefined-variable,unused-variable + a = 2 + return a + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', 'a = 1', ('a = 2', 'return a')), + (('a = 1', 'a = 2'), 'return a', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'Try:2', 'return a'), + ('a = 1', 'ExceptHandler:4', 'return a'), + ), + ) + self.assertGraphEnds(graph, 'a', ('return a',)) + + def test_try_except_multiple(self): + + def test_fn(a): + try: + a = 1 + except Exception1: # pylint:disable=undefined-variable + a = 2 + except Exception2: # pylint:disable=undefined-variable + a = 3 + return a + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', 'a = 1', ('a = 2', 'a = 3', 'return a')), + (('a = 1', 'a = 2', 'a = 3'), 'return a', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'Try:2', 'return a'), + ('a = 1', 'ExceptHandler:4', 'return a'), + ('a = 1', 'ExceptHandler:6', 'return a'), + ), + ) + self.assertGraphEnds(graph, 'a', ('return a',)) + + def test_try_except_finally(self): + + def test_fn(a): + try: + a = 1 + except Exception1: # pylint:disable=undefined-variable + a = 2 + except Exception2: # pylint:disable=undefined-variable + a = 3 + finally: + a = 4 + return a + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', 'a = 1', ('a = 2', 'a = 3', 'a = 4')), + (('a = 1', 'a = 2', 'a = 3'), 'a = 4', 'return a'), + ('a = 4', 'return a', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'Try:2', 'return a'), + ('a = 1', 'ExceptHandler:4', 'a = 4'), + ('a = 1', 'ExceptHandler:6', 'a = 4'), + ), + ) + self.assertGraphEnds(graph, 'a', ('return a',)) + + def test_try_in_if(self): + + def test_fn(a): + try: + if a > 0: + a = 1 + else: + a = 2 + except Exception1: # pylint:disable=undefined-variable + a = 3 + a = 4 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', '(a > 0)', ('a = 1', 'a = 2')), + ('(a > 0)', 'a = 1', ('a = 3', 'a = 4')), + ('(a > 0)', 'a = 2', ('a = 3', 'a = 4')), + (('a = 1', 'a = 2'), 'a = 3', 'a = 4'), + (('a = 1', 'a = 2', 'a = 3'), 'a = 4', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a', 'Try:2', 'a = 4'), + ('a', 'If:3', ('a = 3', 'a = 4')), + (('a = 1', 'a = 2'), 'ExceptHandler:7', 'a = 4'), + ), + ) + self.assertGraphEnds(graph, 'a', ('a = 4',)) + + def test_try_in_if_all_branches_exit(self): + + def test_fn(a, b): + try: + if a > 0: + raise b + else: + return 0 + except b: + return 1 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a, b', '(a > 0)', ('raise b', 'return 0')), + ('(a > 0)', 'raise b', 'return 1'), + ('(a > 0)', 'return 0', None), + ('raise b', 'return 1', None), + ), + ) + self.assertStatementEdges( + graph, + ( + ('a, b', 'Try:2', None), + ('a, b', 'If:3', 'return 1'), + ('raise b', 'ExceptHandler:7', None), + ), + ) + self.assertGraphEnds(graph, 'a, b', ('return 0', 'return 1', 'raise b')) + + def test_raise_exits(self): + + def test_fn(a, b): + raise b + return a # pylint:disable=unreachable + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a, b', 'raise b', None), + (None, 'return a', None), + ), + ) + self.assertGraphEnds(graph, 'a, b', ('raise b', 'return a')) + + def test_raise_triggers_enclosing_finally(self): + + def test_fn(a): + try: + try: + raise a + return 1 # pylint:disable=unreachable + finally: + b = 1 + return 2 + finally: + b = 2 + return b + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', 'raise a', 'b = 1'), + (('raise a', 'return 1'), 'b = 1', 'b = 2'), + (None, 'return 1', 'b = 1'), + (None, 'return 2', 'b = 2'), + (('return 2', 'b = 1'), 'b = 2', None), + (None, 'return b', None), + ), + ) + self.assertGraphEnds( + graph, 'a', ('return b', 'b = 2')) + + def test_raise_adds_finally_sortcuts(self): + + def test_fn(a): + try: + try: + if a > 0: + raise a + c = 1 + finally: + b = 1 + c = 2 + finally: + b = 2 + return b, c + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', '(a > 0)', ('raise a', 'c = 1')), + ('(a > 0)', 'raise a', 'b = 1'), + ('(a > 0)', 'c = 1', 'b = 1'), + (('raise a', 'c = 1'), 'b = 1', ('c = 2', 'b = 2')), + ('b = 1', 'c = 2', 'b = 2'), + (('b = 1', 'c = 2'), 'b = 2', 'return (b, c)'), + ('b = 2', 'return (b, c)', None), + ), + ) + self.assertGraphEnds( + graph, 'a', ('return (b, c)', 'b = 2')) + + def test_raise_exits_via_except(self): + + def test_fn(a, b): + try: + raise b + except a: + c = 1 + except b: + c = 2 + finally: + c += 3 + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a, b', 'raise b', ('c = 1', 'c = 2', 'c += 3')), + ('raise b', 'c = 1', 'c += 3'), + ('raise b', 'c = 2', 'c += 3'), + (('raise b', 'c = 1', 'c = 2'), 'c += 3', None), + ), + ) + self.assertGraphEnds(graph, 'a, b', ('c += 3',)) + + def test_list_comprehension(self): + + def test_fn(a): + c = [b for b in a] + return c + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a', 'c = [b for b in a]', 'return c'), + ('c = [b for b in a]', 'return c', None), + ), + ) + self.assertGraphEnds(graph, 'a', ('return c',)) + + def test_class_definition_empty(self): + + def test_fn(a, b): + class C(a(b)): + pass + return C + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a, b', 'class C', 'return C'), + ('class C', 'return C', None), + ), + ) + self.assertGraphEnds(graph, 'a, b', ('return C',)) + + def test_class_definition_with_members(self): + + def test_fn(a, b): + class C(a(b)): + d = 1 + return C + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('a, b', 'class C', 'return C'), + ('class C', 'return C', None), + ), + ) + self.assertGraphEnds(graph, 'a, b', ('return C',)) + + def test_import(self): + + def test_fn(): + from a import b # pylint:disable=g-import-not-at-top + return b + + graph, = self._build_cfg(test_fn).values() + + self.assertGraphMatches( + graph, + ( + ('', 'from a import b', 'return b'), + ('from a import b', 'return b', None), + ), + ) + self.assertGraphEnds(graph, '', ('return b',)) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/common_transformers/__init__.py b/src/braket/experimental/autoqasm/autograph/pyct/common_transformers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/braket/experimental/autoqasm/autograph/pyct/common_transformers/anf.py b/src/braket/experimental/autoqasm/autograph/pyct/common_transformers/anf.py new file mode 100644 index 00000000..99ef0135 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/common_transformers/anf.py @@ -0,0 +1,620 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Conversion to A-normal form. + +The general idea of A-normal form is that every intermediate value is +explicitly named with a variable. For more, see +https://en.wikipedia.org/wiki/A-normal_form. + +The specific converters used here are based on Python AST semantics as +documented at https://greentreesnakes.readthedocs.io/en/latest/. +""" + +import collections + +import gast + +from braket.experimental.autoqasm.autograph.pyct import gast_util +from braket.experimental.autoqasm.autograph.pyct import templates +from braket.experimental.autoqasm.autograph.pyct import transformer + + +# TODO(mdan): Replace with naming.Namer. +class DummyGensym: + """A dumb gensym that suffixes a stem by sequential numbers from 1000.""" + + def __init__(self): + # A proper implementation needs to account for: + # * ctx.info.namespace + # * all the symbols defined in the AST + # * the symbols generated so far + self._idx = 0 + + def new_name(self, stem='tmp'): + self._idx += 1 + return stem + '_' + str(1000 + self._idx) + + +REPLACE = lambda _1, _2, _3: True +LEAVE = lambda _1, _2, _3: False +ANY = object() + + +class ASTEdgePattern(collections.namedtuple( + 'ASTEdgePattern', ['parent', 'field', 'child'])): + """A pattern defining a type of AST edge. + + This consists of three components: + - The type of the parent node, checked with isinstance, + - The name of the field, checked with string equality, and + - The type of the child node, also checked with isinstance. + If all three match, the whole pattern is considered to match. + + In all three slots, the special value `anf.ANY` is treated as "match + anything". The internal nodes are produced from the `gast` library rather + than the standard `ast` module, which may affect `isinstance` checks. + """ + __slots__ = () + + def matches(self, parent, field, child): + """Computes whether this pattern matches the given edge.""" + if self.parent is ANY or isinstance(parent, self.parent): + pass # OK + else: + return False + if self.field is ANY or field == self.field: + pass # OK + else: + return False + return self.child is ANY or isinstance(child, self.child) + + +class AnfTransformer(transformer.Base): + """Performs the conversion to A-normal form (ANF).""" + + # The algorithm is a postorder recursive tree walk. Any given node A may, in + # general, require creation of a series B of Assign statements, which compute + # and explicitly name the intermediate values needed to compute the value of + # A. If A was already a statement, it can be replaced with the sequence B + + # [A]. If A was an expression, B needs to be propagated up the tree until a + # statement is encountered. Since the `ast.NodeTransformer` framework makes + # no provision for subtraversals returning side information, this class + # accumulates the sequence B in an instance variable. + + # The only other subtlety is that some Python statements (like `if`) have both + # expression fields (`test`) and statement list fields (`body` and `orelse`). + # Any additional assignments needed to name all the intermediate values in the + # `test` can be prepended to the `if` node, but assignments produced by + # processing the `body` and the `orelse` need to be kept together with them, + # and not accidentally lifted out of the `if`. + + def __init__(self, ctx, config): + """Creates an ANF transformer. + + Args: + ctx: transformer.Context + config: Configuration + """ + super(AnfTransformer, self).__init__(ctx) + if config is None: + # These could be pulled out, but are generally considered to already be in + # A-normal form. Thus they are left in by default, but could be pulled + # out if the configuration calls for it. + if gast_util.GAST2: + literal_node_types = ( + gast.Num, gast.Str, gast.Bytes, gast.NameConstant, + gast.Name # Name is here to cover True, False, and None in Python 2 + ) + elif gast_util.GAST3: + literal_node_types = ( + gast.Constant, + gast.Name # Name is here to cover True, False, and None in Python 2 + ) + else: + assert False + + self._overrides = [ + (ASTEdgePattern(ANY, ANY, literal_node_types), LEAVE), + (ASTEdgePattern(ANY, ANY, gast.expr), REPLACE)] + else: + self._overrides = config + self._gensym = DummyGensym() + self._pending_statements = [] + + def _consume_pending_statements(self): + ans = self._pending_statements + self._pending_statements = [] + return ans + + def _add_pending_statement(self, stmt): + self._pending_statements.append(stmt) + + def _match(self, pattern, parent, field, child): + if pattern is ANY: + return True + else: + return pattern.matches(parent, field, child) + + def _should_transform(self, parent, field, child): + for pat, result in self._overrides: + if self._match(pat, parent, field, child): + return result(parent, field, child) + # Fell off the end of the pattern list: do not transform + return False + + def _do_transform_node(self, node): + temp_name = self._gensym.new_name() + temp_assign = templates.replace( + 'temp_name = expr', temp_name=temp_name, expr=node)[0] + self._add_pending_statement(temp_assign) + answer = templates.replace('temp_name', temp_name=temp_name)[0] + return answer + + def _ensure_node_in_anf(self, parent, field, node): + """Puts `node` in A-normal form, by replacing it with a variable if needed. + + The exact definition of A-normal form is given by the configuration. The + parent and the incoming field name are only needed because the configuration + may be context-dependent. + + Args: + parent: An AST node, the parent of `node`. + field: The field name under which `node` is the child of `parent`. + node: An AST node, potentially to be replaced with a variable reference. + + Returns: + node: An AST node; the argument if transformation was not necessary, + or the new variable reference if it was. + """ + if node is None: + return node + if _is_trivial(node): + return node + if isinstance(node, list): + # If something's field was actually a list, e.g., variadic arguments. + return [self._ensure_node_in_anf(parent, field, n) for n in node] + if isinstance(node, gast.keyword): + node.value = self._ensure_node_in_anf(parent, field, node.value) + return node + if isinstance(node, (gast.Starred, gast.withitem, gast.slice)): + # These nodes aren't really extractable in their own right, but their + # subnodes might be. Propagate the parent and field name to the child + # nodes, instead of querying the configuration for children of, e.g., + # gast.Starred. + return self._ensure_fields_in_anf(node, parent, field) + if self._should_transform(parent, field, node): + return self._do_transform_node(node) + else: + return node + + def _ensure_fields_in_anf(self, node, parent=None, super_field=None): + for field in node._fields: + if field.startswith('__'): + continue + parent_supplied = node if parent is None else parent + field_supplied = field if super_field is None else super_field + setattr(node, field, self._ensure_node_in_anf( + parent_supplied, field_supplied, getattr(node, field))) + return node + + def _visit_strict_statement(self, node, children_ok_to_transform=True): + assert not self._pending_statements + node = self.generic_visit(node) + if children_ok_to_transform: + self._ensure_fields_in_anf(node) + results = self._consume_pending_statements() + results.append(node) + return results + + def _visit_trivial_only_statement(self, node, msg): + assert not self._pending_statements + node = self.generic_visit(node) + self._ensure_fields_in_anf(node) + if self._pending_statements: + raise ValueError(msg) + else: + return node + + def _visit_strict_expression(self, node): + node = self.generic_visit(node) + self._ensure_fields_in_anf(node) + return node + + def _visit_trivial_only_expression(self, node, msg): + k = len(self._pending_statements) + node = self.generic_visit(node) + self._ensure_fields_in_anf(node) + # This check relies on there being no opportunities to consume pending + # statements while traversing children of an expression. + if len(self._pending_statements) != k: + raise ValueError(msg) + else: + return node + + # Note on code order: These are listed in the same order as the grammar + # elements on https://github.com/serge-sans-paille/gast + + # FunctionDef, AsyncFunctionDef, and ClassDef should be correct by default. + + def visit_Return(self, node): + return self._visit_strict_statement(node) + + def visit_Delete(self, node): + return self._visit_strict_statement(node, children_ok_to_transform=False) + + def visit_Assign(self, node): + return self._visit_strict_statement(node, children_ok_to_transform=False) + + def visit_AugAssign(self, node): + return self._visit_strict_statement(node, children_ok_to_transform=False) + + def visit_Print(self, node): + return self._visit_strict_statement(node) + + def visit_For(self, node): + assert not self._pending_statements + # It's important to visit node.iter first, because any statements created + # thereby need to live outside the body. + self.visit(node.iter) + node.iter = self._ensure_node_in_anf(node, 'iter', node.iter) + iter_stmts = self._consume_pending_statements() + # This generic_visit will revisit node.iter, but that is correct because by + # this point the node.iter link has been checked. It may be somewhat + # expensive if the configuration didn't call for transforming node.iter, as + # then it may be large and will be uselessly transformed again. This + # behavior is what causes the documented effect that configuration callables + # may be invoked more than once of the same links; if the code is rewritten + # not to do that (anywhere), the docstring of `transform` should be updated. + node = self.generic_visit(node) + assert not self._pending_statements + iter_stmts.append(node) + return iter_stmts + + def visit_AsyncFor(self, node): + msg = ('Nontrivial AsyncFor nodes not supported yet ' + '(need to think through the semantics).') + return self._visit_trivial_only_statement(node, msg) + + def visit_While(self, node): + assert not self._pending_statements + self.visit(node.test) + node.test = self._ensure_node_in_anf(node, 'test', node.test) + if self._pending_statements: + msg = ('While with nontrivial test not supported yet ' + '(need to avoid precomputing the test).') + raise ValueError(msg) + # If traversing node.test yielded no statements extracted, the generic visit + # will do the right thing. + return self.generic_visit(node) + + def visit_If(self, node): + assert not self._pending_statements + # It's important to visit node.test first, because any statements created + # thereby need to live outside the body. + self.visit(node.test) + node.test = self._ensure_node_in_anf(node, 'test', node.test) + condition_stmts = self._consume_pending_statements() + # This generic_visit will revisit node.test, but that is correct because by + # this point the node.test link has been checked. It may be somewhat + # expensive if the configuration didn't call for transforming node.test, as + # then it may be large and will be uselessly transformed again. This + # happens in several places. + node = self.generic_visit(node) + assert not self._pending_statements + condition_stmts.append(node) + return condition_stmts + + def visit_With(self, node): + assert not self._pending_statements + # It's important to visit node.items first, because any statements created + # thereby need to live outside the body. + for item in node.items: + self.visit(item) + node.items = [self._ensure_node_in_anf(node, 'items', n) + for n in node.items] + contexts_stmts = self._consume_pending_statements() + # This generic_visit will revisit node.items, but that is correct because by + # this point the node.items link has been checked. It may be somewhat + # expensive if the configuration didn't call for transforming node.items, as + # then it may be large and will be uselessly transformed again. This + # happens in several places. + node = self.generic_visit(node) + assert not self._pending_statements + contexts_stmts.append(node) + return contexts_stmts + + def visit_AsyncWith(self, node): + msg = ('Nontrivial AsyncWith nodes not supported yet ' + '(need to think through the semantics).') + return self._visit_trivial_only_statement(node, msg) + + def visit_Raise(self, node): + return self._visit_strict_statement(node) + + # Try should be correct by default. + + def visit_Assert(self, node): + msg = ('Nontrivial Assert nodes not supported yet ' + '(need to avoid computing the test when assertions are off, and ' + 'avoid computing the irritant when the assertion does not fire).') + return self._visit_trivial_only_statement(node, msg) + + # Import and ImportFrom should be correct by default. + + def visit_Exec(self, node): + return self._visit_strict_statement(node) + + # Global and Nonlocal should be correct by default. + + def visit_Expr(self, node): + return self._visit_strict_statement(node, children_ok_to_transform=False) + + # Pass, Break, and Continue should be correct by default. + + def visit_BoolOp(self, node): + msg = ('Nontrivial BoolOp nodes not supported yet ' + '(need to preserve short-circuiting semantics).') + return self._visit_trivial_only_expression(node, msg) + + def visit_BinOp(self, node): + return self._visit_strict_expression(node) + + def visit_UnaryOp(self, node): + return self._visit_strict_expression(node) + + def visit_Lambda(self, node): + msg = ('Nontrivial Lambda nodes not supported ' + '(cannot insert statements into lambda bodies).') + return self._visit_trivial_only_expression(node, msg) + + def visit_IfExp(self, node): + msg = ('Nontrivial IfExp nodes not supported yet ' + '(need to convert to If statement, to evaluate branches lazily ' + 'and insert statements into them).') + return self._visit_trivial_only_expression(node, msg) + + def visit_Dict(self, node): + return self._visit_strict_expression(node) + + def visit_Set(self, node): + return self._visit_strict_expression(node) + + def visit_ListComp(self, node): + msg = ('ListComp nodes not supported ' + '(need to convert to a form that tolerates ' + 'assignment statements in clause bodies).') + raise ValueError(msg) + + def visit_SetComp(self, node): + msg = ('SetComp nodes not supported ' + '(need to convert to a form that tolerates ' + 'assignment statements in clause bodies).') + raise ValueError(msg) + + def visit_DictComp(self, node): + msg = ('DictComp nodes not supported ' + '(need to convert to a form that tolerates ' + 'assignment statements in clause bodies).') + raise ValueError(msg) + + def visit_GeneratorExp(self, node): + msg = ('GeneratorExp nodes not supported ' + '(need to convert to a form that tolerates ' + 'assignment statements in clause bodies).') + raise ValueError(msg) + + def visit_Await(self, node): + msg = ('Nontrivial Await nodes not supported yet ' + '(need to think through the semantics).') + return self._visit_trivial_only_expression(node, msg) + + def visit_Yield(self, node): + return self._visit_strict_expression(node) + + def visit_YieldFrom(self, node): + msg = ('Nontrivial YieldFrom nodes not supported yet ' + '(need to unit-test them in Python 2).') + return self._visit_trivial_only_expression(node, msg) + + def visit_Compare(self, node): + if len(node.ops) > 1: + msg = ('Multi-ary compare nodes not supported yet ' + '(need to preserve short-circuiting semantics).') + raise ValueError(msg) + return self._visit_strict_expression(node) + + def visit_Call(self, node): + return self._visit_strict_expression(node) + + def visit_Repr(self, node): + msg = ('Nontrivial Repr nodes not supported yet ' + '(need to research their syntax and semantics).') + return self._visit_trivial_only_expression(node, msg) + + def visit_FormattedValue(self, node): + msg = ('Nontrivial FormattedValue nodes not supported yet ' + '(need to unit-test them in Python 2).') + return self._visit_trivial_only_expression(node, msg) + + def visit_JoinedStr(self, node): + msg = ('Nontrivial JoinedStr nodes not supported yet ' + '(need to unit-test them in Python 2).') + return self._visit_trivial_only_expression(node, msg) + + def visit_Attribute(self, node): + return self._visit_strict_expression(node) + + def visit_Subscript(self, node): + return self._visit_strict_expression(node) + + # Starred and Name are correct by default, because the right thing to do is to + # just recur. + + def visit_List(self, node): + node = self.generic_visit(node) + if not isinstance(node.ctx, gast.Store): + self._ensure_fields_in_anf(node) + return node + + def visit_Tuple(self, node): + node = self.generic_visit(node) + if not isinstance(node.ctx, gast.Store): + self._ensure_fields_in_anf(node) + return node + + +def _is_py2_name_constant(node): + return isinstance(node, gast.Name) and node.id in ['True', 'False', 'None'] + + +def _is_trivial(node): + """Returns whether to consider the given node 'trivial'. + + The definition of 'trivial' is a node that can't meaningfully be pulled out + into its own assignment statement. + + This is surprisingly difficult to do robustly across versions of Python and + gast, as the parsing of constants has changed, if I may, constantly. + + Args: + node: An AST node to check for triviality + + Returns: + trivial: A Python `bool` indicating whether the node is trivial. + """ + trivial_node_types = ( + # Variable names + gast.Name, + # Non-nodes that show up as AST fields + bool, + str, + # Binary operators + gast.Add, + gast.Sub, + gast.Mult, + gast.Div, + gast.Mod, + gast.Pow, + gast.LShift, + gast.RShift, + gast.BitOr, + gast.BitXor, + gast.BitAnd, + gast.FloorDiv, + # Unary operators + gast.Invert, + gast.Not, + gast.UAdd, + gast.USub, + # Comparison operators + gast.Eq, + gast.NotEq, + gast.Lt, + gast.LtE, + gast.Gt, + gast.GtE, + gast.Is, + gast.IsNot, + gast.In, + gast.NotIn, + # Other leaf nodes that don't make sense standalone. + gast.expr_context, + ) + if isinstance(node, trivial_node_types) and not _is_py2_name_constant(node): + return True + if gast_util.is_ellipsis(node): + return True + + return False + + +def transform(node, ctx, config=None): + """Converts the given node to A-normal form (ANF). + + The general idea of A-normal form: https://en.wikipedia.org/wiki/A-normal_form + + The specific converters used here are based on Python AST semantics as + documented at https://greentreesnakes.readthedocs.io/en/latest/. + + What exactly should be considered A-normal form for any given programming + language is not completely obvious. The transformation defined here is + therefore configurable as to which syntax to replace with a fresh variable and + which to leave be. The configuration is intentionally flexible enough to + define very precise variable insertion transformations, should that be + desired. + + The configuration is a list of syntax rules, each of which is a 2-tuple: + - An `ASTEdgePattern` (which see) defining a type of AST edge, and + - Whether to transform children of such edges. + The special object `anf.ANY` may be used as a pattern that matches all edges. + + Each replacement directive is one of three possible things: + - The object `anf.REPLACE`, meaning "Replace this child node with a variable", + - The object `anf.LEAVE`, meaning "Do not replace this child node with a + variable", or + - A Python callable. If a callable, it is called with the parent node, the + field name, and the child node, and must compute a boolean indicating + whether to transform the child node or not. The callable is free to use + whatever context information it chooses. The callable may be invoked more + than once on the same link, and must produce the same answer each time. + + The syntax rules are tested in order, and the first match governs. If no rule + matches, the node is not transformed. + + The above rules notwithstanding, + - Variable references are never replaced with (fresh) variables, as that would + accomplish nothing. + - The left-hand children of Assign and AugAssign nodes, and the children of + Del nodes, are never replaced with variables, as that would break their + semantics. + - The right-hand children of Assign nodes are never replaced with variables, + as the original assignment would still have to be present in the result + to define the new variable. (That is, there's no point in transforming + `x = sin(y)` into `tmp = sin(y); x = tmp`.) + - The right-hand children of AugAssign nodes are never replaced with variables + either, but only because the difference from Assign was considered a + potential source of confusion (and it would have been slightly awkward in + the code to treat the RHS differently than the LHS). + - Various special-purpose AST nodes are not exposed to the configuration, lest + the transform produce invalid syntax like, e.g., `tmp = +; x = 1 tmp 2`. + + For example, the configuration + ```python + [(anf.ASTEdgePattern(anf.ANY, anf.ANY, gast.expr), anf.REPLACE)] + ``` + gives explicit fresh names to all expressions regardless of context (except as + outlined above), whereas + ```python + [(anf.ASTEdgePattern(gast.If, "test", anf.ANY), anf.REPLACE)] + ``` + only transforms the conditionals of `if` statements (but not, e.g., `while`). + + If no configuration is supplied, the default behavior is to transform all + expressions except literal constants, which is defined as a configuration as + ```python + # For Python 3, and gast library versions before 0.3 + literals = (gast.Num, gast.Str, gast.Bytes, gast.NameConstant) + [(anf.ASTEdgePattern(anf.ANY, anf.ANY, literals), anf.LEAVE), + (anf.ASTEdgePattern(anf.ANY, anf.ANY, gast.expr), anf.REPLACE)] + ``` + + Args: + node: The node to transform. + ctx: transformer.EntityInfo. TODO(mdan): What information does this + argument provide? + config: Optional ANF configuration. If omitted, ANF replaces all expression + expect literal constants. + """ + return AnfTransformer(ctx, config).visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/pyct/common_transformers/anf_test.py b/src/braket/experimental/autoqasm/autograph/pyct/common_transformers/anf_test.py new file mode 100644 index 00000000..4fb04e50 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/common_transformers/anf_test.py @@ -0,0 +1,518 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for anf module.""" + +import textwrap + +import gast + +from braket.experimental.autoqasm.autograph.pyct import loader +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import transformer +from braket.experimental.autoqasm.autograph.pyct.common_transformers import anf +from tensorflow.python.platform import test + + +# TODO(mdan): These two functions no longer need to be at the top level. +# TODO(mdan): Don't use exec. +def exec_test_function(): + # The point is to test A-normal form conversion of exec + # pylint: disable=exec-used + exec('computed' + 5 + 'stuff', globals(), locals()) + + +def exec_expected_result(): + # pylint: disable=exec-used + tmp_1001 = 'computed' + 5 + tmp_1002 = tmp_1001 + 'stuff' + tmp_1003 = globals() + tmp_1004 = locals() + exec(tmp_1002, tmp_1003, tmp_1004) + + +class AnfTestBase(test.TestCase): + + def _simple_context(self): + entity_info = transformer.EntityInfo( + name='test_fn', + source_code=None, + source_file=None, + future_features=(), + namespace=None) + return transformer.Context(entity_info, None, None) + + def assert_same_ast(self, expected_node, node, msg=None): + expected_source = parser.unparse(expected_node, indentation=' ') + expected_str = textwrap.dedent(expected_source).strip() + got_source = parser.unparse(node, indentation=' ') + got_str = textwrap.dedent(got_source).strip() + self.assertEqual(expected_str, got_str, msg=msg) + + def assert_body_anfs_as_expected(self, expected_fn, test_fn, config=None): + # Testing the code bodies only. Wrapping them in functions so the + # syntax highlights nicely, but Python doesn't try to execute the + # statements. + exp_node, _ = parser.parse_entity(expected_fn, future_features=()) + node, _ = parser.parse_entity(test_fn, future_features=()) + node = anf.transform(node, self._simple_context(), config=config) + exp_name = exp_node.name + # Ignoring the function names in the result because they can't be + # the same (because both functions have to exist in the same scope + # at the same time). + node.name = exp_name + self.assert_same_ast(exp_node, node) + # Check that ANF is idempotent + node_repeated = anf.transform(node, self._simple_context()) + self.assert_same_ast(node_repeated, node) + + +class AnfTransformerTest(AnfTestBase): + + def test_basic(self): + def test_function(): + a = 0 + return a + + node, _ = parser.parse_entity(test_function, future_features=()) + node = anf.transform(node, self._simple_context()) + result, _, _ = loader.load_ast(node) + self.assertEqual(test_function(), result.test_function()) + + def test_binop_basic(self): + + def test_function(x, y, z): + a = x + y + z + return a + + def expected_result(x, y, z): + tmp_1001 = x + y + a = tmp_1001 + z + return a + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_if_basic(self): + + def test_function(a, b, c, e, f, g): + if a + b + c: + d = e + f + g + return d + + def expected_result(a, b, c, e, f, g): + tmp_1001 = a + b + tmp_1002 = tmp_1001 + c + if tmp_1002: + tmp_1003 = e + f + d = tmp_1003 + g + return d + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_nested_binop_and_return(self): + + def test_function(b, c, d, e): + return (2 * b + c) + (d + e) + + def expected_result(b, c, d, e): + tmp_1001 = 2 * b + tmp_1002 = tmp_1001 + c + tmp_1003 = d + e + tmp_1004 = tmp_1002 + tmp_1003 + return tmp_1004 + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_function_call_and_expr(self): + + def test_function(call_something, a, b, y, z, c, d, e, f, g, h, i): + call_something(a + b, y * z, kwarg=c + d, *(e + f), **(g + h + i)) + + def expected_result(call_something, a, b, y, z, c, d, e, f, g, h, i): + tmp_1001 = g + h + tmp_1002 = a + b + tmp_1003 = y * z + tmp_1004 = e + f + tmp_1005 = c + d + tmp_1006 = tmp_1001 + i + call_something(tmp_1002, tmp_1003, kwarg=tmp_1005, *tmp_1004, **tmp_1006) + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_with_and_print(self): + + def test_function(a, b, c): + with a + b + c as d: + print(2 * d + 1) + + def expected_result(a, b, c): + tmp_1001 = a + b + tmp_1002 = tmp_1001 + c + with tmp_1002 as d: + tmp_1003 = 2 * d + tmp_1004 = tmp_1003 + 1 + print(tmp_1004) + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_nested_multi_value_assign(self): + + def test_function(a, b, c): + x, y = a, a + b + (z, y), x = (c, y + b), x + a + return z, (y, x) + + def expected_result(a, b, c): + tmp_1001 = a + b + x, y = a, tmp_1001 + tmp_1002 = y + b + tmp_1003 = (c, tmp_1002) + tmp_1004 = x + a + (z, y), x = tmp_1003, tmp_1004 + tmp_1005 = y, x + tmp_1006 = z, tmp_1005 + return tmp_1006 + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_deeply_nested_multi_value_assign(self): + + def test_function(a): + [([(b, c), [d, e]], (f, g)), [(h, i, j), k]] = a + return [([(b, c), [d, e]], (f, g)), [(h, i, j), k]] + + def expected_result(a): + [([(b, c), [d, e]], (f, g)), [(h, i, j), k]] = a + tmp_1001 = b, c + tmp_1002 = [d, e] + tmp_1003 = [tmp_1001, tmp_1002] + tmp_1004 = f, g + tmp_1005 = h, i, j + tmp_1006 = tmp_1003, tmp_1004 + tmp_1007 = [tmp_1005, k] + tmp_1008 = [tmp_1006, tmp_1007] + return tmp_1008 + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_local_definition_and_binary_compare(self): + + def test_function(): + def foo(a, b): + return 2 * a < b + return foo + + def expected_result(): + def foo(a, b): + tmp_1001 = 2 * a + tmp_1002 = tmp_1001 < b + return tmp_1002 + return foo + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_list_literal(self): + + def test_function(a, b, c, d, e, f): + return [a + b, c + d, e + f] + + def expected_result(a, b, c, d, e, f): + tmp_1001 = a + b + tmp_1002 = c + d + tmp_1003 = e + f + tmp_1004 = [tmp_1001, tmp_1002, tmp_1003] + return tmp_1004 + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_tuple_literal_and_unary(self): + + def test_function(a, b, c, d, e, f): + return (a + b, -(c + d), e + f) + + def expected_result(a, b, c, d, e, f): + tmp_1001 = c + d + tmp_1002 = a + b + tmp_1003 = -tmp_1001 + tmp_1004 = e + f + tmp_1005 = (tmp_1002, tmp_1003, tmp_1004) + return tmp_1005 + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_set_literal(self): + + def test_function(a, b, c, d, e, f): + return set(a + b, c + d, e + f) + + def expected_result(a, b, c, d, e, f): + tmp_1001 = a + b + tmp_1002 = c + d + tmp_1003 = e + f + tmp_1004 = set(tmp_1001, tmp_1002, tmp_1003) + return tmp_1004 + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_dict_literal_and_repr(self): + + def test_function(foo, bar, baz): + return repr({foo + bar + baz: 7 | 8}) + + def expected_result(foo, bar, baz): + tmp_1001 = foo + bar + tmp_1002 = tmp_1001 + baz + tmp_1003 = 7 | 8 + tmp_1004 = {tmp_1002: tmp_1003} + tmp_1005 = repr(tmp_1004) + return tmp_1005 + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_field_read_and_write(self): + + def test_function(a, d): + a.b.c = d.e.f + 3 + + def expected_result(a, d): + tmp_1001 = a.b + tmp_1002 = d.e + tmp_1003 = tmp_1002.f + tmp_1001.c = tmp_1003 + 3 + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_subscript_read_and_write(self): + + def test_function(a, b, c, d, e, f): + a[b][c] = d[e][f] + 3 + + def expected_result(a, b, c, d, e, f): + tmp_1001 = a[b] + tmp_1002 = d[e] + tmp_1003 = tmp_1002[f] + tmp_1001[c] = tmp_1003 + 3 + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_augassign_and_delete(self): + + def test_function(a, x, y, z): + a += x + y + z + del a + del z[y][x] + + def expected_result(a, x, y, z): + tmp_1001 = x + y + a += tmp_1001 + z + del a + tmp_1002 = z[y] + del tmp_1002[x] + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_raise_yield_and_raise(self): + + def test_function(a, c, some_computed, exception): + yield a ** c + raise some_computed('complicated' + exception) + + def expected_result(a, c, some_computed, exception): + tmp_1001 = a ** c + yield tmp_1001 + tmp_1002 = 'complicated' + exception + tmp_1003 = some_computed(tmp_1002) + raise tmp_1003 + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_with_and_if_with_expressions(self): + + def test_function(foo, bar, function, quux, quozzle, w, x, y, z): + with foo + bar: + function(x + y) + if quux + quozzle: + function(z / w) + + def expected_result(foo, bar, function, quux, quozzle, w, x, y, z): + tmp_1001 = foo + bar + with tmp_1001: + tmp_1002 = x + y + function(tmp_1002) + tmp_1003 = quux + quozzle + if tmp_1003: + tmp_1004 = z / w + function(tmp_1004) + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_exec(self): + self.assert_body_anfs_as_expected(exec_expected_result, exec_test_function) + + def test_simple_while_and_assert(self): + + def test_function(foo, quux): + while foo: + assert quux + foo = foo + 1 * 3 + + def expected_result(foo, quux): + while foo: + assert quux + tmp_1001 = 1 * 3 + foo = foo + tmp_1001 + + self.assert_body_anfs_as_expected(expected_result, test_function) + + def test_for(self): + + def test_function(compute, something, complicated, foo): + for foo in compute(something + complicated): + bar = foo + 1 * 3 + return bar + + def expected_result(compute, something, complicated, foo): + tmp_1001 = something + complicated + tmp_1002 = compute(tmp_1001) + for foo in tmp_1002: + tmp_1003 = 1 * 3 + bar = foo + tmp_1003 + return bar + + self.assert_body_anfs_as_expected(expected_result, test_function) + + # This test collects several examples where the definition of A-normal form + # implemented by this transformer is questionable. Mostly it's here to spell + # out what the definition is in these cases. + def test_controversial(self): + + def test_function(b, c, d, f): + a = c + d + a.b = c + d + a[b] = c + d + a += c + d + a, b = c + a, b = c, d + a = f(c) + a = f(c + d) + a[b + d] = f.e(c + d) + + def expected_result(b, c, d, f): + a = c + d + a.b = c + d # Should be a.b = tmp? (Definitely not tmp = c + d) + a[b] = c + d # Should be a[b] = tmp? (Definitely not tmp = c + d) + a += c + d # Should be a += tmp? (Definitely not tmp = c + d) + a, b = c # Should be a = c[0], b = c[1]? Or not? + a, b = c, d # Should be a = c, b = d? Or not? + a = f(c) + tmp_1001 = c + d + a = f(tmp_1001) + tmp_1002 = b + d + tmp_1003 = f.e + tmp_1004 = c + d + a[tmp_1002] = tmp_1003(tmp_1004) # Or should be a[tmp1] = tmp2? + + self.assert_body_anfs_as_expected(expected_result, test_function) + + +class AnfNonTransformationTest(AnfTransformerTest): + """Test that specifying "no transformation" does nothing. + + Reuses all the examples of AnfTransformerTest by overriding + `assert_body_anfs_as_expected_`. + """ + + def assert_body_anfs_as_expected(self, expected_fn, test_fn): + # Testing the code bodies only. Wrapping them in functions so the + # syntax highlights nicely, but Python doesn't try to execute the + # statements. + node, _ = parser.parse_entity(test_fn, future_features=()) + orig_source = parser.unparse(node, indentation=' ') + orig_str = textwrap.dedent(orig_source).strip() + config = [(anf.ANY, anf.LEAVE)] # Configuration to transform nothing + node = anf.transform(node, self._simple_context(), config=config) + new_source = parser.unparse(node, indentation=' ') + new_str = textwrap.dedent(new_source).strip() + self.assertEqual(orig_str, new_str) + + +class AnfConfiguredTest(AnfTestBase): + + def test_constants_in_function_calls(self): + # An example specific configuration that differs from the default: Moving + # literals out of being directly passed to functions, but nothing else. + try: + # TODO(b/140808434): Fix this. + # gast pre-0.3 + literals = (gast.Num, gast.Str, gast.Bytes, gast.NameConstant, gast.Name) + except AttributeError: + # gast 0.3+ + literals = (gast.Constant, gast.Name) + config = [(anf.ASTEdgePattern(gast.Call, anf.ANY, literals), anf.REPLACE)] + + def test_function(x, frob): + return frob(x, x+1, 2) + + def expected_result(x, frob): + tmp_1001 = 2 + return frob(x, x+1, tmp_1001) + + self.assert_body_anfs_as_expected(expected_result, test_function, config) + + def test_anf_some_function_calls(self): + # Another example specific configuration that differs from the default: + # Moving all arguments out of some function calls but leaving others be. + allowlist = ['foo'] + + def transform(parent, field, child): + del field + del child + func_name = parent.func.id + return str(func_name) in allowlist + + config = [(anf.ASTEdgePattern(gast.Call, anf.ANY, anf.ANY), transform)] + + def test_function(x, foo, bar): + y = foo(x, x+1, 2) + return bar(y, y+1, 2) + + def expected_result(x, foo, bar): + tmp_1001 = x+1 + tmp_1002 = 2 + y = foo(x, tmp_1001, tmp_1002) + return bar(y, y+1, 2) + + self.assert_body_anfs_as_expected(expected_result, test_function, config) + + def test_touching_name_constant(self): + # Checking that the nodes for `True`, `False`, and `None` can be manipulated + # by a configuration. This is non-trivial, because in Python 2 those are + # represented as `Name`, which is the same node type as variable references. + specials = (gast.Name, gast.Constant) + config = [(anf.ASTEdgePattern(gast.Call, anf.ANY, specials), anf.REPLACE)] + + def test_function(f): + return f(True, False, None) + + def expected_result(f): + tmp_1001 = True + tmp_1002 = False + tmp_1003 = None + return f(tmp_1001, tmp_1002, tmp_1003) + + self.assert_body_anfs_as_expected(expected_result, test_function, config) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/error_utils.py b/src/braket/experimental/autoqasm/autograph/pyct/error_utils.py new file mode 100644 index 00000000..31c0a2cc --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/error_utils.py @@ -0,0 +1,230 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Code transformation exceptions.""" + +import collections + +from braket.experimental.autoqasm.autograph.pyct import origin_info +from braket.experimental.autoqasm.autograph.tf_utils import traceback_utils + + +class FrameInfo( + collections.namedtuple('FrameInfo', + ('filename', 'lineno', 'function_name', 'code', + 'is_converted', 'is_allowlisted'))): + + __slots__ = () + + +def _stack_trace_inside_mapped_code(tb, source_map, converter_filename): + """Summarizes inner traceback frames up to the call to a given function. + + This functions locates the innermost (i.e. most recent) frame that corresponds + to code that can be mapped by source_map originated from, and returns a + translated stack trace ending at that frame. If no such frame is found, the + entire stack trace is summarized. + + For example, the following code: + + def f(): + for i in tf.range(1): + z = y + i # z only defined here + + Would generate this traceback: + + + ag__.for_stmt(...) + + return _known_len_tf_for_stmt(iter_, extra_test, body, init_state) + <_known_len_tf_for_stmt> + _disallow_undefs_into_loop(*init_state) + <_disallow_undefs_into_loop> + raise ... + + Which is then processed into: + + + for i in tf.range(1): + + return _known_len_tf_for_stmt(iter_, extra_test, body, init_state) + <_known_len_tf_for_stmt> + _disallow_undefs_into_loop(*init_state) + <_disallow_undefs_into_loop> + raise ... + + Args: + tb: traceback.FrameSummary, The traceback corresponding to an error. + Typically, the output of traceback.Summary.extract(capture_locals=True). + source_map: Dict[LineLocation, OriginInfo], a source map as created by + origin_info.create_source_map. + converter_filename: str, the file path of the converted module. Call frames + corresponding to this module are elided and their preceding frames are + marked as allowlisted. Note that frames enclosing converted code are + dropped using a different mechanism. + + Returns: + List[FrameInfo] + """ + result_frames = [] + for filename, line_number, function_name, text in reversed(tb): + + loc = origin_info.LineLocation(filename=filename, lineno=line_number) + if loc in source_map: + origin = source_map[loc] + fi = FrameInfo( + filename=origin.loc.filename, + lineno=origin.loc.lineno, + function_name=origin.function_name, + code=origin.source_code_line, + is_converted=True, + is_allowlisted=False) + result_frames.append(fi) + break + + if filename == converter_filename: + if result_frames: + prev = result_frames[-1] + assert not prev.is_converted # See the if above. + fi = FrameInfo( + filename=prev.filename, + lineno=prev.lineno, + function_name=prev.function_name, + code=prev.code, + is_converted=False, + is_allowlisted=True) + result_frames[-1] = fi + continue + + fi = FrameInfo( + filename=filename, + lineno=line_number, + function_name=function_name, + code=text, + is_converted=False, + is_allowlisted=False) + result_frames.append(fi) + + return tuple(result_frames) + + +KNOWN_STRING_CONSTRUCTOR_ERRORS = ( + AssertionError, + AttributeError, + NameError, + NotImplementedError, + RuntimeError, + StopIteration, + TypeError, + UnboundLocalError, + ValueError, +) + + +# KeyError escapes newlines in strings. We create a special subclass +# that doesn't do that. Overriding the name for display purposes; hopefully +# that won't create too many surprises. +class MultilineMessageKeyError(KeyError): + + def __init__(self, message, original_key): + super(MultilineMessageKeyError, self).__init__(original_key) + self.__message = message + + def __str__(self): + return self.__message + +MultilineMessageKeyError.__name__ = KeyError.__name__ + + +class ErrorMetadataBase(object): + """Container objects attached to exceptions raised in user code. + + This metadata allows re-raising exceptions that occur in generated code, with + a custom error message that includes a stack trace relative to user-readable + code from which the generated code originated. + """ + + __slots__ = ('translated_stack', 'cause_message') + + def __init__(self, callsite_tb, cause_metadata, cause_message, source_map, + converter_filename): + translated_stack = _stack_trace_inside_mapped_code( + callsite_tb, source_map, converter_filename) + + if cause_metadata is None: + self.translated_stack = translated_stack + self.cause_message = cause_message + else: + # Daisy chain the translated stacks. + self.translated_stack = ( + cause_metadata.translated_stack + (translated_stack[-1],)) + self.cause_message = cause_metadata.cause_message + + def get_message(self): + """Returns the message for the underlying exception.""" + lines = [] + + lines.append('in user code:') + lines.append('') + + for frame_info in reversed(self.translated_stack): + if (traceback_utils.is_traceback_filtering_enabled() and + not traceback_utils.include_frame(frame_info.filename)): + continue + + # Same format with Python traceback. + formatted_line = (f' File "{frame_info.filename}", line ' + f'{frame_info.lineno}, in {frame_info.function_name}') + if frame_info.is_converted: + formatted_line += ' *' + elif frame_info.is_allowlisted: + formatted_line += ' **' + lines.append(formatted_line) + + if frame_info.code is None: + code_snippet = '' + else: + code_snippet = frame_info.code.strip() + lines.append(' {}'.format(code_snippet)) + + lines.append('') + + message_lines = self.cause_message.split('\n') + for i in range(len(message_lines)): + message_lines[i] = ' ' + message_lines[i] + lines.extend(message_lines) + + lines.append('') + + return '\n'.join(lines) + + def create_exception(self, source_error): + """Creates exception from source_error.""" + preferred_type = type(source_error) + to_ret = None + if preferred_type.__init__ is Exception.__init__: + to_ret = preferred_type(self.get_message()) + if preferred_type in KNOWN_STRING_CONSTRUCTOR_ERRORS: + to_ret = preferred_type(self.get_message()) + elif preferred_type is KeyError: + to_ret = MultilineMessageKeyError(self.get_message(), self.cause_message) + + if to_ret is not None: + return to_ret.with_traceback(source_error.__traceback__) + + def to_exception(self, source_error): + exc = self.create_exception(source_error) + exc.__suppress_context__ = True + exc.ag_error_metadata = self + return exc diff --git a/src/braket/experimental/autoqasm/autograph/pyct/error_utils_test.py b/src/braket/experimental/autoqasm/autograph/pyct/error_utils_test.py new file mode 100644 index 00000000..c049ca70 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/error_utils_test.py @@ -0,0 +1,126 @@ +# Copyright 2019 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for error_utils module.""" + +import re + +from braket.experimental.autoqasm.autograph.pyct import error_utils +from braket.experimental.autoqasm.autograph.pyct import origin_info +from tensorflow.python.platform import test + + +class ErrorMetadataBaseTest(test.TestCase): + + def test_create_exception_default_constructor(self): + + class CustomError(Exception): + pass + + em = error_utils.ErrorMetadataBase( + callsite_tb=(), + cause_metadata=None, + cause_message='test message', + source_map={}, + converter_filename=None) + exc = em.create_exception(CustomError()) + self.assertIsInstance(exc, CustomError) + self.assertIn('test message', str(exc)) + + def test_create_exception_custom_constructor(self): + + class CustomError(Exception): + + def __init__(self): + super(CustomError, self).__init__('test_message') + + em = error_utils.ErrorMetadataBase( + callsite_tb=(), + cause_metadata=None, + cause_message='test message', + source_map={}, + converter_filename=None) + exc = em.create_exception(CustomError()) + self.assertIsNone(exc) + + def test_get_message_no_code(self): + callsite_tb = [ + ('/path/one.py', 11, 'test_fn_1', None), + ('/path/two.py', 171, 'test_fn_2', 'test code'), + ] + cause_message = 'Test message' + em = error_utils.ErrorMetadataBase( + callsite_tb=callsite_tb, + cause_metadata=None, + cause_message=cause_message, + source_map={}, + converter_filename=None) + self.assertRegex( + em.get_message(), + re.compile(('"/path/one.py", line 11, in test_fn_1.*' + '"/path/two.py", line 171, in test_fn_2.*' + 'Test message'), re.DOTALL)) + + def test_get_message_converted_code(self): + callsite_tb = [ + ('/path/one.py', 11, 'test_fn_1', 'test code 1'), + ('/path/two.py', 171, 'test_fn_2', 'test code 2'), + ('/path/three.py', 171, 'test_fn_3', 'test code 3'), + ] + cause_message = 'Test message' + em = error_utils.ErrorMetadataBase( + callsite_tb=callsite_tb, + cause_metadata=None, + cause_message=cause_message, + source_map={ + origin_info.LineLocation(filename='/path/two.py', lineno=171): + origin_info.OriginInfo( + loc=origin_info.LineLocation( + filename='/path/other_two.py', lineno=13), + function_name='converted_fn', + source_code_line='converted test code', + comment=None) + }, + converter_filename=None) + result = em.get_message() + self.assertRegex( + result, + re.compile((r'converted_fn \*.*' + r'"/path/three.py", line 171, in test_fn_3.*' + r'Test message'), re.DOTALL)) + self.assertNotRegex(result, re.compile('test_fn_1')) + + def test_get_message_call_overload(self): + + callsite_tb = [ + ('/path/one.py', 11, 'test_fn_1', 'test code 1'), + ('/path/two.py', 0, 'test_fn_2', 'test code 2'), + ('/path/three.py', 171, 'test_fn_3', 'test code 3'), + ] + cause_message = 'Test message' + em = error_utils.ErrorMetadataBase( + callsite_tb=callsite_tb, + cause_metadata=None, + cause_message=cause_message, + source_map={}, + converter_filename='/path/two.py') + self.assertRegex( + em.get_message(), + re.compile((r'"/path/one.py", line 11, in test_fn_1.*' + r'"/path/three.py", line 171, in test_fn_3 \*\*.*' + r'Test message'), re.DOTALL)) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/errors.py b/src/braket/experimental/autoqasm/autograph/pyct/errors.py new file mode 100644 index 00000000..78148013 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/errors.py @@ -0,0 +1,27 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Code transformation exceptions.""" + + +class PyCTError(Exception): + """Base class for all exceptions.""" + + +class UnsupportedLanguageElementError(PyCTError, NotImplementedError): + """Raised for code patterns that AutoGraph does not support.""" + + +class InaccessibleSourceCodeError(PyCTError, ValueError): + """Raised when inspect can not access source code.""" diff --git a/src/braket/experimental/autoqasm/autograph/pyct/gast_util.py b/src/braket/experimental/autoqasm/autograph/pyct/gast_util.py new file mode 100644 index 00000000..bdbe5007 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/gast_util.py @@ -0,0 +1,74 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Gast compatibility library. Supports 0.2.2 and 0.3.2.""" +# TODO(mdan): Remove this file once it's safe to break compatibility. + +import functools + +import gast + + +GAST2 = hasattr(gast, 'Str') +GAST3 = not GAST2 + + +def _is_constant_gast_2(node): + return isinstance(node, (gast.Num, gast.Str, gast.Bytes, gast.Ellipsis, + gast.NameConstant)) + + +def _is_constant_gast_3(node): + return isinstance(node, gast.Constant) + + +def is_literal(node): + """Tests whether node represents a Python literal.""" + # Normal literals, True/False/None/Etc. in Python3 + if is_constant(node): + return True + + # True/False/None/Etc. in Python2 + if isinstance(node, gast.Name) and node.id in ['True', 'False', 'None']: + return True + + return False + + +def _is_ellipsis_gast_2(node): + return isinstance(node, gast.Ellipsis) + + +def _is_ellipsis_gast_3(node): + return isinstance(node, gast.Constant) and node.value == Ellipsis + + +if GAST2: + is_constant = _is_constant_gast_2 + is_ellipsis = _is_ellipsis_gast_2 + + Module = gast.Module + Name = gast.Name + Str = gast.Str + +elif GAST3: + is_constant = _is_constant_gast_3 + is_ellipsis = _is_ellipsis_gast_3 + + Module = functools.partial(gast.Module, type_ignores=None) # pylint:disable=invalid-name + Name = functools.partial(gast.Name, type_comment=None) # pylint:disable=invalid-name + Str = functools.partial(gast.Constant, kind=None) # pylint:disable=invalid-name + +else: + assert False diff --git a/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils.py b/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils.py new file mode 100644 index 00000000..be7996dd --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils.py @@ -0,0 +1,321 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Live entity inspection utilities. + +This module contains whatever inspect doesn't offer out of the box. +""" + +import builtins +import inspect +import itertools +import linecache +import sys +import threading +import types + +import inspect as tf_inspect + +# This lock seems to help avoid linecache concurrency errors. +_linecache_lock = threading.Lock() + +# Cache all the builtin elements in a frozen set for faster lookup. +_BUILTIN_FUNCTION_IDS = frozenset(id(v) for v in builtins.__dict__.values()) + + +def islambda(f): + if not tf_inspect.isfunction(f): + return False + # TODO(mdan): Look into checking the only the code object. + if not (hasattr(f, '__name__') and hasattr(f, '__code__')): + return False + # Some wrappers can rename the function, but changing the name of the + # code object is harder. + return ((f.__name__ == '') or (f.__code__.co_name == '')) + + +def isnamedtuple(f): + """Returns True if the argument is a namedtuple-like.""" + if not (tf_inspect.isclass(f) and issubclass(f, tuple)): + return False + if not hasattr(f, '_fields'): + return False + fields = getattr(f, '_fields') + if not isinstance(fields, tuple): + return False + if not all(isinstance(f, str) for f in fields): + return False + return True + + +def isbuiltin(f): + """Returns True if the argument is a built-in function.""" + if id(f) in _BUILTIN_FUNCTION_IDS: + return True + elif isinstance(f, types.BuiltinFunctionType): + return True + elif inspect.isbuiltin(f): + return True + elif f is eval: + return True + else: + return False + + +def isconstructor(cls): + """Returns True if the argument is an object constructor. + + In general, any object of type class is a constructor, with the exception + of classes created using a callable metaclass. + See below for why a callable metaclass is not a trivial combination: + https://docs.python.org/2.7/reference/datamodel.html#customizing-class-creation + + Args: + cls: Any + + Returns: + Bool + """ + return (inspect.isclass(cls) and + not (issubclass(cls.__class__, type) and + hasattr(cls.__class__, '__call__') and + cls.__class__.__call__ is not type.__call__)) + + +def _fix_linecache_record(obj): + """Fixes potential corruption of linecache in the presence of functools.wraps. + + functools.wraps modifies the target object's __module__ field, which seems + to confuse linecache in special instances, for example when the source is + loaded from a .par file (see https://google.github.io/subpar/subpar.html). + + This function simply triggers a call to linecache.updatecache when a mismatch + was detected between the object's __module__ property and the object's source + file. + + Args: + obj: Any + """ + if hasattr(obj, '__module__'): + obj_file = inspect.getfile(obj) + obj_module = obj.__module__ + + # A snapshot of the loaded modules helps avoid "dict changed size during + # iteration" errors. + loaded_modules = tuple(sys.modules.values()) + for m in loaded_modules: + if hasattr(m, '__file__') and m.__file__ == obj_file: + if obj_module is not m: + linecache.updatecache(obj_file, m.__dict__) + + +def getimmediatesource(obj): + """A variant of inspect.getsource that ignores the __wrapped__ property.""" + with _linecache_lock: + _fix_linecache_record(obj) + lines, lnum = inspect.findsource(obj) + return ''.join(inspect.getblock(lines[lnum:])) + + +def getnamespace(f): + """Returns the complete namespace of a function. + + Namespace is defined here as the mapping of all non-local variables to values. + This includes the globals and the closure variables. Note that this captures + the entire globals collection of the function, and may contain extra symbols + that it does not actually use. + + Args: + f: User defined function. + + Returns: + A dict mapping symbol names to values. + """ + namespace = dict(f.__globals__) + closure = f.__closure__ + freevars = f.__code__.co_freevars + if freevars and closure: + for name, cell in zip(freevars, closure): + try: + namespace[name] = cell.cell_contents + except ValueError: + # Cell contains undefined variable, omit it from the namespace. + pass + return namespace + + +def getqualifiedname(namespace, object_, max_depth=5, visited=None): + """Returns the name by which a value can be referred to in a given namespace. + + If the object defines a parent module, the function attempts to use it to + locate the object. + + This function will recurse inside modules, but it will not search objects for + attributes. The recursion depth is controlled by max_depth. + + Args: + namespace: Dict[str, Any], the namespace to search into. + object_: Any, the value to search. + max_depth: Optional[int], a limit to the recursion depth when searching + inside modules. + visited: Optional[Set[int]], ID of modules to avoid visiting. + Returns: Union[str, None], the fully-qualified name that resolves to the value + o, or None if it couldn't be found. + """ + if visited is None: + visited = set() + + # Copy the dict to avoid "changed size error" during concurrent invocations. + # TODO(mdan): This is on the hot path. Can we avoid the copy? + namespace = dict(namespace) + + for name in namespace: + # The value may be referenced by more than one symbol, case in which + # any symbol will be fine. If the program contains symbol aliases that + # change over time, this may capture a symbol that will later point to + # something else. + # TODO(mdan): Prefer the symbol that matches the value type name. + if object_ is namespace[name]: + return name + + # If an object is not found, try to search its parent modules. + parent = tf_inspect.getmodule(object_) + if (parent is not None and parent is not object_ and parent is not namespace): + # No limit to recursion depth because of the guard above. + parent_name = getqualifiedname( + namespace, parent, max_depth=0, visited=visited) + if parent_name is not None: + name_in_parent = getqualifiedname( + parent.__dict__, object_, max_depth=0, visited=visited) + assert name_in_parent is not None, ( + 'An object should always be found in its owner module') + return '{}.{}'.format(parent_name, name_in_parent) + + if max_depth: + # Iterating over a copy prevents "changed size due to iteration" errors. + # It's unclear why those occur - suspecting new modules may load during + # iteration. + for name in namespace.keys(): + value = namespace[name] + if tf_inspect.ismodule(value) and id(value) not in visited: + visited.add(id(value)) + name_in_module = getqualifiedname(value.__dict__, object_, + max_depth - 1, visited) + if name_in_module is not None: + return '{}.{}'.format(name, name_in_module) + return None + + +def getdefiningclass(m, owner_class): + """Resolves the class (e.g. one of the superclasses) that defined a method.""" + method_name = m.__name__ + for super_class in inspect.getmro(owner_class): + if ((hasattr(super_class, '__dict__') and + method_name in super_class.__dict__) or + (hasattr(super_class, '__slots__') and + method_name in super_class.__slots__)): + return super_class + return owner_class + + +def getmethodclass(m): + """Resolves a function's owner, e.g. + + a method's class. + + Note that this returns the object that the function was retrieved from, not + necessarily the class where it was defined. + + This function relies on Python stack frame support in the interpreter, and + has the same limitations that inspect.currentframe. + + Limitations. This function will only work correctly if the owned class is + visible in the caller's global or local variables. + + Args: + m: A user defined function + + Returns: + The class that this function was retrieved from, or None if the function + is not an object or class method, or the class that owns the object or + method is not visible to m. + + Raises: + ValueError: if the class could not be resolved for any unexpected reason. + """ + + # Callable objects: return their own class. + if (not hasattr(m, '__name__') and hasattr(m, '__class__') and + hasattr(m, '__call__')): + if isinstance(m.__class__, type): + return m.__class__ + + # Instance and class: return the class of "self". + m_self = getattr(m, '__self__', None) + if m_self is not None: + if inspect.isclass(m_self): + return m_self + return m_self.__class__ + + # Class, static and unbound methods: search all defined classes in any + # namespace. This is inefficient but more robust a method. + owners = [] + caller_frame = tf_inspect.currentframe().f_back + try: + # TODO(mdan): This doesn't consider cell variables. + # TODO(mdan): This won't work if the owner is hidden inside a container. + # Cell variables may be pulled using co_freevars and the closure. + for v in itertools.chain(caller_frame.f_locals.values(), + caller_frame.f_globals.values()): + if hasattr(v, m.__name__): + candidate = getattr(v, m.__name__) + # Py2 methods may be bound or unbound, extract im_func to get the + # underlying function. + if hasattr(candidate, 'im_func'): + candidate = candidate.im_func + if hasattr(m, 'im_func'): + m = m.im_func + if candidate is m: + owners.append(v) + finally: + del caller_frame + + if owners: + if len(owners) == 1: + return owners[0] + + # If multiple owners are found, and are not subclasses, raise an error. + owner_types = tuple(o if tf_inspect.isclass(o) else type(o) for o in owners) + for o in owner_types: + if tf_inspect.isclass(o) and issubclass(o, tuple(owner_types)): + return o + raise ValueError('Found too many owners of %s: %s' % (m, owners)) + + return None + + +def getfutureimports(entity): + """Detects what future imports are necessary to safely execute entity source. + + Args: + entity: Any object + + Returns: + A tuple of future strings + """ + if not (tf_inspect.isfunction(entity) or tf_inspect.ismethod(entity)): + return tuple() + return tuple( + sorted(name for name, value in entity.__globals__.items() + if getattr(value, '__module__', None) == '__future__')) diff --git a/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils_test.py b/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils_test.py new file mode 100644 index 00000000..0b20bbde --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils_test.py @@ -0,0 +1,612 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for inspect_utils module.""" + +import abc +import collections +import functools +import imp +import textwrap + +from tensorflow.python import lib +from braket.experimental.autoqasm.autograph.pyct import inspect_utils +from braket.experimental.autoqasm.autograph.pyct.testing import basic_definitions +from braket.experimental.autoqasm.autograph.pyct.testing import decorators +from tensorflow.python.framework import constant_op +from tensorflow.python.platform import test + + +def decorator(f): + return f + + +def function_decorator(): + def dec(f): + return f + return dec + + +def wrapping_decorator(): + def dec(f): + def replacement(*_): + return None + + @functools.wraps(f) + def wrapper(*args, **kwargs): + return replacement(*args, **kwargs) + return wrapper + return dec + + +class TestClass: + + def member_function(self): + pass + + @decorator + def decorated_member(self): + pass + + @function_decorator() + def fn_decorated_member(self): + pass + + @wrapping_decorator() + def wrap_decorated_member(self): + pass + + @staticmethod + def static_method(): + pass + + @classmethod + def class_method(cls): + pass + + +def free_function(): + pass + + +def factory(): + return free_function + + +def free_factory(): + def local_function(): + pass + return local_function + + +class InspectUtilsTest(test.TestCase): + + def test_islambda(self): + def test_fn(): + pass + + self.assertTrue(inspect_utils.islambda(lambda x: x)) + self.assertFalse(inspect_utils.islambda(test_fn)) + + def test_islambda_renamed_lambda(self): + l = lambda x: 1 + l.__name__ = 'f' + self.assertTrue(inspect_utils.islambda(l)) + + def test_isnamedtuple(self): + nt = collections.namedtuple('TestNamedTuple', ['a', 'b']) + + class NotANamedTuple(tuple): + pass + + self.assertTrue(inspect_utils.isnamedtuple(nt)) + self.assertFalse(inspect_utils.isnamedtuple(NotANamedTuple)) + + def test_isnamedtuple_confounder(self): + """This test highlights false positives when detecting named tuples.""" + + class NamedTupleLike(tuple): + _fields = ('a', 'b') + + self.assertTrue(inspect_utils.isnamedtuple(NamedTupleLike)) + + def test_isnamedtuple_subclass(self): + """This test highlights false positives when detecting named tuples.""" + + class NamedTupleSubclass(collections.namedtuple('Test', ['a', 'b'])): + pass + + self.assertTrue(inspect_utils.isnamedtuple(NamedTupleSubclass)) + + def assertSourceIdentical(self, actual, expected): + self.assertEqual( + textwrap.dedent(actual).strip(), + textwrap.dedent(expected).strip() + ) + + def test_getimmediatesource_basic(self): + + def test_decorator(f): + + def f_wrapper(*args, **kwargs): + return f(*args, **kwargs) + + return f_wrapper + + expected = """ + def f_wrapper(*args, **kwargs): + return f(*args, **kwargs) + """ + + @test_decorator + def test_fn(a): + """Test docstring.""" + return [a] + + self.assertSourceIdentical( + inspect_utils.getimmediatesource(test_fn), expected) + + def test_getimmediatesource_noop_decorator(self): + + def test_decorator(f): + return f + + expected = ''' + @test_decorator + def test_fn(a): + """Test docstring.""" + return [a] + ''' + + @test_decorator + def test_fn(a): + """Test docstring.""" + return [a] + + self.assertSourceIdentical( + inspect_utils.getimmediatesource(test_fn), expected) + + def test_getimmediatesource_functools_wrapper(self): + + def wrapper_decorator(f): + + @functools.wraps(f) + def wrapper(*args, **kwargs): + return f(*args, **kwargs) + + return wrapper + + expected = textwrap.dedent(""" + @functools.wraps(f) + def wrapper(*args, **kwargs): + return f(*args, **kwargs) + """) + + @wrapper_decorator + def test_fn(a): + """Test docstring.""" + return [a] + + self.assertSourceIdentical( + inspect_utils.getimmediatesource(test_fn), expected) + + def test_getimmediatesource_functools_wrapper_different_module(self): + + expected = textwrap.dedent(""" + @functools.wraps(f) + def wrapper(*args, **kwargs): + return f(*args, **kwargs) + """) + + @decorators.wrapping_decorator + def test_fn(a): + """Test docstring.""" + return [a] + + self.assertSourceIdentical( + inspect_utils.getimmediatesource(test_fn), expected) + + def test_getimmediatesource_normal_decorator_different_module(self): + + expected = textwrap.dedent(""" + def standalone_wrapper(*args, **kwargs): + return f(*args, **kwargs) + """) + + @decorators.standalone_decorator + def test_fn(a): + """Test docstring.""" + return [a] + + self.assertSourceIdentical( + inspect_utils.getimmediatesource(test_fn), expected) + + def test_getimmediatesource_normal_functional_decorator_different_module( + self): + + expected = textwrap.dedent(""" + def functional_wrapper(*args, **kwargs): + return f(*args, **kwargs) + """) + + @decorators.functional_decorator() + def test_fn(a): + """Test docstring.""" + return [a] + + self.assertSourceIdentical( + inspect_utils.getimmediatesource(test_fn), expected) + + def test_getnamespace_globals(self): + ns = inspect_utils.getnamespace(factory) + self.assertEqual(ns['free_function'], free_function) + + def test_getnamespace_closure_with_undefined_var(self): + if False: # pylint:disable=using-constant-test + a = 1 + + def test_fn(): + return a + + ns = inspect_utils.getnamespace(test_fn) + self.assertNotIn('a', ns) + + a = 2 + ns = inspect_utils.getnamespace(test_fn) + + self.assertEqual(ns['a'], 2) + + def test_getnamespace_hermetic(self): + + # Intentionally hiding the global function to make sure we don't overwrite + # it in the global namespace. + free_function = object() # pylint:disable=redefined-outer-name + + def test_fn(): + return free_function + + ns = inspect_utils.getnamespace(test_fn) + globs = test_fn.__globals__ + self.assertTrue(ns['free_function'] is free_function) + self.assertFalse(globs['free_function'] is free_function) + + def test_getnamespace_locals(self): + + def called_fn(): + return 0 + + closed_over_list = [] + closed_over_primitive = 1 + + def local_fn(): + closed_over_list.append(1) + local_var = 1 + return called_fn() + local_var + closed_over_primitive + + ns = inspect_utils.getnamespace(local_fn) + self.assertEqual(ns['called_fn'], called_fn) + self.assertEqual(ns['closed_over_list'], closed_over_list) + self.assertEqual(ns['closed_over_primitive'], closed_over_primitive) + self.assertTrue('local_var' not in ns) + + def test_getqualifiedname(self): + foo = object() + qux = imp.new_module('quxmodule') + bar = imp.new_module('barmodule') + baz = object() + bar.baz = baz + + ns = { + 'foo': foo, + 'bar': bar, + 'qux': qux, + } + + self.assertIsNone(inspect_utils.getqualifiedname(ns, inspect_utils)) + self.assertEqual(inspect_utils.getqualifiedname(ns, foo), 'foo') + self.assertEqual(inspect_utils.getqualifiedname(ns, bar), 'bar') + self.assertEqual(inspect_utils.getqualifiedname(ns, baz), 'bar.baz') + + def test_getqualifiedname_efficiency(self): + foo = object() + + # We create a densely connected graph consisting of a relatively small + # number of modules and hide our symbol in one of them. The path to the + # symbol is at least 10, and each node has about 10 neighbors. However, + # by skipping visited modules, the search should take much less. + ns = {} + prev_level = [] + for i in range(10): + current_level = [] + for j in range(10): + mod_name = 'mod_{}_{}'.format(i, j) + mod = imp.new_module(mod_name) + current_level.append(mod) + if i == 9 and j == 9: + mod.foo = foo + if prev_level: + # All modules at level i refer to all modules at level i+1 + for prev in prev_level: + for mod in current_level: + prev.__dict__[mod.__name__] = mod + else: + for mod in current_level: + ns[mod.__name__] = mod + prev_level = current_level + + self.assertIsNone(inspect_utils.getqualifiedname(ns, inspect_utils)) + self.assertIsNotNone( + inspect_utils.getqualifiedname(ns, foo, max_depth=10000000000)) + + def test_getqualifiedname_cycles(self): + foo = object() + + # We create a graph of modules that contains circular references. The + # search process should avoid them. The searched object is hidden at the + # bottom of a path of length roughly 10. + ns = {} + mods = [] + for i in range(10): + mod = imp.new_module('mod_{}'.format(i)) + if i == 9: + mod.foo = foo + # Module i refers to module i+1 + if mods: + mods[-1].__dict__[mod.__name__] = mod + else: + ns[mod.__name__] = mod + # Module i refers to all modules j < i. + for prev in mods: + mod.__dict__[prev.__name__] = prev + mods.append(mod) + + self.assertIsNone(inspect_utils.getqualifiedname(ns, inspect_utils)) + self.assertIsNotNone( + inspect_utils.getqualifiedname(ns, foo, max_depth=10000000000)) + + def test_getqualifiedname_finds_via_parent_module(self): + # TODO(mdan): This test is vulnerable to change in the lib module. + # A better way to forge modules should be found. + self.assertEqual( + inspect_utils.getqualifiedname( + lib.__dict__, lib.io.file_io.FileIO, max_depth=1), + 'io.file_io.FileIO') + + def test_getmethodclass(self): + + self.assertEqual( + inspect_utils.getmethodclass(free_function), None) + self.assertEqual( + inspect_utils.getmethodclass(free_factory()), None) + + self.assertEqual( + inspect_utils.getmethodclass(TestClass.member_function), + TestClass) + self.assertEqual( + inspect_utils.getmethodclass(TestClass.decorated_member), + TestClass) + self.assertEqual( + inspect_utils.getmethodclass(TestClass.fn_decorated_member), + TestClass) + self.assertEqual( + inspect_utils.getmethodclass(TestClass.wrap_decorated_member), + TestClass) + self.assertEqual( + inspect_utils.getmethodclass(TestClass.static_method), + TestClass) + self.assertEqual( + inspect_utils.getmethodclass(TestClass.class_method), + TestClass) + + test_obj = TestClass() + self.assertEqual( + inspect_utils.getmethodclass(test_obj.member_function), + TestClass) + self.assertEqual( + inspect_utils.getmethodclass(test_obj.decorated_member), + TestClass) + self.assertEqual( + inspect_utils.getmethodclass(test_obj.fn_decorated_member), + TestClass) + self.assertEqual( + inspect_utils.getmethodclass(test_obj.wrap_decorated_member), + TestClass) + self.assertEqual( + inspect_utils.getmethodclass(test_obj.static_method), + TestClass) + self.assertEqual( + inspect_utils.getmethodclass(test_obj.class_method), + TestClass) + + def test_getmethodclass_locals(self): + + def local_function(): + pass + + class LocalClass: + + def member_function(self): + pass + + @decorator + def decorated_member(self): + pass + + @function_decorator() + def fn_decorated_member(self): + pass + + @wrapping_decorator() + def wrap_decorated_member(self): + pass + + self.assertEqual( + inspect_utils.getmethodclass(local_function), None) + + self.assertEqual( + inspect_utils.getmethodclass(LocalClass.member_function), + LocalClass) + self.assertEqual( + inspect_utils.getmethodclass(LocalClass.decorated_member), + LocalClass) + self.assertEqual( + inspect_utils.getmethodclass(LocalClass.fn_decorated_member), + LocalClass) + self.assertEqual( + inspect_utils.getmethodclass(LocalClass.wrap_decorated_member), + LocalClass) + + test_obj = LocalClass() + self.assertEqual( + inspect_utils.getmethodclass(test_obj.member_function), + LocalClass) + self.assertEqual( + inspect_utils.getmethodclass(test_obj.decorated_member), + LocalClass) + self.assertEqual( + inspect_utils.getmethodclass(test_obj.fn_decorated_member), + LocalClass) + self.assertEqual( + inspect_utils.getmethodclass(test_obj.wrap_decorated_member), + LocalClass) + + def test_getmethodclass_callables(self): + + class TestCallable: + + def __call__(self): + pass + + c = TestCallable() + self.assertEqual(inspect_utils.getmethodclass(c), TestCallable) + + def test_getmethodclass_no_bool_conversion(self): + + tensor = constant_op.constant([1]) + self.assertEqual( + inspect_utils.getmethodclass(tensor.get_shape), type(tensor)) + + def test_getdefiningclass(self): + + class Superclass: + + def foo(self): + pass + + def bar(self): + pass + + @classmethod + def class_method(cls): + pass + + class Subclass(Superclass): + + def foo(self): + pass + + def baz(self): + pass + + self.assertIs( + inspect_utils.getdefiningclass(Subclass.foo, Subclass), Subclass) + self.assertIs( + inspect_utils.getdefiningclass(Subclass.bar, Subclass), Superclass) + self.assertIs( + inspect_utils.getdefiningclass(Subclass.baz, Subclass), Subclass) + self.assertIs( + inspect_utils.getdefiningclass(Subclass.class_method, Subclass), + Superclass) + + def test_isbuiltin(self): + self.assertTrue(inspect_utils.isbuiltin(enumerate)) + self.assertTrue(inspect_utils.isbuiltin(eval)) + self.assertTrue(inspect_utils.isbuiltin(float)) + self.assertTrue(inspect_utils.isbuiltin(int)) + self.assertTrue(inspect_utils.isbuiltin(len)) + self.assertTrue(inspect_utils.isbuiltin(range)) + self.assertTrue(inspect_utils.isbuiltin(zip)) + self.assertFalse(inspect_utils.isbuiltin(function_decorator)) + + def test_isconstructor(self): + + class OrdinaryClass: + pass + + class OrdinaryCallableClass: + + def __call__(self): + pass + + class Metaclass(type): + pass + + class CallableMetaclass(type): + + def __call__(cls): + pass + + self.assertTrue(inspect_utils.isconstructor(OrdinaryClass)) + self.assertTrue(inspect_utils.isconstructor(OrdinaryCallableClass)) + self.assertTrue(inspect_utils.isconstructor(Metaclass)) + self.assertTrue(inspect_utils.isconstructor(Metaclass('TestClass', (), {}))) + self.assertTrue(inspect_utils.isconstructor(CallableMetaclass)) + + self.assertFalse(inspect_utils.isconstructor( + CallableMetaclass('TestClass', (), {}))) + + def test_isconstructor_abc_callable(self): + + class AbcBase(metaclass=abc.ABCMeta): + + @abc.abstractmethod + def __call__(self): + pass + + class AbcSubclass(AbcBase): + + def __init__(self): + pass + + def __call__(self): + pass + + self.assertTrue(inspect_utils.isconstructor(AbcBase)) + self.assertTrue(inspect_utils.isconstructor(AbcSubclass)) + + def test_getfutureimports_functions(self): + imps = inspect_utils.getfutureimports(basic_definitions.function_with_print) + self.assertNotIn('absolute_import', imps) + self.assertNotIn('division', imps) + self.assertNotIn('print_function', imps) + self.assertNotIn('generators', imps) + + def test_getfutureimports_lambdas(self): + imps = inspect_utils.getfutureimports(basic_definitions.simple_lambda) + self.assertNotIn('absolute_import', imps) + self.assertNotIn('division', imps) + self.assertNotIn('print_function', imps) + self.assertNotIn('generators', imps) + + def test_getfutureimports_methods(self): + imps = inspect_utils.getfutureimports( + basic_definitions.SimpleClass.method_with_print) + self.assertNotIn('absolute_import', imps) + self.assertNotIn('division', imps) + self.assertNotIn('print_function', imps) + self.assertNotIn('generators', imps) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils_test.sh b/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils_test.sh new file mode 100644 index 00000000..02dfae2a --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils_test.sh @@ -0,0 +1,19 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +# Test that runs inspect_utils_test as a .par file. + + +SCRIPT_DIR="$(dirname ${BASH_SOURCE[0]})" +${SCRIPT_DIR}/inspect_utils_test.par diff --git a/src/braket/experimental/autoqasm/autograph/pyct/loader.py b/src/braket/experimental/autoqasm/autograph/pyct/loader.py new file mode 100644 index 00000000..f092a463 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/loader.py @@ -0,0 +1,102 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Converting AST to code and Python entities. + +Adapted from Tangent. +""" + +import atexit +import errno +import importlib +import os +import sys +import tempfile + +from braket.experimental.autoqasm.autograph.pyct import origin_info +from braket.experimental.autoqasm.autograph.pyct import parser + + +def _remove_file(file_name): + """Remove a file, if it exists.""" + try: + os.remove(file_name) + except OSError as e: + if e.errno == errno.ENOENT: + # The file disappeared. Ignore this. Temporary files might get + # cleaned up, especially if they reside in /tmp. + pass + else: + raise + + +def load_source(source, delete_on_exit): + """Loads the given source code as a Python module.""" + with tempfile.NamedTemporaryFile( + mode='w', + suffix='.py', + prefix='__autograph_generated_file', + delete=False, + encoding='utf-8') as f: + module_name = os.path.basename(f.name[:-3]) + file_name = f.name + f.write(source) + + if delete_on_exit: + atexit.register(lambda: _remove_file(file_name)) + + spec = importlib.util.spec_from_file_location(module_name, file_name) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + # TODO(mdan): Use our own garbage-collected cache instead of sys.modules. + sys.modules[module_name] = module + return module, file_name + + +def load_ast(nodes, + indentation=' ', + include_source_map=False, + delete_on_exit=True): + """Loads the given AST as a Python module. + + Compiling the AST code this way ensures that the source code is readable by + e.g. `pdb` or `inspect`. + + Args: + nodes: Union[ast.AST, Iterable[ast.AST]], the code to compile, as an AST + object. + indentation: Text, the string to use for indentation. + include_source_map: bool, whether return a source map. + delete_on_exit: bool, whether to delete the temporary file used for + compilation on exit. + + Returns: + Tuple[module, Text, Dict[LineLocation, OriginInfo]], containing: + the module containing the unparsed nodes, the source code corresponding to + nodes, and the source map. Is include_source_map is False, the source map + will be None. + """ + if not isinstance(nodes, (list, tuple)): + nodes = (nodes,) + + source = parser.unparse(nodes, indentation=indentation) + module, _ = load_source(source, delete_on_exit) + + if include_source_map: + source_map = origin_info.create_source_map(nodes, source, module.__file__) + else: + source_map = None + + # TODO(mdan): Return a structured object. + return module, source, source_map diff --git a/src/braket/experimental/autoqasm/autograph/pyct/loader_test.py b/src/braket/experimental/autoqasm/autograph/pyct/loader_test.py new file mode 100644 index 00000000..2bc0a971 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/loader_test.py @@ -0,0 +1,123 @@ +# coding=utf-8 +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for loader module.""" + +import os +import textwrap + +import gast + +from braket.experimental.autoqasm.autograph.pyct import ast_util +from braket.experimental.autoqasm.autograph.pyct import loader +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import pretty_printer +from tensorflow.python.platform import test +import inspect as tf_inspect + + +class LoaderTest(test.TestCase): + + def assertAstMatches(self, actual_node, expected_node_src): + expected_node = gast.parse(expected_node_src).body[0] + + msg = 'AST did not match expected:\n{}\nActual:\n{}'.format( + pretty_printer.fmt(expected_node), + pretty_printer.fmt(actual_node)) + self.assertTrue(ast_util.matches(actual_node, expected_node), msg) + + def test_parse_load_identity(self): + + def test_fn(x): + a = True + b = '' + if a: + b = (x + 1) + return b + + node, _ = parser.parse_entity(test_fn, future_features=()) + module, _, _ = loader.load_ast(node) + source = tf_inspect.getsource(module.test_fn) + expected_node_src = textwrap.dedent(tf_inspect.getsource(test_fn)) + + self.assertAstMatches(node, source) + self.assertAstMatches(node, expected_node_src) + + def test_load_ast(self): + node = gast.FunctionDef( + name='f', + args=gast.arguments( + args=[ + gast.Name( + 'a', ctx=gast.Param(), annotation=None, type_comment=None) + ], + posonlyargs=[], + vararg=None, + kwonlyargs=[], + kw_defaults=[], + kwarg=None, + defaults=[]), + body=[ + gast.Return( + gast.BinOp( + op=gast.Add(), + left=gast.Name( + 'a', + ctx=gast.Load(), + annotation=None, + type_comment=None), + right=gast.Constant(1, kind=None))) + ], + decorator_list=[], + returns=None, + type_comment=None) + + module, source, _ = loader.load_ast(node) + + expected_node_src = """ + # coding=utf-8 + def f(a): + return (a + 1) + """ + expected_node_src = textwrap.dedent(expected_node_src) + + self.assertAstMatches(node, source) + self.assertAstMatches(node, expected_node_src) + + self.assertEqual(2, module.f(1)) + with open(module.__file__, 'r') as temp_output: + self.assertAstMatches(node, temp_output.read()) + + def test_load_source(self): + test_source = textwrap.dedent(u""" + # coding=utf-8 + def f(a): + '日本語 Δθₜ ← Δθₜ₋₁ + ∇Q(sₜ, aₜ)(rₜ + γₜ₊₁ max Q(⋅))' + return a + 1 + """) + module, _ = loader.load_source(test_source, delete_on_exit=True) + self.assertEqual(module.f(1), 2) + self.assertEqual( + module.f.__doc__, '日本語 Δθₜ ← Δθₜ₋₁ + ∇Q(sₜ, aₜ)(rₜ + γₜ₊₁ max Q(⋅))') + + def test_cleanup(self): + test_source = textwrap.dedent('') + _, filename = loader.load_source(test_source, delete_on_exit=True) + # Clean up the file before loader.py tries to remove it, to check that the + # latter can deal with that situation. + os.unlink(filename) + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/naming.py b/src/braket/experimental/autoqasm/autograph/pyct/naming.py new file mode 100644 index 00000000..17a62671 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/naming.py @@ -0,0 +1,53 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Symbol naming utilities.""" + +from braket.experimental.autoqasm.autograph.pyct import qual_names + + +class Namer(object): + """Symbol name generator.""" + + def __init__(self, global_namespace): + self.global_namespace = global_namespace + self.generated_names = set() + + def new_symbol(self, name_root, reserved_locals): + """See control_flow.SymbolNamer.new_symbol.""" + # reserved_locals may contain QNs. + all_reserved_locals = set() + for s in reserved_locals: + if isinstance(s, qual_names.QN): + all_reserved_locals.update(s.qn) + elif isinstance(s, str): + all_reserved_locals.add(s) + else: + raise ValueError('Unexpected symbol type "%s"' % type(s)) + + pieces = name_root.split('_') + if pieces[-1].isdigit(): + name_root = '_'.join(pieces[:-1]) + n = int(pieces[-1]) + else: + n = 0 + new_name = name_root + + while (new_name in self.global_namespace or + new_name in all_reserved_locals or new_name in self.generated_names): + n += 1 + new_name = '%s_%d' % (name_root, n) + + self.generated_names.add(new_name) + return new_name diff --git a/src/braket/experimental/autoqasm/autograph/pyct/naming_test.py b/src/braket/experimental/autoqasm/autograph/pyct/naming_test.py new file mode 100644 index 00000000..371cc19c --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/naming_test.py @@ -0,0 +1,44 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for naming module.""" + +from braket.experimental.autoqasm.autograph.pyct import naming +from tensorflow.python.platform import test + + +class NamerTest(test.TestCase): + + def test_new_symbol_tracks_names(self): + namer = naming.Namer({}) + self.assertEqual('temp', namer.new_symbol('temp', set())) + self.assertItemsEqual(('temp',), namer.generated_names) + + def test_new_symbol_avoids_duplicates(self): + namer = naming.Namer({}) + self.assertEqual('temp', namer.new_symbol('temp', set())) + self.assertEqual('temp_1', namer.new_symbol('temp', set())) + self.assertItemsEqual(('temp', 'temp_1'), namer.generated_names) + + def test_new_symbol_avoids_conflicts(self): + namer = naming.Namer({'temp': 1}) + # temp is reserved in the global namespace + self.assertEqual('temp_1', namer.new_symbol('temp', set())) + # temp_2 is reserved in the local namespace + self.assertEqual('temp_3', namer.new_symbol('temp', set(('temp_2',)))) + self.assertItemsEqual(('temp_1', 'temp_3'), namer.generated_names) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/origin_info.py b/src/braket/experimental/autoqasm/autograph/pyct/origin_info.py new file mode 100644 index 00000000..ec64fab3 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/origin_info.py @@ -0,0 +1,296 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Container for origin source code information before AutoGraph compilation.""" +import collections +import difflib +import io +import os +import tokenize + +import gast + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import ast_util +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import pretty_printer +import inspect as tf_inspect + + +class LineLocation( + collections.namedtuple('LineLocation', ('filename', 'lineno'))): + """Similar to Location, but without column information. + + Attributes: + filename: Text + lineno: int, 1-based + """ + pass + + +class Location( + collections.namedtuple('Location', ('filename', 'lineno', 'col_offset'))): + """Encodes code location information. + + Attributes: + filename: Text + lineno: int, 1-based + col_offset: int + line_loc: LineLocation + """ + + @property + def line_loc(self): + return LineLocation(self.filename, self.lineno) + + +class OriginInfo( + collections.namedtuple( + 'OriginInfo', + ('loc', 'function_name', 'source_code_line', 'comment'))): + """Container for information about the source code before conversion. + + Attributes: + loc: Location + function_name: Optional[Text] + source_code_line: Text + comment: Optional[Text] + """ + + def as_frame(self): + """Returns a 4-tuple consistent with the return of traceback.extract_tb.""" + return (self.loc.filename, self.loc.lineno, self.function_name, + self.source_code_line) + + def __repr__(self): + if self.loc.filename: + return '{}:{}:{}'.format( + os.path.split(self.loc.filename)[1], self.loc.lineno, + self.loc.col_offset) + return ':{}:{}'.format(self.loc.lineno, self.loc.col_offset) + + +# TODO(mdan): This source map should be a class - easier to refer to. +def create_source_map(nodes, code, filepath): + """Creates a source map between an annotated AST and the code it compiles to. + + Note: this function assumes nodes nodes, code and filepath correspond to the + same code. + + Args: + nodes: Iterable[ast.AST, ...], one or more AST modes. + code: Text, the source code in which nodes are found. + filepath: Text + + Returns: + Dict[LineLocation, OriginInfo], mapping locations in code to locations + indicated by origin annotations in node. + """ + reparsed_nodes = parser.parse(code, preamble_len=0, single_node=False) + for node in reparsed_nodes: + resolve(node, code, filepath, node.lineno, node.col_offset) + + source_map = {} + + try: + for before, after in ast_util.parallel_walk(nodes, reparsed_nodes): + # Note: generated code might not be mapped back to its origin. + # TODO(mdan): Generated code should always be mapped to something. + origin_info = anno.getanno(before, anno.Basic.ORIGIN, default=None) + final_info = anno.getanno(after, anno.Basic.ORIGIN, default=None) + if origin_info is None or final_info is None: + continue + + # Note: the keys are by line only, excluding the column offset. + line_loc = LineLocation(final_info.loc.filename, final_info.loc.lineno) + + existing_origin = source_map.get(line_loc) + if existing_origin is not None: + # Overlaps may exist because of child nodes, but almost never to + # different line locations. Exception make decorated functions, where + # both lines are mapped to the same line in the AST. + + # Line overlaps: keep bottom node. + if existing_origin.loc.line_loc == origin_info.loc.line_loc: + if existing_origin.loc.lineno >= origin_info.loc.lineno: + continue + + # In case of column overlaps, keep the leftmost node. + if existing_origin.loc.col_offset <= origin_info.loc.col_offset: + continue + + source_map[line_loc] = origin_info + + except ValueError as err: + new_msg = 'Inconsistent ASTs detected. This is a bug. Cause: \n' + new_msg += str(err) + new_msg += 'Diff:\n' + + for n, rn in zip(nodes, reparsed_nodes): + nodes_str = pretty_printer.fmt(n, color=False, noanno=True) + reparsed_nodes_str = pretty_printer.fmt(rn, color=False, noanno=True) + diff = difflib.context_diff( + nodes_str.split('\n'), + reparsed_nodes_str.split('\n'), + fromfile='Original nodes', + tofile='Reparsed nodes', + n=7) + diff = '\n'.join(diff) + new_msg += diff + '\n' + raise ValueError(new_msg) + + return source_map + + +class _Function: + + def __init__(self, name): + self.name = name + + +class OriginResolver(gast.NodeVisitor): + """Annotates an AST with additional source information like file name.""" + + def __init__(self, root_node, source_lines, comments_map, + context_lineno, context_col_offset, + filepath): + self._source_lines = source_lines + self._comments_map = comments_map + + if (hasattr(root_node, 'decorator_list') and root_node.decorator_list and + hasattr(root_node.decorator_list[0], 'lineno')): + # Typical case: functions. The line number of the first decorator + # is more accurate than the line number of the function itself in + # 3.8+. In earier versions they coincide. + self._lineno_offset = context_lineno - root_node.decorator_list[0].lineno + else: + # Fall back to the line number of the root node. + self._lineno_offset = context_lineno - root_node.lineno + + self._col_offset = context_col_offset - root_node.col_offset + + self._filepath = filepath + + self._function_stack = [] + + def _absolute_lineno(self, lineno): + return lineno + self._lineno_offset + + def _absolute_col_offset(self, col_offset): + if col_offset is None: + return 0 + return col_offset + self._col_offset + + def _attach_origin_info(self, node): + lineno = getattr(node, 'lineno', None) + col_offset = getattr(node, 'col_offset', None) + + if lineno is None: + return + + if self._function_stack: + function_name = self._function_stack[-1].name + else: + function_name = None + + source_code_line = self._source_lines[lineno - 1] + comment = self._comments_map.get(lineno) + + loc = Location(self._filepath, self._absolute_lineno(lineno), + self._absolute_col_offset(col_offset)) + origin = OriginInfo(loc, function_name, source_code_line, comment) + anno.setanno(node, 'lineno', lineno) + anno.setanno(node, anno.Basic.ORIGIN, origin) + + def visit(self, node): + entered_function = False + if isinstance(node, gast.FunctionDef): + entered_function = True + self._function_stack.append(_Function(node.name)) + + self._attach_origin_info(node) + self.generic_visit(node) + + if entered_function: + self._function_stack.pop() + + +def resolve(node, source, context_filepath, context_lineno, context_col_offset): + """Adds origin information to an AST, based on the source it was loaded from. + + This allows us to map the original source code line numbers to generated + source code. + + Note: the AST may be a part of a larger context (e.g. a function is part of + a module that may contain other things). However, this function does not + assume the source argument contains the entire context, nor that it contains + only code corresponding to node itself. However, it assumes that node was + parsed from the given source code. + For this reason, two extra arguments are required, and they indicate the + location of the node in the original context. + + Args: + node: gast.AST, the AST to annotate. + source: Text, the source code representing node. + context_filepath: Text + context_lineno: int + context_col_offset: int + """ + # TODO(mdan): Pull this to a separate utility. + code_reader = io.StringIO(source) + comments_map = {} + try: + for token in tokenize.generate_tokens(code_reader.readline): + tok_type, tok_string, loc, _, _ = token + srow, _ = loc + if tok_type == tokenize.COMMENT: + comments_map[srow] = tok_string.strip()[1:].strip() + except tokenize.TokenError: + if isinstance(node, gast.Lambda): + # Source code resolution in older Python versions is brittle for + # lambda functions, and may contain garbage. + pass + else: + raise + + source_lines = source.split('\n') + visitor = OriginResolver(node, source_lines, comments_map, + context_lineno, context_col_offset, + context_filepath) + visitor.visit(node) + + +def resolve_entity(node, source, entity): + """Like resolve, but extracts the context information from an entity.""" + lines, lineno = tf_inspect.getsourcelines(entity) + filepath = tf_inspect.getsourcefile(entity) + + # Poor man's attempt at guessing the column offset: count the leading + # whitespace. This might not work well with tabs. + definition_line = lines[0] + col_offset = len(definition_line) - len(definition_line.lstrip()) + + resolve(node, source, filepath, lineno, col_offset) + + +def copy_origin(from_node, to_node): + """Copies the origin info from a node to another, recursively.""" + origin = anno.Basic.ORIGIN.of(from_node, default=None) + if origin is None: + return + if not isinstance(to_node, (list, tuple)): + to_node = (to_node,) + for node in to_node: + for n in gast.walk(node): + anno.setanno(n, anno.Basic.ORIGIN, origin) diff --git a/src/braket/experimental/autoqasm/autograph/pyct/origin_info_test.py b/src/braket/experimental/autoqasm/autograph/pyct/origin_info_test.py new file mode 100644 index 00000000..c1d32ac6 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/origin_info_test.py @@ -0,0 +1,268 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for origin_info module.""" + +import inspect +import sys +import textwrap + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import inspect_utils +from braket.experimental.autoqasm.autograph.pyct import origin_info +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct.testing import basic_definitions +from tensorflow.python.platform import test +import inspect as tf_inspect + + +class OriginInfoTest(test.TestCase): + + def test_create_source_map(self): + + source = """ + def test_fn(x): + return x + 1 + """ + source = textwrap.dedent(source) + + node = parser.parse(source) + fake_origin = origin_info.OriginInfo( + loc=origin_info.Location('fake_filename', 3, 7), + function_name='fake_function_name', + source_code_line='fake source line', + comment=None) + anno.setanno(node, anno.Basic.ORIGIN, fake_origin) + + source_map = origin_info.create_source_map(node, source, 'test_filename') + + loc = origin_info.LineLocation('test_filename', 2) + self.assertIn(loc, source_map) + self.assertIs(source_map[loc], fake_origin) + + def _create_source_map(self, test_fn): + node, source = parser.parse_entity(test_fn, ()) + origin_info.resolve_entity(node, source, test_fn) + # Creating a source map with the source code as output will create + # an identity map. + return origin_info.create_source_map(node, source, 'test_filename') + + def test_create_source_map_identity(self): + test_fn = basic_definitions.simple_function + source_map = self._create_source_map(test_fn) + module_path = tf_inspect.getsourcefile(test_fn) + + # Origin line numbers below should match those in basic_definitions.py + fn_start = inspect.getsourcelines(test_fn)[1] + + definition_loc = origin_info.LineLocation('test_filename', 1) + self.assertIn(definition_loc, source_map) + self.assertEqual(source_map[definition_loc].loc.lineno, fn_start) + self.assertEqual(source_map[definition_loc].loc.filename, module_path) + self.assertEqual(source_map[definition_loc].function_name, + 'simple_function') + + def test_create_source_map_multiline_call(self): + test_fn = basic_definitions.function_with_multiline_call + source_map = self._create_source_map(test_fn) + module_path = tf_inspect.getsourcefile(test_fn) + + # Origin line numbers below should match those in basic_definitions.py + fn_start = inspect.getsourcelines(test_fn)[1] + + call_loc = origin_info.LineLocation('test_filename', 3) + self.assertIn(call_loc, source_map) + self.assertEqual(source_map[call_loc].loc.lineno, fn_start + 2) + self.assertEqual(source_map[call_loc].loc.filename, module_path) + self.assertEqual(source_map[call_loc].function_name, + 'function_with_multiline_call') + self.assertEqual(source_map[call_loc].source_code_line, ' return range(') + + second_arg_loc = origin_info.LineLocation('test_filename', 5) + self.assertIn(second_arg_loc, source_map) + self.assertEqual(source_map[second_arg_loc].loc.lineno, fn_start + 4) + self.assertEqual(source_map[second_arg_loc].loc.filename, module_path) + self.assertEqual(source_map[second_arg_loc].function_name, + 'function_with_multiline_call') + self.assertEqual(source_map[second_arg_loc].source_code_line, + ' x + 1,') + + def test_create_source_map_no_origin_info(self): + + test_fn = basic_definitions.simple_function + node, _ = parser.parse_entity(test_fn, + inspect_utils.getfutureimports(test_fn)) + # No origin information should result in an empty map. + test_fn_lines, _ = tf_inspect.getsourcelines(test_fn) + source_map = origin_info.create_source_map(node, '\n'.join(test_fn_lines), + test_fn) + + self.assertEmpty(source_map) + + def test_resolve(self): + + source = """ + def test_fn(x): + '''Docstring.''' + return x # comment + """ + source = textwrap.dedent(source) + node = parser.parse(source) + origin_info.resolve(node, source, 'test_file', 10, 10) + + def_origin = anno.getanno(node, anno.Basic.ORIGIN) + self.assertEqual(def_origin.loc.filename, 'test_file') + self.assertEqual(def_origin.loc.lineno, 10) + self.assertEqual(def_origin.loc.col_offset, 10) + self.assertEqual(def_origin.source_code_line, 'def test_fn(x):') + self.assertIsNone(def_origin.comment) + + docstring_origin = anno.getanno(node.body[0], anno.Basic.ORIGIN) + self.assertEqual(def_origin.loc.filename, 'test_file') + self.assertEqual(docstring_origin.loc.lineno, 11) + self.assertEqual(docstring_origin.loc.col_offset, 12) + self.assertEqual(docstring_origin.source_code_line, " '''Docstring.'''") + self.assertIsNone(docstring_origin.comment) + + ret_origin = anno.getanno(node.body[1], anno.Basic.ORIGIN) + self.assertEqual(def_origin.loc.filename, 'test_file') + self.assertEqual(ret_origin.loc.lineno, 12) + self.assertEqual(ret_origin.loc.col_offset, 12) + self.assertEqual(ret_origin.source_code_line, ' return x # comment') + self.assertEqual(ret_origin.comment, 'comment') + + def test_resolve_with_trailing_garbage(self): + # This comment will be missed because the tokenizer fails to reach it. + source = ' lambda: foo([], bar=1)), baz=2)()' + clean_source = 'lambda: foo([], bar=1)' + node = parser.parse(clean_source).value + origin_info.resolve(node, source, 'test_file', 10, 10) + + def_origin = anno.getanno(node, anno.Basic.ORIGIN) + self.assertEqual(def_origin.loc.lineno, 10) + self.assertEqual(def_origin.loc.col_offset, 10) + self.assertEqual(def_origin.source_code_line, source) + self.assertIsNone(def_origin.comment) + + def test_resolve_entity(self): + test_fn = basic_definitions.simple_function + node, source = parser.parse_entity( + test_fn, inspect_utils.getfutureimports(test_fn)) + origin_info.resolve_entity(node, source, test_fn) + + # The line numbers below should match those in basic_definitions.py + fn_start = inspect.getsourcelines(test_fn)[1] + + def_origin = anno.getanno(node, anno.Basic.ORIGIN) + self.assertEqual(def_origin.loc.lineno, fn_start) + self.assertEqual(def_origin.loc.col_offset, 0) + self.assertEqual(def_origin.source_code_line, 'def simple_function(x):') + self.assertIsNone(def_origin.comment) + + docstring_origin = anno.getanno(node.body[0], anno.Basic.ORIGIN) + self.assertEqual(docstring_origin.loc.lineno, fn_start + 1) + self.assertEqual(docstring_origin.loc.col_offset, 2) + self.assertEqual(docstring_origin.source_code_line, ' """Docstring."""') + self.assertIsNone(docstring_origin.comment) + + ret_origin = anno.getanno(node.body[1], anno.Basic.ORIGIN) + self.assertEqual(ret_origin.loc.lineno, fn_start + 2) + self.assertEqual(ret_origin.loc.col_offset, 2) + self.assertEqual(ret_origin.source_code_line, ' return x # comment') + self.assertEqual(ret_origin.comment, 'comment') + + def test_resolve_entity_nested_function(self): + test_fn = basic_definitions.nested_functions + node, source = parser.parse_entity( + test_fn, inspect_utils.getfutureimports(test_fn)) + origin_info.resolve_entity(node, source, test_fn) + + # The line numbers below should match those in basic_definitions.py + fn_start = inspect.getsourcelines(test_fn)[1] + + inner_def_origin = anno.getanno(node.body[1], anno.Basic.ORIGIN) + self.assertEqual(inner_def_origin.loc.lineno, fn_start + 3) + self.assertEqual(inner_def_origin.loc.col_offset, 2) + self.assertEqual(inner_def_origin.source_code_line, ' def inner_fn(y):') + self.assertIsNone(inner_def_origin.comment) + + inner_ret_origin = anno.getanno(node.body[1].body[0], anno.Basic.ORIGIN) + self.assertEqual(inner_ret_origin.loc.lineno, fn_start + 4) + self.assertEqual(inner_ret_origin.loc.col_offset, 4) + self.assertEqual(inner_ret_origin.source_code_line, ' return y') + self.assertIsNone(inner_ret_origin.comment) + + def test_resolve_entity_indented_block(self): + + test_fn = basic_definitions.SimpleClass.simple_method + node, source = parser.parse_entity(test_fn, + inspect_utils.getfutureimports(test_fn)) + origin_info.resolve_entity(node, source, test_fn) + + # The line numbers below should match those in basic_definitions.py + fn_start = inspect.getsourcelines(test_fn)[1] + + def_origin = anno.getanno(node, anno.Basic.ORIGIN) + self.assertEqual(def_origin.loc.lineno, fn_start) + self.assertEqual(def_origin.loc.col_offset, 2) + self.assertEqual(def_origin.source_code_line, 'def simple_method(self):') + self.assertIsNone(def_origin.comment) + + ret_origin = anno.getanno(node.body[0], anno.Basic.ORIGIN) + self.assertEqual(ret_origin.loc.lineno, fn_start + 1) + self.assertEqual(ret_origin.loc.col_offset, 4) + self.assertEqual(ret_origin.source_code_line, ' return self') + self.assertIsNone(ret_origin.comment) + + def test_resolve_entity_decorated_function(self): + test_fn = basic_definitions.decorated_function + node, source = parser.parse_entity(test_fn, + inspect_utils.getfutureimports(test_fn)) + origin_info.resolve_entity(node, source, test_fn) + + # The line numbers below should match those in basic_definitions.py + fn_start = inspect.getsourcelines(test_fn)[1] + + def_origin = anno.getanno(node, anno.Basic.ORIGIN) + if sys.version_info >= (3, 8): + self.assertEqual(def_origin.loc.lineno, fn_start + 2) + self.assertEqual(def_origin.source_code_line, + 'def decorated_function(x):') + else: + self.assertEqual(def_origin.loc.lineno, fn_start) + self.assertEqual(def_origin.source_code_line, '@basic_decorator') + self.assertEqual(def_origin.loc.col_offset, 0) + self.assertIsNone(def_origin.comment) + + if_origin = anno.getanno(node.body[0], anno.Basic.ORIGIN) + self.assertEqual(if_origin.loc.lineno, fn_start + 3) + self.assertEqual(if_origin.loc.col_offset, 2) + self.assertEqual(if_origin.source_code_line, ' if x > 0:') + self.assertIsNone(if_origin.comment) + + ret1_origin = anno.getanno(node.body[0].body[0], anno.Basic.ORIGIN) + self.assertEqual(ret1_origin.loc.lineno, fn_start + 4) + self.assertEqual(ret1_origin.loc.col_offset, 4) + self.assertEqual(ret1_origin.source_code_line, ' return 1') + self.assertIsNone(ret1_origin.comment) + + ret2_origin = anno.getanno(node.body[1], anno.Basic.ORIGIN) + self.assertEqual(ret2_origin.loc.lineno, fn_start + 5) + self.assertEqual(ret2_origin.loc.col_offset, 2) + self.assertEqual(ret2_origin.source_code_line, ' return 2') + self.assertIsNone(ret2_origin.comment) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/parser.py b/src/braket/experimental/autoqasm/autograph/pyct/parser.py new file mode 100644 index 00000000..95003553 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/parser.py @@ -0,0 +1,396 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Converting code to AST. + +Adapted from Tangent. +""" + +import ast +import inspect +import io +import linecache +import re +import sys +import textwrap +import tokenize + +import astunparse +import gast + +from braket.experimental.autoqasm.autograph.pyct import errors +from braket.experimental.autoqasm.autograph.pyct import inspect_utils +import inspect as tf_inspect + + +PY2_PREAMBLE = textwrap.dedent(""" +""") +PY3_PREAMBLE = '' +MAX_SIZE = 0 + +if sys.version_info >= (3, 9): + astunparse = ast + +if sys.version_info >= (3,): + STANDARD_PREAMBLE = PY3_PREAMBLE + MAX_SIZE = sys.maxsize +else: + STANDARD_PREAMBLE = PY2_PREAMBLE + MAX_SIZE = sys.maxint + +STANDARD_PREAMBLE_LEN = STANDARD_PREAMBLE.count('__future__') + + +_LEADING_WHITESPACE = re.compile(r'\s*') + + +def _unfold_continuations(code_string): + """Removes any backslash line continuations from the code.""" + return code_string.replace('\\\n', '') + + +def dedent_block(code_string): + """Dedents a code so that its first line starts at row zero.""" + + code_string = _unfold_continuations(code_string) + + token_gen = tokenize.generate_tokens(io.StringIO(code_string).readline) + + block_indentation = None + tokens = [] + try: + for tok in token_gen: + tokens.append(tok) + except tokenize.TokenError: + # Resolution of lambda functions may yield incomplete code, which can + # in turn generate this error. We silently ignore this error because the + # parser may still be able to deal with it. + pass + + for tok in tokens: + tok_type, tok_string, _, _, _ = tok + if tok_type == tokenize.INDENT: + block_indentation = tok_string + block_level = len(block_indentation) + break + elif tok_type not in ( + tokenize.NL, tokenize.NEWLINE, tokenize.STRING, tokenize.COMMENT): + block_indentation = '' + break + + if not block_indentation: + return code_string + + block_level = len(block_indentation) + first_indent_uses_tabs = '\t' in block_indentation + for i, tok in enumerate(tokens): + tok_type, tok_string, _, _, _ = tok + if tok_type == tokenize.INDENT: + if ((' ' in tok_string and first_indent_uses_tabs) + or ('\t' in tok_string and not first_indent_uses_tabs)): + # TODO(mdan): We could attempt to convert tabs to spaces by unix rule. + # See: + # https://docs.python.org/3/reference/lexical_analysis.html#indentation + raise errors.UnsupportedLanguageElementError( + 'code mixing tabs and spaces for indentation is not allowed') + if len(tok_string) >= block_level: + tok_string = tok_string[block_level:] + tokens[i] = (tok_type, tok_string) + + new_code = tokenize.untokenize(tokens) + + # Note: untokenize respects the line structure, but not the whitespace within + # lines. For example, `def foo()` may be untokenized as `def foo ()` + # So instead of using the output of dedent, we match the leading whitespace + # on each line. + dedented_code = [] + for line, new_line in zip(code_string.split('\n'), new_code.split('\n')): + original_indent = re.match(_LEADING_WHITESPACE, line).group() + new_indent = re.match(_LEADING_WHITESPACE, new_line).group() + if len(original_indent) > len(new_indent): + dedented_line = line[len(original_indent) - len(new_indent):] + else: + dedented_line = line + dedented_code.append(dedented_line) + new_code = '\n'.join(dedented_code) + + return new_code + + +def parse_entity(entity, future_features): + """Returns the AST and source code of given entity. + + Args: + entity: Any, Python function/method/class + future_features: Iterable[Text], future features to use (e.g. + 'print_statement'). See + https://docs.python.org/2/reference/simple_stmts.html#future + + Returns: + gast.AST, Text: the parsed AST node; the source code that was parsed to + generate the AST (including any prefixes that this function may have added). + """ + if inspect_utils.islambda(entity): + return _parse_lambda(entity) + + try: + original_source = inspect_utils.getimmediatesource(entity) + except OSError as e: + raise errors.InaccessibleSourceCodeError( + f'Unable to locate the source code of {entity}. Note that functions' + ' defined in certain environments, like the interactive Python shell,' + ' do not expose their source code. If that is the case, you should' + ' define them in a .py source file. If you are certain the code is' + ' graph-compatible, wrap the call using' + f' @tf.autograph.experimental.do_not_convert. Original error: {e}') + + source = dedent_block(original_source) + + future_statements = tuple( + 'from __future__ import {}'.format(name) for name in future_features) + source = '\n'.join(future_statements + (source,)) + + return parse(source, preamble_len=len(future_features)), source + + +def _without_context(node, lines, minl, maxl): + """Returns a clean node and source code without indenting and context.""" + for n in gast.walk(node): + lineno = getattr(n, 'lineno', None) + if lineno is not None: + n.lineno = lineno - minl + end_lineno = getattr(n, 'end_lineno', None) + if end_lineno is not None: + n.end_lineno = end_lineno - minl + + code_lines = lines[minl - 1:maxl] + + # Attempt to clean up surrounding context code. + + end_col_offset = getattr(node, 'end_col_offset', None) + if end_col_offset is not None: + # This is only available in 3.8. + code_lines[-1] = code_lines[-1][:end_col_offset] + + col_offset = getattr(node, 'col_offset', None) + if col_offset is None: + # Older Python: try to find the "lambda" token. This is brittle. + match = re.search(r'(? 0: + return -x + return x + """ + + f = self._eval_code(parser.dedent_block(code), 'f') + self.assertEqual(f(1), -1) + self.assertEqual(f(-1), -1) + + def test_dedent_block_comments_out_of_line(self): + + code = """ + ### + def f(x): +### + if x > 0: + ### + return -x + ### + ### + return x + ### + """ + + f = self._eval_code(parser.dedent_block(code), 'f') + self.assertEqual(f(1), -1) + self.assertEqual(f(-1), -1) + + def test_dedent_block_multiline_string(self): + + code = """ + def f(): + ''' + Docstring. + ''' + return ''' + 1 + 2 + 3''' + """ + + f = self._eval_code(parser.dedent_block(code), 'f') + self.assertEqual(f.__doc__, '\n Docstring.\n ') + self.assertEqual(f(), '\n 1\n 2\n 3') + + def test_dedent_block_multiline_expression(self): + + code = """ + def f(): + return (1, +2, + 3) + """ + + f = self._eval_code(parser.dedent_block(code), 'f') + self.assertEqual(f(), (1, 2, 3)) + + def test_dedent_block_continuation(self): + + code = r""" + def f(): + a = \ + 1 + return a + """ + + f = self._eval_code(parser.dedent_block(code), 'f') + self.assertEqual(f(), 1) + + def test_dedent_block_continuation_in_string(self): + + code = r""" + def f(): + a = "a \ + b" + return a + """ + + f = self._eval_code(parser.dedent_block(code), 'f') + self.assertEqual(f(), 'a b') + + def test_parse_expression(self): + node = parser.parse_expression('a.b') + self.assertEqual('a', node.value.id) + self.assertEqual('b', node.attr) + + def test_unparse(self): + node = gast.If( + test=gast.Constant(1, kind=None), + body=[ + gast.Assign( + targets=[ + gast.Name( + 'a', + ctx=gast.Store(), + annotation=None, + type_comment=None) + ], + value=gast.Name( + 'b', ctx=gast.Load(), annotation=None, type_comment=None)) + ], + orelse=[ + gast.Assign( + targets=[ + gast.Name( + 'a', + ctx=gast.Store(), + annotation=None, + type_comment=None) + ], + value=gast.Constant('c', kind=None)) + ]) + + source = parser.unparse(node, indentation=' ') + self.assertEqual( + textwrap.dedent(""" + # coding=utf-8 + if 1: + a = b + else: + a = 'c' + """).strip(), source.strip()) + + def test_ext_slice_roundtrip(self): + def ext_slice(n): + return n[:, :], n[0, :], n[:, 0] + + node, _ = parser.parse_entity(ext_slice, future_features=()) + source = parser.unparse(node) + self.assertAstMatches(node, source, expr=False) + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/pretty_printer.py b/src/braket/experimental/autoqasm/autograph/pyct/pretty_printer.py new file mode 100644 index 00000000..184355ab --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/pretty_printer.py @@ -0,0 +1,130 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Print an AST tree in a form more readable than ast.dump.""" + +import gast +import termcolor + + +class PrettyPrinter(gast.NodeVisitor): + """Print AST nodes.""" + + def __init__(self, color, noanno): + self.indent_lvl = 0 + self.result = '' + self.color = color + self.noanno = noanno + + def _color(self, string, color, attrs=None): + if self.color: + return termcolor.colored(string, color, attrs=attrs) + return string + + def _type(self, node): + return self._color(node.__class__.__name__, None, ['bold']) + + def _field(self, name): + return self._color(name, 'blue') + + def _value(self, name): + return self._color(name, 'magenta') + + def _warning(self, name): + return self._color(name, 'red') + + def _indent(self): + return self._color('| ' * self.indent_lvl, None, ['dark']) + + def _print(self, s): + self.result += s + self.result += '\n' + + def generic_visit(self, node, name=None): + # In very rare instances, a list can contain something other than a Node. + # e.g. Global contains a list of strings. + if isinstance(node, str): + if name: + self._print('%s%s="%s"' % (self._indent(), name, node)) + else: + self._print('%s"%s"' % (self._indent(), node)) + return + + if node._fields: + cont = ':' + else: + cont = '()' + + if name: + self._print('%s%s=%s%s' % (self._indent(), self._field(name), + self._type(node), cont)) + else: + self._print('%s%s%s' % (self._indent(), self._type(node), cont)) + + self.indent_lvl += 1 + for f in node._fields: + if self.noanno and f.startswith('__'): + continue + if not hasattr(node, f): + self._print('%s%s' % (self._indent(), self._warning('%s=' % f))) + continue + v = getattr(node, f) + if isinstance(v, list): + if v: + self._print('%s%s=[' % (self._indent(), self._field(f))) + self.indent_lvl += 1 + for n in v: + if n is not None: + self.generic_visit(n) + else: + self._print('%sNone' % (self._indent())) + self.indent_lvl -= 1 + self._print('%s]' % (self._indent())) + else: + self._print('%s%s=[]' % (self._indent(), self._field(f))) + elif isinstance(v, tuple): + if v: + self._print('%s%s=(' % (self._indent(), self._field(f))) + self.indent_lvl += 1 + for n in v: + if n is not None: + self.generic_visit(n) + else: + self._print('%sNone' % (self._indent())) + self.indent_lvl -= 1 + self._print('%s)' % (self._indent())) + else: + self._print('%s%s=()' % (self._indent(), self._field(f))) + elif isinstance(v, gast.AST): + self.generic_visit(v, f) + elif isinstance(v, bytes): + self._print('%s%s=%s' % (self._indent(), self._field(f), + self._value('b"%s"' % v))) + elif isinstance(v, str): + self._print('%s%s=%s' % (self._indent(), self._field(f), + self._value('u"%s"' % v))) + else: + self._print('%s%s=%s' % (self._indent(), self._field(f), + self._value(v))) + self.indent_lvl -= 1 + + +def fmt(node, color=True, noanno=False): + printer = PrettyPrinter(color, noanno) + if isinstance(node, (list, tuple)): + for n in node: + printer.visit(n) + else: + printer.visit(node) + return printer.result diff --git a/src/braket/experimental/autoqasm/autograph/pyct/pretty_printer_test.py b/src/braket/experimental/autoqasm/autograph/pyct/pretty_printer_test.py new file mode 100644 index 00000000..952b654a --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/pretty_printer_test.py @@ -0,0 +1,57 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for pretty_printer module.""" + +import ast +import textwrap + +from braket.experimental.autoqasm.autograph.pyct import pretty_printer +from tensorflow.python.platform import test + + +class PrettyPrinterTest(test.TestCase): + + def test_unicode_bytes(self): + source = textwrap.dedent(''' + def f(): + return b'b', u'u', 'depends_py2_py3' + ''') + node = ast.parse(source) + self.assertIsNotNone(pretty_printer.fmt(node)) + + def test_format(self): + node = ast.FunctionDef( + name='f', + args=ast.arguments( + args=[ast.Name(id='a', ctx=ast.Param())], + vararg=None, + kwarg=None, + defaults=[]), + body=[ + ast.Return( + ast.BinOp( + op=ast.Add(), + left=ast.Name(id='a', ctx=ast.Load()), + right=ast.Num(1))) + ], + decorator_list=[], + returns=None) + # Just checking for functionality, the color control characters make it + # difficult to inspect the result. + self.assertIsNotNone(pretty_printer.fmt(node)) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/qual_names.py b/src/braket/experimental/autoqasm/autograph/pyct/qual_names.py new file mode 100644 index 00000000..c9562698 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/qual_names.py @@ -0,0 +1,266 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Utilities for manipulating qualified names. + +A qualified name is a uniform way to refer to simple (e.g. 'foo') and composite +(e.g. 'foo.bar') syntactic symbols. + +This is *not* related to the __qualname__ attribute used by inspect, which +refers to scopes. +""" + +import collections + +import gast + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import parser + + +class CallerMustSetThis(object): + pass + + +class Symbol(collections.namedtuple('Symbol', ['name'])): + """Represents a Python symbol.""" + + +class Literal(collections.namedtuple('Literal', ['value'])): + """Represents a Python numeric literal.""" + + def __str__(self): + if isinstance(self.value, str): + return "'{}'".format(self.value) + return str(self.value) + + def __repr__(self): + return str(self) + + +# TODO(mdan): Use subclasses to remove the has_attr has_subscript booleans. +class QN(object): + """Represents a qualified name.""" + + def __init__(self, base, attr=None, subscript=None): + if attr is not None and subscript is not None: + raise ValueError('A QN can only be either an attr or a subscript, not ' + 'both: attr={}, subscript={}.'.format(attr, subscript)) + self._has_attr = False + self._has_subscript = False + + if attr is not None: + if not isinstance(base, QN): + raise ValueError( + 'for attribute QNs, base must be a QN; got instead "%s"' % base) + if not isinstance(attr, str): + raise ValueError('attr may only be a string; got instead "%s"' % attr) + self._parent = base + # TODO(mdan): Get rid of the tuple - it can only have 1 or 2 elements now. + self.qn = (base, attr) + self._has_attr = True + + elif subscript is not None: + if not isinstance(base, QN): + raise ValueError('For subscript QNs, base must be a QN.') + self._parent = base + self.qn = (base, subscript) + self._has_subscript = True + + else: + if not isinstance(base, (str, Literal)): + # TODO(mdan): Require Symbol instead of string. + raise ValueError( + 'for simple QNs, base must be a string or a Literal object;' + ' got instead "%s"' % type(base)) + assert '.' not in base and '[' not in base and ']' not in base + self._parent = None + self.qn = (base,) + + def is_symbol(self): + return isinstance(self.qn[0], str) + + def is_simple(self): + return len(self.qn) <= 1 + + def is_composite(self): + return len(self.qn) > 1 + + def has_subscript(self): + return self._has_subscript + + def has_attr(self): + return self._has_attr + + @property + def attr(self): + if not self._has_attr: + raise ValueError('Cannot get attr of non-attribute "%s".' % self) + return self.qn[1] + + @property + def parent(self): + if self._parent is None: + raise ValueError('Cannot get parent of simple name "%s".' % self.qn[0]) + return self._parent + + @property + def owner_set(self): + """Returns all the symbols (simple or composite) that own this QN. + + In other words, if this symbol was modified, the symbols in the owner set + may also be affected. + + Examples: + 'a.b[c.d]' has two owners, 'a' and 'a.b' + """ + owners = set() + if self.has_attr() or self.has_subscript(): + owners.add(self.parent) + owners.update(self.parent.owner_set) + return owners + + @property + def support_set(self): + """Returns the set of simple symbols that this QN relies on. + + This would be the smallest set of symbols necessary for the QN to + statically resolve (assuming properties and index ranges are verified + at runtime). + + Examples: + 'a.b' has only one support symbol, 'a' + 'a[i]' has two support symbols, 'a' and 'i' + """ + # TODO(mdan): This might be the set of Name nodes in the AST. Track those? + roots = set() + if self.has_attr(): + roots.update(self.parent.support_set) + elif self.has_subscript(): + roots.update(self.parent.support_set) + roots.update(self.qn[1].support_set) + else: + roots.add(self) + return roots + + def __hash__(self): + return hash(self.qn + (self._has_attr, self._has_subscript)) + + def __eq__(self, other): + return (isinstance(other, QN) and self.qn == other.qn and + self.has_subscript() == other.has_subscript() and + self.has_attr() == other.has_attr()) + + def __lt__(self, other): + return str(self) < str(other) + + def __gt__(self, other): + return str(self) > str(other) + + def __str__(self): + root = self.qn[0] + if self.has_subscript(): + return '{}[{}]'.format(root, self.qn[1]) + if self.has_attr(): + return '.'.join(map(str, self.qn)) + else: + return str(root) + + def __repr__(self): + return str(self) + + def ssf(self): + """Simple symbol form.""" + ssfs = [n.ssf() if isinstance(n, QN) else n for n in self.qn] + ssf_string = '' + for i in range(0, len(self.qn) - 1): + if self.has_subscript(): + delimiter = '_sub_' + else: + delimiter = '_' + ssf_string += ssfs[i] + delimiter + return ssf_string + ssfs[-1] + + def ast(self): + """AST representation.""" + # The caller must adjust the context appropriately. + if self.has_subscript(): + return gast.Subscript( + value=self.parent.ast(), + slice=self.qn[-1].ast(), + ctx=CallerMustSetThis) + if self.has_attr(): + return gast.Attribute( + value=self.parent.ast(), attr=self.qn[-1], ctx=CallerMustSetThis) + + base = self.qn[0] + if isinstance(base, str): + return gast.Name( + base, ctx=CallerMustSetThis, annotation=None, type_comment=None) + elif isinstance(base, Literal): + return gast.Constant(base.value, kind=None) + else: + assert False, ('the constructor should prevent types other than ' + 'str and Literal') + + +class QnResolver(gast.NodeTransformer): + """Annotates nodes with QN information. + + Note: Not using NodeAnnos to avoid circular dependencies. + """ + + def visit_Name(self, node): + node = self.generic_visit(node) + anno.setanno(node, anno.Basic.QN, QN(node.id)) + return node + + def visit_Attribute(self, node): + node = self.generic_visit(node) + if anno.hasanno(node.value, anno.Basic.QN): + anno.setanno(node, anno.Basic.QN, + QN(anno.getanno(node.value, anno.Basic.QN), attr=node.attr)) + return node + + def visit_Subscript(self, node): + # TODO(mdan): This may no longer apply if we overload getitem. + node = self.generic_visit(node) + s = node.slice + if isinstance(s, (gast.Tuple, gast.Slice)): + # TODO(mdan): Support range and multi-dimensional indices. + # Continuing silently because some demos use these. + return node + if isinstance(s, gast.Constant) and s.value != Ellipsis: + subscript = QN(Literal(s.value)) + else: + # The index may be an expression, case in which a name doesn't make sense. + if anno.hasanno(s, anno.Basic.QN): + subscript = anno.getanno(s, anno.Basic.QN) + else: + return node + if anno.hasanno(node.value, anno.Basic.QN): + anno.setanno(node, anno.Basic.QN, + QN(anno.getanno(node.value, anno.Basic.QN), + subscript=subscript)) + return node + + +def resolve(node): + return QnResolver().visit(node) + + +def from_str(qn_str): + node = parser.parse_expression(qn_str) + node = resolve(node) + return anno.getanno(node, anno.Basic.QN) diff --git a/src/braket/experimental/autoqasm/autograph/pyct/qual_names_test.py b/src/braket/experimental/autoqasm/autograph/pyct/qual_names_test.py new file mode 100644 index 00000000..2afff261 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/qual_names_test.py @@ -0,0 +1,261 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for qual_names module.""" + +import textwrap + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import qual_names +from braket.experimental.autoqasm.autograph.pyct.qual_names import QN +from braket.experimental.autoqasm.autograph.pyct.qual_names import resolve +from tensorflow.python.platform import test + + +class QNTest(test.TestCase): + + def test_from_str(self): + a = QN('a') + b = QN('b') + a_dot_b = QN(a, attr='b') + a_sub_b = QN(a, subscript=b) + self.assertEqual(qual_names.from_str('a.b'), a_dot_b) + self.assertEqual(qual_names.from_str('a'), a) + self.assertEqual(qual_names.from_str('a[b]'), a_sub_b) + + def test_basic(self): + a = QN('a') + self.assertEqual(a.qn, ('a',)) + self.assertEqual(str(a), 'a') + self.assertEqual(a.ssf(), 'a') + self.assertEqual(a.ast().id, 'a') + self.assertFalse(a.is_composite()) + with self.assertRaises(ValueError): + _ = a.parent + + a_b = QN(a, attr='b') + self.assertEqual(a_b.qn, (a, 'b')) + self.assertEqual(str(a_b), 'a.b') + self.assertEqual(a_b.ssf(), 'a_b') + self.assertEqual(a_b.ast().value.id, 'a') + self.assertEqual(a_b.ast().attr, 'b') + self.assertTrue(a_b.is_composite()) + self.assertEqual(a_b.parent.qn, ('a',)) + + def test_subscripts(self): + a = QN('a') + b = QN('b') + a_sub_b = QN(a, subscript=b) + self.assertEqual(a_sub_b.qn, (a, b)) + self.assertEqual(str(a_sub_b), 'a[b]') + self.assertEqual(a_sub_b.ssf(), 'a_sub_b') + self.assertEqual(a_sub_b.ast().value.id, 'a') + self.assertEqual(a_sub_b.ast().slice.id, 'b') + self.assertTrue(a_sub_b.is_composite()) + self.assertTrue(a_sub_b.has_subscript()) + self.assertEqual(a_sub_b.parent.qn, ('a',)) + + c = QN('c') + b_sub_c = QN(b, subscript=c) + a_sub_b_sub_c = QN(a, subscript=b_sub_c) + self.assertEqual(a_sub_b_sub_c.qn, (a, b_sub_c)) + self.assertTrue(a_sub_b_sub_c.is_composite()) + self.assertTrue(a_sub_b_sub_c.has_subscript()) + self.assertEqual(b_sub_c.qn, (b, c)) + self.assertEqual(str(a_sub_b_sub_c), 'a[b[c]]') + self.assertEqual(a_sub_b_sub_c.ssf(), 'a_sub_b_sub_c') + self.assertEqual(a_sub_b_sub_c.ast().value.id, 'a') + self.assertEqual(a_sub_b_sub_c.ast().slice.value.id, 'b') + self.assertEqual(a_sub_b_sub_c.ast().slice.slice.id, 'c') + self.assertEqual(b_sub_c.ast().slice.id, 'c') + self.assertEqual(a_sub_b_sub_c.parent.qn, ('a',)) + with self.assertRaises(ValueError): + QN('a', 'b') + + def test_equality(self): + a = QN('a') + a2 = QN('a') + a_b = QN(a, attr='b') + self.assertEqual(a2.qn, ('a',)) + with self.assertRaises(ValueError): + _ = a.parent + + a_b2 = QN(a, attr='b') + self.assertEqual(a_b2.qn, (a, 'b')) + self.assertEqual(a_b2.parent.qn, ('a',)) + + self.assertTrue(a2 == a) + self.assertFalse(a2 is a) + + self.assertTrue(a_b.parent == a) + self.assertTrue(a_b2.parent == a) + + self.assertTrue(a_b2 == a_b) + self.assertFalse(a_b2 is a_b) + self.assertFalse(a_b2 == a) + a_sub_b = QN(a, subscript='b') + a_sub_b2 = QN(a, subscript='b') + self.assertTrue(a_sub_b == a_sub_b2) + self.assertFalse(a_sub_b == a_b) + + def test_nested_attrs_subscripts(self): + a = QN('a') + b = QN('b') + c = QN('c') + b_sub_c = QN(b, subscript=c) + a_sub_b_sub_c = QN(a, subscript=b_sub_c) + + b_dot_c = QN(b, attr='c') + a_sub__b_dot_c = QN(a, subscript=b_dot_c) + + a_sub_b = QN(a, subscript=b) + a_sub_b__dot_c = QN(a_sub_b, attr='c') + + a_dot_b = QN(a, attr='b') + a_dot_b_sub_c = QN(a_dot_b, subscript=c) + + self.assertEqual(str(a_sub_b_sub_c), 'a[b[c]]') + self.assertEqual(str(a_sub__b_dot_c), 'a[b.c]') + self.assertEqual(str(a_sub_b__dot_c), 'a[b].c') + self.assertEqual(str(a_dot_b_sub_c), 'a.b[c]') + + self.assertNotEqual(a_sub_b_sub_c, a_sub__b_dot_c) + self.assertNotEqual(a_sub_b_sub_c, a_sub_b__dot_c) + self.assertNotEqual(a_sub_b_sub_c, a_dot_b_sub_c) + + self.assertNotEqual(a_sub__b_dot_c, a_sub_b__dot_c) + self.assertNotEqual(a_sub__b_dot_c, a_dot_b_sub_c) + + self.assertNotEqual(a_sub_b__dot_c, a_dot_b_sub_c) + + def test_hashable(self): + d = {QN('a'): 'a', QN('b'): 'b'} + self.assertEqual(d[QN('a')], 'a') + self.assertEqual(d[QN('b')], 'b') + self.assertNotIn(QN('c'), d) + + def test_literals(self): + a = QN('a') + a_sub_str_b = QN(a, subscript=QN(qual_names.Literal('b'))) + a_sub_b = QN(a, subscript=QN('b')) + + self.assertNotEqual(a_sub_str_b, a_sub_b) + self.assertNotEqual(hash(a_sub_str_b), hash(a_sub_b)) + self.assertEqual(a_sub_str_b.ast().slice.value, 'b') + self.assertEqual(str(a_sub_str_b), "a['b']") + + a_sub_three = QN(a, subscript=QN(qual_names.Literal(3))) + self.assertEqual(a_sub_three.ast().slice.value, 3) + self.assertEqual(str(a_sub_three), 'a[3]') + + def test_support_set(self): + a = QN('a') + b = QN('b') + c = QN('c') + a_sub_b = QN(a, subscript=b) + a_dot_b = QN(a, attr='b') + a_dot_b_dot_c = QN(a_dot_b, attr='c') + a_dot_b_sub_c = QN(a_dot_b, subscript=c) + + self.assertSetEqual(a.support_set, set((a,))) + self.assertSetEqual(a_sub_b.support_set, set((a, b))) + self.assertSetEqual(a_dot_b.support_set, set((a,))) + self.assertSetEqual(a_dot_b_dot_c.support_set, set((a,))) + self.assertSetEqual(a_dot_b_sub_c.support_set, set((a, c))) + + def test_comparison(self): + less_than_apos = chr(ord('\'') - 1) + + self.assertGreater(QN('z'), QN(qual_names.Literal('a'))) + self.assertLess(QN(less_than_apos), QN(qual_names.Literal('a'))) + + self.assertGreater(QN(qual_names.Literal('z')), QN(less_than_apos)) + self.assertLess(QN(qual_names.Literal('a')), QN('z')) + + +class QNResolverTest(test.TestCase): + + def assertQNStringIs(self, node, qn_str): + self.assertEqual(str(anno.getanno(node, anno.Basic.QN)), qn_str) + + def test_resolve(self): + samples = """ + a + a.b + (c, d.e) + [f, (g.h.i)] + j(k, l) + """ + nodes = parser.parse(textwrap.dedent(samples), single_node=False) + nodes = tuple(resolve(node).value for node in nodes) + + self.assertQNStringIs(nodes[0], 'a') + self.assertQNStringIs(nodes[1], 'a.b') + self.assertQNStringIs(nodes[2].elts[0], 'c') + self.assertQNStringIs(nodes[2].elts[1], 'd.e') + self.assertQNStringIs(nodes[3].elts[0], 'f') + self.assertQNStringIs(nodes[3].elts[1], 'g.h.i') + self.assertQNStringIs(nodes[4].func, 'j') + self.assertQNStringIs(nodes[4].args[0], 'k') + self.assertQNStringIs(nodes[4].args[1], 'l') + + def test_subscript_resolve(self): + samples = """ + x[i] + x[i.b] + a.b[c] + a.b[x.y] + a[z[c]] + a[b[c[d]]] + a[b].c + a.b.c[d].e.f + a.b[c[d]].e.f + a.b[c[d.e.f].g].h + """ + nodes = parser.parse(textwrap.dedent(samples), single_node=False) + nodes = tuple(resolve(node).value for node in nodes) + + self.assertQNStringIs(nodes[0], 'x[i]') + self.assertQNStringIs(nodes[1], 'x[i.b]') + self.assertQNStringIs(nodes[2], 'a.b[c]') + self.assertQNStringIs(nodes[3], 'a.b[x.y]') + self.assertQNStringIs(nodes[4], 'a[z[c]]') + self.assertQNStringIs(nodes[5], 'a[b[c[d]]]') + self.assertQNStringIs(nodes[6], 'a[b].c') + self.assertQNStringIs(nodes[7], 'a.b.c[d].e.f') + self.assertQNStringIs(nodes[8], 'a.b[c[d]].e.f') + self.assertQNStringIs(nodes[9], 'a.b[c[d.e.f].g].h') + + def test_function_calls(self): + samples = """ + a.b + a.b() + a().b + z[i] + z[i]() + z()[i] + """ + nodes = parser.parse(textwrap.dedent(samples), single_node=False) + nodes = tuple(resolve(node).value for node in nodes) + self.assertQNStringIs(nodes[0], 'a.b') + self.assertQNStringIs(nodes[1].func, 'a.b') + self.assertQNStringIs(nodes[2].value.func, 'a') + self.assertQNStringIs(nodes[3], 'z[i]') + self.assertQNStringIs(nodes[4].func, 'z[i]') + self.assertQNStringIs(nodes[5].value.func, 'z') + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/__init__.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/__init__.py new file mode 100644 index 00000000..9ceaeaa8 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/__init__.py @@ -0,0 +1,28 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Static information resolution. + +This module contains utilities to help annotate AST nodes with as much runtime +information as can be possibly extracted without actually executing the code, +under that assumption that the context in which the code will run is known. + +Overall, the different analyses have the functions listed below: + + * activity: inventories symbols read, written to, params, etc. at different + levels + * liveness, reaching_definitions: dataflow analyses based on the program's CFG + and using the symbol information gathered by activity analysis +""" + diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/activity.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/activity.py new file mode 100644 index 00000000..81ca5152 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/activity.py @@ -0,0 +1,706 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Activity analysis. + +Requires qualified name annotations (see qual_names.py). +""" + +import copy +import weakref + +import gast + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import qual_names +from braket.experimental.autoqasm.autograph.pyct import transformer +from braket.experimental.autoqasm.autograph.pyct.static_analysis.annos import NodeAnno + + +class Scope(object): + """Encloses local symbol definition and usage information. + + This can track for instance whether a symbol is modified in the current scope. + Note that scopes do not necessarily align with Python's scopes. For example, + the body of an if statement may be considered a separate scope. + + Caution - the AST references held by this object are weak. + + Scope objects are mutable during construction only, and must be frozen using + `Scope.finalize()` before use. Furthermore, a scope is consistent only after + all its children have been frozen. While analysing code blocks, scopes are + being gradually built, from the innermost scope outward. Freezing indicates + that the analysis of a code block is complete. Once frozen, mutation is no + longer allowed. `is_final` tracks whether the scope is frozen or not. Certain + properties, like `referenced`, are only accurate when called on frozen scopes. + + Attributes: + parent: Optional[Scope], the parent scope, if any. + isolated: bool, whether the scope is a true Python scope (e.g. the scope of + a function), or just a surrogate tracking an ordinary code block. Using + the terminology of the Python 3 reference documentation, True roughly + represents an actual scope, whereas False represents an ordinary code + block. + function_name: Optional[str], name of the function owning this scope. + isolated_names: Set[qual_names.QN], identifiers that are isolated to this + scope (even if the scope is not isolated). + annotations: Set[qual_names.QN], identifiers used as type annotations + in this scope. + read: Set[qual_names.QN], identifiers read in this scope. + modified: Set[qual_names.QN], identifiers modified in this scope. + deleted: Set[qual_names.QN], identifiers deleted in this scope. + bound: Set[qual_names.QN], names that are bound to this scope. See + https://docs.python.org/3/reference/executionmodel.html#binding-of-names + for a precise definition. + globals: Set[qual_names.QN], names that are explicitly marked as global in + this scope. Note that this doesn't include free read-only vars bound to + global symbols. + nonlocals: Set[qual_names.QN], names that are explicitly marked as nonlocal + in this scope. Note that this doesn't include free read-only vars bound to + global symbols. + free_vars: Set[qual_names.QN], the free variables in this scope. See + https://docs.python.org/3/reference/executionmodel.html for a precise + definition. + params: WeakValueDictionary[qual_names.QN, ast.Node], function arguments + visible in this scope, mapped to the function node that defines them. + enclosing_scope: Scope, the innermost isolated scope that is a transitive + parent of this scope. May be the scope itself. + referenced: Set[qual_names.QN], the totality of the symbols used by this + scope and its parents. + is_final: bool, whether the scope is frozen or not. + + Note - simple statements may never delete and modify a symbol at the same + time. However, compound ones like if statements can. In that latter case, it's + undefined whether the symbol is actually modified or deleted upon statement + exit. Certain analyses like reaching definitions need to be careful about + this. + """ + + # Note: this mutable-immutable pattern is used because using a builder would + # have taken a lot more boilerplate. + + def __init__(self, parent, isolated=True, function_name=None): + """Create a new scope. + + Args: + parent: A Scope or None. + isolated: Whether the scope is isolated, that is, whether variables + modified in this scope should be considered modified in the parent + scope. + function_name: Name of the function owning this scope. + """ + self.parent = parent + self.isolated = isolated + self.function_name = function_name + + self.isolated_names = set() + + self.read = set() + self.modified = set() + self.deleted = set() + + self.bound = set() + self.globals = set() + self.nonlocals = set() + self.annotations = set() + + self.params = weakref.WeakValueDictionary() + + # Certain fields can only be accessed after the scope and all its parent + # scopes have been fully built. This field guards that. + self.is_final = False + + @property + def enclosing_scope(self): + assert self.is_final + if self.parent is not None and not self.isolated: + return self.parent + return self + + @property + def referenced(self): + if self.parent is not None: + return self.read | self.parent.referenced + return self.read + + @property + def free_vars(self): + enclosing_scope = self.enclosing_scope + return enclosing_scope.read - enclosing_scope.bound + + def copy_from(self, other): + """Recursively copies the contents of this scope from another scope.""" + assert not self.is_final + if self.parent is not None: + assert other.parent is not None + self.parent.copy_from(other.parent) + self.isolated_names = copy.copy(other.isolated_names) + self.modified = copy.copy(other.modified) + self.read = copy.copy(other.read) + self.deleted = copy.copy(other.deleted) + self.bound = copy.copy(other.bound) + self.annotations = copy.copy(other.annotations) + self.params = copy.copy(other.params) + + @classmethod + def copy_of(cls, other): + if other.parent is not None: + assert other.parent is not None + parent = cls.copy_of(other.parent) + else: + parent = None + new_copy = cls(parent) + new_copy.copy_from(other) + return new_copy + + def merge_from(self, other): + """Adds all activity from another scope to this scope.""" + assert not self.is_final + if self.parent is not None: + assert other.parent is not None + self.parent.merge_from(other.parent) + self.isolated_names.update(other.isolated_names) + self.read.update(other.read) + self.modified.update(other.modified) + self.bound.update(other.bound) + self.deleted.update(other.deleted) + self.annotations.update(other.annotations) + self.params.update(other.params) + + def finalize(self): + """Freezes this scope.""" + assert not self.is_final + # TODO(mdan): freeze read, modified, bound. + if self.parent is not None: + assert not self.parent.is_final + if not self.isolated: + self.parent.read.update(self.read - self.isolated_names) + self.parent.modified.update(self.modified - self.isolated_names) + self.parent.bound.update(self.bound - self.isolated_names) + self.parent.globals.update(self.globals) + self.parent.nonlocals.update(self.nonlocals) + self.parent.annotations.update(self.annotations) + else: + # TODO(mdan): This is not accurate. + self.parent.read.update(self.read - self.bound) + self.parent.annotations.update(self.annotations - self.bound) + self.is_final = True + + def __repr__(self): + return 'Scope{r=%s, w=%s}' % (tuple(self.read), tuple(self.modified)) + + def mark_param(self, name, owner): + # Assumption: all AST nodes have the same life span. This lets us use + # a weak reference to mark the connection between a symbol node and the + # function node whose argument that symbol is. + self.params[name] = owner + + +class _Comprehension(object): + + no_root = True + + def __init__(self): + # TODO(mdan): Consider using an enum. + self.is_list_comp = False + self.targets = set() + + +class _FunctionOrClass(object): + + def __init__(self): + self.node = None + + +class ActivityAnalyzer(transformer.Base): + """Annotates nodes with local scope information. + + See Scope. + + The use of this class requires that qual_names.resolve() has been called on + the node. This class will ignore nodes have not been + annotated with their qualified names. + """ + + def __init__(self, context, parent_scope=None): + super(ActivityAnalyzer, self).__init__(context) + self.allow_skips = False + self.scope = Scope(parent_scope, isolated=True) + + # Note: all these flags crucially rely on the respective nodes are + # leaves in the AST, that is, they cannot contain other statements. + self._in_aug_assign = False + self._in_annotation = False + self._track_annotations_only = False + + @property + def _in_constructor(self): + context = self.state[_FunctionOrClass] + if context.level > 2: + innermost = context.stack[-1].node + parent = context.stack[-2].node + return (isinstance(parent, gast.ClassDef) and + (isinstance(innermost, gast.FunctionDef) and + innermost.name == '__init__')) + return False + + def _node_sets_self_attribute(self, node): + if anno.hasanno(node, anno.Basic.QN): + qn = anno.getanno(node, anno.Basic.QN) + # TODO(mdan): The 'self' argument is not guaranteed to be called 'self'. + if qn.has_attr and qn.parent.qn == ('self',): + return True + return False + + def _track_symbol(self, node, composite_writes_alter_parent=False): + if self._track_annotations_only and not self._in_annotation: + return + + # A QN may be missing when we have an attribute (or subscript) on a function + # call. Example: a().b + if not anno.hasanno(node, anno.Basic.QN): + return + qn = anno.getanno(node, anno.Basic.QN) + + # When inside a comprehension, ignore reads to any of the comprehensions's + # targets. This includes attributes or slices of those arguments. + for l in self.state[_Comprehension]: + if qn in l.targets: + return + if qn.owner_set & set(l.targets): + return + + if isinstance(node.ctx, gast.Store): + # In comprehensions, modified symbols are the comprehension targets. + if self.state[_Comprehension].level > 0: + self.state[_Comprehension].targets.add(qn) + return + + self.scope.modified.add(qn) + self.scope.bound.add(qn) + if qn.is_composite and composite_writes_alter_parent: + self.scope.modified.add(qn.parent) + if self._in_aug_assign: + self.scope.read.add(qn) + + elif isinstance(node.ctx, gast.Load): + self.scope.read.add(qn) + if self._in_annotation: + self.scope.annotations.add(qn) + + elif isinstance(node.ctx, gast.Param): + self.scope.bound.add(qn) + self.scope.mark_param(qn, self.state[_FunctionOrClass].node) + + elif isinstance(node.ctx, gast.Del): + # The read matches the Python semantics - attempting to delete an + # undefined symbol is illegal. + self.scope.read.add(qn) + # Targets of del are considered bound: + # https://docs.python.org/3/reference/executionmodel.html#binding-of-names + self.scope.bound.add(qn) + self.scope.deleted.add(qn) + + else: + raise ValueError('Unknown context {} for node "{}".'.format( + type(node.ctx), qn)) + + def _enter_scope(self, isolated, f_name=None): + self.scope = Scope(self.scope, isolated=isolated, function_name=f_name) + + def _exit_scope(self): + exited_scope = self.scope + exited_scope.finalize() + self.scope = exited_scope.parent + return exited_scope + + def _exit_and_record_scope(self, node, tag=anno.Static.SCOPE): + node_scope = self._exit_scope() + anno.setanno(node, tag, node_scope) + return node_scope + + def _process_statement(self, node): + self._enter_scope(False) + node = self.generic_visit(node) + self._exit_and_record_scope(node) + return node + + def _process_annotation(self, node): + self._in_annotation = True + node = self.visit(node) + self._in_annotation = False + return node + + def visit_Import(self, node): + return self._process_statement(node) + + def visit_ImportFrom(self, node): + return self._process_statement(node) + + def visit_Global(self, node): + self._enter_scope(False) + for name in node.names: + qn = qual_names.QN(name) + self.scope.read.add(qn) + self.scope.globals.add(qn) + self._exit_and_record_scope(node) + return node + + def visit_Nonlocal(self, node): + self._enter_scope(False) + for name in node.names: + qn = qual_names.QN(name) + self.scope.read.add(qn) + self.scope.bound.add(qn) + self.scope.nonlocals.add(qn) + self._exit_and_record_scope(node) + return node + + def visit_Expr(self, node): + return self._process_statement(node) + + def visit_Raise(self, node): + return self._process_statement(node) + + def visit_Return(self, node): + return self._process_statement(node) + + def visit_Assign(self, node): + return self._process_statement(node) + + def visit_AnnAssign(self, node): + self._enter_scope(False) + node.target = self.visit(node.target) + if node.value is not None: + # Can be None for pure declarations, e.g. `n: int`. This is a new thing + # enabled by type annotations, but does not influence static analysis + # (declarations are not definitions). + node.value = self.visit(node.value) + if node.annotation: + node.annotation = self._process_annotation(node.annotation) + self._exit_and_record_scope(node) + return node + + def visit_AugAssign(self, node): + # Special rules for AugAssign. Here, the AST only shows the target as + # written, when it is in fact also read. + self._enter_scope(False) + + self._in_aug_assign = True + node.target = self.visit(node.target) + self._in_aug_assign = False + + node.op = self.visit(node.op) + node.value = self.visit(node.value) + self._exit_and_record_scope(node) + return node + + def visit_Delete(self, node): + return self._process_statement(node) + + def visit_Name(self, node): + if node.annotation: + node.annotation = self._process_annotation(node.annotation) + self._track_symbol(node) + return node + + def visit_alias(self, node): + node = self.generic_visit(node) + + if node.asname is None: + # Only the root name is a real symbol operation. + qn = qual_names.QN(node.name.split('.')[0]) + else: + qn = qual_names.QN(node.asname) + + self.scope.modified.add(qn) + self.scope.bound.add(qn) + return node + + def visit_Attribute(self, node): + node = self.generic_visit(node) + if self._in_constructor and self._node_sets_self_attribute(node): + self._track_symbol(node, composite_writes_alter_parent=True) + else: + self._track_symbol(node) + return node + + def visit_Subscript(self, node): + node = self.generic_visit(node) + # Subscript writes (e.g. a[b] = "value") are considered to modify + # both the element itself (a[b]) and its parent (a). + self._track_symbol(node) + return node + + def visit_Print(self, node): + self._enter_scope(False) + node.values = self.visit_block(node.values) + node_scope = self._exit_and_record_scope(node) + anno.setanno(node, NodeAnno.ARGS_SCOPE, node_scope) + return node + + def visit_Assert(self, node): + return self._process_statement(node) + + def visit_Call(self, node): + self._enter_scope(False) + node.args = self.visit_block(node.args) + node.keywords = self.visit_block(node.keywords) + # TODO(mdan): Account starargs, kwargs + self._exit_and_record_scope(node, tag=NodeAnno.ARGS_SCOPE) + + node.func = self.visit(node.func) + return node + + def _process_block_node(self, node, block, scope_name): + self._enter_scope(False) + block = self.visit_block(block) + self._exit_and_record_scope(node, tag=scope_name) + return node + + def _process_parallel_blocks(self, parent, children): + # Because the scopes are not isolated, processing any child block + # modifies the parent state causing the other child blocks to be + # processed incorrectly. So we need to checkpoint the parent scope so that + # each child sees the same context. + before_parent = Scope.copy_of(self.scope) + after_children = [] + for child, scope_name in children: + self.scope.copy_from(before_parent) + parent = self._process_block_node(parent, child, scope_name) + after_child = Scope.copy_of(self.scope) + after_children.append(after_child) + for after_child in after_children: + self.scope.merge_from(after_child) + return parent + + def _process_comprehension(self, + node, + is_list_comp=False, + is_dict_comp=False): + with self.state[_Comprehension] as comprehension_: + comprehension_.is_list_comp = is_list_comp + # Note: it's important to visit the generators first to properly account + # for the variables local to these generators. Example: `x` is local to + # the expression `z for x in y for z in x`. + node.generators = self.visit_block(node.generators) + if is_dict_comp: + node.key = self.visit(node.key) + node.value = self.visit(node.value) + else: + node.elt = self.visit(node.elt) + return node + + def visit_comprehension(self, node): + # It is important to visit children in this order so that the reads to + # the target name are appropriately ignored. + node.iter = self.visit(node.iter) + node.target = self.visit(node.target) + return self.generic_visit(node) + + def visit_DictComp(self, node): + return self._process_comprehension(node, is_dict_comp=True) + + def visit_ListComp(self, node): + return self._process_comprehension(node, is_list_comp=True) + + def visit_SetComp(self, node): + return self._process_comprehension(node) + + def visit_GeneratorExp(self, node): + return self._process_comprehension(node) + + def visit_ClassDef(self, node): + with self.state[_FunctionOrClass] as fn: + fn.node = node + # The ClassDef node itself has a Scope object that tracks the creation + # of its name, along with the usage of any decorator accompanying it. + self._enter_scope(False) + node.decorator_list = self.visit_block(node.decorator_list) + self.scope.modified.add(qual_names.QN(node.name)) + self.scope.bound.add(qual_names.QN(node.name)) + node.bases = self.visit_block(node.bases) + node.keywords = self.visit_block(node.keywords) + self._exit_and_record_scope(node) + + # A separate Scope tracks the actual class definition. + self._enter_scope(True) + node = self.generic_visit(node) + self._exit_scope() + return node + + def _visit_node_list(self, nodes): + return [(None if n is None else self.visit(n)) for n in nodes] + + def _visit_arg_annotations(self, node): + node.args.kw_defaults = self._visit_node_list(node.args.kw_defaults) + node.args.defaults = self._visit_node_list(node.args.defaults) + self._track_annotations_only = True + node = self._visit_arg_declarations(node) + self._track_annotations_only = False + return node + + def _visit_arg_declarations(self, node): + node.args.posonlyargs = self._visit_node_list(node.args.posonlyargs) + node.args.args = self._visit_node_list(node.args.args) + if node.args.vararg is not None: + node.args.vararg = self.visit(node.args.vararg) + node.args.kwonlyargs = self._visit_node_list(node.args.kwonlyargs) + if node.args.kwarg is not None: + node.args.kwarg = self.visit(node.args.kwarg) + return node + + def visit_FunctionDef(self, node): + with self.state[_FunctionOrClass] as fn: + fn.node = node + # The FunctionDef node itself has a Scope object that tracks the creation + # of its name, along with the usage of any decorator accompanying it. + self._enter_scope(False) + node.decorator_list = self.visit_block(node.decorator_list) + if node.returns: + node.returns = self._process_annotation(node.returns) + # Argument annotartions (includeing defaults) affect the defining context. + node = self._visit_arg_annotations(node) + + function_name = qual_names.QN(node.name) + self.scope.modified.add(function_name) + self.scope.bound.add(function_name) + self._exit_and_record_scope(node) + + # A separate Scope tracks the actual function definition. + self._enter_scope(True, node.name) + + # Keep a separate scope for the arguments node, which is used in the CFG. + self._enter_scope(False, node.name) + + # Arg declarations only affect the function itself, and have no effect + # in the defining context whatsoever. + node = self._visit_arg_declarations(node) + + self._exit_and_record_scope(node.args) + + # Track the body separately. This is for compatibility reasons, it may not + # be strictly needed. + self._enter_scope(False, node.name) + node.body = self.visit_block(node.body) + self._exit_and_record_scope(node, NodeAnno.BODY_SCOPE) + + self._exit_and_record_scope(node, NodeAnno.ARGS_AND_BODY_SCOPE) + return node + + def visit_Lambda(self, node): + # Lambda nodes are treated in roughly the same way as FunctionDef nodes. + with self.state[_FunctionOrClass] as fn: + fn.node = node + # The Lambda node itself has a Scope object that tracks the creation + # of its name, along with the usage of any decorator accompanying it. + self._enter_scope(False) + node = self._visit_arg_annotations(node) + self._exit_and_record_scope(node) + + # A separate Scope tracks the actual function definition. + self._enter_scope(True) + + # Keep a separate scope for the arguments node, which is used in the CFG. + self._enter_scope(False) + node = self._visit_arg_declarations(node) + self._exit_and_record_scope(node.args) + + # Track the body separately. This is for compatibility reasons, it may not + # be strictly needed. + # TODO(mdan): Do remove it, it's confusing. + self._enter_scope(False) + node.body = self.visit(node.body) + + # The lambda body can contain nodes of types normally not found as + # statements, and may not have the SCOPE annotation needed by the CFG. + # So we attach one if necessary. + if not anno.hasanno(node.body, anno.Static.SCOPE): + anno.setanno(node.body, anno.Static.SCOPE, self.scope) + + self._exit_and_record_scope(node, NodeAnno.BODY_SCOPE) + + lambda_scope = self.scope + self._exit_and_record_scope(node, NodeAnno.ARGS_AND_BODY_SCOPE) + + # TODO(bhack:) https://github.com/tensorflow/tensorflow/issues/56089 + # remove after deprecation + # Exception: lambdas are assumed to be used in the place where + # they are defined. Therefore, their activity is passed on to the + # calling statement. + self.scope.read.update(lambda_scope.read - lambda_scope.bound) + + return node + + def visit_With(self, node): + self._enter_scope(False) + node = self.generic_visit(node) + self._exit_and_record_scope(node, NodeAnno.BODY_SCOPE) + return node + + def visit_withitem(self, node): + return self._process_statement(node) + + def visit_If(self, node): + self._enter_scope(False) + node.test = self.visit(node.test) + node_scope = self._exit_and_record_scope(node.test) + anno.setanno(node, NodeAnno.COND_SCOPE, node_scope) + + node = self._process_parallel_blocks(node, + ((node.body, NodeAnno.BODY_SCOPE), + (node.orelse, NodeAnno.ORELSE_SCOPE))) + return node + + def visit_For(self, node): + self._enter_scope(False) + node.target = self.visit(node.target) + node.iter = self.visit(node.iter) + self._exit_and_record_scope(node.iter) + + self._enter_scope(False) + self.visit(node.target) + if anno.hasanno(node, anno.Basic.EXTRA_LOOP_TEST): + self._process_statement(anno.getanno(node, anno.Basic.EXTRA_LOOP_TEST)) + self._exit_and_record_scope(node, tag=NodeAnno.ITERATE_SCOPE) + + node = self._process_parallel_blocks(node, + ((node.body, NodeAnno.BODY_SCOPE), + (node.orelse, NodeAnno.ORELSE_SCOPE))) + return node + + def visit_While(self, node): + self._enter_scope(False) + node.test = self.visit(node.test) + node_scope = self._exit_and_record_scope(node.test) + anno.setanno(node, NodeAnno.COND_SCOPE, node_scope) + + node = self._process_parallel_blocks(node, + ((node.body, NodeAnno.BODY_SCOPE), + (node.orelse, NodeAnno.ORELSE_SCOPE))) + return node + + def visit_ExceptHandler(self, node): + self._enter_scope(False) + # try/except oddity: as expected, it leaks any names you defined inside the + # except block, but not the name of the exception variable. + if node.name is not None: + self.scope.isolated_names.add(anno.getanno(node.name, anno.Basic.QN)) + node = self.generic_visit(node) + self._exit_scope() + return node + + +def resolve(node, context, parent_scope=None): + return ActivityAnalyzer(context, parent_scope).visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/activity_test.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/activity_test.py new file mode 100644 index 00000000..3b80c836 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/activity_test.py @@ -0,0 +1,868 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for activity module.""" + +import gast + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import naming +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import qual_names +from braket.experimental.autoqasm.autograph.pyct import transformer +from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity +from braket.experimental.autoqasm.autograph.pyct.static_analysis import annos +from tensorflow.python.platform import test + + +QN = qual_names.QN +NodeAnno = annos.NodeAnno + +global_a = 7 +global_b = 17 + + +class ScopeTest(test.TestCase): + + def assertMissing(self, qn, scope): + self.assertNotIn(qn, scope.read) + self.assertNotIn(qn, scope.modified) + + def assertReadOnly(self, qn, scope): + self.assertIn(qn, scope.read) + self.assertNotIn(qn, scope.modified) + + def assertWriteOnly(self, qn, scope): + self.assertNotIn(qn, scope.read) + self.assertIn(qn, scope.modified) + + def assertReadWrite(self, qn, scope): + self.assertIn(qn, scope.read) + self.assertIn(qn, scope.modified) + + def test_copy_from(self): + scope = activity.Scope(None) + scope.modified.add(QN('foo')) + other = activity.Scope(None) + other.copy_from(scope) + + self.assertWriteOnly(QN('foo'), other) + + scope.modified.add(QN('bar')) + scope.copy_from(other) + + self.assertMissing(QN('bar'), scope) + + def test_merge_from(self): + scope = activity.Scope(None) + other = activity.Scope(None) + + for col in (scope.modified, scope.read, scope.bound, scope.deleted): + col.add(QN('foo')) + + for col in (other.modified, other.read, other.bound, other.deleted): + col.add(QN('foo')) + col.add(QN('bar')) + + scope.merge_from(other) + + self.assertReadWrite(QN('foo'), scope) + self.assertReadWrite(QN('bar'), scope) + self.assertIn(QN('foo'), scope.bound) + self.assertIn(QN('bar'), scope.bound) + self.assertIn(QN('foo'), scope.deleted) + self.assertIn(QN('bar'), scope.deleted) + + def test_copy_of(self): + scope = activity.Scope(None) + scope.read.add(QN('foo')) + other = activity.Scope.copy_of(scope) + + self.assertReadOnly(QN('foo'), other) + + child_scope = activity.Scope(scope) + child_scope.read.add(QN('bar')) + other = activity.Scope.copy_of(child_scope) + + self.assertReadOnly(QN('bar'), other) + + def test_referenced(self): + scope = activity.Scope(None) + scope.read.add(QN('a')) + + child = activity.Scope(scope) + child.read.add(QN('b')) + + child2 = activity.Scope(child, isolated=False) + child2.read.add(QN('c')) + + child2.finalize() + child.finalize() + scope.finalize() + + self.assertIn(QN('c'), child2.referenced) + self.assertIn(QN('b'), child2.referenced) + self.assertIn(QN('a'), child2.referenced) + + self.assertIn(QN('c'), child.referenced) + self.assertIn(QN('b'), child.referenced) + self.assertIn(QN('a'), child.referenced) + + +class ActivityAnalyzerTestBase(test.TestCase): + + def _parse_and_analyze(self, test_fn): + # TODO(mdan): Use a custom FunctionTransformer here. + node, source = parser.parse_entity(test_fn, future_features=()) + entity_info = transformer.EntityInfo( + name=test_fn.__name__, + source_code=source, + source_file=None, + future_features=(), + namespace={}) + node = qual_names.resolve(node) + namer = naming.Namer({}) + ctx = transformer.Context(entity_info, namer, None) + node = activity.resolve(node, ctx) + return node, entity_info + + def assertSymbolSetsAre(self, expected, actual, name): + expected = set(expected) + actual = set(str(s) for s in actual) + self.assertSetEqual( + expected, actual, 'for symbol set: %s\n' + ' Expected: %s\n' + ' Got: %s\n' + ' Missing: %s\n' + ' Extra: %s\n' % (name.upper(), expected, actual, + expected - actual, actual - expected)) + + def assertScopeIs(self, scope, used, modified): + """Assert the scope contains specific used, modified & created variables.""" + self.assertSymbolSetsAre(used, scope.read, 'read') + self.assertSymbolSetsAre(modified, scope.modified, 'modified') + + +class ActivityAnalyzerTest(ActivityAnalyzerTestBase): + + def test_import(self): + + def test_fn(): + import a, b.x, y as c, z.u as d # pylint:disable=g-multiple-import,g-import-not-at-top,unused-variable + + node, _ = self._parse_and_analyze(test_fn) + scope = anno.getanno(node.body[0], anno.Static.SCOPE) + self.assertScopeIs(scope, (), ('a', 'b', 'c', 'd')) + + def test_import_from(self): + + def test_fn(): + from x import a # pylint:disable=g-import-not-at-top,unused-variable + from y import z as b # pylint:disable=g-import-not-at-top,unused-variable + + node, _ = self._parse_and_analyze(test_fn) + scope = anno.getanno(node.body[0], anno.Static.SCOPE) + self.assertScopeIs(scope, (), ('a',)) + scope = anno.getanno(node.body[1], anno.Static.SCOPE) + self.assertScopeIs(scope, (), ('b',)) + + def test_print_statement(self): + + def test_fn(a): + b = 0 + c = 1 + print(a, b) + return c + + node, _ = self._parse_and_analyze(test_fn) + print_node = node.body[2] + if isinstance(print_node, gast.Print): + # Python 2 + print_args_scope = anno.getanno(print_node, NodeAnno.ARGS_SCOPE) + else: + # Python 3 + assert isinstance(print_node, gast.Expr) + # The call node should be the one being annotated. + print_node = print_node.value + print_args_scope = anno.getanno(print_node, NodeAnno.ARGS_SCOPE) + # We basically need to detect which variables are captured by the call + # arguments. + self.assertScopeIs(print_args_scope, ('a', 'b'), ()) + + def test_call_args(self): + + def test_fn(a): + b = 0 + c = 1 + foo(a, b) # pylint:disable=undefined-variable + return c + + node, _ = self._parse_and_analyze(test_fn) + call_node = node.body[2].value + # We basically need to detect which variables are captured by the call + # arguments. + self.assertScopeIs( + anno.getanno(call_node, NodeAnno.ARGS_SCOPE), ('a', 'b'), ()) + + def test_call_args_attributes(self): + + def foo(*_): + pass + + def test_fn(a): + a.c = 0 + foo(a.b, a.c) + return a.d + + node, _ = self._parse_and_analyze(test_fn) + call_node = node.body[1].value + self.assertScopeIs( + anno.getanno(call_node, NodeAnno.ARGS_SCOPE), ('a', 'a.b', 'a.c'), ()) + + def test_call_args_subscripts(self): + + def foo(*_): + pass + + def test_fn(a): + b = 1 + c = 2 + foo(a[0], a[b]) + return a[c] + + node, _ = self._parse_and_analyze(test_fn) + call_node = node.body[2].value + self.assertScopeIs( + anno.getanno(call_node, NodeAnno.ARGS_SCOPE), + ('a', 'a[0]', 'a[b]', 'b'), ()) + + def test_while(self): + + def test_fn(a): + b = a + while b > 0: + c = b + b -= 1 + return b, c + + node, _ = self._parse_and_analyze(test_fn) + while_node = node.body[1] + self.assertScopeIs( + anno.getanno(while_node, NodeAnno.BODY_SCOPE), ('b',), ('b', 'c')) + self.assertScopeIs( + anno.getanno(while_node, NodeAnno.BODY_SCOPE).parent, ('a', 'b', 'c'), + ('b', 'c')) + self.assertScopeIs( + anno.getanno(while_node, NodeAnno.COND_SCOPE), ('b',), ()) + + def test_for(self): + + def test_fn(a): + b = a + for _ in a: + c = b + b -= 1 + return b, c + + node, _ = self._parse_and_analyze(test_fn) + for_node = node.body[1] + self.assertScopeIs( + anno.getanno(for_node, NodeAnno.ITERATE_SCOPE), (), ('_')) + self.assertScopeIs( + anno.getanno(for_node, NodeAnno.BODY_SCOPE), ('b',), ('b', 'c')) + self.assertScopeIs( + anno.getanno(for_node, NodeAnno.BODY_SCOPE).parent, ('a', 'b', 'c'), + ('b', 'c', '_')) + + def test_if(self): + + def test_fn(x): + if x > 0: + x = -x + y = 2 * x + z = -y + else: + x = 2 * x + y = -x + u = -y + return z, u + + node, _ = self._parse_and_analyze(test_fn) + if_node = node.body[0] + self.assertScopeIs( + anno.getanno(if_node, NodeAnno.BODY_SCOPE), ('x', 'y'), ('x', 'y', 'z')) + self.assertScopeIs( + anno.getanno(if_node, NodeAnno.BODY_SCOPE).parent, ('x', 'y', 'z', 'u'), + ('x', 'y', 'z', 'u')) + self.assertScopeIs( + anno.getanno(if_node, NodeAnno.ORELSE_SCOPE), ('x', 'y'), + ('x', 'y', 'u')) + self.assertScopeIs( + anno.getanno(if_node, NodeAnno.ORELSE_SCOPE).parent, + ('x', 'y', 'z', 'u'), ('x', 'y', 'z', 'u')) + + def test_if_attributes(self): + + def test_fn(a): + if a > 0: + a.b = -a.c + d = 2 * a + else: + a.b = a.c + d = 1 + return d + + node, _ = self._parse_and_analyze(test_fn) + if_node = node.body[0] + self.assertScopeIs( + anno.getanno(if_node, NodeAnno.BODY_SCOPE), ('a', 'a.c'), ('a.b', 'd')) + self.assertScopeIs( + anno.getanno(if_node, NodeAnno.ORELSE_SCOPE), ('a', 'a.c'), + ('a.b', 'd')) + self.assertScopeIs( + anno.getanno(if_node, NodeAnno.BODY_SCOPE).parent, ('a', 'a.c', 'd'), + ('a.b', 'd')) + + def test_if_subscripts(self): + + def test_fn(a, b, c, e): + if a > 0: + a[b] = -a[c] + d = 2 * a + else: + a[0] = e + d = 1 + return d + + node, _ = self._parse_and_analyze(test_fn) + if_node = node.body[0] + self.assertScopeIs( + anno.getanno(if_node, NodeAnno.BODY_SCOPE), ('a', 'b', 'c', 'a[c]'), + ('a[b]', 'd')) + # TODO(mdan): Should subscript writes (a[0] = 1) be considered to read "a"? + self.assertScopeIs( + anno.getanno(if_node, NodeAnno.ORELSE_SCOPE), ('a', 'e'), ('a[0]', 'd')) + self.assertScopeIs( + anno.getanno(if_node, NodeAnno.ORELSE_SCOPE).parent, + ('a', 'b', 'c', 'd', 'e', 'a[c]'), ('d', 'a[b]', 'a[0]')) + + def test_nested_if(self): + + def test_fn(b): + if b > 0: + if b < 5: + a = b + else: + a = b * b + return a + + node, _ = self._parse_and_analyze(test_fn) + inner_if_node = node.body[0].body[0] + self.assertScopeIs( + anno.getanno(inner_if_node, NodeAnno.BODY_SCOPE), ('b',), ('a',)) + self.assertScopeIs( + anno.getanno(inner_if_node, NodeAnno.ORELSE_SCOPE), ('b',), ('a',)) + + def test_nested_function(self): + + def test_fn(a): + + def f(x): + y = x * x + return y + + return f(a) + + node, _ = self._parse_and_analyze(test_fn) + + fn_node = node + scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(scope, ('a', 'f'), ('f',)) + + fn_def_node = node.body[0] + + scope = anno.getanno(fn_def_node, anno.Static.SCOPE) + self.assertScopeIs(scope, (), ('f')) + + scope = anno.getanno(fn_def_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(scope, ('x', 'y'), ('y',)) + + scope = anno.getanno(fn_def_node, NodeAnno.ARGS_AND_BODY_SCOPE) + self.assertScopeIs(scope, ('x', 'y'), ('y',)) + self.assertSymbolSetsAre(('x', 'y'), scope.bound, 'BOUND') + + def test_nested_lambda(self): + + def test_fn(a): + return lambda x: (x * a) + + node, _ = self._parse_and_analyze(test_fn) + + fn_node = node + scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(scope, ('a',), ()) + + return_node = node.body[0] + + scope = anno.getanno(return_node, anno.Static.SCOPE) + self.assertScopeIs(scope, ('a',), ()) + + lam_def_node = return_node.value + + scope = anno.getanno(lam_def_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(scope, ('a', 'x'), ()) + + scope = anno.getanno(lam_def_node, NodeAnno.ARGS_AND_BODY_SCOPE) + self.assertScopeIs(scope, ('a', 'x'), ()) + self.assertSymbolSetsAre(('x',), scope.bound, 'BOUND') + + def test_nested_function_arg_defaults(self): + + def test_fn(a): + + def f(x=a): + y = x * x + return y + + return f(a) + + node, _ = self._parse_and_analyze(test_fn) + fn_def_node = node.body[0] + + self.assertScopeIs( + anno.getanno(fn_def_node, anno.Static.SCOPE), ('a',), ('f',)) + + scope = anno.getanno(fn_def_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(scope, ('x', 'y'), ('y',)) + + scope = anno.getanno(fn_def_node, NodeAnno.ARGS_AND_BODY_SCOPE) + self.assertScopeIs(scope, ('x', 'y'), ('y',)) + self.assertSymbolSetsAre(('x', 'y'), scope.bound, 'BOUND') + + def test_constructor_attributes(self): + + class TestClass(object): + + def __init__(self, a): + self.b = a + self.b.c = 1 + + node, _ = self._parse_and_analyze(TestClass) + init_node = node.body[0] + self.assertScopeIs( + anno.getanno(init_node, NodeAnno.BODY_SCOPE), ('self', 'a', 'self.b'), + ('self', 'self.b', 'self.b.c')) + + def test_aug_assign_subscripts(self): + + def test_fn(a): + a[0] += 1 + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + self.assertScopeIs( + anno.getanno(fn_node, NodeAnno.BODY_SCOPE), ('a', 'a[0]'), ('a[0]',)) + + def test_return_vars_are_read(self): + + def test_fn(a, b, c): # pylint: disable=unused-argument + return c + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + self.assertScopeIs(anno.getanno(fn_node, NodeAnno.BODY_SCOPE), ('c',), ()) + self.assertScopeIs( + anno.getanno(node.body[0], anno.Static.SCOPE), ('c',), ()) + + def test_raise_names_are_read(self): + + def test_fn(a, b, c): # pylint: disable=unused-argument + raise b + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + self.assertScopeIs(anno.getanno(fn_node, NodeAnno.BODY_SCOPE), ('b',), ()) + self.assertScopeIs( + anno.getanno(node.body[0], anno.Static.SCOPE), ('b',), ()) + + def test_except_exposes_names(self): + + def test_fn(a, b, c): # pylint: disable=unused-argument + try: + pass + except: # pylint: disable=bare-except + b = c + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + self.assertScopeIs( + anno.getanno(fn_node, NodeAnno.BODY_SCOPE), ('c',), ('b',)) + + def test_except_hides_exception_var_name(self): + + def test_fn(a, b, c): # pylint: disable=unused-argument + try: + pass + except a as e: + b = e + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + self.assertScopeIs( + anno.getanno(fn_node, NodeAnno.BODY_SCOPE), ('a',), ('b',)) + + def test_aug_assign(self): + + def test_fn(a, b): + a += b + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + self.assertScopeIs( + anno.getanno(fn_node, NodeAnno.BODY_SCOPE), ('a', 'b'), ('a')) + + def test_aug_assign_rvalues(self): + + a = dict(bar=3) + + def foo(): + return a + + def test_fn(x): + foo()['bar'] += x + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + self.assertScopeIs( + anno.getanno(fn_node, NodeAnno.BODY_SCOPE), ('foo', 'x'), ()) + + def test_lambda(self): + + def test_fn(a, b): + return lambda: (a + b) + + node, _ = self._parse_and_analyze(test_fn) + + fn_node = node + scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(scope, ('a', 'b'), ()) + + lam_def_node = node.body[0].value + + scope = anno.getanno(lam_def_node, anno.Static.SCOPE) + self.assertScopeIs(scope, (), ()) + + scope = anno.getanno(lam_def_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(scope, ('a', 'b'), ()) + + scope = anno.getanno(lam_def_node, NodeAnno.ARGS_AND_BODY_SCOPE) + self.assertScopeIs(scope, ('a', 'b'), ()) + self.assertSymbolSetsAre((), scope.bound, 'BOUND') + + scope = anno.getanno(lam_def_node.args, anno.Static.SCOPE) + self.assertSymbolSetsAre((), scope.params.keys(), 'lambda params') + + def test_lambda_params_args(self): + + def test_fn(a, b): # pylint: disable=unused-argument + return lambda a: a + b + + node, _ = self._parse_and_analyze(test_fn) + + fn_node = node + scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + # Note: `a` in `a + b` is not "read" here because it's hidden by the `a` + # argument. + self.assertScopeIs(scope, ('b',), ()) + + lam_def_node = node.body[0].value + + scope = anno.getanno(lam_def_node, anno.Static.SCOPE) + self.assertScopeIs(scope, (), ()) + + scope = anno.getanno(lam_def_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(scope, ('a', 'b'), ()) + + scope = anno.getanno(lam_def_node, NodeAnno.ARGS_AND_BODY_SCOPE) + self.assertScopeIs(scope, ('a', 'b'), ()) + self.assertSymbolSetsAre(('a',), scope.bound, 'BOUND') + + scope = anno.getanno(lam_def_node.args, anno.Static.SCOPE) + self.assertSymbolSetsAre(('a',), scope.params.keys(), 'lambda params') + + def test_lambda_params_arg_defaults(self): + + def test_fn(a, b, c): # pylint: disable=unused-argument + return lambda b=c: a + b + + node, _ = self._parse_and_analyze(test_fn) + + fn_node = node + scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + # Note: `b` is not "read" here because it's hidden by the argument. + self.assertScopeIs(scope, ('a', 'c'), ()) + + lam_def_node = node.body[0].value + + scope = anno.getanno(lam_def_node, anno.Static.SCOPE) + self.assertScopeIs(scope, ('c',), ()) + + scope = anno.getanno(lam_def_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(scope, ('a', 'b'), ()) + + scope = anno.getanno(lam_def_node, NodeAnno.ARGS_AND_BODY_SCOPE) + self.assertScopeIs(scope, ('a', 'b'), ()) + self.assertSymbolSetsAre(('b',), scope.bound, 'BOUND') + + scope = anno.getanno(lam_def_node.args, anno.Static.SCOPE) + self.assertSymbolSetsAre(('b',), scope.params.keys(), 'lambda params') + + def test_lambda_complex(self): + + def test_fn(a, b, c, d, e): # pylint: disable=unused-argument + a = (lambda a, b, c=e: a + b + c)(d, 1, 2) + b + + node, _ = self._parse_and_analyze(test_fn) + + fn_node = node + scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(scope, ('d', 'b', 'e'), ('a',)) + + lam_def_node = node.body[0].value.left.func + + scope = anno.getanno(lam_def_node, anno.Static.SCOPE) + self.assertScopeIs(scope, ('e',), ()) + + scope = anno.getanno(lam_def_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(scope, ('a', 'b', 'c'), ()) + + scope = anno.getanno(lam_def_node, NodeAnno.ARGS_AND_BODY_SCOPE) + self.assertScopeIs(scope, ('a', 'b', 'c'), ()) + self.assertSymbolSetsAre(('a', 'b', 'c'), scope.bound, 'BOUND') + + scope = anno.getanno(lam_def_node.args, anno.Static.SCOPE) + self.assertSymbolSetsAre( + ('a', 'b', 'c'), scope.params.keys(), 'lambda params') + + def test_lambda_nested(self): + + def test_fn(a, b, c, d, e, f): # pylint: disable=unused-argument + a = lambda a, b: d(lambda b=f: a + b + c) # pylint: disable=undefined-variable + + node, _ = self._parse_and_analyze(test_fn) + + fn_node = node + scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(scope, ('d', 'c', 'f'), ('a',)) + + outer_lam_def = node.body[0].value + + scope = anno.getanno(outer_lam_def, anno.Static.SCOPE) + self.assertScopeIs(scope, (), ()) + + scope = anno.getanno(outer_lam_def, NodeAnno.BODY_SCOPE) + self.assertScopeIs(scope, ('d', 'f', 'a', 'c'), ()) + + scope = anno.getanno(outer_lam_def, NodeAnno.ARGS_AND_BODY_SCOPE) + self.assertScopeIs(scope, ('d', 'f', 'a', 'c'), ()) + self.assertSymbolSetsAre(('a', 'b'), scope.bound, 'BOUND') + + scope = anno.getanno(outer_lam_def.args, anno.Static.SCOPE) + self.assertSymbolSetsAre(('a', 'b'), scope.params.keys(), 'lambda params') + + inner_lam_def = outer_lam_def.body.args[0] + + scope = anno.getanno(inner_lam_def, anno.Static.SCOPE) + self.assertScopeIs(scope, ('f',), ()) + + scope = anno.getanno(inner_lam_def, NodeAnno.BODY_SCOPE) + self.assertScopeIs(scope, ('a', 'b', 'c'), ()) + + scope = anno.getanno(inner_lam_def, NodeAnno.ARGS_AND_BODY_SCOPE) + self.assertScopeIs(scope, ('a', 'b', 'c'), ()) + self.assertSymbolSetsAre(('b',), scope.bound, 'BOUND') + + scope = anno.getanno(inner_lam_def.args, anno.Static.SCOPE) + self.assertSymbolSetsAre(('b',), scope.params.keys(), 'lambda params') + + def test_comprehension_targets_are_isolated(self): + + def test_fn(a): + b = {c for c in a} # pylint:disable=unused-variable + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(body_scope, ('a',), ('b',)) + + def test_comprehension_targets_are_isolated_list_function_w_generator(self): + + def test_fn(a): + b = list(c for c in a) # pylint:disable=unused-variable + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(body_scope, ('a', 'list'), ('b',)) + + def test_list_comprehension_targets_are_sometimes_isolated(self): + + def test_fn(a): + b = [c for c in a] # pylint:disable=unused-variable + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(body_scope, ('a',), ('b',)) + + def test_comprehension_targets_are_isolated_in_augassign(self): + + def test_fn(a, b): + b += [c for c in a] # pylint:disable=unused-variable + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(body_scope, ('a', 'b'), ('b',)) + + def test_comprehension_generator_order(self): + + def test_fn(a, b, c): # pylint:disable=unused-argument + e = {d: (a, b) for (a, b) in c for d in b} # pylint:disable=unused-variable,g-complex-comprehension + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(body_scope, ('c',), ('e',)) + + def test_global_symbol(self): + + def test_fn(c): + global global_a + global global_b + global_a = global_b + c + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(body_scope, ('global_a', 'global_b', 'c'), ('global_a',)) + self.assertSetEqual(body_scope.globals, set( + (QN('global_a'), QN('global_b')))) + global_a_scope = anno.getanno(fn_node.body[0], anno.Static.SCOPE) + self.assertScopeIs(global_a_scope, ('global_a',), ()) + + def test_nonlocal_symbol(self): + nonlocal_a = 3 + nonlocal_b = 13 + + def test_fn(c): + nonlocal nonlocal_a + nonlocal nonlocal_b + nonlocal_a = nonlocal_b + c + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs( + body_scope, ('nonlocal_a', 'nonlocal_b', 'c'), ('nonlocal_a',)) + nonlocal_a_scope = anno.getanno(fn_node.body[0], anno.Static.SCOPE) + self.assertScopeIs(nonlocal_a_scope, ('nonlocal_a',), ()) + + def test_annotated_assign(self): + b = int + + def test_fn(c): + a: b = c + return a + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + + body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(body_scope, ('b', 'c', 'a'), ('a',)) + self.assertSymbolSetsAre(('b',), body_scope.annotations, 'annotations') + + ann_assign_scope = anno.getanno(fn_node.body[0], anno.Static.SCOPE) + self.assertScopeIs(ann_assign_scope, ('b', 'c'), ('a',)) + self.assertSymbolSetsAre( + ('b',), ann_assign_scope.annotations, 'annotations') + + def test_pure_definition(self): + b = int + + def test_fn(): + a: b + return a + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + + body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(body_scope, ('b', 'a'), ('a',)) + self.assertSymbolSetsAre(('b',), body_scope.annotations, 'annotations') + + ann_assign_scope = anno.getanno(fn_node.body[0], anno.Static.SCOPE) + self.assertScopeIs(ann_assign_scope, ('b',), ('a',)) + self.assertSymbolSetsAre( + ('b',), ann_assign_scope.annotations, 'annotations') + + def test_function_def_annotations(self): + b = int + c = int + + def test_fn(a: b) -> c: + return a + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + + fn_scope = anno.getanno(fn_node, anno.Static.SCOPE) + self.assertScopeIs(fn_scope, ('b', 'c'), ('test_fn',)) + self.assertSymbolSetsAre(('b', 'c'), fn_scope.annotations, 'annotations') + + body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(body_scope, ('a',), ()) + self.assertSymbolSetsAre((), body_scope.annotations, 'annotations') + + def test_class_definition_basic(self): + + def test_fn(a, b): + class C(a(b)): + d = 1 + return C + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(body_scope, ('a', 'b', 'C'), ('C',)) + + def test_class_definition_isolates_method_writes(self): + + def test_fn(a, b, c): + class C(a(b)): + d = 1 + + def e(self): + f = c + 1 + return f + return C + + node, _ = self._parse_and_analyze(test_fn) + fn_node = node + body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) + self.assertScopeIs(body_scope, ('a', 'b', 'C', 'c'), ('C',)) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/annos.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/annos.py new file mode 100644 index 00000000..54d85cc3 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/annos.py @@ -0,0 +1,55 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Annotations used by the static analyzer.""" + +from enum import Enum + + +# TODO(mdan): Remove. + + +class NoValue(Enum): + + def __repr__(self): # pylint: disable=invalid-repr-returned + return self.name + + +class NodeAnno(NoValue): + """Additional annotations used by the static analyzer. + + These are in addition to the basic annotations declared in anno.py. + """ + + # Symbols + # These flags are boolean. + IS_LOCAL = 'Symbol is local to the function scope being analyzed.' + IS_PARAM = 'Symbol is a parameter to the function being analyzed.' + IS_MODIFIED_SINCE_ENTRY = ( + 'Symbol has been explicitly replaced in the current function scope.') + + # Scopes + # Scopes are represented by objects of type activity.Scope. + ARGS_SCOPE = 'The scope for the argument list of a function call.' + COND_SCOPE = 'The scope for the test node of a conditional statement.' + ITERATE_SCOPE = 'The scope for the iterate assignment of a for loop.' + ARGS_AND_BODY_SCOPE = ( + 'The scope for the main body of a function or lambda, including its' + ' arguments.') + BODY_SCOPE = ( + 'The scope for the main body of a statement (True branch for if ' + 'statements, main body for loops).') + ORELSE_SCOPE = ( + 'The scope for the orelse body of a statement (False branch for if ' + 'statements, orelse body for loops).') diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness.py new file mode 100644 index 00000000..36ee2bec --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness.py @@ -0,0 +1,220 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Live variable analysis. + +See https://en.wikipedia.org/wiki/Live_variable_analysis for a definition of +the following idioms: live variable, live in, live out, which are used +throughout this file. + +This analysis attaches the following: + * symbols that are live at the exit of control flow statements + * symbols that are live at the entry of control flow statements + +Requires activity analysis. +""" + +import gast + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import cfg +from braket.experimental.autoqasm.autograph.pyct import transformer +from braket.experimental.autoqasm.autograph.pyct.static_analysis import annos + + +class Analyzer(cfg.GraphVisitor): + """CFG visitor that performs liveness analysis at statement level.""" + + def __init__(self, graph, include_annotations): + super(Analyzer, self).__init__(graph) + self.include_annotations = include_annotations + + def init_state(self, _): + return set() + + def lamba_check(self, fn_ast_node): + if isinstance(fn_ast_node, gast.Lambda): + # Exception: lambda functions are assumed to be used only in the + # place where they are defined, and not later. + return True + return False + + def visit_node(self, node): + prev_live_in = self.in_[node] + + if anno.hasanno(node.ast_node, anno.Static.SCOPE): + node_scope = anno.getanno(node.ast_node, anno.Static.SCOPE) + + gen = node_scope.read + if not self.include_annotations: + gen -= node_scope.annotations + # TODO(mdan): verify whether composites' parents need to be added. + # E.g. whether x needs to be added if x.y is live. Theoretically the + # activity analysis should have both so that wouldn't be needed. + kill = node_scope.modified | node_scope.deleted + + live_out = set() + for n in node.next: + live_out |= self.in_[n] + live_in = gen | (live_out - kill) + + reaching_functions = anno.getanno( + node.ast_node, anno.Static.DEFINED_FNS_IN) + for fn_ast_node in reaching_functions: + if self.lamba_check(fn_ast_node): + continue + fn_scope = anno.getanno(fn_ast_node, annos.NodeAnno.ARGS_AND_BODY_SCOPE) + # Any closure of a reaching function definition is conservatively + # considered live. + live_in |= (fn_scope.read - fn_scope.bound) + + else: + assert self.can_ignore(node), (node.ast_node, node) + + live_out = set() + for n in node.next: + live_out |= self.in_[n] + live_in = live_out + + self.in_[node] = live_in + self.out[node] = live_out + + # TODO(mdan): Move this to the superclass? + return prev_live_in != live_in + + +class TreeAnnotator(transformer.Base): + """Runs liveness analysis on each of the functions defined in the AST. + + If a function defined other local functions, those will have separate CFGs. + However, dataflow analysis needs to tie up these CFGs to properly emulate the + effect of closures. In the case of liveness, the parent function's live + variables must account for the variables that are live at the entry of each + subfunction. For example: + + def foo(): + # baz is live from here on + def bar(): + print(baz) + + This analyzer runs liveness analysis on each individual function, accounting + for the effect above. + """ + + def __init__(self, source_info, graphs, include_annotations): + super(TreeAnnotator, self).__init__(source_info) + self.include_annotations = include_annotations + self.allow_skips = False + self.graphs = graphs + self.current_analyzer = None + + def visit(self, node): + node = super(TreeAnnotator, self).visit(node) + if (self.current_analyzer is not None and + isinstance(node, gast.stmt) and + node in self.current_analyzer.graph.index): + cfg_node = self.current_analyzer.graph.index[node] + anno.setanno(node, anno.Static.LIVE_VARS_IN, + frozenset(self.current_analyzer.in_[cfg_node])) + return node + + def _analyze_function(self, node, is_lambda): + parent_analyzer = self.current_analyzer + + analyzer = Analyzer(self.graphs[node], self.include_annotations) + analyzer.visit_reverse() + self.current_analyzer = analyzer + node = self.generic_visit(node) + + self.current_analyzer = parent_analyzer + return node + + def visit_Lambda(self, node): + return self._analyze_function(node, is_lambda=True) + + def visit_FunctionDef(self, node): + return self._analyze_function(node, is_lambda=False) + + def _block_statement_live_out(self, node): + successors = self.current_analyzer.graph.stmt_next[node] + stmt_live_out = set() + for s in successors: + stmt_live_out.update(self.current_analyzer.in_[s]) + anno.setanno(node, anno.Static.LIVE_VARS_OUT, frozenset(stmt_live_out)) + return node + + def _block_statement_live_in(self, node, entry_node): + if entry_node in self.current_analyzer.graph.index: + cfg_node = self.current_analyzer.graph.index[entry_node] + stmt_live_in = frozenset(self.current_analyzer.in_[cfg_node]) + else: + assert anno.hasanno(entry_node, anno.Static.LIVE_VARS_IN), ( + 'If not matching a CFG node, must be a block statement:' + ' {}'.format(entry_node)) + stmt_live_in = anno.getanno(entry_node, anno.Static.LIVE_VARS_IN) + anno.setanno(node, anno.Static.LIVE_VARS_IN, stmt_live_in) + return node + + def visit_If(self, node): + node = self.generic_visit(node) + node = self._block_statement_live_out(node) + return self._block_statement_live_in(node, node.test) + + def visit_For(self, node): + node = self.generic_visit(node) + node = self._block_statement_live_out(node) + return self._block_statement_live_in(node, node.iter) + + def visit_While(self, node): + node = self.generic_visit(node) + node = self._block_statement_live_out(node) + return self._block_statement_live_in(node, node.test) + + def visit_Try(self, node): + node = self.generic_visit(node) + node = self._block_statement_live_out(node) + return self._block_statement_live_in(node, node.body[0]) + + def visit_ExceptHandler(self, node): + node = self.generic_visit(node) + node = self._block_statement_live_out(node) + return self._block_statement_live_in(node, node.body[0]) + + def visit_With(self, node): + node = self.generic_visit(node) + return self._block_statement_live_in(node, node.items[0]) + + def visit_Expr(self, node): + node = self.generic_visit(node) + cfg_node = self.current_analyzer.graph.index[node] + anno.setanno(node, anno.Static.LIVE_VARS_OUT, + frozenset(self.current_analyzer.out[cfg_node])) + return node + + +# TODO(mdan): Investigate the possibility of removing include_annotations. +def resolve(node, source_info, graphs, include_annotations=True): + """Resolves the live symbols at the exit of control flow statements. + + Args: + node: ast.AST + source_info: transformer.SourceInfo + graphs: Dict[ast.FunctionDef, cfg.Graph] + include_annotations: Bool, whether type annotations should be included in + the analysis. + Returns: + ast.AST + """ + node = TreeAnnotator(source_info, graphs, include_annotations).visit(node) + return node diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness_py3_test.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness_py3_test.py new file mode 100644 index 00000000..78b91932 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness_py3_test.py @@ -0,0 +1,30 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for liveness module, that only run in Python 3.""" + +from braket.experimental.autoqasm.autograph.pyct.static_analysis import annos +from braket.experimental.autoqasm.autograph.pyct.static_analysis import liveness_test +from tensorflow.python.platform import test + + +NodeAnno = annos.NodeAnno + + +class LivenessAnalyzerTest(liveness_test.LivenessAnalyzerTestBase): + """Tests which can only run in Python 3.""" + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness_test.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness_test.py new file mode 100644 index 00000000..709bd3fe --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness_test.py @@ -0,0 +1,575 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for liveness module.""" + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import cfg +from braket.experimental.autoqasm.autograph.pyct import naming +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import qual_names +from braket.experimental.autoqasm.autograph.pyct import transformer +from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity +from braket.experimental.autoqasm.autograph.pyct.static_analysis import liveness +from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_fndefs +from tensorflow.python.platform import test + + +global_a = 7 +global_b = 17 + + +class LivenessAnalyzerTestBase(test.TestCase): + + def _parse_and_analyze(self, test_fn): + # TODO(mdan): Use a custom FunctionTransformer here. + node, source = parser.parse_entity(test_fn, future_features=()) + entity_info = transformer.EntityInfo( + name=test_fn.__name__, + source_code=source, + source_file=None, + future_features=(), + namespace={}) + node = qual_names.resolve(node) + namer = naming.Namer({}) + ctx = transformer.Context(entity_info, namer, None) + node = activity.resolve(node, ctx) + graphs = cfg.build(node) + node = reaching_fndefs.resolve(node, ctx, graphs) + node = liveness.resolve(node, ctx, graphs) + return node + + def assertHasLiveOut(self, node, expected): + live_out = anno.getanno(node, anno.Static.LIVE_VARS_OUT) + live_out_strs = set(str(v) for v in live_out) + if not expected: + expected = () + if not isinstance(expected, tuple): + expected = (expected,) + self.assertSetEqual(live_out_strs, set(expected)) + + def assertHasLiveIn(self, node, expected): + live_in = anno.getanno(node, anno.Static.LIVE_VARS_IN) + live_in_strs = set(str(v) for v in live_in) + if not expected: + expected = () + if not isinstance(expected, tuple): + expected = (expected,) + self.assertSetEqual(live_in_strs, set(expected)) + + +class LivenessAnalyzerTest(LivenessAnalyzerTestBase): + + def test_live_out_try_block(self): + + def test_fn(x, a, b, c): # pylint:disable=unused-argument + if a > 0: + try: + pass + except: # pylint:disable=bare-except + pass + return x + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveOut(fn_body[0], 'x') + self.assertHasLiveOut(fn_body[0].body[0], 'x') + + def test_live_out_if_inside_except(self): + + def test_fn(x, a, b, c): # pylint:disable=unused-argument + if a > 0: + try: + pass + except: # pylint:disable=bare-except + if b > 0: + x = b + return x + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveOut(fn_body[0], 'x') + self.assertHasLiveOut(fn_body[0].body[0], 'x') + self.assertHasLiveOut(fn_body[0].body[0].handlers[0].body[0], 'x') + + def test_live_out_stacked_if(self): + + def test_fn(x, a): + if a > 0: + x = 0 + if a > 1: + x = 1 + return x + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveOut(fn_body[0], ('a', 'x')) + self.assertHasLiveOut(fn_body[1], 'x') + + def test_live_out_stacked_if_else(self): + + def test_fn(x, a): + if a > 0: + x = 0 + if a > 1: + x = 1 + else: + x = 2 + return x + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveOut(fn_body[0], 'a') + self.assertHasLiveOut(fn_body[1], 'x') + + def test_live_out_for_basic(self): + + def test_fn(x, a): + for i in range(a): + x += i + return x + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveOut(fn_body[0], 'x') + + def test_live_out_for_iterate(self): + + def test_fn(x, a): + for i in range(a): + x += i + return x, i # pylint:disable=undefined-loop-variable + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveOut(fn_body[0], ('x', 'i')) + + def test_live_out_attributes(self): + + def test_fn(x, a): + if a > 0: + x.y = 0 + return x.y + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveOut(fn_body[0], ('x.y', 'x')) + + def test_live_out_nested_functions(self): + + def test_fn(a, b): + if b: + a = [] + + def foo(): + return a + + foo() + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveOut(fn_body[0], 'a') + + def test_live_out_nested_functions_defined_ahead(self): + + def test_fn(a, b): + def foo(): + return a + + if b: + a = [] + + return foo + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveOut(fn_body[1], ('a', 'foo')) + + def test_live_out_nested_functions_defined_after(self): + + def test_fn(a, b): + if b: + a = [] + + def foo(): + return a + + return foo + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveOut(fn_body[0], ('a',)) + + def test_live_out_lambda(self): + + def test_fn(a, b): + if b: + a = [] + + foo = lambda: a + + if b: + pass + + return foo + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveOut(fn_body[0], ('a', 'b')) + #TODO(@bhack): replace this after deprecation + # https://github.com/tensorflow/tensorflow/issues/56089 + self.assertHasLiveOut(fn_body[2], ('foo',)) + #self.assertHasLiveOut(fn_body[2], ('a', 'foo')) + + def test_live_out_nested_functions_hidden_by_argument(self): + + def test_fn(b): + def foo(a): + return a + + if b: + a = [] # pylint:disable=unused-variable + + return foo + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveOut(fn_body[1], ('foo')) + + def test_live_out_nested_functions_isolation(self): + + def test_fn(b): + if b: + a = 0 # pylint:disable=unused-variable + + def child(): + max(a) # pylint:disable=used-before-assignment + a = 1 + return a + + child() + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveOut(fn_body[0], 'max') + + def test_live_out_deletion(self): + + def test_fn(x, y, a): + for _ in a: + if x: + del y + else: + y = 0 + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveOut(fn_body[0], ()) + + def test_live_in_pass(self): + + def test_fn(x, a, b, c): # pylint:disable=unused-argument + if a > 0: + pass + return x + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveIn(fn_body[0], ('a', 'x')) + self.assertHasLiveIn(fn_body[0].body[0], ('x',)) + self.assertHasLiveIn(fn_body[1], ('x',)) + + def test_live_in_raise(self): + + def test_fn(x, a, b, c): + if a > 0: + b = b + 1 + raise c + return x + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveIn(fn_body[0], ('a', 'b', 'c', 'x')) + self.assertHasLiveIn(fn_body[0].body[0], ('b', 'c')) + self.assertHasLiveIn(fn_body[1], ('x',)) + + def test_live_out_except_variable(self): + + def test_fn(x, a): + try: + pass + except a as b: + raise b + return x + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + # Note: 'a' is not live because there is no raise statement inside the + # try, and we discount the possibility of other code in the try block + # raising an error. + self.assertHasLiveIn(fn_body[0], ('b', 'x')) + + def test_live_in_return_statement(self): + + def test_fn(x, a, b, c): # pylint:disable=unused-argument + if a > 0: + return x + return x + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveIn(fn_body[0], ('a', 'x')) + self.assertHasLiveIn(fn_body[0].body[0], ('x',)) + self.assertHasLiveIn(fn_body[1], ('x',)) + + def test_live_in_try_block(self): + + def test_fn(x, a, b, c): # pylint:disable=unused-argument + if a > 0: + try: + pass + except: # pylint:disable=bare-except + pass + return x + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveIn(fn_body[0], ('a', 'x')) + self.assertHasLiveIn(fn_body[0].body[0], ('x',)) + self.assertHasLiveIn(fn_body[1], ('x',)) + + def test_live_in_try_orelse(self): + + def test_fn(x, a, b, c): # pylint:disable=unused-argument + if a > 0: + try: + pass + except: # pylint:disable=bare-except + pass + else: + x = b + return x + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveIn(fn_body[0], ('a', 'b', 'x')) + self.assertHasLiveIn(fn_body[0].body[0], ('b', 'x')) + self.assertHasLiveIn(fn_body[1], ('x',)) + + def test_live_in_if_inside_except(self): + + def test_fn(x, a, b, c): # pylint:disable=unused-argument + if a > 0: + try: + pass + except: # pylint:disable=bare-except + if b > 0: + x = b + return x + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveIn(fn_body[0], ('a', 'b', 'x')) + self.assertHasLiveIn(fn_body[0].body[0], ('b', 'x')) + self.assertHasLiveIn(fn_body[0].body[0].handlers[0].body[0], ('b', 'x')) + self.assertHasLiveIn(fn_body[1], ('x',)) + + def test_live_in_stacked_if(self): + + def test_fn(x, a, b, c): + if a > 0: + x = b + if c > 1: + x = 0 + return x + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveIn(fn_body[0], ('a', 'b', 'c', 'x')) + self.assertHasLiveIn(fn_body[1], ('c', 'x')) + + def test_live_in_stacked_if_else(self): + + def test_fn(x, a, b, c, d): + if a > 1: + x = b + else: + x = c + if d > 0: + x = 0 + return x + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveIn(fn_body[0], ('a', 'b', 'c', 'd')) + self.assertHasLiveIn(fn_body[1], ('d', 'x')) + + def test_live_in_for_basic(self): + + def test_fn(x, y, a): + for i in a: + x = i + y += x + z = 0 + return y, z + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveIn(fn_body[0], ('a', 'y', 'z')) + + def test_live_in_for_nested(self): + + def test_fn(x, y, a): + for i in a: + for j in i: + x = i + y += x + z = j + return y, z + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveIn(fn_body[0], ('a', 'y', 'z')) + + def test_live_in_deletion(self): + + def test_fn(x, y, a): + for _ in a: + if x: + del y + else: + y = 0 + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveIn(fn_body[0], ('a', 'x', 'y')) + + def test_live_in_generator_comprehension(self): + + def test_fn(y): + if all(x for x in y): + return + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveIn(fn_body[0], ('all', 'y')) + + def test_live_in_list_comprehension(self): + + def test_fn(y): + if [x for x in y]: + return + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveIn(fn_body[0], ('y',)) + + def test_live_in_list_comprehension_expression(self): + + def test_fn(y, s): + s += foo([x for x in y]) # pylint:disable=undefined-variable + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveIn(fn_body[0], ('y', 'foo', 's')) + + def test_live_in_set_comprehension(self): + + def test_fn(y): + if {x for x in y}: + return + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveIn(fn_body[0], ('y',)) + + def test_live_in_dict_comprehension(self): + + def test_fn(y): + if {k: v for k, v in y}: + return + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasLiveIn(fn_body[0], ('y',)) + + def test_global_symbol(self): + + def test_fn(c): + global global_a + global global_b + if global_a: + global_b = c + else: + global_b = c + return global_b + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + self.assertHasLiveOut(fn_body[2], ('global_b',)) + self.assertHasLiveIn(fn_body[2], ('global_a', 'c')) + + def test_nonlocal_symbol(self): + + nonlocal_a = 3 + nonlocal_b = 13 + + def test_fn(c): + nonlocal nonlocal_a + nonlocal nonlocal_b + if nonlocal_a: + nonlocal_b = c + else: + nonlocal_b = c + return nonlocal_b + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + self.assertHasLiveOut(fn_body[2], ('nonlocal_b',)) + self.assertHasLiveIn(fn_body[2], ('nonlocal_a', 'c')) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions.py new file mode 100644 index 00000000..6143b05a --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions.py @@ -0,0 +1,288 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Reaching definition analysis. + +This analysis attaches a set of a Definition objects to each symbol, one +for each distinct definition that may reach it. The Definition objects are +mutable and may be used by subsequent analyses to further annotate data like +static type and value information. +The analysis also attaches the set of the symbols defined at the entry of +control flow statements. + +Requires activity analysis. +""" + +import weakref + +import gast + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import cfg +from braket.experimental.autoqasm.autograph.pyct import transformer + + +class Definition(object): + """Definition objects describe a unique definition of a variable. + + Subclasses of this may be used by passing an appropriate factory function to + resolve. + + Attributes: + param_of: Optional[ast.AST] + directives: Dict, optional definition annotations + """ + + def __init__(self): + self.param_of = None + self.directives = {} + + def __repr__(self): + return '%s[%d]' % (self.__class__.__name__, id(self)) + + +class _NodeState(object): + """Abstraction for the state of the CFG walk for reaching definition analysis. + + This is a value type. Only implements the strictly necessary operators. + + Attributes: + value: Dict[qual_names.QN, Set[Definition, ...]], the defined symbols and + their possible definitions + """ + + def __init__(self, init_from=None): + if init_from: + if isinstance(init_from, _NodeState): + self.value = { + s: set(other_infos) for s, other_infos in init_from.value.items() + } + elif isinstance(init_from, dict): + self.value = {s: set((init_from[s],)) for s in init_from} + else: + assert False, init_from + else: + self.value = {} + + def __eq__(self, other): + if frozenset(self.value.keys()) != frozenset(other.value.keys()): + return False + ret = all(self.value[s] == other.value[s] for s in self.value) + return ret + + def __ne__(self, other): + return not self.__eq__(other) + + def __or__(self, other): + assert isinstance(other, _NodeState) + result = _NodeState(self) + for s, other_infos in other.value.items(): + if s in result.value: + result.value[s].update(other_infos) + else: + result.value[s] = set(other_infos) + return result + + def __sub__(self, other): + assert isinstance(other, set) + result = _NodeState(self) + for s in other: + result.value.pop(s, None) + return result + + def __repr__(self): + return 'NodeState[%s]=%s' % (id(self), repr(self.value)) + + +class Analyzer(cfg.GraphVisitor): + """CFG visitor that determines reaching definitions at statement level.""" + + def __init__(self, graph, definition_factory): + self._definition_factory = definition_factory + super(Analyzer, self).__init__(graph) + self.gen_map = {} + + def init_state(self, _): + return _NodeState() + + def visit_node(self, node): + prev_defs_out = self.out[node] + + defs_in = _NodeState() + for n in node.prev: + defs_in |= self.out[n] + + if anno.hasanno(node.ast_node, anno.Static.SCOPE): + node_scope = anno.getanno(node.ast_node, anno.Static.SCOPE) + # The definition objects created by each node must be singletons because + # their ids are used in equality checks. + if node not in self.gen_map: + node_symbols = {} + # Every binding operation (assign, nonlocal, global, etc.) counts as a + # definition, with the exception of del, which only deletes without + # creating a new variable. + newly_defined = ((node_scope.bound | node_scope.globals) - + node_scope.deleted) + for s in newly_defined: + def_ = self._definition_factory() + node_symbols[s] = def_ + # Every param receives a definition. Params are not necessarily + # considered as "modified". + for s, p in node_scope.params.items(): + def_ = self._definition_factory() + def_.param_of = weakref.ref(p) + node_symbols[s] = def_ + self.gen_map[node] = _NodeState(node_symbols) + + gen = self.gen_map[node] + kill = node_scope.modified | node_scope.deleted + defs_out = gen | (defs_in - kill) + + gen = self.gen_map[node] + defs_out = gen | (defs_in - kill) + + else: + assert self.can_ignore(node), (node.ast_node, node) + defs_out = defs_in + + self.in_[node] = defs_in + self.out[node] = defs_out + + return prev_defs_out != defs_out + + +class TreeAnnotator(transformer.Base): + """AST visitor that annotates each symbol name with its reaching definitions. + + Simultaneously, the visitor runs the dataflow analysis on each function node, + accounting for the effect of closures. For example: + + def foo(): + bar = 1 + def baz(): + # bar = 1 reaches here + """ + + def __init__(self, source_info, graphs, definition_factory): + super(TreeAnnotator, self).__init__(source_info) + self.allow_skips = False + self.definition_factory = definition_factory + self.graphs = graphs + self.current_analyzer = None + self.current_cfg_node = None + + def visit_FunctionDef(self, node): + parent_analyzer = self.current_analyzer + subgraph = self.graphs[node] + + analyzer = Analyzer(subgraph, self.definition_factory) + analyzer.visit_forward() + + # Recursively process any remaining subfunctions. + self.current_analyzer = analyzer + node.args = self.visit(node.args) + node.body = self.visit_block(node.body) + self.current_analyzer = parent_analyzer + + return node + + def visit_Name(self, node): + if self.current_analyzer is None: + # Names may appear outside function defs - for example in class + # definitions. + return node + + analyzer = self.current_analyzer + cfg_node = self.current_cfg_node + + assert cfg_node is not None, ('name node, %s, outside of any statement?' + % node.id) + + qn = anno.getanno(node, anno.Basic.QN) + if isinstance(node.ctx, gast.Load): + anno.setanno(node, anno.Static.DEFINITIONS, + tuple(analyzer.in_[cfg_node].value.get(qn, ()))) + else: + anno.setanno(node, anno.Static.DEFINITIONS, + tuple(analyzer.out[cfg_node].value.get(qn, ()))) + + return node + + def _aggregate_predecessors_defined_in(self, node): + preds = self.current_analyzer.graph.stmt_prev[node] + node_defined_in = set() + for p in preds: + node_defined_in |= set(self.current_analyzer.out[p].value.keys()) + anno.setanno(node, anno.Static.DEFINED_VARS_IN, frozenset(node_defined_in)) + + def visit_If(self, node): + self._aggregate_predecessors_defined_in(node) + return self.generic_visit(node) + + def visit_For(self, node): + self._aggregate_predecessors_defined_in(node) + + # Manually accounting for the shortcoming described in + # cfg.AstToCfg.visit_For. + parent = self.current_cfg_node + self.current_cfg_node = self.current_analyzer.graph.index[node.iter] + node.target = self.visit(node.target) + self.current_cfg_node = parent + + node.iter = self.visit(node.iter) + node.body = self.visit_block(node.body) + node.orelse = self.visit_block(node.orelse) + + return node + + def visit_While(self, node): + self._aggregate_predecessors_defined_in(node) + return self.generic_visit(node) + + def visit_Try(self, node): + self._aggregate_predecessors_defined_in(node) + return self.generic_visit(node) + + def visit_ExceptHandler(self, node): + self._aggregate_predecessors_defined_in(node) + # TODO(mdan): Also track the exception type / name symbols. + node.body = self.visit_block(node.body) + return node + + def visit(self, node): + parent = self.current_cfg_node + + if (self.current_analyzer is not None and + node in self.current_analyzer.graph.index): + self.current_cfg_node = self.current_analyzer.graph.index[node] + node = super(TreeAnnotator, self).visit(node) + + self.current_cfg_node = parent + return node + + +def resolve(node, source_info, graphs, definition_factory=Definition): + """Resolves reaching definitions for each symbol. + + Args: + node: ast.AST + source_info: transformer.SourceInfo + graphs: Dict[ast.FunctionDef, cfg.Graph] + definition_factory: Callable[[], Definition] + Returns: + ast.AST + """ + visitor = TreeAnnotator(source_info, graphs, definition_factory) + node = visitor.visit(node) + return node diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions_py3_test.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions_py3_test.py new file mode 100644 index 00000000..8412ffd7 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions_py3_test.py @@ -0,0 +1,92 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for reaching_definitions module, that only run in Python 3.""" + +from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_definitions_test +from tensorflow.python.platform import test + + +class ReachingDefinitionsAnalyzerTest( + reaching_definitions_test.ReachingDefinitionsAnalyzerTestBase): + """Tests which can only run in Python 3.""" + + def test_nonlocal(self): + + a = 3 + b = 13 + + def test_fn(): + nonlocal a + nonlocal b + if a: + b = [] + return a, b + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasDefs(fn_body[2].test, 1) + self.assertHasDefs(fn_body[2].body[0].targets[0], 1) + self.assertHasDefs(fn_body[3].value.elts[0], 1) + self.assertHasDefs(fn_body[3].value.elts[1], 2) + + self.assertSameDef(fn_body[2].test, fn_body[3].value.elts[0]) + + self.assertHasDefinedIn(fn_body[2], ('a', 'b')) + + def test_nonlocal_in_nested_function(self): + + a = 3 + b = 13 + + def test_fn(): + a = 3 + b = 13 + + def local_fn(): + nonlocal a, b + if a: + b = [] + return a, b + + return local_fn() + + node = self._parse_and_analyze(test_fn) + local_body = node.body[2].body + + self.assertHasDefs(local_body[1].test, 1) + self.assertHasDefs(local_body[1].body[0].targets[0], 1) + self.assertHasDefs(local_body[2].value.elts[0], 1) + self.assertHasDefs(local_body[2].value.elts[1], 2) + + self.assertSameDef(local_body[1].test, local_body[2].value.elts[0]) + + # Note: the function name is visible inside the function body. But it's + # a closure variable, not a local. + # + # Example: + # + # >>> def f(): + # ... print(f) + # >>> g = f + # >>> f = 'something else' + # >>> g() + # something else + # + self.assertHasDefinedIn(local_body[1], ('a', 'b')) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions_test.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions_test.py new file mode 100644 index 00000000..49f13038 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions_test.py @@ -0,0 +1,526 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for reaching_definitions module.""" + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import cfg +from braket.experimental.autoqasm.autograph.pyct import naming +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import qual_names +from braket.experimental.autoqasm.autograph.pyct import transformer +from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity +from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_definitions +from tensorflow.python.platform import test + + +global_a = 7 +global_b = 17 + + +class ReachingDefinitionsAnalyzerTestBase(test.TestCase): + + def _parse_and_analyze(self, test_fn): + # TODO(mdan): Use a custom FunctionTransformer here. + node, source = parser.parse_entity(test_fn, future_features=()) + entity_info = transformer.EntityInfo( + name=test_fn.__name__, + source_code=source, + source_file=None, + future_features=(), + namespace={}) + node = qual_names.resolve(node) + namer = naming.Namer({}) + ctx = transformer.Context(entity_info, namer, None) + node = activity.resolve(node, ctx) + graphs = cfg.build(node) + node = reaching_definitions.resolve(node, ctx, graphs, + reaching_definitions.Definition) + return node + + def assertHasDefs(self, node, num): + defs = anno.getanno(node, anno.Static.DEFINITIONS) + self.assertEqual(len(defs), num) + for r in defs: + self.assertIsInstance(r, reaching_definitions.Definition) + + def assertHasDefinedIn(self, node, expected): + defined_in = anno.getanno(node, anno.Static.DEFINED_VARS_IN) + defined_in_str = set(str(v) for v in defined_in) + if not expected: + expected = () + if not isinstance(expected, tuple): + expected = (expected,) + self.assertSetEqual(defined_in_str, set(expected)) + + def assertSameDef(self, first, second): + self.assertHasDefs(first, 1) + self.assertHasDefs(second, 1) + self.assertIs( + anno.getanno(first, anno.Static.DEFINITIONS)[0], + anno.getanno(second, anno.Static.DEFINITIONS)[0]) + + def assertNotSameDef(self, first, second): + self.assertHasDefs(first, 1) + self.assertHasDefs(second, 1) + self.assertIsNot( + anno.getanno(first, anno.Static.DEFINITIONS)[0], + anno.getanno(second, anno.Static.DEFINITIONS)[0]) + + +class ReachingDefinitionsAnalyzerTest(ReachingDefinitionsAnalyzerTestBase): + + def test_conditional(self): + + def test_fn(a, b): + a = [] + if b: + a = [] + return a + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasDefs(fn_body[0].targets[0], 1) + self.assertHasDefs(fn_body[1].test, 1) + self.assertHasDefs(fn_body[1].body[0].targets[0], 1) + self.assertHasDefs(fn_body[2].value, 2) + + self.assertHasDefinedIn(fn_body[1], ('a', 'b')) + + def test_try_in_conditional(self): + + def test_fn(a, b): # pylint:disable=unused-argument + a = [] + if b: + try: + pass + except: # pylint:disable=bare-except + pass + return a + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasDefinedIn(fn_body[1], ('a', 'b')) + self.assertHasDefinedIn(fn_body[1].body[0], ('a', 'b')) + + def test_conditional_in_try_in_conditional(self): + + def test_fn(a, b): + a = [] + if b: + try: + if b: + a = [] + except TestException: # pylint:disable=undefined-variable,unused-variable + pass + return a + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasDefinedIn(fn_body[1], ('a', 'b')) + self.assertHasDefinedIn(fn_body[1].body[0], ('a', 'b')) + # Note: `TestException` and `e` are not tracked. + self.assertHasDefinedIn(fn_body[1].body[0].body[0], ('a', 'b')) + + def test_conditional_in_except_in_conditional(self): + + def test_fn(a, b): + a = [] + if b: + try: + pass + except TestException as e: # pylint:disable=undefined-variable,unused-variable + if b: + a = [] + return a + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasDefinedIn(fn_body[1], ('a', 'b')) + self.assertHasDefinedIn(fn_body[1].body[0], ('a', 'b')) + # Note: `TestException` and `e` are not tracked. + self.assertHasDefinedIn(fn_body[1].body[0].handlers[0].body[0], ('a', 'b')) + + def test_while(self): + + def test_fn(a): + max(a) + while True: + a = a + a = a + return a + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasDefs(fn_body[0].value.args[0], 1) + self.assertHasDefs(fn_body[1].body[0].targets[0], 1) + self.assertHasDefs(fn_body[1].body[1].targets[0], 1) + self.assertHasDefs(fn_body[1].body[1].value, 1) + # The loop does have an invariant test, but the CFG doesn't know that. + self.assertHasDefs(fn_body[1].body[0].value, 2) + self.assertHasDefs(fn_body[2].value, 2) + + def test_while_else(self): + + def test_fn(x, i): + y = 0 + while x: + x += i + if i: + break + else: + y = 1 + return x, y + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasDefs(fn_body[0].targets[0], 1) + self.assertHasDefs(fn_body[1].test, 2) + self.assertHasDefs(fn_body[1].body[0].target, 1) + self.assertHasDefs(fn_body[1].body[1].test, 1) + self.assertHasDefs(fn_body[1].orelse[0].targets[0], 1) + self.assertHasDefs(fn_body[2].value.elts[0], 2) + self.assertHasDefs(fn_body[2].value.elts[1], 2) + + def test_for_else(self): + + def test_fn(x, i): + y = 0 + for i in x: + x += i + if i: + break + else: + continue + else: + y = 1 + return x, y + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasDefs(fn_body[0].targets[0], 1) + self.assertHasDefs(fn_body[1].target, 1) + self.assertHasDefs(fn_body[1].body[0].target, 1) + self.assertHasDefs(fn_body[1].body[1].test, 1) + self.assertHasDefs(fn_body[1].orelse[0].targets[0], 1) + self.assertHasDefs(fn_body[2].value.elts[0], 2) + self.assertHasDefs(fn_body[2].value.elts[1], 2) + + def test_nested_functions(self): + + def test_fn(a, b): + a = [] + if b: + a = [] + + def foo(): + return a + + foo() + + return a + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + def_of_a_in_if = fn_body[1].body[0].targets[0] + + self.assertHasDefs(fn_body[0].targets[0], 1) + self.assertHasDefs(fn_body[1].test, 1) + self.assertHasDefs(def_of_a_in_if, 1) + self.assertHasDefs(fn_body[2].value, 2) + + inner_fn_body = fn_body[1].body[1].body + def_of_a_in_foo = inner_fn_body[0].value + # Even though `a` is visible in the inner functio above, the late binding + # makes it impossible to assume that the same value will be visible at + # call time. + self.assertHasDefs(def_of_a_in_foo, 0) + + def test_nested_functions_isolation(self): + + def test_fn(a): + a = 0 + + def child(): + a = 1 + return a + + child() + return a + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + parent_return = fn_body[3] + child_return = fn_body[1].body[1] + # The assignment `a = 1` makes `a` local to `child`. + self.assertNotSameDef(parent_return.value, child_return.value) + + def test_function_call_in_with(self): + + def foo(_): + pass + + def test_fn(a): + with foo(a): + return a + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasDefs(fn_body[0].items[0].context_expr.func, 0) + self.assertHasDefs(fn_body[0].items[0].context_expr.args[0], 1) + + def test_mutation_subscript(self): + + def test_fn(a): + l = [] + l[0] = a + return l + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + creation = fn_body[0].targets[0] + mutation = fn_body[1].targets[0].value + use = fn_body[2].value + self.assertSameDef(creation, mutation) + self.assertSameDef(creation, use) + + def test_deletion_partial(self): + + def test_fn(a): + a = 0 + if a: + del a + else: + a = 1 + return a + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + first_def = fn_body[0].targets[0] + second_def = fn_body[1].orelse[0].targets[0] + use = fn_body[2].value + self.assertNotSameDef(use, first_def) + self.assertSameDef(use, second_def) + + def test_deletion_total(self): + + def test_fn(a): + if a: + a = 0 + else: + a = 1 + del a + return a + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + use = fn_body[2].value + self.assertHasDefs(use, 0) + + def test_replacement(self): + + def foo(a): + return a + + def test_fn(a): + a = foo(a) + return a + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + param = node.args.args[0] + source = fn_body[0].value.args[0] + target = fn_body[0].targets[0] + retval = fn_body[1].value + self.assertSameDef(param, source) + self.assertNotSameDef(source, target) + self.assertSameDef(target, retval) + + def test_comprehension_leaking(self): + + def test_fn(a): + _ = [x for x in a] + return x # pylint:disable=undefined-loop-variable + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + listcomp_target = fn_body[0].value.generators[0].target + retval = fn_body[1].value + + # Python2 leaks list comprehension symbols. Python3 doesn't. + # For details, see: + # https://stackoverflow.com/questions/4198906/list-comprehension-rebinds-names-even-after-scope-of-comprehension-is-this-righ + self.assertHasDefs(retval, 0) + + def test_function_definition(self): + + def test_fn(): + def a(): + pass + if a: # pylint:disable=using-constant-test + a = None + return a + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasDefs(fn_body[1].test, 1) + self.assertHasDefs(fn_body[1].body[0].targets[0], 1) + self.assertHasDefs(fn_body[2].value, 2) + + self.assertHasDefinedIn(fn_body[1], ('a',)) + + def test_definitions_in_except_block(self): + + def test_fn(): + try: + pass + except ValueError: + a = None + if a: # pylint:disable=using-constant-test + a = None + return a + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasDefs(fn_body[1].test, 1) + self.assertHasDefs(fn_body[1].body[0].targets[0], 1) + self.assertHasDefs(fn_body[2].value, 2) + + self.assertHasDefinedIn(fn_body[1], ('a',)) + + def test_definitions_in_except_block_of_raising_try(self): + + def test_fn(): + try: + raise ValueError() + except ValueError: + a = None + if a: # pylint:disable=using-constant-test + a = None + return a + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasDefs(fn_body[1].test, 1) + self.assertHasDefs(fn_body[1].body[0].targets[0], 1) + self.assertHasDefs(fn_body[2].value, 2) + + self.assertHasDefinedIn(fn_body[1], ('a',)) + + def test_global(self): + + def test_fn(): + global global_a + global global_b + if global_a: + global_b = [] + return global_a, global_b + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasDefs(fn_body[2].test, 1) + self.assertHasDefs(fn_body[2].body[0].targets[0], 1) + self.assertHasDefs(fn_body[3].value.elts[0], 1) + self.assertHasDefs(fn_body[3].value.elts[1], 2) + + self.assertSameDef(fn_body[2].test, fn_body[3].value.elts[0]) + + self.assertHasDefinedIn(fn_body[2], ('global_a', 'global_b')) + + def test_nonlocal(self): + + a = 3 + b = 13 + + def test_fn(): + nonlocal a + nonlocal b + if a: + b = [] + return a, b + + node = self._parse_and_analyze(test_fn) + fn_body = node.body + + self.assertHasDefs(fn_body[2].test, 1) + self.assertHasDefs(fn_body[2].body[0].targets[0], 1) + self.assertHasDefs(fn_body[3].value.elts[0], 1) + self.assertHasDefs(fn_body[3].value.elts[1], 2) + + self.assertSameDef(fn_body[2].test, fn_body[3].value.elts[0]) + + self.assertHasDefinedIn(fn_body[2], ('a', 'b')) + + def test_nonlocal_in_nested_function(self): + + a = 3 + b = 13 + + def test_fn(): + a = 3 + b = 13 + + def local_fn(): + nonlocal a, b + if a: + b = [] + return a, b + + return local_fn() + + node = self._parse_and_analyze(test_fn) + local_body = node.body[2].body + + self.assertHasDefs(local_body[1].test, 1) + self.assertHasDefs(local_body[1].body[0].targets[0], 1) + self.assertHasDefs(local_body[2].value.elts[0], 1) + self.assertHasDefs(local_body[2].value.elts[1], 2) + + self.assertSameDef(local_body[1].test, local_body[2].value.elts[0]) + + # Note: the function name is visible inside the function body. But it's + # a closure variable, not a local. + # + # Example: + # + # >>> def f(): + # ... print(f) + # >>> g = f + # >>> f = 'something else' + # >>> g() + # something else + # + self.assertHasDefinedIn(local_body[1], ('a', 'b')) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_fndefs.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_fndefs.py new file mode 100644 index 00000000..f49a964b --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_fndefs.py @@ -0,0 +1,178 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""An analysis that determines the reach of a function definition. + +A function definition is said to reach a statement if that function may exist +(and therefore may be called) when that statement executes. +""" + +import gast + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import cfg +from braket.experimental.autoqasm.autograph.pyct import transformer + + +class Definition(object): + """Definition objects describe a unique definition of a function.""" + + def __init__(self, def_node): + self.def_node = def_node + + +class _NodeState(object): + """Abstraction for the state of the CFG walk for reaching definition analysis. + + This is a value type. Only implements the strictly necessary operators. + + Attributes: + value: Dict[qual_names.QN, Set[Definition, ...]], the defined symbols and + their possible definitions + """ + + def __init__(self, init_from=None): + if init_from: + self.value = set(init_from) + else: + self.value = set() + + def __eq__(self, other): + return self.value == other.value + + def __ne__(self, other): + return self.value != other.value + + def __or__(self, other): + assert isinstance(other, _NodeState) + result = _NodeState(self.value) + result.value.update(other.value) + return result + + def __add__(self, value): + result = _NodeState(self.value) + result.value.add(value) + return result + + def __repr__(self): + return 'NodeState[%s]=%s' % (id(self), repr(self.value)) + + +class Analyzer(cfg.GraphVisitor): + """CFG visitor that determines reaching definitions at statement level.""" + + def __init__(self, graph, external_defs): + super(Analyzer, self).__init__(graph) + # This allows communicating that nodes have extra reaching definitions, + # e.g. those that a function closes over. + self.external_defs = external_defs + + def init_state(self, _): + return _NodeState() + + def visit_node(self, node): + prev_defs_out = self.out[node] + + if node is self.graph.entry: + defs_in = _NodeState(self.external_defs) + else: + defs_in = prev_defs_out + + for n in node.prev: + defs_in |= self.out[n] + + defs_out = defs_in + if isinstance(node.ast_node, (gast.Lambda, gast.FunctionDef)): + defs_out += node.ast_node + + self.in_[node] = defs_in + self.out[node] = defs_out + + return prev_defs_out != defs_out + + +class TreeAnnotator(transformer.Base): + """AST visitor that annotates each symbol name with its reaching definitions. + + Simultaneously, the visitor runs the dataflow analysis on each function node, + accounting for the effect of closures. For example: + + def foo(): + def f(): + pass + def g(): + # `def f` reaches here + """ + + def __init__(self, source_info, graphs): + super(TreeAnnotator, self).__init__(source_info) + self.graphs = graphs + self.allow_skips = False + self.current_analyzer = None + + def _proces_function(self, node): + parent_analyzer = self.current_analyzer + subgraph = self.graphs[node] + + if (self.current_analyzer is not None + and node in self.current_analyzer.graph.index): + cfg_node = self.current_analyzer.graph.index[node] + defined_in = self.current_analyzer.in_[cfg_node].value + else: + defined_in = () + + analyzer = Analyzer(subgraph, defined_in) + analyzer.visit_forward() + + self.current_analyzer = analyzer + node = self.generic_visit(node) + self.current_analyzer = parent_analyzer + return node + + def visit_FunctionDef(self, node): + return self._proces_function(node) + + def visit_Lambda(self, node): + return self._proces_function(node) + + def visit(self, node): + # This can happen before entering the top level function + if (self.current_analyzer is not None + and node in self.current_analyzer.graph.index): + cfg_node = self.current_analyzer.graph.index[node] + anno.setanno(node, anno.Static.DEFINED_FNS_IN, + self.current_analyzer.in_[cfg_node].value) + + extra_node = anno.getanno(node, anno.Basic.EXTRA_LOOP_TEST, default=None) + if extra_node is not None: + cfg_node = self.current_analyzer.graph.index[extra_node] + anno.setanno(extra_node, anno.Static.DEFINED_FNS_IN, + self.current_analyzer.in_[cfg_node].value) + + return super(TreeAnnotator, self).visit(node) + + +def resolve(node, source_info, graphs): + """Resolves reaching definitions for each symbol. + + Args: + node: ast.AST + source_info: transformer.SourceInfo + graphs: Dict[ast.FunctionDef, cfg.Graph] + Returns: + ast.AST + """ + visitor = TreeAnnotator(source_info, graphs) + node = visitor.visit(node) + return node diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_fndefs_test.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_fndefs_test.py new file mode 100644 index 00000000..ede7f41a --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_fndefs_test.py @@ -0,0 +1,54 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for reaching_fndefs module.""" + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import cfg +from braket.experimental.autoqasm.autograph.pyct import naming +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import qual_names +from braket.experimental.autoqasm.autograph.pyct import transformer +from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity +from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_definitions +from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_fndefs +from tensorflow.python.platform import test + + +class ReachingFndefsAnalyzerTest(test.TestCase): + + def _parse_and_analyze(self, test_fn): + # TODO(mdan): Use a custom FunctionTransformer here. + node, source = parser.parse_entity(test_fn, future_features=()) + entity_info = transformer.EntityInfo( + name=test_fn.__name__, + source_code=source, + source_file=None, + future_features=(), + namespace={}) + node = qual_names.resolve(node) + namer = naming.Namer({}) + ctx = transformer.Context(entity_info, namer, None) + node = activity.resolve(node, ctx) + graphs = cfg.build(node) + node = reaching_definitions.resolve(node, ctx, graphs) + node = reaching_fndefs.resolve(node, ctx, graphs) + return node + + def assertHasFnDefs(self, node): + anno.getanno(node, anno.Static.DEFINED_FNS_IN) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/type_inference.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/type_inference.py new file mode 100644 index 00000000..6504c2cc --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/type_inference.py @@ -0,0 +1,624 @@ +# Copyright 2020 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Type inference. + +This analysis annotates all symbols nodes of an AST with type information +extracted from static sources: + * type annotations + * global and local symbols visible to the function at analysis time + * literals + +Important: This analysis is static, and does not detect dynamic type changes. +The analysis attempts to use the values of external symbols, if available. These +values are also considered static for the purpose of analysis. + +Requires reaching function definitions analysis. +""" + +import itertools + +from typing import Any, Callable, Dict, Set + +import gast + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import cfg +from braket.experimental.autoqasm.autograph.pyct import qual_names +from braket.experimental.autoqasm.autograph.pyct import transformer +from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity +from braket.experimental.autoqasm.autograph.pyct.static_analysis import annos + + +class Resolver(object): + """Resolver objects handle the process of looking up actual names and types. + + Unless noted otherwise, all resolve_* methods: + * have a first namespace argument, mapping string to actual values + * have a second types_namespace argument, mapping string to actual inferred + types + * specify names as QN objects + * specify types as a Set of inferred types + + Unless noted otherwise, all resolve_* methods must return either: + * a set of `type` objects + * None + """ + + def res_name(self, ns, types_ns, name): + """Resolves the type/value an external (e.g. closure, global) variable. + + Args: + ns: namespace + types_ns: types namespace + name: symbol name + Returns: + Tuple (type, static_value). The first element is the type to use for + inferrence. The second is the static value to use. Return None to treat it + as unknown. + """ + raise NotImplementedError('subclasses must implement') + + def res_value(self, ns, value): + """Resolves the type a literal or static value.""" + raise NotImplementedError('subclasses must implement') + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + """Resolves the type of a (possibly annotated) function argument. + + Args: + ns: namespace + types_ns: types namespace + f_name: str, the function name + name: str, the argument name + type_anno: the type annotating the argument, if any + f_is_local: bool, whether the function is a local function + Returns: + Set of the argument types. + """ + raise NotImplementedError('subclasses must implement') + + def res_call(self, ns, types_ns, node, f_type, args, keywords): + """Resolves the return type an external function or method call. + + Args: + ns: namespace + types_ns: types namespace + node: str, the function name + f_type: types of the actual function being called, if known + args: types of each respective argument in node.args + keywords: types of each respective argument in node.keywords + + Returns: + Tuple (return_type, side_effect_types). The first element is just the + return types of the function. The second element is a map from + argument names to sets of types, and allow modelling side effects of + functions (for example via global or nonlocal). + """ + raise NotImplementedError('subclasses must implement') + + # TODO(mdan): Clean this up. + def res_slice(self, ns, types_ns, node_or_slice, value, slice_): + """Resolves the return type of slice operation.""" + raise NotImplementedError('subclasses must implement') + + def res_compare(self, ns, types_ns, node, left, right): + """Resolves the return type of a unary operation.""" + raise NotImplementedError('subclasses must implement') + + def res_unop(self, ns, types_ns, node, opnd): + """Resolves the return type of a unary operation.""" + raise NotImplementedError('subclasses must implement') + + def res_binop(self, ns, types_ns, node, left, right): + """Resolves the return type of a binary operation.""" + raise NotImplementedError('subclasses must implement') + + def res_list_literal(self, ns, elt_types): + """Resolves the type of a list literal from its elements.""" + raise NotImplementedError('subclasses must implement') + + +class _TypeMap(object): + """Abstraction for the state of the CFG walk for type inference. + + This is a value type. Only implements the strictly necessary operators. + + Attributes: + types: Dict[qual_names.QN, Set[Type]], mapping symbols to the set of + possible types. + """ + + def __init__(self, init_from=None): + if init_from: + assert isinstance(init_from, _TypeMap) + self.types = { + s: set(other_types) for s, other_types in init_from.types.items() + } + else: + self.types = {} + + def __eq__(self, other): + if frozenset(self.types.keys()) != frozenset(other.types.keys()): + return False + ret = all(self.types[s] == other.types[s] for s in self.types) + return ret + + def __ne__(self, other): + return not self.__eq__(other) + + def __or__(self, other): + assert isinstance(other, _TypeMap) + result = _TypeMap(self) + for s, other_types in other.types.items(): + if s not in result.types: + self_types = set() + result.types[s] = self_types + else: + self_types = result.types[s] + self_types.update(other_types) + return result + + def __repr__(self): + return 'SymbolTable {}'.format(self.types) + + +NO_VALUE = object() + + +class StmtInferrer(gast.NodeVisitor): + """Runs type inference on a single AST statement. + + This visitor annotates most nodes with type information. It also sets types + for the symbols modified by this statement in its types_out property. + + Note: this inferrer is able to capture side effects of functions, however, + these side effects will not be applied to the current expression. Doing so + would create too much of a dependence on the runtime's internal rules about + execution order. + Example: + + def f(): + nonlocal a + a = 1 + return a + + a = 0.0 + b = f() + a # a = float; side effect of f() ignored + print(a) # a = int; side effect of f() accounted for + """ + + def __init__(self, + resolver: Resolver, + scope: activity.Scope, + namespace: Dict[qual_names.QN, Any], + closure_types: Dict[qual_names.QN, Set[Any]], + types_in: _TypeMap): + self.resolver = resolver + self.scope = scope + self.namespace = namespace + self.closure_types = closure_types + self.types_in = types_in + self.new_symbols = {} + + # rvalue type. This property is set when encountering an assign operation, + # so that visiting nodes with Store ctx (typically found on left side of + # assignments) can infer the type they should receive. + self.rtype = None + + def visit(self, node): + types = super().visit(node) + if __debug__: + self._check_set(types) + if types is not None: + # TODO(mdan): Normalize by removing subtypes. + anno.setanno(node, anno.Static.TYPES, tuple(types)) + return types + + def _check_set(self, value): + if value is not None and not isinstance(value, set): + raise ValueError('{} method expected to return set, got {}'.format( + self.resolver, value)) + + def visit_Constant(self, node): + types = self.resolver.res_value(self.namespace, node.value) + if __debug__: + self._check_set(types) + return types + + def _apply_unpacking(self, node): + assert isinstance(node.ctx, gast.Store) + if self.rtype is not None: + original_stype = self.rtype + # TODO(mdan): Find a better way to express unpacking. + i_type = self.resolver.res_value(self.namespace, 0) + for i, elt in enumerate(node.elts): + self.rtype = self.resolver.res_slice( + self.namespace, self.types_in.types, i, original_stype, i_type) + self.visit(elt) + self.rtype = original_stype + return original_stype + return None + + def visit_Tuple(self, node): + if isinstance(node.ctx, gast.Load): + elt_types = () + for elt in node.elts: + types_ = self.visit(elt) + if types_ is None: + return None + elt_types += (types_,) + return set(itertools.product(*elt_types)) + return self._apply_unpacking(node) + + def visit_List(self, node): + if isinstance(node.ctx, gast.Load): + elt_types = tuple(self.visit(elt) for elt in node.elts) + return self.resolver.res_list_literal(self.namespace, elt_types) + return self._apply_unpacking(node) + + def visit_Set(self, node): + raise NotImplementedError() + + def visit_Name(self, node): + name = anno.getanno(node, anno.Basic.QN) + + if isinstance(node.ctx, gast.Load): + types = self.types_in.types.get(name, None) + if types is None: + if (name not in self.scope.bound) or (name in self.scope.nonlocals): + # TODO(mdan): Test with global variables. + if name in self.closure_types: + types = self.closure_types[name] + else: + types, value = self.resolver.res_name( + self.namespace, self.types_in.types, name) + if value is not None: + anno.setanno(node, anno.Static.VALUE, value) + + elif isinstance(node.ctx, gast.Param): + # The direct parent it the whole function scope. See activity.py. + f_is_local = self.scope.parent.parent is not None + + type_name = anno.getanno(node.annotation, anno.Basic.QN, None) + types = self.resolver.res_arg(self.namespace, self.types_in.types, + self.scope.function_name, name, type_name, + f_is_local) + if types is not None: + self.new_symbols[name] = types + + elif isinstance(node.ctx, gast.Store): + if self.rtype is not None: + self.new_symbols[name] = self.rtype + types = self.rtype + + else: + assert False, 'unknown ctx' + + if __debug__: + self._check_set(types) + + return types + + def visit_Attribute(self, node): + parent_types = self.visit(node.value) + + # Attempt to use the static value if known. + parent_value = anno.Static.VALUE.of(node.value, None) + if parent_value is not None: + static_value = getattr(parent_value, node.attr, NO_VALUE) + + if static_value is NO_VALUE: + # Unexpected failure to resolve attribute. Ask the resolver about the + # full name instead. + types, static_value = self.resolver.res_name( + self.namespace, self.types_in, anno.Basic.QN.of(node)) + anno.setanno(node, anno.Static.VALUE, static_value) + if __debug__: + self._check_set(types) + return types + + else: + # Fall back to the type if that is known. + if parent_types is None: + return None + + inferred_values = [getattr(t, node.attr, None) for t in parent_types] + if not inferred_values: + return None + + static_value = inferred_values[0] + if static_value is None: + return None + + if any(v is not static_value for v in inferred_values[1:]): + # Static value not stable, assume it's dynamic. + return None + + types = self.resolver.res_value(self.namespace, static_value) + anno.setanno(node, anno.Static.VALUE, static_value) + + if __debug__: + self._check_set(types) + + return types + + def visit_FunctionDef(self, node): + f_name = qual_names.QN(node.name) + + if node.decorator_list: + raise NotImplementedError('decorators: {}'.format(node.decorator_list)) + + ret_types = None + if node.returns: + ret_types, _ = self.resolver.res_name( + self.namespace, self.types_in.types, anno.Basic.QN.of(node.returns)) + if __debug__: + self._check_set(ret_types) + + if ret_types is None: + ret_types = {Any} + + f_types = set() + for rt in ret_types: + f_types.add(Callable[[Any], rt]) + + self.new_symbols[f_name] = f_types + # The definition of a function is an expression, hence has no return value. + return None + + def _resolve_typed_callable(self, f_types, arg_types, keyword_types): + ret_types = set() + for t in f_types: + + if isinstance(t, Callable): + # Note: these are undocummented - may be version-specific! + # Callable[[x], y]: __args__ are (x, y) + args = t.__args__ + if args: + ret_types.add(args[-1]) + else: + ret_types.add(Any) + else: + raise NotImplementedError('callable type {}'.format(type(t))) + + # Side effects can not be inferred based on type alone. + side_effects = None + return ret_types, side_effects + + def visit_Call(self, node): + self.visit(node.func) + + f_name = anno.Basic.QN.of(node.func) + arg_types = [self.visit(a) for a in node.args] + keyword_types = [self.visit(kw.value) for kw in node.keywords] + + if f_name in self.scope.bound: + # Local function, use local type definitions, if available. + f_type = self.types_in.types.get(f_name, None) + if f_type is None: + # No static type info available, nothing more to do. + ret_type, side_effects = None, None + else: + ret_type, side_effects = self._resolve_typed_callable( + f_type, arg_types, keyword_types) + + else: + # Nonlocal function, resolve externally. + f_type = anno.Static.TYPES.of(node.func, None) + ret_type, side_effects = self.resolver.res_call(self.namespace, + self.types_in.types, node, + f_type, arg_types, + keyword_types) + + if __debug__: + self._check_set(ret_type) + if side_effects: + if not isinstance(side_effects, dict): + raise ValueError( + 'side effects must be dict, got {}'.format(side_effects)) + for k, v in side_effects.items(): + if not isinstance(k, qual_names.QN): + raise ValueError('side effect keys must be QNs, got {}'.format(k)) + self._check_set(v) + + if side_effects: + self.new_symbols.update(side_effects) + return ret_type + + def visit_Expr(self, node): + return self.visit(node.value) + + def visit_Assign(self, node): + self.rtype = self.visit(node.value) + + for t in node.targets: + self.visit(t) + + self.rtype = None + + def visit_Subscript(self, node): + val_types = self.visit(node.value) + slice_types = self.visit(node.slice) + + if val_types is None or slice_types is None: + return None + + types = self.resolver.res_slice( + self.namespace, self.types_in.types, node, val_types, slice_types) + + if __debug__: + self._check_set(types) + + return types + + def visit_Compare(self, node): + left_types = self.visit(node.left) + right_types = [self.visit(c) for c in node.comparators] + + if left_types is None or any(t is None for t in right_types): + return None + + types = self.resolver.res_compare( + self.namespace, self.types_in.types, node, left_types, right_types) + + if __debug__: + self._check_set(types) + + return types + + def visit_BinOp(self, node): + left_types = self.visit(node.left) + right_types = self.visit(node.right) + + if left_types is None or right_types is None: + return None + + types = self.resolver.res_binop( + self.namespace, self.types_in.types, node, left_types, right_types) + + if __debug__: + self._check_set(types) + + return types + + def visit_UnaryOp(self, node): + opnd_types = self.visit(node.operand) + + if opnd_types is None: + return None + + types = self.resolver.res_unop( + self.namespace, self.types_in.types, node, opnd_types) + + if __debug__: + self._check_set(types) + + return types + + +class Analyzer(cfg.GraphVisitor): + """CFG visitor that propagates type information across statements.""" + + def __init__(self, graph, resolver, namespace, scope, closure_types): + """Creates a new analyzer. + + Args: + graph: cfg.Graph + resolver: Resolver + namespace: Dict[str, Any] + scope: activity.Scope + closure_types: Dict[QN, Set] + """ + super(Analyzer, self).__init__(graph) + self.resolver = resolver + self.namespace = namespace + self.scope = scope + self.closure_types = closure_types + + context_types = { + n: t for n, t in closure_types.items() if n not in scope.bound + } + if context_types: + self.context_types = _TypeMap() + self.context_types.types = context_types + else: + self.context_types = None + + def init_state(self, _): + return _TypeMap() + + def _update_closure_types(self, ast_node, types): + existing_types = anno.Static.CLOSURE_TYPES.of(ast_node, None) + + if existing_types is None: + existing_types = {} + anno.Static.CLOSURE_TYPES.add_to(ast_node, existing_types) + + for k, v in types.types.items(): + if k in existing_types: + existing_types[k].update(v) + else: + existing_types[k] = set(v) + + def visit_node(self, node): + prev_types_out = self.out[node] + + types_in = _TypeMap() + for n in node.prev: + types_in |= self.out[n] + if (self.context_types is not None) and (node is self.graph.entry): + types_in |= self.context_types + + types_out = _TypeMap(types_in) + ast_node = node.ast_node + + inferrer = StmtInferrer(self.resolver, self.scope, self.namespace, + self.closure_types, types_in) + inferrer.visit(ast_node) + types_out.types.update(inferrer.new_symbols) + + reaching_fndefs = anno.Static.DEFINED_FNS_IN.of(ast_node) + node_scope = anno.Static.SCOPE.of(ast_node, None) + if node_scope is not None: + # TODO(mdan): Check that it's actually safe to skip nodes without scope. + reads = {str(qn) for qn in node_scope.read} + for def_node in reaching_fndefs: + if def_node.name in reads: + self._update_closure_types(def_node, types_out) + + self.in_[node] = types_in + self.out[node] = types_out + + return prev_types_out != types_out + + +class FunctionVisitor(transformer.Base): + """AST visitor that applies type inference to each function separately.""" + + def __init__(self, source_info, graphs, resolver): + super(FunctionVisitor, self).__init__(source_info) + self.graphs = graphs + self.resolver = resolver + + def visit_FunctionDef(self, node): + subgraph = self.graphs[node] + scope = anno.getanno(node, annos.NodeAnno.ARGS_AND_BODY_SCOPE) + closure_types = anno.getanno(node, anno.Static.CLOSURE_TYPES, {}) + + analyzer = Analyzer(subgraph, self.resolver, self.ctx.info.namespace, scope, + closure_types) + analyzer.visit_forward() + + # Recursively process any remaining subfunctions. + node.body = self.visit_block(node.body) + + return node + + +def resolve(node, source_info, graphs, resolver): + """Performs type inference. + + Args: + node: ast.AST + source_info: transformer.SourceInfo + graphs: Dict[ast.FunctionDef, cfg.Graph] + resolver: Resolver + + Returns: + ast.AST + """ + visitor = FunctionVisitor(source_info, graphs, resolver) + node = visitor.visit(node) + return node diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/type_inference_test.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/type_inference_test.py new file mode 100644 index 00000000..4dc4dbe0 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/type_inference_test.py @@ -0,0 +1,942 @@ +# Copyright 2020 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for type_inference module.""" + +from typing import Any, Callable, List + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import cfg +from braket.experimental.autoqasm.autograph.pyct import qual_names +from braket.experimental.autoqasm.autograph.pyct import transpiler +from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity +from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_definitions +from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_fndefs +from braket.experimental.autoqasm.autograph.pyct.static_analysis import type_inference +from tensorflow.python.platform import test + + +class BasicTestResolver(type_inference.Resolver): + """A very basic resolver for testing.""" + + def res_name(self, ns, types_ns, name): + str_name = str(name) + if str_name == 'int': + return {int}, int + return {type(ns[str_name])}, ns[str_name] + + def res_value(self, ns, value): + return {type(value)} + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + if type_anno is None: + return None + return {str(type_anno)} + + +class TestTranspiler(transpiler.GenericTranspiler): + + def __init__(self, resolver_type): + super().__init__() + self.resolver = resolver_type() + + def get_transformed_name(self, _): + return 'test_item' + + def transform_ast(self, node, ctx): + node = qual_names.resolve(node) + node = activity.resolve(node, ctx) + graphs = cfg.build(node) + node = reaching_definitions.resolve(node, ctx, graphs) + node = reaching_fndefs.resolve(node, ctx, graphs) + node = type_inference.resolve(node, ctx, graphs, self.resolver) + return node + + +class TypeInferenceAnalyzerTest(test.TestCase): + + def assertTypes(self, node, expected): + if not isinstance(expected, tuple): + expected = expected, + self.assertSetEqual( + set(anno.getanno(node, anno.Static.TYPES)), set(expected)) + + def assertClosureTypes(self, node, expected): + actual = anno.getanno(node, anno.Static.CLOSURE_TYPES) + actual = {str(k): v for k, v in actual.items()} + for k, v in expected.items(): + self.assertIn(k, actual) + self.assertEqual(actual[k], v) + + def test_no_inference_on_unknown_operand_types(self): + + class Resolver(type_inference.Resolver): + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + return None + + def test_fn(a, b): + return a < b, a - b + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + # With no information on operand types, the operators will infer nothing. + self.assertFalse( + anno.hasanno(fn_body[0].value.elts[0], anno.Static.TYPES)) + self.assertFalse( + anno.hasanno(fn_body[0].value.elts[1], anno.Static.TYPES)) + + def test_resolver_output_checked(self): + + class Resolver(type_inference.Resolver): + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + return 1 + + def test_fn(a): + del a + pass + + with self.assertRaisesRegex(ValueError, 'expected to return set'): + TestTranspiler(Resolver).transform(test_fn, None) + + def test_argument(self): + + test_self = self + + class Resolver(type_inference.Resolver): + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + test_self.assertFalse(f_is_local) + if name == qual_names.QN('a'): + test_self.assertEqual(type_anno, qual_names.QN('int')) + return {str(name) + '_type'} + + def test_fn(a: int, b): + return a, b + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].value.elts[0], 'a_type') + self.assertTypes(fn_body[0].value.elts[1], 'b_type') + + def test_argument_of_local_function(self): + + test_self = self + + class Resolver(type_inference.Resolver): + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + if f_name == 'test_fn': + test_self.assertFalse(f_is_local) + test_self.assertEqual(name, qual_names.QN('a')) + test_self.assertEqual(type_anno, qual_names.QN('int')) + elif f_name == 'foo': + test_self.assertTrue(f_is_local) + if name == qual_names.QN('x'): + test_self.assertEqual(type_anno, qual_names.QN('float')) + elif name == qual_names.QN('y'): + test_self.assertIsNone(type_anno) + else: + test_self.fail('unexpected argument {} for {}'.format(name, f_name)) + else: + test_self.fail('unexpected function name {}'.format(f_name)) + return {str(name) + '_type'} + + def test_fn(a: int): + + def foo(x: float, y): + return x, y + + return foo(a, a) + + tr = TestTranspiler(Resolver) + node, _ = tr.transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].body[0].value, (('x_type', 'y_type'),)) + self.assertTypes(fn_body[0].body[0].value.elts[0], 'x_type') + self.assertTypes(fn_body[0].body[0].value.elts[1], 'y_type') + + def test_assign_straightline(self): + + def test_fn(a: int, c: float): + b = a + return a, b, c + + node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].targets[0], 'int') + self.assertTypes(fn_body[0].value, 'int') + self.assertTypes(fn_body[1].value.elts[0], 'int') + self.assertTypes(fn_body[1].value.elts[1], 'int') + self.assertTypes(fn_body[1].value.elts[2], 'float') + + def test_expr(self): + + test_self = self + + class Resolver(type_inference.Resolver): + + def res_value(self, ns, value): + test_self.assertEqual(value, tc.a) + return {str} + + def res_name(self, ns, types_ns, name): + test_self.assertEqual(name, qual_names.QN('tc')) + return {TestClass}, tc + + def res_call(self, ns, types_ns, node, f_type, args, keywords): + test_self.assertEqual(f_type, (str,)) + return {int}, None + + class TestClass: + + def a(self): + pass + + tc = TestClass() + + def test_fn(): + tc.a() + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertEqual( + anno.getanno(fn_body[0].value.func, anno.Static.VALUE), tc.a) + self.assertTypes(fn_body[0].value.func, str) + self.assertTypes(fn_body[0].value, int) + self.assertTypes(fn_body[0], int) + + def test_assign_overwriting(self): + + def test_fn(a: int, b: float): + c = a + c = b + return c + + node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].targets[0], 'int') + self.assertTypes(fn_body[0].value, 'int') + self.assertTypes(fn_body[1].targets[0], 'float') + self.assertTypes(fn_body[1].value, 'float') + + def test_dynamic_attribute_of_static_value(self): + + test_self = self + + class Resolver(type_inference.Resolver): + + def res_value(self, ns, value): + test_self.assertEqual(value, tc.a) + return {int} + + def res_name(self, ns, types_ns, name): + test_self.assertEqual(name, qual_names.QN('tc')) + return {TestClass}, tc + + class TestClass: + + def __init__(self): + self.a = 1 + + tc = TestClass() + + def test_fn(): + return tc.a + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].value.value, TestClass) + self.assertTypes(fn_body[0].value, int) + self.assertIs(anno.getanno(fn_body[0].value.value, anno.Static.VALUE), tc) + self.assertEqual(anno.getanno(fn_body[0].value, anno.Static.VALUE), tc.a) + + def test_static_attribute_of_typed_value(self): + + test_self = self + + class TestClass: + + a = 1 + + tc = TestClass() + + class Resolver(type_inference.Resolver): + + def res_name(self, ns, types_ns, name): + test_self.assertEqual(name, qual_names.QN('tc')) + return {TestClass}, None + + def res_value(self, ns, value): + test_self.assertIs(value, tc.a) + return {str} + + def test_fn(): + return tc.a + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].value.value, TestClass) + self.assertTypes(fn_body[0].value, str) # Resolver is SOT + self.assertFalse(anno.hasanno(fn_body[0].value.value, anno.Static.VALUE)) + self.assertEqual(anno.getanno(fn_body[0].value, anno.Static.VALUE), 1) + + def test_static_attribute_of_ambiguous_type(self): + + test_self = self + + class TestClass1: + + a = 1 + + class TestClass2: + + a = 2 + + tc = TestClass1() + + class Resolver(type_inference.Resolver): + + def res_name(self, ns, types_ns, name): + test_self.assertEqual(name, qual_names.QN('tc')) + return {TestClass1, TestClass2}, None + + def res_value(self, ns, value): + test_self.assertIn(value, (1, 2)) + return {str} + + def test_fn(): + return tc.a + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].value.value, (TestClass1, TestClass2)) + self.assertFalse(anno.hasanno(fn_body[0].value, anno.Static.TYPES)) + self.assertFalse(anno.hasanno(fn_body[0].value.value, anno.Static.VALUE)) + self.assertFalse(anno.hasanno(fn_body[0].value, anno.Static.VALUE)) + + def test_property_of_typed_value(self): + + test_self = self + + class TestClass: + + @property + def a(self): + return 1 + + tc = TestClass() + + class Resolver(type_inference.Resolver): + + def res_name(self, ns, types_ns, name): + test_self.assertEqual(name, qual_names.QN('tc')) + return {TestClass}, None + + def res_value(self, ns, value): + test_self.assertIs(value, TestClass.a) + test_self.assertNotEqual(value, 1) # Can't evaluate property of class. + return {property} + + def test_fn(): + return tc.a + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].value.value, TestClass) + self.assertTypes(fn_body[0].value, property) + self.assertFalse(anno.hasanno(fn_body[0].value.value, anno.Static.VALUE)) + self.assertEqual( + anno.getanno(fn_body[0].value, anno.Static.VALUE), TestClass.a) + + def test_dynamic_attribute_of_typed_value(self): + + test_self = self + + class TestClass: + + def __init__(self): + self.a = 1 + + tc = TestClass() + + class Resolver(type_inference.Resolver): + + def res_name(self, ns, types_ns, name): + test_self.assertEqual(name, qual_names.QN('tc')) + return {TestClass}, None + + def test_fn(): + return tc.a + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].value.value, TestClass) + self.assertFalse(anno.hasanno(fn_body[0].value, anno.Static.TYPES)) + self.assertFalse(anno.hasanno(fn_body[0].value.value, anno.Static.VALUE)) + self.assertFalse(anno.hasanno(fn_body[0].value, anno.Static.VALUE)) + + def test_external_value(self): + + a = 'foo' + + def test_fn(): + b = a + return b + + node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].targets[0], str) + self.assertTypes(fn_body[1].value, str) + + def test_external_function(self): + + test_self = self + + class Resolver(type_inference.Resolver): + + def res_name(self, ns, types_ns, name): + test_self.assertEqual(name, qual_names.QN('g')) + return {str}, g + + def res_call(self, ns, types_ns, node, f_type, args, keywords): + test_self.assertEqual(f_type, (str,)) + test_self.assertEqual( + anno.getanno(node.func, anno.Basic.QN), qual_names.QN('g')) + return {float}, None + + def g() -> float: + return 1.0 + + def test_fn(): + a = g() + return a + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].value.func, str) + self.assertTypes(fn_body[0].targets[0], float) + self.assertTypes(fn_body[1].value, float) + + def test_external_function_side_effects(self): + + test_self = self + + class Resolver(type_inference.Resolver): + + def res_name(self, ns, types_ns, name): + test_self.assertEqual(name, qual_names.QN('g')) + return None, g + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + return {str(type_anno)} + + def res_call(self, ns, types_ns, node, f_type, args, keywords): + test_self.assertIsNone(f_type) + return None, {qual_names.QN('x'): {str}} + + def g(): + # The resolver will pretend that this function has the following body: + # + # nonlocal x + # x = 'a' + pass + + def test_fn(x: int): + y = x + g() + return x, y + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].targets[0], 'int') + self.assertTypes(fn_body[0].value, 'int') + self.assertTypes(fn_body[2].value.elts[0], str) + self.assertTypes(fn_body[2].value.elts[1], 'int') + + def test_local_function_closure(self): + + def test_fn(x: int): + + def foo(): + return x + + foo() + + node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].body[0].value, 'int') + self.assertClosureTypes(fn_body[0], {'x': {'int'}}) + + def test_local_function_closure_nested(self): + + def test_fn(x: int): + + def foo(): + + def bar(): + return x + + bar() + + foo() + + node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].body[0].body[0].value, 'int') + self.assertClosureTypes(fn_body[0], {'x': {'int'}}) + self.assertClosureTypes(fn_body[0].body[0], {'x': {'int'}}) + + def test_local_function_closure_mutable_var(self): + + def test_fn(x: int): + + def foo(): + nonlocal x + return x + + foo() + + node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].body[1].value, 'int') + self.assertClosureTypes(fn_body[0], {'x': {'int'}}) + + def test_local_function_closure_ignored_for_bound_symbols(self): + + def test_fn(x: float): # pylint:disable=unused-argument + + def foo(): + x = x + 1 # pylint:disable=used-before-assignment + + foo() + + node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) + fn_body = node.body + + self.assertFalse( + anno.hasanno(fn_body[0].body[0].value.left, anno.Static.TYPES)) + self.assertClosureTypes(fn_body[0], {'x': {'float'}}) + + def test_local_function_closure_uses_call_site_types(self): + + def test_fn(x: int): + + def foo(): + return x + + x = 1.0 + foo() + + node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].body[0].value, float) + self.assertTypes(fn_body[1].targets[0], float) + self.assertClosureTypes(fn_body[0], {'x': {float}}) + + def test_local_function_hides_locals(self): + + def test_fn(a: int): # pylint:disable=unused-argument + + def local_fn(v): + a = v + return a + + local_fn(1) + + node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) + fn_body = node.body + + self.assertFalse( + anno.hasanno(fn_body[0].body[0].targets[0], anno.Static.TYPES)) + + def test_local_function_type(self): + + def test_fn(x: int): + + def foo() -> int: + return x + + foo() + + node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[1].value.func, Callable[[Any], int]) + self.assertTypes(fn_body[1].value, int) + self.assertTypes(fn_body[1], int) + + def test_side_effects_on_arg_function_closure(self): + + test_self = self + + class Resolver(type_inference.Resolver): + + def res_name(self, ns, types_ns, name): + test_self.assertEqual(name, qual_names.QN('g')) + return {Callable[[Callable], None]}, g + + def res_value(self, ns, value): + test_self.assertEqual(value, 1.0) + return {float} + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + return {str(type_anno)} + + def res_call(self, ns, types_ns, node, f_type, args, keywords): + test_self.assertEqual(node.func.id, 'g') + test_self.assertEqual(f_type, (Callable[[Callable], None],)) + return None, {qual_names.QN('x'): {str}} + + def g(foo): + # The resolver will convey that this function has the following body: + # + # nonlocal x + # x = 'a' + # foo() + del foo + pass + + def test_fn(x: int): # pylint:disable=unused-argument + + def foo(): + return x + + x = 1.0 + g(foo) + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].body[0].value, str) + + def test_subscript(self): + + test_self = self + + class Resolver(type_inference.Resolver): + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + return {list} + + def res_value(self, ns, value): + return {int} + + def res_slice(self, ns, types_ns, node, value, slice_): + test_self.assertSetEqual(value, {list}) + test_self.assertSetEqual(slice_, {int}) + return {str} + + def test_fn(a): + return a[1] + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].value, str) + self.assertTypes(fn_body[0].value.value, list) + self.assertTypes(fn_body[0].value.slice, int) + + def test_tuple_unpacking(self): + + test_self = self + + class Resolver(type_inference.Resolver): + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + return {list} + + def res_value(self, ns, value): + return {int} + + def res_slice(self, ns, types_ns, node_or_slice, value, slice_): + test_self.assertIn(node_or_slice, (0, 1)) + test_self.assertSetEqual(value, {list}) + test_self.assertSetEqual(slice_, {int}) + if node_or_slice == 0: + return {float} + else: + return {str} + + def test_fn(t): + a, b = t + return a, b + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[1].value, ((float, str),)) + self.assertTypes(fn_body[1].value.elts[0], float) + self.assertTypes(fn_body[1].value.elts[1], str) + + def test_compare(self): + + test_self = self + + class Resolver(type_inference.Resolver): + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + return {int} + + def res_compare(self, ns, types_ns, node, left, right): + test_self.assertSetEqual(left, {int}) + test_self.assertListEqual(right, [{int}]) + return {bool} + + def test_fn(a, b): + return a < b + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].value, bool) + self.assertTypes(fn_body[0].value.left, int) + self.assertTypes(fn_body[0].value.comparators[0], int) + + def test_binop(self): + + test_self = self + + class Resolver(type_inference.Resolver): + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + return {list} + + def res_binop(self, ns, types_ns, node, left, right): + test_self.assertSetEqual(left, {list}) + test_self.assertSetEqual(right, {list}) + return {float} + + def test_fn(a, b): + return a @ b + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].value, float) + self.assertTypes(fn_body[0].value.left, list) + self.assertTypes(fn_body[0].value.right, list) + + def test_unop(self): + + class Resolver(type_inference.Resolver): + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + return {list} + + def res_unop(self, ns, types_ns, node, opnd): + return {float} + + def test_fn(a): + return -a + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].value, float) + self.assertTypes(fn_body[0].value.operand, list) + + def test_tuple_literal(self): + + class Resolver(type_inference.Resolver): + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + return {int} + + def test_fn(a, b): + return a, b + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].value, ((int, int),)) + self.assertTypes(fn_body[0].value.elts[0], int) + self.assertTypes(fn_body[0].value.elts[1], int) + + def test_list_literal(self): + + class Resolver(type_inference.Resolver): + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + return {int} + + def res_list_literal(self, ns, elt_types): + all_types = set() + for s in elt_types: + all_types |= s + return {List[t] for t in all_types} + + def test_fn(a, b): + return [a, b] + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[0].value, List[int]) + self.assertTypes(fn_body[0].value.elts[0], int) + self.assertTypes(fn_body[0].value.elts[1], int) + + def test_tuple_unpacking_syntactic(self): + + test_self = self + + class Resolver(type_inference.Resolver): + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + if name == qual_names.QN('a'): + return {int} + else: + return {float} + + def res_value(self, ns, value): + test_self.assertIn(value, (0, 1)) + return int + + def res_slice(self, ns, types_ns, node_or_slice, value, slice_): + test_self.assertIn(node_or_slice, (0, 1)) + test_self.assertSetEqual(value, {(int, float)}) + test_self.assertEqual(slice_, int) + return {t[node_or_slice] for t in value} + + def test_fn(a, b): + c, d = a, b + return c, d + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[1].value, ((int, float),)) + self.assertTypes(fn_body[1].value.elts[0], int) + self.assertTypes(fn_body[1].value.elts[1], float) + + def test_tuple_unpacking_operational(self): + + test_self = self + + class Resolver(type_inference.Resolver): + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + return {(int, float)} + + def res_value(self, ns, value): + test_self.assertIn(value, (0, 1)) + return int + + def res_slice(self, ns, types_ns, node_or_slice, value, slice_): + test_self.assertIn(node_or_slice, (0, 1)) + test_self.assertSetEqual(value, {(int, float)}) + test_self.assertEqual(slice_, int) + return {t[node_or_slice] for t in value} + + def test_fn(a): + c, d = a + return c, d + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + self.assertTypes(fn_body[1].value, ((int, float),)) + self.assertTypes(fn_body[1].value.elts[0], int) + self.assertTypes(fn_body[1].value.elts[1], float) + + def test_list_expansion_syntactic(self): + + test_self = self + + class Resolver(type_inference.Resolver): + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + if name == qual_names.QN('a'): + return {int} + else: + return {float} + + def res_value(self, ns, value): + test_self.assertIn(value, (0, 1)) + return int + + def res_slice(self, ns, types_ns, node_or_slice, value, slice_): + test_self.assertIn(node_or_slice, (0, 1)) + test_self.assertSetEqual(value, {(int, float)}) + test_self.assertEqual(slice_, int) + return {t[node_or_slice] for t in value} + + def test_fn(a, b): + [c, d] = a, b + return c, d + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + # TODO(mdan): Whether it's List or Tuple might be open for interpretation. + self.assertTypes(fn_body[1].value, ((int, float),)) + self.assertTypes(fn_body[1].value.elts[0], int) + self.assertTypes(fn_body[1].value.elts[1], float) + + def test_list_expansion_operational(self): + + test_self = self + + class Resolver(type_inference.Resolver): + + def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): + if name == qual_names.QN('a'): + return {int} + else: + return {float} + + def res_value(self, ns, value): + test_self.assertIn(value, (0, 1)) + return int + + def res_slice(self, ns, types_ns, node_or_slice, value, slice_): + test_self.assertIn(node_or_slice, (0, 1)) + test_self.assertSetEqual(value, {(int, float)}) + test_self.assertEqual(slice_, int) + return {t[node_or_slice] for t in value} + + def test_fn(a, b): + [c, d] = a, b + return c, d + + node, _ = TestTranspiler(Resolver).transform(test_fn, None) + fn_body = node.body + + # TODO(mdan): Whether it's List or Tuple might be open for interpretation. + self.assertTypes(fn_body[1].value, ((int, float),)) + self.assertTypes(fn_body[1].value.elts[0], int) + self.assertTypes(fn_body[1].value.elts[1], float) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/templates.py b/src/braket/experimental/autoqasm/autograph/pyct/templates.py new file mode 100644 index 00000000..068a36b7 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/templates.py @@ -0,0 +1,290 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""AST conversion templates. + +Adapted from Tangent. +""" + +import ast +import textwrap + +import gast + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import ast_util +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import qual_names + + +class ContextAdjuster(gast.NodeTransformer): + """Adjusts the ctx field of nodes to ensure consistency. + + This transformer can change the ctx fields of a variable, tuple and other + AST elements that allow one, based on whether the element is being read or + written. + """ + + def __init__(self, override_value): + self._ctx_override = override_value + + def visit(self, node): + original_override = self._ctx_override + node = super(ContextAdjuster, self).visit(node) + if hasattr(node, 'ctx'): + assert node.ctx is not None, 'node {} has ctx unset'.format(node) + self._ctx_override = original_override + return node + + def _apply_override(self, node): + if self._ctx_override is not None: + node.ctx = self._ctx_override() + + def visit_Attribute(self, node): + self._apply_override(node) + self._ctx_override = gast.Load + node = self.generic_visit(node) + return node + + def visit_Tuple(self, node): + self._apply_override(node) + return self.generic_visit(node) + + def visit_List(self, node): + self._apply_override(node) + return self.generic_visit(node) + + def visit_Name(self, node): + self._apply_override(node) + return self.generic_visit(node) + + def visit_Call(self, node): + self._apply_override(node) + # We may be able to override these to Load(), but for now it's simpler + # to just assert that they're set. + self._ctx_override = None + return self.generic_visit(node) + + def visit_Dict(self, node): + # We may be able to override these to Load(), but for now it's simpler + # to just assert that they're set. + self._ctx_override = None + return self.generic_visit(node) + + def visit_Subscript(self, node): + self._apply_override(node) + self._ctx_override = gast.Load + node.value = self.visit(node.value) + return self.generic_visit(node) + + def visit_comprehension(self, node): + # We may be able to override some of these, but for now it's simpler + # to just assert that they're set. + self._ctx_override = None + return self.generic_visit(node) + + def visit_Lambda(self, node): + # We may be able to override some of these, but for now it's simpler + # to just assert that they're set. + self._ctx_override = None + return self.generic_visit(node) + + +class ReplaceTransformer(gast.NodeTransformer): + """Replace AST nodes.""" + + def __init__(self, replacements): + """Create a new ReplaceTransformer. + + Args: + replacements: A mapping from placeholder names to (lists of) AST nodes + that these placeholders will be replaced by. + """ + self.replacements = replacements + self.in_replacements = False + self.preserved_annos = { + anno.Basic.DIRECTIVES, + anno.Basic.EXTRA_LOOP_TEST, + anno.Basic.ORIGIN, + anno.Basic.SKIP_PROCESSING, + anno.Static.ORIG_DEFINITIONS, + 'function_context_name', + } + + def _prepare_replacement(self, replaced, key): + """Prepares a replacement AST that's safe to swap in for a node. + + Args: + replaced: ast.AST, the node being replaced + key: Hashable, the key of the replacement AST + Returns: + ast.AST, the replacement AST + """ + repl = self.replacements[key] + + new_nodes = ast_util.copy_clean(repl, preserve_annos=self.preserved_annos) + if isinstance(new_nodes, gast.AST): + new_nodes = [new_nodes] + + return new_nodes + + def visit_Expr(self, node): + # When replacing a placeholder with an entire statement, the replacement + # must stand on its own and not be wrapped in an Expr. + new_value = self.visit(node.value) + if new_value is node.value: + return node + return new_value + + def visit_keyword(self, node): + if node.arg not in self.replacements: + return self.generic_visit(node) + + repl = self._prepare_replacement(node, node.arg) + if isinstance(repl, gast.keyword): + return repl + elif (repl and isinstance(repl, (list, tuple)) and + all(isinstance(r, gast.keyword) for r in repl)): + return repl + # TODO(mdan): We may allow replacing with a string as well. + # For example, if one wanted to replace foo with bar in foo=baz, then + # we could allow changing just node arg, so that we end up with bar=baz. + raise ValueError( + 'a keyword argument may only be replaced by another keyword or a ' + 'non-empty list of keywords. Found: {} for keyword {}'.format( + repl, node.arg)) + + def visit_FunctionDef(self, node): + node = self.generic_visit(node) + if node.name not in self.replacements: + return node + + repl = self.replacements[node.name] + if not isinstance(repl, (gast.Name, ast.Name)): + raise ValueError( + 'a function name can only be replaced by a Name node. Found: %s' % + repl) + node.name = repl.id + return node + + def visit_Attribute(self, node): + node = self.generic_visit(node) + if node.attr not in self.replacements: + return node + + repl = self.replacements[node.attr] + if not isinstance(repl, gast.Name): + raise ValueError( + 'An attribute can only be replaced by a Name node. Found: %s' % repl) + node.attr = repl.id + return node + + def visit_Name(self, node): + if node.id not in self.replacements: + return node + + new_nodes = self._prepare_replacement(node, node.id) + + if not new_nodes: + return new_nodes + + # Preserve the target context. + adjuster = ContextAdjuster(type(node.ctx)) + for n in new_nodes: + if hasattr(n, 'ctx'): + adjuster.visit(n) + + if len(new_nodes) == 1: + new_nodes, = new_nodes + + return new_nodes + + +def _convert_to_ast(n): + """Converts from a known data type to AST.""" + # Note: When generating AST nodes from strings/QNs in isolation, ctx is + # unknown. ctx must be filled in according to the template being used. + # See ReplaceTransformer.visit_Name. + if isinstance(n, str): + return gast.Name(id=n, ctx=None, annotation=None, type_comment=None) + if isinstance(n, qual_names.QN): + return n.ast() + if isinstance(n, list): + return [_convert_to_ast(e) for e in n] + if isinstance(n, tuple): + return tuple(_convert_to_ast(e) for e in n) + return n + + +def replace(template, **replacements): + """Replaces placeholders in a Python template. + + AST Name and Tuple nodes always receive the context that inferred from + the template. However, when replacing more complex nodes (that can potentially + contain Name children), then the caller is responsible for setting the + appropriate context. + + Args: + template: A string representing Python code. Any symbol name can be used + that appears in the template code can be used as placeholder. + **replacements: A mapping from placeholder names to (lists of) AST nodes + that these placeholders will be replaced by. String values are also + supported as a shorthand for AST Name nodes with the respective ID. + + Returns: + An AST node or list of AST nodes with the replacements made. If the + template was a function, a list will be returned. If the template was a + node, the same node will be returned. If the template was a string, an + AST node will be returned (a `Module` node in the case of a multi-line + string, an `Expr` node otherwise). + + Raises: + ValueError: if the arguments are incorrect. + """ + if not isinstance(template, str): + raise ValueError('Expected string template, got %s' % type(template)) + for k in replacements: + replacements[k] = _convert_to_ast(replacements[k]) + template_str = parser.STANDARD_PREAMBLE + textwrap.dedent(template) + nodes = parser.parse( + template_str, + preamble_len=parser.STANDARD_PREAMBLE_LEN, + single_node=False) + results = [] + for node in nodes: + node = ReplaceTransformer(replacements).visit(node) + if isinstance(node, (list, tuple)): + results.extend(node) + else: + results.append(node) + results = [qual_names.resolve(r) for r in results] + return results + + +def replace_as_expression(template, **replacements): + """Variant of replace that generates expressions, instead of code blocks.""" + replacement = replace(template, **replacements) + if len(replacement) != 1: + raise ValueError( + 'single expression expected; for more general templates use replace') + node, = replacement + + if isinstance(node, gast.Expr): + return node.value + elif isinstance(node, gast.Name): + return node + + raise ValueError( + 'the template is expected to generate an expression or a name node;' + ' instead found %s' % node) diff --git a/src/braket/experimental/autoqasm/autograph/pyct/templates_test.py b/src/braket/experimental/autoqasm/autograph/pyct/templates_test.py new file mode 100644 index 00000000..8980e2c6 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/templates_test.py @@ -0,0 +1,338 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for templates module.""" + +import imp + +from absl.testing import parameterized +import gast + +from braket.experimental.autoqasm.autograph.pyct import loader +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import qual_names as qn +from braket.experimental.autoqasm.autograph.pyct import templates +from tensorflow.python.platform import test + + +class _CtxClearer(gast.NodeTransformer): + + def visit(self, node): + super(_CtxClearer, self).visit(node) + if hasattr(node, 'ctx'): + node.ctx = None + return node + + +def _parse_with_unset_ctx(expr_source): + ast_node = parser.parse_expression(expr_source) + _CtxClearer().visit(ast_node) + return ast_node + + +class _CtxChecker(gast.NodeTransformer): + + def __init__(self, test_instance, expected_ctx): + self.at_top_level = True + self.test_instance = test_instance + self.expected_ctx = expected_ctx + + def visit(self, node): + if hasattr(node, 'ctx'): + self.test_instance.assertIsInstance(node.ctx, self.expected_ctx) + if self.at_top_level: + self.at_top_level = False + self.expected_ctx = gast.Load + return super(_CtxChecker, self).visit(node) + + +class TemplatesTest(test.TestCase, parameterized.TestCase): + + def assertExpectedCtxSet(self, node, ctx): + """Assert that node has ctx=ctx at top and ctx=gast.Load everywhere else.""" + checker = _CtxChecker(self, ctx) + checker.visit(node) + + def test_replace_tuple(self): + template = """ + def test_fn(a, c): + return b, + """ + + node = templates.replace(template, b=('a', 'c'))[0] + result, _, _ = loader.load_ast(node) + + self.assertEqual((2, 3), result.test_fn(2, 3)) + + def test_replace_variable(self): + template = """ + def test_fn(a): + a += 1 + a = 2 * a + 1 + return b + """ + + node = templates.replace(template, a='b')[0] + result, _, _ = loader.load_ast(node) + self.assertEqual(7, result.test_fn(2)) + + def test_replace_function_name(self): + template = """ + def fname(a): + a += 1 + a = 2 * a + 1 + return a + """ + + node = templates.replace(template, fname='test_fn')[0] + result, _, _ = loader.load_ast(node) + self.assertEqual(7, result.test_fn(2)) + + def test_replace_code_block(self): + template = """ + def test_fn(a): + block + return a + """ + + class ShouldBeReplaced(object): + pass + + node = templates.replace( + template, + block=[ + gast.Assign( + [ + gast.Name( + 'a', + ctx=ShouldBeReplaced, + annotation=None, + type_comment=None) + ], + gast.BinOp( + gast.Name( + 'a', + ctx=ShouldBeReplaced, + annotation=None, + type_comment=None), gast.Add(), + gast.Constant(1, kind=None)), + ), + ] * 2)[0] + result, _, _ = loader.load_ast(node) + self.assertEqual(3, result.test_fn(1)) + + def test_replace_attribute(self): + template = """ + def test_fn(a): + return a.foo + """ + + node = templates.replace(template, foo='b')[0] + result, _, _ = loader.load_ast(node) + mod = imp.new_module('test') + mod.b = 3 + self.assertEqual(3, result.test_fn(mod)) + + with self.assertRaises(ValueError): + templates.replace(template, foo=1) + + def test_replace_attribute_context(self): + template = """ + def test_fn(foo): + foo = 0 + """ + + node = templates.replace( + template, + foo=parser.parse_expression('a.b.c'))[0] + self.assertIsInstance(node.body[0].targets[0].ctx, gast.Store) + self.assertIsInstance(node.body[0].targets[0].value.ctx, gast.Load) + self.assertIsInstance(node.body[0].targets[0].value.value.ctx, gast.Load) + + def test_replace_list_context(self): + template = """ + def test_fn(foo): + foo = 0 + """ + + node = templates.replace(template, foo=parser.parse_expression('[a, b]'))[0] + self.assertIsInstance(node.body[0].targets[0].ctx, gast.Store) + self.assertIsInstance(node.body[0].targets[0].elts[0].ctx, gast.Store) + self.assertIsInstance(node.body[0].targets[0].elts[1].ctx, gast.Store) + + def test_replace_tuple_context(self): + template = """ + def test_fn(foo): + foo = 0 + """ + + node = templates.replace(template, foo=parser.parse_expression('(a, b)'))[0] + self.assertIsInstance(node.body[0].targets[0].ctx, gast.Store) + self.assertIsInstance(node.body[0].targets[0].elts[0].ctx, gast.Store) + self.assertIsInstance(node.body[0].targets[0].elts[1].ctx, gast.Store) + + def test_replace_expression_context(self): + template = """ + def test_fn(): + foo + """ + + node = templates.replace( + template, foo=parser.parse_expression('a + 2 * b / -c'))[0] + self.assertIsInstance(node.body[0].left.ctx, gast.Load) + self.assertIsInstance(node.body[0].right.left.right.ctx, gast.Load) + + def test_replace_complex_context(self): + template = """ + def test_fn(): + foo = 0 + """ + + node = templates.replace( + template, foo=parser.parse_expression('bar(([a, b],)).baz'))[0] + self.assertIsInstance(node.body[0].targets[0].ctx, gast.Store) + function_call_arg = node.body[0].targets[0].value.args[0] + self.assertIsInstance(function_call_arg.elts[0].ctx, gast.Load) + self.assertIsInstance(function_call_arg.elts[0].elts[0].ctx, gast.Load) + self.assertIsInstance(function_call_arg.elts[0].elts[1].ctx, gast.Load) + + def test_replace_index(self): + template = """ + def test_fn(): + foo = 0 + """ + + node = templates.replace( + template, foo=parser.parse_expression('foo(a[b]).bar'))[0] + function_call_arg = node.body[0].targets[0].value.args[0] + self.assertIsInstance(function_call_arg.ctx, gast.Load) + self.assertIsInstance(function_call_arg.slice.ctx, gast.Load) + + def test_replace_call_keyword(self): + template = """ + def test_fn(): + def f(a, d, f): + return a + d + f + return f(1, kws=None) + """ + + source = parser.parse_expression('f(d=3, f=5)') + node = templates.replace(template, kws=source.keywords)[0] + result, _, _ = loader.load_ast(node) + self.assertEqual(9, result.test_fn()) + + with self.assertRaises(ValueError): + templates.replace(template, kws=[]) + templates.replace(template, kws=1) + + def test_replace_name_with_call(self): + template = """ + def test_fn(): + b = 5 + def g(a): + return 3 * a + def f(): + return g + return foo + """ + + source = parser.parse_expression('f()(b)') + node = templates.replace(template, foo=source)[0] + result, _, _ = loader.load_ast(node) + self.assertEqual(15, result.test_fn()) + + def test_replace_name_with_dict(self): + template = """ + def test_fn(): + return foo['bar'] + """ + + source = parser.parse_expression('{\'bar\': 3}') + node = templates.replace(template, foo=source)[0] + result, _, _ = loader.load_ast(node) + self.assertEqual(3, result.test_fn()) + + def test_replace_as_expression(self): + template = """ + foo(a) + """ + + node = templates.replace_as_expression(template, foo='bar', a='baz') + self.assertIsInstance(node, gast.Call) + self.assertEqual(node.func.id, 'bar') + self.assertEqual(node.args[0].id, 'baz') + + def test_replace_as_expression_restrictions(self): + template = """ + foo(a) + bar(b) + """ + with self.assertRaises(ValueError): + templates.replace_as_expression(template) + + def test_function_call_in_list(self): + template = """ + foo(bar) + """ + source = parser.parse_expression('[a(b(1))]') + templates.replace_as_expression(template, bar=source) + + def test_star_comprehension_in_function_call(self): + template = """ + a = foo(func, args) + """ + source = parser.parse_expression('bar(*[i for i in range(j)])') + node = templates.replace(template, func=source.func, args=source.args) + arg_node = node[0].value.args[1].value + self.assertIsInstance(arg_node.generators[0].target.ctx, gast.Store) + self.assertIsInstance(arg_node.elt.ctx, gast.Load) + + def test_lambda_in_function_call(self): + template = """ + a = foo(arg) + """ + source = parser.parse_expression('[lambda i: i]') + node = templates.replace(template, arg=source) + lambda_arg = node[0].value.args[0].elts[0] + self.assertIsInstance(lambda_arg.args.args[0].ctx, gast.Param) + self.assertIsInstance(lambda_arg.body.ctx, gast.Load) + + def test_replace_name_with_subscript(self): + template = """ + foo = bar + """ + replacement = qn.QN(qn.QN('dictionary'), subscript=qn.QN('key')) + + node = templates.replace(template, foo=replacement)[0].targets[0] + self.assertIsInstance(node.ctx, gast.Store) + self.assertIsInstance(node.value.ctx, gast.Load) + + @parameterized.named_parameters([ + ('mixed_attr_subscript', 'a.b["c"]'), + ('mixed_subscript_attr', 'a[b.c]'), + ('nested_subscript', 'a[b[c]]'), + ('repeated_subscript', 'a[b][c]'), + ]) + def test_replace_name_mixed_attr_subscript(self, expression_source): + template = 'foo = bar' + replacement = _parse_with_unset_ctx(expression_source) + + target_node = templates.replace(template, foo=replacement)[0].targets[0] + self.assertExpectedCtxSet(target_node, gast.Store) + + value_node = templates.replace(template, bar=replacement)[0].value + self.assertExpectedCtxSet(value_node, gast.Load) + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/testing/basic_definitions.py b/src/braket/experimental/autoqasm/autograph/pyct/testing/basic_definitions.py new file mode 100644 index 00000000..333b56ee --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/testing/basic_definitions.py @@ -0,0 +1,65 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Module with basic entity definitions for testing.""" + + +def simple_function(x): + """Docstring.""" + return x # comment + + +def nested_functions(x): + """Docstring.""" + + def inner_fn(y): + return y + + return inner_fn(x) + + +def function_with_print(): + print('foo') + + +simple_lambda = lambda: None + + +class SimpleClass(object): + + def simple_method(self): + return self + + def method_with_print(self): + print('foo') + + +def function_with_multiline_call(x): + """Docstring.""" + return range( + x, + x + 1, + ) + + +def basic_decorator(f): + return f + + +@basic_decorator +@basic_decorator +def decorated_function(x): + if x > 0: + return 1 + return 2 diff --git a/src/braket/experimental/autoqasm/autograph/pyct/testing/codegen.py b/src/braket/experimental/autoqasm/autograph/pyct/testing/codegen.py new file mode 100644 index 00000000..e644edee --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/testing/codegen.py @@ -0,0 +1,230 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Random code generation for testing/fuzzing.""" +# pylint: disable=invalid-name +import random +import string + +import gast +import numpy as np + +from braket.experimental.autoqasm.autograph.pyct import templates + + +class NodeSampler(object): + sample_map = None + + def sample(self): + nodes, magnitudes = zip(*self.sample_map.items()) + return np.random.choice( + nodes, p=np.array(magnitudes, dtype='float32') / np.sum(magnitudes)) + + +class StatementSampler(NodeSampler): + sample_map = dict(( + (gast.Assign, 10), + (gast.Print, 1), + (gast.If, 2), + (gast.While, 2), + (gast.For, 0), + )) + + +class ExpressionSampler(NodeSampler): + sample_map = dict(( + (gast.UnaryOp, 1), + (gast.BinOp, 8), + (gast.Name, 1), + (gast.Call, 0), + )) + + +class CompareSampler(NodeSampler): + sample_map = dict(( + (gast.Eq, 1), + (gast.NotEq, 1), + (gast.Lt, 1), + (gast.LtE, 1), + (gast.Gt, 1), + (gast.GtE, 1), + (gast.Is, 1), + (gast.IsNot, 1), + )) + + +class BinaryOpSampler(NodeSampler): + sample_map = dict(( + (gast.Add, 1), + (gast.Sub, 1), + (gast.Mult, 1), + (gast.Div, 1), + (gast.FloorDiv, 1), + (gast.Mod, 1), + (gast.Pow, 1), + )) + + +class UnaryOpSampler(NodeSampler): + sample_map = dict(((gast.USub, 1), (gast.UAdd, 0))) + + +class NameSampler(NodeSampler): + sample_map = dict(( + ('new', 1), + ('existing', 1), + )) + + +N_CONTROLFLOW_STATEMENTS = 10 +N_FUNCTIONDEF_STATEMENTS = 10 + + +class CodeGenerator(object): + """Generate random syntactically-valid Python ASTs.""" + + def __init__(self, max_depth=3, depth=0): + self.max_depth = max_depth + self.depth = depth + + def generate_statement(self): + """Generate a statement node, dispatching to the correct class method.""" + desired_node = StatementSampler().sample() + self.depth += 1 + + # Enforce some constraints on generating statements. + # E.g., if statements need at least 3 readable variables. + # If we fail to satisfy our constraints, draw another sample. + if desired_node in (gast.While, gast.For, gast.If): + if self.depth > self.max_depth: + return self.generate_statement() + + # Go get the generator method and run it + method = 'generate_' + desired_node.__name__ + visitor = getattr(self, method) + node = visitor() + self.depth -= 1 + return node + + def sample_node_list(self, low, high, generator): + """Generate a list of statements of random length. + + Args: + low: Fewest number of statements to generate. + high: Highest number of statements to generate. + generator: Function to call to generate nodes. + + Returns: + A list of statements. + """ + statements = [] + for _ in range(np.random.randint(low, high)): + statements.append(generator()) + return statements + + def generate_Name(self, ctx=gast.Load()): + variable_name = '_' + ''.join( + random.choice(string.ascii_lowercase) for _ in range(4)) + return gast.Name(variable_name, ctx=ctx, annotation=None) + + def generate_BinOp(self): + # TODO(alexbw): convert to generate_expression when we get to limit + # expression depth. + op = BinaryOpSampler().sample()() + return gast.BinOp(self.generate_Name(), op, self.generate_Name()) + + def generate_Compare(self): + op = CompareSampler().sample()() + return gast.Compare(self.generate_Name(), [op], [self.generate_Name()]) + + def generate_UnaryOp(self): + operand = self.generate_Name() + op = UnaryOpSampler().sample()() + return gast.UnaryOp(op, operand) + + def generate_expression(self): + desired_node = ExpressionSampler().sample() + # Go get the generator method and run it + method = 'generate_' + desired_node.__name__ + generator = getattr(self, method) + return generator() + + def generate_Assign(self): + """Generate an Assign node.""" + # Generate left-hand side + target_node = self.generate_Name(gast.Store()) + # Generate right-hand side + value_node = self.generate_expression() + # Put it all together + node = gast.Assign(targets=[target_node], value=value_node) + return node + + def generate_If(self): + """Generate an If node.""" + test = self.generate_Compare() + + # Generate true branch statements + body = self.sample_node_list( + low=1, + high=N_CONTROLFLOW_STATEMENTS // 2, + generator=self.generate_statement) + + # Generate false branch statements + orelse = self.sample_node_list( + low=1, + high=N_CONTROLFLOW_STATEMENTS // 2, + generator=self.generate_statement) + + node = gast.If(test, body, orelse) + return node + + def generate_While(self): + """Generate a While node.""" + + test = self.generate_Compare() + body = self.sample_node_list( + low=1, high=N_CONTROLFLOW_STATEMENTS, generator=self.generate_statement) + orelse = [] # not generating else statements + + node = gast.While(test, body, orelse) + return node + + def generate_Call(self): + raise NotImplementedError + + def generate_Return(self): + return gast.Return(self.generate_expression()) + + def generate_Print(self): + return templates.replace('print(x)', x=self.generate_expression())[0] + + def generate_FunctionDef(self): + """Generate a FunctionDef node.""" + + # Generate the arguments, register them as available + arg_vars = self.sample_node_list( + low=2, high=10, generator=lambda: self.generate_Name(gast.Param())) + args = gast.arguments(arg_vars, None, [], [], None, []) + + # Generate the function body + body = self.sample_node_list( + low=1, high=N_FUNCTIONDEF_STATEMENTS, generator=self.generate_statement) + body.append(self.generate_Return()) + fn_name = self.generate_Name().id + node = gast.FunctionDef(fn_name, args, body, (), None) + return node + + +def generate_random_functiondef(): + return CodeGenerator().generate_FunctionDef() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/testing/decorators.py b/src/braket/experimental/autoqasm/autograph/pyct/testing/decorators.py new file mode 100644 index 00000000..332686ae --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/testing/decorators.py @@ -0,0 +1,46 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Module with test decorators.""" + +import functools + + +def wrapping_decorator(f): + + @functools.wraps(f) + def wrapper(*args, **kwargs): + return f(*args, **kwargs) + + return wrapper + + +def standalone_decorator(f): + + def standalone_wrapper(*args, **kwargs): + return f(*args, **kwargs) + + return standalone_wrapper + + +def functional_decorator(): + + def decorator(f): + + def functional_wrapper(*args, **kwargs): + return f(*args, **kwargs) + + return functional_wrapper + + return decorator diff --git a/src/braket/experimental/autoqasm/autograph/pyct/transformer.py b/src/braket/experimental/autoqasm/autograph/pyct/transformer.py new file mode 100644 index 00000000..9c054210 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/transformer.py @@ -0,0 +1,538 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""A node transformer that includes utilities for SCT.""" + +import collections +import enum + +import gast + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import pretty_printer +from braket.experimental.autoqasm.autograph.pyct import templates + + +class AnalysisLevel(enum.IntEnum): + + NONE = 0 + ACTIVITY = 1 + DEFINEDNESS = 2 + LIVENESS = 3 + + +# TODO(znado): Use namedtuple. +class Context(object): + """Contains information about a source code transformation. + + This object is mutable, and is updated during conversion. Not thread safe. + + Attributes: + info: EntityInfo, immutable. + namer: naming.Namer. + current_origin: origin_info.OriginInfo, holds the OriginInfo of the last + AST node to be processed successfully. Useful for error handling. + user: An user-supplied context object. The object is opaque to the + infrastructure, but will pe passed through to all custom transformations. + """ + + def __init__(self, info, namer, user_context): + self.info = info + self.namer = namer + self.current_origin = None + self.user = user_context + + +# TODO(mdan): Move to a standalone file. +class EntityInfo( + collections.namedtuple( + 'EntityInfo', + ('name', 'source_code', 'source_file', 'future_features', 'namespace')) +): + """Contains information about a Python entity. + + Immutable. + + Examples of entities include functions and classes. + + Attributes: + name: The name that identifies this entity. + source_code: The entity's source code. + source_file: The entity's source file. + future_features: Tuple[Text], the future features that this entity was + compiled with. See + https://docs.python.org/2/reference/simple_stmts.html#future. + namespace: Dict[str, ], containing symbols visible to the entity (excluding + parameters). + """ + pass + + +class _StateStack(object): + """Templated context manager. + + This class provides syntactic sugar for a stack of objects of known + type. It allows accessing attributes of the object at the top of the stack + directly against this object, which allows for very terse syntax. + + For example, this code: + + stack = _StateStack(Foo) + stack.enter() + stack.bar + + Is equivalent to: + + stack = [] + stack.append(Foo()) + foo = stack[-1] + foo.bar + + See _State for more on how this is used. + + Attributes: + type: Any, the type of objects that this stack holds + level: int, the current stack depth + stack: List[Any], the actual stack + value: Any, the instance of the object at the top of the stack + """ + + def __init__(self, type_): + # Because we override __setattr__, we need to attach these attributes using + # the superclass' setattr. + object.__setattr__(self, 'type', type_) + object.__setattr__(self, '_stack', []) + if not hasattr(type_, 'no_root'): + self.enter() + + def __enter__(self): + self.enter() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.exit() + + def enter(self): + self._stack.append(self.type()) + + def exit(self): + self._stack.pop() + + @property + def stack(self): + return self._stack + + @property + def level(self): + return len(self._stack) + + @property + def value(self): + return self._stack[-1] + + def __iter__(self): + return iter(self._stack) + + def __getattr__(self, key): + return getattr(self._stack[-1], key) + + def __setattr__(self, key, value): + setattr(self._stack[-1], key, value) + + +class _State(object): + """Syntactic sugar for accessing an instance of a StateStack context manager. + + This structure offers syntactic sugar over a dict of stacks of objects + of known type. These structures are useful to keep state during AST walks. + Multiple different scopes can be tracked in parallel. For example: + + s = _State() + + s[foo].enter() + s[bar].enter() # this will not affect s[foo] + + Element access has special semantics: + * keys are a data type + * element values are _StateStack(type=key) objects + * missing elements are automatically added, similarly to defaultdict + + For example, the following block : + + _State s + s[Foo] + + Is equivalent to: + + s = {} + if Foo not in s: + s[Foo] = Foo() + s[Foo] + + See Base for how it's used. + """ + + def __init__(self): + self._value = {} + + def __getitem__(self, key): + if key not in self._value: + self._value[key] = _StateStack(key) + return self._value[key] + + +class NodeStateTracker(object): + """Base class for general-purpose Python code transformation. + + This abstract class provides helpful functions, like state tracking within + the scope of arbitrary node, helpers for processing code blocks, debugging, + mapping of transformed code to original code, and others. + + Scope-local state tracking: to keep state across nodes, at the level of + (possibly nested) scopes, use enter/exit_local_scope and set/get_local. + You must call enter/exit_local_scope manually, but the transformer detects + when they are not properly paired. + + The transformer allows keeping state across calls that is local + to arbitrary nodes and their descendants, using the self.state attribute. + Multiple independent scopes are allowed and automatically constructed. + + For example, to keep track of the `If` node that encloses any `Name` node, + one can write: + + ``` + class FooType(object): + + def __init__(self): + self.foo_property = None + + class DummyTransformer(NodeStateTracker, ast.NodeTransformer): + + def visit_If(self, node): + self.state[FooType].enter() + self.state[FooType].foo_property = node + node = self.veneric_visit(node) + self.state[FooType].exit() + return node + + def visit_Name(self, node): + self.state[FooType].foo_property # will hold the innermost enclosing if + ``` + + Alternatively, the `enter()`/`exit()` calls can be managed by a `with` + statement: + + ``` + def visit_If(self, node): + with self.state[FooType] as foo: + foo.foo_property = node + return self.generic_visit(node) + ``` + """ + + # TODO(mdan): Document all extra features. + + def __init__(self, ctx): + """Initialize the transformer. + + Subclasses should call this. + + Args: + ctx: A Context object. + """ + self._lineno = 0 + self._col_offset = 0 + self.ctx = ctx + + # Allows scoping of local variables to keep state across calls to visit_* + # methods. Multiple scope hierarchies may exist and are keyed by tag. A + # scope is valid at one or more nodes and all its children. Scopes created + # in child nodes supersede their parent. Scopes are isolated from one + # another. + self.state = _State() + + def debug_print(self, node): + """Helper method useful for debugging. Prints the AST.""" + if __debug__: + print(pretty_printer.fmt(node)) + return node + + def debug_print_src(self, node): + """Helper method useful for debugging. Prints the AST as code.""" + if __debug__: + print(parser.unparse(node)) + return node + + def visit_block(self, nodes, before_visit=None, after_visit=None): + """A more powerful version of generic_visit for statement blocks. + + An example of a block is the body of an if statement. + + This function allows specifying a postprocessing callback (the + after_visit argument) argument which can be used to move nodes to a new + destination. This is done by after_visit by returning a non-null + second return value, e.g. return new_node, new_destination. + + For example, a transformer could perform the following move: + + foo() + bar() + baz() + + foo() + if cond: + bar() + baz() + + The above could be done with a postprocessor of this kind: + + def after_visit(node): + if node_is_function_call(bar): + new_container_node = build_cond() + new_container_node.body.append(node) + return new_container_node, new_container_node.body + else: + # Once we set a new destination, all subsequent items will be + # moved to it, so we don't need to explicitly handle baz. + return node, None + + Args: + nodes: enumerable of AST node objects. If None, the function returns None. + before_visit: optional callable that is called before visiting each item + in nodes + after_visit: optional callable that takes in an AST node and returns a + tuple (new_node, new_destination). It is called after visiting each item + in nodes. Is used in the same was as the + visit_* methods: new_node will replace the node; if not None, + new_destination must be a list, and subsequent nodes will be placed + in this list instead of the list returned by visit_block. + + Returns: + A list of AST node objects containing the transformed items fron nodes, + except those nodes that have been relocated using after_visit. + """ + if nodes is None: + return None + + results = [] + node_destination = results + for node in nodes: + if before_visit: + # TODO(mdan): We can modify node here too, if ever needed. + before_visit() + + replacement = self.visit(node) + + if after_visit and replacement: + replacement, new_destination = after_visit(replacement) + else: + new_destination = None + + if replacement: + if isinstance(replacement, (list, tuple)): + node_destination.extend(replacement) + else: + node_destination.append(replacement) + + # Allow the postprocessor to reroute the remaining nodes to a new list. + if new_destination is not None: + node_destination = new_destination + return results + + +# TODO(mdan): Rename to PythonCodeTransformer. +class Base(NodeStateTracker, gast.NodeTransformer): + """Base class for general-purpose Python-to-Python code transformation. + + This is an extension of ast.NodeTransformer that provides the additional + functions offered by NodeStateTracker. + """ + + def create_assignment(self, target, expression): + template = """ + target = expression + """ + return templates.replace(template, target=target, expression=expression) + + # TODO(mdan): Remove. + def apply_to_single_assignments(self, targets, values, apply_fn): + """Applies a function to each individual assignment. + + This function can process a possibly-unpacked (e.g. a, b = c, d) assignment. + It tries to break down the unpacking if possible. In effect, it has the same + effect as passing the assigned values in SSA form to apply_fn. + + Examples: + + The following will result in apply_fn(a, c), apply_fn(b, d): + + a, b = c, d + + The following will result in apply_fn(a, c[0]), apply_fn(b, c[1]): + + a, b = c + + The following will result in apply_fn(a, (b, c)): + + a = b, c + + It uses the visitor pattern to allow subclasses to process single + assignments individually. + + Args: + targets: list, tuple of or individual AST node. Should be used with the + targets field of an ast.Assign node. + values: an AST node. + apply_fn: a function of a single argument, which will be called with the + respective nodes of each single assignment. The signature is + apply_fn(target, value), no return value. + """ + if not isinstance(targets, (list, tuple)): + targets = (targets,) + for target in targets: + if isinstance(target, (gast.Tuple, gast.List)): + for i in range(len(target.elts)): + target_el = target.elts[i] + if isinstance(values, (gast.Tuple, gast.List)): + value_el = values.elts[i] + else: + value_el = gast.Subscript(values, i, ctx=gast.Store()) + self.apply_to_single_assignments(target_el, value_el, apply_fn) + else: + # TODO(mdan): Look into allowing to rewrite the AST here. + apply_fn(target, values) + + def visit(self, node): + if not isinstance(node, gast.AST): + # This is not that uncommon a mistake: various node bodies are lists, for + # example, posing a land mine for transformers that need to recursively + # call `visit`. The error needs to be raised before the exception handler + # below is installed, because said handler will mess up if `node` is not, + # in fact, a node. + msg = ('invalid value for "node": expected "ast.AST", got "{}"; to' + ' visit lists of nodes, use "visit_block" instead').format( + type(node)) + raise ValueError(msg) + + if anno.hasanno(node, anno.Basic.SKIP_PROCESSING): + return node + + parent_origin = self.ctx.current_origin + if anno.hasanno(node, anno.Basic.ORIGIN): + self.ctx.current_origin = anno.getanno(node, anno.Basic.ORIGIN) + + try: + processing_expr_node = isinstance(node, gast.Expr) + if processing_expr_node: + entry_expr_value = node.value + + result = super(Base, self).visit(node) + + # Adjust for consistency: replacing the value of an Expr with + # an Assign node removes the need for the Expr node. + if (processing_expr_node and isinstance(result, gast.Expr) and + (result.value is not entry_expr_value)): + # When the replacement is a list, it is assumed that the list came + # from a template that contained a number of statements, which + # themselves are standalone and don't require an enclosing Expr. + if isinstance(result.value, + (list, tuple, gast.Assign, gast.AugAssign)): + result = result.value + + # By default, all replacements receive the origin info of the replaced + # node. + if result is not node and result is not None: + inherited_origin = anno.getanno( + node, anno.Basic.ORIGIN, default=parent_origin) + if inherited_origin is not None: + nodes_to_adjust = result + if isinstance(result, (list, tuple)): + nodes_to_adjust = result + else: + nodes_to_adjust = (result,) + for n in nodes_to_adjust: + if not anno.hasanno(n, anno.Basic.ORIGIN): + anno.setanno(n, anno.Basic.ORIGIN, inherited_origin) + finally: + self.ctx.current_origin = parent_origin + + return result + + +class CodeGenerator(NodeStateTracker, gast.NodeVisitor): + """Base class for general-purpose Python-to-string code transformation. + + Similar to Base, but outputs arbitrary strings instead of a Python AST. + + This uses the same visitor mechanism that the standard NodeVisitor uses, + meaning that subclasses write handlers for the different kinds of nodes. + New code is generated using the emit method, which appends to a code buffer + that can be afterwards obtained from code_buffer. + + Example: + + class SimpleCodeGen(CodeGenerator): + + def visitIf(self, node): + self.emit('if ') + self.visit(node.test) + self.emit(' { ') + self.visit(node.body) + self.emit(' } else { ') + self.visit(node.orelse) + self.emit(' } ') + + node = ast.parse(...) + gen = SimpleCodeGen() + gen.visit(node) + # gen.code_buffer contains the resulting code + """ + + def __init__(self, ctx): + super(CodeGenerator, self).__init__(ctx) + + self._output_code = '' + self.source_map = {} + + def emit(self, code): + self._output_code += code + + @property + def code_buffer(self): + return self._output_code + + def visit(self, node): + if anno.hasanno(node, anno.Basic.SKIP_PROCESSING): + return + + parent_origin = self.ctx.current_origin + eof_before = len(self._output_code) + if anno.hasanno(node, anno.Basic.ORIGIN): + self.ctx.current_origin = anno.getanno(node, anno.Basic.ORIGIN) + + try: + ret = super(CodeGenerator, self).visit(node) + + # By default, all replacements receive the origin info of the replaced + # node. + eof_after = len(self._output_code) + if eof_before - eof_after: + inherited_origin = anno.getanno( + node, anno.Basic.ORIGIN, default=parent_origin) + if inherited_origin is not None: + self.source_map[(eof_before, eof_after)] = inherited_origin + return ret + finally: + self.ctx.current_origin = parent_origin diff --git a/src/braket/experimental/autoqasm/autograph/pyct/transformer_test.py b/src/braket/experimental/autoqasm/autograph/pyct/transformer_test.py new file mode 100644 index 00000000..e1507b79 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/transformer_test.py @@ -0,0 +1,364 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for templates module.""" + +import re +import gast + +from braket.experimental.autoqasm.autograph.pyct import anno +from braket.experimental.autoqasm.autograph.pyct import origin_info +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import transformer +from tensorflow.python.platform import test + + +class TransformerTest(test.TestCase): + + def _simple_context(self): + entity_info = transformer.EntityInfo( + name='Test_fn', + source_code=None, + source_file=None, + future_features=(), + namespace=None) + return transformer.Context(entity_info, None, None) + + def assertSameAnno(self, first, second, key): + self.assertIs(anno.getanno(first, key), anno.getanno(second, key)) + + def assertDifferentAnno(self, first, second, key): + self.assertIsNot(anno.getanno(first, key), anno.getanno(second, key)) + + def test_state_tracking(self): + + class LoopState(object): + pass + + class CondState(object): + pass + + class TestTransformer(transformer.Base): + + def visit(self, node): + anno.setanno(node, 'loop_state', self.state[LoopState].value) + anno.setanno(node, 'cond_state', self.state[CondState].value) + return super(TestTransformer, self).visit(node) + + def visit_While(self, node): + self.state[LoopState].enter() + node = self.generic_visit(node) + self.state[LoopState].exit() + return node + + def visit_If(self, node): + self.state[CondState].enter() + node = self.generic_visit(node) + self.state[CondState].exit() + return node + + tr = TestTransformer(self._simple_context()) + + def test_function(a): + a = 1 + while a: + _ = 'a' + if a > 2: + _ = 'b' + while True: + raise '1' + if a > 3: + _ = 'c' + while True: + raise '1' + + node, _ = parser.parse_entity(test_function, future_features=()) + node = tr.visit(node) + + fn_body = node.body + outer_while_body = fn_body[1].body + self.assertSameAnno(fn_body[0], outer_while_body[0], 'cond_state') + self.assertDifferentAnno(fn_body[0], outer_while_body[0], 'loop_state') + + first_if_body = outer_while_body[1].body + self.assertDifferentAnno(outer_while_body[0], first_if_body[0], + 'cond_state') + self.assertSameAnno(outer_while_body[0], first_if_body[0], 'loop_state') + + first_inner_while_body = first_if_body[1].body + self.assertSameAnno(first_if_body[0], first_inner_while_body[0], + 'cond_state') + self.assertDifferentAnno(first_if_body[0], first_inner_while_body[0], + 'loop_state') + + second_if_body = outer_while_body[2].body + self.assertDifferentAnno(first_if_body[0], second_if_body[0], 'cond_state') + self.assertSameAnno(first_if_body[0], second_if_body[0], 'loop_state') + + second_inner_while_body = second_if_body[1].body + self.assertDifferentAnno(first_inner_while_body[0], + second_inner_while_body[0], 'cond_state') + self.assertDifferentAnno(first_inner_while_body[0], + second_inner_while_body[0], 'loop_state') + + def test_state_tracking_context_manager(self): + + class CondState(object): + pass + + class TestTransformer(transformer.Base): + + def visit(self, node): + anno.setanno(node, 'cond_state', self.state[CondState].value) + return super(TestTransformer, self).visit(node) + + def visit_If(self, node): + with self.state[CondState]: + return self.generic_visit(node) + + tr = TestTransformer(self._simple_context()) + + def test_function(a): + a = 1 + if a > 2: + _ = 'b' + if a < 5: + _ = 'c' + _ = 'd' + + node, _ = parser.parse_entity(test_function, future_features=()) + node = tr.visit(node) + + fn_body = node.body + outer_if_body = fn_body[1].body + self.assertDifferentAnno(fn_body[0], outer_if_body[0], 'cond_state') + self.assertSameAnno(outer_if_body[0], outer_if_body[2], 'cond_state') + + inner_if_body = outer_if_body[1].body + self.assertDifferentAnno(inner_if_body[0], outer_if_body[0], 'cond_state') + + def test_visit_block_postprocessing(self): + + class TestTransformer(transformer.Base): + + def _process_body_item(self, node): + if isinstance(node, gast.Assign) and (node.value.id == 'y'): + if_node = gast.If( + gast.Name( + 'x', ctx=gast.Load(), annotation=None, type_comment=None), + [node], []) + return if_node, if_node.body + return node, None + + def visit_FunctionDef(self, node): + node.body = self.visit_block( + node.body, after_visit=self._process_body_item) + return node + + def test_function(x, y): + z = x + z = y + return z + + tr = TestTransformer(self._simple_context()) + + node, _ = parser.parse_entity(test_function, future_features=()) + node = tr.visit(node) + + self.assertEqual(len(node.body), 2) + self.assertIsInstance(node.body[0], gast.Assign) + self.assertIsInstance(node.body[1], gast.If) + self.assertIsInstance(node.body[1].body[0], gast.Assign) + self.assertIsInstance(node.body[1].body[1], gast.Return) + + def test_robust_error_on_list_visit(self): + + class BrokenTransformer(transformer.Base): + + def visit_If(self, node): + # This is broken because visit expects a single node, not a list, and + # the body of an if is a list. + # Importantly, the default error handling in visit also expects a single + # node. Therefore, mistakes like this need to trigger a type error + # before the visit called here installs its error handler. + # That type error can then be caught by the enclosing call to visit, + # and correctly blame the If node. + self.visit(node.body) + return node + + def test_function(x): + if x > 0: + return x + + tr = BrokenTransformer(self._simple_context()) + + node, _ = parser.parse_entity(test_function, future_features=()) + with self.assertRaises(ValueError) as cm: + node = tr.visit(node) + obtained_message = str(cm.exception) + expected_message = r'expected "ast.AST", got "\<(type|class) \'list\'\>"' + self.assertRegex(obtained_message, expected_message) + + def test_robust_error_on_ast_corruption(self): + # A child class should not be able to be so broken that it causes the error + # handling in `transformer.Base` to raise an exception. Why not? Because + # then the original error location is dropped, and an error handler higher + # up in the call stack gives misleading information. + + # Here we test that the error handling in `visit` completes, and blames the + # correct original exception, even if the AST gets corrupted. + + class NotANode(object): + pass + + class BrokenTransformer(transformer.Base): + + def visit_If(self, node): + node.body = NotANode() + raise ValueError('I blew up') + + def test_function(x): + if x > 0: + return x + + tr = BrokenTransformer(self._simple_context()) + + node, _ = parser.parse_entity(test_function, future_features=()) + with self.assertRaises(ValueError) as cm: + node = tr.visit(node) + obtained_message = str(cm.exception) + # The message should reference the exception actually raised, not anything + # from the exception handler. + expected_substring = 'I blew up' + self.assertIn(expected_substring, obtained_message) + + def test_origin_info_propagated_to_new_nodes(self): + + class TestTransformer(transformer.Base): + + def visit_If(self, node): + return gast.Pass() + + tr = TestTransformer(self._simple_context()) + + def test_fn(): + x = 1 + if x > 0: + x = 1 + return x + + node, source = parser.parse_entity(test_fn, future_features=()) + origin_info.resolve(node, source, 'test_file', 100, 0) + node = tr.visit(node) + + created_pass_node = node.body[1] + # Takes the line number of the if statement. + self.assertEqual( + anno.getanno(created_pass_node, anno.Basic.ORIGIN).loc.lineno, 102) + + def test_origin_info_preserved_in_moved_nodes(self): + + class TestTransformer(transformer.Base): + + def visit_If(self, node): + return node.body + + tr = TestTransformer(self._simple_context()) + + def test_fn(): + x = 1 + if x > 0: + x = 1 + x += 3 + return x + + node, source = parser.parse_entity(test_fn, future_features=()) + origin_info.resolve(node, source, 'test_file', 100, 0) + node = tr.visit(node) + + assign_node = node.body[1] + aug_assign_node = node.body[2] + # Keep their original line numbers. + self.assertEqual( + anno.getanno(assign_node, anno.Basic.ORIGIN).loc.lineno, 103) + self.assertEqual( + anno.getanno(aug_assign_node, anno.Basic.ORIGIN).loc.lineno, 104) + + +class CodeGeneratorTest(test.TestCase): + + def _simple_context(self): + entity_info = transformer.EntityInfo( + name='test_fn', + source_code=None, + source_file=None, + future_features=(), + namespace=None) + return transformer.Context(entity_info, None, None) + + def test_basic_codegen(self): + + class TestCodegen(transformer.CodeGenerator): + + def visit_Assign(self, node): + self.emit(parser.unparse(node, include_encoding_marker=False)) + self.emit('\n') + + def visit_Return(self, node): + self.emit(parser.unparse(node, include_encoding_marker=False)) + self.emit('\n') + + def visit_If(self, node): + self.emit('if ') + # This is just for simplifity. A real generator will walk the tree and + # emit proper code. + self.emit(parser.unparse(node.test, include_encoding_marker=False)) + self.emit(' {\n') + self.visit_block(node.body) + self.emit('} else {\n') + self.visit_block(node.orelse) + self.emit('}\n') + + tg = TestCodegen(self._simple_context()) + + def test_fn(): + x = 1 + if x > 0: + x = 2 + if x > 1: + x = 3 + return x + + node, source = parser.parse_entity(test_fn, future_features=()) + origin_info.resolve(node, source, 'test_file', 100, 0) + tg.visit(node) + + r = re.compile('.*'.join([ + r'x = 1', + r'if \(?x > 0\)? {', + r'x = 2', + r'if \(?x > 1\)? {', + r'x = 3', + r'} else {', + r'}', + r'} else {', + r'}', + r'return x']), re.DOTALL) + + self.assertRegex(tg.code_buffer, r) + # TODO(mdan): Test the source map. + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/transpiler.py b/src/braket/experimental/autoqasm/autograph/pyct/transpiler.py new file mode 100644 index 00000000..aad92b13 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/transpiler.py @@ -0,0 +1,495 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Generic source code transformation infrastructure.""" + +import inspect +import threading +import types + +import gast + +from braket.experimental.autoqasm.autograph.pyct import cache +from braket.experimental.autoqasm.autograph.pyct import inspect_utils +from braket.experimental.autoqasm.autograph.pyct import loader +from braket.experimental.autoqasm.autograph.pyct import naming +from braket.experimental.autoqasm.autograph.pyct import origin_info +from braket.experimental.autoqasm.autograph.pyct import parser +from braket.experimental.autoqasm.autograph.pyct import templates +from braket.experimental.autoqasm.autograph.pyct import transformer +from braket.experimental.autoqasm.autograph.logging import ag_logging as logging + + +def _wrap_into_factory(nodes, entity_name, inner_factory_name, + outer_factory_name, closure_vars, factory_args, + future_features): + """Wraps an AST into the body of a factory with consistent lexical context. + + The AST is expected to define some symbol with a name given by `entity_name`. + + This mechanism ensures that the resulting transformed entity has lexical + scoping identical to that of the source entity, while allowing extra + parametrization. + + Two nested factories achieve the following: + + 1. The inner factory dynamically creates the entity represented by `nodes`. + 2. The inner factory is parametrized by a custom set of arguments. + 3. The inner factory has a closure identical to that of the transformed + entity. + 4. The inner factory has local variables named like `args`, which `nodes` may + use as additional parameters. + 5. The inner factory returns the variables given by `entity_name`. + 6. The outer factory is niladic. + 7. The outer factory has no closure. + 8. The outer factory creates the necessary lexical scope for the inner + factory, so that the loaded code has the given configuration for + closure/globals. + 9. The outer factory returns the inner factory. + + Roughly speaking, the following code is generated: + + from __future__ import future_feature_1 + from __future__ import future_feature_2 + ... + + def outer_factory(): + closure_var_1 = None + closure_var_2 = None + ... + + def inner_factory(arg_1, arg_2, ...): + <> + return entity + + return inner_factory + + The lexical scoping is created using dummy symbol declarations which create + local variables in the body of the outer factory, so that the Python parser + correctly marks them as free non-global variables upon load (that is, it + creates cell slots for each symbol. These symbols are initialized with None, + but their values are not expected to be used; instead, the caller is expected + to replace them with the cells of the source entity. For more details, see: + https://docs.python.org/3/reference/executionmodel.html#binding-of-names + + Args: + nodes: Tuple[ast.AST], the source code to wrap. + entity_name: Union[Text, ast.AST], the name of the principal entity that + `nodes` define. + inner_factory_name: Text, the name of the inner factory. + outer_factory_name: Text, the name of the outer factory. + closure_vars: Iterable[Text], names of the closure variables for the inner + factory. + factory_args: Iterable[Text], names of additional arguments for the + inner factory. Useful to configure variables that the converted code can + use. Typically, these are modules. + future_features: Iterable[Text], names of future statements to associate the + code with. + + Returns: + ast.AST + """ + dummy_closure_defs = [] + for var_name in closure_vars: + template = """ + var_name = None + """ + dummy_closure_defs.extend(templates.replace(template, var_name=var_name)) + + if future_features: + future_imports = gast.ImportFrom( + module='__future__', + names=[gast.alias(name=name, asname=None) for name in future_features], + level=0) + else: + future_imports = [] + + factory_args = [ + gast.Name(name, ctx=gast.Param(), annotation=None, type_comment=None) + for name in factory_args + ] + + template = """ + future_imports + def outer_factory_name(): + dummy_closure_defs + def inner_factory_name(factory_args): + entity_defs + return entity_name + return inner_factory_name + """ + return templates.replace( + template, + dummy_closure_defs=dummy_closure_defs, + entity_defs=nodes, + entity_name=entity_name, + factory_args=factory_args, + future_imports=future_imports, + inner_factory_name=inner_factory_name, + outer_factory_name=outer_factory_name) + + +class _PythonFnFactory(object): + """Helper object that wraps a Python function factory.""" + + def __init__(self, name, freevars, extra_locals): + """Creates a new factory for a Python function. + + Args: + name: The function name. + freevars: The list of non-global free variables for the function. + extra_locals: Dict[Text, Any], names and values for custom variables that + are accessible to the generated code as local variables. + """ + self._name = name + self._freevars = freevars + self._extra_locals = extra_locals + + self._unbound_factory = None + self.module = None + self.source_map = None + + def create(self, + nodes, + namer, + inner_factory_name='inner_factory', + outer_factory_name='outer_factory', + future_features=()): + """Initializes a function.""" + if self._unbound_factory is not None: + raise ValueError('double initialization; create a new object instead') + + inner_factory_name = namer.new_symbol(inner_factory_name, ()) + outer_factory_name = namer.new_symbol(outer_factory_name, ()) + nodes = _wrap_into_factory(nodes, self._name, inner_factory_name, + outer_factory_name, self._freevars, + self._extra_locals.keys(), future_features) + + module, _, source_map = loader.load_ast( + nodes, include_source_map=True) + outer_factory = getattr(module, outer_factory_name) + self._unbound_factory = outer_factory() + self.module = module + self.source_map = source_map + + def instantiate(self, + globals_, + closure, + defaults=None, + kwdefaults=None): + """Creates a new function instance.""" + if self._unbound_factory is None: + raise ValueError('call create first') + + factory_code = self._unbound_factory.__code__ + factory_freevars = factory_code.co_freevars + closure_map = dict(zip(self._freevars, closure)) + factory_closure = tuple( + closure_map[name] for name in factory_code.co_freevars) + if len(factory_closure) != len(closure): + raise ValueError( + 'closure mismatch, requested {}, but source function had {}'.format( + self._freevars, factory_freevars)) + + bound_factory = types.FunctionType( + code=factory_code, + globals=globals_, + name=self._name, + argdefs=(), + closure=factory_closure) + + # The lint override is a false positive. + new_fn = bound_factory(**self._extra_locals) # pylint:disable=not-callable + + if defaults: + new_fn.__defaults__ = defaults + if kwdefaults: + new_fn.__kwdefaults__ = kwdefaults + + return new_fn + + +class GenericTranspiler(object): + """A generic transpiler for Python functions. + + Its interface is the `transform` API, which can process Python function + objects. Internally, it handles parsing. + + Users typically subclass this, customizing the `transform_ast` method. The + output of transformed_ast is returned directly by `transform`. Existing + methods like `transform_function` may also be overloaded. + + Example: + + class MyTransformer(GenericTranspiler): + + def transform_ast(self, node, ctx): + result = <> + return result + + transformer = MyTransfomer() + + result = transformer.transform(f, ...) + # result is the output + """ + + def get_transformed_name(self, node): + """Returns a name for the output function. Subclasses may override this.""" + if isinstance(node, gast.Lambda): + return 'lam' + elif isinstance(node, gast.FunctionDef): + return node.name + raise ValueError('Unknown node type {}'.format(node)) + + def transform_ast(self, node, ctx): + """Performs an actual transformation of a function's AST. + + Subclasses must implement this method, and do not usually call it. + + Args: + node: One or more ast.AST nodes representing the AST to be transformed. + ctx: transformer.Context. + """ + raise NotImplementedError('subclasses must override this') + + def transform(self, obj, user_context): + """Transforms a Python object. + + Users typically call this method. + + Args: + obj: A Python object, function, type, etc. + user_context: An opaque object (may be None) that is forwarded to + transform_ast, through the ctx.user attribute. + Returns: + The result of calling transform_function. + + Raises: + NotImplementedError: if the type of obj is not handled. + """ + if inspect.isfunction(obj) or inspect.ismethod(obj): + return self.transform_function(obj, user_context) + + raise NotImplementedError('Non-function: {}'.format(type(obj))) + + def _erase_arg_defaults(self, node): + """Erase arg default expressions, which would otherwise be unbound.""" + args = node.args + for i in range(len(args.defaults)): + args.defaults[i] = parser.parse_expression('None') + for i, d in enumerate(args.kw_defaults): + if d is not None: + args.kw_defaults[i] = parser.parse_expression('None') + return node + + def transform_module(self, mod, user_context): + """Transforms a module. + + Subclasses may override this method. The return value is opaque. + + The method receives the original AST. The result is passed as-is to the + output of `transform`. + + Args: + mod: A Python module. + user_context: An opaque object (may be None) that is forwarded to + transform_ast, through the ctx.user attribute. + Returns: + List[Tuple[Any, Any]]. By default it returns the output of transform_ast, + evaluated on each supported member, other than modules, together with a + `transformer.Context` containing information about the transformation + process. + """ + result = [] + for member in mod.__dict__.values(): + if inspect.ismodule(member): + continue # Not transforming modules recursively. + try: + result.append(self.transform(member, user_context)) + except NotImplementedError: + pass # Skip unsupported elements. + return result + + def transform_function(self, fn, user_context): + """Transforms a function. + + Subclasses may override this method. The return value is opaque. + + The method receives the original AST. The result is passed as-is to the + output of `transform`. + + Args: + fn: A function or lambda. + user_context: An opaque object (may be None) that is forwarded to + transform_ast, through the ctx.user attribute. + Returns: + Tuple[Any, Any]. By default it returns the output of transform_ast, + together with a `transformer.Context` containing information about the + transformation process. + """ + future_features = inspect_utils.getfutureimports(fn) + node, source = parser.parse_entity(fn, future_features=future_features) + logging.log(3, 'Source code of %s:\n\n%s\n', fn, source) + + origin_info.resolve_entity(node, source, fn) + + namespace = inspect_utils.getnamespace(fn) + namer = naming.Namer(namespace) + new_name = namer.new_symbol(self.get_transformed_name(node), ()) + entity_info = transformer.EntityInfo( + name=new_name, + source_code=source, + source_file='', + future_features=future_features, + namespace=namespace) + context = transformer.Context(entity_info, namer, user_context) + + node = self._erase_arg_defaults(node) + result = self.transform_ast(node, context) + + return result, context + + +class PyToPy(GenericTranspiler): + """A generic Python-to-Python transpiler. + + Its `transform` method offers a function-in, function-out interface. + Internally, it takes care of parsing, caching and loading of the translated + code. + + Users typically subclass this, overriding `transform_ast`. + + Usually, instances of this class are singletons, since each instance manages + its own cache. The caching can be controlled by overriding `get_caching_key`. + + Example: + + class MyTransformer(PyToPy): + + def transform_ast(self, node, ctx): + node = <> + return node + + transformer = MyTransfomer() + + new_f, module, source_map = transformer.transform_function(f, ...) + # new_f is a function with signature identical to f + + The transformed function has access to the same namespace as the original + function. To allow access to internal APIs, users may inject additional + symbols by overriding `get_extra_locals`. + """ + + def __init__(self): + self._cache_lock = threading.RLock() + self._cache = cache.CodeObjectCache() + + def get_extra_locals(self): + """Returns extra static local variables to be made to transformed code. + + Subclasses must override this. + + Returns: + extra_locals: A Dict[Text, Any] containing additional variables to make + available to the transformed code. + """ + raise NotImplementedError('subclasses must override this') + + def get_caching_key(self, user_context): + """Returns a unique key to use for caching. + + Subclasses must override this. + + Calls made to `transform_function` with functions that have the same code + object and caching key will return a cached instance on subsequent + invocations. + + Args: + user_context: The context object which was passed to `transform`. + + Returns: + extra_locals: A hashable. + """ + raise NotImplementedError('subclasses must override this') + + def _cached_factory(self, fn, cache_subkey): + cached_factory = self._cache[fn][cache_subkey] + logging.log(3, 'Cache hit for %s subkey %s: %s', fn, cache_subkey, + cached_factory) + return cached_factory + + def transform_function(self, fn, user_context): + """Transforms a function. See GenericTranspiler.trasnform_function. + + This overload wraps the parent's `transform_function`, adding caching and + facilities to instantiate the output as a Python object. It also + adds facilities to make new symbols available to the generated Python code, + visible as local variables - see `get_extra_locals`. + + Args: + fn: A function or lambda. + user_context: An opaque object (may be None) that is forwarded to + transform_ast, through the ctx.user attribute. + Returns: + A tuple: + * A function or lambda with the same signature and closure as `fn` + * The temporary module into which the transformed function was loaded + * The source map as a + Dict[origin_info.LineLocation, origin_info.OriginInfo] + """ + cache_subkey = self.get_caching_key(user_context) + + if self._cache.has(fn, cache_subkey): + # Fast path: use a lock-free check. + factory = self._cached_factory(fn, cache_subkey) + + else: + with self._cache_lock: + # Check again under lock. + if self._cache.has(fn, cache_subkey): + factory = self._cached_factory(fn, cache_subkey) + + else: + logging.log(1, '%s is not cached for subkey %s', fn, cache_subkey) + # TODO(mdan): Confusing overloading pattern. Fix. + nodes, ctx = super(PyToPy, self).transform_function(fn, user_context) + + if isinstance(nodes, gast.Lambda): + nodes = gast.Assign( + targets=[ + gast.Name( + ctx.info.name, + ctx=gast.Store(), + annotation=None, + type_comment=None) + ], + value=nodes) + else: + nodes.name = ctx.info.name + + if logging.has_verbosity(2): + logging.log(2, 'Transformed %s:\n\n%s\n', fn, parser.unparse(nodes)) + + factory = _PythonFnFactory( + ctx.info.name, fn.__code__.co_freevars, self.get_extra_locals()) + factory.create( + nodes, ctx.namer, future_features=ctx.info.future_features) + self._cache[fn][cache_subkey] = factory + + transformed_fn = factory.instantiate( + globals_=fn.__globals__, + closure=fn.__closure__ or (), + defaults=fn.__defaults__, + kwdefaults=getattr(fn, '__kwdefaults__', None)) + return transformed_fn, factory.module, factory.source_map diff --git a/src/braket/experimental/autoqasm/autograph/pyct/transpiler_test.py b/src/braket/experimental/autoqasm/autograph/pyct/transpiler_test.py new file mode 100644 index 00000000..e5236430 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/pyct/transpiler_test.py @@ -0,0 +1,240 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for transpiler module.""" + +import threading + +import gast + +from braket.experimental.autoqasm.autograph.pyct import transformer +from braket.experimental.autoqasm.autograph.pyct import transpiler +from tensorflow.python.platform import test + + +class FlipSignTransformer(transformer.Base): + + def visit_BinOp(self, node): + if isinstance(node.op, gast.Add): + node.op = gast.Sub() + return self.generic_visit(node) + + +class TestTranspiler(transpiler.PyToPy): + + def get_caching_key(self, ctx): + del ctx + return 0 + + def get_extra_locals(self): + return {} + + def transform_ast(self, node, ctx): + return FlipSignTransformer(ctx).visit(node) + + +global_var_for_test_global = 1 +global_var_for_test_namespace_collisions = object() + + +class PyToPyTest(test.TestCase): + + def test_basic(self): + def f(a): + return a + 1 + + tr = TestTranspiler() + f, _, _ = tr.transform(f, None) + + self.assertEqual(f(1), 0) + + def test_closure(self): + b = 1 + + def f(a): + return a + b + + tr = TestTranspiler() + f, _, _ = tr.transform(f, None) + + self.assertEqual(f(1), 0) + b = 2 + self.assertEqual(f(1), -1) + + def test_global(self): + def f(a): + return a + global_var_for_test_global + + tr = TestTranspiler() + f, _, _ = tr.transform(f, None) + + global global_var_for_test_global + global_var_for_test_global = 1 + self.assertEqual(f(1), 0) + global_var_for_test_global = 2 + self.assertEqual(f(1), -1) + + def test_defaults(self): + b = 2 + c = 1 + + def f(a, d=c + 1): + return a + b + d + + tr = TestTranspiler() + f, _, _ = tr.transform(f, None) + + self.assertEqual(f(1), 1 - 2 - 2) + c = 0 + self.assertEqual(f(1), 1 - 2 - 2) # Defaults are evaluated at definition. + b = 1 + self.assertEqual(f(1), 1 - 2 - 1) + + def test_call_tree(self): + + def g(a): + return a + 1 + + def f(a): + return g(a) + 1 + + tr = TestTranspiler() + f, _, _ = tr.transform(f, None) + + self.assertEqual(f(1), 1 - 1 + 1) # Only f is converted. + + def test_lambda(self): + b = 2 + f = lambda x: (b + (x if x > 0 else -x)) + + tr = TestTranspiler() + f, _, _ = tr.transform(f, None) + + self.assertEqual(f(1), 2 - 1) + self.assertEqual(f(-1), 2 - 1) + + b = 3 + + self.assertEqual(f(1), 3 - 1) + self.assertEqual(f(-1), 3 - 1) + + def test_multiple_lambdas(self): + a, b = 1, 2 + # This can be disambiguated by the argument names. + f, _ = (lambda x: a + x, lambda y: b * y) + + tr = TestTranspiler() + f, _, _ = tr.transform(f, None) + + self.assertEqual(f(1), 1 - 1) + + def test_nested_functions(self): + b = 2 + + def f(x): + + def g(x): + return b + x + + return g(x) + + tr = TestTranspiler() + f, _, _ = tr.transform(f, None) + + self.assertEqual(f(1), 2 - 1) + + def test_nested_lambda(self): + b = 2 + + def f(x): + g = lambda x: b + x + return g(x) + + tr = TestTranspiler() + f, _, _ = tr.transform(f, None) + + self.assertEqual(f(1), 2 - 1) + + def test_concurrency(self): + + def f(): + pass + + outputs = [] + + tr = TestTranspiler() + # Note: this is not a test, it's a required invariant. + assert tr.get_caching_key(None) == tr.get_caching_key(None) + + def conversion_thread(): + _, mod, _ = tr.transform(f, None) + outputs.append(mod.__name__) + + threads = tuple( + threading.Thread(target=conversion_thread) for _ in range(10)) + for t in threads: + t.start() + for t in threads: + t.join() + + # Races would potentially create multiple functions / modules + # (non-deterministically, but with high likelihood). + self.assertEqual(len(set(outputs)), 1) + + def test_reentrance(self): + + def test_fn(): + return 1 + 1 + + class ReentrantTranspiler(transpiler.PyToPy): + + def __init__(self): + super(ReentrantTranspiler, self).__init__() + self._recursion_depth = 0 + + def get_caching_key(self, ctx): + del ctx + return 0 + + def get_extra_locals(self): + return {} + + def transform_ast(self, node, ctx): + self._recursion_depth += 1 + if self._recursion_depth < 2: + self.transform(test_fn, None) + return FlipSignTransformer(ctx).visit(node) + + tr = ReentrantTranspiler() + + f, _, _ = tr.transform(test_fn, None) + self.assertEqual(f(), 0) + + def test_namespace_collisions_avoided(self): + + class TestClass(object): + + def global_var_for_test_namespace_collisions(self): + return global_var_for_test_namespace_collisions + + tr = TestTranspiler() + obj = TestClass() + + f, _, _ = tr.transform( + obj.global_var_for_test_namespace_collisions, None) + self.assertIs(f(obj), global_var_for_test_namespace_collisions) + + +if __name__ == '__main__': + test.main() diff --git a/src/braket/experimental/autoqasm/autograph/tf_utils/tf_decorator.py b/src/braket/experimental/autoqasm/autograph/tf_utils/tf_decorator.py new file mode 100644 index 00000000..635aabca --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/tf_utils/tf_decorator.py @@ -0,0 +1,361 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Base TFDecorator class and utility functions for working with decorators. + +There are two ways to create decorators that TensorFlow can introspect into. +This is important for documentation generation purposes, so that function +signatures aren't obscured by the (*args, **kwds) signature that decorators +often provide. + +1. Call `tf_decorator.make_decorator` on your wrapper function. If your +decorator is stateless, or can capture all of the variables it needs to work +with through lexical closure, this is the simplest option. Create your wrapper +function as usual, but instead of returning it, return +`tf_decorator.make_decorator(target, your_wrapper)`. This will attach some +decorator introspection metadata onto your wrapper and return it. + +Example: + + def print_hello_before_calling(target): + def wrapper(*args, **kwargs): + print('hello') + return target(*args, **kwargs) + return tf_decorator.make_decorator(target, wrapper) + +2. Derive from TFDecorator. If your decorator needs to be stateful, you can +implement it in terms of a TFDecorator. Store whatever state you need in your +derived class, and implement the `__call__` method to do your work before +calling into your target. You can retrieve the target via +`super(MyDecoratorClass, self).decorated_target`, and call it with whatever +parameters it needs. + +Example: + + class CallCounter(tf_decorator.TFDecorator): + def __init__(self, target): + super(CallCounter, self).__init__('count_calls', target) + self.call_count = 0 + + def __call__(self, *args, **kwargs): + self.call_count += 1 + return super(CallCounter, self).decorated_target(*args, **kwargs) + + def count_calls(target): + return CallCounter(target) +""" +import inspect +from typing import Dict, Any + + +def _make_default_values(fullargspec: inspect.FullArgSpec) -> Dict[str, Any]: + """Returns default values from the function's fullargspec.""" + if fullargspec.defaults is not None: + defaults = { + name: value for name, value in zip( + fullargspec.args[-len(fullargspec.defaults):], fullargspec.defaults) + } + else: + defaults = {} + + if fullargspec.kwonlydefaults is not None: + defaults.update(fullargspec.kwonlydefaults) + + return defaults + + +def fullargspec_to_signature( + fullargspec: inspect.FullArgSpec) -> inspect.Signature: + """Repackages fullargspec information into an equivalent inspect.Signature.""" + defaults = _make_default_values(fullargspec) + parameters = [] + + for arg in fullargspec.args: + parameters.append( + inspect.Parameter( + arg, + inspect.Parameter.POSITIONAL_OR_KEYWORD, + default=defaults.get(arg, inspect.Parameter.empty), + ) + ) + + if fullargspec.varargs is not None: + parameters.append( + inspect.Parameter(fullargspec.varargs, inspect.Parameter.VAR_POSITIONAL) + ) + + for kwarg in fullargspec.kwonlyargs: + parameters.append( + inspect.Parameter( + kwarg, + inspect.Parameter.KEYWORD_ONLY, + default=defaults.get(kwarg, inspect.Parameter.empty), + ) + ) + + if fullargspec.varkw is not None: + parameters.append( + inspect.Parameter(fullargspec.varkw, inspect.Parameter.VAR_KEYWORD) + ) + + return inspect.Signature(parameters) + + +def make_decorator(target, + decorator_func, + decorator_name=None, + decorator_doc='', + decorator_argspec=None): + """Make a decorator from a wrapper and a target. + + Args: + target: The final callable to be wrapped. + decorator_func: The wrapper function. + decorator_name: The name of the decorator. If `None`, the name of the + function calling make_decorator. + decorator_doc: Documentation specific to this application of + `decorator_func` to `target`. + decorator_argspec: Override the signature using FullArgSpec. + + Returns: + The `decorator_func` argument with new metadata attached. + """ + if decorator_name is None: + decorator_name = inspect.currentframe().f_back.f_code.co_name + decorator = TFDecorator(decorator_name, target, decorator_doc, + decorator_argspec) + setattr(decorator_func, '_tf_decorator', decorator) + # Objects that are callables (e.g., a functools.partial object) may not have + # the following attributes. + if hasattr(target, '__name__'): + decorator_func.__name__ = target.__name__ + if hasattr(target, '__qualname__'): + decorator_func.__qualname__ = target.__qualname__ + if hasattr(target, '__module__'): + decorator_func.__module__ = target.__module__ + if hasattr(target, '__dict__'): + # Copy dict entries from target which are not overridden by decorator_func. + for name in target.__dict__: + if name not in decorator_func.__dict__: + decorator_func.__dict__[name] = target.__dict__[name] + if hasattr(target, '__doc__'): + decorator_func.__doc__ = decorator.__doc__ + decorator_func.__wrapped__ = target + # Keeping a second handle to `target` allows callers to detect whether the + # decorator was modified using `rewrap`. + decorator_func.__original_wrapped__ = target + if decorator_argspec: + decorator_func.__signature__ = fullargspec_to_signature( + decorator_argspec) + elif callable(target): + try: + signature = inspect.signature(target) + except (TypeError, ValueError): + # Certain callables such as builtins can not be inspected for signature. + pass + else: + bound_instance = _get_bound_instance(target) + # Present the decorated func as a method as well + if bound_instance and 'self' in signature.parameters: + signature = inspect.Signature(list(signature.parameters.values())[1:]) + decorator_func.__self__ = bound_instance + + decorator_func.__signature__ = signature + + return decorator_func + + +def _get_bound_instance(target): + """Returns the instance any of the targets is attached to.""" + decorators, target = unwrap(target) + for decorator in decorators: + if inspect.ismethod(decorator.decorated_target): + return decorator.decorated_target.__self__ + + +def _has_tf_decorator_attr(obj): + """Checks if object has _tf_decorator attribute. + + This check would work for mocked object as well since it would + check if returned attribute has the right type. + + Args: + obj: Python object. + """ + return (hasattr(obj, '_tf_decorator') and + isinstance(getattr(obj, '_tf_decorator'), TFDecorator)) + + +def rewrap(decorator_func, previous_target, new_target): + """Injects a new target into a function built by make_decorator. + + This function allows replacing a function wrapped by `decorator_func`, + assuming the decorator that wraps the function is written as described below. + + The decorator function must use `.__wrapped__` instead of the + wrapped function that is normally used: + + Example: + + # Instead of this: + def simple_parametrized_wrapper(*args, **kwds): + return wrapped_fn(*args, **kwds) + + tf_decorator.make_decorator(simple_parametrized_wrapper, wrapped_fn) + + # Write this: + def simple_parametrized_wrapper(*args, **kwds): + return simple_parametrized_wrapper.__wrapped__(*args, **kwds) + + tf_decorator.make_decorator(simple_parametrized_wrapper, wrapped_fn) + + Note that this process modifies decorator_func. + + Args: + decorator_func: Callable returned by `wrap`. + previous_target: Callable that needs to be replaced. + new_target: Callable to replace previous_target with. + + Returns: + The updated decorator. If decorator_func is not a tf_decorator, new_target + is returned. + """ + # Because the process mutates the decorator, we only need to alter the + # innermost function that wraps previous_target. + cur = decorator_func + innermost_decorator = None + target = None + while _has_tf_decorator_attr(cur): + innermost_decorator = cur + target = getattr(cur, '_tf_decorator') + if target.decorated_target is previous_target: + break + cur = target.decorated_target + assert cur is not None + + # If decorator_func is not a decorator, new_target replaces it directly. + if innermost_decorator is None: + # Consistency check. The caller should always pass the result of + # tf_decorator.unwrap as previous_target. If decorator_func is not a + # decorator, that will have returned decorator_func itself. + assert decorator_func is previous_target + return new_target + + target.decorated_target = new_target + + if inspect.ismethod(innermost_decorator): + # Bound methods can't be assigned attributes. Thankfully, they seem to + # be just proxies for their unbound counterpart, and we can modify that. + if hasattr(innermost_decorator, '__func__'): + innermost_decorator.__func__.__wrapped__ = new_target + elif hasattr(innermost_decorator, 'im_func'): + innermost_decorator.im_func.__wrapped__ = new_target + else: + innermost_decorator.__wrapped__ = new_target + else: + innermost_decorator.__wrapped__ = new_target + + return decorator_func + + +def unwrap(maybe_tf_decorator): + """Unwraps an object into a list of TFDecorators and a final target. + + Args: + maybe_tf_decorator: Any callable object. + + Returns: + A tuple whose first element is an list of TFDecorator-derived objects that + were applied to the final callable target, and whose second element is the + final undecorated callable target. If the `maybe_tf_decorator` parameter is + not decorated by any TFDecorators, the first tuple element will be an empty + list. The `TFDecorator` list is ordered from outermost to innermost + decorators. + """ + decorators = [] + cur = maybe_tf_decorator + while True: + if isinstance(cur, TFDecorator): + decorators.append(cur) + elif _has_tf_decorator_attr(cur): + decorators.append(getattr(cur, '_tf_decorator')) + else: + break + if not hasattr(decorators[-1], 'decorated_target'): + break + cur = decorators[-1].decorated_target + return decorators, cur + + +class TFDecorator(object): + """Base class for all TensorFlow decorators. + + TFDecorator captures and exposes the wrapped target, and provides details + about the current decorator. + """ + + def __init__(self, + decorator_name, + target, + decorator_doc='', + decorator_argspec=None): + self._decorated_target = target + self._decorator_name = decorator_name + self._decorator_doc = decorator_doc + self._decorator_argspec = decorator_argspec + if hasattr(target, '__name__'): + self.__name__ = target.__name__ + if hasattr(target, '__qualname__'): + self.__qualname__ = target.__qualname__ + if self._decorator_doc: + self.__doc__ = self._decorator_doc + elif hasattr(target, '__doc__') and target.__doc__: + self.__doc__ = target.__doc__ + else: + self.__doc__ = '' + + if decorator_argspec: + self.__signature__ = fullargspec_to_signature(decorator_argspec) + elif callable(target): + try: + self.__signature__ = inspect.signature(target) + except (TypeError, ValueError): + # Certain callables such as builtins can not be inspected for signature. + pass + + def __get__(self, instance, owner): + return self._decorated_target.__get__(instance, owner) + + def __call__(self, *args, **kwargs): + return self._decorated_target(*args, **kwargs) + + @property + def decorated_target(self): + return self._decorated_target + + @decorated_target.setter + def decorated_target(self, decorated_target): + self._decorated_target = decorated_target + + @property + def decorator_name(self): + return self._decorator_name + + @property + def decorator_doc(self): + return self._decorator_doc + + @property + def decorator_argspec(self): + return self._decorator_argspec \ No newline at end of file diff --git a/src/braket/experimental/autoqasm/autograph/tf_utils/tf_export.py b/src/braket/experimental/autoqasm/autograph/tf_utils/tf_export.py new file mode 100644 index 00000000..4d60f4bd --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/tf_utils/tf_export.py @@ -0,0 +1,420 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Utilities for exporting TensorFlow symbols to the API. + +Exporting a function or a class: + +To export a function or a class use tf_export decorator. For e.g.: +```python +@tf_export('foo', 'bar.foo') +def foo(...): + ... +``` + +If a function is assigned to a variable, you can export it by calling +tf_export explicitly. For e.g.: +```python +foo = get_foo(...) +tf_export('foo', 'bar.foo')(foo) +``` + + +Exporting a constant +```python +foo = 1 +tf_export('consts.foo').export_constant(__name__, 'foo') +``` +""" +import collections +import functools +import sys + +from braket.experimental.autoqasm.autograph.tf_utils import tf_decorator +import inspect as tf_inspect + +ESTIMATOR_API_NAME = 'estimator' +KERAS_API_NAME = 'keras' +TENSORFLOW_API_NAME = 'tensorflow' + +# List of subpackage names used by TensorFlow components. Have to check that +# TensorFlow core repo does not export any symbols under these names. +SUBPACKAGE_NAMESPACES = [ESTIMATOR_API_NAME] + +_Attributes = collections.namedtuple( + 'ExportedApiAttributes', ['names', 'constants']) + +# Attribute values must be unique to each API. +API_ATTRS = { + TENSORFLOW_API_NAME: _Attributes( + '_tf_api_names', + '_tf_api_constants'), + ESTIMATOR_API_NAME: _Attributes( + '_estimator_api_names', + '_estimator_api_constants'), + KERAS_API_NAME: _Attributes( + '_keras_api_names', + '_keras_api_constants') +} + +API_ATTRS_V1 = { + TENSORFLOW_API_NAME: _Attributes( + '_tf_api_names_v1', + '_tf_api_constants_v1'), + ESTIMATOR_API_NAME: _Attributes( + '_estimator_api_names_v1', + '_estimator_api_constants_v1'), + KERAS_API_NAME: _Attributes( + '_keras_api_names_v1', + '_keras_api_constants_v1') +} + + +class SymbolAlreadyExposedError(Exception): + """Raised when adding API names to symbol that already has API names.""" + pass + + +class InvalidSymbolNameError(Exception): + """Raised when trying to export symbol as an invalid or unallowed name.""" + pass + +_NAME_TO_SYMBOL_MAPPING = dict() + + +def get_symbol_from_name(name): + return _NAME_TO_SYMBOL_MAPPING.get(name) + + +def get_canonical_name_for_symbol( + symbol, api_name=TENSORFLOW_API_NAME, + add_prefix_to_v1_names=False): + """Get canonical name for the API symbol. + + Example: + ```python + from tensorflow.python.util import tf_export + cls = tf_export.get_symbol_from_name('keras.optimizers.Adam') + + # Gives `` + print(cls) + + # Gives `keras.optimizers.Adam` + print(tf_export.get_canonical_name_for_symbol(cls, api_name='keras')) + ``` + + Args: + symbol: API function or class. + api_name: API name (tensorflow or estimator). + add_prefix_to_v1_names: Specifies whether a name available only in V1 + should be prefixed with compat.v1. + + Returns: + Canonical name for the API symbol (for e.g. initializers.zeros) if + canonical name could be determined. Otherwise, returns None. + """ + if not hasattr(symbol, '__dict__'): + return None + api_names_attr = API_ATTRS[api_name].names + _, undecorated_symbol = tf_decorator.unwrap(symbol) + if api_names_attr not in undecorated_symbol.__dict__: + return None + api_names = getattr(undecorated_symbol, api_names_attr) + deprecated_api_names = undecorated_symbol.__dict__.get( + '_tf_deprecated_api_names', []) + + canonical_name = get_canonical_name(api_names, deprecated_api_names) + if canonical_name: + return canonical_name + + # If there is no V2 canonical name, get V1 canonical name. + api_names_attr = API_ATTRS_V1[api_name].names + api_names = getattr(undecorated_symbol, api_names_attr) + v1_canonical_name = get_canonical_name(api_names, deprecated_api_names) + if add_prefix_to_v1_names: + return 'compat.v1.%s' % v1_canonical_name + return v1_canonical_name + + +def get_canonical_name(api_names, deprecated_api_names): + """Get preferred endpoint name. + + Args: + api_names: API names iterable. + deprecated_api_names: Deprecated API names iterable. + Returns: + Returns one of the following in decreasing preference: + - first non-deprecated endpoint + - first endpoint + - None + """ + non_deprecated_name = next( + (name for name in api_names if name not in deprecated_api_names), + None) + if non_deprecated_name: + return non_deprecated_name + if api_names: + return api_names[0] + return None + + +def get_v1_names(symbol): + """Get a list of TF 1.* names for this symbol. + + Args: + symbol: symbol to get API names for. + + Returns: + List of all API names for this symbol including TensorFlow and + Estimator names. + """ + names_v1 = [] + tensorflow_api_attr_v1 = API_ATTRS_V1[TENSORFLOW_API_NAME].names + estimator_api_attr_v1 = API_ATTRS_V1[ESTIMATOR_API_NAME].names + keras_api_attr_v1 = API_ATTRS_V1[KERAS_API_NAME].names + + if not hasattr(symbol, '__dict__'): + return names_v1 + if tensorflow_api_attr_v1 in symbol.__dict__: + names_v1.extend(getattr(symbol, tensorflow_api_attr_v1)) + if estimator_api_attr_v1 in symbol.__dict__: + names_v1.extend(getattr(symbol, estimator_api_attr_v1)) + if keras_api_attr_v1 in symbol.__dict__: + names_v1.extend(getattr(symbol, keras_api_attr_v1)) + return names_v1 + + +def get_v2_names(symbol): + """Get a list of TF 2.0 names for this symbol. + + Args: + symbol: symbol to get API names for. + + Returns: + List of all API names for this symbol including TensorFlow and + Estimator names. + """ + names_v2 = [] + tensorflow_api_attr = API_ATTRS[TENSORFLOW_API_NAME].names + estimator_api_attr = API_ATTRS[ESTIMATOR_API_NAME].names + keras_api_attr = API_ATTRS[KERAS_API_NAME].names + + if not hasattr(symbol, '__dict__'): + return names_v2 + if tensorflow_api_attr in symbol.__dict__: + names_v2.extend(getattr(symbol, tensorflow_api_attr)) + if estimator_api_attr in symbol.__dict__: + names_v2.extend(getattr(symbol, estimator_api_attr)) + if keras_api_attr in symbol.__dict__: + names_v2.extend(getattr(symbol, keras_api_attr)) + return names_v2 + + +def get_v1_constants(module): + """Get a list of TF 1.* constants in this module. + + Args: + module: TensorFlow module. + + Returns: + List of all API constants under the given module including TensorFlow and + Estimator constants. + """ + constants_v1 = [] + tensorflow_constants_attr_v1 = API_ATTRS_V1[TENSORFLOW_API_NAME].constants + estimator_constants_attr_v1 = API_ATTRS_V1[ESTIMATOR_API_NAME].constants + + if hasattr(module, tensorflow_constants_attr_v1): + constants_v1.extend(getattr(module, tensorflow_constants_attr_v1)) + if hasattr(module, estimator_constants_attr_v1): + constants_v1.extend(getattr(module, estimator_constants_attr_v1)) + return constants_v1 + + +def get_v2_constants(module): + """Get a list of TF 2.0 constants in this module. + + Args: + module: TensorFlow module. + + Returns: + List of all API constants under the given module including TensorFlow and + Estimator constants. + """ + constants_v2 = [] + tensorflow_constants_attr = API_ATTRS[TENSORFLOW_API_NAME].constants + estimator_constants_attr = API_ATTRS[ESTIMATOR_API_NAME].constants + + if hasattr(module, tensorflow_constants_attr): + constants_v2.extend(getattr(module, tensorflow_constants_attr)) + if hasattr(module, estimator_constants_attr): + constants_v2.extend(getattr(module, estimator_constants_attr)) + return constants_v2 + + +class api_export(object): # pylint: disable=invalid-name + """Provides ways to export symbols to the TensorFlow API.""" + + def __init__(self, *args, **kwargs): # pylint: disable=g-doc-args + """Export under the names *args (first one is considered canonical). + + Args: + *args: API names in dot delimited format. + **kwargs: Optional keyed arguments. + v1: Names for the TensorFlow V1 API. If not set, we will use V2 API + names both for TensorFlow V1 and V2 APIs. + overrides: List of symbols that this is overriding + (those overrided api exports will be removed). Note: passing overrides + has no effect on exporting a constant. + api_name: Name of the API you want to generate (e.g. `tensorflow` or + `estimator`). Default is `tensorflow`. + allow_multiple_exports: Allow symbol to be exported multiple time under + different names. + """ + self._names = args + self._names_v1 = kwargs.get('v1', args) + if 'v2' in kwargs: + raise ValueError('You passed a "v2" argument to tf_export. This is not ' + 'what you want. Pass v2 names directly as positional ' + 'arguments instead.') + self._api_name = kwargs.get('api_name', TENSORFLOW_API_NAME) + self._overrides = kwargs.get('overrides', []) + self._allow_multiple_exports = kwargs.get('allow_multiple_exports', False) + + self._validate_symbol_names() + + def _validate_symbol_names(self): + """Validate you are exporting symbols under an allowed package. + + We need to ensure things exported by tf_export, estimator_export, etc. + export symbols under disjoint top-level package names. + + For TensorFlow, we check that it does not export anything under subpackage + names used by components (estimator, keras, etc.). + + For each component, we check that it exports everything under its own + subpackage. + + Raises: + InvalidSymbolNameError: If you try to export symbol under disallowed name. + """ + all_symbol_names = set(self._names) | set(self._names_v1) + if self._api_name == TENSORFLOW_API_NAME: + for subpackage in SUBPACKAGE_NAMESPACES: + if any(n.startswith(subpackage) for n in all_symbol_names): + raise InvalidSymbolNameError( + '@tf_export is not allowed to export symbols under %s.*' % ( + subpackage)) + else: + if not all(n.startswith(self._api_name) for n in all_symbol_names): + raise InvalidSymbolNameError( + 'Can only export symbols under package name of component. ' + 'e.g. tensorflow_estimator must export all symbols under ' + 'tf.estimator') + + def __call__(self, func): + """Calls this decorator. + + Args: + func: decorated symbol (function or class). + + Returns: + The input function with _tf_api_names attribute set. + + Raises: + SymbolAlreadyExposedError: Raised when a symbol already has API names + and kwarg `allow_multiple_exports` not set. + """ + api_names_attr = API_ATTRS[self._api_name].names + api_names_attr_v1 = API_ATTRS_V1[self._api_name].names + # Undecorate overridden names + for f in self._overrides: + _, undecorated_f = tf_decorator.unwrap(f) + delattr(undecorated_f, api_names_attr) + delattr(undecorated_f, api_names_attr_v1) + + _, undecorated_func = tf_decorator.unwrap(func) + self.set_attr(undecorated_func, api_names_attr, self._names) + self.set_attr(undecorated_func, api_names_attr_v1, self._names_v1) + + for name in self._names: + _NAME_TO_SYMBOL_MAPPING[name] = func + for name_v1 in self._names_v1: + _NAME_TO_SYMBOL_MAPPING['compat.v1.%s' % name_v1] = func + + return func + + def set_attr(self, func, api_names_attr, names): + # Check for an existing api. We check if attribute name is in + # __dict__ instead of using hasattr to verify that subclasses have + # their own _tf_api_names as opposed to just inheriting it. + if api_names_attr in func.__dict__: + if not self._allow_multiple_exports: + raise SymbolAlreadyExposedError( + 'Symbol %s is already exposed as %s.' % + (func.__name__, getattr(func, api_names_attr))) # pylint: disable=protected-access + setattr(func, api_names_attr, names) + + def export_constant(self, module_name, name): + """Store export information for constants/string literals. + + Export information is stored in the module where constants/string literals + are defined. + + e.g. + ```python + foo = 1 + bar = 2 + tf_export("consts.foo").export_constant(__name__, 'foo') + tf_export("consts.bar").export_constant(__name__, 'bar') + ``` + + Args: + module_name: (string) Name of the module to store constant at. + name: (string) Current constant name. + """ + module = sys.modules[module_name] + api_constants_attr = API_ATTRS[self._api_name].constants + api_constants_attr_v1 = API_ATTRS_V1[self._api_name].constants + + if not hasattr(module, api_constants_attr): + setattr(module, api_constants_attr, []) + # pylint: disable=protected-access + getattr(module, api_constants_attr).append( + (self._names, name)) + + if not hasattr(module, api_constants_attr_v1): + setattr(module, api_constants_attr_v1, []) + getattr(module, api_constants_attr_v1).append( + (self._names_v1, name)) + + +def kwarg_only(f): + """A wrapper that throws away all non-kwarg arguments.""" + f_argspec = tf_inspect.getfullargspec(f) + + def wrapper(*args, **kwargs): + if args: + raise TypeError( + '{f} only takes keyword args (possible keys: {kwargs}). ' + 'Please pass these args as kwargs instead.' + .format(f=f.__name__, kwargs=f_argspec.args)) + return f(**kwargs) + + return tf_decorator.make_decorator( + f, wrapper, decorator_argspec=f_argspec) + + +tf_export = functools.partial(api_export, api_name=TENSORFLOW_API_NAME) +keras_export = functools.partial(api_export, api_name=KERAS_API_NAME) \ No newline at end of file diff --git a/src/braket/experimental/autoqasm/autograph/tf_utils/tf_stack.py b/src/braket/experimental/autoqasm/autograph/tf_utils/tf_stack.py new file mode 100644 index 00000000..91cc8df3 --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/tf_utils/tf_stack.py @@ -0,0 +1,168 @@ +# Copyright 2015 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Functions used to extract and analyze stacks. Faster than Python libs.""" +# pylint: disable=g-bad-name +import collections +import inspect +import threading +import traceback + +# Generally such lookups should be done using `threading.local()`. See +# https://blogs.gnome.org/jamesh/2008/06/11/tls-python/ for a detailed +# explanation of why. However the transform stacks are expected to be empty +# when a thread is joined, so reusing the key does not introduce a correctness +# issue. Moreover, get_ident is faster than storing and retrieving a unique +# key in a thread local store. +_get_thread_key = threading.get_ident + + +# TODO(mdan): Move these to C++ as well. +# Moving to C++ can further avoid extra copies made by get_effective_map. +_source_mapper_stacks = collections.defaultdict(lambda: [SentinelMapper()]) +_source_filter_stacks = collections.defaultdict(lambda: [SentinelFilter()]) + + +class StackTraceTransform(object): + """Base class for stack trace transformation functions.""" + + _stack_dict = None # Subclasses should override + _thread_key = None + + def __enter__(self): + # Any given instance is assumed to be used by a single thread, which reduces + # expensive thread local lookups. + if self._thread_key is None: + self._thread_key = _get_thread_key() + else: + assert self._thread_key == _get_thread_key(), 'Shared across threads?' + + stack = self._stack_dict[self._thread_key] + self.parent = stack[-1] + stack.append(self) + self.update() + return self + + def __exit__(self, unused_type, unused_value, unused_traceback): + top = self._stack_dict[self._thread_key].pop() + assert top is self, 'Concurrent access?' + + def update(self): + raise NotImplementedError('subclasses need to override this') + + +class StackTraceMapper(StackTraceTransform): + """Allows remapping traceback information to different source code.""" + _stack_dict = _source_mapper_stacks + + def __init__(self): + pass + + def update(self): + pass + + def get_effective_source_map(self): + """Returns a map (filename, lineno) -> (filename, lineno, function_name).""" + raise NotImplementedError('subclasses need to override this') + + +EMPTY_DICT = {} + + +class SentinelMapper(StackTraceMapper): + + def get_effective_source_map(self): + return EMPTY_DICT + + +class StackTraceFilter(StackTraceTransform): + """Allows filtering traceback information by removing superfluous frames.""" + _stack_dict = _source_filter_stacks + + def __init__(self): + pass + + def update(self): + pass + + def get_filtered_filenames(self): + raise NotImplementedError('subclasses need to override this') + + +EMPTY_SET = frozenset() + + +class SentinelFilter(StackTraceFilter): + + def get_filtered_filenames(self): + return EMPTY_SET + + +class CurrentModuleFilter(StackTraceFilter): + """Filters stack frames from the module where this is used (best effort).""" + + def __init__(self): + super().__init__() + filter_filename = None + outer_f = None + f = inspect.currentframe() + try: + if f is not None: + # The current frame is __init__. The first outer frame should be the + # caller. + outer_f = f.f_back + if outer_f is not None: + filter_filename = inspect.getsourcefile(outer_f) + self._filename = filter_filename + # This may be called repeatedly: once on entry by the superclass, then by + # each child context manager. + self._cached_set = None + finally: + # Avoid reference cycles, see: + # https://docs.python.org/3.7/library/inspect.html#the-interpreter-stack + del f + del outer_f + + def get_filtered_filenames(self): + if self._cached_set is not None: + return self._cached_set + + filtered_filenames = frozenset((self._filename,)) + if self.parent is not None: + filtered_filenames |= self.parent.get_filtered_filenames() + self._cached_set = filtered_filenames + return filtered_filenames + + +def extract_stack(): + """An eager-friendly alternative to traceback.extract_stack. + + Returns: + A list-like FrameSummary containing StackFrame-like objects, which are + namedtuple-like objects with the following fields: filename, lineno, name, + line, meant to masquerade as traceback.FrameSummary objects. + """ + return traceback.extract_stack() + + +# TODO(mdan): Revisit these - a single location is almost always sufficient. +def extract_stack_for_op(c_op, stacklevel=1): + """Attaches the current stack trace to `c_op`. + + Args: + c_op: a TF_Operation object. + stacklevel: An integer for ignoring Python wrapper stack frames. + The default value of 1 ignores this function from the frame. + """ + return traceback.extract_stack() \ No newline at end of file diff --git a/src/braket/experimental/autoqasm/autograph/tf_utils/traceback_utils.py b/src/braket/experimental/autoqasm/autograph/tf_utils/traceback_utils.py new file mode 100644 index 00000000..bf7cb88d --- /dev/null +++ b/src/braket/experimental/autoqasm/autograph/tf_utils/traceback_utils.py @@ -0,0 +1,157 @@ +# Copyright 2021 The TensorFlow Authors. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============================================================================== +"""Utilities related to TensorFlow exception stack trace prettifying.""" + +import os +import sys +import threading +import traceback +import types +from braket.experimental.autoqasm.autograph.tf_utils import tf_decorator +from braket.experimental.autoqasm.autograph.tf_utils.tf_export import tf_export + + +_ENABLE_TRACEBACK_FILTERING = threading.local() +_EXCLUDED_PATHS = ( + os.path.abspath(os.path.join(__file__, '..', '..')), +) + + +@tf_export('debugging.is_traceback_filtering_enabled') +def is_traceback_filtering_enabled(): + """Check whether traceback filtering is currently enabled. + + See also `tf.debugging.enable_traceback_filtering()` and + `tf.debugging.disable_traceback_filtering()`. Note that filtering out + internal frames from the tracebacks of exceptions raised by TensorFlow code + is the default behavior. + + Returns: + True if traceback filtering is enabled + (e.g. if `tf.debugging.enable_traceback_filtering()` was called), + and False otherwise (e.g. if `tf.debugging.disable_traceback_filtering()` + was called). + """ + value = getattr(_ENABLE_TRACEBACK_FILTERING, 'value', True) + return value + + +@tf_export('debugging.enable_traceback_filtering') +def enable_traceback_filtering(): + """Enable filtering out TensorFlow-internal frames in exception stack traces. + + Raw TensorFlow stack traces involve many internal frames, which can be + challenging to read through, while not being actionable for end users. + By default, TensorFlow filters internal frames in most exceptions that it + raises, to keep stack traces short, readable, and focused on what's + actionable for end users (their own code). + + If you have previously disabled traceback filtering via + `tf.debugging.disable_traceback_filtering()`, you can re-enable it via + `tf.debugging.enable_traceback_filtering()`. + + Raises: + RuntimeError: If Python version is not at least 3.7. + """ + if sys.version_info.major != 3 or sys.version_info.minor < 7: + raise RuntimeError( + f'Traceback filtering is only available with Python 3.7 or higher. ' + f'This Python version: {sys.version}') + global _ENABLE_TRACEBACK_FILTERING + _ENABLE_TRACEBACK_FILTERING.value = True + + +@tf_export('debugging.disable_traceback_filtering') +def disable_traceback_filtering(): + """Disable filtering out TensorFlow-internal frames in exception stack traces. + + Raw TensorFlow stack traces involve many internal frames, which can be + challenging to read through, while not being actionable for end users. + By default, TensorFlow filters internal frames in most exceptions that it + raises, to keep stack traces short, readable, and focused on what's + actionable for end users (their own code). + + Calling `tf.debugging.disable_traceback_filtering` disables this filtering + mechanism, meaning that TensorFlow exceptions stack traces will include + all frames, in particular TensorFlow-internal ones. + + **If you are debugging a TensorFlow-internal issue, you need to call + `tf.debugging.disable_traceback_filtering`**. + To re-enable traceback filtering afterwards, you can call + `tf.debugging.enable_traceback_filtering()`. + """ + global _ENABLE_TRACEBACK_FILTERING + _ENABLE_TRACEBACK_FILTERING.value = False + + +def include_frame(fname): + for exclusion in _EXCLUDED_PATHS: + if exclusion in fname: + return False + return True + + +def _process_traceback_frames(tb): + new_tb = None + tb_list = list(traceback.walk_tb(tb)) + for f, line_no in reversed(tb_list): + if include_frame(f.f_code.co_filename): + new_tb = types.TracebackType(new_tb, f, f.f_lasti, line_no) + if new_tb is None and tb_list: + f, line_no = tb_list[-1] + new_tb = types.TracebackType(new_tb, f, f.f_lasti, line_no) + return new_tb + + +def filter_traceback(fn): + """Decorator to filter out TF-internal stack trace frames in exceptions. + + Raw TensorFlow stack traces involve many internal frames, which can be + challenging to read through, while not being actionable for end users. + By default, TensorFlow filters internal frames in most exceptions that it + raises, to keep stack traces short, readable, and focused on what's + actionable for end users (their own code). + + Arguments: + fn: The function or method to decorate. Any exception raised within the + function will be reraised with its internal stack trace frames filtered + out. + + Returns: + Decorated function or method. + """ + if sys.version_info.major != 3 or sys.version_info.minor < 7: + return fn + + def error_handler(*args, **kwargs): + try: + if not is_traceback_filtering_enabled(): + return fn(*args, **kwargs) + except NameError: + # In some very rare cases, + # `is_traceback_filtering_enabled` (from the outer scope) may not be + # accessible from inside this function + return fn(*args, **kwargs) + + filtered_tb = None + try: + return fn(*args, **kwargs) + except Exception as e: + filtered_tb = _process_traceback_frames(e.__traceback__) + raise e.with_traceback(filtered_tb) from None + finally: + del filtered_tb + + return tf_decorator.make_decorator(fn, error_handler) \ No newline at end of file diff --git a/src/braket/experimental/autoqasm/constants.py b/src/braket/experimental/autoqasm/constants.py new file mode 100644 index 00000000..629fc200 --- /dev/null +++ b/src/braket/experimental/autoqasm/constants.py @@ -0,0 +1,37 @@ +# 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. + +"""Constants used in the output conversion of AutoQASM programs. When we output programs to +to OpenQASM, we must declare variables and thus associate them with a string name. +""" + +ARRAY_NAME_TEMPLATE = "__arr_{0}__" +"""String template for auto-generated array names.""" + +BIT_NAME_TEMPLATE = "__bit_{0}__" +"""String template for auto-generated bit variable names.""" + +BOOL_NAME_TEMPLATE = "__bool_{0}__" +"""String template for auto-generated boolean variable names.""" + +FLOAT_NAME_TEMPLATE = "__float_{0}__" +"""String template for auto-generated float names.""" + +INT_NAME_TEMPLATE = "__int_{0}__" +"""String template for auto-generated integer names.""" + +QUBIT_REGISTER = "__qubits__" +"""Qubits are globally addressed, and so we can specify a single qubit register name.""" + +RETVAL_VARIABLE_NAME = "retval_" +"""A special name for variables assigned to the return values of function calls.""" diff --git a/src/braket/experimental/autoqasm/converters/__init__.py b/src/braket/experimental/autoqasm/converters/__init__.py new file mode 100644 index 00000000..85d6df76 --- /dev/null +++ b/src/braket/experimental/autoqasm/converters/__init__.py @@ -0,0 +1,19 @@ +# 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. + + +"""AST node-level transformations for code related to AutoQASM types. A converter transformation +injects the content of an AST node into a code template, creating a transformed AST node. The +transformed AST node is the output of a converter. This module implements converters that AutoQASM +overloads or adds on top of AutoGraph. +""" diff --git a/src/braket/experimental/autoqasm/converters/assignments.py b/src/braket/experimental/autoqasm/converters/assignments.py new file mode 100644 index 00000000..bef261e6 --- /dev/null +++ b/src/braket/experimental/autoqasm/converters/assignments.py @@ -0,0 +1,72 @@ +# 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. + + +"""Converters for assignment nodes.""" + +import ast + +import gast + +from braket.experimental.autoqasm.autograph.core import ag_ctx, converter +from braket.experimental.autoqasm.autograph.pyct import templates + + +class AssignTransformer(converter.Base): + def visit_Assign(self, node: ast.stmt) -> ast.stmt: + """Converts assignment operations to their AutoQASM counterpart. + Supports assignment to a single variable. Operator declares the + ``oq`` variable, or sets variable's value if it's already declared. + + Args: + node (ast.stmt): AST node to transform. + + Returns: + ast.stmt: Transformed node. + """ + template = """ + tar_ = ag__.assign_stmt(tar_name_, val_) + """ + node = self.generic_visit(node) + + # TODO: implement when target has multiple variable + if len(node.targets) > 1: + raise NotImplementedError + + if isinstance(node.targets[0], gast.Name): + target_name = gast.Constant(node.targets[0].id, None) + new_node = templates.replace( + template, + tar_name_=target_name, + tar_=node.targets[0], + val_=node.value, + original=node, + ) + else: + new_node = node + + return new_node + + +def transform(node: ast.stmt, ctx: ag_ctx.ControlStatusCtx) -> ast.stmt: + """Transform assignment nodes. + + Args: + node (ast.stmt): AST node to transform. + ctx (ag_ctx.ControlStatusCtx): Transformer context. + + Returns: + ast.stmt: Transformed node. + """ + + return AssignTransformer(ctx).visit(node) diff --git a/src/braket/experimental/autoqasm/errors.py b/src/braket/experimental/autoqasm/errors.py new file mode 100644 index 00000000..9d2f2da5 --- /dev/null +++ b/src/braket/experimental/autoqasm/errors.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. + +"""Errors raised in the AutoQASM build process.""" + +from braket.experimental.autoqasm.program import ProgramOptions + + +class AutoQasmError(Exception): + """Base class for all AutoQASM exceptions.""" + + +class UnknownQubitCountError(AutoQasmError): + """Missing declaration for the number of qubits.""" + + def __init__(self): + self.message = f"""Unspecified number of qubits. + +Please declare the total number of qubits for your program. \ +You can do that by calling your AutoQASM program with the \ +{ProgramOptions.NUM_QUBITS.value} keyword argument. \ +""" + + def __str__(self): + return self.message diff --git a/src/braket/experimental/autoqasm/gates/__init__.py b/src/braket/experimental/autoqasm/gates/__init__.py new file mode 100644 index 00000000..9e34606c --- /dev/null +++ b/src/braket/experimental/autoqasm/gates/__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. + +"""Quantum operations for use in AutoQASM programs.""" + +from .gates import QubitIdentifier, cnot, h, x # noqa: F401 +from .measurements import measure # noqa: F401 diff --git a/src/braket/experimental/autoqasm/gates/gates.py b/src/braket/experimental/autoqasm/gates/gates.py new file mode 100644 index 00000000..60b4906f --- /dev/null +++ b/src/braket/experimental/autoqasm/gates/gates.py @@ -0,0 +1,62 @@ +# 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. + + +"""Quantum gates, which are applied to qubits. Some gates take parameters as arguments in addition +to the qubits. + +Example of using a `h` gate and a `cnot` gate to create a Bell circuit: + +.. code-block:: python + + @aq.function + def bell(): + h(0) + cnot(0, 1) +""" + + +from braket.experimental.autoqasm import program + +from .qubits import QubitIdentifier, _qubit + + +def h(q: QubitIdentifier) -> None: + """Adds a Hadamard gate to the program on the specified qubit. + + Args: + q (QubitIdentifier): The target qubit. + """ + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + oqpy_program.gate(_qubit(q), "h") + + +def x(q: QubitIdentifier) -> None: + """Adds a pi rotation around the X axis on the specified qubit. + + Args: + q (QubitIdentifier): The target qubit. + """ + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + oqpy_program.gate(_qubit(q), "x") + + +def cnot(q_ctrl: QubitIdentifier, q_target: QubitIdentifier) -> None: + """Adds a CNOT gate to the program on the specified qubits. + + Args: + q_ctrl (QubitIdentifier): The control qubit. + q_target (QubitIdentifier): The target qubit. + """ + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + oqpy_program.gate([_qubit(q_ctrl), _qubit(q_target)], "cnot") diff --git a/src/braket/experimental/autoqasm/gates/measurements.py b/src/braket/experimental/autoqasm/gates/measurements.py new file mode 100644 index 00000000..c97059b8 --- /dev/null +++ b/src/braket/experimental/autoqasm/gates/measurements.py @@ -0,0 +1,62 @@ +# 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. + +"""Quantum measurement on qubits. + +Example of measuring qubit 0: + +.. code-block:: python + + @aq.function + def my_program(): + measure(0) +""" + + +from typing import List, Union + +from braket.experimental.autoqasm import BitVar, program +from braket.experimental.autoqasm.gates.qubits import QubitIdentifier, _qubit + + +def measure(qubits: Union[QubitIdentifier, List[QubitIdentifier]]) -> BitVar: + """Add qubit measurement statements to the program and assign the measurement + results to bit variables. + + Args: + qubits (Union[QubitIdentifier, List[QubitIdentifier]]): The target qubits to measure. + + Returns: + BitVar: Bit variable the measurement results are assigned to. + """ + if not isinstance(qubits, List): + qubits = [qubits] + + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + + bit_var_size = len(qubits) if len(qubits) > 1 else None + bit_var = BitVar( + name=program.get_program_conversion_context().next_var_name(BitVar), + size=bit_var_size, + needs_declaration=True, + ) + oqpy_program.declare(bit_var) + + qubits = [_qubit(qubit) for qubit in qubits] + if len(qubits) == 1: + oqpy_program.measure(qubits[0], bit_var) + else: + for idx, qubit in enumerate(qubits): + oqpy_program.measure(qubit, bit_var[idx]) + + return bit_var diff --git a/src/braket/experimental/autoqasm/gates/qubits.py b/src/braket/experimental/autoqasm/gates/qubits.py new file mode 100644 index 00000000..416d0f6a --- /dev/null +++ b/src/braket/experimental/autoqasm/gates/qubits.py @@ -0,0 +1,95 @@ +# 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. + + +"""Utility functions that handle qubit construction and naming.""" + +from functools import singledispatch +from typing import Any, Union + +import oqpy.base +from openpulse.printer import dumps + +from braket.experimental.autoqasm import constants, errors, program + +QubitIdentifier = Union[int, oqpy._ClassicalVar, oqpy.base.OQPyExpression, str] + + +def _global_qubit_register(qubit_idx_expr: Union[int, str]) -> str: + # TODO: We should index into a oqpy.QubitArray rather + # than manually generating the string to index into + # a hard-coded global qubit array. + return f"{constants.QUBIT_REGISTER}[{qubit_idx_expr}]" + + +@singledispatch +def _qubit(qid: Any) -> oqpy.Qubit: + """Maps a given qubit representation to an oqpy qubit. + + Args: + qid (Any): The qubit argument provided to a gate. + + Returns: + Qubit: A translated oqpy qubit. + """ + raise ValueError(f"invalid qubit label: '{qid}'") + + +@_qubit.register +def _(qid: bool) -> oqpy.Qubit: + raise ValueError(f"invalid qubit label: '{qid}'") + + +@_qubit.register +def _(qid: float) -> oqpy.Qubit: + raise TypeError(f"qubit index cannot be a float: '{qid}'") + + +@_qubit.register +def _(qid: int) -> oqpy.Qubit: + # Integer virtual qubit, like `h(0)` + program.get_program_conversion_context().register_qubit(qid) + return oqpy.Qubit(_global_qubit_register(qid), needs_declaration=False) + + +@_qubit.register +def _(qid: oqpy._ClassicalVar) -> oqpy.Qubit: + # Indexed by variable, such as i in range(n); h(i) + if program.get_program_conversion_context().get_declared_qubits() is None: + raise errors.UnknownQubitCountError() + return oqpy.Qubit(_global_qubit_register(qid.name), needs_declaration=False) + + +@_qubit.register +def _(qid: oqpy.base.OQPyExpression) -> oqpy.Qubit: + # Indexed by expression, such as i in range(n); h(i + 1) + if program.get_program_conversion_context().get_declared_qubits() is None: + raise errors.UnknownQubitCountError() + + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + qubit_idx_expr = dumps(qid.to_ast(oqpy_program)) + return oqpy.Qubit(_global_qubit_register(qubit_idx_expr), needs_declaration=False) + + +@_qubit.register +def _(qid: str) -> oqpy.Qubit: + # Physical qubit label, like `h("$0")` + if qid.startswith("$"): + qubit_idx = qid[1:] + try: + int(qubit_idx) + except ValueError: + raise ValueError(f"invalid physical qubit label: '{qid}'") + return oqpy.PhysicalQubits[qubit_idx] + else: + raise ValueError(f"invalid qubit label: '{qid}'") diff --git a/src/braket/experimental/autoqasm/operators/__init__.py b/src/braket/experimental/autoqasm/operators/__init__.py new file mode 100644 index 00000000..d99cee37 --- /dev/null +++ b/src/braket/experimental/autoqasm/operators/__init__.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. + + +"""Python program-level transformations for objects, operations and logical controls that relate +to AutoQASM types. Generally, operators are only used in the code template in AutoQASM converters. +This module implements operators that AutoQASM overloads or adds on top of AutoGraph. +""" + +# TODO: Commented out operators below are not yet implemented. +# We need to either implement these, or determine they are not needed and remove them. + +# Operators below are imported directly from core autograph implementation +from braket.experimental.autoqasm.autograph.operators.variables import ( # noqa: F401 + Undefined, + UndefinedReturnValue, + ld, + ldu, +) + +from .assignments import assign_stmt # noqa: F401 +from .conditional_expressions import if_exp # noqa: F401 +from .control_flow import for_stmt, if_stmt, while_stmt # noqa: F401 + +# from .data_structures import list_append +# from .data_structures import list_pop +# from .data_structures import list_stack +# from .data_structures import ListPopOpts +# from .data_structures import ListStackOpts +from .data_structures import new_list # noqa: F401 + +# from .exceptions import assert_stmt +# from .logical import and_ +from .logical import eq # noqa: F401 + +# from .logical import not_ +# from .logical import not_eq +# from .logical import or_ +# from .py_builtins import float_ +# from .py_builtins import int_ +# from .py_builtins import len_ +# from .py_builtins import print_ +# from .py_builtins import range_ +from .slices import GetItemOpts, get_item, set_item # noqa: F401 diff --git a/src/braket/experimental/autoqasm/operators/assignments.py b/src/braket/experimental/autoqasm/operators/assignments.py new file mode 100644 index 00000000..65bad6e2 --- /dev/null +++ b/src/braket/experimental/autoqasm/operators/assignments.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. + + +"""Operators for assignment statements.""" + +from typing import Any + +import oqpy +import oqpy.base + +from braket.experimental.autoqasm import constants, program, types +from braket.experimental.autoqasm.autograph.operators.variables import UndefinedReturnValue + + +def assign_stmt(target_name: str, value: Any) -> Any: + """Operator declares the `oq` variable, or sets variable's value if it's + already declared. + + Args: + target_name (str): The name of assignment target. It is the variable + name on the lhs of an assignment statement. + value (Any): The value of assignment. It is the object on the rhs of + an assignment statement. + + Returns: + Any: Assignment value with updated name attribute if the value is an + `oqpy` type. Otherwise, it returns unchanged assignment value. + """ + # TODO: The logic branch for return value and measurement should be handled + # in different converters. + if isinstance(value, UndefinedReturnValue): + return value + + if target_name == constants.RETVAL_VARIABLE_NAME: + return types.wrap_value(value) + + if isinstance(value, oqpy.base.Var): + # TODO: If name is defined in value, it might be different from target_name. + # We should probably validate that. + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + + is_target_name_used = _is_variable_used(target_name) + is_value_used = _is_variable_used(value.name) + + if is_target_name_used: + target = _get_oqpy_program_variable(target_name) + _validate_variables_type_size(target, value) + if is_value_used: + oqpy_program.set(target, value) + else: + # Set to `value.init_expression` to avoid declaring an unnecessary variable. + oqpy_program.set(target, value.init_expression) + else: + target = type(value)(name=target_name, size=value.size) + + if is_value_used: + oqpy_program.declare(target) + oqpy_program.set(target, value) + else: + # Set to `value.init_expression` to avoid declaring an unnecessary variable. + target.init_expression = value.init_expression + oqpy_program.declare(target) + + return target + + return value + + +def _is_variable_used(var_name: str) -> bool: + """Check if the variable already exists in the oqpy program. + + Args: + var_name (str): variable name + + Returns: + bool: Return True if the variable already exists + """ + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + return ( + var_name in oqpy_program.declared_vars.keys() + or var_name in oqpy_program.undeclared_vars.keys() + ) + + +def _get_oqpy_program_variable(var_name: str) -> oqpy.base.Var: + """Return oqpy variable of the specified name used in the oqpy program. + + Args: + var_name (str): Name of the variable + + Returns: + oqpy.base.Var: Variable with the specified name in the oqpy program. + """ + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + variables = {**oqpy_program.declared_vars, **oqpy_program.undeclared_vars} + return variables[var_name] + + +def _validate_variables_type_size(var1: oqpy.base.Var, var2: oqpy.base.Var) -> None: + """Raise error when the size or type of the two variables do not match. + + Args: + var1 (oqpy.base.Var): Variable to validate. + var2 (oqpy.base.Var): Variable to validate. + """ + var1_size = var1.size or 1 + var2_size = var2.size or 1 + + if type(var1) != type(var2): + raise ValueError("Variables in assignment statements must have the same type") + if var1_size != var2_size: + raise ValueError("Variables in assignment statements must have the same size") diff --git a/src/braket/experimental/autoqasm/operators/conditional_expressions.py b/src/braket/experimental/autoqasm/operators/conditional_expressions.py new file mode 100644 index 00000000..a38fa7ec --- /dev/null +++ b/src/braket/experimental/autoqasm/operators/conditional_expressions.py @@ -0,0 +1,63 @@ +# 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. + + +"""Operators for conditional expressions (e.g. the ternary if statement).""" + +from typing import Any, Callable, Optional + +import oqpy.base + +from braket.experimental.autoqasm import program +from braket.experimental.autoqasm.types import is_qasm_type + + +def if_exp( + cond: Any, if_true: Callable[[], Any], if_false: Callable[[], Any], expr_repr: Optional[str] +) -> Any: + """Implements a conditional if expression. + + Args: + cond (Any): The condition of the if clause. + if_true (Callable[[], Any]): The function to run if the condition is true. + if_false (Callable[[], Any]): The function to run if the condition is false. + expr_repr (Optional[str]): The conditional expression represented as a string. + + Returns: + Any: The value returned from the conditional expression. + """ + if is_qasm_type(cond): + return _oqpy_if_exp(cond, if_true, if_false, expr_repr) + else: + return _py_if_exp(cond, if_true, if_false) + + +def _oqpy_if_exp( + cond: Any, + if_true: Callable[[None], Any], + if_false: Callable[[None], Any], + expr_repr: Optional[str], +) -> None: + """Overload of if_exp that stages an oqpy conditional.""" + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + if isinstance(cond, oqpy.base.Var) and cond.name not in oqpy_program.declared_vars.keys(): + cond.name = program.get_program_conversion_context().next_var_name(type(cond)) + oqpy_program.declare(cond) + with oqpy.If(oqpy_program, cond): + if_true() + with oqpy.Else(oqpy_program): + if_false() + + +def _py_if_exp(cond: Any, if_true: Callable[[None], Any], if_false: Callable[[None], Any]) -> Any: + return if_true() if cond else if_false() diff --git a/src/braket/experimental/autoqasm/operators/control_flow.py b/src/braket/experimental/autoqasm/operators/control_flow.py new file mode 100644 index 00000000..e60eee1c --- /dev/null +++ b/src/braket/experimental/autoqasm/operators/control_flow.py @@ -0,0 +1,171 @@ +# 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. + + +"""Operators for control flow constructs (e.g. if, for, while).""" + +from typing import Any, Callable, Iterable, Optional, Union + +import oqpy.base + +from braket.experimental.autoqasm import program +from braket.experimental.autoqasm.types import is_qasm_type + + +def for_stmt( + iter: Union[Iterable, oqpy.Range], + extra_test: Optional[Callable[[], Any]], + body: Callable[[Any], None], + get_state: Any, + set_state: Any, + symbol_names: Any, + opts: dict, +) -> None: + """Implements a for loop. + + Args: + iter (Union[Iterable, Range]): The iterable to be looped over. + extra_test (Optional[Callable[[], Any]]): A function to cause the loop to break if true. + body (Callable[[Any],]): The body of the for loop. + get_state (Any): Unused. + set_state (Any): Unused. + symbol_names (Any): Unused. + opts (dict): Options of the for loop. + """ + del get_state, set_state, symbol_names + if is_qasm_type(iter): + _oqpy_for_stmt(iter, extra_test, body, opts) + else: + _py_for_stmt(iter, extra_test, body) + + +def _oqpy_for_stmt( + iter: oqpy.Range, + extra_test: Callable[[], Any], + body: Callable[[Any], None], + opts: dict, +) -> None: + """Overload of for_stmt that produces an oqpy for loop.""" + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + # TODO: Should check extra_test() on each iteration and break if False, + # but oqpy doesn't currently support break statements at the moment. + with oqpy.ForIn(oqpy_program, iter, opts["iterate_names"]) as f: + body(f) + + +def _py_for_stmt( + iter: Iterable, + extra_test: Callable[[], Any], + body: Callable[[Any], None], +) -> None: + """Overload of for_stmt that executes a Python for loop.""" + if extra_test is not None: + raise NotImplementedError("break and return statements are not supported in for loops.") + else: + for target in iter: + body(target) + + +def while_stmt( + test: Callable[[], Any], + body: Callable[[], None], + get_state: Any, + set_state: Any, + symbol_names: Any, + opts: dict, +) -> None: + """Implements a while loop. + + Args: + test (Callable[[], Any]): The condition of the while loop. + body (Callable[[],]): The body of the while loop. + get_state (Any): Unused. + set_state (Any): Unused. + symbol_names (Any): Unused. + opts (dict): Options of the while loop. + """ + del get_state, set_state, symbol_names, opts + if is_qasm_type(test()): + _oqpy_while_stmt(test, body) + else: + _py_while_stmt(test, body) + + +def _oqpy_while_stmt( + test: Callable[[], Any], + body: Callable[[], None], +) -> None: + """Overload of while_stmt that produces an oqpy while loop.""" + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + with oqpy.While(oqpy_program, test()): + body() + + +def _py_while_stmt( + test: Callable[[], Any], + body: Callable[[], None], +) -> None: + """Overload of while_stmt that executes a Python while loop.""" + while test(): + body() + + +def if_stmt( + cond: Any, + body: Callable[[], Any], + orelse: Callable[[], Any], + get_state: Any, + set_state: Any, + symbol_names: Any, + nouts: int, +) -> None: + """Implements an if/else statement. + + Args: + cond (Any): The condition of the if statement. + body (Callable[[], Any]): The contents of the if block. + orelse (Callable[[], Any]): The contents of the else block. + get_state (Any): Unused. + set_state (Any): Unused. + symbol_names (Any): Unused. + nouts (int): The number of outputs from the if block. + """ + del get_state, set_state, symbol_names, nouts + if is_qasm_type(cond): + _oqpy_if_stmt(cond, body, orelse) + else: + _py_if_stmt(cond, body, orelse) + + +def _oqpy_if_stmt( + cond: Any, + body: Callable[[], Any], + orelse: Callable[[], Any], +) -> None: + """Overload of if_stmt that stages an oqpy cond.""" + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + if isinstance(cond, oqpy.base.Var) and cond.name not in oqpy_program.declared_vars.keys(): + cond.name = program.get_program_conversion_context().next_var_name(type(cond)) + oqpy_program.declare(cond) + with oqpy.If(oqpy_program, cond): + body() + with oqpy.Else(oqpy_program): + orelse() + + +def _py_if_stmt(cond: Any, body: Callable[[], Any], orelse: Callable[[], Any]) -> None: + """Overload of if_stmt that executes a Python if statement.""" + if cond: + body() + else: + orelse() diff --git a/src/braket/experimental/autoqasm/operators/data_structures.py b/src/braket/experimental/autoqasm/operators/data_structures.py new file mode 100644 index 00000000..ce42aea8 --- /dev/null +++ b/src/braket/experimental/autoqasm/operators/data_structures.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. + + +"""Operators for other data structures (e.g. list).""" + +from typing import Iterable, Optional + + +def new_list(iterable: Optional[Iterable] = None) -> list: + """The list constructor + + Args: + iterable (Optional[Iterable]): Optional elements to fill the list with. Defaults to None. + + Returns: + list: A list-like object. The exact return value depends on the initial elements. + """ + return list(iterable) if iterable else [] diff --git a/src/braket/experimental/autoqasm/operators/logical.py b/src/braket/experimental/autoqasm/operators/logical.py new file mode 100644 index 00000000..d16f5d2a --- /dev/null +++ b/src/braket/experimental/autoqasm/operators/logical.py @@ -0,0 +1,52 @@ +# 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. + + +"""Operators for logical boolean operators (e.g. not, and, or).""" + +from typing import Any, Union + +import oqpy.base + +from braket.experimental.autoqasm import program +from braket.experimental.autoqasm.types import is_qasm_type + + +def eq(a: Any, b: Any) -> Union[bool, oqpy.BoolVar]: + """Functional form of "equal". + + Args: + a (Any): First expression to compare. + b (Any): Second expression to compare. + + Returns: + Union[bool, BoolVar]: Whether the expressions are equal. + """ + if is_qasm_type(a) or is_qasm_type(b): + return _oqpy_eq(a, b) + else: + return _py_eq(a, b) + + +def _oqpy_eq(a: Any, b: Any) -> oqpy.BoolVar: + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + is_equal = oqpy.BoolVar( + name=program.get_program_conversion_context().next_var_name(oqpy.BoolVar) + ) + oqpy_program.declare(is_equal) + oqpy_program.set(is_equal, a == b) + return is_equal + + +def _py_eq(a: Any, b: Any) -> bool: + return a == b diff --git a/src/braket/experimental/autoqasm/operators/slices.py b/src/braket/experimental/autoqasm/operators/slices.py new file mode 100644 index 00000000..4602605e --- /dev/null +++ b/src/braket/experimental/autoqasm/operators/slices.py @@ -0,0 +1,104 @@ +# 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. + + +"""Operators for slices.""" + +import collections +from typing import Any + +import oqpy.base + +from braket.experimental.autoqasm import program +from braket.experimental.autoqasm.types import is_qasm_type + + +class GetItemOpts(collections.namedtuple("GetItemOpts", ("element_dtype",))): + pass + + +def get_item(target: Any, i: Any, opts: GetItemOpts) -> Any: + """The slice read operator (i.e. __getitem__). + + Args: + target (Any): An entity that supports getitem semantics. + i (Any): Index to read from. + opts (GetItemOpts): A GetItemOpts object. + + Returns: + Any: The read element. + """ + if is_qasm_type(target) or is_qasm_type(i): + return _oqpy_get_item(target, i, opts) + else: + return _py_get_item(target, i) + + +def _oqpy_get_item(target: Any, i: Any, opts: GetItemOpts) -> Any: + """Overload of get_item that produces an oqpy list read.""" + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + if isinstance(target, oqpy.ArrayVar): + base_type = target.base_type + elif isinstance(target, oqpy.BitVar): + base_type = type(target) + else: + raise TypeError(f"{str(type(target))} object is not subscriptable") + + var = base_type(name=program.get_program_conversion_context().next_var_name(base_type)) + oqpy_program.set(var, target[i]) + return var + + +def _py_get_item(target: Any, i: Any) -> Any: + """Overload of get_item that executes a Python list read.""" + return target[i] + + +def set_item(target: Any, i: Any, x: Any) -> Any: + """The slice write operator (i.e. __setitem__). + + Note: it is unspecified whether target will be mutated or not. In general, + if target is mutable (like Python lists), it will be mutated. + + Args: + target (Any): An entity that supports setitem semantics. + i (Any): Index to modify. + x (Any): The new element value. + + Returns: + Any: Same as target, after the update was performed. + """ + if is_qasm_type(target) or is_qasm_type(i): + return _oqpy_set_item(target, i, x) + else: + return _py_set_item(target, i, x) + + +def _oqpy_set_item(target: Any, i: Any, x: Any) -> Any: + """Overload of set_item that produces an oqpy list modification.""" + if not isinstance(target, oqpy.BitVar): + raise NotImplementedError("Slice assignment is not supported.") + + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + if x.name in oqpy_program.declared_vars.keys(): + value = x + else: + value = x.init_expression + oqpy_program.set(target[i], value) + return target + + +def _py_set_item(target: Any, i: Any, x: Any) -> Any: + """Overload of set_item that executes a Python list modification.""" + target[i] = x + return target diff --git a/src/braket/experimental/autoqasm/program/__init__.py b/src/braket/experimental/autoqasm/program/__init__.py new file mode 100644 index 00000000..5fdc743d --- /dev/null +++ b/src/braket/experimental/autoqasm/program/__init__.py @@ -0,0 +1,27 @@ +# 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. + +"""This module implements the central program data structure and other related structures +for AutoQASM. +""" + +from .pragmas import Verbatim # noqa: F401 +from .program import ( # noqa: F401 + Program, + ProgramConversionContext, + ProgramOptions, + UserConfig, + build_program, + get_program_conversion_context, + in_active_program_conversion_context, +) diff --git a/src/braket/experimental/autoqasm/program/pragmas.py b/src/braket/experimental/autoqasm/program/pragmas.py new file mode 100644 index 00000000..3b04ad55 --- /dev/null +++ b/src/braket/experimental/autoqasm/program/pragmas.py @@ -0,0 +1,52 @@ +# 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. + +"""AutoQASM supported pragmas. + +Pragmas specify how a program should be compiled or executed. In AutoQASM, we support them via +`with` statements. For example: + +.. code-block:: python + + @aq.function + def pragma_example() -> None: + with aq.Verbatim(): + h(0) + cnot(0, 1) + x(0) + +The verbatim pragma would then apply to the `h` and `cnot`, but not the `x`. +""" + + +import oqpy.base + +from braket.experimental.autoqasm import program + + +class Verbatim: + """Context management protocol that, when used with a `with` statement, wraps the code block + in a verbatim box. + + The verbatim pragma around a code block specifies that operations are to be executed as + programmed without compilation or modification of any sort. + """ + + def __enter__(self): + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + self.box = oqpy.Box(oqpy_program) + oqpy_program.pragma("braket verbatim") + self.box.__enter__() + + def __exit__(self, exc_type, exc, traceback): + return self.box.__exit__(exc_type, exc, traceback) diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py new file mode 100644 index 00000000..eee6bcfe --- /dev/null +++ b/src/braket/experimental/autoqasm/program/program.py @@ -0,0 +1,246 @@ +# 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. + +"""AutoQASM Program class, context managers, and related functions.""" + +import enum +import threading +from dataclasses import dataclass +from typing import List, Optional + +import oqpy.base + +from braket.circuits.serialization import IRType +from braket.experimental.autoqasm import constants + +# Prepare to initialize the global program conversion context. +_local = threading.local() +setattr(_local, "program_conversion_context", None) + + +class ProgramOptions(enum.Enum): + """All options configurable by the user at program invocation time via keyword arguments + injected by the aq.function decorator. + """ + + # Note: this is the exact kwarg name that the user must pass, + # which is later input to UserConfig + NUM_QUBITS = "num_qubits" + + +@dataclass +class UserConfig: + """User-specified configurations that influence program building.""" + + num_qubits: Optional[int] = None + + +class Program: + """The program that has been generated with AutoQASM. This object can + be passed to the run() method of a Braket Device.""" + + def __init__(self, oqpy_program: oqpy.Program): + """Initializes an AutoQASM Program object. + + Args: + oqpy_program (oqpy.Program): The oqpy program object which + contains the generated program. + """ + self._oqpy_program = oqpy_program + + 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: If the supplied `ir_type` is not supported. + + Returns: + str: A representation of the program in the `ir_type` format. + """ + if ir_type == IRType.OPENQASM: + return self._oqpy_program.to_qasm() + + raise ValueError(f"Supplied ir_type {ir_type} is not supported.") + + +class ProgramConversionContext: + """The data structure used while converting a program. Intended for internal use.""" + + def __init__(self, user_config: Optional[UserConfig] = None): + self.oqpy_program_stack = [oqpy.Program()] + self.subroutines_processing = set() # the set of subroutines queued for processing + self.user_config = user_config or UserConfig() + self._qubits_seen = set() + self._var_idx = 0 + + def make_program(self) -> Program: + """Makes a Program object using the oqpy program from this conversion context. + + Returns: + Program: The program object. + """ + return Program(self.get_oqpy_program()) + + @property + def qubits(self) -> List[int]: + """Return a sorted list of virtual qubits used in this program. + + Returns: + List[int]: The list of virtual qubits, e.g. [0, 1, 2] + """ + # Can be memoized or otherwise made more performant + return sorted(list(self._qubits_seen)) + + def register_qubit(self, qubit: int) -> None: + """Register a virtual qubit to use in this program.""" + self._qubits_seen.add(qubit) + + def get_declared_qubits(self) -> Optional[int]: + """Return the number of qubits to declare in the program, as specified by the user. + Returns None if the user did not specify how many qubits are in the program. + """ + return self.user_config.num_qubits + + def next_var_name(self, kind: type) -> str: + """Return the next name for a new classical variable. + + For example, a declared bit will be named __bit_0__ and the next integer + will be named __int_1__. + + Args: + kind (type): The type of the new variable. + + Returns: + str: The name for the variable. + """ + next = self._var_idx + self._var_idx += 1 + if kind == oqpy.ArrayVar: + return constants.ARRAY_NAME_TEMPLATE.format(next) + elif kind == oqpy.BitVar: + return constants.BIT_NAME_TEMPLATE.format(next) + elif kind == oqpy.BoolVar: + return constants.BOOL_NAME_TEMPLATE.format(next) + elif kind == oqpy.FloatVar: + return constants.FLOAT_NAME_TEMPLATE.format(next) + elif kind == oqpy.IntVar: + return constants.INT_NAME_TEMPLATE.format(next) + + raise NotImplementedError(f"Program's do not yet support type {kind}.") + + def get_oqpy_program(self) -> oqpy.Program: + """Gets the oqpy program from the top of the stack. + + Returns: + oqpy.Program: The current oqpy program. + """ + return self.oqpy_program_stack[-1] + + class OqpyProgramContextManager: + """Context manager responsible for managing the oqpy programs which are used + by the ProgramConversionContext.""" + + def __init__(self, oqpy_program: oqpy.Program, oqpy_program_stack: List[oqpy.Program]): + self.oqpy_program = oqpy_program + self.oqpy_program_stack = oqpy_program_stack + + def __enter__(self): + self.oqpy_program_stack.append(self.oqpy_program) + + def __exit__(self, exc_type, exc_value, exc_tb): + self.oqpy_program_stack.pop() + + def push_oqpy_program(self, oqpy_program: oqpy.Program) -> OqpyProgramContextManager: + """Pushes the provided oqpy program onto the stack. + + Args: + oqpy_program (Program): The oqpy program to push onto the stack. + + Returns: + OqpyProgramContextManager: A context manager which will pop the provided + oqpy program from the stack when exited. + """ + return self.OqpyProgramContextManager(oqpy_program, self.oqpy_program_stack) + + +class ProgramContextManager: + """Context responsible for managing the ProgramConversionContext.""" + + def __init__(self, user_config): + self.owns_program_conversion_context = False + self.user_config = user_config + + def __enter__(self) -> ProgramConversionContext: + if not _local.program_conversion_context: + _local.program_conversion_context = ProgramConversionContext(self.user_config) + self.owns_program_conversion_context = True + return _local.program_conversion_context + + def __exit__(self, exc_type, exc_value, exc_tb): + if self.owns_program_conversion_context: + _local.program_conversion_context = None + + +def build_program(user_config: Optional[UserConfig] = None) -> ProgramContextManager: + """Creates a context manager which ensures there is a valid thread-local + ProgramConversionContext object. If this context manager created the + ProgramConversionContext object, it removes it from thread-local storage when + exiting the context manager. + + For example:: + + with build_program() as program_conversion_context: + h(0) + cnot(0, 1) + program = program_conversion_context.make_program() + + Args: + user_config (Optional[UserConfig]): User-supplied program building options. + + Returns: + ProgramContextManager: The context manager which manages + the thread-local Program object. + """ + return ProgramContextManager(user_config) + + +def in_active_program_conversion_context() -> bool: + """Indicates whether a program conversion context exists in the current scope, + that is, whether there is an active ProgramContextManager. + + Returns: + bool: Whether there is a program currently being built. + """ + return _local.program_conversion_context is not None + + +def get_program_conversion_context() -> ProgramConversionContext: + """Gets the current thread-local ProgramConversionContext object. + + Must be called inside an active ProgramContextManager (that is, while building a program) so + that there is a valid thread-local ProgramConversionContext object. + + Returns: + ProgramConversionContext: The thread-local ProgramConversionContext object. + """ + assert ( + _local.program_conversion_context is not None + ), "get_program_conversion_context() must be called inside build_program() block" + return _local.program_conversion_context diff --git a/src/braket/experimental/autoqasm/transpiler/__init__.py b/src/braket/experimental/autoqasm/transpiler/__init__.py new file mode 100644 index 00000000..cdc44dd8 --- /dev/null +++ b/src/braket/experimental/autoqasm/transpiler/__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. + +"""This module implements the AutoQASM transpiler which uses autograph to convert a +decorated Python function to an oqpy program.""" + +from .transpiler import PyToOqpy, converted_call # noqa: F401 diff --git a/src/braket/experimental/autoqasm/transpiler/transpiler.py b/src/braket/experimental/autoqasm/transpiler/transpiler.py new file mode 100644 index 00000000..ad3e5934 --- /dev/null +++ b/src/braket/experimental/autoqasm/transpiler/transpiler.py @@ -0,0 +1,380 @@ +# 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. + +"""The PyToOqpy transpiler. + +TODO @shaffry: this file is mostly copied from the class PyToTF +in the TensorFlow implementation of autograph. Consider refactoring +to reduce duplication if possible. +""" + +import functools +import importlib +import inspect +from typing import Any, Callable, Optional, Tuple, Union + +import gast + +from braket.experimental.autoqasm import operators, program, types +from braket.experimental.autoqasm.autograph.converters import ( + asserts, + break_statements, + call_trees, + conditional_expressions, + continue_statements, + control_flow, + directives, + functions, + lists, + logical_expressions, + return_statements, + slices, + variables, +) +from braket.experimental.autoqasm.autograph.core import ( + ag_ctx, + converter, + function_wrappers, + unsupported_features_checker, +) +from braket.experimental.autoqasm.autograph.impl.api_core import ( + StackTraceMapper, + _attach_error_metadata, + _log_callargs, + is_autograph_artifact, + is_autograph_strict_conversion_mode, +) +from braket.experimental.autoqasm.autograph.logging import ag_logging as logging +from braket.experimental.autoqasm.autograph.pyct import anno, cfg, errors, qual_names, transpiler +from braket.experimental.autoqasm.autograph.pyct.static_analysis import ( + activity, + reaching_definitions, +) +from braket.experimental.autoqasm.autograph.tf_utils import tf_stack +from braket.experimental.autoqasm.converters import assignments + + +class PyToOqpy(transpiler.PyToPy): + """The AutoQASM transpiler which converts a Python function into an oqpy program.""" + + def __init__(self): + super(PyToOqpy, self).__init__() + self._extra_locals = None + + def get_transformed_name(self, node: Union[gast.Lambda, gast.FunctionDef]) -> str: + return "oq__" + super(PyToOqpy, self).get_transformed_name(node) + + def get_extra_locals(self) -> dict: + """Returns extra static local variables to be made to transformed code. + + Returns: + dict: Additional variables to make available to the transformed code. + """ + if self._extra_locals is None: + module_spec = importlib.machinery.ModuleSpec("autograph", None) + ag_internal = importlib.util.module_from_spec(module_spec) + ag_internal.__dict__.update(inspect.getmodule(PyToOqpy).__dict__) + ag_internal.ConversionOptions = converter.ConversionOptions + ag_internal.STD = converter.STANDARD_OPTIONS + ag_internal.Feature = converter.Feature + ag_internal.program = program + ag_internal.FunctionScope = function_wrappers.FunctionScope + ag_internal.with_function_scope = function_wrappers.with_function_scope + # We don't want to create a submodule because we want the operators to be + # accessible as ag__. + ag_internal.__dict__.update(operators.__dict__) + + self._extra_locals = {"ag__": ag_internal} + return self._extra_locals + + def get_caching_key(self, ctx: ag_ctx.ControlStatusCtx) -> converter.ConversionOptions: + return ctx.options + + def _initial_analysis( + self, node: Union[gast.Lambda, gast.FunctionDef], ctx: ag_ctx.ControlStatusCtx + ) -> Union[gast.Lambda, gast.FunctionDef]: + graphs = cfg.build(node) + node = qual_names.resolve(node) + node = activity.resolve(node, ctx, None) + node = reaching_definitions.resolve(node, ctx, graphs) + anno.dup( + node, + { + anno.Static.DEFINITIONS: anno.Static.ORIG_DEFINITIONS, + }, + ) + return node + + def transform_ast( + self, node: Union[gast.Lambda, gast.FunctionDef], ctx: ag_ctx.ControlStatusCtx + ) -> Union[gast.Lambda, gast.FunctionDef]: + """Performs an actual transformation of a function's AST. + + Args: + node (Union[Lambda, FunctionDef]): One or more ast.AST nodes + representing the AST to be transformed. + ctx (ControlStatusCtx): transformer context. + + Returns: + Union[Lambda, FunctionDef]: The root of the transformed AST. + """ + unsupported_features_checker.verify(node) + node = self._initial_analysis(node, ctx) + + # autograph converters + node = functions.transform(node, ctx) + node = directives.transform(node, ctx) + node = break_statements.transform(node, ctx) + if ctx.user.options.uses(converter.Feature.ASSERT_STATEMENTS): + node = asserts.transform(node, ctx) + # Note: sequencing continue canonicalization before for loop one avoids + # dealing with the extra loop increment operation that the for + # canonicalization creates. + node = continue_statements.transform(node, ctx) + node = return_statements.transform(node, ctx) + node = assignments.transform(node, ctx) + if ctx.user.options.uses(converter.Feature.LISTS): + node = lists.transform(node, ctx) + node = slices.transform(node, ctx) + node = call_trees.transform(node, ctx) + node = control_flow.transform(node, ctx) + node = conditional_expressions.transform(node, ctx) + node = logical_expressions.transform(node, ctx) + node = variables.transform(node, ctx) + + return node + + +def _convert_actual(entity: Callable, program_ctx: Optional[ag_ctx.ControlStatusCtx]) -> Callable: + """Applies AutoGraph to entity.""" + if not hasattr(entity, "__code__"): + raise ValueError( + "Cannot apply autograph to a function that doesn't " + "expose a __code__ object. If this is a @tf.function," + " try passing f.python_function instead." + ) + + transformed, module, source_map = _TRANSPILER.transform(entity, program_ctx) + + assert not hasattr(transformed, "ag_module") + assert not hasattr(transformed, "ag_source_map") + transformed.ag_module = module + transformed.ag_source_map = source_map + return transformed + + +# +# Generated code support +# + + +def converted_call( + f: Callable, + args: tuple, + kwargs: Optional[dict], + caller_fn_scope: Optional[function_wrappers.FunctionScope] = None, + options: Optional[converter.ConversionOptions] = None, +) -> Any: + """Converts a function call inline. + + For internal use only. + + Note: The argument list is optimized for readability of generated code, which + may look like this: + + `ag__.converted_call(f, (arg1, arg2), None, fscope)` + `ag__.converted_call(f, (), dict(arg1=val1, **kwargs), fscope)` + `ag__.converted_call(f, (arg1, arg2) + varargs, dict(**kwargs), lscope)` + + Args: + f (Callable): The function to convert. + args (tuple): the original positional arguments of f. + kwargs (Optional[dict]): the original keyword arguments of f. + caller_fn_scope (Optional[FunctionScope]): the function scope of the converted + function in which this call was originally made. Defaults to None. + options (Optional[ConversionOptions]): conversion options. If not + specified, the value of caller_fn_scope.callopts is used. Either options + or caller_fn_scope must be present. Defaults to None. + + Returns: + Any: the result of executing a possibly-converted `f` with the given arguments. + """ + logging.log(1, "Converted call: %s\n args: %s\n kwargs: %s\n", f, args, kwargs) + + assert options is not None or caller_fn_scope is not None + options = options or caller_fn_scope.callopts + + if ag_ctx.control_status_ctx().status == ag_ctx.Status.DISABLED: + logging.log(2, "Allowlisted: %s: AutoGraph is disabled in context", f) + return _call_unconverted(f, args, kwargs, options, False) + + if is_autograph_artifact(f): + logging.log(2, "Permanently allowed: %s: AutoGraph artifact", f) + return _call_unconverted(f, args, kwargs, options) + + # If this is a partial, unwrap it and redo all the checks. + if isinstance(f, functools.partial): + return _converted_partial(f, args, kwargs, caller_fn_scope, options) + + # internal_convert_user_code is for example turned off when issuing a dynamic + # call conversion from generated code while in nonrecursive mode. In that + # case we evidently don't want to recurse, but we still have to convert + # things like builtins. + if not options.internal_convert_user_code: + return _call_unconverted(f, args, kwargs, options) + + target_entity, effective_args, exc = _inspect_callable(f, args) + if exc: + return _fall_back_unconverted(f, args, kwargs, options, exc) + + if _is_permanently_allowed_code(target_entity): + return _call_unconverted(f, args, kwargs, options) + + converted_f, exc = _try_convert_actual(target_entity, effective_args, kwargs, options) + if exc: + return _fall_back_unconverted(f, args, kwargs, options, exc) + + with StackTraceMapper(converted_f), tf_stack.CurrentModuleFilter(): + try: + effective_kwargs = kwargs or {} + result = converted_f(*effective_args, **effective_kwargs) + except Exception as e: + _attach_error_metadata(e, converted_f) + raise + + return types.wrap_value(result) + + +def _converted_partial( + f: Callable, + args: tuple, + kwargs: Optional[dict], + caller_fn_scope: Optional[function_wrappers.FunctionScope] = None, + options: Optional[converter.ConversionOptions] = None, +) -> Any: + new_kwargs = {} + if f.keywords is not None: + # Use copy to avoid mutating the underlying keywords. + new_kwargs = f.keywords.copy() + if kwargs is not None: + new_kwargs.update(kwargs) + new_args = f.args + args + logging.log(3, "Forwarding call of partial %s with\n%s\n%s\n", f, new_args, new_kwargs) + return converted_call( + f.func, new_args, new_kwargs, caller_fn_scope=caller_fn_scope, options=options + ) + + +def _inspect_callable(f: Callable, args: tuple) -> Tuple[Callable, tuple, Optional[Exception]]: + target_entity = None + effective_args = None + exc = None + try: + if inspect.ismethod(f) or inspect.isfunction(f): + target_entity = f + effective_args = args + + f_self = getattr(f, "__self__", None) + if f_self is not None: + effective_args = (f_self,) + effective_args + + elif hasattr(f, "__class__") and hasattr(f.__class__, "__call__"): + # Callable objects. Dunder methods have special lookup rules, see: + # https://docs.python.org/3/reference/datamodel.html#specialnames + target_entity = f.__class__.__call__ + effective_args = (f,) + args + + else: + target_entity = f + raise NotImplementedError('unknown callable type "%s"' % type(f)) + + except Exception as e: + logging.log(1, "Error transforming entity %s", target_entity, exc_info=True) + if is_autograph_strict_conversion_mode(): + raise + exc = e + + return target_entity, effective_args, exc + + +def _is_permanently_allowed_code(target_entity: Callable) -> bool: + if not hasattr(target_entity, "__code__"): + logging.log(2, "Permanently allowed: %s: native binding", target_entity) + return True + elif ( + hasattr(target_entity.__code__, "co_filename") + and target_entity.__code__.co_filename == "" + ): + logging.log(2, "Permanently allowed: %s: dynamic code (exec?)", target_entity) + return True + return False + + +def _try_convert_actual( + target_entity: Callable, + effective_args: tuple, + kwargs: dict, + options: converter.ConversionOptions, +) -> Tuple[Callable, Optional[Exception]]: + converted_f = None + exc = None + try: + program_ctx = converter.ProgramContext(options=options) + converted_f = _convert_actual(target_entity, program_ctx) + if logging.has_verbosity(2): + _log_callargs(converted_f, effective_args, kwargs) + except Exception as e: + logging.log(1, "Error transforming entity %s", target_entity, exc_info=True) + if is_autograph_strict_conversion_mode(): + raise + exc = e + return converted_f, exc + + +def _fall_back_unconverted( + f: Callable, args: tuple, kwargs: dict, options: converter.ConversionOptions, exc: Exception +) -> Any: + """Falls back to calling the function unconverted, in case of error.""" + warning_template = ( + "AutoGraph could not transform %s and will run it as-is.\n" "%s" "Cause: %s\n" + ) + if isinstance(exc, errors.InaccessibleSourceCodeError): + if ag_ctx.INSPECT_SOURCE_SUPPORTED: + logging.warning(warning_template, f, "", exc) + elif isinstance(exc, errors.UnsupportedLanguageElementError): + logging.warning(warning_template, f, "", exc) + else: + file_bug_message = ( + "Please report this in the AutoQASM repo. When filing the bug, set" + " the verbosity to 10 (on Linux, `export AUTOGRAPH_VERBOSITY=10`) and" + " attach the full output.\n" + ) + logging.warning(warning_template, f, file_bug_message, exc) + + return _call_unconverted(f, args, kwargs, options) + + +def _call_unconverted( + f: Callable, + args: tuple, + kwargs: dict, + options: converter.ConversionOptions, + update_cache: bool = True, +) -> Any: + """Calls the original function without converting with AutoGraph.""" + if kwargs is not None: + return f(*args, **kwargs) + return f(*args) + + +_TRANSPILER = PyToOqpy() diff --git a/src/braket/experimental/autoqasm/types/__init__.py b/src/braket/experimental/autoqasm/types/__init__.py new file mode 100644 index 00000000..70b0c61b --- /dev/null +++ b/src/braket/experimental/autoqasm/types/__init__.py @@ -0,0 +1,19 @@ +# 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. + +"""This module implements types used by AutoQASM programs, as well as utilities and converters +for type handling. +""" + +from .py_to_oqpy import map_type, wrap_value # noqa: F401 +from .types import is_qasm_type, qasm_range # noqa: F401 diff --git a/src/braket/experimental/autoqasm/types/py_to_oqpy.py b/src/braket/experimental/autoqasm/types/py_to_oqpy.py new file mode 100644 index 00000000..0d3a2738 --- /dev/null +++ b/src/braket/experimental/autoqasm/types/py_to_oqpy.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. + +"""Type conversions between Python and the internal oqpy representation for types.""" + +import ast as python_ast +import typing +from collections.abc import Iterable +from functools import singledispatch +from typing import Any, Union + +import numpy as np +import openqasm3.ast as qasm_ast +import oqpy + +from braket.experimental.autoqasm import program + + +def map_type(python_type: type) -> type: + """Maps a given Python type to the corresponding oqpy type. + + Args: + python_type (type): The Python type to be mapped. + + Returns: + type: The corresponding oqpy type. + """ + origin_type = typing.get_origin(python_type) or python_type + type_args = typing.get_args(python_type) + + if issubclass(origin_type, bool): + return oqpy.BoolVar + if issubclass(origin_type, (int, np.integer)): + return oqpy.IntVar + if issubclass(origin_type, (float, np.floating)): + return oqpy.FloatVar + if issubclass(origin_type, list) and issubclass(type_args[0], (int, np.integer)): + return oqpy.ArrayVar[oqpy.IntVar, 10] # TODO don't use fixed length 10 + + # TODO add all supported types + return python_type + + +@singledispatch +def wrap_value(node: Any, **kwargs: dict) -> Any: + """Wraps an object in an oqpy variable. + + Args: + node (Any): The object to be wrapped. + + Raises: + NotImplementedError: If logic for wrapping the given object + type does not exist. + + Returns: + Any: The oqpy variable wrapping the given object. + """ + if node is None: + return None + + # TODO add any missing cases + raise NotImplementedError(node) + + +@wrap_value.register(bool) +def _(node: bool, **kwargs: dict): + return oqpy.BoolVar( + node, name=program.get_program_conversion_context().next_var_name(oqpy.BoolVar) + ) + + +@wrap_value.register(int) +@wrap_value.register(np.integer) +def _(node: Union[int, np.integer], **kwargs: dict): + return oqpy.IntVar( + node, name=program.get_program_conversion_context().next_var_name(oqpy.IntVar) + ) + + +@wrap_value.register(float) +@wrap_value.register(np.floating) +def _(node: Union[float, np.floating], **kwargs: dict): + return oqpy.FloatVar( + node, name=program.get_program_conversion_context().next_var_name(oqpy.FloatVar) + ) + + +@wrap_value.register +def _(node: Iterable, **kwargs: dict): + # TODO: pass base_type to ArrayVar constructor + return oqpy.ArrayVar( + node, + dimensions=list(np.shape(node)), + name=program.get_program_conversion_context().next_var_name(oqpy.ArrayVar), + ) + + +@wrap_value.register +def _(node: oqpy.base.Var, **kwargs: dict): + return node + + +@wrap_value.register +def _(node: oqpy.base.OQPyExpression, **kwargs: dict): + return node + + +@wrap_value.register +def _(node: python_ast.Constant, **kwargs: dict): + return node.value + + +@wrap_value.register +def _(node: python_ast.Name, **kwargs: dict): + return oqpy.classical_types.Identifier(node.id) + + +@wrap_value.register +def _(node: python_ast.BinOp, **kwargs: dict): + return wrap_value(node.op, left=node.left, right=node.right) + + +@wrap_value.register +def _(node: python_ast.Add, **kwargs: dict): + left = kwargs["left"] + right = kwargs["right"] + return oqpy.base.OQPyBinaryExpression( + getattr(qasm_ast.BinaryOperator, "+"), + wrap_value(left), + wrap_value(right), + ) + + +@wrap_value.register +def _(node: python_ast.Sub, **kwargs: dict): + left = kwargs["left"] + right = kwargs["right"] + return oqpy.base.OQPyBinaryExpression( + getattr(qasm_ast.BinaryOperator, "-"), + wrap_value(left), + wrap_value(right), + ) + + +@wrap_value.register +def _(node: python_ast.Call, **kwargs: dict): + if node.func.id == "range": + arg_values = [wrap_value(arg) for arg in node.args] + return range(*arg_values) + + +@wrap_value.register +def _(node: python_ast.Compare, **kwargs: dict): + if len(node.ops) > 1: + raise NotImplementedError(node) + op = node.ops[0] + op_map = { + python_ast.Eq: "==", + python_ast.NotEq: "!=", + python_ast.Lt: "<", + python_ast.LtE: "<=", + python_ast.Gt: ">", + python_ast.GtE: ">=", + } + return oqpy.base.OQPyBinaryExpression( + getattr(qasm_ast.BinaryOperator, op_map[type(op)]), + wrap_value(node.left), + wrap_value(node.comparators[0]), + ) diff --git a/src/braket/experimental/autoqasm/types/types.py b/src/braket/experimental/autoqasm/types/types.py new file mode 100644 index 00000000..bfbed26c --- /dev/null +++ b/src/braket/experimental/autoqasm/types/types.py @@ -0,0 +1,51 @@ +# 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. + +"""AutoQASM types and type utilities.""" + +from typing import Any, Optional + +import oqpy.base + + +def is_qasm_type(val: Any) -> bool: + """Returns whether the provided object is of a QASM type + which receives special treatment by AutoQASM. + + Args: + val (Any): The object of which to check the type. + + Returns: + bool: Whether the object is a QASM type. + """ + return isinstance( + val, + (oqpy.Range, oqpy._ClassicalVar, oqpy.base.OQPyExpression), + ) + + +def qasm_range(start: int, stop: Optional[int] = None, step: Optional[int] = 1) -> oqpy.Range: + """Range definition. + + Args: + start (int): Start of the range + stop (Optional[int]): End of the range. Defaults to None. + step (Optional[int]): Step of the range. Defaults to 1. + + Returns: + oqpy.Range: oqpy range definition. + """ + if stop is None: + stop = start + start = 0 + return oqpy.Range(start, stop, step) diff --git a/test/unit_tests/braket/experimental/autoqasm/conftest.py b/test/unit_tests/braket/experimental/autoqasm/conftest.py new file mode 100644 index 00000000..8ab1e9d8 --- /dev/null +++ b/test/unit_tests/braket/experimental/autoqasm/conftest.py @@ -0,0 +1,71 @@ +# 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. + +"""Test fixtures shared among the tests.""" + +from typing import Callable + +import pytest + +import braket.experimental.autoqasm as aq +from braket.experimental.autoqasm.gates import cnot, h + + +@pytest.fixture +def empty_program() -> Callable: + """Empty program fixture. + + Returns: + Callable: the aq program. + """ + + @aq.function + def empty_function() -> None: + """A function that does nothing.""" + pass + + return empty_function + + +@pytest.fixture +def bell_state_program() -> Callable: + """Bell state preparation program fixture. + + Returns: + Callable: the aq program. + """ + + @aq.function + def bell_state() -> None: + """A function that generates a two-qubit Bell state.""" + h(0) + cnot(0, 1) + + return bell_state + + +@pytest.fixture +def physical_bell_program() -> Callable: + """Physical bell state preparation program fixture. + + Returns: + Callable: the aq program. + """ + + @aq.function + def physical_bell() -> None: + """A function that generates a two-qubit Bell state on particular qubits.""" + h("$0") + cnot("$0", "$5") + + return physical_bell diff --git a/test/unit_tests/braket/experimental/autoqasm/mock_transpiler.py b/test/unit_tests/braket/experimental/autoqasm/mock_transpiler.py new file mode 100644 index 00000000..75166c9d --- /dev/null +++ b/test/unit_tests/braket/experimental/autoqasm/mock_transpiler.py @@ -0,0 +1,56 @@ +# 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. + +"""Mock transpiler for testing converters.""" + +from typing import List, Union + +import gast + +from braket.experimental.autoqasm.autograph.core import ag_ctx +from braket.experimental.autoqasm.transpiler import PyToOqpy + +# TODO: Implement a converter abstract class for better type hinting. + + +class MockTranspiler(PyToOqpy): + def __init__(self, converters: List): + """A custom transpiler based on `transpiler.PyToOqpy` for unit testing + converters. + Args: + converters (List): List of converters to test. + """ + super(MockTranspiler, self).__init__() + if isinstance(converters, (list, tuple)): + self._converters = converters + else: + self._converters = (converters,) + + def transform_ast( + self, node: Union[gast.Lambda, gast.FunctionDef], ctx: ag_ctx.ControlStatusCtx + ) -> Union[gast.Lambda, gast.FunctionDef]: + """Transform AST from a node using the provided converters. + Args: + node (Union[Lambda, FunctionDef]): One or more ast.AST nodes + representing the AST to be transformed. + ctx (ControlStatusCtx): transformer context. + + Returns: + Union[Lambda, FunctionDef]: The root of the transformed AST. + """ + node = self._initial_analysis(node, ctx) + + for c in self._converters: + node = c.transform(node, ctx) + + return node diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py new file mode 100644 index 00000000..3bafda42 --- /dev/null +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -0,0 +1,798 @@ +# 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. + +"""AutoQASM tests exercising the main API: building programs and correctly +generating OpenQASM, and in turn verifying the OpenQASM by running against +the local simulator. +""" + +import pytest + +import braket.experimental.autoqasm as aq +from braket.default_simulator import StateVectorSimulator +from braket.devices.local_simulator import LocalSimulator +from braket.experimental.autoqasm import errors +from braket.experimental.autoqasm.gates import cnot, h, measure, x +from braket.tasks.local_quantum_task import LocalQuantumTask + + +def _test_on_local_sim(program: aq.Program) -> None: + device = LocalSimulator(backend=StateVectorSimulator()) + task = device.run(program, shots=10, mcm=True) + assert isinstance(task, LocalQuantumTask) + assert isinstance(task.result().measurements, dict) + + +def test_empty_function(empty_program) -> None: + """Test that a function with no instructions generates an empty program.""" + expected = """OPENQASM 3.0;""" + assert empty_program().to_ir() == expected + + +def test_sim_empty(empty_program) -> None: + """Test an empty subroutine on the local simulator.""" + _test_on_local_sim(empty_program()) + + +def test_sim_bell_state(bell_state_program) -> None: + _test_on_local_sim(bell_state_program()) + + +def test_multiple_calls(empty_program, bell_state_program) -> None: + """Tests multiple calls to a single aq.function to ensure that each resulting + Program object has the expected contents. + """ + + def count_function_calls(program: aq.Program, func_name: str) -> int: + return program.to_ir().count(f"{func_name}();") + + @aq.function + def empty_program_wrapper(): + empty_program() + + @aq.function + def bell_state_program_wrapper(): + bell_state_program() + + first_program = empty_program_wrapper() + assert 1 == count_function_calls(first_program, "empty_function") + assert 0 == count_function_calls(first_program, "bell_state") + second_program = empty_program_wrapper() + third_program = bell_state_program_wrapper() + assert 1 == count_function_calls(first_program, "empty_function"), "reverify first program" + assert 0 == count_function_calls(first_program, "bell_state"), "reverify first program" + assert 1 == count_function_calls(second_program, "empty_function") + assert 0 == count_function_calls(second_program, "bell_state") + assert 0 == count_function_calls(third_program, "empty_function") + assert 1 == count_function_calls(third_program, "bell_state") + + +def test_subroutines(empty_program, bell_state_program, physical_bell_program): + """Tests calling several subroutines consecutively.""" + + @aq.function + def call_subroutines() -> None: + empty_program() + bell_state_program() + physical_bell_program() + + expected = """OPENQASM 3.0; +def empty_function() { +} +def bell_state() { + h __qubits__[0]; + cnot __qubits__[0], __qubits__[1]; +} +def physical_bell() { + h $0; + cnot $0, $5; +} +qubit[2] __qubits__; +empty_function(); +bell_state(); +physical_bell();""" + + assert call_subroutines().to_ir() == expected + + _test_on_local_sim(call_subroutines()) + + +@aq.function +def do_h(q: int): + h(q) + + +@aq.function +def recursive_h(q: int): + do_h(q) + if q > 0: + recursive_h(q - 1) + + +@aq.function +def recursive_h_wrapper(q: int): + recursive_h(q) + + +def test_recursive_h_wrapper(): + expected = """OPENQASM 3.0; +def do_h(int[32] q) { + h __qubits__[q]; +} +def recursive_h(int[32] q) { + do_h(q); + if (q > 0) { + recursive_h(q - 1); + } +} +qubit[6] __qubits__; +recursive_h(5);""" + assert recursive_h_wrapper(5, num_qubits=6).to_ir() == expected + + +def test_sim_recursive_h_wrapper(): + _test_on_local_sim(recursive_h_wrapper(5, num_qubits=6)) + + +def test_recursive_h(): + expected = """OPENQASM 3.0; +def do_h(int[32] q) { + h __qubits__[q]; +} +def recursive_h(int[32] q) { + do_h(q); + if (q > 0) { + recursive_h(q - 1); + } +} +qubit[6] __qubits__; +do_h(5); +recursive_h(4);""" + assert recursive_h(5, num_qubits=6).to_ir() == expected + + +def test_sim_recursive_h(): + _test_on_local_sim(recursive_h(5, num_qubits=6)) + + +@aq.function +def bell_state_arbitrary_qubits(q0: int, q1: int) -> None: + h(q0) + cnot(q0, q1) + + +@aq.function +def double_bell_state() -> None: + bell_state_arbitrary_qubits(0, 1) + bell_state_arbitrary_qubits(2, 3) + + +def test_double_bell_state() -> None: + expected = """OPENQASM 3.0; +def bell_state_arbitrary_qubits(int[32] q0, int[32] q1) { + h __qubits__[q0]; + cnot __qubits__[q0], __qubits__[q1]; +} +qubit[4] __qubits__; +bell_state_arbitrary_qubits(0, 1); +bell_state_arbitrary_qubits(2, 3);""" + assert double_bell_state(num_qubits=4).to_ir() == expected + + +def test_sim_double_bell() -> None: + _test_on_local_sim(double_bell_state(num_qubits=4)) + + +@aq.function +def bell_measurement_undeclared() -> None: + """A function that generates and measures a two-qubit Bell state.""" + h(0) + cnot(0, 1) + c = measure([0, 1]) # noqa: F841 + + +def test_bell_measurement_undeclared() -> None: + expected = """OPENQASM 3.0; +qubit[2] __qubits__; +h __qubits__[0]; +cnot __qubits__[0], __qubits__[1]; +bit[2] __bit_0__; +__bit_0__[0] = measure __qubits__[0]; +__bit_0__[1] = measure __qubits__[1]; +bit[2] c; +c = __bit_0__;""" + assert bell_measurement_undeclared().to_ir() == expected + + +@aq.function +def bell_measurement_declared() -> None: + """A function that generates and measures a two-qubit Bell state.""" + c = aq.BitVar([0, 0], size=2) + h(0) + cnot(0, 1) + c = measure([0, 1]) # noqa: F841 + + +def test_bell_measurement_declared() -> None: + expected = """OPENQASM 3.0; +qubit[2] __qubits__; +bit[2] c = {0, 0}; +h __qubits__[0]; +cnot __qubits__[0], __qubits__[1]; +bit[2] __bit_0__; +__bit_0__[0] = measure __qubits__[0]; +__bit_0__[1] = measure __qubits__[1]; +c = __bit_0__;""" + assert bell_measurement_declared().to_ir() == expected + + +@aq.function +def bell_partial_measurement() -> None: + """A function that generates and measures a two-qubit Bell state.""" + h(0) + cnot(0, 1) + c = measure(1) # noqa: F841 + + +def test_bell_partial_measurement() -> None: + expected = """OPENQASM 3.0; +qubit[2] __qubits__; +h __qubits__[0]; +cnot __qubits__[0], __qubits__[1]; +bit __bit_0__; +__bit_0__ = measure __qubits__[1]; +bit c; +c = __bit_0__;""" + assert bell_partial_measurement().to_ir() == expected + + +@aq.function +def bell_measurement_invalid_declared_type() -> None: + """A function that generates and measures a two-qubit Bell state. But stores + reuslt in an variable with invalid type. + """ + c = aq.IntVar(0) + h(0) + cnot(0, 1) + c = measure(1) # noqa: F841 + + +def test_bell_measurement_invalid_declared_type() -> None: + """Test measurement with reuslt stored in an variable with invalid type.""" + expected_error_message = "Variables in assignment statements must have the same type" + with pytest.raises(ValueError) as exc_info: + bell_measurement_invalid_declared_type() + assert expected_error_message in str(exc_info.value) + + +@aq.function +def bell_measurement_invalid_declared_size() -> None: + """A function that generates and measures a two-qubit Bell state. But stores + reuslt in an variable with invalid size. + """ + c = aq.BitVar([0, 0], size=2) + h(0) + cnot(0, 1) + c = measure(1) # noqa: F841 + + +def test_bell_measurement_invalid_declared_size() -> None: + """Test measurement with reuslt stored in an variable with invalid size.""" + expected_error_message = "Variables in assignment statements must have the same size" + with pytest.raises(ValueError) as exc_info: + bell_measurement_invalid_declared_size() + assert expected_error_message in str(exc_info.value) + + +@aq.function +def ghz_qasm_for_loop() -> None: + """A function that generates a GHZ state using a QASM for loop.""" + n_qubits = 5 + h(0) + for i in aq.range(n_qubits - 1): + cnot(i, i + 1) + + +def test_ghz_qasm_for_loop() -> None: + expected = """OPENQASM 3.0; +qubit[5] __qubits__; +h __qubits__[0]; +for int i in [0:3] { + cnot __qubits__[i], __qubits__[i + 1]; +}""" + assert ghz_qasm_for_loop(num_qubits=5).to_ir() == expected + + +def test_sim_ghz_qasm_for_loop() -> None: + _test_on_local_sim(ghz_qasm_for_loop(num_qubits=5)) + + +@aq.function +def ghz_py_for_loop() -> None: + """A function that generates a GHZ state using a Python for loop.""" + n_qubits = 5 + h(0) + for i in range(n_qubits - 1): + cnot(i, i + 1) + + +def test_ghz_py_for_loop() -> None: + expected = """OPENQASM 3.0; +qubit[5] __qubits__; +h __qubits__[0]; +cnot __qubits__[0], __qubits__[1]; +cnot __qubits__[1], __qubits__[2]; +cnot __qubits__[2], __qubits__[3]; +cnot __qubits__[3], __qubits__[4];""" + assert ghz_py_for_loop().to_ir() == expected + + +def test_sim_ghz_py_for_loop() -> None: + _test_on_local_sim(ghz_py_for_loop()) + + +@aq.function +def qasm_simple_condition(do_cnot: bool) -> bool: + """A function that contains a QASM conditional statement. + + Args: + do_cnot (bool): Determines whether to insert a cnot operation. + + Returns: + bool: Whether a cnot operation was performed. + """ + h(0) + if do_cnot: + cnot(0, 1) + return do_cnot + + +@aq.function +def qasm_simple_condition_wrapper(do_cnot: bool): + qasm_simple_condition(do_cnot) + + +@pytest.mark.parametrize("do_cnot", [True, False]) +def test_qasm_simple_condition(do_cnot: bool) -> None: + expected = """OPENQASM 3.0; +def qasm_simple_condition(bool do_cnot) -> bool { + h __qubits__[0]; + if (do_cnot) { + cnot __qubits__[0], __qubits__[1]; + } + return do_cnot; +} +bool __bool_0__ = false; +qubit[2] __qubits__; +""" + expected += f"__bool_0__ = qasm_simple_condition({'true' if do_cnot else 'false'});" + assert qasm_simple_condition_wrapper(do_cnot).to_ir() == expected + + +@pytest.mark.parametrize("do_cnot", [True, False]) +def test_sim_qasm_simple_condition(do_cnot: bool) -> None: + _test_on_local_sim(qasm_simple_condition_wrapper(do_cnot)) + + +@aq.function +def qasm_inline_var_condition() -> aq.BitVar: + """A function that contains a QASM conditional statement with an inline var condition. + + Returns: + aq.BitVar: Measurement result. + """ + h(0) + if aq.BitVar(1): + cnot(0, 1) + x(0) if aq.IntVar(1) else x(1) + return measure(1) + + +def test_qasm_inline_var_condition() -> None: + """Tests the QASM contents of qasm_inline_var_condition.""" + expected = """OPENQASM 3.0; +qubit[2] __qubits__; +h __qubits__[0]; +bit __bit_0__ = 1; +if (__bit_0__) { + cnot __qubits__[0], __qubits__[1]; +} +int[32] __int_1__ = 1; +if (__int_1__) { + x __qubits__[0]; +} else { + x __qubits__[1]; +} +bit __bit_2__; +__bit_2__ = measure __qubits__[1];""" + assert qasm_inline_var_condition().to_ir() == expected + + +def test_sim_qasm_inline_var_condition() -> None: + """Tests the function qasm_inline_var_condition on the local simulator.""" + _test_on_local_sim(qasm_inline_var_condition()) + + +@aq.function +def ground_state_measurements() -> aq.BitVar: + """Measure a few ground state qubits.""" + return measure([5, 2, 1]) + + +def test_measurement_qubit_discovery() -> None: + """Test that qubits measured by integer are automatically discovered for the purpose + of qubit declaration. + """ + assert "qubit[6] __qubits__;" in ground_state_measurements().to_ir() + + +def test_simple_measurement() -> None: + """Test that a program with only measurements is generated correctly.""" + expected = """OPENQASM 3.0; +qubit[6] __qubits__; +bit[3] __bit_0__; +__bit_0__[0] = measure __qubits__[5]; +__bit_0__[1] = measure __qubits__[2]; +__bit_0__[2] = measure __qubits__[1];""" + assert ground_state_measurements().to_ir() == expected + + +def test_simple_measurement_return() -> None: + """Test that a program with only measurements is generated correctly, when + the measurement results are returned from a subroutine. + """ + + @aq.function + def ground_state_measurements_wrapper() -> None: + ground_state_measurements() + + expected = """OPENQASM 3.0; +def ground_state_measurements() -> bit[3] { + bit[3] __bit_0__; + __bit_0__[0] = measure __qubits__[5]; + __bit_0__[1] = measure __qubits__[2]; + __bit_0__[2] = measure __qubits__[1]; + return __bit_0__; +} +""" + # TODO: this should be `bit[3]`, but there's a bug. It's being tracked in an issue. + expected += """bit __bit_1__; +qubit[6] __qubits__; +__bit_1__ = ground_state_measurements();""" + assert ground_state_measurements_wrapper().to_ir() == expected + + +@aq.function +def qasm_measurement_condition() -> aq.BitVar: + """A function that contains a mid-circuit measurement conditional. + + Returns: + BitVar: The result of measuring qubit 1. + """ + h(0) + if measure(0): + cnot(0, 1) + return measure(1) + + +def test_qasm_measurement_condition() -> None: + expected = """OPENQASM 3.0; +qubit[2] __qubits__; +h __qubits__[0]; +bit __bit_0__; +__bit_0__ = measure __qubits__[0]; +if (__bit_0__) { + cnot __qubits__[0], __qubits__[1]; +} +bit __bit_1__; +__bit_1__ = measure __qubits__[1];""" + assert qasm_measurement_condition().to_ir() == expected + + +def test_sim_measurement_condition() -> None: + _test_on_local_sim(qasm_measurement_condition()) + + +def test_virtual_int_qubit_decl(bell_state_program) -> None: + """Tests for ex. h(0) -> qubit[1] __qubits__""" + qasm = bell_state_program().to_ir() + assert "\nqubit[2] __qubits__;" in qasm + + +def test_py_int_qubit_decl() -> None: + """Tests for ex. i = 1; h(i) -> qubit[1] __qubits__""" + qasm = ghz_py_for_loop().to_ir() + assert "\nqubit[5] __qubits__;" in qasm + + +def test_physical_qubit_decl(physical_bell_program) -> None: + """Tests e.g. h("$0"). Note that physical qubits aren't declared.""" + qasm = physical_bell_program().to_ir() + assert "__qubits__" not in qasm + + +def test_explicit_qubit_decl() -> None: + """Tests the qubit declaration functionality.""" + qasm = ghz_py_for_loop(num_qubits=10).to_ir() + assert "\nqubit[10] __qubits__;" in qasm + + +def test_invalid_physical_qubit_fails() -> None: + """Tests invalid physical qubit formatting.""" + + @aq.function + def broken() -> None: + "Uses invalid string for qubit index" + cnot("$0l", "$O1") + + with pytest.raises(ValueError): + broken() + + +def test_invalid_qubit_label_fails() -> None: + """Tests random string fails for qubit label.""" + + @aq.function + def broken() -> None: + "Uses invalid string for qubit index" + h("nope") + + with pytest.raises(ValueError): + broken() + + +def test_float_qubit_index_fails() -> None: + """Tests floats fails for qubit label.""" + + @aq.function + def broken() -> None: + "Uses float for qubit index" + i = 1 + h(i / 2) + + with pytest.raises(TypeError): + broken() + + +def test_bool_qubit_index_fails() -> None: + """Tests that an error is raised for boolean qubit type.""" + + @aq.function + def broken() -> None: + "Uses invalid type for qubit index" + h(True) + + with pytest.raises(ValueError): + broken() + + +def test_invalid_qubit_type_fails() -> None: + """Tests that an error is raised for other unusual qubit types.""" + + @aq.function + def broken() -> None: + "Uses invalid type for qubit index" + h(h) + + with pytest.raises(ValueError): + broken() + + +def test_bit_array_name() -> None: + """Tests that auto declared bits are given a reasonable name.""" + + @aq.function + def my_program() -> aq.BitVar: + """Program which requires generating a bit""" + h(0) + return measure(0) + + @aq.function + def my_program_wrapper() -> None: + my_program() + + expected = """ + bit __bit_0__; + __bit_0__ = measure __qubits__[0]; + return __bit_0__;""" + assert expected in my_program_wrapper().to_ir() + + +@aq.function +def reset() -> None: + "Reset qubit 0." + if measure(0): + x(0) + if measure(0): + x(0) + if measure(0): + x(0) + + +def test_bit_array_name_multi() -> None: + """Tests that auto declared bits are given a reasonable name.""" + expected = """OPENQASM 3.0; +qubit[1] __qubits__; +bit __bit_0__; +__bit_0__ = measure __qubits__[0]; +if (__bit_0__) { + x __qubits__[0]; +} +bit __bit_1__; +__bit_1__ = measure __qubits__[0]; +if (__bit_1__) { + x __qubits__[0]; +} +bit __bit_2__; +__bit_2__ = measure __qubits__[0]; +if (__bit_2__) { + x __qubits__[0]; +}""" + assert reset().to_ir() == expected + + +def test_program_simple_expr() -> None: + """Test that a program with simple expressions for the qubit index raises + an error if the user doesn't specify the number of qubits. + """ + + @aq.function + def simple_range() -> None: + "Uses aq.range iterator for qubit index." + for i in aq.range(5): + h(i) + + with pytest.raises(errors.UnknownQubitCountError): + simple_range() + + +def test_program_with_expr() -> None: + """Test that a program with expressions for the qubit index raises + an error if the user doesn't specify the number of qubits. + """ + + @aq.function + def qubit_expr() -> None: + "Uses aq.range iterator for qubit index." + for i in aq.range(5): + h(i + 3) + + with pytest.raises(errors.UnknownQubitCountError): + qubit_expr() + + +def test_program_with_expr_and_declaration() -> None: + """Test that a program with expressions for the qubit index does not + raise an error if the user explicitly declares the number of qubits. + """ + ghz_qasm_for_loop(num_qubits=8) + + +def test_multi_for_loop() -> None: + """Test that a program with multiple differing for loops is generated + correctly. + """ + expected = """OPENQASM 3.0; +qubit[6] __qubits__; +for int i in [0:2] { + cnot __qubits__[i], __qubits__[i + 1]; +} +for int i in [0:4] { + h __qubits__[i]; +}""" + + @aq.function + def prog(): + for i in aq.range(3): + cnot(i, i + 1) + + for i in aq.range(5): + h(i) + + assert prog(num_qubits=6).to_ir() == expected + + +@aq.function +def bell(q0: int, q1: int) -> None: + h(q0) + cnot(q0, q1) + + +@aq.function +def bell_in_for_loop() -> None: + for i in aq.range(3): + bell(0, 1) + + +def test_subroutines_in_for_loop() -> None: + """Test calling a parameterized subroutine from inside a for loop.""" + expected = """OPENQASM 3.0; +def bell(int[32] q0, int[32] q1) { + h __qubits__[q0]; + cnot __qubits__[q0], __qubits__[q1]; +} +qubit[5] __qubits__; +for int i in [0:2] { + bell(0, 1); +}""" + + assert bell_in_for_loop(num_qubits=5).to_ir() == expected + + +def test_classical_variables_types(): + @aq.function + def prog() -> None: + a = aq.BitVar(0) + a = aq.BitVar(1) # noqa: F841 + + i = aq.IntVar(1) + a_array = aq.BitVar(size=2) + a_array[0] = aq.BitVar(0) + a_array[i] = aq.BitVar(1) + + b = aq.IntVar(10) + b = aq.IntVar(15) # noqa: F841 + + c = aq.FloatVar(1.2) + c = aq.FloatVar(3.4) # noqa: F841 + + expected = """OPENQASM 3.0; +bit a = 0; +a = 1; +int[32] i = 1; +bit[2] a_array; +a_array[0] = 0; +a_array[i] = 1; +int[32] b = 10; +b = 15; +float[64] c = 1.2; +c = 3.4;""" + assert prog().to_ir() == expected + + +def test_classical_variables_assignment(): + @aq.function + def prog() -> None: + a = aq.IntVar(1) # undeclared target, undeclared value + a = aq.IntVar(2) # declared target, undeclared value + b = a # undeclared target, declared value # noqa: F841 + a = b # declared target, declared value # noqa: F841 + + expected = """OPENQASM 3.0; +int[32] a = 1; +a = 2; +int[32] b; +b = a; +a = b;""" + assert prog().to_ir() == expected + + +def test_assignment_measurement_results(): + @aq.function + def prog() -> None: + a = measure(0) + b = a # noqa: F841 + + expected = """OPENQASM 3.0; +qubit[1] __qubits__; +bit __bit_0__; +__bit_0__ = measure __qubits__[0]; +bit a; +a = __bit_0__; +bit b; +b = a;""" + assert prog().to_ir() == expected + + +def test_generated_docstring(bell_state_program) -> None: + assert "num_qubits (int)" in bell_state_program.__doc__ diff --git a/test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py b/test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py new file mode 100644 index 00000000..72bfb796 --- /dev/null +++ b/test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py @@ -0,0 +1,52 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +"""Tests for the gates module.""" + +import braket.experimental.autoqasm as aq +from braket.experimental.autoqasm.gates import cnot, h, measure + + +def test_bell_state_prep(bell_state_program) -> None: + """Tests Bell state generation without measurement.""" + expected = """OPENQASM 3.0; +qubit[2] __qubits__; +h __qubits__[0]; +cnot __qubits__[0], __qubits__[1];""" + actual_result = bell_state_program().to_ir() + assert actual_result == expected + + +def test_physical_q_bell_state_prep(physical_bell_program) -> None: + """Tests Bell state generation without measurement on particular qubits.""" + expected = """OPENQASM 3.0; +h $0; +cnot $0, $5;""" + actual_result = physical_bell_program().to_ir() + assert actual_result == expected + + +def test_bell_with_measure() -> None: + """Tests Bell state with measurement result stored in an undeclared variable.""" + with aq.build_program() as program_conversion_context: + h(0) + cnot(0, 1) + measure(0) + expected = """OPENQASM 3.0; +h __qubits__[0]; +cnot __qubits__[0], __qubits__[1]; +bit __bit_0__; +__bit_0__ = measure __qubits__[0];""" + + qasm = program_conversion_context.make_program().to_ir() + assert qasm == expected.strip() diff --git a/test/unit_tests/braket/experimental/autoqasm/test_converters.py b/test/unit_tests/braket/experimental/autoqasm/test_converters.py new file mode 100644 index 00000000..333dc230 --- /dev/null +++ b/test/unit_tests/braket/experimental/autoqasm/test_converters.py @@ -0,0 +1,59 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +"""Tests for the converters module.""" + +import pytest +from mock_transpiler import MockTranspiler + +import braket.experimental.autoqasm as aq +from braket.experimental.autoqasm.autograph.core import ag_ctx, converter +from braket.experimental.autoqasm.converters import assignments + + +@pytest.fixture(autouse=True) +def program_ctx() -> None: + return converter.ProgramContext( + options=converter.ConversionOptions(recursive=True), autograph_module=aq.api + ) + + +def test_assignment(program_ctx: ag_ctx.ControlStatusCtx) -> None: + """Tests the assignment converter. + + Args: + program_ctx (ag_ctx.ControlStatusCtx): Transformer context. + """ + + def fn() -> None: + """user program to test""" + a = aq.IntVar(5) # noqa: F841 + b = aq.FloatVar(1.2) # noqa: F841 + c = 123 # noqa: F841 + d = (0.123, "foo") # noqa: F841 + a = aq.IntVar(1) # noqa: F841 + e = a # noqa: F841 + + with aq.build_program() as program_conversion_context: + mock_transpiler = MockTranspiler(assignments) + transformed, _, _ = mock_transpiler.transform_function(fn, program_ctx) + transformed() + + qasm = program_conversion_context.make_program().to_ir() + expected_qasm = """OPENQASM 3.0; +int[32] a = 5; +float[64] b = 1.2; +a = 1; +int[32] e; +e = a;""" + assert qasm == expected_qasm diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py new file mode 100644 index 00000000..1cc28925 --- /dev/null +++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py @@ -0,0 +1,546 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +"""Tests for the operators module.""" + +from typing import Any, Callable + +import oqpy.base +import pytest + +import braket.experimental.autoqasm as aq +from braket.experimental.autoqasm.gates import cnot, h, measure, x + + +@pytest.fixture +def if_true() -> dict: + return {"body": lambda: h(0), "qasm": "h __qubits__[0];"} + + +@pytest.fixture +def if_false() -> dict: + return {"body": lambda: h(1), "qasm": "h __qubits__[1];"} + + +def test_conditional_expressions_qasm_cond(if_true: dict, if_false: dict) -> None: + """Tests aq.operators.if_exp with a QASM condition. + + Args: + if_true (dict): Fixture for true case. + if_false (dict): Fixture for false case. + """ + with aq.build_program() as program_conversion_context: + qasm_cond = oqpy.BoolVar(True) + aq.operators.if_exp(qasm_cond, if_true["body"], if_false["body"], expr_repr=None) + + qasm = program_conversion_context.make_program().to_ir() + assert if_true["qasm"] in qasm + assert if_false["qasm"] in qasm + + +def test_conditional_expressions_qasm_measurement(if_true: dict, if_false: dict) -> None: + """Tests operators.if_exp with a QASM measurement condition. + + Args: + if_true (dict): Fixture for true case. + if_false (dict): Fixture for false case. + """ + with aq.build_program() as program_conversion_context: + qasm_cond = measure(0) + aq.operators.if_exp(qasm_cond, if_true["body"], if_false["body"], expr_repr=None) + + qasm = program_conversion_context.make_program().to_ir() + assert if_true["qasm"] in qasm + assert if_false["qasm"] in qasm + + +def test_conditional_expressions_py_cond(if_true: dict, if_false: dict) -> None: + """Tests aq.operators.if_exp with a Python condition. + + Args: + if_true (dict): Fixture for true case. + if_false (dict): Fixture for false case. + """ + with aq.build_program() as program_conversion_context: + py_cond = True + aq.operators.if_exp(py_cond, if_true["body"], if_false["body"], expr_repr=None) + + qasm = program_conversion_context.make_program().to_ir() + assert str(py_cond) not in qasm + assert if_true["qasm"] in qasm + assert if_false["qasm"] not in qasm + + +def for_body(i: aq.QubitIdentifier) -> None: + h(i) + + +def test_control_flow_for_loop_qasm() -> None: + """Tests aq.operators.for_stmt with a QASM iterable.""" + with aq.build_program(aq.program.UserConfig(num_qubits=10)) as program_conversion_context: + aq.operators.for_stmt( + iter=aq.types.qasm_range(3), + extra_test=None, + body=for_body, + get_state=None, + set_state=None, + symbol_names=None, + opts={"iterate_names": "idx"}, + ) + + qasm = program_conversion_context.make_program().to_ir() + expected_qasm = """OPENQASM 3.0; +for int idx in [0:2] { + h __qubits__[idx]; +}""" + + assert qasm == expected_qasm + + +def test_control_flow_for_loop_py() -> None: + """Tests aq.operators.for_stmt with a Python iterable.""" + with aq.build_program() as program_conversion_context: + aq.operators.for_stmt( + iter=range(3), + extra_test=None, + body=for_body, + get_state=None, + set_state=None, + symbol_names=None, + opts={"iterate_names": "idx"}, + ) + + qasm = program_conversion_context.make_program().to_ir() + assert "for" not in qasm + for i in range(3): + assert f"h __qubits__[{i}];" in qasm + + +def while_test(value: bool) -> Callable[[], bool]: + return lambda: value + + +def while_body() -> None: + cnot(0, 1) + + +def test_control_flow_while_loop_qasm() -> None: + """Tests aq.operators.while_stmt with a QASM condition.""" + with aq.build_program() as program_conversion_context: + qasm_cond = oqpy.BoolVar(False) + aq.operators.while_stmt( + test=while_test(qasm_cond), + body=while_body, + get_state=None, + set_state=None, + symbol_names=None, + opts=None, + ) + + qasm = program_conversion_context.make_program().to_ir() + assert "while" in qasm + + +def test_control_flow_while_loop_py() -> None: + """Tests aq.operators.while_stmt with a Python condition.""" + with aq.build_program() as program_conversion_context: + py_cond = False + aq.operators.while_stmt( + test=while_test(py_cond), + body=while_body, + get_state=None, + set_state=None, + symbol_names=None, + opts=None, + ) + + qasm = program_conversion_context.make_program().to_ir() + assert str(py_cond) not in qasm + assert "while" not in qasm + + +def test_control_flow_if_qasm(if_true: dict, if_false: dict) -> None: + """Tests aq.operators.if_stmt with a QASM condition. + + Args: + if_true (dict): Fixture for true case. + if_false (dict): Fixture for false case. + """ + with aq.build_program() as program_conversion_context: + qasm_cond = oqpy.BoolVar(True) + aq.operators.if_stmt( + qasm_cond, + if_true["body"], + if_false["body"], + get_state=None, + set_state=None, + symbol_names=None, + nouts=None, + ) + + qasm = program_conversion_context.make_program().to_ir() + assert if_true["qasm"] in qasm + assert if_false["qasm"] in qasm + + +def test_control_flow_if_qasm_measurement(if_true: dict, if_false: dict) -> None: + """Tests operators.if_stmt with a QASM measurement condition. + + Args: + if_true (dict): Fixture for true case. + if_false (dict): Fixture for false case. + """ + with aq.build_program() as program_conversion_context: + qasm_cond = measure(0) + aq.operators.if_stmt( + qasm_cond, + if_true["body"], + if_false["body"], + get_state=None, + set_state=None, + symbol_names=None, + nouts=None, + ) + + qasm = program_conversion_context.make_program().to_ir() + assert if_true["qasm"] in qasm + assert if_false["qasm"] in qasm + + +def test_control_flow_if_py(if_true: dict, if_false: dict) -> None: + """Tests aq.operators.if_stmt with a Python condition. + + Args: + if_true (dict): Fixture for true case. + if_false (dict): Fixture for false case. + """ + with aq.build_program() as program_conversion_context: + py_cond = True + aq.operators.if_stmt( + py_cond, + if_true["body"], + if_false["body"], + get_state=None, + set_state=None, + symbol_names=None, + nouts=None, + ) + + qasm = program_conversion_context.make_program().to_ir() + assert str(py_cond) not in qasm + assert if_true["qasm"] in qasm + assert if_false["qasm"] not in qasm + + +def test_data_structures_new_list() -> None: + """Tests aq.operators.new_list.""" + assert isinstance(aq.operators.new_list([2, 3, 4]), list) + assert isinstance(aq.operators.new_list(range(2)), list) + assert isinstance(aq.operators.new_list(None), list) + + +def test_logical_eq_py_cond() -> None: + """Tests aq.operators.eq for Python expressions.""" + with aq.build_program() as program_conversion_context: + a = 2 + b = 2 + aq.operators.eq(a, b) + + qasm = program_conversion_context.make_program().to_ir() + assert "==" not in qasm + + +def test_logical_eq_qasm_cond() -> None: + """Tests aq.operators.eq for QASM expressions.""" + with aq.build_program() as program_conversion_context: + a = oqpy.IntVar(2) + b = 2 + aq.operators.eq(a, b) + + qasm = program_conversion_context.make_program().to_ir() + assert "==" in qasm + + +@pytest.mark.parametrize( + "target", [oqpy.ArrayVar(dimensions=[3], name="arr"), oqpy.BitVar(size=3, name="arr")] +) +def test_slices_get_item_qasm(target) -> None: + """Tests aq.operators.get_item with QASM target array.""" + with aq.build_program() as program_conversion_context: + i = 1 + var = aq.operators.get_item(target=target, i=i, opts=aq.operators.GetItemOpts(int)) + assert isinstance(var, oqpy._ClassicalVar) + + qasm = program_conversion_context.make_program().to_ir() + assert "arr[1]" in qasm + + +@pytest.mark.parametrize("target", [oqpy.IntVar(size=32), oqpy.FloatVar(size=32)]) +def test_slices_get_item_qasm_invalid_target_type(target) -> None: + """Tests aq.operators.get_item validation on QASM target type.""" + expected_error_message = f"{str(type(target))} object is not subscriptable" + + with aq.build_program(): + i = 1 + with pytest.raises(TypeError) as exc_info: + _ = aq.operators.get_item(target=target, i=i, opts=aq.operators.GetItemOpts(int)) + assert expected_error_message in str(exc_info.value) + + +@pytest.mark.parametrize( + "target", [oqpy.ArrayVar(dimensions=[3], name="arr"), oqpy.BitVar(size=3, name="arr")] +) +def test_slices_get_item_qasm_with_qasm_index(target) -> None: + """Tests aq.operators.get_item with QASM target array.""" + with aq.build_program() as program_conversion_context: + i = oqpy.IntVar(name="index") + var = aq.operators.get_item(target=target, i=i, opts=aq.operators.GetItemOpts(int)) + assert isinstance(var, oqpy._ClassicalVar) + + qasm = program_conversion_context.make_program().to_ir() + assert "arr[index]" in qasm + + +def test_slices_get_item_py() -> None: + """Tests aq.operators.get_item with Python target list.""" + with aq.build_program() as program_conversion_context: + target = [5, 6, 7] + i = 1 + var = aq.operators.get_item(target=target, i=i, opts=aq.operators.GetItemOpts(int)) + assert isinstance(var, int) + + qasm = program_conversion_context.make_program().to_ir() + assert "array" not in qasm + + +def test_slices_set_item_qasm() -> None: + """Tests aq.operators.set_item with QASM target array.""" + with aq.build_program() as program_conversion_context: + i = oqpy.IntVar(1, name="index") + target = oqpy.BitVar(size=2, name="target") + new_value = oqpy.BitVar(1, name="x") + var = aq.operators.set_item(target=target, i=i, x=new_value) + + qasm = program_conversion_context.make_program().to_ir() + assert "target[index] = 1;" in qasm + assert var.__dict__ == target.__dict__ + + +def test_slices_set_item_py() -> None: + """Tests aq.operators.set_item with Python target list.""" + target = [5, 6, 7] + i = 1 + new_value = 0 + var = aq.operators.set_item(target=target, i=i, x=new_value) + assert isinstance(var, list) + assert var[i] == new_value + + +def test_slice_bits() -> None: + """Test that bit var slicing and assignment works when assigning to a + size 1 bit.""" + + @aq.function + def slice(): + a = aq.BitVar(0, size=6) + b = aq.BitVar(1) + a[3] = b + + expected = """OPENQASM 3.0; +bit[6] a = 0; +bit b = 1; +a[3] = b;""" + + assert slice().to_ir() == expected + + +def test_slice_bits_w_measure() -> None: + """Test assignment of one bit in an array to a measurement result.""" + + @aq.function + def measure_to_slice(): + b0 = aq.BitVar(size=10) + c = measure(0) + b0[3] = c + + expected = """OPENQASM 3.0; +qubit[1] __qubits__; +bit[10] b0; +bit __bit_0__; +__bit_0__ = measure __qubits__[0]; +bit c; +c = __bit_0__; +b0[3] = c;""" + + assert measure_to_slice().to_ir() == expected + + +@pytest.mark.parametrize( + "target_name,value,expected_qasm", + [ + ("foo", oqpy.IntVar(5), "\nint[32] foo = 5;"), + ("bar", oqpy.FloatVar(1.2), "\nfloat[64] bar = 1.2;"), + ("baz", oqpy.BitVar(0), "\nbit baz = 0;"), + ], +) +def test_assignment_qasm_undeclared_target( + target_name: str, value: oqpy.base.Var, expected_qasm: str +) -> None: + """Tests operators.assign_stmt with a QASM assignment value that is + undeclared in the program. + + Args: + target_name (str): Name of the assignment target. + value (oqpy.base.Var): Assignment value. + expected_qasm (str): Expected QASM script. + """ + with aq.build_program() as program_conversion_context: + _ = aq.operators.assign_stmt( + target_name=target_name, + value=value, + ) + + qasm = program_conversion_context.make_program().to_ir() + assert expected_qasm in qasm + + +@pytest.mark.parametrize( + "target_name,value,expected_qasm", + [ + ("foo", oqpy.IntVar(5), "\nfoo = 5;"), + ("bar", oqpy.FloatVar(1.2), "\nbar = 1.2;"), + ("baz", oqpy.BitVar(0), "\nbaz = 0;"), + ], +) +def test_assignment_qasm_declared_target( + target_name: str, value: oqpy.base.Var, expected_qasm: str +) -> None: + """Tests operators.assign_stmt with a QASM assignment value that is + declared in the program. + + Args: + target_name (str): Name of the assignment target. + value (oqpy.base.Var): Assignment value. + expected_qasm (str): Expected QASM script. + """ + with aq.build_program() as program_conversion_context: + oqpy_program = program_conversion_context.get_oqpy_program() + var = type(value)(name=target_name) + oqpy_program.declare(var) + + _ = aq.operators.assign_stmt( + target_name=target_name, + value=value, + ) + + qasm = program_conversion_context.make_program().to_ir() + assert expected_qasm in qasm + + +@pytest.mark.parametrize( + "target_name,declared_var,value,expected_error_message", + [ + ( + "a", + oqpy.BitVar(name="a", size=2), + oqpy.BitVar(name="a", size=3), + "Variables in assignment statements must have the same size", + ), + ( + "a", + oqpy.BitVar(name="a"), + oqpy.IntVar(name="a"), + "Variables in assignment statements must have the same type", + ), + ], +) +def test_assignment_qasm_invalid_size_type( + target_name, declared_var, value, expected_error_message +) -> None: + """Tests operators.assign_stmt validation against variable size and type.""" + with aq.build_program() as program_conversion_context: + oqpy_program = program_conversion_context.get_oqpy_program() + oqpy_program.declare(declared_var) + + with pytest.raises(ValueError) as exc_info: + _ = aq.operators.assign_stmt( + target_name=target_name, + value=value, + ) + assert str(exc_info.value) == expected_error_message + + +def test_assignment_measurement() -> None: + """Tests operators.assign_stmt with a Measurement being the + assignment value. + """ + target_name = "foo" + with aq.build_program(): + new_value = aq.operators.assign_stmt( + target_name=target_name, + value=measure(3), + ) + assert isinstance(new_value, oqpy.BitVar) + + +@pytest.mark.parametrize("value", [0, 2.6, True]) +def test_assignment_py(value: Any) -> None: + """Tests operators.assign_stmt with a python assignment value. + + Args: + value (Any): Assignment value. + """ + new_value = aq.operators.assign_stmt( + target_name="foo", + value=value, + ) + assert new_value == value + + +@pytest.mark.parametrize("value", [0, 1]) +def test_py_if_stmt(value: int) -> None: + """Test Python if branches on true and false conditions.""" + + @aq.function + def test_control_flow(a: int): + "Quick if statement test" + if a: + h(0) + else: + x(0) + + expected = """OPENQASM 3.0; +qubit[1] __qubits__; +{} __qubits__[0];""".format( + "h" if value else "x" + ) + assert test_control_flow(value).to_ir() == expected + + +def test_py_while() -> None: + """Test Python while loop.""" + + @aq.function + def test_control_flow(): + "Quick while loop test" + a = 3 + while a >= 2: + a -= 1 + h(0) + + expected = """OPENQASM 3.0; +qubit[1] __qubits__; +h __qubits__[0]; +h __qubits__[0];""" + + assert test_control_flow().to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py b/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py new file mode 100644 index 00000000..a745fefb --- /dev/null +++ b/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py @@ -0,0 +1,38 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +"""Tests for pragmas.""" + +import braket.experimental.autoqasm as aq +from braket.experimental.autoqasm.gates import cnot, h + + +def test_with_verbatim_box() -> None: + """Tests the with statement with verbatim box `Verbatim`.""" + + @aq.function + def program_func() -> None: + """User program to test.""" + h(0) + with aq.Verbatim(): + cnot(1, 2) + + expected = """OPENQASM 3.0; +qubit[3] __qubits__; +h __qubits__[0]; +pragma braket verbatim +box { + cnot __qubits__[1], __qubits__[2]; +}""" + + assert program_func().to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/braket/experimental/autoqasm/test_program.py new file mode 100644 index 00000000..16a552ea --- /dev/null +++ b/test/unit_tests/braket/experimental/autoqasm/test_program.py @@ -0,0 +1,62 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +"""Tests for the program module.""" + +import oqpy.base +import pytest + +import braket.experimental.autoqasm as aq +from braket.circuits.serialization import IRType + + +def test_program_conversion_context() -> None: + """Tests the ProgramConversionContext class.""" + prog = aq.ProgramConversionContext() + initial_oqpy_program = prog.get_oqpy_program() + assert len(prog.oqpy_program_stack) == 1 + + new_oqpy_program = oqpy.Program() + with prog.push_oqpy_program(new_oqpy_program): + assert len(prog.oqpy_program_stack) == 2 + assert prog.get_oqpy_program() == new_oqpy_program + + assert prog.get_oqpy_program() == initial_oqpy_program + assert len(prog.oqpy_program_stack) == 1 + + +def test_build_program() -> None: + """Tests the aq.build_program function.""" + with pytest.raises(AssertionError): + aq.get_program_conversion_context() + + with aq.build_program() as program_conversion_context: + assert aq.get_program_conversion_context() == program_conversion_context + with aq.build_program() as inner_context: + assert inner_context is program_conversion_context + assert aq.get_program_conversion_context() == inner_context + + with pytest.raises(AssertionError): + aq.get_program_conversion_context() + + +def test_to_ir() -> None: + """Tests that an appropriate error is raised for unsupported ir_types.""" + with aq.build_program() as program_conversion_context: + aq.gates.h(0) + prog = program_conversion_context.make_program() + # No error for OpenQASM + prog.to_ir(IRType.OPENQASM) + + with pytest.raises(ValueError): + prog.to_ir(IRType.JAQCD) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py new file mode 100644 index 00000000..30ac09cd --- /dev/null +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -0,0 +1,43 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +"""Tests for the types module.""" + +from typing import Tuple + +import oqpy +import pytest + +from braket.experimental.autoqasm.types.types import qasm_range + + +@pytest.mark.parametrize( + "range_params, expected_range_params", + [ + ((0, 5, 1), (0, 5, 1)), + ((5, None, 2), (0, 5, 2)), + ], +) +def test_qasm_range( + range_params: Tuple[int, int, int], expected_range_params: Tuple[int, int, int] +) -> None: + """Test `qasm_range()` returning correct `Range` object. + + Args: + range_params (Tuple[int, int, int]): Range parameters to instantiate `oqpy.Range` + expected_range_params (Tuple[int, int, int]): Expected range parameters + """ + start, stop, step = range_params + qrange = qasm_range(start, stop, step) + assert isinstance(qrange, oqpy.Range) + assert (qrange.start, qrange.stop, qrange.step) == expected_range_params diff --git a/tox.ini b/tox.ini index af1fd909..4e4f3e33 100644 --- a/tox.ini +++ b/tox.ini @@ -66,7 +66,7 @@ skip_install = true deps = black commands = - black ./ {posargs} + black ./ --extend-exclude=src/braket/experimental/autoqasm/autograph {posargs} [testenv:docs] basepython = python3 @@ -95,4 +95,4 @@ commands = deps = # If you need to test on a certain branch, add @ after .git git+https://github.com/aws/amazon-braket-schemas-python.git - git+https://github.com/aws/amazon-braket-default-simulator-python.git + git+https://github.com/aws/amazon-braket-default-simulator-python.git@d63d367aafc21e2a8cc470c6ab20ca87cbe525aa # mcm-sim branch From d38ccb7a1a587c575c8df419ecfa90bd092a7205 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 5 Jul 2023 20:33:59 +0100 Subject: [PATCH 0721/1165] Exclude autograph from coverage (#594) --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 79545ce2..0024deed 100644 --- a/.coveragerc +++ b/.coveragerc @@ -9,6 +9,7 @@ omit = **/braket/schema_common/* **/braket/task_result/* **/braket/simulator/* + **/autoqasm/autograph/* */site-packages/braket/* [paths] From d994357cc3aae29d4741151237ab8963ac06b031 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Wed, 5 Jul 2023 16:55:27 -0400 Subject: [PATCH 0722/1165] change: [autoqasm] Increase types module coverage to 100% and fix related bugs (#595) --- src/braket/experimental/autoqasm/__init__.py | 4 +- src/braket/experimental/autoqasm/api/api.py | 18 +- .../autoqasm/operators/assignments.py | 5 +- .../experimental/autoqasm/types/py_to_oqpy.py | 98 ++---- .../experimental/autoqasm/types/types.py | 14 +- .../experimental/autoqasm/test_types.py | 278 +++++++++++++++++- 6 files changed, 323 insertions(+), 94 deletions(-) diff --git a/src/braket/experimental/autoqasm/__init__.py b/src/braket/experimental/autoqasm/__init__.py index 5e7d1fc6..47af9827 100644 --- a/src/braket/experimental/autoqasm/__init__.py +++ b/src/braket/experimental/autoqasm/__init__.py @@ -40,9 +40,7 @@ def my_program(): result[1] = measure __qubits__[1]; """ -from oqpy import BitVar # noqa: F401 -from oqpy import FloatVar # noqa: F401 -from oqpy import IntVar # noqa: F401 +from oqpy import ArrayVar, BitVar, BoolVar, FloatVar, IntVar # noqa: F401 from . import api, constants, gates, operators, types # noqa: F401 from .api import function # noqa: F401 diff --git a/src/braket/experimental/autoqasm/api/api.py b/src/braket/experimental/autoqasm/api/api.py index 4916770f..6ec9badd 100644 --- a/src/braket/experimental/autoqasm/api/api.py +++ b/src/braket/experimental/autoqasm/api/api.py @@ -264,7 +264,15 @@ def _convert_program_as_subroutine( # Add the subroutine invocation to the program return_instance = _make_return_instance(f, program_conversion_context) - if return_instance is not None: + if isinstance(return_instance, list): + ret_type = subroutine_function_call.subroutine_decl.return_type + return_variable = oqpy.ArrayVar( + return_instance, + dimensions=[d.value for d in ret_type.dimensions], + name=program_conversion_context.next_var_name(oqpy.ArrayVar), + ) + oqpy_program.set(return_variable, subroutine_function_call) + elif return_instance is not None: return_variable = aq_types.wrap_value(return_instance) oqpy_program.set(return_variable, subroutine_function_call) else: @@ -288,10 +296,14 @@ def _make_return_instance( return_type = annotations["return"] if "return" in annotations else None return_instance = None - if return_type and issubclass(return_type, oqpy.base.Var): + if return_type and aq_types.is_qasm_type(return_type): return_instance = return_type(name=program_conversion_context.next_var_name(return_type)) elif return_type: - return_instance = return_type() + if hasattr(return_type, "__origin__"): + # Types from python's typing module, such as `List`. origin gives us `list`` + return_instance = return_type.__origin__() + else: + return_instance = return_type() return return_instance diff --git a/src/braket/experimental/autoqasm/operators/assignments.py b/src/braket/experimental/autoqasm/operators/assignments.py index 65bad6e2..154355b0 100644 --- a/src/braket/experimental/autoqasm/operators/assignments.py +++ b/src/braket/experimental/autoqasm/operators/assignments.py @@ -14,6 +14,7 @@ """Operators for assignment statements.""" +import copy from typing import Any import oqpy @@ -62,7 +63,9 @@ def assign_stmt(target_name: str, value: Any) -> Any: # Set to `value.init_expression` to avoid declaring an unnecessary variable. oqpy_program.set(target, value.init_expression) else: - target = type(value)(name=target_name, size=value.size) + target = copy.copy(value) + target.init_expression = None + target.name = target_name if is_value_used: oqpy_program.declare(target) diff --git a/src/braket/experimental/autoqasm/types/py_to_oqpy.py b/src/braket/experimental/autoqasm/types/py_to_oqpy.py index 0d3a2738..3c917fc1 100644 --- a/src/braket/experimental/autoqasm/types/py_to_oqpy.py +++ b/src/braket/experimental/autoqasm/types/py_to_oqpy.py @@ -13,14 +13,11 @@ """Type conversions between Python and the internal oqpy representation for types.""" -import ast as python_ast import typing -from collections.abc import Iterable from functools import singledispatch from typing import Any, Union import numpy as np -import openqasm3.ast as qasm_ast import oqpy from braket.experimental.autoqasm import program @@ -45,14 +42,22 @@ def map_type(python_type: type) -> type: if issubclass(origin_type, (float, np.floating)): return oqpy.FloatVar if issubclass(origin_type, list) and issubclass(type_args[0], (int, np.integer)): - return oqpy.ArrayVar[oqpy.IntVar, 10] # TODO don't use fixed length 10 + # TODO: Update array length to match the input rather than hardcoding + # OQPY and QASM require arrays have a set length. python doesn't require this, + # so the length of the array is indeterminate. + # At this point we only have access to the _parameter_ (type hint), not the + # _argument_ (concrete value), which is the only place length information is stored + # Here's where the info is stored for oqpy variables: + # ctx = program.get_program_conversion_context() + # dims = ctx.get_oqpy_program().declared_vars[name_of_var].dimensions + return oqpy.ArrayVar[oqpy.IntVar, 10] # TODO add all supported types return python_type @singledispatch -def wrap_value(node: Any, **kwargs: dict) -> Any: +def wrap_value(node: Any) -> Any: """Wraps an object in an oqpy variable. Args: @@ -73,7 +78,7 @@ def wrap_value(node: Any, **kwargs: dict) -> Any: @wrap_value.register(bool) -def _(node: bool, **kwargs: dict): +def _(node: bool): return oqpy.BoolVar( node, name=program.get_program_conversion_context().next_var_name(oqpy.BoolVar) ) @@ -81,7 +86,7 @@ def _(node: bool, **kwargs: dict): @wrap_value.register(int) @wrap_value.register(np.integer) -def _(node: Union[int, np.integer], **kwargs: dict): +def _(node: Union[int, np.integer]): return oqpy.IntVar( node, name=program.get_program_conversion_context().next_var_name(oqpy.IntVar) ) @@ -89,91 +94,22 @@ def _(node: Union[int, np.integer], **kwargs: dict): @wrap_value.register(float) @wrap_value.register(np.floating) -def _(node: Union[float, np.floating], **kwargs: dict): +def _(node: Union[float, np.floating]): return oqpy.FloatVar( node, name=program.get_program_conversion_context().next_var_name(oqpy.FloatVar) ) @wrap_value.register -def _(node: Iterable, **kwargs: dict): - # TODO: pass base_type to ArrayVar constructor - return oqpy.ArrayVar( - node, - dimensions=list(np.shape(node)), - name=program.get_program_conversion_context().next_var_name(oqpy.ArrayVar), - ) - - -@wrap_value.register -def _(node: oqpy.base.Var, **kwargs: dict): +def _(node: oqpy.base.Var): return node @wrap_value.register -def _(node: oqpy.base.OQPyExpression, **kwargs: dict): +def _(node: oqpy.base.OQPyExpression): return node @wrap_value.register -def _(node: python_ast.Constant, **kwargs: dict): - return node.value - - -@wrap_value.register -def _(node: python_ast.Name, **kwargs: dict): - return oqpy.classical_types.Identifier(node.id) - - -@wrap_value.register -def _(node: python_ast.BinOp, **kwargs: dict): - return wrap_value(node.op, left=node.left, right=node.right) - - -@wrap_value.register -def _(node: python_ast.Add, **kwargs: dict): - left = kwargs["left"] - right = kwargs["right"] - return oqpy.base.OQPyBinaryExpression( - getattr(qasm_ast.BinaryOperator, "+"), - wrap_value(left), - wrap_value(right), - ) - - -@wrap_value.register -def _(node: python_ast.Sub, **kwargs: dict): - left = kwargs["left"] - right = kwargs["right"] - return oqpy.base.OQPyBinaryExpression( - getattr(qasm_ast.BinaryOperator, "-"), - wrap_value(left), - wrap_value(right), - ) - - -@wrap_value.register -def _(node: python_ast.Call, **kwargs: dict): - if node.func.id == "range": - arg_values = [wrap_value(arg) for arg in node.args] - return range(*arg_values) - - -@wrap_value.register -def _(node: python_ast.Compare, **kwargs: dict): - if len(node.ops) > 1: - raise NotImplementedError(node) - op = node.ops[0] - op_map = { - python_ast.Eq: "==", - python_ast.NotEq: "!=", - python_ast.Lt: "<", - python_ast.LtE: "<=", - python_ast.Gt: ">", - python_ast.GtE: ">=", - } - return oqpy.base.OQPyBinaryExpression( - getattr(qasm_ast.BinaryOperator, op_map[type(op)]), - wrap_value(node.left), - wrap_value(node.comparators[0]), - ) +def _(node: program.Program): + return node diff --git a/src/braket/experimental/autoqasm/types/types.py b/src/braket/experimental/autoqasm/types/types.py index bfbed26c..e38a7f50 100644 --- a/src/braket/experimental/autoqasm/types/types.py +++ b/src/braket/experimental/autoqasm/types/types.py @@ -19,7 +19,7 @@ def is_qasm_type(val: Any) -> bool: - """Returns whether the provided object is of a QASM type + """Returns whether the provided object is of a QASM type or is itself a QASM type which receives special treatment by AutoQASM. Args: @@ -28,10 +28,14 @@ def is_qasm_type(val: Any) -> bool: Returns: bool: Whether the object is a QASM type. """ - return isinstance( - val, - (oqpy.Range, oqpy._ClassicalVar, oqpy.base.OQPyExpression), - ) + try: + if issubclass(val, (oqpy.Range, oqpy._ClassicalVar, oqpy.base.OQPyExpression)): + return True + except TypeError: + # `val` is not a class + pass + + return isinstance(val, (oqpy.Range, oqpy._ClassicalVar, oqpy.base.OQPyExpression)) def qasm_range(start: int, stop: Optional[int] = None, step: Optional[int] = 1) -> oqpy.Range: diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index 30ac09cd..c2463a6c 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -13,11 +13,12 @@ """Tests for the types module.""" -from typing import Tuple +from typing import List, Tuple import oqpy import pytest +import braket.experimental.autoqasm as aq from braket.experimental.autoqasm.types.types import qasm_range @@ -41,3 +42,278 @@ def test_qasm_range( qrange = qasm_range(start, stop, step) assert isinstance(qrange, oqpy.Range) assert (qrange.start, qrange.stop, qrange.step) == expected_range_params + + +def test_return_bit(): + """Test type discovery of bit return values.""" + + @aq.function + def ret_test() -> aq.BitVar: + # TODO: These should work even in one line + res = aq.BitVar(1) + return res + + @aq.function + def main() -> aq.BitVar: + return ret_test() + + expected = """OPENQASM 3.0; +def ret_test() -> bit { + bit res = 1; + return res; +} +bit __bit_0__; +__bit_0__ = ret_test();""" + + assert main().to_ir() == expected + + +def test_return_int(): + """Test type discovery of int return values.""" + + @aq.function + def ret_test() -> int: + res = aq.IntVar(1) + return res + + @aq.function + def main() -> int: + return ret_test() + + expected = """OPENQASM 3.0; +def ret_test() -> int[32] { + int[32] res = 1; + return res; +} +int[32] __int_0__ = 0; +__int_0__ = ret_test();""" + + assert main().to_ir() == expected + + +def test_return_float(): + """Test type discovery of float return values.""" + + @aq.function + def ret_test() -> float: + res = aq.FloatVar(1.0) + return res + + @aq.function + def main() -> float: + return ret_test() + + expected = """OPENQASM 3.0; +def ret_test() -> float[64] { + float[64] res = 1.0; + return res; +} +float[64] __float_0__ = 0.0; +__float_0__ = ret_test();""" + + assert main().to_ir() == expected + + +def test_return_bool(): + """Test type discovery of boolean return values.""" + + @aq.function + def ret_test() -> bool: + res = aq.BoolVar(True) + return res + + @aq.function + def main() -> bool: + return ret_test() + + expected = """OPENQASM 3.0; +def ret_test() -> bool { + bool res = true; + return res; +} +bool __bool_0__ = false; +__bool_0__ = ret_test();""" + + assert main().to_ir() == expected + + +def test_return_bin_expr(): + """Test type discovery of int return values from an expression.""" + + @aq.function + def add(a: int, b: int) -> int: + return a + b + + @aq.function + def ret_test() -> int: + a = aq.IntVar(5) + b = aq.IntVar(6) + return add(a, b) + + expected = """OPENQASM 3.0; +def add(int[32] a, int[32] b) -> int[32] { + return a + b; +} +int[32] __int_0__ = 0; +int[32] a = 5; +int[32] b = 6; +__int_0__ = add(a, b);""" + + assert ret_test().to_ir() == expected + + +def test_return_none(): + """Test discovery of None return annotation.""" + + @aq.function + def ret_test() -> None: + return None + + ret_test().to_ir() + + +def test_return_array(): + """Test return type discovery of array values.""" + + @aq.function + def ret_test() -> List[int]: + res = aq.ArrayVar([1, 2, 3], dimensions=[3]) + return res + + @aq.function + def main() -> List[int]: + return ret_test() + + expected = """OPENQASM 3.0; +def ret_test() -> array[int[32], 3] { + array[int[32], 3] res = {1, 2, 3}; + return res; +} +array[int[32], 3] __arr_0__ = {}; +__arr_0__ = ret_test();""" + + assert main().to_ir() == expected + + +def test_return_func_call(): + """Test returning the result of another function call.""" + + @aq.function + def helper() -> int: + res = aq.IntVar(1) + return res + + @aq.function + def ret_test() -> int: + return helper() + + expected = """OPENQASM 3.0; +def helper() -> int[32] { + int[32] res = 1; + return res; +} +int[32] __int_0__ = 0; +__int_0__ = helper();""" + + assert ret_test().to_ir() == expected + + +def test_map_bool(): + """Test boolean input parameter type.""" + + @aq.function + def annotation_test(input: bool): + pass + + @aq.function + def main(): + annotation_test(True) + + expected = """OPENQASM 3.0; +def annotation_test(bool input) { +} +annotation_test(true);""" + + assert main().to_ir() == expected + + +def test_map_int(): + """Test integer input parameter type.""" + + @aq.function + def annotation_test(input: int): + pass + + @aq.function + def main(): + annotation_test(1) + + expected = """OPENQASM 3.0; +def annotation_test(int[32] input) { +} +annotation_test(1);""" + + assert main().to_ir() == expected + + +def test_map_float(): + """Test float input parameter type.""" + + @aq.function + def annotation_test(input: float): + pass + + @aq.function + def main(): + annotation_test(1.0) + + expected = """OPENQASM 3.0; +def annotation_test(float[64] input) { +} +annotation_test(1.0);""" + + assert main().to_ir() == expected + + +def test_map_array(): + """Test array input parameter type.""" + + @aq.function + def annotation_test(input: List[int]): + pass + + @aq.function + def main(): + a = aq.ArrayVar([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dimensions=[10]) + annotation_test(a) + + expected = """OPENQASM 3.0; +def annotation_test(array[int[32], 10] input) { +} +array[int[32], 10] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; +annotation_test(a);""" + + assert main().to_ir() == expected + + +def test_map_other(): + """Test unexpected input parameter type handling.""" + + # TODO: Should be able to pass aq.Bit directly to annotation_test + + @aq.function + def annotation_test(input: aq.BitVar): + pass + + @aq.function + def main(): + a = aq.BitVar(1) + annotation_test(a) + + expected = """OPENQASM 3.0; +def annotation_test(bit input) { +} +bit a = 1; +annotation_test(a);""" + + assert main().to_ir() == expected From 6323205b82b39d151c5ece9355c829137c4b9d65 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 5 Jul 2023 14:34:00 -0700 Subject: [PATCH 0723/1165] fix: pass gate modifiers to to_unitary (#596) --- src/braket/circuits/unitary_calculation.py | 14 ++++++-- .../braket/circuits/test_circuit.py | 36 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/braket/circuits/unitary_calculation.py b/src/braket/circuits/unitary_calculation.py index e5b93e11..628d230e 100644 --- a/src/braket/circuits/unitary_calculation.py +++ b/src/braket/circuits/unitary_calculation.py @@ -14,6 +14,7 @@ from typing import Iterable import numpy as np +from scipy.linalg import fractional_matrix_power from braket.circuits.compiler_directive import CompilerDirective from braket.circuits.gate import Gate @@ -124,17 +125,26 @@ def calculate_unitary_big_endian( index_substitutions = {qubits_sorted[i]: i for i in range(qubit_count)} rank = 2**qubit_count # Initialize identity unitary as type (rank, rank) tensor - unitary = np.eye(rank).reshape([2] * 2 * qubit_count) + unitary = np.eye(rank, dtype=complex).reshape([2] * 2 * qubit_count) for instruction in instructions: if isinstance(instruction.operator, CompilerDirective): continue if not isinstance(instruction.operator, Gate): raise TypeError("Only Gate operators are supported to build the unitary") + + base_gate_matrix = instruction.operator.to_matrix() + if int(instruction.power) == instruction.power: + gate_matrix = np.linalg.matrix_power(base_gate_matrix, int(instruction.power)) + else: + gate_matrix = fractional_matrix_power(base_gate_matrix, instruction.power) + unitary = multiply_matrix( unitary, - instruction.operator.to_matrix(), + gate_matrix, tuple(index_substitutions[qubit] for qubit in instruction.target), + controls=instruction.control, + control_state=instruction.control_state, ) return unitary.reshape(rank, rank) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 298eb773..4c536665 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -1491,6 +1491,42 @@ def test_to_unitary_with_compiler_directives_returns_expected_unitary(): dtype=complex, ), ), + ( + Circuit().x(0, control=1), + np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + ], + dtype=complex, + ), + ), + ( + Circuit().x(1, control=0, power=0.5), + np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 0.5 + 0.5j, 0.5 - 0.5j], + [0.0, 0.0, 0.5 - 0.5j, 0.5 + 0.5j], + ], + dtype=complex, + ), + ), + ( + Circuit().x(1, control=0, power=2), + np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + ], + dtype=complex, + ), + ), ( Circuit().ccnot(1, 2, 0), np.array( From 1240799eb1db6237a6527ca277141d116d196570 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Wed, 5 Jul 2023 18:01:46 -0400 Subject: [PATCH 0724/1165] feat: draw barrier/delay with qubits (#592) * draw barrier/delay with qubits * change list to set * make delay act like a barrier * add no-arg delay test --------- Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> --- src/braket/pulse/ast/approximation_parser.py | 39 ++- .../pulse/ast/test_approximation_parser.py | 282 +++++++++++++++++- 2 files changed, 316 insertions(+), 5 deletions(-) diff --git a/src/braket/pulse/ast/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py index 68a36b45..89b318ea 100644 --- a/src/braket/pulse/ast/approximation_parser.py +++ b/src/braket/pulse/ast/approximation_parser.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 from collections import defaultdict from dataclasses import dataclass from typing import Any, Dict, KeysView, List, Optional, Union @@ -56,6 +57,7 @@ def __init__(self, program: Program, frames: Dict[str, Frame]): self.frequencies = defaultdict(TimeSeries) self.phases = defaultdict(TimeSeries) context = _ParseState(variables=dict(), 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) def visit( @@ -73,9 +75,14 @@ def visit( def _get_frame_parameters( self, parameters: List[ast.Expression], context: _ParseState ) -> Union[KeysView, List[str]]: - frame_ids = [] + frame_ids = set() for expression in parameters: - frame_ids.append(self.visit(expression, context)) + identifier_name = self.visit(expression, context) + if match := re.search(r"^\$[0-9]+$", identifier_name): + qubit_number = match.group()[1:] + frame_ids.update(self._qubit_frames_mapping.get(qubit_number, [])) + else: + frame_ids.add(identifier_name) return frame_ids def _delay_frame(self, frame_id: str, to_delay_time: float, context: _ParseState) -> None: @@ -141,9 +148,18 @@ def visit_DelayInstruction(self, node: ast.DelayInstruction, context: _ParseStat """ duration = self.visit(node.duration, context) frames = self._get_frame_parameters(node.qubits, context) + if len(frames) == 0: + # 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]) + # 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) + barrier_time = _ceil_approx(max_time / lcm) * lcm + for frame_id in frames: - frame_data = context.frame_data[frame_id] - self._delay_frame(frame_id, frame_data.current_time + duration, context) + self._delay_frame(frame_id, barrier_time + duration, context) def visit_QuantumBarrier(self, node: ast.QuantumBarrier, context: _ParseState) -> None: """Visit a Quantum Barrier. @@ -164,6 +180,7 @@ def visit_QuantumBarrier(self, node: ast.QuantumBarrier, context: _ParseState) - # after the longest time of all considered frames lcm = _lcm_floats(*dts) barrier_time = _ceil_approx(max_time / lcm) * lcm + for frame_id in frames: self._delay_frame(frame_id, barrier_time, context) @@ -456,6 +473,20 @@ def _init_frame_data(frames: Dict[str, Frame]) -> Dict[str, _FrameState]: return frame_states +def _init_qubit_frame_mapping(frames: Dict[str, Frame]) -> Dict[str, List[str]]: + mapping = {} + for frameId in frames.keys(): + if m := ( + re.search(r"q(\d+)_q(\d+)_[a-z_]+", frameId) or re.search(r"[rq](\d+)_[a-z_]+", frameId) + ): + for qubit in m.groups(): + if qubit in mapping: + mapping[qubit].append(frameId) + else: + mapping[qubit] = [frameId] + return mapping + + def _lcm_floats(*dts: List[float]) -> float: """Return the least common multiple of time increments of a list of frames A time increment is the inverse of the corresponding sample rate which is considered 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 2f83730f..c41a1712 100644 --- a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py +++ b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py @@ -19,6 +19,7 @@ from openpulse import ast from oqpy import IntVar +from braket.circuits.qubit_set import QubitSet from braket.pulse import ArbitraryWaveform, ConstantWaveform, DragGaussianWaveform, GaussianWaveform from braket.pulse.ast.approximation_parser import _ApproximationParser from braket.pulse.frame import Frame @@ -55,6 +56,226 @@ def test_delay(port): verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) +def test_delay_multiple_frames(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) + pulse_seq = ( + PulseSequence() + .play(frame1, ConstantWaveform(12e-9, 0.75)) # Inst1 + .delay([frame1, frame2], 10e-9) # Inst 2 + .play(frame1, ConstantWaveform(16e-9, 1)) # Inst3 + .play(frame2, ConstantWaveform(8e-9, -1)) # Inst4 + ) + + expected_amplitudes = {"frame1": TimeSeries(), "frame2": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries(), "frame2": TimeSeries()} + expected_phases = {"frame1": TimeSeries(), "frame2": TimeSeries()} + + # Inst1 + shift_time_frame1 = 0 + shift_time_frame2 = 0 + pulse_length = 12e-9 + times = np.arange(0, pulse_length, port.dt) + values = 0.75 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) + expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) + expected_phases["frame1"].put(shift_time_frame1 + t, 0) + + # Inst2 + # Delay frame1 and frame2 by 10e-9 + # frame2 is 0 from 0ns to 21ns + shift_time_frame1 = shift_time_frame1 + pulse_length + pulse_length = 10e-9 + + expected_amplitudes["frame1"].put(shift_time_frame1, 0).put( + shift_time_frame1 + pulse_length - port.dt, 0 + ) + expected_frequencies["frame1"].put(shift_time_frame1, 1e8).put( + shift_time_frame1 + pulse_length - port.dt, 1e8 + ) + expected_phases["frame1"].put(shift_time_frame1, 0).put( + shift_time_frame1 + pulse_length - port.dt, 0 + ) + + # sync frames (to shift_time_frame1) and then add delay of pulse_length + expected_amplitudes["frame2"].put(0, 0).put(shift_time_frame1 + pulse_length - port.dt, 0) + expected_frequencies["frame2"].put(0, 1e8).put(shift_time_frame1 + pulse_length - port.dt, 1e8) + expected_phases["frame2"].put(0, 0).put(shift_time_frame1 + pulse_length - port.dt, 0) + + # Inst3 + shift_time_frame1 = shift_time_frame1 + pulse_length + pulse_length = 16e-9 + times = np.arange(0, pulse_length, port.dt) + values = 1 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) + expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) + expected_phases["frame1"].put(shift_time_frame1 + t, 0) + + # Inst4 + shift_time_frame2 = shift_time_frame1 + pulse_length = 8e-9 + times = np.arange(0, pulse_length, port.dt) + values = -1 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["frame2"].put(shift_time_frame2 + t, v) + expected_frequencies["frame2"].put(shift_time_frame2 + t, 1e8) + expected_phases["frame2"].put(shift_time_frame2 + t, 0) + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) + + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + + +def test_delay_qubits(port): + frame1 = Frame(frame_id="q0_frame", port=port, frequency=1e8, phase=0, is_predefined=False) + frame2 = Frame(frame_id="q0_q1_frame", port=port, frequency=1e8, phase=0, is_predefined=False) + pulse_seq = ( + PulseSequence() + .play(frame1, ConstantWaveform(12e-9, 0.75)) # Inst1 + .delay(QubitSet([0, 1, 2]), 10e-9) # Inst 2 + .play(frame1, ConstantWaveform(16e-9, 1)) # Inst3 + .play(frame2, ConstantWaveform(8e-9, -1)) # Inst4 + ) + expected_amplitudes = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} + expected_frequencies = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} + expected_phases = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} + + # Inst1 + shift_time_frame1 = 0 + shift_time_frame2 = 0 + pulse_length = 12e-9 + times = np.arange(0, pulse_length, port.dt) + values = 0.75 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["q0_frame"].put(shift_time_frame1 + t, v) + expected_frequencies["q0_frame"].put(shift_time_frame1 + t, 1e8) + expected_phases["q0_frame"].put(shift_time_frame1 + t, 0) + + # Inst2 + # Delay frame1 and frame2 by 10e-9 + # frame2 is 0 from 0ns to 21ns + shift_time_frame1 = shift_time_frame1 + pulse_length + pulse_length = 10e-9 + + expected_amplitudes["q0_frame"].put(shift_time_frame1, 0).put( + shift_time_frame1 + pulse_length - port.dt, 0 + ) + expected_frequencies["q0_frame"].put(shift_time_frame1, 1e8).put( + shift_time_frame1 + pulse_length - port.dt, 1e8 + ) + expected_phases["q0_frame"].put(shift_time_frame1, 0).put( + shift_time_frame1 + pulse_length - port.dt, 0 + ) + + # sync frames (to shift_time_frame1) and then add delay of pulse_length + expected_amplitudes["q0_q1_frame"].put(0, 0).put(shift_time_frame1 + pulse_length - port.dt, 0) + expected_frequencies["q0_q1_frame"].put(0, 1e8).put( + shift_time_frame1 + pulse_length - port.dt, 1e8 + ) + 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 + pulse_length = 16e-9 + times = np.arange(0, pulse_length, port.dt) + values = 1 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["q0_frame"].put(shift_time_frame1 + t, v) + expected_frequencies["q0_frame"].put(shift_time_frame1 + t, 1e8) + expected_phases["q0_frame"].put(shift_time_frame1 + t, 0) + + # Inst4 + shift_time_frame2 = shift_time_frame1 + pulse_length = 8e-9 + times = np.arange(0, pulse_length, port.dt) + values = -1 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["q0_q1_frame"].put(shift_time_frame2 + t, v) + expected_frequencies["q0_q1_frame"].put(shift_time_frame2 + t, 1e8) + expected_phases["q0_q1_frame"].put(shift_time_frame2 + t, 0) + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) + + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + assert list(parser.amplitudes.keys()) == ["q0_frame", "q0_q1_frame"] # no frame belonging to $2 + + +def test_delay_no_args(port): + frame1 = Frame(frame_id="q0_frame", port=port, frequency=1e8, phase=0, is_predefined=False) + frame2 = Frame(frame_id="q0_q1_frame", port=port, frequency=1e8, phase=0, is_predefined=False) + pulse_seq = ( + PulseSequence() + .play(frame1, ConstantWaveform(12e-9, 0.75)) # Inst1 + .delay([], 10e-9) # Inst 2 + .play(frame1, ConstantWaveform(16e-9, 1)) # Inst3 + .play(frame2, ConstantWaveform(8e-9, -1)) # Inst4 + ) + expected_amplitudes = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} + expected_frequencies = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} + expected_phases = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} + + # Inst1 + shift_time_frame1 = 0 + shift_time_frame2 = 0 + pulse_length = 12e-9 + times = np.arange(0, pulse_length, port.dt) + values = 0.75 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["q0_frame"].put(shift_time_frame1 + t, v) + expected_frequencies["q0_frame"].put(shift_time_frame1 + t, 1e8) + expected_phases["q0_frame"].put(shift_time_frame1 + t, 0) + + # Inst2 + # Delay frame1 and frame2 by 10e-9 + # frame2 is 0 from 0ns to 21ns + shift_time_frame1 = shift_time_frame1 + pulse_length + pulse_length = 10e-9 + + expected_amplitudes["q0_frame"].put(shift_time_frame1, 0).put( + shift_time_frame1 + pulse_length - port.dt, 0 + ) + expected_frequencies["q0_frame"].put(shift_time_frame1, 1e8).put( + shift_time_frame1 + pulse_length - port.dt, 1e8 + ) + expected_phases["q0_frame"].put(shift_time_frame1, 0).put( + shift_time_frame1 + pulse_length - port.dt, 0 + ) + + # sync frames (to shift_time_frame1) and then add delay of pulse_length + expected_amplitudes["q0_q1_frame"].put(0, 0).put(shift_time_frame1 + pulse_length - port.dt, 0) + expected_frequencies["q0_q1_frame"].put(0, 1e8).put( + shift_time_frame1 + pulse_length - port.dt, 1e8 + ) + 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 + pulse_length = 16e-9 + times = np.arange(0, pulse_length, port.dt) + values = 1 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["q0_frame"].put(shift_time_frame1 + t, v) + expected_frequencies["q0_frame"].put(shift_time_frame1 + t, 1e8) + expected_phases["q0_frame"].put(shift_time_frame1 + t, 0) + + # Inst4 + shift_time_frame2 = shift_time_frame1 + pulse_length = 8e-9 + times = np.arange(0, pulse_length, port.dt) + values = -1 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["q0_q1_frame"].put(shift_time_frame2 + t, v) + expected_frequencies["q0_q1_frame"].put(shift_time_frame2 + t, 1e8) + expected_phases["q0_q1_frame"].put(shift_time_frame2 + t, 0) + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) + + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + assert list(parser.amplitudes.keys()) == ["q0_frame", "q0_q1_frame"] # no frame belonging to $2 + + def test_predefined_frame(port): frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=True) pulse_seq = PulseSequence().delay(frame, 3e-9) @@ -531,7 +752,66 @@ def test_barrier_no_args(port): verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) -def test_barrier_different_dt(port): +def test_barrier_qubits(port): + frame1 = Frame(frame_id="q0_frame", port=port, frequency=1e8, phase=0, is_predefined=False) + frame2 = Frame(frame_id="q0_q1_frame", port=port, frequency=1e8, phase=0, is_predefined=False) + pulse_seq = ( + PulseSequence() + .play(frame1, ConstantWaveform(12e-9, 0.75)) # Inst1 + .barrier(QubitSet([0, 1, 2])) # Inst 2 + .play(frame1, ConstantWaveform(16e-9, 1)) # Inst3 + .play(frame2, ConstantWaveform(8e-9, -1)) # Inst4 + ) + + expected_amplitudes = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} + expected_frequencies = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} + expected_phases = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} + + # Inst1 + shift_time_frame1 = 0 + pulse_length = 12e-9 + times = np.arange(0, pulse_length, port.dt) + values = 0.75 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["q0_frame"].put(shift_time_frame1 + t, v) + expected_frequencies["q0_frame"].put(shift_time_frame1 + t, 1e8) + expected_phases["q0_frame"].put(shift_time_frame1 + t, 0) + + # Inst2 + # Delay frame2 from 0ns to 11ns + shift_time_frame2 = 0 + + expected_amplitudes["q0_q1_frame"].put(0, 0).put(11e-9, 0) + expected_frequencies["q0_q1_frame"].put(0, 1e8).put(11e-9, 1e8) + expected_phases["q0_q1_frame"].put(0, 0).put(11e-9, 0) + + # Inst3 + shift_time_frame1 = shift_time_frame1 + pulse_length + pulse_length = 16e-9 + times = np.arange(0, pulse_length, port.dt) + values = 1 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["q0_frame"].put(shift_time_frame1 + t, v) + expected_frequencies["q0_frame"].put(shift_time_frame1 + t, 1e8) + expected_phases["q0_frame"].put(shift_time_frame1 + t, 0) + + # Inst4 + shift_time_frame2 = shift_time_frame1 + pulse_length = 8e-9 + times = np.arange(0, pulse_length, port.dt) + values = -1 * np.ones_like(times) + for t, v in zip(times, values): + expected_amplitudes["q0_q1_frame"].put(shift_time_frame2 + t, v) + expected_frequencies["q0_q1_frame"].put(shift_time_frame2 + t, 1e8) + expected_phases["q0_q1_frame"].put(shift_time_frame2 + t, 0) + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) + + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + assert list(parser.amplitudes.keys()) == ["q0_frame", "q0_q1_frame"] # no frame belonging to $2 + + +def test_barrier_different_dt(): port1 = Port(port_id="device_port_x1", dt=5e-9, properties={}) port2 = Port(port_id="device_port_x2", dt=4e-9, properties={}) frame1 = Frame(frame_id="frame1", port=port1, frequency=1e8, phase=0, is_predefined=False) From e11131ec89aeb853a3046b2e04d72dfced50a0c7 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Wed, 5 Jul 2023 20:27:33 -0400 Subject: [PATCH 0725/1165] fix: pass the expression field when parsing to FreeParameterExpression (#588) Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> --- src/braket/parametric/free_parameter_expression.py | 2 +- .../braket/parametric/test_free_parameter_expression.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index 9059cdb5..eef9f8a6 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -56,7 +56,7 @@ def __init__(self, expression: Union[FreeParameterExpression, Number, Expr, str] elif isinstance(expression, (Number, Expr)): self._expression = expression elif isinstance(expression, str): - self._expression = self._parse_string_expression(expression) + self._expression = self._parse_string_expression(expression).expression else: raise NotImplementedError 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 735c3d3d..8a9a73e2 100644 --- a/test/unit_tests/braket/parametric/test_free_parameter_expression.py +++ b/test/unit_tests/braket/parametric/test_free_parameter_expression.py @@ -49,6 +49,11 @@ def test_equality_str(): param_values = {"theta": 1} assert expr_1 == expr_2 assert expr_1.subs(param_values) == expr_2.subs(param_values) + assert ( + hasattr(expr_1.expression, "free_symbols") + == hasattr(expr_2.expression, "free_symbols") + == True + ) @pytest.mark.xfail(raises=ValueError) From e721a3a7d19ca749a0b0e5164e570ab150796a6f Mon Sep 17 00:00:00 2001 From: Stephen Face <60493521+shpface@users.noreply.github.com> Date: Wed, 5 Jul 2023 21:34:10 -0400 Subject: [PATCH 0726/1165] fix: constrain boto version (#598) --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c71f8cc9..41ba7267 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,8 @@ "setuptools", "backoff", "boltons", - "boto3>=1.22.3", + "botocore<1.29.160", + "boto3>=1.22.3,<1.26.160", "nest-asyncio", "networkx", "numpy", From 27351ea5e4181469f3484193da16db6efe137834 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 6 Jul 2023 04:21:03 +0000 Subject: [PATCH 0727/1165] prepare release v1.48.0 --- CHANGELOG.md | 12 ++++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91bc4499..7a49b9fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## v1.48.0 (2023-07-06) + +### Features + + * draw barrier/delay with qubits + +### Bug Fixes and Other Changes + + * constrain boto version + * pass the expression field when parsing to FreeParameterExpression + * pass gate modifiers to to_unitary + ## v1.47.0 (2023-06-30) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 10f3c763..c9e55f80 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.47.1.dev0" +__version__ = "1.48.0" From 3e6d65cc0e0bc30b2ee9cd61f88b42f68e0e6def Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 6 Jul 2023 04:21:03 +0000 Subject: [PATCH 0728/1165] update development version to v1.48.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 c9e55f80..601a52e7 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.48.0" +__version__ = "1.48.1.dev0" From 6aa6babf940cca9a37257102ba511402ead98930 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Thu, 6 Jul 2023 14:55:36 -0400 Subject: [PATCH 0729/1165] change: move simulator mcm=True argument into device.run for autoqasm programs (#600) * Move mcm=True argument into device.run * Add comments explaining mcm=True kwarg --- setup.py | 11 +++++++++-- src/braket/devices/local_simulator.py | 5 +++++ src/braket/experimental/autoqasm/README.md | 5 ++--- .../braket/experimental/autoqasm/test_api.py | 2 +- tox.ini | 2 +- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 7c97788c..5b4b996b 100644 --- a/setup.py +++ b/setup.py @@ -28,8 +28,15 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.17.0", - "amazon-braket-default-simulator @ git+https://github.com/aws/amazon-braket-default-simulator-python.git@9b0a2a7c6a9b8a580ddc04f3d1a048dc47fac374#egg=amazon-braket-default-simulator", # mcm-sim branch # noqa E501 - "oqpy @ git+https://github.com/ajberdy/oqpy.git@7e5885af6193009265c8195dad7553db02bdfd96#egg=oqpy", # qubit-array branch # noqa E501 + # Pin the latest commit of mcm-sim branch of aws/amazon-braket-default-simulator-python.git + # to get the version of the simulator that supports the mcm=True argument for Monte Carlo + # simulation of mid-circuit measurement, which AutoQASM requires. + # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main. + "amazon-braket-default-simulator @ git+https://github.com/aws/amazon-braket-default-simulator-python.git@9b0a2a7c6a9b8a580ddc04f3d1a048dc47fac374#egg=amazon-braket-default-simulator", # noqa E501 + # Pin the latest commit of the qubit-array branch of ajberdy/oqpy.git to get the version of + # oqpy which contains changes that AutoQASM relies on, including the QubitArray type. + # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main. + "oqpy @ git+https://github.com/ajberdy/oqpy.git@7e5885af6193009265c8195dad7553db02bdfd96#egg=oqpy", # noqa E501 "setuptools", "backoff", "boltons", diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 9efb10f7..c81c7853 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -314,6 +314,11 @@ def _( if DeviceActionType.OPENQASM not in simulator.properties.action: raise NotImplementedError(f"{type(simulator)} does not support OpenQASM programs") program = Program(source=program.to_ir(ir_type=IRType.OPENQASM)) + + # Pass mcm=True to the simulator to enable mid-circuit measurement simulation. + # When setting mcm=True, the simulator returns only the measurement results, + # which we then wrap into a GateModelQuantumTaskResult object to return. + kwargs["mcm"] = True results = simulator.run(program, shots, *args, **kwargs) return GateModelQuantumTaskResult( task_metadata=TaskMetadata.construct(id=""), diff --git a/src/braket/experimental/autoqasm/README.md b/src/braket/experimental/autoqasm/README.md index ce909d2d..8db2dd74 100644 --- a/src/braket/experimental/autoqasm/README.md +++ b/src/braket/experimental/autoqasm/README.md @@ -79,14 +79,13 @@ and quantum runtime side-by-side. For the moment, we support only a few quantum them out! The Amazon Braket local simulator supports AutoQASM programs as input. -Let's simulate the `my_reset_program` with the `mcm=True` argument, -which enables simulation of mid-circuit measurements: +Let's simulate the `my_reset_program`: ``` from braket.devices.local_simulator import LocalSimulator device = LocalSimulator() -task = device.run(my_reset_program, shots=100, mcm=True) +task = device.run(my_reset_program, shots=100) result = task.result() ``` diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index 3bafda42..f16d80e8 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -28,7 +28,7 @@ def _test_on_local_sim(program: aq.Program) -> None: device = LocalSimulator(backend=StateVectorSimulator()) - task = device.run(program, shots=10, mcm=True) + task = device.run(program, shots=10) assert isinstance(task, LocalQuantumTask) assert isinstance(task.result().measurements, dict) diff --git a/tox.ini b/tox.ini index 4e4f3e33..23ca2799 100644 --- a/tox.ini +++ b/tox.ini @@ -95,4 +95,4 @@ commands = deps = # If you need to test on a certain branch, add @ after .git git+https://github.com/aws/amazon-braket-schemas-python.git - git+https://github.com/aws/amazon-braket-default-simulator-python.git@d63d367aafc21e2a8cc470c6ab20ca87cbe525aa # mcm-sim branch + git+https://github.com/aws/amazon-braket-default-simulator-python.git From 77060446052c9a43e34425ce041f651bf0078fad Mon Sep 17 00:00:00 2001 From: Stephen Face <60493521+shpface@users.noreply.github.com> Date: Thu, 6 Jul 2023 23:04:34 -0400 Subject: [PATCH 0730/1165] fix: use event callbacks to add braket user agents (#602) --- setup.py | 3 +- src/braket/aws/aws_session.py | 34 +++++++++++-------- test/integ_tests/test_user_agents.py | 33 ++++++++++++++++++ .../unit_tests/braket/aws/test_aws_session.py | 20 +++++++---- 4 files changed, 67 insertions(+), 23 deletions(-) create mode 100644 test/integ_tests/test_user_agents.py diff --git a/setup.py b/setup.py index 41ba7267..c71f8cc9 100644 --- a/setup.py +++ b/setup.py @@ -33,8 +33,7 @@ "setuptools", "backoff", "boltons", - "botocore<1.29.160", - "boto3>=1.22.3,<1.26.160", + "boto3>=1.22.3", "nest-asyncio", "networkx", "numpy", diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 7ce1de27..56a4cc1b 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -22,7 +22,7 @@ import backoff import boto3 -from botocore import client +from botocore import awsrequest, client from botocore.config import Config from botocore.exceptions import ClientError @@ -83,6 +83,9 @@ def __init__( 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 @@ -157,8 +160,7 @@ def _update_user_agent(self) -> None: """ 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"). See: - https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html#botocore.config.Config + values (For example: "Boto3/1.14.43 Python/3.7.9 Botocore/1.17.44"). """ def _notebook_instance_version() -> str: @@ -166,16 +168,12 @@ def _notebook_instance_version() -> str: nbi_metadata_path = "/opt/ml/metadata/resource-metadata.json" return "0" if os.path.exists(nbi_metadata_path) else "None" - additional_user_agent_fields = ( + self._braket_user_agents = ( f"BraketSdk/{braket_sdk.__version__} " f"BraketSchemas/{braket_schemas.__version__} " f"NotebookInstance/{_notebook_instance_version()}" ) - self.braket_client._client_config.user_agent = ( - f"{self.braket_client._client_config.user_agent} {additional_user_agent_fields}" - ) - 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 @@ -185,12 +183,20 @@ def add_braket_user_agent(self, user_agent: str) -> None: Args: user_agent (str): The user_agent value to append to the header. """ - existing_user_agent = self.braket_client._client_config.user_agent - if user_agent not in existing_user_agent: - self.braket_client._client_config.user_agent = f"{existing_user_agent} {user_agent}" + 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) @staticmethod - def _add_cost_tracker_count_handler(request: Any, **kwargs) -> None: + def _add_cost_tracker_count_handler(request: awsrequest.AWSRequest, **kwargs) -> None: request.headers.add_header("Braket-Trackers", str(len(active_trackers()))) # @@ -815,7 +821,5 @@ def copy_session( boto_session=boto_session, config=config, default_bucket=default_bucket ) # Preserve user_agent information - copied_session.braket_client._client_config.user_agent = ( - self.braket_client._client_config.user_agent - ) + copied_session._braket_user_agents = self._braket_user_agents return copied_session diff --git a/test/integ_tests/test_user_agents.py b/test/integ_tests/test_user_agents.py new file mode 100644 index 00000000..009a9a4a --- /dev/null +++ b/test/integ_tests/test_user_agents.py @@ -0,0 +1,33 @@ +from botocore import awsrequest + +import braket._schemas as braket_schemas +import braket._sdk as braket_sdk + + +def test_default_user_agent(aws_session): + braket_agents = [ + f"BraketSdk/{braket_sdk.__version__}", + f"BraketSchemas/{braket_schemas.__version__}", + "NotebookInstance/", + ] + + def assert_in_user_agent(request: awsrequest.AWSPreparedRequest, **kwargs): + user_agent = request.headers.get("User-Agent") + for agent in braket_agents: + 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() + + +def test_add_user_agent(aws_session): + aws_session.add_braket_user_agent("foo/1.0") + + def assert_in_user_agent(request: awsrequest.AWSPreparedRequest, **kwargs): + user_agent = request.headers.get("User-Agent") + 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/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index c0938d38..53423d99 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -15,6 +15,7 @@ import os import tempfile import time +from http.client import HTTPMessage from pathlib import Path from unittest.mock import MagicMock, Mock, patch @@ -282,21 +283,28 @@ def test_ecr(aws_session): ], ) def test_populates_user_agent(os_path_exists_mock, metadata_file_exists, initial_user_agent): + 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.user_agent = initial_user_agent 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) expected_user_agent = ( - f"{initial_user_agent} BraketSdk/{braket_sdk.__version__} " + 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) - assert aws_session.braket_client._client_config.user_agent == expected_user_agent + + 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 @patch("braket.aws.aws_session.active_trackers") @@ -1274,13 +1282,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_client._client_config.user_agent = "foo/bar" + aws_session._braket_user_agents = "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_client._client_config.user_agent == "foo/bar" + assert copied_session._braket_user_agents == "foo/bar" assert copied_session._default_bucket is None @@ -1319,4 +1327,4 @@ def test_add_braket_user_agent(aws_session): user_agent = "newAgent/1.0" aws_session.add_braket_user_agent(user_agent) aws_session.add_braket_user_agent(user_agent) - assert aws_session.braket_client._client_config.user_agent.count(user_agent) == 1 + aws_session._braket_user_agents.count(user_agent) == 1 From 04b00cebf4f4c3a854ad61fbffbb525063795ae5 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Fri, 7 Jul 2023 09:59:19 -0400 Subject: [PATCH 0731/1165] change: increase autoqasm transpiler code coverage to 100% (#599) * Begin improving transpiler code coverage * Code coverage for transpiler to 100% * Remove unnecessary line * Improve exception handling and test cases --- src/braket/experimental/autoqasm/api/api.py | 14 ++- .../autoqasm/transpiler/transpiler.py | 88 +++----------- .../experimental/autoqasm/test_transpiler.py | 110 ++++++++++++++++++ 3 files changed, 137 insertions(+), 75 deletions(-) create mode 100644 test/unit_tests/braket/experimental/autoqasm/test_transpiler.py diff --git a/src/braket/experimental/autoqasm/api/api.py b/src/braket/experimental/autoqasm/api/api.py index 6ec9badd..3e6cebfe 100644 --- a/src/braket/experimental/autoqasm/api/api.py +++ b/src/braket/experimental/autoqasm/api/api.py @@ -26,6 +26,7 @@ import braket.experimental.autoqasm.transpiler as aq_transpiler import braket.experimental.autoqasm.types as aq_types from braket.experimental.autoqasm import errors +from braket.experimental.autoqasm.autograph import ag_logging from braket.experimental.autoqasm.autograph.core import ag_ctx, converter from braket.experimental.autoqasm.autograph.impl.api_core import ( autograph_artifact, @@ -53,14 +54,20 @@ def function(f: Callable) -> Callable[[Any], aq_program.Program]: return f # Update documentation with user configuration - if f.__doc__ is None: - f.__doc__ = "" - f.__doc__ += f""" + try: + if f.__doc__ is None: + f.__doc__ = "" + f.__doc__ += f""" Keyword Args: {aq_program.ProgramOptions.NUM_QUBITS.value} (int): Configuration to set the total number of qubits to declare in the program. """ + except AttributeError as e: + # AttributeError: object attribute '__doc__' is read-only + # Typically occurs when `f` is not a user-defined function. This will likely lead to + # another exception down the line, but it's better simply to warn at this point. + ag_logging.warning(f"Unable to set docstring for converted function. Exception: {e}") f_wrapper = f decorators, f = tf_decorator.unwrap(f) @@ -68,6 +75,7 @@ def function(f: Callable) -> Callable[[Any], aq_program.Program]: wrapper_factory = convert_wrapper( recursive=False, optional_features=( + converter.Feature.ASSERT_STATEMENTS, converter.Feature.LISTS, converter.Feature.EQUALITY_OPERATORS, ), diff --git a/src/braket/experimental/autoqasm/transpiler/transpiler.py b/src/braket/experimental/autoqasm/transpiler/transpiler.py index ad3e5934..8cf86a99 100644 --- a/src/braket/experimental/autoqasm/transpiler/transpiler.py +++ b/src/braket/experimental/autoqasm/transpiler/transpiler.py @@ -52,10 +52,9 @@ _attach_error_metadata, _log_callargs, is_autograph_artifact, - is_autograph_strict_conversion_mode, ) from braket.experimental.autoqasm.autograph.logging import ag_logging as logging -from braket.experimental.autoqasm.autograph.pyct import anno, cfg, errors, qual_names, transpiler +from braket.experimental.autoqasm.autograph.pyct import anno, cfg, qual_names, transpiler from braket.experimental.autoqasm.autograph.pyct.static_analysis import ( activity, reaching_definitions, @@ -233,16 +232,10 @@ def converted_call( if not options.internal_convert_user_code: return _call_unconverted(f, args, kwargs, options) - target_entity, effective_args, exc = _inspect_callable(f, args) - if exc: - return _fall_back_unconverted(f, args, kwargs, options, exc) - - if _is_permanently_allowed_code(target_entity): - return _call_unconverted(f, args, kwargs, options) - + target_entity, effective_args = _inspect_callable(f, args) converted_f, exc = _try_convert_actual(target_entity, effective_args, kwargs, options) if exc: - return _fall_back_unconverted(f, args, kwargs, options, exc) + raise exc with StackTraceMapper(converted_f), tf_stack.CurrentModuleFilter(): try: @@ -275,49 +268,25 @@ def _converted_partial( ) -def _inspect_callable(f: Callable, args: tuple) -> Tuple[Callable, tuple, Optional[Exception]]: +def _inspect_callable(f: Callable, args: tuple) -> Tuple[Callable, tuple]: target_entity = None effective_args = None - exc = None - try: - if inspect.ismethod(f) or inspect.isfunction(f): - target_entity = f - effective_args = args - - f_self = getattr(f, "__self__", None) - if f_self is not None: - effective_args = (f_self,) + effective_args - - elif hasattr(f, "__class__") and hasattr(f.__class__, "__call__"): - # Callable objects. Dunder methods have special lookup rules, see: - # https://docs.python.org/3/reference/datamodel.html#specialnames - target_entity = f.__class__.__call__ - effective_args = (f,) + args - else: - target_entity = f - raise NotImplementedError('unknown callable type "%s"' % type(f)) - - except Exception as e: - logging.log(1, "Error transforming entity %s", target_entity, exc_info=True) - if is_autograph_strict_conversion_mode(): - raise - exc = e + if inspect.ismethod(f) or inspect.isfunction(f): + target_entity = f + effective_args = args - return target_entity, effective_args, exc + f_self = getattr(f, "__self__", None) + if f_self is not None: + effective_args = (f_self,) + effective_args + elif hasattr(f, "__class__") and hasattr(f.__class__, "__call__"): + # Callable objects. Dunder methods have special lookup rules, see: + # https://docs.python.org/3/reference/datamodel.html#specialnames + target_entity = f.__class__.__call__ + effective_args = (f,) + args -def _is_permanently_allowed_code(target_entity: Callable) -> bool: - if not hasattr(target_entity, "__code__"): - logging.log(2, "Permanently allowed: %s: native binding", target_entity) - return True - elif ( - hasattr(target_entity.__code__, "co_filename") - and target_entity.__code__.co_filename == "" - ): - logging.log(2, "Permanently allowed: %s: dynamic code (exec?)", target_entity) - return True - return False + return target_entity, effective_args def _try_convert_actual( @@ -335,35 +304,10 @@ def _try_convert_actual( _log_callargs(converted_f, effective_args, kwargs) except Exception as e: logging.log(1, "Error transforming entity %s", target_entity, exc_info=True) - if is_autograph_strict_conversion_mode(): - raise exc = e return converted_f, exc -def _fall_back_unconverted( - f: Callable, args: tuple, kwargs: dict, options: converter.ConversionOptions, exc: Exception -) -> Any: - """Falls back to calling the function unconverted, in case of error.""" - warning_template = ( - "AutoGraph could not transform %s and will run it as-is.\n" "%s" "Cause: %s\n" - ) - if isinstance(exc, errors.InaccessibleSourceCodeError): - if ag_ctx.INSPECT_SOURCE_SUPPORTED: - logging.warning(warning_template, f, "", exc) - elif isinstance(exc, errors.UnsupportedLanguageElementError): - logging.warning(warning_template, f, "", exc) - else: - file_bug_message = ( - "Please report this in the AutoQASM repo. When filing the bug, set" - " the verbosity to 10 (on Linux, `export AUTOGRAPH_VERBOSITY=10`) and" - " attach the full output.\n" - ) - logging.warning(warning_template, f, file_bug_message, exc) - - return _call_unconverted(f, args, kwargs, options) - - def _call_unconverted( f: Callable, args: tuple, diff --git a/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py b/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py new file mode 100644 index 00000000..d45a6f35 --- /dev/null +++ b/test/unit_tests/braket/experimental/autoqasm/test_transpiler.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. + +"""Tests for transpiler.""" + +import functools + +import pytest + +import braket.experimental.autoqasm as aq +from braket.experimental.autoqasm.autograph import ag_logging +from braket.experimental.autoqasm.autograph.core.ag_ctx import ControlStatusCtx, Status +from braket.experimental.autoqasm.gates import cnot, h, measure, x + + +def test_convert_invalid_object() -> None: + """Tests the aq.function decorator on something that is not a function.""" + + @aq.function + class MyClass: + pass + + with pytest.raises(ValueError): + MyClass() + + +def test_autograph_disabled() -> None: + """Tests the aq.function decorator with autograph disabled by control status, + and verifies that the function is not converted.""" + + @aq.function + def my_program(): + h(0) + if measure(0): + x(0) + + with ControlStatusCtx(Status.DISABLED): + with pytest.raises(RuntimeError): + my_program() + + +def test_partial_function() -> None: + """Tests aq.function decorator application to a partial function.""" + + def bell(q0: int, q1: int): + h(q0) + cnot(q0, q1) + + @aq.function + def bell_decorated(q0: int, q1: int): + bell(q0, q1) + + expected = """OPENQASM 3.0; +qubit[4] __qubits__; +h __qubits__[1]; +cnot __qubits__[1], __qubits__[3];""" + + bell_partial = aq.function(functools.partial(bell, 1)) + assert bell_partial(3).to_ir() == expected + + bell_decorated_partial = functools.partial(bell_decorated, 1) + assert bell_decorated_partial(3).to_ir() == expected + + +def test_classmethod() -> None: + """Tests aq.function decorator application to a classmethod.""" + + class MyClass: + @classmethod + def bell(self, q0: int, q1: int): + h(q0) + cnot(q0, q1) + + @classmethod + @aq.function + def bell_decorated(self, q0: int, q1: int): + self.bell(q0, q1) + + expected = """OPENQASM 3.0; +qubit[4] __qubits__; +h __qubits__[1]; +cnot __qubits__[1], __qubits__[3];""" + + assert aq.function(MyClass.bell)(1, 3).to_ir() == expected + assert MyClass.bell_decorated(1, 3).to_ir() == expected + + a = MyClass() + assert aq.function(a.bell)(1, 3).to_ir() == expected + assert a.bell_decorated(1, 3).to_ir() == expected + + +def test_with_verbose_logging() -> None: + """Tests aq.function decorator application with verbose logging enabled.""" + + @aq.function + def nothing(): + pass + + ag_logging.set_verbosity(10) + nothing() From f57f1d5ebdb68f7f2ee536837e290c9b6d6e29e7 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 7 Jul 2023 18:05:15 +0000 Subject: [PATCH 0732/1165] prepare release v1.48.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 7a49b9fd..2b2ba62a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.48.1 (2023-07-07) + +### Bug Fixes and Other Changes + + * use event callbacks to add braket user agents + ## v1.48.0 (2023-07-06) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 601a52e7..7aced30b 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.48.1.dev0" +__version__ = "1.48.1" From ad5bfd0255ed8343e9fa0b99e943146270582d5a Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 7 Jul 2023 18:05:15 +0000 Subject: [PATCH 0733/1165] update development version to v1.48.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 7aced30b..6644d057 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.48.1" +__version__ = "1.48.2.dev0" From 3996f384635591e9b1b8a2e4cc856ff1f72c1df8 Mon Sep 17 00:00:00 2001 From: StepSecurity Bot Date: Fri, 7 Jul 2023 12:33:37 -0700 Subject: [PATCH 0734/1165] ci: Harden GitHub Actions (#607) Signed-off-by: StepSecurity Bot Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> --- .github/workflows/check-format.yml | 7 +++++-- .github/workflows/dependent-tests.yml | 7 +++++-- .github/workflows/publish-to-pypi.yml | 9 ++++++--- .github/workflows/python-package.yml | 9 ++++++--- .github/workflows/stale_issue.yml | 2 +- .github/workflows/twine-check.yml | 7 +++++-- 6 files changed, 28 insertions(+), 13 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index 51a56630..6d56ffe5 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -9,13 +9,16 @@ on: - main - feature/** +permissions: + contents: read + jobs: check-code-format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@bd6b4b6205c4dbad673328db7b31b7fab9e241c0 # v4.6.1 with: python-version: '3.x' - name: Install dependencies diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index d6e247d4..13b05d1c 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -6,6 +6,9 @@ on: - main - feature/** +permissions: + contents: read + jobs: build: @@ -18,9 +21,9 @@ jobs: - amazon-braket-pennylane-plugin-python steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@bd6b4b6205c4dbad673328db7b31b7fab9e241c0 # v4.6.1 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 c6171c41..313533e1 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -4,14 +4,17 @@ on: release: types: [published] +permissions: + contents: read + jobs: build-and-publish: name: Build and publish distribution to PyPi runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@bd6b4b6205c4dbad673328db7b31b7fab9e241c0 # v4.6.1 with: python-version: '3.x' - name: Install wheel @@ -21,6 +24,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@release/v1 + uses: pypa/gh-action-pypi-publish@f5622bde02b04381239da3573277701ceca8f6a0 # release/v1 with: password: ${{ secrets.pypi_token }} diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index fe10ad51..996fcbc7 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -12,6 +12,9 @@ on: - main - feature/** +permissions: + contents: read + jobs: build: runs-on: ${{ matrix.os }} @@ -21,9 +24,9 @@ jobs: python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@bd6b4b6205c4dbad673328db7b31b7fab9e241c0 # v4.6.1 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -34,5 +37,5 @@ jobs: run: | tox -e unit-tests - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 if: ${{ strategy.job-index }} == 0 diff --git a/.github/workflows/stale_issue.yml b/.github/workflows/stale_issue.yml index 482c7084..3b19878a 100644 --- a/.github/workflows/stale_issue.yml +++ b/.github/workflows/stale_issue.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest name: Stale issue job steps: - - uses: aws-actions/stale-issue-cleanup@v6 + - uses: aws-actions/stale-issue-cleanup@7de35968489e4142233d2a6812519a82e68b5c38 # v6 with: # Setting messages to an empty string will cause the automation to skip # that category diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index a6035204..50747091 100644 --- a/.github/workflows/twine-check.yml +++ b/.github/workflows/twine-check.yml @@ -6,14 +6,17 @@ on: - main - feature/** +permissions: + contents: read + jobs: twine-check: name: Check long description runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@bd6b4b6205c4dbad673328db7b31b7fab9e241c0 # v4.6.1 with: python-version: '3.x' - name: Install wheel From 0926fab9dae3f57563587de11790475fa2a83a1a Mon Sep 17 00:00:00 2001 From: Sai Prakash Ch <52249348+Sai-prakash15@users.noreply.github.com> Date: Fri, 7 Jul 2023 16:57:30 -0600 Subject: [PATCH 0735/1165] feat: OpenQASM to `Circuit` translator (#587) Adds a new `from_ir` method that converts an OpenQASM program to a `Circuit`. --- src/braket/circuits/angled_gate.py | 4 +- src/braket/circuits/basis_state.py | 3 + src/braket/circuits/braket_program_context.py | 105 +++ src/braket/circuits/circuit.py | 22 + src/braket/circuits/instruction.py | 6 +- src/braket/circuits/translations.py | 219 ++++++ .../braket/circuits/test_circuit.py | 652 ++++++++++++++++++ .../test_free_parameter_expression.py | 6 +- 8 files changed, 1007 insertions(+), 10 deletions(-) create mode 100644 src/braket/circuits/braket_program_context.py create mode 100644 src/braket/circuits/translations.py diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index c40dce17..4c148612 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -395,14 +395,14 @@ def _multi_angled_ascii_characters( 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): + def format_string(angle: Union[FreeParameterExpression, float]) -> str: 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 0d32712b..66e406aa 100644 --- a/src/braket/circuits/basis_state.py +++ b/src/braket/circuits/basis_state.py @@ -30,6 +30,9 @@ def __len__(self) -> int: def __iter__(self): return iter(self.state) + def __eq__(self, other): + return self.state == other.state + BasisStateInput = Union[int, List[int], str, BasisState] diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py new file mode 100644 index 00000000..a21bfbf7 --- /dev/null +++ b/src/braket/circuits/braket_program_context.py @@ -0,0 +1,105 @@ +# 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 typing import List, Optional, Tuple + +import numpy as np + +from braket.circuits import Circuit, Instruction +from braket.circuits.gates import Unitary +from braket.circuits.translations import ( + BRAKET_GATES, + braket_noise_gate_to_instruction, + braket_result_to_result_type, +) +from braket.default_simulator import KrausOperation +from braket.default_simulator.openqasm.program_context import AbstractProgramContext +from braket.ir.jaqcd.program_v1 import Results + + +class BraketProgramContext(AbstractProgramContext): + def __init__(self, circuit: Optional[Circuit] = None): + """ + Args: + circuit (Optional[Circuit]): A partially-built circuit to continue building with this + context. Default: None. + """ + super().__init__() + self._circuit = circuit or Circuit() + + @property + def circuit(self) -> Circuit: + return self._circuit + + def is_builtin_gate(self, name: str) -> bool: + """Whether the gate is currently in scope as a built-in Braket gate. + + Args: + name (str): name of the built-in Braket gate + + Returns: + bool: return TRUE if it is a built-in gate else FALSE. + """ + 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_gate_instruction( + self, gate_name: str, target: Tuple[int], *params, ctrl_modifiers: List[int], power: float + ) -> None: + """Add Braket gate to the circuit. + + Args: + gate_name (str): name of the built-in Braket gate. + target (Tuple[int]): control_qubits + target_qubits. + ctrl_modifiers (List[int]): Quantum state on which to control the + operation. Must be a binary sequence of same length as number of qubits in + `control-qubits` in target. 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. + power(float): Integer or fractional power to raise the gate to. + """ + target_qubits = target[len(ctrl_modifiers) :] + control_qubits = target[: len(ctrl_modifiers)] + instruction = Instruction( + BRAKET_GATES[gate_name](*params[0]), + target=target_qubits, + control=control_qubits, + control_state=ctrl_modifiers, + power=power, + ) + self._circuit.add_instruction(instruction) + + def add_custom_unitary( + self, + unitary: np.ndarray, + target: Tuple[int], + ) -> None: + """Add a custom Unitary instruction to the circuit + + Args: + unitary (np.ndarray): unitary matrix + target (Tuple[int]): control_qubits + target_qubits + """ + instruction = Instruction(Unitary(unitary), target) + self._circuit.add_instruction(instruction) + + def add_noise_instruction(self, noise: KrausOperation) -> None: + """Add a noise instruction the circuit""" + self._circuit.add_instruction(braket_noise_gate_to_instruction(noise)) + + def add_result(self, result: Results) -> None: + """Add a result type to the circuit""" + self._circuit.add_result_type(braket_result_to_result_type(result)) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index b7f770d7..38d6793f 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -53,8 +53,10 @@ SerializationProperties, ) from braket.circuits.unitary_calculation import calculate_unitary, 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.pulse_sequence import _validate_uniqueness @@ -1129,6 +1131,26 @@ def to_ir( else: raise ValueError(f"Supplied ir_type {ir_type} is not supported.") + @staticmethod + def from_ir(source: str, inputs: Optional[Dict[str, io_type]] = None) -> Circuit: + """ + Converts an OpenQASM program to a Braket Circuit object. + + Args: + source (str): OpenQASM string. + inputs (Optional[Dict[str, io_type]]): Inputs to the circuit. + + Returns: + Circuit: Braket Circuit implementing the OpenQASM program. + """ + from braket.circuits.braket_program_context import BraketProgramContext + + return Interpreter(BraketProgramContext()).build_circuit( + source=source, + inputs=inputs, + is_file=False, + ) + def _to_jaqcd(self) -> JaqcdProgram: jaqcd_ir_type = IRType.JAQCD ir_instructions = [instr.to_ir(ir_type=jaqcd_ir_type) for instr in self.instructions] diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index cde92183..6c555e3a 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -289,9 +289,9 @@ def __eq__(self, other): ) == ( other._operator, other._target, - self._control, - self._control_state, - self._power, + other._control, + other._control_state, + other._power, ) return NotImplemented diff --git a/src/braket/circuits/translations.py b/src/braket/circuits/translations.py new file mode 100644 index 00000000..d98a0387 --- /dev/null +++ b/src/braket/circuits/translations.py @@ -0,0 +1,219 @@ +# 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 reduce, singledispatch +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.ir.jaqcd.shared_models as models +from braket.circuits import Instruction, Observable, observables +from braket.default_simulator import KrausOperation +from braket.default_simulator.noise_operations import ( + AmplitudeDamping, + BitFlip, + Depolarizing, + GeneralizedAmplitudeDamping, + PauliChannel, + PhaseDamping, + PhaseFlip, + TwoQubitDephasing, + TwoQubitDepolarizing, +) +from braket.ir.jaqcd import ( + Amplitude, + DensityMatrix, + Expectation, + Probability, + Sample, + StateVector, + Variance, +) +from braket.ir.jaqcd.program_v1 import Results + +BRAKET_GATES = { + "i": braket_gates.I, + "h": braket_gates.H, + "x": braket_gates.X, + "y": braket_gates.Y, + "z": braket_gates.Z, + "cv": braket_gates.CV, + "cnot": braket_gates.CNot, + "cy": braket_gates.CY, + "cz": braket_gates.CZ, + "ecr": braket_gates.ECR, + "s": braket_gates.S, + "si": braket_gates.Si, + "t": braket_gates.T, + "ti": braket_gates.Ti, + "v": braket_gates.V, + "vi": braket_gates.Vi, + "phaseshift": braket_gates.PhaseShift, + "cphaseshift": braket_gates.CPhaseShift, + "cphaseshift00": braket_gates.CPhaseShift00, + "cphaseshift01": braket_gates.CPhaseShift01, + "cphaseshift10": braket_gates.CPhaseShift10, + "rx": braket_gates.Rx, + "ry": braket_gates.Ry, + "rz": braket_gates.Rz, + "swap": braket_gates.Swap, + "iswap": braket_gates.ISwap, + "pswap": braket_gates.PSwap, + "xy": braket_gates.XY, + "xx": braket_gates.XX, + "yy": braket_gates.YY, + "zz": braket_gates.ZZ, + "ccnot": braket_gates.CCNot, + "cswap": braket_gates.CSwap, + "gpi": braket_gates.GPi, + "gpi2": braket_gates.GPi2, + "ms": braket_gates.MS, + "unitary": braket_gates.Unitary, +} + + +@singledispatch +def _braket_noise_gate_to_instruction(noise: KrausOperation) -> Union[Instruction]: + raise TypeError(f"Operation {type(noise).__name__} not supported") + + +def braket_noise_gate_to_instruction(noise: KrausOperation) -> Union[Instruction]: + return _braket_noise_gate_to_instruction(noise) + + +@_braket_noise_gate_to_instruction.register(BitFlip) +def _(noise): + return Instruction(noises.BitFlip(noise.probability), target=noise.targets) + + +@_braket_noise_gate_to_instruction.register(PhaseFlip) +def _(noise): + return Instruction(noises.PhaseFlip(noise.probability), target=noise.targets) + + +@_braket_noise_gate_to_instruction.register(PauliChannel) +def _(noise): + return Instruction(noises.PauliChannel(*noise.probabilities), target=noise.targets) + + +@_braket_noise_gate_to_instruction.register(Depolarizing) +def _(noise): + return Instruction(noises.Depolarizing(noise.probability), target=noise.targets) + + +@_braket_noise_gate_to_instruction.register(TwoQubitDepolarizing) +def _(noise): + return Instruction(noises.TwoQubitDepolarizing(noise.probability), target=noise.targets) + + +@_braket_noise_gate_to_instruction.register(TwoQubitDephasing) +def _(noise): + return Instruction(noises.TwoQubitDephasing(noise.probability), target=noise.targets) + + +@_braket_noise_gate_to_instruction.register(AmplitudeDamping) +def _(noise): + return Instruction(noises.AmplitudeDamping(noise.gamma), target=noise.targets) + + +@_braket_noise_gate_to_instruction.register(GeneralizedAmplitudeDamping) +def _(noise): + return Instruction( + noises.GeneralizedAmplitudeDamping(noise.gamma, noise.probability), target=noise.targets + ) + + +@_braket_noise_gate_to_instruction.register(PhaseDamping) +def _(noise): + return Instruction(noises.PhaseDamping(noise.gamma), target=noise.targets) + + +def get_observable(obs: Union[models.Observable, list]) -> Observable: + return _get_observable(obs) + + +@singledispatch +def _get_observable(obs: Union[models.Observable, list]) -> Observable: + raise NotImplementedError + + +@_get_observable.register(list) +def _(obs): + raise NotImplementedError + + +@_get_observable.register(str) +def _(name: str): + return getattr(observables, name.upper())() + + +def get_tensor_product(observable: Union[models.Observable, list]) -> Observable: + """Generate an braket circuit observable + + Args: + observable (Union[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) + + +@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: + return _braket_result_to_result_type(result) + + +@_braket_result_to_result_type.register(Amplitude) +def _(result): + return ResultTypes.Amplitude(state=result.states) + + +@_braket_result_to_result_type.register(Expectation) +def _(result): + 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): + return ResultTypes.Probability(result.targets) + + +@_braket_result_to_result_type.register(Sample) +def _(result): + 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): + return ResultTypes.StateVector() + + +@_braket_result_to_result_type.register(DensityMatrix) +def _(result): + return ResultTypes.DensityMatrix(target=result.targets) + + +@_braket_result_to_result_type.register(Variance) +def _(result): + tensor_product = get_tensor_product(result.observable) + return ResultTypes.Variance(observable=tensor_product, target=result.targets) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 4c536665..6ca3a146 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -31,12 +31,17 @@ compiler_directives, gates, noise, + observables, ) from braket.circuits.serialization import ( IRType, OpenQASMSerializationProperties, QubitReferenceType, ) +from braket.circuits.translations import ( + braket_noise_gate_to_instruction, + braket_result_to_result_type, +) from braket.ir.openqasm import Program as OpenQasmProgram from braket.pulse import DragGaussianWaveform, Frame, GaussianWaveform, Port, PulseSequence @@ -841,6 +846,653 @@ def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): ) +@pytest.mark.parametrize( + "expected_circuit, ir", + [ + ( + Circuit().h(0, control=1, control_state=0), + 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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().cnot(target=0, control=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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().x(0, control=[1], control_state=[0]), + 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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().rx(0, 0.15, control=1, control_state=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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().ry(0, 0.2, control=1, control_state=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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().rz(0, 0.25, control=[1], control_state=[0]), + 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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().s(target=0, control=[1], control_state=[0]), + 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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().t(target=1, control=[0], control_state=[0]), + 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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().cphaseshift(target=0, control=1, angle=0.15), + 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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().ccnot(*[0, 1], target=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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().h(0).state_vector(), + OpenQasmProgram( + 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])", + ] + ), + 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])", + ] + ), + 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])", + ] + ), + 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]", + ] + ), + inputs={}, + ), + ), + ( + Circuit().bit_flip(0, 0.1), + 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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().generalized_amplitude_damping(0, 0.1, 0.1), + 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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().phase_flip(0, 0.2), + 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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().depolarizing(0, 0.5), + 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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().amplitude_damping(0, 0.8), + 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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().phase_damping(0, 0.1), + 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];", + ] + ), + 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"', + ] + ), + inputs={}, + ), + ), + ( + Circuit() + .rx(0, 0.15, control=2, control_state=0) + .rx(1, 0.3, control=[2, 3]) + .cnot(target=0, control=[2, 3, 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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().cnot(0, 1).cnot(target=2, control=3).cnot(target=4, control=[5, 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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().h(0, power=-2.5).h(0, power=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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().unitary(matrix=np.array([[0, 1], [1, 0]]), targets=[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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().pauli_channel(0, probX=0.1, probY=0.2, probZ=0.3), + 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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().two_qubit_depolarizing(0, 1, probability=0.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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().two_qubit_dephasing(0, 1, probability=0.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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().two_qubit_dephasing(0, 1, probability=0.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];", + ] + ), + 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])", + ] + ), + 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])", + ] + ), + 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]", + ] + ), + inputs={}, + ), + ), + ], +) +def test_circuit_from_ir(expected_circuit, ir): + circuit_from_ir = Circuit.from_ir(source=ir.source, inputs=ir.inputs) + + assert circuit_from_ir == expected_circuit + + +@pytest.mark.parametrize( + "expected_circuit, ir", + [ + ( + Circuit().h(0).cnot(0, 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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().h(0).h(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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().h(0).h(1).cnot(0, 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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().h(0).h(1).cnot(0, 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];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().x(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];", + ] + ), + inputs={}, + ), + ), + ], +) +def test_circuit_from_ir_greater_functionality(expected_circuit, ir): + circuit_from_ir = Circuit.from_ir(source=ir.source, inputs=ir.inputs) + + assert circuit_from_ir == expected_circuit + + +def test_braket_result_to_result_type_raises_type_error(): + with pytest.raises(TypeError, match="Result type str is not supported"): + braket_result_to_result_type("type error test") + + +def test_braket_noise_gate_to_instruction_raises_type_error(): + with pytest.raises(TypeError, match="Operation str not supported"): + braket_noise_gate_to_instruction("type error test") + + @pytest.mark.parametrize( "ir_type, serialization_properties, expected_exception, expected_message", [ 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 8a9a73e2..0d67b9f6 100644 --- a/test/unit_tests/braket/parametric/test_free_parameter_expression.py +++ b/test/unit_tests/braket/parametric/test_free_parameter_expression.py @@ -49,11 +49,7 @@ def test_equality_str(): param_values = {"theta": 1} assert expr_1 == expr_2 assert expr_1.subs(param_values) == expr_2.subs(param_values) - assert ( - hasattr(expr_1.expression, "free_symbols") - == hasattr(expr_2.expression, "free_symbols") - == True - ) + assert hasattr(expr_1.expression, "free_symbols") and hasattr(expr_2.expression, "free_symbols") @pytest.mark.xfail(raises=ValueError) From 255d5954fefbd9764c86d4bafb19cd73ea4b5fab Mon Sep 17 00:00:00 2001 From: Sai Prakash Ch <52249348+Sai-prakash15@users.noreply.github.com> Date: Mon, 10 Jul 2023 15:56:47 -0600 Subject: [PATCH 0736/1165] change: update noise operation in program context (#609) --- src/braket/circuits/braket_program_context.py | 40 ++++++++-- src/braket/circuits/noises.py | 6 +- src/braket/circuits/translations.py | 80 +++---------------- .../braket/circuits/test_circuit.py | 33 +++++--- 4 files changed, 73 insertions(+), 86 deletions(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index a21bfbf7..d88bafe7 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -17,12 +17,12 @@ from braket.circuits import Circuit, Instruction from braket.circuits.gates import Unitary +from braket.circuits.noises import Kraus from braket.circuits.translations import ( BRAKET_GATES, - braket_noise_gate_to_instruction, braket_result_to_result_type, + one_prob_noise_map, ) -from braket.default_simulator import KrausOperation from braket.default_simulator.openqasm.program_context import AbstractProgramContext from braket.ir.jaqcd.program_v1 import Results @@ -39,6 +39,7 @@ def __init__(self, circuit: Optional[Circuit] = None): @property def circuit(self) -> Circuit: + """The circuit being built in this context.""" return self._circuit def is_builtin_gate(self, name: str) -> bool: @@ -96,10 +97,37 @@ def add_custom_unitary( instruction = Instruction(Unitary(unitary), target) self._circuit.add_instruction(instruction) - def add_noise_instruction(self, noise: KrausOperation) -> None: - """Add a noise instruction the circuit""" - self._circuit.add_instruction(braket_noise_gate_to_instruction(noise)) + def add_noise_instruction( + self, noise_instruction: str, target: List[int], probabilities: List[float] + ) -> None: + """Method to add a noise instruction to the circuit + + Args: + noise_instruction (str): The name of the noise operation + target (List[int]): The target qubit or qubits to which the noise operation is applied. + probabilities (List[float]): The probabilities associated with each possible outcome + of the noise operation. + """ + instruction = Instruction( + one_prob_noise_map[noise_instruction](*probabilities), target=target + ) + self._circuit.add_instruction(instruction) + + def add_kraus_instruction(self, matrices: List[np.ndarray], target: List[int]) -> None: + """Method to add a Kraus instruction to the circuit + + Args: + matrices (List[ndarray]): The matrices defining the Kraus operation + target (List[int]): The target qubit or qubits to which the Kraus operation is applied. + """ + instruction = Instruction(Kraus(matrices), target) + self._circuit.add_instruction(instruction) def add_result(self, result: Results) -> None: - """Add a result type to the circuit""" + """ + Abstract method to add result type to the circuit + + Args: + result (Results): The result object representing the measurement results + """ self._circuit.add_result_type(braket_result_to_result_type(result)) diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py index 0f9b8c08..75cec6c4 100644 --- a/src/braket/circuits/noises.py +++ b/src/braket/circuits/noises.py @@ -1399,9 +1399,9 @@ def kraus( Iterable[Instruction]: `Iterable` of Kraus instructions. Examples: - >>> K0 = np.eye(4) * sqrt(0.9) - >>> K1 = np.kron([[1., 0.],[0., 1.]], [[0., 1.],[1., 0.]]) * sqrt(0.1) - >>> circ = Circuit().kraus(0, matrices=[K0, K1]) + >>> K0 = np.eye(4) * np.sqrt(0.9) + >>> K1 = np.kron([[1., 0.],[0., 1.]], [[0., 1.],[1., 0.]]) * np.sqrt(0.1) + >>> circ = Circuit().kraus([1, 0], matrices=[K0, K1]) """ if 2 ** len(targets) != matrices[0].shape[0]: raise ValueError( diff --git a/src/braket/circuits/translations.py b/src/braket/circuits/translations.py index d98a0387..ba536594 100644 --- a/src/braket/circuits/translations.py +++ b/src/braket/circuits/translations.py @@ -18,19 +18,7 @@ import braket.circuits.noises as noises import braket.circuits.result_types as ResultTypes import braket.ir.jaqcd.shared_models as models -from braket.circuits import Instruction, Observable, observables -from braket.default_simulator import KrausOperation -from braket.default_simulator.noise_operations import ( - AmplitudeDamping, - BitFlip, - Depolarizing, - GeneralizedAmplitudeDamping, - PauliChannel, - PhaseDamping, - PhaseFlip, - TwoQubitDephasing, - TwoQubitDepolarizing, -) +from braket.circuits import Observable, observables from braket.ir.jaqcd import ( Amplitude, DensityMatrix, @@ -82,61 +70,17 @@ "unitary": braket_gates.Unitary, } - -@singledispatch -def _braket_noise_gate_to_instruction(noise: KrausOperation) -> Union[Instruction]: - raise TypeError(f"Operation {type(noise).__name__} not supported") - - -def braket_noise_gate_to_instruction(noise: KrausOperation) -> Union[Instruction]: - return _braket_noise_gate_to_instruction(noise) - - -@_braket_noise_gate_to_instruction.register(BitFlip) -def _(noise): - return Instruction(noises.BitFlip(noise.probability), target=noise.targets) - - -@_braket_noise_gate_to_instruction.register(PhaseFlip) -def _(noise): - return Instruction(noises.PhaseFlip(noise.probability), target=noise.targets) - - -@_braket_noise_gate_to_instruction.register(PauliChannel) -def _(noise): - return Instruction(noises.PauliChannel(*noise.probabilities), target=noise.targets) - - -@_braket_noise_gate_to_instruction.register(Depolarizing) -def _(noise): - return Instruction(noises.Depolarizing(noise.probability), target=noise.targets) - - -@_braket_noise_gate_to_instruction.register(TwoQubitDepolarizing) -def _(noise): - return Instruction(noises.TwoQubitDepolarizing(noise.probability), target=noise.targets) - - -@_braket_noise_gate_to_instruction.register(TwoQubitDephasing) -def _(noise): - return Instruction(noises.TwoQubitDephasing(noise.probability), target=noise.targets) - - -@_braket_noise_gate_to_instruction.register(AmplitudeDamping) -def _(noise): - return Instruction(noises.AmplitudeDamping(noise.gamma), target=noise.targets) - - -@_braket_noise_gate_to_instruction.register(GeneralizedAmplitudeDamping) -def _(noise): - return Instruction( - noises.GeneralizedAmplitudeDamping(noise.gamma, noise.probability), target=noise.targets - ) - - -@_braket_noise_gate_to_instruction.register(PhaseDamping) -def _(noise): - return Instruction(noises.PhaseDamping(noise.gamma), target=noise.targets) +one_prob_noise_map = { + "bit_flip": noises.BitFlip, + "phase_flip": noises.PhaseFlip, + "pauli_channel": noises.PauliChannel, + "depolarizing": noises.Depolarizing, + "two_qubit_depolarizing": noises.TwoQubitDepolarizing, + "two_qubit_dephasing": noises.TwoQubitDephasing, + "amplitude_damping": noises.AmplitudeDamping, + "generalized_amplitude_damping": noises.GeneralizedAmplitudeDamping, + "phase_damping": noises.PhaseDamping, +} def get_observable(obs: Union[models.Observable, list]) -> Observable: diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 6ca3a146..f24e4a83 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -38,10 +38,7 @@ OpenQASMSerializationProperties, QubitReferenceType, ) -from braket.circuits.translations import ( - braket_noise_gate_to_instruction, - braket_result_to_result_type, -) +from braket.circuits.translations import braket_result_to_result_type from braket.ir.openqasm import Program as OpenQasmProgram from braket.pulse import DragGaussianWaveform, Frame, GaussianWaveform, Port, PulseSequence @@ -1368,6 +1365,29 @@ def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): inputs={}, ), ), + ( + Circuit().kraus( + [0], + matrices=[ + np.array([[0.9486833j, 0], [0, 0.9486833j]]), + np.array([[0, 0.31622777], [0.31622777, 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];", + ] + ), + inputs={}, + ), + ), ], ) def test_circuit_from_ir(expected_circuit, ir): @@ -1488,11 +1508,6 @@ def test_braket_result_to_result_type_raises_type_error(): braket_result_to_result_type("type error test") -def test_braket_noise_gate_to_instruction_raises_type_error(): - with pytest.raises(TypeError, match="Operation str not supported"): - braket_noise_gate_to_instruction("type error test") - - @pytest.mark.parametrize( "ir_type, serialization_properties, expected_exception, expected_message", [ From c70124ed82e5c3bc8bff2adb9a5da31740d79630 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 10 Jul 2023 15:32:48 -0700 Subject: [PATCH 0737/1165] Update Braket dependencies (#611) --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index c71f8cc9..226189ee 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.17.0", - "amazon-braket-default-simulator>=1.15.0", + "amazon-braket-schemas>=1.18.0", + "amazon-braket-default-simulator>=1.18.1", "oqpy~=0.1.1", "setuptools", "backoff", From ae37dc39d424ab221bdb301592d02aff85cacf1a Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 10 Jul 2023 22:48:04 +0000 Subject: [PATCH 0738/1165] prepare release v1.49.0 --- CHANGELOG.md | 12 ++++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b2ba62a..22b68fff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## v1.49.0 (2023-07-10) + +### Features + + * OpenQASM to `Circuit` translator + +### Bug Fixes and Other Changes + + * Update Braket dependencies + * update noise operation in program context + * ci: Harden GitHub Actions + ## v1.48.1 (2023-07-07) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 6644d057..4641492f 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.48.2.dev0" +__version__ = "1.49.0" From a324b7468b9219e8bb299656be8da2f437314315 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 10 Jul 2023 22:48:04 +0000 Subject: [PATCH 0739/1165] update development version to v1.49.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 4641492f..12433d17 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.49.0" +__version__ = "1.49.1.dev0" From f37837e3adacc4a798a78c4730d3103b9e5af805 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Tue, 11 Jul 2023 14:38:06 -0400 Subject: [PATCH 0740/1165] fix: coerce ArbitraryWaveform.amplitudes type (#608) * fix: coerce ArbWF.amplitudes type * fix bare except * remove unnecessary try-except --------- Co-authored-by: Cody Wang --- src/braket/pulse/waveforms.py | 2 +- .../unit_tests/braket/pulse/test_waveforms.py | 22 +++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index 76f7fcf9..f7aeb2d2 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -69,7 +69,7 @@ def __init__(self, amplitudes: List[complex], id: Optional[str] = None): id (Optional[str]): The identifier used for declaring this waveform. A random string of ascii characters is assigned by default. """ - self.amplitudes = amplitudes + self.amplitudes = list(amplitudes) self.id = id or _make_identifier_name() def __eq__(self, other): diff --git a/test/unit_tests/braket/pulse/test_waveforms.py b/test/unit_tests/braket/pulse/test_waveforms.py index 198bcd10..1c8d9e45 100644 --- a/test/unit_tests/braket/pulse/test_waveforms.py +++ b/test/unit_tests/braket/pulse/test_waveforms.py @@ -14,6 +14,8 @@ import re from copy import deepcopy +import numpy as np +import pytest from oqpy import Program from braket.circuits.free_parameter import FreeParameter @@ -21,14 +23,20 @@ from braket.pulse.ast.qasm_parser import ast_to_qasm -def test_arbitrary_waveform(): - amps = [complex(1, 2), complex(0.3, -1), 0, 4.2] +@pytest.mark.parametrize( + "amps", + [ + [complex(1, 2), complex(0.3, -1), 0, 4.2], + np.array([complex(1, 2), complex(0.3, -1), 0, 4.2]), + ], +) +def test_arbitrary_waveform(amps): id = "arb_wf_x" wf = ArbitraryWaveform(amps, id) - assert wf.amplitudes == amps + assert wf.amplitudes == list(amps) assert wf.id == id oq_exp = wf._to_oqpy_expression() - assert oq_exp.init_expression == amps + assert oq_exp.init_expression == list(amps) assert oq_exp.name == wf.id @@ -49,6 +57,12 @@ def test_arbitrary_wf_eq(): assert wf != wfc +def test_arbitrary_waveform_not_castable_into_list(): + amps = 1 + with pytest.raises(TypeError): + wf = ArbitraryWaveform(amps) + + def test_constant_waveform(): length = 4e-3 iq = 4 From 02a35e02bd7664a95b40b839d1c04023232d877d Mon Sep 17 00:00:00 2001 From: Stephen Face <60493521+shpface@users.noreply.github.com> Date: Tue, 11 Jul 2023 14:59:04 -0400 Subject: [PATCH 0741/1165] infra: Update CODEOWNERS (#591) --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 088560a6..2e2d8f10 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,4 +3,4 @@ # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, these accounts # will be requested for review when someone opens a pull request. -* @aws/amazon-braket +* @aws/amazon-braket-maintainers From a2c4c478235fbcdbfb0a8212ecfb3abd73bafb89 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 12 Jul 2023 16:13:06 +0000 Subject: [PATCH 0742/1165] prepare release v1.49.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 22b68fff..4f955195 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.49.1 (2023-07-12) + +### Bug Fixes and Other Changes + + * coerce ArbitraryWaveform.amplitudes type + ## v1.49.0 (2023-07-10) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 12433d17..366ee4bb 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.49.1.dev0" +__version__ = "1.49.1" From 4e535b82b15d5064ca71eb79e5812cd158a41f7e Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 12 Jul 2023 16:13:06 +0000 Subject: [PATCH 0743/1165] update development version to v1.49.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 366ee4bb..165fdee4 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.49.1" +__version__ = "1.49.2.dev0" From 39ef218b44e59ee158fa8d342d6e537659dfbeff Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Fri, 14 Jul 2023 14:48:59 -0700 Subject: [PATCH 0744/1165] =?UTF-8?q?doc:=20update=20aws=5Fquantum=5Fjob.p?= =?UTF-8?q?y=20to=20add=20pattern=20for=20create=20job=5Fname=20pa?= =?UTF-8?q?=E2=80=A6=20(#618)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/braket/aws/aws_quantum_job.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index d5a0341c..a74307e6 100644 --- a/src/braket/aws/aws_quantum_job.py +++ b/src/braket/aws/aws_quantum_job.py @@ -107,6 +107,7 @@ def create( for the containers supported by Braket. Default = ``. job_name (str): A str that specifies the name with which the job is created. + Allowed pattern for job name: `^[a-zA-Z0-9](-*[a-zA-Z0-9]){0,50}$` Default: f'{image_uri_type}-{timestamp}'. code_location (str): The S3 prefix URI where custom code will be uploaded. From d8402fb459fb01a3c72a893cbe11adab176e6247 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Fri, 14 Jul 2023 18:31:12 -0400 Subject: [PATCH 0745/1165] Update local simulator commit (#617) * Update local simulator commit * update simulator commit hash in tox --------- Co-authored-by: Tim --- setup.py | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 38c9f35d..3f3fba7c 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ # to get the version of the simulator that supports the mcm=True argument for Monte Carlo # simulation of mid-circuit measurement, which AutoQASM requires. # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main. - "amazon-braket-default-simulator @ git+https://github.com/aws/amazon-braket-default-simulator-python.git@9b0a2a7c6a9b8a580ddc04f3d1a048dc47fac374#egg=amazon-braket-default-simulator", # noqa E501 + "amazon-braket-default-simulator @ git+https://github.com/aws/amazon-braket-default-simulator-python.git@3d9a746796a46fb9afff540e1446d5e1c7fa222d#egg=amazon-braket-default-simulator", # noqa E501 # Pin the latest commit of the qubit-array branch of ajberdy/oqpy.git to get the version of # oqpy which contains changes that AutoQASM relies on, including the QubitArray type. # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main. diff --git a/tox.ini b/tox.ini index 23ca2799..11c46879 100644 --- a/tox.ini +++ b/tox.ini @@ -95,4 +95,4 @@ commands = deps = # If you need to test on a certain branch, add @ after .git git+https://github.com/aws/amazon-braket-schemas-python.git - git+https://github.com/aws/amazon-braket-default-simulator-python.git + git+https://github.com/aws/amazon-braket-default-simulator-python.git@3d9a746796a46fb9afff540e1446d5e1c7fa222 # mcm-sim branch From afc65e646179f39b0be0dffb5860e3e6ce0d5843 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Mon, 17 Jul 2023 10:41:56 -0400 Subject: [PATCH 0746/1165] fix: support multiprocessing for autoqasm program generation (#606) * Enable multiprocessing for aq.function calls * Add rx gate --- .../experimental/autoqasm/gates/__init__.py | 2 +- .../experimental/autoqasm/gates/gates.py | 10 ++++ .../experimental/autoqasm/program/program.py | 27 ++++++---- .../experimental/autoqasm/test_aq_gates.py | 21 +++++++- .../experimental/autoqasm/test_program.py | 49 +++++++++++++++++++ 5 files changed, 98 insertions(+), 11 deletions(-) diff --git a/src/braket/experimental/autoqasm/gates/__init__.py b/src/braket/experimental/autoqasm/gates/__init__.py index 9e34606c..53ef1de7 100644 --- a/src/braket/experimental/autoqasm/gates/__init__.py +++ b/src/braket/experimental/autoqasm/gates/__init__.py @@ -13,5 +13,5 @@ """Quantum operations for use in AutoQASM programs.""" -from .gates import QubitIdentifier, cnot, h, x # noqa: F401 +from .gates import QubitIdentifier, cnot, h, rx, x # noqa: F401 from .measurements import measure # noqa: F401 diff --git a/src/braket/experimental/autoqasm/gates/gates.py b/src/braket/experimental/autoqasm/gates/gates.py index 60b4906f..375fbae8 100644 --- a/src/braket/experimental/autoqasm/gates/gates.py +++ b/src/braket/experimental/autoqasm/gates/gates.py @@ -51,6 +51,16 @@ def x(q: QubitIdentifier) -> None: oqpy_program.gate(_qubit(q), "x") +def rx(q: QubitIdentifier, angle: float) -> None: + """Adds a rotation around the X axis by `angle` on the specified qubit. + Args: + q (QubitIdentifier): The target qubit. + angle (float): Angle in radians. + """ + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + oqpy_program.gate(_qubit(q), "rx", angle) + + def cnot(q_ctrl: QubitIdentifier, q_target: QubitIdentifier) -> None: """Adds a CNOT gate to the program on the specified qubits. diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index eee6bcfe..31af2ba6 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -23,9 +23,18 @@ from braket.circuits.serialization import IRType from braket.experimental.autoqasm import constants -# Prepare to initialize the global program conversion context. +# Create the thread-local object for the program conversion context. _local = threading.local() -setattr(_local, "program_conversion_context", None) + + +def _get_local() -> threading.local: + """Gets the thread-local object which stores the program conversion context. + Returns: + local: The thread-local object which stores the program conversion context. + """ + if not hasattr(_local, "program_conversion_context"): + setattr(_local, "program_conversion_context", None) + return _local class ProgramOptions(enum.Enum): @@ -188,14 +197,14 @@ def __init__(self, user_config): self.user_config = user_config def __enter__(self) -> ProgramConversionContext: - if not _local.program_conversion_context: - _local.program_conversion_context = ProgramConversionContext(self.user_config) + if not _get_local().program_conversion_context: + _get_local().program_conversion_context = ProgramConversionContext(self.user_config) self.owns_program_conversion_context = True - return _local.program_conversion_context + return _get_local().program_conversion_context def __exit__(self, exc_type, exc_value, exc_tb): if self.owns_program_conversion_context: - _local.program_conversion_context = None + _get_local().program_conversion_context = None def build_program(user_config: Optional[UserConfig] = None) -> ProgramContextManager: @@ -228,7 +237,7 @@ def in_active_program_conversion_context() -> bool: Returns: bool: Whether there is a program currently being built. """ - return _local.program_conversion_context is not None + return _get_local().program_conversion_context is not None def get_program_conversion_context() -> ProgramConversionContext: @@ -241,6 +250,6 @@ def get_program_conversion_context() -> ProgramConversionContext: ProgramConversionContext: The thread-local ProgramConversionContext object. """ assert ( - _local.program_conversion_context is not None + _get_local().program_conversion_context is not None ), "get_program_conversion_context() must be called inside build_program() block" - return _local.program_conversion_context + return _get_local().program_conversion_context diff --git a/test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py b/test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py index 72bfb796..9d7c7999 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py @@ -13,8 +13,10 @@ """Tests for the gates module.""" +import pytest + import braket.experimental.autoqasm as aq -from braket.experimental.autoqasm.gates import cnot, h, measure +from braket.experimental.autoqasm.gates import cnot, h, measure, rx, x def test_bell_state_prep(bell_state_program) -> None: @@ -50,3 +52,20 @@ def test_bell_with_measure() -> None: qasm = program_conversion_context.make_program().to_ir() assert qasm == expected.strip() + + +@pytest.mark.parametrize( + "gate,qubits,params,expected_qasm", + [ + (h, [0], [], "\nh __qubits__[0];"), + (x, [0], [], "\nx __qubits__[0];"), + (rx, [0], [0.1], "\nrx(0.1) __qubits__[0];"), + (cnot, [0, 1], [], "\ncnot __qubits__[0], __qubits__[1];"), + ], +) +def test_gates(gate, qubits, params, expected_qasm) -> None: + """Tests quantum gates.""" + with aq.build_program() as program_conversion_context: + gate(*qubits, *params) + + assert expected_qasm in program_conversion_context.make_program().to_ir() diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/braket/experimental/autoqasm/test_program.py index 16a552ea..714eb3ae 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_program.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_program.py @@ -13,11 +13,15 @@ """Tests for the program module.""" +import itertools +from multiprocessing.pool import ThreadPool + import oqpy.base import pytest import braket.experimental.autoqasm as aq from braket.circuits.serialization import IRType +from braket.experimental.autoqasm.gates import cnot, measure, rx def test_program_conversion_context() -> None: @@ -60,3 +64,48 @@ def test_to_ir() -> None: with pytest.raises(ValueError): prog.to_ir(IRType.JAQCD) + + +def test_multiprocessing() -> None: + """Tests multiprocessing with the aq.Program object.""" + + @aq.function + def circuit(angle: float): + rx(0, angle) + cnot(0, 1) + + @aq.function + def zne(scale: int, angle: float) -> aq.BitVar: + for i in aq.range(scale): + circuit(angle) + return measure(1) + + scales = [2, 4, 6] + angles = [0.1, 0.2, 0.3] + with ThreadPool(processes=5) as executor: + programs = executor.map( + lambda args: zne(*args), + [(scale, angle) for scale, angle in itertools.product(scales, angles)], + ) + + def expected(scale, angle): + return ( + """OPENQASM 3.0; +def circuit(float[64] angle) { + rx(angle) __qubits__[0]; + cnot __qubits__[0], __qubits__[1]; +} +qubit[2] __qubits__; +for int i in [0:""" + + str(scale - 1) + + """] { + circuit(""" + + str(angle) + + """); +} +bit __bit_0__; +__bit_0__ = measure __qubits__[1];""" + ) + + for i, (scale, angle) in enumerate(itertools.product(scales, angles)): + assert programs[i].to_ir() == expected(scale, angle) From f46eec6bcc5c4de36740ab5d1b2a03169629d501 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Mon, 17 Jul 2023 11:09:58 -0400 Subject: [PATCH 0747/1165] Change: update how num_qubits get supplied by autoqasm functions (#612) * Change: update how num_qubits get supplied by autoqasm functions * Update README * Add test for mismatched num_qubits args --- src/braket/experimental/autoqasm/README.md | 4 +- src/braket/experimental/autoqasm/api/api.py | 104 +++++++++++------- src/braket/experimental/autoqasm/errors.py | 26 ++++- .../experimental/autoqasm/program/__init__.py | 1 - .../experimental/autoqasm/program/program.py | 11 -- .../braket/experimental/autoqasm/test_api.py | 58 +++++----- 6 files changed, 112 insertions(+), 92 deletions(-) diff --git a/src/braket/experimental/autoqasm/README.md b/src/braket/experimental/autoqasm/README.md index 8db2dd74..4e7101f4 100644 --- a/src/braket/experimental/autoqasm/README.md +++ b/src/braket/experimental/autoqasm/README.md @@ -67,10 +67,8 @@ def reset(qubit: int, num_repeats: int) -> None: ``` Now that the program takes inputs, you must pass those inputs when you call your function. -In this case, the qubits are indexed by variable, rather than by integer literals, so we -must additionally pass the `num_qubits` keyword argument to AutoQASM. ``` -my_reset_program = reset(qubit=0, num_repeats=3, num_qubits=1) +my_reset_program = reset(qubit=0, num_repeats=3) ``` AutoQASM can support nested subroutines and complex control flow. You can use the Python runtime diff --git a/src/braket/experimental/autoqasm/api/api.py b/src/braket/experimental/autoqasm/api/api.py index 3e6cebfe..4b385d35 100644 --- a/src/braket/experimental/autoqasm/api/api.py +++ b/src/braket/experimental/autoqasm/api/api.py @@ -26,7 +26,6 @@ import braket.experimental.autoqasm.transpiler as aq_transpiler import braket.experimental.autoqasm.types as aq_types from braket.experimental.autoqasm import errors -from braket.experimental.autoqasm.autograph import ag_logging from braket.experimental.autoqasm.autograph.core import ag_ctx, converter from braket.experimental.autoqasm.autograph.impl.api_core import ( autograph_artifact, @@ -35,16 +34,51 @@ from braket.experimental.autoqasm.autograph.tf_utils import tf_decorator -def function(f: Callable) -> Callable[[Any], aq_program.Program]: +def function(*args, num_qubits: Optional[int] = None) -> Callable[[Any], aq_program.Program]: """Decorator that converts a function into a callable that returns a Program object containing the quantum program. The decorator re-converts the target function whenever the decorated function is called, and a new Program object is returned. + Args: + num_qubits (int): Configuration to set the total number of qubits to declare in the program. + + Returns: + Callable[[Any], Program]: A callable which returns the converted + quantum program when called. + """ + # First, we just process the arguments to the decorator function + user_config = aq_program.UserConfig(num_qubits=num_qubits) + + if len(args): + # In this case, num_qubits wasn't supplied. + # Matches the following syntax: + # @aq.function + # def my_func(...): + # Equivalently, `function(my_func, num_qubits=None)` + return _function_without_params(args[0], user_config=user_config) + else: + # In this case, num_qubits was supplied, and we don't know `f` yet. + # Matches the following syntax: + # @aq.function(num_qubits=x) + # def my_func(...): + # Equivalently: `function(num_qubits=x)(my_func)` + def _function_with_params(f): + return _function_without_params(f, user_config=user_config) + + return _function_with_params + + +def _function_without_params( + f: Callable, user_config: aq_program.UserConfig +) -> Callable[[Any], aq_program.Program]: + """Wrapping and conversion logic around the user function `f`. + Args: f (Callable): The target function to be converted which represents the entry point of the quantum program. + user_config (UserConfig): User-specified settings that influence program building Returns: Callable[[Any], Program]: A callable which returns the converted @@ -53,26 +87,11 @@ def function(f: Callable) -> Callable[[Any], aq_program.Program]: if is_autograph_artifact(f): return f - # Update documentation with user configuration - try: - if f.__doc__ is None: - f.__doc__ = "" - f.__doc__ += f""" - -Keyword Args: - {aq_program.ProgramOptions.NUM_QUBITS.value} (int): Configuration to set the total number of - qubits to declare in the program. -""" - except AttributeError as e: - # AttributeError: object attribute '__doc__' is read-only - # Typically occurs when `f` is not a user-defined function. This will likely lead to - # another exception down the line, but it's better simply to warn at this point. - ag_logging.warning(f"Unable to set docstring for converted function. Exception: {e}") - f_wrapper = f decorators, f = tf_decorator.unwrap(f) - wrapper_factory = convert_wrapper( + wrapper_factory = _convert_wrapper( + user_config=user_config, recursive=False, optional_features=( converter.Feature.ASSERT_STATEMENTS, @@ -88,7 +107,8 @@ def function(f: Callable) -> Callable[[Any], aq_program.Program]: return autograph_artifact(wrapper) -def convert_wrapper( +def _convert_wrapper( + user_config: aq_program.UserConfig, recursive: bool = False, optional_features: Optional[Tuple[converter.Feature]] = None, user_requested: bool = True, @@ -98,6 +118,7 @@ def convert_wrapper( that returns a Program object containing the quantum program. Args: + user_config (UserConfig): User-specified settings that influence program building recursive (bool): whether to recursively convert any functions or classes that the converted function may use. Defaults to False. optional_features (Optional[Tuple[Feature]]): allows toggling @@ -118,12 +139,15 @@ def _decorator(f: Callable) -> Callable[[Any], aq_program.Program]: def _wrapper(*args, **kwargs) -> aq_program.Program: """Wrapper that calls the converted version of f.""" + # This code is executed once the decorated function is called + if aq_program.in_active_program_conversion_context(): + _validate_subroutine_args(user_config) options = converter.ConversionOptions( recursive=recursive, user_requested=user_requested, optional_features=optional_features, ) - return _convert(f, conversion_ctx, options, args, kwargs) + return _convert(f, conversion_ctx, options, user_config, args, kwargs) if inspect.isfunction(f) or inspect.ismethod(f): _wrapper = functools.update_wrapper(_wrapper, f) @@ -134,10 +158,28 @@ def _wrapper(*args, **kwargs) -> aq_program.Program: return _decorator +def _validate_subroutine_args(user_config: aq_program.UserConfig) -> None: + """Validate decorator arguments to subroutines. + + Args: + user_config (UserConfig): User-specified settings that influence program building + + Raises: + InconsistentUserConfiguration: If subroutine num_qubits does not match the main + function's num_qubits argument. + """ + if user_config.num_qubits is None: + return + # Allow num_qubits only if the arg matches the value provided to the main function + if user_config.num_qubits != aq_program.get_program_conversion_context().get_declared_qubits(): + raise errors.InconsistentNumQubits() + + def _convert( f: Callable, conversion_ctx: ag_ctx.ControlStatusCtx, options: converter.ConversionOptions, + user_config: aq_program.UserConfig, args: List[Any], kwargs: Dict[str, Any], ) -> aq_program.Program: @@ -150,16 +192,13 @@ def _convert( f (Callable): The function to be converted. conversion_ctx (ControlStatusCtx): the Autograph context in which `f` is used. options (converter.ConversionOptions): Converter options. + user_config (UserConfig): User-specified settings that influence program building args (List[Any]): Arguments passed to the program when called. kwargs (Dict[str, Any]): Keyword arguments passed to the program when called. Returns: Program: The converted program. """ - # User-supplied kwargs need to be processed and removed - # _before_ the program is processed - user_config = _process_user_config(kwargs) - # We will convert this function as a subroutine if we are already inside an # existing conversion process (i.e., this is a subroutine call). convert_as_subroutine = aq_program.in_active_program_conversion_context() @@ -184,21 +223,6 @@ def _convert( return program_conversion_context.make_program() -def _process_user_config(kwargs: Dict[str, Any]) -> aq_program.UserConfig: - """ - Process the user supplied kwargs and return a standardized user config dictionary. - - Args: - kwargs (Dict[str, Any]): Keyword arguments passed to the program when called. - - Returns: - UserConfig: Processed user-config keyword arguments. - """ - return aq_program.UserConfig( - num_qubits=kwargs.pop(aq_program.ProgramOptions.NUM_QUBITS.value, None) - ) - - def _convert_program_as_main( f: Callable, program_conversion_context: aq_program.ProgramConversionContext, diff --git a/src/braket/experimental/autoqasm/errors.py b/src/braket/experimental/autoqasm/errors.py index 9d2f2da5..1357a878 100644 --- a/src/braket/experimental/autoqasm/errors.py +++ b/src/braket/experimental/autoqasm/errors.py @@ -13,8 +13,6 @@ """Errors raised in the AutoQASM build process.""" -from braket.experimental.autoqasm.program import ProgramOptions - class AutoQasmError(Exception): """Base class for all AutoQASM exceptions.""" @@ -24,12 +22,28 @@ class UnknownQubitCountError(AutoQasmError): """Missing declaration for the number of qubits.""" def __init__(self): - self.message = f"""Unspecified number of qubits. + self.message = """Unspecified number of qubits. + +Specify the number of qubits used by your program by supplying the \ +`num_qubits` argument to `autoqasm.function`. For example: -Please declare the total number of qubits for your program. \ -You can do that by calling your AutoQASM program with the \ -{ProgramOptions.NUM_QUBITS.value} keyword argument. \ + @autoqasm.function(num_qubits=5) + def my_autoqasm_function(): + ... """ def __str__(self): return self.message + + +class InconsistentNumQubits(AutoQasmError): + """Num qubits supplied to main function does not match subroutine.""" + + def __init__(self): + self.message = """\ +The number of qubits specified by one of your functions does not match the \ +argument supplied elsewhere. Remove the `num_qubits` argument from nested \ +function calls.""" + + def __str__(self): + return self.message diff --git a/src/braket/experimental/autoqasm/program/__init__.py b/src/braket/experimental/autoqasm/program/__init__.py index 5fdc743d..7b8ed219 100644 --- a/src/braket/experimental/autoqasm/program/__init__.py +++ b/src/braket/experimental/autoqasm/program/__init__.py @@ -19,7 +19,6 @@ from .program import ( # noqa: F401 Program, ProgramConversionContext, - ProgramOptions, UserConfig, build_program, get_program_conversion_context, diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 31af2ba6..26e50211 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -13,7 +13,6 @@ """AutoQASM Program class, context managers, and related functions.""" -import enum import threading from dataclasses import dataclass from typing import List, Optional @@ -37,16 +36,6 @@ def _get_local() -> threading.local: return _local -class ProgramOptions(enum.Enum): - """All options configurable by the user at program invocation time via keyword arguments - injected by the aq.function decorator. - """ - - # Note: this is the exact kwarg name that the user must pass, - # which is later input to UserConfig - NUM_QUBITS = "num_qubits" - - @dataclass class UserConfig: """User-specified configurations that influence program building.""" diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index f16d80e8..206889be 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -112,14 +112,14 @@ def do_h(q: int): h(q) -@aq.function +@aq.function(num_qubits=6) def recursive_h(q: int): do_h(q) if q > 0: recursive_h(q - 1) -@aq.function +@aq.function(num_qubits=6) def recursive_h_wrapper(q: int): recursive_h(q) @@ -137,11 +137,11 @@ def recursive_h(int[32] q) { } qubit[6] __qubits__; recursive_h(5);""" - assert recursive_h_wrapper(5, num_qubits=6).to_ir() == expected + assert recursive_h_wrapper(5).to_ir() == expected def test_sim_recursive_h_wrapper(): - _test_on_local_sim(recursive_h_wrapper(5, num_qubits=6)) + _test_on_local_sim(recursive_h_wrapper(5)) def test_recursive_h(): @@ -158,11 +158,11 @@ def recursive_h(int[32] q) { qubit[6] __qubits__; do_h(5); recursive_h(4);""" - assert recursive_h(5, num_qubits=6).to_ir() == expected + assert recursive_h(5).to_ir() == expected def test_sim_recursive_h(): - _test_on_local_sim(recursive_h(5, num_qubits=6)) + _test_on_local_sim(recursive_h(5)) @aq.function @@ -171,7 +171,7 @@ def bell_state_arbitrary_qubits(q0: int, q1: int) -> None: cnot(q0, q1) -@aq.function +@aq.function(num_qubits=4) def double_bell_state() -> None: bell_state_arbitrary_qubits(0, 1) bell_state_arbitrary_qubits(2, 3) @@ -186,11 +186,11 @@ def bell_state_arbitrary_qubits(int[32] q0, int[32] q1) { qubit[4] __qubits__; bell_state_arbitrary_qubits(0, 1); bell_state_arbitrary_qubits(2, 3);""" - assert double_bell_state(num_qubits=4).to_ir() == expected + assert double_bell_state().to_ir() == expected def test_sim_double_bell() -> None: - _test_on_local_sim(double_bell_state(num_qubits=4)) + _test_on_local_sim(double_bell_state()) @aq.function @@ -294,7 +294,7 @@ def test_bell_measurement_invalid_declared_size() -> None: assert expected_error_message in str(exc_info.value) -@aq.function +@aq.function(num_qubits=5) def ghz_qasm_for_loop() -> None: """A function that generates a GHZ state using a QASM for loop.""" n_qubits = 5 @@ -310,11 +310,11 @@ def test_ghz_qasm_for_loop() -> None: for int i in [0:3] { cnot __qubits__[i], __qubits__[i + 1]; }""" - assert ghz_qasm_for_loop(num_qubits=5).to_ir() == expected + assert ghz_qasm_for_loop().to_ir() == expected def test_sim_ghz_qasm_for_loop() -> None: - _test_on_local_sim(ghz_qasm_for_loop(num_qubits=5)) + _test_on_local_sim(ghz_qasm_for_loop()) @aq.function @@ -521,12 +521,6 @@ def test_physical_qubit_decl(physical_bell_program) -> None: assert "__qubits__" not in qasm -def test_explicit_qubit_decl() -> None: - """Tests the qubit declaration functionality.""" - qasm = ghz_py_for_loop(num_qubits=10).to_ir() - assert "\nqubit[10] __qubits__;" in qasm - - def test_invalid_physical_qubit_fails() -> None: """Tests invalid physical qubit formatting.""" @@ -671,13 +665,6 @@ def qubit_expr() -> None: qubit_expr() -def test_program_with_expr_and_declaration() -> None: - """Test that a program with expressions for the qubit index does not - raise an error if the user explicitly declares the number of qubits. - """ - ghz_qasm_for_loop(num_qubits=8) - - def test_multi_for_loop() -> None: """Test that a program with multiple differing for loops is generated correctly. @@ -691,7 +678,7 @@ def test_multi_for_loop() -> None: h __qubits__[i]; }""" - @aq.function + @aq.function(num_qubits=6) def prog(): for i in aq.range(3): cnot(i, i + 1) @@ -699,7 +686,7 @@ def prog(): for i in aq.range(5): h(i) - assert prog(num_qubits=6).to_ir() == expected + assert prog().to_ir() == expected @aq.function @@ -708,7 +695,7 @@ def bell(q0: int, q1: int) -> None: cnot(q0, q1) -@aq.function +@aq.function(num_qubits=5) def bell_in_for_loop() -> None: for i in aq.range(3): bell(0, 1) @@ -726,7 +713,7 @@ def bell(int[32] q0, int[32] q1) { bell(0, 1); }""" - assert bell_in_for_loop(num_qubits=5).to_ir() == expected + assert bell_in_for_loop().to_ir() == expected def test_classical_variables_types(): @@ -794,5 +781,14 @@ def prog() -> None: assert prog().to_ir() == expected -def test_generated_docstring(bell_state_program) -> None: - assert "num_qubits (int)" in bell_state_program.__doc__ +def test_mismatched_qubits(): + @aq.function(num_qubits=4) + def subroutine() -> None: + a = measure(0) + + @aq.function(num_qubits=8) + def main() -> None: + subroutine() + + with pytest.raises(errors.InconsistentNumQubits): + main() From d321f8afff1fbe7317142e01ae249cf146071b07 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 17 Jul 2023 16:15:04 +0000 Subject: [PATCH 0748/1165] prepare release v1.49.1.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 4f955195..447da078 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.49.1.post0 (2023-07-17) + +### Documentation Changes + + * update aws_quantum_job.py to add pattern for create job_name pa… + ## v1.49.1 (2023-07-12) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 165fdee4..04c6e5dd 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.49.2.dev0" +__version__ = "1.49.1.post0" From b6bc1693f882727cfa6e1c6307362fae0477461b Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 17 Jul 2023 16:15:04 +0000 Subject: [PATCH 0749/1165] update development version to v1.49.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 04c6e5dd..165fdee4 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.49.1.post0" +__version__ = "1.49.2.dev0" From dfd812a7486b7ff5e198e104a95c576e7633e1d3 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Mon, 17 Jul 2023 14:00:51 -0400 Subject: [PATCH 0750/1165] [AutoQASM] Clean up user API (#622) * Clean up user API --- src/braket/experimental/autoqasm/__init__.py | 12 ++--------- .../experimental/autoqasm/{api => }/api.py | 1 + .../experimental/autoqasm/api/__init__.py | 16 --------------- .../experimental/autoqasm/gates/__init__.py | 2 +- .../experimental/autoqasm/gates/gates.py | 20 +++++++++---------- .../autoqasm/gates/measurements.py | 7 ++++--- .../experimental/autoqasm/gates/qubits.py | 2 +- .../experimental/autoqasm/test_operators.py | 2 +- .../experimental/autoqasm/test_program.py | 10 +++++----- 9 files changed, 25 insertions(+), 47 deletions(-) rename src/braket/experimental/autoqasm/{api => }/api.py (99%) delete mode 100644 src/braket/experimental/autoqasm/api/__init__.py diff --git a/src/braket/experimental/autoqasm/__init__.py b/src/braket/experimental/autoqasm/__init__.py index 47af9827..37c7617e 100644 --- a/src/braket/experimental/autoqasm/__init__.py +++ b/src/braket/experimental/autoqasm/__init__.py @@ -42,16 +42,8 @@ def my_program(): from oqpy import ArrayVar, BitVar, BoolVar, FloatVar, IntVar # noqa: F401 -from . import api, constants, gates, operators, types # noqa: F401 from .api import function # noqa: F401 -from .gates import QubitIdentifier # noqa: F401 -from .program import ( # noqa: F401 - Program, - ProgramConversionContext, - Verbatim, - build_program, - get_program_conversion_context, - in_active_program_conversion_context, -) +from .gates import QubitIdentifierType # noqa: F401 +from .program import Program, Verbatim, build_program # noqa: F401 from .transpiler import transpiler # noqa: F401 from .types import qasm_range as range # noqa: F401 diff --git a/src/braket/experimental/autoqasm/api/api.py b/src/braket/experimental/autoqasm/api.py similarity index 99% rename from src/braket/experimental/autoqasm/api/api.py rename to src/braket/experimental/autoqasm/api.py index 4b385d35..e60c6028 100644 --- a/src/braket/experimental/autoqasm/api/api.py +++ b/src/braket/experimental/autoqasm/api.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. +"""This module implements the decorator API for generating programs using AutoQASM.""" import copy import functools diff --git a/src/braket/experimental/autoqasm/api/__init__.py b/src/braket/experimental/autoqasm/api/__init__.py deleted file mode 100644 index 564d4dcc..00000000 --- a/src/braket/experimental/autoqasm/api/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# 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. - -"""This module implements the decorator API for generating programs using AutoQASM.""" - -from .api import function # noqa: F401 diff --git a/src/braket/experimental/autoqasm/gates/__init__.py b/src/braket/experimental/autoqasm/gates/__init__.py index 53ef1de7..5eefa74e 100644 --- a/src/braket/experimental/autoqasm/gates/__init__.py +++ b/src/braket/experimental/autoqasm/gates/__init__.py @@ -13,5 +13,5 @@ """Quantum operations for use in AutoQASM programs.""" -from .gates import QubitIdentifier, cnot, h, rx, x # noqa: F401 +from .gates import QubitIdentifierType, cnot, h, rx, x # noqa: F401 from .measurements import measure # noqa: F401 diff --git a/src/braket/experimental/autoqasm/gates/gates.py b/src/braket/experimental/autoqasm/gates/gates.py index 375fbae8..bfc7f358 100644 --- a/src/braket/experimental/autoqasm/gates/gates.py +++ b/src/braket/experimental/autoqasm/gates/gates.py @@ -28,45 +28,45 @@ def bell(): from braket.experimental.autoqasm import program -from .qubits import QubitIdentifier, _qubit +from .qubits import QubitIdentifierType, _qubit -def h(q: QubitIdentifier) -> None: +def h(q: QubitIdentifierType) -> None: """Adds a Hadamard gate to the program on the specified qubit. Args: - q (QubitIdentifier): The target qubit. + q (QubitIdentifierType): The target qubit. """ oqpy_program = program.get_program_conversion_context().get_oqpy_program() oqpy_program.gate(_qubit(q), "h") -def x(q: QubitIdentifier) -> None: +def x(q: QubitIdentifierType) -> None: """Adds a pi rotation around the X axis on the specified qubit. Args: - q (QubitIdentifier): The target qubit. + q (QubitIdentifierType): The target qubit. """ oqpy_program = program.get_program_conversion_context().get_oqpy_program() oqpy_program.gate(_qubit(q), "x") -def rx(q: QubitIdentifier, angle: float) -> None: +def rx(q: QubitIdentifierType, angle: float) -> None: """Adds a rotation around the X axis by `angle` on the specified qubit. Args: - q (QubitIdentifier): The target qubit. + q (QubitIdentifierType): The target qubit. angle (float): Angle in radians. """ oqpy_program = program.get_program_conversion_context().get_oqpy_program() oqpy_program.gate(_qubit(q), "rx", angle) -def cnot(q_ctrl: QubitIdentifier, q_target: QubitIdentifier) -> None: +def cnot(q_ctrl: QubitIdentifierType, q_target: QubitIdentifierType) -> None: """Adds a CNOT gate to the program on the specified qubits. Args: - q_ctrl (QubitIdentifier): The control qubit. - q_target (QubitIdentifier): The target qubit. + q_ctrl (QubitIdentifierType): The control qubit. + q_target (QubitIdentifierType): The target qubit. """ oqpy_program = program.get_program_conversion_context().get_oqpy_program() oqpy_program.gate([_qubit(q_ctrl), _qubit(q_target)], "cnot") diff --git a/src/braket/experimental/autoqasm/gates/measurements.py b/src/braket/experimental/autoqasm/gates/measurements.py index c97059b8..dda9ef28 100644 --- a/src/braket/experimental/autoqasm/gates/measurements.py +++ b/src/braket/experimental/autoqasm/gates/measurements.py @@ -26,15 +26,16 @@ def my_program(): from typing import List, Union from braket.experimental.autoqasm import BitVar, program -from braket.experimental.autoqasm.gates.qubits import QubitIdentifier, _qubit +from braket.experimental.autoqasm.gates.qubits import QubitIdentifierType, _qubit -def measure(qubits: Union[QubitIdentifier, List[QubitIdentifier]]) -> BitVar: +def measure(qubits: Union[QubitIdentifierType, List[QubitIdentifierType]]) -> BitVar: """Add qubit measurement statements to the program and assign the measurement results to bit variables. Args: - qubits (Union[QubitIdentifier, List[QubitIdentifier]]): The target qubits to measure. + qubits (Union[QubitIdentifierType, List[QubitIdentifierType]]): The target qubits + to measure. Returns: BitVar: Bit variable the measurement results are assigned to. diff --git a/src/braket/experimental/autoqasm/gates/qubits.py b/src/braket/experimental/autoqasm/gates/qubits.py index 416d0f6a..a0ce40f8 100644 --- a/src/braket/experimental/autoqasm/gates/qubits.py +++ b/src/braket/experimental/autoqasm/gates/qubits.py @@ -22,7 +22,7 @@ from braket.experimental.autoqasm import constants, errors, program -QubitIdentifier = Union[int, oqpy._ClassicalVar, oqpy.base.OQPyExpression, str] +QubitIdentifierType = Union[int, oqpy._ClassicalVar, oqpy.base.OQPyExpression, str] def _global_qubit_register(qubit_idx_expr: Union[int, str]) -> str: diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py index 1cc28925..ffaa81cf 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_operators.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py @@ -81,7 +81,7 @@ def test_conditional_expressions_py_cond(if_true: dict, if_false: dict) -> None: assert if_false["qasm"] not in qasm -def for_body(i: aq.QubitIdentifier) -> None: +def for_body(i: aq.QubitIdentifierType) -> None: h(i) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/braket/experimental/autoqasm/test_program.py index 714eb3ae..6a5f17d9 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_program.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_program.py @@ -26,7 +26,7 @@ def test_program_conversion_context() -> None: """Tests the ProgramConversionContext class.""" - prog = aq.ProgramConversionContext() + prog = aq.program.ProgramConversionContext() initial_oqpy_program = prog.get_oqpy_program() assert len(prog.oqpy_program_stack) == 1 @@ -42,16 +42,16 @@ def test_program_conversion_context() -> None: def test_build_program() -> None: """Tests the aq.build_program function.""" with pytest.raises(AssertionError): - aq.get_program_conversion_context() + aq.program.get_program_conversion_context() with aq.build_program() as program_conversion_context: - assert aq.get_program_conversion_context() == program_conversion_context + assert aq.program.get_program_conversion_context() == program_conversion_context with aq.build_program() as inner_context: assert inner_context is program_conversion_context - assert aq.get_program_conversion_context() == inner_context + assert aq.program.get_program_conversion_context() == inner_context with pytest.raises(AssertionError): - aq.get_program_conversion_context() + aq.program.get_program_conversion_context() def test_to_ir() -> None: From d4178f02aabe494dde5bdfc5ca12700dcbe39bfe Mon Sep 17 00:00:00 2001 From: kvathupo Date: Tue, 18 Jul 2023 15:02:02 -0400 Subject: [PATCH 0751/1165] feat: Add dot(), power(), and to_circuit() to PauliString (#574) --- .../quantum_information/pauli_string.py | 183 ++++++++++++++++++ .../quantum_information/test_pauli_string.py | 123 ++++++++++++ 2 files changed, 306 insertions(+) diff --git a/src/braket/quantum_information/pauli_string.py b/src/braket/quantum_information/pauli_string.py index 2c9e7ea0..1f145449 100644 --- a/src/braket/quantum_information/pauli_string.py +++ b/src/braket/quantum_information/pauli_string.py @@ -24,6 +24,11 @@ _PAULI_Y = "Y" _PAULI_Z = "Z" _PAULI_INDICES = {_IDENTITY: 0, _PAULI_X: 1, _PAULI_Y: 2, _PAULI_Z: 3} +_PRODUCT_MAP = { + "X": {"Y": ["Z", 1j], "Z": ["Y", -1j]}, + "Y": {"X": ["Z", -1j], "Z": ["X", 1j]}, + "Z": {"X": ["Y", 1j], "Y": ["X", -1j]}, +} _PAULI_OBSERVABLES = {_PAULI_X: X(), _PAULI_Y: Y(), _PAULI_Z: Z()} _SIGN_MAP = {"+": 1, "-": -1} @@ -142,6 +147,184 @@ def eigenstate(self, signs: Optional[Union[str, List[int], Tuple[int, ...]]] = N raise ValueError(f"signs must be +/-1, got {signs}") return self._generate_eigenstate_circuit(signs_tup) + def dot(self, other: PauliString, inplace: bool = False) -> PauliString: + """Right multiplies this Pauli string with the argument. + + Returns the result of multiplying the current circuit by the argument on its right. For + example, if called on `-XYZ` with argument `ZYX`, then `YIY` is the result. In-place + computation is off by default. + + Args: + other (PauliString): The right multiplicand. + inplace (bool): If `True`, `self` is updated to hold the product. + + Returns: + PauliString: The resultant circuit from right multiplying `self` with `other`. + + Raises: + ValueError: If the lengths of the Pauli strings being multiplied differ. + """ + if self._qubit_count != other._qubit_count: + raise ValueError( + f"Input Pauli string must be of length ({self._qubit_count}), " + f"not {other._qubit_count}" + ) + pauli_result = "" + phase_result = self._phase * other._phase + for i in range(self._qubit_count): + # Are either identity? + if i not in self._nontrivial and i not in other._nontrivial: + pauli_result += "I" + elif i not in self._nontrivial: + pauli_result += other._nontrivial[i] + elif i not in other._nontrivial: + pauli_result += self._nontrivial[i] + elif self._nontrivial[i] == other._nontrivial[i]: + pauli_result += "I" + else: + gate, phase = _PRODUCT_MAP[self._nontrivial[i]][other._nontrivial[i]] + pauli_result += gate + phase_result *= phase + + # ignore complex global phase + if phase_result.real < 0 or phase_result.imag < 0: + pauli_result = "-" + pauli_result + out_pauli_string = PauliString(pauli_result) + + if inplace: + self._phase = out_pauli_string._phase + self._qubit_count = out_pauli_string._qubit_count + self._nontrivial = out_pauli_string._nontrivial + return out_pauli_string + + def __mul__(self, other: PauliString) -> PauliString: + """Right multiplication operator overload using `dot()`. + + Returns the result of multiplying the current circuit by the argument on its right. + + Args: + other (PauliString): The right multiplicand. + + Returns: + PauliString: The resultant circuit from right multiplying `self` with `other`. + + Raises: + ValueError: If the lengths of the Pauli strings being multiplied differ. + + See Also: + `braket.quantum_information.PauliString.dot()` + """ + return self.dot(other) + + def __imul__(self, other: PauliString) -> PauliString: + """Operator overload for right-multiplication assignment (`*=`) using `dot()`. + + Right-multiplies `self` by `other`, and assigns the result to `self`. + + Args: + other (PauliString): The right multiplicand. + + Returns: + PauliString: The resultant circuit from right multiplying `self` with `other`. + + Raises: + ValueError: If the lengths of the Pauli strings being multiplied differ. + + See Also: + `braket.quantum_information.PauliString.dot()` + """ + return self.dot(other, inplace=True) + + def power(self, n: int, inplace: bool = False) -> PauliString: + """Composes Pauli string with itself n times. + + Args: + n (int): The number of times to self-multiply. Can be any integer value. + inplace (bool): Update `self` if `True` + + Returns: + PauliString: If `n` is positive, result from self-multiplication `n` times. + If zero, identity. If negative, self-multiplication from trivial + inverse (recall Pauli operators are involutory). + + Raises: + ValueError: If `n` isn't a plain Python `int`. + """ + if not isinstance(n, int): + raise ValueError("Must be raised to integer power") + + # Since pauli ops involutory, result is either identity or unchanged + pauli_other = PauliString(self) + if n % 2 == 0: + pauli_other._phase = 1 + pauli_other._nontrivial = {} + + if inplace: + self._phase = pauli_other._phase + self._qubit_count = pauli_other._qubit_count + self._nontrivial = pauli_other._nontrivial + return pauli_other + + def __pow__(self, n: int) -> PauliString: + """Pow operator overload for Pauli string composition. + + Syntactic sugar for `power()`. + + Args: + n (int): The number of times to self-multiply. Can be any integer + + Returns: + PauliString: If `n` is positive, result from self-multiplication `n` times. + If zero, identity. If negative, self-multiplication from trivial + inverse (recall Pauli operators are involutory). + + Raises: + ValueError: If `n` isn't a plain Python `int`. + + See Also: + `braket.quantum_information.PauliString.power()` + """ + return self.power(n) + + def __ipow__(self, n: int) -> PauliString: + """Operator overload for in-place pow assignment (`**=`) using `power()`. + + Syntactic sugar for in-place `power()`. + + Args: + n (int): The number of times to self-multiply. Can be any integer + + Returns: + PauliString: If `n` is positive, result from self-multiplication `n` times. + If zero, identity. If negative, self-multiplication from trivial + inverse (recall Pauli operators are involutory). + + Raises: + ValueError: If `n` isn't a plain Python `int`. + + See Also: + `braket.quantum_information.PauliString.power()` + """ + return self.power(n, inplace=True) + + def to_circuit(self) -> Circuit: + """Returns circuit represented by this `PauliString`. + + Returns: + Circuit: The circuit for this `PauliString`. + """ + circ = Circuit() + for qubit in range(self._qubit_count): + if qubit not in self._nontrivial: + circ = circ.i(qubit) + elif self._nontrivial[qubit] == "X": + circ = circ.x(qubit) + elif self._nontrivial[qubit] == "Y": + circ = circ.y(qubit) + else: + circ = circ.z(qubit) + return circ + def __eq__(self, other): if isinstance(other, PauliString): return ( 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 4876bbaf..82e3b069 100644 --- a/test/unit_tests/braket/quantum_information/test_pauli_string.py +++ b/test/unit_tests/braket/quantum_information/test_pauli_string.py @@ -19,6 +19,7 @@ import pytest from braket.circuits import gates +from braket.circuits.circuit import Circuit from braket.circuits.observables import X, Y, Z from braket.quantum_information import PauliString @@ -135,3 +136,125 @@ def test_eigenstate(string, signs): @pytest.mark.parametrize("sign", ["+ab", "--+-", [+2, -1, -1], (-1, 1j, 1)]) def test_eigenstate_invalid_signs(sign): PauliString("XYZ").eigenstate(sign) + + +@pytest.mark.parametrize( + "circ_arg_1, circ_arg_2, circ_res", + [ + ("III", "+III", "III"), + ("Z", "I", "Z"), + ("I", "-Y", "-Y"), + ("XYXZY", "+XYXZY", "IIIII"), + ("XYZ", "ZYX", "YIY"), + ("YZ", "ZX", "-XY"), + ("-Z", "Y", "X"), + ("Z", "Y", "-X"), + ], +) +def test_dot(circ_arg_1, circ_arg_2, circ_res): + circ1 = PauliString(circ_arg_1) + circ2 = circ1.dot(PauliString(circ_arg_2)) + assert circ2 == PauliString(circ_res) + assert circ1 == circ1 + + # Test in-place computation + circ1 = PauliString(circ_arg_1) + circ1.dot(PauliString(circ_arg_2), inplace=True) + assert circ1 == PauliString(circ_res) + + # Test operator overloads + circ1 = PauliString(circ_arg_1) + circ2 = PauliString(circ_arg_2) + assert circ1 * circ2 == PauliString(circ_res) + circ1 *= circ2 + assert circ1 == PauliString(circ_res) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize( + "circ1, circ2, operation", + [ + (PauliString("III"), PauliString("II"), "dot()"), + (PauliString("III"), PauliString("II"), "*"), + (PauliString("III"), PauliString("II"), "*="), + (PauliString("IXI"), PauliString("II"), "dot()"), + (PauliString("IXI"), PauliString("II"), "*"), + (PauliString("IXI"), PauliString("II"), "*="), + ], +) +def test_dot_unequal_lengths(circ1, circ2, operation): + if operation == "dot()": + circ1.dot(circ2) + elif operation == "*": + circ1 * circ2 + elif operation == "*=": + circ1 *= circ2 + + +@pytest.mark.parametrize( + "circ, n, circ_res", + [ + ("-X", 1, "-X"), + ("Y", 2, "I"), + ("Y", -2, "I"), + ("-X", 3, "-X"), + ("XYZ", 5, "XYZ"), + ("XYZ", -5, "XYZ"), + ("XYZ", 6, "III"), + ("XYZ", 1, "XYZ"), + ("-YX", 0, "II"), + ("Y", 0, "I"), + ], +) +def test_power(circ, n, circ_res): + circ1 = PauliString(circ) + circ2 = circ1.power(n) + assert circ2 == PauliString(circ_res) + assert circ1 == PauliString(circ) + + # Test in-place computation + circ1.power(n, inplace=True) + assert circ1 == PauliString(circ_res) + + # Test operator overloads + circ1 = PauliString(circ) + assert (circ1**n) == PauliString(circ_res) + circ1 **= n + assert circ1 == PauliString(circ_res) + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize( + "circ, n, operation", + [ + (PauliString("XYZ"), "-1.2", "power()"), + (PauliString("XYZ"), "-1.2", "**"), + (PauliString("XYZ"), "-1.2", "**="), + (PauliString("ZYX"), "1.2", "power()"), + (PauliString("ZYX"), "1.2", "**"), + (PauliString("ZYX"), "1.2", "**="), + (PauliString("I"), "3j", "power()"), + (PauliString("I"), "3j", "**"), + (PauliString("I"), "3j", "**="), + ], +) +def test_power_invalid_exp(circ, n, operation): + if operation == "power()": + circ.power(n) + elif operation == "**": + circ**n + elif operation == "**=": + circ **= n + + +@pytest.mark.parametrize( + "circ, circ_res", + [ + (PauliString("I"), Circuit().i(0)), + (PauliString("-X"), Circuit().x(0)), + (PauliString("IYX"), Circuit().i(0).y(1).x(2)), + (PauliString("ZIIIX"), Circuit().z(0).i(1).i(2).i(3).x(4)), + ], +) +def test_to_circuit(circ, circ_res): + assert circ.to_circuit() == circ_res From 48452901eb1ade1e97696c89e2f914583b5c774c Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Tue, 18 Jul 2023 17:02:13 -0400 Subject: [PATCH 0752/1165] Add AutoQASM example notebooks (#610) * add gates for notebook examples * autoqasm example notebooks * update example in readme * update notebooks and readme * minor change in mentioning qubit number * update text * update text for merge and for comments --- .../1_Getting_started_with_AutoQASM.ipynb | 173 +++++++++++ .../2_Expressing_classical_control_flow.ipynb | 281 ++++++++++++++++++ .../3_Iterative_phase_estimation.ipynb | 225 ++++++++++++++ src/braket/experimental/autoqasm/README.md | 28 +- .../experimental/autoqasm/gates/__init__.py | 2 +- .../experimental/autoqasm/gates/gates.py | 53 ++++ .../experimental/autoqasm/test_aq_gates.py | 9 +- 7 files changed, 755 insertions(+), 16 deletions(-) create mode 100644 examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb create mode 100644 examples/autoqasm/2_Expressing_classical_control_flow.ipynb create mode 100644 examples/autoqasm/3_Iterative_phase_estimation.ipynb diff --git a/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb b/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb new file mode 100644 index 00000000..e8dbac3d --- /dev/null +++ b/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb @@ -0,0 +1,173 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "135014b8", + "metadata": {}, + "source": [ + "# Getting started with AutoQASM\n", + "In this notebook, we demonstrate how to write and execute a quantum program with AutoQASM, using Bell state preparation as an example." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "15cdfaee", + "metadata": {}, + "outputs": [], + "source": [ + "# general imports\n", + "from collections import Counter\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# AWS imports: Import Braket SDK modules\n", + "from braket.devices.local_simulator import LocalSimulator\n", + "import braket.experimental.autoqasm as aq\n", + "from braket.experimental.autoqasm.gates import measure, h, cnot" + ] + }, + { + "cell_type": "markdown", + "id": "d2d7004f", + "metadata": {}, + "source": [ + "## Build and run a Bell state program\n", + "As a hello-world example, we create a Bell state and execute it on the Braket local simulator. The quantum program is defined in the `bell_state` function. The `@aq.function` decorator marks the `bell_state` function as a quantum program and enables AutoQASM syntax in the function scope." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2fa589ad", + "metadata": {}, + "outputs": [], + "source": [ + "@aq.function\n", + "def bell_state():\n", + " h(0)\n", + " cnot(0, 1)\n", + " c = measure([0, 1])" + ] + }, + { + "cell_type": "markdown", + "id": "2c59985b", + "metadata": {}, + "source": [ + "The quantum program is returned by calling the decorated `bell_state` function. You can view the generated OpenQASM script with the `to_ir()` method. This can help you debug if you are already familiar with OpenQASM." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "19467aa5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "qubit[2] __qubits__;\n", + "h __qubits__[0];\n", + "cnot __qubits__[0], __qubits__[1];\n", + "bit[2] __bit_0__;\n", + "__bit_0__[0] = measure __qubits__[0];\n", + "__bit_0__[1] = measure __qubits__[1];\n", + "bit[2] c;\n", + "c = __bit_0__;\n" + ] + } + ], + "source": [ + "bell_state_program = bell_state()\n", + "print(bell_state_program.to_ir())" + ] + }, + { + "cell_type": "markdown", + "id": "756d0587", + "metadata": {}, + "source": [ + "Here we submit the quantum program to the local simulator and obtain the results." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "57bf2546", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "measurement counts: Counter({'00': 51, '11': 49})\n" + ] + } + ], + "source": [ + "device = LocalSimulator()\n", + "result = device.run(bell_state_program, shots=100).result()\n", + "counts = Counter(result.measurements[\"c\"])\n", + "print(\"measurement counts: \", counts)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "7fc6f4a6", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjIAAAGwCAYAAACzXI8XAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAgqklEQVR4nO3de3BU9d3H8c+G3IBkNyaEhJhEUJGACLRBYNEqptGUUiomVUqZARGhYqDCesGMAqIwkU41KA1gLQbbMV5oC5Z2imiE2GLCJYLXkqJFk06yQdAkEMhFss8fHXa6DwTCsuHsL75fM2eGc87u2S+Zib455+yuzePxeAQAAGCgEKsHAAAA8BchAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjhVo9QFdrb29XTU2NoqOjZbPZrB4HAAB0gsfj0dGjR5WUlKSQkI7Pu3T7kKmpqVFKSorVYwAAAD9UV1crOTm5w/3dPmSio6Ml/fcHYbfbLZ4GAAB0RmNjo1JSUrz/H+9Itw+ZU5eT7HY7IQMAgGHOdVsIN/sCAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADBWqNUDmKz/w3+1egQgqH3+5ASrRwDQzXFGBgAAGIuQAQAAxrI0ZB577DHZbDafJS0tzbu/ublZubm5iouLU1RUlHJyclRXV2fhxAAAIJhYfkbm6quvVm1trXf5xz/+4d23YMECbd68WRs2bFBpaalqamqUnZ1t4bQAACCYWH6zb2hoqBITE0/b3tDQoHXr1qm4uFgZGRmSpKKiIg0ePFjl5eUaM2bMxR4VAAAEGcvPyBw4cEBJSUm6/PLLNXXqVFVVVUmSKioq1NbWpszMTO9j09LSlJqaqrKysg6P19LSosbGRp8FAAB0T5aGzOjRo7V+/Xpt2bJFa9as0cGDB/W9731PR48eldvtVnh4uGJiYnyek5CQILfb3eEx8/Pz5XA4vEtKSkoX/y0AAIBVLL20NH78eO+fhw0bptGjR+uyyy7Ta6+9pp49e/p1zLy8PLlcLu96Y2MjMQMAQDdl+aWl/xUTE6OrrrpKn376qRITE9Xa2qr6+nqfx9TV1Z3xnppTIiIiZLfbfRYAANA9BVXIHDt2TJ999pn69eun9PR0hYWFqaSkxLu/srJSVVVVcjqdFk4JAACChaWXlh544AFNnDhRl112mWpqarRkyRL16NFDU6ZMkcPh0MyZM+VyuRQbGyu73a558+bJ6XTyjiUAACDJ4pD5z3/+oylTpujIkSOKj4/X9ddfr/LycsXHx0uSCgoKFBISopycHLW0tCgrK0urV6+2cmQAABBEbB6Px2P1EF2psbFRDodDDQ0NAb9fhi+NBM6OL40E4K/O/v/b8g/EA4Bgxz9agI5Z/Q+WoLrZFwAA4HwQMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjBU3IPPnkk7LZbJo/f753W3Nzs3JzcxUXF6eoqCjl5OSorq7OuiEBAEBQCYqQ2b17t5577jkNGzbMZ/uCBQu0efNmbdiwQaWlpaqpqVF2drZFUwIAgGBjecgcO3ZMU6dO1fPPP69LLrnEu72hoUHr1q3T008/rYyMDKWnp6uoqEjvvvuuysvLLZwYAAAEC8tDJjc3VxMmTFBmZqbP9oqKCrW1tflsT0tLU2pqqsrKyjo8XktLixobG30WAADQPYVa+eKvvPKK3nvvPe3evfu0fW63W+Hh4YqJifHZnpCQILfb3eEx8/PztXTp0kCPCgAAgpBlZ2Sqq6t133336aWXXlJkZGTAjpuXl6eGhgbvUl1dHbBjAwCA4GJZyFRUVOjQoUP67ne/q9DQUIWGhqq0tFTPPvusQkNDlZCQoNbWVtXX1/s8r66uTomJiR0eNyIiQna73WcBAADdk2WXlr7//e/rww8/9Nk2Y8YMpaWlaeHChUpJSVFYWJhKSkqUk5MjSaqsrFRVVZWcTqcVIwMAgCBjWchER0dr6NChPtt69+6tuLg47/aZM2fK5XIpNjZWdrtd8+bNk9Pp1JgxY6wYGQAABBlLb/Y9l4KCAoWEhCgnJ0ctLS3KysrS6tWrrR4LAAAEiaAKme3bt/usR0ZGqrCwUIWFhdYMBAAAgprlnyMDAADgL0IGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGMvSkFmzZo2GDRsmu90uu90up9Opv/3tb979zc3Nys3NVVxcnKKiopSTk6O6ujoLJwYAAMHE0pBJTk7Wk08+qYqKCu3Zs0cZGRm69dZb9fHHH0uSFixYoM2bN2vDhg0qLS1VTU2NsrOzrRwZAAAEkVArX3zixIk+68uXL9eaNWtUXl6u5ORkrVu3TsXFxcrIyJAkFRUVafDgwSovL9eYMWOsGBkAAASRoLlH5uTJk3rllVfU1NQkp9OpiooKtbW1KTMz0/uYtLQ0paamqqysrMPjtLS0qLGx0WcBAADdk+Uh8+GHHyoqKkoRERG65557tHHjRg0ZMkRut1vh4eGKiYnxeXxCQoLcbneHx8vPz5fD4fAuKSkpXfw3AAAAVrE8ZAYNGqR9+/Zp586dmjNnjqZPn65PPvnE7+Pl5eWpoaHBu1RXVwdwWgAAEEz8ukemurpaNptNycnJkqRdu3apuLhYQ4YM0ezZs8/rWOHh4bryyislSenp6dq9e7eeeeYZTZ48Wa2traqvr/c5K1NXV6fExMQOjxcREaGIiIjz/0sBAADj+HVG5mc/+5m2bdsmSXK73br55pu1a9cuPfLII3r88ccvaKD29na1tLQoPT1dYWFhKikp8e6rrKxUVVWVnE7nBb0GAADoHvw6I/PRRx9p1KhRkqTXXntNQ4cO1Y4dO7R161bdc889Wrx4caeOk5eXp/Hjxys1NVVHjx5VcXGxtm/frjfeeEMOh0MzZ86Uy+VSbGys7Ha75s2bJ6fTyTuWAACAJD9Dpq2tzXv55q233tKPf/xjSf99V1FtbW2nj3Po0CFNmzZNtbW1cjgcGjZsmN544w3dfPPNkqSCggKFhIQoJydHLS0tysrK0urVq/0ZGQAAdEN+hczVV1+ttWvXasKECXrzzTf1xBNPSJJqamoUFxfX6eOsW7furPsjIyNVWFiowsJCf8YEAADdnF/3yKxYsULPPfecxo0bpylTpmj48OGSpD//+c/eS04AAABdza8zMuPGjdPhw4fV2NioSy65xLt99uzZ6t27d8CGAwAAOBu/zshkZGTo6NGjPhEjSbGxsZo8eXJABgMAADgXv0Jm+/btam1tPW17c3Oz/v73v1/wUAAAAJ1xXpeWPvjgA++fP/nkE5+vCjh58qS2bNmiSy+9NHDTAQAAnMV5hcyIESNks9lks9m830j9v3r27KlVq1YFbDgAAICzOa+QOXjwoDwejy6//HLt2rVL8fHx3n3h4eHq27evevToEfAhAQAAzuS8Quayyy6T9N+vEQAAALCaX2+/lqQDBw5o27ZtOnTo0Glh09mvKAAAALgQfoXM888/rzlz5qhPnz5KTEyUzWbz7rPZbIQMAAC4KPwKmWXLlmn58uVauHBhoOcBAADoNL8+R+brr7/W7bffHuhZAAAAzotfIXP77bdr69atgZ4FAADgvPh1aenKK6/UokWLVF5ermuuuUZhYWE++3/xi18EZDgAAICz8StkfvOb3ygqKkqlpaUqLS312Wez2QgZAABwUfgVMgcPHgz0HAAAAOfNr3tkAAAAgoFfZ2Tuuuuus+5/4YUX/BoGAADgfPgVMl9//bXPeltbmz766CPV19ef8cskAQAAuoJfIbNx48bTtrW3t2vOnDm64oorLngoAACAzgjYPTIhISFyuVwqKCgI1CEBAADOKqA3+3722Wf65ptvAnlIAACADvl1acnlcvmsezwe1dbW6q9//aumT58ekMEAAADOxa+Q2bt3r896SEiI4uPj9dRTT53zHU0AAACB4lfIbNu2LdBzAAAAnDe/QuaUL7/8UpWVlZKkQYMGKT4+PiBDAQAAdIZfN/s2NTXprrvuUr9+/XTDDTfohhtuUFJSkmbOnKnjx48HekYAAIAz8itkXC6XSktLtXnzZtXX16u+vl6vv/66SktLdf/99wd6RgAAgDPy69LSH//4R/3hD3/QuHHjvNt++MMfqmfPnrrjjju0Zs2aQM0HAADQIb/OyBw/flwJCQmnbe/bty+XlgAAwEXjV8g4nU4tWbJEzc3N3m0nTpzQ0qVL5XQ6AzYcAADA2fh1aWnlypX6wQ9+oOTkZA0fPlyS9P777ysiIkJbt24N6IAAAAAd8StkrrnmGh04cEAvvfSS9u/fL0maMmWKpk6dqp49ewZ0QAAAgI74FTL5+flKSEjQrFmzfLa/8MIL+vLLL7Vw4cKADAcAAHA2ft0j89xzzyktLe207VdffbXWrl17wUMBAAB0hl8h43a71a9fv9O2x8fHq7a29oKHAgAA6Ay/QiYlJUU7duw4bfuOHTuUlJR0wUMBAAB0hl/3yMyaNUvz589XW1ubMjIyJEklJSV66KGH+GRfAABw0fgVMg8++KCOHDmie++9V62trZKkyMhILVy4UHl5eQEdEAAAoCN+hYzNZtOKFSu0aNEi/fOf/1TPnj01cOBARUREBHo+AACADvkVMqdERUXp2muvDdQsAAAA58Wvm30BAACCASEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADCWpSGTn5+va6+9VtHR0erbt68mTZqkyspKn8c0NzcrNzdXcXFxioqKUk5Ojurq6iyaGAAABBNLQ6a0tFS5ubkqLy/Xm2++qba2Nt1yyy1qamryPmbBggXavHmzNmzYoNLSUtXU1Cg7O9vCqQEAQLAItfLFt2zZ4rO+fv169e3bVxUVFbrhhhvU0NCgdevWqbi4WBkZGZKkoqIiDR48WOXl5RozZowVYwMAgCARVPfINDQ0SJJiY2MlSRUVFWpra1NmZqb3MWlpaUpNTVVZWdkZj9HS0qLGxkafBQAAdE9BEzLt7e2aP3++rrvuOg0dOlSS5Ha7FR4erpiYGJ/HJiQkyO12n/E4+fn5cjgc3iUlJaWrRwcAABYJmpDJzc3VRx99pFdeeeWCjpOXl6eGhgbvUl1dHaAJAQBAsLH0HplT5s6dq7/85S965513lJyc7N2emJio1tZW1dfX+5yVqaurU2Ji4hmPFRERoYiIiK4eGQAABAFLz8h4PB7NnTtXGzdu1Ntvv60BAwb47E9PT1dYWJhKSkq82yorK1VVVSWn03mxxwUAAEHG0jMyubm5Ki4u1uuvv67o6GjvfS8Oh0M9e/aUw+HQzJkz5XK5FBsbK7vdrnnz5snpdPKOJQAAYG3IrFmzRpI0btw4n+1FRUW68847JUkFBQUKCQlRTk6OWlpalJWVpdWrV1/kSQEAQDCyNGQ8Hs85HxMZGanCwkIVFhZehIkAAIBJguZdSwAAAOeLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGsjRk3nnnHU2cOFFJSUmy2WzatGmTz36Px6PFixerX79+6tmzpzIzM3XgwAFrhgUAAEHH0pBpamrS8OHDVVhYeMb9v/zlL/Xss89q7dq12rlzp3r37q2srCw1Nzdf5EkBAEAwCrXyxcePH6/x48efcZ/H49HKlSv16KOP6tZbb5Uk/e53v1NCQoI2bdqkn/70pxdzVAAAEISC9h6ZgwcPyu12KzMz07vN4XBo9OjRKisr6/B5LS0tamxs9FkAAED3FLQh43a7JUkJCQk+2xMSErz7ziQ/P18Oh8O7pKSkdOmcAADAOkEbMv7Ky8tTQ0ODd6murrZ6JAAA0EWCNmQSExMlSXV1dT7b6+rqvPvOJCIiQna73WcBAADdU9CGzIABA5SYmKiSkhLvtsbGRu3cuVNOp9PCyQAAQLCw9F1Lx44d06effupdP3jwoPbt26fY2FilpqZq/vz5WrZsmQYOHKgBAwZo0aJFSkpK0qRJk6wbGgAABA1LQ2bPnj266aabvOsul0uSNH36dK1fv14PPfSQmpqaNHv2bNXX1+v666/Xli1bFBkZadXIAAAgiFgaMuPGjZPH4+lwv81m0+OPP67HH3/8Ik4FAABMEbT3yAAAAJwLIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMZUTIFBYWqn///oqMjNTo0aO1a9cuq0cCAABBIOhD5tVXX5XL5dKSJUv03nvvafjw4crKytKhQ4esHg0AAFgs6EPm6aef1qxZszRjxgwNGTJEa9euVa9evfTCCy9YPRoAALBYqNUDnE1ra6sqKiqUl5fn3RYSEqLMzEyVlZWd8TktLS1qaWnxrjc0NEiSGhsbAz5fe8vxgB8T6E664vfOCvyuAx3rqt/zU8f1eDxnfVxQh8zhw4d18uRJJSQk+GxPSEjQ/v37z/ic/Px8LV269LTtKSkpXTIjgI45Vlo9AYCu1tW/50ePHpXD4ehwf1CHjD/y8vLkcrm86+3t7frqq68UFxcnm81m4WToao2NjUpJSVF1dbXsdrvV4wDoAvyef3t4PB4dPXpUSUlJZ31cUIdMnz591KNHD9XV1flsr6urU2Ji4hmfExERoYiICJ9tMTExXTUigpDdbuc/cEA3x+/5t8PZzsScEtQ3+4aHhys9PV0lJSXebe3t7SopKZHT6bRwMgAAEAyC+oyMJLlcLk2fPl0jR47UqFGjtHLlSjU1NWnGjBlWjwYAACwW9CEzefJkffnll1q8eLHcbrdGjBihLVu2nHYDMBAREaElS5acdmkRQPfB7zn+P5vnXO9rAgAACFJBfY8MAADA2RAyAADAWIQMAAAwFiEDAACMRcjASIWFherfv78iIyM1evRo7dq1y7uvublZubm5iouLU1RUlHJyck77UEUAwe2dd97RxIkTlZSUJJvNpk2bNvns/9Of/qRbbrnF+6nt+/bts2ROWI+QgXFeffVVuVwuLVmyRO+9956GDx+urKwsHTp0SJK0YMECbd68WRs2bFBpaalqamqUnZ1t8dQAzkdTU5OGDx+uwsLCDvdff/31WrFixUWeDMGGt1/DOKNHj9a1116rX//615L++2nPKSkpmjdvnubMmaP4+HgVFxfrJz/5iSRp//79Gjx4sMrKyjRmzBgrRwfgB5vNpo0bN2rSpEmn7fv88881YMAA7d27VyNGjLjos8F6nJGBUVpbW1VRUaHMzEzvtpCQEGVmZqqsrEwVFRVqa2vz2Z+WlqbU1FSVlZVZMTIAoAsRMjDK4cOHdfLkydM+2TkhIUFut1tut1vh4eGnfVHoqf0AgO6FkAEAAMYiZGCUPn36qEePHqe9C6murk6JiYlKTExUa2ur6uvrz7gfANC9EDIwSnh4uNLT01VSUuLd1t7erpKSEjmdTqWnpyssLMxnf2VlpaqqquR0Oq0YGQDQhYL+26+B/8/lcmn69OkaOXKkRo0apZUrV6qpqUkzZsyQw+HQzJkz5XK5FBsbK7vdrnnz5snpdPKOJcAgx44d06effupdP3jwoPbt26fY2Filpqbqq6++UlVVlWpqaiT99x8skrxnZvEt4gEMtGrVKk9qaqonPDzcM2rUKE95ebl334kTJzz33nuv55JLLvH06tXLc9ttt3lqa2stnBbA+dq2bZtH0mnL9OnTPR6Px1NUVHTG/UuWLLF0blx8fI4MAAAwFvfIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAC4YOPGjdP8+fM73N+/f3+tXLnyoszy+eefy2azad++fRfl9QBYi5AB0OV2796t2bNne9dtNps2bdrU6eevX79eMTExnXpsSkqKamtrNXTo0POcEoCJ+NJIAF0uPj7+orxOa2urwsPD+dJA4FuEMzIAAuKbb77R3Llz5XA41KdPHy1atEinvsrtfy8t9e/fX5J02223yWazedfff/993XTTTYqOjpbdbld6err27Nmj7du3a8aMGWpoaJDNZpPNZtNjjz3mPdYTTzyhadOmyW63a/bs2addWtq+fbtsNptKSko0cuRI9erVS2PHjvV+W/Ipy5YtU9++fRUdHa27775bDz/8sEaMGOHdv337do0aNUq9e/dWTEyMrrvuOn3xxRdd9eME0EmEDICAePHFFxUaGqpdu3bpmWee0dNPP63f/va3pz1u9+7dkqSioiLV1tZ616dOnark5GTt3r1bFRUVevjhhxUWFqaxY8dq5cqVstvtqq2tVW1trR544AHv8X71q19p+PDh2rt3rxYtWtThfI888oieeuop7dmzR6Ghobrrrru8+1566SUtX75cK1asUEVFhVJTU7VmzRrv/m+++UaTJk3SjTfeqA8++EBlZWWaPXu2bDbbBf/cAFwYLi0BCIiUlBQVFBTIZrNp0KBB+vDDD1VQUKBZs2b5PO7UZaaYmBifS0BVVVV68MEHlZaWJkkaOHCgd5/D4ZDNZjvjJaOMjAzdf//93vXPP//8jPMtX75cN954oyTp4Ycf1oQJE9Tc3KzIyEitWrVKM2fO1IwZMyRJixcv1tatW3Xs2DFJUmNjoxoaGvSjH/1IV1xxhSRp8ODB5/XzAdA1OCMDICDGjBnjc4bC6XTqwIEDOnnyZKee73K5dPfddyszM1NPPvmkPvvss049b+TIkZ163LBhw7x/7tevnyTp0KFDkqTKykqNGjXK5/H/ux4bG6s777xTWVlZmjhxop555hnV1tZ26nUBdC1CBkBQeOyxx/Txxx9rwoQJevvttzVkyBBt3LjxnM/r3bt3p44fFhbm/fOp4Gpvb+/0fEVFRSorK9PYsWP16quv6qqrrlJ5eXmnnw+gaxAyAAJi586dPuvl5eUaOHCgevTocdpjw8LCznim5qqrrtKCBQu0detWZWdnq6ioSJIUHh7e6TM7/hg0aJD3Xp1T/v+6JH3nO99RXl6e3n33XQ0dOlTFxcVdNhOAziFkAAREVVWVXC6XKisr9fLLL2vVqlW67777zvjY/v37q6SkRG63W19//bVOnDihuXPnavv27friiy+0Y8cO7d6923sfSv/+/XXs2DGVlJTo8OHDOn78eEBnnzdvntatW6cXX3xRBw4c0LJly/TBBx94z9wcPHhQeXl5Kisr0xdffKGtW7fqwIED3CcDBAFu9gUQENOmTdOJEyc0atQo9ejRQ/fdd5/Ph+D9r6eeekoul0vPP/+8Lr30Uv3rX//SkSNHNG3aNNXV1alPnz7Kzs7W0qVLJUljx47VPffco8mTJ+vIkSNasmSJ9y3YgTB16lT9+9//1gMPPKDm5mbdcccduvPOO7Vr1y5JUq9evbR//369+OKLOnLkiPr166fc3Fz9/Oc/D9gMAPxj85z6oAcAgNfNN9+sxMRE/f73v7d6FABnwRkZAN96x48f19q1a5WVlaUePXro5Zdf1ltvvaU333zT6tEAnANnZAB86504cUITJ07U3r171dzcrEGDBunRRx9Vdna21aMBOAdCBgAAGIt3LQEAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACM9X8aMoGHN2LscQAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.bar(counts.keys(), counts.values())\n", + "\n", + "plt.xlabel(\"bitstrings\")\n", + "plt.ylabel(\"counts\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "658c21ce", + "metadata": {}, + "source": [ + "The output shows roughly equal measurement counts of the \"00\" and \"11\" bitstrings, a feature of the Bell state.\n", + "\n", + "In this notebook, you learn how to use AutoQASM to compose a basic quantum program. To learn more about composing more complex quantum programs with control flow and subroutines, continue to the next AutoQASM example notebook!" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/autoqasm/2_Expressing_classical_control_flow.ipynb b/examples/autoqasm/2_Expressing_classical_control_flow.ipynb new file mode 100644 index 00000000..26e37a99 --- /dev/null +++ b/examples/autoqasm/2_Expressing_classical_control_flow.ipynb @@ -0,0 +1,281 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "135014b8", + "metadata": {}, + "source": [ + "# Expressing classical control flow in quantum programs\n", + "In the getting-started notebook, you learn how to compose a basic quantum program with only quantum gates. A quantum program should also be able to express classical control flow, such as if-else branches, for-loops and subroutines. With AutoQASM, you write control flow the same way you would do in a Python program. The Pythonic experience in expressing classical control flow makes the quantum programs more readable, and makes it easier to build more complex programs.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "15cdfaee", + "metadata": {}, + "outputs": [], + "source": [ + "# general imports\n", + "from collections import Counter\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# AWS imports: Import Braket SDK modules\n", + "from braket.devices.local_simulator import LocalSimulator\n", + "import braket.experimental.autoqasm as aq\n", + "from braket.experimental.autoqasm.gates import measure, h, cnot" + ] + }, + { + "cell_type": "markdown", + "id": "2cccd8c2", + "metadata": {}, + "source": [ + "## Subroutine\n", + "When your quantum program uses the same logic multiple times, subroutines can be a helpful tool. With subroutines, you can generate a more efficient program representation. Your programs become easier to read and reason about, and less work to maintain.\n", + "\n", + "As an example, we want to prepare two Bell states, each on different qubit pair. Because the gates to prepare Bell states are the same, we can implement them as a subroutine. Similar to a quantum program, a subroutine is marked by the `@aq.function` decorator. Any number of functions in your program can be marked as a quantum program with this decorator, and they can call one another. Here, we define the Bell state preparation as a subroutine." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6e012372", + "metadata": {}, + "outputs": [], + "source": [ + "@aq.function\n", + "def bell(q0: int, q1: int) -> None:\n", + " h(q0)\n", + " cnot(q0, q1)" + ] + }, + { + "cell_type": "markdown", + "id": "e2a884e6", + "metadata": {}, + "source": [ + "In `two_bell`, the subroutine is called twice to prepare Bell states on different qubit pairs." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c268be19", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "def bell(int[32] q0, int[32] q1) {\n", + " h __qubits__[q0];\n", + " cnot __qubits__[q0], __qubits__[q1];\n", + "}\n", + "qubit[4] __qubits__;\n", + "bell(0, 1);\n", + "bell(2, 3);\n" + ] + } + ], + "source": [ + "@aq.function(num_qubits=4)\n", + "def two_bell() -> None:\n", + " bell(0, 1)\n", + " bell(2, 3)\n", + "\n", + "\n", + "two_bell_program = two_bell()\n", + "print(two_bell_program.to_ir())" + ] + }, + { + "cell_type": "markdown", + "id": "c6c59f55", + "metadata": {}, + "source": [ + "With classical control flow, sometimes the number of qubits used could be undetermined before the execution time (i.e., when program is executed on a QPU or a simulator). Therefore, AutoQASM does not know how many qubits the program may need. In cases like subroutine and using variables as qubit indices, we must define the qubit count using the keyword argument `num_qubits` in `@aq.function`." + ] + }, + { + "cell_type": "markdown", + "id": "1e86b3d5", + "metadata": {}, + "source": [ + "## If-else logic branch" + ] + }, + { + "cell_type": "markdown", + "id": "bf201c13", + "metadata": {}, + "source": [ + "In this example, we demonstrate if-else logic. We use a Bell state again, but this time we prepare the state on a dynamically determined qubit pair. We measure qubit `0` and apply the Bell state preparation onto qubits `1` and `2` if the result is `1` or else `3` and `4` if the result is `0`. Finally, we measure all five qubits." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "850bb524", + "metadata": {}, + "outputs": [], + "source": [ + "@aq.function(num_qubits=5)\n", + "def conditioned_bell() -> None:\n", + " h(0)\n", + " if measure(0):\n", + " bell(1, 2)\n", + " else:\n", + " bell(3, 4)\n", + "\n", + " c = measure([0, 1, 2, 3, 4])\n", + "\n", + "\n", + "conditioned_bell_program = conditioned_bell()" + ] + }, + { + "cell_type": "markdown", + "id": "db483f0e", + "metadata": {}, + "source": [ + "Let's run the quantum program on the Braket simulator." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c6603968", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "measurement counts: Counter({'00000': 135, '10000': 130, '11100': 123, '00011': 112})\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGxCAYAAACEFXd4AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAssElEQVR4nO3df1TUdb7H8dcgP/0BiAoDBUmtqamrriZh3jJjQzM3V2+lccvMpB9aGWXKvaHl2qJmabom2S21e7Ta7l3t1y5FmNomoiJWmhEVKScZLBVQFET53j86fk+z/khhYIZPz8c533P8fj6f74f3zGebfZ3vfGbGYVmWJQAAAEP5ebsAAACApkTYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYzd/bBfiC+vp67du3T+3atZPD4fB2OQAA4DxYlqXDhw8rJiZGfn5nv39D2JG0b98+xcbGersMAADQAKWlpbr44ovP2u/VsLNx40Y988wzKigoUFlZmdasWaORI0eecex9992nF198UQsWLNCUKVPs9oMHD+rBBx/UO++8Iz8/P40ePVrPP/+82rZte951tGvXTtJPT1ZoaGhjHhIAAGgmVVVVio2Ntf9//Gy8Gnaqq6vVu3dv3X333Ro1atRZx61Zs0abN29WTEzMaX0pKSkqKytTTk6O6urqNH78eKWmpmr16tXnXcept65CQ0MJOwAAtDC/tAXFq2Fn2LBhGjZs2DnHfP/993rwwQf1/vvva/jw4W59u3fvVnZ2trZu3ar+/ftLkhYvXqwbb7xR8+fPP2M4AgAAvy4+/Wms+vp63XHHHZo6dap69OhxWn9eXp7Cw8PtoCNJSUlJ8vPzU35+/lnnra2tVVVVldsBAADM5NNhZ+7cufL399dDDz10xn6Xy6XIyEi3Nn9/f0VERMjlcp113szMTIWFhdkHm5MBADCXz4adgoICPf/881qxYoXHPw6enp6uyspK+ygtLfXo/AAAwHf4bNj5+OOPtX//fsXFxcnf31/+/v7as2ePHn30UXXu3FmS5HQ6tX//frfrTpw4oYMHD8rpdJ517qCgIHszMpuSAQAwm89+z84dd9yhpKQkt7bk5GTdcccdGj9+vCQpMTFRFRUVKigoUL9+/SRJ69atU319vRISEpq9ZgAA4Hu8GnaOHDmir7/+2j4vKSnRjh07FBERobi4OHXo0MFtfEBAgJxOp7p27SpJ6t69u4YOHaqJEycqKytLdXV1mjx5ssaMGcMnsQAAgCQvv421bds29e3bV3379pUkpaWlqW/fvpoxY8Z5z7Fq1Sp169ZN119/vW688UYNGjRIy5Yta6qSAQBAC+OwLMvydhHeVlVVpbCwMFVWVrJ/BwCAFuJ8///bZzcoAwAAeAJhBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Xz2G5RN0Xn6e94u4VfruznDvV0CAMAHcGcHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKPxQ6BAA/Ejr97Dj7wCuBDc2QEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABG8/d2AQDgazpPf8/bJfwqfTdnuLdLgKG4swMAAIxG2AEAAEYj7AAAAKN5Nexs3LhRI0aMUExMjBwOh9auXWv31dXVadq0aerVq5fatGmjmJgY3Xnnndq3b5/bHAcPHlRKSopCQ0MVHh6uCRMm6MiRI838SAAAgK/yatiprq5W7969tWTJktP6jh49qu3btysjI0Pbt2/X3/72NxUVFekPf/iD27iUlBTt2rVLOTk5evfdd7Vx40alpqY210MAAAA+zqufxho2bJiGDRt2xr6wsDDl5OS4tf3lL3/RgAEDtHfvXsXFxWn37t3Kzs7W1q1b1b9/f0nS4sWLdeONN2r+/PmKiYlp8scAAAB8W4vas1NZWSmHw6Hw8HBJUl5ensLDw+2gI0lJSUny8/NTfn7+Weepra1VVVWV2wEAAMzUYsJOTU2Npk2bprFjxyo0NFSS5HK5FBkZ6TbO399fERERcrlcZ50rMzNTYWFh9hEbG9uktQMAAO9pEWGnrq5Ot956qyzL0tKlSxs9X3p6uiorK+2jtLTUA1UCAABf5PPfoHwq6OzZs0fr1q2z7+pIktPp1P79+93GnzhxQgcPHpTT6TzrnEFBQQoKCmqymgEAgO/w6Ts7p4JOcXGxPvzwQ3Xo0MGtPzExURUVFSooKLDb1q1bp/r6eiUkJDR3uQAAwAd59c7OkSNH9PXXX9vnJSUl2rFjhyIiIhQdHa1///d/1/bt2/Xuu+/q5MmT9j6ciIgIBQYGqnv37ho6dKgmTpyorKws1dXVafLkyRozZgyfxAIAAJK8HHa2bdum6667zj5PS0uTJI0bN05PPvmk3n77bUlSnz593K776KOPNHjwYEnSqlWrNHnyZF1//fXy8/PT6NGjtWjRomapHwAA+D6vhp3BgwfLsqyz9p+r75SIiAitXr3ak2UBAACD+PSeHQAAgMYi7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwmr+3CwAAoDl0nv6et0v41fpuznCv/n3u7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNG8GnY2btyoESNGKCYmRg6HQ2vXrnXrtyxLM2bMUHR0tEJCQpSUlKTi4mK3MQcPHlRKSopCQ0MVHh6uCRMm6MiRI834KAAAgC/zatiprq5W7969tWTJkjP2z5s3T4sWLVJWVpby8/PVpk0bJScnq6amxh6TkpKiXbt2KScnR++++642btyo1NTU5noIAADAx/l7848PGzZMw4YNO2OfZVlauHChnnjiCd18882SpFdffVVRUVFau3atxowZo927dys7O1tbt25V//79JUmLFy/WjTfeqPnz5ysmJqbZHgsAAPBNPrtnp6SkRC6XS0lJSXZbWFiYEhISlJeXJ0nKy8tTeHi4HXQkKSkpSX5+fsrPzz/r3LW1taqqqnI7AACAmXw27LhcLklSVFSUW3tUVJTd53K5FBkZ6dbv7++viIgIe8yZZGZmKiwszD5iY2M9XD0AAPAVPht2mlJ6eroqKyvto7S01NslAQCAJuKzYcfpdEqSysvL3drLy8vtPqfTqf3797v1nzhxQgcPHrTHnElQUJBCQ0PdDgAAYCafDTvx8fFyOp3Kzc2126qqqpSfn6/ExERJUmJioioqKlRQUGCPWbdunerr65WQkNDsNQMAAN/j1U9jHTlyRF9//bV9XlJSoh07digiIkJxcXGaMmWKZs+erS5duig+Pl4ZGRmKiYnRyJEjJUndu3fX0KFDNXHiRGVlZamurk6TJ0/WmDFj+CQWAACQ5OWws23bNl133XX2eVpamiRp3LhxWrFihR5//HFVV1crNTVVFRUVGjRokLKzsxUcHGxfs2rVKk2ePFnXX3+9/Pz8NHr0aC1atKjZHwsAAPBNXg07gwcPlmVZZ+13OByaNWuWZs2addYxERERWr16dVOUBwAADOCze3YAAAA8gbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGM2nw87JkyeVkZGh+Ph4hYSE6LLLLtOf/vQnWZZlj7EsSzNmzFB0dLRCQkKUlJSk4uJiL1YNAAB8iU+Hnblz52rp0qX6y1/+ot27d2vu3LmaN2+eFi9ebI+ZN2+eFi1apKysLOXn56tNmzZKTk5WTU2NFysHAAC+wt/bBZzLpk2bdPPNN2v48OGSpM6dO+u1117Tli1bJP10V2fhwoV64okndPPNN0uSXn31VUVFRWnt2rUaM2aM12oHAAC+wafv7AwcOFC5ubn66quvJEmffvqp/vnPf2rYsGGSpJKSErlcLiUlJdnXhIWFKSEhQXl5eWedt7a2VlVVVW4HAAAwk0/f2Zk+fbqqqqrUrVs3tWrVSidPntTTTz+tlJQUSZLL5ZIkRUVFuV0XFRVl951JZmamnnrqqaYrHAAA+AyfvrPz17/+VatWrdLq1au1fft2rVy5UvPnz9fKlSsbNW96eroqKyvto7S01EMVAwAAX+PTd3amTp2q6dOn23tvevXqpT179igzM1Pjxo2T0+mUJJWXlys6Otq+rry8XH369DnrvEFBQQoKCmrS2gEAgG/w6Ts7R48elZ+fe4mtWrVSfX29JCk+Pl5Op1O5ubl2f1VVlfLz85WYmNistQIAAN/UoDs7paWlcjgcuvjiiyVJW7Zs0erVq3XFFVcoNTXVY8WNGDFCTz/9tOLi4tSjRw8VFhbqueee09133y1JcjgcmjJlimbPnq0uXbooPj5eGRkZiomJ0ciRIz1WBwAAaLkaFHZuv/12paam6o477pDL5dLvf/979ejRQ6tWrZLL5dKMGTM8UtzixYuVkZGhBx54QPv371dMTIzuvfdet/kff/xxVVdXKzU1VRUVFRo0aJCys7MVHBzskRoAAEDL1qC3sXbu3KkBAwZI+mkTcc+ePbVp0yatWrVKK1as8Fhx7dq108KFC7Vnzx4dO3ZM33zzjWbPnq3AwEB7jMPh0KxZs+RyuVRTU6MPP/xQl19+ucdqAAAALVuDwk5dXZ29wffDDz/UH/7wB0lSt27dVFZW5rnqAAAAGqlBYadHjx7KysrSxx9/rJycHA0dOlSStG/fPnXo0MGjBQIAADRGg8LO3Llz9eKLL2rw4MEaO3asevfuLUl6++237be3AAAAfEGDNigPHjxYP/74o6qqqtS+fXu7PTU1VW3atPFYcQAAAI3VoDs7Q4YM0eHDh92CjiRFRETotttu80hhAAAAntCgsLN+/XodP378tPaamhp9/PHHjS4KAADAUy7obazPPvvM/vcXX3zh9mObJ0+eVHZ2ti666CLPVQcAANBIFxR2+vTpI4fDIYfDoSFDhpzWHxISosWLF3usOAAAgMa6oLBTUlIiy7J06aWXasuWLerUqZPdFxgYqMjISLVq1crjRQIAADTUBYWdSy65RJLsH+IEAADwdQ366LkkFRcX66OPPtL+/ftPCz+e+m0sAACAxmpQ2HnppZd0//33q2PHjnI6nXI4HHafw+Eg7AAAAJ/RoLAze/ZsPf3005o2bZqn6wEAAPCoBn3PzqFDh3TLLbd4uhYAAACPa1DYueWWW/TBBx94uhYAAACPa9DbWL/5zW+UkZGhzZs3q1evXgoICHDrf+ihhzxSHAAAQGM1KOwsW7ZMbdu21YYNG7Rhwwa3PofDQdgBAAA+o0Fhp6SkxNN1AAAANIkG7dkBAABoKRp0Z+fuu+8+Z/8rr7zSoGIAAAA8rUFh59ChQ27ndXV12rlzpyoqKs74A6EAAADe0qCws2bNmtPa6uvrdf/99+uyyy5rdFEAAACe4rE9O35+fkpLS9OCBQs8NSUAAECjeXSD8jfffKMTJ054ckoAAIBGadDbWGlpaW7nlmWprKxM7733nsaNG+eRwgAAADyhQWGnsLDQ7dzPz0+dOnXSs88++4uf1AIAAGhODQo7H330kafrAAAAaBINCjun/PDDDyoqKpIkde3aVZ06dfJIUQAAAJ7SoA3K1dXVuvvuuxUdHa1rrrlG11xzjWJiYjRhwgQdPXrU0zUCAAA0WIPCTlpamjZs2KB33nlHFRUVqqio0FtvvaUNGzbo0Ucf9XSNAAAADdagt7H+7//+T//7v/+rwYMH22033nijQkJCdOutt2rp0qWeqg8AAKBRGnRn5+jRo4qKijqtPTIykrexAACAT2lQ2ElMTNTMmTNVU1Njtx07dkxPPfWUEhMTPVYcAABAYzXobayFCxdq6NChuvjii9W7d29J0qeffqqgoCB98MEHHi0QAACgMRoUdnr16qXi4mKtWrVKX375pSRp7NixSklJUUhIiEcLBAAAaIwGhZ3MzExFRUVp4sSJbu2vvPKKfvjhB02bNs0jxQEAADRWg/bsvPjii+rWrdtp7T169FBWVlajiwIAAPCUBoUdl8ul6Ojo09o7deqksrKyRhcFAADgKQ0KO7Gxsfrkk09Oa//kk08UExPT6KIAAAA8pUF7diZOnKgpU6aorq5OQ4YMkSTl5ubq8ccf5xuUAQCAT2lQ2Jk6daoOHDigBx54QMePH5ckBQcHa9q0aUpPT/dogQAAAI3RoLDjcDg0d+5cZWRkaPfu3QoJCVGXLl0UFBTk6foAAAAapUF7dk5p27atrrzySvXs2bPJgs7333+v//iP/1CHDh0UEhKiXr16adu2bXa/ZVmaMWOGoqOjFRISoqSkJBUXFzdJLQAAoOVpVNhpaocOHdLVV1+tgIAA/eMf/9AXX3yhZ599Vu3bt7fHzJs3T4sWLVJWVpby8/PVpk0bJScnu/2UBQAA+PVq0NtYzWXu3LmKjY3V8uXL7bb4+Hj735ZlaeHChXriiSd08803S5JeffVVRUVFae3atRozZswZ562trVVtba19XlVV1USPAAAAeJtP39l5++231b9/f91yyy2KjIxU37599dJLL9n9JSUlcrlcSkpKstvCwsKUkJCgvLy8s86bmZmpsLAw+4iNjW3SxwEAALzHp8POt99+q6VLl6pLly56//33df/99+uhhx7SypUrJf305YaSFBUV5XZdVFSU3Xcm6enpqqystI/S0tKmexAAAMCrfPptrPr6evXv319//vOfJUl9+/bVzp07lZWVpXHjxjV43qCgID45BgDAr4RP39mJjo7WFVdc4dbWvXt37d27V5LkdDolSeXl5W5jysvL7T4AAPDr5tNh5+qrr1ZRUZFb21dffaVLLrlE0k+blZ1Op3Jzc+3+qqoq5efnKzExsVlrBQAAvsmn38Z65JFHNHDgQP35z3/Wrbfeqi1btmjZsmVatmyZpJ++3HDKlCmaPXu2unTpovj4eGVkZCgmJkYjR470bvEAAMAn+HTYufLKK7VmzRqlp6dr1qxZio+P18KFC5WSkmKPefzxx1VdXa3U1FRVVFRo0KBBys7OVnBwsBcrBwAAvsKnw44k3XTTTbrpppvO2u9wODRr1izNmjWrGasCAAAthU/v2QEAAGgswg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgtBYVdubMmSOHw6EpU6bYbTU1NZo0aZI6dOigtm3bavTo0SovL/dekQAAwKe0mLCzdetWvfjii/rtb3/r1v7II4/onXfe0ZtvvqkNGzZo3759GjVqlJeqBAAAvqZFhJ0jR44oJSVFL730ktq3b2+3V1ZW6uWXX9Zzzz2nIUOGqF+/flq+fLk2bdqkzZs3n3W+2tpaVVVVuR0AAMBMLSLsTJo0ScOHD1dSUpJbe0FBgerq6tzau3Xrpri4OOXl5Z11vszMTIWFhdlHbGxsk9UOAAC8y+fDzuuvv67t27crMzPztD6Xy6XAwECFh4e7tUdFRcnlcp11zvT0dFVWVtpHaWmpp8sGAAA+wt/bBZxLaWmpHn74YeXk5Cg4ONhj8wYFBSkoKMhj8wEAAN/l03d2CgoKtH//fv3ud7+Tv7+//P39tWHDBi1atEj+/v6KiorS8ePHVVFR4XZdeXm5nE6nd4oGAAA+xafv7Fx//fX6/PPP3drGjx+vbt26adq0aYqNjVVAQIByc3M1evRoSVJRUZH27t2rxMREb5QMAAB8jE+HnXbt2qlnz55ubW3atFGHDh3s9gkTJigtLU0REREKDQ3Vgw8+qMTERF111VXeKBkAAPgYnw4752PBggXy8/PT6NGjVVtbq+TkZL3wwgveLgsAAPiIFhd21q9f73YeHBysJUuWaMmSJd4pCAAA+DSf3qAMAADQWIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGg+HXYyMzN15ZVXql27doqMjNTIkSNVVFTkNqampkaTJk1Shw4d1LZtW40ePVrl5eVeqhgAAPganw47GzZs0KRJk7R582bl5OSorq5ON9xwg6qrq+0xjzzyiN555x29+eab2rBhg/bt26dRo0Z5sWoAAOBL/L1dwLlkZ2e7na9YsUKRkZEqKCjQNddco8rKSr388stavXq1hgwZIklavny5unfvrs2bN+uqq67yRtkAAMCH+PSdnX9VWVkpSYqIiJAkFRQUqK6uTklJSfaYbt26KS4uTnl5eWedp7a2VlVVVW4HAAAwU4sJO/X19ZoyZYquvvpq9ezZU5LkcrkUGBio8PBwt7FRUVFyuVxnnSszM1NhYWH2ERsb25SlAwAAL2oxYWfSpEnauXOnXn/99UbPlZ6ersrKSvsoLS31QIUAAMAX+fSenVMmT56sd999Vxs3btTFF19stzudTh0/flwVFRVud3fKy8vldDrPOl9QUJCCgoKasmQAAOAjfPrOjmVZmjx5stasWaN169YpPj7erb9fv34KCAhQbm6u3VZUVKS9e/cqMTGxucsFAAA+yKfv7EyaNEmrV6/WW2+9pXbt2tn7cMLCwhQSEqKwsDBNmDBBaWlpioiIUGhoqB588EElJibySSwAACDJx8PO0qVLJUmDBw92a1++fLnuuusuSdKCBQvk5+en0aNHq7a2VsnJyXrhhReauVIAAOCrfDrsWJb1i2OCg4O1ZMkSLVmypBkqAgAALY1P79kBAABoLMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDRjws6SJUvUuXNnBQcHKyEhQVu2bPF2SQAAwAcYEXbeeOMNpaWlaebMmdq+fbt69+6t5ORk7d+/39ulAQAALzMi7Dz33HOaOHGixo8fryuuuEJZWVlq3bq1XnnlFW+XBgAAvMzf2wU01vHjx1VQUKD09HS7zc/PT0lJScrLyzvjNbW1taqtrbXPKysrJUlVVVUer6++9qjH58T5aYr1/DnW1ntYWzOxruZqqrU9Na9lWecc1+LDzo8//qiTJ08qKirKrT0qKkpffvnlGa/JzMzUU089dVp7bGxsk9QI7whb6O0K0FRYWzOxruZq6rU9fPiwwsLCztrf4sNOQ6SnpystLc0+r6+v18GDB9WhQwc5HA4vVuZbqqqqFBsbq9LSUoWGhnq7HHgI62ou1tZcrO2ZWZalw4cPKyYm5pzjWnzY6dixo1q1aqXy8nK39vLycjmdzjNeExQUpKCgILe28PDwpiqxxQsNDeU/LgOxruZibc3F2p7uXHd0TmnxG5QDAwPVr18/5ebm2m319fXKzc1VYmKiFysDAAC+oMXf2ZGktLQ0jRs3Tv3799eAAQO0cOFCVVdXa/z48d4uDQAAeJkRYee2227TDz/8oBkzZsjlcqlPnz7Kzs4+bdMyLkxQUJBmzpx52lt+aNlYV3OxtuZibRvHYf3S57UAAABasBa/ZwcAAOBcCDsAAMBohB0AAGA0wg4AADAaYcdAS5YsUefOnRUcHKyEhARt2bLF7qupqdGkSZPUoUMHtW3bVqNHjz7tCxn37t2r4cOHq3Xr1oqMjNTUqVN14sQJtzHr16/X7373OwUFBek3v/mNVqxYcUF14MJs3LhRI0aMUExMjBwOh9auXevWb1mWZsyYoejoaIWEhCgpKUnFxcVuYw4ePKiUlBSFhoYqPDxcEyZM0JEjR9zGfPbZZ/q3f/s3BQcHKzY2VvPmzTutljfffFPdunVTcHCwevXqpb///e8ef7y/Fr+0rn/72990ww032N/uvmPHjtPmWLZsmQYPHqzQ0FA5HA5VVFScNsZTa48L19Svx2VlZbr99tt1+eWXy8/PT1OmTDmthl27dmn06NHq3LmzHA6HFi5c2FQP12cRdgzzxhtvKC0tTTNnztT27dvVu3dvJScna//+/ZKkRx55RO+8847efPNNbdiwQfv27dOoUaPs60+ePKnhw4fr+PHj2rRpk1auXKkVK1ZoxowZ9piSkhINHz5c1113nXbs2KEpU6bonnvu0fvvv3/edeDCVFdXq3fv3lqyZMkZ++fNm6dFixYpKytL+fn5atOmjZKTk1VTU2OPSUlJ0a5du5STk6N3331XGzduVGpqqt1fVVWlG264QZdccokKCgr0zDPP6Mknn9SyZcvsMZs2bdLYsWM1YcIEFRYWauTIkRo5cqR27tzZdA/eYL+0rtXV1Ro0aJDmzp171jmOHj2qoUOH6j//8z/POsYTa48L1xyvx7W1terUqZOeeOIJ9e7d+4x1HD16VJdeeqnmzJlz1l8WMJ4FowwYMMCaNGmSfX7y5EkrJibGyszMtCoqKqyAgADrzTfftPt3795tSbLy8vIsy7Ksv//975afn5/lcrnsMUuXLrVCQ0Ot2tpay7Is6/HHH7d69Ojh9ndvu+02Kzk5+bzqQONIstasWWOf19fXW06n03rmmWfstoqKCisoKMh67bXXLMuyrC+++MKSZG3dutUe849//MNyOBzW999/b1mWZb3wwgtW+/bt7XW2LMuaNm2a1bVrV/v81ltvtYYPH+5WT0JCgnXvvfd69DH+Gv3ruv5cSUmJJckqLCw86/UfffSRJck6dOiQW7un1h4Xrjlej3/u2muvtR5++OFz1nTJJZdYCxYsaNwDa4G4s2OQ48ePq6CgQElJSXabn5+fkpKSlJeXp4KCAtXV1bn1d+vWTXFxccrLy5Mk5eXlqVevXm5fyJicnKyqqirt2rXLHvPzOU6NOTXHL9UBzyopKZHL5XJ7vsPCwpSQkOC2ruHh4erfv789JikpSX5+fsrPz7fHXHPNNQoMDLTHJCcnq6ioSIcOHbLHnGvt4Xs8tfa4MM31eozzQ9gxyI8//qiTJ0+e9s3RUVFRcrlccrlcCgwMPO1HT0/1S5LL5Trj9af6zjWmqqpKx44d+8U64FmnntNzPd8ul0uRkZFu/f7+/oqIiPDI2rOuvstTa48L01yvxzg/hB0AAGA0wo5BOnbsqFatWp22m7+8vFxOp1NOp1PHjx8/7dMap/olyel0nvH6U33nGhMaGqqQkJBfrAOedeo5Pdfz7XQ6T9scfuLECR08eNAja8+6+i5PrT0uTHO9HuP8EHYMEhgYqH79+ik3N9duq6+vV25urhITE9WvXz8FBAS49RcVFWnv3r1KTEyUJCUmJurzzz93e3HMyclRaGiorrjiCnvMz+c4NebUHL9UBzwrPj5eTqfT7fmuqqpSfn6+27pWVFSooKDAHrNu3TrV19crISHBHrNx40bV1dXZY3JyctS1a1e1b9/eHnOutYfv8dTa48I01+sxzpO3d0jDs15//XUrKCjIWrFihfXFF19YqampVnh4uL2b/7777rPi4uKsdevWWdu2bbMSExOtxMRE+/oTJ05YPXv2tG644QZrx44dVnZ2ttWpUycrPT3dHvPtt99arVu3tqZOnWrt3r3bWrJkidWqVSsrOzv7vOvAhTl8+LBVWFhoFRYWWpKs5557ziosLLT27NljWZZlzZkzxwoPD7feeust67PPPrNuvvlmKz4+3jp27Jg9x9ChQ62+ffta+fn51j//+U+rS5cu1tixY+3+iooKKyoqyrrjjjusnTt3Wq+//rrVunVr68UXX7THfPLJJ5a/v781f/58a/fu3dbMmTOtgIAA6/PPP2++J8Mgv7SuBw4csAoLC6333nvPkmS9/vrrVmFhoVVWVmbPUVZWZhUWFlovvfSSJcnauHGjVVhYaB04cMAe44m1x4Vrjtdjy7Ls/w3169fPuv32263CwkJr165ddn9tba09Jjo62nrssceswsJCq7i4uHmeCB9A2DHQ4sWLrbi4OCswMNAaMGCAtXnzZrvv2LFj1gMPPGC1b9/eat26tfXHP/7R7YXTsizru+++s4YNG2aFhIRYHTt2tB599FGrrq7ObcxHH31k9enTxwoMDLQuvfRSa/ny5RdUBy7MqY8V/+sxbtw4y7J++vh5RkaGFRUVZQUFBVnXX3+9VVRU5DbHgQMHrLFjx1pt27a1QkNDrfHjx1uHDx92G/Ppp59agwYNsoKCgqyLLrrImjNnzmm1/PWvf7Uuv/xyKzAw0OrRo4f13nvvNdnjNt0vrevy5cvP2D9z5kx7jpkzZ55xzM//m/TU2uPCNcfr8ZnW/5JLLrH7T311wb8e1157bVM+dJ/isCzLauq7RwAAAN7Cnh0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQBNbvDgwZoyZcpZ+zt37qyFCxc2Sy3fffedHA6HduzY0Sx/D4D3EXYAeN3WrVuVmppqnzscDq1du/a8r1+xYoXCw8PPa2xsbKzKysrUs2fPC6wSQEvl7+0CAKBTp07N8neOHz+uwMBAOZ3OZvl7AHwDd3YANIsTJ05o8uTJCgsLU8eOHZWRkaFTP83387exOnfuLEn64x//KIfDYZ9/+umnuu6669SuXTuFhoaqX79+2rZtm9avX6/x48ersrJSDodDDodDTz75pD3Xn/70J915550KDQ1VamrqaW9jrV+/Xg6HQ7m5uerfv79at26tgQMHqqioyK3+2bNnKzIyUu3atdM999yj6dOnq0+fPnb/+vXrNWDAALVp00bh4eG6+uqrtWfPnqZ6OgFcAMIOgGaxcuVK+fv7a8uWLXr++ef13HPP6b//+79PG7d161ZJ0vLly1VWVmafp6Sk6OKLL9bWrVtVUFCg6dOnKyAgQAMHDtTChQsVGhqqsrIylZWV6bHHHrPnmz9/vnr37q3CwkJlZGSctb7/+q//0rPPPqtt27bJ399fd999t923atUqPf3005o7d64KCgoUFxenpUuX2v0nTpzQyJEjde211+qzzz5TXl6eUlNT5XA4Gv28AWg83sYC0CxiY2O1YMECORwOde3aVZ9//rkWLFigiRMnuo079ZZWeHi429tNe/fu1dSpU9WtWzdJUpcuXey+sLAwORyOM749NWTIED366KP2+XfffXfG+p5++mlde+21kqTp06dr+PDhqqmpUXBwsBYvXqwJEyZo/PjxkqQZM2bogw8+0JEjRyRJVVVVqqys1E033aTLLrtMktS9e/cLen4ANB3u7ABoFldddZXbnY7ExEQVFxfr5MmT53V9Wlqa7rnnHiUlJWnOnDn65ptvzuu6/v37n9e43/72t/a/o6OjJUn79++XJBUVFWnAgAFu439+HhERobvuukvJyckaMWKEnn/+eZWVlZ3X3wXQ9Ag7AFqEJ598Urt27dLw4cO1bt06XXHFFVqzZs0vXtemTZvzmj8gIMD+96lQVl9ff971LV++XHl5eRo4cKDeeOMNXX755dq8efN5Xw+g6RB2ADSL/Px8t/PNmzerS5cuatWq1WljAwICznjH5/LLL9cjjzyiDz74QKNGjdLy5cslSYGBged9h6ghunbtau8dOuVfzyWpb9++Sk9P16ZNm9SzZ0+tXr26yWoCcP4IOwCaxd69e5WWlqaioiK99tprWrx4sR5++OEzju3cubNyc3Plcrl06NAhHTt2TJMnT9b69eu1Z88effLJJ9q6dau9L6Zz5846cuSIcnNz9eOPP+ro0aMerf3BBx/Uyy+/rJUrV6q4uFizZ8/WZ599Zt8BKikpUXp6uvLy8rRnzx598MEHKi4uZt8O4CPYoAygWdx55506duyYBgwYoFatWunhhx92+yLBn3v22WeVlpaml156SRdddJG++uorHThwQHfeeafKy8vVsWNHjRo1Sk899ZQkaeDAgbrvvvt022236cCBA5o5c6b98XNPSElJ0bfffqvHHntMNTU1uvXWW3XXXXdpy5YtkqTWrVvryy+/1MqVK3XgwAFFR0dr0qRJuvfeez1WA4CGc1invugCAHDefv/738vpdOp//ud/vF0KgF/AnR0A+AVHjx5VVlaWkpOT1apVK7322mv68MMPlZOT4+3SAJwH7uwAwC84duyYRowYocLCQtXU1Khr16564oknNGrUKG+XBuA8EHYAAIDR+DQWAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGC0/wftywIip0tLFwAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "device = LocalSimulator()\n", + "result = device.run(conditioned_bell_program, shots=500).result()\n", + "counts = Counter(result.measurements[\"c\"])\n", + "print(\"measurement counts: \", counts)\n", + "\n", + "plt.bar(counts.keys(), counts.values())\n", + "plt.xlabel(\"bitstrings\")\n", + "plt.ylabel(\"counts\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "c1ded7b8", + "metadata": {}, + "source": [ + "The simulator result shows that the Bell state is prepared on the qubit pair (1, 2) when the 0th (leftmost) qubit is measured as 1, resulting in measured bitstrings '10000' and '11100'. Otherwise, the Bell state is prepared on the qubit pair (3, 4), resulting in measured bitstrings '00000' and '00011'." + ] + }, + { + "cell_type": "markdown", + "id": "edee6e9c", + "metadata": {}, + "source": [ + "## For-loop" + ] + }, + { + "cell_type": "markdown", + "id": "ce1da10e", + "metadata": {}, + "source": [ + "In this example, we demonstrate for-loop by creating many Bell states in a quantum program. Instead of writing the preparation steps explicitly, we can use a for-loop to make the program simpler and more readable. In the generated QASM script, you can see the code structure of for-loop is preserved." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "88403f29", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "def bell(int[32] q0, int[32] q1) {\n", + " h __qubits__[q0];\n", + " cnot __qubits__[q0], __qubits__[q1];\n", + "}\n", + "qubit[6] __qubits__;\n", + "for int i in [0:2:5] {\n", + " bell(i, i + 1);\n", + "}\n" + ] + } + ], + "source": [ + "n_bell = 3\n", + "\n", + "\n", + "@aq.function(num_qubits=n_bell * 2)\n", + "def multiple_bell(n_bell) -> None:\n", + " for i in aq.range(0, 2 * n_bell, 2):\n", + " bell(i, i + 1)\n", + "\n", + "\n", + "multiple_bell_program = multiple_bell(n_bell)\n", + "print(multiple_bell_program.to_ir())" + ] + }, + { + "cell_type": "markdown", + "id": "9303a270", + "metadata": {}, + "source": [ + "## Summary" + ] + }, + { + "cell_type": "markdown", + "id": "2619f38d", + "metadata": {}, + "source": [ + "In this notebook, you have learned how do use AutoQASM to express classical control flow in your quantum program. AutoQASM not only enables writing program with real-time control flow based on measurement results, but also increases the readability by taking advantage of high level programming features such as for-loops and subroutines." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/autoqasm/3_Iterative_phase_estimation.ipynb b/examples/autoqasm/3_Iterative_phase_estimation.ipynb new file mode 100644 index 00000000..6408bb0c --- /dev/null +++ b/examples/autoqasm/3_Iterative_phase_estimation.ipynb @@ -0,0 +1,225 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "08d77a63", + "metadata": {}, + "source": [ + "# Iterative phase estimation\n", + "Quantum phase estimation (QPE) is a central building block for many quantum algorithms, most famously Shor's algorithm for factoring and the HHL algorithm for solving linear systems of equations on a quantum computer. In this notebook, we implement the iterative phase estimation (IPE) algorithm, an implementation of QPE, described in Ref [1] and [2]. \n", + "\n", + "In this notebook, two qubits are used, with the 0th qubit being the ancilla qubit and the 1st qubit being the data qubit. The phase oracle in this example uses an ancilla qubit to add a phase $e^{i\\phi}$ to the $|1\\rangle$ component of the state of the data qubit. The oracle is written as a subroutine that will be used by the quantum program. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "15cdfaee", + "metadata": {}, + "outputs": [], + "source": [ + "# general imports\n", + "import math\n", + "from collections import Counter\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# AWS imports: Import Braket SDK modules\n", + "from braket.devices.local_simulator import LocalSimulator\n", + "import braket.experimental.autoqasm as aq\n", + "from braket.experimental.autoqasm.gates import measure, x, rz, h, cphaseshift, reset" + ] + }, + { + "cell_type": "markdown", + "id": "d471ea9c", + "metadata": {}, + "source": [ + "The phase in the oracle is defined by `phase` in the subroutine which is assumed to be unknown to the IPE algorithm. In this example, the phase is 3/16, which is 0.0011 in the binary fraction representation." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b3f0adfe", + "metadata": {}, + "outputs": [], + "source": [ + "@aq.function\n", + "def phase_oracle(ancilla_qubit: int, data_qubit: int) -> None:\n", + " \"\"\"Phase oracle that applies phase oracle on q1\n", + " conditioned on q0.\n", + "\n", + " Args:\n", + " ancilla_qubit (int): qubit that control adding phase to data qubit\n", + " data_qubit (int): qubit that the phase is added to\n", + " \"\"\"\n", + " phase = 2 * math.pi * (3 / 16)\n", + " cphaseshift(ancilla_qubit, data_qubit, phase)" + ] + }, + { + "cell_type": "markdown", + "id": "f76eb5a3", + "metadata": {}, + "source": [ + "The algorithm starts by preparing the data qubit (i.e., qubit 1) in an eigenstate of the oracle. Because the oracle in this example is the phase shift operation, the eigenstates are computational basis states (i.e., $|0\\rangle$ and $|1\\rangle$), and the oracle only applies non-trivially on $|1\\rangle$ of the data qubit. We choose $|1\\rangle$ to be the starting state of qubit 1. \n", + "\n", + "Each iteration estimates a precision digit, in binary fraction representation of the phase, starting from the smallest digit. There are four steps in an iteration:\n", + "\n", + "1. Preparing a superposition state, $|0\\rangle+|1\\rangle$, on the ancilla qubit. The algorithm uses the relative phase between $|0\\rangle$ and $|1\\rangle$ for computation.\n", + "\n", + "2. Except for the first iteration, apply a phase offset to the ancilla which offsets the phases estimated by previous iterations in order to yield a more accurate estimation for larger digits. This step corresponds to the Z gate in Fig. 1 of Ref [1]. \n", + "\n", + "3. Apply the phase oracle on the data and ancilla qubits by a number of times, depending on the iteration. \n", + "\n", + "4. Measure ancilla qubit to get the phase estimate. The measurement outcome $c$ in iteration $i$ means that the phase estimate for that iteration is $2\\pi c/2^{i}$." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "732baf1b", + "metadata": {}, + "outputs": [], + "source": [ + "@aq.function(num_qubits=2)\n", + "def ipe(n_iterations: int):\n", + " \"\"\"Iterative phase estimation algorithm.\n", + "\n", + " Args:\n", + " n_iterations (int): Number of iterations.\n", + " \"\"\"\n", + " q_ancilla = 0\n", + " q_data = 1\n", + "\n", + " c = aq.BitVar(0)\n", + " b0 = aq.BitVar(size=n_iterations)\n", + "\n", + " # Prepare an eigenstate of the oracle on the data qubit.\n", + " x(q_data)\n", + "\n", + " for iteration in aq.range(n_iterations):\n", + " # 1. Prepare superposition on the ancilla qubit\n", + " h(q_ancilla)\n", + "\n", + " # 2. Apply phase offset\n", + " if iteration > 0:\n", + " for i in aq.range(iteration):\n", + " k = b0[i]\n", + " if k:\n", + " rz(q_ancilla, -2 * math.pi / 2 ** (iteration + 1 - i))\n", + "\n", + " # 3. Apply phase oracle\n", + " n_oracle = 2 ** (n_iterations - 1 - iteration)\n", + " for j in aq.range(n_oracle):\n", + " phase_oracle(q_ancilla, q_data)\n", + "\n", + " # 4. Measure on the ancilla qubit in X basis\n", + " h(q_ancilla)\n", + " c = measure(q_ancilla)\n", + " b0[iteration] = c\n", + "\n", + " reset(q_ancilla)\n", + "\n", + "\n", + "n_iterations = 4\n", + "ipe_program = ipe(n_iterations)" + ] + }, + { + "cell_type": "markdown", + "id": "1184691f", + "metadata": {}, + "source": [ + "Let's execute the IPE algorithm on the Braket simulator." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "7f44ea40", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "measurement counts: Counter({'1100': 100})\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGwCAYAAABPSaTdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAie0lEQVR4nO3df1RUdf7H8dcgiojOGP4YIDHY1sRfaWkq6ikzijZzc3UrW3czNWkNLcT8wUl0LQ31rD/SNSm3xPaoW+2uttbJ1UjthwhK2m+JCpX96qCpzCgKosz3j45zmlXLhoEZPj0f58w53c+9c3njPzy7c2fG4na73QIAADBUSKAHAAAAqEvEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMFhroAYJBTU2NDh06pBYtWshisQR6HAAAcAXcbrdOnjypmJgYhYRc/voNsSPp0KFDio2NDfQYAADAB6WlpWrXrt1l9xM7klq0aCHpu38sq9Ua4GkAAMCVcLlcio2N9fwdvxxiR/K8dGW1WokdAAAamB+7BYUblAEAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABgtoLHz7rvvasiQIYqJiZHFYtGGDRu89rvdbs2cOVPR0dEKDw9XUlKSiouLvY45fvy4Ro4cKavVqpYtW2rs2LE6depUPf4WAAAgmAU0dioqKtS9e3ctX778kvsXLFigpUuXKjs7W/n5+YqIiFBycrIqKys9x4wcOVKfffaZtmzZojfeeEPvvvuuUlJS6utXAAAAQc7idrvdgR5C+u4bS9evX6+hQ4dK+u6qTkxMjCZPnqwnnnhCkuR0OmW325WTk6MRI0boiy++UOfOnbVr1y716tVLkrRp0ybddddd+u9//6uYmJgr+tkul0s2m01Op5NvPQcAoIG40r/fQXvPTklJiRwOh5KSkjxrNptNffr0UV5eniQpLy9PLVu29ISOJCUlJSkkJET5+fmXPXdVVZVcLpfXAwAAmCk00ANcjsPhkCTZ7Xavdbvd7tnncDjUtm1br/2hoaGKjIz0HHMpWVlZmj17tp8nvrS46W/Wy88BACBY7Z83OKA/P2iv7NSljIwMOZ1Oz6O0tDTQIwEAgDoStLETFRUlSSorK/NaLysr8+yLiorSkSNHvPafO3dOx48f9xxzKWFhYbJarV4PAABgpqCNnfj4eEVFRSk3N9ez5nK5lJ+fr8TERElSYmKiysvLVVhY6DnmnXfeUU1Njfr06VPvMwMAgOAT0Ht2Tp06pa+++sqzXVJSor179yoyMlLt27dXWlqa5syZow4dOig+Pl6ZmZmKiYnxvGOrU6dOuvPOOzVu3DhlZ2erurpaEyZM0IgRI674nVgAAMBsAY2d3bt369Zbb/Vsp6enS5JGjRqlnJwcTZ06VRUVFUpJSVF5ebkGDBigTZs2qWnTpp7nrFmzRhMmTNBtt92mkJAQDR8+XEuXLq333wUAAASnoPmcnUCqy8/Z4d1YAICfu7p6N1aD/5wdAAAAfyB2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGC2oY+f8+fPKzMxUfHy8wsPDde211+rpp5+W2+32HON2uzVz5kxFR0crPDxcSUlJKi4uDuDUAAAgmAR17MyfP18rVqzQX/7yF33xxReaP3++FixYoGXLlnmOWbBggZYuXars7Gzl5+crIiJCycnJqqysDODkAAAgWIQGeoAfsmPHDt1zzz0aPHiwJCkuLk7r1q1TQUGBpO+u6ixZskQzZszQPffcI0l6+eWXZbfbtWHDBo0YMeKS562qqlJVVZVn2+Vy1fFvAgAAAiWor+z069dPubm5+vLLLyVJH330kd5//3396le/kiSVlJTI4XAoKSnJ8xybzaY+ffooLy/vsufNysqSzWbzPGJjY+v2FwEAAAET1Fd2pk+fLpfLpYSEBDVq1Ejnz5/X3LlzNXLkSEmSw+GQJNntdq/n2e12z75LycjIUHp6umfb5XIRPAAAGCqoY+fVV1/VmjVrtHbtWnXp0kV79+5VWlqaYmJiNGrUKJ/PGxYWprCwMD9OCgAAglVQx86UKVM0ffp0z7033bp104EDB5SVlaVRo0YpKipKklRWVqbo6GjP88rKytSjR49AjAwAAIJMUN+zc/r0aYWEeI/YqFEj1dTUSJLi4+MVFRWl3Nxcz36Xy6X8/HwlJibW66wAACA4BfWVnSFDhmju3Llq3769unTpoj179mjRokUaM2aMJMlisSgtLU1z5sxRhw4dFB8fr8zMTMXExGjo0KGBHR4AAASFoI6dZcuWKTMzU48++qiOHDmimJgYPfLII5o5c6bnmKlTp6qiokIpKSkqLy/XgAEDtGnTJjVt2jSAkwMAgGBhcX//44h/plwul2w2m5xOp6xWq1/PHTf9Tb+eDwCAhmb/vMF1ct4r/fsd1PfsAAAA1BaxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMFfez83//9n37/+9+rVatWCg8PV7du3bR7927PfrfbrZkzZyo6Olrh4eFKSkpScXFxACcGAADBJKhj58SJE+rfv78aN26st956S59//rkWLlyoq666ynPMggULtHTpUmVnZys/P18RERFKTk5WZWVlACcHAADBIjTQA/yQ+fPnKzY2VqtWrfKsxcfHe/7b7XZryZIlmjFjhu655x5J0ssvvyy73a4NGzZoxIgR9T4zAAAILkF9Zeff//63evXqpXvvvVdt27bVDTfcoJUrV3r2l5SUyOFwKCkpybNms9nUp08f5eXlXfa8VVVVcrlcXg8AAGCmoI6db775RitWrFCHDh30n//8R+PHj9djjz2m1atXS5IcDockyW63ez3Pbrd79l1KVlaWbDab5xEbG1t3vwQAAAiooI6dmpoa3XjjjXrmmWd0ww03KCUlRePGjVN2dnatzpuRkSGn0+l5lJaW+mliAAAQbII6dqKjo9W5c2evtU6dOungwYOSpKioKElSWVmZ1zFlZWWefZcSFhYmq9Xq9QAAAGYK6tjp37+/ioqKvNa+/PJLXXPNNZK+u1k5KipKubm5nv0ul0v5+flKTEys11kBAEBwCup3Y02aNEn9+vXTM888o/vuu08FBQV64YUX9MILL0iSLBaL0tLSNGfOHHXo0EHx8fHKzMxUTEyMhg4dGtjhAQBAUAjq2Lnpppu0fv16ZWRk6KmnnlJ8fLyWLFmikSNHeo6ZOnWqKioqlJKSovLycg0YMECbNm1S06ZNAzg5AAAIFha32+3+qU8qLS2VxWJRu3btJEkFBQVau3atOnfurJSUFL8PWddcLpdsNpucTqff79+Jm/6mX88HAEBDs3/e4Do575X+/fbpnp3f/e532rp1q6Tv3v59++23q6CgQE8++aSeeuop3yYGAACoAz7FzqeffqrevXtLkl599VV17dpVO3bs0Jo1a5STk+PP+QAAAGrFp9iprq5WWFiYJOntt9/Wr3/9a0lSQkKCDh8+7L/pAAAAasmn2OnSpYuys7P13nvvacuWLbrzzjslSYcOHVKrVq38OiAAAEBt+BQ78+fP1/PPP6+BAwfqgQceUPfu3SV9911WF17eAgAACAY+vfV84MCB+vbbb+VyuXTVVVd51lNSUhQREeG34QAAAGrLpys7gwYN0smTJ71CR5IiIyN1//33+2UwAAAAf/ApdrZt26azZ89etF5ZWan33nuv1kMBAAD4y096Gevjjz/2/Pfnn38uh8Ph2T5//rw2bdqkq6++2n/TAQAA1NJPip0ePXrIYrHIYrFo0KBBF+0PDw/XsmXL/DYcAABAbf2k2CkpKZHb7dYvfvELFRQUqE2bNp59TZo0Udu2bdWoUSO/DwkAAOCrnxQ711xzjSSppqamToYBAADwN5+/9by4uFhbt27VkSNHLoqfmTNn1nowAAAAf/ApdlauXKnx48erdevWioqKksVi8eyzWCzEDgAACBo+xc6cOXM0d+5cTZs2zd/zAAAA+JVPn7Nz4sQJ3Xvvvf6eBQAAwO98ip17771Xmzdv9vcsAAAAfufTy1i//OUvlZmZqZ07d6pbt25q3Lix1/7HHnvML8MBAADUlsXtdrt/6pPi4+Mvf0KLRd98802thqpvLpdLNptNTqdTVqvVr+eOm/6mX88HAEBDs3/e4Do575X+/fbpyk5JSYnPgwEAANQnn+7ZAQAAaCh8urIzZsyYH9z/0ksv+TQMAACAv/kUOydOnPDarq6u1qeffqry8vJLfkEoAABAoPgUO+vXr79oraamRuPHj9e1115b66EAAAD8xW/37ISEhCg9PV2LFy/21ykBAABqza83KH/99dc6d+6cP08JAABQKz69jJWenu617Xa7dfjwYb355psaNWqUXwYDAADwB59iZ8+ePV7bISEhatOmjRYuXPij79QCAACoTz7FztatW/09BwAAQJ3wKXYuOHr0qIqKiiRJHTt2VJs2bfwyFAAAgL/4dINyRUWFxowZo+joaN188826+eabFRMTo7Fjx+r06dP+nhEAAMBnPsVOenq6tm/fro0bN6q8vFzl5eV6/fXXtX37dk2ePNnfMwIAAPjMp5ex/vnPf+of//iHBg4c6Fm76667FB4ervvuu08rVqzw13wAAAC14tOVndOnT8tut1+03rZtW17GAgAAQcWn2ElMTNSsWbNUWVnpWTtz5oxmz56txMREvw0HAABQWz69jLVkyRLdeeedateunbp37y5J+uijjxQWFqbNmzf7dUAAAIDa8Cl2unXrpuLiYq1Zs0b79u2TJD3wwAMaOXKkwsPD/TogAABAbfgUO1lZWbLb7Ro3bpzX+ksvvaSjR49q2rRpfhkOAACgtny6Z+f5559XQkLCRetdunRRdnZ2rYcCAADwF59ix+FwKDo6+qL1Nm3a6PDhw7UeCgAAwF98ip3Y2Fh98MEHF61/8MEHiomJqfVQAAAA/uLTPTvjxo1TWlqaqqurNWjQIElSbm6upk6dyicoAwCAoOJT7EyZMkXHjh3To48+qrNnz0qSmjZtqmnTpikjI8OvAwIAANSGT7FjsVg0f/58ZWZm6osvvlB4eLg6dOigsLAwf88HAABQKz7FzgXNmzfXTTfd5K9ZAAAA/M6nG5QBAAAaCmIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYrUHFzrx582SxWJSWluZZq6ysVGpqqlq1aqXmzZtr+PDhKisrC9yQAAAgqDSY2Nm1a5eef/55XX/99V7rkyZN0saNG/Xaa69p+/btOnTokIYNGxagKQEAQLBpELFz6tQpjRw5UitXrtRVV13lWXc6nXrxxRe1aNEiDRo0SD179tSqVau0Y8cO7dy5M4ATAwCAYNEgYic1NVWDBw9WUlKS13phYaGqq6u91hMSEtS+fXvl5eVd9nxVVVVyuVxeDwAAYKbQQA/wY/7+97/rww8/1K5duy7a53A41KRJE7Vs2dJr3W63y+FwXPacWVlZmj17tr9HBQAAQSior+yUlpbq8ccf15o1a9S0aVO/nTcjI0NOp9PzKC0t9du5AQBAcAnq2CksLNSRI0d04403KjQ0VKGhodq+fbuWLl2q0NBQ2e12nT17VuXl5V7PKysrU1RU1GXPGxYWJqvV6vUAAABmCuqXsW677TZ98sknXmujR49WQkKCpk2bptjYWDVu3Fi5ubkaPny4JKmoqEgHDx5UYmJiIEYGAABBJqhjp0WLFuratavXWkREhFq1auVZHzt2rNLT0xUZGSmr1aqJEycqMTFRffv2DcTIAAAgyAR17FyJxYsXKyQkRMOHD1dVVZWSk5P13HPPBXosAAAQJCxut9sd6CECzeVyyWazyel0+v3+nbjpb/r1fAAANDT75w2uk/Ne6d/voL5BGQAAoLaIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYL6tjJysrSTTfdpBYtWqht27YaOnSoioqKvI6prKxUamqqWrVqpebNm2v48OEqKysL0MQAACDYBHXsbN++Xampqdq5c6e2bNmi6upq3XHHHaqoqPAcM2nSJG3cuFGvvfaatm/frkOHDmnYsGEBnBoAAAST0EAP8EM2bdrktZ2Tk6O2bduqsLBQN998s5xOp1588UWtXbtWgwYNkiStWrVKnTp10s6dO9W3b99LnreqqkpVVVWebZfLVXe/BAAACKigvrLzv5xOpyQpMjJSklRYWKjq6molJSV5jklISFD79u2Vl5d32fNkZWXJZrN5HrGxsXU7OAAACJgGEzs1NTVKS0tT//791bVrV0mSw+FQkyZN1LJlS69j7Xa7HA7HZc+VkZEhp9PpeZSWltbl6AAAIICC+mWs70tNTdWnn36q999/v9bnCgsLU1hYmB+mAgAAwa5BXNmZMGGC3njjDW3dulXt2rXzrEdFRens2bMqLy/3Or6srExRUVH1PCUAAAhGQR07brdbEyZM0Pr16/XOO+8oPj7ea3/Pnj3VuHFj5ebmetaKiop08OBBJSYm1ve4AAAgCAX1y1ipqalau3atXn/9dbVo0cJzH47NZlN4eLhsNpvGjh2r9PR0RUZGymq1auLEiUpMTLzsO7EAAMDPS1DHzooVKyRJAwcO9FpftWqVHnroIUnS4sWLFRISouHDh6uqqkrJycl67rnn6nlSAAAQrII6dtxu948e07RpUy1fvlzLly+vh4kAAEBDE9T37AAAANQWsQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjGRM7y5cvV1xcnJo2bao+ffqooKAg0CMBAIAgYETsvPLKK0pPT9esWbP04Ycfqnv37kpOTtaRI0cCPRoAAAgwI2Jn0aJFGjdunEaPHq3OnTsrOztbzZo100svvRTo0QAAQICFBnqA2jp79qwKCwuVkZHhWQsJCVFSUpLy8vIu+ZyqqipVVVV5tp1OpyTJ5XL5fb6aqtN+PycAAA1JXfx9/f553W73Dx7X4GPn22+/1fnz52W3273W7Xa79u3bd8nnZGVlafbs2Retx8bG1smMAAD8nNmW1O35T548KZvNdtn9DT52fJGRkaH09HTPdk1NjY4fP65WrVrJYrEEcDIA/uZyuRQbG6vS0lJZrdZAjwPAj9xut06ePKmYmJgfPK7Bx07r1q3VqFEjlZWVea2XlZUpKirqks8JCwtTWFiY11rLli3rakQAQcBqtRI7gIF+6IrOBQ3+BuUmTZqoZ8+eys3N9azV1NQoNzdXiYmJAZwMAAAEgwZ/ZUeS0tPTNWrUKPXq1Uu9e/fWkiVLVFFRodGjRwd6NAAAEGBGxM7999+vo0ePaubMmXI4HOrRo4c2bdp00U3LAH5+wsLCNGvWrIteugbw82Fx/9j7tQAAABqwBn/PDgAAwA8hdgAAgNGIHQAAYDRiBwAAGI3YARD03n33XQ0ZMkQxMTGyWCzasGGD1/5//etfuuOOOzyfgr53796LzlFZWanU1FS1atVKzZs31/Dhwy/6MNKDBw9q8ODBatasmdq2baspU6bo3LlzdfibAagPxA6AoFdRUaHu3btr+fLll90/YMAAzZ8//7LnmDRpkjZu3KjXXntN27dv16FDhzRs2DDP/vPnz2vw4ME6e/asduzYodWrVysnJ0czZ870++8DoH7x1nMADYrFYtH69es1dOjQi/bt379f8fHx2rNnj3r06OFZdzqdatOmjdauXavf/va3kqR9+/apU6dOysvLU9++ffXWW2/p7rvv1qFDhzyf0ZWdna1p06bp6NGjatKkSX38egDqAFd2ABivsLBQ1dXVSkpK8qwlJCSoffv2ysvLkyTl5eWpW7duXh9GmpycLJfLpc8++6zeZwbgP8QOAOM5HA41adLkoi/8tdvtcjgcnmP+91PXL2xfOAZAw0TsAAAAoxE7AIwXFRWls2fPqry83Gu9rKxMUVFRnmP+991ZF7YvHAOgYSJ2ABivZ8+eaty4sXJzcz1rRUVFOnjwoBITEyVJiYmJ+uSTT3TkyBHPMVu2bJHValXnzp3rfWYA/mPEt54DMNupU6f01VdfebZLSkq0d+9eRUZGqn379jp+/LgOHjyoQ4cOSfouZKTvrshERUXJZrNp7NixSk9PV2RkpKxWqyZOnKjExET17dtXknTHHXeoc+fO+sMf/qAFCxbI4XBoxowZSk1N5RvTgQaOt54DCHrbtm3TrbfeetH6qFGjlJOTo5ycHI0ePfqi/bNmzdKf/vQnSd99qODkyZO1bt06VVVVKTk5Wc8995zXS1QHDhzQ+PHjtW3bNkVERGjUqFGaN2+eQkP5/0KgISN2AACA0bhnBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgdAnRs4cKDS0tIuuz8uLk5Lliypl1n2798vi8WivXv31svPAxB4xA6AgNu1a5dSUlI82xaLRRs2bLji5+fk5Khly5ZXdGxsbKwOHz6srl27/sQpATRUfOELgIBr06ZNvfycs2fPqkmTJl7fhwXAfFzZAVAvzp07pwkTJshms6l169bKzMzUha/m+/7LWHFxcZKk3/zmN7JYLJ7tjz76SLfeeqtatGghq9Wqnj17avfu3dq2bZtGjx4tp9Mpi8Uii8Xi+fLPuLg4Pf3003rwwQdltVqVkpJy0ctY27Ztk8ViUW5urnr16qVmzZqpX79+nm9Ov2DOnDlq27atWrRooYcffljTp09Xjx49PPu3bdum3r17KyIiQi1btlT//v114MCBuvrnBPATEDsA6sXq1asVGhqqgoICPfvss1q0aJH++te/XnTcrl27JEmrVq3S4cOHPdsjR45Uu3bttGvXLhUWFmr69Olq3Lix+vXrpyVLlshqterw4cM6fPiwnnjiCc/5/vznP6t79+7as2ePMjMzLzvfk08+qYULF2r37t0KDQ3VmDFjPPvWrFmjuXPnav78+SosLFT79u21YsUKz/5z585p6NChuuWWW/Txxx8rLy9PKSkpslgstf53A1B7vIwFoF7ExsZq8eLFslgs6tixoz755BMtXrxY48aN8zruwktaLVu29Hq56eDBg5oyZYoSEhIkSR06dPDss9lsslgsl3x5atCgQZo8ebJne//+/Zecb+7cubrlllskSdOnT9fgwYNVWVmppk2batmyZRo7dqxGjx4tSZo5c6Y2b96sU6dOSZJcLpecTqfuvvtuXXvttZKkTp06/aR/HwB1hys7AOpF3759va50JCYmqri4WOfPn7+i56enp+vhhx9WUlKS5s2bp6+//vqKnterV68rOu7666/3/Hd0dLQk6ciRI5KkoqIi9e7d2+v4729HRkbqoYceUnJysoYMGaJnn31Whw8fvqKfC6DuETsAGoQ//elP+uyzzzR48GC988476ty5s9avX/+jz4uIiLii8zdu3Njz3xeirKam5ornW7VqlfLy8tSvXz+98soruu6667Rz584rfj6AukPsAKgX+fn5Xts7d+5Uhw4d1KhRo4uObdy48SWv+Fx33XWaNGmSNm/erGHDhmnVqlWSpCZNmlzxFSJfdOzY0XPv0AX/uy1JN9xwgzIyMrRjxw517dpVa9eurbOZAFw5YgdAvTh48KDS09NVVFSkdevWadmyZXr88ccveWxcXJxyc3PlcDh04sQJnTlzRhMmTNC2bdt04MABffDBB9q1a5fnvpi4uDidOnVKubm5+vbbb3X69Gm/zj5x4kS9+OKLWr16tYqLizVnzhx9/PHHnitAJSUlysjIUF5eng4cOKDNmzeruLiY+3aAIMENygDqxYMPPqgzZ86od+/eatSokR5//HGvDxL8voULFyo9PV0rV67U1VdfrS+//FLHjh3Tgw8+qLKyMrVu3VrDhg3T7NmzJUn9+vXTH//4R91///06duyYZs2a5Xn7uT+MHDlS33zzjZ544glVVlbqvvvu00MPPaSCggJJUrNmzbRv3z6tXr1ax44dU3R0tFJTU/XII4/4bQYAvrO4L3zQBQDgit1+++2KiorS3/72t0CPAuBHcGUHAH7E6dOnlZ2dreTkZDVq1Ejr1q3T22+/rS1btgR6NABXgCs7APAjzpw5oyFDhmjPnj2qrKxUx44dNWPGDA0bNizQowG4AsQOAAAwGu/GAgAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABjt/wFgOMP0atPibwAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "device = LocalSimulator()\n", + "result = device.run(ipe_program, shots=100).result()\n", + "counts = Counter(result.measurements[\"b0\"])\n", + "print(\"measurement counts: \", counts)\n", + "\n", + "plt.bar(counts.keys(), counts.values())\n", + "plt.xlabel(\"bitstrings\")\n", + "plt.ylabel(\"counts\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "2b4ff1a9", + "metadata": {}, + "source": [ + "The measurement results of the IPE algorithm are \"1100\" in all shots, which is equivalent to 0.0011 in binary fraction or 3/16. We have correctly estimated the phase in the oracle! " + ] + }, + { + "cell_type": "markdown", + "id": "63c39db4", + "metadata": {}, + "source": [ + "## Summary\n", + "In this notebook, we walked you through all stages of executing the IPE algorithm, from constructing a subroutine for the phase oracle, composing the dynamic circuit in the main IPE program, running the quantum program on the Braket simulator to visualizing the results. AutoQASM not only provides expressibility of classical control flow which enables a broader class of quantum programs, it also integrates with Amazon Braket which provides an end-to-end experience for a quantum algorithm." + ] + }, + { + "cell_type": "markdown", + "id": "26c3bf88", + "metadata": {}, + "source": [ + "\n", + "### References:\n", + "\n", + "[1] K. M. Svore et al., *Faster Phase Estimation* (2013). arXiv: https://arxiv.org/abs/1304.0741\n", + "\n", + "[2] A. D. Corcoles et al., *Exploiting dynamic quantum circuits in a quantum algorithm with superconducting qubits* (2021). arXiv: https://arxiv.org/abs/2102.01682" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/src/braket/experimental/autoqasm/README.md b/src/braket/experimental/autoqasm/README.md index 4e7101f4..0c016cca 100644 --- a/src/braket/experimental/autoqasm/README.md +++ b/src/braket/experimental/autoqasm/README.md @@ -56,19 +56,21 @@ def bell_state() -> None: You can view the output format, which is OpenQASM, by running `bell_state().to_ir()`. AutoQASM enables users to use more complicated program constructs with a compact and readable -structure. We can demonstrate this with an active reset program: -``` -# A program that actively resets a qubit back to the ground state -@aq.function -def reset(qubit: int, num_repeats: int) -> None: - for repeats in aq.range(num_repeats): - if measure(qubit): - x(qubit) +structure. We can demonstrate this with a program that conditionally prepares multiple Bell states +on qubit pairs (1, 2) and (3, 4). ``` +@aq.function(num_qubits=5) +def conditional_multi_bell_states() -> None: + h(0) + if measure(0): + for i in aq.range(2): + qubit = 2 * i + 1 + h(qubit) + cnot(qubit, qubit+1) -Now that the program takes inputs, you must pass those inputs when you call your function. -``` -my_reset_program = reset(qubit=0, num_repeats=3) + measure([0,1,2,3,4]) + +my_bell_program = conditional_multi_bell_states() ``` AutoQASM can support nested subroutines and complex control flow. You can use the Python runtime @@ -77,13 +79,13 @@ and quantum runtime side-by-side. For the moment, we support only a few quantum them out! The Amazon Braket local simulator supports AutoQASM programs as input. -Let's simulate the `my_reset_program`: +Let's simulate the `my_bell_program`: ``` from braket.devices.local_simulator import LocalSimulator device = LocalSimulator() -task = device.run(my_reset_program, shots=100) +task = device.run(my_bell_program, shots=100) result = task.result() ``` diff --git a/src/braket/experimental/autoqasm/gates/__init__.py b/src/braket/experimental/autoqasm/gates/__init__.py index 5eefa74e..d0014113 100644 --- a/src/braket/experimental/autoqasm/gates/__init__.py +++ b/src/braket/experimental/autoqasm/gates/__init__.py @@ -13,5 +13,5 @@ """Quantum operations for use in AutoQASM programs.""" -from .gates import QubitIdentifierType, cnot, h, rx, x # noqa: F401 +from .gates import QubitIdentifierType, cnot, cphaseshift, h, reset, rx, rz, x, y, z # noqa: F401 from .measurements import measure # noqa: F401 diff --git a/src/braket/experimental/autoqasm/gates/gates.py b/src/braket/experimental/autoqasm/gates/gates.py index bfc7f358..fecad991 100644 --- a/src/braket/experimental/autoqasm/gates/gates.py +++ b/src/braket/experimental/autoqasm/gates/gates.py @@ -31,6 +31,16 @@ def bell(): from .qubits import QubitIdentifierType, _qubit +def reset(q: QubitIdentifierType) -> None: + """Adds a reset instruction on a specified qubit. + + Args: + q (QubitIdentifierType): The target qubit. + """ + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + oqpy_program.gate(_qubit(q), "reset") + + def h(q: QubitIdentifierType) -> None: """Adds a Hadamard gate to the program on the specified qubit. @@ -51,6 +61,37 @@ def x(q: QubitIdentifierType) -> None: oqpy_program.gate(_qubit(q), "x") +def y(q: QubitIdentifierType) -> None: + """Adds a pi rotation around the Y axis on the specified qubit. + + Args: + q (QubitIdentifierType): The target qubit. + """ + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + oqpy_program.gate(_qubit(q), "y") + + +def z(q: QubitIdentifierType) -> None: + """Adds a pi rotation around the Z axis on the specified qubit. + + Args: + q (QubitIdentifierType): The target qubit. + """ + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + oqpy_program.gate(_qubit(q), "z") + + +def rz(q: QubitIdentifierType, angle: float) -> None: + """Adds a rotation around the Z axis by `angle` on the specified qubit. + + Args: + q (QubitIdentifierType): The target qubit. + angle (float): Angle in radians. + """ + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + oqpy_program.gate(_qubit(q), "rz", angle) + + def rx(q: QubitIdentifierType, angle: float) -> None: """Adds a rotation around the X axis by `angle` on the specified qubit. Args: @@ -70,3 +111,15 @@ def cnot(q_ctrl: QubitIdentifierType, q_target: QubitIdentifierType) -> None: """ oqpy_program = program.get_program_conversion_context().get_oqpy_program() oqpy_program.gate([_qubit(q_ctrl), _qubit(q_target)], "cnot") + + +def cphaseshift(q_ctrl: QubitIdentifierType, q_target: QubitIdentifierType, angle: float) -> None: + """Adds a CPhaseShift gate to the program on the specified qubits. + + Args: + q_ctrl (QubitIdentifierType): The control qubit. + q_target (QubitIdentifierType): The target qubit. + angle (float): Phase in radians. + """ + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + oqpy_program.gate([_qubit(q_ctrl), _qubit(q_target)], "cphaseshift", angle) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py b/test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py index 9d7c7999..7de73894 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py @@ -16,7 +16,7 @@ import pytest import braket.experimental.autoqasm as aq -from braket.experimental.autoqasm.gates import cnot, h, measure, rx, x +from braket.experimental.autoqasm.gates import cnot, cphaseshift, h, measure, reset, rx, rz, x, y, z def test_bell_state_prep(bell_state_program) -> None: @@ -59,8 +59,13 @@ def test_bell_with_measure() -> None: [ (h, [0], [], "\nh __qubits__[0];"), (x, [0], [], "\nx __qubits__[0];"), - (rx, [0], [0.1], "\nrx(0.1) __qubits__[0];"), + (y, [0], [], "\ny __qubits__[0];"), + (z, [0], [], "\nz __qubits__[0];"), + (rz, [0], [0.1], "\nrz(0.1) __qubits__[0];"), + (reset, [0], [], "\nreset __qubits__[0];"), (cnot, [0, 1], [], "\ncnot __qubits__[0], __qubits__[1];"), + (cphaseshift, [0, 1], [0.1], "\ncphaseshift(0.1) __qubits__[0], __qubits__[1];"), + (rx, [0], [0.1], "\nrx(0.1) __qubits__[0];"), ], ) def test_gates(gate, qubits, params, expected_qasm) -> None: From 2a9d5a65a25c672b2e915a0a9789b52ab9dc1738 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Wed, 19 Jul 2023 09:23:31 -0400 Subject: [PATCH 0753/1165] Finish transpiler branching coverage (#623) --- src/braket/experimental/autoqasm/api.py | 5 ---- .../autoqasm/transpiler/transpiler.py | 23 +++++-------------- .../braket/experimental/autoqasm/test_api.py | 2 +- .../experimental/autoqasm/test_transpiler.py | 3 +++ 4 files changed, 10 insertions(+), 23 deletions(-) diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index e60c6028..be5fd98a 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -94,11 +94,6 @@ def _function_without_params( wrapper_factory = _convert_wrapper( user_config=user_config, recursive=False, - optional_features=( - converter.Feature.ASSERT_STATEMENTS, - converter.Feature.LISTS, - converter.Feature.EQUALITY_OPERATORS, - ), ) wrapper = wrapper_factory(f) diff --git a/src/braket/experimental/autoqasm/transpiler/transpiler.py b/src/braket/experimental/autoqasm/transpiler/transpiler.py index 8cf86a99..d2816efb 100644 --- a/src/braket/experimental/autoqasm/transpiler/transpiler.py +++ b/src/braket/experimental/autoqasm/transpiler/transpiler.py @@ -134,17 +134,15 @@ def transform_ast( node = functions.transform(node, ctx) node = directives.transform(node, ctx) node = break_statements.transform(node, ctx) - if ctx.user.options.uses(converter.Feature.ASSERT_STATEMENTS): - node = asserts.transform(node, ctx) + node = asserts.transform(node, ctx) # Note: sequencing continue canonicalization before for loop one avoids # dealing with the extra loop increment operation that the for # canonicalization creates. node = continue_statements.transform(node, ctx) node = return_statements.transform(node, ctx) node = assignments.transform(node, ctx) - if ctx.user.options.uses(converter.Feature.LISTS): - node = lists.transform(node, ctx) - node = slices.transform(node, ctx) + node = lists.transform(node, ctx) + node = slices.transform(node, ctx) node = call_trees.transform(node, ctx) node = control_flow.transform(node, ctx) node = conditional_expressions.transform(node, ctx) @@ -255,12 +253,9 @@ def _converted_partial( caller_fn_scope: Optional[function_wrappers.FunctionScope] = None, options: Optional[converter.ConversionOptions] = None, ) -> Any: - new_kwargs = {} - if f.keywords is not None: - # Use copy to avoid mutating the underlying keywords. - new_kwargs = f.keywords.copy() - if kwargs is not None: - new_kwargs.update(kwargs) + # Use copy to avoid mutating the underlying keywords. + new_kwargs = f.keywords.copy() + new_kwargs.update(kwargs) new_args = f.args + args logging.log(3, "Forwarding call of partial %s with\n%s\n%s\n", f, new_args, new_kwargs) return converted_call( @@ -280,12 +275,6 @@ def _inspect_callable(f: Callable, args: tuple) -> Tuple[Callable, tuple]: if f_self is not None: effective_args = (f_self,) + effective_args - elif hasattr(f, "__class__") and hasattr(f.__class__, "__call__"): - # Callable objects. Dunder methods have special lookup rules, see: - # https://docs.python.org/3/reference/datamodel.html#specialnames - target_entity = f.__class__.__call__ - effective_args = (f,) + args - return target_entity, effective_args diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index 206889be..5c1ffb0b 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -784,7 +784,7 @@ def prog() -> None: def test_mismatched_qubits(): @aq.function(num_qubits=4) def subroutine() -> None: - a = measure(0) + _ = measure(0) @aq.function(num_qubits=8) def main() -> None: diff --git a/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py b/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py index d45a6f35..4217dc54 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py @@ -71,6 +71,9 @@ def bell_decorated(q0: int, q1: int): bell_decorated_partial = functools.partial(bell_decorated, 1) assert bell_decorated_partial(3).to_ir() == expected + bell_noarg_partial = functools.partial(bell_decorated, 1, 3) + assert bell_noarg_partial().to_ir() == expected + def test_classmethod() -> None: """Tests aq.function decorator application to a classmethod.""" From bba9fe351345fafaadaa2dafa4bc00f2fb26f7a2 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 19 Jul 2023 16:13:15 +0000 Subject: [PATCH 0754/1165] prepare release v1.50.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 447da078..7da03d17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.50.0 (2023-07-19) + +### Features + + * Add dot(), power(), and to_circuit() to PauliString + ## v1.49.1.post0 (2023-07-17) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 165fdee4..390376ef 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.49.2.dev0" +__version__ = "1.50.0" From eaa81be34c97ea3caba5d412d54eb6fefece3b3e Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 19 Jul 2023 16:13:15 +0000 Subject: [PATCH 0755/1165] update development version to v1.50.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 390376ef..cd90f054 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.50.0" +__version__ = "1.50.1.dev0" From 989bacdc9b90b0f057f56416fa1e94e2c25a94df Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 19 Jul 2023 11:49:20 -0700 Subject: [PATCH 0756/1165] feat: add gate calibration data for supported quantum devices (#614) * feat: add gate calibration data for supported quantum devices --------- Co-authored-by: Abe Coull Co-authored-by: Jean-Christophe Jaskula Co-authored-by: Kshitij Chhabra --- setup.py | 4 +- src/braket/aws/aws_device.py | 147 ++++++- src/braket/aws/aws_quantum_task.py | 21 +- src/braket/circuits/__init__.py | 1 + src/braket/circuits/angled_gate.py | 18 + src/braket/circuits/circuit.py | 178 +++++++- src/braket/circuits/gate.py | 3 + src/braket/circuits/gate_calibrations.py | 161 ++++++++ src/braket/circuits/gates.py | 3 + .../parametric/free_parameter_expression.py | 18 + src/braket/pulse/ast/approximation_parser.py | 2 + src/braket/pulse/pulse_sequence.py | 75 ++++ src/braket/pulse/waveforms.py | 78 +++- test/integ_tests/test_device_creation.py | 6 + test/integ_tests/test_pulse.py | 60 ++- .../braket/aws/common_test_utils.py | 10 + test/unit_tests/braket/aws/test_aws_device.py | 384 +++++++++++++++++- .../braket/circuits/test_angled_gate.py | 20 + .../braket/circuits/test_circuit.py | 322 ++++++++++++++- .../braket/circuits/test_gate_calibration.py | 148 +++++++ test/unit_tests/braket/circuits/test_gates.py | 6 + .../pulse/ast/test_approximation_parser.py | 11 + .../braket/pulse/test_pulse_sequence.py | 83 ++++ .../unit_tests/braket/pulse/test_waveforms.py | 69 +++- 24 files changed, 1778 insertions(+), 50 deletions(-) create mode 100644 src/braket/circuits/gate_calibrations.py create mode 100644 test/unit_tests/braket/circuits/test_gate_calibration.py diff --git a/setup.py b/setup.py index 226189ee..33ab9c8e 100644 --- a/setup.py +++ b/setup.py @@ -27,9 +27,9 @@ packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "amazon-braket-schemas>=1.18.0", + "amazon-braket-schemas>=1.19.0", "amazon-braket-default-simulator>=1.18.1", - "oqpy~=0.1.1", + "oqpy~=0.2.1", "setuptools", "backoff", "boltons", diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 51fd40ce..48ab2aae 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -13,11 +13,13 @@ from __future__ import annotations +import importlib import json import os +import urllib.request from datetime import datetime from enum import Enum -from typing import Dict, List, Optional, Union +from typing import Dict, List, Optional, Tuple, Union from botocore.errorfactory import ClientError from networkx import DiGraph, complete_graph, from_edgelist @@ -27,7 +29,8 @@ 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.circuits import Circuit +from braket.circuits import Circuit, Gate, QubitSet +from braket.circuits.gate_calibrations import GateCalibrations 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 @@ -36,7 +39,10 @@ from braket.devices.device import Device from braket.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQasmProgram -from braket.pulse import Frame, Port, PulseSequence +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 @@ -62,6 +68,14 @@ class AwsDevice(Device): _GET_DEVICES_ORDER_BY_KEYS = frozenset({"arn", "name", "type", "provider_name", "status"}) + _RIGETTI_GATES_TO_BRAKET = { + # Rx_12 does not exist in the Braket SDK, it is a gate between |1> and |2>. + "Rx_12": None, + "Cz": "CZ", + "Cphaseshift": "CPhaseShift", + "Xy": "XY", + } + def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): """ Args: @@ -80,6 +94,7 @@ def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): """ super().__init__(name=None, status=None) self._arn = arn + self._gate_calibrations = None self._properties = None self._provider_name = None self._poll_interval_seconds = None @@ -103,6 +118,7 @@ def run( poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: Optional[float] = None, inputs: Optional[Dict[str, float]] = None, + gate_calibrations: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTask: @@ -126,6 +142,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: {}. + gate_calibrations (Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]): A `GateCalibrations` for user defined gate + calibrations. Returns: AwsQuantumTask: An AwsQuantumTask that tracks the execution on the device. @@ -174,6 +192,7 @@ def run( poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds or self._poll_interval_seconds, inputs=inputs, + gate_calibrations=gate_calibrations, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) @@ -207,6 +226,7 @@ def run_batch( 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, + gate_calibrations: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTaskBatch: @@ -234,6 +254,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. Default: {}. + gate_calibrations (Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]): A + `Dict[Tuple[Gate, QubitSet], PulseSequence]]` for user defined gate calibrations. Returns: AwsQuantumTaskBatch: A batch containing all of the tasks run @@ -258,6 +280,7 @@ def run_batch( poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds or self._poll_interval_seconds, inputs=inputs, + gate_calibrations=gate_calibrations, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) @@ -349,6 +372,20 @@ def arn(self) -> str: """str: Return the ARN of the device""" return self._arn + @property + def gate_calibrations(self) -> Optional[GateCalibrations]: + """ + 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: + Optional[GateCalibrations]: The calibration object. Returns `None` if the data + is not present. + """ + if not self._gate_calibrations: + self._gate_calibrations = self.refresh_gate_calibrations() + return self._gate_calibrations + @property def is_available(self) -> bool: """Returns true if the device is currently available. @@ -365,10 +402,7 @@ def is_available(self) -> bool: weekday = current_datetime_utc.weekday() current_time_utc = current_datetime_utc.time().replace(microsecond=0) - if ( - execution_window.windowEndHour < execution_window.windowStartHour - and current_time_utc < execution_window.windowEndHour - ): + if current_time_utc < execution_window.windowEndHour < execution_window.windowStartHour: weekday = (weekday - 1) % 7 matched_day = execution_window.executionDay == ExecutionDay.EVERYDAY @@ -612,6 +646,9 @@ def get_device_region(device_arn: str) -> str: Args: device_arn (str): The device ARN. + Raises: + ValueError: Raised if the ARN is not properly formatted + Returns: str: the region of the ARN. """ @@ -622,3 +659,99 @@ def get_device_region(device_arn: str) -> str: 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'" ) + + def refresh_gate_calibrations(self) -> Optional[GateCalibrations]: + """ + Refreshes the gate calibration data upon request. + + If the device does not have calibration data, None is returned. + + Raises: + URLError: If the URL provided returns a non 2xx response. + + Returns: + Optional[GateCalibrations]: the calibration data for the device. None + is returned if the device does not have a gate calibrations URL associated. + """ + if hasattr(self.properties, "pulse") and hasattr( + self.properties.pulse, "nativeGateCalibrationsRef" + ): + try: + with urllib.request.urlopen( + self.properties.pulse.nativeGateCalibrationsRef.split("?")[0] + ) as f: + json_calibration_data = self._parse_calibration_json( + json.loads(f.read().decode("utf-8")) + ) + return GateCalibrations(json_calibration_data) + except urllib.error.URLError: + raise urllib.error.URLError( + f"Unable to reach {self.properties.pulse.nativeGateCalibrationsRef}" + ) + else: + return None + + def _parse_waveforms(self, waveforms_json: Dict) -> Dict: + waveforms = dict() + for waveform in waveforms_json: + parsed_waveform = _parse_waveform_from_calibration_schema(waveforms_json[waveform]) + waveforms[parsed_waveform.id] = parsed_waveform + return waveforms + + def _parse_pulse_sequence( + self, calibration: Dict, waveforms: Dict[ArbitraryWaveform] + ) -> PulseSequence: + return PulseSequence._parse_from_calibration_schema(calibration, waveforms, self.frames) + + 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 + corresponding `Dict[Tuple[Gate, QubitSet], PulseSequence]` to represent the calibration data. + + Args: + calibration_data (Dict): The data to be parsed. Based on + https://github.com/aws/amazon-braket-schemas-python/blob/main/src/braket/device_schema/pulse/native_gate_calibrations_v1.py. + + Returns: + Dict[Tuple[Gate, QubitSet], PulseSequence]: The + structured data based on a mapping of `Tuple[Gate, Qubit]` to its calibration repesented as a + `PulseSequence`. + + """ # noqa: E501 + waveforms = self._parse_waveforms(calibration_data["waveforms"]) + parsed_calibration_data = {} + for qubit_node in calibration_data["gates"]: + qubit = calibration_data["gates"][qubit_node] + for gate_node in qubit: + for gate in qubit[gate_node]: + gate_capitalized = getattr( + self, + f"_{self.provider_name.upper()}_GATES_TO_BRAKET", + {}, + ).get(gate_node.capitalize(), gate_node.capitalize()) + gate_obj = ( + getattr(importlib.import_module("braket.circuits.gates"), gate_capitalized) + if gate_capitalized is not None + else None + ) + qubits = QubitSet([int(x) for x in gate["qubits"]]) + if gate_obj is None: + # We drop out gates that are not implemented in the BDK + continue + + argument = None + if gate["arguments"]: + argument = ( + float(gate["arguments"][0]) + if _is_float(gate["arguments"][0]) + else FreeParameter(gate["arguments"][0]) + ) + gate_qubit_key = ( + (gate_obj(argument), qubits) if argument else (gate_obj(), qubits) + ) + gate_qubit_pulse = self._parse_pulse_sequence(gate["calibrations"], waveforms) + parsed_calibration_data[gate_qubit_key] = gate_qubit_pulse + + return parsed_calibration_data diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 7c65c57f..afa59025 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, Dict, Union +from typing import Any, Dict, Optional, Union import boto3 @@ -28,6 +28,7 @@ from braket.circuits.circuit import Circuit from braket.circuits.circuit_helpers import validate_circuit_and_shots from braket.circuits.compiler_directives import StartVerbatimBox +from braket.circuits.gate_calibrations import GateCalibrations from braket.circuits.gates import PulseGate from braket.circuits.serialization import ( IRType, @@ -103,6 +104,7 @@ def create( disable_qubit_rewiring: bool = False, tags: Dict[str, str] = None, inputs: Dict[str, float] = None, + gate_calibrations: Optional[GateCalibrations] = None, *args, **kwargs, ) -> AwsQuantumTask: @@ -143,6 +145,10 @@ def create( IR. If the IR supports inputs, the inputs will be updated with this value. Default: {}. + gate_calibrations (Optional[GateCalibrations]): A `GateCalibrations` for + user defined gate calibration. + Default: None. + Returns: AwsQuantumTask: AwsQuantumTask tracking the task execution on the device. @@ -187,6 +193,7 @@ def create( device_parameters or {}, disable_qubit_rewiring, inputs, + gate_calibrations=gate_calibrations, *args, **kwargs, ) @@ -473,6 +480,7 @@ def _create_internal( device_parameters: Union[dict, BraketSchemaBase], disable_qubit_rewiring: bool, inputs: Dict[str, float], + gate_calibrations: Optional[GateCalibrations], *args, **kwargs, ) -> AwsQuantumTask: @@ -488,6 +496,7 @@ def _( _device_parameters: Union[dict, BraketSchemaBase], # Not currently used for OpenQasmProgram _disable_qubit_rewiring: bool, inputs: Dict[str, float], + gate_calibrations: Optional[GateCalibrations], *args, **kwargs, ) -> AwsQuantumTask: @@ -505,6 +514,7 @@ def _( device_parameters: Union[dict, BraketSchemaBase], _disable_qubit_rewiring: bool, inputs: Dict[str, float], + gate_calibrations: Optional[GateCalibrations], *args, **kwargs, ) -> AwsQuantumTask: @@ -543,6 +553,7 @@ def _( _device_parameters: Union[dict, BraketSchemaBase], _disable_qubit_rewiring: bool, inputs: Dict[str, float], + gate_calibrations: Optional[GateCalibrations], *args, **kwargs, ) -> AwsQuantumTask: @@ -560,6 +571,7 @@ def _( device_parameters: Union[dict, BraketSchemaBase], disable_qubit_rewiring: bool, inputs: Dict[str, float], + gate_calibrations: Optional[GateCalibrations], *args, **kwargs, ) -> AwsQuantumTask: @@ -580,6 +592,7 @@ def _( if ( disable_qubit_rewiring or Instruction(StartVerbatimBox()) in circuit.instructions + or gate_calibrations is not None or any(isinstance(instruction.operator, PulseGate) for instruction in circuit.instructions) ): qubit_reference_type = QubitReferenceType.PHYSICAL @@ -589,7 +602,9 @@ def _( ) openqasm_program = circuit.to_ir( - ir_type=IRType.OPENQASM, serialization_properties=serialization_properties + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + gate_calibrations=gate_calibrations.copy() if gate_calibrations is not None else None, ) if inputs: @@ -624,6 +639,7 @@ def _( ], _, inputs: Dict[str, float], + gate_calibrations: Optional[GateCalibrations], *args, **kwargs, ) -> AwsQuantumTask: @@ -648,6 +664,7 @@ def _( device_parameters: dict, _, inputs: Dict[str, float], + gate_calibrations: Optional[GateCalibrations], *args, **kwargs, ) -> AwsQuantumTask: diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py index a6e185d9..d2788746 100644 --- a/src/braket/circuits/__init__.py +++ b/src/braket/circuits/__init__.py @@ -27,6 +27,7 @@ from braket.circuits.free_parameter import FreeParameter # noqa: F401 from braket.circuits.free_parameter_expression import FreeParameterExpression # noqa: F401 from braket.circuits.gate import Gate # noqa: F401 +from braket.circuits.gate_calibrations import GateCalibrations # noqa: F401 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 diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index 4c148612..f45bf616 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -119,6 +119,9 @@ def __eq__(self, other): def __repr__(self): return f"{self.name}('angle': {self.angle}, 'qubit_count': {self.qubit_count})" + def __hash__(self): + return hash((self.name, self.angle, self.qubit_count)) + class DoubleAngledGate(Gate, Parameterizable): """ @@ -231,6 +234,9 @@ def __repr__(self): f"'qubit_count': {self.qubit_count})" ) + def __hash__(self): + return hash((self.name, self.angle_1, self.angle_2, self.qubit_count)) + class TripleAngledGate(Gate, Parameterizable): """ @@ -358,6 +364,9 @@ def __repr__(self): f"'qubit_count': {self.qubit_count})" ) + def __hash__(self): + return hash((self.name, self.angle_1, self.angle_2, self.angle_3, self.qubit_count)) + @singledispatch def _angles_equal( @@ -403,6 +412,15 @@ def _multi_angled_ascii_characters( """ def format_string(angle: Union[FreeParameterExpression, float]) -> str: + """ + Formats an angle for ASCII representation. + + Args: + angle (Union[FreeParameterExpression, float]): The angle to format. + + Returns: + str: The ASCII representation of the angle. + """ 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/circuit.py b/src/braket/circuits/circuit.py index 38d6793f..be19f8a3 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -18,7 +18,7 @@ from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar, Union import numpy as np -from oqpy import Program as OqpyProgram +import oqpy from braket.circuits import compiler_directives from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram @@ -57,8 +57,9 @@ 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.pulse_sequence import _validate_uniqueness +from braket.pulse.pulse_sequence import PulseSequence, _validate_uniqueness SubroutineReturn = TypeVar( "SubroutineReturn", Iterable[Instruction], Instruction, ResultType, Iterable[ResultType] @@ -1097,6 +1098,7 @@ def to_ir( self, ir_type: IRType = IRType.JAQCD, serialization_properties: SerializationProperties = None, + gate_calibrations: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None, ) -> Union[OpenQasmProgram, JaqcdProgram]: """ Converts the circuit into the canonical intermediate representation. @@ -1108,6 +1110,8 @@ def to_ir( serialization_properties (SerializationProperties): 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_calibrations (Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]): The + calibration data for the device. default: None. Returns: Union[OpenQasmProgram, JaqcdProgram]: A representation of the circuit in the @@ -1127,7 +1131,10 @@ def to_ir( "serialization_properties must be of type OpenQASMSerializationProperties " "for IRType.OPENQASM." ) - return self._to_openqasm(serialization_properties or OpenQASMSerializationProperties()) + return self._to_openqasm( + serialization_properties or OpenQASMSerializationProperties(), + gate_calibrations, + ) else: raise ValueError(f"Supplied ir_type {ir_type} is not supported.") @@ -1165,9 +1172,11 @@ def _to_jaqcd(self) -> JaqcdProgram: ) def _to_openqasm( - self, serialization_properties: OpenQASMSerializationProperties + self, + serialization_properties: OpenQASMSerializationProperties, + gate_calibrations: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], ) -> OpenQasmProgram: - ir_instructions = self._create_openqasm_header(serialization_properties) + ir_instructions = self._create_openqasm_header(serialization_properties, gate_calibrations) openqasm_ir_type = IRType.OPENQASM ir_instructions.extend( [ @@ -1200,7 +1209,9 @@ def _to_openqasm( return OpenQasmProgram.construct(source="\n".join(ir_instructions), inputs={}) def _create_openqasm_header( - self, serialization_properties: OpenQASMSerializationProperties + self, + serialization_properties: OpenQASMSerializationProperties, + gate_calibrations: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], ) -> List[str]: ir_instructions = ["OPENQASM 3.0;"] for parameter in self.parameters: @@ -1217,16 +1228,78 @@ def _create_openqasm_header( f"{serialization_properties.qubit_reference_type} supplied." ) - frame_wf_declarations = self._generate_frame_wf_declarations() + frame_wf_declarations = self._generate_frame_wf_defcal_declarations(gate_calibrations) if frame_wf_declarations: ir_instructions.append(frame_wf_declarations) return ir_instructions - def _generate_frame_wf_declarations(self) -> Optional[str]: - frames = {} - waveforms = {} + def _validate_gate_calbrations_uniqueness( + self, + gate_calibrations: Dict[Tuple[Gate, QubitSet], PulseSequence], + frames: Dict[Frame], + waveforms: Dict[ArbitraryWaveform], + ) -> None: + for key, calibration in gate_calibrations.items(): + for frame in calibration._frames.values(): + _validate_uniqueness(frames, frame) + frames[frame.id] = frame + for waveform in calibration._waveforms.values(): + _validate_uniqueness(waveforms, waveform) + waveforms[waveform.id] = waveform + + def _generate_frame_wf_defcal_declarations( + self, gate_calibrations: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] + ) -> Optional[str]: + program = oqpy.Program(None) + + frames, waveforms = self._get_frames_waveforms_from_instrs(gate_calibrations) + + if gate_calibrations is not None: + self._validate_gate_calbrations_uniqueness(gate_calibrations, 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_calibrations is not None: + 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_calibrations is not None: + for key, calibration in gate_calibrations.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 = ( + [calibration._format_parameter_ast(value) for value in gate.parameters] + if isinstance(gate, Parameterizable) + else None + ) + 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) + + return None + + def _get_frames_waveforms_from_instrs( + self, gate_calibrations: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] + ) -> Tuple[Dict[Frame], Dict[ArbitraryWaveform]]: from braket.circuits.gates import PulseGate + frames = {} + waveforms = {} for instruction in self.instructions: if isinstance(instruction.operator, PulseGate): for frame in instruction.operator.pulse_sequence._frames.values(): @@ -1235,18 +1308,81 @@ def _generate_frame_wf_declarations(self) -> Optional[str]: for waveform in instruction.operator.pulse_sequence._waveforms.values(): _validate_uniqueness(waveforms, waveform) waveforms[waveform.id] = waveform + # this will change with full parametric calibration support + elif ( + isinstance(instruction.operator, Parameterizable) and gate_calibrations is not None + ): + fixed_argument_calibrations = self._add_fixed_argument_calibrations( + gate_calibrations, instruction + ) + gate_calibrations.update(fixed_argument_calibrations) + return 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: - program = OqpyProgram(None) - for f in declarable_frames: - program.declare(f._to_oqpy_expression()) - for wf in waveforms.values(): - program.declare(wf._to_oqpy_expression()) - ast = program.to_ast(encal=True, include_externs=False) - return ast_to_qasm(ast) - return None + def _add_fixed_argument_calibrations( + self, + gate_calibrations: Dict[Tuple[Gate, QubitSet], PulseSequence], + instruction: Instruction, + ) -> Dict[Tuple[Gate, QubitSet], PulseSequence]: + """Adds calibrations with arguments set to the instruction parameter values + + Given the collection of parameters in instruction.operator, this function looks for matching + parametric calibrations that have free parameters. If such a calibration is found and the + number N of its free parameters equals the number of instruction parameters, we can bind + the arguments of the calibration and add it to the calibration dictionary. + + If N is smaller, it is probably impossible to assign the instruction parameter values to the + corresponding calibration parameters so we raise an error. + If N=0, we ignore it as it will not be removed by _generate_frame_wf_defcal_declarations. + + Args: + gate_calibrations (Dict[Tuple[Gate, QubitSet], PulseSequence]): a dictionary of + calibrations + instruction (Instruction): a Circuit instruction + + Returns: + Dict[Tuple[Gate, QubitSet], PulseSequence]: additional calibrations + + Raises: + NotImplementedError: in two cases: (i) if the instruction contains unbound parameters + and the calibration dictionary contains a parametric calibration applicable to this + instructions; (ii) if the calibration is defined with a partial number of unbound + parameters. + """ + additional_calibrations = {} + for key, calibration in gate_calibrations.items(): + gate = key[0] + target = key[1] + if target != instruction.target: + continue + if isinstance(gate, type(instruction.operator)) and len( + instruction.operator.parameters + ) == len(gate.parameters): + free_parameter_number = sum( + [isinstance(p, FreeParameterExpression) for p in gate.parameters] + ) + if free_parameter_number == 0: + continue + elif free_parameter_number < len(gate.parameters): + raise NotImplementedError( + "Calibrations with a partial number of fixed parameters are not supported." + ) + elif any( + isinstance(p, FreeParameterExpression) for p in instruction.operator.parameters + ): + raise NotImplementedError( + "Parametric calibrations cannot be attached with parametric circuits." + ) + bound_key = ( + 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) + } + ) + return additional_calibrations def as_unitary(self) -> np.ndarray: r""" diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index eaf9675f..81f2499d 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -210,6 +210,9 @@ def __eq__(self, other): def __repr__(self): return f"{self.name}('qubit_count': {self._qubit_count})" + def __hash__(self): + return hash((self.name, self.qubit_count)) + @classmethod def register_gate(cls, gate: Type[Gate]) -> None: """Register a gate implementation by adding it into the Gate class. diff --git a/src/braket/circuits/gate_calibrations.py b/src/braket/circuits/gate_calibrations.py new file mode 100644 index 00000000..01694a1e --- /dev/null +++ b/src/braket/circuits/gate_calibrations.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 copy import deepcopy +from typing import Any, Dict, List, Optional, Tuple + +from braket.circuits.gate import Gate +from braket.circuits.qubit_set import QubitSet +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + QubitReferenceType, +) +from braket.pulse.pulse_sequence import PulseSequence + + +class GateCalibrations: + """ + 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 + + def __init__( + self, + pulse_sequences: Dict[Tuple[Gate, QubitSet], PulseSequence], + ): + """ + Args: + 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`. + + Returns: + Dict[Tuple[Gate, QubitSet], PulseSequence]: The calibration data Dictionary. + """ + return self._pulse_sequences + + @pulse_sequences.setter + def pulse_sequences(self, value: Any) -> None: + """ + Sets the mapping of (Gate, Qubit) to the corresponding `PulseSequence`. + + Args: + value(Any): The value for the pulse_sequences property to be set to. + + Raises: + TypeError: Raised if the type is not Dict[Tuple[Gate, QubitSet], PulseSequence] + + """ + if isinstance(value, dict) and all( + isinstance(k[0], Gate) and isinstance(k[1], QubitSet) and isinstance(v, PulseSequence) + for (k, v) in value.items() + ): + self._pulse_sequences = value + else: + raise TypeError( + "The value for pulse_sequence must be of type: " + "Dict[Tuple[Gate, QubitSet], PulseSequence]" + ) + + def copy(self) -> GateCalibrations: + """ + Returns a copy of the object. + + Returns: + GateCalibrations: a copy of the calibrations. + """ + return GateCalibrations(deepcopy(self._pulse_sequences)) + + def __len__(self): + return len(self._pulse_sequences) + + def filter( + self, gates: Optional[List[Gate]] = None, qubits: Optional[QubitSet] = None + ) -> Optional[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. + + Returns: + Optional[GateCalibrations]: A filtered GateCalibrations object. Otherwise, returns + none if no matches are found. + """ # noqa: E501 + keys = self.pulse_sequences.keys() + 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])) + ] + 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: + """ + Returns the defcal representation for the `GateCalibrations` object. + + Args: + calibration_key (Optional[Tuple[Gate, QubitSet]]): An optional key to get a specific defcal. + Default: None + + 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(): + raise ValueError( + f"The key {calibration_key} does not exist in this GateCalibrations object." + ) + return ( + self.pulse_sequences[calibration_key] + .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 + + 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], + ] + ) + + def __eq__(self, other): + 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 a37e3208..ac57d05b 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -2781,6 +2781,9 @@ def __eq__(self, other): return self.matrix_equivalence(other) return False + def __hash__(self): + return hash((self.name, str(self._matrix), self.qubit_count)) + @staticmethod def _transform_matrix_to_ir(matrix: np.ndarray) -> List: return [[[element.real, element.imag] for element in row] for row in matrix.tolist()] diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index eef9f8a6..1b64e2dd 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -186,3 +186,21 @@ def subs_if_free_parameter(parameter: Any, **kwargs) -> Any: substituted = float(substituted) return substituted return parameter + + +def _is_float(argument: str) -> bool: + """ + Checks if a string can be cast into a float. + + Args: + argument (str): String to check. + + Returns: + bool: Returns true if the string can be cast as a float. False, otherwise. + + """ + try: + float(argument) + return True + except ValueError: + return False diff --git a/src/braket/pulse/ast/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py index 89b318ea..69e9fafa 100644 --- a/src/braket/pulse/ast/approximation_parser.py +++ b/src/braket/pulse/ast/approximation_parser.py @@ -419,6 +419,8 @@ def play(self, node: ast.FunctionCall, context: _ParseState) -> None: amps = self.visit(node.arguments[1], context) 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.") else: raise NotImplementedError frame_data = context.frame_data[frame_id] diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index f51531fe..3606bbd1 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -13,7 +13,9 @@ from __future__ import annotations +import builtins from copy import deepcopy +from inspect import signature from typing import Any, Dict, List, Set, Union from openpulse import ast @@ -331,6 +333,72 @@ def _format_parameter_ast( return _FreeParameterExpressionIdentifier(parameter) return parameter + 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"), + "expr": FreeParameterExpression, + } + if argument["type"] in nonprimitive_arg_type.keys(): + return nonprimitive_arg_type[argument["type"]](argument["value"]) + else: + 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. + + 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. + + 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']}"): + 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") + return calibration_sequence + def __call__(self, arg: Any = None, **kwargs) -> PulseSequence: """ Implements the call function to easily make a bound PulseSequence. @@ -350,6 +418,13 @@ def __call__(self, arg: Any = None, **kwargs) -> PulseSequence: param_values[str(key)] = val return self.make_bound_pulse_sequence(param_values) + def __eq__(self, other): + return ( + isinstance(other, PulseSequence) + and self.parameters == other.parameters + and self.to_ir() == other.to_ir() + ) + def _validate_uniqueness( mapping: Dict[str, Any], values: Union[Frame, Waveform, List[Frame], List[Waveform]] diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index f7aeb2d2..f298dd3e 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -16,7 +16,7 @@ import random import string from abc import ABC, abstractmethod -from typing import List, Optional, Union +from typing import Dict, List, Optional, Union import numpy as np from oqpy import WaveformVar, bool_, complex128, declare_waveform_generator, duration, float64 @@ -55,6 +55,19 @@ def sample(self, dt: float) -> np.ndarray: 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 + + 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 @@ -94,6 +107,12 @@ def sample(self, dt: float) -> np.ndarray: """ raise NotImplementedError + @staticmethod + def _from_calibration_schema(waveform_json: Dict) -> ArbitraryWaveform: + wave_id = waveform_json["waveformId"] + complex_amplitudes = [complex(i[0], i[1]) for i in waveform_json["amplitudes"]] + return ArbitraryWaveform(complex_amplitudes, wave_id) + class ConstantWaveform(Waveform, Parameterizable): """A constant waveform which holds the supplied `iq` value as its amplitude for the @@ -166,6 +185,25 @@ def sample(self, dt: float) -> np.ndarray: samples = self.iq * np.ones_like(sample_range) return samples + @staticmethod + def _from_calibration_schema(waveform_json: Dict) -> ConstantWaveform: + wave_id = waveform_json["waveformId"] + length = iq = None + for val in waveform_json["arguments"]: + if val["name"] == "length": + length = ( + float(val["value"]) + if val["type"] == "float" + else FreeParameterExpression(val["value"]) + ) + if val["name"] == "iq": + iq = ( + complex(val["value"]) + if val["type"] == "complex" + else FreeParameterExpression(val["value"]) + ) + return ConstantWaveform(length=length, iq=iq, id=wave_id) + class DragGaussianWaveform(Waveform, Parameterizable): """A gaussian waveform with an additional gaussian derivative component and lifting applied.""" @@ -282,6 +320,17 @@ def sample(self, dt: float) -> np.ndarray: ) return samples + @staticmethod + def _from_calibration_schema(waveform_json: Dict) -> DragGaussianWaveform: + 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 DragGaussianWaveform(**waveform_parameters) + class GaussianWaveform(Waveform, Parameterizable): """A waveform with amplitudes following a gaussian distribution for the specified parameters.""" @@ -387,6 +436,17 @@ def sample(self, dt: float) -> np.ndarray: ) return samples + @staticmethod + def _from_calibration_schema(waveform_json: Dict) -> GaussianWaveform: + 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 GaussianWaveform(**waveform_parameters) + def _make_identifier_name() -> str: return "".join([random.choice(string.ascii_letters) for _ in range(10)]) @@ -402,3 +462,19 @@ def _map_to_oqpy_type( else _FreeParameterExpressionIdentifier(parameter) ) return parameter + + +def _parse_waveform_from_calibration_schema(waveform: Dict) -> Waveform: + waveform_names = { + "arbitrary": ArbitraryWaveform._from_calibration_schema, + "drag_gaussian": DragGaussianWaveform._from_calibration_schema, + "gaussian": GaussianWaveform._from_calibration_schema, + "constant": ConstantWaveform._from_calibration_schema, + } + if "amplitudes" in waveform.keys(): + 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") diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index c12b899f..2b8ee77a 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -56,6 +56,12 @@ def test_get_devices_arn(arn): assert results[0].arn == arn +@pytest.mark.parametrize("arn", [(PULSE_ARN)]) +def test_device_gate_calibrations(arn, aws_session): + device = AwsDevice(arn, aws_session=aws_session) + assert device.gate_calibrations + + def test_get_devices_others(): provider_names = ["Amazon Braket"] types = ["SIMULATOR"] diff --git a/test/integ_tests/test_pulse.py b/test/integ_tests/test_pulse.py index 3b4694e4..48746a78 100644 --- a/test/integ_tests/test_pulse.py +++ b/test/integ_tests/test_pulse.py @@ -1,10 +1,12 @@ +import math + import numpy as np import pytest from braket.aws import AwsDevice, AwsQuantumTask -from braket.circuits import Circuit +from braket.circuits import Circuit, Gate, GateCalibrations, QubitSet from braket.parametric import FreeParameter -from braket.pulse import ArbitraryWaveform, PulseSequence +from braket.pulse import ArbitraryWaveform, Frame, Port, PulseSequence @pytest.fixture @@ -144,6 +146,30 @@ def arbitrary_waveform(): ) +@pytest.fixture +def port(): + return Port("test_port_ff", dt=1e-9) + + +@pytest.fixture +def frame_id(device): + return next(iter(device.frames)) + + +@pytest.fixture +def frame(frame_id, port): + return Frame(frame_id, port, 1e6, is_predefined=True) + + +@pytest.fixture +def pulse_sequence(frame, arbitrary_waveform): + return ( + PulseSequence() + .barrier(qubits_or_frames=[frame]) + .play(frame=frame, waveform=arbitrary_waveform) + ) + + 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) @@ -281,3 +307,33 @@ def test_pulse_sequence(arbitrary_waveform, device): ) chi_squared = np.sum((observed - expected) ** 2 / expected) assert chi_squared < 10 # adjust this threshold if test is flaky + + +def test_gate_calibration_run(device, pulse_sequence): + 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) + user_calibration_task = device.run( + bell_circuit, + gate_calibrations=user_gate_calibrations.pulse_sequences, + shots=num_shots, + disable_qubit_rewiring=True, + ) + device_calibration_task = device.run( + bell_circuit, + gate_calibrations=device.gate_calibrations.pulse_sequences, + shots=num_shots, + disable_qubit_rewiring=True, + ) + + if not device.is_available: + try: + assert user_calibration_task.state not in AwsQuantumTask.TERMINAL_STATES + assert device_calibration_task.state not in AwsQuantumTask.TERMINAL_STATES + finally: + user_calibration_task.cancel() + device_calibration_task.cancel() + return + + assert user_calibration_task.result().measurement_counts + assert device_calibration_task.result().measurement_counts diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index cd7d76c3..4ad01ce2 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -200,6 +200,7 @@ def run_and_assert( poll_timeout_seconds, # Treated as positional arg poll_interval_seconds, # Treated as positional arg inputs, # Treated as positional arg + gate_calibrations, # Treated as positional arg extra_args, extra_kwargs, ): @@ -217,6 +218,8 @@ def run_and_assert( run_args.append(poll_interval_seconds) if inputs is not None: run_args.append(inputs) + if gate_calibrations is not None: + run_args.append(gate_calibrations) run_args += extra_args if extra_args else [] run_kwargs = extra_kwargs or {} @@ -233,6 +236,7 @@ def run_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, + gate_calibrations, extra_args, extra_kwargs, ) @@ -258,6 +262,7 @@ def run_batch_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, + gate_calibrations, extra_args, extra_kwargs, ): @@ -282,6 +287,8 @@ def run_batch_and_assert( run_args.append(poll_interval_seconds) if inputs is not None: run_args.append(inputs) + if gate_calibrations is not None: + run_args.append(gate_calibrations) run_args += extra_args if extra_args else [] run_kwargs = extra_kwargs or {} @@ -298,6 +305,7 @@ def run_batch_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, + gate_calibrations, extra_args, extra_kwargs, ) @@ -324,6 +332,7 @@ def _create_task_args_and_kwargs( poll_timeout_seconds, poll_interval_seconds, inputs, + gate_calibrations, extra_args, extra_kwargs, ): @@ -342,6 +351,7 @@ def _create_task_args_and_kwargs( if poll_interval_seconds is not None else default_poll_interval, "inputs": inputs, + "gate_calibrations": gate_calibrations, } ) 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 a4674e07..ff59e5bc 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -10,10 +10,12 @@ # 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 from datetime import datetime from unittest.mock import Mock, PropertyMock, patch +from urllib.error import URLError import networkx as nx import pytest @@ -31,17 +33,15 @@ ) from jsonschema import validate -from braket.aws import AwsDevice, AwsDeviceType, AwsQuantumTask, AwsQuantumTaskBatch -from braket.circuits import Circuit, FreeParameter +from braket.aws import AwsDevice, AwsDeviceType, AwsQuantumTask +from braket.circuits import Circuit, FreeParameter, Gate, QubitSet +from braket.circuits.gate_calibrations import GateCalibrations from braket.device_schema.device_execution_window import DeviceExecutionWindow from braket.device_schema.dwave import DwaveDeviceCapabilities -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, -) from braket.device_schema.rigetti import RigettiDeviceCapabilities from braket.device_schema.simulators import GateModelSimulatorDeviceCapabilities from braket.ir.openqasm import Program as OpenQasmProgram -from braket.pulse import Frame, Port +from braket.pulse import DragGaussianWaveform, Frame, Port, PulseSequence MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_1 = { "braketSchemaHeader": { @@ -78,6 +78,136 @@ ) +MOCK_gate_calibrations_JSON = { + "gates": { + "0": { + "cphaseshift": [ + { + "name": "cphaseshift", + "qubits": ["0", "1"], + "arguments": ["-1.5707963267948966"], + "calibrations": [ + { + "name": "barrier", + "arguments": [{"name": "qubit", "value": "0", "type": "string"}], + }, + { + "name": "play", + "arguments": [ + {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"}, + { + "name": "waveform", + "value": "wf_drag_gaussian_0", + "type": "waveform", + }, + ], + }, + { + "name": "barrier", + "arguments": [ + {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"} + ], + }, + { + "name": "barrier", + "arguments": None, + }, + { + "name": "delay", + "arguments": [ + {"name": "duration", "value": 3e-07, "type": "float"}, + {"name": "qubit", "value": "0", "type": "string"}, + {"name": "qubit", "value": "1", "type": "string"}, + ], + }, + { + "name": "delay", + "arguments": [ + {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"}, + {"name": "duration", "value": 3e-07, "type": "float"}, + ], + }, + { + "name": "shift_phase", + "arguments": [ + {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"}, + {"name": "phase", "value": 3e-07, "type": "float"}, + ], + }, + { + "name": "shift_frequency", + "arguments": [ + {"name": "frequency", "value": "theta", "type": "expr"}, + {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"}, + {"name": "extra", "value": "q0_q1_cphase_frame", "type": "string"}, + ], + }, + ], + } + ], + "rx": [ + { + "gateName": "rx", + "gateId": "rz_1", + "qubits": "0", + "arguments": ["theta"], + "calibrations": [ + {"name": "barrier", "arguments": None}, + ], + } + ], + }, + "0_1": { + "cz": [ + { + "gateName": "cz", + "gateId": "cz_0_1", + "qubits": ["1", "0"], + "arguments": [], + "calibrations": [ + {"name": "barrier", "arguments": None}, + ], + } + ], + "rx_12": [], + }, + }, + "waveforms": { + "q0_q1_cz_CZ": { + "waveformId": "q0_q1_cz_CZ", + "amplitudes": [[0.0, 0.0], [0.0, 0.0]], + }, + "wf_drag_gaussian_0": { + "waveformId": "wf_drag_gaussian_0", + "name": "drag_gaussian", + "arguments": [ + {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, + {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, + {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, + {"name": "beta", "value": 7.494904522022295e-10, "type": "float"}, + ], + }, + "wf_gaussian_0": { + "waveformId": "wf_gaussian_0", + "name": "gaussian", + "arguments": [ + {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, + {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, + {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, + ], + }, + "wf_constant": { + "waveformId": "wf_constant", + "name": "constant", + "arguments": [ + {"name": "length", "value": 2, "type": "float"}, + {"name": "iq", "value": 0.23, "type": "complex"}, + ], + }, + }, +} + + def test_mock_rigetti_schema_1(): validate(MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_1, RigettiDeviceCapabilities.schema()) @@ -85,7 +215,7 @@ def test_mock_rigetti_schema_1(): MOCK_GATE_MODEL_QPU_1 = { "deviceName": "Aspen-10", "deviceType": "QPU", - "providerName": "provider1", + "providerName": "Rigetti", "deviceStatus": "OFFLINE", "deviceCapabilities": MOCK_GATE_MODEL_QPU_CAPABILITIES_1.json(), } @@ -495,6 +625,7 @@ def test_device_refresh_metadata(arn): "qhpSpecificProperties": None, } }, + "nativeGateCalibrationsRef": "file://hostname/foo/bar", } @@ -516,6 +647,7 @@ def test_device_refresh_metadata(arn): "qhpSpecificProperties": None, } }, + "nativeGateCalibrationssRef": "file://hostname/foo/bar", } @@ -535,6 +667,32 @@ def get_pulse_model(capabilities_json): ], "shotsRange": [1, 10], }, + "provider": { + "specs": { + "1Q": { + "0": { + "fActiveReset": 0.9715, + "fRO": 0.951, + "f1QRB": 0.997339217568556, + "f1QRB_std_err": 0.00006690422818326937, + "f1Q_simultaneous_RB": 0.9949723201166536, + "f1Q_simultaneous_RB_std_err": 0.00021695233492231294, + "T1": 0.000010019627401991471, + "T2": 0.000018156447816365015, + } + }, + "2Q": { + "0-1": { + "fCZ": 0.9586440436264603, + "fCZ_std_err": 0.007025921432645824, + "fCPHASE": 0.9287330972713645, + "fCPHASE_std_err": 0.009709406809550082, + "fXY": 0.9755179214520402, + "fXY_std_err": 0.0060234488782598536, + }, + }, + } + }, "action": { "braket.ir.jaqcd.program": { "actionType": "braket.ir.jaqcd.program", @@ -554,7 +712,7 @@ def get_pulse_model(capabilities_json): return { "deviceName": "M-2-Pulse", "deviceType": "QPU", - "providerName": "provider1", + "providerName": "Rigetti", "deviceStatus": "OFFLINE", "deviceCapabilities": device_obj.json(), } @@ -588,6 +746,104 @@ def test_device_pulse_metadata(pulse_device_capabilities): assert device.frames == {} +def test_gate_calibration_refresh_no_url(arn): + mock_session = Mock() + mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 + mock_session.region = RIGETTI_REGION + device = AwsDevice(arn, mock_session) + + assert device.refresh_gate_calibrations() == None + + +@patch("urllib.request.urlopen") +def test_device_gate_calibrations_exists(mock_url_request): + # The data is accessed using a device manager so here data is prepped and passed for the return val. + response_data_content = { + "gates": { + "0_1": { + "cphaseshift": [ + { + "name": "cphaseshift", + "qubits": ["0", "1"], + "arguments": ["-1.5707963267948966"], + "calibrations": [ + { + "name": "play", + "arguments": [ + { + "name": "waveform", + "value": "wf_drag_gaussian_0", + "type": "waveform", + }, + { + "name": "frame", + "value": "q0_q1_cphase_frame", + "type": "frame", + }, + ], + }, + ], + } + ], + "rx_12": [{"name": "rx_12", "qubits": ["0"]}], + }, + }, + "waveforms": { + "wf_drag_gaussian_0": { + "waveformId": "wf_drag_gaussian_0", + "name": "drag_gaussian", + "arguments": [ + {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, + {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, + {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, + {"name": "beta", "value": 7.494904522022295e-10, "type": "float"}, + ], + }, + }, + } + + response_data_stream = io.BytesIO(json.dumps(response_data_content).encode("utf-8")) + mock_url_request.return_value.__enter__.return_value = response_data_stream + mock_session = Mock() + mock_session.get_device.return_value = get_pulse_model( + MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 + ) + device = AwsDevice(RIGETTI_ARN, mock_session) + + expected_waveforms = { + "wf_drag_gaussian_0": DragGaussianWaveform( + length=6.000000000000001e-8, + sigma=6.369913502160144e-9, + amplitude=-0.4549282253548838, + beta=7.494904522022295e-10, + id="wf_drag_gaussian_0", + ) + } + expected_ngc = GateCalibrations( + pulse_sequences={ + (Gate.CPhaseShift(-1.5707963267948966), QubitSet([0, 1])): PulseSequence().play( + device.frames["q0_q1_cphase_frame"], expected_waveforms["wf_drag_gaussian_0"] + ) + } + ) + assert device.gate_calibrations == expected_ngc + # Called twice to check that the property stays the same after being initially fetched + assert device.gate_calibrations == expected_ngc + + +@pytest.mark.xfail(raises=URLError) +@patch("urllib.request.urlopen") +def test_refresh_data_url_error(mock_url_request): + mock_url_request.side_effect = URLError("mock reason") + mock_session = Mock() + mock_session.get_device.return_value = get_pulse_model( + MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 + ) + device = AwsDevice(RIGETTI_ARN, mock_session) + + device.gate_calibrations + + def test_equality(arn): mock_session = Mock() mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 @@ -1054,6 +1310,7 @@ def test_default_bucket_not_called(aws_quantum_task_mock, device, circuit, s3_de None, None, None, + None, ) device._aws_session.default_bucket.assert_not_called() @@ -1227,6 +1484,7 @@ def _run_and_assert( poll_timeout_seconds=None, # Treated as positional arg poll_interval_seconds=None, # Treated as positional arg inputs=None, # Treated as positional arg + gate_calibrations=None, # Treated as positional arg extra_args=None, extra_kwargs=None, ): @@ -1243,6 +1501,7 @@ def _run_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, + gate_calibrations, extra_args, extra_kwargs, ) @@ -1260,6 +1519,7 @@ def _run_batch_and_assert( poll_timeout_seconds=None, # Treated as a positional arg poll_interval_seconds=None, # Treated as positional arg inputs=None, # Treated as positional arg + gate_calibrations=None, # Treated as positional arg extra_args=None, extra_kwargs=None, ): @@ -1279,6 +1539,7 @@ def _run_batch_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, + gate_calibrations, extra_args, extra_kwargs, ) @@ -1569,3 +1830,110 @@ def test_device_topology_graph_data(get_device_data, expected_graph, arn): new_val = "new_val" device._topology_graph = new_val assert device.topology_graph == new_val + + +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) + + +def test_parse_calibration_data(): + mock_session = Mock() + mock_session.get_device.return_value = get_pulse_model( + MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 + ) + device = AwsDevice(DWAVE_ARN, mock_session) + calibration_data = device._parse_calibration_json(MOCK_gate_calibrations_JSON) + device_ngc = GateCalibrations(calibration_data) + + expected_waveforms = { + "wf_drag_gaussian_0": DragGaussianWaveform( + length=6.000000000000001e-8, + sigma=6.369913502160144e-9, + amplitude=-0.4549282253548838, + beta=7.494904522022295e-10, + id="wf_drag_gaussian_0", + ) + } + expected_pulse_sequences = { + (Gate.CPhaseShift(-1.5707963267948966), QubitSet([0, 1])): PulseSequence() + .barrier(QubitSet(0)) + .play(device.frames["q0_q1_cphase_frame"], expected_waveforms["wf_drag_gaussian_0"]) + .barrier([device.frames["q0_q1_cphase_frame"]]) + .barrier([]) + .delay(QubitSet([0, 1]), 3e-07) + .delay([device.frames["q0_q1_cphase_frame"]], 3e-07) + .shift_phase(device.frames["q0_q1_cphase_frame"], 3e-07) + .shift_frequency(device.frames["q0_q1_cphase_frame"], FreeParameter("theta")), + (Gate.Rx(FreeParameter("theta")), QubitSet(0)): PulseSequence().barrier([]), + (Gate.CZ(), QubitSet([1, 0])): PulseSequence().barrier([]), + } + expected_ngc = GateCalibrations(pulse_sequences=expected_pulse_sequences) + assert device_ngc == expected_ngc + + +@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"}, + }, + } + ), + ], +) +@pytest.mark.xfail(raises=ValueError) +def test_parse_calibration_data_bad_instr(bad_input): + mock_session = Mock() + mock_session.get_device.return_value = get_pulse_model( + MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 + ) + device = AwsDevice(DWAVE_ARN, mock_session) + device._parse_calibration_json(bad_input) diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index 8dde92f2..76b7329f 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -192,3 +192,23 @@ def test_double_angle_parameters(): assert DoubleAngledGate( qubit_count=1, ascii_symbols=["foo"], angle_1=1, angle_2=2 ).parameters == [1, 2] + + +def test_hash_double_angle(): + symbol1 = FreeParameter("theta") + assert hash( + DoubleAngledGate(angle_1=symbol1, angle_2=1, qubit_count=1, ascii_symbols=["bar"]) + ) == hash(DoubleAngledGate(angle_1=symbol1, angle_2=1, qubit_count=1, ascii_symbols=["bar"])) + + +def test_hash_triple_angle(): + symbol1 = FreeParameter("theta") + assert hash( + TripleAngledGate( + angle_1=symbol1, angle_2=1, angle_3=3, qubit_count=1, ascii_symbols=["bar"] + ) + ) == hash( + TripleAngledGate( + angle_1=symbol1, angle_2=1, angle_3=3, qubit_count=1, ascii_symbols=["bar"] + ) + ) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index f24e4a83..2b34d421 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -33,6 +33,8 @@ noise, observables, ) +from braket.circuits.gate_calibrations import GateCalibrations +from braket.circuits.parameterizable import Parameterizable from braket.circuits.serialization import ( IRType, OpenQASMSerializationProperties, @@ -107,6 +109,61 @@ def user_defined_frame(port): ) +@pytest.fixture +def pulse_sequence(predefined_frame_1): + return ( + PulseSequence() + .set_frequency( + predefined_frame_1, + 6e6, + ) + .play( + predefined_frame_1, + DragGaussianWaveform(length=3e-3, sigma=0.4, beta=0.2, id="drag_gauss_wf"), + ) + ) + + +@pytest.fixture +def pulse_sequence_2(predefined_frame_1): + return ( + PulseSequence() + .shift_phase( + predefined_frame_1, + FreeParameter("alpha"), + ) + .set_phase( + predefined_frame_1, + FreeParameter("gamma"), + ) + .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])) + calibration_key_2 = (Gate.Rx(FreeParameter("theta")), QubitSet([0])) + calibration_key_3 = ( + 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, + } + ) + + def test_repr_instructions(h): expected = f"Circuit('instructions': {h.instructions})" assert repr(h) == expected @@ -681,6 +738,18 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "OPENQASM 3.0;", "bit[2] b;", "qubit[2] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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];", @@ -698,6 +767,18 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): [ "OPENQASM 3.0;", "bit[2] b;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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;", @@ -717,6 +798,18 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): source="\n".join( [ "OPENQASM 3.0;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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{", @@ -740,6 +833,18 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): [ "OPENQASM 3.0;", "qubit[5] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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]", @@ -759,6 +864,18 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "input float theta;", "bit[2] b;", "qubit[2] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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];", @@ -780,6 +897,18 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "OPENQASM 3.0;", "bit[5] b;", "qubit[5] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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];", @@ -802,6 +931,14 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "OPENQASM 3.0;", "bit[7] b;", "qubit[7] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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];", @@ -818,27 +955,202 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): ), ), ( - Circuit().h(0, power=-2.5).h(0, power=0), + 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[1] b;", - "qubit[1] q;", + "bit[2] b;", + "qubit[2] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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={}, ), ), + pytest.param( + Circuit().h(0, power=-2.5).h(0, power=0).rx(0, angle=FreeParameter("theta")), + OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), + OpenQasmProgram( + source="", + inputs={}, + ), + marks=pytest.mark.xfail( + reason="Parametric calibrations cannot be attached with parametric circuits." + ), + ), ], ) -def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): +def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir, gate_calibrations): assert ( - circuit.to_ir(ir_type=IRType.OPENQASM, serialization_properties=serialization_properties) + circuit.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + gate_calibrations=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) + 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, + } + ) + + 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" + + "(3000000.0ns, 400000000.0ns, 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={}, + ) + + assert ( + circ.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + gate_calibrations=gate_calibrations.pulse_sequences, + ) + == expected_ir + ) + + +@pytest.mark.xfail( + reasons="Calibrations with a partial number of fixed parameters are not supported." +) +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} + ), + ) + circuit.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + gate_calibrations=gate_calibrations.pulse_sequences, + ) + + +def test_circuit_user_gate(pulse_sequence_2): + class Foo(Gate, Parameterizable): + def __init__( + self, + bar, + ): + super().__init__(qubit_count=1, ascii_symbols=["Foo"]) + self._parameters = [bar] + + @property + def parameters(self): + return self._parameters + + def bind_values(self, **kwargs): + raise NotImplementedError + + @property + def _qasm_name(self): + return "foo" + + def __hash__(self): + return hash((self.name, self.parameters[0], self.qubit_count)) + + @staticmethod + @circuit.subroutine(register=True) + def foo( + target, + bar, + ): + return Instruction(Foo(bar), target=target) + + Gate.register_gate(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} + ) + } + ) + + expected_ir = OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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={}, + ) + + assert ( + circ.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + gate_calibrations=gate_calibrations.pulse_sequences, + ) == expected_ir ) diff --git a/test/unit_tests/braket/circuits/test_gate_calibration.py b/test/unit_tests/braket/circuits/test_gate_calibration.py new file mode 100644 index 00000000..31c2384d --- /dev/null +++ b/test/unit_tests/braket/circuits/test_gate_calibration.py @@ -0,0 +1,148 @@ +# 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 Gate, QubitSet +from braket.circuits.gate_calibrations import GateCalibrations +from braket.pulse import Frame, Port, PulseSequence + + +@pytest.fixture +def port(): + return Port("test_port_ff", dt=1e-9) + + +@pytest.fixture +def frame_id(): + return "test_frame_rf" + + +@pytest.fixture +def frame(frame_id, port): + return Frame(frame_id, port, 1e6, is_predefined=True) + + +@pytest.fixture +def pulse_sequence(frame): + return ( + PulseSequence() + .barrier(qubits_or_frames=[frame]) + .delay(qubits_or_frames=[frame], duration=1000) + ) + + +def test_gc_creation(pulse_sequence): + calibration_key = (Gate.H(), QubitSet([0, 1])) + calibration = GateCalibrations({calibration_key: pulse_sequence}) + + assert calibration.pulse_sequences[calibration_key] == pulse_sequence + + +def test_gc_copy(pulse_sequence): + calibration_key = (Gate.H(), QubitSet([0, 1])) + calibration = GateCalibrations({calibration_key: pulse_sequence}) + + assert calibration == calibration.copy() + + +def test_filter(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_calibration_1 = GateCalibrations({calibration_key: pulse_sequence}) + expected_calibration_2 = GateCalibrations( + {calibration_key: pulse_sequence, calibration_key_2: pulse_sequence} + ) + expected_calibration_3 = GateCalibrations({calibration_key_2: 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)) + + +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[1000000000000.0ns] test_frame_rf;", + "}", + ] + ) + + assert calibration.to_ir() == expected_ir + + +@pytest.mark.xfail(raises=ValueError) +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[1000000000000.0ns] 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[1000000000000.0ns] 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} + ) + + assert len(calibration) == 2 + + +@pytest.mark.parametrize( + "bad_input", + [ + ({(Gate.Rx(1), "string"): PulseSequence()}), + ({(Gate.Rx(1), QubitSet(0)): 4}), + ({("string_a", "string_b"): PulseSequence()}), + ], +) +@pytest.mark.xfail(raises=TypeError) +def test_bad_pulse_sequence(bad_input): + GateCalibrations(bad_input) diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 90dc7d5a..ce9698e9 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -1149,3 +1149,9 @@ def test_gate_power(gate, target, power, expected_ir): ) == expected_ir ) + + +def test_hash(): + assert hash(Gate.Unitary(Gate.CCNot().to_matrix())) == hash( + Gate.Unitary(Gate.CCNot().to_matrix()) + ) 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 c41a1712..e49c4d78 100644 --- a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py +++ b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py @@ -399,6 +399,17 @@ def test_play_arbitrary_waveforms(port): verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) +@pytest.mark.xfail(raises=NameError) +def test_missing_waveform(port): + frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) + my_arb_wf = ArbitraryWaveform([0.4 + 0.1j, -0.8 + 0.1j, 1 + 0.2j]) + pulse_seq = PulseSequence() + identifier = my_arb_wf._to_oqpy_expression() + identifier._needs_declaration = False + pulse_seq._program.play(frame, identifier.to_ast(pulse_seq._program)) + _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) + + def test_play_literal(port): frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) pulse_seq = PulseSequence() diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py index 064ab302..14add748 100644 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -337,3 +337,86 @@ def test_pulse_sequence_to_ir(predefined_frame_1, predefined_frame_2): ] ) assert pulse_sequence.to_ir() == expected_str + + +def test_parse_from_calibration_schema(predefined_frame_1, predefined_frame_2): + waveforms = { + "drag_gauss_wf": DragGaussianWaveform(length=3e-3, sigma=0.4, beta=0.2, id="drag_gauss_wf") + } + frames = {predefined_frame_1.id: predefined_frame_1, predefined_frame_2.id: predefined_frame_2} + + calibration_instrs = [ + { + "name": "barrier", + "arguments": [{"name": "qubit", "value": "0", "type": "string"}], + }, + { + "name": "play", + "arguments": [ + {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, + { + "name": "waveform", + "value": "drag_gauss_wf", + "type": "waveform", + }, + ], + }, + { + "name": "barrier", + "arguments": [ + {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, + {"name": "frame", "value": "predefined_frame_2", "type": "frame"}, + ], + }, + { + "name": "barrier", + "arguments": None, + }, + { + "name": "delay", + "arguments": [ + {"name": "duration", "value": 3e-07, "type": "float"}, + {"name": "qubit", "value": "0", "type": "string"}, + {"name": "qubit", "value": "1", "type": "string"}, + ], + }, + { + "name": "delay", + "arguments": [ + {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, + {"name": "duration", "value": 3e-07, "type": "float"}, + ], + }, + { + "name": "shift_phase", + "arguments": [ + {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, + {"name": "phase", "value": 3e-07, "type": "float"}, + ], + }, + { + "name": "shift_frequency", + "arguments": [ + {"name": "frequency", "value": "theta", "type": "expr"}, + {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, + {"name": "extra", "value": "predefined_frame_1", "type": "string"}, + ], + }, + ] + + expected_pulse_sequence = ( + PulseSequence() + .barrier(QubitSet(0)) + .play(predefined_frame_1, waveforms["drag_gauss_wf"]) + .barrier([predefined_frame_1, predefined_frame_2]) + .barrier([]) + .delay(QubitSet([0, 1]), 3e-07) + .delay([predefined_frame_1], 3e-07) + .shift_phase(predefined_frame_1, 3e-07) + .shift_frequency(predefined_frame_1, FreeParameter("theta")) + ) + + assert ( + PulseSequence._parse_from_calibration_schema(calibration_instrs, waveforms, frames) + == expected_pulse_sequence + ) diff --git a/test/unit_tests/braket/pulse/test_waveforms.py b/test/unit_tests/braket/pulse/test_waveforms.py index 1c8d9e45..699b78f1 100644 --- a/test/unit_tests/braket/pulse/test_waveforms.py +++ b/test/unit_tests/braket/pulse/test_waveforms.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 math import re from copy import deepcopy @@ -21,6 +22,7 @@ from braket.circuits.free_parameter import FreeParameter from braket.pulse import ArbitraryWaveform, ConstantWaveform, DragGaussianWaveform, GaussianWaveform from braket.pulse.ast.qasm_parser import ast_to_qasm +from braket.pulse.waveforms import _parse_waveform_from_calibration_schema @pytest.mark.parametrize( @@ -57,10 +59,10 @@ def test_arbitrary_wf_eq(): assert wf != wfc +@pytest.mark.xfail(raises=TypeError) def test_arbitrary_waveform_not_castable_into_list(): amps = 1 - with pytest.raises(TypeError): - wf = ArbitraryWaveform(amps) + ArbitraryWaveform(amps) def test_constant_waveform(): @@ -260,3 +262,66 @@ def _assert_wf_qasm(waveform, expected_qasm): p = Program(None) p.declare(waveform._to_oqpy_expression()) assert ast_to_qasm(p.to_ast(include_externs=False)) == expected_qasm + + +@pytest.mark.parametrize( + "waveform_json, waveform", + [ + ( + { + "waveformId": "q0_q1_cz_CZ", + "amplitudes": [[0.0, 0.0], [0.0, 0.0]], + }, + ArbitraryWaveform(id="q0_q1_cz_CZ", amplitudes=[complex(0.0, 0.0), complex(0.0, 0.0)]), + ), + ( + { + "waveformId": "wf_drag_gaussian_0", + "name": "drag_gaussian", + "arguments": [ + {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, + {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, + {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, + {"name": "beta", "value": 7.494904522022295e-10, "type": "float"}, + ], + }, + DragGaussianWaveform( + id="wf_drag_gaussian_0", + sigma=6.369913502160144e-9, + length=6.000000000000001e-8, + beta=7.494904522022295e-10, + amplitude=-0.4549282253548838, + ), + ), + ( + { + "waveformId": "wf_gaussian_0", + "name": "gaussian", + "arguments": [ + {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, + {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, + {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, + ], + }, + GaussianWaveform( + id="wf_gaussian_0", + length=6.000000000000001e-8, + sigma=6.369913502160144e-9, + amplitude=-0.4549282253548838, + ), + ), + ( + { + "waveformId": "wf_constant", + "name": "constant", + "arguments": [ + {"name": "length", "value": 2.1, "type": "float"}, + {"name": "iq", "value": 0.23, "type": "complex"}, + ], + }, + ConstantWaveform(id="wf_constant", length=2.1, iq=0.23), + ), + ], +) +def test_parse_waveform_from_calibration_schema(waveform_json, waveform): + assert _parse_waveform_from_calibration_schema(waveform_json) == waveform From 6113645ff6f18455ac9597ee58cfa4b63520e70d Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Wed, 19 Jul 2023 17:10:58 -0400 Subject: [PATCH 0757/1165] fix: copy calibrations in to_ir (#628) --- src/braket/aws/aws_quantum_task.py | 2 +- src/braket/circuits/circuit.py | 2 +- test/unit_tests/braket/circuits/test_circuit.py | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index afa59025..1b40521e 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -604,7 +604,7 @@ def _( openqasm_program = circuit.to_ir( ir_type=IRType.OPENQASM, serialization_properties=serialization_properties, - gate_calibrations=gate_calibrations.copy() if gate_calibrations is not None else None, + gate_calibrations=gate_calibrations, ) if inputs: diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index be19f8a3..2470ad59 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -1133,7 +1133,7 @@ def to_ir( ) return self._to_openqasm( serialization_properties or OpenQASMSerializationProperties(), - gate_calibrations, + gate_calibrations.copy() if gate_calibrations is not None else None, ) else: raise ValueError(f"Supplied ir_type {ir_type} is not supported.") diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 2b34d421..15c19558 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -1001,6 +1001,7 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): ], ) def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir, gate_calibrations): + copy_of_gate_calibrations = gate_calibrations.copy() assert ( circuit.to_ir( ir_type=IRType.OPENQASM, @@ -1009,6 +1010,7 @@ def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir, ) == expected_ir ) + assert copy_of_gate_calibrations.pulse_sequences == gate_calibrations.pulse_sequences def test_parametric_circuit_with_fixed_argument_defcal(pulse_sequence): From 02a0c37c7759824be135a80644ce6c3c0c8a6fb4 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 19 Jul 2023 16:39:52 -0700 Subject: [PATCH 0758/1165] infra: change top level permissions to read for the stale issue workflow (#625) * infra: chagne top level permissions to read for the stale issue workflow --------- Co-authored-by: Abe Coull --- .github/workflows/stale_issue.yml | 5 +++++ test/unit_tests/braket/pulse/test_waveforms.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale_issue.yml b/.github/workflows/stale_issue.yml index 3b19878a..35329683 100644 --- a/.github/workflows/stale_issue.yml +++ b/.github/workflows/stale_issue.yml @@ -6,9 +6,14 @@ on: schedule: - cron: "0 10 * * *" +permissions: + contents: read + jobs: cleanup: runs-on: ubuntu-latest + permissions: + issues: write # comment on issues name: Stale issue job steps: - uses: aws-actions/stale-issue-cleanup@7de35968489e4142233d2a6812519a82e68b5c38 # v6 diff --git a/test/unit_tests/braket/pulse/test_waveforms.py b/test/unit_tests/braket/pulse/test_waveforms.py index 699b78f1..b42eacc0 100644 --- a/test/unit_tests/braket/pulse/test_waveforms.py +++ b/test/unit_tests/braket/pulse/test_waveforms.py @@ -59,10 +59,10 @@ def test_arbitrary_wf_eq(): assert wf != wfc -@pytest.mark.xfail(raises=TypeError) def test_arbitrary_waveform_not_castable_into_list(): amps = 1 - ArbitraryWaveform(amps) + with pytest.raises(TypeError): + ArbitraryWaveform(amps) def test_constant_waveform(): From 313e605af128758ecf3144adc62f465996a2cc82 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 19 Jul 2023 16:56:29 -0700 Subject: [PATCH 0759/1165] fix: handle the optional calibration URL returning None (#627) * fix: handle the optional calibration URL returning None --------- Co-authored-by: Abe Coull --- src/braket/aws/aws_device.py | 28 +++++++---- src/braket/aws/aws_quantum_task.py | 33 ++++++------- src/braket/circuits/circuit.py | 48 +++++++++---------- test/integ_tests/test_pulse.py | 4 +- .../braket/aws/common_test_utils.py | 20 ++++---- test/unit_tests/braket/aws/test_aws_device.py | 8 ++-- .../braket/circuits/test_circuit.py | 8 ++-- 7 files changed, 78 insertions(+), 71 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 48ab2aae..a34facac 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -118,7 +118,7 @@ def run( poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: Optional[float] = None, inputs: Optional[Dict[str, float]] = None, - gate_calibrations: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None, + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTask: @@ -142,8 +142,11 @@ 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: {}. - gate_calibrations (Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]): A `GateCalibrations` for user defined gate - calibrations. + gate_definitions (Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]): A + `Dict[Tuple[Gate, QubitSet], PulseSequence]]` for a user defined gate calibration. + The calibration is defined for a particular `Gate` on a particular `QubitSet` + and is represented by a `PulseSequence`. + Default: None. Returns: AwsQuantumTask: An AwsQuantumTask that tracks the execution on the device. @@ -192,7 +195,7 @@ def run( poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds or self._poll_interval_seconds, inputs=inputs, - gate_calibrations=gate_calibrations, + gate_definitions=gate_definitions, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) @@ -226,7 +229,7 @@ def run_batch( 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, - gate_calibrations: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None, + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTaskBatch: @@ -254,8 +257,11 @@ 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. Default: {}. - gate_calibrations (Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]): A - `Dict[Tuple[Gate, QubitSet], PulseSequence]]` for user defined gate calibrations. + gate_definitions (Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]): A + `Dict[Tuple[Gate, QubitSet], PulseSequence]]` for a user defined gate calibration. + The calibration is defined for a particular `Gate` on a particular `QubitSet` + and is represented by a `PulseSequence`. + Default: None. Returns: AwsQuantumTaskBatch: A batch containing all of the tasks run @@ -280,7 +286,7 @@ def run_batch( poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds or self._poll_interval_seconds, inputs=inputs, - gate_calibrations=gate_calibrations, + gate_definitions=gate_definitions, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) @@ -673,8 +679,10 @@ def refresh_gate_calibrations(self) -> Optional[GateCalibrations]: Optional[GateCalibrations]: the calibration data for the device. None is returned if the device does not have a gate calibrations URL associated. """ - if hasattr(self.properties, "pulse") and hasattr( - self.properties.pulse, "nativeGateCalibrationsRef" + if ( + hasattr(self.properties, "pulse") + and hasattr(self.properties.pulse, "nativeGateCalibrationsRef") + and self.properties.pulse.nativeGateCalibrationsRef ): try: with urllib.request.urlopen( diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 1b40521e..76fa3d56 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, Dict, Optional, Union +from typing import Any, Dict, Optional, Tuple, Union import boto3 @@ -25,10 +25,9 @@ from braket.annealing.problem import Problem from braket.aws.aws_session import AwsSession from braket.circuits import Instruction -from braket.circuits.circuit import Circuit +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.gate_calibrations import GateCalibrations from braket.circuits.gates import PulseGate from braket.circuits.serialization import ( IRType, @@ -104,7 +103,7 @@ def create( disable_qubit_rewiring: bool = False, tags: Dict[str, str] = None, inputs: Dict[str, float] = None, - gate_calibrations: Optional[GateCalibrations] = None, + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None, *args, **kwargs, ) -> AwsQuantumTask: @@ -145,8 +144,10 @@ def create( IR. If the IR supports inputs, the inputs will be updated with this value. Default: {}. - gate_calibrations (Optional[GateCalibrations]): A `GateCalibrations` for - user defined gate calibration. + gate_definitions (Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]): + 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`. Default: None. Returns: @@ -193,7 +194,7 @@ def create( device_parameters or {}, disable_qubit_rewiring, inputs, - gate_calibrations=gate_calibrations, + gate_definitions=gate_definitions, *args, **kwargs, ) @@ -480,7 +481,7 @@ def _create_internal( device_parameters: Union[dict, BraketSchemaBase], disable_qubit_rewiring: bool, inputs: Dict[str, float], - gate_calibrations: Optional[GateCalibrations], + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: @@ -496,7 +497,7 @@ def _( _device_parameters: Union[dict, BraketSchemaBase], # Not currently used for OpenQasmProgram _disable_qubit_rewiring: bool, inputs: Dict[str, float], - gate_calibrations: Optional[GateCalibrations], + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: @@ -514,7 +515,7 @@ def _( device_parameters: Union[dict, BraketSchemaBase], _disable_qubit_rewiring: bool, inputs: Dict[str, float], - gate_calibrations: Optional[GateCalibrations], + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: @@ -553,7 +554,7 @@ def _( _device_parameters: Union[dict, BraketSchemaBase], _disable_qubit_rewiring: bool, inputs: Dict[str, float], - gate_calibrations: Optional[GateCalibrations], + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: @@ -571,7 +572,7 @@ def _( device_parameters: Union[dict, BraketSchemaBase], disable_qubit_rewiring: bool, inputs: Dict[str, float], - gate_calibrations: Optional[GateCalibrations], + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: @@ -592,7 +593,7 @@ def _( if ( disable_qubit_rewiring or Instruction(StartVerbatimBox()) in circuit.instructions - or gate_calibrations is not None + or gate_definitions is not None or any(isinstance(instruction.operator, PulseGate) for instruction in circuit.instructions) ): qubit_reference_type = QubitReferenceType.PHYSICAL @@ -604,7 +605,7 @@ def _( openqasm_program = circuit.to_ir( ir_type=IRType.OPENQASM, serialization_properties=serialization_properties, - gate_calibrations=gate_calibrations, + gate_definitions=gate_definitions, ) if inputs: @@ -639,7 +640,7 @@ def _( ], _, inputs: Dict[str, float], - gate_calibrations: Optional[GateCalibrations], + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: @@ -664,7 +665,7 @@ def _( device_parameters: dict, _, inputs: Dict[str, float], - gate_calibrations: Optional[GateCalibrations], + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 2470ad59..10c0cf8b 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -1098,7 +1098,7 @@ def to_ir( self, ir_type: IRType = IRType.JAQCD, serialization_properties: SerializationProperties = None, - gate_calibrations: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None, + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None, ) -> Union[OpenQasmProgram, JaqcdProgram]: """ Converts the circuit into the canonical intermediate representation. @@ -1110,7 +1110,7 @@ def to_ir( serialization_properties (SerializationProperties): 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_calibrations (Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]): The + gate_definitions (Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]): The calibration data for the device. default: None. Returns: @@ -1133,7 +1133,7 @@ def to_ir( ) return self._to_openqasm( serialization_properties or OpenQASMSerializationProperties(), - gate_calibrations.copy() if gate_calibrations is not None else None, + gate_definitions.copy() if gate_definitions is not None else None, ) else: raise ValueError(f"Supplied ir_type {ir_type} is not supported.") @@ -1174,9 +1174,9 @@ def _to_jaqcd(self) -> JaqcdProgram: def _to_openqasm( self, serialization_properties: OpenQASMSerializationProperties, - gate_calibrations: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], ) -> OpenQasmProgram: - ir_instructions = self._create_openqasm_header(serialization_properties, gate_calibrations) + ir_instructions = self._create_openqasm_header(serialization_properties, gate_definitions) openqasm_ir_type = IRType.OPENQASM ir_instructions.extend( [ @@ -1211,7 +1211,7 @@ def _to_openqasm( def _create_openqasm_header( self, serialization_properties: OpenQASMSerializationProperties, - gate_calibrations: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], ) -> List[str]: ir_instructions = ["OPENQASM 3.0;"] for parameter in self.parameters: @@ -1228,18 +1228,18 @@ def _create_openqasm_header( f"{serialization_properties.qubit_reference_type} supplied." ) - frame_wf_declarations = self._generate_frame_wf_defcal_declarations(gate_calibrations) + frame_wf_declarations = self._generate_frame_wf_defcal_declarations(gate_definitions) if frame_wf_declarations: ir_instructions.append(frame_wf_declarations) return ir_instructions def _validate_gate_calbrations_uniqueness( self, - gate_calibrations: Dict[Tuple[Gate, QubitSet], PulseSequence], + gate_definitions: Dict[Tuple[Gate, QubitSet], PulseSequence], frames: Dict[Frame], waveforms: Dict[ArbitraryWaveform], ) -> None: - for key, calibration in gate_calibrations.items(): + for key, calibration in gate_definitions.items(): for frame in calibration._frames.values(): _validate_uniqueness(frames, frame) frames[frame.id] = frame @@ -1248,24 +1248,24 @@ def _validate_gate_calbrations_uniqueness( waveforms[waveform.id] = waveform def _generate_frame_wf_defcal_declarations( - self, gate_calibrations: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] + self, gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] ) -> Optional[str]: program = oqpy.Program(None) - frames, waveforms = self._get_frames_waveforms_from_instrs(gate_calibrations) + frames, waveforms = self._get_frames_waveforms_from_instrs(gate_definitions) - if gate_calibrations is not None: - self._validate_gate_calbrations_uniqueness(gate_calibrations, frames, waveforms) + if gate_definitions is not None: + self._validate_gate_calbrations_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_calibrations is not None: + if declarable_frames or waveforms or gate_definitions is not None: 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_calibrations is not None: - for key, calibration in gate_calibrations.items(): + if gate_definitions is not None: + for key, calibration in gate_definitions.items(): gate, qubits = key # Ignoring parametric gates @@ -1294,7 +1294,7 @@ def _generate_frame_wf_defcal_declarations( return None def _get_frames_waveforms_from_instrs( - self, gate_calibrations: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] + self, gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] ) -> Tuple[Dict[Frame], Dict[ArbitraryWaveform]]: from braket.circuits.gates import PulseGate @@ -1309,18 +1309,16 @@ 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_calibrations is not None - ): + elif isinstance(instruction.operator, Parameterizable) and gate_definitions is not None: fixed_argument_calibrations = self._add_fixed_argument_calibrations( - gate_calibrations, instruction + gate_definitions, instruction ) - gate_calibrations.update(fixed_argument_calibrations) + gate_definitions.update(fixed_argument_calibrations) return frames, waveforms def _add_fixed_argument_calibrations( self, - gate_calibrations: Dict[Tuple[Gate, QubitSet], PulseSequence], + gate_definitions: Dict[Tuple[Gate, QubitSet], PulseSequence], instruction: Instruction, ) -> Dict[Tuple[Gate, QubitSet], PulseSequence]: """Adds calibrations with arguments set to the instruction parameter values @@ -1335,7 +1333,7 @@ def _add_fixed_argument_calibrations( If N=0, we ignore it as it will not be removed by _generate_frame_wf_defcal_declarations. Args: - gate_calibrations (Dict[Tuple[Gate, QubitSet], PulseSequence]): a dictionary of + gate_definitions (Dict[Tuple[Gate, QubitSet], PulseSequence]): a dictionary of calibrations instruction (Instruction): a Circuit instruction @@ -1349,7 +1347,7 @@ def _add_fixed_argument_calibrations( parameters. """ additional_calibrations = {} - for key, calibration in gate_calibrations.items(): + for key, calibration in gate_definitions.items(): gate = key[0] target = key[1] if target != instruction.target: diff --git a/test/integ_tests/test_pulse.py b/test/integ_tests/test_pulse.py index 48746a78..c40a4556 100644 --- a/test/integ_tests/test_pulse.py +++ b/test/integ_tests/test_pulse.py @@ -315,13 +315,13 @@ def test_gate_calibration_run(device, pulse_sequence): bell_circuit = Circuit().rx(0, math.pi / 2).rx(1, math.pi / 2).cz(0, 1).rx(1, -math.pi / 2) user_calibration_task = device.run( bell_circuit, - gate_calibrations=user_gate_calibrations.pulse_sequences, + gate_definitions=user_gate_calibrations.pulse_sequences, shots=num_shots, disable_qubit_rewiring=True, ) device_calibration_task = device.run( bell_circuit, - gate_calibrations=device.gate_calibrations.pulse_sequences, + gate_definitions=device.gate_calibrations.pulse_sequences, shots=num_shots, disable_qubit_rewiring=True, ) diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 4ad01ce2..5dcec5fb 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -200,7 +200,7 @@ def run_and_assert( poll_timeout_seconds, # Treated as positional arg poll_interval_seconds, # Treated as positional arg inputs, # Treated as positional arg - gate_calibrations, # Treated as positional arg + gate_definitions, # Treated as positional arg extra_args, extra_kwargs, ): @@ -218,8 +218,8 @@ def run_and_assert( run_args.append(poll_interval_seconds) if inputs is not None: run_args.append(inputs) - if gate_calibrations is not None: - run_args.append(gate_calibrations) + if gate_definitions is not None: + run_args.append(gate_definitions) run_args += extra_args if extra_args else [] run_kwargs = extra_kwargs or {} @@ -236,7 +236,7 @@ def run_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, - gate_calibrations, + gate_definitions, extra_args, extra_kwargs, ) @@ -262,7 +262,7 @@ def run_batch_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, - gate_calibrations, + gate_definitions, extra_args, extra_kwargs, ): @@ -287,8 +287,8 @@ def run_batch_and_assert( run_args.append(poll_interval_seconds) if inputs is not None: run_args.append(inputs) - if gate_calibrations is not None: - run_args.append(gate_calibrations) + if gate_definitions is not None: + run_args.append(gate_definitions) run_args += extra_args if extra_args else [] run_kwargs = extra_kwargs or {} @@ -305,7 +305,7 @@ def run_batch_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, - gate_calibrations, + gate_definitions, extra_args, extra_kwargs, ) @@ -332,7 +332,7 @@ def _create_task_args_and_kwargs( poll_timeout_seconds, poll_interval_seconds, inputs, - gate_calibrations, + gate_definitions, extra_args, extra_kwargs, ): @@ -351,7 +351,7 @@ def _create_task_args_and_kwargs( if poll_interval_seconds is not None else default_poll_interval, "inputs": inputs, - "gate_calibrations": gate_calibrations, + "gate_definitions": gate_definitions, } ) 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 ff59e5bc..40abf638 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -1484,7 +1484,7 @@ def _run_and_assert( poll_timeout_seconds=None, # Treated as positional arg poll_interval_seconds=None, # Treated as positional arg inputs=None, # Treated as positional arg - gate_calibrations=None, # Treated as positional arg + gate_definitions=None, # Treated as positional arg extra_args=None, extra_kwargs=None, ): @@ -1501,7 +1501,7 @@ def _run_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, - gate_calibrations, + gate_definitions, extra_args, extra_kwargs, ) @@ -1519,7 +1519,7 @@ def _run_batch_and_assert( poll_timeout_seconds=None, # Treated as a positional arg poll_interval_seconds=None, # Treated as positional arg inputs=None, # Treated as positional arg - gate_calibrations=None, # Treated as positional arg + gate_definitions=None, # Treated as positional arg extra_args=None, extra_kwargs=None, ): @@ -1539,7 +1539,7 @@ def _run_batch_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, - gate_calibrations, + gate_definitions, extra_args, extra_kwargs, ) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 15c19558..baf9ec8c 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -1006,7 +1006,7 @@ def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir, circuit.to_ir( ir_type=IRType.OPENQASM, serialization_properties=serialization_properties, - gate_calibrations=gate_calibrations.pulse_sequences, + gate_definitions=gate_calibrations.pulse_sequences, ) == expected_ir ) @@ -1057,7 +1057,7 @@ def test_parametric_circuit_with_fixed_argument_defcal(pulse_sequence): circ.to_ir( ir_type=IRType.OPENQASM, serialization_properties=serialization_properties, - gate_calibrations=gate_calibrations.pulse_sequences, + gate_definitions=gate_calibrations.pulse_sequences, ) == expected_ir ) @@ -1077,7 +1077,7 @@ def test_circuit_with_partial_calibrations(pulse_sequence_2): circuit.to_ir( ir_type=IRType.OPENQASM, serialization_properties=serialization_properties, - gate_calibrations=gate_calibrations.pulse_sequences, + gate_definitions=gate_calibrations.pulse_sequences, ) @@ -1151,7 +1151,7 @@ def foo( circ.to_ir( ir_type=IRType.OPENQASM, serialization_properties=serialization_properties, - gate_calibrations=gate_calibrations.pulse_sequences, + gate_definitions=gate_calibrations.pulse_sequences, ) == expected_ir ) From f9005391f174d3e79689b3657e23e0695a27b66e Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Thu, 20 Jul 2023 09:55:37 -0700 Subject: [PATCH 0760/1165] change: making additional meta available in AHS results (#629) --- ...iltonian_simulation_quantum_task_result.py | 8 +- ...alog_hamiltonian_simulation_task_result.py | 73 ++++++++++++++++++- 2 files changed, 76 insertions(+), 5 deletions(-) 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 37346fbc..74c0e848 100644 --- a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py +++ b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py @@ -20,7 +20,11 @@ import numpy as np -from braket.task_result import AnalogHamiltonianSimulationTaskResult, TaskMetadata +from braket.task_result import ( + AdditionalMetadata, + AnalogHamiltonianSimulationTaskResult, + TaskMetadata, +) class AnalogHamiltonianSimulationShotStatus(str, Enum): @@ -48,6 +52,7 @@ def __eq__(self, other) -> bool: @dataclass class AnalogHamiltonianSimulationQuantumTaskResult: task_metadata: TaskMetadata + additional_metadata: AdditionalMetadata measurements: List[ShotResult] = None def __eq__(self, other) -> bool: @@ -80,6 +85,7 @@ def _from_object_internal( measurements = None return cls( task_metadata=result.taskMetadata, + additional_metadata=result.additionalMetadata, measurements=measurements, ) 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 3d60843d..23cca78b 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 @@ -14,7 +14,9 @@ import numpy as np import pytest +from braket.ir.ahs import Program from braket.task_result import ( + AdditionalMetadata, AnalogHamiltonianSimulationShotMeasurement, AnalogHamiltonianSimulationShotMetadata, AnalogHamiltonianSimulationShotResult, @@ -33,6 +35,62 @@ def task_metadata(): return TaskMetadata(**{"id": "task_arn", "deviceId": "arn1", "shots": 100}) +@pytest.fixture +def additional_metadata(): + return AdditionalMetadata( + action=Program( + setup={ + "ahs_register": { + "sites": [ + [0.0, 0.0], + [0.0, 3.0e-6], + [0.0, 6.0e-6], + [3.0e-6, 0.0], + [3.0e-6, 3.0e-6], + [3.0e-6, 6.0e-6], + ], + "filling": [1, 1, 1, 1, 0, 0], + } + }, + hamiltonian={ + "drivingFields": [ + { + "amplitude": { + "time_series": { + "values": [0.0, 2.51327e7, 2.51327e7, 0.0], + "times": [0.0, 3.0e-7, 2.7e-6, 3.0e-6], + }, + "pattern": "uniform", + }, + "phase": { + "time_series": {"values": [0, 0], "times": [0.0, 3.0e-6]}, + "pattern": "uniform", + }, + "detuning": { + "time_series": { + "values": [-1.25664e8, -1.25664e8, 1.25664e8, 1.25664e8], + "times": [0.0, 3.0e-7, 2.7e-6, 3.0e-6], + }, + "pattern": "uniform", + }, + } + ], + "shiftingFields": [ + { + "magnitude": { + "time_series": { + "values": [-1.25664e8, 1.25664e8], + "times": [0.0, 3.0e-6], + }, + "pattern": [0.5, 1.0, 0.5, 0.5, 0.5, 0.5], + } + } + ], + }, + ) + ) + + @pytest.fixture def success_measurement(): return AnalogHamiltonianSimulationShotMeasurement( @@ -90,27 +148,30 @@ def measurements_extended( @pytest.fixture -def result_str_1(task_metadata, measurements): +def result_str_1(task_metadata, additional_metadata, measurements): result = AnalogHamiltonianSimulationTaskResult( taskMetadata=task_metadata, + additionalMetadata=additional_metadata, measurements=measurements, ) return result.json() @pytest.fixture -def result_str_2(task_metadata, measurements): +def result_str_2(task_metadata, additional_metadata, measurements): result = AnalogHamiltonianSimulationTaskResult( taskMetadata=task_metadata, + additionalMetadata=additional_metadata, measurements=None, ) return result.json() @pytest.fixture -def result_str_3(task_metadata, measurements_extended): +def result_str_3(task_metadata, additional_metadata, measurements_extended): result = AnalogHamiltonianSimulationTaskResult( taskMetadata=task_metadata, + additionalMetadata=additional_metadata, measurements=measurements_extended, ) return result.json() @@ -127,6 +188,7 @@ def validate_result_from_str_1(result): assert result.measurements[2].status == AnalogHamiltonianSimulationShotStatus.FAILURE assert result.measurements[2].pre_sequence is None assert result.measurements[2].post_sequence is None + assert result.additional_metadata.action is not None def test_from_object(result_str_1, task_metadata): @@ -160,6 +222,7 @@ def test_equality(task_metadata, result_str_1, result_str_2): assert result_1 != other_result assert result_1 != AnalogHamiltonianSimulationQuantumTaskResult( task_metadata=task_metadata, + additional_metadata=additional_metadata, measurements=[result_1.measurements[1], result_1.measurements[0]], ) assert result_1 != non_result @@ -177,7 +240,9 @@ def test_get_counts(result_str_3): def test_get_counts_failed_task(task_metadata): measurement = ShotResult(AnalogHamiltonianSimulationShotStatus.FAILURE, [], []) result = AnalogHamiltonianSimulationQuantumTaskResult( - task_metadata=task_metadata, measurements=[measurement] + task_metadata=task_metadata, + additional_metadata=additional_metadata, + measurements=[measurement], ) counts = result.get_counts() From 2c2a62797a95702f105b3d0227bc46011c794756 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 20 Jul 2023 14:11:32 -0700 Subject: [PATCH 0761/1165] fix: revert adding gate calibration data (#631) --- setup.py | 4 +- src/braket/aws/aws_device.py | 155 +------ src/braket/aws/aws_quantum_task.py | 24 +- src/braket/circuits/__init__.py | 1 - src/braket/circuits/angled_gate.py | 18 - src/braket/circuits/circuit.py | 176 +------- src/braket/circuits/gate.py | 3 - src/braket/circuits/gate_calibrations.py | 161 -------- src/braket/circuits/gates.py | 3 - .../parametric/free_parameter_expression.py | 18 - src/braket/pulse/ast/approximation_parser.py | 2 - src/braket/pulse/pulse_sequence.py | 75 ---- src/braket/pulse/waveforms.py | 78 +--- test/integ_tests/test_device_creation.py | 6 - test/integ_tests/test_pulse.py | 60 +-- .../braket/aws/common_test_utils.py | 10 - test/unit_tests/braket/aws/test_aws_device.py | 384 +----------------- .../braket/circuits/test_angled_gate.py | 20 - .../braket/circuits/test_circuit.py | 324 +-------------- .../braket/circuits/test_gate_calibration.py | 148 ------- test/unit_tests/braket/circuits/test_gates.py | 6 - .../pulse/ast/test_approximation_parser.py | 11 - .../braket/pulse/test_pulse_sequence.py | 83 ---- .../unit_tests/braket/pulse/test_waveforms.py | 65 --- 24 files changed, 49 insertions(+), 1786 deletions(-) delete mode 100644 src/braket/circuits/gate_calibrations.py delete mode 100644 test/unit_tests/braket/circuits/test_gate_calibration.py diff --git a/setup.py b/setup.py index 33ab9c8e..226189ee 100644 --- a/setup.py +++ b/setup.py @@ -27,9 +27,9 @@ packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "amazon-braket-schemas>=1.19.0", + "amazon-braket-schemas>=1.18.0", "amazon-braket-default-simulator>=1.18.1", - "oqpy~=0.2.1", + "oqpy~=0.1.1", "setuptools", "backoff", "boltons", diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index a34facac..51fd40ce 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -13,13 +13,11 @@ from __future__ import annotations -import importlib import json import os -import urllib.request from datetime import datetime from enum import Enum -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, Optional, Union from botocore.errorfactory import ClientError from networkx import DiGraph, complete_graph, from_edgelist @@ -29,8 +27,7 @@ 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.circuits import Circuit, Gate, QubitSet -from braket.circuits.gate_calibrations import GateCalibrations +from braket.circuits import Circuit 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 @@ -39,10 +36,7 @@ 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.pulse import Frame, Port, PulseSequence from braket.schema_common import BraketSchemaBase @@ -68,14 +62,6 @@ class AwsDevice(Device): _GET_DEVICES_ORDER_BY_KEYS = frozenset({"arn", "name", "type", "provider_name", "status"}) - _RIGETTI_GATES_TO_BRAKET = { - # Rx_12 does not exist in the Braket SDK, it is a gate between |1> and |2>. - "Rx_12": None, - "Cz": "CZ", - "Cphaseshift": "CPhaseShift", - "Xy": "XY", - } - def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): """ Args: @@ -94,7 +80,6 @@ def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): """ super().__init__(name=None, status=None) self._arn = arn - self._gate_calibrations = None self._properties = None self._provider_name = None self._poll_interval_seconds = None @@ -118,7 +103,6 @@ def run( 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, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTask: @@ -142,11 +126,6 @@ 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: {}. - gate_definitions (Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]): A - `Dict[Tuple[Gate, QubitSet], PulseSequence]]` for a user defined gate calibration. - The calibration is defined for a particular `Gate` on a particular `QubitSet` - and is represented by a `PulseSequence`. - Default: None. Returns: AwsQuantumTask: An AwsQuantumTask that tracks the execution on the device. @@ -195,7 +174,6 @@ def run( poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds or self._poll_interval_seconds, inputs=inputs, - gate_definitions=gate_definitions, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) @@ -229,7 +207,6 @@ def run_batch( 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, - gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTaskBatch: @@ -257,11 +234,6 @@ 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. Default: {}. - gate_definitions (Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]): A - `Dict[Tuple[Gate, QubitSet], PulseSequence]]` for a user defined gate calibration. - The calibration is defined for a particular `Gate` on a particular `QubitSet` - and is represented by a `PulseSequence`. - Default: None. Returns: AwsQuantumTaskBatch: A batch containing all of the tasks run @@ -286,7 +258,6 @@ def run_batch( poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds or self._poll_interval_seconds, inputs=inputs, - gate_definitions=gate_definitions, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) @@ -378,20 +349,6 @@ def arn(self) -> str: """str: Return the ARN of the device""" return self._arn - @property - def gate_calibrations(self) -> Optional[GateCalibrations]: - """ - 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: - Optional[GateCalibrations]: The calibration object. Returns `None` if the data - is not present. - """ - if not self._gate_calibrations: - self._gate_calibrations = self.refresh_gate_calibrations() - return self._gate_calibrations - @property def is_available(self) -> bool: """Returns true if the device is currently available. @@ -408,7 +365,10 @@ def is_available(self) -> bool: weekday = current_datetime_utc.weekday() current_time_utc = current_datetime_utc.time().replace(microsecond=0) - if current_time_utc < execution_window.windowEndHour < execution_window.windowStartHour: + if ( + execution_window.windowEndHour < execution_window.windowStartHour + and current_time_utc < execution_window.windowEndHour + ): weekday = (weekday - 1) % 7 matched_day = execution_window.executionDay == ExecutionDay.EVERYDAY @@ -652,9 +612,6 @@ def get_device_region(device_arn: str) -> str: Args: device_arn (str): The device ARN. - Raises: - ValueError: Raised if the ARN is not properly formatted - Returns: str: the region of the ARN. """ @@ -665,101 +622,3 @@ def get_device_region(device_arn: str) -> str: 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'" ) - - def refresh_gate_calibrations(self) -> Optional[GateCalibrations]: - """ - Refreshes the gate calibration data upon request. - - If the device does not have calibration data, None is returned. - - Raises: - URLError: If the URL provided returns a non 2xx response. - - Returns: - Optional[GateCalibrations]: the calibration data for the device. None - is returned if the device does not have a gate calibrations URL associated. - """ - if ( - hasattr(self.properties, "pulse") - and hasattr(self.properties.pulse, "nativeGateCalibrationsRef") - and self.properties.pulse.nativeGateCalibrationsRef - ): - try: - with urllib.request.urlopen( - self.properties.pulse.nativeGateCalibrationsRef.split("?")[0] - ) as f: - json_calibration_data = self._parse_calibration_json( - json.loads(f.read().decode("utf-8")) - ) - return GateCalibrations(json_calibration_data) - except urllib.error.URLError: - raise urllib.error.URLError( - f"Unable to reach {self.properties.pulse.nativeGateCalibrationsRef}" - ) - else: - return None - - def _parse_waveforms(self, waveforms_json: Dict) -> Dict: - waveforms = dict() - for waveform in waveforms_json: - parsed_waveform = _parse_waveform_from_calibration_schema(waveforms_json[waveform]) - waveforms[parsed_waveform.id] = parsed_waveform - return waveforms - - def _parse_pulse_sequence( - self, calibration: Dict, waveforms: Dict[ArbitraryWaveform] - ) -> PulseSequence: - return PulseSequence._parse_from_calibration_schema(calibration, waveforms, self.frames) - - 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 - corresponding `Dict[Tuple[Gate, QubitSet], PulseSequence]` to represent the calibration data. - - Args: - calibration_data (Dict): The data to be parsed. Based on - https://github.com/aws/amazon-braket-schemas-python/blob/main/src/braket/device_schema/pulse/native_gate_calibrations_v1.py. - - Returns: - Dict[Tuple[Gate, QubitSet], PulseSequence]: The - structured data based on a mapping of `Tuple[Gate, Qubit]` to its calibration repesented as a - `PulseSequence`. - - """ # noqa: E501 - waveforms = self._parse_waveforms(calibration_data["waveforms"]) - parsed_calibration_data = {} - for qubit_node in calibration_data["gates"]: - qubit = calibration_data["gates"][qubit_node] - for gate_node in qubit: - for gate in qubit[gate_node]: - gate_capitalized = getattr( - self, - f"_{self.provider_name.upper()}_GATES_TO_BRAKET", - {}, - ).get(gate_node.capitalize(), gate_node.capitalize()) - gate_obj = ( - getattr(importlib.import_module("braket.circuits.gates"), gate_capitalized) - if gate_capitalized is not None - else None - ) - qubits = QubitSet([int(x) for x in gate["qubits"]]) - if gate_obj is None: - # We drop out gates that are not implemented in the BDK - continue - - argument = None - if gate["arguments"]: - argument = ( - float(gate["arguments"][0]) - if _is_float(gate["arguments"][0]) - else FreeParameter(gate["arguments"][0]) - ) - gate_qubit_key = ( - (gate_obj(argument), qubits) if argument else (gate_obj(), qubits) - ) - gate_qubit_pulse = self._parse_pulse_sequence(gate["calibrations"], waveforms) - parsed_calibration_data[gate_qubit_key] = gate_qubit_pulse - - return parsed_calibration_data diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 76fa3d56..7c65c57f 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, Dict, Optional, Tuple, Union +from typing import Any, Dict, Union import boto3 @@ -25,7 +25,7 @@ from braket.annealing.problem import Problem from braket.aws.aws_session import AwsSession from braket.circuits import Instruction -from braket.circuits.circuit import Circuit, Gate, QubitSet +from braket.circuits.circuit import Circuit from braket.circuits.circuit_helpers import validate_circuit_and_shots from braket.circuits.compiler_directives import StartVerbatimBox from braket.circuits.gates import PulseGate @@ -103,7 +103,6 @@ def create( disable_qubit_rewiring: bool = False, tags: Dict[str, str] = None, inputs: Dict[str, float] = None, - gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None, *args, **kwargs, ) -> AwsQuantumTask: @@ -144,12 +143,6 @@ 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]]): - 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`. - Default: None. - Returns: AwsQuantumTask: AwsQuantumTask tracking the task execution on the device. @@ -194,7 +187,6 @@ def create( device_parameters or {}, disable_qubit_rewiring, inputs, - gate_definitions=gate_definitions, *args, **kwargs, ) @@ -481,7 +473,6 @@ def _create_internal( device_parameters: Union[dict, BraketSchemaBase], disable_qubit_rewiring: bool, inputs: Dict[str, float], - gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: @@ -497,7 +488,6 @@ 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]], *args, **kwargs, ) -> AwsQuantumTask: @@ -515,7 +505,6 @@ def _( device_parameters: Union[dict, BraketSchemaBase], _disable_qubit_rewiring: bool, inputs: Dict[str, float], - gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: @@ -554,7 +543,6 @@ def _( _device_parameters: Union[dict, BraketSchemaBase], _disable_qubit_rewiring: bool, inputs: Dict[str, float], - gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: @@ -572,7 +560,6 @@ def _( device_parameters: Union[dict, BraketSchemaBase], disable_qubit_rewiring: bool, inputs: Dict[str, float], - gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: @@ -593,7 +580,6 @@ def _( if ( disable_qubit_rewiring or Instruction(StartVerbatimBox()) in circuit.instructions - or gate_definitions is not None or any(isinstance(instruction.operator, PulseGate) for instruction in circuit.instructions) ): qubit_reference_type = QubitReferenceType.PHYSICAL @@ -603,9 +589,7 @@ def _( ) openqasm_program = circuit.to_ir( - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - gate_definitions=gate_definitions, + ir_type=IRType.OPENQASM, serialization_properties=serialization_properties ) if inputs: @@ -640,7 +624,6 @@ def _( ], _, inputs: Dict[str, float], - gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: @@ -665,7 +648,6 @@ def _( device_parameters: dict, _, inputs: Dict[str, float], - gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py index d2788746..a6e185d9 100644 --- a/src/braket/circuits/__init__.py +++ b/src/braket/circuits/__init__.py @@ -27,7 +27,6 @@ from braket.circuits.free_parameter import FreeParameter # noqa: F401 from braket.circuits.free_parameter_expression import FreeParameterExpression # noqa: F401 from braket.circuits.gate import Gate # noqa: F401 -from braket.circuits.gate_calibrations import GateCalibrations # noqa: F401 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 diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index f45bf616..4c148612 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -119,9 +119,6 @@ def __eq__(self, other): def __repr__(self): return f"{self.name}('angle': {self.angle}, 'qubit_count': {self.qubit_count})" - def __hash__(self): - return hash((self.name, self.angle, self.qubit_count)) - class DoubleAngledGate(Gate, Parameterizable): """ @@ -234,9 +231,6 @@ def __repr__(self): f"'qubit_count': {self.qubit_count})" ) - def __hash__(self): - return hash((self.name, self.angle_1, self.angle_2, self.qubit_count)) - class TripleAngledGate(Gate, Parameterizable): """ @@ -364,9 +358,6 @@ def __repr__(self): f"'qubit_count': {self.qubit_count})" ) - def __hash__(self): - return hash((self.name, self.angle_1, self.angle_2, self.angle_3, self.qubit_count)) - @singledispatch def _angles_equal( @@ -412,15 +403,6 @@ def _multi_angled_ascii_characters( """ def format_string(angle: Union[FreeParameterExpression, float]) -> str: - """ - Formats an angle for ASCII representation. - - Args: - angle (Union[FreeParameterExpression, float]): The angle to format. - - Returns: - str: The ASCII representation of the angle. - """ 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/circuit.py b/src/braket/circuits/circuit.py index 10c0cf8b..38d6793f 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -18,7 +18,7 @@ from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar, Union import numpy as np -import oqpy +from oqpy import Program as OqpyProgram from braket.circuits import compiler_directives from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram @@ -57,9 +57,8 @@ 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.pulse_sequence import PulseSequence, _validate_uniqueness +from braket.pulse.pulse_sequence import _validate_uniqueness SubroutineReturn = TypeVar( "SubroutineReturn", Iterable[Instruction], Instruction, ResultType, Iterable[ResultType] @@ -1098,7 +1097,6 @@ def to_ir( self, ir_type: IRType = IRType.JAQCD, serialization_properties: SerializationProperties = None, - gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None, ) -> Union[OpenQasmProgram, JaqcdProgram]: """ Converts the circuit into the canonical intermediate representation. @@ -1110,8 +1108,6 @@ def to_ir( serialization_properties (SerializationProperties): 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 - calibration data for the device. default: None. Returns: Union[OpenQasmProgram, JaqcdProgram]: A representation of the circuit in the @@ -1131,10 +1127,7 @@ 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 gate_definitions is not None else None, - ) + return self._to_openqasm(serialization_properties or OpenQASMSerializationProperties()) else: raise ValueError(f"Supplied ir_type {ir_type} is not supported.") @@ -1172,11 +1165,9 @@ def _to_jaqcd(self) -> JaqcdProgram: ) def _to_openqasm( - self, - serialization_properties: OpenQASMSerializationProperties, - gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], + self, serialization_properties: OpenQASMSerializationProperties ) -> OpenQasmProgram: - ir_instructions = self._create_openqasm_header(serialization_properties, gate_definitions) + ir_instructions = self._create_openqasm_header(serialization_properties) openqasm_ir_type = IRType.OPENQASM ir_instructions.extend( [ @@ -1209,9 +1200,7 @@ def _to_openqasm( return OpenQasmProgram.construct(source="\n".join(ir_instructions), inputs={}) def _create_openqasm_header( - self, - serialization_properties: OpenQASMSerializationProperties, - gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], + self, serialization_properties: OpenQASMSerializationProperties ) -> List[str]: ir_instructions = ["OPENQASM 3.0;"] for parameter in self.parameters: @@ -1228,78 +1217,16 @@ def _create_openqasm_header( f"{serialization_properties.qubit_reference_type} supplied." ) - frame_wf_declarations = self._generate_frame_wf_defcal_declarations(gate_definitions) + frame_wf_declarations = self._generate_frame_wf_declarations() if frame_wf_declarations: ir_instructions.append(frame_wf_declarations) return ir_instructions - def _validate_gate_calbrations_uniqueness( - self, - gate_definitions: Dict[Tuple[Gate, QubitSet], PulseSequence], - frames: Dict[Frame], - waveforms: Dict[ArbitraryWaveform], - ) -> None: - for key, calibration in gate_definitions.items(): - for frame in calibration._frames.values(): - _validate_uniqueness(frames, frame) - frames[frame.id] = frame - for waveform in calibration._waveforms.values(): - _validate_uniqueness(waveforms, waveform) - waveforms[waveform.id] = waveform - - def _generate_frame_wf_defcal_declarations( - self, gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] - ) -> Optional[str]: - program = oqpy.Program(None) - - 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) - - # 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: - 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 = ( - [calibration._format_parameter_ast(value) for value in gate.parameters] - if isinstance(gate, Parameterizable) - else None - ) - 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) - - return None - - def _get_frames_waveforms_from_instrs( - self, gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] - ) -> Tuple[Dict[Frame], Dict[ArbitraryWaveform]]: - from braket.circuits.gates import PulseGate - + def _generate_frame_wf_declarations(self) -> Optional[str]: frames = {} waveforms = {} + from braket.circuits.gates import PulseGate + for instruction in self.instructions: if isinstance(instruction.operator, PulseGate): for frame in instruction.operator.pulse_sequence._frames.values(): @@ -1308,79 +1235,18 @@ def _get_frames_waveforms_from_instrs( for waveform in instruction.operator.pulse_sequence._waveforms.values(): _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: - fixed_argument_calibrations = self._add_fixed_argument_calibrations( - gate_definitions, instruction - ) - gate_definitions.update(fixed_argument_calibrations) - return frames, waveforms - - def _add_fixed_argument_calibrations( - self, - gate_definitions: Dict[Tuple[Gate, QubitSet], PulseSequence], - instruction: Instruction, - ) -> Dict[Tuple[Gate, QubitSet], PulseSequence]: - """Adds calibrations with arguments set to the instruction parameter values - - Given the collection of parameters in instruction.operator, this function looks for matching - parametric calibrations that have free parameters. If such a calibration is found and the - number N of its free parameters equals the number of instruction parameters, we can bind - the arguments of the calibration and add it to the calibration dictionary. - - If N is smaller, it is probably impossible to assign the instruction parameter values to the - corresponding calibration parameters so we raise an error. - If N=0, we ignore it as it will not be removed by _generate_frame_wf_defcal_declarations. - Args: - gate_definitions (Dict[Tuple[Gate, QubitSet], PulseSequence]): a dictionary of - calibrations - instruction (Instruction): a Circuit instruction - - Returns: - Dict[Tuple[Gate, QubitSet], PulseSequence]: additional calibrations - - Raises: - NotImplementedError: in two cases: (i) if the instruction contains unbound parameters - and the calibration dictionary contains a parametric calibration applicable to this - instructions; (ii) if the calibration is defined with a partial number of unbound - parameters. - """ - additional_calibrations = {} - for key, calibration in gate_definitions.items(): - gate = key[0] - target = key[1] - if target != instruction.target: - continue - if isinstance(gate, type(instruction.operator)) and len( - instruction.operator.parameters - ) == len(gate.parameters): - free_parameter_number = sum( - [isinstance(p, FreeParameterExpression) for p in gate.parameters] - ) - if free_parameter_number == 0: - continue - elif free_parameter_number < len(gate.parameters): - raise NotImplementedError( - "Calibrations with a partial number of fixed parameters are not supported." - ) - elif any( - isinstance(p, FreeParameterExpression) for p in instruction.operator.parameters - ): - raise NotImplementedError( - "Parametric calibrations cannot be attached with parametric circuits." - ) - bound_key = ( - 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) - } - ) - return additional_calibrations + # 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: + program = OqpyProgram(None) + for f in declarable_frames: + program.declare(f._to_oqpy_expression()) + for wf in waveforms.values(): + program.declare(wf._to_oqpy_expression()) + ast = program.to_ast(encal=True, include_externs=False) + return ast_to_qasm(ast) + return None def as_unitary(self) -> np.ndarray: r""" diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index 81f2499d..eaf9675f 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -210,9 +210,6 @@ def __eq__(self, other): def __repr__(self): return f"{self.name}('qubit_count': {self._qubit_count})" - def __hash__(self): - return hash((self.name, self.qubit_count)) - @classmethod def register_gate(cls, gate: Type[Gate]) -> None: """Register a gate implementation by adding it into the Gate class. diff --git a/src/braket/circuits/gate_calibrations.py b/src/braket/circuits/gate_calibrations.py deleted file mode 100644 index 01694a1e..00000000 --- a/src/braket/circuits/gate_calibrations.py +++ /dev/null @@ -1,161 +0,0 @@ -# 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 copy import deepcopy -from typing import Any, Dict, List, Optional, Tuple - -from braket.circuits.gate import Gate -from braket.circuits.qubit_set import QubitSet -from braket.circuits.serialization import ( - IRType, - OpenQASMSerializationProperties, - QubitReferenceType, -) -from braket.pulse.pulse_sequence import PulseSequence - - -class GateCalibrations: - """ - 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 - - def __init__( - self, - pulse_sequences: Dict[Tuple[Gate, QubitSet], PulseSequence], - ): - """ - Args: - 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`. - - Returns: - Dict[Tuple[Gate, QubitSet], PulseSequence]: The calibration data Dictionary. - """ - return self._pulse_sequences - - @pulse_sequences.setter - def pulse_sequences(self, value: Any) -> None: - """ - Sets the mapping of (Gate, Qubit) to the corresponding `PulseSequence`. - - Args: - value(Any): The value for the pulse_sequences property to be set to. - - Raises: - TypeError: Raised if the type is not Dict[Tuple[Gate, QubitSet], PulseSequence] - - """ - if isinstance(value, dict) and all( - isinstance(k[0], Gate) and isinstance(k[1], QubitSet) and isinstance(v, PulseSequence) - for (k, v) in value.items() - ): - self._pulse_sequences = value - else: - raise TypeError( - "The value for pulse_sequence must be of type: " - "Dict[Tuple[Gate, QubitSet], PulseSequence]" - ) - - def copy(self) -> GateCalibrations: - """ - Returns a copy of the object. - - Returns: - GateCalibrations: a copy of the calibrations. - """ - return GateCalibrations(deepcopy(self._pulse_sequences)) - - def __len__(self): - return len(self._pulse_sequences) - - def filter( - self, gates: Optional[List[Gate]] = None, qubits: Optional[QubitSet] = None - ) -> Optional[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. - - Returns: - Optional[GateCalibrations]: A filtered GateCalibrations object. Otherwise, returns - none if no matches are found. - """ # noqa: E501 - keys = self.pulse_sequences.keys() - 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])) - ] - 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: - """ - Returns the defcal representation for the `GateCalibrations` object. - - Args: - calibration_key (Optional[Tuple[Gate, QubitSet]]): An optional key to get a specific defcal. - Default: None - - 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(): - raise ValueError( - f"The key {calibration_key} does not exist in this GateCalibrations object." - ) - return ( - self.pulse_sequences[calibration_key] - .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 - - 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], - ] - ) - - def __eq__(self, other): - 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 ac57d05b..a37e3208 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -2781,9 +2781,6 @@ def __eq__(self, other): return self.matrix_equivalence(other) return False - def __hash__(self): - return hash((self.name, str(self._matrix), self.qubit_count)) - @staticmethod def _transform_matrix_to_ir(matrix: np.ndarray) -> List: return [[[element.real, element.imag] for element in row] for row in matrix.tolist()] diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index 1b64e2dd..eef9f8a6 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -186,21 +186,3 @@ def subs_if_free_parameter(parameter: Any, **kwargs) -> Any: substituted = float(substituted) return substituted return parameter - - -def _is_float(argument: str) -> bool: - """ - Checks if a string can be cast into a float. - - Args: - argument (str): String to check. - - Returns: - bool: Returns true if the string can be cast as a float. False, otherwise. - - """ - try: - float(argument) - return True - except ValueError: - return False diff --git a/src/braket/pulse/ast/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py index 69e9fafa..89b318ea 100644 --- a/src/braket/pulse/ast/approximation_parser.py +++ b/src/braket/pulse/ast/approximation_parser.py @@ -419,8 +419,6 @@ def play(self, node: ast.FunctionCall, context: _ParseState) -> None: amps = self.visit(node.arguments[1], context) 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.") else: raise NotImplementedError frame_data = context.frame_data[frame_id] diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index 3606bbd1..f51531fe 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -13,9 +13,7 @@ from __future__ import annotations -import builtins from copy import deepcopy -from inspect import signature from typing import Any, Dict, List, Set, Union from openpulse import ast @@ -333,72 +331,6 @@ def _format_parameter_ast( return _FreeParameterExpressionIdentifier(parameter) return parameter - 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"), - "expr": FreeParameterExpression, - } - if argument["type"] in nonprimitive_arg_type.keys(): - return nonprimitive_arg_type[argument["type"]](argument["value"]) - else: - 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. - - 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. - - 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']}"): - 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") - return calibration_sequence - def __call__(self, arg: Any = None, **kwargs) -> PulseSequence: """ Implements the call function to easily make a bound PulseSequence. @@ -418,13 +350,6 @@ def __call__(self, arg: Any = None, **kwargs) -> PulseSequence: param_values[str(key)] = val return self.make_bound_pulse_sequence(param_values) - def __eq__(self, other): - return ( - isinstance(other, PulseSequence) - and self.parameters == other.parameters - and self.to_ir() == other.to_ir() - ) - def _validate_uniqueness( mapping: Dict[str, Any], values: Union[Frame, Waveform, List[Frame], List[Waveform]] diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index f298dd3e..f7aeb2d2 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -16,7 +16,7 @@ import random import string from abc import ABC, abstractmethod -from typing import Dict, List, Optional, Union +from typing import List, Optional, Union import numpy as np from oqpy import WaveformVar, bool_, complex128, declare_waveform_generator, duration, float64 @@ -55,19 +55,6 @@ def sample(self, dt: float) -> np.ndarray: 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 - - 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 @@ -107,12 +94,6 @@ def sample(self, dt: float) -> np.ndarray: """ raise NotImplementedError - @staticmethod - def _from_calibration_schema(waveform_json: Dict) -> ArbitraryWaveform: - wave_id = waveform_json["waveformId"] - complex_amplitudes = [complex(i[0], i[1]) for i in waveform_json["amplitudes"]] - return ArbitraryWaveform(complex_amplitudes, wave_id) - class ConstantWaveform(Waveform, Parameterizable): """A constant waveform which holds the supplied `iq` value as its amplitude for the @@ -185,25 +166,6 @@ def sample(self, dt: float) -> np.ndarray: samples = self.iq * np.ones_like(sample_range) return samples - @staticmethod - def _from_calibration_schema(waveform_json: Dict) -> ConstantWaveform: - wave_id = waveform_json["waveformId"] - length = iq = None - for val in waveform_json["arguments"]: - if val["name"] == "length": - length = ( - float(val["value"]) - if val["type"] == "float" - else FreeParameterExpression(val["value"]) - ) - if val["name"] == "iq": - iq = ( - complex(val["value"]) - if val["type"] == "complex" - else FreeParameterExpression(val["value"]) - ) - return ConstantWaveform(length=length, iq=iq, id=wave_id) - class DragGaussianWaveform(Waveform, Parameterizable): """A gaussian waveform with an additional gaussian derivative component and lifting applied.""" @@ -320,17 +282,6 @@ def sample(self, dt: float) -> np.ndarray: ) return samples - @staticmethod - def _from_calibration_schema(waveform_json: Dict) -> DragGaussianWaveform: - 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 DragGaussianWaveform(**waveform_parameters) - class GaussianWaveform(Waveform, Parameterizable): """A waveform with amplitudes following a gaussian distribution for the specified parameters.""" @@ -436,17 +387,6 @@ def sample(self, dt: float) -> np.ndarray: ) return samples - @staticmethod - def _from_calibration_schema(waveform_json: Dict) -> GaussianWaveform: - 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 GaussianWaveform(**waveform_parameters) - def _make_identifier_name() -> str: return "".join([random.choice(string.ascii_letters) for _ in range(10)]) @@ -462,19 +402,3 @@ def _map_to_oqpy_type( else _FreeParameterExpressionIdentifier(parameter) ) return parameter - - -def _parse_waveform_from_calibration_schema(waveform: Dict) -> Waveform: - waveform_names = { - "arbitrary": ArbitraryWaveform._from_calibration_schema, - "drag_gaussian": DragGaussianWaveform._from_calibration_schema, - "gaussian": GaussianWaveform._from_calibration_schema, - "constant": ConstantWaveform._from_calibration_schema, - } - if "amplitudes" in waveform.keys(): - 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") diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 2b8ee77a..c12b899f 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -56,12 +56,6 @@ def test_get_devices_arn(arn): assert results[0].arn == arn -@pytest.mark.parametrize("arn", [(PULSE_ARN)]) -def test_device_gate_calibrations(arn, aws_session): - device = AwsDevice(arn, aws_session=aws_session) - assert device.gate_calibrations - - def test_get_devices_others(): provider_names = ["Amazon Braket"] types = ["SIMULATOR"] diff --git a/test/integ_tests/test_pulse.py b/test/integ_tests/test_pulse.py index c40a4556..3b4694e4 100644 --- a/test/integ_tests/test_pulse.py +++ b/test/integ_tests/test_pulse.py @@ -1,12 +1,10 @@ -import math - import numpy as np import pytest from braket.aws import AwsDevice, AwsQuantumTask -from braket.circuits import Circuit, Gate, GateCalibrations, QubitSet +from braket.circuits import Circuit from braket.parametric import FreeParameter -from braket.pulse import ArbitraryWaveform, Frame, Port, PulseSequence +from braket.pulse import ArbitraryWaveform, PulseSequence @pytest.fixture @@ -146,30 +144,6 @@ def arbitrary_waveform(): ) -@pytest.fixture -def port(): - return Port("test_port_ff", dt=1e-9) - - -@pytest.fixture -def frame_id(device): - return next(iter(device.frames)) - - -@pytest.fixture -def frame(frame_id, port): - return Frame(frame_id, port, 1e6, is_predefined=True) - - -@pytest.fixture -def pulse_sequence(frame, arbitrary_waveform): - return ( - PulseSequence() - .barrier(qubits_or_frames=[frame]) - .play(frame=frame, waveform=arbitrary_waveform) - ) - - 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) @@ -307,33 +281,3 @@ def test_pulse_sequence(arbitrary_waveform, device): ) chi_squared = np.sum((observed - expected) ** 2 / expected) assert chi_squared < 10 # adjust this threshold if test is flaky - - -def test_gate_calibration_run(device, pulse_sequence): - 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) - user_calibration_task = device.run( - bell_circuit, - gate_definitions=user_gate_calibrations.pulse_sequences, - shots=num_shots, - disable_qubit_rewiring=True, - ) - device_calibration_task = device.run( - bell_circuit, - gate_definitions=device.gate_calibrations.pulse_sequences, - shots=num_shots, - disable_qubit_rewiring=True, - ) - - if not device.is_available: - try: - assert user_calibration_task.state not in AwsQuantumTask.TERMINAL_STATES - assert device_calibration_task.state not in AwsQuantumTask.TERMINAL_STATES - finally: - user_calibration_task.cancel() - device_calibration_task.cancel() - return - - assert user_calibration_task.result().measurement_counts - assert device_calibration_task.result().measurement_counts diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 5dcec5fb..cd7d76c3 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -200,7 +200,6 @@ def run_and_assert( poll_timeout_seconds, # Treated as positional arg poll_interval_seconds, # Treated as positional arg inputs, # Treated as positional arg - gate_definitions, # Treated as positional arg extra_args, extra_kwargs, ): @@ -218,8 +217,6 @@ def run_and_assert( run_args.append(poll_interval_seconds) if inputs is not None: run_args.append(inputs) - if gate_definitions is not None: - run_args.append(gate_definitions) run_args += extra_args if extra_args else [] run_kwargs = extra_kwargs or {} @@ -236,7 +233,6 @@ def run_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, - gate_definitions, extra_args, extra_kwargs, ) @@ -262,7 +258,6 @@ def run_batch_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, - gate_definitions, extra_args, extra_kwargs, ): @@ -287,8 +282,6 @@ def run_batch_and_assert( run_args.append(poll_interval_seconds) if inputs is not None: run_args.append(inputs) - if gate_definitions is not None: - run_args.append(gate_definitions) run_args += extra_args if extra_args else [] run_kwargs = extra_kwargs or {} @@ -305,7 +298,6 @@ def run_batch_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, - gate_definitions, extra_args, extra_kwargs, ) @@ -332,7 +324,6 @@ def _create_task_args_and_kwargs( poll_timeout_seconds, poll_interval_seconds, inputs, - gate_definitions, extra_args, extra_kwargs, ): @@ -351,7 +342,6 @@ def _create_task_args_and_kwargs( if poll_interval_seconds is not None else default_poll_interval, "inputs": inputs, - "gate_definitions": gate_definitions, } ) 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 40abf638..a4674e07 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -10,12 +10,10 @@ # 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 from datetime import datetime from unittest.mock import Mock, PropertyMock, patch -from urllib.error import URLError import networkx as nx import pytest @@ -33,15 +31,17 @@ ) from jsonschema import validate -from braket.aws import AwsDevice, AwsDeviceType, AwsQuantumTask -from braket.circuits import Circuit, FreeParameter, Gate, QubitSet -from braket.circuits.gate_calibrations import GateCalibrations +from braket.aws import AwsDevice, AwsDeviceType, AwsQuantumTask, AwsQuantumTaskBatch +from braket.circuits import Circuit, FreeParameter from braket.device_schema.device_execution_window import DeviceExecutionWindow from braket.device_schema.dwave import DwaveDeviceCapabilities +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, +) from braket.device_schema.rigetti import RigettiDeviceCapabilities from braket.device_schema.simulators import GateModelSimulatorDeviceCapabilities from braket.ir.openqasm import Program as OpenQasmProgram -from braket.pulse import DragGaussianWaveform, Frame, Port, PulseSequence +from braket.pulse import Frame, Port MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_1 = { "braketSchemaHeader": { @@ -78,136 +78,6 @@ ) -MOCK_gate_calibrations_JSON = { - "gates": { - "0": { - "cphaseshift": [ - { - "name": "cphaseshift", - "qubits": ["0", "1"], - "arguments": ["-1.5707963267948966"], - "calibrations": [ - { - "name": "barrier", - "arguments": [{"name": "qubit", "value": "0", "type": "string"}], - }, - { - "name": "play", - "arguments": [ - {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"}, - { - "name": "waveform", - "value": "wf_drag_gaussian_0", - "type": "waveform", - }, - ], - }, - { - "name": "barrier", - "arguments": [ - {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"} - ], - }, - { - "name": "barrier", - "arguments": None, - }, - { - "name": "delay", - "arguments": [ - {"name": "duration", "value": 3e-07, "type": "float"}, - {"name": "qubit", "value": "0", "type": "string"}, - {"name": "qubit", "value": "1", "type": "string"}, - ], - }, - { - "name": "delay", - "arguments": [ - {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"}, - {"name": "duration", "value": 3e-07, "type": "float"}, - ], - }, - { - "name": "shift_phase", - "arguments": [ - {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"}, - {"name": "phase", "value": 3e-07, "type": "float"}, - ], - }, - { - "name": "shift_frequency", - "arguments": [ - {"name": "frequency", "value": "theta", "type": "expr"}, - {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"}, - {"name": "extra", "value": "q0_q1_cphase_frame", "type": "string"}, - ], - }, - ], - } - ], - "rx": [ - { - "gateName": "rx", - "gateId": "rz_1", - "qubits": "0", - "arguments": ["theta"], - "calibrations": [ - {"name": "barrier", "arguments": None}, - ], - } - ], - }, - "0_1": { - "cz": [ - { - "gateName": "cz", - "gateId": "cz_0_1", - "qubits": ["1", "0"], - "arguments": [], - "calibrations": [ - {"name": "barrier", "arguments": None}, - ], - } - ], - "rx_12": [], - }, - }, - "waveforms": { - "q0_q1_cz_CZ": { - "waveformId": "q0_q1_cz_CZ", - "amplitudes": [[0.0, 0.0], [0.0, 0.0]], - }, - "wf_drag_gaussian_0": { - "waveformId": "wf_drag_gaussian_0", - "name": "drag_gaussian", - "arguments": [ - {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, - {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, - {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, - {"name": "beta", "value": 7.494904522022295e-10, "type": "float"}, - ], - }, - "wf_gaussian_0": { - "waveformId": "wf_gaussian_0", - "name": "gaussian", - "arguments": [ - {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, - {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, - {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, - ], - }, - "wf_constant": { - "waveformId": "wf_constant", - "name": "constant", - "arguments": [ - {"name": "length", "value": 2, "type": "float"}, - {"name": "iq", "value": 0.23, "type": "complex"}, - ], - }, - }, -} - - def test_mock_rigetti_schema_1(): validate(MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_1, RigettiDeviceCapabilities.schema()) @@ -215,7 +85,7 @@ def test_mock_rigetti_schema_1(): MOCK_GATE_MODEL_QPU_1 = { "deviceName": "Aspen-10", "deviceType": "QPU", - "providerName": "Rigetti", + "providerName": "provider1", "deviceStatus": "OFFLINE", "deviceCapabilities": MOCK_GATE_MODEL_QPU_CAPABILITIES_1.json(), } @@ -625,7 +495,6 @@ def test_device_refresh_metadata(arn): "qhpSpecificProperties": None, } }, - "nativeGateCalibrationsRef": "file://hostname/foo/bar", } @@ -647,7 +516,6 @@ def test_device_refresh_metadata(arn): "qhpSpecificProperties": None, } }, - "nativeGateCalibrationssRef": "file://hostname/foo/bar", } @@ -667,32 +535,6 @@ def get_pulse_model(capabilities_json): ], "shotsRange": [1, 10], }, - "provider": { - "specs": { - "1Q": { - "0": { - "fActiveReset": 0.9715, - "fRO": 0.951, - "f1QRB": 0.997339217568556, - "f1QRB_std_err": 0.00006690422818326937, - "f1Q_simultaneous_RB": 0.9949723201166536, - "f1Q_simultaneous_RB_std_err": 0.00021695233492231294, - "T1": 0.000010019627401991471, - "T2": 0.000018156447816365015, - } - }, - "2Q": { - "0-1": { - "fCZ": 0.9586440436264603, - "fCZ_std_err": 0.007025921432645824, - "fCPHASE": 0.9287330972713645, - "fCPHASE_std_err": 0.009709406809550082, - "fXY": 0.9755179214520402, - "fXY_std_err": 0.0060234488782598536, - }, - }, - } - }, "action": { "braket.ir.jaqcd.program": { "actionType": "braket.ir.jaqcd.program", @@ -712,7 +554,7 @@ def get_pulse_model(capabilities_json): return { "deviceName": "M-2-Pulse", "deviceType": "QPU", - "providerName": "Rigetti", + "providerName": "provider1", "deviceStatus": "OFFLINE", "deviceCapabilities": device_obj.json(), } @@ -746,104 +588,6 @@ def test_device_pulse_metadata(pulse_device_capabilities): assert device.frames == {} -def test_gate_calibration_refresh_no_url(arn): - mock_session = Mock() - mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 - mock_session.region = RIGETTI_REGION - device = AwsDevice(arn, mock_session) - - assert device.refresh_gate_calibrations() == None - - -@patch("urllib.request.urlopen") -def test_device_gate_calibrations_exists(mock_url_request): - # The data is accessed using a device manager so here data is prepped and passed for the return val. - response_data_content = { - "gates": { - "0_1": { - "cphaseshift": [ - { - "name": "cphaseshift", - "qubits": ["0", "1"], - "arguments": ["-1.5707963267948966"], - "calibrations": [ - { - "name": "play", - "arguments": [ - { - "name": "waveform", - "value": "wf_drag_gaussian_0", - "type": "waveform", - }, - { - "name": "frame", - "value": "q0_q1_cphase_frame", - "type": "frame", - }, - ], - }, - ], - } - ], - "rx_12": [{"name": "rx_12", "qubits": ["0"]}], - }, - }, - "waveforms": { - "wf_drag_gaussian_0": { - "waveformId": "wf_drag_gaussian_0", - "name": "drag_gaussian", - "arguments": [ - {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, - {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, - {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, - {"name": "beta", "value": 7.494904522022295e-10, "type": "float"}, - ], - }, - }, - } - - response_data_stream = io.BytesIO(json.dumps(response_data_content).encode("utf-8")) - mock_url_request.return_value.__enter__.return_value = response_data_stream - mock_session = Mock() - mock_session.get_device.return_value = get_pulse_model( - MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 - ) - device = AwsDevice(RIGETTI_ARN, mock_session) - - expected_waveforms = { - "wf_drag_gaussian_0": DragGaussianWaveform( - length=6.000000000000001e-8, - sigma=6.369913502160144e-9, - amplitude=-0.4549282253548838, - beta=7.494904522022295e-10, - id="wf_drag_gaussian_0", - ) - } - expected_ngc = GateCalibrations( - pulse_sequences={ - (Gate.CPhaseShift(-1.5707963267948966), QubitSet([0, 1])): PulseSequence().play( - device.frames["q0_q1_cphase_frame"], expected_waveforms["wf_drag_gaussian_0"] - ) - } - ) - assert device.gate_calibrations == expected_ngc - # Called twice to check that the property stays the same after being initially fetched - assert device.gate_calibrations == expected_ngc - - -@pytest.mark.xfail(raises=URLError) -@patch("urllib.request.urlopen") -def test_refresh_data_url_error(mock_url_request): - mock_url_request.side_effect = URLError("mock reason") - mock_session = Mock() - mock_session.get_device.return_value = get_pulse_model( - MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 - ) - device = AwsDevice(RIGETTI_ARN, mock_session) - - device.gate_calibrations - - def test_equality(arn): mock_session = Mock() mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 @@ -1310,7 +1054,6 @@ def test_default_bucket_not_called(aws_quantum_task_mock, device, circuit, s3_de None, None, None, - None, ) device._aws_session.default_bucket.assert_not_called() @@ -1484,7 +1227,6 @@ def _run_and_assert( poll_timeout_seconds=None, # Treated as positional arg poll_interval_seconds=None, # Treated as positional arg inputs=None, # Treated as positional arg - gate_definitions=None, # Treated as positional arg extra_args=None, extra_kwargs=None, ): @@ -1501,7 +1243,6 @@ def _run_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, - gate_definitions, extra_args, extra_kwargs, ) @@ -1519,7 +1260,6 @@ def _run_batch_and_assert( poll_timeout_seconds=None, # Treated as a positional arg poll_interval_seconds=None, # Treated as positional arg inputs=None, # Treated as positional arg - gate_definitions=None, # Treated as positional arg extra_args=None, extra_kwargs=None, ): @@ -1539,7 +1279,6 @@ def _run_batch_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, - gate_definitions, extra_args, extra_kwargs, ) @@ -1830,110 +1569,3 @@ def test_device_topology_graph_data(get_device_data, expected_graph, arn): new_val = "new_val" device._topology_graph = new_val assert device.topology_graph == new_val - - -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) - - -def test_parse_calibration_data(): - mock_session = Mock() - mock_session.get_device.return_value = get_pulse_model( - MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 - ) - device = AwsDevice(DWAVE_ARN, mock_session) - calibration_data = device._parse_calibration_json(MOCK_gate_calibrations_JSON) - device_ngc = GateCalibrations(calibration_data) - - expected_waveforms = { - "wf_drag_gaussian_0": DragGaussianWaveform( - length=6.000000000000001e-8, - sigma=6.369913502160144e-9, - amplitude=-0.4549282253548838, - beta=7.494904522022295e-10, - id="wf_drag_gaussian_0", - ) - } - expected_pulse_sequences = { - (Gate.CPhaseShift(-1.5707963267948966), QubitSet([0, 1])): PulseSequence() - .barrier(QubitSet(0)) - .play(device.frames["q0_q1_cphase_frame"], expected_waveforms["wf_drag_gaussian_0"]) - .barrier([device.frames["q0_q1_cphase_frame"]]) - .barrier([]) - .delay(QubitSet([0, 1]), 3e-07) - .delay([device.frames["q0_q1_cphase_frame"]], 3e-07) - .shift_phase(device.frames["q0_q1_cphase_frame"], 3e-07) - .shift_frequency(device.frames["q0_q1_cphase_frame"], FreeParameter("theta")), - (Gate.Rx(FreeParameter("theta")), QubitSet(0)): PulseSequence().barrier([]), - (Gate.CZ(), QubitSet([1, 0])): PulseSequence().barrier([]), - } - expected_ngc = GateCalibrations(pulse_sequences=expected_pulse_sequences) - assert device_ngc == expected_ngc - - -@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"}, - }, - } - ), - ], -) -@pytest.mark.xfail(raises=ValueError) -def test_parse_calibration_data_bad_instr(bad_input): - mock_session = Mock() - mock_session.get_device.return_value = get_pulse_model( - MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 - ) - device = AwsDevice(DWAVE_ARN, mock_session) - device._parse_calibration_json(bad_input) diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index 76b7329f..8dde92f2 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -192,23 +192,3 @@ def test_double_angle_parameters(): assert DoubleAngledGate( qubit_count=1, ascii_symbols=["foo"], angle_1=1, angle_2=2 ).parameters == [1, 2] - - -def test_hash_double_angle(): - symbol1 = FreeParameter("theta") - assert hash( - DoubleAngledGate(angle_1=symbol1, angle_2=1, qubit_count=1, ascii_symbols=["bar"]) - ) == hash(DoubleAngledGate(angle_1=symbol1, angle_2=1, qubit_count=1, ascii_symbols=["bar"])) - - -def test_hash_triple_angle(): - symbol1 = FreeParameter("theta") - assert hash( - TripleAngledGate( - angle_1=symbol1, angle_2=1, angle_3=3, qubit_count=1, ascii_symbols=["bar"] - ) - ) == hash( - TripleAngledGate( - angle_1=symbol1, angle_2=1, angle_3=3, qubit_count=1, ascii_symbols=["bar"] - ) - ) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index baf9ec8c..f24e4a83 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -33,8 +33,6 @@ noise, observables, ) -from braket.circuits.gate_calibrations import GateCalibrations -from braket.circuits.parameterizable import Parameterizable from braket.circuits.serialization import ( IRType, OpenQASMSerializationProperties, @@ -109,61 +107,6 @@ def user_defined_frame(port): ) -@pytest.fixture -def pulse_sequence(predefined_frame_1): - return ( - PulseSequence() - .set_frequency( - predefined_frame_1, - 6e6, - ) - .play( - predefined_frame_1, - DragGaussianWaveform(length=3e-3, sigma=0.4, beta=0.2, id="drag_gauss_wf"), - ) - ) - - -@pytest.fixture -def pulse_sequence_2(predefined_frame_1): - return ( - PulseSequence() - .shift_phase( - predefined_frame_1, - FreeParameter("alpha"), - ) - .set_phase( - predefined_frame_1, - FreeParameter("gamma"), - ) - .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])) - calibration_key_2 = (Gate.Rx(FreeParameter("theta")), QubitSet([0])) - calibration_key_3 = ( - 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, - } - ) - - def test_repr_instructions(h): expected = f"Circuit('instructions': {h.instructions})" assert repr(h) == expected @@ -738,18 +681,6 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "OPENQASM 3.0;", "bit[2] b;", "qubit[2] q;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 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];", @@ -767,18 +698,6 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): [ "OPENQASM 3.0;", "bit[2] b;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 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;", @@ -798,18 +717,6 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): source="\n".join( [ "OPENQASM 3.0;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 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{", @@ -833,18 +740,6 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): [ "OPENQASM 3.0;", "qubit[5] q;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 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]", @@ -864,18 +759,6 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "input float theta;", "bit[2] b;", "qubit[2] q;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 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];", @@ -897,18 +780,6 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "OPENQASM 3.0;", "bit[5] b;", "qubit[5] q;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 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];", @@ -931,14 +802,6 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "OPENQASM 3.0;", "bit[7] b;", "qubit[7] q;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 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];", @@ -955,204 +818,27 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): ), ), ( - Circuit().h(0, power=-2.5).h(0, power=0).ms(0, 1, -0.1, -0.2, -0.3), + Circuit().h(0, power=-2.5).h(0, power=0), OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), OpenQasmProgram( source="\n".join( [ "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 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);", - "}", + "bit[1] b;", + "qubit[1] q;", "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={}, ), ), - pytest.param( - Circuit().h(0, power=-2.5).h(0, power=0).rx(0, angle=FreeParameter("theta")), - OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), - OpenQasmProgram( - source="", - inputs={}, - ), - marks=pytest.mark.xfail( - reason="Parametric calibrations cannot be attached with parametric circuits." - ), - ), ], ) -def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir, gate_calibrations): - copy_of_gate_calibrations = gate_calibrations.copy() - assert ( - circuit.to_ir( - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - gate_definitions=gate_calibrations.pulse_sequences, - ) - == expected_ir - ) - assert copy_of_gate_calibrations.pulse_sequences == gate_calibrations.pulse_sequences - - -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) - 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, - } - ) - - 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" - + "(3000000.0ns, 400000000.0ns, 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={}, - ) - - assert ( - circ.to_ir( - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - gate_definitions=gate_calibrations.pulse_sequences, - ) - == expected_ir - ) - - -@pytest.mark.xfail( - reasons="Calibrations with a partial number of fixed parameters are not supported." -) -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} - ), - ) - circuit.to_ir( - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - gate_definitions=gate_calibrations.pulse_sequences, - ) - - -def test_circuit_user_gate(pulse_sequence_2): - class Foo(Gate, Parameterizable): - def __init__( - self, - bar, - ): - super().__init__(qubit_count=1, ascii_symbols=["Foo"]) - self._parameters = [bar] - - @property - def parameters(self): - return self._parameters - - def bind_values(self, **kwargs): - raise NotImplementedError - - @property - def _qasm_name(self): - return "foo" - - def __hash__(self): - return hash((self.name, self.parameters[0], self.qubit_count)) - - @staticmethod - @circuit.subroutine(register=True) - def foo( - target, - bar, - ): - return Instruction(Foo(bar), target=target) - - Gate.register_gate(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} - ) - } - ) - - expected_ir = OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 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={}, - ) - +def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): assert ( - circ.to_ir( - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - gate_definitions=gate_calibrations.pulse_sequences, - ) + circuit.to_ir(ir_type=IRType.OPENQASM, serialization_properties=serialization_properties) == expected_ir ) diff --git a/test/unit_tests/braket/circuits/test_gate_calibration.py b/test/unit_tests/braket/circuits/test_gate_calibration.py deleted file mode 100644 index 31c2384d..00000000 --- a/test/unit_tests/braket/circuits/test_gate_calibration.py +++ /dev/null @@ -1,148 +0,0 @@ -# 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 Gate, QubitSet -from braket.circuits.gate_calibrations import GateCalibrations -from braket.pulse import Frame, Port, PulseSequence - - -@pytest.fixture -def port(): - return Port("test_port_ff", dt=1e-9) - - -@pytest.fixture -def frame_id(): - return "test_frame_rf" - - -@pytest.fixture -def frame(frame_id, port): - return Frame(frame_id, port, 1e6, is_predefined=True) - - -@pytest.fixture -def pulse_sequence(frame): - return ( - PulseSequence() - .barrier(qubits_or_frames=[frame]) - .delay(qubits_or_frames=[frame], duration=1000) - ) - - -def test_gc_creation(pulse_sequence): - calibration_key = (Gate.H(), QubitSet([0, 1])) - calibration = GateCalibrations({calibration_key: pulse_sequence}) - - assert calibration.pulse_sequences[calibration_key] == pulse_sequence - - -def test_gc_copy(pulse_sequence): - calibration_key = (Gate.H(), QubitSet([0, 1])) - calibration = GateCalibrations({calibration_key: pulse_sequence}) - - assert calibration == calibration.copy() - - -def test_filter(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_calibration_1 = GateCalibrations({calibration_key: pulse_sequence}) - expected_calibration_2 = GateCalibrations( - {calibration_key: pulse_sequence, calibration_key_2: pulse_sequence} - ) - expected_calibration_3 = GateCalibrations({calibration_key_2: 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)) - - -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[1000000000000.0ns] test_frame_rf;", - "}", - ] - ) - - assert calibration.to_ir() == expected_ir - - -@pytest.mark.xfail(raises=ValueError) -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[1000000000000.0ns] 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[1000000000000.0ns] 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} - ) - - assert len(calibration) == 2 - - -@pytest.mark.parametrize( - "bad_input", - [ - ({(Gate.Rx(1), "string"): PulseSequence()}), - ({(Gate.Rx(1), QubitSet(0)): 4}), - ({("string_a", "string_b"): PulseSequence()}), - ], -) -@pytest.mark.xfail(raises=TypeError) -def test_bad_pulse_sequence(bad_input): - GateCalibrations(bad_input) diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index ce9698e9..90dc7d5a 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -1149,9 +1149,3 @@ def test_gate_power(gate, target, power, expected_ir): ) == expected_ir ) - - -def test_hash(): - assert hash(Gate.Unitary(Gate.CCNot().to_matrix())) == hash( - Gate.Unitary(Gate.CCNot().to_matrix()) - ) 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 e49c4d78..c41a1712 100644 --- a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py +++ b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py @@ -399,17 +399,6 @@ def test_play_arbitrary_waveforms(port): verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) -@pytest.mark.xfail(raises=NameError) -def test_missing_waveform(port): - frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) - my_arb_wf = ArbitraryWaveform([0.4 + 0.1j, -0.8 + 0.1j, 1 + 0.2j]) - pulse_seq = PulseSequence() - identifier = my_arb_wf._to_oqpy_expression() - identifier._needs_declaration = False - pulse_seq._program.play(frame, identifier.to_ast(pulse_seq._program)) - _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) - - def test_play_literal(port): frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) pulse_seq = PulseSequence() diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py index 14add748..064ab302 100644 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -337,86 +337,3 @@ def test_pulse_sequence_to_ir(predefined_frame_1, predefined_frame_2): ] ) assert pulse_sequence.to_ir() == expected_str - - -def test_parse_from_calibration_schema(predefined_frame_1, predefined_frame_2): - waveforms = { - "drag_gauss_wf": DragGaussianWaveform(length=3e-3, sigma=0.4, beta=0.2, id="drag_gauss_wf") - } - frames = {predefined_frame_1.id: predefined_frame_1, predefined_frame_2.id: predefined_frame_2} - - calibration_instrs = [ - { - "name": "barrier", - "arguments": [{"name": "qubit", "value": "0", "type": "string"}], - }, - { - "name": "play", - "arguments": [ - {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, - { - "name": "waveform", - "value": "drag_gauss_wf", - "type": "waveform", - }, - ], - }, - { - "name": "barrier", - "arguments": [ - {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, - {"name": "frame", "value": "predefined_frame_2", "type": "frame"}, - ], - }, - { - "name": "barrier", - "arguments": None, - }, - { - "name": "delay", - "arguments": [ - {"name": "duration", "value": 3e-07, "type": "float"}, - {"name": "qubit", "value": "0", "type": "string"}, - {"name": "qubit", "value": "1", "type": "string"}, - ], - }, - { - "name": "delay", - "arguments": [ - {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, - {"name": "duration", "value": 3e-07, "type": "float"}, - ], - }, - { - "name": "shift_phase", - "arguments": [ - {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, - {"name": "phase", "value": 3e-07, "type": "float"}, - ], - }, - { - "name": "shift_frequency", - "arguments": [ - {"name": "frequency", "value": "theta", "type": "expr"}, - {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, - {"name": "extra", "value": "predefined_frame_1", "type": "string"}, - ], - }, - ] - - expected_pulse_sequence = ( - PulseSequence() - .barrier(QubitSet(0)) - .play(predefined_frame_1, waveforms["drag_gauss_wf"]) - .barrier([predefined_frame_1, predefined_frame_2]) - .barrier([]) - .delay(QubitSet([0, 1]), 3e-07) - .delay([predefined_frame_1], 3e-07) - .shift_phase(predefined_frame_1, 3e-07) - .shift_frequency(predefined_frame_1, FreeParameter("theta")) - ) - - assert ( - PulseSequence._parse_from_calibration_schema(calibration_instrs, waveforms, frames) - == expected_pulse_sequence - ) diff --git a/test/unit_tests/braket/pulse/test_waveforms.py b/test/unit_tests/braket/pulse/test_waveforms.py index b42eacc0..973cfaf4 100644 --- a/test/unit_tests/braket/pulse/test_waveforms.py +++ b/test/unit_tests/braket/pulse/test_waveforms.py @@ -10,7 +10,6 @@ # 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 re from copy import deepcopy @@ -22,7 +21,6 @@ from braket.circuits.free_parameter import FreeParameter from braket.pulse import ArbitraryWaveform, ConstantWaveform, DragGaussianWaveform, GaussianWaveform from braket.pulse.ast.qasm_parser import ast_to_qasm -from braket.pulse.waveforms import _parse_waveform_from_calibration_schema @pytest.mark.parametrize( @@ -262,66 +260,3 @@ def _assert_wf_qasm(waveform, expected_qasm): p = Program(None) p.declare(waveform._to_oqpy_expression()) assert ast_to_qasm(p.to_ast(include_externs=False)) == expected_qasm - - -@pytest.mark.parametrize( - "waveform_json, waveform", - [ - ( - { - "waveformId": "q0_q1_cz_CZ", - "amplitudes": [[0.0, 0.0], [0.0, 0.0]], - }, - ArbitraryWaveform(id="q0_q1_cz_CZ", amplitudes=[complex(0.0, 0.0), complex(0.0, 0.0)]), - ), - ( - { - "waveformId": "wf_drag_gaussian_0", - "name": "drag_gaussian", - "arguments": [ - {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, - {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, - {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, - {"name": "beta", "value": 7.494904522022295e-10, "type": "float"}, - ], - }, - DragGaussianWaveform( - id="wf_drag_gaussian_0", - sigma=6.369913502160144e-9, - length=6.000000000000001e-8, - beta=7.494904522022295e-10, - amplitude=-0.4549282253548838, - ), - ), - ( - { - "waveformId": "wf_gaussian_0", - "name": "gaussian", - "arguments": [ - {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, - {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, - {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, - ], - }, - GaussianWaveform( - id="wf_gaussian_0", - length=6.000000000000001e-8, - sigma=6.369913502160144e-9, - amplitude=-0.4549282253548838, - ), - ), - ( - { - "waveformId": "wf_constant", - "name": "constant", - "arguments": [ - {"name": "length", "value": 2.1, "type": "float"}, - {"name": "iq", "value": 0.23, "type": "complex"}, - ], - }, - ConstantWaveform(id="wf_constant", length=2.1, iq=0.23), - ), - ], -) -def test_parse_waveform_from_calibration_schema(waveform_json, waveform): - assert _parse_waveform_from_calibration_schema(waveform_json) == waveform From 676f101ff8160bf1889e69b04a7556722a3ab17a Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 21 Jul 2023 17:28:37 +0000 Subject: [PATCH 0762/1165] prepare release v1.51.0 --- CHANGELOG.md | 13 +++++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7da03d17..12752779 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## v1.51.0 (2023-07-21) + +### Features + + * add gate calibration data for supported quantum devices + +### Bug Fixes and Other Changes + + * revert adding gate calibration data + * making additional meta available in AHS results + * handle the optional calibration URL returning None + * copy calibrations in to_ir + ## v1.50.0 (2023-07-19) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index cd90f054..784aa53a 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.50.1.dev0" +__version__ = "1.51.0" From 44c789416f7b5399df3083c7b29cf94e6e860e91 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 21 Jul 2023 17:28:37 +0000 Subject: [PATCH 0763/1165] update development version to v1.51.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 784aa53a..743d07c9 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.51.0" +__version__ = "1.51.1.dev0" From 6d7088485a4eefca9840cca78b6c0c10c18ac6d7 Mon Sep 17 00:00:00 2001 From: Sai Prakash Ch <52249348+Sai-prakash15@users.noreply.github.com> Date: Fri, 21 Jul 2023 17:42:20 -0600 Subject: [PATCH 0764/1165] fix: Add parameter support (#636) --- src/braket/circuits/braket_program_context.py | 10 +++++++++- test/unit_tests/braket/circuits/test_circuit.py | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index d88bafe7..a625a1e8 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.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, Optional, Tuple +from typing import List, Optional, Tuple, Type, Union import numpy as np @@ -23,8 +23,11 @@ braket_result_to_result_type, one_prob_noise_map, ) +from braket.default_simulator.openqasm._helpers.casting import LiteralType +from braket.default_simulator.openqasm.parser.openqasm_ast import ClassicalType, Identifier from braket.default_simulator.openqasm.program_context import AbstractProgramContext from braket.ir.jaqcd.program_v1 import Results +from braket.parametric import FreeParameter class BraketProgramContext(AbstractProgramContext): @@ -42,6 +45,11 @@ def circuit(self) -> Circuit: """The circuit being built in this context.""" return self._circuit + def add_parameter( + self, name: str, type: Union[ClassicalType, Type[LiteralType], Type[Identifier]] + ) -> None: + self.declare_variable(name, type, FreeParameter(name)) + def is_builtin_gate(self, name: str) -> bool: """Whether the gate is currently in scope as a built-in Braket gate. diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index f24e4a83..bb86a75d 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -1388,6 +1388,22 @@ def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): inputs={}, ), ), + ( + Circuit().rx(0, FreeParameter("theta")), + 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];", + ] + ), + inputs={}, + ), + ), ], ) def test_circuit_from_ir(expected_circuit, ir): From 1fed55d88333d4acd2302734ee88f707ce418a12 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Fri, 21 Jul 2023 18:04:31 -0700 Subject: [PATCH 0765/1165] fix: local import in job example (#634) --- examples/job.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/job.py b/examples/job.py index fdb2cec6..6b153d12 100644 --- a/examples/job.py +++ b/examples/job.py @@ -15,7 +15,6 @@ from braket.aws import AwsDevice, AwsQuantumJob from braket.circuits import Circuit -from braket.devices import Devices from braket.jobs import save_job_result @@ -36,6 +35,8 @@ def run_job(): if __name__ == "__main__": + from braket.devices import Devices + job = AwsQuantumJob.create( device=Devices.Amazon.SV1, source_module="job.py", From 043ce72903a76a27c44ccf4b35e09fc113041569 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Mon, 24 Jul 2023 11:41:01 -0400 Subject: [PATCH 0766/1165] fix: Handling for autoqasm subroutine return values (#633) * Handle unnamed return values properly * Combine retval variable names * Fix handling of recursive function return values * Fix handling of return values from non-recursive functions * Move new tests to test_types.py --- src/braket/experimental/autoqasm/api.py | 22 ++- .../autoqasm/operators/assignments.py | 23 +++- .../experimental/autoqasm/program/program.py | 1 + .../experimental/autoqasm/types/py_to_oqpy.py | 5 - .../braket/experimental/autoqasm/test_api.py | 6 +- .../experimental/autoqasm/test_types.py | 128 +++++++++++++++++- 6 files changed, 164 insertions(+), 21 deletions(-) diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index be5fd98a..75d12d78 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -17,7 +17,7 @@ import functools import inspect from types import FunctionType -from typing import Any, Callable, Dict, List, Optional, Tuple +from typing import Any, Callable, Dict, List, Optional, Tuple, Union import openqasm3.ast as qasm_ast import oqpy.base @@ -133,7 +133,7 @@ def _convert_wrapper( def _decorator(f: Callable) -> Callable[[Any], aq_program.Program]: """Decorator implementation.""" - def _wrapper(*args, **kwargs) -> aq_program.Program: + def _wrapper(*args, **kwargs) -> Union[aq_program.Program, Optional[oqpy.base.Var]]: """Wrapper that calls the converted version of f.""" # This code is executed once the decorated function is called if aq_program.in_active_program_conversion_context(): @@ -178,7 +178,7 @@ def _convert( user_config: aq_program.UserConfig, args: List[Any], kwargs: Dict[str, Any], -) -> aq_program.Program: +) -> Union[aq_program.Program, Optional[oqpy.base.Var]]: """Convert the initial callable `f` into a full AutoQASM program `program`. This function adds error handling around `_convert_program_as_subroutine` @@ -193,7 +193,9 @@ def _convert( kwargs (Dict[str, Any]): Keyword arguments passed to the program when called. Returns: - Program: The converted program. + Union[Program, Optional[Var]]: The converted program, if this is a top-level call + to the conversion process. Or, the oqpy variable returned from the converted function, + if this is a subroutine conversion. """ # We will convert this function as a subroutine if we are already inside an # existing conversion process (i.e., this is a subroutine call). @@ -216,7 +218,10 @@ def _convert( else: raise - return program_conversion_context.make_program() + if convert_as_subroutine: + return program_conversion_context.return_variable + + return program_conversion_context.make_program() def _convert_program_as_main( @@ -292,6 +297,7 @@ def _convert_program_as_subroutine( # Add the subroutine invocation to the program return_instance = _make_return_instance(f, program_conversion_context) + return_variable = None if isinstance(return_instance, list): ret_type = subroutine_function_call.subroutine_decl.return_type return_variable = oqpy.ArrayVar( @@ -302,11 +308,15 @@ def _convert_program_as_subroutine( oqpy_program.set(return_variable, subroutine_function_call) elif return_instance is not None: return_variable = aq_types.wrap_value(return_instance) + oqpy_program.declare(return_variable) oqpy_program.set(return_variable, subroutine_function_call) else: function_call = subroutine_function_call.to_ast(oqpy_program) oqpy_program._add_statement(qasm_ast.ExpressionStatement(function_call)) + # Store the return variable in the program conversion context + program_conversion_context.return_variable = return_variable + # Add the subroutine definition to the root-level program if necessary root_oqpy_program = program_conversion_context.oqpy_program_stack[0] subroutine_name = subroutine_function_call.identifier.name @@ -379,7 +389,7 @@ def _clone_function(f_source: Callable) -> Callable: copy.copy(f_source.__globals__), copy.deepcopy(f_source.__name__), copy.deepcopy(f_source.__defaults__), - copy.deepcopy(f_source.__closure__), + copy.copy(f_source.__closure__), ) setattr(f_clone, "__signature__", copy.deepcopy(inspect.signature(f_source))) setattr(f_clone, "__annotations__", copy.deepcopy(f_source.__annotations__)) diff --git a/src/braket/experimental/autoqasm/operators/assignments.py b/src/braket/experimental/autoqasm/operators/assignments.py index 154355b0..2b1d03d1 100644 --- a/src/braket/experimental/autoqasm/operators/assignments.py +++ b/src/braket/experimental/autoqasm/operators/assignments.py @@ -44,20 +44,31 @@ def assign_stmt(target_name: str, value: Any) -> Any: return value if target_name == constants.RETVAL_VARIABLE_NAME: - return types.wrap_value(value) + # AutoGraph transpiles return statements like + # return + # into + # retval_ = + # return retval_ + # The special logic here is to handle this case properly and avoid + # declaring a new variable unless it is necessary. + + if isinstance(value, oqpy.base.Var) and _is_variable_used(value.name): + # This is a value which already exists as a variable in the program. + # Return it directly without wrapping it or declaring a new variable. + return value + + value = types.wrap_value(value) if isinstance(value, oqpy.base.Var): - # TODO: If name is defined in value, it might be different from target_name. - # We should probably validate that. oqpy_program = program.get_program_conversion_context().get_oqpy_program() is_target_name_used = _is_variable_used(target_name) - is_value_used = _is_variable_used(value.name) + is_value_name_used = _is_variable_used(value.name) if is_target_name_used: target = _get_oqpy_program_variable(target_name) _validate_variables_type_size(target, value) - if is_value_used: + if is_value_name_used: oqpy_program.set(target, value) else: # Set to `value.init_expression` to avoid declaring an unnecessary variable. @@ -67,7 +78,7 @@ def assign_stmt(target_name: str, value: Any) -> Any: target.init_expression = None target.name = target_name - if is_value_used: + if is_value_name_used: oqpy_program.declare(target) oqpy_program.set(target, value) else: diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 26e50211..7d26453f 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -85,6 +85,7 @@ def __init__(self, user_config: Optional[UserConfig] = None): self.oqpy_program_stack = [oqpy.Program()] self.subroutines_processing = set() # the set of subroutines queued for processing self.user_config = user_config or UserConfig() + self.return_variable = None self._qubits_seen = set() self._var_idx = 0 diff --git a/src/braket/experimental/autoqasm/types/py_to_oqpy.py b/src/braket/experimental/autoqasm/types/py_to_oqpy.py index 3c917fc1..fff42732 100644 --- a/src/braket/experimental/autoqasm/types/py_to_oqpy.py +++ b/src/braket/experimental/autoqasm/types/py_to_oqpy.py @@ -108,8 +108,3 @@ def _(node: oqpy.base.Var): @wrap_value.register def _(node: oqpy.base.OQPyExpression): return node - - -@wrap_value.register -def _(node: program.Program): - return node diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index 5c1ffb0b..c9efea3c 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -372,8 +372,8 @@ def qasm_simple_condition(bool do_cnot) -> bool { } return do_cnot; } -bool __bool_0__ = false; qubit[2] __qubits__; +bool __bool_0__ = false; """ expected += f"__bool_0__ = qasm_simple_condition({'true' if do_cnot else 'false'});" assert qasm_simple_condition_wrapper(do_cnot).to_ir() == expected @@ -466,8 +466,8 @@ def ground_state_measurements() -> bit[3] { } """ # TODO: this should be `bit[3]`, but there's a bug. It's being tracked in an issue. - expected += """bit __bit_1__; -qubit[6] __qubits__; + expected += """qubit[6] __qubits__; +bit __bit_1__; __bit_1__ = ground_state_measurements();""" assert ground_state_measurements_wrapper().to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index c2463a6c..709346c2 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -154,9 +154,9 @@ def ret_test() -> int: def add(int[32] a, int[32] b) -> int[32] { return a + b; } -int[32] __int_0__ = 0; int[32] a = 5; int[32] b = 6; +int[32] __int_0__ = 0; __int_0__ = add(a, b);""" assert ret_test().to_ir() == expected @@ -317,3 +317,129 @@ def annotation_test(bit input) { annotation_test(a);""" assert main().to_ir() == expected + + +def test_unnamed_retval_python_type() -> None: + """Tests subroutines which return unnamed Python values.""" + + @aq.function + def retval_test() -> int: + return 1 + + @aq.function + def caller() -> int: + return retval_test() + + expected_qasm = """OPENQASM 3.0; +def retval_test() -> int[32] { + int[32] retval_ = 1; + return retval_; +} +int[32] __int_1__ = 0; +__int_1__ = retval_test();""" + + assert caller().to_ir() == expected_qasm + + +def test_unnamed_retval_qasm_type() -> None: + """Tests subroutines which return unnamed QASM values.""" + + @aq.function + def retval_test() -> aq.BitVar: + return aq.BitVar(1) + + @aq.function + def caller() -> aq.BitVar: + return retval_test() + + expected_qasm = """OPENQASM 3.0; +def retval_test() -> bit { + bit retval_ = 1; + return retval_; +} +bit __bit_0__; +__bit_0__ = retval_test();""" + + assert caller().to_ir() == expected_qasm + + +def test_recursive_unassigned_retval_python_type() -> None: + """Tests recursive subroutines which do not assign the return value to a variable.""" + + @aq.function + def retval_recursive() -> int: + retval_recursive() + return 1 + + expected_qasm = """OPENQASM 3.0; +def retval_recursive() -> int[32] { + int[32] __int_1__ = 0; + __int_1__ = retval_recursive(); + int[32] retval_ = 1; + return retval_; +} +int[32] __int_3__ = 0; +__int_3__ = retval_recursive(); +int[32] retval_ = 1;""" + + assert retval_recursive().to_ir() == expected_qasm + + +def test_recursive_assigned_retval_python_type() -> None: + """Tests recursive subroutines which assign the return value to a variable.""" + + @aq.function + def retval_recursive() -> int: + a = retval_recursive() # noqa: F841 + return 1 + + expected_qasm = """OPENQASM 3.0; +def retval_recursive() -> int[32] { + int[32] __int_1__ = 0; + __int_1__ = retval_recursive(); + int[32] a; + a = __int_1__; + int[32] retval_ = 1; + return retval_; +} +int[32] __int_3__ = 0; +__int_3__ = retval_recursive(); +int[32] a; +a = __int_3__; +int[32] retval_ = 1;""" + + assert retval_recursive().to_ir() == expected_qasm + + +def test_recursive_retval_expression_python_type() -> None: + """Tests recursive subroutines which use the return value in an expression.""" + + @aq.function + def retval_constant() -> int: + return 3 + + @aq.function + def retval_recursive() -> int: + a = 2 * retval_recursive() + (retval_constant() + 2) / 3 + return a + + @aq.function + def caller() -> int: + return retval_recursive() + + expected_qasm = """OPENQASM 3.0; +def retval_recursive() -> int[32] { + int[32] __int_1__ = 0; + __int_1__ = retval_recursive(); + int[32] __int_3__ = 0; + __int_3__ = retval_constant(); + return 2 * __int_1__ + (__int_3__ + 2) / 3; +} +def retval_constant() -> int[32] { + int[32] retval_ = 3; + return retval_; +} +int[32] __int_4__ = 0; +__int_4__ = retval_recursive();""" + + assert caller().to_ir() == expected_qasm From f7b34a9e94811719e1dc36923ee2a3419a2ec013 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Mon, 24 Jul 2023 14:01:39 -0400 Subject: [PATCH 0767/1165] Update simulator mcm-sim commit hash (#639) --- setup.py | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 3f3fba7c..44ce1237 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ # to get the version of the simulator that supports the mcm=True argument for Monte Carlo # simulation of mid-circuit measurement, which AutoQASM requires. # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main. - "amazon-braket-default-simulator @ git+https://github.com/aws/amazon-braket-default-simulator-python.git@3d9a746796a46fb9afff540e1446d5e1c7fa222d#egg=amazon-braket-default-simulator", # noqa E501 + "amazon-braket-default-simulator @ git+https://github.com/aws/amazon-braket-default-simulator-python.git@731f2545961abb41dcf13bf26e99f4ded79a15aa#egg=amazon-braket-default-simulator", # noqa E501 # Pin the latest commit of the qubit-array branch of ajberdy/oqpy.git to get the version of # oqpy which contains changes that AutoQASM relies on, including the QubitArray type. # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main. diff --git a/tox.ini b/tox.ini index 11c46879..31448410 100644 --- a/tox.ini +++ b/tox.ini @@ -95,4 +95,4 @@ commands = deps = # If you need to test on a certain branch, add @ after .git git+https://github.com/aws/amazon-braket-schemas-python.git - git+https://github.com/aws/amazon-braket-default-simulator-python.git@3d9a746796a46fb9afff540e1446d5e1c7fa222 # mcm-sim branch + git+https://github.com/aws/amazon-braket-default-simulator-python.git@731f2545961abb41dcf13bf26e99f4ded79a15aa # mcm-sim branch From 8f95df73fffe63b8ad5da30d936d8c149c992e7b Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Mon, 24 Jul 2023 15:10:28 -0700 Subject: [PATCH 0768/1165] test: update devices enum test (#635) --- test/integ_tests/test_device_creation.py | 99 ++++++++++++++---------- 1 file changed, 60 insertions(+), 39 deletions(-) diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index c12b899f..222a82f4 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_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 typing import List, Set import pytest @@ -74,43 +75,63 @@ def test_get_devices_all(): assert arn in result_arns +def _get_provider_name(device: AwsDevice) -> str: + arn_provider = device.arn.split("/")[2] + + # capitalize as in provider name + provider_primary_name = device.provider_name.split()[0] + if arn_provider == provider_primary_name.lower(): + capitalized = provider_primary_name + else: + capitalized = arn_provider.upper() + + # remove dashes + return capitalized.replace("-", "") + + +def _get_device_name(device: AwsDevice) -> str: + arn_device_name = device.arn.split("/")[-1] + + device_name = ( + arn_device_name.replace("Advantage_system", "Advantage").replace("_", "").replace("-", "") + ) + + if device.provider_name == "Amazon Braket": + device_name = device_name.upper() + 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)) + return active_providers + + +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}" + device_name = _get_device_name(device) + if device.status == "RETIRED": + device_name = f"_{device_name}" + + assert getattr(getattr(Devices, provider_name), device_name) == device.arn + + def test_device_enum(): - provider_name_to_enum_map = { - "Amazon Braket": "Amazon", - "D-Wave Systems": "_DWave", - "IonQ": "IonQ", - "Oxford": "OQC", - "QuEra": "QuEra", - "Rigetti": "Rigetti", - "Xanadu": "_Xanadu", - } - device_name_to_enum_map = { - "SV1": "SV1", - "TN1": "TN1", - "dm1": "DM1", - "Advantage_system1.1": "_Advantage1", - "Advantage_system3.2": "_Advantage3", - "Advantage_system4.1": "_Advantage4", - "Advantage_system6.1": "_Advantage6", - "DW_2000Q_6": "_DW2000Q6", - "Harmony": "Harmony", - "Aria 1": "Aria1", - "Lucy": "Lucy", - "Aquila": "Aquila", - "Aspen-8": "_Aspen8", - "Aspen-9": "_Aspen9", - "Aspen-10": "_Aspen10", - "Aspen-11": "_Aspen11", - "Aspen-M-1": "_AspenM1", - "Aspen-M-2": "_AspenM2", - "Aspen-M-3": "AspenM3", - "Borealis": "_Borealis", - } - for device in AwsDevice.get_devices(): - assert ( - getattr( - getattr(Devices, provider_name_to_enum_map[device.provider_name]), - device_name_to_enum_map[device.name], - ) - == device.arn - ) + aws_devices = AwsDevice.get_devices() + active_providers = _get_active_providers(aws_devices) + + # validate all devices in API + for device in aws_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: + devices = [getattr(provider, attr) for attr in dir(provider) if not attr.startswith("__")] + for arn in devices: + device = AwsDevice(arn) + _validate_device(device, active_providers) From 1ec12d0be6a8c12b821f2f8922025b08c4ac68e4 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Mon, 24 Jul 2023 17:40:17 -0700 Subject: [PATCH 0769/1165] feat: Support symbolic expressions in `from_ir` (#641) --- src/braket/circuits/braket_program_context.py | 17 ++++++++--------- src/braket/parametric/free_parameter.py | 2 +- test/unit_tests/braket/circuits/test_circuit.py | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index a625a1e8..b41eb3b9 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -11,9 +11,10 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import List, Optional, Tuple, Type, Union +from typing import List, Optional, Tuple import numpy as np +from sympy import Expr from braket.circuits import Circuit, Instruction from braket.circuits.gates import Unitary @@ -23,11 +24,9 @@ braket_result_to_result_type, one_prob_noise_map, ) -from braket.default_simulator.openqasm._helpers.casting import LiteralType -from braket.default_simulator.openqasm.parser.openqasm_ast import ClassicalType, Identifier from braket.default_simulator.openqasm.program_context import AbstractProgramContext from braket.ir.jaqcd.program_v1 import Results -from braket.parametric import FreeParameter +from braket.parametric import FreeParameterExpression class BraketProgramContext(AbstractProgramContext): @@ -45,11 +44,6 @@ def circuit(self) -> Circuit: """The circuit being built in this context.""" return self._circuit - def add_parameter( - self, name: str, type: Union[ClassicalType, Type[LiteralType], Type[Identifier]] - ) -> None: - self.declare_variable(name, type, FreeParameter(name)) - def is_builtin_gate(self, name: str) -> bool: """Whether the gate is currently in scope as a built-in Braket gate. @@ -139,3 +133,8 @@ def add_result(self, result: Results) -> None: result (Results): The result object representing the measurement results """ self._circuit.add_result_type(braket_result_to_result_type(result)) + + def handle_parameter_value(self, value): + if isinstance(value, Expr): + return FreeParameterExpression(str(value)) + return value diff --git a/src/braket/parametric/free_parameter.py b/src/braket/parametric/free_parameter.py index 3ac4cdd8..3eed47ef 100644 --- a/src/braket/parametric/free_parameter.py +++ b/src/braket/parametric/free_parameter.py @@ -82,7 +82,7 @@ def __hash__(self) -> int: def __eq__(self, other): if isinstance(other, FreeParameter): return self._name == other._name - return False + return super().__eq__(other) def __repr__(self) -> str: """ diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index bb86a75d..dad69c6d 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -1511,6 +1511,22 @@ def test_circuit_from_ir(expected_circuit, ir): inputs={}, ), ), + ( + Circuit().rx(0, FreeParameter("theta")).rx(0, 2 * FreeParameter("theta")), + 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];", + ] + ), + inputs={}, + ), + ), ], ) def test_circuit_from_ir_greater_functionality(expected_circuit, ir): From 3a8e57608bbd6cd753ac5209dd5e2737ae55ecca Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 25 Jul 2023 11:06:44 -0400 Subject: [PATCH 0770/1165] fix: handling of variable and function declarations inside autoqasm programs (#640) --- src/braket/experimental/autoqasm/__init__.py | 3 +- src/braket/experimental/autoqasm/api.py | 5 +- .../autoqasm/gates/measurements.py | 8 +-- .../autoqasm/operators/__init__.py | 1 + .../operators/conditional_expressions.py | 1 - .../autoqasm/operators/control_flow.py | 1 - .../autoqasm/operators/logical.py | 14 ++--- .../experimental/autoqasm/operators/slices.py | 2 +- .../experimental/autoqasm/types/__init__.py | 12 +++- .../types/{py_to_oqpy.py => conversions.py} | 20 +++---- .../experimental/autoqasm/types/types.py | 33 +++++++++++ .../braket/experimental/autoqasm/test_api.py | 35 ++++++++++-- .../experimental/autoqasm/test_operators.py | 6 +- .../experimental/autoqasm/test_types.py | 55 ++++++++++++------- 14 files changed, 132 insertions(+), 64 deletions(-) rename src/braket/experimental/autoqasm/types/{py_to_oqpy.py => conversions.py} (82%) diff --git a/src/braket/experimental/autoqasm/__init__.py b/src/braket/experimental/autoqasm/__init__.py index 37c7617e..90afcf00 100644 --- a/src/braket/experimental/autoqasm/__init__.py +++ b/src/braket/experimental/autoqasm/__init__.py @@ -40,10 +40,9 @@ def my_program(): result[1] = measure __qubits__[1]; """ -from oqpy import ArrayVar, BitVar, BoolVar, FloatVar, IntVar # noqa: F401 - from .api import function # noqa: F401 from .gates import QubitIdentifierType # noqa: F401 from .program import Program, Verbatim, build_program # noqa: F401 from .transpiler import transpiler # noqa: F401 +from .types import ArrayVar, BitVar, BoolVar, FloatVar, IntVar # noqa: F401 from .types import qasm_range as range # noqa: F401 diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 75d12d78..fb2ec711 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -300,10 +300,9 @@ def _convert_program_as_subroutine( return_variable = None if isinstance(return_instance, list): ret_type = subroutine_function_call.subroutine_decl.return_type - return_variable = oqpy.ArrayVar( + return_variable = aq_types.ArrayVar( return_instance, dimensions=[d.value for d in ret_type.dimensions], - name=program_conversion_context.next_var_name(oqpy.ArrayVar), ) oqpy_program.set(return_variable, subroutine_function_call) elif return_instance is not None: @@ -335,7 +334,7 @@ def _make_return_instance( return_instance = None if return_type and aq_types.is_qasm_type(return_type): - return_instance = return_type(name=program_conversion_context.next_var_name(return_type)) + return_instance = return_type() elif return_type: if hasattr(return_type, "__origin__"): # Types from python's typing module, such as `List`. origin gives us `list`` diff --git a/src/braket/experimental/autoqasm/gates/measurements.py b/src/braket/experimental/autoqasm/gates/measurements.py index dda9ef28..ec6123cf 100644 --- a/src/braket/experimental/autoqasm/gates/measurements.py +++ b/src/braket/experimental/autoqasm/gates/measurements.py @@ -25,11 +25,12 @@ def my_program(): from typing import List, Union -from braket.experimental.autoqasm import BitVar, program +from braket.experimental.autoqasm import program +from braket.experimental.autoqasm import types as aq_types from braket.experimental.autoqasm.gates.qubits import QubitIdentifierType, _qubit -def measure(qubits: Union[QubitIdentifierType, List[QubitIdentifierType]]) -> BitVar: +def measure(qubits: Union[QubitIdentifierType, List[QubitIdentifierType]]) -> aq_types.BitVar: """Add qubit measurement statements to the program and assign the measurement results to bit variables. @@ -46,8 +47,7 @@ def measure(qubits: Union[QubitIdentifierType, List[QubitIdentifierType]]) -> Bi oqpy_program = program.get_program_conversion_context().get_oqpy_program() bit_var_size = len(qubits) if len(qubits) > 1 else None - bit_var = BitVar( - name=program.get_program_conversion_context().next_var_name(BitVar), + bit_var = aq_types.BitVar( size=bit_var_size, needs_declaration=True, ) diff --git a/src/braket/experimental/autoqasm/operators/__init__.py b/src/braket/experimental/autoqasm/operators/__init__.py index d99cee37..fa01e89e 100644 --- a/src/braket/experimental/autoqasm/operators/__init__.py +++ b/src/braket/experimental/autoqasm/operators/__init__.py @@ -21,6 +21,7 @@ # We need to either implement these, or determine they are not needed and remove them. # Operators below are imported directly from core autograph implementation +from braket.experimental.autoqasm.autograph.impl.api_core import autograph_artifact # noqa: F401 from braket.experimental.autoqasm.autograph.operators.variables import ( # noqa: F401 Undefined, UndefinedReturnValue, diff --git a/src/braket/experimental/autoqasm/operators/conditional_expressions.py b/src/braket/experimental/autoqasm/operators/conditional_expressions.py index a38fa7ec..faf22d96 100644 --- a/src/braket/experimental/autoqasm/operators/conditional_expressions.py +++ b/src/braket/experimental/autoqasm/operators/conditional_expressions.py @@ -51,7 +51,6 @@ def _oqpy_if_exp( """Overload of if_exp that stages an oqpy conditional.""" oqpy_program = program.get_program_conversion_context().get_oqpy_program() if isinstance(cond, oqpy.base.Var) and cond.name not in oqpy_program.declared_vars.keys(): - cond.name = program.get_program_conversion_context().next_var_name(type(cond)) oqpy_program.declare(cond) with oqpy.If(oqpy_program, cond): if_true() diff --git a/src/braket/experimental/autoqasm/operators/control_flow.py b/src/braket/experimental/autoqasm/operators/control_flow.py index e60eee1c..59ef5d00 100644 --- a/src/braket/experimental/autoqasm/operators/control_flow.py +++ b/src/braket/experimental/autoqasm/operators/control_flow.py @@ -155,7 +155,6 @@ def _oqpy_if_stmt( """Overload of if_stmt that stages an oqpy cond.""" oqpy_program = program.get_program_conversion_context().get_oqpy_program() if isinstance(cond, oqpy.base.Var) and cond.name not in oqpy_program.declared_vars.keys(): - cond.name = program.get_program_conversion_context().next_var_name(type(cond)) oqpy_program.declare(cond) with oqpy.If(oqpy_program, cond): body() diff --git a/src/braket/experimental/autoqasm/operators/logical.py b/src/braket/experimental/autoqasm/operators/logical.py index d16f5d2a..df40a9ae 100644 --- a/src/braket/experimental/autoqasm/operators/logical.py +++ b/src/braket/experimental/autoqasm/operators/logical.py @@ -16,13 +16,11 @@ from typing import Any, Union -import oqpy.base - from braket.experimental.autoqasm import program -from braket.experimental.autoqasm.types import is_qasm_type +from braket.experimental.autoqasm import types as aq_types -def eq(a: Any, b: Any) -> Union[bool, oqpy.BoolVar]: +def eq(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]: """Functional form of "equal". Args: @@ -32,17 +30,15 @@ def eq(a: Any, b: Any) -> Union[bool, oqpy.BoolVar]: Returns: Union[bool, BoolVar]: Whether the expressions are equal. """ - if is_qasm_type(a) or is_qasm_type(b): + if aq_types.is_qasm_type(a) or aq_types.is_qasm_type(b): return _oqpy_eq(a, b) else: return _py_eq(a, b) -def _oqpy_eq(a: Any, b: Any) -> oqpy.BoolVar: +def _oqpy_eq(a: Any, b: Any) -> aq_types.BoolVar: oqpy_program = program.get_program_conversion_context().get_oqpy_program() - is_equal = oqpy.BoolVar( - name=program.get_program_conversion_context().next_var_name(oqpy.BoolVar) - ) + is_equal = aq_types.BoolVar() oqpy_program.declare(is_equal) oqpy_program.set(is_equal, a == b) return is_equal diff --git a/src/braket/experimental/autoqasm/operators/slices.py b/src/braket/experimental/autoqasm/operators/slices.py index 4602605e..0c015c1f 100644 --- a/src/braket/experimental/autoqasm/operators/slices.py +++ b/src/braket/experimental/autoqasm/operators/slices.py @@ -54,7 +54,7 @@ def _oqpy_get_item(target: Any, i: Any, opts: GetItemOpts) -> Any: else: raise TypeError(f"{str(type(target))} object is not subscriptable") - var = base_type(name=program.get_program_conversion_context().next_var_name(base_type)) + var = base_type() oqpy_program.set(var, target[i]) return var diff --git a/src/braket/experimental/autoqasm/types/__init__.py b/src/braket/experimental/autoqasm/types/__init__.py index 70b0c61b..86221c3f 100644 --- a/src/braket/experimental/autoqasm/types/__init__.py +++ b/src/braket/experimental/autoqasm/types/__init__.py @@ -15,5 +15,13 @@ for type handling. """ -from .py_to_oqpy import map_type, wrap_value # noqa: F401 -from .types import is_qasm_type, qasm_range # noqa: F401 +from .conversions import map_type, wrap_value # noqa: F401 +from .types import ( # noqa: F401 + ArrayVar, + BitVar, + BoolVar, + FloatVar, + IntVar, + is_qasm_type, + qasm_range, +) diff --git a/src/braket/experimental/autoqasm/types/py_to_oqpy.py b/src/braket/experimental/autoqasm/types/conversions.py similarity index 82% rename from src/braket/experimental/autoqasm/types/py_to_oqpy.py rename to src/braket/experimental/autoqasm/types/conversions.py index fff42732..7989da1e 100644 --- a/src/braket/experimental/autoqasm/types/py_to_oqpy.py +++ b/src/braket/experimental/autoqasm/types/conversions.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. -"""Type conversions between Python and the internal oqpy representation for types.""" +"""Type conversions between Python and the autoqasm representation for types.""" import typing from functools import singledispatch @@ -20,7 +20,7 @@ import numpy as np import oqpy -from braket.experimental.autoqasm import program +from braket.experimental.autoqasm import types as aq_types def map_type(python_type: type) -> type: @@ -58,7 +58,7 @@ def map_type(python_type: type) -> type: @singledispatch def wrap_value(node: Any) -> Any: - """Wraps an object in an oqpy variable. + """Wraps an object in an autoqasm variable. Args: node (Any): The object to be wrapped. @@ -68,7 +68,7 @@ def wrap_value(node: Any) -> Any: type does not exist. Returns: - Any: The oqpy variable wrapping the given object. + Any: The autoqasm variable wrapping the given object. """ if node is None: return None @@ -79,25 +79,19 @@ def wrap_value(node: Any) -> Any: @wrap_value.register(bool) def _(node: bool): - return oqpy.BoolVar( - node, name=program.get_program_conversion_context().next_var_name(oqpy.BoolVar) - ) + return aq_types.BoolVar(node) @wrap_value.register(int) @wrap_value.register(np.integer) def _(node: Union[int, np.integer]): - return oqpy.IntVar( - node, name=program.get_program_conversion_context().next_var_name(oqpy.IntVar) - ) + return aq_types.IntVar(node) @wrap_value.register(float) @wrap_value.register(np.floating) def _(node: Union[float, np.floating]): - return oqpy.FloatVar( - node, name=program.get_program_conversion_context().next_var_name(oqpy.FloatVar) - ) + return aq_types.FloatVar(node) @wrap_value.register diff --git a/src/braket/experimental/autoqasm/types/types.py b/src/braket/experimental/autoqasm/types/types.py index e38a7f50..19dbe620 100644 --- a/src/braket/experimental/autoqasm/types/types.py +++ b/src/braket/experimental/autoqasm/types/types.py @@ -15,8 +15,11 @@ from typing import Any, Optional +import oqpy import oqpy.base +from braket.experimental.autoqasm import program + def is_qasm_type(val: Any) -> bool: """Returns whether the provided object is of a QASM type or is itself a QASM type @@ -53,3 +56,33 @@ def qasm_range(start: int, stop: Optional[int] = None, step: Optional[int] = 1) stop = start start = 0 return oqpy.Range(start, stop, step) + + +class ArrayVar(oqpy.ArrayVar): + def __init__(self, *args, **kwargs): + super(ArrayVar, self).__init__(*args, **kwargs) + self.name = program.get_program_conversion_context().next_var_name(oqpy.ArrayVar) + + +class BitVar(oqpy.BitVar): + def __init__(self, *args, **kwargs): + super(BitVar, self).__init__(*args, **kwargs) + self.name = program.get_program_conversion_context().next_var_name(oqpy.BitVar) + + +class BoolVar(oqpy.BoolVar): + def __init__(self, *args, **kwargs): + super(BoolVar, self).__init__(*args, **kwargs) + self.name = program.get_program_conversion_context().next_var_name(oqpy.BoolVar) + + +class FloatVar(oqpy.FloatVar): + def __init__(self, *args, **kwargs): + super(FloatVar, self).__init__(*args, **kwargs) + self.name = program.get_program_conversion_context().next_var_name(oqpy.FloatVar) + + +class IntVar(oqpy.IntVar): + def __init__(self, *args, **kwargs): + super(IntVar, self).__init__(*args, **kwargs) + self.name = program.get_program_conversion_context().next_var_name(oqpy.IntVar) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index c9efea3c..26311f1e 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -229,10 +229,10 @@ def test_bell_measurement_declared() -> None: bit[2] c = {0, 0}; h __qubits__[0]; cnot __qubits__[0], __qubits__[1]; -bit[2] __bit_0__; -__bit_0__[0] = measure __qubits__[0]; -__bit_0__[1] = measure __qubits__[1]; -c = __bit_0__;""" +bit[2] __bit_1__; +__bit_1__[0] = measure __qubits__[0]; +__bit_1__[1] = measure __qubits__[1]; +c = __bit_1__;""" assert bell_measurement_declared().to_ir() == expected @@ -464,10 +464,10 @@ def ground_state_measurements() -> bit[3] { __bit_0__[2] = measure __qubits__[1]; return __bit_0__; } +qubit[6] __qubits__; """ # TODO: this should be `bit[3]`, but there's a bug. It's being tracked in an issue. - expected += """qubit[6] __qubits__; -bit __bit_1__; + expected += """bit __bit_1__; __bit_1__ = ground_state_measurements();""" assert ground_state_measurements_wrapper().to_ir() == expected @@ -792,3 +792,26 @@ def main() -> None: with pytest.raises(errors.InconsistentNumQubits): main() + + +def test_nested_function(): + @aq.function + def make_ghz(n: int) -> None: + def ghz(n: int): + if n == 1: + h(0) + else: + ghz(n - 1) + cnot(0, n - 1) + + ghz(n) + + expected = """OPENQASM 3.0; +qubit[5] __qubits__; +h __qubits__[0]; +cnot __qubits__[0], __qubits__[1]; +cnot __qubits__[0], __qubits__[2]; +cnot __qubits__[0], __qubits__[3]; +cnot __qubits__[0], __qubits__[4];""" + + assert make_ghz(5).to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py index ffaa81cf..500a23de 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_operators.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py @@ -376,10 +376,10 @@ def measure_to_slice(): expected = """OPENQASM 3.0; qubit[1] __qubits__; bit[10] b0; -bit __bit_0__; -__bit_0__ = measure __qubits__[0]; +bit __bit_1__; +__bit_1__ = measure __qubits__[0]; bit c; -c = __bit_0__; +c = __bit_1__; b0[3] = c;""" assert measure_to_slice().to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index 709346c2..cbc1ea0e 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -49,7 +49,6 @@ def test_return_bit(): @aq.function def ret_test() -> aq.BitVar: - # TODO: These should work even in one line res = aq.BitVar(1) return res @@ -62,8 +61,8 @@ def ret_test() -> bit { bit res = 1; return res; } -bit __bit_0__; -__bit_0__ = ret_test();""" +bit __bit_1__; +__bit_1__ = ret_test();""" assert main().to_ir() == expected @@ -85,8 +84,8 @@ def ret_test() -> int[32] { int[32] res = 1; return res; } -int[32] __int_0__ = 0; -__int_0__ = ret_test();""" +int[32] __int_1__ = 0; +__int_1__ = ret_test();""" assert main().to_ir() == expected @@ -108,8 +107,8 @@ def ret_test() -> float[64] { float[64] res = 1.0; return res; } -float[64] __float_0__ = 0.0; -__float_0__ = ret_test();""" +float[64] __float_1__ = 0.0; +__float_1__ = ret_test();""" assert main().to_ir() == expected @@ -131,8 +130,8 @@ def ret_test() -> bool { bool res = true; return res; } -bool __bool_0__ = false; -__bool_0__ = ret_test();""" +bool __bool_1__ = false; +__bool_1__ = ret_test();""" assert main().to_ir() == expected @@ -156,8 +155,8 @@ def add(int[32] a, int[32] b) -> int[32] { } int[32] a = 5; int[32] b = 6; -int[32] __int_0__ = 0; -__int_0__ = add(a, b);""" +int[32] __int_2__ = 0; +__int_2__ = add(a, b);""" assert ret_test().to_ir() == expected @@ -189,8 +188,8 @@ def ret_test() -> array[int[32], 3] { array[int[32], 3] res = {1, 2, 3}; return res; } -array[int[32], 3] __arr_0__ = {}; -__arr_0__ = ret_test();""" +array[int[32], 3] __arr_1__ = {}; +__arr_1__ = ret_test();""" assert main().to_ir() == expected @@ -212,8 +211,8 @@ def helper() -> int[32] { int[32] res = 1; return res; } -int[32] __int_0__ = 0; -__int_0__ = helper();""" +int[32] __int_1__ = 0; +__int_1__ = helper();""" assert ret_test().to_ir() == expected @@ -299,8 +298,6 @@ def annotation_test(array[int[32], 10] input) { def test_map_other(): """Test unexpected input parameter type handling.""" - # TODO: Should be able to pass aq.Bit directly to annotation_test - @aq.function def annotation_test(input: aq.BitVar): pass @@ -319,6 +316,26 @@ def annotation_test(bit input) { assert main().to_ir() == expected +def test_map_other_unnamed_arg(): + """Test unexpected input parameter type handling with unnamed arg.""" + + @aq.function + def annotation_test(input: aq.BitVar): + pass + + @aq.function + def main(): + annotation_test(aq.BitVar(1)) + + expected = """OPENQASM 3.0; +def annotation_test(bit input) { +} +bit __bit_0__ = 1; +annotation_test(__bit_0__);""" + + assert main().to_ir() == expected + + def test_unnamed_retval_python_type() -> None: """Tests subroutines which return unnamed Python values.""" @@ -357,8 +374,8 @@ def retval_test() -> bit { bit retval_ = 1; return retval_; } -bit __bit_0__; -__bit_0__ = retval_test();""" +bit __bit_1__; +__bit_1__ = retval_test();""" assert caller().to_ir() == expected_qasm From 772ff1585dded24cfb52a34f082632f6b32a37e6 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 25 Jul 2023 12:07:36 -0400 Subject: [PATCH 0771/1165] fix: update autoqasm dependency mcm-sim branch commit hash (#642) --- setup.py | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 44ce1237..b6528355 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ # to get the version of the simulator that supports the mcm=True argument for Monte Carlo # simulation of mid-circuit measurement, which AutoQASM requires. # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main. - "amazon-braket-default-simulator @ git+https://github.com/aws/amazon-braket-default-simulator-python.git@731f2545961abb41dcf13bf26e99f4ded79a15aa#egg=amazon-braket-default-simulator", # noqa E501 + "amazon-braket-default-simulator @ git+https://github.com/aws/amazon-braket-default-simulator-python.git@800a31a0860de5ec09532a3dbc127059d03cf9ac#egg=amazon-braket-default-simulator", # noqa E501 # Pin the latest commit of the qubit-array branch of ajberdy/oqpy.git to get the version of # oqpy which contains changes that AutoQASM relies on, including the QubitArray type. # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main. diff --git a/tox.ini b/tox.ini index 31448410..5873a686 100644 --- a/tox.ini +++ b/tox.ini @@ -95,4 +95,4 @@ commands = deps = # If you need to test on a certain branch, add @ after .git git+https://github.com/aws/amazon-braket-schemas-python.git - git+https://github.com/aws/amazon-braket-default-simulator-python.git@731f2545961abb41dcf13bf26e99f4ded79a15aa # mcm-sim branch + git+https://github.com/aws/amazon-braket-default-simulator-python.git@800a31a0860de5ec09532a3dbc127059d03cf9ac # mcm-sim branch From 34ac14505518e1ff105d826cbcf113b4735badcf Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 25 Jul 2023 16:15:07 +0000 Subject: [PATCH 0772/1165] prepare release v1.52.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 12752779..f4f0f5d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.52.0 (2023-07-25) + +### Features + + * Support symbolic expressions in `from_ir` + +### Bug Fixes and Other Changes + + * local import in job example + * Add parameter support + ## v1.51.0 (2023-07-21) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 743d07c9..5f3b74f4 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.51.1.dev0" +__version__ = "1.52.0" From 5ea718a6c899d535ba2b2b6421cd56b9bddbb95b Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 25 Jul 2023 16:15:07 +0000 Subject: [PATCH 0773/1165] update development version to v1.52.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 5f3b74f4..b6203397 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.52.0" +__version__ = "1.52.1.dev0" From 4e2989f7d4e959aaeab870e4bb41e33b13c448cb Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Tue, 25 Jul 2023 11:57:21 -0700 Subject: [PATCH 0774/1165] fix: pull latest container image by default (#643) --- setup.py | 2 +- src/braket/jobs/quantum_job_creation.py | 5 +++-- test/unit_tests/braket/jobs/test_quantum_job_creation.py | 6 ++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 226189ee..bca0b392 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.18.0", - "amazon-braket-default-simulator>=1.18.1", + "amazon-braket-default-simulator>=1.19.0", "oqpy~=0.1.1", "setuptools", "backoff", diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py index b6f1a41e..400bb03b 100644 --- a/src/braket/jobs/quantum_job_creation.py +++ b/src/braket/jobs/quantum_job_creation.py @@ -24,6 +24,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union from braket.aws.aws_session import AwsSession +from braket.jobs import Framework, image_uris from braket.jobs.config import ( CheckpointConfig, DeviceConfig, @@ -178,8 +179,8 @@ def prepare_quantum_job( "compressionType": "GZIP", } } - if image_uri: - algorithm_specification["containerImage"] = {"uri": image_uri} + image_uri = image_uri or image_uris.retrieve_image(Framework.BASE, aws_session.region) + algorithm_specification["containerImage"] = {"uri": image_uri} if not output_data_config.s3Path: output_data_config.s3Path = AwsSession.construct_s3_uri( default_bucket, 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 842117c0..f1a1f2fc 100644 --- a/test/unit_tests/braket/jobs/test_quantum_job_creation.py +++ b/test/unit_tests/braket/jobs/test_quantum_job_creation.py @@ -22,6 +22,7 @@ import pytest from braket.aws import AwsSession +from braket.jobs import Framework, image_uris from braket.jobs.config import ( CheckpointConfig, InstanceConfig, @@ -46,6 +47,7 @@ def aws_session(): _aws_session = Mock(spec=AwsSession) _aws_session.default_bucket.return_value = "default-bucket-name" _aws_session.get_default_jobs_role.return_value = "default-role-arn" + _aws_session.region = "us-east-1" return _aws_session @@ -347,8 +349,8 @@ def _translate_creation_args(create_job_args): "compressionType": "GZIP", } } - if image_uri: - algorithm_specification["containerImage"] = {"uri": image_uri} + image_uri = image_uri or image_uris.retrieve_image(Framework.BASE, aws_session.region) + algorithm_specification["containerImage"] = {"uri": image_uri} tags = create_job_args.get("tags", {}) test_kwargs = { From c3bbab8627cf6cd39df86846eee691d364fd9f4f Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 25 Jul 2023 15:34:08 -0700 Subject: [PATCH 0775/1165] fix: change all tests in the circuit test path to use with pytest.raises (#645) --- src/braket/circuits/braket_program_context.py | 4 +- .../braket/circuits/test_angled_gate.py | 12 +-- .../braket/circuits/test_circuit.py | 72 +++++++------- .../braket/circuits/test_circuit_helpers.py | 36 +++---- .../circuits/test_compiler_directive.py | 8 +- test/unit_tests/braket/circuits/test_gate.py | 8 +- test/unit_tests/braket/circuits/test_gates.py | 36 +++---- .../braket/circuits/test_instruction.py | 20 ++-- .../braket/circuits/test_moments.py | 12 +-- test/unit_tests/braket/circuits/test_noise.py | 96 +++++++++---------- .../braket/circuits/test_noise_helpers.py | 88 ++++++++--------- .../unit_tests/braket/circuits/test_noises.py | 17 ++-- .../braket/circuits/test_observable.py | 44 ++++----- .../braket/circuits/test_observables.py | 20 ++-- .../braket/circuits/test_quantum_operator.py | 40 ++++---- .../circuits/test_quantum_operator_helpers.py | 20 ++-- test/unit_tests/braket/circuits/test_qubit.py | 8 +- .../braket/circuits/test_result_type.py | 36 +++---- .../braket/circuits/test_result_types.py | 4 +- 19 files changed, 291 insertions(+), 290 deletions(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index b41eb3b9..afd6ce78 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.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, Optional, Tuple +from typing import Any, List, Optional, Tuple import numpy as np from sympy import Expr @@ -134,7 +134,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): + def handle_parameter_value(self, value: Any) -> Any: if isinstance(value, Expr): return FreeParameterExpression(str(value)) return value diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index 8dde92f2..168e193c 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -30,9 +30,9 @@ def test_is_operator(angled_gate): assert isinstance(angled_gate, Gate) -@pytest.mark.xfail(raises=ValueError) def test_angle_is_none(): - AngledGate(qubit_count=1, ascii_symbols=["foo"], angle=None) + with pytest.raises(ValueError): + AngledGate(qubit_count=1, ascii_symbols=["foo"], angle=None) def test_getters(): @@ -46,9 +46,9 @@ def test_getters(): assert gate.angle == angle -@pytest.mark.xfail(raises=AttributeError) def test_angle_setter(angled_gate): - angled_gate.angle = 0.14 + with pytest.raises(AttributeError): + angled_gate.angle = 0.14 def test_adjoint(angled_gate): @@ -111,11 +111,11 @@ def test_angle_adjoint(): assert np.array_equal(gate2_adj[0].ascii_symbols, ["foo(-0.15)"]) -@pytest.mark.xfail(raises=NotImplementedError) def test_bind_values(): theta = FreeParameter("theta") gate = AngledGate(angle=theta, qubit_count=1, ascii_symbols=["bar"]) - gate.bind_values(theta=1) + with pytest.raises(NotImplementedError): + gate.bind_values(theta=1) def test_angled_gate_with_expr(): diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index dad69c6d..6acf5690 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -339,9 +339,9 @@ def test_add_result_type_same_observable_wrong_target_order_hermitian(): assert not circ.basis_rotation_instructions -@pytest.mark.xfail(raises=TypeError) def test_add_result_type_with_target_and_mapping(prob): - Circuit().add_result_type(prob, target=[10], target_mapping={0: 10}) + with pytest.raises(TypeError): + Circuit().add_result_type(prob, target=[10], target_mapping={0: 10}) def test_add_instruction_default(cnot_instr): @@ -367,9 +367,9 @@ def test_add_multiple_single_qubit_instruction(h_instr): assert circ == expected -@pytest.mark.xfail(raises=TypeError) def test_add_instruction_with_target_and_mapping(h): - Circuit().add_instruction(h, target=[10], target_mapping={0: 10}) + with pytest.raises(TypeError): + Circuit().add_instruction(h, target=[10], target_mapping={0: 10}) def test_add_circuit_default(bell_pair): @@ -411,9 +411,9 @@ def test_add_circuit_with_target_and_non_continuous_qubits(): assert circ == expected -@pytest.mark.xfail(raises=TypeError) def test_add_circuit_with_target_and_mapping(h): - Circuit().add_circuit(h, target=[10], target_mapping={0: 10}) + with pytest.raises(TypeError): + Circuit().add_circuit(h, target=[10], target_mapping={0: 10}) def test_add_verbatim_box(): @@ -481,16 +481,16 @@ def test_add_verbatim_box_with_target(cnot): assert circ == expected -@pytest.mark.xfail(raises=TypeError) def test_add_verbatim_box_with_target_and_mapping(h): - Circuit().add_verbatim_box(h, target=[10], target_mapping={0: 10}) + with pytest.raises(TypeError): + Circuit().add_verbatim_box(h, target=[10], target_mapping={0: 10}) -@pytest.mark.xfail(raises=ValueError) def test_add_verbatim_box_result_types(): - Circuit().h(0).add_verbatim_box( - Circuit().cnot(0, 1).expectation(observable=Observable.X(), target=0) - ) + with pytest.raises(ValueError): + Circuit().h(0).add_verbatim_box( + Circuit().cnot(0, 1).expectation(observable=Observable.X(), target=0) + ) def test_add_with_instruction_with_default(cnot_instr): @@ -581,9 +581,9 @@ def test_add_operator(h, bell_pair): assert addition != (h + h + bell_pair + h) -@pytest.mark.xfail(raises=TypeError) def test_iadd_with_unknown_type(h): - h += 100 + with pytest.raises(TypeError): + h += 100 def test_subroutine_register(): @@ -1627,16 +1627,16 @@ def test_as_unitary_empty_instructions_returns_empty_array(): ), ], ) -@pytest.mark.xfail(raises=TypeError) def test_as_unitary_noise_raises_error(circuit): - circuit.as_unitary() + with pytest.raises(TypeError): + circuit.as_unitary() -@pytest.mark.xfail(raises=TypeError) def test_as_unitary_parameterized(): theta = FreeParameter("theta") circ = Circuit().rx(angle=theta, target=0) - assert np.allclose(circ.as_unitary()) + with pytest.raises(TypeError): + assert np.allclose(circ.as_unitary()) def test_as_unitary_noise_not_apply_returns_expected_unitary(recwarn): @@ -2040,16 +2040,16 @@ def test_to_unitary_empty_instructions_returns_empty_array(): ), ], ) -@pytest.mark.xfail(raises=TypeError) def test_to_unitary_noise_raises_error(circuit): - circuit.to_unitary() + with pytest.raises(TypeError): + circuit.to_unitary() -@pytest.mark.xfail(raises=TypeError) def test_to_unitary_parameterized(): theta = FreeParameter("theta") circ = Circuit().rx(angle=theta, target=0) - assert np.allclose(circ.to_unitary()) + with pytest.raises(TypeError): + np.allclose(circ.to_unitary()) def test_to_unitary_noise_not_apply_returns_expected_unitary(recwarn): @@ -2676,36 +2676,36 @@ def test_depth_getter(h): assert h.depth is h._moments.depth -@pytest.mark.xfail(raises=AttributeError) def test_depth_setter(h): - h.depth = 1 + with pytest.raises(AttributeError): + h.depth = 1 def test_instructions_getter(h): assert h.instructions == list(h._moments.values()) -@pytest.mark.xfail(raises=AttributeError) def test_instructions_setter(h, h_instr): - h.instructions = [h_instr] + with pytest.raises(AttributeError): + h.instructions = [h_instr] def test_moments_getter(h): assert h.moments is h._moments -@pytest.mark.xfail(raises=AttributeError) def test_moments_setter(h): - h.moments = Moments() + with pytest.raises(AttributeError): + h.moments = Moments() def test_qubit_count_getter(h): assert h.qubit_count is h._moments.qubit_count -@pytest.mark.xfail(raises=AttributeError) def test_qubit_count_setter(h): - h.qubit_count = 1 + with pytest.raises(AttributeError): + h.qubit_count = 1 @pytest.mark.parametrize( @@ -2771,9 +2771,9 @@ def test_qubits_getter(h): assert h.qubits is not h._moments.qubits -@pytest.mark.xfail(raises=AttributeError) def test_qubits_setter(h): - h.qubits = QubitSet(1) + with pytest.raises(AttributeError): + h.qubits = QubitSet(1) def test_diagram(h): @@ -2926,20 +2926,20 @@ def test_make_bound_circuit_partial_bind(): assert circ_new == expected_circ and circ_new.parameters == expected_parameters -@pytest.mark.xfail(raises=ValueError) def test_make_bound_circuit_non_existent_param(): theta = FreeParameter("theta") input_val = np.pi circ = Circuit().ry(angle=theta, target=0).ry(angle=theta, target=1).ry(angle=theta, target=2) - circ.make_bound_circuit({"alpha": input_val}, strict=True) + with pytest.raises(ValueError): + circ.make_bound_circuit({"alpha": input_val}, strict=True) -@pytest.mark.xfail(raises=ValueError) 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) - circ.make_bound_circuit({"theta": input_val}) + with pytest.raises(ValueError): + circ.make_bound_circuit({"theta": input_val}) def test_circuit_with_expr(): diff --git a/test/unit_tests/braket/circuits/test_circuit_helpers.py b/test/unit_tests/braket/circuits/test_circuit_helpers.py index 84efd585..56f7fd2e 100644 --- a/test/unit_tests/braket/circuits/test_circuit_helpers.py +++ b/test/unit_tests/braket/circuits/test_circuit_helpers.py @@ -17,19 +17,19 @@ from braket.circuits.circuit_helpers import validate_circuit_and_shots -@pytest.mark.xfail(raises=ValueError) def test_validate_circuit_and_shots_no_instructions(): - validate_circuit_and_shots(Circuit(), 100) + with pytest.raises(ValueError): + validate_circuit_and_shots(Circuit(), 100) -@pytest.mark.xfail(raises=ValueError) def test_validate_circuit_and_shots_0_no_instructions(): - validate_circuit_and_shots(Circuit(), 0) + with pytest.raises(ValueError): + validate_circuit_and_shots(Circuit(), 0) -@pytest.mark.xfail(raises=ValueError) def test_validate_circuit_and_shots_0_no_results(): - validate_circuit_and_shots(Circuit().h(0), 0) + with pytest.raises(ValueError): + validate_circuit_and_shots(Circuit().h(0), 0) def test_validate_circuit_and_shots_100_no_results(): @@ -53,14 +53,14 @@ def test_validate_circuit_and_shots_100_results_mixed_result(): ) -@pytest.mark.xfail(raises=ValueError) def test_validate_circuit_and_shots_100_result_state_vector(): - validate_circuit_and_shots(Circuit().h(0).state_vector(), 100) + with pytest.raises(ValueError): + validate_circuit_and_shots(Circuit().h(0).state_vector(), 100) -@pytest.mark.xfail(raises=ValueError) def test_validate_circuit_and_shots_100_result_amplitude(): - validate_circuit_and_shots(Circuit().h(0).amplitude(state=["0"]), 100) + with pytest.raises(ValueError): + validate_circuit_and_shots(Circuit().h(0).amplitude(state=["0"]), 100) def test_validate_circuit_and_shots_0_noncommuting(): @@ -73,15 +73,15 @@ def test_validate_circuit_and_shots_0_noncommuting(): ) -@pytest.mark.xfail(raises=ValueError) def test_validate_circuit_and_shots_100_noncommuting(): - validate_circuit_and_shots( - Circuit() - .h(0) - .expectation(observables.X() @ observables.Y(), [0, 1]) - .expectation(observables.Y() @ observables.X(), [0, 1]), - 100, - ) + with pytest.raises(ValueError): + validate_circuit_and_shots( + Circuit() + .h(0) + .expectation(observables.X() @ observables.Y(), [0, 1]) + .expectation(observables.Y() @ observables.X(), [0, 1]), + 100, + ) def test_probability_limit(): diff --git a/test/unit_tests/braket/circuits/test_compiler_directive.py b/test/unit_tests/braket/circuits/test_compiler_directive.py index a6a5c29e..f212b380 100644 --- a/test/unit_tests/braket/circuits/test_compiler_directive.py +++ b/test/unit_tests/braket/circuits/test_compiler_directive.py @@ -36,9 +36,9 @@ def test_ascii_symbols(compiler_directive, ascii_symbols): assert compiler_directive.ascii_symbols == tuple(ascii_symbols) -@pytest.mark.xfail(raises=ValueError) def test_none_ascii(): - CompilerDirective(ascii_symbols=None) + with pytest.raises(ValueError): + CompilerDirective(ascii_symbols=None) def test_name(compiler_directive): @@ -46,9 +46,9 @@ def test_name(compiler_directive): assert compiler_directive.name == expected -@pytest.mark.xfail(raises=NotImplementedError) def test_counterpart_not_implemented_by_default(compiler_directive): - compiler_directive.counterpart() + with pytest.raises(NotImplementedError): + compiler_directive.counterpart() def test_str(compiler_directive): diff --git a/test/unit_tests/braket/circuits/test_gate.py b/test/unit_tests/braket/circuits/test_gate.py index 600a108f..0b6d9be6 100644 --- a/test/unit_tests/braket/circuits/test_gate.py +++ b/test/unit_tests/braket/circuits/test_gate.py @@ -26,14 +26,14 @@ def test_is_operator(gate): assert isinstance(gate, QuantumOperator) -@pytest.mark.xfail(raises=NotImplementedError) def test_adjoint_not_implemented_by_default(gate): - gate.adjoint() + with pytest.raises(NotImplementedError): + gate.adjoint() -@pytest.mark.xfail(raises=NotImplementedError) def test_to_matrix_not_implemented_by_default(gate): - gate.to_matrix(None) + with pytest.raises(NotImplementedError): + gate.to_matrix(None) def test_matrix_equivalence(): diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 90dc7d5a..e10170cb 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -981,35 +981,35 @@ def to_ir(pulse_gate): assert ac_bound_ir != a_bound_ir -@pytest.mark.xfail(raises=ValueError) def test_pulse_gate_capture_throws(): - Circuit().pulse_gate( - 0, PulseSequence().capture_v0(Frame("user_frame", Port("device_port_x", dt=1e-9), 1e9)) - ) + with pytest.raises(ValueError): + Circuit().pulse_gate( + 0, PulseSequence().capture_v0(Frame("user_frame", Port("device_port_x", dt=1e-9), 1e9)) + ) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("matrix", invalid_unitary_matrices) def test_unitary_invalid_matrix(matrix): - Gate.Unitary(matrix=matrix) + with pytest.raises(ValueError): + Gate.Unitary(matrix=matrix) -@pytest.mark.xfail(raises=ValueError) def test_unitary_matrix_target_size_mismatch(): - Circuit().unitary( - matrix=np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]), targets=[0] - ) + with pytest.raises(ValueError): + Circuit().unitary( + matrix=np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]), targets=[0] + ) -@pytest.mark.xfail(raises=NotImplementedError) def test_pulse_gate_to_matrix(): - Gate.PulseGate( - PulseSequence().play( - Frame("user_frame", Port("device_port_x", 1e-9), 1e9), - ArbitraryWaveform([1, 2], "arb_wf"), - ), - 1, - ).to_matrix() + with pytest.raises(NotImplementedError): + Gate.PulseGate( + PulseSequence().play( + Frame("user_frame", Port("device_port_x", 1e-9), 1e9), + ArbitraryWaveform([1, 2], "arb_wf"), + ), + 1, + ).to_matrix() @pytest.mark.parametrize( diff --git a/test/unit_tests/braket/circuits/test_instruction.py b/test/unit_tests/braket/circuits/test_instruction.py index 3b8f2fc9..212e6594 100644 --- a/test/unit_tests/braket/circuits/test_instruction.py +++ b/test/unit_tests/braket/circuits/test_instruction.py @@ -36,14 +36,14 @@ def ccry(): return Instruction(Gate.Ry(1.23), 0, control=[1, 2]) -@pytest.mark.xfail(raises=ValueError) def test_empty_operator(): - Instruction(None, target=0) + with pytest.raises(ValueError): + Instruction(None, target=0) -@pytest.mark.xfail(raises=ValueError) def test_non_matching_qubit_set_and_qubit_count(): - Instruction(Gate.CNot(), target=[0, 0]) + with pytest.raises(ValueError): + Instruction(Gate.CNot(), target=[0, 0]) def test_init_with_qubits(): @@ -79,14 +79,14 @@ def test_getters(): assert instr.target == QubitSet([0, 1]) -@pytest.mark.xfail(raises=AttributeError) def test_operator_setter(instr): - instr.operator = Gate.H() + with pytest.raises(AttributeError): + instr.operator = Gate.H() -@pytest.mark.xfail(raises=AttributeError) def test_target_setter(instr): - instr.target = QubitSet(0) + with pytest.raises(AttributeError): + instr.target = QubitSet(0) def test_adjoint_gate(): @@ -100,9 +100,9 @@ def test_adjoint_compiler_directive(): assert instr == [Instruction(compiler_directives.EndVerbatimBox())] -@pytest.mark.xfail(raises=NotImplementedError) def test_adjoint_unsupported(): - Instruction(Noise.BitFlip(0.1), 0).adjoint() + with pytest.raises(NotImplementedError): + Instruction(Noise.BitFlip(0.1), 0).adjoint() def test_str(instr): diff --git a/test/unit_tests/braket/circuits/test_moments.py b/test/unit_tests/braket/circuits/test_moments.py index 53697321..982649f6 100644 --- a/test/unit_tests/braket/circuits/test_moments.py +++ b/test/unit_tests/braket/circuits/test_moments.py @@ -74,9 +74,9 @@ def test_depth(): assert moments.depth == 2 -@pytest.mark.xfail(raises=AttributeError) def test_depth_setter(moments): - moments.depth = 5 + with pytest.raises(AttributeError): + moments.depth = 5 def test_overlaping_qubits(): @@ -97,14 +97,14 @@ def test_qubits(): assert moments.qubit_count == len(expected) -@pytest.mark.xfail(raises=AttributeError) def test_qubits_setter(moments): - moments.qubits = QubitSet(1) + with pytest.raises(AttributeError): + moments.qubits = QubitSet(1) -@pytest.mark.xfail(raises=AttributeError) def test_qubit_count_setter(moments): - moments.qubit_count = 1 + with pytest.raises(AttributeError): + moments.qubit_count = 1 def test_time_slices(): diff --git a/test/unit_tests/braket/circuits/test_noise.py b/test/unit_tests/braket/circuits/test_noise.py index 587ec0f1..81b217d3 100644 --- a/test/unit_tests/braket/circuits/test_noise.py +++ b/test/unit_tests/braket/circuits/test_noise.py @@ -75,183 +75,183 @@ def generalized_amplitude_damping_noise(): ) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("qubit_count, ascii_symbols", invalid_data_qubit_count) def test_invalid_data_qubit_count(qubit_count, ascii_symbols): - Noise(qubit_count, ascii_symbols) + with pytest.raises(ValueError): + Noise(qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("qubit_count, ascii_symbols", invalid_data_ascii_symbols) def test_invalid_data_ascii_symbols(qubit_count, ascii_symbols): - Noise(qubit_count, ascii_symbols) + with pytest.raises(ValueError): + Noise(qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("qubit_count, ascii_symbols", invalid_data_ascii_symbols_length) def test_invalid_data_ascii_symbols_length(qubit_count, ascii_symbols): - Noise(qubit_count, ascii_symbols) + with pytest.raises(ValueError): + Noise(qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("probability", invalid_data_prob) def test_invalid_data_single_prob(probability): qubit_count = 1 ascii_symbols = ["foo"] - SingleProbabilisticNoise(probability, qubit_count, ascii_symbols) + with pytest.raises(ValueError): + SingleProbabilisticNoise(probability, qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("probability", invalid_data_prob) def test_invalid_data_single_prob_34(probability): qubit_count = 1 ascii_symbols = ["foo"] - SingleProbabilisticNoise_34(probability, qubit_count, ascii_symbols) + with pytest.raises(ValueError): + SingleProbabilisticNoise_34(probability, qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("probability", invalid_data_prob) def test_invalid_data_single_prob_1516(probability): qubit_count = 1 ascii_symbols = ["foo"] - SingleProbabilisticNoise_1516(probability, qubit_count, ascii_symbols) + with pytest.raises(ValueError): + SingleProbabilisticNoise_1516(probability, qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=TypeError) @pytest.mark.parametrize("probability", invalid_data_prob_2) def test_invalid_data_type_single_prob(probability): qubit_count = 1 ascii_symbols = ["foo"] - SingleProbabilisticNoise(probability, qubit_count, ascii_symbols) + with pytest.raises(TypeError): + SingleProbabilisticNoise(probability, qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=TypeError) @pytest.mark.parametrize("probability", invalid_data_prob_2) def test_invalid_data_type_single_prob_34(probability): qubit_count = 1 ascii_symbols = ["foo"] - SingleProbabilisticNoise_34(probability, qubit_count, ascii_symbols) + with pytest.raises(TypeError): + SingleProbabilisticNoise_34(probability, qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=TypeError) @pytest.mark.parametrize("probability", invalid_data_prob_2) def test_invalid_data_type_single_prob_1516(probability): qubit_count = 1 ascii_symbols = ["foo"] - SingleProbabilisticNoise_1516(probability, qubit_count, ascii_symbols) + with pytest.raises(TypeError): + SingleProbabilisticNoise_1516(probability, qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("probX", invalid_data_prob) def test_invalid_data_pauli_probX(probX): qubit_count = 1 ascii_symbols = ["foo"] probY = 0.1 probZ = 0.1 - PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) + with pytest.raises(ValueError): + PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("probY", invalid_data_prob) def test_invalid_data_pauli_probY(probY): qubit_count = 1 ascii_symbols = ["foo"] probX = 0.1 probZ = 0.1 - PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) + with pytest.raises(ValueError): + PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("probZ", invalid_data_prob) def test_invalid_data_pauli_probZ(probZ): qubit_count = 1 ascii_symbols = ["foo"] probX = 0.1 probY = 0.1 - PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) + with pytest.raises(ValueError): + PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=TypeError) @pytest.mark.parametrize("probX", invalid_data_prob_2) def test_invalid_data_type_pauli_probX(probX): qubit_count = 1 ascii_symbols = ["foo"] probY = 0.1 probZ = 0.1 - PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) + with pytest.raises(TypeError): + PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=TypeError) @pytest.mark.parametrize("probY", invalid_data_prob_2) def test_invalid_data_type_pauli_probY(probY): qubit_count = 1 ascii_symbols = ["foo"] probX = 0.1 probZ = 0.1 - PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) + with pytest.raises(TypeError): + PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=TypeError) @pytest.mark.parametrize("probZ", invalid_data_prob_2) def test_invalid_data_type_pauli_probZ(probZ): qubit_count = 1 ascii_symbols = ["foo"] probX = 0.1 probY = 0.1 - PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) + with pytest.raises(TypeError): + PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=ValueError) def test_invalid_data_pauli_sum(): qubit_count = 1 ascii_symbols = ["foo"] probX = 0.1 probY = 0.1 probZ = 0.9 - PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) + with pytest.raises(ValueError): + PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("gamma", invalid_data_prob_damping) def test_invalid_data_damping_prob(gamma): qubit_count = 1 ascii_symbols = ["foo"] - DampingNoise(gamma, qubit_count, ascii_symbols) + with pytest.raises(ValueError): + DampingNoise(gamma, qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("probability", invalid_data_prob_damping) def test_invalid_data_generalized_amplitude_damping_prob(probability): qubit_count = 1 ascii_symbols = ["foo"] gamma = 0.1 - GeneralizedAmplitudeDampingNoise(gamma, probability, qubit_count, ascii_symbols) + with pytest.raises(ValueError): + GeneralizedAmplitudeDampingNoise(gamma, probability, qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=TypeError) @pytest.mark.parametrize("gamma", invalid_data_prob_damping_2) def test_invalid_data_type_damping_prob(gamma): qubit_count = 1 ascii_symbols = ["foo"] - DampingNoise(gamma, qubit_count, ascii_symbols) + with pytest.raises(TypeError): + DampingNoise(gamma, qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=TypeError) @pytest.mark.parametrize("probability", invalid_data_prob_damping_2) def test_invalid_data_type_generalized_amplitude_damping_prob(probability): qubit_count = 1 ascii_symbols = ["foo"] gamma = 0.1 - GeneralizedAmplitudeDampingNoise(gamma, probability, qubit_count, ascii_symbols) + with pytest.raises(TypeError): + GeneralizedAmplitudeDampingNoise(gamma, probability, qubit_count, ascii_symbols) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("gamma", invalid_data_prob_damping) def test_invalid_data_generalized_amplitude_damping_gamma(gamma): qubit_count = 1 ascii_symbols = ["foo"] probability = 0.1 - GeneralizedAmplitudeDampingNoise(gamma, probability, qubit_count, ascii_symbols) + with pytest.raises(ValueError): + GeneralizedAmplitudeDampingNoise(gamma, probability, qubit_count, ascii_symbols) def test_ascii_symbols(base_noise): @@ -290,14 +290,14 @@ def test_noise_to_ir( assert exc.value.args[0] == expected_message -@pytest.mark.xfail(raises=NotImplementedError) def test_to_matrix_not_implemented_by_default(base_noise): - base_noise.to_matrix(None) + with pytest.raises(NotImplementedError): + base_noise.to_matrix(None) -@pytest.mark.xfail(raises=NotImplementedError) def test_invalid_deserializatoin(): - Noise.from_dict({}) + with pytest.raises(NotImplementedError): + Noise.from_dict({}) @pytest.mark.parametrize( @@ -430,9 +430,9 @@ def __init__(self): (GeneralizedAmplitudeDampingNoise, {"gamma": 0.1, "probability": 1.2}), ], ) -@pytest.mark.xfail(raises=ValueError) def test_invalid_values(noise_class, params): - noise_class(**params, qubit_count=1, ascii_symbols=["foo"]) + with pytest.raises(ValueError): + noise_class(**params, qubit_count=1, ascii_symbols=["foo"]) @pytest.mark.parametrize( diff --git a/test/unit_tests/braket/circuits/test_noise_helpers.py b/test/unit_tests/braket/circuits/test_noise_helpers.py index 6d9d2a81..421cf69c 100644 --- a/test/unit_tests/braket/circuits/test_noise_helpers.py +++ b/test/unit_tests/braket/circuits/test_noise_helpers.py @@ -73,137 +73,137 @@ def noise_2qubit(): return Noise.Kraus(matrices=[E0, E1]) -@pytest.mark.xfail(raises=IndexError) def test_apply_gate_noise_to_empty_circuit(noise_1qubit): - Circuit().apply_gate_noise(noise_1qubit) + with pytest.raises(IndexError): + Circuit().apply_gate_noise(noise_1qubit) -@pytest.mark.xfail(raises=IndexError) def test_apply_initialization_noise_to_empty_circuit(noise_1qubit): - Circuit().apply_initialization_noise(noise_1qubit) + with pytest.raises(IndexError): + Circuit().apply_initialization_noise(noise_1qubit) -@pytest.mark.xfail(raises=IndexError) def test_apply_readout_noise_to_empty_circuit(noise_1qubit): - Circuit().apply_readout_noise(noise_1qubit) + with pytest.raises(IndexError): + Circuit().apply_readout_noise(noise_1qubit) -@pytest.mark.xfail(raises=ValueError) def test_apply_gate_noise_with_target_gates_and_unitary(circuit_2qubit, noise_1qubit): - circuit_2qubit.apply_gate_noise( - noise_1qubit, target_gates=Gate.X, target_unitary=np.array([[0, 1], [1, 0]]) - ) + with pytest.raises(ValueError): + circuit_2qubit.apply_gate_noise( + noise_1qubit, target_gates=Gate.X, target_unitary=np.array([[0, 1], [1, 0]]) + ) -@pytest.mark.xfail(raises=IndexError) def test_apply_gate_noise_to_outside_qubit_range(circuit_2qubit, noise_1qubit): - circuit_2qubit.apply_gate_noise(noise_1qubit, target_qubits=[0, 1, 2]) + with pytest.raises(IndexError): + circuit_2qubit.apply_gate_noise(noise_1qubit, target_qubits=[0, 1, 2]) -@pytest.mark.xfail(raises=TypeError) @pytest.mark.parametrize("noise", invalid_data_noise_type) def test_apply_gate_noise_invalid_noise_type(circuit_2qubit, noise): - circuit_2qubit.apply_gate_noise(noise) + with pytest.raises(TypeError): + circuit_2qubit.apply_gate_noise(noise) -@pytest.mark.xfail(raises=TypeError) @pytest.mark.parametrize("noise", invalid_data_noise_type) def test_apply_initialization_noise_invalid_noise_type(circuit_2qubit, noise): - circuit_2qubit.apply_initialization_noise(noise) + with pytest.raises(TypeError): + circuit_2qubit.apply_initialization_noise(noise) -@pytest.mark.xfail(raises=TypeError) @pytest.mark.parametrize("noise", invalid_data_noise_type) def test_apply_readout_noise_invalid_noise_type(circuit_2qubit, noise): - circuit_2qubit.apply_readout_noise(noise) + with pytest.raises(TypeError): + circuit_2qubit.apply_readout_noise(noise) -@pytest.mark.xfail(raises=TypeError) @pytest.mark.parametrize("target_gates", invalid_data_target_gates_type) def test_apply_gate_noise_invalid_target_gates_type(circuit_2qubit, noise_1qubit, target_gates): - circuit_2qubit.apply_gate_noise(noise_1qubit, target_gates=target_gates) + with pytest.raises(TypeError): + circuit_2qubit.apply_gate_noise(noise_1qubit, target_gates=target_gates) -@pytest.mark.xfail(raises=TypeError) @pytest.mark.parametrize("target_unitary", invalid_data_target_unitary_type) def test_apply_gate_noise_invalid_target_unitary_type(circuit_2qubit, noise_1qubit, target_unitary): - circuit_2qubit.apply_gate_noise(noise_1qubit, target_unitary=target_unitary) + with pytest.raises(TypeError): + circuit_2qubit.apply_gate_noise(noise_1qubit, target_unitary=target_unitary) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("target_unitary", invalid_data_target_unitary_value) def test_apply_gate_noise_invalid_target_unitary_value( circuit_2qubit, noise_1qubit, target_unitary ): - circuit_2qubit.apply_gate_noise(noise_1qubit, target_unitary=target_unitary) + with pytest.raises(ValueError): + circuit_2qubit.apply_gate_noise(noise_1qubit, target_unitary=target_unitary) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_value) def test_apply_gate_noise_invalid_target_qubits_value(circuit_2qubit, noise_1qubit, target_qubits): - circuit_2qubit.apply_gate_noise(noise_1qubit, target_qubits=target_qubits, target_gates=[]) + with pytest.raises(ValueError): + circuit_2qubit.apply_gate_noise(noise_1qubit, target_qubits=target_qubits, target_gates=[]) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_value) def test_apply_initialization_noise_invalid_target_qubits_value( circuit_2qubit, noise_1qubit, target_qubits ): - circuit_2qubit.apply_initialization_noise(noise_1qubit, target_qubits=target_qubits) + with pytest.raises(ValueError): + circuit_2qubit.apply_initialization_noise(noise_1qubit, target_qubits=target_qubits) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_value) def test_apply_readout_noise_invalid_target_qubits_value( circuit_2qubit, noise_1qubit, target_qubits ): - circuit_2qubit.apply_readout_noise(noise_1qubit, target_qubits=target_qubits) + with pytest.raises(ValueError): + circuit_2qubit.apply_readout_noise(noise_1qubit, target_qubits=target_qubits) -@pytest.mark.xfail(raises=TypeError) @pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_type) def test_apply_gate_noise_invalid_target_qubits_type(circuit_2qubit, noise_1qubit, target_qubits): - circuit_2qubit.apply_gate_noise(noise_1qubit, target_qubits=target_qubits) + with pytest.raises(TypeError): + circuit_2qubit.apply_gate_noise(noise_1qubit, target_qubits=target_qubits) -@pytest.mark.xfail(raises=TypeError) @pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_type) def test_apply_initialization_noise_invalid_target_qubits_type( circuit_2qubit, noise_1qubit, target_qubits ): - circuit_2qubit.apply_initialization_noise(noise_1qubit, target_qubits=target_qubits) + with pytest.raises(TypeError): + circuit_2qubit.apply_initialization_noise(noise_1qubit, target_qubits=target_qubits) -@pytest.mark.xfail(raises=TypeError) @pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_type) def test_apply_readout_noise_invalid_target_qubits_type( circuit_2qubit, noise_1qubit, target_qubits ): - circuit_2qubit.apply_readout_noise(noise_1qubit, target_qubits=target_qubits) + with pytest.raises(TypeError): + circuit_2qubit.apply_readout_noise(noise_1qubit, target_qubits=target_qubits) -@pytest.mark.xfail(raises=ValueError) def test_apply_gate_noise_fixed_qubit_count_not_implemented(noise_2qubit): circ = Circuit().unitary([0, 1], matrix=np.eye(4)) - circ.apply_gate_noise(noise_2qubit, target_gates=Gate.Unitary) + with pytest.raises(ValueError): + circ.apply_gate_noise(noise_2qubit, target_gates=Gate.Unitary) -@pytest.mark.xfail(raises=ValueError) def test_apply_gate_noise_mismatch_qubit_count_with_target_gates(noise_2qubit): circ = Circuit().cswap(0, 1, 2) - circ.apply_gate_noise(noise_2qubit, target_gates=Gate.CSwap) + with pytest.raises(ValueError): + circ.apply_gate_noise(noise_2qubit, target_gates=Gate.CSwap) -@pytest.mark.xfail(raises=ValueError) def test_apply_initialization_noise_mismatch_qubit_count_with_target_qubits(noise_2qubit): circ = Circuit().cswap(0, 1, 2) - circ.apply_initialization_noise(noise_2qubit, target_qubits=[0, 1, 2]) + with pytest.raises(ValueError): + circ.apply_initialization_noise(noise_2qubit, target_qubits=[0, 1, 2]) -@pytest.mark.xfail(raises=ValueError) def test_apply_readout_noise_mismatch_qubit_count_with_target_qubits(noise_2qubit): circ = Circuit().cswap(0, 1, 2) - circ.apply_readout_noise(noise_2qubit, target_qubits=[0, 1, 2]) + with pytest.raises(ValueError): + circ.apply_readout_noise(noise_2qubit, target_qubits=[0, 1, 2]) def test_apply_gate_noise_1QubitNoise_1(circuit_2qubit, noise_1qubit): diff --git a/test/unit_tests/braket/circuits/test_noises.py b/test/unit_tests/braket/circuits/test_noises.py index 758aafdf..2b55dfa4 100644 --- a/test/unit_tests/braket/circuits/test_noises.py +++ b/test/unit_tests/braket/circuits/test_noises.py @@ -498,20 +498,20 @@ def test_parameterized_noise(): # Additional Unitary noise tests -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("matrices", invalid_kraus_matrices) def test_kraus_invalid_matrix(matrices): - Noise.Kraus(matrices=matrices) + with pytest.raises(ValueError): + Noise.Kraus(matrices=matrices) -@pytest.mark.xfail(raises=ValueError) def test_kraus_matrix_target_size_mismatch(): - Circuit().kraus( - matrices=[np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])], targets=[0] - ) + with pytest.raises(ValueError): + Circuit().kraus( + matrices=[np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])], + targets=[0], + ) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize( "probs", [ @@ -524,7 +524,8 @@ def test_kraus_matrix_target_size_mismatch(): ], ) def test_invalid_values_pauli_channel_two_qubit(probs): - Noise.TwoQubitPauliChannel(probs) + with pytest.raises(ValueError): + Noise.TwoQubitPauliChannel(probs) @pytest.mark.parametrize( diff --git a/test/unit_tests/braket/circuits/test_observable.py b/test/unit_tests/braket/circuits/test_observable.py index 150e2d8f..b7e9c201 100644 --- a/test/unit_tests/braket/circuits/test_observable.py +++ b/test/unit_tests/braket/circuits/test_observable.py @@ -42,19 +42,19 @@ def test_is_operator(observable): assert isinstance(observable, QuantumOperator) -@pytest.mark.xfail(raises=ValueError) def test_qubit_count_lt_one(): - Observable(qubit_count=0, ascii_symbols=[]) + with pytest.raises(ValueError): + Observable(qubit_count=0, ascii_symbols=[]) -@pytest.mark.xfail(raises=ValueError) def test_none_ascii(): - Observable(qubit_count=1, ascii_symbols=None) + with pytest.raises(ValueError): + Observable(qubit_count=1, ascii_symbols=None) -@pytest.mark.xfail(raises=ValueError) def test_mismatch_length_ascii(): - Observable(qubit_count=1, ascii_symbols=["foo", "bar"]) + with pytest.raises(ValueError): + Observable(qubit_count=1, ascii_symbols=["foo", "bar"]) def test_name(observable): @@ -71,19 +71,19 @@ def test_getters(): assert observable.ascii_symbols == ascii_symbols -@pytest.mark.xfail(raises=AttributeError) def test_qubit_count_setter(observable): - observable.qubit_count = 10 + with pytest.raises(AttributeError): + observable.qubit_count = 10 -@pytest.mark.xfail(raises=AttributeError) def test_ascii_symbols_setter(observable): - observable.ascii_symbols = ["foo", "bar"] + with pytest.raises(AttributeError): + observable.ascii_symbols = ["foo", "bar"] -@pytest.mark.xfail(raises=AttributeError) def test_name_setter(observable): - observable.name = "hi" + with pytest.raises(AttributeError): + observable.name = "hi" @pytest.mark.parametrize( @@ -109,24 +109,24 @@ def test_observable_to_ir( assert exc.value.args[0] == expected_message -@pytest.mark.xfail(raises=NotImplementedError) def test_to_matrix_not_implemented_by_default(observable): - observable.to_matrix(None) + with pytest.raises(NotImplementedError): + observable.to_matrix(None) -@pytest.mark.xfail(raises=NotImplementedError) def test_basis_rotation_gates_not_implemented_by_default(observable): - observable.basis_rotation_gates + with pytest.raises(NotImplementedError): + observable.basis_rotation_gates -@pytest.mark.xfail(raises=NotImplementedError) def test_eigenvalues_not_implemented_by_default(observable): - observable.eigenvalues + with pytest.raises(NotImplementedError): + observable.eigenvalues -@pytest.mark.xfail(raises=NotImplementedError) def test_eigenvalue_not_implemented_by_default(observable): - observable.eigenvalue(0) + with pytest.raises(NotImplementedError): + observable.eigenvalue(0) def test_str(observable): @@ -154,9 +154,9 @@ def test_matmul_observable(): assert o3.ascii_symbols == ("I@Z", "I@Z") -@pytest.mark.xfail(raises=ValueError) def test_matmul_non_observable(): - Observable.I() @ "a" + with pytest.raises(ValueError): + Observable.I() @ "a" def test_observable_equality(): diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index 22c0ab9f..79f917af 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -423,10 +423,10 @@ def test_observable_from_ir(testobject, gateobject, expected_ir, basis_rotation_ # Hermitian -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("matrix", invalid_hermitian_matrices) def test_hermitian_invalid_matrix(matrix): - Observable.Hermitian(matrix=matrix) + with pytest.raises(ValueError): + Observable.Hermitian(matrix=matrix) def test_hermitian_equality(): @@ -501,10 +501,10 @@ def test_hermitian_basis_rotation_gates(matrix, basis_rotation_matrix): assert expected_unitary.matrix_equivalence(actual_rotation_gates[0]) -@pytest.mark.xfail(raises=ValueError) def test_observable_from_ir_hermitian_value_error(): ir_observable = [[[[1.0, 0], [0, 1]], [[0.0, 1], [1, 0]]]] - observable_from_ir(ir_observable) + with pytest.raises(ValueError): + observable_from_ir(ir_observable) def test_observable_from_ir_hermitian(): @@ -550,15 +550,15 @@ def test_tensor_product_matmul_observable(): assert t.ascii_symbols == tuple(["Z@I@X@I"] * 4) -@pytest.mark.xfail(raises=ValueError) def test_tensor_product_eigenvalue_index_out_of_bounds(): obs = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) - obs.eigenvalue(8) + with pytest.raises(ValueError): + obs.eigenvalue(8) -@pytest.mark.xfail(raises=ValueError) def test_tensor_product_value_error(): - Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) @ "a" + with pytest.raises(ValueError): + Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) @ "a" def test_tensor_product_rmatmul_observable(): @@ -618,9 +618,9 @@ def test_observable_from_ir_tensor_product(): assert expected_observable == actual_observable -@pytest.mark.xfail(raises=ValueError) def test_observable_from_ir_tensor_product_value_error(): - observable_from_ir(["z", "i", "foo"]) + with pytest.raises(ValueError): + observable_from_ir(["z", "i", "foo"]) def compare_eigenvalues(observable, expected): diff --git a/test/unit_tests/braket/circuits/test_quantum_operator.py b/test/unit_tests/braket/circuits/test_quantum_operator.py index 11c7441b..ed58d5d6 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator.py @@ -46,29 +46,29 @@ def test_fixed_qubit_count_implemented(): assert operator.qubit_count == _DummyQuantumOperator.fixed_qubit_count() -@pytest.mark.xfail(raises=ValueError) def test_qubit_count_fixed_qubit_count_unequal(): - _DummyQuantumOperator(qubit_count=1, ascii_symbols=["foo", "bar"]) + with pytest.raises(ValueError): + _DummyQuantumOperator(qubit_count=1, ascii_symbols=["foo", "bar"]) -@pytest.mark.xfail(raises=TypeError) def test_qubit_count_not_int(): - QuantumOperator(qubit_count="hello", ascii_symbols=[]) + with pytest.raises(TypeError): + QuantumOperator(qubit_count="hello", ascii_symbols=[]) -@pytest.mark.xfail(raises=ValueError) def test_qubit_count_lt_one(): - QuantumOperator(qubit_count=0, ascii_symbols=[]) + with pytest.raises(ValueError): + QuantumOperator(qubit_count=0, ascii_symbols=[]) -@pytest.mark.xfail(raises=ValueError) def test_none_ascii(): - QuantumOperator(qubit_count=1, ascii_symbols=None) + with pytest.raises(ValueError): + QuantumOperator(qubit_count=1, ascii_symbols=None) -@pytest.mark.xfail(raises=ValueError) def test_mismatch_length_ascii(): - QuantumOperator(qubit_count=1, ascii_symbols=["foo", "bar"]) + with pytest.raises(ValueError): + QuantumOperator(qubit_count=1, ascii_symbols=["foo", "bar"]) def test_name(quantum_operator): @@ -85,29 +85,29 @@ def test_getters(): assert quantum_operator.ascii_symbols == ascii_symbols -@pytest.mark.xfail(raises=AttributeError) def test_qubit_count_setter(quantum_operator): - quantum_operator.qubit_count = 10 + with pytest.raises(AttributeError): + quantum_operator.qubit_count = 10 -@pytest.mark.xfail(raises=AttributeError) def test_ascii_symbols_setter(quantum_operator): - quantum_operator.ascii_symbols = ["foo", "bar"] + with pytest.raises(AttributeError): + quantum_operator.ascii_symbols = ["foo", "bar"] -@pytest.mark.xfail(raises=AttributeError) def test_name_setter(quantum_operator): - quantum_operator.name = "hi" + with pytest.raises(AttributeError): + quantum_operator.name = "hi" -@pytest.mark.xfail(raises=NotImplementedError) def test_to_ir_not_implemented_by_default(quantum_operator): - quantum_operator.to_ir(None) + with pytest.raises(NotImplementedError): + quantum_operator.to_ir(None) -@pytest.mark.xfail(raises=NotImplementedError) def test_to_matrix_not_implemented_by_default(quantum_operator): - quantum_operator.to_matrix(None) + with pytest.raises(NotImplementedError): + quantum_operator.to_matrix(None) def test_matrix_equivalence(): diff --git a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py index c76f09a2..c0f8b2e2 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py @@ -72,10 +72,10 @@ def test_is_square_matrix(): assert is_square_matrix(valid_unitary_hermitian_matrix) -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("matrix", invalid_dimension_matrices) def test_verify_quantum_operator_matrix_dimensions_value_error(matrix): - verify_quantum_operator_matrix_dimensions(matrix) + with pytest.raises(ValueError): + verify_quantum_operator_matrix_dimensions(matrix) @pytest.mark.parametrize("matrix", invalid_unitary_matrices_false) @@ -92,19 +92,19 @@ def test_is_cptp_false(): assert not is_cptp(invalid_CPTP_matrices_false) -@pytest.mark.xfail(raises=Exception) def test_is_hermitian_exception(): - is_hermitian(invalid_matrix_type_error) + with pytest.raises(Exception): + is_hermitian(invalid_matrix_type_error) -@pytest.mark.xfail(raises=Exception) def test_is_unitary_exception(): - is_unitary(invalid_matrix_type_error) + with pytest.raises(Exception): + is_unitary(invalid_matrix_type_error) -@pytest.mark.xfail(raises=Exception) def test_is_cptp_exception(): - is_cptp([invalid_matrix_type_error]) + with pytest.raises(Exception): + is_cptp([invalid_matrix_type_error]) def test_get_pauli_eigenvalues_correct_eigenvalues_one_qubit(): @@ -133,7 +133,7 @@ def test_get_pauli_eigenvalues_cache_usage(depth): assert functools._CacheInfo(depth - 1, depth, 128, depth) == get_pauli_eigenvalues.cache_info() -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize("num_qubits", [1, 2]) def test_get_pauli_eigenvalues_immutable(num_qubits): - get_pauli_eigenvalues(num_qubits)[0] = 100 + with pytest.raises(ValueError): + get_pauli_eigenvalues(num_qubits)[0] = 100 diff --git a/test/unit_tests/braket/circuits/test_qubit.py b/test/unit_tests/braket/circuits/test_qubit.py index 7f72722e..b961986c 100644 --- a/test/unit_tests/braket/circuits/test_qubit.py +++ b/test/unit_tests/braket/circuits/test_qubit.py @@ -22,15 +22,15 @@ def qubit(): return Qubit(5) -@pytest.mark.xfail(raises=ValueError) def test_index_lt_zero(): - Qubit(-1) + with pytest.raises(ValueError): + Qubit(-1) @pytest.mark.parametrize("qubit_arg", ("not a number", 0.5)) -@pytest.mark.xfail(raises=TypeError) def test_index_non_int(qubit_arg): - Qubit(qubit_arg) + with pytest.raises(TypeError): + Qubit(qubit_arg) @pytest.mark.parametrize("qubit_index", (0, 5, np.int64(5))) diff --git a/test/unit_tests/braket/circuits/test_result_type.py b/test/unit_tests/braket/circuits/test_result_type.py index 2f734a7c..bc7cc090 100644 --- a/test/unit_tests/braket/circuits/test_result_type.py +++ b/test/unit_tests/braket/circuits/test_result_type.py @@ -35,9 +35,9 @@ def sv(): return ResultType.StateVector() -@pytest.mark.xfail(raises=ValueError) def test_none_ascii(): - ResultType(ascii_symbols=None) + with pytest.raises(ValueError): + ResultType(ascii_symbols=None) def test_name(result_type): @@ -71,14 +71,14 @@ def test_equality_densitymatrix(): assert result1 != result4 -@pytest.mark.xfail(raises=AttributeError) def test_ascii_symbol_setter(result_type): - result_type.ascii_symbols = ["bar"] + with pytest.raises(AttributeError): + result_type.ascii_symbols = ["bar"] -@pytest.mark.xfail(raises=AttributeError) def test_name_setter(result_type): - result_type.name = "hi" + with pytest.raises(AttributeError): + result_type.name = "hi" def test_register_result(): @@ -120,31 +120,31 @@ def test_copy_with_target(sv): assert sv.copy(target=target) == expected -@pytest.mark.xfail(raises=TypeError) def test_copy_with_target_and_mapping(prob): - prob.copy(target=[10], target_mapping={0: 10}) + with pytest.raises(TypeError): + prob.copy(target=[10], target_mapping={0: 10}) # ObservableResultType -@pytest.mark.xfail(raises=ValueError) def test_expectation_init_value_error_target(): - ObservableResultType( - ascii_symbols=["Obs", "Obs"], observable=Observable.X() @ Observable.Y(), target=[] - ) + with pytest.raises(ValueError): + ObservableResultType( + ascii_symbols=["Obs", "Obs"], observable=Observable.X() @ Observable.Y(), target=[] + ) -@pytest.mark.xfail(raises=ValueError) def test_expectation_init_value_error_ascii_symbols(): - ObservableResultType( - ascii_symbols=["Obs"], observable=Observable.X() @ Observable.Y(), target=[1, 2] - ) + with pytest.raises(ValueError): + ObservableResultType( + ascii_symbols=["Obs"], observable=Observable.X() @ Observable.Y(), target=[1, 2] + ) -@pytest.mark.xfail(raises=ValueError) def test_obs_rt_init_value_error_qubit_count(): - ObservableResultType(ascii_symbols=["Obs"], observable=Observable.X(), target=[0, 1]) + with pytest.raises(ValueError): + ObservableResultType(ascii_symbols=["Obs"], observable=Observable.X(), target=[0, 1]) def test_obs_rt_equality(): diff --git a/test/unit_tests/braket/circuits/test_result_types.py b/test/unit_tests/braket/circuits/test_result_types.py index 501d1f8b..5f76eeaf 100644 --- a/test/unit_tests/braket/circuits/test_result_types.py +++ b/test/unit_tests/braket/circuits/test_result_types.py @@ -298,13 +298,13 @@ def test_result_equality(testclass, subroutine_name, irclass, input, ir_input): # Amplitude -@pytest.mark.xfail(raises=ValueError) @pytest.mark.parametrize( "state", ((["2", "11"]), ([1, 0]), ([0.1, 0]), ("-0", "1"), (["", ""]), (None), ([None, None]), ("10")), ) def test_amplitude_init_invalid_state_value_error(state): - ResultType.Amplitude(state=state) + with pytest.raises(ValueError): + ResultType.Amplitude(state=state) def test_amplitude_equality(): From c38ef9e27126d83c9bdf9f74809fb52550d241b8 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Tue, 25 Jul 2023 16:30:56 -0700 Subject: [PATCH 0776/1165] feat: point image uri to latest tag (#644) --- src/braket/jobs/image_uri_config/base.json | 19 ++++++++----------- .../jobs/image_uri_config/pl_pytorch.json | 19 ++++++++----------- .../jobs/image_uri_config/pl_tensorflow.json | 19 ++++++++----------- src/braket/jobs/image_uris.py | 19 +++++-------------- .../unit_tests/braket/jobs/test_image_uris.py | 9 +++------ 5 files changed, 32 insertions(+), 53 deletions(-) diff --git a/src/braket/jobs/image_uri_config/base.json b/src/braket/jobs/image_uri_config/base.json index d3bc8064..eb71e60f 100644 --- a/src/braket/jobs/image_uri_config/base.json +++ b/src/braket/jobs/image_uri_config/base.json @@ -1,13 +1,10 @@ { - "versions": { - "1.0": { - "registries": { - "us-east-1": "292282985366", - "us-west-1": "292282985366", - "us-west-2": "292282985366", - "eu-west-2": "292282985366" - }, - "repository": "amazon-braket-base-jobs" - } - } + "registry": "292282985366", + "repository": "amazon-braket-base-jobs", + "supported_regions": [ + "us-east-1", + "us-west-1", + "us-west-2", + "eu-west-2" + ] } diff --git a/src/braket/jobs/image_uri_config/pl_pytorch.json b/src/braket/jobs/image_uri_config/pl_pytorch.json index 2391dc25..c7e28fbd 100644 --- a/src/braket/jobs/image_uri_config/pl_pytorch.json +++ b/src/braket/jobs/image_uri_config/pl_pytorch.json @@ -1,13 +1,10 @@ { - "versions": { - "2.0.0": { - "registries": { - "us-east-1": "292282985366", - "us-west-1": "292282985366", - "us-west-2": "292282985366", - "eu-west-2": "292282985366" - }, - "repository": "amazon-braket-pytorch-jobs" - } - } + "registry": "292282985366", + "repository": "amazon-braket-pytorch-jobs", + "supported_regions": [ + "us-east-1", + "us-west-1", + "us-west-2", + "eu-west-2" + ] } diff --git a/src/braket/jobs/image_uri_config/pl_tensorflow.json b/src/braket/jobs/image_uri_config/pl_tensorflow.json index 84436d66..3278a871 100644 --- a/src/braket/jobs/image_uri_config/pl_tensorflow.json +++ b/src/braket/jobs/image_uri_config/pl_tensorflow.json @@ -1,13 +1,10 @@ { - "versions": { - "2.12.0": { - "registries": { - "us-east-1": "292282985366", - "us-west-1": "292282985366", - "us-west-2": "292282985366", - "eu-west-2": "292282985366" - }, - "repository": "amazon-braket-tensorflow-jobs" - } - } + "registry": "292282985366", + "repository": "amazon-braket-tensorflow-jobs", + "supported_regions": [ + "us-east-1", + "us-west-1", + "us-west-2", + "eu-west-2" + ] } diff --git a/src/braket/jobs/image_uris.py b/src/braket/jobs/image_uris.py index 4e58e8dd..0a29f2ce 100644 --- a/src/braket/jobs/image_uris.py +++ b/src/braket/jobs/image_uris.py @@ -42,16 +42,8 @@ def retrieve_image(framework: Framework, region: str) -> str: # Validate framework framework = Framework(framework) config = _config_for_framework(framework) - framework_version = max(version for version in config["versions"]) - version_config = config["versions"][framework_version] - registry = _registry_for_region(version_config, region) - tag = "" - if framework == Framework.PL_TENSORFLOW: - tag = f"{version_config['repository']}:{framework_version}-gpu-py310-cu118-ubuntu20.04" - elif framework == Framework.PL_PYTORCH: - tag = f"{version_config['repository']}:{framework_version}-gpu-py310-cu118-ubuntu20.04" - else: - tag = f"{version_config['repository']}:{framework_version}-cpu-py310-ubuntu22.04" + registry = _registry_for_region(config, region) + tag = f"{config['repository']}:latest" return f"{registry}.dkr.ecr.{region}.amazonaws.com/{tag}" @@ -82,10 +74,9 @@ def _registry_for_region(config: Dict[str, str], region: str) -> str: Raises: ValueError: If the supplied region is invalid or not supported. """ - registry_config = config["registries"] - if region not in registry_config: + if region not in (supported_regions := config["supported_regions"]): raise ValueError( f"Unsupported region: {region}. You may need to upgrade your SDK version for newer " - f"regions. Supported region(s): {list(registry_config.keys())}" + f"regions. Supported region(s): {supported_regions}" ) - return registry_config[region] + return config["registry"] diff --git a/test/unit_tests/braket/jobs/test_image_uris.py b/test/unit_tests/braket/jobs/test_image_uris.py index 7450346c..2e6ae396 100644 --- a/test/unit_tests/braket/jobs/test_image_uris.py +++ b/test/unit_tests/braket/jobs/test_image_uris.py @@ -22,20 +22,17 @@ ( "us-west-1", Framework.BASE, - "292282985366.dkr.ecr.us-west-1.amazonaws.com/" - "amazon-braket-base-jobs:1.0-cpu-py310-ubuntu22.04", + "292282985366.dkr.ecr.us-west-1.amazonaws.com/amazon-braket-base-jobs:latest", ), ( "us-east-1", Framework.PL_TENSORFLOW, - "292282985366.dkr.ecr.us-east-1.amazonaws.com/amazon-braket-tensorflow-jobs:" - "2.12.0-gpu-py310-cu118-ubuntu20.04", + "292282985366.dkr.ecr.us-east-1.amazonaws.com/amazon-braket-tensorflow-jobs:latest", ), ( "us-west-2", Framework.PL_PYTORCH, - "292282985366.dkr.ecr.us-west-2.amazonaws.com/" - "amazon-braket-pytorch-jobs:2.0.0-gpu-py310-cu118-ubuntu20.04", + "292282985366.dkr.ecr.us-west-2.amazonaws.com/amazon-braket-pytorch-jobs:latest", ), ], ) From ce61ed9f29fb1e5ede1c76ad879994e336549aee Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Tue, 25 Jul 2023 16:51:38 -0700 Subject: [PATCH 0777/1165] fix: update doc string for handle_parameter_value (#646) --- src/braket/circuits/braket_program_context.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index afd6ce78..44ce4bb1 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.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, List, Optional, Tuple +from typing import List, Optional, Tuple, Union import numpy as np from sympy import Expr @@ -134,7 +134,18 @@ def add_result(self, result: Results) -> None: """ self._circuit.add_result_type(braket_result_to_result_type(result)) - def handle_parameter_value(self, value: Any) -> Any: + def handle_parameter_value( + self, value: Union[float, Expr] + ) -> Union[float, FreeParameterExpression]: + """Convert parameter value to required format. + + Args: + value (Union[float, Expr]): Value of the parameter + + Returns: + Union[float, FreeParameterExpression]: Return the value directly if numeric, + otherwise wraps the symbolic expression as a `FreeParameterExpression`. + """ if isinstance(value, Expr): return FreeParameterExpression(str(value)) return value From c668be8facb836eebe0b410eda7d963886cff428 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 26 Jul 2023 12:13:54 -0700 Subject: [PATCH 0778/1165] fix: move import back to top level in job example (#648) This reverts commit 1fed55d88333d4acd2302734ee88f707ce418a12. --- examples/job.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/job.py b/examples/job.py index 6b153d12..fdb2cec6 100644 --- a/examples/job.py +++ b/examples/job.py @@ -15,6 +15,7 @@ from braket.aws import AwsDevice, AwsQuantumJob from braket.circuits import Circuit +from braket.devices import Devices from braket.jobs import save_job_result @@ -35,8 +36,6 @@ def run_job(): if __name__ == "__main__": - from braket.devices import Devices - job = AwsQuantumJob.create( device=Devices.Amazon.SV1, source_module="job.py", From e29065224fc0a1df48b3141223cf8f66cace142e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jul 2023 18:30:34 -0700 Subject: [PATCH 0779/1165] build(deps): bump pypa/gh-action-pypi-publish from 1.8.7 to 1.8.8 (#620) build(deps): bump pypa/gh-action-pypi-publish from 1.8.7 to 1.8.8 ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Abe Coull <85974725+math411@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 313533e1..b061d1eb 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -24,6 +24,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@f5622bde02b04381239da3573277701ceca8f6a0 # release/v1 + uses: pypa/gh-action-pypi-publish@f8c70e705ffc13c3b4d1221169b84f12a75d6ca8 # release/v1 with: password: ${{ secrets.pypi_token }} From 3ea9184ac91d99b64804b8433823c13bf4752a1a Mon Sep 17 00:00:00 2001 From: Kshitij Chhabra Date: Fri, 28 Jul 2023 10:08:28 -0700 Subject: [PATCH 0780/1165] fix: Update quantum job tests for latest containers (#649) --- test/integ_tests/test_create_quantum_job.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py index 74303279..76c49cd4 100644 --- a/test/integ_tests/test_create_quantum_job.py +++ b/test/integ_tests/test_create_quantum_job.py @@ -59,7 +59,7 @@ def test_failed_quantum_job(aws_session, capsys): assert errors == "" logs_to_validate = [ "Invoking script with the following command:", - "/usr/local/bin/python3.8 braket_container.py", + "braket_container.py", "Running Code As Process", "Test job started!!!!!", "AssertionError", @@ -167,7 +167,7 @@ def test_completed_quantum_job(aws_session, capsys): assert errors == "" logs_to_validate = [ "Invoking script with the following command:", - "/usr/local/bin/python3.8 braket_container.py", + "braket_container.py", "Running Code As Process", "Test job started!!!!!", "Test job completed!!!!!", From 2e9e68d90ba8734008b4ee764b8c448287fd7344 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Fri, 28 Jul 2023 16:37:13 -0400 Subject: [PATCH 0781/1165] fix: autoqasm programs with assignments inside conditionals and branches (#650) --- src/braket/experimental/autoqasm/api.py | 6 +- src/braket/experimental/autoqasm/errors.py | 20 +++ .../autoqasm/operators/assignments.py | 66 ++++------ .../operators/conditional_expressions.py | 29 +++-- .../autoqasm/operators/control_flow.py | 2 - .../experimental/autoqasm/operators/slices.py | 10 +- .../experimental/autoqasm/program/program.py | 15 +++ .../experimental/autoqasm/types/__init__.py | 2 +- .../autoqasm/types/conversions.py | 38 ++++++ .../braket/experimental/autoqasm/test_api.py | 36 +++--- .../experimental/autoqasm/test_converters.py | 8 +- .../experimental/autoqasm/test_operators.py | 118 ++++++++++++++++-- .../experimental/autoqasm/test_types.py | 59 ++++++--- 13 files changed, 299 insertions(+), 110 deletions(-) diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index fb2ec711..722c6b7d 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -425,9 +425,11 @@ def _wrap_for_oqpy_subroutine(f: Callable, options: converter.ConversionOptions) @functools.wraps(f) def _func(*args, **kwargs) -> Any: - inner_program = args[0] + inner_program: oqpy.Program = args[0] with aq_program.get_program_conversion_context().push_oqpy_program(inner_program): - return aq_transpiler.converted_call(f, args[1:], kwargs, options=options) + result = aq_transpiler.converted_call(f, args[1:], kwargs, options=options) + inner_program.autodeclare() + return result # Replace the function signature with a new signature where the first # argument is of type `oqpy.Program`. diff --git a/src/braket/experimental/autoqasm/errors.py b/src/braket/experimental/autoqasm/errors.py index 1357a878..7bf95974 100644 --- a/src/braket/experimental/autoqasm/errors.py +++ b/src/braket/experimental/autoqasm/errors.py @@ -14,6 +14,9 @@ """Errors raised in the AutoQASM build process.""" +from typing import Optional + + class AutoQasmError(Exception): """Base class for all AutoQASM exceptions.""" @@ -47,3 +50,20 @@ def __init__(self): def __str__(self): return self.message + + +class UnsupportedConditionalExpressionError(AutoQasmError): + """Conditional expressions which return values are not supported.""" + + def __init__(self, true_type: Optional[type], false_type: Optional[type]): + if_type = true_type.__name__ if true_type else "None" + else_type = false_type.__name__ if false_type else "None" + self.message = """\ +`if` clause resolves to {}, but `else` clause resolves to {}. \ +Both the `if` and `else` clauses of an inline conditional expression \ +must resolve to the same type.""".format( + if_type, else_type + ) + + def __str__(self): + return self.message diff --git a/src/braket/experimental/autoqasm/operators/assignments.py b/src/braket/experimental/autoqasm/operators/assignments.py index 2b1d03d1..d84b3afc 100644 --- a/src/braket/experimental/autoqasm/operators/assignments.py +++ b/src/braket/experimental/autoqasm/operators/assignments.py @@ -43,6 +43,11 @@ def assign_stmt(target_name: str, value: Any) -> Any: if isinstance(value, UndefinedReturnValue): return value + is_target_name_used = program.get_program_conversion_context().is_var_name_used(target_name) + is_value_name_used = isinstance( + value, oqpy.base.Var + ) and program.get_program_conversion_context().is_var_name_used(value.name) + if target_name == constants.RETVAL_VARIABLE_NAME: # AutoGraph transpiles return statements like # return @@ -52,59 +57,32 @@ def assign_stmt(target_name: str, value: Any) -> Any: # The special logic here is to handle this case properly and avoid # declaring a new variable unless it is necessary. - if isinstance(value, oqpy.base.Var) and _is_variable_used(value.name): + if is_value_name_used: # This is a value which already exists as a variable in the program. # Return it directly without wrapping it or declaring a new variable. return value value = types.wrap_value(value) - if isinstance(value, oqpy.base.Var): - oqpy_program = program.get_program_conversion_context().get_oqpy_program() - - is_target_name_used = _is_variable_used(target_name) - is_value_name_used = _is_variable_used(value.name) - - if is_target_name_used: - target = _get_oqpy_program_variable(target_name) - _validate_variables_type_size(target, value) - if is_value_name_used: - oqpy_program.set(target, value) - else: - # Set to `value.init_expression` to avoid declaring an unnecessary variable. - oqpy_program.set(target, value.init_expression) - else: - target = copy.copy(value) - target.init_expression = None - target.name = target_name - - if is_value_name_used: - oqpy_program.declare(target) - oqpy_program.set(target, value) - else: - # Set to `value.init_expression` to avoid declaring an unnecessary variable. - target.init_expression = value.init_expression - oqpy_program.declare(target) - - return target - - return value - - -def _is_variable_used(var_name: str) -> bool: - """Check if the variable already exists in the oqpy program. + if not isinstance(value, oqpy.base.Var): + return value - Args: - var_name (str): variable name + if is_target_name_used: + target = _get_oqpy_program_variable(target_name) + _validate_variables_type_size(target, value) + else: + target = copy.copy(value) + target.init_expression = None + target.name = target_name - Returns: - bool: Return True if the variable already exists - """ oqpy_program = program.get_program_conversion_context().get_oqpy_program() - return ( - var_name in oqpy_program.declared_vars.keys() - or var_name in oqpy_program.undeclared_vars.keys() - ) + if is_value_name_used or value.init_expression is None: + oqpy_program.set(target, value) + else: + # Set to `value.init_expression` to avoid declaring an unnecessary variable. + oqpy_program.set(target, value.init_expression) + + return target def _get_oqpy_program_variable(var_name: str) -> oqpy.base.Var: diff --git a/src/braket/experimental/autoqasm/operators/conditional_expressions.py b/src/braket/experimental/autoqasm/operators/conditional_expressions.py index faf22d96..40b591f7 100644 --- a/src/braket/experimental/autoqasm/operators/conditional_expressions.py +++ b/src/braket/experimental/autoqasm/operators/conditional_expressions.py @@ -18,8 +18,9 @@ import oqpy.base -from braket.experimental.autoqasm import program -from braket.experimental.autoqasm.types import is_qasm_type +from braket.experimental.autoqasm import program as aq_program +from braket.experimental.autoqasm import types as aq_types +from braket.experimental.autoqasm.errors import UnsupportedConditionalExpressionError def if_exp( @@ -36,7 +37,7 @@ def if_exp( Returns: Any: The value returned from the conditional expression. """ - if is_qasm_type(cond): + if aq_types.is_qasm_type(cond): return _oqpy_if_exp(cond, if_true, if_false, expr_repr) else: return _py_if_exp(cond, if_true, if_false) @@ -47,15 +48,25 @@ def _oqpy_if_exp( if_true: Callable[[None], Any], if_false: Callable[[None], Any], expr_repr: Optional[str], -) -> None: +) -> Optional[oqpy.base.Var]: """Overload of if_exp that stages an oqpy conditional.""" - oqpy_program = program.get_program_conversion_context().get_oqpy_program() - if isinstance(cond, oqpy.base.Var) and cond.name not in oqpy_program.declared_vars.keys(): - oqpy_program.declare(cond) + result_var = None + oqpy_program = aq_program.get_program_conversion_context().get_oqpy_program() with oqpy.If(oqpy_program, cond): - if_true() + true_result = aq_types.wrap_value(if_true()) + true_result_type = aq_types.var_type_from_oqpy(true_result) + if true_result is not None: + result_var = true_result_type() + oqpy_program.set(result_var, true_result) with oqpy.Else(oqpy_program): - if_false() + false_result = aq_types.wrap_value(if_false()) + false_result_type = aq_types.var_type_from_oqpy(false_result) + if false_result_type != true_result_type: + raise UnsupportedConditionalExpressionError(true_result_type, false_result_type) + if false_result is not None: + oqpy_program.set(result_var, false_result) + + return result_var def _py_if_exp(cond: Any, if_true: Callable[[None], Any], if_false: Callable[[None], Any]) -> Any: diff --git a/src/braket/experimental/autoqasm/operators/control_flow.py b/src/braket/experimental/autoqasm/operators/control_flow.py index 59ef5d00..6ead0d57 100644 --- a/src/braket/experimental/autoqasm/operators/control_flow.py +++ b/src/braket/experimental/autoqasm/operators/control_flow.py @@ -154,8 +154,6 @@ def _oqpy_if_stmt( ) -> None: """Overload of if_stmt that stages an oqpy cond.""" oqpy_program = program.get_program_conversion_context().get_oqpy_program() - if isinstance(cond, oqpy.base.Var) and cond.name not in oqpy_program.declared_vars.keys(): - oqpy_program.declare(cond) with oqpy.If(oqpy_program, cond): body() with oqpy.Else(oqpy_program): diff --git a/src/braket/experimental/autoqasm/operators/slices.py b/src/braket/experimental/autoqasm/operators/slices.py index 0c015c1f..0ed2ea47 100644 --- a/src/braket/experimental/autoqasm/operators/slices.py +++ b/src/braket/experimental/autoqasm/operators/slices.py @@ -89,12 +89,14 @@ def _oqpy_set_item(target: Any, i: Any, x: Any) -> Any: if not isinstance(target, oqpy.BitVar): raise NotImplementedError("Slice assignment is not supported.") + is_var_name_used = program.get_program_conversion_context().is_var_name_used(x.name) oqpy_program = program.get_program_conversion_context().get_oqpy_program() - if x.name in oqpy_program.declared_vars.keys(): - value = x + if is_var_name_used or x.init_expression is None: + oqpy_program.set(target[i], x) else: - value = x.init_expression - oqpy_program.set(target[i], value) + # Set to `x.init_expression` to avoid declaring an unnecessary variable. + oqpy_program.set(target[i], x.init_expression) + return target diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 7d26453f..9ededf4d 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -144,6 +144,21 @@ def next_var_name(self, kind: type) -> str: raise NotImplementedError(f"Program's do not yet support type {kind}.") + def is_var_name_used(self, var_name: str) -> bool: + """Check if the variable already exists in the oqpy program. + + Args: + var_name (str): variable name + + Returns: + bool: Return True if the variable already exists + """ + oqpy_program = self.get_oqpy_program() + return ( + var_name in oqpy_program.declared_vars.keys() + or var_name in oqpy_program.undeclared_vars.keys() + ) + def get_oqpy_program(self) -> oqpy.Program: """Gets the oqpy program from the top of the stack. diff --git a/src/braket/experimental/autoqasm/types/__init__.py b/src/braket/experimental/autoqasm/types/__init__.py index 86221c3f..5e032ca4 100644 --- a/src/braket/experimental/autoqasm/types/__init__.py +++ b/src/braket/experimental/autoqasm/types/__init__.py @@ -15,7 +15,7 @@ for type handling. """ -from .conversions import map_type, wrap_value # noqa: F401 +from .conversions import map_type, var_type_from_oqpy, wrap_value # noqa: F401 from .types import ( # noqa: F401 ArrayVar, BitVar, diff --git a/src/braket/experimental/autoqasm/types/conversions.py b/src/braket/experimental/autoqasm/types/conversions.py index 7989da1e..c2396495 100644 --- a/src/braket/experimental/autoqasm/types/conversions.py +++ b/src/braket/experimental/autoqasm/types/conversions.py @@ -19,6 +19,7 @@ import numpy as np import oqpy +from openpulse import ast from braket.experimental.autoqasm import types as aq_types @@ -56,6 +57,43 @@ def map_type(python_type: type) -> type: return python_type +def var_type_from_ast_type(ast_type: ast.ClassicalType) -> type: + """Converts an OpenQASM AST type to the corresponding AutoQASM variable type. + + Args: + ast_type (ast.ClassicalType): The OpenQASM AST type to be converted. + + Returns: + type: The corresponding AutoQASM variable type. + """ + if isinstance(ast_type, ast.IntType): + return aq_types.IntVar + if isinstance(ast_type, ast.FloatType): + return aq_types.FloatVar + if isinstance(ast_type, ast.BoolType): + return aq_types.BoolVar + if isinstance(ast_type, ast.BitType): + return aq_types.BitVar + if isinstance(ast_type, ast.ArrayType): + return aq_types.ArrayVar + + raise NotImplementedError + + +def var_type_from_oqpy(expr_or_var: Union[oqpy.base.OQPyExpression, oqpy.base.Var]) -> type: + """Returns the AutoQASM variable type corresponding to the provided OQPy object. + + Args: + expr_or_var (Union[OQPyExpression, Var]): An OQPy expression or variable. + + Returns: + type: The corresponding AutoQASM variable type. + """ + if isinstance(expr_or_var, oqpy.base.OQPyExpression): + return var_type_from_ast_type(expr_or_var.type) + return type(expr_or_var) + + @singledispatch def wrap_value(node: Any) -> Any: """Wraps an object in an autoqasm variable. diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index 26311f1e..129c3842 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -203,13 +203,13 @@ def bell_measurement_undeclared() -> None: def test_bell_measurement_undeclared() -> None: expected = """OPENQASM 3.0; +bit[2] c; qubit[2] __qubits__; h __qubits__[0]; cnot __qubits__[0], __qubits__[1]; bit[2] __bit_0__; __bit_0__[0] = measure __qubits__[0]; __bit_0__[1] = measure __qubits__[1]; -bit[2] c; c = __bit_0__;""" assert bell_measurement_undeclared().to_ir() == expected @@ -225,8 +225,9 @@ def bell_measurement_declared() -> None: def test_bell_measurement_declared() -> None: expected = """OPENQASM 3.0; +bit[2] c; qubit[2] __qubits__; -bit[2] c = {0, 0}; +c = {0, 0}; h __qubits__[0]; cnot __qubits__[0], __qubits__[1]; bit[2] __bit_1__; @@ -246,12 +247,12 @@ def bell_partial_measurement() -> None: def test_bell_partial_measurement() -> None: expected = """OPENQASM 3.0; +bit c; qubit[2] __qubits__; h __qubits__[0]; cnot __qubits__[0], __qubits__[1]; bit __bit_0__; __bit_0__ = measure __qubits__[1]; -bit c; c = __bit_0__;""" assert bell_partial_measurement().to_ir() == expected @@ -401,13 +402,13 @@ def qasm_inline_var_condition() -> aq.BitVar: def test_qasm_inline_var_condition() -> None: """Tests the QASM contents of qasm_inline_var_condition.""" expected = """OPENQASM 3.0; +bit __bit_0__ = 1; +int[32] __int_1__ = 1; qubit[2] __qubits__; h __qubits__[0]; -bit __bit_0__ = 1; if (__bit_0__) { cnot __qubits__[0], __qubits__[1]; } -int[32] __int_1__ = 1; if (__int_1__) { x __qubits__[0]; } else { @@ -734,15 +735,21 @@ def prog() -> None: c = aq.FloatVar(3.4) # noqa: F841 expected = """OPENQASM 3.0; -bit a = 0; -a = 1; -int[32] i = 1; +bit a; +int[32] i; bit[2] a_array; +bit[2] __bit_3__; +int[32] b; +float[64] c; +a = 0; +a = 1; +i = 1; +a_array = __bit_3__; a_array[0] = 0; a_array[i] = 1; -int[32] b = 10; +b = 10; b = 15; -float[64] c = 1.2; +c = 1.2; c = 3.4;""" assert prog().to_ir() == expected @@ -756,9 +763,10 @@ def prog() -> None: a = b # declared target, declared value # noqa: F841 expected = """OPENQASM 3.0; -int[32] a = 1; -a = 2; +int[32] a; int[32] b; +a = 1; +a = 2; b = a; a = b;""" assert prog().to_ir() == expected @@ -771,12 +779,12 @@ def prog() -> None: b = a # noqa: F841 expected = """OPENQASM 3.0; +bit a; +bit b; qubit[1] __qubits__; bit __bit_0__; __bit_0__ = measure __qubits__[0]; -bit a; a = __bit_0__; -bit b; b = a;""" assert prog().to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_converters.py b/test/unit_tests/braket/experimental/autoqasm/test_converters.py index 333dc230..73ee3403 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_converters.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_converters.py @@ -51,9 +51,11 @@ def fn() -> None: qasm = program_conversion_context.make_program().to_ir() expected_qasm = """OPENQASM 3.0; -int[32] a = 5; -float[64] b = 1.2; -a = 1; +int[32] a; +float[64] b; int[32] e; +a = 5; +b = 1.2; +a = 1; e = a;""" assert qasm == expected_qasm diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py index 500a23de..537a95c9 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_operators.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py @@ -19,6 +19,7 @@ import pytest import braket.experimental.autoqasm as aq +from braket.experimental.autoqasm.errors import UnsupportedConditionalExpressionError from braket.experimental.autoqasm.gates import cnot, h, measure, x @@ -81,6 +82,96 @@ def test_conditional_expressions_py_cond(if_true: dict, if_false: dict) -> None: assert if_false["qasm"] not in qasm +def test_inline_conditional_assignment() -> None: + """Tests conditional expression where the if and else clauses return different types.""" + + @aq.function + def cond_exp_assignment(): + a = aq.IntVar(2) * aq.IntVar(3) if aq.BoolVar(True) else aq.IntVar(4) # noqa: F841 + + expected = """OPENQASM 3.0; +int[32] __int_3__; +int[32] __int_1__ = 2; +int[32] __int_2__ = 3; +bool __bool_0__ = true; +int[32] __int_4__ = 4; +int[32] a; +if (__bool_0__) { + __int_3__ = __int_1__ * __int_2__; +} else { + __int_3__ = __int_4__; +} +a = __int_3__;""" + + assert cond_exp_assignment().to_ir() == expected + + +@pytest.mark.parametrize( + "else_value", + [ + lambda: aq.FloatVar(2), + lambda: aq.BoolVar(False), + lambda: aq.BitVar(0), + lambda: aq.ArrayVar(dimensions=[3]), + ], +) +def test_unsupported_inline_conditional_assignment(else_value) -> None: + """Tests conditional expression where the if and else clauses return different types.""" + + @aq.function + def cond_exp_assignment_different_types(): + a = aq.IntVar(1) if aq.BoolVar(True) else else_value() # noqa: F841 + + with pytest.raises(UnsupportedConditionalExpressionError): + cond_exp_assignment_different_types() + + +def test_branch_assignment_undeclared() -> None: + """Tests if-else branch where an undeclared variable is assigned in both branches.""" + + @aq.function + def branch_assignment_undeclared(): + if aq.BoolVar(True): + a = aq.IntVar(1) # noqa: F841 + else: + a = aq.IntVar(2) # noqa: F841 + + expected = """OPENQASM 3.0; +int[32] a; +bool __bool_0__ = true; +if (__bool_0__) { + a = 1; +} else { + a = 2; +}""" + + assert branch_assignment_undeclared().to_ir() == expected + + +def test_branch_assignment_declared() -> None: + """Tests if-else branch where a declared variable is assigned in both branches.""" + + @aq.function + def branch_assignment_declared(): + a = aq.IntVar(5) + if aq.BoolVar(True): + a = aq.IntVar(6) # noqa: F841 + else: + a = aq.IntVar(7) # noqa: F841 + + expected = """OPENQASM 3.0; +int[32] a; +bool __bool_1__ = true; +a = 5; +if (__bool_1__) { + a = 6; +} else { + a = 7; +}""" + + assert branch_assignment_declared().to_ir() == expected + + def for_body(i: aq.QubitIdentifierType) -> None: h(i) @@ -357,8 +448,10 @@ def slice(): a[3] = b expected = """OPENQASM 3.0; -bit[6] a = 0; -bit b = 1; +bit[6] a; +bit b; +a = 0; +b = 1; a[3] = b;""" assert slice().to_ir() == expected @@ -374,11 +467,13 @@ def measure_to_slice(): b0[3] = c expected = """OPENQASM 3.0; -qubit[1] __qubits__; bit[10] b0; +bit[10] __bit_0__; +bit c; +qubit[1] __qubits__; +b0 = __bit_0__; bit __bit_1__; __bit_1__ = measure __qubits__[0]; -bit c; c = __bit_1__; b0[3] = c;""" @@ -388,9 +483,9 @@ def measure_to_slice(): @pytest.mark.parametrize( "target_name,value,expected_qasm", [ - ("foo", oqpy.IntVar(5), "\nint[32] foo = 5;"), - ("bar", oqpy.FloatVar(1.2), "\nfloat[64] bar = 1.2;"), - ("baz", oqpy.BitVar(0), "\nbit baz = 0;"), + ("foo", oqpy.IntVar(5), "\nint[32] foo;\nfoo = 5;"), + ("bar", oqpy.FloatVar(1.2), "\nfloat[64] bar;\nbar = 1.2;"), + ("baz", oqpy.BitVar(0), "\nbit baz;\nbaz = 0;"), ], ) def test_assignment_qasm_undeclared_target( @@ -500,10 +595,11 @@ def test_assignment_py(value: Any) -> None: Args: value (Any): Assignment value. """ - new_value = aq.operators.assign_stmt( - target_name="foo", - value=value, - ) + with aq.build_program(): + new_value = aq.operators.assign_stmt( + target_name="foo", + value=value, + ) assert new_value == value diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index cbc1ea0e..e2f17679 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -58,7 +58,8 @@ def main() -> aq.BitVar: expected = """OPENQASM 3.0; def ret_test() -> bit { - bit res = 1; + bit res; + res = 1; return res; } bit __bit_1__; @@ -81,7 +82,8 @@ def main() -> int: expected = """OPENQASM 3.0; def ret_test() -> int[32] { - int[32] res = 1; + int[32] res; + res = 1; return res; } int[32] __int_1__ = 0; @@ -104,7 +106,8 @@ def main() -> float: expected = """OPENQASM 3.0; def ret_test() -> float[64] { - float[64] res = 1.0; + float[64] res; + res = 1.0; return res; } float[64] __float_1__ = 0.0; @@ -127,7 +130,8 @@ def main() -> bool: expected = """OPENQASM 3.0; def ret_test() -> bool { - bool res = true; + bool res; + res = true; return res; } bool __bool_1__ = false; @@ -153,8 +157,10 @@ def ret_test() -> int: def add(int[32] a, int[32] b) -> int[32] { return a + b; } -int[32] a = 5; -int[32] b = 6; +int[32] a; +int[32] b; +a = 5; +b = 6; int[32] __int_2__ = 0; __int_2__ = add(a, b);""" @@ -168,7 +174,9 @@ def test_return_none(): def ret_test() -> None: return None - ret_test().to_ir() + expected = "OPENQASM 3.0;" + + assert ret_test().to_ir() == expected def test_return_array(): @@ -185,7 +193,8 @@ def main() -> List[int]: expected = """OPENQASM 3.0; def ret_test() -> array[int[32], 3] { - array[int[32], 3] res = {1, 2, 3}; + array[int[32], 3] res; + res = {1, 2, 3}; return res; } array[int[32], 3] __arr_1__ = {}; @@ -208,7 +217,8 @@ def ret_test() -> int: expected = """OPENQASM 3.0; def helper() -> int[32] { - int[32] res = 1; + int[32] res; + res = 1; return res; } int[32] __int_1__ = 0; @@ -289,7 +299,8 @@ def main(): expected = """OPENQASM 3.0; def annotation_test(array[int[32], 10] input) { } -array[int[32], 10] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; +array[int[32], 10] a; +a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; annotation_test(a);""" assert main().to_ir() == expected @@ -310,7 +321,8 @@ def main(): expected = """OPENQASM 3.0; def annotation_test(bit input) { } -bit a = 1; +bit a; +a = 1; annotation_test(a);""" assert main().to_ir() == expected @@ -349,7 +361,8 @@ def caller() -> int: expected_qasm = """OPENQASM 3.0; def retval_test() -> int[32] { - int[32] retval_ = 1; + int[32] retval_; + retval_ = 1; return retval_; } int[32] __int_1__ = 0; @@ -371,7 +384,8 @@ def caller() -> aq.BitVar: expected_qasm = """OPENQASM 3.0; def retval_test() -> bit { - bit retval_ = 1; + bit retval_; + retval_ = 1; return retval_; } bit __bit_1__; @@ -390,14 +404,16 @@ def retval_recursive() -> int: expected_qasm = """OPENQASM 3.0; def retval_recursive() -> int[32] { + int[32] retval_; int[32] __int_1__ = 0; __int_1__ = retval_recursive(); - int[32] retval_ = 1; + retval_ = 1; return retval_; } +int[32] retval_; int[32] __int_3__ = 0; __int_3__ = retval_recursive(); -int[32] retval_ = 1;""" +retval_ = 1;""" assert retval_recursive().to_ir() == expected_qasm @@ -412,18 +428,20 @@ def retval_recursive() -> int: expected_qasm = """OPENQASM 3.0; def retval_recursive() -> int[32] { + int[32] a; + int[32] retval_; int[32] __int_1__ = 0; __int_1__ = retval_recursive(); - int[32] a; a = __int_1__; - int[32] retval_ = 1; + retval_ = 1; return retval_; } +int[32] a; +int[32] retval_; int[32] __int_3__ = 0; __int_3__ = retval_recursive(); -int[32] a; a = __int_3__; -int[32] retval_ = 1;""" +retval_ = 1;""" assert retval_recursive().to_ir() == expected_qasm @@ -453,7 +471,8 @@ def retval_recursive() -> int[32] { return 2 * __int_1__ + (__int_3__ + 2) / 3; } def retval_constant() -> int[32] { - int[32] retval_ = 3; + int[32] retval_; + retval_ = 3; return retval_; } int[32] __int_4__ = 0; From ec404e3a2cbcbdbf14957c313ec6d492de4b8acb Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 31 Jul 2023 17:03:55 +0000 Subject: [PATCH 0782/1165] prepare release v1.53.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 f4f0f5d2..1902b3eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## v1.53.0 (2023-07-31) + +### Features + + * point image uri to latest tag + +### Bug Fixes and Other Changes + + * Update quantum job tests for latest containers + * build(deps): bump pypa/gh-action-pypi-publish from 1.8.7 to 1.8.8 + * move import back to top level in job example + * update doc string for handle_parameter_value + * change all tests in the circuit test path to use with pytest.raises + * pull latest container image by default + ## v1.52.0 (2023-07-25) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index b6203397..08e293e9 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.52.1.dev0" +__version__ = "1.53.0" From 6f79c45e396141567d88cc8c9bc6c82b453bd2d1 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 31 Jul 2023 17:03:55 +0000 Subject: [PATCH 0783/1165] update development version to v1.53.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 08e293e9..c3eebe12 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.53.0" +__version__ = "1.53.1.dev0" From c4969217b30a6d9e5fc02de835c6f6e09b8f36a9 Mon Sep 17 00:00:00 2001 From: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com> Date: Tue, 1 Aug 2023 10:37:27 -0700 Subject: [PATCH 0784/1165] doc: fix flake8 issues in tests (#652) --- src/braket/circuits/braket_program_context.py | 2 +- test/unit_tests/braket/circuits/test_gates.py | 6 +++--- .../braket/parametric/test_free_parameter_expression.py | 2 +- .../braket/pulse/ast/test_approximation_parser.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 44ce4bb1..266ee06d 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -144,7 +144,7 @@ def handle_parameter_value( Returns: Union[float, FreeParameterExpression]: Return the value directly if numeric, - otherwise wraps the symbolic expression as a `FreeParameterExpression`. + otherwise wraps the symbolic expression as a `FreeParameterExpression`. """ if isinstance(value, Expr): return FreeParameterExpression(str(value)) diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index e10170cb..032dc550 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -933,12 +933,12 @@ def test_bind_values(gate): new_gate = param_gate.bind_values(**mapping) expected = gate(*range(num_params)) - assert type(new_gate) == type(param_gate) and new_gate == expected + assert type(new_gate) is type(param_gate) and new_gate == expected if triple_angled: for angle in new_gate.angle_1, new_gate.angle_2, new_gate.angle_3: - assert type(angle) == float + assert isinstance(angle, float) else: - assert type(new_gate.angle) == float + assert isinstance(new_gate.angle, float) def test_bind_values_pulse_gate(): 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 0d67b9f6..879706fe 100644 --- a/test/unit_tests/braket/parametric/test_free_parameter_expression.py +++ b/test/unit_tests/braket/parametric/test_free_parameter_expression.py @@ -168,4 +168,4 @@ def test_sub_return_expression(): def test_subs_if_free_parameter(param, kwargs, expected_value, expected_type): value = subs_if_free_parameter(param, **kwargs) assert value == expected_value - assert type(value) == expected_type + assert isinstance(value, expected_type) 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 c41a1712..ae1bc622 100644 --- a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py +++ b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py @@ -289,7 +289,7 @@ def test_predefined_frame(port): expected_phases["frame1"].put(0, 0).put(2e-9, 0) for statement in pulse_seq._program._state.body: - assert type(statement) != ast.FrameType + assert not isinstance(statement, ast.FrameType) parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) From b84a169e3ebc638efd0f6030b0df3790b101c259 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 2 Aug 2023 16:12:56 +0000 Subject: [PATCH 0785/1165] prepare release v1.53.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 1902b3eb..3c37a651 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.53.0.post0 (2023-08-02) + +### Documentation Changes + + * fix flake8 issues in tests + ## v1.53.0 (2023-07-31) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index c3eebe12..e59cdc7a 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.53.1.dev0" +__version__ = "1.53.0.post0" From e7281e9cb91bfdb589259cc2a9b605d0e25cd805 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 2 Aug 2023 16:12:56 +0000 Subject: [PATCH 0786/1165] update development version to v1.53.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 e59cdc7a..c3eebe12 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.53.0.post0" +__version__ = "1.53.1.dev0" From 618722669dc22c6716016cceccca8b6f103e9e6d Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 2 Aug 2023 12:11:17 -0700 Subject: [PATCH 0787/1165] test: update devices enum integ test (#654) --- test/integ_tests/test_device_creation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 222a82f4..0be1d22e 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_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 typing import List, Set import pytest @@ -131,7 +132,6 @@ def test_device_enum(): # validate all devices in enum providers = [getattr(Devices, attr) for attr in dir(Devices) if not attr.startswith("__")] for provider in providers: - devices = [getattr(provider, attr) for attr in dir(provider) if not attr.startswith("__")] - for arn in devices: - device = AwsDevice(arn) + for device_arn in provider: + device = AwsDevice(device_arn) _validate_device(device, active_providers) From aa4f881b4708e6514ebbe44e3ffdd3c3494e252e Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 2 Aug 2023 21:48:49 -0700 Subject: [PATCH 0788/1165] Support OpenQASM `Program`s in `from_ir` (#655) --- src/braket/circuits/circuit.py | 12 +++++++-- .../braket/circuits/test_circuit.py | 27 ++++++++++++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 38d6793f..58d4cdda 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -1132,17 +1132,25 @@ def to_ir( raise ValueError(f"Supplied ir_type {ir_type} is not supported.") @staticmethod - def from_ir(source: str, inputs: Optional[Dict[str, io_type]] = None) -> Circuit: + def from_ir( + source: Union[str, OpenQasmProgram], inputs: Optional[Dict[str, io_type]] = None + ) -> Circuit: """ Converts an OpenQASM program to a Braket Circuit object. Args: - source (str): OpenQASM string. + source (Union[str, OpenQasmProgram]): OpenQASM string. inputs (Optional[Dict[str, io_type]]): Inputs to the circuit. Returns: Circuit: Braket Circuit implementing the OpenQASM program. """ + if isinstance(source, OpenQasmProgram): + if inputs: + inputs_copy = source.inputs.copy() if source.inputs is not None else {} + inputs_copy.update(inputs) + inputs = inputs_copy + source = source.source from braket.circuits.braket_program_context import BraketProgramContext return Interpreter(BraketProgramContext()).build_circuit( diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 6acf5690..e1165fe9 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -1406,10 +1406,29 @@ def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): ), ], ) -def test_circuit_from_ir(expected_circuit, ir): - circuit_from_ir = Circuit.from_ir(source=ir.source, inputs=ir.inputs) +def test_from_ir(expected_circuit, ir): + assert Circuit.from_ir(source=ir.source, inputs=ir.inputs) == expected_circuit + assert Circuit.from_ir(source=ir) == expected_circuit - assert circuit_from_ir == expected_circuit + +def test_from_ir_inputs_updated(): + circuit = Circuit().rx(0, 0.2).ry(0, 0.1) + 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];", + ] + ), + inputs={"theta": 0.2, "phi": 0.3}, + ) + assert Circuit.from_ir(source=openqasm, inputs={"phi": 0.1}) == circuit @pytest.mark.parametrize( @@ -1529,7 +1548,7 @@ def test_circuit_from_ir(expected_circuit, ir): ), ], ) -def test_circuit_from_ir_greater_functionality(expected_circuit, ir): +def test_from_ir_advanced_openqasm(expected_circuit, ir): circuit_from_ir = Circuit.from_ir(source=ir.source, inputs=ir.inputs) assert circuit_from_ir == expected_circuit From 2792fb994ef67b91fc63b5b3e569676ea1ec1e24 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 3 Aug 2023 16:14:07 +0000 Subject: [PATCH 0789/1165] prepare release v1.53.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 3c37a651..2b4b3ad1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.53.1 (2023-08-03) + +### Bug Fixes and Other Changes + + * Support OpenQASM `Program`s in `from_ir` + ## v1.53.0.post0 (2023-08-02) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index c3eebe12..b22005d8 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.53.1.dev0" +__version__ = "1.53.1" From da00284fdd4d79457fa50f6df1cfedc5b8b8f7d9 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 3 Aug 2023 16:14:07 +0000 Subject: [PATCH 0790/1165] update development version to v1.53.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 b22005d8..889347c8 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.53.1" +__version__ = "1.53.2.dev0" From b60db933e30cfbed18d1bac576aca7d7fba0a452 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Thu, 3 Aug 2023 14:49:31 -0700 Subject: [PATCH 0791/1165] fix: don't wrap FreeParameterExpression input as string (#656) --- src/braket/circuits/braket_program_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 266ee06d..6d939882 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -147,5 +147,5 @@ def handle_parameter_value( otherwise wraps the symbolic expression as a `FreeParameterExpression`. """ if isinstance(value, Expr): - return FreeParameterExpression(str(value)) + return FreeParameterExpression(value) return value From 682663a333427e08d6a86f339b20ba8d24e9facf Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Fri, 4 Aug 2023 14:08:11 -0400 Subject: [PATCH 0792/1165] fix: Ensure AutoQASM bit registers are always properly initialized (#658) --- .github/workflows/python-package.yml | 8 +++ .../experimental/autoqasm/types/types.py | 4 ++ .../braket/experimental/autoqasm/test_api.py | 60 ++++++++++++------- .../experimental/autoqasm/test_operators.py | 5 +- tox.ini | 17 +++++- 5 files changed, 68 insertions(+), 26 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 996fcbc7..f90aa2af 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -36,6 +36,14 @@ jobs: - name: Run unit tests run: | tox -e unit-tests + - name: Run AutoQASM example notebooks + # NOTE: Do not merge this part to main. The AutoQASM example notebooks should be moved + # to the example notebooks repo rather than living in the Braket SDK repo. + run: | + pip install notebook matplotlib + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/2_Expressing_classical_control_flow.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/3_Iterative_phase_estimation.ipynb - name: Upload coverage report to Codecov uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 if: ${{ strategy.job-index }} == 0 diff --git a/src/braket/experimental/autoqasm/types/types.py b/src/braket/experimental/autoqasm/types/types.py index 19dbe620..27b2467f 100644 --- a/src/braket/experimental/autoqasm/types/types.py +++ b/src/braket/experimental/autoqasm/types/types.py @@ -17,6 +17,7 @@ import oqpy import oqpy.base +from openpulse import ast from braket.experimental.autoqasm import program @@ -68,6 +69,9 @@ class BitVar(oqpy.BitVar): def __init__(self, *args, **kwargs): super(BitVar, self).__init__(*args, **kwargs) self.name = program.get_program_conversion_context().next_var_name(oqpy.BitVar) + if self.size: + value = self.init_expression or 0 + self.init_expression = ast.BitstringLiteral(value, self.size) class BoolVar(oqpy.BoolVar): diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index 129c3842..71bc0d16 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -207,17 +207,21 @@ def test_bell_measurement_undeclared() -> None: qubit[2] __qubits__; h __qubits__[0]; cnot __qubits__[0], __qubits__[1]; -bit[2] __bit_0__; +bit[2] __bit_0__ = "00"; __bit_0__[0] = measure __qubits__[0]; __bit_0__[1] = measure __qubits__[1]; c = __bit_0__;""" assert bell_measurement_undeclared().to_ir() == expected +def test_sim_bell_measurement_undeclared() -> None: + _test_on_local_sim(bell_measurement_undeclared()) + + @aq.function def bell_measurement_declared() -> None: """A function that generates and measures a two-qubit Bell state.""" - c = aq.BitVar([0, 0], size=2) + c = aq.BitVar(0, size=2) h(0) cnot(0, 1) c = measure([0, 1]) # noqa: F841 @@ -227,16 +231,20 @@ def test_bell_measurement_declared() -> None: expected = """OPENQASM 3.0; bit[2] c; qubit[2] __qubits__; -c = {0, 0}; +c = "00"; h __qubits__[0]; cnot __qubits__[0], __qubits__[1]; -bit[2] __bit_1__; +bit[2] __bit_1__ = "00"; __bit_1__[0] = measure __qubits__[0]; __bit_1__[1] = measure __qubits__[1]; c = __bit_1__;""" assert bell_measurement_declared().to_ir() == expected +def test_sim_bell_measurement_declared() -> None: + _test_on_local_sim(bell_measurement_declared()) + + @aq.function def bell_partial_measurement() -> None: """A function that generates and measures a two-qubit Bell state.""" @@ -441,13 +449,17 @@ def test_simple_measurement() -> None: """Test that a program with only measurements is generated correctly.""" expected = """OPENQASM 3.0; qubit[6] __qubits__; -bit[3] __bit_0__; +bit[3] __bit_0__ = "000"; __bit_0__[0] = measure __qubits__[5]; __bit_0__[1] = measure __qubits__[2]; __bit_0__[2] = measure __qubits__[1];""" assert ground_state_measurements().to_ir() == expected +def test_sim_simple_measurement() -> None: + _test_on_local_sim(ground_state_measurements()) + + def test_simple_measurement_return() -> None: """Test that a program with only measurements is generated correctly, when the measurement results are returned from a subroutine. @@ -459,7 +471,7 @@ def ground_state_measurements_wrapper() -> None: expected = """OPENQASM 3.0; def ground_state_measurements() -> bit[3] { - bit[3] __bit_0__; + bit[3] __bit_0__ = "000"; __bit_0__[0] = measure __qubits__[5]; __bit_0__[1] = measure __qubits__[2]; __bit_0__[2] = measure __qubits__[1]; @@ -717,41 +729,45 @@ def bell(int[32] q0, int[32] q1) { assert bell_in_for_loop().to_ir() == expected -def test_classical_variables_types(): - @aq.function - def prog() -> None: - a = aq.BitVar(0) - a = aq.BitVar(1) # noqa: F841 +@aq.function +def classical_variables_types() -> None: + a = aq.BitVar(0) + a = aq.BitVar(1) # noqa: F841 + + i = aq.IntVar(1) + a_array = aq.BitVar(size=2) + a_array[0] = aq.BitVar(0) + a_array[i] = aq.BitVar(1) - i = aq.IntVar(1) - a_array = aq.BitVar(size=2) - a_array[0] = aq.BitVar(0) - a_array[i] = aq.BitVar(1) + b = aq.IntVar(10) + b = aq.IntVar(15) # noqa: F841 - b = aq.IntVar(10) - b = aq.IntVar(15) # noqa: F841 + c = aq.FloatVar(1.2) + c = aq.FloatVar(3.4) # noqa: F841 - c = aq.FloatVar(1.2) - c = aq.FloatVar(3.4) # noqa: F841 +def test_classical_variables_types(): expected = """OPENQASM 3.0; bit a; int[32] i; bit[2] a_array; -bit[2] __bit_3__; int[32] b; float[64] c; a = 0; a = 1; i = 1; -a_array = __bit_3__; +a_array = "00"; a_array[0] = 0; a_array[i] = 1; b = 10; b = 15; c = 1.2; c = 3.4;""" - assert prog().to_ir() == expected + assert classical_variables_types().to_ir() == expected + + +def test_sim_classical_variables_types(): + _test_on_local_sim(classical_variables_types()) def test_classical_variables_assignment(): diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py index 537a95c9..9f93fb06 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_operators.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py @@ -450,7 +450,7 @@ def slice(): expected = """OPENQASM 3.0; bit[6] a; bit b; -a = 0; +a = "000000"; b = 1; a[3] = b;""" @@ -468,10 +468,9 @@ def measure_to_slice(): expected = """OPENQASM 3.0; bit[10] b0; -bit[10] __bit_0__; bit c; qubit[1] __qubits__; -b0 = __bit_0__; +b0 = "0000000000"; bit __bit_1__; __bit_1__ = measure __qubits__[0]; c = __bit_1__; diff --git a/tox.ini b/tox.ini index 5873a686..6d68d0aa 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = clean,linters,docs,unit-tests +envlist = clean,linters,docs,unit-tests,notebooks [testenv:clean] deps = coverage @@ -28,6 +28,21 @@ commands = pytest test/integ_tests {posargs} extras = test +[testenv:notebooks] +# NOTE: Do not merge this part to main. The AutoQASM example notebooks should be moved +# to the example notebooks repo rather than living in the Braket SDK repo. +usedevelop=True +basepython = python3 +deps = + {[testenv:unit-tests]deps} + notebook + matplotlib +commands = + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/2_Expressing_classical_control_flow.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/3_Iterative_phase_estimation.ipynb +extras = test + [testenv:linters] basepython = python3 skip_install = true From c9444896a8eeaf5996974482a49ee9446908ba93 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:02:44 -0400 Subject: [PATCH 0793/1165] fix: assigning AutoQASM subroutine arg to a local variable (#660) --- .../autoqasm/operators/assignments.py | 3 ++- .../experimental/autoqasm/test_types.py | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/braket/experimental/autoqasm/operators/assignments.py b/src/braket/experimental/autoqasm/operators/assignments.py index d84b3afc..fd42f0c0 100644 --- a/src/braket/experimental/autoqasm/operators/assignments.py +++ b/src/braket/experimental/autoqasm/operators/assignments.py @@ -22,6 +22,7 @@ from braket.experimental.autoqasm import constants, program, types from braket.experimental.autoqasm.autograph.operators.variables import UndefinedReturnValue +from braket.experimental.autoqasm.types.conversions import var_type_from_oqpy def assign_stmt(target_name: str, value: Any) -> Any: @@ -109,7 +110,7 @@ def _validate_variables_type_size(var1: oqpy.base.Var, var2: oqpy.base.Var) -> N var1_size = var1.size or 1 var2_size = var2.size or 1 - if type(var1) != type(var2): + if var_type_from_oqpy(var1) != var_type_from_oqpy(var2): raise ValueError("Variables in assignment statements must have the same type") if var1_size != var2_size: raise ValueError("Variables in assignment statements must have the same size") diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index e2f17679..dd108de2 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -348,6 +348,32 @@ def annotation_test(bit input) { assert main().to_ir() == expected +def test_map_and_assign_arg(): + """Test input parameter handling which is assigned to another variable.""" + + @aq.function + def assign_param(c: int) -> None: + d = aq.IntVar(4) + c = d # noqa: F841 + + @aq.function + def main(): + c = aq.IntVar(0) + assign_param(c) + + expected = """OPENQASM 3.0; +def assign_param(int[32] c) { + int[32] d; + d = 4; + c = d; +} +int[32] c; +c = 0; +assign_param(c);""" + + assert main().to_ir() == expected + + def test_unnamed_retval_python_type() -> None: """Tests subroutines which return unnamed Python values.""" From e576f94a700ca2863ab38e5619417268212a958e Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 7 Aug 2023 16:12:56 +0000 Subject: [PATCH 0794/1165] prepare release v1.53.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 2b4b3ad1..08afa19b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.53.2 (2023-08-07) + +### Bug Fixes and Other Changes + + * don't wrap FreeParameterExpression input as string + ## v1.53.1 (2023-08-03) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 889347c8..a395e894 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.53.2.dev0" +__version__ = "1.53.2" From 2332362968fbe5517ef5eec27ec0cd2bda15d982 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 7 Aug 2023 16:12:57 +0000 Subject: [PATCH 0795/1165] update development version to v1.53.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 a395e894..3be434e5 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.53.2" +__version__ = "1.53.3.dev0" From 87965c4c979d84c7dc40d37ab75df7916cb4beae Mon Sep 17 00:00:00 2001 From: maolinml <86260930+maolinml@users.noreply.github.com> Date: Mon, 7 Aug 2023 19:27:12 -0700 Subject: [PATCH 0796/1165] fix: fix a bug in time series and add trapezoidal time series (#637) * add trapezoidal time series * change some doc * change doc * update an argument * fix linters --------- Co-authored-by: Mao Lin Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> --- src/braket/timings/time_series.py | 72 +++++++++++++++++- .../braket/timings/test_time_series.py | 75 +++++++++++++++++++ 2 files changed, 145 insertions(+), 2 deletions(-) diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py index cfc87141..3baffe63 100644 --- a/src/braket/timings/time_series.py +++ b/src/braket/timings/time_series.py @@ -118,15 +118,19 @@ def from_lists(times: List[float], values: List[float]) -> TimeSeries: @staticmethod def constant_like(times: Union[List[float], TimeSeries], constant: float = 0.0) -> TimeSeries: - """Obtain a constant time series given the list of time points and the constant values + """Obtain a constant time series given another time series or the list of time points, + and the constant values Args: - times (Union[List[float], TimeSeries]): list of time points + times (Union[List[float], TimeSeries]): list of time points or a time series constant (float): constant value Returns: TimeSeries: A constant time series """ + if not isinstance(times, List): + times = times.times() + ts = TimeSeries() for t in times: ts.put(t, constant) @@ -298,6 +302,70 @@ def periodic_signal(times: List[float], values: List[float], num_repeat: int = 1 return new_time_series + @staticmethod + def trapezoidal_signal( + area: float, value_max: float, slew_rate_max: float, time_separation_min: float = 0.0 + ) -> TimeSeries: + """Get a trapezoidal time series with specified area, maximum value, maximum slew rate + and minimum separation of time points + + Args: + area (float): Total area under the time series + value_max (float): The maximum value of the time series + slew_rate_max (float): The maximum slew rate + time_separation_min (float): The minimum separation of time points + + Returns: + TimeSeries: A trapezoidal time series + + Notes: + The area of a time series f(t) is defined as the time integral of + 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: + raise ValueError("The maximum value of the trapezoidal time series has to be positive.") + if slew_rate_max <= 0.0: + raise ValueError( + "The maximum slew rate of the trapezoidal time series has to be positive." + ) + if time_separation_min < 0.0: + raise ValueError( + "The minimum separation of time points of the trapezoidal time series " + "has to be non-negative." + ) + + # Determine the ramp time to reach the max allowed value + t_ramp = max(time_separation_min, value_max / slew_rate_max) + + # The max achievable area if there are 3 time points: [0, t_ramp, 2 * t_ramp] + area_threshold_1 = t_ramp * value_max + + # The max achievable area if there are 4 time points: + # [0, t_ramp, t_ramp + time_separation_min, 2 * t_ramp + time_separation_min] + area_threshold_2 = (t_ramp + time_separation_min) * value_max + + if area <= area_threshold_1: + # Determine the max value if area <= area_threshold_1 + value = area / t_ramp + times = [0, t_ramp, 2 * t_ramp] + values = [0, value, 0] + elif area <= area_threshold_2: + # Determine the max value if area_threshold_1 < area <= area_threshold_2 + value = area / (t_ramp + time_separation_min) + times = [0, t_ramp, t_ramp + time_separation_min, 2 * t_ramp + time_separation_min] + values = [0, value, value, 0] + else: + # Determine the t_plateau if area > area_threshold_2 + t_plateau = (area - area_threshold_2) / value_max + time_separation_min + times = [0, t_ramp, t_ramp + t_plateau, 2 * t_ramp + t_plateau] + values = [0, value_max, value_max, 0] + + return TimeSeries.from_lists(times, values) + # TODO: Verify if this belongs here. def _all_close(first: TimeSeries, second: TimeSeries, tolerance: Number = 1e-7) -> bool: diff --git a/test/unit_tests/braket/timings/test_time_series.py b/test/unit_tests/braket/timings/test_time_series.py index 8c3a6f28..0f99ca65 100755 --- a/test/unit_tests/braket/timings/test_time_series.py +++ b/test/unit_tests/braket/timings/test_time_series.py @@ -72,6 +72,81 @@ def test_periodic_signal(): assert new_ts.values() == expected_values +def test_constant_like_with_time_series(): + time_series = TimeSeries().put(0.0, 0.0).put(1.2, 3.14) + constant_ts = TimeSeries.constant_like(time_series, constant=3.14) + times = time_series.times() + assert times == constant_ts.times() + assert constant_ts.values() == [3.14] * len(times) + + +@pytest.mark.parametrize( + "area, value_max, slew_rate_max, time_separation_min", + [ + (2.0, 2.0, 4.0, 1.0), + (1.0, 2.0, 4.0, 1.0), + (4.0, 2.0, 1.0, 1.0), + (1.0, 2.0, 1.0, 1.0), + ], +) +def test_trapezoidal_signal_as_triangular_signal( + area, value_max, slew_rate_max, time_separation_min +): + ts = TimeSeries.trapezoidal_signal(area, value_max, slew_rate_max, time_separation_min) + t_ramp = max(time_separation_min, value_max / slew_rate_max) + value = area / t_ramp + assert ts.times() == [0, t_ramp, 2 * t_ramp] + assert ts.values() == [0, value, 0] + + +@pytest.mark.parametrize( + "area, value_max, slew_rate_max, time_separation_min", + [ + (4.0, 2.0, 4.0, 1.0), + (2.1, 2.0, 4.0, 1.0), + (6.0, 2.0, 1.0, 1.0), + (4.1, 2.0, 1.0, 1.0), + ], +) +def test_trapezoidal_signal_as_min_trapezoidal_signal( + area, value_max, slew_rate_max, time_separation_min +): + ts = TimeSeries.trapezoidal_signal(area, value_max, slew_rate_max, time_separation_min) + t_ramp = max(time_separation_min, value_max / slew_rate_max) + value = area / (t_ramp + time_separation_min) + assert ts.times() == [0, t_ramp, t_ramp + time_separation_min, 2 * t_ramp + time_separation_min] + assert ts.values() == [0, value, value, 0] + + +@pytest.mark.parametrize( + "area, value_max, slew_rate_max, time_separation_min", + [ + (4.1, 2.0, 4.0, 1.0), + (6.1, 2.0, 1.0, 1.0), + ], +) +def test_trapezoidal_signal_with_large_area(area, value_max, slew_rate_max, time_separation_min): + ts = TimeSeries.trapezoidal_signal(area, value_max, slew_rate_max, time_separation_min) + t_ramp = max(time_separation_min, value_max / slew_rate_max) + t_plateau = area / value_max - t_ramp + assert ts.times() == [0, t_ramp, t_ramp + t_plateau, 2 * t_ramp + t_plateau] + assert ts.values() == [0, value_max, value_max, 0] + + +@pytest.mark.xfail(raises=ValueError) +@pytest.mark.parametrize( + "area, value_max, slew_rate_max, time_separation_min", + [ + (-4.1, 2.0, 4.0, 1.0), + (4.1, -2.0, 4.0, 1.0), + (4.1, 2.0, -4.0, 1.0), + (4.1, 2.0, 4.0, -1.0), + ], +) +def test_trapezoidal_signal_with_negative_para(area, value_max, slew_rate_max, time_separation_min): + TimeSeries.trapezoidal_signal(area, value_max, slew_rate_max, time_separation_min) + + @pytest.mark.xfail(raises=ValueError) def test_periodic_signal_not_eq_length(): times = list(range(5)) From 44e3d8106286b9a90b5a9158e7eed098c0f81d6b Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 8 Aug 2023 16:13:22 +0000 Subject: [PATCH 0797/1165] prepare release v1.53.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 08afa19b..b528e35e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.53.3 (2023-08-08) + +### Bug Fixes and Other Changes + + * fix a bug in time series and add trapezoidal time series + ## v1.53.2 (2023-08-07) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 3be434e5..71ef4201 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.53.3.dev0" +__version__ = "1.53.3" From 98378bf2ea8658efb9ee6671434e64ff5c1a3134 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 8 Aug 2023 16:13:22 +0000 Subject: [PATCH 0798/1165] update development version to v1.53.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 71ef4201..d2bc4d05 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.53.3" +__version__ = "1.53.4.dev0" From 10a80e2c688c4a869e368a20f1fc02add8ad2e8a Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Tue, 8 Aug 2023 13:32:39 -0400 Subject: [PATCH 0799/1165] Raise error if the user puts a break in their autoqasm loop (#662) * Raise error if the user puts a break in their autoqasm loop * Update src/braket/experimental/autoqasm/converters/break_statements.py Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --------- Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- .../autoqasm/converters/break_statements.py | 35 +++++++++++++++++++ src/braket/experimental/autoqasm/errors.py | 4 +++ .../autoqasm/transpiler/transpiler.py | 3 +- .../experimental/autoqasm/test_converters.py | 22 ++++++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 src/braket/experimental/autoqasm/converters/break_statements.py diff --git a/src/braket/experimental/autoqasm/converters/break_statements.py b/src/braket/experimental/autoqasm/converters/break_statements.py new file mode 100644 index 00000000..15563d2c --- /dev/null +++ b/src/braket/experimental/autoqasm/converters/break_statements.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. + + +"""Converters for break statement nodes.""" + +import ast + +from braket.experimental.autoqasm import errors +from braket.experimental.autoqasm.autograph.converters import break_statements +from braket.experimental.autoqasm.autograph.core import ag_ctx, converter + + +class BreakValidator(converter.Base): + def visit_Break(self, node: ast.stmt) -> ast.stmt: + """Break statements are currently unsupported.""" + raise errors.UnsupportedFeature("Break statements are currently unsupported.") + + +def transform( + node: ast.stmt, ctx: ag_ctx.ControlStatusCtx, default_to_null_return: bool = True +) -> ast.stmt: + node = BreakValidator(ctx).visit(node) + # When break statements are supported, we may want to fall back on default behavior + return break_statements.transform(node, ctx) # pragma: no cover diff --git a/src/braket/experimental/autoqasm/errors.py b/src/braket/experimental/autoqasm/errors.py index 7bf95974..169683a0 100644 --- a/src/braket/experimental/autoqasm/errors.py +++ b/src/braket/experimental/autoqasm/errors.py @@ -21,6 +21,10 @@ class AutoQasmError(Exception): """Base class for all AutoQASM exceptions.""" +class UnsupportedFeature(AutoQasmError): + """AutoQASM unsupported feature.""" + + class UnknownQubitCountError(AutoQasmError): """Missing declaration for the number of qubits.""" diff --git a/src/braket/experimental/autoqasm/transpiler/transpiler.py b/src/braket/experimental/autoqasm/transpiler/transpiler.py index d2816efb..58dd6431 100644 --- a/src/braket/experimental/autoqasm/transpiler/transpiler.py +++ b/src/braket/experimental/autoqasm/transpiler/transpiler.py @@ -28,7 +28,6 @@ from braket.experimental.autoqasm import operators, program, types from braket.experimental.autoqasm.autograph.converters import ( asserts, - break_statements, call_trees, conditional_expressions, continue_statements, @@ -60,7 +59,7 @@ reaching_definitions, ) from braket.experimental.autoqasm.autograph.tf_utils import tf_stack -from braket.experimental.autoqasm.converters import assignments +from braket.experimental.autoqasm.converters import assignments, break_statements class PyToOqpy(transpiler.PyToPy): diff --git a/test/unit_tests/braket/experimental/autoqasm/test_converters.py b/test/unit_tests/braket/experimental/autoqasm/test_converters.py index 73ee3403..3fdec50d 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_converters.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_converters.py @@ -59,3 +59,25 @@ def fn() -> None: a = 1; e = a;""" assert qasm == expected_qasm + + +def test_break_for_loop(): + @aq.function + def main(): + for i in aq.range(3): + aq.gates.h(i) + break + + with pytest.raises(aq.errors.UnsupportedFeature): + main() + + +def test_break_while_loop(): + @aq.function + def uses_while_w_break(): + while aq.gates.measure(0): + aq.gates.x(0) + break + + with pytest.raises(aq.errors.UnsupportedFeature): + uses_while_w_break() From 97f6cab07c5025c6dab32c2a3d9f6028081c5969 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Wed, 9 Aug 2023 09:01:50 -0400 Subject: [PATCH 0800/1165] Usability: Warning when user calls "return" from their autoqasm entry point function (#661) * Add error message for using 'return' from the main autoqasm function. * Respond to CR --- .../autoqasm/converters/return_statements.py | 40 +++++++++++++++++++ .../autoqasm/transpiler/transpiler.py | 3 +- .../braket/experimental/autoqasm/test_api.py | 22 ++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 src/braket/experimental/autoqasm/converters/return_statements.py diff --git a/src/braket/experimental/autoqasm/converters/return_statements.py b/src/braket/experimental/autoqasm/converters/return_statements.py new file mode 100644 index 00000000..5089b4e9 --- /dev/null +++ b/src/braket/experimental/autoqasm/converters/return_statements.py @@ -0,0 +1,40 @@ +# 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. + + +"""Converters for return statement nodes.""" + +import ast +import warnings + +from braket.experimental.autoqasm import program +from braket.experimental.autoqasm.autograph.converters import return_statements +from braket.experimental.autoqasm.autograph.core import ag_ctx, converter + + +class ReturnValidator(converter.Base): + def visit_Return(self, node: ast.stmt) -> ast.stmt: + aq_context = program.get_program_conversion_context() + if not aq_context.subroutines_processing and node.value is not None: + warnings.warn("Return value from top level function is ignored.") + return node + + +def transform( + node: ast.stmt, ctx: ag_ctx.ControlStatusCtx, default_to_null_return: bool = True +) -> ast.stmt: + """Handle AutoQASM-specific return statement functionality before + passing control to AutoGraph. + """ + ReturnValidator(ctx).visit(node) + return return_statements.transform(node, ctx) diff --git a/src/braket/experimental/autoqasm/transpiler/transpiler.py b/src/braket/experimental/autoqasm/transpiler/transpiler.py index 58dd6431..b2e4f33b 100644 --- a/src/braket/experimental/autoqasm/transpiler/transpiler.py +++ b/src/braket/experimental/autoqasm/transpiler/transpiler.py @@ -36,7 +36,6 @@ functions, lists, logical_expressions, - return_statements, slices, variables, ) @@ -59,7 +58,7 @@ reaching_definitions, ) from braket.experimental.autoqasm.autograph.tf_utils import tf_stack -from braket.experimental.autoqasm.converters import assignments, break_statements +from braket.experimental.autoqasm.converters import assignments, break_statements, return_statements class PyToOqpy(transpiler.PyToPy): diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index 71bc0d16..72def194 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -839,3 +839,25 @@ def ghz(n: int): cnot __qubits__[0], __qubits__[4];""" assert make_ghz(5).to_ir() == expected + + +def test_main_return(): + @aq.function + def main() -> int: + return 1 + + with pytest.warns(UserWarning, match="Return value from top level function is ignored"): + main() + + +def test_main_no_return(): + @aq.function + def tester(x: int) -> int: + return measure(x) + + @aq.function(num_qubits=3) + def main(): + x = 3 + tester(x) + + main() From a9d0237410e4646ab2466a46727d44a9dde0b83c Mon Sep 17 00:00:00 2001 From: Yunong Shi Date: Wed, 9 Aug 2023 11:30:52 -0400 Subject: [PATCH 0801/1165] change: improving autoqasm api.py coverage to 100% (#665) * change: improving autoqasm api.py coverage to 100% * Update src/braket/experimental/autoqasm/api.py --------- Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- src/braket/experimental/autoqasm/api.py | 12 +----------- .../braket/experimental/autoqasm/test_api.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 722c6b7d..1ea5c460 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -88,18 +88,12 @@ def _function_without_params( if is_autograph_artifact(f): return f - f_wrapper = f - decorators, f = tf_decorator.unwrap(f) - wrapper_factory = _convert_wrapper( user_config=user_config, recursive=False, ) wrapper = wrapper_factory(f) - if decorators: - wrapper = tf_decorator.rewrap(f_wrapper, f, wrapper) - return autograph_artifact(wrapper) @@ -357,10 +351,6 @@ def _add_qubit_declaration(program_conversion_context: aq_program.ProgramConvers """ root_oqpy_program = program_conversion_context.oqpy_program_stack[0] - # Return early if the qubit register is already declared - if aq_constants.QUBIT_REGISTER in root_oqpy_program.declared_vars: - return - # Declare the global qubit register if necessary user_specified_num_qubits = program_conversion_context.get_declared_qubits() @@ -399,7 +389,7 @@ def _dummy_function(f_source: Callable) -> Callable: return_instance = _make_return_instance(f_source, aq_program.get_program_conversion_context()) def f_dummy(*args, **kwargs) -> Any: - return return_instance + return return_instance # pragma: no cover f_dummy.__name__ = copy.deepcopy(f_source.__name__) f_dummy.__defaults__ = copy.deepcopy(f_source.__defaults__) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index 72def194..fa8f46e2 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -841,6 +841,16 @@ def ghz(n: int): assert make_ghz(5).to_ir() == expected +def test_double_decorated_function(): + @aq.function + @aq.function + def empty_program() -> None: + pass + + expected = """OPENQASM 3.0;""" + assert empty_program().to_ir() == expected + + def test_main_return(): @aq.function def main() -> int: From 3a3fc06237e39eaf157c2bfdc4823c2896765285 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Wed, 9 Aug 2023 14:05:25 -0400 Subject: [PATCH 0802/1165] Improve error message for tuple parameters (#668) * Improve error message for tuple parameters * Update src/braket/experimental/autoqasm/types/conversions.py Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- .../experimental/autoqasm/types/conversions.py | 5 +++++ .../braket/experimental/autoqasm/test_types.py | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/braket/experimental/autoqasm/types/conversions.py b/src/braket/experimental/autoqasm/types/conversions.py index c2396495..cc43a55a 100644 --- a/src/braket/experimental/autoqasm/types/conversions.py +++ b/src/braket/experimental/autoqasm/types/conversions.py @@ -52,6 +52,11 @@ def map_type(python_type: type) -> type: # ctx = program.get_program_conversion_context() # dims = ctx.get_oqpy_program().declared_vars[name_of_var].dimensions return oqpy.ArrayVar[oqpy.IntVar, 10] + if issubclass(origin_type, tuple): + raise TypeError( + "Tuples are not supported as parameters to AutoQASM functions; " + "please separate the tuple into multiple parameters or use a list instead." + ) # TODO add all supported types return python_type diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index dd108de2..b2b4fd63 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -505,3 +505,18 @@ def retval_constant() -> int[32] { __int_4__ = retval_recursive();""" assert caller().to_ir() == expected_qasm + + +def test_error_for_tuple_param() -> None: + """Tuples are not supported as parameters.""" + + @aq.function + def param_test(input: Tuple): + pass + + @aq.function + def main(): + param_test(aq.BitVar(1)) + + with pytest.raises(TypeError): + main() From cf80539574ac9081c680e455433300dbb137fc55 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Wed, 9 Aug 2023 17:23:43 -0400 Subject: [PATCH 0803/1165] Add error for missing parameter type hints (#669) --- src/braket/experimental/autoqasm/api.py | 6 ++++++ src/braket/experimental/autoqasm/errors.py | 4 ++++ .../braket/experimental/autoqasm/test_types.py | 15 +++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 1ea5c460..c20e9361 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -433,6 +433,12 @@ def _func(*args, **kwargs) -> Any: new_params = [first_param] for param in sig.parameters.values(): + if param.annotation is param.empty: + raise errors.MissingParameterTypeError( + f'Parameter "{param.name}" for subroutine "{_func.__name__}" ' + "is missing a required type hint." + ) + new_param = inspect.Parameter( name=param.name, kind=param.kind, annotation=aq_types.map_type(param.annotation) ) diff --git a/src/braket/experimental/autoqasm/errors.py b/src/braket/experimental/autoqasm/errors.py index 169683a0..adf96995 100644 --- a/src/braket/experimental/autoqasm/errors.py +++ b/src/braket/experimental/autoqasm/errors.py @@ -25,6 +25,10 @@ class UnsupportedFeature(AutoQasmError): """AutoQASM unsupported feature.""" +class MissingParameterTypeError(AutoQasmError): + """AutoQASM requires type hints for subroutine parameters.""" + + class UnknownQubitCountError(AutoQasmError): """Missing declaration for the number of qubits.""" diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index b2b4fd63..4af6b194 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -520,3 +520,18 @@ def main(): with pytest.raises(TypeError): main() + + +def test_error_for_missing_param_type() -> None: + """Parameters require type hints.""" + + @aq.function + def param_test(input): + pass + + @aq.function + def main(): + param_test(aq.BitVar(1)) + + with pytest.raises(aq.errors.MissingParameterTypeError): + main() From 821209cefa0d0e62e0426095b2e1d741971a6e07 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Thu, 10 Aug 2023 14:11:21 -0400 Subject: [PATCH 0804/1165] Return type hint ignored when possible in favor of actual return type (#667) * Return type hint ignored when possible in favor of actual return type * Update test/unit_tests/braket/experimental/autoqasm/test_types.py Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- src/braket/experimental/autoqasm/api.py | 21 +- .../autoqasm/converters/break_statements.py | 2 +- src/braket/experimental/autoqasm/errors.py | 6 +- .../autoqasm/types/conversions.py | 25 ++- .../braket/experimental/autoqasm/test_api.py | 2 +- .../experimental/autoqasm/test_converters.py | 4 +- .../experimental/autoqasm/test_types.py | 198 ++++++++++++++++-- 7 files changed, 228 insertions(+), 30 deletions(-) diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index c20e9361..b06871ac 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -290,10 +290,10 @@ def _convert_program_as_subroutine( subroutine_function_call = oqpy_sub(oqpy_program, *args, **kwargs) # Add the subroutine invocation to the program - return_instance = _make_return_instance(f, program_conversion_context) + ret_type = subroutine_function_call.subroutine_decl.return_type + return_instance = _make_return_instance_from_oqpy_return_type(ret_type) return_variable = None if isinstance(return_instance, list): - ret_type = subroutine_function_call.subroutine_decl.return_type return_variable = aq_types.ArrayVar( return_instance, dimensions=[d.value for d in ret_type.dimensions], @@ -320,9 +320,18 @@ def _convert_program_as_subroutine( root_oqpy_program._add_subroutine(subroutine_name, subroutine_function_call.subroutine_decl) -def _make_return_instance( - f: Callable, program_conversion_context: aq_program.ProgramConversionContext -) -> Any: +def _make_return_instance_from_oqpy_return_type(return_type) -> Any: + if not return_type: + return None + + return_type = aq_types.conversions.var_type_from_ast_type(return_type) + if return_type == aq_types.ArrayVar: + return [] + return return_type() + + +def _make_return_instance_from_f_annotation(f: Callable) -> Any: + # TODO: Recursive functions should work even if the user's type hint is wrong annotations = f.__annotations__ return_type = annotations["return"] if "return" in annotations else None @@ -386,7 +395,7 @@ def _clone_function(f_source: Callable) -> Callable: def _dummy_function(f_source: Callable) -> Callable: - return_instance = _make_return_instance(f_source, aq_program.get_program_conversion_context()) + return_instance = _make_return_instance_from_f_annotation(f_source) def f_dummy(*args, **kwargs) -> Any: return return_instance # pragma: no cover diff --git a/src/braket/experimental/autoqasm/converters/break_statements.py b/src/braket/experimental/autoqasm/converters/break_statements.py index 15563d2c..d6d98e98 100644 --- a/src/braket/experimental/autoqasm/converters/break_statements.py +++ b/src/braket/experimental/autoqasm/converters/break_statements.py @@ -24,7 +24,7 @@ class BreakValidator(converter.Base): def visit_Break(self, node: ast.stmt) -> ast.stmt: """Break statements are currently unsupported.""" - raise errors.UnsupportedFeature("Break statements are currently unsupported.") + raise errors.UnsupportedFeatureError("Break statements are currently unsupported.") def transform( diff --git a/src/braket/experimental/autoqasm/errors.py b/src/braket/experimental/autoqasm/errors.py index adf96995..59474a44 100644 --- a/src/braket/experimental/autoqasm/errors.py +++ b/src/braket/experimental/autoqasm/errors.py @@ -21,10 +21,14 @@ class AutoQasmError(Exception): """Base class for all AutoQASM exceptions.""" -class UnsupportedFeature(AutoQasmError): +class UnsupportedFeatureError(AutoQasmError): """AutoQASM unsupported feature.""" +class ParameterTypeError(AutoQasmError): + """AutoQASM parameter type error.""" + + class MissingParameterTypeError(AutoQasmError): """AutoQASM requires type hints for subroutine parameters.""" diff --git a/src/braket/experimental/autoqasm/types/conversions.py b/src/braket/experimental/autoqasm/types/conversions.py index cc43a55a..61b7c0c6 100644 --- a/src/braket/experimental/autoqasm/types/conversions.py +++ b/src/braket/experimental/autoqasm/types/conversions.py @@ -21,6 +21,7 @@ import oqpy from openpulse import ast +from braket.experimental.autoqasm import errors from braket.experimental.autoqasm import types as aq_types @@ -42,12 +43,20 @@ def map_type(python_type: type) -> type: return oqpy.IntVar if issubclass(origin_type, (float, np.floating)): return oqpy.FloatVar - if issubclass(origin_type, list) and issubclass(type_args[0], (int, np.integer)): + if issubclass(origin_type, list): + if not type_args: + raise errors.ParameterTypeError("Please supply a type argument to list.") + + item_type = map_type(type_args[0]) + if not item_type == oqpy.IntVar: + raise errors.ParameterTypeError( + f"Unsupported array type: {item_type}. AutoQASM arrays only support ints." + ) # TODO: Update array length to match the input rather than hardcoding - # OQPY and QASM require arrays have a set length. python doesn't require this, - # so the length of the array is indeterminate. - # At this point we only have access to the _parameter_ (type hint), not the - # _argument_ (concrete value), which is the only place length information is stored + # OQPY and QASM require arrays have a set length. python doesn't require this, + # so the length of the array is indeterminate. + # At this point we only have access to the _parameter_ (type hint), not the + # _argument_ (concrete value), which is the only place length information is stored # Here's where the info is stored for oqpy variables: # ctx = program.get_program_conversion_context() # dims = ctx.get_oqpy_program().declared_vars[name_of_var].dimensions @@ -137,6 +146,12 @@ def _(node: Union[float, np.floating]): return aq_types.FloatVar(node) +@wrap_value.register(list) +def _(node: list): + # TODO: Update array length to match the input rather than hardcoding + return aq_types.ArrayVar(node, dimensions=[10]) + + @wrap_value.register def _(node: oqpy.base.Var): return node diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index fa8f46e2..efedf12a 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -382,7 +382,7 @@ def qasm_simple_condition(bool do_cnot) -> bool { return do_cnot; } qubit[2] __qubits__; -bool __bool_0__ = false; +bool __bool_0__; """ expected += f"__bool_0__ = qasm_simple_condition({'true' if do_cnot else 'false'});" assert qasm_simple_condition_wrapper(do_cnot).to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_converters.py b/test/unit_tests/braket/experimental/autoqasm/test_converters.py index 3fdec50d..9e0de73e 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_converters.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_converters.py @@ -68,7 +68,7 @@ def main(): aq.gates.h(i) break - with pytest.raises(aq.errors.UnsupportedFeature): + with pytest.raises(aq.errors.UnsupportedFeatureError): main() @@ -79,5 +79,5 @@ def uses_while_w_break(): aq.gates.x(0) break - with pytest.raises(aq.errors.UnsupportedFeature): + with pytest.raises(aq.errors.UnsupportedFeatureError): uses_while_w_break() diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index 4af6b194..c700b138 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -86,7 +86,7 @@ def ret_test() -> int[32] { res = 1; return res; } -int[32] __int_1__ = 0; +int[32] __int_1__; __int_1__ = ret_test();""" assert main().to_ir() == expected @@ -110,7 +110,7 @@ def ret_test() -> float[64] { res = 1.0; return res; } -float[64] __float_1__ = 0.0; +float[64] __float_1__; __float_1__ = ret_test();""" assert main().to_ir() == expected @@ -134,7 +134,7 @@ def ret_test() -> bool { res = true; return res; } -bool __bool_1__ = false; +bool __bool_1__; __bool_1__ = ret_test();""" assert main().to_ir() == expected @@ -161,7 +161,7 @@ def add(int[32] a, int[32] b) -> int[32] { int[32] b; a = 5; b = 6; -int[32] __int_2__ = 0; +int[32] __int_2__; __int_2__ = add(a, b);""" assert ret_test().to_ir() == expected @@ -179,7 +179,7 @@ def ret_test() -> None: assert ret_test().to_ir() == expected -def test_return_array(): +def test_return_array_int(): """Test return type discovery of array values.""" @aq.function @@ -203,6 +203,44 @@ def ret_test() -> array[int[32], 3] { assert main().to_ir() == expected +def test_return_python_array(): + """Test returning a python array of ints.""" + + @aq.function + def tester(arr: List[int]) -> List[int]: + return [1, 2, 3] + + @aq.function(num_qubits=4) + def main(): + tester() + + expected = """OPENQASM 3.0; +def tester(array[int[32], 10] arr) -> array[int[32], 10] { + array[int[32], 10] retval_; + retval_ = {1, 2, 3}; + return retval_; +} +array[int[32], 10] __arr_1__ = {}; +qubit[4] __qubits__; +__arr_1__ = tester();""" + assert main().to_ir() == expected + + +def test_return_array_unsupported(): + """Test unsupported array type.""" + + @aq.function + def tester(arr: List[float]) -> List[float]: + return [1.2, 2.1] + + @aq.function(num_qubits=4) + def main(): + tester([3.3]) + + with pytest.raises(aq.errors.ParameterTypeError): + assert main() + + def test_return_func_call(): """Test returning the result of another function call.""" @@ -221,7 +259,7 @@ def helper() -> int[32] { res = 1; return res; } -int[32] __int_1__ = 0; +int[32] __int_1__; __int_1__ = helper();""" assert ret_test().to_ir() == expected @@ -391,7 +429,7 @@ def retval_test() -> int[32] { retval_ = 1; return retval_; } -int[32] __int_1__ = 0; +int[32] __int_1__; __int_1__ = retval_test();""" assert caller().to_ir() == expected_qasm @@ -431,13 +469,13 @@ def retval_recursive() -> int: expected_qasm = """OPENQASM 3.0; def retval_recursive() -> int[32] { int[32] retval_; - int[32] __int_1__ = 0; + int[32] __int_1__; __int_1__ = retval_recursive(); retval_ = 1; return retval_; } int[32] retval_; -int[32] __int_3__ = 0; +int[32] __int_3__; __int_3__ = retval_recursive(); retval_ = 1;""" @@ -456,7 +494,7 @@ def retval_recursive() -> int: def retval_recursive() -> int[32] { int[32] a; int[32] retval_; - int[32] __int_1__ = 0; + int[32] __int_1__; __int_1__ = retval_recursive(); a = __int_1__; retval_ = 1; @@ -464,7 +502,7 @@ def retval_recursive() -> int[32] { } int[32] a; int[32] retval_; -int[32] __int_3__ = 0; +int[32] __int_3__; __int_3__ = retval_recursive(); a = __int_3__; retval_ = 1;""" @@ -490,9 +528,9 @@ def caller() -> int: expected_qasm = """OPENQASM 3.0; def retval_recursive() -> int[32] { - int[32] __int_1__ = 0; + int[32] __int_1__; __int_1__ = retval_recursive(); - int[32] __int_3__ = 0; + int[32] __int_3__; __int_3__ = retval_constant(); return 2 * __int_1__ + (__int_3__ + 2) / 3; } @@ -501,12 +539,34 @@ def retval_constant() -> int[32] { retval_ = 3; return retval_; } -int[32] __int_4__ = 0; +int[32] __int_4__; __int_4__ = retval_recursive();""" assert caller().to_ir() == expected_qasm +def test_recursive_list() -> None: + """Tests recursive subroutines which return a list.""" + + @aq.function + def retval_recursive() -> List[int]: + retval_recursive() + return [1] + + assert "-> array[int[32], 10]" in retval_recursive().to_ir() + + +def test_recursive_oqpy_type() -> None: + """Tests recursive subroutines which returns an oqpy type.""" + + @aq.function + def retval_recursive() -> aq.BitVar: + retval_recursive() + return aq.BitVar(0) + + assert "-> bit" in retval_recursive().to_ir() + + def test_error_for_tuple_param() -> None: """Tuples are not supported as parameters.""" @@ -535,3 +595,113 @@ def main(): with pytest.raises(aq.errors.MissingParameterTypeError): main() + + +def test_ignore_ret_typehint_bool(): + """Test type discovery of boolean return values.""" + + @aq.function + def ret_test() -> List[int]: + return True + + @aq.function + def main() -> bool: + ret_test() + + expected = """OPENQASM 3.0; +def ret_test() -> bool { + bool retval_; + retval_ = true; + return retval_; +} +bool __bool_1__; +__bool_1__ = ret_test();""" + + assert main().to_ir() == expected + + +def test_ignore_ret_typehint_list(): + """Test type discovery of list return values.""" + + @aq.function + def ret_test() -> int: + return [1, 2, 3] + + @aq.function(num_qubits=4) + def main() -> float: + ret_test() + + expected = """OPENQASM 3.0; +def ret_test() -> array[int[32], 10] { + array[int[32], 10] retval_; + retval_ = {1, 2, 3}; + return retval_; +} +array[int[32], 10] __arr_1__ = {}; +qubit[4] __qubits__; +__arr_1__ = ret_test();""" + + assert main().to_ir() == expected + + +def test_ignore_missing_ret_typehint_list(): + """Test type discovery of return types with no annotations.""" + + @aq.function + def ret_test(): + return [1, 2, 3] + + @aq.function(num_qubits=4) + def main(): + ret_test() + + expected = """OPENQASM 3.0; +def ret_test() -> array[int[32], 10] { + array[int[32], 10] retval_; + retval_ = {1, 2, 3}; + return retval_; +} +array[int[32], 10] __arr_1__ = {}; +qubit[4] __qubits__; +__arr_1__ = ret_test();""" + + assert main().to_ir() == expected + + +def test_ignore_missing_ret_typehint_float(): + """Test type discovery of return types with no annotations.""" + + @aq.function + def ret_test(): + return 1.2 + + @aq.function(num_qubits=4) + def main(): + ret_test() + + expected = """OPENQASM 3.0; +def ret_test() -> float[64] { + float[64] retval_; + retval_ = 1.2; + return retval_; +} +qubit[4] __qubits__; +float[64] __float_1__; +__float_1__ = ret_test();""" + + assert main().to_ir() == expected + + +def test_param_array_list_missing_arg(): + """Test list parameter with missing type arg (list rather than list[int]).""" + + @aq.function + def param_test(arr: List) -> int: + return 1 + + @aq.function(num_qubits=4) + def main(): + param_test() + + with pytest.raises(aq.errors.ParameterTypeError): + main() From ec2a3fb60ea1d0ac5bc02e71a666439abeab0fa8 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Fri, 11 Aug 2023 09:41:06 -0400 Subject: [PATCH 0805/1165] docs: add "why" section to AutoQASM readme (#671) --- src/braket/experimental/autoqasm/README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/braket/experimental/autoqasm/README.md b/src/braket/experimental/autoqasm/README.md index 0c016cca..0121a439 100644 --- a/src/braket/experimental/autoqasm/README.md +++ b/src/braket/experimental/autoqasm/README.md @@ -7,9 +7,7 @@ for developing quantum programs. All of the code in the `experimental` module is _experimental_ software. We may change, remove, or deprecate parts of the AutoQASM API without notice. The name AutoQASM is a working title and is -also subject to change. The name is inspired by the -[AutoGraph module of TensorFlow](https://www.tensorflow.org/api_docs/python/tf/autograph), -which we have used as a foundation for this project. +also subject to change. For a fully supported quantum developer experience, please continue to use the rest of the Amazon Braket Python SDK by following @@ -17,6 +15,17 @@ please continue to use the rest of the Amazon Braket Python SDK by following If you are interested in our active development efforts, and you are not afraid of a few bugs, please keep on reading! +## Why AutoQASM? + +AutoQASM provides a Pythonic developer experience for writing quantum programs. The working title "AutoQASM" is derived from the name of the [AutoGraph module of TensorFlow](https://www.tensorflow.org/api_docs/python/tf/autograph). AutoQASM uses AutoGraph to construct quantum assembly (QASM) programs rather than TensorFlow graphs. + +AutoQASM provides a natural interface for expressing quantum programs with mid-circuit measurements and classical control flow using native Python language features. It allows the construction of modular programs consisting of common programming constructs such as loops and subroutines, and it preserves this modularity when serializing the program to OpenQASM. This enables a more imperative programming style than constructing programs via a series of function calls on a circuit object. + +Although it is still a work in progress, the intent is that AutoQASM will support any quantum programming paradigm which falls into the [OpenQASM 3.0](https://openqasm.com) language scope. AutoQASM supports serializing quantum programs to OpenQASM, which allows the programs to interoperate with any library or service that supports OpenQASM programs, such as Amazon Braket. + +See the [Quick Start](#quick-start) section below, as well as the AutoQASM [example notebooks](../../../../examples/autoqasm), for examples of AutoQASM usage. + + ## Installation AutoQASM is an experimental module and is not yet part of the released Amazon Braket SDK. From 68b42417eb4efd4b7067571a5336a09e19ff71c8 Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Fri, 11 Aug 2023 12:05:36 -0400 Subject: [PATCH 0806/1165] alias verbatim class (#672) --- src/braket/experimental/autoqasm/__init__.py | 2 +- src/braket/experimental/autoqasm/program/__init__.py | 2 +- test/unit_tests/braket/experimental/autoqasm/test_pragmas.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/braket/experimental/autoqasm/__init__.py b/src/braket/experimental/autoqasm/__init__.py index 90afcf00..8392f506 100644 --- a/src/braket/experimental/autoqasm/__init__.py +++ b/src/braket/experimental/autoqasm/__init__.py @@ -42,7 +42,7 @@ def my_program(): from .api import function # noqa: F401 from .gates import QubitIdentifierType # noqa: F401 -from .program import Program, Verbatim, build_program # noqa: F401 +from .program import Program, build_program, verbatim # noqa: F401 from .transpiler import transpiler # noqa: F401 from .types import ArrayVar, BitVar, BoolVar, FloatVar, IntVar # noqa: F401 from .types import qasm_range as range # noqa: F401 diff --git a/src/braket/experimental/autoqasm/program/__init__.py b/src/braket/experimental/autoqasm/program/__init__.py index 7b8ed219..c9515984 100644 --- a/src/braket/experimental/autoqasm/program/__init__.py +++ b/src/braket/experimental/autoqasm/program/__init__.py @@ -15,7 +15,7 @@ for AutoQASM. """ -from .pragmas import Verbatim # noqa: F401 +from .pragmas import Verbatim as verbatim # noqa: F401 from .program import ( # noqa: F401 Program, ProgramConversionContext, diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py b/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py index a745fefb..d2fc5fd6 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py @@ -24,7 +24,7 @@ def test_with_verbatim_box() -> None: def program_func() -> None: """User program to test.""" h(0) - with aq.Verbatim(): + with aq.verbatim(): cnot(1, 2) expected = """OPENQASM 3.0; From ad185aa7855f971b613188ef02b2355f27cde446 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 14 Aug 2023 21:33:06 -0700 Subject: [PATCH 0807/1165] docs: add mermaid diagram to describe the CI flow (#647) * docs: add mermaid diagram to describe the CI flow --- CONTRIBUTING.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7bc3046e..1e659a5b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -132,6 +132,34 @@ Please remember to: * 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) + Project setup
(make dev) : Code checkout + : Virtual environment + : Dependencies + : Local branch + : Local changes + : Local tests + + Pre-Pull Request
(make PR) : Code linting (tox -e linters) + : Docs linting (tox -e docs) + : Static typing analysis (covered by the linters) + : Tests (tox -e unit-tests|tox -e integ-tests) + + Pull Request
(CI checks) : Semantic PR title check + : Related issue check + : Acknowledgment check + : Code coverage diff + : Dependencies check + + After merge
(CI checks) : End-to-end tests + : GitHub Actions check + : Rebuild Changelog + : Deploy staging docs + : Prepare release +``` + ## Documentation Guidelines We use reStructuredText (RST) for most of our documentation. For a quick primer on the syntax, From b712636a7686a551420a8d610a6e315e0f70cd88 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 15 Aug 2023 16:12:59 +0000 Subject: [PATCH 0808/1165] prepare release v1.53.4 --- 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 b528e35e..c40b3452 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.53.4 (2023-08-15) + +### Bug Fixes and Other Changes + + * docs: add mermaid diagram to describe the CI flow + ## v1.53.3 (2023-08-08) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d2bc4d05..4ac162ca 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.53.4.dev0" +__version__ = "1.53.4" From b7b5b415698b6c270c09dd1558504cc0b7866bf6 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 15 Aug 2023 16:12:59 +0000 Subject: [PATCH 0809/1165] update development version to v1.53.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 4ac162ca..2931b9ec 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.53.4" +__version__ = "1.53.5.dev0" From 19a7fcff8c075baab21240733a4f8247747ecff5 Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Tue, 15 Aug 2023 13:37:24 -0400 Subject: [PATCH 0810/1165] extend gate set (#673) * extend the gate set * update imports in notebooks * update module doc * modify gate function signature and annotations * explicitly define gates * simplify repeated code for gate definition --- .../1_Getting_started_with_AutoQASM.ipynb | 2 +- .../2_Expressing_classical_control_flow.ipynb | 2 +- .../3_Iterative_phase_estimation.ipynb | 2 +- src/braket/experimental/autoqasm/README.md | 2 +- src/braket/experimental/autoqasm/__init__.py | 4 +- .../experimental/autoqasm/gates/gates.py | 125 ----- .../{gates => instructions}/__init__.py | 16 +- .../autoqasm/instructions/gates.py | 529 ++++++++++++++++++ .../autoqasm/instructions/instructions.py | 36 ++ .../{gates => instructions}/measurements.py | 2 +- .../{gates => instructions}/qubits.py | 0 .../braket/experimental/autoqasm/conftest.py | 2 +- .../braket/experimental/autoqasm/test_api.py | 2 +- .../experimental/autoqasm/test_aq_gates.py | 80 ++- .../experimental/autoqasm/test_operators.py | 2 +- .../experimental/autoqasm/test_pragmas.py | 2 +- .../experimental/autoqasm/test_program.py | 4 +- .../experimental/autoqasm/test_transpiler.py | 2 +- 18 files changed, 667 insertions(+), 147 deletions(-) delete mode 100644 src/braket/experimental/autoqasm/gates/gates.py rename src/braket/experimental/autoqasm/{gates => instructions}/__init__.py (61%) create mode 100644 src/braket/experimental/autoqasm/instructions/gates.py create mode 100644 src/braket/experimental/autoqasm/instructions/instructions.py rename src/braket/experimental/autoqasm/{gates => instructions}/measurements.py (95%) rename src/braket/experimental/autoqasm/{gates => instructions}/qubits.py (100%) diff --git a/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb b/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb index e8dbac3d..03535ddc 100644 --- a/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb +++ b/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb @@ -23,7 +23,7 @@ "# AWS imports: Import Braket SDK modules\n", "from braket.devices.local_simulator import LocalSimulator\n", "import braket.experimental.autoqasm as aq\n", - "from braket.experimental.autoqasm.gates import measure, h, cnot" + "from braket.experimental.autoqasm.instructions import measure, h, cnot" ] }, { diff --git a/examples/autoqasm/2_Expressing_classical_control_flow.ipynb b/examples/autoqasm/2_Expressing_classical_control_flow.ipynb index 26e37a99..beb7c092 100644 --- a/examples/autoqasm/2_Expressing_classical_control_flow.ipynb +++ b/examples/autoqasm/2_Expressing_classical_control_flow.ipynb @@ -24,7 +24,7 @@ "# AWS imports: Import Braket SDK modules\n", "from braket.devices.local_simulator import LocalSimulator\n", "import braket.experimental.autoqasm as aq\n", - "from braket.experimental.autoqasm.gates import measure, h, cnot" + "from braket.experimental.autoqasm.instructions import measure, h, cnot" ] }, { diff --git a/examples/autoqasm/3_Iterative_phase_estimation.ipynb b/examples/autoqasm/3_Iterative_phase_estimation.ipynb index 6408bb0c..912a834d 100644 --- a/examples/autoqasm/3_Iterative_phase_estimation.ipynb +++ b/examples/autoqasm/3_Iterative_phase_estimation.ipynb @@ -26,7 +26,7 @@ "# AWS imports: Import Braket SDK modules\n", "from braket.devices.local_simulator import LocalSimulator\n", "import braket.experimental.autoqasm as aq\n", - "from braket.experimental.autoqasm.gates import measure, x, rz, h, cphaseshift, reset" + "from braket.experimental.autoqasm.instructions import measure, x, rz, h, cphaseshift, reset" ] }, { diff --git a/src/braket/experimental/autoqasm/README.md b/src/braket/experimental/autoqasm/README.md index 0121a439..408ff06a 100644 --- a/src/braket/experimental/autoqasm/README.md +++ b/src/braket/experimental/autoqasm/README.md @@ -46,7 +46,7 @@ model programming paradigm that is also used in the Amazon Braket SDK. First, import the following modules and functions: ``` import braket.experimental.autoqasm as aq -from braket.experimental.autoqasm.gates import h, cnot, measure +from braket.experimental.autoqasm.instructions import h, cnot, measure ``` To create a quantum program using the AutoQASM experience, you decorate a function with `@aq.function`. diff --git a/src/braket/experimental/autoqasm/__init__.py b/src/braket/experimental/autoqasm/__init__.py index 8392f506..b754fb57 100644 --- a/src/braket/experimental/autoqasm/__init__.py +++ b/src/braket/experimental/autoqasm/__init__.py @@ -17,7 +17,7 @@ The basic usage of AutoQASM is as follows: import braket.experimental.autoqasm as aq - from braket.experimental.autoqasm.gates import h, cnot, measure + from braket.experimental.autoqasm.instructions import h, cnot, measure @aq.function def my_program(): @@ -41,7 +41,7 @@ def my_program(): """ from .api import function # noqa: F401 -from .gates import QubitIdentifierType # noqa: F401 +from .instructions import QubitIdentifierType # noqa: F401 from .program import Program, build_program, verbatim # noqa: F401 from .transpiler import transpiler # noqa: F401 from .types import ArrayVar, BitVar, BoolVar, FloatVar, IntVar # noqa: F401 diff --git a/src/braket/experimental/autoqasm/gates/gates.py b/src/braket/experimental/autoqasm/gates/gates.py deleted file mode 100644 index fecad991..00000000 --- a/src/braket/experimental/autoqasm/gates/gates.py +++ /dev/null @@ -1,125 +0,0 @@ -# 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. - - -"""Quantum gates, which are applied to qubits. Some gates take parameters as arguments in addition -to the qubits. - -Example of using a `h` gate and a `cnot` gate to create a Bell circuit: - -.. code-block:: python - - @aq.function - def bell(): - h(0) - cnot(0, 1) -""" - - -from braket.experimental.autoqasm import program - -from .qubits import QubitIdentifierType, _qubit - - -def reset(q: QubitIdentifierType) -> None: - """Adds a reset instruction on a specified qubit. - - Args: - q (QubitIdentifierType): The target qubit. - """ - oqpy_program = program.get_program_conversion_context().get_oqpy_program() - oqpy_program.gate(_qubit(q), "reset") - - -def h(q: QubitIdentifierType) -> None: - """Adds a Hadamard gate to the program on the specified qubit. - - Args: - q (QubitIdentifierType): The target qubit. - """ - oqpy_program = program.get_program_conversion_context().get_oqpy_program() - oqpy_program.gate(_qubit(q), "h") - - -def x(q: QubitIdentifierType) -> None: - """Adds a pi rotation around the X axis on the specified qubit. - - Args: - q (QubitIdentifierType): The target qubit. - """ - oqpy_program = program.get_program_conversion_context().get_oqpy_program() - oqpy_program.gate(_qubit(q), "x") - - -def y(q: QubitIdentifierType) -> None: - """Adds a pi rotation around the Y axis on the specified qubit. - - Args: - q (QubitIdentifierType): The target qubit. - """ - oqpy_program = program.get_program_conversion_context().get_oqpy_program() - oqpy_program.gate(_qubit(q), "y") - - -def z(q: QubitIdentifierType) -> None: - """Adds a pi rotation around the Z axis on the specified qubit. - - Args: - q (QubitIdentifierType): The target qubit. - """ - oqpy_program = program.get_program_conversion_context().get_oqpy_program() - oqpy_program.gate(_qubit(q), "z") - - -def rz(q: QubitIdentifierType, angle: float) -> None: - """Adds a rotation around the Z axis by `angle` on the specified qubit. - - Args: - q (QubitIdentifierType): The target qubit. - angle (float): Angle in radians. - """ - oqpy_program = program.get_program_conversion_context().get_oqpy_program() - oqpy_program.gate(_qubit(q), "rz", angle) - - -def rx(q: QubitIdentifierType, angle: float) -> None: - """Adds a rotation around the X axis by `angle` on the specified qubit. - Args: - q (QubitIdentifierType): The target qubit. - angle (float): Angle in radians. - """ - oqpy_program = program.get_program_conversion_context().get_oqpy_program() - oqpy_program.gate(_qubit(q), "rx", angle) - - -def cnot(q_ctrl: QubitIdentifierType, q_target: QubitIdentifierType) -> None: - """Adds a CNOT gate to the program on the specified qubits. - - Args: - q_ctrl (QubitIdentifierType): The control qubit. - q_target (QubitIdentifierType): The target qubit. - """ - oqpy_program = program.get_program_conversion_context().get_oqpy_program() - oqpy_program.gate([_qubit(q_ctrl), _qubit(q_target)], "cnot") - - -def cphaseshift(q_ctrl: QubitIdentifierType, q_target: QubitIdentifierType, angle: float) -> None: - """Adds a CPhaseShift gate to the program on the specified qubits. - - Args: - q_ctrl (QubitIdentifierType): The control qubit. - q_target (QubitIdentifierType): The target qubit. - angle (float): Phase in radians. - """ - oqpy_program = program.get_program_conversion_context().get_oqpy_program() - oqpy_program.gate([_qubit(q_ctrl), _qubit(q_target)], "cphaseshift", angle) diff --git a/src/braket/experimental/autoqasm/gates/__init__.py b/src/braket/experimental/autoqasm/instructions/__init__.py similarity index 61% rename from src/braket/experimental/autoqasm/gates/__init__.py rename to src/braket/experimental/autoqasm/instructions/__init__.py index d0014113..4da36596 100644 --- a/src/braket/experimental/autoqasm/gates/__init__.py +++ b/src/braket/experimental/autoqasm/instructions/__init__.py @@ -11,7 +11,19 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -"""Quantum operations for use in AutoQASM programs.""" +"""Instructions that apply to qubits, including quantum gates, reset and measure. -from .gates import QubitIdentifierType, cnot, cphaseshift, h, reset, rx, rz, x, y, z # noqa: F401 +Example of using a `h` gate and a `cnot` gate to create a Bell circuit: + +.. code-block:: python + + @aq.function + def bell(): + h(0) + cnot(0, 1) + measure([0, 1]) +""" + +from .gates import * # noqa: F401, F403 +from .instructions import QubitIdentifierType, reset # noqa: F401 from .measurements import measure # noqa: F401 diff --git a/src/braket/experimental/autoqasm/instructions/gates.py b/src/braket/experimental/autoqasm/instructions/gates.py new file mode 100644 index 00000000..1a4ae82a --- /dev/null +++ b/src/braket/experimental/autoqasm/instructions/gates.py @@ -0,0 +1,529 @@ +# 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. + + +"""Quantum gates, unitary instructions, that apply to qubits. +""" + +from .instructions import _qubit_instruction +from .qubits import QubitIdentifierType + + +def ccnot( + control_0: QubitIdentifierType, + control_1: QubitIdentifierType, + target: QubitIdentifierType, +) -> None: + """CCNOT gate or Toffoli gate. + + Args: + control_0 (QubitIdentifierType): Control qubit 0. + control_1 (QubitIdentifierType): Control qubit 1. + target (QubitIdentifierType): Target qubit. + + """ + _qubit_instruction("ccnot", [control_0, control_1, target]) + + +def cnot( + control: QubitIdentifierType, + target: QubitIdentifierType, +) -> None: + """Controlled NOT gate. + + Args: + control (QubitIdentifierType): Control qubit. + target (QubitIdentifierType): Target qubit. + + """ + _qubit_instruction("cnot", [control, target]) + + +def cphaseshift( + control: QubitIdentifierType, + target: QubitIdentifierType, + angle: float, +) -> None: + """Controlled phase shift gate. + + Args: + control (QubitIdentifierType): Control qubit. + target (QubitIdentifierType): Target qubit. + angle (float): Rotation angle in radians. + + """ + _qubit_instruction("cphaseshift", [control, target], angle) + + +def cphaseshift00( + control: QubitIdentifierType, + target: QubitIdentifierType, + angle: float, +) -> None: + """Controlled phase shift gate for phasing the \\|00> state. + + Args: + control (QubitIdentifierType): Control qubit. + target (QubitIdentifierType): Target qubit. + angle (float): Rotation angle in radians. + + """ + _qubit_instruction("cphaseshift00", [control, target], angle) + + +def cphaseshift01( + control: QubitIdentifierType, + target: QubitIdentifierType, + angle: float, +) -> None: + """Controlled phase shift gate for phasing the \\|01> state. + + Args: + control (QubitIdentifierType): Control qubit. + target (QubitIdentifierType): Target qubit. + angle (float): Rotation angle in radians. + + """ + _qubit_instruction("cphaseshift01", [control, target], angle) + + +def cphaseshift10( + control: QubitIdentifierType, + target: QubitIdentifierType, + angle: float, +) -> None: + """Controlled phase shift gate for phasing the \\|10> state. + + Args: + control (QubitIdentifierType): Control qubit. + target (QubitIdentifierType): Target qubit. + angle (float): Rotation angle in radians. + + """ + _qubit_instruction("cphaseshift10", [control, target], angle) + + +def cswap( + control: QubitIdentifierType, + target_0: QubitIdentifierType, + target_1: QubitIdentifierType, +) -> None: + """Controlled Swap gate. + + Args: + control (QubitIdentifierType): Control qubit. + target_0 (QubitIdentifierType): Target qubit 0. + target_1 (QubitIdentifierType): Target qubit 1. + + """ + _qubit_instruction("cswap", [control, target_0, target_1]) + + +def cv( + control: QubitIdentifierType, + target: QubitIdentifierType, +) -> None: + """Controlled Sqrt of NOT gate. + + Args: + control_0 (QubitIdentifierType): Control qubit 0. + target_0 (QubitIdentifierType): Target qubit 0. + + """ + _qubit_instruction("cv", [control, target]) + + +def cy( + control: QubitIdentifierType, + target: QubitIdentifierType, +) -> None: + """Controlled Pauli-Y gate. + + Args: + control (QubitIdentifierType): Control qubit. + target (QubitIdentifierType): Target qubit. + + """ + _qubit_instruction("cy", [control, target]) + + +def cz( + control: QubitIdentifierType, + target: QubitIdentifierType, +) -> None: + """Controlled Pauli-Z gate. + + Args: + control_0 (QubitIdentifierType): Control qubit. + target_0 (QubitIdentifierType): Target qubit. + + """ + _qubit_instruction("cz", [control, target]) + + +def ecr( + target_0: QubitIdentifierType, + target_1: QubitIdentifierType, +) -> None: + """An echoed RZX(pi/2) gate. + + Args: + target_0 (QubitIdentifierType): Target qubit 0. + target_1 (QubitIdentifierType): Target qubit 1. + + """ + _qubit_instruction("ecr", [target_0, target_1]) + + +def gpi( + target: QubitIdentifierType, + angle: float, +) -> None: + """IonQ GPi gate. + + Args: + target (QubitIdentifierType): Target qubit. + angle (float): Rotation angle in radians. + + """ + _qubit_instruction("gpi", [target], angle) + + +def gpi2( + target: QubitIdentifierType, + angle: float, +) -> None: + """IonQ GPi2 gate. + + Args: + target (QubitIdentifierType): Target qubit. + angle (float): Rotation angle in radians. + + """ + _qubit_instruction("gpi2", [target], angle) + + +def h( + target: QubitIdentifierType, +) -> None: + """Hadamard gate. + + Args: + target (QubitIdentifierType): Target qubit. + + """ + _qubit_instruction("h", [target]) + + +def i( + target: QubitIdentifierType, +) -> None: + """Identity gate. + + Args: + target (QubitIdentifierType): Target qubit. + + """ + _qubit_instruction("i", [target]) + + +def iswap( + target_0: QubitIdentifierType, + target_1: QubitIdentifierType, +) -> None: + """ISwap gate. + + Args: + target_0 (QubitIdentifierType): Target qubit 0. + target_1 (QubitIdentifierType): Target qubit 1. + + """ + _qubit_instruction("iswap", [target_0, target_1]) + + +def ms( + target_0: QubitIdentifierType, + target_1: QubitIdentifierType, + angle_0: float, + angle_1: float, + angle_2: float, +) -> None: + """IonQ Mølmer-Sørenson gate. + + Args: + target_0 (QubitIdentifierType): Target qubit 0. + target_1 (QubitIdentifierType): Target qubit 1. + angle_0 (float): Rotation angle 0 in radians. + angle_1 (float): Rotation angle 1 in radians. + angle_2 (float): Rotation angle 2 in radians. + + """ + _qubit_instruction("ms", [target_0, target_1], angle_0, angle_1, angle_2) + + +def phaseshift( + target: QubitIdentifierType, + angle: float, +) -> None: + """Phase shift gate. + + Args: + target (QubitIdentifierType): Target qubit. + angle (float): Rotation angle in radians. + + """ + _qubit_instruction("phaseshift", [target], angle) + + +def pswap( + target_0: QubitIdentifierType, + target_1: QubitIdentifierType, + angle: float, +) -> None: + """PSwap gate. + + Args: + target_0 (QubitIdentifierType): Target qubit 0. + target_1 (QubitIdentifierType): Target qubit 1. + angle (float): Rotation angle in radians. + + """ + _qubit_instruction("pswap", [target_0, target_1], angle) + + +def rx( + target: QubitIdentifierType, + angle: float, +) -> None: + """X-axis rotation gate. + + Args: + target (QubitIdentifierType): Target qubit. + angle (float): Rotation angle in radians. + + """ + _qubit_instruction("rx", [target], angle) + + +def ry( + target: QubitIdentifierType, + angle: float, +) -> None: + """Y-axis rotation gate. + + Args: + target (QubitIdentifierType): Target qubit. + angle (float): Rotation angle in radians. + + """ + _qubit_instruction("ry", [target], angle) + + +def rz( + target: QubitIdentifierType, + angle: float, +) -> None: + """Z-axis rotation gate. + + Args: + target (QubitIdentifierType): Target qubit. + angle (float): Rotation angle in radians. + + """ + _qubit_instruction("rz", [target], angle) + + +def s( + target: QubitIdentifierType, +) -> None: + """S gate. + + Args: + target (QubitIdentifierType): Target qubit. + + """ + _qubit_instruction("s", [target]) + + +def si( + target: QubitIdentifierType, +) -> None: + """Conjugate transpose of S gate. + + Args: + target (QubitIdentifierType): Target qubit. + + """ + _qubit_instruction("si", [target]) + + +def swap( + target_0: QubitIdentifierType, + target_1: QubitIdentifierType, +) -> None: + """Swap gate. + + Args: + target_0 (QubitIdentifierType): Target qubit 0. + target_1 (QubitIdentifierType): Target qubit 1. + + """ + _qubit_instruction("swap", [target_0, target_1]) + + +def t( + target: QubitIdentifierType, +) -> None: + """T gate. + + Args: + target (QubitIdentifierType): Target qubit. + + """ + _qubit_instruction("t", [target]) + + +def ti( + target: QubitIdentifierType, +) -> None: + """Conjugate transpose of T gate. + + Args: + target (QubitIdentifierType): Target qubit. + + """ + _qubit_instruction("ti", [target]) + + +def v( + target: QubitIdentifierType, +) -> None: + """Square root of not gate. + + Args: + target (QubitIdentifierType): Target qubit. + + """ + _qubit_instruction("v", [target]) + + +def vi( + target: QubitIdentifierType, +) -> None: + """Conjugate transpose of square root of not gate. + + Args: + target (QubitIdentifierType): Target qubit. + + """ + _qubit_instruction("vi", [target]) + + +def x( + target: QubitIdentifierType, +) -> None: + """Pauli-X gate. + + Args: + target (QubitIdentifierType): Target qubit. + + """ + _qubit_instruction("x", [target]) + + +def xx( + target_0: QubitIdentifierType, + target_1: QubitIdentifierType, + angle: float, +) -> None: + """Ising XX coupling gate. + + Args: + target_0 (QubitIdentifierType): Target qubit 0. + target_1 (QubitIdentifierType): Target qubit 1. + angle (float): Rotation angle in radians. + + """ + _qubit_instruction("xx", [target_0, target_1], angle) + + +def xy( + target_0: QubitIdentifierType, + target_1: QubitIdentifierType, + angle: float, +) -> None: + """XY gates + + Args: + target_0 (QubitIdentifierType): Target qubit 0. + target_1 (QubitIdentifierType): Target qubit 1. + angle (float): Rotation angle in radians. + + """ + _qubit_instruction("xy", [target_0, target_1], angle) + + +def y( + target: QubitIdentifierType, +) -> None: + """Pauli-Y gate. + + Args: + target (QubitIdentifierType): Target qubit. + + """ + _qubit_instruction("y", [target]) + + +def yy( + target_0: QubitIdentifierType, + target_1: QubitIdentifierType, + angle: float, +) -> None: + """Ising YY coupling gate. + + Args: + target_0 (QubitIdentifierType): Target qubit 0. + target_1 (QubitIdentifierType): Target qubit 1. + angle (float): Rotation angle in radians. + + """ + _qubit_instruction("yy", [target_0, target_1], angle) + + +def z( + target: QubitIdentifierType, +) -> None: + """Pauli-Z gate. + + Args: + target (QubitIdentifierType): Target qubit. + + """ + _qubit_instruction("z", [target]) + + +def zz( + target_0: QubitIdentifierType, + target_1: QubitIdentifierType, + angle: float, +) -> None: + """Ising ZZ coupling gate. + + Args: + target_0 (QubitIdentifierType): Target qubit 0. + target_1 (QubitIdentifierType): Target qubit 1. + angle (float): Rotation angle in radians. + + """ + _qubit_instruction("zz", [target_0, target_1], angle) diff --git a/src/braket/experimental/autoqasm/instructions/instructions.py b/src/braket/experimental/autoqasm/instructions/instructions.py new file mode 100644 index 00000000..058f371c --- /dev/null +++ b/src/braket/experimental/autoqasm/instructions/instructions.py @@ -0,0 +1,36 @@ +# 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. + + +"""Non-unitary instructions that apply to qubits. +""" + +from typing import Any, List + +from braket.experimental.autoqasm import program + +from .qubits import QubitIdentifierType, _qubit + + +def _qubit_instruction(name: str, qubits: List[QubitIdentifierType], *args: Any): + oqpy_program = program.get_program_conversion_context().get_oqpy_program() + oqpy_program.gate([_qubit(q) for q in qubits], name, *args) + + +def reset(target: QubitIdentifierType) -> None: + """Adds a reset instruction on a specified qubit. + + Args: + target (QubitIdentifierType): The target qubit. + """ + _qubit_instruction("reset", [target]) diff --git a/src/braket/experimental/autoqasm/gates/measurements.py b/src/braket/experimental/autoqasm/instructions/measurements.py similarity index 95% rename from src/braket/experimental/autoqasm/gates/measurements.py rename to src/braket/experimental/autoqasm/instructions/measurements.py index ec6123cf..8b8ad22d 100644 --- a/src/braket/experimental/autoqasm/gates/measurements.py +++ b/src/braket/experimental/autoqasm/instructions/measurements.py @@ -27,7 +27,7 @@ def my_program(): from braket.experimental.autoqasm import program from braket.experimental.autoqasm import types as aq_types -from braket.experimental.autoqasm.gates.qubits import QubitIdentifierType, _qubit +from braket.experimental.autoqasm.instructions.qubits import QubitIdentifierType, _qubit def measure(qubits: Union[QubitIdentifierType, List[QubitIdentifierType]]) -> aq_types.BitVar: diff --git a/src/braket/experimental/autoqasm/gates/qubits.py b/src/braket/experimental/autoqasm/instructions/qubits.py similarity index 100% rename from src/braket/experimental/autoqasm/gates/qubits.py rename to src/braket/experimental/autoqasm/instructions/qubits.py diff --git a/test/unit_tests/braket/experimental/autoqasm/conftest.py b/test/unit_tests/braket/experimental/autoqasm/conftest.py index 8ab1e9d8..cb8b95f7 100644 --- a/test/unit_tests/braket/experimental/autoqasm/conftest.py +++ b/test/unit_tests/braket/experimental/autoqasm/conftest.py @@ -18,7 +18,7 @@ import pytest import braket.experimental.autoqasm as aq -from braket.experimental.autoqasm.gates import cnot, h +from braket.experimental.autoqasm.instructions import cnot, h @pytest.fixture diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index efedf12a..539f10fc 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -22,7 +22,7 @@ from braket.default_simulator import StateVectorSimulator from braket.devices.local_simulator import LocalSimulator from braket.experimental.autoqasm import errors -from braket.experimental.autoqasm.gates import cnot, h, measure, x +from braket.experimental.autoqasm.instructions import cnot, h, measure, x from braket.tasks.local_quantum_task import LocalQuantumTask diff --git a/test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py b/test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py index 7de73894..3f30c2fc 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py @@ -16,7 +16,46 @@ import pytest import braket.experimental.autoqasm as aq -from braket.experimental.autoqasm.gates import cnot, cphaseshift, h, measure, reset, rx, rz, x, y, z +from braket.experimental.autoqasm.instructions import ( + ccnot, + cnot, + cphaseshift, + cphaseshift00, + cphaseshift01, + cphaseshift10, + cswap, + cv, + cy, + cz, + ecr, + gpi, + gpi2, + h, + i, + iswap, + measure, + ms, + phaseshift, + pswap, + reset, + rx, + ry, + rz, + s, + si, + swap, + t, + ti, + v, + vi, + x, + xx, + xy, + y, + yy, + z, + zz, +) def test_bell_state_prep(bell_state_program) -> None: @@ -41,10 +80,12 @@ def test_physical_q_bell_state_prep(physical_bell_program) -> None: def test_bell_with_measure() -> None: """Tests Bell state with measurement result stored in an undeclared variable.""" with aq.build_program() as program_conversion_context: + reset(0) h(0) cnot(0, 1) measure(0) expected = """OPENQASM 3.0; +reset __qubits__[0]; h __qubits__[0]; cnot __qubits__[0], __qubits__[1]; bit __bit_0__; @@ -57,15 +98,42 @@ def test_bell_with_measure() -> None: @pytest.mark.parametrize( "gate,qubits,params,expected_qasm", [ + (ccnot, [0, 1, 2], [], "\nccnot __qubits__[0], __qubits__[1], __qubits__[2];"), + (cnot, [0, 1], [], "\ncnot __qubits__[0], __qubits__[1];"), + (cphaseshift, [0, 1], [0.1], "\ncphaseshift(0.1) __qubits__[0], __qubits__[1];"), + (cphaseshift00, [0, 1], [0.1], "\ncphaseshift00(0.1) __qubits__[0], __qubits__[1];"), + (cphaseshift01, [0, 1], [0.1], "\ncphaseshift01(0.1) __qubits__[0], __qubits__[1];"), + (cphaseshift10, [0, 1], [0.1], "\ncphaseshift10(0.1) __qubits__[0], __qubits__[1];"), + (cswap, [0, 1, 2], [], "\ncswap __qubits__[0], __qubits__[1], __qubits__[2];"), + (cv, [0, 1], [], "\ncv __qubits__[0], __qubits__[1];"), + (cy, [0, 1], [], "\ncy __qubits__[0], __qubits__[1];"), + (cz, [0, 1], [], "\ncz __qubits__[0], __qubits__[1];"), + (ecr, [0, 1], [], "\necr __qubits__[0], __qubits__[1];"), + (gpi, [0], [0.1], "\ngpi(0.1) __qubits__[0];"), + (gpi2, [0], [0.1], "\ngpi2(0.1) __qubits__[0];"), (h, [0], [], "\nh __qubits__[0];"), + (i, [0], [], "\ni __qubits__[0];"), + (iswap, [0, 1], [], "\niswap __qubits__[0], __qubits__[1];"), + (ms, [0, 1], [0.1, 0.2, 0.3], "\nms(0.1, 0.2, 0.3) __qubits__[0], __qubits__[1];"), + (phaseshift, [0], [0.1], "\nphaseshift(0.1) __qubits__[0];"), + (pswap, [0, 1], [0.1], "\npswap(0.1) __qubits__[0], __qubits__[1];"), + (rx, [0], [0.1], "\nrx(0.1) __qubits__[0];"), + (ry, [0], [0.1], "\nry(0.1) __qubits__[0];"), + (rz, [0], [0.1], "\nrz(0.1) __qubits__[0];"), + (s, [0], [], "\ns __qubits__[0];"), + (si, [0], [], "\nsi __qubits__[0];"), + (swap, [0, 1], [], "\nswap __qubits__[0], __qubits__[1];"), + (t, [0], [], "\nt __qubits__[0];"), + (ti, [0], [], "\nti __qubits__[0];"), + (v, [0], [], "\nv __qubits__[0];"), + (vi, [0], [], "\nvi __qubits__[0];"), (x, [0], [], "\nx __qubits__[0];"), + (xx, [0, 1], [0.1], "\nxx(0.1) __qubits__[0], __qubits__[1];"), + (xy, [0, 1], [0.1], "\nxy(0.1) __qubits__[0], __qubits__[1];"), (y, [0], [], "\ny __qubits__[0];"), + (yy, [0, 1], [0.1], "\nyy(0.1) __qubits__[0], __qubits__[1];"), (z, [0], [], "\nz __qubits__[0];"), - (rz, [0], [0.1], "\nrz(0.1) __qubits__[0];"), - (reset, [0], [], "\nreset __qubits__[0];"), - (cnot, [0, 1], [], "\ncnot __qubits__[0], __qubits__[1];"), - (cphaseshift, [0, 1], [0.1], "\ncphaseshift(0.1) __qubits__[0], __qubits__[1];"), - (rx, [0], [0.1], "\nrx(0.1) __qubits__[0];"), + (zz, [0, 1], [0.1], "\nzz(0.1) __qubits__[0], __qubits__[1];"), ], ) def test_gates(gate, qubits, params, expected_qasm) -> None: diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py index 9f93fb06..7f4999dd 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_operators.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py @@ -20,7 +20,7 @@ import braket.experimental.autoqasm as aq from braket.experimental.autoqasm.errors import UnsupportedConditionalExpressionError -from braket.experimental.autoqasm.gates import cnot, h, measure, x +from braket.experimental.autoqasm.instructions import cnot, h, measure, x @pytest.fixture diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py b/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py index d2fc5fd6..ac5337f9 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py @@ -14,7 +14,7 @@ """Tests for pragmas.""" import braket.experimental.autoqasm as aq -from braket.experimental.autoqasm.gates import cnot, h +from braket.experimental.autoqasm.instructions import cnot, h def test_with_verbatim_box() -> None: diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/braket/experimental/autoqasm/test_program.py index 6a5f17d9..381c9502 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_program.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_program.py @@ -21,7 +21,7 @@ import braket.experimental.autoqasm as aq from braket.circuits.serialization import IRType -from braket.experimental.autoqasm.gates import cnot, measure, rx +from braket.experimental.autoqasm.instructions import cnot, measure, rx def test_program_conversion_context() -> None: @@ -57,7 +57,7 @@ def test_build_program() -> None: def test_to_ir() -> None: """Tests that an appropriate error is raised for unsupported ir_types.""" with aq.build_program() as program_conversion_context: - aq.gates.h(0) + aq.instructions.h(0) prog = program_conversion_context.make_program() # No error for OpenQASM prog.to_ir(IRType.OPENQASM) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py b/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py index 4217dc54..e10ec295 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py @@ -20,7 +20,7 @@ import braket.experimental.autoqasm as aq from braket.experimental.autoqasm.autograph import ag_logging from braket.experimental.autoqasm.autograph.core.ag_ctx import ControlStatusCtx, Status -from braket.experimental.autoqasm.gates import cnot, h, measure, x +from braket.experimental.autoqasm.instructions import cnot, h, measure, x def test_convert_invalid_object() -> None: From 0d3dedf1b8939dbbb6b95e52dfde453b5e4a036e Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 16 Aug 2023 10:45:33 -0700 Subject: [PATCH 0811/1165] feat: enable gate calibrations on supported devices (#670) * feat: enable gate calibrations on supported devices --------- Co-authored-by: Abe Coull --- setup.py | 6 +- src/braket/aws/aws_device.py | 155 ++++++- src/braket/aws/aws_quantum_task.py | 24 +- src/braket/circuits/__init__.py | 1 + src/braket/circuits/angled_gate.py | 18 + src/braket/circuits/circuit.py | 176 +++++++- src/braket/circuits/gate.py | 3 + src/braket/circuits/gate_calibrations.py | 161 ++++++++ src/braket/circuits/gates.py | 3 + .../parametric/free_parameter_expression.py | 18 + src/braket/pulse/ast/approximation_parser.py | 2 + src/braket/pulse/pulse_sequence.py | 75 ++++ src/braket/pulse/waveforms.py | 78 +++- test/integ_tests/test_device_creation.py | 6 + test/integ_tests/test_pulse.py | 60 ++- .../braket/aws/common_test_utils.py | 10 + test/unit_tests/braket/aws/test_aws_device.py | 384 +++++++++++++++++- .../braket/circuits/test_angled_gate.py | 20 + .../braket/circuits/test_circuit.py | 324 ++++++++++++++- .../braket/circuits/test_gate_calibration.py | 148 +++++++ test/unit_tests/braket/circuits/test_gates.py | 6 + .../pulse/ast/test_approximation_parser.py | 11 + .../braket/pulse/test_pulse_sequence.py | 83 ++++ .../unit_tests/braket/pulse/test_waveforms.py | 65 +++ 24 files changed, 1787 insertions(+), 50 deletions(-) create mode 100644 src/braket/circuits/gate_calibrations.py create mode 100644 test/unit_tests/braket/circuits/test_gate_calibration.py diff --git a/setup.py b/setup.py index bca0b392..25fca825 100644 --- a/setup.py +++ b/setup.py @@ -27,9 +27,9 @@ packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "amazon-braket-schemas>=1.18.0", - "amazon-braket-default-simulator>=1.19.0", - "oqpy~=0.1.1", + "amazon-braket-schemas>=1.19.1", + "amazon-braket-default-simulator>=1.19.1", + "oqpy~=0.2.1", "setuptools", "backoff", "boltons", diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 51fd40ce..a34facac 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -13,11 +13,13 @@ from __future__ import annotations +import importlib import json import os +import urllib.request from datetime import datetime from enum import Enum -from typing import Dict, List, Optional, Union +from typing import Dict, List, Optional, Tuple, Union from botocore.errorfactory import ClientError from networkx import DiGraph, complete_graph, from_edgelist @@ -27,7 +29,8 @@ 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.circuits import Circuit +from braket.circuits import Circuit, Gate, QubitSet +from braket.circuits.gate_calibrations import GateCalibrations 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 @@ -36,7 +39,10 @@ from braket.devices.device import Device from braket.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQasmProgram -from braket.pulse import Frame, Port, PulseSequence +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 @@ -62,6 +68,14 @@ class AwsDevice(Device): _GET_DEVICES_ORDER_BY_KEYS = frozenset({"arn", "name", "type", "provider_name", "status"}) + _RIGETTI_GATES_TO_BRAKET = { + # Rx_12 does not exist in the Braket SDK, it is a gate between |1> and |2>. + "Rx_12": None, + "Cz": "CZ", + "Cphaseshift": "CPhaseShift", + "Xy": "XY", + } + def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): """ Args: @@ -80,6 +94,7 @@ def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): """ super().__init__(name=None, status=None) self._arn = arn + self._gate_calibrations = None self._properties = None self._provider_name = None self._poll_interval_seconds = None @@ -103,6 +118,7 @@ def run( 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, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTask: @@ -126,6 +142,11 @@ 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: {}. + gate_definitions (Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]): A + `Dict[Tuple[Gate, QubitSet], PulseSequence]]` for a user defined gate calibration. + The calibration is defined for a particular `Gate` on a particular `QubitSet` + and is represented by a `PulseSequence`. + Default: None. Returns: AwsQuantumTask: An AwsQuantumTask that tracks the execution on the device. @@ -174,6 +195,7 @@ def run( poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds or self._poll_interval_seconds, inputs=inputs, + gate_definitions=gate_definitions, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) @@ -207,6 +229,7 @@ def run_batch( 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, + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTaskBatch: @@ -234,6 +257,11 @@ 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. Default: {}. + gate_definitions (Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]): A + `Dict[Tuple[Gate, QubitSet], PulseSequence]]` for a user defined gate calibration. + The calibration is defined for a particular `Gate` on a particular `QubitSet` + and is represented by a `PulseSequence`. + Default: None. Returns: AwsQuantumTaskBatch: A batch containing all of the tasks run @@ -258,6 +286,7 @@ def run_batch( poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds or self._poll_interval_seconds, inputs=inputs, + gate_definitions=gate_definitions, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) @@ -349,6 +378,20 @@ def arn(self) -> str: """str: Return the ARN of the device""" return self._arn + @property + def gate_calibrations(self) -> Optional[GateCalibrations]: + """ + 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: + Optional[GateCalibrations]: The calibration object. Returns `None` if the data + is not present. + """ + if not self._gate_calibrations: + self._gate_calibrations = self.refresh_gate_calibrations() + return self._gate_calibrations + @property def is_available(self) -> bool: """Returns true if the device is currently available. @@ -365,10 +408,7 @@ def is_available(self) -> bool: weekday = current_datetime_utc.weekday() current_time_utc = current_datetime_utc.time().replace(microsecond=0) - if ( - execution_window.windowEndHour < execution_window.windowStartHour - and current_time_utc < execution_window.windowEndHour - ): + if current_time_utc < execution_window.windowEndHour < execution_window.windowStartHour: weekday = (weekday - 1) % 7 matched_day = execution_window.executionDay == ExecutionDay.EVERYDAY @@ -612,6 +652,9 @@ def get_device_region(device_arn: str) -> str: Args: device_arn (str): The device ARN. + Raises: + ValueError: Raised if the ARN is not properly formatted + Returns: str: the region of the ARN. """ @@ -622,3 +665,101 @@ def get_device_region(device_arn: str) -> str: 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'" ) + + def refresh_gate_calibrations(self) -> Optional[GateCalibrations]: + """ + Refreshes the gate calibration data upon request. + + If the device does not have calibration data, None is returned. + + Raises: + URLError: If the URL provided returns a non 2xx response. + + Returns: + Optional[GateCalibrations]: the calibration data for the device. None + is returned if the device does not have a gate calibrations URL associated. + """ + if ( + hasattr(self.properties, "pulse") + and hasattr(self.properties.pulse, "nativeGateCalibrationsRef") + and self.properties.pulse.nativeGateCalibrationsRef + ): + try: + with urllib.request.urlopen( + self.properties.pulse.nativeGateCalibrationsRef.split("?")[0] + ) as f: + json_calibration_data = self._parse_calibration_json( + json.loads(f.read().decode("utf-8")) + ) + return GateCalibrations(json_calibration_data) + except urllib.error.URLError: + raise urllib.error.URLError( + f"Unable to reach {self.properties.pulse.nativeGateCalibrationsRef}" + ) + else: + return None + + def _parse_waveforms(self, waveforms_json: Dict) -> Dict: + waveforms = dict() + for waveform in waveforms_json: + parsed_waveform = _parse_waveform_from_calibration_schema(waveforms_json[waveform]) + waveforms[parsed_waveform.id] = parsed_waveform + return waveforms + + def _parse_pulse_sequence( + self, calibration: Dict, waveforms: Dict[ArbitraryWaveform] + ) -> PulseSequence: + return PulseSequence._parse_from_calibration_schema(calibration, waveforms, self.frames) + + 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 + corresponding `Dict[Tuple[Gate, QubitSet], PulseSequence]` to represent the calibration data. + + Args: + calibration_data (Dict): The data to be parsed. Based on + https://github.com/aws/amazon-braket-schemas-python/blob/main/src/braket/device_schema/pulse/native_gate_calibrations_v1.py. + + Returns: + Dict[Tuple[Gate, QubitSet], PulseSequence]: The + structured data based on a mapping of `Tuple[Gate, Qubit]` to its calibration repesented as a + `PulseSequence`. + + """ # noqa: E501 + waveforms = self._parse_waveforms(calibration_data["waveforms"]) + parsed_calibration_data = {} + for qubit_node in calibration_data["gates"]: + qubit = calibration_data["gates"][qubit_node] + for gate_node in qubit: + for gate in qubit[gate_node]: + gate_capitalized = getattr( + self, + f"_{self.provider_name.upper()}_GATES_TO_BRAKET", + {}, + ).get(gate_node.capitalize(), gate_node.capitalize()) + gate_obj = ( + getattr(importlib.import_module("braket.circuits.gates"), gate_capitalized) + if gate_capitalized is not None + else None + ) + qubits = QubitSet([int(x) for x in gate["qubits"]]) + if gate_obj is None: + # We drop out gates that are not implemented in the BDK + continue + + argument = None + if gate["arguments"]: + argument = ( + float(gate["arguments"][0]) + if _is_float(gate["arguments"][0]) + else FreeParameter(gate["arguments"][0]) + ) + gate_qubit_key = ( + (gate_obj(argument), qubits) if argument else (gate_obj(), qubits) + ) + gate_qubit_pulse = self._parse_pulse_sequence(gate["calibrations"], waveforms) + parsed_calibration_data[gate_qubit_key] = gate_qubit_pulse + + return parsed_calibration_data diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 7c65c57f..76fa3d56 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, Dict, Union +from typing import Any, Dict, Optional, Tuple, Union import boto3 @@ -25,7 +25,7 @@ from braket.annealing.problem import Problem from braket.aws.aws_session import AwsSession from braket.circuits import Instruction -from braket.circuits.circuit import Circuit +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 @@ -103,6 +103,7 @@ def create( disable_qubit_rewiring: bool = False, tags: Dict[str, str] = None, inputs: Dict[str, float] = None, + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None, *args, **kwargs, ) -> AwsQuantumTask: @@ -143,6 +144,12 @@ 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]]): + 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`. + Default: None. + Returns: AwsQuantumTask: AwsQuantumTask tracking the task execution on the device. @@ -187,6 +194,7 @@ def create( device_parameters or {}, disable_qubit_rewiring, inputs, + gate_definitions=gate_definitions, *args, **kwargs, ) @@ -473,6 +481,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]], *args, **kwargs, ) -> AwsQuantumTask: @@ -488,6 +497,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]], *args, **kwargs, ) -> AwsQuantumTask: @@ -505,6 +515,7 @@ def _( device_parameters: Union[dict, BraketSchemaBase], _disable_qubit_rewiring: bool, inputs: Dict[str, float], + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: @@ -543,6 +554,7 @@ def _( _device_parameters: Union[dict, BraketSchemaBase], _disable_qubit_rewiring: bool, inputs: Dict[str, float], + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: @@ -560,6 +572,7 @@ def _( device_parameters: Union[dict, BraketSchemaBase], disable_qubit_rewiring: bool, inputs: Dict[str, float], + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: @@ -580,6 +593,7 @@ def _( if ( disable_qubit_rewiring or Instruction(StartVerbatimBox()) in circuit.instructions + or gate_definitions is not None or any(isinstance(instruction.operator, PulseGate) for instruction in circuit.instructions) ): qubit_reference_type = QubitReferenceType.PHYSICAL @@ -589,7 +603,9 @@ def _( ) openqasm_program = circuit.to_ir( - ir_type=IRType.OPENQASM, serialization_properties=serialization_properties + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + gate_definitions=gate_definitions, ) if inputs: @@ -624,6 +640,7 @@ def _( ], _, inputs: Dict[str, float], + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: @@ -648,6 +665,7 @@ def _( device_parameters: dict, _, inputs: Dict[str, float], + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py index a6e185d9..d2788746 100644 --- a/src/braket/circuits/__init__.py +++ b/src/braket/circuits/__init__.py @@ -27,6 +27,7 @@ from braket.circuits.free_parameter import FreeParameter # noqa: F401 from braket.circuits.free_parameter_expression import FreeParameterExpression # noqa: F401 from braket.circuits.gate import Gate # noqa: F401 +from braket.circuits.gate_calibrations import GateCalibrations # noqa: F401 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 diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index 4c148612..f45bf616 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -119,6 +119,9 @@ def __eq__(self, other): def __repr__(self): return f"{self.name}('angle': {self.angle}, 'qubit_count': {self.qubit_count})" + def __hash__(self): + return hash((self.name, self.angle, self.qubit_count)) + class DoubleAngledGate(Gate, Parameterizable): """ @@ -231,6 +234,9 @@ def __repr__(self): f"'qubit_count': {self.qubit_count})" ) + def __hash__(self): + return hash((self.name, self.angle_1, self.angle_2, self.qubit_count)) + class TripleAngledGate(Gate, Parameterizable): """ @@ -358,6 +364,9 @@ def __repr__(self): f"'qubit_count': {self.qubit_count})" ) + def __hash__(self): + return hash((self.name, self.angle_1, self.angle_2, self.angle_3, self.qubit_count)) + @singledispatch def _angles_equal( @@ -403,6 +412,15 @@ def _multi_angled_ascii_characters( """ def format_string(angle: Union[FreeParameterExpression, float]) -> str: + """ + Formats an angle for ASCII representation. + + Args: + angle (Union[FreeParameterExpression, float]): The angle to format. + + Returns: + str: The ASCII representation of the angle. + """ 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/circuit.py b/src/braket/circuits/circuit.py index 58d4cdda..7e3bfcd4 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -18,7 +18,7 @@ from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar, Union import numpy as np -from oqpy import Program as OqpyProgram +import oqpy from braket.circuits import compiler_directives from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram @@ -57,8 +57,9 @@ 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.pulse_sequence import _validate_uniqueness +from braket.pulse.pulse_sequence import PulseSequence, _validate_uniqueness SubroutineReturn = TypeVar( "SubroutineReturn", Iterable[Instruction], Instruction, ResultType, Iterable[ResultType] @@ -1097,6 +1098,7 @@ def to_ir( self, ir_type: IRType = IRType.JAQCD, serialization_properties: SerializationProperties = None, + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None, ) -> Union[OpenQasmProgram, JaqcdProgram]: """ Converts the circuit into the canonical intermediate representation. @@ -1108,6 +1110,8 @@ def to_ir( serialization_properties (SerializationProperties): 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 + calibration data for the device. default: None. Returns: Union[OpenQasmProgram, JaqcdProgram]: A representation of the circuit in the @@ -1127,7 +1131,10 @@ def to_ir( "serialization_properties must be of type OpenQASMSerializationProperties " "for IRType.OPENQASM." ) - return self._to_openqasm(serialization_properties or OpenQASMSerializationProperties()) + return self._to_openqasm( + serialization_properties or OpenQASMSerializationProperties(), + gate_definitions.copy() if gate_definitions is not None else None, + ) else: raise ValueError(f"Supplied ir_type {ir_type} is not supported.") @@ -1173,9 +1180,11 @@ def _to_jaqcd(self) -> JaqcdProgram: ) def _to_openqasm( - self, serialization_properties: OpenQASMSerializationProperties + self, + serialization_properties: OpenQASMSerializationProperties, + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], ) -> OpenQasmProgram: - ir_instructions = self._create_openqasm_header(serialization_properties) + ir_instructions = self._create_openqasm_header(serialization_properties, gate_definitions) openqasm_ir_type = IRType.OPENQASM ir_instructions.extend( [ @@ -1208,7 +1217,9 @@ def _to_openqasm( return OpenQasmProgram.construct(source="\n".join(ir_instructions), inputs={}) def _create_openqasm_header( - self, serialization_properties: OpenQASMSerializationProperties + self, + serialization_properties: OpenQASMSerializationProperties, + gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]], ) -> List[str]: ir_instructions = ["OPENQASM 3.0;"] for parameter in self.parameters: @@ -1225,16 +1236,78 @@ def _create_openqasm_header( f"{serialization_properties.qubit_reference_type} supplied." ) - frame_wf_declarations = self._generate_frame_wf_declarations() + frame_wf_declarations = self._generate_frame_wf_defcal_declarations(gate_definitions) if frame_wf_declarations: ir_instructions.append(frame_wf_declarations) return ir_instructions - def _generate_frame_wf_declarations(self) -> Optional[str]: - frames = {} - waveforms = {} + def _validate_gate_calbrations_uniqueness( + self, + gate_definitions: Dict[Tuple[Gate, QubitSet], PulseSequence], + frames: Dict[Frame], + waveforms: Dict[ArbitraryWaveform], + ) -> None: + for key, calibration in gate_definitions.items(): + for frame in calibration._frames.values(): + _validate_uniqueness(frames, frame) + frames[frame.id] = frame + for waveform in calibration._waveforms.values(): + _validate_uniqueness(waveforms, waveform) + waveforms[waveform.id] = waveform + + def _generate_frame_wf_defcal_declarations( + self, gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] + ) -> Optional[str]: + program = oqpy.Program(None) + + 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) + + # 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: + 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 = ( + [calibration._format_parameter_ast(value) for value in gate.parameters] + if isinstance(gate, Parameterizable) + else None + ) + 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) + + return None + + def _get_frames_waveforms_from_instrs( + self, gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] + ) -> Tuple[Dict[Frame], Dict[ArbitraryWaveform]]: from braket.circuits.gates import PulseGate + frames = {} + waveforms = {} for instruction in self.instructions: if isinstance(instruction.operator, PulseGate): for frame in instruction.operator.pulse_sequence._frames.values(): @@ -1243,18 +1316,79 @@ def _generate_frame_wf_declarations(self) -> Optional[str]: for waveform in instruction.operator.pulse_sequence._waveforms.values(): _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: + fixed_argument_calibrations = self._add_fixed_argument_calibrations( + gate_definitions, instruction + ) + gate_definitions.update(fixed_argument_calibrations) + return 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: - program = OqpyProgram(None) - for f in declarable_frames: - program.declare(f._to_oqpy_expression()) - for wf in waveforms.values(): - program.declare(wf._to_oqpy_expression()) - ast = program.to_ast(encal=True, include_externs=False) - return ast_to_qasm(ast) - return None + def _add_fixed_argument_calibrations( + self, + gate_definitions: Dict[Tuple[Gate, QubitSet], PulseSequence], + instruction: Instruction, + ) -> Dict[Tuple[Gate, QubitSet], PulseSequence]: + """Adds calibrations with arguments set to the instruction parameter values + + Given the collection of parameters in instruction.operator, this function looks for matching + parametric calibrations that have free parameters. If such a calibration is found and the + number N of its free parameters equals the number of instruction parameters, we can bind + the arguments of the calibration and add it to the calibration dictionary. + + If N is smaller, it is probably impossible to assign the instruction parameter values to the + corresponding calibration parameters so we raise an error. + If N=0, we ignore it as it will not be removed by _generate_frame_wf_defcal_declarations. + + Args: + gate_definitions (Dict[Tuple[Gate, QubitSet], PulseSequence]): a dictionary of + calibrations + instruction (Instruction): a Circuit instruction + + Returns: + Dict[Tuple[Gate, QubitSet], PulseSequence]: additional calibrations + + Raises: + NotImplementedError: in two cases: (i) if the instruction contains unbound parameters + and the calibration dictionary contains a parametric calibration applicable to this + instructions; (ii) if the calibration is defined with a partial number of unbound + parameters. + """ + additional_calibrations = {} + for key, calibration in gate_definitions.items(): + gate = key[0] + target = key[1] + if target != instruction.target: + continue + if isinstance(gate, type(instruction.operator)) and len( + instruction.operator.parameters + ) == len(gate.parameters): + free_parameter_number = sum( + [isinstance(p, FreeParameterExpression) for p in gate.parameters] + ) + if free_parameter_number == 0: + continue + elif free_parameter_number < len(gate.parameters): + raise NotImplementedError( + "Calibrations with a partial number of fixed parameters are not supported." + ) + elif any( + isinstance(p, FreeParameterExpression) for p in instruction.operator.parameters + ): + raise NotImplementedError( + "Parametric calibrations cannot be attached with parametric circuits." + ) + bound_key = ( + 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) + } + ) + return additional_calibrations def as_unitary(self) -> np.ndarray: r""" diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index eaf9675f..81f2499d 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -210,6 +210,9 @@ def __eq__(self, other): def __repr__(self): return f"{self.name}('qubit_count': {self._qubit_count})" + def __hash__(self): + return hash((self.name, self.qubit_count)) + @classmethod def register_gate(cls, gate: Type[Gate]) -> None: """Register a gate implementation by adding it into the Gate class. diff --git a/src/braket/circuits/gate_calibrations.py b/src/braket/circuits/gate_calibrations.py new file mode 100644 index 00000000..01694a1e --- /dev/null +++ b/src/braket/circuits/gate_calibrations.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 copy import deepcopy +from typing import Any, Dict, List, Optional, Tuple + +from braket.circuits.gate import Gate +from braket.circuits.qubit_set import QubitSet +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + QubitReferenceType, +) +from braket.pulse.pulse_sequence import PulseSequence + + +class GateCalibrations: + """ + 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 + + def __init__( + self, + pulse_sequences: Dict[Tuple[Gate, QubitSet], PulseSequence], + ): + """ + Args: + 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`. + + Returns: + Dict[Tuple[Gate, QubitSet], PulseSequence]: The calibration data Dictionary. + """ + return self._pulse_sequences + + @pulse_sequences.setter + def pulse_sequences(self, value: Any) -> None: + """ + Sets the mapping of (Gate, Qubit) to the corresponding `PulseSequence`. + + Args: + value(Any): The value for the pulse_sequences property to be set to. + + Raises: + TypeError: Raised if the type is not Dict[Tuple[Gate, QubitSet], PulseSequence] + + """ + if isinstance(value, dict) and all( + isinstance(k[0], Gate) and isinstance(k[1], QubitSet) and isinstance(v, PulseSequence) + for (k, v) in value.items() + ): + self._pulse_sequences = value + else: + raise TypeError( + "The value for pulse_sequence must be of type: " + "Dict[Tuple[Gate, QubitSet], PulseSequence]" + ) + + def copy(self) -> GateCalibrations: + """ + Returns a copy of the object. + + Returns: + GateCalibrations: a copy of the calibrations. + """ + return GateCalibrations(deepcopy(self._pulse_sequences)) + + def __len__(self): + return len(self._pulse_sequences) + + def filter( + self, gates: Optional[List[Gate]] = None, qubits: Optional[QubitSet] = None + ) -> Optional[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. + + Returns: + Optional[GateCalibrations]: A filtered GateCalibrations object. Otherwise, returns + none if no matches are found. + """ # noqa: E501 + keys = self.pulse_sequences.keys() + 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])) + ] + 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: + """ + Returns the defcal representation for the `GateCalibrations` object. + + Args: + calibration_key (Optional[Tuple[Gate, QubitSet]]): An optional key to get a specific defcal. + Default: None + + 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(): + raise ValueError( + f"The key {calibration_key} does not exist in this GateCalibrations object." + ) + return ( + self.pulse_sequences[calibration_key] + .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 + + 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], + ] + ) + + def __eq__(self, other): + 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 a37e3208..ac57d05b 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -2781,6 +2781,9 @@ def __eq__(self, other): return self.matrix_equivalence(other) return False + def __hash__(self): + return hash((self.name, str(self._matrix), self.qubit_count)) + @staticmethod def _transform_matrix_to_ir(matrix: np.ndarray) -> List: return [[[element.real, element.imag] for element in row] for row in matrix.tolist()] diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index eef9f8a6..1b64e2dd 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -186,3 +186,21 @@ def subs_if_free_parameter(parameter: Any, **kwargs) -> Any: substituted = float(substituted) return substituted return parameter + + +def _is_float(argument: str) -> bool: + """ + Checks if a string can be cast into a float. + + Args: + argument (str): String to check. + + Returns: + bool: Returns true if the string can be cast as a float. False, otherwise. + + """ + try: + float(argument) + return True + except ValueError: + return False diff --git a/src/braket/pulse/ast/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py index 89b318ea..69e9fafa 100644 --- a/src/braket/pulse/ast/approximation_parser.py +++ b/src/braket/pulse/ast/approximation_parser.py @@ -419,6 +419,8 @@ def play(self, node: ast.FunctionCall, context: _ParseState) -> None: amps = self.visit(node.arguments[1], context) 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.") else: raise NotImplementedError frame_data = context.frame_data[frame_id] diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index f51531fe..3606bbd1 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -13,7 +13,9 @@ from __future__ import annotations +import builtins from copy import deepcopy +from inspect import signature from typing import Any, Dict, List, Set, Union from openpulse import ast @@ -331,6 +333,72 @@ def _format_parameter_ast( return _FreeParameterExpressionIdentifier(parameter) return parameter + 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"), + "expr": FreeParameterExpression, + } + if argument["type"] in nonprimitive_arg_type.keys(): + return nonprimitive_arg_type[argument["type"]](argument["value"]) + else: + 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. + + 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. + + 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']}"): + 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") + return calibration_sequence + def __call__(self, arg: Any = None, **kwargs) -> PulseSequence: """ Implements the call function to easily make a bound PulseSequence. @@ -350,6 +418,13 @@ def __call__(self, arg: Any = None, **kwargs) -> PulseSequence: param_values[str(key)] = val return self.make_bound_pulse_sequence(param_values) + def __eq__(self, other): + return ( + isinstance(other, PulseSequence) + and self.parameters == other.parameters + and self.to_ir() == other.to_ir() + ) + def _validate_uniqueness( mapping: Dict[str, Any], values: Union[Frame, Waveform, List[Frame], List[Waveform]] diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index f7aeb2d2..f298dd3e 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -16,7 +16,7 @@ import random import string from abc import ABC, abstractmethod -from typing import List, Optional, Union +from typing import Dict, List, Optional, Union import numpy as np from oqpy import WaveformVar, bool_, complex128, declare_waveform_generator, duration, float64 @@ -55,6 +55,19 @@ def sample(self, dt: float) -> np.ndarray: 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 + + 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 @@ -94,6 +107,12 @@ def sample(self, dt: float) -> np.ndarray: """ raise NotImplementedError + @staticmethod + def _from_calibration_schema(waveform_json: Dict) -> ArbitraryWaveform: + wave_id = waveform_json["waveformId"] + complex_amplitudes = [complex(i[0], i[1]) for i in waveform_json["amplitudes"]] + return ArbitraryWaveform(complex_amplitudes, wave_id) + class ConstantWaveform(Waveform, Parameterizable): """A constant waveform which holds the supplied `iq` value as its amplitude for the @@ -166,6 +185,25 @@ def sample(self, dt: float) -> np.ndarray: samples = self.iq * np.ones_like(sample_range) return samples + @staticmethod + def _from_calibration_schema(waveform_json: Dict) -> ConstantWaveform: + wave_id = waveform_json["waveformId"] + length = iq = None + for val in waveform_json["arguments"]: + if val["name"] == "length": + length = ( + float(val["value"]) + if val["type"] == "float" + else FreeParameterExpression(val["value"]) + ) + if val["name"] == "iq": + iq = ( + complex(val["value"]) + if val["type"] == "complex" + else FreeParameterExpression(val["value"]) + ) + return ConstantWaveform(length=length, iq=iq, id=wave_id) + class DragGaussianWaveform(Waveform, Parameterizable): """A gaussian waveform with an additional gaussian derivative component and lifting applied.""" @@ -282,6 +320,17 @@ def sample(self, dt: float) -> np.ndarray: ) return samples + @staticmethod + def _from_calibration_schema(waveform_json: Dict) -> DragGaussianWaveform: + 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 DragGaussianWaveform(**waveform_parameters) + class GaussianWaveform(Waveform, Parameterizable): """A waveform with amplitudes following a gaussian distribution for the specified parameters.""" @@ -387,6 +436,17 @@ def sample(self, dt: float) -> np.ndarray: ) return samples + @staticmethod + def _from_calibration_schema(waveform_json: Dict) -> GaussianWaveform: + 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 GaussianWaveform(**waveform_parameters) + def _make_identifier_name() -> str: return "".join([random.choice(string.ascii_letters) for _ in range(10)]) @@ -402,3 +462,19 @@ def _map_to_oqpy_type( else _FreeParameterExpressionIdentifier(parameter) ) return parameter + + +def _parse_waveform_from_calibration_schema(waveform: Dict) -> Waveform: + waveform_names = { + "arbitrary": ArbitraryWaveform._from_calibration_schema, + "drag_gaussian": DragGaussianWaveform._from_calibration_schema, + "gaussian": GaussianWaveform._from_calibration_schema, + "constant": ConstantWaveform._from_calibration_schema, + } + if "amplitudes" in waveform.keys(): + 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") diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 0be1d22e..decd7d87 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -58,6 +58,12 @@ def test_get_devices_arn(arn): assert results[0].arn == arn +@pytest.mark.parametrize("arn", [(PULSE_ARN)]) +def test_device_gate_calibrations(arn, aws_session): + device = AwsDevice(arn, aws_session=aws_session) + assert device.gate_calibrations + + def test_get_devices_others(): provider_names = ["Amazon Braket"] types = ["SIMULATOR"] diff --git a/test/integ_tests/test_pulse.py b/test/integ_tests/test_pulse.py index 3b4694e4..c40a4556 100644 --- a/test/integ_tests/test_pulse.py +++ b/test/integ_tests/test_pulse.py @@ -1,10 +1,12 @@ +import math + import numpy as np import pytest from braket.aws import AwsDevice, AwsQuantumTask -from braket.circuits import Circuit +from braket.circuits import Circuit, Gate, GateCalibrations, QubitSet from braket.parametric import FreeParameter -from braket.pulse import ArbitraryWaveform, PulseSequence +from braket.pulse import ArbitraryWaveform, Frame, Port, PulseSequence @pytest.fixture @@ -144,6 +146,30 @@ def arbitrary_waveform(): ) +@pytest.fixture +def port(): + return Port("test_port_ff", dt=1e-9) + + +@pytest.fixture +def frame_id(device): + return next(iter(device.frames)) + + +@pytest.fixture +def frame(frame_id, port): + return Frame(frame_id, port, 1e6, is_predefined=True) + + +@pytest.fixture +def pulse_sequence(frame, arbitrary_waveform): + return ( + PulseSequence() + .barrier(qubits_or_frames=[frame]) + .play(frame=frame, waveform=arbitrary_waveform) + ) + + 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) @@ -281,3 +307,33 @@ def test_pulse_sequence(arbitrary_waveform, device): ) chi_squared = np.sum((observed - expected) ** 2 / expected) assert chi_squared < 10 # adjust this threshold if test is flaky + + +def test_gate_calibration_run(device, pulse_sequence): + 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) + user_calibration_task = device.run( + bell_circuit, + gate_definitions=user_gate_calibrations.pulse_sequences, + shots=num_shots, + disable_qubit_rewiring=True, + ) + device_calibration_task = device.run( + bell_circuit, + gate_definitions=device.gate_calibrations.pulse_sequences, + shots=num_shots, + disable_qubit_rewiring=True, + ) + + if not device.is_available: + try: + assert user_calibration_task.state not in AwsQuantumTask.TERMINAL_STATES + assert device_calibration_task.state not in AwsQuantumTask.TERMINAL_STATES + finally: + user_calibration_task.cancel() + device_calibration_task.cancel() + return + + assert user_calibration_task.result().measurement_counts + assert device_calibration_task.result().measurement_counts diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index cd7d76c3..5dcec5fb 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -200,6 +200,7 @@ def run_and_assert( poll_timeout_seconds, # Treated as positional arg poll_interval_seconds, # Treated as positional arg inputs, # Treated as positional arg + gate_definitions, # Treated as positional arg extra_args, extra_kwargs, ): @@ -217,6 +218,8 @@ def run_and_assert( run_args.append(poll_interval_seconds) if inputs is not None: run_args.append(inputs) + if gate_definitions is not None: + run_args.append(gate_definitions) run_args += extra_args if extra_args else [] run_kwargs = extra_kwargs or {} @@ -233,6 +236,7 @@ def run_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, + gate_definitions, extra_args, extra_kwargs, ) @@ -258,6 +262,7 @@ def run_batch_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, + gate_definitions, extra_args, extra_kwargs, ): @@ -282,6 +287,8 @@ def run_batch_and_assert( run_args.append(poll_interval_seconds) if inputs is not None: run_args.append(inputs) + if gate_definitions is not None: + run_args.append(gate_definitions) run_args += extra_args if extra_args else [] run_kwargs = extra_kwargs or {} @@ -298,6 +305,7 @@ def run_batch_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, + gate_definitions, extra_args, extra_kwargs, ) @@ -324,6 +332,7 @@ def _create_task_args_and_kwargs( poll_timeout_seconds, poll_interval_seconds, inputs, + gate_definitions, extra_args, extra_kwargs, ): @@ -342,6 +351,7 @@ def _create_task_args_and_kwargs( if poll_interval_seconds is not None else default_poll_interval, "inputs": inputs, + "gate_definitions": gate_definitions, } ) 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 a4674e07..40abf638 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -10,10 +10,12 @@ # 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 from datetime import datetime from unittest.mock import Mock, PropertyMock, patch +from urllib.error import URLError import networkx as nx import pytest @@ -31,17 +33,15 @@ ) from jsonschema import validate -from braket.aws import AwsDevice, AwsDeviceType, AwsQuantumTask, AwsQuantumTaskBatch -from braket.circuits import Circuit, FreeParameter +from braket.aws import AwsDevice, AwsDeviceType, AwsQuantumTask +from braket.circuits import Circuit, FreeParameter, Gate, QubitSet +from braket.circuits.gate_calibrations import GateCalibrations from braket.device_schema.device_execution_window import DeviceExecutionWindow from braket.device_schema.dwave import DwaveDeviceCapabilities -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, -) from braket.device_schema.rigetti import RigettiDeviceCapabilities from braket.device_schema.simulators import GateModelSimulatorDeviceCapabilities from braket.ir.openqasm import Program as OpenQasmProgram -from braket.pulse import Frame, Port +from braket.pulse import DragGaussianWaveform, Frame, Port, PulseSequence MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_1 = { "braketSchemaHeader": { @@ -78,6 +78,136 @@ ) +MOCK_gate_calibrations_JSON = { + "gates": { + "0": { + "cphaseshift": [ + { + "name": "cphaseshift", + "qubits": ["0", "1"], + "arguments": ["-1.5707963267948966"], + "calibrations": [ + { + "name": "barrier", + "arguments": [{"name": "qubit", "value": "0", "type": "string"}], + }, + { + "name": "play", + "arguments": [ + {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"}, + { + "name": "waveform", + "value": "wf_drag_gaussian_0", + "type": "waveform", + }, + ], + }, + { + "name": "barrier", + "arguments": [ + {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"} + ], + }, + { + "name": "barrier", + "arguments": None, + }, + { + "name": "delay", + "arguments": [ + {"name": "duration", "value": 3e-07, "type": "float"}, + {"name": "qubit", "value": "0", "type": "string"}, + {"name": "qubit", "value": "1", "type": "string"}, + ], + }, + { + "name": "delay", + "arguments": [ + {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"}, + {"name": "duration", "value": 3e-07, "type": "float"}, + ], + }, + { + "name": "shift_phase", + "arguments": [ + {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"}, + {"name": "phase", "value": 3e-07, "type": "float"}, + ], + }, + { + "name": "shift_frequency", + "arguments": [ + {"name": "frequency", "value": "theta", "type": "expr"}, + {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"}, + {"name": "extra", "value": "q0_q1_cphase_frame", "type": "string"}, + ], + }, + ], + } + ], + "rx": [ + { + "gateName": "rx", + "gateId": "rz_1", + "qubits": "0", + "arguments": ["theta"], + "calibrations": [ + {"name": "barrier", "arguments": None}, + ], + } + ], + }, + "0_1": { + "cz": [ + { + "gateName": "cz", + "gateId": "cz_0_1", + "qubits": ["1", "0"], + "arguments": [], + "calibrations": [ + {"name": "barrier", "arguments": None}, + ], + } + ], + "rx_12": [], + }, + }, + "waveforms": { + "q0_q1_cz_CZ": { + "waveformId": "q0_q1_cz_CZ", + "amplitudes": [[0.0, 0.0], [0.0, 0.0]], + }, + "wf_drag_gaussian_0": { + "waveformId": "wf_drag_gaussian_0", + "name": "drag_gaussian", + "arguments": [ + {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, + {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, + {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, + {"name": "beta", "value": 7.494904522022295e-10, "type": "float"}, + ], + }, + "wf_gaussian_0": { + "waveformId": "wf_gaussian_0", + "name": "gaussian", + "arguments": [ + {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, + {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, + {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, + ], + }, + "wf_constant": { + "waveformId": "wf_constant", + "name": "constant", + "arguments": [ + {"name": "length", "value": 2, "type": "float"}, + {"name": "iq", "value": 0.23, "type": "complex"}, + ], + }, + }, +} + + def test_mock_rigetti_schema_1(): validate(MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_1, RigettiDeviceCapabilities.schema()) @@ -85,7 +215,7 @@ def test_mock_rigetti_schema_1(): MOCK_GATE_MODEL_QPU_1 = { "deviceName": "Aspen-10", "deviceType": "QPU", - "providerName": "provider1", + "providerName": "Rigetti", "deviceStatus": "OFFLINE", "deviceCapabilities": MOCK_GATE_MODEL_QPU_CAPABILITIES_1.json(), } @@ -495,6 +625,7 @@ def test_device_refresh_metadata(arn): "qhpSpecificProperties": None, } }, + "nativeGateCalibrationsRef": "file://hostname/foo/bar", } @@ -516,6 +647,7 @@ def test_device_refresh_metadata(arn): "qhpSpecificProperties": None, } }, + "nativeGateCalibrationssRef": "file://hostname/foo/bar", } @@ -535,6 +667,32 @@ def get_pulse_model(capabilities_json): ], "shotsRange": [1, 10], }, + "provider": { + "specs": { + "1Q": { + "0": { + "fActiveReset": 0.9715, + "fRO": 0.951, + "f1QRB": 0.997339217568556, + "f1QRB_std_err": 0.00006690422818326937, + "f1Q_simultaneous_RB": 0.9949723201166536, + "f1Q_simultaneous_RB_std_err": 0.00021695233492231294, + "T1": 0.000010019627401991471, + "T2": 0.000018156447816365015, + } + }, + "2Q": { + "0-1": { + "fCZ": 0.9586440436264603, + "fCZ_std_err": 0.007025921432645824, + "fCPHASE": 0.9287330972713645, + "fCPHASE_std_err": 0.009709406809550082, + "fXY": 0.9755179214520402, + "fXY_std_err": 0.0060234488782598536, + }, + }, + } + }, "action": { "braket.ir.jaqcd.program": { "actionType": "braket.ir.jaqcd.program", @@ -554,7 +712,7 @@ def get_pulse_model(capabilities_json): return { "deviceName": "M-2-Pulse", "deviceType": "QPU", - "providerName": "provider1", + "providerName": "Rigetti", "deviceStatus": "OFFLINE", "deviceCapabilities": device_obj.json(), } @@ -588,6 +746,104 @@ def test_device_pulse_metadata(pulse_device_capabilities): assert device.frames == {} +def test_gate_calibration_refresh_no_url(arn): + mock_session = Mock() + mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 + mock_session.region = RIGETTI_REGION + device = AwsDevice(arn, mock_session) + + assert device.refresh_gate_calibrations() == None + + +@patch("urllib.request.urlopen") +def test_device_gate_calibrations_exists(mock_url_request): + # The data is accessed using a device manager so here data is prepped and passed for the return val. + response_data_content = { + "gates": { + "0_1": { + "cphaseshift": [ + { + "name": "cphaseshift", + "qubits": ["0", "1"], + "arguments": ["-1.5707963267948966"], + "calibrations": [ + { + "name": "play", + "arguments": [ + { + "name": "waveform", + "value": "wf_drag_gaussian_0", + "type": "waveform", + }, + { + "name": "frame", + "value": "q0_q1_cphase_frame", + "type": "frame", + }, + ], + }, + ], + } + ], + "rx_12": [{"name": "rx_12", "qubits": ["0"]}], + }, + }, + "waveforms": { + "wf_drag_gaussian_0": { + "waveformId": "wf_drag_gaussian_0", + "name": "drag_gaussian", + "arguments": [ + {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, + {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, + {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, + {"name": "beta", "value": 7.494904522022295e-10, "type": "float"}, + ], + }, + }, + } + + response_data_stream = io.BytesIO(json.dumps(response_data_content).encode("utf-8")) + mock_url_request.return_value.__enter__.return_value = response_data_stream + mock_session = Mock() + mock_session.get_device.return_value = get_pulse_model( + MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 + ) + device = AwsDevice(RIGETTI_ARN, mock_session) + + expected_waveforms = { + "wf_drag_gaussian_0": DragGaussianWaveform( + length=6.000000000000001e-8, + sigma=6.369913502160144e-9, + amplitude=-0.4549282253548838, + beta=7.494904522022295e-10, + id="wf_drag_gaussian_0", + ) + } + expected_ngc = GateCalibrations( + pulse_sequences={ + (Gate.CPhaseShift(-1.5707963267948966), QubitSet([0, 1])): PulseSequence().play( + device.frames["q0_q1_cphase_frame"], expected_waveforms["wf_drag_gaussian_0"] + ) + } + ) + assert device.gate_calibrations == expected_ngc + # Called twice to check that the property stays the same after being initially fetched + assert device.gate_calibrations == expected_ngc + + +@pytest.mark.xfail(raises=URLError) +@patch("urllib.request.urlopen") +def test_refresh_data_url_error(mock_url_request): + mock_url_request.side_effect = URLError("mock reason") + mock_session = Mock() + mock_session.get_device.return_value = get_pulse_model( + MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 + ) + device = AwsDevice(RIGETTI_ARN, mock_session) + + device.gate_calibrations + + def test_equality(arn): mock_session = Mock() mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 @@ -1054,6 +1310,7 @@ def test_default_bucket_not_called(aws_quantum_task_mock, device, circuit, s3_de None, None, None, + None, ) device._aws_session.default_bucket.assert_not_called() @@ -1227,6 +1484,7 @@ def _run_and_assert( poll_timeout_seconds=None, # Treated as positional arg poll_interval_seconds=None, # Treated as positional arg inputs=None, # Treated as positional arg + gate_definitions=None, # Treated as positional arg extra_args=None, extra_kwargs=None, ): @@ -1243,6 +1501,7 @@ def _run_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, + gate_definitions, extra_args, extra_kwargs, ) @@ -1260,6 +1519,7 @@ def _run_batch_and_assert( poll_timeout_seconds=None, # Treated as a positional arg poll_interval_seconds=None, # Treated as positional arg inputs=None, # Treated as positional arg + gate_definitions=None, # Treated as positional arg extra_args=None, extra_kwargs=None, ): @@ -1279,6 +1539,7 @@ def _run_batch_and_assert( poll_timeout_seconds, poll_interval_seconds, inputs, + gate_definitions, extra_args, extra_kwargs, ) @@ -1569,3 +1830,110 @@ def test_device_topology_graph_data(get_device_data, expected_graph, arn): new_val = "new_val" device._topology_graph = new_val assert device.topology_graph == new_val + + +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) + + +def test_parse_calibration_data(): + mock_session = Mock() + mock_session.get_device.return_value = get_pulse_model( + MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 + ) + device = AwsDevice(DWAVE_ARN, mock_session) + calibration_data = device._parse_calibration_json(MOCK_gate_calibrations_JSON) + device_ngc = GateCalibrations(calibration_data) + + expected_waveforms = { + "wf_drag_gaussian_0": DragGaussianWaveform( + length=6.000000000000001e-8, + sigma=6.369913502160144e-9, + amplitude=-0.4549282253548838, + beta=7.494904522022295e-10, + id="wf_drag_gaussian_0", + ) + } + expected_pulse_sequences = { + (Gate.CPhaseShift(-1.5707963267948966), QubitSet([0, 1])): PulseSequence() + .barrier(QubitSet(0)) + .play(device.frames["q0_q1_cphase_frame"], expected_waveforms["wf_drag_gaussian_0"]) + .barrier([device.frames["q0_q1_cphase_frame"]]) + .barrier([]) + .delay(QubitSet([0, 1]), 3e-07) + .delay([device.frames["q0_q1_cphase_frame"]], 3e-07) + .shift_phase(device.frames["q0_q1_cphase_frame"], 3e-07) + .shift_frequency(device.frames["q0_q1_cphase_frame"], FreeParameter("theta")), + (Gate.Rx(FreeParameter("theta")), QubitSet(0)): PulseSequence().barrier([]), + (Gate.CZ(), QubitSet([1, 0])): PulseSequence().barrier([]), + } + expected_ngc = GateCalibrations(pulse_sequences=expected_pulse_sequences) + assert device_ngc == expected_ngc + + +@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"}, + }, + } + ), + ], +) +@pytest.mark.xfail(raises=ValueError) +def test_parse_calibration_data_bad_instr(bad_input): + mock_session = Mock() + mock_session.get_device.return_value = get_pulse_model( + MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 + ) + device = AwsDevice(DWAVE_ARN, mock_session) + device._parse_calibration_json(bad_input) diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index 168e193c..e76756e2 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -192,3 +192,23 @@ def test_double_angle_parameters(): assert DoubleAngledGate( qubit_count=1, ascii_symbols=["foo"], angle_1=1, angle_2=2 ).parameters == [1, 2] + + +def test_hash_double_angle(): + symbol1 = FreeParameter("theta") + assert hash( + DoubleAngledGate(angle_1=symbol1, angle_2=1, qubit_count=1, ascii_symbols=["bar"]) + ) == hash(DoubleAngledGate(angle_1=symbol1, angle_2=1, qubit_count=1, ascii_symbols=["bar"])) + + +def test_hash_triple_angle(): + symbol1 = FreeParameter("theta") + assert hash( + TripleAngledGate( + angle_1=symbol1, angle_2=1, angle_3=3, qubit_count=1, ascii_symbols=["bar"] + ) + ) == hash( + TripleAngledGate( + angle_1=symbol1, angle_2=1, angle_3=3, qubit_count=1, ascii_symbols=["bar"] + ) + ) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index e1165fe9..fd2170a5 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -33,6 +33,8 @@ noise, observables, ) +from braket.circuits.gate_calibrations import GateCalibrations +from braket.circuits.parameterizable import Parameterizable from braket.circuits.serialization import ( IRType, OpenQASMSerializationProperties, @@ -107,6 +109,61 @@ def user_defined_frame(port): ) +@pytest.fixture +def pulse_sequence(predefined_frame_1): + return ( + PulseSequence() + .set_frequency( + predefined_frame_1, + 6e6, + ) + .play( + predefined_frame_1, + DragGaussianWaveform(length=3e-3, sigma=0.4, beta=0.2, id="drag_gauss_wf"), + ) + ) + + +@pytest.fixture +def pulse_sequence_2(predefined_frame_1): + return ( + PulseSequence() + .shift_phase( + predefined_frame_1, + FreeParameter("alpha"), + ) + .set_phase( + predefined_frame_1, + FreeParameter("gamma"), + ) + .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])) + calibration_key_2 = (Gate.Rx(FreeParameter("theta")), QubitSet([0])) + calibration_key_3 = ( + 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, + } + ) + + def test_repr_instructions(h): expected = f"Circuit('instructions': {h.instructions})" assert repr(h) == expected @@ -681,6 +738,18 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "OPENQASM 3.0;", "bit[2] b;", "qubit[2] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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];", @@ -698,6 +767,18 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): [ "OPENQASM 3.0;", "bit[2] b;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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;", @@ -717,6 +798,18 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): source="\n".join( [ "OPENQASM 3.0;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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{", @@ -740,6 +833,18 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): [ "OPENQASM 3.0;", "qubit[5] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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]", @@ -759,6 +864,18 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "input float theta;", "bit[2] b;", "qubit[2] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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];", @@ -780,6 +897,18 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "OPENQASM 3.0;", "bit[5] b;", "qubit[5] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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];", @@ -802,6 +931,14 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "OPENQASM 3.0;", "bit[7] b;", "qubit[7] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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];", @@ -818,27 +955,204 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): ), ), ( - Circuit().h(0, power=-2.5).h(0, power=0), + 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[1] b;", - "qubit[1] q;", + "bit[2] b;", + "qubit[2] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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={}, ), ), + pytest.param( + Circuit().h(0, power=-2.5).h(0, power=0).rx(0, angle=FreeParameter("theta")), + OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), + OpenQasmProgram( + source="", + inputs={}, + ), + marks=pytest.mark.xfail( + reason="Parametric calibrations cannot be attached with parametric circuits." + ), + ), ], ) -def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): +def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir, gate_calibrations): + copy_of_gate_calibrations = gate_calibrations.copy() + assert ( + circuit.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + gate_definitions=gate_calibrations.pulse_sequences, + ) + == expected_ir + ) + assert copy_of_gate_calibrations.pulse_sequences == gate_calibrations.pulse_sequences + + +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) + 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, + } + ) + + 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" + + "(3000000.0ns, 400000000.0ns, 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={}, + ) + + assert ( + circ.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + gate_definitions=gate_calibrations.pulse_sequences, + ) + == expected_ir + ) + + +@pytest.mark.xfail( + reasons="Calibrations with a partial number of fixed parameters are not supported." +) +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} + ), + ) + circuit.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + gate_definitions=gate_calibrations.pulse_sequences, + ) + + +def test_circuit_user_gate(pulse_sequence_2): + class Foo(Gate, Parameterizable): + def __init__( + self, + bar, + ): + super().__init__(qubit_count=1, ascii_symbols=["Foo"]) + self._parameters = [bar] + + @property + def parameters(self): + return self._parameters + + def bind_values(self, **kwargs): + raise NotImplementedError + + @property + def _qasm_name(self): + return "foo" + + def __hash__(self): + return hash((self.name, self.parameters[0], self.qubit_count)) + + @staticmethod + @circuit.subroutine(register=True) + def foo( + target, + bar, + ): + return Instruction(Foo(bar), target=target) + + Gate.register_gate(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} + ) + } + ) + + expected_ir = OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3000000.0ns, 400000000.0ns, 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={}, + ) + assert ( - circuit.to_ir(ir_type=IRType.OPENQASM, serialization_properties=serialization_properties) + circ.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + gate_definitions=gate_calibrations.pulse_sequences, + ) == expected_ir ) diff --git a/test/unit_tests/braket/circuits/test_gate_calibration.py b/test/unit_tests/braket/circuits/test_gate_calibration.py new file mode 100644 index 00000000..31c2384d --- /dev/null +++ b/test/unit_tests/braket/circuits/test_gate_calibration.py @@ -0,0 +1,148 @@ +# 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 Gate, QubitSet +from braket.circuits.gate_calibrations import GateCalibrations +from braket.pulse import Frame, Port, PulseSequence + + +@pytest.fixture +def port(): + return Port("test_port_ff", dt=1e-9) + + +@pytest.fixture +def frame_id(): + return "test_frame_rf" + + +@pytest.fixture +def frame(frame_id, port): + return Frame(frame_id, port, 1e6, is_predefined=True) + + +@pytest.fixture +def pulse_sequence(frame): + return ( + PulseSequence() + .barrier(qubits_or_frames=[frame]) + .delay(qubits_or_frames=[frame], duration=1000) + ) + + +def test_gc_creation(pulse_sequence): + calibration_key = (Gate.H(), QubitSet([0, 1])) + calibration = GateCalibrations({calibration_key: pulse_sequence}) + + assert calibration.pulse_sequences[calibration_key] == pulse_sequence + + +def test_gc_copy(pulse_sequence): + calibration_key = (Gate.H(), QubitSet([0, 1])) + calibration = GateCalibrations({calibration_key: pulse_sequence}) + + assert calibration == calibration.copy() + + +def test_filter(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_calibration_1 = GateCalibrations({calibration_key: pulse_sequence}) + expected_calibration_2 = GateCalibrations( + {calibration_key: pulse_sequence, calibration_key_2: pulse_sequence} + ) + expected_calibration_3 = GateCalibrations({calibration_key_2: 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)) + + +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[1000000000000.0ns] test_frame_rf;", + "}", + ] + ) + + assert calibration.to_ir() == expected_ir + + +@pytest.mark.xfail(raises=ValueError) +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[1000000000000.0ns] 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[1000000000000.0ns] 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} + ) + + assert len(calibration) == 2 + + +@pytest.mark.parametrize( + "bad_input", + [ + ({(Gate.Rx(1), "string"): PulseSequence()}), + ({(Gate.Rx(1), QubitSet(0)): 4}), + ({("string_a", "string_b"): PulseSequence()}), + ], +) +@pytest.mark.xfail(raises=TypeError) +def test_bad_pulse_sequence(bad_input): + GateCalibrations(bad_input) diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 032dc550..05b98ade 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -1149,3 +1149,9 @@ def test_gate_power(gate, target, power, expected_ir): ) == expected_ir ) + + +def test_hash(): + assert hash(Gate.Unitary(Gate.CCNot().to_matrix())) == hash( + Gate.Unitary(Gate.CCNot().to_matrix()) + ) 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 ae1bc622..786c6b7e 100644 --- a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py +++ b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py @@ -399,6 +399,17 @@ def test_play_arbitrary_waveforms(port): verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) +@pytest.mark.xfail(raises=NameError) +def test_missing_waveform(port): + frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) + my_arb_wf = ArbitraryWaveform([0.4 + 0.1j, -0.8 + 0.1j, 1 + 0.2j]) + pulse_seq = PulseSequence() + identifier = my_arb_wf._to_oqpy_expression() + identifier._needs_declaration = False + pulse_seq._program.play(frame, identifier.to_ast(pulse_seq._program)) + _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) + + def test_play_literal(port): frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) pulse_seq = PulseSequence() diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py index 064ab302..14add748 100644 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -337,3 +337,86 @@ def test_pulse_sequence_to_ir(predefined_frame_1, predefined_frame_2): ] ) assert pulse_sequence.to_ir() == expected_str + + +def test_parse_from_calibration_schema(predefined_frame_1, predefined_frame_2): + waveforms = { + "drag_gauss_wf": DragGaussianWaveform(length=3e-3, sigma=0.4, beta=0.2, id="drag_gauss_wf") + } + frames = {predefined_frame_1.id: predefined_frame_1, predefined_frame_2.id: predefined_frame_2} + + calibration_instrs = [ + { + "name": "barrier", + "arguments": [{"name": "qubit", "value": "0", "type": "string"}], + }, + { + "name": "play", + "arguments": [ + {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, + { + "name": "waveform", + "value": "drag_gauss_wf", + "type": "waveform", + }, + ], + }, + { + "name": "barrier", + "arguments": [ + {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, + {"name": "frame", "value": "predefined_frame_2", "type": "frame"}, + ], + }, + { + "name": "barrier", + "arguments": None, + }, + { + "name": "delay", + "arguments": [ + {"name": "duration", "value": 3e-07, "type": "float"}, + {"name": "qubit", "value": "0", "type": "string"}, + {"name": "qubit", "value": "1", "type": "string"}, + ], + }, + { + "name": "delay", + "arguments": [ + {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, + {"name": "duration", "value": 3e-07, "type": "float"}, + ], + }, + { + "name": "shift_phase", + "arguments": [ + {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, + {"name": "phase", "value": 3e-07, "type": "float"}, + ], + }, + { + "name": "shift_frequency", + "arguments": [ + {"name": "frequency", "value": "theta", "type": "expr"}, + {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, + {"name": "extra", "value": "predefined_frame_1", "type": "string"}, + ], + }, + ] + + expected_pulse_sequence = ( + PulseSequence() + .barrier(QubitSet(0)) + .play(predefined_frame_1, waveforms["drag_gauss_wf"]) + .barrier([predefined_frame_1, predefined_frame_2]) + .barrier([]) + .delay(QubitSet([0, 1]), 3e-07) + .delay([predefined_frame_1], 3e-07) + .shift_phase(predefined_frame_1, 3e-07) + .shift_frequency(predefined_frame_1, FreeParameter("theta")) + ) + + assert ( + PulseSequence._parse_from_calibration_schema(calibration_instrs, waveforms, frames) + == expected_pulse_sequence + ) diff --git a/test/unit_tests/braket/pulse/test_waveforms.py b/test/unit_tests/braket/pulse/test_waveforms.py index 973cfaf4..b42eacc0 100644 --- a/test/unit_tests/braket/pulse/test_waveforms.py +++ b/test/unit_tests/braket/pulse/test_waveforms.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 math import re from copy import deepcopy @@ -21,6 +22,7 @@ from braket.circuits.free_parameter import FreeParameter from braket.pulse import ArbitraryWaveform, ConstantWaveform, DragGaussianWaveform, GaussianWaveform from braket.pulse.ast.qasm_parser import ast_to_qasm +from braket.pulse.waveforms import _parse_waveform_from_calibration_schema @pytest.mark.parametrize( @@ -260,3 +262,66 @@ def _assert_wf_qasm(waveform, expected_qasm): p = Program(None) p.declare(waveform._to_oqpy_expression()) assert ast_to_qasm(p.to_ast(include_externs=False)) == expected_qasm + + +@pytest.mark.parametrize( + "waveform_json, waveform", + [ + ( + { + "waveformId": "q0_q1_cz_CZ", + "amplitudes": [[0.0, 0.0], [0.0, 0.0]], + }, + ArbitraryWaveform(id="q0_q1_cz_CZ", amplitudes=[complex(0.0, 0.0), complex(0.0, 0.0)]), + ), + ( + { + "waveformId": "wf_drag_gaussian_0", + "name": "drag_gaussian", + "arguments": [ + {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, + {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, + {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, + {"name": "beta", "value": 7.494904522022295e-10, "type": "float"}, + ], + }, + DragGaussianWaveform( + id="wf_drag_gaussian_0", + sigma=6.369913502160144e-9, + length=6.000000000000001e-8, + beta=7.494904522022295e-10, + amplitude=-0.4549282253548838, + ), + ), + ( + { + "waveformId": "wf_gaussian_0", + "name": "gaussian", + "arguments": [ + {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, + {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, + {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, + ], + }, + GaussianWaveform( + id="wf_gaussian_0", + length=6.000000000000001e-8, + sigma=6.369913502160144e-9, + amplitude=-0.4549282253548838, + ), + ), + ( + { + "waveformId": "wf_constant", + "name": "constant", + "arguments": [ + {"name": "length", "value": 2.1, "type": "float"}, + {"name": "iq", "value": 0.23, "type": "complex"}, + ], + }, + ConstantWaveform(id="wf_constant", length=2.1, iq=0.23), + ), + ], +) +def test_parse_waveform_from_calibration_schema(waveform_json, waveform): + assert _parse_waveform_from_calibration_schema(waveform_json) == waveform From a3779001991af9de63013c95cf0110e6dbc62ef0 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 16 Aug 2023 18:00:21 +0000 Subject: [PATCH 0812/1165] prepare release v1.54.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 c40b3452..f54f87ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.54.0 (2023-08-16) + +### Features + + * enable gate calibrations on supported devices + ## v1.53.4 (2023-08-15) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 2931b9ec..4259a856 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.53.5.dev0" +__version__ = "1.54.0" From 885a63b60ba9c708ac40ace72e58a957c8f536e9 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 16 Aug 2023 18:00:21 +0000 Subject: [PATCH 0813/1165] update development version to v1.54.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 4259a856..0e1d56aa 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.54.0" +__version__ = "1.54.1.dev0" From 4fc1244388633c309c9038b930dd8d867373f33c Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Fri, 18 Aug 2023 13:35:34 -0400 Subject: [PATCH 0814/1165] feat: add pulse control operations to autoqasm (#676) * feat: add pulse control operations * update module docstrings * avoid type check with subscripted generics * update docstrings * update docstring --- .../autoqasm/instructions/__init__.py | 4 +- .../autoqasm/instructions/pulse_control.py | 152 ++++++++++++++++ .../autoqasm/instructions/qubits.py | 12 ++ .../experimental/autoqasm/program/program.py | 10 +- .../autoqasm/test_pulse_control.py | 166 ++++++++++++++++++ 5 files changed, 340 insertions(+), 4 deletions(-) create mode 100644 src/braket/experimental/autoqasm/instructions/pulse_control.py create mode 100644 test/unit_tests/braket/experimental/autoqasm/test_pulse_control.py diff --git a/src/braket/experimental/autoqasm/instructions/__init__.py b/src/braket/experimental/autoqasm/instructions/__init__.py index 4da36596..e850590a 100644 --- a/src/braket/experimental/autoqasm/instructions/__init__.py +++ b/src/braket/experimental/autoqasm/instructions/__init__.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. -"""Instructions that apply to qubits, including quantum gates, reset and measure. +"""Instructions that apply to qubits or frames, including quantum gates, reset, measure +and pulse control. Example of using a `h` gate and a `cnot` gate to create a Bell circuit: @@ -27,3 +28,4 @@ def bell(): from .gates import * # noqa: F401, F403 from .instructions import QubitIdentifierType, reset # noqa: F401 from .measurements import measure # noqa: F401 +from .pulse_control import * # noqa: F401, F403 diff --git a/src/braket/experimental/autoqasm/instructions/pulse_control.py b/src/braket/experimental/autoqasm/instructions/pulse_control.py new file mode 100644 index 00000000..dd114f8b --- /dev/null +++ b/src/braket/experimental/autoqasm/instructions/pulse_control.py @@ -0,0 +1,152 @@ +# 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. + + +"""Pulse control instructions that apply to frames or qubits. +""" + +from typing import List, Union + +import oqpy + +from braket.circuits.qubit_set import QubitSet +from braket.experimental.autoqasm import program +from braket.pulse import PulseSequence +from braket.pulse.frame import Frame +from braket.pulse.waveforms import Waveform + +from .qubits import QubitIdentifierType, is_qubit_identifier_type + + +def _pulse_instruction(name: str, frame: Frame, *args) -> None: + """Define a pulse control instruction. + + Args: + name (str): Name of the pulse control instruction. + frame (Frame): Frame for which the instruction is apply to. + """ + program_conversion_context = program.get_program_conversion_context() + program_conversion_context._has_pulse_control = True + + pulse_sequence = PulseSequence() + pulse_sequence._program = program_conversion_context.get_oqpy_program() + with oqpy.Cal(pulse_sequence._program): + getattr(pulse_sequence, name)(frame, *args) + + +def set_frequency(frame: Frame, frequency: float) -> None: + """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. + frequency (float): Frequency value to set for the specified frame. + """ + _pulse_instruction("set_frequency", frame, frequency) + + +def shift_frequency(frame: Frame, frequency: float) -> None: + """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. + frequency (float): Frequency value by which to shift the frequency for the specified frame. + """ + _pulse_instruction("shift_frequency", frame, frequency) + + +def set_phase(frame: Frame, phase: float) -> None: + """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. + phase (float): Phase value to set for the specified frame. + """ + _pulse_instruction("set_phase", frame, phase) + + +def shift_phase(frame: Frame, phase: float) -> None: + """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. + phase (float): Phase value by which to shift the phase for the specified frame. + """ + _pulse_instruction("shift_phase", frame, phase) + + +def set_scale(frame: Frame, scale: float) -> None: + """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. + scale (float): scale value to set on the specified frame. + """ + _pulse_instruction("set_scale", frame, scale) + + +def play(frame: Frame, waveform: Waveform) -> None: + """Adds an instruction to play the specified waveform on the supplied frame. + + Args: + frame (Frame): Frame on which the specified waveform signal would be output. + waveform (Waveform): Waveform envelope specifying the signal to output on the specified + frame. + """ + _pulse_instruction("play", frame, waveform) + + +def capture_v0(frame: Frame) -> None: + """Adds an instruction to capture the bit output from measuring the specified frame. + + Args: + frame (Frame): Frame on which the capture operation needs to be performed. + """ + _pulse_instruction("capture_v0", frame) + + +def delay( + qubits_or_frames: Union[Frame, List[Frame], QubitIdentifierType, List[QubitIdentifierType]], + duration: float, +) -> None: + """Adds an instruction to advance the frame clock by the specified `duration` value. + + Args: + qubits_or_frames (Union[Frame, List[Frame], QubitIdentifierType, + List[QubitIdentifierType]]): Qubits or frame(s) on which the delay needs to be + introduced. + duration (float): Value (in seconds) defining the duration of the delay. + """ + if not isinstance(qubits_or_frames, List): + qubits_or_frames = [qubits_or_frames] + if all(is_qubit_identifier_type(q) for q in qubits_or_frames): + qubits_or_frames = QubitSet(qubits_or_frames) + _pulse_instruction("delay", qubits_or_frames, duration) + + +def barrier( + qubits_or_frames: Union[Frame, List[Frame], QubitIdentifierType, List[QubitIdentifierType]] +) -> None: + """Adds an instruction to align the frame clocks to the latest time across all the specified + frames. When applied on qubits, it prevents compilations across the barrier, if the compiler + supports barrier. + + Args: + qubits_or_frames (Union[Frame, List[Frame], QubitIdentifierType, + List[QubitIdentifierType]]): Qubits or frame(s) on which the barrier needs to be + introduced. + """ + if not isinstance(qubits_or_frames, List): + qubits_or_frames = [qubits_or_frames] + if all(is_qubit_identifier_type(q) for q in qubits_or_frames): + qubits_or_frames = QubitSet(qubits_or_frames) + _pulse_instruction("barrier", qubits_or_frames) diff --git a/src/braket/experimental/autoqasm/instructions/qubits.py b/src/braket/experimental/autoqasm/instructions/qubits.py index a0ce40f8..325960f5 100644 --- a/src/braket/experimental/autoqasm/instructions/qubits.py +++ b/src/braket/experimental/autoqasm/instructions/qubits.py @@ -25,6 +25,18 @@ QubitIdentifierType = Union[int, oqpy._ClassicalVar, oqpy.base.OQPyExpression, str] +def is_qubit_identifier_type(qubit: Any) -> bool: + """Checks if a given object is a qubit identifier type. + + Args: + qubit (Any): The object to check. + + Returns: + bool: True if the object is a qubit identifier type, False otherwise. + """ + return isinstance(qubit, QubitIdentifierType.__args__) + + def _global_qubit_register(qubit_idx_expr: Union[int, str]) -> str: # TODO: We should index into a oqpy.QubitArray rather # than manually generating the string to index into diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 9ededf4d..9471959a 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -47,14 +47,17 @@ class Program: """The program that has been generated with AutoQASM. This object can be passed to the run() method of a Braket Device.""" - def __init__(self, oqpy_program: oqpy.Program): + def __init__(self, oqpy_program: oqpy.Program, has_pulse_control: bool = False): """Initializes an AutoQASM Program object. Args: oqpy_program (oqpy.Program): The oqpy program object which contains the generated program. + has_pulse_control (bool): Whether the program contains pulse + control instructions. Defaults to False. """ self._oqpy_program = oqpy_program + self._has_pulse_control = has_pulse_control def to_ir( self, @@ -73,7 +76,7 @@ def to_ir( str: A representation of the program in the `ir_type` format. """ if ir_type == IRType.OPENQASM: - return self._oqpy_program.to_qasm() + return self._oqpy_program.to_qasm(encal_declarations=self._has_pulse_control) raise ValueError(f"Supplied ir_type {ir_type} is not supported.") @@ -88,6 +91,7 @@ def __init__(self, user_config: Optional[UserConfig] = None): self.return_variable = None self._qubits_seen = set() self._var_idx = 0 + self._has_pulse_control = False def make_program(self) -> Program: """Makes a Program object using the oqpy program from this conversion context. @@ -95,7 +99,7 @@ def make_program(self) -> Program: Returns: Program: The program object. """ - return Program(self.get_oqpy_program()) + return Program(self.get_oqpy_program(), has_pulse_control=self._has_pulse_control) @property def qubits(self) -> List[int]: diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pulse_control.py b/test/unit_tests/braket/experimental/autoqasm/test_pulse_control.py new file mode 100644 index 00000000..6d4ce10d --- /dev/null +++ b/test/unit_tests/braket/experimental/autoqasm/test_pulse_control.py @@ -0,0 +1,166 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +"""Tests for the pulse control module.""" + +import textwrap + +import pytest + +import braket.experimental.autoqasm as aq +from braket.experimental.autoqasm.instructions import ( + barrier, + capture_v0, + delay, + h, + play, + set_frequency, + set_phase, + set_scale, + shift_frequency, + shift_phase, +) +from braket.pulse import ArbitraryWaveform, Frame, Port + +PORT = Port(port_id="device_port_x0", dt=1e-9, properties={}) +FRAME1 = Frame(frame_id="predefined_frame_1", frequency=2e9, port=PORT, phase=0, is_predefined=True) +FRAME2 = Frame(frame_id="predefined_frame_2", frequency=1e6, port=PORT, is_predefined=True) +WAVEFORM = ArbitraryWaveform([complex(1, 0.4), 0, 0.3, complex(0.1, 0.2)], id="arb_wf") + + +def test_mix_gate_pulse() -> None: + """Test mixed usage of gates and pulses.""" + + @aq.function + def my_program(): + shift_frequency(FRAME1, 0.1234) + h(1) + play(FRAME1, WAVEFORM) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + defcalgrammar "openpulse"; + cal { + waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im}; + } + qubit[2] __qubits__; + cal { + shift_frequency(predefined_frame_1, 0.1234); + } + h __qubits__[1]; + cal { + play(predefined_frame_1, arb_wf); + } + """ + ).strip() + qasm = my_program().to_ir() + assert qasm == expected + + +def test_merge_cal_box() -> None: + """Test subsequent cal boxes are merged.""" + + @aq.function + def my_program(): + barrier(0) + delay([3, 4], 0.34) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + defcalgrammar "openpulse"; + cal { + barrier $0; + delay[340000000.0ns] $3, $4; + } + """ + ).strip() + qasm = my_program().to_ir() + assert qasm == expected + + +@pytest.mark.parametrize( + "instruction,qubits_or_frames,params,expected_qasm", + [ + ( + shift_frequency, + FRAME1, + [0.12], + "\ncal {\n shift_frequency(predefined_frame_1, 0.12);\n}", + ), + ( + set_frequency, + FRAME1, + [0.12], + "\ncal {\n set_frequency(predefined_frame_1, 0.12);\n}", + ), + ( + shift_phase, + FRAME1, + [0.12], + "\ncal {\n shift_phase(predefined_frame_1, 0.12);\n}", + ), + ( + set_phase, + FRAME1, + [0.12], + "\ncal {\n set_phase(predefined_frame_1, 0.12);\n}", + ), + ( + set_scale, + FRAME1, + [0.12], + "\ncal {\n set_scale(predefined_frame_1, 0.12);\n}", + ), + (delay, 3, [0.34], "\ncal {\n delay[340000000.0ns] $3;\n}"), + (delay, [3, 4], [0.34], "\ncal {\n delay[340000000.0ns] $3, $4;\n}"), + ( + delay, + FRAME1, + [0.34], + "\ncal {\n delay[340000000.0ns] predefined_frame_1;\n}", + ), + ( + delay, + [FRAME1, FRAME2], + [0.34], + "\ncal {\n delay[340000000.0ns] predefined_frame_1, predefined_frame_2;\n}", + ), + (barrier, 3, [], "\ncal {\n barrier $3;\n}"), + (barrier, [3, 4], [], "\ncal {\n barrier $3, $4;\n}"), + (barrier, FRAME1, [], "\ncal {\n barrier predefined_frame_1;\n}"), + ( + barrier, + [FRAME1, FRAME2], + [], + "\ncal {\n barrier predefined_frame_1, predefined_frame_2;\n}", + ), + ( + play, + FRAME1, + [WAVEFORM], + ( + "\ncal {\n waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};" + "\n play(predefined_frame_1, arb_wf);\n}" + ), + ), + (capture_v0, FRAME1, [], "\ncal {\n capture_v0(predefined_frame_1);\n}"), + ], +) +def test_pulse_control(instruction, qubits_or_frames, params, expected_qasm) -> None: + """Test pulse control operations.""" + with aq.build_program() as program_conversion_context: + instruction(qubits_or_frames, *params) + + assert expected_qasm in program_conversion_context.make_program().to_ir() From e6d86f07877c095b4de83b8aa38ed9274036b2d6 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Fri, 18 Aug 2023 14:06:54 -0400 Subject: [PATCH 0815/1165] change: Update oqpy version for feature/autoqasm branch (#677) * Update oqpy commit hash * Fix pulse tests after oqpy version upgrade * Lint * Update oqpy commit hash * Use correct return type hint in test * Coverage * Adjust duration literals in pulse control tests * Update OQ3 duration literals in tests --- setup.py | 2 +- src/braket/devices/local_simulator.py | 4 ++- src/braket/pulse/pulse_sequence.py | 4 +-- .../braket/circuits/test_circuit.py | 34 ++++++++----------- .../braket/circuits/test_gate_calibration.py | 6 ++-- .../braket/experimental/autoqasm/test_api.py | 8 ++--- .../experimental/autoqasm/test_operators.py | 2 +- .../experimental/autoqasm/test_program.py | 4 +-- .../autoqasm/test_pulse_control.py | 10 +++--- .../experimental/autoqasm/test_types.py | 14 ++++---- .../braket/pulse/test_pulse_sequence.py | 31 ++++++++--------- .../unit_tests/braket/pulse/test_waveforms.py | 22 +++++------- 12 files changed, 65 insertions(+), 76 deletions(-) diff --git a/setup.py b/setup.py index 07c0cdb9..9caac452 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ # Pin the latest commit of the qubit-array branch of ajberdy/oqpy.git to get the version of # oqpy which contains changes that AutoQASM relies on, including the QubitArray type. # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main. - "oqpy @ git+https://github.com/ajberdy/oqpy.git@7e5885af6193009265c8195dad7553db02bdfd96#egg=oqpy", # noqa E501 + "oqpy @ git+https://github.com/ajberdy/oqpy.git@26cf4f9089c3b381370917734d2d964c45c4458d#egg=oqpy", # noqa E501 "setuptools", "backoff", "boltons", diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index c81c7853..03ab81dd 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -212,7 +212,9 @@ def _run_internal_wrap( **kwargs, ) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: """Wraps _run_interal for pickle dump""" - return self._run_internal(task_specification, shots, inputs=inputs, *args, **kwargs) + return self._run_internal( + task_specification, shots, inputs=inputs, *args, **kwargs + ) # pragma: no cover (this line sometimes doesn't get detected by codecov) @singledispatchmethod def _get_simulator(self, simulator: Union[str, BraketSimulator]) -> LocalSimulator: diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index 3606bbd1..9455e12f 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -46,7 +46,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() @@ -278,7 +278,7 @@ def make_bound_pulse_sequence(self, param_values: Dict[str, float]) -> PulseSequ tree: ast.Program = program.to_ast(include_externs=False, ignore_needs_declaration=True) new_tree: ast.Program = _FreeParameterTransformer(param_values).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 x in new_tree.statements: diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index fd2170a5..81f75695 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);", @@ -3360,11 +3358,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 {", @@ -3477,7 +3473,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 {", @@ -3502,7 +3498,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 31c2384d..4037f3fa 100644 --- a/test/unit_tests/braket/circuits/test_gate_calibration.py +++ b/test/unit_tests/braket/circuits/test_gate_calibration.py @@ -80,7 +80,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;", "}", ] ) @@ -100,7 +100,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;", "}", ] ) @@ -118,7 +118,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/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index 539f10fc..fef676cf 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -316,7 +316,7 @@ def test_ghz_qasm_for_loop() -> None: expected = """OPENQASM 3.0; qubit[5] __qubits__; h __qubits__[0]; -for int i in [0:3] { +for int i in [0:4 - 1] { cnot __qubits__[i], __qubits__[i + 1]; }""" assert ghz_qasm_for_loop().to_ir() == expected @@ -684,10 +684,10 @@ def test_multi_for_loop() -> None: """ expected = """OPENQASM 3.0; qubit[6] __qubits__; -for int i in [0:2] { +for int i in [0:3 - 1] { cnot __qubits__[i], __qubits__[i + 1]; } -for int i in [0:4] { +for int i in [0:5 - 1] { h __qubits__[i]; }""" @@ -722,7 +722,7 @@ def bell(int[32] q0, int[32] q1) { cnot __qubits__[q0], __qubits__[q1]; } qubit[5] __qubits__; -for int i in [0:2] { +for int i in [0:3 - 1] { bell(0, 1); }""" diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py index 7f4999dd..2f4e7d4a 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_operators.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py @@ -191,7 +191,7 @@ def test_control_flow_for_loop_qasm() -> None: qasm = program_conversion_context.make_program().to_ir() expected_qasm = """OPENQASM 3.0; -for int idx in [0:2] { +for int idx in [0:3 - 1] { h __qubits__[idx]; }""" diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/braket/experimental/autoqasm/test_program.py index 381c9502..c33bd2c9 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_program.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_program.py @@ -97,8 +97,8 @@ def circuit(float[64] angle) { } qubit[2] __qubits__; for int i in [0:""" - + str(scale - 1) - + """] { + + str(scale) + + """ - 1] { circuit(""" + str(angle) + """); diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pulse_control.py b/test/unit_tests/braket/experimental/autoqasm/test_pulse_control.py index 6d4ce10d..4721c0d0 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_pulse_control.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_pulse_control.py @@ -82,7 +82,7 @@ def my_program(): defcalgrammar "openpulse"; cal { barrier $0; - delay[340000000.0ns] $3, $4; + delay[340.0ms] $3, $4; } """ ).strip() @@ -123,19 +123,19 @@ def my_program(): [0.12], "\ncal {\n set_scale(predefined_frame_1, 0.12);\n}", ), - (delay, 3, [0.34], "\ncal {\n delay[340000000.0ns] $3;\n}"), - (delay, [3, 4], [0.34], "\ncal {\n delay[340000000.0ns] $3, $4;\n}"), + (delay, 3, [0.34], "\ncal {\n delay[340.0ms] $3;\n}"), + (delay, [3, 4], [0.34], "\ncal {\n delay[340.0ms] $3, $4;\n}"), ( delay, FRAME1, [0.34], - "\ncal {\n delay[340000000.0ns] predefined_frame_1;\n}", + "\ncal {\n delay[340.0ms] predefined_frame_1;\n}", ), ( delay, [FRAME1, FRAME2], [0.34], - "\ncal {\n delay[340000000.0ns] predefined_frame_1, predefined_frame_2;\n}", + "\ncal {\n delay[340.0ms] predefined_frame_1, predefined_frame_2;\n}", ), (barrier, 3, [], "\ncal {\n barrier $3;\n}"), (barrier, [3, 4], [], "\ncal {\n barrier $3, $4;\n}"), diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index c700b138..a7cd0709 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -518,7 +518,7 @@ def retval_constant() -> int: return 3 @aq.function - def retval_recursive() -> int: + def retval_recursive() -> float: a = 2 * retval_recursive() + (retval_constant() + 2) / 3 return a @@ -527,20 +527,20 @@ def caller() -> int: return retval_recursive() expected_qasm = """OPENQASM 3.0; -def retval_recursive() -> int[32] { - int[32] __int_1__; - __int_1__ = retval_recursive(); +def retval_recursive() -> float[64] { + float[64] __float_1__; + __float_1__ = retval_recursive(); int[32] __int_3__; __int_3__ = retval_constant(); - return 2 * __int_1__ + (__int_3__ + 2) / 3; + return 2 * __float_1__ + (__int_3__ + 2) / 3; } def retval_constant() -> int[32] { int[32] retval_; retval_ = 3; return retval_; } -int[32] __int_4__; -__int_4__ = retval_recursive();""" +float[64] __float_4__; +__float_4__ = retval_recursive();""" assert caller().to_ir() == expected_qasm diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py index 14add748..cb554c82 100644 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -143,7 +143,7 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined " predefined_frame_1, predefined_frame_2;" ), " delay[(1000000000.0*a + 2000000000.0*b)ns] predefined_frame_1;", - " delay[1000000.0ns] 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,10 +173,9 @@ 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);", + " waveform gauss_wf = gaussian(1.0ms, (1000000000.0*sigma_g)ns, 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);", @@ -187,7 +186,7 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined " 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[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);", @@ -206,10 +205,9 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined [ "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);", @@ -220,7 +218,7 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined " 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[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 +309,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 +321,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 b42eacc0..b2421ca6 100644 --- a/test/unit_tests/braket/pulse/test_waveforms.py +++ b/test/unit_tests/braket/pulse/test_waveforms.py @@ -74,7 +74,7 @@ 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_default_params(): @@ -108,7 +108,7 @@ def test_constant_wf_free_params(): 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 +126,7 @@ 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_default_params(): @@ -167,7 +165,7 @@ def test_gaussian_waveform(): assert wf.sigma == sigma assert wf.length == length - _assert_wf_qasm(wf, "waveform gauss_wf = gaussian(4.0ns, 300000000.0ns, 0.4, false);") + _assert_wf_qasm(wf, "waveform gauss_wf = gaussian(4.0ns, 300.0ms, 0.4, false);") def test_drag_gaussian_wf_free_params(): @@ -200,15 +198,13 @@ def test_drag_gaussian_wf_free_params(): ] _assert_wf_qasm( wf_2, - "waveform d_gauss_wf = drag_gaussian(600000000.0ns, (1000000000.0*sigma_b " + "waveform d_gauss_wf = drag_gaussian(600.0ms, (1000000000.0*sigma_b " "+ 400000000.0)ns, 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_default_params(): @@ -249,13 +245,11 @@ def test_gaussian_wf_free_params(): 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);" - ) + _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] - _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 b158736a5f394fd709f4d12d6f5e0890d05dbbf6 Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Mon, 21 Aug 2023 09:53:42 -0700 Subject: [PATCH 0816/1165] update: restricting parameter names to not collide with ones we use for OpenQASM generation. (#675) --- src/braket/circuits/circuit.py | 6 +- src/braket/circuits/serialization.py | 4 +- src/braket/parametric/free_parameter.py | 16 +- src/braket/pulse/ast/qasm_transformer.py | 4 +- test/integ_tests/test_adjoint_gradient.py | 32 +- .../braket/aws/test_aws_quantum_task.py | 6 +- .../braket/circuits/test_circuit.py | 530 +++++++++--------- test/unit_tests/braket/circuits/test_gates.py | 126 +++-- .../unit_tests/braket/circuits/test_noises.py | 22 +- .../braket/circuits/test_observables.py | 20 +- .../braket/circuits/test_result_types.py | 24 +- .../braket/devices/test_local_simulator.py | 8 +- .../braket/parametric/test_free_parameter.py | 33 ++ 13 files changed, 446 insertions(+), 385 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 7e3bfcd4..d3bf2d0e 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -1212,7 +1212,7 @@ def _to_openqasm( ) for idx, qubit in enumerate(qubits): qubit_target = serialization_properties.format_target(int(qubit)) - ir_instructions.append(f"b[{idx}] = measure {qubit_target};") + ir_instructions.append(f"__bits__[{idx}] = measure {qubit_target};") return OpenQasmProgram.construct(source="\n".join(ir_instructions), inputs={}) @@ -1225,11 +1225,11 @@ 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;") + ir_instructions.append(f"bit[{self.qubit_count}] __bits__;") if serialization_properties.qubit_reference_type == QubitReferenceType.VIRTUAL: total_qubits = max(self.qubits).real + 1 - ir_instructions.append(f"qubit[{total_qubits}] q;") + ir_instructions.append(f"qubit[{total_qubits}] __qubits__;") elif serialization_properties.qubit_reference_type != QubitReferenceType.PHYSICAL: raise ValueError( f"Invalid qubit_reference_type " diff --git a/src/braket/circuits/serialization.py b/src/braket/circuits/serialization.py index 1e0826e8..596a1f36 100644 --- a/src/braket/circuits/serialization.py +++ b/src/braket/circuits/serialization.py @@ -39,7 +39,7 @@ class OpenQASMSerializationProperties: Properties for serializing a circuit to OpenQASM. qubit_reference_type (QubitReferenceType): determines whether to use - logical qubits or physical qubits (q[i] vs $i). + logical qubits or physical qubits (__qubits__[i] vs $i). """ qubit_reference_type: QubitReferenceType = QubitReferenceType.VIRTUAL @@ -53,7 +53,7 @@ def format_target(self, target: int) -> str: str: The OpenQASM representation of the target qubit. """ qubit_reference_format = ( - "q[{}]" if self.qubit_reference_type == QubitReferenceType.VIRTUAL else "${}" + "__qubits__[{}]" if self.qubit_reference_type == QubitReferenceType.VIRTUAL else "${}" ) return qubit_reference_format.format(target) diff --git a/src/braket/parametric/free_parameter.py b/src/braket/parametric/free_parameter.py index 3eed47ef..27e588d7 100644 --- a/src/braket/parametric/free_parameter.py +++ b/src/braket/parametric/free_parameter.py @@ -44,12 +44,13 @@ def __init__(self, name: str): Args: name (str): Name of the :class:'FreeParameter'. Can be a unicode value. + Must not start with '__'. Examples: >>> param1 = FreeParameter("theta") >>> param1 = FreeParameter("\u03B8") """ - self._name = Symbol(name) + self._set_name(name) super().__init__(expression=self._name) @property @@ -99,6 +100,19 @@ def to_dict(self) -> dict: "name": self.name, } + def _set_name(self, name: str) -> None: + FreeParameter._validate_name(name) + self._name = Symbol(name) + + @staticmethod + def _validate_name(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 name.startswith("__"): + raise ValueError("FreeParameter names must not start with two underscores '__'") + @classmethod def from_dict(cls, parameter: dict) -> FreeParameter: return FreeParameter(parameter["name"]) diff --git a/src/braket/pulse/ast/qasm_transformer.py b/src/braket/pulse/ast/qasm_transformer.py index ae4cccad..57a9018e 100644 --- a/src/braket/pulse/ast/qasm_transformer.py +++ b/src/braket/pulse/ast/qasm_transformer.py @@ -43,8 +43,8 @@ def visit_ExpressionStatement(self, expression_statement: ast.ExpressionStatemen ): # For capture_v0 nodes, it replaces it with classical assignment statements # of the form: - # b[0] = capture_v0(...) - # b[1] = capture_v0(...) + # __bits__[0] = capture_v0(...) + # __bits__[1] = capture_v0(...) new_val = ast.ClassicalAssignment( # Ideally should use IndexedIdentifier here, but this works since it is just # for printing. diff --git a/test/integ_tests/test_adjoint_gradient.py b/test/integ_tests/test_adjoint_gradient.py index 99ab7dfe..0f0b6982 100644 --- a/test/integ_tests/test_adjoint_gradient.py +++ b/test/integ_tests/test_adjoint_gradient.py @@ -42,10 +42,10 @@ def test_adjoint_gradient_quantum_task_with_nested_targets( expected_openqasm = ( "OPENQASM 3.0;\n" "input float theta;\n" - "qubit[4] q;\n" - "rx(theta) q[0];\n" - "#pragma braket result adjoint_gradient expectation(-6 * y(q[0]) @ i(q[1]) + 0.75 * " - "y(q[2]) @ z(q[3])) theta" + "qubit[4] __qubits__;\n" + "rx(theta) __qubits__[0];\n" + "#pragma braket result adjoint_gradient expectation(-6 * " + "y(__qubits__[0]) @ i(__qubits__[1]) + 0.75 * y(__qubits__[2]) @ z(__qubits__[3])) theta" ) gradient_task = AwsQuantumTask.create( @@ -83,10 +83,10 @@ def test_adjoint_gradient_with_standard_observable_terms( expected_openqasm = ( "OPENQASM 3.0;\n" "input float theta;\n" - "qubit[3] q;\n" - "rx(theta) q[0];\n" - "#pragma braket result adjoint_gradient expectation(2 * x(q[0]) + 3 * y(q[1]) " - "- 1 * z(q[2])) theta" + "qubit[3] __qubits__;\n" + "rx(theta) __qubits__[0];\n" + "#pragma braket result adjoint_gradient expectation(2 * " + "x(__qubits__[0]) + 3 * y(__qubits__[1]) - 1 * z(__qubits__[2])) theta" ) gradient_task = AwsQuantumTask.create( @@ -132,17 +132,19 @@ def test_adjoint_gradient_with_batch_circuits(aws_session, s3_destination_folder ( "OPENQASM 3.0;\n" "input float theta;\n" - "qubit[2] q;\n" - "rx(theta) q[0];\n" - "#pragma braket result adjoint_gradient expectation(6 * y(q[0]) @ i(q[1])) theta" + "qubit[2] __qubits__;\n" + "rx(theta) __qubits__[0];\n" + "#pragma braket result adjoint_gradient expectation(6 *" + " y(__qubits__[0]) @ i(__qubits__[1])) theta" ), ( "OPENQASM 3.0;\n" "input float theta;\n" - "qubit[2] q;\n" - "rx(theta) q[0];\n" - "#pragma braket result adjoint_gradient expectation(-6 * y(q[0]) @ i(q[1]) + 0.75 * " - "y(q[0]) @ z(q[1])) theta" + "qubit[2] __qubits__;\n" + "rx(theta) __qubits__[0];\n" + "#pragma braket result adjoint_gradient expectation(-6 *" + " y(__qubits__[0]) @ i(__qubits__[1]) + 0.75 *" + " y(__qubits__[0]) @ z(__qubits__[1])) theta" ), ] 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 de8ead78..0e16a738 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -573,12 +573,12 @@ def test_create_pulse_gate_circuit( expected_openqasm = "\n".join( ( "OPENQASM 3.0;", - "bit[2] b;", + "bit[2] __bits__;", "cal {", " set_frequency(predefined_frame_1, 6000000.0);", "}", - "b[0] = measure $0;", - "b[1] = measure $1;", + "__bits__[0] = measure $0;", + "__bits__[1] = measure $1;", ) ) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index fd2170a5..21f972b8 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -736,8 +736,8 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): source="\n".join( [ "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", + "bit[2] __bits__;", + "qubit[2] __qubits__;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -750,10 +750,10 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): " 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];", + "rx(0.15) __qubits__[0];", + "rx(0.3) __qubits__[1];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -766,7 +766,7 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): source="\n".join( [ "OPENQASM 3.0;", - "bit[2] b;", + "bit[2] __bits__;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -781,8 +781,8 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "}", "rx(0.15) $0;", "rx(0.3) $4;", - "b[0] = measure $0;", - "b[1] = measure $4;", + "__bits__[0] = measure $0;", + "__bits__[1] = measure $4;", ] ), inputs={}, @@ -832,7 +832,7 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): source="\n".join( [ "OPENQASM 3.0;", - "qubit[5] q;", + "qubit[5] __qubits__;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -845,10 +845,10 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): " 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) __qubits__[0];", + "rx(0.3) __qubits__[4];", + "#pragma braket noise bit_flip(0.2) __qubits__[3]", + "#pragma braket result expectation i(__qubits__[0])", ] ), inputs={}, @@ -862,8 +862,8 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): [ "OPENQASM 3.0;", "input float theta;", - "bit[2] b;", - "qubit[2] q;", + "bit[2] __bits__;", + "qubit[2] __qubits__;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -876,10 +876,10 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): " 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];", + "rx(0.15) __qubits__[0];", + "rx(theta) __qubits__[1];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -895,8 +895,8 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): source="\n".join( [ "OPENQASM 3.0;", - "bit[5] b;", - "qubit[5] q;", + "bit[5] __bits__;", + "qubit[5] __qubits__;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -909,14 +909,14 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): " 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];", + "negctrl @ rx(0.15) __qubits__[2], __qubits__[0];", + "ctrl(2) @ rx(0.3) __qubits__[2], __qubits__[3], __qubits__[1];", + "ctrl(2) @ cnot __qubits__[2], __qubits__[3], __qubits__[4], __qubits__[0];", # noqa + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", + "__bits__[2] = measure __qubits__[2];", + "__bits__[3] = measure __qubits__[3];", + "__bits__[4] = measure __qubits__[4];", ] ), inputs={}, @@ -929,8 +929,8 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): source="\n".join( [ "OPENQASM 3.0;", - "bit[7] b;", - "qubit[7] q;", + "bit[7] __bits__;", + "qubit[7] __qubits__;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -939,16 +939,16 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): " 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];", + "cnot __qubits__[0], __qubits__[1];", + "cnot __qubits__[3], __qubits__[2];", + "ctrl @ cnot __qubits__[5], __qubits__[6], __qubits__[4];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", + "__bits__[2] = measure __qubits__[2];", + "__bits__[3] = measure __qubits__[3];", + "__bits__[4] = measure __qubits__[4];", + "__bits__[5] = measure __qubits__[5];", + "__bits__[6] = measure __qubits__[6];", ] ), inputs={}, @@ -961,8 +961,8 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): source="\n".join( [ "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", + "bit[2] __bits__;", + "qubit[2] __qubits__;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -977,11 +977,11 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): " 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];", + "inv @ pow(2.5) @ h __qubits__[0];", + "pow(0) @ h __qubits__[0];", + "ms(-0.1, -0.2, -0.3) __qubits__[0], __qubits__[1];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -1030,8 +1030,8 @@ def test_parametric_circuit_with_fixed_argument_defcal(pulse_sequence): [ "OPENQASM 3.0;", "input float theta;", - "bit[1] b;", - "qubit[1] q;", + "bit[1] __bits__;", + "qubit[1] __qubits__;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -1044,10 +1044,10 @@ def test_parametric_circuit_with_fixed_argument_defcal(pulse_sequence): " 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];", + "inv @ pow(2.5) @ h __qubits__[0];", + "pow(0) @ h __qubits__[0];", + "rx(theta) __qubits__[0];", + "__bits__[0] = measure __qubits__[0];", ] ), inputs={}, @@ -1128,8 +1128,8 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", + "bit[1] __bits__;", + "qubit[1] __qubits__;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -1140,8 +1140,8 @@ def foo( " shift_phase(predefined_frame_1, -0.2);", " play(predefined_frame_1, drag_gauss_wf);", "}", - "foo(-0.2) q[0];", - "b[0] = measure q[0];", + "foo(-0.2) __qubits__[0];", + "__bits__[0] = measure __qubits__[0];", ] ), inputs={}, @@ -1166,11 +1166,11 @@ def foo( 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];", + "bit[2] __bits__;", + "qubit[2] __qubits__;", + "negctrl @ h __qubits__[1], __qubits__[0];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -1182,11 +1182,11 @@ def foo( 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];", + "bit[2] __bits__;", + "qubit[2] __qubits__;", + "cnot __qubits__[1], __qubits__[0];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -1198,11 +1198,11 @@ def foo( 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];", + "bit[2] __bits__;", + "qubit[2] __qubits__;", + "negctrl @ x __qubits__[1], __qubits__[0];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -1214,11 +1214,11 @@ def foo( 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];", + "bit[2] __bits__;", + "qubit[2] __qubits__;", + "ctrl @ rx(0.15) __qubits__[1], __qubits__[0];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -1230,11 +1230,11 @@ def foo( 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];", + "bit[2] __bits__;", + "qubit[2] __qubits__;", + "ctrl @ ry(0.2) __qubits__[1], __qubits__[0];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -1246,11 +1246,11 @@ def foo( 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];", + "bit[2] __bits__;", + "qubit[2] __qubits__;", + "negctrl @ rz(0.25) __qubits__[1], __qubits__[0];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -1262,11 +1262,11 @@ def foo( 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];", + "bit[2] __bits__;", + "qubit[2] __qubits__;", + "negctrl @ s __qubits__[1], __qubits__[0];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -1278,11 +1278,11 @@ def foo( 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];", + "bit[2] __bits__;", + "qubit[2] __qubits__;", + "negctrl @ t __qubits__[0], __qubits__[1];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -1294,11 +1294,11 @@ def foo( 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];", + "bit[2] __bits__;", + "qubit[2] __qubits__;", + "cphaseshift(0.15) __qubits__[1], __qubits__[0];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -1310,12 +1310,12 @@ def foo( 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];", + "bit[3] __bits__;", + "qubit[3] __qubits__;", + "ccnot __qubits__[0], __qubits__[1], __qubits__[2];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", + "__bits__[2] = measure __qubits__[2];", ] ), inputs={}, @@ -1327,8 +1327,8 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[1] q;", - "h q[0];", + "qubit[1] __qubits__;", + "h __qubits__[0];", "#pragma braket result state_vector", ] ), @@ -1341,9 +1341,9 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[1] q;", - "h q[0];", - "#pragma braket result expectation x(q[0])", + "qubit[1] __qubits__;", + "h __qubits__[0];", + "#pragma braket result expectation x(__qubits__[0])", ] ), inputs={}, @@ -1355,9 +1355,9 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[2] q;", - "h q[0];", - "#pragma braket result expectation h(q[0]) @ x(q[1])", + "qubit[2] __qubits__;", + "h __qubits__[0];", + "#pragma braket result expectation h(__qubits__[0]) @ x(__qubits__[1])", ] ), inputs={}, @@ -1369,9 +1369,9 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[2] q;", - "h q[0];", - "#pragma braket result variance h(q[0]) @ x(q[1])", + "qubit[2] __qubits__;", + "h __qubits__[0];", + "#pragma braket result variance h(__qubits__[0]) @ x(__qubits__[1])", ] ), inputs={}, @@ -1383,9 +1383,9 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[1] q;", - "h q[0];", - "#pragma braket result probability q[0]", + "qubit[1] __qubits__;", + "h __qubits__[0];", + "#pragma braket result probability __qubits__[0]", ] ), inputs={}, @@ -1397,10 +1397,10 @@ def foo( 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];", + "bit[1] __bits__;", + "qubit[1] __qubits__;", + "#pragma braket noise bit_flip(0.1) __qubits__[0]", + "__bits__[0] = measure __qubits__[0];", ] ), inputs={}, @@ -1412,10 +1412,10 @@ def foo( 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];", + "bit[1] __bits__;", + "qubit[1] __qubits__;", + "#pragma braket noise generalized_amplitude_damping(0.1, 0.1) __qubits__[0]", # noqa + "__bits__[0] = measure __qubits__[0];", ] ), inputs={}, @@ -1427,10 +1427,10 @@ def foo( 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];", + "bit[1] __bits__;", + "qubit[1] __qubits__;", + "#pragma braket noise phase_flip(0.2) __qubits__[0]", + "__bits__[0] = measure __qubits__[0];", ] ), inputs={}, @@ -1442,10 +1442,10 @@ def foo( 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];", + "bit[1] __bits__;", + "qubit[1] __qubits__;", + "#pragma braket noise depolarizing(0.5) __qubits__[0]", + "__bits__[0] = measure __qubits__[0];", ] ), inputs={}, @@ -1457,10 +1457,10 @@ def foo( 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];", + "bit[1] __bits__;", + "qubit[1] __qubits__;", + "#pragma braket noise amplitude_damping(0.8) __qubits__[0]", + "__bits__[0] = measure __qubits__[0];", ] ), inputs={}, @@ -1472,10 +1472,10 @@ def foo( 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];", + "bit[1] __bits__;", + "qubit[1] __qubits__;", + "#pragma braket noise phase_damping(0.1) __qubits__[0]", + "__bits__[0] = measure __qubits__[0];", ] ), inputs={}, @@ -1487,8 +1487,8 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[1] q;", - "h q[0];", + "qubit[1] __qubits__;", + "h __qubits__[0];", '#pragma braket result amplitude "0", "1"', ] ), @@ -1504,16 +1504,16 @@ def foo( 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];", + "bit[5] __bits__;", + "qubit[5] __qubits__;", + "negctrl @ rx(0.15) __qubits__[2], __qubits__[0];", + "ctrl(2) @ rx(0.3) __qubits__[2], __qubits__[3], __qubits__[1];", + "ctrl(2) @ cnot __qubits__[2], __qubits__[3], __qubits__[4], __qubits__[0];", # noqa + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", + "__bits__[2] = measure __qubits__[2];", + "__bits__[3] = measure __qubits__[3];", + "__bits__[4] = measure __qubits__[4];", ] ), inputs={}, @@ -1525,18 +1525,18 @@ def foo( 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];", + "bit[7] __bits__;", + "qubit[7] __qubits__;", + "cnot __qubits__[0], __qubits__[1];", + "cnot __qubits__[3], __qubits__[2];", + "ctrl @ cnot __qubits__[5], __qubits__[6], __qubits__[4];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", + "__bits__[2] = measure __qubits__[2];", + "__bits__[3] = measure __qubits__[3];", + "__bits__[4] = measure __qubits__[4];", + "__bits__[5] = measure __qubits__[5];", + "__bits__[6] = measure __qubits__[6];", ] ), inputs={}, @@ -1548,11 +1548,11 @@ def foo( 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];", + "bit[1] __bits__;", + "qubit[1] __qubits__;", + "inv @ pow(2.5) @ h __qubits__[0];", + "pow(0) @ h __qubits__[0];", + "__bits__[0] = measure __qubits__[0];", ] ), inputs={}, @@ -1564,10 +1564,10 @@ def foo( 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];", + "bit[1] __bits__;", + "qubit[1] __qubits__;", + "#pragma braket unitary([[0, 1.0], [1.0, 0]]) __qubits__[0]", + "__bits__[0] = measure __qubits__[0];", ] ), inputs={}, @@ -1579,10 +1579,10 @@ def foo( 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];", + "bit[1] __bits__;", + "qubit[1] __qubits__;", + "#pragma braket noise pauli_channel(0.1, 0.2, 0.3) __qubits__[0]", + "__bits__[0] = measure __qubits__[0];", ] ), inputs={}, @@ -1594,11 +1594,11 @@ def foo( 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];", + "bit[2] __bits__;", + "qubit[2] __qubits__;", + "#pragma braket noise two_qubit_depolarizing(0.1) __qubits__[0], __qubits__[1]", # noqa + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -1610,11 +1610,11 @@ def foo( 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];", + "bit[2] __bits__;", + "qubit[2] __qubits__;", + "#pragma braket noise two_qubit_dephasing(0.1) __qubits__[0], __qubits__[1]", # noqa + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -1626,11 +1626,11 @@ def foo( 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];", + "bit[2] __bits__;", + "qubit[2] __qubits__;", + "#pragma braket noise two_qubit_dephasing(0.1) __qubits__[0], __qubits__[1]", # noqa + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -1642,9 +1642,9 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[1] q;", - "h q[0];", - "#pragma braket result sample z(q[0])", + "qubit[1] __qubits__;", + "h __qubits__[0];", + "#pragma braket result sample z(__qubits__[0])", ] ), inputs={}, @@ -1656,9 +1656,9 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[1] q;", - "h q[0];", - "#pragma braket result sample z(q[0])", + "qubit[1] __qubits__;", + "h __qubits__[0];", + "#pragma braket result sample z(__qubits__[0])", ] ), inputs={}, @@ -1670,10 +1670,10 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[2] q;", - "h q[0];", - "x q[1];", - "#pragma braket result density_matrix q[0], q[1]", + "qubit[2] __qubits__;", + "h __qubits__[0];", + "x __qubits__[1];", + "#pragma braket result density_matrix __qubits__[0], __qubits__[1]", ] ), inputs={}, @@ -1691,12 +1691,12 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", + "bit[1] __bits__;", + "qubit[1] __qubits__;", "#pragma braket noise " "kraus([[0.9486833im, 0], [0, 0.9486833im]], [[0, 0.31622777], " - "[0.31622777, 0]]) q[0]", - "b[0] = measure q[0];", + "[0.31622777, 0]]) __qubits__[0]", + "__bits__[0] = measure __qubits__[0];", ] ), inputs={}, @@ -1709,10 +1709,10 @@ def foo( [ "OPENQASM 3.0;", "input float theta;", - "bit[1] b;", - "qubit[1] q;", - "rx(theta) q[0];", - "b[0] = measure q[0];", + "bit[1] __bits__;", + "qubit[1] __qubits__;", + "rx(theta) __qubits__[0];", + "__bits__[0] = measure __qubits__[0];", ] ), inputs={}, @@ -1733,11 +1733,11 @@ def test_from_ir_inputs_updated(): "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];", + "bit[1] __bits__;", + "qubit[1] __qubits__;", + "rx(theta) __qubits__[0];", + "ry(phi) __qubits__[0];", + "__bits__[0] = measure __qubits__[0];", ] ), inputs={"theta": 0.2, "phi": 0.3}, @@ -1754,15 +1754,15 @@ def test_from_ir_inputs_updated(): source="\n".join( [ "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", + "bit[2] __bits__;", + "qubit[2] __qubits__;", "gate my_gate a,b {", "h a;", - "cnot a,b;", + "cnot a, b;", "}", - "my_gate q[0], q[1];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", + "my_gate __qubits__[0], __qubits__[1];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -1774,15 +1774,15 @@ def test_from_ir_inputs_updated(): source="\n".join( [ "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", + "bit[2] __bits__;", + "qubit[2] __qubits__;", "def my_sub(qubit q) {", "h q;", "}", - "h q[0];", - "my_sub(q[1]);", - "b[0] = measure q[0];", - "b[1] = measure q[1];", + "h __qubits__[0];", + "my_sub(__qubits__[1]);", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -1794,14 +1794,14 @@ def test_from_ir_inputs_updated(): source="\n".join( [ "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", + "bit[2] __bits__;", + "qubit[2] __qubits__;", "for uint i in [0:1] {", - "h q[i];", + "h __qubits__[i];", "}", - "cnot q[0], q[1];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", + "cnot __qubits__[0], __qubits__[1];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -1813,14 +1813,14 @@ def test_from_ir_inputs_updated(): source="\n".join( [ "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", + "bit[2] __bits__;", + "qubit[2] __qubits__;", "for uint i in [0:1] {", - "h q[i];", + "h __qubits__[i];", "}", - "cnot q[0], q[1];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", + "cnot __qubits__[0], __qubits__[1];", + "__bits__[0] = measure __qubits__[0];", + "__bits__[1] = measure __qubits__[1];", ] ), inputs={}, @@ -1832,13 +1832,13 @@ def test_from_ir_inputs_updated(): source="\n".join( [ "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", + "bit[1] __bits__;", + "qubit[1] __qubits__;", "bit c = 0;", "if (c ==0){", - "x q[0];", + "x __qubits__[0];", "}", - "b[0] = measure q[0];", + "__bits__[0] = measure __qubits__[0];", ] ), inputs={}, @@ -1850,11 +1850,11 @@ def test_from_ir_inputs_updated(): 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];", + "input float theta;" "bit[1] __bits__;", + "qubit[1] __qubits__;", + "rx(theta) __qubits__[0];", + "rx(2*theta) __qubits__[0];", + "__bits__[0] = measure __qubits__[0];", ] ), inputs={}, @@ -3357,7 +3357,7 @@ def test_pulse_circuit_to_openqasm(predefined_frame_1, user_defined_frame): ).source == "\n".join( [ "OPENQASM 3.0;", - "bit[2] b;", + "bit[2] __bits__;", "cal {", " frame user_defined_frame_0 = newframe(device_port_x0, 10000000.0, 3.14);", " waveform gauss_wf = gaussian(1000000.0ns, 700000000.0ns, 1, false);", @@ -3379,8 +3379,8 @@ def test_pulse_circuit_to_openqasm(predefined_frame_1, user_defined_frame): " play(predefined_frame_1, drag_gauss_wf_2);", "}", "h $1;", - "b[0] = measure $0;", - "b[1] = measure $1;", + "__bits__[0] = measure $0;", + "__bits__[1] = measure $1;", ] ) @@ -3474,7 +3474,7 @@ def test_parametrized_pulse_circuit(user_defined_frame): [ "OPENQASM 3.0;", "input float frequency;", - "bit[2] b;", + "bit[2] __bits__;", "cal {", " frame user_defined_frame_0 = newframe(device_port_x0, 10000000.0, 3.14);", " waveform gauss_wf = gaussian(10000.0ns, 700000000.0ns, 1, false);", @@ -3484,8 +3484,8 @@ def test_parametrized_pulse_circuit(user_defined_frame): " set_frequency(user_defined_frame_0, frequency);", " play(user_defined_frame_0, gauss_wf);", "}", - "b[0] = measure $0;", - "b[1] = measure $1;", + "__bits__[0] = measure $0;", + "__bits__[1] = measure $1;", ] ) @@ -3499,7 +3499,7 @@ def test_parametrized_pulse_circuit(user_defined_frame): ).source == "\n".join( [ "OPENQASM 3.0;", - "bit[2] b;", + "bit[2] __bits__;", "cal {", " frame user_defined_frame_0 = newframe(device_port_x0, 10000000.0, 3.14);", " waveform gauss_wf = gaussian(10000.0ns, 700000000.0ns, 1, false);", @@ -3509,8 +3509,8 @@ def test_parametrized_pulse_circuit(user_defined_frame): " set_frequency(user_defined_frame_0, 10000000.0);", " play(user_defined_frame_0, gauss_wf);", "}", - "b[0] = measure $0;", - "b[1] = measure $1;", + "__bits__[0] = measure $0;", + "__bits__[1] = measure $1;", ] ) diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 05b98ade..9d5dd558 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -306,7 +306,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Rx(angle=0.17), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "rx(0.17) q[4];", + "rx(0.17) __qubits__[4];", ), ( Gate.Rx(angle=0.17), @@ -318,7 +318,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.X(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "x q[4];", + "x __qubits__[4];", ), ( Gate.X(), @@ -330,7 +330,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Z(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "z q[4];", + "z __qubits__[4];", ), ( Gate.Z(), @@ -342,7 +342,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Y(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "y q[4];", + "y __qubits__[4];", ), ( Gate.Y(), @@ -354,7 +354,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.H(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "h q[4];", + "h __qubits__[4];", ), ( Gate.H(), @@ -366,7 +366,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Ry(angle=0.17), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "ry(0.17) q[4];", + "ry(0.17) __qubits__[4];", ), ( Gate.Ry(angle=0.17), @@ -378,7 +378,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.ZZ(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "zz(0.17) q[4], q[5];", + "zz(0.17) __qubits__[4], __qubits__[5];", ), ( Gate.ZZ(angle=0.17), @@ -390,7 +390,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.I(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "i q[4];", + "i __qubits__[4];", ), ( Gate.I(), @@ -402,7 +402,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.V(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "v q[4];", + "v __qubits__[4];", ), ( Gate.V(), @@ -414,7 +414,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CY(), [0, 1], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cy q[0], q[1];", + "cy __qubits__[0], __qubits__[1];", ), ( Gate.CY(), @@ -426,7 +426,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Rz(angle=0.17), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "rz(0.17) q[4];", + "rz(0.17) __qubits__[4];", ), ( Gate.Rz(angle=0.17), @@ -438,7 +438,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.XX(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "xx(0.17) q[4], q[5];", + "xx(0.17) __qubits__[4], __qubits__[5];", ), ( Gate.XX(angle=0.17), @@ -450,7 +450,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.T(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "t q[4];", + "t __qubits__[4];", ), ( Gate.T(), @@ -468,13 +468,13 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CZ(), [0, 1], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cz q[0], q[1];", + "cz __qubits__[0], __qubits__[1];", ), ( Gate.YY(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "yy(0.17) q[4], q[5];", + "yy(0.17) __qubits__[4], __qubits__[5];", ), ( Gate.YY(angle=0.17), @@ -486,7 +486,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.XY(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "xy(0.17) q[4], q[5];", + "xy(0.17) __qubits__[4], __qubits__[5];", ), ( Gate.XY(angle=0.17), @@ -504,7 +504,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.ISwap(), [0, 1], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "iswap q[0], q[1];", + "iswap __qubits__[0], __qubits__[1];", ), ( Gate.Swap(), @@ -516,7 +516,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Swap(), [0, 1], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "swap q[0], q[1];", + "swap __qubits__[0], __qubits__[1];", ), ( Gate.ECR(), @@ -528,7 +528,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.ECR(), [0, 1], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "ecr q[0], q[1];", + "ecr __qubits__[0], __qubits__[1];", ), ( Gate.CV(), @@ -540,13 +540,13 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CV(), [0, 1], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cv q[0], q[1];", + "cv __qubits__[0], __qubits__[1];", ), ( Gate.Vi(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "vi q[4];", + "vi __qubits__[4];", ), ( Gate.Vi(), @@ -558,7 +558,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CSwap(), [0, 1, 2], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cswap q[0], q[1], q[2];", + "cswap __qubits__[0], __qubits__[1], __qubits__[2];", ), ( Gate.CSwap(), @@ -570,7 +570,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CPhaseShift01(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cphaseshift01(0.17) q[4], q[5];", + "cphaseshift01(0.17) __qubits__[4], __qubits__[5];", ), ( Gate.CPhaseShift01(angle=0.17), @@ -582,7 +582,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CPhaseShift00(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cphaseshift00(0.17) q[4], q[5];", + "cphaseshift00(0.17) __qubits__[4], __qubits__[5];", ), ( Gate.CPhaseShift00(angle=0.17), @@ -594,7 +594,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CPhaseShift(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cphaseshift(0.17) q[4], q[5];", + "cphaseshift(0.17) __qubits__[4], __qubits__[5];", ), ( Gate.CPhaseShift(angle=0.17), @@ -606,7 +606,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.S(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "s q[4];", + "s __qubits__[4];", ), ( Gate.S(), @@ -618,7 +618,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Si(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "si q[4];", + "si __qubits__[4];", ), ( Gate.Si(), @@ -630,7 +630,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Ti(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "ti q[4];", + "ti __qubits__[4];", ), ( Gate.Ti(), @@ -642,7 +642,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.PhaseShift(angle=0.17), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "phaseshift(0.17) q[4];", + "phaseshift(0.17) __qubits__[4];", ), ( Gate.PhaseShift(angle=0.17), @@ -654,7 +654,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CNot(), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cnot q[4], q[5];", + "cnot __qubits__[4], __qubits__[5];", ), ( Gate.CNot(), @@ -666,7 +666,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.PSwap(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "pswap(0.17) q[4], q[5];", + "pswap(0.17) __qubits__[4], __qubits__[5];", ), ( Gate.PSwap(angle=0.17), @@ -678,7 +678,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CPhaseShift10(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cphaseshift10(0.17) q[4], q[5];", + "cphaseshift10(0.17) __qubits__[4], __qubits__[5];", ), ( Gate.CPhaseShift10(angle=0.17), @@ -690,7 +690,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CCNot(), [4, 5, 6], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "ccnot q[4], q[5], q[6];", + "ccnot __qubits__[4], __qubits__[5], __qubits__[6];", ), ( Gate.CCNot(), @@ -711,7 +711,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs "[0, 0, 0, 0, 0, 1.0, 0, 0], " "[0, 0, 0, 0, 0, 0, 0, 1.0], " "[0, 0, 0, 0, 0, 0, 1.0, 0]" - "]) q[4], q[5], q[6]", + "]) __qubits__[4], __qubits__[5], __qubits__[6]", ), ( Gate.Unitary(Gate.CCNot().to_matrix()), @@ -737,7 +737,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs "[0, 0, 0.70710678im, 0.70710678], " "[0.70710678, -0.70710678im, 0, 0], " "[-0.70710678im, 0.70710678, 0, 0]" - "]) q[4], q[5]", + "]) __qubits__[4], __qubits__[5]", ), ( Gate.Unitary(np.round(Gate.ECR().to_matrix(), 8)), @@ -754,7 +754,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Unitary(np.round(Gate.T().to_matrix(), 8)), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket unitary([[1.0, 0], [0, 0.70710678 + 0.70710678im]]) q[4]", + "#pragma braket unitary([[1.0, 0], [0, 0.70710678 + 0.70710678im]]) __qubits__[4]", ), ( Gate.Unitary(np.round(Gate.T().to_matrix(), 8)), @@ -766,7 +766,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Unitary(np.array([[1.0, 0], [0, 0.70710678 - 0.70710678j]])), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket unitary([[1.0, 0], [0, 0.70710678 - 0.70710678im]]) q[4]", + "#pragma braket unitary([[1.0, 0], [0, 0.70710678 - 0.70710678im]]) __qubits__[4]", ), ( Gate.PulseGate( @@ -784,7 +784,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.GPi(angle=0.17), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "gpi(0.17) q[4];", + "gpi(0.17) __qubits__[4];", ), ( Gate.GPi(angle=0.17), @@ -796,7 +796,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.GPi2(angle=0.17), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "gpi2(0.17) q[4];", + "gpi2(0.17) __qubits__[4];", ), ( Gate.GPi2(angle=0.17), @@ -808,7 +808,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.MS(angle_1=0.17, angle_2=3.45), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - f"ms(0.17, 3.45, {np.pi / 2}) q[4], q[5];", + f"ms(0.17, 3.45, {np.pi / 2}) __qubits__[4], __qubits__[5];", ), ( Gate.MS(angle_1=0.17, angle_2=3.45), @@ -1015,22 +1015,34 @@ def test_pulse_gate_to_matrix(): @pytest.mark.parametrize( "gate, target, control, control_state, expected_ir", ( - (Gate.H(), QubitSet(0), QubitSet(1), None, "ctrl @ h q[1], q[0];"), - (Gate.H(), QubitSet(0), QubitSet([1, 2]), None, "ctrl(2) @ h q[1], q[2], q[0];"), - (Gate.Ry(angle=1.23), QubitSet(0), QubitSet([2]), None, "ctrl @ ry(1.23) q[2], q[0];"), + (Gate.H(), QubitSet(0), QubitSet(1), None, "ctrl @ h __qubits__[1], __qubits__[0];"), + ( + Gate.H(), + QubitSet(0), + QubitSet([1, 2]), + None, + "ctrl(2) @ h __qubits__[1], __qubits__[2], __qubits__[0];", + ), + ( + Gate.Ry(angle=1.23), + QubitSet(0), + QubitSet([2]), + None, + "ctrl @ ry(1.23) __qubits__[2], __qubits__[0];", + ), ( Gate.MS(angle_1=0.17, angle_2=3.45), QubitSet(0), QubitSet([1, 2]), None, - f"ctrl(2) @ ms(0.17, 3.45, {np.pi / 2}) q[1], q[2], q[0];", + f"ctrl(2) @ ms(0.17, 3.45, {np.pi / 2}) __qubits__[1], __qubits__[2], __qubits__[0];", ), ( Gate.CCNot(), QubitSet([0, 1, 2]), QubitSet([3, 4]), None, - "ctrl(2) @ ccnot q[3], q[4], q[0], q[1], q[2];", + "ctrl(2) @ ccnot __qubits__[3], __qubits__[4], __qubits__[0], __qubits__[1], __qubits__[2];", # noqa ), ( Gate.Z(), @@ -1038,7 +1050,7 @@ def test_pulse_gate_to_matrix(): QubitSet([1, 2, 3, 4, 5, 6, 7]), [1, 1, 1, 0, 0, 1, 0], "ctrl(3) @ negctrl(2) @ ctrl @ negctrl @ " - "z q[1], q[2], q[3], q[4], q[5], q[6], q[7], q[0];", + "z __qubits__[1], __qubits__[2], __qubits__[3], __qubits__[4], __qubits__[5], __qubits__[6], __qubits__[7], __qubits__[0];", # noqa ), ( Gate.Z(), @@ -1046,7 +1058,7 @@ def test_pulse_gate_to_matrix(): QubitSet([1, 2, 3, 4, 5, 6, 7]), "1110010", "ctrl(3) @ negctrl(2) @ ctrl @ negctrl @ " - "z q[1], q[2], q[3], q[4], q[5], q[6], q[7], q[0];", + "z __qubits__[1], __qubits__[2], __qubits__[3], __qubits__[4], __qubits__[5], __qubits__[6], __qubits__[7], __qubits__[0];", # noqa ), ( Gate.Z(), @@ -1054,21 +1066,21 @@ def test_pulse_gate_to_matrix(): QubitSet([1, 2, 3, 4, 5, 6, 7]), 114, "ctrl(3) @ negctrl(2) @ ctrl @ negctrl @ " - "z q[1], q[2], q[3], q[4], q[5], q[6], q[7], q[0];", + "z __qubits__[1], __qubits__[2], __qubits__[3], __qubits__[4], __qubits__[5], __qubits__[6], __qubits__[7], __qubits__[0];", # noqa ), ( Gate.Z(), QubitSet([0]), QubitSet([1, 2, 3]), [1, 0], - "negctrl @ ctrl @ negctrl @ z q[1], q[2], q[3], q[0];", + "negctrl @ ctrl @ negctrl @ z __qubits__[1], __qubits__[2], __qubits__[3], __qubits__[0];", # noqa ), ( Gate.Z(), QubitSet([0]), QubitSet([1, 2, 3]), "10", - "negctrl @ ctrl @ negctrl @ z q[1], q[2], q[3], q[0];", + "negctrl @ ctrl @ negctrl @ z __qubits__[1], __qubits__[2], __qubits__[3], __qubits__[0];", # noqa ), ), ) @@ -1127,13 +1139,13 @@ def test_gate_control_invalid_state(control, control_state, error_string): @pytest.mark.parametrize( "gate, target, power, expected_ir", ( - (Gate.H(), QubitSet(0), 2, "pow(2) @ h q[0];"), - (Gate.H(), QubitSet(0), 2.0, "pow(2.0) @ h q[0];"), - (Gate.H(), QubitSet(0), 2.5, "pow(2.5) @ h q[0];"), - (Gate.H(), QubitSet(0), 0, "pow(0) @ h q[0];"), - (Gate.H(), QubitSet(0), -2, "inv @ pow(2) @ h q[0];"), - (Gate.H(), QubitSet(0), -2.0, "inv @ pow(2.0) @ h q[0];"), - (Gate.H(), QubitSet(0), -2.5, "inv @ pow(2.5) @ h q[0];"), + (Gate.H(), QubitSet(0), 2, "pow(2) @ h __qubits__[0];"), + (Gate.H(), QubitSet(0), 2.0, "pow(2.0) @ h __qubits__[0];"), + (Gate.H(), QubitSet(0), 2.5, "pow(2.5) @ h __qubits__[0];"), + (Gate.H(), QubitSet(0), 0, "pow(0) @ h __qubits__[0];"), + (Gate.H(), QubitSet(0), -2, "inv @ pow(2) @ h __qubits__[0];"), + (Gate.H(), QubitSet(0), -2.0, "inv @ pow(2.0) @ h __qubits__[0];"), + (Gate.H(), QubitSet(0), -2.5, "inv @ pow(2.5) @ h __qubits__[0];"), ), ) def test_gate_power(gate, target, power, expected_ir): diff --git a/test/unit_tests/braket/circuits/test_noises.py b/test/unit_tests/braket/circuits/test_noises.py index 2b55dfa4..64710c8f 100644 --- a/test/unit_tests/braket/circuits/test_noises.py +++ b/test/unit_tests/braket/circuits/test_noises.py @@ -547,7 +547,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.BitFlip(0.5), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "#pragma braket noise bit_flip(0.5) q[3]", + "#pragma braket noise bit_flip(0.5) __qubits__[3]", ), ( Noise.BitFlip(0.5), @@ -559,7 +559,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.PhaseFlip(0.5), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "#pragma braket noise phase_flip(0.5) q[3]", + "#pragma braket noise phase_flip(0.5) __qubits__[3]", ), ( Noise.PhaseFlip(0.5), @@ -571,7 +571,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.PauliChannel(0.1, 0.2, 0.3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "#pragma braket noise pauli_channel(0.1, 0.2, 0.3) q[3]", + "#pragma braket noise pauli_channel(0.1, 0.2, 0.3) __qubits__[3]", ), ( Noise.PauliChannel(0.1, 0.2, 0.3), @@ -583,7 +583,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.Depolarizing(0.5), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "#pragma braket noise depolarizing(0.5) q[3]", + "#pragma braket noise depolarizing(0.5) __qubits__[3]", ), ( Noise.Depolarizing(0.5), @@ -595,7 +595,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.TwoQubitDepolarizing(0.5), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3, 5], - "#pragma braket noise two_qubit_depolarizing(0.5) q[3], q[5]", + "#pragma braket noise two_qubit_depolarizing(0.5) __qubits__[3], __qubits__[5]", ), ( Noise.TwoQubitDepolarizing(0.5), @@ -607,7 +607,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.TwoQubitDephasing(0.5), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3, 5], - "#pragma braket noise two_qubit_dephasing(0.5) q[3], q[5]", + "#pragma braket noise two_qubit_dephasing(0.5) __qubits__[3], __qubits__[5]", ), ( Noise.TwoQubitDephasing(0.5), @@ -619,7 +619,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.AmplitudeDamping(0.5), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "#pragma braket noise amplitude_damping(0.5) q[3]", + "#pragma braket noise amplitude_damping(0.5) __qubits__[3]", ), ( Noise.AmplitudeDamping(0.5), @@ -631,7 +631,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.GeneralizedAmplitudeDamping(0.5, 0.1), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "#pragma braket noise generalized_amplitude_damping(0.5, 0.1) q[3]", + "#pragma braket noise generalized_amplitude_damping(0.5, 0.1) __qubits__[3]", ), ( Noise.GeneralizedAmplitudeDamping(0.5, 0.1), @@ -643,7 +643,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.PhaseDamping(0.5), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "#pragma braket noise phase_damping(0.5) q[3]", + "#pragma braket noise phase_damping(0.5) __qubits__[3]", ), ( Noise.PhaseDamping(0.5), @@ -668,7 +668,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): "[0, 0.31622776601683794, 0, 0], " "[0.31622776601683794, 0, 0, 0], " "[0, 0, 0, 0.31622776601683794], " - "[0, 0, 0.31622776601683794, 0]]) q[3], q[5]", + "[0, 0, 0.31622776601683794, 0]]) __qubits__[3], __qubits__[5]", ), ( Noise.Kraus( @@ -700,7 +700,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): [3], "#pragma braket noise kraus([" "[0.9486833im, 0], [0, 0.9486833im]], [" - "[0, 0.31622777], [0.31622777, 0]]) q[3]", + "[0, 0.31622777], [0.31622777, 0]]) __qubits__[3]", ), ( Noise.Kraus( diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index 79f917af..41578637 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -67,7 +67,7 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv Observable.I(), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "i(q[3])", + "i(__qubits__[3])", ), ( Observable.I(), @@ -85,7 +85,7 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv Observable.X(), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "x(q[3])", + "x(__qubits__[3])", ), ( Observable.X(), @@ -103,7 +103,7 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv Observable.Y(), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "y(q[3])", + "y(__qubits__[3])", ), ( Observable.Y(), @@ -121,7 +121,7 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv Observable.Z(), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "z(q[3])", + "z(__qubits__[3])", ), ( Observable.Z(), @@ -139,7 +139,7 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv Observable.H(), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "h(q[3])", + "h(__qubits__[3])", ), ( Observable.H(), @@ -158,7 +158,7 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv 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]", + "[0im, 0im, 1+0im, 0im], [0im, 0im, 0im, 1+0im]]) __qubits__[1], __qubits__[2]", ), ( Observable.Hermitian(np.eye(4)), @@ -177,7 +177,7 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv Observable.H() @ Observable.Z(), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3, 0], - "h(q[3]) @ z(q[0])", + "h(__qubits__[3]) @ z(__qubits__[0])", ), ( Observable.H() @ Observable.Z(), @@ -189,7 +189,7 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv Observable.H() @ Observable.Z() @ Observable.I(), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3, 0, 1], - "h(q[3]) @ z(q[0]) @ i(q[1])", + "h(__qubits__[3]) @ z(__qubits__[0]) @ i(__qubits__[1])", ), ( Observable.H() @ Observable.Z() @ Observable.I(), @@ -202,8 +202,8 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3, 0, 1], "hermitian([[1+0im, 0im, 0im, 0im], [0im, 1+0im, 0im, 0im], " - "[0im, 0im, 1+0im, 0im], [0im, 0im, 0im, 1+0im]]) q[3], q[0]" - " @ i(q[1])", + "[0im, 0im, 1+0im, 0im], [0im, 0im, 0im, 1+0im]]) __qubits__[3], __qubits__[0]" + " @ i(__qubits__[1])", ), ( Observable.I() @ Observable.Hermitian(np.eye(4)), diff --git a/test/unit_tests/braket/circuits/test_result_types.py b/test/unit_tests/braket/circuits/test_result_types.py index 5f76eeaf..8b3b9cd0 100644 --- a/test/unit_tests/braket/circuits/test_result_types.py +++ b/test/unit_tests/braket/circuits/test_result_types.py @@ -155,7 +155,7 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): ( ResultType.Expectation(Observable.I(), target=0), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result expectation i(q[0])", + "#pragma braket result expectation i(__qubits__[0])", ), ( ResultType.Expectation(Observable.I()), @@ -175,7 +175,7 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): ( ResultType.DensityMatrix([0, 2]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result density_matrix q[0], q[2]", + "#pragma braket result density_matrix __qubits__[0], __qubits__[2]", ), ( ResultType.DensityMatrix(0), @@ -195,7 +195,7 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): ( ResultType.Probability([0, 2]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result probability q[0], q[2]", + "#pragma braket result probability __qubits__[0], __qubits__[2]", ), ( ResultType.Probability(0), @@ -205,7 +205,7 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): ( ResultType.Sample(Observable.I(), target=0), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result sample i(q[0])", + "#pragma braket result sample i(__qubits__[0])", ), ( ResultType.Sample(Observable.I()), @@ -215,7 +215,7 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): ( ResultType.Variance(Observable.I(), target=0), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result variance i(q[0])", + "#pragma braket result variance i(__qubits__[0])", ), ( ResultType.Variance(Observable.I()), @@ -225,12 +225,12 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): ( ResultType.AdjointGradient(Observable.I(), target=0, parameters=["alpha", "beta"]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result adjoint_gradient expectation(i(q[0])) alpha, beta", + "#pragma braket result adjoint_gradient expectation(i(__qubits__[0])) alpha, beta", ), ( ResultType.AdjointGradient(Observable.I(), target=0, parameters=["alpha"]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result adjoint_gradient expectation(i(q[0])) alpha", + "#pragma braket result adjoint_gradient expectation(i(__qubits__[0])) alpha", ), ( ResultType.AdjointGradient( @@ -239,7 +239,7 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): parameters=[FreeParameter("alpha"), "beta", FreeParameter("gamma")], ), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result adjoint_gradient expectation(h(q[0]) @ i(q[1])) " + "#pragma braket result adjoint_gradient expectation(h(__qubits__[0]) @ i(__qubits__[1])) " # noqa "alpha, beta, gamma", ), ( @@ -249,20 +249,20 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): 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])) " + "#pragma braket result adjoint_gradient expectation(h(__qubits__[0]) @ i(__qubits__[1]) + 2 * z(__qubits__[2])) " # noqa "alpha, beta, gamma", ), ( ResultType.AdjointGradient(Observable.I(), target=0, parameters=[]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result adjoint_gradient expectation(i(q[0])) all", + "#pragma braket result adjoint_gradient expectation(i(__qubits__[0])) all", ), ( ResultType.AdjointGradient( Observable.X() @ Observable.Y(), target=[0, 1], parameters=[] ), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result adjoint_gradient expectation(x(q[0]) @ y(q[1])) all", + "#pragma braket result adjoint_gradient expectation(x(__qubits__[0]) @ y(__qubits__[1])) all", # noqa ), ( ResultType.AdjointGradient( @@ -270,7 +270,7 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): ), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), "#pragma braket result adjoint_gradient expectation(hermitian([[1+0im, 0im], " - "[0im, 1+0im]]) q[0]) all", + "[0im, 1+0im]]) __qubits__[0]) all", ), ], ) diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 08f2a19c..736c510a 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -404,10 +404,10 @@ def test_run_gate_model_inputs(): ( "OPENQASM 3.0;", "input float theta;", - "bit[1] b;", - "qubit[1] q;", - "rx(theta) q[0];", - "b[0] = measure q[0];", + "bit[1] __bits__;", + "qubit[1] __qubits__;", + "rx(theta) __qubits__[0];", + "__bits__[0] = measure __qubits__[0];", ) ), inputs={"theta": 2}, diff --git a/test/unit_tests/braket/parametric/test_free_parameter.py b/test/unit_tests/braket/parametric/test_free_parameter.py index 816bc0ce..b3b391a8 100644 --- a/test/unit_tests/braket/parametric/test_free_parameter.py +++ b/test/unit_tests/braket/parametric/test_free_parameter.py @@ -61,3 +61,36 @@ def test_sub_successful(free_parameter): def test_sub_wrong_param(free_parameter): assert free_parameter.subs({"alpha": 1}) == FreeParameter("theta") + + +@pytest.mark.parametrize( + "name", + ( + "a", + "b", + "q", + "bit", + "qubit", + "_a", + "\u03B8", + "a\u03B8", + "a123", + "z123", + "\u03B8\u03B8", + "\u03B8a1", + ), +) +def test_valid_names(name): + FreeParameter(name) + + +@pytest.mark.parametrize( + "name", + ( + "", + "__a", + ), +) +def test_invalid_names(name): + with pytest.raises(ValueError): + FreeParameter(name) From e465a094ead5719085eb5c7cf4225a80c8a005d8 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 22 Aug 2023 00:30:34 +0000 Subject: [PATCH 0817/1165] prepare release v1.54.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 f54f87ee..b820f2ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.54.1 (2023-08-22) + +### Bug Fixes and Other Changes + + * update: restricting parameter names to not collide with ones we use for OpenQASM generation. + ## v1.54.0 (2023-08-16) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 0e1d56aa..83509c9a 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.54.1.dev0" +__version__ = "1.54.1" From 28fb3b9fc7129fc9f014ddbaad22fd32013b3871 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 22 Aug 2023 00:30:34 +0000 Subject: [PATCH 0818/1165] update development version to v1.54.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 83509c9a..89402ce3 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.54.1" +__version__ = "1.54.2.dev0" From c376860d5a29bde2030f53b8cfe8ab93fd12ef5e Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Wed, 23 Aug 2023 14:31:54 -0400 Subject: [PATCH 0819/1165] move pulse module to the top level (#680) * rename pulse module * remove pulse from init of instructions * move pulse to a separate module * update module docstring * fix: docstring typos --- .../autoqasm/instructions/__init__.py | 1 - .../experimental/autoqasm/pulse/__init__.py | 30 +++++++++++++++++++ .../pulse_control.py => pulse/pulse.py} | 12 ++++---- .../{test_pulse_control.py => test_pulse.py} | 8 ++--- 4 files changed, 41 insertions(+), 10 deletions(-) create mode 100644 src/braket/experimental/autoqasm/pulse/__init__.py rename src/braket/experimental/autoqasm/{instructions/pulse_control.py => pulse/pulse.py} (95%) rename test/unit_tests/braket/experimental/autoqasm/{test_pulse_control.py => test_pulse.py} (96%) diff --git a/src/braket/experimental/autoqasm/instructions/__init__.py b/src/braket/experimental/autoqasm/instructions/__init__.py index e850590a..f9eb2685 100644 --- a/src/braket/experimental/autoqasm/instructions/__init__.py +++ b/src/braket/experimental/autoqasm/instructions/__init__.py @@ -28,4 +28,3 @@ def bell(): from .gates import * # noqa: F401, F403 from .instructions import QubitIdentifierType, reset # noqa: F401 from .measurements import measure # noqa: F401 -from .pulse_control import * # noqa: F401, F403 diff --git a/src/braket/experimental/autoqasm/pulse/__init__.py b/src/braket/experimental/autoqasm/pulse/__init__.py new file mode 100644 index 00000000..162a23aa --- /dev/null +++ b/src/braket/experimental/autoqasm/pulse/__init__.py @@ -0,0 +1,30 @@ +# 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. + +"""Pulse programming is a level of programming that controls qubits or frames at a lower level than +gates, such as playing a waveform or shifting the frequency of a frame. This module includes +instructions for pulse programming. A program can have only pulse instructions, or mixed usage of +pulse and gate instructions, if the device supports it. Pulse programming can also be used to define +the calibration of gates in a program. + +Example of a program that uses only pulse instructions: + +.. code-block:: python + + @aq.function + def my_pulse_program(): + pulse.shift_frequency(frame, 123) + pulse.delay([3, 4], 0.34) +""" + +from .pulse import * # noqa: F401, F403 diff --git a/src/braket/experimental/autoqasm/instructions/pulse_control.py b/src/braket/experimental/autoqasm/pulse/pulse.py similarity index 95% rename from src/braket/experimental/autoqasm/instructions/pulse_control.py rename to src/braket/experimental/autoqasm/pulse/pulse.py index dd114f8b..4161f388 100644 --- a/src/braket/experimental/autoqasm/instructions/pulse_control.py +++ b/src/braket/experimental/autoqasm/pulse/pulse.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. -"""Pulse control instructions that apply to frames or qubits. +"""Pulse instructions that apply to frames or qubits. """ from typing import List, Union @@ -21,18 +21,20 @@ from braket.circuits.qubit_set import QubitSet from braket.experimental.autoqasm import program +from braket.experimental.autoqasm.instructions.qubits import ( + QubitIdentifierType, + is_qubit_identifier_type, +) from braket.pulse import PulseSequence from braket.pulse.frame import Frame from braket.pulse.waveforms import Waveform -from .qubits import QubitIdentifierType, is_qubit_identifier_type - def _pulse_instruction(name: str, frame: Frame, *args) -> None: - """Define a pulse control instruction. + """Define a pulse instruction. Args: - name (str): Name of the pulse control instruction. + name (str): Name of the pulse instruction. frame (Frame): Frame for which the instruction is apply to. """ program_conversion_context = program.get_program_conversion_context() diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pulse_control.py b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py similarity index 96% rename from test/unit_tests/braket/experimental/autoqasm/test_pulse_control.py rename to test/unit_tests/braket/experimental/autoqasm/test_pulse.py index 4721c0d0..ee12021d 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_pulse_control.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py @@ -18,11 +18,11 @@ import pytest import braket.experimental.autoqasm as aq -from braket.experimental.autoqasm.instructions import ( +from braket.experimental.autoqasm.instructions import rx +from braket.experimental.autoqasm.pulse import ( barrier, capture_v0, delay, - h, play, set_frequency, set_phase, @@ -44,7 +44,7 @@ def test_mix_gate_pulse() -> None: @aq.function def my_program(): shift_frequency(FRAME1, 0.1234) - h(1) + rx(1, 0.1) play(FRAME1, WAVEFORM) expected = textwrap.dedent( @@ -58,7 +58,7 @@ def my_program(): cal { shift_frequency(predefined_frame_1, 0.1234); } - h __qubits__[1]; + rx(0.1) __qubits__[1]; cal { play(predefined_frame_1, arb_wf); } From a7dee52a1a6ad476566218f7f3b08d9049adce14 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Wed, 23 Aug 2023 15:41:02 -0400 Subject: [PATCH 0820/1165] doc: fix linter issues in feature/autoqasm after fixing BCS flake8 plugin (#681) * Fix BCS flake8 linter issues * Use main branch of amazon-braket-build-tools --- src/braket/experimental/autoqasm/api.py | 7 ++++--- .../autoqasm/converters/break_statements.py | 11 +++++++++++ .../autoqasm/converters/return_statements.py | 8 ++++++++ .../experimental/autoqasm/instructions/gates.py | 8 ++++---- .../autoqasm/instructions/instructions.py | 2 +- src/braket/experimental/autoqasm/pulse/pulse.py | 14 ++++++-------- 6 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index b06871ac..65234375 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -43,7 +43,8 @@ def function(*args, num_qubits: Optional[int] = None) -> Callable[[Any], aq_prog function is called, and a new Program object is returned. Args: - num_qubits (int): Configuration to set the total number of qubits to declare in the program. + num_qubits (Optional[int]): Configuration to set the total number of qubits to declare in + the program. Returns: Callable[[Any], Program]: A callable which returns the converted @@ -65,7 +66,7 @@ def function(*args, num_qubits: Optional[int] = None) -> Callable[[Any], aq_prog # @aq.function(num_qubits=x) # def my_func(...): # Equivalently: `function(num_qubits=x)(my_func)` - def _function_with_params(f): + def _function_with_params(f: Callable) -> Callable[[Any], aq_program.Program]: return _function_without_params(f, user_config=user_config) return _function_with_params @@ -320,7 +321,7 @@ def _convert_program_as_subroutine( root_oqpy_program._add_subroutine(subroutine_name, subroutine_function_call.subroutine_decl) -def _make_return_instance_from_oqpy_return_type(return_type) -> Any: +def _make_return_instance_from_oqpy_return_type(return_type: Any) -> Any: if not return_type: return None diff --git a/src/braket/experimental/autoqasm/converters/break_statements.py b/src/braket/experimental/autoqasm/converters/break_statements.py index d6d98e98..47884066 100644 --- a/src/braket/experimental/autoqasm/converters/break_statements.py +++ b/src/braket/experimental/autoqasm/converters/break_statements.py @@ -30,6 +30,17 @@ def visit_Break(self, node: ast.stmt) -> ast.stmt: def transform( node: ast.stmt, ctx: ag_ctx.ControlStatusCtx, default_to_null_return: bool = True ) -> ast.stmt: + """AutoQASM-specific break statement handling. + + Args: + node (ast.stmt): Break statement node to transform. + ctx (ag_ctx.ControlStatusCtx): Transformer context. + default_to_null_return (bool): Whether to return null by default. + Defaults to True. + + Returns: + ast.stmt: Transformed break statement node. + """ node = BreakValidator(ctx).visit(node) # When break statements are supported, we may want to fall back on default behavior return break_statements.transform(node, ctx) # pragma: no cover diff --git a/src/braket/experimental/autoqasm/converters/return_statements.py b/src/braket/experimental/autoqasm/converters/return_statements.py index 5089b4e9..3116c46c 100644 --- a/src/braket/experimental/autoqasm/converters/return_statements.py +++ b/src/braket/experimental/autoqasm/converters/return_statements.py @@ -24,6 +24,14 @@ class ReturnValidator(converter.Base): def visit_Return(self, node: ast.stmt) -> ast.stmt: + """AutoQASM-specific return statement validation. + + Args: + node (ast.stmt): Return statement node to transform. + + Returns: + ast.stmt: Transformed return statement node. + """ aq_context = program.get_program_conversion_context() if not aq_context.subroutines_processing and node.value is not None: warnings.warn("Return value from top level function is ignored.") diff --git a/src/braket/experimental/autoqasm/instructions/gates.py b/src/braket/experimental/autoqasm/instructions/gates.py index 1a4ae82a..aff44f6b 100644 --- a/src/braket/experimental/autoqasm/instructions/gates.py +++ b/src/braket/experimental/autoqasm/instructions/gates.py @@ -136,8 +136,8 @@ def cv( """Controlled Sqrt of NOT gate. Args: - control_0 (QubitIdentifierType): Control qubit 0. - target_0 (QubitIdentifierType): Target qubit 0. + control (QubitIdentifierType): Control qubit. + target (QubitIdentifierType): Target qubit. """ _qubit_instruction("cv", [control, target]) @@ -164,8 +164,8 @@ def cz( """Controlled Pauli-Z gate. Args: - control_0 (QubitIdentifierType): Control qubit. - target_0 (QubitIdentifierType): Target qubit. + control (QubitIdentifierType): Control qubit. + target (QubitIdentifierType): Target qubit. """ _qubit_instruction("cz", [control, target]) diff --git a/src/braket/experimental/autoqasm/instructions/instructions.py b/src/braket/experimental/autoqasm/instructions/instructions.py index 058f371c..24e3e990 100644 --- a/src/braket/experimental/autoqasm/instructions/instructions.py +++ b/src/braket/experimental/autoqasm/instructions/instructions.py @@ -22,7 +22,7 @@ from .qubits import QubitIdentifierType, _qubit -def _qubit_instruction(name: str, qubits: List[QubitIdentifierType], *args: Any): +def _qubit_instruction(name: str, qubits: List[QubitIdentifierType], *args: Any) -> None: oqpy_program = program.get_program_conversion_context().get_oqpy_program() oqpy_program.gate([_qubit(q) for q in qubits], name, *args) diff --git a/src/braket/experimental/autoqasm/pulse/pulse.py b/src/braket/experimental/autoqasm/pulse/pulse.py index 4161f388..c27da3c1 100644 --- a/src/braket/experimental/autoqasm/pulse/pulse.py +++ b/src/braket/experimental/autoqasm/pulse/pulse.py @@ -123,11 +123,10 @@ def delay( """Adds an instruction to advance the frame clock by the specified `duration` value. Args: - qubits_or_frames (Union[Frame, List[Frame], QubitIdentifierType, - List[QubitIdentifierType]]): Qubits or frame(s) on which the delay needs to be - introduced. + qubits_or_frames (Union[Frame, List[Frame], QubitIdentifierType, List[QubitIdentifierType]]): + Qubits or frame(s) on which the delay needs to be introduced. duration (float): Value (in seconds) defining the duration of the delay. - """ + """ # noqa: E501 if not isinstance(qubits_or_frames, List): qubits_or_frames = [qubits_or_frames] if all(is_qubit_identifier_type(q) for q in qubits_or_frames): @@ -143,10 +142,9 @@ def barrier( supports barrier. Args: - qubits_or_frames (Union[Frame, List[Frame], QubitIdentifierType, - List[QubitIdentifierType]]): Qubits or frame(s) on which the barrier needs to be - introduced. - """ + qubits_or_frames (Union[Frame, List[Frame], QubitIdentifierType, List[QubitIdentifierType]]): + Qubits or frame(s) on which the barrier needs to be introduced. + """ # noqa: E501 if not isinstance(qubits_or_frames, List): qubits_or_frames = [qubits_or_frames] if all(is_qubit_identifier_type(q) for q in qubits_or_frames): From c29653e01bbd11f92b70e0f08f41dc22858b6780 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:43:56 -0400 Subject: [PATCH 0821/1165] feature: Add @aq.gate decorator for AutoQASM custom gate definitions (#679) --- src/braket/experimental/autoqasm/__init__.py | 4 +- src/braket/experimental/autoqasm/api.py | 140 +++++++- src/braket/experimental/autoqasm/errors.py | 4 + .../autoqasm/instructions/instructions.py | 10 +- .../autoqasm/instructions/qubits.py | 5 + .../experimental/autoqasm/program/__init__.py | 1 + .../experimental/autoqasm/program/program.py | 144 +++++--- .../autoqasm/test_gate_decorator.py | 311 ++++++++++++++++++ ...t_aq_gates.py => test_gate_definitions.py} | 0 .../experimental/autoqasm/test_operators.py | 2 +- 10 files changed, 559 insertions(+), 62 deletions(-) create mode 100644 test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py rename test/unit_tests/braket/experimental/autoqasm/{test_aq_gates.py => test_gate_definitions.py} (100%) diff --git a/src/braket/experimental/autoqasm/__init__.py b/src/braket/experimental/autoqasm/__init__.py index b754fb57..9fd6d3dc 100644 --- a/src/braket/experimental/autoqasm/__init__.py +++ b/src/braket/experimental/autoqasm/__init__.py @@ -40,8 +40,8 @@ def my_program(): result[1] = measure __qubits__[1]; """ -from .api import function # noqa: F401 -from .instructions import QubitIdentifierType # noqa: F401 +from .api import function, gate # noqa: F401 +from .instructions import QubitIdentifierType as Qubit # noqa: F401 from .program import Program, build_program, verbatim # noqa: F401 from .transpiler import transpiler # noqa: F401 from .types import ArrayVar, BitVar, BoolVar, FloatVar, IntVar # noqa: F401 diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 65234375..9c74b4a1 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -23,6 +23,7 @@ import oqpy.base import braket.experimental.autoqasm.constants as aq_constants +import braket.experimental.autoqasm.instructions as aq_instructions import braket.experimental.autoqasm.program as aq_program import braket.experimental.autoqasm.transpiler as aq_transpiler import braket.experimental.autoqasm.types as aq_types @@ -72,6 +73,23 @@ def _function_with_params(f: Callable) -> Callable[[Any], aq_program.Program]: return _function_with_params +def gate(*args) -> Callable[[Any], None]: + """Decorator that converts a function into a callable gate definition. + + Returns: + Callable[[Any],]: A callable which can be used as a custom gate inside an + aq.function or inside another aq.gate. + """ + f = args[0] + if is_autograph_artifact(f): + return f + + wrapper_factory = _convert_gate_wrapper() + wrapper = wrapper_factory(f) + + return autograph_artifact(wrapper) + + def _function_without_params( f: Callable, user_config: aq_program.UserConfig ) -> Callable[[Any], aq_program.Program]: @@ -89,7 +107,7 @@ def _function_without_params( if is_autograph_artifact(f): return f - wrapper_factory = _convert_wrapper( + wrapper_factory = _convert_program_wrapper( user_config=user_config, recursive=False, ) @@ -98,10 +116,9 @@ def _function_without_params( return autograph_artifact(wrapper) -def _convert_wrapper( +def _convert_program_wrapper( user_config: aq_program.UserConfig, recursive: bool = False, - optional_features: Optional[Tuple[converter.Feature]] = None, user_requested: bool = True, conversion_ctx: Optional[ag_ctx.ControlStatusCtx] = ag_ctx.NullCtx(), ) -> Callable: @@ -112,9 +129,6 @@ def _convert_wrapper( user_config (UserConfig): User-specified settings that influence program building recursive (bool): whether to recursively convert any functions or classes that the converted function may use. Defaults to False. - optional_features (Optional[Tuple[Feature]]): allows toggling - optional or experimental features. When set to None, only the core features are - enabled. Defaults to None. user_requested (bool): whether this is a function that the user explicitly asked to be converted. See ConversionOptions.user_requested. Defaults to True. conversion_ctx (Optional[ControlStatusCtx]): the Autograph context in @@ -126,7 +140,7 @@ def _convert_wrapper( """ def _decorator(f: Callable) -> Callable[[Any], aq_program.Program]: - """Decorator implementation.""" + """aq.function decorator implementation.""" def _wrapper(*args, **kwargs) -> Union[aq_program.Program, Optional[oqpy.base.Var]]: """Wrapper that calls the converted version of f.""" @@ -136,9 +150,29 @@ def _wrapper(*args, **kwargs) -> Union[aq_program.Program, Optional[oqpy.base.Va options = converter.ConversionOptions( recursive=recursive, user_requested=user_requested, - optional_features=optional_features, + optional_features=_autograph_optional_features(), ) - return _convert(f, conversion_ctx, options, user_config, args, kwargs) + return _convert_program(f, conversion_ctx, options, user_config, args, kwargs) + + if inspect.isfunction(f) or inspect.ismethod(f): + _wrapper = functools.update_wrapper(_wrapper, f) + + decorated_wrapper = tf_decorator.make_decorator(f, _wrapper) + return autograph_artifact(decorated_wrapper) + + return _decorator + + +def _convert_gate_wrapper() -> Callable: + def _decorator(f: Callable) -> Callable[[Any], None]: + """aq.gate decorator implementation.""" + + def _wrapper(*args, **kwargs) -> Callable: + """Wrapper that calls the converted version of f.""" + options = converter.ConversionOptions( + optional_features=_autograph_optional_features(), + ) + return _convert_gate(f, options, args, kwargs) if inspect.isfunction(f) or inspect.ismethod(f): _wrapper = functools.update_wrapper(_wrapper, f) @@ -166,7 +200,14 @@ def _validate_subroutine_args(user_config: aq_program.UserConfig) -> None: raise errors.InconsistentNumQubits() -def _convert( +def _autograph_optional_features() -> Tuple[converter.Feature]: + # Exclude autograph features which are TensorFlow-specific + return converter.Feature.all_but( + (converter.Feature.NAME_SCOPES, converter.Feature.AUTO_CONTROL_DEPS) + ) + + +def _convert_program( f: Callable, conversion_ctx: ag_ctx.ControlStatusCtx, options: converter.ConversionOptions, @@ -321,6 +362,56 @@ def _convert_program_as_subroutine( root_oqpy_program._add_subroutine(subroutine_name, subroutine_function_call.subroutine_decl) +def _convert_gate( + f: Callable, + options: converter.ConversionOptions, + args: List[Any], + kwargs: Dict[str, Any], +) -> Callable: + try: + # We must be inside an active conversion context in order to invoke a gate + program_conversion_context = aq_program.get_program_conversion_context() + + # Wrap the function into an oqpy gate definition + wrapped_f, gate_args = _wrap_for_oqpy_gate(f, options) + gate_name = f.__name__ + + # Validate that the gate definition acts on at least one qubit + if not gate_args.qubits: + raise errors.ParameterTypeError( + f'Gate definition "{gate_name}" has no arguments of type aq.Qubit. ' + "Every gate definition must contain at least one qubit argument." + ) + + # Process the gate definition + with program_conversion_context.gate_definition(gate_name, gate_args): + # TODO - enforce that nothing gets added to the program inside here except gates + wrapped_f(gate_args._args) + + # Add the gate definition to the root-level program if necessary + root_oqpy_program = program_conversion_context.oqpy_program_stack[0] + if gate_name not in root_oqpy_program.gates: + gate_stmt = program_conversion_context.get_oqpy_program().gates[gate_name] + root_oqpy_program._add_gate(gate_name, gate_stmt) + + # Add the gate invocation to the program + if len(args) != len(gate_args): + raise errors.ParameterTypeError( + f'Incorrect number of arguments passed to gate "{gate_name}". ' + f"Expected {len(gate_args)}, got {len(args)}." + ) + qubit_args = [args[i] for i in gate_args.qubit_indices] + angle_args = [args[i] for i in gate_args.angle_indices] + aq_instructions.instructions._qubit_instruction(gate_name, qubit_args, *angle_args) + except Exception as e: + if isinstance(e, errors.AutoQasmError): + raise + elif hasattr(e, "ag_error_metadata"): + raise e.ag_error_metadata.to_exception(e) + else: + raise + + def _make_return_instance_from_oqpy_return_type(return_type: Any) -> Any: if not return_type: return None @@ -457,3 +548,32 @@ def _func(*args, **kwargs) -> Any: _func.__signature__ = sig.replace(parameters=new_params) return _func + + +def _wrap_for_oqpy_gate( + f: Callable, + options: converter.ConversionOptions, +) -> Tuple[Callable[..., None], aq_program.GateArgs]: + gate_args = aq_program.GateArgs() + sig = inspect.signature(f) + for param in sig.parameters.values(): + if param.annotation is param.empty: + raise errors.MissingParameterTypeError( + f'Parameter "{param.name}" for gate "{f.__name__}" ' + "is missing a required type hint." + ) + + if param.annotation == aq_instructions.QubitIdentifierType: + gate_args.append(param.name, True) + elif param.annotation in [float, aq_types.FloatVar]: + gate_args.append(param.name, False) + else: + raise errors.ParameterTypeError( + f'Parameter "{param.name}" for gate "{f.__name__}" ' + "must have a type hint of either aq.Qubit or float." + ) + + def _func(*args: Any) -> None: + aq_transpiler.converted_call(f, *args, kwargs={}, options=options) + + return _func, gate_args diff --git a/src/braket/experimental/autoqasm/errors.py b/src/braket/experimental/autoqasm/errors.py index 59474a44..5c4ebce2 100644 --- a/src/braket/experimental/autoqasm/errors.py +++ b/src/braket/experimental/autoqasm/errors.py @@ -33,6 +33,10 @@ class MissingParameterTypeError(AutoQasmError): """AutoQASM requires type hints for subroutine parameters.""" +class InvalidGateDefinition(AutoQasmError): + """Gate definition does not meet the necessary requirements.""" + + class UnknownQubitCountError(AutoQasmError): """Missing declaration for the number of qubits.""" diff --git a/src/braket/experimental/autoqasm/instructions/instructions.py b/src/braket/experimental/autoqasm/instructions/instructions.py index 24e3e990..ee23959a 100644 --- a/src/braket/experimental/autoqasm/instructions/instructions.py +++ b/src/braket/experimental/autoqasm/instructions/instructions.py @@ -17,13 +17,19 @@ from typing import Any, List -from braket.experimental.autoqasm import program +from braket.experimental.autoqasm import program as aq_program from .qubits import QubitIdentifierType, _qubit def _qubit_instruction(name: str, qubits: List[QubitIdentifierType], *args: Any) -> None: - oqpy_program = program.get_program_conversion_context().get_oqpy_program() + # If this is an instruction inside a gate definition, ensure that it only operates on + # qubits which are passed as arguments to the gate definition. + program_conversion_context = aq_program.get_program_conversion_context() + program_conversion_context.validate_target_qubits(qubits) + + # Add the instruction to the program. + oqpy_program = program_conversion_context.get_oqpy_program() oqpy_program.gate([_qubit(q) for q in qubits], name, *args) diff --git a/src/braket/experimental/autoqasm/instructions/qubits.py b/src/braket/experimental/autoqasm/instructions/qubits.py index 325960f5..099f57dc 100644 --- a/src/braket/experimental/autoqasm/instructions/qubits.py +++ b/src/braket/experimental/autoqasm/instructions/qubits.py @@ -105,3 +105,8 @@ def _(qid: str) -> oqpy.Qubit: return oqpy.PhysicalQubits[qubit_idx] else: raise ValueError(f"invalid qubit label: '{qid}'") + + +@_qubit.register +def _(qid: oqpy.Qubit) -> oqpy.Qubit: + return qid diff --git a/src/braket/experimental/autoqasm/program/__init__.py b/src/braket/experimental/autoqasm/program/__init__.py index c9515984..241999de 100644 --- a/src/braket/experimental/autoqasm/program/__init__.py +++ b/src/braket/experimental/autoqasm/program/__init__.py @@ -17,6 +17,7 @@ from .pragmas import Verbatim as verbatim # noqa: F401 from .program import ( # noqa: F401 + GateArgs, Program, ProgramConversionContext, UserConfig, diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 9471959a..f6769c5c 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -13,14 +13,15 @@ """AutoQASM Program class, context managers, and related functions.""" +import contextlib import threading from dataclasses import dataclass -from typing import List, Optional +from typing import Any, List, Optional, Union import oqpy.base from braket.circuits.serialization import IRType -from braket.experimental.autoqasm import constants +from braket.experimental.autoqasm import constants, errors # Create the thread-local object for the program conversion context. _local = threading.local() @@ -81,6 +82,44 @@ def to_ir( raise ValueError(f"Supplied ir_type {ir_type} is not supported.") +class GateArgs: + """Represents a list of qubit and angle arguments for a gate definition.""" + + def __init__(self): + self._args: List[Union[oqpy.Qubit, oqpy.AngleVar]] = [] + + def __len__(self): + return len(self._args) + + def append(self, name: str, is_qubit: bool) -> None: + """Appends an argument to the list of gate arguments. + + Args: + name (str): The name of the argument. + is_qubit (bool): Whether the argument represents a qubit. + """ + if is_qubit: + self._args.append(oqpy.Qubit(name, needs_declaration=False)) + else: + self._args.append(oqpy.AngleVar(name=name)) + + @property + def qubits(self) -> List[oqpy.Qubit]: + return [self._args[i] for i in self.qubit_indices] + + @property + def angles(self) -> List[oqpy.AngleVar]: + return [self._args[i] for i in self.angle_indices] + + @property + def qubit_indices(self) -> List[int]: + return [i for i, arg in enumerate(self._args) if isinstance(arg, oqpy.Qubit)] + + @property + def angle_indices(self) -> List[int]: + return [i for i, arg in enumerate(self._args) if isinstance(arg, oqpy.AngleVar)] + + class ProgramConversionContext: """The data structure used while converting a program. Intended for internal use.""" @@ -89,6 +128,7 @@ def __init__(self, user_config: Optional[UserConfig] = None): self.subroutines_processing = set() # the set of subroutines queued for processing self.user_config = user_config or UserConfig() self.return_variable = None + self._gate_definitions_processing = [] self._qubits_seen = set() self._var_idx = 0 self._has_pulse_control = False @@ -163,6 +203,27 @@ def is_var_name_used(self, var_name: str) -> bool: or var_name in oqpy_program.undeclared_vars.keys() ) + def validate_target_qubits(self, qubits: List[Any]) -> None: + """Validate that the specified qubits are valid target qubits at this point in the program. + + Args: + qubits (List[Any]): The list of target qubits to validate. + + Raises: + errors.InvalidGateDefinition: Target qubits are invalid in the current gate definition. + """ + if self._gate_definitions_processing: + gate_name = self._gate_definitions_processing[-1]["name"] + gate_qubit_args = self._gate_definitions_processing[-1]["gate_args"].qubits + for qubit in qubits: + if not isinstance(qubit, oqpy.Qubit) or qubit not in gate_qubit_args: + qubit_name = qubit.name if isinstance(qubit, oqpy.Qubit) else str(qubit) + raise errors.InvalidGateDefinition( + f'Gate definition "{gate_name}" uses qubit "{qubit_name}" which is not ' + "an argument to the gate. Gates may only operate on qubits which are " + "passed as arguments." + ) + def get_oqpy_program(self) -> oqpy.Program: """Gets the oqpy program from the top of the stack. @@ -171,52 +232,37 @@ def get_oqpy_program(self) -> oqpy.Program: """ return self.oqpy_program_stack[-1] - class OqpyProgramContextManager: - """Context manager responsible for managing the oqpy programs which are used - by the ProgramConversionContext.""" - - def __init__(self, oqpy_program: oqpy.Program, oqpy_program_stack: List[oqpy.Program]): - self.oqpy_program = oqpy_program - self.oqpy_program_stack = oqpy_program_stack - - def __enter__(self): - self.oqpy_program_stack.append(self.oqpy_program) - - def __exit__(self, exc_type, exc_value, exc_tb): - self.oqpy_program_stack.pop() - - def push_oqpy_program(self, oqpy_program: oqpy.Program) -> OqpyProgramContextManager: + @contextlib.contextmanager + def push_oqpy_program(self, oqpy_program: oqpy.Program) -> None: """Pushes the provided oqpy program onto the stack. Args: oqpy_program (Program): The oqpy program to push onto the stack. - - Returns: - OqpyProgramContextManager: A context manager which will pop the provided - oqpy program from the stack when exited. """ - return self.OqpyProgramContextManager(oqpy_program, self.oqpy_program_stack) - - -class ProgramContextManager: - """Context responsible for managing the ProgramConversionContext.""" - - def __init__(self, user_config): - self.owns_program_conversion_context = False - self.user_config = user_config + try: + self.oqpy_program_stack.append(oqpy_program) + yield + finally: + self.oqpy_program_stack.pop() - def __enter__(self) -> ProgramConversionContext: - if not _get_local().program_conversion_context: - _get_local().program_conversion_context = ProgramConversionContext(self.user_config) - self.owns_program_conversion_context = True - return _get_local().program_conversion_context + @contextlib.contextmanager + def gate_definition(self, gate_name: str, gate_args: GateArgs) -> None: + """Sets the program conversion context into a gate definition context. - def __exit__(self, exc_type, exc_value, exc_tb): - if self.owns_program_conversion_context: - _get_local().program_conversion_context = None + Args: + gate_name (str): The name of the gate being defined. + gate_args (GateArgs): The list of arguments to the gate. + """ + try: + self._gate_definitions_processing.append({"name": gate_name, "gate_args": gate_args}) + with oqpy.gate(self.get_oqpy_program(), gate_args.qubits, gate_name, gate_args.angles): + yield + finally: + self._gate_definitions_processing.pop() -def build_program(user_config: Optional[UserConfig] = None) -> ProgramContextManager: +@contextlib.contextmanager +def build_program(user_config: Optional[UserConfig] = None) -> None: """Creates a context manager which ensures there is a valid thread-local ProgramConversionContext object. If this context manager created the ProgramConversionContext object, it removes it from thread-local storage when @@ -231,17 +277,21 @@ def build_program(user_config: Optional[UserConfig] = None) -> ProgramContextMan Args: user_config (Optional[UserConfig]): User-supplied program building options. - - Returns: - ProgramContextManager: The context manager which manages - the thread-local Program object. """ - return ProgramContextManager(user_config) + try: + owns_program_conversion_context = False + if not _get_local().program_conversion_context: + _get_local().program_conversion_context = ProgramConversionContext(user_config) + owns_program_conversion_context = True + yield _get_local().program_conversion_context + finally: + if owns_program_conversion_context: + _get_local().program_conversion_context = None def in_active_program_conversion_context() -> bool: """Indicates whether a program conversion context exists in the current scope, - that is, whether there is an active ProgramContextManager. + that is, whether there is an active program conversion context. Returns: bool: Whether there is a program currently being built. @@ -252,8 +302,8 @@ def in_active_program_conversion_context() -> bool: def get_program_conversion_context() -> ProgramConversionContext: """Gets the current thread-local ProgramConversionContext object. - Must be called inside an active ProgramContextManager (that is, while building a program) so - that there is a valid thread-local ProgramConversionContext object. + Must be called inside an active program conversion context (that is, while building a program) + so that there is a valid thread-local ProgramConversionContext object. Returns: ProgramConversionContext: The thread-local ProgramConversionContext object. diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py new file mode 100644 index 00000000..b5e6a72d --- /dev/null +++ b/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py @@ -0,0 +1,311 @@ +# 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. + +"""AutoQASM tests exercising the @aq.gate decorator and related functionality. +""" + +import numpy as np +import pytest +from test_api import _test_on_local_sim + +import braket.experimental.autoqasm as aq +from braket.experimental.autoqasm import errors +from braket.experimental.autoqasm.instructions import h, measure, rx, rz, x + + +@aq.gate +def empty_gate(q: aq.Qubit): + pass + + +def test_empty_gate() -> None: + @aq.function + def my_program(): + empty_gate(0) + + expected = """OPENQASM 3.0; +gate empty_gate q { +} +qubit[1] __qubits__; +empty_gate __qubits__[0];""" + + program = my_program() + assert program.to_ir() == expected + + _test_on_local_sim(program) + + +def test_double_gate_decorator() -> None: + double_decorated = aq.gate(empty_gate) + + @aq.function + def my_program(): + double_decorated(0) + + expected = """OPENQASM 3.0; +gate empty_gate q { +} +qubit[1] __qubits__; +empty_gate __qubits__[0];""" + + program = my_program() + assert program.to_ir() == expected + + +def test_gate_class() -> None: + """Tests the aq.gate decorator on something that is not a function.""" + + @aq.gate + class MyGate: + def __init__(self, q: aq.Qubit): + h(q) + + @aq.function + def main(): + MyGate(0) + + with pytest.raises(ValueError): + main() + + +def test_invalid_symbol() -> None: + @aq.gate + def my_gate(q: aq.Qubit): + h(q) + not_a_symbol() # noqa: F821 # type: ignore + + @aq.function + def main(): + my_gate(0) + + with pytest.raises(NameError): + main() + + +def test_duplicate_gate_names() -> None: + @aq.gate + def my_gate(q: aq.Qubit): + h(q) + + @aq.gate + def my_gate(q: aq.Qubit, angle: float): # noqa: F811 + rx(q, angle) + + @aq.function + def main(): + my_gate(0, np.pi / 4) + + expected = """OPENQASM 3.0; +gate my_gate(angle) q { + rx(angle) q; +} +qubit[1] __qubits__; +my_gate(pi / 4) __qubits__[0];""" + + program = main() + assert program.to_ir() == expected + + +def test_duplicate_gate_names_in_subroutine() -> None: + """Verify that gates can only be defined at the top level.""" + + @aq.function + def define_gate_in_subroutine(): + @aq.gate + def my_gate(q: aq.Qubit): + h(q) + + my_gate(1) + + @aq.gate + def my_gate(q: aq.Qubit, angle: float): + rx(q, angle) + + @aq.function + def main(): + my_gate(0, np.pi / 4) + define_gate_in_subroutine() + + expected = """OPENQASM 3.0; +def define_gate_in_subroutine() { + h __qubits__[1]; +} +gate my_gate(angle) q { + rx(angle) q; +} +qubit[2] __qubits__; +my_gate(pi / 4) __qubits__[0]; +define_gate_in_subroutine();""" + + program = main() + assert program.to_ir() == expected + + +def test_incorrect_arg_count() -> None: + @aq.gate + def my_gate(q0: aq.Qubit, q1: aq.Qubit): + h(q0) + x(q1) + + @aq.function + def incorrect_arg_count(): + my_gate(0) + + with pytest.raises( + errors.ParameterTypeError, + match='Incorrect number of arguments passed to gate "my_gate". Expected 2, got 1.', + ): + incorrect_arg_count() + + +def test_incorrect_arg_types() -> None: + @aq.gate + def my_gate(q: aq.Qubit, theta: float): + h(q) + rx(q, theta) + + @aq.function + def incorrect_arg_types(): + my_gate(0.25, 0) + + with pytest.raises(TypeError): + incorrect_arg_types() + + +def test_missing_annotation() -> None: + @aq.gate + def my_gate(a): + pass + + @aq.function + def my_program(): + my_gate("test") + + with pytest.raises(errors.MissingParameterTypeError): + my_program() + + +def test_incorrect_annotation() -> None: + @aq.gate + def my_gate(a: str): + pass + + @aq.function + def my_program(): + my_gate("test") + + with pytest.raises(errors.ParameterTypeError): + my_program() + + +def test_no_qubit_args() -> None: + @aq.gate + def not_a_gate(angle: float): + pass + + @aq.function + def my_program(): + not_a_gate(np.pi) + + with pytest.raises( + errors.ParameterTypeError, + match='Gate definition "not_a_gate" has no arguments of type aq.Qubit.', + ): + my_program() + + +def test_invalid_qubit_used() -> None: + @aq.gate + def my_gate(q: aq.Qubit): + h(q) + x(1) # invalid + + @aq.function + def my_program(): + my_gate(0) + + with pytest.raises( + errors.InvalidGateDefinition, + match='Gate definition "my_gate" uses qubit "1" which is not an argument to the gate.', + ): + my_program() + + +def test_nested_gates() -> None: + @aq.gate + def t(q: aq.Qubit): + rz(q, np.pi / 4) + + @aq.gate + def my_gate(q: aq.Qubit, theta: float): + h(q) + t(q) + rx(q, theta) + + @aq.function + def my_program(): + my_gate(0, np.pi / 4) + my_gate(1, 3 * np.pi / 4) + measure([0, 1]) + + expected = """OPENQASM 3.0; +gate t q { + rz(pi / 4) q; +} +gate my_gate(theta) q { + h q; + t q; + rx(theta) q; +} +qubit[2] __qubits__; +my_gate(pi / 4) __qubits__[0]; +my_gate(3 * pi / 4) __qubits__[1]; +bit[2] __bit_0__ = "00"; +__bit_0__[0] = measure __qubits__[0]; +__bit_0__[1] = measure __qubits__[1];""" + + program = my_program() + assert program.to_ir() == expected + + _test_on_local_sim(program) + + +def test_gate_called_from_subroutine() -> None: + @aq.gate + def t(q: aq.Qubit): + rz(q, np.pi / 4) + + @aq.function + def subroutine(q0: int, q1: int): + t(q0) + t(q1) + + @aq.function(num_qubits=4) + def main(): + subroutine(0, 1) + subroutine(2, 3) + + expected = """OPENQASM 3.0; +def subroutine(int[32] q0, int[32] q1) { + t __qubits__[q0]; + t __qubits__[q1]; +} +gate t q { + rz(pi / 4) q; +} +qubit[4] __qubits__; +subroutine(0, 1); +subroutine(2, 3);""" + + program = main() + assert program.to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_definitions.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/test_aq_gates.py rename to test/unit_tests/braket/experimental/autoqasm/test_gate_definitions.py diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py index 2f4e7d4a..181f5dd5 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_operators.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py @@ -172,7 +172,7 @@ def branch_assignment_declared(): assert branch_assignment_declared().to_ir() == expected -def for_body(i: aq.QubitIdentifierType) -> None: +def for_body(i: aq.Qubit) -> None: h(i) From 1375c8199e8ccb2fb630aa802d0cbe32977ca488 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Aug 2023 13:19:59 -0700 Subject: [PATCH 0822/1165] build(deps): bump pypa/gh-action-pypi-publish from 1.8.8 to 1.8.10 (#674) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.8 to 1.8.10. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/f8c70e705ffc13c3b4d1221169b84f12a75d6ca8...b7f401de30cb6434a1e19f805ff006643653240e) --- 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> Co-authored-by: Abe Coull <85974725+math411@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 b061d1eb..ff7d3883 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -24,6 +24,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@f8c70e705ffc13c3b4d1221169b84f12a75d6ca8 # release/v1 + uses: pypa/gh-action-pypi-publish@b7f401de30cb6434a1e19f805ff006643653240e # release/v1 with: password: ${{ secrets.pypi_token }} From e2c6f2400d513a4ba55c726781fa94a7fc444d06 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Fri, 25 Aug 2023 11:48:48 -0400 Subject: [PATCH 0823/1165] Split aq.function into aq.main and aq.subroutine (#682) * Split aq.function into aq.main and aq.subroutine Remove unused error, add new tests, update docstrings. Update example notebooks * Apply suggestions from code review Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --------- Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- .../1_Getting_started_with_AutoQASM.ipynb | 11 +- .../2_Expressing_classical_control_flow.ipynb | 24 ++- .../3_Iterative_phase_estimation.ipynb | 11 +- src/braket/experimental/autoqasm/README.md | 10 +- src/braket/experimental/autoqasm/__init__.py | 4 +- src/braket/experimental/autoqasm/api.py | 125 +++++------ src/braket/experimental/autoqasm/errors.py | 17 +- .../autoqasm/instructions/__init__.py | 2 +- .../autoqasm/instructions/measurements.py | 2 +- .../experimental/autoqasm/program/pragmas.py | 2 +- .../experimental/autoqasm/pulse/__init__.py | 2 +- .../braket/experimental/autoqasm/conftest.py | 56 ++++- .../braket/experimental/autoqasm/test_api.py | 196 +++++++++++------- .../experimental/autoqasm/test_converters.py | 4 +- .../autoqasm/test_gate_decorator.py | 32 +-- .../experimental/autoqasm/test_operators.py | 16 +- .../experimental/autoqasm/test_pragmas.py | 2 +- .../experimental/autoqasm/test_program.py | 4 +- .../experimental/autoqasm/test_pulse.py | 4 +- .../experimental/autoqasm/test_transpiler.py | 43 ++-- .../experimental/autoqasm/test_types.py | 150 +++++++------- 21 files changed, 427 insertions(+), 290 deletions(-) diff --git a/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb b/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb index 03535ddc..fd1fecb9 100644 --- a/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb +++ b/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb @@ -1,6 +1,7 @@ { "cells": [ { + "attachments": {}, "cell_type": "markdown", "id": "135014b8", "metadata": {}, @@ -11,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "15cdfaee", "metadata": {}, "outputs": [], @@ -27,12 +28,13 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "d2d7004f", "metadata": {}, "source": [ "## Build and run a Bell state program\n", - "As a hello-world example, we create a Bell state and execute it on the Braket local simulator. The quantum program is defined in the `bell_state` function. The `@aq.function` decorator marks the `bell_state` function as a quantum program and enables AutoQASM syntax in the function scope." + "As a hello-world example, we create a Bell state and execute it on the Braket local simulator. The quantum program is defined in the `bell_state` function. The `@aq.main` decorator marks the `bell_state` function as a quantum program and enables AutoQASM syntax in the function scope." ] }, { @@ -42,7 +44,7 @@ "metadata": {}, "outputs": [], "source": [ - "@aq.function\n", + "@aq.main\n", "def bell_state():\n", " h(0)\n", " cnot(0, 1)\n", @@ -50,6 +52,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "2c59985b", "metadata": {}, @@ -85,6 +88,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "756d0587", "metadata": {}, @@ -139,6 +143,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "658c21ce", "metadata": {}, diff --git a/examples/autoqasm/2_Expressing_classical_control_flow.ipynb b/examples/autoqasm/2_Expressing_classical_control_flow.ipynb index beb7c092..8fb2eefd 100644 --- a/examples/autoqasm/2_Expressing_classical_control_flow.ipynb +++ b/examples/autoqasm/2_Expressing_classical_control_flow.ipynb @@ -1,6 +1,7 @@ { "cells": [ { + "attachments": {}, "cell_type": "markdown", "id": "135014b8", "metadata": {}, @@ -28,6 +29,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "2cccd8c2", "metadata": {}, @@ -35,7 +37,7 @@ "## Subroutine\n", "When your quantum program uses the same logic multiple times, subroutines can be a helpful tool. With subroutines, you can generate a more efficient program representation. Your programs become easier to read and reason about, and less work to maintain.\n", "\n", - "As an example, we want to prepare two Bell states, each on different qubit pair. Because the gates to prepare Bell states are the same, we can implement them as a subroutine. Similar to a quantum program, a subroutine is marked by the `@aq.function` decorator. Any number of functions in your program can be marked as a quantum program with this decorator, and they can call one another. Here, we define the Bell state preparation as a subroutine." + "As an example, we want to prepare two Bell states, each on different qubit pair. Because the gates to prepare Bell states are the same, we can implement them as a subroutine. Similar to a quantum program, a subroutine is marked by the `@aq.subroutine` decorator. Any number of functions in your program can be marked as a quantum program with this decorator, and they can call one another. Here, we define the Bell state preparation as a subroutine." ] }, { @@ -45,13 +47,14 @@ "metadata": {}, "outputs": [], "source": [ - "@aq.function\n", + "@aq.subroutine\n", "def bell(q0: int, q1: int) -> None:\n", " h(q0)\n", " cnot(q0, q1)" ] }, { + "attachments": {}, "cell_type": "markdown", "id": "e2a884e6", "metadata": {}, @@ -81,7 +84,7 @@ } ], "source": [ - "@aq.function(num_qubits=4)\n", + "@aq.main(num_qubits=4)\n", "def two_bell() -> None:\n", " bell(0, 1)\n", " bell(2, 3)\n", @@ -92,14 +95,16 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "c6c59f55", "metadata": {}, "source": [ - "With classical control flow, sometimes the number of qubits used could be undetermined before the execution time (i.e., when program is executed on a QPU or a simulator). Therefore, AutoQASM does not know how many qubits the program may need. In cases like subroutine and using variables as qubit indices, we must define the qubit count using the keyword argument `num_qubits` in `@aq.function`." + "With classical control flow, sometimes the number of qubits used could be undetermined before the execution time (i.e., when program is executed on a QPU or a simulator). Therefore, AutoQASM does not know how many qubits the program may need. In cases like subroutine and using variables as qubit indices, we must define the qubit count using the keyword argument `num_qubits` in `@aq.main`." ] }, { + "attachments": {}, "cell_type": "markdown", "id": "1e86b3d5", "metadata": {}, @@ -108,6 +113,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "bf201c13", "metadata": {}, @@ -122,7 +128,7 @@ "metadata": {}, "outputs": [], "source": [ - "@aq.function(num_qubits=5)\n", + "@aq.main(num_qubits=5)\n", "def conditioned_bell() -> None:\n", " h(0)\n", " if measure(0):\n", @@ -137,6 +143,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "db483f0e", "metadata": {}, @@ -181,6 +188,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "c1ded7b8", "metadata": {}, @@ -189,6 +197,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "edee6e9c", "metadata": {}, @@ -197,6 +206,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "ce1da10e", "metadata": {}, @@ -230,7 +240,7 @@ "n_bell = 3\n", "\n", "\n", - "@aq.function(num_qubits=n_bell * 2)\n", + "@aq.main(num_qubits=n_bell * 2)\n", "def multiple_bell(n_bell) -> None:\n", " for i in aq.range(0, 2 * n_bell, 2):\n", " bell(i, i + 1)\n", @@ -241,6 +251,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "9303a270", "metadata": {}, @@ -249,6 +260,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "2619f38d", "metadata": {}, diff --git a/examples/autoqasm/3_Iterative_phase_estimation.ipynb b/examples/autoqasm/3_Iterative_phase_estimation.ipynb index 912a834d..99c433b7 100644 --- a/examples/autoqasm/3_Iterative_phase_estimation.ipynb +++ b/examples/autoqasm/3_Iterative_phase_estimation.ipynb @@ -1,6 +1,7 @@ { "cells": [ { + "attachments": {}, "cell_type": "markdown", "id": "08d77a63", "metadata": {}, @@ -30,6 +31,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "d471ea9c", "metadata": {}, @@ -44,7 +46,7 @@ "metadata": {}, "outputs": [], "source": [ - "@aq.function\n", + "@aq.subroutine\n", "def phase_oracle(ancilla_qubit: int, data_qubit: int) -> None:\n", " \"\"\"Phase oracle that applies phase oracle on q1\n", " conditioned on q0.\n", @@ -58,6 +60,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "f76eb5a3", "metadata": {}, @@ -82,7 +85,7 @@ "metadata": {}, "outputs": [], "source": [ - "@aq.function(num_qubits=2)\n", + "@aq.main(num_qubits=2)\n", "def ipe(n_iterations: int):\n", " \"\"\"Iterative phase estimation algorithm.\n", "\n", @@ -127,6 +130,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "1184691f", "metadata": {}, @@ -171,6 +175,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "2b4ff1a9", "metadata": {}, @@ -179,6 +184,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "63c39db4", "metadata": {}, @@ -188,6 +194,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "26c3bf88", "metadata": {}, diff --git a/src/braket/experimental/autoqasm/README.md b/src/braket/experimental/autoqasm/README.md index 408ff06a..2584ba88 100644 --- a/src/braket/experimental/autoqasm/README.md +++ b/src/braket/experimental/autoqasm/README.md @@ -49,14 +49,14 @@ import braket.experimental.autoqasm as aq from braket.experimental.autoqasm.instructions import h, cnot, measure ``` -To create a quantum program using the AutoQASM experience, you decorate a function with `@aq.function`. +To create a quantum program using the AutoQASM experience, you decorate a function with `@aq.main`. This allows AutoQASM to hook into the program definition and generate an output format that is accepted by quantum devices. For instance, we can create a Bell state like so: ``` # A program that generates a maximally entangled state -@aq.function +@aq.main def bell_state() -> None: h(0) cnot(0, 1) @@ -68,7 +68,7 @@ AutoQASM enables users to use more complicated program constructs with a compact structure. We can demonstrate this with a program that conditionally prepares multiple Bell states on qubit pairs (1, 2) and (3, 4). ``` -@aq.function(num_qubits=5) +@aq.main(num_qubits=5) def conditional_multi_bell_states() -> None: h(0) if measure(0): @@ -82,7 +82,7 @@ def conditional_multi_bell_states() -> None: my_bell_program = conditional_multi_bell_states() ``` -AutoQASM can support nested subroutines and complex control flow. You can use the Python runtime +AutoQASM can support subroutines and complex control flow. You can use the Python runtime and quantum runtime side-by-side. For the moment, we support only a few quantum operations such as `h`, `x`, `cnot`, and `measure`. There are rough edges at the moment, but we're actively smoothing them out! @@ -103,7 +103,7 @@ For more example usage of AutoQASM, visit the [example notebooks](../../../../ex ## Architecture AutoQASM is built on top of the `autograph` component of TensorFlow. A quantum program is -written as a Python function which includes an `@aq.function` decorator. When calling this +written as a Python function which is decorated with `@aq.main`. When calling this decorated function, the user’s Python function is converted into a transformed Python function by `autograph`. This transformed function is then executed to produce an AutoQASM `Program` object which can be simulated and/or serialized to OpenQASM. diff --git a/src/braket/experimental/autoqasm/__init__.py b/src/braket/experimental/autoqasm/__init__.py index 9fd6d3dc..23098ace 100644 --- a/src/braket/experimental/autoqasm/__init__.py +++ b/src/braket/experimental/autoqasm/__init__.py @@ -19,7 +19,7 @@ import braket.experimental.autoqasm as aq from braket.experimental.autoqasm.instructions import h, cnot, measure - @aq.function + @aq.main def my_program(): h(0) cnot(0, 1) @@ -40,7 +40,7 @@ def my_program(): result[1] = measure __qubits__[1]; """ -from .api import function, gate # noqa: F401 +from .api import gate, main, subroutine # noqa: F401 from .instructions import QubitIdentifierType as Qubit # noqa: F401 from .program import Program, build_program, verbatim # noqa: F401 from .transpiler import transpiler # noqa: F401 diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 9c74b4a1..06dc871f 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -36,12 +36,12 @@ from braket.experimental.autoqasm.autograph.tf_utils import tf_decorator -def function(*args, num_qubits: Optional[int] = None) -> Callable[[Any], aq_program.Program]: +def main(*args, num_qubits: Optional[int] = None) -> Callable[[Any], aq_program.Program]: """Decorator that converts a function into a callable that returns a Program object containing the quantum program. The decorator re-converts the target function whenever the decorated - function is called, and a new Program object is returned. + function is called, and a new Program object is returned each time. Args: num_qubits (Optional[int]): Configuration to set the total number of qubits to declare in @@ -57,22 +57,33 @@ def function(*args, num_qubits: Optional[int] = None) -> Callable[[Any], aq_prog if len(args): # In this case, num_qubits wasn't supplied. # Matches the following syntax: - # @aq.function + # @aq.main # def my_func(...): - # Equivalently, `function(my_func, num_qubits=None)` - return _function_without_params(args[0], user_config=user_config) + # Equivalently, `main(my_func, num_qubits=None)` + return _function_wrapper(args[0], user_config=user_config) else: # In this case, num_qubits was supplied, and we don't know `f` yet. # Matches the following syntax: - # @aq.function(num_qubits=x) + # @aq.main(num_qubits=x) # def my_func(...): - # Equivalently: `function(num_qubits=x)(my_func)` + # Equivalently: `main(num_qubits=x)(my_func)` def _function_with_params(f: Callable) -> Callable[[Any], aq_program.Program]: - return _function_without_params(f, user_config=user_config) + return _function_wrapper(f, user_config=user_config) return _function_with_params +def subroutine(*args) -> Callable[[Any], aq_program.Program]: + """Decorator that converts a function into a callable that will insert a subroutine into + the quantum program. + + Returns: + Callable[[Any], Program]: A callable which returns the converted + quantum program when called. + """ + return _function_wrapper(*args, user_config=None, is_subroutine=True) + + def gate(*args) -> Callable[[Any], None]: """Decorator that converts a function into a callable gate definition. @@ -85,20 +96,22 @@ def gate(*args) -> Callable[[Any], None]: return f wrapper_factory = _convert_gate_wrapper() - wrapper = wrapper_factory(f) - return autograph_artifact(wrapper) + return autograph_artifact(wrapper_factory(f)) -def _function_without_params( - f: Callable, user_config: aq_program.UserConfig +def _function_wrapper( + f: Callable, + user_config: aq_program.UserConfig, + is_subroutine: bool = False, ) -> Callable[[Any], aq_program.Program]: """Wrapping and conversion logic around the user function `f`. Args: f (Callable): The target function to be converted which represents the entry point of the quantum program. - user_config (UserConfig): User-specified settings that influence program building + user_config (UserConfig): User-specified settings that influence program building. + is_subroutine (bool): If the function corresponds to a subroutine or main. Returns: Callable[[Any], Program]: A callable which returns the converted @@ -107,37 +120,31 @@ def _function_without_params( if is_autograph_artifact(f): return f + # TODO: (laurecap) Update to accept a callback wrapper_factory = _convert_program_wrapper( user_config=user_config, - recursive=False, + is_subroutine=is_subroutine, ) - wrapper = wrapper_factory(f) - return autograph_artifact(wrapper) + return autograph_artifact(wrapper_factory(f)) def _convert_program_wrapper( user_config: aq_program.UserConfig, - recursive: bool = False, - user_requested: bool = True, - conversion_ctx: Optional[ag_ctx.ControlStatusCtx] = ag_ctx.NullCtx(), + is_subroutine: bool = False, ) -> Callable: """Generates a factory which does the conversion of a function into a callable that returns a Program object containing the quantum program. Args: - user_config (UserConfig): User-specified settings that influence program building - recursive (bool): whether to recursively convert any functions or classes - that the converted function may use. Defaults to False. - user_requested (bool): whether this is a function that the user explicitly - asked to be converted. See ConversionOptions.user_requested. Defaults to True. - conversion_ctx (Optional[ControlStatusCtx]): the Autograph context in - which `f` is used. Defaults to ag_ctx.NullCtx(). + user_config (UserConfig): User-specified settings that influence program building. + is_subroutine (bool): If the function corresponds to a subroutine or main. Returns: Callable: a decorator that converts the given function into an equivalent function that uses AutoQASM operations. """ + conversion_ctx = ag_ctx.NullCtx() def _decorator(f: Callable) -> Callable[[Any], aq_program.Program]: """aq.function decorator implementation.""" @@ -145,14 +152,14 @@ def _decorator(f: Callable) -> Callable[[Any], aq_program.Program]: def _wrapper(*args, **kwargs) -> Union[aq_program.Program, Optional[oqpy.base.Var]]: """Wrapper that calls the converted version of f.""" # This code is executed once the decorated function is called - if aq_program.in_active_program_conversion_context(): - _validate_subroutine_args(user_config) options = converter.ConversionOptions( - recursive=recursive, - user_requested=user_requested, + recursive=False, + user_requested=True, optional_features=_autograph_optional_features(), ) - return _convert_program(f, conversion_ctx, options, user_config, args, kwargs) + return _convert_program( + f, conversion_ctx, options, user_config, args, kwargs, is_subroutine + ) if inspect.isfunction(f) or inspect.ismethod(f): _wrapper = functools.update_wrapper(_wrapper, f) @@ -183,23 +190,6 @@ def _wrapper(*args, **kwargs) -> Callable: return _decorator -def _validate_subroutine_args(user_config: aq_program.UserConfig) -> None: - """Validate decorator arguments to subroutines. - - Args: - user_config (UserConfig): User-specified settings that influence program building - - Raises: - InconsistentUserConfiguration: If subroutine num_qubits does not match the main - function's num_qubits argument. - """ - if user_config.num_qubits is None: - return - # Allow num_qubits only if the arg matches the value provided to the main function - if user_config.num_qubits != aq_program.get_program_conversion_context().get_declared_qubits(): - raise errors.InconsistentNumQubits() - - def _autograph_optional_features() -> Tuple[converter.Feature]: # Exclude autograph features which are TensorFlow-specific return converter.Feature.all_but( @@ -214,11 +204,12 @@ def _convert_program( user_config: aq_program.UserConfig, args: List[Any], kwargs: Dict[str, Any], + is_subroutine: bool, ) -> Union[aq_program.Program, Optional[oqpy.base.Var]]: """Convert the initial callable `f` into a full AutoQASM program `program`. - This function adds error handling around `_convert_program_as_subroutine` - and `_convert_program_as_main`, where the conversion logic itself lives. + This function adds error handling around `_convert_subroutine` + and `_convert_main`, where the conversion logic itself lives. Args: f (Callable): The function to be converted. @@ -227,25 +218,33 @@ def _convert_program( user_config (UserConfig): User-specified settings that influence program building args (List[Any]): Arguments passed to the program when called. kwargs (Dict[str, Any]): Keyword arguments passed to the program when called. + is_subroutine (bool): If the function corresponds to a subroutine or main. + Returns: Union[Program, Optional[Var]]: The converted program, if this is a top-level call to the conversion process. Or, the oqpy variable returned from the converted function, if this is a subroutine conversion. """ - # We will convert this function as a subroutine if we are already inside an - # existing conversion process (i.e., this is a subroutine call). - convert_as_subroutine = aq_program.in_active_program_conversion_context() + if is_subroutine and not aq_program.in_active_program_conversion_context(): + raise errors.AutoQasmTypeError( + "Subroutines shouldn't be called directly. Please define an entry point " + "function, decorate it with '@aq.main', and call your subroutine " + "from within that function." + ) + elif not is_subroutine and aq_program.in_active_program_conversion_context(): + raise errors.AutoQasmTypeError( + f"Cannot call main function '{f.__name__}' from another main function. Did you mean " + "to use '@aq.subroutine'?" + ) with aq_program.build_program(user_config) as program_conversion_context: try: with conversion_ctx: - if convert_as_subroutine: - _convert_program_as_subroutine( - f, program_conversion_context, options, args, kwargs - ) + if is_subroutine: + _convert_subroutine(f, program_conversion_context, options, args, kwargs) else: - _convert_program_as_main(f, program_conversion_context, options, args, kwargs) + _convert_main(f, program_conversion_context, options, args, kwargs) except Exception as e: if isinstance(e, errors.AutoQasmError): raise @@ -254,13 +253,13 @@ def _convert_program( else: raise - if convert_as_subroutine: + if is_subroutine: return program_conversion_context.return_variable return program_conversion_context.make_program() -def _convert_program_as_main( +def _convert_main( f: Callable, program_conversion_context: aq_program.ProgramConversionContext, options: converter.ConversionOptions, @@ -269,7 +268,7 @@ def _convert_program_as_main( ) -> None: """Convert the initial callable `f` into a full AutoQASM program `program`. Puts the contents of `f` at the global level of the program, rather than - putting it into a subroutine as done in `_convert_program_as_subroutine`. + putting it into a subroutine as done in `_convert_subroutine`. Some program pre- and post-processing occurs here, such as adding a qubit declaration and adding the subroutine invocation at the top level. @@ -288,7 +287,7 @@ def _convert_program_as_main( _add_qubit_declaration(program_conversion_context) -def _convert_program_as_subroutine( +def _convert_subroutine( f: Callable, program_conversion_context: aq_program.ProgramConversionContext, options: converter.ConversionOptions, @@ -474,6 +473,8 @@ def _add_qubit_declaration(program_conversion_context: aq_program.ProgramConvers def _clone_function(f_source: Callable) -> Callable: + if not hasattr(f_source, "__code__"): + raise ValueError(f"AutoQASM encountered a callable that it cannot process: {f_source}.") f_clone = FunctionType( copy.deepcopy(f_source.__code__), copy.copy(f_source.__globals__), diff --git a/src/braket/experimental/autoqasm/errors.py b/src/braket/experimental/autoqasm/errors.py index 5c4ebce2..6eb6fbd3 100644 --- a/src/braket/experimental/autoqasm/errors.py +++ b/src/braket/experimental/autoqasm/errors.py @@ -21,6 +21,10 @@ class AutoQasmError(Exception): """Base class for all AutoQASM exceptions.""" +class AutoQasmTypeError(AutoQasmError): + """Generic type error.""" + + class UnsupportedFeatureError(AutoQasmError): """AutoQASM unsupported feature.""" @@ -55,19 +59,6 @@ def __str__(self): return self.message -class InconsistentNumQubits(AutoQasmError): - """Num qubits supplied to main function does not match subroutine.""" - - def __init__(self): - self.message = """\ -The number of qubits specified by one of your functions does not match the \ -argument supplied elsewhere. Remove the `num_qubits` argument from nested \ -function calls.""" - - def __str__(self): - return self.message - - class UnsupportedConditionalExpressionError(AutoQasmError): """Conditional expressions which return values are not supported.""" diff --git a/src/braket/experimental/autoqasm/instructions/__init__.py b/src/braket/experimental/autoqasm/instructions/__init__.py index f9eb2685..244655ab 100644 --- a/src/braket/experimental/autoqasm/instructions/__init__.py +++ b/src/braket/experimental/autoqasm/instructions/__init__.py @@ -18,7 +18,7 @@ .. code-block:: python - @aq.function + @aq.main def bell(): h(0) cnot(0, 1) diff --git a/src/braket/experimental/autoqasm/instructions/measurements.py b/src/braket/experimental/autoqasm/instructions/measurements.py index 8b8ad22d..b747ed9f 100644 --- a/src/braket/experimental/autoqasm/instructions/measurements.py +++ b/src/braket/experimental/autoqasm/instructions/measurements.py @@ -17,7 +17,7 @@ .. code-block:: python - @aq.function + @aq.main def my_program(): measure(0) """ diff --git a/src/braket/experimental/autoqasm/program/pragmas.py b/src/braket/experimental/autoqasm/program/pragmas.py index 3b04ad55..03825b5e 100644 --- a/src/braket/experimental/autoqasm/program/pragmas.py +++ b/src/braket/experimental/autoqasm/program/pragmas.py @@ -18,7 +18,7 @@ .. code-block:: python - @aq.function + @aq.main def pragma_example() -> None: with aq.Verbatim(): h(0) diff --git a/src/braket/experimental/autoqasm/pulse/__init__.py b/src/braket/experimental/autoqasm/pulse/__init__.py index 162a23aa..65e4b09f 100644 --- a/src/braket/experimental/autoqasm/pulse/__init__.py +++ b/src/braket/experimental/autoqasm/pulse/__init__.py @@ -21,7 +21,7 @@ .. code-block:: python - @aq.function + @aq.main def my_pulse_program(): pulse.shift_frequency(frame, 123) pulse.delay([3, 4], 0.34) diff --git a/test/unit_tests/braket/experimental/autoqasm/conftest.py b/test/unit_tests/braket/experimental/autoqasm/conftest.py index cb8b95f7..417d8dd2 100644 --- a/test/unit_tests/braket/experimental/autoqasm/conftest.py +++ b/test/unit_tests/braket/experimental/autoqasm/conftest.py @@ -21,6 +21,22 @@ from braket.experimental.autoqasm.instructions import cnot, h +@pytest.fixture +def empty_subroutine() -> Callable: + """Empty subroutine fixture. + + Returns: + Callable: the aq program. + """ + + @aq.subroutine + def empty_function() -> None: + """A function that does nothing.""" + pass + + return empty_function + + @pytest.fixture def empty_program() -> Callable: """Empty program fixture. @@ -29,7 +45,7 @@ def empty_program() -> Callable: Callable: the aq program. """ - @aq.function + @aq.main def empty_function() -> None: """A function that does nothing.""" pass @@ -37,6 +53,23 @@ def empty_function() -> None: return empty_function +@pytest.fixture +def bell_state_subroutine() -> Callable: + """Bell state preparation subroutine fixture. + + Returns: + Callable: the aq program. + """ + + @aq.subroutine + def bell_state() -> None: + """A function that generates a two-qubit Bell state.""" + h(0) + cnot(0, 1) + + return bell_state + + @pytest.fixture def bell_state_program() -> Callable: """Bell state preparation program fixture. @@ -45,7 +78,7 @@ def bell_state_program() -> Callable: Callable: the aq program. """ - @aq.function + @aq.main def bell_state() -> None: """A function that generates a two-qubit Bell state.""" h(0) @@ -54,6 +87,23 @@ def bell_state() -> None: return bell_state +@pytest.fixture +def physical_bell_subroutine() -> Callable: + """Physical bell state preparation program fixture. + + Returns: + Callable: the aq program. + """ + + @aq.subroutine + def physical_bell() -> None: + """A function that generates a two-qubit Bell state on particular qubits.""" + h("$0") + cnot("$0", "$5") + + return physical_bell + + @pytest.fixture def physical_bell_program() -> Callable: """Physical bell state preparation program fixture. @@ -62,7 +112,7 @@ def physical_bell_program() -> Callable: Callable: the aq program. """ - @aq.function + @aq.main def physical_bell() -> None: """A function that generates a two-qubit Bell state on particular qubits.""" h("$0") diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index fef676cf..12405adc 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -48,21 +48,21 @@ def test_sim_bell_state(bell_state_program) -> None: _test_on_local_sim(bell_state_program()) -def test_multiple_calls(empty_program, bell_state_program) -> None: - """Tests multiple calls to a single aq.function to ensure that each resulting +def test_multiple_calls(empty_subroutine, bell_state_subroutine) -> None: + """Tests multiple calls to a single aq.main to ensure that each resulting Program object has the expected contents. """ def count_function_calls(program: aq.Program, func_name: str) -> int: return program.to_ir().count(f"{func_name}();") - @aq.function + @aq.main def empty_program_wrapper(): - empty_program() + empty_subroutine() - @aq.function + @aq.main def bell_state_program_wrapper(): - bell_state_program() + bell_state_subroutine() first_program = empty_program_wrapper() assert 1 == count_function_calls(first_program, "empty_function") @@ -77,14 +77,14 @@ def bell_state_program_wrapper(): assert 1 == count_function_calls(third_program, "bell_state") -def test_subroutines(empty_program, bell_state_program, physical_bell_program): +def test_subroutines(empty_subroutine, bell_state_subroutine, physical_bell_subroutine): """Tests calling several subroutines consecutively.""" - @aq.function + @aq.main def call_subroutines() -> None: - empty_program() - bell_state_program() - physical_bell_program() + empty_subroutine() + bell_state_subroutine() + physical_bell_subroutine() expected = """OPENQASM 3.0; def empty_function() { @@ -107,19 +107,19 @@ def physical_bell() { _test_on_local_sim(call_subroutines()) -@aq.function +@aq.subroutine def do_h(q: int): h(q) -@aq.function(num_qubits=6) +@aq.subroutine def recursive_h(q: int): do_h(q) if q > 0: recursive_h(q - 1) -@aq.function(num_qubits=6) +@aq.main(num_qubits=6) def recursive_h_wrapper(q: int): recursive_h(q) @@ -145,6 +145,10 @@ def test_sim_recursive_h_wrapper(): def test_recursive_h(): + @aq.main(num_qubits=6) + def main(n: int): + recursive_h(n) + expected = """OPENQASM 3.0; def do_h(int[32] q) { h __qubits__[q]; @@ -156,22 +160,18 @@ def recursive_h(int[32] q) { } } qubit[6] __qubits__; -do_h(5); recursive_h(4);""" - assert recursive_h(5).to_ir() == expected - -def test_sim_recursive_h(): - _test_on_local_sim(recursive_h(5)) + assert main(4).to_ir() == expected -@aq.function +@aq.subroutine def bell_state_arbitrary_qubits(q0: int, q1: int) -> None: h(q0) cnot(q0, q1) -@aq.function(num_qubits=4) +@aq.main(num_qubits=4) def double_bell_state() -> None: bell_state_arbitrary_qubits(0, 1) bell_state_arbitrary_qubits(2, 3) @@ -193,7 +193,7 @@ def test_sim_double_bell() -> None: _test_on_local_sim(double_bell_state()) -@aq.function +@aq.main def bell_measurement_undeclared() -> None: """A function that generates and measures a two-qubit Bell state.""" h(0) @@ -218,7 +218,7 @@ def test_sim_bell_measurement_undeclared() -> None: _test_on_local_sim(bell_measurement_undeclared()) -@aq.function +@aq.main def bell_measurement_declared() -> None: """A function that generates and measures a two-qubit Bell state.""" c = aq.BitVar(0, size=2) @@ -245,7 +245,7 @@ def test_sim_bell_measurement_declared() -> None: _test_on_local_sim(bell_measurement_declared()) -@aq.function +@aq.main def bell_partial_measurement() -> None: """A function that generates and measures a two-qubit Bell state.""" h(0) @@ -265,7 +265,7 @@ def test_bell_partial_measurement() -> None: assert bell_partial_measurement().to_ir() == expected -@aq.function +@aq.main def bell_measurement_invalid_declared_type() -> None: """A function that generates and measures a two-qubit Bell state. But stores reuslt in an variable with invalid type. @@ -284,7 +284,7 @@ def test_bell_measurement_invalid_declared_type() -> None: assert expected_error_message in str(exc_info.value) -@aq.function +@aq.main def bell_measurement_invalid_declared_size() -> None: """A function that generates and measures a two-qubit Bell state. But stores reuslt in an variable with invalid size. @@ -303,7 +303,7 @@ def test_bell_measurement_invalid_declared_size() -> None: assert expected_error_message in str(exc_info.value) -@aq.function(num_qubits=5) +@aq.main(num_qubits=5) def ghz_qasm_for_loop() -> None: """A function that generates a GHZ state using a QASM for loop.""" n_qubits = 5 @@ -326,7 +326,7 @@ def test_sim_ghz_qasm_for_loop() -> None: _test_on_local_sim(ghz_qasm_for_loop()) -@aq.function +@aq.main def ghz_py_for_loop() -> None: """A function that generates a GHZ state using a Python for loop.""" n_qubits = 5 @@ -350,7 +350,7 @@ def test_sim_ghz_py_for_loop() -> None: _test_on_local_sim(ghz_py_for_loop()) -@aq.function +@aq.subroutine def qasm_simple_condition(do_cnot: bool) -> bool: """A function that contains a QASM conditional statement. @@ -366,7 +366,7 @@ def qasm_simple_condition(do_cnot: bool) -> bool: return do_cnot -@aq.function +@aq.main def qasm_simple_condition_wrapper(do_cnot: bool): qasm_simple_condition(do_cnot) @@ -393,7 +393,7 @@ def test_sim_qasm_simple_condition(do_cnot: bool) -> None: _test_on_local_sim(qasm_simple_condition_wrapper(do_cnot)) -@aq.function +@aq.main def qasm_inline_var_condition() -> aq.BitVar: """A function that contains a QASM conditional statement with an inline var condition. @@ -432,7 +432,7 @@ def test_sim_qasm_inline_var_condition() -> None: _test_on_local_sim(qasm_inline_var_condition()) -@aq.function +@aq.main def ground_state_measurements() -> aq.BitVar: """Measure a few ground state qubits.""" return measure([5, 2, 1]) @@ -465,12 +465,17 @@ def test_simple_measurement_return() -> None: the measurement results are returned from a subroutine. """ - @aq.function + @aq.subroutine + def ground_state_measurements_subroutine() -> aq.BitVar: + """Measure a few ground state qubits.""" + return measure([5, 2, 1]) + + @aq.main def ground_state_measurements_wrapper() -> None: - ground_state_measurements() + ground_state_measurements_subroutine() expected = """OPENQASM 3.0; -def ground_state_measurements() -> bit[3] { +def ground_state_measurements_subroutine() -> bit[3] { bit[3] __bit_0__ = "000"; __bit_0__[0] = measure __qubits__[5]; __bit_0__[1] = measure __qubits__[2]; @@ -481,11 +486,11 @@ def ground_state_measurements() -> bit[3] { """ # TODO: this should be `bit[3]`, but there's a bug. It's being tracked in an issue. expected += """bit __bit_1__; -__bit_1__ = ground_state_measurements();""" +__bit_1__ = ground_state_measurements_subroutine();""" assert ground_state_measurements_wrapper().to_ir() == expected -@aq.function +@aq.main def qasm_measurement_condition() -> aq.BitVar: """A function that contains a mid-circuit measurement conditional. @@ -528,16 +533,20 @@ def test_py_int_qubit_decl() -> None: assert "\nqubit[5] __qubits__;" in qasm -def test_physical_qubit_decl(physical_bell_program) -> None: +def test_physical_qubit_decl(physical_bell_subroutine) -> None: """Tests e.g. h("$0"). Note that physical qubits aren't declared.""" - qasm = physical_bell_program().to_ir() - assert "__qubits__" not in qasm + + @aq.main + def main(): + physical_bell_subroutine() + + assert "__qubits__" not in main().to_ir() def test_invalid_physical_qubit_fails() -> None: """Tests invalid physical qubit formatting.""" - @aq.function + @aq.main def broken() -> None: "Uses invalid string for qubit index" cnot("$0l", "$O1") @@ -549,7 +558,7 @@ def broken() -> None: def test_invalid_qubit_label_fails() -> None: """Tests random string fails for qubit label.""" - @aq.function + @aq.main def broken() -> None: "Uses invalid string for qubit index" h("nope") @@ -561,7 +570,7 @@ def broken() -> None: def test_float_qubit_index_fails() -> None: """Tests floats fails for qubit label.""" - @aq.function + @aq.main def broken() -> None: "Uses float for qubit index" i = 1 @@ -574,7 +583,7 @@ def broken() -> None: def test_bool_qubit_index_fails() -> None: """Tests that an error is raised for boolean qubit type.""" - @aq.function + @aq.main def broken() -> None: "Uses invalid type for qubit index" h(True) @@ -586,7 +595,7 @@ def broken() -> None: def test_invalid_qubit_type_fails() -> None: """Tests that an error is raised for other unusual qubit types.""" - @aq.function + @aq.main def broken() -> None: "Uses invalid type for qubit index" h(h) @@ -598,13 +607,13 @@ def broken() -> None: def test_bit_array_name() -> None: """Tests that auto declared bits are given a reasonable name.""" - @aq.function + @aq.subroutine def my_program() -> aq.BitVar: """Program which requires generating a bit""" h(0) return measure(0) - @aq.function + @aq.main def my_program_wrapper() -> None: my_program() @@ -615,7 +624,7 @@ def my_program_wrapper() -> None: assert expected in my_program_wrapper().to_ir() -@aq.function +@aq.main def reset() -> None: "Reset qubit 0." if measure(0): @@ -653,7 +662,7 @@ def test_program_simple_expr() -> None: an error if the user doesn't specify the number of qubits. """ - @aq.function + @aq.main def simple_range() -> None: "Uses aq.range iterator for qubit index." for i in aq.range(5): @@ -668,7 +677,7 @@ def test_program_with_expr() -> None: an error if the user doesn't specify the number of qubits. """ - @aq.function + @aq.main def qubit_expr() -> None: "Uses aq.range iterator for qubit index." for i in aq.range(5): @@ -691,7 +700,7 @@ def test_multi_for_loop() -> None: h __qubits__[i]; }""" - @aq.function(num_qubits=6) + @aq.main(num_qubits=6) def prog(): for i in aq.range(3): cnot(i, i + 1) @@ -702,13 +711,13 @@ def prog(): assert prog().to_ir() == expected -@aq.function +@aq.subroutine def bell(q0: int, q1: int) -> None: h(q0) cnot(q0, q1) -@aq.function(num_qubits=5) +@aq.main(num_qubits=5) def bell_in_for_loop() -> None: for i in aq.range(3): bell(0, 1) @@ -729,7 +738,7 @@ def bell(int[32] q0, int[32] q1) { assert bell_in_for_loop().to_ir() == expected -@aq.function +@aq.main def classical_variables_types() -> None: a = aq.BitVar(0) a = aq.BitVar(1) # noqa: F841 @@ -771,7 +780,7 @@ def test_sim_classical_variables_types(): def test_classical_variables_assignment(): - @aq.function + @aq.main def prog() -> None: a = aq.IntVar(1) # undeclared target, undeclared value a = aq.IntVar(2) # declared target, undeclared value @@ -789,7 +798,7 @@ def prog() -> None: def test_assignment_measurement_results(): - @aq.function + @aq.main def prog() -> None: a = measure(0) b = a # noqa: F841 @@ -805,21 +814,8 @@ def prog() -> None: assert prog().to_ir() == expected -def test_mismatched_qubits(): - @aq.function(num_qubits=4) - def subroutine() -> None: - _ = measure(0) - - @aq.function(num_qubits=8) - def main() -> None: - subroutine() - - with pytest.raises(errors.InconsistentNumQubits): - main() - - def test_nested_function(): - @aq.function + @aq.main def make_ghz(n: int) -> None: def ghz(n: int): if n == 1: @@ -842,8 +838,8 @@ def ghz(n: int): def test_double_decorated_function(): - @aq.function - @aq.function + @aq.main + @aq.main def empty_program() -> None: pass @@ -852,7 +848,7 @@ def empty_program() -> None: def test_main_return(): - @aq.function + @aq.main def main() -> int: return 1 @@ -861,13 +857,63 @@ def main() -> int: def test_main_no_return(): - @aq.function + @aq.subroutine def tester(x: int) -> int: return measure(x) - @aq.function(num_qubits=3) + @aq.main(num_qubits=3) def main(): x = 3 tester(x) main() + + +def test_subroutine_args(): + """Test that subroutines will fail if supplied args.""" + with pytest.raises(TypeError, match="got an unexpected keyword argument"): + + @aq.subroutine(num_qubits=5) + def bell(q0: int, q1: int) -> None: + h(q0) + cnot(q0, q1) + + +def test_direct_subroutine_call_w_args(): + """Shouldn't be able to call a subroutine directly.""" + + @aq.subroutine + def bell(q0: int, q1: int) -> None: + h(q0) + cnot(q0, q1) + + with pytest.raises(errors.AutoQasmTypeError): + bell() + + +def test_direct_subroutine_call_no_args(): + """Shouldn't be able to call a subroutine directly.""" + + @aq.subroutine + def bell() -> None: + h(0) + cnot(0, 1) + + with pytest.raises(errors.AutoQasmTypeError): + bell() + + +def test_main_from_main(): + """Can't call main from main!""" + + @aq.main + def bell(q0: int, q1: int) -> None: + h(q0) + cnot(q0, q1) + + @aq.main + def main(): + bell(0, 1) + + with pytest.raises(errors.AutoQasmTypeError): + main() diff --git a/test/unit_tests/braket/experimental/autoqasm/test_converters.py b/test/unit_tests/braket/experimental/autoqasm/test_converters.py index 9e0de73e..6e169e53 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_converters.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_converters.py @@ -62,7 +62,7 @@ def fn() -> None: def test_break_for_loop(): - @aq.function + @aq.main def main(): for i in aq.range(3): aq.gates.h(i) @@ -73,7 +73,7 @@ def main(): def test_break_while_loop(): - @aq.function + @aq.main def uses_while_w_break(): while aq.gates.measure(0): aq.gates.x(0) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py index b5e6a72d..288052e9 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py @@ -29,7 +29,7 @@ def empty_gate(q: aq.Qubit): def test_empty_gate() -> None: - @aq.function + @aq.main def my_program(): empty_gate(0) @@ -48,7 +48,7 @@ def my_program(): def test_double_gate_decorator() -> None: double_decorated = aq.gate(empty_gate) - @aq.function + @aq.main def my_program(): double_decorated(0) @@ -70,7 +70,7 @@ class MyGate: def __init__(self, q: aq.Qubit): h(q) - @aq.function + @aq.main def main(): MyGate(0) @@ -84,7 +84,7 @@ def my_gate(q: aq.Qubit): h(q) not_a_symbol() # noqa: F821 # type: ignore - @aq.function + @aq.main def main(): my_gate(0) @@ -101,7 +101,7 @@ def my_gate(q: aq.Qubit): def my_gate(q: aq.Qubit, angle: float): # noqa: F811 rx(q, angle) - @aq.function + @aq.main def main(): my_gate(0, np.pi / 4) @@ -119,7 +119,7 @@ def main(): def test_duplicate_gate_names_in_subroutine() -> None: """Verify that gates can only be defined at the top level.""" - @aq.function + @aq.subroutine def define_gate_in_subroutine(): @aq.gate def my_gate(q: aq.Qubit): @@ -131,7 +131,7 @@ def my_gate(q: aq.Qubit): def my_gate(q: aq.Qubit, angle: float): rx(q, angle) - @aq.function + @aq.main def main(): my_gate(0, np.pi / 4) define_gate_in_subroutine() @@ -157,7 +157,7 @@ def my_gate(q0: aq.Qubit, q1: aq.Qubit): h(q0) x(q1) - @aq.function + @aq.main def incorrect_arg_count(): my_gate(0) @@ -174,7 +174,7 @@ def my_gate(q: aq.Qubit, theta: float): h(q) rx(q, theta) - @aq.function + @aq.main def incorrect_arg_types(): my_gate(0.25, 0) @@ -187,7 +187,7 @@ def test_missing_annotation() -> None: def my_gate(a): pass - @aq.function + @aq.main def my_program(): my_gate("test") @@ -200,7 +200,7 @@ def test_incorrect_annotation() -> None: def my_gate(a: str): pass - @aq.function + @aq.main def my_program(): my_gate("test") @@ -213,7 +213,7 @@ def test_no_qubit_args() -> None: def not_a_gate(angle: float): pass - @aq.function + @aq.main def my_program(): not_a_gate(np.pi) @@ -230,7 +230,7 @@ def my_gate(q: aq.Qubit): h(q) x(1) # invalid - @aq.function + @aq.main def my_program(): my_gate(0) @@ -252,7 +252,7 @@ def my_gate(q: aq.Qubit, theta: float): t(q) rx(q, theta) - @aq.function + @aq.main def my_program(): my_gate(0, np.pi / 4) my_gate(1, 3 * np.pi / 4) @@ -285,12 +285,12 @@ def test_gate_called_from_subroutine() -> None: def t(q: aq.Qubit): rz(q, np.pi / 4) - @aq.function + @aq.subroutine def subroutine(q0: int, q1: int): t(q0) t(q1) - @aq.function(num_qubits=4) + @aq.main(num_qubits=4) def main(): subroutine(0, 1) subroutine(2, 3) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py index 181f5dd5..ac438f99 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_operators.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py @@ -85,7 +85,7 @@ def test_conditional_expressions_py_cond(if_true: dict, if_false: dict) -> None: def test_inline_conditional_assignment() -> None: """Tests conditional expression where the if and else clauses return different types.""" - @aq.function + @aq.main def cond_exp_assignment(): a = aq.IntVar(2) * aq.IntVar(3) if aq.BoolVar(True) else aq.IntVar(4) # noqa: F841 @@ -118,7 +118,7 @@ def cond_exp_assignment(): def test_unsupported_inline_conditional_assignment(else_value) -> None: """Tests conditional expression where the if and else clauses return different types.""" - @aq.function + @aq.main def cond_exp_assignment_different_types(): a = aq.IntVar(1) if aq.BoolVar(True) else else_value() # noqa: F841 @@ -129,7 +129,7 @@ def cond_exp_assignment_different_types(): def test_branch_assignment_undeclared() -> None: """Tests if-else branch where an undeclared variable is assigned in both branches.""" - @aq.function + @aq.main def branch_assignment_undeclared(): if aq.BoolVar(True): a = aq.IntVar(1) # noqa: F841 @@ -151,7 +151,7 @@ def branch_assignment_undeclared(): def test_branch_assignment_declared() -> None: """Tests if-else branch where a declared variable is assigned in both branches.""" - @aq.function + @aq.main def branch_assignment_declared(): a = aq.IntVar(5) if aq.BoolVar(True): @@ -441,7 +441,7 @@ def test_slice_bits() -> None: """Test that bit var slicing and assignment works when assigning to a size 1 bit.""" - @aq.function + @aq.main def slice(): a = aq.BitVar(0, size=6) b = aq.BitVar(1) @@ -460,7 +460,7 @@ def slice(): def test_slice_bits_w_measure() -> None: """Test assignment of one bit in an array to a measurement result.""" - @aq.function + @aq.main def measure_to_slice(): b0 = aq.BitVar(size=10) c = measure(0) @@ -606,7 +606,7 @@ def test_assignment_py(value: Any) -> None: def test_py_if_stmt(value: int) -> None: """Test Python if branches on true and false conditions.""" - @aq.function + @aq.main def test_control_flow(a: int): "Quick if statement test" if a: @@ -625,7 +625,7 @@ def test_control_flow(a: int): def test_py_while() -> None: """Test Python while loop.""" - @aq.function + @aq.main def test_control_flow(): "Quick while loop test" a = 3 diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py b/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py index ac5337f9..10982b07 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py @@ -20,7 +20,7 @@ def test_with_verbatim_box() -> None: """Tests the with statement with verbatim box `Verbatim`.""" - @aq.function + @aq.main def program_func() -> None: """User program to test.""" h(0) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/braket/experimental/autoqasm/test_program.py index c33bd2c9..4540f9aa 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_program.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_program.py @@ -69,12 +69,12 @@ def test_to_ir() -> None: def test_multiprocessing() -> None: """Tests multiprocessing with the aq.Program object.""" - @aq.function + @aq.subroutine def circuit(angle: float): rx(0, angle) cnot(0, 1) - @aq.function + @aq.main def zne(scale: int, angle: float) -> aq.BitVar: for i in aq.range(scale): circuit(angle) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pulse.py b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py index ee12021d..9e527acc 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_pulse.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py @@ -41,7 +41,7 @@ def test_mix_gate_pulse() -> None: """Test mixed usage of gates and pulses.""" - @aq.function + @aq.main def my_program(): shift_frequency(FRAME1, 0.1234) rx(1, 0.1) @@ -71,7 +71,7 @@ def my_program(): def test_merge_cal_box() -> None: """Test subsequent cal boxes are merged.""" - @aq.function + @aq.main def my_program(): barrier(0) delay([3, 4], 0.34) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py b/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py index e10ec295..95afdef0 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py @@ -23,10 +23,10 @@ from braket.experimental.autoqasm.instructions import cnot, h, measure, x -def test_convert_invalid_object() -> None: - """Tests the aq.function decorator on something that is not a function.""" +def test_convert_invalid_main_object() -> None: + """Tests the aq.main decorator on something that is not a function.""" - @aq.function + @aq.main class MyClass: pass @@ -34,11 +34,26 @@ class MyClass: MyClass() +def test_convert_invalid_subroutine_object() -> None: + """Tests the aq.subroutine decorator on something that is not a function.""" + + @aq.subroutine + class MyClass: + pass + + @aq.main + def main(): + MyClass() + + with pytest.raises(ValueError): + main() + + def test_autograph_disabled() -> None: - """Tests the aq.function decorator with autograph disabled by control status, + """Tests the aq.main decorator with autograph disabled by control status, and verifies that the function is not converted.""" - @aq.function + @aq.main def my_program(): h(0) if measure(0): @@ -50,13 +65,13 @@ def my_program(): def test_partial_function() -> None: - """Tests aq.function decorator application to a partial function.""" + """Tests aq.main decorator application to a partial function.""" def bell(q0: int, q1: int): h(q0) cnot(q0, q1) - @aq.function + @aq.main def bell_decorated(q0: int, q1: int): bell(q0, q1) @@ -65,7 +80,7 @@ def bell_decorated(q0: int, q1: int): h __qubits__[1]; cnot __qubits__[1], __qubits__[3];""" - bell_partial = aq.function(functools.partial(bell, 1)) + bell_partial = aq.main(functools.partial(bell, 1)) assert bell_partial(3).to_ir() == expected bell_decorated_partial = functools.partial(bell_decorated, 1) @@ -76,7 +91,7 @@ def bell_decorated(q0: int, q1: int): def test_classmethod() -> None: - """Tests aq.function decorator application to a classmethod.""" + """Tests aq.main decorator application to a classmethod.""" class MyClass: @classmethod @@ -85,7 +100,7 @@ def bell(self, q0: int, q1: int): cnot(q0, q1) @classmethod - @aq.function + @aq.main def bell_decorated(self, q0: int, q1: int): self.bell(q0, q1) @@ -94,18 +109,18 @@ def bell_decorated(self, q0: int, q1: int): h __qubits__[1]; cnot __qubits__[1], __qubits__[3];""" - assert aq.function(MyClass.bell)(1, 3).to_ir() == expected + assert aq.main(MyClass.bell)(1, 3).to_ir() == expected assert MyClass.bell_decorated(1, 3).to_ir() == expected a = MyClass() - assert aq.function(a.bell)(1, 3).to_ir() == expected + assert aq.main(a.bell)(1, 3).to_ir() == expected assert a.bell_decorated(1, 3).to_ir() == expected def test_with_verbose_logging() -> None: - """Tests aq.function decorator application with verbose logging enabled.""" + """Tests aq.main decorator application with verbose logging enabled.""" - @aq.function + @aq.main def nothing(): pass diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index a7cd0709..834fc54e 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -47,12 +47,12 @@ def test_qasm_range( def test_return_bit(): """Test type discovery of bit return values.""" - @aq.function + @aq.subroutine def ret_test() -> aq.BitVar: res = aq.BitVar(1) return res - @aq.function + @aq.main def main() -> aq.BitVar: return ret_test() @@ -71,12 +71,12 @@ def ret_test() -> bit { def test_return_int(): """Test type discovery of int return values.""" - @aq.function + @aq.subroutine def ret_test() -> int: res = aq.IntVar(1) return res - @aq.function + @aq.main def main() -> int: return ret_test() @@ -95,12 +95,12 @@ def ret_test() -> int[32] { def test_return_float(): """Test type discovery of float return values.""" - @aq.function + @aq.subroutine def ret_test() -> float: res = aq.FloatVar(1.0) return res - @aq.function + @aq.main def main() -> float: return ret_test() @@ -119,12 +119,12 @@ def ret_test() -> float[64] { def test_return_bool(): """Test type discovery of boolean return values.""" - @aq.function + @aq.subroutine def ret_test() -> bool: res = aq.BoolVar(True) return res - @aq.function + @aq.main def main() -> bool: return ret_test() @@ -143,11 +143,11 @@ def ret_test() -> bool { def test_return_bin_expr(): """Test type discovery of int return values from an expression.""" - @aq.function + @aq.subroutine def add(a: int, b: int) -> int: return a + b - @aq.function + @aq.main def ret_test() -> int: a = aq.IntVar(5) b = aq.IntVar(6) @@ -170,7 +170,7 @@ def add(int[32] a, int[32] b) -> int[32] { def test_return_none(): """Test discovery of None return annotation.""" - @aq.function + @aq.main def ret_test() -> None: return None @@ -182,12 +182,12 @@ def ret_test() -> None: def test_return_array_int(): """Test return type discovery of array values.""" - @aq.function + @aq.subroutine def ret_test() -> List[int]: res = aq.ArrayVar([1, 2, 3], dimensions=[3]) return res - @aq.function + @aq.main def main() -> List[int]: return ret_test() @@ -206,11 +206,11 @@ def ret_test() -> array[int[32], 3] { def test_return_python_array(): """Test returning a python array of ints.""" - @aq.function + @aq.subroutine def tester(arr: List[int]) -> List[int]: return [1, 2, 3] - @aq.function(num_qubits=4) + @aq.main(num_qubits=4) def main(): tester() @@ -229,11 +229,11 @@ def tester(array[int[32], 10] arr) -> array[int[32], 10] { def test_return_array_unsupported(): """Test unsupported array type.""" - @aq.function + @aq.subroutine def tester(arr: List[float]) -> List[float]: return [1.2, 2.1] - @aq.function(num_qubits=4) + @aq.main(num_qubits=4) def main(): tester([3.3]) @@ -244,12 +244,12 @@ def main(): def test_return_func_call(): """Test returning the result of another function call.""" - @aq.function + @aq.subroutine def helper() -> int: res = aq.IntVar(1) return res - @aq.function + @aq.main def ret_test() -> int: return helper() @@ -268,11 +268,11 @@ def helper() -> int[32] { def test_map_bool(): """Test boolean input parameter type.""" - @aq.function + @aq.subroutine def annotation_test(input: bool): pass - @aq.function + @aq.main def main(): annotation_test(True) @@ -287,11 +287,11 @@ def annotation_test(bool input) { def test_map_int(): """Test integer input parameter type.""" - @aq.function + @aq.subroutine def annotation_test(input: int): pass - @aq.function + @aq.main def main(): annotation_test(1) @@ -306,11 +306,11 @@ def annotation_test(int[32] input) { def test_map_float(): """Test float input parameter type.""" - @aq.function + @aq.subroutine def annotation_test(input: float): pass - @aq.function + @aq.main def main(): annotation_test(1.0) @@ -325,11 +325,11 @@ def annotation_test(float[64] input) { def test_map_array(): """Test array input parameter type.""" - @aq.function + @aq.subroutine def annotation_test(input: List[int]): pass - @aq.function + @aq.main def main(): a = aq.ArrayVar([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dimensions=[10]) annotation_test(a) @@ -347,11 +347,11 @@ def annotation_test(array[int[32], 10] input) { def test_map_other(): """Test unexpected input parameter type handling.""" - @aq.function + @aq.subroutine def annotation_test(input: aq.BitVar): pass - @aq.function + @aq.main def main(): a = aq.BitVar(1) annotation_test(a) @@ -369,11 +369,11 @@ def annotation_test(bit input) { def test_map_other_unnamed_arg(): """Test unexpected input parameter type handling with unnamed arg.""" - @aq.function + @aq.subroutine def annotation_test(input: aq.BitVar): pass - @aq.function + @aq.main def main(): annotation_test(aq.BitVar(1)) @@ -389,12 +389,12 @@ def annotation_test(bit input) { def test_map_and_assign_arg(): """Test input parameter handling which is assigned to another variable.""" - @aq.function + @aq.subroutine def assign_param(c: int) -> None: d = aq.IntVar(4) c = d # noqa: F841 - @aq.function + @aq.main def main(): c = aq.IntVar(0) assign_param(c) @@ -415,11 +415,11 @@ def assign_param(int[32] c) { def test_unnamed_retval_python_type() -> None: """Tests subroutines which return unnamed Python values.""" - @aq.function + @aq.subroutine def retval_test() -> int: return 1 - @aq.function + @aq.main def caller() -> int: return retval_test() @@ -438,11 +438,11 @@ def retval_test() -> int[32] { def test_unnamed_retval_qasm_type() -> None: """Tests subroutines which return unnamed QASM values.""" - @aq.function + @aq.subroutine def retval_test() -> aq.BitVar: return aq.BitVar(1) - @aq.function + @aq.main def caller() -> aq.BitVar: return retval_test() @@ -461,11 +461,15 @@ def retval_test() -> bit { def test_recursive_unassigned_retval_python_type() -> None: """Tests recursive subroutines which do not assign the return value to a variable.""" - @aq.function + @aq.subroutine def retval_recursive() -> int: retval_recursive() return 1 + @aq.main + def main(): + retval_recursive() + expected_qasm = """OPENQASM 3.0; def retval_recursive() -> int[32] { int[32] retval_; @@ -474,22 +478,24 @@ def retval_recursive() -> int[32] { retval_ = 1; return retval_; } -int[32] retval_; int[32] __int_3__; -__int_3__ = retval_recursive(); -retval_ = 1;""" +__int_3__ = retval_recursive();""" - assert retval_recursive().to_ir() == expected_qasm + assert main().to_ir() == expected_qasm def test_recursive_assigned_retval_python_type() -> None: """Tests recursive subroutines which assign the return value to a variable.""" - @aq.function + @aq.subroutine def retval_recursive() -> int: a = retval_recursive() # noqa: F841 return 1 + @aq.main + def main(): + retval_recursive() + expected_qasm = """OPENQASM 3.0; def retval_recursive() -> int[32] { int[32] a; @@ -500,29 +506,25 @@ def retval_recursive() -> int[32] { retval_ = 1; return retval_; } -int[32] a; -int[32] retval_; int[32] __int_3__; -__int_3__ = retval_recursive(); -a = __int_3__; -retval_ = 1;""" +__int_3__ = retval_recursive();""" - assert retval_recursive().to_ir() == expected_qasm + assert main().to_ir() == expected_qasm def test_recursive_retval_expression_python_type() -> None: """Tests recursive subroutines which use the return value in an expression.""" - @aq.function + @aq.subroutine def retval_constant() -> int: return 3 - @aq.function + @aq.subroutine def retval_recursive() -> float: a = 2 * retval_recursive() + (retval_constant() + 2) / 3 return a - @aq.function + @aq.main def caller() -> int: return retval_recursive() @@ -548,33 +550,41 @@ def retval_constant() -> int[32] { def test_recursive_list() -> None: """Tests recursive subroutines which return a list.""" - @aq.function + @aq.subroutine def retval_recursive() -> List[int]: retval_recursive() return [1] - assert "-> array[int[32], 10]" in retval_recursive().to_ir() + @aq.main + def main(): + retval_recursive() + + assert "-> array[int[32], 10]" in main().to_ir() def test_recursive_oqpy_type() -> None: """Tests recursive subroutines which returns an oqpy type.""" - @aq.function + @aq.subroutine def retval_recursive() -> aq.BitVar: retval_recursive() return aq.BitVar(0) - assert "-> bit" in retval_recursive().to_ir() + @aq.main + def main(): + retval_recursive() + + assert "-> bit" in main().to_ir() def test_error_for_tuple_param() -> None: """Tuples are not supported as parameters.""" - @aq.function + @aq.subroutine def param_test(input: Tuple): pass - @aq.function + @aq.main def main(): param_test(aq.BitVar(1)) @@ -585,11 +595,11 @@ def main(): def test_error_for_missing_param_type() -> None: """Parameters require type hints.""" - @aq.function + @aq.subroutine def param_test(input): pass - @aq.function + @aq.main def main(): param_test(aq.BitVar(1)) @@ -600,11 +610,11 @@ def main(): def test_ignore_ret_typehint_bool(): """Test type discovery of boolean return values.""" - @aq.function + @aq.subroutine def ret_test() -> List[int]: return True - @aq.function + @aq.main def main() -> bool: ret_test() @@ -623,11 +633,11 @@ def ret_test() -> bool { def test_ignore_ret_typehint_list(): """Test type discovery of list return values.""" - @aq.function + @aq.subroutine def ret_test() -> int: return [1, 2, 3] - @aq.function(num_qubits=4) + @aq.main(num_qubits=4) def main() -> float: ret_test() @@ -647,11 +657,11 @@ def ret_test() -> array[int[32], 10] { def test_ignore_missing_ret_typehint_list(): """Test type discovery of return types with no annotations.""" - @aq.function + @aq.subroutine def ret_test(): return [1, 2, 3] - @aq.function(num_qubits=4) + @aq.main(num_qubits=4) def main(): ret_test() @@ -671,11 +681,11 @@ def ret_test() -> array[int[32], 10] { def test_ignore_missing_ret_typehint_float(): """Test type discovery of return types with no annotations.""" - @aq.function + @aq.subroutine def ret_test(): return 1.2 - @aq.function(num_qubits=4) + @aq.main(num_qubits=4) def main(): ret_test() @@ -695,11 +705,11 @@ def ret_test() -> float[64] { def test_param_array_list_missing_arg(): """Test list parameter with missing type arg (list rather than list[int]).""" - @aq.function + @aq.subroutine def param_test(arr: List) -> int: return 1 - @aq.function(num_qubits=4) + @aq.main(num_qubits=4) def main(): param_test() From 7edd68231d7079c611004fe89610e8b3f6547943 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Fri, 25 Aug 2023 14:13:40 -0400 Subject: [PATCH 0824/1165] Move error handling into the context manager (#684) --- src/braket/experimental/autoqasm/api.py | 88 ++++++++----------- .../experimental/autoqasm/program/program.py | 7 ++ 2 files changed, 43 insertions(+), 52 deletions(-) diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 06dc871f..2150b631 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -239,19 +239,11 @@ def _convert_program( ) with aq_program.build_program(user_config) as program_conversion_context: - try: - with conversion_ctx: - if is_subroutine: - _convert_subroutine(f, program_conversion_context, options, args, kwargs) - else: - _convert_main(f, program_conversion_context, options, args, kwargs) - except Exception as e: - if isinstance(e, errors.AutoQasmError): - raise - elif hasattr(e, "ag_error_metadata"): - raise e.ag_error_metadata.to_exception(e) + with conversion_ctx: + if is_subroutine: + _convert_subroutine(f, program_conversion_context, options, args, kwargs) else: - raise + _convert_main(f, program_conversion_context, options, args, kwargs) if is_subroutine: return program_conversion_context.return_variable @@ -367,48 +359,40 @@ def _convert_gate( args: List[Any], kwargs: Dict[str, Any], ) -> Callable: - try: - # We must be inside an active conversion context in order to invoke a gate - program_conversion_context = aq_program.get_program_conversion_context() - - # Wrap the function into an oqpy gate definition - wrapped_f, gate_args = _wrap_for_oqpy_gate(f, options) - gate_name = f.__name__ - - # Validate that the gate definition acts on at least one qubit - if not gate_args.qubits: - raise errors.ParameterTypeError( - f'Gate definition "{gate_name}" has no arguments of type aq.Qubit. ' - "Every gate definition must contain at least one qubit argument." - ) - - # Process the gate definition - with program_conversion_context.gate_definition(gate_name, gate_args): - # TODO - enforce that nothing gets added to the program inside here except gates - wrapped_f(gate_args._args) + # We must be inside an active conversion context in order to invoke a gate + program_conversion_context = aq_program.get_program_conversion_context() + + # Wrap the function into an oqpy gate definition + wrapped_f, gate_args = _wrap_for_oqpy_gate(f, options) + gate_name = f.__name__ + + # Validate that the gate definition acts on at least one qubit + if not gate_args.qubits: + raise errors.ParameterTypeError( + f'Gate definition "{gate_name}" has no arguments of type aq.Qubit. ' + "Every gate definition must contain at least one qubit argument." + ) - # Add the gate definition to the root-level program if necessary - root_oqpy_program = program_conversion_context.oqpy_program_stack[0] - if gate_name not in root_oqpy_program.gates: - gate_stmt = program_conversion_context.get_oqpy_program().gates[gate_name] - root_oqpy_program._add_gate(gate_name, gate_stmt) + # Process the gate definition + with program_conversion_context.gate_definition(gate_name, gate_args): + # TODO - enforce that nothing gets added to the program inside here except gates + wrapped_f(gate_args._args) - # Add the gate invocation to the program - if len(args) != len(gate_args): - raise errors.ParameterTypeError( - f'Incorrect number of arguments passed to gate "{gate_name}". ' - f"Expected {len(gate_args)}, got {len(args)}." - ) - qubit_args = [args[i] for i in gate_args.qubit_indices] - angle_args = [args[i] for i in gate_args.angle_indices] - aq_instructions.instructions._qubit_instruction(gate_name, qubit_args, *angle_args) - except Exception as e: - if isinstance(e, errors.AutoQasmError): - raise - elif hasattr(e, "ag_error_metadata"): - raise e.ag_error_metadata.to_exception(e) - else: - raise + # Add the gate definition to the root-level program if necessary + root_oqpy_program = program_conversion_context.oqpy_program_stack[0] + if gate_name not in root_oqpy_program.gates: + gate_stmt = program_conversion_context.get_oqpy_program().gates[gate_name] + root_oqpy_program._add_gate(gate_name, gate_stmt) + + # Add the gate invocation to the program + if len(args) != len(gate_args): + raise errors.ParameterTypeError( + f'Incorrect number of arguments passed to gate "{gate_name}". ' + f"Expected {len(gate_args)}, got {len(args)}." + ) + qubit_args = [args[i] for i in gate_args.qubit_indices] + angle_args = [args[i] for i in gate_args.angle_indices] + aq_instructions.instructions._qubit_instruction(gate_name, qubit_args, *angle_args) def _make_return_instance_from_oqpy_return_type(return_type: Any) -> Any: diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index f6769c5c..faa4faee 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -284,6 +284,13 @@ def build_program(user_config: Optional[UserConfig] = None) -> None: _get_local().program_conversion_context = ProgramConversionContext(user_config) owns_program_conversion_context = True yield _get_local().program_conversion_context + except Exception as e: + if isinstance(e, errors.AutoQasmError): + raise + elif hasattr(e, "ag_error_metadata"): + raise e.ag_error_metadata.to_exception(e) + else: + raise finally: if owns_program_conversion_context: _get_local().program_conversion_context = None From 8b4f7aac1407c3a55e6b67b1ad7ddb072b5c8fa3 Mon Sep 17 00:00:00 2001 From: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com> Date: Fri, 25 Aug 2023 14:33:22 -0700 Subject: [PATCH 0825/1165] change: readthedocs integration (#686) * change: readthedocs integration * Update .readthedocs.yml --------- Co-authored-by: Cody Wang --- .readthedocs.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index a52ab5c2..59d0f163 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -12,10 +12,15 @@ 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 + tools: + python: "3.8" # Optionally set the version of Python and requirements required to build your docs python: - version: 3.8 install: - method: pip path: . From d21e10b70f59287386e9730a446117945213cbe5 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Sat, 26 Aug 2023 18:30:33 -0400 Subject: [PATCH 0826/1165] Use a callback to simplify autoqasm/api logic (#685) * Use a callback to simplify autoqasm/api logic * Move code, respond to CR --- src/braket/experimental/autoqasm/api.py | 532 ++++++++++-------------- 1 file changed, 215 insertions(+), 317 deletions(-) diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 2150b631..2ddb2313 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -17,7 +17,7 @@ import functools import inspect from types import FunctionType -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from typing import Any, Callable, Dict, List, Optional, Tuple import openqasm3.ast as qasm_ast import oqpy.base @@ -28,7 +28,7 @@ import braket.experimental.autoqasm.transpiler as aq_transpiler import braket.experimental.autoqasm.types as aq_types from braket.experimental.autoqasm import errors -from braket.experimental.autoqasm.autograph.core import ag_ctx, converter +from braket.experimental.autoqasm.autograph.core import converter from braket.experimental.autoqasm.autograph.impl.api_core import ( autograph_artifact, is_autograph_artifact, @@ -51,27 +51,17 @@ def main(*args, num_qubits: Optional[int] = None) -> Callable[[Any], aq_program. Callable[[Any], Program]: A callable which returns the converted quantum program when called. """ - # First, we just process the arguments to the decorator function user_config = aq_program.UserConfig(num_qubits=num_qubits) - if len(args): - # In this case, num_qubits wasn't supplied. - # Matches the following syntax: - # @aq.main - # def my_func(...): - # Equivalently, `main(my_func, num_qubits=None)` - return _function_wrapper(args[0], user_config=user_config) - else: - # In this case, num_qubits was supplied, and we don't know `f` yet. - # Matches the following syntax: - # @aq.main(num_qubits=x) - # def my_func(...): - # Equivalently: `main(num_qubits=x)(my_func)` + if not args: + def _function_with_params(f: Callable) -> Callable[[Any], aq_program.Program]: - return _function_wrapper(f, user_config=user_config) + return _function_wrapper(f, _convert_main, converter_args={"user_config": user_config}) return _function_with_params + return _function_wrapper(args[0], _convert_main, converter_args={"user_config": user_config}) + def subroutine(*args) -> Callable[[Any], aq_program.Program]: """Decorator that converts a function into a callable that will insert a subroutine into @@ -81,7 +71,7 @@ def subroutine(*args) -> Callable[[Any], aq_program.Program]: Callable[[Any], Program]: A callable which returns the converted quantum program when called. """ - return _function_wrapper(*args, user_config=None, is_subroutine=True) + return _function_wrapper(args[0], _convert_subroutine) def gate(*args) -> Callable[[Any], None]: @@ -91,27 +81,21 @@ def gate(*args) -> Callable[[Any], None]: Callable[[Any],]: A callable which can be used as a custom gate inside an aq.function or inside another aq.gate. """ - f = args[0] - if is_autograph_artifact(f): - return f - - wrapper_factory = _convert_gate_wrapper() - - return autograph_artifact(wrapper_factory(f)) + return _function_wrapper(args[0], _convert_gate) def _function_wrapper( f: Callable, - user_config: aq_program.UserConfig, - is_subroutine: bool = False, + converter_callback: Callable, + converter_args: Optional[Dict[str, Any]] = None, ) -> Callable[[Any], aq_program.Program]: """Wrapping and conversion logic around the user function `f`. Args: f (Callable): The target function to be converted which represents the entry point of the quantum program. - user_config (UserConfig): User-specified settings that influence program building. - is_subroutine (bool): If the function corresponds to a subroutine or main. + converter_callback (Callable): The function converter, e.g., _convert_main. + converter_args (Optional[Dict[str, Any]]): Extra arguments for the function converter. Returns: Callable[[Any], Program]: A callable which returns the converted @@ -120,74 +104,23 @@ def _function_wrapper( if is_autograph_artifact(f): return f - # TODO: (laurecap) Update to accept a callback - wrapper_factory = _convert_program_wrapper( - user_config=user_config, - is_subroutine=is_subroutine, - ) - - return autograph_artifact(wrapper_factory(f)) + if not converter_args: + converter_args = {} + def _wrapper(*args, **kwargs) -> Callable: + """Wrapper that calls the converted version of f.""" + options = converter.ConversionOptions( + user_requested=True, + optional_features=_autograph_optional_features(), + ) + # Call the appropriate function converter + return converter_callback(f, options, args, kwargs, **converter_args) -def _convert_program_wrapper( - user_config: aq_program.UserConfig, - is_subroutine: bool = False, -) -> Callable: - """Generates a factory which does the conversion of a function into a callable - that returns a Program object containing the quantum program. - - Args: - user_config (UserConfig): User-specified settings that influence program building. - is_subroutine (bool): If the function corresponds to a subroutine or main. - - Returns: - Callable: a decorator that converts the given function into an equivalent - function that uses AutoQASM operations. - """ - conversion_ctx = ag_ctx.NullCtx() - - def _decorator(f: Callable) -> Callable[[Any], aq_program.Program]: - """aq.function decorator implementation.""" - - def _wrapper(*args, **kwargs) -> Union[aq_program.Program, Optional[oqpy.base.Var]]: - """Wrapper that calls the converted version of f.""" - # This code is executed once the decorated function is called - options = converter.ConversionOptions( - recursive=False, - user_requested=True, - optional_features=_autograph_optional_features(), - ) - return _convert_program( - f, conversion_ctx, options, user_config, args, kwargs, is_subroutine - ) - - if inspect.isfunction(f) or inspect.ismethod(f): - _wrapper = functools.update_wrapper(_wrapper, f) - - decorated_wrapper = tf_decorator.make_decorator(f, _wrapper) - return autograph_artifact(decorated_wrapper) - - return _decorator - - -def _convert_gate_wrapper() -> Callable: - def _decorator(f: Callable) -> Callable[[Any], None]: - """aq.gate decorator implementation.""" - - def _wrapper(*args, **kwargs) -> Callable: - """Wrapper that calls the converted version of f.""" - options = converter.ConversionOptions( - optional_features=_autograph_optional_features(), - ) - return _convert_gate(f, options, args, kwargs) - - if inspect.isfunction(f) or inspect.ismethod(f): - _wrapper = functools.update_wrapper(_wrapper, f) - - decorated_wrapper = tf_decorator.make_decorator(f, _wrapper) - return autograph_artifact(decorated_wrapper) + if inspect.isfunction(f) or inspect.ismethod(f): + _wrapper = functools.update_wrapper(_wrapper, f) - return _decorator + decorated_wrapper = tf_decorator.make_decorator(f, _wrapper) + return autograph_artifact(decorated_wrapper) def _autograph_optional_features() -> Tuple[converter.Feature]: @@ -197,66 +130,12 @@ def _autograph_optional_features() -> Tuple[converter.Feature]: ) -def _convert_program( - f: Callable, - conversion_ctx: ag_ctx.ControlStatusCtx, - options: converter.ConversionOptions, - user_config: aq_program.UserConfig, - args: List[Any], - kwargs: Dict[str, Any], - is_subroutine: bool, -) -> Union[aq_program.Program, Optional[oqpy.base.Var]]: - """Convert the initial callable `f` into a full AutoQASM program `program`. - - This function adds error handling around `_convert_subroutine` - and `_convert_main`, where the conversion logic itself lives. - - Args: - f (Callable): The function to be converted. - conversion_ctx (ControlStatusCtx): the Autograph context in which `f` is used. - options (converter.ConversionOptions): Converter options. - user_config (UserConfig): User-specified settings that influence program building - args (List[Any]): Arguments passed to the program when called. - kwargs (Dict[str, Any]): Keyword arguments passed to the program when called. - is_subroutine (bool): If the function corresponds to a subroutine or main. - - - Returns: - Union[Program, Optional[Var]]: The converted program, if this is a top-level call - to the conversion process. Or, the oqpy variable returned from the converted function, - if this is a subroutine conversion. - """ - if is_subroutine and not aq_program.in_active_program_conversion_context(): - raise errors.AutoQasmTypeError( - "Subroutines shouldn't be called directly. Please define an entry point " - "function, decorate it with '@aq.main', and call your subroutine " - "from within that function." - ) - elif not is_subroutine and aq_program.in_active_program_conversion_context(): - raise errors.AutoQasmTypeError( - f"Cannot call main function '{f.__name__}' from another main function. Did you mean " - "to use '@aq.subroutine'?" - ) - - with aq_program.build_program(user_config) as program_conversion_context: - with conversion_ctx: - if is_subroutine: - _convert_subroutine(f, program_conversion_context, options, args, kwargs) - else: - _convert_main(f, program_conversion_context, options, args, kwargs) - - if is_subroutine: - return program_conversion_context.return_variable - - return program_conversion_context.make_program() - - def _convert_main( f: Callable, - program_conversion_context: aq_program.ProgramConversionContext, options: converter.ConversionOptions, args: List[Any], kwargs: Dict[str, Any], + user_config: aq_program.UserConfig, ) -> None: """Convert the initial callable `f` into a full AutoQASM program `program`. Puts the contents of `f` at the global level of the program, rather than @@ -267,160 +146,25 @@ def _convert_main( Args: f (Callable): The function to be converted. - program_conversion_context (ProgramConversionContext): The program being converted. - options (converter.ConversionOptions): Converter options. - args (List[Any]): Arguments passed to the program when called. - kwargs (Dict[str, Any]): Keyword arguments passed to the program when called. - """ - # Process the program - aq_transpiler.converted_call(f, args, kwargs, options=options) - - # Modify program to add qubit declaration if necessary - _add_qubit_declaration(program_conversion_context) - - -def _convert_subroutine( - f: Callable, - program_conversion_context: aq_program.ProgramConversionContext, - options: converter.ConversionOptions, - args: List[Any], - kwargs: Dict[str, Any], -) -> None: - """Convert the initial callable `f` into a full AutoQASM program `program`. - The contents of `f` are converted into a subroutine in the program. - - Some program pre- and post-processing occurs here, such as adding a qubit - declaration and adding the subroutine invocation at the top level. - - Args: - f (Callable): The function to be converted. - program_conversion_context (ProgramConversionContext): The program being converted. options (converter.ConversionOptions): Converter options. args (List[Any]): Arguments passed to the program when called. kwargs (Dict[str, Any]): Keyword arguments passed to the program when called. + user_config (UserConfig): User-specified settings that influence program building. """ - oqpy_program = program_conversion_context.get_oqpy_program() - - if f not in program_conversion_context.subroutines_processing: - # Mark that we are starting to process this function to short-circuit recursion - program_conversion_context.subroutines_processing.add(f) - - # Convert the function via autograph into an oqpy subroutine - # NOTE: Process a clone of the function so that we don't modify the original object - oqpy_sub = oqpy.subroutine(_wrap_for_oqpy_subroutine(_clone_function(f), options)) - - # Process the program - subroutine_function_call = oqpy_sub(oqpy_program, *args, **kwargs) - - # Mark that we are finished processing this function - program_conversion_context.subroutines_processing.remove(f) - else: - # Convert the function via autograph into an oqpy subroutine - # NOTE: Recursive call; process a dummy version of the function instead - oqpy_sub = oqpy.subroutine(_wrap_for_oqpy_subroutine(_dummy_function(f), options)) - - # Process the program - subroutine_function_call = oqpy_sub(oqpy_program, *args, **kwargs) - - # Add the subroutine invocation to the program - ret_type = subroutine_function_call.subroutine_decl.return_type - return_instance = _make_return_instance_from_oqpy_return_type(ret_type) - return_variable = None - if isinstance(return_instance, list): - return_variable = aq_types.ArrayVar( - return_instance, - dimensions=[d.value for d in ret_type.dimensions], - ) - oqpy_program.set(return_variable, subroutine_function_call) - elif return_instance is not None: - return_variable = aq_types.wrap_value(return_instance) - oqpy_program.declare(return_variable) - oqpy_program.set(return_variable, subroutine_function_call) - else: - function_call = subroutine_function_call.to_ast(oqpy_program) - oqpy_program._add_statement(qasm_ast.ExpressionStatement(function_call)) - - # Store the return variable in the program conversion context - program_conversion_context.return_variable = return_variable - - # Add the subroutine definition to the root-level program if necessary - root_oqpy_program = program_conversion_context.oqpy_program_stack[0] - subroutine_name = subroutine_function_call.identifier.name - if ( - subroutine_name not in root_oqpy_program.subroutines - and subroutine_function_call.subroutine_decl is not None - ): - root_oqpy_program._add_subroutine(subroutine_name, subroutine_function_call.subroutine_decl) - - -def _convert_gate( - f: Callable, - options: converter.ConversionOptions, - args: List[Any], - kwargs: Dict[str, Any], -) -> Callable: - # We must be inside an active conversion context in order to invoke a gate - program_conversion_context = aq_program.get_program_conversion_context() - - # Wrap the function into an oqpy gate definition - wrapped_f, gate_args = _wrap_for_oqpy_gate(f, options) - gate_name = f.__name__ - - # Validate that the gate definition acts on at least one qubit - if not gate_args.qubits: - raise errors.ParameterTypeError( - f'Gate definition "{gate_name}" has no arguments of type aq.Qubit. ' - "Every gate definition must contain at least one qubit argument." - ) - - # Process the gate definition - with program_conversion_context.gate_definition(gate_name, gate_args): - # TODO - enforce that nothing gets added to the program inside here except gates - wrapped_f(gate_args._args) - - # Add the gate definition to the root-level program if necessary - root_oqpy_program = program_conversion_context.oqpy_program_stack[0] - if gate_name not in root_oqpy_program.gates: - gate_stmt = program_conversion_context.get_oqpy_program().gates[gate_name] - root_oqpy_program._add_gate(gate_name, gate_stmt) - - # Add the gate invocation to the program - if len(args) != len(gate_args): - raise errors.ParameterTypeError( - f'Incorrect number of arguments passed to gate "{gate_name}". ' - f"Expected {len(gate_args)}, got {len(args)}." + if aq_program.in_active_program_conversion_context(): + raise errors.AutoQasmTypeError( + f"Cannot call main function '{f.__name__}' from another main function. Did you mean " + "to use '@aq.subroutine'?" ) - qubit_args = [args[i] for i in gate_args.qubit_indices] - angle_args = [args[i] for i in gate_args.angle_indices] - aq_instructions.instructions._qubit_instruction(gate_name, qubit_args, *angle_args) - - -def _make_return_instance_from_oqpy_return_type(return_type: Any) -> Any: - if not return_type: - return None - - return_type = aq_types.conversions.var_type_from_ast_type(return_type) - if return_type == aq_types.ArrayVar: - return [] - return return_type() + with aq_program.build_program(user_config) as program_conversion_context: + # Process the program + aq_transpiler.converted_call(f, args, kwargs, options=options) -def _make_return_instance_from_f_annotation(f: Callable) -> Any: - # TODO: Recursive functions should work even if the user's type hint is wrong - annotations = f.__annotations__ - return_type = annotations["return"] if "return" in annotations else None + # Modify program to add qubit declaration if necessary + _add_qubit_declaration(program_conversion_context) - return_instance = None - if return_type and aq_types.is_qasm_type(return_type): - return_instance = return_type() - elif return_type: - if hasattr(return_type, "__origin__"): - # Types from python's typing module, such as `List`. origin gives us `list`` - return_instance = return_type.__origin__() - else: - return_instance = return_type() - - return return_instance + return program_conversion_context.make_program() def _add_qubit_declaration(program_conversion_context: aq_program.ProgramConversionContext) -> None: @@ -456,32 +200,88 @@ def _add_qubit_declaration(program_conversion_context: aq_program.ProgramConvers ) -def _clone_function(f_source: Callable) -> Callable: - if not hasattr(f_source, "__code__"): - raise ValueError(f"AutoQASM encountered a callable that it cannot process: {f_source}.") - f_clone = FunctionType( - copy.deepcopy(f_source.__code__), - copy.copy(f_source.__globals__), - copy.deepcopy(f_source.__name__), - copy.deepcopy(f_source.__defaults__), - copy.copy(f_source.__closure__), - ) - setattr(f_clone, "__signature__", copy.deepcopy(inspect.signature(f_source))) - setattr(f_clone, "__annotations__", copy.deepcopy(f_source.__annotations__)) - return f_clone +def _convert_subroutine( + f: Callable, + options: converter.ConversionOptions, + args: List[Any], + kwargs: Dict[str, Any], +) -> None: + """Convert the initial callable `f` into a full AutoQASM program `program`. + The contents of `f` are converted into a subroutine in the program. + Some program pre- and post-processing occurs here, such as adding a qubit + declaration and adding the subroutine invocation at the top level. -def _dummy_function(f_source: Callable) -> Callable: - return_instance = _make_return_instance_from_f_annotation(f_source) + Args: + f (Callable): The function to be converted. + options (converter.ConversionOptions): Converter options. + args (List[Any]): Arguments passed to the program when called. + kwargs (Dict[str, Any]): Keyword arguments passed to the program when called. + """ + if not aq_program.in_active_program_conversion_context(): + raise errors.AutoQasmTypeError( + "Subroutines shouldn't be called directly. Please define an entry point " + "function, decorate it with '@aq.main', and call your subroutine " + "from within that function." + ) - def f_dummy(*args, **kwargs) -> Any: - return return_instance # pragma: no cover + with aq_program.build_program() as program_conversion_context: + oqpy_program = program_conversion_context.get_oqpy_program() - f_dummy.__name__ = copy.deepcopy(f_source.__name__) - f_dummy.__defaults__ = copy.deepcopy(f_source.__defaults__) - setattr(f_dummy, "__signature__", copy.deepcopy(inspect.signature(f_source))) - setattr(f_dummy, "__annotations__", copy.deepcopy(f_source.__annotations__)) - return f_dummy + if f not in program_conversion_context.subroutines_processing: + # Mark that we are starting to process this function to short-circuit recursion + program_conversion_context.subroutines_processing.add(f) + + # Convert the function via autograph into an oqpy subroutine + # NOTE: Process a clone of the function so that we don't modify the original object + oqpy_sub = oqpy.subroutine(_wrap_for_oqpy_subroutine(_clone_function(f), options)) + + # Process the program + subroutine_function_call = oqpy_sub(oqpy_program, *args, **kwargs) + + # Mark that we are finished processing this function + program_conversion_context.subroutines_processing.remove(f) + else: + # Convert the function via autograph into an oqpy subroutine + # NOTE: Recursive call; process a dummy version of the function instead + oqpy_sub = oqpy.subroutine(_wrap_for_oqpy_subroutine(_dummy_function(f), options)) + + # Process the program + subroutine_function_call = oqpy_sub(oqpy_program, *args, **kwargs) + + # Add the subroutine invocation to the program + ret_type = subroutine_function_call.subroutine_decl.return_type + return_instance = _make_return_instance_from_oqpy_return_type(ret_type) + return_variable = None + if isinstance(return_instance, list): + return_variable = aq_types.ArrayVar( + return_instance, + dimensions=[d.value for d in ret_type.dimensions], + ) + oqpy_program.set(return_variable, subroutine_function_call) + elif return_instance is not None: + return_variable = aq_types.wrap_value(return_instance) + oqpy_program.declare(return_variable) + oqpy_program.set(return_variable, subroutine_function_call) + else: + function_call = subroutine_function_call.to_ast(oqpy_program) + oqpy_program._add_statement(qasm_ast.ExpressionStatement(function_call)) + + # Store the return variable in the program conversion context + program_conversion_context.return_variable = return_variable + + # Add the subroutine definition to the root-level program if necessary + root_oqpy_program = program_conversion_context.oqpy_program_stack[0] + subroutine_name = subroutine_function_call.identifier.name + if ( + subroutine_name not in root_oqpy_program.subroutines + and subroutine_function_call.subroutine_decl is not None + ): + root_oqpy_program._add_subroutine( + subroutine_name, subroutine_function_call.subroutine_decl + ) + + return program_conversion_context.return_variable def _wrap_for_oqpy_subroutine(f: Callable, options: converter.ConversionOptions) -> Callable: @@ -535,6 +335,104 @@ def _func(*args, **kwargs) -> Any: return _func +def _clone_function(f_source: Callable) -> Callable: + if not hasattr(f_source, "__code__"): + raise ValueError(f"AutoQASM encountered a callable that it cannot process: {f_source}.") + f_clone = FunctionType( + copy.deepcopy(f_source.__code__), + copy.copy(f_source.__globals__), + copy.deepcopy(f_source.__name__), + copy.deepcopy(f_source.__defaults__), + copy.copy(f_source.__closure__), + ) + setattr(f_clone, "__signature__", copy.deepcopy(inspect.signature(f_source))) + setattr(f_clone, "__annotations__", copy.deepcopy(f_source.__annotations__)) + return f_clone + + +def _dummy_function(f_source: Callable) -> Callable: + return_instance = _make_return_instance_from_f_annotation(f_source) + + def f_dummy(*args, **kwargs) -> Any: + return return_instance # pragma: no cover + + f_dummy.__name__ = copy.deepcopy(f_source.__name__) + f_dummy.__defaults__ = copy.deepcopy(f_source.__defaults__) + setattr(f_dummy, "__signature__", copy.deepcopy(inspect.signature(f_source))) + setattr(f_dummy, "__annotations__", copy.deepcopy(f_source.__annotations__)) + return f_dummy + + +def _make_return_instance_from_f_annotation(f: Callable) -> Any: + # TODO: Recursive functions should work even if the user's type hint is wrong + annotations = f.__annotations__ + return_type = annotations["return"] if "return" in annotations else None + + return_instance = None + if return_type and aq_types.is_qasm_type(return_type): + return_instance = return_type() + elif return_type: + if hasattr(return_type, "__origin__"): + # Types from python's typing module, such as `List`. origin gives us `list`` + return_instance = return_type.__origin__() + else: + return_instance = return_type() + + return return_instance + + +def _make_return_instance_from_oqpy_return_type(return_type: Any) -> Any: + if not return_type: + return None + + return_type = aq_types.conversions.var_type_from_ast_type(return_type) + if return_type == aq_types.ArrayVar: + return [] + return return_type() + + +def _convert_gate( + f: Callable, + options: converter.ConversionOptions, + args: List[Any], + kwargs: Dict[str, Any], +) -> Callable: + # We must be inside an active conversion context in order to invoke a gate + program_conversion_context = aq_program.get_program_conversion_context() + + # Wrap the function into an oqpy gate definition + wrapped_f, gate_args = _wrap_for_oqpy_gate(f, options) + gate_name = f.__name__ + + # Validate that the gate definition acts on at least one qubit + if not gate_args.qubits: + raise errors.ParameterTypeError( + f'Gate definition "{gate_name}" has no arguments of type aq.Qubit. ' + "Every gate definition must contain at least one qubit argument." + ) + + # Process the gate definition + with program_conversion_context.gate_definition(gate_name, gate_args): + # TODO - enforce that nothing gets added to the program inside here except gates + wrapped_f(gate_args._args) + + # Add the gate definition to the root-level program if necessary + root_oqpy_program = program_conversion_context.oqpy_program_stack[0] + if gate_name not in root_oqpy_program.gates: + gate_stmt = program_conversion_context.get_oqpy_program().gates[gate_name] + root_oqpy_program._add_gate(gate_name, gate_stmt) + + # Add the gate invocation to the program + if len(args) != len(gate_args): + raise errors.ParameterTypeError( + f'Incorrect number of arguments passed to gate "{gate_name}". ' + f"Expected {len(gate_args)}, got {len(args)}." + ) + qubit_args = [args[i] for i in gate_args.qubit_indices] + angle_args = [args[i] for i in gate_args.angle_indices] + aq_instructions.instructions._qubit_instruction(gate_name, qubit_args, *angle_args) + + def _wrap_for_oqpy_gate( f: Callable, options: converter.ConversionOptions, From 8bda8357b80acbba7ef67cccae8172a981eb472a Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Mon, 28 Aug 2023 11:03:45 -0400 Subject: [PATCH 0827/1165] fix: enforce that AutoQASM gates only contain unitary gate operations (#688) --- src/braket/experimental/autoqasm/api.py | 17 ++-- .../autoqasm/instructions/instructions.py | 13 +-- .../experimental/autoqasm/program/__init__.py | 2 + .../experimental/autoqasm/program/program.py | 83 ++++++++++++++++--- .../autoqasm/test_gate_decorator.py | 57 ++++++++++++- .../experimental/autoqasm/test_program.py | 6 +- 6 files changed, 153 insertions(+), 25 deletions(-) diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 2ddb2313..3b13f52e 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -177,7 +177,9 @@ def _add_qubit_declaration(program_conversion_context: aq_program.ProgramConvers Args: program_conversion_context (ProgramConversionContext): The program conversion context. """ - root_oqpy_program = program_conversion_context.oqpy_program_stack[0] + root_oqpy_program = program_conversion_context.get_oqpy_program( + scope=aq_program.ProgramScope.MAIN + ) # Declare the global qubit register if necessary user_specified_num_qubits = program_conversion_context.get_declared_qubits() @@ -271,7 +273,9 @@ def _convert_subroutine( program_conversion_context.return_variable = return_variable # Add the subroutine definition to the root-level program if necessary - root_oqpy_program = program_conversion_context.oqpy_program_stack[0] + root_oqpy_program = program_conversion_context.get_oqpy_program( + scope=aq_program.ProgramScope.MAIN + ) subroutine_name = subroutine_function_call.identifier.name if ( subroutine_name not in root_oqpy_program.subroutines @@ -413,13 +417,16 @@ def _convert_gate( # Process the gate definition with program_conversion_context.gate_definition(gate_name, gate_args): - # TODO - enforce that nothing gets added to the program inside here except gates wrapped_f(gate_args._args) # Add the gate definition to the root-level program if necessary - root_oqpy_program = program_conversion_context.oqpy_program_stack[0] + root_oqpy_program = program_conversion_context.get_oqpy_program( + scope=aq_program.ProgramScope.MAIN, mode=aq_program.ProgramMode.UNITARY + ) if gate_name not in root_oqpy_program.gates: - gate_stmt = program_conversion_context.get_oqpy_program().gates[gate_name] + gate_stmt = program_conversion_context.get_oqpy_program( + mode=aq_program.ProgramMode.UNITARY + ).gates[gate_name] root_oqpy_program._add_gate(gate_name, gate_stmt) # Add the gate invocation to the program diff --git a/src/braket/experimental/autoqasm/instructions/instructions.py b/src/braket/experimental/autoqasm/instructions/instructions.py index ee23959a..3da002c0 100644 --- a/src/braket/experimental/autoqasm/instructions/instructions.py +++ b/src/braket/experimental/autoqasm/instructions/instructions.py @@ -22,14 +22,17 @@ from .qubits import QubitIdentifierType, _qubit -def _qubit_instruction(name: str, qubits: List[QubitIdentifierType], *args: Any) -> None: +def _qubit_instruction( + name: str, qubits: List[QubitIdentifierType], *args: Any, is_unitary: bool = True +) -> None: # If this is an instruction inside a gate definition, ensure that it only operates on - # qubits which are passed as arguments to the gate definition. + # qubits and angles which are passed as arguments to the gate definition. program_conversion_context = aq_program.get_program_conversion_context() - program_conversion_context.validate_target_qubits(qubits) + program_conversion_context.validate_gate_targets(qubits, args) # Add the instruction to the program. - oqpy_program = program_conversion_context.get_oqpy_program() + program_mode = aq_program.ProgramMode.UNITARY if is_unitary else aq_program.ProgramMode.NONE + oqpy_program = program_conversion_context.get_oqpy_program(mode=program_mode) oqpy_program.gate([_qubit(q) for q in qubits], name, *args) @@ -39,4 +42,4 @@ def reset(target: QubitIdentifierType) -> None: Args: target (QubitIdentifierType): The target qubit. """ - _qubit_instruction("reset", [target]) + _qubit_instruction("reset", [target], is_unitary=False) diff --git a/src/braket/experimental/autoqasm/program/__init__.py b/src/braket/experimental/autoqasm/program/__init__.py index 241999de..f0d376ad 100644 --- a/src/braket/experimental/autoqasm/program/__init__.py +++ b/src/braket/experimental/autoqasm/program/__init__.py @@ -20,6 +20,8 @@ GateArgs, Program, ProgramConversionContext, + ProgramMode, + ProgramScope, UserConfig, build_program, get_program_conversion_context, diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index faa4faee..b17692f1 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -16,6 +16,7 @@ import contextlib import threading from dataclasses import dataclass +from enum import Enum from typing import Any, List, Optional, Union import oqpy.base @@ -44,6 +45,24 @@ class UserConfig: num_qubits: Optional[int] = None +class ProgramScope(Enum): + """Values used to specify the desired scope of a program to obtain.""" + + CURRENT = 0 + """References the current scope of the program conversion context.""" + MAIN = 1 + """References the top-level (root) scope of the program conversion context.""" + + +class ProgramMode(Enum): + """Values used to specify the desired mode of a program conversion context.""" + + NONE = 0 + """For general program conversion where all operations are allowed.""" + UNITARY = 1 + """For program conversion inside a context where only unitary operations are allowed.""" + + class Program: """The program that has been generated with AutoQASM. This object can be passed to the run() method of a Braket Device.""" @@ -124,10 +143,10 @@ class ProgramConversionContext: """The data structure used while converting a program. Intended for internal use.""" def __init__(self, user_config: Optional[UserConfig] = None): - self.oqpy_program_stack = [oqpy.Program()] self.subroutines_processing = set() # the set of subroutines queued for processing self.user_config = user_config or UserConfig() self.return_variable = None + self._oqpy_program_stack = [oqpy.Program()] self._gate_definitions_processing = [] self._qubits_seen = set() self._var_idx = 0 @@ -203,14 +222,15 @@ def is_var_name_used(self, var_name: str) -> bool: or var_name in oqpy_program.undeclared_vars.keys() ) - def validate_target_qubits(self, qubits: List[Any]) -> None: - """Validate that the specified qubits are valid target qubits at this point in the program. + def validate_gate_targets(self, qubits: List[Any], angles: List[Any]) -> None: + """Validate that the specified gate targets are valid at this point in the program. Args: qubits (List[Any]): The list of target qubits to validate. + angles (List[Any]): The list of target angles to validate. Raises: - errors.InvalidGateDefinition: Target qubits are invalid in the current gate definition. + errors.InvalidGateDefinition: Targets are invalid in the current gate definition. """ if self._gate_definitions_processing: gate_name = self._gate_definitions_processing[-1]["name"] @@ -223,14 +243,50 @@ def validate_target_qubits(self, qubits: List[Any]) -> None: "an argument to the gate. Gates may only operate on qubits which are " "passed as arguments." ) + gate_angle_args = self._gate_definitions_processing[-1]["gate_args"].angles + gate_angle_arg_names = [arg.name for arg in gate_angle_args] + for angle in angles: + if isinstance(angle, oqpy.base.Var) and angle.name not in gate_angle_arg_names: + raise errors.InvalidGateDefinition( + f'Gate definition "{gate_name}" uses angle "{angle.name}" which is not ' + "an argument to the gate. Gates may only use constant angles or angles " + "passed as arguments." + ) + + def get_oqpy_program( + self, scope: ProgramScope = ProgramScope.CURRENT, mode: ProgramMode = ProgramMode.NONE + ) -> oqpy.Program: + """Gets the oqpy.Program object associated with this program conversion context. + + Args: + scope (ProgramScope): The scope of the oqpy.Program to retrieve. + Defaults to ProgramScope.CURRENT. + mode (ProgramMode): The mode for which the oqpy.Program is being retrieved. + Defaults to ProgramMode.NONE. - def get_oqpy_program(self) -> oqpy.Program: - """Gets the oqpy program from the top of the stack. + Raises: + errors.InvalidGateDefinition: If this function is called from within a gate + definition where only unitary gate operations are allowed, and the + `mode` parameter is not specified as `ProgramMode.UNITARY`. Returns: - oqpy.Program: The current oqpy program. + oqpy.Program: The requested oqpy program. """ - return self.oqpy_program_stack[-1] + if self._gate_definitions_processing and mode != ProgramMode.UNITARY: + gate_name = self._gate_definitions_processing[-1]["name"] + raise errors.InvalidGateDefinition( + f'Gate definition "{gate_name}" contains invalid operations. ' + "A gate definition must only call unitary gate operations." + ) + + if scope == ProgramScope.CURRENT: + requested_index = -1 + elif scope == ProgramScope.MAIN: + requested_index = 0 + else: + raise NotImplementedError("Unexpected ProgramScope value") + + return self._oqpy_program_stack[requested_index] @contextlib.contextmanager def push_oqpy_program(self, oqpy_program: oqpy.Program) -> None: @@ -240,10 +296,10 @@ def push_oqpy_program(self, oqpy_program: oqpy.Program) -> None: oqpy_program (Program): The oqpy program to push onto the stack. """ try: - self.oqpy_program_stack.append(oqpy_program) + self._oqpy_program_stack.append(oqpy_program) yield finally: - self.oqpy_program_stack.pop() + self._oqpy_program_stack.pop() @contextlib.contextmanager def gate_definition(self, gate_name: str, gate_args: GateArgs) -> None: @@ -255,7 +311,12 @@ def gate_definition(self, gate_name: str, gate_args: GateArgs) -> None: """ try: self._gate_definitions_processing.append({"name": gate_name, "gate_args": gate_args}) - with oqpy.gate(self.get_oqpy_program(), gate_args.qubits, gate_name, gate_args.angles): + with oqpy.gate( + self.get_oqpy_program(mode=ProgramMode.UNITARY), + gate_args.qubits, + gate_name, + gate_args.angles, + ): yield finally: self._gate_definitions_processing.pop() diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py index 288052e9..870079a5 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py @@ -20,7 +20,7 @@ import braket.experimental.autoqasm as aq from braket.experimental.autoqasm import errors -from braket.experimental.autoqasm.instructions import h, measure, rx, rz, x +from braket.experimental.autoqasm.instructions import h, measure, reset, rx, rz, x @aq.gate @@ -241,6 +241,61 @@ def my_program(): my_program() +def test_invalid_angle_used() -> None: + with aq.build_program(): + beta = aq.FloatVar() + + @aq.gate + def my_gate(q: aq.Qubit, theta: float): + rx(q, theta) + rx(q, beta) # invalid + + @aq.main + def my_program(): + my_gate(0, np.pi / 2) + + with pytest.raises( + errors.InvalidGateDefinition, + match='Gate definition "my_gate" uses angle (.*) which is not an argument to the gate.', + ): + my_program() + + +def test_invalid_instruction() -> None: + @aq.gate + def my_gate(q: aq.Qubit): + h(q) + reset(q) # invalid + + @aq.main + def my_program(): + my_gate(0) + + with pytest.raises( + errors.InvalidGateDefinition, + match='Gate definition "my_gate" contains invalid operations.', + ): + my_program() + + +def test_invalid_control_flow() -> None: + @aq.gate + def my_gate(q: aq.Qubit): + h(q) + if measure(q): + x(q) + + @aq.main + def my_program(): + my_gate(0) + + with pytest.raises( + errors.InvalidGateDefinition, + match='Gate definition "my_gate" contains invalid operations.', + ): + my_program() + + def test_nested_gates() -> None: @aq.gate def t(q: aq.Qubit): diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/braket/experimental/autoqasm/test_program.py index 4540f9aa..274e70d6 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_program.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_program.py @@ -28,15 +28,15 @@ def test_program_conversion_context() -> None: """Tests the ProgramConversionContext class.""" prog = aq.program.ProgramConversionContext() initial_oqpy_program = prog.get_oqpy_program() - assert len(prog.oqpy_program_stack) == 1 + assert len(prog._oqpy_program_stack) == 1 new_oqpy_program = oqpy.Program() with prog.push_oqpy_program(new_oqpy_program): - assert len(prog.oqpy_program_stack) == 2 + assert len(prog._oqpy_program_stack) == 2 assert prog.get_oqpy_program() == new_oqpy_program assert prog.get_oqpy_program() == initial_oqpy_program - assert len(prog.oqpy_program_stack) == 1 + assert len(prog._oqpy_program_stack) == 1 def test_build_program() -> None: From 832803d2c8ab004fc0c9633268c03a6a5ec8fcf1 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 28 Aug 2023 16:14:03 +0000 Subject: [PATCH 0828/1165] prepare release v1.54.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 b820f2ce..3b08e412 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.54.2 (2023-08-28) + +### Bug Fixes and Other Changes + + * readthedocs integration + * build(deps): bump pypa/gh-action-pypi-publish from 1.8.8 to 1.8.10 + ## v1.54.1 (2023-08-22) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 89402ce3..33c9541b 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.54.2.dev0" +__version__ = "1.54.2" From 6f9192190ec3e11a017dcdacd18263029fa3aa1a Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 28 Aug 2023 16:14:03 +0000 Subject: [PATCH 0829/1165] update development version to v1.54.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 33c9541b..b9c61a5e 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.54.2" +__version__ = "1.54.3.dev0" From 35daf164ae86c5e672ee291f9697ccd81a340495 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 28 Aug 2023 13:58:49 -0700 Subject: [PATCH 0830/1165] test: speed up pulse time trace and add test timing outputs (#689) Co-authored-by: Abe Coull --- setup.cfg | 2 +- test/unit_tests/braket/pulse/test_pulse_sequence.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 432fec80..03f3d1ac 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 + --verbose -n auto --durations=0 --durations-min=1 testpaths = test/unit_tests [isort] diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py index 14add748..57ba20fb 100644 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -27,7 +27,7 @@ @pytest.fixture def port(): - return Port(port_id="device_port_x0", dt=1e-9, properties={}) + return Port(port_id="device_port_x0", dt=1e-3, properties={}) @pytest.fixture From ad00dd875986363a593f705c3fce467caeece61d Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 28 Aug 2023 14:45:30 -0700 Subject: [PATCH 0831/1165] infra: update dependendabot to use the infra prefix (#690) --- .github/dependabot.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index bf7c9851..ed79a0d6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,4 +8,6 @@ updates: schedule: # Check for updates to GitHub Actions every week interval: "weekly" + commit-message: + prefix: infra From 41f6fbe0391bd45881aadb5bb5ae09cb02fab77f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Aug 2023 15:57:31 -0600 Subject: [PATCH 0832/1165] build(deps): bump actions/setup-python from 4.6.1 to 4.7.0 (#621) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.6.1 to 4.7.0. --- .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 6d56ffe5..d1985bb6 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Set up Python - uses: actions/setup-python@bd6b4b6205c4dbad673328db7b31b7fab9e241c0 # v4.6.1 + uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0 with: python-version: '3.x' - name: Install dependencies diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index 13b05d1c..7d42acbe 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@bd6b4b6205c4dbad673328db7b31b7fab9e241c0 # v4.6.1 + uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.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 ff7d3883..4a9d15dc 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Set up Python - uses: actions/setup-python@bd6b4b6205c4dbad673328db7b31b7fab9e241c0 # v4.6.1 + uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0 with: python-version: '3.x' - name: Install wheel diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 996fcbc7..db9fcc27 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@bd6b4b6205c4dbad673328db7b31b7fab9e241c0 # v4.6.1 + uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.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 50747091..f0e6d5a9 100644 --- a/.github/workflows/twine-check.yml +++ b/.github/workflows/twine-check.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Set up Python - uses: actions/setup-python@bd6b4b6205c4dbad673328db7b31b7fab9e241c0 # v4.6.1 + uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0 with: python-version: '3.x' - name: Install wheel From 45b38848f596522250ecc53b521e07c0c9739924 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Aug 2023 16:27:48 -0600 Subject: [PATCH 0833/1165] infra: bump actions/checkout from 3.5.3 to 3.6.0 (#687) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.3 to 3.6.0. --- .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 d1985bb6..c088c064 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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - name: Set up Python uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0 with: diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index 7d42acbe..c4e4a005 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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0 with: diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 4a9d15dc..0fd1b03a 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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - name: Set up Python uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0 with: diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index db9fcc27..f773f151 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -24,7 +24,7 @@ jobs: python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0 with: diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index f0e6d5a9..46be37a2 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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - name: Set up Python uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0 with: From 0dcbd70d49653b8e2e40358a25ab919a36f0e073 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 28 Aug 2023 17:06:26 -0600 Subject: [PATCH 0834/1165] Move inline `_flatten` to top of `qubit_set.py` (#691) --- src/braket/circuits/qubit_set.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/braket/circuits/qubit_set.py b/src/braket/circuits/qubit_set.py index 155ca548..d4571e91 100644 --- a/src/braket/circuits/qubit_set.py +++ b/src/braket/circuits/qubit_set.py @@ -22,6 +22,14 @@ QubitSetInput = Union[QubitInput, Iterable[QubitInput]] +def _flatten(other: Any) -> Any: + if isinstance(other, Iterable) and not isinstance(other, str): + for item in other: + yield from _flatten(item) + else: + yield other + + class QubitSet(IndexedSet): """ An ordered, unique set of quantum bits. @@ -54,13 +62,6 @@ def __init__(self, qubits: QubitSetInput = None): Qubit(3) """ - def _flatten(other: Any) -> Any: - if isinstance(other, Iterable) and not isinstance(other, str): - for item in other: - yield from _flatten(item) - else: - yield other - _qubits = [Qubit.new(qubit) for qubit in _flatten(qubits)] if qubits is not None else None super().__init__(_qubits) From 0ff3675b8e68ef72926b7b3a2677033b3b695357 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 30 Aug 2023 23:01:28 +0000 Subject: [PATCH 0835/1165] prepare release v1.54.3 --- 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 3b08e412..5b7e6003 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.54.3 (2023-08-30) + +### Bug Fixes and Other Changes + + * Move inline `_flatten` to top of `qubit_set.py` + * build(deps): bump actions/setup-python from 4.6.1 to 4.7.0 + ## v1.54.2 (2023-08-28) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index b9c61a5e..ae0e4d1f 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.54.3.dev0" +__version__ = "1.54.3" From 53bc0ea36d7d65754f35081024d622665ec2bbd1 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 30 Aug 2023 23:01:28 +0000 Subject: [PATCH 0836/1165] update development version to v1.54.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 ae0e4d1f..78e34acd 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.54.3" +__version__ = "1.54.4.dev0" From bc53d489c15375491136ab8a771900de0468576a Mon Sep 17 00:00:00 2001 From: ashlhans <65787294+ashlhans@users.noreply.github.com> Date: Thu, 31 Aug 2023 16:28:13 -0700 Subject: [PATCH 0837/1165] doc: standardize task and job naming to quantum task and hybrid job (#694) --- README.md | 18 +-- doc/examples-braket-features.rst | 4 +- doc/examples-hybrid-jobs.rst | 2 +- src/braket/aws/aws_device.py | 19 +-- src/braket/aws/aws_quantum_job.py | 114 +++++++++--------- src/braket/aws/aws_quantum_task.py | 43 ++++--- src/braket/aws/aws_quantum_task_batch.py | 52 ++++---- src/braket/aws/aws_session.py | 18 +-- src/braket/circuits/instruction.py | 3 +- src/braket/devices/device.py | 14 +-- src/braket/devices/local_simulator.py | 14 +-- src/braket/jobs/config.py | 8 +- src/braket/jobs/data_persistence.py | 14 +-- src/braket/jobs/local/local_job.py | 63 +++++----- src/braket/jobs/local/local_job_container.py | 11 +- .../jobs/local/local_job_container_setup.py | 26 ++-- src/braket/jobs/metrics.py | 2 +- .../cwl_insights_metrics_fetcher.py | 20 +-- .../jobs/metrics_data/cwl_metrics_fetcher.py | 19 +-- src/braket/jobs/quantum_job.py | 53 ++++---- src/braket/jobs/quantum_job_creation.py | 54 +++++---- .../tasks/annealing_quantum_task_result.py | 4 +- .../tasks/gate_model_quantum_task_result.py | 6 +- src/braket/tasks/local_quantum_task.py | 2 +- src/braket/tasks/quantum_task.py | 6 +- src/braket/tracking/tracker.py | 10 +- .../test_create_local_quantum_job.py | 6 +- test/integ_tests/test_create_quantum_job.py | 10 +- 28 files changed, 318 insertions(+), 297 deletions(-) diff --git a/README.md b/README.md index eea4e659..1f7fcb46 100644 --- a/README.md +++ b/README.md @@ -84,16 +84,16 @@ task = device.run(bell, shots=100) print(task.result().measurement_counts) ``` -The code sample imports the Amazon Braket framework, then defines the device to use (the SV1 AWS simulator). It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the job. This example can be found in `../examples/bell.py`. +The code sample imports the Amazon Braket framework, then defines the device to use (the SV1 AWS simulator). It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the hybrid job. This example can be found in `../examples/bell.py`. -### Running multiple tasks at once +### Running multiple quantum tasks at once -Many quantum algorithms need to run multiple independent circuits, and submitting the circuits in parallel can be faster than submitting them one at a time. In particular, parallel task processing provides a significant speed up when using simulator devices. The following example shows how to run a batch of tasks on SV1: +Many quantum algorithms need to run multiple independent circuits, and submitting the circuits in parallel can be faster than submitting them one at a time. In particular, parallel quantum task processing provides a significant speed up when using simulator devices. The following example shows how to run a batch of quantum tasks on SV1: ```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 task in the batch +print(batch.results()[0].measurement_counts) # The result of the first quantum task in the batch ``` ### Running a hybrid job @@ -112,19 +112,19 @@ print(job.result()) where `run_job` is a function in the file `job.py`. -The code sample imports the Amazon Braket framework, then creates a hybrid job with the entry point being the `run_job` function. The hybrid job creates quantum tasks against the SV1 AWS Simulator. The job runs synchronously, and prints logs until it completes. The complete example can be found in `../examples/job.py`. +The code sample imports the Amazon Braket framework, then creates a hybrid job with the entry point being the `run_job` function. The hybrid job creates quantum tasks against the SV1 AWS Simulator. The hybrid job runs synchronously, and prints logs until it completes. The complete example can be found in `../examples/job.py`. ### Available Simulators Amazon Braket provides access to two types of simulators: fully managed simulators, available through the Amazon Braket service, and the local simulators that are part of the Amazon Braket SDK. - Fully managed simulators offer high-performance circuit simulations. These simulators can handle circuits larger than circuits that run on quantum hardware. For example, the SV1 state vector simulator shown in the previous examples requires approximately 1 or 2 hours to complete a 34-qubit, dense, and square circuit (circuit depth = 34), depending on the type of gates used and other factors. -- The Amazon Braket Python SDK includes an implementation of quantum simulators that can run circuits on your local, classic hardware. For example the braket_sv local simulator is well suited for rapid prototyping on small circuits up to 25 qubits, depending on the hardware specifications of your Braket notebook instance or your local environment. An example of how to execute the task locally is included in the repository `../examples/local_bell.py`. +- The Amazon Braket Python SDK includes an implementation of quantum simulators that can run circuits on your local, classic hardware. For example the braket_sv local simulator is well suited for rapid prototyping on small circuits up to 25 qubits, depending on the hardware specifications of your Braket notebook instance or your local environment. An example of how to execute the quantum task locally is included in the repository `../examples/local_bell.py`. For a list of available simulators and their features, consult the [Amazon Braket Developer Guide](https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html). ### Debugging logs -Tasks sent to QPUs don't always run right away. To view task status, you can enable debugging logs. An example of how to enable these logs is included in repo: `../examples/debug_bell.py`. This example enables task logging so that status updates are continuously printed to the terminal after a quantum task is executed. The logs can also be configured to save to a file or output to another stream. You can use the debugging example to get information on the tasks you submit, such as the current status, so that you know when your task completes. +Quantum tasks sent to QPUs don't always run right away. To view quantum task status, you can enable debugging logs. An example of how to enable these logs is included in repo: `../examples/debug_bell.py`. This example enables quantum task logging so that status updates are continuously printed to the terminal after a quantum task is executed. The logs can also be configured to save to a file or output to another stream. You can use the debugging example to get information on the quantum tasks you submit, such as the current status, so that you know when your quantum task completes. ### Running a Quantum Algorithm on a Quantum Computer With Amazon Braket, you can run your quantum circuit on a physical quantum computer. @@ -152,7 +152,7 @@ print(task.result().measurement_counts) To select a quantum hardware device, specify its ARN as the value of the `device_arn` argument. A list of available quantum devices and their features can be found in the [Amazon Braket Developer Guide](https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html). -**Important** Tasks may not run immediately on the QPU. The QPUs only execute tasks during execution windows. To find their execution windows, please refer to the [AWS console](https://console.aws.amazon.com/braket/home) in the "Devices" tab. +**Important** Quantum tasks may not run immediately on the QPU. The QPUs only execute quantum tasks during execution windows. To find their execution windows, please refer to the [AWS console](https://console.aws.amazon.com/braket/home) in the "Devices" tab. ## Sample Notebooks Sample Jupyter notebooks can be found in the [amazon-braket-examples](https://github.com/aws/amazon-braket-examples/) repo. @@ -214,7 +214,7 @@ After you create a profile, use the following command to set the `AWS_PROFILE` s ```bash export AWS_PROFILE=YOUR_PROFILE_NAME ``` -To run the integration tests for local jobs, you need to have Docker installed and running. To install Docker follow these instructions: [Install Docker](https://docs.docker.com/get-docker/) +To run the integration tests for local hybrid jobs, you need to have Docker installed and running. To install Docker follow these instructions: [Install Docker](https://docs.docker.com/get-docker/) Run the tests: diff --git a/doc/examples-braket-features.rst b/doc/examples-braket-features.rst index cbb81fb6..1bfc9c0d 100644 --- a/doc/examples-braket-features.rst +++ b/doc/examples-braket-features.rst @@ -8,12 +8,12 @@ Learn more about the indivudal features of Amazon Braket. :maxdepth: 2 ******************************************************************************************************************************************************************************************************************************* -`Getting notifications when a task completes `_ +`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 task completions on your phone. +and Amazon Eventbridge to receive text notification about quantum task completions on your phone. ************************************************************************************************************************************************************* `Allocating Qubits on QPU Devices `_ diff --git a/doc/examples-hybrid-jobs.rst b/doc/examples-hybrid-jobs.rst index 88d4e8f6..76b2026e 100644 --- a/doc/examples-hybrid-jobs.rst +++ b/doc/examples-hybrid-jobs.rst @@ -20,7 +20,7 @@ This tutorial shows how to run your first Amazon Braket Hybrid Job. This notebook demonstrates a typical quantum machine learning workflow, including uploading data, monitoring training, and tuning hyperparameters. ******************************************************************************************************************************************************************************************** -`Using Pennylane with Braket Jobs `_ +`Using Pennylane with Braket Hybrid Jobs `_ ******************************************************************************************************************************************************************************************** In this tutorial, we use PennyLane within Amazon Braket Hybrid Jobs to run the Quantum Approximate Optimization Algorithm (QAOA) on a Max-Cut problem. diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index a34facac..fbd49307 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -123,15 +123,16 @@ def run( **aws_quantum_task_kwargs, ) -> AwsQuantumTask: """ - Run a quantum task specification on this device. A 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 - Specification of task (circuit or annealing problem or program) to run on device. + Specification of quantum task (circuit, OpenQASM program or AHS program) + to run on device. s3_destination_folder (Optional[S3DestinationFolder]): The S3 location to - save the task's results to. Default is `/tasks` if evoked outside a - Braket Job, `/jobs//tasks` if evoked inside a Braket Job. + 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. shots (Optional[int]): The number of times to run the circuit or annealing problem. Default is 1000 for QPUs and 0 for simulators. poll_timeout_seconds (float): The polling timeout for `AwsQuantumTask.result()`, @@ -233,20 +234,20 @@ def run_batch( *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTaskBatch: - """Executes a batch of tasks in parallel + """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. s3_destination_folder (Optional[S3DestinationFolder]): The S3 location to - save the tasks' results to. Default is `/tasks` if evoked outside a + save the quantum tasks' results to. Default is `/tasks` if evoked outside a Braket Job, `/jobs//tasks` if evoked inside a Braket Job. shots (Optional[int]): The number of times to run the circuit or annealing problem. Default is 1000 for QPUs and 0 for simulators. - max_parallel (Optional[int]): The maximum number of tasks to run on AWS in parallel. + max_parallel (Optional[int]): The maximum number of quantum tasks to run on AWS in parallel. Batch creation will fail if this value is greater than the maximum allowed - concurrent tasks on the device. Default: 10 + concurrent quantum tasks on the device. Default: 10 max_connections (int): The maximum number of connections in the Boto3 connection pool. Also the maximum number of thread pool workers for the batch. Default: 100 poll_timeout_seconds (float): The polling timeout for `AwsQuantumTask.result()`, @@ -264,7 +265,7 @@ def run_batch( Default: None. Returns: - AwsQuantumTaskBatch: A batch containing all of the tasks run + AwsQuantumTaskBatch: A batch containing all of the quantum tasks run See Also: `braket.aws.aws_quantum_task_batch.AwsQuantumTaskBatch` diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index a74307e6..288f1725 100644 --- a/src/braket/aws/aws_quantum_job.py +++ b/src/braket/aws/aws_quantum_job.py @@ -82,32 +82,32 @@ def create( tags: Dict[str, str] = None, logger: Logger = getLogger(__name__), ) -> AwsQuantumJob: - """Creates a job by invoking the Braket CreateJob API. + """Creates a hybrid job by invoking the Braket CreateJob API. Args: device (str): ARN for the AWS device which is primarily accessed for the execution - of this job. Alternatively, a string of the format "local:/" - for using a local simulator for the job. This string will be available as the - environment variable `AMZN_BRAKET_DEVICE_ARN` inside the job container when - using a Braket container. + of this hybrid job. Alternatively, a string of the format + "local:/" for using a local simulator for the hybrid job. + This string will be available as the environment variable `AMZN_BRAKET_DEVICE_ARN` + inside the hybrid job container when using a Braket container. source_module (str): Path (absolute, relative or an S3 URI) to a python module to be tarred and uploaded. If `source_module` is an S3 URI, it must point to a tar.gz file. Otherwise, source_module may be a file or directory. - entry_point (str): A str that specifies the entry point of the job, relative to + entry_point (str): A str that specifies the entry point of the hybrid job, relative to the source module. The entry point must be in the format `importable.module` or `importable.module:callable`. For example, `source_module.submodule:start_here` indicates the `start_here` function contained in `source_module.submodule`. If source_module is an S3 URI, entry point must be given. Default: source_module's name - image_uri (str): A str that specifies the ECR image to use for executing the job. + image_uri (str): A str that specifies the ECR image to use for executing the hybrid job. `image_uris.retrieve_image()` function may be used for retrieving the ECR image URIs for the containers supported by Braket. Default = ``. - job_name (str): A str that specifies the name with which the job is created. - Allowed pattern for job name: `^[a-zA-Z0-9](-*[a-zA-Z0-9]){0,50}$` + job_name (str): A str that specifies the name with which the hybrid job is created. + Allowed pattern for hybrid job name: `^[a-zA-Z0-9](-*[a-zA-Z0-9]){0,50}$` Default: f'{image_uri_type}-{timestamp}'. code_location (str): The S3 prefix URI where custom code will be uploaded. @@ -116,11 +116,12 @@ def create( role_arn (str): A str providing the IAM role ARN used to execute the script. Default: IAM role returned by AwsSession's `get_default_jobs_role()`. - wait_until_complete (bool): `True` if we should wait until the job completes. - This would tail the job logs as it waits. Otherwise `False`. Default: `False`. + wait_until_complete (bool): `True` if we should wait until the hybrid job completes. + This would tail the hybrid job logs as it waits. Otherwise `False`. + Default: `False`. - hyperparameters (Dict[str, Any]): Hyperparameters accessible to the job. - The hyperparameters are made accessible as a Dict[str, str] to the job. + hyperparameters (Dict[str, Any]): Hyperparameters accessible to the hybrid job. + The hyperparameters are made accessible as a Dict[str, str] to the hybrid job. For convenience, this accepts other types for keys and values, but `str()` is called to convert them before being passed on. Default: None. @@ -133,26 +134,28 @@ def create( Default: {}. instance_config (InstanceConfig): Configuration of the instances to be used - to execute the job. Default: InstanceConfig(instanceType='ml.m5.large', + to execute the hybrid job. Default: InstanceConfig(instanceType='ml.m5.large', instanceCount=1, volumeSizeInGB=30). - distribution (str): A str that specifies how the job should be distributed. If set to - "data_parallel", the hyperparameters for the job will be set to use data parallelism - features for PyTorch or TensorFlow. Default: None. + distribution (str): A str that specifies how the hybrid job should be distributed. + If set to "data_parallel", the hyperparameters for the hybrid job will be set + to use data parallelism features for PyTorch or TensorFlow. Default: None. stopping_condition (StoppingCondition): The maximum length of time, in seconds, - and the maximum number of tasks that a job can run before being forcefully stopped. + and the maximum number of quantum tasks that a hybrid job can run before being + forcefully stopped. Default: StoppingCondition(maxRuntimeInSeconds=5 * 24 * 60 * 60). - output_data_config (OutputDataConfig): Specifies the location for the output of the job. + output_data_config (OutputDataConfig): Specifies the location for the output of the + hybrid job. Default: OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data', kmsKeyId=None). - copy_checkpoints_from_job (str): A str that specifies the job ARN whose checkpoint you - want to use in the current job. Specifying this value will copy over the checkpoint - data from `use_checkpoints_from_job`'s checkpoint_config s3Uri to the current job's - checkpoint_config s3Uri, making it available at checkpoint_config.localPath during - the job execution. Default: None + copy_checkpoints_from_job (str): A str that specifies the hybrid job ARN whose + checkpoint you want to use in the current hybrid job. Specifying this value will + copy over the checkpoint data from `use_checkpoints_from_job`'s checkpoint_config + s3Uri to the current hybrid job's checkpoint_config s3Uri, making it available at + checkpoint_config.localPath during the hybrid job execution. Default: None checkpoint_config (CheckpointConfig): Configuration that specifies the location where checkpoint data is stored. @@ -162,14 +165,15 @@ def create( aws_session (AwsSession): AwsSession for connecting to AWS Services. Default: AwsSession() - tags (Dict[str, str]): Dict specifying the key-value pairs for tagging this job. + tags (Dict[str, str]): Dict specifying the key-value pairs for tagging this hybrid job. Default: {}. - logger (Logger): Logger object with which to write logs, such as task statuses - while waiting for task to be in a terminal state. Default is `getLogger(__name__)` + 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__)` Returns: - AwsQuantumJob: Job tracking the execution on Amazon Braket. + AwsQuantumJob: Hybrid job tracking the execution on Amazon Braket. Raises: ValueError: Raises ValueError if the parameters are not valid. @@ -208,10 +212,10 @@ def create( def __init__(self, arn: str, aws_session: AwsSession = None): """ Args: - arn (str): The ARN of the job. + arn (str): The ARN of the hybrid job. aws_session (AwsSession): The `AwsSession` for connecting to AWS services. Default is `None`, in which case an `AwsSession` object will be created with the - region of the job. + region of the hybrid job. """ self._arn: str = arn if aws_session: @@ -234,13 +238,14 @@ def _is_valid_aws_session_region_for_job_arn(aws_session: AwsSession, job_arn: s @staticmethod def _default_session_for_job_arn(job_arn: str) -> AwsSession: - """Get an AwsSession for the Job ARN. The AWS session should be in the region of the job. + """Get an AwsSession for the Hybrid Job ARN. The AWS session should be in the region of the + hybrid job. Args: - job_arn (str): The ARN for the quantum job. + job_arn (str): The ARN for the quantum hybrid job. Returns: - AwsSession: `AwsSession` object with default `boto_session` in job's region. + AwsSession: `AwsSession` object with default `boto_session` in hybrid job's region. """ job_region = job_arn.split(":")[3] boto_session = boto3.Session(region_name=job_region) @@ -248,7 +253,7 @@ def _default_session_for_job_arn(job_arn: str) -> AwsSession: @property def arn(self) -> str: - """str: The ARN (Amazon Resource Name) of the quantum job.""" + """str: The ARN (Amazon Resource Name) of the quantum hybrid job.""" return self._arn @property @@ -257,7 +262,7 @@ def name(self) -> str: return self._arn.partition("job/")[-1] def state(self, use_cached_value: bool = False) -> str: - """The state of the quantum job. + """The state of the quantum hybrid job. Args: use_cached_value (bool): If `True`, uses the value most recently retrieved @@ -274,28 +279,29 @@ def state(self, use_cached_value: bool = False) -> str: return self.metadata(use_cached_value).get("status") def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: - """Display logs for a given job, optionally tailing them until job is complete. + """Display logs for a given hybrid job, optionally tailing them until hybrid job is + complete. If the output is a tty or a Jupyter cell, it will be color-coded based on which instance the log entry is from. Args: - wait (bool): `True` to keep looking for new log entries until the job completes; + wait (bool): `True` to keep looking for new log entries until the hybrid job completes; otherwise `False`. Default: `False`. poll_interval_seconds (int): The interval of time, in seconds, between polling for - new log entries and job completion (default: 5). + new log entries and hybrid job completion (default: 5). Raises: - exceptions.UnexpectedStatusException: If waiting and the training job fails. + exceptions.UnexpectedStatusException: If waiting and the training hybrid job fails. """ - # The loop below implements a state machine that alternates between checking the job status - # and reading whatever is available in the logs at this point. Note, that if we were - # called with wait == False, we never check the job status. + # The loop below implements a state machine that alternates between checking the hybrid job + # status and reading whatever is available in the logs at this point. Note, that if we were + # called with wait == False, we never check the hybrid job status. # - # If wait == TRUE and job is not completed, the initial state is TAILING - # If wait == FALSE, the initial state is COMPLETE (doesn't matter if the job really is - # complete). + # If wait == TRUE and hybrid job is not completed, the initial state is TAILING + # If wait == FALSE, the initial state is COMPLETE (doesn't matter if the hybrid job really + # is complete). # # The state table: # @@ -348,7 +354,7 @@ def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: log_state = AwsQuantumJob.LogState.JOB_COMPLETE def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: - """Gets the job metadata defined in Amazon Braket. + """Gets the hybrid job metadata defined in Amazon Braket. Args: use_cached_value (bool): If `True`, uses the value most recently retrieved @@ -356,7 +362,7 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: `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 job metadata defined in Amazon Braket. + Dict[str, Any]: Dict that specifies the hybrid job metadata defined in Amazon Braket. """ if not use_cached_value or not self._metadata: self._metadata = self._aws_session.get_job(self._arn) @@ -413,7 +419,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 job result persisted using save_job_result() function. + """Retrieves the hybrid job result persisted using save_job_result() function. Args: poll_timeout_seconds (float): The polling timeout, in seconds, for `result()`. @@ -425,8 +431,8 @@ def result( Dict[str, Any]: Dict specifying the job results. Raises: - RuntimeError: if job is in a FAILED or CANCELLED state. - TimeoutError: if job execution exceeds the polling timeout period. + 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: @@ -459,13 +465,13 @@ def download_result( poll_timeout_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_INTERVAL, ) -> None: - """Downloads the results from the job output S3 bucket and extracts the tar.gz + """Downloads the results from the hybrid job output S3 bucket and extracts the tar.gz bundle to the location specified by `extract_to`. If no location is specified, the results are extracted to the current directory. Args: extract_to (str): The directory to which the results are extracted. The results - are extracted to a folder titled with the job name within this directory. + are extracted to a folder titled with the hybrid job name within this directory. Default= `Current working directory`. poll_timeout_seconds (float): The polling timeout, in seconds, for `download_result()`. Default: 10 days. @@ -473,8 +479,8 @@ def download_result( `download_result()`.Default: 5 seconds. Raises: - RuntimeError: if job is in a FAILED or CANCELLED state. - TimeoutError: if job execution exceeds the polling timeout period. + 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() diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 76fa3d56..a4538cdd 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -73,8 +73,8 @@ class AwsQuantumTask(QuantumTask): - """Amazon Braket implementation of a quantum task. A task can be a circuit or an annealing - problem.""" + """Amazon Braket implementation of a quantum task. A quantum task can be a circuit, + 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"} @@ -116,17 +116,17 @@ 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 task to run on device. + task_specification (Union[Circuit, Problem, OpenQASMProgram, BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation]): # noqa + The specification of the quantum task to run on device. 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 task results in. + to store quantum task results in. - shots (int): The number of times to run the task on the device. If the device is a - simulator, this implies the state is sampled N times, where N = `shots`. + shots (int): The number of times to run the quantum task on the device. If the device is + a simulator, this implies the state is sampled N times, where N = `shots`. `shots=0` is only available on simulators and means that the simulator - will compute the exact results based on the task specification. + will compute the exact results based on the quantum task specification. device_parameters (Dict[str, Any]): Additional parameters to send to the device. @@ -151,7 +151,7 @@ def create( Default: None. Returns: - AwsQuantumTask: AwsQuantumTask tracking the task execution on the device. + AwsQuantumTask: AwsQuantumTask tracking the quantum task execution on the device. Note: The following arguments are typically defined via clients of Device. @@ -209,14 +209,15 @@ def __init__( ): """ Args: - arn (str): The ARN of the task. + arn (str): The ARN of the quantum task. aws_session (AwsSession): The `AwsSession` for connecting to AWS services. Default is `None`, in which case an `AwsSession` object will be created with the - region of the task. + region of the quantum task. poll_timeout_seconds (float): The polling timeout for `result()`. Default: 5 days. poll_interval_seconds (float): The polling interval for `result()`. Default: 1 second. - logger (Logger): Logger object with which to write logs, such as task statuses - while waiting for task to be in a terminal state. Default is `getLogger(__name__)` + 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__)` Examples: >>> task = AwsQuantumTask(arn='task_arn') @@ -247,10 +248,11 @@ def __init__( @staticmethod def _aws_session_for_task_arn(task_arn: str) -> AwsSession: """ - Get an AwsSession for the Task ARN. The AWS session should be in the region of the 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 task's region. + AwsSession: `AwsSession` object with default `boto_session` in quantum task's region. """ task_region = task_arn.split(":")[3] boto_session = boto3.Session(region_name=task_region) @@ -270,13 +272,14 @@ def _cancel_future(self) -> None: self._future.cancel() def cancel(self) -> None: - """Cancel the quantum task. This cancels the future and the task in Amazon Braket.""" + """Cancel the quantum task. This cancels the future and the quantum task in Amazon + Braket.""" self._cancel_future() self._aws_session.cancel_quantum_task(self._arn) def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: """ - Get 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 @@ -335,7 +338,7 @@ def result( ]: """ Get the quantum task result by polling Amazon Braket to see if the task is completed. - Once the task is completed, the result is retrieved from S3 and returned as a + Once the quantum task is completed, the result is retrieved from S3 and returned as a `GateModelQuantumTaskResult` or `AnnealingQuantumTaskResult` This method is a blocking thread call and synchronously returns a result. @@ -344,8 +347,8 @@ def result( Returns: Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]: # noqa - The result of the task, if the task completed successfully; returns `None` if the task - did not complete successfully or the future timed out. + 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 diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index b0cd3a38..caa48b39 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -31,7 +31,7 @@ class AwsQuantumTaskBatch(QuantumTaskBatch): """Executes a batch of quantum tasks in parallel. - Using this class can yield vast speedups over executing tasks sequentially, + Using this class can yield vast speedups over executing quantum tasks sequentially, and is particularly useful for computations that can be parallelized, such as calculating quantum gradients or statistics of terms in a Hamiltonian. @@ -71,18 +71,18 @@ def __init__( 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 task + problems, pulse sequences, or photonics program as specification of quantum task to run on device. 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 task results in. - shots (int): The number of times to run the task on the device. If the device is a - simulator, this implies the state is sampled N times, where N = `shots`. + to store quantum task results in. + shots (int): The number of times to run the quantum task on the device. If the device is + a simulator, this implies the state is sampled N times, where N = `shots`. `shots=0` is only available on simulators and means that the simulator - will compute the exact results based on the task specification. - max_parallel (int): The maximum number of tasks to run on AWS in parallel. + will compute the exact results based on the quantum task specification. + max_parallel (int): The maximum number of quantum tasks to run on AWS in parallel. Batch creation will fail if this value is greater than the maximum allowed - concurrent tasks on the device. + concurrent quantum tasks on the device. max_workers (int): The maximum number of thread pool workers. Default: 100 poll_timeout_seconds (float): The polling timeout for `AwsQuantumTask.result()`, in seconds. Default: 5 days. @@ -224,7 +224,7 @@ def _execute( ] except KeyboardInterrupt: # If an exception is thrown before the thread pool has finished, - # clean up the tasks which have not yet been created before reraising it. + # clean up the quantum tasks which have not yet been created before reraising it. if "task_futures" in locals(): for future in task_futures: future.cancel() @@ -265,7 +265,7 @@ def _create_task( remaining.pop() - # If the task hits a terminal state before all tasks have been created, + # 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: @@ -279,24 +279,24 @@ def results( max_retries: int = MAX_RETRIES, use_cached_value: bool = True, ) -> List[AwsQuantumTask]: - """Retrieves the result of every task in the batch. + """Retrieves the result of every quantum task in the batch. - Polling for results happens in parallel; this method returns when all tasks + Polling for results happens in parallel; this method returns when all quantum tasks have reached a terminal state. The result of this method is cached. Args: fail_unsuccessful (bool): If set to `True`, this method will fail - if any task in the batch fails to return a result even after + if any quantum task in the batch fails to return a result even after `max_retries` retries. - max_retries (int): Maximum number of times to retry any failed tasks, - i.e. any tasks in the `FAILED` or `CANCELLED` state or that didn't + max_retries (int): Maximum number of times to retry any failed quantum tasks, + i.e. any quantum tasks in the `FAILED` or `CANCELLED` state or that didn't complete within the timeout. Default: 3. use_cached_value (bool): If `False`, will refetch the results from S3, even when results have already been cached. Default: `True`. Returns: - List[AwsQuantumTask]: The results of all of the tasks in the batch. - `FAILED`, `CANCELLED`, or timed out tasks will have a result of None + List[AwsQuantumTask]: The 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: self._results = AwsQuantumTaskBatch._retrieve_results(self._tasks, self._max_workers) @@ -322,14 +322,14 @@ def _retrieve_results(tasks: List[AwsQuantumTask], max_workers: int) -> List[Aws return [future.result() for future in result_futures] def retry_unsuccessful_tasks(self) -> bool: - """Retries any tasks in the batch without valid results. + """Retries any quantum tasks in the batch without valid results. This method should only be called after `results()` has been called at least once. - The method will generate new tasks for any failed tasks, so `self.task` and + The method will generate new quantum tasks for any failed quantum tasks, so `self.task` and `self.results()` may return different values after a call to this method. Returns: - bool: Whether or not all retried tasks completed successfully. + bool: Whether or not all retried quantum tasks completed successfully. """ if not self._results: raise RuntimeError("results() should be called before attempting to retry") @@ -363,19 +363,20 @@ def retry_unsuccessful_tasks(self) -> bool: @property def tasks(self) -> List[AwsQuantumTask]: - """List[AwsQuantumTask]: The tasks in this batch, as a list of AwsQuantumTask objects""" + """List[AwsQuantumTask]: The quantum tasks in this batch, as a list of AwsQuantumTask + objects""" return list(self._tasks) @property def size(self) -> int: - """int: The number of tasks in the batch""" + """int: The number of quantum tasks in the batch""" return len(self._tasks) @property def unfinished(self) -> Set[str]: - """Gets all the IDs of all the tasks in teh batch that have yet to complete. + """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 tasks in the batch that have yet to complete. + Set[str]: The IDs of all the quantum tasks in the batch that have yet to complete. """ with ThreadPoolExecutor(max_workers=self._max_workers) as executor: status_futures = {task.id: executor.submit(task.state) for task in self._tasks} @@ -390,5 +391,6 @@ def unfinished(self) -> Set[str]: @property def unsuccessful(self) -> Set[str]: - """Set[str]: The IDs of all the FAILED, CANCELLED, or timed out tasks in the batch.""" + """Set[str]: The IDs of all the FAILED, CANCELLED, or timed out quantum tasks in the + batch.""" return set(self._unsuccessful) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 56a4cc1b..300aee13 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -240,13 +240,13 @@ def create_quantum_task(self, **boto3_kwargs) -> str: def create_job(self, **boto3_kwargs) -> str: """ - Create a quantum job. + Create a quantum hybrid job. Args: ``**boto3_kwargs``: Keyword arguments for the Amazon Braket `CreateJob` operation. Returns: - str: The ARN of the job. + str: The ARN of the hybrid job. """ response = self.braket_client.create_job(**boto3_kwargs) return response["jobArn"] @@ -285,7 +285,7 @@ def get_quantum_task(self, arn: str) -> Dict[str, Any]: def get_default_jobs_role(self) -> str: """ - Returns the role ARN for the default jobs role created in the Amazon Braket Console. + 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/`. @@ -316,10 +316,10 @@ def get_default_jobs_role(self) -> str: ) def get_job(self, arn: str) -> Dict[str, Any]: """ - Gets the quantum job. + Gets the hybrid job. Args: - arn (str): The ARN of the quantum job to get. + arn (str): The ARN of the hybrid job to get. Returns: Dict[str, Any]: The response from the Amazon Braket `GetQuantumJob` operation. @@ -328,10 +328,10 @@ def get_job(self, arn: str) -> Dict[str, Any]: def cancel_job(self, arn: str) -> Dict[str, Any]: """ - Cancel the quantum job. + Cancel the hybrid job. Args: - arn (str): The ARN of the quantum job to cancel. + arn (str): The ARN of the hybrid job to cancel. Returns: Dict[str, Any]: The response from the Amazon Braket `CancelJob` operation. @@ -502,8 +502,8 @@ def default_bucket(self) -> str: 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 Job (if evoked inside of a Braket Job), or a default value of - "amazon-braket--. Except in the case of a user- + currently running Braket Hybrid Job (if evoked inside of a Braket Hybrid Job), or a default + value of "amazon-braket--. Except in the case of a user- specified bucket name, this method will create the default bucket if it does not exist. diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index 6c555e3a..148711c7 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -30,7 +30,8 @@ class Instruction: """ - An instruction is a quantum directive that describes the task to perform on a quantum device. + An instruction is a quantum directive that describes the quantum task to perform on a quantum + device. """ def __init__( diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 7223ff6b..6ecb2ecc 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -41,13 +41,13 @@ def run( *args, **kwargs ) -> QuantumTask: - """Run a quantum task specification on this quantum device. A task can be a circuit + """Run a quantum task specification on this quantum device. A quantum task can be a circuit or an annealing problem. Args: - task_specification (Union[Circuit, Problem]): Specification of a task + task_specification (Union[Circuit, Problem]): Specification of a quantum task to run on device. - shots (Optional[int]): The number of times to run the task on the device. + shots (Optional[int]): The number of times to run the quantum task on the device. Default is `None`. 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. @@ -70,21 +70,21 @@ def run_batch( *args, **kwargs ) -> QuantumTaskBatch: - """Executes a batch of tasks in parallel + """Executes a batch of quantum tasks in parallel Args: task_specifications (Union[Union[Circuit, Problem], List[Union[Circuit, Problem]]]): 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 tasks to run in parallel. + 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 tasks on the device. + concurrent quantum tasks on the device. 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. Returns: - QuantumTaskBatch: A batch containing all of the tasks run + QuantumTaskBatch: A batch containing all of the qauntum tasks run """ @property diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 8c97193a..a4462ab6 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -76,10 +76,10 @@ def run( Args: task_specification (Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]): - The task specification. + 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 task specification. + results based on the quantum task specification. Sampling is not supported for shots=0. 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 @@ -113,21 +113,21 @@ def run_batch( *args, **kwargs, ) -> LocalQuantumTaskBatch: - """Executes a batch of tasks in parallel + """Executes a batch of quantum tasks in parallel Args: task_specifications (Union[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], List[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]]]): # noqa - Single instance or list of task specification. - shots (Optional[int]): The number of times to run the task. + 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 tasks to run in parallel. Default + max_parallel (Optional[int]): The maximum number of quantum tasks to run in parallel. Default is the number of CPU. 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: {}. Returns: - LocalQuantumTaskBatch: A batch containing all of the tasks run + LocalQuantumTaskBatch: A batch containing all of the quantum tasks run See Also: `braket.tasks.local_quantum_task_batch.LocalQuantumTaskBatch` diff --git a/src/braket/jobs/config.py b/src/braket/jobs/config.py index de24ed78..73467dd3 100644 --- a/src/braket/jobs/config.py +++ b/src/braket/jobs/config.py @@ -25,7 +25,7 @@ class CheckpointConfig: @dataclass class InstanceConfig: - """Configuration of the instances used to execute the job.""" + """Configuration of the instances used to execute the hybrid job.""" instanceType: str = "ml.m5.large" volumeSizeInGb: int = 30 @@ -34,7 +34,7 @@ class InstanceConfig: @dataclass class OutputDataConfig: - """Configuration that specifies the location for the output of the job.""" + """Configuration that specifies the location for the output of the hybrid job.""" s3Path: Optional[str] = None kmsKeyId: Optional[str] = None @@ -42,7 +42,7 @@ class OutputDataConfig: @dataclass class StoppingCondition: - """Conditions that specify when the job should be forcefully stopped.""" + """Conditions that specify when the hybrid job should be forcefully stopped.""" maxRuntimeInSeconds: int = 5 * 24 * 60 * 60 @@ -64,7 +64,7 @@ def __init__( s3_data, content_type=None, ): - """Create a definition for input data used by a Braket job. + """Create a definition for input data used by a Braket Hybrid job. Args: s3_data (str): Defines the location of s3 data to train on. diff --git a/src/braket/jobs/data_persistence.py b/src/braket/jobs/data_persistence.py index 5bd44adb..198a7e22 100644 --- a/src/braket/jobs/data_persistence.py +++ b/src/braket/jobs/data_persistence.py @@ -64,17 +64,17 @@ def save_job_checkpoint( def load_job_checkpoint(job_name: str, checkpoint_file_suffix: str = "") -> Dict[str, Any]: """ - 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. + Loads the hybrid 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 hybrid job + whose checkpoint data you expect to be available in the file path specified by the + `CHECKPOINT_DIR` container environment variable. - Note: This function for loading job checkpoints is only for use inside the job container + Note: This function for loading hybrid job checkpoints is only for use inside the job container as it writes data to directories and references env variables set in the containers. Args: - job_name (str): str that specifies the name of the job whose checkpoints + job_name (str): str that specifies the name of the hybrid job whose checkpoints are to be loaded. checkpoint_file_suffix (str): str specifying the file suffix that is used to locate the checkpoint file to load. The resulting file name @@ -113,7 +113,7 @@ def save_job_result( environment variable `AMZN_BRAKET_JOB_RESULTS_DIR`, with the filename 'results.json'. The `result_data` values are serialized to the specified `data_format`. - Note: This function for storing the results is only for use inside the job container + Note: This function for storing the results is only for use inside the hybrid job container as it writes data to directories and references env variables set in the containers. diff --git a/src/braket/jobs/local/local_job.py b/src/braket/jobs/local/local_job.py index 6be15720..a6fc8d27 100644 --- a/src/braket/jobs/local/local_job.py +++ b/src/braket/jobs/local/local_job.py @@ -31,7 +31,7 @@ class LocalQuantumJob(QuantumJob): - """Amazon Braket implementation of a quantum job that runs locally.""" + """Amazon Braket implementation of a hybrid job that runs locally.""" @classmethod def create( @@ -50,42 +50,42 @@ def create( aws_session: AwsSession = None, local_container_update: bool = True, ) -> LocalQuantumJob: - """Creates and runs job by setting up and running the customer script in a local + """Creates and runs hybrid job by setting up and running the customer script in a local docker container. Args: device (str): ARN for the AWS device which is primarily accessed for the execution - of this job. Alternatively, a string of the format "local:/" - for using a local simulator for the job. This string will be available as the - environment variable `AMZN_BRAKET_DEVICE_ARN` inside the job container when - using a Braket container. + of this hybrid job. Alternatively, a string of the format + "local:/" for using a local simulator for the hybrid job. This + string will be available as the environment variable `AMZN_BRAKET_DEVICE_ARN` inside + the hybrid job container when using a Braket container. source_module (str): Path (absolute, relative or an S3 URI) to a python module to be tarred and uploaded. If `source_module` is an S3 URI, it must point to a tar.gz file. Otherwise, source_module may be a file or directory. - entry_point (str): A str that specifies the entry point of the job, relative to + entry_point (str): A str that specifies the entry point of the hybrid job, relative to the source module. The entry point must be in the format `importable.module` or `importable.module:callable`. For example, `source_module.submodule:start_here` indicates the `start_here` function contained in `source_module.submodule`. If source_module is an S3 URI, entry point must be given. Default: source_module's name - image_uri (str): A str that specifies the ECR image to use for executing the job. + image_uri (str): A str that specifies the ECR image to use for executing the hybrid job. `image_uris.retrieve_image()` function may be used for retrieving the ECR image URIs for the containers supported by Braket. Default = ``. - job_name (str): A str that specifies the name with which the job is created. + job_name (str): A str that specifies the name with which the hybrid job is created. Default: f'{image_uri_type}-{timestamp}'. code_location (str): The S3 prefix URI where custom code will be uploaded. Default: f's3://{default_bucket_name}/jobs/{job_name}/script'. - role_arn (str): This field is currently not used for local jobs. Local jobs will use - the current role's credentials. This may be subject to change. + role_arn (str): This field is currently not used for local hybrid jobs. Local hybrid + jobs will use the current role's credentials. This may be subject to change. - hyperparameters (Dict[str, Any]): Hyperparameters accessible to the job. - The hyperparameters are made accessible as a Dict[str, str] to the job. + hyperparameters (Dict[str, Any]): Hyperparameters accessible to the hybrid job. + The hyperparameters are made accessible as a Dict[str, str] to the hybrid job. For convenience, this accepts other types for keys and values, but `str()` is called to convert them before being passed on. Default: None. @@ -97,7 +97,8 @@ def create( channel name "input". Default: {}. - output_data_config (OutputDataConfig): Specifies the location for the output of the job. + output_data_config (OutputDataConfig): Specifies the location for the output of the + hybrid job. Default: OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data', kmsKeyId=None). @@ -114,7 +115,7 @@ def create( Default: True. Returns: - LocalQuantumJob: The representation of a local Braket Job. + LocalQuantumJob: The representation of a local Braket Hybrid Job. """ create_job_kwargs = prepare_quantum_job( device=device, @@ -164,8 +165,8 @@ def create( def __init__(self, arn: str, run_log: str = None): """ Args: - arn (str): The ARN of the job. - run_log (str): The container output log of running the job with the given arn. + arn (str): The ARN of the hybrid job. + run_log (str): The container output log of running the hybrid job with the given arn. """ if not arn.startswith("local:job/"): raise ValueError(f"Arn {arn} is not a valid local job arn") @@ -177,20 +178,20 @@ def __init__(self, arn: str, run_log: str = None): @property def arn(self) -> str: - """str: The ARN (Amazon Resource Name) of the quantum job.""" + """str: The ARN (Amazon Resource Name) of the hybrid job.""" return self._arn @property def name(self) -> str: - """str: The name of the quantum job.""" + """str: The name of the hybrid job.""" return self._name @property def run_log(self) -> str: - """Gets the run output log from running the job. + """Gets the run output log from running the hybrid job. Returns: - str: The container output log from running the job. + str: The container output log from running the hybrid job. """ if not self._run_log: try: @@ -201,7 +202,7 @@ def run_log(self) -> str: return self._run_log def state(self, use_cached_value: bool = False) -> str: - """The state of the quantum job. + """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 @@ -213,7 +214,7 @@ def state(self, use_cached_value: bool = False) -> str: return "COMPLETED" def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: - """When running the quantum job in local mode, the metadata is not available. + """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, @@ -225,7 +226,7 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: pass def cancel(self) -> str: - """When running the quantum job in local mode, the cancelling a running is not possible. + """When running the hybrid job in local mode, the cancelling a running is not possible. Returns: str: None """ @@ -237,11 +238,11 @@ def download_result( poll_timeout_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_INTERVAL, ) -> None: - """When running the quantum job in local mode, results are automatically stored locally. + """When running the hybrid job in local mode, results are automatically stored locally. Args: extract_to (str): The directory to which the results are extracted. The results - are extracted to a folder titled with the job name within this directory. + are extracted to a folder titled with the hybrid job name within this directory. Default= `Current working directory`. poll_timeout_seconds (float): The polling timeout, in seconds, for `result()`. Default: 10 days. @@ -255,7 +256,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 job result persisted using save_job_result() function. + """Retrieves the hybrid job result persisted using save_job_result() function. Args: poll_timeout_seconds (float): The polling timeout, in seconds, for `result()`. @@ -264,7 +265,7 @@ def result( Default: 5 seconds. Returns: - Dict[str, Any]: Dict specifying the job results. + Dict[str, Any]: Dict specifying the hybrid job results. """ try: with open(os.path.join(self.name, "results.json"), "r") as f: @@ -308,13 +309,13 @@ def metrics( return parser.get_parsed_metrics(metric_type, statistic) def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: - """Display container logs for a given job + """Display container logs for a given hybrid job Args: - wait (bool): `True` to keep looking for new log entries until the job completes; + wait (bool): `True` to keep looking for new log entries until the hybrid job completes; otherwise `False`. Default: `False`. poll_interval_seconds (int): The interval of time, in seconds, between polling for - new log entries and job completion (default: 5). + new log entries and hybrid job completion (default: 5). """ return print(self.run_log) diff --git a/src/braket/jobs/local/local_job_container.py b/src/braket/jobs/local/local_job_container.py index d9f1c119..f924db47 100644 --- a/src/braket/jobs/local/local_job_container.py +++ b/src/braket/jobs/local/local_job_container.py @@ -21,7 +21,7 @@ class _LocalJobContainer(object): - """Uses docker CLI to run Braket Jobs on a local docker container.""" + """Uses docker CLI to run Braket Hybrid Jobs on a local docker container.""" ECR_URI_PATTERN = r"^((\d+)\.dkr\.ecr\.([^.]+)\.[^/]*)/([^:]*):(.*)$" CONTAINER_CODE_PATH = "/opt/ml/code/" @@ -33,7 +33,8 @@ def __init__( logger: Logger = getLogger(__name__), force_update: bool = False, ): - """Represents and provides functions for interacting with a Braket Jobs docker container. + """Represents and provides functions for interacting with a Braket Hybrid Jobs docker + container. The function "end_session" must be called when the container is no longer needed. Args: @@ -71,7 +72,7 @@ def _envs_to_list(environment_variables: Dict[str, str]) -> List[str]: 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 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 = [] @@ -229,11 +230,11 @@ def run_local_job( self, environment_variables: Dict[str, str], ) -> None: - """Runs a Braket job in a local container. + """Runs a Braket Hybrid job in a local container. Args: environment_variables (Dict[str, str]): The environment variables to make available - as part of running the job. + as part of running the hybrid job. """ 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 6372957e..7505dcbf 100644 --- a/src/braket/jobs/local/local_job_container_setup.py +++ b/src/braket/jobs/local/local_job_container_setup.py @@ -24,17 +24,17 @@ def setup_container( container: _LocalJobContainer, aws_session: AwsSession, **creation_kwargs ) -> Dict[str, str]: - """Sets up a container with prerequisites for running a Braket Job. The prerequisites are - based on the options the customer has chosen for the job. Similarly, any environment variables - that are needed during runtime will be returned by this function. + """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. Args: - container(_LocalJobContainer): The container that will run the braket job. + container(_LocalJobContainer): The container that will run the braket hybrid job. aws_session (AwsSession): AwsSession for connecting to AWS Services. Returns: - Dict[str, str]: A dictionary of environment variables that reflect Braket Jobs options - requested by the customer. + Dict[str, str]: A dictionary of environment variables that reflect Braket Hybrid Jobs + options requested by the customer. """ logger = getLogger(__name__) _create_expected_paths(container, **creation_kwargs) @@ -52,10 +52,10 @@ def setup_container( def _create_expected_paths(container: _LocalJobContainer, **creation_kwargs) -> None: - """Creates the basic paths required for Braket Jobs to run. + """Creates the basic paths required for Braket Hybrid Jobs to run. Args: - container(_LocalJobContainer): The container that will run the braket job. + container(_LocalJobContainer): The container that will run the braket hybrid job. """ container.makedir("/opt/ml/model") container.makedir(creation_kwargs["checkpointConfig"]["localPath"]) @@ -95,7 +95,7 @@ def _get_env_script_mode_config(script_mode_config: Dict[str, str]) -> Dict[str, Args: script_mode_config (Dict[str, str]): The values for scriptModeConfig in the boto3 input - parameters for running a Braket Job. + parameters for running a Braket Hybrid Job. Returns: Dict[str, str]: The set of key/value pairs that should be added as environment variables @@ -137,7 +137,7 @@ def _get_env_default_vars(aws_session: AwsSession, **creation_kwargs) -> Dict[st 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 job. + provided hyperpameters to the hybrid job. Returns: Dict[str, str]: The set of key/value pairs that should be added as environment variables @@ -150,7 +150,7 @@ def _get_env_hyperparameters() -> 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 job. + provided input data to the hybrid job. Returns: Dict[str, str]: The set of key/value pairs that should be added as environment variables @@ -187,13 +187,13 @@ def _download_input_data( download_dir: str, input_data: Dict[str, Any], ) -> None: - """Downloads input data for a job. + """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 - running a Braket Job. + running a Braket Hybrid Job. """ # 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 diff --git a/src/braket/jobs/metrics.py b/src/braket/jobs/metrics.py index cd862628..462501cb 100644 --- a/src/braket/jobs/metrics.py +++ b/src/braket/jobs/metrics.py @@ -22,7 +22,7 @@ def log_metric( iteration_number: Optional[int] = None, ) -> None: """ - Records Braket Job metrics. + Records Braket Hybrid Job metrics. Args: metric_name (str) : The name of the metric. 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 bbcc4f3c..94ed5499 100644 --- a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py @@ -39,8 +39,9 @@ def __init__( in seconds. Default: 10 seconds. poll_interval_seconds (float): The interval of time, in seconds, between polling for results. Default: 1 second. - logger (Logger): Logger object with which to write logs, such as task statuses - while waiting for a task to be in a terminal state. Default is `getLogger(__name__)` + logger (Logger): Logger object with which to write logs, such as quantum task statuses + while waiting for a quantum task to be in a terminal state. Default is + `getLogger(__name__)` """ self._poll_timeout_seconds = poll_timeout_seconds self._poll_interval_seconds = poll_interval_seconds @@ -136,18 +137,18 @@ def get_metrics_for_job( job_end_time: int = None, ) -> Dict[str, List[Union[str, float, int]]]: """ - Synchronously retrieves all the algorithm metrics logged by a given Job. + Synchronously retrieves all the algorithm metrics logged by a given Hybrid Job. Args: - job_name (str): The name of the Job. The name must be exact to ensure only the relevant - metrics are retrieved. + job_name (str): The name of the Hybrid Job. The name must be exact to ensure only the + relevant metrics are retrieved. metric_type (MetricType): The type of metrics to get. Default is MetricType.TIMESTAMP. statistic (MetricStatistic): The statistic to determine which metric value to use when there is a conflict. Default is MetricStatistic.MAX. - job_start_time (int): The time when the job started. + job_start_time (int): The time when the hybrid job started. Default: 3 hours before job_end_time. - job_end_time (int): If the job is complete, this should be the time at which the - job finished. Default: current time. + job_end_time (int): If the hybrid job is complete, this should be the time at which the + hybrid job finished. Default: current time. Returns: Dict[str, List[Union[str, float, int]]] : The metrics data, where the keys @@ -164,7 +165,8 @@ 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 job name needs to be unique to prevent jobs with similar names from being conflated. + # The hybrid job name needs to be unique to prevent jobs with similar names from being + # conflated. query = ( f"fields @timestamp, @message " f"| filter @logStream like /^{job_name}\\// " diff --git a/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py index c7db59da..5e3ef28f 100644 --- a/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py @@ -34,8 +34,9 @@ def __init__( aws_session (AwsSession): AwsSession to connect to AWS with. poll_timeout_seconds (float): The polling timeout for retrieving the metrics, in seconds. Default: 10 seconds. - logger (Logger): Logger object with which to write logs, such as task statuses - while waiting for task to be in a terminal state. Default is `getLogger(__name__)` + 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__)` """ self._poll_timeout_seconds = poll_timeout_seconds self._logger = logger @@ -63,7 +64,7 @@ def _parse_metrics_from_log_stream( parser: LogMetricsParser, ) -> None: """ - Synchronously retrieves the algorithm metrics logged in a given 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. @@ -94,14 +95,14 @@ def _parse_metrics_from_log_stream( def _get_log_streams_for_job(self, job_name: str, timeout_time: float) -> List[str]: """ - Retrieves the list of log streams relevant to a job. + Retrieves the list of log streams relevant to a hybrid job. Args: - job_name (str): The name of the job. + 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 job. + List[str] : A list of log stream names for the given hybrid job. """ kwargs = { "logGroupName": self.LOG_GROUP_NAME, @@ -130,11 +131,11 @@ def get_metrics_for_job( statistic: MetricStatistic = MetricStatistic.MAX, ) -> Dict[str, List[Union[str, float, int]]]: """ - Synchronously retrieves all the algorithm metrics logged by a given Job. + Synchronously retrieves all the algorithm metrics logged by a given Hybrid Job. Args: - job_name (str): The name of the Job. The name must be exact to ensure only the relevant - metrics are retrieved. + job_name (str): The name of the Hybrid Job. The name must be exact to ensure only the + relevant metrics are retrieved. metric_type (MetricType): The type of metrics to get. Default is MetricType.TIMESTAMP. statistic (MetricStatistic): The statistic to determine which metric value to use when there is a conflict. Default is MetricStatistic.MAX. diff --git a/src/braket/jobs/quantum_job.py b/src/braket/jobs/quantum_job.py index f99fe18d..f022d28a 100644 --- a/src/braket/jobs/quantum_job.py +++ b/src/braket/jobs/quantum_job.py @@ -23,22 +23,22 @@ class QuantumJob(ABC): @property @abstractmethod def arn(self) -> str: - """The ARN (Amazon Resource Name) of the quantum job. + """The ARN (Amazon Resource Name) of the hybrid job. Returns: - str: The ARN (Amazon Resource Name) of the quantum job. + str: The ARN (Amazon Resource Name) of the hybrid job. """ @property @abstractmethod def name(self) -> str: - """The name of the quantum job. + """The name of the hybrid job. Returns: - str: The name of the quantum job. + str: The name of the hybrid job. """ @abstractmethod def state(self, use_cached_value: bool = False) -> str: - """The state of the quantum job. + """The state of the hybrid job. Args: use_cached_value (bool): If `True`, uses the value most recently retrieved @@ -55,28 +55,29 @@ def state(self, use_cached_value: bool = False) -> str: @abstractmethod def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: - """Display logs for a given job, optionally tailing them until job is complete. + """Display logs for a given hybrid job, optionally tailing them until hybrid job is + complete. If the output is a tty or a Jupyter cell, it will be color-coded based on which instance the log entry is from. Args: - wait (bool): `True` to keep looking for new log entries until the job completes; + wait (bool): `True` to keep looking for new log entries until the hybrid job completes; otherwise `False`. Default: `False`. poll_interval_seconds (int): The interval of time, in seconds, between polling for - new log entries and job completion (default: 5). + new log entries and hybrid job completion (default: 5). Raises: - RuntimeError: If waiting and the job fails. + RuntimeError: If waiting and the hybrid job fails. """ - # The loop below implements a state machine that alternates between checking the job status - # and reading whatever is available in the logs at this point. Note, that if we were - # called with wait == False, we never check the job status. + # The loop below implements a state machine that alternates between checking the hybrid job + # status and reading whatever is available in the logs at this point. Note, that if we were + # called with wait == False, we never check the hybrid job status. # - # If wait == TRUE and job is not completed, the initial state is TAILING - # If wait == FALSE, the initial state is COMPLETE (doesn't matter if the job really is - # complete). + # If wait == TRUE and hybrid job is not completed, the initial state is TAILING + # If wait == FALSE, the initial state is COMPLETE (doesn't matter if the hybrid job really + # is complete). # # The state table: # @@ -101,7 +102,7 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: `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 job metadata defined in Amazon Braket. + Dict[str, Any]: Dict that specifies the hybrid job metadata defined in Amazon Braket. """ @abstractmethod @@ -133,10 +134,10 @@ def metrics( @abstractmethod def cancel(self) -> str: - """Cancels the job. + """Cancels the hybrid job. Returns: - str: Indicates the status of the job. + str: Indicates the status of the hybrid job. Raises: ClientError: If there are errors invoking the CancelJob API. @@ -148,7 +149,7 @@ def result( poll_timeout_seconds: float = DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = DEFAULT_RESULTS_POLL_INTERVAL, ) -> Dict[str, Any]: - """Retrieves the job result persisted using save_job_result() function. + """Retrieves the hybrid job result persisted using save_job_result() function. Args: poll_timeout_seconds (float): The polling timeout, in seconds, for `result()`. @@ -159,11 +160,11 @@ def result( Returns: - Dict[str, Any]: Dict specifying the job results. + Dict[str, Any]: Dict specifying the hybrid job results. Raises: - RuntimeError: if job is in a FAILED or CANCELLED state. - TimeoutError: if job execution exceeds the polling timeout period. + RuntimeError: if hybrid job is in a FAILED or CANCELLED state. + TimeoutError: if hybrid job execution exceeds the polling timeout period. """ @abstractmethod @@ -173,13 +174,13 @@ def download_result( poll_timeout_seconds: float = DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = DEFAULT_RESULTS_POLL_INTERVAL, ) -> None: - """Downloads the results from the job output S3 bucket and extracts the tar.gz + """Downloads the results from the hybrid job output S3 bucket and extracts the tar.gz bundle to the location specified by `extract_to`. If no location is specified, the results are extracted to the current directory. Args: extract_to (str): The directory to which the results are extracted. The results - are extracted to a folder titled with the job name within this directory. + are extracted to a folder titled with the hybrid job name within this directory. Default= `Current working directory`. poll_timeout_seconds (float): The polling timeout, in seconds, for `download_result()`. @@ -189,6 +190,6 @@ def download_result( `download_result()`.Default: 5 seconds. Raises: - RuntimeError: if job is in a FAILED or CANCELLED state. - TimeoutError: if job execution exceeds the polling timeout period. + RuntimeError: if hybrid job is in a FAILED or CANCELLED state. + TimeoutError: if hybrid job execution exceeds the polling timeout period. """ diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py index 400bb03b..1dbc7dc4 100644 --- a/src/braket/jobs/quantum_job_creation.py +++ b/src/braket/jobs/quantum_job_creation.py @@ -54,28 +54,29 @@ def prepare_quantum_job( aws_session: AwsSession = None, tags: Dict[str, str] = None, ) -> Dict: - """Creates a job by invoking the Braket CreateJob API. + """Creates a hybrid job by invoking the Braket CreateJob API. Args: device (str): ARN for the AWS device which is primarily - accessed for the execution of this job. + accessed for the execution of this hybrid job. source_module (str): Path (absolute, relative or an S3 URI) to a python module to be tarred and uploaded. If `source_module` is an S3 URI, it must point to a tar.gz file. Otherwise, source_module may be a file or directory. - entry_point (str): A str that specifies the entry point of the job, relative to + entry_point (str): A str that specifies the entry point of the hybrid job, relative to the source module. The entry point must be in the format `importable.module` or `importable.module:callable`. For example, `source_module.submodule:start_here` indicates the `start_here` function contained in `source_module.submodule`. If source_module is an S3 URI, entry point must be given. Default: source_module's name - image_uri (str): A str that specifies the ECR image to use for executing the job. + image_uri (str): A str that specifies the ECR image to use for executing the hybrid job. `image_uris.retrieve_image()` function may be used for retrieving the ECR image URIs for the containers supported by Braket. Default = ``. - job_name (str): A str that specifies the name with which the job is created. The job + job_name (str): A str that specifies the name with which the hybrid job is created. The + hybrid job name must be between 0 and 50 characters long and cannot contain underscores. Default: f'{image_uri_type}-{timestamp}'. @@ -85,8 +86,8 @@ def prepare_quantum_job( role_arn (str): A str providing the IAM role ARN used to execute the script. Default: IAM role returned by AwsSession's `get_default_jobs_role()`. - hyperparameters (Dict[str, Any]): Hyperparameters accessible to the job. - The hyperparameters are made accessible as a Dict[str, str] to the job. + hyperparameters (Dict[str, Any]): Hyperparameters accessible to the hybrid job. + The hyperparameters are made accessible as a Dict[str, str] to the hybrid job. For convenience, this accepts other types for keys and values, but `str()` is called to convert them before being passed on. Default: None. @@ -99,26 +100,27 @@ def prepare_quantum_job( Default: {}. instance_config (InstanceConfig): Configuration of the instances to be used - to execute the job. Default: InstanceConfig(instanceType='ml.m5.large', + to execute the hybrid job. Default: InstanceConfig(instanceType='ml.m5.large', instanceCount=1, volumeSizeInGB=30, volumeKmsKey=None). - distribution (str): A str that specifies how the job should be distributed. If set to - "data_parallel", the hyperparameters for the job will be set to use data parallelism - features for PyTorch or TensorFlow. Default: None. + distribution (str): A str that specifies how the hybrid job should be distributed. If set to + "data_parallel", the hyperparameters for the hybrid job will be set to use data + parallelism features for PyTorch or TensorFlow. Default: None. stopping_condition (StoppingCondition): The maximum length of time, in seconds, - and the maximum number of tasks that a job can run before being forcefully stopped. - Default: StoppingCondition(maxRuntimeInSeconds=5 * 24 * 60 * 60). + and the maximum number of quantum tasks that a hybrid job can run before being + forcefully stopped. Default: StoppingCondition(maxRuntimeInSeconds=5 * 24 * 60 * 60). - output_data_config (OutputDataConfig): Specifies the location for the output of the job. + output_data_config (OutputDataConfig): Specifies the location for the output of the hybrid + job. Default: OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data', kmsKeyId=None). - copy_checkpoints_from_job (str): A str that specifies the job ARN whose checkpoint you - want to use in the current job. Specifying this value will copy over the checkpoint - data from `use_checkpoints_from_job`'s checkpoint_config s3Uri to the current job's - checkpoint_config s3Uri, making it available at checkpoint_config.localPath during - the job execution. Default: None + copy_checkpoints_from_job (str): A str that specifies the hybrid job ARN whose checkpoint + you want to use in the current hybrid job. Specifying this value will copy over the + checkpoint data from `use_checkpoints_from_job`'s checkpoint_config s3Uri to the current + hybrid job's checkpoint_config s3Uri, making it available at checkpoint_config.localPath + during the hybrid job execution. Default: None checkpoint_config (CheckpointConfig): Configuration that specifies the location where checkpoint data is stored. @@ -128,11 +130,11 @@ def prepare_quantum_job( aws_session (AwsSession): AwsSession for connecting to AWS Services. Default: AwsSession() - tags (Dict[str, str]): Dict specifying the key-value pairs for tagging this job. + tags (Dict[str, str]): Dict specifying the key-value pairs for tagging this hybrid job. Default: {}. Returns: - Dict: Job tracking the execution on Amazon Braket. + Dict: Hybrid job tracking the execution on Amazon Braket. Raises: ValueError: Raises ValueError if the parameters are not valid. @@ -226,12 +228,12 @@ def prepare_quantum_job( def _generate_default_job_name(image_uri: Optional[str]) -> str: """ - Generate default job name using the image uri and a timestamp + Generate default hybrid job name using the image uri and a timestamp Args: image_uri (Optional[str]): URI for the image container. Returns: - str: Job name. + str: Hybrid job name. """ if not image_uri: job_type = "-default" @@ -253,7 +255,7 @@ def _process_s3_source_module( Args: source_module (str): S3 URI pointing to the tarred source module. - entry_point (str): Entry point for the job. + entry_point (str): Entry point for the hybrid job. 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. @@ -362,7 +364,7 @@ def _process_input_data( input_data (Union[str, Dict, S3DataSourceConfig]): Either a channel definition or a dictionary mapping channel names to channel definitions, where a channel definition can be an S3DataSourceConfig or a str corresponding to a local prefix or S3 prefix. - job_name (str): Job name. + job_name (str): Hybrid job name. aws_session (AwsSession): AwsSession for possibly uploading local data. Returns: @@ -383,7 +385,7 @@ def _process_channel( Convert a location to an S3DataSourceConfig, uploading local data to S3, if necessary. Args: location (str): Local prefix or S3 prefix. - job_name (str): Job name. + job_name (str): Hybrid job name. aws_session (AwsSession): AwsSession to be used for uploading local data. channel_name (str): Name of the channel. diff --git a/src/braket/tasks/annealing_quantum_task_result.py b/src/braket/tasks/annealing_quantum_task_result.py index 82828942..1a5fbc2c 100644 --- a/src/braket/tasks/annealing_quantum_task_result.py +++ b/src/braket/tasks/annealing_quantum_task_result.py @@ -35,8 +35,8 @@ class AnnealingQuantumTaskResult: output or energy of the solutions. variable_count (int): the number of variables problem_type (ProblemType): the type of annealing problem - task_metadata (TaskMetadata): Task metadata. - additional_metadata (AdditionalMetadata): Additional metadata about the task + task_metadata (TaskMetadata): Quantum task metadata. + additional_metadata (AdditionalMetadata): Additional metadata about the quantum task """ record_array: numpy.recarray diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 04b201e3..9b21b007 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -40,8 +40,8 @@ class GateModelQuantumTaskResult: to be initialized by a QuantumTask class. Args: - task_metadata (TaskMetadata): Task metadata. - additional_metadata (AdditionalMetadata): Additional metadata about the task + task_metadata (TaskMetadata): Quantum task metadata. + additional_metadata (AdditionalMetadata): Additional metadata about the quantum task result_types (List[Dict[str, Any]]): List of dictionaries where each dictionary has two keys: 'Type' (the result type in IR JSON form) and 'Value' (the result value for this result type). @@ -105,7 +105,7 @@ def __post_init__(self): def get_value_by_result_type(self, result_type: ResultType) -> Any: """ Get value by result type. The result type must have already been - requested in the circuit sent to the device for this task result. + requested in the circuit sent to the device for this quantum task result. Args: result_type (ResultType): result type requested diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py index 5b079f63..69a0f1bd 100644 --- a/src/braket/tasks/local_quantum_task.py +++ b/src/braket/tasks/local_quantum_task.py @@ -23,7 +23,7 @@ class LocalQuantumTask(QuantumTask): - """A task containing the results of a local simulation. + """A quantum task containing the results of a local simulation. Since this class is instantiated with the results, cancel() and run_async() are unsupported. """ diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py index ccefc7f5..47ea8fff 100644 --- a/src/braket/tasks/quantum_task.py +++ b/src/braket/tasks/quantum_task.py @@ -26,9 +26,9 @@ class QuantumTask(ABC): @property @abstractmethod def id(self) -> str: - """Get the task ID. + """Get the quantum task ID. Returns: - str: The task ID. + str: The quantum task ID. """ @abstractmethod @@ -71,6 +71,6 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: request. Default is False. Returns: - Dict[str, Any]: The metadata regarding the task. If `use_cached_value` is True, + Dict[str, Any]: The metadata regarding the quantum task. If `use_cached_value` is True, then the value retrieved from the most recent request is used. """ diff --git a/src/braket/tracking/tracker.py b/src/braket/tracking/tracker.py index e902e170..9558d1d5 100644 --- a/src/braket/tracking/tracker.py +++ b/src/braket/tracking/tracker.py @@ -71,7 +71,7 @@ def tracked_resources(self) -> List[str]: Resources tracked by this tracker. Returns: - List[str]: The list of task ids for tasks tracked by this tracker. + List[str]: The list of quantum task ids for quantum tasks tracked by this tracker. """ return list(self._resources.keys()) @@ -98,8 +98,8 @@ def simulator_tasks_cost(self) -> Decimal: """ Estimate cost of all quantum tasks tracked by this tracker using Braket simulator devices. - Note: The cost of a simulator task is not available until after the results for the task - have been fetched. Call `result()` on an `AwsQuantumTask` before estimating its cost + 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 to ensure that the simulator usage is included in the cost estimate. Note: Charges shown are estimates based on your Amazon Braket simulator and quantum @@ -123,9 +123,9 @@ 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 tasks run on the device. The summary includes the + 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 tasks on simulator devices, the summary + created on this device. For finished quantum tasks on simulator devices, the summary also includes the duration of the simulation. Example: diff --git a/test/integ_tests/test_create_local_quantum_job.py b/test/integ_tests/test_create_local_quantum_job.py index 02ae41da..ad91d0a0 100644 --- a/test/integ_tests/test_create_local_quantum_job.py +++ b/test/integ_tests/test_create_local_quantum_job.py @@ -23,10 +23,10 @@ def test_completed_local_job(aws_session, capsys): - """Asserts the job is completed with the respective files and folders for logs, + """Asserts the hybrid job is completed with the respective files and folders for logs, results and checkpoints. Validate the results are what we expect. Also, assert that logs contains all the necessary steps for setup and running - the job is displayed to the user. + the hybrid job is displayed to the user. """ absolute_source_module = str(Path("test/integ_tests/job_test_script.py").resolve()) current_dir = Path.cwd() @@ -104,7 +104,7 @@ def test_completed_local_job(aws_session, capsys): def test_failed_local_job(aws_session, capsys): - """Asserts the job is failed with the output, checkpoints not created in bucket + """Asserts the hybrid job is failed with the output, checkpoints not created in bucket and only logs are populated. Validate the calling result function raises the ValueError. Also, check if the logs displays the required error message. """ diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py index 76c49cd4..568a53bc 100644 --- a/test/integ_tests/test_create_quantum_job.py +++ b/test/integ_tests/test_create_quantum_job.py @@ -21,8 +21,8 @@ def test_failed_quantum_job(aws_session, capsys): - """Asserts the job is failed with the output, checkpoints, - tasks not created in bucket and only input is uploaded to s3. Validate the + """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. """ @@ -76,10 +76,10 @@ def test_failed_quantum_job(aws_session, capsys): def test_completed_quantum_job(aws_session, capsys): - """Asserts the job is completed with the output, checkpoints, tasks and - script folder created in S3 for respective job. Validate the results are + """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 job and is displayed to the user. + necessary steps for setup and running the hybrid job and is displayed to the user. """ job = AwsQuantumJob.create( From fccba361b227a2a88a05bc7eed197d7e0af7e471 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 4 Sep 2023 16:13:49 +0000 Subject: [PATCH 0838/1165] prepare release v1.54.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 5b7e6003..a5350f18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.54.3.post0 (2023-09-04) + +### Documentation Changes + + * standardize task and job naming to quantum task and hybrid job + ## v1.54.3 (2023-08-30) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 78e34acd..f7e999c7 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.54.4.dev0" +__version__ = "1.54.3.post0" From 1ea24fb1749b74184aea5048a73ceac867c1c534 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 4 Sep 2023 16:13:49 +0000 Subject: [PATCH 0839/1165] update development version to v1.54.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 f7e999c7..78e34acd 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.54.3.post0" +__version__ = "1.54.4.dev0" From 4c71dc6716c4782a21b62af1d17f4a035869103e Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 5 Sep 2023 14:11:51 -0400 Subject: [PATCH 0840/1165] feature: basic device targeting for autoqasm programs (#693) --- src/braket/circuits/serialization.py | 21 +++ src/braket/devices/device.py | 11 +- src/braket/devices/local_simulator.py | 21 +-- src/braket/experimental/autoqasm/api.py | 89 +++++++----- src/braket/experimental/autoqasm/errors.py | 10 +- .../experimental/autoqasm/program/program.py | 15 +- .../braket/experimental/autoqasm/test_api.py | 17 +++ .../experimental/autoqasm/test_devices.py | 136 ++++++++++++++++++ 8 files changed, 267 insertions(+), 53 deletions(-) create mode 100644 test/unit_tests/braket/experimental/autoqasm/test_devices.py diff --git a/src/braket/circuits/serialization.py b/src/braket/circuits/serialization.py index 596a1f36..152f31de 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 @@ -33,6 +34,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: If the supplied `ir_type` is not supported. + + Returns: + str: A representation of the program in the `ir_type` format. + """ + + @dataclass class OpenQASMSerializationProperties: """ diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 11623e31..7223ff6b 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -14,7 +14,6 @@ from abc import ABC, abstractmethod from typing import Dict, List, Optional, Union -import braket.experimental.autoqasm as aq from braket.annealing.problem import Problem from braket.circuits import Circuit from braket.tasks.quantum_task import QuantumTask @@ -36,7 +35,7 @@ def __init__(self, name: str, status: str): @abstractmethod def run( self, - task_specification: Union[Circuit, Problem, aq.Program], + task_specification: Union[Circuit, Problem], shots: Optional[int], inputs: Optional[Dict[str, float]], *args, @@ -46,7 +45,7 @@ def run( or an annealing problem. Args: - task_specification (Union[Circuit, Problem, Program]): Specification of a task + task_specification (Union[Circuit, Problem]): Specification of a task to run on device. shots (Optional[int]): The number of times to run the task on the device. Default is `None`. @@ -62,8 +61,8 @@ def run( def run_batch( self, task_specifications: Union[ - Union[Circuit, Problem, aq.Program], - List[Union[Circuit, Problem, aq.Program]], + Union[Circuit, Problem], + List[Union[Circuit, Problem]], ], shots: Optional[int], max_parallel: Optional[int], @@ -74,7 +73,7 @@ def run_batch( """Executes a batch of tasks in parallel Args: - task_specifications (Union[Union[Circuit, Problem, Program], List[Union[Circuit, Problem, Program]]]): # noqa E501 + task_specifications (Union[Union[Circuit, Problem], List[Union[Circuit, Problem]]]): 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 tasks to run in parallel. diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 03ab81dd..5b0092e3 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -21,12 +21,11 @@ import pkg_resources -import braket.experimental.autoqasm as aq 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.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 @@ -69,7 +68,7 @@ def __init__(self, backend: Union[str, BraketSimulator] = "default"): def run( self, task_specification: Union[ - Circuit, Problem, Program, AnalogHamiltonianSimulation, aq.Program + Circuit, Problem, Program, AnalogHamiltonianSimulation, SerializableProgram ], shots: int = 0, inputs: Optional[Dict[str, float]] = None, @@ -79,7 +78,7 @@ def run( """Runs the given task with the wrapped local simulator. Args: - task_specification (Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, Program]): # noqa E501 + task_specification (Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, SerializableProgram]): # noqa E501 The 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 @@ -108,8 +107,10 @@ def run( def run_batch( self, task_specifications: Union[ - Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, aq.Program], - List[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, aq.Program]], + Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, SerializableProgram], + List[ + Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, SerializableProgram] + ], ], shots: Optional[int] = 0, max_parallel: Optional[int] = None, @@ -120,7 +121,7 @@ def run_batch( """Executes a batch of tasks in parallel Args: - task_specifications (Union[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, Program], List[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, Program]]]): # noqa E501 + task_specifications (Union[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, SerializableProgram], List[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, SerializableProgram]]]): # noqa E501 Single instance or list of task specification. shots (Optional[int]): The number of times to run the task. Default: 0. @@ -204,7 +205,7 @@ def registered_backends() -> Set[str]: def _run_internal_wrap( self, task_specification: Union[ - Circuit, Problem, Program, AnalogHamiltonianSimulation, aq.Program + Circuit, Problem, Program, AnalogHamiltonianSimulation, SerializableProgram ], shots: Optional[int] = None, inputs: Optional[Dict[str, float]] = None, @@ -238,7 +239,7 @@ def _(self, backend_impl: BraketSimulator): def _run_internal( self, task_specification: Union[ - Circuit, Problem, Program, AnalogHamiltonianSimulation, AHSProgram, aq.Program + Circuit, Problem, Program, AnalogHamiltonianSimulation, AHSProgram, SerializableProgram ], shots: Optional[int] = None, *args, @@ -306,7 +307,7 @@ def _( @_run_internal.register def _( self, - program: aq.Program, + program: SerializableProgram, shots: Optional[int] = None, inputs: Optional[Dict[str, float]] = None, *args, diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 3b13f52e..beb04ef6 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -17,7 +17,7 @@ import functools import inspect from types import FunctionType -from typing import Any, Callable, Dict, List, Optional, Tuple +from typing import Any, Callable, Dict, List, Optional, Tuple, Union import openqasm3.ast as qasm_ast import oqpy.base @@ -27,6 +27,7 @@ import braket.experimental.autoqasm.program as aq_program import braket.experimental.autoqasm.transpiler as aq_transpiler import braket.experimental.autoqasm.types as aq_types +from braket.aws import AwsDevice from braket.experimental.autoqasm import errors from braket.experimental.autoqasm.autograph.core import converter from braket.experimental.autoqasm.autograph.impl.api_core import ( @@ -36,7 +37,9 @@ from braket.experimental.autoqasm.autograph.tf_utils import tf_decorator -def main(*args, num_qubits: Optional[int] = None) -> Callable[[Any], aq_program.Program]: +def main( + *args, num_qubits: Optional[int] = None, device: Optional[Union[AwsDevice, str]] = None +) -> Callable[[Any], aq_program.Program]: """Decorator that converts a function into a callable that returns a Program object containing the quantum program. @@ -46,21 +49,21 @@ def main(*args, num_qubits: Optional[int] = None) -> Callable[[Any], aq_program. Args: num_qubits (Optional[int]): Configuration to set the total number of qubits to declare in the program. + device (Optional[Union[AwsDevice, str]]): Configuration to set the target device for the + program. Can be either an AwsDevice object or a valid Amazon Braket device ARN. Returns: Callable[[Any], Program]: A callable which returns the converted quantum program when called. """ - user_config = aq_program.UserConfig(num_qubits=num_qubits) - - if not args: + if isinstance(device, str): + device = AwsDevice(device) - def _function_with_params(f: Callable) -> Callable[[Any], aq_program.Program]: - return _function_wrapper(f, _convert_main, converter_args={"user_config": user_config}) - - return _function_with_params - - return _function_wrapper(args[0], _convert_main, converter_args={"user_config": user_config}) + return _function_wrapper( + args, + _convert_main, + converter_args={"user_config": aq_program.UserConfig(num_qubits=num_qubits, device=device)}, + ) def subroutine(*args) -> Callable[[Any], aq_program.Program]: @@ -71,7 +74,7 @@ def subroutine(*args) -> Callable[[Any], aq_program.Program]: Callable[[Any], Program]: A callable which returns the converted quantum program when called. """ - return _function_wrapper(args[0], _convert_subroutine) + return _function_wrapper(args, _convert_subroutine) def gate(*args) -> Callable[[Any], None]: @@ -81,19 +84,18 @@ def gate(*args) -> Callable[[Any], None]: Callable[[Any],]: A callable which can be used as a custom gate inside an aq.function or inside another aq.gate. """ - return _function_wrapper(args[0], _convert_gate) + return _function_wrapper(args, _convert_gate) def _function_wrapper( - f: Callable, + args: Tuple[Any], converter_callback: Callable, converter_args: Optional[Dict[str, Any]] = None, ) -> Callable[[Any], aq_program.Program]: """Wrapping and conversion logic around the user function `f`. Args: - f (Callable): The target function to be converted which represents - the entry point of the quantum program. + args (Tuple[Any]): The arguments to the decorated function. converter_callback (Callable): The function converter, e.g., _convert_main. converter_args (Optional[Dict[str, Any]]): Extra arguments for the function converter. @@ -101,6 +103,18 @@ def _function_wrapper( Callable[[Any], Program]: A callable which returns the converted quantum program when called. """ + if not args: + # This the case where a decorator is called with only keyword args, for example: + # @aq.main(num_qubits=4) + # def my_function(): + # To make this work, here we simply return another wrapper function which expects + # a Callable as the first argument. + def _function_wrapper_with_params(*args) -> Callable[[Any], aq_program.Program]: + return _function_wrapper(args, converter_callback, converter_args=converter_args) + + return _function_wrapper_with_params + + f = args[0] if is_autograph_artifact(f): return f @@ -177,29 +191,40 @@ def _add_qubit_declaration(program_conversion_context: aq_program.ProgramConvers Args: program_conversion_context (ProgramConversionContext): The program conversion context. """ - root_oqpy_program = program_conversion_context.get_oqpy_program( - scope=aq_program.ProgramScope.MAIN - ) + num_qubits = None - # Declare the global qubit register if necessary + # User-supplied qubit count user_specified_num_qubits = program_conversion_context.get_declared_qubits() - if user_specified_num_qubits is not None: - # User-supplied qubit count - root_oqpy_program.declare( - [oqpy.QubitArray(aq_constants.QUBIT_REGISTER, user_specified_num_qubits)], - to_beginning=True, - ) + num_qubits = user_specified_num_qubits - else: - # Qubit count from program inspection + # Qubit count from program inspection + if num_qubits is None: qubits = program_conversion_context.qubits max_qubit_index = qubits[-1] if len(qubits) else None if max_qubit_index is not None: - root_oqpy_program.declare( - [oqpy.QubitArray(aq_constants.QUBIT_REGISTER, max_qubit_index + 1)], - to_beginning=True, - ) + num_qubits = max_qubit_index + 1 + + # Early return if we are not going to declare any qubits + if num_qubits is None: + return + + # Validate that the target device has enough qubits + device = program_conversion_context.get_target_device() + if device and num_qubits > device.properties.paradigm.qubitCount: + raise errors.InsufficientQubitCountError( + f'Program requires {num_qubits} qubits, but target device "{device.name}" has ' + f"only {device.properties.paradigm.qubitCount} qubits." + ) + + # Declare the global qubit register + root_oqpy_program = program_conversion_context.get_oqpy_program( + scope=aq_program.ProgramScope.MAIN + ) + root_oqpy_program.declare( + [oqpy.QubitArray(aq_constants.QUBIT_REGISTER, num_qubits)], + to_beginning=True, + ) def _convert_subroutine( diff --git a/src/braket/experimental/autoqasm/errors.py b/src/braket/experimental/autoqasm/errors.py index 6eb6fbd3..74a0db99 100644 --- a/src/braket/experimental/autoqasm/errors.py +++ b/src/braket/experimental/autoqasm/errors.py @@ -48,10 +48,10 @@ def __init__(self): self.message = """Unspecified number of qubits. Specify the number of qubits used by your program by supplying the \ -`num_qubits` argument to `autoqasm.function`. For example: +`num_qubits` argument to `aq.main`. For example: - @autoqasm.function(num_qubits=5) - def my_autoqasm_function(): + @aq.main(num_qubits=5) + def my_autoqasm_program(): ... """ @@ -59,6 +59,10 @@ def __str__(self): return self.message +class InsufficientQubitCountError(AutoQasmError): + """Target device does not have enough qubits for the program.""" + + class UnsupportedConditionalExpressionError(AutoQasmError): """Conditional expressions which return values are not supported.""" diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index b17692f1..3569c554 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -21,7 +21,8 @@ import oqpy.base -from braket.circuits.serialization import IRType +from braket.aws import AwsDevice +from braket.circuits.serialization import IRType, SerializableProgram from braket.experimental.autoqasm import constants, errors # Create the thread-local object for the program conversion context. @@ -43,6 +44,10 @@ class UserConfig: """User-specified configurations that influence program building.""" num_qubits: Optional[int] = None + """The total number of qubits to declare in the program.""" + + device: Optional[AwsDevice] = None + """The target device for the program.""" class ProgramScope(Enum): @@ -63,7 +68,7 @@ class ProgramMode(Enum): """For program conversion inside a context where only unitary operations are allowed.""" -class Program: +class Program(SerializableProgram): """The program that has been generated with AutoQASM. This object can be passed to the run() method of a Braket Device.""" @@ -180,6 +185,12 @@ def get_declared_qubits(self) -> Optional[int]: """ return self.user_config.num_qubits + def get_target_device(self) -> Optional[AwsDevice]: + """Return the target device for the program, as specified by the user. + Returns None if the user did not specify a target device. + """ + return self.user_config.device + def next_var_name(self, kind: type) -> str: """Return the next name for a new classical variable. diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index 12405adc..6f79b463 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -917,3 +917,20 @@ def main(): with pytest.raises(errors.AutoQasmTypeError): main() + + +def test_empty_decorator_parentheses(): + @aq.subroutine() + def nothing(): + pass + + @aq.main() + def main(): + nothing() + + expected = """OPENQASM 3.0; +def nothing() { +} +nothing();""" + + assert main().to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_devices.py b/test/unit_tests/braket/experimental/autoqasm/test_devices.py new file mode 100644 index 00000000..d0bdb4a0 --- /dev/null +++ b/test/unit_tests/braket/experimental/autoqasm/test_devices.py @@ -0,0 +1,136 @@ +# 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. + +"""AutoQASM tests exercising device-specific targeting functionality. +""" + +from unittest.mock import Mock, patch + +import pytest + +import braket.experimental.autoqasm as aq +from braket.aws import AwsDevice +from braket.device_schema.simulators import GateModelSimulatorDeviceCapabilities +from braket.devices import Devices +from braket.experimental.autoqasm import errors +from braket.experimental.autoqasm.instructions import h + +RIGETTI_REGION = "us-west-1" + +MOCK_DEFAULT_S3_DESTINATION_FOLDER = ( + "amazon-braket-us-test-1-00000000", + "tasks", +) + +MOCK_GATE_MODEL_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.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": ["H"], + } + }, + "paradigm": {"qubitCount": 30}, + "deviceParameters": {}, +} + +MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES = GateModelSimulatorDeviceCapabilities.parse_obj( + MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES_JSON +) + +MOCK_GATE_MODEL_SIMULATOR = { + "deviceName": "SV1", + "deviceType": "SIMULATOR", + "providerName": "provider1", + "deviceStatus": "ONLINE", + "deviceCapabilities": MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES.json(), +} + + +@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 + _aws_session.get_device.return_value = MOCK_GATE_MODEL_SIMULATOR + + return _aws_session + + +@pytest.fixture +def aws_device(): + _aws_device = Mock() + _aws_device.name = "Mock SV1 Device" + _aws_device.properties.paradigm.qubitCount = 34 + return _aws_device + + +@patch("braket.aws.aws_device.AwsSession.copy_session") +@patch("braket.aws.aws_device.AwsSession") +@patch("braket.aws.aws_device.AwsDevice") +def test_device_parameter( + aws_session_init: Mock, + aws_device_init: Mock, + mock_copy_session: Mock, + aws_session: Mock, + aws_device: Mock, +) -> None: + aws_session_init.return_value = aws_session + aws_device_init.return_value = aws_device + mock_copy_session.return_value = aws_session + + devices = [None, Devices.Amazon.SV1, AwsDevice(Devices.Amazon.SV1)] + + for device in devices: + + @aq.main(device=device) + def my_program(): + h(0) + + program = my_program() + assert program.to_ir() + + +def test_insufficient_qubits(aws_device: Mock) -> None: + @aq.main(device=aws_device, num_qubits=35) + def my_program(): + pass + + with pytest.raises(errors.InsufficientQubitCountError): + my_program() From d22b3e893181480e12c7be9387f50f52268dc821 Mon Sep 17 00:00:00 2001 From: ashlhans <65787294+ashlhans@users.noreply.github.com> Date: Fri, 8 Sep 2023 15:06:41 -0700 Subject: [PATCH 0841/1165] feat: add Aria2 enum (#653) Co-authored-by: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com> Co-authored-by: Cody Wang --- 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 e179bdfd..8f873033 100644 --- a/src/braket/devices/devices.py +++ b/src/braket/devices/devices.py @@ -30,6 +30,7 @@ class _DWave(str, Enum): 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" + Aria2 = "arn:aws:braket:us-east-1::device/qpu/ionq/Aria-2" class _OQC(str, Enum): Lucy = "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" From 4140f68bcf503d8968f7b817c8072cd013b25802 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Sep 2023 19:06:49 -0700 Subject: [PATCH 0842/1165] infra: bump actions/checkout from 3.6.0 to 4.0.0 (#696) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.6.0 to 4.0.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/f43a0e5ff2bd294095638e18286ca9a3d1956744...3df4ab11eba7bda6032a0b82a6bb43b11571feac) --- updated-dependencies: - dependency-name: actions/checkout 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 c088c064..160e8e00 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@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - name: Set up Python uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0 with: diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index c4e4a005..732600ba 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@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0 with: diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 0fd1b03a..a26168c1 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@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - name: Set up Python uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0 with: diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index f773f151..591ffed1 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -24,7 +24,7 @@ jobs: python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0 with: diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index 46be37a2..5a596676 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@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - name: Set up Python uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0 with: From 2ab99acc7900f679b6e65b11814eb79dcf8b5666 Mon Sep 17 00:00:00 2001 From: ci Date: Sat, 9 Sep 2023 04:42:17 +0000 Subject: [PATCH 0843/1165] prepare release v1.55.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 a5350f18..e83803ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.55.0 (2023-09-09) + +### Features + + * add Aria2 enum + ## v1.54.3.post0 (2023-09-04) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 78e34acd..11b6dcd5 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.54.4.dev0" +__version__ = "1.55.0" From 44e097458139eb94cb2cc8192f8e583cea2f45f6 Mon Sep 17 00:00:00 2001 From: ci Date: Sat, 9 Sep 2023 04:42:17 +0000 Subject: [PATCH 0844/1165] update development version to v1.55.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 11b6dcd5..46367d95 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.55.0" +__version__ = "1.55.1.dev0" From a69c39b4e3eff2628f2614822e0e8c050afbfd1d Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Mon, 11 Sep 2023 10:35:48 -0400 Subject: [PATCH 0845/1165] feat: gate calibrations for binding to built main programs (#692) * calibration as a karg of converted program call * add a unit test * bind_calibrations method of aq.Program class * use _function_wrapper for aq.calibration * update type check * aq.pulse_sequence as the interface for gate calibration * adapted to updated _function_wrapper * increase test coverage * increase validation coverage * fix typo * simplify logic of args matching * rename calibration entry point * update docstring * use aq program in GateCalibration * enforce physical qubits for barrier and delay * update type hint * fix unit test for not binding calibration inplace --- src/braket/experimental/autoqasm/__init__.py | 2 +- src/braket/experimental/autoqasm/api.py | 152 +++++++++++- src/braket/experimental/autoqasm/errors.py | 4 + .../autoqasm/instructions/qubits.py | 2 +- .../autoqasm/program/gate_calibrations.py | 43 ++++ .../experimental/autoqasm/program/program.py | 64 ++++- .../experimental/autoqasm/pulse/pulse.py | 39 ++- .../autoqasm/test_gate_calibrations.py | 232 ++++++++++++++++++ .../experimental/autoqasm/test_pulse.py | 28 ++- 9 files changed, 546 insertions(+), 20 deletions(-) create mode 100644 src/braket/experimental/autoqasm/program/gate_calibrations.py create mode 100644 test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py diff --git a/src/braket/experimental/autoqasm/__init__.py b/src/braket/experimental/autoqasm/__init__.py index 23098ace..1c9187b0 100644 --- a/src/braket/experimental/autoqasm/__init__.py +++ b/src/braket/experimental/autoqasm/__init__.py @@ -40,7 +40,7 @@ def my_program(): result[1] = measure __qubits__[1]; """ -from .api import gate, main, subroutine # noqa: F401 +from .api import gate, gate_calibration, main, subroutine # noqa: F401 from .instructions import QubitIdentifierType as Qubit # noqa: F401 from .program import Program, build_program, verbatim # noqa: F401 from .transpiler import transpiler # noqa: F401 diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index beb04ef6..11c11b9d 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -35,6 +35,9 @@ is_autograph_artifact, ) from braket.experimental.autoqasm.autograph.tf_utils import tf_decorator +from braket.experimental.autoqasm.instructions.qubits import QubitIdentifierType as Qubit +from braket.experimental.autoqasm.instructions.qubits import is_qubit_identifier_type +from braket.experimental.autoqasm.program.gate_calibrations import GateCalibration def main( @@ -87,6 +90,25 @@ def gate(*args) -> Callable[[Any], None]: return _function_wrapper(args, _convert_gate) +def gate_calibration(*args, implements: Callable, **kwargs) -> Callable[[], GateCalibration]: + """A decorator that register the decorated function as a gate calibration definition. The + decorated function is added to a main program using `with_calibrations` method of the main + program. The fixed values of qubits or angles that the calibration is implemented against + are supplied as kwargs. The name of the kwargs must match the args of the gate function it + implements. + + Args: + implements (Callable): Gate function. + + Returns: + Callable[[], GateCalibration]: A callable to be added to a main program using + `with_calibrations` method of the main program. + """ + converter_args = {"gate_function": implements, **kwargs} + + return _function_wrapper(args, _convert_calibration, converter_args) + + def _function_wrapper( args: Tuple[Any], converter_callback: Callable, @@ -430,10 +452,11 @@ def _convert_gate( program_conversion_context = aq_program.get_program_conversion_context() # Wrap the function into an oqpy gate definition - wrapped_f, gate_args = _wrap_for_oqpy_gate(f, options) + wrapped_f = _wrap_for_oqpy_gate(f, options) gate_name = f.__name__ # Validate that the gate definition acts on at least one qubit + gate_args = _get_gate_args(f) if not gate_args.qubits: raise errors.ParameterTypeError( f'Gate definition "{gate_name}" has no arguments of type aq.Qubit. ' @@ -468,7 +491,33 @@ def _convert_gate( def _wrap_for_oqpy_gate( f: Callable, options: converter.ConversionOptions, -) -> Tuple[Callable[..., None], aq_program.GateArgs]: +) -> Callable[..., None]: + """Wraps the given function into a callable expected by oqpy.gate. + + Args: + f (Callable): The function to be wrapped. + options (converter.ConversionOptions): Converter options. + + Returns: + Callable[...,]: The modified function for use with oqpy.gate. + """ + + def _func(*args: Any) -> None: + aq_transpiler.converted_call(f, *args, kwargs={}, options=options) + + return _func + + +def _get_gate_args(f: Callable) -> aq_program.GateArgs: + """Build a GateArgs object from the function signature of a gate. + + Args: + f (Callable): Gate function + + Returns: + aq_program.GateArgs: Object representing a list of qubit and angle arguments for + a gate definition. + """ gate_args = aq_program.GateArgs() sig = inspect.signature(f) for param in sig.parameters.values(): @@ -487,8 +536,101 @@ def _wrap_for_oqpy_gate( f'Parameter "{param.name}" for gate "{f.__name__}" ' "must have a type hint of either aq.Qubit or float." ) + return gate_args - def _func(*args: Any) -> None: - aq_transpiler.converted_call(f, *args, kwargs={}, options=options) - return _func, gate_args +def _convert_calibration( + f: Callable, + options: converter.ConversionOptions, + args: List[Any], + kwargs: Dict[str, Any], + gate_function: Callable, + **decorator_kwargs, +) -> GateCalibration: + """Convert the initial callable `f` into a GateCalibration object that will be added to + the main program as defcal. + + Args: + f (Callable): The function to be converted. + options (converter.ConversionOptions): Converter options. + args (List[Any]): Arguments passed to the program when called. + kwargs (Dict[str, Any]): Keyword arguments passed to the program when called. + gate_function (Callable): The gate function which calibration is being defined. + + Returns: + GateCalibration: Object representing the calibration definition. + """ + func_args = _get_gate_args(f) + _validate_calibration_args(gate_function, decorator_kwargs, func_args) + + union_deco_func_args = {**decorator_kwargs, **{var.name: var for var in func_args._args}} + + gate_calibration_qubits = [] + gate_calibration_angles = [] + gate_args = _get_gate_args(gate_function) + for i, var in enumerate(gate_args._args): + name = var.name + value = union_deco_func_args[name] + is_qubit = i in gate_args.qubit_indices + + if is_qubit and not is_qubit_identifier_type(value): + raise errors.ParameterTypeError(f'Parameter "{name}" must have a type of aq.Qubit.') + + if not is_qubit and not isinstance(value, (float, oqpy.AngleVar)): + raise errors.ParameterTypeError(f'Parameter "{name}" must have a type of float.') + + if is_qubit: + gate_calibration_qubits.append(value) + else: + gate_calibration_angles.append(value) + + func_call_kwargs = { + **{var.name: var for var in func_args.qubits}, + **{ + var.name: oqpy.FloatVar(name=var.name, needs_declaration=False) + for var in func_args.angles + }, + } + + with aq_program.build_program() as program_conversion_context: + with program_conversion_context.calibration_definition( + gate_function.__name__, gate_calibration_qubits, gate_calibration_angles + ): + aq_transpiler.converted_call(f, [], func_call_kwargs, options=options) + + return GateCalibration( + gate_function=gate_function, + qubits=gate_calibration_qubits, + angles=gate_calibration_angles, + program=program_conversion_context.make_program(), + ) + + +def _validate_calibration_args( + gate_function: Callable, + decorator_args: Dict[str, Union[Qubit, float]], + func_args: aq_program.GateArgs, +) -> None: + """Validate the arguments passed to the calibration decorator and function. + + Args: + gate_function (Callable): The gate function which calibration is being defined. + decorator_args (Dict[str, Union[Qubit, float]]): The calibration decorator arguments. + func_args (aq_program.GateArgs): The gate function arguments. + """ + gate_args = _get_gate_args(gate_function) + gate_args_names = [var.name for var in gate_args._args] + func_args_names = [var.name for var in func_args._args] + decorator_args_names = decorator_args.keys() + + # validate the name of args + if not set(gate_args_names) == set(decorator_args_names) | set(func_args_names): + raise errors.InvalidCalibrationDefinition( + "The union of calibration decorator arguments and function arguments must match the" + " gate arguments." + ) + + if any(name in decorator_args_names for name in func_args_names): + raise errors.InvalidCalibrationDefinition( + "The function arguments must not duplicate any argument in the calibration decorator." + ) diff --git a/src/braket/experimental/autoqasm/errors.py b/src/braket/experimental/autoqasm/errors.py index 74a0db99..5ae50919 100644 --- a/src/braket/experimental/autoqasm/errors.py +++ b/src/braket/experimental/autoqasm/errors.py @@ -41,6 +41,10 @@ class InvalidGateDefinition(AutoQasmError): """Gate definition does not meet the necessary requirements.""" +class InvalidCalibrationDefinition(AutoQasmError): + """Calibration definition does not meet the necessary requirements.""" + + class UnknownQubitCountError(AutoQasmError): """Missing declaration for the number of qubits.""" diff --git a/src/braket/experimental/autoqasm/instructions/qubits.py b/src/braket/experimental/autoqasm/instructions/qubits.py index 099f57dc..a8def724 100644 --- a/src/braket/experimental/autoqasm/instructions/qubits.py +++ b/src/braket/experimental/autoqasm/instructions/qubits.py @@ -22,7 +22,7 @@ from braket.experimental.autoqasm import constants, errors, program -QubitIdentifierType = Union[int, oqpy._ClassicalVar, oqpy.base.OQPyExpression, str] +QubitIdentifierType = Union[int, oqpy._ClassicalVar, oqpy.base.OQPyExpression, str, oqpy.Qubit] def is_qubit_identifier_type(qubit: Any) -> bool: diff --git a/src/braket/experimental/autoqasm/program/gate_calibrations.py b/src/braket/experimental/autoqasm/program/gate_calibrations.py new file mode 100644 index 00000000..ad76dcd5 --- /dev/null +++ b/src/braket/experimental/autoqasm/program/gate_calibrations.py @@ -0,0 +1,43 @@ +# 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 Callable, Iterable + +from braket.experimental.autoqasm.instructions.qubits import QubitIdentifierType as Qubit +from braket.experimental.autoqasm.program import Program + + +class GateCalibration: + def __init__( + self, + gate_function: Callable, + qubits: Iterable[Qubit], + angles: Iterable[float], + program: Program, + ): + """Definition of a gate calibration, including pulse instructions and the qubits, angles + and the gate it implements. + + Args: + gate_function (Callable): The gate function which calibration is defined. + qubits (Iterable[Qubit]): The qubits on which the gate calibration is defined. + angles (Iterable[float]): The angles at which the gate calibration is defined. + program (Program): Calibration instructions as an AutoQASM program. + """ + self.gate_function = gate_function + self.qubits = qubits + self.angles = angles + self.program = program diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 3569c554..1897f450 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -12,18 +12,21 @@ # language governing permissions and limitations under the License. """AutoQASM Program class, context managers, and related functions.""" +from __future__ import annotations import contextlib import threading from dataclasses import dataclass from enum import Enum -from typing import Any, List, Optional, Union +from typing import Any, Callable, Iterable, List, Optional, Union import oqpy.base from braket.aws import AwsDevice from braket.circuits.serialization import IRType, SerializableProgram from braket.experimental.autoqasm import constants, errors +from braket.experimental.autoqasm.instructions.qubits import QubitIdentifierType as Qubit +from braket.experimental.autoqasm.instructions.qubits import _qubit # Create the thread-local object for the program conversion context. _local = threading.local() @@ -66,6 +69,8 @@ class ProgramMode(Enum): """For general program conversion where all operations are allowed.""" UNITARY = 1 """For program conversion inside a context where only unitary operations are allowed.""" + PULSE = 2 + """For program conversion inside a context where only pulse operations are allowed.""" class Program(SerializableProgram): @@ -84,6 +89,27 @@ def __init__(self, oqpy_program: oqpy.Program, has_pulse_control: bool = False): self._oqpy_program = oqpy_program self._has_pulse_control = has_pulse_control + def with_calibrations(self, gate_calibrations: Union[Callable, List[Callable]]) -> Program: + """Add the gate calibrations to the program. The calibration added program is returned + as a new object. The original program is not modified. + + Args: + gate_calibrations (Union[Callable, List[Callable]]): The gate calibrations to add to + the main program. Calibration are passed as callable without evaluation. + + Returns: + Program: The program with gate calibrations added. + """ + if isinstance(gate_calibrations, Callable): + gate_calibrations = [gate_calibrations] + assert all(isinstance(gc, Callable) for gc in gate_calibrations) + + combined_oqpy_program = oqpy.Program() + for gc in gate_calibrations: + combined_oqpy_program += gc().program._oqpy_program + combined_oqpy_program += self._oqpy_program + return Program(combined_oqpy_program, self._has_pulse_control) + def to_ir( self, ir_type: IRType = IRType.OPENQASM, @@ -153,6 +179,7 @@ def __init__(self, user_config: Optional[UserConfig] = None): self.return_variable = None self._oqpy_program_stack = [oqpy.Program()] self._gate_definitions_processing = [] + self._calibration_definitions_processing = [] self._qubits_seen = set() self._var_idx = 0 self._has_pulse_control = False @@ -279,6 +306,9 @@ def get_oqpy_program( errors.InvalidGateDefinition: If this function is called from within a gate definition where only unitary gate operations are allowed, and the `mode` parameter is not specified as `ProgramMode.UNITARY`. + errors.InvalidCalibrationDefinition: If this function is called from within a + calibration definition where only pulse operations are allowed, and the + `mode` parameter is not specified as `ProgramMode.PULSE`. Returns: oqpy.Program: The requested oqpy program. @@ -289,6 +319,12 @@ def get_oqpy_program( f'Gate definition "{gate_name}" contains invalid operations. ' "A gate definition must only call unitary gate operations." ) + if self._calibration_definitions_processing and mode != ProgramMode.PULSE: + gate_name = self._calibration_definitions_processing[-1]["name"] + raise errors.InvalidCalibrationDefinition( + f'Calibration definition "{gate_name}" contains invalid operations. ' + "A calibration definition must only call pulse operations." + ) if scope == ProgramScope.CURRENT: requested_index = -1 @@ -332,6 +368,32 @@ def gate_definition(self, gate_name: str, gate_args: GateArgs) -> None: finally: self._gate_definitions_processing.pop() + @contextlib.contextmanager + def calibration_definition( + self, gate_name: str, qubits: Iterable[Qubit], angles: Iterable[float] + ) -> None: + """Sets the program conversion context into a calibration definition context. + + Args: + gate_name (str): The name of the gate being defined. + qubits (Iterable[Qubit]): The list of qubits to the gate. + angles (Iterable[float]): The angles at which the gate calibration is defined. + """ + try: + qubits = [_qubit(q) for q in qubits] + self._calibration_definitions_processing.append( + {"name": gate_name, "qubits": qubits, "angles": angles} + ) + with oqpy.defcal( + self.get_oqpy_program(mode=ProgramMode.PULSE), + qubits, + gate_name, + angles, + ): + yield + finally: + self._calibration_definitions_processing.pop() + @contextlib.contextmanager def build_program(user_config: Optional[UserConfig] = None) -> None: diff --git a/src/braket/experimental/autoqasm/pulse/pulse.py b/src/braket/experimental/autoqasm/pulse/pulse.py index c27da3c1..90dff13f 100644 --- a/src/braket/experimental/autoqasm/pulse/pulse.py +++ b/src/braket/experimental/autoqasm/pulse/pulse.py @@ -15,12 +15,13 @@ """Pulse instructions that apply to frames or qubits. """ +import re from typing import List, Union import oqpy from braket.circuits.qubit_set import QubitSet -from braket.experimental.autoqasm import program +from braket.experimental.autoqasm import program as aq_program from braket.experimental.autoqasm.instructions.qubits import ( QubitIdentifierType, is_qubit_identifier_type, @@ -37,13 +38,39 @@ def _pulse_instruction(name: str, frame: Frame, *args) -> None: name (str): Name of the pulse instruction. frame (Frame): Frame for which the instruction is apply to. """ - program_conversion_context = program.get_program_conversion_context() + program_conversion_context = aq_program.get_program_conversion_context() program_conversion_context._has_pulse_control = True pulse_sequence = PulseSequence() - pulse_sequence._program = program_conversion_context.get_oqpy_program() - with oqpy.Cal(pulse_sequence._program): + pulse_sequence._program = program_conversion_context.get_oqpy_program( + mode=aq_program.ProgramMode.PULSE + ) + + if program_conversion_context._calibration_definitions_processing: getattr(pulse_sequence, name)(frame, *args) + else: + with oqpy.Cal(pulse_sequence._program): + getattr(pulse_sequence, name)(frame, *args) + + +def _physical_qubit_to_braket_qubit(qids: List[str]) -> QubitSet: + """Convert a physical qubit label to a QubitSet. + + Args: + qids (List[str]): Physical qubit labels. + + Returns: + QubitSet: Represent physical qubits. + """ + braket_qubits = [] + for qid in qids: + if not (isinstance(qid, str) and re.match(r"\$\d+", qid)): + raise ValueError( + f"invalid physical qubit label: '{qid}'. Physical qubit must be labeled as a string" + "with `$` followed by an integer. For example: `$1`." + ) + braket_qubits.append(int(qid[1:])) + return QubitSet(braket_qubits) def set_frequency(frame: Frame, frequency: float) -> None: @@ -130,7 +157,7 @@ def delay( if not isinstance(qubits_or_frames, List): qubits_or_frames = [qubits_or_frames] if all(is_qubit_identifier_type(q) for q in qubits_or_frames): - qubits_or_frames = QubitSet(qubits_or_frames) + qubits_or_frames = _physical_qubit_to_braket_qubit(qubits_or_frames) _pulse_instruction("delay", qubits_or_frames, duration) @@ -148,5 +175,5 @@ def barrier( if not isinstance(qubits_or_frames, List): qubits_or_frames = [qubits_or_frames] if all(is_qubit_identifier_type(q) for q in qubits_or_frames): - qubits_or_frames = QubitSet(qubits_or_frames) + qubits_or_frames = _physical_qubit_to_braket_qubit(qubits_or_frames) _pulse_instruction("barrier", qubits_or_frames) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py new file mode 100644 index 00000000..db2e6e5c --- /dev/null +++ b/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py @@ -0,0 +1,232 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +"""Tests for the pulse control module.""" + +import textwrap + +import pytest + +import braket.experimental.autoqasm as aq +from braket.experimental.autoqasm import errors, pulse +from braket.experimental.autoqasm.instructions import h, rx + + +def test_gate_calibrations_fixed_args(): + """test gate calibrations with fixed args""" + + @aq.gate_calibration(implements=h, target="$0") + def cal_1(): + pulse.barrier("$0") + + @aq.gate_calibration(implements=rx, target="$1", angle=1.789) + def cal_2(): + pulse.delay("$1", 0.123) + + @aq.main + def my_program(): + h("$0") + rx("$1", 1.0) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + defcal h $0 { + barrier $0; + } + defcal rx(1.789) $1 { + delay[123.0ms] $1; + } + h $0; + rx(1.0) $1; + """ + ).strip() + qasm = my_program().with_calibrations([cal_1, cal_2]).to_ir() + assert qasm == expected + + +def test_gate_calibrations_variable_args(): + """test gate calibrations with variable args""" + + @aq.gate_calibration(implements=rx, target="$1") + def cal_1(angle: float): + pulse.delay("$1", angle) + + @aq.main + def my_program(): + rx("$1", 1.0) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + defcal rx(angle[32] angle) $1 { + delay[angle * 1s] $1; + } + rx(1.0) $1; + """ + ).strip() + qasm = my_program().with_calibrations(cal_1).to_ir() + assert qasm == expected + + +def test_gate_calibrations_invalid_args(): + """test gate calibrations with invalid args name""" + + @aq.gate_calibration(implements=rx, target="$1", foo=0) + def cal_1(angle: float): + pulse.delay("$1", angle) + + @aq.main + def my_program(): + rx("$1", 1.0) + + with pytest.raises(errors.InvalidCalibrationDefinition): + _ = my_program().with_calibrations(cal_1) + + +def test_gate_calibrations_invalid_type(): + """test gate calibrations with invalid args type""" + + @aq.gate_calibration(implements=rx, target=0.123) + def cal_1(angle: float): + pulse.delay("$1", angle) + + @aq.gate_calibration(implements=rx, target={"foo": "bar"}) + def cal_2(angle: float): + pulse.delay("$1", angle) + + @aq.gate_calibration(implements=rx, target=0, angle="$0") + def cal_3(): + pulse.delay("$1", 0.123) + + @aq.gate_calibration(implements=rx, target=0) + def cal_4(angle: aq.Qubit): + pulse.delay("$1", angle) + + @aq.gate_calibration(implements=rx) + def cal_5(target: float, angle: aq.Qubit): + pulse.delay("$0", angle) + + @aq.main + def my_program(): + rx("$1", 1.0) + + for cal in [cal_1, cal_2, cal_3, cal_4, cal_5]: + with pytest.raises(errors.ParameterTypeError): + _ = my_program().with_calibrations(cal) + + +def test_gate_calibrations_insufficient_args(): + """test gate calibrations with insufficient args""" + + @aq.gate_calibration(implements=rx, target="$1") + def cal_1(): + pulse.delay("$1", 0.123) + + @aq.gate_calibration(implements=rx) + def cal_2(angle: float): + pulse.delay("$1", angle) + + @aq.main + def my_program(): + rx("$1", 1.0) + + with pytest.raises(errors.InvalidCalibrationDefinition): + _ = my_program().with_calibrations(cal_1) + + with pytest.raises(errors.InvalidCalibrationDefinition): + _ = my_program().with_calibrations(cal_2) + + +def test_gate_calibrations_duplicated_args(): + """test gate calibrations with duplicated args""" + + @aq.gate_calibration(implements=rx, target="$1", angle=0.123) + def cal_1(angle: float): + pulse.delay("$1", angle) + + @aq.main + def my_program(): + rx("$1", 1.0) + + with pytest.raises(errors.InvalidCalibrationDefinition): + _ = my_program().with_calibrations(cal_1) + + +def test_gate_calibrations_invalid_instructions(): + """test gate calibrations with invalid instructions that are not pulse""" + + @aq.gate_calibration(implements=rx, target="$1") + def cal_1(angle: float): + h(0) + pulse.delay("$1", angle) + + @aq.main + def my_program(): + rx("$1", 1.0) + + with pytest.raises(errors.InvalidCalibrationDefinition): + _ = my_program().with_calibrations(cal_1) + + +def test_gate_calibrations_bind_calibrations_not_inplace(): + """test that bind_calibrations does not modify the original program""" + + @aq.gate_calibration(implements=rx, target="$1") + def cal_1(angle: float): + pulse.delay("$1", angle) + + @aq.main + def my_program(): + rx("$1", 1.0) + + program_1 = my_program() + _ = program_1.with_calibrations(cal_1) + + program_2 = my_program() + + assert program_1.to_ir() == program_2.to_ir() + + +def test_gate_calibrations_with_gate_definition(): + """test gate calibrations on gate defined by aq.gate""" + + @aq.gate + def my_gate(q: aq.Qubit, a: float): + h(q) + + @aq.gate_calibration(implements=my_gate, q="$0") + def cal_1(a: float): + pulse.barrier("$0") + pulse.delay("$0", a) + + @aq.main + def my_program(): + my_gate(2, 0.123) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + gate my_gate(a) q { + h q; + } + defcal my_gate(angle[32] a) $0 { + barrier $0; + delay[a * 1s] $0; + } + qubit[3] __qubits__; + my_gate(0.123) __qubits__[2]; + """ + ).strip() + qasm = my_program().with_calibrations(cal_1).to_ir() + assert qasm == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pulse.py b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py index 9e527acc..58840ce6 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_pulse.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py @@ -73,8 +73,8 @@ def test_merge_cal_box() -> None: @aq.main def my_program(): - barrier(0) - delay([3, 4], 0.34) + barrier("$0") + delay(["$3", "$4"], 0.34) expected = textwrap.dedent( """ @@ -123,8 +123,8 @@ def my_program(): [0.12], "\ncal {\n set_scale(predefined_frame_1, 0.12);\n}", ), - (delay, 3, [0.34], "\ncal {\n delay[340.0ms] $3;\n}"), - (delay, [3, 4], [0.34], "\ncal {\n delay[340.0ms] $3, $4;\n}"), + (delay, "$3", [0.34], "\ncal {\n delay[340.0ms] $3;\n}"), + (delay, ["$3", "$4"], [0.34], "\ncal {\n delay[340.0ms] $3, $4;\n}"), ( delay, FRAME1, @@ -137,8 +137,8 @@ def my_program(): [0.34], "\ncal {\n delay[340.0ms] predefined_frame_1, predefined_frame_2;\n}", ), - (barrier, 3, [], "\ncal {\n barrier $3;\n}"), - (barrier, [3, 4], [], "\ncal {\n barrier $3, $4;\n}"), + (barrier, "$3", [], "\ncal {\n barrier $3;\n}"), + (barrier, ["$3", "$4"], [], "\ncal {\n barrier $3, $4;\n}"), (barrier, FRAME1, [], "\ncal {\n barrier predefined_frame_1;\n}"), ( barrier, @@ -164,3 +164,19 @@ def test_pulse_control(instruction, qubits_or_frames, params, expected_qasm) -> instruction(qubits_or_frames, *params) assert expected_qasm in program_conversion_context.make_program().to_ir() + + +@pytest.mark.parametrize( + "instruction,qubits_or_frames,params", + [ + (barrier, "1", []), + (barrier, 1, []), + (barrier, ["1", "2"], []), + (barrier, [1, 2], []), + ], +) +def test_pulse_control_invalid_physical_qubit(instruction, qubits_or_frames, params) -> None: + """Test pulse control operations with invalid lables for physical qubits.""" + with pytest.raises(ValueError): + with aq.build_program(): + instruction(qubits_or_frames, *params) From 202a8178aa0511d3cb4b43fb5a22262583a222b3 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Wed, 13 Sep 2023 07:50:55 -0400 Subject: [PATCH 0846/1165] feature: device-specific gate validation for autoqasm programs (#695) --- .github/workflows/check-format.yml | 2 +- examples/autoqasm/4_Native_programming.ipynb | 489 ++++++++++++++++++ examples/autoqasm/ionq_gates.py | 36 ++ src/braket/experimental/autoqasm/api.py | 37 +- src/braket/experimental/autoqasm/errors.py | 16 + .../autoqasm/instructions/instructions.py | 3 +- .../autoqasm/instructions/qubits.py | 23 +- .../experimental/autoqasm/program/__init__.py | 2 +- .../experimental/autoqasm/program/pragmas.py | 50 +- .../experimental/autoqasm/program/program.py | 110 +++- .../experimental/autoqasm/pulse/pulse.py | 26 +- .../experimental/autoqasm/test_devices.py | 131 ++++- .../experimental/autoqasm/test_pragmas.py | 44 +- tox.ini | 1 + 14 files changed, 894 insertions(+), 76 deletions(-) create mode 100644 examples/autoqasm/4_Native_programming.ipynb create mode 100644 examples/autoqasm/ionq_gates.py diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index 491a5bd7..ed12dd33 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -28,4 +28,4 @@ jobs: - name: Run code format checks run: | black --check . --extend-exclude=src/braket/experimental/autoqasm/autograph - flake8 --enable-extensions=BCS src/braket/experimental/autoqasm + flake8 --enable-extensions=BCS src diff --git a/examples/autoqasm/4_Native_programming.ipynb b/examples/autoqasm/4_Native_programming.ipynb new file mode 100644 index 00000000..5d779f25 --- /dev/null +++ b/examples/autoqasm/4_Native_programming.ipynb @@ -0,0 +1,489 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5f4d0289", + "metadata": {}, + "source": [ + "# Native programming\n", + "\n", + "In order to execute a program on a quantum computer, each qubit in the program must be mapped to a physical qubit on the device, and each operation must be mapped to one or more \"native gates\", that is, gates that are natively implemented by the hardware. While this can be handled automatically by a compiler, a quantum software developer or researcher may want to be able to control these mappings explicitly. We refer to this low-level programming as \"native programming\".\n", + "\n", + "This notebook provides a demonstration of the native programming features of AutoQASM by targeting a simple two-qubit circuit to physical qubits and native gates of an IonQ quantum computer, which is available through Amazon Braket. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "954638fe", + "metadata": {}, + "outputs": [], + "source": [ + "# general imports\n", + "import numpy as np\n", + "import IPython\n", + "\n", + "# AWS imports: Import Braket SDK modules\n", + "from braket.devices import Devices\n", + "import braket.experimental.autoqasm as aq" + ] + }, + { + "cell_type": "markdown", + "id": "5dd374f2", + "metadata": {}, + "source": [ + "The circuit we will use for this demonstration is a program which creates and measures a Bell state on two qubits. Here, we write this program at the typical level of abstraction, which is hardware-agnostic. We use integers `0` and `1` to specify qubit indices, and we use the built-in `h` and `cnot` instructions from the AutoQASM `instructions` module." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ddf6cca5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "qubit[2] __qubits__;\n", + "h __qubits__[0];\n", + "cnot __qubits__[0], __qubits__[1];\n", + "bit[2] __bit_0__ = \"00\";\n", + "__bit_0__[0] = measure __qubits__[0];\n", + "__bit_0__[1] = measure __qubits__[1];\n" + ] + } + ], + "source": [ + "from braket.experimental.autoqasm.instructions import h, cnot, measure\n", + "\n", + "\n", + "@aq.main\n", + "def bell_state():\n", + " h(0)\n", + " cnot(0, 1)\n", + " measure([0, 1])\n", + "\n", + "\n", + "bell_state_program = bell_state()\n", + "print(bell_state_program.to_ir())" + ] + }, + { + "cell_type": "markdown", + "id": "9ff3d255", + "metadata": {}, + "source": [ + "As seen in the generated OpenQASM program, this produces a program that uses a two-qubit register `__qubits__` and the built-in `h` and `cnot` gates. At runtime, the compiler will automatically map this to two physical qubits, and will compile the `h` and `cnot` instructions to an equivalent sequence of gates which are native to the target device.\n", + "\n", + "In the native programming scenario, however, the developer wants full control over the physical qubit mappings and conversion to native gates. We can take advantage of two features of AutoQASM to enable this. First, we replace the integers `0` and `1`, which specify virtual qubit indices, with the strings `\"$0\"` and `\"$1\"`, which specify physical qubits. Second, we wrap the gates inside a `verbatim` block (using the `aq.verbatim()` context), which instructs the compiler to avoid modifying anything inside the block." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "940d4b22", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "pragma braket verbatim\n", + "box {\n", + " h $0;\n", + " cnot $0, $1;\n", + "}\n", + "bit[2] __bit_0__ = \"00\";\n", + "__bit_0__[0] = measure $0;\n", + "__bit_0__[1] = measure $1;\n" + ] + } + ], + "source": [ + "@aq.main\n", + "def bell_state():\n", + " with aq.verbatim():\n", + " h(\"$0\")\n", + " cnot(\"$0\", \"$1\")\n", + " measure([\"$0\", \"$1\"])\n", + "\n", + "\n", + "bell_state_program = bell_state()\n", + "print(bell_state_program.to_ir())" + ] + }, + { + "cell_type": "markdown", + "id": "a9182030", + "metadata": {}, + "source": [ + "This program now targets physical qubits, and the gates will not be modified by the compiler.\n", + "\n", + "## Device-specific validation\n", + "\n", + "Bypassing the mapping and compilation is only the first step of native programming. Because native programming is intended for targeting a program to a specific device, we need to specify the target device in the AutoQASM program. We can accomplish this by adding a `device` argument to the `@aq.main` decorator, passing the ARN of the Amazon Braket device (or, optionally, a `braket.devices.device.Device` object) that we want to target.\n", + "\n", + "Here we modify the program to target the `Devices.IonQ.Aria1` device. When building this program, AutoQASM will validate that (among other things) the contents of any `verbatim` blocks respect the native gate set and connectivity of the target device. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "72cde00f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ERROR: The gate \"h\" is not a native gate of the target device \"Aria 1\". Only native gates may be used inside a verbatim block. The native gates of the device are: ['gpi', 'gpi2', 'ms']\n" + ] + } + ], + "source": [ + "@aq.main(device=Devices.IonQ.Aria1)\n", + "def bell_state():\n", + " with aq.verbatim():\n", + " h(\"$0\")\n", + " cnot(\"$0\", \"$1\")\n", + " measure([\"$0\", \"$1\"])\n", + "\n", + "\n", + "try:\n", + " bell_state_program = bell_state()\n", + "except Exception as e:\n", + " print(\"ERROR:\", e)" + ] + }, + { + "cell_type": "markdown", + "id": "3ef34c2d", + "metadata": {}, + "source": [ + "The validation error indicates that we cannot use `h` and `cnot` inside a `verbatim` block for this device. Instead, we must express our program in terms of the native gates of the device: `gpi`, `gpi2`, and `ms`.\n", + "\n", + "## Custom gate definitions using `@aq.gate`\n", + "\n", + "In order to do this, we can use the `@aq.gate` decorator in AutoQASM to define custom gates, which we implement in terms of this native gate set. In the Python script `ionq_gates.py`, we define custom implementations of the `h` and `cnot` gates which are built on top of the `gpi`, `gpi2`, and `ms` gates." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "518b9d23", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "
import numpy as np\n",
+       "import braket.experimental.autoqasm as aq\n",
+       "from braket.experimental.autoqasm.instructions import gpi, gpi2, ms\n",
+       "\n",
+       "\n",
+       "@aq.gate\n",
+       "def h(q: aq.Qubit):\n",
+       "    gpi2(q, np.pi / 2)\n",
+       "    gpi(q, 0)\n",
+       "\n",
+       "\n",
+       "@aq.gate\n",
+       "def u(q: aq.Qubit, a: float, b: float, c: float):\n",
+       "    gpi2(q, a)\n",
+       "    gpi(q, b)\n",
+       "    gpi2(q, c)\n",
+       "\n",
+       "\n",
+       "@aq.gate\n",
+       "def rx(q: aq.Qubit, theta: float):\n",
+       "    u(q, np.pi / 2, theta / 2 + np.pi / 2, np.pi / 2)\n",
+       "\n",
+       "\n",
+       "@aq.gate\n",
+       "def ry(q: aq.Qubit, theta: float):\n",
+       "    u(q, np.pi, theta / 2 + np.pi, np.pi)\n",
+       "\n",
+       "\n",
+       "@aq.gate\n",
+       "def cnot(q0: aq.Qubit, q1: aq.Qubit):\n",
+       "    ry(q0, np.pi / 2)\n",
+       "    ms(q0, q1, 0, 0, np.pi / 2)\n",
+       "    rx(q0, -np.pi / 2)\n",
+       "    rx(q1, -np.pi / 2)\n",
+       "    ry(q0, -np.pi / 2)\n",
+       "
\n" + ], + "text/latex": [ + "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", + "\\PY{k+kn}{import} \\PY{n+nn}{numpy} \\PY{k}{as} \\PY{n+nn}{np}\n", + "\\PY{k+kn}{import} \\PY{n+nn}{braket}\\PY{n+nn}{.}\\PY{n+nn}{experimental}\\PY{n+nn}{.}\\PY{n+nn}{autoqasm} \\PY{k}{as} \\PY{n+nn}{aq}\n", + "\\PY{k+kn}{from} \\PY{n+nn}{braket}\\PY{n+nn}{.}\\PY{n+nn}{experimental}\\PY{n+nn}{.}\\PY{n+nn}{autoqasm}\\PY{n+nn}{.}\\PY{n+nn}{instructions} \\PY{k+kn}{import} \\PY{n}{gpi}\\PY{p}{,} \\PY{n}{gpi2}\\PY{p}{,} \\PY{n}{ms}\n", + "\n", + "\n", + "\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n", + "\\PY{k}{def} \\PY{n+nf}{h}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{)}\\PY{p}{:}\n", + " \\PY{n}{gpi2}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", + " \\PY{n}{gpi}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{l+m+mi}{0}\\PY{p}{)}\n", + "\n", + "\n", + "\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n", + "\\PY{k}{def} \\PY{n+nf}{u}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{a}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{,} \\PY{n}{b}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{,} \\PY{n}{c}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{)}\\PY{p}{:}\n", + " \\PY{n}{gpi2}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{a}\\PY{p}{)}\n", + " \\PY{n}{gpi}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{b}\\PY{p}{)}\n", + " \\PY{n}{gpi2}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{c}\\PY{p}{)}\n", + "\n", + "\n", + "\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n", + "\\PY{k}{def} \\PY{n+nf}{rx}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{theta}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{)}\\PY{p}{:}\n", + " \\PY{n}{u}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{,} \\PY{n}{theta} \\PY{o}{/} \\PY{l+m+mi}{2} \\PY{o}{+} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", + "\n", + "\n", + "\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n", + "\\PY{k}{def} \\PY{n+nf}{ry}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{theta}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{)}\\PY{p}{:}\n", + " \\PY{n}{u}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi}\\PY{p}{,} \\PY{n}{theta} \\PY{o}{/} \\PY{l+m+mi}{2} \\PY{o}{+} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi}\\PY{p}{)}\n", + "\n", + "\n", + "\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n", + "\\PY{k}{def} \\PY{n+nf}{cnot}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{q1}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{)}\\PY{p}{:}\n", + " \\PY{n}{ry}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", + " \\PY{n}{ms}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{n}{q1}\\PY{p}{,} \\PY{l+m+mi}{0}\\PY{p}{,} \\PY{l+m+mi}{0}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", + " \\PY{n}{rx}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{o}{\\PYZhy{}}\\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", + " \\PY{n}{rx}\\PY{p}{(}\\PY{n}{q1}\\PY{p}{,} \\PY{o}{\\PYZhy{}}\\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", + " \\PY{n}{ry}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{o}{\\PYZhy{}}\\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", + "\\end{Verbatim}\n" + ], + "text/plain": [ + "import numpy as np\n", + "import braket.experimental.autoqasm as aq\n", + "from braket.experimental.autoqasm.instructions import gpi, gpi2, ms\n", + "\n", + "\n", + "@aq.gate\n", + "def h(q: aq.Qubit):\n", + " gpi2(q, np.pi / 2)\n", + " gpi(q, 0)\n", + "\n", + "\n", + "@aq.gate\n", + "def u(q: aq.Qubit, a: float, b: float, c: float):\n", + " gpi2(q, a)\n", + " gpi(q, b)\n", + " gpi2(q, c)\n", + "\n", + "\n", + "@aq.gate\n", + "def rx(q: aq.Qubit, theta: float):\n", + " u(q, np.pi / 2, theta / 2 + np.pi / 2, np.pi / 2)\n", + "\n", + "\n", + "@aq.gate\n", + "def ry(q: aq.Qubit, theta: float):\n", + " u(q, np.pi, theta / 2 + np.pi, np.pi)\n", + "\n", + "\n", + "@aq.gate\n", + "def cnot(q0: aq.Qubit, q1: aq.Qubit):\n", + " ry(q0, np.pi / 2)\n", + " ms(q0, q1, 0, 0, np.pi / 2)\n", + " rx(q0, -np.pi / 2)\n", + " rx(q1, -np.pi / 2)\n", + " ry(q0, -np.pi / 2)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "IPython.display.Code(filename=\"ionq_gates.py\")" + ] + }, + { + "cell_type": "markdown", + "id": "5ef522c8", + "metadata": {}, + "source": [ + "We can now use these definitions of the `h` and `cnot` gates in our device-targeted program." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "366c4035", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "gate h q {\n", + " gpi2(pi / 2) q;\n", + " gpi(0) q;\n", + "}\n", + "gate u(a, b, c) q {\n", + " gpi2(a) q;\n", + " gpi(b) q;\n", + " gpi2(c) q;\n", + "}\n", + "gate ry(theta) q {\n", + " u(pi, theta / 2 + pi, pi) q;\n", + "}\n", + "gate rx(theta) q {\n", + " u(pi / 2, theta / 2 + pi / 2, pi / 2) q;\n", + "}\n", + "gate cnot q0, q1 {\n", + " ry(pi / 2) q0;\n", + " ms(0, 0, pi / 2) q0, q1;\n", + " rx(-(pi / 2)) q0;\n", + " rx(-(pi / 2)) q1;\n", + " ry(-(pi / 2)) q0;\n", + "}\n", + "pragma braket verbatim\n", + "box {\n", + " h $0;\n", + " cnot $0, $1;\n", + "}\n", + "bit[2] __bit_0__ = \"00\";\n", + "__bit_0__[0] = measure $0;\n", + "__bit_0__[1] = measure $1;\n" + ] + } + ], + "source": [ + "from ionq_gates import h, cnot\n", + "\n", + "\n", + "@aq.main(device=Devices.IonQ.Aria1)\n", + "def bell_state():\n", + " with aq.verbatim():\n", + " h(\"$0\")\n", + " cnot(\"$0\", \"$1\")\n", + " measure([\"$0\", \"$1\"])\n", + "\n", + "\n", + "bell_state_program = bell_state()\n", + "print(bell_state_program.to_ir())" + ] + }, + { + "cell_type": "markdown", + "id": "ba0be720", + "metadata": {}, + "source": [ + "The device-specific validation now passes, and the program is successfully built. We can see that the generated OpenQASM program contains `gate` definitions for `h`, `u`, `ry`, `rx`, and `cnot`, which correspond to the `@aq.gate` definitions in `ionq_gates.py`." + ] + }, + { + "cell_type": "markdown", + "id": "fa2c2c9e", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "In this notebook, we demonstrated several aspects of native programming using a two-qubit example program. We showed how to modify a program to use physical qubits instead of virtual qubits. We introduced the usage of `verbatim` blocks via the `aq.verbatim()` context, and we demonstrated the device-specific targeting functionality provided by AutoQASM. Finally, we demonstrated the definition of custom gates using the `@aq.gate` decorator, and we used these gate definitions to implement our example program purely in terms of the native gates of the target device." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/autoqasm/ionq_gates.py b/examples/autoqasm/ionq_gates.py new file mode 100644 index 00000000..a8e69c6b --- /dev/null +++ b/examples/autoqasm/ionq_gates.py @@ -0,0 +1,36 @@ +import numpy as np + +import braket.experimental.autoqasm as aq +from braket.experimental.autoqasm.instructions import gpi, gpi2, ms + + +@aq.gate +def h(q: aq.Qubit): + gpi2(q, np.pi / 2) + gpi(q, 0) + + +@aq.gate +def u(q: aq.Qubit, a: float, b: float, c: float): + gpi2(q, a) + gpi(q, b) + gpi2(q, c) + + +@aq.gate +def rx(q: aq.Qubit, theta: float): + u(q, np.pi / 2, theta / 2 + np.pi / 2, np.pi / 2) + + +@aq.gate +def ry(q: aq.Qubit, theta: float): + u(q, np.pi, theta / 2 + np.pi, np.pi) + + +@aq.gate +def cnot(q0: aq.Qubit, q1: aq.Qubit): + ry(q0, np.pi / 2) + ms(q0, q1, 0, 0, np.pi / 2) + rx(q0, -np.pi / 2) + rx(q1, -np.pi / 2) + ry(q0, -np.pi / 2) diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 11c11b9d..4a6fb064 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -28,6 +28,7 @@ import braket.experimental.autoqasm.transpiler as aq_transpiler import braket.experimental.autoqasm.types as aq_types from braket.aws import AwsDevice +from braket.devices.device import Device from braket.experimental.autoqasm import errors from braket.experimental.autoqasm.autograph.core import converter from braket.experimental.autoqasm.autograph.impl.api_core import ( @@ -41,7 +42,7 @@ def main( - *args, num_qubits: Optional[int] = None, device: Optional[Union[AwsDevice, str]] = None + *args, num_qubits: Optional[int] = None, device: Optional[Union[Device, str]] = None ) -> Callable[[Any], aq_program.Program]: """Decorator that converts a function into a callable that returns a Program object containing the quantum program. @@ -52,8 +53,8 @@ def main( Args: num_qubits (Optional[int]): Configuration to set the total number of qubits to declare in the program. - device (Optional[Union[AwsDevice, str]]): Configuration to set the target device for the - program. Can be either an AwsDevice object or a valid Amazon Braket device ARN. + device (Optional[Union[Device, str]]): Configuration to set the target device for the + program. Can be either an Device object or a valid Amazon Braket device ARN. Returns: Callable[[Any], Program]: A callable which returns the converted @@ -63,8 +64,8 @@ def main( device = AwsDevice(device) return _function_wrapper( - args, - _convert_main, + *args, + converter_callback=_convert_main, converter_args={"user_config": aq_program.UserConfig(num_qubits=num_qubits, device=device)}, ) @@ -77,7 +78,7 @@ def subroutine(*args) -> Callable[[Any], aq_program.Program]: Callable[[Any], Program]: A callable which returns the converted quantum program when called. """ - return _function_wrapper(args, _convert_subroutine) + return _function_wrapper(*args, converter_callback=_convert_subroutine) def gate(*args) -> Callable[[Any], None]: @@ -87,7 +88,7 @@ def gate(*args) -> Callable[[Any], None]: Callable[[Any],]: A callable which can be used as a custom gate inside an aq.function or inside another aq.gate. """ - return _function_wrapper(args, _convert_gate) + return _function_wrapper(*args, converter_callback=_convert_gate) def gate_calibration(*args, implements: Callable, **kwargs) -> Callable[[], GateCalibration]: @@ -104,20 +105,21 @@ def gate_calibration(*args, implements: Callable, **kwargs) -> Callable[[], Gate Callable[[], GateCalibration]: A callable to be added to a main program using `with_calibrations` method of the main program. """ - converter_args = {"gate_function": implements, **kwargs} - - return _function_wrapper(args, _convert_calibration, converter_args) + return _function_wrapper( + *args, + converter_callback=_convert_calibration, + converter_args={"gate_function": implements, **kwargs}, + ) def _function_wrapper( - args: Tuple[Any], + *args: Tuple[Any], converter_callback: Callable, converter_args: Optional[Dict[str, Any]] = None, ) -> Callable[[Any], aq_program.Program]: """Wrapping and conversion logic around the user function `f`. Args: - args (Tuple[Any]): The arguments to the decorated function. converter_callback (Callable): The function converter, e.g., _convert_main. converter_args (Optional[Dict[str, Any]]): Extra arguments for the function converter. @@ -129,12 +131,11 @@ def _function_wrapper( # This the case where a decorator is called with only keyword args, for example: # @aq.main(num_qubits=4) # def my_function(): - # To make this work, here we simply return another wrapper function which expects - # a Callable as the first argument. - def _function_wrapper_with_params(*args) -> Callable[[Any], aq_program.Program]: - return _function_wrapper(args, converter_callback, converter_args=converter_args) - - return _function_wrapper_with_params + # To make this work, here we simply return a partial application of this function + # which still expects a Callable as the first argument. + return functools.partial( + _function_wrapper, converter_callback=converter_callback, converter_args=converter_args + ) f = args[0] if is_autograph_artifact(f): diff --git a/src/braket/experimental/autoqasm/errors.py b/src/braket/experimental/autoqasm/errors.py index 5ae50919..9b7903a7 100644 --- a/src/braket/experimental/autoqasm/errors.py +++ b/src/braket/experimental/autoqasm/errors.py @@ -45,6 +45,22 @@ class InvalidCalibrationDefinition(AutoQasmError): """Calibration definition does not meet the necessary requirements.""" +class InvalidTargetQubit(AutoQasmError): + """Target qubit is invalid in the current context.""" + + +class UnsupportedGate(AutoQasmError): + """Gate is not supported by the target device.""" + + +class UnsupportedNativeGate(AutoQasmError): + """Native gate is not supported by the target device.""" + + +class VerbatimBlockNotAllowed(AutoQasmError): + """Verbatim block is not supported by the target device.""" + + class UnknownQubitCountError(AutoQasmError): """Missing declaration for the number of qubits.""" diff --git a/src/braket/experimental/autoqasm/instructions/instructions.py b/src/braket/experimental/autoqasm/instructions/instructions.py index 3da002c0..0e93f797 100644 --- a/src/braket/experimental/autoqasm/instructions/instructions.py +++ b/src/braket/experimental/autoqasm/instructions/instructions.py @@ -25,12 +25,11 @@ def _qubit_instruction( name: str, qubits: List[QubitIdentifierType], *args: Any, is_unitary: bool = True ) -> None: - # If this is an instruction inside a gate definition, ensure that it only operates on - # qubits and angles which are passed as arguments to the gate definition. program_conversion_context = aq_program.get_program_conversion_context() program_conversion_context.validate_gate_targets(qubits, args) # Add the instruction to the program. + program_conversion_context.register_gate(name) program_mode = aq_program.ProgramMode.UNITARY if is_unitary else aq_program.ProgramMode.NONE oqpy_program = program_conversion_context.get_oqpy_program(mode=program_mode) oqpy_program.gate([_qubit(q) for q in qubits], name, *args) diff --git a/src/braket/experimental/autoqasm/instructions/qubits.py b/src/braket/experimental/autoqasm/instructions/qubits.py index a8def724..78a7035c 100644 --- a/src/braket/experimental/autoqasm/instructions/qubits.py +++ b/src/braket/experimental/autoqasm/instructions/qubits.py @@ -14,8 +14,9 @@ """Utility functions that handle qubit construction and naming.""" +import re from functools import singledispatch -from typing import Any, Union +from typing import Any, List, Union import oqpy.base from openpulse.printer import dumps @@ -37,6 +38,26 @@ def is_qubit_identifier_type(qubit: Any) -> bool: return isinstance(qubit, QubitIdentifierType.__args__) +def _get_physical_qubit_indices(qids: List[str]) -> List[int]: + """Convert physical qubit labels to the corresponding qubit indices. + + Args: + qids (List[str]): Physical qubit labels. + + Returns: + List[int]: Qubit indices corresponding to the input physical qubits. + """ + braket_qubits = [] + for qid in qids: + if not (isinstance(qid, str) and re.match(r"\$\d+", qid)): + raise ValueError( + f"Invalid physical qubit label: '{qid}'. Physical qubit must be labeled as a string" + "with '$' followed by an integer. For example: '$1'." + ) + braket_qubits.append(int(qid[1:])) + return braket_qubits + + def _global_qubit_register(qubit_idx_expr: Union[int, str]) -> str: # TODO: We should index into a oqpy.QubitArray rather # than manually generating the string to index into diff --git a/src/braket/experimental/autoqasm/program/__init__.py b/src/braket/experimental/autoqasm/program/__init__.py index f0d376ad..672ddc06 100644 --- a/src/braket/experimental/autoqasm/program/__init__.py +++ b/src/braket/experimental/autoqasm/program/__init__.py @@ -15,7 +15,7 @@ for AutoQASM. """ -from .pragmas import Verbatim as verbatim # noqa: F401 +from .pragmas import verbatim # noqa: F401 from .program import ( # noqa: F401 GateArgs, Program, diff --git a/src/braket/experimental/autoqasm/program/pragmas.py b/src/braket/experimental/autoqasm/program/pragmas.py index 03825b5e..ee952f1c 100644 --- a/src/braket/experimental/autoqasm/program/pragmas.py +++ b/src/braket/experimental/autoqasm/program/pragmas.py @@ -20,7 +20,7 @@ @aq.main def pragma_example() -> None: - with aq.Verbatim(): + with aq.verbatim(): h(0) cnot(0, 1) x(0) @@ -29,24 +29,48 @@ def pragma_example() -> None: """ -import oqpy.base +import contextlib +from enum import Enum -from braket.experimental.autoqasm import program +from braket.device_schema import DeviceActionType +from braket.experimental.autoqasm import errors, program -class Verbatim: +class PragmaType(str, Enum): + """Values used in pragma statements.""" + + VERBATIM = "braket verbatim" + """Denotes a box as a verbatim block.""" + + +@contextlib.contextmanager +def verbatim() -> None: """Context management protocol that, when used with a `with` statement, wraps the code block - in a verbatim box. + in a verbatim block. - The verbatim pragma around a code block specifies that operations are to be executed as + A verbatim block specifies that operations contained within the block are to be executed as programmed without compilation or modification of any sort. + + Raises: + errors.VerbatimBlockNotAllowed: If a verbatim block is not allowed at this point in + the program; for example, if the target device does not support verbatim blocks. """ + program_conversion_context = program.get_program_conversion_context() + + if program_conversion_context.in_verbatim_block: + raise errors.VerbatimBlockNotAllowed("Verbatim blocks cannot be nested.") - def __enter__(self): - oqpy_program = program.get_program_conversion_context().get_oqpy_program() - self.box = oqpy.Box(oqpy_program) - oqpy_program.pragma("braket verbatim") - self.box.__enter__() + device = program_conversion_context.get_target_device() + if device: + supported_pragmas = device.properties.action[DeviceActionType.OPENQASM].supportedPragmas + if "verbatim" not in supported_pragmas: + raise errors.VerbatimBlockNotAllowed( + f'The target device "{device.name}" does not support verbatim blocks.' + ) - def __exit__(self, exc_type, exc, traceback): - return self.box.__exit__(exc_type, exc, traceback) + try: + with program.get_program_conversion_context().box(pragma=PragmaType.VERBATIM): + program_conversion_context.in_verbatim_block = True + yield + finally: + program_conversion_context.in_verbatim_block = False diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 1897f450..b69d2e0e 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -22,11 +22,12 @@ import oqpy.base -from braket.aws import AwsDevice from braket.circuits.serialization import IRType, SerializableProgram +from braket.device_schema import DeviceActionType +from braket.devices.device import Device from braket.experimental.autoqasm import constants, errors from braket.experimental.autoqasm.instructions.qubits import QubitIdentifierType as Qubit -from braket.experimental.autoqasm.instructions.qubits import _qubit +from braket.experimental.autoqasm.instructions.qubits import _get_physical_qubit_indices, _qubit # Create the thread-local object for the program conversion context. _local = threading.local() @@ -49,7 +50,7 @@ class UserConfig: num_qubits: Optional[int] = None """The total number of qubits to declare in the program.""" - device: Optional[AwsDevice] = None + device: Optional[Device] = None """The target device for the program.""" @@ -177,10 +178,13 @@ def __init__(self, user_config: Optional[UserConfig] = None): self.subroutines_processing = set() # the set of subroutines queued for processing self.user_config = user_config or UserConfig() self.return_variable = None + self.in_verbatim_block = False self._oqpy_program_stack = [oqpy.Program()] self._gate_definitions_processing = [] self._calibration_definitions_processing = [] - self._qubits_seen = set() + self._gates_defined = set() + self._gates_used = set() + self._virtual_qubits_used = set() self._var_idx = 0 self._has_pulse_control = False @@ -190,6 +194,20 @@ def make_program(self) -> Program: Returns: Program: The program object. """ + # Validate the gates for the target device + device = self.get_target_device() + if device: + device_supported_gates = self._normalize_gate_names( + device.properties.action[DeviceActionType.OPENQASM].supportedOperations + ) + valid_gates = self._gates_defined.union(device_supported_gates) + invalid_gates_used = self._gates_used.difference(valid_gates) + if invalid_gates_used: + raise errors.UnsupportedGate( + f'The target device "{device.name}" does not support ' + f"the following gates used in the program: {invalid_gates_used}" + ) + return Program(self.get_oqpy_program(), has_pulse_control=self._has_pulse_control) @property @@ -200,11 +218,11 @@ def qubits(self) -> List[int]: List[int]: The list of virtual qubits, e.g. [0, 1, 2] """ # Can be memoized or otherwise made more performant - return sorted(list(self._qubits_seen)) + return sorted(list(self._virtual_qubits_used)) def register_qubit(self, qubit: int) -> None: - """Register a virtual qubit to use in this program.""" - self._qubits_seen.add(qubit) + """Register a virtual qubit that is used in this program.""" + self._virtual_qubits_used.add(qubit) def get_declared_qubits(self) -> Optional[int]: """Return the number of qubits to declare in the program, as specified by the user. @@ -212,7 +230,34 @@ def get_declared_qubits(self) -> Optional[int]: """ return self.user_config.num_qubits - def get_target_device(self) -> Optional[AwsDevice]: + def register_gate(self, gate_name: str) -> None: + """Register a gate that is used in this program. + + Args: + gate_name (str): The name of the gate being used. + + Raises: + errors.UnsupportedNativeGate: If the gate is being used inside a verbatim block + and the gate is not a native gate of the target device. + """ + if not self.in_verbatim_block: + self._gates_used.add(gate_name) + return + + # If we are in verbatim and there is a target device specified, validate that the + # provided gate is a native gate on the target device (or is a custom gate definition). + device = self.get_target_device() + if device: + native_gates = self._normalize_gate_names(device.properties.paradigm.nativeGateSet) + allowed_verbatim_gates = self._gates_defined.union(native_gates) + if gate_name not in allowed_verbatim_gates: + raise errors.UnsupportedNativeGate( + f'The gate "{gate_name}" is not a native gate of the target ' + f'device "{device.name}". Only native gates may be used inside a verbatim ' + f"block. The native gates of the device are: {native_gates}" + ) + + def get_target_device(self) -> Optional[Device]: """Return the target device for the program, as specified by the user. Returns None if the user did not specify a target device. """ @@ -268,8 +313,12 @@ def validate_gate_targets(self, qubits: List[Any], angles: List[Any]) -> None: angles (List[Any]): The list of target angles to validate. Raises: + errors.InvalidTargetQubit: Target qubits are invalid in the current context. errors.InvalidGateDefinition: Targets are invalid in the current gate definition. """ + if self.in_verbatim_block and not self._gate_definitions_processing: + self._validate_verbatim_target_qubits(qubits) + if self._gate_definitions_processing: gate_name = self._gate_definitions_processing[-1]["name"] gate_qubit_args = self._gate_definitions_processing[-1]["gate_args"].qubits @@ -291,6 +340,37 @@ def validate_gate_targets(self, qubits: List[Any], angles: List[Any]) -> None: "passed as arguments." ) + @staticmethod + def _normalize_gate_names(gate_names: Iterable[str]) -> List[str]: + return [gate_name.lower() for gate_name in gate_names] + + def _validate_verbatim_target_qubits(self, qubits: List[Any]) -> None: + # Only physical target qubits are allowed in a verbatim block: + for qubit in qubits: + if not isinstance(qubit, str): + qubit_name = qubit.name if isinstance(qubit, oqpy.Qubit) else str(qubit) + raise errors.InvalidTargetQubit( + f'Qubit "{qubit_name}" is not a physical qubit. Only physical qubits such ' + 'as "$0" can be targeted inside a verbatim block.' + ) + qubits = _get_physical_qubit_indices(qubits) + + # Validate physical qubit connectivity on the target device: + device = self.get_target_device() + if device and not device.properties.paradigm.connectivity.fullyConnected: + connectivity_graph = device.properties.paradigm.connectivity.connectivityGraph + + # connectivity_graph uses integer qubit indices, but represented as strings. + start_qubit = qubits[0] + valid_target_qubits = connectivity_graph[str(start_qubit)] + for target_qubit in qubits[1:]: + if str(target_qubit) not in valid_target_qubits: + raise errors.InvalidTargetQubit( + f'Qubit "{start_qubit}" is not connected to qubit "{target_qubit}" ' + f'on device "{device.name}". The connectivity graph of the device is: ' + f"{connectivity_graph}" + ) + def get_oqpy_program( self, scope: ProgramScope = ProgramScope.CURRENT, mode: ProgramMode = ProgramMode.NONE ) -> oqpy.Program: @@ -356,6 +436,7 @@ def gate_definition(self, gate_name: str, gate_args: GateArgs) -> None: gate_name (str): The name of the gate being defined. gate_args (GateArgs): The list of arguments to the gate. """ + self._gates_defined.add(gate_name) try: self._gate_definitions_processing.append({"name": gate_name, "gate_args": gate_args}) with oqpy.gate( @@ -394,6 +475,19 @@ def calibration_definition( finally: self._calibration_definitions_processing.pop() + @contextlib.contextmanager + def box(self, pragma: Optional[str] = None) -> None: + """Sets the program conversion context into a box context. + + Args: + pragma (Optional[str]): Pragma to include before the box. Defaults to None. + """ + oqpy_program = self.get_oqpy_program() + if pragma: + oqpy_program.pragma(pragma) + with oqpy.Box(oqpy_program): + yield + @contextlib.contextmanager def build_program(user_config: Optional[UserConfig] = None) -> None: diff --git a/src/braket/experimental/autoqasm/pulse/pulse.py b/src/braket/experimental/autoqasm/pulse/pulse.py index 90dff13f..a5753d70 100644 --- a/src/braket/experimental/autoqasm/pulse/pulse.py +++ b/src/braket/experimental/autoqasm/pulse/pulse.py @@ -15,7 +15,6 @@ """Pulse instructions that apply to frames or qubits. """ -import re from typing import List, Union import oqpy @@ -24,6 +23,7 @@ from braket.experimental.autoqasm import program as aq_program from braket.experimental.autoqasm.instructions.qubits import ( QubitIdentifierType, + _get_physical_qubit_indices, is_qubit_identifier_type, ) from braket.pulse import PulseSequence @@ -53,26 +53,6 @@ def _pulse_instruction(name: str, frame: Frame, *args) -> None: getattr(pulse_sequence, name)(frame, *args) -def _physical_qubit_to_braket_qubit(qids: List[str]) -> QubitSet: - """Convert a physical qubit label to a QubitSet. - - Args: - qids (List[str]): Physical qubit labels. - - Returns: - QubitSet: Represent physical qubits. - """ - braket_qubits = [] - for qid in qids: - if not (isinstance(qid, str) and re.match(r"\$\d+", qid)): - raise ValueError( - f"invalid physical qubit label: '{qid}'. Physical qubit must be labeled as a string" - "with `$` followed by an integer. For example: `$1`." - ) - braket_qubits.append(int(qid[1:])) - return QubitSet(braket_qubits) - - def set_frequency(frame: Frame, frequency: float) -> None: """Adds an instruction to set the frequency of the frame to the specified `frequency` value. @@ -157,7 +137,7 @@ def delay( if not isinstance(qubits_or_frames, List): qubits_or_frames = [qubits_or_frames] if all(is_qubit_identifier_type(q) for q in qubits_or_frames): - qubits_or_frames = _physical_qubit_to_braket_qubit(qubits_or_frames) + qubits_or_frames = QubitSet(_get_physical_qubit_indices(qubits_or_frames)) _pulse_instruction("delay", qubits_or_frames, duration) @@ -175,5 +155,5 @@ def barrier( if not isinstance(qubits_or_frames, List): qubits_or_frames = [qubits_or_frames] if all(is_qubit_identifier_type(q) for q in qubits_or_frames): - qubits_or_frames = _physical_qubit_to_braket_qubit(qubits_or_frames) + qubits_or_frames = QubitSet(_get_physical_qubit_indices(qubits_or_frames)) _pulse_instruction("barrier", qubits_or_frames) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_devices.py b/test/unit_tests/braket/experimental/autoqasm/test_devices.py index d0bdb4a0..4313ffb8 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_devices.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_devices.py @@ -20,10 +20,11 @@ import braket.experimental.autoqasm as aq from braket.aws import AwsDevice +from braket.device_schema import DeviceActionType from braket.device_schema.simulators import GateModelSimulatorDeviceCapabilities from braket.devices import Devices from braket.experimental.autoqasm import errors -from braket.experimental.autoqasm.instructions import h +from braket.experimental.autoqasm.instructions import cnot, cphaseshift00, h, x RIGETTI_REGION = "us-west-1" @@ -48,10 +49,10 @@ "shotsRange": [1, 10], }, "action": { - "braket.ir.jaqcd.program": { - "actionType": "braket.ir.jaqcd.program", + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", "version": ["1"], - "supportedOperations": ["H"], + "supportedOperations": ["h"], } }, "paradigm": {"qubitCount": 30}, @@ -97,7 +98,11 @@ def aws_session(): def aws_device(): _aws_device = Mock() _aws_device.name = "Mock SV1 Device" - _aws_device.properties.paradigm.qubitCount = 34 + _aws_device.properties.paradigm.qubitCount = 30 + _aws_device_action = Mock() + _aws_device_action.supportedOperations = [] + _aws_device_action.supportedPragmas = [] + _aws_device.properties.action = {DeviceActionType.OPENQASM: _aws_device_action} return _aws_device @@ -128,9 +133,123 @@ def my_program(): def test_insufficient_qubits(aws_device: Mock) -> None: - @aq.main(device=aws_device, num_qubits=35) + aws_device.properties.paradigm.qubitCount = 9 + + @aq.main(device=aws_device, num_qubits=10) def my_program(): pass with pytest.raises(errors.InsufficientQubitCountError): my_program() + + +def test_unsupported_gate(aws_device: Mock) -> None: + aws_device.properties.action[DeviceActionType.OPENQASM].supportedOperations = ["h"] + + @aq.main(device=aws_device) + def my_program(): + cphaseshift00(0, 1, 0.123) + + with pytest.raises(errors.UnsupportedGate): + my_program() + + +def test_unsupported_native_gate(aws_device: Mock) -> None: + aws_device.properties.action[DeviceActionType.OPENQASM].supportedOperations = ["h, x"] + aws_device.properties.action[DeviceActionType.OPENQASM].supportedPragmas = ["verbatim"] + aws_device.properties.paradigm.nativeGateSet = ["x"] + + @aq.main(device=aws_device) + def my_program(): + with aq.verbatim(): + x("$0") + h("$0") + + with pytest.raises(errors.UnsupportedNativeGate): + my_program() + + +def test_supported_native_gate_inside_gate_definition(aws_device: Mock) -> None: + aws_device.properties.action[DeviceActionType.OPENQASM].supportedOperations = ["h, x"] + aws_device.properties.action[DeviceActionType.OPENQASM].supportedPragmas = ["verbatim"] + aws_device.properties.paradigm.nativeGateSet = ["x"] + + @aq.gate + def my_gate(q: aq.Qubit): + x(q) + + @aq.main(device=aws_device) + def my_program(): + with aq.verbatim(): + x("$0") + my_gate("$0") + + assert my_program().to_ir() + + +def test_unsupported_native_gate_inside_gate_definition(aws_device: Mock) -> None: + aws_device.properties.action[DeviceActionType.OPENQASM].supportedOperations = ["h, x"] + aws_device.properties.action[DeviceActionType.OPENQASM].supportedPragmas = ["verbatim"] + aws_device.properties.paradigm.nativeGateSet = ["x"] + + @aq.gate + def my_gate(q: aq.Qubit): + h(q) + + @aq.main(device=aws_device) + def my_program(): + with aq.verbatim(): + my_gate("$0") + + with pytest.raises(errors.UnsupportedNativeGate): + my_program() + + +def test_unsupported_verbatim_block(aws_device: Mock) -> None: + aws_device.properties.action[DeviceActionType.OPENQASM].supportedPragmas = [] + + @aq.main(device=aws_device) + def my_program(): + with aq.verbatim(): + h("$0") + + with pytest.raises(errors.VerbatimBlockNotAllowed): + my_program() + + +def test_validate_connectivity(aws_device: Mock) -> None: + aws_device.properties.action[DeviceActionType.OPENQASM].supportedOperations = ["rx, ry, rz"] + aws_device.properties.action[DeviceActionType.OPENQASM].supportedPragmas = ["verbatim"] + aws_device.properties.paradigm.nativeGateSet = ["H", "CNOT"] + aws_device.properties.paradigm.connectivity.fullyConnected = False + aws_device.properties.paradigm.connectivity.connectivityGraph = {"0": ["2"], "1": ["0"]} + + @aq.main(device=aws_device) + def my_program(): + with aq.verbatim(): + h("$0") + cnot("$0", "$1") + + with pytest.raises(errors.InvalidTargetQubit): + my_program() + + @aq.main(device=aws_device) + def my_program(): + with aq.verbatim(): + h("$0") + cnot("$0", "$2") + cnot("$1", "$0") + + assert my_program().to_ir() + + aws_device.properties.paradigm.connectivity.fullyConnected = True + aws_device.properties.paradigm.connectivity.connectivityGraph = {} + + @aq.main(device=aws_device) + def my_program(): + with aq.verbatim(): + h("$0") + cnot("$0", "$7") + cnot("$5", "$2") + + assert my_program().to_ir() diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py b/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py index 10982b07..583a5bd7 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py @@ -13,10 +13,27 @@ """Tests for pragmas.""" +import pytest + import braket.experimental.autoqasm as aq +from braket.experimental.autoqasm import errors from braket.experimental.autoqasm.instructions import cnot, h +def test_basic_box() -> None: + """Tests the box statement without a pragma.""" + with aq.build_program() as program_conversion_context: + with program_conversion_context.box(): + pass + + expected = """OPENQASM 3.0; +box { +}""" + + program = program_conversion_context.make_program() + assert program.to_ir() == expected + + def test_with_verbatim_box() -> None: """Tests the with statement with verbatim box `Verbatim`.""" @@ -25,14 +42,35 @@ def program_func() -> None: """User program to test.""" h(0) with aq.verbatim(): - cnot(1, 2) + cnot("$1", "$2") expected = """OPENQASM 3.0; -qubit[3] __qubits__; +qubit[1] __qubits__; h __qubits__[0]; pragma braket verbatim box { - cnot __qubits__[1], __qubits__[2]; + cnot $1, $2; }""" assert program_func().to_ir() == expected + + +def test_nested_verbatim_box() -> None: + @aq.main + def program_func() -> None: + with aq.verbatim(): + with aq.verbatim(): + h(0) + + with pytest.raises(errors.VerbatimBlockNotAllowed): + program_func() + + +def test_verbatim_box_invalid_target_qubit() -> None: + @aq.main + def program_func() -> None: + with aq.verbatim(): + h(0) + + with pytest.raises(errors.InvalidTargetQubit): + program_func() diff --git a/tox.ini b/tox.ini index 6d68d0aa..e7f65be6 100644 --- a/tox.ini +++ b/tox.ini @@ -41,6 +41,7 @@ commands = jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/2_Expressing_classical_control_flow.ipynb jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/3_Iterative_phase_estimation.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/4_Native_programming.ipynb extras = test [testenv:linters] From 268086987861e686e324304051797eafde818e10 Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Wed, 13 Sep 2023 10:09:09 -0700 Subject: [PATCH 0847/1165] Revert "update: restricting parameter names to not collide with ones we use for OpenQASM generation. (#675)" (#701) This reverts commit b158736a5f394fd709f4d12d6f5e0890d05dbbf6. --- src/braket/circuits/circuit.py | 6 +- src/braket/circuits/serialization.py | 4 +- src/braket/parametric/free_parameter.py | 16 +- src/braket/pulse/ast/qasm_transformer.py | 4 +- test/integ_tests/test_adjoint_gradient.py | 32 +- .../braket/aws/test_aws_quantum_task.py | 6 +- .../braket/circuits/test_circuit.py | 530 +++++++++--------- test/unit_tests/braket/circuits/test_gates.py | 126 ++--- .../unit_tests/braket/circuits/test_noises.py | 22 +- .../braket/circuits/test_observables.py | 20 +- .../braket/circuits/test_result_types.py | 24 +- .../braket/devices/test_local_simulator.py | 8 +- .../braket/parametric/test_free_parameter.py | 33 -- 13 files changed, 385 insertions(+), 446 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index d3bf2d0e..7e3bfcd4 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -1212,7 +1212,7 @@ def _to_openqasm( ) for idx, qubit in enumerate(qubits): qubit_target = serialization_properties.format_target(int(qubit)) - ir_instructions.append(f"__bits__[{idx}] = measure {qubit_target};") + ir_instructions.append(f"b[{idx}] = measure {qubit_target};") return OpenQasmProgram.construct(source="\n".join(ir_instructions), inputs={}) @@ -1225,11 +1225,11 @@ 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}] __bits__;") + ir_instructions.append(f"bit[{self.qubit_count}] b;") if serialization_properties.qubit_reference_type == QubitReferenceType.VIRTUAL: total_qubits = max(self.qubits).real + 1 - ir_instructions.append(f"qubit[{total_qubits}] __qubits__;") + ir_instructions.append(f"qubit[{total_qubits}] q;") elif serialization_properties.qubit_reference_type != QubitReferenceType.PHYSICAL: raise ValueError( f"Invalid qubit_reference_type " diff --git a/src/braket/circuits/serialization.py b/src/braket/circuits/serialization.py index 596a1f36..1e0826e8 100644 --- a/src/braket/circuits/serialization.py +++ b/src/braket/circuits/serialization.py @@ -39,7 +39,7 @@ class OpenQASMSerializationProperties: Properties for serializing a circuit to OpenQASM. qubit_reference_type (QubitReferenceType): determines whether to use - logical qubits or physical qubits (__qubits__[i] vs $i). + logical qubits or physical qubits (q[i] vs $i). """ qubit_reference_type: QubitReferenceType = QubitReferenceType.VIRTUAL @@ -53,7 +53,7 @@ def format_target(self, target: int) -> str: str: The OpenQASM representation of the target qubit. """ qubit_reference_format = ( - "__qubits__[{}]" if self.qubit_reference_type == QubitReferenceType.VIRTUAL else "${}" + "q[{}]" if self.qubit_reference_type == QubitReferenceType.VIRTUAL else "${}" ) return qubit_reference_format.format(target) diff --git a/src/braket/parametric/free_parameter.py b/src/braket/parametric/free_parameter.py index 27e588d7..3eed47ef 100644 --- a/src/braket/parametric/free_parameter.py +++ b/src/braket/parametric/free_parameter.py @@ -44,13 +44,12 @@ def __init__(self, name: str): Args: name (str): Name of the :class:'FreeParameter'. Can be a unicode value. - Must not start with '__'. Examples: >>> param1 = FreeParameter("theta") >>> param1 = FreeParameter("\u03B8") """ - self._set_name(name) + self._name = Symbol(name) super().__init__(expression=self._name) @property @@ -100,19 +99,6 @@ def to_dict(self) -> dict: "name": self.name, } - def _set_name(self, name: str) -> None: - FreeParameter._validate_name(name) - self._name = Symbol(name) - - @staticmethod - def _validate_name(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 name.startswith("__"): - raise ValueError("FreeParameter names must not start with two underscores '__'") - @classmethod def from_dict(cls, parameter: dict) -> FreeParameter: return FreeParameter(parameter["name"]) diff --git a/src/braket/pulse/ast/qasm_transformer.py b/src/braket/pulse/ast/qasm_transformer.py index 57a9018e..ae4cccad 100644 --- a/src/braket/pulse/ast/qasm_transformer.py +++ b/src/braket/pulse/ast/qasm_transformer.py @@ -43,8 +43,8 @@ def visit_ExpressionStatement(self, expression_statement: ast.ExpressionStatemen ): # For capture_v0 nodes, it replaces it with classical assignment statements # of the form: - # __bits__[0] = capture_v0(...) - # __bits__[1] = capture_v0(...) + # 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. diff --git a/test/integ_tests/test_adjoint_gradient.py b/test/integ_tests/test_adjoint_gradient.py index 0f0b6982..99ab7dfe 100644 --- a/test/integ_tests/test_adjoint_gradient.py +++ b/test/integ_tests/test_adjoint_gradient.py @@ -42,10 +42,10 @@ def test_adjoint_gradient_quantum_task_with_nested_targets( expected_openqasm = ( "OPENQASM 3.0;\n" "input float theta;\n" - "qubit[4] __qubits__;\n" - "rx(theta) __qubits__[0];\n" - "#pragma braket result adjoint_gradient expectation(-6 * " - "y(__qubits__[0]) @ i(__qubits__[1]) + 0.75 * y(__qubits__[2]) @ z(__qubits__[3])) theta" + "qubit[4] q;\n" + "rx(theta) q[0];\n" + "#pragma braket result adjoint_gradient expectation(-6 * y(q[0]) @ i(q[1]) + 0.75 * " + "y(q[2]) @ z(q[3])) theta" ) gradient_task = AwsQuantumTask.create( @@ -83,10 +83,10 @@ def test_adjoint_gradient_with_standard_observable_terms( expected_openqasm = ( "OPENQASM 3.0;\n" "input float theta;\n" - "qubit[3] __qubits__;\n" - "rx(theta) __qubits__[0];\n" - "#pragma braket result adjoint_gradient expectation(2 * " - "x(__qubits__[0]) + 3 * y(__qubits__[1]) - 1 * z(__qubits__[2])) theta" + "qubit[3] q;\n" + "rx(theta) q[0];\n" + "#pragma braket result adjoint_gradient expectation(2 * x(q[0]) + 3 * y(q[1]) " + "- 1 * z(q[2])) theta" ) gradient_task = AwsQuantumTask.create( @@ -132,19 +132,17 @@ def test_adjoint_gradient_with_batch_circuits(aws_session, s3_destination_folder ( "OPENQASM 3.0;\n" "input float theta;\n" - "qubit[2] __qubits__;\n" - "rx(theta) __qubits__[0];\n" - "#pragma braket result adjoint_gradient expectation(6 *" - " y(__qubits__[0]) @ i(__qubits__[1])) theta" + "qubit[2] q;\n" + "rx(theta) q[0];\n" + "#pragma braket result adjoint_gradient expectation(6 * y(q[0]) @ i(q[1])) theta" ), ( "OPENQASM 3.0;\n" "input float theta;\n" - "qubit[2] __qubits__;\n" - "rx(theta) __qubits__[0];\n" - "#pragma braket result adjoint_gradient expectation(-6 *" - " y(__qubits__[0]) @ i(__qubits__[1]) + 0.75 *" - " y(__qubits__[0]) @ z(__qubits__[1])) theta" + "qubit[2] q;\n" + "rx(theta) q[0];\n" + "#pragma braket result adjoint_gradient expectation(-6 * y(q[0]) @ i(q[1]) + 0.75 * " + "y(q[0]) @ z(q[1])) theta" ), ] 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 0e16a738..de8ead78 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -573,12 +573,12 @@ def test_create_pulse_gate_circuit( expected_openqasm = "\n".join( ( "OPENQASM 3.0;", - "bit[2] __bits__;", + "bit[2] b;", "cal {", " set_frequency(predefined_frame_1, 6000000.0);", "}", - "__bits__[0] = measure $0;", - "__bits__[1] = measure $1;", + "b[0] = measure $0;", + "b[1] = measure $1;", ) ) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 21f972b8..fd2170a5 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -736,8 +736,8 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", + "bit[2] b;", + "qubit[2] q;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -750,10 +750,10 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): " set_frequency(predefined_frame_1, 6000000.0);", " play(predefined_frame_1, drag_gauss_wf);", "}", - "rx(0.15) __qubits__[0];", - "rx(0.3) __qubits__[1];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "rx(0.15) q[0];", + "rx(0.3) q[1];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", ] ), inputs={}, @@ -766,7 +766,7 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", + "bit[2] b;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -781,8 +781,8 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "}", "rx(0.15) $0;", "rx(0.3) $4;", - "__bits__[0] = measure $0;", - "__bits__[1] = measure $4;", + "b[0] = measure $0;", + "b[1] = measure $4;", ] ), inputs={}, @@ -832,7 +832,7 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): source="\n".join( [ "OPENQASM 3.0;", - "qubit[5] __qubits__;", + "qubit[5] q;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -845,10 +845,10 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): " set_frequency(predefined_frame_1, 6000000.0);", " play(predefined_frame_1, drag_gauss_wf);", "}", - "rx(0.15) __qubits__[0];", - "rx(0.3) __qubits__[4];", - "#pragma braket noise bit_flip(0.2) __qubits__[3]", - "#pragma braket result expectation i(__qubits__[0])", + "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={}, @@ -862,8 +862,8 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): [ "OPENQASM 3.0;", "input float theta;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", + "bit[2] b;", + "qubit[2] q;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -876,10 +876,10 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): " set_frequency(predefined_frame_1, 6000000.0);", " play(predefined_frame_1, drag_gauss_wf);", "}", - "rx(0.15) __qubits__[0];", - "rx(theta) __qubits__[1];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "rx(0.15) q[0];", + "rx(theta) q[1];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", ] ), inputs={}, @@ -895,8 +895,8 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): source="\n".join( [ "OPENQASM 3.0;", - "bit[5] __bits__;", - "qubit[5] __qubits__;", + "bit[5] b;", + "qubit[5] q;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -909,14 +909,14 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): " set_frequency(predefined_frame_1, 6000000.0);", " play(predefined_frame_1, drag_gauss_wf);", "}", - "negctrl @ rx(0.15) __qubits__[2], __qubits__[0];", - "ctrl(2) @ rx(0.3) __qubits__[2], __qubits__[3], __qubits__[1];", - "ctrl(2) @ cnot __qubits__[2], __qubits__[3], __qubits__[4], __qubits__[0];", # noqa - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", - "__bits__[2] = measure __qubits__[2];", - "__bits__[3] = measure __qubits__[3];", - "__bits__[4] = measure __qubits__[4];", + "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={}, @@ -929,8 +929,8 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): source="\n".join( [ "OPENQASM 3.0;", - "bit[7] __bits__;", - "qubit[7] __qubits__;", + "bit[7] b;", + "qubit[7] q;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -939,16 +939,16 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): " set_frequency(predefined_frame_1, 6000000.0);", " play(predefined_frame_1, drag_gauss_wf);", "}", - "cnot __qubits__[0], __qubits__[1];", - "cnot __qubits__[3], __qubits__[2];", - "ctrl @ cnot __qubits__[5], __qubits__[6], __qubits__[4];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", - "__bits__[2] = measure __qubits__[2];", - "__bits__[3] = measure __qubits__[3];", - "__bits__[4] = measure __qubits__[4];", - "__bits__[5] = measure __qubits__[5];", - "__bits__[6] = measure __qubits__[6];", + "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={}, @@ -961,8 +961,8 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", + "bit[2] b;", + "qubit[2] q;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -977,11 +977,11 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): " shift_phase(predefined_frame_1, -0.2);", " play(predefined_frame_1, drag_gauss_wf);", "}", - "inv @ pow(2.5) @ h __qubits__[0];", - "pow(0) @ h __qubits__[0];", - "ms(-0.1, -0.2, -0.3) __qubits__[0], __qubits__[1];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "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={}, @@ -1030,8 +1030,8 @@ def test_parametric_circuit_with_fixed_argument_defcal(pulse_sequence): [ "OPENQASM 3.0;", "input float theta;", - "bit[1] __bits__;", - "qubit[1] __qubits__;", + "bit[1] b;", + "qubit[1] q;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -1044,10 +1044,10 @@ def test_parametric_circuit_with_fixed_argument_defcal(pulse_sequence): " set_frequency(predefined_frame_1, 6000000.0);", " play(predefined_frame_1, drag_gauss_wf);", "}", - "inv @ pow(2.5) @ h __qubits__[0];", - "pow(0) @ h __qubits__[0];", - "rx(theta) __qubits__[0];", - "__bits__[0] = measure __qubits__[0];", + "inv @ pow(2.5) @ h q[0];", + "pow(0) @ h q[0];", + "rx(theta) q[0];", + "b[0] = measure q[0];", ] ), inputs={}, @@ -1128,8 +1128,8 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[1] __bits__;", - "qubit[1] __qubits__;", + "bit[1] b;", + "qubit[1] q;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", @@ -1140,8 +1140,8 @@ def foo( " shift_phase(predefined_frame_1, -0.2);", " play(predefined_frame_1, drag_gauss_wf);", "}", - "foo(-0.2) __qubits__[0];", - "__bits__[0] = measure __qubits__[0];", + "foo(-0.2) q[0];", + "b[0] = measure q[0];", ] ), inputs={}, @@ -1166,11 +1166,11 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", - "negctrl @ h __qubits__[1], __qubits__[0];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "bit[2] b;", + "qubit[2] q;", + "negctrl @ h q[1], q[0];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", ] ), inputs={}, @@ -1182,11 +1182,11 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", - "cnot __qubits__[1], __qubits__[0];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "bit[2] b;", + "qubit[2] q;", + "cnot q[1], q[0];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", ] ), inputs={}, @@ -1198,11 +1198,11 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", - "negctrl @ x __qubits__[1], __qubits__[0];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "bit[2] b;", + "qubit[2] q;", + "negctrl @ x q[1], q[0];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", ] ), inputs={}, @@ -1214,11 +1214,11 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", - "ctrl @ rx(0.15) __qubits__[1], __qubits__[0];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "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={}, @@ -1230,11 +1230,11 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", - "ctrl @ ry(0.2) __qubits__[1], __qubits__[0];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "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={}, @@ -1246,11 +1246,11 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", - "negctrl @ rz(0.25) __qubits__[1], __qubits__[0];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "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={}, @@ -1262,11 +1262,11 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", - "negctrl @ s __qubits__[1], __qubits__[0];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "bit[2] b;", + "qubit[2] q;", + "negctrl @ s q[1], q[0];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", ] ), inputs={}, @@ -1278,11 +1278,11 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", - "negctrl @ t __qubits__[0], __qubits__[1];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "bit[2] b;", + "qubit[2] q;", + "negctrl @ t q[0], q[1];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", ] ), inputs={}, @@ -1294,11 +1294,11 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", - "cphaseshift(0.15) __qubits__[1], __qubits__[0];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "bit[2] b;", + "qubit[2] q;", + "cphaseshift(0.15) q[1], q[0];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", ] ), inputs={}, @@ -1310,12 +1310,12 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[3] __bits__;", - "qubit[3] __qubits__;", - "ccnot __qubits__[0], __qubits__[1], __qubits__[2];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", - "__bits__[2] = measure __qubits__[2];", + "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={}, @@ -1327,8 +1327,8 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[1] __qubits__;", - "h __qubits__[0];", + "qubit[1] q;", + "h q[0];", "#pragma braket result state_vector", ] ), @@ -1341,9 +1341,9 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[1] __qubits__;", - "h __qubits__[0];", - "#pragma braket result expectation x(__qubits__[0])", + "qubit[1] q;", + "h q[0];", + "#pragma braket result expectation x(q[0])", ] ), inputs={}, @@ -1355,9 +1355,9 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[2] __qubits__;", - "h __qubits__[0];", - "#pragma braket result expectation h(__qubits__[0]) @ x(__qubits__[1])", + "qubit[2] q;", + "h q[0];", + "#pragma braket result expectation h(q[0]) @ x(q[1])", ] ), inputs={}, @@ -1369,9 +1369,9 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[2] __qubits__;", - "h __qubits__[0];", - "#pragma braket result variance h(__qubits__[0]) @ x(__qubits__[1])", + "qubit[2] q;", + "h q[0];", + "#pragma braket result variance h(q[0]) @ x(q[1])", ] ), inputs={}, @@ -1383,9 +1383,9 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[1] __qubits__;", - "h __qubits__[0];", - "#pragma braket result probability __qubits__[0]", + "qubit[1] q;", + "h q[0];", + "#pragma braket result probability q[0]", ] ), inputs={}, @@ -1397,10 +1397,10 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[1] __bits__;", - "qubit[1] __qubits__;", - "#pragma braket noise bit_flip(0.1) __qubits__[0]", - "__bits__[0] = measure __qubits__[0];", + "bit[1] b;", + "qubit[1] q;", + "#pragma braket noise bit_flip(0.1) q[0]", + "b[0] = measure q[0];", ] ), inputs={}, @@ -1412,10 +1412,10 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[1] __bits__;", - "qubit[1] __qubits__;", - "#pragma braket noise generalized_amplitude_damping(0.1, 0.1) __qubits__[0]", # noqa - "__bits__[0] = measure __qubits__[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={}, @@ -1427,10 +1427,10 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[1] __bits__;", - "qubit[1] __qubits__;", - "#pragma braket noise phase_flip(0.2) __qubits__[0]", - "__bits__[0] = measure __qubits__[0];", + "bit[1] b;", + "qubit[1] q;", + "#pragma braket noise phase_flip(0.2) q[0]", + "b[0] = measure q[0];", ] ), inputs={}, @@ -1442,10 +1442,10 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[1] __bits__;", - "qubit[1] __qubits__;", - "#pragma braket noise depolarizing(0.5) __qubits__[0]", - "__bits__[0] = measure __qubits__[0];", + "bit[1] b;", + "qubit[1] q;", + "#pragma braket noise depolarizing(0.5) q[0]", + "b[0] = measure q[0];", ] ), inputs={}, @@ -1457,10 +1457,10 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[1] __bits__;", - "qubit[1] __qubits__;", - "#pragma braket noise amplitude_damping(0.8) __qubits__[0]", - "__bits__[0] = measure __qubits__[0];", + "bit[1] b;", + "qubit[1] q;", + "#pragma braket noise amplitude_damping(0.8) q[0]", + "b[0] = measure q[0];", ] ), inputs={}, @@ -1472,10 +1472,10 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[1] __bits__;", - "qubit[1] __qubits__;", - "#pragma braket noise phase_damping(0.1) __qubits__[0]", - "__bits__[0] = measure __qubits__[0];", + "bit[1] b;", + "qubit[1] q;", + "#pragma braket noise phase_damping(0.1) q[0]", + "b[0] = measure q[0];", ] ), inputs={}, @@ -1487,8 +1487,8 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[1] __qubits__;", - "h __qubits__[0];", + "qubit[1] q;", + "h q[0];", '#pragma braket result amplitude "0", "1"', ] ), @@ -1504,16 +1504,16 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[5] __bits__;", - "qubit[5] __qubits__;", - "negctrl @ rx(0.15) __qubits__[2], __qubits__[0];", - "ctrl(2) @ rx(0.3) __qubits__[2], __qubits__[3], __qubits__[1];", - "ctrl(2) @ cnot __qubits__[2], __qubits__[3], __qubits__[4], __qubits__[0];", # noqa - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", - "__bits__[2] = measure __qubits__[2];", - "__bits__[3] = measure __qubits__[3];", - "__bits__[4] = measure __qubits__[4];", + "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={}, @@ -1525,18 +1525,18 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[7] __bits__;", - "qubit[7] __qubits__;", - "cnot __qubits__[0], __qubits__[1];", - "cnot __qubits__[3], __qubits__[2];", - "ctrl @ cnot __qubits__[5], __qubits__[6], __qubits__[4];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", - "__bits__[2] = measure __qubits__[2];", - "__bits__[3] = measure __qubits__[3];", - "__bits__[4] = measure __qubits__[4];", - "__bits__[5] = measure __qubits__[5];", - "__bits__[6] = measure __qubits__[6];", + "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={}, @@ -1548,11 +1548,11 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[1] __bits__;", - "qubit[1] __qubits__;", - "inv @ pow(2.5) @ h __qubits__[0];", - "pow(0) @ h __qubits__[0];", - "__bits__[0] = measure __qubits__[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={}, @@ -1564,10 +1564,10 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[1] __bits__;", - "qubit[1] __qubits__;", - "#pragma braket unitary([[0, 1.0], [1.0, 0]]) __qubits__[0]", - "__bits__[0] = measure __qubits__[0];", + "bit[1] b;", + "qubit[1] q;", + "#pragma braket unitary([[0, 1.0], [1.0, 0]]) q[0]", + "b[0] = measure q[0];", ] ), inputs={}, @@ -1579,10 +1579,10 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[1] __bits__;", - "qubit[1] __qubits__;", - "#pragma braket noise pauli_channel(0.1, 0.2, 0.3) __qubits__[0]", - "__bits__[0] = measure __qubits__[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={}, @@ -1594,11 +1594,11 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", - "#pragma braket noise two_qubit_depolarizing(0.1) __qubits__[0], __qubits__[1]", # noqa - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "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={}, @@ -1610,11 +1610,11 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", - "#pragma braket noise two_qubit_dephasing(0.1) __qubits__[0], __qubits__[1]", # noqa - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "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={}, @@ -1626,11 +1626,11 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", - "#pragma braket noise two_qubit_dephasing(0.1) __qubits__[0], __qubits__[1]", # noqa - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "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={}, @@ -1642,9 +1642,9 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[1] __qubits__;", - "h __qubits__[0];", - "#pragma braket result sample z(__qubits__[0])", + "qubit[1] q;", + "h q[0];", + "#pragma braket result sample z(q[0])", ] ), inputs={}, @@ -1656,9 +1656,9 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[1] __qubits__;", - "h __qubits__[0];", - "#pragma braket result sample z(__qubits__[0])", + "qubit[1] q;", + "h q[0];", + "#pragma braket result sample z(q[0])", ] ), inputs={}, @@ -1670,10 +1670,10 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "qubit[2] __qubits__;", - "h __qubits__[0];", - "x __qubits__[1];", - "#pragma braket result density_matrix __qubits__[0], __qubits__[1]", + "qubit[2] q;", + "h q[0];", + "x q[1];", + "#pragma braket result density_matrix q[0], q[1]", ] ), inputs={}, @@ -1691,12 +1691,12 @@ def foo( source="\n".join( [ "OPENQASM 3.0;", - "bit[1] __bits__;", - "qubit[1] __qubits__;", + "bit[1] b;", + "qubit[1] q;", "#pragma braket noise " "kraus([[0.9486833im, 0], [0, 0.9486833im]], [[0, 0.31622777], " - "[0.31622777, 0]]) __qubits__[0]", - "__bits__[0] = measure __qubits__[0];", + "[0.31622777, 0]]) q[0]", + "b[0] = measure q[0];", ] ), inputs={}, @@ -1709,10 +1709,10 @@ def foo( [ "OPENQASM 3.0;", "input float theta;", - "bit[1] __bits__;", - "qubit[1] __qubits__;", - "rx(theta) __qubits__[0];", - "__bits__[0] = measure __qubits__[0];", + "bit[1] b;", + "qubit[1] q;", + "rx(theta) q[0];", + "b[0] = measure q[0];", ] ), inputs={}, @@ -1733,11 +1733,11 @@ def test_from_ir_inputs_updated(): "OPENQASM 3.0;", "input float theta;", "input float phi;", - "bit[1] __bits__;", - "qubit[1] __qubits__;", - "rx(theta) __qubits__[0];", - "ry(phi) __qubits__[0];", - "__bits__[0] = measure __qubits__[0];", + "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}, @@ -1754,15 +1754,15 @@ def test_from_ir_inputs_updated(): source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", + "bit[2] b;", + "qubit[2] q;", "gate my_gate a,b {", "h a;", - "cnot a, b;", + "cnot a,b;", "}", - "my_gate __qubits__[0], __qubits__[1];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "my_gate q[0], q[1];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", ] ), inputs={}, @@ -1774,15 +1774,15 @@ def test_from_ir_inputs_updated(): source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", + "bit[2] b;", + "qubit[2] q;", "def my_sub(qubit q) {", "h q;", "}", - "h __qubits__[0];", - "my_sub(__qubits__[1]);", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "h q[0];", + "my_sub(q[1]);", + "b[0] = measure q[0];", + "b[1] = measure q[1];", ] ), inputs={}, @@ -1794,14 +1794,14 @@ def test_from_ir_inputs_updated(): source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", + "bit[2] b;", + "qubit[2] q;", "for uint i in [0:1] {", - "h __qubits__[i];", + "h q[i];", "}", - "cnot __qubits__[0], __qubits__[1];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "cnot q[0], q[1];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", ] ), inputs={}, @@ -1813,14 +1813,14 @@ def test_from_ir_inputs_updated(): source="\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", - "qubit[2] __qubits__;", + "bit[2] b;", + "qubit[2] q;", "for uint i in [0:1] {", - "h __qubits__[i];", + "h q[i];", "}", - "cnot __qubits__[0], __qubits__[1];", - "__bits__[0] = measure __qubits__[0];", - "__bits__[1] = measure __qubits__[1];", + "cnot q[0], q[1];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", ] ), inputs={}, @@ -1832,13 +1832,13 @@ def test_from_ir_inputs_updated(): source="\n".join( [ "OPENQASM 3.0;", - "bit[1] __bits__;", - "qubit[1] __qubits__;", + "bit[1] b;", + "qubit[1] q;", "bit c = 0;", "if (c ==0){", - "x __qubits__[0];", + "x q[0];", "}", - "__bits__[0] = measure __qubits__[0];", + "b[0] = measure q[0];", ] ), inputs={}, @@ -1850,11 +1850,11 @@ def test_from_ir_inputs_updated(): source="\n".join( [ "OPENQASM 3.0;", - "input float theta;" "bit[1] __bits__;", - "qubit[1] __qubits__;", - "rx(theta) __qubits__[0];", - "rx(2*theta) __qubits__[0];", - "__bits__[0] = measure __qubits__[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={}, @@ -3357,7 +3357,7 @@ def test_pulse_circuit_to_openqasm(predefined_frame_1, user_defined_frame): ).source == "\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", + "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);", @@ -3379,8 +3379,8 @@ def test_pulse_circuit_to_openqasm(predefined_frame_1, user_defined_frame): " play(predefined_frame_1, drag_gauss_wf_2);", "}", "h $1;", - "__bits__[0] = measure $0;", - "__bits__[1] = measure $1;", + "b[0] = measure $0;", + "b[1] = measure $1;", ] ) @@ -3474,7 +3474,7 @@ def test_parametrized_pulse_circuit(user_defined_frame): [ "OPENQASM 3.0;", "input float frequency;", - "bit[2] __bits__;", + "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);", @@ -3484,8 +3484,8 @@ def test_parametrized_pulse_circuit(user_defined_frame): " set_frequency(user_defined_frame_0, frequency);", " play(user_defined_frame_0, gauss_wf);", "}", - "__bits__[0] = measure $0;", - "__bits__[1] = measure $1;", + "b[0] = measure $0;", + "b[1] = measure $1;", ] ) @@ -3499,7 +3499,7 @@ def test_parametrized_pulse_circuit(user_defined_frame): ).source == "\n".join( [ "OPENQASM 3.0;", - "bit[2] __bits__;", + "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);", @@ -3509,8 +3509,8 @@ def test_parametrized_pulse_circuit(user_defined_frame): " set_frequency(user_defined_frame_0, 10000000.0);", " play(user_defined_frame_0, gauss_wf);", "}", - "__bits__[0] = measure $0;", - "__bits__[1] = measure $1;", + "b[0] = measure $0;", + "b[1] = measure $1;", ] ) diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 9d5dd558..05b98ade 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -306,7 +306,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Rx(angle=0.17), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "rx(0.17) __qubits__[4];", + "rx(0.17) q[4];", ), ( Gate.Rx(angle=0.17), @@ -318,7 +318,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.X(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "x __qubits__[4];", + "x q[4];", ), ( Gate.X(), @@ -330,7 +330,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Z(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "z __qubits__[4];", + "z q[4];", ), ( Gate.Z(), @@ -342,7 +342,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Y(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "y __qubits__[4];", + "y q[4];", ), ( Gate.Y(), @@ -354,7 +354,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.H(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "h __qubits__[4];", + "h q[4];", ), ( Gate.H(), @@ -366,7 +366,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Ry(angle=0.17), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "ry(0.17) __qubits__[4];", + "ry(0.17) q[4];", ), ( Gate.Ry(angle=0.17), @@ -378,7 +378,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.ZZ(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "zz(0.17) __qubits__[4], __qubits__[5];", + "zz(0.17) q[4], q[5];", ), ( Gate.ZZ(angle=0.17), @@ -390,7 +390,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.I(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "i __qubits__[4];", + "i q[4];", ), ( Gate.I(), @@ -402,7 +402,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.V(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "v __qubits__[4];", + "v q[4];", ), ( Gate.V(), @@ -414,7 +414,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CY(), [0, 1], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cy __qubits__[0], __qubits__[1];", + "cy q[0], q[1];", ), ( Gate.CY(), @@ -426,7 +426,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Rz(angle=0.17), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "rz(0.17) __qubits__[4];", + "rz(0.17) q[4];", ), ( Gate.Rz(angle=0.17), @@ -438,7 +438,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.XX(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "xx(0.17) __qubits__[4], __qubits__[5];", + "xx(0.17) q[4], q[5];", ), ( Gate.XX(angle=0.17), @@ -450,7 +450,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.T(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "t __qubits__[4];", + "t q[4];", ), ( Gate.T(), @@ -468,13 +468,13 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CZ(), [0, 1], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cz __qubits__[0], __qubits__[1];", + "cz q[0], q[1];", ), ( Gate.YY(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "yy(0.17) __qubits__[4], __qubits__[5];", + "yy(0.17) q[4], q[5];", ), ( Gate.YY(angle=0.17), @@ -486,7 +486,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.XY(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "xy(0.17) __qubits__[4], __qubits__[5];", + "xy(0.17) q[4], q[5];", ), ( Gate.XY(angle=0.17), @@ -504,7 +504,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.ISwap(), [0, 1], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "iswap __qubits__[0], __qubits__[1];", + "iswap q[0], q[1];", ), ( Gate.Swap(), @@ -516,7 +516,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Swap(), [0, 1], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "swap __qubits__[0], __qubits__[1];", + "swap q[0], q[1];", ), ( Gate.ECR(), @@ -528,7 +528,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.ECR(), [0, 1], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "ecr __qubits__[0], __qubits__[1];", + "ecr q[0], q[1];", ), ( Gate.CV(), @@ -540,13 +540,13 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CV(), [0, 1], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cv __qubits__[0], __qubits__[1];", + "cv q[0], q[1];", ), ( Gate.Vi(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "vi __qubits__[4];", + "vi q[4];", ), ( Gate.Vi(), @@ -558,7 +558,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CSwap(), [0, 1, 2], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cswap __qubits__[0], __qubits__[1], __qubits__[2];", + "cswap q[0], q[1], q[2];", ), ( Gate.CSwap(), @@ -570,7 +570,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CPhaseShift01(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cphaseshift01(0.17) __qubits__[4], __qubits__[5];", + "cphaseshift01(0.17) q[4], q[5];", ), ( Gate.CPhaseShift01(angle=0.17), @@ -582,7 +582,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CPhaseShift00(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cphaseshift00(0.17) __qubits__[4], __qubits__[5];", + "cphaseshift00(0.17) q[4], q[5];", ), ( Gate.CPhaseShift00(angle=0.17), @@ -594,7 +594,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CPhaseShift(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cphaseshift(0.17) __qubits__[4], __qubits__[5];", + "cphaseshift(0.17) q[4], q[5];", ), ( Gate.CPhaseShift(angle=0.17), @@ -606,7 +606,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.S(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "s __qubits__[4];", + "s q[4];", ), ( Gate.S(), @@ -618,7 +618,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Si(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "si __qubits__[4];", + "si q[4];", ), ( Gate.Si(), @@ -630,7 +630,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Ti(), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "ti __qubits__[4];", + "ti q[4];", ), ( Gate.Ti(), @@ -642,7 +642,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.PhaseShift(angle=0.17), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "phaseshift(0.17) __qubits__[4];", + "phaseshift(0.17) q[4];", ), ( Gate.PhaseShift(angle=0.17), @@ -654,7 +654,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CNot(), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cnot __qubits__[4], __qubits__[5];", + "cnot q[4], q[5];", ), ( Gate.CNot(), @@ -666,7 +666,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.PSwap(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "pswap(0.17) __qubits__[4], __qubits__[5];", + "pswap(0.17) q[4], q[5];", ), ( Gate.PSwap(angle=0.17), @@ -678,7 +678,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CPhaseShift10(angle=0.17), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cphaseshift10(0.17) __qubits__[4], __qubits__[5];", + "cphaseshift10(0.17) q[4], q[5];", ), ( Gate.CPhaseShift10(angle=0.17), @@ -690,7 +690,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.CCNot(), [4, 5, 6], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "ccnot __qubits__[4], __qubits__[5], __qubits__[6];", + "ccnot q[4], q[5], q[6];", ), ( Gate.CCNot(), @@ -711,7 +711,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs "[0, 0, 0, 0, 0, 1.0, 0, 0], " "[0, 0, 0, 0, 0, 0, 0, 1.0], " "[0, 0, 0, 0, 0, 0, 1.0, 0]" - "]) __qubits__[4], __qubits__[5], __qubits__[6]", + "]) q[4], q[5], q[6]", ), ( Gate.Unitary(Gate.CCNot().to_matrix()), @@ -737,7 +737,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs "[0, 0, 0.70710678im, 0.70710678], " "[0.70710678, -0.70710678im, 0, 0], " "[-0.70710678im, 0.70710678, 0, 0]" - "]) __qubits__[4], __qubits__[5]", + "]) q[4], q[5]", ), ( Gate.Unitary(np.round(Gate.ECR().to_matrix(), 8)), @@ -754,7 +754,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Unitary(np.round(Gate.T().to_matrix(), 8)), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket unitary([[1.0, 0], [0, 0.70710678 + 0.70710678im]]) __qubits__[4]", + "#pragma braket unitary([[1.0, 0], [0, 0.70710678 + 0.70710678im]]) q[4]", ), ( Gate.Unitary(np.round(Gate.T().to_matrix(), 8)), @@ -766,7 +766,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.Unitary(np.array([[1.0, 0], [0, 0.70710678 - 0.70710678j]])), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket unitary([[1.0, 0], [0, 0.70710678 - 0.70710678im]]) __qubits__[4]", + "#pragma braket unitary([[1.0, 0], [0, 0.70710678 - 0.70710678im]]) q[4]", ), ( Gate.PulseGate( @@ -784,7 +784,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.GPi(angle=0.17), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "gpi(0.17) __qubits__[4];", + "gpi(0.17) q[4];", ), ( Gate.GPi(angle=0.17), @@ -796,7 +796,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.GPi2(angle=0.17), [4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "gpi2(0.17) __qubits__[4];", + "gpi2(0.17) q[4];", ), ( Gate.GPi2(angle=0.17), @@ -808,7 +808,7 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs Gate.MS(angle_1=0.17, angle_2=3.45), [4, 5], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - f"ms(0.17, 3.45, {np.pi / 2}) __qubits__[4], __qubits__[5];", + f"ms(0.17, 3.45, {np.pi / 2}) q[4], q[5];", ), ( Gate.MS(angle_1=0.17, angle_2=3.45), @@ -1015,34 +1015,22 @@ def test_pulse_gate_to_matrix(): @pytest.mark.parametrize( "gate, target, control, control_state, expected_ir", ( - (Gate.H(), QubitSet(0), QubitSet(1), None, "ctrl @ h __qubits__[1], __qubits__[0];"), - ( - Gate.H(), - QubitSet(0), - QubitSet([1, 2]), - None, - "ctrl(2) @ h __qubits__[1], __qubits__[2], __qubits__[0];", - ), - ( - Gate.Ry(angle=1.23), - QubitSet(0), - QubitSet([2]), - None, - "ctrl @ ry(1.23) __qubits__[2], __qubits__[0];", - ), + (Gate.H(), QubitSet(0), QubitSet(1), None, "ctrl @ h q[1], q[0];"), + (Gate.H(), QubitSet(0), QubitSet([1, 2]), None, "ctrl(2) @ h q[1], q[2], q[0];"), + (Gate.Ry(angle=1.23), QubitSet(0), QubitSet([2]), None, "ctrl @ ry(1.23) q[2], q[0];"), ( Gate.MS(angle_1=0.17, angle_2=3.45), QubitSet(0), QubitSet([1, 2]), None, - f"ctrl(2) @ ms(0.17, 3.45, {np.pi / 2}) __qubits__[1], __qubits__[2], __qubits__[0];", + f"ctrl(2) @ ms(0.17, 3.45, {np.pi / 2}) q[1], q[2], q[0];", ), ( Gate.CCNot(), QubitSet([0, 1, 2]), QubitSet([3, 4]), None, - "ctrl(2) @ ccnot __qubits__[3], __qubits__[4], __qubits__[0], __qubits__[1], __qubits__[2];", # noqa + "ctrl(2) @ ccnot q[3], q[4], q[0], q[1], q[2];", ), ( Gate.Z(), @@ -1050,7 +1038,7 @@ def test_pulse_gate_to_matrix(): QubitSet([1, 2, 3, 4, 5, 6, 7]), [1, 1, 1, 0, 0, 1, 0], "ctrl(3) @ negctrl(2) @ ctrl @ negctrl @ " - "z __qubits__[1], __qubits__[2], __qubits__[3], __qubits__[4], __qubits__[5], __qubits__[6], __qubits__[7], __qubits__[0];", # noqa + "z q[1], q[2], q[3], q[4], q[5], q[6], q[7], q[0];", ), ( Gate.Z(), @@ -1058,7 +1046,7 @@ def test_pulse_gate_to_matrix(): QubitSet([1, 2, 3, 4, 5, 6, 7]), "1110010", "ctrl(3) @ negctrl(2) @ ctrl @ negctrl @ " - "z __qubits__[1], __qubits__[2], __qubits__[3], __qubits__[4], __qubits__[5], __qubits__[6], __qubits__[7], __qubits__[0];", # noqa + "z q[1], q[2], q[3], q[4], q[5], q[6], q[7], q[0];", ), ( Gate.Z(), @@ -1066,21 +1054,21 @@ def test_pulse_gate_to_matrix(): QubitSet([1, 2, 3, 4, 5, 6, 7]), 114, "ctrl(3) @ negctrl(2) @ ctrl @ negctrl @ " - "z __qubits__[1], __qubits__[2], __qubits__[3], __qubits__[4], __qubits__[5], __qubits__[6], __qubits__[7], __qubits__[0];", # noqa + "z q[1], q[2], q[3], q[4], q[5], q[6], q[7], q[0];", ), ( Gate.Z(), QubitSet([0]), QubitSet([1, 2, 3]), [1, 0], - "negctrl @ ctrl @ negctrl @ z __qubits__[1], __qubits__[2], __qubits__[3], __qubits__[0];", # noqa + "negctrl @ ctrl @ negctrl @ z q[1], q[2], q[3], q[0];", ), ( Gate.Z(), QubitSet([0]), QubitSet([1, 2, 3]), "10", - "negctrl @ ctrl @ negctrl @ z __qubits__[1], __qubits__[2], __qubits__[3], __qubits__[0];", # noqa + "negctrl @ ctrl @ negctrl @ z q[1], q[2], q[3], q[0];", ), ), ) @@ -1139,13 +1127,13 @@ def test_gate_control_invalid_state(control, control_state, error_string): @pytest.mark.parametrize( "gate, target, power, expected_ir", ( - (Gate.H(), QubitSet(0), 2, "pow(2) @ h __qubits__[0];"), - (Gate.H(), QubitSet(0), 2.0, "pow(2.0) @ h __qubits__[0];"), - (Gate.H(), QubitSet(0), 2.5, "pow(2.5) @ h __qubits__[0];"), - (Gate.H(), QubitSet(0), 0, "pow(0) @ h __qubits__[0];"), - (Gate.H(), QubitSet(0), -2, "inv @ pow(2) @ h __qubits__[0];"), - (Gate.H(), QubitSet(0), -2.0, "inv @ pow(2.0) @ h __qubits__[0];"), - (Gate.H(), QubitSet(0), -2.5, "inv @ pow(2.5) @ h __qubits__[0];"), + (Gate.H(), QubitSet(0), 2, "pow(2) @ h q[0];"), + (Gate.H(), QubitSet(0), 2.0, "pow(2.0) @ h q[0];"), + (Gate.H(), QubitSet(0), 2.5, "pow(2.5) @ h q[0];"), + (Gate.H(), QubitSet(0), 0, "pow(0) @ h q[0];"), + (Gate.H(), QubitSet(0), -2, "inv @ pow(2) @ h q[0];"), + (Gate.H(), QubitSet(0), -2.0, "inv @ pow(2.0) @ h q[0];"), + (Gate.H(), QubitSet(0), -2.5, "inv @ pow(2.5) @ h q[0];"), ), ) def test_gate_power(gate, target, power, expected_ir): diff --git a/test/unit_tests/braket/circuits/test_noises.py b/test/unit_tests/braket/circuits/test_noises.py index 64710c8f..2b55dfa4 100644 --- a/test/unit_tests/braket/circuits/test_noises.py +++ b/test/unit_tests/braket/circuits/test_noises.py @@ -547,7 +547,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.BitFlip(0.5), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "#pragma braket noise bit_flip(0.5) __qubits__[3]", + "#pragma braket noise bit_flip(0.5) q[3]", ), ( Noise.BitFlip(0.5), @@ -559,7 +559,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.PhaseFlip(0.5), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "#pragma braket noise phase_flip(0.5) __qubits__[3]", + "#pragma braket noise phase_flip(0.5) q[3]", ), ( Noise.PhaseFlip(0.5), @@ -571,7 +571,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.PauliChannel(0.1, 0.2, 0.3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "#pragma braket noise pauli_channel(0.1, 0.2, 0.3) __qubits__[3]", + "#pragma braket noise pauli_channel(0.1, 0.2, 0.3) q[3]", ), ( Noise.PauliChannel(0.1, 0.2, 0.3), @@ -583,7 +583,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.Depolarizing(0.5), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "#pragma braket noise depolarizing(0.5) __qubits__[3]", + "#pragma braket noise depolarizing(0.5) q[3]", ), ( Noise.Depolarizing(0.5), @@ -595,7 +595,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.TwoQubitDepolarizing(0.5), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3, 5], - "#pragma braket noise two_qubit_depolarizing(0.5) __qubits__[3], __qubits__[5]", + "#pragma braket noise two_qubit_depolarizing(0.5) q[3], q[5]", ), ( Noise.TwoQubitDepolarizing(0.5), @@ -607,7 +607,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.TwoQubitDephasing(0.5), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3, 5], - "#pragma braket noise two_qubit_dephasing(0.5) __qubits__[3], __qubits__[5]", + "#pragma braket noise two_qubit_dephasing(0.5) q[3], q[5]", ), ( Noise.TwoQubitDephasing(0.5), @@ -619,7 +619,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.AmplitudeDamping(0.5), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "#pragma braket noise amplitude_damping(0.5) __qubits__[3]", + "#pragma braket noise amplitude_damping(0.5) q[3]", ), ( Noise.AmplitudeDamping(0.5), @@ -631,7 +631,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.GeneralizedAmplitudeDamping(0.5, 0.1), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "#pragma braket noise generalized_amplitude_damping(0.5, 0.1) __qubits__[3]", + "#pragma braket noise generalized_amplitude_damping(0.5, 0.1) q[3]", ), ( Noise.GeneralizedAmplitudeDamping(0.5, 0.1), @@ -643,7 +643,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): Noise.PhaseDamping(0.5), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "#pragma braket noise phase_damping(0.5) __qubits__[3]", + "#pragma braket noise phase_damping(0.5) q[3]", ), ( Noise.PhaseDamping(0.5), @@ -668,7 +668,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): "[0, 0.31622776601683794, 0, 0], " "[0.31622776601683794, 0, 0, 0], " "[0, 0, 0, 0.31622776601683794], " - "[0, 0, 0.31622776601683794, 0]]) __qubits__[3], __qubits__[5]", + "[0, 0, 0.31622776601683794, 0]]) q[3], q[5]", ), ( Noise.Kraus( @@ -700,7 +700,7 @@ def test_valid_values_pauli_channel_two_qubit(probs): [3], "#pragma braket noise kraus([" "[0.9486833im, 0], [0, 0.9486833im]], [" - "[0, 0.31622777], [0.31622777, 0]]) __qubits__[3]", + "[0, 0.31622777], [0.31622777, 0]]) q[3]", ), ( Noise.Kraus( diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index 41578637..79f917af 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -67,7 +67,7 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv Observable.I(), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "i(__qubits__[3])", + "i(q[3])", ), ( Observable.I(), @@ -85,7 +85,7 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv Observable.X(), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "x(__qubits__[3])", + "x(q[3])", ), ( Observable.X(), @@ -103,7 +103,7 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv Observable.Y(), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "y(__qubits__[3])", + "y(q[3])", ), ( Observable.Y(), @@ -121,7 +121,7 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv Observable.Z(), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "z(__qubits__[3])", + "z(q[3])", ), ( Observable.Z(), @@ -139,7 +139,7 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv Observable.H(), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], - "h(__qubits__[3])", + "h(q[3])", ), ( Observable.H(), @@ -158,7 +158,7 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv 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]]) __qubits__[1], __qubits__[2]", + "[0im, 0im, 1+0im, 0im], [0im, 0im, 0im, 1+0im]]) q[1], q[2]", ), ( Observable.Hermitian(np.eye(4)), @@ -177,7 +177,7 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv Observable.H() @ Observable.Z(), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3, 0], - "h(__qubits__[3]) @ z(__qubits__[0])", + "h(q[3]) @ z(q[0])", ), ( Observable.H() @ Observable.Z(), @@ -189,7 +189,7 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv Observable.H() @ Observable.Z() @ Observable.I(), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3, 0, 1], - "h(__qubits__[3]) @ z(__qubits__[0]) @ i(__qubits__[1])", + "h(q[3]) @ z(q[0]) @ i(q[1])", ), ( Observable.H() @ Observable.Z() @ Observable.I(), @@ -202,8 +202,8 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3, 0, 1], "hermitian([[1+0im, 0im, 0im, 0im], [0im, 1+0im, 0im, 0im], " - "[0im, 0im, 1+0im, 0im], [0im, 0im, 0im, 1+0im]]) __qubits__[3], __qubits__[0]" - " @ i(__qubits__[1])", + "[0im, 0im, 1+0im, 0im], [0im, 0im, 0im, 1+0im]]) q[3], q[0]" + " @ i(q[1])", ), ( Observable.I() @ Observable.Hermitian(np.eye(4)), diff --git a/test/unit_tests/braket/circuits/test_result_types.py b/test/unit_tests/braket/circuits/test_result_types.py index 8b3b9cd0..5f76eeaf 100644 --- a/test/unit_tests/braket/circuits/test_result_types.py +++ b/test/unit_tests/braket/circuits/test_result_types.py @@ -155,7 +155,7 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): ( ResultType.Expectation(Observable.I(), target=0), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result expectation i(__qubits__[0])", + "#pragma braket result expectation i(q[0])", ), ( ResultType.Expectation(Observable.I()), @@ -175,7 +175,7 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): ( ResultType.DensityMatrix([0, 2]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result density_matrix __qubits__[0], __qubits__[2]", + "#pragma braket result density_matrix q[0], q[2]", ), ( ResultType.DensityMatrix(0), @@ -195,7 +195,7 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): ( ResultType.Probability([0, 2]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result probability __qubits__[0], __qubits__[2]", + "#pragma braket result probability q[0], q[2]", ), ( ResultType.Probability(0), @@ -205,7 +205,7 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): ( ResultType.Sample(Observable.I(), target=0), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result sample i(__qubits__[0])", + "#pragma braket result sample i(q[0])", ), ( ResultType.Sample(Observable.I()), @@ -215,7 +215,7 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): ( ResultType.Variance(Observable.I(), target=0), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result variance i(__qubits__[0])", + "#pragma braket result variance i(q[0])", ), ( ResultType.Variance(Observable.I()), @@ -225,12 +225,12 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): ( ResultType.AdjointGradient(Observable.I(), target=0, parameters=["alpha", "beta"]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result adjoint_gradient expectation(i(__qubits__[0])) alpha, beta", + "#pragma braket result adjoint_gradient expectation(i(q[0])) alpha, beta", ), ( ResultType.AdjointGradient(Observable.I(), target=0, parameters=["alpha"]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result adjoint_gradient expectation(i(__qubits__[0])) alpha", + "#pragma braket result adjoint_gradient expectation(i(q[0])) alpha", ), ( ResultType.AdjointGradient( @@ -239,7 +239,7 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): parameters=[FreeParameter("alpha"), "beta", FreeParameter("gamma")], ), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result adjoint_gradient expectation(h(__qubits__[0]) @ i(__qubits__[1])) " # noqa + "#pragma braket result adjoint_gradient expectation(h(q[0]) @ i(q[1])) " "alpha, beta, gamma", ), ( @@ -249,20 +249,20 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): parameters=[FreeParameter("alpha"), "beta", FreeParameter("gamma")], ), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result adjoint_gradient expectation(h(__qubits__[0]) @ i(__qubits__[1]) + 2 * z(__qubits__[2])) " # noqa + "#pragma braket result adjoint_gradient expectation(h(q[0]) @ i(q[1]) + 2 * z(q[2])) " "alpha, beta, gamma", ), ( ResultType.AdjointGradient(Observable.I(), target=0, parameters=[]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result adjoint_gradient expectation(i(__qubits__[0])) all", + "#pragma braket result adjoint_gradient expectation(i(q[0])) all", ), ( ResultType.AdjointGradient( Observable.X() @ Observable.Y(), target=[0, 1], parameters=[] ), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result adjoint_gradient expectation(x(__qubits__[0]) @ y(__qubits__[1])) all", # noqa + "#pragma braket result adjoint_gradient expectation(x(q[0]) @ y(q[1])) all", ), ( ResultType.AdjointGradient( @@ -270,7 +270,7 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): ), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), "#pragma braket result adjoint_gradient expectation(hermitian([[1+0im, 0im], " - "[0im, 1+0im]]) __qubits__[0]) all", + "[0im, 1+0im]]) q[0]) all", ), ], ) diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 736c510a..08f2a19c 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -404,10 +404,10 @@ def test_run_gate_model_inputs(): ( "OPENQASM 3.0;", "input float theta;", - "bit[1] __bits__;", - "qubit[1] __qubits__;", - "rx(theta) __qubits__[0];", - "__bits__[0] = measure __qubits__[0];", + "bit[1] b;", + "qubit[1] q;", + "rx(theta) q[0];", + "b[0] = measure q[0];", ) ), inputs={"theta": 2}, diff --git a/test/unit_tests/braket/parametric/test_free_parameter.py b/test/unit_tests/braket/parametric/test_free_parameter.py index b3b391a8..816bc0ce 100644 --- a/test/unit_tests/braket/parametric/test_free_parameter.py +++ b/test/unit_tests/braket/parametric/test_free_parameter.py @@ -61,36 +61,3 @@ def test_sub_successful(free_parameter): def test_sub_wrong_param(free_parameter): assert free_parameter.subs({"alpha": 1}) == FreeParameter("theta") - - -@pytest.mark.parametrize( - "name", - ( - "a", - "b", - "q", - "bit", - "qubit", - "_a", - "\u03B8", - "a\u03B8", - "a123", - "z123", - "\u03B8\u03B8", - "\u03B8a1", - ), -) -def test_valid_names(name): - FreeParameter(name) - - -@pytest.mark.parametrize( - "name", - ( - "", - "__a", - ), -) -def test_invalid_names(name): - with pytest.raises(ValueError): - FreeParameter(name) From 63c1f44d23b0da1afca016c42cec6ebf200cbb3c Mon Sep 17 00:00:00 2001 From: Angela Guo Date: Wed, 13 Sep 2023 12:22:11 -0700 Subject: [PATCH 0848/1165] infra: update codeowner file to amazon-braket/braket-dev (#699) Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 2e2d8f10..df13e062 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,4 +3,4 @@ # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, these accounts # will be requested for review when someone opens a pull request. -* @aws/amazon-braket-maintainers +* @amazon-braket/braket-maintainers From ccda0ec7a17f25eab9c86ffdc543edfc71ca42be Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 13 Sep 2023 16:16:33 -0700 Subject: [PATCH 0849/1165] doc: Replace aws org with amazon-braket (#705) --- .github/pull_request_template.md | 6 ++--- CONTRIBUTING.md | 12 +++++----- README.md | 12 +++++----- doc/examples-adv-circuits-algorithms.rst | 24 +++++++++---------- doc/examples-braket-features.rst | 30 ++++++++++++------------ doc/examples-getting-started.rst | 30 ++++++++++++------------ doc/examples-hybrid-jobs.rst | 24 +++++++++---------- doc/examples-hybrid-quantum.rst | 18 +++++++------- doc/examples-ml-pennylane.rst | 24 +++++++++---------- doc/examples.rst | 4 ++-- doc/index.rst | 2 +- setup.py | 2 +- tox.ini | 6 ++--- 13 files changed, 97 insertions(+), 97 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 819c4301..87dda8a1 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -10,9 +10,9 @@ _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/aws/amazon-braket-sdk-python/blob/main/CONTRIBUTING.md) doc -- [ ] I used the commit message format described in [CONTRIBUTING](https://github.com/aws/amazon-braket-sdk-python/blob/main/CONTRIBUTING.md#commit-your-change) -- [ ] I have updated any necessary documentation, including [READMEs](https://github.com/aws/amazon-braket-sdk-python/blob/main/README.md) and [API docs](https://github.com/aws/amazon-braket-sdk-python/blob/main/CONTRIBUTING.md#documentation-guidelines) (if appropriate) +- [ ] 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 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 1e659a5b..7362a17b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,7 @@ information to effectively respond to your bug report or contribution. We welcome you to use the GitHub issue tracker to report bugs or suggest features. -When filing an issue, please check [existing open](https://github.com/aws/amazon-braket-sdk-python/issues) and [recently closed](https://github.com/aws/amazon-braket-sdk-python/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20) issues to make sure somebody else hasn't already +When filing an issue, please check [existing open](https://github.com/amazon-braket/amazon-braket-sdk-python/issues) and [recently closed](https://github.com/amazon-braket/amazon-braket-sdk-python/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20) issues to make sure somebody else hasn't already reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: * A reproducible test case or series of steps. @@ -199,7 +199,7 @@ If a parameter of a function has a default value, please note what the default i If that default value is `None`, it can also be helpful to explain what happens when the parameter is `None`. If `**kwargs` is part of the function signature, link to the parent class(es) or method(s) so that the reader knows where to find the available parameters. -For an example file with docstrings, see [the `circuit` module](https://github.com/aws/amazon-braket-sdk-python/blob/main/src/braket/circuits/circuit.py). +For an example file with docstrings, see [the `circuit` module](https://github.com/amazon-braket/amazon-braket-sdk-python/blob/main/src/braket/circuits/circuit.py). ### Build and Test Documentation @@ -215,12 +215,12 @@ You can then find the generated HTML files in `build/documentation/html`. ## Find Contributions to Work On -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/aws/amazon-braket-sdk-python/labels/help%20wanted) issues is a great place to start. +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/aws/amazon-braket-pennylane-plugin-python), [Strawberryfields](https://github.com/aws/amazon-braket-strawberryfields-plugin-python) and [DWave's Ocean library](https://github.com/aws/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/aws/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 @@ -236,6 +236,6 @@ If you discover a potential security issue in this project we ask that you notif ## Licensing -See the [LICENSE](https://github.com/aws/amazon-braket-sdk-python/blob/main/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. +See the [LICENSE](https://github.com/amazon-braket/amazon-braket-sdk-python/blob/main/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. diff --git a/README.md b/README.md index 1f7fcb46..f276de5d 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ [![Latest Version](https://img.shields.io/pypi/v/amazon-braket-sdk.svg)](https://pypi.python.org/pypi/amazon-braket-sdk) [![Supported Python Versions](https://img.shields.io/pypi/pyversions/amazon-braket-sdk.svg)](https://pypi.python.org/pypi/amazon-braket-sdk) -[![Build status](https://github.com/aws/amazon-braket-sdk-python/actions/workflows/python-package.yml/badge.svg?branch=main)](https://github.com/aws/amazon-braket-sdk-python/actions/workflows/python-package.yml) -[![codecov](https://codecov.io/gh/aws/amazon-braket-sdk-python/branch/main/graph/badge.svg?token=1lsqkZL3Ll)](https://codecov.io/gh/aws/amazon-braket-sdk-python) +[![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) 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. @@ -44,7 +44,7 @@ pip install amazon-braket-sdk You can also install from source by cloning this repository and running a pip install command in the root directory of the repository: ```bash -git clone https://github.com/aws/amazon-braket-sdk-python.git +git clone https://github.com/amazon-braket/amazon-braket-sdk-python.git cd amazon-braket-sdk-python pip install . ``` @@ -155,7 +155,7 @@ To select a quantum hardware device, specify its ARN as the value of the `device **Important** Quantum tasks may not run immediately on the QPU. The QPUs only execute quantum tasks during execution windows. To find their execution windows, please refer to the [AWS console](https://console.aws.amazon.com/braket/home) in the "Devices" tab. ## Sample Notebooks -Sample Jupyter notebooks can be found in the [amazon-braket-examples](https://github.com/aws/amazon-braket-examples/) repo. +Sample Jupyter notebooks can be found in the [amazon-braket-examples](https://github.com/amazon-braket/amazon-braket-examples/) repo. ## Braket Python SDK API Reference Documentation @@ -233,13 +233,13 @@ 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/aws/amazon-braket-sdk-python/issues/). +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) and add the tag 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/aws/amazon-braket-sdk-python/issues/) is our preferred mechanism for collecting feedback and feature requests, allowing other users +[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. ## License diff --git a/doc/examples-adv-circuits-algorithms.rst b/doc/examples-adv-circuits-algorithms.rst index ea99a475..b37e87e3 100644 --- a/doc/examples-adv-circuits-algorithms.rst +++ b/doc/examples-adv-circuits-algorithms.rst @@ -7,9 +7,9 @@ Learn more about working with advanced circuits and algoritms. .. toctree:: :maxdepth: 2 -************************************************************************************************************************************************ -`Grover's search algorithm `_ -************************************************************************************************************************************************ +********************************************************************************************************************************************************** +`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 @@ -17,9 +17,9 @@ 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 `_ -******************************************************************************************************************************************************************************************************** +****************************************************************************************************************************************************************************************************************** +`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 @@ -29,18 +29,18 @@ target states in a given search space. In a quantum computer, QAA can be used to quadratic speedup over several classical algorithms. -************************************************************************************************************************************************************************************** -`Quantum Fourier transform `_ -************************************************************************************************************************************************************************************** +************************************************************************************************************************************************************************************************ +`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. -*********************************************************************************************************************************************************************************** -`Quantum phase estimation `_ -*********************************************************************************************************************************************************************************** +********************************************************************************************************************************************************************************************* +`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 diff --git a/doc/examples-braket-features.rst b/doc/examples-braket-features.rst index 1bfc9c0d..75361f17 100644 --- a/doc/examples-braket-features.rst +++ b/doc/examples-braket-features.rst @@ -7,40 +7,40 @@ Learn more about the indivudal features of Amazon Braket. .. toctree:: :maxdepth: 2 -******************************************************************************************************************************************************************************************************************************* -`Getting notifications when a quantum task completes `_ -******************************************************************************************************************************************************************************************************************************* +***************************************************************************************************************************************************************************************************************************************************************** +`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. -************************************************************************************************************************************************************* -`Allocating Qubits on QPU Devices `_ -************************************************************************************************************************************************************* +*********************************************************************************************************************************************************************** +`Allocating Qubits on QPU Devices `_ +*********************************************************************************************************************************************************************** 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 `_ -***************************************************************************************************************************************************************************************** +*************************************************************************************************************************************************************************************************** +`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) programatically, and how to gain access to their properties. -************************************************************************************************************************************************************************* -`Using the tensor network simulator TN1 `_ -************************************************************************************************************************************************************************* +*********************************************************************************************************************************************************************************** +`Using the 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. -*************************************************************************************************************************************************************** -`Simulating noise on Amazon Braket `_ -*************************************************************************************************************************************************************** +************************************************************************************************************************************************************************* +`Simulating noise on Amazon Braket `_ +************************************************************************************************************************************************************************* This notebook provides a detailed overview of noise simulation on Amazon Braket. You will learn how to define noise channels, apply noise to new or existing circuits, and run those circuits diff --git a/doc/examples-getting-started.rst b/doc/examples-getting-started.rst index 9523320c..8c9eb90f 100644 --- a/doc/examples-getting-started.rst +++ b/doc/examples-getting-started.rst @@ -7,15 +7,15 @@ Get started on Amazon Braket with some introductory examples. .. toctree:: :maxdepth: 2 -*********************************************************************************************************************************************** -`Getting started `_ -*********************************************************************************************************************************************** +********************************************************************************************************************************************************* +`Getting started `_ +********************************************************************************************************************************************************* A hello-world tutorial that shows you how to build a simple circuit and run it on a local simulator. -******************************************************************************************************************************************************************************************************************** -`Running quantum circuits on simulators `_ -******************************************************************************************************************************************************************************************************************** +****************************************************************************************************************************************************************************************************************************** +`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). @@ -26,9 +26,9 @@ and quantum metrology. **Note:** When a circuit is ran using a simulator, customers are required to use contiguous qubits/indices. -*********************************************************************************************************************************************************************************************************************** -`Running quantum circuits on QPU devices `_ -*********************************************************************************************************************************************************************************************************************** +********************************************************************************************************************************************************************************************************************************* +`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 @@ -36,9 +36,9 @@ 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 `_ -******************************************************************************************************************************************************************************************************************************************** +****************************************************************************************************************************************************************************************************************************************************** +`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 @@ -47,9 +47,9 @@ 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 `_ -***************************************************************************************************************************************************** +*************************************************************************************************************************************************************** +`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 diff --git a/doc/examples-hybrid-jobs.rst b/doc/examples-hybrid-jobs.rst index 76b2026e..af873407 100644 --- a/doc/examples-hybrid-jobs.rst +++ b/doc/examples-hybrid-jobs.rst @@ -7,27 +7,27 @@ Learn more about hybrid jobs on Amazon Braket. .. toctree:: :maxdepth: 2 -************************************************************************************************************************************************************************************** -`Creating your first Hybrid Job `_ -************************************************************************************************************************************************************************************** +************************************************************************************************************************************************************************************************ +`Creating your first Hybrid Job `_ +************************************************************************************************************************************************************************************************ This tutorial shows how to run your first Amazon Braket Hybrid Job. -*********************************************************************************************************************************************************************************************************************************************************** -`Quantum machine learning in Amazon Braket Hybrid Jobs `_ -*********************************************************************************************************************************************************************************************************************************************************** +********************************************************************************************************************************************************************************************************************************************************************* +`Quantum machine learning in Amazon Braket Hybrid Jobs `_ +********************************************************************************************************************************************************************************************************************************************************************* This notebook demonstrates a typical quantum machine learning workflow, including uploading data, monitoring training, and tuning hyperparameters. -******************************************************************************************************************************************************************************************** -`Using Pennylane with Braket Hybrid Jobs `_ -******************************************************************************************************************************************************************************************** +*************************************************************************************************************************************************************************************************************************** +`Using Pennylane with Braket Hybrid Jobs `_ +*************************************************************************************************************************************************************************************************************************** In this tutorial, we use PennyLane within Amazon Braket Hybrid Jobs to run the Quantum Approximate Optimization Algorithm (QAOA) on a Max-Cut problem. -******************************************************************************************************************************************************************** -`Bring your own container `_ -******************************************************************************************************************************************************************** +****************************************************************************************************************************************************************************** +`Bring your own container `_ +****************************************************************************************************************************************************************************** Amazon Braket has pre-configured containers for executing Amazon Braket Hybrid Jobs, which are sufficient for many use cases involving the Braket SDK and PennyLane. However, if we want to use custom packages outside the scope of pre-configured containers, we have the ability to supply a custom-built container. In this tutorial, we show how to use Braket Hybrid Jobs to train a quantum machine learning model using BYOC (Bring Your Own Container). diff --git a/doc/examples-hybrid-quantum.rst b/doc/examples-hybrid-quantum.rst index b30083af..9c7f3aca 100644 --- a/doc/examples-hybrid-quantum.rst +++ b/doc/examples-hybrid-quantum.rst @@ -7,23 +7,23 @@ Learn more about hybrid quantum algorithms. .. toctree:: :maxdepth: 2 -*************************************************************************************************************************** -`QAOA `_ -*************************************************************************************************************************** +************************************************************************************************************************************* +`QAOA `_ +************************************************************************************************************************************* This tutorial shows how to (approximately) solve binary combinatorial optimization problems using the Quantum Approximate Optimization Algorithm (QAOA). -************************************************************************************************************************************************************************** -`VQE Transverse Ising `_ -************************************************************************************************************************************************************************** +************************************************************************************************************************************************************************************ +`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). -****************************************************************************************************************************************************** -`VQE Chemistry `_ -****************************************************************************************************************************************************** +**************************************************************************************************************************************************************** +`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. diff --git a/doc/examples-ml-pennylane.rst b/doc/examples-ml-pennylane.rst index dae85912..5c7db93a 100644 --- a/doc/examples-ml-pennylane.rst +++ b/doc/examples-ml-pennylane.rst @@ -7,16 +7,16 @@ Learn more about how to combine PennyLane with Amazon Braket. .. toctree:: :maxdepth: 2 -**************************************************************************************************************************************************************** -`Combining PennyLane with Amazon Braket `_ -**************************************************************************************************************************************************************** +************************************************************************************************************************************************************************** +`Combining PennyLane with Amazon Braket `_ +************************************************************************************************************************************************************************** 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 `_ -******************************************************************************************************************************************************************************************************************************************* +***************************************************************************************************************************************************************************************************************************************************** +`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 @@ -25,9 +25,9 @@ The tutorial benchmarks SV1 against a local simulator, showing that SV1 outperfo local simulator for both executions and gradient calculations. This illustrates how parallel capabilities can be combined between PennyLane and SV1. -******************************************************************************************************************************************************************************** -`Graph optimization with QAOA `_ -******************************************************************************************************************************************************************************** +****************************************************************************************************************************************************************************************** +`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 @@ -36,9 +36,9 @@ then extends to a more difficult 20-node graph and uses the parallel capabilitie 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 `_ -***************************************************************************************************************************************************************************************************** +*************************************************************************************************************************************************************************************************************** +`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 diff --git a/doc/examples.rst b/doc/examples.rst index e607920a..87c2e1f7 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -3,7 +3,7 @@ Examples ######## There are several examples available in the Amazon Braket repo: -https://github.com/aws/amazon-braket-examples. +https://github.com/amazon-braket/amazon-braket-examples. .. toctree:: :maxdepth: 2 @@ -15,4 +15,4 @@ https://github.com/aws/amazon-braket-examples. examples-ml-pennylane.rst examples-hybrid-jobs.rst - \ No newline at end of file + diff --git a/doc/index.rst b/doc/index.rst index 38dce728..8d996f4c 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -6,7 +6,7 @@ The Amazon Braket Python SDK is an open source library to design and build quant 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 -homepage is in Github https://github.com/aws/amazon-braket-sdk-python. The project +homepage is in Github https://github.com/amazon-braket/amazon-braket-sdk-python. The project includes SDK source, installation instructions, and other information. *************** diff --git a/setup.py b/setup.py index 25fca825..f21f27c3 100644 --- a/setup.py +++ b/setup.py @@ -62,7 +62,7 @@ ] }, include_package_data=True, - url="https://github.com/aws/amazon-braket-sdk-python", + url="https://github.com/amazon-braket/amazon-braket-sdk-python", author="Amazon Web Services", description=( "An open source library for interacting with quantum computing devices on Amazon Braket" diff --git a/tox.ini b/tox.ini index af1fd909..59de91a1 100644 --- a/tox.ini +++ b/tox.ini @@ -47,7 +47,7 @@ skip_install = true deps = flake8 flake8-rst-docstrings - git+https://github.com/aws/amazon-braket-build-tools.git + git+https://github.com/amazon-braket/amazon-braket-build-tools.git commands = flake8 {posargs} flake8 --enable-extensions=BCS src @@ -94,5 +94,5 @@ commands = [test-deps] deps = # If you need to test on a certain branch, add @ after .git - git+https://github.com/aws/amazon-braket-schemas-python.git - git+https://github.com/aws/amazon-braket-default-simulator-python.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 12b3f44f13e3ca9e5d433415facc7397a6ea1b6c Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 14 Sep 2023 16:14:26 +0000 Subject: [PATCH 0850/1165] prepare release v1.55.1 --- 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 e83803ba..c3f5eccb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.55.1 (2023-09-14) + +### Bug Fixes and Other Changes + + * Revert "update: restricting parameter names to not collide with ones we use for OpenQASM generation. (#675)" + +### Documentation Changes + + * Replace aws org with amazon-braket + ## v1.55.0 (2023-09-09) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 46367d95..56a67c23 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.55.1.dev0" +__version__ = "1.55.1" From 0bd52d213faa3cff0ad71fd06b2b52521edecafc Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 14 Sep 2023 16:14:26 +0000 Subject: [PATCH 0851/1165] update development version to v1.55.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 56a67c23..865a962d 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.55.1" +__version__ = "1.55.2.dev0" From 9d5fd39d90542236df62408b3fe66348985daa6a Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 14 Sep 2023 10:33:54 -0700 Subject: [PATCH 0852/1165] doc: change the sphinx requirement to be greater than 7.0.0 (#704) Co-authored-by: Cody Wang --- doc/requirements.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 6a86be51..b80e6baf 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,4 +1,3 @@ -docutils<0.18 -sphinx -sphinx-rtd-theme +sphinx>7 +sphinx-rtd-theme>=1.3.0 sphinxcontrib-apidoc From a03e42ab19a85c8189fc44013c344513b1b66307 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 14 Sep 2023 11:00:49 -0700 Subject: [PATCH 0853/1165] doc: add code contributors to the readme (#703) Co-authored-by: Cody Wang --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f276de5d..779c13c9 100644 --- a/README.md +++ b/README.md @@ -242,5 +242,9 @@ If you have feedback or features that you would like to see on Amazon Braket, we [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 + +[![Contributors](https://contrib.rocks/image?repo=amazon-braket/amazon-braket-sdk-python)](https://github.com/amazon-braket/amazon-braket-sdk-python/graphs/contributors)``` + ## License This project is licensed under the Apache-2.0 License. From 687f70670ecbe991fccb601da7bfb3de00a298aa Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 14 Sep 2023 12:15:39 -0700 Subject: [PATCH 0854/1165] doc: Remove trailing backquotes (#706) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 779c13c9..d8533713 100644 --- a/README.md +++ b/README.md @@ -244,7 +244,7 @@ to engage in the conversation, and +1 issues to help drive priority. ### Code contributors -[![Contributors](https://contrib.rocks/image?repo=amazon-braket/amazon-braket-sdk-python)](https://github.com/amazon-braket/amazon-braket-sdk-python/graphs/contributors)``` +[![Contributors](https://contrib.rocks/image?repo=amazon-braket/amazon-braket-sdk-python)](https://github.com/amazon-braket/amazon-braket-sdk-python/graphs/contributors) ## License This project is licensed under the Apache-2.0 License. From 5ca2d163589547cac2f569e0eaed259810673f00 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Fri, 15 Sep 2023 06:06:27 -0700 Subject: [PATCH 0855/1165] infra: update the pre-commit hook with linters (#678) * infra: update the pre-commit hook with linters and secrets check Co-authored-by: Abe Coull Co-authored-by: Cody Wang --- .git-template/hooks/pre-commit | 5 +++++ .pre-commit-config.yaml | 24 +++++++----------------- tox.ini | 29 +++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 17 deletions(-) create mode 100644 .git-template/hooks/pre-commit diff --git a/.git-template/hooks/pre-commit b/.git-template/hooks/pre-commit new file mode 100644 index 00000000..6d8d24a1 --- /dev/null +++ b/.git-template/hooks/pre-commit @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +if [ -f .pre-commit-config.yaml ]; then + echo 'pre-commit configuration detected, but `pre-commit install` was never run' 1>&2 + exit 1 +fi diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d03e4d3d..e9eea758 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,8 @@ repos: -- repo: local - hooks: - - id: tox - name: tox - stages: [push] - language: system - entry: tox - types: [python] - pass_filenames: false - - - id: tox_integ_tests - name: tox integ tests - stages: [push] - language: system - entry: tox -e integ-tests - types: [python] - pass_filenames: false + - repo: local + hooks: + - id: tox_linters + name: linters + entry: tox -e linters_check + language: system + types: [python] diff --git a/tox.ini b/tox.ini index 59de91a1..b49c673c 100644 --- a/tox.ini +++ b/tox.ini @@ -41,6 +41,19 @@ commands = {[testenv:black]commands} {[testenv:flake8]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} +commands = + {[testenv:isort_check]commands} + {[testenv:black_check]commands} + {[testenv:flake8]commands} + [testenv:flake8] basepython = python3 skip_install = true @@ -60,6 +73,14 @@ deps = commands = isort . {posargs} +[testenv:isort_check] +basepython = python3 +skip_install = true +deps = + isort +commands = + isort . -c {posargs} + [testenv:black] basepython = python3 skip_install = true @@ -68,6 +89,14 @@ deps = commands = black ./ {posargs} +[testenv:black_check] +basepython = python3 +skip_install = true +deps = + black +commands = + black --check . {posargs} + [testenv:docs] basepython = python3 deps = From 6985361a1dcfcc1fa2f7055f483207405868d5e2 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Mon, 18 Sep 2023 11:51:13 -0400 Subject: [PATCH 0856/1165] Return correctly-sized bit register (#707) --- src/braket/experimental/autoqasm/api.py | 14 +++++++++++--- .../braket/experimental/autoqasm/test_api.py | 4 +--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 4a6fb064..e6d14af9 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -437,10 +437,18 @@ def _make_return_instance_from_oqpy_return_type(return_type: Any) -> Any: if not return_type: return None - return_type = aq_types.conversions.var_type_from_ast_type(return_type) - if return_type == aq_types.ArrayVar: + var_type = aq_types.conversions.var_type_from_ast_type(return_type) + if var_type == aq_types.ArrayVar: return [] - return return_type() + if var_type == aq_types.BitVar: + return var_type(size=_get_bitvar_size(return_type)) + return var_type() + + +def _get_bitvar_size(node: qasm_ast.BitType) -> Optional[int]: + if not isinstance(node, qasm_ast.BitType) or not node.size: + return None + return node.size.value def _convert_gate( diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index 6f79b463..5d0973d4 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -483,9 +483,7 @@ def ground_state_measurements_subroutine() -> bit[3] { return __bit_0__; } qubit[6] __qubits__; -""" - # TODO: this should be `bit[3]`, but there's a bug. It's being tracked in an issue. - expected += """bit __bit_1__; +bit[3] __bit_1__ = "000"; __bit_1__ = ground_state_measurements_subroutine();""" assert ground_state_measurements_wrapper().to_ir() == expected From 796b1cf1c4968471c70293280702ec2a7fbc8288 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 18 Sep 2023 16:14:09 +0000 Subject: [PATCH 0857/1165] prepare release v1.55.1.post0 --- 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 c3f5eccb..cb5d60c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## v1.55.1.post0 (2023-09-18) + +### Documentation Changes + + * Remove trailing backquotes + * add code contributors to the readme + * change the sphinx requirement to be greater than 7.0.0 + ## v1.55.1 (2023-09-14) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 865a962d..ab6dc7cd 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.55.2.dev0" +__version__ = "1.55.1.post0" From 10f57238b839f67b97fef9a6fc0a2b1cdcfa5732 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 18 Sep 2023 16:14:09 +0000 Subject: [PATCH 0858/1165] update development version to v1.55.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 ab6dc7cd..865a962d 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.55.1.post0" +__version__ = "1.55.2.dev0" From 7040c1d88d6527d0e0e195ecdf4f88fd28f74e65 Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Wed, 20 Sep 2023 13:59:46 -0400 Subject: [PATCH 0859/1165] add pulse programming notebooks (#700) * add pulse programming notebook * remove overwriting pi * include pulse notebook in tests * remove pulse notebook from test * add gate calibration notebook * add notebook test in tox for calibration notebook * update pulse programming notebook * update the gate calibration notebook * update text to reflect comments * add introduction in a section --- ...programming_and_dynamical_decoupling.ipynb | 285 +++++++++++++ .../6_Customize_gate_calibrations.ipynb | 391 ++++++++++++++++++ tox.ini | 2 + 3 files changed, 678 insertions(+) create mode 100644 examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb create mode 100644 examples/autoqasm/6_Customize_gate_calibrations.ipynb diff --git a/examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb b/examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb new file mode 100644 index 00000000..9b5f695d --- /dev/null +++ b/examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb @@ -0,0 +1,285 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "8e867f9e", + "metadata": {}, + "source": [ + "# Pulse programming and dynamical decoupling" + ] + }, + { + "cell_type": "markdown", + "id": "e9543db0", + "metadata": {}, + "source": [ + "Native gates are a set of carefully calibrated gates directly supported by the hardware. Behind the scenes, native gates themselves are implemented by analog control signals, or \"pulses\", applied to the state of the qubits. Composing a program directly with pulse operations is called \"pulse programming\", giving you even more control than native gates. For example, you can implement quantum operations directly with pulses in order to implement error suppression schemes such as dynamical decoupling, or error mitigation methods such as zero noise extrapolation. You can also improve the elementary operations by experimenting with novel custom gate implementations.\n", + "\n", + "In this notebook, we demonstrate the pulse programming features of AutoQASM with two examples, one with a qubit idling program and one with dynamical decoupling which suppresses the error from qubit idling. These examples are taken from [this blog post](https://aws.amazon.com/blogs/quantum-computing/suppressing-errors-with-dynamical-decoupling-using-pulse-control-on-amazon-braket/) which composes programs using Amazon Braket SDK. This notebook shows you the examples in AutoQASM which provides an integrated experience of composing gate and pulse instructions." + ] + }, + { + "cell_type": "markdown", + "id": "2eeb58e3", + "metadata": {}, + "source": [ + "We start with basic imports, and initialize the Rigetti Aspen-M-3 device which is the targeted device for this example notebook." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e0504f72", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "import braket.experimental.autoqasm as aq\n", + "from braket.experimental.autoqasm import pulse\n", + "from braket.experimental.autoqasm.instructions import rx, rz, measure\n", + "\n", + "from braket.aws import AwsDevice\n", + "from braket.devices import Devices\n", + "\n", + "device = AwsDevice(Devices.Rigetti.AspenM3)" + ] + }, + { + "cell_type": "markdown", + "id": "590f013f", + "metadata": {}, + "source": [ + "Unlike gate operations, pulse operations often target Frame objects instead of qubits. Basic pulse operations include setting the frequency and phase of a frame, playing a waveform, applying a delay, and capturing the output of a frame. The pulse operations are defined in the [pulse module](https://github.com/amazon-braket/amazon-braket-sdk-python/blob/feature/autoqasm/src/braket/experimental/autoqasm/pulse/pulse.py) of AutoQASM. You can read this [documentation](https://docs.aws.amazon.com/braket/latest/developerguide/braket-pulse.html) to learn more about frames and waveforms. The examples below are provided under the assumption that the reader is familiar with the analog aspect of a quantum hardware." + ] + }, + { + "cell_type": "markdown", + "id": "ce6b5cb2", + "metadata": {}, + "source": [ + "## Customize delay with pulse programming\n", + "As a hello-world example, the pulse program below includes a `delay` instruction on the frame of the physical qubit `$0` followed by a capture instruction on the same frame. The pulse program represents measuring a qubit after a variable idling duration. It can be used quantify the decoherence of a qubit by comparing the measured state and the initial state after certain idling duration." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "8ebd3454", + "metadata": {}, + "outputs": [], + "source": [ + "@aq.main\n", + "def idle(qubit: int, idle_duration: float):\n", + " control_frame = device.frames[f\"q{qubit}_rf_frame\"]\n", + " pulse.delay(control_frame, idle_duration)\n", + " pulse.capture_v0(control_frame)" + ] + }, + { + "cell_type": "markdown", + "id": "eeee0f33", + "metadata": {}, + "source": [ + "Below we print out the OpenQASM script of the program. The pulse instructions, delay and capture, are in a `cal` block, which stands for \"calibration\". In OpenQASM, \"calibration-level\" instructions are low-level hardware instructions that can implement higher-level instructions such as gates. A `cal` block therefore defines the scope of these low-level hardware instructions, which are pulse instructions in this example." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "3e76fdca", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "defcalgrammar \"openpulse\";\n", + "cal {\n", + " delay[10.0us] q0_rf_frame;\n", + " capture_v0(q0_rf_frame);\n", + "}\n" + ] + } + ], + "source": [ + "print(idle(0, 10e-6).to_ir())" + ] + }, + { + "cell_type": "markdown", + "id": "098bcda5", + "metadata": {}, + "source": [ + "This example demonstrates how to create a program with only pulse instructions. In the next example, we show that you can compose both gate and pulse instructions in the same `@aq.main` scope. This increases the flexiblity in choosing the right level of abstraction for different parts of the same program." + ] + }, + { + "cell_type": "markdown", + "id": "2b55c594", + "metadata": {}, + "source": [ + "## Error suppression with dynamical decoupling sequences" + ] + }, + { + "cell_type": "markdown", + "id": "05d92b91", + "metadata": {}, + "source": [ + "In the `idle` program above, we intentionally add a delay instruction to the program to make the qubit idle. In general, qubit idling is inevitable. For instance, if an instruction applies to multiple qubits, it will execute only when all of its qubits are available. Some qubits may idle while the other qubits are still undergoing other instructions. During this idling time, the qubits are subject to decoherence, resulting in low program fidelity. Dynamical decoupling is a technique to suppress such errors. It includes alternating pairs of X and Y \"$\\pi$ pulse\", a rotation around the Bloch sphere axis with an angle $\\pi$, on the qubits while they idle. These pulses together are equivalent to an identity gate, but alternating \"$\\pi$ pulses\" cause some of the decoherence to effectively cancel out. To learn more about dynamical decoupling, [this blog post](https://aws.amazon.com/blogs/quantum-computing/suppressing-errors-with-dynamical-decoupling-using-pulse-control-on-amazon-braket/) has a detailed explanations and visualization of dynamical decoupling sequences." + ] + }, + { + "cell_type": "markdown", + "id": "814647c1", + "metadata": {}, + "source": [ + "First, we create the X and Y $\\pi$ pulse in terms of the native gates of Rigetti Aspen-M-3 device. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9130f6b8", + "metadata": {}, + "outputs": [], + "source": [ + "pi = np.pi\n", + "\n", + "def x_pulse(qubit: int):\n", + " # Pi pulse apply on X-axis of Bloch sphere\n", + " qubit = f\"${qubit}\"\n", + " rx(qubit, pi)\n", + "\n", + "def y_pulse(qubit: int):\n", + " # Pi pulse apply on Y-axis of Bloch sphere\n", + " qubit = f\"${qubit}\"\n", + " rz(qubit, -0.5 * pi)\n", + " rx(qubit, pi)\n", + " rz(qubit, +0.5 * pi)" + ] + }, + { + "cell_type": "markdown", + "id": "ffb0e2d1", + "metadata": {}, + "source": [ + "Each cycle of a dynamical decoupling is a sequence of X-Y-X-Y pulses, and each of the four pulses are equally distributed in a cycle. For example, if the idling duration assigned to a cycle is $8\\tau$, each pulse is assigned to the middle of each $2\\tau$ duration. The resulting pulse distribution of a dynamical decoupling cycle is shown in the figure below.\n", + "\n", + "\n", + "\n", + "The program below realizes this pattern of the dynamical decoupling sequence. The sequence is shown as a standalone program, but the same sequence can be inserted into any program that has idling qubits." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "464ee2ed", + "metadata": {}, + "outputs": [], + "source": [ + "@aq.main\n", + "def idle_with_dd(qubit: int, idle_duration: float, n_cycles: int = 1) -> None:\n", + " dd_spacing = idle_duration / (4*n_cycles)\n", + "\n", + " control_frame = device.frames[f\"q{qubit}_rf_frame\"]\n", + " \n", + " pulse.delay(control_frame, dd_spacing)\n", + " for _ in aq.range(n_cycles):\n", + " x_pulse(qubit)\n", + " pulse.delay(control_frame, 2*dd_spacing)\n", + " y_pulse(qubit)\n", + " pulse.delay(control_frame, 2*dd_spacing)\n", + " x_pulse(qubit)\n", + " pulse.delay(control_frame, 2*dd_spacing)\n", + " y_pulse(qubit)\n", + " \n", + " pulse.delay(control_frame, dd_spacing)" + ] + }, + { + "cell_type": "markdown", + "id": "8853db5e", + "metadata": {}, + "source": [ + "The OpenQASM script of the dynamical decoupling program is printed out below. " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "e117caf0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "defcalgrammar \"openpulse\";\n", + "cal {\n", + " delay[833.333333333333ns] q0_rf_frame;\n", + "}\n", + "for int _ in [0:3 - 1] {\n", + " rx(pi) $0;\n", + " cal {\n", + " delay[1.666666666667us] q0_rf_frame;\n", + " }\n", + " rz(-(pi / 2)) $0;\n", + " rx(pi) $0;\n", + " rz(pi / 2) $0;\n", + " cal {\n", + " delay[1.666666666667us] q0_rf_frame;\n", + " }\n", + " rx(pi) $0;\n", + " cal {\n", + " delay[1.666666666667us] q0_rf_frame;\n", + " }\n", + " rz(-(pi / 2)) $0;\n", + " rx(pi) $0;\n", + " rz(pi / 2) $0;\n", + "}\n", + "cal {\n", + " delay[833.333333333333ns] q0_rf_frame;\n", + "}\n" + ] + } + ], + "source": [ + "print(idle_with_dd(0, 10e-6, 3).to_ir())" + ] + }, + { + "cell_type": "markdown", + "id": "dab285b5", + "metadata": {}, + "source": [ + "## Summary\n", + "This example shows you how to use pulse programming to create a program. Just as with a gate-based program, a pulse program in AutoQASM is composed in a function decorated by `@aq.main`. With AutoQASM, the pulse instructions are automatically added to `cal` blocks in OpenQASM script, and gate and pulse instructions can be used side-by-side within the same scope of the AutoQASM program." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/autoqasm/6_Customize_gate_calibrations.ipynb b/examples/autoqasm/6_Customize_gate_calibrations.ipynb new file mode 100644 index 00000000..a58e21c5 --- /dev/null +++ b/examples/autoqasm/6_Customize_gate_calibrations.ipynb @@ -0,0 +1,391 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "dda432a3", + "metadata": {}, + "source": [ + "# Customize gate calibrations\n", + "\n", + "The pulse implementation of a gate is called \"gate calibration\". Gaining access to gate calibrations empowers you to experiment with quantum gate designs and execute quantum programs with customized, improved gate calibrations. This notebook shows you how to access the native gate calibrations provided by quantum hardware providers, and shows you how to define custom gate calibrations for your quantum programs.\n", + "\n", + "We start with basic imports, and initialize the Rigetti Aspen-M-3 device which is the targeted device for this example notebook." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "30fd0b59", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "import braket.experimental.autoqasm as aq\n", + "from braket.experimental.autoqasm import pulse\n", + "from braket.experimental.autoqasm.instructions import rx, rz, measure\n", + "\n", + "from braket.aws import AwsDevice\n", + "from braket.devices import Devices\n", + "from braket.circuits import Gate, QubitSet, FreeParameter\n", + "from braket.pulse import DragGaussianWaveform\n", + "\n", + "device = AwsDevice(Devices.Rigetti.AspenM3)" + ] + }, + { + "cell_type": "markdown", + "id": "f9fdfd5e", + "metadata": {}, + "source": [ + "## Gate calibrations from hardware providers" + ] + }, + { + "cell_type": "markdown", + "id": "5d08537b", + "metadata": {}, + "source": [ + "To customize a gate calibration, you often need to be intimately familiar with how quantum gates are implemented with pulses. It is often a good place to start by viewing the native gate calibrations supplied by hardware providers. To do so, we first retrieve the information from the `gate_calibrations` attribute of a device. " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f1e5a16f", + "metadata": {}, + "outputs": [], + "source": [ + "provider_calibrations = device.gate_calibrations" + ] + }, + { + "cell_type": "markdown", + "id": "959c5fe8", + "metadata": {}, + "source": [ + "Even for the same gate, different physical qubits and angles usually have a different pulse implementation. Because of this, a gate calibration may be associated with specific physical qubits and angles. In the code snippet below, we retrieve the gate calibration of the Rx gate for physical qubit `$0` and `angle=pi/2`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "3248ed04", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "cal {\n", + " waveform wf_drag_gaussian_1 = drag_gaussian(24.0ns, 2.547965400864ns, 2.296242349815984e-10, 0.293772761206776, false);\n", + " barrier $0;\n", + " shift_frequency(q0_rf_frame, -321047.14178613486);\n", + " play(q0_rf_frame, wf_drag_gaussian_1);\n", + " shift_frequency(q0_rf_frame, 321047.14178613486);\n", + " barrier $0;\n", + "}\n" + ] + } + ], + "source": [ + "pulse_sequence_rx_pi_2_q0 = provider_calibrations.pulse_sequences[(Gate.Rx(np.pi / 2), QubitSet(0))]\n", + "\n", + "print(pulse_sequence_rx_pi_2_q0.to_ir())" + ] + }, + { + "cell_type": "markdown", + "id": "e5579f6e", + "metadata": {}, + "source": [ + "Some gate calibrations are defined not with fixed values of angles, but rather with variables. In this case, these gate calibrations can be retrieved with angles of `FreeParameter` type. For example, the code snippet below retrieves the gate calibration of the Rz gate on the qubit `$1` with a variable angle `theta`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "81bb487f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "cal {\n", + " barrier $1;\n", + " shift_phase(q1_rf_frame, -1.0*theta);\n", + " shift_phase(q0_q1_xy_frame, 0.5*theta);\n", + " shift_phase(q1_q2_xy_frame, 0.5*theta);\n", + " shift_phase(q1_q16_xy_frame, 0.5*theta);\n", + " barrier $1;\n", + "}\n" + ] + } + ], + "source": [ + "theta = FreeParameter(\"theta\")\n", + "pulse_sequence_rz_theta_q0 = provider_calibrations.pulse_sequences[(Gate.Rz(theta), QubitSet(1))]\n", + "\n", + "print(pulse_sequence_rz_theta_q0.to_ir())" + ] + }, + { + "cell_type": "markdown", + "id": "9d4d9641", + "metadata": {}, + "source": [ + "## Compose custom gate calibrations with AutoQASM\n", + "In this section, we show you how to customize a gate calibration and use it in a quantum program. To create a custom gate calibration, you need to \n", + "1. Find the definition of the gate in AutoQASM.\n", + "2. Decide the qubits and angles you want to define the calibration.\n", + "3. Compose a pulse program that defines the calibration.\n", + "\n", + "As an example, let's define the gate calibration of `rx` on qubit `$1` and at angle `pi/2`. We first inspect the definition of the `rx` gate. You can find the definition of gate instructions in the [gate module](https://github.com/amazon-braket/amazon-braket-sdk-python/blob/feature/autoqasm/src/braket/experimental/autoqasm/instructions/gates.py) of AutoQASM. You can also use the Python built-in `help` function to retrieve the definition. A gate calibration for the `rx` gate must fully specify the inputs of the gate, `target` and `angle`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "63e15583", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on function rx in module braket.experimental.autoqasm.instructions.gates:\n", + "\n", + "rx(target: Union[int, oqpy.classical_types._ClassicalVar, oqpy.base.OQPyExpression, str, oqpy.quantum_types.Qubit], angle: float) -> None\n", + " X-axis rotation gate.\n", + " \n", + " Args:\n", + " target (QubitIdentifierType): Target qubit.\n", + " angle (float): Rotation angle in radians.\n", + "\n" + ] + } + ], + "source": [ + "help(rx)" + ] + }, + { + "cell_type": "markdown", + "id": "a1189626", + "metadata": {}, + "source": [ + "To specify fixed values for the parameters `target` and `angle`, we set the values through keyword arguments of the decorator. The pulse implementation for the gate calibration is in the body of the `my_rx_cal` function decorated by `@aq.gate_calibration`. In the example in the next cell, we want to experiment with offsetting the frequency of the pulse by 100 Hz away from the hardware provider's implementation. The gate calibration `my_rx_cal` recreates the hardware provider's implementation but with an offset in the frequency." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "64ebd3a2", + "metadata": {}, + "outputs": [], + "source": [ + "wf = DragGaussianWaveform(\n", + " 24e-9, 2.547965400864e-9, 2.370235498840002e-10, 0.293350447987059, False, \"wf_drag_gaussian_1\"\n", + ")\n", + "q0_rf_frame = device.frames['q0_rf_frame']\n", + "\n", + "@aq.gate_calibration(implements=rx, target=\"$0\", angle=np.pi/2)\n", + "def my_rx_cal():\n", + " pulse.barrier(\"$0\")\n", + " pulse.shift_frequency(q0_rf_frame, -321047.14178613486 - 100)\n", + " pulse.play(q0_rf_frame, wf)\n", + " pulse.shift_frequency(q0_rf_frame, 321047.14178613486 + 100)\n", + " pulse.barrier(\"$0\")" + ] + }, + { + "cell_type": "markdown", + "id": "9300f0f8", + "metadata": {}, + "source": [ + "Next, we will demonstrate how to attach `my_rx_cal` to the main quantum program `main_program`. The program is defined in the code block below." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "bae7731a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "rx(pi / 2) $0;\n", + "rz(0.123) $1;\n", + "bit __bit_0__;\n", + "__bit_0__ = measure $0;\n" + ] + } + ], + "source": [ + "@aq.main\n", + "def my_program():\n", + " rx(\"$0\", np.pi/2)\n", + " rz(\"$1\", 0.123)\n", + " measure(\"$0\")\n", + "\n", + "main_program = my_program()\n", + "print(main_program.to_ir())" + ] + }, + { + "cell_type": "markdown", + "id": "c7579516", + "metadata": {}, + "source": [ + "To attach gate calibrations to the program, call the `with_calibrations` method on `main_program` to create a new program with `my_rx_cal` attached, leaving the original intact. This allows you to experiment with different gate calibrations on the same program without the need to redefine the main program every time. \n", + "\n", + "In the printed OpenQASM script of the program, the gate definition for the `rx` gate becomes a `defcal` block." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "6b7cc158", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "extern drag_gaussian(duration, duration, float[64], float[64], bool) -> waveform;\n", + "waveform wf_drag_gaussian_1 = drag_gaussian(24.0ns, 2.547965400864ns, 2.370235498840002e-10, 0.293350447987059, false);\n", + "defcal rx(pi / 2) $0 {\n", + " barrier $0;\n", + " shift_frequency(q0_rf_frame, -321147.14178613486);\n", + " play(q0_rf_frame, wf_drag_gaussian_1);\n", + " shift_frequency(q0_rf_frame, 321147.14178613486);\n", + " barrier $0;\n", + "}\n", + "rx(pi / 2) $0;\n", + "rz(0.123) $1;\n", + "bit __bit_0__;\n", + "__bit_0__ = measure $0;\n" + ] + } + ], + "source": [ + "custom_program = main_program.with_calibrations(my_rx_cal)\n", + "print(custom_program.to_ir())" + ] + }, + { + "cell_type": "markdown", + "id": "f038b8c8", + "metadata": {}, + "source": [ + "You can also define a gate calibration with variable parameters. Variable parameters are used in the body of the decorated function. The variable parameters must be arguments of the decorated Python function, instead of being arguments to the decorator `@aq.gate_calibration`. The variable parameters must have a type hint of either `aq.Qubit` for qubits or `float` for angles. Let's define another gate calibration with a variable parameter. This time, we define a calibration for the `rz` gate on qubit `$1` and a variable angle `angle`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "49d9155b", + "metadata": {}, + "outputs": [], + "source": [ + "q1_rf_frame = device.frames['q1_rf_frame']\n", + "\n", + "@aq.gate_calibration(implements=rz, target=\"$1\")\n", + "def my_rz_cal(angle: float):\n", + " pulse.barrier(\"$1\")\n", + " pulse.shift_frequency(q1_rf_frame, -1.0*angle)\n", + " pulse.barrier(\"$1\")" + ] + }, + { + "cell_type": "markdown", + "id": "6690a735", + "metadata": {}, + "source": [ + "We then attach this gate calibration, `my_rz_cal`, to the main program we composed previously, together with the other gate calibration, `my_rx_cal`." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "a40c95c7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "extern drag_gaussian(duration, duration, float[64], float[64], bool) -> waveform;\n", + "waveform wf_drag_gaussian_1 = drag_gaussian(24.0ns, 2.547965400864ns, 2.370235498840002e-10, 0.293350447987059, false);\n", + "defcal rx(pi / 2) $0 {\n", + " barrier $0;\n", + " shift_frequency(q0_rf_frame, -321147.14178613486);\n", + " play(q0_rf_frame, wf_drag_gaussian_1);\n", + " shift_frequency(q0_rf_frame, 321147.14178613486);\n", + " barrier $0;\n", + "}\n", + "defcal rz(angle[32] angle) $1 {\n", + " barrier $1;\n", + " shift_frequency(q1_rf_frame, -1.0 * angle);\n", + " barrier $1;\n", + "}\n", + "rx(pi / 2) $0;\n", + "rz(0.123) $1;\n", + "bit __bit_0__;\n", + "__bit_0__ = measure $0;\n" + ] + } + ], + "source": [ + "custom_program = main_program.with_calibrations([my_rx_cal, my_rz_cal])\n", + "print(custom_program.to_ir())" + ] + }, + { + "cell_type": "markdown", + "id": "10d4cc3a", + "metadata": {}, + "source": [ + "In the printed OpenQASM script of the program, the gate definitions for the `rx` gate and the `rz` become `defcal` blocks of the program. The variable parameter `angle` for the `rz` gate is captured." + ] + }, + { + "cell_type": "markdown", + "id": "7cbc7366", + "metadata": {}, + "source": [ + "## Summary\n", + "This example notebook shows you how to retrieve and view the gate calibrations supplied by hardware providers. With AutoQASM, you can customize gate calibrations to explore different pulse implementations of gates. You can define gate calibrations against fixed values of gate parameters, as well as using parametric definitions for variable parameters. Multiple gate calibrations can be attached to a program to create a new program. This makes it easy to experiment with different gate calibrations on a single program." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tox.ini b/tox.ini index 9e4089f0..aa176cf6 100644 --- a/tox.ini +++ b/tox.ini @@ -42,6 +42,8 @@ commands = jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/2_Expressing_classical_control_flow.ipynb jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/3_Iterative_phase_estimation.ipynb jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/4_Native_programming.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/6_Customize_gate_calibrations.ipynb extras = test [testenv:linters] From 3850d1ec0383006f1aeff0b466f57058d34f41f4 Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Mon, 25 Sep 2023 11:51:23 -0400 Subject: [PATCH 0860/1165] feature: add serialization configuration to AutoQASM (#710) * add SerializationConfig and default to braket compatible patterns * update options in serialization config * add field for mutable default in dataclass * fix typo * simplify and move serialization config to to_ir * fix gate_calibration has_pulse_control * rerun notebooks * upper case QASM * rename to OpenQASMSerializationProperties --- .../1_Getting_started_with_AutoQASM.ipynb | 13 +-- .../2_Expressing_classical_control_flow.ipynb | 16 +-- .../3_Iterative_phase_estimation.ipynb | 7 -- examples/autoqasm/4_Native_programming.ipynb | 21 ++-- ...programming_and_dynamical_decoupling.ipynb | 21 ++-- .../6_Customize_gate_calibrations.ipynb | 22 +++-- src/braket/experimental/autoqasm/api.py | 11 ++- .../experimental/autoqasm/program/__init__.py | 1 + .../experimental/autoqasm/program/program.py | 28 ++++-- .../program/serialization_properties.py | 30 ++++++ .../experimental/autoqasm/pulse/pulse.py | 28 +++++- .../autoqasm/test_gate_decorator.py | 10 +- .../experimental/autoqasm/test_pulse.py | 9 +- .../autoqasm/test_seralization_config.py | 97 +++++++++++++++++++ 14 files changed, 237 insertions(+), 77 deletions(-) create mode 100644 src/braket/experimental/autoqasm/program/serialization_properties.py create mode 100644 test/unit_tests/braket/experimental/autoqasm/test_seralization_config.py diff --git a/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb b/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb index fd1fecb9..663a998f 100644 --- a/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb +++ b/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb @@ -1,7 +1,6 @@ { "cells": [ { - "attachments": {}, "cell_type": "markdown", "id": "135014b8", "metadata": {}, @@ -12,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "15cdfaee", "metadata": {}, "outputs": [], @@ -28,7 +27,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "d2d7004f", "metadata": {}, @@ -52,7 +50,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "2c59985b", "metadata": {}, @@ -71,13 +68,13 @@ "output_type": "stream", "text": [ "OPENQASM 3.0;\n", + "bit[2] c;\n", "qubit[2] __qubits__;\n", "h __qubits__[0];\n", "cnot __qubits__[0], __qubits__[1];\n", - "bit[2] __bit_0__;\n", + "bit[2] __bit_0__ = \"00\";\n", "__bit_0__[0] = measure __qubits__[0];\n", "__bit_0__[1] = measure __qubits__[1];\n", - "bit[2] c;\n", "c = __bit_0__;\n" ] } @@ -88,7 +85,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "756d0587", "metadata": {}, @@ -106,7 +102,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "measurement counts: Counter({'00': 51, '11': 49})\n" + "measurement counts: Counter({'00': 63, '11': 37})\n" ] } ], @@ -143,7 +139,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "658c21ce", "metadata": {}, diff --git a/examples/autoqasm/2_Expressing_classical_control_flow.ipynb b/examples/autoqasm/2_Expressing_classical_control_flow.ipynb index 8fb2eefd..3c73d6a2 100644 --- a/examples/autoqasm/2_Expressing_classical_control_flow.ipynb +++ b/examples/autoqasm/2_Expressing_classical_control_flow.ipynb @@ -1,7 +1,6 @@ { "cells": [ { - "attachments": {}, "cell_type": "markdown", "id": "135014b8", "metadata": {}, @@ -29,7 +28,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "2cccd8c2", "metadata": {}, @@ -54,7 +52,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "e2a884e6", "metadata": {}, @@ -95,7 +92,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "c6c59f55", "metadata": {}, @@ -104,7 +100,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "1e86b3d5", "metadata": {}, @@ -113,7 +108,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "bf201c13", "metadata": {}, @@ -143,7 +137,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "db483f0e", "metadata": {}, @@ -188,7 +181,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "c1ded7b8", "metadata": {}, @@ -197,7 +189,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "edee6e9c", "metadata": {}, @@ -206,7 +197,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "ce1da10e", "metadata": {}, @@ -216,7 +206,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "88403f29", "metadata": {}, "outputs": [ @@ -230,7 +220,7 @@ " cnot __qubits__[q0], __qubits__[q1];\n", "}\n", "qubit[6] __qubits__;\n", - "for int i in [0:2:5] {\n", + "for int i in [0:2:6 - 1] {\n", " bell(i, i + 1);\n", "}\n" ] @@ -251,7 +241,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "9303a270", "metadata": {}, @@ -260,7 +249,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "2619f38d", "metadata": {}, diff --git a/examples/autoqasm/3_Iterative_phase_estimation.ipynb b/examples/autoqasm/3_Iterative_phase_estimation.ipynb index 99c433b7..968e0d9b 100644 --- a/examples/autoqasm/3_Iterative_phase_estimation.ipynb +++ b/examples/autoqasm/3_Iterative_phase_estimation.ipynb @@ -1,7 +1,6 @@ { "cells": [ { - "attachments": {}, "cell_type": "markdown", "id": "08d77a63", "metadata": {}, @@ -31,7 +30,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "d471ea9c", "metadata": {}, @@ -60,7 +58,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "f76eb5a3", "metadata": {}, @@ -130,7 +127,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "1184691f", "metadata": {}, @@ -175,7 +171,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "2b4ff1a9", "metadata": {}, @@ -184,7 +179,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "63c39db4", "metadata": {}, @@ -194,7 +188,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "26c3bf88", "metadata": {}, diff --git a/examples/autoqasm/4_Native_programming.ipynb b/examples/autoqasm/4_Native_programming.ipynb index 5d779f25..d133598e 100644 --- a/examples/autoqasm/4_Native_programming.ipynb +++ b/examples/autoqasm/4_Native_programming.ipynb @@ -256,6 +256,7 @@ ".output_html .vi { color: #19177C } /* Name.Variable.Instance */\n", ".output_html .vm { color: #19177C } /* Name.Variable.Magic */\n", ".output_html .il { color: #666666 } /* Literal.Number.Integer.Long */
import numpy as np\n",
+       "\n",
        "import braket.experimental.autoqasm as aq\n",
        "from braket.experimental.autoqasm.instructions import gpi, gpi2, ms\n",
        "\n",
@@ -295,6 +296,7 @@
       "text/latex": [
        "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n",
        "\\PY{k+kn}{import} \\PY{n+nn}{numpy} \\PY{k}{as} \\PY{n+nn}{np}\n",
+       "\n",
        "\\PY{k+kn}{import} \\PY{n+nn}{braket}\\PY{n+nn}{.}\\PY{n+nn}{experimental}\\PY{n+nn}{.}\\PY{n+nn}{autoqasm} \\PY{k}{as} \\PY{n+nn}{aq}\n",
        "\\PY{k+kn}{from} \\PY{n+nn}{braket}\\PY{n+nn}{.}\\PY{n+nn}{experimental}\\PY{n+nn}{.}\\PY{n+nn}{autoqasm}\\PY{n+nn}{.}\\PY{n+nn}{instructions} \\PY{k+kn}{import} \\PY{n}{gpi}\\PY{p}{,} \\PY{n}{gpi2}\\PY{p}{,} \\PY{n}{ms}\n",
        "\n",
@@ -333,6 +335,7 @@
       ],
       "text/plain": [
        "import numpy as np\n",
+       "\n",
        "import braket.experimental.autoqasm as aq\n",
        "from braket.experimental.autoqasm.instructions import gpi, gpi2, ms\n",
        "\n",
@@ -398,7 +401,7 @@
      "text": [
       "OPENQASM 3.0;\n",
       "gate h q {\n",
-      "    gpi2(pi / 2) q;\n",
+      "    gpi2(1.5707963267948966) q;\n",
       "    gpi(0) q;\n",
       "}\n",
       "gate u(a, b, c) q {\n",
@@ -407,17 +410,17 @@
       "    gpi2(c) q;\n",
       "}\n",
       "gate ry(theta) q {\n",
-      "    u(pi, theta / 2 + pi, pi) q;\n",
+      "    u(3.141592653589793, theta / 2 + 3.141592653589793, 3.141592653589793) q;\n",
       "}\n",
       "gate rx(theta) q {\n",
-      "    u(pi / 2, theta / 2 + pi / 2, pi / 2) q;\n",
+      "    u(1.5707963267948966, theta / 2 + 1.5707963267948966, 1.5707963267948966) q;\n",
       "}\n",
       "gate cnot q0, q1 {\n",
-      "    ry(pi / 2) q0;\n",
-      "    ms(0, 0, pi / 2) q0, q1;\n",
-      "    rx(-(pi / 2)) q0;\n",
-      "    rx(-(pi / 2)) q1;\n",
-      "    ry(-(pi / 2)) q0;\n",
+      "    ry(1.5707963267948966) q0;\n",
+      "    ms(0, 0, 1.5707963267948966) q0, q1;\n",
+      "    rx(-1.5707963267948966) q0;\n",
+      "    rx(-1.5707963267948966) q1;\n",
+      "    ry(-1.5707963267948966) q0;\n",
       "}\n",
       "pragma braket verbatim\n",
       "box {\n",
@@ -481,7 +484,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.8.16"
+   "version": "3.10.9"
   }
  },
  "nbformat": 4,
diff --git a/examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb b/examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb
index 9b5f695d..d1f0416b 100644
--- a/examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb
+++ b/examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb
@@ -95,10 +95,10 @@
      "output_type": "stream",
      "text": [
       "OPENQASM 3.0;\n",
-      "defcalgrammar \"openpulse\";\n",
+      "bit __bit_0__;\n",
       "cal {\n",
       "    delay[10.0us] q0_rf_frame;\n",
-      "    capture_v0(q0_rf_frame);\n",
+      "    __bit_0__ = capture_v0(q0_rf_frame);\n",
       "}\n"
      ]
     }
@@ -218,28 +218,27 @@
      "output_type": "stream",
      "text": [
       "OPENQASM 3.0;\n",
-      "defcalgrammar \"openpulse\";\n",
       "cal {\n",
       "    delay[833.333333333333ns] q0_rf_frame;\n",
       "}\n",
       "for int _ in [0:3 - 1] {\n",
-      "    rx(pi) $0;\n",
+      "    rx(3.141592653589793) $0;\n",
       "    cal {\n",
       "        delay[1.666666666667us] q0_rf_frame;\n",
       "    }\n",
-      "    rz(-(pi / 2)) $0;\n",
-      "    rx(pi) $0;\n",
-      "    rz(pi / 2) $0;\n",
+      "    rz(-1.5707963267948966) $0;\n",
+      "    rx(3.141592653589793) $0;\n",
+      "    rz(1.5707963267948966) $0;\n",
       "    cal {\n",
       "        delay[1.666666666667us] q0_rf_frame;\n",
       "    }\n",
-      "    rx(pi) $0;\n",
+      "    rx(3.141592653589793) $0;\n",
       "    cal {\n",
       "        delay[1.666666666667us] q0_rf_frame;\n",
       "    }\n",
-      "    rz(-(pi / 2)) $0;\n",
-      "    rx(pi) $0;\n",
-      "    rz(pi / 2) $0;\n",
+      "    rz(-1.5707963267948966) $0;\n",
+      "    rx(3.141592653589793) $0;\n",
+      "    rz(1.5707963267948966) $0;\n",
       "}\n",
       "cal {\n",
       "    delay[833.333333333333ns] q0_rf_frame;\n",
diff --git a/examples/autoqasm/6_Customize_gate_calibrations.ipynb b/examples/autoqasm/6_Customize_gate_calibrations.ipynb
index a58e21c5..7670a6c1 100644
--- a/examples/autoqasm/6_Customize_gate_calibrations.ipynb
+++ b/examples/autoqasm/6_Customize_gate_calibrations.ipynb
@@ -79,7 +79,7 @@
      "text": [
       "OPENQASM 3.0;\n",
       "cal {\n",
-      "    waveform wf_drag_gaussian_1 = drag_gaussian(24.0ns, 2.547965400864ns, 2.296242349815984e-10, 0.293772761206776, false);\n",
+      "    waveform wf_drag_gaussian_1 = drag_gaussian(24.0ns, 2.547965400864ns, 2.3154335273287345e-10, 0.294418024251374, false);\n",
       "    barrier $0;\n",
       "    shift_frequency(q0_rf_frame, -321047.14178613486);\n",
       "    play(q0_rf_frame, wf_drag_gaussian_1);\n",
@@ -220,7 +220,7 @@
      "output_type": "stream",
      "text": [
       "OPENQASM 3.0;\n",
-      "rx(pi / 2) $0;\n",
+      "rx(1.5707963267948966) $0;\n",
       "rz(0.123) $1;\n",
       "bit __bit_0__;\n",
       "__bit_0__ = measure $0;\n"
@@ -259,16 +259,17 @@
      "output_type": "stream",
      "text": [
       "OPENQASM 3.0;\n",
-      "extern drag_gaussian(duration, duration, float[64], float[64], bool) -> waveform;\n",
-      "waveform wf_drag_gaussian_1 = drag_gaussian(24.0ns, 2.547965400864ns, 2.370235498840002e-10, 0.293350447987059, false);\n",
-      "defcal rx(pi / 2) $0 {\n",
+      "cal {\n",
+      "    waveform wf_drag_gaussian_1 = drag_gaussian(24.0ns, 2.547965400864ns, 2.370235498840002e-10, 0.293350447987059, false);\n",
+      "}\n",
+      "defcal rx(1.5707963267948966) $0 {\n",
       "    barrier $0;\n",
       "    shift_frequency(q0_rf_frame, -321147.14178613486);\n",
       "    play(q0_rf_frame, wf_drag_gaussian_1);\n",
       "    shift_frequency(q0_rf_frame, 321147.14178613486);\n",
       "    barrier $0;\n",
       "}\n",
-      "rx(pi / 2) $0;\n",
+      "rx(1.5707963267948966) $0;\n",
       "rz(0.123) $1;\n",
       "bit __bit_0__;\n",
       "__bit_0__ = measure $0;\n"
@@ -323,9 +324,10 @@
      "output_type": "stream",
      "text": [
       "OPENQASM 3.0;\n",
-      "extern drag_gaussian(duration, duration, float[64], float[64], bool) -> waveform;\n",
-      "waveform wf_drag_gaussian_1 = drag_gaussian(24.0ns, 2.547965400864ns, 2.370235498840002e-10, 0.293350447987059, false);\n",
-      "defcal rx(pi / 2) $0 {\n",
+      "cal {\n",
+      "    waveform wf_drag_gaussian_1 = drag_gaussian(24.0ns, 2.547965400864ns, 2.370235498840002e-10, 0.293350447987059, false);\n",
+      "}\n",
+      "defcal rx(1.5707963267948966) $0 {\n",
       "    barrier $0;\n",
       "    shift_frequency(q0_rf_frame, -321147.14178613486);\n",
       "    play(q0_rf_frame, wf_drag_gaussian_1);\n",
@@ -337,7 +339,7 @@
       "    shift_frequency(q1_rf_frame, -1.0 * angle);\n",
       "    barrier $1;\n",
       "}\n",
-      "rx(pi / 2) $0;\n",
+      "rx(1.5707963267948966) $0;\n",
       "rz(0.123) $1;\n",
       "bit __bit_0__;\n",
       "__bit_0__ = measure $0;\n"
diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py
index e6d14af9..03c89c8d 100644
--- a/src/braket/experimental/autoqasm/api.py
+++ b/src/braket/experimental/autoqasm/api.py
@@ -42,7 +42,9 @@
 
 
 def main(
-    *args, num_qubits: Optional[int] = None, device: Optional[Union[Device, str]] = None
+    *args,
+    num_qubits: Optional[int] = None,
+    device: Optional[Union[Device, str]] = None,
 ) -> Callable[[Any], aq_program.Program]:
     """Decorator that converts a function into a callable that returns
     a Program object containing the quantum program.
@@ -66,7 +68,12 @@ def main(
     return _function_wrapper(
         *args,
         converter_callback=_convert_main,
-        converter_args={"user_config": aq_program.UserConfig(num_qubits=num_qubits, device=device)},
+        converter_args={
+            "user_config": aq_program.UserConfig(
+                num_qubits=num_qubits,
+                device=device,
+            )
+        },
     )
 
 
diff --git a/src/braket/experimental/autoqasm/program/__init__.py b/src/braket/experimental/autoqasm/program/__init__.py
index 672ddc06..2e3aefab 100644
--- a/src/braket/experimental/autoqasm/program/__init__.py
+++ b/src/braket/experimental/autoqasm/program/__init__.py
@@ -27,3 +27,4 @@
     get_program_conversion_context,
     in_active_program_conversion_context,
 )
+from .serialization_properties import OpenQASMSerializationProperties  # noqa: F401
diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py
index b69d2e0e..10a654ed 100644
--- a/src/braket/experimental/autoqasm/program/program.py
+++ b/src/braket/experimental/autoqasm/program/program.py
@@ -28,6 +28,10 @@
 from braket.experimental.autoqasm import constants, errors
 from braket.experimental.autoqasm.instructions.qubits import QubitIdentifierType as Qubit
 from braket.experimental.autoqasm.instructions.qubits import _get_physical_qubit_indices, _qubit
+from braket.experimental.autoqasm.program.serialization_properties import (
+    OpenQASMSerializationProperties,
+    SerializationProperties,
+)
 
 # Create the thread-local object for the program conversion context.
 _local = threading.local()
@@ -78,7 +82,11 @@ class Program(SerializableProgram):
     """The program that has been generated with AutoQASM. This object can
     be passed to the run() method of a Braket Device."""
 
-    def __init__(self, oqpy_program: oqpy.Program, has_pulse_control: bool = False):
+    def __init__(
+        self,
+        oqpy_program: oqpy.Program,
+        has_pulse_control: bool = False,
+    ):
         """Initializes an AutoQASM Program object.
 
         Args:
@@ -105,21 +113,24 @@ def with_calibrations(self, gate_calibrations: Union[Callable, List[Callable]])
             gate_calibrations = [gate_calibrations]
         assert all(isinstance(gc, Callable) for gc in gate_calibrations)
 
-        combined_oqpy_program = oqpy.Program()
+        combined_oqpy_program = oqpy.Program(simplify_constants=False)
         for gc in gate_calibrations:
             combined_oqpy_program += gc().program._oqpy_program
         combined_oqpy_program += self._oqpy_program
-        return Program(combined_oqpy_program, self._has_pulse_control)
+        return Program(combined_oqpy_program, has_pulse_control=True)
 
     def to_ir(
         self,
         ir_type: IRType = IRType.OPENQASM,
+        serialization_properties: SerializationProperties = OpenQASMSerializationProperties(),
     ) -> 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.
+            serialization_properties (SerializationProperties): IR serialization configuration.
+                Default to OpenQASMSerializationProperties().
 
         Raises:
             ValueError: If the supplied `ir_type` is not supported.
@@ -128,7 +139,13 @@ def to_ir(
             str: A representation of the program in the `ir_type` format.
         """
         if ir_type == IRType.OPENQASM:
-            return self._oqpy_program.to_qasm(encal_declarations=self._has_pulse_control)
+            openqasm_ir = self._oqpy_program.to_qasm(
+                encal_declarations=self._has_pulse_control,
+                include_externs=serialization_properties.include_externs,
+            )
+            if self._has_pulse_control and not serialization_properties.auto_defcalgrammar:
+                openqasm_ir = openqasm_ir.replace('defcalgrammar "openpulse";\n', "")
+            return openqasm_ir
 
         raise ValueError(f"Supplied ir_type {ir_type} is not supported.")
 
@@ -179,7 +196,7 @@ def __init__(self, user_config: Optional[UserConfig] = None):
         self.user_config = user_config or UserConfig()
         self.return_variable = None
         self.in_verbatim_block = False
-        self._oqpy_program_stack = [oqpy.Program()]
+        self._oqpy_program_stack = [oqpy.Program(simplify_constants=False)]
         self._gate_definitions_processing = []
         self._calibration_definitions_processing = []
         self._gates_defined = set()
@@ -207,7 +224,6 @@ def make_program(self) -> Program:
                     f'The target device "{device.name}" does not support '
                     f"the following gates used in the program: {invalid_gates_used}"
                 )
-
         return Program(self.get_oqpy_program(), has_pulse_control=self._has_pulse_control)
 
     @property
diff --git a/src/braket/experimental/autoqasm/program/serialization_properties.py b/src/braket/experimental/autoqasm/program/serialization_properties.py
new file mode 100644
index 00000000..c63c11be
--- /dev/null
+++ b/src/braket/experimental/autoqasm/program/serialization_properties.py
@@ -0,0 +1,30 @@
+# 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 dataclasses import dataclass
+from typing import Optional
+
+
+@dataclass
+class OpenQASMSerializationProperties:
+    auto_defcalgrammar: Optional[bool] = False
+    """Whether to automatically include defcalgrammar when pulses are used. Default to False."""
+
+    include_externs: Optional[bool] = False
+    """Whether to include externs. Default to False."""
+
+
+# Type alias to refer to possible serialization properties. Can be expanded once
+# new properties are added.
+SerializationProperties = OpenQASMSerializationProperties
diff --git a/src/braket/experimental/autoqasm/pulse/pulse.py b/src/braket/experimental/autoqasm/pulse/pulse.py
index a5753d70..9ae6c1f6 100644
--- a/src/braket/experimental/autoqasm/pulse/pulse.py
+++ b/src/braket/experimental/autoqasm/pulse/pulse.py
@@ -26,8 +26,10 @@
     _get_physical_qubit_indices,
     is_qubit_identifier_type,
 )
+from braket.experimental.autoqasm.types import BitVar
 from braket.pulse import PulseSequence
 from braket.pulse.frame import Frame
+from braket.pulse.pulse_sequence import _validate_uniqueness
 from braket.pulse.waveforms import Waveform
 
 
@@ -120,7 +122,7 @@ def capture_v0(frame: Frame) -> None:
     Args:
         frame (Frame): Frame on which the capture operation needs to be performed.
     """
-    _pulse_instruction("capture_v0", frame)
+    _pulse_instruction("_capture_v0_with_return", frame)
 
 
 def delay(
@@ -157,3 +159,27 @@ def barrier(
     if all(is_qubit_identifier_type(q) for q in qubits_or_frames):
         qubits_or_frames = QubitSet(_get_physical_qubit_indices(qubits_or_frames))
     _pulse_instruction("barrier", qubits_or_frames)
+
+
+def _pulse_sequence_capture_v0_with_return(self, frame: Frame) -> PulseSequence:
+    """
+    Implement a custom capturing method to be register it to the `PulseSequence` class. This method
+    adds an instruction to capture the bit output from measuring the specified frame and assigns
+    the output to a bit variable explicitly.
+
+    Args:
+        frame (Frame): Frame on which the capture operation needs to be performed.
+
+    Returns:
+        PulseSequence: self, with the instruction added.
+    """
+    _validate_uniqueness(self._frames, frame)
+
+    extern_call = oqpy.declare_extern("capture_v0", [("frame", oqpy.FrameVar)], oqpy.bit)
+    self._program.set(BitVar(), extern_call(frame))
+    self._capture_v0_count += 1
+    self._frames[frame.id] = frame
+    return self
+
+
+setattr(PulseSequence, "_capture_v0_with_return", _pulse_sequence_capture_v0_with_return)
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py
index 870079a5..c9548d3e 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py
@@ -110,7 +110,7 @@ def main():
     rx(angle) q;
 }
 qubit[1] __qubits__;
-my_gate(pi / 4) __qubits__[0];"""
+my_gate(0.7853981633974483) __qubits__[0];"""
 
     program = main()
     assert program.to_ir() == expected
@@ -144,7 +144,7 @@ def define_gate_in_subroutine() {
     rx(angle) q;
 }
 qubit[2] __qubits__;
-my_gate(pi / 4) __qubits__[0];
+my_gate(0.7853981633974483) __qubits__[0];
 define_gate_in_subroutine();"""
 
     program = main()
@@ -315,7 +315,7 @@ def my_program():
 
     expected = """OPENQASM 3.0;
 gate t q {
-    rz(pi / 4) q;
+    rz(0.7853981633974483) q;
 }
 gate my_gate(theta) q {
     h q;
@@ -323,8 +323,8 @@ def my_program():
     rx(theta) q;
 }
 qubit[2] __qubits__;
-my_gate(pi / 4) __qubits__[0];
-my_gate(3 * pi / 4) __qubits__[1];
+my_gate(0.7853981633974483) __qubits__[0];
+my_gate(2.356194490192345) __qubits__[1];
 bit[2] __bit_0__ = "00";
 __bit_0__[0] = measure __qubits__[0];
 __bit_0__[1] = measure __qubits__[1];"""
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pulse.py b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py
index 58840ce6..e886c09a 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_pulse.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py
@@ -50,7 +50,6 @@ def my_program():
     expected = textwrap.dedent(
         """
         OPENQASM 3.0;
-        defcalgrammar "openpulse";
         cal {
             waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};
         }
@@ -79,7 +78,6 @@ def my_program():
     expected = textwrap.dedent(
         """
         OPENQASM 3.0;
-        defcalgrammar "openpulse";
         cal {
             barrier $0;
             delay[340.0ms] $3, $4;
@@ -155,7 +153,12 @@ def my_program():
                 "\n    play(predefined_frame_1, arb_wf);\n}"
             ),
         ),
-        (capture_v0, FRAME1, [], "\ncal {\n    capture_v0(predefined_frame_1);\n}"),
+        (
+            capture_v0,
+            FRAME1,
+            [],
+            ("\nbit __bit_0__;" "\ncal {\n    __bit_0__ = capture_v0(predefined_frame_1);\n}"),
+        ),
     ],
 )
 def test_pulse_control(instruction, qubits_or_frames, params, expected_qasm) -> None:
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_seralization_config.py b/test/unit_tests/braket/experimental/autoqasm/test_seralization_config.py
new file mode 100644
index 00000000..7d190e2f
--- /dev/null
+++ b/test/unit_tests/braket/experimental/autoqasm/test_seralization_config.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.
+
+"""Tests for serialization configuration."""
+
+import textwrap
+
+import braket.experimental.autoqasm as aq
+from braket.experimental.autoqasm.program import OpenQASMSerializationProperties
+from braket.experimental.autoqasm.pulse import barrier, play
+from braket.pulse import Frame, GaussianWaveform, Port
+
+PORT = Port(port_id="device_port_x0", dt=1e-9, properties={})
+FRAME = Frame(frame_id="predefined_frame_1", frequency=2e9, port=PORT, phase=0, is_predefined=True)
+WAVEFORM = GaussianWaveform(4e-3, 0.3, 0.7, True, "wf_dg")
+
+
+def test_openqasm_serialization_properties_auto_defcalgrammar() -> None:
+    """Tests serializing with defcalgrammar on top."""
+
+    @aq.main
+    def my_program():
+        barrier("$0")
+
+    expected_true = textwrap.dedent(
+        """
+        OPENQASM 3.0;
+        defcalgrammar "openpulse";
+        cal {
+            barrier $0;
+        }
+        """
+    ).strip()
+    qasm = my_program().to_ir(
+        serialization_properties=OpenQASMSerializationProperties(auto_defcalgrammar=True)
+    )
+    assert qasm == expected_true
+
+    expected_false = textwrap.dedent(
+        """
+        OPENQASM 3.0;
+        cal {
+            barrier $0;
+        }
+        """
+    ).strip()
+    qasm = my_program().to_ir(
+        serialization_properties=OpenQASMSerializationProperties(auto_defcalgrammar=False)
+    )
+    assert qasm == expected_false
+
+
+def test_openqasm_serialization_properties_include_externs() -> None:
+    """Tests serializing with extern definition."""
+
+    @aq.main
+    def my_program():
+        play(FRAME, WAVEFORM)
+
+    expected_true = textwrap.dedent(
+        """
+        OPENQASM 3.0;
+        cal {
+            extern gaussian(duration, duration, float[64], bool) -> waveform;
+            waveform wf_dg = gaussian(4.0ms, 300.0ms, 0.7, true);
+            play(predefined_frame_1, wf_dg);
+        }
+        """
+    ).strip()
+    qasm = my_program().to_ir(
+        serialization_properties=OpenQASMSerializationProperties(include_externs=True)
+    )
+    assert qasm == expected_true
+
+    expected_false = textwrap.dedent(
+        """
+        OPENQASM 3.0;
+        cal {
+            waveform wf_dg = gaussian(4.0ms, 300.0ms, 0.7, true);
+            play(predefined_frame_1, wf_dg);
+        }
+        """
+    ).strip()
+    qasm = my_program().to_ir(
+        serialization_properties=OpenQASMSerializationProperties(include_externs=False)
+    )
+    assert qasm == expected_false

From e2d5899b5a5e6619192939dd7c43745fa44e930d Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 25 Sep 2023 13:12:24 -0700
Subject: [PATCH 0861/1165] infra: bump actions/checkout from 4.0.0 to 4.1.0
 (#711)

---
 .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 160e8e00..631b9036 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@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
+    - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
     - name: Set up Python
       uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0
       with:
diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml
index 732600ba..10752e27 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@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
+    - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
     - name: Set up Python ${{ matrix.python-version }}
       uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0
       with:
diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml
index a26168c1..fbd9a894 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@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
+      - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
       - name: Set up Python
         uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0
         with:
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 591ffed1..1e316a8c 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -24,7 +24,7 @@ jobs:
         python-version: ["3.8", "3.9", "3.10", "3.11"]
 
     steps:
-    - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
+    - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
     - name: Set up Python ${{ matrix.python-version }}
       uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0
       with:
diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml
index 5a596676..f73925f8 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@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
+      - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
       - name: Set up Python
         uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0
         with:

From c95fddfb8cc999269b0492395e6c234e86ed71d3 Mon Sep 17 00:00:00 2001
From: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com>
Date: Mon, 25 Sep 2023 17:51:17 -0700
Subject: [PATCH 0862/1165] feat: add queue visibility information (#712)

* feat: add queue position for tasks (#299)

* feat: add queue position for tasks

* update docstring for queue_position

* update docstrings

* update package_name

* update to enums and dataclass

* test: add integ test for task queue_position (#300)

* refactor: dataclass and file naming for queue info (#301)

* refactor: dataclass and file naming for queue info

* apply suggestions

* update: task queue position after refactor (#309)

* update: task queue position after refactor

* add queue_position type hint details, change order of info

* feat: queue position for hybrid jobs (#302)

* feat: queue position for hybrid jobs

* handle message return

* add docstring changes

* update docstring, and return None

* add context in dataclass

* update docstrings

* indent fix

* test: add integ test for jobs queue position (#310)

* feat: queue position for hybrid jobs

* handle message return

* add docstring changes

* update docstring, and return None

* add context in dataclass

* test: add integ test for jobs queue position

* remove comment

* minor fix

* remove unnecessary branching

* fix docstring merge

* remove dataclass redefinition after merge

* feat: queue depth for devices (#306)

* feat: queue depth for devices (dataclass version)

* add unit-test for queue depth

* modify docstrings, remove helper funcs

* add more info to docstrings

* minor test edit

* docstrings

* indent

* update QueuePriority to QueueType

* test: add integ test for queue_depth (#311)

* refactor: job and quantum_task keywords for queue_depth (#312)

* refactor: job and quantum_task keywords for queue_depth

* Update src/braket/aws/queue_information.py

Co-authored-by: Kshitij Chhabra 

---------

Co-authored-by: Kshitij Chhabra 

* deps: update boto3 version for queue visibility (#319)

* sync: public-main changes into feature/queue_visibility (#320)

* feat: add Aria2 enum (#653)

Co-authored-by: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com>
Co-authored-by: Cody Wang 

* infra: bump actions/checkout from 3.6.0 to 4.0.0 (#696)

Bumps [actions/checkout](https://github.com/actions/checkout) from 3.6.0 to 4.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/f43a0e5ff2bd294095638e18286ca9a3d1956744...3df4ab11eba7bda6032a0b82a6bb43b11571feac)

---
updated-dependencies:
- dependency-name: actions/checkout
  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>

* prepare release v1.55.0

* update development version to v1.55.1.dev0

* Revert "update: restricting parameter names to not collide with ones we use for OpenQASM generation. (#675)" (#701)

This reverts commit b158736a5f394fd709f4d12d6f5e0890d05dbbf6.

* infra: update codeowner file to amazon-braket/braket-dev (#699)

Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com>

* doc: Replace aws org with amazon-braket (#705)

* prepare release v1.55.1

* update development version to v1.55.2.dev0

* doc: change the sphinx requirement to be greater than 7.0.0 (#704)

Co-authored-by: Cody Wang 

* doc: add code contributors to the readme (#703)

Co-authored-by: Cody Wang 

* doc: Remove trailing backquotes (#706)

* infra: update the pre-commit hook with linters (#678)

* infra: update the pre-commit hook with linters and secrets check

Co-authored-by: Abe Coull 
Co-authored-by: Cody Wang 

* prepare release v1.55.1.post0

* update development version to v1.55.2.dev0

---------

Signed-off-by: dependabot[bot] 
Co-authored-by: ashlhans <65787294+ashlhans@users.noreply.github.com>
Co-authored-by: Cody Wang 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: ci 
Co-authored-by: Milan <30416311+krneta@users.noreply.github.com>
Co-authored-by: Angela Guo 
Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com>
Co-authored-by: Abe Coull 

* Revert "sync: public-main changes into feature/queue_visibility (#320)"

This reverts commit be6460cb0d5382cc52f0943f2dcb2cb29a6db8f4.

* update github script

* minor fix

---------

Signed-off-by: dependabot[bot] 
Co-authored-by: Kshitij Chhabra 
Co-authored-by: ashlhans <65787294+ashlhans@users.noreply.github.com>
Co-authored-by: Cody Wang 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Milan <30416311+krneta@users.noreply.github.com>
Co-authored-by: Angela Guo 
Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com>
Co-authored-by: Abe Coull 
---
 setup.py                                      |  2 +-
 src/braket/aws/aws_device.py                  | 49 +++++++++++
 src/braket/aws/aws_quantum_job.py             | 33 +++++++
 src/braket/aws/aws_quantum_task.py            | 35 ++++++++
 src/braket/aws/aws_session.py                 |  6 +-
 src/braket/aws/queue_information.py           | 85 +++++++++++++++++++
 test/integ_tests/test_queue_information.py    | 84 ++++++++++++++++++
 test/unit_tests/braket/aws/test_aws_device.py | 19 ++++-
 .../braket/aws/test_aws_quantum_job.py        | 22 +++++
 .../braket/aws/test_aws_quantum_task.py       | 49 +++++++++--
 .../unit_tests/braket/aws/test_aws_session.py | 30 +++++--
 11 files changed, 398 insertions(+), 16 deletions(-)
 create mode 100644 src/braket/aws/queue_information.py
 create mode 100644 test/integ_tests/test_queue_information.py

diff --git a/setup.py b/setup.py
index f21f27c3..4dff452c 100644
--- a/setup.py
+++ b/setup.py
@@ -33,7 +33,7 @@
         "setuptools",
         "backoff",
         "boltons",
-        "boto3>=1.22.3",
+        "boto3>=1.28.53",
         "nest-asyncio",
         "networkx",
         "numpy",
diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py
index fbd49307..87f7de88 100644
--- a/src/braket/aws/aws_device.py
+++ b/src/braket/aws/aws_device.py
@@ -29,6 +29,7 @@
 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.gate_calibrations import GateCalibrations
 from braket.device_schema import DeviceCapabilities, ExecutionDay, GateModelQpuParadigmProperties
@@ -667,6 +668,54 @@ def get_device_region(device_arn: str) -> str:
                 "see 'https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html'"
             )
 
+    def queue_depth(self) -> QueueDepthInfo:
+        """
+        Task queue depth refers to the total number of quantum tasks currently waiting
+        to run on a particular device.
+
+        Returns:
+            QueueDepthInfo: Instance of the QueueDepth class representing queue depth
+            information for quantum tasks and hybrid jobs.
+            Queue depth refers to the number of quantum tasks and hybrid jobs queued on a particular
+            device. The normal tasks refers to the quantum tasks not submitted via Hybrid Jobs.
+            Whereas, the priority tasks refers to the total number of quantum tasks waiting to run
+            submitted through Amazon Braket Hybrid Jobs. These tasks run before the normal tasks.
+            If the queue depth for normal or priority quantum tasks is greater than 4000, we display
+            their respective queue depth as '>4000'. Similarly, for hybrid jobs if there are more
+            than 1000 jobs queued on a device, display the hybrid jobs queue depth as '>1000'.
+            Additionally, for QPUs if hybrid jobs queue depth is 0, we display information about
+            priority and count of the running hybrid job.
+
+        Example:
+            Queue depth information for a running job.
+            >>> device = AwsDevice(Device.Amazon.SV1)
+            >>> print(device.queue_depth())
+            QueueDepthInfo(quantum_tasks={: '0',
+            : '1'}, jobs='0 (1 prioritized job(s) running)')
+
+            If more than 4000 quantum tasks queued on a device.
+            >>> device = AwsDevice(Device.Amazon.DM1)
+            >>> print(device.queue_depth())
+            QueueDepthInfo(quantum_tasks={: '>4000',
+            : '2000'}, jobs='100')
+        """
+        metadata = self.aws_session.get_device(arn=self.arn)
+        queue_metadata = metadata.get("deviceQueueInfo")
+        queue_info = {}
+
+        for response in queue_metadata:
+            queue_name = response.get("queue")
+            queue_priority = response.get("queuePriority")
+            queue_size = response.get("queueSize")
+
+            if queue_name == "QUANTUM_TASKS_QUEUE":
+                priority_enum = QueueType(queue_priority)
+                queue_info.setdefault("quantum_tasks", {})[priority_enum] = queue_size
+            else:
+                queue_info["jobs"] = queue_size
+
+        return QueueDepthInfo(**queue_info)
+
     def refresh_gate_calibrations(self) -> Optional[GateCalibrations]:
         """
         Refreshes the gate calibration data upon request.
diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py
index 288f1725..3120384f 100644
--- a/src/braket/aws/aws_quantum_job.py
+++ b/src/braket/aws/aws_quantum_job.py
@@ -27,6 +27,7 @@
 
 from braket.aws import AwsDevice
 from braket.aws.aws_session import AwsSession
+from braket.aws.queue_information import HybridJobQueueInfo
 from braket.jobs import logs
 from braket.jobs.config import (
     CheckpointConfig,
@@ -278,6 +279,38 @@ 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.
+
+        Returns:
+            HybridJobQueueInfo: Instance of HybridJobQueueInfo class representing
+            the queue position information for the hybrid job. The queue_position is
+            only returned when the hybrid job is not in RUNNING/CANCELLING/TERMINAL states,
+            else queue_position is returned as None. If the queue position of the hybrid
+            job is greater than 15, we return '>15' as the queue_position return value.
+
+        Examples:
+            job status = QUEUED and position is 2 in the queue.
+            >>> job.queue_position()
+            HybridJobQueueInfo(queue_position='2', message=None)
+
+            job status = QUEUED and position is 18 in the queue.
+            >>> job.queue_position()
+            HybridJobQueueInfo(queue_position='>15', message=None)
+
+            job status = COMPLETED
+            >>> job.queue_position()
+            HybridJobQueueInfo(queue_position=None,
+            message='Job is in COMPLETED status. AmazonBraket does
+                        not show queue position for this status.')
+        """
+        response = self.metadata()["queueInfo"]
+        queue_position = None if response.get("position") == "None" else response.get("position")
+        message = response.get("message")
+
+        return HybridJobQueueInfo(queue_position=queue_position, message=message)
+
     def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None:
         """Display logs for a given hybrid job, optionally tailing them until hybrid job is
            complete.
diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py
index a4538cdd..57fecb26 100644
--- a/src/braket/aws/aws_quantum_task.py
+++ b/src/braket/aws/aws_quantum_task.py
@@ -24,6 +24,7 @@
 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
@@ -314,6 +315,40 @@ def state(self, use_cached_value: bool = False) -> str:
         """
         return self._status(use_cached_value)
 
+    def queue_position(self) -> QuantumTaskQueueInfo:
+        """
+        The queue position details for the quantum task.
+
+        Returns:
+            QuantumTaskQueueInfo: Instance of QuantumTaskQueueInfo class
+            representing the queue position information for the quantum task.
+            The queue_position is only returned when quantum task is not in
+            RUNNING/CANCELLING/TERMINAL states, else queue_position is returned as None.
+            The normal tasks refers to the quantum tasks not submitted via Hybrid Jobs.
+            Whereas, the priority tasks refers to the total number of quantum tasks waiting to run
+            submitted through Amazon Braket Hybrid Jobs. These tasks run before the normal tasks.
+            If the queue position for normal or priority quantum tasks is greater than 2000,
+            we display their respective queue position as '>2000'.
+
+        Examples:
+            task status = QUEUED and queue position is 2050
+            >>> task.queue_position()
+            QuantumTaskQueueInfo(queue_type=,
+            queue_position='>2000', message=None)
+
+            task status = COMPLETED
+            >>> task.queue_position()
+            QuantumTaskQueueInfo(queue_type=,
+            queue_position=None, message='Task is in COMPLETED status. AmazonBraket does
+            not show queue position for this status.')
+        """
+        response = self.metadata()["queueInfo"]
+        queue_type = QueueType(response["queuePriority"])
+        queue_position = None if response.get("position") == "None" else response.get("position")
+        message = response.get("message")
+
+        return QuantumTaskQueueInfo(queue_type, queue_position, message)
+
     def _status(self, use_cached_value: bool = False) -> str:
         metadata = self.metadata(use_cached_value)
         status = metadata.get("status")
diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py
index 300aee13..5ce04a82 100644
--- a/src/braket/aws/aws_session.py
+++ b/src/braket/aws/aws_session.py
@@ -279,7 +279,9 @@ def get_quantum_task(self, arn: str) -> Dict[str, Any]:
         Returns:
             Dict[str, Any]: The response from the Amazon Braket `GetQuantumTask` operation.
         """
-        response = self.braket_client.get_quantum_task(quantumTaskArn=arn)
+        response = self.braket_client.get_quantum_task(
+            quantumTaskArn=arn, additionalAttributeNames=["QueueInfo"]
+        )
         broadcast_event(_TaskStatusEvent(arn=response["quantumTaskArn"], status=response["status"]))
         return response
 
@@ -324,7 +326,7 @@ def get_job(self, arn: str) -> Dict[str, Any]:
         Returns:
             Dict[str, Any]: The response from the Amazon Braket `GetQuantumJob` operation.
         """
-        return self.braket_client.get_job(jobArn=arn)
+        return self.braket_client.get_job(jobArn=arn, additionalAttributeNames=["QueueInfo"])
 
     def cancel_job(self, arn: str) -> Dict[str, Any]:
         """
diff --git a/src/braket/aws/queue_information.py b/src/braket/aws/queue_information.py
new file mode 100644
index 00000000..d45ed876
--- /dev/null
+++ b/src/braket/aws/queue_information.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.
+
+from dataclasses import dataclass
+from enum import Enum
+from typing import Dict, Optional
+
+
+class QueueType(str, Enum):
+    """
+    Enumerates the possible priorities for the queue.
+
+    Values:
+        NORMAL: Represents normal queue for the device.
+        PRIORITY: Represents priority queue for the device.
+    """
+
+    NORMAL = "Normal"
+    PRIORITY = "Priority"
+
+
+@dataclass()
+class QueueDepthInfo:
+    """
+    Represents quantum tasks and hybrid jobs queue depth information.
+
+    Attributes:
+        quantum_tasks (Dict[QueueType, str]): number of quantum tasks waiting
+            to run on a device. This includes both 'Normal' and 'Priority' tasks.
+            For Example, {'quantum_tasks': {QueueType.NORMAL: '7', QueueType.PRIORITY: '3'}}
+        jobs (str): number of hybrid jobs waiting to run on a device. Additionally, for QPUs if
+            hybrid jobs queue depth is 0, we display information about priority and count of the
+            running hybrid jobs. Example, 'jobs': '0 (1 prioritized job(s) running)'
+    """
+
+    quantum_tasks: Dict[QueueType, str]
+    jobs: str
+
+
+@dataclass
+class QuantumTaskQueueInfo:
+    """
+    Represents quantum tasks queue information.
+
+    Attributes:
+        queue_type (QueueType): type of the quantum_task queue either 'Normal'
+            or 'Priority'.
+        queue_position (Optional[str]): current position of your quantum task within a respective
+            device queue. This value can be None based on the state of the task. Default: None.
+        message (Optional[str]): Additional message information. This key is present only
+            if 'queue_position' is None. Default: None.
+    """
+
+    queue_type: QueueType
+    queue_position: Optional[str] = None
+    message: Optional[str] = None
+
+
+@dataclass
+class HybridJobQueueInfo:
+    """
+    Represents hybrid job queue information.
+
+    Attributes:
+        queue_position (Optional[str]): current position of your hybrid job within a respective
+            device queue. If the queue position of the hybrid job is greater than 15, we
+            return '>15' as the queue_position return value. The queue_position is only
+            returned when hybrid job is not in RUNNING/CANCELLING/TERMINAL states, else
+            queue_position is returned as None.
+        message (Optional[str]): Additional message information. This key is present only
+            if 'queue_position' is None. Default: None.
+    """
+
+    queue_position: Optional[str] = None
+    message: Optional[str] = None
diff --git a/test/integ_tests/test_queue_information.py b/test/integ_tests/test_queue_information.py
new file mode 100644
index 00000000..3398fde4
--- /dev/null
+++ b/test/integ_tests/test_queue_information.py
@@ -0,0 +1,84 @@
+# 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.aws import AwsDevice, AwsQuantumJob
+from braket.aws.queue_information import (
+    HybridJobQueueInfo,
+    QuantumTaskQueueInfo,
+    QueueDepthInfo,
+    QueueType,
+)
+from braket.circuits import Circuit
+from braket.devices import Devices
+
+
+def test_task_queue_position():
+    device = AwsDevice(Devices.Amazon.SV1)
+
+    bell = Circuit().h(0).cnot(0, 1)
+    task = device.run(bell, shots=10)
+
+    # call the queue_position method.
+    queue_information = task.queue_position()
+
+    # data type validations
+    assert isinstance(queue_information, QuantumTaskQueueInfo)
+    assert isinstance(queue_information.queue_type, QueueType)
+    assert isinstance(queue_information.queue_position, (str, type(None)))
+
+    # assert queue priority
+    assert queue_information.queue_type in [QueueType.NORMAL, QueueType.PRIORITY]
+
+    # assert message
+    if queue_information.queue_position is None:
+        assert queue_information.message is not None
+        assert isinstance(queue_information.message, (str, type(None)))
+    else:
+        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"},
+    )
+
+    # call the queue_position method.
+    queue_information = job.queue_position()
+
+    # data type validations
+    assert isinstance(queue_information, HybridJobQueueInfo)
+
+    # assert message
+    assert queue_information.queue_position is None
+    assert isinstance(queue_information.message, str)
+
+
+def test_queue_depth():
+    device = AwsDevice(Devices.Amazon.SV1)
+
+    # call the queue_depth method.
+    queue_information = device.queue_depth()
+
+    # data type validations
+    assert isinstance(queue_information, QueueDepthInfo)
+    assert isinstance(queue_information.quantum_tasks, dict)
+    assert isinstance(queue_information.jobs, str)
+
+    for key, value in queue_information.quantum_tasks.items():
+        assert isinstance(key, QueueType)
+        assert isinstance(value, str)
diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py
index 40abf638..f279cac6 100644
--- a/test/unit_tests/braket/aws/test_aws_device.py
+++ b/test/unit_tests/braket/aws/test_aws_device.py
@@ -34,6 +34,7 @@
 from jsonschema import validate
 
 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.gate_calibrations import GateCalibrations
 from braket.device_schema.device_execution_window import DeviceExecutionWindow
@@ -77,7 +78,6 @@
     MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_1
 )
 
-
 MOCK_gate_calibrations_JSON = {
     "gates": {
         "0": {
@@ -218,6 +218,11 @@ def test_mock_rigetti_schema_1():
     "providerName": "Rigetti",
     "deviceStatus": "OFFLINE",
     "deviceCapabilities": MOCK_GATE_MODEL_QPU_CAPABILITIES_1.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)"},
+    ],
 }
 
 MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_2 = {
@@ -628,7 +633,6 @@ def test_device_refresh_metadata(arn):
     "nativeGateCalibrationsRef": "file://hostname/foo/bar",
 }
 
-
 MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_2 = {
     "braketSchemaHeader": {
         "name": "braket.device_schema.pulse.pulse_device_action_properties",
@@ -1937,3 +1941,14 @@ def test_parse_calibration_data_bad_instr(bad_input):
     )
     device = AwsDevice(DWAVE_ARN, mock_session)
     device._parse_calibration_json(bad_input)
+
+
+def test_queue_depth(arn):
+    mock_session = Mock()
+    mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1
+    mock_session.region = RIGETTI_REGION
+    device = AwsDevice(arn, mock_session)
+    assert device.queue_depth() == QueueDepthInfo(
+        quantum_tasks={QueueType.NORMAL: "19", QueueType.PRIORITY: "3"},
+        jobs="0 (3 prioritized job(s) running)",
+    )
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 cbc535a2..ffc9bdb3 100644
--- a/test/unit_tests/braket/aws/test_aws_quantum_job.py
+++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py
@@ -24,6 +24,7 @@
 from botocore.exceptions import ClientError
 
 from braket.aws import AwsQuantumJob, AwsSession
+from braket.aws.queue_information import HybridJobQueueInfo
 
 
 @pytest.fixture
@@ -226,6 +227,27 @@ def test_metadata_caching(quantum_job, aws_session, generate_get_job_response, q
     assert aws_session.get_job.call_count == 1
 
 
+def test_queue_position(quantum_job, aws_session, generate_get_job_response):
+    state_1 = "COMPLETED"
+    queue_info = {
+        "queue": "JOBS_QUEUE",
+        "position": "None",
+        "message": "Job is in COMPLETED status. "
+        "AmazonBraket does not show queue position for this status.",
+    }
+    get_job_response_completed = generate_get_job_response(status=state_1, queueInfo=queue_info)
+    aws_session.get_job.return_value = get_job_response_completed
+    assert quantum_job.queue_position() == HybridJobQueueInfo(
+        queue_position=None, message=queue_info["message"]
+    )
+
+    state_2 = "QUEUED"
+    queue_info = {"queue": "JOBS_QUEUE", "position": "2"}
+    get_job_response_queued = generate_get_job_response(status=state_2, queueInfo=queue_info)
+    aws_session.get_job.return_value = get_job_response_queued
+    assert quantum_job.queue_position() == HybridJobQueueInfo(queue_position="2", message=None)
+
+
 def test_state(quantum_job, aws_session, generate_get_job_response, quantum_job_arn):
     state_1 = "RUNNING"
     get_job_response_running = generate_get_job_response(status=state_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 de8ead78..99270ad2 100644
--- a/test/unit_tests/braket/aws/test_aws_quantum_task.py
+++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py
@@ -26,6 +26,7 @@
 from braket.aws import AwsQuantumTask
 from braket.aws.aws_quantum_task import _create_annealing_device_params
 from braket.aws.aws_session import AwsSession
+from braket.aws.queue_information import QuantumTaskQueueInfo, QueueType
 from braket.circuits import Circuit
 from braket.circuits.gates import PulseGate
 from braket.circuits.serialization import (
@@ -202,6 +203,23 @@ def test_metadata_call_if_none(quantum_task):
     quantum_task._aws_session.get_quantum_task.assert_called_with(quantum_task.id)
 
 
+def test_queue_position(quantum_task):
+    state_1 = "QUEUED"
+    _mock_metadata(quantum_task._aws_session, state_1)
+    assert quantum_task.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._aws_session, state_2)
+    assert quantum_task.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)
@@ -1097,11 +1115,32 @@ def _assert_create_quantum_task_called_with(
 
 
 def _mock_metadata(aws_session, state):
-    aws_session.get_quantum_task.return_value = {
-        "status": state,
-        "outputS3Bucket": S3_TARGET.bucket,
-        "outputS3Directory": S3_TARGET.key,
-    }
+    message = (
+        f"'Task is in {state} status. AmazonBraket does not show queue position for this status.'"
+    )
+    if state in AwsQuantumTask.TERMINAL_STATES or state in ["RUNNING", "CANCELLING"]:
+        aws_session.get_quantum_task.return_value = {
+            "status": state,
+            "outputS3Bucket": S3_TARGET.bucket,
+            "outputS3Directory": S3_TARGET.key,
+            "queueInfo": {
+                "queue": "QUANTUM_TASKS_QUEUE",
+                "position": "None",
+                "queuePriority": "Normal",
+                "message": message,
+            },
+        }
+    else:
+        aws_session.get_quantum_task.return_value = {
+            "status": state,
+            "outputS3Bucket": S3_TARGET.bucket,
+            "outputS3Directory": S3_TARGET.key,
+            "queueInfo": {
+                "queue": "QUANTUM_TASKS_QUEUE",
+                "position": "2",
+                "queuePriority": "Normal",
+            },
+        }
 
 
 def _mock_s3(aws_session, result):
diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py
index 53423d99..bfc65d54 100644
--- a/test/unit_tests/braket/aws/test_aws_session.py
+++ b/test/unit_tests/braket/aws/test_aws_session.py
@@ -418,16 +418,20 @@ def test_create_quantum_task_with_job_token(aws_session):
 def test_get_quantum_task(aws_session):
     arn = "foo:bar:arn"
     status = "STATUS"
+    queue_info = ["QueueInfo"]
     return_value = {"quantumTaskArn": arn, "status": status}
     aws_session.braket_client.get_quantum_task.return_value = return_value
 
     assert aws_session.get_quantum_task(arn) == return_value
-    aws_session.braket_client.get_quantum_task.assert_called_with(quantumTaskArn=arn)
+    aws_session.braket_client.get_quantum_task.assert_called_with(
+        quantumTaskArn=arn, additionalAttributeNames=queue_info
+    )
 
 
 def test_get_quantum_task_retry(aws_session, throttling_response, resource_not_found_response):
     arn = "foo:bar:arn"
     status = "STATUS"
+    queue_info = ["QueueInfo"]
     return_value = {"quantumTaskArn": arn, "status": status}
 
     aws_session.braket_client.get_quantum_task.side_effect = [
@@ -437,7 +441,9 @@ def test_get_quantum_task_retry(aws_session, throttling_response, resource_not_f
     ]
 
     assert aws_session.get_quantum_task(arn) == return_value
-    aws_session.braket_client.get_quantum_task.assert_called_with(quantumTaskArn=arn)
+    aws_session.braket_client.get_quantum_task.assert_called_with(
+        quantumTaskArn=arn, additionalAttributeNames=queue_info
+    )
     assert aws_session.braket_client.get_quantum_task.call_count == 3
 
 
@@ -474,16 +480,20 @@ def test_get_quantum_task_does_not_retry_other_exceptions(aws_session):
 
 def test_get_job(aws_session, get_job_response):
     arn = "arn:aws:braket:us-west-2:1234567890:job/job-name"
+    queue_info = ["QueueInfo"]
     aws_session.braket_client.get_job.return_value = get_job_response
 
     assert aws_session.get_job(arn) == get_job_response
-    aws_session.braket_client.get_job.assert_called_with(jobArn=arn)
+    aws_session.braket_client.get_job.assert_called_with(
+        jobArn=arn, additionalAttributeNames=queue_info
+    )
 
 
 def test_get_job_retry(
     aws_session, get_job_response, throttling_response, resource_not_found_response
 ):
     arn = "arn:aws:braket:us-west-2:1234567890:job/job-name"
+    queue_info = ["QueueInfo"]
 
     aws_session.braket_client.get_job.side_effect = [
         ClientError(resource_not_found_response, "unit-test"),
@@ -492,12 +502,15 @@ def test_get_job_retry(
     ]
 
     assert aws_session.get_job(arn) == get_job_response
-    aws_session.braket_client.get_job.assert_called_with(jobArn=arn)
+    aws_session.braket_client.get_job.assert_called_with(
+        jobArn=arn, additionalAttributeNames=queue_info
+    )
     assert aws_session.braket_client.get_job.call_count == 3
 
 
 def test_get_job_fail_after_retries(aws_session, throttling_response, resource_not_found_response):
     arn = "arn:aws:braket:us-west-2:1234567890:job/job-name"
+    queue_info = ["QueueInfo"]
 
     aws_session.braket_client.get_job.side_effect = [
         ClientError(resource_not_found_response, "unit-test"),
@@ -507,12 +520,15 @@ def test_get_job_fail_after_retries(aws_session, throttling_response, resource_n
 
     with pytest.raises(ClientError):
         aws_session.get_job(arn)
-    aws_session.braket_client.get_job.assert_called_with(jobArn=arn)
+    aws_session.braket_client.get_job.assert_called_with(
+        jobArn=arn, additionalAttributeNames=queue_info
+    )
     assert aws_session.braket_client.get_job.call_count == 3
 
 
 def test_get_job_does_not_retry_other_exceptions(aws_session):
     arn = "arn:aws:braket:us-west-2:1234567890:job/job-name"
+    queue_info = ["QueueInfo"]
     exception_response = {
         "Error": {
             "Code": "SomeOtherException",
@@ -526,7 +542,9 @@ def test_get_job_does_not_retry_other_exceptions(aws_session):
 
     with pytest.raises(ClientError):
         aws_session.get_job(arn)
-    aws_session.braket_client.get_job.assert_called_with(jobArn=arn)
+    aws_session.braket_client.get_job.assert_called_with(
+        jobArn=arn, additionalAttributeNames=queue_info
+    )
     assert aws_session.braket_client.get_job.call_count == 1
 
 

From 52286dc8b9fe8c347133443dfc3a881d886b0288 Mon Sep 17 00:00:00 2001
From: ci 
Date: Tue, 26 Sep 2023 01:07:10 +0000
Subject: [PATCH 0863/1165] prepare release v1.56.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 cb5d60c8..e059789f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## v1.56.0 (2023-09-26)
+
+### Features
+
+ * add queue visibility information
+
 ## v1.55.1.post0 (2023-09-18)
 
 ### Documentation Changes
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index 865a962d..cc106996 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.55.2.dev0"
+__version__ = "1.56.0"

From f015dd00b0dae4d203f74556b220993d2312027c Mon Sep 17 00:00:00 2001
From: ci 
Date: Tue, 26 Sep 2023 01:07:10 +0000
Subject: [PATCH 0864/1165] update development version to v1.56.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 cc106996..ed195245 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.56.0"
+__version__ = "1.56.1.dev0"

From 1acf47684e988a59c3ef060594816acc2ebd05a2 Mon Sep 17 00:00:00 2001
From: Milan <30416311+krneta@users.noreply.github.com>
Date: Tue, 26 Sep 2023 14:11:07 -0700
Subject: [PATCH 0865/1165] fix: fixing search device when don't have access to
 a region. (#708)

---
 src/braket/aws/aws_device.py                  | 42 +++++++-----
 test/unit_tests/braket/aws/test_aws_device.py | 68 +++++++++++++++++++
 2 files changed, 94 insertions(+), 16 deletions(-)

diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py
index 87f7de88..d495dbe3 100644
--- a/src/braket/aws/aws_device.py
+++ b/src/braket/aws/aws_device.py
@@ -17,6 +17,7 @@
 import json
 import os
 import urllib.request
+import warnings
 from datetime import datetime
 from enum import Enum
 from typing import Dict, List, Optional, Tuple, Union
@@ -602,23 +603,32 @@ def get_devices(
             types_for_region = sorted(
                 types if region == session_region else types - {AwsDeviceType.SIMULATOR}
             )
-            region_device_arns = [
-                result["deviceArn"]
-                for result in session_for_region.search_devices(
-                    arns=arns,
-                    names=names,
-                    types=types_for_region,
-                    statuses=statuses,
-                    provider_names=provider_names,
+            try:
+                region_device_arns = [
+                    result["deviceArn"]
+                    for result in session_for_region.search_devices(
+                        arns=arns,
+                        names=names,
+                        types=types_for_region,
+                        statuses=statuses,
+                        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.update(
-                {
-                    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(
+                    f"{error_code}: Unable to search region '{region}' for devices."
+                    " Please check your settings or try again later."
+                    f" Continuing without devices in '{region}'."
+                )
+
         devices = list(device_map.values())
         devices.sort(key=lambda x: getattr(x, order_by))
         return devices
diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py
index f279cac6..c1e03483 100644
--- a/test/unit_tests/braket/aws/test_aws_device.py
+++ b/test/unit_tests/braket/aws/test_aws_device.py
@@ -1673,6 +1673,74 @@ def test_get_devices_simulators_only(mock_copy_session, aws_session):
     assert [result.name for result in results] == ["SV1"]
 
 
+@patch("braket.aws.aws_device.AwsSession.copy_session")
+def test_get_devices_with_error_in_region(mock_copy_session, aws_session):
+    aws_session.search_devices.side_effect = [
+        # us-west-1
+        [
+            {
+                "deviceArn": SV1_ARN,
+                "deviceName": "SV1",
+                "deviceType": "SIMULATOR",
+                "deviceStatus": "ONLINE",
+                "providerName": "Amazon Braket",
+            }
+        ],
+        ValueError("should not be reachable"),
+    ]
+    aws_session.get_device.side_effect = [
+        MOCK_GATE_MODEL_SIMULATOR,
+        ValueError("should not be reachable"),
+    ]
+    session_for_region = Mock()
+    session_for_region.search_devices.side_effect = [
+        # us-east-1
+        [
+            {
+                "deviceArn": IONQ_ARN,
+                "deviceName": "IonQ Device",
+                "deviceType": "QPU",
+                "deviceStatus": "ONLINE",
+                "providerName": "IonQ",
+            },
+        ],
+        # us-west-2
+        ClientError(
+            {
+                "Error": {
+                    "Code": "Test Code",
+                    "Message": "Test Message",
+                }
+            },
+            "Test Operation",
+        ),
+        # eu-west-2
+        [
+            {
+                "deviceArn": OQC_ARN,
+                "deviceName": "Lucy",
+                "deviceType": "QPU",
+                "deviceStatus": "ONLINE",
+                "providerName": "OQC",
+            }
+        ],
+        # Only two regions to search outside of current
+        ValueError("should not be reachable"),
+    ]
+    session_for_region.get_device.side_effect = [
+        MOCK_GATE_MODEL_QPU_2,
+        MOCK_GATE_MODEL_QPU_3,
+        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
+    results = AwsDevice.get_devices(
+        statuses=["ONLINE"],
+        aws_session=aws_session,
+    )
+    assert [result.name for result in results] == ["Blah", "Lucy", "SV1"]
+
+
 @pytest.mark.xfail(raises=ValueError)
 def test_get_devices_invalid_order_by():
     AwsDevice.get_devices(order_by="foo")

From dc67ca22fd506926ab2e3e27c9eef2c1486309f0 Mon Sep 17 00:00:00 2001
From: ci 
Date: Wed, 27 Sep 2023 16:17:17 +0000
Subject: [PATCH 0866/1165] prepare release v1.56.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 e059789f..fb09fb1c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## v1.56.1 (2023-09-27)
+
+### Bug Fixes and Other Changes
+
+ * fixing search device when don't have access to a region.
+
 ## v1.56.0 (2023-09-26)
 
 ### Features
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index ed195245..f3c84dd0 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.56.1.dev0"
+__version__ = "1.56.1"

From 4b690fdfe0a2625fe8bf7d0e7f837fe8a74ff2f4 Mon Sep 17 00:00:00 2001
From: ci 
Date: Wed, 27 Sep 2023 16:17:17 +0000
Subject: [PATCH 0867/1165] update development version to v1.56.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 f3c84dd0..f9bd793a 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.56.1"
+__version__ = "1.56.2.dev0"

From 9cf49c2303caa75398c5ca870662425c1202fa0e Mon Sep 17 00:00:00 2001
From: Lauren Capelluto 
Date: Thu, 28 Sep 2023 15:54:19 -0400
Subject: [PATCH 0868/1165] Add documentation about decorators (#714)

* Add documentation about decorators

Co-authored-by: Tim Chen 
Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>

* Respond to CR

---------

Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
---
 src/braket/experimental/autoqasm/README.md    |   2 +
 .../experimental/autoqasm/doc/decorators.md   | 131 ++++++++++++++++++
 2 files changed, 133 insertions(+)
 create mode 100644 src/braket/experimental/autoqasm/doc/decorators.md

diff --git a/src/braket/experimental/autoqasm/README.md b/src/braket/experimental/autoqasm/README.md
index 2584ba88..1bc1af13 100644
--- a/src/braket/experimental/autoqasm/README.md
+++ b/src/braket/experimental/autoqasm/README.md
@@ -98,6 +98,8 @@ task = device.run(my_bell_program, shots=100)
 result = task.result()
 ```
 
+Read more about AutoQASM decorators like `@aq.main` [here](doc/decorators.md).
+
 For more example usage of AutoQASM, visit the [example notebooks](../../../../examples/autoqasm).
 
 ## Architecture
diff --git a/src/braket/experimental/autoqasm/doc/decorators.md b/src/braket/experimental/autoqasm/doc/decorators.md
new file mode 100644
index 00000000..494cb90c
--- /dev/null
+++ b/src/braket/experimental/autoqasm/doc/decorators.md
@@ -0,0 +1,131 @@
+# AutoQASM decorators
+
+AutoQASM function decorators allow us to override the normal behavior of the decorated code. This is how we are able to hook into normal Python control flow statements and add them to the quantum program within our wrapped functions, for instance.
+
+There are a handful of decorators available through AutoQASM. Each one attaches its own special behaviors to the function it wraps. If you are new to AutoQASM, you can just use `@aq.main`! The other decorators unlock further capabilities, when you need it.
+
+## `@aq.main`
+
+This decorator marks the entry point to a quantum program.
+
+You can include gates, pulse control, classical control and subroutine calls. When you call the function wrapped by `@aq.main`, you will get a `Program` object. The `Program` object can be executed on [devices available through Amazon Braket](https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html), including local simulators. The code snippet below creates a quantum program with `@aq.main` and runs it on the `Device` instantiated as `device`.
+
+```
+@aq.main(num_qubits=5)
+def ghz_state(max_qubits):
+    """Create a GHZ state from a variable number of qubits."""
+    h(0)
+    for i in aq.range(max_qubits):
+        cnot(0, i)
+    measure(list(range(max_qubits))) 
+    
+ghz_state_program = ghz_state(max_qubits=5)
+
+device.run(ghz_state_program)
+```
+
+When you run your quantum program, the Amazon Braket SDK automatically serializes the program to OpenQASM before sending it to the local simulator or the Amazon Braket service. In AutoQASM, you can optionally view the OpenQASM script of your quantum program before submitting to a device by calling `to_ir()` on the `Program` object.
+
+```
+print(ghz_state_program.to_ir())
+```
+
+## `@aq.subroutine`
+
+This decorator declares a function to be a quantum program subroutine.
+
+Like any subroutine, `@aq.subroutine` is often used to simplify repeated code and increase the readability of a program. A subroutine must be called at least once from within an `@aq.main` function or another `@aq.subroutine` function in order to be included in a program.
+
+Because AutoQASM supports strongly-typed serialization formats such as OpenQASM, you must provide type hints for the inputs of your subroutine definitions.
+
+Our example below uses a subroutine to make Bell states on two pairs of qubits.
+```
+@aq.subroutine
+def bell(q0: int, q1: int) -> None:
+    h(q0)
+    cnot(q0, q1)
+
+    
+@aq.main(num_qubits=4)
+def two_bell() -> None:
+    bell(0, 1)
+    bell(2, 3)
+
+two_bell_program = two_bell()
+```
+
+Let's take a look at the serialized output from `two_bell_program.to_ir()`, which shows that the modularity of the subroutine is preserved.
+
+```
+OPENQASM 3.0;
+def bell(int[32] q0, int[32] q1) {
+    h __qubits__[q0];
+    cnot __qubits__[q0], __qubits__[q1];
+}
+qubit[4] __qubits__;
+bell(0, 1);
+bell(2, 3);
+```
+
+## `@aq.gate`
+
+Represents a gate definition.
+
+Gate definitions define higher-level gates in terms of other gates, and are often used to decompose a gate into the native gates of a device.
+
+The body of a gate definition can only contain gates. Qubits used in the body of a gate definition must be passed as input arguments, with the type hint `aq.Qubit`. Like subroutines, a gate definition must be called from within the context of a main quantum program or subroutine in order to be included in the program.
+
+```
+@aq.gate
+def ch(q0: aq.Qubit, q1: aq.Qubit):
+    """Define a controlled-Hadamard gate."""
+    ry(q1, -math.pi / 4)
+    cz(q0, q1)
+    ry(q1, math.pi / 4)
+    
+@aq.main(num_qubits=2)
+def main():
+    h(0)
+    ch(0, 1)
+    
+main_program = main()
+```
+
+
+## `@aq.gate_calibration`
+
+This decorator allows you to register a calibration for a gate. A gate calibration is a device-specific, low-level, pulse implementation for a logical gate operation.
+
+At the pulse level, qubits are no longer interchangable. Each one has unique properties. Thus, a gate calibration is usually defined for a concrete set of qubits and parameters, but you can use input arguments to your function as well.
+
+The body of a function decorated with `@aq.gate_calibration` must only contain pulse operations.
+
+The first argument to the `@aq.gate_calibration` decorator must be the gate function that the calibration will be registered to. Concrete values for the qubits and parameters are supplied as keyword arguments to the decorator.
+Every qubit and angle parameter of the gate being implemented must appear either as an argument to the `@aq.gate_calibration` decorator, or as a parameter of the decorated function.
+
+For example, the gate `rx` takes two arguments, target and angle. Each arguments must be either set in the decorator or declared as an input parameter to the decorated function.
+
+```
+# This calibration only applies to physical qubit zero, so we
+# mark that in the decorator call
+@aq.gate_calibration(implements=rx, target="$0")
+def cal_1(angle: float):
+    # The calibration is applicable for any rotation angle,
+    # so we accept it as an input argument
+    pulse.barrier("$0")
+    pulse.shift_frequency(q0_rf_frame, -321047.14178613486)
+    pulse.play(q0_rf_frame, waveform(angle))
+    pulse.shift_frequency(q0_rf_frame, 321047.14178613486)
+    pulse.barrier("$0")
+```
+
+To add the gate calibration to your program, use the `with_calibrations` method of the main program.
+
+```
+@aq.main
+def my_program():
+    rx("$0", 0.123)
+    measure("$0")
+
+my_program().with_calibrations([cal_1])
+```

From 4a28baf51d2671dcc71ddd46ccc4152d7c848487 Mon Sep 17 00:00:00 2001
From: Lauren Capelluto 
Date: Thu, 28 Sep 2023 15:54:44 -0400
Subject: [PATCH 0869/1165] doc: Add FAQs to AutoQASM README (#713)

* Add FAQs to AutoQASM README

* Apply suggestions from code review

Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>

* Respond to CR

---------

Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
---
 src/braket/experimental/autoqasm/README.md | 65 +++++++++++++++++++++-
 1 file changed, 63 insertions(+), 2 deletions(-)

diff --git a/src/braket/experimental/autoqasm/README.md b/src/braket/experimental/autoqasm/README.md
index 1bc1af13..e2ab52f2 100644
--- a/src/braket/experimental/autoqasm/README.md
+++ b/src/braket/experimental/autoqasm/README.md
@@ -19,7 +19,13 @@ afraid of a few bugs, please keep on reading!
 
 AutoQASM provides a Pythonic developer experience for writing quantum programs. The working title "AutoQASM" is derived from the name of the [AutoGraph module of TensorFlow](https://www.tensorflow.org/api_docs/python/tf/autograph). AutoQASM uses AutoGraph to construct quantum assembly (QASM) programs rather than TensorFlow graphs.
 
-AutoQASM provides a natural interface for expressing quantum programs with mid-circuit measurements and classical control flow using native Python language features. It allows the construction of modular programs consisting of common programming constructs such as loops and subroutines, and it preserves this modularity when serializing the program to OpenQASM. This enables a more imperative programming style than constructing programs via a series of function calls on a circuit object.
+AutoQASM provides a natural interface for expressing quantum programs with mid-circuit measurements
+and classical control flow using native Python language features. It allows the construction of
+modular programs consisting of common programming constructs such as loops and subroutines. This
+enables a more imperative programming style than constructing programs via a series of function calls
+on a circuit object.
+
+AutoQASM programs can be serialized to OpenQASM. This textual representation for quantum programs is widely supported and enables interoperability among various frameworks. A crucial part of our serialization process is that modular structures within the program, such as loops and subroutines, are preserved when serializing to OpenQASM.
 
 Although it is still a work in progress, the intent is that AutoQASM will support any quantum programming paradigm which falls into the [OpenQASM 3.0](https://openqasm.com) language scope. AutoQASM supports serializing quantum programs to OpenQASM, which allows the programs to interoperate with any library or service that supports OpenQASM programs, such as Amazon Braket.
 
@@ -138,10 +144,65 @@ Please tag your question with "Amazon Braket" and mention AutoQASM in the questi
 
 ## Tests
 
-To run only AutoQASM tests (and skip the rest of the unit tests), run:
+To run only AutoQASM tests (and skip the rest of the Amazon Braket SDK unit tests), run:
 ```
 tox -e unit-tests -- test/unit_test/braket/experimental/autoqasm
 ```
 
 Note that you may first need to run `pip install -e .[test]`. More information on running tests
 can be found in the [top-level README](../../../../README.md).
+
+## Frequently asked questions
+
+###  1. Will AutoQASM be extended to contain a library of quantum algorithms or quantum applications?
+
+No, we are focused on AutoQASM as an interface for low-level expression of
+quantum programs: circuits, gates and pulses. Higher-level algorithm
+libraries could be implemented using AutoQASM and benefit from modular
+AutoQASM functionality such as subroutines.
+
+### 2. What is the relationship between AutoQASM and OpenQASM?
+
+AutoQASM is a quantum programming interface built in Python.
+OpenQASM is a quantum assembly language, often used as a serialization format
+for quantum programming frameworks and quantum hardware providers. We can
+represent a quantum program equivalently in either format, but using AutoQASM
+allows one to also make use of Python, including the Amazon Braket SDK.
+
+AutoQASM can be seen as implementing a builder pattern for OpenQASM. It
+allows you serialize your program to OpenQASM with `Program.to_ir()`. The
+interface is not strongly tied to OpenQASM, so we could serialize to other
+formats in the future.
+
+### 3. What is the relationship between AutoQASM and the Amazon Braket SDK?
+
+AutoQASM lives alongside the Amazon Braket SDK as an experimental feature
+branch. It supplements the program building experience and integrates with
+Amazon Braket SDK features. For instance, one can build a program through
+AutoQASM, and then use the SDK to run the program on a local simulator or on
+an Amazon Braket device.
+
+### 4. Does AutoQASM support other providers beyond Amazon Braket?
+
+Yes. AutoQASM serializes to OpenQASM, and so it is applicable to any library
+or QPU that supports OpenQASM. We do have features that use the Amazon Braket
+SDK, such as [device-specific validation](../../../../examples/autoqasm/4_Native_programming.ipynb).
+Because AutoQASM is open-source, anyone could
+build similar integrations for another service. Reach out if you're
+interested in doing this and would like support.
+
+
+### 5. Does AutoQASM offer special support for device-specific programming?
+
+Yes, AutoQASM has device-specific validation to support native programming.
+We plan to expand this functionality in the future. Learn more with our
+[native programming example notebook](../../../../examples/autoqasm/4_Native_programming.ipynb).
+
+### 6. Do the devices available through Amazon Braket support all of AutoQASM's features?
+
+No, for example, the `reset` instruction is not supported by all devices. In
+general, different QPUs and QHPs support different sets of features, so
+AutoQASM will often support features that a particular device doesn't
+support. We intend that AutoQASM will eventually be able to generate any
+program representable by OpenQASM 3.0, with additional Python-side features
+such as validation and visualization.

From 98ea275456c6e4259a3e92f0ace86ff394ba3eae Mon Sep 17 00:00:00 2001
From: Kshitij Chhabra 
Date: Mon, 2 Oct 2023 21:26:21 -0700
Subject: [PATCH 0870/1165] fix: Refactor Qubit and QubitSet to a separate
 module (#717)

* fix: Refactor Qubit and QubitSet to a separate module

* Rename qubits module to registers
---
 src/braket/circuits/ascii_circuit_diagram.py  |  2 +-
 src/braket/circuits/circuit.py                |  4 +-
 src/braket/circuits/compiler_directive.py     |  2 +-
 src/braket/circuits/gate.py                   |  2 +-
 src/braket/circuits/gate_calibrations.py      |  2 +-
 src/braket/circuits/gates.py                  |  4 +-
 src/braket/circuits/instruction.py            |  4 +-
 src/braket/circuits/moments.py                |  4 +-
 src/braket/circuits/noise.py                  |  2 +-
 src/braket/circuits/noise_helpers.py          |  2 +-
 .../circuit_instruction_criteria.py           |  2 +-
 .../noise_model/criteria_input_parsing.py     |  2 +-
 .../circuits/noise_model/gate_criteria.py     |  2 +-
 .../noise_model/initialization_criteria.py    |  2 +-
 .../circuits/noise_model/noise_model.py       |  2 +-
 .../noise_model/observable_criteria.py        |  2 +-
 .../qubit_initialization_criteria.py          |  2 +-
 .../noise_model/unitary_gate_criteria.py      |  2 +-
 src/braket/circuits/noises.py                 |  4 +-
 src/braket/circuits/observable.py             |  2 +-
 src/braket/circuits/observables.py            |  2 +-
 src/braket/circuits/qubit.py                  | 56 +----------
 src/braket/circuits/qubit_set.py              | 81 +---------------
 src/braket/circuits/result_type.py            |  4 +-
 src/braket/circuits/result_types.py           |  2 +-
 src/braket/circuits/unitary_calculation.py    |  2 +-
 src/braket/pulse/pulse_sequence.py            |  2 +-
 src/braket/registers/__init__.py              | 15 +++
 src/braket/registers/qubit.py                 | 68 ++++++++++++++
 src/braket/registers/qubit_set.py             | 93 +++++++++++++++++++
 .../braket/circuits/test_noise_helpers.py     |  2 +-
 .../pulse/ast/test_approximation_parser.py    |  2 +-
 .../{circuits => registers}/test_qubit.py     |  2 +-
 .../{circuits => registers}/test_qubit_set.py |  2 +-
 34 files changed, 213 insertions(+), 170 deletions(-)
 create mode 100644 src/braket/registers/__init__.py
 create mode 100644 src/braket/registers/qubit.py
 create mode 100644 src/braket/registers/qubit_set.py
 rename test/unit_tests/braket/{circuits => registers}/test_qubit.py (97%)
 rename test/unit_tests/braket/{circuits => registers}/test_qubit_set.py (97%)

diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py
index f43f37a3..85de40a1 100644
--- a/src/braket/circuits/ascii_circuit_diagram.py
+++ b/src/braket/circuits/ascii_circuit_diagram.py
@@ -22,8 +22,8 @@
 from braket.circuits.gate import Gate
 from braket.circuits.instruction import Instruction
 from braket.circuits.noise import Noise
-from braket.circuits.qubit_set import QubitSet
 from braket.circuits.result_type import ResultType
+from braket.registers.qubit_set import QubitSet
 
 
 class AsciiCircuitDiagram(CircuitDiagram):
diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py
index 7e3bfcd4..34bde9b9 100644
--- a/src/braket/circuits/circuit.py
+++ b/src/braket/circuits/circuit.py
@@ -39,8 +39,6 @@
 from braket.circuits.observable import Observable
 from braket.circuits.observables import TensorProduct
 from braket.circuits.parameterizable import Parameterizable
-from braket.circuits.qubit import QubitInput
-from braket.circuits.qubit_set import QubitSet, QubitSetInput
 from braket.circuits.result_type import (
     ObservableParameterResultType,
     ObservableResultType,
@@ -60,6 +58,8 @@
 from braket.pulse import ArbitraryWaveform, Frame
 from braket.pulse.ast.qasm_parser import ast_to_qasm
 from braket.pulse.pulse_sequence import PulseSequence, _validate_uniqueness
+from braket.registers.qubit import QubitInput
+from braket.registers.qubit_set import QubitSet, QubitSetInput
 
 SubroutineReturn = TypeVar(
     "SubroutineReturn", Iterable[Instruction], Instruction, ResultType, Iterable[ResultType]
diff --git a/src/braket/circuits/compiler_directive.py b/src/braket/circuits/compiler_directive.py
index cd40a596..3ca677ff 100644
--- a/src/braket/circuits/compiler_directive.py
+++ b/src/braket/circuits/compiler_directive.py
@@ -16,8 +16,8 @@
 from typing import Any, Sequence, Tuple
 
 from braket.circuits.operator import Operator
-from braket.circuits.qubit_set import QubitSet
 from braket.circuits.serialization import IRType, SerializationProperties
+from braket.registers.qubit_set import QubitSet
 
 
 class CompilerDirective(Operator):
diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py
index 81f2499d..135a892c 100644
--- a/src/braket/circuits/gate.py
+++ b/src/braket/circuits/gate.py
@@ -18,12 +18,12 @@
 
 from braket.circuits.basis_state import BasisState, BasisStateInput
 from braket.circuits.quantum_operator import QuantumOperator
-from braket.circuits.qubit_set import QubitSet
 from braket.circuits.serialization import (
     IRType,
     OpenQASMSerializationProperties,
     SerializationProperties,
 )
+from braket.registers.qubit_set import QubitSet
 
 
 class Gate(QuantumOperator):
diff --git a/src/braket/circuits/gate_calibrations.py b/src/braket/circuits/gate_calibrations.py
index 01694a1e..3c5abefd 100644
--- a/src/braket/circuits/gate_calibrations.py
+++ b/src/braket/circuits/gate_calibrations.py
@@ -17,13 +17,13 @@
 from typing import Any, Dict, List, Optional, Tuple
 
 from braket.circuits.gate import Gate
-from braket.circuits.qubit_set import QubitSet
 from braket.circuits.serialization import (
     IRType,
     OpenQASMSerializationProperties,
     QubitReferenceType,
 )
 from braket.pulse.pulse_sequence import PulseSequence
+from braket.registers.qubit_set import QubitSet
 
 
 class GateCalibrations:
diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py
index ac57d05b..a68d9ead 100644
--- a/src/braket/circuits/gates.py
+++ b/src/braket/circuits/gates.py
@@ -39,11 +39,11 @@
     is_unitary,
     verify_quantum_operator_matrix_dimensions,
 )
-from braket.circuits.qubit import QubitInput
-from braket.circuits.qubit_set import QubitSet, QubitSetInput
 from braket.circuits.serialization import OpenQASMSerializationProperties
 from braket.pulse.ast.qasm_parser import ast_to_qasm
 from braket.pulse.pulse_sequence import PulseSequence
+from braket.registers.qubit import QubitInput
+from braket.registers.qubit_set import QubitSet, QubitSetInput
 
 """
 To add a new gate:
diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py
index 148711c7..6211e122 100644
--- a/src/braket/circuits/instruction.py
+++ b/src/braket/circuits/instruction.py
@@ -20,9 +20,9 @@
 from braket.circuits.gate import Gate
 from braket.circuits.operator import Operator
 from braket.circuits.quantum_operator import QuantumOperator
-from braket.circuits.qubit import QubitInput
-from braket.circuits.qubit_set import QubitSet, QubitSetInput
 from braket.circuits.serialization import IRType, SerializationProperties
+from braket.registers.qubit import QubitInput
+from braket.registers.qubit_set import QubitSet, QubitSetInput
 
 # InstructionOperator is a type alias, and it can be expanded to include other operators
 InstructionOperator = Operator
diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py
index f1387d02..70cc81bc 100644
--- a/src/braket/circuits/moments.py
+++ b/src/braket/circuits/moments.py
@@ -31,8 +31,8 @@
 from braket.circuits.compiler_directive import CompilerDirective
 from braket.circuits.instruction import Instruction
 from braket.circuits.noise import Noise
-from braket.circuits.qubit import Qubit
-from braket.circuits.qubit_set import QubitSet
+from braket.registers.qubit import Qubit
+from braket.registers.qubit_set import QubitSet
 
 
 class MomentType(str, Enum):
diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py
index 5abf19f6..498f70fd 100644
--- a/src/braket/circuits/noise.py
+++ b/src/braket/circuits/noise.py
@@ -21,12 +21,12 @@
 from braket.circuits.free_parameter_expression import FreeParameterExpression
 from braket.circuits.parameterizable import Parameterizable
 from braket.circuits.quantum_operator import QuantumOperator
-from braket.circuits.qubit_set import QubitSet
 from braket.circuits.serialization import (
     IRType,
     OpenQASMSerializationProperties,
     SerializationProperties,
 )
+from braket.registers.qubit_set import QubitSet
 
 
 class Noise(QuantumOperator):
diff --git a/src/braket/circuits/noise_helpers.py b/src/braket/circuits/noise_helpers.py
index 29ade8cf..2238a2d6 100644
--- a/src/braket/circuits/noise_helpers.py
+++ b/src/braket/circuits/noise_helpers.py
@@ -23,7 +23,7 @@
 from braket.circuits.moments import Moments
 from braket.circuits.noise import Noise
 from braket.circuits.quantum_operator_helpers import is_unitary
-from braket.circuits.qubit_set import QubitSet, QubitSetInput
+from braket.registers.qubit_set import QubitSet, QubitSetInput
 
 if TYPE_CHECKING:  # pragma: no cover
     from braket.circuits.circuit import Circuit
diff --git a/src/braket/circuits/noise_model/circuit_instruction_criteria.py b/src/braket/circuits/noise_model/circuit_instruction_criteria.py
index 2e592885..0a8c11bd 100644
--- a/src/braket/circuits/noise_model/circuit_instruction_criteria.py
+++ b/src/braket/circuits/noise_model/circuit_instruction_criteria.py
@@ -16,7 +16,7 @@
 
 from braket.circuits.instruction import Instruction
 from braket.circuits.noise_model.criteria import Criteria
-from braket.circuits.qubit_set import QubitSetInput
+from braket.registers.qubit_set import QubitSetInput
 
 
 class CircuitInstructionCriteria(Criteria):
diff --git a/src/braket/circuits/noise_model/criteria_input_parsing.py b/src/braket/circuits/noise_model/criteria_input_parsing.py
index 193525f3..a47d8d78 100644
--- a/src/braket/circuits/noise_model/criteria_input_parsing.py
+++ b/src/braket/circuits/noise_model/criteria_input_parsing.py
@@ -14,7 +14,7 @@
 from typing import Iterable, Optional, Set, Tuple, Union
 
 from braket.circuits.quantum_operator import QuantumOperator
-from braket.circuits.qubit_set import QubitSetInput
+from braket.registers.qubit_set import QubitSetInput
 
 
 def parse_operator_input(
diff --git a/src/braket/circuits/noise_model/gate_criteria.py b/src/braket/circuits/noise_model/gate_criteria.py
index 3505391d..571c6cb6 100644
--- a/src/braket/circuits/noise_model/gate_criteria.py
+++ b/src/braket/circuits/noise_model/gate_criteria.py
@@ -21,7 +21,7 @@
     parse_operator_input,
     parse_qubit_input,
 )
-from braket.circuits.qubit_set import QubitSetInput
+from braket.registers.qubit_set import QubitSetInput
 
 
 class GateCriteria(CircuitInstructionCriteria):
diff --git a/src/braket/circuits/noise_model/initialization_criteria.py b/src/braket/circuits/noise_model/initialization_criteria.py
index 6229573b..e40d4e9d 100644
--- a/src/braket/circuits/noise_model/initialization_criteria.py
+++ b/src/braket/circuits/noise_model/initialization_criteria.py
@@ -14,7 +14,7 @@
 from abc import abstractmethod
 
 from braket.circuits.noise_model.criteria import Criteria
-from braket.circuits.qubit_set import QubitSetInput
+from braket.registers.qubit_set import QubitSetInput
 
 
 class InitializationCriteria(Criteria):
diff --git a/src/braket/circuits/noise_model/noise_model.py b/src/braket/circuits/noise_model/noise_model.py
index 76cdffa2..b6805e33 100644
--- a/src/braket/circuits/noise_model/noise_model.py
+++ b/src/braket/circuits/noise_model/noise_model.py
@@ -25,8 +25,8 @@
 from braket.circuits.noise_model.criteria import Criteria, CriteriaKey, CriteriaKeyResult
 from braket.circuits.noise_model.initialization_criteria import InitializationCriteria
 from braket.circuits.noise_model.result_type_criteria import ResultTypeCriteria
-from braket.circuits.qubit_set import QubitSetInput
 from braket.circuits.result_types import ObservableResultType
+from braket.registers.qubit_set import QubitSetInput
 
 
 @dataclass
diff --git a/src/braket/circuits/noise_model/observable_criteria.py b/src/braket/circuits/noise_model/observable_criteria.py
index e6affc94..46c0e3e7 100644
--- a/src/braket/circuits/noise_model/observable_criteria.py
+++ b/src/braket/circuits/noise_model/observable_criteria.py
@@ -20,8 +20,8 @@
 )
 from braket.circuits.noise_model.result_type_criteria import ResultTypeCriteria
 from braket.circuits.observable import Observable
-from braket.circuits.qubit_set import QubitSetInput
 from braket.circuits.result_type import ObservableResultType, ResultType
+from braket.registers.qubit_set import QubitSetInput
 
 
 class ObservableCriteria(ResultTypeCriteria):
diff --git a/src/braket/circuits/noise_model/qubit_initialization_criteria.py b/src/braket/circuits/noise_model/qubit_initialization_criteria.py
index 0646845c..dade3a0e 100644
--- a/src/braket/circuits/noise_model/qubit_initialization_criteria.py
+++ b/src/braket/circuits/noise_model/qubit_initialization_criteria.py
@@ -16,7 +16,7 @@
 from braket.circuits.noise_model.criteria import Criteria, CriteriaKey, CriteriaKeyResult
 from braket.circuits.noise_model.criteria_input_parsing import parse_qubit_input
 from braket.circuits.noise_model.initialization_criteria import InitializationCriteria
-from braket.circuits.qubit_set import QubitSet, QubitSetInput
+from braket.registers.qubit_set import QubitSet, QubitSetInput
 
 
 class QubitInitializationCriteria(InitializationCriteria):
diff --git a/src/braket/circuits/noise_model/unitary_gate_criteria.py b/src/braket/circuits/noise_model/unitary_gate_criteria.py
index c8729298..7642a143 100644
--- a/src/braket/circuits/noise_model/unitary_gate_criteria.py
+++ b/src/braket/circuits/noise_model/unitary_gate_criteria.py
@@ -18,7 +18,7 @@
 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.circuits.qubit_set import QubitSetInput
+from braket.registers.qubit_set import QubitSetInput
 
 
 class UnitaryGateCriteria(CircuitInstructionCriteria):
diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py
index 75cec6c4..11dac20e 100644
--- a/src/braket/circuits/noises.py
+++ b/src/braket/circuits/noises.py
@@ -36,9 +36,9 @@
     is_cptp,
     verify_quantum_operator_matrix_dimensions,
 )
-from braket.circuits.qubit import QubitInput
-from braket.circuits.qubit_set import QubitSet, QubitSetInput
 from braket.circuits.serialization import OpenQASMSerializationProperties
+from braket.registers.qubit import QubitInput
+from braket.registers.qubit_set import QubitSet, QubitSetInput
 
 """
 To add a new Noise implementation:
diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py
index c63304c9..6b70d338 100644
--- a/src/braket/circuits/observable.py
+++ b/src/braket/circuits/observable.py
@@ -21,12 +21,12 @@
 
 from braket.circuits.gate import Gate
 from braket.circuits.quantum_operator import QuantumOperator
-from braket.circuits.qubit_set import QubitSet
 from braket.circuits.serialization import (
     IRType,
     OpenQASMSerializationProperties,
     SerializationProperties,
 )
+from braket.registers.qubit_set import QubitSet
 
 
 class Observable(QuantumOperator):
diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py
index b2717023..9b1a4904 100644
--- a/src/braket/circuits/observables.py
+++ b/src/braket/circuits/observables.py
@@ -29,8 +29,8 @@
     is_hermitian,
     verify_quantum_operator_matrix_dimensions,
 )
-from braket.circuits.qubit_set import QubitSet
 from braket.circuits.serialization import IRType, OpenQASMSerializationProperties
+from braket.registers.qubit_set import QubitSet
 
 
 class H(StandardObservable):
diff --git a/src/braket/circuits/qubit.py b/src/braket/circuits/qubit.py
index 479a453e..f82e91fa 100644
--- a/src/braket/circuits/qubit.py
+++ b/src/braket/circuits/qubit.py
@@ -11,58 +11,4 @@
 # 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 numbers
-from typing import Union
-
-QubitInput = Union["Qubit", int]
-
-
-class Qubit(int):
-    """
-    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):
-        """
-        Args:
-            index (int): Index of the qubit.
-
-        Raises:
-            ValueError: If `index` is less than zero.
-
-        Examples:
-            >>> Qubit(0)
-            >>> Qubit(1)
-        """
-        if not isinstance(index, numbers.Integral):
-            raise TypeError(f"Supplied qubit index, {index}, must be an integer.")
-        if index < 0:
-            raise ValueError(f"Supplied qubit index, {index}, cannot be less than zero.")
-        return super().__new__(cls, index)
-
-    def __repr__(self):
-        return f"Qubit({super().__repr__()})"
-
-    def __str__(self):
-        return self.__repr__()
-
-    @staticmethod
-    def new(qubit: QubitInput) -> Qubit:
-        """
-        Helper constructor - if input is a `Qubit` it returns the same value,
-        else a new `Qubit` is constructed.
-
-        Args:
-            qubit (QubitInput): `Qubit` index. If `type == Qubit` then the `qubit` is returned.
-
-        Returns:
-            Qubit: The qubit.
-        """
-
-        if isinstance(qubit, Qubit):
-            return qubit
-        else:
-            return Qubit(qubit)
+from braket.registers import Qubit, QubitInput  # noqa: F401
diff --git a/src/braket/circuits/qubit_set.py b/src/braket/circuits/qubit_set.py
index d4571e91..b2c1bbfc 100644
--- a/src/braket/circuits/qubit_set.py
+++ b/src/braket/circuits/qubit_set.py
@@ -11,83 +11,4 @@
 # 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, Dict, Iterable, Union
-
-from boltons.setutils import IndexedSet
-
-from braket.circuits.qubit import Qubit, QubitInput
-
-QubitSetInput = Union[QubitInput, Iterable[QubitInput]]
-
-
-def _flatten(other: Any) -> Any:
-    if isinstance(other, Iterable) and not isinstance(other, str):
-        for item in other:
-            yield from _flatten(item)
-    else:
-        yield other
-
-
-class QubitSet(IndexedSet):
-    """
-    An ordered, unique set of quantum bits.
-
-    Note:
-        QubitSet implements `__hash__()` but is a mutable object, therefore be careful when
-        mutating this object.
-    """
-
-    def __init__(self, qubits: QubitSetInput = None):
-        """
-        Args:
-            qubits (QubitSetInput): Qubits to be included in the `QubitSet`. Default is `None`.
-
-        Examples:
-            >>> 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
-        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`.
-        If this instance contains a qubit that is not in the `mapping` that qubit is not modified.
-
-        Args:
-            mapping (Dict[QubitInput, QubitInput]): A dictionary of qubit mappings to
-                apply. Key is the qubit in this instance to target, and the value is what
-                the key will be changed to.
-
-        Returns:
-            QubitSet: A new QubitSet with the `mapping` applied.
-
-        Examples:
-            >>> qubits = QubitSet([0, 1])
-            >>> mapping = {0: 10, Qubit(1): Qubit(11)}
-            >>> qubits.map(mapping)
-            QubitSet([Qubit(10), Qubit(11)])
-        """
-
-        new_qubits = [mapping.get(qubit, qubit) for qubit in self]
-
-        return QubitSet(new_qubits)
-
-    def __hash__(self):
-        return hash(tuple(self))
+from braket.registers.qubit_set import QubitSet, QubitSetInput  # noqa: F401
diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py
index d711e273..787d9ee0 100644
--- a/src/braket/circuits/result_type.py
+++ b/src/braket/circuits/result_type.py
@@ -18,13 +18,13 @@
 from braket.circuits.free_parameter import FreeParameter
 from braket.circuits.observable import Observable
 from braket.circuits.observables import Sum
-from braket.circuits.qubit import QubitInput
-from braket.circuits.qubit_set import QubitSet, QubitSetInput
 from braket.circuits.serialization import (
     IRType,
     OpenQASMSerializationProperties,
     SerializationProperties,
 )
+from braket.registers.qubit import QubitInput
+from braket.registers.qubit_set import QubitSet, QubitSetInput
 
 
 class ResultType:
diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py
index d73c7fbd..25ff6371 100644
--- a/src/braket/circuits/result_types.py
+++ b/src/braket/circuits/result_types.py
@@ -22,13 +22,13 @@
 from braket.circuits.free_parameter import FreeParameter
 from braket.circuits.observable import Observable
 from braket.circuits.observables import Sum
-from braket.circuits.qubit_set import QubitSet, QubitSetInput
 from braket.circuits.result_type import (
     ObservableParameterResultType,
     ObservableResultType,
     ResultType,
 )
 from braket.circuits.serialization import IRType, OpenQASMSerializationProperties
+from braket.registers.qubit_set import QubitSet, QubitSetInput
 
 """
 To add a new result type:
diff --git a/src/braket/circuits/unitary_calculation.py b/src/braket/circuits/unitary_calculation.py
index 628d230e..2059976e 100644
--- a/src/braket/circuits/unitary_calculation.py
+++ b/src/braket/circuits/unitary_calculation.py
@@ -19,8 +19,8 @@
 from braket.circuits.compiler_directive import CompilerDirective
 from braket.circuits.gate import Gate
 from braket.circuits.instruction import Instruction
-from braket.circuits.qubit_set import QubitSet
 from braket.default_simulator.linalg_utils import multiply_matrix
+from braket.registers.qubit_set import QubitSet
 
 
 def _einsum_subscripts(targets: QubitSet, qubit_count: int) -> str:
diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py
index 3606bbd1..0a3dba15 100644
--- a/src/braket/pulse/pulse_sequence.py
+++ b/src/braket/pulse/pulse_sequence.py
@@ -22,7 +22,6 @@
 from oqpy import BitVar, PhysicalQubits, Program
 from oqpy.timing import OQDurationLiteral
 
-from braket.circuits.qubit_set import QubitSet
 from braket.parametric.free_parameter import FreeParameter
 from braket.parametric.free_parameter_expression import FreeParameterExpression
 from braket.parametric.parameterizable import Parameterizable
@@ -36,6 +35,7 @@
 from braket.pulse.frame import Frame
 from braket.pulse.pulse_sequence_trace import PulseSequenceTrace
 from braket.pulse.waveforms import Waveform
+from braket.registers.qubit_set import QubitSet
 
 
 class PulseSequence:
diff --git a/src/braket/registers/__init__.py b/src/braket/registers/__init__.py
new file mode 100644
index 00000000..0f433333
--- /dev/null
+++ b/src/braket/registers/__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.registers.qubit import Qubit, QubitInput  # noqa: F401
+from braket.registers.qubit_set import QubitSet, QubitSetInput  # noqa: F401
diff --git a/src/braket/registers/qubit.py b/src/braket/registers/qubit.py
new file mode 100644
index 00000000..479a453e
--- /dev/null
+++ b/src/braket/registers/qubit.py
@@ -0,0 +1,68 @@
+# 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 numbers
+from typing import Union
+
+QubitInput = Union["Qubit", int]
+
+
+class Qubit(int):
+    """
+    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):
+        """
+        Args:
+            index (int): Index of the qubit.
+
+        Raises:
+            ValueError: If `index` is less than zero.
+
+        Examples:
+            >>> Qubit(0)
+            >>> Qubit(1)
+        """
+        if not isinstance(index, numbers.Integral):
+            raise TypeError(f"Supplied qubit index, {index}, must be an integer.")
+        if index < 0:
+            raise ValueError(f"Supplied qubit index, {index}, cannot be less than zero.")
+        return super().__new__(cls, index)
+
+    def __repr__(self):
+        return f"Qubit({super().__repr__()})"
+
+    def __str__(self):
+        return self.__repr__()
+
+    @staticmethod
+    def new(qubit: QubitInput) -> Qubit:
+        """
+        Helper constructor - if input is a `Qubit` it returns the same value,
+        else a new `Qubit` is constructed.
+
+        Args:
+            qubit (QubitInput): `Qubit` index. If `type == Qubit` then the `qubit` is returned.
+
+        Returns:
+            Qubit: The qubit.
+        """
+
+        if isinstance(qubit, Qubit):
+            return qubit
+        else:
+            return Qubit(qubit)
diff --git a/src/braket/registers/qubit_set.py b/src/braket/registers/qubit_set.py
new file mode 100644
index 00000000..ea012bc2
--- /dev/null
+++ b/src/braket/registers/qubit_set.py
@@ -0,0 +1,93 @@
+# 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, Dict, Iterable, Union
+
+from boltons.setutils import IndexedSet
+
+from braket.registers.qubit import Qubit, QubitInput
+
+QubitSetInput = Union[QubitInput, Iterable[QubitInput]]
+
+
+def _flatten(other: Any) -> Any:
+    if isinstance(other, Iterable) and not isinstance(other, str):
+        for item in other:
+            yield from _flatten(item)
+    else:
+        yield other
+
+
+class QubitSet(IndexedSet):
+    """
+    An ordered, unique set of quantum bits.
+
+    Note:
+        QubitSet implements `__hash__()` but is a mutable object, therefore be careful when
+        mutating this object.
+    """
+
+    def __init__(self, qubits: QubitSetInput = None):
+        """
+        Args:
+            qubits (QubitSetInput): Qubits to be included in the `QubitSet`. Default is `None`.
+
+        Examples:
+            >>> 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
+        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`.
+        If this instance contains a qubit that is not in the `mapping` that qubit is not modified.
+
+        Args:
+            mapping (Dict[QubitInput, QubitInput]): A dictionary of qubit mappings to
+                apply. Key is the qubit in this instance to target, and the value is what
+                the key will be changed to.
+
+        Returns:
+            QubitSet: A new QubitSet with the `mapping` applied.
+
+        Examples:
+            >>> qubits = QubitSet([0, 1])
+            >>> mapping = {0: 10, Qubit(1): Qubit(11)}
+            >>> qubits.map(mapping)
+            QubitSet([Qubit(10), Qubit(11)])
+        """
+
+        new_qubits = [mapping.get(qubit, qubit) for qubit in self]
+
+        return QubitSet(new_qubits)
+
+    def __hash__(self):
+        return hash(tuple(self))
diff --git a/test/unit_tests/braket/circuits/test_noise_helpers.py b/test/unit_tests/braket/circuits/test_noise_helpers.py
index 421cf69c..f2c956ea 100644
--- a/test/unit_tests/braket/circuits/test_noise_helpers.py
+++ b/test/unit_tests/braket/circuits/test_noise_helpers.py
@@ -20,7 +20,7 @@
 from braket.circuits.moments import Moments
 from braket.circuits.noise import Noise
 from braket.circuits.noise_helpers import apply_noise_to_gates, apply_noise_to_moments
-from braket.circuits.qubit_set import QubitSet
+from braket.registers.qubit_set import QubitSet
 
 invalid_data_noise_type = [Gate.X(), None, 1.5]
 invalid_data_target_gates_type = [[-1, "foo"], [1.5, None, -1], "X", [Gate.X, "CNot"]]
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 786c6b7e..c4199e81 100644
--- a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py
+++ b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py
@@ -19,12 +19,12 @@
 from openpulse import ast
 from oqpy import IntVar
 
-from braket.circuits.qubit_set import QubitSet
 from braket.pulse import ArbitraryWaveform, ConstantWaveform, DragGaussianWaveform, GaussianWaveform
 from braket.pulse.ast.approximation_parser import _ApproximationParser
 from braket.pulse.frame import Frame
 from braket.pulse.port import Port
 from braket.pulse.pulse_sequence import PulseSequence
+from braket.registers.qubit_set import QubitSet
 from braket.timings.time_series import TimeSeries, _all_close
 
 
diff --git a/test/unit_tests/braket/circuits/test_qubit.py b/test/unit_tests/braket/registers/test_qubit.py
similarity index 97%
rename from test/unit_tests/braket/circuits/test_qubit.py
rename to test/unit_tests/braket/registers/test_qubit.py
index b961986c..98f89cf8 100644
--- a/test/unit_tests/braket/circuits/test_qubit.py
+++ b/test/unit_tests/braket/registers/test_qubit.py
@@ -14,7 +14,7 @@
 import numpy as np
 import pytest
 
-from braket.circuits import Qubit
+from braket.registers import Qubit
 
 
 @pytest.fixture
diff --git a/test/unit_tests/braket/circuits/test_qubit_set.py b/test/unit_tests/braket/registers/test_qubit_set.py
similarity index 97%
rename from test/unit_tests/braket/circuits/test_qubit_set.py
rename to test/unit_tests/braket/registers/test_qubit_set.py
index 4a7eda39..1fd8d721 100644
--- a/test/unit_tests/braket/circuits/test_qubit_set.py
+++ b/test/unit_tests/braket/registers/test_qubit_set.py
@@ -13,7 +13,7 @@
 
 import pytest
 
-from braket.circuits import Qubit, QubitSet
+from braket.registers import Qubit, QubitSet
 
 
 @pytest.fixture

From 78808d1e1235d334e7a7041aa834898a5d40848c Mon Sep 17 00:00:00 2001
From: ci 
Date: Tue, 3 Oct 2023 16:16:13 +0000
Subject: [PATCH 0871/1165] prepare release v1.56.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 fb09fb1c..238a38e2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## v1.56.2 (2023-10-03)
+
+### Bug Fixes and Other Changes
+
+ * Refactor Qubit and QubitSet to a separate module
+
 ## v1.56.1 (2023-09-27)
 
 ### Bug Fixes and Other Changes
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index f9bd793a..3c8890fa 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.56.2.dev0"
+__version__ = "1.56.2"

From bb05cae237de19b05766435f18a4abb20138fe5f Mon Sep 17 00:00:00 2001
From: ci 
Date: Tue, 3 Oct 2023 16:16:13 +0000
Subject: [PATCH 0872/1165] update development version to v1.56.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 3c8890fa..b01e8f75 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.56.2"
+__version__ = "1.56.3.dev0"

From 82ced098f92e5973639e8496d1df335f0e85f97b Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Tue, 3 Oct 2023 17:44:36 -0700
Subject: [PATCH 0873/1165] feat: job helper functions (#720)

Co-authored-by: Matthew Beach <85963088+mbeach-aws@users.noreply.github.com>
---
 examples/job.py                               |  4 +-
 src/braket/jobs/__init__.py                   |  8 +++
 src/braket/jobs/data_persistence.py           | 10 +--
 src/braket/jobs/environment_variables.py      | 71 +++++++++++++++++++
 test/integ_tests/job_test_script.py           | 16 ++---
 .../braket/jobs/test_environment_variables.py | 66 +++++++++++++++++
 6 files changed, 160 insertions(+), 15 deletions(-)
 create mode 100644 src/braket/jobs/environment_variables.py
 create mode 100644 test/unit_tests/braket/jobs/test_environment_variables.py

diff --git a/examples/job.py b/examples/job.py
index fdb2cec6..f5e330d1 100644
--- a/examples/job.py
+++ b/examples/job.py
@@ -16,11 +16,11 @@
 from braket.aws import AwsDevice, AwsQuantumJob
 from braket.circuits import Circuit
 from braket.devices import Devices
-from braket.jobs import save_job_result
+from braket.jobs import get_job_device_arn, save_job_result
 
 
 def run_job():
-    device = AwsDevice(os.environ.get("AMZN_BRAKET_DEVICE_ARN"))
+    device = AwsDevice(get_job_device_arn())
 
     bell = Circuit().h(0).cnot(0, 1)
     num_tasks = 10
diff --git a/src/braket/jobs/__init__.py b/src/braket/jobs/__init__.py
index e58ba16e..478305df 100644
--- a/src/braket/jobs/__init__.py
+++ b/src/braket/jobs/__init__.py
@@ -23,4 +23,12 @@
     save_job_checkpoint,
     save_job_result,
 )
+from braket.jobs.environment_variables import (  # noqa: F401
+    get_checkpoint_dir,
+    get_hyperparameters,
+    get_input_data_dir,
+    get_job_device_arn,
+    get_job_name,
+    get_results_dir,
+)
 from braket.jobs.image_uris import Framework, retrieve_image  # noqa: F401
diff --git a/src/braket/jobs/data_persistence.py b/src/braket/jobs/data_persistence.py
index 198a7e22..8a04d0a5 100644
--- a/src/braket/jobs/data_persistence.py
+++ b/src/braket/jobs/data_persistence.py
@@ -11,9 +11,9 @@
 # ANY KIND, either express or implied. See the License for the specific
 # language governing permissions and limitations under the License.
 
-import os
 from typing import Any, Dict
 
+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
 
@@ -49,8 +49,8 @@ def save_job_checkpoint(
     """
     if not checkpoint_data:
         raise ValueError("The checkpoint_data argument cannot be empty.")
-    checkpoint_directory = os.environ["AMZN_BRAKET_CHECKPOINT_DIR"]
-    job_name = os.environ["AMZN_BRAKET_JOB_NAME"]
+    checkpoint_directory = get_checkpoint_dir()
+    job_name = get_job_name()
     checkpoint_file_path = (
         f"{checkpoint_directory}/{job_name}_{checkpoint_file_suffix}.json"
         if checkpoint_file_suffix
@@ -90,7 +90,7 @@ def load_job_checkpoint(job_name: str, checkpoint_file_suffix: str = "") -> Dict
         ValueError: If the data stored in the checkpoint file can't be deserialized (possibly due to
             corruption).
     """
-    checkpoint_directory = os.environ["AMZN_BRAKET_CHECKPOINT_DIR"]
+    checkpoint_directory = get_checkpoint_dir()
     checkpoint_file_path = (
         f"{checkpoint_directory}/{job_name}_{checkpoint_file_suffix}.json"
         if checkpoint_file_suffix
@@ -128,7 +128,7 @@ def save_job_result(
     """
     if not result_data:
         raise ValueError("The result_data argument cannot be empty.")
-    result_directory = os.environ["AMZN_BRAKET_JOB_RESULTS_DIR"]
+    result_directory = get_results_dir()
     result_path = f"{result_directory}/results.json"
     with open(result_path, "w") as f:
         serialized_data = serialize_values(result_data or {}, data_format)
diff --git a/src/braket/jobs/environment_variables.py b/src/braket/jobs/environment_variables.py
new file mode 100644
index 00000000..489b1f18
--- /dev/null
+++ b/src/braket/jobs/environment_variables.py
@@ -0,0 +1,71 @@
+import json
+import os
+from typing import Dict
+
+
+def get_job_name() -> str:
+    """
+    Get the name of the current job.
+
+    Returns:
+        str: The name of the job if in a job, else an empty string.
+    """
+    return os.getenv("AMZN_BRAKET_JOB_NAME", "")
+
+
+def get_job_device_arn() -> str:
+    """
+    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".
+    """
+    return os.getenv("AMZN_BRAKET_DEVICE_ARN", "local:none/none")
+
+
+def get_input_data_dir(channel: str = "input") -> str:
+    """
+    Get the job input data directory.
+
+    Args:
+        channel (str): The name of the input channel. Default value
+            corresponds to the default input channel name, `input`.
+
+    Returns:
+        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
+
+
+def get_results_dir() -> str:
+    """
+    Get the job result directory.
+
+    Returns:
+        str: The results directory, defaulting to current working directory.
+    """
+    return os.getenv("AMZN_BRAKET_JOB_RESULTS_DIR", ".")
+
+
+def get_checkpoint_dir() -> str:
+    """
+    Get the job checkpoint directory.
+    Returns:
+        str: The checkpoint directory, defaulting to current working directory.
+    """
+    return os.getenv("AMZN_BRAKET_CHECKPOINT_DIR", ".")
+
+
+def get_hyperparameters() -> Dict[str, str]:
+    """
+    Get the job checkpoint directory.
+    Returns:
+        str: The checkpoint directory, defaulting to current working directory.
+    """
+    if "AMZN_BRAKET_HP_FILE" in os.environ:
+        with open(os.getenv("AMZN_BRAKET_HP_FILE"), "r") as f:
+            return json.load(f)
+    return {}
diff --git a/test/integ_tests/job_test_script.py b/test/integ_tests/job_test_script.py
index 7071ffd6..1fd7a364 100644
--- a/test/integ_tests/job_test_script.py
+++ b/test/integ_tests/job_test_script.py
@@ -11,19 +11,19 @@
 # ANY KIND, either express or implied. See the License for the specific
 # language governing permissions and limitations under the License.
 
-import json
-import os
-
 from braket.aws import AwsDevice
 from braket.circuits import Circuit
-from braket.jobs import save_job_checkpoint, save_job_result
+from braket.jobs import (
+    get_hyperparameters,
+    get_job_device_arn,
+    save_job_checkpoint,
+    save_job_result,
+)
 from braket.jobs_data import PersistedJobDataFormat
 
 
 def start_here():
-    hp_file = os.environ["AMZN_BRAKET_HP_FILE"]
-    with open(hp_file, "r") as f:
-        hyperparameters = json.load(f)
+    hyperparameters = get_hyperparameters()
 
     if hyperparameters["test_case"] == "completed":
         completed_job_script()
@@ -40,7 +40,7 @@ def completed_job_script():
     print("Test job started!!!!!")
 
     # Use the device declared in the Orchestration Script
-    device = AwsDevice(os.environ["AMZN_BRAKET_DEVICE_ARN"])
+    device = AwsDevice(get_job_device_arn())
 
     bell = Circuit().h(0).cnot(0, 1)
     for count in range(5):
diff --git a/test/unit_tests/braket/jobs/test_environment_variables.py b/test/unit_tests/braket/jobs/test_environment_variables.py
new file mode 100644
index 00000000..2c8a2f54
--- /dev/null
+++ b/test/unit_tests/braket/jobs/test_environment_variables.py
@@ -0,0 +1,66 @@
+import json
+import os
+import tempfile
+from pathlib import Path
+from unittest.mock import patch
+
+from braket.jobs import (
+    get_checkpoint_dir,
+    get_hyperparameters,
+    get_input_data_dir,
+    get_job_device_arn,
+    get_job_name,
+    get_results_dir,
+)
+
+
+def test_job_name():
+    assert get_job_name() == ""
+    job_name = "my_job_name"
+    with patch.dict(os.environ, {"AMZN_BRAKET_JOB_NAME": job_name}):
+        assert get_job_name() == job_name
+
+
+def test_job_device_arn():
+    assert get_job_device_arn() == "local:none/none"
+    device_arn = "my_device_arn"
+    with patch.dict(os.environ, {"AMZN_BRAKET_DEVICE_ARN": device_arn}):
+        assert get_job_device_arn() == device_arn
+
+
+def test_input_data_dir():
+    assert get_input_data_dir() == "."
+    input_path = "my/input/path"
+    with patch.dict(os.environ, {"AMZN_BRAKET_INPUT_DIR": input_path}):
+        assert get_input_data_dir() == f"{input_path}/input"
+        channel_name = "my_channel"
+        assert get_input_data_dir(channel_name) == f"{input_path}/{channel_name}"
+
+
+def test_results_dir():
+    assert get_results_dir() == "."
+    results_dir = "my_results_dir"
+    with patch.dict(os.environ, {"AMZN_BRAKET_JOB_RESULTS_DIR": results_dir}):
+        assert get_results_dir() == results_dir
+
+
+def test_checkpoint_dir():
+    assert get_checkpoint_dir() == "."
+    checkpoint_dir = "my_checkpoint_dir"
+    with patch.dict(os.environ, {"AMZN_BRAKET_CHECKPOINT_DIR": checkpoint_dir}):
+        assert get_checkpoint_dir() == checkpoint_dir
+
+
+def test_hyperparameters():
+    assert get_hyperparameters() == {}
+    hp_file = "my_hyperparameters.json"
+    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 open(str(Path(temp_dir) / hp_file), "w") as f:
+            json.dump(hyperparameters, f)
+        assert get_hyperparameters() == hyperparameters

From 5c1e4e7dd7389416d5aaf5f6e9388a79c441d53d Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Wed, 4 Oct 2023 00:52:43 -0700
Subject: [PATCH 0874/1165] feat: wrap non-dict results and update results on
 subsequent calls (#721)

---
 src/braket/aws/aws_quantum_job.py             | 13 +--
 src/braket/jobs/data_persistence.py           | 80 +++++++++++++++----
 .../braket/jobs/test_data_persistence.py      | 72 ++++++++++++++++-
 3 files changed, 135 insertions(+), 30 deletions(-)

diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py
index 3120384f..77161157 100644
--- a/src/braket/aws/aws_quantum_job.py
+++ b/src/braket/aws/aws_quantum_job.py
@@ -36,6 +36,7 @@
     S3DataSourceConfig,
     StoppingCondition,
 )
+from braket.jobs.data_persistence import load_job_result
 from braket.jobs.metrics_data.cwl_insights_metrics_fetcher import CwlInsightsMetricsFetcher
 
 # TODO: Have added metric file in metrics folder, but have to decide on the name for keep
@@ -43,8 +44,6 @@
 from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType
 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 AwsQuantumJob(QuantumJob):
@@ -482,15 +481,7 @@ def result(
 
     @staticmethod
     def _read_and_deserialize_results(temp_dir: str, job_name: str) -> Dict[str, Any]:
-        try:
-            with open(f"{temp_dir}/{job_name}/{AwsQuantumJob.RESULTS_FILENAME}", "r") as f:
-                persisted_data = PersistedJobData.parse_raw(f.read())
-                deserialized_data = deserialize_values(
-                    persisted_data.dataDictionary, persisted_data.dataFormat
-                )
-                return deserialized_data
-        except FileNotFoundError:
-            return {}
+        return load_job_result(Path(temp_dir, job_name, AwsQuantumJob.RESULTS_FILENAME))
 
     def download_result(
         self,
diff --git a/src/braket/jobs/data_persistence.py b/src/braket/jobs/data_persistence.py
index 8a04d0a5..aa574d0d 100644
--- a/src/braket/jobs/data_persistence.py
+++ b/src/braket/jobs/data_persistence.py
@@ -10,8 +10,8 @@
 # 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 typing import Any, Dict
+from pathlib import Path
+from typing import Any, Dict, Union
 
 from braket.jobs.environment_variables import get_checkpoint_dir, get_job_name, get_results_dir
 from braket.jobs.serialization import deserialize_values, serialize_values
@@ -104,33 +104,83 @@ def load_job_checkpoint(job_name: str, checkpoint_file_suffix: str = "") -> Dict
         return deserialized_data
 
 
+def _load_persisted_data(filename: Union[str, Path] = None) -> PersistedJobData:
+    filename = filename or Path(get_results_dir()) / "results.json"
+    try:
+        with open(filename, mode="r") as f:
+            return PersistedJobData.parse_raw(f.read())
+    except FileNotFoundError:
+        return PersistedJobData(
+            dataDictionary={},
+            dataFormat=PersistedJobDataFormat.PLAINTEXT,
+        )
+
+
+def load_job_result(filename: Union[str, Path] = None) -> Dict[str, Any]:
+    """
+    Loads job result of currently running job.
+
+    Args:
+        filename (Union[str, Path]): Location of job results. Default `results.json` in job
+            results directory in a job instance or in working directory locally. This file
+            must be in the format used by `save_job_result`.
+
+    Returns:
+         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
+
+
 def save_job_result(
-    result_data: Dict[str, Any],
-    data_format: PersistedJobDataFormat = PersistedJobDataFormat.PLAINTEXT,
+    result_data: Union[Dict[str, Any], Any],
+    data_format: PersistedJobDataFormat = None,
 ) -> None:
     """
     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`.
 
-    Note: This function for storing the results is only for use inside the hybrid job container
+    Note: This function for storing the results is only for use inside the job container
           as it writes data to directories and references env variables set in the containers.
 
 
     Args:
-        result_data (Dict[str, Any]): Dict that specifies the result data to be persisted.
+        result_data (Union[Dict[str, Any], Any]): Dict that specifies the result data to be
+            persisted. If result data is not a dict, then it will be wrapped as
+            `{"result": result_data}`.
         data_format (PersistedJobDataFormat): 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:
-        ValueError: If the supplied `result_data` is `None` or empty.
     """
-    if not result_data:
-        raise ValueError("The result_data argument cannot be empty.")
-    result_directory = get_results_dir()
-    result_path = f"{result_directory}/results.json"
-    with open(result_path, "w") as f:
-        serialized_data = serialize_values(result_data or {}, data_format)
+    if not isinstance(result_data, dict):
+        result_data = {"result": result_data}
+
+    current_persisted_data = _load_persisted_data()
+
+    if current_persisted_data.dataFormat == PersistedJobDataFormat.PICKLED_V4:
+        # if results are already pickled, maintain pickled format
+        # if user explicitly specifies plaintext, raise error
+        if data_format == PersistedJobDataFormat.PLAINTEXT:
+            raise TypeError(
+                "Cannot update results object serialized with "
+                f"{current_persisted_data.dataFormat.value} using data format "
+                f"{data_format.value}."
+            )
+
+        data_format = PersistedJobDataFormat.PICKLED_V4
+
+    # if not specified or already pickled, default to plaintext
+    data_format = data_format or PersistedJobDataFormat.PLAINTEXT
+
+    current_results = deserialize_values(
+        current_persisted_data.dataDictionary,
+        current_persisted_data.dataFormat,
+    )
+    updated_results = {**current_results, **result_data}
+
+    with open(Path(get_results_dir()) / "results.json", "w") 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/test/unit_tests/braket/jobs/test_data_persistence.py b/test/unit_tests/braket/jobs/test_data_persistence.py
index d40d2c20..6a5e2728 100644
--- a/test/unit_tests/braket/jobs/test_data_persistence.py
+++ b/test/unit_tests/braket/jobs/test_data_persistence.py
@@ -20,7 +20,12 @@
 import numpy as np
 import pytest
 
-from braket.jobs.data_persistence import load_job_checkpoint, save_job_checkpoint, save_job_result
+from braket.jobs.data_persistence import (
+    load_job_checkpoint,
+    load_job_result,
+    save_job_checkpoint,
+    save_job_result,
+)
 from braket.jobs_data import PersistedJobDataFormat
 
 
@@ -266,9 +271,68 @@ def test_save_job_result(data_format, result_data, expected_saved_data):
             assert expected_file.read() == expected_saved_data
 
 
-@pytest.mark.xfail(raises=ValueError)
 @pytest.mark.parametrize("result_data", [{}, None])
-def test_save_job_result_raises_error_empty_data(result_data):
+def test_save_job_result_does_not_raise_error_empty_data(result_data):
     with tempfile.TemporaryDirectory() as tmp_dir:
-        with patch.dict(os.environ, {"AMZN_BRAKET_CHECKPOINT_DIR": tmp_dir}):
+        with patch.dict(os.environ, {"AMZN_BRAKET_JOB_RESULTS_DIR": tmp_dir}):
             save_job_result(result_data)
+
+
+@pytest.mark.parametrize(
+    "first_result_data,"
+    "first_data_format,"
+    "second_result_data,"
+    "second_data_format,"
+    "expected_result_data",
+    (
+        (
+            "hello",
+            PersistedJobDataFormat.PLAINTEXT,
+            "goodbye",
+            PersistedJobDataFormat.PLAINTEXT,
+            {"result": "goodbye"},
+        ),
+        (
+            "hello",
+            PersistedJobDataFormat.PLAINTEXT,
+            "goodbye",
+            PersistedJobDataFormat.PICKLED_V4,
+            {"result": "goodbye"},
+        ),
+        ("hello", PersistedJobDataFormat.PICKLED_V4, "goodbye", None, {"result": "goodbye"}),
+        (
+            # not json serializable
+            PersistedJobDataFormat,
+            PersistedJobDataFormat.PICKLED_V4,
+            {"other_field": "value"},
+            None,
+            {"result": PersistedJobDataFormat, "other_field": "value"},
+        ),
+    ),
+)
+def test_update_result_data(
+    first_result_data,
+    first_data_format,
+    second_result_data,
+    second_data_format,
+    expected_result_data,
+):
+    with tempfile.TemporaryDirectory() as tmp_dir:
+        with patch.dict(os.environ, {"AMZN_BRAKET_JOB_RESULTS_DIR": tmp_dir}):
+            save_job_result(first_result_data, first_data_format)
+            save_job_result(second_result_data, second_data_format)
+
+            assert load_job_result() == expected_result_data
+
+
+def test_update_pickled_results_as_plaintext_error():
+    with tempfile.TemporaryDirectory() as tmp_dir:
+        with patch.dict(os.environ, {"AMZN_BRAKET_JOB_RESULTS_DIR": tmp_dir}):
+            save_job_result(np.arange(5), PersistedJobDataFormat.PICKLED_V4)
+
+            cannot_convert_pickled_to_plaintext = (
+                "Cannot update results object serialized with "
+                "pickled_v4 using data format plaintext."
+            )
+            with pytest.raises(TypeError, match=cannot_convert_pickled_to_plaintext):
+                save_job_result("hello", PersistedJobDataFormat.PLAINTEXT)

From 3d62becd9e297fabdedc66fe671fa86702728e61 Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Wed, 4 Oct 2023 10:50:23 -0700
Subject: [PATCH 0875/1165] fix: revert integ test changes (#722)

---
 test/integ_tests/job_test_script.py | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/test/integ_tests/job_test_script.py b/test/integ_tests/job_test_script.py
index 1fd7a364..7071ffd6 100644
--- a/test/integ_tests/job_test_script.py
+++ b/test/integ_tests/job_test_script.py
@@ -11,19 +11,19 @@
 # ANY KIND, either express or implied. See the License for the specific
 # language governing permissions and limitations under the License.
 
+import json
+import os
+
 from braket.aws import AwsDevice
 from braket.circuits import Circuit
-from braket.jobs import (
-    get_hyperparameters,
-    get_job_device_arn,
-    save_job_checkpoint,
-    save_job_result,
-)
+from braket.jobs import save_job_checkpoint, save_job_result
 from braket.jobs_data import PersistedJobDataFormat
 
 
 def start_here():
-    hyperparameters = get_hyperparameters()
+    hp_file = os.environ["AMZN_BRAKET_HP_FILE"]
+    with open(hp_file, "r") as f:
+        hyperparameters = json.load(f)
 
     if hyperparameters["test_case"] == "completed":
         completed_job_script()
@@ -40,7 +40,7 @@ def completed_job_script():
     print("Test job started!!!!!")
 
     # Use the device declared in the Orchestration Script
-    device = AwsDevice(get_job_device_arn())
+    device = AwsDevice(os.environ["AMZN_BRAKET_DEVICE_ARN"])
 
     bell = Circuit().h(0).cnot(0, 1)
     for count in range(5):

From bdea9a1208b73170a03a49d104e623d7f7ab9465 Mon Sep 17 00:00:00 2001
From: ci 
Date: Wed, 4 Oct 2023 18:08:10 +0000
Subject: [PATCH 0876/1165] prepare release v1.57.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 238a38e2..4aa6cf9f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
 # Changelog
 
+## v1.57.0 (2023-10-04)
+
+### Features
+
+ * wrap non-dict results and update results on subsequent calls
+ * job helper functions
+
+### Bug Fixes and Other Changes
+
+ * revert integ test changes
+
 ## v1.56.2 (2023-10-03)
 
 ### Bug Fixes and Other Changes
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index b01e8f75..3538fc58 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.56.3.dev0"
+__version__ = "1.57.0"

From abfda05c99b60945a44f396357ef86306ffd0149 Mon Sep 17 00:00:00 2001
From: ci 
Date: Wed, 4 Oct 2023 18:08:10 +0000
Subject: [PATCH 0877/1165] update development version to v1.57.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 3538fc58..e32e384d 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.57.0"
+__version__ = "1.57.1.dev0"

From 7977b948702db7549878ebdca071cfe49a6ea2c6 Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Wed, 4 Oct 2023 14:34:23 -0700
Subject: [PATCH 0878/1165] test: add job helpers to integ test (#724)

This reverts commit 3d62becd9e297fabdedc66fe671fa86702728e61.
---
 test/integ_tests/job_test_script.py | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/test/integ_tests/job_test_script.py b/test/integ_tests/job_test_script.py
index 7071ffd6..1fd7a364 100644
--- a/test/integ_tests/job_test_script.py
+++ b/test/integ_tests/job_test_script.py
@@ -11,19 +11,19 @@
 # ANY KIND, either express or implied. See the License for the specific
 # language governing permissions and limitations under the License.
 
-import json
-import os
-
 from braket.aws import AwsDevice
 from braket.circuits import Circuit
-from braket.jobs import save_job_checkpoint, save_job_result
+from braket.jobs import (
+    get_hyperparameters,
+    get_job_device_arn,
+    save_job_checkpoint,
+    save_job_result,
+)
 from braket.jobs_data import PersistedJobDataFormat
 
 
 def start_here():
-    hp_file = os.environ["AMZN_BRAKET_HP_FILE"]
-    with open(hp_file, "r") as f:
-        hyperparameters = json.load(f)
+    hyperparameters = get_hyperparameters()
 
     if hyperparameters["test_case"] == "completed":
         completed_job_script()
@@ -40,7 +40,7 @@ def completed_job_script():
     print("Test job started!!!!!")
 
     # Use the device declared in the Orchestration Script
-    device = AwsDevice(os.environ["AMZN_BRAKET_DEVICE_ARN"])
+    device = AwsDevice(get_job_device_arn())
 
     bell = Circuit().h(0).cnot(0, 1)
     for count in range(5):

From ba6bb52ea0b22087efb2bf4cf2c3526eb2c07df0 Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Wed, 4 Oct 2023 15:36:35 -0700
Subject: [PATCH 0879/1165] docs: fix helper docstring (#725)

---
 src/braket/jobs/environment_variables.py | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/braket/jobs/environment_variables.py b/src/braket/jobs/environment_variables.py
index 489b1f18..e298304c 100644
--- a/src/braket/jobs/environment_variables.py
+++ b/src/braket/jobs/environment_variables.py
@@ -53,6 +53,7 @@ def get_results_dir() -> str:
 def get_checkpoint_dir() -> str:
     """
     Get the job checkpoint directory.
+
     Returns:
         str: The checkpoint directory, defaulting to current working directory.
     """
@@ -61,9 +62,10 @@ def get_checkpoint_dir() -> str:
 
 def get_hyperparameters() -> Dict[str, str]:
     """
-    Get the job checkpoint directory.
+    Get the job hyperparameters as strings.
+
     Returns:
-        str: The checkpoint directory, defaulting to current working directory.
+        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:

From 52059c5bcae5777b3246e2836d5cb86b7cbbb227 Mon Sep 17 00:00:00 2001
From: ci 
Date: Thu, 5 Oct 2023 16:15:19 +0000
Subject: [PATCH 0880/1165] prepare release v1.57.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 4aa6cf9f..3e7fd4e9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## v1.57.1 (2023-10-05)
+
+### Bug Fixes and Other Changes
+
+ * docs: fix helper docstring
+
 ## v1.57.0 (2023-10-04)
 
 ### Features
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index e32e384d..e4a9c4c5 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.57.1.dev0"
+__version__ = "1.57.1"

From 6eee1aac18625a6f3e32e6bb75b60500f357c9fa Mon Sep 17 00:00:00 2001
From: ci 
Date: Thu, 5 Oct 2023 16:15:19 +0000
Subject: [PATCH 0881/1165] update development version to v1.57.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 e4a9c4c5..a16206e2 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.57.1"
+__version__ = "1.57.2.dev0"

From 4dd45a9388385f30b8fae5f630cb9bb70a42c8c6 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 9 Oct 2023 12:32:42 -0700
Subject: [PATCH 0882/1165] infra: bump actions/setup-python from 4.7.0 to
 4.7.1 (#728)

Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.7.0 to 4.7.1.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/61a6322f88396a6271a6ee3565807d608ecaddd1...65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-patch
...
---
 .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 631b9036..0e0b9927 100644
--- a/.github/workflows/check-format.yml
+++ b/.github/workflows/check-format.yml
@@ -18,7 +18,7 @@ jobs:
     steps:
     - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
     - name: Set up Python
-      uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0
+      uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1
       with:
         python-version: '3.x'
     - name: Install dependencies
diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml
index 10752e27..e70ea313 100644
--- a/.github/workflows/dependent-tests.yml
+++ b/.github/workflows/dependent-tests.yml
@@ -23,7 +23,7 @@ jobs:
     steps:
     - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0
+      uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1
       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 fbd9a894..e4cbed2d 100644
--- a/.github/workflows/publish-to-pypi.yml
+++ b/.github/workflows/publish-to-pypi.yml
@@ -14,7 +14,7 @@ jobs:
     steps:
       - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
       - name: Set up Python
-        uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0
+        uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1
         with:
           python-version: '3.x'
       - name: Install wheel
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 1e316a8c..7593eb91 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -26,7 +26,7 @@ jobs:
     steps:
     - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0
+      uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1
       with:
         python-version: ${{ matrix.python-version }}
     - name: Install dependencies
diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml
index f73925f8..8832a8a8 100644
--- a/.github/workflows/twine-check.yml
+++ b/.github/workflows/twine-check.yml
@@ -16,7 +16,7 @@ jobs:
     steps:
       - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
       - name: Set up Python
-        uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0
+        uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1
         with:
           python-version: '3.x'
       - name: Install wheel

From 02316ffd4bab6270e3cc97c5b2cb8cbf39e8dbe1 Mon Sep 17 00:00:00 2001
From: Abe Coull <85974725+math411@users.noreply.github.com>
Date: Tue, 10 Oct 2023 12:00:58 -0700
Subject: [PATCH 0883/1165] infra: drop support for Python 3.8 (#719)

---
 .github/workflows/dependent-tests.yml | 2 +-
 .github/workflows/python-package.yml  | 2 +-
 .readthedocs.yml                      | 2 +-
 README.md                             | 4 ++--
 setup.py                              | 3 +--
 5 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml
index e70ea313..320195c9 100644
--- a/.github/workflows/dependent-tests.yml
+++ b/.github/workflows/dependent-tests.yml
@@ -16,7 +16,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
-        python-version: ["3.8", "3.9", "3.10", "3.11"]
+        python-version: ["3.9", "3.10", "3.11"]
         dependent:
           - amazon-braket-pennylane-plugin-python
 
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 7593eb91..c27a0542 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -21,7 +21,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
-        python-version: ["3.8", "3.9", "3.10", "3.11"]
+        python-version: ["3.9", "3.10", "3.11"]
 
     steps:
     - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 59d0f163..e824a6af 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -17,7 +17,7 @@ formats:
 build:
   os: ubuntu-22.04
   tools:
-    python: "3.8"
+    python: "3.9"
 
 # Optionally set the version of Python and requirements required to build your docs
 python:
diff --git a/README.md b/README.md
index d8533713..f27fcc6b 100644
--- a/README.md
+++ b/README.md
@@ -11,8 +11,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.8 or greater
-Download and install Python 3.8 or greater from [Python.org](https://www.python.org/downloads/).
+### Python 3.9 or greater
+Download and install Python 3.9 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/setup.py b/setup.py
index 4dff452c..428cbb58 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.8.2",
+    python_requires=">= 3.9",
     packages=find_namespace_packages(where="src", exclude=("test",)),
     package_dir={"": "src"},
     install_requires=[
@@ -76,7 +76,6 @@
         "Natural Language :: English",
         "License :: OSI Approved :: Apache Software License",
         "Programming Language :: Python",
-        "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
         "Programming Language :: Python :: 3.10",
         "Programming Language :: Python :: 3.11",

From cbbd40eda1dd70a35929abddb421ca5336151240 Mon Sep 17 00:00:00 2001
From: Cody Wang 
Date: Tue, 10 Oct 2023 18:46:51 -0700
Subject: [PATCH 0884/1165] Use builtins for type hints (#734)

---
 examples/job.py                               |   2 -
 .../ahs/analog_hamiltonian_simulation.py      |   7 +-
 src/braket/ahs/atom_arrangement.py            |  13 +-
 src/braket/ahs/driving_field.py               |  14 +--
 src/braket/ahs/hamiltonian.py                 |   8 +-
 src/braket/ahs/pattern.py                     |   9 +-
 src/braket/ahs/shifting_field.py              |  12 +-
 src/braket/aws/aws_device.py                  |  74 ++++++------
 src/braket/aws/aws_quantum_task.py            |  72 ++++++------
 src/braket/aws/aws_quantum_task_batch.py      |  48 ++++----
 src/braket/aws/aws_session.py                 |  58 ++++-----
 src/braket/aws/queue_information.py           |   6 +-
 src/braket/circuits/angled_gate.py            |  27 +++--
 src/braket/circuits/ascii_circuit_diagram.py  |  26 ++--
 src/braket/circuits/basis_state.py            |   4 +-
 src/braket/circuits/braket_program_context.py |  26 ++--
 src/braket/circuits/circuit.py                | 111 +++++++++---------
 src/braket/circuits/circuit_diagram.py        |   1 +
 src/braket/circuits/compiler_directive.py     |   7 +-
 src/braket/circuits/gate.py                   |  15 +--
 src/braket/circuits/gate_calibrations.py      |  26 ++--
 src/braket/circuits/gates.py                  |  55 ++++-----
 src/braket/circuits/instruction.py            |  18 +--
 src/braket/circuits/moments.py                |  22 +---
 src/braket/circuits/noise.py                  |  37 +++---
 src/braket/circuits/noise_helpers.py          |  31 ++---
 .../circuit_instruction_criteria.py           |   6 +-
 src/braket/circuits/noise_model/criteria.py   |  11 +-
 .../noise_model/criteria_input_parsing.py     |  11 +-
 .../circuits/noise_model/gate_criteria.py     |   7 +-
 .../circuits/noise_model/noise_model.py       |  42 +++----
 .../noise_model/observable_criteria.py        |   7 +-
 .../qubit_initialization_criteria.py          |   7 +-
 .../noise_model/unitary_gate_criteria.py      |   7 +-
 src/braket/circuits/noises.py                 |  17 +--
 src/braket/circuits/observable.py             |  15 +--
 src/braket/circuits/observables.py            |  70 +++++------
 src/braket/circuits/quantum_operator.py       |   7 +-
 .../circuits/quantum_operator_helpers.py      |   2 +-
 src/braket/circuits/result_type.py            |  32 ++---
 src/braket/circuits/result_types.py           |  30 ++---
 src/braket/circuits/unitary_calculation.py    |   2 +-
 src/braket/devices/device.py                  |  14 +--
 src/braket/devices/local_simulator.py         |  24 ++--
 src/braket/error_mitigation/debias.py         |   4 +-
 .../error_mitigation/error_mitigation.py      |   6 +-
 src/braket/jobs/data_persistence.py           |   2 +-
 src/braket/parametric/free_parameter.py       |   6 +-
 .../parametric/free_parameter_expression.py   |   6 +-
 src/braket/parametric/parameterizable.py      |   6 +-
 src/braket/pulse/ast/approximation_parser.py  |  22 ++--
 src/braket/pulse/ast/free_parameters.py       |   5 +-
 src/braket/pulse/ast/qasm_parser.py           |   1 +
 src/braket/pulse/ast/qasm_transformer.py      |   1 +
 src/braket/pulse/frame.py                     |   6 +-
 src/braket/pulse/port.py                      |   6 +-
 src/braket/pulse/pulse_sequence.py            |  28 ++---
 src/braket/pulse/pulse_sequence_trace.py      |   7 +-
 src/braket/pulse/waveforms.py                 |  26 ++--
 .../quantum_information/pauli_string.py       |  14 +--
 src/braket/registers/qubit_set.py             |   7 +-
 ...iltonian_simulation_quantum_task_result.py |   9 +-
 .../tasks/gate_model_quantum_task_result.py   |  57 ++++-----
 src/braket/tasks/local_quantum_task_batch.py  |   6 +-
 src/braket/tasks/quantum_task.py              |   6 +-
 src/braket/tasks/quantum_task_batch.py        |   6 +-
 src/braket/timings/time_series.py             |  29 ++---
 src/braket/tracking/pricing.py                |   5 +-
 src/braket/tracking/tracker.py                |  10 +-
 69 files changed, 662 insertions(+), 659 deletions(-)

diff --git a/examples/job.py b/examples/job.py
index f5e330d1..87b06bf4 100644
--- a/examples/job.py
+++ b/examples/job.py
@@ -11,8 +11,6 @@
 # ANY KIND, either express or implied. See the License for the specific
 # language governing permissions and limitations under the License.
 
-import os
-
 from braket.aws import AwsDevice, AwsQuantumJob
 from braket.circuits import Circuit
 from braket.devices import Devices
diff --git a/src/braket/ahs/analog_hamiltonian_simulation.py b/src/braket/ahs/analog_hamiltonian_simulation.py
index 80c4f2f5..b02091d4 100644
--- a/src/braket/ahs/analog_hamiltonian_simulation.py
+++ b/src/braket/ahs/analog_hamiltonian_simulation.py
@@ -15,7 +15,6 @@
 
 from collections import defaultdict
 from functools import singledispatch
-from typing import Tuple
 
 import braket.ir.ahs as ir
 from braket.ahs.atom_arrangement import AtomArrangement, SiteType
@@ -113,12 +112,12 @@ def discretize(self, device) -> AnalogHamiltonianSimulation:  # noqa
 @singledispatch
 def _get_term_ir(
     term: Hamiltonian,
-) -> Tuple[str, dict]:
+) -> tuple[str, dict]:
     raise TypeError(f"Unable to convert Hamiltonian term type {type(term)}.")
 
 
 @_get_term_ir.register
-def _(term: ShiftingField) -> Tuple[str, ir.ShiftingField]:
+def _(term: ShiftingField) -> tuple[str, ir.ShiftingField]:
     return AnalogHamiltonianSimulation.SHIFTING_FIELDS_PROPERTY, ir.ShiftingField(
         magnitude=ir.PhysicalField(
             time_series=ir.TimeSeries(
@@ -131,7 +130,7 @@ def _(term: ShiftingField) -> Tuple[str, ir.ShiftingField]:
 
 
 @_get_term_ir.register
-def _(term: DrivingField) -> Tuple[str, ir.DrivingField]:
+def _(term: DrivingField) -> tuple[str, ir.DrivingField]:
     return AnalogHamiltonianSimulation.DRIVING_FIELDS_PROPERTY, ir.DrivingField(
         amplitude=ir.PhysicalField(
             time_series=ir.TimeSeries(
diff --git a/src/braket/ahs/atom_arrangement.py b/src/braket/ahs/atom_arrangement.py
index 70fc64b0..bb708834 100644
--- a/src/braket/ahs/atom_arrangement.py
+++ b/src/braket/ahs/atom_arrangement.py
@@ -13,11 +13,12 @@
 
 from __future__ import annotations
 
+from collections.abc import Iterator
 from dataclasses import dataclass
 from decimal import Decimal
 from enum import Enum
 from numbers import Number
-from typing import Iterator, List, Tuple, Union
+from typing import Union
 
 import numpy as np
 
@@ -33,7 +34,7 @@ class SiteType(Enum):
 class AtomArrangementItem:
     """Represents an item (coordinate and metadata) in an atom arrangement."""
 
-    coordinate: Tuple[Number, Number]
+    coordinate: tuple[Number, Number]
     site_type: SiteType
 
     def _validate_coordinate(self) -> None:
@@ -62,13 +63,13 @@ def __init__(self):
 
     def add(
         self,
-        coordinate: Union[Tuple[Number, Number], np.ndarray],
+        coordinate: Union[tuple[Number, Number], np.ndarray],
         site_type: SiteType = SiteType.FILLED,
     ) -> AtomArrangement:
         """Add a coordinate to the atom arrangement.
 
         Args:
-            coordinate (Union[Tuple[Number, Number], ndarray]): The coordinate of the
+            coordinate (Union[tuple[Number, Number], ndarray]): The coordinate of the
                 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.
@@ -78,14 +79,14 @@ def add(
         self._sites.append(AtomArrangementItem(tuple(coordinate), site_type))
         return self
 
-    def coordinate_list(self, coordinate_index: Number) -> List[Number]:
+    def coordinate_list(self, coordinate_index: Number) -> list[Number]:
         """Returns all the coordinates at the given index.
 
         Args:
             coordinate_index (Number): The index to get for each coordinate.
 
         Returns:
-            List[Number]:The list of coordinates at the given index.
+            list[Number]:The list of coordinates at the given index.
 
         Example:
             To get a list of all x-coordinates: coordinate_list(0)
diff --git a/src/braket/ahs/driving_field.py b/src/braket/ahs/driving_field.py
index 45914063..02c8bd27 100644
--- a/src/braket/ahs/driving_field.py
+++ b/src/braket/ahs/driving_field.py
@@ -13,7 +13,7 @@
 
 from __future__ import annotations
 
-from typing import List, Union
+from typing import Union
 
 from braket.ahs.discretization_types import DiscretizationProperties
 from braket.ahs.field import Field
@@ -65,7 +65,7 @@ def __init__(
         self._detuning = detuning if isinstance(detuning, Field) else Field(detuning)
 
     @property
-    def terms(self) -> List[Hamiltonian]:
+    def terms(self) -> list[Hamiltonian]:
         return [self]
 
     @property
@@ -141,7 +141,7 @@ def discretize(self, properties: DiscretizationProperties) -> DrivingField:
 
     @staticmethod
     def from_lists(
-        times: List[float], amplitudes: List[float], detunings: List[float], phases: List[float]
+        times: list[float], amplitudes: list[float], detunings: list[float], phases: list[float]
     ) -> DrivingField:
         """
         Builds DrivingField Hamiltonian from lists defining time evolution
@@ -149,10 +149,10 @@ def from_lists(
         The values of the parameters at each time points are global for all atoms.
 
         Args:
-            times (List[float]): The time points of the driving field
-            amplitudes (List[float]): The values of the amplitude
-            detunings (List[float]): The values of the detuning
-            phases (List[float]): The values of the phase
+            times (list[float]): The time points of the driving field
+            amplitudes (list[float]): The values of the amplitude
+            detunings (list[float]): The values of the detuning
+            phases (list[float]): The values of the phase
 
         Returns:
             DrivingField: DrivingField Hamiltonian.
diff --git a/src/braket/ahs/hamiltonian.py b/src/braket/ahs/hamiltonian.py
index 83b80e69..548befc5 100644
--- a/src/braket/ahs/hamiltonian.py
+++ b/src/braket/ahs/hamiltonian.py
@@ -13,13 +13,13 @@
 
 from __future__ import annotations
 
-from typing import List, Optional
+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: Optional[list[Hamiltonian]] = None):
         r"""A Hamiltonian representing a system to be simulated.
 
         A Hamiltonian :math:`H` may be expressed as a sum of multiple terms
@@ -30,8 +30,8 @@ def __init__(self, terms: Optional[List[Hamiltonian]] = None):
         self._terms = terms or []
 
     @property
-    def terms(self) -> List[Hamiltonian]:
-        """List[Hamiltonian]: The list of terms in this Hamiltonian."""
+    def terms(self) -> list[Hamiltonian]:
+        """list[Hamiltonian]: The list of terms in this Hamiltonian."""
         return self._terms
 
     def discretize(self, properties: DiscretizationProperties) -> Hamiltonian:
diff --git a/src/braket/ahs/pattern.py b/src/braket/ahs/pattern.py
index ebd1eafc..17e40a36 100644
--- a/src/braket/ahs/pattern.py
+++ b/src/braket/ahs/pattern.py
@@ -15,22 +15,21 @@
 
 from decimal import Decimal
 from numbers import Number
-from typing import List
 
 
 class Pattern:
-    def __init__(self, series: List[Number]):
+    def __init__(self, series: list[Number]):
         """Represents the spatial dependence of a Field.
 
         Args:
-            series (List[Number]): A series of numbers representing the the local
+            series (list[Number]): A series of numbers representing the the local
                 pattern of real numbers.
         """
         self._series = series
 
     @property
-    def series(self) -> List[Number]:
-        """List[Number]: A series of numbers representing the local
+    def series(self) -> list[Number]:
+        """list[Number]: A series of numbers representing the local
         pattern of real numbers."""
         return self._series
 
diff --git a/src/braket/ahs/shifting_field.py b/src/braket/ahs/shifting_field.py
index bd3f59da..846d7ad2 100644
--- a/src/braket/ahs/shifting_field.py
+++ b/src/braket/ahs/shifting_field.py
@@ -13,8 +13,6 @@
 
 from __future__ import annotations
 
-from typing import List
-
 from braket.ahs.discretization_types import DiscretizationProperties
 from braket.ahs.field import Field
 from braket.ahs.hamiltonian import Hamiltonian
@@ -51,7 +49,7 @@ def __init__(self, magnitude: Field) -> None:
         self._magnitude = magnitude
 
     @property
-    def terms(self) -> List[Hamiltonian]:
+    def terms(self) -> list[Hamiltonian]:
         return [self]
 
     @property
@@ -62,13 +60,13 @@ def magnitude(self) -> Field:
         return self._magnitude
 
     @staticmethod
-    def from_lists(times: List[float], values: List[float], pattern: List[float]) -> ShiftingField:
+    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
+            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
 
         Returns:
             ShiftingField: The shifting field obtained
diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py
index d495dbe3..c9a20841 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 Dict, List, Optional, Tuple, Union
+from typing import Optional, Union
 
 from botocore.errorfactory import ClientError
 from networkx import DiGraph, complete_graph, from_edgelist
@@ -119,8 +119,8 @@ def run(
         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,
+        inputs: Optional[dict[str, float]] = None,
+        gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None,
         *aws_quantum_task_args,
         **aws_quantum_task_kwargs,
     ) -> AwsQuantumTask:
@@ -142,11 +142,11 @@ def run(
             poll_interval_seconds (Optional[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[Dict[str, float]]): Inputs to be passed along with the
+            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: {}.
-            gate_definitions (Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]): A
-                `Dict[Tuple[Gate, QubitSet], PulseSequence]]` for a user defined gate calibration.
+            gate_definitions (Optional[dict[tuple[Gate, QubitSet], PulseSequence]]): A
+                `dict[tuple[Gate, QubitSet], PulseSequence]]` for a user defined gate calibration.
                 The calibration is defined for a particular `Gate` on a particular `QubitSet`
                 and is represented by a `PulseSequence`.
                 Default: None.
@@ -214,7 +214,7 @@ def run_batch(
                 PulseSequence,
                 AnalogHamiltonianSimulation,
             ],
-            List[
+            list[
                 Union[
                     Circuit,
                     Problem,
@@ -231,15 +231,15 @@ 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,
-        gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None,
+        inputs: Optional[Union[dict[str, float], list[dict[str, float]]]] = None,
+        gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None,
         *aws_quantum_task_args,
         **aws_quantum_task_kwargs,
     ) -> AwsQuantumTaskBatch:
         """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
+            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.
             s3_destination_folder (Optional[S3DestinationFolder]): The S3 location to
@@ -257,11 +257,11 @@ 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[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: {}.
-            gate_definitions (Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]): A
-                `Dict[Tuple[Gate, QubitSet], PulseSequence]]` for a user defined gate calibration.
+            gate_definitions (Optional[dict[tuple[Gate, QubitSet], PulseSequence]]): A
+                `dict[tuple[Gate, QubitSet], PulseSequence]]` for a user defined gate calibration.
                 The calibration is defined for a particular `Gate` on a particular `QubitSet`
                 and is represented by a `PulseSequence`.
                 Default: None.
@@ -531,29 +531,29 @@ def __eq__(self, other):
         return NotImplemented
 
     @property
-    def frames(self) -> Dict[str, Frame]:
-        """Returns a Dict mapping frame ids to the frame objects for predefined frames
+    def frames(self) -> dict[str, Frame]:
+        """Returns a dict mapping frame ids to the frame objects for predefined frames
         for this device."""
         self._update_pulse_properties()
         return self._frames or dict()
 
     @property
-    def ports(self) -> Dict[str, Port]:
-        """Returns a Dict mapping port ids to the port objects for predefined ports
+    def ports(self) -> dict[str, Port]:
+        """Returns a dict mapping port ids to the port objects for predefined ports
         for this device."""
         self._update_pulse_properties()
         return self._ports or dict()
 
     @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: 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,
         order_by: str = "name",
         aws_session: Optional[AwsSession] = None,
-    ) -> List[AwsDevice]:
+    ) -> 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`.
@@ -564,20 +564,20 @@ def get_devices(
             >>> AwsDevice.get_devices(types=['SIMULATOR'])
 
         Args:
-            arns (Optional[List[str]]): device ARN list, default is `None`
-            names (Optional[List[str]]): device name list, default is `None`
-            types (Optional[List[AwsDeviceType]]): device type list, default is `None`
+            arns (Optional[list[str]]): device ARN list, default is `None`
+            names (Optional[list[str]]): device name list, default is `None`
+            types (Optional[list[AwsDeviceType]]): device type list, default is `None`
                 QPUs will be searched for all regions and simulators will only be
                 searched for the region of the current session.
-            statuses (Optional[List[str]]): device status list, default is `None`
-            provider_names (Optional[List[str]]): provider name list, default is `None`
+            statuses (Optional[list[str]]): device status list, default is `None`
+            provider_names (Optional[list[str]]): provider name list, default is `None`
             order_by (str): field to order result by, default is `name`.
                 Accepted values are ['arn', 'name', 'type', 'provider_name', 'status']
             aws_session (Optional[AwsSession]): An AWS session object.
                 Default is `None`.
 
         Returns:
-            List[AwsDevice]: list of AWS devices
+            list[AwsDevice]: list of AWS devices
         """
 
         if order_by not in AwsDevice._GET_DEVICES_ORDER_BY_KEYS:
@@ -759,7 +759,7 @@ def refresh_gate_calibrations(self) -> Optional[GateCalibrations]:
         else:
             return None
 
-    def _parse_waveforms(self, waveforms_json: Dict) -> Dict:
+    def _parse_waveforms(self, waveforms_json: dict) -> dict:
         waveforms = dict()
         for waveform in waveforms_json:
             parsed_waveform = _parse_waveform_from_calibration_schema(waveforms_json[waveform])
@@ -767,24 +767,24 @@ def _parse_waveforms(self, waveforms_json: Dict) -> Dict:
         return waveforms
 
     def _parse_pulse_sequence(
-        self, calibration: Dict, waveforms: Dict[ArbitraryWaveform]
+        self, calibration: dict, waveforms: dict[ArbitraryWaveform]
     ) -> PulseSequence:
         return PulseSequence._parse_from_calibration_schema(calibration, waveforms, self.frames)
 
     def _parse_calibration_json(
-        self, calibration_data: Dict
-    ) -> Dict[Tuple[Gate, QubitSet], PulseSequence]:
+        self, calibration_data: dict
+    ) -> dict[tuple[Gate, QubitSet], PulseSequence]:
         """
         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.
+        corresponding `dict[tuple[Gate, QubitSet], PulseSequence]` to represent the calibration data.
 
         Args:
-            calibration_data (Dict): The data to be parsed. Based on
+            calibration_data (dict): The data to be parsed. Based on
                 https://github.com/aws/amazon-braket-schemas-python/blob/main/src/braket/device_schema/pulse/native_gate_calibrations_v1.py.
 
         Returns:
-            Dict[Tuple[Gate, QubitSet], PulseSequence]: The
-            structured data based on a mapping of `Tuple[Gate, Qubit]` to its calibration repesented as a
+            dict[tuple[Gate, QubitSet], PulseSequence]: The
+            structured data based on a mapping of `tuple[Gate, Qubit]` to its calibration repesented as a
             `PulseSequence`.
 
         """  # noqa: E501
diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py
index 57fecb26..346816e7 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, Dict, Optional, Tuple, Union
+from typing import Any, Optional, Union
 
 import boto3
 
@@ -100,11 +100,11 @@ def create(
         ],
         s3_destination_folder: AwsSession.S3DestinationFolder,
         shots: int,
-        device_parameters: Dict[str, Any] = None,
+        device_parameters: dict[str, Any] = None,
         disable_qubit_rewiring: bool = False,
-        tags: Dict[str, str] = None,
-        inputs: Dict[str, float] = None,
-        gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None,
+        tags: dict[str, str] = None,
+        inputs: dict[str, float] = None,
+        gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None,
         *args,
         **kwargs,
     ) -> AwsQuantumTask:
@@ -129,7 +129,7 @@ def create(
                 `shots=0` is only available on simulators and means that the simulator
                 will compute the exact results based on the quantum task specification.
 
-            device_parameters (Dict[str, Any]): Additional parameters to send to the device.
+            device_parameters (dict[str, Any]): Additional parameters to send to the device.
 
             disable_qubit_rewiring (bool): Whether to run the circuit with the exact qubits chosen,
                 without any rewiring downstream, if this is supported by the device.
@@ -137,15 +137,15 @@ def create(
                 If ``True``, no qubit rewiring is allowed; if ``False``, qubit rewiring is allowed.
                 Default: False
 
-            tags (Dict[str, str]): Tags, which are Key-Value pairs to add to this quantum task.
+            tags (dict[str, str]): Tags, which are Key-Value pairs to add to this quantum task.
                 An example would be:
                 `{"state": "washington"}`
 
-            inputs (Dict[str, float]): Inputs to be passed along with the
+            inputs (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]]):
+            gate_definitions (Optional[dict[tuple[Gate, QubitSet], PulseSequence]]):
                 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`.
@@ -241,7 +241,7 @@ def __init__(
 
         self._logger = logger
 
-        self._metadata: Dict[str, Any] = {}
+        self._metadata: dict[str, Any] = {}
         self._result: Union[
             GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult
         ] = None
@@ -278,7 +278,7 @@ def cancel(self) -> None:
         self._cancel_future()
         self._aws_session.cancel_quantum_task(self._arn)
 
-    def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]:
+    def metadata(self, use_cached_value: bool = False) -> dict[str, Any]:
         """
         Get quantum task metadata defined in Amazon Braket.
 
@@ -288,7 +288,7 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]:
                 `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.
+            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.
@@ -514,12 +514,12 @@ def __hash__(self) -> int:
 def _create_internal(
     task_specification: Union[Circuit, Problem, BlackbirdProgram],
     aws_session: AwsSession,
-    create_task_kwargs: Dict[str, Any],
+    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]],
+    inputs: dict[str, float],
+    gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]],
     *args,
     **kwargs,
 ) -> AwsQuantumTask:
@@ -530,12 +530,12 @@ def _create_internal(
 def _(
     pulse_sequence: PulseSequence,
     aws_session: AwsSession,
-    create_task_kwargs: Dict[str, Any],
+    create_task_kwargs: dict[str, Any],
     device_arn: str,
     _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]],
+    inputs: dict[str, float],
+    gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]],
     *args,
     **kwargs,
 ) -> AwsQuantumTask:
@@ -548,12 +548,12 @@ def _(
 def _(
     openqasm_program: OpenQASMProgram,
     aws_session: AwsSession,
-    create_task_kwargs: Dict[str, Any],
+    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]],
+    inputs: dict[str, float],
+    gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]],
     *args,
     **kwargs,
 ) -> AwsQuantumTask:
@@ -587,12 +587,12 @@ def _(
 def _(
     blackbird_program: BlackbirdProgram,
     aws_session: AwsSession,
-    create_task_kwargs: Dict[str, any],
+    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]],
+    inputs: dict[str, float],
+    gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]],
     *args,
     **kwargs,
 ) -> AwsQuantumTask:
@@ -605,12 +605,12 @@ def _(
 def _(
     circuit: Circuit,
     aws_session: AwsSession,
-    create_task_kwargs: Dict[str, Any],
+    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]],
+    inputs: dict[str, float],
+    gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]],
     *args,
     **kwargs,
 ) -> AwsQuantumTask:
@@ -668,7 +668,7 @@ def _(
 def _(
     problem: Problem,
     aws_session: AwsSession,
-    create_task_kwargs: Dict[str, Any],
+    create_task_kwargs: dict[str, Any],
     device_arn: str,
     device_parameters: Union[
         dict,
@@ -677,8 +677,8 @@ def _(
         Dwave2000QDeviceParameters,
     ],
     _,
-    inputs: Dict[str, float],
-    gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]],
+    inputs: dict[str, float],
+    gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]],
     *args,
     **kwargs,
 ) -> AwsQuantumTask:
@@ -698,12 +698,12 @@ def _(
 def _(
     analog_hamiltonian_simulation: AnalogHamiltonianSimulation,
     aws_session: AwsSession,
-    create_task_kwargs: Dict[str, Any],
+    create_task_kwargs: dict[str, Any],
     device_arn: str,
     device_parameters: dict,
     _,
-    inputs: Dict[str, float],
-    gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]],
+    inputs: dict[str, float],
+    gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]],
     *args,
     **kwargs,
 ) -> AwsQuantumTask:
@@ -732,12 +732,12 @@ def _circuit_device_params_from_dict(
 
 
 def _create_annealing_device_params(
-    device_params: Dict[str, Any], device_arn: str
+    device_params: dict[str, Any], device_arn: str
 ) -> Union[DwaveAdvantageDeviceParameters, Dwave2000QDeviceParameters]:
     """Gets Annealing Device Parameters.
 
     Args:
-        device_params (Dict[str, Any]): Additional parameters for the device.
+        device_params (dict[str, Any]): Additional parameters for the device.
         device_arn (str): The ARN of the quantum device.
 
     Returns:
@@ -774,7 +774,7 @@ def _create_annealing_device_params(
 
 def _create_common_params(
     device_arn: str, s3_destination_folder: AwsSession.S3DestinationFolder, shots: int
-) -> Dict[str, Any]:
+) -> dict[str, Any]:
     return {
         "deviceArn": device_arn,
         "outputS3Bucket": s3_destination_folder[0],
diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py
index caa48b39..d29533a9 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 Dict, List, Set, Tuple, Union
+from typing import Union
 
 from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation
 from braket.annealing import Problem
@@ -48,7 +48,7 @@ def __init__(
         device_arn: str,
         task_specifications: Union[
             Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation],
-            List[
+            list[
                 Union[
                     Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation
                 ]
@@ -60,7 +60,7 @@ 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,
+        inputs: Union[dict[str, float], list[dict[str, float]]] = None,
         *aws_quantum_task_args,
         **aws_quantum_task_kwargs,
     ):
@@ -69,7 +69,7 @@ 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
+            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.
@@ -88,7 +88,7 @@ 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]]]): Inputs to be passed
+            inputs (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: {}.
         """
@@ -127,17 +127,17 @@ def __init__(
     def _tasks_and_inputs(
         task_specifications: Union[
             Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation],
-            List[
+            list[
                 Union[
                     Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation
                 ]
             ],
         ],
-        inputs: Union[Dict[str, float], List[Dict[str, float]]] = None,
-    ) -> List[
-        Tuple[
+        inputs: Union[dict[str, float], list[dict[str, float]]] = None,
+    ) -> list[
+        tuple[
             Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation],
-            Dict[str, float],
+            dict[str, float],
         ]
     ]:
         inputs = inputs or {}
@@ -184,7 +184,7 @@ def _execute(
         device_arn: str,
         task_specifications: Union[
             Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation],
-            List[
+            list[
                 Union[
                     Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation
                 ]
@@ -196,10 +196,10 @@ 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: Union[dict[str, float], list[dict[str, float]]] = None,
         *args,
         **kwargs,
-    ) -> List[AwsQuantumTask]:
+    ) -> list[AwsQuantumTask]:
         tasks_and_inputs = AwsQuantumTaskBatch._tasks_and_inputs(task_specifications, inputs)
         max_threads = min(max_parallel, max_workers)
         remaining = [0 for _ in tasks_and_inputs]
@@ -238,7 +238,7 @@ def _execute(
 
     @staticmethod
     def _create_task(
-        remaining: List[int],
+        remaining: list[int],
         aws_session: AwsSession,
         device_arn: str,
         task_specification: Union[
@@ -247,7 +247,7 @@ def _create_task(
         s3_destination_folder: AwsSession.S3DestinationFolder,
         shots: int,
         poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL,
-        inputs: Dict[str, float] = None,
+        inputs: dict[str, float] = None,
         *args,
         **kwargs,
     ) -> AwsQuantumTask:
@@ -278,7 +278,7 @@ def results(
         fail_unsuccessful: bool = False,
         max_retries: int = MAX_RETRIES,
         use_cached_value: bool = True,
-    ) -> List[AwsQuantumTask]:
+    ) -> list[AwsQuantumTask]:
         """Retrieves the result of every quantum task in the batch.
 
         Polling for results happens in parallel; this method returns when all quantum tasks
@@ -295,7 +295,7 @@ 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[AwsQuantumTask]: The 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:
@@ -316,7 +316,7 @@ 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[AwsQuantumTask]:
         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]
@@ -362,8 +362,8 @@ def retry_unsuccessful_tasks(self) -> bool:
         return not self._unsuccessful
 
     @property
-    def tasks(self) -> List[AwsQuantumTask]:
-        """List[AwsQuantumTask]: The quantum tasks in this batch, as a list of AwsQuantumTask
+    def tasks(self) -> list[AwsQuantumTask]:
+        """list[AwsQuantumTask]: The quantum tasks in this batch, as a list of AwsQuantumTask
         objects"""
         return list(self._tasks)
 
@@ -373,10 +373,10 @@ def size(self) -> int:
         return len(self._tasks)
 
     @property
-    def unfinished(self) -> Set[str]:
+    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.
+            set[str]: The IDs of all the quantum tasks in the batch that have yet to complete.
         """
         with ThreadPoolExecutor(max_workers=self._max_workers) as executor:
             status_futures = {task.id: executor.submit(task.state) for task in self._tasks}
@@ -390,7 +390,7 @@ def unfinished(self) -> Set[str]:
         return unfinished
 
     @property
-    def unsuccessful(self) -> Set[str]:
-        """Set[str]: The IDs of all the FAILED, CANCELLED, or timed out quantum tasks in the
+    def unsuccessful(self) -> set[str]:
+        """set[str]: The IDs of all the FAILED, CANCELLED, or timed out quantum tasks in the
         batch."""
         return set(self._unsuccessful)
diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py
index 5ce04a82..225140d8 100644
--- a/src/braket/aws/aws_session.py
+++ b/src/braket/aws/aws_session.py
@@ -18,7 +18,7 @@
 import os.path
 import re
 from pathlib import Path
-from typing import Any, Dict, List, NamedTuple, Optional, Tuple
+from typing import Any, NamedTuple, Optional
 
 import backoff
 import boto3
@@ -269,7 +269,7 @@ def _should_giveup(err: Exception) -> bool:
         jitter=backoff.full_jitter,
         giveup=_should_giveup.__func__,
     )
-    def get_quantum_task(self, arn: str) -> Dict[str, Any]:
+    def get_quantum_task(self, arn: str) -> dict[str, Any]:
         """
         Gets the quantum task.
 
@@ -277,7 +277,7 @@ def get_quantum_task(self, arn: str) -> Dict[str, Any]:
             arn (str): The ARN of the quantum task to get.
 
         Returns:
-            Dict[str, Any]: The response from the Amazon Braket `GetQuantumTask` operation.
+            dict[str, Any]: The response from the Amazon Braket `GetQuantumTask` operation.
         """
         response = self.braket_client.get_quantum_task(
             quantumTaskArn=arn, additionalAttributeNames=["QueueInfo"]
@@ -316,7 +316,7 @@ def get_default_jobs_role(self) -> str:
         jitter=backoff.full_jitter,
         giveup=_should_giveup.__func__,
     )
-    def get_job(self, arn: str) -> Dict[str, Any]:
+    def get_job(self, arn: str) -> dict[str, Any]:
         """
         Gets the hybrid job.
 
@@ -324,11 +324,11 @@ def get_job(self, arn: str) -> Dict[str, Any]:
             arn (str): The ARN of the hybrid job to get.
 
         Returns:
-            Dict[str, Any]: The response from the Amazon Braket `GetQuantumJob` operation.
+            dict[str, Any]: The response from the Amazon Braket `GetQuantumJob` operation.
         """
         return self.braket_client.get_job(jobArn=arn, additionalAttributeNames=["QueueInfo"])
 
-    def cancel_job(self, arn: str) -> Dict[str, Any]:
+    def cancel_job(self, arn: str) -> dict[str, Any]:
         """
         Cancel the hybrid job.
 
@@ -336,7 +336,7 @@ def cancel_job(self, arn: str) -> Dict[str, Any]:
             arn (str): The ARN of the hybrid job to cancel.
 
         Returns:
-            Dict[str, Any]: The response from the Amazon Braket `CancelJob` operation.
+            dict[str, Any]: The response from the Amazon Braket `CancelJob` operation.
         """
         return self.braket_client.cancel_job(jobArn=arn)
 
@@ -473,7 +473,7 @@ def copy_s3_directory(self, source_s3_path: str, destination_s3_path: str) -> No
                 key.replace(source_prefix, destination_prefix, 1),
             )
 
-    def list_keys(self, bucket: str, prefix: str) -> List[str]:
+    def list_keys(self, bucket: str, prefix: str) -> list[str]:
         """
         Lists keys matching prefix in bucket.
 
@@ -482,7 +482,7 @@ def list_keys(self, bucket: str, prefix: str) -> List[str]:
             prefix (str): The S3 path prefix to be matched
 
         Returns:
-            List[str]: A list of all keys matching the prefix in
+            list[str]: A list of all keys matching the prefix in
             the bucket.
         """
         list_objects = self.s3_client.list_objects_v2(
@@ -596,7 +596,7 @@ def _create_s3_bucket_if_it_does_not_exist(self, bucket_name: str, region: str)
             else:
                 raise
 
-    def get_device(self, arn: str) -> Dict[str, Any]:
+    def get_device(self, arn: str) -> dict[str, Any]:
         """
         Calls the Amazon Braket `get_device` API to retrieve device metadata.
 
@@ -604,31 +604,31 @@ def get_device(self, arn: str) -> Dict[str, Any]:
             arn (str): The ARN of the device.
 
         Returns:
-            Dict[str, Any]: The response from the Amazon Braket `GetDevice` operation.
+            dict[str, Any]: The response from the Amazon Braket `GetDevice` operation.
         """
         return self.braket_client.get_device(deviceArn=arn)
 
     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,
-    ) -> List[Dict[str, Any]]:
+        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,
+    ) -> list[dict[str, Any]]:
         """
         Get devices based on filters. The result is the AND of
         all the filters `arns`, `names`, `types`, `statuses`, `provider_names`.
 
         Args:
-            arns (Optional[List[str]]): device ARN list, default is `None`.
-            names (Optional[List[str]]): device name list, default is `None`.
-            types (Optional[List[str]]): device type list, default is `None`.
-            statuses (Optional[List[str]]): device status list, default is `None`.
-            provider_names (Optional[List[str]]): provider name list, default is `None`.
+            arns (Optional[list[str]]): device ARN list, default is `None`.
+            names (Optional[list[str]]): device name list, default is `None`.
+            types (Optional[list[str]]): device type list, default is `None`.
+            statuses (Optional[list[str]]): device status list, default is `None`.
+            provider_names (Optional[list[str]]): provider name list, default is `None`.
 
         Returns:
-            List[Dict[str, Any]]: The response from the Amazon Braket `SearchDevices` operation.
+            list[dict[str, Any]]: The response from the Amazon Braket `SearchDevices` operation.
         """
         filters = []
         if arns:
@@ -665,7 +665,7 @@ def is_s3_uri(string: str) -> bool:
         return True
 
     @staticmethod
-    def parse_s3_uri(s3_uri: str) -> Tuple[str, str]:
+    def parse_s3_uri(s3_uri: str) -> tuple[str, str]:
         """
         Parse S3 URI to get bucket and key
 
@@ -673,7 +673,7 @@ def parse_s3_uri(s3_uri: str) -> Tuple[str, str]:
             s3_uri (str): S3 URI.
 
         Returns:
-            Tuple[str, str]: Bucket and Key tuple.
+            tuple[str, str]: Bucket and Key tuple.
 
         Raises:
             ValueError: Raises a ValueError if the provided string is not
@@ -717,7 +717,7 @@ def describe_log_streams(
         log_stream_prefix: str,
         limit: int = None,
         next_token: Optional[str] = None,
-    ) -> Dict[str, Any]:
+    ) -> dict[str, Any]:
         """
         Describes CloudWatch log streams in a log group with a given prefix.
 
@@ -730,7 +730,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]: Dicionary containing logStreams and nextToken
         """
         log_stream_args = {
             "logGroupName": log_group,
@@ -753,7 +753,7 @@ def get_log_events(
         start_time: int,
         start_from_head: bool = True,
         next_token: Optional[str] = None,
-    ) -> Dict[str, Any]:
+    ) -> dict[str, Any]:
         """
         Gets CloudWatch log events from a given log stream.
 
@@ -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]: Dicionary containing events, nextForwardToken, and nextBackwardToken
         """
         log_events_args = {
             "logGroupName": log_group,
diff --git a/src/braket/aws/queue_information.py b/src/braket/aws/queue_information.py
index d45ed876..10963275 100644
--- a/src/braket/aws/queue_information.py
+++ b/src/braket/aws/queue_information.py
@@ -13,7 +13,7 @@
 
 from dataclasses import dataclass
 from enum import Enum
-from typing import Dict, Optional
+from typing import Optional
 
 
 class QueueType(str, Enum):
@@ -35,7 +35,7 @@ class QueueDepthInfo:
     Represents quantum tasks and hybrid jobs queue depth information.
 
     Attributes:
-        quantum_tasks (Dict[QueueType, str]): number of quantum tasks waiting
+        quantum_tasks (dict[QueueType, str]): number of quantum tasks waiting
             to run on a device. This includes both 'Normal' and 'Priority' tasks.
             For Example, {'quantum_tasks': {QueueType.NORMAL: '7', QueueType.PRIORITY: '3'}}
         jobs (str): number of hybrid jobs waiting to run on a device. Additionally, for QPUs if
@@ -43,7 +43,7 @@ class QueueDepthInfo:
             running hybrid jobs. Example, 'jobs': '0 (1 prioritized job(s) running)'
     """
 
-    quantum_tasks: Dict[QueueType, str]
+    quantum_tasks: dict[QueueType, str]
     jobs: str
 
 
diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py
index f45bf616..e453177a 100644
--- a/src/braket/circuits/angled_gate.py
+++ b/src/braket/circuits/angled_gate.py
@@ -15,8 +15,9 @@
 
 import copy
 import math
+from collections.abc import Sequence
 from functools import singledispatch
-from typing import List, Optional, Sequence, Union
+from typing import Optional, Union
 
 from sympy import Float
 
@@ -61,13 +62,13 @@ 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[Union[FreeParameterExpression, float]]:
         """
         Returns the parameters associated with the object, either unbound free parameters or
         bound values.
 
         Returns:
-            List[Union[FreeParameterExpression, float]]: The free parameters or fixed value
+            list[Union[FreeParameterExpression, float]]: The free parameters or fixed value
             associated with the object.
         """
         return self._parameters
@@ -93,11 +94,11 @@ def bind_values(self, **kwargs) -> AngledGate:
         """
         raise NotImplementedError
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         """Returns the adjoint of this gate as a singleton list.
 
         Returns:
-            List[Gate]: A list containing the gate with negated angle.
+            list[Gate]: A list containing the gate with negated angle.
         """
         gate_ascii_name_index = self.ascii_symbols[0].find("(")
         gate_ascii_name = self.ascii_symbols[0][:gate_ascii_name_index]
@@ -166,13 +167,13 @@ def __init__(
         ]
 
     @property
-    def parameters(self) -> List[Union[FreeParameterExpression, float]]:
+    def parameters(self) -> list[Union[FreeParameterExpression, float]]:
         """
         Returns the parameters associated with the object, either unbound free parameters or
         bound values.
 
         Returns:
-            List[Union[FreeParameterExpression, float]]: The free parameters or fixed value
+            list[Union[FreeParameterExpression, float]]: The free parameters or fixed value
             associated with the object.
         """
         return self._parameters
@@ -212,11 +213,11 @@ def bind_values(self, **kwargs) -> AngledGate:
         """
         raise NotImplementedError
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         """Returns the adjoint of this gate as a singleton list.
 
         Returns:
-            List[Gate]: A list containing the gate with negated angle.
+            list[Gate]: A list containing the gate with negated angle.
         """
         raise NotImplementedError
 
@@ -285,13 +286,13 @@ def __init__(
         ]
 
     @property
-    def parameters(self) -> List[Union[FreeParameterExpression, float]]:
+    def parameters(self) -> list[Union[FreeParameterExpression, float]]:
         """
         Returns the parameters associated with the object, either unbound free parameters or
         bound values.
 
         Returns:
-            List[Union[FreeParameterExpression, float]]: The free parameters or fixed value
+            list[Union[FreeParameterExpression, float]]: The free parameters or fixed value
             associated with the object.
         """
         return self._parameters
@@ -341,11 +342,11 @@ def bind_values(self, **kwargs) -> AngledGate:
         """
         raise NotImplementedError
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         """Returns the adjoint of this gate as a singleton list.
 
         Returns:
-            List[Gate]: A list containing the gate with negated angle.
+            list[Gate]: A list containing the gate with negated angle.
         """
         raise NotImplementedError
 
diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py
index 85de40a1..daef0179 100644
--- a/src/braket/circuits/ascii_circuit_diagram.py
+++ b/src/braket/circuits/ascii_circuit_diagram.py
@@ -14,7 +14,7 @@
 from __future__ import annotations
 
 from functools import reduce
-from typing import List, Tuple, Union
+from typing import Union
 
 import braket.circuits.circuit as cir
 from braket.circuits.circuit_diagram import CircuitDiagram
@@ -100,17 +100,17 @@ def build_diagram(circuit: cir.Circuit) -> str:
     @staticmethod
     def _ascii_group_items(
         circuit_qubits: QubitSet,
-        items: List[Union[Instruction, ResultType]],
-    ) -> List[Tuple[QubitSet, List[Instruction]]]:
+        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
+            items (list[Union[Instruction, ResultType]]): list of instructions or result types
 
         Returns:
-            List[Tuple[QubitSet, List[Instruction]]]: list of grouped instructions or result types.
+            list[tuple[QubitSet, list[Instruction]]]: list of grouped instructions or result types.
         """
         groupings = []
         for item in items:
@@ -151,16 +151,16 @@ def _ascii_group_items(
 
     @staticmethod
     def _categorize_result_types(
-        result_types: List[ResultType],
-    ) -> Tuple[List[str], List[ResultType]]:
+        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
+            result_types (list[ResultType]): list of result types
 
         Returns:
-            Tuple[List[str], List[ResultType]]: first element is a list of result types
+            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
         """
@@ -175,7 +175,7 @@ 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]]
     ) -> str:
         """
         Return a set of columns in the ASCII string diagram of the circuit for a list of items.
@@ -183,7 +183,7 @@ def _ascii_diagram_column_set(
         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
+            items (list[Union[Instruction, ResultType]]): list of instructions or result types
 
         Returns:
             str: An ASCII string diagram for the column set.
@@ -220,14 +220,14 @@ 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]]
     ) -> 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
+            items (list[Union[Instruction, ResultType]]): list of instructions or result types
 
         Returns:
             str: An ASCII string diagram for the specified moment in time for a column.
diff --git a/src/braket/circuits/basis_state.py b/src/braket/circuits/basis_state.py
index 66e406aa..814444e7 100644
--- a/src/braket/circuits/basis_state.py
+++ b/src/braket/circuits/basis_state.py
@@ -1,5 +1,5 @@
 from functools import singledispatch
-from typing import List, Optional, Union
+from typing import Optional, Union
 
 import numpy as np
 
@@ -34,7 +34,7 @@ def __eq__(self, other):
         return self.state == other.state
 
 
-BasisStateInput = Union[int, List[int], str, BasisState]
+BasisStateInput = Union[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 6d939882..9d8c69af 100644
--- a/src/braket/circuits/braket_program_context.py
+++ b/src/braket/circuits/braket_program_context.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, Optional, Tuple, Union
+from typing import Optional, Union
 
 import numpy as np
 from sympy import Expr
@@ -56,18 +56,18 @@ 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:
+    def add_phase_instruction(self, target: tuple[int], phase_value: int) -> None:
         raise NotImplementedError
 
     def add_gate_instruction(
-        self, gate_name: str, target: Tuple[int], *params, ctrl_modifiers: List[int], power: float
+        self, gate_name: str, target: tuple[int], *params, ctrl_modifiers: list[int], power: float
     ) -> None:
         """Add Braket gate to the circuit.
 
         Args:
             gate_name (str): name of the built-in Braket gate.
-            target (Tuple[int]): control_qubits + target_qubits.
-            ctrl_modifiers (List[int]): Quantum state on which to control the
+            target (tuple[int]): control_qubits + target_qubits.
+            ctrl_modifiers (list[int]): Quantum state on which to control the
                 operation. Must be a binary sequence of same length as number of qubits in
                 `control-qubits` in target. 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
@@ -88,26 +88,26 @@ def add_gate_instruction(
     def add_custom_unitary(
         self,
         unitary: np.ndarray,
-        target: Tuple[int],
+        target: tuple[int],
     ) -> None:
         """Add a custom Unitary instruction to the circuit
 
         Args:
             unitary (np.ndarray): unitary matrix
-            target (Tuple[int]): control_qubits + target_qubits
+            target (tuple[int]): control_qubits + target_qubits
         """
         instruction = Instruction(Unitary(unitary), target)
         self._circuit.add_instruction(instruction)
 
     def add_noise_instruction(
-        self, noise_instruction: str, target: List[int], probabilities: List[float]
+        self, noise_instruction: str, target: list[int], probabilities: list[float]
     ) -> None:
         """Method to add a noise instruction to the circuit
 
         Args:
             noise_instruction (str): The name of the noise operation
-            target (List[int]): The target qubit or qubits to which the noise operation is applied.
-            probabilities (List[float]): The probabilities associated with each possible outcome
+            target (list[int]): The target qubit or qubits to which the noise operation is applied.
+            probabilities (list[float]): The probabilities associated with each possible outcome
                 of the noise operation.
         """
         instruction = Instruction(
@@ -115,12 +115,12 @@ def add_noise_instruction(
         )
         self._circuit.add_instruction(instruction)
 
-    def add_kraus_instruction(self, matrices: List[np.ndarray], target: List[int]) -> None:
+    def add_kraus_instruction(self, matrices: list[np.ndarray], target: list[int]) -> None:
         """Method to add a Kraus instruction to the circuit
 
         Args:
-            matrices (List[ndarray]): The matrices defining the Kraus operation
-            target (List[int]): The target qubit or qubits to which the Kraus operation is applied.
+            matrices (list[ndarray]): The matrices defining the Kraus operation
+            target (list[int]): The target qubit or qubits to which the Kraus operation is applied.
         """
         instruction = Instruction(Kraus(matrices), target)
         self._circuit.add_instruction(instruction)
diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py
index 34bde9b9..5d191c69 100644
--- a/src/braket/circuits/circuit.py
+++ b/src/braket/circuits/circuit.py
@@ -14,8 +14,9 @@
 from __future__ import annotations
 
 import warnings
+from collections.abc import Callable, Iterable
 from numbers import Number
-from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar, Union
+from typing import Any, Optional, TypeVar, Union
 
 import numpy as np
 import oqpy
@@ -140,9 +141,9 @@ def __init__(self, addable: AddableTypes = None, *args, **kwargs):
 
         """
         self._moments: Moments = Moments()
-        self._result_types: Dict[ResultType] = {}
-        self._qubit_observable_mapping: Dict[Union[int, Circuit._ALL_QUBITS], Observable] = {}
-        self._qubit_observable_target_mapping: Dict[int, Tuple[int]] = {}
+        self._result_types: dict[ResultType] = {}
+        self._qubit_observable_mapping: dict[Union[int, Circuit._ALL_QUBITS], Observable] = {}
+        self._qubit_observable_target_mapping: dict[int, tuple[int]] = {}
         self._qubit_observable_set = set()
         self._parameters = set()
         self._observables_simultaneously_measurable = True
@@ -157,21 +158,21 @@ def depth(self) -> int:
         return self._moments.depth
 
     @property
-    def instructions(self) -> List[Instruction]:
+    def instructions(self) -> list[Instruction]:
         """Iterable[Instruction]: Get an `iterable` of instructions in the circuit."""
         return list(self._moments.values())
 
     @property
-    def result_types(self) -> List[ResultType]:
-        """List[ResultType]: Get a list of requested result types in the circuit."""
+    def result_types(self) -> list[ResultType]:
+        """list[ResultType]: Get a list of requested result types in the circuit."""
         return list(self._result_types.keys())
 
     @property
-    def basis_rotation_instructions(self) -> List[Instruction]:
+    def basis_rotation_instructions(self) -> list[Instruction]:
         """Gets a list of basis rotation instructions.
 
         Returns:
-            List[Instruction]: Get a list of basis rotation instructions in the circuit.
+            list[Instruction]: Get a list of basis rotation instructions in the circuit.
             These basis rotation instructions are added if result types are requested for
             an observable other than Pauli-Z.
 
@@ -199,8 +200,8 @@ def basis_rotation_instructions(self) -> List[Instruction]:
 
     @staticmethod
     def _observable_to_instruction(
-        observable: Observable, target_list: List[int]
-    ) -> List[Instruction]:
+        observable: Observable, target_list: list[int]
+    ) -> list[Instruction]:
         return [Instruction(gate, target_list) for gate in observable.basis_rotation_gates]
 
     @property
@@ -223,12 +224,12 @@ def qubits(self) -> QubitSet:
         return QubitSet(self._moments.qubits.union(self._qubit_observable_set))
 
     @property
-    def parameters(self) -> Set[FreeParameter]:
+    def parameters(self) -> set[FreeParameter]:
         """
         Gets a set of the parameters in the Circuit.
 
         Returns:
-            Set[FreeParameter]: The `FreeParameters` in the Circuit.
+            set[FreeParameter]: The `FreeParameters` in the Circuit.
         """
         return self._parameters
 
@@ -236,7 +237,7 @@ def add_result_type(
         self,
         result_type: ResultType,
         target: QubitSetInput = None,
-        target_mapping: Dict[QubitInput, QubitInput] = None,
+        target_mapping: dict[QubitInput, QubitInput] = None,
     ) -> Circuit:
         """
         Add a requested result type to `self`, returns `self` for chaining ability.
@@ -246,7 +247,7 @@ def add_result_type(
             target (QubitSetInput): Target qubits for the
                 `result_type`.
                 Default = `None`.
-            target_mapping (Dict[QubitInput, QubitInput]): A dictionary of
+            target_mapping (dict[QubitInput, QubitInput]): A dictionary of
                 qubit mappings to apply to the `result_type.target`. Key is the qubit in
                 `result_type.target` and the value is what the key will be changed to.
                 Default = `None`.
@@ -376,7 +377,7 @@ def _add_to_qubit_observable_mapping(
     @staticmethod
     def _tensor_product_index_dict(
         observable: TensorProduct, observable_target: QubitSet
-    ) -> Dict[int, Tuple[Observable, Tuple[int, ...]]]:
+    ) -> dict[int, tuple[Observable, tuple[int, ...]]]:
         obj_dict = {}
         i = 0
         factors = list(observable.factors)
@@ -400,7 +401,7 @@ def add_instruction(
         self,
         instruction: Instruction,
         target: QubitSetInput = None,
-        target_mapping: Dict[QubitInput, QubitInput] = None,
+        target_mapping: dict[QubitInput, QubitInput] = None,
     ) -> Circuit:
         """
         Add an instruction to `self`, returns `self` for chaining ability.
@@ -411,7 +412,7 @@ def add_instruction(
                 `instruction`. If a single qubit gate, an instruction is created for every index
                 in `target`.
                 Default = `None`.
-            target_mapping (Dict[QubitInput, QubitInput]): A dictionary of
+            target_mapping (dict[QubitInput, QubitInput]): 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`.
@@ -492,7 +493,7 @@ def add_circuit(
         self,
         circuit: Circuit,
         target: QubitSetInput = None,
-        target_mapping: Dict[QubitInput, QubitInput] = None,
+        target_mapping: dict[QubitInput, QubitInput] = None,
     ) -> Circuit:
         """
         Add a `circuit` to self, returns self for chaining ability.
@@ -503,7 +504,7 @@ def add_circuit(
                 supplied circuit. This is a macro over `target_mapping`; `target` is converted to
                 a `target_mapping` by zipping together a sorted `circuit.qubits` and `target`.
                 Default = `None`.
-            target_mapping (Dict[QubitInput, QubitInput]): A dictionary of
+            target_mapping (dict[QubitInput, QubitInput]): A dictionary of
                 qubit mappings to apply to the qubits of `circuit.instructions`. Key is the qubit
                 to map, and the value is what to change it to. Default = `None`.
 
@@ -568,7 +569,7 @@ def add_verbatim_box(
         self,
         verbatim_circuit: Circuit,
         target: QubitSetInput = None,
-        target_mapping: Dict[QubitInput, QubitInput] = None,
+        target_mapping: dict[QubitInput, QubitInput] = None,
     ) -> Circuit:
         """
         Add a verbatim `circuit` to self, that is, ensures that `circuit` is not modified in any way
@@ -580,7 +581,7 @@ def add_verbatim_box(
                 supplied circuit. This is a macro over `target_mapping`; `target` is converted to
                 a `target_mapping` by zipping together a sorted `circuit.qubits` and `target`.
                 Default = `None`.
-            target_mapping (Dict[QubitInput, QubitInput]): A dictionary of
+            target_mapping (dict[QubitInput, QubitInput]): A dictionary of
                 qubit mappings to apply to the qubits of `circuit.instructions`. Key is the qubit
                 to map, and the value is what to change it to. Default = `None`.
 
@@ -636,8 +637,8 @@ def add_verbatim_box(
 
     def apply_gate_noise(
         self,
-        noise: Union[Type[Noise], Iterable[Type[Noise]]],
-        target_gates: Optional[Union[Type[Gate], Iterable[Type[Gate]]]] = None,
+        noise: Union[type[Noise], Iterable[type[Noise]]],
+        target_gates: Optional[Union[type[Gate], Iterable[type[Gate]]]] = None,
         target_unitary: np.ndarray = None,
         target_qubits: Optional[QubitSetInput] = None,
     ) -> Circuit:
@@ -663,9 +664,9 @@ def apply_gate_noise(
         only applied to gates with the same qubit_count in target_qubits.
 
         Args:
-            noise (Union[Type[Noise], Iterable[Type[Noise]]]): Noise channel(s) to be applied
+            noise (Union[type[Noise], Iterable[type[Noise]]]): Noise channel(s) to be applied
                 to the circuit.
-            target_gates (Optional[Union[Type[Gate], Iterable[Type[Gate]]]]): Gate class or
+            target_gates (Optional[Union[type[Gate], Iterable[type[Gate]]]]): Gate class or
                 List of Gate classes which `noise` is applied to. Default=None.
             target_unitary (ndarray): matrix of the target unitary gates. Default=None.
             target_qubits (Optional[QubitSetInput]): Index or indices of qubit(s).
@@ -780,7 +781,7 @@ def apply_gate_noise(
 
     def apply_initialization_noise(
         self,
-        noise: Union[Type[Noise], Iterable[Type[Noise]]],
+        noise: Union[type[Noise], Iterable[type[Noise]]],
         target_qubits: Optional[QubitSetInput] = None,
     ) -> Circuit:
         """Apply `noise` at the beginning of the circuit for every qubit (default) or
@@ -792,7 +793,7 @@ def apply_initialization_noise(
         to `noise.qubit_count`.
 
         Args:
-            noise (Union[Type[Noise], Iterable[Type[Noise]]]): Noise channel(s) to be applied
+            noise (Union[type[Noise], Iterable[type[Noise]]]): Noise channel(s) to be applied
                 to the circuit.
             target_qubits (Optional[QubitSetInput]): Index or indices of qubit(s).
                 Default=None.
@@ -847,13 +848,13 @@ 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:
+    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
         share the same name, all the parameters of that name will be set to the mapped value.
 
         Args:
-            param_values (Dict[str, Number]):  A mapping of FreeParameter names
+            param_values (dict[str, Number]):  A mapping of FreeParameter names
                 to a value to assign to them.
             strict (bool): If True, raises a ValueError if none of the FreeParameters
                 in param_values appear in the circuit. False by default."
@@ -866,12 +867,12 @@ def make_bound_circuit(self, param_values: Dict[str, Number], strict: bool = Fal
             self._validate_parameters(param_values)
         return self._use_parameter_value(param_values)
 
-    def _validate_parameters(self, parameter_values: Dict[str, Number]) -> None:
+    def _validate_parameters(self, parameter_values: dict[str, Number]) -> None:
         """
         This runs a check to see that the parameters are in the Circuit.
 
         Args:
-            parameter_values (Dict[str, Number]):  A mapping of FreeParameter names
+            parameter_values (dict[str, Number]):  A mapping of FreeParameter names
                 to a value to assign to them.
 
         Raises:
@@ -885,12 +886,12 @@ def _validate_parameters(self, parameter_values: Dict[str, Number]) -> None:
             if param not in parameter_strings:
                 raise ValueError(f"No parameter in the circuit named: {param}")
 
-    def _use_parameter_value(self, param_values: Dict[str, Number]) -> Circuit:
+    def _use_parameter_value(self, param_values: dict[str, Number]) -> Circuit:
         """
         Creates a Circuit that uses the parameter values passed in.
 
         Args:
-            param_values (Dict[str, Number]): A mapping of FreeParameter names
+            param_values (dict[str, Number]): A mapping of FreeParameter names
                 to a value to assign to them.
 
         Returns:
@@ -931,7 +932,7 @@ def _validate_parameter_value(val: Any) -> None:
 
     def apply_readout_noise(
         self,
-        noise: Union[Type[Noise], Iterable[Type[Noise]]],
+        noise: Union[type[Noise], Iterable[type[Noise]]],
         target_qubits: Optional[QubitSetInput] = None,
     ) -> Circuit:
         """Apply `noise` right before measurement in every qubit (default) or target_qubits`.
@@ -942,7 +943,7 @@ def apply_readout_noise(
         to `noise.qubit_count`.
 
         Args:
-            noise (Union[Type[Noise], Iterable[Type[Noise]]]): Noise channel(s) to be applied
+            noise (Union[type[Noise], Iterable[type[Noise]]]): Noise channel(s) to be applied
                 to the circuit.
             target_qubits (Optional[QubitSetInput]): Index or indices of qubit(s).
                 Default=None.
@@ -1081,12 +1082,12 @@ 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 = AsciiCircuitDiagram) -> str:
         """
         Get a diagram for the current circuit.
 
         Args:
-            circuit_diagram_class (Type): A `CircuitDiagram` class that builds the
+            circuit_diagram_class (type): A `CircuitDiagram` class that builds the
                 diagram for this circuit. Default = `AsciiCircuitDiagram`.
 
         Returns:
@@ -1098,7 +1099,7 @@ def to_ir(
         self,
         ir_type: IRType = IRType.JAQCD,
         serialization_properties: SerializationProperties = None,
-        gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]] = None,
+        gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None,
     ) -> Union[OpenQasmProgram, JaqcdProgram]:
         """
         Converts the circuit into the canonical intermediate representation.
@@ -1110,7 +1111,7 @@ def to_ir(
             serialization_properties (SerializationProperties): 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 (Optional[dict[tuple[Gate, QubitSet], PulseSequence]]): The
                 calibration data for the device. default: None.
 
         Returns:
@@ -1140,14 +1141,14 @@ def to_ir(
 
     @staticmethod
     def from_ir(
-        source: Union[str, OpenQasmProgram], inputs: Optional[Dict[str, io_type]] = None
+        source: Union[str, OpenQasmProgram], inputs: Optional[dict[str, io_type]] = None
     ) -> Circuit:
         """
         Converts an OpenQASM program to a Braket Circuit object.
 
         Args:
             source (Union[str, OpenQasmProgram]): OpenQASM string.
-            inputs (Optional[Dict[str, io_type]]): Inputs to the circuit.
+            inputs (Optional[dict[str, io_type]]): Inputs to the circuit.
 
         Returns:
             Circuit: Braket Circuit implementing the OpenQASM program.
@@ -1182,7 +1183,7 @@ def _to_jaqcd(self) -> JaqcdProgram:
     def _to_openqasm(
         self,
         serialization_properties: OpenQASMSerializationProperties,
-        gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]],
+        gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]],
     ) -> OpenQasmProgram:
         ir_instructions = self._create_openqasm_header(serialization_properties, gate_definitions)
         openqasm_ir_type = IRType.OPENQASM
@@ -1219,8 +1220,8 @@ def _to_openqasm(
     def _create_openqasm_header(
         self,
         serialization_properties: OpenQASMSerializationProperties,
-        gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]],
-    ) -> List[str]:
+        gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]],
+    ) -> list[str]:
         ir_instructions = ["OPENQASM 3.0;"]
         for parameter in self.parameters:
             ir_instructions.append(f"input float {parameter};")
@@ -1243,9 +1244,9 @@ def _create_openqasm_header(
 
     def _validate_gate_calbrations_uniqueness(
         self,
-        gate_definitions: Dict[Tuple[Gate, QubitSet], PulseSequence],
-        frames: Dict[Frame],
-        waveforms: Dict[ArbitraryWaveform],
+        gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence],
+        frames: dict[Frame],
+        waveforms: dict[ArbitraryWaveform],
     ) -> None:
         for key, calibration in gate_definitions.items():
             for frame in calibration._frames.values():
@@ -1256,7 +1257,7 @@ def _validate_gate_calbrations_uniqueness(
                 waveforms[waveform.id] = waveform
 
     def _generate_frame_wf_defcal_declarations(
-        self, gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]
+        self, gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]]
     ) -> Optional[str]:
         program = oqpy.Program(None)
 
@@ -1302,8 +1303,8 @@ def _generate_frame_wf_defcal_declarations(
         return None
 
     def _get_frames_waveforms_from_instrs(
-        self, gate_definitions: Optional[Dict[Tuple[Gate, QubitSet], PulseSequence]]
-    ) -> Tuple[Dict[Frame], Dict[ArbitraryWaveform]]:
+        self, gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]]
+    ) -> tuple[dict[Frame], dict[ArbitraryWaveform]]:
         from braket.circuits.gates import PulseGate
 
         frames = {}
@@ -1326,9 +1327,9 @@ def _get_frames_waveforms_from_instrs(
 
     def _add_fixed_argument_calibrations(
         self,
-        gate_definitions: Dict[Tuple[Gate, QubitSet], PulseSequence],
+        gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence],
         instruction: Instruction,
-    ) -> Dict[Tuple[Gate, QubitSet], PulseSequence]:
+    ) -> dict[tuple[Gate, QubitSet], PulseSequence]:
         """Adds calibrations with arguments set to the instruction parameter values
 
         Given the collection of parameters in instruction.operator, this function looks for matching
@@ -1341,12 +1342,12 @@ def _add_fixed_argument_calibrations(
         If N=0, we ignore it as it will not be removed by _generate_frame_wf_defcal_declarations.
 
         Args:
-            gate_definitions (Dict[Tuple[Gate, QubitSet], PulseSequence]): a dictionary of
+            gate_definitions (dict[tuple[Gate, QubitSet], PulseSequence]): a dictionary of
                 calibrations
             instruction (Instruction): a Circuit instruction
 
         Returns:
-            Dict[Tuple[Gate, QubitSet], PulseSequence]: additional calibrations
+            dict[tuple[Gate, QubitSet], PulseSequence]: additional calibrations
 
         Raises:
             NotImplementedError: in two cases: (i) if the instruction contains unbound parameters
diff --git a/src/braket/circuits/circuit_diagram.py b/src/braket/circuits/circuit_diagram.py
index ee09d73f..5b156d29 100644
--- a/src/braket/circuits/circuit_diagram.py
+++ b/src/braket/circuits/circuit_diagram.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/circuits/compiler_directive.py b/src/braket/circuits/compiler_directive.py
index 3ca677ff..d5b5591f 100644
--- a/src/braket/circuits/compiler_directive.py
+++ b/src/braket/circuits/compiler_directive.py
@@ -13,7 +13,8 @@
 
 from __future__ import annotations
 
-from typing import Any, Sequence, Tuple
+from collections.abc import Sequence
+from typing import Any
 
 from braket.circuits.operator import Operator
 from braket.circuits.serialization import IRType, SerializationProperties
@@ -41,8 +42,8 @@ def name(self) -> str:
         return self.__class__.__name__
 
     @property
-    def ascii_symbols(self) -> Tuple[str, ...]:
-        """Tuple[str, ...]: Returns the ascii symbols for the compiler directive."""
+    def ascii_symbols(self) -> tuple[str, ...]:
+        """tuple[str, ...]: Returns the ascii symbols for the compiler directive."""
         return self._ascii_symbols
 
     def to_ir(
diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py
index 135a892c..718e3344 100644
--- a/src/braket/circuits/gate.py
+++ b/src/braket/circuits/gate.py
@@ -13,8 +13,9 @@
 
 from __future__ import annotations
 
+from collections.abc import Sequence
 from itertools import groupby
-from typing import Any, List, Optional, Sequence, Tuple, Type
+from typing import Any, Optional
 
 from braket.circuits.basis_state import BasisState, BasisStateInput
 from braket.circuits.quantum_operator import QuantumOperator
@@ -55,13 +56,13 @@ def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]):
     def _qasm_name(self) -> NotImplementedError:
         raise NotImplementedError()
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         """Returns a list of gates that implement the adjoint of this gate.
 
         This is a list because some gates do not have an inverse defined by a single existing gate.
 
         Returns:
-            List[Gate]: The gates comprising the adjoint of this gate.
+            list[Gate]: The gates comprising the adjoint of this gate.
         """
         raise NotImplementedError(f"Gate {self.name} does not have adjoint implemented")
 
@@ -200,8 +201,8 @@ def _to_openqasm(
         )
 
     @property
-    def ascii_symbols(self) -> Tuple[str, ...]:
-        """Tuple[str, ...]: Returns the ascii symbols for the quantum operator."""
+    def ascii_symbols(self) -> tuple[str, ...]:
+        """tuple[str, ...]: Returns the ascii symbols for the quantum operator."""
         return self._ascii_symbols
 
     def __eq__(self, other):
@@ -214,10 +215,10 @@ def __hash__(self):
         return hash((self.name, self.qubit_count))
 
     @classmethod
-    def register_gate(cls, gate: Type[Gate]) -> None:
+    def register_gate(cls, gate: type[Gate]) -> None:
         """Register a gate implementation by adding it into the Gate class.
 
         Args:
-            gate (Type[Gate]): Gate class to register.
+            gate (type[Gate]): Gate class to register.
         """
         setattr(cls, gate.__name__, gate)
diff --git a/src/braket/circuits/gate_calibrations.py b/src/braket/circuits/gate_calibrations.py
index 3c5abefd..6cbdd97d 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, Dict, List, Optional, Tuple
+from typing import Any, Optional
 
 from braket.circuits.gate import Gate
 from braket.circuits.serialization import (
@@ -35,23 +35,23 @@ class GateCalibrations:
 
     def __init__(
         self,
-        pulse_sequences: Dict[Tuple[Gate, QubitSet], PulseSequence],
+        pulse_sequences: dict[tuple[Gate, QubitSet], PulseSequence],
     ):
         """
         Args:
-            pulse_sequences (Dict[Tuple[Gate, QubitSet], PulseSequence]): A mapping containing a key of
+            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
+        self.pulse_sequences: dict[tuple[Gate, QubitSet], PulseSequence] = pulse_sequences
 
     @property
-    def pulse_sequences(self) -> Dict[Tuple[Gate, QubitSet], PulseSequence]:
+    def pulse_sequences(self) -> dict[tuple[Gate, QubitSet], PulseSequence]:
         """
         Gets the mapping of (Gate, Qubit) to the corresponding `PulseSequence`.
 
         Returns:
-            Dict[Tuple[Gate, QubitSet], PulseSequence]: The calibration data Dictionary.
+            dict[tuple[Gate, QubitSet], PulseSequence]: The calibration data Dictionary.
         """
         return self._pulse_sequences
 
@@ -64,7 +64,7 @@ def pulse_sequences(self, value: Any) -> None:
             value(Any): The value for the pulse_sequences property to be set to.
 
         Raises:
-            TypeError: Raised if the type is not Dict[Tuple[Gate, QubitSet], PulseSequence]
+            TypeError: Raised if the type is not dict[tuple[Gate, QubitSet], PulseSequence]
 
         """
         if isinstance(value, dict) and all(
@@ -75,7 +75,7 @@ def pulse_sequences(self, value: Any) -> None:
         else:
             raise TypeError(
                 "The value for pulse_sequence must be of type: "
-                "Dict[Tuple[Gate, QubitSet], PulseSequence]"
+                "dict[tuple[Gate, QubitSet], PulseSequence]"
             )
 
     def copy(self) -> GateCalibrations:
@@ -91,13 +91,13 @@ def __len__(self):
         return len(self._pulse_sequences)
 
     def filter(
-        self, gates: Optional[List[Gate]] = None, qubits: Optional[QubitSet] = None
+        self, gates: Optional[list[Gate]] = None, qubits: Optional[QubitSet] = None
     ) -> Optional[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.
+            gates (Optional[list[Gate]]): An optional list of gates to filter on.
             qubits (Optional[QubitSet]): An optional `QubitSet` to filter on.
 
         Returns:
@@ -114,12 +114,12 @@ def filter(
             {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: Optional[tuple[Gate, QubitSet]] = 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 (Optional[tuple[Gate, QubitSet]]): An optional key to get a specific defcal.
                 Default: None
 
         Returns:
@@ -143,7 +143,7 @@ def to_ir(self, calibration_key: Optional[Tuple[Gate, QubitSet]] = None) -> str:
             )
             return defcal
 
-    def _def_cal_gate(self, gate_key: Tuple[Gate, QubitSet]) -> str:
+    def _def_cal_gate(self, gate_key: tuple[Gate, QubitSet]) -> str:
         return " ".join(
             [
                 "defcal",
diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py
index a68d9ead..dbc2e1a0 100644
--- a/src/braket/circuits/gates.py
+++ b/src/braket/circuits/gates.py
@@ -13,8 +13,9 @@
 
 from __future__ import annotations
 
+from collections.abc import Iterable
 from copy import deepcopy
-from typing import Any, Iterable, List, Optional, Union
+from typing import Any, Optional, Union
 
 import numpy as np
 from oqpy import Program
@@ -68,7 +69,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "h"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [H()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -133,7 +134,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "i"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [I()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -198,7 +199,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "x"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [X()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -263,7 +264,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "y"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [Y()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -328,7 +329,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "z"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [Z()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -393,7 +394,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "s"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [Si()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -458,7 +459,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "si"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [S()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -523,7 +524,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "t"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [Ti()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -588,7 +589,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "ti"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [T()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -653,7 +654,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "v"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [Vi()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -718,7 +719,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "vi"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [V()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -1103,7 +1104,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "cnot"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [CNot()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -1163,7 +1164,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "swap"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [Swap()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -1238,7 +1239,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "iswap"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [self, self, self]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -1773,7 +1774,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "cv"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [self, self, self]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -1833,7 +1834,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "cy"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [CY()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -1893,7 +1894,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "cz"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [CZ()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -1945,7 +1946,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "ecr"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [ECR()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -2304,7 +2305,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "ccnot"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [CCNot()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -2387,7 +2388,7 @@ def __init__(self):
     def _qasm_name(self) -> str:
         return "cswap"
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [CSwap()]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -2476,7 +2477,7 @@ def to_matrix(self) -> np.ndarray:
             ]
         )
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [GPi(self.angle)]
 
     @staticmethod
@@ -2555,7 +2556,7 @@ def to_matrix(self) -> np.ndarray:
             ]
         ) / np.sqrt(2)
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [GPi2(self.angle + np.pi)]
 
     @staticmethod
@@ -2665,7 +2666,7 @@ def to_matrix(self) -> np.ndarray:
             ]
         )
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [MS(self.angle_1 + np.pi, self.angle_2, self.angle_3)]
 
     @staticmethod
@@ -2754,7 +2755,7 @@ def __init__(self, matrix: np.ndarray, display_name: str = "U"):
     def to_matrix(self) -> np.ndarray:
         return np.array(self._matrix)
 
-    def adjoint(self) -> List[Gate]:
+    def adjoint(self) -> list[Gate]:
         return [Unitary(self._matrix.conj().T, display_name=f"({self.ascii_symbols})^†")]
 
     def _to_jaqcd(self, target: QubitSet) -> Any:
@@ -2785,7 +2786,7 @@ def __hash__(self):
         return hash((self.name, str(self._matrix), self.qubit_count))
 
     @staticmethod
-    def _transform_matrix_to_ir(matrix: np.ndarray) -> List:
+    def _transform_matrix_to_ir(matrix: np.ndarray) -> list:
         return [[[element.real, element.imag] for element in row] for row in matrix.tolist()]
 
     @staticmethod
@@ -2847,7 +2848,7 @@ def pulse_sequence(self) -> PulseSequence:
         return self._pulse_sequence
 
     @property
-    def parameters(self) -> List[FreeParameter]:
+    def parameters(self) -> list[FreeParameter]:
         """Returns the list of `FreeParameter` s associated with the gate."""
         return list(self._pulse_sequence.parameters)
 
diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py
index 6211e122..18dc49e4 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, Dict, List, Optional, Tuple
+from typing import Any, Optional
 
 from braket.circuits.basis_state import BasisState, BasisStateInput
 from braket.circuits.compiler_directive import CompilerDirective
@@ -135,13 +135,13 @@ def power(self) -> float:
         """
         return self._power
 
-    def adjoint(self) -> List[Instruction]:
+    def adjoint(self) -> list[Instruction]:
         """Returns a list of Instructions implementing adjoint of this instruction's own operator
 
         This operation only works on Gate operators and compiler directives.
 
         Returns:
-            List[Instruction]: A list of new instructions that comprise the adjoint of this operator
+            list[Instruction]: A list of new instructions that comprise the adjoint of this operator
 
         Raises:
             NotImplementedError: If `operator` is not of type `Gate` or `CompilerDirective`
@@ -195,15 +195,15 @@ def to_ir(
         )
 
     @property
-    def ascii_symbols(self) -> Tuple[str, ...]:
-        """Tuple[str, ...]: Returns the ascii symbols for the instruction's operator."""
+    def ascii_symbols(self) -> tuple[str, ...]:
+        """tuple[str, ...]: Returns the ascii symbols for the instruction's operator."""
         return self._operator.ascii_symbols
 
     def copy(
         self,
-        target_mapping: Dict[QubitInput, QubitInput] = None,
+        target_mapping: dict[QubitInput, QubitInput] = None,
         target: QubitSetInput = None,
-        control_mapping: Dict[QubitInput, QubitInput] = None,
+        control_mapping: dict[QubitInput, QubitInput] = None,
         control: QubitSetInput = None,
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
@@ -217,11 +217,11 @@ def copy(
             Same relationship holds for `control_mapping`.
 
         Args:
-            target_mapping (Dict[QubitInput, QubitInput]): A dictionary of
+            target_mapping (dict[QubitInput, QubitInput]): A dictionary of
                 qubit mappings to apply to the target. Key is the qubit in this `target` and the
                 value is what the key is changed to. Default = `None`.
             target (QubitSetInput): Target qubits for the new instruction. Default is None.
-            control_mapping (Dict[QubitInput, QubitInput]): A dictionary of
+            control_mapping (dict[QubitInput, QubitInput]): A dictionary of
                 qubit mappings to apply to the control. Key is the qubit in this `control` and the
                 value is what the key is changed to. Default = `None`.
             control (QubitSetInput): Control qubits for the new instruction. Default is None.
diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py
index 70cc81bc..a4c31339 100644
--- a/src/braket/circuits/moments.py
+++ b/src/braket/circuits/moments.py
@@ -13,20 +13,10 @@
 
 from __future__ import annotations
 
+from collections import OrderedDict
+from collections.abc import ItemsView, Iterable, KeysView, Mapping, ValuesView
 from enum import Enum
-from typing import (
-    Any,
-    Dict,
-    ItemsView,
-    Iterable,
-    KeysView,
-    List,
-    Mapping,
-    NamedTuple,
-    OrderedDict,
-    Union,
-    ValuesView,
-)
+from typing import Any, NamedTuple, Union
 
 from braket.circuits.compiler_directive import CompilerDirective
 from braket.circuits.instruction import Instruction
@@ -112,7 +102,7 @@ class Moments(Mapping[MomentsKey, Instruction]):
 
     def __init__(self, instructions: Iterable[Instruction] = None):
         self._moments: OrderedDict[MomentsKey, Instruction] = OrderedDict()
-        self._max_times: Dict[Qubit, int] = {}
+        self._max_times: dict[Qubit, int] = {}
         self._qubits = QubitSet()
         self._depth = 0
         self._time_all_qubits = -1
@@ -141,12 +131,12 @@ def qubits(self) -> QubitSet:
         """
         return self._qubits
 
-    def time_slices(self) -> Dict[int, List[Instruction]]:
+    def time_slices(self) -> dict[int, list[Instruction]]:
         """
         Get instructions keyed by time.
 
         Returns:
-            Dict[int, List[Instruction]]: Key is the time and value is a list of instructions that
+            dict[int, list[Instruction]]: Key is the time and value is a list of instructions that
             occur at that moment in time. The order of instructions is in no particular order.
 
         Note:
diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py
index 498f70fd..8e5b9cc7 100644
--- a/src/braket/circuits/noise.py
+++ b/src/braket/circuits/noise.py
@@ -13,7 +13,8 @@
 
 from __future__ import annotations
 
-from typing import Any, Dict, Iterable, List, Optional, Sequence, Set, Type, Union
+from collections.abc import Iterable, Sequence
+from typing import Any, Optional, Union
 
 import numpy as np
 
@@ -163,10 +164,10 @@ def from_dict(cls, noise: dict) -> Noise:
         raise NotImplementedError
 
     @classmethod
-    def register_noise(cls, noise: Type[Noise]) -> None:
+    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.
+            noise (type[Noise]): Noise class to register.
         """
         setattr(cls, noise.__name__, noise)
 
@@ -220,13 +221,13 @@ def __str__(self):
         return f"{self.name}({self.probability})"
 
     @property
-    def parameters(self) -> List[Union[FreeParameterExpression, float]]:
+    def parameters(self) -> list[Union[FreeParameterExpression, float]]:
         """
         Returns the parameters associated with the object, either unbound free parameter expressions
         or bound values.
 
         Returns:
-            List[Union[FreeParameterExpression, float]]: The free parameter expressions
+            list[Union[FreeParameterExpression, float]]: The free parameter expressions
             or fixed values associated with the object.
         """
         return [self._probability]
@@ -343,14 +344,14 @@ class MultiQubitPauliNoise(Noise, Parameterizable):
 
     def __init__(
         self,
-        probabilities: Dict[str, Union[FreeParameterExpression, float]],
+        probabilities: dict[str, Union[FreeParameterExpression, float]],
         qubit_count: Optional[int],
         ascii_symbols: Sequence[str],
     ):
         """[summary]
 
         Args:
-            probabilities (Dict[str, Union[FreeParameterExpression, float]]): A dictionary with
+            probabilities (dict[str, Union[FreeParameterExpression, float]]): A dictionary with
                 Pauli strings as keys and the probabilities as values, i.e. {"XX": 0.1. "IZ": 0.2}.
             qubit_count (Optional[int]): The number of qubits the Pauli noise acts on.
             ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when
@@ -402,7 +403,7 @@ def __init__(
 
     @classmethod
     def _validate_pauli_string(
-        cls, pauli_str: str, qubit_count: int, allowed_substrings: Set[str]
+        cls, pauli_str: str, qubit_count: int, allowed_substrings: set[str]
     ) -> None:
         if not isinstance(pauli_str, str):
             raise TypeError(f"Type of {pauli_str} was not a string.")
@@ -436,15 +437,15 @@ def __eq__(self, other):
         return False
 
     @property
-    def probabilities(self) -> Dict[str, float]:
+    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.
+            dict[str, float]: A map of a Pauli string to its corresponding probability.
         """
         return self._probabilities
 
     @property
-    def parameters(self) -> List[Union[FreeParameterExpression, float]]:
+    def parameters(self) -> list[Union[FreeParameterExpression, float]]:
         """
         Returns the parameters associated with the object, either unbound free parameter expressions
         or bound values.
@@ -452,7 +453,7 @@ def parameters(self) -> List[Union[FreeParameterExpression, float]]:
         Parameters are in alphabetical order of the Pauli strings in `probabilities`.
 
         Returns:
-            List[Union[FreeParameterExpression, float]]: The free parameter expressions
+            list[Union[FreeParameterExpression, float]]: The free parameter expressions
             or fixed values associated with the object.
         """
         return [
@@ -596,7 +597,7 @@ def __eq__(self, other):
         return False
 
     @property
-    def parameters(self) -> List[Union[FreeParameterExpression, float]]:
+    def parameters(self) -> list[Union[FreeParameterExpression, float]]:
         """
         Returns the parameters associated with the object, either unbound free parameter expressions
         or bound values.
@@ -604,7 +605,7 @@ def parameters(self) -> List[Union[FreeParameterExpression, float]]:
         Parameters are in the order [probX, probY, probZ]
 
         Returns:
-            List[Union[FreeParameterExpression, float]]: The free parameter expressions
+            list[Union[FreeParameterExpression, float]]: The free parameter expressions
             or fixed values associated with the object.
         """
         return self._parameters
@@ -689,13 +690,13 @@ def __str__(self):
         return f"{self.name}({self.gamma})"
 
     @property
-    def parameters(self) -> List[Union[FreeParameterExpression, float]]:
+    def parameters(self) -> list[Union[FreeParameterExpression, float]]:
         """
         Returns the parameters associated with the object, either unbound free parameter expressions
         or bound values.
 
         Returns:
-            List[Union[FreeParameterExpression, float]]: The free parameter expressions
+            list[Union[FreeParameterExpression, float]]: The free parameter expressions
             or fixed values associated with the object.
         """
         return [self._gamma]
@@ -791,7 +792,7 @@ def __str__(self):
         return f"{self.name}({self.gamma}, {self.probability})"
 
     @property
-    def parameters(self) -> List[Union[FreeParameterExpression, float]]:
+    def parameters(self) -> list[Union[FreeParameterExpression, float]]:
         """
         Returns the parameters associated with the object, either unbound free parameter expressions
         or bound values.
@@ -799,7 +800,7 @@ def parameters(self) -> List[Union[FreeParameterExpression, float]]:
         Parameters are in the order [gamma, probability]
 
         Returns:
-            List[Union[FreeParameterExpression, float]]: The free parameter expressions
+            list[Union[FreeParameterExpression, float]]: The free parameter expressions
             or fixed values associated with the object.
         """
         return [self.gamma, self.probability]
diff --git a/src/braket/circuits/noise_helpers.py b/src/braket/circuits/noise_helpers.py
index 2238a2d6..06c0b362 100644
--- a/src/braket/circuits/noise_helpers.py
+++ b/src/braket/circuits/noise_helpers.py
@@ -14,7 +14,8 @@
 from __future__ import annotations
 
 import warnings
-from typing import TYPE_CHECKING, Any, Iterable, List, Optional, Tuple, Type, Union
+from collections.abc import Iterable
+from typing import TYPE_CHECKING, Any, Optional, Union
 
 import numpy as np
 
@@ -42,27 +43,27 @@ def no_noise_applied_warning(noise_applied: bool) -> None:
         )
 
 
-def wrap_with_list(an_item: Any) -> List[Any]:
+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.
 
     Returns:
-        List[Any]: The item wrapped in a list.
+        list[Any]: The item wrapped in a list.
     """
     if an_item is not None and not isinstance(an_item, list):
         an_item = [an_item]
     return an_item
 
 
-def check_noise_target_gates(noise: Noise, target_gates: Iterable[Type[Gate]]) -> None:
+def check_noise_target_gates(noise: Noise, target_gates: Iterable[type[Gate]]) -> None:
     """Helper function to check
     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
+        target_gates (Iterable[type[Gate]]): Gate class or
             List of Gate classes which `noise` is applied to.
     """
     if not all(isinstance(g, type) and issubclass(g, Gate) for g in target_gates):
@@ -126,7 +127,7 @@ def check_noise_target_qubits(
 
 
 def apply_noise_to_moments(
-    circuit: Circuit, noise: Iterable[Type[Noise]], target_qubits: QubitSet, position: str
+    circuit: Circuit, noise: Iterable[type[Noise]], target_qubits: QubitSet, position: str
 ) -> Circuit:
     """
     Apply initialization/readout noise to the circuit.
@@ -138,7 +139,7 @@ def apply_noise_to_moments(
 
     Args:
         circuit (Circuit): A ciruit where `noise` is applied to.
-        noise (Iterable[Type[Noise]]): Noise channel(s) to be applied
+        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.
         position (str): The position to add the noise to. May be 'initialization' or
@@ -181,18 +182,18 @@ def apply_noise_to_moments(
 
 
 def _apply_noise_to_gates_helper(
-    noise: Iterable[Type[Noise]],
+    noise: Iterable[type[Noise]],
     target_qubits: QubitSet,
     instruction: Instruction,
     noise_index: int,
     intersection: QubitSet,
     noise_applied: bool,
     new_noise_instruction: Iterable,
-) -> Tuple[Iterable[Instruction], int, bool]:
+) -> tuple[Iterable[Instruction], int, bool]:
     """Helper function to work out the noise instructions to be attached to a gate.
 
     Args:
-        noise (Iterable[Type[Noise]]): Noise channel(s) to be applied
+        noise (Iterable[type[Noise]]): Noise channel(s) to be applied
             to the circuit.
         target_qubits (QubitSet): Index or indices of qubits which `noise` is applied to.
         instruction (Instruction): Instruction of the gate which `noise` is applied to.
@@ -204,7 +205,7 @@ def _apply_noise_to_gates_helper(
             to the circuit.
 
     Returns:
-        Tuple[Iterable[Instruction], int, bool]: A tuple of three values:
+        tuple[Iterable[Instruction], int, bool]: A tuple of three values:
         new_noise_instruction: A list of noise intructions
         noise_index: The number of noise channels applied to the gate
         noise_applied: Whether noise is applied or not
@@ -234,8 +235,8 @@ 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],
+    noise: Iterable[type[Noise]],
+    target_gates: Union[Iterable[type[Gate]], np.ndarray],
     target_qubits: QubitSet,
 ) -> Circuit:
     """Apply noise after target gates in target qubits.
@@ -247,9 +248,9 @@ def apply_noise_to_gates(
 
     Args:
         circuit (Circuit): A ciruit where `noise` is applied to.
-        noise (Iterable[Type[Noise]]): Noise channel(s) to be applied
+        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
+        target_gates (Union[Iterable[type[Gate]], ndarray]): List of gates, or a unitary matrix
             which `noise` is applied to.
         target_qubits (QubitSet): Index or indices of qubits which `noise` is applied to.
 
diff --git a/src/braket/circuits/noise_model/circuit_instruction_criteria.py b/src/braket/circuits/noise_model/circuit_instruction_criteria.py
index 0a8c11bd..170d3e99 100644
--- a/src/braket/circuits/noise_model/circuit_instruction_criteria.py
+++ b/src/braket/circuits/noise_model/circuit_instruction_criteria.py
@@ -12,7 +12,7 @@
 # language governing permissions and limitations under the License.
 
 from abc import abstractmethod
-from typing import Optional, Set, Tuple, Union
+from typing import Optional, Union
 
 from braket.circuits.instruction import Instruction
 from braket.circuits.noise_model.criteria import Criteria
@@ -36,13 +36,13 @@ def instruction_matches(self, instruction: Instruction) -> bool:
 
     @staticmethod
     def _check_target_in_qubits(
-        qubits: Optional[Set[Union[int, Tuple[int]]]], target: QubitSetInput
+        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.
 
         Args:
-            qubits (Optional[Set[Union[int, Tuple[int]]]]): The qubits provided to the criteria.
+            qubits (Optional[set[Union[int, tuple[int]]]]): The qubits provided to the criteria.
             target (QubitSetInput): Targets of an instruction.
 
         Returns:
diff --git a/src/braket/circuits/noise_model/criteria.py b/src/braket/circuits/noise_model/criteria.py
index 539bf382..b9f0d2cc 100644
--- a/src/braket/circuits/noise_model/criteria.py
+++ b/src/braket/circuits/noise_model/criteria.py
@@ -14,8 +14,9 @@
 from __future__ import annotations
 
 from abc import ABC, abstractmethod
+from collections.abc import Iterable
 from enum import Enum
-from typing import Any, Iterable, Set, Type, Union
+from typing import Any, Union
 
 
 class CriteriaKey(str, Enum):
@@ -54,14 +55,14 @@ 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) -> Union[CriteriaKeyResult, set[Any]]:
         """Returns a set of key for a given key type.
 
         Args:
             key_type (CriteriaKey): The criteria key type.
 
         Returns:
-            Union[CriteriaKeyResult, Set[Any]]: Returns a set of keys for a key type. The
+            Union[CriteriaKeyResult, set[Any]]: Returns a set of keys for a key type. The
             actual returned keys will depend on the CriteriaKey. If the provided key type
             is not relevant the returned list will be empty. If the provided key type is
             relevant for all possible inputs, the string CriteriaKeyResult.ALL will be returned.
@@ -105,10 +106,10 @@ def from_dict(cls, criteria: dict) -> Criteria:
         raise NotImplementedError
 
     @classmethod
-    def register_criteria(cls, criteria: Type[Criteria]) -> None:
+    def register_criteria(cls, criteria: type[Criteria]) -> None:
         """Register a criteria implementation by adding it into the Criteria class.
 
         Args:
-            criteria (Type[Criteria]): Criteria class to register.
+            criteria (type[Criteria]): Criteria class to register.
         """
         setattr(cls, criteria.__name__, criteria)
diff --git a/src/braket/circuits/noise_model/criteria_input_parsing.py b/src/braket/circuits/noise_model/criteria_input_parsing.py
index a47d8d78..11f24619 100644
--- a/src/braket/circuits/noise_model/criteria_input_parsing.py
+++ b/src/braket/circuits/noise_model/criteria_input_parsing.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 typing import Iterable, Optional, Set, Tuple, Union
+from collections.abc import Iterable
+from typing import Optional, Union
 
 from braket.circuits.quantum_operator import QuantumOperator
 from braket.registers.qubit_set import QubitSetInput
@@ -19,7 +20,7 @@
 
 def parse_operator_input(
     operators: Union[QuantumOperator, Iterable[QuantumOperator]]
-) -> Optional[Set[QuantumOperator]]:
+) -> Optional[set[QuantumOperator]]:
     """
     Processes the quantum operator input to __init__ to validate and return a set of
     QuantumOperators.
@@ -28,7 +29,7 @@ def parse_operator_input(
         operators (Union[QuantumOperator, Iterable[QuantumOperator]]): QuantumOperator input.
 
     Returns:
-        Optional[Set[QuantumOperator]]: The set of relevant QuantumOperators or None if none
+        Optional[set[QuantumOperator]]: The set of relevant QuantumOperators or None if none
         is specified.
 
     Throws:
@@ -47,7 +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]]]]:
+) -> Optional[set[Union[int, tuple[int]]]]:
     """
     Processes the qubit input to __init__ to validate and return a set of qubit targets.
 
@@ -58,7 +59,7 @@ def parse_qubit_input(
             expected qubit count matches the actual qubit count. Default is 0.
 
     Returns:
-        Optional[Set[Union[int, Tuple[int]]]]: The set of qubit targets, or None if no qubits
+        Optional[set[Union[int, tuple[int]]]]: The set of qubit targets, or None if no qubits
         are specified.
     """
     if qubits is None:
diff --git a/src/braket/circuits/noise_model/gate_criteria.py b/src/braket/circuits/noise_model/gate_criteria.py
index 571c6cb6..623c0247 100644
--- a/src/braket/circuits/noise_model/gate_criteria.py
+++ b/src/braket/circuits/noise_model/gate_criteria.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 typing import Any, Iterable, Optional, Set, Union
+from collections.abc import Iterable
+from typing import Any, Optional, Union
 
 from braket.circuits.gate import Gate
 from braket.circuits.instruction import Instruction
@@ -65,14 +66,14 @@ 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) -> 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:
+            Union[CriteriaKeyResult, set[Any]]: The return value is based on the key type:
             GATE will return a set of Gate classes that are relevant to this Criteria.
             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.
diff --git a/src/braket/circuits/noise_model/noise_model.py b/src/braket/circuits/noise_model/noise_model.py
index b6805e33..8922cda7 100644
--- a/src/braket/circuits/noise_model/noise_model.py
+++ b/src/braket/circuits/noise_model/noise_model.py
@@ -15,7 +15,7 @@
 
 from collections import defaultdict
 from dataclasses import dataclass
-from typing import List, Optional, Type
+from typing import Optional
 
 from braket.circuits.circuit import Circuit
 from braket.circuits.gate import Gate
@@ -76,9 +76,9 @@ def from_dict(cls, noise_model_item: dict) -> NoiseModelInstruction:
 class NoiseModelInstructions:
     """Represents the instructions in a noise model, separated by type."""
 
-    initialization_noise: List[NoiseModelInstruction]
-    gate_noise: List[NoiseModelInstruction]
-    readout_noise: List[NoiseModelInstruction]
+    initialization_noise: list[NoiseModelInstruction]
+    gate_noise: list[NoiseModelInstruction]
+    readout_noise: list[NoiseModelInstruction]
 
 
 class NoiseModel:
@@ -90,7 +90,7 @@ class NoiseModel:
     a phase flip.
     """
 
-    def __init__(self, instructions: List[NoiseModelInstruction] = None):
+    def __init__(self, instructions: list[NoiseModelInstruction] = None):
         self._instructions = instructions or []
 
     def __repr__(self):
@@ -109,12 +109,12 @@ def __str__(self):
         return "\n".join(all_strings)
 
     @property
-    def instructions(self) -> List[NoiseModelInstruction]:
+    def instructions(self) -> list[NoiseModelInstruction]:
         """
         List all the noise in the NoiseModel.
 
         Returns:
-            List[NoiseModelInstruction]: The noise model instructions.
+            list[NoiseModelInstruction]: The noise model instructions.
         """
         return self._instructions
 
@@ -198,7 +198,7 @@ def from_filter(
         self,
         qubit: Optional[QubitSetInput] = None,
         gate: Optional[Gate] = None,
-        noise: Optional[Type[Noise]] = None,
+        noise: Optional[type[Noise]] = None,
     ) -> NoiseModel:
         """
         Returns a new NoiseModel from this NoiseModel using a given filter. If no filters are
@@ -211,7 +211,7 @@ def from_filter(
             gate (Optional[Gate]): The gate to filter. Default is None. If not None,
                 the returned NoiseModel will only have Noise that might be applicable
                 to the passed Gate.
-            noise (Optional[Type[Noise]]): The noise class to filter. Default is None.
+            noise (Optional[type[Noise]]): The noise class to filter. Default is None.
                 If not None, the returned NoiseModel will only have noise that is of the same
                 class as the given noise class.
 
@@ -259,7 +259,7 @@ def to_dict(self) -> dict:
     def _apply_gate_noise(
         cls,
         circuit: Circuit,
-        gate_noise_instructions: List[NoiseModelInstruction],
+        gate_noise_instructions: list[NoiseModelInstruction],
     ) -> Circuit:
         """
         Applies the gate noise to return a new circuit that's the `noisy` version of the given
@@ -267,7 +267,7 @@ def _apply_gate_noise(
 
         Args:
             circuit (Circuit): a circuit to apply `noise` to.
-            gate_noise_instructions (List[NoiseModelInstruction]): a list of gate noise
+            gate_noise_instructions (list[NoiseModelInstruction]): a list of gate noise
                 instructions to apply to the circuit.
 
         Returns:
@@ -293,14 +293,14 @@ def _apply_gate_noise(
     def _apply_init_noise(
         cls,
         circuit: Circuit,
-        init_noise_instructions: List[NoiseModelInstruction],
+        init_noise_instructions: list[NoiseModelInstruction],
     ) -> 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.
-            init_noise_instructions (List[NoiseModelInstruction]): A list of initialization noise
+            init_noise_instructions (list[NoiseModelInstruction]): A list of initialization noise
                 model instructions.
 
         Returns:
@@ -318,14 +318,14 @@ def _apply_init_noise(
     def _apply_readout_noise(
         cls,
         circuit: Circuit,
-        readout_noise_instructions: List[NoiseModelInstruction],
+        readout_noise_instructions: list[NoiseModelInstruction],
     ) -> 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.
-            readout_noise_instructions (List[NoiseModelInstruction]): The list of readout noise
+            readout_noise_instructions (list[NoiseModelInstruction]): The list of readout noise
                 to apply.
 
         Returns:
@@ -337,17 +337,17 @@ def _apply_readout_noise(
 
     @classmethod
     def _items_to_string(
-        cls, instructions_title: str, instructions: List[NoiseModelInstruction]
-    ) -> List[str]:
+        cls, instructions_title: str, instructions: list[NoiseModelInstruction]
+    ) -> list[str]:
         """
         Creates a string representation of a list of instructions.
 
         Args:
             instructions_title (str): The title for this list of instructions.
-            instructions (List[NoiseModelInstruction]): A list of instructions.
+            instructions (list[NoiseModelInstruction]): A list of instructions.
 
         Returns:
-            List[str]: A list of string representations of the passed instructions.
+            list[str]: A list of string representations of the passed instructions.
         """
         results = []
         if len(instructions) > 0:
@@ -376,14 +376,14 @@ def from_dict(cls, noise_dict: dict) -> NoiseModel:
 
 
 def _apply_noise_on_observable_result_types(
-    circuit: Circuit, readout_noise_instructions: List[NoiseModelInstruction]
+    circuit: Circuit, readout_noise_instructions: list[NoiseModelInstruction]
 ) -> Circuit:
     """Applies readout noise based on the observable result types in the circuit. Each applicable
     Noise will be applied only once to a target in the ObservableResultType.
 
     Args:
         circuit (Circuit): The circuit to apply the readout noise to.
-        readout_noise_instructions (List[NoiseModelInstruction]): The list of readout noise
+        readout_noise_instructions (list[NoiseModelInstruction]): The list of readout noise
             to apply.
 
     Returns:
diff --git a/src/braket/circuits/noise_model/observable_criteria.py b/src/braket/circuits/noise_model/observable_criteria.py
index 46c0e3e7..2275ccce 100644
--- a/src/braket/circuits/noise_model/observable_criteria.py
+++ b/src/braket/circuits/noise_model/observable_criteria.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 typing import Any, Iterable, Optional, Set, Union
+from collections.abc import Iterable
+from typing import Any, Optional, Union
 
 from braket.circuits.noise_model.criteria import Criteria, CriteriaKey, CriteriaKeyResult
 from braket.circuits.noise_model.criteria_input_parsing import (
@@ -71,14 +72,14 @@ 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) -> 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:
+            Union[CriteriaKeyResult, set[Any]]: The return value is based on the key type:
             OBSERVABLE will return a set of Observable classes that are relevant to this Criteria,
             or CriteriaKeyResult.ALL if the Criteria is relevant for all (possible) observables.
             QUBIT will return a set of qubit targets that are relevant to this Criteria, or
diff --git a/src/braket/circuits/noise_model/qubit_initialization_criteria.py b/src/braket/circuits/noise_model/qubit_initialization_criteria.py
index dade3a0e..abed13af 100644
--- a/src/braket/circuits/noise_model/qubit_initialization_criteria.py
+++ b/src/braket/circuits/noise_model/qubit_initialization_criteria.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 typing import Any, Iterable, Optional, Set, Union
+from collections.abc import Iterable
+from typing import Any, Optional, Union
 
 from braket.circuits.noise_model.criteria import Criteria, CriteriaKey, CriteriaKeyResult
 from braket.circuits.noise_model.criteria_input_parsing import parse_qubit_input
@@ -45,14 +46,14 @@ 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) -> 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:
+            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
             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/noise_model/unitary_gate_criteria.py b/src/braket/circuits/noise_model/unitary_gate_criteria.py
index 7642a143..229fc11b 100644
--- a/src/braket/circuits/noise_model/unitary_gate_criteria.py
+++ b/src/braket/circuits/noise_model/unitary_gate_criteria.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 typing import Any, Iterable, Optional, Set, Union
+from collections.abc import Iterable
+from typing import Any, Optional, Union
 
 from braket.circuits.gates import Unitary
 from braket.circuits.instruction import Instruction
@@ -53,14 +54,14 @@ 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) -> 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:
+            Union[CriteriaKeyResult, set[Any]]: The return value is based on the key type:
             UNITARY_GATE will return a set containing the bytes of the unitary matrix representing
             the unitary gate.
             QUBIT will return a set of qubit targets that are relevant to this Criteria, or
diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py
index 11dac20e..572489a9 100644
--- a/src/braket/circuits/noises.py
+++ b/src/braket/circuits/noises.py
@@ -12,7 +12,8 @@
 # language governing permissions and limitations under the License.
 
 import itertools
-from typing import Any, Dict, Iterable, List, Union
+from collections.abc import Iterable
+from typing import Any, Union
 
 import numpy as np
 
@@ -357,7 +358,7 @@ def pauli_channel(
 
         Args:
             target (QubitSetInput): Target qubit(s)
-                probability List[float]: Probabilities for the Pauli X, Y and Z noise
+                probability list[float]: Probabilities for the Pauli X, Y and Z noise
                 happening in the Kraus channel.
             probX (float): X rotation probability.
             probY (float): Y rotation probability.
@@ -863,7 +864,7 @@ class TwoQubitPauliChannel(MultiQubitPauliNoise):
     _tensor_products_strings = itertools.product(_paulis.keys(), repeat=2)
     _names_list = ["".join(x) for x in _tensor_products_strings]
 
-    def __init__(self, probabilities: Dict[str, float]):
+    def __init__(self, probabilities: dict[str, float]):
         super().__init__(
             probabilities=probabilities,
             qubit_count=None,
@@ -906,14 +907,14 @@ def fixed_qubit_count() -> int:
     @staticmethod
     @circuit.subroutine(register=True)
     def two_qubit_pauli_channel(
-        target1: QubitInput, target2: QubitInput, probabilities: Dict[str, float]
+        target1: QubitInput, target2: QubitInput, probabilities: dict[str, float]
     ) -> Iterable[Instruction]:
         """Registers this function into the circuit class.
 
         Args:
             target1 (QubitInput): Target qubit 1.
             target2 (QubitInput): Target qubit 2.
-            probabilities (Dict[str, float]): Probability of two-qubit Pauli channel.
+            probabilities (dict[str, float]): Probability of two-qubit Pauli channel.
 
         Returns:
             Iterable[Instruction]: `Iterable` of Depolarizing instructions.
@@ -1374,7 +1375,7 @@ def _to_openqasm(
         return f"#pragma braket noise kraus({matrix_list}) {qubit_list}"
 
     @staticmethod
-    def _transform_matrix_to_ir(matrices: Iterable[np.ndarray]) -> List:
+    def _transform_matrix_to_ir(matrices: Iterable[np.ndarray]) -> list:
         serializable = []
         for matrix in matrices:
             matrix_as_list = [
@@ -1439,14 +1440,14 @@ def from_dict(cls, noise: dict) -> Noise:
 
 
 def _ascii_representation(
-    noise: str, parameters: List[Union[FreeParameterExpression, float]]
+    noise: str, parameters: list[Union[FreeParameterExpression, float]]
 ) -> str:
     """
     Generates a formatted ascii representation of a noise.
 
     Args:
         noise (str): The name of the noise.
-        parameters (List[Union[FreeParameterExpression, float]]): The parameters to the noise.
+        parameters (list[Union[FreeParameterExpression, float]]): The parameters to the noise.
 
     Returns:
         str: The ascii representation of the noise.
diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py
index 6b70d338..217bd597 100644
--- a/src/braket/circuits/observable.py
+++ b/src/braket/circuits/observable.py
@@ -14,8 +14,9 @@
 from __future__ import annotations
 
 import numbers
+from collections.abc import Sequence
 from copy import deepcopy
-from typing import List, Sequence, Tuple, Union
+from typing import Union
 
 import numpy as np
 
@@ -49,7 +50,7 @@ def to_ir(
         target: QubitSet = None,
         ir_type: IRType = IRType.JAQCD,
         serialization_properties: SerializationProperties = None,
-    ) -> Union[str, List[Union[str, List[List[List[float]]]]]]:
+    ) -> Union[str, list[Union[str, list[list[list[float]]]]]]:
         """Returns the IR representation for the observable
 
         Args:
@@ -61,7 +62,7 @@ def to_ir(
                 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
+            Union[str, list[Union[str, list[list[list[float]]]]]]: The IR representation for
             the observable.
 
         Raises:
@@ -84,7 +85,7 @@ 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[Union[str, list[list[list[float]]]]]:
         """Returns the JAQCD representation of the observable."""
         raise NotImplementedError("to_jaqcd has not been implemented yet.")
 
@@ -113,10 +114,10 @@ def coefficient(self) -> int:
         return self._coef
 
     @property
-    def basis_rotation_gates(self) -> Tuple[Gate, ...]:
+    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.
+            tuple[Gate, ...]: The basis rotation gates for this observable.
         """
         raise NotImplementedError
 
@@ -210,7 +211,7 @@ def eigenvalue(self, index: int) -> float:
         return self.coefficient * self._eigenvalues[index]
 
     @property
-    def ascii_symbols(self) -> Tuple[str, ...]:
+    def ascii_symbols(self) -> tuple[str, ...]:
         return tuple(
             f"{self.coefficient if self.coefficient != 1 else ''}{ascii_symbol}"
             for ascii_symbol in self._ascii_symbols
diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py
index 9b1a4904..dc0ff078 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 Dict, List, Tuple, Union
+from typing import Union
 
 import numpy as np
 
@@ -46,7 +46,7 @@ def __init__(self):
     def _unscaled(self) -> StandardObservable:
         return H()
 
-    def _to_jaqcd(self) -> List[str]:
+    def _to_jaqcd(self) -> list[str]:
         if self.coefficient != 1:
             raise ValueError("Observable coefficients not supported with Jaqcd")
         return ["h"]
@@ -67,7 +67,7 @@ def to_matrix(self) -> np.ndarray:
         )
 
     @property
-    def basis_rotation_gates(self) -> Tuple[Gate, ...]:
+    def basis_rotation_gates(self) -> tuple[Gate, ...]:
         return tuple([Gate.Ry(-math.pi / 4)])
 
 
@@ -87,7 +87,7 @@ def __init__(self):
     def _unscaled(self) -> Observable:
         return I()
 
-    def _to_jaqcd(self) -> List[str]:
+    def _to_jaqcd(self) -> list[str]:
         if self.coefficient != 1:
             raise ValueError("Observable coefficients not supported with Jaqcd")
         return ["i"]
@@ -106,7 +106,7 @@ def to_matrix(self) -> np.ndarray:
         return self.coefficient * np.eye(2, dtype=complex)
 
     @property
-    def basis_rotation_gates(self) -> Tuple[Gate, ...]:
+    def basis_rotation_gates(self) -> tuple[Gate, ...]:
         return ()
 
     @property
@@ -137,7 +137,7 @@ def __init__(self):
     def _unscaled(self) -> StandardObservable:
         return X()
 
-    def _to_jaqcd(self) -> List[str]:
+    def _to_jaqcd(self) -> list[str]:
         if self.coefficient != 1:
             raise ValueError("Observable coefficients not supported with Jaqcd")
         return ["x"]
@@ -156,7 +156,7 @@ def to_matrix(self) -> np.ndarray:
         return self.coefficient * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex)
 
     @property
-    def basis_rotation_gates(self) -> Tuple[Gate, ...]:
+    def basis_rotation_gates(self) -> tuple[Gate, ...]:
         return tuple([Gate.H()])
 
 
@@ -176,7 +176,7 @@ def __init__(self):
     def _unscaled(self) -> StandardObservable:
         return Y()
 
-    def _to_jaqcd(self) -> List[str]:
+    def _to_jaqcd(self) -> list[str]:
         if self.coefficient != 1:
             raise ValueError("Observable coefficients not supported with Jaqcd")
         return ["y"]
@@ -195,7 +195,7 @@ def to_matrix(self) -> np.ndarray:
         return self.coefficient * np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex)
 
     @property
-    def basis_rotation_gates(self) -> Tuple[Gate, ...]:
+    def basis_rotation_gates(self) -> tuple[Gate, ...]:
         return tuple([Gate.Z(), Gate.S(), Gate.H()])
 
 
@@ -215,7 +215,7 @@ def __init__(self):
     def _unscaled(self) -> StandardObservable:
         return Z()
 
-    def _to_jaqcd(self) -> List[str]:
+    def _to_jaqcd(self) -> list[str]:
         if self.coefficient != 1:
             raise ValueError("Observable coefficients not supported with Jaqcd")
         return ["z"]
@@ -234,7 +234,7 @@ def to_matrix(self) -> np.ndarray:
         return self.coefficient * np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex)
 
     @property
-    def basis_rotation_gates(self) -> Tuple[Gate, ...]:
+    def basis_rotation_gates(self) -> tuple[Gate, ...]:
         return ()
 
 
@@ -244,10 +244,10 @@ def basis_rotation_gates(self) -> Tuple[Gate, ...]:
 class TensorProduct(Observable):
     """Tensor product of observables"""
 
-    def __init__(self, observables: List[Observable]):
+    def __init__(self, observables: list[Observable]):
         """
         Args:
-            observables (List[Observable]): List of observables for tensor product
+            observables (list[Observable]): List of observables for tensor product
 
         Examples:
             >>> t1 = Observable.Y() @ Observable.X()
@@ -295,7 +295,7 @@ def __init__(self, observables: List[Observable]):
         self._all_eigenvalues = None
 
     @property
-    def ascii_symbols(self) -> Tuple[str, ...]:
+    def ascii_symbols(self) -> tuple[str, ...]:
         return tuple(
             f"{self.coefficient if self.coefficient != 1 else ''}"
             f"{'@'.join([obs.ascii_symbols[0] for obs in self.factors])}"
@@ -307,7 +307,7 @@ def _unscaled(self) -> Observable:
         copied._coef = 1
         return copied
 
-    def _to_jaqcd(self) -> List[str]:
+    def _to_jaqcd(self) -> list[str]:
         if self.coefficient != 1:
             raise ValueError("Observable coefficients not supported with Jaqcd")
         ir = []
@@ -336,8 +336,8 @@ def _to_openqasm(
         return f"{coef_prefix}{' @ '.join(factors)}"
 
     @property
-    def factors(self) -> Tuple[Observable, ...]:
-        """Tuple[Observable]: The observables that comprise this tensor product."""
+    def factors(self) -> tuple[Observable, ...]:
+        """tuple[Observable]: The observables that comprise this tensor product."""
         return self._factors
 
     def to_matrix(self) -> np.ndarray:
@@ -346,10 +346,10 @@ def to_matrix(self) -> np.ndarray:
         )
 
     @property
-    def basis_rotation_gates(self) -> Tuple[Gate, ...]:
+    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.
+            tuple[Gate, ...]: The basis rotation gates for this observable.
         """
         gates = []
         for obs in self.factors:
@@ -404,7 +404,7 @@ def __eq__(self, other):
         return self.matrix_equivalence(other)
 
     @staticmethod
-    def _compute_eigenvalues(observables: Tuple[Observable], num_qubits: int) -> np.ndarray:
+    def _compute_eigenvalues(observables: tuple[Observable], num_qubits: int) -> np.ndarray:
         if False in [isinstance(observable, StandardObservable) for observable in observables]:
             # Tensor product of observables contains a mixture
             # of standard and non-standard observables
@@ -431,10 +431,10 @@ def _compute_eigenvalues(observables: Tuple[Observable], num_qubits: int) -> np.
 class Sum(Observable):
     """Sum of observables"""
 
-    def __init__(self, observables: List[Observable], display_name: str = "Hamiltonian"):
+    def __init__(self, observables: list[Observable], display_name: str = "Hamiltonian"):
         """
         Args:
-            observables (List[Observable]): List of observables for Sum
+            observables (list[Observable]): List of observables for Sum
             display_name (str): Name to use for an instance of this Sum
                 observable for circuit diagrams. Defaults to `Hamiltonian`.
 
@@ -465,13 +465,13 @@ def __mul__(self, other) -> Observable:
             return sum_copy
         raise TypeError("Observable coefficients must be numbers.")
 
-    def _to_jaqcd(self) -> List[str]:
+    def _to_jaqcd(self) -> list[str]:
         raise NotImplementedError("Sum Observable is not supported in Jaqcd")
 
     def _to_openqasm(
         self,
         serialization_properties: OpenQASMSerializationProperties,
-        target: List[QubitSet] = None,
+        target: list[QubitSet] = None,
     ) -> str:
         if len(self.summands) != len(target):
             raise ValueError(
@@ -493,15 +493,15 @@ def _to_openqasm(
         ).replace("+ -", "- ")
 
     @property
-    def summands(self) -> Tuple[Observable, ...]:
-        """Tuple[Observable]: The observables that comprise this sum."""
+    def summands(self) -> tuple[Observable, ...]:
+        """tuple[Observable]: The observables that comprise this sum."""
         return self._summands
 
     def to_matrix(self) -> np.ndarray:
         raise NotImplementedError("Matrix operation is not supported for Sum")
 
     @property
-    def basis_rotation_gates(self) -> Tuple[Gate, ...]:
+    def basis_rotation_gates(self) -> tuple[Gate, ...]:
         raise NotImplementedError("Basis rotation calculation not supported for Sum")
 
     @property
@@ -518,7 +518,7 @@ def __eq__(self, other):
         return repr(self) == repr(other)
 
     @staticmethod
-    def _compute_eigenvalues(observables: Tuple[Observable], num_qubits: int) -> np.ndarray:
+    def _compute_eigenvalues(observables: tuple[Observable], num_qubits: int) -> np.ndarray:
         raise NotImplementedError("Eigenvalue calculation not supported for Sum")
 
 
@@ -563,7 +563,7 @@ def __init__(self, matrix: np.ndarray, display_name: str = "Hermitian"):
     def _unscaled(self) -> Observable:
         return Hermitian(matrix=self._matrix, display_name=self.ascii_symbols[0])
 
-    def _to_jaqcd(self) -> List[List[List[List[float]]]]:
+    def _to_jaqcd(self) -> list[list[list[list[float]]]]:
         if self.coefficient != 1:
             raise ValueError("Observable coefficients not supported with Jaqcd")
         return [
@@ -598,7 +598,7 @@ def __eq__(self, other) -> bool:
         return self.matrix_equivalence(other)
 
     @property
-    def basis_rotation_gates(self) -> Tuple[Gate, ...]:
+    def basis_rotation_gates(self) -> tuple[Gate, ...]:
         return self._diagonalizing_gates
 
     @property
@@ -613,7 +613,7 @@ def eigenvalue(self, index: int) -> float:
         return self._eigenvalues[index]
 
     @staticmethod
-    def _get_eigendecomposition(matrix: np.ndarray) -> Dict[str, np.ndarray]:
+    def _get_eigendecomposition(matrix: np.ndarray) -> dict[str, np.ndarray]:
         """
         Decomposes the Hermitian matrix into its eigenvectors and associated eigenvalues.
         The eigendecomposition is cached so that if another Hermitian observable
@@ -624,7 +624,7 @@ def _get_eigendecomposition(matrix: np.ndarray) -> Dict[str, np.ndarray]:
             matrix (ndarray): The Hermitian matrix.
 
         Returns:
-            Dict[str, ndarray]: The keys are "eigenvectors_conj_t", mapping to the
+            dict[str, ndarray]: The keys are "eigenvectors_conj_t", mapping to the
             conjugate transpose of a matrix whose columns are the eigenvectors of the matrix,
             and "eigenvalues", a list of associated eigenvalues in the order of their
             corresponding eigenvectors in the "eigenvectors" matrix. These cached values
@@ -648,13 +648,13 @@ 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[Union[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.
 
     Args:
-        ir_observable (List[Union[str, List[List[List[float]]]]]): observable as defined in IR
+        ir_observable (list[Union[str, list[list[list[float]]]]]): observable as defined in IR
 
     Returns:
         Observable: observable object
@@ -666,7 +666,7 @@ def observable_from_ir(ir_observable: List[Union[str, List[List[List[float]]]]])
         return observable
 
 
-def _observable_from_ir_list_item(observable: Union[str, List[List[List[float]]]]) -> Observable:
+def _observable_from_ir_list_item(observable: Union[str, list[list[list[float]]]]) -> Observable:
     if observable == "i":
         return I()
     elif observable == "h":
diff --git a/src/braket/circuits/quantum_operator.py b/src/braket/circuits/quantum_operator.py
index 846573c6..df67bf19 100644
--- a/src/braket/circuits/quantum_operator.py
+++ b/src/braket/circuits/quantum_operator.py
@@ -13,7 +13,8 @@
 
 from __future__ import annotations
 
-from typing import Any, Optional, Sequence, Tuple
+from collections.abc import Sequence
+from typing import Any, Optional
 
 import numpy as np
 
@@ -96,8 +97,8 @@ def qubit_count(self) -> int:
         return self._qubit_count
 
     @property
-    def ascii_symbols(self) -> Tuple[str, ...]:
-        """Tuple[str, ...]: Returns the ascii symbols for the quantum operator."""
+    def ascii_symbols(self) -> tuple[str, ...]:
+        """tuple[str, ...]: Returns the ascii symbols for the quantum operator."""
         return self._ascii_symbols
 
     @property
diff --git a/src/braket/circuits/quantum_operator_helpers.py b/src/braket/circuits/quantum_operator_helpers.py
index cb6da9e8..a264d0b3 100644
--- a/src/braket/circuits/quantum_operator_helpers.py
+++ b/src/braket/circuits/quantum_operator_helpers.py
@@ -11,8 +11,8 @@
 # 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 functools import lru_cache
-from typing import Iterable
 
 import numpy as np
 
diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py
index 787d9ee0..a59d9ff3 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, Dict, List, Type, Union
+from typing import Any, Union
 
 from braket.circuits.free_parameter import FreeParameter
 from braket.circuits.observable import Observable
@@ -34,10 +34,10 @@ class ResultType:
     the metadata that defines what a requested result type is and what it does.
     """
 
-    def __init__(self, ascii_symbols: List[str]):
+    def __init__(self, ascii_symbols: list[str]):
         """
         Args:
-            ascii_symbols (List[str]): ASCII string symbols for the result type. This is used when
+            ascii_symbols (list[str]): ASCII string symbols for the result type. This is used when
                 printing a diagram of circuits.
 
         Raises:
@@ -50,8 +50,8 @@ def __init__(self, ascii_symbols: List[str]):
         self._ascii_symbols = ascii_symbols
 
     @property
-    def ascii_symbols(self) -> List[str]:
-        """List[str]: Returns the ascii symbols for the requested result type."""
+    def ascii_symbols(self) -> list[str]:
+        """list[str]: Returns the ascii symbols for the requested result type."""
         return self._ascii_symbols
 
     @property
@@ -118,7 +118,7 @@ def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties
         raise NotImplementedError("to_openqasm has not been implemented yet.")
 
     def copy(
-        self, target_mapping: Dict[QubitInput, QubitInput] = None, target: QubitSetInput = None
+        self, target_mapping: dict[QubitInput, QubitInput] = None, target: QubitSetInput = None
     ) -> ResultType:
         """
         Return a shallow copy of the result type.
@@ -128,7 +128,7 @@ def copy(
             qubits. This is useful apply an instruction to a circuit and change the target qubits.
 
         Args:
-            target_mapping (Dict[QubitInput, QubitInput]): A dictionary of
+            target_mapping (dict[QubitInput, QubitInput]): A dictionary of
                 qubit mappings to apply to the target. Key is the qubit in this `target` and the
                 value is what the key is changed to. Default = `None`.
             target (QubitSetInput): Target qubits for the new instruction.
@@ -162,11 +162,11 @@ def copy(
         return copy
 
     @classmethod
-    def register_result_type(cls, result_type: Type[ResultType]) -> None:
+    def register_result_type(cls, result_type: type[ResultType]) -> None:
         """Register a result type implementation by adding it into the `ResultType` class.
 
         Args:
-            result_type (Type[ResultType]): `ResultType` class to register.
+            result_type (type[ResultType]): `ResultType` class to register.
         """
         setattr(cls, result_type.__name__, result_type)
 
@@ -188,11 +188,11 @@ class ObservableResultType(ResultType):
     """
 
     def __init__(
-        self, ascii_symbols: List[str], observable: Observable, target: QubitSetInput = None
+        self, ascii_symbols: list[str], observable: Observable, target: QubitSetInput = None
     ):
         """
         Args:
-            ascii_symbols (List[str]): ASCII string symbols for the result type. This is used when
+            ascii_symbols (list[str]): ASCII string symbols for the result type. This is used when
                 printing a diagram of circuits.
             observable (Observable): the observable for the result type
             target (QubitSetInput): Target qubits that the
@@ -285,10 +285,10 @@ class ObservableParameterResultType(ObservableResultType):
 
     def __init__(
         self,
-        ascii_symbols: List[str],
+        ascii_symbols: list[str],
         observable: Observable,
         target: QubitSetInput = None,
-        parameters: List[Union[str, FreeParameter]] = None,
+        parameters: list[Union[str, FreeParameter]] = None,
     ):
         super().__init__(ascii_symbols, observable, target)
 
@@ -300,13 +300,13 @@ def __init__(
 
         """
         Args:
-            ascii_symbols (List[str]): ASCII string symbols for the result type. This is used when
+            ascii_symbols (list[str]): ASCII string symbols for the result type. This is used when
                 printing a diagram of circuits.
             observable (Observable): the observable for the result type.
             target (QubitSetInput): Target qubits that the result type is requested for.
                 Default is `None`, which means the observable must only operate on 1
                 qubit and it will be applied to all qubits in parallel.
-            parameters (List[Union[str, FreeParameter]]): List of string inputs or
+            parameters (list[Union[str, FreeParameter]]): List of string inputs or
                 FreeParameter objects. These inputs will be used as parameters for
                 gradient calculation. Default: `all`.
 
@@ -318,7 +318,7 @@ def __init__(
         """
 
     @property
-    def parameters(self) -> List[str]:
+    def parameters(self) -> list[str]:
         return self._parameters
 
     def __repr__(self) -> str:
diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py
index 25ff6371..cfe21616 100644
--- a/src/braket/circuits/result_types.py
+++ b/src/braket/circuits/result_types.py
@@ -15,7 +15,7 @@
 
 import re
 from functools import reduce
-from typing import List, Union
+from typing import Union
 
 import braket.ir.jaqcd as ir
 from braket.circuits import circuit
@@ -178,19 +178,19 @@ class AdjointGradient(ObservableParameterResultType):
     def __init__(
         self,
         observable: Observable,
-        target: List[QubitSetInput] = None,
-        parameters: List[Union[str, FreeParameter]] = None,
+        target: list[QubitSetInput] = None,
+        parameters: list[Union[str, FreeParameter]] = None,
     ):
         """
         Args:
             observable (Observable): The expectation value of this observable is the function
                 against which parameters in the gradient are differentiated.
-            target (List[QubitSetInput]): Target qubits that the result type is requested for.
+            target (list[QubitSetInput]): Target qubits that the result type is requested for.
                 Each term in the target list should have the same number of qubits as the
                 corresponding term in the observable. Default is `None`, which means the
                 observable must operate only on 1 qubit and it is applied to all qubits
                 in parallel.
-            parameters (List[Union[str, FreeParameter]]): The free parameters in the circuit to
+            parameters (list[Union[str, FreeParameter]]): The free parameters in the circuit to
                 differentiate with respect to. Default: `all`.
 
         Raises:
@@ -240,20 +240,20 @@ def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties
     @circuit.subroutine(register=True)
     def adjoint_gradient(
         observable: Observable,
-        target: List[QubitSetInput] = None,
-        parameters: List[Union[str, FreeParameter]] = None,
+        target: list[QubitSetInput] = None,
+        parameters: list[Union[str, FreeParameter]] = None,
     ) -> ResultType:
         """Registers this function into the circuit class.
 
         Args:
             observable (Observable): The expectation value of this observable is the function
                 against which parameters in the gradient are differentiated.
-            target (List[QubitSetInput]): Target qubits that the result type is requested for.
+            target (list[QubitSetInput]): Target qubits that the result type is requested for.
                 Each term in the target list should have the same number of qubits as the
                 corresponding term in the observable. Default is `None`, which means the
                 observable must operate only on 1 qubit and it is applied to all qubits
                 in parallel.
-            parameters (List[Union[str, FreeParameter]]): The free parameters in the circuit to
+            parameters (list[Union[str, FreeParameter]]): The free parameters in the circuit to
                 differentiate with respect to. Default: `all`.
 
         Returns:
@@ -279,10 +279,10 @@ class Amplitude(ResultType):
     This is available on simulators only when `shots=0`.
     """
 
-    def __init__(self, state: List[str]):
+    def __init__(self, state: list[str]):
         """
         Args:
-            state (List[str]): list of quantum states as strings with "0" and "1"
+            state (list[str]): list of quantum states as strings with "0" and "1"
 
         Raises:
             ValueError: If state is `None` or an empty list, or
@@ -293,7 +293,7 @@ def __init__(self, state: List[str]):
         """
         if (
             not state
-            or not isinstance(state, List)
+            or not isinstance(state, list)
             or not all(
                 isinstance(amplitude, str) and re.fullmatch("^[01]+$", amplitude)
                 for amplitude in state
@@ -306,7 +306,7 @@ def __init__(self, state: List[str]):
         self._state = state
 
     @property
-    def state(self) -> List[str]:
+    def state(self) -> list[str]:
         return self._state
 
     def _to_jaqcd(self) -> ir.Amplitude:
@@ -318,11 +318,11 @@ def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties
 
     @staticmethod
     @circuit.subroutine(register=True)
-    def amplitude(state: List[str]) -> ResultType:
+    def amplitude(state: list[str]) -> ResultType:
         """Registers this function into the circuit class.
 
         Args:
-            state (List[str]): list of quantum states as strings with "0" and "1"
+            state (list[str]): list of quantum states as strings with "0" and "1"
 
         Returns:
             ResultType: state vector as a requested result type
diff --git a/src/braket/circuits/unitary_calculation.py b/src/braket/circuits/unitary_calculation.py
index 2059976e..d51a378c 100644
--- a/src/braket/circuits/unitary_calculation.py
+++ b/src/braket/circuits/unitary_calculation.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 Iterable
+from collections.abc import Iterable
 
 import numpy as np
 from scipy.linalg import fractional_matrix_power
diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py
index 6ecb2ecc..49f510ed 100644
--- a/src/braket/devices/device.py
+++ b/src/braket/devices/device.py
@@ -12,7 +12,7 @@
 # language governing permissions and limitations under the License.
 
 from abc import ABC, abstractmethod
-from typing import Dict, List, Optional, Union
+from typing import Optional, Union
 
 from braket.annealing.problem import Problem
 from braket.circuits import Circuit
@@ -37,7 +37,7 @@ def run(
         self,
         task_specification: Union[Circuit, Problem],
         shots: Optional[int],
-        inputs: Optional[Dict[str, float]],
+        inputs: Optional[dict[str, float]],
         *args,
         **kwargs
     ) -> QuantumTask:
@@ -49,7 +49,7 @@ def run(
                 to run on device.
             shots (Optional[int]): The number of times to run the quantum task on the device.
                 Default is `None`.
-            inputs (Optional[Dict[str, float]]): Inputs to be passed along with the
+            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: {}.
 
@@ -62,24 +62,24 @@ def run_batch(
         self,
         task_specifications: Union[
             Union[Circuit, Problem],
-            List[Union[Circuit, Problem]],
+            list[Union[Circuit, Problem]],
         ],
         shots: Optional[int],
         max_parallel: Optional[int],
-        inputs: Optional[Union[Dict[str, float], List[Dict[str, float]]]],
+        inputs: Optional[Union[dict[str, float], list[dict[str, float]]]],
         *args,
         **kwargs
     ) -> QuantumTaskBatch:
         """Executes a batch of quantum tasks in parallel
 
         Args:
-            task_specifications (Union[Union[Circuit, Problem], List[Union[Circuit, Problem]]]):
+            task_specifications (Union[Union[Circuit, Problem], list[Union[Circuit, Problem]]]):
                 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[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.
 
diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py
index a4462ab6..c719978a 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 Dict, List, Optional, Set, Union
+from typing import Optional, Union
 
 import pkg_resources
 
@@ -68,7 +68,7 @@ def run(
         self,
         task_specification: Union[Circuit, Problem, Program, AnalogHamiltonianSimulation],
         shots: int = 0,
-        inputs: Optional[Dict[str, float]] = None,
+        inputs: Optional[dict[str, float]] = None,
         *args,
         **kwargs,
     ) -> LocalQuantumTask:
@@ -81,7 +81,7 @@ def run(
                 Default is 0, which means that the simulator will compute the exact
                 results based on the quantum task specification.
                 Sampling is not supported for shots=0.
-            inputs (Optional[Dict[str, float]]): Inputs to be passed along with the
+            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: {}.
 
@@ -105,24 +105,24 @@ def run_batch(
         self,
         task_specifications: Union[
             Union[Circuit, Problem, Program, AnalogHamiltonianSimulation],
-            List[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]],
+            list[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]],
         ],
         shots: Optional[int] = 0,
         max_parallel: Optional[int] = None,
-        inputs: Optional[Union[Dict[str, float], List[Dict[str, float]]]] = None,
+        inputs: Optional[Union[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, Program, AnalogHamiltonianSimulation], List[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]]]): # noqa
+            task_specifications (Union[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], list[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]]]): # noqa
                 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 CPU.
-            inputs (Optional[Union[Dict[str, float], List[Dict[str, float]]]]): Inputs to be passed
+            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: {}.
 
@@ -188,11 +188,11 @@ def properties(self) -> DeviceCapabilities:
         return self._delegate.properties
 
     @staticmethod
-    def registered_backends() -> Set[str]:
+    def registered_backends() -> set[str]:
         """Gets the backends that have been registered as entry points
 
         Returns:
-            Set[str]: The names of the available backends that can be passed
+            set[str]: The names of the available backends that can be passed
             into LocalSimulator's constructor
         """
         return set(_simulator_devices.keys())
@@ -201,7 +201,7 @@ def _run_internal_wrap(
         self,
         task_specification: Union[Circuit, Problem, Program, AnalogHamiltonianSimulation],
         shots: Optional[int] = None,
-        inputs: Optional[Dict[str, float]] = None,
+        inputs: Optional[dict[str, float]] = None,
         *args,
         **kwargs,
     ) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]:  # pragma: no cover
@@ -243,7 +243,7 @@ def _(
         self,
         circuit: Circuit,
         shots: Optional[int] = None,
-        inputs: Optional[Dict[str, float]] = None,
+        inputs: Optional[dict[str, float]] = None,
         *args,
         **kwargs,
     ):
@@ -278,7 +278,7 @@ def _(
         self,
         program: Program,
         shots: Optional[int] = None,
-        inputs: Optional[Dict[str, float]] = None,
+        inputs: Optional[dict[str, float]] = None,
         *args,
         **kwargs,
     ):
diff --git a/src/braket/error_mitigation/debias.py b/src/braket/error_mitigation/debias.py
index 154094a0..305bf7b7 100644
--- a/src/braket/error_mitigation/debias.py
+++ b/src/braket/error_mitigation/debias.py
@@ -11,8 +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
-
 from braket.device_schema import error_mitigation
 from braket.error_mitigation.error_mitigation import ErrorMitigation
 
@@ -22,5 +20,5 @@ class Debias(ErrorMitigation):
     The debias error mitigation scheme. This scheme takes no parameters.
     """
 
-    def serialize(self) -> List[error_mitigation.Debias]:
+    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 c03fbf6d..79b1f3e3 100644
--- a/src/braket/error_mitigation/error_mitigation.py
+++ b/src/braket/error_mitigation/error_mitigation.py
@@ -11,16 +11,14 @@
 # ANY KIND, either express or implied. See the License for the specific
 # language governing permissions and limitations under the License.
 
-from typing import List
-
 from braket.device_schema import error_mitigation
 
 
 class ErrorMitigation:
-    def serialize(self) -> List[error_mitigation.ErrorMitigationScheme]:
+    def serialize(self) -> list[error_mitigation.ErrorMitigationScheme]:
         """
         Returns:
-            List[ErrorMitigationScheme]: A list of service-readable error
+            list[ErrorMitigationScheme]: A list of service-readable error
             mitigation scheme descriptions.
         """
         raise NotImplementedError("serialize is not implemented.")
diff --git a/src/braket/jobs/data_persistence.py b/src/braket/jobs/data_persistence.py
index aa574d0d..10806812 100644
--- a/src/braket/jobs/data_persistence.py
+++ b/src/braket/jobs/data_persistence.py
@@ -126,7 +126,7 @@ def load_job_result(filename: Union[str, Path] = None) -> Dict[str, Any]:
             must be in the format used by `save_job_result`.
 
     Returns:
-         Dict[str, Any]: Job result data of current job
+        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)
diff --git a/src/braket/parametric/free_parameter.py b/src/braket/parametric/free_parameter.py
index 3eed47ef..db22f5f6 100644
--- a/src/braket/parametric/free_parameter.py
+++ b/src/braket/parametric/free_parameter.py
@@ -14,7 +14,7 @@
 from __future__ import annotations
 
 from numbers import Number
-from typing import Dict, Union
+from typing import Union
 
 from sympy import Symbol
 
@@ -59,12 +59,12 @@ def name(self) -> str:
         """
         return self._name.name
 
-    def subs(self, parameter_values: Dict[str, Number]) -> Union[FreeParameter, Number]:
+    def subs(self, parameter_values: dict[str, Number]) -> Union[FreeParameter, Number]:
         """
         Substitutes a value in if the parameter exists within the mapping.
 
         Args:
-            parameter_values (Dict[str, Number]): A mapping of parameter to its
+            parameter_values (dict[str, Number]): A mapping of parameter to its
                 corresponding value.
 
         Returns:
diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py
index 1b64e2dd..cd5fd7f8 100644
--- a/src/braket/parametric/free_parameter_expression.py
+++ b/src/braket/parametric/free_parameter_expression.py
@@ -15,7 +15,7 @@
 
 import ast
 from numbers import Number
-from typing import Any, Dict, Union
+from typing import Any, Union
 
 from sympy import Expr, Float, Symbol, sympify
 
@@ -69,14 +69,14 @@ def expression(self) -> Union[Number, Expr]:
         return self._expression
 
     def subs(
-        self, parameter_values: Dict[str, Number]
+        self, parameter_values: dict[str, Number]
     ) -> Union[FreeParameterExpression, Number, Expr]:
         """
         Similar to a substitution in Sympy. Parameters are swapped for corresponding values or
         expressions from the dictionary.
 
         Args:
-            parameter_values (Dict[str, Number]): A mapping of parameters to their corresponding
+            parameter_values (dict[str, Number]): A mapping of parameters to their corresponding
                 values to be assigned.
 
         Returns:
diff --git a/src/braket/parametric/parameterizable.py b/src/braket/parametric/parameterizable.py
index 45c7561f..bd5dbc5a 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, List, Union
+from typing import Any, Union
 
 from braket.parametric.free_parameter import FreeParameter
 from braket.parametric.free_parameter_expression import FreeParameterExpression
@@ -28,11 +28,11 @@ class Parameterizable(ABC):
 
     @property
     @abstractmethod
-    def parameters(self) -> List[Union[FreeParameterExpression, FreeParameter, float]]:
+    def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float]]:
         """Get the parameters.
 
         Returns:
-            List[Union[FreeParameterExpression, FreeParameter, float]]: The parameters associated
+            list[Union[FreeParameterExpression, FreeParameter, float]]: The parameters associated
             with the object, either unbound free parameter expressions or bound values. The order
             of the parameters is determined by the subclass.
         """
diff --git a/src/braket/pulse/ast/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py
index 69e9fafa..4398d281 100644
--- a/src/braket/pulse/ast/approximation_parser.py
+++ b/src/braket/pulse/ast/approximation_parser.py
@@ -10,10 +10,12 @@
 # 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 collections import defaultdict
+from collections.abc import KeysView
 from dataclasses import dataclass
-from typing import Any, Dict, KeysView, List, Optional, Union
+from typing import Any, Optional, Union
 
 import numpy as np
 from openpulse import ast
@@ -43,7 +45,7 @@ class _FrameState:
 @dataclass
 class _ParseState:
     variables: dict
-    frame_data: Dict[str, _FrameState]
+    frame_data: dict[str, _FrameState]
 
 
 class _ApproximationParser(QASMVisitor[_ParseState]):
@@ -52,12 +54,12 @@ class _ApproximationParser(QASMVisitor[_ParseState]):
 
     TIME_UNIT_TO_EXP = {"dt": 4, "ns": 3, "us": 2, "ms": 1, "s": 0}
 
-    def __init__(self, program: Program, frames: Dict[str, Frame]):
+    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))
-        self._qubit_frames_mapping: Dict[str, List[str]] = _init_qubit_frame_mapping(frames)
+        self._qubit_frames_mapping: dict[str, list[str]] = _init_qubit_frame_mapping(frames)
         self.visit(program.to_ast(include_externs=False), context)
 
     def visit(
@@ -73,8 +75,8 @@ def visit(
         return super().visit(node, context)
 
     def _get_frame_parameters(
-        self, parameters: List[ast.Expression], context: _ParseState
-    ) -> Union[KeysView, List[str]]:
+        self, parameters: list[ast.Expression], context: _ParseState
+    ) -> Union[KeysView, list[str]]:
         frame_ids = set()
         for expression in parameters:
             identifier_name = self.visit(expression, context)
@@ -466,7 +468,7 @@ def drag_gaussian(self, node: ast.FunctionCall, context: _ParseState) -> Wavefor
         return DragGaussianWaveform(*args)
 
 
-def _init_frame_data(frames: Dict[str, Frame]) -> Dict[str, _FrameState]:
+def _init_frame_data(frames: dict[str, Frame]) -> dict[str, _FrameState]:
     frame_states = dict()
     for frameId, frame in frames.items():
         frame_states[frameId] = _FrameState(
@@ -475,7 +477,7 @@ def _init_frame_data(frames: Dict[str, Frame]) -> Dict[str, _FrameState]:
     return frame_states
 
 
-def _init_qubit_frame_mapping(frames: Dict[str, Frame]) -> Dict[str, List[str]]:
+def _init_qubit_frame_mapping(frames: dict[str, Frame]) -> dict[str, list[str]]:
     mapping = {}
     for frameId in frames.keys():
         if m := (
@@ -489,7 +491,7 @@ def _init_qubit_frame_mapping(frames: Dict[str, Frame]) -> Dict[str, List[str]]:
     return mapping
 
 
-def _lcm_floats(*dts: List[float]) -> float:
+def _lcm_floats(*dts: list[float]) -> float:
     """Return the least common multiple of time increments of a list of frames
         A time increment is the inverse of the corresponding sample rate which is considered
         an integer.
@@ -497,7 +499,7 @@ def _lcm_floats(*dts: List[float]) -> float:
         Hence the LCM of dts is 1/gcd([sample rates])
 
     Args:
-        *dts (List[float]): list of time resolutions
+        *dts (list[float]): list of time resolutions
     """
 
     sample_rates = [round(1 / dt) for dt in dts]
diff --git a/src/braket/pulse/ast/free_parameters.py b/src/braket/pulse/ast/free_parameters.py
index 6cfc36d0..1581ddd8 100644
--- a/src/braket/pulse/ast/free_parameters.py
+++ b/src/braket/pulse/ast/free_parameters.py
@@ -10,7 +10,8 @@
 # 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 typing import Dict, Union
+
+from typing import Union
 
 from openpulse import ast
 from openqasm3.ast import DurationLiteral
@@ -34,7 +35,7 @@ def expression(self) -> FreeParameterExpression:
 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]):
         self.param_values = param_values
         super().__init__()
 
diff --git a/src/braket/pulse/ast/qasm_parser.py b/src/braket/pulse/ast/qasm_parser.py
index 8e6f94de..bd1b26e4 100644
--- a/src/braket/pulse/ast/qasm_parser.py
+++ b/src/braket/pulse/ast/qasm_parser.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
 
 from openpulse import ast
diff --git a/src/braket/pulse/ast/qasm_transformer.py b/src/braket/pulse/ast/qasm_transformer.py
index ae4cccad..f5e35088 100644
--- a/src/braket/pulse/ast/qasm_transformer.py
+++ b/src/braket/pulse/ast/qasm_transformer.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 typing import Any, Optional
 
 from openpulse import ast
diff --git a/src/braket/pulse/frame.py b/src/braket/pulse/frame.py
index aab243a4..b84e4a44 100644
--- a/src/braket/pulse/frame.py
+++ b/src/braket/pulse/frame.py
@@ -12,7 +12,7 @@
 # language governing permissions and limitations under the License.
 
 import math
-from typing import Any, Dict, Optional
+from typing import Any, Optional
 
 from oqpy import FrameVar as OQFrame
 from oqpy.base import OQPyExpression
@@ -33,7 +33,7 @@ def __init__(
         frequency: float,
         phase: float = 0,
         is_predefined: bool = False,
-        properties: Optional[Dict[str, Any]] = None,
+        properties: Optional[dict[str, Any]] = None,
     ):
         """
         Args:
@@ -43,7 +43,7 @@ def __init__(
             phase (float): phase to which this frame should be initialized. Defaults to 0.
             is_predefined (bool): bool indicating whether this is a predefined frame on
                 the device. Defaults to False.
-            properties (Optional[Dict[str, Any]]): Dict containing properties of this frame.
+            properties (Optional[dict[str, Any]]): Dict containing properties of this frame.
                 Defaults to None.
         """
         self._frame_id = frame_id
diff --git a/src/braket/pulse/port.py b/src/braket/pulse/port.py
index 37383687..2b176041 100644
--- a/src/braket/pulse/port.py
+++ b/src/braket/pulse/port.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, Dict, Optional
+from typing import Any, Optional
 
 from oqpy import PortVar
 from oqpy.base import OQPyExpression
@@ -23,12 +23,12 @@ 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: Optional[dict[str, Any]] = None):
         """
         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.
-            properties (Optional[Dict[str, Any]]): Dict containing properties of
+            properties (Optional[dict[str, Any]]): Dict containing properties of
                 this port. Defaults to None.
         """
         self._port_id = port_id
diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py
index 0a3dba15..e0808316 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, Dict, List, Set, Union
+from typing import Any, Union
 
 from openpulse import ast
 from oqpy import BitVar, PhysicalQubits, Program
@@ -67,7 +67,7 @@ def to_time_trace(self) -> PulseSequenceTrace:
         )
 
     @property
-    def parameters(self) -> Set[FreeParameter]:
+    def parameters(self) -> set[FreeParameter]:
         """Returns the set of `FreeParameter` s in the PulseSequence."""
         return self._free_parameters.copy()
 
@@ -169,14 +169,14 @@ def set_scale(
 
     def delay(
         self,
-        qubits_or_frames: Union[Frame, List[Frame], QubitSet],
+        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.
 
         Args:
-            qubits_or_frames (Union[Frame, List[Frame], QubitSet]): Qubits or frame(s) on which
+            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.
@@ -199,13 +199,13 @@ 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: Union[list[Frame], QubitSet]) -> PulseSequence:
         """
         Adds an instruction to align the frame clocks to the latest time across all the specified
         frames.
 
         Args:
-            qubits_or_frames (Union[List[Frame], QubitSet]): Qubits or frames which the delay
+            qubits_or_frames (Union[list[Frame], QubitSet]): Qubits or frames which the delay
                 needs to be introduced.
 
         Returns:
@@ -261,13 +261,13 @@ def capture_v0(self, frame: Frame) -> PulseSequence:
         self._frames[frame.id] = frame
         return self
 
-    def make_bound_pulse_sequence(self, param_values: Dict[str, float]) -> PulseSequence:
+    def make_bound_pulse_sequence(self, param_values: dict[str, float]) -> PulseSequence:
         """
         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:
-            param_values (Dict[str, float]):  A mapping of FreeParameter names
+            param_values (dict[str, float]):  A mapping of FreeParameter names
                 to a value to assign to them.
 
         Returns:
@@ -334,7 +334,7 @@ def _format_parameter_ast(
         return parameter
 
     def _parse_arg_from_calibration_schema(
-        self, argument: Dict, waveforms: Dict[Waveform], frames: Dict[Frame]
+        self, argument: dict, waveforms: dict[Waveform], frames: dict[Frame]
     ) -> Any:
         nonprimitive_arg_type = {
             "frame": getattr(frames, "get"),
@@ -348,15 +348,15 @@ def _parse_arg_from_calibration_schema(
 
     @classmethod
     def _parse_from_calibration_schema(
-        cls, calibration: Dict, waveforms: Dict[Waveform], frames: Dict[Frame]
+        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.
 
         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.
+            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.
 
         Returns:
             PulseSequence: The parse sequence obtain from parsing a pulse instruction.
@@ -427,7 +427,7 @@ def __eq__(self, other):
 
 
 def _validate_uniqueness(
-    mapping: Dict[str, Any], values: Union[Frame, Waveform, List[Frame], List[Waveform]]
+    mapping: dict[str, Any], values: Union[Frame, Waveform, list[Frame], list[Waveform]]
 ) -> None:
     if not isinstance(values, list):
         values = [values]
diff --git a/src/braket/pulse/pulse_sequence_trace.py b/src/braket/pulse/pulse_sequence_trace.py
index 9b4f5593..dba4762b 100644
--- a/src/braket/pulse/pulse_sequence_trace.py
+++ b/src/braket/pulse/pulse_sequence_trace.py
@@ -14,7 +14,6 @@
 from __future__ import annotations
 
 from dataclasses import dataclass
-from typing import Dict
 
 from braket.timings.time_series import TimeSeries
 
@@ -34,6 +33,6 @@ class PulseSequenceTrace:
             the waveform phase.
     """
 
-    amplitudes: Dict[str, TimeSeries]
-    frequencies: Dict[str, TimeSeries]
-    phases: Dict[str, TimeSeries]
+    amplitudes: dict[str, TimeSeries]
+    frequencies: dict[str, TimeSeries]
+    phases: dict[str, TimeSeries]
diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py
index f298dd3e..dbf89e14 100644
--- a/src/braket/pulse/waveforms.py
+++ b/src/braket/pulse/waveforms.py
@@ -16,7 +16,7 @@
 import random
 import string
 from abc import ABC, abstractmethod
-from typing import Dict, List, Optional, Union
+from typing import Optional, Union
 
 import numpy as np
 from oqpy import WaveformVar, bool_, complex128, declare_waveform_generator, duration, float64
@@ -57,12 +57,12 @@ def sample(self, dt: float) -> np.ndarray:
 
     @staticmethod
     @abstractmethod
-    def _from_calibration_schema(waveform_json: Dict) -> Waveform:
+    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
 
         Args:
-            waveform_json (Dict): A JSON object with the needed parameters for making the Waveform.
+            waveform_json (dict): A JSON object with the needed parameters for making the Waveform.
 
         Returns:
             Waveform: A Waveform object parsed from the supplied JSON.
@@ -73,10 +73,10 @@ class ArbitraryWaveform(Waveform):
     """An arbitrary waveform with amplitudes at each timestep explicitly specified using
     an array."""
 
-    def __init__(self, amplitudes: List[complex], id: Optional[str] = None):
+    def __init__(self, amplitudes: list[complex], id: Optional[str] = None):
         """
         Args:
-            amplitudes (List[complex]): Array of complex values specifying the
+            amplitudes (list[complex]): Array of complex values specifying the
                 waveform amplitude at each timestep. The timestep is determined by the sampling rate
                 of the frame to which waveform is applied to.
             id (Optional[str]): The identifier used for declaring this waveform. A random string of
@@ -108,7 +108,7 @@ def sample(self, dt: float) -> np.ndarray:
         raise NotImplementedError
 
     @staticmethod
-    def _from_calibration_schema(waveform_json: Dict) -> ArbitraryWaveform:
+    def _from_calibration_schema(waveform_json: dict) -> ArbitraryWaveform:
         wave_id = waveform_json["waveformId"]
         complex_amplitudes = [complex(i[0], i[1]) for i in waveform_json["amplitudes"]]
         return ArbitraryWaveform(complex_amplitudes, wave_id)
@@ -134,7 +134,7 @@ def __init__(
         self.id = id or _make_identifier_name()
 
     @property
-    def parameters(self) -> List[Union[FreeParameterExpression, FreeParameter, float]]:
+    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]
@@ -186,7 +186,7 @@ def sample(self, dt: float) -> np.ndarray:
         return samples
 
     @staticmethod
-    def _from_calibration_schema(waveform_json: Dict) -> ConstantWaveform:
+    def _from_calibration_schema(waveform_json: dict) -> ConstantWaveform:
         wave_id = waveform_json["waveformId"]
         length = iq = None
         for val in waveform_json["arguments"]:
@@ -239,7 +239,7 @@ def __init__(
         self.id = id or _make_identifier_name()
 
     @property
-    def parameters(self) -> List[Union[FreeParameterExpression, FreeParameter, float]]:
+    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.sigma, self.beta, self.amplitude]
@@ -321,7 +321,7 @@ def sample(self, dt: float) -> np.ndarray:
         return samples
 
     @staticmethod
-    def _from_calibration_schema(waveform_json: Dict) -> DragGaussianWaveform:
+    def _from_calibration_schema(waveform_json: dict) -> DragGaussianWaveform:
         waveform_parameters = {"id": waveform_json["waveformId"]}
         for val in waveform_json["arguments"]:
             waveform_parameters[val["name"]] = (
@@ -363,7 +363,7 @@ def __init__(
         self.id = id or _make_identifier_name()
 
     @property
-    def parameters(self) -> List[Union[FreeParameterExpression, FreeParameter, float]]:
+    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.sigma, self.amplitude]
@@ -437,7 +437,7 @@ def sample(self, dt: float) -> np.ndarray:
         return samples
 
     @staticmethod
-    def _from_calibration_schema(waveform_json: Dict) -> GaussianWaveform:
+    def _from_calibration_schema(waveform_json: dict) -> GaussianWaveform:
         waveform_parameters = {"id": waveform_json["waveformId"]}
         for val in waveform_json["arguments"]:
             waveform_parameters[val["name"]] = (
@@ -464,7 +464,7 @@ def _map_to_oqpy_type(
     return parameter
 
 
-def _parse_waveform_from_calibration_schema(waveform: Dict) -> Waveform:
+def _parse_waveform_from_calibration_schema(waveform: dict) -> Waveform:
     waveform_names = {
         "arbitrary": ArbitraryWaveform._from_calibration_schema,
         "drag_gaussian": DragGaussianWaveform._from_calibration_schema,
diff --git a/src/braket/quantum_information/pauli_string.py b/src/braket/quantum_information/pauli_string.py
index 1f145449..8dca0662 100644
--- a/src/braket/quantum_information/pauli_string.py
+++ b/src/braket/quantum_information/pauli_string.py
@@ -14,7 +14,7 @@
 from __future__ import annotations
 
 import itertools
-from typing import List, Optional, Tuple, Union
+from typing import Optional, Union
 
 from braket.circuits.circuit import Circuit
 from braket.circuits.observables import TensorProduct, X, Y, Z
@@ -86,7 +86,7 @@ def to_unsigned_observable(self) -> TensorProduct:
             [_PAULI_OBSERVABLES[self._nontrivial[qubit]] for qubit in sorted(self._nontrivial)]
         )
 
-    def weight_n_substrings(self, weight: int) -> Tuple[PauliString, ...]:
+    def weight_n_substrings(self, weight: int) -> tuple[PauliString, ...]:
         r"""Returns every substring of this Pauli string with exactly `weight` nontrivial factors.
 
         The number of substrings is equal to :math:`\binom{n}{w}`, where :math`n` is the number of
@@ -96,7 +96,7 @@ def weight_n_substrings(self, weight: int) -> Tuple[PauliString, ...]:
             weight (int): The number of non-identity factors in the substrings.
 
         Returns:
-            Tuple[PauliString, ...]: A tuple of weight-n Pauli substrings.
+            tuple[PauliString, ...]: A tuple of weight-n Pauli substrings.
         """
         substrings = []
         for indices in itertools.combinations(self._nontrivial, weight):
@@ -111,7 +111,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: Optional[Union[str, list[int], tuple[int, ...]]] = 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
@@ -120,7 +120,7 @@ def eigenstate(self, signs: Optional[Union[str, List[int], Tuple[int, ...]]] = N
         phase of the Pauli string is ignored).
 
         Args:
-            signs (Optional[Union[str, List[int], Tuple[int, ...]]]): The sign of each factor of the
+            signs (Optional[Union[str, list[int], tuple[int, ...]]]): The sign of each factor of the
                 eigenstate, specified either as a string of "+" and "_", or as a list or tuple of
                 +/-1. The length of signs must be equal to the length of the Pauli string. If not
                 specified, it is assumed to be all +. Default: None.
@@ -350,7 +350,7 @@ def __repr__(self):
         return f"{PauliString._phase_to_str(self._phase)}{''.join(factors)}"
 
     @staticmethod
-    def _split(pauli_word: str) -> Tuple[int, str]:
+    def _split(pauli_word: str) -> tuple[int, str]:
         index = 0
         phase = 1
         if pauli_word[index] in {"+", "-"}:
@@ -367,7 +367,7 @@ def _split(pauli_word: str) -> Tuple[int, str]:
     def _phase_to_str(phase: int) -> str:
         return "+" if phase > 0 else "-"
 
-    def _generate_eigenstate_circuit(self, signs: Tuple[int, ...]) -> Circuit:
+    def _generate_eigenstate_circuit(self, signs: tuple[int, ...]) -> Circuit:
         circ = Circuit()
         for qubit in range(len(signs)):
             state = signs[qubit] * self[qubit]
diff --git a/src/braket/registers/qubit_set.py b/src/braket/registers/qubit_set.py
index ea012bc2..938d7fb5 100644
--- a/src/braket/registers/qubit_set.py
+++ b/src/braket/registers/qubit_set.py
@@ -13,7 +13,8 @@
 
 from __future__ import annotations
 
-from typing import Any, Dict, Iterable, Union
+from collections.abc import Iterable
+from typing import Any, Union
 
 from boltons.setutils import IndexedSet
 
@@ -65,13 +66,13 @@ def __init__(self, qubits: QubitSetInput = None):
         _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:
+    def map(self, mapping: dict[QubitInput, QubitInput]) -> QubitSet:
         """
         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:
-            mapping (Dict[QubitInput, QubitInput]): A dictionary of qubit mappings to
+            mapping (dict[QubitInput, QubitInput]): A dictionary of qubit mappings to
                 apply. Key is the qubit in this instance to target, and the value is what
                 the key will be changed to.
 
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 74c0e848..abc39753 100644
--- a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py
+++ b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py
@@ -16,7 +16,6 @@
 from collections import Counter
 from dataclasses import dataclass
 from enum import Enum
-from typing import Dict, List
 
 import numpy as np
 
@@ -53,7 +52,7 @@ def __eq__(self, other) -> bool:
 class AnalogHamiltonianSimulationQuantumTaskResult:
     task_metadata: TaskMetadata
     additional_metadata: AdditionalMetadata
-    measurements: List[ShotResult] = None
+    measurements: list[ShotResult] = None
 
     def __eq__(self, other) -> bool:
         if isinstance(other, AnalogHamiltonianSimulationQuantumTaskResult):
@@ -90,7 +89,7 @@ def _from_object_internal(
         )
 
     @classmethod
-    def _get_measurements(cls, result: AnalogHamiltonianSimulationTaskResult) -> List[ShotResult]:
+    def _get_measurements(cls, result: AnalogHamiltonianSimulationTaskResult) -> list[ShotResult]:
         measurements = []
         for measurement in result.measurements:
             status = AnalogHamiltonianSimulationShotStatus(measurement.shotMetadata.shotStatus)
@@ -105,7 +104,7 @@ def _get_measurements(cls, result: AnalogHamiltonianSimulationTaskResult) -> Lis
             measurements.append(ShotResult(status, pre_sequence, post_sequence))
         return measurements
 
-    def get_counts(self) -> Dict[str, int]:
+    def get_counts(self) -> dict[str, int]:
         """Aggregate state counts from AHS shot results.
 
         Notes:
@@ -115,7 +114,7 @@ def get_counts(self) -> Dict[str, int]:
             g: ground state atom
 
         Returns:
-            Dict[str, int]: number of times each state configuration is measured.
+            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.
         """
diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py
index 9b21b007..453dcacf 100644
--- a/src/braket/tasks/gate_model_quantum_task_result.py
+++ b/src/braket/tasks/gate_model_quantum_task_result.py
@@ -15,8 +15,9 @@
 
 import json
 from collections import Counter
+from collections.abc import Callable
 from dataclasses import dataclass
-from typing import Any, Callable, Dict, List, Optional, TypeVar, Union
+from typing import Any, Optional, TypeVar, Union
 
 import numpy as np
 
@@ -42,27 +43,27 @@ class GateModelQuantumTaskResult:
     Args:
         task_metadata (TaskMetadata): Quantum task metadata.
         additional_metadata (AdditionalMetadata): Additional metadata about the quantum task
-        result_types (List[Dict[str, Any]]): List of dictionaries where each dictionary
+        result_types (list[dict[str, Any]]): List of dictionaries where each dictionary
             has two keys: 'Type' (the result type in IR JSON form) and
             'Value' (the result value for this result type).
             This can be an empty list if no result types are specified in the IR.
             This is calculated from `measurements` and
             the IR of the circuit program when `shots>0`.
-        values (List[Any]): The values for result types requested in the circuit.
+        values (list[Any]): The values for result types requested in the circuit.
             This can be an empty list if no result types are specified in the IR.
             This is calculated from `measurements` and
             the IR of the circuit program when `shots>0`.
         measurements (numpy.ndarray, optional): 2d array - row is shot and column is qubit.
             Default is None. Only available when shots > 0. The qubits in `measurements`
             are the ones in `GateModelQuantumTaskResult.measured_qubits`.
-        measured_qubits (List[int], optional): The indices of the measured qubits. Default
+        measured_qubits (list[int], optional): The indices of the measured qubits. Default
             is None. Only available when shots > 0. Indicates which qubits are in
             `measurements`.
         measurement_counts (Counter, optional): A `Counter` of measurements. Key is the measurements
             in a big endian binary string. Value is the number of times that measurement occurred.
             Default is None. Only available when shots > 0. Note that the keys in `Counter` are
             unordered.
-        measurement_probabilities (Dict[str, float], optional):
+        measurement_probabilities (dict[str, float], optional):
             A dictionary of probabilistic results.
             Key is the measurements in a big endian binary string.
             Value is the probability the measurement occurred.
@@ -81,17 +82,17 @@ class GateModelQuantumTaskResult:
 
     task_metadata: TaskMetadata
     additional_metadata: AdditionalMetadata
-    result_types: List[ResultTypeValue] = None
-    values: List[Any] = None
+    result_types: list[ResultTypeValue] = None
+    values: list[Any] = None
     measurements: np.ndarray = None
-    measured_qubits: List[int] = None
+    measured_qubits: list[int] = None
     measurement_counts: Counter = None
-    measurement_probabilities: Dict[str, float] = None
+    measurement_probabilities: dict[str, float] = None
     measurements_copied_from_device: bool = None
     measurement_counts_copied_from_device: bool = None
     measurement_probabilities_copied_from_device: bool = None
 
-    _result_types_indices: Dict[str, int] = None
+    _result_types_indices: dict[str, int] = None
 
     def __post_init__(self):
         if self.result_types is not None:
@@ -153,7 +154,7 @@ def measurement_counts_from_measurements(measurements: np.ndarray) -> Counter:
     @staticmethod
     def measurement_probabilities_from_measurement_counts(
         measurement_counts: Counter,
-    ) -> Dict[str, float]:
+    ) -> dict[str, float]:
         """
         Creates measurement probabilities from measurement counts
 
@@ -163,7 +164,7 @@ def measurement_probabilities_from_measurement_counts(
                 occurred.
 
         Returns:
-            Dict[str, float]: A dictionary of probabilistic results. Key is the measurements
+            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 = {}
@@ -175,13 +176,13 @@ def measurement_probabilities_from_measurement_counts(
 
     @staticmethod
     def measurements_from_measurement_probabilities(
-        measurement_probabilities: Dict[str, float], shots: int
+        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.
+            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.
@@ -352,8 +353,8 @@ def cast_result_types(gate_model_task_result: GateModelTaskResult) -> None:
 
     @staticmethod
     def _calculate_result_types(
-        ir_string: str, measurements: np.ndarray, measured_qubits: List[int]
-    ) -> List[ResultTypeValue]:
+        ir_string: str, measurements: np.ndarray, measured_qubits: list[int]
+    ) -> list[ResultTypeValue]:
         ir = json.loads(ir_string)
         result_types = []
         if not ir.get("results"):
@@ -402,7 +403,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: Optional[list[int]]
     ) -> np.ndarray:
         if targets is not None and targets != measured_qubits:
             # Only some qubits targeted
@@ -412,12 +413,12 @@ def _selected_measurements(
 
     @staticmethod
     def _calculate_for_targets(
-        calculate_function: Callable[[np.ndarray, List[int], Observable, List[int]], T],
+        calculate_function: Callable[[np.ndarray, list[int], Observable, list[int]], T],
         measurements: np.ndarray,
-        measured_qubits: List[int],
+        measured_qubits: list[int],
         observable: Observable,
-        targets: List[int],
-    ) -> Union[T, List[T]]:
+        targets: list[int],
+    ) -> Union[T, list[T]]:
         if targets:
             return calculate_function(measurements, measured_qubits, observable, targets)
         else:
@@ -434,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: Optional[list[int]]
     ) -> np.ndarray:
         measurements = GateModelQuantumTaskResult._selected_measurements(
             measurements, measured_qubits, targets
@@ -452,9 +453,9 @@ def _probability_from_measurements(
     @staticmethod
     def _variance_from_measurements(
         measurements: np.ndarray,
-        measured_qubits: List[int],
+        measured_qubits: list[int],
         observable: Observable,
-        targets: List[int],
+        targets: list[int],
     ) -> float:
         samples = GateModelQuantumTaskResult._samples_from_measurements(
             measurements, measured_qubits, observable, targets
@@ -464,9 +465,9 @@ def _variance_from_measurements(
     @staticmethod
     def _expectation_from_measurements(
         measurements: np.ndarray,
-        measured_qubits: List[int],
+        measured_qubits: list[int],
         observable: Observable,
-        targets: List[int],
+        targets: list[int],
     ) -> float:
         samples = GateModelQuantumTaskResult._samples_from_measurements(
             measurements, measured_qubits, observable, targets
@@ -476,9 +477,9 @@ def _expectation_from_measurements(
     @staticmethod
     def _samples_from_measurements(
         measurements: np.ndarray,
-        measured_qubits: List[int],
+        measured_qubits: list[int],
         observable: Observable,
-        targets: List[int],
+        targets: list[int],
     ) -> np.ndarray:
         measurements = GateModelQuantumTaskResult._selected_measurements(
             measurements, measured_qubits, targets
diff --git a/src/braket/tasks/local_quantum_task_batch.py b/src/braket/tasks/local_quantum_task_batch.py
index 6e36246e..a47a47e6 100644
--- a/src/braket/tasks/local_quantum_task_batch.py
+++ b/src/braket/tasks/local_quantum_task_batch.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 braket.tasks import (
     AnnealingQuantumTaskResult,
@@ -29,7 +29,7 @@ class LocalQuantumTaskBatch(QuantumTaskBatch):
 
     def __init__(
         self,
-        results: List[
+        results: list[
             Union[
                 GateModelQuantumTaskResult,
                 AnnealingQuantumTaskResult,
@@ -41,7 +41,7 @@ def __init__(
 
     def results(
         self,
-    ) -> List[
+    ) -> list[
         Union[
             GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult
         ]
diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py
index 47ea8fff..c306078c 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, Dict, Union
+from typing import Any, Union
 
 from braket.tasks.annealing_quantum_task_result import AnnealingQuantumTaskResult
 from braket.tasks.gate_model_quantum_task_result import GateModelQuantumTaskResult
@@ -62,7 +62,7 @@ def async_result(self) -> asyncio.Task:
             Task: Get the quantum task result asynchronously.
         """
 
-    def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]:
+    def metadata(self, use_cached_value: bool = False) -> dict[str, Any]:
         """
         Get task metadata.
 
@@ -71,6 +71,6 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]:
                 request. Default is False.
 
         Returns:
-            Dict[str, Any]: The metadata regarding the quantum task. If `use_cached_value` is True,
+            dict[str, Any]: The metadata regarding the quantum task. If `use_cached_value` is True,
             then the value retrieved from the most recent request is used.
         """
diff --git a/src/braket/tasks/quantum_task_batch.py b/src/braket/tasks/quantum_task_batch.py
index 721afd7c..ff0a0b82 100644
--- a/src/braket/tasks/quantum_task_batch.py
+++ b/src/braket/tasks/quantum_task_batch.py
@@ -12,7 +12,7 @@
 # language governing permissions and limitations under the License.
 
 from abc import ABC, abstractmethod
-from typing import List, Union
+from typing import Union
 
 from braket.tasks.annealing_quantum_task_result import AnnealingQuantumTaskResult
 from braket.tasks.gate_model_quantum_task_result import GateModelQuantumTaskResult
@@ -25,13 +25,13 @@ class QuantumTaskBatch(ABC):
     @abstractmethod
     def results(
         self,
-    ) -> List[
+    ) -> list[
         Union[
             GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult
         ]
     ]:
         """Get the quantum task results.
         Returns:
-            List[Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]]:: # noqa
+            list[Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]]:: # noqa
             Get the quantum task results.
         """
diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py
index 3baffe63..9d2b9cf0 100644
--- a/src/braket/timings/time_series.py
+++ b/src/braket/timings/time_series.py
@@ -14,11 +14,12 @@
 from __future__ import annotations
 
 from collections import OrderedDict
+from collections.abc import Iterator
 from dataclasses import dataclass
 from decimal import Decimal
 from enum import Enum
 from numbers import Number
-from typing import Iterator, List, Union
+from typing import Union
 
 
 @dataclass
@@ -64,20 +65,20 @@ def put(
             self._sorted = False
         return self
 
-    def times(self) -> List[Number]:
+    def times(self) -> list[Number]:
         """Returns the times in the time series.
 
         Returns:
-            List[Number]: The times in the time series.
+            list[Number]: The times in the time series.
         """
         self._ensure_sorted()
         return [item.time for item in self._series.values()]
 
-    def values(self) -> List[Number]:
+    def values(self) -> list[Number]:
         """Returns the values in the time series.
 
         Returns:
-            List[Number]: The values in the time series.
+            list[Number]: The values in the time series.
         """
         self._ensure_sorted()
         return [item.value for item in self._series.values()]
@@ -95,12 +96,12 @@ def _ensure_sorted(self) -> None:
             self._sorted = True
 
     @staticmethod
-    def from_lists(times: List[float], values: List[float]) -> TimeSeries:
+    def from_lists(times: list[float], values: list[float]) -> TimeSeries:
         """Create a time series from the list of time and value points
 
         Args:
-            times (List[float]): list of time points
-            values (List[float]): list of value points
+            times (list[float]): list of time points
+            values (list[float]): list of value points
 
         Returns:
             TimeSeries: time series constructed from lists
@@ -117,18 +118,18 @@ 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: Union[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
 
         Args:
-            times (Union[List[float], TimeSeries]): list of time points or a time series
+            times (Union[list[float], TimeSeries]): list of time points or a time series
             constant (float): constant value
 
         Returns:
             TimeSeries: A constant time series
         """
-        if not isinstance(times, List):
+        if not isinstance(times, list):
             times = times.times()
 
         ts = TimeSeries()
@@ -280,12 +281,12 @@ def discretize(self, time_resolution: Decimal, value_resolution: Decimal) -> Tim
         return discretized_ts
 
     @staticmethod
-    def periodic_signal(times: List[float], values: List[float], num_repeat: int = 1) -> TimeSeries:
+    def periodic_signal(times: list[float], values: list[float], num_repeat: int = 1) -> TimeSeries:
         """Create a periodic time series by repeating the same block multiple times.
 
         Args:
-            times (List[float]): List of time points in a single block
-            values (List[float]): Values for the time series in a single block
+            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
 
         Returns:
diff --git a/src/braket/tracking/pricing.py b/src/braket/tracking/pricing.py
index fb4b9469..2b049b25 100644
--- a/src/braket/tracking/pricing.py
+++ b/src/braket/tracking/pricing.py
@@ -17,7 +17,6 @@
 import io
 import os
 from functools import lru_cache
-from typing import Dict, List
 
 import urllib3
 
@@ -55,10 +54,10 @@ def get_prices(self) -> None:
         self._price_list = list(csv.DictReader(text_response))
 
     @lru_cache()
-    def price_search(self, **kwargs) -> List[Dict[str, str]]:
+    def price_search(self, **kwargs) -> list[dict[str, str]]:
         """Searches the price list for a given set of parameters.
         Returns:
-            List[Dict[str, str]]: The price list.
+            list[dict[str, str]]: The price list.
         """
         if not self._price_list:
             self.get_prices()
diff --git a/src/braket/tracking/tracker.py b/src/braket/tracking/tracker.py
index 9558d1d5..c30fc774 100644
--- a/src/braket/tracking/tracker.py
+++ b/src/braket/tracking/tracker.py
@@ -16,7 +16,7 @@
 from datetime import timedelta
 from decimal import Decimal
 from functools import singledispatchmethod
-from typing import Any, Dict, List
+from typing import Any
 
 from braket.tracking.pricing import price_search
 from braket.tracking.tracking_context import deregister_tracker, register_tracker
@@ -66,12 +66,12 @@ def receive_event(self, event: _TaskCreationEvent) -> None:
         """
         self._recieve_internal(event)
 
-    def tracked_resources(self) -> List[str]:
+    def tracked_resources(self) -> list[str]:
         """
         Resources tracked by this tracker.
 
         Returns:
-            List[str]: The list of quantum task ids for quantum tasks tracked by this tracker.
+            list[str]: The list of quantum task ids for quantum tasks tracked by this tracker.
         """
         return list(self._resources.keys())
 
@@ -117,12 +117,12 @@ def simulator_tasks_cost(self) -> Decimal:
                 total_cost = total_cost + _get_simulator_task_cost(task_arn, details)
         return total_cost
 
-    def quantum_tasks_statistics(self) -> Dict[str, Dict[str, Any]]:
+    def quantum_tasks_statistics(self) -> dict[str, dict[str, Any]]:
         """
         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

From cb7ed096a95764146c4c43e2615f7951ee055565 Mon Sep 17 00:00:00 2001
From: ci 
Date: Wed, 11 Oct 2023 16:16:26 +0000
Subject: [PATCH 0885/1165] prepare release v1.57.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 3e7fd4e9..3ea45409 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## v1.57.2 (2023-10-11)
+
+### Bug Fixes and Other Changes
+
+ * Use builtins for type hints
+
 ## v1.57.1 (2023-10-05)
 
 ### Bug Fixes and Other Changes
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index a16206e2..ec7889fb 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.57.2.dev0"
+__version__ = "1.57.2"

From c4f07f5eebad41566cdf97cfed80b81313c08693 Mon Sep 17 00:00:00 2001
From: ci 
Date: Wed, 11 Oct 2023 16:16:26 +0000
Subject: [PATCH 0886/1165] update development version to v1.57.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 ec7889fb..69256550 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.57.2"
+__version__ = "1.57.3.dev0"

From 0b8f1bde8099acda282e8889aad011a7fff936c9 Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Mon, 16 Oct 2023 10:31:23 -0400
Subject: [PATCH 0887/1165] Fix merge conflict

---
 src/braket/devices/local_simulator.py | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py
index f30a31c7..253732a0 100644
--- a/src/braket/devices/local_simulator.py
+++ b/src/braket/devices/local_simulator.py
@@ -108,7 +108,9 @@ def run_batch(
         self,
         task_specifications: Union[
             Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, SerializableProgram],
-            list[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, SerializableProgram]],
+            list[
+                Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, SerializableProgram]
+            ],
         ],
         shots: Optional[int] = 0,
         max_parallel: Optional[int] = None,
@@ -307,7 +309,7 @@ def _(
         self,
         program: SerializableProgram,
         shots: Optional[int] = None,
-        inputs: Optional[Dict[str, float]] = None,
+        inputs: Optional[dict[str, float]] = None,
         *args,
         **kwargs,
     ):

From c544b7749de713e2e73459ee135985a5fb1601dc Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Mon, 16 Oct 2023 10:35:19 -0400
Subject: [PATCH 0888/1165] Update mcm-sim branch commit

---
 setup.py | 2 +-
 tox.ini  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/setup.py b/setup.py
index 75ae42a7..a6541b81 100644
--- a/setup.py
+++ b/setup.py
@@ -32,7 +32,7 @@
         # to get the version of the simulator that supports the mcm=True argument for Monte Carlo
         # simulation of mid-circuit measurement, which AutoQASM requires.
         # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main.
-        "amazon-braket-default-simulator @ git+https://github.com/aws/amazon-braket-default-simulator-python.git@800a31a0860de5ec09532a3dbc127059d03cf9ac#egg=amazon-braket-default-simulator",  # noqa E501
+        "amazon-braket-default-simulator @ git+https://github.com/aws/amazon-braket-default-simulator-python.git@46aea776976ad7f958d847c06f29f3a7976f5cf5#egg=amazon-braket-default-simulator",  # noqa E501
         # Pin the latest commit of the qubit-array branch of ajberdy/oqpy.git to get the version of
         # oqpy which contains changes that AutoQASM relies on, including the QubitArray type.
         # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main.
diff --git a/tox.ini b/tox.ini
index aa176cf6..367fd046 100644
--- a/tox.ini
+++ b/tox.ini
@@ -142,4 +142,4 @@ commands =
 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@800a31a0860de5ec09532a3dbc127059d03cf9ac  # mcm-sim branch
+    git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git@46aea776976ad7f958d847c06f29f3a7976f5cf5  # mcm-sim branch

From 71ef0ecf50fd806cc6cb744403af4627b8c6f0db Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Mon, 16 Oct 2023 10:22:23 -0700
Subject: [PATCH 0889/1165] feat: job decorator (#729)

Co-authored-by: Matthew Beach <85963088+mbeach-aws@users.noreply.github.com>
---
 examples/hybrid_job.py                        |  49 ++
 examples/hybrid_job_script.py                 |  56 ++
 examples/job.py                               |  43 --
 setup.py                                      |   1 +
 src/braket/aws/aws_quantum_job.py             |  51 +-
 src/braket/aws/aws_session.py                 |  36 ++
 src/braket/jobs/__init__.py                   |   2 +
 src/braket/jobs/_entry_point_template.py      |  79 +++
 src/braket/jobs/config.py                     |  15 +-
 src/braket/jobs/data_persistence.py           |  44 +-
 src/braket/jobs/environment_variables.py      |  20 +-
 src/braket/jobs/hybrid_job.py                 | 398 ++++++++++++++
 src/braket/jobs/image_uris.py                 |   6 +
 src/braket/jobs/local/local_job.py            |  33 +-
 src/braket/jobs/quantum_job_creation.py       |  93 ++--
 .../job_test_submodule_file.py                |   3 +
 .../job_test_submodule/requirements.txt       |   1 +
 test/integ_tests/job_test_script.py           |   5 +
 test/integ_tests/requirements.txt             |   1 +
 test/integ_tests/test_create_quantum_job.py   | 125 ++++-
 .../unit_tests/braket/aws/test_aws_session.py |  35 ++
 test/unit_tests/braket/jobs/job_module.py     |   2 +
 .../unit_tests/braket/jobs/test_hybrid_job.py | 495 ++++++++++++++++++
 .../braket/jobs/test_quantum_job_creation.py  |   7 +-
 24 files changed, 1439 insertions(+), 161 deletions(-)
 create mode 100644 examples/hybrid_job.py
 create mode 100644 examples/hybrid_job_script.py
 delete mode 100644 examples/job.py
 create mode 100644 src/braket/jobs/_entry_point_template.py
 create mode 100644 src/braket/jobs/hybrid_job.py
 create mode 100644 test/integ_tests/job_test_module/job_test_submodule/job_test_submodule_file.py
 create mode 100644 test/integ_tests/job_test_module/job_test_submodule/requirements.txt
 create mode 100644 test/integ_tests/requirements.txt
 create mode 100644 test/unit_tests/braket/jobs/job_module.py
 create mode 100644 test/unit_tests/braket/jobs/test_hybrid_job.py

diff --git a/examples/hybrid_job.py b/examples/hybrid_job.py
new file mode 100644
index 00000000..2e09d646
--- /dev/null
+++ b/examples/hybrid_job.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 braket.aws import AwsDevice
+from braket.circuits import Circuit, FreeParameter, Observable
+from braket.devices import Devices
+from braket.jobs import get_job_device_arn, hybrid_job
+from braket.jobs.metrics import log_metric
+
+
+@hybrid_job(device=Devices.Amazon.SV1, wait_until_complete=True)
+def run_hybrid_job(num_tasks=1):
+    # declare AwsDevice within the hybrid job
+    device = AwsDevice(get_job_device_arn())
+
+    # create a parametric circuit
+    circ = Circuit()
+    circ.rx(0, FreeParameter("theta"))
+    circ.cnot(0, 1)
+    circ.expectation(observable=Observable.X(), target=0)
+
+    # initial parameter
+    theta = 0.0
+
+    for i in range(num_tasks):
+        # run task, specifying input parameter
+        task = device.run(circ, shots=100, inputs={"theta": theta})
+        exp_val = task.result().values[0]
+
+        # modify the parameter (e.g. gradient descent)
+        theta += exp_val
+
+        log_metric(metric_name="exp_val", value=exp_val, iteration_number=i)
+
+    return {"final_theta": theta, "final_exp_val": exp_val}
+
+
+job = run_hybrid_job(num_tasks=5)
+print(job.result())
diff --git a/examples/hybrid_job_script.py b/examples/hybrid_job_script.py
new file mode 100644
index 00000000..b544ff9d
--- /dev/null
+++ b/examples/hybrid_job_script.py
@@ -0,0 +1,56 @@
+# 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.aws import AwsDevice, AwsQuantumJob
+from braket.circuits import Circuit, FreeParameter, Observable
+from braket.devices import Devices
+from braket.jobs import get_job_device_arn, save_job_result
+from braket.jobs.metrics import log_metric
+
+
+def run_hybrid_job(num_tasks: int):
+    # use the device specified in the hybrid job
+    device = AwsDevice(get_job_device_arn())
+
+    # create a parametric circuit
+    circ = Circuit()
+    circ.rx(0, FreeParameter("theta"))
+    circ.cnot(0, 1)
+    circ.expectation(observable=Observable.X(), target=0)
+
+    # initial parameter
+    theta = 0.0
+
+    for i in range(num_tasks):
+        # run task, specifying input parameter
+        task = device.run(circ, shots=100, inputs={"theta": theta})
+        exp_val = task.result().values[0]
+
+        # modify the parameter (e.g. gradient descent)
+        theta += exp_val
+
+        log_metric(metric_name="exp_val", value=exp_val, iteration_number=i)
+
+    save_job_result({"final_theta": theta, "final_exp_val": exp_val})
+
+
+if __name__ == "__main__":
+    job = AwsQuantumJob.create(
+        device=Devices.Amazon.SV1,  # choose priority device
+        source_module="hybrid_job_script.py",  # specify file or directory with code to run
+        entry_point="hybrid_job_script:run_hybrid_job",  # specify function to run
+        hyperparameters={"num_tasks": 5},
+        wait_until_complete=True,
+    )
+    print(job.result())
diff --git a/examples/job.py b/examples/job.py
deleted file mode 100644
index 87b06bf4..00000000
--- a/examples/job.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# 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.aws import AwsDevice, AwsQuantumJob
-from braket.circuits import Circuit
-from braket.devices import Devices
-from braket.jobs import get_job_device_arn, save_job_result
-
-
-def run_job():
-    device = AwsDevice(get_job_device_arn())
-
-    bell = Circuit().h(0).cnot(0, 1)
-    num_tasks = 10
-    results = []
-
-    for i in range(num_tasks):
-        task = device.run(bell, shots=100)
-        result = task.result().measurement_counts
-        results.append(result)
-        print(f"iter {i}: {result}")
-
-    save_job_result({"results": results})
-
-
-if __name__ == "__main__":
-    job = AwsQuantumJob.create(
-        device=Devices.Amazon.SV1,
-        source_module="job.py",
-        entry_point="job:run_job",
-        wait_until_complete=True,
-    )
-    print(job.result())
diff --git a/setup.py b/setup.py
index 428cbb58..d3724c59 100644
--- a/setup.py
+++ b/setup.py
@@ -34,6 +34,7 @@
         "backoff",
         "boltons",
         "boto3>=1.28.53",
+        "cloudpickle==2.2.1",
         "nest-asyncio",
         "networkx",
         "numpy",
diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py
index 77161157..7f6fdfd0 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, Dict, List, Union
+from typing import Any
 
 import boto3
 from botocore.exceptions import ClientError
@@ -70,8 +70,8 @@ def create(
         code_location: str = None,
         role_arn: str = None,
         wait_until_complete: bool = False,
-        hyperparameters: Dict[str, Any] = None,
-        input_data: Union[str, Dict, S3DataSourceConfig] = None,
+        hyperparameters: dict[str, Any] = None,
+        input_data: str | dict | S3DataSourceConfig = None,
         instance_config: InstanceConfig = None,
         distribution: str = None,
         stopping_condition: StoppingCondition = None,
@@ -79,17 +79,18 @@ def create(
         copy_checkpoints_from_job: str = None,
         checkpoint_config: CheckpointConfig = None,
         aws_session: AwsSession = None,
-        tags: Dict[str, str] = None,
+        tags: dict[str, str] = None,
         logger: Logger = getLogger(__name__),
     ) -> AwsQuantumJob:
         """Creates a hybrid job by invoking the Braket CreateJob API.
 
         Args:
-            device (str): ARN for the AWS device which is primarily accessed for the execution
-                of this hybrid job. Alternatively, a string of the format
-                "local:/" for using a local simulator for the hybrid job.
-                This string will be available as the environment variable `AMZN_BRAKET_DEVICE_ARN`
-                inside the hybrid job container when using a Braket container.
+            device (str): 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 queue so that only one hybrid job is running at a time. The device string is
+                accessible in the hybrid job instance as the environment variable
+                "AMZN_BRAKET_DEVICE_ARN". When using embedded simulators, you may provide the device
+                argument as a string of the form: "local:/".
 
             source_module (str): Path (absolute, relative or an S3 URI) to a python module to be
                 tarred and uploaded. If `source_module` is an S3 URI, it must point to a
@@ -120,12 +121,12 @@ def create(
                 This would tail the hybrid job logs as it waits. Otherwise `False`.
                 Default: `False`.
 
-            hyperparameters (Dict[str, Any]): Hyperparameters accessible to the hybrid job.
-                The hyperparameters are made accessible as a Dict[str, str] to the hybrid job.
+            hyperparameters (dict[str, Any]): Hyperparameters accessible to the hybrid job.
+                The hyperparameters are made accessible as a dict[str, str] to the hybrid job.
                 For convenience, this accepts other types for keys and values, but `str()`
                 is called to convert them before being passed on. Default: None.
 
-            input_data (Union[str, Dict, S3DataSourceConfig]): Information about the training
+            input_data (str | dict | S3DataSourceConfig): Information about the training
                 data. Dictionary maps channel names to local paths or S3 URIs. Contents found
                 at any local paths will be uploaded to S3 at
                 f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local
@@ -133,9 +134,9 @@ def create(
                 channel name "input".
                 Default: {}.
 
-            instance_config (InstanceConfig): Configuration of the instances to be used
-                to execute the hybrid job. Default: InstanceConfig(instanceType='ml.m5.large',
-                instanceCount=1, volumeSizeInGB=30).
+            instance_config (InstanceConfig): Configuration of the instance(s) for running the
+                classical code for the hybrid job. Default:
+                `InstanceConfig(instanceType='ml.m5.large', instanceCount=1, volumeSizeInGB=30)`.
 
             distribution (str): A str that specifies how the hybrid job should be distributed.
                 If set to "data_parallel", the hyperparameters for the hybrid job will be set
@@ -165,7 +166,7 @@ def create(
             aws_session (AwsSession): AwsSession for connecting to AWS Services.
                 Default: AwsSession()
 
-            tags (Dict[str, str]): Dict specifying the key-value pairs for tagging this hybrid job.
+            tags (dict[str, str]): Dict specifying the key-value pairs for tagging this hybrid job.
                 Default: {}.
 
             logger (Logger): Logger object with which to write logs, such as quantum task statuses
@@ -385,7 +386,7 @@ def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None:
             elif self.state() in AwsQuantumJob.TERMINAL_STATES:
                 log_state = AwsQuantumJob.LogState.JOB_COMPLETE
 
-    def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]:
+    def metadata(self, use_cached_value: bool = False) -> dict[str, Any]:
         """Gets the hybrid job metadata defined in Amazon Braket.
 
         Args:
@@ -394,7 +395,7 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]:
                 `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.
         """
         if not use_cached_value or not self._metadata:
             self._metadata = self._aws_session.get_job(self._arn)
@@ -404,7 +405,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. For example, the table:
             timestamp energy
@@ -421,7 +422,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)
@@ -450,7 +451,7 @@ def result(
         self,
         poll_timeout_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_TIMEOUT,
         poll_interval_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_INTERVAL,
-    ) -> Dict[str, Any]:
+    ) -> dict[str, Any]:
         """Retrieves the hybrid job result persisted using save_job_result() function.
 
         Args:
@@ -460,7 +461,7 @@ def result(
                 Default: 5 seconds.
 
         Returns:
-            Dict[str, Any]: Dict specifying the job results.
+            dict[str, Any]: Dict specifying the job results.
 
         Raises:
             RuntimeError: if hybrid job is in a FAILED or CANCELLED state.
@@ -480,7 +481,7 @@ def result(
             return AwsQuantumJob._read_and_deserialize_results(temp_dir, job_name)
 
     @staticmethod
-    def _read_and_deserialize_results(temp_dir: str, job_name: str) -> Dict[str, Any]:
+    def _read_and_deserialize_results(temp_dir: str, job_name: str) -> dict[str, Any]:
         return load_job_result(Path(temp_dir, job_name, AwsQuantumJob.RESULTS_FILENAME))
 
     def download_result(
@@ -565,9 +566,7 @@ def __hash__(self) -> int:
         return hash(self.arn)
 
     @staticmethod
-    def _initialize_session(
-        session_value: AwsSession, device: AwsDevice, logger: Logger
-    ) -> AwsSession:
+    def _initialize_session(session_value: AwsSession, device: str, logger: Logger) -> AwsSession:
         aws_session = session_value or AwsSession()
         if device.startswith("local:"):
             return aws_session
diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py
index 225140d8..eabe06dc 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
+from functools import cache
 from pathlib import Path
 from typing import Any, NamedTuple, Optional
 
@@ -825,3 +826,38 @@ def copy_session(
         # Preserve user_agent information
         copied_session._braket_user_agents = self._braket_user_agents
         return copied_session
+
+    @cache
+    def get_full_image_tag(self, image_uri: str) -> str:
+        """
+        Get verbose image tag from image uri.
+
+        Args:
+            image_uri (str): Image uri to get tag for.
+
+        Returns:
+            str: Verbose image tag for given image.
+        """
+        registry = image_uri.split(".")[0]
+        repository, tag = image_uri.split("/")[-1].split(":")
+
+        # get image digest of latest image
+        digest = self.ecr_client.batch_get_image(
+            registryId=registry,
+            repositoryName=repository,
+            imageIds=[{"imageTag": tag}],
+        )["images"][0]["imageId"]["imageDigest"]
+
+        # get all images matching digest (same image, different tags)
+        images = self.ecr_client.batch_get_image(
+            registryId=registry,
+            repositoryName=repository,
+            imageIds=[{"imageDigest": digest}],
+        )["images"]
+
+        # find the tag with the python version info
+        for image in images:
+            if re.search(r"py\d\d+", tag := image["imageId"]["imageTag"]):
+                return tag
+
+        raise ValueError("Full image tag missing.")
diff --git a/src/braket/jobs/__init__.py b/src/braket/jobs/__init__.py
index 478305df..0a6fb520 100644
--- a/src/braket/jobs/__init__.py
+++ b/src/braket/jobs/__init__.py
@@ -20,6 +20,7 @@
 )
 from braket.jobs.data_persistence import (  # noqa: F401
     load_job_checkpoint,
+    load_job_result,
     save_job_checkpoint,
     save_job_result,
 )
@@ -31,4 +32,5 @@
     get_job_name,
     get_results_dir,
 )
+from braket.jobs.hybrid_job import hybrid_job  # noqa: F401
 from braket.jobs.image_uris import Framework, retrieve_image  # noqa: F401
diff --git a/src/braket/jobs/_entry_point_template.py b/src/braket/jobs/_entry_point_template.py
new file mode 100644
index 00000000..285e4d85
--- /dev/null
+++ b/src/braket/jobs/_entry_point_template.py
@@ -0,0 +1,79 @@
+run_entry_point = """
+import cloudpickle
+import os
+from braket.jobs import get_results_dir, save_job_result
+from braket.jobs_data import PersistedJobDataFormat
+
+
+# set working directory to results dir
+os.chdir(get_results_dir())
+
+# create symlinks to input data
+links = link_input()
+
+# load and run serialized entry point function
+recovered = cloudpickle.loads({serialized})
+def {function_name}():
+    try:
+        result = recovered()
+    finally:
+        clean_links(links)
+    if result is not None:
+        save_job_result(result, data_format=PersistedJobDataFormat.PICKLED_V4)
+    return result
+"""
+
+symlink_input_data = '''
+from pathlib import Path
+from braket.jobs import get_input_data_dir
+
+
+def make_link(input_link_path, input_data_path, links):
+    """ Create symlink from input_link_path to input_data_path. """
+    input_link_path.parent.mkdir(parents=True, exist_ok=True)
+    input_link_path.symlink_to(input_data_path)
+    print(input_link_path, '->', input_data_path)
+    links[input_link_path] = input_data_path
+
+
+def link_input():
+    links = {{}}
+    dirs = set()
+    # map of data sources to lists of matched local files
+    prefix_matches = {prefix_matches}
+
+    for channel, data in {input_data_items}:
+
+        if channel in {prefix_channels}:
+            # link all matched files
+            for input_link_name in prefix_matches[channel]:
+                input_link_path = Path(input_link_name)
+                input_data_path = Path(get_input_data_dir(channel)) / input_link_path.name
+                make_link(input_link_path, input_data_path, links)
+
+        else:
+            input_link_path = Path(data)
+            if channel in {directory_channels}:
+                # link directory source directly to input channel directory
+                input_data_path = Path(get_input_data_dir(channel))
+            else:
+                # link file source to file within input channel directory
+                input_data_path = Path(get_input_data_dir(channel), Path(data).name)
+            make_link(input_link_path, input_data_path, links)
+
+    return links
+
+
+def clean_links(links):
+    for link, target in links.items():
+        if link.is_symlink and link.readlink() == target:
+            link.unlink()
+
+        if link.is_relative_to(Path()):
+            for dir in link.parents[:-1]:
+                try:
+                    dir.rmdir()
+                except:
+                    # directory not empty
+                    pass
+'''
diff --git a/src/braket/jobs/config.py b/src/braket/jobs/config.py
index 73467dd3..b846a8bf 100644
--- a/src/braket/jobs/config.py
+++ b/src/braket/jobs/config.py
@@ -11,8 +11,9 @@
 # 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 dataclasses import dataclass
-from typing import Optional
 
 
 @dataclass
@@ -20,12 +21,12 @@ class CheckpointConfig:
     """Configuration that specifies the location where checkpoint data is stored."""
 
     localPath: str = "/opt/jobs/checkpoints"
-    s3Uri: Optional[str] = None
+    s3Uri: str | None = None
 
 
 @dataclass
 class InstanceConfig:
-    """Configuration of the instances used to execute the hybrid job."""
+    """Configuration of the instance(s) used to run the hybrid job."""
 
     instanceType: str = "ml.m5.large"
     volumeSizeInGb: int = 30
@@ -36,8 +37,8 @@ class InstanceConfig:
 class OutputDataConfig:
     """Configuration that specifies the location for the output of the hybrid job."""
 
-    s3Path: Optional[str] = None
-    kmsKeyId: Optional[str] = None
+    s3Path: str | None = None
+    kmsKeyId: str | None = None
 
 
 @dataclass
@@ -61,8 +62,8 @@ class S3DataSourceConfig:
 
     def __init__(
         self,
-        s3_data,
-        content_type=None,
+        s3_data: str,
+        content_type: str | None = None,
     ):
         """Create a definition for input data used by a Braket Hybrid job.
 
diff --git a/src/braket/jobs/data_persistence.py b/src/braket/jobs/data_persistence.py
index 10806812..ccd9a47d 100644
--- a/src/braket/jobs/data_persistence.py
+++ b/src/braket/jobs/data_persistence.py
@@ -10,8 +10,11 @@
 # 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 pathlib import Path
-from typing import Any, Dict, Union
+from typing import Any
 
 from braket.jobs.environment_variables import get_checkpoint_dir, get_job_name, get_results_dir
 from braket.jobs.serialization import deserialize_values, serialize_values
@@ -19,7 +22,7 @@
 
 
 def save_job_checkpoint(
-    checkpoint_data: Dict[str, Any],
+    checkpoint_data: dict[str, Any],
     checkpoint_file_suffix: str = "",
     data_format: PersistedJobDataFormat = PersistedJobDataFormat.PLAINTEXT,
 ) -> None:
@@ -35,7 +38,7 @@ def save_job_checkpoint(
 
 
     Args:
-        checkpoint_data (Dict[str, Any]): Dict that specifies the checkpoint data to be persisted.
+        checkpoint_data (dict[str, Any]): Dict that specifies the checkpoint data to be persisted.
         checkpoint_file_suffix (str): str that specifies the file suffix to be used for
             the checkpoint filename. The resulting filename
             `f"{job_name}(_{checkpoint_file_suffix}).json"` is used to save the checkpoints.
@@ -62,27 +65,31 @@ def save_job_checkpoint(
         f.write(persisted_data.json())
 
 
-def load_job_checkpoint(job_name: str, checkpoint_file_suffix: str = "") -> Dict[str, Any]:
+def load_job_checkpoint(
+    job_name: str | None = None, checkpoint_file_suffix: str = ""
+) -> dict[str, Any]:
     """
-    Loads the hybrid 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 hybrid job
-    whose checkpoint data you expect to be available in the file path specified by the
-    `CHECKPOINT_DIR` container environment variable.
+    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
+    job's name.
 
     Note: This function for loading hybrid job checkpoints is only for use inside the job container
           as it writes data to directories and references env variables set in the containers.
 
 
     Args:
-        job_name (str): str that specifies the name of the hybrid job whose checkpoints
-            are to be loaded.
+        job_name (str | None): str that specifies the name of the job whose checkpoints
+            are to be loaded. Default: current job name.
+
         checkpoint_file_suffix (str): str specifying the file suffix that is used to
             locate the checkpoint file to load. The resulting file name
             `f"{job_name}(_{checkpoint_file_suffix}).json"` is used to locate the
             checkpoint file. Default: ""
 
     Returns:
-        Dict[str, Any]: Dict that contains the checkpoint data persisted in the checkpoint file.
+        dict[str, Any]: Dict that contains the checkpoint data persisted in the checkpoint file.
 
     Raises:
         FileNotFoundError: If the file `f"{job_name}(_{checkpoint_file_suffix})"` could not be found
@@ -90,6 +97,7 @@ def load_job_checkpoint(job_name: str, checkpoint_file_suffix: str = "") -> Dict
         ValueError: If the data stored in the checkpoint file can't be deserialized (possibly due to
             corruption).
     """
+    job_name = job_name or get_job_name()
     checkpoint_directory = get_checkpoint_dir()
     checkpoint_file_path = (
         f"{checkpoint_directory}/{job_name}_{checkpoint_file_suffix}.json"
@@ -104,7 +112,7 @@ def load_job_checkpoint(job_name: str, checkpoint_file_suffix: str = "") -> Dict
         return deserialized_data
 
 
-def _load_persisted_data(filename: Union[str, Path] = None) -> PersistedJobData:
+def _load_persisted_data(filename: str | Path = None) -> PersistedJobData:
     filename = filename or Path(get_results_dir()) / "results.json"
     try:
         with open(filename, mode="r") as f:
@@ -116,17 +124,17 @@ def _load_persisted_data(filename: Union[str, Path] = None) -> PersistedJobData:
         )
 
 
-def load_job_result(filename: Union[str, Path] = None) -> Dict[str, Any]:
+def load_job_result(filename: str | Path = None) -> dict[str, Any]:
     """
     Loads job result of currently running job.
 
     Args:
-        filename (Union[str, Path]): Location of job results. Default `results.json` in job
+        filename (str | Path): Location of job results. Default `results.json` in job
             results directory in a job instance or in working directory locally. This file
             must be in the format used by `save_job_result`.
 
     Returns:
-        Dict[str, Any]: Job result data of current job
+        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)
@@ -134,7 +142,7 @@ def load_job_result(filename: Union[str, Path] = None) -> Dict[str, Any]:
 
 
 def save_job_result(
-    result_data: Union[Dict[str, Any], Any],
+    result_data: dict[str, Any] | Any,
     data_format: PersistedJobDataFormat = None,
 ) -> None:
     """
@@ -147,7 +155,7 @@ def save_job_result(
 
 
     Args:
-        result_data (Union[Dict[str, Any], Any]): Dict that specifies the result data to be
+        result_data (dict[str, Any] | Any): Dict that specifies the result data to be
             persisted. If result data is not a dict, then it will be wrapped as
             `{"result": result_data}`.
         data_format (PersistedJobDataFormat): The data format used to serialize the
@@ -178,7 +186,7 @@ def save_job_result(
         current_persisted_data.dataDictionary,
         current_persisted_data.dataFormat,
     )
-    updated_results = {**current_results, **result_data}
+    updated_results = current_results | result_data
 
     with open(Path(get_results_dir()) / "results.json", "w") as f:
         serialized_data = serialize_values(updated_results or {}, data_format)
diff --git a/src/braket/jobs/environment_variables.py b/src/braket/jobs/environment_variables.py
index e298304c..4fba9315 100644
--- a/src/braket/jobs/environment_variables.py
+++ b/src/braket/jobs/environment_variables.py
@@ -1,6 +1,18 @@
+# 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
-from typing import Dict
 
 
 def get_job_name() -> str:
@@ -60,12 +72,12 @@ def get_checkpoint_dir() -> str:
     return os.getenv("AMZN_BRAKET_CHECKPOINT_DIR", ".")
 
 
-def get_hyperparameters() -> Dict[str, str]:
+def get_hyperparameters() -> dict[str, str]:
     """
-    Get the job hyperparameters as strings.
+    Get the job hyperparameters as a dict, with the values stringified.
 
     Returns:
-        Dict[str, str]: The hyperparameters of the job.
+        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:
diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py
new file mode 100644
index 00000000..83ba9a9f
--- /dev/null
+++ b/src/braket/jobs/hybrid_job.py
@@ -0,0 +1,398 @@
+# 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 functools
+import importlib.util
+import inspect
+import re
+import shutil
+import sys
+import tempfile
+import warnings
+from collections.abc import Callable, Iterable
+from logging import Logger, getLogger
+from pathlib import Path
+from types import ModuleType
+from typing import Any
+
+import cloudpickle
+
+from braket.aws.aws_session import AwsSession
+from braket.jobs._entry_point_template import run_entry_point, symlink_input_data
+from braket.jobs.config import (
+    CheckpointConfig,
+    InstanceConfig,
+    OutputDataConfig,
+    S3DataSourceConfig,
+    StoppingCondition,
+)
+from braket.jobs.image_uris import Framework, built_in_images, retrieve_image
+from braket.jobs.quantum_job import QuantumJob
+from braket.jobs.quantum_job_creation import _generate_default_job_name
+
+
+def hybrid_job(
+    *,
+    device: str | None,
+    include_modules: str | ModuleType | Iterable[str | ModuleType] = None,
+    dependencies: str | Path | None = None,
+    local: bool = False,
+    job_name: str | None = None,
+    image_uri: str | None = None,
+    input_data: str | dict | S3DataSourceConfig = None,
+    wait_until_complete: bool = False,
+    instance_config: InstanceConfig | None = None,
+    distribution: str | None = None,
+    copy_checkpoints_from_job: str | None = None,
+    checkpoint_config: CheckpointConfig | None = None,
+    role_arn: str | None = None,
+    stopping_condition: StoppingCondition | None = None,
+    output_data_config: OutputDataConfig | None = None,
+    aws_session: AwsSession | None = None,
+    tags: dict[str, str] = None,
+    logger: Logger = getLogger(__name__),
+) -> Callable:
+    """Defines a hybrid job by decorating the entry point function. The job will be created
+    when the decorated function is called.
+
+    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`.
+
+    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
+            queue so that only one hybrid job is running at a time. The device string is accessible
+            in the hybrid job instance as the environment variable "AMZN_BRAKET_DEVICE_ARN".
+            When using embedded simulators, you may provide the device argument as string of the
+            form: "local:/" or `None`.
+
+        include_modules (str | ModuleType | Iterable[str | ModuleType]): Either a
+            single module or module name or a list of module or module names referring to local
+            modules to be included. Any references to members of these modules in the hybrid job
+            algorithm code will be serialized as part of the algorithm code. Default value `[]`
+
+        dependencies (str | Path | None): Path (absolute or relative) to a requirements.txt
+            file to be used for the hybrid job.
+
+        local (bool): Whether to use local mode for the hybrid job. Default `False`
+
+        job_name (str | None): A string that specifies the name with which the job is created.
+            Allowed pattern for job name: `^[a-zA-Z0-9](-*[a-zA-Z0-9]){0,50}$`. Defaults to
+            f'{decorated-function-name}-{timestamp}'.
+
+        image_uri (str | None): A str that specifies the ECR image to use for executing the job.
+            `retrieve_image()` function may be used for retrieving the ECR image URIs
+            for the containers supported by Braket. Default = ``.
+
+        input_data (str | Dict | S3DataSourceConfig): Information about the training
+            data. Dictionary maps channel names to local paths or S3 URIs. Contents found
+            at any local paths will be uploaded to S3 at
+            f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local
+            path, S3 URI, or S3DataSourceConfig is provided, it will be given a default
+            channel name "input".
+            Default: {}.
+
+        wait_until_complete (bool): `True` if we should wait until the job completes.
+            This would tail the job logs as it waits. Otherwise `False`. Ignored if using
+            local mode. Default: `False`.
+
+        instance_config (InstanceConfig | None): Configuration of the instance(s) for running the
+            classical code for the hybrid job. Defaults to
+            `InstanceConfig(instanceType='ml.m5.large', instanceCount=1, volumeSizeInGB=30)`.
+
+        distribution (str | None): A str that specifies how the job should be distributed.
+            If set to "data_parallel", the hyperparameters for the job will be set to use data
+            parallelism features for PyTorch or TensorFlow. Default: None.
+
+        copy_checkpoints_from_job (str | None): A str that specifies the job ARN whose
+            checkpoint you want to use in the current job. Specifying this value will copy
+            over the checkpoint data from `use_checkpoints_from_job`'s checkpoint_config
+            s3Uri to the current job's checkpoint_config s3Uri, making it available at
+            checkpoint_config.localPath during the job execution. Default: None
+
+        checkpoint_config (CheckpointConfig | None): Configuration that specifies the
+            location where checkpoint data is stored.
+            Default: CheckpointConfig(localPath='/opt/jobs/checkpoints',
+            s3Uri=f's3://{default_bucket_name}/jobs/{job_name}/checkpoints').
+
+        role_arn (str | None): A str providing the IAM role ARN used to execute the
+            script. Default: IAM role returned by AwsSession's `get_default_jobs_role()`.
+
+        stopping_condition (StoppingCondition | None): The maximum length of time, in seconds,
+            and the maximum number of tasks that a job can run before being forcefully stopped.
+            Default: StoppingCondition(maxRuntimeInSeconds=5 * 24 * 60 * 60).
+
+        output_data_config (OutputDataConfig | None): Specifies the location for the output of
+            the job.
+            Default: OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data',
+            kmsKeyId=None).
+
+        aws_session (AwsSession | None): AwsSession for connecting to AWS Services.
+            Default: AwsSession()
+
+        tags (dict[str, str] | None): Dict specifying the key-value pairs for tagging this job.
+            Default: {}.
+
+        logger (Logger): Logger object with which to write logs, such as task statuses
+            while waiting for task to be in a terminal state. Default is `getLogger(__name__)`
+    """
+    aws_session = aws_session or AwsSession()
+    _validate_python_version(aws_session, image_uri)
+
+    def _hybrid_job(entry_point):
+        @functools.wraps(entry_point)
+        def job_wrapper(*args, **kwargs):
+            with _IncludeModules(include_modules), tempfile.TemporaryDirectory(
+                dir="", prefix="decorator_job_"
+            ) as temp_dir:
+                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),
+                        ]
+                    )
+                    entry_point_file.write(template)
+
+                if dependencies:
+                    shutil.copy(Path(dependencies).resolve(), temp_dir_path / "requirements.txt")
+
+                job_args = {
+                    "device": device or "local:none/none",
+                    "source_module": temp_dir,
+                    "entry_point": (
+                        f"{temp_dir}.{entry_point_file_path.stem}:{entry_point.__name__}"
+                    ),
+                    "wait_until_complete": wait_until_complete,
+                    "job_name": job_name or _generate_default_job_name(func=entry_point),
+                    "hyperparameters": _log_hyperparameters(entry_point, args, kwargs),
+                    "logger": logger,
+                }
+                optional_args = {
+                    "image_uri": image_uri,
+                    "input_data": input_data,
+                    "instance_config": instance_config,
+                    "distribution": distribution,
+                    "checkpoint_config": checkpoint_config,
+                    "copy_checkpoints_from_job": copy_checkpoints_from_job,
+                    "role_arn": role_arn,
+                    "stopping_condition": stopping_condition,
+                    "output_data_config": output_data_config,
+                    "aws_session": aws_session,
+                    "tags": tags,
+                }
+                for key, value in optional_args.items():
+                    if value is not None:
+                        job_args[key] = value
+
+                job = _create_job(job_args, local)
+            return job
+
+        return job_wrapper
+
+    return _hybrid_job
+
+
+def _validate_python_version(aws_session: AwsSession, image_uri: str | None):
+    """Validate python version at job definition time"""
+    # user provides a custom image_uri
+    if image_uri and image_uri not in built_in_images(aws_session.region):
+        print(
+            "Skipping python version validation, make sure versions match "
+            "between local environment and container."
+        )
+    else:
+        # set default image_uri to base
+        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) == (
+            int(major_version),
+            int(minor_version),
+        ):
+            raise RuntimeError(
+                "Python version must match between local environment and container. "
+                f"Client is running Python {sys.version_info.major}.{sys.version_info.minor} "
+                f"locally, but container uses Python {major_version}.{minor_version}."
+            )
+
+
+class _IncludeModules:
+    def __init__(self, modules: str | ModuleType | Iterable[str | ModuleType] = None):
+        modules = modules or []
+        if isinstance(modules, (str, ModuleType)):
+            modules = [modules]
+        self._modules = [
+            (importlib.import_module(module) if isinstance(module, str) else module)
+            for module in modules
+        ]
+
+    def __enter__(self):
+        """Register included modules with cloudpickle to be pickled by value"""
+        for module in self._modules:
+            cloudpickle.register_pickle_by_value(module)
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        """Unregister included modules with cloudpickle to be pickled by value"""
+        for module in self._modules:
+            cloudpickle.unregister_pickle_by_value(module)
+
+
+def _serialize_entry_point(entry_point: Callable, args: tuple, kwargs: dict) -> str:
+    """Create an entry point from a function"""
+
+    def wrapped_entry_point():
+        """Partial function wrapping entry point with given parameters"""
+        return entry_point(*args, **kwargs)
+
+    try:
+        serialized = cloudpickle.dumps(wrapped_entry_point)
+    except Exception as e:
+        raise RuntimeError(
+            "Serialization failed for decorator hybrid job. If you are referencing "
+            "an object from outside the function scope, either directly or through "
+            "function parameters, try instantiating the object inside the decorated "
+            "function instead."
+        ) from e
+
+    return run_entry_point.format(
+        serialized=serialized,
+        function_name=entry_point.__name__,
+    )
+
+
+def _log_hyperparameters(entry_point: Callable, args: tuple, kwargs: dict):
+    """Capture function arguments as hyperparameters"""
+    signature = inspect.signature(entry_point)
+    bound_args = signature.bind(*args, **kwargs)
+    bound_args.apply_defaults()
+    hyperparameters = {}
+    for param, value in bound_args.arguments.items():
+        param_kind = signature.parameters[param].kind
+        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)
+        else:
+            warnings.warn(
+                "Positional only arguments will not be logged to the hyperparameters file."
+            )
+    return {name: _sanitize(value) for name, value in hyperparameters.items()}
+
+
+def _sanitize(hyperparameter: Any) -> str:
+    """Sanitize forbidden characters from hp strings"""
+    string_hp = str(hyperparameter)
+
+    sanitized = (
+        string_hp
+        # replace forbidden characters with close matches
+        .replace("\n", " ")
+        .replace("$", "?")
+        .replace("(", "{")
+        .replace("&", "+")
+        .replace("`", "'")
+        # not technically forbidden, but to avoid mismatched parens
+        .replace(")", "}")
+    )
+
+    # 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 sanitized
+
+
+def _process_input_data(input_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
+        input data matches exactly one directory: cwd/dirname/* -> channel/*
+        else (multiple matches, possibly including exact):
+            cwd/prefix_match -> channel/prefix_match, for each match
+    """
+    input_data = input_data or {}
+    if not isinstance(input_data, dict):
+        input_data = {"input": input_data}
+
+    def matches(prefix):
+        return [
+            str(path) for path in Path(prefix).parent.iterdir() if str(path).startswith(str(prefix))
+        ]
+
+    def is_prefix(path):
+        return len(matches(path)) > 1 or not Path(path).exists()
+
+    prefix_channels = set()
+    directory_channels = set()
+    file_channels = set()
+
+    for channel, data in input_data.items():
+        if AwsSession.is_s3_uri(str(data)):
+            channel_arg = f'channel="{channel}"' if channel != "input" else ""
+            print(
+                "Input data channels mapped to an S3 source will not be available in "
+                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):
+            prefix_channels.add(channel)
+        elif Path(data).is_dir():
+            directory_channels.add(channel)
+        else:
+            file_channels.add(channel)
+
+    return symlink_input_data.format(
+        prefix_matches={channel: matches(input_data[channel]) for channel in prefix_channels},
+        input_data_items=[
+            (channel, data)
+            for channel, data in input_data.items()
+            if channel in prefix_channels | directory_channels | file_channels
+        ],
+        prefix_channels=prefix_channels,
+        directory_channels=directory_channels,
+    )
+
+
+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
+
+        for aws_only_arg in [
+            "wait_until_complete",
+            "copy_checkpoints_from_job",
+            "instance_config",
+            "distribution",
+            "stopping_condition",
+            "tags",
+            "logger",
+        ]:
+            if aws_only_arg in job_args:
+                del job_args[aws_only_arg]
+        return LocalQuantumJob.create(**job_args)
+    else:
+        from braket.aws import AwsQuantumJob
+
+        return AwsQuantumJob.create(**job_args)
diff --git a/src/braket/jobs/image_uris.py b/src/braket/jobs/image_uris.py
index 0a29f2ce..eedc5e79 100644
--- a/src/braket/jobs/image_uris.py
+++ b/src/braket/jobs/image_uris.py
@@ -14,6 +14,7 @@
 import json
 import os
 from enum import Enum
+from functools import cache
 from typing import Dict
 
 
@@ -25,6 +26,11 @@ class Framework(str, Enum):
     PL_PYTORCH = "PL_PYTORCH"
 
 
+def built_in_images(region):
+    return {retrieve_image(framework, region) for framework in Framework}
+
+
+@cache
 def retrieve_image(framework: Framework, region: str) -> str:
     """Retrieves the ECR URI for the Docker image matching the specified arguments.
 
diff --git a/src/braket/jobs/local/local_job.py b/src/braket/jobs/local/local_job.py
index a6fc8d27..dc9b09e8 100644
--- a/src/braket/jobs/local/local_job.py
+++ b/src/braket/jobs/local/local_job.py
@@ -15,7 +15,7 @@
 
 import os
 import time
-from typing import Any, Dict, List, Union
+from typing import Any
 
 from braket.aws.aws_session import AwsSession
 from braket.jobs.config import CheckpointConfig, OutputDataConfig, S3DataSourceConfig
@@ -43,8 +43,8 @@ def create(
         job_name: str = None,
         code_location: str = None,
         role_arn: str = None,
-        hyperparameters: Dict[str, Any] = None,
-        input_data: Union[str, Dict, S3DataSourceConfig] = None,
+        hyperparameters: dict[str, Any] = None,
+        input_data: str | dict | S3DataSourceConfig = None,
         output_data_config: OutputDataConfig = None,
         checkpoint_config: CheckpointConfig = None,
         aws_session: AwsSession = None,
@@ -54,11 +54,12 @@ def create(
         docker container.
 
         Args:
-            device (str): ARN for the AWS device which is primarily accessed for the execution
-                of this hybrid job. Alternatively, a string of the format
-                "local:/" for using a local simulator for the hybrid job. This
-                string will be available as the environment variable `AMZN_BRAKET_DEVICE_ARN` inside
-                the hybrid job container when using a Braket container.
+            device (str): 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 queue so that only one hybrid job is running at a time. The device string is
+                accessible in the hybrid job instance as the environment variable
+                "AMZN_BRAKET_DEVICE_ARN". When using embedded simulators, you may provide the device
+                argument as a string of the form: "local:/".
 
             source_module (str): Path (absolute, relative or an S3 URI) to a python module to be
                 tarred and uploaded. If `source_module` is an S3 URI, it must point to a
@@ -84,12 +85,12 @@ def create(
             role_arn (str): This field is currently not used for local hybrid jobs. Local hybrid
                 jobs will use the current role's credentials. This may be subject to change.
 
-            hyperparameters (Dict[str, Any]): Hyperparameters accessible to the hybrid job.
+            hyperparameters (dict[str, Any]): Hyperparameters accessible to the hybrid job.
                 The hyperparameters are made accessible as a Dict[str, str] to the hybrid job.
                 For convenience, this accepts other types for keys and values, but `str()`
                 is called to convert them before being passed on. Default: None.
 
-            input_data (Union[str, Dict, S3DataSourceConfig]): Information about the training
+            input_data (str | dict | S3DataSourceConfig): Information about the training
                 data. Dictionary maps channel names to local paths or S3 URIs. Contents found
                 at any local paths will be uploaded to S3 at
                 f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local
@@ -213,7 +214,7 @@ def state(self, use_cached_value: bool = False) -> str:
         """
         return "COMPLETED"
 
-    def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]:
+    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
@@ -221,7 +222,7 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]:
                 `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
+            dict[str, Any]: None
         """
         pass
 
@@ -255,7 +256,7 @@ def result(
         self,
         poll_timeout_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_TIMEOUT,
         poll_interval_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_INTERVAL,
-    ) -> Dict[str, Any]:
+    ) -> dict[str, Any]:
         """Retrieves the hybrid job result persisted using save_job_result() function.
 
         Args:
@@ -265,7 +266,7 @@ def result(
                 Default: 5 seconds.
 
         Returns:
-            Dict[str, Any]: Dict specifying the hybrid job results.
+            dict[str, Any]: Dict specifying the hybrid job results.
         """
         try:
             with open(os.path.join(self.name, "results.json"), "r") as f:
@@ -281,7 +282,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.
 
@@ -299,7 +300,7 @@ def metrics(
             values may be integers, floats, strings or None.
 
         Returns:
-            Dict[str, List[Any]]: The metrics data.
+            dict[str, list[Any]]: The metrics data.
         """
         parser = LogMetricsParser()
         current_time = str(time.time())
diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py
index 1dbc7dc4..65d674c2 100644
--- a/src/braket/jobs/quantum_job_creation.py
+++ b/src/braket/jobs/quantum_job_creation.py
@@ -19,12 +19,13 @@
 import tarfile
 import tempfile
 import time
+import warnings
+from collections.abc import Callable
 from dataclasses import asdict
 from pathlib import Path
-from typing import Any, Dict, List, Optional, Tuple, Union
+from typing import Any
 
 from braket.aws.aws_session import AwsSession
-from braket.jobs import Framework, image_uris
 from braket.jobs.config import (
     CheckpointConfig,
     DeviceConfig,
@@ -33,6 +34,7 @@
     S3DataSourceConfig,
     StoppingCondition,
 )
+from braket.jobs.image_uris import Framework, retrieve_image
 
 
 def prepare_quantum_job(
@@ -43,8 +45,8 @@ def prepare_quantum_job(
     job_name: str = None,
     code_location: str = None,
     role_arn: str = None,
-    hyperparameters: Dict[str, Any] = None,
-    input_data: Union[str, Dict, S3DataSourceConfig] = None,
+    hyperparameters: dict[str, Any] = None,
+    input_data: str | dict | S3DataSourceConfig = None,
     instance_config: InstanceConfig = None,
     distribution: str = None,
     stopping_condition: StoppingCondition = None,
@@ -52,13 +54,17 @@ def prepare_quantum_job(
     copy_checkpoints_from_job: str = None,
     checkpoint_config: CheckpointConfig = None,
     aws_session: AwsSession = None,
-    tags: Dict[str, str] = None,
-) -> Dict:
+    tags: dict[str, str] = None,
+) -> dict:
     """Creates a hybrid job by invoking the Braket CreateJob API.
 
     Args:
-        device (str): ARN for the AWS device which is primarily
-            accessed for the execution of this hybrid job.
+        device (str): 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
+            queue so that only one hybrid job is running at a time. The device string is accessible
+            in the hybrid job instance as the environment variable "AMZN_BRAKET_DEVICE_ARN".
+            When using embedded simulators, you may provide the device argument as string of the
+            form: "local:/".
 
         source_module (str): Path (absolute, relative or an S3 URI) to a python module to be
             tarred and uploaded. If `source_module` is an S3 URI, it must point to a
@@ -86,12 +92,12 @@ def prepare_quantum_job(
         role_arn (str): A str providing the IAM role ARN used to execute the
             script. Default: IAM role returned by AwsSession's `get_default_jobs_role()`.
 
-        hyperparameters (Dict[str, Any]): Hyperparameters accessible to the hybrid job.
+        hyperparameters (dict[str, Any]): Hyperparameters accessible to the hybrid job.
             The hyperparameters are made accessible as a Dict[str, str] to the hybrid job.
             For convenience, this accepts other types for keys and values, but `str()`
             is called to convert them before being passed on. Default: None.
 
-        input_data (Union[str, Dict, S3DataSourceConfig]): Information about the training
+        input_data (str | dict | S3DataSourceConfig): Information about the training
             data. Dictionary maps channel names to local paths or S3 URIs. Contents found
             at any local paths will be uploaded to S3 at
             f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local
@@ -99,9 +105,9 @@ def prepare_quantum_job(
             channel name "input".
             Default: {}.
 
-        instance_config (InstanceConfig): Configuration of the instances to be used
-            to execute the hybrid job. Default: InstanceConfig(instanceType='ml.m5.large',
-            instanceCount=1, volumeSizeInGB=30, volumeKmsKey=None).
+        instance_config (InstanceConfig): Configuration of the instance(s) for running the
+            classical code for the hybrid job. Defaults to
+            `InstanceConfig(instanceType='ml.m5.large', instanceCount=1, volumeSizeInGB=30)`.
 
         distribution (str): A str that specifies how the hybrid job should be distributed. If set to
             "data_parallel", the hyperparameters for the hybrid job will be set to use data
@@ -130,11 +136,11 @@ def prepare_quantum_job(
         aws_session (AwsSession): AwsSession for connecting to AWS Services.
             Default: AwsSession()
 
-        tags (Dict[str, str]): Dict specifying the key-value pairs for tagging this hybrid job.
+        tags (dict[str, str]): Dict specifying the key-value pairs for tagging this hybrid job.
             Default: {}.
 
     Returns:
-        Dict: Hybrid job tracking the execution on Amazon Braket.
+        dict: Hybrid job tracking the execution on Amazon Braket.
 
     Raises:
         ValueError: Raises ValueError if the parameters are not valid.
@@ -149,7 +155,7 @@ 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)
+    job_name = job_name or _generate_default_job_name(image_uri=image_uri)
     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()}
@@ -181,7 +187,7 @@ def prepare_quantum_job(
             "compressionType": "GZIP",
         }
     }
-    image_uri = image_uri or image_uris.retrieve_image(Framework.BASE, aws_session.region)
+    image_uri = image_uri or retrieve_image(Framework.BASE, aws_session.region)
     algorithm_specification["containerImage"] = {"uri": image_uri}
     if not output_data_config.s3Path:
         output_data_config.s3Path = AwsSession.construct_s3_uri(
@@ -226,24 +232,37 @@ def prepare_quantum_job(
     return create_job_kwargs
 
 
-def _generate_default_job_name(image_uri: Optional[str]) -> str:
+def _generate_default_job_name(image_uri: str | None = None, func: Callable | None = None) -> str:
     """
-    Generate default hybrid job name using the image uri and a timestamp
+    Generate default job name using the image uri and entrypoint function.
+
     Args:
-        image_uri (Optional[str]): URI for the image container.
+        image_uri (str | None): URI for the image container.
+        func (Callable | None): The entry point function.
 
     Returns:
         str: Hybrid job name.
     """
-    if not image_uri:
-        job_type = "-default"
+    max_length = 50
+    timestamp = str(int(time.time() * 1000))
+
+    if func:
+        name = func.__name__.replace("_", "-")
+        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}."
+            )
     else:
-        job_type_match = re.search("/amazon-braket-(.*)-jobs:", image_uri) or re.search(
-            "/amazon-braket-([^:/]*)", image_uri
-        )
-        job_type = f"-{job_type_match.groups()[0]}" if job_type_match else ""
-
-    return f"braket-job{job_type}-{time.time() * 1000:.0f}"
+        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}"
+    return f"{name}-{timestamp}"
 
 
 def _process_s3_source_module(
@@ -337,7 +356,7 @@ def _tar_and_upload_to_code_location(
         aws_session.upload_to_s3(f"{temp_dir}/source.tar.gz", f"{code_location}/source.tar.gz")
 
 
-def _validate_params(dict_arr: Dict[str, Tuple[any, any]]) -> None:
+def _validate_params(dict_arr: dict[str, tuple[any, any]]) -> None:
     """
     Validate that config parameters are of the right type.
 
@@ -356,19 +375,19 @@ def _validate_params(dict_arr: Dict[str, Tuple[any, any]]) -> None:
 
 
 def _process_input_data(
-    input_data: Union[str, Dict, S3DataSourceConfig], job_name: str, aws_session: AwsSession
-) -> List[Dict[str, Any]]:
+    input_data: str | dict | S3DataSourceConfig, job_name: str, aws_session: AwsSession
+) -> list[dict[str, Any]]:
     """
     Convert input data into a list of dicts compatible with the Braket API.
     Args:
-        input_data (Union[str, Dict, S3DataSourceConfig]): Either a channel definition or a
+        input_data (str | dict | S3DataSourceConfig): Either a channel definition or a
             dictionary mapping channel names to channel definitions, where a channel definition
             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.
 
     Returns:
-        List[Dict[str, Any]]: A list of channel configs.
+        list[dict[str, Any]]: A list of channel configs.
     """
     if not isinstance(input_data, dict):
         input_data = {"input": input_data}
@@ -405,17 +424,17 @@ def _process_channel(
         return S3DataSourceConfig(s3_prefix)
 
 
-def _convert_input_to_config(input_data: Dict[str, S3DataSourceConfig]) -> List[Dict[str, Any]]:
+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
     configs compatible with the Braket API.
 
     Args:
-        input_data (Dict[str, S3DataSourceConfig]): A dictionary mapping channel names to
+        input_data (dict[str, S3DataSourceConfig]): A dictionary mapping channel names to
             S3DataSourceConfig objects.
 
     Returns:
-        List[Dict[str, Any]]: A list of channel configs.
+        list[dict[str, Any]]: A list of channel configs.
     """
     return [
         {
@@ -426,5 +445,5 @@ def _convert_input_to_config(input_data: Dict[str, S3DataSourceConfig]) -> List[
     ]
 
 
-def _exclude_nones_factory(items: List[Tuple]) -> Dict:
+def _exclude_nones_factory(items: list[tuple]) -> dict:
     return {k: v for k, v in items if v is not None}
diff --git a/test/integ_tests/job_test_module/job_test_submodule/job_test_submodule_file.py b/test/integ_tests/job_test_module/job_test_submodule/job_test_submodule_file.py
new file mode 100644
index 00000000..6c916a60
--- /dev/null
+++ b/test/integ_tests/job_test_module/job_test_submodule/job_test_submodule_file.py
@@ -0,0 +1,3 @@
+def submodule_helper():
+    print("import successful!")
+    return {"status": "SUCCESS"}
diff --git a/test/integ_tests/job_test_module/job_test_submodule/requirements.txt b/test/integ_tests/job_test_module/job_test_submodule/requirements.txt
new file mode 100644
index 00000000..e079f8a6
--- /dev/null
+++ b/test/integ_tests/job_test_module/job_test_submodule/requirements.txt
@@ -0,0 +1 @@
+pytest
diff --git a/test/integ_tests/job_test_script.py b/test/integ_tests/job_test_script.py
index 1fd7a364..8bc3b92c 100644
--- a/test/integ_tests/job_test_script.py
+++ b/test/integ_tests/job_test_script.py
@@ -51,3 +51,8 @@ def completed_job_script():
         save_job_checkpoint({"some_data": "abc"}, data_format=PersistedJobDataFormat.PICKLED_V4)
 
     print("Test job completed!!!!!")
+
+
+def job_helper():
+    print("import successful!")
+    return {"status": "SUCCESS"}
diff --git a/test/integ_tests/requirements.txt b/test/integ_tests/requirements.txt
new file mode 100644
index 00000000..e079f8a6
--- /dev/null
+++ b/test/integ_tests/requirements.txt
@@ -0,0 +1 @@
+pytest
diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py
index 568a53bc..80a68447 100644
--- a/test/integ_tests/test_create_quantum_job.py
+++ b/test/integ_tests/test_create_quantum_job.py
@@ -17,7 +17,13 @@
 import tempfile
 from pathlib import Path
 
+import job_test_script
+import pytest
+from job_test_module.job_test_submodule.job_test_submodule_file import submodule_helper
+
 from braket.aws.aws_quantum_job import AwsQuantumJob
+from braket.devices import Devices
+from braket.jobs import get_input_data_dir, hybrid_job, save_job_result
 
 
 def test_failed_quantum_job(aws_session, capsys):
@@ -152,14 +158,17 @@ def test_completed_quantum_job(aws_session, capsys):
 
     with tempfile.TemporaryDirectory() as temp_dir:
         os.chdir(temp_dir)
-        job.download_result()
-        assert (
-            Path(AwsQuantumJob.RESULTS_TAR_FILENAME).exists() and Path(downloaded_result).exists()
-        )
+        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}
-        os.chdir(current_dir)
+            # Check results match the expectations.
+            assert job.result() == {"converged": True, "energy": -0.2}
+        finally:
+            os.chdir(current_dir)
 
     # Check the logs and validate it contains required output.
     job.logs(wait=True)
@@ -178,3 +187,105 @@ def test_completed_quantum_job(aws_session, capsys):
 
     for data in logs_to_validate:
         assert data in log_data
+
+
+def test_decorator_job():
+    class MyClass:
+        attribute = "value"
+
+        def __str__(self):
+            return f"MyClass({self.attribute})"
+
+    @hybrid_job(
+        device=Devices.Amazon.SV1,
+        include_modules=[
+            "job_test_script",
+            # this can be removed once the container bdk version is updated
+            "braket.jobs",
+        ],
+        dependencies=str(Path("test", "integ_tests", "requirements.txt")),
+        input_data=str(Path("test", "integ_tests", "requirements")),
+    )
+    def decorator_job(a, b: int, c=0, d: float = 1.0, **extras):
+        save_job_result(job_test_script.job_helper())
+        with open(Path(get_input_data_dir()) / "requirements.txt", "r") as f:
+            assert f.readlines() == ["pytest\n"]
+        with open(Path("test", "integ_tests", "requirements.txt"), "r") as f:
+            assert f.readlines() == ["pytest\n"]
+        assert dir(pytest)
+        assert a.attribute == "value"
+        assert b == 2
+        assert c == 0
+        assert d == 5
+        assert extras["extra_arg"] == "extra_value"
+
+        hp_file = os.environ["AMZN_BRAKET_HP_FILE"]
+        with open(hp_file, "r") as f:
+            hyperparameters = json.load(f)
+        assert hyperparameters == {
+            "a": "MyClass{value}",
+            "b": "2",
+            "c": "0",
+            "d": "5",
+            "extra_arg": "extra_value",
+        }
+
+        with open("test/output_file.txt", "w") as f:
+            f.write("hello")
+
+    job = decorator_job(MyClass(), 2, d=5, extra_arg="extra_value")
+    assert job.result()["status"] == "SUCCESS"
+
+    current_dir = Path.cwd()
+    with tempfile.TemporaryDirectory() as temp_dir:
+        os.chdir(temp_dir)
+        try:
+            job.download_result()
+            with open(Path(job.name, "test", "output_file.txt"), "r") as f:
+                assert f.read() == "hello"
+            assert (
+                Path(job.name, "results.json").exists()
+                and Path(job.name, "test").exists()
+                and not Path(job.name, "test", "integ_tests").exists()
+            )
+        finally:
+            os.chdir(current_dir)
+
+
+def test_decorator_job_submodule():
+    @hybrid_job(
+        device=Devices.Amazon.SV1,
+        include_modules=[
+            "job_test_module",
+            # this can be removed once the container bdk version is updated
+            "braket.jobs",
+        ],
+        dependencies=Path(
+            "test", "integ_tests", "job_test_module", "job_test_submodule", "requirements.txt"
+        ),
+        input_data={
+            "my_input": str(Path("test", "integ_tests", "requirements.txt")),
+            "my_dir": str(Path("test", "integ_tests", "job_test_module")),
+        },
+    )
+    def decorator_job_submodule():
+        save_job_result(submodule_helper())
+        with open(Path(get_input_data_dir("my_input")) / "requirements.txt", "r") as f:
+            assert f.readlines() == ["pytest\n"]
+        with open(Path("test", "integ_tests", "requirements.txt"), "r") as f:
+            assert f.readlines() == ["pytest\n"]
+        with open(
+            Path(get_input_data_dir("my_dir")) / "job_test_submodule" / "requirements.txt", "r"
+        ) as f:
+            assert f.readlines() == ["pytest\n"]
+        with open(
+            Path(
+                "test", "integ_tests", "job_test_module", "job_test_submodule", "requirements.txt"
+            ),
+            "r",
+        ) as f:
+            assert f.readlines() == ["pytest\n"]
+        assert dir(pytest)
+
+    job = decorator_job_submodule()
+    assert job.result()["status"] == "SUCCESS"
diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py
index bfc65d54..d2ee45a3 100644
--- a/test/unit_tests/braket/aws/test_aws_session.py
+++ b/test/unit_tests/braket/aws/test_aws_session.py
@@ -1346,3 +1346,38 @@ def test_add_braket_user_agent(aws_session):
     aws_session.add_braket_user_agent(user_agent)
     aws_session.add_braket_user_agent(user_agent)
     aws_session._braket_user_agents.count(user_agent) == 1
+
+
+def test_get_full_image_tag(aws_session):
+    aws_session.ecr_client.batch_get_image.side_effect = (
+        {"images": [{"imageId": {"imageDigest": "my-digest"}}]},
+        {
+            "images": [
+                {"imageId": {"imageTag": "my-tag"}},
+                {"imageId": {"imageTag": "my-tag-py3"}},
+                {"imageId": {"imageTag": "my-tag-py310"}},
+                {"imageId": {"imageTag": "latest"}},
+            ]
+        },
+        AssertionError("Image tag not cached"),
+    )
+    image_uri = "123456.image_uri/repo-name:my-tag"
+    assert aws_session.get_full_image_tag(image_uri) == "my-tag-py310"
+    assert aws_session.get_full_image_tag(image_uri) == "my-tag-py310"
+
+
+def test_get_full_image_tag_no_py_info(aws_session):
+    aws_session.ecr_client.batch_get_image.side_effect = (
+        {"images": [{"imageId": {"imageDigest": "my-digest"}}]},
+        {
+            "images": [
+                {"imageId": {"imageTag": "my-tag"}},
+                {"imageId": {"imageTag": "latest"}},
+            ]
+        },
+    )
+    image_uri = "123456.image_uri/repo-name:my-tag"
+
+    no_py_info = "Full image tag missing."
+    with pytest.raises(ValueError, match=no_py_info):
+        aws_session.get_full_image_tag(image_uri)
diff --git a/test/unit_tests/braket/jobs/job_module.py b/test/unit_tests/braket/jobs/job_module.py
new file mode 100644
index 00000000..5dbc56d0
--- /dev/null
+++ b/test/unit_tests/braket/jobs/job_module.py
@@ -0,0 +1,2 @@
+def some_helper():
+    return "success"
diff --git a/test/unit_tests/braket/jobs/test_hybrid_job.py b/test/unit_tests/braket/jobs/test_hybrid_job.py
new file mode 100644
index 00000000..2877063d
--- /dev/null
+++ b/test/unit_tests/braket/jobs/test_hybrid_job.py
@@ -0,0 +1,495 @@
+import ast
+import importlib
+import re
+import sys
+import tempfile
+from logging import getLogger
+from pathlib import Path
+from ssl import SSLContext
+from unittest.mock import MagicMock, patch
+
+import job_module
+import pytest
+from cloudpickle import cloudpickle
+
+from braket.aws import AwsQuantumJob
+from braket.devices import Devices
+from braket.jobs import hybrid_job
+from braket.jobs.config import CheckpointConfig, InstanceConfig, OutputDataConfig, StoppingCondition
+from braket.jobs.hybrid_job import _sanitize, _serialize_entry_point
+from braket.jobs.local import LocalQuantumJob
+
+
+@pytest.fixture
+def aws_session():
+    aws_session = MagicMock()
+    python_version_str = f"py{sys.version_info.major}{sys.version_info.minor}"
+    aws_session.get_full_image_tag.return_value = f"1.0-cpu-{python_version_str}-ubuntu22.04"
+    aws_session.region = "us-west-2"
+    return aws_session
+
+
+@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_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest"
+
+    @hybrid_job(device=None, aws_session=aws_session)
+    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
+
+    source_module = mock_tempdir_name
+    entry_point = f"{mock_tempdir_name}.entry_point:my_entry"
+    wait_until_complete = False
+
+    device = "local:none/none"
+
+    my_entry()
+
+    mock_create.assert_called_with(
+        device=device,
+        source_module=source_module,
+        entry_point=entry_point,
+        wait_until_complete=wait_until_complete,
+        job_name="my-entry-123000",
+        hyperparameters={"c": "0", "d": "1.0"},
+        logger=getLogger("braket.jobs.hybrid_job"),
+        aws_session=aws_session,
+    )
+    assert mock_tempdir.return_value.__exit__.called
+
+
+@pytest.mark.parametrize("include_modules", (job_module, ["job_module"]))
+@patch("braket.jobs.image_uris.retrieve_image")
+@patch("sys.stdout")
+@patch("time.time", return_value=123.0)
+@patch("cloudpickle.register_pickle_by_value")
+@patch("cloudpickle.unregister_pickle_by_value")
+@patch("shutil.copy")
+@patch("builtins.open")
+@patch.object(AwsQuantumJob, "create")
+def test_decorator_non_defaults(
+    mock_create,
+    _mock_open,
+    mock_copy,
+    mock_register,
+    mock_unregister,
+    mock_time,
+    mock_stdout,
+    mock_retrieve,
+    include_modules,
+):
+    mock_retrieve.return_value = "should-not-be-used"
+    dependencies = "my_requirements.txt"
+    image_uri = "my_image.uri"
+    default_instance = InstanceConfig()
+    distribution = "data_parallel"
+    copy_checkpoints_from_job = "arn/other-job"
+    checkpoint_config = CheckpointConfig(localPath="local", s3Uri="s3")
+    role_arn = "role_arn"
+    stopping_condition = StoppingCondition(maxRuntimeInSeconds=10)
+    output_data_config = OutputDataConfig(s3Path="s3")
+    aws_session = MagicMock()
+    tags = {"my_tag": "my_value"}
+    logger = getLogger(__name__)
+
+    with tempfile.TemporaryDirectory() as tempdir:
+        Path(tempdir, "temp_dir").mkdir()
+        Path(tempdir, "temp_file").touch()
+
+        input_data = {
+            "my_prefix": "my_input_data",
+            "my_dir": Path(tempdir, "temp_dir"),
+            "my_file": Path(tempdir, "temp_file"),
+            "my_s3_prefix": "s3://bucket/path/to/prefix",
+        }
+
+        @hybrid_job(
+            device=Devices.Amazon.SV1,
+            include_modules=include_modules,
+            dependencies=dependencies,
+            image_uri=image_uri,
+            input_data=input_data,
+            wait_until_complete=True,
+            instance_config=default_instance,
+            distribution=distribution,
+            checkpoint_config=checkpoint_config,
+            copy_checkpoints_from_job=copy_checkpoints_from_job,
+            role_arn=role_arn,
+            stopping_condition=stopping_condition,
+            output_data_config=output_data_config,
+            aws_session=aws_session,
+            tags=tags,
+            logger=logger,
+        )
+        def my_entry(a, b: int, c=0, d: float = 1.0, **extras) -> str:
+            return "my entry return value"
+
+        mock_tempdir = MagicMock(spec=tempfile.TemporaryDirectory)
+        mock_tempdir_name = "job_temp_dir_00000"
+        mock_tempdir.__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"
+        wait_until_complete = True
+
+        s3_not_linked = (
+            "Input data channels mapped to an S3 source will not be available in the working "
+            'directory. Use `get_input_data_dir(channel="my_s3_prefix")` to read input data '
+            "from S3 source inside the job container."
+        )
+
+        with patch("tempfile.TemporaryDirectory", return_value=mock_tempdir):
+            my_entry("a", 2, 3, 4, extra_param="value", another=6)
+
+    mock_create.assert_called_with(
+        device=device,
+        source_module=source_module,
+        entry_point=entry_point,
+        image_uri=image_uri,
+        input_data=input_data,
+        wait_until_complete=wait_until_complete,
+        job_name="my-entry-123000",
+        instance_config=default_instance,
+        distribution=distribution,
+        hyperparameters={
+            "a": "a",
+            "b": "2",
+            "c": "3",
+            "d": "4",
+            "extra_param": "value",
+            "another": "6",
+        },
+        checkpoint_config=checkpoint_config,
+        copy_checkpoints_from_job=copy_checkpoints_from_job,
+        role_arn=role_arn,
+        stopping_condition=stopping_condition,
+        output_data_config=output_data_config,
+        aws_session=aws_session,
+        tags=tags,
+        logger=logger,
+    )
+    included_module = importlib.import_module("job_module")
+    mock_register.assert_called_with(included_module)
+    mock_unregister.assert_called_with(included_module)
+    mock_copy.assert_called_with(
+        Path("my_requirements.txt").resolve(), Path(mock_tempdir_name, "requirements.txt")
+    )
+    assert mock_tempdir.__exit__.called
+    mock_stdout.write.assert_any_call(s3_not_linked)
+
+
+@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_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest"
+    input_prefix = "my_input"
+
+    @hybrid_job(device=None, input_data=input_prefix, aws_session=aws_session)
+    def my_entry():
+        return "my entry return value"
+
+    mock_tempdir_name = "job_temp_dir_00000"
+    mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name
+
+    source_module = mock_tempdir_name
+    entry_point = f"{mock_tempdir_name}.entry_point:my_entry"
+    wait_until_complete = False
+
+    device = "local:none/none"
+
+    my_entry()
+
+    mock_create.assert_called_with(
+        device=device,
+        source_module=source_module,
+        entry_point=entry_point,
+        wait_until_complete=wait_until_complete,
+        job_name="my-entry-123000",
+        hyperparameters={},
+        logger=getLogger("braket.jobs.hybrid_job"),
+        input_data=input_prefix,
+        aws_session=aws_session,
+    )
+    assert mock_tempdir.return_value.__exit__.called
+
+
+@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_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest"
+
+    @hybrid_job(device=Devices.Amazon.SV1, local=True, aws_session=aws_session)
+    def my_entry():
+        return "my entry return value"
+
+    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()
+
+    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,
+    )
+    assert mock_tempdir.return_value.__exit__.called
+
+
+@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_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest"
+
+    @hybrid_job(
+        device=Devices.Amazon.SV1,
+        local=True,
+        wait_until_complete=True,
+        copy_checkpoints_from_job="arn/other-job",
+        instance_config=InstanceConfig(),
+        distribution="data_parallel",
+        stopping_condition=StoppingCondition(),
+        tags={"my_tag": "my_value"},
+        logger=getLogger(__name__),
+        aws_session=aws_session,
+    )
+    def my_entry():
+        return "my entry return value"
+
+    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()
+
+    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,
+    )
+    assert mock_tempdir.return_value.__exit__.called
+
+
+@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_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest"
+
+    @hybrid_job(device="local:braket/default", aws_session=aws_session)
+    def this_is_a_50_character_func_name_for_testing_names():
+        return "my entry return value"
+
+    mock_tempdir_name = "job_temp_dir_00000"
+    mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name
+
+    device = "local:braket/default"
+    source_module = mock_tempdir_name
+    entry_point = (
+        f"{mock_tempdir_name}.entry_point:this_is_a_50_character_func_name_for_testing_names"
+    )
+    wait_until_complete = False
+
+    with pytest.warns(UserWarning):
+        this_is_a_50_character_func_name_for_testing_names()
+
+        expected_job_name = "this-is-a-50-character-func-name-for-testin-123000"
+
+        mock_create.assert_called_with(
+            device=device,
+            source_module=source_module,
+            entry_point=entry_point,
+            wait_until_complete=wait_until_complete,
+            job_name=expected_job_name,
+            hyperparameters={},
+            logger=getLogger("braket.jobs.hybrid_job"),
+            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"], "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_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest"
+
+    @hybrid_job(device="local:braket/default", aws_session=aws_session)
+    def my_entry(pos_only, /):
+        return "my entry return value"
+
+    mock_tempdir_name = "job_temp_dir_00000"
+    mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name
+
+    device = "local:braket/default"
+    source_module = mock_tempdir_name
+    entry_point = f"{mock_tempdir_name}.entry_point:my_entry"
+    wait_until_complete = False
+
+    pos_only_warning = "Positional only arguments will not be logged to the hyperparameters file."
+    with pytest.warns(match=pos_only_warning):
+        my_entry("pos_only")
+
+    mock_create.assert_called_with(
+        device=device,
+        source_module=source_module,
+        entry_point=entry_point,
+        wait_until_complete=wait_until_complete,
+        job_name="my-entry-123000",
+        hyperparameters={},
+        logger=getLogger("braket.jobs.hybrid_job"),
+        aws_session=aws_session,
+    )
+    assert mock_tempdir.return_value.__exit__.called
+
+
+@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_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest"
+
+    @hybrid_job(device="local:braket/default", aws_session=aws_session)
+    def my_entry(*args):
+        return "my entry return value"
+
+    mock_tempdir_name = "job_temp_dir_00000"
+    mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name
+
+    device = "local:braket/default"
+    source_module = mock_tempdir_name
+    entry_point = f"{mock_tempdir_name}.entry_point:my_entry"
+    wait_until_complete = False
+
+    pos_only_warning = "Positional only arguments will not be logged to the hyperparameters file."
+    with pytest.warns(match=pos_only_warning):
+        my_entry("pos_only")
+
+    mock_create.assert_called_with(
+        device=device,
+        source_module=source_module,
+        entry_point=entry_point,
+        wait_until_complete=wait_until_complete,
+        job_name="my-entry-123000",
+        hyperparameters={},
+        logger=getLogger("braket.jobs.hybrid_job"),
+        aws_session=aws_session,
+    )
+    assert mock_tempdir.return_value.__exit__.called
+
+
+def test_serialization_error(aws_session):
+    ssl_context = SSLContext()
+
+    @hybrid_job(device=None, aws_session=aws_session)
+    def fails_serialization():
+        print(ssl_context)
+
+    serialization_failed = (
+        "Serialization failed for decorator hybrid job. If you are referencing "
+        "an object from outside the function scope, either directly or through "
+        "function parameters, try instantiating the object inside the decorated "
+        "function instead."
+    )
+    with pytest.raises(RuntimeError, match=serialization_failed):
+        fails_serialization()
+
+
+def test_serialization_wrapping():
+    def my_entry(*args, **kwargs):
+        print("something with \" and ' and \n")
+        return 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)
+    byte_str = ast.literal_eval(pickled_str)
+
+    recovered = cloudpickle.loads(byte_str)
+    assert recovered() == (args, kwargs)
+
+
+def test_python_validation(aws_session):
+    aws_session.get_full_image_tag.return_value = "1.0-cpu-py38-ubuntu22.04"
+
+    bad_version = (
+        "Python version must match between local environment and container. "
+        f"Client is running Python {sys.version_info.major}.{sys.version_info.minor} "
+        "locally, but container uses Python 3.8."
+    )
+    with pytest.raises(RuntimeError, match=bad_version):
+
+        @hybrid_job(device=None, aws_session=aws_session)
+        def my_job():
+            pass
+
+
+@pytest.mark.parametrize(
+    "hyperparameter, expected",
+    (
+        (
+            "with\nnewline",
+            "with newline",
+        ),
+        (
+            "with weird chars: (&$`)",
+            "with weird chars: {+?'}",
+        ),
+        (
+            "?" * 2600,
+            f"{'?'*2477}...{'?'*20}",
+        ),
+    ),
+)
+def test_sanitize_hyperparameters(hyperparameter, expected):
+    assert _sanitize(hyperparameter) == expected
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 f1a1f2fc..1b2c6b5b 100644
--- a/test/unit_tests/braket/jobs/test_quantum_job_creation.py
+++ b/test/unit_tests/braket/jobs/test_quantum_job_creation.py
@@ -22,7 +22,7 @@
 import pytest
 
 from braket.aws import AwsSession
-from braket.jobs import Framework, image_uris
+from braket.jobs import Framework, retrieve_image
 from braket.jobs.config import (
     CheckpointConfig,
     InstanceConfig,
@@ -349,7 +349,7 @@ def _translate_creation_args(create_job_args):
             "compressionType": "GZIP",
         }
     }
-    image_uri = image_uri or image_uris.retrieve_image(Framework.BASE, aws_session.region)
+    image_uri = image_uri or retrieve_image(Framework.BASE, aws_session.region)
     algorithm_specification["containerImage"] = {"uri": image_uri}
     tags = create_job_args.get("tags", {})
 
@@ -381,7 +381,8 @@ def test_generate_default_job_name(mock_time, image_uri):
     }
     job_type = job_type_mapping[image_uri]
     mock_time.return_value = datetime.datetime.now().timestamp()
-    assert _generate_default_job_name(image_uri) == f"braket-job{job_type}-{time.time() * 1000:.0f}"
+    timestamp = str(int(time.time() * 1000))
+    assert _generate_default_job_name(image_uri) == f"braket-job{job_type}-{timestamp}"
 
 
 @pytest.mark.parametrize(

From 56450e23a417c632bbebf68e97996cf6cd813079 Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Mon, 16 Oct 2023 11:24:36 -0700
Subject: [PATCH 0890/1165] fix: update integ test for non-py310 (#744)

---
 test/integ_tests/test_create_quantum_job.py | 149 +++++++++++---------
 1 file changed, 82 insertions(+), 67 deletions(-)

diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py
index 80a68447..7cab63c9 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 tempfile
 from pathlib import Path
+from warnings import warn
 
 import job_test_script
 import pytest
@@ -196,42 +197,46 @@ class MyClass:
         def __str__(self):
             return f"MyClass({self.attribute})"
 
-    @hybrid_job(
-        device=Devices.Amazon.SV1,
-        include_modules=[
-            "job_test_script",
-            # this can be removed once the container bdk version is updated
-            "braket.jobs",
-        ],
-        dependencies=str(Path("test", "integ_tests", "requirements.txt")),
-        input_data=str(Path("test", "integ_tests", "requirements")),
-    )
-    def decorator_job(a, b: int, c=0, d: float = 1.0, **extras):
-        save_job_result(job_test_script.job_helper())
-        with open(Path(get_input_data_dir()) / "requirements.txt", "r") as f:
-            assert f.readlines() == ["pytest\n"]
-        with open(Path("test", "integ_tests", "requirements.txt"), "r") as f:
-            assert f.readlines() == ["pytest\n"]
-        assert dir(pytest)
-        assert a.attribute == "value"
-        assert b == 2
-        assert c == 0
-        assert d == 5
-        assert extras["extra_arg"] == "extra_value"
-
-        hp_file = os.environ["AMZN_BRAKET_HP_FILE"]
-        with open(hp_file, "r") as f:
-            hyperparameters = json.load(f)
-        assert hyperparameters == {
-            "a": "MyClass{value}",
-            "b": "2",
-            "c": "0",
-            "d": "5",
-            "extra_arg": "extra_value",
-        }
-
-        with open("test/output_file.txt", "w") as f:
-            f.write("hello")
+    try:
+
+        @hybrid_job(
+            device=Devices.Amazon.SV1,
+            include_modules="job_test_script",
+            dependencies=str(Path("test", "integ_tests", "requirements.txt")),
+            input_data=str(Path("test", "integ_tests", "requirements")),
+        )
+        def decorator_job(a, b: int, c=0, d: float = 1.0, **extras):
+            save_job_result(job_test_script.job_helper())
+            with open(Path(get_input_data_dir()) / "requirements.txt", "r") as f:
+                assert f.readlines() == ["pytest\n"]
+            with open(Path("test", "integ_tests", "requirements.txt"), "r") as f:
+                assert f.readlines() == ["pytest\n"]
+            assert dir(pytest)
+            assert a.attribute == "value"
+            assert b == 2
+            assert c == 0
+            assert d == 5
+            assert extras["extra_arg"] == "extra_value"
+
+            hp_file = os.environ["AMZN_BRAKET_HP_FILE"]
+            with open(hp_file, "r") as f:
+                hyperparameters = json.load(f)
+            assert hyperparameters == {
+                "a": "MyClass{value}",
+                "b": "2",
+                "c": "0",
+                "d": "5",
+                "extra_arg": "extra_value",
+            }
+
+            with open("test/output_file.txt", "w") as f:
+                f.write("hello")
+
+    except RuntimeError as e:
+        if str(e).startswith("Python version must match between local environment and container."):
+            warn("skipping test due to python version mismatch")
+            return
+        raise e
 
     job = decorator_job(MyClass(), 2, d=5, extra_arg="extra_value")
     assert job.result()["status"] == "SUCCESS"
@@ -253,39 +258,49 @@ def decorator_job(a, b: int, c=0, d: float = 1.0, **extras):
 
 
 def test_decorator_job_submodule():
-    @hybrid_job(
-        device=Devices.Amazon.SV1,
-        include_modules=[
-            "job_test_module",
-            # this can be removed once the container bdk version is updated
-            "braket.jobs",
-        ],
-        dependencies=Path(
-            "test", "integ_tests", "job_test_module", "job_test_submodule", "requirements.txt"
-        ),
-        input_data={
-            "my_input": str(Path("test", "integ_tests", "requirements.txt")),
-            "my_dir": str(Path("test", "integ_tests", "job_test_module")),
-        },
-    )
-    def decorator_job_submodule():
-        save_job_result(submodule_helper())
-        with open(Path(get_input_data_dir("my_input")) / "requirements.txt", "r") as f:
-            assert f.readlines() == ["pytest\n"]
-        with open(Path("test", "integ_tests", "requirements.txt"), "r") as f:
-            assert f.readlines() == ["pytest\n"]
-        with open(
-            Path(get_input_data_dir("my_dir")) / "job_test_submodule" / "requirements.txt", "r"
-        ) as f:
-            assert f.readlines() == ["pytest\n"]
-        with open(
-            Path(
+    try:
+
+        @hybrid_job(
+            device=Devices.Amazon.SV1,
+            include_modules=[
+                "job_test_module",
+            ],
+            dependencies=Path(
                 "test", "integ_tests", "job_test_module", "job_test_submodule", "requirements.txt"
             ),
-            "r",
-        ) as f:
-            assert f.readlines() == ["pytest\n"]
-        assert dir(pytest)
+            input_data={
+                "my_input": str(Path("test", "integ_tests", "requirements.txt")),
+                "my_dir": str(Path("test", "integ_tests", "job_test_module")),
+            },
+        )
+        def decorator_job_submodule():
+            save_job_result(submodule_helper())
+            with open(Path(get_input_data_dir("my_input")) / "requirements.txt", "r") as f:
+                assert f.readlines() == ["pytest\n"]
+            with open(Path("test", "integ_tests", "requirements.txt"), "r") as f:
+                assert f.readlines() == ["pytest\n"]
+            with open(
+                Path(get_input_data_dir("my_dir")) / "job_test_submodule" / "requirements.txt", "r"
+            ) as f:
+                assert f.readlines() == ["pytest\n"]
+            with open(
+                Path(
+                    "test",
+                    "integ_tests",
+                    "job_test_module",
+                    "job_test_submodule",
+                    "requirements.txt",
+                ),
+                "r",
+            ) as f:
+                assert f.readlines() == ["pytest\n"]
+            assert dir(pytest)
+
+    except RuntimeError as e:
+        if str(e).startswith("Python version must match between local environment and container."):
+            warn("skipping test due to python version mismatch")
+            return
+        raise e
 
     job = decorator_job_submodule()
     assert job.result()["status"] == "SUCCESS"

From 44d33c03ba014dac3c5645bf90b97b070f95cbc9 Mon Sep 17 00:00:00 2001
From: ci 
Date: Mon, 16 Oct 2023 18:39:54 +0000
Subject: [PATCH 0891/1165] prepare release v1.58.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 3ea45409..41925987 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
 # Changelog
 
+## v1.58.0 (2023-10-16)
+
+### Features
+
+ * job decorator
+
+### Bug Fixes and Other Changes
+
+ * update integ test for non-py310
+
 ## v1.57.2 (2023-10-11)
 
 ### Bug Fixes and Other Changes
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index 69256550..dc9363a2 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.57.3.dev0"
+__version__ = "1.58.0"

From 120d7d26b1e4f1133ce28e1974d0c422535559b7 Mon Sep 17 00:00:00 2001
From: ci 
Date: Mon, 16 Oct 2023 18:39:54 +0000
Subject: [PATCH 0892/1165] update development version to v1.58.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 dc9363a2..4c7bbd32 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.58.0"
+__version__ = "1.58.1.dev0"

From 5ea59f486c555fc3206526e610f166fabaec894a Mon Sep 17 00:00:00 2001
From: "Tim (Yi-Ting)" 
Date: Mon, 16 Oct 2023 16:56:37 -0400
Subject: [PATCH 0893/1165] feature: upgrade AutoQASM to use oqpy 0.3.3 (#715)

---
 setup.py                                      |  5 +-
 src/braket/experimental/autoqasm/api.py       |  2 +-
 .../autoqasm/instructions/qubits.py           |  6 +--
 .../experimental/autoqasm/program/program.py  |  4 +-
 .../experimental/autoqasm/pulse/pulse.py      |  7 ++-
 .../autoqasm/types/conversions.py             |  1 +
 .../parametric/free_parameter_expression.py   | 52 +++++++++++++++++--
 src/braket/pulse/ast/approximation_parser.py  |  2 +-
 src/braket/pulse/ast/free_parameters.py       | 38 +++++++-------
 src/braket/pulse/ast/qasm_parser.py           |  6 +--
 src/braket/pulse/pulse_sequence.py            | 19 ++++---
 src/braket/pulse/waveforms.py                 | 16 +++---
 test/unit_tests/braket/circuits/test_gates.py |  2 +-
 .../autoqasm/test_gate_calibrations.py        |  4 +-
 .../experimental/autoqasm/test_types.py       |  4 +-
 .../braket/pulse/test_pulse_sequence.py       | 26 ++++------
 .../unit_tests/braket/pulse/test_waveforms.py | 13 ++---
 17 files changed, 123 insertions(+), 84 deletions(-)

diff --git a/setup.py b/setup.py
index 468c6466..ac3bc6bf 100644
--- a/setup.py
+++ b/setup.py
@@ -33,10 +33,7 @@
         # simulation of mid-circuit measurement, which AutoQASM requires.
         # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main.
         "amazon-braket-default-simulator @ git+https://github.com/aws/amazon-braket-default-simulator-python.git@46aea776976ad7f958d847c06f29f3a7976f5cf5#egg=amazon-braket-default-simulator",  # noqa E501
-        # Pin the latest commit of the qubit-array branch of ajberdy/oqpy.git to get the version of
-        # oqpy which contains changes that AutoQASM relies on, including the QubitArray type.
-        # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main.
-        "oqpy @ git+https://github.com/ajberdy/oqpy.git@26cf4f9089c3b381370917734d2d964c45c4458d#egg=oqpy",  # noqa E501
+        "oqpy~=0.3.3",
         "setuptools",
         "backoff",
         "boltons",
diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py
index 03c89c8d..e1eb3685 100644
--- a/src/braket/experimental/autoqasm/api.py
+++ b/src/braket/experimental/autoqasm/api.py
@@ -252,7 +252,7 @@ def _add_qubit_declaration(program_conversion_context: aq_program.ProgramConvers
         scope=aq_program.ProgramScope.MAIN
     )
     root_oqpy_program.declare(
-        [oqpy.QubitArray(aq_constants.QUBIT_REGISTER, num_qubits)],
+        [oqpy.Qubit(aq_constants.QUBIT_REGISTER, num_qubits)],
         to_beginning=True,
     )
 
diff --git a/src/braket/experimental/autoqasm/instructions/qubits.py b/src/braket/experimental/autoqasm/instructions/qubits.py
index 78a7035c..7a33bc88 100644
--- a/src/braket/experimental/autoqasm/instructions/qubits.py
+++ b/src/braket/experimental/autoqasm/instructions/qubits.py
@@ -59,9 +59,9 @@ def _get_physical_qubit_indices(qids: List[str]) -> List[int]:
 
 
 def _global_qubit_register(qubit_idx_expr: Union[int, str]) -> str:
-    # TODO: We should index into a oqpy.QubitArray rather
+    # TODO: We should index into a oqpy.Qubit register rather
     # than manually generating the string to index into
-    # a hard-coded global qubit array.
+    # a hard-coded global qubit register.
     return f"{constants.QUBIT_REGISTER}[{qubit_idx_expr}]"
 
 
@@ -120,7 +120,7 @@ def _(qid: str) -> oqpy.Qubit:
     if qid.startswith("$"):
         qubit_idx = qid[1:]
         try:
-            int(qubit_idx)
+            qubit_idx = int(qubit_idx)
         except ValueError:
             raise ValueError(f"invalid physical qubit label: '{qid}'")
         return oqpy.PhysicalQubits[qubit_idx]
diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py
index 10a654ed..411cbcb6 100644
--- a/src/braket/experimental/autoqasm/program/program.py
+++ b/src/braket/experimental/autoqasm/program/program.py
@@ -32,6 +32,7 @@
     OpenQASMSerializationProperties,
     SerializationProperties,
 )
+from braket.pulse.ast.qasm_parser import ast_to_qasm
 
 # Create the thread-local object for the program conversion context.
 _local = threading.local()
@@ -139,10 +140,11 @@ def to_ir(
             str: A representation of the program in the `ir_type` format.
         """
         if ir_type == IRType.OPENQASM:
-            openqasm_ir = self._oqpy_program.to_qasm(
+            openqasm_ast = self._oqpy_program.to_ast(
                 encal_declarations=self._has_pulse_control,
                 include_externs=serialization_properties.include_externs,
             )
+            openqasm_ir = ast_to_qasm(openqasm_ast)
             if self._has_pulse_control and not serialization_properties.auto_defcalgrammar:
                 openqasm_ir = openqasm_ir.replace('defcalgrammar "openpulse";\n', "")
             return openqasm_ir
diff --git a/src/braket/experimental/autoqasm/pulse/pulse.py b/src/braket/experimental/autoqasm/pulse/pulse.py
index 9ae6c1f6..e0581b1b 100644
--- a/src/braket/experimental/autoqasm/pulse/pulse.py
+++ b/src/braket/experimental/autoqasm/pulse/pulse.py
@@ -27,6 +27,7 @@
     is_qubit_identifier_type,
 )
 from braket.experimental.autoqasm.types import BitVar
+from braket.parametric.free_parameter import FreeParameter
 from braket.pulse import PulseSequence
 from braket.pulse.frame import Frame
 from braket.pulse.pulse_sequence import _validate_uniqueness
@@ -127,19 +128,21 @@ def capture_v0(frame: Frame) -> None:
 
 def delay(
     qubits_or_frames: Union[Frame, List[Frame], QubitIdentifierType, List[QubitIdentifierType]],
-    duration: float,
+    duration: Union[float, oqpy.FloatVar],
 ) -> None:
     """Adds an instruction to advance the frame clock by the specified `duration` value.
 
     Args:
         qubits_or_frames (Union[Frame, List[Frame], QubitIdentifierType, List[QubitIdentifierType]]):
             Qubits or frame(s) on which the delay needs to be introduced.
-        duration (float): Value (in seconds) defining the duration of the delay.
+        duration (Union[float, FloatVar]): Value (in seconds) defining the duration of the delay.
     """  # noqa: E501
     if not isinstance(qubits_or_frames, List):
         qubits_or_frames = [qubits_or_frames]
     if all(is_qubit_identifier_type(q) for q in qubits_or_frames):
         qubits_or_frames = QubitSet(_get_physical_qubit_indices(qubits_or_frames))
+    if isinstance(duration, oqpy.FloatVar):
+        duration = FreeParameter(duration.name)
     _pulse_instruction("delay", qubits_or_frames, duration)
 
 
diff --git a/src/braket/experimental/autoqasm/types/conversions.py b/src/braket/experimental/autoqasm/types/conversions.py
index 61b7c0c6..8761b024 100644
--- a/src/braket/experimental/autoqasm/types/conversions.py
+++ b/src/braket/experimental/autoqasm/types/conversions.py
@@ -52,6 +52,7 @@ def map_type(python_type: type) -> type:
             raise errors.ParameterTypeError(
                 f"Unsupported array type: {item_type}. AutoQASM arrays only support ints."
             )
+
         # TODO: Update array length to match the input rather than hardcoding
         # OQPY and QASM require arrays have a set length. python doesn't require this,
         # so the length of the array is indeterminate.
diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py
index cd5fd7f8..6b06063e 100644
--- a/src/braket/parametric/free_parameter_expression.py
+++ b/src/braket/parametric/free_parameter_expression.py
@@ -15,8 +15,18 @@
 
 import ast
 from numbers import Number
-from typing import Any, Union
-
+from typing import Any, Optional, Union
+
+from openpulse.ast import (
+    ClassicalType,
+    DurationLiteral,
+    DurationType,
+    Expression,
+    FloatType,
+    Identifier,
+    TimeUnit,
+)
+from oqpy import Program
 from sympy import Expr, Float, Symbol, sympify
 
 
@@ -30,7 +40,11 @@ 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, Expr, str],
+        _type: Optional[ClassicalType] = None,
+    ):
         """
         Initializes a FreeParameterExpression. Best practice is to initialize using
         FreeParameters and Numbers. Not meant to be initialized directly.
@@ -39,6 +53,10 @@ def __init__(self, expression: Union[FreeParameterExpression, Number, Expr, str]
 
         Args:
             expression (Union[FreeParameterExpression, Number, Expr, str]): The expression to use.
+            _type (Optional[ClassicalType]): The OpenQASM3 type associated with the expression.
+                Subtypes of openqasm3.ast.ClassicalType are used to specify how to express the
+                expression in the OpenQASM3 IR. Any type other than DurationType is considered
+                as FloatType.
 
         Examples:
             >>> expression_1 = FreeParameter("theta") * FreeParameter("alpha")
@@ -51,8 +69,11 @@ def __init__(self, expression: Union[FreeParameterExpression, Number, Expr, str]
             ast.Pow: self.__pow__,
             ast.USub: self.__neg__,
         }
+        self._type = _type if _type is not None else FloatType()
         if isinstance(expression, FreeParameterExpression):
             self._expression = expression.expression
+            if _type is None:
+                self._type = expression._type
         elif isinstance(expression, (Number, Expr)):
             self._expression = expression
         elif isinstance(expression, str):
@@ -170,6 +191,31 @@ def __repr__(self) -> str:
         """
         return repr(self.expression)
 
+    def to_ast(self, program: Program) -> Expression:
+        """Creates an AST node for the :class:'FreeParameterExpression'.
+
+        Args:
+            program (Program): Unused.
+
+        Returns:
+            Expression: The AST node.
+        """
+        if isinstance(self._type, DurationType):
+            return DurationLiteral(_FreeParameterExpressionIdentifier(self), TimeUnit.s)
+        return _FreeParameterExpressionIdentifier(self)
+
+
+class _FreeParameterExpressionIdentifier(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
+
 
 def subs_if_free_parameter(parameter: Any, **kwargs) -> Any:
     """Substitute a free parameter with the given kwargs, if any.
diff --git a/src/braket/pulse/ast/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py
index 4398d281..851eb675 100644
--- a/src/braket/pulse/ast/approximation_parser.py
+++ b/src/braket/pulse/ast/approximation_parser.py
@@ -58,7 +58,7 @@ 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={"pi": np.pi}, 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)
 
diff --git a/src/braket/pulse/ast/free_parameters.py b/src/braket/pulse/ast/free_parameters.py
index 1581ddd8..639645b2 100644
--- a/src/braket/pulse/ast/free_parameters.py
+++ b/src/braket/pulse/ast/free_parameters.py
@@ -14,37 +14,30 @@
 from typing import Union
 
 from openpulse import ast
-from openqasm3.ast import DurationLiteral
 from openqasm3.visitor import QASMTransformer
+from oqpy.program import Program
+from oqpy.timing import OQDurationLiteral
 
-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 braket.parametric.free_parameter_expression import (
+    FreeParameterExpression,
+    _FreeParameterExpressionIdentifier,
+)
 
 
 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(
-        self, identifier: ast.Identifier
+        self, identifier: _FreeParameterExpressionIdentifier
     ) -> Union[_FreeParameterExpressionIdentifier, ast.FloatLiteral]:
         """Visit a FreeParameterExpressionIdentifier.
         Args:
-            identifier (Identifier): The identifier.
+            identifier (_FreeParameterExpressionIdentifier): The identifier.
 
         Returns:
             Union[_FreeParameterExpressionIdentifier, FloatLiteral]: The transformed expression.
@@ -55,7 +48,7 @@ def visit__FreeParameterExpressionIdentifier(
         else:
             return ast.FloatLiteral(new_value)
 
-    def visit_DurationLiteral(self, duration_literal: DurationLiteral) -> DurationLiteral:
+    def visit_DurationLiteral(self, duration_literal: ast.DurationLiteral) -> ast.DurationLiteral:
         """Visit Duration Literal.
             node.value, node.unit (node.unit.name, node.unit.value)
             1
@@ -65,6 +58,11 @@ def visit_DurationLiteral(self, duration_literal: DurationLiteral) -> DurationLi
             DurationLiteral: The transformed duration literal.
         """
         duration = duration_literal.value
-        if not isinstance(duration, FreeParameterExpression):
+        if not isinstance(duration, _FreeParameterExpressionIdentifier):
             return duration_literal
-        return DurationLiteral(duration.subs(self.param_values), duration_literal.unit)
+        new_duration = duration.expression.subs(self.param_values)
+        if isinstance(new_duration, FreeParameterExpression):
+            return ast.DurationLiteral(
+                _FreeParameterExpressionIdentifier(new_duration), duration_literal.unit
+            )
+        return OQDurationLiteral(new_duration).to_ast(self.program)
diff --git a/src/braket/pulse/ast/qasm_parser.py b/src/braket/pulse/ast/qasm_parser.py
index bd1b26e4..1f5c48cc 100644
--- a/src/braket/pulse/ast/qasm_parser.py
+++ b/src/braket/pulse/ast/qasm_parser.py
@@ -18,7 +18,7 @@
 from openqasm3.ast import DurationLiteral
 from openqasm3.printer import PrinterState
 
-from braket.parametric.free_parameter_expression import FreeParameterExpression
+from braket.pulse.ast.free_parameters import _FreeParameterExpressionIdentifier
 
 
 class _PulsePrinter(Printer):
@@ -46,8 +46,8 @@ def visit_DurationLiteral(self, node: DurationLiteral, context: PrinterState) ->
             context (PrinterState): The printer state context.
         """
         duration = node.value
-        if isinstance(duration, FreeParameterExpression):
-            self.stream.write(f"({duration.expression}){node.unit.name}")
+        if isinstance(duration, _FreeParameterExpressionIdentifier):
+            self.stream.write(f"({duration.expression}) * 1{node.unit.name}")
         else:
             super().visit_DurationLiteral(node, context)
 
diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py
index 8ccd703f..05ef7dbc 100644
--- a/src/braket/pulse/pulse_sequence.py
+++ b/src/braket/pulse/pulse_sequence.py
@@ -183,10 +183,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)
+        duration = self._format_parameter_ast(duration, _type=ast.DurationType())
         if not isinstance(qubits_or_frames, QubitSet):
             if not isinstance(qubits_or_frames, list):
                 qubits_or_frames = [qubits_or_frames]
@@ -276,7 +273,7 @@ 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(simplify_constants=False)
         new_program.declared_vars = program.declared_vars
@@ -325,13 +322,19 @@ def to_ir(self) -> str:
         return ast_to_qasm(tree)
 
     def _format_parameter_ast(
-        self, parameter: Union[float, FreeParameterExpression]
+        self,
+        parameter: Union[float, FreeParameterExpression],
+        _type: ast.ClassicalType = ast.FloatType(),
     ) -> Union[float, _FreeParameterExpressionIdentifier]:
         if isinstance(parameter, FreeParameterExpression):
             for p in parameter.expression.free_symbols:
                 self._free_parameters.add(FreeParameter(p.name))
-            return _FreeParameterExpressionIdentifier(parameter)
-        return parameter
+            return (
+                FreeParameterExpression(parameter, _type)
+                if isinstance(_type, ast.DurationType)
+                else parameter
+            )
+        return OQDurationLiteral(parameter) if isinstance(_type, ast.DurationType) else 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 dbf89e14..b7dafac5 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):
@@ -454,14 +452,12 @@ def _make_identifier_name() -> str:
 
 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
+) -> Union[FreeParameterExpression, OQPyExpression]:
+    return (
+        FreeParameterExpression(parameter, duration)
+        if isinstance(parameter, FreeParameterExpression) and is_duration_type
+        else parameter
+    )
 
 
 def _parse_waveform_from_calibration_schema(waveform: dict) -> Waveform:
diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py
index 05b98ade..23483486 100644
--- a/test/unit_tests/braket/circuits/test_gates.py
+++ b/test/unit_tests/braket/circuits/test_gates.py
@@ -962,7 +962,7 @@ def to_ir(pulse_gate):
         [
             "cal {",
             "    set_frequency(user_frame, b + 3);",
-            "    delay[(1000000000.0*c)ns] user_frame;",
+            "    delay[(c) * 1s] user_frame;",
             "}",
         ]
     )
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py
index db2e6e5c..ed7a2516 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py
@@ -70,7 +70,7 @@ def my_program():
         """
         OPENQASM 3.0;
         defcal rx(angle[32] angle) $1 {
-            delay[angle * 1s] $1;
+            delay[(angle) * 1s] $1;
         }
         rx(1.0) $1;
         """
@@ -222,7 +222,7 @@ def my_program():
         }
         defcal my_gate(angle[32] a) $0 {
             barrier $0;
-            delay[a * 1s] $0;
+            delay[(a) * 1s] $0;
         }
         qubit[3] __qubits__;
         my_gate(0.123) __qubits__[2];
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py
index 834fc54e..c5b97577 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_types.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py
@@ -207,7 +207,7 @@ def test_return_python_array():
     """Test returning a python array of ints."""
 
     @aq.subroutine
-    def tester(arr: List[int]) -> List[int]:
+    def tester() -> List[int]:
         return [1, 2, 3]
 
     @aq.main(num_qubits=4)
@@ -215,7 +215,7 @@ def main():
         tester()
 
     expected = """OPENQASM 3.0;
-def tester(array[int[32], 10] arr) -> array[int[32], 10] {
+def tester() -> array[int[32], 10] {
     array[int[32], 10] retval_;
     retval_ = {1, 2, 3};
     return retval_;
diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py
index 637083c5..4890a4b3 100644
--- a/test/unit_tests/braket/pulse/test_pulse_sequence.py
+++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py
@@ -125,11 +125,10 @@ 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);",
+            "    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);",
@@ -138,11 +137,8 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined
             "    shift_phase(predefined_frame_1, a + 2*b);",
             "    set_scale(predefined_frame_1, a + 2*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[(a + 2*b) * 1s] predefined_frame_1, predefined_frame_2;",
+            "    delay[(a + 2*b) * 1s] predefined_frame_1;",
             "    delay[1.0ms] predefined_frame_1;",
             "    barrier predefined_frame_1, predefined_frame_2;",
             "    play(predefined_frame_1, gauss_wf);",
@@ -173,7 +169,7 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined
         [
             "OPENQASM 3.0;",
             "cal {",
-            "    waveform gauss_wf = gaussian(1.0ms, (1000000000.0*sigma_g)ns, 1, false);",
+            "    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};",
@@ -184,8 +180,8 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined
             "    shift_phase(predefined_frame_1, a + 4);",
             "    set_scale(predefined_frame_1, a + 4);",
             "    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[(a + 4) * 1s] predefined_frame_1, predefined_frame_2;",
+            "    delay[(a + 4) * 1s] predefined_frame_1;",
             "    delay[1.0ms] predefined_frame_1;",
             "    barrier predefined_frame_1, predefined_frame_2;",
             "    play(predefined_frame_1, gauss_wf);",
@@ -216,8 +212,8 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined
             "    shift_phase(predefined_frame_1, 5);",
             "    set_scale(predefined_frame_1, 5);",
             "    psb[0] = capture_v0(predefined_frame_1);",
-            "    delay[5000000000.00000ns] predefined_frame_1, predefined_frame_2;",
-            "    delay[5000000000.00000ns] predefined_frame_1;",
+            "    delay[5s] predefined_frame_1, predefined_frame_2;",
+            "    delay[5s] predefined_frame_1;",
             "    delay[1.0ms] predefined_frame_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 b2421ca6..5e8e7811 100644
--- a/test/unit_tests/braket/pulse/test_waveforms.py
+++ b/test/unit_tests/braket/pulse/test_waveforms.py
@@ -101,8 +101,7 @@ 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);",
+        "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)
@@ -185,8 +184,8 @@ def test_drag_gaussian_wf_free_params():
     _assert_wf_qasm(
         wf,
         "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)
@@ -198,8 +197,7 @@ def test_drag_gaussian_wf_free_params():
     ]
     _assert_wf_qasm(
         wf_2,
-        "waveform d_gauss_wf = drag_gaussian(600.0ms, (1000000000.0*sigma_b "
-        "+ 400000000.0)ns, beta_y, amp_z, false);",
+        "waveform d_gauss_wf = drag_gaussian(600.0ms, (sigma_b + 0.4) * 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)
@@ -239,8 +237,7 @@ 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);",
+        "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)

From 06a993593973c456d0db430b865d7e9267e06568 Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Mon, 16 Oct 2023 16:16:47 -0700
Subject: [PATCH 0894/1165] fix: use separate aws session for python validation
 (#745)

---
 src/braket/jobs/hybrid_job.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py
index 83ba9a9f..521da7fe 100644
--- a/src/braket/jobs/hybrid_job.py
+++ b/src/braket/jobs/hybrid_job.py
@@ -150,8 +150,7 @@ 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 is `getLogger(__name__)`
     """
-    aws_session = aws_session or AwsSession()
-    _validate_python_version(aws_session, image_uri)
+    _validate_python_version(image_uri, aws_session)
 
     def _hybrid_job(entry_point):
         @functools.wraps(entry_point)
@@ -209,8 +208,9 @@ def job_wrapper(*args, **kwargs):
     return _hybrid_job
 
 
-def _validate_python_version(aws_session: AwsSession, image_uri: str | None):
+def _validate_python_version(image_uri: str | None, aws_session: AwsSession | None = None):
     """Validate python version at job definition time"""
+    aws_session = aws_session or AwsSession()
     # user provides a custom image_uri
     if image_uri and image_uri not in built_in_images(aws_session.region):
         print(

From eb5813f6bbda8d122cf46de995a5247a296be428 Mon Sep 17 00:00:00 2001
From: ci 
Date: Mon, 16 Oct 2023 23:32:34 +0000
Subject: [PATCH 0895/1165] prepare release v1.58.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 41925987..204d12dc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## v1.58.1 (2023-10-16)
+
+### Bug Fixes and Other Changes
+
+ * use separate aws session for python validation
+
 ## v1.58.0 (2023-10-16)
 
 ### Features
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index 4c7bbd32..9ffdb910 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.58.1.dev0"
+__version__ = "1.58.1"

From adee440d0b587fe505ec4a734b5b242e60ff9db5 Mon Sep 17 00:00:00 2001
From: ci 
Date: Mon, 16 Oct 2023 23:32:34 +0000
Subject: [PATCH 0896/1165] update development version to v1.58.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 9ffdb910..2b4cc2e9 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.58.1"
+__version__ = "1.58.2.dev0"

From 49260972dcd2fb7fc0a72c15bddfeecd2054342b Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Tue, 17 Oct 2023 12:04:35 -0700
Subject: [PATCH 0897/1165] feat: use region property (#746)

---
 src/braket/aws/aws_quantum_job.py                  |  2 +-
 test/unit_tests/braket/aws/test_aws_quantum_job.py | 10 +++++-----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py
index 7f6fdfd0..76ff0648 100644
--- a/src/braket/aws/aws_quantum_job.py
+++ b/src/braket/aws/aws_quantum_job.py
@@ -235,7 +235,7 @@ def _is_valid_aws_session_region_for_job_arn(aws_session: AwsSession, job_arn: s
         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.braket_client.meta.region_name
+        return job_region == aws_session.region
 
     @staticmethod
     def _default_session_for_job_arn(job_arn: str) -> AwsSession:
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 ffc9bdb3..19b46d72 100644
--- a/test/unit_tests/braket/aws/test_aws_quantum_job.py
+++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py
@@ -43,7 +43,7 @@ def fake_copy_session(region):
 
     _aws_session.copy_session.side_effect = fake_copy_session
     _aws_session.list_keys.return_value = ["job-path/output/model.tar.gz"]
-    _aws_session.region = "us-test-1"
+    _aws_session.region = job_region
 
     _braket_client_mock = Mock(meta=Mock(region_name=job_region))
     _aws_session.braket_client = _braket_client_mock
@@ -142,7 +142,7 @@ def quantum_job(quantum_job_arn, aws_session):
 
 
 def test_equality(quantum_job_arn, aws_session, job_region):
-    new_aws_session = Mock(braket_client=Mock(meta=Mock(region_name=job_region)))
+    new_aws_session = Mock(region=job_region)
     quantum_job_1 = AwsQuantumJob(quantum_job_arn, aws_session)
     quantum_job_2 = AwsQuantumJob(quantum_job_arn, aws_session)
     quantum_job_3 = AwsQuantumJob(quantum_job_arn, new_aws_session)
@@ -195,7 +195,7 @@ def test_quantum_job_constructor_invalid_region(aws_session):
 
 @patch("braket.aws.aws_quantum_job.boto3.Session")
 def test_quantum_job_constructor_explicit_session(mock_session, quantum_job_arn, job_region):
-    aws_session_mock = Mock(braket_client=Mock(meta=Mock(region_name=job_region)))
+    aws_session_mock = Mock(region=job_region)
     job = AwsQuantumJob(quantum_job_arn, aws_session_mock)
     assert job._aws_session == aws_session_mock
     assert job.arn == quantum_job_arn
@@ -507,7 +507,7 @@ def role_arn():
 
 @pytest.fixture(
     params=[
-        "arn:aws:braket:us-test-1::device/qpu/test/device-name",
+        "arn:aws:braket:us-west-2::device/qpu/test/device-name",
         "arn:aws:braket:::device/qpu/test/device-name",
     ]
 )
@@ -961,7 +961,7 @@ def test_no_region_routing_simulator(aws_session):
     )
 
     device_arn = "arn:aws:braket:::device/simulator/test/device-name"
-    device_not_found = f"Simulator '{device_arn}' not found in 'us-test-1'"
+    device_not_found = f"Simulator '{device_arn}' not found in 'us-west-2'"
     with pytest.raises(ValueError, match=device_not_found):
         AwsQuantumJob._initialize_session(aws_session, device_arn, logger)
 

From 2eccce31367fcf01e34bcd8a25b41dfb8693c7fb Mon Sep 17 00:00:00 2001
From: ci 
Date: Tue, 17 Oct 2023 19:20:46 +0000
Subject: [PATCH 0898/1165] prepare release v1.59.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 204d12dc..0d5b70c2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## v1.59.0 (2023-10-17)
+
+### Features
+
+ * use region property
+
 ## v1.58.1 (2023-10-16)
 
 ### Bug Fixes and Other Changes
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index 2b4cc2e9..79c88725 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.58.2.dev0"
+__version__ = "1.59.0"

From 4cc0ebad9f486348284b35cbc26320b54f1f4bc8 Mon Sep 17 00:00:00 2001
From: ci 
Date: Tue, 17 Oct 2023 19:20:46 +0000
Subject: [PATCH 0899/1165] update development version to v1.59.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 79c88725..ee48017d 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.59.0"
+__version__ = "1.59.1.dev0"

From f4957ec6c6268f544c28d7cd4cd9e6cd9d155e46 Mon Sep 17 00:00:00 2001
From: Milan <30416311+krneta@users.noreply.github.com>
Date: Tue, 17 Oct 2023 17:34:13 -0700
Subject: [PATCH 0900/1165] fix: doc fixes (#748)

---
 src/braket/jobs/config.py               |  2 +-
 src/braket/jobs/data_persistence.py     |  6 +-
 src/braket/jobs/hybrid_job.py           | 78 ++++++++++++++-----------
 src/braket/jobs/image_uris.py           |  4 +-
 src/braket/jobs/quantum_job_creation.py |  8 +--
 5 files changed, 52 insertions(+), 46 deletions(-)

diff --git a/src/braket/jobs/config.py b/src/braket/jobs/config.py
index b846a8bf..432e740e 100644
--- a/src/braket/jobs/config.py
+++ b/src/braket/jobs/config.py
@@ -63,7 +63,7 @@ class S3DataSourceConfig:
     def __init__(
         self,
         s3_data: str,
-        content_type: str | None = None,
+        content_type: str = None,
     ):
         """Create a definition for input data used by a Braket Hybrid job.
 
diff --git a/src/braket/jobs/data_persistence.py b/src/braket/jobs/data_persistence.py
index ccd9a47d..6ef5a6d1 100644
--- a/src/braket/jobs/data_persistence.py
+++ b/src/braket/jobs/data_persistence.py
@@ -65,9 +65,7 @@ def save_job_checkpoint(
         f.write(persisted_data.json())
 
 
-def load_job_checkpoint(
-    job_name: str | None = None, checkpoint_file_suffix: str = ""
-) -> dict[str, Any]:
+def load_job_checkpoint(job_name: str = None, checkpoint_file_suffix: str = "") -> dict[str, Any]:
     """
     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
@@ -80,7 +78,7 @@ def load_job_checkpoint(
 
 
     Args:
-        job_name (str | None): str that specifies the name of the job whose checkpoints
+        job_name (str): str that specifies the name of the job whose checkpoints
             are to be loaded. Default: current job name.
 
         checkpoint_file_suffix (str): str specifying the file suffix that is used to
diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py
index 521da7fe..8de496f5 100644
--- a/src/braket/jobs/hybrid_job.py
+++ b/src/braket/jobs/hybrid_job.py
@@ -25,7 +25,7 @@
 from logging import Logger, getLogger
 from pathlib import Path
 from types import ModuleType
-from typing import Any
+from typing import Any, Dict, List
 
 import cloudpickle
 
@@ -45,22 +45,22 @@
 
 def hybrid_job(
     *,
-    device: str | None,
+    device: str,
     include_modules: str | ModuleType | Iterable[str | ModuleType] = None,
-    dependencies: str | Path | None = None,
+    dependencies: str | Path = None,
     local: bool = False,
-    job_name: str | None = None,
-    image_uri: str | None = None,
+    job_name: str = None,
+    image_uri: str = None,
     input_data: str | dict | S3DataSourceConfig = None,
     wait_until_complete: bool = False,
-    instance_config: InstanceConfig | None = None,
-    distribution: str | None = None,
-    copy_checkpoints_from_job: str | None = None,
-    checkpoint_config: CheckpointConfig | None = None,
-    role_arn: str | None = None,
-    stopping_condition: StoppingCondition | None = None,
-    output_data_config: OutputDataConfig | None = None,
-    aws_session: AwsSession | None = None,
+    instance_config: InstanceConfig = None,
+    distribution: str = None,
+    copy_checkpoints_from_job: str = None,
+    checkpoint_config: CheckpointConfig = None,
+    role_arn: str = None,
+    stopping_condition: StoppingCondition = None,
+    output_data_config: OutputDataConfig = None,
+    aws_session: AwsSession = None,
     tags: dict[str, str] = None,
     logger: Logger = getLogger(__name__),
 ) -> Callable:
@@ -73,7 +73,7 @@ def hybrid_job(
     `copy_checkpoints_from_job`, `stopping_condition`, `tags`, and `logger`.
 
     Args:
-        device (str | None): Device ARN of the QPU device that receives priority quantum
+        device (str): 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
             queue so that only one hybrid job is running at a time. The device string is accessible
             in the hybrid job instance as the environment variable "AMZN_BRAKET_DEVICE_ARN".
@@ -85,20 +85,20 @@ def hybrid_job(
             modules to be included. Any references to members of these modules in the hybrid job
             algorithm code will be serialized as part of the algorithm code. Default value `[]`
 
-        dependencies (str | Path | None): Path (absolute or relative) to a requirements.txt
+        dependencies (str | Path): Path (absolute or relative) to a requirements.txt
             file to be used for the hybrid job.
 
         local (bool): Whether to use local mode for the hybrid job. Default `False`
 
-        job_name (str | None): A string that specifies the name with which the job is created.
+        job_name (str): A string that specifies the name with which the job is created.
             Allowed pattern for job name: `^[a-zA-Z0-9](-*[a-zA-Z0-9]){0,50}$`. Defaults to
             f'{decorated-function-name}-{timestamp}'.
 
-        image_uri (str | None): A str that specifies the ECR image to use for executing the job.
+        image_uri (str): A str that specifies the ECR image to use for executing the job.
             `retrieve_image()` function may be used for retrieving the ECR image URIs
             for the containers supported by Braket. Default = ``.
 
-        input_data (str | Dict | S3DataSourceConfig): Information about the training
+        input_data (str | dict | S3DataSourceConfig): Information about the training
             data. Dictionary maps channel names to local paths or S3 URIs. Contents found
             at any local paths will be uploaded to S3 at
             f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local
@@ -110,51 +110,59 @@ def hybrid_job(
             This would tail the job logs as it waits. Otherwise `False`. Ignored if using
             local mode. Default: `False`.
 
-        instance_config (InstanceConfig | None): Configuration of the instance(s) for running the
+        instance_config (InstanceConfig): Configuration of the instance(s) for running the
             classical code for the hybrid job. Defaults to
             `InstanceConfig(instanceType='ml.m5.large', instanceCount=1, volumeSizeInGB=30)`.
 
-        distribution (str | None): A str that specifies how the job should be distributed.
+        distribution (str): A str that specifies how the job should be distributed.
             If set to "data_parallel", the hyperparameters for the job will be set to use data
             parallelism features for PyTorch or TensorFlow. Default: None.
 
-        copy_checkpoints_from_job (str | None): A str that specifies the job ARN whose
+        copy_checkpoints_from_job (str): A str that specifies the job ARN whose
             checkpoint you want to use in the current job. Specifying this value will copy
             over the checkpoint data from `use_checkpoints_from_job`'s checkpoint_config
             s3Uri to the current job's checkpoint_config s3Uri, making it available at
             checkpoint_config.localPath during the job execution. Default: None
 
-        checkpoint_config (CheckpointConfig | None): Configuration that specifies the
+        checkpoint_config (CheckpointConfig): Configuration that specifies the
             location where checkpoint data is stored.
             Default: CheckpointConfig(localPath='/opt/jobs/checkpoints',
             s3Uri=f's3://{default_bucket_name}/jobs/{job_name}/checkpoints').
 
-        role_arn (str | None): A str providing the IAM role ARN used to execute the
+        role_arn (str): A str providing the IAM role ARN used to execute the
             script. Default: IAM role returned by AwsSession's `get_default_jobs_role()`.
 
-        stopping_condition (StoppingCondition | None): The maximum length of time, in seconds,
+        stopping_condition (StoppingCondition): The maximum length of time, in seconds,
             and the maximum number of tasks that a job can run before being forcefully stopped.
             Default: StoppingCondition(maxRuntimeInSeconds=5 * 24 * 60 * 60).
 
-        output_data_config (OutputDataConfig | None): Specifies the location for the output of
+        output_data_config (OutputDataConfig): Specifies the location for the output of
             the job.
             Default: OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data',
             kmsKeyId=None).
 
-        aws_session (AwsSession | None): AwsSession for connecting to AWS Services.
+        aws_session (AwsSession): AwsSession for connecting to AWS Services.
             Default: AwsSession()
 
-        tags (dict[str, str] | None): Dict specifying the key-value pairs for tagging this job.
+        tags (dict[str, str]): Dict specifying the key-value pairs for tagging this job.
             Default: {}.
 
         logger (Logger): Logger object with which to write logs, such as task statuses
             while waiting for task to be in a terminal state. Default is `getLogger(__name__)`
+
+    Returns:
+        Callable: the callable for creating a Hybrid Job.
     """
     _validate_python_version(image_uri, aws_session)
 
-    def _hybrid_job(entry_point):
+    def _hybrid_job(entry_point: Callable) -> Callable:
         @functools.wraps(entry_point)
-        def job_wrapper(*args, **kwargs):
+        def job_wrapper(*args, **kwargs) -> Callable:
+            """
+            The job wrapper.
+            Returns:
+                Callable: the callable for creating a Hybrid Job.
+            """
             with _IncludeModules(include_modules), tempfile.TemporaryDirectory(
                 dir="", prefix="decorator_job_"
             ) as temp_dir:
@@ -208,7 +216,7 @@ def job_wrapper(*args, **kwargs):
     return _hybrid_job
 
 
-def _validate_python_version(image_uri: str | None, aws_session: AwsSession | None = None):
+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()
     # user provides a custom image_uri
@@ -257,7 +265,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
 def _serialize_entry_point(entry_point: Callable, args: tuple, kwargs: dict) -> str:
     """Create an entry point from a function"""
 
-    def wrapped_entry_point():
+    def wrapped_entry_point() -> Any:
         """Partial function wrapping entry point with given parameters"""
         return entry_point(*args, **kwargs)
 
@@ -277,7 +285,7 @@ def wrapped_entry_point():
     )
 
 
-def _log_hyperparameters(entry_point: Callable, args: tuple, kwargs: dict):
+def _log_hyperparameters(entry_point: Callable, args: tuple, kwargs: dict) -> Dict:
     """Capture function arguments as hyperparameters"""
     signature = inspect.signature(entry_point)
     bound_args = signature.bind(*args, **kwargs)
@@ -322,7 +330,7 @@ def _sanitize(hyperparameter: Any) -> str:
     return sanitized
 
 
-def _process_input_data(input_data):
+def _process_input_data(input_data: Dict) -> List[str]:
     """
     Create symlinks to data
 
@@ -336,12 +344,12 @@ def _process_input_data(input_data):
     if not isinstance(input_data, dict):
         input_data = {"input": input_data}
 
-    def matches(prefix):
+    def matches(prefix: str) -> List[str]:
         return [
             str(path) for path in Path(prefix).parent.iterdir() if str(path).startswith(str(prefix))
         ]
 
-    def is_prefix(path):
+    def is_prefix(path: str) -> bool:
         return len(matches(path)) > 1 or not Path(path).exists()
 
     prefix_channels = set()
diff --git a/src/braket/jobs/image_uris.py b/src/braket/jobs/image_uris.py
index eedc5e79..3a3346ab 100644
--- a/src/braket/jobs/image_uris.py
+++ b/src/braket/jobs/image_uris.py
@@ -15,7 +15,7 @@
 import os
 from enum import Enum
 from functools import cache
-from typing import Dict
+from typing import Dict, Set
 
 
 class Framework(str, Enum):
@@ -26,7 +26,7 @@ class Framework(str, Enum):
     PL_PYTORCH = "PL_PYTORCH"
 
 
-def built_in_images(region):
+def built_in_images(region: str) -> Set[str]:
     return {retrieve_image(framework, region) for framework in Framework}
 
 
diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py
index 65d674c2..e34df050 100644
--- a/src/braket/jobs/quantum_job_creation.py
+++ b/src/braket/jobs/quantum_job_creation.py
@@ -232,13 +232,13 @@ 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, func: Callable = 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.
+        image_uri (str): URI for the image container.
+        func (Callable): The entry point function.
 
     Returns:
         str: Hybrid job name.
@@ -361,7 +361,7 @@ def _validate_params(dict_arr: dict[str, tuple[any, any]]) -> None:
     Validate that config parameters are of the right type.
 
     Args:
-        dict_arr (Dict[str, Tuple[any, any]]): dict mapping parameter names to
+        dict_arr (dict[str, tuple[any, any]]): dict mapping parameter names to
             a tuple containing the provided value and expected type.
     """
     for parameter_name, value_tuple in dict_arr.items():

From 2e978f602195c26a8e5afc0e777a8ada6d546132 Mon Sep 17 00:00:00 2001
From: ci 
Date: Wed, 18 Oct 2023 16:17:29 +0000
Subject: [PATCH 0901/1165] prepare release v1.59.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 0d5b70c2..90bf780c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## v1.59.1 (2023-10-18)
+
+### Bug Fixes and Other Changes
+
+ * doc fixes
+
 ## v1.59.0 (2023-10-17)
 
 ### Features
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index ee48017d..849b7345 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.59.1.dev0"
+__version__ = "1.59.1"

From b814970a7b98d73d784288aac6ab6cc4e6fb4f87 Mon Sep 17 00:00:00 2001
From: ci 
Date: Wed, 18 Oct 2023 16:17:29 +0000
Subject: [PATCH 0902/1165] update development version to v1.59.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 849b7345..e754f901 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.59.1"
+__version__ = "1.59.2.dev0"

From 9b43e72cd5df2b09dfd30ddcfce0342ac1a93694 Mon Sep 17 00:00:00 2001
From: Abe Coull <85974725+math411@users.noreply.github.com>
Date: Wed, 18 Oct 2023 11:55:41 -0700
Subject: [PATCH 0903/1165] infra: have linters run a read-only check of code
 (#747)

---
 .github/workflows/check-format.yml | 3 +--
 setup.cfg                          | 1 +
 tox.ini                            | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml
index 0e0b9927..2d7e95eb 100644
--- a/.github/workflows/check-format.yml
+++ b/.github/workflows/check-format.yml
@@ -27,5 +27,4 @@ jobs:
         pip install -e .[test]
     - name: Run code format checks
       run: |
-        black --check .
-        flake8 --enable-extensions=BCS src
+        tox -e linters_check
diff --git a/setup.cfg b/setup.cfg
index 03f3d1ac..94f28ec7 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -12,6 +12,7 @@ testpaths = test/unit_tests
 line_length = 100
 multi_line_output = 3
 include_trailing_comma = true
+profile = black
   
 [flake8]
 ignore =
diff --git a/tox.ini b/tox.ini
index b49c673c..dc3faea5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -95,7 +95,7 @@ skip_install = true
 deps =
     black
 commands =
-    black --check . {posargs}
+    black --check ./ {posargs}
 
 [testenv:docs]
 basepython = python3

From 6ba56a7e260b44509ce9a003b20d27f84a9e686e Mon Sep 17 00:00:00 2001
From: Abe Coull <85974725+math411@users.noreply.github.com>
Date: Wed, 18 Oct 2023 12:51:25 -0700
Subject: [PATCH 0904/1165] infra: stop flake8 running over the src directory
 twice (#749)

---
 tox.ini | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/tox.ini b/tox.ini
index dc3faea5..b77ac452 100644
--- a/tox.ini
+++ b/tox.ini
@@ -62,8 +62,8 @@ deps =
     flake8-rst-docstrings
     git+https://github.com/amazon-braket/amazon-braket-build-tools.git
 commands =
-    flake8 {posargs}
-    flake8 --enable-extensions=BCS src
+    flake8 --extend-exclude src {posargs}
+    flake8 --enable-extensions=BCS src {posargs}
 
 [testenv:isort]
 basepython = python3

From 1d9a8ebbbb7c12870e9dbce49adc1fb23a6f9395 Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Fri, 20 Oct 2023 13:53:28 -0700
Subject: [PATCH 0905/1165] test: pytest xfail python version mismatch (#751)

---
 test/integ_tests/test_create_quantum_job.py | 168 ++++++++++----------
 1 file changed, 85 insertions(+), 83 deletions(-)

diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py
index 7cab63c9..640cb168 100644
--- a/test/integ_tests/test_create_quantum_job.py
+++ b/test/integ_tests/test_create_quantum_job.py
@@ -14,9 +14,9 @@
 import json
 import os.path
 import re
+import sys
 import tempfile
 from pathlib import Path
-from warnings import warn
 
 import job_test_script
 import pytest
@@ -24,7 +24,15 @@
 
 from braket.aws.aws_quantum_job import AwsQuantumJob
 from braket.devices import Devices
-from braket.jobs import get_input_data_dir, hybrid_job, save_job_result
+from braket.jobs import Framework, get_input_data_dir, hybrid_job, retrieve_image, save_job_result
+
+
+@pytest.fixture
+def decorator_python_version(aws_session):
+    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 major_version, minor_version
 
 
 def test_failed_quantum_job(aws_session, capsys):
@@ -190,6 +198,11 @@ def test_completed_quantum_job(aws_session, capsys):
         assert data in log_data
 
 
+@pytest.mark.xfail(
+    (sys.version_info.major, sys.version_info.minor) != decorator_python_version,
+    raises=RuntimeError,
+    reason="Python version mismatch",
+)
 def test_decorator_job():
     class MyClass:
         attribute = "value"
@@ -197,46 +210,38 @@ class MyClass:
         def __str__(self):
             return f"MyClass({self.attribute})"
 
-    try:
-
-        @hybrid_job(
-            device=Devices.Amazon.SV1,
-            include_modules="job_test_script",
-            dependencies=str(Path("test", "integ_tests", "requirements.txt")),
-            input_data=str(Path("test", "integ_tests", "requirements")),
-        )
-        def decorator_job(a, b: int, c=0, d: float = 1.0, **extras):
-            save_job_result(job_test_script.job_helper())
-            with open(Path(get_input_data_dir()) / "requirements.txt", "r") as f:
-                assert f.readlines() == ["pytest\n"]
-            with open(Path("test", "integ_tests", "requirements.txt"), "r") as f:
-                assert f.readlines() == ["pytest\n"]
-            assert dir(pytest)
-            assert a.attribute == "value"
-            assert b == 2
-            assert c == 0
-            assert d == 5
-            assert extras["extra_arg"] == "extra_value"
-
-            hp_file = os.environ["AMZN_BRAKET_HP_FILE"]
-            with open(hp_file, "r") as f:
-                hyperparameters = json.load(f)
-            assert hyperparameters == {
-                "a": "MyClass{value}",
-                "b": "2",
-                "c": "0",
-                "d": "5",
-                "extra_arg": "extra_value",
-            }
-
-            with open("test/output_file.txt", "w") as f:
-                f.write("hello")
-
-    except RuntimeError as e:
-        if str(e).startswith("Python version must match between local environment and container."):
-            warn("skipping test due to python version mismatch")
-            return
-        raise e
+    @hybrid_job(
+        device=Devices.Amazon.SV1,
+        include_modules="job_test_script",
+        dependencies=str(Path("test", "integ_tests", "requirements.txt")),
+        input_data=str(Path("test", "integ_tests", "requirements")),
+    )
+    def decorator_job(a, b: int, c=0, d: float = 1.0, **extras):
+        save_job_result(job_test_script.job_helper())
+        with open(Path(get_input_data_dir()) / "requirements.txt", "r") as f:
+            assert f.readlines() == ["pytest\n"]
+        with open(Path("test", "integ_tests", "requirements.txt"), "r") as f:
+            assert f.readlines() == ["pytest\n"]
+        assert dir(pytest)
+        assert a.attribute == "value"
+        assert b == 2
+        assert c == 0
+        assert d == 5
+        assert extras["extra_arg"] == "extra_value"
+
+        hp_file = os.environ["AMZN_BRAKET_HP_FILE"]
+        with open(hp_file, "r") as f:
+            hyperparameters = json.load(f)
+        assert hyperparameters == {
+            "a": "MyClass{value}",
+            "b": "2",
+            "c": "0",
+            "d": "5",
+            "extra_arg": "extra_value",
+        }
+
+        with open("test/output_file.txt", "w") as f:
+            f.write("hello")
 
     job = decorator_job(MyClass(), 2, d=5, extra_arg="extra_value")
     assert job.result()["status"] == "SUCCESS"
@@ -257,50 +262,47 @@ def decorator_job(a, b: int, c=0, d: float = 1.0, **extras):
             os.chdir(current_dir)
 
 
+@pytest.mark.xfail(
+    (sys.version_info.major, sys.version_info.minor) != decorator_python_version,
+    raises=RuntimeError,
+    reason="Python version mismatch",
+)
 def test_decorator_job_submodule():
-    try:
-
-        @hybrid_job(
-            device=Devices.Amazon.SV1,
-            include_modules=[
+    @hybrid_job(
+        device=Devices.Amazon.SV1,
+        include_modules=[
+            "job_test_module",
+        ],
+        dependencies=Path(
+            "test", "integ_tests", "job_test_module", "job_test_submodule", "requirements.txt"
+        ),
+        input_data={
+            "my_input": str(Path("test", "integ_tests", "requirements.txt")),
+            "my_dir": str(Path("test", "integ_tests", "job_test_module")),
+        },
+    )
+    def decorator_job_submodule():
+        save_job_result(submodule_helper())
+        with open(Path(get_input_data_dir("my_input")) / "requirements.txt", "r") as f:
+            assert f.readlines() == ["pytest\n"]
+        with open(Path("test", "integ_tests", "requirements.txt"), "r") as f:
+            assert f.readlines() == ["pytest\n"]
+        with open(
+            Path(get_input_data_dir("my_dir")) / "job_test_submodule" / "requirements.txt", "r"
+        ) as f:
+            assert f.readlines() == ["pytest\n"]
+        with open(
+            Path(
+                "test",
+                "integ_tests",
                 "job_test_module",
-            ],
-            dependencies=Path(
-                "test", "integ_tests", "job_test_module", "job_test_submodule", "requirements.txt"
+                "job_test_submodule",
+                "requirements.txt",
             ),
-            input_data={
-                "my_input": str(Path("test", "integ_tests", "requirements.txt")),
-                "my_dir": str(Path("test", "integ_tests", "job_test_module")),
-            },
-        )
-        def decorator_job_submodule():
-            save_job_result(submodule_helper())
-            with open(Path(get_input_data_dir("my_input")) / "requirements.txt", "r") as f:
-                assert f.readlines() == ["pytest\n"]
-            with open(Path("test", "integ_tests", "requirements.txt"), "r") as f:
-                assert f.readlines() == ["pytest\n"]
-            with open(
-                Path(get_input_data_dir("my_dir")) / "job_test_submodule" / "requirements.txt", "r"
-            ) as f:
-                assert f.readlines() == ["pytest\n"]
-            with open(
-                Path(
-                    "test",
-                    "integ_tests",
-                    "job_test_module",
-                    "job_test_submodule",
-                    "requirements.txt",
-                ),
-                "r",
-            ) as f:
-                assert f.readlines() == ["pytest\n"]
-            assert dir(pytest)
-
-    except RuntimeError as e:
-        if str(e).startswith("Python version must match between local environment and container."):
-            warn("skipping test due to python version mismatch")
-            return
-        raise e
+            "r",
+        ) as f:
+            assert f.readlines() == ["pytest\n"]
+        assert dir(pytest)
 
     job = decorator_job_submodule()
     assert job.result()["status"] == "SUCCESS"

From fb0b4ec6444a18af19b0486077477bbd26c99615 Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Mon, 23 Oct 2023 11:23:19 -0400
Subject: [PATCH 0906/1165] Exclude autograph from tox testenv:black_check

---
 tox.ini | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tox.ini b/tox.ini
index bf25fe06..8c22d788 100644
--- a/tox.ini
+++ b/tox.ini
@@ -113,7 +113,7 @@ skip_install = true
 deps =
     black
 commands =
-    black --check ./ {posargs}
+    black --check ./ --extend-exclude=src/braket/experimental/autoqasm/autograph {posargs}
 
 [testenv:docs]
 basepython = python3

From a079c522136e03f63bb1065407942169902df35f Mon Sep 17 00:00:00 2001
From: Lauren Capelluto 
Date: Mon, 23 Oct 2023 13:23:47 -0400
Subject: [PATCH 0907/1165] Update type hints (#753)

* change: Update type hints to use built-ins
---
 src/braket/experimental/autoqasm/api.py       | 56 ++++++++-----------
 .../autoqasm/instructions/instructions.py     |  4 +-
 .../autoqasm/instructions/measurements.py     |  8 +--
 .../autoqasm/instructions/qubits.py           |  8 +--
 .../operators/conditional_expressions.py      |  3 +-
 .../autoqasm/operators/control_flow.py        |  3 +-
 .../autoqasm/program/gate_calibrations.py     |  2 +-
 .../experimental/autoqasm/program/program.py  | 31 +++++-----
 .../experimental/autoqasm/pulse/pulse.py      | 14 ++---
 .../autoqasm/transpiler/transpiler.py         |  7 ++-
 .../experimental/autoqasm/types/types.py      | 11 ++--
 .../braket/experimental/autoqasm/conftest.py  |  2 +-
 .../experimental/autoqasm/mock_transpiler.py  |  6 +-
 .../experimental/autoqasm/test_operators.py   |  3 +-
 .../experimental/autoqasm/test_types.py       | 26 ++++-----
 15 files changed, 87 insertions(+), 97 deletions(-)

diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py
index e1eb3685..b6f421e3 100644
--- a/src/braket/experimental/autoqasm/api.py
+++ b/src/braket/experimental/autoqasm/api.py
@@ -16,8 +16,9 @@
 import copy
 import functools
 import inspect
+from collections.abc import Callable
 from types import FunctionType
-from typing import Any, Callable, Dict, List, Optional, Tuple, Union
+from typing import Any, Optional, Union
 
 import openqasm3.ast as qasm_ast
 import oqpy.base
@@ -120,15 +121,15 @@ def gate_calibration(*args, implements: Callable, **kwargs) -> Callable[[], Gate
 
 
 def _function_wrapper(
-    *args: Tuple[Any],
+    *args: tuple[Any],
     converter_callback: Callable,
-    converter_args: Optional[Dict[str, Any]] = None,
+    converter_args: Optional[dict[str, Any]] = None,
 ) -> Callable[[Any], aq_program.Program]:
     """Wrapping and conversion logic around the user function `f`.
 
     Args:
         converter_callback (Callable): The function converter, e.g., _convert_main.
-        converter_args (Optional[Dict[str, Any]]): Extra arguments for the function converter.
+        converter_args (Optional[dict[str, Any]]): Extra arguments for the function converter.
 
     Returns:
         Callable[[Any], Program]: A callable which returns the converted
@@ -167,7 +168,7 @@ def _wrapper(*args, **kwargs) -> Callable:
     return autograph_artifact(decorated_wrapper)
 
 
-def _autograph_optional_features() -> Tuple[converter.Feature]:
+def _autograph_optional_features() -> tuple[converter.Feature]:
     # Exclude autograph features which are TensorFlow-specific
     return converter.Feature.all_but(
         (converter.Feature.NAME_SCOPES, converter.Feature.AUTO_CONTROL_DEPS)
@@ -177,8 +178,8 @@ def _autograph_optional_features() -> Tuple[converter.Feature]:
 def _convert_main(
     f: Callable,
     options: converter.ConversionOptions,
-    args: List[Any],
-    kwargs: Dict[str, Any],
+    args: list[Any],
+    kwargs: dict[str, Any],
     user_config: aq_program.UserConfig,
 ) -> None:
     """Convert the initial callable `f` into a full AutoQASM program `program`.
@@ -191,8 +192,8 @@ def _convert_main(
     Args:
         f (Callable): The function to be converted.
         options (converter.ConversionOptions): Converter options.
-        args (List[Any]): Arguments passed to the program when called.
-        kwargs (Dict[str, Any]): Keyword arguments passed to the program when called.
+        args (list[Any]): Arguments passed to the program when called.
+        kwargs (dict[str, Any]): Keyword arguments passed to the program when called.
         user_config (UserConfig): User-specified settings that influence program building.
     """
     if aq_program.in_active_program_conversion_context():
@@ -260,8 +261,8 @@ def _add_qubit_declaration(program_conversion_context: aq_program.ProgramConvers
 def _convert_subroutine(
     f: Callable,
     options: converter.ConversionOptions,
-    args: List[Any],
-    kwargs: Dict[str, Any],
+    args: list[Any],
+    kwargs: dict[str, Any],
 ) -> None:
     """Convert the initial callable `f` into a full AutoQASM program `program`.
     The contents of `f` are converted into a subroutine in the program.
@@ -272,8 +273,8 @@ def _convert_subroutine(
     Args:
         f (Callable): The function to be converted.
         options (converter.ConversionOptions): Converter options.
-        args (List[Any]): Arguments passed to the program when called.
-        kwargs (Dict[str, Any]): Keyword arguments passed to the program when called.
+        args (list[Any]): Arguments passed to the program when called.
+        kwargs (dict[str, Any]): Keyword arguments passed to the program when called.
     """
     if not aq_program.in_active_program_conversion_context():
         raise errors.AutoQasmTypeError(
@@ -426,18 +427,7 @@ def _make_return_instance_from_f_annotation(f: Callable) -> Any:
     # TODO: Recursive functions should work even if the user's type hint is wrong
     annotations = f.__annotations__
     return_type = annotations["return"] if "return" in annotations else None
-
-    return_instance = None
-    if return_type and aq_types.is_qasm_type(return_type):
-        return_instance = return_type()
-    elif return_type:
-        if hasattr(return_type, "__origin__"):
-            # Types from python's typing module, such as `List`. origin gives us `list``
-            return_instance = return_type.__origin__()
-        else:
-            return_instance = return_type()
-
-    return return_instance
+    return return_type() if return_type else None
 
 
 def _make_return_instance_from_oqpy_return_type(return_type: Any) -> Any:
@@ -461,8 +451,8 @@ def _get_bitvar_size(node: qasm_ast.BitType) -> Optional[int]:
 def _convert_gate(
     f: Callable,
     options: converter.ConversionOptions,
-    args: List[Any],
-    kwargs: Dict[str, Any],
+    args: list[Any],
+    kwargs: dict[str, Any],
 ) -> Callable:
     # We must be inside an active conversion context in order to invoke a gate
     program_conversion_context = aq_program.get_program_conversion_context()
@@ -558,8 +548,8 @@ def _get_gate_args(f: Callable) -> aq_program.GateArgs:
 def _convert_calibration(
     f: Callable,
     options: converter.ConversionOptions,
-    args: List[Any],
-    kwargs: Dict[str, Any],
+    args: list[Any],
+    kwargs: dict[str, Any],
     gate_function: Callable,
     **decorator_kwargs,
 ) -> GateCalibration:
@@ -569,8 +559,8 @@ def _convert_calibration(
     Args:
         f (Callable): The function to be converted.
         options (converter.ConversionOptions): Converter options.
-        args (List[Any]): Arguments passed to the program when called.
-        kwargs (Dict[str, Any]): Keyword arguments passed to the program when called.
+        args (list[Any]): Arguments passed to the program when called.
+        kwargs (dict[str, Any]): Keyword arguments passed to the program when called.
         gate_function (Callable): The gate function which calibration is being defined.
 
     Returns:
@@ -624,14 +614,14 @@ def _convert_calibration(
 
 def _validate_calibration_args(
     gate_function: Callable,
-    decorator_args: Dict[str, Union[Qubit, float]],
+    decorator_args: dict[str, Union[Qubit, float]],
     func_args: aq_program.GateArgs,
 ) -> None:
     """Validate the arguments passed to the calibration decorator and function.
 
     Args:
         gate_function (Callable): The gate function which calibration is being defined.
-        decorator_args (Dict[str, Union[Qubit, float]]): The calibration decorator arguments.
+        decorator_args (dict[str, Union[Qubit, float]]): The calibration decorator arguments.
         func_args (aq_program.GateArgs): The gate function arguments.
     """
     gate_args = _get_gate_args(gate_function)
diff --git a/src/braket/experimental/autoqasm/instructions/instructions.py b/src/braket/experimental/autoqasm/instructions/instructions.py
index 0e93f797..24077b16 100644
--- a/src/braket/experimental/autoqasm/instructions/instructions.py
+++ b/src/braket/experimental/autoqasm/instructions/instructions.py
@@ -15,7 +15,7 @@
 """Non-unitary instructions that apply to qubits.
 """
 
-from typing import Any, List
+from typing import Any
 
 from braket.experimental.autoqasm import program as aq_program
 
@@ -23,7 +23,7 @@
 
 
 def _qubit_instruction(
-    name: str, qubits: List[QubitIdentifierType], *args: Any, is_unitary: bool = True
+    name: str, qubits: list[QubitIdentifierType], *args: Any, is_unitary: bool = True
 ) -> None:
     program_conversion_context = aq_program.get_program_conversion_context()
     program_conversion_context.validate_gate_targets(qubits, args)
diff --git a/src/braket/experimental/autoqasm/instructions/measurements.py b/src/braket/experimental/autoqasm/instructions/measurements.py
index b747ed9f..2b9fb98d 100644
--- a/src/braket/experimental/autoqasm/instructions/measurements.py
+++ b/src/braket/experimental/autoqasm/instructions/measurements.py
@@ -23,25 +23,25 @@ def my_program():
 """
 
 
-from typing import List, Union
+from typing import Union
 
 from braket.experimental.autoqasm import program
 from braket.experimental.autoqasm import types as aq_types
 from braket.experimental.autoqasm.instructions.qubits import QubitIdentifierType, _qubit
 
 
-def measure(qubits: Union[QubitIdentifierType, List[QubitIdentifierType]]) -> aq_types.BitVar:
+def measure(qubits: Union[QubitIdentifierType, list[QubitIdentifierType]]) -> aq_types.BitVar:
     """Add qubit measurement statements to the program and assign the measurement
     results to bit variables.
 
     Args:
-        qubits (Union[QubitIdentifierType, List[QubitIdentifierType]]): The target qubits
+        qubits (Union[QubitIdentifierType, list[QubitIdentifierType]]): The target qubits
             to measure.
 
     Returns:
         BitVar: Bit variable the measurement results are assigned to.
     """
-    if not isinstance(qubits, List):
+    if not isinstance(qubits, list):
         qubits = [qubits]
 
     oqpy_program = program.get_program_conversion_context().get_oqpy_program()
diff --git a/src/braket/experimental/autoqasm/instructions/qubits.py b/src/braket/experimental/autoqasm/instructions/qubits.py
index 7a33bc88..507ba178 100644
--- a/src/braket/experimental/autoqasm/instructions/qubits.py
+++ b/src/braket/experimental/autoqasm/instructions/qubits.py
@@ -16,7 +16,7 @@
 
 import re
 from functools import singledispatch
-from typing import Any, List, Union
+from typing import Any, Union
 
 import oqpy.base
 from openpulse.printer import dumps
@@ -38,14 +38,14 @@ def is_qubit_identifier_type(qubit: Any) -> bool:
     return isinstance(qubit, QubitIdentifierType.__args__)
 
 
-def _get_physical_qubit_indices(qids: List[str]) -> List[int]:
+def _get_physical_qubit_indices(qids: list[str]) -> list[int]:
     """Convert physical qubit labels to the corresponding qubit indices.
 
     Args:
-        qids (List[str]): Physical qubit labels.
+        qids (list[str]): Physical qubit labels.
 
     Returns:
-        List[int]: Qubit indices corresponding to the input physical qubits.
+        list[int]: Qubit indices corresponding to the input physical qubits.
     """
     braket_qubits = []
     for qid in qids:
diff --git a/src/braket/experimental/autoqasm/operators/conditional_expressions.py b/src/braket/experimental/autoqasm/operators/conditional_expressions.py
index 40b591f7..94a9e0f2 100644
--- a/src/braket/experimental/autoqasm/operators/conditional_expressions.py
+++ b/src/braket/experimental/autoqasm/operators/conditional_expressions.py
@@ -14,7 +14,8 @@
 
 """Operators for conditional expressions (e.g. the ternary if statement)."""
 
-from typing import Any, Callable, Optional
+from collections.abc import Callable
+from typing import Any, Optional
 
 import oqpy.base
 
diff --git a/src/braket/experimental/autoqasm/operators/control_flow.py b/src/braket/experimental/autoqasm/operators/control_flow.py
index 6ead0d57..33fb1096 100644
--- a/src/braket/experimental/autoqasm/operators/control_flow.py
+++ b/src/braket/experimental/autoqasm/operators/control_flow.py
@@ -14,7 +14,8 @@
 
 """Operators for control flow constructs (e.g. if, for, while)."""
 
-from typing import Any, Callable, Iterable, Optional, Union
+from collections.abc import Callable, Iterable
+from typing import Any, Optional, Union
 
 import oqpy.base
 
diff --git a/src/braket/experimental/autoqasm/program/gate_calibrations.py b/src/braket/experimental/autoqasm/program/gate_calibrations.py
index ad76dcd5..75045f04 100644
--- a/src/braket/experimental/autoqasm/program/gate_calibrations.py
+++ b/src/braket/experimental/autoqasm/program/gate_calibrations.py
@@ -14,7 +14,7 @@
 
 from __future__ import annotations
 
-from typing import Callable, Iterable
+from collections.abc import Callable, Iterable
 
 from braket.experimental.autoqasm.instructions.qubits import QubitIdentifierType as Qubit
 from braket.experimental.autoqasm.program import Program
diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py
index 411cbcb6..b6eaed22 100644
--- a/src/braket/experimental/autoqasm/program/program.py
+++ b/src/braket/experimental/autoqasm/program/program.py
@@ -16,9 +16,10 @@
 
 import contextlib
 import threading
+from collections.abc import Callable, Iterable
 from dataclasses import dataclass
 from enum import Enum
-from typing import Any, Callable, Iterable, List, Optional, Union
+from typing import Any, Optional, Union
 
 import oqpy.base
 
@@ -99,12 +100,12 @@ def __init__(
         self._oqpy_program = oqpy_program
         self._has_pulse_control = has_pulse_control
 
-    def with_calibrations(self, gate_calibrations: Union[Callable, List[Callable]]) -> Program:
+    def with_calibrations(self, gate_calibrations: Union[Callable, list[Callable]]) -> Program:
         """Add the gate calibrations to the program. The calibration added program is returned
         as a new object. The original program is not modified.
 
         Args:
-            gate_calibrations (Union[Callable, List[Callable]]): The gate calibrations to add to
+            gate_calibrations (Union[Callable, list[Callable]]): The gate calibrations to add to
                 the main program. Calibration are passed as callable without evaluation.
 
         Returns:
@@ -156,7 +157,7 @@ class GateArgs:
     """Represents a list of qubit and angle arguments for a gate definition."""
 
     def __init__(self):
-        self._args: List[Union[oqpy.Qubit, oqpy.AngleVar]] = []
+        self._args: list[Union[oqpy.Qubit, oqpy.AngleVar]] = []
 
     def __len__(self):
         return len(self._args)
@@ -174,19 +175,19 @@ def append(self, name: str, is_qubit: bool) -> None:
             self._args.append(oqpy.AngleVar(name=name))
 
     @property
-    def qubits(self) -> List[oqpy.Qubit]:
+    def qubits(self) -> list[oqpy.Qubit]:
         return [self._args[i] for i in self.qubit_indices]
 
     @property
-    def angles(self) -> List[oqpy.AngleVar]:
+    def angles(self) -> list[oqpy.AngleVar]:
         return [self._args[i] for i in self.angle_indices]
 
     @property
-    def qubit_indices(self) -> List[int]:
+    def qubit_indices(self) -> list[int]:
         return [i for i, arg in enumerate(self._args) if isinstance(arg, oqpy.Qubit)]
 
     @property
-    def angle_indices(self) -> List[int]:
+    def angle_indices(self) -> list[int]:
         return [i for i, arg in enumerate(self._args) if isinstance(arg, oqpy.AngleVar)]
 
 
@@ -229,11 +230,11 @@ def make_program(self) -> Program:
         return Program(self.get_oqpy_program(), has_pulse_control=self._has_pulse_control)
 
     @property
-    def qubits(self) -> List[int]:
+    def qubits(self) -> list[int]:
         """Return a sorted list of virtual qubits used in this program.
 
         Returns:
-            List[int]: The list of virtual qubits, e.g. [0, 1, 2]
+            list[int]: The list of virtual qubits, e.g. [0, 1, 2]
         """
         # Can be memoized or otherwise made more performant
         return sorted(list(self._virtual_qubits_used))
@@ -323,12 +324,12 @@ def is_var_name_used(self, var_name: str) -> bool:
             or var_name in oqpy_program.undeclared_vars.keys()
         )
 
-    def validate_gate_targets(self, qubits: List[Any], angles: List[Any]) -> None:
+    def validate_gate_targets(self, qubits: list[Any], angles: list[Any]) -> None:
         """Validate that the specified gate targets are valid at this point in the program.
 
         Args:
-            qubits (List[Any]): The list of target qubits to validate.
-            angles (List[Any]): The list of target angles to validate.
+            qubits (list[Any]): The list of target qubits to validate.
+            angles (list[Any]): The list of target angles to validate.
 
         Raises:
             errors.InvalidTargetQubit: Target qubits are invalid in the current context.
@@ -359,10 +360,10 @@ def validate_gate_targets(self, qubits: List[Any], angles: List[Any]) -> None:
                     )
 
     @staticmethod
-    def _normalize_gate_names(gate_names: Iterable[str]) -> List[str]:
+    def _normalize_gate_names(gate_names: Iterable[str]) -> list[str]:
         return [gate_name.lower() for gate_name in gate_names]
 
-    def _validate_verbatim_target_qubits(self, qubits: List[Any]) -> None:
+    def _validate_verbatim_target_qubits(self, qubits: list[Any]) -> None:
         # Only physical target qubits are allowed in a verbatim block:
         for qubit in qubits:
             if not isinstance(qubit, str):
diff --git a/src/braket/experimental/autoqasm/pulse/pulse.py b/src/braket/experimental/autoqasm/pulse/pulse.py
index e0581b1b..c8c12741 100644
--- a/src/braket/experimental/autoqasm/pulse/pulse.py
+++ b/src/braket/experimental/autoqasm/pulse/pulse.py
@@ -15,7 +15,7 @@
 """Pulse instructions that apply to frames or qubits.
 """
 
-from typing import List, Union
+from typing import Union
 
 import oqpy
 
@@ -127,17 +127,17 @@ def capture_v0(frame: Frame) -> None:
 
 
 def delay(
-    qubits_or_frames: Union[Frame, List[Frame], QubitIdentifierType, List[QubitIdentifierType]],
+    qubits_or_frames: Union[Frame, list[Frame], QubitIdentifierType, list[QubitIdentifierType]],
     duration: Union[float, oqpy.FloatVar],
 ) -> None:
     """Adds an instruction to advance the frame clock by the specified `duration` value.
 
     Args:
-        qubits_or_frames (Union[Frame, List[Frame], QubitIdentifierType, List[QubitIdentifierType]]):
+        qubits_or_frames (Union[Frame, list[Frame], QubitIdentifierType, list[QubitIdentifierType]]):
             Qubits or frame(s) on which the delay needs to be introduced.
         duration (Union[float, FloatVar]): Value (in seconds) defining the duration of the delay.
     """  # noqa: E501
-    if not isinstance(qubits_or_frames, List):
+    if not isinstance(qubits_or_frames, list):
         qubits_or_frames = [qubits_or_frames]
     if all(is_qubit_identifier_type(q) for q in qubits_or_frames):
         qubits_or_frames = QubitSet(_get_physical_qubit_indices(qubits_or_frames))
@@ -147,17 +147,17 @@ def delay(
 
 
 def barrier(
-    qubits_or_frames: Union[Frame, List[Frame], QubitIdentifierType, List[QubitIdentifierType]]
+    qubits_or_frames: Union[Frame, list[Frame], QubitIdentifierType, list[QubitIdentifierType]]
 ) -> None:
     """Adds an instruction to align the frame clocks to the latest time across all the specified
     frames. When applied on qubits, it prevents compilations across the barrier, if the compiler
     supports barrier.
 
     Args:
-        qubits_or_frames (Union[Frame, List[Frame], QubitIdentifierType, List[QubitIdentifierType]]):
+        qubits_or_frames (Union[Frame, list[Frame], QubitIdentifierType, list[QubitIdentifierType]]):
             Qubits or frame(s) on which the barrier needs to be introduced.
     """  # noqa: E501
-    if not isinstance(qubits_or_frames, List):
+    if not isinstance(qubits_or_frames, list):
         qubits_or_frames = [qubits_or_frames]
     if all(is_qubit_identifier_type(q) for q in qubits_or_frames):
         qubits_or_frames = QubitSet(_get_physical_qubit_indices(qubits_or_frames))
diff --git a/src/braket/experimental/autoqasm/transpiler/transpiler.py b/src/braket/experimental/autoqasm/transpiler/transpiler.py
index b2e4f33b..2dc0dbfc 100644
--- a/src/braket/experimental/autoqasm/transpiler/transpiler.py
+++ b/src/braket/experimental/autoqasm/transpiler/transpiler.py
@@ -21,7 +21,8 @@
 import functools
 import importlib
 import inspect
-from typing import Any, Callable, Optional, Tuple, Union
+from collections.abc import Callable
+from typing import Any, Optional, Union
 
 import gast
 
@@ -261,7 +262,7 @@ def _converted_partial(
     )
 
 
-def _inspect_callable(f: Callable, args: tuple) -> Tuple[Callable, tuple]:
+def _inspect_callable(f: Callable, args: tuple) -> tuple[Callable, tuple]:
     target_entity = None
     effective_args = None
 
@@ -281,7 +282,7 @@ def _try_convert_actual(
     effective_args: tuple,
     kwargs: dict,
     options: converter.ConversionOptions,
-) -> Tuple[Callable, Optional[Exception]]:
+) -> tuple[Callable, Optional[Exception]]:
     converted_f = None
     exc = None
     try:
diff --git a/src/braket/experimental/autoqasm/types/types.py b/src/braket/experimental/autoqasm/types/types.py
index 27b2467f..2bf8627d 100644
--- a/src/braket/experimental/autoqasm/types/types.py
+++ b/src/braket/experimental/autoqasm/types/types.py
@@ -32,13 +32,10 @@ def is_qasm_type(val: Any) -> bool:
     Returns:
         bool: Whether the object is a QASM type.
     """
-    try:
-        if issubclass(val, (oqpy.Range, oqpy._ClassicalVar, oqpy.base.OQPyExpression)):
-            return True
-    except TypeError:
-        # `val` is not a class
-        pass
-
+    # The input can either be a class, like oqpy.Range ...
+    if type(val) is type:
+        return issubclass(val, (oqpy.Range, oqpy._ClassicalVar, oqpy.base.OQPyExpression))
+    # ... or an instance of a class, like oqpy.Range(10)
     return isinstance(val, (oqpy.Range, oqpy._ClassicalVar, oqpy.base.OQPyExpression))
 
 
diff --git a/test/unit_tests/braket/experimental/autoqasm/conftest.py b/test/unit_tests/braket/experimental/autoqasm/conftest.py
index 417d8dd2..beaa53d4 100644
--- a/test/unit_tests/braket/experimental/autoqasm/conftest.py
+++ b/test/unit_tests/braket/experimental/autoqasm/conftest.py
@@ -13,7 +13,7 @@
 
 """Test fixtures shared among the tests."""
 
-from typing import Callable
+from collections.abc import Callable
 
 import pytest
 
diff --git a/test/unit_tests/braket/experimental/autoqasm/mock_transpiler.py b/test/unit_tests/braket/experimental/autoqasm/mock_transpiler.py
index 75166c9d..fa53451a 100644
--- a/test/unit_tests/braket/experimental/autoqasm/mock_transpiler.py
+++ b/test/unit_tests/braket/experimental/autoqasm/mock_transpiler.py
@@ -13,7 +13,7 @@
 
 """Mock transpiler for testing converters."""
 
-from typing import List, Union
+from typing import Union
 
 import gast
 
@@ -24,11 +24,11 @@
 
 
 class MockTranspiler(PyToOqpy):
-    def __init__(self, converters: List):
+    def __init__(self, converters: list):
         """A custom transpiler based on `transpiler.PyToOqpy` for unit testing
         converters.
         Args:
-            converters (List): List of converters to test.
+            converters (list): List of converters to test.
         """
         super(MockTranspiler, self).__init__()
         if isinstance(converters, (list, tuple)):
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py
index ac438f99..7d144743 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_operators.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py
@@ -13,7 +13,8 @@
 
 """Tests for the operators module."""
 
-from typing import Any, Callable
+from collections.abc import Callable
+from typing import Any
 
 import oqpy.base
 import pytest
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py
index c5b97577..b214720f 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_types.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py
@@ -13,8 +13,6 @@
 
 """Tests for the types module."""
 
-from typing import List, Tuple
-
 import oqpy
 import pytest
 
@@ -30,13 +28,13 @@
     ],
 )
 def test_qasm_range(
-    range_params: Tuple[int, int, int], expected_range_params: Tuple[int, int, int]
+    range_params: tuple[int, int, int], expected_range_params: tuple[int, int, int]
 ) -> None:
     """Test `qasm_range()` returning correct `Range` object.
 
     Args:
-        range_params (Tuple[int, int, int]): Range parameters to instantiate `oqpy.Range`
-        expected_range_params (Tuple[int, int, int]): Expected range parameters
+        range_params (tuple[int, int, int]): Range parameters to instantiate `oqpy.Range`
+        expected_range_params (tuple[int, int, int]): Expected range parameters
     """
     start, stop, step = range_params
     qrange = qasm_range(start, stop, step)
@@ -183,12 +181,12 @@ def test_return_array_int():
     """Test return type discovery of array values."""
 
     @aq.subroutine
-    def ret_test() -> List[int]:
+    def ret_test() -> list[int]:
         res = aq.ArrayVar([1, 2, 3], dimensions=[3])
         return res
 
     @aq.main
-    def main() -> List[int]:
+    def main() -> list[int]:
         return ret_test()
 
     expected = """OPENQASM 3.0;
@@ -207,7 +205,7 @@ def test_return_python_array():
     """Test returning a python array of ints."""
 
     @aq.subroutine
-    def tester() -> List[int]:
+    def tester() -> list[int]:
         return [1, 2, 3]
 
     @aq.main(num_qubits=4)
@@ -230,7 +228,7 @@ def test_return_array_unsupported():
     """Test unsupported array type."""
 
     @aq.subroutine
-    def tester(arr: List[float]) -> List[float]:
+    def tester(arr: list[float]) -> list[float]:
         return [1.2, 2.1]
 
     @aq.main(num_qubits=4)
@@ -326,7 +324,7 @@ def test_map_array():
     """Test array input parameter type."""
 
     @aq.subroutine
-    def annotation_test(input: List[int]):
+    def annotation_test(input: list[int]):
         pass
 
     @aq.main
@@ -551,7 +549,7 @@ def test_recursive_list() -> None:
     """Tests recursive subroutines which return a list."""
 
     @aq.subroutine
-    def retval_recursive() -> List[int]:
+    def retval_recursive() -> list[int]:
         retval_recursive()
         return [1]
 
@@ -581,7 +579,7 @@ def test_error_for_tuple_param() -> None:
     """Tuples are not supported as parameters."""
 
     @aq.subroutine
-    def param_test(input: Tuple):
+    def param_test(input: tuple):
         pass
 
     @aq.main
@@ -611,7 +609,7 @@ def test_ignore_ret_typehint_bool():
     """Test type discovery of boolean return values."""
 
     @aq.subroutine
-    def ret_test() -> List[int]:
+    def ret_test() -> list[int]:
         return True
 
     @aq.main
@@ -706,7 +704,7 @@ def test_param_array_list_missing_arg():
     """Test list parameter with missing type arg (list rather than list[int])."""
 
     @aq.subroutine
-    def param_test(arr: List) -> int:
+    def param_test(arr: list) -> int:
         return 1
 
     @aq.main(num_qubits=4)

From b3536bfcc6049c972ee803296e7ec7e80a295335 Mon Sep 17 00:00:00 2001
From: Abe Coull <85974725+math411@users.noreply.github.com>
Date: Mon, 23 Oct 2023 15:48:28 -0700
Subject: [PATCH 0908/1165] doc: add the amazon braket tag in the stack
 exchange URL (#752)

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index f27fcc6b..1c79e803 100644
--- a/README.md
+++ b/README.md
@@ -234,7 +234,7 @@ tox -e integ-tests -- your-arguments
 
 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) and add the tag amazon-braket.
+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
 

From d4ef24193d54e7ee2bb84a4bfebeb202d3d02984 Mon Sep 17 00:00:00 2001
From: ci 
Date: Tue, 24 Oct 2023 16:17:36 +0000
Subject: [PATCH 0909/1165] prepare release v1.59.1.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 90bf780c..03b0def5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## v1.59.1.post0 (2023-10-24)
+
+### Documentation Changes
+
+ * add the amazon braket tag in the stack exchange URL
+
 ## v1.59.1 (2023-10-18)
 
 ### Bug Fixes and Other Changes
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index e754f901..e4635229 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.59.2.dev0"
+__version__ = "1.59.1.post0"

From 63d6540728fd9ab3196b6c29c0d9297be25403b3 Mon Sep 17 00:00:00 2001
From: ci 
Date: Tue, 24 Oct 2023 16:17:36 +0000
Subject: [PATCH 0910/1165] update development version to v1.59.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 e4635229..e754f901 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.59.1.post0"
+__version__ = "1.59.2.dev0"

From b89d394fd3a284a5884ec253b3fa289c6286ef37 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 24 Oct 2023 11:06:38 -0700
Subject: [PATCH 0911/1165] infra: bump actions/checkout from 4.1.0 to 4.1.1
 (#755)

---
 .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 2d7e95eb..034e95be 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@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
+    - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
     - name: Set up Python
       uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1
       with:
diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml
index 320195c9..cfd39524 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@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
+    - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
     - name: Set up Python ${{ matrix.python-version }}
       uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1
       with:
diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml
index e4cbed2d..a1524083 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@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
       - name: Set up Python
         uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1
         with:
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index c27a0542..0c02a756 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@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
+    - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
     - name: Set up Python ${{ matrix.python-version }}
       uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1
       with:
diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml
index 8832a8a8..4465ffef 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@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
       - name: Set up Python
         uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1
         with:

From 8ba0b4afb6fa31975e14c315c6947a77ac3a59d8 Mon Sep 17 00:00:00 2001
From: Abe Coull <85974725+math411@users.noreply.github.com>
Date: Tue, 24 Oct 2023 15:52:19 -0700
Subject: [PATCH 0912/1165] fix: remove deprecated as_unitary method (#738)

---
 src/braket/circuits/circuit.py                |  49 +--
 src/braket/circuits/unitary_calculation.py    |  71 ---
 .../braket/circuits/test_circuit.py           | 413 ------------------
 3 files changed, 1 insertion(+), 532 deletions(-)

diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py
index 5d191c69..99b3e8ec 100644
--- a/src/braket/circuits/circuit.py
+++ b/src/braket/circuits/circuit.py
@@ -13,7 +13,6 @@
 
 from __future__ import annotations
 
-import warnings
 from collections.abc import Callable, Iterable
 from numbers import Number
 from typing import Any, Optional, TypeVar, Union
@@ -51,7 +50,7 @@
     QubitReferenceType,
     SerializationProperties,
 )
-from braket.circuits.unitary_calculation import calculate_unitary, calculate_unitary_big_endian
+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
@@ -1391,52 +1390,6 @@ def _add_fixed_argument_calibrations(
                 )
         return additional_calibrations
 
-    def as_unitary(self) -> np.ndarray:
-        r"""
-        Returns the unitary matrix representation, in little endian format, of the entire circuit.
-        *Note*: The performance of this method degrades with qubit count. It might be slow for
-        qubit count > 10.
-
-        Returns:
-            ndarray: A numpy array with shape (2^qubit_count, 2^qubit_count) representing the
-            circuit as a unitary. *Note*: For an empty circuit, an empty numpy array is
-            returned (`array([], dtype=complex128)`)
-
-        Warnings:
-            This method has been deprecated, please use to_unitary() instead.
-            The unitary returned by this method is *little-endian*; the first qubit in the circuit
-            is the _least_ significant. For example, a circuit `Circuit().h(0).x(1)` will yield the
-            unitary :math:`X(1) \otimes H(0)`.
-
-        Raises:
-            TypeError: If circuit is not composed only of `Gate` instances,
-                i.e. a circuit with `Noise` operators will raise this error.
-
-        Examples:
-            >>> circ = Circuit().h(0).cnot(0, 1)
-            >>> circ.as_unitary()
-            array([[ 0.70710678+0.j,  0.70710678+0.j,  0.        +0.j,
-                     0.        +0.j],
-                   [ 0.        +0.j,  0.        +0.j,  0.70710678+0.j,
-                    -0.70710678+0.j],
-                   [ 0.        +0.j,  0.        +0.j,  0.70710678+0.j,
-                     0.70710678+0.j],
-                   [ 0.70710678+0.j, -0.70710678+0.j,  0.        +0.j,
-                     0.        +0.j]])
-        """
-        warnings.warn(
-            "Matrix returned will have qubits in little-endian order; "
-            "This method has been deprecated. Please use to_unitary() instead.",
-            category=DeprecationWarning,
-        )
-
-        qubits = self.qubits
-        if not qubits:
-            return np.zeros(0, dtype=complex)
-        qubit_count = max(qubits) + 1
-
-        return calculate_unitary(qubit_count, self.instructions)
-
     def to_unitary(self) -> np.ndarray:
         """
         Returns the unitary matrix representation of the entire circuit.
diff --git a/src/braket/circuits/unitary_calculation.py b/src/braket/circuits/unitary_calculation.py
index d51a378c..ebc3c787 100644
--- a/src/braket/circuits/unitary_calculation.py
+++ b/src/braket/circuits/unitary_calculation.py
@@ -23,77 +23,6 @@
 from braket.registers.qubit_set import QubitSet
 
 
-def _einsum_subscripts(targets: QubitSet, qubit_count: int) -> str:
-    target_count = len(targets)
-
-    gate_left_indexes = list(range(target_count))
-    un_left_indexes = list(range(target_count, target_count + qubit_count))
-    un_right_indexes = list(range(target_count + qubit_count, target_count + 2 * qubit_count))
-
-    gate_right_indexes = [un_left_indexes[-1 - target] for target in targets]
-
-    result_left_indexes = un_left_indexes.copy()
-    for pos, target in enumerate(targets):
-        result_left_indexes[-1 - target] = gate_left_indexes[pos]
-
-    return (
-        gate_left_indexes + gate_right_indexes,
-        un_left_indexes + un_right_indexes,
-        result_left_indexes + un_right_indexes,
-    )
-
-
-def calculate_unitary(qubit_count: int, instructions: Iterable[Instruction]) -> np.ndarray:
-    """
-    Returns the unitary matrix representation for all the `instructions` with a given
-    `qubit_count`.
-    *Note*: The performance of this method degrades with qubit count. It might be slow for
-    qubit count > 10.
-
-    Args:
-        qubit_count (int): Total number of qubits, enough for all the `instructions`.
-        instructions (Iterable[Instruction]): The instructions for which the unitary matrix
-            will be calculated.
-
-    Returns:
-        np.ndarray: A numpy array with shape (2^qubit_count, 2^qubit_count) representing the
-        `instructions` as a unitary.
-
-    Raises:
-        TypeError: If `instructions` is not composed only of `Gate` instances,
-            i.e. a circuit with `Noise` operators will raise this error.
-            Any `CompilerDirective` instructions will be ignored, as these should
-            not affect the unitary representation of the circuit.
-    """
-    unitary = np.eye(2**qubit_count, dtype=complex)
-    un_tensor = np.reshape(unitary, qubit_count * [2, 2])
-
-    for instr in instructions:
-        if isinstance(instr.operator, CompilerDirective):
-            continue
-
-        if not isinstance(instr.operator, Gate):
-            raise TypeError("Only Gate operators are supported to build the unitary")
-
-        matrix = instr.operator.to_matrix()
-        targets = instr.target
-
-        gate_indexes, un_indexes, result_indexes = _einsum_subscripts(targets, qubit_count)
-        gate_matrix = np.reshape(matrix, len(targets) * [2, 2])
-
-        un_tensor = np.einsum(
-            gate_matrix,
-            gate_indexes,
-            un_tensor,
-            un_indexes,
-            result_indexes,
-            dtype=complex,
-            casting="no",
-        )
-
-    return np.reshape(un_tensor, 2 * [2**qubit_count])
-
-
 def calculate_unitary_big_endian(
     instructions: Iterable[Instruction], qubits: QubitSet
 ) -> np.ndarray:
diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py
index fd2170a5..5824967b 100644
--- a/test/unit_tests/braket/circuits/test_circuit.py
+++ b/test/unit_tests/braket/circuits/test_circuit.py
@@ -1906,419 +1906,6 @@ def test_circuit_to_ir_invalid_inputs(
     assert exc.value.args[0] == expected_message
 
 
-def test_as_unitary_empty_instructions_returns_empty_array():
-    circ = Circuit()
-    circ.as_unitary() == []
-
-
-@pytest.mark.parametrize(
-    "circuit",
-    [
-        (Circuit().phaseshift(0, 0.15).apply_gate_noise(noise.Noise.BitFlip(probability=0.1))),
-        (Circuit().cnot(1, 0).apply_gate_noise(noise.Noise.TwoQubitDepolarizing(probability=0.1))),
-        (
-            Circuit()
-            .x(1)
-            .i(2)
-            .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[1])
-        ),
-        (
-            Circuit()
-            .x(1)
-            .i(2)
-            .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[2])
-        ),
-        (Circuit().x(1).i(2).apply_gate_noise(noise.Noise.BitFlip(probability=0.1))),
-        (Circuit().x(1).apply_gate_noise(noise.Noise.BitFlip(probability=0.1)).i(2)),
-        (
-            Circuit()
-            .y(1)
-            .z(2)
-            .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[1])
-        ),
-        (
-            Circuit()
-            .y(1)
-            .z(2)
-            .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[2])
-        ),
-        (Circuit().y(1).z(2).apply_gate_noise(noise.Noise.BitFlip(probability=0.1))),
-        (Circuit().y(1).apply_gate_noise(noise.Noise.BitFlip(probability=0.1)).z(2)),
-        (
-            Circuit()
-            .cphaseshift(2, 1, 0.15)
-            .si(3)
-            .apply_gate_noise(
-                noise.Noise.TwoQubitDepolarizing(probability=0.1), target_qubits=[1, 2]
-            )
-        ),
-        (
-            Circuit()
-            .cphaseshift(2, 1, 0.15)
-            .apply_gate_noise(noise.Noise.TwoQubitDepolarizing(probability=0.1))
-            .si(3)
-        ),
-    ],
-)
-def test_as_unitary_noise_raises_error(circuit):
-    with pytest.raises(TypeError):
-        circuit.as_unitary()
-
-
-def test_as_unitary_parameterized():
-    theta = FreeParameter("theta")
-    circ = Circuit().rx(angle=theta, target=0)
-    with pytest.raises(TypeError):
-        assert np.allclose(circ.as_unitary())
-
-
-def test_as_unitary_noise_not_apply_returns_expected_unitary(recwarn):
-    circuit = (
-        Circuit()
-        .cphaseshift(2, 1, 0.15)
-        .si(3)
-        .apply_gate_noise(noise.Noise.TwoQubitDepolarizing(probability=0.1), target_qubits=[1, 3])
-    )
-
-    assert len(recwarn) == 1
-    assert str(recwarn[0].message).startswith("Noise is not applied to any gate")
-
-    assert np.allclose(
-        circuit.as_unitary(),
-        np.kron(gates.Si().to_matrix(), np.kron(gates.CPhaseShift(0.15).to_matrix(), np.eye(2))),
-    )
-
-
-def test_as_unitary_with_compiler_directives_returns_expected_unitary():
-    circuit = Circuit().add_verbatim_box(Circuit().cphaseshift(2, 1, 0.15).si(3))
-    assert np.allclose(
-        circuit.as_unitary(),
-        np.kron(gates.Si().to_matrix(), np.kron(gates.CPhaseShift(0.15).to_matrix(), np.eye(2))),
-    )
-
-
-@pytest.mark.parametrize(
-    "circuit,expected_unitary",
-    [
-        (Circuit().h(0), gates.H().to_matrix()),
-        (Circuit().h(0).add_result_type(ResultType.Probability(target=[0])), gates.H().to_matrix()),
-        (Circuit().x(0), gates.X().to_matrix()),
-        (Circuit().y(0), gates.Y().to_matrix()),
-        (Circuit().z(0), gates.Z().to_matrix()),
-        (Circuit().s(0), gates.S().to_matrix()),
-        (Circuit().si(0), gates.Si().to_matrix()),
-        (Circuit().t(0), gates.T().to_matrix()),
-        (Circuit().ti(0), gates.Ti().to_matrix()),
-        (Circuit().v(0), gates.V().to_matrix()),
-        (Circuit().vi(0), gates.Vi().to_matrix()),
-        (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().phaseshift(0, 0.15), gates.PhaseShift(0.15).to_matrix()),
-        (Circuit().cnot(1, 0), gates.CNot().to_matrix()),
-        (Circuit().cnot(1, 0).add_result_type(ResultType.StateVector()), gates.CNot().to_matrix()),
-        (Circuit().swap(1, 0), gates.Swap().to_matrix()),
-        (Circuit().swap(0, 1), gates.Swap().to_matrix()),
-        (Circuit().iswap(1, 0), gates.ISwap().to_matrix()),
-        (Circuit().iswap(0, 1), gates.ISwap().to_matrix()),
-        (Circuit().pswap(1, 0, 0.15), gates.PSwap(0.15).to_matrix()),
-        (Circuit().pswap(0, 1, 0.15), gates.PSwap(0.15).to_matrix()),
-        (Circuit().xy(1, 0, 0.15), gates.XY(0.15).to_matrix()),
-        (Circuit().xy(0, 1, 0.15), gates.XY(0.15).to_matrix()),
-        (Circuit().cphaseshift(1, 0, 0.15), gates.CPhaseShift(0.15).to_matrix()),
-        (Circuit().cphaseshift00(1, 0, 0.15), gates.CPhaseShift00(0.15).to_matrix()),
-        (Circuit().cphaseshift01(1, 0, 0.15), gates.CPhaseShift01(0.15).to_matrix()),
-        (Circuit().cphaseshift10(1, 0, 0.15), gates.CPhaseShift10(0.15).to_matrix()),
-        (Circuit().cy(1, 0), gates.CY().to_matrix()),
-        (Circuit().cz(1, 0), gates.CZ().to_matrix()),
-        (Circuit().xx(1, 0, 0.15), gates.XX(0.15).to_matrix()),
-        (Circuit().yy(1, 0, 0.15), gates.YY(0.15).to_matrix()),
-        (Circuit().zz(1, 0, 0.15), gates.ZZ(0.15).to_matrix()),
-        (Circuit().ccnot(2, 1, 0), gates.CCNot().to_matrix()),
-        (
-            Circuit()
-            .ccnot(2, 1, 0)
-            .add_result_type(ResultType.Expectation(observable=Observable.Y(), target=[1])),
-            gates.CCNot().to_matrix(),
-        ),
-        (Circuit().ccnot(1, 2, 0), gates.CCNot().to_matrix()),
-        (Circuit().cswap(2, 1, 0), gates.CSwap().to_matrix()),
-        (Circuit().cswap(2, 0, 1), gates.CSwap().to_matrix()),
-        (Circuit().h(1), np.kron(gates.H().to_matrix(), np.eye(2))),
-        (Circuit().x(1).i(2), np.kron(np.eye(2), np.kron(gates.X().to_matrix(), np.eye(2)))),
-        (
-            Circuit().y(1).z(2),
-            np.kron(gates.Z().to_matrix(), np.kron(gates.Y().to_matrix(), np.eye(2))),
-        ),
-        (Circuit().rx(1, 0.15), np.kron(gates.Rx(0.15).to_matrix(), np.eye(2))),
-        (
-            Circuit().ry(1, 0.15).i(2),
-            np.kron(np.eye(2), np.kron(gates.Ry(0.15).to_matrix(), np.eye(2))),
-        ),
-        (
-            Circuit().rz(1, 0.15).s(2),
-            np.kron(gates.S().to_matrix(), np.kron(gates.Rz(0.15).to_matrix(), np.eye(2))),
-        ),
-        (Circuit().pswap(2, 1, 0.15), np.kron(gates.PSwap(0.15).to_matrix(), np.eye(2))),
-        (Circuit().pswap(1, 2, 0.15), np.kron(gates.PSwap(0.15).to_matrix(), np.eye(2))),
-        (
-            Circuit().xy(2, 1, 0.15).i(3),
-            np.kron(np.eye(2), np.kron(gates.XY(0.15).to_matrix(), np.eye(2))),
-        ),
-        (
-            Circuit().xy(1, 2, 0.15).i(3),
-            np.kron(np.eye(2), np.kron(gates.XY(0.15).to_matrix(), np.eye(2))),
-        ),
-        (
-            Circuit().cphaseshift(2, 1, 0.15).si(3),
-            np.kron(
-                gates.Si().to_matrix(), np.kron(gates.CPhaseShift(0.15).to_matrix(), np.eye(2))
-            ),
-        ),
-        (Circuit().ccnot(3, 2, 1), np.kron(gates.CCNot().to_matrix(), np.eye(2))),
-        (Circuit().ccnot(2, 3, 1), np.kron(gates.CCNot().to_matrix(), np.eye(2))),
-        (
-            Circuit().cswap(3, 2, 1).i(4),
-            np.kron(np.eye(2), np.kron(gates.CSwap().to_matrix(), np.eye(2))),
-        ),
-        (
-            Circuit().cswap(3, 1, 2).i(4),
-            np.kron(np.eye(2), np.kron(gates.CSwap().to_matrix(), np.eye(2))),
-        ),
-        (
-            Circuit().cswap(3, 2, 1).t(4),
-            np.kron(gates.T().to_matrix(), np.kron(gates.CSwap().to_matrix(), np.eye(2))),
-        ),
-        (
-            Circuit().cswap(3, 1, 2).t(4),
-            np.kron(gates.T().to_matrix(), np.kron(gates.CSwap().to_matrix(), np.eye(2))),
-        ),
-        (Circuit().h(0).h(0), gates.I().to_matrix()),
-        (Circuit().h(0).x(0), np.dot(gates.X().to_matrix(), gates.H().to_matrix())),
-        (Circuit().x(0).h(0), np.dot(gates.H().to_matrix(), gates.X().to_matrix())),
-        (
-            Circuit().y(0).z(1).cnot(1, 0),
-            np.dot(gates.CNot().to_matrix(), np.kron(gates.Z().to_matrix(), gates.Y().to_matrix())),
-        ),
-        (
-            Circuit().z(0).y(1).cnot(1, 0),
-            np.dot(gates.CNot().to_matrix(), np.kron(gates.Y().to_matrix(), gates.Z().to_matrix())),
-        ),
-        (
-            Circuit().z(0).y(1).cnot(1, 0).cnot(2, 1),
-            np.dot(
-                np.dot(
-                    np.dot(
-                        np.kron(gates.CNot().to_matrix(), np.eye(2)),
-                        np.kron(np.eye(2), gates.CNot().to_matrix()),
-                    ),
-                    np.kron(np.kron(np.eye(2), gates.Y().to_matrix()), np.eye(2)),
-                ),
-                np.kron(np.eye(4), gates.Z().to_matrix()),
-            ),
-        ),
-        (
-            Circuit().z(0).y(1).cnot(1, 0).ccnot(2, 1, 0),
-            np.dot(
-                np.dot(
-                    np.dot(
-                        gates.CCNot().to_matrix(),
-                        np.kron(np.eye(2), gates.CNot().to_matrix()),
-                    ),
-                    np.kron(np.kron(np.eye(2), gates.Y().to_matrix()), np.eye(2)),
-                ),
-                np.kron(np.eye(4), gates.Z().to_matrix()),
-            ),
-        ),
-        (
-            Circuit().cnot(0, 1),
-            np.array(
-                [
-                    [1.0, 0.0, 0.0, 0.0],
-                    [0.0, 0.0, 0.0, 1.0],
-                    [0.0, 0.0, 1.0, 0.0],
-                    [0.0, 1.0, 0.0, 0.0],
-                ],
-                dtype=complex,
-            ),
-        ),
-        (
-            Circuit().ccnot(0, 1, 2),
-            np.array(
-                [
-                    [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                    [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                    [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                    [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0],
-                    [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0],
-                    [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0],
-                    [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0],
-                    [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0],
-                ],
-                dtype=complex,
-            ),
-        ),
-        (
-            Circuit().ccnot(1, 0, 2),
-            np.array(
-                [
-                    [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                    [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                    [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                    [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0],
-                    [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0],
-                    [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0],
-                    [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0],
-                    [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0],
-                ],
-                dtype=complex,
-            ),
-        ),
-        (
-            Circuit().ccnot(0, 2, 1),
-            np.array(
-                [
-                    [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                    [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                    [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                    [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0],
-                    [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0],
-                    [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0],
-                    [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0],
-                    [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0],
-                ],
-                dtype=complex,
-            ),
-        ),
-        (
-            Circuit().ccnot(2, 0, 1),
-            np.array(
-                [
-                    [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                    [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                    [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                    [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0],
-                    [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0],
-                    [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0],
-                    [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0],
-                    [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0],
-                ],
-                dtype=complex,
-            ),
-        ),
-        (
-            Circuit().s(0).v(1).cnot(0, 1).cnot(1, 2),
-            np.dot(
-                np.dot(
-                    np.dot(
-                        np.kron(
-                            np.array(
-                                [
-                                    [1.0, 0.0, 0.0, 0.0],
-                                    [0.0, 0.0, 0.0, 1.0],
-                                    [0.0, 0.0, 1.0, 0.0],
-                                    [0.0, 1.0, 0.0, 0.0],
-                                ],
-                                dtype=complex,
-                            ),
-                            np.eye(2),
-                        ),
-                        np.kron(
-                            np.eye(2),
-                            np.array(
-                                [
-                                    [1.0, 0.0, 0.0, 0.0],
-                                    [0.0, 0.0, 0.0, 1.0],
-                                    [0.0, 0.0, 1.0, 0.0],
-                                    [0.0, 1.0, 0.0, 0.0],
-                                ],
-                                dtype=complex,
-                            ),
-                        ),
-                    ),
-                    np.kron(np.kron(np.eye(2), gates.V().to_matrix()), np.eye(2)),
-                ),
-                np.kron(np.eye(4), gates.S().to_matrix()),
-            ),
-        ),
-        (
-            Circuit().z(0).y(1).cnot(0, 1).ccnot(0, 1, 2),
-            np.dot(
-                np.dot(
-                    np.dot(
-                        np.array(
-                            [
-                                [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                                [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                                [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                                [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0],
-                                [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0],
-                                [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0],
-                                [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0],
-                                [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0],
-                            ],
-                            dtype=complex,
-                        ),
-                        np.kron(
-                            np.eye(2),
-                            np.array(
-                                [
-                                    [1.0, 0.0, 0.0, 0.0],
-                                    [0.0, 0.0, 0.0, 1.0],
-                                    [0.0, 0.0, 1.0, 0.0],
-                                    [0.0, 1.0, 0.0, 0.0],
-                                ],
-                                dtype=complex,
-                            ),
-                        ),
-                    ),
-                    np.kron(np.kron(np.eye(2), gates.Y().to_matrix()), np.eye(2)),
-                ),
-                np.kron(np.eye(4), gates.Z().to_matrix()),
-            ),
-        ),
-        (
-            Circuit().z(0).y(1).cnot(0, 1).ccnot(2, 0, 1),
-            np.dot(
-                np.dot(
-                    np.dot(
-                        np.array(
-                            [
-                                [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                                [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                                [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-                                [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0],
-                                [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0],
-                                [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0],
-                                [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0],
-                                [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0],
-                            ],
-                            dtype=complex,
-                        ),
-                        np.kron(
-                            np.eye(2),
-                            np.array(
-                                [
-                                    [1.0, 0.0, 0.0, 0.0],
-                                    [0.0, 0.0, 0.0, 1.0],
-                                    [0.0, 0.0, 1.0, 0.0],
-                                    [0.0, 1.0, 0.0, 0.0],
-                                ],
-                                dtype=complex,
-                            ),
-                        ),
-                    ),
-                    np.kron(np.kron(np.eye(2), gates.Y().to_matrix()), np.eye(2)),
-                ),
-                np.kron(np.eye(4), gates.Z().to_matrix()),
-            ),
-        ),
-    ],
-)
-def test_as_unitary_one_gate_returns_expected_unitary(circuit, expected_unitary):
-    assert np.allclose(circuit.as_unitary(), expected_unitary)
-
-
 def test_to_unitary_empty_instructions_returns_empty_array():
     circ = Circuit()
     circ.to_unitary() == []

From 1535bd7b0c6ab5c6892a982ca53954dcf17eeae5 Mon Sep 17 00:00:00 2001
From: ci 
Date: Wed, 25 Oct 2023 16:17:24 +0000
Subject: [PATCH 0913/1165] prepare release v1.59.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 03b0def5..a96eff9e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## v1.59.2 (2023-10-25)
+
+### Bug Fixes and Other Changes
+
+ * remove deprecated as_unitary method
+
 ## v1.59.1.post0 (2023-10-24)
 
 ### Documentation Changes
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index e754f901..461ff1f4 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.59.2.dev0"
+__version__ = "1.59.2"

From 0e19e30988531481b9ddecb84d059ab7152a727e Mon Sep 17 00:00:00 2001
From: ci 
Date: Wed, 25 Oct 2023 16:17:24 +0000
Subject: [PATCH 0914/1165] update development version to v1.59.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 461ff1f4..3ff3dc46 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.59.2"
+__version__ = "1.59.3.dev0"

From e5aca8508f90cd74d125210f364a61d26a61e748 Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Thu, 26 Oct 2023 17:35:36 -0700
Subject: [PATCH 0915/1165] feat: support dependency list for decorator hybrid
 jobs (#764)

---
 .github/workflows/twine-check.yml             |  2 +
 src/braket/jobs/hybrid_job.py                 | 30 ++++++++----
 .../unit_tests/braket/jobs/test_hybrid_job.py | 49 ++++++++++++++++++-
 3 files changed, 71 insertions(+), 10 deletions(-)

diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml
index 4465ffef..f8e01fd7 100644
--- a/.github/workflows/twine-check.yml
+++ b/.github/workflows/twine-check.yml
@@ -23,6 +23,8 @@ jobs:
         run: python -m pip install --user --upgrade wheel
       - name: Install twine
         run: python -m pip install --user --upgrade twine
+      - name: Install setuptools
+        run: python -m pip install --user --upgrade setuptools
       - name: Build a binary wheel and a source tarball
         run: python setup.py sdist bdist_wheel
       - name: Check that long description will render correctly on PyPI.
diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py
index 8de496f5..0deca7ef 100644
--- a/src/braket/jobs/hybrid_job.py
+++ b/src/braket/jobs/hybrid_job.py
@@ -25,7 +25,7 @@
 from logging import Logger, getLogger
 from pathlib import Path
 from types import ModuleType
-from typing import Any, Dict, List
+from typing import Any
 
 import cloudpickle
 
@@ -47,7 +47,7 @@ def hybrid_job(
     *,
     device: str,
     include_modules: str | ModuleType | Iterable[str | ModuleType] = None,
-    dependencies: str | Path = None,
+    dependencies: str | Path | list[str] = None,
     local: bool = False,
     job_name: str = None,
     image_uri: str = None,
@@ -85,8 +85,10 @@ def hybrid_job(
             modules to be included. Any references to members of these modules in the hybrid job
             algorithm code will be serialized as part of the algorithm code. Default value `[]`
 
-        dependencies (str | Path): Path (absolute or relative) to a requirements.txt
-            file to be used for the hybrid job.
+        dependencies (str | Path | list[str]): Path (absolute or relative) to a requirements.txt
+            file, or alternatively a list of strings, with each string being a `requirement
+            specifier `_, to be used for the hybrid job.
 
         local (bool): Whether to use local mode for the hybrid job. Default `False`
 
@@ -178,7 +180,7 @@ def job_wrapper(*args, **kwargs) -> Callable:
                     entry_point_file.write(template)
 
                 if dependencies:
-                    shutil.copy(Path(dependencies).resolve(), temp_dir_path / "requirements.txt")
+                    _process_dependencies(dependencies, temp_dir_path)
 
                 job_args = {
                     "device": device or "local:none/none",
@@ -241,6 +243,16 @@ 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)):
+        # requirements file
+        shutil.copy(Path(dependencies).resolve(), temp_dir / "requirements.txt")
+    else:
+        # list of packages
+        with open(temp_dir / "requirements.txt", "w") as f:
+            f.write("\n".join(dependencies))
+
+
 class _IncludeModules:
     def __init__(self, modules: str | ModuleType | Iterable[str | ModuleType] = None):
         modules = modules or []
@@ -285,7 +297,7 @@ def wrapped_entry_point() -> Any:
     )
 
 
-def _log_hyperparameters(entry_point: Callable, args: tuple, kwargs: dict) -> Dict:
+def _log_hyperparameters(entry_point: Callable, args: tuple, kwargs: dict) -> dict:
     """Capture function arguments as hyperparameters"""
     signature = inspect.signature(entry_point)
     bound_args = signature.bind(*args, **kwargs)
@@ -326,11 +338,11 @@ 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
 
 
-def _process_input_data(input_data: Dict) -> List[str]:
+def _process_input_data(input_data: dict) -> list[str]:
     """
     Create symlinks to data
 
@@ -344,7 +356,7 @@ def _process_input_data(input_data: Dict) -> List[str]:
     if not isinstance(input_data, dict):
         input_data = {"input": input_data}
 
-    def matches(prefix: str) -> List[str]:
+    def matches(prefix: str) -> list[str]:
         return [
             str(path) for path in Path(prefix).parent.iterdir() if str(path).startswith(str(prefix))
         ]
diff --git a/test/unit_tests/braket/jobs/test_hybrid_job.py b/test/unit_tests/braket/jobs/test_hybrid_job.py
index 2877063d..5c804b0c 100644
--- a/test/unit_tests/braket/jobs/test_hybrid_job.py
+++ b/test/unit_tests/braket/jobs/test_hybrid_job.py
@@ -228,6 +228,53 @@ def my_entry():
     assert mock_tempdir.return_value.__exit__.called
 
 
+@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_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest"
+    dependency_list = ["dep_1", "dep_2", "dep_3"]
+
+    @hybrid_job(
+        device=None,
+        aws_session=aws_session,
+        dependencies=dependency_list,
+    )
+    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
+
+    source_module = mock_tempdir_name
+    entry_point = f"{mock_tempdir_name}.entry_point:my_entry"
+    wait_until_complete = False
+
+    device = "local:none/none"
+
+    my_entry()
+
+    mock_create.assert_called_with(
+        device=device,
+        source_module=source_module,
+        entry_point=entry_point,
+        wait_until_complete=wait_until_complete,
+        job_name="my-entry-123000",
+        hyperparameters={"c": "0", "d": "1.0"},
+        logger=getLogger("braket.jobs.hybrid_job"),
+        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.return_value.__enter__.return_value.write.assert_called_with(
+        "\n".join(dependency_list)
+    )
+
+
 @patch.object(sys.modules["braket.jobs.hybrid_job"], "retrieve_image")
 @patch("time.time", return_value=123.0)
 @patch("builtins.open")
@@ -487,7 +534,7 @@ def my_job():
         ),
         (
             "?" * 2600,
-            f"{'?'*2477}...{'?'*20}",
+            f"{'?' * 2477}...{'?' * 20}",
         ),
     ),
 )

From 10f49958a169ef9fc41ab7407ae885efdbfd1aab Mon Sep 17 00:00:00 2001
From: Abe Coull <85974725+math411@users.noreply.github.com>
Date: Fri, 27 Oct 2023 16:18:38 -0700
Subject: [PATCH 0916/1165] doc: update intended audience to include education
 and research (#768)

---
 setup.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/setup.py b/setup.py
index d3724c59..f1577748 100644
--- a/setup.py
+++ b/setup.py
@@ -74,6 +74,8 @@
     classifiers=[
         "Development Status :: 5 - Production/Stable",
         "Intended Audience :: Developers",
+        "Intended Audience :: Education",
+        "Intended Audience :: Science/Research",
         "Natural Language :: English",
         "License :: OSI Approved :: Apache Software License",
         "Programming Language :: Python",

From eca82e6bef519159c8844522e09ebcc7ad3b96a7 Mon Sep 17 00:00:00 2001
From: Lauren Capelluto 
Date: Mon, 30 Oct 2023 12:15:22 -0400
Subject: [PATCH 0917/1165] [feature] AutoQASM: Free parameters (#762)

* feature: Add basic support for FreeParameters
---
 src/braket/devices/local_simulator.py         |   8 +-
 src/braket/experimental/autoqasm/api.py       |  24 +-
 .../autoqasm/instructions/gates.py            |  79 ++---
 .../autoqasm/instructions/instructions.py     |   4 +-
 .../experimental/autoqasm/program/program.py  |  50 ++-
 .../experimental/autoqasm/test_parameters.py  | 289 ++++++++++++++++++
 6 files changed, 401 insertions(+), 53 deletions(-)
 create mode 100644 test/unit_tests/braket/experimental/autoqasm/test_parameters.py

diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py
index 253732a0..c3c70cd6 100644
--- a/src/braket/devices/local_simulator.py
+++ b/src/braket/devices/local_simulator.py
@@ -317,7 +317,13 @@ def _(
         if DeviceActionType.OPENQASM not in simulator.properties.action:
             raise NotImplementedError(f"{type(simulator)} does not support OpenQASM programs")
         program = Program(source=program.to_ir(ir_type=IRType.OPENQASM))
-
+        if inputs:
+            inputs_copy = program.inputs.copy() if program.inputs is not None else {}
+            inputs_copy.update(inputs)
+            program = Program(
+                source=program.source,
+                inputs=inputs_copy,
+            )
         # Pass mcm=True to the simulator to enable mid-circuit measurement simulation.
         # When setting mcm=True, the simulator returns only the measurement results,
         # which we then wrap into a GateModelQuantumTaskResult object to return.
diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py
index b6f421e3..6e3c4fee 100644
--- a/src/braket/experimental/autoqasm/api.py
+++ b/src/braket/experimental/autoqasm/api.py
@@ -18,7 +18,7 @@
 import inspect
 from collections.abc import Callable
 from types import FunctionType
-from typing import Any, Optional, Union
+from typing import Any, Optional, Union, get_args
 
 import openqasm3.ast as qasm_ast
 import oqpy.base
@@ -206,12 +206,23 @@ def _convert_main(
         # Process the program
         aq_transpiler.converted_call(f, args, kwargs, options=options)
 
-        # Modify program to add qubit declaration if necessary
+        # Modify program to add global declarations if necessary
         _add_qubit_declaration(program_conversion_context)
+        _add_io_declarations(program_conversion_context)
 
     return program_conversion_context.make_program()
 
 
+def _add_io_declarations(program_conversion_context: aq_program.ProgramConversionContext) -> None:
+    root_oqpy_program = program_conversion_context.get_oqpy_program(
+        scope=aq_program.ProgramScope.MAIN
+    )
+    root_oqpy_program.declare(
+        program_conversion_context.get_free_parameters(),
+        to_beginning=True,
+    )
+
+
 def _add_qubit_declaration(program_conversion_context: aq_program.ProgramConversionContext) -> None:
     """Modify the program to include a global qubit register declaration.
 
@@ -296,6 +307,7 @@ def _convert_subroutine(
 
             # Process the program
             subroutine_function_call = oqpy_sub(oqpy_program, *args, **kwargs)
+            program_conversion_context.register_args(args)
 
             # Mark that we are finished processing this function
             program_conversion_context.subroutines_processing.remove(f)
@@ -534,9 +546,11 @@ def _get_gate_args(f: Callable) -> aq_program.GateArgs:
             )
 
         if param.annotation == aq_instructions.QubitIdentifierType:
-            gate_args.append(param.name, True)
-        elif param.annotation in [float, aq_types.FloatVar]:
-            gate_args.append(param.name, False)
+            gate_args.append_qubit(param.name)
+        elif param.annotation == float or any(
+            type_ == float for type_ in get_args(param.annotation)
+        ):
+            gate_args.append_angle(param.name)
         else:
             raise errors.ParameterTypeError(
                 f'Parameter "{param.name}" for gate "{f.__name__}" '
diff --git a/src/braket/experimental/autoqasm/instructions/gates.py b/src/braket/experimental/autoqasm/instructions/gates.py
index aff44f6b..40ee7518 100644
--- a/src/braket/experimental/autoqasm/instructions/gates.py
+++ b/src/braket/experimental/autoqasm/instructions/gates.py
@@ -12,8 +12,11 @@
 # language governing permissions and limitations under the License.
 
 
-"""Quantum gates, unitary instructions, that apply to qubits.
-"""
+"""Quantum gates, unitary instructions, that apply to qubits."""
+
+from typing import Union
+
+from braket.circuits.free_parameter_expression import FreeParameterExpression
 
 from .instructions import _qubit_instruction
 from .qubits import QubitIdentifierType
@@ -52,14 +55,14 @@ def cnot(
 def cphaseshift(
     control: QubitIdentifierType,
     target: QubitIdentifierType,
-    angle: float,
+    angle: Union[float, FreeParameterExpression],
 ) -> None:
     """Controlled phase shift gate.
 
     Args:
         control (QubitIdentifierType): Control qubit.
         target (QubitIdentifierType): Target qubit.
-        angle (float): Rotation angle in radians.
+        angle (Union[float, FreeParameterExpression]): Rotation angle in radians.
 
     """
     _qubit_instruction("cphaseshift", [control, target], angle)
@@ -68,14 +71,14 @@ def cphaseshift(
 def cphaseshift00(
     control: QubitIdentifierType,
     target: QubitIdentifierType,
-    angle: float,
+    angle: Union[float, FreeParameterExpression],
 ) -> None:
     """Controlled phase shift gate for phasing the \\|00> state.
 
     Args:
         control (QubitIdentifierType): Control qubit.
         target (QubitIdentifierType): Target qubit.
-        angle (float): Rotation angle in radians.
+        angle (Union[float, FreeParameterExpression]): Rotation angle in radians.
 
     """
     _qubit_instruction("cphaseshift00", [control, target], angle)
@@ -84,14 +87,14 @@ def cphaseshift00(
 def cphaseshift01(
     control: QubitIdentifierType,
     target: QubitIdentifierType,
-    angle: float,
+    angle: Union[float, FreeParameterExpression],
 ) -> None:
     """Controlled phase shift gate for phasing the \\|01> state.
 
     Args:
         control (QubitIdentifierType): Control qubit.
         target (QubitIdentifierType): Target qubit.
-        angle (float): Rotation angle in radians.
+        angle (Union[float, FreeParameterExpression]): Rotation angle in radians.
 
     """
     _qubit_instruction("cphaseshift01", [control, target], angle)
@@ -100,14 +103,14 @@ def cphaseshift01(
 def cphaseshift10(
     control: QubitIdentifierType,
     target: QubitIdentifierType,
-    angle: float,
+    angle: Union[float, FreeParameterExpression],
 ) -> None:
     """Controlled phase shift gate for phasing the \\|10> state.
 
     Args:
         control (QubitIdentifierType): Control qubit.
         target (QubitIdentifierType): Target qubit.
-        angle (float): Rotation angle in radians.
+        angle (Union[float, FreeParameterExpression]): Rotation angle in radians.
 
     """
     _qubit_instruction("cphaseshift10", [control, target], angle)
@@ -187,13 +190,13 @@ def ecr(
 
 def gpi(
     target: QubitIdentifierType,
-    angle: float,
+    angle: Union[float, FreeParameterExpression],
 ) -> None:
     """IonQ GPi gate.
 
     Args:
         target (QubitIdentifierType): Target qubit.
-        angle (float): Rotation angle in radians.
+        angle (Union[float, FreeParameterExpression]): Rotation angle in radians.
 
     """
     _qubit_instruction("gpi", [target], angle)
@@ -201,13 +204,13 @@ def gpi(
 
 def gpi2(
     target: QubitIdentifierType,
-    angle: float,
+    angle: Union[float, FreeParameterExpression],
 ) -> None:
     """IonQ GPi2 gate.
 
     Args:
         target (QubitIdentifierType): Target qubit.
-        angle (float): Rotation angle in radians.
+        angle (Union[float, FreeParameterExpression]): Rotation angle in radians.
 
     """
     _qubit_instruction("gpi2", [target], angle)
@@ -254,18 +257,18 @@ def iswap(
 def ms(
     target_0: QubitIdentifierType,
     target_1: QubitIdentifierType,
-    angle_0: float,
-    angle_1: float,
-    angle_2: float,
+    angle_0: Union[float, FreeParameterExpression],
+    angle_1: Union[float, FreeParameterExpression],
+    angle_2: Union[float, FreeParameterExpression],
 ) -> None:
     """IonQ Mølmer-Sørenson gate.
 
     Args:
         target_0 (QubitIdentifierType): Target qubit 0.
         target_1 (QubitIdentifierType): Target qubit 1.
-        angle_0 (float): Rotation angle 0 in radians.
-        angle_1 (float): Rotation angle 1 in radians.
-        angle_2 (float): Rotation angle 2 in radians.
+        angle_0 (Union[float, FreeParameterExpression]): Rotation angle 0 in radians.
+        angle_1 (Union[float, FreeParameterExpression]): Rotation angle 1 in radians.
+        angle_2 (Union[float, FreeParameterExpression]): Rotation angle 2 in radians.
 
     """
     _qubit_instruction("ms", [target_0, target_1], angle_0, angle_1, angle_2)
@@ -273,13 +276,13 @@ def ms(
 
 def phaseshift(
     target: QubitIdentifierType,
-    angle: float,
+    angle: Union[float, FreeParameterExpression],
 ) -> None:
     """Phase shift gate.
 
     Args:
         target (QubitIdentifierType): Target qubit.
-        angle (float): Rotation angle in radians.
+        angle (Union[float, FreeParameterExpression]): Rotation angle in radians.
 
     """
     _qubit_instruction("phaseshift", [target], angle)
@@ -288,14 +291,14 @@ def phaseshift(
 def pswap(
     target_0: QubitIdentifierType,
     target_1: QubitIdentifierType,
-    angle: float,
+    angle: Union[float, FreeParameterExpression],
 ) -> None:
     """PSwap gate.
 
     Args:
         target_0 (QubitIdentifierType): Target qubit 0.
         target_1 (QubitIdentifierType): Target qubit 1.
-        angle (float): Rotation angle in radians.
+        angle (Union[float, FreeParameterExpression]): Rotation angle in radians.
 
     """
     _qubit_instruction("pswap", [target_0, target_1], angle)
@@ -303,13 +306,13 @@ def pswap(
 
 def rx(
     target: QubitIdentifierType,
-    angle: float,
+    angle: Union[float, FreeParameterExpression],
 ) -> None:
     """X-axis rotation gate.
 
     Args:
         target (QubitIdentifierType): Target qubit.
-        angle (float): Rotation angle in radians.
+        angle (Union[float, FreeParameterExpression]): Rotation angle in radians.
 
     """
     _qubit_instruction("rx", [target], angle)
@@ -317,13 +320,13 @@ def rx(
 
 def ry(
     target: QubitIdentifierType,
-    angle: float,
+    angle: Union[float, FreeParameterExpression],
 ) -> None:
     """Y-axis rotation gate.
 
     Args:
         target (QubitIdentifierType): Target qubit.
-        angle (float): Rotation angle in radians.
+        angle (Union[float, FreeParameterExpression]): Rotation angle in radians.
 
     """
     _qubit_instruction("ry", [target], angle)
@@ -331,13 +334,13 @@ def ry(
 
 def rz(
     target: QubitIdentifierType,
-    angle: float,
+    angle: Union[float, FreeParameterExpression],
 ) -> None:
     """Z-axis rotation gate.
 
     Args:
         target (QubitIdentifierType): Target qubit.
-        angle (float): Rotation angle in radians.
+        angle (Union[float, FreeParameterExpression]): Rotation angle in radians.
 
     """
     _qubit_instruction("rz", [target], angle)
@@ -444,14 +447,14 @@ def x(
 def xx(
     target_0: QubitIdentifierType,
     target_1: QubitIdentifierType,
-    angle: float,
+    angle: Union[float, FreeParameterExpression],
 ) -> None:
     """Ising XX coupling gate.
 
     Args:
         target_0 (QubitIdentifierType): Target qubit 0.
         target_1 (QubitIdentifierType): Target qubit 1.
-        angle (float): Rotation angle in radians.
+        angle (Union[float, FreeParameterExpression]): Rotation angle in radians.
 
     """
     _qubit_instruction("xx", [target_0, target_1], angle)
@@ -460,14 +463,14 @@ def xx(
 def xy(
     target_0: QubitIdentifierType,
     target_1: QubitIdentifierType,
-    angle: float,
+    angle: Union[float, FreeParameterExpression],
 ) -> None:
     """XY gates
 
     Args:
         target_0 (QubitIdentifierType): Target qubit 0.
         target_1 (QubitIdentifierType): Target qubit 1.
-        angle (float): Rotation angle in radians.
+        angle (Union[float, FreeParameterExpression]): Rotation angle in radians.
 
     """
     _qubit_instruction("xy", [target_0, target_1], angle)
@@ -488,14 +491,14 @@ def y(
 def yy(
     target_0: QubitIdentifierType,
     target_1: QubitIdentifierType,
-    angle: float,
+    angle: Union[float, FreeParameterExpression],
 ) -> None:
     """Ising YY coupling gate.
 
     Args:
         target_0 (QubitIdentifierType): Target qubit 0.
         target_1 (QubitIdentifierType): Target qubit 1.
-        angle (float): Rotation angle in radians.
+        angle (Union[float, FreeParameterExpression]): Rotation angle in radians.
 
     """
     _qubit_instruction("yy", [target_0, target_1], angle)
@@ -516,14 +519,14 @@ def z(
 def zz(
     target_0: QubitIdentifierType,
     target_1: QubitIdentifierType,
-    angle: float,
+    angle: Union[float, FreeParameterExpression],
 ) -> None:
     """Ising ZZ coupling gate.
 
     Args:
         target_0 (QubitIdentifierType): Target qubit 0.
         target_1 (QubitIdentifierType): Target qubit 1.
-        angle (float): Rotation angle in radians.
+        angle (Union[float, FreeParameterExpression]): Rotation angle in radians.
 
     """
     _qubit_instruction("zz", [target_0, target_1], angle)
diff --git a/src/braket/experimental/autoqasm/instructions/instructions.py b/src/braket/experimental/autoqasm/instructions/instructions.py
index 24077b16..6a846caf 100644
--- a/src/braket/experimental/autoqasm/instructions/instructions.py
+++ b/src/braket/experimental/autoqasm/instructions/instructions.py
@@ -12,8 +12,7 @@
 # language governing permissions and limitations under the License.
 
 
-"""Non-unitary instructions that apply to qubits.
-"""
+"""Non-unitary instructions that apply to qubits."""
 
 from typing import Any
 
@@ -30,6 +29,7 @@ def _qubit_instruction(
 
     # Add the instruction to the program.
     program_conversion_context.register_gate(name)
+    program_conversion_context.register_args(args)
     program_mode = aq_program.ProgramMode.UNITARY if is_unitary else aq_program.ProgramMode.NONE
     oqpy_program = program_conversion_context.get_oqpy_program(mode=program_mode)
     oqpy_program.gate([_qubit(q) for q in qubits], name, *args)
diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py
index b6eaed22..d2538061 100644
--- a/src/braket/experimental/autoqasm/program/program.py
+++ b/src/braket/experimental/autoqasm/program/program.py
@@ -23,6 +23,8 @@
 
 import oqpy.base
 
+from braket.circuits.free_parameter import FreeParameter
+from braket.circuits.free_parameter_expression import FreeParameterExpression
 from braket.circuits.serialization import IRType, SerializableProgram
 from braket.device_schema import DeviceActionType
 from braket.devices.device import Device
@@ -162,17 +164,21 @@ def __init__(self):
     def __len__(self):
         return len(self._args)
 
-    def append(self, name: str, is_qubit: bool) -> None:
-        """Appends an argument to the list of gate arguments.
+    def append_qubit(self, name: str) -> None:
+        """Appends a qubit argument to the list of gate arguments.
 
         Args:
             name (str): The name of the argument.
-            is_qubit (bool): Whether the argument represents a qubit.
         """
-        if is_qubit:
-            self._args.append(oqpy.Qubit(name, needs_declaration=False))
-        else:
-            self._args.append(oqpy.AngleVar(name=name))
+        self._args.append(oqpy.Qubit(name, needs_declaration=False))
+
+    def append_angle(self, name: str) -> None:
+        """Appends a parameter argument to the list of gate arguments.
+
+        Args:
+            name (str): The name of the argument.
+        """
+        self._args.append(oqpy.AngleVar(name=name))
 
     @property
     def qubits(self) -> list[oqpy.Qubit]:
@@ -207,6 +213,7 @@ def __init__(self, user_config: Optional[UserConfig] = None):
         self._virtual_qubits_used = set()
         self._var_idx = 0
         self._has_pulse_control = False
+        self._free_parameters = {}
 
     def make_program(self) -> Program:
         """Makes a Program object using the oqpy program from this conversion context.
@@ -276,6 +283,35 @@ def register_gate(self, gate_name: str) -> None:
                     f"block. The native gates of the device are: {native_gates}"
                 )
 
+    def register_args(self, args: list[Any]) -> None:
+        """Register any FreeParameters in the list of arguments.
+
+        Args:
+            args (list[Any]): Arguments passed to the main program or a subroutine.
+        """
+        for arg in args:
+            if isinstance(arg, FreeParameter):
+                self.register_parameter(arg.name)
+            elif isinstance(arg, FreeParameterExpression):
+                # TODO laurecap: Support for expressions
+                raise NotImplementedError(
+                    "Expressions of FreeParameters will be supported shortly!"
+                )
+
+    def register_parameter(self, name: str) -> None:
+        """Register an input parameter with the given name, if it has not already been
+        registered. Only floats are currently supported.
+
+        Args:
+            name (str): The identifier for the parameter.
+        """
+        if name not in self._free_parameters:
+            self._free_parameters[name] = oqpy.FloatVar("input", name=name)
+
+    def get_free_parameters(self) -> list[oqpy.FloatVar]:
+        """Return a list of named oqpy.Vars that are used as free parameters in the program."""
+        return list(self._free_parameters.values())
+
     def get_target_device(self) -> Optional[Device]:
         """Return the target device for the program, as specified by the user.
         Returns None if the user did not specify a target device.
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py
new file mode 100644
index 00000000..3971d45c
--- /dev/null
+++ b/test/unit_tests/braket/experimental/autoqasm/test_parameters.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.
+
+"""AutoQASM tests for parameter support."""
+
+import braket.experimental.autoqasm as aq
+from braket.circuits import FreeParameter
+from braket.default_simulator import StateVectorSimulator
+from braket.devices.local_simulator import LocalSimulator
+from braket.experimental.autoqasm import pulse
+from braket.experimental.autoqasm.instructions import cnot, cphaseshift, measure, ms, rx, rz
+from braket.tasks.local_quantum_task import LocalQuantumTask
+
+
+def _test_parametric_on_local_sim(program: aq.Program, inputs: dict[str, float]) -> None:
+    device = LocalSimulator(backend=StateVectorSimulator())
+    task = device.run(program, shots=10, inputs=inputs)
+    assert isinstance(task, LocalQuantumTask)
+    assert isinstance(task.result().measurements, dict)
+    return task.result().measurements
+
+
+@aq.main
+def simple_parametric():
+    rx(0, FreeParameter("theta"))
+    measure(0)
+
+
+def test_simple_parametric():
+    """Test a program with a parameter can be serialized."""
+
+    expected = """OPENQASM 3.0;
+input float[64] theta;
+qubit[1] __qubits__;
+rx(theta) __qubits__[0];
+bit __bit_0__;
+__bit_0__ = measure __qubits__[0];"""
+    assert simple_parametric().to_ir() == expected
+
+
+def test_sim_simple():
+    measurements = _test_parametric_on_local_sim(simple_parametric(), {"theta": 0})
+    assert 1 not in measurements["__bit_0__"]
+    measurements = _test_parametric_on_local_sim(simple_parametric(), {"theta": 3.14})
+    assert 0 not in measurements["__bit_0__"]
+
+
+@aq.main
+def multi_parametric():
+    rx(0, FreeParameter("alpha"))
+    rx(1, FreeParameter("theta"))
+    c = measure([0, 1])  # noqa: F841
+
+
+def test_multiple_parameters():
+    """Test that multiple free parameters all appear in the processed program."""
+
+    expected = """OPENQASM 3.0;
+bit[2] c;
+input float[64] alpha;
+input float[64] theta;
+qubit[2] __qubits__;
+rx(alpha) __qubits__[0];
+rx(theta) __qubits__[1];
+bit[2] __bit_0__ = "00";
+__bit_0__[0] = measure __qubits__[0];
+__bit_0__[1] = measure __qubits__[1];
+c = __bit_0__;"""
+    assert multi_parametric().to_ir() == expected
+
+
+def test_sim_multi_param():
+    measurements = _test_parametric_on_local_sim(multi_parametric(), {"alpha": 3.14, "theta": 0})
+    assert all(val == "10" for val in measurements["c"])
+    measurements = _test_parametric_on_local_sim(multi_parametric(), {"alpha": 0, "theta": 3.14})
+    assert all(val == "01" for val in measurements["c"])
+
+
+def test_repeat_parameter():
+    """Test that programs can use the same parameter multiple times."""
+
+    @aq.main
+    def parametric():
+        alpha = FreeParameter("alpha")
+        theta = FreeParameter("theta")
+        rx(0, alpha)
+        rx(1, theta)
+        cnot(0, 1)
+        rx(0, theta)
+        rx(1, alpha)
+
+    expected = """OPENQASM 3.0;
+input float[64] alpha;
+input float[64] theta;
+qubit[2] __qubits__;
+rx(alpha) __qubits__[0];
+rx(theta) __qubits__[1];
+cnot __qubits__[0], __qubits__[1];
+rx(theta) __qubits__[0];
+rx(alpha) __qubits__[1];"""
+    assert parametric().to_ir() == expected
+
+
+def test_parameter_in_subroutine():
+    """Test that parameters in subroutines are declared appropriately."""
+
+    @aq.subroutine
+    def rx_alpha(qubit: int):
+        rx(qubit, FreeParameter("alpha"))
+
+    @aq.main(num_qubits=3)
+    def parametric():
+        rx_alpha(2)
+
+    expected = """OPENQASM 3.0;
+def rx_alpha(int[32] qubit) {
+    rx(alpha) __qubits__[qubit];
+}
+input float[64] alpha;
+qubit[3] __qubits__;
+rx_alpha(2);"""
+    assert parametric().to_ir() == expected
+
+
+def test_captured_parameter():
+    """Test that a parameter declared in a larger scope is captured
+    and functions correctly.
+    """
+
+    alpha = FreeParameter("alpha")
+
+    @aq.main
+    def parametric():
+        rz(0, alpha)
+        rx(1, alpha)
+
+    expected = """OPENQASM 3.0;
+input float[64] alpha;
+qubit[2] __qubits__;
+rz(alpha) __qubits__[0];
+rx(alpha) __qubits__[1];"""
+    assert parametric().to_ir() == expected
+
+
+def test_multi_angle_gates():
+    """Test that FreeParameters work with gates that take multiple inputs."""
+
+    @aq.main(num_qubits=5)
+    def parametric(qubit_0: int, phi: float, theta: float):
+        ms(0, qubit_0, phi, phi, theta)
+
+    expected = """OPENQASM 3.0;
+input float[64] phi;
+qubit[5] __qubits__;
+ms(phi, phi, 0.5) __qubits__[0], __qubits__[2];"""
+    assert parametric(2, FreeParameter("phi"), 0.5).to_ir() == expected
+
+
+def test_sim_multi_angle():
+    @aq.main
+    def parametric(phi: float, theta: float):
+        ms(0, 1, phi, phi, theta)
+
+    _test_parametric_on_local_sim(parametric(FreeParameter("phi"), 0.0), {"phi": 3.14})
+
+
+def test_parameters_passed_as_main_arg():
+    """Test that parameters work when passed as input values."""
+
+    @aq.main
+    def parametric(phi: float):
+        cphaseshift(0, 1, phi)
+
+    expected = """OPENQASM 3.0;
+input float[64] my_phi;
+qubit[2] __qubits__;
+cphaseshift(my_phi) __qubits__[0], __qubits__[1];"""
+    assert parametric(FreeParameter("my_phi")).to_ir() == expected
+
+
+def test_simple_subroutine_arg():
+    """Test that parameters work when passed as input values."""
+
+    @aq.subroutine
+    def silly_rz(theta: float):
+        rz(0, theta)
+
+    @aq.main(num_qubits=1)
+    def parametric():
+        silly_rz(FreeParameter("alpha"))
+
+    expected = """OPENQASM 3.0;
+def silly_rz(float[64] theta) {
+    rz(theta) __qubits__[0];
+}
+input float[64] alpha;
+qubit[1] __qubits__;
+silly_rz(alpha);"""
+    assert parametric().to_ir() == expected
+
+
+def test_parameters_passed_as_subroutine_arg():
+    """Test that parameters work when passed as input values."""
+
+    @aq.subroutine
+    def silly_ms(qubit_0: int, phi: float, theta: float):
+        ms(0, qubit_0, phi, phi, theta)
+
+    @aq.main(num_qubits=5)
+    def parametric():
+        silly_ms(1, FreeParameter("alpha"), 0.707)
+        silly_ms(3, 0.5, FreeParameter("beta"))
+
+    expected = """OPENQASM 3.0;
+def silly_ms(int[32] qubit_0, float[64] phi, float[64] theta) {
+    ms(phi, phi, theta) __qubits__[0], __qubits__[qubit_0];
+}
+input float[64] alpha;
+input float[64] beta;
+qubit[5] __qubits__;
+silly_ms(1, alpha, 0.707);
+silly_ms(3, 0.5, beta);"""
+    assert parametric().to_ir() == expected
+
+
+def test_sim_subroutine_arg():
+    @aq.subroutine
+    def rx_theta(theta: float):
+        rx(0, theta)
+
+    @aq.main
+    def parametric():
+        rx_theta(FreeParameter("theta"))
+        measure(0)
+
+    measurements = _test_parametric_on_local_sim(parametric(), {"theta": 3.14})
+    assert 0 not in measurements["__bit_0__"]
+
+
+def test_parametric_gate_args():
+    """Test that gates can be used with parameters."""
+
+    @aq.gate
+    def rx_theta(q: aq.Qubit, theta: float):
+        rx(q, theta)
+
+    @aq.main(num_qubits=3)
+    def parametric():
+        rx_theta(2, FreeParameter("θ"))
+
+    expected = """OPENQASM 3.0;
+gate rx_theta(theta) q {
+    rx(theta) q;
+}
+input float[64] θ;
+qubit[3] __qubits__;
+rx_theta(θ) __qubits__[2];"""
+    assert parametric().to_ir() == expected
+
+
+def test_parametric_pulse_cals():
+    """Test that pulse calibrations work with free parameters."""
+
+    @aq.gate_calibration(implements=rx, target="$1")
+    def cal_1(angle: float):
+        pulse.delay("$1", angle)
+
+    @aq.main
+    def my_program():
+        rx("$1", FreeParameter("theta"))
+
+    expected = """OPENQASM 3.0;
+defcal rx(angle[32] angle) $1 {
+    delay[(angle) * 1s] $1;
+}
+input float[64] theta;
+rx(theta) $1;"""
+    qasm = my_program().with_calibrations(cal_1).to_ir()
+    assert qasm == expected

From f92871f3e96395296a7fc2b92583ba06a7cdda4e Mon Sep 17 00:00:00 2001
From: Katharine Hyatt <67932820+kshyatt-aws@users.noreply.github.com>
Date: Mon, 30 Oct 2023 12:16:40 -0400
Subject: [PATCH 0918/1165] doc: Fix some nits in the decorator doc string
 (#750)

Add closing tics, harmonize representation of defaults.
---
 src/braket/jobs/hybrid_job.py | 28 ++++++++++++++--------------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py
index 0deca7ef..9fe28e59 100644
--- a/src/braket/jobs/hybrid_job.py
+++ b/src/braket/jobs/hybrid_job.py
@@ -68,8 +68,8 @@ def hybrid_job(
     when the decorated function is called.
 
     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`,
+    `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`.
 
     Args:
@@ -83,14 +83,14 @@ def hybrid_job(
         include_modules (str | ModuleType | Iterable[str | ModuleType]): Either a
             single module or module name or a list of module or module names referring to local
             modules to be included. Any references to members of these modules in the hybrid job
-            algorithm code will be serialized as part of the algorithm code. Default value `[]`
+            algorithm code will be serialized as part of the algorithm code. Default: `[]`
 
         dependencies (str | Path | list[str]): Path (absolute or relative) to a requirements.txt
             file, or alternatively a list of strings, with each string being a `requirement
             specifier `_, to be used for the hybrid job.
 
-        local (bool): Whether to use local mode for the hybrid job. Default `False`
+        local (bool): Whether to use local mode for the hybrid job. Default: `False`
 
         job_name (str): A string that specifies the name with which the job is created.
             Allowed pattern for job name: `^[a-zA-Z0-9](-*[a-zA-Z0-9]){0,50}$`. Defaults to
@@ -98,12 +98,12 @@ def hybrid_job(
 
         image_uri (str): A str that specifies the ECR image to use for executing the job.
             `retrieve_image()` function may be used for retrieving the ECR image URIs
-            for the containers supported by Braket. Default = ``.
+            for the containers supported by Braket. Default: ``.
 
         input_data (str | dict | S3DataSourceConfig): Information about the training
             data. Dictionary maps channel names to local paths or S3 URIs. Contents found
             at any local paths will be uploaded to S3 at
-            f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local
+            f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}'. If a local
             path, S3 URI, or S3DataSourceConfig is provided, it will be given a default
             channel name "input".
             Default: {}.
@@ -113,23 +113,23 @@ def hybrid_job(
             local mode. Default: `False`.
 
         instance_config (InstanceConfig): Configuration of the instance(s) for running the
-            classical code for the hybrid job. Defaults to
+            classical code for the hybrid job. Default:
             `InstanceConfig(instanceType='ml.m5.large', instanceCount=1, volumeSizeInGB=30)`.
 
         distribution (str): A str that specifies how the job should be distributed.
             If set to "data_parallel", the hyperparameters for the job will be set to use data
-            parallelism features for PyTorch or TensorFlow. Default: None.
+            parallelism features for PyTorch or TensorFlow. Default: `None`.
 
         copy_checkpoints_from_job (str): A str that specifies the job ARN whose
             checkpoint you want to use in the current job. Specifying this value will copy
             over the checkpoint data from `use_checkpoints_from_job`'s checkpoint_config
             s3Uri to the current job's checkpoint_config s3Uri, making it available at
-            checkpoint_config.localPath during the job execution. Default: None
+            checkpoint_config.localPath during the job execution. Default: `None`
 
         checkpoint_config (CheckpointConfig): Configuration that specifies the
             location where checkpoint data is stored.
-            Default: CheckpointConfig(localPath='/opt/jobs/checkpoints',
-            s3Uri=f's3://{default_bucket_name}/jobs/{job_name}/checkpoints').
+            Default: `CheckpointConfig(localPath='/opt/jobs/checkpoints',
+            s3Uri=f's3://{default_bucket_name}/jobs/{job_name}/checkpoints')`.
 
         role_arn (str): A str providing the IAM role ARN used to execute the
             script. Default: IAM role returned by AwsSession's `get_default_jobs_role()`.
@@ -140,8 +140,8 @@ def hybrid_job(
 
         output_data_config (OutputDataConfig): Specifies the location for the output of
             the job.
-            Default: OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data',
-            kmsKeyId=None).
+            Default: `OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data',
+            kmsKeyId=None)`.
 
         aws_session (AwsSession): AwsSession for connecting to AWS Services.
             Default: AwsSession()
@@ -150,7 +150,7 @@ def hybrid_job(
             Default: {}.
 
         logger (Logger): Logger object with which to write logs, such as task statuses
-            while waiting for task to be in a terminal state. Default is `getLogger(__name__)`
+            while waiting for task to be in a terminal state. Default: `getLogger(__name__)`
 
     Returns:
         Callable: the callable for creating a Hybrid Job.

From 71af67da5c17880854eef177b657e4be1ed8adfe Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Mon, 30 Oct 2023 15:37:24 -0400
Subject: [PATCH 0919/1165] feature: Improve AutoQASM support for arrays (#763)

---
 src/braket/experimental/autoqasm/api.py       |  14 +-
 src/braket/experimental/autoqasm/errors.py    |  12 ++
 .../autoqasm/instructions/measurements.py     |   7 +-
 .../autoqasm/operators/__init__.py            |  31 +--
 .../autoqasm/operators/assignments.py         |  62 ++++--
 .../operators/conditional_expressions.py      |  10 +-
 .../autoqasm/operators/control_flow.py        |  32 ++-
 .../autoqasm/operators/data_structures.py     |  56 +++++-
 .../autoqasm/operators/exceptions.py          |  36 ++++
 .../autoqasm/operators/logical.py             | 120 +++++++++++-
 .../experimental/autoqasm/operators/slices.py |   7 +-
 .../experimental/autoqasm/program/program.py  |  62 ++++++
 .../experimental/autoqasm/types/__init__.py   |   2 +-
 .../autoqasm/types/conversions.py             |  40 +---
 .../experimental/autoqasm/types/types.py      |   9 +-
 .../braket/experimental/autoqasm/test_api.py  |  25 +--
 .../experimental/autoqasm/test_converters.py  |   6 +-
 .../experimental/autoqasm/test_operators.py   | 185 ++++++++++++++++--
 .../experimental/autoqasm/test_types.py       | 185 ++++++++----------
 19 files changed, 651 insertions(+), 250 deletions(-)
 create mode 100644 src/braket/experimental/autoqasm/operators/exceptions.py

diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py
index 6e3c4fee..c795186a 100644
--- a/src/braket/experimental/autoqasm/api.py
+++ b/src/braket/experimental/autoqasm/api.py
@@ -323,13 +323,7 @@ def _convert_subroutine(
         ret_type = subroutine_function_call.subroutine_decl.return_type
         return_instance = _make_return_instance_from_oqpy_return_type(ret_type)
         return_variable = None
-        if isinstance(return_instance, list):
-            return_variable = aq_types.ArrayVar(
-                return_instance,
-                dimensions=[d.value for d in ret_type.dimensions],
-            )
-            oqpy_program.set(return_variable, subroutine_function_call)
-        elif return_instance is not None:
+        if return_instance is not None:
             return_variable = aq_types.wrap_value(return_instance)
             oqpy_program.declare(return_variable)
             oqpy_program.set(return_variable, subroutine_function_call)
@@ -398,7 +392,9 @@ def _func(*args, **kwargs) -> Any:
             )
 
         new_param = inspect.Parameter(
-            name=param.name, kind=param.kind, annotation=aq_types.map_type(param.annotation)
+            name=param.name,
+            kind=param.kind,
+            annotation=aq_types.map_parameter_type(param.annotation),
         )
         new_params.append(new_param)
         _func.__annotations__[new_param.name] = new_param.annotation
@@ -447,8 +443,6 @@ def _make_return_instance_from_oqpy_return_type(return_type: Any) -> Any:
         return None
 
     var_type = aq_types.conversions.var_type_from_ast_type(return_type)
-    if var_type == aq_types.ArrayVar:
-        return []
     if var_type == aq_types.BitVar:
         return var_type(size=_get_bitvar_size(return_type))
     return var_type()
diff --git a/src/braket/experimental/autoqasm/errors.py b/src/braket/experimental/autoqasm/errors.py
index 9b7903a7..5a6b1ede 100644
--- a/src/braket/experimental/autoqasm/errors.py
+++ b/src/braket/experimental/autoqasm/errors.py
@@ -98,3 +98,15 @@ def __init__(self, true_type: Optional[type], false_type: Optional[type]):
 
     def __str__(self):
         return self.message
+
+
+class InvalidAssignmentStatement(AutoQasmError):
+    """Invalid assignment statement for an AutoQASM variable."""
+
+
+class InvalidArrayDeclaration(AutoQasmError):
+    """Invalid declaration of an AutoQASM array variable."""
+
+
+class UnsupportedSubroutineReturnType(AutoQasmError):
+    """Unsupported return type for an AutoQASM subroutine."""
diff --git a/src/braket/experimental/autoqasm/instructions/measurements.py b/src/braket/experimental/autoqasm/instructions/measurements.py
index 2b9fb98d..d92357de 100644
--- a/src/braket/experimental/autoqasm/instructions/measurements.py
+++ b/src/braket/experimental/autoqasm/instructions/measurements.py
@@ -23,6 +23,7 @@ def my_program():
 """
 
 
+from collections.abc import Iterable
 from typing import Union
 
 from braket.experimental.autoqasm import program
@@ -30,18 +31,18 @@ def my_program():
 from braket.experimental.autoqasm.instructions.qubits import QubitIdentifierType, _qubit
 
 
-def measure(qubits: Union[QubitIdentifierType, list[QubitIdentifierType]]) -> aq_types.BitVar:
+def measure(qubits: Union[QubitIdentifierType, Iterable[QubitIdentifierType]]) -> aq_types.BitVar:
     """Add qubit measurement statements to the program and assign the measurement
     results to bit variables.
 
     Args:
-        qubits (Union[QubitIdentifierType, list[QubitIdentifierType]]): The target qubits
+        qubits (Union[QubitIdentifierType, Iterable[QubitIdentifierType]]): The target qubits
             to measure.
 
     Returns:
         BitVar: Bit variable the measurement results are assigned to.
     """
-    if not isinstance(qubits, list):
+    if not isinstance(qubits, Iterable):
         qubits = [qubits]
 
     oqpy_program = program.get_program_conversion_context().get_oqpy_program()
diff --git a/src/braket/experimental/autoqasm/operators/__init__.py b/src/braket/experimental/autoqasm/operators/__init__.py
index fa01e89e..853b8ce0 100644
--- a/src/braket/experimental/autoqasm/operators/__init__.py
+++ b/src/braket/experimental/autoqasm/operators/__init__.py
@@ -17,9 +17,6 @@
 This module implements operators that AutoQASM overloads or adds on top of AutoGraph.
 """
 
-# TODO: Commented out operators below are not yet implemented.
-# We need to either implement these, or determine they are not needed and remove them.
-
 # Operators below are imported directly from core autograph implementation
 from braket.experimental.autoqasm.autograph.impl.api_core import autograph_artifact  # noqa: F401
 from braket.experimental.autoqasm.autograph.operators.variables import (  # noqa: F401
@@ -32,24 +29,16 @@
 from .assignments import assign_stmt  # noqa: F401
 from .conditional_expressions import if_exp  # noqa: F401
 from .control_flow import for_stmt, if_stmt, while_stmt  # noqa: F401
-
-# from .data_structures import list_append
-# from .data_structures import list_pop
-# from .data_structures import list_stack
-# from .data_structures import ListPopOpts
-# from .data_structures import ListStackOpts
+from .data_structures import ListPopOpts  # noqa: F401
+from .data_structures import ListStackOpts  # noqa: F401
+from .data_structures import list_append  # noqa: F401
+from .data_structures import list_pop  # noqa: F401
+from .data_structures import list_stack  # noqa: F401
 from .data_structures import new_list  # noqa: F401
-
-# from .exceptions import assert_stmt
-# from .logical import and_
+from .exceptions import assert_stmt  # noqa: F401
+from .logical import and_  # noqa: F401
 from .logical import eq  # noqa: F401
-
-# from .logical import not_
-# from .logical import not_eq
-# from .logical import or_
-# from .py_builtins import float_
-# from .py_builtins import int_
-# from .py_builtins import len_
-# from .py_builtins import print_
-# from .py_builtins import range_
+from .logical import not_  # noqa: F401
+from .logical import not_eq  # noqa: F401
+from .logical import or_  # noqa: F401
 from .slices import GetItemOpts, get_item, set_item  # noqa: F401
diff --git a/src/braket/experimental/autoqasm/operators/assignments.py b/src/braket/experimental/autoqasm/operators/assignments.py
index fd42f0c0..771fd261 100644
--- a/src/braket/experimental/autoqasm/operators/assignments.py
+++ b/src/braket/experimental/autoqasm/operators/assignments.py
@@ -20,7 +20,7 @@
 import oqpy
 import oqpy.base
 
-from braket.experimental.autoqasm import constants, program, types
+from braket.experimental.autoqasm import constants, errors, program, types
 from braket.experimental.autoqasm.autograph.operators.variables import UndefinedReturnValue
 from braket.experimental.autoqasm.types.conversions import var_type_from_oqpy
 
@@ -44,10 +44,11 @@ def assign_stmt(target_name: str, value: Any) -> Any:
     if isinstance(value, UndefinedReturnValue):
         return value
 
-    is_target_name_used = program.get_program_conversion_context().is_var_name_used(target_name)
+    program_conversion_context = program.get_program_conversion_context()
+    is_target_name_used = program_conversion_context.is_var_name_used(target_name)
     is_value_name_used = isinstance(
         value, oqpy.base.Var
-    ) and program.get_program_conversion_context().is_var_name_used(value.name)
+    ) and program_conversion_context.is_var_name_used(value.name)
 
     if target_name == constants.RETVAL_VARIABLE_NAME:
         # AutoGraph transpiles return statements like
@@ -63,6 +64,11 @@ def assign_stmt(target_name: str, value: Any) -> Any:
             # Return it directly without wrapping it or declaring a new variable.
             return value
 
+        if program_conversion_context.subroutines_processing and isinstance(value, list):
+            raise errors.UnsupportedSubroutineReturnType(
+                "Subroutine returns an array or list, which is not allowed."
+            )
+
         value = types.wrap_value(value)
 
     if not isinstance(value, oqpy.base.Var):
@@ -70,17 +76,37 @@ def assign_stmt(target_name: str, value: Any) -> Any:
 
     if is_target_name_used:
         target = _get_oqpy_program_variable(target_name)
-        _validate_variables_type_size(target, value)
+        _validate_assignment_types(target, value)
     else:
         target = copy.copy(value)
         target.init_expression = None
         target.name = target_name
 
-    oqpy_program = program.get_program_conversion_context().get_oqpy_program()
+    oqpy_program = program_conversion_context.get_oqpy_program()
     if is_value_name_used or value.init_expression is None:
+        # Directly assign the value to the target.
+        # For example:
+        #   a = b;
+        # where `b` is previously declared.
         oqpy_program.set(target, value)
+    elif (
+        target.name not in oqpy_program.declared_vars
+        and program_conversion_context.at_function_root_scope
+    ):
+        # Explicitly declare and initialize the variable at the root scope.
+        # For example:
+        #   int[32] a = 10;
+        # where `a` is at the root scope of the function (not inside any if/for/while block).
+        target.init_expression = value.init_expression
+        oqpy_program.declare(target)
     else:
         # Set to `value.init_expression` to avoid declaring an unnecessary variable.
+        # The variable will be set in the current scope and auto-declared at the root scope.
+        # For example, the `a = 1` and `a = 0` statements in the following:
+        #   int[32] a;
+        #   if (b == True) { a = 1; }
+        #   else { a = 0; }
+        # where `b` is previously declared.
         oqpy_program.set(target, value.init_expression)
 
     return target
@@ -100,17 +126,27 @@ def _get_oqpy_program_variable(var_name: str) -> oqpy.base.Var:
     return variables[var_name]
 
 
-def _validate_variables_type_size(var1: oqpy.base.Var, var2: oqpy.base.Var) -> None:
-    """Raise error when the size or type of the two variables do not match.
+def _validate_assignment_types(var1: oqpy.base.Var, var2: oqpy.base.Var) -> None:
+    """Validates that the size and type of the variables are compatible for assignment.
 
     Args:
         var1 (oqpy.base.Var): Variable to validate.
         var2 (oqpy.base.Var): Variable to validate.
     """
-    var1_size = var1.size or 1
-    var2_size = var2.size or 1
-
     if var_type_from_oqpy(var1) != var_type_from_oqpy(var2):
-        raise ValueError("Variables in assignment statements must have the same type")
-    if var1_size != var2_size:
-        raise ValueError("Variables in assignment statements must have the same size")
+        raise errors.InvalidAssignmentStatement(
+            "Variables in assignment statements must have the same type"
+        )
+
+    if isinstance(var1, oqpy.ArrayVar):
+        if var1.dimensions != var2.dimensions:
+            raise errors.InvalidAssignmentStatement(
+                "Arrays in assignment statements must have the same dimensions"
+            )
+    else:
+        var1_size = var1.size or 1
+        var2_size = var2.size or 1
+        if var1_size != var2_size:
+            raise errors.InvalidAssignmentStatement(
+                "Variables in assignment statements must have the same size"
+            )
diff --git a/src/braket/experimental/autoqasm/operators/conditional_expressions.py b/src/braket/experimental/autoqasm/operators/conditional_expressions.py
index 94a9e0f2..27bfb7d2 100644
--- a/src/braket/experimental/autoqasm/operators/conditional_expressions.py
+++ b/src/braket/experimental/autoqasm/operators/conditional_expressions.py
@@ -52,20 +52,20 @@ def _oqpy_if_exp(
 ) -> Optional[oqpy.base.Var]:
     """Overload of if_exp that stages an oqpy conditional."""
     result_var = None
-    oqpy_program = aq_program.get_program_conversion_context().get_oqpy_program()
-    with oqpy.If(oqpy_program, cond):
+    program_conversion_context = aq_program.get_program_conversion_context()
+    with program_conversion_context.if_block(cond):
         true_result = aq_types.wrap_value(if_true())
         true_result_type = aq_types.var_type_from_oqpy(true_result)
         if true_result is not None:
             result_var = true_result_type()
-            oqpy_program.set(result_var, true_result)
-    with oqpy.Else(oqpy_program):
+            program_conversion_context.get_oqpy_program().set(result_var, true_result)
+    with program_conversion_context.else_block():
         false_result = aq_types.wrap_value(if_false())
         false_result_type = aq_types.var_type_from_oqpy(false_result)
         if false_result_type != true_result_type:
             raise UnsupportedConditionalExpressionError(true_result_type, false_result_type)
         if false_result is not None:
-            oqpy_program.set(result_var, false_result)
+            program_conversion_context.get_oqpy_program().set(result_var, false_result)
 
     return result_var
 
diff --git a/src/braket/experimental/autoqasm/operators/control_flow.py b/src/braket/experimental/autoqasm/operators/control_flow.py
index 33fb1096..8f454fd0 100644
--- a/src/braket/experimental/autoqasm/operators/control_flow.py
+++ b/src/braket/experimental/autoqasm/operators/control_flow.py
@@ -44,37 +44,33 @@ def for_stmt(
         opts (dict): Options of the for loop.
     """
     del get_state, set_state, symbol_names
+    if extra_test is not None:
+        raise NotImplementedError("break and return statements are not supported in for loops.")
+
     if is_qasm_type(iter):
-        _oqpy_for_stmt(iter, extra_test, body, opts)
+        _oqpy_for_stmt(iter, body, opts)
     else:
-        _py_for_stmt(iter, extra_test, body)
+        _py_for_stmt(iter, body)
 
 
 def _oqpy_for_stmt(
     iter: oqpy.Range,
-    extra_test: Callable[[], Any],
     body: Callable[[Any], None],
     opts: dict,
 ) -> None:
     """Overload of for_stmt that produces an oqpy for loop."""
-    oqpy_program = program.get_program_conversion_context().get_oqpy_program()
-    # TODO: Should check extra_test() on each iteration and break if False,
-    # but oqpy doesn't currently support break statements at the moment.
-    with oqpy.ForIn(oqpy_program, iter, opts["iterate_names"]) as f:
+    program_conversion_context = program.get_program_conversion_context()
+    with program_conversion_context.for_in(iter, opts["iterate_names"]) as f:
         body(f)
 
 
 def _py_for_stmt(
     iter: Iterable,
-    extra_test: Callable[[], Any],
     body: Callable[[Any], None],
 ) -> None:
     """Overload of for_stmt that executes a Python for loop."""
-    if extra_test is not None:
-        raise NotImplementedError("break and return statements are not supported in for loops.")
-    else:
-        for target in iter:
-            body(target)
+    for target in iter:
+        body(target)
 
 
 def while_stmt(
@@ -107,8 +103,8 @@ def _oqpy_while_stmt(
     body: Callable[[], None],
 ) -> None:
     """Overload of while_stmt that produces an oqpy while loop."""
-    oqpy_program = program.get_program_conversion_context().get_oqpy_program()
-    with oqpy.While(oqpy_program, test()):
+    program_conversion_context = program.get_program_conversion_context()
+    with program_conversion_context.while_loop(test()):
         body()
 
 
@@ -154,10 +150,10 @@ def _oqpy_if_stmt(
     orelse: Callable[[], Any],
 ) -> None:
     """Overload of if_stmt that stages an oqpy cond."""
-    oqpy_program = program.get_program_conversion_context().get_oqpy_program()
-    with oqpy.If(oqpy_program, cond):
+    program_conversion_context = program.get_program_conversion_context()
+    with program_conversion_context.if_block(cond):
         body()
-    with oqpy.Else(oqpy_program):
+    with program_conversion_context.else_block():
         orelse()
 
 
diff --git a/src/braket/experimental/autoqasm/operators/data_structures.py b/src/braket/experimental/autoqasm/operators/data_structures.py
index ce42aea8..c27aeeea 100644
--- a/src/braket/experimental/autoqasm/operators/data_structures.py
+++ b/src/braket/experimental/autoqasm/operators/data_structures.py
@@ -14,11 +14,63 @@
 
 """Operators for other data structures (e.g. list)."""
 
-from typing import Iterable, Optional
+import collections
+from collections.abc import Iterable
+from typing import Any, Optional
+
+
+class ListPopOpts(collections.namedtuple("ListPopOpts", ("element_dtype", "element_shape"))):
+    pass
+
+
+class ListStackOpts(collections.namedtuple("ListStackOpts", ("element_dtype", "original_call"))):
+    pass
+
+
+def list_append(target: list, element: Any) -> list:
+    """The list append function.
+
+    Args:
+        target (list): An entity that supports append semantics.
+        element (Any): The element to append.
+
+    Returns:
+        list: The resulting list after performing the append.
+    """
+    target.append(element)
+    return target
+
+
+def list_pop(target: list, element: Optional[Any], opts: ListPopOpts) -> tuple:
+    """The list pop function.
+
+    Args:
+        target (list): An entity that supports append semantics.
+        element (Optional[Any]): The element index to pop. If None, pops the last element.
+        opts (ListPopOpts): Metadata about the converted pop operation.
+
+    Returns:
+        tuple: The resulting list after performing the pop, and the popped item.
+    """
+    popped = target.pop(element) if element is not None else target.pop()
+    return target, popped
+
+
+def list_stack(target: list, opts: ListStackOpts) -> list:
+    """The list stack function.
+
+    Args:
+        target (list): An entity that supports stack semantics.
+        opts (ListStackOpts): Metadata about the converted stack operation.
+
+    Returns:
+        list: The stacked list.
+    """
+    return opts.original_call(target)
 
 
 def new_list(iterable: Optional[Iterable] = None) -> list:
-    """The list constructor
+    """The list constructor.
 
     Args:
         iterable (Optional[Iterable]): Optional elements to fill the list with. Defaults to None.
diff --git a/src/braket/experimental/autoqasm/operators/exceptions.py b/src/braket/experimental/autoqasm/operators/exceptions.py
new file mode 100644
index 00000000..bfe7cefa
--- /dev/null
+++ b/src/braket/experimental/autoqasm/operators/exceptions.py
@@ -0,0 +1,36 @@
+# 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.
+
+
+"""Operators for exception handling."""
+
+from collections.abc import Callable
+
+from braket.experimental.autoqasm.types import is_qasm_type
+
+
+def assert_stmt(test: bool, message: Callable) -> None:
+    """An assertion statement.
+
+    Args:
+        test (bool): The condition on which to assert.
+        message (Callable): A function which returns the message to be used
+            if the assertion fails.
+    """
+    if is_qasm_type(test):
+        raise NotImplementedError(
+            "Assertions are not supported for values that depend on "
+            "measurement results or AutoQASM variables."
+        )
+
+    assert test, message()
diff --git a/src/braket/experimental/autoqasm/operators/logical.py b/src/braket/experimental/autoqasm/operators/logical.py
index df40a9ae..6813e090 100644
--- a/src/braket/experimental/autoqasm/operators/logical.py
+++ b/src/braket/experimental/autoqasm/operators/logical.py
@@ -14,12 +14,102 @@
 
 """Operators for logical boolean operators (e.g. not, and, or)."""
 
-from typing import Any, Union
+from typing import Any, Callable, Union
+
+import oqpy.base
+from openpulse import ast
 
 from braket.experimental.autoqasm import program
 from braket.experimental.autoqasm import types as aq_types
 
 
+def and_(a: Callable[[], Any], b: Callable[[], Any]) -> Union[bool, aq_types.BoolVar]:
+    """Functional form of "and".
+
+    Args:
+        a (Callable[[], Any]): Callable that returns the first expression.
+        b (Callable[[], Any]): Callable that returns the second expression.
+
+    Returns:
+        Union[bool, BoolVar]: Whether both expressions are true.
+    """
+    a = a()
+    b = b()
+    if aq_types.is_qasm_type(a) or aq_types.is_qasm_type(b):
+        return _oqpy_and(a, b)
+    else:
+        return _py_and(a, b)
+
+
+def _oqpy_and(a: Any, b: Any) -> aq_types.BoolVar:
+    oqpy_program = program.get_program_conversion_context().get_oqpy_program()
+    result = aq_types.BoolVar()
+    oqpy_program.declare(result)
+    oqpy_program.set(result, oqpy.base.logical_and(a, b))
+    return result
+
+
+def _py_and(a: Any, b: Any) -> bool:
+    return a and b
+
+
+def or_(a: Callable[[], Any], b: Callable[[], Any]) -> Union[bool, aq_types.BoolVar]:
+    """Functional form of "or".
+
+    Args:
+        a (Callable[[], Any]): Callable that returns the first expression.
+        b (Callable[[], Any]): Callable that returns the second expression.
+
+    Returns:
+        Union[bool, BoolVar]: Whether either expression is true.
+    """
+    a = a()
+    b = b()
+    if aq_types.is_qasm_type(a) or aq_types.is_qasm_type(b):
+        return _oqpy_or(a, b)
+    else:
+        return _py_or(a, b)
+
+
+def _oqpy_or(a: Any, b: Any) -> aq_types.BoolVar:
+    oqpy_program = program.get_program_conversion_context().get_oqpy_program()
+    result = aq_types.BoolVar()
+    oqpy_program.declare(result)
+    oqpy_program.set(result, oqpy.base.logical_or(a, b))
+    return result
+
+
+def _py_or(a: Any, b: Any) -> bool:
+    return a or b
+
+
+def not_(a: Any) -> Union[bool, aq_types.BoolVar]:
+    """Functional form of "not".
+
+    Args:
+        a (Any): Expression to negate.
+
+    Returns:
+        Union[bool, BoolVar]: Whether the expression is false.
+    """
+    if aq_types.is_qasm_type(a):
+        return _oqpy_not(a)
+    else:
+        return _py_not(a)
+
+
+def _oqpy_not(a: Any) -> aq_types.BoolVar:
+    oqpy_program = program.get_program_conversion_context().get_oqpy_program()
+    result = aq_types.BoolVar()
+    oqpy_program.declare(result)
+    oqpy_program.set(result, oqpy.base.OQPyUnaryExpression(ast.UnaryOperator["!"], a))
+    return result
+
+
+def _py_not(a: Any) -> bool:
+    return not a
+
+
 def eq(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]:
     """Functional form of "equal".
 
@@ -46,3 +136,31 @@ def _oqpy_eq(a: Any, b: Any) -> aq_types.BoolVar:
 
 def _py_eq(a: Any, b: Any) -> bool:
     return a == b
+
+
+def not_eq(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]:
+    """Functional form of "not equal".
+
+    Args:
+        a (Any): First expression to compare.
+        b (Any): Second expression to compare.
+
+    Returns:
+        Union[bool, BoolVar]: Whether the expressions are not equal.
+    """
+    if aq_types.is_qasm_type(a) or aq_types.is_qasm_type(b):
+        return _oqpy_not_eq(a, b)
+    else:
+        return _py_not_eq(a, b)
+
+
+def _oqpy_not_eq(a: Any, b: Any) -> aq_types.BoolVar:
+    oqpy_program = program.get_program_conversion_context().get_oqpy_program()
+    is_not_equal = aq_types.BoolVar()
+    oqpy_program.declare(is_not_equal)
+    oqpy_program.set(is_not_equal, a != b)
+    return is_not_equal
+
+
+def _py_not_eq(a: Any, b: Any) -> bool:
+    return a != b
diff --git a/src/braket/experimental/autoqasm/operators/slices.py b/src/braket/experimental/autoqasm/operators/slices.py
index 0ed2ea47..726abea8 100644
--- a/src/braket/experimental/autoqasm/operators/slices.py
+++ b/src/braket/experimental/autoqasm/operators/slices.py
@@ -20,7 +20,7 @@
 import oqpy.base
 
 from braket.experimental.autoqasm import program
-from braket.experimental.autoqasm.types import is_qasm_type
+from braket.experimental.autoqasm.types import is_qasm_type, wrap_value
 
 
 class GetItemOpts(collections.namedtuple("GetItemOpts", ("element_dtype",))):
@@ -86,9 +86,10 @@ def set_item(target: Any, i: Any, x: Any) -> Any:
 
 def _oqpy_set_item(target: Any, i: Any, x: Any) -> Any:
     """Overload of set_item that produces an oqpy list modification."""
-    if not isinstance(target, oqpy.BitVar):
-        raise NotImplementedError("Slice assignment is not supported.")
+    if not isinstance(target, (oqpy.BitVar, oqpy.ArrayVar)):
+        raise NotImplementedError("Only BitVar and ArrayVar types support slice assignment.")
 
+    x = wrap_value(x)
     is_var_name_used = program.get_program_conversion_context().is_var_name_used(x.name)
     oqpy_program = program.get_program_conversion_context().get_oqpy_program()
     if is_var_name_used or x.init_expression is None:
diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py
index d2538061..e5c29cbc 100644
--- a/src/braket/experimental/autoqasm/program/program.py
+++ b/src/braket/experimental/autoqasm/program/program.py
@@ -205,6 +205,7 @@ def __init__(self, user_config: Optional[UserConfig] = None):
         self.user_config = user_config or UserConfig()
         self.return_variable = None
         self.in_verbatim_block = False
+        self.at_function_root_scope = True  # whether we are at the root scope of main or subroutine
         self._oqpy_program_stack = [oqpy.Program(simplify_constants=False)]
         self._gate_definitions_processing = []
         self._calibration_definitions_processing = []
@@ -483,6 +484,67 @@ def push_oqpy_program(self, oqpy_program: oqpy.Program) -> None:
         finally:
             self._oqpy_program_stack.pop()
 
+    @contextlib.contextmanager
+    def _control_flow_block(
+        self, _context_manager: contextlib._GeneratorContextManager
+    ) -> contextlib._GeneratorContextManager:
+        original = self.at_function_root_scope
+        try:
+            self.at_function_root_scope = False
+            with _context_manager as _cm:
+                yield _cm
+        finally:
+            self.at_function_root_scope = original
+
+    def if_block(self, condition: Any) -> contextlib._GeneratorContextManager:
+        """Sets the program conversion context into an if block context.
+
+        Args:
+            condition (Any): The condition of the if block.
+
+        Yields:
+            _GeneratorContextManager: The context manager of the oqpy.If block.
+        """
+        oqpy_program = self.get_oqpy_program()
+        return self._control_flow_block(oqpy.If(oqpy_program, condition))
+
+    def else_block(self) -> contextlib._GeneratorContextManager:
+        """Sets the program conversion context into an else block context.
+        Must be immediately preceded by an if block.
+
+        Yields:
+            _GeneratorContextManager: The context manager of the oqpy.Else block.
+        """
+        oqpy_program = self.get_oqpy_program()
+        return self._control_flow_block(oqpy.Else(oqpy_program))
+
+    def for_in(
+        self, iterator: oqpy.Range, iterator_name: Optional[str]
+    ) -> contextlib._GeneratorContextManager:
+        """Sets the program conversion context into a for loop context.
+
+        Args:
+            iterator (oqpy.Range): The iterator of the for loop.
+            iterator_name (Optional[str]): The symbol to use as the name of the iterator.
+
+        Yields:
+            _GeneratorContextManager: The context manager of the oqpy.ForIn block.
+        """
+        oqpy_program = self.get_oqpy_program()
+        return self._control_flow_block(oqpy.ForIn(oqpy_program, iterator, iterator_name))
+
+    def while_loop(self, condition: Any) -> contextlib._GeneratorContextManager:
+        """Sets the program conversion context into a while loop context.
+
+        Args:
+            condition (Any): The condition of the while loop.
+
+        Yields:
+            _GeneratorContextManager: The context manager of the oqpy.While block.
+        """
+        oqpy_program = self.get_oqpy_program()
+        return self._control_flow_block(oqpy.While(oqpy_program, condition))
+
     @contextlib.contextmanager
     def gate_definition(self, gate_name: str, gate_args: GateArgs) -> None:
         """Sets the program conversion context into a gate definition context.
diff --git a/src/braket/experimental/autoqasm/types/__init__.py b/src/braket/experimental/autoqasm/types/__init__.py
index 5e032ca4..999973e9 100644
--- a/src/braket/experimental/autoqasm/types/__init__.py
+++ b/src/braket/experimental/autoqasm/types/__init__.py
@@ -15,7 +15,7 @@
 for type handling.
 """
 
-from .conversions import map_type, var_type_from_oqpy, wrap_value  # noqa: F401
+from .conversions import map_parameter_type, var_type_from_oqpy, wrap_value  # noqa: F401
 from .types import (  # noqa: F401
     ArrayVar,
     BitVar,
diff --git a/src/braket/experimental/autoqasm/types/conversions.py b/src/braket/experimental/autoqasm/types/conversions.py
index 8761b024..55b33f5f 100644
--- a/src/braket/experimental/autoqasm/types/conversions.py
+++ b/src/braket/experimental/autoqasm/types/conversions.py
@@ -25,8 +25,9 @@
 from braket.experimental.autoqasm import types as aq_types
 
 
-def map_type(python_type: type) -> type:
-    """Maps a given Python type to the corresponding oqpy type.
+def map_parameter_type(python_type: type) -> type:
+    """Maps a given Python function parameter type to the corresponding oqpy
+    subroutine parameter type.
 
     Args:
         python_type (type): The Python type to be mapped.
@@ -35,7 +36,6 @@ def map_type(python_type: type) -> type:
         type: The corresponding oqpy type.
     """
     origin_type = typing.get_origin(python_type) or python_type
-    type_args = typing.get_args(python_type)
 
     if issubclass(origin_type, bool):
         return oqpy.BoolVar
@@ -43,32 +43,12 @@ def map_type(python_type: type) -> type:
         return oqpy.IntVar
     if issubclass(origin_type, (float, np.floating)):
         return oqpy.FloatVar
-    if issubclass(origin_type, list):
-        if not type_args:
-            raise errors.ParameterTypeError("Please supply a type argument to list.")
-
-        item_type = map_type(type_args[0])
-        if not item_type == oqpy.IntVar:
-            raise errors.ParameterTypeError(
-                f"Unsupported array type: {item_type}. AutoQASM arrays only support ints."
-            )
-
-        # TODO: Update array length to match the input rather than hardcoding
-        # OQPY and QASM require arrays have a set length. python doesn't require this,
-        # so the length of the array is indeterminate.
-        # At this point we only have access to the _parameter_ (type hint), not the
-        # _argument_ (concrete value), which is the only place length information is stored
-        # Here's where the info is stored for oqpy variables:
-        # ctx = program.get_program_conversion_context()
-        # dims = ctx.get_oqpy_program().declared_vars[name_of_var].dimensions
-        return oqpy.ArrayVar[oqpy.IntVar, 10]
-    if issubclass(origin_type, tuple):
-        raise TypeError(
-            "Tuples are not supported as parameters to AutoQASM functions; "
-            "please separate the tuple into multiple parameters or use a list instead."
+    if issubclass(origin_type, tuple) or issubclass(origin_type, list):
+        raise errors.ParameterTypeError(
+            "Lists and tuples are not supported as parameters to AutoQASM functions; "
+            "consider passing the values as multiple separate parameters."
         )
 
-    # TODO add all supported types
     return python_type
 
 
@@ -147,12 +127,6 @@ def _(node: Union[float, np.floating]):
     return aq_types.FloatVar(node)
 
 
-@wrap_value.register(list)
-def _(node: list):
-    # TODO: Update array length to match the input rather than hardcoding
-    return aq_types.ArrayVar(node, dimensions=[10])
-
-
 @wrap_value.register
 def _(node: oqpy.base.Var):
     return node
diff --git a/src/braket/experimental/autoqasm/types/types.py b/src/braket/experimental/autoqasm/types/types.py
index 2bf8627d..fcfaea88 100644
--- a/src/braket/experimental/autoqasm/types/types.py
+++ b/src/braket/experimental/autoqasm/types/types.py
@@ -19,7 +19,7 @@
 import oqpy.base
 from openpulse import ast
 
-from braket.experimental.autoqasm import program
+from braket.experimental.autoqasm import errors, program
 
 
 def is_qasm_type(val: Any) -> bool:
@@ -58,6 +58,13 @@ def qasm_range(start: int, stop: Optional[int] = None, step: Optional[int] = 1)
 
 class ArrayVar(oqpy.ArrayVar):
     def __init__(self, *args, **kwargs):
+        if (
+            program.get_program_conversion_context().subroutines_processing
+            or not program.get_program_conversion_context().at_function_root_scope
+        ):
+            raise errors.InvalidArrayDeclaration(
+                "Arrays may only be declared at the root scope of an AutoQASM main function."
+            )
         super(ArrayVar, self).__init__(*args, **kwargs)
         self.name = program.get_program_conversion_context().next_var_name(oqpy.ArrayVar)
 
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py
index 5d0973d4..057cc0ce 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_api.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py
@@ -229,9 +229,8 @@ def bell_measurement_declared() -> None:
 
 def test_bell_measurement_declared() -> None:
     expected = """OPENQASM 3.0;
-bit[2] c;
 qubit[2] __qubits__;
-c = "00";
+bit[2] c = "00";
 h __qubits__[0];
 cnot __qubits__[0], __qubits__[1];
 bit[2] __bit_1__ = "00";
@@ -279,7 +278,7 @@ def bell_measurement_invalid_declared_type() -> None:
 def test_bell_measurement_invalid_declared_type() -> None:
     """Test measurement with reuslt stored in an variable with invalid type."""
     expected_error_message = "Variables in assignment statements must have the same type"
-    with pytest.raises(ValueError) as exc_info:
+    with pytest.raises(errors.InvalidAssignmentStatement) as exc_info:
         bell_measurement_invalid_declared_type()
     assert expected_error_message in str(exc_info.value)
 
@@ -298,7 +297,7 @@ def bell_measurement_invalid_declared_size() -> None:
 def test_bell_measurement_invalid_declared_size() -> None:
     """Test measurement with reuslt stored in an variable with invalid size."""
     expected_error_message = "Variables in assignment statements must have the same size"
-    with pytest.raises(ValueError) as exc_info:
+    with pytest.raises(errors.InvalidAssignmentStatement) as exc_info:
         bell_measurement_invalid_declared_size()
     assert expected_error_message in str(exc_info.value)
 
@@ -755,20 +754,15 @@ def classical_variables_types() -> None:
 
 def test_classical_variables_types():
     expected = """OPENQASM 3.0;
-bit a;
-int[32] i;
-bit[2] a_array;
-int[32] b;
-float[64] c;
-a = 0;
+bit a = 0;
 a = 1;
-i = 1;
-a_array = "00";
+int[32] i = 1;
+bit[2] a_array = "00";
 a_array[0] = 0;
 a_array[i] = 1;
-b = 10;
+int[32] b = 10;
 b = 15;
-c = 1.2;
+float[64] c = 1.2;
 c = 3.4;"""
     assert classical_variables_types().to_ir() == expected
 
@@ -786,9 +780,8 @@ def prog() -> None:
         a = b  # declared target, declared value # noqa: F841
 
     expected = """OPENQASM 3.0;
-int[32] a;
 int[32] b;
-a = 1;
+int[32] a = 1;
 a = 2;
 b = a;
 a = b;"""
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_converters.py b/test/unit_tests/braket/experimental/autoqasm/test_converters.py
index 6e169e53..c22af68b 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_converters.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_converters.py
@@ -51,11 +51,9 @@ def fn() -> None:
 
     qasm = program_conversion_context.make_program().to_ir()
     expected_qasm = """OPENQASM 3.0;
-int[32] a;
-float[64] b;
 int[32] e;
-a = 5;
-b = 1.2;
+int[32] a = 5;
+float[64] b = 1.2;
 a = 1;
 e = a;"""
     assert qasm == expected_qasm
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py
index 7d144743..dba42099 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_operators.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py
@@ -16,10 +16,12 @@
 from collections.abc import Callable
 from typing import Any
 
+import numpy as np
 import oqpy.base
 import pytest
 
 import braket.experimental.autoqasm as aq
+from braket.experimental.autoqasm import errors
 from braket.experimental.autoqasm.errors import UnsupportedConditionalExpressionError
 from braket.experimental.autoqasm.instructions import cnot, h, measure, x
 
@@ -113,7 +115,6 @@ def cond_exp_assignment():
         lambda: aq.FloatVar(2),
         lambda: aq.BoolVar(False),
         lambda: aq.BitVar(0),
-        lambda: aq.ArrayVar(dimensions=[3]),
     ],
 )
 def test_unsupported_inline_conditional_assignment(else_value) -> None:
@@ -161,9 +162,8 @@ def branch_assignment_declared():
             a = aq.IntVar(7)  # noqa: F841
 
     expected = """OPENQASM 3.0;
-int[32] a;
 bool __bool_1__ = true;
-a = 5;
+int[32] a = 5;
 if (__bool_1__) {
     a = 6;
 } else {
@@ -363,6 +363,130 @@ def test_logical_eq_qasm_cond() -> None:
     assert "==" in qasm
 
 
+def test_logical_op_and() -> None:
+    @aq.subroutine
+    def do_and(a: bool, b: bool):
+        return a and b
+
+    @aq.main
+    def prog():
+        do_and(True, False)
+
+    expected = """OPENQASM 3.0;
+def do_and(bool a, bool b) -> bool {
+    bool __bool_0__;
+    __bool_0__ = a && b;
+    return __bool_0__;
+}
+bool __bool_1__;
+__bool_1__ = do_and(true, false);"""
+
+    assert prog().to_ir() == expected
+
+
+def test_logical_op_or() -> None:
+    @aq.subroutine
+    def do_or(a: bool, b: bool):
+        return a or b
+
+    @aq.main
+    def prog():
+        do_or(True, False)
+
+    expected = """OPENQASM 3.0;
+def do_or(bool a, bool b) -> bool {
+    bool __bool_0__;
+    __bool_0__ = a || b;
+    return __bool_0__;
+}
+bool __bool_1__;
+__bool_1__ = do_or(true, false);"""
+
+    assert prog().to_ir() == expected
+
+
+def test_logical_op_not() -> None:
+    @aq.subroutine
+    def do_not(a: bool):
+        return not a
+
+    @aq.main
+    def prog():
+        do_not(True)
+
+    expected = """OPENQASM 3.0;
+def do_not(bool a) -> bool {
+    bool __bool_0__;
+    __bool_0__ = !a;
+    return __bool_0__;
+}
+bool __bool_1__;
+__bool_1__ = do_not(true);"""
+
+    assert prog().to_ir() == expected
+
+
+def test_logical_op_eq() -> None:
+    @aq.subroutine
+    def do_eq(a: int, b: int):
+        return a == b
+
+    @aq.main
+    def prog():
+        do_eq(1, 2)
+
+    expected = """OPENQASM 3.0;
+def do_eq(int[32] a, int[32] b) -> bool {
+    bool __bool_0__;
+    __bool_0__ = a == b;
+    return __bool_0__;
+}
+bool __bool_1__;
+__bool_1__ = do_eq(1, 2);"""
+
+    assert prog().to_ir() == expected
+
+
+def test_logical_op_not_eq() -> None:
+    @aq.subroutine
+    def do_not_eq(a: int, b: int):
+        return a != b
+
+    @aq.main
+    def prog():
+        do_not_eq(1, 2)
+
+    expected = """OPENQASM 3.0;
+def do_not_eq(int[32] a, int[32] b) -> bool {
+    bool __bool_0__;
+    __bool_0__ = a != b;
+    return __bool_0__;
+}
+bool __bool_1__;
+__bool_1__ = do_not_eq(1, 2);"""
+
+    assert prog().to_ir() == expected
+
+
+def test_logical_ops_py() -> None:
+    """Tests the logical aq.operators for Python expressions."""
+
+    @aq.main
+    def prog():
+        a = True
+        b = False
+        c = a and b
+        d = a or c
+        e = not c
+        f = a == e
+        g = d != f
+        assert all([a, not b, not c, d, e, f, not g])
+
+    expected = """OPENQASM 3.0;"""
+
+    assert prog().to_ir() == expected
+
+
 @pytest.mark.parametrize(
     "target", [oqpy.ArrayVar(dimensions=[3], name="arr"), oqpy.BitVar(size=3, name="arr")]
 )
@@ -449,10 +573,8 @@ def slice():
         a[3] = b
 
     expected = """OPENQASM 3.0;
-bit[6] a;
-bit b;
-a = "000000";
-b = 1;
+bit[6] a = "000000";
+bit b = 1;
 a[3] = b;"""
 
     assert slice().to_ir() == expected
@@ -468,10 +590,9 @@ def measure_to_slice():
         b0[3] = c
 
     expected = """OPENQASM 3.0;
-bit[10] b0;
 bit c;
 qubit[1] __qubits__;
-b0 = "0000000000";
+bit[10] b0 = "0000000000";
 bit __bit_1__;
 __bit_1__ = measure __qubits__[0];
 c = __bit_1__;
@@ -483,9 +604,9 @@ def measure_to_slice():
 @pytest.mark.parametrize(
     "target_name,value,expected_qasm",
     [
-        ("foo", oqpy.IntVar(5), "\nint[32] foo;\nfoo = 5;"),
-        ("bar", oqpy.FloatVar(1.2), "\nfloat[64] bar;\nbar = 1.2;"),
-        ("baz", oqpy.BitVar(0), "\nbit baz;\nbaz = 0;"),
+        ("foo", oqpy.IntVar(5), "\nint[32] foo = 5;"),
+        ("bar", oqpy.FloatVar(1.2), "\nfloat[64] bar = 1.2;"),
+        ("baz", oqpy.BitVar(0), "\nbit baz = 0;"),
     ],
 )
 def test_assignment_qasm_undeclared_target(
@@ -567,7 +688,7 @@ def test_assignment_qasm_invalid_size_type(
         oqpy_program = program_conversion_context.get_oqpy_program()
         oqpy_program.declare(declared_var)
 
-        with pytest.raises(ValueError) as exc_info:
+        with pytest.raises(errors.InvalidAssignmentStatement) as exc_info:
             _ = aq.operators.assign_stmt(
                 target_name=target_name,
                 value=value,
@@ -640,3 +761,41 @@ def test_control_flow():
 h __qubits__[0];"""
 
     assert test_control_flow().to_ir() == expected
+
+
+def test_py_assert() -> None:
+    """Test Python assertions inside an AutoQASM program."""
+
+    @aq.main
+    def test_assert(value: bool):
+        assert value
+
+    test_assert(True)
+    with pytest.raises(AssertionError):
+        test_assert(False)
+
+
+def test_measurement_assert() -> None:
+    """Test assertions on measurement results inside an AutoQASM program."""
+
+    @aq.main
+    def test_assert():
+        assert measure(0)
+
+    with pytest.raises(NotImplementedError):
+        test_assert()
+
+
+def test_py_list_ops() -> None:
+    """Test Python list operations inside an AutoQASM program."""
+
+    @aq.main
+    def test_list_ops():
+        a = [1, 2, 3]
+        a.append(4)
+        b = a.pop(0)
+        assert b == 1
+        c = np.stack([a, a])
+        assert np.array_equal(c, [[2, 3, 4], [2, 3, 4]])
+
+    test_list_ops()
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py
index b214720f..cc6f1f92 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_types.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py
@@ -56,8 +56,7 @@ def main() -> aq.BitVar:
 
     expected = """OPENQASM 3.0;
 def ret_test() -> bit {
-    bit res;
-    res = 1;
+    bit res = 1;
     return res;
 }
 bit __bit_1__;
@@ -80,8 +79,7 @@ def main() -> int:
 
     expected = """OPENQASM 3.0;
 def ret_test() -> int[32] {
-    int[32] res;
-    res = 1;
+    int[32] res = 1;
     return res;
 }
 int[32] __int_1__;
@@ -104,8 +102,7 @@ def main() -> float:
 
     expected = """OPENQASM 3.0;
 def ret_test() -> float[64] {
-    float[64] res;
-    res = 1.0;
+    float[64] res = 1.0;
     return res;
 }
 float[64] __float_1__;
@@ -128,8 +125,7 @@ def main() -> bool:
 
     expected = """OPENQASM 3.0;
 def ret_test() -> bool {
-    bool res;
-    res = true;
+    bool res = true;
     return res;
 }
 bool __bool_1__;
@@ -155,10 +151,8 @@ def ret_test() -> int:
 def add(int[32] a, int[32] b) -> int[32] {
     return a + b;
 }
-int[32] a;
-int[32] b;
-a = 5;
-b = 6;
+int[32] a = 5;
+int[32] b = 6;
 int[32] __int_2__;
 __int_2__ = add(a, b);"""
 
@@ -177,28 +171,65 @@ def ret_test() -> None:
     assert ret_test().to_ir() == expected
 
 
-def test_return_array_int():
-    """Test return type discovery of array values."""
+def test_declare_array():
+    """Test declaring and assigning an array."""
+
+    @aq.main
+    def declare_array():
+        a = aq.ArrayVar([1, 2, 3], base_type=aq.IntVar, dimensions=[3])
+        a[0] = 11
+        b = aq.ArrayVar([4, 5, 6], base_type=aq.IntVar, dimensions=[3])
+        b[2] = 14
+        b = a
+
+    expected = """OPENQASM 3.0;
+array[int[32], 3] a = {1, 2, 3};
+a[0] = 11;
+array[int[32], 3] b = {4, 5, 6};
+b[2] = 14;
+b = a;"""
+
+    assert declare_array().to_ir() == expected
+
+
+def test_invalid_array_assignment():
+    """Test invalid array assignment."""
+
+    @aq.main
+    def invalid():
+        a = aq.ArrayVar([1, 2, 3], base_type=aq.IntVar, dimensions=[3])
+        b = aq.ArrayVar([4, 5], base_type=aq.IntVar, dimensions=[2])
+        a = b  # noqa: F841
+
+    with pytest.raises(aq.errors.InvalidAssignmentStatement):
+        invalid()
+
+
+def test_declare_array_in_local_scope():
+    """Test declaring an array inside a local scope."""
+
+    @aq.main
+    def declare_array():
+        if aq.BoolVar(True):
+            _ = aq.ArrayVar([1, 2, 3], base_type=aq.IntVar, dimensions=[3])
+
+    with pytest.raises(aq.errors.InvalidArrayDeclaration):
+        declare_array()
+
+
+def test_declare_array_in_subroutine():
+    """Test declaring an array inside a subroutine."""
 
     @aq.subroutine
-    def ret_test() -> list[int]:
-        res = aq.ArrayVar([1, 2, 3], dimensions=[3])
-        return res
+    def declare_array():
+        _ = aq.ArrayVar([1, 2, 3], dimensions=[3])
 
     @aq.main
     def main() -> list[int]:
-        return ret_test()
-
-    expected = """OPENQASM 3.0;
-def ret_test() -> array[int[32], 3] {
-    array[int[32], 3] res;
-    res = {1, 2, 3};
-    return res;
-}
-array[int[32], 3] __arr_1__ = {};
-__arr_1__ = ret_test();"""
+        return declare_array()
 
-    assert main().to_ir() == expected
+    with pytest.raises(aq.errors.InvalidArrayDeclaration):
+        main()
 
 
 def test_return_python_array():
@@ -212,16 +243,8 @@ def tester() -> list[int]:
     def main():
         tester()
 
-    expected = """OPENQASM 3.0;
-def tester() -> array[int[32], 10] {
-    array[int[32], 10] retval_;
-    retval_ = {1, 2, 3};
-    return retval_;
-}
-array[int[32], 10] __arr_1__ = {};
-qubit[4] __qubits__;
-__arr_1__ = tester();"""
-    assert main().to_ir() == expected
+    with pytest.raises(aq.errors.UnsupportedSubroutineReturnType):
+        main()
 
 
 def test_return_array_unsupported():
@@ -253,8 +276,7 @@ def ret_test() -> int:
 
     expected = """OPENQASM 3.0;
 def helper() -> int[32] {
-    int[32] res;
-    res = 1;
+    int[32] res = 1;
     return res;
 }
 int[32] __int_1__;
@@ -332,14 +354,8 @@ def main():
         a = aq.ArrayVar([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dimensions=[10])
         annotation_test(a)
 
-    expected = """OPENQASM 3.0;
-def annotation_test(array[int[32], 10] input) {
-}
-array[int[32], 10] a;
-a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
-annotation_test(a);"""
-
-    assert main().to_ir() == expected
+    with pytest.raises(aq.errors.ParameterTypeError):
+        main()
 
 
 def test_map_other():
@@ -357,8 +373,7 @@ def main():
     expected = """OPENQASM 3.0;
 def annotation_test(bit input) {
 }
-bit a;
-a = 1;
+bit a = 1;
 annotation_test(a);"""
 
     assert main().to_ir() == expected
@@ -399,12 +414,10 @@ def main():
 
     expected = """OPENQASM 3.0;
 def assign_param(int[32] c) {
-    int[32] d;
-    d = 4;
+    int[32] d = 4;
     c = d;
 }
-int[32] c;
-c = 0;
+int[32] c = 0;
 assign_param(c);"""
 
     assert main().to_ir() == expected
@@ -423,8 +436,7 @@ def caller() -> int:
 
     expected_qasm = """OPENQASM 3.0;
 def retval_test() -> int[32] {
-    int[32] retval_;
-    retval_ = 1;
+    int[32] retval_ = 1;
     return retval_;
 }
 int[32] __int_1__;
@@ -446,8 +458,7 @@ def caller() -> aq.BitVar:
 
     expected_qasm = """OPENQASM 3.0;
 def retval_test() -> bit {
-    bit retval_;
-    retval_ = 1;
+    bit retval_ = 1;
     return retval_;
 }
 bit __bit_1__;
@@ -470,10 +481,9 @@ def main():
 
     expected_qasm = """OPENQASM 3.0;
 def retval_recursive() -> int[32] {
-    int[32] retval_;
     int[32] __int_1__;
     __int_1__ = retval_recursive();
-    retval_ = 1;
+    int[32] retval_ = 1;
     return retval_;
 }
 int[32] __int_3__;
@@ -497,11 +507,10 @@ def main():
     expected_qasm = """OPENQASM 3.0;
 def retval_recursive() -> int[32] {
     int[32] a;
-    int[32] retval_;
     int[32] __int_1__;
     __int_1__ = retval_recursive();
     a = __int_1__;
-    retval_ = 1;
+    int[32] retval_ = 1;
     return retval_;
 }
 int[32] __int_3__;
@@ -535,8 +544,7 @@ def retval_recursive() -> float[64] {
     return 2 * __float_1__ + (__int_3__ + 2) / 3;
 }
 def retval_constant() -> int[32] {
-    int[32] retval_;
-    retval_ = 3;
+    int[32] retval_ = 3;
     return retval_;
 }
 float[64] __float_4__;
@@ -545,21 +553,6 @@ def retval_constant() -> int[32] {
     assert caller().to_ir() == expected_qasm
 
 
-def test_recursive_list() -> None:
-    """Tests recursive subroutines which return a list."""
-
-    @aq.subroutine
-    def retval_recursive() -> list[int]:
-        retval_recursive()
-        return [1]
-
-    @aq.main
-    def main():
-        retval_recursive()
-
-    assert "-> array[int[32], 10]" in main().to_ir()
-
-
 def test_recursive_oqpy_type() -> None:
     """Tests recursive subroutines which returns an oqpy type."""
 
@@ -586,7 +579,7 @@ def param_test(input: tuple):
     def main():
         param_test(aq.BitVar(1))
 
-    with pytest.raises(TypeError):
+    with pytest.raises(aq.errors.ParameterTypeError):
         main()
 
 
@@ -618,8 +611,7 @@ def main() -> bool:
 
     expected = """OPENQASM 3.0;
 def ret_test() -> bool {
-    bool retval_;
-    retval_ = true;
+    bool retval_ = true;
     return retval_;
 }
 bool __bool_1__;
@@ -639,17 +631,8 @@ def ret_test() -> int:
     def main() -> float:
         ret_test()
 
-    expected = """OPENQASM 3.0;
-def ret_test() -> array[int[32], 10] {
-    array[int[32], 10] retval_;
-    retval_ = {1, 2, 3};
-    return retval_;
-}
-array[int[32], 10] __arr_1__ = {};
-qubit[4] __qubits__;
-__arr_1__ = ret_test();"""
-
-    assert main().to_ir() == expected
+    with pytest.raises(aq.errors.UnsupportedSubroutineReturnType):
+        main()
 
 
 def test_ignore_missing_ret_typehint_list():
@@ -663,17 +646,8 @@ def ret_test():
     def main():
         ret_test()
 
-    expected = """OPENQASM 3.0;
-def ret_test() -> array[int[32], 10] {
-    array[int[32], 10] retval_;
-    retval_ = {1, 2, 3};
-    return retval_;
-}
-array[int[32], 10] __arr_1__ = {};
-qubit[4] __qubits__;
-__arr_1__ = ret_test();"""
-
-    assert main().to_ir() == expected
+    with pytest.raises(aq.errors.UnsupportedSubroutineReturnType):
+        main()
 
 
 def test_ignore_missing_ret_typehint_float():
@@ -689,8 +663,7 @@ def main():
 
     expected = """OPENQASM 3.0;
 def ret_test() -> float[64] {
-    float[64] retval_;
-    retval_ = 1.2;
+    float[64] retval_ = 1.2;
     return retval_;
 }
 qubit[4] __qubits__;

From 5459174be41d04ddd5ee6c7d0c0a9e180719a8db Mon Sep 17 00:00:00 2001
From: Lauren Capelluto 
Date: Mon, 30 Oct 2023 16:38:26 -0400
Subject: [PATCH 0920/1165] bug: Fixup numerical imprecision (#770)

---
 .../braket/experimental/autoqasm/test_parameters.py  | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py
index 3971d45c..26943c2b 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py
@@ -13,6 +13,8 @@
 
 """AutoQASM tests for parameter support."""
 
+import numpy as np
+
 import braket.experimental.autoqasm as aq
 from braket.circuits import FreeParameter
 from braket.default_simulator import StateVectorSimulator
@@ -51,7 +53,7 @@ def test_simple_parametric():
 def test_sim_simple():
     measurements = _test_parametric_on_local_sim(simple_parametric(), {"theta": 0})
     assert 1 not in measurements["__bit_0__"]
-    measurements = _test_parametric_on_local_sim(simple_parametric(), {"theta": 3.14})
+    measurements = _test_parametric_on_local_sim(simple_parametric(), {"theta": np.pi})
     assert 0 not in measurements["__bit_0__"]
 
 
@@ -80,9 +82,9 @@ def test_multiple_parameters():
 
 
 def test_sim_multi_param():
-    measurements = _test_parametric_on_local_sim(multi_parametric(), {"alpha": 3.14, "theta": 0})
+    measurements = _test_parametric_on_local_sim(multi_parametric(), {"alpha": np.pi, "theta": 0})
     assert all(val == "10" for val in measurements["c"])
-    measurements = _test_parametric_on_local_sim(multi_parametric(), {"alpha": 0, "theta": 3.14})
+    measurements = _test_parametric_on_local_sim(multi_parametric(), {"alpha": 0, "theta": np.pi})
     assert all(val == "01" for val in measurements["c"])
 
 
@@ -171,7 +173,7 @@ def test_sim_multi_angle():
     def parametric(phi: float, theta: float):
         ms(0, 1, phi, phi, theta)
 
-    _test_parametric_on_local_sim(parametric(FreeParameter("phi"), 0.0), {"phi": 3.14})
+    _test_parametric_on_local_sim(parametric(FreeParameter("phi"), 0.0), {"phi": np.pi})
 
 
 def test_parameters_passed_as_main_arg():
@@ -243,7 +245,7 @@ def parametric():
         rx_theta(FreeParameter("theta"))
         measure(0)
 
-    measurements = _test_parametric_on_local_sim(parametric(), {"theta": 3.14})
+    measurements = _test_parametric_on_local_sim(parametric(), {"theta": np.pi})
     assert 0 not in measurements["__bit_0__"]
 
 

From d10497a18330e842bcf0df76eeb91e5824214ef5 Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Mon, 30 Oct 2023 16:30:25 -0700
Subject: [PATCH 0921/1165] test: update failed job integ test (#772)

---
 test/integ_tests/job_test_script.py         | 2 +-
 test/integ_tests/test_create_quantum_job.py | 7 ++++---
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/test/integ_tests/job_test_script.py b/test/integ_tests/job_test_script.py
index 8bc3b92c..95b890d6 100644
--- a/test/integ_tests/job_test_script.py
+++ b/test/integ_tests/job_test_script.py
@@ -33,7 +33,7 @@ def start_here():
 
 def failed_job_script():
     print("Test job started!!!!!")
-    assert 0
+    open("fake_file")
 
 
 def completed_job_script():
diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py
index 640cb168..122fd8a6 100644
--- a/test/integ_tests/test_create_quantum_job.py
+++ b/test/integ_tests/test_create_quantum_job.py
@@ -77,7 +77,7 @@ def test_failed_quantum_job(aws_session, capsys):
         "braket_container.py",
         "Running Code As Process",
         "Test job started!!!!!",
-        "AssertionError",
+        "FileNotFoundError: [Errno 2] No such file or directory: 'fake_file'",
         "Code Run Finished",
         '"user_entry_point": "braket_container.py"',
     ]
@@ -85,8 +85,9 @@ def test_failed_quantum_job(aws_session, capsys):
     for data in logs_to_validate:
         assert data in log_data
 
-    assert job.metadata()["failureReason"].startswith(
-        "AlgorithmError: Job at job_test_script:start_here"
+    assert job.metadata()["failureReason"] == (
+        "AlgorithmError: FileNotFoundError: [Errno 2] "
+        "No such file or directory: 'fake_file', exit code: 1"
     )
 
 

From facb2fedc74308c9df3c050b25a7d2808f2285b4 Mon Sep 17 00:00:00 2001
From: Cody Wang 
Date: Mon, 30 Oct 2023 18:39:40 -0700
Subject: [PATCH 0922/1165] fix: Don't run pulse tests when QPU offline (#773)

---
 test/integ_tests/test_pulse.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/test/integ_tests/test_pulse.py b/test/integ_tests/test_pulse.py
index c40a4556..4eb3ffa9 100644
--- a/test/integ_tests/test_pulse.py
+++ b/test/integ_tests/test_pulse.py
@@ -210,6 +210,8 @@ def cz_pulse(
 
 
 def test_pulse_bell(arbitrary_waveform, device):
+    if device.status == "OFFLINE":
+        pytest.skip("Device offline")
     (
         a,
         b,
@@ -258,6 +260,8 @@ def test_pulse_bell(arbitrary_waveform, device):
 
 
 def test_pulse_sequence(arbitrary_waveform, device):
+    if device.status == "OFFLINE":
+        pytest.skip("Device offline")
     (
         a,
         b,
@@ -310,6 +314,8 @@ def test_pulse_sequence(arbitrary_waveform, device):
 
 
 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)

From fea560e0abf489f643dc247cd8c9d754283c89d4 Mon Sep 17 00:00:00 2001
From: ci 
Date: Tue, 31 Oct 2023 01:58:14 +0000
Subject: [PATCH 0923/1165] prepare release v1.60.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 a96eff9e..aa5cc879 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,20 @@
 # Changelog
 
+## v1.60.0 (2023-10-31)
+
+### Features
+
+ * support dependency list for decorator hybrid jobs
+
+### Bug Fixes and Other Changes
+
+ * Don't run pulse tests when QPU offline
+
+### Documentation Changes
+
+ * Fix some nits in the decorator doc string
+ * update intended audience to include education and research
+
 ## v1.59.2 (2023-10-25)
 
 ### Bug Fixes and Other Changes
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index 3ff3dc46..502568dd 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.59.3.dev0"
+__version__ = "1.60.0"

From 6c12cfe6c920be89742556af0a01a2f152b02661 Mon Sep 17 00:00:00 2001
From: ci 
Date: Tue, 31 Oct 2023 01:58:14 +0000
Subject: [PATCH 0924/1165] update development version to v1.60.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 502568dd..faa72dc8 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.60.0"
+__version__ = "1.60.1.dev0"

From 53ea00672a942da99499645d3baaa6e2c7fef8ae Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Tue, 31 Oct 2023 11:56:46 -0400
Subject: [PATCH 0925/1165] Use qubit types from braket.registers module (#771)

---
 .../autoqasm/instructions/qubits.py            | 18 +++++++++---------
 .../experimental/autoqasm/pulse/pulse.py       |  2 +-
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/src/braket/experimental/autoqasm/instructions/qubits.py b/src/braket/experimental/autoqasm/instructions/qubits.py
index 507ba178..babb75aa 100644
--- a/src/braket/experimental/autoqasm/instructions/qubits.py
+++ b/src/braket/experimental/autoqasm/instructions/qubits.py
@@ -22,8 +22,11 @@
 from openpulse.printer import dumps
 
 from braket.experimental.autoqasm import constants, errors, program
+from braket.registers.qubit import Qubit
 
-QubitIdentifierType = Union[int, oqpy._ClassicalVar, oqpy.base.OQPyExpression, str, oqpy.Qubit]
+QubitIdentifierType = Union[
+    int, str, Qubit, oqpy._ClassicalVar, oqpy.base.OQPyExpression, oqpy.Qubit
+]
 
 
 def is_qubit_identifier_type(qubit: Any) -> bool:
@@ -58,11 +61,8 @@ def _get_physical_qubit_indices(qids: list[str]) -> list[int]:
     return braket_qubits
 
 
-def _global_qubit_register(qubit_idx_expr: Union[int, str]) -> str:
-    # TODO: We should index into a oqpy.Qubit register rather
-    # than manually generating the string to index into
-    # a hard-coded global qubit register.
-    return f"{constants.QUBIT_REGISTER}[{qubit_idx_expr}]"
+def _global_qubit_register(qubit_idx_expr: Union[int, str]) -> oqpy.Qubit:
+    return oqpy.Qubit(f"{constants.QUBIT_REGISTER}[{qubit_idx_expr}]", needs_declaration=False)
 
 
 @singledispatch
@@ -92,7 +92,7 @@ def _(qid: float) -> oqpy.Qubit:
 def _(qid: int) -> oqpy.Qubit:
     # Integer virtual qubit, like `h(0)`
     program.get_program_conversion_context().register_qubit(qid)
-    return oqpy.Qubit(_global_qubit_register(qid), needs_declaration=False)
+    return _global_qubit_register(qid)
 
 
 @_qubit.register
@@ -100,7 +100,7 @@ def _(qid: oqpy._ClassicalVar) -> oqpy.Qubit:
     # Indexed by variable, such as i in range(n); h(i)
     if program.get_program_conversion_context().get_declared_qubits() is None:
         raise errors.UnknownQubitCountError()
-    return oqpy.Qubit(_global_qubit_register(qid.name), needs_declaration=False)
+    return _global_qubit_register(qid.name)
 
 
 @_qubit.register
@@ -111,7 +111,7 @@ def _(qid: oqpy.base.OQPyExpression) -> oqpy.Qubit:
 
     oqpy_program = program.get_program_conversion_context().get_oqpy_program()
     qubit_idx_expr = dumps(qid.to_ast(oqpy_program))
-    return oqpy.Qubit(_global_qubit_register(qubit_idx_expr), needs_declaration=False)
+    return _global_qubit_register(qubit_idx_expr)
 
 
 @_qubit.register
diff --git a/src/braket/experimental/autoqasm/pulse/pulse.py b/src/braket/experimental/autoqasm/pulse/pulse.py
index c8c12741..751e106a 100644
--- a/src/braket/experimental/autoqasm/pulse/pulse.py
+++ b/src/braket/experimental/autoqasm/pulse/pulse.py
@@ -19,7 +19,6 @@
 
 import oqpy
 
-from braket.circuits.qubit_set import QubitSet
 from braket.experimental.autoqasm import program as aq_program
 from braket.experimental.autoqasm.instructions.qubits import (
     QubitIdentifierType,
@@ -32,6 +31,7 @@
 from braket.pulse.frame import Frame
 from braket.pulse.pulse_sequence import _validate_uniqueness
 from braket.pulse.waveforms import Waveform
+from braket.registers.qubit_set import QubitSet
 
 
 def _pulse_instruction(name: str, frame: Frame, *args) -> None:

From 14a85a7b167bd365fa3cfcf531b7f4bc11a5ff07 Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Tue, 31 Oct 2023 10:42:50 -0700
Subject: [PATCH 0926/1165] fix: s3 config support for decorator jobs (#774)

---
 src/braket/jobs/hybrid_job.py                  | 2 +-
 test/unit_tests/braket/jobs/test_hybrid_job.py | 9 ++++++++-
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py
index 9fe28e59..cb4792a2 100644
--- a/src/braket/jobs/hybrid_job.py
+++ b/src/braket/jobs/hybrid_job.py
@@ -369,7 +369,7 @@ def is_prefix(path: str) -> bool:
     file_channels = set()
 
     for channel, data in input_data.items():
-        if AwsSession.is_s3_uri(str(data)):
+        if AwsSession.is_s3_uri(str(data)) or isinstance(data, S3DataSourceConfig):
             channel_arg = f'channel="{channel}"' if channel != "input" else ""
             print(
                 "Input data channels mapped to an S3 source will not be available in "
diff --git a/test/unit_tests/braket/jobs/test_hybrid_job.py b/test/unit_tests/braket/jobs/test_hybrid_job.py
index 5c804b0c..b7b7485d 100644
--- a/test/unit_tests/braket/jobs/test_hybrid_job.py
+++ b/test/unit_tests/braket/jobs/test_hybrid_job.py
@@ -15,7 +15,13 @@
 from braket.aws import AwsQuantumJob
 from braket.devices import Devices
 from braket.jobs import hybrid_job
-from braket.jobs.config import CheckpointConfig, InstanceConfig, OutputDataConfig, StoppingCondition
+from braket.jobs.config import (
+    CheckpointConfig,
+    InstanceConfig,
+    OutputDataConfig,
+    S3DataSourceConfig,
+    StoppingCondition,
+)
 from braket.jobs.hybrid_job import _sanitize, _serialize_entry_point
 from braket.jobs.local import LocalQuantumJob
 
@@ -110,6 +116,7 @@ def test_decorator_non_defaults(
             "my_dir": Path(tempdir, "temp_dir"),
             "my_file": Path(tempdir, "temp_file"),
             "my_s3_prefix": "s3://bucket/path/to/prefix",
+            "my_s3_config": S3DataSourceConfig(s3_data="s3://bucket/path/to/prefix"),
         }
 
         @hybrid_job(

From e7bbc8af5e5a54504afc6a95a6c44024810118a0 Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Tue, 31 Oct 2023 17:05:10 -0700
Subject: [PATCH 0927/1165] fix: set decorator job working directory inside of
 function (#775)

---
 src/braket/jobs/_entry_point_template.py | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/braket/jobs/_entry_point_template.py b/src/braket/jobs/_entry_point_template.py
index 285e4d85..dceea98d 100644
--- a/src/braket/jobs/_entry_point_template.py
+++ b/src/braket/jobs/_entry_point_template.py
@@ -5,16 +5,16 @@
 from braket.jobs_data import PersistedJobDataFormat
 
 
-# set working directory to results dir
-os.chdir(get_results_dir())
-
-# create symlinks to input data
-links = link_input()
-
 # load and run serialized entry point function
 recovered = cloudpickle.loads({serialized})
 def {function_name}():
     try:
+        # set working directory to results dir
+        os.chdir(get_results_dir())
+
+        # create symlinks to input data
+        links = link_input()
+
         result = recovered()
     finally:
         clean_links(links)

From 524b1a13e481c7bb931eda6cc4886cc6c0e6546c Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Tue, 31 Oct 2023 18:11:37 -0700
Subject: [PATCH 0928/1165] fix: set python container version explicitly (#776)

---
 test/integ_tests/test_create_quantum_job.py | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py
index 122fd8a6..3b1b8ae9 100644
--- a/test/integ_tests/test_create_quantum_job.py
+++ b/test/integ_tests/test_create_quantum_job.py
@@ -22,17 +22,18 @@
 import pytest
 from job_test_module.job_test_submodule.job_test_submodule_file import submodule_helper
 
+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
 
 
-@pytest.fixture
-def decorator_python_version(aws_session):
+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 major_version, minor_version
+    return int(major_version), int(minor_version)
 
 
 def test_failed_quantum_job(aws_session, capsys):
@@ -200,7 +201,7 @@ def test_completed_quantum_job(aws_session, capsys):
 
 
 @pytest.mark.xfail(
-    (sys.version_info.major, sys.version_info.minor) != decorator_python_version,
+    (sys.version_info.major, sys.version_info.minor) != decorator_python_version(),
     raises=RuntimeError,
     reason="Python version mismatch",
 )
@@ -218,7 +219,6 @@ def __str__(self):
         input_data=str(Path("test", "integ_tests", "requirements")),
     )
     def decorator_job(a, b: int, c=0, d: float = 1.0, **extras):
-        save_job_result(job_test_script.job_helper())
         with open(Path(get_input_data_dir()) / "requirements.txt", "r") as f:
             assert f.readlines() == ["pytest\n"]
         with open(Path("test", "integ_tests", "requirements.txt"), "r") as f:
@@ -244,6 +244,8 @@ def decorator_job(a, b: int, c=0, d: float = 1.0, **extras):
         with open("test/output_file.txt", "w") as f:
             f.write("hello")
 
+        return job_test_script.job_helper()
+
     job = decorator_job(MyClass(), 2, d=5, extra_arg="extra_value")
     assert job.result()["status"] == "SUCCESS"
 
@@ -264,7 +266,7 @@ def decorator_job(a, b: int, c=0, d: float = 1.0, **extras):
 
 
 @pytest.mark.xfail(
-    (sys.version_info.major, sys.version_info.minor) != decorator_python_version,
+    (sys.version_info.major, sys.version_info.minor) != decorator_python_version(),
     raises=RuntimeError,
     reason="Python version mismatch",
 )
@@ -283,7 +285,6 @@ def test_decorator_job_submodule():
         },
     )
     def decorator_job_submodule():
-        save_job_result(submodule_helper())
         with open(Path(get_input_data_dir("my_input")) / "requirements.txt", "r") as f:
             assert f.readlines() == ["pytest\n"]
         with open(Path("test", "integ_tests", "requirements.txt"), "r") as f:
@@ -304,6 +305,7 @@ def decorator_job_submodule():
         ) as f:
             assert f.readlines() == ["pytest\n"]
         assert dir(pytest)
+        save_job_result(submodule_helper())
 
     job = decorator_job_submodule()
     assert job.result()["status"] == "SUCCESS"

From 48f5939eb08b783394057c5b42d3fd7b76a468a9 Mon Sep 17 00:00:00 2001
From: ci 
Date: Wed, 1 Nov 2023 01:34:56 +0000
Subject: [PATCH 0929/1165] prepare release v1.60.1

---
 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 aa5cc879..7e16e5d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Changelog
 
+## v1.60.1 (2023-11-01)
+
+### Bug Fixes and Other Changes
+
+ * set python container version explicitly
+ * set decorator job working directory inside of function
+ * s3 config support for decorator jobs
+
 ## v1.60.0 (2023-10-31)
 
 ### Features
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index faa72dc8..22b0d2c7 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.60.1.dev0"
+__version__ = "1.60.1"

From 7aaf1752a11bb9cf6682919b1542e5dea8ac4bf0 Mon Sep 17 00:00:00 2001
From: ci 
Date: Wed, 1 Nov 2023 01:34:56 +0000
Subject: [PATCH 0930/1165] update development version to v1.60.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 22b0d2c7..ded65106 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.60.1"
+__version__ = "1.60.2.dev0"

From fdad0f1dc2b7963c2d7dd3b2f3d540f578782204 Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Tue, 31 Oct 2023 18:49:10 -0700
Subject: [PATCH 0931/1165] infra: install setuptools for pypi workflow (#777)

---
 .github/workflows/publish-to-pypi.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml
index a1524083..10aef9c2 100644
--- a/.github/workflows/publish-to-pypi.yml
+++ b/.github/workflows/publish-to-pypi.yml
@@ -21,6 +21,8 @@ jobs:
         run: python -m pip install --user --upgrade wheel
       - name: Install twine
         run: python -m pip install --user --upgrade twine
+      - name: Install setuptools
+        run: python -m pip install --user --upgrade setuptools
       - name: Build a binary wheel and a source tarball
         run: python setup.py sdist bdist_wheel
       - name: Publish distribution to PyPI

From 163867f25271c84d356ffff15bcc04c4703bb3b6 Mon Sep 17 00:00:00 2001
From: Abe Coull <85974725+math411@users.noreply.github.com>
Date: Wed, 1 Nov 2023 10:51:44 -0700
Subject: [PATCH 0932/1165] fix: drop task count for batch task tests to 3
 (#760)

---
 test/unit_tests/braket/aws/test_aws_device.py          |  1 +
 test/unit_tests/braket/devices/test_local_simulator.py | 10 +++++-----
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py
index c1e03483..3770d38a 100644
--- a/test/unit_tests/braket/aws/test_aws_device.py
+++ b/test/unit_tests/braket/aws/test_aws_device.py
@@ -1673,6 +1673,7 @@ def test_get_devices_simulators_only(mock_copy_session, aws_session):
     assert [result.name for result in results] == ["SV1"]
 
 
+@pytest.mark.filterwarnings("ignore:Test Code:")
 @patch("braket.aws.aws_device.AwsSession.copy_session")
 def test_get_devices_with_error_in_region(mock_copy_session, aws_session):
     aws_session.search_devices.side_effect = [
diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py
index 08f2a19c..8485dc5e 100644
--- a/test/unit_tests/braket/devices/test_local_simulator.py
+++ b/test/unit_tests/braket/devices/test_local_simulator.py
@@ -316,7 +316,7 @@ def test_batch_circuit():
     theta = FreeParameter("theta")
     task = Circuit().rx(angle=theta, target=0)
     device = LocalSimulator(dummy)
-    num_tasks = 10
+    num_tasks = 3
     circuits = [task for _ in range(num_tasks)]
     inputs = [{"theta": i} for i in range(num_tasks)]
     batch = device.run_batch(circuits, inputs=inputs, shots=10)
@@ -329,7 +329,7 @@ def test_batch_with_max_parallel():
     dummy = DummyProgramSimulator()
     task = Circuit().h(0).cnot(0, 1)
     device = LocalSimulator(dummy)
-    num_tasks = 10
+    num_tasks = 3
     circuits = [task for _ in range(num_tasks)]
     batch = device.run_batch(circuits, shots=10, max_parallel=2)
     assert len(batch.results()) == num_tasks
@@ -341,7 +341,7 @@ def test_batch_with_annealing_problems():
     dummy = DummyAnnealingSimulator()
     problem = Problem(ProblemType.ISING)
     device = LocalSimulator(dummy)
-    num_tasks = 10
+    num_tasks = 3
     problems = [problem for _ in range(num_tasks)]
     batch = device.run_batch(problems, shots=10)
     assert len(batch.results()) == num_tasks
@@ -353,7 +353,7 @@ def test_batch_circuit_without_inputs():
     dummy = DummyProgramSimulator()
     bell = Circuit().h(0).cnot(0, 1)
     device = LocalSimulator(dummy)
-    num_tasks = 10
+    num_tasks = 3
     circuits = [bell for _ in range(num_tasks)]
     batch = device.run_batch(circuits, shots=10)
     assert len(batch.results()) == num_tasks
@@ -385,7 +385,7 @@ def test_batch_circuit_with_task_and_input_mismatch():
     dummy = DummyProgramSimulator()
     bell = Circuit().h(0).cnot(0, 1)
     device = LocalSimulator(dummy)
-    num_tasks = 10
+    num_tasks = 3
     circuits = [bell for _ in range(num_tasks)]
     inputs = [{} for _ in range(num_tasks - 1)]
     with pytest.raises(ValueError):

From 2337c261a4b0d0ea731af4435757382ea508fdc3 Mon Sep 17 00:00:00 2001
From: ci 
Date: Wed, 1 Nov 2023 18:09:10 +0000
Subject: [PATCH 0933/1165] prepare release v1.60.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 7e16e5d0..7a17eb6b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## v1.60.2 (2023-11-01)
+
+### Bug Fixes and Other Changes
+
+ * drop task count for batch task tests to 3
+
 ## v1.60.1 (2023-11-01)
 
 ### Bug Fixes and Other Changes
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index ded65106..bdd07dae 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.60.2.dev0"
+__version__ = "1.60.2"

From 4f4938ec457e8e4b1cc92668839f43e32bd02175 Mon Sep 17 00:00:00 2001
From: ci 
Date: Wed, 1 Nov 2023 18:09:10 +0000
Subject: [PATCH 0934/1165] update development version to v1.60.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 bdd07dae..20b34811 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.60.2"
+__version__ = "1.60.3.dev0"

From ef4c92f373cbb7530d086cca39f683f3f1d77a45 Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Thu, 2 Nov 2023 13:10:31 -0700
Subject: [PATCH 0935/1165] feat: simplify entry point wrapper (#781)

---
 src/braket/jobs/hybrid_job.py | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py
index cb4792a2..da0b8436 100644
--- a/src/braket/jobs/hybrid_job.py
+++ b/src/braket/jobs/hybrid_job.py
@@ -276,10 +276,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
 
 def _serialize_entry_point(entry_point: Callable, args: tuple, kwargs: dict) -> str:
     """Create an entry point from a function"""
-
-    def wrapped_entry_point() -> Any:
-        """Partial function wrapping entry point with given parameters"""
-        return entry_point(*args, **kwargs)
+    wrapped_entry_point = functools.partial(entry_point, *args, **kwargs)
 
     try:
         serialized = cloudpickle.dumps(wrapped_entry_point)

From c953d2f6c8d9bf7c3398dd3988b56b9237ca3da9 Mon Sep 17 00:00:00 2001
From: Abe Coull <85974725+math411@users.noreply.github.com>
Date: Thu, 2 Nov 2023 15:24:35 -0700
Subject: [PATCH 0936/1165] infra: pin the linter check to only use Python 3.9
 (#766)

---
 .github/workflows/check-format.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml
index 034e95be..dbe9599c 100644
--- a/.github/workflows/check-format.yml
+++ b/.github/workflows/check-format.yml
@@ -20,7 +20,7 @@ jobs:
     - name: Set up Python
       uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1
       with:
-        python-version: '3.x'
+        python-version: '3.9'
     - name: Install dependencies
       run: |
         pip install --upgrade pip

From dc2ac5619cd8fd9be8a2f1c29cd27204daebf416 Mon Sep 17 00:00:00 2001
From: Milan <30416311+krneta@users.noreply.github.com>
Date: Fri, 3 Nov 2023 11:06:06 -0700
Subject: [PATCH 0937/1165] fix: fixing some type hints for optional params
 (#782)

---
 src/braket/annealing/problem.py               |  8 +-
 src/braket/aws/aws_quantum_job.py             | 88 +++++++++---------
 src/braket/aws/aws_quantum_task.py            | 22 ++---
 src/braket/aws/aws_quantum_task_batch.py      |  4 +-
 src/braket/aws/aws_session.py                 | 20 ++---
 src/braket/circuits/circuit.py                | 53 +++++------
 src/braket/circuits/compiler_directive.py     | 12 +--
 src/braket/circuits/gate.py                   |  9 +-
 src/braket/circuits/instruction.py            | 31 ++++---
 src/braket/circuits/moments.py                |  6 +-
 src/braket/circuits/noise.py                  |  8 +-
 src/braket/circuits/observable.py             | 18 ++--
 src/braket/circuits/result_type.py            | 28 +++---
 src/braket/circuits/result_types.py           | 64 ++++++-------
 src/braket/jobs/config.py                     |  4 +-
 src/braket/jobs/data_persistence.py           | 17 ++--
 src/braket/jobs/hybrid_job.py                 | 64 ++++++-------
 src/braket/jobs/local/local_job.py            | 63 ++++++-------
 src/braket/jobs/local/local_job_container.py  |  6 +-
 .../cwl_insights_metrics_fetcher.py           | 12 +--
 src/braket/jobs/quantum_job.py                |  6 +-
 src/braket/jobs/quantum_job_creation.py       | 89 ++++++++++---------
 src/braket/pulse/pulse_sequence.py            |  4 +-
 src/braket/registers/qubit_set.py             |  5 +-
 24 files changed, 332 insertions(+), 309 deletions(-)

diff --git a/src/braket/annealing/problem.py b/src/braket/annealing/problem.py
index 4959df52..d8b40c37 100644
--- a/src/braket/annealing/problem.py
+++ b/src/braket/annealing/problem.py
@@ -37,16 +37,16 @@ class Problem:
     def __init__(
         self,
         problem_type: ProblemType,
-        linear: Dict[int, float] = None,
-        quadratic: Dict[Tuple[int, int], float] = None,
+        linear: Dict[int, float] | None = None,
+        quadratic: Dict[Tuple[int, int], float] | None = None,
     ):
         """
 
         Args:
             problem_type (ProblemType): The type of annealing problem
-            linear (Dict[int, float]): 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]): 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:
diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py
index 76ff0648..8ae4bc12 100644
--- a/src/braket/aws/aws_quantum_job.py
+++ b/src/braket/aws/aws_quantum_job.py
@@ -64,22 +64,22 @@ def create(
         cls,
         device: str,
         source_module: str,
-        entry_point: str = None,
-        image_uri: str = None,
-        job_name: str = None,
-        code_location: str = None,
-        role_arn: str = None,
+        entry_point: str | None = None,
+        image_uri: str | None = None,
+        job_name: str | None = None,
+        code_location: str | None = None,
+        role_arn: str | None = None,
         wait_until_complete: bool = False,
-        hyperparameters: dict[str, Any] = None,
-        input_data: str | dict | S3DataSourceConfig = None,
-        instance_config: InstanceConfig = None,
-        distribution: str = None,
-        stopping_condition: StoppingCondition = None,
-        output_data_config: OutputDataConfig = None,
-        copy_checkpoints_from_job: str = None,
-        checkpoint_config: CheckpointConfig = None,
-        aws_session: AwsSession = None,
-        tags: dict[str, str] = None,
+        hyperparameters: dict[str, Any] | None = None,
+        input_data: str | dict | S3DataSourceConfig | None = None,
+        instance_config: InstanceConfig | None = None,
+        distribution: str | None = None,
+        stopping_condition: StoppingCondition | None = None,
+        output_data_config: OutputDataConfig | None = None,
+        copy_checkpoints_from_job: str | None = None,
+        checkpoint_config: CheckpointConfig | None = None,
+        aws_session: AwsSession | None = None,
+        tags: dict[str, str] | None = None,
         logger: Logger = getLogger(__name__),
     ) -> AwsQuantumJob:
         """Creates a hybrid job by invoking the Braket CreateJob API.
@@ -96,37 +96,38 @@ def create(
                 tarred and uploaded. If `source_module` is an S3 URI, it must point to a
                 tar.gz file. Otherwise, source_module may be a file or directory.
 
-            entry_point (str): A str that specifies the entry point of the hybrid job, relative to
-                the source module. The entry point must be in the format
+            entry_point (str | None): A str that specifies the entry point of the hybrid job,
+                relative to the source module. The entry point must be in the format
                 `importable.module` or `importable.module:callable`. For example,
                 `source_module.submodule:start_here` indicates the `start_here` function
                 contained in `source_module.submodule`. If source_module is an S3 URI,
                 entry point must be given. Default: source_module's name
 
-            image_uri (str): A str that specifies the ECR image to use for executing the hybrid job.
-                `image_uris.retrieve_image()` function may be used for retrieving the ECR image URIs
-                for the containers supported by Braket. Default = ``.
+            image_uri (str | None): A str that specifies the ECR image to use for executing the
+                hybrid job. `image_uris.retrieve_image()` function may be used for retrieving the
+                ECR image URIs for the containers supported by Braket.
+                Default = ``.
 
-            job_name (str): A str that specifies the name with which the hybrid job is created.
-                Allowed pattern for hybrid job name: `^[a-zA-Z0-9](-*[a-zA-Z0-9]){0,50}$`
+            job_name (str | None): A str that specifies the name with which the hybrid job is
+                created. Allowed pattern for hybrid job name: `^[a-zA-Z0-9](-*[a-zA-Z0-9]){0,50}$`
                 Default: f'{image_uri_type}-{timestamp}'.
 
-            code_location (str): The S3 prefix URI where custom code will be uploaded.
+            code_location (str | None): The S3 prefix URI where custom code will be uploaded.
                 Default: f's3://{default_bucket_name}/jobs/{job_name}/script'.
 
-            role_arn (str): A str providing the IAM role ARN used to execute the
+            role_arn (str | None): A str providing the IAM role ARN used to execute the
                 script. Default: IAM role returned by AwsSession's `get_default_jobs_role()`.
 
             wait_until_complete (bool): `True` if we should wait until the hybrid job completes.
                 This would tail the hybrid job logs as it waits. Otherwise `False`.
                 Default: `False`.
 
-            hyperparameters (dict[str, Any]): Hyperparameters accessible to the hybrid job.
+            hyperparameters (dict[str, Any] | None): Hyperparameters accessible to the hybrid job.
                 The hyperparameters are made accessible as a dict[str, str] to the hybrid job.
                 For convenience, this accepts other types for keys and values, but `str()`
                 is called to convert them before being passed on. Default: None.
 
-            input_data (str | dict | S3DataSourceConfig): Information about the training
+            input_data (str | dict | S3DataSourceConfig | None): Information about the training
                 data. Dictionary maps channel names to local paths or S3 URIs. Contents found
                 at any local paths will be uploaded to S3 at
                 f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local
@@ -134,39 +135,40 @@ def create(
                 channel name "input".
                 Default: {}.
 
-            instance_config (InstanceConfig): Configuration of the instance(s) for running the
-                classical code for the hybrid job. Default:
+            instance_config (InstanceConfig | None): Configuration of the instance(s) for running
+                the classical code for the hybrid job. Default:
                 `InstanceConfig(instanceType='ml.m5.large', instanceCount=1, volumeSizeInGB=30)`.
 
-            distribution (str): A str that specifies how the hybrid job should be distributed.
-                If set to "data_parallel", the hyperparameters for the hybrid job will be set
-                to use data parallelism features for PyTorch or TensorFlow. Default: None.
+            distribution (str | None): A str that specifies how the hybrid job should be
+                distributed. If set to "data_parallel", the hyperparameters for the hybrid job will
+                be set to use data parallelism features for PyTorch or TensorFlow. Default: None.
 
-            stopping_condition (StoppingCondition): The maximum length of time, in seconds,
+            stopping_condition (StoppingCondition | None): The maximum length of time, in seconds,
                 and the maximum number of quantum tasks that a hybrid job can run before being
                 forcefully stopped.
                 Default: StoppingCondition(maxRuntimeInSeconds=5 * 24 * 60 * 60).
 
-            output_data_config (OutputDataConfig): Specifies the location for the output of the
-                hybrid job.
+            output_data_config (OutputDataConfig | None): Specifies the location for the output of
+                the hybrid job.
                 Default: OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data',
                 kmsKeyId=None).
 
-            copy_checkpoints_from_job (str): A str that specifies the hybrid job ARN whose
+            copy_checkpoints_from_job (str | None): A str that specifies the hybrid job ARN whose
                 checkpoint you want to use in the current hybrid job. Specifying this value will
                 copy over the checkpoint data from `use_checkpoints_from_job`'s checkpoint_config
                 s3Uri to the current hybrid job's checkpoint_config s3Uri, making it available at
                 checkpoint_config.localPath during the hybrid job execution. Default: None
 
-            checkpoint_config (CheckpointConfig): Configuration that specifies the location where
-                checkpoint data is stored.
+            checkpoint_config (CheckpointConfig | None): Configuration that specifies the location
+                where checkpoint data is stored.
                 Default: CheckpointConfig(localPath='/opt/jobs/checkpoints',
                 s3Uri=f's3://{default_bucket_name}/jobs/{job_name}/checkpoints').
 
-            aws_session (AwsSession): AwsSession for connecting to AWS Services.
+            aws_session (AwsSession | None): AwsSession for connecting to AWS Services.
                 Default: AwsSession()
 
-            tags (dict[str, str]): Dict specifying the key-value pairs for tagging this hybrid job.
+            tags (dict[str, str] | None): Dict specifying the key-value pairs for tagging this
+                hybrid job.
                 Default: {}.
 
             logger (Logger): Logger object with which to write logs, such as quantum task statuses
@@ -210,11 +212,11 @@ def create(
 
         return job
 
-    def __init__(self, arn: str, aws_session: AwsSession = None):
+    def __init__(self, arn: str, aws_session: AwsSession | None = None):
         """
         Args:
             arn (str): The ARN of the hybrid job.
-            aws_session (AwsSession): The `AwsSession` for connecting to AWS services.
+            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.
         """
@@ -486,7 +488,7 @@ def _read_and_deserialize_results(temp_dir: str, job_name: str) -> dict[str, Any
 
     def download_result(
         self,
-        extract_to: str = None,
+        extract_to: str | None = None,
         poll_timeout_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_TIMEOUT,
         poll_interval_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_INTERVAL,
     ) -> None:
@@ -495,7 +497,7 @@ def download_result(
         the results are extracted to the current directory.
 
         Args:
-            extract_to (str): The directory to which the results are extracted. The results
+            extract_to (str | None): The directory to which the results are extracted. The results
                 are extracted to a folder titled with the hybrid job name within this directory.
                 Default= `Current working directory`.
             poll_timeout_seconds (float): The polling timeout, in seconds, for `download_result()`.
diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py
index 346816e7..fa58d911 100644
--- a/src/braket/aws/aws_quantum_task.py
+++ b/src/braket/aws/aws_quantum_task.py
@@ -100,11 +100,11 @@ def create(
         ],
         s3_destination_folder: AwsSession.S3DestinationFolder,
         shots: int,
-        device_parameters: dict[str, Any] = None,
+        device_parameters: dict[str, Any] | None = None,
         disable_qubit_rewiring: bool = False,
-        tags: dict[str, str] = None,
-        inputs: 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: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] | None = None,
         *args,
         **kwargs,
     ) -> AwsQuantumTask:
@@ -129,7 +129,7 @@ def create(
                 `shots=0` is only available on simulators and means that the simulator
                 will compute the exact results based on the quantum task specification.
 
-            device_parameters (dict[str, Any]): Additional parameters to send to the device.
+            device_parameters (dict[str, Any] | None): Additional parameters to send to the device.
 
             disable_qubit_rewiring (bool): Whether to run the circuit with the exact qubits chosen,
                 without any rewiring downstream, if this is supported by the device.
@@ -137,15 +137,15 @@ def create(
                 If ``True``, no qubit rewiring is allowed; if ``False``, qubit rewiring is allowed.
                 Default: False
 
-            tags (dict[str, str]): Tags, which are Key-Value pairs to add to this quantum task.
-                An example would be:
+            tags (dict[str, str] | None): Tags, which are Key-Value pairs to add to this quantum
+                task. An example would be:
                 `{"state": "washington"}`
 
-            inputs (dict[str, float]): Inputs to be passed along with the
+            inputs (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 (Optional[dict[tuple[Gate, QubitSet], PulseSequence]]):
+            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`.
@@ -203,7 +203,7 @@ def create(
     def __init__(
         self,
         arn: str,
-        aws_session: 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__),
@@ -211,7 +211,7 @@ def __init__(
         """
         Args:
             arn (str): The ARN of the quantum task.
-            aws_session (AwsSession): The `AwsSession` for connecting to AWS services.
+            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 quantum task.
             poll_timeout_seconds (float): The polling timeout for `result()`. Default: 5 days.
diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py
index d29533a9..adf15bda 100644
--- a/src/braket/aws/aws_quantum_task_batch.py
+++ b/src/braket/aws/aws_quantum_task_batch.py
@@ -60,7 +60,7 @@ 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,
+        inputs: Union[dict[str, float], list[dict[str, float]]] | None = None,
         *aws_quantum_task_args,
         **aws_quantum_task_kwargs,
     ):
@@ -88,7 +88,7 @@ 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]]]): Inputs to be passed
+            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: {}.
         """
diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py
index eabe06dc..9523f057 100644
--- a/src/braket/aws/aws_session.py
+++ b/src/braket/aws/aws_session.py
@@ -40,17 +40,17 @@ class AwsSession(object):
 
     def __init__(
         self,
-        boto_session: boto3.Session = None,
-        braket_client: client = None,
-        config: Config = None,
-        default_bucket: str = None,
+        boto_session: boto3.Session | None = None,
+        braket_client: client | None = None,
+        config: Config | None = None,
+        default_bucket: str | None = None,
     ):
         """
         Args:
-            boto_session (Session): A boto3 session object.
-            braket_client (client): A boto3 Braket client.
-            config (Config): A botocore Config object.
-            default_bucket (str): The name of the default bucket of the AWS Session.
+            boto_session (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.
         """
         if (
             boto_session
@@ -716,7 +716,7 @@ def describe_log_streams(
         self,
         log_group: str,
         log_stream_prefix: str,
-        limit: int = None,
+        limit: Optional[int] = None,
         next_token: Optional[str] = None,
     ) -> dict[str, Any]:
         """
@@ -725,7 +725,7 @@ def describe_log_streams(
         Args:
             log_group (str): Name of the log group.
             log_stream_prefix (str): Prefix for log streams to include.
-            limit (int): Limit for number of log streams returned.
+            limit (Optional[int]): Limit for number of log streams returned.
                 default is 50.
             next_token (Optional[str]): The token for the next set of items to return.
                 Would have been received in a previous call.
diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py
index 99b3e8ec..81139e9e 100644
--- a/src/braket/circuits/circuit.py
+++ b/src/braket/circuits/circuit.py
@@ -117,10 +117,10 @@ def method_from_subroutine(self, *args, **kwargs) -> SubroutineReturn:
         function_attr = getattr(cls, function_name)
         setattr(function_attr, "__doc__", func.__doc__)
 
-    def __init__(self, addable: AddableTypes = None, *args, **kwargs):
+    def __init__(self, addable: AddableTypes | None = None, *args, **kwargs):
         """
         Args:
-            addable (AddableTypes): The item(s) to add to self.
+            addable (AddableTypes | None): The item(s) to add to self.
                 Default = None.
 
         Raises:
@@ -235,18 +235,18 @@ def parameters(self) -> set[FreeParameter]:
     def add_result_type(
         self,
         result_type: ResultType,
-        target: QubitSetInput = None,
-        target_mapping: dict[QubitInput, QubitInput] = None,
+        target: QubitSetInput | None = None,
+        target_mapping: dict[QubitInput, QubitInput] | None = None,
     ) -> Circuit:
         """
         Add a requested result type to `self`, returns `self` for chaining ability.
 
         Args:
             result_type (ResultType): `ResultType` to add into `self`.
-            target (QubitSetInput): Target qubits for the
+            target (QubitSetInput | None): Target qubits for the
                 `result_type`.
                 Default = `None`.
-            target_mapping (dict[QubitInput, QubitInput]): A dictionary of
+            target_mapping (dict[QubitInput, QubitInput] | None): A dictionary of
                 qubit mappings to apply to the `result_type.target`. Key is the qubit in
                 `result_type.target` and the value is what the key will be changed to.
                 Default = `None`.
@@ -399,19 +399,19 @@ def _add_to_qubit_observable_set(self, result_type: ResultType) -> None:
     def add_instruction(
         self,
         instruction: Instruction,
-        target: QubitSetInput = None,
-        target_mapping: dict[QubitInput, QubitInput] = None,
+        target: QubitSetInput | None = None,
+        target_mapping: dict[QubitInput, QubitInput] | None = None,
     ) -> Circuit:
         """
         Add an instruction to `self`, returns `self` for chaining ability.
 
         Args:
             instruction (Instruction): `Instruction` to add into `self`.
-            target (QubitSetInput): Target qubits for the
+            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]): A dictionary of
+            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`.
@@ -491,19 +491,19 @@ def _check_for_params(self, instruction: Instruction) -> bool:
     def add_circuit(
         self,
         circuit: Circuit,
-        target: QubitSetInput = None,
-        target_mapping: dict[QubitInput, QubitInput] = None,
+        target: QubitSetInput | None = None,
+        target_mapping: dict[QubitInput, QubitInput] | None = None,
     ) -> Circuit:
         """
         Add a `circuit` to self, returns self for chaining ability.
 
         Args:
             circuit (Circuit): Circuit to add into self.
-            target (QubitSetInput): Target qubits for the
+            target (QubitSetInput | None): Target qubits for the
                 supplied circuit. This is a macro over `target_mapping`; `target` is converted to
                 a `target_mapping` by zipping together a sorted `circuit.qubits` and `target`.
                 Default = `None`.
-            target_mapping (dict[QubitInput, QubitInput]): A dictionary of
+            target_mapping (dict[QubitInput, QubitInput] | None): A dictionary of
                 qubit mappings to apply to the qubits of `circuit.instructions`. Key is the qubit
                 to map, and the value is what to change it to. Default = `None`.
 
@@ -567,8 +567,8 @@ def add_circuit(
     def add_verbatim_box(
         self,
         verbatim_circuit: Circuit,
-        target: QubitSetInput = None,
-        target_mapping: dict[QubitInput, QubitInput] = None,
+        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
@@ -576,11 +576,11 @@ def add_verbatim_box(
 
         Args:
             verbatim_circuit (Circuit): Circuit to add into self.
-            target (QubitSetInput): Target qubits for the
+            target (QubitSetInput | None): Target qubits for the
                 supplied circuit. This is a macro over `target_mapping`; `target` is converted to
                 a `target_mapping` by zipping together a sorted `circuit.qubits` and `target`.
                 Default = `None`.
-            target_mapping (dict[QubitInput, QubitInput]): A dictionary of
+            target_mapping (dict[QubitInput, QubitInput] | None): A dictionary of
                 qubit mappings to apply to the qubits of `circuit.instructions`. Key is the qubit
                 to map, and the value is what to change it to. Default = `None`.
 
@@ -638,7 +638,7 @@ def apply_gate_noise(
         self,
         noise: Union[type[Noise], Iterable[type[Noise]]],
         target_gates: Optional[Union[type[Gate], Iterable[type[Gate]]]] = None,
-        target_unitary: np.ndarray = None,
+        target_unitary: Optional[np.ndarray] = None,
         target_qubits: Optional[QubitSetInput] = None,
     ) -> Circuit:
         """Apply `noise` to the circuit according to `target_gates`, `target_unitary` and
@@ -667,7 +667,7 @@ def apply_gate_noise(
                 to the circuit.
             target_gates (Optional[Union[type[Gate], Iterable[type[Gate]]]]): Gate class or
                 List of Gate classes which `noise` is applied to. Default=None.
-            target_unitary (ndarray): matrix of the target unitary gates. Default=None.
+            target_unitary (Optional[ndarray]): matrix of the target unitary gates. Default=None.
             target_qubits (Optional[QubitSetInput]): Index or indices of qubit(s).
                 Default=None.
 
@@ -1097,7 +1097,7 @@ def diagram(self, circuit_diagram_class: type = AsciiCircuitDiagram) -> str:
     def to_ir(
         self,
         ir_type: IRType = IRType.JAQCD,
-        serialization_properties: SerializationProperties = None,
+        serialization_properties: Optional[SerializationProperties] = None,
         gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None,
     ) -> Union[OpenQasmProgram, JaqcdProgram]:
         """
@@ -1107,9 +1107,10 @@ def to_ir(
         Args:
             ir_type (IRType): The IRType to use for converting the circuit object to its
                 IR representation.
-            serialization_properties (SerializationProperties): 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.
+            serialization_properties (Optional[SerializationProperties]): 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
                 calibration data for the device. default: None.
 
@@ -1490,12 +1491,12 @@ def __eq__(self, other):
             )
         return NotImplemented
 
-    def __call__(self, arg: Any = None, **kwargs) -> Circuit:
+    def __call__(self, arg: Any | None = None, **kwargs) -> Circuit:
         """
         Implements the call function to easily make a bound Circuit.
 
         Args:
-            arg (Any): A value to bind to all parameters. Defaults to None and
+            arg (Any | None): A value to bind to all parameters. Defaults to None and
                 can be overridden if the parameter is in kwargs.
 
         Returns:
diff --git a/src/braket/circuits/compiler_directive.py b/src/braket/circuits/compiler_directive.py
index d5b5591f..628422c7 100644
--- a/src/braket/circuits/compiler_directive.py
+++ b/src/braket/circuits/compiler_directive.py
@@ -48,20 +48,20 @@ def ascii_symbols(self) -> tuple[str, ...]:
 
     def to_ir(
         self,
-        target: QubitSet = None,
+        target: QubitSet | None = None,
         ir_type: IRType = IRType.JAQCD,
-        serialization_properties: SerializationProperties = None,
+        serialization_properties: SerializationProperties | None = None,
         **kwargs,
     ) -> Any:
         """Returns IR object of the compiler directive.
 
         Args:
-            target (QubitSet): target qubit(s). Defaults to None
+            target (QubitSet | None): target qubit(s). Defaults to None
             ir_type(IRType) : The IRType to use for converting the compiler directive object to its
                 IR representation. Defaults to IRType.JAQCD.
-            serialization_properties (SerializationProperties): 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.
+            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 compiler directive.
diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py
index 718e3344..3c59409e 100644
--- a/src/braket/circuits/gate.py
+++ b/src/braket/circuits/gate.py
@@ -70,7 +70,7 @@ def to_ir(
         self,
         target: QubitSet,
         ir_type: IRType = IRType.JAQCD,
-        serialization_properties: SerializationProperties = None,
+        serialization_properties: Optional[SerializationProperties] = None,
         *,
         control: Optional[QubitSet] = None,
         control_state: Optional[BasisStateInput] = None,
@@ -82,9 +82,10 @@ def to_ir(
             target (QubitSet): target qubit(s).
             ir_type(IRType) : The IRType to use for converting the gate object to its
                 IR representation. Defaults to IRType.JAQCD.
-            serialization_properties (SerializationProperties): 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.
+            serialization_properties (Optional[SerializationProperties]): 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.
             control (Optional[QubitSet]): Control qubit(s). Only supported for OpenQASM.
                 Default None.
             control_state (Optional[BasisStateInput]): Quantum state on which to control the
diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py
index 18dc49e4..0b2f90d0 100644
--- a/src/braket/circuits/instruction.py
+++ b/src/braket/circuits/instruction.py
@@ -37,7 +37,7 @@ class Instruction:
     def __init__(
         self,
         operator: InstructionOperator,
-        target: QubitSetInput = None,
+        target: Optional[QubitSetInput] = None,
         *,
         control: Optional[QubitSetInput] = None,
         control_state: Optional[BasisStateInput] = None,
@@ -48,7 +48,8 @@ def __init__(
 
         Args:
             operator (InstructionOperator): Operator for the instruction.
-            target (QubitSetInput): Target qubits that the operator is applied to. Default is None.
+            target (Optional[QubitSetInput]): Target qubits that the operator is applied to.
+                Default is None.
             control (Optional[QubitSetInput]): Target qubits that the operator is controlled on.
                 Default is None.
             control_state (Optional[BasisStateInput]): Quantum state on which to control the
@@ -165,7 +166,7 @@ def adjoint(self) -> list[Instruction]:
     def to_ir(
         self,
         ir_type: IRType = IRType.JAQCD,
-        serialization_properties: SerializationProperties = None,
+        serialization_properties: SerializationProperties | None = None,
     ) -> Any:
         """
         Converts the operator into the canonical intermediate representation.
@@ -174,9 +175,9 @@ def to_ir(
         Args:
             ir_type(IRType) : The IRType to use for converting the instruction object to its
                 IR representation.
-            serialization_properties (SerializationProperties): 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.
+            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 instruction.
@@ -201,10 +202,10 @@ def ascii_symbols(self) -> tuple[str, ...]:
 
     def copy(
         self,
-        target_mapping: dict[QubitInput, QubitInput] = None,
-        target: QubitSetInput = None,
-        control_mapping: dict[QubitInput, QubitInput] = None,
-        control: QubitSetInput = None,
+        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,
         power: float = 1,
     ) -> Instruction:
@@ -217,14 +218,16 @@ def copy(
             Same relationship holds for `control_mapping`.
 
         Args:
-            target_mapping (dict[QubitInput, QubitInput]): A dictionary of
+            target_mapping (Optional[dict[QubitInput, QubitInput]]): A dictionary of
                 qubit mappings to apply to the target. Key is the qubit in this `target` and the
                 value is what the key is changed to. Default = `None`.
-            target (QubitSetInput): Target qubits for the new instruction. Default is None.
-            control_mapping (dict[QubitInput, QubitInput]): A dictionary of
+            target (Optional[QubitSetInput]): Target qubits for the new instruction.
+                Default is None.
+            control_mapping (Optional[dict[QubitInput, QubitInput]]): A dictionary of
                 qubit mappings to apply to the control. Key is the qubit in this `control` and the
                 value is what the key is changed to. Default = `None`.
-            control (QubitSetInput): Control qubits for the new instruction. Default is None.
+            control (Optional[QubitSetInput]): Control qubits for the new instruction.
+                Default is 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
diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py
index a4c31339..7cd8e0a9 100644
--- a/src/braket/circuits/moments.py
+++ b/src/braket/circuits/moments.py
@@ -100,7 +100,7 @@ class Moments(Mapping[MomentsKey, Instruction]):
             Value: Instruction('operator': H, 'target': QubitSet([Qubit(1)]))
     """
 
-    def __init__(self, instructions: Iterable[Instruction] = None):
+    def __init__(self, instructions: Iterable[Instruction] | None = None):
         self._moments: OrderedDict[MomentsKey, Instruction] = OrderedDict()
         self._max_times: dict[Qubit, int] = {}
         self._qubits = QubitSet()
@@ -283,13 +283,13 @@ def values(self) -> ValuesView[Instruction]:
         self.sort_moments()
         return self._moments.values()
 
-    def get(self, key: MomentsKey, default: Any = None) -> Instruction:
+    def get(self, key: MomentsKey, default: Any | None = None) -> Instruction:
         """
         Get the instruction in self by key.
 
         Args:
             key (MomentsKey): Key of the instruction to fetch.
-            default (Any): Value to return if `key` is not in `moments`. Default = `None`.
+            default (Any | None): Value to return if `key` is not in `moments`. Default = `None`.
 
         Returns:
             Instruction: `moments[key]` if `key` in `moments`, else `default` is returned.
diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py
index 8e5b9cc7..af1bb422 100644
--- a/src/braket/circuits/noise.py
+++ b/src/braket/circuits/noise.py
@@ -68,7 +68,7 @@ def to_ir(
         self,
         target: QubitSet,
         ir_type: IRType = IRType.JAQCD,
-        serialization_properties: SerializationProperties = None,
+        serialization_properties: SerializationProperties | None = None,
     ) -> Any:
         """Returns IR object of quantum operator and target
 
@@ -76,9 +76,9 @@ def to_ir(
             target (QubitSet): target qubit(s)
             ir_type(IRType) : The IRType to use for converting the noise object to its
                 IR representation. Defaults to IRType.JAQCD.
-            serialization_properties (SerializationProperties): 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.
+            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
 
diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py
index 217bd597..03cd0714 100644
--- a/src/braket/circuits/observable.py
+++ b/src/braket/circuits/observable.py
@@ -47,19 +47,19 @@ def _unscaled(self) -> Observable:
 
     def to_ir(
         self,
-        target: QubitSet = None,
+        target: QubitSet | None = None,
         ir_type: IRType = IRType.JAQCD,
-        serialization_properties: SerializationProperties = None,
+        serialization_properties: SerializationProperties | None = None,
     ) -> Union[str, list[Union[str, list[list[list[float]]]]]]:
         """Returns the IR representation for the observable
 
         Args:
-            target (QubitSet): target qubit(s). Defaults to None.
+            target (QubitSet | 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): 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.
+            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:
             Union[str, list[Union[str, list[list[list[float]]]]]]: The IR representation for
@@ -90,7 +90,9 @@ def _to_jaqcd(self) -> list[Union[str, list[list[list[float]]]]]:
         raise NotImplementedError("to_jaqcd has not been implemented yet.")
 
     def _to_openqasm(
-        self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None
+        self,
+        serialization_properties: OpenQASMSerializationProperties,
+        target: QubitSet | None = None,
     ) -> str:
         """
         Returns the openqasm string representation of the result type.
@@ -98,7 +100,7 @@ def _to_openqasm(
         Args:
             serialization_properties (OpenQASMSerializationProperties): The serialization properties
                 to use while serializing the object to the IR representation.
-            target (QubitSet): target qubit(s). Defaults to None.
+            target (QubitSet | None): target qubit(s). Defaults to None.
 
         Returns:
             str: Representing the openqasm representation of the result type.
diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py
index a59d9ff3..c6877262 100644
--- a/src/braket/circuits/result_type.py
+++ b/src/braket/circuits/result_type.py
@@ -67,7 +67,7 @@ def name(self) -> str:
     def to_ir(
         self,
         ir_type: IRType = IRType.JAQCD,
-        serialization_properties: SerializationProperties = None,
+        serialization_properties: SerializationProperties | None = None,
         **kwargs,
     ) -> Any:
         """Returns IR object of the result type
@@ -75,9 +75,9 @@ def to_ir(
         Args:
             ir_type(IRType) : The IRType to use for converting the result type object to its
                 IR representation. Defaults to IRType.JAQCD.
-            serialization_properties (SerializationProperties): 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.
+            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 result type
@@ -118,7 +118,9 @@ def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties
         raise NotImplementedError("to_openqasm has not been implemented yet.")
 
     def copy(
-        self, target_mapping: dict[QubitInput, QubitInput] = None, target: QubitSetInput = None
+        self,
+        target_mapping: dict[QubitInput, QubitInput] | None = None,
+        target: QubitSetInput | None = None,
     ) -> ResultType:
         """
         Return a shallow copy of the result type.
@@ -128,10 +130,10 @@ def copy(
             qubits. This is useful apply an instruction to a circuit and change the target qubits.
 
         Args:
-            target_mapping (dict[QubitInput, QubitInput]): A dictionary of
+            target_mapping (dict[QubitInput, QubitInput] | None): A dictionary of
                 qubit mappings to apply to the target. Key is the qubit in this `target` and the
                 value is what the key is changed to. Default = `None`.
-            target (QubitSetInput): Target qubits for the new instruction.
+            target (QubitSetInput | None): Target qubits for the new instruction.
 
         Returns:
             ResultType: A shallow copy of the result type.
@@ -188,14 +190,14 @@ class ObservableResultType(ResultType):
     """
 
     def __init__(
-        self, ascii_symbols: list[str], observable: Observable, target: QubitSetInput = None
+        self, ascii_symbols: list[str], observable: Observable, target: QubitSetInput | None = None
     ):
         """
         Args:
             ascii_symbols (list[str]): ASCII string symbols for the result type. This is used when
                 printing a diagram of circuits.
             observable (Observable): the observable for the result type
-            target (QubitSetInput): Target qubits that the
+            target (QubitSetInput | None): Target qubits that the
                 result type is requested for. Default is `None`, which means the observable must
                 only operate on 1 qubit and it will be applied to all qubits in parallel
 
@@ -287,8 +289,8 @@ def __init__(
         self,
         ascii_symbols: list[str],
         observable: Observable,
-        target: QubitSetInput = None,
-        parameters: list[Union[str, FreeParameter]] = None,
+        target: QubitSetInput | None = None,
+        parameters: list[Union[str, FreeParameter]] | None = None,
     ):
         super().__init__(ascii_symbols, observable, target)
 
@@ -303,10 +305,10 @@ def __init__(
             ascii_symbols (list[str]): ASCII string symbols for the result type. This is used when
                 printing a diagram of circuits.
             observable (Observable): the observable for the result type.
-            target (QubitSetInput): Target qubits that the result type is requested for.
+            target (QubitSetInput | None): Target qubits that the result type is requested for.
                 Default is `None`, which means the observable must only operate on 1
                 qubit and it will be applied to all qubits in parallel.
-            parameters (list[Union[str, FreeParameter]]): List of string inputs or
+            parameters (list[Union[str, FreeParameter]] | None): List of string inputs or
                 FreeParameter objects. These inputs will be used as parameters for
                 gradient calculation. Default: `all`.
 
diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py
index cfe21616..0a1a1c63 100644
--- a/src/braket/circuits/result_types.py
+++ b/src/braket/circuits/result_types.py
@@ -91,10 +91,10 @@ class DensityMatrix(ResultType):
     This is available on simulators only when `shots=0`.
     """
 
-    def __init__(self, target: QubitSetInput = None):
+    def __init__(self, target: QubitSetInput | None = None):
         """
         Args:
-            target (QubitSetInput): The target qubits
+            target (QubitSetInput | None): The target qubits
                 of the reduced density matrix. Default is `None`, and the
                 full density matrix is returned.
 
@@ -134,10 +134,10 @@ def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties
 
     @staticmethod
     @circuit.subroutine(register=True)
-    def density_matrix(target: QubitSetInput = None) -> ResultType:
+    def density_matrix(target: QubitSetInput | None = None) -> ResultType:
         """Registers this function into the circuit class.
         Args:
-            target (QubitSetInput): The target qubits
+            target (QubitSetInput | None): The target qubits
                 of the reduced density matrix. Default is `None`, and the
                 full density matrix is returned.
 
@@ -178,20 +178,20 @@ class AdjointGradient(ObservableParameterResultType):
     def __init__(
         self,
         observable: Observable,
-        target: list[QubitSetInput] = None,
-        parameters: list[Union[str, FreeParameter]] = None,
+        target: list[QubitSetInput] | None = None,
+        parameters: list[Union[str, FreeParameter]] | None = None,
     ):
         """
         Args:
             observable (Observable): The expectation value of this observable is the function
                 against which parameters in the gradient are differentiated.
-            target (list[QubitSetInput]): Target qubits that the result type is requested for.
-                Each term in the target list should have the same number of qubits as the
+            target (list[QubitSetInput] | None): Target qubits that the result type is requested
+                for. Each term in the target list should have the same number of qubits as the
                 corresponding term in the observable. Default is `None`, which means the
                 observable must operate only on 1 qubit and it is applied to all qubits
                 in parallel.
-            parameters (list[Union[str, FreeParameter]]): The free parameters in the circuit to
-                differentiate with respect to. Default: `all`.
+            parameters (list[Union[str, FreeParameter]] | None): The free parameters in the circuit
+                to differentiate with respect to. Default: `all`.
 
         Raises:
             ValueError: If the observable's qubit count does not equal the number of target
@@ -240,21 +240,21 @@ def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties
     @circuit.subroutine(register=True)
     def adjoint_gradient(
         observable: Observable,
-        target: list[QubitSetInput] = None,
-        parameters: list[Union[str, FreeParameter]] = None,
+        target: list[QubitSetInput] | None = None,
+        parameters: list[Union[str, FreeParameter]] | None = None,
     ) -> ResultType:
         """Registers this function into the circuit class.
 
         Args:
             observable (Observable): The expectation value of this observable is the function
                 against which parameters in the gradient are differentiated.
-            target (list[QubitSetInput]): Target qubits that the result type is requested for.
-                Each term in the target list should have the same number of qubits as the
+            target (list[QubitSetInput] | None): Target qubits that the result type is requested
+                for. Each term in the target list should have the same number of qubits as the
                 corresponding term in the observable. Default is `None`, which means the
                 observable must operate only on 1 qubit and it is applied to all qubits
                 in parallel.
-            parameters (list[Union[str, FreeParameter]]): The free parameters in the circuit to
-                differentiate with respect to. Default: `all`.
+            parameters (list[Union[str, FreeParameter]] | None): The free parameters in the circuit
+                to differentiate with respect to. Default: `all`.
 
         Returns:
             ResultType: gradient computed via adjoint differentiation as a requested result type
@@ -361,10 +361,10 @@ class Probability(ResultType):
     only on simulators and represents the exact result.
     """
 
-    def __init__(self, target: QubitSetInput = None):
+    def __init__(self, target: QubitSetInput | None = None):
         """
         Args:
-            target (QubitSetInput): The target qubits that the
+            target (QubitSetInput | None): The target qubits that the
                 result type is requested for. Default is `None`, which means all qubits for the
                 circuit.
 
@@ -404,11 +404,11 @@ def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties
 
     @staticmethod
     @circuit.subroutine(register=True)
-    def probability(target: QubitSetInput = None) -> ResultType:
+    def probability(target: QubitSetInput | None = None) -> ResultType:
         """Registers this function into the circuit class.
 
         Args:
-            target (QubitSetInput): The target qubits that the
+            target (QubitSetInput | None): The target qubits that the
                 result type is requested for. Default is `None`, which means all qubits for the
                 circuit.
 
@@ -451,11 +451,11 @@ class Expectation(ObservableResultType):
     See :mod:`braket.circuits.observables` module for all of the supported observables.
     """
 
-    def __init__(self, observable: Observable, target: QubitSetInput = None):
+    def __init__(self, observable: Observable, target: QubitSetInput | None = None):
         """
         Args:
             observable (Observable): the observable for the result type
-            target (QubitSetInput): Target qubits that the
+            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.
 
@@ -493,12 +493,12 @@ def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties
 
     @staticmethod
     @circuit.subroutine(register=True)
-    def expectation(observable: Observable, target: QubitSetInput = None) -> ResultType:
+    def expectation(observable: Observable, target: QubitSetInput | None = None) -> ResultType:
         """Registers this function into the circuit class.
 
         Args:
             observable (Observable): the observable for the result type
-            target (QubitSetInput): Target qubits that the
+            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.
 
@@ -526,11 +526,11 @@ class Sample(ObservableResultType):
     See :mod:`braket.circuits.observables` module for all of the supported observables.
     """
 
-    def __init__(self, observable: Observable, target: QubitSetInput = None):
+    def __init__(self, observable: Observable, target: QubitSetInput | None = None):
         """
         Args:
             observable (Observable): the observable for the result type
-            target (QubitSetInput): Target qubits that the
+            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.
 
@@ -568,12 +568,12 @@ def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties
 
     @staticmethod
     @circuit.subroutine(register=True)
-    def sample(observable: Observable, target: QubitSetInput = None) -> ResultType:
+    def sample(observable: Observable, target: QubitSetInput | None = None) -> ResultType:
         """Registers this function into the circuit class.
 
         Args:
             observable (Observable): the observable for the result type
-            target (QubitSetInput): Target qubits that the
+            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.
 
@@ -602,11 +602,11 @@ class Variance(ObservableResultType):
     See :mod:`braket.circuits.observables` module for all of the supported observables.
     """
 
-    def __init__(self, observable: Observable, target: QubitSetInput = None):
+    def __init__(self, observable: Observable, target: QubitSetInput | None = None):
         """
         Args:
             observable (Observable): the observable for the result type
-            target (QubitSetInput): Target qubits that the
+            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.
 
@@ -644,12 +644,12 @@ def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties
 
     @staticmethod
     @circuit.subroutine(register=True)
-    def variance(observable: Observable, target: QubitSetInput = None) -> ResultType:
+    def variance(observable: Observable, target: QubitSetInput | None = None) -> ResultType:
         """Registers this function into the circuit class.
 
         Args:
             observable (Observable): the observable for the result type
-            target (QubitSetInput): Target qubits that the
+            target (QubitSetInput | None): Target qubits that the
                 result type is requested for. Default is `None`, which means the observable must
                 only operate on 1 qubit and it will be applied to all qubits in parallel
 
diff --git a/src/braket/jobs/config.py b/src/braket/jobs/config.py
index 432e740e..a598388e 100644
--- a/src/braket/jobs/config.py
+++ b/src/braket/jobs/config.py
@@ -63,13 +63,13 @@ class S3DataSourceConfig:
     def __init__(
         self,
         s3_data: str,
-        content_type: str = None,
+        content_type: str | None = None,
     ):
         """Create a definition for input data used by a Braket Hybrid job.
 
         Args:
             s3_data (str): Defines the location of s3 data to train on.
-            content_type (str): MIME type of the input data (default: None).
+            content_type (str | None): MIME type of the input data (default: None).
         """
         self.config = {
             "dataSource": {
diff --git a/src/braket/jobs/data_persistence.py b/src/braket/jobs/data_persistence.py
index 6ef5a6d1..f2ec9b6f 100644
--- a/src/braket/jobs/data_persistence.py
+++ b/src/braket/jobs/data_persistence.py
@@ -65,7 +65,9 @@ def save_job_checkpoint(
         f.write(persisted_data.json())
 
 
-def load_job_checkpoint(job_name: str = None, checkpoint_file_suffix: str = "") -> dict[str, Any]:
+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
     file that ends with the `checkpoint_file_suffix`. The `job_name` can refer to any job whose
@@ -78,7 +80,7 @@ def load_job_checkpoint(job_name: str = None, checkpoint_file_suffix: str = "")
 
 
     Args:
-        job_name (str): str that specifies the name of the job whose checkpoints
+        job_name (str | None): str that specifies the name of the job whose checkpoints
             are to be loaded. Default: current job name.
 
         checkpoint_file_suffix (str): str specifying the file suffix that is used to
@@ -110,7 +112,7 @@ def load_job_checkpoint(job_name: str = None, checkpoint_file_suffix: str = "")
         return deserialized_data
 
 
-def _load_persisted_data(filename: str | Path = None) -> PersistedJobData:
+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:
@@ -122,12 +124,12 @@ def _load_persisted_data(filename: str | Path = None) -> PersistedJobData:
         )
 
 
-def load_job_result(filename: str | Path = None) -> dict[str, Any]:
+def load_job_result(filename: str | Path | None = None) -> dict[str, Any]:
     """
     Loads job result of currently running job.
 
     Args:
-        filename (str | Path): Location of job results. Default `results.json` in job
+        filename (str | Path | None): Location of job results. Default `results.json` in job
             results directory in a job instance or in working directory locally. This file
             must be in the format used by `save_job_result`.
 
@@ -141,7 +143,7 @@ def load_job_result(filename: str | Path = None) -> dict[str, Any]:
 
 def save_job_result(
     result_data: dict[str, Any] | Any,
-    data_format: PersistedJobDataFormat = None,
+    data_format: PersistedJobDataFormat | None = None,
 ) -> None:
     """
     Saves the `result_data` to the local output directory that is specified by the container
@@ -151,12 +153,11 @@ def save_job_result(
     Note: This function for storing the results is only for use inside the job container
           as it writes data to directories and references env variables set in the containers.
 
-
     Args:
         result_data (dict[str, Any] | Any): Dict that specifies the result data to be
             persisted. If result data is not a dict, then it will be wrapped as
             `{"result": result_data}`.
-        data_format (PersistedJobDataFormat): The data format used to serialize the
+        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.
     """
diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py
index da0b8436..ae17c271 100644
--- a/src/braket/jobs/hybrid_job.py
+++ b/src/braket/jobs/hybrid_job.py
@@ -45,23 +45,23 @@
 
 def hybrid_job(
     *,
-    device: str,
-    include_modules: str | ModuleType | Iterable[str | ModuleType] = None,
-    dependencies: str | Path | list[str] = None,
+    device: str | None,
+    include_modules: str | ModuleType | Iterable[str | ModuleType] | None = None,
+    dependencies: str | Path | list[str] | None = None,
     local: bool = False,
-    job_name: str = None,
-    image_uri: str = None,
-    input_data: str | dict | S3DataSourceConfig = None,
+    job_name: str | None = None,
+    image_uri: str | None = None,
+    input_data: str | dict | S3DataSourceConfig | None = None,
     wait_until_complete: bool = False,
-    instance_config: InstanceConfig = None,
-    distribution: str = None,
-    copy_checkpoints_from_job: str = None,
-    checkpoint_config: CheckpointConfig = None,
-    role_arn: str = None,
-    stopping_condition: StoppingCondition = None,
-    output_data_config: OutputDataConfig = None,
-    aws_session: AwsSession = None,
-    tags: dict[str, str] = None,
+    instance_config: InstanceConfig | None = None,
+    distribution: str | None = None,
+    copy_checkpoints_from_job: str | None = None,
+    checkpoint_config: CheckpointConfig | None = None,
+    role_arn: str | None = None,
+    stopping_condition: StoppingCondition | None = None,
+    output_data_config: OutputDataConfig | None = None,
+    aws_session: AwsSession | None = None,
+    tags: dict[str, str] | None = None,
     logger: Logger = getLogger(__name__),
 ) -> Callable:
     """Defines a hybrid job by decorating the entry point function. The job will be created
@@ -73,34 +73,34 @@ def hybrid_job(
     `copy_checkpoints_from_job`, `stopping_condition`, `tags`, and `logger`.
 
     Args:
-        device (str): Device ARN of the QPU device that receives priority quantum
+        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
             queue so that only one hybrid job is running at a time. The device string is accessible
             in the hybrid job instance as the environment variable "AMZN_BRAKET_DEVICE_ARN".
             When using embedded simulators, you may provide the device argument as string of the
             form: "local:/" or `None`.
 
-        include_modules (str | ModuleType | Iterable[str | ModuleType]): Either a
+        include_modules (str | ModuleType | Iterable[str | ModuleType] | None): Either a
             single module or module name or a list of module or module names referring to local
             modules to be included. Any references to members of these modules in the hybrid job
             algorithm code will be serialized as part of the algorithm code. Default: `[]`
 
-        dependencies (str | Path | list[str]): Path (absolute or relative) to a requirements.txt
-            file, or alternatively a list of strings, with each string being a `requirement
-            specifier `_, to be used for the hybrid job.
 
         local (bool): Whether to use local mode for the hybrid job. Default: `False`
 
-        job_name (str): A string that specifies the name with which the job is created.
+        job_name (str | None): A string that specifies the name with which the job is created.
             Allowed pattern for job name: `^[a-zA-Z0-9](-*[a-zA-Z0-9]){0,50}$`. Defaults to
             f'{decorated-function-name}-{timestamp}'.
 
-        image_uri (str): A str that specifies the ECR image to use for executing the job.
+        image_uri (str | None): A str that specifies the ECR image to use for executing the job.
             `retrieve_image()` function may be used for retrieving the ECR image URIs
             for the containers supported by Braket. Default: ``.
 
-        input_data (str | dict | S3DataSourceConfig): Information about the training
+        input_data (str | dict | S3DataSourceConfig | None): Information about the training
             data. Dictionary maps channel names to local paths or S3 URIs. Contents found
             at any local paths will be uploaded to S3 at
             f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}'. If a local
@@ -112,41 +112,41 @@ def hybrid_job(
             This would tail the job logs as it waits. Otherwise `False`. Ignored if using
             local mode. Default: `False`.
 
-        instance_config (InstanceConfig): Configuration of the instance(s) for running the
+        instance_config (InstanceConfig | None): Configuration of the instance(s) for running the
             classical code for the hybrid job. Default:
             `InstanceConfig(instanceType='ml.m5.large', instanceCount=1, volumeSizeInGB=30)`.
 
-        distribution (str): A str that specifies how the job should be distributed.
+        distribution (str | None): A str that specifies how the job should be distributed.
             If set to "data_parallel", the hyperparameters for the job will be set to use data
             parallelism features for PyTorch or TensorFlow. Default: `None`.
 
-        copy_checkpoints_from_job (str): A str that specifies the job ARN whose
+        copy_checkpoints_from_job (str | None): A str that specifies the job ARN whose
             checkpoint you want to use in the current job. Specifying this value will copy
             over the checkpoint data from `use_checkpoints_from_job`'s checkpoint_config
             s3Uri to the current job's checkpoint_config s3Uri, making it available at
             checkpoint_config.localPath during the job execution. Default: `None`
 
-        checkpoint_config (CheckpointConfig): Configuration that specifies the
+        checkpoint_config (CheckpointConfig | None): Configuration that specifies the
             location where checkpoint data is stored.
             Default: `CheckpointConfig(localPath='/opt/jobs/checkpoints',
             s3Uri=f's3://{default_bucket_name}/jobs/{job_name}/checkpoints')`.
 
-        role_arn (str): A str providing the IAM role ARN used to execute the
+        role_arn (str | None): A str providing the IAM role ARN used to execute the
             script. Default: IAM role returned by AwsSession's `get_default_jobs_role()`.
 
-        stopping_condition (StoppingCondition): The maximum length of time, in seconds,
+        stopping_condition (StoppingCondition | None): The maximum length of time, in seconds,
             and the maximum number of tasks that a job can run before being forcefully stopped.
             Default: StoppingCondition(maxRuntimeInSeconds=5 * 24 * 60 * 60).
 
-        output_data_config (OutputDataConfig): Specifies the location for the output of
+        output_data_config (OutputDataConfig | None): Specifies the location for the output of
             the job.
             Default: `OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data',
             kmsKeyId=None)`.
 
-        aws_session (AwsSession): AwsSession for connecting to AWS Services.
+        aws_session (AwsSession | None): AwsSession for connecting to AWS Services.
             Default: AwsSession()
 
-        tags (dict[str, str]): Dict specifying the key-value pairs for tagging this job.
+        tags (dict[str, str] | None): Dict specifying the key-value pairs for tagging this job.
             Default: {}.
 
         logger (Logger): Logger object with which to write logs, such as task statuses
diff --git a/src/braket/jobs/local/local_job.py b/src/braket/jobs/local/local_job.py
index dc9b09e8..f516d969 100644
--- a/src/braket/jobs/local/local_job.py
+++ b/src/braket/jobs/local/local_job.py
@@ -38,16 +38,16 @@ def create(
         cls,
         device: str,
         source_module: str,
-        entry_point: str = None,
-        image_uri: str = None,
-        job_name: str = None,
-        code_location: str = None,
-        role_arn: str = None,
-        hyperparameters: dict[str, Any] = None,
-        input_data: str | dict | S3DataSourceConfig = None,
-        output_data_config: OutputDataConfig = None,
-        checkpoint_config: CheckpointConfig = None,
-        aws_session: AwsSession = None,
+        entry_point: str | None = None,
+        image_uri: str | None = None,
+        job_name: str | None = None,
+        code_location: str | None = None,
+        role_arn: str | None = None,
+        hyperparameters: dict[str, Any] | None = None,
+        input_data: str | dict | S3DataSourceConfig | None = None,
+        output_data_config: OutputDataConfig | None = None,
+        checkpoint_config: CheckpointConfig | None = None,
+        aws_session: AwsSession | None = None,
         local_container_update: bool = True,
     ) -> LocalQuantumJob:
         """Creates and runs hybrid job by setting up and running the customer script in a local
@@ -65,32 +65,34 @@ def create(
                 tarred and uploaded. If `source_module` is an S3 URI, it must point to a
                 tar.gz file. Otherwise, source_module may be a file or directory.
 
-            entry_point (str): A str that specifies the entry point of the hybrid job, relative to
-                the source module. The entry point must be in the format
+            entry_point (str | None): A str that specifies the entry point of the hybrid job,
+                relative to the source module. The entry point must be in the format
                 `importable.module` or `importable.module:callable`. For example,
                 `source_module.submodule:start_here` indicates the `start_here` function
                 contained in `source_module.submodule`. If source_module is an S3 URI,
                 entry point must be given. Default: source_module's name
 
-            image_uri (str): A str that specifies the ECR image to use for executing the hybrid job.
-                `image_uris.retrieve_image()` function may be used for retrieving the ECR image URIs
-                for the containers supported by Braket. Default = ``.
+            image_uri (str | None): A str that specifies the ECR image to use for executing the
+                hybrid job. `image_uris.retrieve_image()` function may be used for retrieving the
+                ECR image URIs for the containers supported by Braket.
+                Default = ``.
 
-            job_name (str): A str that specifies the name with which the hybrid job is created.
+            job_name (str | None): A str that specifies the name with which the hybrid job is
+                created.
                 Default: f'{image_uri_type}-{timestamp}'.
 
-            code_location (str): The S3 prefix URI where custom code will be uploaded.
+            code_location (str | None): The S3 prefix URI where custom code will be uploaded.
                 Default: f's3://{default_bucket_name}/jobs/{job_name}/script'.
 
-            role_arn (str): This field is currently not used for local hybrid jobs. Local hybrid
-                jobs will use the current role's credentials. This may be subject to change.
+            role_arn (str | None): This field is currently not used for local hybrid jobs. Local
+                hybrid jobs will use the current role's credentials. This may be subject to change.
 
-            hyperparameters (dict[str, Any]): Hyperparameters accessible to the hybrid job.
+            hyperparameters (dict[str, Any] | None): Hyperparameters accessible to the hybrid job.
                 The hyperparameters are made accessible as a Dict[str, str] to the hybrid job.
                 For convenience, this accepts other types for keys and values, but `str()`
                 is called to convert them before being passed on. Default: None.
 
-            input_data (str | dict | S3DataSourceConfig): Information about the training
+            input_data (str | dict | S3DataSourceConfig | None): Information about the training
                 data. Dictionary maps channel names to local paths or S3 URIs. Contents found
                 at any local paths will be uploaded to S3 at
                 f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local
@@ -98,17 +100,17 @@ def create(
                 channel name "input".
                 Default: {}.
 
-            output_data_config (OutputDataConfig): Specifies the location for the output of the
-                hybrid job.
+            output_data_config (OutputDataConfig | None): Specifies the location for the output of
+                the hybrid job.
                 Default: OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data',
                 kmsKeyId=None).
 
-            checkpoint_config (CheckpointConfig): Configuration that specifies the location where
-                checkpoint data is stored.
+            checkpoint_config (CheckpointConfig | None): Configuration that specifies the location
+                where checkpoint data is stored.
                 Default: CheckpointConfig(localPath='/opt/jobs/checkpoints',
                 s3Uri=f's3://{default_bucket_name}/jobs/{job_name}/checkpoints').
 
-            aws_session (AwsSession): AwsSession for connecting to AWS Services.
+            aws_session (AwsSession | None): AwsSession for connecting to AWS Services.
                 Default: AwsSession()
 
             local_container_update (bool): Perform an update, if available, from ECR to the local
@@ -163,11 +165,12 @@ def create(
             run_log = container.run_log
         return LocalQuantumJob(f"local:job/{job_name}", run_log)
 
-    def __init__(self, arn: str, run_log: str = None):
+    def __init__(self, arn: str, run_log: str | None = None):
         """
         Args:
             arn (str): The ARN of the hybrid job.
-            run_log (str): 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.
         """
         if not arn.startswith("local:job/"):
             raise ValueError(f"Arn {arn} is not a valid local job arn")
@@ -235,14 +238,14 @@ def cancel(self) -> str:
 
     def download_result(
         self,
-        extract_to: str = None,
+        extract_to: str | None = None,
         poll_timeout_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_TIMEOUT,
         poll_interval_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_INTERVAL,
     ) -> None:
         """When running the hybrid job in local mode, results are automatically stored locally.
 
         Args:
-            extract_to (str): The directory to which the results are extracted. The results
+            extract_to (str | None): The directory to which the results are extracted. The results
                 are extracted to a folder titled with the hybrid job name within this directory.
                 Default= `Current working directory`.
             poll_timeout_seconds (float): The polling timeout, in seconds, for `result()`.
diff --git a/src/braket/jobs/local/local_job_container.py b/src/braket/jobs/local/local_job_container.py
index f924db47..ea562562 100644
--- a/src/braket/jobs/local/local_job_container.py
+++ b/src/braket/jobs/local/local_job_container.py
@@ -10,6 +10,8 @@
 # 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
 import re
 import subprocess
@@ -29,7 +31,7 @@ class _LocalJobContainer(object):
     def __init__(
         self,
         image_uri: str,
-        aws_session: AwsSession = None,
+        aws_session: AwsSession | None = None,
         logger: Logger = getLogger(__name__),
         force_update: bool = False,
     ):
@@ -39,7 +41,7 @@ def __init__(
         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): AwsSession for connecting to AWS Services.
+            aws_session (AwsSession | None): AwsSession for connecting to AWS Services.
                 Default: AwsSession()
             logger (Logger): Logger object with which to write logs.
                 Default: `getLogger(__name__)`
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 94ed5499..b32cd6b9 100644
--- a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py
+++ b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.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 time
 from logging import Logger, getLogger
 from typing import Any, Dict, List, Optional, Union
@@ -133,8 +135,8 @@ def get_metrics_for_job(
         job_name: str,
         metric_type: MetricType = MetricType.TIMESTAMP,
         statistic: MetricStatistic = MetricStatistic.MAX,
-        job_start_time: int = None,
-        job_end_time: int = None,
+        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.
@@ -145,10 +147,10 @@ def get_metrics_for_job(
             metric_type (MetricType): The type of metrics to get. Default is MetricType.TIMESTAMP.
             statistic (MetricStatistic): The statistic to determine which metric value to use
                 when there is a conflict. Default is MetricStatistic.MAX.
-            job_start_time (int): The time when the hybrid job started.
+            job_start_time (int | None): The time when the hybrid job started.
                 Default: 3 hours before job_end_time.
-            job_end_time (int): If the hybrid job is complete, this should be the time at which the
-                hybrid job finished. Default: current 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.
 
         Returns:
             Dict[str, List[Union[str, float, int]]] : The metrics data, where the keys
diff --git a/src/braket/jobs/quantum_job.py b/src/braket/jobs/quantum_job.py
index f022d28a..a8411899 100644
--- a/src/braket/jobs/quantum_job.py
+++ b/src/braket/jobs/quantum_job.py
@@ -10,6 +10,8 @@
 # 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 typing import Any, Dict, List
 
@@ -170,7 +172,7 @@ def result(
     @abstractmethod
     def download_result(
         self,
-        extract_to: str = None,
+        extract_to: str | None = None,
         poll_timeout_seconds: float = DEFAULT_RESULTS_POLL_TIMEOUT,
         poll_interval_seconds: float = DEFAULT_RESULTS_POLL_INTERVAL,
     ) -> None:
@@ -179,7 +181,7 @@ def download_result(
         the results are extracted to the current directory.
 
         Args:
-            extract_to (str): The directory to which the results are extracted. The results
+            extract_to (str | None): The directory to which the results are extracted. The results
                 are extracted to a folder titled with the hybrid job name within this directory.
                 Default= `Current working directory`.
 
diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py
index e34df050..458a56b4 100644
--- a/src/braket/jobs/quantum_job_creation.py
+++ b/src/braket/jobs/quantum_job_creation.py
@@ -40,21 +40,21 @@
 def prepare_quantum_job(
     device: str,
     source_module: str,
-    entry_point: str = None,
-    image_uri: str = None,
-    job_name: str = None,
-    code_location: str = None,
-    role_arn: str = None,
-    hyperparameters: dict[str, Any] = None,
-    input_data: str | dict | S3DataSourceConfig = None,
-    instance_config: InstanceConfig = None,
-    distribution: str = None,
-    stopping_condition: StoppingCondition = None,
-    output_data_config: OutputDataConfig = None,
-    copy_checkpoints_from_job: str = None,
-    checkpoint_config: CheckpointConfig = None,
-    aws_session: AwsSession = None,
-    tags: dict[str, str] = None,
+    entry_point: str | None = None,
+    image_uri: str | None = None,
+    job_name: str | None = None,
+    code_location: str | None = None,
+    role_arn: str | None = None,
+    hyperparameters: dict[str, Any] | None = None,
+    input_data: str | dict | S3DataSourceConfig | None = None,
+    instance_config: InstanceConfig | None = None,
+    distribution: str | None = None,
+    stopping_condition: StoppingCondition | None = None,
+    output_data_config: OutputDataConfig | None = None,
+    copy_checkpoints_from_job: str | None = None,
+    checkpoint_config: CheckpointConfig | None = None,
+    aws_session: AwsSession | None = None,
+    tags: dict[str, str] | None = None,
 ) -> dict:
     """Creates a hybrid job by invoking the Braket CreateJob API.
 
@@ -70,34 +70,34 @@ def prepare_quantum_job(
             tarred and uploaded. If `source_module` is an S3 URI, it must point to a
             tar.gz file. Otherwise, source_module may be a file or directory.
 
-        entry_point (str): A str that specifies the entry point of the hybrid job, relative to
-            the source module. The entry point must be in the format
+        entry_point (str | None): A str that specifies the entry point of the hybrid job, relative
+            to the source module. The entry point must be in the format
             `importable.module` or `importable.module:callable`. For example,
             `source_module.submodule:start_here` indicates the `start_here` function
             contained in `source_module.submodule`. If source_module is an S3 URI,
             entry point must be given. Default: source_module's name
 
-        image_uri (str): A str that specifies the ECR image to use for executing the hybrid job.
-            `image_uris.retrieve_image()` function may be used for retrieving the ECR image URIs
+        image_uri (str | None): A str that specifies the ECR image to use for executing the hybrid
+            job.`image_uris.retrieve_image()` function may be used for retrieving the ECR image URIs
             for the containers supported by Braket. Default = ``.
 
-        job_name (str): A str that specifies the name with which the hybrid job is created. The
-            hybrid job
-            name must be between 0 and 50 characters long and cannot contain underscores.
+        job_name (str | None): A str that specifies the name with which the hybrid job is created.
+            The hybrid job name must be between 0 and 50 characters long and cannot contain
+            underscores.
             Default: f'{image_uri_type}-{timestamp}'.
 
-        code_location (str): The S3 prefix URI where custom code will be uploaded.
+        code_location (str | None): The S3 prefix URI where custom code will be uploaded.
             Default: f's3://{default_bucket_name}/jobs/{job_name}/script'.
 
-        role_arn (str): A str providing the IAM role ARN used to execute the
+        role_arn (str | None): A str providing the IAM role ARN used to execute the
             script. Default: IAM role returned by AwsSession's `get_default_jobs_role()`.
 
-        hyperparameters (dict[str, Any]): Hyperparameters accessible to the hybrid job.
+        hyperparameters (dict[str, Any] | None): Hyperparameters accessible to the hybrid job.
             The hyperparameters are made accessible as a Dict[str, str] to the hybrid job.
             For convenience, this accepts other types for keys and values, but `str()`
             is called to convert them before being passed on. Default: None.
 
-        input_data (str | dict | S3DataSourceConfig): Information about the training
+        input_data (str | dict | S3DataSourceConfig | None): Information about the training
             data. Dictionary maps channel names to local paths or S3 URIs. Contents found
             at any local paths will be uploaded to S3 at
             f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local
@@ -105,38 +105,39 @@ def prepare_quantum_job(
             channel name "input".
             Default: {}.
 
-        instance_config (InstanceConfig): Configuration of the instance(s) for running the
+        instance_config (InstanceConfig | None): Configuration of the instance(s) for running the
             classical code for the hybrid job. Defaults to
             `InstanceConfig(instanceType='ml.m5.large', instanceCount=1, volumeSizeInGB=30)`.
 
-        distribution (str): A str that specifies how the hybrid job should be distributed. If set to
-            "data_parallel", the hyperparameters for the hybrid job will be set to use data
-            parallelism features for PyTorch or TensorFlow. Default: None.
+        distribution (str | None): A str that specifies how the hybrid job should be distributed.
+            If set to "data_parallel", the hyperparameters for the hybrid job will be set to use
+            data parallelism features for PyTorch or TensorFlow. Default: None.
 
-        stopping_condition (StoppingCondition): The maximum length of time, in seconds,
+        stopping_condition (StoppingCondition | None): The maximum length of time, in seconds,
             and the maximum number of quantum tasks that a hybrid job can run before being
             forcefully stopped. Default: StoppingCondition(maxRuntimeInSeconds=5 * 24 * 60 * 60).
 
-        output_data_config (OutputDataConfig): Specifies the location for the output of the hybrid
-            job.
+        output_data_config (OutputDataConfig | None): Specifies the location for the output of the
+            hybrid job.
             Default: OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data',
             kmsKeyId=None).
 
-        copy_checkpoints_from_job (str): A str that specifies the hybrid job ARN whose checkpoint
-            you want to use in the current hybrid job. Specifying this value will copy over the
-            checkpoint data from `use_checkpoints_from_job`'s checkpoint_config s3Uri to the current
-            hybrid job's checkpoint_config s3Uri, making it available at checkpoint_config.localPath
-            during the hybrid job execution. Default: None
+        copy_checkpoints_from_job (str | None): A str that specifies the hybrid job ARN whose
+            checkpoint you want to use in the current hybrid job. Specifying this value will copy
+            over the checkpoint data from `use_checkpoints_from_job`'s checkpoint_config s3Uri to
+            the current hybrid job's checkpoint_config s3Uri, making it available at
+            checkpoint_config.localPath during the hybrid job execution. Default: None
 
-        checkpoint_config (CheckpointConfig): Configuration that specifies the location where
+        checkpoint_config (CheckpointConfig | None): Configuration that specifies the location where
             checkpoint data is stored.
             Default: CheckpointConfig(localPath='/opt/jobs/checkpoints',
             s3Uri=f's3://{default_bucket_name}/jobs/{job_name}/checkpoints').
 
-        aws_session (AwsSession): AwsSession for connecting to AWS Services.
+        aws_session (AwsSession | None): AwsSession for connecting to AWS Services.
             Default: AwsSession()
 
-        tags (dict[str, str]): Dict specifying the key-value pairs for tagging this hybrid job.
+        tags (dict[str, str] | None): Dict specifying the key-value pairs for tagging this
+            hybrid job.
             Default: {}.
 
     Returns:
@@ -232,13 +233,13 @@ def prepare_quantum_job(
     return create_job_kwargs
 
 
-def _generate_default_job_name(image_uri: str = None, func: Callable = None) -> str:
+def _generate_default_job_name(image_uri: str | None = None, func: Callable | None = None) -> str:
     """
     Generate default job name using the image uri and entrypoint function.
 
     Args:
-        image_uri (str): URI for the image container.
-        func (Callable): The entry point function.
+        image_uri (str | None): URI for the image container.
+        func (Callable | None): The entry point function.
 
     Returns:
         str: Hybrid job name.
diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py
index e0808316..bb783c69 100644
--- a/src/braket/pulse/pulse_sequence.py
+++ b/src/braket/pulse/pulse_sequence.py
@@ -399,12 +399,12 @@ 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, **kwargs) -> PulseSequence:
+    def __call__(self, arg: Any | None = None, **kwargs) -> PulseSequence:
         """
         Implements the call function to easily make a bound PulseSequence.
 
         Args:
-            arg (Any): A value to bind to all parameters. Defaults to None and
+            arg (Any | None): A value to bind to all parameters. Defaults to None and
                 can be overridden if the parameter is in kwargs.
 
         Returns:
diff --git a/src/braket/registers/qubit_set.py b/src/braket/registers/qubit_set.py
index 938d7fb5..0f9d0a7a 100644
--- a/src/braket/registers/qubit_set.py
+++ b/src/braket/registers/qubit_set.py
@@ -40,10 +40,11 @@ class QubitSet(IndexedSet):
         mutating this object.
     """
 
-    def __init__(self, qubits: QubitSetInput = None):
+    def __init__(self, qubits: QubitSetInput | None = None):
         """
         Args:
-            qubits (QubitSetInput): Qubits to be included in the `QubitSet`. Default is `None`.
+            qubits (QubitSetInput | None): Qubits to be included in the `QubitSet`.
+                Default is `None`.
 
         Examples:
             >>> qubits = QubitSet([0, 1])

From 3dd806e8ff7d470de53c8a7c9b9f72872048f7e9 Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Fri, 3 Nov 2023 14:42:25 -0400
Subject: [PATCH 0938/1165] Update docstring type hints

---
 src/braket/experimental/autoqasm/api.py                    | 4 ++--
 src/braket/experimental/autoqasm/operators/control_flow.py | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py
index c795186a..e002edd0 100644
--- a/src/braket/experimental/autoqasm/api.py
+++ b/src/braket/experimental/autoqasm/api.py
@@ -93,7 +93,7 @@ def gate(*args) -> Callable[[Any], None]:
     """Decorator that converts a function into a callable gate definition.
 
     Returns:
-        Callable[[Any],]: A callable which can be used as a custom gate inside an
+        Callable[[Any], None]: A callable which can be used as a custom gate inside an
         aq.function or inside another aq.gate.
     """
     return _function_wrapper(*args, converter_callback=_convert_gate)
@@ -511,7 +511,7 @@ def _wrap_for_oqpy_gate(
         options (converter.ConversionOptions): Converter options.
 
     Returns:
-        Callable[...,]: The modified function for use with oqpy.gate.
+        Callable[..., None]: The modified function for use with oqpy.gate.
     """
 
     def _func(*args: Any) -> None:
diff --git a/src/braket/experimental/autoqasm/operators/control_flow.py b/src/braket/experimental/autoqasm/operators/control_flow.py
index 8f454fd0..a510f6a5 100644
--- a/src/braket/experimental/autoqasm/operators/control_flow.py
+++ b/src/braket/experimental/autoqasm/operators/control_flow.py
@@ -37,7 +37,7 @@ def for_stmt(
     Args:
         iter (Union[Iterable, Range]): The iterable to be looped over.
         extra_test (Optional[Callable[[], Any]]): A function to cause the loop to break if true.
-        body (Callable[[Any],]): The body of the for loop.
+        body (Callable[[Any], None]): The body of the for loop.
         get_state (Any): Unused.
         set_state (Any): Unused.
         symbol_names (Any): Unused.
@@ -85,7 +85,7 @@ def while_stmt(
 
     Args:
         test (Callable[[], Any]): The condition of the while loop.
-        body (Callable[[],]): The body of the while loop.
+        body (Callable[[], None]): The body of the while loop.
         get_state (Any): Unused.
         set_state (Any): Unused.
         symbol_names (Any): Unused.

From 4446f4bd03f29d1f7d09e433e2727f13a55b5077 Mon Sep 17 00:00:00 2001
From: Stephen Face <60493521+shpface@users.noreply.github.com>
Date: Fri, 3 Nov 2023 16:22:05 -0400
Subject: [PATCH 0939/1165] Fix: Invalidate importlib cache for decorator job
 (#783)

---
 src/braket/jobs/quantum_job_creation.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py
index 458a56b4..b935de7e 100644
--- a/src/braket/jobs/quantum_job_creation.py
+++ b/src/braket/jobs/quantum_job_creation.py
@@ -330,6 +330,7 @@ def _validate_entry_point(source_module_path: Path, entry_point: str) -> None:
     sys.path.append(str(source_module_path.parent))
     try:
         # second argument allows relative imports
+        importlib.invalidate_caches()
         module = importlib.util.find_spec(importable, source_module_path.stem)
         assert module is not None
     # if entry point is nested (ie contains '.'), parent modules are imported

From 2dd29dd8e1ea2a2c6e6a830fcb17a748892e59c9 Mon Sep 17 00:00:00 2001
From: ci 
Date: Mon, 6 Nov 2023 16:17:07 +0000
Subject: [PATCH 0940/1165] prepare release v1.61.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 7a17eb6b..5ddb9e4c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
 # Changelog
 
+## v1.61.0 (2023-11-06)
+
+### Features
+
+ * simplify entry point wrapper
+
+### Bug Fixes and Other Changes
+
+ * fixing some type hints for optional params
+
 ## v1.60.2 (2023-11-01)
 
 ### Bug Fixes and Other Changes
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index 20b34811..83b9e8b0 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.60.3.dev0"
+__version__ = "1.61.0"

From 9dfb98cc7421fda973d878e80f8ed04918f83d3d Mon Sep 17 00:00:00 2001
From: ci 
Date: Mon, 6 Nov 2023 16:17:07 +0000
Subject: [PATCH 0941/1165] update development version to v1.61.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 83b9e8b0..803ab75f 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.61.0"
+__version__ = "1.61.1.dev0"

From 9bfd34c4fa01d8df5fbbbf969d639990f88f919f Mon Sep 17 00:00:00 2001
From: Lauren Capelluto 
Date: Mon, 6 Nov 2023 12:55:58 -0500
Subject: [PATCH 0942/1165] feature: support FreeParameter binding via
 `make_bound_program` (#779)

* feature: Add support for binding FreeParameters in AutoQASM programs.
* Move IO declaration logic into Program
* Allow "strict" option for parameter binding
---
 src/braket/experimental/autoqasm/api.py       |  12 +-
 .../experimental/autoqasm/program/program.py  |  35 +++
 .../experimental/autoqasm/test_parameters.py  | 199 +++++++++++++++++-
 3 files changed, 234 insertions(+), 12 deletions(-)

diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py
index e002edd0..bd19a1e4 100644
--- a/src/braket/experimental/autoqasm/api.py
+++ b/src/braket/experimental/autoqasm/api.py
@@ -208,21 +208,11 @@ def _convert_main(
 
         # Modify program to add global declarations if necessary
         _add_qubit_declaration(program_conversion_context)
-        _add_io_declarations(program_conversion_context)
+        program_conversion_context.add_io_declarations()
 
     return program_conversion_context.make_program()
 
 
-def _add_io_declarations(program_conversion_context: aq_program.ProgramConversionContext) -> None:
-    root_oqpy_program = program_conversion_context.get_oqpy_program(
-        scope=aq_program.ProgramScope.MAIN
-    )
-    root_oqpy_program.declare(
-        program_conversion_context.get_free_parameters(),
-        to_beginning=True,
-    )
-
-
 def _add_qubit_declaration(program_conversion_context: aq_program.ProgramConversionContext) -> None:
     """Modify the program to include a global qubit register declaration.
 
diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py
index e5c29cbc..bd7416bc 100644
--- a/src/braket/experimental/autoqasm/program/program.py
+++ b/src/braket/experimental/autoqasm/program/program.py
@@ -15,6 +15,7 @@
 from __future__ import annotations
 
 import contextlib
+import copy
 import threading
 from collections.abc import Callable, Iterable
 from dataclasses import dataclass
@@ -123,6 +124,34 @@ def with_calibrations(self, gate_calibrations: Union[Callable, list[Callable]])
         combined_oqpy_program += self._oqpy_program
         return Program(combined_oqpy_program, has_pulse_control=True)
 
+    def make_bound_program(self, param_values: dict[str, float], strict: bool = False) -> Program:
+        """Binds FreeParameters based upon their name and values passed in.
+
+        Args:
+            param_values (dict[str, float]): A mapping of FreeParameter names
+                to a value to assign to them.
+            strict (bool): If True, raises a ValueError if any of the FreeParameters
+                in param_values do not appear in the program. False by default.
+
+        Raises:
+            ValueError: If a parameter name is given which does not appear in the program.
+
+        Returns:
+            Program: Returns a program with all present parameters fixed to their respective
+            values.
+        """
+        # Copy the program so that we don't modify the original program
+        bound_oqpy_program = copy.deepcopy(self._oqpy_program)
+        for name, value in param_values.items():
+            if name in bound_oqpy_program.undeclared_vars:
+                target = bound_oqpy_program.undeclared_vars[name]
+                assert target.init_expression == "input", "Only free parameters can be bound."
+                target.init_expression = value
+            elif strict:
+                raise ValueError(f"No parameter in the program named: {name}")
+
+        return Program(bound_oqpy_program, self._has_pulse_control)
+
     def to_ir(
         self,
         ir_type: IRType = IRType.OPENQASM,
@@ -313,6 +342,12 @@ def get_free_parameters(self) -> list[oqpy.FloatVar]:
         """Return a list of named oqpy.Vars that are used as free parameters in the program."""
         return list(self._free_parameters.values())
 
+    def add_io_declarations(self) -> None:
+        """Add input and output declaration statements to the program."""
+        root_oqpy_program = self.get_oqpy_program(scope=ProgramScope.MAIN)
+        for parameter in self.get_free_parameters():
+            root_oqpy_program._add_var(parameter)
+
     def get_target_device(self) -> Optional[Device]:
         """Return the target device for the program, as specified by the user.
         Returns None if the user did not specify a target device.
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py
index 26943c2b..0a43b32e 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py
@@ -14,6 +14,7 @@
 """AutoQASM tests for parameter support."""
 
 import numpy as np
+import pytest
 
 import braket.experimental.autoqasm as aq
 from braket.circuits import FreeParameter
@@ -282,10 +283,206 @@ def my_program():
         rx("$1", FreeParameter("theta"))
 
     expected = """OPENQASM 3.0;
+input float[64] theta;
 defcal rx(angle[32] angle) $1 {
     delay[(angle) * 1s] $1;
 }
-input float[64] theta;
 rx(theta) $1;"""
     qasm = my_program().with_calibrations(cal_1).to_ir()
     assert qasm == expected
+
+
+def test_bind_parameters():
+    """Test binding FreeParameters to concrete values."""
+
+    @aq.main
+    def parametric(theta: float):
+        rx(0, theta)
+        measure(0)
+
+    prog = parametric(FreeParameter("alpha"))
+    unbound_expected = """OPENQASM 3.0;
+input float[64] alpha;
+qubit[1] __qubits__;
+rx(alpha) __qubits__[0];
+bit __bit_0__;
+__bit_0__ = measure __qubits__[0];"""
+
+    bound_template = """OPENQASM 3.0;
+float[64] alpha = {};
+qubit[1] __qubits__;
+rx(alpha) __qubits__[0];
+bit __bit_0__;
+__bit_0__ = measure __qubits__[0];"""
+
+    assert prog.to_ir() == unbound_expected
+    bound_prog = prog.make_bound_program({"alpha": 0.5})
+    # Original program unchanged
+    assert prog.to_ir() == unbound_expected
+    assert bound_prog.to_ir() == bound_template.format(0.5)
+    # Can rebind
+    bound_prog = prog.make_bound_program({"alpha": 0.432143})
+    assert bound_prog.to_ir() == bound_template.format(0.432143)
+
+
+def test_multi_bind_parameters():
+    """Test binding FreeParameters to concrete values."""
+
+    @aq.subroutine
+    def sub(alpha: float, theta: float):
+        rx(0, alpha)
+        rx(1, theta)
+        cnot(0, 1)
+        rx(0, theta)
+        rx(1, alpha)
+
+    @aq.subroutine
+    def rx_alpha(qubit: int):
+        rx(qubit, FreeParameter("alpha"))
+
+    @aq.main(num_qubits=3)
+    def parametric(alpha: float, theta: float):
+        sub(alpha, theta)
+        rx_alpha(2)
+
+    prog = parametric(FreeParameter("alpha"), FreeParameter("beta"))
+    bound_prog = prog.make_bound_program({"alpha": 0.5, "beta": 1.5})
+
+    expected = """OPENQASM 3.0;
+def sub(float[64] alpha, float[64] theta) {
+    rx(alpha) __qubits__[0];
+    rx(theta) __qubits__[1];
+    cnot __qubits__[0], __qubits__[1];
+    rx(theta) __qubits__[0];
+    rx(alpha) __qubits__[1];
+}
+def rx_alpha(int[32] qubit) {
+    rx(alpha) __qubits__[qubit];
+}
+float[64] alpha = 0.5;
+float[64] beta = 1.5;
+qubit[3] __qubits__;
+sub(alpha, beta);
+rx_alpha(2);"""
+    assert bound_prog.to_ir() == expected
+
+
+def test_partial_bind():
+    """Test binding some but not all FreeParameters."""
+
+    @aq.subroutine
+    def rx_alpha(qubit: int, theta: float):
+        rx(qubit, theta)
+
+    @aq.main(num_qubits=3)
+    def parametric(alpha: float, beta: float):
+        rx_alpha(2, alpha)
+        rx_alpha(2, beta)
+
+    prog = parametric(FreeParameter("alpha"), FreeParameter("beta"))
+    bound_prog = prog.make_bound_program({"beta": np.pi})
+
+    expected = """OPENQASM 3.0;
+def rx_alpha(int[32] qubit, float[64] theta) {
+    rx(theta) __qubits__[qubit];
+}
+input float[64] alpha;
+float[64] beta = 3.141592653589793;
+qubit[3] __qubits__;
+rx_alpha(2, alpha);
+rx_alpha(2, beta);"""
+    assert bound_prog.to_ir() == expected
+
+
+def test_binding_pulse_parameters():
+    """Test binding programs with parametric pulse instructions."""
+
+    @aq.gate_calibration(implements=rx, target="$1")
+    def cal_1(angle: float):
+        pulse.delay("$1", angle)
+
+    @aq.main
+    def my_program():
+        rx("$1", FreeParameter("theta"))
+
+    qasm1 = my_program().with_calibrations(cal_1).make_bound_program({"theta": 0.6}).to_ir()
+    qasm2 = my_program().make_bound_program({"theta": 0.6}).with_calibrations(cal_1).to_ir()
+    assert qasm1 == qasm2
+
+    expected = """OPENQASM 3.0;
+float[64] theta = 0.6;
+defcal rx(angle[32] angle) $1 {
+    delay[(angle) * 1s] $1;
+}
+rx(theta) $1;"""
+    assert expected == qasm1
+
+
+def test_bind_empty_program():
+    """Test that binding behaves well on empty programs."""
+
+    @aq.main
+    def empty_program():
+        pass
+
+    qasm = empty_program().to_ir()
+    bound_program1 = empty_program().make_bound_program({}).to_ir()
+    bound_program2 = empty_program().make_bound_program({"alpha": 0.5}).to_ir()
+    assert qasm == bound_program1 == bound_program2
+
+
+def test_strict_parameter_bind():
+    """Test make_bound_program with strict set to True."""
+
+    @aq.main
+    def parametric(theta: float):
+        rx(0, theta)
+        measure(0)
+
+    prog = parametric(FreeParameter("alpha"))
+
+    template = """OPENQASM 3.0;
+float[64] alpha = {};
+qubit[1] __qubits__;
+rx(alpha) __qubits__[0];
+bit __bit_0__;
+__bit_0__ = measure __qubits__[0];"""
+
+    bound_prog = prog.make_bound_program({"alpha": 0.5}, strict=True)
+    assert bound_prog.to_ir() == template.format(0.5)
+
+
+def test_strict_parameter_bind_failure():
+    """Test make_bound_program with strict set to True."""
+
+    @aq.main
+    def parametric(theta: float):
+        rx(0, theta)
+        measure(0)
+
+    prog = parametric(FreeParameter("alpha"))
+    with pytest.raises(ValueError, match="No parameter in the program named: beta"):
+        prog.make_bound_program({"beta": 0.5}, strict=True)
+
+
+def test_duplicate_variable_name_fails():
+    """Test using a variable and FreeParameter with the same name."""
+
+    @aq.main
+    def parametric():
+        alpha = aq.FloatVar(1.2)  # noqa: F841
+        rx(0, FreeParameter("alpha"))
+
+    with pytest.raises(RuntimeError, match="conflicting variables with name alpha"):
+        parametric()
+
+
+def test_binding_variable_fails():
+    """Test that trying to bind a variable that isn't declared as a FreeParameter fails."""
+
+    @aq.main
+    def parametric():
+        alpha = aq.FloatVar(1.2)  # noqa: F841
+
+    with pytest.raises(ValueError, match="No parameter in the program named: beta"):
+        parametric().make_bound_program({"beta": 0.5}, strict=True)

From 8dd8c7d4059e660d87bf2404fe6b080c14ed9bae Mon Sep 17 00:00:00 2001
From: Lauren Capelluto 
Date: Mon, 6 Nov 2023 15:51:04 -0500
Subject: [PATCH 0943/1165] documentation: Improve docstring for
 make_bound_circuit (#784)

---
 src/braket/circuits/circuit.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py
index 81139e9e..76e98608 100644
--- a/src/braket/circuits/circuit.py
+++ b/src/braket/circuits/circuit.py
@@ -855,8 +855,8 @@ def make_bound_circuit(self, param_values: dict[str, Number], strict: bool = Fal
         Args:
             param_values (dict[str, Number]):  A mapping of FreeParameter names
                 to a value to assign to them.
-            strict (bool): If True, raises a ValueError if none of the FreeParameters
-                in param_values appear in the circuit. False by default."
+            strict (bool): If True, raises a ValueError if any of the FreeParameters
+                in param_values do not appear in the circuit. False by default.
 
         Returns:
             Circuit: Returns a circuit with all present parameters fixed to their respective
@@ -875,8 +875,8 @@ def _validate_parameters(self, parameter_values: dict[str, Number]) -> None:
                 to a value to assign to them.
 
         Raises:
-            ValueError: If there are no parameters that match the key for the arg
-            param_values.
+            ValueError: If a parameter name is given which does not appear in the circuit.
+
         """
         parameter_strings = set()
         for parameter in self.parameters:

From 885b9808eef8c8166c17f9284b8460daa22d2061 Mon Sep 17 00:00:00 2001
From: ci 
Date: Tue, 7 Nov 2023 16:15:11 +0000
Subject: [PATCH 0944/1165] prepare release v1.61.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 5ddb9e4c..69650a27 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## v1.61.0.post0 (2023-11-07)
+
+### Documentation Changes
+
+ * Improve docstring for make_bound_circuit
+
 ## v1.61.0 (2023-11-06)
 
 ### Features
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index 803ab75f..828b6a71 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.61.1.dev0"
+__version__ = "1.61.0.post0"

From 5408783ce843ae4c3de26b7686461b720b099f42 Mon Sep 17 00:00:00 2001
From: ci 
Date: Tue, 7 Nov 2023 16:15:11 +0000
Subject: [PATCH 0945/1165] update development version to v1.61.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 828b6a71..803ab75f 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.61.0.post0"
+__version__ = "1.61.1.dev0"

From 7c2f958616caaeeae1a3394c419cab8889ad6f91 Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Tue, 7 Nov 2023 15:29:50 -0500
Subject: [PATCH 0946/1165] Update decorators.md

---
 src/braket/experimental/autoqasm/doc/decorators.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/braket/experimental/autoqasm/doc/decorators.md b/src/braket/experimental/autoqasm/doc/decorators.md
index 494cb90c..2a570734 100644
--- a/src/braket/experimental/autoqasm/doc/decorators.md
+++ b/src/braket/experimental/autoqasm/doc/decorators.md
@@ -15,7 +15,7 @@ You can include gates, pulse control, classical control and subroutine calls. Wh
 def ghz_state(max_qubits):
     """Create a GHZ state from a variable number of qubits."""
     h(0)
-    for i in aq.range(max_qubits):
+    for i in aq.range(1, max_qubits):
         cnot(0, i)
     measure(list(range(max_qubits))) 
     

From f6e2f9a6e2e33e1e41c221acd05b9f52fe3cdae6 Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Tue, 7 Nov 2023 15:32:03 -0500
Subject: [PATCH 0947/1165] Fix measurement of physical qubits (#788)

---
 .../autoqasm/instructions/measurements.py     |  8 ++++--
 .../braket/experimental/autoqasm/test_api.py  | 26 +++++++++++++++++++
 2 files changed, 32 insertions(+), 2 deletions(-)

diff --git a/src/braket/experimental/autoqasm/instructions/measurements.py b/src/braket/experimental/autoqasm/instructions/measurements.py
index d92357de..216b454d 100644
--- a/src/braket/experimental/autoqasm/instructions/measurements.py
+++ b/src/braket/experimental/autoqasm/instructions/measurements.py
@@ -28,7 +28,11 @@ def my_program():
 
 from braket.experimental.autoqasm import program
 from braket.experimental.autoqasm import types as aq_types
-from braket.experimental.autoqasm.instructions.qubits import QubitIdentifierType, _qubit
+from braket.experimental.autoqasm.instructions.qubits import (
+    QubitIdentifierType,
+    _qubit,
+    is_qubit_identifier_type,
+)
 
 
 def measure(qubits: Union[QubitIdentifierType, Iterable[QubitIdentifierType]]) -> aq_types.BitVar:
@@ -42,7 +46,7 @@ def measure(qubits: Union[QubitIdentifierType, Iterable[QubitIdentifierType]]) -
     Returns:
         BitVar: Bit variable the measurement results are assigned to.
     """
-    if not isinstance(qubits, Iterable):
+    if is_qubit_identifier_type(qubits):
         qubits = [qubits]
 
     oqpy_program = program.get_program_conversion_context().get_oqpy_program()
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py
index 057cc0ce..f5f2d19c 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_api.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py
@@ -302,6 +302,32 @@ def test_bell_measurement_invalid_declared_size() -> None:
     assert expected_error_message in str(exc_info.value)
 
 
+@aq.main
+def measure_physical_qubits() -> None:
+    """A program that measures physical qubits."""
+    a = measure("$0")  # noqa: F841
+    b = measure("$1")  # noqa: F841
+    c = measure(["$0", "$1"])  # noqa: F841
+
+
+def test_measure_physical_qubits() -> None:
+    expected = """OPENQASM 3.0;
+bit a;
+bit b;
+bit[2] c;
+bit __bit_0__;
+__bit_0__ = measure $0;
+a = __bit_0__;
+bit __bit_1__;
+__bit_1__ = measure $1;
+b = __bit_1__;
+bit[2] __bit_2__ = "00";
+__bit_2__[0] = measure $0;
+__bit_2__[1] = measure $1;
+c = __bit_2__;"""
+    assert measure_physical_qubits().to_ir() == expected
+
+
 @aq.main(num_qubits=5)
 def ghz_qasm_for_loop() -> None:
     """A function that generates a GHZ state using a QASM for loop."""

From 5ed43f3d1676a6e00cd1ea4be998a73c5800c269 Mon Sep 17 00:00:00 2001
From: Lauren Capelluto 
Date: Wed, 8 Nov 2023 15:30:02 -0500
Subject: [PATCH 0948/1165] feature: Add get_compiled_circuit convenience
 method (#787)

* feature: Add get_compiled_circuit convenience method to GateModelQuantumTaskResult
---
 .../tasks/gate_model_quantum_task_result.py   |  17 +++
 .../test_gate_model_quantum_task_result.py    | 112 ++++++++++++++++++
 2 files changed, 129 insertions(+)

diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py
index 453dcacf..631ca45e 100644
--- a/src/braket/tasks/gate_model_quantum_task_result.py
+++ b/src/braket/tasks/gate_model_quantum_task_result.py
@@ -134,6 +134,23 @@ def __eq__(self, other) -> bool:
             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.
+
+        Returns:
+            Optional[str]: The compiled circuit or None.
+        """
+        metadata = self.additional_metadata
+        if not metadata:
+            return None
+        if metadata.rigettiMetadata:
+            return metadata.rigettiMetadata.compiledProgram
+        elif metadata.oqcMetadata:
+            return metadata.oqcMetadata.compiledProgram
+        else:
+            return None
+
     @staticmethod
     def measurement_counts_from_measurements(measurements: np.ndarray) -> Counter:
         """
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 7a9ae3a2..8c0f8d9a 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,8 @@
     ResultTypeValue,
     TaskMetadata,
 )
+from braket.task_result.oqc_metadata_v1 import OqcMetadata
+from braket.task_result.rigetti_metadata_v1 import RigettiMetadata
 from braket.tasks import GateModelQuantumTaskResult
 
 
@@ -61,6 +63,79 @@ def additional_metadata_openqasm():
     return AdditionalMetadata(action=program)
 
 
+@pytest.fixture
+def quil_program():
+    return """
+PRAGMA INITIAL_REWIRING "NAIVE"
+RESET
+DECLARE ro BIT[2]
+PRAGMA PRESERVE_BLOCK
+RX(1.5707963267948966) 0
+RX(1.5707963267948966) 7
+RZ(1.5707963267948966) 0
+RZ(1.5707963267948966) 7
+RX(-1.5707963267948966) 7
+CZ 0 7
+RZ(3.141592653589793) 7
+RX(1.5707963267948966) 7
+RZ(1.5707963267948966) 7
+RX(-1.5707963267948966) 7
+PRAGMA END_PRESERVE_BLOCK
+MEASURE 0 ro[0]
+MEASURE 7 ro[1]
+"""
+
+
+@pytest.fixture
+def additional_metadata_rigetti(quil_program):
+    program = openqasm.Program(
+        source="""
+        OPENQASM 3.0;
+        bit[2] b;
+        h $0;
+        cnot $0, $7;
+        b[0] = measure $0;
+        b[1] = measure $7;
+        """
+    )
+    rigetti_metadata = RigettiMetadata(compiledProgram=quil_program)
+
+    return AdditionalMetadata(action=program, rigettiMetadata=rigetti_metadata)
+
+
+@pytest.fixture
+def qasm2_program():
+    return """
+    OPENQASM 2.0;
+    include "qelib1.inc";
+
+    qreg node[8];
+    creg b[2];
+    u3(0.5*pi,0.0*pi,1.0*pi) node[0];
+    cx node[0],node[1];
+    measure node[0] -> b[0];
+    measure node[1] -> b[1];
+    """
+
+
+@pytest.fixture
+def additional_metadata_oqc(qasm2_program):
+    program = openqasm.Program(
+        source="""
+        OPENQASM 3.0;
+        bit[2] b;
+        qubit[8] q;
+        h q[0];
+        cnot q[0], q[7];
+        b[0] = measure q[0];
+        b[1] = measure q[7];
+        """
+    )
+    oqc_metadata = OqcMetadata(compiledProgram=qasm2_program)
+
+    return AdditionalMetadata(action=program, oqcMetadata=oqc_metadata)
+
+
 @pytest.fixture
 def result_obj_1(task_metadata_shots, additional_metadata):
     return GateModelTaskResult(
@@ -71,6 +146,20 @@ def result_obj_1(task_metadata_shots, additional_metadata):
     )
 
 
+@pytest.fixture
+def result_rigetti(result_obj_1, additional_metadata_rigetti):
+    result = GateModelQuantumTaskResult.from_object(result_obj_1)
+    result.additional_metadata = additional_metadata_rigetti
+    return result
+
+
+@pytest.fixture
+def result_oqc(result_obj_1, additional_metadata_oqc):
+    result = GateModelQuantumTaskResult.from_object(result_obj_1)
+    result.additional_metadata = additional_metadata_oqc
+    return result
+
+
 @pytest.fixture
 def result_str_1(result_obj_1):
     return result_obj_1.json()
@@ -234,6 +323,29 @@ def test_openqasm_shots_calculate_result_types(openqasm_result_obj_shots):
 ]
 
 
+def test_get_compiled_circuit_rigetti(result_rigetti, quil_program):
+    """Test get_compiled_circuit method."""
+    assert result_rigetti.get_compiled_circuit() == quil_program
+
+
+def test_get_compiled_circuit_oqc(result_oqc, qasm2_program):
+    """Test get_compiled_circuit method."""
+    assert result_oqc.get_compiled_circuit() == qasm2_program
+
+
+def test_get_compiled_circuit_no_qhp_metadata(result_obj_1):
+    """Test get_compiled_circuit method."""
+    result = GateModelQuantumTaskResult.from_object(result_obj_1)
+    assert result.get_compiled_circuit() is None
+
+
+def test_get_compiled_circuit_no_metadata(result_obj_1):
+    """Test that the method does not raise an error if metadata is missing."""
+    result = GateModelQuantumTaskResult.from_object(result_obj_1)
+    result.additional_metadata = None
+    assert result.get_compiled_circuit() is None
+
+
 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]]

From 94dfa763cf62ba6ad8fbb4880b36c1202b2ab7ea Mon Sep 17 00:00:00 2001
From: ci 
Date: Thu, 9 Nov 2023 16:15:12 +0000
Subject: [PATCH 0949/1165] prepare release v1.62.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 69650a27..19b0fba8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## v1.62.0 (2023-11-09)
+
+### Features
+
+ * Add get_compiled_circuit convenience method
+
 ## v1.61.0.post0 (2023-11-07)
 
 ### Documentation Changes
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index 803ab75f..c371dfad 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.61.1.dev0"
+__version__ = "1.62.0"

From 7f28109b1da0e98a56e83746cda4fd1611cb858b Mon Sep 17 00:00:00 2001
From: ci 
Date: Thu, 9 Nov 2023 16:15:12 +0000
Subject: [PATCH 0950/1165] update development version to v1.62.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 c371dfad..8cc1a9a4 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.62.0"
+__version__ = "1.62.1.dev0"

From 77690e63284cadbf84efbb2bb9fdf0bd6b6746ae Mon Sep 17 00:00:00 2001
From: Lauren Capelluto 
Date: Fri, 10 Nov 2023 15:55:02 -0500
Subject: [PATCH 0951/1165] feature: Add support for FreeParameters in AutoQASM
 conditional statements (#789)

* feature: FreeParameters in conditional statements

* Update type check to include FreeParameter as a qasm type
* Add support for free parameters in logical operations
* Add support for comparison statements
---------

Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
---
 .../autoqasm/converters/comparisons.py        |  70 +++++
 src/braket/experimental/autoqasm/errors.py    |   4 +
 .../autoqasm/operators/__init__.py            |   1 +
 .../autoqasm/operators/comparisons.py         | 126 ++++++++
 .../autoqasm/operators/logical.py             |  12 +
 .../experimental/autoqasm/operators/utils.py  |  47 +++
 .../experimental/autoqasm/program/program.py  |  36 ++-
 .../autoqasm/transpiler/transpiler.py         |   8 +-
 .../experimental/autoqasm/types/types.py      |   6 +-
 .../braket/experimental/autoqasm/test_api.py  |   8 +-
 .../experimental/autoqasm/test_operators.py   |  67 ++++
 .../experimental/autoqasm/test_parameters.py  | 297 +++++++++++++++++-
 .../experimental/autoqasm/test_program.py     |   9 +
 13 files changed, 673 insertions(+), 18 deletions(-)
 create mode 100644 src/braket/experimental/autoqasm/converters/comparisons.py
 create mode 100644 src/braket/experimental/autoqasm/operators/comparisons.py
 create mode 100644 src/braket/experimental/autoqasm/operators/utils.py

diff --git a/src/braket/experimental/autoqasm/converters/comparisons.py b/src/braket/experimental/autoqasm/converters/comparisons.py
new file mode 100644
index 00000000..7b8d53a5
--- /dev/null
+++ b/src/braket/experimental/autoqasm/converters/comparisons.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.
+
+"""Converters for comparison nodes."""
+
+import ast
+
+import gast
+
+from braket.experimental.autoqasm.autograph.core import ag_ctx, converter
+from braket.experimental.autoqasm.autograph.pyct import templates
+
+COMPARISON_OPERATORS = {
+    gast.Lt: "ag__.lt_",
+    gast.LtE: "ag__.lteq_",
+    gast.Gt: "ag__.gt_",
+    gast.GtE: "ag__.gteq_",
+}
+
+
+class ComparisonTransformer(converter.Base):
+    """Transformer for comparison nodes."""
+
+    def visit_Compare(self, node: ast.stmt) -> ast.stmt:
+        """Transforms a comparison node.
+
+        Args:
+            node (ast.stmt): AST node to transform.
+
+        Returns:
+            ast.stmt: Transformed node.
+        """
+        node = self.generic_visit(node)
+
+        op_type = type(node.ops[0])
+        if op_type not in COMPARISON_OPERATORS:
+            return node
+
+        template = f"{COMPARISON_OPERATORS[op_type]}(lhs_, rhs_)"
+
+        return templates.replace(
+            template,
+            lhs_=node.left,
+            rhs_=node.comparators[0],
+            original=node,
+        )[0].value
+
+
+def transform(node: ast.stmt, ctx: ag_ctx.ControlStatusCtx) -> ast.stmt:
+    """Transform comparison nodes.
+
+    Args:
+        node (ast.stmt): AST node to transform.
+        ctx (ag_ctx.ControlStatusCtx): Transformer context.
+
+    Returns:
+        ast.stmt: Transformed node.
+    """
+
+    return ComparisonTransformer(ctx).visit(node)
diff --git a/src/braket/experimental/autoqasm/errors.py b/src/braket/experimental/autoqasm/errors.py
index 5a6b1ede..33bd23c4 100644
--- a/src/braket/experimental/autoqasm/errors.py
+++ b/src/braket/experimental/autoqasm/errors.py
@@ -37,6 +37,10 @@ class MissingParameterTypeError(AutoQasmError):
     """AutoQASM requires type hints for subroutine parameters."""
 
 
+class ParameterNotFoundError(AutoQasmError):
+    """A FreeParameter could not be found in the program."""
+
+
 class InvalidGateDefinition(AutoQasmError):
     """Gate definition does not meet the necessary requirements."""
 
diff --git a/src/braket/experimental/autoqasm/operators/__init__.py b/src/braket/experimental/autoqasm/operators/__init__.py
index 853b8ce0..75b44c87 100644
--- a/src/braket/experimental/autoqasm/operators/__init__.py
+++ b/src/braket/experimental/autoqasm/operators/__init__.py
@@ -27,6 +27,7 @@
 )
 
 from .assignments import assign_stmt  # noqa: F401
+from .comparisons import gt_, gteq_, lt_, lteq_  # noqa: F401
 from .conditional_expressions import if_exp  # noqa: F401
 from .control_flow import for_stmt, if_stmt, while_stmt  # noqa: F401
 from .data_structures import ListPopOpts  # noqa: F401
diff --git a/src/braket/experimental/autoqasm/operators/comparisons.py b/src/braket/experimental/autoqasm/operators/comparisons.py
new file mode 100644
index 00000000..6f08f29e
--- /dev/null
+++ b/src/braket/experimental/autoqasm/operators/comparisons.py
@@ -0,0 +1,126 @@
+# 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.
+
+
+"""Operators for comparison operators: <, <=, >, and >=."""
+
+from typing import Any, Union
+
+from braket.experimental.autoqasm import program
+from braket.experimental.autoqasm import types as aq_types
+
+from .utils import _register_and_convert_parameters
+
+
+def lt_(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]:
+    """Functional form of "<".
+
+    Args:
+        a (Any): The first expression.
+        b (Any): The second expression.
+
+    Returns:
+        Union[bool, BoolVar]: Whether the first expression is less than the second.
+    """
+    if aq_types.is_qasm_type(a) or aq_types.is_qasm_type(b):
+        return _aq_lt(a, b)
+    else:
+        return a < b
+
+
+def _aq_lt(a: Any, b: Any) -> aq_types.BoolVar:
+    a, b = _register_and_convert_parameters(a, b)
+
+    oqpy_program = program.get_program_conversion_context().get_oqpy_program()
+    result = aq_types.BoolVar()
+    oqpy_program.declare(result)
+    oqpy_program.set(result, a < b)
+    return result
+
+
+def lteq_(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]:
+    """Functional form of "<=".
+
+    Args:
+        a (Any): The first expression.
+        b (Any): The second expression.
+
+    Returns:
+        Union[bool, BoolVar]: Whether the first expression is less than or equal to the second.
+    """
+    if aq_types.is_qasm_type(a) or aq_types.is_qasm_type(b):
+        return _aq_lteq(a, b)
+    else:
+        return a <= b
+
+
+def _aq_lteq(a: Any, b: Any) -> aq_types.BoolVar:
+    a, b = _register_and_convert_parameters(a, b)
+
+    oqpy_program = program.get_program_conversion_context().get_oqpy_program()
+    result = aq_types.BoolVar()
+    oqpy_program.declare(result)
+    oqpy_program.set(result, a <= b)
+    return result
+
+
+def gt_(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]:
+    """Functional form of ">".
+
+    Args:
+        a (Any): The first expression.
+        b (Any): The second expression.
+
+    Returns:
+        Union[bool, BoolVar]: Whether the first expression is greater than the second.
+    """
+    if aq_types.is_qasm_type(a) or aq_types.is_qasm_type(b):
+        return _aq_gt(a, b)
+    else:
+        return a > b
+
+
+def _aq_gt(a: Any, b: Any) -> aq_types.BoolVar:
+    a, b = _register_and_convert_parameters(a, b)
+
+    oqpy_program = program.get_program_conversion_context().get_oqpy_program()
+    result = aq_types.BoolVar()
+    oqpy_program.declare(result)
+    oqpy_program.set(result, a > b)
+    return result
+
+
+def gteq_(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]:
+    """Functional form of ">=".
+
+    Args:
+        a (Any): The first expression.
+        b (Any): The second expression.
+
+    Returns:
+        Union[bool, BoolVar]: Whether the first expression is greater than or equal to the second.
+    """
+    if aq_types.is_qasm_type(a) or aq_types.is_qasm_type(b):
+        return _aq_gteq(a, b)
+    else:
+        return a >= b
+
+
+def _aq_gteq(a: Any, b: Any) -> aq_types.BoolVar:
+    a, b = _register_and_convert_parameters(a, b)
+
+    oqpy_program = program.get_program_conversion_context().get_oqpy_program()
+    result = aq_types.BoolVar()
+    oqpy_program.declare(result)
+    oqpy_program.set(result, a >= b)
+    return result
diff --git a/src/braket/experimental/autoqasm/operators/logical.py b/src/braket/experimental/autoqasm/operators/logical.py
index 6813e090..c7bcf7c5 100644
--- a/src/braket/experimental/autoqasm/operators/logical.py
+++ b/src/braket/experimental/autoqasm/operators/logical.py
@@ -22,6 +22,8 @@
 from braket.experimental.autoqasm import program
 from braket.experimental.autoqasm import types as aq_types
 
+from .utils import _register_and_convert_parameters
+
 
 def and_(a: Callable[[], Any], b: Callable[[], Any]) -> Union[bool, aq_types.BoolVar]:
     """Functional form of "and".
@@ -42,6 +44,8 @@ def and_(a: Callable[[], Any], b: Callable[[], Any]) -> Union[bool, aq_types.Boo
 
 
 def _oqpy_and(a: Any, b: Any) -> aq_types.BoolVar:
+    a, b = _register_and_convert_parameters(a, b)
+
     oqpy_program = program.get_program_conversion_context().get_oqpy_program()
     result = aq_types.BoolVar()
     oqpy_program.declare(result)
@@ -72,6 +76,8 @@ def or_(a: Callable[[], Any], b: Callable[[], Any]) -> Union[bool, aq_types.Bool
 
 
 def _oqpy_or(a: Any, b: Any) -> aq_types.BoolVar:
+    a, b = _register_and_convert_parameters(a, b)
+
     oqpy_program = program.get_program_conversion_context().get_oqpy_program()
     result = aq_types.BoolVar()
     oqpy_program.declare(result)
@@ -99,6 +105,8 @@ def not_(a: Any) -> Union[bool, aq_types.BoolVar]:
 
 
 def _oqpy_not(a: Any) -> aq_types.BoolVar:
+    a = _register_and_convert_parameters(a)
+
     oqpy_program = program.get_program_conversion_context().get_oqpy_program()
     result = aq_types.BoolVar()
     oqpy_program.declare(result)
@@ -127,6 +135,8 @@ def eq(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]:
 
 
 def _oqpy_eq(a: Any, b: Any) -> aq_types.BoolVar:
+    a, b = _register_and_convert_parameters(a, b)
+
     oqpy_program = program.get_program_conversion_context().get_oqpy_program()
     is_equal = aq_types.BoolVar()
     oqpy_program.declare(is_equal)
@@ -155,6 +165,8 @@ def not_eq(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]:
 
 
 def _oqpy_not_eq(a: Any, b: Any) -> aq_types.BoolVar:
+    a, b = _register_and_convert_parameters(a, b)
+
     oqpy_program = program.get_program_conversion_context().get_oqpy_program()
     is_not_equal = aq_types.BoolVar()
     oqpy_program.declare(is_not_equal)
diff --git a/src/braket/experimental/autoqasm/operators/utils.py b/src/braket/experimental/autoqasm/operators/utils.py
new file mode 100644
index 00000000..a24b502e
--- /dev/null
+++ b/src/braket/experimental/autoqasm/operators/utils.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.
+
+
+"Utility methods for operators."
+
+from typing import Any, Union
+
+from braket.circuits import FreeParameter
+from braket.experimental.autoqasm import program
+from braket.experimental.autoqasm import types as aq_types
+
+
+def _register_and_convert_parameters(
+    *args: tuple[Any],
+) -> Union[list[aq_types.FloatVar], aq_types.FloatVar]:
+    """Adds FreeParameters to the program conversion context parameter registry, and
+    returns the associated FloatVar objects.
+
+    Notes: Adding a parameter to the registry twice is safe. Conversion is a pass through
+    for non-FreeParameter inputs. Input and output arity is the same.
+
+    FloatVars are more compatible with the program conversion operations.
+
+    Returns:
+        Union[list[FloatVar], FloatVar]: FloatVars for program conversion.
+    """
+    program_conversion_context = program.get_program_conversion_context()
+    program_conversion_context.register_args(args)
+    result = []
+    for arg in args:
+        if isinstance(arg, FreeParameter):
+            var = program.get_program_conversion_context().get_parameter(arg.name)
+            result.append(var)
+        else:
+            result.append(arg)
+    return result[0] if len(result) == 1 else result
diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py
index bd7416bc..9f2a241b 100644
--- a/src/braket/experimental/autoqasm/program/program.py
+++ b/src/braket/experimental/autoqasm/program/program.py
@@ -130,11 +130,12 @@ def make_bound_program(self, param_values: dict[str, float], strict: bool = Fals
         Args:
             param_values (dict[str, float]): A mapping of FreeParameter names
                 to a value to assign to them.
-            strict (bool): If True, raises a ValueError if any of the FreeParameters
+            strict (bool): If True, raises a ParameterNotFoundError if any of the FreeParameters
                 in param_values do not appear in the program. False by default.
 
         Raises:
-            ValueError: If a parameter name is given which does not appear in the program.
+            ParameterNotFoundError: If a parameter name is given which does not appear in
+                the program.
 
         Returns:
             Program: Returns a program with all present parameters fixed to their respective
@@ -148,7 +149,7 @@ def make_bound_program(self, param_values: dict[str, float], strict: bool = Fals
                 assert target.init_expression == "input", "Only free parameters can be bound."
                 target.init_expression = value
             elif strict:
-                raise ValueError(f"No parameter in the program named: {name}")
+                raise errors.ParameterNotFoundError(f"No parameter in the program named: {name}")
 
         return Program(bound_oqpy_program, self._has_pulse_control)
 
@@ -321,22 +322,39 @@ def register_args(self, args: list[Any]) -> None:
         """
         for arg in args:
             if isinstance(arg, FreeParameter):
-                self.register_parameter(arg.name)
+                self.register_parameter(arg)
             elif isinstance(arg, FreeParameterExpression):
                 # TODO laurecap: Support for expressions
                 raise NotImplementedError(
                     "Expressions of FreeParameters will be supported shortly!"
                 )
 
-    def register_parameter(self, name: str) -> None:
-        """Register an input parameter with the given name, if it has not already been
-        registered. Only floats are currently supported.
+    def register_parameter(self, parameter: FreeParameter) -> None:
+        """Register an input parameter if it has not already been registered.
+        Only floats are currently supported.
 
         Args:
-            name (str): The identifier for the parameter.
+            parameter (FreeParameter): The parameter to register with the program.
+        """
+        if parameter.name not in self._free_parameters:
+            self._free_parameters[parameter.name] = oqpy.FloatVar("input", name=parameter.name)
+
+    def get_parameter(self, name: str) -> oqpy.FloatVar:
+        """Return a named oqpy.FloatVar that is used as a free parameter in the program.
+
+        Args:
+            name (str): The name of the parameter.
+
+        Raises:
+            ParameterNotFoundError: If there is no parameter with the given name registered
+            with the program.
+
+        Returns:
+            FloatVar: The associated variable.
         """
         if name not in self._free_parameters:
-            self._free_parameters[name] = oqpy.FloatVar("input", name=name)
+            raise errors.ParameterNotFoundError(f"Free parameter '{name}' was not found.")
+        return self._free_parameters[name]
 
     def get_free_parameters(self) -> list[oqpy.FloatVar]:
         """Return a list of named oqpy.Vars that are used as free parameters in the program."""
diff --git a/src/braket/experimental/autoqasm/transpiler/transpiler.py b/src/braket/experimental/autoqasm/transpiler/transpiler.py
index 2dc0dbfc..46cea08d 100644
--- a/src/braket/experimental/autoqasm/transpiler/transpiler.py
+++ b/src/braket/experimental/autoqasm/transpiler/transpiler.py
@@ -59,7 +59,12 @@
     reaching_definitions,
 )
 from braket.experimental.autoqasm.autograph.tf_utils import tf_stack
-from braket.experimental.autoqasm.converters import assignments, break_statements, return_statements
+from braket.experimental.autoqasm.converters import (
+    assignments,
+    break_statements,
+    comparisons,
+    return_statements,
+)
 
 
 class PyToOqpy(transpiler.PyToPy):
@@ -145,6 +150,7 @@ def transform_ast(
         node = call_trees.transform(node, ctx)
         node = control_flow.transform(node, ctx)
         node = conditional_expressions.transform(node, ctx)
+        node = comparisons.transform(node, ctx)
         node = logical_expressions.transform(node, ctx)
         node = variables.transform(node, ctx)
 
diff --git a/src/braket/experimental/autoqasm/types/types.py b/src/braket/experimental/autoqasm/types/types.py
index fcfaea88..740d76b2 100644
--- a/src/braket/experimental/autoqasm/types/types.py
+++ b/src/braket/experimental/autoqasm/types/types.py
@@ -19,6 +19,7 @@
 import oqpy.base
 from openpulse import ast
 
+from braket.circuits import FreeParameterExpression
 from braket.experimental.autoqasm import errors, program
 
 
@@ -32,11 +33,12 @@ def is_qasm_type(val: Any) -> bool:
     Returns:
         bool: Whether the object is a QASM type.
     """
+    qasm_types = (oqpy.Range, oqpy._ClassicalVar, oqpy.base.OQPyExpression, FreeParameterExpression)
     # The input can either be a class, like oqpy.Range ...
     if type(val) is type:
-        return issubclass(val, (oqpy.Range, oqpy._ClassicalVar, oqpy.base.OQPyExpression))
+        return issubclass(val, qasm_types)
     # ... or an instance of a class, like oqpy.Range(10)
-    return isinstance(val, (oqpy.Range, oqpy._ClassicalVar, oqpy.base.OQPyExpression))
+    return isinstance(val, qasm_types)
 
 
 def qasm_range(start: int, stop: Optional[int] = None, step: Optional[int] = 1) -> oqpy.Range:
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py
index f5f2d19c..b757033b 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_api.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py
@@ -131,7 +131,9 @@ def do_h(int[32] q) {
 }
 def recursive_h(int[32] q) {
     do_h(q);
-    if (q > 0) {
+    bool __bool_0__;
+    __bool_0__ = q > 0;
+    if (__bool_0__) {
         recursive_h(q - 1);
     }
 }
@@ -155,7 +157,9 @@ def do_h(int[32] q) {
 }
 def recursive_h(int[32] q) {
     do_h(q);
-    if (q > 0) {
+    bool __bool_0__;
+    __bool_0__ = q > 0;
+    if (__bool_0__) {
         recursive_h(q - 1);
     }
 }
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py
index dba42099..ca85fec3 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_operators.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py
@@ -487,6 +487,73 @@ def prog():
     assert prog().to_ir() == expected
 
 
+def test_comparison_lt() -> None:
+    """Tests less than operator handling."""
+
+    @aq.main
+    def prog():
+        a = measure(0)
+        if a < 1:
+            h(0)
+
+    expected = """OPENQASM 3.0;
+bit a;
+qubit[1] __qubits__;
+bit __bit_0__;
+__bit_0__ = measure __qubits__[0];
+a = __bit_0__;
+bool __bool_1__;
+__bool_1__ = a < 1;
+if (__bool_1__) {
+    h __qubits__[0];
+}"""
+    qasm = prog().to_ir()
+    assert qasm == expected
+
+
+def test_comparison_gt() -> None:
+    """Tests greater than operator handling."""
+
+    @aq.main
+    def prog():
+        a = measure(0)
+        if a > 1:
+            h(0)
+
+    expected = """OPENQASM 3.0;
+bit a;
+qubit[1] __qubits__;
+bit __bit_0__;
+__bit_0__ = measure __qubits__[0];
+a = __bit_0__;
+bool __bool_1__;
+__bool_1__ = a > 1;
+if (__bool_1__) {
+    h __qubits__[0];
+}"""
+    qasm = prog().to_ir()
+    assert qasm == expected
+
+
+def test_comparison_ops_py() -> None:
+    """Tests the comparison aq.operators for Python expressions."""
+
+    @aq.main
+    def prog():
+        a = 1.2
+        b = 12
+        c = a < b
+        d = a <= b
+        e = a > b
+        f = a >= b
+        g = 1.2
+        h = a <= g
+        assert all([c, d, not e, not f, h])
+
+    expected = """OPENQASM 3.0;"""
+    assert prog().to_ir() == expected
+
+
 @pytest.mark.parametrize(
     "target", [oqpy.ArrayVar(dimensions=[3], name="arr"), oqpy.BitVar(size=3, name="arr")]
 )
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py
index 0a43b32e..6633a36a 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py
@@ -21,13 +21,13 @@
 from braket.default_simulator import StateVectorSimulator
 from braket.devices.local_simulator import LocalSimulator
 from braket.experimental.autoqasm import pulse
-from braket.experimental.autoqasm.instructions import cnot, cphaseshift, measure, ms, rx, rz
+from braket.experimental.autoqasm.instructions import cnot, cphaseshift, h, measure, ms, rx, rz, x
 from braket.tasks.local_quantum_task import LocalQuantumTask
 
 
 def _test_parametric_on_local_sim(program: aq.Program, inputs: dict[str, float]) -> None:
     device = LocalSimulator(backend=StateVectorSimulator())
-    task = device.run(program, shots=10, inputs=inputs)
+    task = device.run(program, shots=100, inputs=inputs)
     assert isinstance(task, LocalQuantumTask)
     assert isinstance(task.result().measurements, dict)
     return task.result().measurements
@@ -461,7 +461,9 @@ def parametric(theta: float):
         measure(0)
 
     prog = parametric(FreeParameter("alpha"))
-    with pytest.raises(ValueError, match="No parameter in the program named: beta"):
+    with pytest.raises(
+        aq.errors.ParameterNotFoundError, match="No parameter in the program named: beta"
+    ):
         prog.make_bound_program({"beta": 0.5}, strict=True)
 
 
@@ -484,5 +486,292 @@ def test_binding_variable_fails():
     def parametric():
         alpha = aq.FloatVar(1.2)  # noqa: F841
 
-    with pytest.raises(ValueError, match="No parameter in the program named: beta"):
+    with pytest.raises(
+        aq.errors.ParameterNotFoundError, match="No parameter in the program named: beta"
+    ):
         parametric().make_bound_program({"beta": 0.5}, strict=True)
+
+
+def test_compound_condition():
+    """Test parameters used in greater than conditional statements."""
+
+    @aq.main
+    def parametric(val: float):
+        threshold = 0.9
+        if val > threshold or val >= 1.2:
+            x(0)
+        measure(0)
+
+    expected = """OPENQASM 3.0;
+input float[64] val;
+qubit[1] __qubits__;
+bool __bool_0__;
+__bool_0__ = val > 0.9;
+bool __bool_1__;
+__bool_1__ = val >= 1.2;
+bool __bool_2__;
+__bool_2__ = __bool_0__ || __bool_1__;
+if (__bool_2__) {
+    x __qubits__[0];
+}
+bit __bit_3__;
+__bit_3__ = measure __qubits__[0];"""
+    assert parametric(FreeParameter("val")).to_ir() == expected
+
+
+def test_lt_condition():
+    """Test parameters used in less than conditional statements."""
+
+    @aq.main
+    def parametric(val: float):
+        if val < 0.9:
+            x(0)
+        if val <= 0.9:
+            h(0)
+        measure(0)
+
+    expected = """OPENQASM 3.0;
+input float[64] val;
+qubit[1] __qubits__;
+bool __bool_0__;
+__bool_0__ = val < 0.9;
+if (__bool_0__) {
+    x __qubits__[0];
+}
+bool __bool_1__;
+__bool_1__ = val <= 0.9;
+if (__bool_1__) {
+    h __qubits__[0];
+}
+bit __bit_2__;
+__bit_2__ = measure __qubits__[0];"""
+    assert parametric(FreeParameter("val")).to_ir() == expected
+
+
+def test_parameter_in_predicate_in_subroutine():
+    """Test parameters used in conditional statements."""
+
+    @aq.subroutine
+    def sub(val: float):
+        threshold = 0.9
+        if val > threshold:
+            x(0)
+
+    @aq.main
+    def parametric(val: float):
+        sub(val)
+        measure(0)
+
+    expected = """OPENQASM 3.0;
+def sub(float[64] val) {
+    bool __bool_0__;
+    __bool_0__ = val > 0.9;
+    if (__bool_0__) {
+        x __qubits__[0];
+    }
+}
+input float[64] val;
+qubit[1] __qubits__;
+sub(val);
+bit __bit_1__;
+__bit_1__ = measure __qubits__[0];"""
+    assert parametric(FreeParameter("val")).to_ir() == expected
+
+
+def test_eq_condition():
+    """Test parameters used in conditional equals statements."""
+
+    @aq.main
+    def parametric(basis: int):
+        if basis == 1:
+            h(0)
+        elif basis == 2:
+            x(0)
+        else:
+            pass
+        measure(0)
+
+    expected = """OPENQASM 3.0;
+input float[64] basis;
+qubit[1] __qubits__;
+bool __bool_0__;
+__bool_0__ = basis == 1;
+if (__bool_0__) {
+    h __qubits__[0];
+} else {
+    bool __bool_1__;
+    __bool_1__ = basis == 2;
+    if (__bool_1__) {
+        x __qubits__[0];
+    }
+}
+bit __bit_2__;
+__bit_2__ = measure __qubits__[0];"""
+    assert parametric(FreeParameter("basis")).to_ir() == expected
+
+
+def test_sim_conditional_stmts():
+    @aq.main
+    def main(basis: int):
+        if basis == 1:
+            h(0)
+        else:
+            x(0)
+        c = measure(0)  # noqa: F841
+
+    measurements = _test_parametric_on_local_sim(main(FreeParameter("basis")), {"basis": 0})
+    assert all(val == 1 for val in measurements["c"])
+    measurements = _test_parametric_on_local_sim(main(FreeParameter("basis")), {"basis": 1})
+    assert 1 in measurements["c"] and 0 in measurements["c"]
+
+
+def test_sim_comparison_stmts():
+    @aq.main
+    def main(basis: int):
+        if basis > 0.5:
+            x(0)
+        c = measure(0)  # noqa: F841
+
+    measurements = _test_parametric_on_local_sim(main(FreeParameter("basis")), {"basis": 0.5})
+    assert all(val == 0 for val in measurements["c"])
+    measurements = _test_parametric_on_local_sim(main(FreeParameter("basis")), {"basis": 0.55})
+    assert all(val == 1 for val in measurements["c"])
+
+
+def test_param_neq():
+    """Test parameters used in conditional not equals statements."""
+
+    @aq.main
+    def parametric(val: int):
+        if val != 1:
+            h(0)
+        measure(0)
+
+    expected = """OPENQASM 3.0;
+input float[64] val;
+qubit[1] __qubits__;
+bool __bool_0__;
+__bool_0__ = val != 1;
+if (__bool_0__) {
+    h __qubits__[0];
+}
+bit __bit_1__;
+__bit_1__ = measure __qubits__[0];"""
+    assert parametric(FreeParameter("val")).to_ir() == expected
+
+
+def test_param_or():
+    """Test parameters used in conditional `or` statements."""
+
+    @aq.main
+    def parametric(alpha: float, beta: float):
+        if alpha or beta:
+            rx(0, alpha)
+            rx(0, beta)
+        measure(0)
+
+    expected = """OPENQASM 3.0;
+input float[64] alpha;
+input float[64] beta;
+qubit[1] __qubits__;
+bool __bool_0__;
+__bool_0__ = alpha || beta;
+if (__bool_0__) {
+    rx(alpha) __qubits__[0];
+    rx(beta) __qubits__[0];
+}
+bit __bit_1__;
+__bit_1__ = measure __qubits__[0];"""
+    assert parametric(FreeParameter("alpha"), FreeParameter("beta")).to_ir() == expected
+
+
+def test_param_and():
+    """Test parameters used in conditional `and` statements."""
+
+    @aq.main
+    def parametric(alpha: float, beta: float):
+        if alpha and beta:
+            rx(0, alpha)
+        measure(0)
+
+    expected = """OPENQASM 3.0;
+input float[64] alpha;
+input float[64] beta;
+qubit[1] __qubits__;
+bool __bool_0__;
+__bool_0__ = alpha && beta;
+if (__bool_0__) {
+    rx(alpha) __qubits__[0];
+}
+bit __bit_1__;
+__bit_1__ = measure __qubits__[0];"""
+    assert parametric(FreeParameter("alpha"), FreeParameter("beta")).to_ir() == expected
+
+
+def test_param_and_float():
+    """Test parameters used in conditional `and` statements."""
+
+    @aq.main
+    def parametric(alpha: float, beta: float):
+        if alpha and beta:
+            rx(0, alpha)
+        measure(0)
+
+    expected = """OPENQASM 3.0;
+input float[64] alpha;
+qubit[1] __qubits__;
+bool __bool_0__;
+__bool_0__ = alpha && 1.5;
+if (__bool_0__) {
+    rx(alpha) __qubits__[0];
+}
+bit __bit_1__;
+__bit_1__ = measure __qubits__[0];"""
+    assert parametric(FreeParameter("alpha"), 1.5).to_ir() == expected
+
+
+def test_param_not():
+    """Test parameters used in conditional `not` statements."""
+
+    @aq.main
+    def parametric(val: int):
+        if not val:
+            h(0)
+        measure(0)
+
+    expected = """OPENQASM 3.0;
+input float[64] val;
+qubit[1] __qubits__;
+bool __bool_0__;
+__bool_0__ = !val;
+if (__bool_0__) {
+    h __qubits__[0];
+}
+bit __bit_1__;
+__bit_1__ = measure __qubits__[0];"""
+    assert parametric(FreeParameter("val")).to_ir() == expected
+
+
+def test_parameter_binding_conditions():
+    """Test that parameters can be used in conditions and then bound."""
+
+    @aq.main
+    def parametric(val: float):
+        if val == 1:
+            x(0)
+        measure(0)
+
+    template = """OPENQASM 3.0;
+float[64] val = {};
+qubit[1] __qubits__;
+bool __bool_0__;
+__bool_0__ = val == 1;
+if (__bool_0__) {{
+    x __qubits__[0];
+}}
+bit __bit_1__;
+__bit_1__ = measure __qubits__[0];"""
+    bound_prog = parametric(FreeParameter("val")).make_bound_program({"val": 0})
+    assert bound_prog.to_ir() == template.format(0)
+    bound_prog = parametric(FreeParameter("val")).make_bound_program({"val": 1})
+    assert bound_prog.to_ir() == template.format(1)
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/braket/experimental/autoqasm/test_program.py
index 274e70d6..04dde260 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_program.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_program.py
@@ -20,6 +20,7 @@
 import pytest
 
 import braket.experimental.autoqasm as aq
+from braket.circuits import FreeParameter
 from braket.circuits.serialization import IRType
 from braket.experimental.autoqasm.instructions import cnot, measure, rx
 
@@ -39,6 +40,14 @@ def test_program_conversion_context() -> None:
     assert len(prog._oqpy_program_stack) == 1
 
 
+def test_get_parameter_invalid_name():
+    """Tests the get_parameter function."""
+    prog = aq.program.ProgramConversionContext()
+    prog.register_parameter(FreeParameter("alpha"))
+    with pytest.raises(aq.errors.ParameterNotFoundError):
+        prog.get_parameter("not_a_parameter")
+
+
 def test_build_program() -> None:
     """Tests the aq.build_program function."""
     with pytest.raises(AssertionError):

From c345fdb930869fa795e6b967eb95d0b590dd7f6f Mon Sep 17 00:00:00 2001
From: "Tim (Yi-Ting)" 
Date: Tue, 14 Nov 2023 16:15:13 -0500
Subject: [PATCH 0952/1165] feat: support freeparameter in AutoQASM pulse
 instruction (#794)

* enable freeparameter for pulse instruction

* add pulse test with freeparameter in condition
---
 .../experimental/autoqasm/pulse/pulse.py      |  1 +
 .../experimental/autoqasm/test_pulse.py       | 71 +++++++++++++++++++
 2 files changed, 72 insertions(+)

diff --git a/src/braket/experimental/autoqasm/pulse/pulse.py b/src/braket/experimental/autoqasm/pulse/pulse.py
index 751e106a..0b211c0a 100644
--- a/src/braket/experimental/autoqasm/pulse/pulse.py
+++ b/src/braket/experimental/autoqasm/pulse/pulse.py
@@ -43,6 +43,7 @@ def _pulse_instruction(name: str, frame: Frame, *args) -> None:
     """
     program_conversion_context = aq_program.get_program_conversion_context()
     program_conversion_context._has_pulse_control = True
+    program_conversion_context.register_args(args)
 
     pulse_sequence = PulseSequence()
     pulse_sequence._program = program_conversion_context.get_oqpy_program(
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pulse.py b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py
index e886c09a..de6bca7d 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_pulse.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py
@@ -30,6 +30,7 @@
     shift_frequency,
     shift_phase,
 )
+from braket.parametric import FreeParameter
 from braket.pulse import ArbitraryWaveform, Frame, Port
 
 PORT = Port(port_id="device_port_x0", dt=1e-9, properties={})
@@ -183,3 +184,73 @@ def test_pulse_control_invalid_physical_qubit(instruction, qubits_or_frames, par
     with pytest.raises(ValueError):
         with aq.build_program():
             instruction(qubits_or_frames, *params)
+
+
+def test_pulse_freeparameter() -> None:
+    """Test pulse program with freeparameter."""
+
+    @aq.main
+    def my_program():
+        delay(["$3", "$4"], FreeParameter("duration"))
+
+    expected = textwrap.dedent(
+        """
+        OPENQASM 3.0;
+        input float[64] duration;
+        cal {
+            delay[(duration) * 1s] $3, $4;
+        }
+        """
+    ).strip()
+    qasm = my_program().to_ir()
+    assert qasm == expected
+
+
+def test_pulse_freeparameter_bound() -> None:
+    """Test pulse program with freeparameter bound with values."""
+
+    @aq.main
+    def my_program():
+        delay(["$3", "$4"], FreeParameter("duration"))
+
+    expected = textwrap.dedent(
+        """
+        OPENQASM 3.0;
+        float[64] duration = 0.123;
+        cal {
+            delay[(duration) * 1s] $3, $4;
+        }
+        """
+    ).strip()
+    qasm = my_program().make_bound_program({"duration": 0.123}).to_ir()
+    assert qasm == expected
+
+
+def test_pulse_freeparameter_condition() -> None:
+    """Test pulse program with freeparameter in condition."""
+
+    @aq.main
+    def my_program():
+        delay(["$3", "$4"], FreeParameter("duration"))
+        if FreeParameter("duration") > FreeParameter("duration2"):
+            delay(["$3", "$4"], FreeParameter("duration2"))
+
+    expected = textwrap.dedent(
+        """
+        OPENQASM 3.0;
+        input float[64] duration;
+        input float[64] duration2;
+        cal {
+            delay[(duration) * 1s] $3, $4;
+        }
+        bool __bool_0__;
+        __bool_0__ = duration > duration2;
+        if (__bool_0__) {
+            cal {
+                delay[(duration2) * 1s] $3, $4;
+            }
+        }
+        """
+    ).strip()
+    qasm = my_program().to_ir()
+    assert qasm == expected

From 45c051af2a440ca49a216e9def877f275a4aef27 Mon Sep 17 00:00:00 2001
From: "Tim (Yi-Ting)" 
Date: Tue, 14 Nov 2023 16:48:23 -0500
Subject: [PATCH 0953/1165] feat: support AutoQASM program in AwsDevice.run
 (#795)

* enable freeparameter for pulse instruction

* AwsDevice.run supports AutoQASM program

* add test to increase coverage

* remove duplicated code
---
 src/braket/aws/aws_quantum_task.py            | 29 +++++++++++
 .../experimental/autoqasm/test_devices.py     | 48 ++++++++++++++++++-
 2 files changed, 76 insertions(+), 1 deletion(-)

diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py
index fa58d911..aefc2de2 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 (
@@ -583,6 +584,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/test/unit_tests/braket/experimental/autoqasm/test_devices.py b/test/unit_tests/braket/experimental/autoqasm/test_devices.py
index 4313ffb8..b63554ce 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_devices.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_devices.py
@@ -14,6 +14,7 @@
 """AutoQASM tests exercising device-specific targeting functionality.
 """
 
+import json
 from unittest.mock import Mock, patch
 
 import pytest
@@ -24,7 +25,8 @@
 from braket.device_schema.simulators import GateModelSimulatorDeviceCapabilities
 from braket.devices import Devices
 from braket.experimental.autoqasm import errors
-from braket.experimental.autoqasm.instructions import cnot, cphaseshift00, h, x
+from braket.experimental.autoqasm.instructions import cnot, cphaseshift00, h, rx, x
+from braket.parametric import FreeParameter
 
 RIGETTI_REGION = "us-west-1"
 
@@ -253,3 +255,47 @@ def my_program():
             cnot("$5", "$2")
 
     assert my_program().to_ir()
+
+
+@pytest.mark.parametrize(
+    "inputs,device_parameters",
+    [
+        (None, None),
+        ({"angle": 0.123}, {"foo": "bar"}),
+    ],
+)
+@patch("braket.aws.aws_device.AwsSession.copy_session")
+@patch("braket.aws.aws_device.AwsSession")
+def test_aws_device_run(
+    aws_session_init: Mock,
+    mock_copy_session: Mock,
+    aws_session: Mock,
+    inputs,
+    device_parameters,
+) -> None:
+    """Tests AwsDevice.run with AutoQASM program."""
+    aws_session_init.return_value = aws_session
+    mock_copy_session.return_value = aws_session
+
+    @aq.main
+    def my_program():
+        h(0)
+        rx(0, FreeParameter("angle"))
+
+    program = my_program()
+    aws_device = AwsDevice(Devices.Amazon.SV1.value)
+    _ = aws_device.run(program, shots=10, inputs=inputs, device_parameters=device_parameters)
+
+    run_call_args = aws_session.create_quantum_task.mock_calls[0].kwargs
+    run_call_args_action = json.loads(run_call_args["action"])
+
+    expected_run_call_args = {
+        "deviceArn": "arn:aws:braket:::device/quantum-simulator/amazon/sv1",
+        "outputS3Bucket": "amazon-braket-us-test-1-00000000",
+        "outputS3KeyPrefix": "tasks",
+        "shots": 10,
+    }
+    aws_session.create_quantum_task.assert_called_once()
+    assert expected_run_call_args.items() <= run_call_args.items()
+    assert run_call_args_action["source"] == program.to_ir()
+    assert run_call_args_action["inputs"] == inputs

From 87d48ffbdae6dbfff6324c4d8ba60868a5671937 Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Wed, 15 Nov 2023 12:35:47 -0500
Subject: [PATCH 0954/1165] feat: Support FreeParameter expressions in AutoQASM
 programs (#798)

---
 .../experimental/autoqasm/operators/utils.py  |   6 +-
 .../experimental/autoqasm/program/program.py  |  62 +++++---
 .../experimental/autoqasm/test_parameters.py  | 148 +++++++++++++++++-
 .../experimental/autoqasm/test_program.py     |  10 +-
 4 files changed, 198 insertions(+), 28 deletions(-)

diff --git a/src/braket/experimental/autoqasm/operators/utils.py b/src/braket/experimental/autoqasm/operators/utils.py
index a24b502e..2960585d 100644
--- a/src/braket/experimental/autoqasm/operators/utils.py
+++ b/src/braket/experimental/autoqasm/operators/utils.py
@@ -16,7 +16,7 @@
 
 from typing import Any, Union
 
-from braket.circuits import FreeParameter
+from braket.circuits import FreeParameterExpression
 from braket.experimental.autoqasm import program
 from braket.experimental.autoqasm import types as aq_types
 
@@ -39,8 +39,8 @@ def _register_and_convert_parameters(
     program_conversion_context.register_args(args)
     result = []
     for arg in args:
-        if isinstance(arg, FreeParameter):
-            var = program.get_program_conversion_context().get_parameter(arg.name)
+        if isinstance(arg, FreeParameterExpression):
+            var = program.get_program_conversion_context().get_expression_var(arg)
             result.append(var)
         else:
             result.append(arg)
diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py
index 9f2a241b..1a79a202 100644
--- a/src/braket/experimental/autoqasm/program/program.py
+++ b/src/braket/experimental/autoqasm/program/program.py
@@ -23,7 +23,9 @@
 from typing import Any, Optional, Union
 
 import oqpy.base
+from sympy import Symbol
 
+import braket.experimental.autoqasm.types as aq_types
 from braket.circuits.free_parameter import FreeParameter
 from braket.circuits.free_parameter_expression import FreeParameterExpression
 from braket.circuits.serialization import IRType, SerializableProgram
@@ -321,40 +323,60 @@ def register_args(self, args: list[Any]) -> None:
             args (list[Any]): Arguments passed to the main program or a subroutine.
         """
         for arg in args:
-            if isinstance(arg, FreeParameter):
-                self.register_parameter(arg)
-            elif isinstance(arg, FreeParameterExpression):
-                # TODO laurecap: Support for expressions
-                raise NotImplementedError(
-                    "Expressions of FreeParameters will be supported shortly!"
-                )
+            if isinstance(arg, FreeParameterExpression):
+                for free_symbol_name in self._free_symbol_names(arg):
+                    self.register_parameter(free_symbol_name)
+
+    @staticmethod
+    def _free_symbol_names(expr: FreeParameterExpression) -> Iterable[str]:
+        """Return the names of any free symbols found in the provided expression
+        which are Symbol objects.
+
+        Args:
+            expr (FreeParameterExpression): The expression in which to look for free symbols.
+
+        Returns:
+            Iterable[str]: The list of free symbol names in sorted order (sorted to ensure
+            that the order is deterministic).
+        """
+        return sorted([str(s) for s in expr._expression.free_symbols if isinstance(s, Symbol)])
 
-    def register_parameter(self, parameter: FreeParameter) -> None:
+    def register_parameter(self, parameter_name: str) -> None:
         """Register an input parameter if it has not already been registered.
         Only floats are currently supported.
 
         Args:
-            parameter (FreeParameter): The parameter to register with the program.
+            parameter_name (str): The name of the parameter to register with the program.
         """
-        if parameter.name not in self._free_parameters:
-            self._free_parameters[parameter.name] = oqpy.FloatVar("input", name=parameter.name)
+        if parameter_name not in self._free_parameters:
+            self._free_parameters[parameter_name] = oqpy.FloatVar("input", name=parameter_name)
 
-    def get_parameter(self, name: str) -> oqpy.FloatVar:
-        """Return a named oqpy.FloatVar that is used as a free parameter in the program.
+    def get_expression_var(self, expression: FreeParameterExpression) -> oqpy.FloatVar:
+        """Return an oqpy.FloatVar that represents the provided expression.
 
         Args:
-            name (str): The name of the parameter.
+            expression (FreeParameterExpression): The expression to represent.
 
         Raises:
-            ParameterNotFoundError: If there is no parameter with the given name registered
-            with the program.
+            ParameterNotFoundError: If the expression contains any free parameter which has
+            not already been registered with the program.
 
         Returns:
-            FloatVar: The associated variable.
+            FloatVar: The variable representing the expression.
         """
-        if name not in self._free_parameters:
-            raise errors.ParameterNotFoundError(f"Free parameter '{name}' was not found.")
-        return self._free_parameters[name]
+        # Validate that all of the free symbols are registered as free parameters.
+        for name in self._free_symbol_names(expression):
+            if name not in self._free_parameters:
+                raise errors.ParameterNotFoundError(f"Free parameter '{name}' was not found.")
+
+        # If the expression is just a standalone parameter, return the registered variable.
+        if isinstance(expression, FreeParameter):
+            return self._free_parameters[expression.name]
+
+        # Otherwise, create a new variable and declare it here
+        var = aq_types.FloatVar(init_expression=expression)
+        self.get_oqpy_program().declare(var)
+        return var
 
     def get_free_parameters(self) -> list[oqpy.FloatVar]:
         """Return a list of named oqpy.Vars that are used as free parameters in the program."""
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py
index 6633a36a..5a9aff16 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py
@@ -21,7 +21,17 @@
 from braket.default_simulator import StateVectorSimulator
 from braket.devices.local_simulator import LocalSimulator
 from braket.experimental.autoqasm import pulse
-from braket.experimental.autoqasm.instructions import cnot, cphaseshift, h, measure, ms, rx, rz, x
+from braket.experimental.autoqasm.instructions import (
+    cnot,
+    cphaseshift,
+    gpi,
+    h,
+    measure,
+    ms,
+    rx,
+    rz,
+    x,
+)
 from braket.tasks.local_quantum_task import LocalQuantumTask
 
 
@@ -775,3 +785,139 @@ def parametric(val: float):
     assert bound_prog.to_ir() == template.format(0)
     bound_prog = parametric(FreeParameter("val")).make_bound_program({"val": 1})
     assert bound_prog.to_ir() == template.format(1)
+
+
+def test_parameter_expressions():
+    """Test expressions of free parameters with numeric literals."""
+
+    @aq.main
+    def parametric():
+        expr = 2 * FreeParameter("theta")
+        gpi(0, expr)
+
+    expected = """OPENQASM 3.0;
+input float[64] theta;
+qubit[1] __qubits__;
+gpi(2*theta) __qubits__[0];"""
+    assert parametric().to_ir() == expected
+
+
+def test_sim_expressions():
+    @aq.main
+    def parametric():
+        rx(0, 2 * FreeParameter("phi"))
+        measure(0)
+
+    measurements = _test_parametric_on_local_sim(parametric(), {"phi": np.pi / 2})
+    assert 0 not in measurements["__bit_0__"]
+
+
+def test_multi_parameter_expressions():
+    """Test expressions of multiple free parameters."""
+
+    @aq.main
+    def parametric():
+        expr = FreeParameter("alpha") * FreeParameter("theta")
+        gpi(0, expr)
+
+    expected = """OPENQASM 3.0;
+input float[64] alpha;
+input float[64] theta;
+qubit[1] __qubits__;
+gpi(alpha*theta) __qubits__[0];"""
+    assert parametric().to_ir() == expected
+
+
+def test_bound_parameter_expressions():
+    """Test expressions of free parameters bound to specific values."""
+
+    @aq.main
+    def parametric():
+        rx(0, 2 * FreeParameter("phi"))
+
+    expected = """OPENQASM 3.0;
+float[64] phi = 1.5707963267948966;
+qubit[1] __qubits__;
+rx(2*phi) __qubits__[0];"""
+    assert parametric().make_bound_program({"phi": np.pi / 2}).to_ir() == expected
+
+
+def test_partially_bound_parameter_expressions():
+    """Test expressions of free parameters partially bound to specific values."""
+
+    @aq.main
+    def parametric():
+        expr = FreeParameter("prefactor") * FreeParameter("theta")
+        gpi(0, expr)
+
+    expected = """OPENQASM 3.0;
+float[64] prefactor = 3;
+input float[64] theta;
+qubit[1] __qubits__;
+gpi(prefactor*theta) __qubits__[0];"""
+    assert parametric().make_bound_program({"prefactor": 3}).to_ir() == expected
+
+
+def test_subroutine_parameter_expressions():
+    """Test expressions of free parameters passed to subroutines."""
+
+    @aq.subroutine
+    def rotate(theta: float):
+        rx(0, 3 * theta)
+
+    @aq.main
+    def parametric():
+        rotate(2 * FreeParameter("alpha"))
+
+    expected = """OPENQASM 3.0;
+def rotate(float[64] theta) {
+    rx(3 * theta) __qubits__[0];
+}
+input float[64] alpha;
+qubit[1] __qubits__;
+rotate(2*alpha);"""
+    assert parametric().to_ir() == expected
+
+
+def test_gate_parameter_expressions():
+    """Test expressions of free parameters passed to custom gates."""
+
+    @aq.gate
+    def rotate(q: aq.Qubit, theta: float):
+        rx(q, 3 * theta)
+
+    @aq.main
+    def parametric():
+        rotate(0, 2 * FreeParameter("alpha"))
+
+    expected = """OPENQASM 3.0;
+gate rotate(theta) q {
+    rx(3 * theta) q;
+}
+input float[64] alpha;
+qubit[1] __qubits__;
+rotate(2*alpha) __qubits__[0];"""
+    assert parametric().to_ir() == expected
+
+
+def test_conditional_parameter_expressions():
+    """Test expressions of free parameters contained in conditional statements."""
+
+    @aq.main
+    def parametric():
+        if 2 * FreeParameter("phi") > np.pi:
+            h(0)
+        measure(0)
+
+    expected = """OPENQASM 3.0;
+input float[64] phi;
+qubit[1] __qubits__;
+float[64] __float_0__ = 2*phi;
+bool __bool_1__;
+__bool_1__ = __float_0__ > 3.141592653589793;
+if (__bool_1__) {
+    h __qubits__[0];
+}
+bit __bit_2__;
+__bit_2__ = measure __qubits__[0];"""
+    assert parametric().to_ir() == expected
diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/braket/experimental/autoqasm/test_program.py
index 04dde260..7d809ed7 100644
--- a/test/unit_tests/braket/experimental/autoqasm/test_program.py
+++ b/test/unit_tests/braket/experimental/autoqasm/test_program.py
@@ -40,12 +40,14 @@ def test_program_conversion_context() -> None:
     assert len(prog._oqpy_program_stack) == 1
 
 
-def test_get_parameter_invalid_name():
-    """Tests the get_parameter function."""
+def test_get_expression_var_invalid_name():
+    """Tests the get_expression_var function."""
     prog = aq.program.ProgramConversionContext()
-    prog.register_parameter(FreeParameter("alpha"))
+    prog.register_parameter("alpha")
     with pytest.raises(aq.errors.ParameterNotFoundError):
-        prog.get_parameter("not_a_parameter")
+        prog.get_expression_var(FreeParameter("not_a_parameter"))
+    with pytest.raises(aq.errors.ParameterNotFoundError):
+        prog.get_expression_var(3 * FreeParameter("also_not_a_parameter"))
 
 
 def test_build_program() -> None:

From f1a549a678a057c5c1341d5099a7e7cdac7db333 Mon Sep 17 00:00:00 2001
From: Milan <30416311+krneta@users.noreply.github.com>
Date: Wed, 15 Nov 2023 10:00:39 -0800
Subject: [PATCH 0955/1165] update: default no longer returning RETIRED devices
 from get_devices (#796)

---
 src/braket/aws/aws_device.py                  | 12 +++++++-----
 src/braket/aws/aws_session.py                 | 12 ++++++++----
 test/integ_tests/test_device_creation.py      |  2 +-
 .../unit_tests/braket/aws/test_aws_session.py | 19 +++++++++++++++++++
 4 files changed, 35 insertions(+), 10 deletions(-)

diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py
index c9a20841..0cff446d 100644
--- a/src/braket/aws/aws_device.py
+++ b/src/braket/aws/aws_device.py
@@ -564,13 +564,15 @@ def get_devices(
             >>> AwsDevice.get_devices(types=['SIMULATOR'])
 
         Args:
-            arns (Optional[list[str]]): device ARN list, default is `None`
-            names (Optional[list[str]]): device name list, default is `None`
-            types (Optional[list[AwsDeviceType]]): device type list, default is `None`
+            arns (Optional[list[str]]): device ARN filter, default is `None`
+            names (Optional[list[str]]): device name filter, default is `None`
+            types (Optional[list[AwsDeviceType]]): device type filter, default is `None`
                 QPUs will be searched for all regions and simulators will only be
                 searched for the region of the current session.
-            statuses (Optional[list[str]]): device status list, default is `None`
-            provider_names (Optional[list[str]]): provider name list, default is `None`
+            statuses (Optional[list[str]]): device status filter, default is `None`. When `None`
+                is used, RETIRED devices will not be returned. To include RETIRED devices in
+                the results, use a filter that includes "RETIRED" for this parameter.
+            provider_names (Optional[list[str]]): provider name filter, default is `None`
             order_by (str): field to order result by, default is `name`.
                 Accepted values are ['arn', 'name', 'type', 'provider_name', 'status']
             aws_session (Optional[AwsSession]): An AWS session object.
diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py
index 9523f057..2ebc3c8d 100644
--- a/src/braket/aws/aws_session.py
+++ b/src/braket/aws/aws_session.py
@@ -622,10 +622,12 @@ def search_devices(
         all the filters `arns`, `names`, `types`, `statuses`, `provider_names`.
 
         Args:
-            arns (Optional[list[str]]): device ARN list, default is `None`.
-            names (Optional[list[str]]): device name list, default is `None`.
-            types (Optional[list[str]]): device type list, default is `None`.
-            statuses (Optional[list[str]]): device status list, default is `None`.
+            arns (Optional[list[str]]): device ARN filter, default is `None`.
+            names (Optional[list[str]]): device name filter, default is `None`.
+            types (Optional[list[str]]): device type filter, default is `None`.
+            statuses (Optional[list[str]]): device status filter, default is `None`. When `None`
+                is used, RETIRED devices will not be returned. To include RETIRED devices in
+                the results, use a filter that includes "RETIRED" for this parameter.
             provider_names (Optional[list[str]]): provider name list, default is `None`.
 
         Returns:
@@ -645,6 +647,8 @@ def search_devices(
                     continue
                 if statuses and result["deviceStatus"] not in statuses:
                     continue
+                if statuses is None and result["deviceStatus"] == "RETIRED":
+                    continue
                 if provider_names and result["providerName"] not in provider_names:
                     continue
                 results.append(result)
diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py
index decd7d87..4cb7de2b 100644
--- a/test/integ_tests/test_device_creation.py
+++ b/test/integ_tests/test_device_creation.py
@@ -18,7 +18,7 @@
 from braket.aws import AwsDevice
 from braket.devices import Devices
 
-RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-10"
+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"
 SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1"
 OQC_ARN = "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy"
diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py
index d2ee45a3..c61d2260 100644
--- a/test/unit_tests/braket/aws/test_aws_session.py
+++ b/test/unit_tests/braket/aws/test_aws_session.py
@@ -673,6 +673,18 @@ def test_cancel_job_surfaces_errors(exception_type, aws_session):
                 },
             ],
         ),
+        (
+            {"statuses": ["RETIRED"]},
+            [
+                {
+                    "deviceArn": "arn4",
+                    "deviceName": "name4",
+                    "deviceType": "QPU",
+                    "deviceStatus": "RETIRED",
+                    "providerName": "pname3",
+                },
+            ],
+        ),
         (
             {"provider_names": ["pname2"]},
             [
@@ -745,6 +757,13 @@ def test_search_devices(input, output, aws_session):
                     "deviceStatus": "ONLINE",
                     "providerName": "pname2",
                 },
+                {
+                    "deviceArn": "arn4",
+                    "deviceName": "name4",
+                    "deviceType": "QPU",
+                    "deviceStatus": "RETIRED",
+                    "providerName": "pname3",
+                },
             ]
         }
     ]

From 0485562077c83eec9c2780f78c8ec44995d6d684 Mon Sep 17 00:00:00 2001
From: ykharkov <112575584+ykharkov@users.noreply.github.com>
Date: Thu, 16 Nov 2023 11:02:29 -0500
Subject: [PATCH 0956/1165] doc: Add matrix expressions to docstrings (#756)

* Add matrix expressions to docstrings

* linter

* fix r-strings

* Capitalize H gate

* Rename theta->phi

* Address comments

* Update src/braket/circuits/gates.py

Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>

* add matrices to subrountines

* Remove [Registers this function into the circuit class] line from docstrings

---------

Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com>
Co-authored-by: Cody Wang 
Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Co-authored-by: Abe Coull 
Co-authored-by: Aaron Berdy 
---
 src/braket/circuits/gates.py | 719 +++++++++++++++++++++++++++++++----
 1 file changed, 639 insertions(+), 80 deletions(-)

diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py
index dbc2e1a0..9a4214c3 100644
--- a/src/braket/circuits/gates.py
+++ b/src/braket/circuits/gates.py
@@ -60,7 +60,14 @@
 
 
 class H(Gate):
-    """Hadamard gate."""
+    r"""Hadamard gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{H} = \frac{1}{\sqrt{2}} \begin{bmatrix}
+                1 & 1 \\
+                1 & -1 \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["H"])
@@ -91,7 +98,13 @@ def h(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""Hadamard gate.
+
+        Unitary matrix:
+
+            .. math:: \mathtt{H} = \frac{1}{\sqrt{2}} \begin{bmatrix}
+                    1 & 1 \\
+                    1 & -1 \end{bmatrix}.
 
         Args:
             target (QubitSetInput): Target qubit(s)
@@ -125,7 +138,14 @@ def h(
 
 
 class I(Gate):  # noqa: E742, E261
-    """Identity gate."""
+    r"""Identity gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{I} = \begin{bmatrix}
+                1 & 0 \\
+                0 & 1 \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["I"])
@@ -156,7 +176,13 @@ def i(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""Identity gate.
+
+        Unitary matrix:
+
+            .. math:: \mathtt{I} = \begin{bmatrix}
+                    1 & 0 \\
+                    0 & 1 \end{bmatrix}.
 
         Args:
             target (QubitSetInput): Target qubit(s)
@@ -190,7 +216,15 @@ def i(
 
 
 class X(Gate):
-    """Pauli-X gate."""
+    r"""Pauli-X gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{X} = \begin{bmatrix}
+                0 & 1 \\
+                1 & 0
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["X"])
@@ -221,7 +255,14 @@ def x(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""Pauli-X gate.
+
+        Unitary matrix:
+
+            .. math:: \mathtt{X} = \begin{bmatrix}
+                    0 & 1 \\
+                    1 & 0
+                    \end{bmatrix}.
 
         Args:
             target (QubitSetInput): Target qubit(s)
@@ -255,7 +296,15 @@ def x(
 
 
 class Y(Gate):
-    """Pauli-Y gate."""
+    r"""Pauli-Y gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{Y} = \begin{bmatrix}
+                0 & -i \\
+                i & 0
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["Y"])
@@ -286,7 +335,14 @@ def y(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""Pauli-Y gate.
+
+        Unitary matrix:
+
+            .. math:: \mathtt{Y} = \begin{bmatrix}
+                    0 & -i \\
+                    i & 0
+                    \end{bmatrix}.
 
         Args:
             target (QubitSetInput): Target qubit(s)
@@ -320,7 +376,15 @@ def y(
 
 
 class Z(Gate):
-    """Pauli-Z gate."""
+    r"""Pauli-Z gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{Z} = \begin{bmatrix}
+                1 & 0 \\
+                0 & -1
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["Z"])
@@ -351,7 +415,12 @@ def z(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""Pauli-Z gate.
+
+        .. math:: \mathtt{Z} = \begin{bmatrix}
+                1 & 0 \\
+                0 & -1
+                \end{bmatrix}.
 
         Args:
             target (QubitSetInput): Target qubit(s)
@@ -385,7 +454,15 @@ def z(
 
 
 class S(Gate):
-    """S gate."""
+    r"""S gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{S} = \begin{bmatrix}
+                1 & 0 \\
+                0 & i
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["S"])
@@ -416,7 +493,12 @@ def s(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""S gate.
+
+        .. math:: \mathtt{S} = \begin{bmatrix}
+                1 & 0 \\
+                0 & i
+                \end{bmatrix}.
 
         Args:
             target (QubitSetInput): Target qubit(s)
@@ -450,7 +532,15 @@ def s(
 
 
 class Si(Gate):
-    """Conjugate transpose of S gate."""
+    r"""Conjugate transpose of S gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{S}^\dagger = \begin{bmatrix}
+                1 & 0 \\
+                0 & -i
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["Si"])
@@ -481,7 +571,12 @@ def si(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""Conjugate transpose of S gate.
+
+        .. math:: \mathtt{S}^\dagger = \begin{bmatrix}
+                1 & 0 \\
+                0 & -i
+                \end{bmatrix}.
 
         Args:
             target (QubitSetInput): Target qubit(s)
@@ -515,7 +610,15 @@ def si(
 
 
 class T(Gate):
-    """T gate."""
+    r"""T gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{T} = \begin{bmatrix}
+                1 & 0 \\
+                0 & e^{i \pi/4}
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["T"])
@@ -546,7 +649,12 @@ def t(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""T gate.
+
+        .. math:: \mathtt{T} = \begin{bmatrix}
+                1 & 0 \\
+                0 & e^{i \pi/4}
+                \end{bmatrix}.
 
         Args:
             target (QubitSetInput): Target qubit(s)
@@ -580,7 +688,15 @@ def t(
 
 
 class Ti(Gate):
-    """Conjugate transpose of T gate."""
+    r"""Conjugate transpose of T gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{T}^\dagger = \begin{bmatrix}
+                1 & 0 \\
+                0 & e^{-i \pi/4}
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["Ti"])
@@ -611,7 +727,12 @@ def ti(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""Conjugate transpose of T gate.
+
+        .. math:: \mathtt{T}^\dagger = \begin{bmatrix}
+                1 & 0 \\
+                0 & e^{-i \pi/4}
+                \end{bmatrix}.
 
         Args:
             target (QubitSetInput): Target qubit(s)
@@ -645,7 +766,15 @@ def ti(
 
 
 class V(Gate):
-    """Square root of not gate."""
+    r"""Square root of X gate (V gate).
+
+    Unitary matrix:
+
+        .. math:: \mathtt{V} = \frac{1}{2}\begin{bmatrix}
+                1+i & 1-i \\
+                1-i & 1+i
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["V"])
@@ -676,7 +805,12 @@ def v(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""Square root of X gate (V gate).
+
+        .. math:: \mathtt{V} = \frac{1}{2}\begin{bmatrix}
+                1+i & 1-i \\
+                1-i & 1+i
+                \end{bmatrix}.
 
         Args:
             target (QubitSetInput): Target qubit(s)
@@ -710,7 +844,15 @@ def v(
 
 
 class Vi(Gate):
-    """Conjugate transpose of square root of not gate."""
+    r"""Conjugate transpose of square root of X gate (conjugate transpose of V).
+
+    Unitary matrix:
+
+        .. math:: \mathtt{V}^\dagger = \frac{1}{2}\begin{bmatrix}
+                1-i & 1+i \\
+                1+i & 1-i
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["Vi"])
@@ -741,7 +883,12 @@ def vi(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""Conjugate transpose of square root of X gate (conjugate transpose of V).
+
+        .. math:: \mathtt{V}^\dagger = \frac{1}{2}\begin{bmatrix}
+                1-i & 1+i \\
+                1+i & 1-i
+                \end{bmatrix}.
 
         Args:
             target (QubitSetInput): Target qubit(s)
@@ -778,7 +925,14 @@ def vi(
 
 
 class Rx(AngledGate):
-    """X-axis rotation gate.
+    r"""X-axis rotation gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{R_x}(\phi) = \begin{bmatrix}
+                \cos{(\phi/2)} & -i \sin{(\phi/2)} \\
+                -i \sin{(\phi/2)} & \cos{(\phi/2)}
+                \end{bmatrix}.
 
     Args:
         angle (Union[FreeParameterExpression, float]): angle in radians.
@@ -799,7 +953,7 @@ def _to_jaqcd(self, target: QubitSet, **kwargs) -> Any:
         return ir.Rx.construct(target=target[0], angle=self.angle)
 
     def to_matrix(self) -> np.ndarray:
-        """Returns a matrix representation of this gate.
+        r"""Returns a matrix representation of this gate.
         Returns:
             ndarray: The matrix representation of this gate.
         """
@@ -824,7 +978,12 @@ def rx(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""X-axis rotation gate.
+
+        .. math:: \mathtt{R_x}(\phi) = \begin{bmatrix}
+                \cos{(\phi/2)} & -i \sin{(\phi/2)} \\
+                -i \sin{(\phi/2)} & \cos{(\phi/2)}
+                \end{bmatrix}.
 
         Args:
             target (QubitSetInput): Target qubit(s).
@@ -858,7 +1017,14 @@ def rx(
 
 
 class Ry(AngledGate):
-    """Y-axis rotation gate.
+    r"""Y-axis rotation gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{R_y}(\phi) = \begin{bmatrix}
+                \cos{(\phi/2)} & -\sin{(\phi/2)} \\
+                \sin{(\phi/2)} & \cos{(\phi/2)}
+                \end{bmatrix}.
 
     Args:
         angle (Union[FreeParameterExpression, float]): angle in radians.
@@ -879,7 +1045,7 @@ def _to_jaqcd(self, target: QubitSet) -> Any:
         return ir.Ry.construct(target=target[0], angle=self.angle)
 
     def to_matrix(self) -> np.ndarray:
-        """Returns a matrix representation of this gate.
+        r"""Returns a matrix representation of this gate.
         Returns:
             ndarray: The matrix representation of this gate.
         """
@@ -904,7 +1070,12 @@ def ry(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""Y-axis rotation gate.
+
+        .. math:: \mathtt{R_y}(\phi) = \begin{bmatrix}
+                \cos{(\phi/2)} & -\sin{(\phi/2)} \\
+                \sin{(\phi/2)} & \cos{(\phi/2)}
+                \end{bmatrix}.
 
         Args:
             target (QubitSetInput): Target qubit(s).
@@ -923,6 +1094,7 @@ def ry(
         Returns:
             Iterable[Instruction]: Rx instruction.
 
+
         Examples:
             >>> circ = Circuit().ry(0, 0.15)
         """
@@ -938,7 +1110,14 @@ def ry(
 
 
 class Rz(AngledGate):
-    """Z-axis rotation gate.
+    r"""Z-axis rotation gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{R_z}(\phi) = \begin{bmatrix}
+                e^{-i \phi/2} & 0 \\
+                0 & e^{i \phi/2}
+                \end{bmatrix}.
 
     Args:
         angle (Union[FreeParameterExpression, float]): angle in radians.
@@ -980,7 +1159,12 @@ def rz(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""Z-axis rotation gate.
+
+        .. math:: \mathtt{R_z}(\phi) = \begin{bmatrix}
+                e^{-i \phi/2} & 0 \\
+                0 & e^{i \phi/2}
+                \end{bmatrix}.
 
         Args:
             target (QubitSetInput): Target qubit(s).
@@ -1014,7 +1198,14 @@ def rz(
 
 
 class PhaseShift(AngledGate):
-    """Phase shift gate.
+    r"""Phase shift gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{PhaseShift}(\phi) = \begin{bmatrix}
+                1 & 0 \\
+                0 & e^{i \phi}
+                \end{bmatrix}
 
     Args:
         angle (Union[FreeParameterExpression, float]): angle in radians.
@@ -1054,7 +1245,12 @@ def phaseshift(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""Phase shift gate.
+
+        .. math:: \mathtt{PhaseShift}(\phi) = \begin{bmatrix}
+                1 & 0 \\
+                0 & e^{i \phi}
+                \end{bmatrix}
 
         Args:
             target (QubitSetInput): Target qubit(s).
@@ -1095,7 +1291,17 @@ def phaseshift(
 
 
 class CNot(Gate):
-    """Controlled NOT gate."""
+    r"""Controlled NOT gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{CNOT} = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & 0 & 1 \\
+                0 & 0 & 1 & 0 \\
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["C", "X"])
@@ -1128,7 +1334,14 @@ def fixed_qubit_count() -> int:
     @staticmethod
     @circuit.subroutine(register=True)
     def cnot(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""Controlled NOT gate.
+
+        .. math:: \mathtt{CNOT} = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & 0 & 1 \\
+                0 & 0 & 1 & 0 \\
+                \end{bmatrix}.
 
         Args:
             control (QubitSetInput): Control qubit(s). The last control qubit
@@ -1155,7 +1368,17 @@ def cnot(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instru
 
 
 class Swap(Gate):
-    """Swap gate."""
+    r"""Swap gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{SWAP} = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 0 & 1 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & 0 & 1 \\
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["SWAP", "SWAP"])
@@ -1195,7 +1418,14 @@ def swap(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""Swap gate.
+
+        .. math:: \mathtt{SWAP} = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 0 & 1 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & 0 & 1 \\
+                \end{bmatrix}.
 
         Args:
             target1 (QubitInput): Target qubit 1 index.
@@ -1230,7 +1460,17 @@ def swap(
 
 
 class ISwap(Gate):
-    """ISwap gate."""
+    r"""ISwap gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{iSWAP} = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 0 & i & 0 \\
+                0 & i & 0 & 0 \\
+                0 & 0 & 0 & 1 \\
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["ISWAP", "ISWAP"])
@@ -1270,7 +1510,14 @@ def iswap(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""ISwap gate.
+
+        .. math:: \mathtt{iSWAP} = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 0 & i & 0 \\
+                0 & i & 0 & 0 \\
+                0 & 0 & 0 & 1 \\
+                \end{bmatrix}.
 
         Args:
             target1 (QubitInput): Target qubit 1 index.
@@ -1305,7 +1552,16 @@ def iswap(
 
 
 class PSwap(AngledGate):
-    """PSwap gate.
+    r"""PSwap gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{PSWAP}(\phi) = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 0 & e^{i \phi} & 0 \\
+                0 & e^{i \phi} & 0 & 0 \\
+                0 & 0 & 0 & 1 \\
+                \end{bmatrix}.
 
     Args:
         angle (Union[FreeParameterExpression, float]): angle in radians.
@@ -1357,7 +1613,14 @@ def pswap(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""PSwap gate.
+
+        .. math:: \mathtt{PSWAP}(\phi) = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 0 & e^{i \phi} & 0 \\
+                0 & e^{i \phi} & 0 & 0 \\
+                0 & 0 & 0 & 1 \\
+                \end{bmatrix}.
 
         Args:
             target1 (QubitInput): Target qubit 1 index.
@@ -1393,10 +1656,20 @@ def pswap(
 
 
 class XY(AngledGate):
-    """XY gate.
+    r"""XY gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{XY}(\phi) = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & \cos{(\phi/2)} & i\sin{(\phi/2)} & 0 \\
+                0 & i\sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\
+                0 & 0 & 0 & 1 \\
+            \end{bmatrix}.
 
     Reference: https://arxiv.org/abs/1912.04424v1
 
+
     Args:
         angle (Union[FreeParameterExpression, float]): angle in radians.
     """
@@ -1419,7 +1692,7 @@ def _to_jaqcd(self, target: QubitSet) -> Any:
         return ir.XY.construct(targets=[target[0], target[1]], angle=self.angle)
 
     def to_matrix(self) -> np.ndarray:
-        """Returns a matrix representation of this gate.
+        r"""Returns a matrix representation of this gate.
         Returns:
             ndarray: The matrix representation of this gate.
         """
@@ -1453,7 +1726,14 @@ def xy(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""XY gate.
+
+        .. math:: \mathtt{XY}(\phi) = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & \cos{(\phi/2)} & i\sin{(\phi/2)} & 0 \\
+                0 & i\sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\
+                0 & 0 & 0 & 1 \\
+            \end{bmatrix}.
 
         Args:
             target1 (QubitInput): Target qubit 1 index.
@@ -1489,7 +1769,16 @@ def xy(
 
 
 class CPhaseShift(AngledGate):
-    """Controlled phase shift gate.
+    r"""Controlled phase shift gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{CPhaseShift}(\phi) = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & 1 & 0 \\
+                0 & 0 & 0 & e^{i \phi}
+            \end{bmatrix}.
 
     Args:
         angle (Union[FreeParameterExpression, float]): angle in radians.
@@ -1527,7 +1816,14 @@ def cphaseshift(
         angle: Union[FreeParameterExpression, float],
         power: float = 1,
     ) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""Controlled phase shift gate.
+
+        .. math:: \mathtt{CPhaseShift}(\phi) = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & 1 & 0 \\
+                0 & 0 & 0 & e^{i \phi}
+            \end{bmatrix}.
 
         Args:
             control (QubitSetInput): Control qubit(s). The last control qubit
@@ -1558,7 +1854,16 @@ def cphaseshift(
 
 
 class CPhaseShift00(AngledGate):
-    """Controlled phase shift gate for phasing the \\|00> state.
+    r"""Controlled phase shift gate for phasing the \|00> state.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{CPhaseShift00}(\phi) = \begin{bmatrix}
+                e^{i \phi} & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & 1 & 0 \\
+                0 & 0 & 0 & 1
+                \end{bmatrix}.
 
     Args:
         angle (Union[FreeParameterExpression, float]): angle in radians.
@@ -1596,7 +1901,14 @@ def cphaseshift00(
         angle: Union[FreeParameterExpression, float],
         power: float = 1,
     ) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""Controlled phase shift gate for phasing the \|00> state.
+
+        .. math:: \mathtt{CPhaseShift00}(\phi) = \begin{bmatrix}
+                e^{i \phi} & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & 1 & 0 \\
+                0 & 0 & 0 & 1
+                \end{bmatrix}.
 
         Args:
             control (QubitSetInput): Control qubit(s). The last control qubit
@@ -1627,7 +1939,16 @@ def cphaseshift00(
 
 
 class CPhaseShift01(AngledGate):
-    """Controlled phase shift gate for phasing the \\|01> state.
+    r"""Controlled phase shift gate for phasing the \|01> state.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{CPhaseShift01}(\phi) = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & e^{i \phi} & 0 & 0 \\
+                0 & 0 & 1 & 0 \\
+                0 & 0 & 0 & 1
+            \end{bmatrix}.
 
     Args:
         angle (Union[FreeParameterExpression, float]): angle in radians.
@@ -1665,7 +1986,14 @@ def cphaseshift01(
         angle: Union[FreeParameterExpression, float],
         power: float = 1,
     ) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""Controlled phase shift gate for phasing the \|01> state.
+
+        .. math:: \mathtt{CPhaseShift01}(\phi) = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & e^{i \phi} & 0 & 0 \\
+                0 & 0 & 1 & 0 \\
+                0 & 0 & 0 & 1
+            \end{bmatrix}.
 
         Args:
             control (QubitSetInput): Control qubit(s). The last control qubit
@@ -1696,7 +2024,16 @@ def cphaseshift01(
 
 
 class CPhaseShift10(AngledGate):
-    """Controlled phase shift gate for phasing the \\|10> state.
+    r"""Controlled phase shift gate for phasing the \\|10> state.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{CPhaseShift10}(\phi) = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & e^{i \phi} & 0 \\
+                0 & 0 & 0 & 1
+                \end{bmatrix}.
 
     Args:
         angle (Union[FreeParameterExpression, float]): angle in radians.
@@ -1734,7 +2071,14 @@ def cphaseshift10(
         angle: Union[FreeParameterExpression, float],
         power: float = 1,
     ) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""Controlled phase shift gate for phasing the \\|10> state.
+
+        .. math:: \mathtt{CPhaseShift10}(\phi) = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & e^{i \phi} & 0 \\
+                0 & 0 & 0 & 1
+                \end{bmatrix}.
 
         Args:
             control (QubitSetInput): Control qubit(s). The last control qubit
@@ -1765,7 +2109,17 @@ def cphaseshift10(
 
 
 class CV(Gate):
-    """Controlled Sqrt of NOT gate."""
+    r"""Controlled Sqrt of X gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{CV} = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & 0.5+0.5i & 0.5-0.5i \\
+                0 & 0 & 0.5-0.5i & 0.5+0.5i
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["C", "V"])
@@ -1798,7 +2152,14 @@ def fixed_qubit_count() -> int:
     @staticmethod
     @circuit.subroutine(register=True)
     def cv(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""Controlled Sqrt of X gate.
+
+        .. math:: \mathtt{CV} = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & 0.5+0.5i & 0.5-0.5i \\
+                0 & 0 & 0.5-0.5i & 0.5+0.5i
+                \end{bmatrix}.
 
         Args:
             control (QubitSetInput): Control qubit(s). The last control qubit
@@ -1825,7 +2186,17 @@ def cv(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruct
 
 
 class CY(Gate):
-    """Controlled Pauli-Y gate."""
+    r"""Controlled Pauli-Y gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{CY} = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & 0 & -i \\
+                0 & 0 & i & 0
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["C", "Y"])
@@ -1858,7 +2229,14 @@ def fixed_qubit_count() -> int:
     @staticmethod
     @circuit.subroutine(register=True)
     def cy(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""Controlled Pauli-Y gate.
+
+        .. math:: \mathtt{CY} = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & 0 & -i \\
+                0 & 0 & i & 0
+                \end{bmatrix}.
 
         Args:
             control (QubitSetInput): Control qubit(s). The last control qubit
@@ -1885,7 +2263,17 @@ def cy(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruct
 
 
 class CZ(Gate):
-    """Controlled Pauli-Z gate."""
+    r"""Controlled Pauli-Z gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{CZ} = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & 1 & 0 \\
+                0 & 0 & 0 & -1
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["C", "Z"])
@@ -1910,7 +2298,14 @@ def fixed_qubit_count() -> int:
     @staticmethod
     @circuit.subroutine(register=True)
     def cz(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""Controlled Pauli-Z gate.
+
+        .. math:: \mathtt{CZ} = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & 1 & 0 \\
+                0 & 0 & 0 & -1
+                \end{bmatrix}.
 
         Args:
             control (QubitSetInput): Control qubit(s). The last control qubit
@@ -1937,7 +2332,17 @@ def cz(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruct
 
 
 class ECR(Gate):
-    """An echoed RZX(pi/2) gate."""
+    r"""An echoed RZX(pi/2) gate (ECR gate).
+
+    Unitary matrix:
+
+        .. math:: \mathtt{ECR} = \begin{bmatrix}
+                0 & 0 & 1 & i \\
+                0 & 0 & i & 1 \\
+                1 & -i & 0 & 0 \\
+                -i & 1 & 0 & 0
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["ECR", "ECR"])
@@ -1976,7 +2381,14 @@ def ecr(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""An echoed RZX(pi/2) gate (ECR gate).
+
+        .. math:: \mathtt{ECR} = \begin{bmatrix}
+                0 & 0 & 1 & i \\
+                0 & 0 & i & 1 \\
+                1 & -i & 0 & 0 \\
+                -i & 1 & 0 & 0
+                \end{bmatrix}.
 
         Args:
             target1 (QubitInput): Target qubit 1 index.
@@ -2011,7 +2423,16 @@ def ecr(
 
 
 class XX(AngledGate):
-    """Ising XX coupling gate.
+    r"""Ising XX coupling gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{XX}(\phi) = \begin{bmatrix}
+                \cos{(\phi/2)} & 0 & 0 & -i \sin{(\phi/2)} \\
+                0 & \cos{(\phi/2)} & -i \sin{(\phi/2)} & 0 \\
+                0 & -i \sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\
+                -i \sin{(\phi/2)} & 0 & 0 & \cos{(\phi/2)}
+                \end{bmatrix}.
 
     Reference: https://arxiv.org/abs/1707.06356
 
@@ -2037,7 +2458,7 @@ def _to_jaqcd(self, target: QubitSet) -> Any:
         return ir.XX.construct(targets=[target[0], target[1]], angle=self.angle)
 
     def to_matrix(self) -> np.ndarray:
-        """Returns a matrix representation of this gate.
+        r"""Returns a matrix representation of this gate.
         Returns:
             ndarray: The matrix representation of this gate.
         """
@@ -2071,7 +2492,14 @@ def xx(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""Ising XX coupling gate.
+
+        .. math:: \mathtt{XX}(\phi) = \begin{bmatrix}
+                \cos{(\phi/2)} & 0 & 0 & -i \sin{(\phi/2)} \\
+                0 & \cos{(\phi/2)} & -i \sin{(\phi/2)} & 0 \\
+                0 & -i \sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\
+                -i \sin{(\phi/2)} & 0 & 0 & \cos{(\phi/2)}
+                \end{bmatrix}.
 
         Args:
             target1 (QubitInput): Target qubit 1 index.
@@ -2107,7 +2535,16 @@ def xx(
 
 
 class YY(AngledGate):
-    """Ising YY coupling gate.
+    r"""Ising YY coupling gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{YY}(\phi) = \begin{bmatrix}
+                \cos{(\phi/2)} & 0 & 0 & i \sin{(\phi/2)} \\
+                0 & \cos{(\phi/2)} & -i \sin{(\phi/2)} & 0 \\
+                0 & -i \sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\
+                i \sin{(\phi/2)} & 0 & 0 & \cos{(\phi/2)}
+                \end{bmatrix}.
 
     Reference: https://arxiv.org/abs/1707.06356
 
@@ -2133,7 +2570,7 @@ def _to_jaqcd(self, target: QubitSet) -> Any:
         return ir.YY.construct(targets=[target[0], target[1]], angle=self.angle)
 
     def to_matrix(self) -> np.ndarray:
-        """Returns a matrix representation of this gate.
+        r"""Returns a matrix representation of this gate.
         Returns:
             ndarray: The matrix representation of this gate.
         """
@@ -2167,7 +2604,14 @@ def yy(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""Ising YY coupling gate.
+
+        .. math:: \mathtt{YY}(\phi) = \begin{bmatrix}
+                \cos{(\phi/2)} & 0 & 0 & i \sin{(\phi/2)} \\
+                0 & \cos{(\phi/2)} & -i \sin{(\phi/2)} & 0 \\
+                0 & -i \sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\
+                i \sin{(\phi/2)} & 0 & 0 & \cos{(\phi/2)}
+                \end{bmatrix}.
 
         Args:
             target1 (QubitInput): Target qubit 1 index.
@@ -2203,7 +2647,16 @@ def yy(
 
 
 class ZZ(AngledGate):
-    """Ising ZZ coupling gate.
+    r"""Ising ZZ coupling gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{ZZ}(\phi) = \begin{bmatrix}
+                e^{-i\phi/2} & 0 & 0 & 0 \\
+                0 & e^{i\phi/2} & 0 & 0 \\
+                0 & 0 & e^{i\phi/2} & 0 \\
+                0 & 0 & 0 & e^{-i\phi/2}
+            \end{bmatrix}.
 
     Reference: https://arxiv.org/abs/1707.06356
 
@@ -2257,7 +2710,14 @@ def zz(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""Ising ZZ coupling gate.
+
+        .. math:: \mathtt{ZZ}(\phi) = \begin{bmatrix}
+                e^{-i\phi/2} & 0 & 0 & 0 \\
+                0 & e^{i\phi/2} & 0 & 0 \\
+                0 & 0 & e^{i\phi/2} & 0 \\
+                0 & 0 & 0 & e^{-i\phi/2}
+            \end{bmatrix}.
 
         Args:
             target1 (QubitInput): Target qubit 1 index.
@@ -2296,7 +2756,21 @@ def zz(
 
 
 class CCNot(Gate):
-    """CCNOT gate or Toffoli gate."""
+    r"""CCNOT gate or Toffoli gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{CCNOT} = \begin{bmatrix}
+                1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 & 0 & 0 & 0 & 0  \\
+                0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\
+                0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\
+                0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\
+                0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\
+                0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\
+                0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["C", "C", "X"])
@@ -2341,7 +2815,18 @@ def ccnot(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""CCNOT gate or Toffoli gate.
+
+        .. math:: \mathtt{CCNOT} = \begin{bmatrix}
+                1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 & 0 & 0 & 0 & 0  \\
+                0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\
+                0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\
+                0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\
+                0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\
+                0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\
+                0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\
+                \end{bmatrix}.
 
         Args:
             control1 (QubitInput): Control qubit 1 index.
@@ -2379,7 +2864,21 @@ def ccnot(
 
 
 class CSwap(Gate):
-    """Controlled Swap gate."""
+    r"""Controlled Swap gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{CSWAP} = \begin{bmatrix}
+                1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 & 0 & 0 & 0 & 0  \\
+                0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\
+                0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\
+                0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\
+                0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\
+                0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\
+                0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\
+                \end{bmatrix}.
+    """
 
     def __init__(self):
         super().__init__(qubit_count=None, ascii_symbols=["C", "SWAP", "SWAP"])
@@ -2421,7 +2920,18 @@ def cswap(
         target2: QubitInput,
         power: float = 1,
     ) -> Instruction:
-        """Registers this function into the circuit class.
+        r"""Controlled Swap gate.
+
+        .. math:: \mathtt{CSWAP} = \begin{bmatrix}
+                1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 & 0 & 0 & 0 & 0  \\
+                0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\
+                0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\
+                0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\
+                0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\
+                0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\
+                0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\
+                \end{bmatrix}.
 
         Args:
             control (QubitSetInput): Control qubit(s). The last control qubit
@@ -2452,7 +2962,14 @@ def cswap(
 
 
 class GPi(AngledGate):
-    """IonQ GPi gate.
+    r"""IonQ GPi gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{GPi}(\phi) = \begin{bmatrix}
+                0 & e^{-i \phi} \\
+                e^{i \phi} & 0
+                \end{bmatrix}.
 
     Args:
         angle (Union[FreeParameterExpression, float]): angle in radians.
@@ -2497,7 +3014,12 @@ def gpi(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""IonQ GPi gate.
+
+        .. math:: \mathtt{GPi}(\phi) = \begin{bmatrix}
+                0 & e^{-i \phi} \\
+                e^{i \phi} & 0
+                \end{bmatrix}.
 
         Args:
             target (QubitSetInput): Target qubit(s).
@@ -2531,7 +3053,14 @@ def gpi(
 
 
 class GPi2(AngledGate):
-    """IonQ GPi2 gate.
+    r"""IonQ GPi2 gate.
+
+    Unitary matrix:
+
+        .. math:: \mathtt{GPi2}(\phi) = \begin{bmatrix}
+                1 & -i e^{-i \phi} \\
+                -i e^{i \phi} & 1
+            \end{bmatrix}.
 
     Args:
         angle (Union[FreeParameterExpression, float]): angle in radians.
@@ -2576,7 +3105,12 @@ def gpi2(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""IonQ GPi2 gate.
+
+        .. math:: \mathtt{GPi2}(\phi) = \begin{bmatrix}
+                1 & -i e^{-i \phi} \\
+                -i e^{i \phi} & 1
+            \end{bmatrix}.
 
         Args:
             target (QubitSetInput): Target qubit(s).
@@ -2610,12 +3144,26 @@ def gpi2(
 
 
 class MS(TripleAngledGate):
-    """IonQ Mølmer-Sørenson gate.
+    r"""IonQ Mølmer-Sørensen gate.
+
+    Unitary matrix:
+
+            .. math:: &\mathtt{MS}(\phi_0, \phi_1, \theta) =\\ &\begin{bmatrix}
+                    \cos{\frac{\theta}{2}} & 0 &
+                    0 & -ie^{-i (\phi_0 + \phi_1)}\sin{\frac{\theta}{2}} \\
+                    0 & \cos{\frac{\theta}{2}} &
+                    -ie^{-i (\phi_0 - \phi_1)}\sin{\frac{\theta}{2}} & 0 \\
+                    0 & -ie^{i (\phi_0 - \phi_1)}\sin{\frac{\theta}{2}} &
+                    \cos{\frac{\theta}{2}} & 0 \\
+                    -ie^{i (\phi_0 + \phi_1)}\sin{\frac{\theta}{2}} & 0
+                    & 0 & \cos{\frac{\theta}{2}}
+                    \end{bmatrix}.
 
     Args:
         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.
     """
 
     def __init__(
@@ -2689,7 +3237,18 @@ def ms(
         control_state: Optional[BasisStateInput] = None,
         power: float = 1,
     ) -> Iterable[Instruction]:
-        """Registers this function into the circuit class.
+        r"""IonQ Mølmer-Sørensen gate.
+
+        .. math:: &\mathtt{MS}(\phi_0, \phi_1, \theta) =\\ &\begin{bmatrix}
+                    \cos{\frac{\theta}{2}} & 0 &
+                    0 & -ie^{-i (\phi_0 + \phi_1)}\sin{\frac{\theta}{2}} \\
+                    0 & \cos{\frac{\theta}{2}} &
+                    -ie^{-i (\phi_0 - \phi_1)}\sin{\frac{\theta}{2}} & 0 \\
+                    0 & -ie^{i (\phi_0 - \phi_1)}\sin{\frac{\theta}{2}} &
+                    \cos{\frac{\theta}{2}} & 0 \\
+                    -ie^{i (\phi_0 + \phi_1)}\sin{\frac{\theta}{2}} & 0
+                    & 0 & \cos{\frac{\theta}{2}}
+                    \end{bmatrix}.
 
         Args:
             target1 (QubitInput): Target qubit 1 index.
@@ -2729,7 +3288,7 @@ def ms(
 
 
 class Unitary(Gate):
-    """Arbitrary unitary gate
+    """Arbitrary unitary gate.
 
     Args:
         matrix (numpy.ndarray): Unitary matrix which defines the gate.
@@ -2792,7 +3351,7 @@ def _transform_matrix_to_ir(matrix: np.ndarray) -> list:
     @staticmethod
     @circuit.subroutine(register=True)
     def unitary(targets: QubitSet, matrix: np.ndarray, display_name: str = "U") -> Instruction:
-        """Registers this function into the circuit class.
+        r"""Arbitrary unitary gate.
 
         Args:
             targets (QubitSet): Target qubits.
@@ -2849,7 +3408,7 @@ def pulse_sequence(self) -> PulseSequence:
 
     @property
     def parameters(self) -> list[FreeParameter]:
-        """Returns the list of `FreeParameter` s associated with the gate."""
+        r"""Returns the list of `FreeParameter` s associated with the gate."""
         return list(self._pulse_sequence.parameters)
 
     def bind_values(self, **kwargs) -> PulseGate:

From 20ac4e468e03cbb24ea43b619804e0fa9734b2dc Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Thu, 16 Nov 2023 19:46:06 -0500
Subject: [PATCH 0957/1165] Fix broken link to example notebook (#802)

---
 doc/examples-hybrid-jobs.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/examples-hybrid-jobs.rst b/doc/examples-hybrid-jobs.rst
index af873407..56a1d9ec 100644
--- a/doc/examples-hybrid-jobs.rst
+++ b/doc/examples-hybrid-jobs.rst
@@ -8,7 +8,7 @@ Learn more about hybrid jobs on Amazon Braket.
     :maxdepth: 2
 
 ************************************************************************************************************************************************************************************************
-`Creating your first Hybrid Job `_
+`Creating your first Hybrid Job `_
 ************************************************************************************************************************************************************************************************
 
 This tutorial shows how to run your first Amazon Braket Hybrid Job.

From 176c6a79393373c853de25d83665f2f7bd451f38 Mon Sep 17 00:00:00 2001
From: ci 
Date: Fri, 17 Nov 2023 04:07:56 +0000
Subject: [PATCH 0958/1165] prepare release v1.62.1

---
 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 19b0fba8..876595e2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
 # Changelog
 
+## v1.62.1 (2023-11-17)
+
+### Bug Fixes and Other Changes
+
+ * Fix broken link to example notebook
+ * update: default no longer returning RETIRED devices from get_devices
+
+### Documentation Changes
+
+ * Add matrix expressions to docstrings
+
 ## v1.62.0 (2023-11-09)
 
 ### Features
diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py
index 8cc1a9a4..26e9bb0a 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.62.1.dev0"
+__version__ = "1.62.1"

From 44a2bbb39d0142e74a4e39c3bf5aca0cf2785cfe Mon Sep 17 00:00:00 2001
From: ci 
Date: Fri, 17 Nov 2023 04:07:56 +0000
Subject: [PATCH 0959/1165] update development version to v1.62.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 26e9bb0a..c0e18d0a 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.62.1"
+__version__ = "1.62.2.dev0"

From 8cdf8a4f789c353968ca2683d79d4bafc6268b8e Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Fri, 17 Nov 2023 14:18:23 -0800
Subject: [PATCH 0960/1165] feat: remove *args from api decorators (#803)

---
 src/braket/experimental/autoqasm/api.py | 81 ++++++++++++++++---------
 1 file changed, 52 insertions(+), 29 deletions(-)

diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py
index bd19a1e4..04c1fe4d 100644
--- a/src/braket/experimental/autoqasm/api.py
+++ b/src/braket/experimental/autoqasm/api.py
@@ -13,6 +13,8 @@
 
 """This module implements the decorator API for generating programs using AutoQASM."""
 
+from __future__ import annotations
+
 import copy
 import functools
 import inspect
@@ -43,10 +45,11 @@
 
 
 def main(
-    *args,
+    func: Optional[Callable] = None,
+    *,
     num_qubits: Optional[int] = None,
     device: Optional[Union[Device, str]] = None,
-) -> Callable[[Any], aq_program.Program]:
+) -> Callable[..., aq_program.Program]:
     """Decorator that converts a function into a callable that returns
     a Program object containing the quantum program.
 
@@ -54,20 +57,22 @@ def main(
     function is called, and a new Program object is returned each time.
 
     Args:
+        func (Optional[Callable]): Decorated function. May be `None` in the case where decorator
+            is used with parentheses.
         num_qubits (Optional[int]): Configuration to set the total number of qubits to declare in
             the program.
         device (Optional[Union[Device, str]]): Configuration to set the target device for the
             program. Can be either an Device object or a valid Amazon Braket device ARN.
 
     Returns:
-        Callable[[Any], Program]: A callable which returns the converted
+        Callable[..., Program]: A callable which returns the converted
         quantum program when called.
     """
     if isinstance(device, str):
         device = AwsDevice(device)
 
     return _function_wrapper(
-        *args,
+        func,
         converter_callback=_convert_main,
         converter_args={
             "user_config": aq_program.UserConfig(
@@ -78,28 +83,36 @@ def main(
     )
 
 
-def subroutine(*args) -> Callable[[Any], aq_program.Program]:
+def subroutine(func: Optional[Callable] = None) -> Callable[..., aq_program.Program]:
     """Decorator that converts a function into a callable that will insert a subroutine into
     the quantum program.
 
+    Args:
+        func (Optional[Callable]): Decorated function. May be `None` in the case where decorator
+            is used with parentheses.
+
     Returns:
-        Callable[[Any], Program]: A callable which returns the converted
+        Callable[..., Program]: A callable which returns the converted
         quantum program when called.
     """
-    return _function_wrapper(*args, converter_callback=_convert_subroutine)
+    return _function_wrapper(func, converter_callback=_convert_subroutine)
 
 
-def gate(*args) -> Callable[[Any], None]:
+def gate(func: Optional[Callable] = None) -> Callable[..., None]:
     """Decorator that converts a function into a callable gate definition.
 
+    Args:
+        func (Optional[Callable]): Decorated function. May be `None` in the case where decorator
+            is used with parentheses.
+
     Returns:
-        Callable[[Any], None]: A callable which can be used as a custom gate inside an
+        Callable[..., None]: A callable which can be used as a custom gate inside an
         aq.function or inside another aq.gate.
     """
-    return _function_wrapper(*args, converter_callback=_convert_gate)
+    return _function_wrapper(func, converter_callback=_convert_gate)
 
 
-def gate_calibration(*args, implements: Callable, **kwargs) -> Callable[[], GateCalibration]:
+def gate_calibration(*, implements: Callable, **kwargs) -> Callable[[], GateCalibration]:
     """A decorator that register the decorated function as a gate calibration definition. The
     decorated function is added to a main program using `with_calibrations` method of the main
     program. The fixed values of qubits or angles that the calibration is implemented against
@@ -114,40 +127,50 @@ def gate_calibration(*args, implements: Callable, **kwargs) -> Callable[[], Gate
         `with_calibrations` method of the main program.
     """
     return _function_wrapper(
-        *args,
+        None,
         converter_callback=_convert_calibration,
         converter_args={"gate_function": implements, **kwargs},
     )
 
 
 def _function_wrapper(
-    *args: tuple[Any],
+    func: Optional[Callable],
+    *,
     converter_callback: Callable,
     converter_args: Optional[dict[str, Any]] = None,
-) -> Callable[[Any], aq_program.Program]:
+) -> Callable[..., Optional[Union[aq_program.Program, GateCalibration]]]:
     """Wrapping and conversion logic around the user function `f`.
 
     Args:
+        func (Optional[Callable]): Decorated function. May be `None` in the case where decorator
+            is used with parentheses.
         converter_callback (Callable): The function converter, e.g., _convert_main.
         converter_args (Optional[dict[str, Any]]): Extra arguments for the function converter.
 
     Returns:
-        Callable[[Any], Program]: A callable which returns the converted
-        quantum program when called.
+        Callable[..., Optional[Union[Program, GateCalibration]]]: A callable which
+        returns the converted construct, if any, when called.
     """
-    if not args:
-        # This the case where a decorator is called with only keyword args, for example:
-        #     @aq.main(num_qubits=4)
+    if not (func and callable(func)):
+        # This the case where a decorator is called either without a positional argument,
+        # or with a non-callable positional argument, which is as close of an approximation
+        # we can get to the case where a decorator is called with parentheses.
+        #
+        # There is still a false negative case, where we have something like:
+        #     @aq.main(callable_pos_arg)
         #     def my_function():
+        #
+        # but this is known limitation in python (consider the valid non-decorator usage
+        # `aq.main(my_function)` for an example of why this ambiguity exists).
+        #
         # To make this work, here we simply return a partial application of this function
-        # which still expects a Callable as the first argument.
+        # which still expects a Callable as the single positional argument.
         return functools.partial(
             _function_wrapper, converter_callback=converter_callback, converter_args=converter_args
         )
 
-    f = args[0]
-    if is_autograph_artifact(f):
-        return f
+    if is_autograph_artifact(func):
+        return func
 
     if not converter_args:
         converter_args = {}
@@ -159,12 +182,12 @@ def _wrapper(*args, **kwargs) -> Callable:
             optional_features=_autograph_optional_features(),
         )
         # Call the appropriate function converter
-        return converter_callback(f, options, args, kwargs, **converter_args)
+        return converter_callback(func, options, args, kwargs, **converter_args)
 
-    if inspect.isfunction(f) or inspect.ismethod(f):
-        _wrapper = functools.update_wrapper(_wrapper, f)
+    if inspect.isfunction(func) or inspect.ismethod(func):
+        _wrapper = functools.update_wrapper(_wrapper, func)
 
-    decorated_wrapper = tf_decorator.make_decorator(f, _wrapper)
+    decorated_wrapper = tf_decorator.make_decorator(func, _wrapper)
     return autograph_artifact(decorated_wrapper)
 
 
@@ -178,7 +201,7 @@ def _autograph_optional_features() -> tuple[converter.Feature]:
 def _convert_main(
     f: Callable,
     options: converter.ConversionOptions,
-    args: list[Any],
+    args: tuple[Any],
     kwargs: dict[str, Any],
     user_config: aq_program.UserConfig,
 ) -> None:
@@ -192,7 +215,7 @@ def _convert_main(
     Args:
         f (Callable): The function to be converted.
         options (converter.ConversionOptions): Converter options.
-        args (list[Any]): Arguments passed to the program when called.
+        args (tuple[Any]): Arguments passed to the program when called.
         kwargs (dict[str, Any]): Keyword arguments passed to the program when called.
         user_config (UserConfig): User-specified settings that influence program building.
     """

From 5d8e38c3807d41631450af494b0d65db2b745f2f Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Mon, 20 Nov 2023 10:52:30 -0800
Subject: [PATCH 0961/1165] infra: code freeze workflow (#767)

Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com>
---
 .github/workflows/code-freeze.yml | 39 +++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)
 create mode 100644 .github/workflows/code-freeze.yml

diff --git a/.github/workflows/code-freeze.yml b/.github/workflows/code-freeze.yml
new file mode 100644
index 00000000..5aff0abc
--- /dev/null
+++ b/.github/workflows/code-freeze.yml
@@ -0,0 +1,39 @@
+name: Code Freeze
+
+on:
+  pull_request:
+    branches:
+      - main
+  workflow_dispatch:
+
+permissions:
+  contents: read
+
+env:
+  FROZEN: ${{ vars.FROZEN }}
+  UNFROZEN_PREFIX: ${{ vars.UNFROZEN_PREFIX }}
+
+jobs:
+  check-pr-frozen-status:
+    runs-on: ubuntu-latest
+    steps:
+    - name: Fetch PR data and check if merge allowed
+      if: env.FROZEN == 'true'
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      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 [[ "$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
+        fi

From 2fff31113babefb618dfc96cc3320d869e604a38 Mon Sep 17 00:00:00 2001
From: Aaron Berdy 
Date: Mon, 4 Dec 2023 11:09:05 -0800
Subject: [PATCH 0962/1165] feat: build autoqasm program directly from main
 decorator (#804)

---
 .../1_Getting_started_with_AutoQASM.ipynb     |  15 +-
 .../2_Expressing_classical_control_flow.ipynb |  25 +-
 .../3_Iterative_phase_estimation.ipynb        |  24 +-
 examples/autoqasm/4_Native_programming.ipynb  | 220 +-----
 ...programming_and_dynamical_decoupling.ipynb |  17 +-
 .../6_Customize_gate_calibrations.ipynb       |  13 +-
 src/braket/experimental/autoqasm/__init__.py  |   2 +-
 src/braket/experimental/autoqasm/api.py       |  48 +-
 .../autoqasm/instructions/gates.py            |  76 +-
 .../autoqasm/instructions/qubits.py           |  12 +
 .../experimental/autoqasm/program/program.py  |  18 +-
 .../experimental/autoqasm/pulse/pulse.py      |   6 +-
 .../autoqasm/transpiler/transpiler.py         |   1 +
 .../braket/experimental/autoqasm/conftest.py  |   6 +-
 .../braket/experimental/autoqasm/test_api.py  | 648 ++++++++++--------
 .../experimental/autoqasm/test_converters.py  |  26 +-
 .../experimental/autoqasm/test_devices.py     |  80 +--
 .../autoqasm/test_gate_calibrations.py        |  27 +-
 .../autoqasm/test_gate_decorator.py           | 121 ++--
 .../autoqasm/test_gate_definitions.py         |   4 +-
 .../experimental/autoqasm/test_operators.py   |  84 ++-
 .../experimental/autoqasm/test_parameters.py  | 306 +++++----
 .../experimental/autoqasm/test_pragmas.py     |  26 +-
 .../experimental/autoqasm/test_program.py     |  15 +-
 .../experimental/autoqasm/test_pulse.py       |  35 +-
 ...config.py => test_serialization_config.py} |   8 +-
 .../experimental/autoqasm/test_transpiler.py  |  83 +--
 .../experimental/autoqasm/test_types.py       | 153 ++---
 28 files changed, 1030 insertions(+), 1069 deletions(-)
 rename test/unit_tests/braket/experimental/autoqasm/{test_seralization_config.py => test_serialization_config.py} (95%)

diff --git a/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb b/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb
index 663a998f..4b4159d4 100644
--- a/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb
+++ b/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb
@@ -54,7 +54,7 @@
    "id": "2c59985b",
    "metadata": {},
    "source": [
-    "The quantum program is returned by calling the decorated `bell_state` function. You can view the generated OpenQASM script with the `to_ir()` method. This can help you debug if you are already familiar with OpenQASM."
+    "The quantum program is defined by the decorated `bell_state` function. You can view the generated OpenQASM script with the `to_ir()` method. This can help you debug if you are already familiar with OpenQASM."
    ]
   },
   {
@@ -80,8 +80,7 @@
     }
    ],
    "source": [
-    "bell_state_program = bell_state()\n",
-    "print(bell_state_program.to_ir())"
+    "print(bell_state.to_ir())"
    ]
   },
   {
@@ -102,13 +101,13 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "measurement counts:  Counter({'00': 63, '11': 37})\n"
+      "measurement counts:  Counter({'11': 51, '00': 49})\n"
      ]
     }
    ],
    "source": [
     "device = LocalSimulator()\n",
-    "result = device.run(bell_state_program, shots=100).result()\n",
+    "result = device.run(bell_state, shots=100).result()\n",
     "counts = Counter(result.measurements[\"c\"])\n",
     "print(\"measurement counts: \", counts)"
    ]
@@ -121,10 +120,8 @@
    "outputs": [
     {
      "data": {
-      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjIAAAGwCAYAAACzXI8XAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAgqklEQVR4nO3de3BU9d3H8c+G3IBkNyaEhJhEUJGACLRBYNEqptGUUiomVUqZARGhYqDCesGMAqIwkU41KA1gLQbbMV5oC5Z2imiE2GLCJYLXkqJFk06yQdAkEMhFss8fHXa6DwTCsuHsL75fM2eGc87u2S+Zib455+yuzePxeAQAAGCgEKsHAAAA8BchAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjhVo9QFdrb29XTU2NoqOjZbPZrB4HAAB0gsfj0dGjR5WUlKSQkI7Pu3T7kKmpqVFKSorVYwAAAD9UV1crOTm5w/3dPmSio6Ml/fcHYbfbLZ4GAAB0RmNjo1JSUrz/H+9Itw+ZU5eT7HY7IQMAgGHOdVsIN/sCAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADBWqNUDmKz/w3+1egQgqH3+5ASrRwDQzXFGBgAAGIuQAQAAxrI0ZB577DHZbDafJS0tzbu/ublZubm5iouLU1RUlHJyclRXV2fhxAAAIJhYfkbm6quvVm1trXf5xz/+4d23YMECbd68WRs2bFBpaalqamqUnZ1t4bQAACCYWH6zb2hoqBITE0/b3tDQoHXr1qm4uFgZGRmSpKKiIg0ePFjl5eUaM2bMxR4VAAAEGcvPyBw4cEBJSUm6/PLLNXXqVFVVVUmSKioq1NbWpszMTO9j09LSlJqaqrKysg6P19LSosbGRp8FAAB0T5aGzOjRo7V+/Xpt2bJFa9as0cGDB/W9731PR48eldvtVnh4uGJiYnyek5CQILfb3eEx8/Pz5XA4vEtKSkoX/y0AAIBVLL20NH78eO+fhw0bptGjR+uyyy7Ta6+9pp49e/p1zLy8PLlcLu96Y2MjMQMAQDdl+aWl/xUTE6OrrrpKn376qRITE9Xa2qr6+nqfx9TV1Z3xnppTIiIiZLfbfRYAANA9BVXIHDt2TJ999pn69eun9PR0hYWFqaSkxLu/srJSVVVVcjqdFk4JAACChaWXlh544AFNnDhRl112mWpqarRkyRL16NFDU6ZMkcPh0MyZM+VyuRQbGyu73a558+bJ6XTyjiUAACDJ4pD5z3/+oylTpujIkSOKj4/X9ddfr/LycsXHx0uSCgoKFBISopycHLW0tCgrK0urV6+2cmQAABBEbB6Px2P1EF2psbFRDodDDQ0NAb9fhi+NBM6OL40E4K/O/v/b8g/EA4Bgxz9agI5Z/Q+WoLrZFwAA4HwQMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjBU3IPPnkk7LZbJo/f753W3Nzs3JzcxUXF6eoqCjl5OSorq7OuiEBAEBQCYqQ2b17t5577jkNGzbMZ/uCBQu0efNmbdiwQaWlpaqpqVF2drZFUwIAgGBjecgcO3ZMU6dO1fPPP69LLrnEu72hoUHr1q3T008/rYyMDKWnp6uoqEjvvvuuysvLLZwYAAAEC8tDJjc3VxMmTFBmZqbP9oqKCrW1tflsT0tLU2pqqsrKyjo8XktLixobG30WAADQPYVa+eKvvPKK3nvvPe3evfu0fW63W+Hh4YqJifHZnpCQILfb3eEx8/PztXTp0kCPCgAAgpBlZ2Sqq6t133336aWXXlJkZGTAjpuXl6eGhgbvUl1dHbBjAwCA4GJZyFRUVOjQoUP67ne/q9DQUIWGhqq0tFTPPvusQkNDlZCQoNbWVtXX1/s8r66uTomJiR0eNyIiQna73WcBAADdk2WXlr7//e/rww8/9Nk2Y8YMpaWlaeHChUpJSVFYWJhKSkqUk5MjSaqsrFRVVZWcTqcVIwMAgCBjWchER0dr6NChPtt69+6tuLg47/aZM2fK5XIpNjZWdrtd8+bNk9Pp1JgxY6wYGQAABBlLb/Y9l4KCAoWEhCgnJ0ctLS3KysrS6tWrrR4LAAAEiaAKme3bt/usR0ZGqrCwUIWFhdYMBAAAgprlnyMDAADgL0IGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGMvSkFmzZo2GDRsmu90uu90up9Opv/3tb979zc3Nys3NVVxcnKKiopSTk6O6ujoLJwYAAMHE0pBJTk7Wk08+qYqKCu3Zs0cZGRm69dZb9fHHH0uSFixYoM2bN2vDhg0qLS1VTU2NsrOzrRwZAAAEkVArX3zixIk+68uXL9eaNWtUXl6u5ORkrVu3TsXFxcrIyJAkFRUVafDgwSovL9eYMWOsGBkAAASRoLlH5uTJk3rllVfU1NQkp9OpiooKtbW1KTMz0/uYtLQ0paamqqysrMPjtLS0qLGx0WcBAADdk+Uh8+GHHyoqKkoRERG65557tHHjRg0ZMkRut1vh4eGKiYnxeXxCQoLcbneHx8vPz5fD4fAuKSkpXfw3AAAAVrE8ZAYNGqR9+/Zp586dmjNnjqZPn65PPvnE7+Pl5eWpoaHBu1RXVwdwWgAAEEz8ukemurpaNptNycnJkqRdu3apuLhYQ4YM0ezZs8/rWOHh4bryyislSenp6dq9e7eeeeYZTZ48Wa2traqvr/c5K1NXV6fExMQOjxcREaGIiIjz/0sBAADj+HVG5mc/+5m2bdsmSXK73br55pu1a9cuPfLII3r88ccvaKD29na1tLQoPT1dYWFhKikp8e6rrKxUVVWVnE7nBb0GAADoHvw6I/PRRx9p1KhRkqTXXntNQ4cO1Y4dO7R161bdc889Wrx4caeOk5eXp/Hjxys1NVVHjx5VcXGxtm/frjfeeEMOh0MzZ86Uy+VSbGys7Ha75s2bJ6fTyTuWAACAJD9Dpq2tzXv55q233tKPf/xjSf99V1FtbW2nj3Po0CFNmzZNtbW1cjgcGjZsmN544w3dfPPNkqSCggKFhIQoJydHLS0tysrK0urVq/0ZGQAAdEN+hczVV1+ttWvXasKECXrzzTf1xBNPSJJqamoUFxfX6eOsW7furPsjIyNVWFiowsJCf8YEAADdnF/3yKxYsULPPfecxo0bpylTpmj48OGSpD//+c/eS04AAABdza8zMuPGjdPhw4fV2NioSy65xLt99uzZ6t27d8CGAwAAOBu/zshkZGTo6NGjPhEjSbGxsZo8eXJABgMAADgXv0Jm+/btam1tPW17c3Oz/v73v1/wUAAAAJ1xXpeWPvjgA++fP/nkE5+vCjh58qS2bNmiSy+9NHDTAQAAnMV5hcyIESNks9lks9m830j9v3r27KlVq1YFbDgAAICzOa+QOXjwoDwejy6//HLt2rVL8fHx3n3h4eHq27evevToEfAhAQAAzuS8Quayyy6T9N+vEQAAALCaX2+/lqQDBw5o27ZtOnTo0Glh09mvKAAAALgQfoXM888/rzlz5qhPnz5KTEyUzWbz7rPZbIQMAAC4KPwKmWXLlmn58uVauHBhoOcBAADoNL8+R+brr7/W7bffHuhZAAAAzotfIXP77bdr69atgZ4FAADgvPh1aenKK6/UokWLVF5ermuuuUZhYWE++3/xi18EZDgAAICz8StkfvOb3ygqKkqlpaUqLS312Wez2QgZAABwUfgVMgcPHgz0HAAAAOfNr3tkAAAAgoFfZ2Tuuuuus+5/4YUX/BoGAADgfPgVMl9//bXPeltbmz766CPV19ef8cskAQAAuoJfIbNx48bTtrW3t2vOnDm64oorLngoAACAzgjYPTIhISFyuVwqKCgI1CEBAADOKqA3+3722Wf65ptvAnlIAACADvl1acnlcvmsezwe1dbW6q9//aumT58ekMEAAADOxa+Q2bt3r896SEiI4uPj9dRTT53zHU0AAACB4lfIbNu2LdBzAAAAnDe/QuaUL7/8UpWVlZKkQYMGKT4+PiBDAQAAdIZfN/s2NTXprrvuUr9+/XTDDTfohhtuUFJSkmbOnKnjx48HekYAAIAz8itkXC6XSktLtXnzZtXX16u+vl6vv/66SktLdf/99wd6RgAAgDPy69LSH//4R/3hD3/QuHHjvNt++MMfqmfPnrrjjju0Zs2aQM0HAADQIb/OyBw/flwJCQmnbe/bty+XlgAAwEXjV8g4nU4tWbJEzc3N3m0nTpzQ0qVL5XQ6AzYcAADA2fh1aWnlypX6wQ9+oOTkZA0fPlyS9P777ysiIkJbt24N6IAAAAAd8StkrrnmGh04cEAvvfSS9u/fL0maMmWKpk6dqp49ewZ0QAAAgI74FTL5+flKSEjQrFmzfLa/8MIL+vLLL7Vw4cKADAcAAHA2ft0j89xzzyktLe207VdffbXWrl17wUMBAAB0hl8h43a71a9fv9O2x8fHq7a29oKHAgAA6Ay/QiYlJUU7duw4bfuOHTuUlJR0wUMBAAB0hl/3yMyaNUvz589XW1ubMjIyJEklJSV66KGH+GRfAABw0fgVMg8++KCOHDmie++9V62trZKkyMhILVy4UHl5eQEdEAAAoCN+hYzNZtOKFSu0aNEi/fOf/1TPnj01cOBARUREBHo+AACADvkVMqdERUXp2muvDdQsAAAA58Wvm30BAACCASEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADCWpSGTn5+va6+9VtHR0erbt68mTZqkyspKn8c0NzcrNzdXcXFxioqKUk5Ojurq6iyaGAAABBNLQ6a0tFS5ubkqLy/Xm2++qba2Nt1yyy1qamryPmbBggXavHmzNmzYoNLSUtXU1Cg7O9vCqQEAQLAItfLFt2zZ4rO+fv169e3bVxUVFbrhhhvU0NCgdevWqbi4WBkZGZKkoqIiDR48WOXl5RozZowVYwMAgCARVPfINDQ0SJJiY2MlSRUVFWpra1NmZqb3MWlpaUpNTVVZWdkZj9HS0qLGxkafBQAAdE9BEzLt7e2aP3++rrvuOg0dOlSS5Ha7FR4erpiYGJ/HJiQkyO12n/E4+fn5cjgc3iUlJaWrRwcAABYJmpDJzc3VRx99pFdeeeWCjpOXl6eGhgbvUl1dHaAJAQBAsLH0HplT5s6dq7/85S965513lJyc7N2emJio1tZW1dfX+5yVqaurU2Ji4hmPFRERoYiIiK4eGQAABAFLz8h4PB7NnTtXGzdu1Ntvv60BAwb47E9PT1dYWJhKSkq82yorK1VVVSWn03mxxwUAAEHG0jMyubm5Ki4u1uuvv67o6GjvfS8Oh0M9e/aUw+HQzJkz5XK5FBsbK7vdrnnz5snpdPKOJQAAYG3IrFmzRpI0btw4n+1FRUW68847JUkFBQUKCQlRTk6OWlpalJWVpdWrV1/kSQEAQDCyNGQ8Hs85HxMZGanCwkIVFhZehIkAAIBJguZdSwAAAOeLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGsjRk3nnnHU2cOFFJSUmy2WzatGmTz36Px6PFixerX79+6tmzpzIzM3XgwAFrhgUAAEHH0pBpamrS8OHDVVhYeMb9v/zlL/Xss89q7dq12rlzp3r37q2srCw1Nzdf5EkBAEAwCrXyxcePH6/x48efcZ/H49HKlSv16KOP6tZbb5Uk/e53v1NCQoI2bdqkn/70pxdzVAAAEISC9h6ZgwcPyu12KzMz07vN4XBo9OjRKisr6/B5LS0tamxs9FkAAED3FLQh43a7JUkJCQk+2xMSErz7ziQ/P18Oh8O7pKSkdOmcAADAOkEbMv7Ky8tTQ0ODd6murrZ6JAAA0EWCNmQSExMlSXV1dT7b6+rqvPvOJCIiQna73WcBAADdU9CGzIABA5SYmKiSkhLvtsbGRu3cuVNOp9PCyQAAQLCw9F1Lx44d06effupdP3jwoPbt26fY2FilpqZq/vz5WrZsmQYOHKgBAwZo0aJFSkpK0qRJk6wbGgAABA1LQ2bPnj266aabvOsul0uSNH36dK1fv14PPfSQmpqaNHv2bNXX1+v666/Xli1bFBkZadXIAAAgiFgaMuPGjZPH4+lwv81m0+OPP67HH3/8Ik4FAABMEbT3yAAAAJwLIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMZUTIFBYWqn///oqMjNTo0aO1a9cuq0cCAABBIOhD5tVXX5XL5dKSJUv03nvvafjw4crKytKhQ4esHg0AAFgs6EPm6aef1qxZszRjxgwNGTJEa9euVa9evfTCCy9YPRoAALBYqNUDnE1ra6sqKiqUl5fn3RYSEqLMzEyVlZWd8TktLS1qaWnxrjc0NEiSGhsbAz5fe8vxgB8T6E664vfOCvyuAx3rqt/zU8f1eDxnfVxQh8zhw4d18uRJJSQk+GxPSEjQ/v37z/ic/Px8LV269LTtKSkpXTIjgI45Vlo9AYCu1tW/50ePHpXD4ehwf1CHjD/y8vLkcrm86+3t7frqq68UFxcnm81m4WToao2NjUpJSVF1dbXsdrvV4wDoAvyef3t4PB4dPXpUSUlJZ31cUIdMnz591KNHD9XV1flsr6urU2Ji4hmfExERoYiICJ9tMTExXTUigpDdbuc/cEA3x+/5t8PZzsScEtQ3+4aHhys9PV0lJSXebe3t7SopKZHT6bRwMgAAEAyC+oyMJLlcLk2fPl0jR47UqFGjtHLlSjU1NWnGjBlWjwYAACwW9CEzefJkffnll1q8eLHcbrdGjBihLVu2nHYDMBAREaElS5acdmkRQPfB7zn+P5vnXO9rAgAACFJBfY8MAADA2RAyAADAWIQMAAAwFiEDAACMRcjASIWFherfv78iIyM1evRo7dq1y7uvublZubm5iouLU1RUlHJyck77UEUAwe2dd97RxIkTlZSUJJvNpk2bNvns/9Of/qRbbrnF+6nt+/bts2ROWI+QgXFeffVVuVwuLVmyRO+9956GDx+urKwsHTp0SJK0YMECbd68WRs2bFBpaalqamqUnZ1t8dQAzkdTU5OGDx+uwsLCDvdff/31WrFixUWeDMGGt1/DOKNHj9a1116rX//615L++2nPKSkpmjdvnubMmaP4+HgVFxfrJz/5iSRp//79Gjx4sMrKyjRmzBgrRwfgB5vNpo0bN2rSpEmn7fv88881YMAA7d27VyNGjLjos8F6nJGBUVpbW1VRUaHMzEzvtpCQEGVmZqqsrEwVFRVqa2vz2Z+WlqbU1FSVlZVZMTIAoAsRMjDK4cOHdfLkydM+2TkhIUFut1tut1vh4eGnfVHoqf0AgO6FkAEAAMYiZGCUPn36qEePHqe9C6murk6JiYlKTExUa2ur6uvrz7gfANC9EDIwSnh4uNLT01VSUuLd1t7erpKSEjmdTqWnpyssLMxnf2VlpaqqquR0Oq0YGQDQhYL+26+B/8/lcmn69OkaOXKkRo0apZUrV6qpqUkzZsyQw+HQzJkz5XK5FBsbK7vdrnnz5snpdPKOJcAgx44d06effupdP3jwoPbt26fY2Filpqbqq6++UlVVlWpqaiT99x8skrxnZvEt4gEMtGrVKk9qaqonPDzcM2rUKE95ebl334kTJzz33nuv55JLLvH06tXLc9ttt3lqa2stnBbA+dq2bZtH0mnL9OnTPR6Px1NUVHTG/UuWLLF0blx8fI4MAAAwFvfIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAC4YOPGjdP8+fM73N+/f3+tXLnyoszy+eefy2azad++fRfl9QBYi5AB0OV2796t2bNne9dtNps2bdrU6eevX79eMTExnXpsSkqKamtrNXTo0POcEoCJ+NJIAF0uPj7+orxOa2urwsPD+dJA4FuEMzIAAuKbb77R3Llz5XA41KdPHy1atEinvsrtfy8t9e/fX5J02223yWazedfff/993XTTTYqOjpbdbld6err27Nmj7du3a8aMGWpoaJDNZpPNZtNjjz3mPdYTTzyhadOmyW63a/bs2addWtq+fbtsNptKSko0cuRI9erVS2PHjvV+W/Ipy5YtU9++fRUdHa27775bDz/8sEaMGOHdv337do0aNUq9e/dWTEyMrrvuOn3xxRdd9eME0EmEDICAePHFFxUaGqpdu3bpmWee0dNPP63f/va3pz1u9+7dkqSioiLV1tZ616dOnark5GTt3r1bFRUVevjhhxUWFqaxY8dq5cqVstvtqq2tVW1trR544AHv8X71q19p+PDh2rt3rxYtWtThfI888oieeuop7dmzR6Ghobrrrru8+1566SUtX75cK1asUEVFhVJTU7VmzRrv/m+++UaTJk3SjTfeqA8++EBlZWWaPXu2bDbbBf/cAFwYLi0BCIiUlBQVFBTIZrNp0KBB+vDDD1VQUKBZs2b5PO7UZaaYmBifS0BVVVV68MEHlZaWJkkaOHCgd5/D4ZDNZjvjJaOMjAzdf//93vXPP//8jPMtX75cN954oyTp4Ycf1oQJE9Tc3KzIyEitWrVKM2fO1IwZMyRJixcv1tatW3Xs2DFJUmNjoxoaGvSjH/1IV1xxhSRp8ODB5/XzAdA1OCMDICDGjBnjc4bC6XTqwIEDOnnyZKee73K5dPfddyszM1NPPvmkPvvss049b+TIkZ163LBhw7x/7tevnyTp0KFDkqTKykqNGjXK5/H/ux4bG6s777xTWVlZmjhxop555hnV1tZ26nUBdC1CBkBQeOyxx/Txxx9rwoQJevvttzVkyBBt3LjxnM/r3bt3p44fFhbm/fOp4Gpvb+/0fEVFRSorK9PYsWP16quv6qqrrlJ5eXmnnw+gaxAyAAJi586dPuvl5eUaOHCgevTocdpjw8LCznim5qqrrtKCBQu0detWZWdnq6ioSJIUHh7e6TM7/hg0aJD3Xp1T/v+6JH3nO99RXl6e3n33XQ0dOlTFxcVdNhOAziFkAAREVVWVXC6XKisr9fLLL2vVqlW67777zvjY/v37q6SkRG63W19//bVOnDihuXPnavv27friiy+0Y8cO7d6923sfSv/+/XXs2DGVlJTo8OHDOn78eEBnnzdvntatW6cXX3xRBw4c0LJly/TBBx94z9wcPHhQeXl5Kisr0xdffKGtW7fqwIED3CcDBAFu9gUQENOmTdOJEyc0atQo9ejRQ/fdd5/Ph+D9r6eeekoul0vPP/+8Lr30Uv3rX//SkSNHNG3aNNXV1alPnz7Kzs7W0qVLJUljx47VPffco8mTJ+vIkSNasmSJ9y3YgTB16lT9+9//1gMPPKDm5mbdcccduvPOO7Vr1y5JUq9evbR//369+OKLOnLkiPr166fc3Fz9/Oc/D9gMAPxj85z6oAcAgNfNN9+sxMRE/f73v7d6FABnwRkZAN96x48f19q1a5WVlaUePXro5Zdf1ltvvaU333zT6tEAnANnZAB86504cUITJ07U3r171dzcrEGDBunRRx9Vdna21aMBOAdCBgAAGIt3LQEAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACM9X8aMoGHN2LscQAAAABJRU5ErkJggg==",
-      "text/plain": [
-       "
" - ] + "text/plain": "
", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjMAAAGwCAYAAABcnuQpAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAg50lEQVR4nO3df1BVdf7H8dfJH1dQwPx1LyQqbWg/UNs0TfoW5AaNue667PRLp7QyNbON2FYlKq+tgrqT0sZm6Zax21Lt7Ga1024LWaATuaLJalZmhcqO3EgjQEFIPd8/Gu94QxSv4Lkfez5mzoz3c869vHOGfM7nHriWbdu2AAAADHWe0wMAAACcCWIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEbr7PQAHe3o0aPau3evIiIiZFmW0+MAAIA2sG1b9fX1iomJ0XnnnXzv5ZyPmb179yo2NtbpMQAAQBAqKyvVv3//k15zzsdMRESEpO/+MiIjIx2eBgAAtEVdXZ1iY2P9/46fzDkfM8feWoqMjCRmAAAwTFtuEeEGYAAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARuvs9AAAEOoGzXvT6RGAkLVr8XinR2BnBgAAmI2YAQAARnM0ZrxeryzLCjg8Ho//vG3b8nq9iomJUVhYmJKTk7V9+3YHJwYAAKHG8Z2Zyy67TFVVVf5j27Zt/nNLly7VsmXLlJeXp7KyMnk8HqWkpKi+vt7BiQEAQChxPGY6d+4sj8fjP/r27Svpu12Z3NxcZWVlKS0tTQkJCcrPz1dDQ4MKCgocnhoAAIQKx2Nm586diomJUVxcnG699VZ98cUXkqSKigr5fD6lpqb6r3W5XEpKSlJpaWmrr9fU1KS6urqAAwAAnLscjZnRo0frT3/6k/79739r1apV8vl8SkxM1P79++Xz+SRJbrc74Dlut9t/7kRycnIUFRXlP2JjYzv0vwEAADjL0ZgZN26cfvnLX2ro0KG6/vrr9eab3/0uh/z8fP81lmUFPMe27RZrx8vMzFRtba3/qKys7JjhAQBASHD8babjde/eXUOHDtXOnTv9P9X0/V2Y6urqFrs1x3O5XIqMjAw4AADAuSukYqapqUkff/yxoqOjFRcXJ4/Ho6KiIv/55uZmlZSUKDEx0cEpAQBAKHH04wweeughTZgwQQMGDFB1dbUWLlyouro6TZkyRZZlKT09XdnZ2YqPj1d8fLyys7MVHh6uSZMmOTk2AAAIIY7GzP/+9z/ddttt2rdvn/r27aurrrpKGzZs0MCBAyVJc+bMUWNjo2bNmqWamhqNHj1ahYWFioiIcHJsAAAQQizbtm2nh+hIdXV1ioqKUm1tLffPAAgKHzQJtK6jPmjydP795lOzzxD/kwNaFwqfpgvg3BdSNwADAACcLmIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYLSQiZmcnBxZlqX09HT/mm3b8nq9iomJUVhYmJKTk7V9+3bnhgQAACEnJGKmrKxMK1eu1LBhwwLWly5dqmXLlikvL09lZWXyeDxKSUlRfX29Q5MCAIBQ43jMHDhwQJMnT9aqVat0/vnn+9dt21Zubq6ysrKUlpamhIQE5efnq6GhQQUFBQ5ODAAAQonjMXPfffdp/Pjxuv766wPWKyoq5PP5lJqa6l9zuVxKSkpSaWlpq6/X1NSkurq6gAMAAJy7Ojv5xV9++WV98MEHKisra3HO5/NJktxud8C62+3W7t27W33NnJwcLViwoH0HBQAAIcuxnZnKyko98MADevHFF9WtW7dWr7MsK+Cxbdst1o6XmZmp2tpa/1FZWdluMwMAgNDj2M7M5s2bVV1drREjRvjXjhw5onXr1ikvL087duyQ9N0OTXR0tP+a6urqFrs1x3O5XHK5XB03OAAACCmO7cz85Cc/0bZt21ReXu4/Ro4cqcmTJ6u8vFwXXnihPB6PioqK/M9pbm5WSUmJEhMTnRobAACEGMd2ZiIiIpSQkBCw1r17d/Xu3du/np6eruzsbMXHxys+Pl7Z2dkKDw/XpEmTnBgZAACEIEdvAD6VOXPmqLGxUbNmzVJNTY1Gjx6twsJCRUREOD0aAAAIESEVM8XFxQGPLcuS1+uV1+t1ZB4AABD6HP89MwAAAGeCmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGczRmVqxYoWHDhikyMlKRkZEaM2aM/vWvf/nP27Ytr9ermJgYhYWFKTk5Wdu3b3dwYgAAEGocjZn+/ftr8eLF2rRpkzZt2qSxY8fq5z//uT9Yli5dqmXLlikvL09lZWXyeDxKSUlRfX29k2MDAIAQ4mjMTJgwQTfeeKMGDx6swYMHa9GiRerRo4c2bNgg27aVm5urrKwspaWlKSEhQfn5+WpoaFBBQYGTYwMAgBASMvfMHDlyRC+//LIOHjyoMWPGqKKiQj6fT6mpqf5rXC6XkpKSVFpa2urrNDU1qa6uLuAAAADnLsdjZtu2berRo4dcLpdmzpypNWvW6NJLL5XP55Mkud3ugOvdbrf/3Ink5OQoKirKf8TGxnbo/AAAwFmOx8yQIUNUXl6uDRs26N5779WUKVP00Ucf+c9blhVwvW3bLdaOl5mZqdraWv9RWVnZYbMDAADndQ7mSZWVlbIsS/3795ckbdy4UQUFBbr00ks1ffr003qtrl276qKLLpIkjRw5UmVlZXryySc1d+5cSZLP51N0dLT/+urq6ha7NcdzuVxyuVyn+58EAAAMFdTOzKRJk/Tuu+9K+i42UlJStHHjRj388MN6/PHHz2gg27bV1NSkuLg4eTweFRUV+c81NzerpKREiYmJZ/Q1AADAuSOomPnwww81atQoSdJf//pXJSQkqLS0VAUFBXrhhRfa/DoPP/yw1q9fr127dmnbtm3KyspScXGxJk+eLMuylJ6eruzsbK1Zs0Yffvihpk6dqvDwcE2aNCmYsQEAwDkoqLeZvv32W/9bOW+//bZ+9rOfSZIuvvhiVVVVtfl1vvzyS91+++2qqqpSVFSUhg0bprfeekspKSmSpDlz5qixsVGzZs1STU2NRo8ercLCQkVERAQzNgAAOAcFFTOXXXaZnnnmGY0fP15FRUX67W9/K0nau3evevfu3ebXee6550563rIseb1eeb3eYMYEAAA/AEG9zbRkyRI9++yzSk5O1m233abhw4dLkt544w3/208AAABnQ1A7M8nJydq3b5/q6up0/vnn+9enT5+u7t27t9twAAAApxLUzszYsWNVX18fEDKS1KtXL91yyy3tMhgAAEBbBBUzxcXFam5ubrF+6NAhrV+//oyHAgAAaKvTeptp69at/j9/9NFHAR8rcOTIEb311lu64IIL2m86AACAUzitmLn88stlWZYsy9LYsWNbnA8LC9NTTz3VbsMBAACcymnFTEVFhWzb1oUXXqiNGzeqb9++/nNdu3ZVv3791KlTp3YfEgAAoDWnFTMDBw6UJB09erRDhgEAADhdQf1otiR9+umnKi4uVnV1dYu4eeyxx854MAAAgLYIKmZWrVqle++9V3369JHH45FlWf5zlmURMwAA4KwJKmYWLlyoRYsWae7cue09DwAAwGkJ6vfM1NTU6KabbmrvWQAAAE5bUDFz0003qbCwsL1nAQAAOG1Bvc100UUX6dFHH9WGDRs0dOhQdenSJeD8r371q3YZDgAA4FSCipmVK1eqR48eKikpUUlJScA5y7KIGQAAcNYEFTMVFRXtPQcAAEBQgrpnBgAAIFQEtTNz1113nfT8888/H9QwAAAApyuomKmpqQl4/O233+rDDz/UN998c8IPoAQAAOgoQcXMmjVrWqwdPXpUs2bN0oUXXnjGQwEAALRVu90zc9555+nBBx/U8uXL2+slAQAATqldbwD+/PPPdfjw4fZ8SQAAgJMK6m2mjIyMgMe2bauqqkpvvvmmpkyZ0i6DAQAAtEVQMbNly5aAx+edd5769u2rJ5544pQ/6QQAANCegoqZd999t73nAAAACEpQMXPMV199pR07dsiyLA0ePFh9+/Ztr7kAAADaJKgbgA8ePKi77rpL0dHRuvbaa3XNNdcoJiZGd999txoaGtp7RgAAgFYFFTMZGRkqKSnRP/7xD33zzTf65ptv9Prrr6ukpES//vWv23tGAACAVgX1NtPf//53/e1vf1NycrJ/7cYbb1RYWJhuvvlmrVixor3mAwAAOKmgdmYaGhrkdrtbrPfr14+3mQAAwFkVVMyMGTNG8+fP16FDh/xrjY2NWrBggcaMGdNuwwEAAJxKUG8z5ebmaty4cerfv7+GDx8uy7JUXl4ul8ulwsLC9p4RAACgVUHFzNChQ7Vz5069+OKL+uSTT2Tbtm699VZNnjxZYWFh7T0jAABAq4KKmZycHLndbt1zzz0B688//7y++uorzZ07t12GAwAAOJWg7pl59tlndfHFF7dYv+yyy/TMM8+c8VAAAABtFVTM+Hw+RUdHt1jv27evqqqqzngoAACAtgoqZmJjY/Xee++1WH/vvfcUExNzxkMBAAC0VVD3zEybNk3p6en69ttvNXbsWEnS2rVrNWfOHH4DMAAAOKuCipk5c+bo66+/1qxZs9Tc3CxJ6tatm+bOnavMzMx2HRAAAOBkgooZy7K0ZMkSPfroo/r4448VFham+Ph4uVyu9p4PAADgpIKKmWN69OihK6+8sr1mAQAAOG1B3QAMAAAQKogZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRHI2ZnJwcXXnllYqIiFC/fv00ceJE7dixI+Aa27bl9XoVExOjsLAwJScna/v27Q5NDAAAQo2jMVNSUqL77rtPGzZsUFFRkQ4fPqzU1FQdPHjQf83SpUu1bNky5eXlqaysTB6PRykpKaqvr3dwcgAAECo6O/nF33rrrYDHq1evVr9+/bR582Zde+21sm1bubm5ysrKUlpamiQpPz9fbrdbBQUFmjFjhhNjAwCAEBJS98zU1tZKknr16iVJqqiokM/nU2pqqv8al8ulpKQklZaWnvA1mpqaVFdXF3AAAIBzV8jEjG3bysjI0P/93/8pISFBkuTz+SRJbrc74Fq32+0/9305OTmKioryH7GxsR07OAAAcFTIxMzs2bO1detWvfTSSy3OWZYV8Ni27RZrx2RmZqq2ttZ/VFZWdsi8AAAgNDh6z8wx999/v9544w2tW7dO/fv39697PB5J3+3QREdH+9erq6tb7NYc43K55HK5OnZgAAAQMhzdmbFtW7Nnz9arr76qd955R3FxcQHn4+Li5PF4VFRU5F9rbm5WSUmJEhMTz/a4AAAgBDm6M3PfffepoKBAr7/+uiIiIvz3wURFRSksLEyWZSk9PV3Z2dmKj49XfHy8srOzFR4erkmTJjk5OgAACBGOxsyKFSskScnJyQHrq1ev1tSpUyVJc+bMUWNjo2bNmqWamhqNHj1ahYWFioiIOMvTAgCAUORozNi2fcprLMuS1+uV1+vt+IEAAIBxQuanmQAAAIJBzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjORoz69at04QJExQTEyPLsvTaa68FnLdtW16vVzExMQoLC1NycrK2b9/uzLAAACAkORozBw8e1PDhw5WXl3fC80uXLtWyZcuUl5ensrIyeTwepaSkqL6+/ixPCgAAQlVnJ7/4uHHjNG7cuBOes21bubm5ysrKUlpamiQpPz9fbrdbBQUFmjFjxtkcFQAAhKiQvWemoqJCPp9Pqamp/jWXy6WkpCSVlpa2+rympibV1dUFHAAA4NwVsjHj8/kkSW63O2Dd7Xb7z51ITk6OoqKi/EdsbGyHzgkAAJwVsjFzjGVZAY9t226xdrzMzEzV1tb6j8rKyo4eEQAAOMjRe2ZOxuPxSPpuhyY6Otq/Xl1d3WK35ngul0sul6vD5wMAAKEhZHdm4uLi5PF4VFRU5F9rbm5WSUmJEhMTHZwMAACEEkd3Zg4cOKDPPvvM/7iiokLl5eXq1auXBgwYoPT0dGVnZys+Pl7x8fHKzs5WeHi4Jk2a5ODUAAAglDgaM5s2bdJ1113nf5yRkSFJmjJlil544QXNmTNHjY2NmjVrlmpqajR69GgVFhYqIiLCqZEBAECIcTRmkpOTZdt2q+cty5LX65XX6z17QwEAAKOE7D0zAAAAbUHMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMZETNPP/204uLi1K1bN40YMULr1693eiQAABAiQj5mXnnlFaWnpysrK0tbtmzRNddco3HjxmnPnj1OjwYAAEJAyMfMsmXLdPfdd2vatGm65JJLlJubq9jYWK1YscLp0QAAQAjo7PQAJ9Pc3KzNmzdr3rx5AeupqakqLS094XOamprU1NTkf1xbWytJqqur65AZjzY1dMjrAueCjvq+O9v4Pgda11Hf58de17btU14b0jGzb98+HTlyRG63O2Dd7XbL5/Od8Dk5OTlasGBBi/XY2NgOmRFA66JynZ4AQEfr6O/z+vp6RUVFnfSakI6ZYyzLCnhs23aLtWMyMzOVkZHhf3z06FF9/fXX6t27d6vPwbmhrq5OsbGxqqysVGRkpNPjAOgAfJ//cNi2rfr6esXExJzy2pCOmT59+qhTp04tdmGqq6tb7NYc43K55HK5AtZ69uzZUSMiBEVGRvI/OeAcx/f5D8OpdmSOCekbgLt27aoRI0aoqKgoYL2oqEiJiYkOTQUAAEJJSO/MSFJGRoZuv/12jRw5UmPGjNHKlSu1Z88ezZw50+nRAABACAj5mLnlllu0f/9+Pf7446qqqlJCQoL++c9/auDAgU6PhhDjcrk0f/78Fm8zAjh38H2OE7HstvzMEwAAQIgK6XtmAAAAToWYAQAARiNmAACA0YgZAABgNGIGRnr66acVFxenbt26acSIEVq/fr3/nG3b8nq9iomJUVhYmJKTk7V9+3YHpwVwutatW6cJEyYoJiZGlmXptddeCzj/6quv6oYbblCfPn1kWZbKy8sdmROhgZiBcV555RWlp6crKytLW7Zs0TXXXKNx48Zpz549kqSlS5dq2bJlysvLU1lZmTwej1JSUlRfX+/w5ADa6uDBgxo+fLjy8vJaPX/11Vdr8eLFZ3kyhCJ+NBvGGT16tK644gqtWLHCv3bJJZdo4sSJys7OVkxMjNLT0zV37lxJ332Sutvt1pIlSzRjxgynxgYQJMuytGbNGk2cOLHFuV27dikuLk5btmzR5ZdfftZnQ2hgZwZGaW5u1ubNm5WamhqwnpqaqtLSUlVUVMjn8wWcd7lcSkpKUmlp6dkeFwBwFhAzMMq+fft05MiRFh806na75fP5/B9K2tp5AMC5h5iBkSzLCnhs23bA2qnOAwDOHcQMjNKnTx916tSpxS5LdXW13G63PB6PJLV6HgBw7iFmYJSuXbtqxIgRKioqClgvKipSYmKi4uLi5PF4As43NzerpKREiYmJZ3tcAMBZEPKfmg18X0ZGhm6//XaNHDlSY8aM0cqVK7Vnzx7NnDlTlmUpPT1d2dnZio+PV3x8vLKzsxUeHq5JkyY5PTqANjpw4IA+++wz/+OKigqVl5erV69eGjBggL7++mvt2bNHe/fulSTt2LFDkuTxePw7tPgBsQED/eEPf7AHDhxod+3a1b7iiivskpIS/7mjR4/a8+fPtz0ej+1yuexrr73W3rZtm4PTAjhd7777ri2pxTFlyhTbtm179erVJzw/f/58R+eGM/g9MwAAwGjcMwMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjED4IwlJycrPT291fODBg1Sbm7uWZll165dsixL5eXlZ+XrAXAeMQOgw5WVlWn69On+x5Zl6bXXXmvz81944QX17NmzTdfGxsaqqqpKCQkJpzklAFPxQZMAOlzfvn3Pytdpbm5W165d+aBB4AeGnRkA7eLw4cOaPXu2evbsqd69e+uRRx7RsY9+O/5tpkGDBkmSfvGLX8iyLP/j//73v7ruuusUERGhyMhIjRgxQps2bVJxcbHuvPNO1dbWyrIsWZYlr9frf62FCxdq6tSpioqK0j333NPibabi4mJZlqW1a9dq5MiRCg8PV2Jiov9Tlo9ZuHCh+vXrp4iICE2bNk3z5s3T5Zdf7j9fXFysUaNGqXv37urZs6euvvpq7d69u6P+OgGcBmIGQLvIz89X586d9Z///Ee///3vtXz5cv3xj39scV1ZWZkkafXq1aqqqvI/njx5svr376+ysjJt3rxZ8+bNU5cuXZSYmKjc3FxFRkaqqqpKVVVVeuihh/yv97vf/U4JCQnavHmzHn300Vbny8rK0hNPPKFNmzapc+fOuuuuu/zn/vKXv2jRokVasmSJNm/erAEDBmjFihX+84cPH9bEiROVlJSkrVu36v3339f06dNlWdYZ/70BOHO8zQSgXcTGxmr58uWyLEtDhgzRtm3btHz5ct1zzz0B1x17y6lnz54Bbwft2bNHv/nNb3TxxRdLkuLj4/3noqKiZFnWCd8+Gjt2bEDc7Nq164TzLVq0SElJSZKkefPmafz48Tp06JC6deump556SnfffbfuvPNOSdJjjz2mwsJCHThwQJJUV1en2tpa/fSnP9WPfvQjSdIll1xyWn8/ADoOOzMA2sVVV10VsFMxZswY7dy5U0eOHGnT8zMyMjRt2jRdf/31Wrx4sT7//PM2PW/kyJFtum7YsGH+P0dHR0uSqqurJUk7duzQqFGjAq4//nGvXr00depU3XDDDZowYYKefPJJVVVVtenrAuh4xAyAkOD1erV9+3aNHz9e77zzji699FKtWbPmlM/r3r17m16/S5cu/j8fi66jR4+2WDvm2P0+x6xevVrvv/++EhMT9corr2jw4MHasGFDm742gI5FzABoF9//h33Dhg2Kj49Xp06dWlzbpUuXE+7YDB48WA8++KAKCwuVlpam1atXS5K6du3a5h2eYAwZMkQbN24MWNu0aVOL63784x8rMzNTpaWlSkhIUEFBQYfNBKDtiBkA7aKyslIZGRnasWOHXnrpJT311FN64IEHTnjtoEGDtHbtWvl8PtXU1KixsVGzZ89WcXGxdu/erffee09lZWX++1IGDRqkAwcOaO3atdq3b58aGhradfb7779fzz33nPLz87Vz504tXLhQW7du9e/WVFRUKDMzU++//752796twsJCffrpp9w3A4QIbgAG0C7uuOMONTY2atSoUerUqZPuv//+gF+Ud7wnnnhCGRkZWrVqlS644AJ9+umn2r9/v+644w59+eWX6tOnj9LS0rRgwQJJUmJiombOnKlbbrlF+/fv1/z58/0/nt0eJk+erC+++EIPPfSQDh06pJtvvllTp07179aEh4frk08+UX5+vvbv36/o6GjNnj1bM2bMaLcZAATPsr//xjAAQCkpKfJ4PPrzn//s9CgAToGdGQA/eA0NDXrmmWd0ww03qFOnTnrppZf09ttvq6ioyOnRALQBOzMAfvAaGxs1YcIEffDBB2pqatKQIUP0yCOPKC0tzenRALQBMQMAAIzGTzMBAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjPb/6ba9dQ2md+AAAAAASUVORK5CYII=" }, "metadata": {}, "output_type": "display_data" diff --git a/examples/autoqasm/2_Expressing_classical_control_flow.ipynb b/examples/autoqasm/2_Expressing_classical_control_flow.ipynb index 3c73d6a2..c4d32364 100644 --- a/examples/autoqasm/2_Expressing_classical_control_flow.ipynb +++ b/examples/autoqasm/2_Expressing_classical_control_flow.ipynb @@ -87,8 +87,7 @@ " bell(2, 3)\n", "\n", "\n", - "two_bell_program = two_bell()\n", - "print(two_bell_program.to_ir())" + "print(two_bell.to_ir())" ] }, { @@ -130,10 +129,7 @@ " else:\n", " bell(3, 4)\n", "\n", - " c = measure([0, 1, 2, 3, 4])\n", - "\n", - "\n", - "conditioned_bell_program = conditioned_bell()" + " c = measure([0, 1, 2, 3, 4])" ] }, { @@ -154,15 +150,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "measurement counts: Counter({'00000': 135, '10000': 130, '11100': 123, '00011': 112})\n" + "measurement counts: Counter({'00011': 132, '00000': 130, '11100': 122, '10000': 116})\n" ] }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGxCAYAAACEFXd4AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAssElEQVR4nO3df1TUdb7H8dcgP/0BiAoDBUmtqamrriZh3jJjQzM3V2+lccvMpB9aGWXKvaHl2qJmabom2S21e7Ta7l3t1y5FmNomoiJWmhEVKScZLBVQFET53j86fk+z/khhYIZPz8c533P8fj6f74f3zGebfZ3vfGbGYVmWJQAAAEP5ebsAAACApkTYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYzd/bBfiC+vp67du3T+3atZPD4fB2OQAA4DxYlqXDhw8rJiZGfn5nv39D2JG0b98+xcbGersMAADQAKWlpbr44ovP2u/VsLNx40Y988wzKigoUFlZmdasWaORI0eecex9992nF198UQsWLNCUKVPs9oMHD+rBBx/UO++8Iz8/P40ePVrPP/+82rZte951tGvXTtJPT1ZoaGhjHhIAAGgmVVVVio2Ntf9//Gy8Gnaqq6vVu3dv3X333Ro1atRZx61Zs0abN29WTEzMaX0pKSkqKytTTk6O6urqNH78eKWmpmr16tXnXcept65CQ0MJOwAAtDC/tAXFq2Fn2LBhGjZs2DnHfP/993rwwQf1/vvva/jw4W59u3fvVnZ2trZu3ar+/ftLkhYvXqwbb7xR8+fPP2M4AgAAvy4+/Wms+vp63XHHHZo6dap69OhxWn9eXp7Cw8PtoCNJSUlJ8vPzU35+/lnnra2tVVVVldsBAADM5NNhZ+7cufL399dDDz10xn6Xy6XIyEi3Nn9/f0VERMjlcp113szMTIWFhdkHm5MBADCXz4adgoICPf/881qxYoXHPw6enp6uyspK+ygtLfXo/AAAwHf4bNj5+OOPtX//fsXFxcnf31/+/v7as2ePHn30UXXu3FmS5HQ6tX//frfrTpw4oYMHD8rpdJ517qCgIHszMpuSAQAwm89+z84dd9yhpKQkt7bk5GTdcccdGj9+vCQpMTFRFRUVKigoUL9+/SRJ69atU319vRISEpq9ZgAA4Hu8GnaOHDmir7/+2j4vKSnRjh07FBERobi4OHXo0MFtfEBAgJxOp7p27SpJ6t69u4YOHaqJEycqKytLdXV1mjx5ssaMGcMnsQAAgCQvv421bds29e3bV3379pUkpaWlqW/fvpoxY8Z5z7Fq1Sp169ZN119/vW688UYNGjRIy5Yta6qSAQBAC+OwLMvydhHeVlVVpbCwMFVWVrJ/BwCAFuJ8///bZzcoAwAAeAJhBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Xz2G5RN0Xn6e94u4VfruznDvV0CAMAHcGcHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKPxQ6BAA/Ejr97Dj7wCuBDc2QEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABG8/d2AQDgazpPf8/bJfwqfTdnuLdLgKG4swMAAIxG2AEAAEYj7AAAAKN5Nexs3LhRI0aMUExMjBwOh9auXWv31dXVadq0aerVq5fatGmjmJgY3Xnnndq3b5/bHAcPHlRKSopCQ0MVHh6uCRMm6MiRI838SAAAgK/yatiprq5W7969tWTJktP6jh49qu3btysjI0Pbt2/X3/72NxUVFekPf/iD27iUlBTt2rVLOTk5evfdd7Vx40alpqY210MAAAA+zqufxho2bJiGDRt2xr6wsDDl5OS4tf3lL3/RgAEDtHfvXsXFxWn37t3Kzs7W1q1b1b9/f0nS4sWLdeONN2r+/PmKiYlp8scAAAB8W4vas1NZWSmHw6Hw8HBJUl5ensLDw+2gI0lJSUny8/NTfn7+Weepra1VVVWV2wEAAMzUYsJOTU2Npk2bprFjxyo0NFSS5HK5FBkZ6TbO399fERERcrlcZ50rMzNTYWFh9hEbG9uktQMAAO9pEWGnrq5Ot956qyzL0tKlSxs9X3p6uiorK+2jtLTUA1UCAABf5PPfoHwq6OzZs0fr1q2z7+pIktPp1P79+93GnzhxQgcPHpTT6TzrnEFBQQoKCmqymgEAgO/w6Ts7p4JOcXGxPvzwQ3Xo0MGtPzExURUVFSooKLDb1q1bp/r6eiUkJDR3uQAAwAd59c7OkSNH9PXXX9vnJSUl2rFjhyIiIhQdHa1///d/1/bt2/Xuu+/q5MmT9j6ciIgIBQYGqnv37ho6dKgmTpyorKws1dXVafLkyRozZgyfxAIAAJK8HHa2bdum6667zj5PS0uTJI0bN05PPvmk3n77bUlSnz593K776KOPNHjwYEnSqlWrNHnyZF1//fXy8/PT6NGjtWjRomapHwAA+D6vhp3BgwfLsqyz9p+r75SIiAitXr3ak2UBAACD+PSeHQAAgMYi7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwmr+3CwAAoDl0nv6et0v41fpuznCv/n3u7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNG8GnY2btyoESNGKCYmRg6HQ2vXrnXrtyxLM2bMUHR0tEJCQpSUlKTi4mK3MQcPHlRKSopCQ0MVHh6uCRMm6MiRI834KAAAgC/zatiprq5W7969tWTJkjP2z5s3T4sWLVJWVpby8/PVpk0bJScnq6amxh6TkpKiXbt2KScnR++++642btyo1NTU5noIAADAx/l7848PGzZMw4YNO2OfZVlauHChnnjiCd18882SpFdffVVRUVFau3atxowZo927dys7O1tbt25V//79JUmLFy/WjTfeqPnz5ysmJqbZHgsAAPBNPrtnp6SkRC6XS0lJSXZbWFiYEhISlJeXJ0nKy8tTeHi4HXQkKSkpSX5+fsrPzz/r3LW1taqqqnI7AACAmXw27LhcLklSVFSUW3tUVJTd53K5FBkZ6dbv7++viIgIe8yZZGZmKiwszD5iY2M9XD0AAPAVPht2mlJ6eroqKyvto7S01NslAQCAJuKzYcfpdEqSysvL3drLy8vtPqfTqf3797v1nzhxQgcPHrTHnElQUJBCQ0PdDgAAYCafDTvx8fFyOp3Kzc2126qqqpSfn6/ExERJUmJioioqKlRQUGCPWbdunerr65WQkNDsNQMAAN/j1U9jHTlyRF9//bV9XlJSoh07digiIkJxcXGaMmWKZs+erS5duig+Pl4ZGRmKiYnRyJEjJUndu3fX0KFDNXHiRGVlZamurk6TJ0/WmDFj+CQWAACQ5OWws23bNl133XX2eVpamiRp3LhxWrFihR5//HFVV1crNTVVFRUVGjRokLKzsxUcHGxfs2rVKk2ePFnXX3+9/Pz8NHr0aC1atKjZHwsAAPBNXg07gwcPlmVZZ+13OByaNWuWZs2addYxERERWr16dVOUBwAADOCze3YAAAA8gbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGM2nw87JkyeVkZGh+Ph4hYSE6LLLLtOf/vQnWZZlj7EsSzNmzFB0dLRCQkKUlJSk4uJiL1YNAAB8iU+Hnblz52rp0qX6y1/+ot27d2vu3LmaN2+eFi9ebI+ZN2+eFi1apKysLOXn56tNmzZKTk5WTU2NFysHAAC+wt/bBZzLpk2bdPPNN2v48OGSpM6dO+u1117Tli1bJP10V2fhwoV64okndPPNN0uSXn31VUVFRWnt2rUaM2aM12oHAAC+wafv7AwcOFC5ubn66quvJEmffvqp/vnPf2rYsGGSpJKSErlcLiUlJdnXhIWFKSEhQXl5eWedt7a2VlVVVW4HAAAwk0/f2Zk+fbqqqqrUrVs3tWrVSidPntTTTz+tlJQUSZLL5ZIkRUVFuV0XFRVl951JZmamnnrqqaYrHAAA+AyfvrPz17/+VatWrdLq1au1fft2rVy5UvPnz9fKlSsbNW96eroqKyvto7S01EMVAwAAX+PTd3amTp2q6dOn23tvevXqpT179igzM1Pjxo2T0+mUJJWXlys6Otq+rry8XH369DnrvEFBQQoKCmrS2gEAgG/w6Ts7R48elZ+fe4mtWrVSfX29JCk+Pl5Op1O5ubl2f1VVlfLz85WYmNistQIAAN/UoDs7paWlcjgcuvjiiyVJW7Zs0erVq3XFFVcoNTXVY8WNGDFCTz/9tOLi4tSjRw8VFhbqueee09133y1JcjgcmjJlimbPnq0uXbooPj5eGRkZiomJ0ciRIz1WBwAAaLkaFHZuv/12paam6o477pDL5dLvf/979ejRQ6tWrZLL5dKMGTM8UtzixYuVkZGhBx54QPv371dMTIzuvfdet/kff/xxVVdXKzU1VRUVFRo0aJCys7MVHBzskRoAAEDL1qC3sXbu3KkBAwZI+mkTcc+ePbVp0yatWrVKK1as8Fhx7dq108KFC7Vnzx4dO3ZM33zzjWbPnq3AwEB7jMPh0KxZs+RyuVRTU6MPP/xQl19+ucdqAAAALVuDwk5dXZ29wffDDz/UH/7wB0lSt27dVFZW5rnqAAAAGqlBYadHjx7KysrSxx9/rJycHA0dOlSStG/fPnXo0MGjBQIAADRGg8LO3Llz9eKLL2rw4MEaO3asevfuLUl6++237be3AAAAfEGDNigPHjxYP/74o6qqqtS+fXu7PTU1VW3atPFYcQAAAI3VoDs7Q4YM0eHDh92CjiRFRETotttu80hhAAAAntCgsLN+/XodP378tPaamhp9/PHHjS4KAADAUy7obazPPvvM/vcXX3zh9mObJ0+eVHZ2ti666CLPVQcAANBIFxR2+vTpI4fDIYfDoSFDhpzWHxISosWLF3usOAAAgMa6oLBTUlIiy7J06aWXasuWLerUqZPdFxgYqMjISLVq1crjRQIAADTUBYWdSy65RJLsH+IEAADwdQ366LkkFRcX66OPPtL+/ftPCz+e+m0sAACAxmpQ2HnppZd0//33q2PHjnI6nXI4HHafw+Eg7AAAAJ/RoLAze/ZsPf3005o2bZqn6wEAAPCoBn3PzqFDh3TLLbd4uhYAAACPa1DYueWWW/TBBx94uhYAAACPa9DbWL/5zW+UkZGhzZs3q1evXgoICHDrf+ihhzxSHAAAQGM1KOwsW7ZMbdu21YYNG7Rhwwa3PofDQdgBAAA+o0Fhp6SkxNN1AAAANIkG7dkBAABoKRp0Z+fuu+8+Z/8rr7zSoGIAAAA8rUFh59ChQ27ndXV12rlzpyoqKs74A6EAAADe0qCws2bNmtPa6uvrdf/99+uyyy5rdFEAAACe4rE9O35+fkpLS9OCBQs8NSUAAECjeXSD8jfffKMTJ054ckoAAIBGadDbWGlpaW7nlmWprKxM7733nsaNG+eRwgAAADyhQWGnsLDQ7dzPz0+dOnXSs88++4uf1AIAAGhODQo7H330kafrAAAAaBINCjun/PDDDyoqKpIkde3aVZ06dfJIUQAAAJ7SoA3K1dXVuvvuuxUdHa1rrrlG11xzjWJiYjRhwgQdPXrU0zUCAAA0WIPCTlpamjZs2KB33nlHFRUVqqio0FtvvaUNGzbo0Ucf9XSNAAAADdagt7H+7//+T//7v/+rwYMH22033nijQkJCdOutt2rp0qWeqg8AAKBRGnRn5+jRo4qKijqtPTIykrexAACAT2lQ2ElMTNTMmTNVU1Njtx07dkxPPfWUEhMTPVYcAABAYzXobayFCxdq6NChuvjii9W7d29J0qeffqqgoCB98MEHHi0QAACgMRoUdnr16qXi4mKtWrVKX375pSRp7NixSklJUUhIiEcLBAAAaIwGhZ3MzExFRUVp4sSJbu2vvPKKfvjhB02bNs0jxQEAADRWg/bsvPjii+rWrdtp7T169FBWVlajiwIAAPCUBoUdl8ul6Ojo09o7deqksrKyRhcFAADgKQ0KO7Gxsfrkk09Oa//kk08UExPT6KIAAAA8pUF7diZOnKgpU6aorq5OQ4YMkSTl5ubq8ccf5xuUAQCAT2lQ2Jk6daoOHDigBx54QMePH5ckBQcHa9q0aUpPT/dogQAAAI3RoLDjcDg0d+5cZWRkaPfu3QoJCVGXLl0UFBTk6foAAAAapUF7dk5p27atrrzySvXs2bPJgs7333+v//iP/1CHDh0UEhKiXr16adu2bXa/ZVmaMWOGoqOjFRISoqSkJBUXFzdJLQAAoOVpVNhpaocOHdLVV1+tgIAA/eMf/9AXX3yhZ599Vu3bt7fHzJs3T4sWLVJWVpby8/PVpk0bJScnu/2UBQAA+PVq0NtYzWXu3LmKjY3V8uXL7bb4+Hj735ZlaeHChXriiSd08803S5JeffVVRUVFae3atRozZswZ562trVVtba19XlVV1USPAAAAeJtP39l5++231b9/f91yyy2KjIxU37599dJLL9n9JSUlcrlcSkpKstvCwsKUkJCgvLy8s86bmZmpsLAw+4iNjW3SxwEAALzHp8POt99+q6VLl6pLly56//33df/99+uhhx7SypUrJf305YaSFBUV5XZdVFSU3Xcm6enpqqystI/S0tKmexAAAMCrfPptrPr6evXv319//vOfJUl9+/bVzp07lZWVpXHjxjV43qCgID45BgDAr4RP39mJjo7WFVdc4dbWvXt37d27V5LkdDolSeXl5W5jysvL7T4AAPDr5tNh5+qrr1ZRUZFb21dffaVLLrlE0k+blZ1Op3Jzc+3+qqoq5efnKzExsVlrBQAAvsmn38Z65JFHNHDgQP35z3/Wrbfeqi1btmjZsmVatmyZpJ++3HDKlCmaPXu2unTpovj4eGVkZCgmJkYjR470bvEAAMAn+HTYufLKK7VmzRqlp6dr1qxZio+P18KFC5WSkmKPefzxx1VdXa3U1FRVVFRo0KBBys7OVnBwsBcrBwAAvsKnw44k3XTTTbrpppvO2u9wODRr1izNmjWrGasCAAAthU/v2QEAAGgswg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgtBYVdubMmSOHw6EpU6bYbTU1NZo0aZI6dOigtm3bavTo0SovL/dekQAAwKe0mLCzdetWvfjii/rtb3/r1v7II4/onXfe0ZtvvqkNGzZo3759GjVqlJeqBAAAvqZFhJ0jR44oJSVFL730ktq3b2+3V1ZW6uWXX9Zzzz2nIUOGqF+/flq+fLk2bdqkzZs3n3W+2tpaVVVVuR0AAMBMLSLsTJo0ScOHD1dSUpJbe0FBgerq6tzau3Xrpri4OOXl5Z11vszMTIWFhdlHbGxsk9UOAAC8y+fDzuuvv67t27crMzPztD6Xy6XAwECFh4e7tUdFRcnlcp11zvT0dFVWVtpHaWmpp8sGAAA+wt/bBZxLaWmpHn74YeXk5Cg4ONhj8wYFBSkoKMhj8wEAAN/l03d2CgoKtH//fv3ud7+Tv7+//P39tWHDBi1atEj+/v6KiorS8ePHVVFR4XZdeXm5nE6nd4oGAAA+xafv7Fx//fX6/PPP3drGjx+vbt26adq0aYqNjVVAQIByc3M1evRoSVJRUZH27t2rxMREb5QMAAB8jE+HnXbt2qlnz55ubW3atFGHDh3s9gkTJigtLU0REREKDQ3Vgw8+qMTERF111VXeKBkAAPgYnw4752PBggXy8/PT6NGjVVtbq+TkZL3wwgveLgsAAPiIFhd21q9f73YeHBysJUuWaMmSJd4pCAAA+DSf3qAMAADQWIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGg+HXYyMzN15ZVXql27doqMjNTIkSNVVFTkNqampkaTJk1Shw4d1LZtW40ePVrl5eVeqhgAAPganw47GzZs0KRJk7R582bl5OSorq5ON9xwg6qrq+0xjzzyiN555x29+eab2rBhg/bt26dRo0Z5sWoAAOBL/L1dwLlkZ2e7na9YsUKRkZEqKCjQNddco8rKSr388stavXq1hgwZIklavny5unfvrs2bN+uqq67yRtkAAMCH+PSdnX9VWVkpSYqIiJAkFRQUqK6uTklJSfaYbt26KS4uTnl5eWedp7a2VlVVVW4HAAAwU4sJO/X19ZoyZYquvvpq9ezZU5LkcrkUGBio8PBwt7FRUVFyuVxnnSszM1NhYWH2ERsb25SlAwAAL2oxYWfSpEnauXOnXn/99UbPlZ6ersrKSvsoLS31QIUAAMAX+fSenVMmT56sd999Vxs3btTFF19stzudTh0/flwVFRVud3fKy8vldDrPOl9QUJCCgoKasmQAAOAjfPrOjmVZmjx5stasWaN169YpPj7erb9fv34KCAhQbm6u3VZUVKS9e/cqMTGxucsFAAA+yKfv7EyaNEmrV6/WW2+9pXbt2tn7cMLCwhQSEqKwsDBNmDBBaWlpioiIUGhoqB588EElJibySSwAACDJx8PO0qVLJUmDBw92a1++fLnuuusuSdKCBQvk5+en0aNHq7a2VsnJyXrhhReauVIAAOCrfDrsWJb1i2OCg4O1ZMkSLVmypBkqAgAALY1P79kBAABoLMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDRjws6SJUvUuXNnBQcHKyEhQVu2bPF2SQAAwAcYEXbeeOMNpaWlaebMmdq+fbt69+6t5ORk7d+/39ulAQAALzMi7Dz33HOaOHGixo8fryuuuEJZWVlq3bq1XnnlFW+XBgAAvMzf2wU01vHjx1VQUKD09HS7zc/PT0lJScrLyzvjNbW1taqtrbXPKysrJUlVVVUer6++9qjH58T5aYr1/DnW1ntYWzOxruZqqrU9Na9lWecc1+LDzo8//qiTJ08qKirKrT0qKkpffvnlGa/JzMzUU089dVp7bGxsk9QI7whb6O0K0FRYWzOxruZq6rU9fPiwwsLCztrf4sNOQ6SnpystLc0+r6+v18GDB9WhQwc5HA4vVuZbqqqqFBsbq9LSUoWGhnq7HHgI62ou1tZcrO2ZWZalw4cPKyYm5pzjWnzY6dixo1q1aqXy8nK39vLycjmdzjNeExQUpKCgILe28PDwpiqxxQsNDeU/LgOxruZibc3F2p7uXHd0TmnxG5QDAwPVr18/5ebm2m319fXKzc1VYmKiFysDAAC+oMXf2ZGktLQ0jRs3Tv3799eAAQO0cOFCVVdXa/z48d4uDQAAeJkRYee2227TDz/8oBkzZsjlcqlPnz7Kzs4+bdMyLkxQUJBmzpx52lt+aNlYV3OxtuZibRvHYf3S57UAAABasBa/ZwcAAOBcCDsAAMBohB0AAGA0wg4AADAaYcdAS5YsUefOnRUcHKyEhARt2bLF7qupqdGkSZPUoUMHtW3bVqNHjz7tCxn37t2r4cOHq3Xr1oqMjNTUqVN14sQJtzHr16/X7373OwUFBek3v/mNVqxYcUF14MJs3LhRI0aMUExMjBwOh9auXevWb1mWZsyYoejoaIWEhCgpKUnFxcVuYw4ePKiUlBSFhoYqPDxcEyZM0JEjR9zGfPbZZ/q3f/s3BQcHKzY2VvPmzTutljfffFPdunVTcHCwevXqpb///e8ef7y/Fr+0rn/72990ww032N/uvmPHjtPmWLZsmQYPHqzQ0FA5HA5VVFScNsZTa48L19Svx2VlZbr99tt1+eWXy8/PT1OmTDmthl27dmn06NHq3LmzHA6HFi5c2FQP12cRdgzzxhtvKC0tTTNnztT27dvVu3dvJScna//+/ZKkRx55RO+8847efPNNbdiwQfv27dOoUaPs60+ePKnhw4fr+PHj2rRpk1auXKkVK1ZoxowZ9piSkhINHz5c1113nXbs2KEpU6bonnvu0fvvv3/edeDCVFdXq3fv3lqyZMkZ++fNm6dFixYpKytL+fn5atOmjZKTk1VTU2OPSUlJ0a5du5STk6N3331XGzduVGpqqt1fVVWlG264QZdccokKCgr0zDPP6Mknn9SyZcvsMZs2bdLYsWM1YcIEFRYWauTIkRo5cqR27tzZdA/eYL+0rtXV1Ro0aJDmzp171jmOHj2qoUOH6j//8z/POsYTa48L1xyvx7W1terUqZOeeOIJ9e7d+4x1HD16VJdeeqnmzJlz1l8WMJ4FowwYMMCaNGmSfX7y5EkrJibGyszMtCoqKqyAgADrzTfftPt3795tSbLy8vIsy7Ksv//975afn5/lcrnsMUuXLrVCQ0Ot2tpay7Is6/HHH7d69Ojh9ndvu+02Kzk5+bzqQONIstasWWOf19fXW06n03rmmWfstoqKCisoKMh67bXXLMuyrC+++MKSZG3dutUe849//MNyOBzW999/b1mWZb3wwgtW+/bt7XW2LMuaNm2a1bVrV/v81ltvtYYPH+5WT0JCgnXvvfd69DH+Gv3ruv5cSUmJJckqLCw86/UfffSRJck6dOiQW7un1h4Xrjlej3/u2muvtR5++OFz1nTJJZdYCxYsaNwDa4G4s2OQ48ePq6CgQElJSXabn5+fkpKSlJeXp4KCAtXV1bn1d+vWTXFxccrLy5Mk5eXlqVevXm5fyJicnKyqqirt2rXLHvPzOU6NOTXHL9UBzyopKZHL5XJ7vsPCwpSQkOC2ruHh4erfv789JikpSX5+fsrPz7fHXHPNNQoMDLTHJCcnq6ioSIcOHbLHnGvt4Xs8tfa4MM31eozzQ9gxyI8//qiTJ0+e9s3RUVFRcrlccrlcCgwMPO1HT0/1S5LL5Trj9af6zjWmqqpKx44d+8U64FmnntNzPd8ul0uRkZFu/f7+/oqIiPDI2rOuvstTa48L01yvxzg/hB0AAGA0wo5BOnbsqFatWp22m7+8vFxOp1NOp1PHjx8/7dMap/olyel0nvH6U33nGhMaGqqQkJBfrAOedeo5Pdfz7XQ6T9scfuLECR08eNAja8+6+i5PrT0uTHO9HuP8EHYMEhgYqH79+ik3N9duq6+vV25urhITE9WvXz8FBAS49RcVFWnv3r1KTEyUJCUmJurzzz93e3HMyclRaGiorrjiCnvMz+c4NebUHL9UBzwrPj5eTqfT7fmuqqpSfn6+27pWVFSooKDAHrNu3TrV19crISHBHrNx40bV1dXZY3JyctS1a1e1b9/eHnOutYfv8dTa48I01+sxzpO3d0jDs15//XUrKCjIWrFihfXFF19YqampVnh4uL2b/7777rPi4uKsdevWWdu2bbMSExOtxMRE+/oTJ05YPXv2tG644QZrx44dVnZ2ttWpUycrPT3dHvPtt99arVu3tqZOnWrt3r3bWrJkidWqVSsrOzv7vOvAhTl8+LBVWFhoFRYWWpKs5557ziosLLT27NljWZZlzZkzxwoPD7feeust67PPPrNuvvlmKz4+3jp27Jg9x9ChQ62+ffta+fn51j//+U+rS5cu1tixY+3+iooKKyoqyrrjjjusnTt3Wq+//rrVunVr68UXX7THfPLJJ5a/v781f/58a/fu3dbMmTOtgIAA6/PPP2++J8Mgv7SuBw4csAoLC6333nvPkmS9/vrrVmFhoVVWVmbPUVZWZhUWFlovvfSSJcnauHGjVVhYaB04cMAe44m1x4Vrjtdjy7Ls/w3169fPuv32263CwkJr165ddn9tba09Jjo62nrssceswsJCq7i4uHmeCB9A2DHQ4sWLrbi4OCswMNAaMGCAtXnzZrvv2LFj1gMPPGC1b9/eat26tfXHP/7R7YXTsizru+++s4YNG2aFhIRYHTt2tB599FGrrq7ObcxHH31k9enTxwoMDLQuvfRSa/ny5RdUBy7MqY8V/+sxbtw4y7J++vh5RkaGFRUVZQUFBVnXX3+9VVRU5DbHgQMHrLFjx1pt27a1QkNDrfHjx1uHDx92G/Ppp59agwYNsoKCgqyLLrrImjNnzmm1/PWvf7Uuv/xyKzAw0OrRo4f13nvvNdnjNt0vrevy5cvP2D9z5kx7jpkzZ55xzM//m/TU2uPCNcfr8ZnW/5JLLrH7T311wb8e1157bVM+dJ/isCzLauq7RwAAAN7Cnh0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQBNbvDgwZoyZcpZ+zt37qyFCxc2Sy3fffedHA6HduzY0Sx/D4D3EXYAeN3WrVuVmppqnzscDq1du/a8r1+xYoXCw8PPa2xsbKzKysrUs2fPC6wSQEvl7+0CAKBTp07N8neOHz+uwMBAOZ3OZvl7AHwDd3YANIsTJ05o8uTJCgsLU8eOHZWRkaFTP83387exOnfuLEn64x//KIfDYZ9/+umnuu6669SuXTuFhoaqX79+2rZtm9avX6/x48ersrJSDodDDodDTz75pD3Xn/70J915550KDQ1VamrqaW9jrV+/Xg6HQ7m5uerfv79at26tgQMHqqioyK3+2bNnKzIyUu3atdM999yj6dOnq0+fPnb/+vXrNWDAALVp00bh4eG6+uqrtWfPnqZ6OgFcAMIOgGaxcuVK+fv7a8uWLXr++ef13HPP6b//+79PG7d161ZJ0vLly1VWVmafp6Sk6OKLL9bWrVtVUFCg6dOnKyAgQAMHDtTChQsVGhqqsrIylZWV6bHHHrPnmz9/vnr37q3CwkJlZGSctb7/+q//0rPPPqtt27bJ399fd999t923atUqPf3005o7d64KCgoUFxenpUuX2v0nTpzQyJEjde211+qzzz5TXl6eUlNT5XA4Gv28AWg83sYC0CxiY2O1YMECORwOde3aVZ9//rkWLFigiRMnuo079ZZWeHi429tNe/fu1dSpU9WtWzdJUpcuXey+sLAwORyOM749NWTIED366KP2+XfffXfG+p5++mlde+21kqTp06dr+PDhqqmpUXBwsBYvXqwJEyZo/PjxkqQZM2bogw8+0JEjRyRJVVVVqqys1E033aTLLrtMktS9e/cLen4ANB3u7ABoFldddZXbnY7ExEQVFxfr5MmT53V9Wlqa7rnnHiUlJWnOnDn65ptvzuu6/v37n9e43/72t/a/o6OjJUn79++XJBUVFWnAgAFu439+HhERobvuukvJyckaMWKEnn/+eZWVlZ3X3wXQ9Ag7AFqEJ598Urt27dLw4cO1bt06XXHFFVqzZs0vXtemTZvzmj8gIMD+96lQVl9ff971LV++XHl5eRo4cKDeeOMNXX755dq8efN5Xw+g6RB2ADSL/Px8t/PNmzerS5cuatWq1WljAwICznjH5/LLL9cjjzyiDz74QKNGjdLy5cslSYGBged9h6ghunbtau8dOuVfzyWpb9++Sk9P16ZNm9SzZ0+tXr26yWoCcP4IOwCaxd69e5WWlqaioiK99tprWrx4sR5++OEzju3cubNyc3Plcrl06NAhHTt2TJMnT9b69eu1Z88effLJJ9q6dau9L6Zz5846cuSIcnNz9eOPP+ro0aMerf3BBx/Uyy+/rJUrV6q4uFizZ8/WZ599Zt8BKikpUXp6uvLy8rRnzx598MEHKi4uZt8O4CPYoAygWdx55506duyYBgwYoFatWunhhx92+yLBn3v22WeVlpaml156SRdddJG++uorHThwQHfeeafKy8vVsWNHjRo1Sk899ZQkaeDAgbrvvvt022236cCBA5o5c6b98XNPSElJ0bfffqvHHntMNTU1uvXWW3XXXXdpy5YtkqTWrVvryy+/1MqVK3XgwAFFR0dr0qRJuvfeez1WA4CGc1invugCAHDefv/738vpdOp//ud/vF0KgF/AnR0A+AVHjx5VVlaWkpOT1apVK7322mv68MMPlZOT4+3SAJwH7uwAwC84duyYRowYocLCQtXU1Khr16564oknNGrUKG+XBuA8EHYAAIDR+DQWAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGC0/wftywIip0tLFwAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] + "text/plain": "
", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGwCAYAAABPSaTdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAoxUlEQVR4nO3df1TVdZ7H8ddXlCsSoPjjXu9ISbuYTZg2mI40JVlijj921rNZ6ZaNVjqmRdSYjJXYUVDnpOzCpqPbqJtrtmcnm3abdUFHMUNHRKi0TZ2GlAqGMgRUBMTv/tHxe7ohaXjhXj4+H+fcc7zf7+de3tfvgZ597/eKZdu2LQAAAEN1CvQAAAAAbYnYAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDROgd6gGBw/vx5ff7554qIiJBlWYEeBwAAXAbbtlVbWyuv16tOnVo+f0PsSPr8888VExMT6DEAAEArlJWVqV+/fi3uJ3YkRURESPr6LysyMjLA0wAAgMtRU1OjmJgY57/jLSF2JOetq8jISGIHAIAO5lKXoHCBMgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAo3UO9AAAEGz6z3870CNctT5ZOi7QI8BAnNkBAABGI3YAAIDRiB0AAGA0YgcAABiNC5SBVuIi1sDhIlYA3wdndgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABG6xzoAQAAaA/9578d6BGuWp8sHRfQr8+ZHQAAYLSAxs6uXbs0YcIEeb1eWZalN99809nX2NioZ599VoMGDVJ4eLi8Xq8eeughff755z7PUV9fr7lz56pXr14KDw/XxIkT9emnn7bzKwEAAMEqoLFz+vRpDR48WDk5Oc32nTlzRgcOHNDzzz+vAwcO6I033tCRI0c0ceJEn3UpKSnasmWLNm/erN27d+vUqVMaP368mpqa2utlAACAIBbQa3bGjh2rsWPHXnRfVFSU8vLyfLZlZ2dr2LBhOn78uK699lpVV1frlVde0auvvqq7775bkrRx40bFxMRo27ZtGjNmTJu/BgAAENw61DU71dXVsixL3bt3lyQVFRWpsbFRycnJzhqv16v4+HgVFBS0+Dz19fWqqanxuQEAADN1mNg5e/as5s+frylTpigyMlKSVFFRodDQUPXo0cNnrdvtVkVFRYvPlZmZqaioKOcWExPTprMDAIDA6RCx09jYqPvvv1/nz5/Xyy+/fMn1tm3LsqwW96elpam6utq5lZWV+XNcAAAQRII+dhobGzV58mSVlpYqLy/POasjSR6PRw0NDaqqqvJ5TGVlpdxud4vP6XK5FBkZ6XMDAABmCurYuRA6R48e1bZt29SzZ0+f/QkJCerSpYvPhczl5eU6ePCgEhMT23tcAAAQhAL6aaxTp07pz3/+s3O/tLRUJSUlio6Oltfr1T/8wz/owIED+u///m81NTU51+FER0crNDRUUVFRmjFjhp5++mn17NlT0dHReuaZZzRo0CDn01kAAODqFtDY2b9/v+68807nfmpqqiRp2rRpSk9P11tvvSVJGjJkiM/jduzYoaSkJEnSypUr1blzZ02ePFl1dXW66667tH79eoWEhLTLawAAAMEtoLGTlJQk27Zb3P9d+y7o2rWrsrOzlZ2d7c/RAACAIYL6mh0AAIArRewAAACjETsAAMBoAb1m52rQf/7bgR7hqvXJ0nGBHgEAEAQ4swMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMFNHZ27dqlCRMmyOv1yrIsvfnmmz77bdtWenq6vF6vwsLClJSUpEOHDvmsqa+v19y5c9WrVy+Fh4dr4sSJ+vTTT9vxVQAAgGAW0Ng5ffq0Bg8erJycnIvuX758uVasWKGcnBwVFhbK4/Fo9OjRqq2tddakpKRoy5Yt2rx5s3bv3q1Tp05p/Pjxampqaq+XAQAAgljnQH7xsWPHauzYsRfdZ9u2srKytGDBAk2aNEmStGHDBrndbm3atEkzZ85UdXW1XnnlFb366qu6++67JUkbN25UTEyMtm3bpjFjxrTbawEAAMEpaK/ZKS0tVUVFhZKTk51tLpdLI0eOVEFBgSSpqKhIjY2NPmu8Xq/i4+OdNRdTX1+vmpoanxsAADBT0MZORUWFJMntdvtsd7vdzr6KigqFhoaqR48eLa65mMzMTEVFRTm3mJgYP08PAACCRdDGzgWWZfnct2272bZvu9SatLQ0VVdXO7eysjK/zAoAAIJP0MaOx+ORpGZnaCorK52zPR6PRw0NDaqqqmpxzcW4XC5FRkb63AAAgJmCNnZiY2Pl8XiUl5fnbGtoaFB+fr4SExMlSQkJCerSpYvPmvLych08eNBZAwAArm4B/TTWqVOn9Oc//9m5X1paqpKSEkVHR+vaa69VSkqKMjIyFBcXp7i4OGVkZKhbt26aMmWKJCkqKkozZszQ008/rZ49eyo6OlrPPPOMBg0a5Hw6CwAAXN0CGjv79+/XnXfe6dxPTU2VJE2bNk3r16/XvHnzVFdXp9mzZ6uqqkrDhw9Xbm6uIiIinMesXLlSnTt31uTJk1VXV6e77rpL69evV0hISLu/HgAAEHwCGjtJSUmybbvF/ZZlKT09Xenp6S2u6dq1q7Kzs5Wdnd0GEwIAgI4uaK/ZAQAA8AdiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGC+rYOXfunJ577jnFxsYqLCxM119/vV588UWdP3/eWWPbttLT0+X1ehUWFqakpCQdOnQogFMDAIBgEtSxs2zZMq1evVo5OTn6v//7Py1fvly//vWvlZ2d7axZvny5VqxYoZycHBUWFsrj8Wj06NGqra0N4OQAACBYdA70AN9lz549+ru/+zuNGzdOktS/f3+99tpr2r9/v6Svz+pkZWVpwYIFmjRpkiRpw4YNcrvd2rRpk2bOnHnR562vr1d9fb1zv6ampo1fCQAACJSgPrPzk5/8RNu3b9eRI0ckSe+99552796tn/70p5Kk0tJSVVRUKDk52XmMy+XSyJEjVVBQ0OLzZmZmKioqyrnFxMS07QsBAAABE9Rndp599llVV1dr4MCBCgkJUVNTk5YsWaIHHnhAklRRUSFJcrvdPo9zu906duxYi8+blpam1NRU535NTQ3BAwCAoYI6dl5//XVt3LhRmzZt0k033aSSkhKlpKTI6/Vq2rRpzjrLsnweZ9t2s23f5HK55HK52mxuAAAQPII6dn75y19q/vz5uv/++yVJgwYN0rFjx5SZmalp06bJ4/FI+voMT9++fZ3HVVZWNjvbAwAArk5Bfc3OmTNn1KmT74ghISHOR89jY2Pl8XiUl5fn7G9oaFB+fr4SExPbdVYAABCcgvrMzoQJE7RkyRJde+21uummm1RcXKwVK1Zo+vTpkr5++yolJUUZGRmKi4tTXFycMjIy1K1bN02ZMiXA0wMAgGAQ1LGTnZ2t559/XrNnz1ZlZaW8Xq9mzpypF154wVkzb9481dXVafbs2aqqqtLw4cOVm5uriIiIAE4OAACCRVDHTkREhLKyspSVldXiGsuylJ6ervT09HabCwAAdBxBfc0OAADAlWpV7JSVlenTTz917u/bt08pKSlas2aN3wYDAADwh1bFzpQpU7Rjxw5JX3/se/To0dq3b59+9atf6cUXX/TrgAAAAFeiVbFz8OBBDRs2TJL0H//xH4qPj1dBQYE2bdqk9evX+3M+AACAK9Kq2GlsbHT+BeJt27Zp4sSJkqSBAweqvLzcf9MBAABcoVbFzk033aTVq1frnXfeUV5enu655x5J0ueff66ePXv6dUAAAIAr0arYWbZsmX7zm98oKSlJDzzwgAYPHixJeuutt5y3twAAAIJBq/6dnaSkJH355ZeqqalRjx49nO2PPfaYwsPD/TYcAADAlWrVmZ1Ro0aptrbWJ3QkKTo6Wvfdd59fBgMAAPCHVsXOzp071dDQ0Gz72bNn9c4771zxUAAAAP7yvd7Gev/9950/f/jhh6qoqHDuNzU1aevWrfrBD37gv+kAAACu0PeKnSFDhsiyLFmWpVGjRjXbHxYWpuzsbL8NBwAAcKW+V+yUlpbKtm1df/312rdvn3r37u3sCw0NVZ8+fRQSEuL3IQEAAFrre8XOddddJ0k6f/58mwwDAADgb6366LkkHTlyRDt37lRlZWWz+HnhhReueDAAAAB/aFXsrF27Vr/4xS/Uq1cveTweWZbl7LMsi9gBAABBo1Wxs3jxYi1ZskTPPvusv+cBAADwq1b9OztVVVW69957/T0LAACA37Uqdu69917l5ub6exYAAAC/a9XbWH/7t3+r559/Xnv37tWgQYPUpUsXn/1PPPGEX4YDAAC4Uq2KnTVr1uiaa65Rfn6+8vPzffZZlkXsAACAoNGq2CktLfX3HAAAAG2iVdfsAAAAdBStOrMzffr079z/29/+tlXDAAAA+FurYqeqqsrnfmNjow4ePKiTJ09e9BeEAgAABEqrYmfLli3Ntp0/f16zZ8/W9ddff8VDAQAA+Ivfrtnp1KmTnnrqKa1cudJfTwkAAHDF/HqB8scff6xz58758ykBAACuSKvexkpNTfW5b9u2ysvL9fbbb2vatGl+GQwAAMAfWhU7xcXFPvc7deqk3r1766WXXrrkJ7UAAADaU6tiZ8eOHf6eAwAAoE20KnYu+OKLL3T48GFZlqUBAwaod+/e/poLAADAL1p1gfLp06c1ffp09e3bV3fccYduv/12eb1ezZgxQ2fOnPH3jAAAAK3WqthJTU1Vfn6+/uu//ksnT57UyZMn9fvf/175+fl6+umn/T0jAABAq7Xqbazf/e53+s///E8lJSU52376058qLCxMkydP1qpVq/w1HwAAwBVp1ZmdM2fOyO12N9vep08f3sYCAABBpVWxM2LECC1cuFBnz551ttXV1WnRokUaMWKE34YDAAC4Uq16GysrK0tjx45Vv379NHjwYFmWpZKSErlcLuXm5vp7RgAAgFZrVewMGjRIR48e1caNG/XRRx/Jtm3df//9mjp1qsLCwvw9IwAAQKu1KnYyMzPldrv16KOP+mz/7W9/qy+++ELPPvusX4YDAAC4Uq26Zuc3v/mNBg4c2Gz7TTfdpNWrV1/xUAAAAP7SqtipqKhQ3759m23v3bu3ysvLr3goAAAAf2lV7MTExOjdd99ttv3dd9+V1+u94qG+6bPPPtM//uM/qmfPnurWrZuGDBmioqIiZ79t20pPT5fX61VYWJiSkpJ06NAhv84AAAA6rlZds/PII48oJSVFjY2NGjVqlCRp+/btmjdvnl//BeWqqirddtttuvPOO/U///M/6tOnjz7++GN1797dWbN8+XKtWLFC69ev14ABA7R48WKNHj1ahw8fVkREhN9mAQAAHVOrYmfevHn66quvNHv2bDU0NEiSunbtqmeffVZpaWl+G27ZsmWKiYnRunXrnG39+/d3/mzbtrKysrRgwQJNmjRJkrRhwwa53W5t2rRJM2fO9NssAACgY2rV21iWZWnZsmX64osvtHfvXr333nv66quv9MILL/h1uLfeektDhw7Vvffeqz59+uiWW27R2rVrnf2lpaWqqKhQcnKys83lcmnkyJEqKCho8Xnr6+tVU1PjcwMAAGZqVexccM011+jWW29VfHy8XC6Xv2Zy/OUvf9GqVasUFxen//3f/9WsWbP0xBNP6N/+7d8kfX2htKRmv7rC7XY7+y4mMzNTUVFRzi0mJsbvswMAgOBwRbHT1s6fP68f/ehHysjI0C233KKZM2fq0UcfbfaLRi3L8rlv23azbd+Ulpam6upq51ZWVtYm8wMAgMAL6tjp27evfvjDH/psu/HGG3X8+HFJksfjkaRmZ3EqKysv+otKL3C5XIqMjPS5AQAAMwV17Nx22206fPiwz7YjR47ouuuukyTFxsbK4/EoLy/P2d/Q0KD8/HwlJia266wAACA4terTWO3lqaeeUmJiojIyMjR58mTt27dPa9as0Zo1ayR9/fZVSkqKMjIyFBcXp7i4OGVkZKhbt26aMmVKgKcHAADBIKhj59Zbb9WWLVuUlpamF198UbGxscrKytLUqVOdNfPmzVNdXZ1mz56tqqoqDR8+XLm5ufwbOwAAQFKQx44kjR8/XuPHj29xv2VZSk9PV3p6evsNBQAAOoygvmYHAADgShE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoHSp2MjMzZVmWUlJSnG22bSs9PV1er1dhYWFKSkrSoUOHAjckAAAIKh0mdgoLC7VmzRrdfPPNPtuXL1+uFStWKCcnR4WFhfJ4PBo9erRqa2sDNCkAAAgmHSJ2Tp06palTp2rt2rXq0aOHs922bWVlZWnBggWaNGmS4uPjtWHDBp05c0abNm0K4MQAACBYdIjYefzxxzVu3DjdfffdPttLS0tVUVGh5ORkZ5vL5dLIkSNVUFDQ4vPV19erpqbG5wYAAMzUOdADXMrmzZt14MABFRYWNttXUVEhSXK73T7b3W63jh071uJzZmZmatGiRf4dFAAABKWgPrNTVlamJ598Uhs3blTXrl1bXGdZls9927abbfumtLQ0VVdXO7eysjK/zQwAAIJLUJ/ZKSoqUmVlpRISEpxtTU1N2rVrl3JycnT48GFJX5/h6du3r7OmsrKy2dmeb3K5XHK5XG03OAAACBpBfWbnrrvu0gcffKCSkhLnNnToUE2dOlUlJSW6/vrr5fF4lJeX5zymoaFB+fn5SkxMDODkAAAgWAT1mZ2IiAjFx8f7bAsPD1fPnj2d7SkpKcrIyFBcXJzi4uKUkZGhbt26acqUKYEYGQAABJmgjp3LMW/ePNXV1Wn27NmqqqrS8OHDlZubq4iIiECPBgAAgkCHi52dO3f63LcsS+np6UpPTw/IPAAAILgF9TU7AAAAV4rYAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRgjp2MjMzdeuttyoiIkJ9+vTRz372Mx0+fNhnjW3bSk9Pl9frVVhYmJKSknTo0KEATQwAAIJNUMdOfn6+Hn/8ce3du1d5eXk6d+6ckpOTdfr0aWfN8uXLtWLFCuXk5KiwsFAej0ejR49WbW1tACcHAADBonOgB/guW7du9bm/bt069enTR0VFRbrjjjtk27aysrK0YMECTZo0SZK0YcMGud1ubdq0STNnzrzo89bX16u+vt65X1NT03YvAgAABFRQn9n5turqaklSdHS0JKm0tFQVFRVKTk521rhcLo0cOVIFBQUtPk9mZqaioqKcW0xMTNsODgAAAqbDxI5t20pNTdVPfvITxcfHS5IqKiokSW6322et2+129l1MWlqaqqurnVtZWVnbDQ4AAAIqqN/G+qY5c+bo/fff1+7du5vtsyzL575t2822fZPL5ZLL5fL7jAAAIPh0iDM7c+fO1VtvvaUdO3aoX79+znaPxyNJzc7iVFZWNjvbAwAArk5BHTu2bWvOnDl644039Mc//lGxsbE++2NjY+XxeJSXl+dsa2hoUH5+vhITE9t7XAAAEISC+m2sxx9/XJs2bdLvf/97RUREOGdwoqKiFBYWJsuylJKSooyMDMXFxSkuLk4ZGRnq1q2bpkyZEuDpAQBAMAjq2Fm1apUkKSkpyWf7unXr9PDDD0uS5s2bp7q6Os2ePVtVVVUaPny4cnNzFRER0c7TAgCAYBTUsWPb9iXXWJal9PR0paent/1AAACgwwnqa3YAAACuFLEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxmTOy8/PLLio2NVdeuXZWQkKB33nkn0CMBAIAgYETsvP7660pJSdGCBQtUXFys22+/XWPHjtXx48cDPRoAAAgwI2JnxYoVmjFjhh555BHdeOONysrKUkxMjFatWhXo0QAAQIB1DvQAV6qhoUFFRUWaP3++z/bk5GQVFBRc9DH19fWqr6937ldXV0uSampq/D7f+fozfn9OXJ62OJ7fxLENHI6tudry2HJcA6etjuuF57Vt+zvXdfjY+fLLL9XU1CS32+2z3e12q6Ki4qKPyczM1KJFi5ptj4mJaZMZERhRWYGeAG2FY2sujq2Z2vq41tbWKioqqsX9HT52LrAsy+e+bdvNtl2Qlpam1NRU5/758+f11VdfqWfPni0+5mpUU1OjmJgYlZWVKTIyMtDjwI84tmbiuJqLY3txtm2rtrZWXq/3O9d1+Njp1auXQkJCmp3FqaysbHa25wKXyyWXy+WzrXv37m01YocXGRnJN5ehOLZm4riai2Pb3Hed0bmgw1+gHBoaqoSEBOXl5flsz8vLU2JiYoCmAgAAwaLDn9mRpNTUVD344IMaOnSoRowYoTVr1uj48eOaNWtWoEcDAAABZkTs3HfffTpx4oRefPFFlZeXKz4+Xn/4wx903XXXBXq0Ds3lcmnhwoXN3vJDx8exNRPH1Vwc2ytj2Zf6vBYAAEAH1uGv2QEAAPguxA4AADAasQMAAIxG7AAAAKMROwbZtWuXJkyYIK/XK8uy9Oabb/rst21b6enp8nq9CgsLU1JSkg4dOuSzpr6+XnPnzlWvXr0UHh6uiRMn6tNPP/VZU1VVpQcffFBRUVGKiorSgw8+qJMnT/qsOX78uCZMmKDw8HD16tVLTzzxhBoaGtriZV81Xn75ZcXGxqpr165KSEjQO++84+zj2HZs7XFslyxZosTERHXr1q3Ff0T1ySefVEJCglwul4YMGeLvl3lVudTP4zfeeENjxoxRr169ZFmWSkpKmj3HmjVrlJSUpMjISFmW1ex7UeJ79nIROwY5ffq0Bg8erJycnIvuX758uVasWKGcnBwVFhbK4/Fo9OjRqq2tddakpKRoy5Yt2rx5s3bv3q1Tp05p/PjxampqctZMmTJFJSUl2rp1q7Zu3aqSkhI9+OCDzv6mpiaNGzdOp0+f1u7du7V582b97ne/09NPP912L95wr7/+ulJSUrRgwQIVFxfr9ttv19ixY3X8+HFJHNuOrL2ObUNDg+6991794he/aHEW27Y1ffp03XfffW33gq8Sl/p5fPr0ad12221aunRpi89x5swZ3XPPPfrVr37V4hq+Zy+TDSNJsrds2eLcP3/+vO3xeOylS5c6286ePWtHRUXZq1evtm3btk+ePGl36dLF3rx5s7Pms88+szt16mRv3brVtm3b/vDDD21J9t69e501e/bssSXZH330kW3btv2HP/zB7tSpk/3ZZ585a1577TXb5XLZ1dXVbfJ6TTds2DB71qxZPtsGDhxoz58/n2PbwbXHsf2mdevW2VFRUd8508KFC+3Bgwe3/kXBx7d/Hn9TaWmpLckuLi5u8fE7duywJdlVVVU+2/mevXyc2blKlJaWqqKiQsnJyc42l8ulkSNHqqCgQJJUVFSkxsZGnzVer1fx8fHOmj179igqKkrDhw931vz4xz9WVFSUz5r4+HifX8w2ZswY1dfXq6ioqE1fp4kaGhpUVFTkc1wkKTk5WQUFBRzbDqy9ji3MxPfs5SN2rhIXflHqt385qtvtdvZVVFQoNDRUPXr0+M41ffr0afb8ffr08Vnz7a/To0cPhYaGNvuFrbi0L7/8Uk1NTS0eO45tx9VexxZm4nv28hE7VxnLsnzu27bdbNu3fXvNxda3Zg2+n0sdO45tx9UexxZm4nv28hA7VwmPxyNJzUq+srLSqX6Px6OGhgZVVVV955q//vWvzZ7/iy++8Fnz7a9TVVWlxsbGZv+HgUvr1auXQkJCWjx2HNuOq72OLczE9+zlI3auErGxsfJ4PMrLy3O2NTQ0KD8/X4mJiZKkhIQEdenSxWdNeXm5Dh486KwZMWKEqqurtW/fPmfNn/70J1VXV/usOXjwoMrLy501ubm5crlcSkhIaNPXaaLQ0FAlJCT4HBdJysvLU2JiIse2A2uvYwsz8T37PQTiqmi0jdraWru4uNguLi62JdkrVqywi4uL7WPHjtm2bdtLly61o6Ki7DfeeMP+4IMP7AceeMDu27evXVNT4zzHrFmz7H79+tnbtm2zDxw4YI8aNcoePHiwfe7cOWfNPffcY9988832nj177D179tiDBg2yx48f7+w/d+6cHR8fb9911132gQMH7G3bttn9+vWz58yZ035/GYbZvHmz3aVLF/uVV16xP/zwQzslJcUODw+3P/nkE9u2ObYdWXsd22PHjtnFxcX2okWL7Guuucb5WVFbW+usOXr0qF1cXGzPnDnTHjBggLOmvr6+/f5CDHGpn8cnTpywi4uL7bffftuWZG/evNkuLi62y8vLnecoLy+3i4uL7bVr19qS7F27dtnFxcX2iRMnnDV8z14eYscgFz6e+O3btGnTbNv++uPnCxcutD0ej+1yuew77rjD/uCDD3yeo66uzp4zZ44dHR1th4WF2ePHj7ePHz/us+bEiRP21KlT7YiICDsiIsKeOnVqs49EHjt2zB43bpwdFhZmR0dH23PmzLHPnj3bli/feP/yL/9iX3fddXZoaKj9ox/9yM7Pz3f2cWw7tvY4ttOmTbvoz4cdO3Y4a0aOHHnRNaWlpW358o10qZ/H69atu+j+hQsXOs+xcOHCi65Zt26ds4bv2ctj2bZtt/XZIwAAgEDhmh0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAO0iKSlJKSkpLe7v37+/srKy2mWWTz75RJZlqaSkpF2+HoDAInYABIXCwkI99thjzn3LsvTmm29e9uPXr1+v7t27X9bamJgYlZeXKz4+/ntOCaAj6hzoAQBAknr37t0uX6ehoUGhoaHyeDzt8vUABB5ndgC0m3PnzmnOnDnq3r27evbsqeeee04Xfj3fN9/G6t+/vyTp7//+72VZlnP/vffe05133qmIiAhFRkYqISFB+/fv186dO/Xzn/9c1dXVsixLlmUpPT3dea7Fixfr4YcfVlRUlB599NFmb2Pt3LlTlmVp+/btGjp0qLp166bExEQdPnzYZ/7FixerT58+ioiI0COPPKL58+dryJAhzv6dO3dq2LBhCg8PV/fu3XXbbbfp2LFjbfXXCeAyETsA2s2GDRvUuXNn/elPf9I///M/a+XKlfrXf/3XZusKCwslSevWrVN5eblzf+rUqerXr58KCwtVVFSk+fPnq0uXLkpMTFRWVpYiIyNVXl6u8vJyPfPMM87z/frXv1Z8fLyKior0/PPPtzjfggUL9NJLL2n//v3q3Lmzpk+f7uz793//dy1ZskTLli1TUVGRrr32Wq1atcrZf+7cOf3sZz/TyJEj9f7772vPnj167LHHZFnWFf+9AbgyvI0FoN3ExMRo5cqVsixLN9xwgz744AOtXLlSjz76qM+6C29pde/e3eftpuPHj+uXv/ylBg4cKEmKi4tz9kVFRcmyrIu+PTVq1Cif+Pnkk08uOt+SJUs0cuRISdL8+fM1btw4nT17Vl27dlV2drZmzJihn//855KkF154Qbm5uTp16pQkqaamRtXV1Ro/frz+5m/+RpJ04403fq+/HwBtgzM7ANrNj3/8Y58zHSNGjNDRo0fV1NR0WY9PTU3VI488orvvvltLly7Vxx9/fFmPGzp06GWtu/nmm50/9+3bV5JUWVkpSTp8+LCGDRvms/6b96Ojo/Xwww9rzJgxmjBhgv7pn/5J5eXll/V1AbQtYgdAh5Genq5Dhw5p3Lhx+uMf/6gf/vCH2rJlyyUfFx4eflnP36VLF+fPF6Ls/PnzzbZdcOF6owvWrVunPXv2KDExUa+//roGDBigvXv3XtbXBtB2iB0A7ebb/+Hfu3ev4uLiFBIS0mxtly5dLnrGZ8CAAXrqqaeUm5urSZMmad26dZKk0NDQyz5D1Bo33HCD9u3b57Nt//79zdbdcsstSktLU0FBgeLj47Vp06Y2mwnA5SF2ALSbsrIypaam6vDhw3rttdeUnZ2tJ5988qJr+/fvr+3bt6uiokJVVVWqq6vTnDlztHPnTh07dkzvvvuuCgsLneti+vfvr1OnTmn79u368ssvdebMGb/OPnfuXL3yyivasGGDjh49qsWLF+v99993zvaUlpYqLS1Ne/bs0bFjx5Sbm6sjR45w3Q4QBLhAGUC7eeihh1RXV6dhw4YpJCREc+fO9fmHBL/ppZdeUmpqqtauXasf/OAHOnLkiE6cOKGHHnpIf/3rX9WrVy9NmjRJixYtkiQlJiZq1qxZuu+++3TixAktXLjQ+fi5P0ydOlV/+ctf9Mwzz+js2bOaPHmyHn74YedsT7du3fTRRx9pw4YNOnHihPr27as5c+Zo5syZfpsBQOtY9rffdAYAXJbRo0fL4/Ho1VdfDfQoAL4DZ3YA4DKcOXNGq1ev1pgxYxQSEqLXXntN27ZtU15eXqBHA3AJnNkBgMtQV1enCRMm6MCBA6qvr9cNN9yg5557TpMmTQr0aAAugdgBAABG49NYAADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKP9P4FugkOtagcYAAAAAElFTkSuQmCC" }, "metadata": {}, "output_type": "display_data" @@ -170,7 +164,7 @@ ], "source": [ "device = LocalSimulator()\n", - "result = device.run(conditioned_bell_program, shots=500).result()\n", + "result = device.run(conditioned_bell, shots=500).result()\n", "counts = Counter(result.measurements[\"c\"])\n", "print(\"measurement counts: \", counts)\n", "\n", @@ -206,7 +200,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "88403f29", "metadata": {}, "outputs": [ @@ -231,13 +225,12 @@ "\n", "\n", "@aq.main(num_qubits=n_bell * 2)\n", - "def multiple_bell(n_bell) -> None:\n", + "def multiple_bell() -> None:\n", " for i in aq.range(0, 2 * n_bell, 2):\n", " bell(i, i + 1)\n", "\n", "\n", - "multiple_bell_program = multiple_bell(n_bell)\n", - "print(multiple_bell_program.to_ir())" + "print(multiple_bell.to_ir())" ] }, { diff --git a/examples/autoqasm/3_Iterative_phase_estimation.ipynb b/examples/autoqasm/3_Iterative_phase_estimation.ipynb index 968e0d9b..37a5b590 100644 --- a/examples/autoqasm/3_Iterative_phase_estimation.ipynb +++ b/examples/autoqasm/3_Iterative_phase_estimation.ipynb @@ -82,13 +82,11 @@ "metadata": {}, "outputs": [], "source": [ - "@aq.main(num_qubits=2)\n", - "def ipe(n_iterations: int):\n", - " \"\"\"Iterative phase estimation algorithm.\n", + "n_iterations = 4\n", "\n", - " Args:\n", - " n_iterations (int): Number of iterations.\n", - " \"\"\"\n", + "@aq.main(num_qubits=2)\n", + "def ipe():\n", + " \"\"\"Iterative phase estimation algorithm.\"\"\"\n", " q_ancilla = 0\n", " q_data = 1\n", "\n", @@ -119,11 +117,7 @@ " c = measure(q_ancilla)\n", " b0[iteration] = c\n", "\n", - " reset(q_ancilla)\n", - "\n", - "\n", - "n_iterations = 4\n", - "ipe_program = ipe(n_iterations)" + " reset(q_ancilla)" ] }, { @@ -149,10 +143,8 @@ }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGwCAYAAABPSaTdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAie0lEQVR4nO3df1RUdf7H8dcgiojOGP4YIDHY1sRfaWkq6ikzijZzc3UrW3czNWkNLcT8wUl0LQ31rD/SNSm3xPaoW+2uttbJ1UjthwhK2m+JCpX96qCpzCgKosz3j45zmlXLhoEZPj0f58w53c+9c3njPzy7c2fG4na73QIAADBUSKAHAAAAqEvEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMFhroAYJBTU2NDh06pBYtWshisQR6HAAAcAXcbrdOnjypmJgYhYRc/voNsSPp0KFDio2NDfQYAADAB6WlpWrXrt1l9xM7klq0aCHpu38sq9Ua4GkAAMCVcLlcio2N9fwdvxxiR/K8dGW1WokdAAAamB+7BYUblAEAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABgtoLHz7rvvasiQIYqJiZHFYtGGDRu89rvdbs2cOVPR0dEKDw9XUlKSiouLvY45fvy4Ro4cKavVqpYtW2rs2LE6depUPf4WAAAgmAU0dioqKtS9e3ctX778kvsXLFigpUuXKjs7W/n5+YqIiFBycrIqKys9x4wcOVKfffaZtmzZojfeeEPvvvuuUlJS6utXAAAAQc7idrvdgR5C+u4bS9evX6+hQ4dK+u6qTkxMjCZPnqwnnnhCkuR0OmW325WTk6MRI0boiy++UOfOnbVr1y716tVLkrRp0ybddddd+u9//6uYmJgr+tkul0s2m01Op5NvPQcAoIG40r/fQXvPTklJiRwOh5KSkjxrNptNffr0UV5eniQpLy9PLVu29ISOJCUlJSkkJET5+fmXPXdVVZVcLpfXAwAAmCk00ANcjsPhkCTZ7Xavdbvd7tnncDjUtm1br/2hoaGKjIz0HHMpWVlZmj17tp8nvrS46W/Wy88BACBY7Z83OKA/P2iv7NSljIwMOZ1Oz6O0tDTQIwEAgDoStLETFRUlSSorK/NaLysr8+yLiorSkSNHvPafO3dOx48f9xxzKWFhYbJarV4PAABgpqCNnfj4eEVFRSk3N9ez5nK5lJ+fr8TERElSYmKiysvLVVhY6DnmnXfeUU1Njfr06VPvMwMAgOAT0Ht2Tp06pa+++sqzXVJSor179yoyMlLt27dXWlqa5syZow4dOig+Pl6ZmZmKiYnxvGOrU6dOuvPOOzVu3DhlZ2erurpaEyZM0IgRI674nVgAAMBsAY2d3bt369Zbb/Vsp6enS5JGjRqlnJwcTZ06VRUVFUpJSVF5ebkGDBigTZs2qWnTpp7nrFmzRhMmTNBtt92mkJAQDR8+XEuXLq333wUAAASnoPmcnUCqy8/Z4d1YAICfu7p6N1aD/5wdAAAAfyB2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGC2oY+f8+fPKzMxUfHy8wsPDde211+rpp5+W2+32HON2uzVz5kxFR0crPDxcSUlJKi4uDuDUAAAgmAR17MyfP18rVqzQX/7yF33xxReaP3++FixYoGXLlnmOWbBggZYuXars7Gzl5+crIiJCycnJqqysDODkAAAgWIQGeoAfsmPHDt1zzz0aPHiwJCkuLk7r1q1TQUGBpO+u6ixZskQzZszQPffcI0l6+eWXZbfbtWHDBo0YMeKS562qqlJVVZVn2+Vy1fFvAgAAAiWor+z069dPubm5+vLLLyVJH330kd5//3396le/kiSVlJTI4XAoKSnJ8xybzaY+ffooLy/vsufNysqSzWbzPGJjY+v2FwEAAAET1Fd2pk+fLpfLpYSEBDVq1Ejnz5/X3LlzNXLkSEmSw+GQJNntdq/n2e12z75LycjIUHp6umfb5XIRPAAAGCqoY+fVV1/VmjVrtHbtWnXp0kV79+5VWlqaYmJiNGrUKJ/PGxYWprCwMD9OCgAAglVQx86UKVM0ffp0z7033bp104EDB5SVlaVRo0YpKipKklRWVqbo6GjP88rKytSjR49AjAwAAIJMUN+zc/r0aYWEeI/YqFEj1dTUSJLi4+MVFRWl3Nxcz36Xy6X8/HwlJibW66wAACA4BfWVnSFDhmju3Llq3769unTpoj179mjRokUaM2aMJMlisSgtLU1z5sxRhw4dFB8fr8zMTMXExGjo0KGBHR4AAASFoI6dZcuWKTMzU48++qiOHDmimJgYPfLII5o5c6bnmKlTp6qiokIpKSkqLy/XgAEDtGnTJjVt2jSAkwMAgGBhcX//44h/plwul2w2m5xOp6xWq1/PHTf9Tb+eDwCAhmb/vMF1ct4r/fsd1PfsAAAA1BaxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMFfez83//9n37/+9+rVatWCg8PV7du3bR7927PfrfbrZkzZyo6Olrh4eFKSkpScXFxACcGAADBJKhj58SJE+rfv78aN26st956S59//rkWLlyoq666ynPMggULtHTpUmVnZys/P18RERFKTk5WZWVlACcHAADBIjTQA/yQ+fPnKzY2VqtWrfKsxcfHe/7b7XZryZIlmjFjhu655x5J0ssvvyy73a4NGzZoxIgR9T4zAAAILkF9Zeff//63evXqpXvvvVdt27bVDTfcoJUrV3r2l5SUyOFwKCkpybNms9nUp08f5eXlXfa8VVVVcrlcXg8AAGCmoI6db775RitWrFCHDh30n//8R+PHj9djjz2m1atXS5IcDockyW63ez3Pbrd79l1KVlaWbDab5xEbG1t3vwQAAAiooI6dmpoa3XjjjXrmmWd0ww03KCUlRePGjVN2dnatzpuRkSGn0+l5lJaW+mliAAAQbII6dqKjo9W5c2evtU6dOungwYOSpKioKElSWVmZ1zFlZWWefZcSFhYmq9Xq9QAAAGYK6tjp37+/ioqKvNa+/PJLXXPNNZK+u1k5KipKubm5nv0ul0v5+flKTEys11kBAEBwCup3Y02aNEn9+vXTM888o/vuu08FBQV64YUX9MILL0iSLBaL0tLSNGfOHHXo0EHx8fHKzMxUTEyMhg4dGtjhAQBAUAjq2Lnpppu0fv16ZWRk6KmnnlJ8fLyWLFmikSNHeo6ZOnWqKioqlJKSovLycg0YMECbNm1S06ZNAzg5AAAIFha32+3+qU8qLS2VxWJRu3btJEkFBQVau3atOnfurJSUFL8PWddcLpdsNpucTqff79+Jm/6mX88HAEBDs3/e4Do575X+/fbpnp3f/e532rp1q6Tv3v59++23q6CgQE8++aSeeuop3yYGAACoAz7FzqeffqrevXtLkl599VV17dpVO3bs0Jo1a5STk+PP+QAAAGrFp9iprq5WWFiYJOntt9/Wr3/9a0lSQkKCDh8+7L/pAAAAasmn2OnSpYuys7P13nvvacuWLbrzzjslSYcOHVKrVq38OiAAAEBt+BQ78+fP1/PPP6+BAwfqgQceUPfu3SV9911WF17eAgAACAY+vfV84MCB+vbbb+VyuXTVVVd51lNSUhQREeG34QAAAGrLpys7gwYN0smTJ71CR5IiIyN1//33+2UwAAAAf/ApdrZt26azZ89etF5ZWan33nuv1kMBAAD4y096Gevjjz/2/Pfnn38uh8Ph2T5//rw2bdqkq6++2n/TAQAA1NJPip0ePXrIYrHIYrFo0KBBF+0PDw/XsmXL/DYcAABAbf2k2CkpKZHb7dYvfvELFRQUqE2bNp59TZo0Udu2bdWoUSO/DwkAAOCrnxQ711xzjSSppqamToYBAADwN5+/9by4uFhbt27VkSNHLoqfmTNn1nowAAAAf/ApdlauXKnx48erdevWioqKksVi8eyzWCzEDgAACBo+xc6cOXM0d+5cTZs2zd/zAAAA+JVPn7Nz4sQJ3Xvvvf6eBQAAwO98ip17771Xmzdv9vcsAAAAfufTy1i//OUvlZmZqZ07d6pbt25q3Lix1/7HHnvML8MBAADUlsXtdrt/6pPi4+Mvf0KLRd98802thqpvLpdLNptNTqdTVqvVr+eOm/6mX88HAEBDs3/e4Do575X+/fbpyk5JSYnPgwEAANQnn+7ZAQAAaCh8urIzZsyYH9z/0ksv+TQMAACAv/kUOydOnPDarq6u1qeffqry8vJLfkEoAABAoPgUO+vXr79oraamRuPHj9e1115b66EAAAD8xW/37ISEhCg9PV2LFy/21ykBAABqza83KH/99dc6d+6cP08JAABQKz69jJWenu617Xa7dfjwYb355psaNWqUXwYDAADwB59iZ8+ePV7bISEhatOmjRYuXPij79QCAACoTz7FztatW/09BwAAQJ3wKXYuOHr0qIqKiiRJHTt2VJs2bfwyFAAAgL/4dINyRUWFxowZo+joaN188826+eabFRMTo7Fjx+r06dP+nhEAAMBnPsVOenq6tm/fro0bN6q8vFzl5eV6/fXXtX37dk2ePNnfMwIAAPjMp5ex/vnPf+of//iHBg4c6Fm76667FB4ervvuu08rVqzw13wAAAC14tOVndOnT8tut1+03rZtW17GAgAAQcWn2ElMTNSsWbNUWVnpWTtz5oxmz56txMREvw0HAABQWz69jLVkyRLdeeedateunbp37y5J+uijjxQWFqbNmzf7dUAAAIDa8Cl2unXrpuLiYq1Zs0b79u2TJD3wwAMaOXKkwsPD/TogAABAbfgUO1lZWbLb7Ro3bpzX+ksvvaSjR49q2rRpfhkOAACgtny6Z+f5559XQkLCRetdunRRdnZ2rYcCAADwF59ix+FwKDo6+qL1Nm3a6PDhw7UeCgAAwF98ip3Y2Fh98MEHF61/8MEHiomJqfVQAAAA/uLTPTvjxo1TWlqaqqurNWjQIElSbm6upk6dyicoAwCAoOJT7EyZMkXHjh3To48+qrNnz0qSmjZtqmnTpikjI8OvAwIAANSGT7FjsVg0f/58ZWZm6osvvlB4eLg6dOigsLAwf88HAABQKz7FzgXNmzfXTTfd5K9ZAAAA/M6nG5QBAAAaCmIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYrUHFzrx582SxWJSWluZZq6ysVGpqqlq1aqXmzZtr+PDhKisrC9yQAAAgqDSY2Nm1a5eef/55XX/99V7rkyZN0saNG/Xaa69p+/btOnTokIYNGxagKQEAQLBpELFz6tQpjRw5UitXrtRVV13lWXc6nXrxxRe1aNEiDRo0SD179tSqVau0Y8cO7dy5M4ATAwCAYNEgYic1NVWDBw9WUlKS13phYaGqq6u91hMSEtS+fXvl5eVd9nxVVVVyuVxeDwAAYKbQQA/wY/7+97/rww8/1K5duy7a53A41KRJE7Vs2dJr3W63y+FwXPacWVlZmj17tr9HBQAAQSior+yUlpbq8ccf15o1a9S0aVO/nTcjI0NOp9PzKC0t9du5AQBAcAnq2CksLNSRI0d04403KjQ0VKGhodq+fbuWLl2q0NBQ2e12nT17VuXl5V7PKysrU1RU1GXPGxYWJqvV6vUAAABmCuqXsW677TZ98sknXmujR49WQkKCpk2bptjYWDVu3Fi5ubkaPny4JKmoqEgHDx5UYmJiIEYGAABBJqhjp0WLFuratavXWkREhFq1auVZHzt2rNLT0xUZGSmr1aqJEycqMTFRffv2DcTIAAAgyAR17FyJxYsXKyQkRMOHD1dVVZWSk5P13HPPBXosAAAQJCxut9sd6CECzeVyyWazyel0+v3+nbjpb/r1fAAANDT75w2uk/Ne6d/voL5BGQAAoLaIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYL6tjJysrSTTfdpBYtWqht27YaOnSoioqKvI6prKxUamqqWrVqpebNm2v48OEqKysL0MQAACDYBHXsbN++Xampqdq5c6e2bNmi6upq3XHHHaqoqPAcM2nSJG3cuFGvvfaatm/frkOHDmnYsGEBnBoAAAST0EAP8EM2bdrktZ2Tk6O2bduqsLBQN998s5xOp1588UWtXbtWgwYNkiStWrVKnTp10s6dO9W3b99LnreqqkpVVVWebZfLVXe/BAAACKigvrLzv5xOpyQpMjJSklRYWKjq6molJSV5jklISFD79u2Vl5d32fNkZWXJZrN5HrGxsXU7OAAACJgGEzs1NTVKS0tT//791bVrV0mSw+FQkyZN1LJlS69j7Xa7HA7HZc+VkZEhp9PpeZSWltbl6AAAIICC+mWs70tNTdWnn36q999/v9bnCgsLU1hYmB+mAgAAwa5BXNmZMGGC3njjDW3dulXt2rXzrEdFRens2bMqLy/3Or6srExRUVH1PCUAAAhGQR07brdbEyZM0Pr16/XOO+8oPj7ea3/Pnj3VuHFj5ebmetaKiop08OBBJSYm1ve4AAAgCAX1y1ipqalau3atXn/9dbVo0cJzH47NZlN4eLhsNpvGjh2r9PR0RUZGymq1auLEiUpMTLzsO7EAAMDPS1DHzooVKyRJAwcO9FpftWqVHnroIUnS4sWLFRISouHDh6uqqkrJycl67rnn6nlSAAAQrII6dtxu948e07RpUy1fvlzLly+vh4kAAEBDE9T37AAAANQWsQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjGRM7y5cvV1xcnJo2bao+ffqooKAg0CMBAIAgYETsvPLKK0pPT9esWbP04Ycfqnv37kpOTtaRI0cCPRoAAAgwI2Jn0aJFGjdunEaPHq3OnTsrOztbzZo100svvRTo0QAAQICFBnqA2jp79qwKCwuVkZHhWQsJCVFSUpLy8vIu+ZyqqipVVVV5tp1OpyTJ5XL5fb6aqtN+PycAAA1JXfx9/f553W73Dx7X4GPn22+/1fnz52W3273W7Xa79u3bd8nnZGVlafbs2Retx8bG1smMAAD8nNmW1O35T548KZvNdtn9DT52fJGRkaH09HTPdk1NjY4fP65WrVrJYrEEcDIA/uZyuRQbG6vS0lJZrdZAjwPAj9xut06ePKmYmJgfPK7Bx07r1q3VqFEjlZWVea2XlZUpKirqks8JCwtTWFiY11rLli3rakQAQcBqtRI7gIF+6IrOBQ3+BuUmTZqoZ8+eys3N9azV1NQoNzdXiYmJAZwMAAAEgwZ/ZUeS0tPTNWrUKPXq1Uu9e/fWkiVLVFFRodGjRwd6NAAAEGBGxM7999+vo0ePaubMmXI4HOrRo4c2bdp00U3LAH5+wsLCNGvWrIteugbw82Fx/9j7tQAAABqwBn/PDgAAwA8hdgAAgNGIHQAAYDRiBwAAGI3YARD03n33XQ0ZMkQxMTGyWCzasGGD1/5//etfuuOOOzyfgr53796LzlFZWanU1FS1atVKzZs31/Dhwy/6MNKDBw9q8ODBatasmdq2baspU6bo3LlzdfibAagPxA6AoFdRUaHu3btr+fLll90/YMAAzZ8//7LnmDRpkjZu3KjXXntN27dv16FDhzRs2DDP/vPnz2vw4ME6e/asduzYodWrVysnJ0czZ870++8DoH7x1nMADYrFYtH69es1dOjQi/bt379f8fHx2rNnj3r06OFZdzqdatOmjdauXavf/va3kqR9+/apU6dOysvLU9++ffXWW2/p7rvv1qFDhzyf0ZWdna1p06bp6NGjatKkSX38egDqAFd2ABivsLBQ1dXVSkpK8qwlJCSoffv2ysvLkyTl5eWpW7duXh9GmpycLJfLpc8++6zeZwbgP8QOAOM5HA41adLkoi/8tdvtcjgcnmP+91PXL2xfOAZAw0TsAAAAoxE7AIwXFRWls2fPqry83Gu9rKxMUVFRnmP+991ZF7YvHAOgYSJ2ABivZ8+eaty4sXJzcz1rRUVFOnjwoBITEyVJiYmJ+uSTT3TkyBHPMVu2bJHValXnzp3rfWYA/mPEt54DMNupU6f01VdfebZLSkq0d+9eRUZGqn379jp+/LgOHjyoQ4cOSfouZKTvrshERUXJZrNp7NixSk9PV2RkpKxWqyZOnKjExET17dtXknTHHXeoc+fO+sMf/qAFCxbI4XBoxowZSk1N5RvTgQaOt54DCHrbtm3TrbfeetH6qFGjlJOTo5ycHI0ePfqi/bNmzdKf/vQnSd99qODkyZO1bt06VVVVKTk5Wc8995zXS1QHDhzQ+PHjtW3bNkVERGjUqFGaN2+eQkP5/0KgISN2AACA0bhnBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgdAnRs4cKDS0tIuuz8uLk5Lliypl1n2798vi8WivXv31svPAxB4xA6AgNu1a5dSUlI82xaLRRs2bLji5+fk5Khly5ZXdGxsbKwOHz6srl27/sQpATRUfOELgIBr06ZNvfycs2fPqkmTJl7fhwXAfFzZAVAvzp07pwkTJshms6l169bKzMzUha/m+/7LWHFxcZKk3/zmN7JYLJ7tjz76SLfeeqtatGghq9Wqnj17avfu3dq2bZtGjx4tp9Mpi8Uii8Xi+fLPuLg4Pf3003rwwQdltVqVkpJy0ctY27Ztk8ViUW5urnr16qVmzZqpX79+nm9Ov2DOnDlq27atWrRooYcffljTp09Xjx49PPu3bdum3r17KyIiQi1btlT//v114MCBuvrnBPATEDsA6sXq1asVGhqqgoICPfvss1q0aJH++te/XnTcrl27JEmrVq3S4cOHPdsjR45Uu3bttGvXLhUWFmr69Olq3Lix+vXrpyVLlshqterw4cM6fPiwnnjiCc/5/vznP6t79+7as2ePMjMzLzvfk08+qYULF2r37t0KDQ3VmDFjPPvWrFmjuXPnav78+SosLFT79u21YsUKz/5z585p6NChuuWWW/Txxx8rLy9PKSkpslgstf53A1B7vIwFoF7ExsZq8eLFslgs6tixoz755BMtXrxY48aN8zruwktaLVu29Hq56eDBg5oyZYoSEhIkSR06dPDss9lsslgsl3x5atCgQZo8ebJne//+/Zecb+7cubrlllskSdOnT9fgwYNVWVmppk2batmyZRo7dqxGjx4tSZo5c6Y2b96sU6dOSZJcLpecTqfuvvtuXXvttZKkTp06/aR/HwB1hys7AOpF3759va50JCYmqri4WOfPn7+i56enp+vhhx9WUlKS5s2bp6+//vqKnterV68rOu7666/3/Hd0dLQk6ciRI5KkoqIi9e7d2+v4729HRkbqoYceUnJysoYMGaJnn31Whw8fvqKfC6DuETsAGoQ//elP+uyzzzR48GC988476ty5s9avX/+jz4uIiLii8zdu3Njz3xeirKam5ornW7VqlfLy8tSvXz+98soruu6667Rz584rfj6AukPsAKgX+fn5Xts7d+5Uhw4d1KhRo4uObdy48SWv+Fx33XWaNGmSNm/erGHDhmnVqlWSpCZNmlzxFSJfdOzY0XPv0AX/uy1JN9xwgzIyMrRjxw517dpVa9eurbOZAFw5YgdAvTh48KDS09NVVFSkdevWadmyZXr88ccveWxcXJxyc3PlcDh04sQJnTlzRhMmTNC2bdt04MABffDBB9q1a5fnvpi4uDidOnVKubm5+vbbb3X69Gm/zj5x4kS9+OKLWr16tYqLizVnzhx9/PHHnitAJSUlysjIUF5eng4cOKDNmzeruLiY+3aAIMENygDqxYMPPqgzZ86od+/eatSokR5//HGvDxL8voULFyo9PV0rV67U1VdfrS+//FLHjh3Tgw8+qLKyMrVu3VrDhg3T7NmzJUn9+vXTH//4R91///06duyYZs2a5Xn7uT+MHDlS33zzjZ544glVVlbqvvvu00MPPaSCggJJUrNmzbRv3z6tXr1ax44dU3R0tFJTU/XII4/4bQYAvrO4L3zQBQDgit1+++2KiorS3/72t0CPAuBHcGUHAH7E6dOnlZ2dreTkZDVq1Ejr1q3T22+/rS1btgR6NABXgCs7APAjzpw5oyFDhmjPnj2qrKxUx44dNWPGDA0bNizQowG4AsQOAAAwGu/GAgAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABjt/wFgOMP0atPibwAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] + "text/plain": "
", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGwCAYAAABPSaTdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAiuUlEQVR4nO3df1RUdf7H8dcNdQIXxvzBjCQF7lJmmLaaJtVKpliZres5ZemW5s+WtIhM5WslehTSjsqunOzHburWUu3ZzWpPrQtZUoYmkmRaaT9QaYUoxQEVwR/3+0fHOc6iZcPADJ+ej3PmHO/n3rm8x394njt3GMu2bVsAAACGOi/YAwAAADQnYgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARmsT7AFCwcmTJ7Vv3z5FRkbKsqxgjwMAAM6Bbduqra1VTEyMzjvv7NdviB1J+/btU2xsbLDHAAAAfigvL1e3bt3Oup/YkRQZGSnp+/+sqKioIE8DAADORU1NjWJjY72/x8+G2JG8b11FRUUROwAAtDI/dgsKNygDAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwWlBj591339WIESMUExMjy7L06quv+uy3bVuZmZmKiYlReHi4kpOTtWPHDp9j6uvrNX36dHXu3Fnt27fXrbfeqq+//roFXwUAAAhlQY2dw4cPq3fv3srNzT3j/sWLF2vp0qXKzc1VcXGx3G63hg4dqtraWu8xaWlpWrNmjV566SVt2LBBhw4d0i233KITJ0601MsAAAAhzLJt2w72ENL331i6Zs0ajRw5UtL3V3ViYmKUlpamWbNmSfr+Ko7L5dKiRYs0depUeTwedenSRc8//7xGjx4tSdq3b59iY2P15ptvatiwYef0s2tqauR0OuXxePjWcwAAWolz/f0dsvfslJWVqbKyUikpKd41h8OhQYMGqaioSJJUUlKiY8eO+RwTExOjxMRE7zFnUl9fr5qaGp8HAAAwU5tgD3A2lZWVkiSXy+Wz7nK5tGfPHu8x7dq10wUXXNDomFPPP5Ps7GzNmzcvwBOfWdzsN1rk5wAAEKp2Pz48qD8/ZK/snGJZls+2bduN1v7Xjx2TkZEhj8fjfZSXlwdkVgAAEHpCNnbcbrckNbpCU1VV5b3a43a71dDQoOrq6rMecyYOh0NRUVE+DwAAYKaQjZ34+Hi53W4VFBR41xoaGlRYWKikpCRJUt++fdW2bVufYyoqKrR9+3bvMQAA4OctqPfsHDp0SF988YV3u6ysTKWlperYsaMuuugipaWlKSsrSwkJCUpISFBWVpYiIiI0ZswYSZLT6dTEiRP10EMPqVOnTurYsaNmzJihXr16aciQIcF6WQAAIIQENXa2bNmi66+/3rudnp4uSRo3bpxWrVqlmTNnqq6uTqmpqaqurtaAAQOUn5+vyMhI73OWLVumNm3a6Pbbb1ddXZ1uuOEGrVq1SmFhYS3+egAAQOgJmb+zE0zN+Xd2+DQWAODnrrk+jdXq/84OAABAIBA7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjBbSsXP8+HE98sgjio+PV3h4uLp376758+fr5MmT3mNs21ZmZqZiYmIUHh6u5ORk7dixI4hTAwCAUBLSsbNo0SI99dRTys3N1aeffqrFixfriSee0PLly73HLF68WEuXLlVubq6Ki4vldrs1dOhQ1dbWBnFyAAAQKtoEe4AfsnHjRv32t7/V8OHDJUlxcXF68cUXtWXLFknfX9XJycnRnDlzNGrUKEnS6tWr5XK5lJeXp6lTp57xvPX19aqvr/du19TUNPMrAQAAwRLSV3auvfZarVu3Trt27ZIkffTRR9qwYYNuvvlmSVJZWZkqKyuVkpLifY7D4dCgQYNUVFR01vNmZ2fL6XR6H7Gxsc37QgAAQNCE9JWdWbNmyePxqEePHgoLC9OJEye0cOFC3XnnnZKkyspKSZLL5fJ5nsvl0p49e8563oyMDKWnp3u3a2pqCB4AAAwV0rHz8ssv64UXXlBeXp4uv/xylZaWKi0tTTExMRo3bpz3OMuyfJ5n23ajtdM5HA45HI5mmxsAAISOkI6dhx9+WLNnz9Ydd9whSerVq5f27Nmj7OxsjRs3Tm63W9L3V3i6du3qfV5VVVWjqz0AAODnKaTv2Tly5IjOO893xLCwMO9Hz+Pj4+V2u1VQUODd39DQoMLCQiUlJbXorAAAIDSF9JWdESNGaOHChbrooot0+eWXa+vWrVq6dKkmTJgg6fu3r9LS0pSVlaWEhAQlJCQoKytLERERGjNmTJCnBwAAoSCkY2f58uV69NFHlZqaqqqqKsXExGjq1Kl67LHHvMfMnDlTdXV1Sk1NVXV1tQYMGKD8/HxFRkYGcXIAABAqLNu27WAPEWw1NTVyOp3yeDyKiooK6LnjZr8R0PMBANDa7H58eLOc91x/f4f0PTsAAABNRewAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaCEfO//973/1+9//Xp06dVJERIT69OmjkpIS737btpWZmamYmBiFh4crOTlZO3bsCOLEAAAglIR07FRXV+uaa65R27Zt9e9//1uffPKJlixZog4dOniPWbx4sZYuXarc3FwVFxfL7XZr6NChqq2tDd7gAAAgZLQJ9gA/ZNGiRYqNjdXKlSu9a3Fxcd5/27atnJwczZkzR6NGjZIkrV69Wi6XS3l5eZo6dWpLjwwAAEJMSF/Zef3119WvXz/ddtttio6O1pVXXqlnn33Wu7+srEyVlZVKSUnxrjkcDg0aNEhFRUVnPW99fb1qamp8HgAAwEwhHTtfffWVVqxYoYSEBP3nP//Rvffeq/vvv19//etfJUmVlZWSJJfL5fM8l8vl3Xcm2dnZcjqd3kdsbGzzvQgAABBUIR07J0+e1K9//WtlZWXpyiuv1NSpUzV58mStWLHC5zjLsny2bdtutHa6jIwMeTwe76O8vLxZ5gcAAMEX0rHTtWtX9ezZ02ftsssu0969eyVJbrdbkhpdxamqqmp0ted0DodDUVFRPg8AAGCmkI6da665Rjt37vRZ27Vrly6++GJJUnx8vNxutwoKCrz7GxoaVFhYqKSkpBadFQAAhKaQ/jTWgw8+qKSkJGVlZen222/X5s2b9cwzz+iZZ56R9P3bV2lpacrKylJCQoISEhKUlZWliIgIjRkzJsjTAwCAUBDSsXPVVVdpzZo1ysjI0Pz58xUfH6+cnByNHTvWe8zMmTNVV1en1NRUVVdXa8CAAcrPz1dkZGQQJwcAAKHCsm3b/qlPKi8vl2VZ6tatmyRp8+bNysvLU8+ePTVlypSAD9ncampq5HQ65fF4An7/TtzsNwJ6PgAAWpvdjw9vlvOe6+9vv+7ZGTNmjN555x1J398cPHToUG3evFn/93//p/nz5/s3MQAAQDPwK3a2b9+u/v37S5L+/ve/KzExUUVFRcrLy9OqVasCOR8AAECT+BU7x44dk8PhkCS99dZbuvXWWyVJPXr0UEVFReCmAwAAaCK/Yufyyy/XU089pffee08FBQW68cYbJUn79u1Tp06dAjogAABAU/gVO4sWLdLTTz+t5ORk3Xnnnerdu7ek77/L6tTbWwAAAKHAr4+eJycn67vvvlNNTY0uuOAC7/qUKVPUvn37gA0HAADQVH5d2Rk8eLBqa2t9QkeSOnbsqNGjRwdkMAAAgEDwK3bWr1+vhoaGRutHjx7Ve++91+ShAAAAAuUnvY21bds2778/+eQTny/gPHHihNauXasLL7wwcNMBAAA00U+KnT59+siyLFmWpcGDBzfaHx4eruXLlwdsOAAAgKb6SbFTVlYm27bVvXt3bd68WV26dPHua9eunaKjoxUWFhbwIQEAAPz1k2Ln4osvliSdPHmyWYYBAAAINL+/9XzXrl1av369qqqqGsXPY4891uTBAAAAAsGv2Hn22Wf1hz/8QZ07d5bb7ZZlWd59lmUROwAAIGT4FTsLFizQwoULNWvWrEDPAwAAEFB+/Z2d6upq3XbbbYGeBQAAIOD8ip3bbrtN+fn5gZ4FAAAg4Px6G+tXv/qVHn30UW3atEm9evVS27Ztffbff//9ARkOAACgqSzbtu2f+qT4+Pizn9Cy9NVXXzVpqJZWU1Mjp9Mpj8ejqKiogJ47bvYbAT0fAACtze7HhzfLec/197dfV3bKysr8HgwAAKAl+XXPDgAAQGvh15WdCRMm/OD+5557zq9hAAAAAs2v2KmurvbZPnbsmLZv366DBw+e8QtCAQAAgsWv2FmzZk2jtZMnTyo1NVXdu3dv8lAAAACBErB7ds477zw9+OCDWrZsWaBOCQAA0GQBvUH5yy+/1PHjxwN5SgAAgCbx622s9PR0n23btlVRUaE33nhD48aNC8hgAAAAgeBX7GzdutVn+7zzzlOXLl20ZMmSH/2kFgAAQEvyK3beeeedQM8BAADQLPyKnVO+/fZb7dy5U5Zl6ZJLLlGXLl0CNRcAAEBA+HWD8uHDhzVhwgR17dpVv/nNb3TdddcpJiZGEydO1JEjRwI9IwAAgN/8ip309HQVFhbqX//6lw4ePKiDBw/qtddeU2FhoR566KFAzwgAAOA3v97G+uc//6l//OMfSk5O9q7dfPPNCg8P1+23364VK1YEaj4AAIAm8evKzpEjR+RyuRqtR0dH8zYWAAAIKX7FzsCBAzV37lwdPXrUu1ZXV6d58+Zp4MCBARsOAACgqfx6GysnJ0c33XSTunXrpt69e8uyLJWWlsrhcCg/Pz/QMwIAAPjNr9jp1auXPv/8c73wwgv67LPPZNu27rjjDo0dO1bh4eGBnhEAAMBvfsVOdna2XC6XJk+e7LP+3HPP6dtvv9WsWbMCMhwAAEBT+XXPztNPP60ePXo0Wr/88sv11FNPNXkoAACAQPErdiorK9W1a9dG6126dFFFRUWThwIAAAgUv2InNjZW77//fqP1999/XzExMU0eCgAAIFD8umdn0qRJSktL07FjxzR48GBJ0rp16zRz5kz+gjIAAAgpfsXOzJkzdeDAAaWmpqqhoUGSdP7552vWrFnKyMgI6IAAAABN4VfsWJalRYsW6dFHH9Wnn36q8PBwJSQkyOFwBHo+AACAJvErdk75xS9+oauuuipQswAAAAScXzcoAwAAtBbEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMFqrip3s7GxZlqW0tDTvmm3byszMVExMjMLDw5WcnKwdO3YEb0gAABBSWk3sFBcX65lnntEVV1zhs7548WItXbpUubm5Ki4ultvt1tChQ1VbWxukSQEAQChpFbFz6NAhjR07Vs8++6wuuOAC77pt28rJydGcOXM0atQoJSYmavXq1Tpy5Ijy8vKCODEAAAgVrSJ27rvvPg0fPlxDhgzxWS8rK1NlZaVSUlK8aw6HQ4MGDVJRUdFZz1dfX6+amhqfBwAAMFObYA/wY1566SV9+OGHKi4ubrSvsrJSkuRyuXzWXS6X9uzZc9ZzZmdna968eYEdFAAAhKSQvrJTXl6uBx54QC+88ILOP//8sx5nWZbPtm3bjdZOl5GRIY/H432Ul5cHbGYAABBaQvrKTklJiaqqqtS3b1/v2okTJ/Tuu+8qNzdXO3fulPT9FZ6uXbt6j6mqqmp0ted0DodDDoej+QYHAAAhI6Sv7Nxwww36+OOPVVpa6n3069dPY8eOVWlpqbp37y63262CggLvcxoaGlRYWKikpKQgTg4AAEJFSF/ZiYyMVGJios9a+/bt1alTJ+96WlqasrKylJCQoISEBGVlZSkiIkJjxowJxsgAACDEhHTsnIuZM2eqrq5Oqampqq6u1oABA5Sfn6/IyMhgjwYAAEKAZdu2Hewhgq2mpkZOp1Mej0dRUVEBPXfc7DcCej4AAFqb3Y8Pb5bznuvv75C+ZwcAAKCpiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGC+nYyc7O1lVXXaXIyEhFR0dr5MiR2rlzp88xtm0rMzNTMTExCg8PV3Jysnbs2BGkiQEAQKgJ6dgpLCzUfffdp02bNqmgoEDHjx9XSkqKDh8+7D1m8eLFWrp0qXJzc1VcXCy3262hQ4eqtrY2iJMDAIBQ0SbYA/yQtWvX+myvXLlS0dHRKikp0W9+8xvZtq2cnBzNmTNHo0aNkiStXr1aLpdLeXl5mjp16hnPW19fr/r6eu92TU1N870IAAAQVCF9Zed/eTweSVLHjh0lSWVlZaqsrFRKSor3GIfDoUGDBqmoqOis58nOzpbT6fQ+YmNjm3dwAAAQNK0mdmzbVnp6uq699lolJiZKkiorKyVJLpfL51iXy+XddyYZGRnyeDzeR3l5efMNDgAAgiqk38Y63bRp07Rt2zZt2LCh0T7Lsny2bdtutHY6h8Mhh8MR8BkBAEDoaRVXdqZPn67XX39d77zzjrp16+Zdd7vdktToKk5VVVWjqz0AAODnKaRjx7ZtTZs2Ta+88orefvttxcfH++yPj4+X2+1WQUGBd62hoUGFhYVKSkpq6XEBAEAICum3se677z7l5eXptddeU2RkpPcKjtPpVHh4uCzLUlpamrKyspSQkKCEhARlZWUpIiJCY8aMCfL0AAAgFIR07KxYsUKSlJyc7LO+cuVKjR8/XpI0c+ZM1dXVKTU1VdXV1RowYIDy8/MVGRnZwtMCAIBQFNKxY9v2jx5jWZYyMzOVmZnZ/AMBAIBWJ6Tv2QEAAGgqYgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGMyZ2nnzyScXHx+v8889X37599d577wV7JAAAEAKMiJ2XX35ZaWlpmjNnjrZu3arrrrtON910k/bu3Rvs0QAAQJAZETtLly7VxIkTNWnSJF122WXKyclRbGysVqxYEezRAABAkLUJ9gBN1dDQoJKSEs2ePdtnPSUlRUVFRWd8Tn19verr673bHo9HklRTUxPw+U7WHwn4OQEAaE2a4/fr6ee1bfsHj2v1sfPdd9/pxIkTcrlcPusul0uVlZVnfE52drbmzZvXaD02NrZZZgQA4OfMmdO856+trZXT6Tzr/lYfO6dYluWzbdt2o7VTMjIylJ6e7t0+efKkDhw4oE6dOp31OQBap5qaGsXGxqq8vFxRUVHBHgdAANm2rdraWsXExPzgca0+djp37qywsLBGV3GqqqoaXe05xeFwyOFw+Kx16NChuUYEEAKioqKIHcBAP3RF55RWf4Nyu3bt1LdvXxUUFPisFxQUKCkpKUhTAQCAUNHqr+xIUnp6uu666y7169dPAwcO1DPPPKO9e/fq3nvvDfZoAAAgyIyIndGjR2v//v2aP3++KioqlJiYqDfffFMXX3xxsEcDEGQOh0Nz585t9NY1gJ8Py/6xz2sBAAC0Yq3+nh0AAIAfQuwAAACjETsAAMBoxA4AADAasQMg5L377rsaMWKEYmJiZFmWXn31VZ/9r7zyioYNG6bOnTvLsiyVlpY2Okd9fb2mT5+uzp07q3379rr11lv19ddf+xxTXV2tu+66S06nU06nU3fddZcOHjzYfC8MQIsgdgCEvMOHD6t3797Kzc096/5rrrlGjz/++FnPkZaWpjVr1uill17Shg0bdOjQId1yyy06ceKE95gxY8aotLRUa9eu1dq1a1VaWqq77ror4K8HQMvio+cAWhXLsrRmzRqNHDmy0b7du3crPj5eW7duVZ8+fbzrHo9HXbp00fPPP6/Ro0dLkvbt26fY2Fi9+eabGjZsmD799FP17NlTmzZt0oABAyRJmzZt0sCBA/XZZ5/p0ksvbYmXB6AZcGUHgPFKSkp07NgxpaSkeNdiYmKUmJiooqIiSdLGjRvldDq9oSNJV199tZxOp/cYAK0TsQPAeJWVlWrXrp0uuOACn3WXy+X9EuHKykpFR0c3em50dHSjLxoG0LoQOwB+tmzblmVZ3u3T/322YwC0PsQOAOO53W41NDSourraZ72qqkoul8t7zDfffNPoud9++633GACtE7EDwHh9+/ZV27ZtVVBQ4F2rqKjQ9u3blZSUJEkaOHCgPB6PNm/e7D3mgw8+kMfj8R4DoHUy4lvPAZjt0KFD+uKLL7zbZWVlKi0tVceOHXXRRRfpwIED2rt3r/bt2ydJ2rlzp6Tvr9a43W45nU5NnDhRDz30kDp16qSOHTtqxowZ6tWrl4YMGSJJuuyyy3TjjTdq8uTJevrppyVJU6ZM0S233MInsYBWjo+eAwh569ev1/XXX99ofdy4cVq1apVWrVqle+65p9H+uXPnKjMzU5J09OhRPfzww8rLy1NdXZ1uuOEGPfnkk4qNjfUef+DAAd1///16/fXXJUm33nqrcnNz1aFDh2Z5XQBaBrEDAACMxj07AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwBaRHJystLS0s66Py4uTjk5OS0yy+7du2VZlkpLS1vk5wEILmIHQEgoLi7WlClTvNuWZenVV1895+evWrXqnL/WITY2VhUVFUpMTPyJUwJojfgiUAAhoUuXLi3ycxoaGtSuXTu53e4W+XkAgo8rOwBazPHjxzVt2jR16NBBnTp10iOPPKJTX893+ttYcXFxkqTf/e53sizLu/3RRx/p+uuvV2RkpKKiotS3b19t2bJF69ev1z333COPxyPLsmRZlvcLQOPi4rRgwQKNHz9eTqdTkydPbvQ21vr162VZltatW6d+/fopIiJCSUlJ3m9PP2XBggWKjo5WZGSkJk2apNmzZ6tPnz7e/evXr1f//v3Vvn17dejQQddcc4327NnTXP+dAM4RsQOgxaxevVpt2rTRBx98oD/96U9atmyZ/vznPzc6rri4WJK0cuVKVVRUeLfHjh2rbt26qbi4WCUlJZo9e7batm2rpKQk5eTkKCoqShUVFaqoqNCMGTO853viiSeUmJiokpISPfroo2edb86cOVqyZIm2bNmiNm3aaMKECd59f/vb37Rw4UItWrRIJSUluuiii7RixQrv/uPHj2vkyJEaNGiQtm3bpo0bN2rKlCmyLKvJ/28Amoa3sQC0mNjYWC1btkyWZenSSy/Vxx9/rGXLlmny5Mk+x516S6tDhw4+bzft3btXDz/8sHr06CFJSkhI8O5zOp2yLOuMb08NHjzYJ3527959xvkWLlyoQYMGSZJmz56t4cOH6+jRozr//PO1fPlyTZw4Uffcc48k6bHHHlN+fr4OHTokSaqpqZHH49Ett9yiX/7yl5Kkyy677Cf9/wBoHlzZAdBirr76ap8rHQMHDtTnn3+uEydOnNPz09PTNWnSJA0ZMkSPP/64vvzyy3N6Xr9+/c7puCuuuML7765du0qSqqqqJEk7d+5U//79fY4/fbtjx44aP368hg0bphEjRuiPf/yjKioqzunnAmhexA6AViMzM1M7duzQ8OHD9fbbb6tnz55as2bNjz6vffv253T+tm3bev99KspOnjzZaO2UU/cbnbJy5Upt3LhRSUlJevnll3XJJZdo06ZN5/SzATQfYgdAi/nfX/ybNm1SQkKCwsLCGh3btm3bM17xueSSS/Tggw8qPz9fo0aN0sqVKyVJ7dq1O+crRP649NJLtXnzZp+1LVu2NDruyiuvVEZGhoqKipSYmKi8vLxmmwnAuSF2ALSY8vJypaena+fOnXrxxRe1fPlyPfDAA2c8Ni4uTuvWrVNlZaWqq6tVV1enadOmaf369dqzZ4/ef/99FRcXe++LiYuL06FDh7Ru3Tp99913OnLkSEBnnz59uv7yl79o9erV+vzzz7VgwQJt27bNe7WnrKxMGRkZ2rhxo/bs2aP8/Hzt2rWL+3aAEMANygBazN133626ujr1799fYWFhmj59us8fEjzdkiVLlJ6ermeffVYXXnihdu3apf379+vuu+/WN998o86dO2vUqFGaN2+eJCkpKUn33nuvRo8erf3792vu3Lnej58HwtixY/XVV19pxowZOnr0qG6//XaNHz/ee7UnIiJCn332mVavXq39+/era9eumjZtmqZOnRqwGQD4x7L/901nAMA5GTp0qNxut55//vlgjwLgB3BlBwDOwZEjR/TUU09p2LBhCgsL04svvqi33npLBQUFwR4NwI/gyg4AnIO6ujqNGDFCH374oerr63XppZfqkUce0ahRo4I9GoAfQewAAACj8WksAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNH+H35v7XyPK5xOAAAAAElFTkSuQmCC" }, "metadata": {}, "output_type": "display_data" @@ -160,7 +152,7 @@ ], "source": [ "device = LocalSimulator()\n", - "result = device.run(ipe_program, shots=100).result()\n", + "result = device.run(ipe, shots=100).result()\n", "counts = Counter(result.measurements[\"b0\"])\n", "print(\"measurement counts: \", counts)\n", "\n", diff --git a/examples/autoqasm/4_Native_programming.ipynb b/examples/autoqasm/4_Native_programming.ipynb index d133598e..16790387 100644 --- a/examples/autoqasm/4_Native_programming.ipynb +++ b/examples/autoqasm/4_Native_programming.ipynb @@ -20,7 +20,6 @@ "outputs": [], "source": [ "# general imports\n", - "import numpy as np\n", "import IPython\n", "\n", "# AWS imports: Import Braket SDK modules\n", @@ -67,8 +66,7 @@ " measure([0, 1])\n", "\n", "\n", - "bell_state_program = bell_state()\n", - "print(bell_state_program.to_ir())" + "print(bell_state.to_ir())" ] }, { @@ -112,8 +110,7 @@ " measure([\"$0\", \"$1\"])\n", "\n", "\n", - "bell_state_program = bell_state()\n", - "print(bell_state_program.to_ir())" + "print(bell_state.to_ir())" ] }, { @@ -125,7 +122,7 @@ "\n", "## Device-specific validation\n", "\n", - "Bypassing the mapping and compilation is only the first step of native programming. Because native programming is intended for targeting a program to a specific device, we need to specify the target device in the AutoQASM program. We can accomplish this by adding a `device` argument to the `@aq.main` decorator, passing the ARN of the Amazon Braket device (or, optionally, a `braket.devices.device.Device` object) that we want to target.\n", + "Bypassing the mapping and compilation is only the first step of native programming. Because native programming is intended for targeting a program to a specific device, we need to specify the target device in the AutoQASM program. We can accomplish this by adding a `device` argument to the `@aq.main` decorator, passing the ARN of the Amazon Braket device (or, optionally, a `braket.devices.Device` object) that we want to target.\n", "\n", "Here we modify the program to target the `Devices.IonQ.Aria1` device. When building this program, AutoQASM will validate that (among other things) the contents of any `verbatim` blocks respect the native gate set and connectivity of the target device. " ] @@ -145,16 +142,13 @@ } ], "source": [ - "@aq.main(device=Devices.IonQ.Aria1)\n", - "def bell_state():\n", - " with aq.verbatim():\n", - " h(\"$0\")\n", - " cnot(\"$0\", \"$1\")\n", - " measure([\"$0\", \"$1\"])\n", - "\n", - "\n", "try:\n", - " bell_state_program = bell_state()\n", + " @aq.main(device=Devices.IonQ.Aria1)\n", + " def bell_state():\n", + " with aq.verbatim():\n", + " h(\"$0\")\n", + " cnot(\"$0\", \"$1\")\n", + " measure([\"$0\", \"$1\"])\n", "except Exception as e:\n", " print(\"ERROR:\", e)" ] @@ -181,196 +175,9 @@ "outputs": [ { "data": { - "text/html": [ - "
import numpy as np\n",
-       "\n",
-       "import braket.experimental.autoqasm as aq\n",
-       "from braket.experimental.autoqasm.instructions import gpi, gpi2, ms\n",
-       "\n",
-       "\n",
-       "@aq.gate\n",
-       "def h(q: aq.Qubit):\n",
-       "    gpi2(q, np.pi / 2)\n",
-       "    gpi(q, 0)\n",
-       "\n",
-       "\n",
-       "@aq.gate\n",
-       "def u(q: aq.Qubit, a: float, b: float, c: float):\n",
-       "    gpi2(q, a)\n",
-       "    gpi(q, b)\n",
-       "    gpi2(q, c)\n",
-       "\n",
-       "\n",
-       "@aq.gate\n",
-       "def rx(q: aq.Qubit, theta: float):\n",
-       "    u(q, np.pi / 2, theta / 2 + np.pi / 2, np.pi / 2)\n",
-       "\n",
-       "\n",
-       "@aq.gate\n",
-       "def ry(q: aq.Qubit, theta: float):\n",
-       "    u(q, np.pi, theta / 2 + np.pi, np.pi)\n",
-       "\n",
-       "\n",
-       "@aq.gate\n",
-       "def cnot(q0: aq.Qubit, q1: aq.Qubit):\n",
-       "    ry(q0, np.pi / 2)\n",
-       "    ms(q0, q1, 0, 0, np.pi / 2)\n",
-       "    rx(q0, -np.pi / 2)\n",
-       "    rx(q1, -np.pi / 2)\n",
-       "    ry(q0, -np.pi / 2)\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{k+kn}{import} \\PY{n+nn}{numpy} \\PY{k}{as} \\PY{n+nn}{np}\n", - "\n", - "\\PY{k+kn}{import} \\PY{n+nn}{braket}\\PY{n+nn}{.}\\PY{n+nn}{experimental}\\PY{n+nn}{.}\\PY{n+nn}{autoqasm} \\PY{k}{as} \\PY{n+nn}{aq}\n", - "\\PY{k+kn}{from} \\PY{n+nn}{braket}\\PY{n+nn}{.}\\PY{n+nn}{experimental}\\PY{n+nn}{.}\\PY{n+nn}{autoqasm}\\PY{n+nn}{.}\\PY{n+nn}{instructions} \\PY{k+kn}{import} \\PY{n}{gpi}\\PY{p}{,} \\PY{n}{gpi2}\\PY{p}{,} \\PY{n}{ms}\n", - "\n", - "\n", - "\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n", - "\\PY{k}{def} \\PY{n+nf}{h}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{)}\\PY{p}{:}\n", - " \\PY{n}{gpi2}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", - " \\PY{n}{gpi}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{l+m+mi}{0}\\PY{p}{)}\n", - "\n", - "\n", - "\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n", - "\\PY{k}{def} \\PY{n+nf}{u}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{a}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{,} \\PY{n}{b}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{,} \\PY{n}{c}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{)}\\PY{p}{:}\n", - " \\PY{n}{gpi2}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{a}\\PY{p}{)}\n", - " \\PY{n}{gpi}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{b}\\PY{p}{)}\n", - " \\PY{n}{gpi2}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{c}\\PY{p}{)}\n", - "\n", - "\n", - "\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n", - "\\PY{k}{def} \\PY{n+nf}{rx}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{theta}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{)}\\PY{p}{:}\n", - " \\PY{n}{u}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{,} \\PY{n}{theta} \\PY{o}{/} \\PY{l+m+mi}{2} \\PY{o}{+} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", - "\n", - "\n", - "\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n", - "\\PY{k}{def} \\PY{n+nf}{ry}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{theta}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{)}\\PY{p}{:}\n", - " \\PY{n}{u}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi}\\PY{p}{,} \\PY{n}{theta} \\PY{o}{/} \\PY{l+m+mi}{2} \\PY{o}{+} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi}\\PY{p}{)}\n", - "\n", - "\n", - "\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n", - "\\PY{k}{def} \\PY{n+nf}{cnot}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{q1}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{)}\\PY{p}{:}\n", - " \\PY{n}{ry}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", - " \\PY{n}{ms}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{n}{q1}\\PY{p}{,} \\PY{l+m+mi}{0}\\PY{p}{,} \\PY{l+m+mi}{0}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", - " \\PY{n}{rx}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{o}{\\PYZhy{}}\\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", - " \\PY{n}{rx}\\PY{p}{(}\\PY{n}{q1}\\PY{p}{,} \\PY{o}{\\PYZhy{}}\\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", - " \\PY{n}{ry}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{o}{\\PYZhy{}}\\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", - "\\end{Verbatim}\n" - ], - "text/plain": [ - "import numpy as np\n", - "\n", - "import braket.experimental.autoqasm as aq\n", - "from braket.experimental.autoqasm.instructions import gpi, gpi2, ms\n", - "\n", - "\n", - "@aq.gate\n", - "def h(q: aq.Qubit):\n", - " gpi2(q, np.pi / 2)\n", - " gpi(q, 0)\n", - "\n", - "\n", - "@aq.gate\n", - "def u(q: aq.Qubit, a: float, b: float, c: float):\n", - " gpi2(q, a)\n", - " gpi(q, b)\n", - " gpi2(q, c)\n", - "\n", - "\n", - "@aq.gate\n", - "def rx(q: aq.Qubit, theta: float):\n", - " u(q, np.pi / 2, theta / 2 + np.pi / 2, np.pi / 2)\n", - "\n", - "\n", - "@aq.gate\n", - "def ry(q: aq.Qubit, theta: float):\n", - " u(q, np.pi, theta / 2 + np.pi, np.pi)\n", - "\n", - "\n", - "@aq.gate\n", - "def cnot(q0: aq.Qubit, q1: aq.Qubit):\n", - " ry(q0, np.pi / 2)\n", - " ms(q0, q1, 0, 0, np.pi / 2)\n", - " rx(q0, -np.pi / 2)\n", - " rx(q1, -np.pi / 2)\n", - " ry(q0, -np.pi / 2)" - ] + "text/plain": "import numpy as np\n\nimport braket.experimental.autoqasm as aq\nfrom braket.experimental.autoqasm.instructions import gpi, gpi2, ms\n\n\n@aq.gate\ndef h(q: aq.Qubit):\n gpi2(q, np.pi / 2)\n gpi(q, 0)\n\n\n@aq.gate\ndef u(q: aq.Qubit, a: float, b: float, c: float):\n gpi2(q, a)\n gpi(q, b)\n gpi2(q, c)\n\n\n@aq.gate\ndef rx(q: aq.Qubit, theta: float):\n u(q, np.pi / 2, theta / 2 + np.pi / 2, np.pi / 2)\n\n\n@aq.gate\ndef ry(q: aq.Qubit, theta: float):\n u(q, np.pi, theta / 2 + np.pi, np.pi)\n\n\n@aq.gate\ndef cnot(q0: aq.Qubit, q1: aq.Qubit):\n ry(q0, np.pi / 2)\n ms(q0, q1, 0, 0, np.pi / 2)\n rx(q0, -np.pi / 2)\n rx(q1, -np.pi / 2)\n ry(q0, -np.pi / 2)", + "text/html": "
import numpy as np\n\nimport braket.experimental.autoqasm as aq\nfrom braket.experimental.autoqasm.instructions import gpi, gpi2, ms\n\n\n@aq.gate\ndef h(q: aq.Qubit):\n    gpi2(q, np.pi / 2)\n    gpi(q, 0)\n\n\n@aq.gate\ndef u(q: aq.Qubit, a: float, b: float, c: float):\n    gpi2(q, a)\n    gpi(q, b)\n    gpi2(q, c)\n\n\n@aq.gate\ndef rx(q: aq.Qubit, theta: float):\n    u(q, np.pi / 2, theta / 2 + np.pi / 2, np.pi / 2)\n\n\n@aq.gate\ndef ry(q: aq.Qubit, theta: float):\n    u(q, np.pi, theta / 2 + np.pi, np.pi)\n\n\n@aq.gate\ndef cnot(q0: aq.Qubit, q1: aq.Qubit):\n    ry(q0, np.pi / 2)\n    ms(q0, q1, 0, 0, np.pi / 2)\n    rx(q0, -np.pi / 2)\n    rx(q1, -np.pi / 2)\n    ry(q0, -np.pi / 2)\n
\n", + "text/latex": "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n\\PY{k+kn}{import} \\PY{n+nn}{numpy} \\PY{k}{as} \\PY{n+nn}{np}\n\n\\PY{k+kn}{import} \\PY{n+nn}{braket}\\PY{n+nn}{.}\\PY{n+nn}{experimental}\\PY{n+nn}{.}\\PY{n+nn}{autoqasm} \\PY{k}{as} \\PY{n+nn}{aq}\n\\PY{k+kn}{from} \\PY{n+nn}{braket}\\PY{n+nn}{.}\\PY{n+nn}{experimental}\\PY{n+nn}{.}\\PY{n+nn}{autoqasm}\\PY{n+nn}{.}\\PY{n+nn}{instructions} \\PY{k+kn}{import} \\PY{n}{gpi}\\PY{p}{,} \\PY{n}{gpi2}\\PY{p}{,} \\PY{n}{ms}\n\n\n\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n\\PY{k}{def} \\PY{n+nf}{h}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{)}\\PY{p}{:}\n \\PY{n}{gpi2}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n \\PY{n}{gpi}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{l+m+mi}{0}\\PY{p}{)}\n\n\n\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n\\PY{k}{def} \\PY{n+nf}{u}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{a}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{,} \\PY{n}{b}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{,} \\PY{n}{c}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{)}\\PY{p}{:}\n \\PY{n}{gpi2}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{a}\\PY{p}{)}\n \\PY{n}{gpi}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{b}\\PY{p}{)}\n \\PY{n}{gpi2}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{c}\\PY{p}{)}\n\n\n\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n\\PY{k}{def} \\PY{n+nf}{rx}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{theta}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{)}\\PY{p}{:}\n \\PY{n}{u}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{,} \\PY{n}{theta} \\PY{o}{/} \\PY{l+m+mi}{2} \\PY{o}{+} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n\n\n\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n\\PY{k}{def} \\PY{n+nf}{ry}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{theta}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{)}\\PY{p}{:}\n \\PY{n}{u}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi}\\PY{p}{,} \\PY{n}{theta} \\PY{o}{/} \\PY{l+m+mi}{2} \\PY{o}{+} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi}\\PY{p}{)}\n\n\n\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n\\PY{k}{def} \\PY{n+nf}{cnot}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{q1}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{)}\\PY{p}{:}\n \\PY{n}{ry}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n \\PY{n}{ms}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{n}{q1}\\PY{p}{,} \\PY{l+m+mi}{0}\\PY{p}{,} \\PY{l+m+mi}{0}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n \\PY{n}{rx}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{o}{\\PYZhy{}}\\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n \\PY{n}{rx}\\PY{p}{(}\\PY{n}{q1}\\PY{p}{,} \\PY{o}{\\PYZhy{}}\\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n \\PY{n}{ry}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{o}{\\PYZhy{}}\\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n\\end{Verbatim}\n" }, "execution_count": 5, "metadata": {}, @@ -445,8 +252,7 @@ " measure([\"$0\", \"$1\"])\n", "\n", "\n", - "bell_state_program = bell_state()\n", - "print(bell_state_program.to_ir())" + "print(bell_state.to_ir())" ] }, { diff --git a/examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb b/examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb index d1f0416b..1d3a9129 100644 --- a/examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb +++ b/examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb @@ -37,7 +37,7 @@ "\n", "import braket.experimental.autoqasm as aq\n", "from braket.experimental.autoqasm import pulse\n", - "from braket.experimental.autoqasm.instructions import rx, rz, measure\n", + "from braket.experimental.autoqasm.instructions import rx, rz\n", "\n", "from braket.aws import AwsDevice\n", "from braket.devices import Devices\n", @@ -69,8 +69,11 @@ "metadata": {}, "outputs": [], "source": [ + "qubit = 0\n", + "idle_duration = 10e-6\n", + "\n", "@aq.main\n", - "def idle(qubit: int, idle_duration: float):\n", + "def idle():\n", " control_frame = device.frames[f\"q{qubit}_rf_frame\"]\n", " pulse.delay(control_frame, idle_duration)\n", " pulse.capture_v0(control_frame)" @@ -104,7 +107,7 @@ } ], "source": [ - "print(idle(0, 10e-6).to_ir())" + "print(idle.to_ir())" ] }, { @@ -180,8 +183,12 @@ "metadata": {}, "outputs": [], "source": [ + "qubit = 0\n", + "idle_duration = 10e-6\n", + "n_cycles = 3\n", + "\n", "@aq.main\n", - "def idle_with_dd(qubit: int, idle_duration: float, n_cycles: int = 1) -> None:\n", + "def idle_with_dd():\n", " dd_spacing = idle_duration / (4*n_cycles)\n", "\n", " control_frame = device.frames[f\"q{qubit}_rf_frame\"]\n", @@ -247,7 +254,7 @@ } ], "source": [ - "print(idle_with_dd(0, 10e-6, 3).to_ir())" + "print(idle_with_dd.to_ir())" ] }, { diff --git a/examples/autoqasm/6_Customize_gate_calibrations.ipynb b/examples/autoqasm/6_Customize_gate_calibrations.ipynb index 7670a6c1..271efd65 100644 --- a/examples/autoqasm/6_Customize_gate_calibrations.ipynb +++ b/examples/autoqasm/6_Customize_gate_calibrations.ipynb @@ -79,7 +79,7 @@ "text": [ "OPENQASM 3.0;\n", "cal {\n", - " waveform wf_drag_gaussian_1 = drag_gaussian(24.0ns, 2.547965400864ns, 2.3154335273287345e-10, 0.294418024251374, false);\n", + " waveform wf_drag_gaussian_1 = drag_gaussian(24.0ns, 2.547965400864ns, 2.3223415460834803e-10, 0.28585537029609, false);\n", " barrier $0;\n", " shift_frequency(q0_rf_frame, -321047.14178613486);\n", " play(q0_rf_frame, wf_drag_gaussian_1);\n", @@ -158,12 +158,12 @@ "text": [ "Help on function rx in module braket.experimental.autoqasm.instructions.gates:\n", "\n", - "rx(target: Union[int, oqpy.classical_types._ClassicalVar, oqpy.base.OQPyExpression, str, oqpy.quantum_types.Qubit], angle: float) -> None\n", + "rx(target: Union[int, str, braket.registers.qubit.Qubit, oqpy.classical_types._ClassicalVar, oqpy.base.OQPyExpression, oqpy.quantum_types.Qubit], angle: Union[float, braket.parametric.free_parameter_expression.FreeParameterExpression, oqpy.classical_types._ClassicalVar]) -> None\n", " X-axis rotation gate.\n", " \n", " Args:\n", " target (QubitIdentifierType): Target qubit.\n", - " angle (float): Rotation angle in radians.\n", + " angle (GateParameterType): Rotation angle in radians.\n", "\n" ] } @@ -234,8 +234,7 @@ " rz(\"$1\", 0.123)\n", " measure(\"$0\")\n", "\n", - "main_program = my_program()\n", - "print(main_program.to_ir())" + "print(my_program.to_ir())" ] }, { @@ -277,7 +276,7 @@ } ], "source": [ - "custom_program = main_program.with_calibrations(my_rx_cal)\n", + "custom_program = my_program.with_calibrations(my_rx_cal)\n", "print(custom_program.to_ir())" ] }, @@ -347,7 +346,7 @@ } ], "source": [ - "custom_program = main_program.with_calibrations([my_rx_cal, my_rz_cal])\n", + "custom_program = my_program.with_calibrations([my_rx_cal, my_rz_cal])\n", "print(custom_program.to_ir())" ] }, diff --git a/src/braket/experimental/autoqasm/__init__.py b/src/braket/experimental/autoqasm/__init__.py index 1c9187b0..9efb0852 100644 --- a/src/braket/experimental/autoqasm/__init__.py +++ b/src/braket/experimental/autoqasm/__init__.py @@ -39,7 +39,7 @@ def my_program(): result[0] = measure __qubits__[0]; result[1] = measure __qubits__[1]; """ - +from . import errors, operators # noqa: F401 from .api import gate, gate_calibration, main, subroutine # noqa: F401 from .instructions import QubitIdentifierType as Qubit # noqa: F401 from .program import Program, build_program, verbatim # noqa: F401 diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 04c1fe4d..7a80b360 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -42,6 +42,7 @@ from braket.experimental.autoqasm.instructions.qubits import QubitIdentifierType as Qubit from braket.experimental.autoqasm.instructions.qubits import is_qubit_identifier_type from braket.experimental.autoqasm.program.gate_calibrations import GateCalibration +from braket.parametric import FreeParameter def main( @@ -49,9 +50,8 @@ def main( *, num_qubits: Optional[int] = None, device: Optional[Union[Device, str]] = None, -) -> Callable[..., aq_program.Program]: - """Decorator that converts a function into a callable that returns - a Program object containing the quantum program. +) -> aq_program.Program | functools.partial: + """Decorator that converts a function into a Program object containing the quantum program. The decorator re-converts the target function whenever the decorated function is called, and a new Program object is returned each time. @@ -65,13 +65,22 @@ def main( program. Can be either an Device object or a valid Amazon Braket device ARN. Returns: - Callable[..., Program]: A callable which returns the converted - quantum program when called. + Program | partial: The Program object containing the converted quantum program, or a + partial function of the `main` decorator. """ if isinstance(device, str): device = AwsDevice(device) - return _function_wrapper( + # decorator is called on a Program + if isinstance(func, aq_program.Program): + return func + + # decorator is used with parentheses + # (see _function_wrapper for more details) + if not (func and callable(func)): + return functools.partial(main, num_qubits=num_qubits, device=device) + + program_builder = _function_wrapper( func, converter_callback=_convert_main, converter_args={ @@ -82,6 +91,8 @@ def main( }, ) + return program_builder() + def subroutine(func: Optional[Callable] = None) -> Callable[..., aq_program.Program]: """Decorator that converts a function into a callable that will insert a subroutine into @@ -182,7 +193,7 @@ def _wrapper(*args, **kwargs) -> Callable: optional_features=_autograph_optional_features(), ) # Call the appropriate function converter - return converter_callback(func, options, args, kwargs, **converter_args) + return converter_callback(func, options=options, args=args, kwargs=kwargs, **converter_args) if inspect.isfunction(func) or inspect.ismethod(func): _wrapper = functools.update_wrapper(_wrapper, func) @@ -204,7 +215,7 @@ def _convert_main( args: tuple[Any], kwargs: dict[str, Any], user_config: aq_program.UserConfig, -) -> None: +) -> aq_program.Program: """Convert the initial callable `f` into a full AutoQASM program `program`. Puts the contents of `f` at the global level of the program, rather than putting it into a subroutine as done in `_convert_subroutine`. @@ -218,16 +229,25 @@ def _convert_main( args (tuple[Any]): Arguments passed to the program when called. kwargs (dict[str, Any]): Keyword arguments passed to the program when called. user_config (UserConfig): User-specified settings that influence program building. + + Returns: + aq_program.Program: Generated AutoQASM Program. """ - if aq_program.in_active_program_conversion_context(): - raise errors.AutoQasmTypeError( - f"Cannot call main function '{f.__name__}' from another main function. Did you mean " - "to use '@aq.subroutine'?" - ) + kwargs = {} + parameters = inspect.signature(f).parameters with aq_program.build_program(user_config) as program_conversion_context: + # Capture inputs to decorated function as `FreeParameter` inputs for the Program + for param in parameters.values(): + if param.kind == param.POSITIONAL_OR_KEYWORD: + kwargs[param.name] = FreeParameter(param.name) + param_type = param.annotation if param.annotation is not param.empty else float + program_conversion_context.register_parameter(param.name, param_type) + else: + raise NotImplementedError + # Process the program - aq_transpiler.converted_call(f, args, kwargs, options=options) + aq_transpiler.converted_call(f, (), kwargs, options=options) # Modify program to add global declarations if necessary _add_qubit_declaration(program_conversion_context) diff --git a/src/braket/experimental/autoqasm/instructions/gates.py b/src/braket/experimental/autoqasm/instructions/gates.py index 40ee7518..f8c58cb2 100644 --- a/src/braket/experimental/autoqasm/instructions/gates.py +++ b/src/braket/experimental/autoqasm/instructions/gates.py @@ -16,11 +16,15 @@ from typing import Union +import oqpy + from braket.circuits.free_parameter_expression import FreeParameterExpression from .instructions import _qubit_instruction from .qubits import QubitIdentifierType +GateParameterType = Union[float, FreeParameterExpression, oqpy._ClassicalVar] + def ccnot( control_0: QubitIdentifierType, @@ -55,14 +59,14 @@ def cnot( def cphaseshift( control: QubitIdentifierType, target: QubitIdentifierType, - angle: Union[float, FreeParameterExpression], + angle: GateParameterType, ) -> None: """Controlled phase shift gate. Args: control (QubitIdentifierType): Control qubit. target (QubitIdentifierType): Target qubit. - angle (Union[float, FreeParameterExpression]): Rotation angle in radians. + angle (GateParameterType): Rotation angle in radians. """ _qubit_instruction("cphaseshift", [control, target], angle) @@ -71,14 +75,14 @@ def cphaseshift( def cphaseshift00( control: QubitIdentifierType, target: QubitIdentifierType, - angle: Union[float, FreeParameterExpression], + angle: GateParameterType, ) -> None: """Controlled phase shift gate for phasing the \\|00> state. Args: control (QubitIdentifierType): Control qubit. target (QubitIdentifierType): Target qubit. - angle (Union[float, FreeParameterExpression]): Rotation angle in radians. + angle (GateParameterType): Rotation angle in radians. """ _qubit_instruction("cphaseshift00", [control, target], angle) @@ -87,14 +91,14 @@ def cphaseshift00( def cphaseshift01( control: QubitIdentifierType, target: QubitIdentifierType, - angle: Union[float, FreeParameterExpression], + angle: GateParameterType, ) -> None: """Controlled phase shift gate for phasing the \\|01> state. Args: control (QubitIdentifierType): Control qubit. target (QubitIdentifierType): Target qubit. - angle (Union[float, FreeParameterExpression]): Rotation angle in radians. + angle (GateParameterType): Rotation angle in radians. """ _qubit_instruction("cphaseshift01", [control, target], angle) @@ -103,14 +107,14 @@ def cphaseshift01( def cphaseshift10( control: QubitIdentifierType, target: QubitIdentifierType, - angle: Union[float, FreeParameterExpression], + angle: GateParameterType, ) -> None: """Controlled phase shift gate for phasing the \\|10> state. Args: control (QubitIdentifierType): Control qubit. target (QubitIdentifierType): Target qubit. - angle (Union[float, FreeParameterExpression]): Rotation angle in radians. + angle (GateParameterType): Rotation angle in radians. """ _qubit_instruction("cphaseshift10", [control, target], angle) @@ -190,13 +194,13 @@ def ecr( def gpi( target: QubitIdentifierType, - angle: Union[float, FreeParameterExpression], + angle: GateParameterType, ) -> None: """IonQ GPi gate. Args: target (QubitIdentifierType): Target qubit. - angle (Union[float, FreeParameterExpression]): Rotation angle in radians. + angle (GateParameterType): Rotation angle in radians. """ _qubit_instruction("gpi", [target], angle) @@ -204,13 +208,13 @@ def gpi( def gpi2( target: QubitIdentifierType, - angle: Union[float, FreeParameterExpression], + angle: GateParameterType, ) -> None: """IonQ GPi2 gate. Args: target (QubitIdentifierType): Target qubit. - angle (Union[float, FreeParameterExpression]): Rotation angle in radians. + angle (GateParameterType): Rotation angle in radians. """ _qubit_instruction("gpi2", [target], angle) @@ -257,18 +261,18 @@ def iswap( def ms( target_0: QubitIdentifierType, target_1: QubitIdentifierType, - angle_0: Union[float, FreeParameterExpression], - angle_1: Union[float, FreeParameterExpression], - angle_2: Union[float, FreeParameterExpression], + angle_0: GateParameterType, + angle_1: GateParameterType, + angle_2: GateParameterType, ) -> None: """IonQ Mølmer-Sørenson gate. Args: target_0 (QubitIdentifierType): Target qubit 0. target_1 (QubitIdentifierType): Target qubit 1. - angle_0 (Union[float, FreeParameterExpression]): Rotation angle 0 in radians. - angle_1 (Union[float, FreeParameterExpression]): Rotation angle 1 in radians. - angle_2 (Union[float, FreeParameterExpression]): Rotation angle 2 in radians. + angle_0 (GateParameterType): Rotation angle 0 in radians. + angle_1 (GateParameterType): Rotation angle 1 in radians. + angle_2 (GateParameterType): Rotation angle 2 in radians. """ _qubit_instruction("ms", [target_0, target_1], angle_0, angle_1, angle_2) @@ -276,13 +280,13 @@ def ms( def phaseshift( target: QubitIdentifierType, - angle: Union[float, FreeParameterExpression], + angle: GateParameterType, ) -> None: """Phase shift gate. Args: target (QubitIdentifierType): Target qubit. - angle (Union[float, FreeParameterExpression]): Rotation angle in radians. + angle (GateParameterType): Rotation angle in radians. """ _qubit_instruction("phaseshift", [target], angle) @@ -291,14 +295,14 @@ def phaseshift( def pswap( target_0: QubitIdentifierType, target_1: QubitIdentifierType, - angle: Union[float, FreeParameterExpression], + angle: GateParameterType, ) -> None: """PSwap gate. Args: target_0 (QubitIdentifierType): Target qubit 0. target_1 (QubitIdentifierType): Target qubit 1. - angle (Union[float, FreeParameterExpression]): Rotation angle in radians. + angle (GateParameterType): Rotation angle in radians. """ _qubit_instruction("pswap", [target_0, target_1], angle) @@ -306,13 +310,13 @@ def pswap( def rx( target: QubitIdentifierType, - angle: Union[float, FreeParameterExpression], + angle: GateParameterType, ) -> None: """X-axis rotation gate. Args: target (QubitIdentifierType): Target qubit. - angle (Union[float, FreeParameterExpression]): Rotation angle in radians. + angle (GateParameterType): Rotation angle in radians. """ _qubit_instruction("rx", [target], angle) @@ -320,13 +324,13 @@ def rx( def ry( target: QubitIdentifierType, - angle: Union[float, FreeParameterExpression], + angle: GateParameterType, ) -> None: """Y-axis rotation gate. Args: target (QubitIdentifierType): Target qubit. - angle (Union[float, FreeParameterExpression]): Rotation angle in radians. + angle (GateParameterType): Rotation angle in radians. """ _qubit_instruction("ry", [target], angle) @@ -334,13 +338,13 @@ def ry( def rz( target: QubitIdentifierType, - angle: Union[float, FreeParameterExpression], + angle: GateParameterType, ) -> None: """Z-axis rotation gate. Args: target (QubitIdentifierType): Target qubit. - angle (Union[float, FreeParameterExpression]): Rotation angle in radians. + angle (GateParameterType): Rotation angle in radians. """ _qubit_instruction("rz", [target], angle) @@ -447,14 +451,14 @@ def x( def xx( target_0: QubitIdentifierType, target_1: QubitIdentifierType, - angle: Union[float, FreeParameterExpression], + angle: GateParameterType, ) -> None: """Ising XX coupling gate. Args: target_0 (QubitIdentifierType): Target qubit 0. target_1 (QubitIdentifierType): Target qubit 1. - angle (Union[float, FreeParameterExpression]): Rotation angle in radians. + angle (GateParameterType): Rotation angle in radians. """ _qubit_instruction("xx", [target_0, target_1], angle) @@ -463,14 +467,14 @@ def xx( def xy( target_0: QubitIdentifierType, target_1: QubitIdentifierType, - angle: Union[float, FreeParameterExpression], + angle: GateParameterType, ) -> None: """XY gates Args: target_0 (QubitIdentifierType): Target qubit 0. target_1 (QubitIdentifierType): Target qubit 1. - angle (Union[float, FreeParameterExpression]): Rotation angle in radians. + angle (GateParameterType): Rotation angle in radians. """ _qubit_instruction("xy", [target_0, target_1], angle) @@ -491,14 +495,14 @@ def y( def yy( target_0: QubitIdentifierType, target_1: QubitIdentifierType, - angle: Union[float, FreeParameterExpression], + angle: GateParameterType, ) -> None: """Ising YY coupling gate. Args: target_0 (QubitIdentifierType): Target qubit 0. target_1 (QubitIdentifierType): Target qubit 1. - angle (Union[float, FreeParameterExpression]): Rotation angle in radians. + angle (GateParameterType): Rotation angle in radians. """ _qubit_instruction("yy", [target_0, target_1], angle) @@ -519,14 +523,14 @@ def z( def zz( target_0: QubitIdentifierType, target_1: QubitIdentifierType, - angle: Union[float, FreeParameterExpression], + angle: GateParameterType, ) -> None: """Ising ZZ coupling gate. Args: target_0 (QubitIdentifierType): Target qubit 0. target_1 (QubitIdentifierType): Target qubit 1. - angle (Union[float, FreeParameterExpression]): Rotation angle in radians. + angle (GateParameterType): Rotation angle in radians. """ _qubit_instruction("zz", [target_0, target_1], angle) diff --git a/src/braket/experimental/autoqasm/instructions/qubits.py b/src/braket/experimental/autoqasm/instructions/qubits.py index babb75aa..10994926 100644 --- a/src/braket/experimental/autoqasm/instructions/qubits.py +++ b/src/braket/experimental/autoqasm/instructions/qubits.py @@ -22,6 +22,7 @@ from openpulse.printer import dumps from braket.experimental.autoqasm import constants, errors, program +from braket.parametric import FreeParameterExpression from braket.registers.qubit import Qubit QubitIdentifierType = Union[ @@ -128,6 +129,17 @@ def _(qid: str) -> oqpy.Qubit: raise ValueError(f"invalid qubit label: '{qid}'") +@_qubit.register +def _(qid: FreeParameterExpression) -> oqpy.Qubit: + # Unbound expression dependent on input, like `h(q)` where q is unbound + int_var = oqpy.IntVar( + name=str(qid), + needs_declaration=False, + size=32, + ) + return _qubit(int_var) + + @_qubit.register def _(qid: oqpy.Qubit) -> oqpy.Qubit: return qid diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 1a79a202..55a9fb26 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -341,15 +341,27 @@ def _free_symbol_names(expr: FreeParameterExpression) -> Iterable[str]: """ return sorted([str(s) for s in expr._expression.free_symbols if isinstance(s, Symbol)]) - def register_parameter(self, parameter_name: str) -> None: + def register_parameter( + self, parameter_name: str, parameter_type: Union[float, int, bool] = float + ) -> None: """Register an input parameter if it has not already been registered. - Only floats are currently supported. Args: parameter_name (str): The name of the parameter to register with the program. + parameter_type (Union[float, int, bool]): The type of the parameter to register + with the program. Default: float. """ + # TODO (#814): add type validation against existing inputs if parameter_name not in self._free_parameters: - self._free_parameters[parameter_name] = oqpy.FloatVar("input", name=parameter_name) + if parameter_type == float: + var_class = oqpy.FloatVar + elif parameter_type == int: + var_class = oqpy.IntVar + elif parameter_type == bool: + var_class = oqpy.BoolVar + else: + raise NotImplementedError(parameter_type) + self._free_parameters[parameter_name] = var_class("input", name=parameter_name) def get_expression_var(self, expression: FreeParameterExpression) -> oqpy.FloatVar: """Return an oqpy.FloatVar that represents the provided expression. diff --git a/src/braket/experimental/autoqasm/pulse/pulse.py b/src/braket/experimental/autoqasm/pulse/pulse.py index 0b211c0a..aab1fbd0 100644 --- a/src/braket/experimental/autoqasm/pulse/pulse.py +++ b/src/braket/experimental/autoqasm/pulse/pulse.py @@ -26,6 +26,7 @@ is_qubit_identifier_type, ) from braket.experimental.autoqasm.types import BitVar +from braket.parametric import FreeParameterExpression from braket.parametric.free_parameter import FreeParameter from braket.pulse import PulseSequence from braket.pulse.frame import Frame @@ -129,14 +130,15 @@ def capture_v0(frame: Frame) -> None: def delay( qubits_or_frames: Union[Frame, list[Frame], QubitIdentifierType, list[QubitIdentifierType]], - duration: Union[float, oqpy.FloatVar], + duration: Union[float, oqpy.FloatVar, FreeParameterExpression], ) -> None: """Adds an instruction to advance the frame clock by the specified `duration` value. Args: qubits_or_frames (Union[Frame, list[Frame], QubitIdentifierType, list[QubitIdentifierType]]): Qubits or frame(s) on which the delay needs to be introduced. - duration (Union[float, FloatVar]): Value (in seconds) defining the duration of the delay. + duration (Union[float, FloatVar, FreeParameterExpression]): Value (in seconds) defining the + duration of the delay. """ # noqa: E501 if not isinstance(qubits_or_frames, list): qubits_or_frames = [qubits_or_frames] diff --git a/src/braket/experimental/autoqasm/transpiler/transpiler.py b/src/braket/experimental/autoqasm/transpiler/transpiler.py index 46cea08d..b78e0c03 100644 --- a/src/braket/experimental/autoqasm/transpiler/transpiler.py +++ b/src/braket/experimental/autoqasm/transpiler/transpiler.py @@ -132,6 +132,7 @@ def transform_ast( Union[Lambda, FunctionDef]: The root of the transformed AST. """ unsupported_features_checker.verify(node) + # TODO (#809): add forbidden_aq_program_usage_checker.verify(node) node = self._initial_analysis(node, ctx) # autograph converters diff --git a/test/unit_tests/braket/experimental/autoqasm/conftest.py b/test/unit_tests/braket/experimental/autoqasm/conftest.py index beaa53d4..d4bb60c5 100644 --- a/test/unit_tests/braket/experimental/autoqasm/conftest.py +++ b/test/unit_tests/braket/experimental/autoqasm/conftest.py @@ -38,7 +38,7 @@ def empty_function() -> None: @pytest.fixture -def empty_program() -> Callable: +def empty_program() -> aq.Program: """Empty program fixture. Returns: @@ -71,7 +71,7 @@ def bell_state() -> None: @pytest.fixture -def bell_state_program() -> Callable: +def bell_state_program() -> aq.Program: """Bell state preparation program fixture. Returns: @@ -105,7 +105,7 @@ def physical_bell() -> None: @pytest.fixture -def physical_bell_program() -> Callable: +def physical_bell_program() -> aq.Program: """Physical bell state preparation program fixture. Returns: diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index b757033b..b7cdb746 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -22,13 +22,13 @@ from braket.default_simulator import StateVectorSimulator from braket.devices.local_simulator import LocalSimulator from braket.experimental.autoqasm import errors -from braket.experimental.autoqasm.instructions import cnot, h, measure, x +from braket.experimental.autoqasm.instructions import cnot, h, measure, rx, x from braket.tasks.local_quantum_task import LocalQuantumTask -def _test_on_local_sim(program: aq.Program) -> None: +def _test_on_local_sim(program: aq.Program, inputs=None) -> None: device = LocalSimulator(backend=StateVectorSimulator()) - task = device.run(program, shots=10) + task = device.run(program, shots=10, inputs=inputs or {}) assert isinstance(task, LocalQuantumTask) assert isinstance(task.result().measurements, dict) @@ -36,16 +36,16 @@ def _test_on_local_sim(program: aq.Program) -> None: def test_empty_function(empty_program) -> None: """Test that a function with no instructions generates an empty program.""" expected = """OPENQASM 3.0;""" - assert empty_program().to_ir() == expected + assert empty_program.to_ir() == expected def test_sim_empty(empty_program) -> None: """Test an empty subroutine on the local simulator.""" - _test_on_local_sim(empty_program()) + _test_on_local_sim(empty_program) def test_sim_bell_state(bell_state_program) -> None: - _test_on_local_sim(bell_state_program()) + _test_on_local_sim(bell_state_program) def test_multiple_calls(empty_subroutine, bell_state_subroutine) -> None: @@ -56,19 +56,17 @@ def test_multiple_calls(empty_subroutine, bell_state_subroutine) -> None: def count_function_calls(program: aq.Program, func_name: str) -> int: return program.to_ir().count(f"{func_name}();") - @aq.main def empty_program_wrapper(): empty_subroutine() - @aq.main def bell_state_program_wrapper(): bell_state_subroutine() - first_program = empty_program_wrapper() + first_program = aq.main(empty_program_wrapper) assert 1 == count_function_calls(first_program, "empty_function") assert 0 == count_function_calls(first_program, "bell_state") - second_program = empty_program_wrapper() - third_program = bell_state_program_wrapper() + second_program = aq.main(empty_program_wrapper) + third_program = aq.main(bell_state_program_wrapper) assert 1 == count_function_calls(first_program, "empty_function"), "reverify first program" assert 0 == count_function_calls(first_program, "bell_state"), "reverify first program" assert 1 == count_function_calls(second_program, "empty_function") @@ -102,9 +100,9 @@ def physical_bell() { bell_state(); physical_bell();""" - assert call_subroutines().to_ir() == expected + assert call_subroutines.to_ir() == expected - _test_on_local_sim(call_subroutines()) + _test_on_local_sim(call_subroutines) @aq.subroutine @@ -119,12 +117,18 @@ def recursive_h(q: int): recursive_h(q - 1) -@aq.main(num_qubits=6) -def recursive_h_wrapper(q: int): - recursive_h(q) +@pytest.fixture +def recursive_h_wrapper(): + q = 5 + + @aq.main(num_qubits=q + 1) + def recursive_h_wrapper(): + recursive_h(q) + + return recursive_h_wrapper -def test_recursive_h_wrapper(): +def test_recursive_h_wrapper(recursive_h_wrapper): expected = """OPENQASM 3.0; def do_h(int[32] q) { h __qubits__[q]; @@ -139,16 +143,18 @@ def recursive_h(int[32] q) { } qubit[6] __qubits__; recursive_h(5);""" - assert recursive_h_wrapper(5).to_ir() == expected + assert recursive_h_wrapper.to_ir() == expected -def test_sim_recursive_h_wrapper(): - _test_on_local_sim(recursive_h_wrapper(5)) +def test_sim_recursive_h_wrapper(recursive_h_wrapper): + _test_on_local_sim(recursive_h_wrapper) def test_recursive_h(): + n = 4 + @aq.main(num_qubits=6) - def main(n: int): + def main(): recursive_h(n) expected = """OPENQASM 3.0; @@ -166,7 +172,7 @@ def recursive_h(int[32] q) { qubit[6] __qubits__; recursive_h(4);""" - assert main(4).to_ir() == expected + assert main.to_ir() == expected @aq.subroutine @@ -175,13 +181,17 @@ def bell_state_arbitrary_qubits(q0: int, q1: int) -> None: cnot(q0, q1) -@aq.main(num_qubits=4) -def double_bell_state() -> None: - bell_state_arbitrary_qubits(0, 1) - bell_state_arbitrary_qubits(2, 3) +@pytest.fixture +def double_bell_state(): + @aq.main(num_qubits=4) + def double_bell_state() -> None: + bell_state_arbitrary_qubits(0, 1) + bell_state_arbitrary_qubits(2, 3) + return double_bell_state -def test_double_bell_state() -> None: + +def test_double_bell_state(double_bell_state) -> None: expected = """OPENQASM 3.0; def bell_state_arbitrary_qubits(int[32] q0, int[32] q1) { h __qubits__[q0]; @@ -190,22 +200,26 @@ def bell_state_arbitrary_qubits(int[32] q0, int[32] q1) { qubit[4] __qubits__; bell_state_arbitrary_qubits(0, 1); bell_state_arbitrary_qubits(2, 3);""" - assert double_bell_state().to_ir() == expected + assert double_bell_state.to_ir() == expected -def test_sim_double_bell() -> None: - _test_on_local_sim(double_bell_state()) +def test_sim_double_bell(double_bell_state) -> None: + _test_on_local_sim(double_bell_state) -@aq.main -def bell_measurement_undeclared() -> None: - """A function that generates and measures a two-qubit Bell state.""" - h(0) - cnot(0, 1) - c = measure([0, 1]) # noqa: F841 +@pytest.fixture +def bell_measurement_undeclared(): + @aq.main + def bell_measurement_undeclared() -> None: + """A function that generates and measures a two-qubit Bell state.""" + h(0) + cnot(0, 1) + c = measure([0, 1]) # noqa: F841 + + return bell_measurement_undeclared -def test_bell_measurement_undeclared() -> None: +def test_bell_measurement_undeclared(bell_measurement_undeclared) -> None: expected = """OPENQASM 3.0; bit[2] c; qubit[2] __qubits__; @@ -215,23 +229,27 @@ def test_bell_measurement_undeclared() -> None: __bit_0__[0] = measure __qubits__[0]; __bit_0__[1] = measure __qubits__[1]; c = __bit_0__;""" - assert bell_measurement_undeclared().to_ir() == expected + assert bell_measurement_undeclared.to_ir() == expected -def test_sim_bell_measurement_undeclared() -> None: - _test_on_local_sim(bell_measurement_undeclared()) +def test_sim_bell_measurement_undeclared(bell_measurement_undeclared) -> None: + _test_on_local_sim(bell_measurement_undeclared) -@aq.main -def bell_measurement_declared() -> None: - """A function that generates and measures a two-qubit Bell state.""" - c = aq.BitVar(0, size=2) - h(0) - cnot(0, 1) - c = measure([0, 1]) # noqa: F841 +@pytest.fixture +def bell_measurement_declared(): + @aq.main + def bell_measurement_declared() -> None: + """A function that generates and measures a two-qubit Bell state.""" + c = aq.BitVar(0, size=2) + h(0) + cnot(0, 1) + c = measure([0, 1]) # noqa: F841 + return bell_measurement_declared -def test_bell_measurement_declared() -> None: + +def test_bell_measurement_declared(bell_measurement_declared) -> None: expected = """OPENQASM 3.0; qubit[2] __qubits__; bit[2] c = "00"; @@ -241,22 +259,26 @@ def test_bell_measurement_declared() -> None: __bit_1__[0] = measure __qubits__[0]; __bit_1__[1] = measure __qubits__[1]; c = __bit_1__;""" - assert bell_measurement_declared().to_ir() == expected + assert bell_measurement_declared.to_ir() == expected -def test_sim_bell_measurement_declared() -> None: - _test_on_local_sim(bell_measurement_declared()) +def test_sim_bell_measurement_declared(bell_measurement_declared) -> None: + _test_on_local_sim(bell_measurement_declared) -@aq.main -def bell_partial_measurement() -> None: - """A function that generates and measures a two-qubit Bell state.""" - h(0) - cnot(0, 1) - c = measure(1) # noqa: F841 +@pytest.fixture +def bell_partial_measurement(): + @aq.main + def bell_partial_measurement() -> None: + """A function that generates and measures a two-qubit Bell state.""" + h(0) + cnot(0, 1) + c = measure(1) # noqa: F841 + + return bell_partial_measurement -def test_bell_partial_measurement() -> None: +def test_bell_partial_measurement(bell_partial_measurement) -> None: expected = """OPENQASM 3.0; bit c; qubit[2] __qubits__; @@ -265,56 +287,58 @@ def test_bell_partial_measurement() -> None: bit __bit_0__; __bit_0__ = measure __qubits__[1]; c = __bit_0__;""" - assert bell_partial_measurement().to_ir() == expected - - -@aq.main -def bell_measurement_invalid_declared_type() -> None: - """A function that generates and measures a two-qubit Bell state. But stores - reuslt in an variable with invalid type. - """ - c = aq.IntVar(0) - h(0) - cnot(0, 1) - c = measure(1) # noqa: F841 + assert bell_partial_measurement.to_ir() == expected def test_bell_measurement_invalid_declared_type() -> None: - """Test measurement with reuslt stored in an variable with invalid type.""" + """Test measurement with result stored in a variable with invalid type.""" expected_error_message = "Variables in assignment statements must have the same type" with pytest.raises(errors.InvalidAssignmentStatement) as exc_info: - bell_measurement_invalid_declared_type() - assert expected_error_message in str(exc_info.value) + @aq.main + def bell_measurement_invalid_declared_type() -> None: + """A function that generates and measures a two-qubit Bell state. But stores + result in a variable with invalid type. + """ + c = aq.IntVar(0) + h(0) + cnot(0, 1) + c = measure(1) # noqa: F841 -@aq.main -def bell_measurement_invalid_declared_size() -> None: - """A function that generates and measures a two-qubit Bell state. But stores - reuslt in an variable with invalid size. - """ - c = aq.BitVar([0, 0], size=2) - h(0) - cnot(0, 1) - c = measure(1) # noqa: F841 + assert expected_error_message in str(exc_info.value) def test_bell_measurement_invalid_declared_size() -> None: - """Test measurement with reuslt stored in an variable with invalid size.""" + """Test measurement with result stored in a variable with invalid size.""" expected_error_message = "Variables in assignment statements must have the same size" with pytest.raises(errors.InvalidAssignmentStatement) as exc_info: - bell_measurement_invalid_declared_size() + + @aq.main + def bell_measurement_invalid_declared_size() -> None: + """A function that generates and measures a two-qubit Bell state. But stores + result in a variable with invalid size. + """ + c = aq.BitVar([0, 0], size=2) + h(0) + cnot(0, 1) + c = measure(1) # noqa: F841 + assert expected_error_message in str(exc_info.value) -@aq.main -def measure_physical_qubits() -> None: - """A program that measures physical qubits.""" - a = measure("$0") # noqa: F841 - b = measure("$1") # noqa: F841 - c = measure(["$0", "$1"]) # noqa: F841 +@pytest.fixture +def measure_physical_qubits(): + @aq.main + def measure_physical_qubits() -> None: + """A program that measures physical qubits.""" + a = measure("$0") # noqa: F841 + b = measure("$1") # noqa: F841 + c = measure(["$0", "$1"]) # noqa: F841 + + return measure_physical_qubits -def test_measure_physical_qubits() -> None: +def test_measure_physical_qubits(measure_physical_qubits) -> None: expected = """OPENQASM 3.0; bit a; bit b; @@ -329,42 +353,50 @@ def test_measure_physical_qubits() -> None: __bit_2__[0] = measure $0; __bit_2__[1] = measure $1; c = __bit_2__;""" - assert measure_physical_qubits().to_ir() == expected + assert measure_physical_qubits.to_ir() == expected -@aq.main(num_qubits=5) -def ghz_qasm_for_loop() -> None: - """A function that generates a GHZ state using a QASM for loop.""" - n_qubits = 5 - h(0) - for i in aq.range(n_qubits - 1): - cnot(i, i + 1) +@pytest.fixture +def ghz_qasm_for_loop(): + @aq.main(num_qubits=5) + def ghz_qasm_for_loop() -> None: + """A function that generates a GHZ state using a QASM for loop.""" + n_qubits = 5 + h(0) + for i in aq.range(n_qubits - 1): + cnot(i, i + 1) + + return ghz_qasm_for_loop -def test_ghz_qasm_for_loop() -> None: +def test_ghz_qasm_for_loop(ghz_qasm_for_loop) -> None: expected = """OPENQASM 3.0; qubit[5] __qubits__; h __qubits__[0]; for int i in [0:4 - 1] { cnot __qubits__[i], __qubits__[i + 1]; }""" - assert ghz_qasm_for_loop().to_ir() == expected + assert ghz_qasm_for_loop.to_ir() == expected -def test_sim_ghz_qasm_for_loop() -> None: - _test_on_local_sim(ghz_qasm_for_loop()) +def test_sim_ghz_qasm_for_loop(ghz_qasm_for_loop) -> None: + _test_on_local_sim(ghz_qasm_for_loop) -@aq.main -def ghz_py_for_loop() -> None: - """A function that generates a GHZ state using a Python for loop.""" - n_qubits = 5 - h(0) - for i in range(n_qubits - 1): - cnot(i, i + 1) +@pytest.fixture +def ghz_py_for_loop(): + @aq.main + def ghz_py_for_loop() -> None: + """A function that generates a GHZ state using a Python for loop.""" + n_qubits = 5 + h(0) + for i in range(n_qubits - 1): + cnot(i, i + 1) + return ghz_py_for_loop -def test_ghz_py_for_loop() -> None: + +def test_ghz_py_for_loop(ghz_py_for_loop) -> None: expected = """OPENQASM 3.0; qubit[5] __qubits__; h __qubits__[0]; @@ -372,11 +404,11 @@ def test_ghz_py_for_loop() -> None: cnot __qubits__[1], __qubits__[2]; cnot __qubits__[2], __qubits__[3]; cnot __qubits__[3], __qubits__[4];""" - assert ghz_py_for_loop().to_ir() == expected + assert ghz_py_for_loop.to_ir() == expected -def test_sim_ghz_py_for_loop() -> None: - _test_on_local_sim(ghz_py_for_loop()) +def test_sim_ghz_py_for_loop(ghz_py_for_loop) -> None: + _test_on_local_sim(ghz_py_for_loop) @aq.subroutine @@ -395,13 +427,20 @@ def qasm_simple_condition(do_cnot: bool) -> bool: return do_cnot -@aq.main -def qasm_simple_condition_wrapper(do_cnot: bool): - qasm_simple_condition(do_cnot) +@pytest.fixture +def build_qasm_simple_condition_wrapper(): + def build_qasm_simple_condition_wrapper(do_cnot: bool): + @aq.main + def qasm_simple_condition_wrapper(): + qasm_simple_condition(do_cnot) + return qasm_simple_condition_wrapper -@pytest.mark.parametrize("do_cnot", [True, False]) -def test_qasm_simple_condition(do_cnot: bool) -> None: + return build_qasm_simple_condition_wrapper + + +@pytest.mark.parametrize("do_cnot", (True, False)) +def test_qasm_simple_condition(do_cnot: bool, build_qasm_simple_condition_wrapper) -> None: expected = """OPENQASM 3.0; def qasm_simple_condition(bool do_cnot) -> bool { h __qubits__[0]; @@ -414,29 +453,33 @@ def qasm_simple_condition(bool do_cnot) -> bool { bool __bool_0__; """ expected += f"__bool_0__ = qasm_simple_condition({'true' if do_cnot else 'false'});" - assert qasm_simple_condition_wrapper(do_cnot).to_ir() == expected + assert build_qasm_simple_condition_wrapper(do_cnot).to_ir() == expected @pytest.mark.parametrize("do_cnot", [True, False]) -def test_sim_qasm_simple_condition(do_cnot: bool) -> None: - _test_on_local_sim(qasm_simple_condition_wrapper(do_cnot)) +def test_sim_qasm_simple_condition(do_cnot: bool, build_qasm_simple_condition_wrapper) -> None: + _test_on_local_sim(build_qasm_simple_condition_wrapper(do_cnot)) -@aq.main -def qasm_inline_var_condition() -> aq.BitVar: - """A function that contains a QASM conditional statement with an inline var condition. +@pytest.fixture +def qasm_inline_var_condition(): + @aq.main + def qasm_inline_var_condition() -> aq.BitVar: + """A function that contains a QASM conditional statement with an inline var condition. - Returns: - aq.BitVar: Measurement result. - """ - h(0) - if aq.BitVar(1): - cnot(0, 1) - x(0) if aq.IntVar(1) else x(1) - return measure(1) + Returns: + aq.BitVar: Measurement result. + """ + h(0) + if aq.BitVar(1): + cnot(0, 1) + x(0) if aq.IntVar(1) else x(1) + return measure(1) + return qasm_inline_var_condition -def test_qasm_inline_var_condition() -> None: + +def test_qasm_inline_var_condition(qasm_inline_var_condition) -> None: """Tests the QASM contents of qasm_inline_var_condition.""" expected = """OPENQASM 3.0; bit __bit_0__ = 1; @@ -453,28 +496,32 @@ def test_qasm_inline_var_condition() -> None: } bit __bit_2__; __bit_2__ = measure __qubits__[1];""" - assert qasm_inline_var_condition().to_ir() == expected + assert qasm_inline_var_condition.to_ir() == expected -def test_sim_qasm_inline_var_condition() -> None: +def test_sim_qasm_inline_var_condition(qasm_inline_var_condition) -> None: """Tests the function qasm_inline_var_condition on the local simulator.""" - _test_on_local_sim(qasm_inline_var_condition()) + _test_on_local_sim(qasm_inline_var_condition) -@aq.main -def ground_state_measurements() -> aq.BitVar: - """Measure a few ground state qubits.""" - return measure([5, 2, 1]) +@pytest.fixture +def ground_state_measurements(): + @aq.main + def ground_state_measurements() -> aq.BitVar: + """Measure a few ground state qubits.""" + return measure([5, 2, 1]) + return ground_state_measurements -def test_measurement_qubit_discovery() -> None: + +def test_measurement_qubit_discovery(ground_state_measurements) -> None: """Test that qubits measured by integer are automatically discovered for the purpose of qubit declaration. """ - assert "qubit[6] __qubits__;" in ground_state_measurements().to_ir() + assert "qubit[6] __qubits__;" in ground_state_measurements.to_ir() -def test_simple_measurement() -> None: +def test_simple_measurement(ground_state_measurements) -> None: """Test that a program with only measurements is generated correctly.""" expected = """OPENQASM 3.0; qubit[6] __qubits__; @@ -482,11 +529,11 @@ def test_simple_measurement() -> None: __bit_0__[0] = measure __qubits__[5]; __bit_0__[1] = measure __qubits__[2]; __bit_0__[2] = measure __qubits__[1];""" - assert ground_state_measurements().to_ir() == expected + assert ground_state_measurements.to_ir() == expected -def test_sim_simple_measurement() -> None: - _test_on_local_sim(ground_state_measurements()) +def test_sim_simple_measurement(ground_state_measurements) -> None: + _test_on_local_sim(ground_state_measurements) def test_simple_measurement_return() -> None: @@ -514,23 +561,27 @@ def ground_state_measurements_subroutine() -> bit[3] { qubit[6] __qubits__; bit[3] __bit_1__ = "000"; __bit_1__ = ground_state_measurements_subroutine();""" - assert ground_state_measurements_wrapper().to_ir() == expected + assert ground_state_measurements_wrapper.to_ir() == expected -@aq.main -def qasm_measurement_condition() -> aq.BitVar: - """A function that contains a mid-circuit measurement conditional. +@pytest.fixture +def qasm_measurement_condition(): + @aq.main + def qasm_measurement_condition() -> aq.BitVar: + """A function that contains a mid-circuit measurement conditional. - Returns: - BitVar: The result of measuring qubit 1. - """ - h(0) - if measure(0): - cnot(0, 1) - return measure(1) + Returns: + BitVar: The result of measuring qubit 1. + """ + h(0) + if measure(0): + cnot(0, 1) + return measure(1) + return qasm_measurement_condition -def test_qasm_measurement_condition() -> None: + +def test_qasm_measurement_condition(qasm_measurement_condition) -> None: expected = """OPENQASM 3.0; qubit[2] __qubits__; h __qubits__[0]; @@ -541,22 +592,22 @@ def test_qasm_measurement_condition() -> None: } bit __bit_1__; __bit_1__ = measure __qubits__[1];""" - assert qasm_measurement_condition().to_ir() == expected + assert qasm_measurement_condition.to_ir() == expected -def test_sim_measurement_condition() -> None: - _test_on_local_sim(qasm_measurement_condition()) +def test_sim_measurement_condition(qasm_measurement_condition) -> None: + _test_on_local_sim(qasm_measurement_condition) def test_virtual_int_qubit_decl(bell_state_program) -> None: """Tests for ex. h(0) -> qubit[1] __qubits__""" - qasm = bell_state_program().to_ir() + qasm = bell_state_program.to_ir() assert "\nqubit[2] __qubits__;" in qasm -def test_py_int_qubit_decl() -> None: +def test_py_int_qubit_decl(ghz_py_for_loop) -> None: """Tests for ex. i = 1; h(i) -> qubit[1] __qubits__""" - qasm = ghz_py_for_loop().to_ir() + qasm = ghz_py_for_loop.to_ir() assert "\nqubit[5] __qubits__;" in qasm @@ -567,68 +618,58 @@ def test_physical_qubit_decl(physical_bell_subroutine) -> None: def main(): physical_bell_subroutine() - assert "__qubits__" not in main().to_ir() + assert "__qubits__" not in main.to_ir() def test_invalid_physical_qubit_fails() -> None: """Tests invalid physical qubit formatting.""" - - @aq.main - def broken() -> None: - "Uses invalid string for qubit index" - cnot("$0l", "$O1") - with pytest.raises(ValueError): - broken() + + @aq.main + def broken() -> None: + """Uses invalid string for qubit index""" + cnot("$0l", "$O1") def test_invalid_qubit_label_fails() -> None: """Tests random string fails for qubit label.""" - - @aq.main - def broken() -> None: - "Uses invalid string for qubit index" - h("nope") - with pytest.raises(ValueError): - broken() + + @aq.main + def broken() -> None: + """Uses invalid string for qubit index""" + h("nope") def test_float_qubit_index_fails() -> None: """Tests floats fails for qubit label.""" - - @aq.main - def broken() -> None: - "Uses float for qubit index" - i = 1 - h(i / 2) - with pytest.raises(TypeError): - broken() + + @aq.main + def broken() -> None: + """Uses float for qubit index""" + i = 1 + h(i / 2) def test_bool_qubit_index_fails() -> None: """Tests that an error is raised for boolean qubit type.""" - - @aq.main - def broken() -> None: - "Uses invalid type for qubit index" - h(True) - with pytest.raises(ValueError): - broken() + + @aq.main + def broken() -> None: + """Uses invalid type for qubit index""" + h(True) def test_invalid_qubit_type_fails() -> None: """Tests that an error is raised for other unusual qubit types.""" - - @aq.main - def broken() -> None: - "Uses invalid type for qubit index" - h(h) - with pytest.raises(ValueError): - broken() + + @aq.main + def broken() -> None: + """Uses invalid type for qubit index""" + h(h) def test_bit_array_name() -> None: @@ -648,21 +689,25 @@ def my_program_wrapper() -> None: bit __bit_0__; __bit_0__ = measure __qubits__[0]; return __bit_0__;""" - assert expected in my_program_wrapper().to_ir() + assert expected in my_program_wrapper.to_ir() + +@pytest.fixture +def reset(): + @aq.main + def reset() -> None: + """Reset qubit 0.""" + if measure(0): + x(0) + if measure(0): + x(0) + if measure(0): + x(0) -@aq.main -def reset() -> None: - "Reset qubit 0." - if measure(0): - x(0) - if measure(0): - x(0) - if measure(0): - x(0) + return reset -def test_bit_array_name_multi() -> None: +def test_bit_array_name_multi(reset) -> None: """Tests that auto declared bits are given a reasonable name.""" expected = """OPENQASM 3.0; qubit[1] __qubits__; @@ -681,37 +726,33 @@ def test_bit_array_name_multi() -> None: if (__bit_2__) { x __qubits__[0]; }""" - assert reset().to_ir() == expected + assert reset.to_ir() == expected def test_program_simple_expr() -> None: """Test that a program with simple expressions for the qubit index raises an error if the user doesn't specify the number of qubits. """ - - @aq.main - def simple_range() -> None: - "Uses aq.range iterator for qubit index." - for i in aq.range(5): - h(i) - with pytest.raises(errors.UnknownQubitCountError): - simple_range() + + @aq.main + def simple_range() -> None: + "Uses aq.range iterator for qubit index." + for i in aq.range(5): + h(i) def test_program_with_expr() -> None: """Test that a program with expressions for the qubit index raises an error if the user doesn't specify the number of qubits. """ - - @aq.main - def qubit_expr() -> None: - "Uses aq.range iterator for qubit index." - for i in aq.range(5): - h(i + 3) - with pytest.raises(errors.UnknownQubitCountError): - qubit_expr() + + @aq.main + def qubit_expr() -> None: + "Uses aq.range iterator for qubit index." + for i in aq.range(5): + h(i + 3) def test_multi_for_loop() -> None: @@ -735,7 +776,7 @@ def prog(): for i in aq.range(5): h(i) - assert prog().to_ir() == expected + assert prog.to_ir() == expected @aq.subroutine @@ -744,13 +785,17 @@ def bell(q0: int, q1: int) -> None: cnot(q0, q1) -@aq.main(num_qubits=5) -def bell_in_for_loop() -> None: - for i in aq.range(3): - bell(0, 1) +@pytest.fixture +def bell_in_for_loop(): + @aq.main(num_qubits=5) + def bell_in_for_loop() -> None: + for i in aq.range(3): + bell(0, 1) + + return bell_in_for_loop -def test_subroutines_in_for_loop() -> None: +def test_subroutines_in_for_loop(bell_in_for_loop) -> None: """Test calling a parameterized subroutine from inside a for loop.""" expected = """OPENQASM 3.0; def bell(int[32] q0, int[32] q1) { @@ -762,27 +807,31 @@ def bell(int[32] q0, int[32] q1) { bell(0, 1); }""" - assert bell_in_for_loop().to_ir() == expected + assert bell_in_for_loop.to_ir() == expected -@aq.main -def classical_variables_types() -> None: - a = aq.BitVar(0) - a = aq.BitVar(1) # noqa: F841 +@pytest.fixture +def classical_variables_types(): + @aq.main + def classical_variables_types() -> None: + a = aq.BitVar(0) + a = aq.BitVar(1) # noqa: F841 + + i = aq.IntVar(1) + a_array = aq.BitVar(size=2) + a_array[0] = aq.BitVar(0) + a_array[i] = aq.BitVar(1) - i = aq.IntVar(1) - a_array = aq.BitVar(size=2) - a_array[0] = aq.BitVar(0) - a_array[i] = aq.BitVar(1) + b = aq.IntVar(10) + b = aq.IntVar(15) # noqa: F841 - b = aq.IntVar(10) - b = aq.IntVar(15) # noqa: F841 + c = aq.FloatVar(1.2) + c = aq.FloatVar(3.4) # noqa: F841 - c = aq.FloatVar(1.2) - c = aq.FloatVar(3.4) # noqa: F841 + return classical_variables_types -def test_classical_variables_types(): +def test_classical_variables_types(classical_variables_types): expected = """OPENQASM 3.0; bit a = 0; a = 1; @@ -794,11 +843,11 @@ def test_classical_variables_types(): b = 15; float[64] c = 1.2; c = 3.4;""" - assert classical_variables_types().to_ir() == expected + assert classical_variables_types.to_ir() == expected -def test_sim_classical_variables_types(): - _test_on_local_sim(classical_variables_types()) +def test_sim_classical_variables_types(classical_variables_types): + _test_on_local_sim(classical_variables_types) def test_classical_variables_assignment(): @@ -815,7 +864,7 @@ def prog() -> None: a = 2; b = a; a = b;""" - assert prog().to_ir() == expected + assert prog.to_ir() == expected def test_assignment_measurement_results(): @@ -832,12 +881,14 @@ def prog() -> None: __bit_0__ = measure __qubits__[0]; a = __bit_0__; b = a;""" - assert prog().to_ir() == expected + assert prog.to_ir() == expected def test_nested_function(): + n = 5 + @aq.main - def make_ghz(n: int) -> None: + def make_ghz() -> None: def ghz(n: int): if n == 1: h(0) @@ -855,7 +906,7 @@ def ghz(n: int): cnot __qubits__[0], __qubits__[3]; cnot __qubits__[0], __qubits__[4];""" - assert make_ghz(5).to_ir() == expected + assert make_ghz.to_ir() == expected def test_double_decorated_function(): @@ -865,16 +916,15 @@ def empty_program() -> None: pass expected = """OPENQASM 3.0;""" - assert empty_program().to_ir() == expected + assert empty_program.to_ir() == expected def test_main_return(): - @aq.main - def main() -> int: - return 1 - with pytest.warns(UserWarning, match="Return value from top level function is ignored"): - main() + + @aq.main + def main() -> int: + return 1 def test_main_no_return(): @@ -887,8 +937,6 @@ def main(): x = 3 tester(x) - main() - def test_subroutine_args(): """Test that subroutines will fail if supplied args.""" @@ -924,20 +972,20 @@ def bell() -> None: bell() +@pytest.mark.xfail(reason="(#809) will fix by adding a checker to the transpiler") def test_main_from_main(): """Can't call main from main!""" - @aq.main + @aq.main(num_qubits=2) def bell(q0: int, q1: int) -> None: h(q0) cnot(q0, q1) - @aq.main - def main(): - bell(0, 1) - with pytest.raises(errors.AutoQasmTypeError): - main() + + @aq.main + def main(): + bell(0, 1) def test_empty_decorator_parentheses(): @@ -954,4 +1002,60 @@ def nothing() { } nothing();""" - assert main().to_ir() == expected + assert main.to_ir() == expected + + +def test_input_types(): + @aq.main + def multiple_input_types(x: int, y: bool, z: float, u): + if x and y: + rx(target=0, angle=u + z) + + @aq.main() + def multiple_input_types_parens(x: int, y: bool, z: float, u): + if x and y: + rx(target=0, angle=u + z) + + @aq.main(num_qubits=1) + def multiple_input_types_params(x: int, y: bool, z: float, u): + if x and y: + rx(target=0, angle=u + z) + + expected_ir = """OPENQASM 3.0; +input int[32] x; +input bool y; +input float[64] z; +input float[64] u; +qubit[1] __qubits__; +bool __bool_0__; +__bool_0__ = x && y; +if (__bool_0__) { + rx(u + z) __qubits__[0]; +}""" + assert ( + multiple_input_types.to_ir() + == multiple_input_types_parens.to_ir() + == multiple_input_types_params.to_ir() + == expected_ir + ) + + +def test_input_qubit_indices(): + @aq.main(num_qubits=8) + def circ(q: int, r: int): + h(2 * q + r) + + expected_ir = """OPENQASM 3.0; +input int[32] q; +input int[32] r; +qubit[8] __qubits__; +h __qubits__[2*q + r];""" + assert circ.to_ir() == expected_ir + + +def test_input_qubit_indices_needs_num_qubits(): + with pytest.raises(errors.UnknownQubitCountError): + + @aq.main + def circ(q: int, r: int): + h(2 * q + r) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_converters.py b/test/unit_tests/braket/experimental/autoqasm/test_converters.py index c22af68b..51f1fea6 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_converters.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_converters.py @@ -60,22 +60,20 @@ def fn() -> None: def test_break_for_loop(): - @aq.main - def main(): - for i in aq.range(3): - aq.gates.h(i) - break - with pytest.raises(aq.errors.UnsupportedFeatureError): - main() + @aq.main + def main(): + for i in aq.range(3): + aq.gates.h(i) + break -def test_break_while_loop(): - @aq.main - def uses_while_w_break(): - while aq.gates.measure(0): - aq.gates.x(0) - break +def test_break_while_loop(): with pytest.raises(aq.errors.UnsupportedFeatureError): - uses_while_w_break() + + @aq.main + def uses_while_w_break(): + while aq.gates.measure(0): + aq.gates.x(0) + break diff --git a/test/unit_tests/braket/experimental/autoqasm/test_devices.py b/test/unit_tests/braket/experimental/autoqasm/test_devices.py index b63554ce..9c2dbfd7 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_devices.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_devices.py @@ -130,30 +130,27 @@ def test_device_parameter( def my_program(): h(0) - program = my_program() - assert program.to_ir() + assert my_program.to_ir() def test_insufficient_qubits(aws_device: Mock) -> None: aws_device.properties.paradigm.qubitCount = 9 - @aq.main(device=aws_device, num_qubits=10) - def my_program(): - pass - with pytest.raises(errors.InsufficientQubitCountError): - my_program() + + @aq.main(device=aws_device, num_qubits=10) + def my_program(): + pass def test_unsupported_gate(aws_device: Mock) -> None: aws_device.properties.action[DeviceActionType.OPENQASM].supportedOperations = ["h"] - @aq.main(device=aws_device) - def my_program(): - cphaseshift00(0, 1, 0.123) - with pytest.raises(errors.UnsupportedGate): - my_program() + + @aq.main(device=aws_device) + def my_program(): + cphaseshift00(0, 1, 0.123) def test_unsupported_native_gate(aws_device: Mock) -> None: @@ -161,14 +158,13 @@ def test_unsupported_native_gate(aws_device: Mock) -> None: aws_device.properties.action[DeviceActionType.OPENQASM].supportedPragmas = ["verbatim"] aws_device.properties.paradigm.nativeGateSet = ["x"] - @aq.main(device=aws_device) - def my_program(): - with aq.verbatim(): - x("$0") - h("$0") - with pytest.raises(errors.UnsupportedNativeGate): - my_program() + + @aq.main(device=aws_device) + def my_program(): + with aq.verbatim(): + x("$0") + h("$0") def test_supported_native_gate_inside_gate_definition(aws_device: Mock) -> None: @@ -186,7 +182,7 @@ def my_program(): x("$0") my_gate("$0") - assert my_program().to_ir() + assert my_program.to_ir() def test_unsupported_native_gate_inside_gate_definition(aws_device: Mock) -> None: @@ -198,25 +194,23 @@ def test_unsupported_native_gate_inside_gate_definition(aws_device: Mock) -> Non def my_gate(q: aq.Qubit): h(q) - @aq.main(device=aws_device) - def my_program(): - with aq.verbatim(): - my_gate("$0") - with pytest.raises(errors.UnsupportedNativeGate): - my_program() + + @aq.main(device=aws_device) + def my_program(): + with aq.verbatim(): + my_gate("$0") def test_unsupported_verbatim_block(aws_device: Mock) -> None: aws_device.properties.action[DeviceActionType.OPENQASM].supportedPragmas = [] - @aq.main(device=aws_device) - def my_program(): - with aq.verbatim(): - h("$0") - with pytest.raises(errors.VerbatimBlockNotAllowed): - my_program() + + @aq.main(device=aws_device) + def my_program(): + with aq.verbatim(): + h("$0") def test_validate_connectivity(aws_device: Mock) -> None: @@ -226,14 +220,13 @@ def test_validate_connectivity(aws_device: Mock) -> None: aws_device.properties.paradigm.connectivity.fullyConnected = False aws_device.properties.paradigm.connectivity.connectivityGraph = {"0": ["2"], "1": ["0"]} - @aq.main(device=aws_device) - def my_program(): - with aq.verbatim(): - h("$0") - cnot("$0", "$1") - with pytest.raises(errors.InvalidTargetQubit): - my_program() + + @aq.main(device=aws_device) + def my_program_invalid(): + with aq.verbatim(): + h("$0") + cnot("$0", "$1") @aq.main(device=aws_device) def my_program(): @@ -242,7 +235,7 @@ def my_program(): cnot("$0", "$2") cnot("$1", "$0") - assert my_program().to_ir() + assert my_program.to_ir() aws_device.properties.paradigm.connectivity.fullyConnected = True aws_device.properties.paradigm.connectivity.connectivityGraph = {} @@ -254,7 +247,7 @@ def my_program(): cnot("$0", "$7") cnot("$5", "$2") - assert my_program().to_ir() + assert my_program.to_ir() @pytest.mark.parametrize( @@ -282,9 +275,8 @@ def my_program(): h(0) rx(0, FreeParameter("angle")) - program = my_program() aws_device = AwsDevice(Devices.Amazon.SV1.value) - _ = aws_device.run(program, shots=10, inputs=inputs, device_parameters=device_parameters) + _ = aws_device.run(my_program, shots=10, inputs=inputs, device_parameters=device_parameters) run_call_args = aws_session.create_quantum_task.mock_calls[0].kwargs run_call_args_action = json.loads(run_call_args["action"]) @@ -297,5 +289,5 @@ def my_program(): } aws_session.create_quantum_task.assert_called_once() assert expected_run_call_args.items() <= run_call_args.items() - assert run_call_args_action["source"] == program.to_ir() + assert run_call_args_action["source"] == my_program.to_ir() assert run_call_args_action["inputs"] == inputs diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py index ed7a2516..152c186b 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py @@ -51,7 +51,7 @@ def my_program(): rx(1.0) $1; """ ).strip() - qasm = my_program().with_calibrations([cal_1, cal_2]).to_ir() + qasm = my_program.with_calibrations([cal_1, cal_2]).to_ir() assert qasm == expected @@ -75,7 +75,7 @@ def my_program(): rx(1.0) $1; """ ).strip() - qasm = my_program().with_calibrations(cal_1).to_ir() + qasm = my_program.with_calibrations(cal_1).to_ir() assert qasm == expected @@ -91,7 +91,7 @@ def my_program(): rx("$1", 1.0) with pytest.raises(errors.InvalidCalibrationDefinition): - _ = my_program().with_calibrations(cal_1) + my_program.with_calibrations(cal_1) def test_gate_calibrations_invalid_type(): @@ -123,7 +123,7 @@ def my_program(): for cal in [cal_1, cal_2, cal_3, cal_4, cal_5]: with pytest.raises(errors.ParameterTypeError): - _ = my_program().with_calibrations(cal) + my_program.with_calibrations(cal) def test_gate_calibrations_insufficient_args(): @@ -142,10 +142,10 @@ def my_program(): rx("$1", 1.0) with pytest.raises(errors.InvalidCalibrationDefinition): - _ = my_program().with_calibrations(cal_1) + my_program.with_calibrations(cal_1) with pytest.raises(errors.InvalidCalibrationDefinition): - _ = my_program().with_calibrations(cal_2) + my_program.with_calibrations(cal_2) def test_gate_calibrations_duplicated_args(): @@ -160,7 +160,7 @@ def my_program(): rx("$1", 1.0) with pytest.raises(errors.InvalidCalibrationDefinition): - _ = my_program().with_calibrations(cal_1) + my_program.with_calibrations(cal_1) def test_gate_calibrations_invalid_instructions(): @@ -176,7 +176,7 @@ def my_program(): rx("$1", 1.0) with pytest.raises(errors.InvalidCalibrationDefinition): - _ = my_program().with_calibrations(cal_1) + my_program.with_calibrations(cal_1) def test_gate_calibrations_bind_calibrations_not_inplace(): @@ -190,12 +190,11 @@ def cal_1(angle: float): def my_program(): rx("$1", 1.0) - program_1 = my_program() - _ = program_1.with_calibrations(cal_1) + program_1 = my_program + program_2 = my_program.with_calibrations(cal_1) + program_3 = my_program - program_2 = my_program() - - assert program_1.to_ir() == program_2.to_ir() + assert program_1.to_ir() == program_3.to_ir() != program_2.to_ir() def test_gate_calibrations_with_gate_definition(): @@ -228,5 +227,5 @@ def my_program(): my_gate(0.123) __qubits__[2]; """ ).strip() - qasm = my_program().with_calibrations(cal_1).to_ir() + qasm = my_program.with_calibrations(cal_1).to_ir() assert qasm == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py index c9548d3e..b5fad84b 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py @@ -39,10 +39,9 @@ def my_program(): qubit[1] __qubits__; empty_gate __qubits__[0];""" - program = my_program() - assert program.to_ir() == expected + assert my_program.to_ir() == expected - _test_on_local_sim(program) + _test_on_local_sim(my_program) def test_double_gate_decorator() -> None: @@ -58,8 +57,7 @@ def my_program(): qubit[1] __qubits__; empty_gate __qubits__[0];""" - program = my_program() - assert program.to_ir() == expected + assert my_program.to_ir() == expected def test_gate_class() -> None: @@ -70,12 +68,11 @@ class MyGate: def __init__(self, q: aq.Qubit): h(q) - @aq.main - def main(): - MyGate(0) - with pytest.raises(ValueError): - main() + + @aq.main + def main(): + MyGate(0) def test_invalid_symbol() -> None: @@ -84,12 +81,11 @@ def my_gate(q: aq.Qubit): h(q) not_a_symbol() # noqa: F821 # type: ignore - @aq.main - def main(): - my_gate(0) - with pytest.raises(NameError): - main() + + @aq.main + def main(): + my_gate(0) def test_duplicate_gate_names() -> None: @@ -112,8 +108,7 @@ def main(): qubit[1] __qubits__; my_gate(0.7853981633974483) __qubits__[0];""" - program = main() - assert program.to_ir() == expected + assert main.to_ir() == expected def test_duplicate_gate_names_in_subroutine() -> None: @@ -147,8 +142,7 @@ def define_gate_in_subroutine() { my_gate(0.7853981633974483) __qubits__[0]; define_gate_in_subroutine();""" - program = main() - assert program.to_ir() == expected + assert main.to_ir() == expected def test_incorrect_arg_count() -> None: @@ -157,15 +151,14 @@ def my_gate(q0: aq.Qubit, q1: aq.Qubit): h(q0) x(q1) - @aq.main - def incorrect_arg_count(): - my_gate(0) - with pytest.raises( errors.ParameterTypeError, match='Incorrect number of arguments passed to gate "my_gate". Expected 2, got 1.', ): - incorrect_arg_count() + + @aq.main + def incorrect_arg_count(): + my_gate(0) def test_incorrect_arg_types() -> None: @@ -174,12 +167,11 @@ def my_gate(q: aq.Qubit, theta: float): h(q) rx(q, theta) - @aq.main - def incorrect_arg_types(): - my_gate(0.25, 0) - with pytest.raises(TypeError): - incorrect_arg_types() + + @aq.main + def incorrect_arg_types(): + my_gate(0.25, 0) def test_missing_annotation() -> None: @@ -187,12 +179,11 @@ def test_missing_annotation() -> None: def my_gate(a): pass - @aq.main - def my_program(): - my_gate("test") - with pytest.raises(errors.MissingParameterTypeError): - my_program() + + @aq.main + def my_program(): + my_gate("test") def test_incorrect_annotation() -> None: @@ -200,12 +191,11 @@ def test_incorrect_annotation() -> None: def my_gate(a: str): pass - @aq.main - def my_program(): - my_gate("test") - with pytest.raises(errors.ParameterTypeError): - my_program() + + @aq.main + def my_program(): + my_gate("test") def test_no_qubit_args() -> None: @@ -213,15 +203,14 @@ def test_no_qubit_args() -> None: def not_a_gate(angle: float): pass - @aq.main - def my_program(): - not_a_gate(np.pi) - with pytest.raises( errors.ParameterTypeError, match='Gate definition "not_a_gate" has no arguments of type aq.Qubit.', ): - my_program() + + @aq.main + def my_program(): + not_a_gate(np.pi) def test_invalid_qubit_used() -> None: @@ -230,15 +219,14 @@ def my_gate(q: aq.Qubit): h(q) x(1) # invalid - @aq.main - def my_program(): - my_gate(0) - with pytest.raises( errors.InvalidGateDefinition, match='Gate definition "my_gate" uses qubit "1" which is not an argument to the gate.', ): - my_program() + + @aq.main + def my_program(): + my_gate(0) def test_invalid_angle_used() -> None: @@ -250,15 +238,14 @@ def my_gate(q: aq.Qubit, theta: float): rx(q, theta) rx(q, beta) # invalid - @aq.main - def my_program(): - my_gate(0, np.pi / 2) - with pytest.raises( errors.InvalidGateDefinition, match='Gate definition "my_gate" uses angle (.*) which is not an argument to the gate.', ): - my_program() + + @aq.main + def my_program(): + my_gate(0, np.pi / 2) def test_invalid_instruction() -> None: @@ -267,15 +254,14 @@ def my_gate(q: aq.Qubit): h(q) reset(q) # invalid - @aq.main - def my_program(): - my_gate(0) - with pytest.raises( errors.InvalidGateDefinition, match='Gate definition "my_gate" contains invalid operations.', ): - my_program() + + @aq.main + def my_program(): + my_gate(0) def test_invalid_control_flow() -> None: @@ -285,15 +271,14 @@ def my_gate(q: aq.Qubit): if measure(q): x(q) - @aq.main - def my_program(): - my_gate(0) - with pytest.raises( errors.InvalidGateDefinition, match='Gate definition "my_gate" contains invalid operations.', ): - my_program() + + @aq.main + def my_program(): + my_gate(0) def test_nested_gates() -> None: @@ -329,10 +314,9 @@ def my_program(): __bit_0__[0] = measure __qubits__[0]; __bit_0__[1] = measure __qubits__[1];""" - program = my_program() - assert program.to_ir() == expected + assert my_program.to_ir() == expected - _test_on_local_sim(program) + _test_on_local_sim(my_program) def test_gate_called_from_subroutine() -> None: @@ -362,5 +346,4 @@ def subroutine(int[32] q0, int[32] q1) { subroutine(0, 1); subroutine(2, 3);""" - program = main() - assert program.to_ir() == expected + assert main.to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_definitions.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_definitions.py index 3f30c2fc..a4253fb4 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_gate_definitions.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_gate_definitions.py @@ -64,7 +64,7 @@ def test_bell_state_prep(bell_state_program) -> None: qubit[2] __qubits__; h __qubits__[0]; cnot __qubits__[0], __qubits__[1];""" - actual_result = bell_state_program().to_ir() + actual_result = bell_state_program.to_ir() assert actual_result == expected @@ -73,7 +73,7 @@ def test_physical_q_bell_state_prep(physical_bell_program) -> None: expected = """OPENQASM 3.0; h $0; cnot $0, $5;""" - actual_result = physical_bell_program().to_ir() + actual_result = physical_bell_program.to_ir() assert actual_result == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py index ca85fec3..7b8b0bb7 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_operators.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py @@ -106,7 +106,7 @@ def cond_exp_assignment(): } a = __int_3__;""" - assert cond_exp_assignment().to_ir() == expected + assert cond_exp_assignment.to_ir() == expected @pytest.mark.parametrize( @@ -120,12 +120,11 @@ def cond_exp_assignment(): def test_unsupported_inline_conditional_assignment(else_value) -> None: """Tests conditional expression where the if and else clauses return different types.""" - @aq.main - def cond_exp_assignment_different_types(): - a = aq.IntVar(1) if aq.BoolVar(True) else else_value() # noqa: F841 - with pytest.raises(UnsupportedConditionalExpressionError): - cond_exp_assignment_different_types() + + @aq.main + def cond_exp_assignment_different_types(): + a = aq.IntVar(1) if aq.BoolVar(True) else else_value() # noqa: F841 def test_branch_assignment_undeclared() -> None: @@ -147,7 +146,7 @@ def branch_assignment_undeclared(): a = 2; }""" - assert branch_assignment_undeclared().to_ir() == expected + assert branch_assignment_undeclared.to_ir() == expected def test_branch_assignment_declared() -> None: @@ -170,7 +169,7 @@ def branch_assignment_declared(): a = 7; }""" - assert branch_assignment_declared().to_ir() == expected + assert branch_assignment_declared.to_ir() == expected def for_body(i: aq.Qubit) -> None: @@ -381,7 +380,7 @@ def do_and(bool a, bool b) -> bool { bool __bool_1__; __bool_1__ = do_and(true, false);""" - assert prog().to_ir() == expected + assert prog.to_ir() == expected def test_logical_op_or() -> None: @@ -402,7 +401,7 @@ def do_or(bool a, bool b) -> bool { bool __bool_1__; __bool_1__ = do_or(true, false);""" - assert prog().to_ir() == expected + assert prog.to_ir() == expected def test_logical_op_not() -> None: @@ -423,7 +422,7 @@ def do_not(bool a) -> bool { bool __bool_1__; __bool_1__ = do_not(true);""" - assert prog().to_ir() == expected + assert prog.to_ir() == expected def test_logical_op_eq() -> None: @@ -444,7 +443,7 @@ def do_eq(int[32] a, int[32] b) -> bool { bool __bool_1__; __bool_1__ = do_eq(1, 2);""" - assert prog().to_ir() == expected + assert prog.to_ir() == expected def test_logical_op_not_eq() -> None: @@ -465,7 +464,7 @@ def do_not_eq(int[32] a, int[32] b) -> bool { bool __bool_1__; __bool_1__ = do_not_eq(1, 2);""" - assert prog().to_ir() == expected + assert prog.to_ir() == expected def test_logical_ops_py() -> None: @@ -484,7 +483,7 @@ def prog(): expected = """OPENQASM 3.0;""" - assert prog().to_ir() == expected + assert prog.to_ir() == expected def test_comparison_lt() -> None: @@ -507,8 +506,7 @@ def prog(): if (__bool_1__) { h __qubits__[0]; }""" - qasm = prog().to_ir() - assert qasm == expected + assert prog.to_ir() == expected def test_comparison_gt() -> None: @@ -531,8 +529,7 @@ def prog(): if (__bool_1__) { h __qubits__[0]; }""" - qasm = prog().to_ir() - assert qasm == expected + assert prog.to_ir() == expected def test_comparison_ops_py() -> None: @@ -551,7 +548,7 @@ def prog(): assert all([c, d, not e, not f, h]) expected = """OPENQASM 3.0;""" - assert prog().to_ir() == expected + assert prog.to_ir() == expected @pytest.mark.parametrize( @@ -644,7 +641,7 @@ def slice(): bit b = 1; a[3] = b;""" - assert slice().to_ir() == expected + assert slice.to_ir() == expected def test_slice_bits_w_measure() -> None: @@ -665,7 +662,7 @@ def measure_to_slice(): c = __bit_1__; b0[3] = c;""" - assert measure_to_slice().to_ir() == expected + assert measure_to_slice.to_ir() == expected @pytest.mark.parametrize( @@ -794,10 +791,11 @@ def test_assignment_py(value: Any) -> None: @pytest.mark.parametrize("value", [0, 1]) def test_py_if_stmt(value: int) -> None: """Test Python if branches on true and false conditions.""" + a = value @aq.main - def test_control_flow(a: int): - "Quick if statement test" + def test_control_flow(): + """Quick if statement test""" if a: h(0) else: @@ -808,7 +806,7 @@ def test_control_flow(a: int): {} __qubits__[0];""".format( "h" if value else "x" ) - assert test_control_flow(value).to_ir() == expected + assert test_control_flow.to_ir() == expected def test_py_while() -> None: @@ -816,7 +814,7 @@ def test_py_while() -> None: @aq.main def test_control_flow(): - "Quick while loop test" + """Quick while loop test""" a = 3 while a >= 2: a -= 1 @@ -827,30 +825,44 @@ def test_control_flow(): h __qubits__[0]; h __qubits__[0];""" - assert test_control_flow().to_ir() == expected + assert test_control_flow.to_ir() == expected def test_py_assert() -> None: """Test Python assertions inside an AutoQASM program.""" + not_supported = ( + "Assertions are not supported for values that depend on " + "measurement results or AutoQASM variables." + ) + with pytest.raises(NotImplementedError, match=not_supported): + + @aq.main + def test_input_assert(value: bool): + assert value + + true_var = 1 + false_var = False + @aq.main - def test_assert(value: bool): - assert value + def test_assert(): + assert true_var - test_assert(True) with pytest.raises(AssertionError): - test_assert(False) + + @aq.main + def test_assert_false(): + assert false_var def test_measurement_assert() -> None: """Test assertions on measurement results inside an AutoQASM program.""" - @aq.main - def test_assert(): - assert measure(0) - with pytest.raises(NotImplementedError): - test_assert() + + @aq.main + def test_assert(): + assert measure(0) def test_py_list_ops() -> None: @@ -864,5 +876,3 @@ def test_list_ops(): assert b == 1 c = np.stack([a, a]) assert np.array_equal(c, [[2, 3, 4], [2, 3, 4]]) - - test_list_ops() diff --git a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py index 5a9aff16..66f9a4ab 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py @@ -35,7 +35,7 @@ from braket.tasks.local_quantum_task import LocalQuantumTask -def _test_parametric_on_local_sim(program: aq.Program, inputs: dict[str, float]) -> None: +def _test_parametric_on_local_sim(program: aq.Program, inputs: dict[str, float]) -> np.ndarray: device = LocalSimulator(backend=StateVectorSimulator()) task = device.run(program, shots=100, inputs=inputs) assert isinstance(task, LocalQuantumTask) @@ -43,13 +43,27 @@ def _test_parametric_on_local_sim(program: aq.Program, inputs: dict[str, float]) return task.result().measurements -@aq.main +@pytest.fixture def simple_parametric(): - rx(0, FreeParameter("theta")) - measure(0) + @aq.main + def simple_parametric(theta): + rx(0, theta) + measure(0) + + return simple_parametric + + +@pytest.fixture +def simple_parametric_fp(): + @aq.main + def simple_parametric(): + rx(0, FreeParameter("theta")) + measure(0) + + return simple_parametric -def test_simple_parametric(): +def test_simple_parametric(simple_parametric): """Test a program with a parameter can be serialized.""" expected = """OPENQASM 3.0; @@ -58,24 +72,47 @@ def test_simple_parametric(): rx(theta) __qubits__[0]; bit __bit_0__; __bit_0__ = measure __qubits__[0];""" - assert simple_parametric().to_ir() == expected + assert simple_parametric.to_ir() == expected -def test_sim_simple(): - measurements = _test_parametric_on_local_sim(simple_parametric(), {"theta": 0}) +def test_simple_parametric_fp(simple_parametric_fp): + """Test a program with an explicit free parameter can be serialized.""" + + expected = """OPENQASM 3.0; +input float[64] theta; +qubit[1] __qubits__; +rx(theta) __qubits__[0]; +bit __bit_0__; +__bit_0__ = measure __qubits__[0];""" + assert simple_parametric_fp.to_ir() == expected + + +def test_sim_simple(simple_parametric): + measurements = _test_parametric_on_local_sim(simple_parametric, {"theta": 0}) + assert 1 not in measurements["__bit_0__"] + measurements = _test_parametric_on_local_sim(simple_parametric, {"theta": np.pi}) + assert 0 not in measurements["__bit_0__"] + + +def test_sim_simple_fp(simple_parametric_fp): + measurements = _test_parametric_on_local_sim(simple_parametric_fp, {"theta": 0}) assert 1 not in measurements["__bit_0__"] - measurements = _test_parametric_on_local_sim(simple_parametric(), {"theta": np.pi}) + measurements = _test_parametric_on_local_sim(simple_parametric_fp, {"theta": np.pi}) assert 0 not in measurements["__bit_0__"] -@aq.main +@pytest.fixture def multi_parametric(): - rx(0, FreeParameter("alpha")) - rx(1, FreeParameter("theta")) - c = measure([0, 1]) # noqa: F841 + @aq.main + def multi_parametric(alpha, theta): + rx(0, alpha) + rx(1, theta) + c = measure([0, 1]) # noqa: F841 + return multi_parametric -def test_multiple_parameters(): + +def test_multiple_parameters(multi_parametric): """Test that multiple free parameters all appear in the processed program.""" expected = """OPENQASM 3.0; @@ -89,13 +126,13 @@ def test_multiple_parameters(): __bit_0__[0] = measure __qubits__[0]; __bit_0__[1] = measure __qubits__[1]; c = __bit_0__;""" - assert multi_parametric().to_ir() == expected + assert multi_parametric.to_ir() == expected -def test_sim_multi_param(): - measurements = _test_parametric_on_local_sim(multi_parametric(), {"alpha": np.pi, "theta": 0}) +def test_sim_multi_param(multi_parametric): + measurements = _test_parametric_on_local_sim(multi_parametric, {"alpha": np.pi, "theta": 0}) assert all(val == "10" for val in measurements["c"]) - measurements = _test_parametric_on_local_sim(multi_parametric(), {"alpha": 0, "theta": np.pi}) + measurements = _test_parametric_on_local_sim(multi_parametric, {"alpha": 0, "theta": np.pi}) assert all(val == "01" for val in measurements["c"]) @@ -103,9 +140,7 @@ def test_repeat_parameter(): """Test that programs can use the same parameter multiple times.""" @aq.main - def parametric(): - alpha = FreeParameter("alpha") - theta = FreeParameter("theta") + def parametric(alpha, theta): rx(0, alpha) rx(1, theta) cnot(0, 1) @@ -121,11 +156,14 @@ def parametric(): cnot __qubits__[0], __qubits__[1]; rx(theta) __qubits__[0]; rx(alpha) __qubits__[1];""" - assert parametric().to_ir() == expected + assert parametric.to_ir() == expected def test_parameter_in_subroutine(): """Test that parameters in subroutines are declared appropriately.""" + # TODO (#816): the openqasm generated here isn't strictly valid + # (cannot close over non-const global variables) + # https://openqasm.com/language/scope.html#subroutine-and-gate-scope @aq.subroutine def rx_alpha(qubit: int): @@ -142,14 +180,13 @@ def rx_alpha(int[32] qubit) { input float[64] alpha; qubit[3] __qubits__; rx_alpha(2);""" - assert parametric().to_ir() == expected + assert parametric.to_ir() == expected def test_captured_parameter(): """Test that a parameter declared in a larger scope is captured and functions correctly. """ - alpha = FreeParameter("alpha") @aq.main @@ -162,43 +199,50 @@ def parametric(): qubit[2] __qubits__; rz(alpha) __qubits__[0]; rx(alpha) __qubits__[1];""" - assert parametric().to_ir() == expected + assert parametric.to_ir() == expected def test_multi_angle_gates(): """Test that FreeParameters work with gates that take multiple inputs.""" + qubit_0 = 2 + theta = 0.5 @aq.main(num_qubits=5) - def parametric(qubit_0: int, phi: float, theta: float): + def parametric(phi: float): ms(0, qubit_0, phi, phi, theta) expected = """OPENQASM 3.0; input float[64] phi; qubit[5] __qubits__; ms(phi, phi, 0.5) __qubits__[0], __qubits__[2];""" - assert parametric(2, FreeParameter("phi"), 0.5).to_ir() == expected + assert parametric.to_ir() == expected def test_sim_multi_angle(): + theta = 0 + @aq.main - def parametric(phi: float, theta: float): + def parametric(phi: float): ms(0, 1, phi, phi, theta) - _test_parametric_on_local_sim(parametric(FreeParameter("phi"), 0.0), {"phi": np.pi}) + _test_parametric_on_local_sim( + parametric, + inputs={"phi": np.pi}, + ) def test_parameters_passed_as_main_arg(): """Test that parameters work when passed as input values.""" @aq.main - def parametric(phi: float): - cphaseshift(0, 1, phi) + def parametric(my_phi: float): + cphaseshift(0, 1, my_phi) expected = """OPENQASM 3.0; input float[64] my_phi; qubit[2] __qubits__; cphaseshift(my_phi) __qubits__[0], __qubits__[1];""" - assert parametric(FreeParameter("my_phi")).to_ir() == expected + assert parametric.to_ir() == expected def test_simple_subroutine_arg(): @@ -209,8 +253,8 @@ def silly_rz(theta: float): rz(0, theta) @aq.main(num_qubits=1) - def parametric(): - silly_rz(FreeParameter("alpha")) + def parametric(alpha): + silly_rz(alpha) expected = """OPENQASM 3.0; def silly_rz(float[64] theta) { @@ -219,7 +263,7 @@ def silly_rz(float[64] theta) { input float[64] alpha; qubit[1] __qubits__; silly_rz(alpha);""" - assert parametric().to_ir() == expected + assert parametric.to_ir() == expected def test_parameters_passed_as_subroutine_arg(): @@ -230,8 +274,8 @@ def silly_ms(qubit_0: int, phi: float, theta: float): ms(0, qubit_0, phi, phi, theta) @aq.main(num_qubits=5) - def parametric(): - silly_ms(1, FreeParameter("alpha"), 0.707) + def parametric(alpha): + silly_ms(1, alpha, 0.707) silly_ms(3, 0.5, FreeParameter("beta")) expected = """OPENQASM 3.0; @@ -243,7 +287,7 @@ def silly_ms(int[32] qubit_0, float[64] phi, float[64] theta) { qubit[5] __qubits__; silly_ms(1, alpha, 0.707); silly_ms(3, 0.5, beta);""" - assert parametric().to_ir() == expected + assert parametric.to_ir() == expected def test_sim_subroutine_arg(): @@ -252,11 +296,11 @@ def rx_theta(theta: float): rx(0, theta) @aq.main - def parametric(): - rx_theta(FreeParameter("theta")) + def parametric(theta): + rx_theta(theta) measure(0) - measurements = _test_parametric_on_local_sim(parametric(), {"theta": np.pi}) + measurements = _test_parametric_on_local_sim(parametric, {"theta": np.pi}) assert 0 not in measurements["__bit_0__"] @@ -278,7 +322,7 @@ def parametric(): input float[64] θ; qubit[3] __qubits__; rx_theta(θ) __qubits__[2];""" - assert parametric().to_ir() == expected + assert parametric.to_ir() == expected def test_parametric_pulse_cals(): @@ -289,8 +333,8 @@ def cal_1(angle: float): pulse.delay("$1", angle) @aq.main - def my_program(): - rx("$1", FreeParameter("theta")) + def my_program(theta): + rx("$1", theta) expected = """OPENQASM 3.0; input float[64] theta; @@ -298,7 +342,7 @@ def my_program(): delay[(angle) * 1s] $1; } rx(theta) $1;""" - qasm = my_program().with_calibrations(cal_1).to_ir() + qasm = my_program.with_calibrations(cal_1).to_ir() assert qasm == expected @@ -306,11 +350,10 @@ def test_bind_parameters(): """Test binding FreeParameters to concrete values.""" @aq.main - def parametric(theta: float): - rx(0, theta) + def parametric(alpha: float): + rx(0, alpha) measure(0) - prog = parametric(FreeParameter("alpha")) unbound_expected = """OPENQASM 3.0; input float[64] alpha; qubit[1] __qubits__; @@ -325,13 +368,13 @@ def parametric(theta: float): bit __bit_0__; __bit_0__ = measure __qubits__[0];""" - assert prog.to_ir() == unbound_expected - bound_prog = prog.make_bound_program({"alpha": 0.5}) + assert parametric.to_ir() == unbound_expected + bound_prog = parametric.make_bound_program({"alpha": 0.5}) # Original program unchanged - assert prog.to_ir() == unbound_expected + assert parametric.to_ir() == unbound_expected assert bound_prog.to_ir() == bound_template.format(0.5) # Can rebind - bound_prog = prog.make_bound_program({"alpha": 0.432143}) + bound_prog = parametric.make_bound_program({"alpha": 0.432143}) assert bound_prog.to_ir() == bound_template.format(0.432143) @@ -351,12 +394,11 @@ def rx_alpha(qubit: int): rx(qubit, FreeParameter("alpha")) @aq.main(num_qubits=3) - def parametric(alpha: float, theta: float): - sub(alpha, theta) + def parametric(alpha: float, beta: float): + sub(alpha, beta) rx_alpha(2) - prog = parametric(FreeParameter("alpha"), FreeParameter("beta")) - bound_prog = prog.make_bound_program({"alpha": 0.5, "beta": 1.5}) + bound_prog = parametric.make_bound_program({"alpha": 0.5, "beta": 1.5}) expected = """OPENQASM 3.0; def sub(float[64] alpha, float[64] theta) { @@ -389,8 +431,7 @@ def parametric(alpha: float, beta: float): rx_alpha(2, alpha) rx_alpha(2, beta) - prog = parametric(FreeParameter("alpha"), FreeParameter("beta")) - bound_prog = prog.make_bound_program({"beta": np.pi}) + bound_prog = parametric.make_bound_program({"beta": np.pi}) expected = """OPENQASM 3.0; def rx_alpha(int[32] qubit, float[64] theta) { @@ -412,11 +453,11 @@ def cal_1(angle: float): pulse.delay("$1", angle) @aq.main - def my_program(): - rx("$1", FreeParameter("theta")) + def my_program(theta): + rx("$1", theta) - qasm1 = my_program().with_calibrations(cal_1).make_bound_program({"theta": 0.6}).to_ir() - qasm2 = my_program().make_bound_program({"theta": 0.6}).with_calibrations(cal_1).to_ir() + qasm1 = my_program.with_calibrations(cal_1).make_bound_program({"theta": 0.6}).to_ir() + qasm2 = my_program.make_bound_program({"theta": 0.6}).with_calibrations(cal_1).to_ir() assert qasm1 == qasm2 expected = """OPENQASM 3.0; @@ -435,9 +476,9 @@ def test_bind_empty_program(): def empty_program(): pass - qasm = empty_program().to_ir() - bound_program1 = empty_program().make_bound_program({}).to_ir() - bound_program2 = empty_program().make_bound_program({"alpha": 0.5}).to_ir() + qasm = empty_program.to_ir() + bound_program1 = empty_program.make_bound_program({}).to_ir() + bound_program2 = empty_program.make_bound_program({"alpha": 0.5}).to_ir() assert qasm == bound_program1 == bound_program2 @@ -445,12 +486,10 @@ def test_strict_parameter_bind(): """Test make_bound_program with strict set to True.""" @aq.main - def parametric(theta: float): - rx(0, theta) + def parametric(alpha: float): + rx(0, alpha) measure(0) - prog = parametric(FreeParameter("alpha")) - template = """OPENQASM 3.0; float[64] alpha = {}; qubit[1] __qubits__; @@ -458,7 +497,7 @@ def parametric(theta: float): bit __bit_0__; __bit_0__ = measure __qubits__[0];""" - bound_prog = prog.make_bound_program({"alpha": 0.5}, strict=True) + bound_prog = parametric.make_bound_program({"alpha": 0.5}, strict=True) assert bound_prog.to_ir() == template.format(0.5) @@ -466,27 +505,32 @@ def test_strict_parameter_bind_failure(): """Test make_bound_program with strict set to True.""" @aq.main - def parametric(theta: float): - rx(0, theta) + def parametric(alpha: float): + rx(0, alpha) measure(0) - prog = parametric(FreeParameter("alpha")) with pytest.raises( aq.errors.ParameterNotFoundError, match="No parameter in the program named: beta" ): - prog.make_bound_program({"beta": 0.5}, strict=True) + parametric.make_bound_program({"beta": 0.5}, strict=True) def test_duplicate_variable_name_fails(): """Test using a variable and FreeParameter with the same name.""" - @aq.main - def parametric(): - alpha = aq.FloatVar(1.2) # noqa: F841 - rx(0, FreeParameter("alpha")) + with pytest.raises(RuntimeError, match="conflicting variables with name alpha"): + + @aq.main + def parametric_explicit(): + alpha = aq.FloatVar(1.2) # noqa: F841 + rx(0, FreeParameter("alpha")) with pytest.raises(RuntimeError, match="conflicting variables with name alpha"): - parametric() + + @aq.main + def parametric(alpha): + alpha = aq.FloatVar(1.2) # noqa: F841 + rx(0, alpha) def test_binding_variable_fails(): @@ -499,7 +543,7 @@ def parametric(): with pytest.raises( aq.errors.ParameterNotFoundError, match="No parameter in the program named: beta" ): - parametric().make_bound_program({"beta": 0.5}, strict=True) + parametric.make_bound_program({"beta": 0.5}, strict=True) def test_compound_condition(): @@ -526,7 +570,7 @@ def parametric(val: float): } bit __bit_3__; __bit_3__ = measure __qubits__[0];""" - assert parametric(FreeParameter("val")).to_ir() == expected + assert parametric.to_ir() == expected def test_lt_condition(): @@ -555,7 +599,7 @@ def parametric(val: float): } bit __bit_2__; __bit_2__ = measure __qubits__[0];""" - assert parametric(FreeParameter("val")).to_ir() == expected + assert parametric.to_ir() == expected def test_parameter_in_predicate_in_subroutine(): @@ -585,39 +629,39 @@ def sub(float[64] val) { sub(val); bit __bit_1__; __bit_1__ = measure __qubits__[0];""" - assert parametric(FreeParameter("val")).to_ir() == expected + assert parametric.to_ir() == expected def test_eq_condition(): """Test parameters used in conditional equals statements.""" @aq.main - def parametric(basis: int): - if basis == 1: + def parametric(threshold: int): + if threshold == 1: h(0) - elif basis == 2: + elif threshold == 2: x(0) else: pass measure(0) expected = """OPENQASM 3.0; -input float[64] basis; +input int[32] threshold; qubit[1] __qubits__; bool __bool_0__; -__bool_0__ = basis == 1; +__bool_0__ = threshold == 1; if (__bool_0__) { h __qubits__[0]; } else { bool __bool_1__; - __bool_1__ = basis == 2; + __bool_1__ = threshold == 2; if (__bool_1__) { x __qubits__[0]; } } bit __bit_2__; __bit_2__ = measure __qubits__[0];""" - assert parametric(FreeParameter("basis")).to_ir() == expected + assert parametric.to_ir() == expected def test_sim_conditional_stmts(): @@ -629,22 +673,22 @@ def main(basis: int): x(0) c = measure(0) # noqa: F841 - measurements = _test_parametric_on_local_sim(main(FreeParameter("basis")), {"basis": 0}) + measurements = _test_parametric_on_local_sim(main, {"basis": 0}) assert all(val == 1 for val in measurements["c"]) - measurements = _test_parametric_on_local_sim(main(FreeParameter("basis")), {"basis": 1}) + measurements = _test_parametric_on_local_sim(main, {"basis": 1}) assert 1 in measurements["c"] and 0 in measurements["c"] def test_sim_comparison_stmts(): @aq.main - def main(basis: int): + def main(basis: float): if basis > 0.5: x(0) c = measure(0) # noqa: F841 - measurements = _test_parametric_on_local_sim(main(FreeParameter("basis")), {"basis": 0.5}) + measurements = _test_parametric_on_local_sim(main, {"basis": 0.5}) assert all(val == 0 for val in measurements["c"]) - measurements = _test_parametric_on_local_sim(main(FreeParameter("basis")), {"basis": 0.55}) + measurements = _test_parametric_on_local_sim(main, {"basis": 0.55}) assert all(val == 1 for val in measurements["c"]) @@ -658,7 +702,7 @@ def parametric(val: int): measure(0) expected = """OPENQASM 3.0; -input float[64] val; +input int[32] val; qubit[1] __qubits__; bool __bool_0__; __bool_0__ = val != 1; @@ -667,7 +711,7 @@ def parametric(val: int): } bit __bit_1__; __bit_1__ = measure __qubits__[0];""" - assert parametric(FreeParameter("val")).to_ir() == expected + assert parametric.to_ir() == expected def test_param_or(): @@ -692,7 +736,7 @@ def parametric(alpha: float, beta: float): } bit __bit_1__; __bit_1__ = measure __qubits__[0];""" - assert parametric(FreeParameter("alpha"), FreeParameter("beta")).to_ir() == expected + assert parametric.to_ir() == expected def test_param_and(): @@ -715,14 +759,15 @@ def parametric(alpha: float, beta: float): } bit __bit_1__; __bit_1__ = measure __qubits__[0];""" - assert parametric(FreeParameter("alpha"), FreeParameter("beta")).to_ir() == expected + assert parametric.to_ir() == expected def test_param_and_float(): """Test parameters used in conditional `and` statements.""" + beta = 1.5 @aq.main - def parametric(alpha: float, beta: float): + def parametric(alpha: float): if alpha and beta: rx(0, alpha) measure(0) @@ -737,7 +782,7 @@ def parametric(alpha: float, beta: float): } bit __bit_1__; __bit_1__ = measure __qubits__[0];""" - assert parametric(FreeParameter("alpha"), 1.5).to_ir() == expected + assert parametric.to_ir() == expected def test_param_not(): @@ -750,7 +795,7 @@ def parametric(val: int): measure(0) expected = """OPENQASM 3.0; -input float[64] val; +input int[32] val; qubit[1] __qubits__; bool __bool_0__; __bool_0__ = !val; @@ -759,7 +804,7 @@ def parametric(val: int): } bit __bit_1__; __bit_1__ = measure __qubits__[0];""" - assert parametric(FreeParameter("val")).to_ir() == expected + assert parametric.to_ir() == expected def test_parameter_binding_conditions(): @@ -781,9 +826,9 @@ def parametric(val: float): }} bit __bit_1__; __bit_1__ = measure __qubits__[0];""" - bound_prog = parametric(FreeParameter("val")).make_bound_program({"val": 0}) + bound_prog = parametric.make_bound_program({"val": 0}) assert bound_prog.to_ir() == template.format(0) - bound_prog = parametric(FreeParameter("val")).make_bound_program({"val": 1}) + bound_prog = parametric.make_bound_program({"val": 1}) assert bound_prog.to_ir() == template.format(1) @@ -791,7 +836,12 @@ def test_parameter_expressions(): """Test expressions of free parameters with numeric literals.""" @aq.main - def parametric(): + def parametric(theta): + expr = 2 * theta + gpi(0, expr) + + @aq.main + def parametric_fp(): expr = 2 * FreeParameter("theta") gpi(0, expr) @@ -799,16 +849,16 @@ def parametric(): input float[64] theta; qubit[1] __qubits__; gpi(2*theta) __qubits__[0];""" - assert parametric().to_ir() == expected + assert parametric.to_ir() == parametric_fp.to_ir() == expected def test_sim_expressions(): @aq.main - def parametric(): - rx(0, 2 * FreeParameter("phi")) + def parametric(phi): + rx(0, 2 * phi) measure(0) - measurements = _test_parametric_on_local_sim(parametric(), {"phi": np.pi / 2}) + measurements = _test_parametric_on_local_sim(parametric, {"phi": np.pi / 2}) assert 0 not in measurements["__bit_0__"] @@ -816,8 +866,8 @@ def test_multi_parameter_expressions(): """Test expressions of multiple free parameters.""" @aq.main - def parametric(): - expr = FreeParameter("alpha") * FreeParameter("theta") + def parametric(alpha, theta): + expr = alpha * theta gpi(0, expr) expected = """OPENQASM 3.0; @@ -825,29 +875,29 @@ def parametric(): input float[64] theta; qubit[1] __qubits__; gpi(alpha*theta) __qubits__[0];""" - assert parametric().to_ir() == expected + assert parametric.to_ir() == expected def test_bound_parameter_expressions(): """Test expressions of free parameters bound to specific values.""" @aq.main - def parametric(): - rx(0, 2 * FreeParameter("phi")) + def parametric(phi): + rx(0, 2 * phi) expected = """OPENQASM 3.0; float[64] phi = 1.5707963267948966; qubit[1] __qubits__; rx(2*phi) __qubits__[0];""" - assert parametric().make_bound_program({"phi": np.pi / 2}).to_ir() == expected + assert parametric.make_bound_program({"phi": np.pi / 2}).to_ir() == expected def test_partially_bound_parameter_expressions(): """Test expressions of free parameters partially bound to specific values.""" @aq.main - def parametric(): - expr = FreeParameter("prefactor") * FreeParameter("theta") + def parametric(prefactor, theta): + expr = prefactor * theta gpi(0, expr) expected = """OPENQASM 3.0; @@ -855,7 +905,7 @@ def parametric(): input float[64] theta; qubit[1] __qubits__; gpi(prefactor*theta) __qubits__[0];""" - assert parametric().make_bound_program({"prefactor": 3}).to_ir() == expected + assert parametric.make_bound_program({"prefactor": 3}).to_ir() == expected def test_subroutine_parameter_expressions(): @@ -866,8 +916,8 @@ def rotate(theta: float): rx(0, 3 * theta) @aq.main - def parametric(): - rotate(2 * FreeParameter("alpha")) + def parametric(alpha): + rotate(2 * alpha) expected = """OPENQASM 3.0; def rotate(float[64] theta) { @@ -876,7 +926,7 @@ def rotate(float[64] theta) { input float[64] alpha; qubit[1] __qubits__; rotate(2*alpha);""" - assert parametric().to_ir() == expected + assert parametric.to_ir() == expected def test_gate_parameter_expressions(): @@ -887,8 +937,8 @@ def rotate(q: aq.Qubit, theta: float): rx(q, 3 * theta) @aq.main - def parametric(): - rotate(0, 2 * FreeParameter("alpha")) + def parametric(alpha): + rotate(0, 2 * alpha) expected = """OPENQASM 3.0; gate rotate(theta) q { @@ -897,15 +947,15 @@ def parametric(): input float[64] alpha; qubit[1] __qubits__; rotate(2*alpha) __qubits__[0];""" - assert parametric().to_ir() == expected + assert parametric.to_ir() == expected def test_conditional_parameter_expressions(): """Test expressions of free parameters contained in conditional statements.""" @aq.main - def parametric(): - if 2 * FreeParameter("phi") > np.pi: + def parametric(phi): + if 2 * phi > np.pi: h(0) measure(0) @@ -920,4 +970,4 @@ def parametric(): } bit __bit_2__; __bit_2__ = measure __qubits__[0];""" - assert parametric().to_ir() == expected + assert parametric.to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py b/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py index 583a5bd7..2ffe007c 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py @@ -52,25 +52,23 @@ def program_func() -> None: cnot $1, $2; }""" - assert program_func().to_ir() == expected + assert program_func.to_ir() == expected def test_nested_verbatim_box() -> None: - @aq.main - def program_func() -> None: - with aq.verbatim(): - with aq.verbatim(): - h(0) - with pytest.raises(errors.VerbatimBlockNotAllowed): - program_func() + @aq.main + def program_func() -> None: + with aq.verbatim(): + with aq.verbatim(): + h(0) -def test_verbatim_box_invalid_target_qubit() -> None: - @aq.main - def program_func() -> None: - with aq.verbatim(): - h(0) +def test_verbatim_box_invalid_target_qubit() -> None: with pytest.raises(errors.InvalidTargetQubit): - program_func() + + @aq.main + def program_func() -> None: + with aq.verbatim(): + h(0) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/braket/experimental/autoqasm/test_program.py index 7d809ed7..116330a5 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_program.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_program.py @@ -85,17 +85,20 @@ def circuit(angle: float): rx(0, angle) cnot(0, 1) - @aq.main - def zne(scale: int, angle: float) -> aq.BitVar: - for i in aq.range(scale): - circuit(angle) - return measure(1) + def build_zne(scale: int, angle: float): + @aq.main + def zne() -> aq.BitVar: + for i in aq.range(scale): + circuit(angle) + return measure(1) + + return zne scales = [2, 4, 6] angles = [0.1, 0.2, 0.3] with ThreadPool(processes=5) as executor: programs = executor.map( - lambda args: zne(*args), + lambda args: build_zne(*args), [(scale, angle) for scale, angle in itertools.product(scales, angles)], ) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pulse.py b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py index de6bca7d..26ee9ba2 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_pulse.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py @@ -64,8 +64,7 @@ def my_program(): } """ ).strip() - qasm = my_program().to_ir() - assert qasm == expected + assert my_program.to_ir() == expected def test_merge_cal_box() -> None: @@ -85,8 +84,7 @@ def my_program(): } """ ).strip() - qasm = my_program().to_ir() - assert qasm == expected + assert my_program.to_ir() == expected @pytest.mark.parametrize( @@ -187,31 +185,33 @@ def test_pulse_control_invalid_physical_qubit(instruction, qubits_or_frames, par def test_pulse_freeparameter() -> None: - """Test pulse program with freeparameter.""" + """Test pulse program with free parameter.""" @aq.main - def my_program(): - delay(["$3", "$4"], FreeParameter("duration")) + def my_program(duration): + delay(["$3", "$4"], duration) + delay(["$3", "$4"], FreeParameter("duration2")) expected = textwrap.dedent( """ OPENQASM 3.0; input float[64] duration; + input float[64] duration2; cal { delay[(duration) * 1s] $3, $4; + delay[(duration2) * 1s] $3, $4; } """ ).strip() - qasm = my_program().to_ir() - assert qasm == expected + assert my_program.to_ir() == expected def test_pulse_freeparameter_bound() -> None: """Test pulse program with freeparameter bound with values.""" @aq.main - def my_program(): - delay(["$3", "$4"], FreeParameter("duration")) + def my_program(duration): + delay(["$3", "$4"], duration) expected = textwrap.dedent( """ @@ -222,7 +222,7 @@ def my_program(): } """ ).strip() - qasm = my_program().make_bound_program({"duration": 0.123}).to_ir() + qasm = my_program.make_bound_program({"duration": 0.123}).to_ir() assert qasm == expected @@ -230,10 +230,10 @@ def test_pulse_freeparameter_condition() -> None: """Test pulse program with freeparameter in condition.""" @aq.main - def my_program(): - delay(["$3", "$4"], FreeParameter("duration")) - if FreeParameter("duration") > FreeParameter("duration2"): - delay(["$3", "$4"], FreeParameter("duration2")) + def my_program(duration, duration2): + delay(["$3", "$4"], duration) + if duration > duration2: + delay(["$3", "$4"], duration2) expected = textwrap.dedent( """ @@ -252,5 +252,4 @@ def my_program(): } """ ).strip() - qasm = my_program().to_ir() - assert qasm == expected + assert my_program.to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_seralization_config.py b/test/unit_tests/braket/experimental/autoqasm/test_serialization_config.py similarity index 95% rename from test/unit_tests/braket/experimental/autoqasm/test_seralization_config.py rename to test/unit_tests/braket/experimental/autoqasm/test_serialization_config.py index 7d190e2f..8ac2a5f2 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_seralization_config.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_serialization_config.py @@ -41,7 +41,7 @@ def my_program(): } """ ).strip() - qasm = my_program().to_ir( + qasm = my_program.to_ir( serialization_properties=OpenQASMSerializationProperties(auto_defcalgrammar=True) ) assert qasm == expected_true @@ -54,7 +54,7 @@ def my_program(): } """ ).strip() - qasm = my_program().to_ir( + qasm = my_program.to_ir( serialization_properties=OpenQASMSerializationProperties(auto_defcalgrammar=False) ) assert qasm == expected_false @@ -77,7 +77,7 @@ def my_program(): } """ ).strip() - qasm = my_program().to_ir( + qasm = my_program.to_ir( serialization_properties=OpenQASMSerializationProperties(include_externs=True) ) assert qasm == expected_true @@ -91,7 +91,7 @@ def my_program(): } """ ).strip() - qasm = my_program().to_ir( + qasm = my_program.to_ir( serialization_properties=OpenQASMSerializationProperties(include_externs=False) ) assert qasm == expected_false diff --git a/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py b/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py index 95afdef0..ef066192 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py @@ -20,18 +20,18 @@ import braket.experimental.autoqasm as aq from braket.experimental.autoqasm.autograph import ag_logging from braket.experimental.autoqasm.autograph.core.ag_ctx import ControlStatusCtx, Status +from braket.experimental.autoqasm.errors import UnknownQubitCountError from braket.experimental.autoqasm.instructions import cnot, h, measure, x def test_convert_invalid_main_object() -> None: """Tests the aq.main decorator on something that is not a function.""" - @aq.main - class MyClass: - pass - with pytest.raises(ValueError): - MyClass() + + @aq.main + class MyClass: + pass def test_convert_invalid_subroutine_object() -> None: @@ -41,27 +41,25 @@ def test_convert_invalid_subroutine_object() -> None: class MyClass: pass - @aq.main - def main(): - MyClass() - with pytest.raises(ValueError): - main() + + @aq.main + def main(): + MyClass() def test_autograph_disabled() -> None: """Tests the aq.main decorator with autograph disabled by control status, and verifies that the function is not converted.""" - @aq.main - def my_program(): - h(0) - if measure(0): - x(0) - with ControlStatusCtx(Status.DISABLED): with pytest.raises(RuntimeError): - my_program() + + @aq.main + def my_program(): + h(0) + if measure(0): + x(0) def test_partial_function() -> None: @@ -71,50 +69,46 @@ def bell(q0: int, q1: int): h(q0) cnot(q0, q1) - @aq.main - def bell_decorated(q0: int, q1: int): - bell(q0, q1) + expected_partial = """OPENQASM 3.0; +input int[32] q1; +qubit[4] __qubits__; +h __qubits__[1]; +cnot __qubits__[1], __qubits__[q1];""" - expected = """OPENQASM 3.0; + expected_no_arg_partial = """OPENQASM 3.0; qubit[4] __qubits__; h __qubits__[1]; cnot __qubits__[1], __qubits__[3];""" - bell_partial = aq.main(functools.partial(bell, 1)) - assert bell_partial(3).to_ir() == expected - - bell_decorated_partial = functools.partial(bell_decorated, 1) - assert bell_decorated_partial(3).to_ir() == expected + with pytest.raises(UnknownQubitCountError): + aq.main(functools.partial(bell, 1)) + bell_partial = aq.main(num_qubits=4)(functools.partial(bell, 1)) + assert bell_partial.to_ir() == expected_partial - bell_noarg_partial = functools.partial(bell_decorated, 1, 3) - assert bell_noarg_partial().to_ir() == expected + bell_noarg_partial = aq.main(functools.partial(bell, 1, 3)) + assert bell_noarg_partial.to_ir() == expected_no_arg_partial def test_classmethod() -> None: - """Tests aq.main decorator application to a classmethod.""" + """Tests aq.main application to a classmethod.""" + # Note - this only works with the function call syntax, since with a decorator, + # the class isn't initialized fully at transpilation time. class MyClass: @classmethod - def bell(self, q0: int, q1: int): + def bell(cls, q0: int, q1: int): h(q0) cnot(q0, q1) - @classmethod - @aq.main - def bell_decorated(self, q0: int, q1: int): - self.bell(q0, q1) - expected = """OPENQASM 3.0; -qubit[4] __qubits__; -h __qubits__[1]; -cnot __qubits__[1], __qubits__[3];""" - - assert aq.main(MyClass.bell)(1, 3).to_ir() == expected - assert MyClass.bell_decorated(1, 3).to_ir() == expected +input int[32] q0; +input int[32] q1; +qubit[2] __qubits__; +h __qubits__[q0]; +cnot __qubits__[q0], __qubits__[q1];""" - a = MyClass() - assert aq.main(a.bell)(1, 3).to_ir() == expected - assert a.bell_decorated(1, 3).to_ir() == expected + assert aq.main(num_qubits=2)(MyClass.bell).to_ir() == expected + assert aq.main(num_qubits=2)(MyClass().bell).to_ir() == expected def test_with_verbose_logging() -> None: @@ -125,4 +119,3 @@ def nothing(): pass ag_logging.set_verbosity(10) - nothing() diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index cc6f1f92..cf54fbd4 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -62,7 +62,7 @@ def ret_test() -> bit { bit __bit_1__; __bit_1__ = ret_test();""" - assert main().to_ir() == expected + assert main.to_ir() == expected def test_return_int(): @@ -85,7 +85,7 @@ def ret_test() -> int[32] { int[32] __int_1__; __int_1__ = ret_test();""" - assert main().to_ir() == expected + assert main.to_ir() == expected def test_return_float(): @@ -108,7 +108,7 @@ def ret_test() -> float[64] { float[64] __float_1__; __float_1__ = ret_test();""" - assert main().to_ir() == expected + assert main.to_ir() == expected def test_return_bool(): @@ -131,7 +131,7 @@ def ret_test() -> bool { bool __bool_1__; __bool_1__ = ret_test();""" - assert main().to_ir() == expected + assert main.to_ir() == expected def test_return_bin_expr(): @@ -156,7 +156,7 @@ def add(int[32] a, int[32] b) -> int[32] { int[32] __int_2__; __int_2__ = add(a, b);""" - assert ret_test().to_ir() == expected + assert ret_test.to_ir() == expected def test_return_none(): @@ -168,7 +168,7 @@ def ret_test() -> None: expected = "OPENQASM 3.0;" - assert ret_test().to_ir() == expected + assert ret_test.to_ir() == expected def test_declare_array(): @@ -189,32 +189,28 @@ def declare_array(): b[2] = 14; b = a;""" - assert declare_array().to_ir() == expected + assert declare_array.to_ir() == expected def test_invalid_array_assignment(): """Test invalid array assignment.""" - - @aq.main - def invalid(): - a = aq.ArrayVar([1, 2, 3], base_type=aq.IntVar, dimensions=[3]) - b = aq.ArrayVar([4, 5], base_type=aq.IntVar, dimensions=[2]) - a = b # noqa: F841 - with pytest.raises(aq.errors.InvalidAssignmentStatement): - invalid() + + @aq.main + def invalid(): + a = aq.ArrayVar([1, 2, 3], base_type=aq.IntVar, dimensions=[3]) + b = aq.ArrayVar([4, 5], base_type=aq.IntVar, dimensions=[2]) + a = b # noqa: F841 def test_declare_array_in_local_scope(): """Test declaring an array inside a local scope.""" - - @aq.main - def declare_array(): - if aq.BoolVar(True): - _ = aq.ArrayVar([1, 2, 3], base_type=aq.IntVar, dimensions=[3]) - with pytest.raises(aq.errors.InvalidArrayDeclaration): - declare_array() + + @aq.main + def declare_array(): + if aq.BoolVar(True): + _ = aq.ArrayVar([1, 2, 3], base_type=aq.IntVar, dimensions=[3]) def test_declare_array_in_subroutine(): @@ -224,12 +220,11 @@ def test_declare_array_in_subroutine(): def declare_array(): _ = aq.ArrayVar([1, 2, 3], dimensions=[3]) - @aq.main - def main() -> list[int]: - return declare_array() - with pytest.raises(aq.errors.InvalidArrayDeclaration): - main() + + @aq.main + def main() -> list[int]: + return declare_array() def test_return_python_array(): @@ -239,12 +234,11 @@ def test_return_python_array(): def tester() -> list[int]: return [1, 2, 3] - @aq.main(num_qubits=4) - def main(): - tester() - with pytest.raises(aq.errors.UnsupportedSubroutineReturnType): - main() + + @aq.main(num_qubits=4) + def main(): + tester() def test_return_array_unsupported(): @@ -254,12 +248,11 @@ def test_return_array_unsupported(): def tester(arr: list[float]) -> list[float]: return [1.2, 2.1] - @aq.main(num_qubits=4) - def main(): - tester([3.3]) - with pytest.raises(aq.errors.ParameterTypeError): - assert main() + + @aq.main(num_qubits=4) + def main(): + tester([3.3]) def test_return_func_call(): @@ -282,7 +275,7 @@ def helper() -> int[32] { int[32] __int_1__; __int_1__ = helper();""" - assert ret_test().to_ir() == expected + assert ret_test.to_ir() == expected def test_map_bool(): @@ -301,7 +294,7 @@ def annotation_test(bool input) { } annotation_test(true);""" - assert main().to_ir() == expected + assert main.to_ir() == expected def test_map_int(): @@ -320,7 +313,7 @@ def annotation_test(int[32] input) { } annotation_test(1);""" - assert main().to_ir() == expected + assert main.to_ir() == expected def test_map_float(): @@ -339,7 +332,7 @@ def annotation_test(float[64] input) { } annotation_test(1.0);""" - assert main().to_ir() == expected + assert main.to_ir() == expected def test_map_array(): @@ -349,13 +342,12 @@ def test_map_array(): def annotation_test(input: list[int]): pass - @aq.main - def main(): - a = aq.ArrayVar([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dimensions=[10]) - annotation_test(a) - with pytest.raises(aq.errors.ParameterTypeError): - main() + + @aq.main + def main(): + a = aq.ArrayVar([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dimensions=[10]) + annotation_test(a) def test_map_other(): @@ -376,7 +368,7 @@ def annotation_test(bit input) { bit a = 1; annotation_test(a);""" - assert main().to_ir() == expected + assert main.to_ir() == expected def test_map_other_unnamed_arg(): @@ -396,7 +388,7 @@ def annotation_test(bit input) { bit __bit_0__ = 1; annotation_test(__bit_0__);""" - assert main().to_ir() == expected + assert main.to_ir() == expected def test_map_and_assign_arg(): @@ -420,7 +412,7 @@ def assign_param(int[32] c) { int[32] c = 0; assign_param(c);""" - assert main().to_ir() == expected + assert main.to_ir() == expected def test_unnamed_retval_python_type() -> None: @@ -442,7 +434,7 @@ def retval_test() -> int[32] { int[32] __int_1__; __int_1__ = retval_test();""" - assert caller().to_ir() == expected_qasm + assert caller.to_ir() == expected_qasm def test_unnamed_retval_qasm_type() -> None: @@ -464,7 +456,7 @@ def retval_test() -> bit { bit __bit_1__; __bit_1__ = retval_test();""" - assert caller().to_ir() == expected_qasm + assert caller.to_ir() == expected_qasm def test_recursive_unassigned_retval_python_type() -> None: @@ -489,7 +481,7 @@ def retval_recursive() -> int[32] { int[32] __int_3__; __int_3__ = retval_recursive();""" - assert main().to_ir() == expected_qasm + assert main.to_ir() == expected_qasm def test_recursive_assigned_retval_python_type() -> None: @@ -516,7 +508,7 @@ def retval_recursive() -> int[32] { int[32] __int_3__; __int_3__ = retval_recursive();""" - assert main().to_ir() == expected_qasm + assert main.to_ir() == expected_qasm def test_recursive_retval_expression_python_type() -> None: @@ -550,7 +542,7 @@ def retval_constant() -> int[32] { float[64] __float_4__; __float_4__ = retval_recursive();""" - assert caller().to_ir() == expected_qasm + assert caller.to_ir() == expected_qasm def test_recursive_oqpy_type() -> None: @@ -565,7 +557,7 @@ def retval_recursive() -> aq.BitVar: def main(): retval_recursive() - assert "-> bit" in main().to_ir() + assert "-> bit" in main.to_ir() def test_error_for_tuple_param() -> None: @@ -575,12 +567,11 @@ def test_error_for_tuple_param() -> None: def param_test(input: tuple): pass - @aq.main - def main(): - param_test(aq.BitVar(1)) - with pytest.raises(aq.errors.ParameterTypeError): - main() + + @aq.main + def main(): + param_test(aq.BitVar(1)) def test_error_for_missing_param_type() -> None: @@ -590,12 +581,11 @@ def test_error_for_missing_param_type() -> None: def param_test(input): pass - @aq.main - def main(): - param_test(aq.BitVar(1)) - with pytest.raises(aq.errors.MissingParameterTypeError): - main() + + @aq.main + def main(): + param_test(aq.BitVar(1)) def test_ignore_ret_typehint_bool(): @@ -617,7 +607,7 @@ def ret_test() -> bool { bool __bool_1__; __bool_1__ = ret_test();""" - assert main().to_ir() == expected + assert main.to_ir() == expected def test_ignore_ret_typehint_list(): @@ -627,12 +617,11 @@ def test_ignore_ret_typehint_list(): def ret_test() -> int: return [1, 2, 3] - @aq.main(num_qubits=4) - def main() -> float: - ret_test() - with pytest.raises(aq.errors.UnsupportedSubroutineReturnType): - main() + + @aq.main(num_qubits=4) + def main() -> float: + ret_test() def test_ignore_missing_ret_typehint_list(): @@ -642,12 +631,11 @@ def test_ignore_missing_ret_typehint_list(): def ret_test(): return [1, 2, 3] - @aq.main(num_qubits=4) - def main(): - ret_test() - with pytest.raises(aq.errors.UnsupportedSubroutineReturnType): - main() + + @aq.main(num_qubits=4) + def main(): + ret_test() def test_ignore_missing_ret_typehint_float(): @@ -670,7 +658,7 @@ def ret_test() -> float[64] { float[64] __float_1__; __float_1__ = ret_test();""" - assert main().to_ir() == expected + assert main.to_ir() == expected def test_param_array_list_missing_arg(): @@ -680,9 +668,8 @@ def test_param_array_list_missing_arg(): def param_test(arr: list) -> int: return 1 - @aq.main(num_qubits=4) - def main(): - param_test() - with pytest.raises(aq.errors.ParameterTypeError): - main() + + @aq.main(num_qubits=4) + def main(): + param_test() From ca581c06777d333694c3d85f4b38ff4cb9aa9bdf Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Mon, 4 Dec 2023 12:30:22 -0800 Subject: [PATCH 0963/1165] feat: support free parameters in aq range indices (#820) --- .../parametric/free_parameter_expression.py | 28 ++++--------- src/braket/pulse/ast/free_parameters.py | 41 ++++++++++--------- src/braket/pulse/ast/qasm_parser.py | 14 +++---- src/braket/pulse/pulse_sequence.py | 7 +--- .../experimental/autoqasm/test_parameters.py | 21 ++++++++++ .../braket/pulse/test_pulse_sequence.py | 12 +++--- 6 files changed, 64 insertions(+), 59 deletions(-) diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index 6b06063e..3bd4dfa3 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -106,7 +106,7 @@ def subs( """ new_parameter_values = dict() for key, val in parameter_values.items(): - if issubclass(type(key), FreeParameterExpression): + if isinstance(key, FreeParameterExpression): new_parameter_values[key.expression] = val else: new_parameter_values[key] = val @@ -139,7 +139,7 @@ def _eval_operation(self, node: Any) -> FreeParameterExpression: raise ValueError(f"Unsupported string detected: {node}") def __add__(self, other): - if issubclass(type(other), FreeParameterExpression): + if isinstance(other, FreeParameterExpression): return FreeParameterExpression(self.expression + other.expression) else: return FreeParameterExpression(self.expression + other) @@ -148,7 +148,7 @@ def __radd__(self, other): return FreeParameterExpression(other + self.expression) def __sub__(self, other): - if issubclass(type(other), FreeParameterExpression): + if isinstance(other, FreeParameterExpression): return FreeParameterExpression(self.expression - other.expression) else: return FreeParameterExpression(self.expression - other) @@ -157,7 +157,7 @@ def __rsub__(self, other): return FreeParameterExpression(other - self.expression) def __mul__(self, other): - if issubclass(type(other), FreeParameterExpression): + if isinstance(other, FreeParameterExpression): return FreeParameterExpression(self.expression * other.expression) else: return FreeParameterExpression(self.expression * other) @@ -166,7 +166,7 @@ def __rmul__(self, other): return FreeParameterExpression(other * self.expression) def __pow__(self, other, modulo=None): - if issubclass(type(other), FreeParameterExpression): + if isinstance(other, FreeParameterExpression): return FreeParameterExpression(self.expression**other.expression) else: return FreeParameterExpression(self.expression**other) @@ -200,21 +200,11 @@ def to_ast(self, program: Program) -> Expression: Returns: Expression: The AST node. """ + # TODO (#822): capture expressions into expression ASTs rather than just an Identifier + identifier = Identifier(name=self) if isinstance(self._type, DurationType): - return DurationLiteral(_FreeParameterExpressionIdentifier(self), TimeUnit.s) - return _FreeParameterExpressionIdentifier(self) - - -class _FreeParameterExpressionIdentifier(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 + return DurationLiteral(identifier, TimeUnit.s) + return identifier def subs_if_free_parameter(parameter: Any, **kwargs) -> Any: diff --git a/src/braket/pulse/ast/free_parameters.py b/src/braket/pulse/ast/free_parameters.py index 639645b2..96e319eb 100644 --- a/src/braket/pulse/ast/free_parameters.py +++ b/src/braket/pulse/ast/free_parameters.py @@ -18,10 +18,7 @@ from oqpy.program import Program from oqpy.timing import OQDurationLiteral -from braket.parametric.free_parameter_expression import ( - FreeParameterExpression, - _FreeParameterExpressionIdentifier, -) +from braket.parametric.free_parameter_expression import FreeParameterExpression class _FreeParameterTransformer(QASMTransformer): @@ -32,21 +29,27 @@ def __init__(self, param_values: dict[str, float], program: Program): self.program = program super().__init__() - def visit__FreeParameterExpressionIdentifier( - self, identifier: _FreeParameterExpressionIdentifier - ) -> Union[_FreeParameterExpressionIdentifier, ast.FloatLiteral]: - """Visit a FreeParameterExpressionIdentifier. + def visit_Identifier( + self, identifier: ast.Identifier + ) -> 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 (_FreeParameterExpressionIdentifier): The identifier. + identifier (Identifier): The identifier. Returns: - Union[_FreeParameterExpressionIdentifier, FloatLiteral]: The transformed expression. + Union[Identifier, 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) + if isinstance(identifier.name, FreeParameterExpression): + new_value = FreeParameterExpression(identifier.name).subs(self.param_values) + if isinstance(new_value, FreeParameterExpression): + return ast.Identifier(new_value) + else: + return ast.FloatLiteral(float(new_value)) + return identifier def visit_DurationLiteral(self, duration_literal: ast.DurationLiteral) -> ast.DurationLiteral: """Visit Duration Literal. @@ -58,11 +61,9 @@ def visit_DurationLiteral(self, duration_literal: ast.DurationLiteral) -> ast.Du DurationLiteral: The transformed duration literal. """ duration = duration_literal.value - if not isinstance(duration, _FreeParameterExpressionIdentifier): + if not isinstance(duration, ast.Identifier): return duration_literal - new_duration = duration.expression.subs(self.param_values) + new_duration = FreeParameterExpression(duration.name).subs(self.param_values) if isinstance(new_duration, FreeParameterExpression): - return ast.DurationLiteral( - _FreeParameterExpressionIdentifier(new_duration), duration_literal.unit - ) + return ast.DurationLiteral(ast.Identifier(str(new_duration)), duration_literal.unit) return OQDurationLiteral(new_duration).to_ast(self.program) diff --git a/src/braket/pulse/ast/qasm_parser.py b/src/braket/pulse/ast/qasm_parser.py index 1f5c48cc..e892ba86 100644 --- a/src/braket/pulse/ast/qasm_parser.py +++ b/src/braket/pulse/ast/qasm_parser.py @@ -18,8 +18,6 @@ from openqasm3.ast import DurationLiteral from openqasm3.printer import PrinterState -from braket.pulse.ast.free_parameters import _FreeParameterExpressionIdentifier - class _PulsePrinter(Printer): """Walks the AST and prints it to an OpenQASM3 string.""" @@ -27,15 +25,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)) + self.stream.write(str(node.name)) def visit_DurationLiteral(self, node: DurationLiteral, context: PrinterState) -> None: """Visit Duration Literal. @@ -46,8 +42,8 @@ def visit_DurationLiteral(self, node: DurationLiteral, context: PrinterState) -> context (PrinterState): The printer state context. """ duration = node.value - if isinstance(duration, _FreeParameterExpressionIdentifier): - self.stream.write(f"({duration.expression}) * 1{node.unit.name}") + if isinstance(duration, ast.Identifier): + self.stream.write(f"({duration.name}) * 1{node.unit.name}") else: super().visit_DurationLiteral(node, context) diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index 4e3bb06d..4fac2bde 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -26,10 +26,7 @@ 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 @@ -325,7 +322,7 @@ def _format_parameter_ast( self, parameter: Union[float, FreeParameterExpression], _type: ast.ClassicalType = ast.FloatType(), - ) -> Union[float, _FreeParameterExpressionIdentifier]: + ) -> Union[float, FreeParameterExpression]: if isinstance(parameter, FreeParameterExpression): for p in parameter.expression.free_symbols: self._free_parameters.add(FreeParameter(p.name)) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py index 66f9a4ab..0bf01a63 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py @@ -971,3 +971,24 @@ def parametric(phi): bit __bit_2__; __bit_2__ = measure __qubits__[0];""" assert parametric.to_ir() == expected + + +def test_parameter_expressions_range_index(): + """Test expressions of free parameters contained in a range index.""" + + @aq.main + def two_n_hs(n: int): + for _ in aq.range(2 * n): + h(0) + measure(0) + + expected = """OPENQASM 3.0; +input int[32] n; +qubit[1] __qubits__; +for int _ in [0:2*n - 1] { + h __qubits__[0]; +} +bit __bit_0__; +__bit_0__ = measure __qubits__[0];""" + assert two_n_hs.to_ir() == expected + _test_parametric_on_local_sim(two_n_hs, {"n": 3}) diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py index 4890a4b3..cbf57a98 100644 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -194,7 +194,7 @@ 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( @@ -206,11 +206,11 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined " 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[5s] predefined_frame_1, predefined_frame_2;", " delay[5s] predefined_frame_1;", From 8e8cdaa5cd1c62befafafd6621f8700ce64369f6 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 4 Dec 2023 16:44:08 -0800 Subject: [PATCH 0964/1165] Add Forte 1 device (#827) --- src/braket/devices/devices.py | 1 + test/integ_tests/test_cost_tracking.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/braket/devices/devices.py b/src/braket/devices/devices.py index 8f873033..f4fe8ff8 100644 --- a/src/braket/devices/devices.py +++ b/src/braket/devices/devices.py @@ -31,6 +31,7 @@ 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" Aria2 = "arn:aws:braket:us-east-1::device/qpu/ionq/Aria-2" + 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" diff --git a/test/integ_tests/test_cost_tracking.py b/test/integ_tests/test_cost_tracking.py index 60ddc1b5..7e97c6f7 100644 --- a/test/integ_tests/test_cost_tracking.py +++ b/test/integ_tests/test_cost_tracking.py @@ -20,9 +20,12 @@ 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", @@ -91,7 +94,7 @@ def test_all_devices_price_search(): tasks = {} for region in AwsDevice.REGIONS: s = AwsSession(boto3.Session(region_name=region)) - for device in devices: + for device in [device for device in devices if device.arn not in _RESERVATION_ONLY_DEVICES]: try: s.get_device(device.arn) From 8a8ef4ed00fed48c5a173fc28060a3ee9d530de4 Mon Sep 17 00:00:00 2001 From: Angela Guo Date: Tue, 5 Dec 2023 05:41:30 -0800 Subject: [PATCH 0965/1165] feat: Allow reservation ARN in task and job creation (#826) --- examples/hybrid_job.py | 9 +- examples/reservation.py | 24 +++++ src/braket/aws/aws_device.py | 15 ++- src/braket/aws/aws_quantum_job.py | 6 ++ src/braket/aws/aws_quantum_task.py | 32 ++++++- src/braket/aws/aws_quantum_task_batch.py | 13 +++ src/braket/jobs/hybrid_job.py | 6 ++ src/braket/jobs/quantum_job_creation.py | 18 ++++ src/braket/tracking/tracker.py | 18 +++- src/braket/tracking/tracking_events.py | 1 + test/integ_tests/test_reservation_arn.py | 91 +++++++++++++++++++ .../braket/aws/common_test_utils.py | 10 ++ test/unit_tests/braket/aws/test_aws_device.py | 63 +++++++++++-- .../braket/aws/test_aws_quantum_job.py | 10 +- .../braket/aws/test_aws_quantum_task.py | 60 ++++++++++++ .../braket/aws/test_aws_quantum_task_batch.py | 52 ++++++++++- .../unit_tests/braket/jobs/test_hybrid_job.py | 5 + .../braket/jobs/test_quantum_job_creation.py | 20 ++++ .../braket/tracking/test_tracker.py | 31 ++++++- 19 files changed, 463 insertions(+), 21 deletions(-) create mode 100644 examples/reservation.py create mode 100644 test/integ_tests/test_reservation_arn.py diff --git a/examples/hybrid_job.py b/examples/hybrid_job.py index 2e09d646..7f54c995 100644 --- a/examples/hybrid_job.py +++ b/examples/hybrid_job.py @@ -18,7 +18,14 @@ from braket.jobs.metrics import log_metric -@hybrid_job(device=Devices.Amazon.SV1, wait_until_complete=True) +@hybrid_job( + device=Devices.Amazon.SV1, + wait_until_complete=True, + # If you want to run the job in a device reservation window, + # change the device to the one you've reserved, + # uncomment the following line and fill in your reservation ARN + # reservation_arn="" +) def run_hybrid_job(num_tasks=1): # declare AwsDevice within the hybrid job device = AwsDevice(get_job_device_arn()) diff --git a/examples/reservation.py b/examples/reservation.py new file mode 100644 index 00000000..682f71f5 --- /dev/null +++ b/examples/reservation.py @@ -0,0 +1,24 @@ +# 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.aws import AwsDevice +from braket.circuits import Circuit +from braket.devices import Devices + +bell = Circuit().h(0).cnot(0, 1) +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") +print(task.result().measurement_counts) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 0cff446d..503834dd 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -121,6 +121,7 @@ def run( 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, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTask: @@ -150,6 +151,11 @@ def run( 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, + those tasks do not need to be created with the reservation ARN. + Default: None. Returns: AwsQuantumTask: An AwsQuantumTask that tracks the execution on the device. @@ -199,6 +205,7 @@ def run( poll_interval_seconds=poll_interval_seconds or self._poll_interval_seconds, inputs=inputs, gate_definitions=gate_definitions, + reservation_arn=reservation_arn, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) @@ -233,6 +240,7 @@ def run_batch( poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, inputs: Optional[Union[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, **aws_quantum_task_kwargs, ) -> AwsQuantumTaskBatch: @@ -263,7 +271,11 @@ def run_batch( gate_definitions (Optional[dict[tuple[Gate, QubitSet], PulseSequence]]): A `dict[tuple[Gate, QubitSet], PulseSequence]]` for a user defined gate calibration. The calibration is defined for a particular `Gate` on a particular `QubitSet` - and is represented by a `PulseSequence`. + and is represented by a `PulseSequence`. Default: None. + reservation_arn (Optional[str]): 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, + those tasks do not need to be created with the reservation ARN. Default: None. Returns: @@ -290,6 +302,7 @@ def run_batch( poll_interval_seconds=poll_interval_seconds or self._poll_interval_seconds, inputs=inputs, gate_definitions=gate_definitions, + reservation_arn=reservation_arn, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index 8ae4bc12..1e929e85 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__), + reservation_arn: str | None = None, ) -> AwsQuantumJob: """Creates a hybrid job by invoking the Braket CreateJob API. @@ -175,6 +176,10 @@ def create( while waiting for quantum task to be in a terminal state. Default is `getLogger(__name__)` + 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. + Returns: AwsQuantumJob: Hybrid job tracking the execution on Amazon Braket. @@ -201,6 +206,7 @@ def create( checkpoint_config=checkpoint_config, aws_session=aws_session, tags=tags, + reservation_arn=reservation_arn, ) job_arn = aws_session.create_job(**create_job_kwargs) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index fa58d911..c490a419 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, + reservation_arn: str | None = None, *args, **kwargs, ) -> AwsQuantumTask: @@ -151,6 +152,12 @@ def create( 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, + those tasks do not need to be created with the reservation ARN. + Default: None. + Returns: AwsQuantumTask: AwsQuantumTask tracking the quantum task execution on the device. @@ -179,6 +186,18 @@ def create( create_task_kwargs.update({"tags": tags}) inputs = inputs or {} + if reservation_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} unbounded_parameters = param_names - set(inputs.keys()) @@ -477,6 +496,12 @@ async def _wait_for_completion( self._result = None 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 + def _download_result( self, ) -> Union[ @@ -488,7 +513,12 @@ def _download_result( current_metadata["outputS3Directory"] + f"/{AwsQuantumTask.RESULTS_FILENAME}", ) self._result = _format_result(BraketSchemaBase.parse_raw_schema(result_string)) - task_event = {"arn": self.id, "status": self.state(), "execution_duration": None} + task_event = { + "arn": self.id, + "status": self.state(), + "execution_duration": None, + "has_reservation_arn": self._has_reservation_arn_from_metadata(current_metadata), + } try: task_event[ "execution_duration" diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index adf15bda..24ff1553 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -61,6 +61,7 @@ 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, + reservation_arn: str | None = None, *aws_quantum_task_args, **aws_quantum_task_kwargs, ): @@ -91,6 +92,11 @@ 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: {}. + 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, + those tasks do not need to be created with the reservation ARN. + Default: None. """ self._tasks = AwsQuantumTaskBatch._execute( aws_session, @@ -103,6 +109,7 @@ def __init__( poll_timeout_seconds, poll_interval_seconds, inputs, + reservation_arn, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) @@ -120,6 +127,7 @@ def __init__( self._poll_timeout_seconds = poll_timeout_seconds self._poll_interval_seconds = poll_interval_seconds self._inputs = inputs + self._reservation_arn = reservation_arn self._aws_quantum_task_args = aws_quantum_task_args self._aws_quantum_task_kwargs = aws_quantum_task_kwargs @@ -197,6 +205,7 @@ 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, + reservation_arn: str | None = None, *args, **kwargs, ) -> list[AwsQuantumTask]: @@ -217,6 +226,7 @@ def _execute( poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds, inputs=input_map, + reservation_arn=reservation_arn, *args, **kwargs, ) @@ -248,6 +258,7 @@ def _create_task( shots: int, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, inputs: dict[str, float] = None, + reservation_arn: str | None = None, *args, **kwargs, ) -> AwsQuantumTask: @@ -259,6 +270,7 @@ def _create_task( shots, poll_interval_seconds=poll_interval_seconds, inputs=inputs, + reservation_arn=reservation_arn, *args, **kwargs, ) @@ -347,6 +359,7 @@ def retry_unsuccessful_tasks(self) -> bool: self._max_workers, self._poll_timeout_seconds, self._poll_interval_seconds, + self._reservation_arn, *self._aws_quantum_task_args, **self._aws_quantum_task_kwargs, ) diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py index ae17c271..707f18fd 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__), + reservation_arn: str | None = None, ) -> Callable: """Defines a hybrid job by decorating the entry point function. The job will be created when the decorated function is called. @@ -152,6 +153,10 @@ 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__)` + 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. + Returns: Callable: the callable for creating a Hybrid Job. """ @@ -205,6 +210,7 @@ def job_wrapper(*args, **kwargs) -> Callable: "output_data_config": output_data_config, "aws_session": aws_session, "tags": tags, + "reservation_arn": reservation_arn, } for key, value in optional_args.items(): if value is not None: diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py index b935de7e..657ed082 100644 --- a/src/braket/jobs/quantum_job_creation.py +++ b/src/braket/jobs/quantum_job_creation.py @@ -55,6 +55,7 @@ def prepare_quantum_job( checkpoint_config: CheckpointConfig | None = None, aws_session: AwsSession | None = None, tags: dict[str, str] | None = None, + reservation_arn: str | None = None, ) -> dict: """Creates a hybrid job by invoking the Braket CreateJob API. @@ -140,6 +141,10 @@ def prepare_quantum_job( hybrid job. Default: {}. + 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. + Returns: dict: Hybrid job tracking the execution on Amazon Braket. @@ -174,6 +179,7 @@ def prepare_quantum_job( job_name, "script", ) + if AwsSession.is_s3_uri(source_module): _process_s3_source_module(source_module, entry_point, aws_session, code_location) else: @@ -230,6 +236,18 @@ def prepare_quantum_job( "tags": tags, } + if reservation_arn: + create_job_kwargs.update( + { + "associations": [ + { + "arn": reservation_arn, + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ] + } + ) + return create_job_kwargs diff --git a/src/braket/tracking/tracker.py b/src/braket/tracking/tracker.py index c30fc774..84b294ad 100644 --- a/src/braket/tracking/tracker.py +++ b/src/braket/tracking/tracker.py @@ -159,8 +159,12 @@ def quantum_tasks_statistics(self) -> dict[str, dict[str, Any]]: + details["execution_duration"] ) billed_duration = ( - device_stats.get("billed_execution_duration", timedelta(0)) - + details["billed_duration"] + timedelta(0) + if details.get("has_reservation_arn") + else ( + device_stats.get("billed_execution_duration", timedelta(0)) + + details["billed_duration"] + ) ) device_stats["execution_duration"] = duration @@ -196,14 +200,20 @@ def _(self, event: _TaskCompletionEvent) -> None: # 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"] = max(duration, MIN_SIMULATOR_DURATION) + resources[event.arn]["billed_duration"] = ( + timedelta(milliseconds=0) + if has_reservation_arn + else max(duration, MIN_SIMULATOR_DURATION) + ) def _get_qpu_task_cost(task_arn: str, details: dict) -> Decimal: - if details["status"] in ["FAILED", "CANCELLED"]: + if details["status"] in ["FAILED", "CANCELLED"] or details.get("has_reservation_arn"): return Decimal(0) task_region = task_arn.split(":")[3] diff --git a/src/braket/tracking/tracking_events.py b/src/braket/tracking/tracking_events.py index 6f37183c..793ff038 100644 --- a/src/braket/tracking/tracking_events.py +++ b/src/braket/tracking/tracking_events.py @@ -33,6 +33,7 @@ class _TaskCreationEvent(_TrackingEvent): class _TaskCompletionEvent(_TrackingEvent): execution_duration: Optional[float] status: str + has_reservation_arn: bool = False @dataclass diff --git a/test/integ_tests/test_reservation_arn.py b/test/integ_tests/test_reservation_arn.py new file mode 100644 index 00000000..e0736f80 --- /dev/null +++ b/test/integ_tests/test_reservation_arn.py @@ -0,0 +1,91 @@ +# 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 sys + +import pytest +from botocore.exceptions import ClientError +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 + + +@pytest.fixture +def reservation_arn(aws_session): + return ( + f"arn:aws:braket:{aws_session.region}" + f":{aws_session.account_id}:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ) + + +def test_create_task_via_invalid_reservation_arn_on_qpu(reservation_arn): + circuit = Circuit().h(0) + device = AwsDevice(Devices.IonQ.Harmony) + + with pytest.raises(ClientError, match="Reservation arn is invalid"): + device.run( + circuit, + shots=10, + reservation_arn=reservation_arn, + ) + + +def test_create_task_via_reservation_arn_on_simulator(reservation_arn): + circuit = Circuit().h(0) + 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, + ) + + +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, + reason="Python version mismatch", +) +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, + reservation_arn=reservation_arn, + ) + def hello_job(): + device = AwsDevice(get_job_device_arn()) + bell = Circuit().h(0).cnot(0, 1) + task = device.run(bell, shots=10) + measurements = task.result().measurements + return measurements + + hello_job() diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 5dcec5fb..2975912f 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -201,6 +201,7 @@ def run_and_assert( poll_interval_seconds, # Treated as positional arg inputs, # Treated as positional arg gate_definitions, # Treated as positional arg + reservation_arn, # Treated as positional arg extra_args, extra_kwargs, ): @@ -222,6 +223,8 @@ def run_and_assert( run_args.append(gate_definitions) run_args += extra_args if extra_args else [] run_kwargs = extra_kwargs or {} + if reservation_arn: + run_kwargs.update({"reservation_arn": reservation_arn}) task = device.run(circuit, *run_args, **run_kwargs) assert task == task_mock @@ -237,6 +240,7 @@ def run_and_assert( poll_interval_seconds, inputs, gate_definitions, + reservation_arn, extra_args, extra_kwargs, ) @@ -263,6 +267,7 @@ def run_batch_and_assert( poll_interval_seconds, inputs, gate_definitions, + reservation_arn, extra_args, extra_kwargs, ): @@ -291,6 +296,8 @@ def run_batch_and_assert( run_args.append(gate_definitions) run_args += extra_args if extra_args else [] run_kwargs = extra_kwargs or {} + if reservation_arn: + run_kwargs.update({"reservation_arn": reservation_arn}) batch = device.run_batch(circuits, *run_args, **run_kwargs) assert batch.tasks == [task_mock for _ in range(len(circuits))] @@ -306,6 +313,7 @@ def run_batch_and_assert( poll_interval_seconds, inputs, gate_definitions, + reservation_arn, extra_args, extra_kwargs, ) @@ -333,6 +341,7 @@ def _create_task_args_and_kwargs( poll_interval_seconds, inputs, gate_definitions, + reservation_arn, extra_args, extra_kwargs, ): @@ -352,6 +361,7 @@ def _create_task_args_and_kwargs( 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 3770d38a..29adf251 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -987,6 +987,16 @@ def test_run_no_extra(aws_quantum_task_mock, device, circuit): ) +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_with_reservation_arn(aws_quantum_task_mock, device, circuit): + _run_and_assert( + aws_quantum_task_mock, + device, + circuit, + reservation_arn="arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9", + ) + + @patch("braket.aws.aws_quantum_task.AwsQuantumTask") def test_run_param_circuit_with_no_inputs( aws_quantum_task_mock, single_circuit_input, device, s3_destination_folder @@ -1024,6 +1034,38 @@ def test_run_param_circuit_with_inputs( ) +@patch("braket.aws.aws_session.boto3.Session") +@patch("braket.aws.aws_session.AwsSession") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_param_circuit_with_reservation_arn_batch_task( + aws_quantum_task_mock, + aws_session_mock, + boto_session_mock, + single_circuit_input, + device, + s3_destination_folder, +): + inputs = {"theta": 0.2} + circ_1 = Circuit().rx(angle=0.2, target=0) + circuits = [circ_1, single_circuit_input] + + _run_batch_and_assert( + aws_quantum_task_mock, + aws_session_mock, + device, + circuits, + s3_destination_folder, + 10, + 20, + 50, + 43200, + 0.25, + inputs, + None, + reservation_arn="arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9", + ) + + @patch("braket.aws.aws_session.boto3.Session") @patch("braket.aws.aws_session.AwsSession") @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") @@ -1308,13 +1350,14 @@ def test_default_bucket_not_called(aws_quantum_task_mock, device, circuit, s3_de AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, circuit, s3_destination_folder, - None, - None, - None, - None, - None, - None, - None, + shots=None, + poll_timeout_seconds=None, + poll_interval_seconds=None, + inputs=None, + gate_definitions=None, + reservation_arn=None, + extra_args=None, + extra_kwargs=None, ) device._aws_session.default_bucket.assert_not_called() @@ -1375,6 +1418,8 @@ def test_run_with_positional_args_and_kwargs( 0.25, {}, ["foo"], + "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9", + None, {"bar": 1, "baz": 2}, ) @@ -1489,6 +1534,7 @@ def _run_and_assert( poll_interval_seconds=None, # Treated as positional arg inputs=None, # Treated as positional arg gate_definitions=None, # Treated as positional arg + reservation_arn=None, # Treated as positional arg extra_args=None, extra_kwargs=None, ): @@ -1506,6 +1552,7 @@ def _run_and_assert( poll_interval_seconds, inputs, gate_definitions, + reservation_arn, extra_args, extra_kwargs, ) @@ -1524,6 +1571,7 @@ def _run_batch_and_assert( poll_interval_seconds=None, # Treated as positional arg inputs=None, # Treated as positional arg gate_definitions=None, # Treated as positional arg + reservation_arn=None, # Treated as positional arg extra_args=None, extra_kwargs=None, ): @@ -1544,6 +1592,7 @@ def _run_batch_and_assert( poll_interval_seconds, inputs, gate_definitions, + reservation_arn, extra_args, extra_kwargs, ) 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 19b46d72..3a36d8e7 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_job.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py @@ -516,7 +516,12 @@ def device_arn(request): @pytest.fixture -def prepare_job_args(aws_session, device_arn): +def reservation_arn(): + return "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + + +@pytest.fixture +def prepare_job_args(aws_session, device_arn, reservation_arn): return { "device": device_arn, "source_module": Mock(), @@ -535,6 +540,7 @@ def prepare_job_args(aws_session, device_arn): "checkpoint_config": Mock(), "aws_session": aws_session, "tags": Mock(), + "reservation_arn": reservation_arn, } @@ -1027,7 +1033,7 @@ def test_initialize_session_local_device(mock_new_session, aws_session): assert AwsQuantumJob._initialize_session(None, device, logger) == mock_new_session() -def test_bad_arn_format(aws_session): +def test_bad_device_arn_format(aws_session): logger = logging.getLogger(__name__) device_not_found = ( "Device ARN is not a valid format: bad-arn-format. For valid Braket ARNs, " 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 99270ad2..656c37dc 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -203,6 +203,29 @@ def test_metadata_call_if_none(quantum_task): quantum_task._aws_session.get_quantum_task.assert_called_with(quantum_task.id) +def test_has_reservation_arn_from_metadata(quantum_task): + metadata_true = { + "associations": [ + { + "arn": "123", + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ] + } + assert quantum_task._has_reservation_arn_from_metadata(metadata_true) + + metadata_false = { + "status": "RUNNING", + "associations": [ + { + "arn": "123", + "type": "other", + } + ], + } + assert not quantum_task._has_reservation_arn_from_metadata(metadata_false) + + def test_queue_position(quantum_task): state_1 = "QUEUED" _mock_metadata(quantum_task._aws_session, state_1) @@ -560,6 +583,31 @@ def test_create_ahs_problem(aws_session, arn, ahs_problem): ) +def test_create_task_with_reservation_arn(aws_session, arn, ahs_problem): + aws_session.create_quantum_task.return_value = arn + shots = 21 + reservation_arn = ( + "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ) + AwsQuantumTask.create( + aws_session, + SIMULATOR_ARN, + ahs_problem, + S3_TARGET, + shots, + reservation_arn=reservation_arn, + ) + + _assert_create_quantum_task_called_with( + aws_session, + SIMULATOR_ARN, + ahs_problem.to_ir().json(), + S3_TARGET, + shots, + reservation_arn=reservation_arn, + ) + + def test_create_pulse_sequence(aws_session, arn, pulse_sequence): expected_openqasm = "\n".join( [ @@ -1098,6 +1146,7 @@ def _assert_create_quantum_task_called_with( shots, device_parameters=None, tags=None, + reservation_arn=None, ): test_kwargs = { "deviceArn": arn, @@ -1111,6 +1160,17 @@ def _assert_create_quantum_task_called_with( test_kwargs.update({"deviceParameters": device_parameters.json(exclude_none=True)}) if tags is not None: test_kwargs.update({"tags": tags}) + if reservation_arn: + test_kwargs.update( + { + "associations": [ + { + "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_quantum_task_batch.py b/test/unit_tests/braket/aws/test_aws_quantum_task_batch.py index 5802db11..2c748ece 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 @@ -34,7 +34,16 @@ def test_creation(mock_create): batch_size = 10 batch = AwsQuantumTaskBatch( - Mock(), "foo", _circuits(batch_size), S3_TARGET, 1000, max_parallel=10 + Mock(), + "foo", + _circuits(batch_size), + S3_TARGET, + 1000, + max_parallel=10, + reservaion_arn=( + "arn:aws:braket:us-west-2:123456789123:" + "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ), ) assert batch.size == batch_size assert batch.tasks == [task_mock for _ in range(batch_size)] @@ -53,7 +62,16 @@ def test_successful(mock_create): batch_size = 15 batch = AwsQuantumTaskBatch( - Mock(), "foo", _circuits(batch_size), S3_TARGET, 1000, max_parallel=10 + Mock(), + "foo", + _circuits(batch_size), + S3_TARGET, + 1000, + max_parallel=10, + reservaion_arn=( + "arn:aws:braket:us-west-2:123456789123:" + "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ), ) assert batch.size == batch_size assert not batch.unfinished @@ -71,7 +89,16 @@ def test_unsuccessful(mock_create): mock_create.return_value = task_mock batch = AwsQuantumTaskBatch( - Mock(), "foo", [Circuit().h(0).cnot(0, 1)], S3_TARGET, 1000, max_parallel=10 + Mock(), + "foo", + [Circuit().h(0).cnot(0, 1)], + S3_TARGET, + 1000, + max_parallel=10, + reservaion_arn=( + "arn:aws:braket:us-west-2:123456789123:" + "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ), ) assert not batch.unfinished assert batch.unsuccessful == {task_id} @@ -106,6 +133,10 @@ def test_retry(mock_create): S3_TARGET, 1000, max_parallel=10, + reservaion_arn=( + "arn:aws:braket:us-west-2:123456789123:" + "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ), ) assert not batch.unfinished assert batch.results(max_retries=0) == [None, result] @@ -142,6 +173,10 @@ def test_abort(mock_threadpool): S3_TARGET, 1000, max_parallel=num_workers, + reservaion_arn=( + "arn:aws:braket:us-west-2:123456789123:" + "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ), ) @@ -153,7 +188,16 @@ def test_early_abort(mock_submit): with pytest.raises(KeyboardInterrupt): AwsQuantumTaskBatch( - Mock(), "foo", _circuits(batch_size), S3_TARGET, 1000, max_parallel=num_workers + Mock(), + "foo", + _circuits(batch_size), + S3_TARGET, + 1000, + max_parallel=num_workers, + reservaion_arn=( + "arn:aws:braket:us-west-2:123456789123:" + "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ), ) diff --git a/test/unit_tests/braket/jobs/test_hybrid_job.py b/test/unit_tests/braket/jobs/test_hybrid_job.py index b7b7485d..e757c6a6 100644 --- a/test/unit_tests/braket/jobs/test_hybrid_job.py +++ b/test/unit_tests/braket/jobs/test_hybrid_job.py @@ -105,6 +105,9 @@ def test_decorator_non_defaults( output_data_config = OutputDataConfig(s3Path="s3") aws_session = MagicMock() tags = {"my_tag": "my_value"} + reservation_arn = ( + "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ) logger = getLogger(__name__) with tempfile.TemporaryDirectory() as tempdir: @@ -135,6 +138,7 @@ def test_decorator_non_defaults( output_data_config=output_data_config, aws_session=aws_session, tags=tags, + reservation_arn=reservation_arn, logger=logger, ) def my_entry(a, b: int, c=0, d: float = 1.0, **extras) -> str: @@ -184,6 +188,7 @@ def my_entry(a, b: int, c=0, d: float = 1.0, **extras) -> str: aws_session=aws_session, tags=tags, logger=logger, + reservation_arn=reservation_arn, ) included_module = importlib.import_module("job_module") mock_register.assert_called_with(included_module) 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 1b2c6b5b..bef4fd64 100644 --- a/test/unit_tests/braket/jobs/test_quantum_job_creation.py +++ b/test/unit_tests/braket/jobs/test_quantum_job_creation.py @@ -175,6 +175,11 @@ def checkpoint_config(bucket, s3_prefix): ) +@pytest.fixture +def reservation_arn(): + return "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + + @pytest.fixture def generate_get_job_response(): def _get_job_response(**kwargs): @@ -247,6 +252,7 @@ def create_job_args( output_data_config, checkpoint_config, tags, + reservation_arn, ): if request.param == "fixtures": return dict( @@ -268,6 +274,7 @@ def create_job_args( "checkpoint_config": checkpoint_config, "aws_session": aws_session, "tags": tags, + "reservation_arn": reservation_arn, }.items() if value is not None ) @@ -325,6 +332,7 @@ def _translate_creation_args(create_job_args): hyperparameters = {str(key): str(value) for key, value in hyperparameters.items()} input_data = create_job_args["input_data"] or {} instance_config = create_job_args["instance_config"] or InstanceConfig() + reservation_arn = create_job_args["reservation_arn"] if create_job_args["distribution"] == "data_parallel": distributed_hyperparams = { "sagemaker_distributed_dataparallel_enabled": "true", @@ -367,6 +375,18 @@ def _translate_creation_args(create_job_args): "tags": tags, } + if reservation_arn: + test_kwargs.update( + { + "associations": [ + { + "arn": reservation_arn, + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ] + } + ) + return test_kwargs diff --git a/test/unit_tests/braket/tracking/test_tracker.py b/test/unit_tests/braket/tracking/test_tracker.py index fc660b80..a97ce57b 100644 --- a/test/unit_tests/braket/tracking/test_tracker.py +++ b/test/unit_tests/braket/tracking/test_tracker.py @@ -85,6 +85,18 @@ def test_receive_fake_event(empty_tracker): _TaskCreationEvent( arn="no_price:::region", shots=1000, is_job_task=False, device="something_else" ), + _TaskCreationEvent( + arn="unbilled_task0:::region", + shots=100, + is_job_task=True, + device="qpu/foo", + ), + _TaskCreationEvent( + arn="unbilled_task1:::region", + shots=100, + is_job_task=True, + device="qpu/foo", + ), ] GET_EVENTS = [ @@ -101,6 +113,18 @@ def test_receive_fake_event(empty_tracker): ), _TaskCompletionEvent(arn="task_fail:::region", execution_duration=12345, status="FAILED"), _TaskCompletionEvent(arn="task_cancel:::region", execution_duration=None, status="CANCELLED"), + _TaskCompletionEvent( + arn="unbilled_task0:::region", + execution_duration=123, + status="COMPLETED", + has_reservation_arn=True, + ), + _TaskCompletionEvent( + arn="unbilled_task1:::region", + execution_duration=123, + status="COMPLETED", + has_reservation_arn=True, + ), ] @@ -174,7 +198,12 @@ def test_simulator_task_cost(price_mock, completed_tracker): def test_quantum_task_statistics(completed_tracker): stats = completed_tracker.quantum_tasks_statistics() expected = { - "qpu/foo": {"shots": 200, "tasks": {"COMPLETED": 1, "FAILED": 1}}, + "qpu/foo": { + "shots": 400, + "tasks": {"COMPLETED": 3, "FAILED": 1}, + "execution_duration": timedelta(microseconds=246000), + "billed_execution_duration": timedelta(0), + }, "simulator/bar": { "shots": 1000, "tasks": {"COMPLETED": 2, "CREATED": 1}, From c6ff90fc297d47ae9a7af2b0131948f2a0119269 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 5 Dec 2023 18:17:00 +0000 Subject: [PATCH 0966/1165] prepare release v1.63.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 876595e2..7f351429 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.63.0 (2023-12-05) + +### Features + + * Allow reservation ARN in task and job creation + +### Bug Fixes and Other Changes + + * Add Forte 1 device + ## v1.62.1 (2023-11-17) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index c0e18d0a..f9e931ce 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.62.2.dev0" +__version__ = "1.63.0" From 75e0bfd20abe69577248da749936dc29633426ab Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 5 Dec 2023 18:17:00 +0000 Subject: [PATCH 0967/1165] update development version to v1.63.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 f9e931ce..70ca418c 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.0" +__version__ = "1.63.1.dev0" From aea53ae66f9dc23e78cdd31aaf30654fc21c005b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 12:30:14 -0800 Subject: [PATCH 0968/1165] infra: bump pypa/gh-action-pypi-publish from 1.8.10 to 1.8.11 (#825) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.10 to 1.8.11. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/b7f401de30cb6434a1e19f805ff006643653240e...2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf) --- 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> Co-authored-by: Abe Coull <85974725+math411@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 10aef9c2..e4d4e68c 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@b7f401de30cb6434a1e19f805ff006643653240e # release/v1 + uses: pypa/gh-action-pypi-publish@2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf # release/v1 with: password: ${{ secrets.pypi_token }} 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 0969/1165] 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 00000000..bd545d94 --- /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 0970/1165] 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 5aff0abc..d19f5b48 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 0971/1165] 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 814444e7..b6ce11bc 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 023494fa..166e7c8f 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 0972/1165] 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 7f351429..c8c50a7d 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 70ca418c..38e0376e 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 0973/1165] 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 38e0376e..156b961b 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 0974/1165] 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 94f28ec7..0a6ad7c4 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 f1577748..86ec939e 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 0975/1165] 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 0a6ad7c4..3bad103a 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 f4bef51a8db27d3800a27a9c392e35a6ff320598 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Mon, 11 Dec 2023 10:22:57 -0800 Subject: [PATCH 0976/1165] feat: qasm highlighting with display (#831) --- setup.py | 1 + .../experimental/autoqasm/program/program.py | 14 +++++++ .../experimental/autoqasm/test_program.py | 37 +++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/setup.py b/setup.py index 82153cf6..a8447a2f 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,7 @@ "astunparse", "gast", "termcolor", + "openqasm_pygments", ], extras_require={ "test": [ diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 55a9fb26..7a91c9ac 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -23,6 +23,9 @@ from typing import Any, Optional, Union import oqpy.base +import pygments +from openqasm_pygments import OpenQASM3Lexer +from pygments.formatters.terminal import TerminalFormatter from sympy import Symbol import braket.experimental.autoqasm.types as aq_types @@ -186,6 +189,17 @@ def to_ir( raise ValueError(f"Supplied ir_type {ir_type} is not supported.") + def display(self, ir_type: IRType = IRType.OPENQASM) -> None: + """ + Print the Program with syntax highlighting. Returns `None` to avoid + duplicate printing when used with `print(program.display())`. + + Args: + ir_type (IRType): The IRType to use for displaying the program. + Defaults to IRType.OPENQASM. + """ + print(pygments.highlight(self.to_ir(ir_type), OpenQASM3Lexer(), TerminalFormatter())) + class GateArgs: """Represents a list of qubit and angle arguments for a gate definition.""" diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/braket/experimental/autoqasm/test_program.py index 116330a5..d3d91ee7 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_program.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_program.py @@ -15,6 +15,7 @@ import itertools from multiprocessing.pool import ThreadPool +from unittest.mock import patch import oqpy.base import pytest @@ -123,3 +124,39 @@ def circuit(float[64] angle) { for i, (scale, angle) in enumerate(itertools.product(scales, angles)): assert programs[i].to_ir() == expected(scale, angle) + + +@patch("builtins.print") +def test_to_ir_highlighted(mock_print): + @aq.subroutine + def sub(q0: int): + if aq.instructions.measure(q0): + aq.instructions.x(1) + + @aq.main(num_qubits=3) + def teleportation(theta): + sub(0) + aq.instructions.rx(1, theta) + + expected = ( + b"\x1b[36mOPENQASM\x1b[39;49;00m\x1b[37m \x1b[39;49;00m3.0;\x1b[37m\x1b[39;49;00m\n" + b"\x1b[34mdef\x1b[39;49;00m\x1b[37m \x1b[39;49;00m\x1b[32msub\x1b[39;49;00m(\x1b[36mint" + b"\x1b[39;49;00m[\x1b[34m32\x1b[39;49;00m]\x1b[37m \x1b[39;49;00mq0)\x1b[37m " + b"\x1b[39;49;00m{\x1b[37m\x1b[39;49;00m\n\x1b[37m \x1b[39;49;00m\x1b[36mbit" + b"\x1b[39;49;00m\x1b[37m \x1b[39;49;00m__bit_0__;\x1b[37m\x1b[39;49;00m\n\x1b[37m" + b" \x1b[39;49;00m__bit_0__\x1b[37m \x1b[39;49;00m=\x1b[37m \x1b[39;49;00m" + b"\x1b[35mmeasure\x1b[39;49;00m\x1b[37m \x1b[39;49;00m__qubits__[q0];\x1b[37m" + b"\x1b[39;49;00m\n\x1b[37m \x1b[39;49;00m\x1b[34mif\x1b[39;49;00m\x1b[37m " + b"\x1b[39;49;00m(__bit_0__)\x1b[37m \x1b[39;49;00m{\x1b[37m\x1b[39;49;00m\n\x1b[37m" + b" \x1b[39;49;00m\x1b[32mx\x1b[39;49;00m\x1b[37m \x1b[39;49;00m__qubits__[" + b"\x1b[34m1\x1b[39;49;00m];\x1b[37m\x1b[39;49;00m\n\x1b[37m \x1b[39;49;00m}" + b"\x1b[37m\x1b[39;49;00m\n}\x1b[37m\x1b[39;49;00m\n\x1b[36minput\x1b[39;49;00m\x1b[37m " + b"\x1b[39;49;00m\x1b[36mfloat\x1b[39;49;00m[\x1b[34m64\x1b[39;49;00m]\x1b[37m " + b"\x1b[39;49;00mtheta;\x1b[37m\x1b[39;49;00m\n\x1b[36mqubit\x1b[39;49;00m[" + b"\x1b[34m3\x1b[39;49;00m]\x1b[37m \x1b[39;49;00m__qubits__;\x1b[37m\x1b[39;49;00m\n" + b"\x1b[32msub\x1b[39;49;00m(\x1b[34m0\x1b[39;49;00m);\x1b[37m\x1b[39;49;00m\n\x1b[32mrx" + b"\x1b[39;49;00m(theta)\x1b[37m \x1b[39;49;00m__qubits__[\x1b[34m1\x1b[39;49;00m];" + b"\x1b[37m\x1b[39;49;00m\n" + ).decode("utf-8") + teleportation.display() + mock_print.assert_called_with(expected) 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 0977/1165] 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 dbe9599c..1795135e 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 cfd39524..aca79d59 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 e4d4e68c..b12cde75 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 0c02a756..56ba81bf 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 f8e01fd7..2c30b8ec 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 0978/1165] 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 56ba81bf..42dda202 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 0979/1165] 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 6cbdd97d..57013df4 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 31c2384d..c95ce74a 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 0980/1165] 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 c8c50a7d..64c04fee 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 156b961b..1f45b0dd 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 0981/1165] 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 1f45b0dd..d63d5913 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 91ab4f5eae4cfe80c0587cc20dd0ed36cb1703e2 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Tue, 12 Dec 2023 14:23:05 -0800 Subject: [PATCH 0982/1165] docs: update main decorator type annotation (#835) --- src/braket/experimental/autoqasm/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 7a80b360..89ebb13f 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -50,7 +50,7 @@ def main( *, num_qubits: Optional[int] = None, device: Optional[Union[Device, str]] = None, -) -> aq_program.Program | functools.partial: +) -> aq_program.Program | Callable[..., aq_program.Program]: """Decorator that converts a function into a Program object containing the quantum program. The decorator re-converts the target function whenever the decorated @@ -65,8 +65,8 @@ def main( program. Can be either an Device object or a valid Amazon Braket device ARN. Returns: - Program | partial: The Program object containing the converted quantum program, or a - partial function of the `main` decorator. + Program | Callable[..., Program]: The Program object containing the converted quantum + program, or a partial function of the `main` decorator. """ if isinstance(device, str): device = AwsDevice(device) From 568bf9d89971298d4a8ba43b0a0e1e626bfeb17b Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Fri, 15 Dec 2023 08:13:38 -0500 Subject: [PATCH 0983/1165] feature: Support OpenQASM annotations in AutoQASM programs (#838) --- src/braket/experimental/autoqasm/__init__.py | 2 +- src/braket/experimental/autoqasm/api.py | 25 +++- .../experimental/autoqasm/program/pragmas.py | 11 +- .../experimental/autoqasm/program/program.py | 18 ++- .../experimental/autoqasm/types/__init__.py | 3 +- .../experimental/autoqasm/types/types.py | 71 ++++++--- .../experimental/autoqasm/test_annotations.py | 141 ++++++++++++++++++ .../experimental/autoqasm/test_operators.py | 2 +- .../experimental/autoqasm/test_types.py | 10 +- 9 files changed, 241 insertions(+), 42 deletions(-) create mode 100644 test/unit_tests/braket/experimental/autoqasm/test_annotations.py diff --git a/src/braket/experimental/autoqasm/__init__.py b/src/braket/experimental/autoqasm/__init__.py index 9efb0852..adc69a3b 100644 --- a/src/braket/experimental/autoqasm/__init__.py +++ b/src/braket/experimental/autoqasm/__init__.py @@ -45,4 +45,4 @@ def my_program(): from .program import Program, build_program, verbatim # noqa: F401 from .transpiler import transpiler # noqa: F401 from .types import ArrayVar, BitVar, BoolVar, FloatVar, IntVar # noqa: F401 -from .types import qasm_range as range # noqa: F401 +from .types import Range as range # noqa: F401 diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 89ebb13f..1ed1324f 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -20,7 +20,7 @@ import inspect from collections.abc import Callable from types import FunctionType -from typing import Any, Optional, Union, get_args +from typing import Any, Iterable, Optional, Union, get_args import openqasm3.ast as qasm_ast import oqpy.base @@ -94,19 +94,28 @@ def main( return program_builder() -def subroutine(func: Optional[Callable] = None) -> Callable[..., aq_program.Program]: +def subroutine( + func: Optional[Callable] = None, annotations: Optional[str | Iterable[str]] = None +) -> Callable[..., aq_program.Program]: """Decorator that converts a function into a callable that will insert a subroutine into the quantum program. Args: func (Optional[Callable]): Decorated function. May be `None` in the case where decorator is used with parentheses. + annotations (Optional[str | Iterable[str]]): Annotations to be added to the subroutine. Returns: Callable[..., Program]: A callable which returns the converted quantum program when called. """ - return _function_wrapper(func, converter_callback=_convert_subroutine) + return _function_wrapper( + func, + converter_callback=_convert_subroutine, + converter_args={ + "annotations": aq_types.make_annotations_list(annotations), + }, + ) def gate(func: Optional[Callable] = None) -> Callable[..., None]: @@ -305,6 +314,7 @@ def _add_qubit_declaration(program_conversion_context: aq_program.ProgramConvers def _convert_subroutine( f: Callable, options: converter.ConversionOptions, + annotations: Iterable[str], args: list[Any], kwargs: dict[str, Any], ) -> None: @@ -317,6 +327,7 @@ def _convert_subroutine( Args: f (Callable): The function to be converted. options (converter.ConversionOptions): Converter options. + annotations (Iterable[str]): Annotations to be added to the subroutine. args (list[Any]): Arguments passed to the program when called. kwargs (dict[str, Any]): Keyword arguments passed to the program when called. """ @@ -336,7 +347,9 @@ def _convert_subroutine( # Convert the function via autograph into an oqpy subroutine # NOTE: Process a clone of the function so that we don't modify the original object - oqpy_sub = oqpy.subroutine(_wrap_for_oqpy_subroutine(_clone_function(f), options)) + oqpy_sub = oqpy.subroutine(annotations=annotations)( + _wrap_for_oqpy_subroutine(_clone_function(f), options) + ) # Process the program subroutine_function_call = oqpy_sub(oqpy_program, *args, **kwargs) @@ -347,7 +360,9 @@ def _convert_subroutine( else: # Convert the function via autograph into an oqpy subroutine # NOTE: Recursive call; process a dummy version of the function instead - oqpy_sub = oqpy.subroutine(_wrap_for_oqpy_subroutine(_dummy_function(f), options)) + oqpy_sub = oqpy.subroutine(annotations=annotations)( + _wrap_for_oqpy_subroutine(_dummy_function(f), options) + ) # Process the program subroutine_function_call = oqpy_sub(oqpy_program, *args, **kwargs) diff --git a/src/braket/experimental/autoqasm/program/pragmas.py b/src/braket/experimental/autoqasm/program/pragmas.py index ee952f1c..0fdf52e7 100644 --- a/src/braket/experimental/autoqasm/program/pragmas.py +++ b/src/braket/experimental/autoqasm/program/pragmas.py @@ -28,9 +28,11 @@ def pragma_example() -> None: The verbatim pragma would then apply to the `h` and `cnot`, but not the `x`. """ +from __future__ import annotations import contextlib from enum import Enum +from typing import Iterable, Optional from braket.device_schema import DeviceActionType from braket.experimental.autoqasm import errors, program @@ -44,13 +46,16 @@ class PragmaType(str, Enum): @contextlib.contextmanager -def verbatim() -> None: +def verbatim(annotations: Optional[str | Iterable[str]] = None) -> None: """Context management protocol that, when used with a `with` statement, wraps the code block in a verbatim block. A verbatim block specifies that operations contained within the block are to be executed as programmed without compilation or modification of any sort. + Args: + annotations (Optional[str | Iterable[str]]): Annotations for the box. + Raises: errors.VerbatimBlockNotAllowed: If a verbatim block is not allowed at this point in the program; for example, if the target device does not support verbatim blocks. @@ -69,7 +74,9 @@ def verbatim() -> None: ) try: - with program.get_program_conversion_context().box(pragma=PragmaType.VERBATIM): + with program.get_program_conversion_context().box( + pragma=PragmaType.VERBATIM, annotations=annotations + ): program_conversion_context.in_verbatim_block = True yield finally: diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 7a91c9ac..298f7140 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -597,6 +597,11 @@ def _control_flow_block( finally: self.at_function_root_scope = original + def _add_annotations(self, annotations: Optional[str | Iterable[str]] = None) -> None: + oqpy_program = self.get_oqpy_program() + for annotation in aq_types.make_annotations_list(annotations): + oqpy_program.annotate(annotation) + def if_block(self, condition: Any) -> contextlib._GeneratorContextManager: """Sets the program conversion context into an if block context. @@ -620,18 +625,19 @@ def else_block(self) -> contextlib._GeneratorContextManager: return self._control_flow_block(oqpy.Else(oqpy_program)) def for_in( - self, iterator: oqpy.Range, iterator_name: Optional[str] + self, iterator: aq_types.Range, iterator_name: Optional[str] ) -> contextlib._GeneratorContextManager: """Sets the program conversion context into a for loop context. Args: - iterator (oqpy.Range): The iterator of the for loop. + iterator (Range): The iterator of the for loop. iterator_name (Optional[str]): The symbol to use as the name of the iterator. Yields: _GeneratorContextManager: The context manager of the oqpy.ForIn block. """ oqpy_program = self.get_oqpy_program() + self._add_annotations(iterator.annotations) return self._control_flow_block(oqpy.ForIn(oqpy_program, iterator, iterator_name)) def while_loop(self, condition: Any) -> contextlib._GeneratorContextManager: @@ -694,15 +700,21 @@ def calibration_definition( self._calibration_definitions_processing.pop() @contextlib.contextmanager - def box(self, pragma: Optional[str] = None) -> None: + def box( + self, + pragma: Optional[str] = None, + annotations: Optional[str | Iterable[str]] = None, + ) -> None: """Sets the program conversion context into a box context. Args: pragma (Optional[str]): Pragma to include before the box. Defaults to None. + annotations (Optional[str | Iterable[str]]): Annotations for the box. """ oqpy_program = self.get_oqpy_program() if pragma: oqpy_program.pragma(pragma) + self._add_annotations(annotations) with oqpy.Box(oqpy_program): yield diff --git a/src/braket/experimental/autoqasm/types/__init__.py b/src/braket/experimental/autoqasm/types/__init__.py index 999973e9..d6d770da 100644 --- a/src/braket/experimental/autoqasm/types/__init__.py +++ b/src/braket/experimental/autoqasm/types/__init__.py @@ -22,6 +22,7 @@ BoolVar, FloatVar, IntVar, + Range, is_qasm_type, - qasm_range, + make_annotations_list, ) diff --git a/src/braket/experimental/autoqasm/types/types.py b/src/braket/experimental/autoqasm/types/types.py index 740d76b2..9f578d7c 100644 --- a/src/braket/experimental/autoqasm/types/types.py +++ b/src/braket/experimental/autoqasm/types/types.py @@ -13,7 +13,10 @@ """AutoQASM types and type utilities.""" -from typing import Any, Optional +from __future__ import annotations + +from collections.abc import Iterable +from typing import Any, List, Optional import oqpy import oqpy.base @@ -41,25 +44,35 @@ def is_qasm_type(val: Any) -> bool: return isinstance(val, qasm_types) -def qasm_range(start: int, stop: Optional[int] = None, step: Optional[int] = 1) -> oqpy.Range: - """Range definition. +def make_annotations_list(annotations: Optional[str | Iterable[str]]) -> List[str]: + return [annotations] if isinstance(annotations, str) else annotations or [] - Args: - start (int): Start of the range - stop (Optional[int]): End of the range. Defaults to None. - step (Optional[int]): Step of the range. Defaults to 1. - Returns: - oqpy.Range: oqpy range definition. - """ - if stop is None: - stop = start - start = 0 - return oqpy.Range(start, stop, step) +class Range(oqpy.Range): + def __init__( + self, + start: int, + stop: Optional[int] = None, + step: Optional[int] = 1, + annotations: Optional[str | Iterable[str]] = None, + ): + """Creates a range definition. + + Args: + start (int): Start of the range. + stop (Optional[int]): End of the range. Defaults to None. + step (Optional[int]): Step of the range. Defaults to 1. + annotations (Optional[str | Iterable[str]]): Annotations for the range. + """ + if stop is None: + stop = start + start = 0 + super(Range, self).__init__(start, stop, step) + self.annotations = make_annotations_list(annotations) class ArrayVar(oqpy.ArrayVar): - def __init__(self, *args, **kwargs): + def __init__(self, *args, annotations: Optional[str | Iterable[str]] = None, **kwargs): if ( program.get_program_conversion_context().subroutines_processing or not program.get_program_conversion_context().at_function_root_scope @@ -67,13 +80,17 @@ def __init__(self, *args, **kwargs): raise errors.InvalidArrayDeclaration( "Arrays may only be declared at the root scope of an AutoQASM main function." ) - super(ArrayVar, self).__init__(*args, **kwargs) + super(ArrayVar, self).__init__( + *args, annotations=make_annotations_list(annotations), **kwargs + ) self.name = program.get_program_conversion_context().next_var_name(oqpy.ArrayVar) class BitVar(oqpy.BitVar): - def __init__(self, *args, **kwargs): - super(BitVar, self).__init__(*args, **kwargs) + def __init__(self, *args, annotations: Optional[str | Iterable[str]] = None, **kwargs): + super(BitVar, self).__init__( + *args, annotations=make_annotations_list(annotations), **kwargs + ) self.name = program.get_program_conversion_context().next_var_name(oqpy.BitVar) if self.size: value = self.init_expression or 0 @@ -81,18 +98,24 @@ def __init__(self, *args, **kwargs): class BoolVar(oqpy.BoolVar): - def __init__(self, *args, **kwargs): - super(BoolVar, self).__init__(*args, **kwargs) + def __init__(self, *args, annotations: Optional[str | Iterable[str]] = None, **kwargs): + super(BoolVar, self).__init__( + *args, annotations=make_annotations_list(annotations), **kwargs + ) self.name = program.get_program_conversion_context().next_var_name(oqpy.BoolVar) class FloatVar(oqpy.FloatVar): - def __init__(self, *args, **kwargs): - super(FloatVar, self).__init__(*args, **kwargs) + def __init__(self, *args, annotations: Optional[str | Iterable[str]] = None, **kwargs): + super(FloatVar, self).__init__( + *args, annotations=make_annotations_list(annotations), **kwargs + ) self.name = program.get_program_conversion_context().next_var_name(oqpy.FloatVar) class IntVar(oqpy.IntVar): - def __init__(self, *args, **kwargs): - super(IntVar, self).__init__(*args, **kwargs) + def __init__(self, *args, annotations: Optional[str | Iterable[str]] = None, **kwargs): + super(IntVar, self).__init__( + *args, annotations=make_annotations_list(annotations), **kwargs + ) self.name = program.get_program_conversion_context().next_var_name(oqpy.IntVar) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_annotations.py b/test/unit_tests/braket/experimental/autoqasm/test_annotations.py new file mode 100644 index 00000000..590a3e6a --- /dev/null +++ b/test/unit_tests/braket/experimental/autoqasm/test_annotations.py @@ -0,0 +1,141 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +"""Tests for annotations.""" + +from __future__ import annotations + +from collections.abc import Iterable +from typing import Any, Optional + +import pytest + +import braket.experimental.autoqasm as aq +from braket.experimental.autoqasm.instructions import cnot, h + + +@pytest.mark.parametrize( + "var_type, var_value, qasm_type, qasm_value", + [ + (aq.IntVar, 0, "int[32]", "0"), + (aq.FloatVar, 0.0, "float[64]", "0.0"), + (aq.BoolVar, False, "bool", "false"), + ], +) +def test_variable_annotations( + var_type: type, var_value: Any, qasm_type: str, qasm_value: str +) -> None: + """Test annotations on variable declarations.""" + + @aq.main + def main(): + a = var_type(var_value, annotations="foo") # noqa: F841 + b = var_type(var_value, annotations=["foo", "bar baz"]) # noqa: F841 + + expected = ( + """OPENQASM 3.0; +@foo +""" + + f"{qasm_type} a = {qasm_value};" + + """ +@foo +@bar baz +""" + + f"{qasm_type} b = {qasm_value};" + ) + assert main.to_ir() == expected + + +@pytest.mark.parametrize( + "annotations, output_annotations", + [ + (None, ""), + ([], ""), + ("foo", "@foo\n"), + (["foo"], "@foo\n"), + (["foo", "bar baz"], "@foo\n@bar baz\n"), + ], +) +def test_subroutine_annotations( + annotations: Optional[str | Iterable[str]], output_annotations: str +): + """Test annotations on subroutine declarations.""" + + @aq.subroutine(annotations=annotations) + def subroutine_test(): + pass + + @aq.main + def main(): + subroutine_test() + + expected = ( + """OPENQASM 3.0;\n""" + + output_annotations + + """def subroutine_test() { +} +subroutine_test();""" + ) + + assert main.to_ir() == expected + + +def test_range_annotations(): + """Test annotations on ranges.""" + + @aq.main(num_qubits=5) + def main(): + for i in aq.range(5, annotations="foo"): + h(i) + for i in aq.range(5, annotations=["foo", "bar baz"]): + h(i) + + expected = """OPENQASM 3.0; +qubit[5] __qubits__; +@foo +for int i in [0:5 - 1] { + h __qubits__[i]; +} +@foo +@bar baz +for int i in [0:5 - 1] { + h __qubits__[i]; +}""" + + assert main.to_ir() == expected + + +def test_verbatim_box_annotations(): + """Test annotations on verbatim boxes.""" + + @aq.main + def main(): + with aq.verbatim(annotations="foo"): + h("$0") + with aq.verbatim(annotations=["foo", "bar baz"]): + cnot("$0", "$1") + + expected = """OPENQASM 3.0; +pragma braket verbatim +@foo +box { + h $0; +} +pragma braket verbatim +@foo +@bar baz +box { + cnot $0, $1; +}""" + + assert main.to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py index 7b8b0bb7..7abbcc62 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_operators.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py @@ -180,7 +180,7 @@ def test_control_flow_for_loop_qasm() -> None: """Tests aq.operators.for_stmt with a QASM iterable.""" with aq.build_program(aq.program.UserConfig(num_qubits=10)) as program_conversion_context: aq.operators.for_stmt( - iter=aq.types.qasm_range(3), + iter=aq.types.Range(3), extra_test=None, body=for_body, get_state=None, diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index cf54fbd4..0084f893 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -17,7 +17,7 @@ import pytest import braket.experimental.autoqasm as aq -from braket.experimental.autoqasm.types.types import qasm_range +from braket.experimental.autoqasm.types import Range @pytest.mark.parametrize( @@ -27,17 +27,17 @@ ((5, None, 2), (0, 5, 2)), ], ) -def test_qasm_range( +def test_range( range_params: tuple[int, int, int], expected_range_params: tuple[int, int, int] ) -> None: - """Test `qasm_range()` returning correct `Range` object. + """Test `Range()` returning correct `Range` object. Args: - range_params (tuple[int, int, int]): Range parameters to instantiate `oqpy.Range` + range_params (tuple[int, int, int]): Range parameters to instantiate `Range` expected_range_params (tuple[int, int, int]): Expected range parameters """ start, stop, step = range_params - qrange = qasm_range(start, stop, step) + qrange = Range(start, stop, step) assert isinstance(qrange, oqpy.Range) assert (qrange.start, qrange.stop, qrange.step) == expected_range_params From af8ddf8002db0c4b9c88a9290218ba8768163aef Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Fri, 15 Dec 2023 10:24:43 -0500 Subject: [PATCH 0984/1165] Resolve error from merge --- .github/workflows/python-package.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index deafd651..eeeb7113 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -39,6 +39,7 @@ jobs: # NOTE: Do not merge this part to main. The AutoQASM example notebooks should be moved # to the example notebooks repo rather than living in the Braket SDK repo. run: | + pip install -e . pip install notebook matplotlib jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/2_Expressing_classical_control_flow.ipynb 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 0985/1165] 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 9d8c69af..19e2c986 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 5824967b..71cff0de 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 d95f937c4dd7547c201cd151eb210a45b32ea2a2 Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Mon, 18 Dec 2023 17:38:39 -0500 Subject: [PATCH 0986/1165] add magic state distillation notebook (#823) * fix: over validation * rename notebook * add magic state distillation notebook * update workflow for notebook testing * update test to increase coverage * update tests for aq.BoolVar * update notebook to address feedbacks * update text in notebook * fix typos * update notebook for feedback * distinguish T-type and A-type magic state in text * fix merge updates in magic state notebook --- .github/workflows/python-package.yml | 3 +- ...b => 3_1_Iterative_phase_estimation.ipynb} | 0 .../3_2_magic_state_distillation.ipynb | 495 ++++++++++++++++++ .../autoqasm/operators/assignments.py | 2 +- .../experimental/autoqasm/test_converters.py | 8 +- tox.ini | 3 +- 6 files changed, 507 insertions(+), 4 deletions(-) rename examples/autoqasm/{3_Iterative_phase_estimation.ipynb => 3_1_Iterative_phase_estimation.ipynb} (100%) create mode 100644 examples/autoqasm/3_2_magic_state_distillation.ipynb diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index eeeb7113..d174e0ba 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -43,7 +43,8 @@ jobs: pip install notebook matplotlib jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/2_Expressing_classical_control_flow.ipynb - jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/3_Iterative_phase_estimation.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/3_1_Iterative_phase_estimation.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/3_2_magic_state_distillation.ipynb - name: Upload coverage report to Codecov uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 if: ${{ strategy.job-index }} == 0 diff --git a/examples/autoqasm/3_Iterative_phase_estimation.ipynb b/examples/autoqasm/3_1_Iterative_phase_estimation.ipynb similarity index 100% rename from examples/autoqasm/3_Iterative_phase_estimation.ipynb rename to examples/autoqasm/3_1_Iterative_phase_estimation.ipynb diff --git a/examples/autoqasm/3_2_magic_state_distillation.ipynb b/examples/autoqasm/3_2_magic_state_distillation.ipynb new file mode 100644 index 00000000..6aa37e9c --- /dev/null +++ b/examples/autoqasm/3_2_magic_state_distillation.ipynb @@ -0,0 +1,495 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6a4927a4-e1ed-4aea-9734-1993f5e741cd", + "metadata": {}, + "source": [ + "# Magic state distillation and gate teleportation\n", + "In fault tolerant quantum computing, Clifford gates have many desirable properties in stabilizer codes. For example, they can be implemented transversally and their noise is contained locally [1]. Therefore, there is a strong preference to include Clifford gates in a logical gateset. However, Clifford gates alone do not form a universal gateset. At least one non-Clifford gate is needed to perform universal quantum computing. Magic states are certain quantum states that enable universal fault-tolerant quantum computing with Clifford gates and preserve the desirable properties of the Clifford gates. With magic states, we can effectively apply non-Clifford gates using only Clifford gates, forming a universal gateset. \n", + "\n", + "In this notebook, we first demonstrate how to implement a non-Clifford gate with only Clifford gates and magic states. This procedure is known as \\\"gate teleportation\\\". Because the preparation of the magic state in this first demonstration is not fault-tolerant (i.e., it is not prepared with an error correction code), it is subject to noise.\n", + " \n", + "In the second demonstration, we introduce a procedure to create logical magic states based on the 5-qubit error correction code [2], called magic state distillation [1]. This procedure is based on post-selection, so the preparation of magic states has a finite probability of failing under this protocol.\n", + " \n", + "Finally, we introduce a \\\"repeat until success\\\" protocol [3] that guarantees every execution prepares a magic state successfully. For all the protocols in this notebook, we use AutoQASM to program these procedures, demonstrating AutoQASM's ability to express the classical control flow needed for magic state distillation and how AutoQASM enables early fault-tolerant quantum computing experiments." + ] + }, + { + "cell_type": "markdown", + "id": "3ba7d657-f5d9-4933-a91b-a870d565d9d4", + "metadata": {}, + "source": [ + "Let's first import the modules used in this notebook." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "ccdb0127-3c13-42b1-980c-c70f2e16fc00", + "metadata": {}, + "outputs": [], + "source": [ + "# general imports\n", + "from typing import Dict, List\n", + "from collections import Counter\n", + "import numpy as np\n", + "from collections import defaultdict\n", + "\n", + "# AWS imports: Import Braket SDK modules\n", + "from braket.devices.local_simulator import LocalSimulator\n", + "import braket.experimental.autoqasm as aq\n", + "import braket.experimental.autoqasm.instructions as ins" + ] + }, + { + "cell_type": "markdown", + "id": "6f902b86-4201-4d0c-a371-6482743f67be", + "metadata": {}, + "source": [ + "## Apply non-Clifford gates with magic states\n", + "To form a universal gateset, at least one non-Clifford gate is needed in addition to Clifford gates. This additional gate is commonly chosen to be $RZ(\\pi/4)$ or $RZ(\\pi/6)$ gate. These non-Clifford gates are not possible to implement transversally with stabilizer code. Including these non-Clifford gates would lose the desirable properties of Clifford gates. Here is where magic states come to the rescue. In this section, let's learn how to apply a $RZ(\\pi/6)$ gate with only Clifford gates and a magic state [1], a procedure known as gate teleportation. Similar to [state teleportation](https://en.wikipedia.org/wiki/Quantum_teleportation), gate teleportation uses forward feedback to form dynamic circuits, but with the intention of applying a quantum gate instead of teleporting a quantum state. For simplicity, we first focus on demonstrating the algorithm of gate teleportation with a physical circuit, i.e., without encoding to an error correction code. The same algorithm can also apply to logical circuits, by replacing the Clifford gates and the magic state with the logical version. After this section, we will introduce how to create a logical magic state." + ] + }, + { + "cell_type": "markdown", + "id": "d2f60849-0d78-4dff-93d8-7213e2ead652", + "metadata": {}, + "source": [ + "First, we define a subroutine, `physical_magic_state_a_type`, to create a A-type physical magic state, $\\ket{A_{\\pi/6}} = \\frac{1}{\\sqrt{2}}(\\ket{0}+e^{i\\pi/6}\\ket{1})$ [1]. Because the example will be executed on an ideal simulator, the subroutine creates an ideal magic state. But on a near-term hardware, this magic state preparation would be subject to noise.\n", + "\n", + "For demonstration purposes, we define a subroutine, `basis_rotation_pi6`, that rotates the basis of a quantum state before a measurement. This helps verify that the main program introduced later indeed implements a $RZ(\\pi/6)$ gate on the data qubit. This subroutine is not a key part of gate teleportation. " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e6d70c3d-a56b-4428-a8cd-477fc1bf50fd", + "metadata": {}, + "outputs": [], + "source": [ + "@aq.subroutine\n", + "def physical_magic_state_a_type(q: int) -> None:\n", + " ins.h(q)\n", + " ins.rz(q, np.pi/6)\n", + "\n", + "@aq.subroutine\n", + "def basis_rotation_pi6(q: int):\n", + " ins.rz(q, -np.pi/6)\n", + " ins.h(q)" + ] + }, + { + "cell_type": "markdown", + "id": "ea3f037d-80c1-4e7d-8127-82978059dfa6", + "metadata": {}, + "source": [ + "The main program `gate_teleportation` below demonstrates applying a $RZ(\\pi/6)$ gate on a data qubit. The data qubit can start out in any state. Without loss of generality, we prepare the data qubit in $\\ket{\\text{data}} = \\frac{1}{\\sqrt{2}}(\\ket{0}+\\ket{1})$. We also prepare a magic state on an ancilla qubit. Then, we apply a CNOT gate over the data and ancilla qubit. Up to this point, the 2-qubit quantum state is \n", + "$$ (\\ket{0}+e^{i\\pi/6}\\ket{1})\\ket{0} + (e^{i\\pi/6}\\ket{0}+\\ket{1})\\ket{1} .$$\n", + "By measuring the ancilla qubit (the second qubit) and post-selecting the results with measurement outcome = 0, the procedure effectively implements a $RZ(\\pi/6)$ gate on the data qubit. To verify the resulting data qubit state is in the target state $RZ(\\pi/6)\\ket{\\text{data}} = \\frac{1}{\\sqrt{2}}(\\ket{0}+e^{i\\pi/6}\\ket{1})$, we rotate the state in the data qubit and then measure it." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "dac47e70-159d-4ebe-86a7-bd11ccf7cbe3", + "metadata": {}, + "outputs": [], + "source": [ + "@aq.main(num_qubits=2)\n", + "def gate_teleportation():\n", + " q_data = 0\n", + " q_magic = 1\n", + "\n", + " # state preparation\n", + " ins.h(q_data)\n", + " physical_magic_state_a_type(q_magic)\n", + "\n", + " # apply CNOT\n", + " ins.cnot(q_data, q_magic)\n", + "\n", + " # measure data qubit in the target basis, measure ancilla in z-basis\n", + " basis_rotation_pi6(q_data)\n", + " c = ins.measure([q_data, q_magic])" + ] + }, + { + "cell_type": "markdown", + "id": "80f8c0be-e3fa-4c5f-bad5-b4e931148b2f", + "metadata": {}, + "source": [ + "Here is a helper function to compute marginal probability. It is used in the analysis following this code block." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "b8546ad8-b6ce-45d1-9c3f-a0efb15b3e8d", + "metadata": {}, + "outputs": [], + "source": [ + "def get_marginal_probs(probs: Dict[str, float], targets: List[int]) -> Dict[str, float]:\n", + " \"\"\"Get marginal probabilities of a distribution.\n", + "\n", + " Args:\n", + " probs (Dict[str, float]): Original probability distribution.\n", + " targets (List[int]): The qubits to compute marginal probability.\n", + "\n", + " Returns:\n", + " Dict[str, float]: Marginal probabilities.\n", + " \"\"\"\n", + " new_probs = defaultdict(float)\n", + " for bitstring, value in probs.items():\n", + " new_bitstring = \"\".join([bitstring[t] for t in targets])\n", + " new_probs[new_bitstring] += value\n", + "\n", + " total_shots = sum(new_probs.values())\n", + " return {k:v/total_shots for k,v in new_probs.items()}" + ] + }, + { + "cell_type": "markdown", + "id": "b82e7365-4353-48d0-ba34-1c65cad43313", + "metadata": {}, + "source": [ + "Now, we are ready to run the gate teleportation program on a local simulator. The measurement counts are then post-selected to keep those which measure \"0\" on the ancilla qubit. The Z expectation value of the data qubit is 1.0, confirming that we indeed implemented $RZ(\\pi/6)\\ket{\\text{data}}$." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "1096e5e3-7499-4423-b650-f84b91c4347a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "measurement counts: Counter({'00': 46, '01': 39, '11': 15})\n", + "Z expectation value: 1.0\n" + ] + } + ], + "source": [ + "# Get measurement result\n", + "result = LocalSimulator().run(gate_teleportation, shots=100).result()\n", + "counts = Counter(result.measurements[\"c\"])\n", + "print(\"measurement counts: \", counts)\n", + "\n", + "# Post-select the measurement outcome that measures \"0\" in ancilla\n", + "post_selected_counts = {k:v for k,v in counts.items() if k[1]==\"0\"}\n", + "\n", + "# Compute the expectation value of Z observable on the data qubit\n", + "marginal_probs = get_marginal_probs(post_selected_counts, [0])\n", + "expval = marginal_probs.get(\"0\", 0) - marginal_probs.get(\"1\", 0)\n", + "print(\"Z expectation value: \", expval)" + ] + }, + { + "cell_type": "markdown", + "id": "0d4f5c35-a204-4fe5-b980-9b0aa4304bd2", + "metadata": {}, + "source": [ + "## Magic state distillation\n", + "We have seen how injecting a magic state into the quantum program enables the $RZ(\\pi/6)$ gate, which, when combined with the Clifford gates, forms a universal gateset. In the above demonstration, the magic state is prepared as a physical state. It is subject to noise on a near-term device. In this section, we demonstrate how to create a logical T-type magic state, $\\ket{T}=\\cos{\\beta}\\ket{0}+e^{i\\pi /4}\\sin{\\beta}\\ket{1}$ with $\\beta=\\frac12 \\arccos{\\frac{1}{\\sqrt{3}}}$, based on the 5-qubit error correction code [1, 2]. The T-type magic state is closely related to the A-type, $\\ket{A_{\\pi/6}}$, introduced in the previous section. The logical A-type magic state can be obtained by post-selecting the +1 outcome of $Z\\otimes Z$ stabilizer on a $\\ket{T}\\otimes\\ket{T}$ state and discarding the second qubit, a procedure detailed in Ref[1]." + ] + }, + { + "cell_type": "markdown", + "id": "7f0134c3-57bd-40b4-be86-d559f58dc38c", + "metadata": {}, + "source": [ + "We first define a subroutine, `physical_magic_state_t_type`, to create a T-type physical magic state. On a near-term hardware, this magic state preparation would be subject to noise. " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a56c8a54-3383-4afe-9de2-1b9fafdf25ea", + "metadata": {}, + "outputs": [], + "source": [ + "@aq.subroutine\n", + "def physical_magic_state_t_type(q: int) -> None:\n", + " ins.ry(q, np.arccos(1/np.sqrt(3)))\n", + " ins.rz(q, np.pi/4)" + ] + }, + { + "cell_type": "markdown", + "id": "ac4515d3-d226-4b69-b56e-349d169bcc13", + "metadata": {}, + "source": [ + "Then, we define a subroutine for the decoder of the 5-qubit error correction code. Measurements after running the decoder are typically used as error syndromes which inform the types of errors in the quantum state. Here, however, we will use the measurement results to post-select desired states." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "6199e8e3-44f6-419c-a0f3-f8aaa9b24ba5", + "metadata": {}, + "outputs": [], + "source": [ + "@aq.subroutine\n", + "def decoder(q0:int, q1:int, q2:int, q3:int, q4:int):\n", + " ins.cnot(q1, q0)\n", + " ins.cz(q1, q0)\n", + " ins.cz(q1, q2)\n", + " ins.cz(q1, q4)\n", + "\n", + " ins.cnot(q2, q0)\n", + " ins.cz(q2, q3)\n", + " ins.cz(q2, q4)\n", + "\n", + " ins.cnot(q3, q0)\n", + "\n", + " ins.cnot(q4, q0)\n", + " ins.cz(q4, q0)\n", + "\n", + " ins.z(q0)\n", + " ins.z(q1)\n", + " ins.z(q4)\n", + "\n", + " ins.h(q1)\n", + " ins.h(q2)\n", + " ins.h(q3)\n", + " ins.h(q4)" + ] + }, + { + "cell_type": "markdown", + "id": "271e6db2-2a6f-40ff-a560-898f76b46130", + "metadata": {}, + "source": [ + "The code snippet below is the main program for magic state distillation. First, we prepare a physical magic state on each of the five qubits. The qubit 0 is the data qubit, while the other four are ancilla. Then, the decoder subroutine is applied on all qubits. Finally, a rotation layer, consisting of a H gate and a Y gate, is needed to rotate the state in data qubit to the magic state. The magic state is successfully \"distilled\" only when all ancilla are measured as \"0\" [1]." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "73dd3d78-1444-4520-98cc-dad077a97d36", + "metadata": {}, + "outputs": [], + "source": [ + "@aq.main(num_qubits=5)\n", + "def distillation():\n", + " qubits = range(5)\n", + "\n", + " # state preparation\n", + " for q in aq.ArrayVar(qubits, dimensions=[5]):\n", + " physical_magic_state_t_type(q)\n", + "\n", + " # decoding\n", + " decoder(*qubits)\n", + "\n", + " # final rotation\n", + " ins.h(qubits[0])\n", + " ins.y(qubits[0])\n", + "\n", + " # measure ancilla\n", + " c = ins.measure(qubits[1:5])" + ] + }, + { + "cell_type": "markdown", + "id": "f3f2b76e-5996-410b-a53f-4196ccc9df6f", + "metadata": {}, + "source": [ + "We run the distillation program on a local simulator." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "43f4c1b1-dbcc-4ee0-8f99-db087bb0c8d8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "measurement counts: Counter({'0000': 181, '1000': 63, '0100': 63, '0010': 62, '0101': 60, '1111': 59, '1101': 59, '0110': 54, '0111': 54, '1100': 52, '1010': 52, '1110': 50, '1001': 49, '1011': 48, '0001': 47, '0011': 47})\n" + ] + } + ], + "source": [ + "n_shots = 1000\n", + "result = LocalSimulator().run(distillation, shots=n_shots).result()\n", + "counts = Counter(result.measurements[\"c\"])\n", + "print(\"measurement counts: \", counts)" + ] + }, + { + "cell_type": "markdown", + "id": "8126350f-c7bd-4ec9-bbdb-74a9300fc3d9", + "metadata": {}, + "source": [ + "About 1/6 of the total shots correspond to successful preparations of T-type magic state on qubit 0 (i.e., measuring \"0000\" in ancilla), agreeing with the result in Ref[1]." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "c0e78ced-1137-4d6e-be88-2cc90b40105d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "success ratio: 0.181\n" + ] + } + ], + "source": [ + "success_count = len([x for x in result.measurements[\"c\"] if x==\"0000\"])\n", + "print(\"success ratio: \", success_count/n_shots)" + ] + }, + { + "cell_type": "markdown", + "id": "74c6e629-48fc-4310-bbac-7194f9c6806c", + "metadata": {}, + "source": [ + "## Repeat until success\n", + "To guarantee a successful preparation of the magic state in every shot, we introduce a repeat-until-success (RUS) protocol [3]. \n", + "\n", + "For demonstration purposes, we first define a subroutine, `basis_rotation_t_type`, that rotates the basis of a quantum state before a measurement. This helps verify that the main program introduced later indeed create a T-type magic state on the data qubit. This subroutine is not a key part of magic state distillation and the RUS protocol. " + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "74855d76-82e9-4e63-b0dc-1310e2f2984f", + "metadata": {}, + "outputs": [], + "source": [ + "@aq.subroutine\n", + "def basis_rotation_t_type(q: int):\n", + " ins.rz(q, -np.pi/4)\n", + " ins.ry(q, -np.arccos(1/np.sqrt(3)))" + ] + }, + { + "cell_type": "markdown", + "id": "a936e847-c101-4086-909f-526daccc52af", + "metadata": {}, + "source": [ + "In the main program, `distillation_rus`, the state preparation and the decoding are repeated until all ancilla measure \\\"0\\\". This RUS protocol guarantees that every shot ends up with successful magic state preparation. This protocol requires `while`-loops in the quantum program because we continue to retry until the success condition is met. It guarantees that every shot ends up with successful magic state preparation, distilling the magic state." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "7f39a60c-389d-46b4-a862-fa1e02ea2063", + "metadata": {}, + "outputs": [], + "source": [ + "@aq.main(num_qubits=5)\n", + "def distillation_rus():\n", + " qubits = range(5)\n", + " aq_qubits = aq.ArrayVar(qubits, dimensions=[len(qubits)])\n", + "\n", + " # RUS: repeat until measuring all-zero in ancilla\n", + " c1 = aq.BoolVar(True)\n", + " while c1:\n", + " # reset qubits\n", + " for q in qubits:\n", + " ins.reset(q)\n", + "\n", + " # state preparation\n", + " for q in aq_qubits:\n", + " physical_magic_state_t_type(q)\n", + "\n", + " # decoding\n", + " decoder(*qubits)\n", + "\n", + " # measure ancilla\n", + " c = ins.measure(qubits[1:5])\n", + " c1 = c[0] or c[1] or c[2] or c[3]\n", + "\n", + " # final rotation\n", + " ins.h(qubits[0])\n", + " ins.y(qubits[0])\n", + "\n", + " # measuring in the basis of magic state\n", + " basis_rotation_t_type(qubits[0])\n", + " c2 = ins.measure(qubits[0])" + ] + }, + { + "cell_type": "markdown", + "id": "408356f1-e4cc-486b-8d6a-db9230c7cf28", + "metadata": {}, + "source": [ + "Running the RUS version of magic state distillation, the expectation value on the data qubit (qubit 0) is 1.0, indicating that T-type magic states are successfully prepared in all 20 shots." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "05a77ec3-ddb0-48e8-b4b4-1d8ca2ab832f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Z expectation value: 1.0\n" + ] + } + ], + "source": [ + "result = LocalSimulator().run(distillation_rus, shots=20).result()\n", + "counts = Counter(result.measurements[\"c2\"])\n", + "probs = {str(k):v/sum(counts.values()) for k,v in counts.items()}\n", + "\n", + "expval = probs.get(\"0\", 0) - probs.get(\"1\", 0)\n", + "print(\"Z expectation value: \", expval)" + ] + }, + { + "cell_type": "markdown", + "id": "a27cc555-1a52-402c-9597-97cfe6ffb425", + "metadata": {}, + "source": [ + "## Summary\n", + "In this notebook, we demonstrate and execute the protocol of magic state distillation and gate teleportation. These protocols are at the core of fault tolerant quantum computing with stabilizer codes. The procedure not only requires feed forward control flow, but also the expressibility of while-loop. We show that AutoQASM has the ability to express quantum programs that tie these complex classical control flow to quantum instructions. " + ] + }, + { + "cell_type": "markdown", + "id": "ce1d114c-b8db-4200-870c-a4bbe4f5f293", + "metadata": {}, + "source": [ + "## Reference\n", + "[1] S. Bravyi et al., Universal Quantum Computation with ideal Clifford gates and noisy ancillas. arXiv: https://arxiv.org/abs/quant-ph/0403025\n", + "\n", + "[2] C. H. Bennett et al., Mixed State Entanglement and Quantum Error Correction. arXiv: https://arxiv.org/abs/quant-ph/9604024\n", + "\n", + "[3] A. Paetznick et al., Repeat-Until-Success: Non-deterministic decomposition of single-qubit unitaries. arXiv: https://arxiv.org/abs/1311.1074" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/src/braket/experimental/autoqasm/operators/assignments.py b/src/braket/experimental/autoqasm/operators/assignments.py index 771fd261..09362cf9 100644 --- a/src/braket/experimental/autoqasm/operators/assignments.py +++ b/src/braket/experimental/autoqasm/operators/assignments.py @@ -143,7 +143,7 @@ def _validate_assignment_types(var1: oqpy.base.Var, var2: oqpy.base.Var) -> None raise errors.InvalidAssignmentStatement( "Arrays in assignment statements must have the same dimensions" ) - else: + elif isinstance(var1, oqpy.classical_types._SizedVar): var1_size = var1.size or 1 var2_size = var2.size or 1 if var1_size != var2_size: diff --git a/test/unit_tests/braket/experimental/autoqasm/test_converters.py b/test/unit_tests/braket/experimental/autoqasm/test_converters.py index 51f1fea6..aa08f8d7 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_converters.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_converters.py @@ -43,6 +43,9 @@ def fn() -> None: d = (0.123, "foo") # noqa: F841 a = aq.IntVar(1) # noqa: F841 e = a # noqa: F841 + f = aq.BoolVar(False) # noqa: F841 + g = aq.BoolVar(True) # noqa: F841 + g = f # noqa: F841 with aq.build_program() as program_conversion_context: mock_transpiler = MockTranspiler(assignments) @@ -55,7 +58,10 @@ def fn() -> None: int[32] a = 5; float[64] b = 1.2; a = 1; -e = a;""" +e = a; +bool f = false; +bool g = true; +g = f;""" assert qasm == expected_qasm diff --git a/tox.ini b/tox.ini index 8c22d788..2f6ac818 100644 --- a/tox.ini +++ b/tox.ini @@ -40,7 +40,8 @@ deps = commands = jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/2_Expressing_classical_control_flow.ipynb - jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/3_Iterative_phase_estimation.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/3_1_Iterative_phase_estimation.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/3_2_magic_state_distillation.ipynb jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/4_Native_programming.ipynb jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/6_Customize_gate_calibrations.ipynb From b33191f4204388e33a0af52cae2dafb979aff6af Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 19 Dec 2023 16:18:49 +0000 Subject: [PATCH 0987/1165] 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 64c04fee..3fc496e2 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 d63d5913..d2cdebb9 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 0988/1165] 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 d2cdebb9..a30db226 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 0989/1165] 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 daef0179..2c702457 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 19e2c986..86351356 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 76e98608..aeae8585 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 3c59409e..2183a232 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 9a4214c3..e955c4bb 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 7cd8e0a9..6e87db78 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 ba536594..bbb194be 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 e76756e2..4e093e5b 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 3f3268f8..916bfb05 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 71cff0de..928cff75 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 05b98ade..fc8fe778 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 0990/1165] 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 3fc496e2..37503c39 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 a30db226..d21a3b47 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 0991/1165] 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 d21a3b47..ec7335e8 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 5850e06226550338d6f593bc49cdaaac5af4487e Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Thu, 21 Dec 2023 16:57:12 -0500 Subject: [PATCH 0992/1165] update commit hash of mcm-sim (#843) --- setup.py | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 75450b7c..17e20b72 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ # to get the version of the simulator that supports the mcm=True argument for Monte Carlo # simulation of mid-circuit measurement, which AutoQASM requires. # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main. - "amazon-braket-default-simulator @ git+https://github.com/aws/amazon-braket-default-simulator-python.git@46aea776976ad7f958d847c06f29f3a7976f5cf5#egg=amazon-braket-default-simulator", # noqa E501 + "amazon-braket-default-simulator @ git+https://github.com/aws/amazon-braket-default-simulator-python.git@e11063f2cc626bbb94ee0a3eb9690dd5ff491308#egg=amazon-braket-default-simulator", # noqa E501 "oqpy~=0.3.3", "setuptools", "backoff", diff --git a/tox.ini b/tox.ini index 2f6ac818..33e7ef96 100644 --- a/tox.ini +++ b/tox.ini @@ -143,4 +143,4 @@ commands = 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@46aea776976ad7f958d847c06f29f3a7976f5cf5 # mcm-sim branch + git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git@e11063f2cc626bbb94ee0a3eb9690dd5ff491308 # mcm-sim branch From fe741da3f9c9def8af5507b29bd0aa2200c26821 Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Fri, 22 Dec 2023 14:04:59 -0500 Subject: [PATCH 0993/1165] simplify looping reset in AutoQASM notebook (#844) * simplify for-looping reset in notebook * update mcm-cim version --- examples/autoqasm/3_2_magic_state_distillation.ipynb | 5 +---- setup.py | 2 +- tox.ini | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/autoqasm/3_2_magic_state_distillation.ipynb b/examples/autoqasm/3_2_magic_state_distillation.ipynb index 6aa37e9c..e0c3ad9e 100644 --- a/examples/autoqasm/3_2_magic_state_distillation.ipynb +++ b/examples/autoqasm/3_2_magic_state_distillation.ipynb @@ -393,12 +393,9 @@ " # RUS: repeat until measuring all-zero in ancilla\n", " c1 = aq.BoolVar(True)\n", " while c1:\n", - " # reset qubits\n", - " for q in qubits:\n", - " ins.reset(q)\n", - "\n", " # state preparation\n", " for q in aq_qubits:\n", + " ins.reset(q)\n", " physical_magic_state_t_type(q)\n", "\n", " # decoding\n", diff --git a/setup.py b/setup.py index 17e20b72..e13f89e2 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ # to get the version of the simulator that supports the mcm=True argument for Monte Carlo # simulation of mid-circuit measurement, which AutoQASM requires. # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main. - "amazon-braket-default-simulator @ git+https://github.com/aws/amazon-braket-default-simulator-python.git@e11063f2cc626bbb94ee0a3eb9690dd5ff491308#egg=amazon-braket-default-simulator", # noqa E501 + "amazon-braket-default-simulator @ git+https://github.com/aws/amazon-braket-default-simulator-python.git@f17d3070a4f87a3bbef677e385a2e94dd386af78#egg=amazon-braket-default-simulator", # noqa E501 "oqpy~=0.3.3", "setuptools", "backoff", diff --git a/tox.ini b/tox.ini index 33e7ef96..d2377605 100644 --- a/tox.ini +++ b/tox.ini @@ -143,4 +143,4 @@ commands = 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@e11063f2cc626bbb94ee0a3eb9690dd5ff491308 # mcm-sim branch + git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git@f17d3070a4f87a3bbef677e385a2e94dd386af78 # mcm-sim branch From 212dcee890bfc823216094a4fd918860aa5e2ccb Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Fri, 22 Dec 2023 15:47:27 -0500 Subject: [PATCH 0994/1165] doc: Update AutoQASM docs to reflect recent changes (#845) * Minor updates to reflect recent changes * Minor updates to reflect recent changes * Update src/braket/experimental/autoqasm/doc/decorators.md Co-authored-by: Aaron Berdy * Address review comments --------- Co-authored-by: Aaron Berdy --- src/braket/experimental/autoqasm/README.md | 19 +++++------ .../experimental/autoqasm/doc/decorators.md | 33 ++++++++----------- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/braket/experimental/autoqasm/README.md b/src/braket/experimental/autoqasm/README.md index e2ab52f2..5025b592 100644 --- a/src/braket/experimental/autoqasm/README.md +++ b/src/braket/experimental/autoqasm/README.md @@ -11,7 +11,7 @@ also subject to change. For a fully supported quantum developer experience, please continue to use the rest of the Amazon Braket Python SDK by following -[these instructions](https://github.com/aws/amazon-braket-sdk-python#installing-the-amazon-braket-python-sdk). +[these instructions](https://github.com/amazon-braket/amazon-braket-sdk-python#installing-the-amazon-braket-python-sdk). If you are interested in our active development efforts, and you are not afraid of a few bugs, please keep on reading! @@ -37,7 +37,7 @@ See the [Quick Start](#quick-start) section below, as well as the AutoQASM [exam AutoQASM is an experimental module and is not yet part of the released Amazon Braket SDK. To use AutoQASM, you'll need to install directly from the `feature/autoqasm` branch: ``` -git clone https://github.com/aws/amazon-braket-sdk-python.git +git clone https://github.com/amazon-braket/amazon-braket-sdk-python.git cd amazon-braket-sdk-python git checkout feature/autoqasm pip install -e . @@ -68,7 +68,7 @@ def bell_state() -> None: cnot(0, 1) ``` -You can view the output format, which is OpenQASM, by running `bell_state().to_ir()`. +You can view the output format, which is OpenQASM, by running `bell_state.display()`. AutoQASM enables users to use more complicated program constructs with a compact and readable structure. We can demonstrate this with a program that conditionally prepares multiple Bell states @@ -84,23 +84,20 @@ def conditional_multi_bell_states() -> None: cnot(qubit, qubit+1) measure([0,1,2,3,4]) - -my_bell_program = conditional_multi_bell_states() ``` AutoQASM can support subroutines and complex control flow. You can use the Python runtime -and quantum runtime side-by-side. For the moment, we support only a few quantum operations such as -`h`, `x`, `cnot`, and `measure`. There are rough edges at the moment, but we're actively smoothing +and quantum runtime side-by-side. There are rough edges at the moment, but we're actively smoothing them out! The Amazon Braket local simulator supports AutoQASM programs as input. -Let's simulate the `my_bell_program`: +Let's simulate the `conditional_multi_bell_states` program: ``` from braket.devices.local_simulator import LocalSimulator device = LocalSimulator() -task = device.run(my_bell_program, shots=100) +task = device.run(conditional_multi_bell_states, shots=100) result = task.result() ``` @@ -131,10 +128,10 @@ the [AutoQASM GitHub project](https://github.com/orgs/amazon-braket/projects/2/) We welcome feature requests, bug reports, or general feedback, which you can share with us by -[opening up an issue](https://github.com/aws/amazon-braket-sdk-python/issues/new/choose). We also +[opening up an issue](https://github.com/amazon-braket/amazon-braket-sdk-python/issues/new/choose). We also welcome pull requests, examples, and documentation -- please open an issue describing your work when you get started, or comment on an existing issue with your intentions. Pull requests should be -targeted to the feature/autoqasm branch of the https://github.com/aws/amazon-braket-sdk-python +targeted to the `feature/autoqasm` branch of the https://github.com/amazon-braket/amazon-braket-sdk-python repository. For more details on contributing to the Amazon Braket SDK, please read the [contributing guidelines](../../../../CONTRIBUTING.md). diff --git a/src/braket/experimental/autoqasm/doc/decorators.md b/src/braket/experimental/autoqasm/doc/decorators.md index 2a570734..7fcc166a 100644 --- a/src/braket/experimental/autoqasm/doc/decorators.md +++ b/src/braket/experimental/autoqasm/doc/decorators.md @@ -8,26 +8,26 @@ There are a handful of decorators available through AutoQASM. Each one attaches This decorator marks the entry point to a quantum program. -You can include gates, pulse control, classical control and subroutine calls. When you call the function wrapped by `@aq.main`, you will get a `Program` object. The `Program` object can be executed on [devices available through Amazon Braket](https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html), including local simulators. The code snippet below creates a quantum program with `@aq.main` and runs it on the `Device` instantiated as `device`. +You can include gates, pulse control, classical control and subroutine calls. The function wrapped by `@aq.main` is converted into a `Program` object. The `Program` object can be executed on [devices available through Amazon Braket](https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html), including local simulators. The code snippet below creates a quantum program with `@aq.main` and runs it on the `Device` instantiated as `device`. ``` -@aq.main(num_qubits=5) -def ghz_state(max_qubits): - """Create a GHZ state from a variable number of qubits.""" +size = 5 + +@aq.main(num_qubits=size) +def ghz_state(): + """Create a GHZ state of the specified size.""" h(0) - for i in aq.range(1, max_qubits): + for i in aq.range(1, size): cnot(0, i) - measure(list(range(max_qubits))) - -ghz_state_program = ghz_state(max_qubits=5) + measure(range(size)) -device.run(ghz_state_program) +device.run(ghz_state) ``` -When you run your quantum program, the Amazon Braket SDK automatically serializes the program to OpenQASM before sending it to the local simulator or the Amazon Braket service. In AutoQASM, you can optionally view the OpenQASM script of your quantum program before submitting to a device by calling `to_ir()` on the `Program` object. +When you run your quantum program, the Amazon Braket SDK automatically serializes the program to OpenQASM before sending it to the local simulator or the Amazon Braket service. In AutoQASM, you can optionally view the OpenQASM script of your quantum program before submitting to a device by calling `display()` on the `Program` object. ``` -print(ghz_state_program.to_ir()) +ghz_state.display() ``` ## `@aq.subroutine` @@ -45,16 +45,13 @@ def bell(q0: int, q1: int) -> None: h(q0) cnot(q0, q1) - @aq.main(num_qubits=4) def two_bell() -> None: bell(0, 1) bell(2, 3) - -two_bell_program = two_bell() ``` -Let's take a look at the serialized output from `two_bell_program.to_ir()`, which shows that the modularity of the subroutine is preserved. +Let's take a look at the serialized output from `two_bell.to_ir()`, which shows that the modularity of the subroutine is preserved. ``` OPENQASM 3.0; @@ -84,11 +81,9 @@ def ch(q0: aq.Qubit, q1: aq.Qubit): ry(q1, math.pi / 4) @aq.main(num_qubits=2) -def main(): +def my_program(): h(0) ch(0, 1) - -main_program = main() ``` @@ -127,5 +122,5 @@ def my_program(): rx("$0", 0.123) measure("$0") -my_program().with_calibrations([cal_1]) +my_program.with_calibrations([cal_1]) ``` 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 0995/1165] 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 44e9bd57..f0e3f314 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 56f7fd2e..8960325c 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 0996/1165] 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 37503c39..47d87d33 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 ec7335e8..ad26cf05 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 0997/1165] 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 ad26cf05..27849428 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 0998/1165] 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 1e929e85..cc6ce0ff 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 3b1b8ae9..77f60a26 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 3a36d8e7..bd931688 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 0999/1165] 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 47d87d33..1f8239b0 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 27849428..7b94d79a 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 1000/1165] 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 7b94d79a..71e2409b 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 1001/1165] 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 cc6ce0ff..562c61ba 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 c490a419..0785c03c 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 707f18fd..b8e1e58b 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 e0f54458..734d5112 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 bd931688..7f9dc1a8 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 656c37dc..e96af57f 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 1002/1165] 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 1f8239b0..f6f19214 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 71e2409b..0a5eef4b 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 1003/1165] 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 0a5eef4b..084dba82 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 1004/1165] 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 657ed082..9e18faea 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 77f60a26..02c16313 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 bef4fd64..8cd1fbca 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 1005/1165] 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 f6f19214..7bafd45c 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 084dba82..b52ccbb3 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 1006/1165] 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 b52ccbb3..05117752 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 1007/1165] 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 86ec939e..d31f89f1 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 b77ac452..467b9ab8 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 1008/1165] 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 23e9abdc..2a8193e5 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 503834dd..14adacb3 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 562c61ba..31347311 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 0785c03c..7b349310 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 2183a232..907c495c 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 bb783c69..98ea3c38 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 8dca0662..f1ca5540 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 2975912f..f1d6d96d 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 bd545d94..728c6311 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 1009/1165] 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 7bafd45c..6b587ce1 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 05117752..9dd7e020 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 1010/1165] 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 9dd7e020..36ac7a1e 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 1011/1165] 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 42dda202..74b49db9 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 1012/1165] 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 24ff1553..6c505e6e 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 1013/1165] 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 2ebc3c8d..0534871a 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 1014/1165] 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 6b587ce1..6ac2a732 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 36ac7a1e..b37c4d28 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 1015/1165] 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 b37c4d28..30917282 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 1016/1165] 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 f1ca5540..1e4624e0 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 82e3b069..c3959d30 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 1017/1165] 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 6ac2a732..5207697a 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 30917282..aade395c 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 1018/1165] 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 aade395c..764a6422 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 1019/1165] 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 d31f89f1..1e85974d 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 7b349310..34f22ae1 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 aeae8585..ec44dec8 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 cd5fd7f8..98916fbf 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 1581ddd8..41c541da 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 bd1b26e4..0146eca6 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 98ea3c38..25e5c3b5 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 dbf89e14..971321a7 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 e96af57f..6e789ec9 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 928cff75..6d8bcb6a 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 c95ce74a..de3fa2fc 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 fc8fe778..1b6ac56c 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 879706fe..370d4508 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 57ba20fb..00632604 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 b42eacc0..0dd8f4ea 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 1020/1165] 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 5207697a..54fdf51a 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 764a6422..d12b14c8 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 1021/1165] 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 d12b14c8..94880a4e 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 1022/1165] =?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 7e97c6f7..d638f80b 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 d74f253d457940662f84d2a52ebfdb393e728c4b Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 7 Feb 2024 15:07:53 -0800 Subject: [PATCH 1023/1165] feat: support qubits as subroutine args (#849) --- src/braket/experimental/autoqasm/api.py | 16 +++++++++-- .../autoqasm/instructions/__init__.py | 2 +- .../autoqasm/instructions/gates.py | 5 ++-- .../autoqasm/instructions/instructions.py | 4 +-- .../autoqasm/instructions/measurements.py | 12 ++++---- .../autoqasm/instructions/qubits.py | 17 ----------- .../autoqasm/program/gate_calibrations.py | 2 +- .../experimental/autoqasm/program/program.py | 6 ++-- .../experimental/autoqasm/pulse/pulse.py | 8 ++---- .../experimental/autoqasm/types/__init__.py | 2 ++ .../autoqasm/types/conversions.py | 2 ++ .../experimental/autoqasm/types/types.py | 20 ++++++++++++- .../braket/experimental/autoqasm/test_api.py | 28 +++++++++---------- .../experimental/autoqasm/test_types.py | 20 +++++++++++++ 14 files changed, 86 insertions(+), 58 deletions(-) diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 1ed1324f..5c6ccb74 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -39,9 +39,8 @@ is_autograph_artifact, ) from braket.experimental.autoqasm.autograph.tf_utils import tf_decorator -from braket.experimental.autoqasm.instructions.qubits import QubitIdentifierType as Qubit -from braket.experimental.autoqasm.instructions.qubits import is_qubit_identifier_type from braket.experimental.autoqasm.program.gate_calibrations import GateCalibration +from braket.experimental.autoqasm.types import QubitIdentifierType as Qubit from braket.parametric import FreeParameter @@ -351,6 +350,17 @@ def _convert_subroutine( _wrap_for_oqpy_subroutine(_clone_function(f), options) ) + # Validate and declare used qubits + quantum_indices = { + i + for i, param in enumerate(inspect.signature(f).parameters.values()) + if param.annotation == aq_types.QubitIdentifierType + } + args = [ + (aq_instructions.qubits._qubit(arg) if i in quantum_indices else arg) + for i, arg in enumerate(args) + ] + # Process the program subroutine_function_call = oqpy_sub(oqpy_program, *args, **kwargs) program_conversion_context.register_args(args) @@ -635,7 +645,7 @@ def _convert_calibration( value = union_deco_func_args[name] is_qubit = i in gate_args.qubit_indices - if is_qubit and not is_qubit_identifier_type(value): + if is_qubit and not aq_types.is_qubit_identifier_type(value): raise errors.ParameterTypeError(f'Parameter "{name}" must have a type of aq.Qubit.') if not is_qubit and not isinstance(value, (float, oqpy.AngleVar)): diff --git a/src/braket/experimental/autoqasm/instructions/__init__.py b/src/braket/experimental/autoqasm/instructions/__init__.py index 244655ab..a8457b1f 100644 --- a/src/braket/experimental/autoqasm/instructions/__init__.py +++ b/src/braket/experimental/autoqasm/instructions/__init__.py @@ -26,5 +26,5 @@ def bell(): """ from .gates import * # noqa: F401, F403 -from .instructions import QubitIdentifierType, reset # noqa: F401 +from .instructions import reset # noqa: F401 from .measurements import measure # noqa: F401 diff --git a/src/braket/experimental/autoqasm/instructions/gates.py b/src/braket/experimental/autoqasm/instructions/gates.py index f8c58cb2..b8b5e28f 100644 --- a/src/braket/experimental/autoqasm/instructions/gates.py +++ b/src/braket/experimental/autoqasm/instructions/gates.py @@ -19,9 +19,8 @@ import oqpy from braket.circuits.free_parameter_expression import FreeParameterExpression - -from .instructions import _qubit_instruction -from .qubits import QubitIdentifierType +from braket.experimental.autoqasm.instructions.instructions import _qubit_instruction +from braket.experimental.autoqasm.types import QubitIdentifierType GateParameterType = Union[float, FreeParameterExpression, oqpy._ClassicalVar] diff --git a/src/braket/experimental/autoqasm/instructions/instructions.py b/src/braket/experimental/autoqasm/instructions/instructions.py index 6a846caf..63e3524d 100644 --- a/src/braket/experimental/autoqasm/instructions/instructions.py +++ b/src/braket/experimental/autoqasm/instructions/instructions.py @@ -17,8 +17,8 @@ from typing import Any from braket.experimental.autoqasm import program as aq_program - -from .qubits import QubitIdentifierType, _qubit +from braket.experimental.autoqasm.instructions.qubits import _qubit +from braket.experimental.autoqasm.types import QubitIdentifierType def _qubit_instruction( diff --git a/src/braket/experimental/autoqasm/instructions/measurements.py b/src/braket/experimental/autoqasm/instructions/measurements.py index 216b454d..9738f61e 100644 --- a/src/braket/experimental/autoqasm/instructions/measurements.py +++ b/src/braket/experimental/autoqasm/instructions/measurements.py @@ -28,14 +28,12 @@ def my_program(): from braket.experimental.autoqasm import program from braket.experimental.autoqasm import types as aq_types -from braket.experimental.autoqasm.instructions.qubits import ( - QubitIdentifierType, - _qubit, - is_qubit_identifier_type, -) +from braket.experimental.autoqasm.instructions.qubits import _qubit -def measure(qubits: Union[QubitIdentifierType, Iterable[QubitIdentifierType]]) -> aq_types.BitVar: +def measure( + qubits: Union[aq_types.QubitIdentifierType, Iterable[aq_types.QubitIdentifierType]] +) -> aq_types.BitVar: """Add qubit measurement statements to the program and assign the measurement results to bit variables. @@ -46,7 +44,7 @@ def measure(qubits: Union[QubitIdentifierType, Iterable[QubitIdentifierType]]) - Returns: BitVar: Bit variable the measurement results are assigned to. """ - if is_qubit_identifier_type(qubits): + if aq_types.is_qubit_identifier_type(qubits): qubits = [qubits] oqpy_program = program.get_program_conversion_context().get_oqpy_program() diff --git a/src/braket/experimental/autoqasm/instructions/qubits.py b/src/braket/experimental/autoqasm/instructions/qubits.py index 10994926..6c339698 100644 --- a/src/braket/experimental/autoqasm/instructions/qubits.py +++ b/src/braket/experimental/autoqasm/instructions/qubits.py @@ -23,23 +23,6 @@ from braket.experimental.autoqasm import constants, errors, program from braket.parametric import FreeParameterExpression -from braket.registers.qubit import Qubit - -QubitIdentifierType = Union[ - int, str, Qubit, oqpy._ClassicalVar, oqpy.base.OQPyExpression, oqpy.Qubit -] - - -def is_qubit_identifier_type(qubit: Any) -> bool: - """Checks if a given object is a qubit identifier type. - - Args: - qubit (Any): The object to check. - - Returns: - bool: True if the object is a qubit identifier type, False otherwise. - """ - return isinstance(qubit, QubitIdentifierType.__args__) def _get_physical_qubit_indices(qids: list[str]) -> list[int]: diff --git a/src/braket/experimental/autoqasm/program/gate_calibrations.py b/src/braket/experimental/autoqasm/program/gate_calibrations.py index 75045f04..cd3d2835 100644 --- a/src/braket/experimental/autoqasm/program/gate_calibrations.py +++ b/src/braket/experimental/autoqasm/program/gate_calibrations.py @@ -16,8 +16,8 @@ from collections.abc import Callable, Iterable -from braket.experimental.autoqasm.instructions.qubits import QubitIdentifierType as Qubit from braket.experimental.autoqasm.program import Program +from braket.experimental.autoqasm.types import QubitIdentifierType as Qubit class GateCalibration: diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 298f7140..f110316e 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -35,12 +35,12 @@ from braket.device_schema import DeviceActionType from braket.devices.device import Device from braket.experimental.autoqasm import constants, errors -from braket.experimental.autoqasm.instructions.qubits import QubitIdentifierType as Qubit from braket.experimental.autoqasm.instructions.qubits import _get_physical_qubit_indices, _qubit from braket.experimental.autoqasm.program.serialization_properties import ( OpenQASMSerializationProperties, SerializationProperties, ) +from braket.experimental.autoqasm.types import QubitIdentifierType as Qubit from braket.pulse.ast.qasm_parser import ast_to_qasm # Create the thread-local object for the program conversion context. @@ -330,11 +330,11 @@ def register_gate(self, gate_name: str) -> None: f"block. The native gates of the device are: {native_gates}" ) - def register_args(self, args: list[Any]) -> None: + def register_args(self, args: Iterable[Any]) -> None: """Register any FreeParameters in the list of arguments. Args: - args (list[Any]): Arguments passed to the main program or a subroutine. + args (Iterable[Any]): Arguments passed to the main program or a subroutine. """ for arg in args: if isinstance(arg, FreeParameterExpression): diff --git a/src/braket/experimental/autoqasm/pulse/pulse.py b/src/braket/experimental/autoqasm/pulse/pulse.py index aab1fbd0..3aabfaf8 100644 --- a/src/braket/experimental/autoqasm/pulse/pulse.py +++ b/src/braket/experimental/autoqasm/pulse/pulse.py @@ -20,12 +20,8 @@ import oqpy from braket.experimental.autoqasm import program as aq_program -from braket.experimental.autoqasm.instructions.qubits import ( - QubitIdentifierType, - _get_physical_qubit_indices, - is_qubit_identifier_type, -) -from braket.experimental.autoqasm.types import BitVar +from braket.experimental.autoqasm.instructions.qubits import _get_physical_qubit_indices +from braket.experimental.autoqasm.types import BitVar, QubitIdentifierType, is_qubit_identifier_type from braket.parametric import FreeParameterExpression from braket.parametric.free_parameter import FreeParameter from braket.pulse import PulseSequence diff --git a/src/braket/experimental/autoqasm/types/__init__.py b/src/braket/experimental/autoqasm/types/__init__.py index d6d770da..f3e25036 100644 --- a/src/braket/experimental/autoqasm/types/__init__.py +++ b/src/braket/experimental/autoqasm/types/__init__.py @@ -22,7 +22,9 @@ BoolVar, FloatVar, IntVar, + QubitIdentifierType, Range, is_qasm_type, + is_qubit_identifier_type, make_annotations_list, ) diff --git a/src/braket/experimental/autoqasm/types/conversions.py b/src/braket/experimental/autoqasm/types/conversions.py index 55b33f5f..8d8d71e0 100644 --- a/src/braket/experimental/autoqasm/types/conversions.py +++ b/src/braket/experimental/autoqasm/types/conversions.py @@ -37,6 +37,8 @@ def map_parameter_type(python_type: type) -> type: """ origin_type = typing.get_origin(python_type) or python_type + if python_type == aq_types.QubitIdentifierType: + return oqpy.Qubit if issubclass(origin_type, bool): return oqpy.BoolVar if issubclass(origin_type, (int, np.integer)): diff --git a/src/braket/experimental/autoqasm/types/types.py b/src/braket/experimental/autoqasm/types/types.py index 9f578d7c..9dc0c608 100644 --- a/src/braket/experimental/autoqasm/types/types.py +++ b/src/braket/experimental/autoqasm/types/types.py @@ -16,7 +16,7 @@ from __future__ import annotations from collections.abc import Iterable -from typing import Any, List, Optional +from typing import Any, List, Optional, Union, get_args import oqpy import oqpy.base @@ -24,6 +24,7 @@ from braket.circuits import FreeParameterExpression from braket.experimental.autoqasm import errors, program +from braket.registers import Qubit def is_qasm_type(val: Any) -> bool: @@ -48,6 +49,23 @@ def make_annotations_list(annotations: Optional[str | Iterable[str]]) -> List[st return [annotations] if isinstance(annotations, str) else annotations or [] +QubitIdentifierType = Union[ + int, str, Qubit, oqpy._ClassicalVar, oqpy.base.OQPyExpression, oqpy.Qubit +] + + +def is_qubit_identifier_type(qubit: Any) -> bool: + """Checks if a given object is a qubit identifier type. + + Args: + qubit (Any): The object to check. + + Returns: + bool: True if the object is a qubit identifier type, False otherwise. + """ + return isinstance(qubit, get_args(QubitIdentifierType)) + + class Range(oqpy.Range): def __init__( self, diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index b7cdb746..724b085d 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -176,14 +176,14 @@ def recursive_h(int[32] q) { @aq.subroutine -def bell_state_arbitrary_qubits(q0: int, q1: int) -> None: +def bell_state_arbitrary_qubits(q0: aq.Qubit, q1: aq.Qubit) -> None: h(q0) cnot(q0, q1) @pytest.fixture def double_bell_state(): - @aq.main(num_qubits=4) + @aq.main def double_bell_state() -> None: bell_state_arbitrary_qubits(0, 1) bell_state_arbitrary_qubits(2, 3) @@ -193,13 +193,13 @@ def double_bell_state() -> None: def test_double_bell_state(double_bell_state) -> None: expected = """OPENQASM 3.0; -def bell_state_arbitrary_qubits(int[32] q0, int[32] q1) { - h __qubits__[q0]; - cnot __qubits__[q0], __qubits__[q1]; +def bell_state_arbitrary_qubits(qubit q0, qubit q1) { + h q0; + cnot q0, q1; } qubit[4] __qubits__; -bell_state_arbitrary_qubits(0, 1); -bell_state_arbitrary_qubits(2, 3);""" +bell_state_arbitrary_qubits(__qubits__[0], __qubits__[1]); +bell_state_arbitrary_qubits(__qubits__[2], __qubits__[3]);""" assert double_bell_state.to_ir() == expected @@ -780,14 +780,14 @@ def prog(): @aq.subroutine -def bell(q0: int, q1: int) -> None: +def bell(q0: aq.Qubit, q1: aq.Qubit) -> None: h(q0) cnot(q0, q1) @pytest.fixture def bell_in_for_loop(): - @aq.main(num_qubits=5) + @aq.main def bell_in_for_loop() -> None: for i in aq.range(3): bell(0, 1) @@ -798,13 +798,13 @@ def bell_in_for_loop() -> None: def test_subroutines_in_for_loop(bell_in_for_loop) -> None: """Test calling a parameterized subroutine from inside a for loop.""" expected = """OPENQASM 3.0; -def bell(int[32] q0, int[32] q1) { - h __qubits__[q0]; - cnot __qubits__[q0], __qubits__[q1]; +def bell(qubit q0, qubit q1) { + h q0; + cnot q0, q1; } -qubit[5] __qubits__; +qubit[2] __qubits__; for int i in [0:3 - 1] { - bell(0, 1); + bell(__qubits__[0], __qubits__[1]); }""" assert bell_in_for_loop.to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index 0084f893..154b2045 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -335,6 +335,26 @@ def annotation_test(float[64] input) { assert main.to_ir() == expected +def test_map_qubit(): + """Test qubit input parameter type.""" + + @aq.subroutine + def annotation_test(input: aq.Qubit): + pass + + @aq.main + def main(): + annotation_test(1) + + expected = """OPENQASM 3.0; +def annotation_test(qubit input) { +} +qubit[2] __qubits__; +annotation_test(__qubits__[1]);""" + + assert main.to_ir() == expected + + def test_map_array(): """Test array input parameter type.""" From 6818b6e80bf47d0854a4c9580a7c48aa85994b8b Mon Sep 17 00:00:00 2001 From: Angela Guo Date: Wed, 7 Feb 2024 15:11:52 -0800 Subject: [PATCH 1024/1165] 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 1e85974d..b336a279 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 1025/1165] 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 54fdf51a..08342f90 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 94880a4e..ab9a50ff 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 1026/1165] 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 ab9a50ff..f459c803 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 1027/1165] 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 14adacb3..ee9c4128 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 bbb194be..74dae1bb 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 49f510ed..d61f9016 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 c719978a..3140e853 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 f1d6d96d..d9d6c3d4 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 29adf251..cac1c153 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 8485dc5e..a2203ee6 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 64a331d62e7f004a33d914a5e960f0a6ee59d815 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Fri, 9 Feb 2024 12:09:43 -0500 Subject: [PATCH 1028/1165] feature: Add support for returning values from AutoQASM main (#871) * feature: Add support for returning values from AutoQASM main * Improve readability of program add_io_declarations Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- src/braket/experimental/autoqasm/api.py | 2 +- .../autoqasm/converters/assignments.py | 9 + .../autoqasm/converters/return_statements.py | 40 ++- .../autoqasm/operators/__init__.py | 3 +- .../autoqasm/operators/assignments.py | 46 +++- .../autoqasm/operators/return_statements.py | 52 ++++ .../experimental/autoqasm/program/program.py | 72 ++++-- .../autoqasm/types/conversions.py | 18 +- .../braket/experimental/autoqasm/test_api.py | 28 +- .../experimental/autoqasm/test_program.py | 6 +- .../experimental/autoqasm/test_return.py | 242 ++++++++++++++++++ .../experimental/autoqasm/test_types.py | 36 ++- 12 files changed, 503 insertions(+), 51 deletions(-) create mode 100644 src/braket/experimental/autoqasm/operators/return_statements.py create mode 100644 test/unit_tests/braket/experimental/autoqasm/test_return.py diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 5c6ccb74..76fc3d99 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -250,7 +250,7 @@ def _convert_main( if param.kind == param.POSITIONAL_OR_KEYWORD: kwargs[param.name] = FreeParameter(param.name) param_type = param.annotation if param.annotation is not param.empty else float - program_conversion_context.register_parameter(param.name, param_type) + program_conversion_context.register_input_parameter(param.name, param_type) else: raise NotImplementedError diff --git a/src/braket/experimental/autoqasm/converters/assignments.py b/src/braket/experimental/autoqasm/converters/assignments.py index bef261e6..c1f64a44 100644 --- a/src/braket/experimental/autoqasm/converters/assignments.py +++ b/src/braket/experimental/autoqasm/converters/assignments.py @@ -20,6 +20,7 @@ from braket.experimental.autoqasm.autograph.core import ag_ctx, converter from braket.experimental.autoqasm.autograph.pyct import templates +from braket.experimental.autoqasm.operators.assignments import assign_for_output class AssignTransformer(converter.Base): @@ -37,6 +38,14 @@ def visit_Assign(self, node: ast.stmt) -> ast.stmt: template = """ tar_ = ag__.assign_stmt(tar_name_, val_) """ + try: + # Assignments for main function return statements have already been handled, + # so return early + if node.value.func.attr == assign_for_output.__name__: + return node + except AttributeError: + pass + node = self.generic_visit(node) # TODO: implement when target has multiple variable diff --git a/src/braket/experimental/autoqasm/converters/return_statements.py b/src/braket/experimental/autoqasm/converters/return_statements.py index 3116c46c..63463141 100644 --- a/src/braket/experimental/autoqasm/converters/return_statements.py +++ b/src/braket/experimental/autoqasm/converters/return_statements.py @@ -15,16 +15,19 @@ """Converters for return statement nodes.""" import ast -import warnings + +import gast from braket.experimental.autoqasm import program from braket.experimental.autoqasm.autograph.converters import return_statements from braket.experimental.autoqasm.autograph.core import ag_ctx, converter +from braket.experimental.autoqasm.autograph.pyct import templates +from braket.experimental.autoqasm.operators.assignments import assign_for_output -class ReturnValidator(converter.Base): +class ReturnTransformer(converter.Base): def visit_Return(self, node: ast.stmt) -> ast.stmt: - """AutoQASM-specific return statement validation. + """AutoQASM-specific return statement transformations. Args: node (ast.stmt): Return statement node to transform. @@ -33,8 +36,25 @@ def visit_Return(self, node: ast.stmt) -> ast.stmt: ast.stmt: Transformed return statement node. """ aq_context = program.get_program_conversion_context() - if not aq_context.subroutines_processing and node.value is not None: - warnings.warn("Return value from top level function is ignored.") + if aq_context.subroutines_processing or node.value is None: + return node + + template = ( + f"name_ = ag__.{assign_for_output.__name__}(name_const_, " + "ag__.return_output_from_main(name_const_, value_))" + ) + + name = "retval_" + if isinstance(node.value, gast.Name): + name = node.value.id + + node = templates.replace( + template, + name_=name, + name_const_=gast.Constant(name, None), + value_=node.value, + original=node, + ) return node @@ -43,6 +63,14 @@ def transform( ) -> ast.stmt: """Handle AutoQASM-specific return statement functionality before passing control to AutoGraph. + + Args: + node (ast.stmt): AST node to transform. + ctx (ag_ctx.ControlStatusCtx): Transformer context. + default_to_null_return (bool): Configuration option. + + Returns: + ast.stmt: Transformed node. """ - ReturnValidator(ctx).visit(node) + node = ReturnTransformer(ctx).visit(node) return return_statements.transform(node, ctx) diff --git a/src/braket/experimental/autoqasm/operators/__init__.py b/src/braket/experimental/autoqasm/operators/__init__.py index 75b44c87..ba8e0331 100644 --- a/src/braket/experimental/autoqasm/operators/__init__.py +++ b/src/braket/experimental/autoqasm/operators/__init__.py @@ -26,7 +26,7 @@ ldu, ) -from .assignments import assign_stmt # noqa: F401 +from .assignments import assign_for_output, assign_stmt # noqa: F401 from .comparisons import gt_, gteq_, lt_, lteq_ # noqa: F401 from .conditional_expressions import if_exp # noqa: F401 from .control_flow import for_stmt, if_stmt, while_stmt # noqa: F401 @@ -42,4 +42,5 @@ from .logical import not_ # noqa: F401 from .logical import not_eq # noqa: F401 from .logical import or_ # noqa: F401 +from .return_statements import return_output_from_main # noqa: F401 from .slices import GetItemOpts, get_item, set_item # noqa: F401 diff --git a/src/braket/experimental/autoqasm/operators/assignments.py b/src/braket/experimental/autoqasm/operators/assignments.py index 09362cf9..975ccb93 100644 --- a/src/braket/experimental/autoqasm/operators/assignments.py +++ b/src/braket/experimental/autoqasm/operators/assignments.py @@ -25,6 +25,50 @@ from braket.experimental.autoqasm.types.conversions import var_type_from_oqpy +def assign_for_output(target_name: str, value: Any) -> Any: + """Operator declares the `oq` variable, or sets variable's value if it's + already declared. Runs only for return statements on `main` decorated + functions. + + Args: + target_name (str): The name of assignment target. It is the variable + name on the lhs of an assignment statement. + value (Any): The value of assignment. It is the object on the rhs of + an assignment statement. + + Returns: + Any: Assignment value with updated name attribute if the value is an + `oqpy` type. Otherwise, it returns unchanged assignment value. + """ + if isinstance(value, UndefinedReturnValue): + return value + program_conversion_context = program.get_program_conversion_context() + + is_value_name_used = isinstance( + value, oqpy.base.Var + ) and program_conversion_context.is_var_name_used(value.name) + + value = types.wrap_value(value) + if not isinstance(value, oqpy.base.Var): + return value + + target = copy.copy(value) + target.init_expression = None + target.name = target_name + + if target_name == value.name: + # Avoid statements like `a = a;` + return value + + oqpy_program = program_conversion_context.get_oqpy_program() + if is_value_name_used or value.init_expression is None: + oqpy_program.set(target, value) + else: + oqpy_program.set(target, value.init_expression) + + return target + + def assign_stmt(target_name: str, value: Any) -> Any: """Operator declares the `oq` variable, or sets variable's value if it's already declared. @@ -59,7 +103,7 @@ def assign_stmt(target_name: str, value: Any) -> Any: # The special logic here is to handle this case properly and avoid # declaring a new variable unless it is necessary. - if is_value_name_used: + if program_conversion_context.subroutines_processing and is_value_name_used: # This is a value which already exists as a variable in the program. # Return it directly without wrapping it or declaring a new variable. return value diff --git a/src/braket/experimental/autoqasm/operators/return_statements.py b/src/braket/experimental/autoqasm/operators/return_statements.py new file mode 100644 index 00000000..aad9492e --- /dev/null +++ b/src/braket/experimental/autoqasm/operators/return_statements.py @@ -0,0 +1,52 @@ +# 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. + + +"""Operations to override return statement behavior.""" + +from typing import Any + +from braket.circuits.free_parameter_expression import FreeParameterExpression +from braket.experimental.autoqasm import program +from braket.experimental.autoqasm import types as aq_types + + +def return_output_from_main(name: str, value: Any) -> Any: + """Registers an output variable in the program with the given name that matches the type + of the value. The value is assigned to the output variable. + + Args: + name (str): The symbol name for the return variable. + value (Any): The value of the returned variable. + Returns: + Any: Returns the same value that is being returned in the statement. + """ + aq_context = program.get_program_conversion_context() + input = aq_context.get_input_parameter(name) + + if input is None: + if isinstance(value, FreeParameterExpression): + aq_context.register_output_parameter(name, aq_types.FloatVar) + else: + aq_context.register_output_parameter(name, type(value)) + else: + # Handle name collisions with input variables + name = name + "_" + value_type = type(input) + aq_context.register_output_parameter(name, value_type) + # Add `val_ = val;` at the end of the program to equate these parameters + oqpy_program = aq_context.get_oqpy_program() + output = aq_context.get_output_parameter(name) + oqpy_program.set(output, input) + + return aq_types.wrap_value(value) diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index f110316e..3678b25f 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -260,7 +260,8 @@ def __init__(self, user_config: Optional[UserConfig] = None): self._virtual_qubits_used = set() self._var_idx = 0 self._has_pulse_control = False - self._free_parameters = {} + self._input_parameters = {} + self._output_parameters = {} def make_program(self) -> Program: """Makes a Program object using the oqpy program from this conversion context. @@ -339,7 +340,7 @@ def register_args(self, args: Iterable[Any]) -> None: for arg in args: if isinstance(arg, FreeParameterExpression): for free_symbol_name in self._free_symbol_names(arg): - self.register_parameter(free_symbol_name) + self.register_input_parameter(free_symbol_name) @staticmethod def _free_symbol_names(expr: FreeParameterExpression) -> Iterable[str]: @@ -355,8 +356,10 @@ def _free_symbol_names(expr: FreeParameterExpression) -> Iterable[str]: """ return sorted([str(s) for s in expr._expression.free_symbols if isinstance(s, Symbol)]) - def register_parameter( - self, parameter_name: str, parameter_type: Union[float, int, bool] = float + def register_input_parameter( + self, + parameter_name: str, + parameter_type: Union[float, int, bool] = float, ) -> None: """Register an input parameter if it has not already been registered. @@ -364,18 +367,34 @@ def register_parameter( parameter_name (str): The name of the parameter to register with the program. parameter_type (Union[float, int, bool]): The type of the parameter to register with the program. Default: float. + + Raises: + NotImplementedError: If the parameter type is not supported. """ # TODO (#814): add type validation against existing inputs - if parameter_name not in self._free_parameters: - if parameter_type == float: - var_class = oqpy.FloatVar - elif parameter_type == int: - var_class = oqpy.IntVar - elif parameter_type == bool: - var_class = oqpy.BoolVar - else: + if parameter_name not in self._input_parameters: + aq_type = aq_types.map_parameter_type(parameter_type) + if aq_type not in [oqpy.FloatVar, oqpy.IntVar, oqpy.BoolVar]: raise NotImplementedError(parameter_type) - self._free_parameters[parameter_name] = var_class("input", name=parameter_name) + self._input_parameters[parameter_name] = aq_type("input", name=parameter_name) + + def register_output_parameter( + self, + parameter_name: str, + parameter_type: Union[float, int, bool, None] = float, + ) -> None: + """Register a new output parameter if it is not None. + + Args: + parameter_name (str): The name of the parameter to register with the program. + parameter_type (Union[float, int, bool, None]): The type of the parameter to register + with the program. Default: float. + """ + if parameter_type is type(None): + return # Do nothing + + aq_type = aq_types.map_parameter_type(parameter_type) + self._output_parameters[parameter_name] = aq_type("output", name=parameter_name) def get_expression_var(self, expression: FreeParameterExpression) -> oqpy.FloatVar: """Return an oqpy.FloatVar that represents the provided expression. @@ -392,26 +411,43 @@ def get_expression_var(self, expression: FreeParameterExpression) -> oqpy.FloatV """ # Validate that all of the free symbols are registered as free parameters. for name in self._free_symbol_names(expression): - if name not in self._free_parameters: + if name not in self._input_parameters: raise errors.ParameterNotFoundError(f"Free parameter '{name}' was not found.") # If the expression is just a standalone parameter, return the registered variable. if isinstance(expression, FreeParameter): - return self._free_parameters[expression.name] + return self._input_parameters[expression.name] # Otherwise, create a new variable and declare it here var = aq_types.FloatVar(init_expression=expression) self.get_oqpy_program().declare(var) return var - def get_free_parameters(self) -> list[oqpy.FloatVar]: + def get_input_parameters(self) -> list[oqpy.Var]: """Return a list of named oqpy.Vars that are used as free parameters in the program.""" - return list(self._free_parameters.values()) + return list(self._input_parameters.values()) + + def get_input_parameter(self, name: str) -> oqpy.Var | None: + """Return the oqpy.Var associated with the variable name `name` in the program.""" + return self._input_parameters.get(name, None) + + def get_output_parameter(self, name: str) -> oqpy.Var | None: + """Return the oqpy.Var associated with the output variable `name` in the program.""" + return self._output_parameters.get(name, None) def add_io_declarations(self) -> None: """Add input and output declaration statements to the program.""" root_oqpy_program = self.get_oqpy_program(scope=ProgramScope.MAIN) - for parameter in self.get_free_parameters(): + for parameter_name, parameter in self._input_parameters.items(): + # The variable names sometimes get overwritten by the initializer + parameter.name = parameter_name + root_oqpy_program._add_var(parameter) + for parameter_name, parameter in self._output_parameters.items(): + # Before adding the output variable to the program, remove any existing reference + root_oqpy_program.undeclared_vars.pop(parameter_name, None) + root_oqpy_program.declared_vars.pop(parameter_name, None) + + parameter.name = parameter_name root_oqpy_program._add_var(parameter) def get_target_device(self) -> Optional[Device]: diff --git a/src/braket/experimental/autoqasm/types/conversions.py b/src/braket/experimental/autoqasm/types/conversions.py index 8d8d71e0..68101a5d 100644 --- a/src/braket/experimental/autoqasm/types/conversions.py +++ b/src/braket/experimental/autoqasm/types/conversions.py @@ -21,7 +21,8 @@ import oqpy from openpulse import ast -from braket.experimental.autoqasm import errors +from braket.circuits.free_parameter_expression import FreeParameterExpression +from braket.experimental.autoqasm import errors, program from braket.experimental.autoqasm import types as aq_types @@ -129,6 +130,21 @@ def _(node: Union[float, np.floating]): return aq_types.FloatVar(node) +@wrap_value.register(FreeParameterExpression) +def _(node: FreeParameterExpression): + aq_context = program.get_program_conversion_context() + if hasattr(node, "name"): + existing_param = aq_context.get_input_parameter(node.name) + if existing_param is not None: + return existing_param + else: + return aq_types.FloatVar(node.name) + else: + raise NotImplementedError( + "Returning expressions is not implemented yet" + ) # Requires oqpy 0.3.5 + + @wrap_value.register def _(node: oqpy.base.Var): return node diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index 724b085d..5f688f30 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -484,6 +484,7 @@ def test_qasm_inline_var_condition(qasm_inline_var_condition) -> None: expected = """OPENQASM 3.0; bit __bit_0__ = 1; int[32] __int_1__ = 1; +output bit retval_; qubit[2] __qubits__; h __qubits__[0]; if (__bit_0__) { @@ -495,7 +496,8 @@ def test_qasm_inline_var_condition(qasm_inline_var_condition) -> None: x __qubits__[1]; } bit __bit_2__; -__bit_2__ = measure __qubits__[1];""" +__bit_2__ = measure __qubits__[1]; +retval_ = __bit_2__;""" assert qasm_inline_var_condition.to_ir() == expected @@ -524,15 +526,23 @@ def test_measurement_qubit_discovery(ground_state_measurements) -> None: def test_simple_measurement(ground_state_measurements) -> None: """Test that a program with only measurements is generated correctly.""" expected = """OPENQASM 3.0; +output bit retval_; qubit[6] __qubits__; bit[3] __bit_0__ = "000"; __bit_0__[0] = measure __qubits__[5]; __bit_0__[1] = measure __qubits__[2]; -__bit_0__[2] = measure __qubits__[1];""" +__bit_0__[2] = measure __qubits__[1]; +retval_ = __bit_0__;""" assert ground_state_measurements.to_ir() == expected -def test_sim_simple_measurement(ground_state_measurements) -> None: +def test_sim_simple_measurement() -> None: + # TODO: Update when local sim has support for `output` + @aq.main + def ground_state_measurements() -> aq.BitVar: + """Measure a few ground state qubits.""" + measure([5, 2, 1]) + _test_on_local_sim(ground_state_measurements) @@ -583,6 +593,7 @@ def qasm_measurement_condition() -> aq.BitVar: def test_qasm_measurement_condition(qasm_measurement_condition) -> None: expected = """OPENQASM 3.0; +output bit retval_; qubit[2] __qubits__; h __qubits__[0]; bit __bit_0__; @@ -591,7 +602,8 @@ def test_qasm_measurement_condition(qasm_measurement_condition) -> None: cnot __qubits__[0], __qubits__[1]; } bit __bit_1__; -__bit_1__ = measure __qubits__[1];""" +__bit_1__ = measure __qubits__[1]; +retval_ = __bit_1__;""" assert qasm_measurement_condition.to_ir() == expected @@ -919,14 +931,6 @@ def empty_program() -> None: assert empty_program.to_ir() == expected -def test_main_return(): - with pytest.warns(UserWarning, match="Return value from top level function is ignored"): - - @aq.main - def main() -> int: - return 1 - - def test_main_no_return(): @aq.subroutine def tester(x: int) -> int: diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/braket/experimental/autoqasm/test_program.py index d3d91ee7..183a1092 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_program.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_program.py @@ -44,7 +44,7 @@ def test_program_conversion_context() -> None: def test_get_expression_var_invalid_name(): """Tests the get_expression_var function.""" prog = aq.program.ProgramConversionContext() - prog.register_parameter("alpha") + prog.register_input_parameter("alpha") with pytest.raises(aq.errors.ParameterNotFoundError): prog.get_expression_var(FreeParameter("not_a_parameter")) with pytest.raises(aq.errors.ParameterNotFoundError): @@ -110,6 +110,7 @@ def circuit(float[64] angle) { rx(angle) __qubits__[0]; cnot __qubits__[0], __qubits__[1]; } +output bit retval_; qubit[2] __qubits__; for int i in [0:""" + str(scale) @@ -119,7 +120,8 @@ def circuit(float[64] angle) { + """); } bit __bit_0__; -__bit_0__ = measure __qubits__[1];""" +__bit_0__ = measure __qubits__[1]; +retval_ = __bit_0__;""" ) for i, (scale, angle) in enumerate(itertools.product(scales, angles)): diff --git a/test/unit_tests/braket/experimental/autoqasm/test_return.py b/test/unit_tests/braket/experimental/autoqasm/test_return.py new file mode 100644 index 00000000..51756724 --- /dev/null +++ b/test/unit_tests/braket/experimental/autoqasm/test_return.py @@ -0,0 +1,242 @@ +# 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. + +"""AutoQASM tests exercising the return statement for `aq.main`.""" + +import pytest + +import braket.experimental.autoqasm as aq +from braket.experimental.autoqasm.instructions import measure + + +def test_float_lit(): + @aq.main + def main(): + return 1.5 + + expected = """OPENQASM 3.0; +output float[64] retval_; +retval_ = 1.5;""" + + assert main.to_ir() == expected + + +def test_int_lit(): + @aq.main + def main(): + return 1 + + expected = """OPENQASM 3.0; +output int[32] retval_; +retval_ = 1;""" + + assert main.to_ir() == expected + + +def test_named_value(): + @aq.main + def main(): + output_name = 1 + return output_name + + expected = """OPENQASM 3.0; +output int[32] output_name; +output_name = 1;""" + + assert main.to_ir() == expected + + +def test_return_measure(): + @aq.main + def main(): + return measure(0) + + expected = """OPENQASM 3.0; +output bit retval_; +qubit[1] __qubits__; +bit __bit_0__; +__bit_0__ = measure __qubits__[0]; +retval_ = __bit_0__;""" + + assert main.to_ir() == expected + + +def test_named_measure(): + @aq.main + def main() -> int: + b = measure(0) + return b + + expected = """OPENQASM 3.0; +output bit b; +qubit[1] __qubits__; +bit __bit_0__; +__bit_0__ = measure __qubits__[0]; +b = __bit_0__;""" + + assert main.to_ir() == expected + + +@pytest.mark.xfail(reason="Not implemented yet") +def test_basic_arithmetic(): + @aq.main + def main(): + val = aq.types.IntVar(1) + aq.types.IntVar(2) + return val + + expected = """OPENQASM 3.0; +input int[32] input_a; +output int[32] val; +val = 3;""" + + assert main.to_ir() == expected + + +@pytest.mark.xfail(reason="Not implemented yet") +def test_expressions(): + @aq.main + def main(input_a: int): + val = 1 + val += input_a + return val + + expected = """OPENQASM 3.0; +input int[32] input_a; +output int[32] val; +val = 1; +val = val + input_a;""" + + assert main.to_ir() == expected + + +@pytest.mark.xfail(reason="Not implemented yet") +def test_expressions_and_control_flow(): + @aq.main(num_qubits=3) + def main(): + val = 0.5 + for i in aq.range(3): + val = val + measure(i) + return val + + expected = """OPENQASM 3.0; +output float[64] val; +qubit[5] __qubits__; +val = 0.5; +for int i in [0:3 - 1] { + bit __bit_1__; + __bit_1__ = measure __qubits__[i]; + val = val + __bit_1__; +}""" + + assert main.to_ir() == expected + + +@pytest.mark.xfail(reason="Not implemented yet") +def test_return_tuple(): + @aq.main + def main(): + return 1, 2 + + expected = """OPENQASM 3.0; +output int[32] retval1_; +output int[32] retval2_; +retval1_ = 1; +retval2_ = 2;""" + + assert main.to_ir() == expected + + +def test_name_collisions(): + @aq.main + def main(val): + return val + + expected = """OPENQASM 3.0; +input float[64] val; +output float[64] val_; +val_ = val;""" + + assert main.to_ir() == expected + + +@pytest.mark.xfail(raises=NotImplementedError) # Needs OQPy 0.3.5 +def test_return_inputs(): + @aq.main + def main(val1, val2): + return val1 + val2 + + expected = """OPENQASM 3.0; +input float[64] val1; +input float[64] val2; +output float[64] retval_; +retval_ = val1 + val2;""" + + assert main.to_ir() == expected + + +def test_returns_with_subroutine(): + """Test returning the result of another function call.""" + + @aq.subroutine + def helper() -> int: + res = aq.IntVar(1) + return res + + @aq.main + def ret_test() -> int: + val = helper() + return val + + expected = """OPENQASM 3.0; +def helper() -> int[32] { + int[32] res = 1; + return res; +} +output int[32] val; +int[32] __int_1__; +__int_1__ = helper(); +val = __int_1__;""" + + assert ret_test.to_ir() == expected + + +def test_return_measure_range(): + @aq.subroutine + def ghz(n: int): + aq.instructions.h(0) + for i in aq.range(n - 1): + aq.instructions.cnot(i, i + 1) + + @aq.main(num_qubits=10) + def program(n: int): + ghz(n) + return measure([0, 1, 2]) + + expected = """OPENQASM 3.0; +def ghz(int[32] n) { + h __qubits__[0]; + for int i in [0:n - 1 - 1] { + cnot __qubits__[i], __qubits__[i + 1]; + } +} +input int[32] n; +output bit retval_; +qubit[10] __qubits__; +ghz(n); +bit[3] __bit_0__ = "000"; +__bit_0__[0] = measure __qubits__[0]; +__bit_0__[1] = measure __qubits__[1]; +__bit_0__[2] = measure __qubits__[2]; +retval_ = __bit_0__;""" + + assert program.to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index 154b2045..e3b51647 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -59,8 +59,10 @@ def ret_test() -> bit { bit res = 1; return res; } +output bit retval_; bit __bit_1__; -__bit_1__ = ret_test();""" +__bit_1__ = ret_test(); +retval_ = __bit_1__;""" assert main.to_ir() == expected @@ -82,8 +84,10 @@ def ret_test() -> int[32] { int[32] res = 1; return res; } +output int[32] retval_; int[32] __int_1__; -__int_1__ = ret_test();""" +__int_1__ = ret_test(); +retval_ = __int_1__;""" assert main.to_ir() == expected @@ -105,8 +109,10 @@ def ret_test() -> float[64] { float[64] res = 1.0; return res; } +output float[64] retval_; float[64] __float_1__; -__float_1__ = ret_test();""" +__float_1__ = ret_test(); +retval_ = __float_1__;""" assert main.to_ir() == expected @@ -128,8 +134,10 @@ def ret_test() -> bool { bool res = true; return res; } +output bool retval_; bool __bool_1__; -__bool_1__ = ret_test();""" +__bool_1__ = ret_test(); +retval_ = __bool_1__;""" assert main.to_ir() == expected @@ -151,10 +159,12 @@ def ret_test() -> int: def add(int[32] a, int[32] b) -> int[32] { return a + b; } +output int[32] retval_; int[32] a = 5; int[32] b = 6; int[32] __int_2__; -__int_2__ = add(a, b);""" +__int_2__ = add(a, b); +retval_ = __int_2__;""" assert ret_test.to_ir() == expected @@ -272,8 +282,10 @@ def helper() -> int[32] { int[32] res = 1; return res; } +output int[32] retval_; int[32] __int_1__; -__int_1__ = helper();""" +__int_1__ = helper(); +retval_ = __int_1__;""" assert ret_test.to_ir() == expected @@ -451,8 +463,10 @@ def retval_test() -> int[32] { int[32] retval_ = 1; return retval_; } +output int[32] retval_; int[32] __int_1__; -__int_1__ = retval_test();""" +__int_1__ = retval_test(); +retval_ = __int_1__;""" assert caller.to_ir() == expected_qasm @@ -473,8 +487,10 @@ def retval_test() -> bit { bit retval_ = 1; return retval_; } +output bit retval_; bit __bit_1__; -__bit_1__ = retval_test();""" +__bit_1__ = retval_test(); +retval_ = __bit_1__;""" assert caller.to_ir() == expected_qasm @@ -559,8 +575,10 @@ def retval_constant() -> int[32] { int[32] retval_ = 3; return retval_; } +output float[64] retval_; float[64] __float_4__; -__float_4__ = retval_recursive();""" +__float_4__ = retval_recursive(); +retval_ = __float_4__;""" assert caller.to_ir() == expected_qasm From 418000098aaac38419ce72b98b4e8a459974f1f8 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 12 Feb 2024 16:15:59 +0000 Subject: [PATCH 1029/1165] 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 08342f90..1e26cc55 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 f459c803..a211f501 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 1030/1165] 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 a211f501..99e2e0b9 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 1031/1165] 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 ec44dec8..adebae07 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 98916fbf..fdfa1469 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 25e5c3b5..2c84507d 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 6d8bcb6a..3cf40fd8 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 00632604..da8e0a8a 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 0dd8f4ea..0c56d354 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 1032/1165] 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 1e26cc55..3682e021 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 99e2e0b9..2d23afac 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 1033/1165] 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 2d23afac..9ed071fb 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 1034/1165] 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 2c84507d..ac6f4906 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 a0c7a13ca2b410cf735c0b39d911f9cd67c57c45 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:34:30 -0500 Subject: [PATCH 1035/1165] Porting oqpy 0.3.5 upgrade to AutoQASM (#869) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * prepare release v1.69.0 * update development version to v1.69.1.dev0 * fix: let price tracker checks skip over devices without execution win… (#866) * 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 * infra: update schema version to latest in setup.py (#870) * prepare release v1.69.1 * update development version to v1.69.2.dev0 * fix merge * port fixes * 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`. * handle machine-size type FloatVar * regression: mark test as xfails * do not add FreeParameter to kwargs * do not touch failing test resuts * fix merge * fix linters * prepare release v1.70.0 * update development version to v1.70.1.dev0 * 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 * prepare release v1.70.1 * update development version to v1.70.2.dev0 * sort input parameters when doing testing equality of two PulseSequences * fix docstrings * add comment * fix: Sort input parameters when doing testing equality of two PulseSequences (#874) * sort input parameters when doing testing equality of two PulseSequences * fix docstrings --------- 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 Co-authored-by: ci Co-authored-by: Abe Coull Co-authored-by: Lauren Capelluto Co-authored-by: Angela Guo Co-authored-by: Tim (Yi-Ting) --- CHANGELOG.md | 24 ++ setup.py | 4 +- src/braket/_sdk/_version.py | 2 +- src/braket/aws/aws_device.py | 21 +- src/braket/aws/aws_quantum_task.py | 7 +- src/braket/circuits/circuit.py | 43 +++- src/braket/circuits/translations.py | 13 + src/braket/devices/device.py | 45 +++- src/braket/devices/local_simulator.py | 23 +- src/braket/experimental/autoqasm/api.py | 6 +- .../experimental/autoqasm/program/program.py | 17 +- .../parametric/free_parameter_expression.py | 84 +++---- src/braket/pulse/ast/approximation_parser.py | 2 +- src/braket/pulse/ast/free_parameters.py | 68 ++++-- src/braket/pulse/ast/qasm_parser.py | 15 -- src/braket/pulse/pulse_sequence.py | 55 +++-- src/braket/pulse/waveforms.py | 45 ++-- test/integ_tests/test_cost_tracking.py | 6 +- .../braket/aws/common_test_utils.py | 1 + test/unit_tests/braket/aws/test_aws_device.py | 156 +++++++++++- .../braket/aws/test_aws_quantum_task.py | 2 +- .../braket/circuits/test_circuit.py | 72 +++++- test/unit_tests/braket/circuits/test_gates.py | 4 +- .../braket/devices/test_local_simulator.py | 230 +++++++++++++++++- .../braket/experimental/autoqasm/test_api.py | 6 +- .../autoqasm/test_gate_calibrations.py | 4 +- .../experimental/autoqasm/test_parameters.py | 122 +++++----- .../experimental/autoqasm/test_program.py | 2 +- .../experimental/autoqasm/test_pulse.py | 20 +- .../experimental/autoqasm/test_return.py | 4 +- .../test_free_parameter_expression.py | 1 + .../braket/pulse/test_pulse_sequence.py | 79 +++--- .../unit_tests/braket/pulse/test_waveforms.py | 87 +++++-- 33 files changed, 969 insertions(+), 301 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5207697a..3682e021 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # 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 + + * Support noise models in DM simulators + +## 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 + + * update OQpy to version 0.3.5 + ## v1.68.3 (2024-02-05) ### Bug Fixes and Other Changes diff --git a/setup.py b/setup.py index 42de167f..c02230c2 100644 --- a/setup.py +++ b/setup.py @@ -27,13 +27,13 @@ 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", # Pin the latest commit of mcm-sim branch of aws/amazon-braket-default-simulator-python.git # to get the version of the simulator that supports the mcm=True argument for Monte Carlo # simulation of mid-circuit measurement, which AutoQASM requires. # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main. "amazon-braket-default-simulator @ git+https://github.com/aws/amazon-braket-default-simulator-python.git@f17d3070a4f87a3bbef677e385a2e94dd386af78#egg=amazon-braket-default-simulator", # noqa E501 - "oqpy~=0.3.3", + "oqpy~=0.3.5", "setuptools", "backoff", "boltons", diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 764a6422..9ed071fb 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.70.2.dev0" diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 14adacb3..ee9c4128 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/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index eb076213..af0478f7 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -584,7 +584,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 aeae8585..adebae07 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 @@ -1234,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: @@ -1248,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 @@ -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(): @@ -1268,9 +1269,21 @@ 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]: - program = oqpy.Program(None) + 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) @@ -1298,11 +1311,15 @@ 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 [] + + 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 ): @@ -1315,7 +1332,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/circuits/translations.py b/src/braket/circuits/translations.py index bbb194be..74dae1bb 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 49f510ed..d61f9016 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 c3c70cd6..a0b75d0d 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, SerializableProgram from braket.device_schema import DeviceActionType, DeviceCapabilities from braket.devices.device import Device @@ -51,12 +52,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__( @@ -64,6 +72,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, @@ -101,10 +112,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, SerializableProgram], @@ -139,6 +152,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/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 76fc3d99..562a59b2 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -41,7 +41,6 @@ from braket.experimental.autoqasm.autograph.tf_utils import tf_decorator from braket.experimental.autoqasm.program.gate_calibrations import GateCalibration from braket.experimental.autoqasm.types import QubitIdentifierType as Qubit -from braket.parametric import FreeParameter def main( @@ -248,9 +247,10 @@ def _convert_main( # Capture inputs to decorated function as `FreeParameter` inputs for the Program for param in parameters.values(): if param.kind == param.POSITIONAL_OR_KEYWORD: - kwargs[param.name] = FreeParameter(param.name) param_type = param.annotation if param.annotation is not param.empty else float - program_conversion_context.register_input_parameter(param.name, param_type) + kwargs[param.name] = program_conversion_context.register_input_parameter( + param.name, param_type + ) else: raise NotImplementedError diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 3678b25f..6e4ac35b 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -376,7 +376,17 @@ def register_input_parameter( aq_type = aq_types.map_parameter_type(parameter_type) if aq_type not in [oqpy.FloatVar, oqpy.IntVar, oqpy.BoolVar]: raise NotImplementedError(parameter_type) - self._input_parameters[parameter_name] = aq_type("input", name=parameter_name) + + # In case a FreeParameter has already created a FloatVar somewhere else, + # we use need_declaration=False to avoid OQPy raising name conflict errors. + if aq_type == oqpy.FloatVar: + var = aq_type("input", name=parameter_name, needs_declaration=False) + var.size = None + var.type.size = None + else: + var = aq_type("input", name=parameter_name) + self._input_parameters[parameter_name] = var + return var def register_output_parameter( self, @@ -441,7 +451,10 @@ def add_io_declarations(self) -> None: for parameter_name, parameter in self._input_parameters.items(): # The variable names sometimes get overwritten by the initializer parameter.name = parameter_name - root_oqpy_program._add_var(parameter) + if parameter.name in root_oqpy_program.undeclared_vars: + root_oqpy_program.undeclared_vars[parameter.name]._needs_declaration = True + else: + root_oqpy_program._add_var(parameter) for parameter_name, parameter in self._output_parameters.items(): # Before adding the output variable to the program, remove any existing reference root_oqpy_program.undeclared_vars.pop(parameter_name, None) diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index 3bd4dfa3..fdfa1469 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -14,20 +14,14 @@ from __future__ import annotations import ast +import operator +from functools import reduce from numbers import Number -from typing import Any, Optional, Union +from typing import Any, Union -from openpulse.ast import ( - ClassicalType, - DurationLiteral, - DurationType, - Expression, - FloatType, - Identifier, - TimeUnit, -) -from oqpy import Program -from sympy import Expr, Float, Symbol, sympify +import sympy +from oqpy.base import OQPyExpression +from oqpy.classical_types import FloatVar class FreeParameterExpression: @@ -40,11 +34,7 @@ class FreeParameterExpression: present will NOT run. Values must be substituted prior to execution. """ - def __init__( - self, - expression: Union[FreeParameterExpression, Number, Expr, str], - _type: Optional[ClassicalType] = None, - ): + 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,10 +43,6 @@ def __init__( Args: expression (Union[FreeParameterExpression, Number, Expr, str]): The expression to use. - _type (Optional[ClassicalType]): The OpenQASM3 type associated with the expression. - Subtypes of openqasm3.ast.ClassicalType are used to specify how to express the - expression in the OpenQASM3 IR. Any type other than DurationType is considered - as FloatType. Examples: >>> expression_1 = FreeParameter("theta") * FreeParameter("alpha") @@ -69,12 +55,9 @@ def __init__( ast.Pow: self.__pow__, ast.USub: self.__neg__, } - self._type = _type if _type is not None else FloatType() if isinstance(expression, FreeParameterExpression): self._expression = expression.expression - if _type is None: - self._type = expression._type - 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 @@ -82,7 +65,7 @@ def __init__( 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. @@ -91,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. @@ -106,7 +89,7 @@ def subs( """ new_parameter_values = dict() for key, val in parameter_values.items(): - if isinstance(key, FreeParameterExpression): + if issubclass(type(key), FreeParameterExpression): new_parameter_values[key.expression] = val else: new_parameter_values[key] = val @@ -124,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)}") @@ -139,7 +122,7 @@ def _eval_operation(self, node: Any) -> FreeParameterExpression: raise ValueError(f"Unsupported string detected: {node}") def __add__(self, other): - if isinstance(other, FreeParameterExpression): + if issubclass(type(other), FreeParameterExpression): return FreeParameterExpression(self.expression + other.expression) else: return FreeParameterExpression(self.expression + other) @@ -148,7 +131,7 @@ def __radd__(self, other): return FreeParameterExpression(other + self.expression) def __sub__(self, other): - if isinstance(other, FreeParameterExpression): + if issubclass(type(other), FreeParameterExpression): return FreeParameterExpression(self.expression - other.expression) else: return FreeParameterExpression(self.expression - other) @@ -157,7 +140,7 @@ def __rsub__(self, other): return FreeParameterExpression(other - self.expression) def __mul__(self, other): - if isinstance(other, FreeParameterExpression): + if issubclass(type(other), FreeParameterExpression): return FreeParameterExpression(self.expression * other.expression) else: return FreeParameterExpression(self.expression * other) @@ -166,7 +149,7 @@ def __rmul__(self, other): return FreeParameterExpression(other * self.expression) def __pow__(self, other, modulo=None): - if isinstance(other, FreeParameterExpression): + if issubclass(type(other), FreeParameterExpression): return FreeParameterExpression(self.expression**other.expression) else: return FreeParameterExpression(self.expression**other) @@ -179,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: @@ -191,20 +174,29 @@ def __repr__(self) -> str: """ return repr(self.expression) - def to_ast(self, program: Program) -> Expression: - """Creates an AST node for the :class:'FreeParameterExpression'. - - Args: - program (Program): Unused. + def _to_oqpy_expression(self) -> OQPyExpression: + """Transforms into an OQPyExpression. Returns: - Expression: The AST node. + OQPyExpression: The AST node. """ - # TODO (#822): capture expressions into expression ASTs rather than just an Identifier - identifier = Identifier(name=self) - if isinstance(self._type, DurationType): - return DurationLiteral(identifier, TimeUnit.s) - return identifier + 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", needs_declaration=False + ) + fvar.size = None + fvar.type.size = None + return fvar def subs_if_free_parameter(parameter: Any, **kwargs) -> Any: @@ -218,7 +210,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/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py index 851eb675..4398d281 100644 --- a/src/braket/pulse/ast/approximation_parser.py +++ b/src/braket/pulse/ast/approximation_parser.py @@ -58,7 +58,7 @@ def __init__(self, program: Program, frames: dict[str, Frame]): self.amplitudes = defaultdict(TimeSeries) self.frequencies = defaultdict(TimeSeries) self.phases = defaultdict(TimeSeries) - context = _ParseState(variables={"pi": np.pi}, frame_data=_init_frame_data(frames)) + context = _ParseState(variables=dict(), 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) diff --git a/src/braket/pulse/ast/free_parameters.py b/src/braket/pulse/ast/free_parameters.py index 96e319eb..41c541da 100644 --- a/src/braket/pulse/ast/free_parameters.py +++ b/src/braket/pulse/ast/free_parameters.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. +import operator from typing import Union from openpulse import ast @@ -18,8 +19,6 @@ from oqpy.program import Program from oqpy.timing import OQDurationLiteral -from braket.parametric.free_parameter_expression import FreeParameterExpression - class _FreeParameterTransformer(QASMTransformer): """Walk the AST and evaluate FreeParameterExpressions.""" @@ -43,27 +42,54 @@ def visit_Identifier( Returns: Union[Identifier, FloatLiteral]: The transformed identifier. """ - if isinstance(identifier.name, FreeParameterExpression): - new_value = FreeParameterExpression(identifier.name).subs(self.param_values) - if isinstance(new_value, FreeParameterExpression): - return ast.Identifier(new_value) - else: - return ast.FloatLiteral(float(new_value)) + if identifier.name in self.param_values: + return ast.FloatLiteral(float(self.param_values[identifier.name])) return identifier - def visit_DurationLiteral(self, duration_literal: ast.DurationLiteral) -> ast.DurationLiteral: - """Visit Duration Literal. - node.value, node.unit (node.unit.name, node.unit.value) - 1 + 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: - duration_literal (DurationLiteral): The duration literal. + node (BinaryExpression): The node. + + Returns: + Union[BinaryExpression, FloatLiteral]: The transformed identifier. + """ + 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: + node (UnaryExpression): The node. + Returns: - DurationLiteral: The transformed duration literal. + Union[UnaryExpression, FloatLiteral]: The transformed identifier. """ - duration = duration_literal.value - if not isinstance(duration, ast.Identifier): - return duration_literal - new_duration = FreeParameterExpression(duration.name).subs(self.param_values) - if isinstance(new_duration, FreeParameterExpression): - return ast.DurationLiteral(ast.Identifier(str(new_duration)), duration_literal.unit) - return OQDurationLiteral(new_duration).to_ast(self.program) + 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 e892ba86..0146eca6 100644 --- a/src/braket/pulse/ast/qasm_parser.py +++ b/src/braket/pulse/ast/qasm_parser.py @@ -15,7 +15,6 @@ from openpulse import ast from openpulse.printer import Printer -from openqasm3.ast import DurationLiteral from openqasm3.printer import PrinterState @@ -33,20 +32,6 @@ def visit_Identifier(self, node: ast.Identifier, context: PrinterState) -> None: """ self.stream.write(str(node.name)) - 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, ast.Identifier): - self.stream.write(f"({duration.name}) * 1{node.unit.name}") - else: - super().visit_DurationLiteral(node, context) - def visit_ClassicalDeclaration( self, node: ast.ClassicalDeclaration, context: PrinterState ) -> None: diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index 0375d142..ac6f4906 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -20,7 +20,6 @@ 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 @@ -84,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 @@ -103,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 @@ -122,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 @@ -141,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 @@ -160,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 @@ -180,7 +184,7 @@ def delay( Returns: PulseSequence: self, with the instruction added. """ - duration = self._format_parameter_ast(duration, _type=ast.DurationType()) + 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] @@ -228,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 @@ -275,6 +277,8 @@ def make_bound_pulse_sequence(self, param_values: dict[str, float]) -> PulseSequ 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) @@ -300,13 +304,26 @@ 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) + 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: register_identifier = "psb" program.declare( @@ -318,20 +335,13 @@ def to_ir(self) -> str: tree = program.to_ast(encal=True, include_externs=False) return ast_to_qasm(tree) - def _format_parameter_ast( + def _register_free_parameters( self, parameter: Union[float, FreeParameterExpression], - _type: ast.ClassicalType = ast.FloatType(), - ) -> Union[float, FreeParameterExpression]: + ) -> None: if isinstance(parameter, FreeParameterExpression): for p in parameter.expression.free_symbols: self._free_parameters.add(FreeParameter(p.name)) - return ( - FreeParameterExpression(parameter, _type) - if isinstance(_type, ast.DurationType) - else parameter - ) - return OQDurationLiteral(parameter) if isinstance(_type, ast.DurationType) else parameter def _parse_arg_from_calibration_schema( self, argument: dict, waveforms: dict[Waveform], frames: dict[Frame] @@ -419,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) ) diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index b7dafac5..971321a7 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -83,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, @@ -131,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 @@ -167,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, ) @@ -236,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 @@ -286,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, @@ -360,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 @@ -407,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, @@ -450,16 +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[FreeParameterExpression, OQPyExpression]: - return ( - FreeParameterExpression(parameter, duration) - if isinstance(parameter, FreeParameterExpression) and is_duration_type - else parameter - ) - - def _parse_waveform_from_calibration_schema(waveform: dict) -> Waveform: waveform_names = { "arbitrary": ArbitraryWaveform._from_calibration_schema, diff --git a/test/integ_tests/test_cost_tracking.py b/test/integ_tests/test_cost_tracking.py index 7e97c6f7..d638f80b 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) diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index f1d6d96d..d9d6c3d4 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 29adf251..cac1c153 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/aws/test_aws_quantum_task.py b/test/unit_tests/braket/aws/test_aws_quantum_task.py index e96af57f..6e789ec9 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 467a82b2..3cf40fd8 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) @@ -3002,8 +3070,8 @@ def test_pulse_circuit_to_openqasm(predefined_frame_1, user_defined_frame): "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);", + " 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 {", diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 5639399a..1b6ac56c 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[(c) * 1s] user_frame;", + " set_frequency(user_frame, 3.0 + b);", + " delay[c * 1s] user_frame;", "}", ] ) diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 8485dc5e..a2203ee6 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 diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index 5f688f30..de278996 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -1028,8 +1028,8 @@ def multiple_input_types_params(x: int, y: bool, z: float, u): expected_ir = """OPENQASM 3.0; input int[32] x; input bool y; -input float[64] z; -input float[64] u; +input float u; +input float z; qubit[1] __qubits__; bool __bool_0__; __bool_0__ = x && y; @@ -1053,7 +1053,7 @@ def circ(q: int, r: int): input int[32] q; input int[32] r; qubit[8] __qubits__; -h __qubits__[2*q + r];""" +h __qubits__[2 * q + r];""" assert circ.to_ir() == expected_ir diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py index 152c186b..37afdb4a 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py @@ -70,7 +70,7 @@ def my_program(): """ OPENQASM 3.0; defcal rx(angle[32] angle) $1 { - delay[(angle) * 1s] $1; + delay[angle * 1s] $1; } rx(1.0) $1; """ @@ -221,7 +221,7 @@ def my_program(): } defcal my_gate(angle[32] a) $0 { barrier $0; - delay[(a) * 1s] $0; + delay[a * 1s] $0; } qubit[3] __qubits__; my_gate(0.123) __qubits__[2]; diff --git a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py index 0bf01a63..9dd52e97 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py @@ -67,7 +67,7 @@ def test_simple_parametric(simple_parametric): """Test a program with a parameter can be serialized.""" expected = """OPENQASM 3.0; -input float[64] theta; +input float theta; qubit[1] __qubits__; rx(theta) __qubits__[0]; bit __bit_0__; @@ -79,7 +79,7 @@ def test_simple_parametric_fp(simple_parametric_fp): """Test a program with an explicit free parameter can be serialized.""" expected = """OPENQASM 3.0; -input float[64] theta; +input float theta; qubit[1] __qubits__; rx(theta) __qubits__[0]; bit __bit_0__; @@ -116,9 +116,9 @@ def test_multiple_parameters(multi_parametric): """Test that multiple free parameters all appear in the processed program.""" expected = """OPENQASM 3.0; +input float alpha; +input float theta; bit[2] c; -input float[64] alpha; -input float[64] theta; qubit[2] __qubits__; rx(alpha) __qubits__[0]; rx(theta) __qubits__[1]; @@ -148,8 +148,8 @@ def parametric(alpha, theta): rx(1, alpha) expected = """OPENQASM 3.0; -input float[64] alpha; -input float[64] theta; +input float alpha; +input float theta; qubit[2] __qubits__; rx(alpha) __qubits__[0]; rx(theta) __qubits__[1]; @@ -159,6 +159,9 @@ def parametric(alpha, theta): assert parametric.to_ir() == expected +@pytest.mark.xfail( + reason="Regression: FreeParameters create FloatVar with _needs_declaration=False" +) def test_parameter_in_subroutine(): """Test that parameters in subroutines are declared appropriately.""" # TODO (#816): the openqasm generated here isn't strictly valid @@ -195,7 +198,7 @@ def parametric(): rx(1, alpha) expected = """OPENQASM 3.0; -input float[64] alpha; +input float alpha; qubit[2] __qubits__; rz(alpha) __qubits__[0]; rx(alpha) __qubits__[1];""" @@ -212,7 +215,7 @@ def parametric(phi: float): ms(0, qubit_0, phi, phi, theta) expected = """OPENQASM 3.0; -input float[64] phi; +input float phi; qubit[5] __qubits__; ms(phi, phi, 0.5) __qubits__[0], __qubits__[2];""" assert parametric.to_ir() == expected @@ -239,7 +242,7 @@ def parametric(my_phi: float): cphaseshift(0, 1, my_phi) expected = """OPENQASM 3.0; -input float[64] my_phi; +input float my_phi; qubit[2] __qubits__; cphaseshift(my_phi) __qubits__[0], __qubits__[1];""" assert parametric.to_ir() == expected @@ -260,7 +263,7 @@ def parametric(alpha): def silly_rz(float[64] theta) { rz(theta) __qubits__[0]; } -input float[64] alpha; +input float alpha; qubit[1] __qubits__; silly_rz(alpha);""" assert parametric.to_ir() == expected @@ -282,8 +285,8 @@ def parametric(alpha): def silly_ms(int[32] qubit_0, float[64] phi, float[64] theta) { ms(phi, phi, theta) __qubits__[0], __qubits__[qubit_0]; } -input float[64] alpha; -input float[64] beta; +input float alpha; +input float beta; qubit[5] __qubits__; silly_ms(1, alpha, 0.707); silly_ms(3, 0.5, beta);""" @@ -319,7 +322,7 @@ def parametric(): gate rx_theta(theta) q { rx(theta) q; } -input float[64] θ; +input float θ; qubit[3] __qubits__; rx_theta(θ) __qubits__[2];""" assert parametric.to_ir() == expected @@ -337,9 +340,9 @@ def my_program(theta): rx("$1", theta) expected = """OPENQASM 3.0; -input float[64] theta; +input float theta; defcal rx(angle[32] angle) $1 { - delay[(angle) * 1s] $1; + delay[angle * 1s] $1; } rx(theta) $1;""" qasm = my_program.with_calibrations(cal_1).to_ir() @@ -355,14 +358,14 @@ def parametric(alpha: float): measure(0) unbound_expected = """OPENQASM 3.0; -input float[64] alpha; +input float alpha; qubit[1] __qubits__; rx(alpha) __qubits__[0]; bit __bit_0__; __bit_0__ = measure __qubits__[0];""" bound_template = """OPENQASM 3.0; -float[64] alpha = {}; +float alpha = {}; qubit[1] __qubits__; rx(alpha) __qubits__[0]; bit __bit_0__; @@ -411,8 +414,8 @@ def sub(float[64] alpha, float[64] theta) { def rx_alpha(int[32] qubit) { rx(alpha) __qubits__[qubit]; } -float[64] alpha = 0.5; -float[64] beta = 1.5; +float alpha = 0.5; +float beta = 1.5; qubit[3] __qubits__; sub(alpha, beta); rx_alpha(2);""" @@ -437,8 +440,8 @@ def parametric(alpha: float, beta: float): def rx_alpha(int[32] qubit, float[64] theta) { rx(theta) __qubits__[qubit]; } -input float[64] alpha; -float[64] beta = 3.141592653589793; +input float alpha; +float beta = 3.141592653589793; qubit[3] __qubits__; rx_alpha(2, alpha); rx_alpha(2, beta);""" @@ -461,9 +464,9 @@ def my_program(theta): assert qasm1 == qasm2 expected = """OPENQASM 3.0; -float[64] theta = 0.6; +float theta = 0.6; defcal rx(angle[32] angle) $1 { - delay[(angle) * 1s] $1; + delay[angle * 1s] $1; } rx(theta) $1;""" assert expected == qasm1 @@ -491,7 +494,7 @@ def parametric(alpha: float): measure(0) template = """OPENQASM 3.0; -float[64] alpha = {}; +float alpha = {}; qubit[1] __qubits__; rx(alpha) __qubits__[0]; bit __bit_0__; @@ -557,7 +560,7 @@ def parametric(val: float): measure(0) expected = """OPENQASM 3.0; -input float[64] val; +input float val; qubit[1] __qubits__; bool __bool_0__; __bool_0__ = val > 0.9; @@ -585,7 +588,7 @@ def parametric(val: float): measure(0) expected = """OPENQASM 3.0; -input float[64] val; +input float val; qubit[1] __qubits__; bool __bool_0__; __bool_0__ = val < 0.9; @@ -624,7 +627,7 @@ def sub(float[64] val) { x __qubits__[0]; } } -input float[64] val; +input float val; qubit[1] __qubits__; sub(val); bit __bit_1__; @@ -725,8 +728,8 @@ def parametric(alpha: float, beta: float): measure(0) expected = """OPENQASM 3.0; -input float[64] alpha; -input float[64] beta; +input float alpha; +input float beta; qubit[1] __qubits__; bool __bool_0__; __bool_0__ = alpha || beta; @@ -749,8 +752,8 @@ def parametric(alpha: float, beta: float): measure(0) expected = """OPENQASM 3.0; -input float[64] alpha; -input float[64] beta; +input float alpha; +input float beta; qubit[1] __qubits__; bool __bool_0__; __bool_0__ = alpha && beta; @@ -773,7 +776,7 @@ def parametric(alpha: float): measure(0) expected = """OPENQASM 3.0; -input float[64] alpha; +input float alpha; qubit[1] __qubits__; bool __bool_0__; __bool_0__ = alpha && 1.5; @@ -817,7 +820,7 @@ def parametric(val: float): measure(0) template = """OPENQASM 3.0; -float[64] val = {}; +float val = {}; qubit[1] __qubits__; bool __bool_0__; __bool_0__ = val == 1; @@ -845,11 +848,17 @@ def parametric_fp(): expr = 2 * FreeParameter("theta") gpi(0, expr) - expected = """OPENQASM 3.0; -input float[64] theta; + expected_1 = """OPENQASM 3.0; +input float theta; qubit[1] __qubits__; -gpi(2*theta) __qubits__[0];""" - assert parametric.to_ir() == parametric_fp.to_ir() == expected +gpi(2 * theta) __qubits__[0];""" + + expected_2 = """OPENQASM 3.0; +input float theta; +qubit[1] __qubits__; +gpi(2.0 * theta) __qubits__[0];""" + assert parametric.to_ir() == expected_1 + assert parametric_fp.to_ir() == expected_2 def test_sim_expressions(): @@ -871,10 +880,10 @@ def parametric(alpha, theta): gpi(0, expr) expected = """OPENQASM 3.0; -input float[64] alpha; -input float[64] theta; +input float alpha; +input float theta; qubit[1] __qubits__; -gpi(alpha*theta) __qubits__[0];""" +gpi(alpha * theta) __qubits__[0];""" assert parametric.to_ir() == expected @@ -886,9 +895,9 @@ def parametric(phi): rx(0, 2 * phi) expected = """OPENQASM 3.0; -float[64] phi = 1.5707963267948966; +float phi = 1.5707963267948966; qubit[1] __qubits__; -rx(2*phi) __qubits__[0];""" +rx(2 * phi) __qubits__[0];""" assert parametric.make_bound_program({"phi": np.pi / 2}).to_ir() == expected @@ -901,10 +910,10 @@ def parametric(prefactor, theta): gpi(0, expr) expected = """OPENQASM 3.0; -float[64] prefactor = 3; -input float[64] theta; +float prefactor = 3; +input float theta; qubit[1] __qubits__; -gpi(prefactor*theta) __qubits__[0];""" +gpi(prefactor * theta) __qubits__[0];""" assert parametric.make_bound_program({"prefactor": 3}).to_ir() == expected @@ -923,9 +932,9 @@ def parametric(alpha): def rotate(float[64] theta) { rx(3 * theta) __qubits__[0]; } -input float[64] alpha; +input float alpha; qubit[1] __qubits__; -rotate(2*alpha);""" +rotate(2 * alpha);""" assert parametric.to_ir() == expected @@ -944,9 +953,9 @@ def parametric(alpha): gate rotate(theta) q { rx(3 * theta) q; } -input float[64] alpha; +input float alpha; qubit[1] __qubits__; -rotate(2*alpha) __qubits__[0];""" +rotate(2 * alpha) __qubits__[0];""" assert parametric.to_ir() == expected @@ -960,16 +969,15 @@ def parametric(phi): measure(0) expected = """OPENQASM 3.0; -input float[64] phi; +input float phi; qubit[1] __qubits__; -float[64] __float_0__ = 2*phi; -bool __bool_1__; -__bool_1__ = __float_0__ > 3.141592653589793; -if (__bool_1__) { +bool __bool_0__; +__bool_0__ = 2 * phi > 3.141592653589793; +if (__bool_0__) { h __qubits__[0]; } -bit __bit_2__; -__bit_2__ = measure __qubits__[0];""" +bit __bit_1__; +__bit_1__ = measure __qubits__[0];""" assert parametric.to_ir() == expected @@ -985,7 +993,7 @@ def two_n_hs(n: int): expected = """OPENQASM 3.0; input int[32] n; qubit[1] __qubits__; -for int _ in [0:2*n - 1] { +for int _ in [0:2 * n - 1] { h __qubits__[0]; } bit __bit_0__; diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/braket/experimental/autoqasm/test_program.py index 183a1092..6f4657a8 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_program.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_program.py @@ -153,7 +153,7 @@ def teleportation(theta): b" \x1b[39;49;00m\x1b[32mx\x1b[39;49;00m\x1b[37m \x1b[39;49;00m__qubits__[" b"\x1b[34m1\x1b[39;49;00m];\x1b[37m\x1b[39;49;00m\n\x1b[37m \x1b[39;49;00m}" b"\x1b[37m\x1b[39;49;00m\n}\x1b[37m\x1b[39;49;00m\n\x1b[36minput\x1b[39;49;00m\x1b[37m " - b"\x1b[39;49;00m\x1b[36mfloat\x1b[39;49;00m[\x1b[34m64\x1b[39;49;00m]\x1b[37m " + b"\x1b[39;49;00m\x1b[36mfloat\x1b[39;49;00m\x1b[37m " b"\x1b[39;49;00mtheta;\x1b[37m\x1b[39;49;00m\n\x1b[36mqubit\x1b[39;49;00m[" b"\x1b[34m3\x1b[39;49;00m]\x1b[37m \x1b[39;49;00m__qubits__;\x1b[37m\x1b[39;49;00m\n" b"\x1b[32msub\x1b[39;49;00m(\x1b[34m0\x1b[39;49;00m);\x1b[37m\x1b[39;49;00m\n\x1b[32mrx" diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pulse.py b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py index 26ee9ba2..86389112 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_pulse.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py @@ -195,11 +195,11 @@ def my_program(duration): expected = textwrap.dedent( """ OPENQASM 3.0; - input float[64] duration; - input float[64] duration2; + input float duration; + input float duration2; cal { - delay[(duration) * 1s] $3, $4; - delay[(duration2) * 1s] $3, $4; + delay[duration * 1s] $3, $4; + delay[duration2 * 1s] $3, $4; } """ ).strip() @@ -216,9 +216,9 @@ def my_program(duration): expected = textwrap.dedent( """ OPENQASM 3.0; - float[64] duration = 0.123; + float duration = 0.123; cal { - delay[(duration) * 1s] $3, $4; + delay[duration * 1s] $3, $4; } """ ).strip() @@ -238,16 +238,16 @@ def my_program(duration, duration2): expected = textwrap.dedent( """ OPENQASM 3.0; - input float[64] duration; - input float[64] duration2; + input float duration; + input float duration2; cal { - delay[(duration) * 1s] $3, $4; + delay[duration * 1s] $3, $4; } bool __bool_0__; __bool_0__ = duration > duration2; if (__bool_0__) { cal { - delay[(duration2) * 1s] $3, $4; + delay[duration2 * 1s] $3, $4; } } """ diff --git a/test/unit_tests/braket/experimental/autoqasm/test_return.py b/test/unit_tests/braket/experimental/autoqasm/test_return.py index 51756724..cc3ecd73 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_return.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_return.py @@ -162,14 +162,14 @@ def main(val): return val expected = """OPENQASM 3.0; -input float[64] val; +input float val; output float[64] val_; val_ = val;""" assert main.to_ir() == expected -@pytest.mark.xfail(raises=NotImplementedError) # Needs OQPy 0.3.5 +@pytest.mark.xfail(raises=TypeError) # Needs OQPy 0.3.5 def test_return_inputs(): @aq.main def main(val1, val2): 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 879706fe..370d4508 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 cbf57a98..da8e0a8a 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,20 +125,24 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined [ "OPENQASM 3.0;", "cal {", - " 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);", + *[ + 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};", + " 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[(a + 2*b) * 1s] predefined_frame_1, predefined_frame_2;", - " delay[(a + 2*b) * 1s] 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);", @@ -150,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} ) @@ -169,19 +171,20 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined [ "OPENQASM 3.0;", "cal {", - " waveform gauss_wf = gaussian(1.0ms, (sigma_g) * 1s, 1, false);", + " 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);", - " 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[(a + 4) * 1s] predefined_frame_1, predefined_frame_2;", - " delay[(a + 4) * 1s] 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);", @@ -201,19 +204,19 @@ 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);", - " shift_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[5s] predefined_frame_1, predefined_frame_2;", - " delay[5s] 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);", @@ -305,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 5e8e7811..0c56d354 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) @@ -77,6 +85,15 @@ def test_constant_waveform(): _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(): amps = [1, 4, 5] wf = ArbitraryWaveform(amps) @@ -101,7 +118,7 @@ def test_constant_wf_free_params(): assert wf.parameters == [FreeParameter("length_v") + FreeParameter("length_w")] _assert_wf_qasm( wf, - "waveform const_wf = " "constant((length_v + length_w) * 1s, 2.0 - 3.0im);", + "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) @@ -128,6 +145,21 @@ def test_drag_gaussian_waveform(): _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(): length = 4e-9 sigma = 0.3 @@ -151,22 +183,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, 300.0ms, 0.4, false);") - - def test_drag_gaussian_wf_free_params(): wf = DragGaussianWaveform( FreeParameter("length_v"), @@ -184,8 +200,7 @@ def test_drag_gaussian_wf_free_params(): _assert_wf_qasm( wf, "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) @@ -197,7 +212,7 @@ def test_drag_gaussian_wf_free_params(): ] _assert_wf_qasm( wf_2, - "waveform d_gauss_wf = drag_gaussian(600.0ms, (sigma_b + 0.4) * 1s, beta_y, amp_z, false);", + "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) @@ -205,6 +220,36 @@ def test_drag_gaussian_wf_free_params(): _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(): length = 4e-9 sigma = 0.3 @@ -237,7 +282,7 @@ def test_gaussian_wf_free_params(): ] _assert_wf_qasm( wf, - "waveform gauss_wf = gaussian((length_v) * 1s, (sigma_x) * 1s, " "amp_z, false);", + "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) From b16cf753f0d82454e255067cfa70cdaca52f4020 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 14 Feb 2024 16:15:48 +0000 Subject: [PATCH 1036/1165] 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 3682e021..6dd9c349 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 9ed071fb..eb0e14a0 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 1037/1165] 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 eb0e14a0..af639799 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 1038/1165] 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 1795135e..8f6807b5 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 00000000..816c4265 --- /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 3bad103a..d9dbb5b6 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 af639799..fc41f12e 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 b02091d4..8d8cce10 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 bb708834..1d47a66b 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 02c8bd27..f6c6430f 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 17e40a36..462f0e36 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 846d7ad2..7bed1fe8 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 d8b40c37..d55de4e6 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 ee9c4128..43780aa3 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 31347311..711aeab1 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 34f22ae1..c6ad36b2 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 6c505e6e..a02dfa6d 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 0534871a..6c63a7e4 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 10963275..77e5f355 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 e453177a..5b4c4fab 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 2c702457..c255377b 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 b6ce11bc..86578fc8 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 86351356..46d6e3b0 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 adebae07..b3063c44 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 5b156d29..cc39aa7e 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 f0e3f314..1a50c4c8 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 628422c7..ad2c701c 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 2533537f..9376d338 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 907c495c..453b121f 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 57013df4..7617493c 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 e955c4bb..df17c03c 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 0b2f90d0..bedfd0c4 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 6e87db78..64a3556c 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 af1bb422..fe256f1d 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 06c0b362..6dda8130 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 170d3e99..1db40aa5 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 b9f0d2cc..63625491 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 11f24619..bc86e53b 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 623c0247..7870e9b6 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 e40d4e9d..4bf29a35 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 8922cda7..4ea56613 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 2275ccce..1a212650 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 abed13af..eb8ea0f2 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 77c8d0d6..4d52c5c2 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 229fc11b..34b348e2 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 572489a9..904f7ac7 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 03cd0714..d3f3fc86 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 dc0ff078..2d5ccdb6 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 06e72c8a..ccd63ac3 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 df67bf19..068c4171 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 a264d0b3..8d64888e 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 c6877262..b66d4da6 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 0a1a1c63..0b73c5e7 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 1e0826e8..afcb5d11 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 74dae1bb..9537460e 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 ebc3c787..9fa40428 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 d61f9016..3f2a28e4 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 3140e853..faee13fd 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 305bf7b7..8beddc7e 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 79b1f3e3..95e6b658 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 20100d94..c443d1b4 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 a598388e..7c84b42d 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 f2ec9b6f..0386ed7a 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 4fba9315..ad42006d 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 b8e1e58b..3cd622e3 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 3a3346ab..af6c5012 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 f516d969..af680615 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 ea562562..c4432dcf 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 7505dcbf..57c1f365 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 734d5112..5e2f12f8 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 462501cb..991370f3 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 b32cd6b9..be4e7254 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 5e3ef28f..8a5a5333 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 7187486c..82142a58 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 a8411899..32c660bc 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 9e18faea..98905dc5 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 f8c854d0..179a4497 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 db22f5f6..9d582652 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 fdfa1469..d3ad9125 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 bd5dbc5a..90c4dc58 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 4398d281..f7ec9d3b 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 0146eca6..25832aa5 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 f5e35088..3d7adb4a 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 b84e4a44..63700d22 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 2b176041..99b1acca 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 ac6f4906..e6d78f11 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 971321a7..9ca43050 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 1e4624e0..9064b942 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 479a453e..4be98b64 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 0f9d0a7a..e7bed6aa 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 bb6cc6e7..d40b0547 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 abc39753..ade8a0f7 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 1a5fbc2c..bf5e9900 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 631ca45e..dec914ba 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 69a0f1bd..8417c71b 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 4d2fa020..59eba68a 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 c306078c..f07a1be7 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 ff0a0b82..790f6e19 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 9d2b9cf0..a558bec7 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 2b049b25..c269208c 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 84b294ad..2f77eec6 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 128af025..37f4a3dc 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 6ccd6f05..63b03bfd 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 2b55dfa4..a9bd3f5a 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 a2203ee6..4877a0b8 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 1039/1165] 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 d91f4d30..bca0430b 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 3c9edd2f..656353c6 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 63b03bfd..19108414 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 95b890d6..42303465 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 ad91d0a0..8ceae35c 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 02c16313..8b2eae75 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 b377bdeb..1fba5ebc 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 4cb7de2b..74b40afb 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 3398fde4..7e7590a8 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 7b9e7d20..0dd4f3f3 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 093781b6..5c406ead 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 467b9ab8..98a9b30e 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 1040/1165] 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 6c63a7e4..a1ae0c7b 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 c5662a1a6cdaa654e4c57349b2be15ff326f7413 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Tue, 20 Feb 2024 12:00:26 -0500 Subject: [PATCH 1041/1165] feature: Follow up with AutoQASM return statement fixes (#876) * Fix returning expressions * Fix output `bit`s size error * Unmark as xfail tests that are now passing * Remove unnecessary local var in program.py Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- src/braket/experimental/autoqasm/errors.py | 4 + .../autoqasm/operators/assignments.py | 25 ++++-- .../autoqasm/operators/return_statements.py | 19 +--- .../experimental/autoqasm/program/program.py | 49 +++++----- .../autoqasm/types/conversions.py | 18 +--- .../experimental/autoqasm/types/types.py | 2 +- .../braket/experimental/autoqasm/test_api.py | 2 +- .../experimental/autoqasm/test_return.py | 89 ++++++++++++++----- 8 files changed, 121 insertions(+), 87 deletions(-) diff --git a/src/braket/experimental/autoqasm/errors.py b/src/braket/experimental/autoqasm/errors.py index 33bd23c4..17aefcc8 100644 --- a/src/braket/experimental/autoqasm/errors.py +++ b/src/braket/experimental/autoqasm/errors.py @@ -114,3 +114,7 @@ class InvalidArrayDeclaration(AutoQasmError): class UnsupportedSubroutineReturnType(AutoQasmError): """Unsupported return type for an AutoQASM subroutine.""" + + +class NameConflict(AutoQasmError): + """Name conflict between user-named variables.""" diff --git a/src/braket/experimental/autoqasm/operators/assignments.py b/src/braket/experimental/autoqasm/operators/assignments.py index 975ccb93..23efb82c 100644 --- a/src/braket/experimental/autoqasm/operators/assignments.py +++ b/src/braket/experimental/autoqasm/operators/assignments.py @@ -40,18 +40,28 @@ def assign_for_output(target_name: str, value: Any) -> Any: Any: Assignment value with updated name attribute if the value is an `oqpy` type. Otherwise, it returns unchanged assignment value. """ - if isinstance(value, UndefinedReturnValue): - return value - program_conversion_context = program.get_program_conversion_context() + aq_context = program.get_program_conversion_context() - is_value_name_used = isinstance( - value, oqpy.base.Var - ) and program_conversion_context.is_var_name_used(value.name) + is_value_name_used = isinstance(value, oqpy.base.Var) and aq_context.is_var_name_used( + value.name + ) value = types.wrap_value(value) - if not isinstance(value, oqpy.base.Var): + + oqpy_program = aq_context.get_oqpy_program() + + if not isinstance(value, (oqpy.base.OQPyExpression, oqpy.base.Var)): return value + if isinstance(value, oqpy.base.OQPyExpression) and not isinstance( + value, oqpy.base.Var + ): # Classical types subclass from both Var and OQPyExpression, and we + # only need to handle `OQPyExpression`s here + # Create a dummy target with the right name + target = oqpy.FloatVar(name=target_name) + oqpy_program.set(target, value) + return target + target = copy.copy(value) target.init_expression = None target.name = target_name @@ -60,7 +70,6 @@ def assign_for_output(target_name: str, value: Any) -> Any: # Avoid statements like `a = a;` return value - oqpy_program = program_conversion_context.get_oqpy_program() if is_value_name_used or value.init_expression is None: oqpy_program.set(target, value) else: diff --git a/src/braket/experimental/autoqasm/operators/return_statements.py b/src/braket/experimental/autoqasm/operators/return_statements.py index aad9492e..7e59512a 100644 --- a/src/braket/experimental/autoqasm/operators/return_statements.py +++ b/src/braket/experimental/autoqasm/operators/return_statements.py @@ -16,7 +16,6 @@ from typing import Any -from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.experimental.autoqasm import program from braket.experimental.autoqasm import types as aq_types @@ -32,21 +31,5 @@ def return_output_from_main(name: str, value: Any) -> Any: Any: Returns the same value that is being returned in the statement. """ aq_context = program.get_program_conversion_context() - input = aq_context.get_input_parameter(name) - - if input is None: - if isinstance(value, FreeParameterExpression): - aq_context.register_output_parameter(name, aq_types.FloatVar) - else: - aq_context.register_output_parameter(name, type(value)) - else: - # Handle name collisions with input variables - name = name + "_" - value_type = type(input) - aq_context.register_output_parameter(name, value_type) - # Add `val_ = val;` at the end of the program to equate these parameters - oqpy_program = aq_context.get_oqpy_program() - output = aq_context.get_output_parameter(name) - oqpy_program.set(output, input) - + aq_context.register_output_parameter(name, value) return aq_types.wrap_value(value) diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 6e4ac35b..93535131 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -358,53 +358,62 @@ def _free_symbol_names(expr: FreeParameterExpression) -> Iterable[str]: def register_input_parameter( self, - parameter_name: str, - parameter_type: Union[float, int, bool] = float, + name: str, + type_: Union[float, int, bool] = float, ) -> None: """Register an input parameter if it has not already been registered. Args: - parameter_name (str): The name of the parameter to register with the program. - parameter_type (Union[float, int, bool]): The type of the parameter to register + name (str): The name of the parameter to register with the program. + type_ (Union[float, int, bool]): The type of the parameter to register with the program. Default: float. Raises: NotImplementedError: If the parameter type is not supported. """ # TODO (#814): add type validation against existing inputs - if parameter_name not in self._input_parameters: - aq_type = aq_types.map_parameter_type(parameter_type) + if name not in self._input_parameters: + aq_type = aq_types.map_parameter_type(type_) if aq_type not in [oqpy.FloatVar, oqpy.IntVar, oqpy.BoolVar]: - raise NotImplementedError(parameter_type) + raise NotImplementedError(type_) # In case a FreeParameter has already created a FloatVar somewhere else, # we use need_declaration=False to avoid OQPy raising name conflict errors. if aq_type == oqpy.FloatVar: - var = aq_type("input", name=parameter_name, needs_declaration=False) + var = aq_type("input", name=name, needs_declaration=False) var.size = None var.type.size = None else: - var = aq_type("input", name=parameter_name) - self._input_parameters[parameter_name] = var + var = aq_type("input", name=name) + self._input_parameters[name] = var return var def register_output_parameter( - self, - parameter_name: str, - parameter_type: Union[float, int, bool, None] = float, + self, name: str, value: Union[bool, int, float, oqpy.base.Var, oqpy.OQPyExpression, None] ) -> None: """Register a new output parameter if it is not None. Args: - parameter_name (str): The name of the parameter to register with the program. - parameter_type (Union[float, int, bool, None]): The type of the parameter to register - with the program. Default: float. + name (str): The name of the parameter to register with the program. + value (Union[bool, int, float, Var, OQPyExpression, None]): Value to + register as an output parameter. """ - if parameter_type is type(None): - return # Do nothing + if value is None: + return - aq_type = aq_types.map_parameter_type(parameter_type) - self._output_parameters[parameter_name] = aq_type("output", name=parameter_name) + if self.get_input_parameter(name) is not None: + raise errors.NameConflict( + f"Your output parameter has the same name as an input parameter: '{name}'. " + "Please give them unique names." + ) + + value_type = aq_types.var_type_from_oqpy(value) + type_class = aq_types.map_parameter_type(value_type) + if isinstance(value, aq_types.BitVar): + new_output = type_class("output", name=name, size=value.size) + else: + new_output = type_class("output", name=name) + self._output_parameters[name] = new_output def get_expression_var(self, expression: FreeParameterExpression) -> oqpy.FloatVar: """Return an oqpy.FloatVar that represents the provided expression. diff --git a/src/braket/experimental/autoqasm/types/conversions.py b/src/braket/experimental/autoqasm/types/conversions.py index 68101a5d..8d8d71e0 100644 --- a/src/braket/experimental/autoqasm/types/conversions.py +++ b/src/braket/experimental/autoqasm/types/conversions.py @@ -21,8 +21,7 @@ import oqpy from openpulse import ast -from braket.circuits.free_parameter_expression import FreeParameterExpression -from braket.experimental.autoqasm import errors, program +from braket.experimental.autoqasm import errors from braket.experimental.autoqasm import types as aq_types @@ -130,21 +129,6 @@ def _(node: Union[float, np.floating]): return aq_types.FloatVar(node) -@wrap_value.register(FreeParameterExpression) -def _(node: FreeParameterExpression): - aq_context = program.get_program_conversion_context() - if hasattr(node, "name"): - existing_param = aq_context.get_input_parameter(node.name) - if existing_param is not None: - return existing_param - else: - return aq_types.FloatVar(node.name) - else: - raise NotImplementedError( - "Returning expressions is not implemented yet" - ) # Requires oqpy 0.3.5 - - @wrap_value.register def _(node: oqpy.base.Var): return node diff --git a/src/braket/experimental/autoqasm/types/types.py b/src/braket/experimental/autoqasm/types/types.py index 9dc0c608..97d2e58b 100644 --- a/src/braket/experimental/autoqasm/types/types.py +++ b/src/braket/experimental/autoqasm/types/types.py @@ -110,7 +110,7 @@ def __init__(self, *args, annotations: Optional[str | Iterable[str]] = None, **k *args, annotations=make_annotations_list(annotations), **kwargs ) self.name = program.get_program_conversion_context().next_var_name(oqpy.BitVar) - if self.size: + if self.size and self.init_expression != "output": value = self.init_expression or 0 self.init_expression = ast.BitstringLiteral(value, self.size) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index de278996..4a524642 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -526,7 +526,7 @@ def test_measurement_qubit_discovery(ground_state_measurements) -> None: def test_simple_measurement(ground_state_measurements) -> None: """Test that a program with only measurements is generated correctly.""" expected = """OPENQASM 3.0; -output bit retval_; +output bit[3] retval_; qubit[6] __qubits__; bit[3] __bit_0__ = "000"; __bit_0__[0] = measure __qubits__[5]; diff --git a/test/unit_tests/braket/experimental/autoqasm/test_return.py b/test/unit_tests/braket/experimental/autoqasm/test_return.py index cc3ecd73..f5465640 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_return.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_return.py @@ -87,7 +87,6 @@ def main() -> int: assert main.to_ir() == expected -@pytest.mark.xfail(reason="Not implemented yet") def test_basic_arithmetic(): @aq.main def main(): @@ -95,14 +94,14 @@ def main(): return val expected = """OPENQASM 3.0; -input int[32] input_a; +int[32] __int_0__ = 1; +int[32] __int_1__ = 2; output int[32] val; -val = 3;""" +val = __int_0__ + __int_1__;""" assert main.to_ir() == expected -@pytest.mark.xfail(reason="Not implemented yet") def test_expressions(): @aq.main def main(input_a: int): @@ -113,8 +112,7 @@ def main(input_a: int): expected = """OPENQASM 3.0; input int[32] input_a; output int[32] val; -val = 1; -val = val + input_a;""" +val = 1 + input_a;""" assert main.to_ir() == expected @@ -130,12 +128,12 @@ def main(): expected = """OPENQASM 3.0; output float[64] val; -qubit[5] __qubits__; +qubit[3] __qubits__; val = 0.5; for int i in [0:3 - 1] { - bit __bit_1__; - __bit_1__ = measure __qubits__[i]; - val = val + __bit_1__; + bit __bit_0__; + __bit_0__ = measure __qubits__[i]; + val = val + __bit_0__; }""" assert main.to_ir() == expected @@ -157,33 +155,80 @@ def main(): def test_name_collisions(): + with pytest.raises(aq.errors.NameConflict): + + @aq.main + def main(val): + return val + + +def test_return_inputs(): @aq.main - def main(val): - return val + def main(val1, val2): + return val1 + val2 expected = """OPENQASM 3.0; -input float val; -output float[64] val_; -val_ = val;""" +input float val1; +input float val2; +output float[64] retval_; +retval_ = val1 + val2;""" assert main.to_ir() == expected -@pytest.mark.xfail(raises=TypeError) # Needs OQPy 0.3.5 -def test_return_inputs(): +def test_return_ints(): @aq.main - def main(val1, val2): + def main(val1: int, val2: int): return val1 + val2 expected = """OPENQASM 3.0; -input float[64] val1; -input float[64] val2; -output float[64] retval_; +input int[32] val1; +input int[32] val2; +output int[32] retval_; retval_ = val1 + val2;""" assert main.to_ir() == expected +def test_return_bools(): + @aq.main + def main(val1: bool, val2: bool): + return val1 or val2 + + expected = """OPENQASM 3.0; +input bool val1; +input bool val2; +output bool retval_; +bool __bool_0__; +__bool_0__ = val1 || val2; +retval_ = __bool_0__;""" + + assert main.to_ir() == expected + + +def test_return_bits(): + @aq.main + def main(): + b0 = measure(0) + b1 = measure(1) + return b0 + b1 + + expected = """OPENQASM 3.0; +bit b0; +bit b1; +output bit retval_; +qubit[2] __qubits__; +bit __bit_0__; +__bit_0__ = measure __qubits__[0]; +b0 = __bit_0__; +bit __bit_1__; +__bit_1__ = measure __qubits__[1]; +b1 = __bit_1__; +retval_ = b0 + b1;""" + + assert main.to_ir() == expected + + def test_returns_with_subroutine(): """Test returning the result of another function call.""" @@ -230,7 +275,7 @@ def ghz(int[32] n) { } } input int[32] n; -output bit retval_; +output bit[3] retval_; qubit[10] __qubits__; ghz(n); bit[3] __bit_0__ = "000"; From 23858e4ad89ad30513ed880b520498069761c758 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Tue, 20 Feb 2024 12:43:36 -0500 Subject: [PATCH 1042/1165] maintenance: remove deadcode (#880) * maintenance: remove deadcode from autoqasm and bring coverage back to 100% --- .../autoqasm/instructions/qubits.py | 12 ------- .../experimental/autoqasm/operators/utils.py | 10 +----- .../experimental/autoqasm/program/program.py | 36 ------------------- .../experimental/autoqasm/test_program.py | 11 ------ 4 files changed, 1 insertion(+), 68 deletions(-) diff --git a/src/braket/experimental/autoqasm/instructions/qubits.py b/src/braket/experimental/autoqasm/instructions/qubits.py index 6c339698..c72b71b5 100644 --- a/src/braket/experimental/autoqasm/instructions/qubits.py +++ b/src/braket/experimental/autoqasm/instructions/qubits.py @@ -22,7 +22,6 @@ from openpulse.printer import dumps from braket.experimental.autoqasm import constants, errors, program -from braket.parametric import FreeParameterExpression def _get_physical_qubit_indices(qids: list[str]) -> list[int]: @@ -112,17 +111,6 @@ def _(qid: str) -> oqpy.Qubit: raise ValueError(f"invalid qubit label: '{qid}'") -@_qubit.register -def _(qid: FreeParameterExpression) -> oqpy.Qubit: - # Unbound expression dependent on input, like `h(q)` where q is unbound - int_var = oqpy.IntVar( - name=str(qid), - needs_declaration=False, - size=32, - ) - return _qubit(int_var) - - @_qubit.register def _(qid: oqpy.Qubit) -> oqpy.Qubit: return qid diff --git a/src/braket/experimental/autoqasm/operators/utils.py b/src/braket/experimental/autoqasm/operators/utils.py index 2960585d..a4ac7f81 100644 --- a/src/braket/experimental/autoqasm/operators/utils.py +++ b/src/braket/experimental/autoqasm/operators/utils.py @@ -16,7 +16,6 @@ from typing import Any, Union -from braket.circuits import FreeParameterExpression from braket.experimental.autoqasm import program from braket.experimental.autoqasm import types as aq_types @@ -37,11 +36,4 @@ def _register_and_convert_parameters( """ program_conversion_context = program.get_program_conversion_context() program_conversion_context.register_args(args) - result = [] - for arg in args: - if isinstance(arg, FreeParameterExpression): - var = program.get_program_conversion_context().get_expression_var(arg) - result.append(var) - else: - result.append(arg) - return result[0] if len(result) == 1 else result + return args[0] if len(args) == 1 else args diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 93535131..cef0f45b 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -29,7 +29,6 @@ from sympy import Symbol import braket.experimental.autoqasm.types as aq_types -from braket.circuits.free_parameter import FreeParameter from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.serialization import IRType, SerializableProgram from braket.device_schema import DeviceActionType @@ -415,45 +414,10 @@ def register_output_parameter( new_output = type_class("output", name=name) self._output_parameters[name] = new_output - def get_expression_var(self, expression: FreeParameterExpression) -> oqpy.FloatVar: - """Return an oqpy.FloatVar that represents the provided expression. - - Args: - expression (FreeParameterExpression): The expression to represent. - - Raises: - ParameterNotFoundError: If the expression contains any free parameter which has - not already been registered with the program. - - Returns: - FloatVar: The variable representing the expression. - """ - # Validate that all of the free symbols are registered as free parameters. - for name in self._free_symbol_names(expression): - if name not in self._input_parameters: - raise errors.ParameterNotFoundError(f"Free parameter '{name}' was not found.") - - # If the expression is just a standalone parameter, return the registered variable. - if isinstance(expression, FreeParameter): - return self._input_parameters[expression.name] - - # Otherwise, create a new variable and declare it here - var = aq_types.FloatVar(init_expression=expression) - self.get_oqpy_program().declare(var) - return var - - def get_input_parameters(self) -> list[oqpy.Var]: - """Return a list of named oqpy.Vars that are used as free parameters in the program.""" - return list(self._input_parameters.values()) - def get_input_parameter(self, name: str) -> oqpy.Var | None: """Return the oqpy.Var associated with the variable name `name` in the program.""" return self._input_parameters.get(name, None) - def get_output_parameter(self, name: str) -> oqpy.Var | None: - """Return the oqpy.Var associated with the output variable `name` in the program.""" - return self._output_parameters.get(name, None) - def add_io_declarations(self) -> None: """Add input and output declaration statements to the program.""" root_oqpy_program = self.get_oqpy_program(scope=ProgramScope.MAIN) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/braket/experimental/autoqasm/test_program.py index 6f4657a8..2f690194 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_program.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_program.py @@ -21,7 +21,6 @@ import pytest import braket.experimental.autoqasm as aq -from braket.circuits import FreeParameter from braket.circuits.serialization import IRType from braket.experimental.autoqasm.instructions import cnot, measure, rx @@ -41,16 +40,6 @@ def test_program_conversion_context() -> None: assert len(prog._oqpy_program_stack) == 1 -def test_get_expression_var_invalid_name(): - """Tests the get_expression_var function.""" - prog = aq.program.ProgramConversionContext() - prog.register_input_parameter("alpha") - with pytest.raises(aq.errors.ParameterNotFoundError): - prog.get_expression_var(FreeParameter("not_a_parameter")) - with pytest.raises(aq.errors.ParameterNotFoundError): - prog.get_expression_var(3 * FreeParameter("also_not_a_parameter")) - - def test_build_program() -> None: """Tests the aq.build_program function.""" with pytest.raises(AssertionError): From ccae267f232d52446ad7e367c4c565105c128786 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:31:40 -0500 Subject: [PATCH 1043/1165] change: Modify AutoQASM to consume autograph from diastatic-malt package (#881) --- .coveragerc | 1 - setup.cfg | 2 - setup.py | 1 + src/braket/experimental/autoqasm/api.py | 11 +- .../autoqasm/autograph/ATTRIBUTION.md | 263 --- .../autoqasm/autograph/__init__.py | 55 - .../autoqasm/autograph/converters/__init__.py | 28 - .../autoqasm/autograph/converters/asserts.py | 48 - .../autograph/converters/break_statements.py | 185 -- .../autograph/converters/call_trees.py | 221 --- .../converters/conditional_expressions.py | 46 - .../converters/continue_statements.py | 164 -- .../autograph/converters/control_flow.py | 413 ----- .../converters/control_flow_deprecated_py2.py | 635 ------- .../autograph/converters/directives.py | 177 -- .../autograph/converters/functions.py | 134 -- .../converters/list_comprehensions.py | 78 - .../autoqasm/autograph/converters/lists.py | 239 --- .../converters/logical_expressions.py | 131 -- .../autograph/converters/return_statements.py | 402 ----- .../autoqasm/autograph/converters/slices.py | 83 - .../autograph/converters/variables.py | 97 - .../autoqasm/autograph/core/ag_ctx.py | 103 -- .../autoqasm/autograph/core/config.py | 61 - .../autoqasm/autograph/core/config_lib.py | 61 - .../autoqasm/autograph/core/converter.py | 314 ---- .../autograph/core/function_wrappers.py | 95 - .../core/unsupported_features_checker.py | 57 - .../autoqasm/autograph/impl/api_core.py | 161 -- .../autoqasm/autograph/lang/directives.py | 91 - .../autoqasm/autograph/logging/ag_logging.py | 142 -- .../autoqasm/autograph/operators/variables.py | 104 -- .../autograph/operators/variables_test.py | 51 - .../autoqasm/autograph/pyct/__init__.py | 16 - .../autoqasm/autograph/pyct/anno.py | 174 -- .../autoqasm/autograph/pyct/anno_test.py | 80 - .../autoqasm/autograph/pyct/ast_util.py | 344 ---- .../autoqasm/autograph/pyct/ast_util_test.py | 246 --- .../autoqasm/autograph/pyct/cache.py | 93 - .../autoqasm/autograph/pyct/cache_test.py | 75 - .../autoqasm/autograph/pyct/cfg.py | 971 ---------- .../autoqasm/autograph/pyct/cfg_test.py | 1602 ----------------- .../pyct/common_transformers/__init__.py | 0 .../autograph/pyct/common_transformers/anf.py | 620 ------- .../pyct/common_transformers/anf_test.py | 518 ------ .../autoqasm/autograph/pyct/error_utils.py | 230 --- .../autograph/pyct/error_utils_test.py | 126 -- .../autoqasm/autograph/pyct/errors.py | 27 - .../autoqasm/autograph/pyct/gast_util.py | 74 - .../autoqasm/autograph/pyct/inspect_utils.py | 321 ---- .../autograph/pyct/inspect_utils_test.py | 612 ------- .../autograph/pyct/inspect_utils_test.sh | 19 - .../autoqasm/autograph/pyct/loader.py | 102 -- .../autoqasm/autograph/pyct/loader_test.py | 123 -- .../autoqasm/autograph/pyct/naming.py | 53 - .../autoqasm/autograph/pyct/naming_test.py | 44 - .../autoqasm/autograph/pyct/origin_info.py | 296 --- .../autograph/pyct/origin_info_test.py | 268 --- .../autoqasm/autograph/pyct/parser.py | 396 ---- .../autoqasm/autograph/pyct/parser_test.py | 385 ---- .../autoqasm/autograph/pyct/pretty_printer.py | 130 -- .../autograph/pyct/pretty_printer_test.py | 57 - .../autoqasm/autograph/pyct/qual_names.py | 266 --- .../autograph/pyct/qual_names_test.py | 261 --- .../pyct/static_analysis/__init__.py | 28 - .../pyct/static_analysis/activity.py | 706 -------- .../pyct/static_analysis/activity_test.py | 868 --------- .../autograph/pyct/static_analysis/annos.py | 55 - .../pyct/static_analysis/liveness.py | 220 --- .../pyct/static_analysis/liveness_py3_test.py | 30 - .../pyct/static_analysis/liveness_test.py | 575 ------ .../static_analysis/reaching_definitions.py | 288 --- .../reaching_definitions_py3_test.py | 92 - .../reaching_definitions_test.py | 526 ------ .../pyct/static_analysis/reaching_fndefs.py | 178 -- .../static_analysis/reaching_fndefs_test.py | 54 - .../pyct/static_analysis/type_inference.py | 624 ------- .../static_analysis/type_inference_test.py | 942 ---------- .../autoqasm/autograph/pyct/templates.py | 290 --- .../autoqasm/autograph/pyct/templates_test.py | 338 ---- .../pyct/testing/basic_definitions.py | 65 - .../autograph/pyct/testing/codegen.py | 230 --- .../autograph/pyct/testing/decorators.py | 46 - .../autoqasm/autograph/pyct/transformer.py | 538 ------ .../autograph/pyct/transformer_test.py | 364 ---- .../autoqasm/autograph/pyct/transpiler.py | 495 ----- .../autograph/pyct/transpiler_test.py | 240 --- .../autograph/tf_utils/tf_decorator.py | 361 ---- .../autoqasm/autograph/tf_utils/tf_export.py | 420 ----- .../autoqasm/autograph/tf_utils/tf_stack.py | 168 -- .../autograph/tf_utils/traceback_utils.py | 157 -- .../autoqasm/converters/assignments.py | 4 +- .../autoqasm/converters/break_statements.py | 5 +- .../autoqasm/converters/comparisons.py | 5 +- .../autoqasm/converters/return_statements.py | 6 +- .../autoqasm/operators/__init__.py | 9 +- .../autoqasm/operators/assignments.py | 2 +- .../autoqasm/transpiler/transpiler.py | 44 +- .../experimental/autoqasm/mock_transpiler.py | 2 +- .../experimental/autoqasm/test_converters.py | 2 +- .../experimental/autoqasm/test_transpiler.py | 4 +- tox.ini | 4 +- 102 files changed, 38 insertions(+), 22130 deletions(-) delete mode 100644 src/braket/experimental/autoqasm/autograph/ATTRIBUTION.md delete mode 100644 src/braket/experimental/autoqasm/autograph/__init__.py delete mode 100644 src/braket/experimental/autoqasm/autograph/converters/__init__.py delete mode 100644 src/braket/experimental/autoqasm/autograph/converters/asserts.py delete mode 100644 src/braket/experimental/autoqasm/autograph/converters/break_statements.py delete mode 100644 src/braket/experimental/autoqasm/autograph/converters/call_trees.py delete mode 100644 src/braket/experimental/autoqasm/autograph/converters/conditional_expressions.py delete mode 100644 src/braket/experimental/autoqasm/autograph/converters/continue_statements.py delete mode 100644 src/braket/experimental/autoqasm/autograph/converters/control_flow.py delete mode 100644 src/braket/experimental/autoqasm/autograph/converters/control_flow_deprecated_py2.py delete mode 100644 src/braket/experimental/autoqasm/autograph/converters/directives.py delete mode 100644 src/braket/experimental/autoqasm/autograph/converters/functions.py delete mode 100644 src/braket/experimental/autoqasm/autograph/converters/list_comprehensions.py delete mode 100644 src/braket/experimental/autoqasm/autograph/converters/lists.py delete mode 100644 src/braket/experimental/autoqasm/autograph/converters/logical_expressions.py delete mode 100644 src/braket/experimental/autoqasm/autograph/converters/return_statements.py delete mode 100644 src/braket/experimental/autoqasm/autograph/converters/slices.py delete mode 100644 src/braket/experimental/autoqasm/autograph/converters/variables.py delete mode 100644 src/braket/experimental/autoqasm/autograph/core/ag_ctx.py delete mode 100644 src/braket/experimental/autoqasm/autograph/core/config.py delete mode 100644 src/braket/experimental/autoqasm/autograph/core/config_lib.py delete mode 100644 src/braket/experimental/autoqasm/autograph/core/converter.py delete mode 100644 src/braket/experimental/autoqasm/autograph/core/function_wrappers.py delete mode 100644 src/braket/experimental/autoqasm/autograph/core/unsupported_features_checker.py delete mode 100644 src/braket/experimental/autoqasm/autograph/impl/api_core.py delete mode 100644 src/braket/experimental/autoqasm/autograph/lang/directives.py delete mode 100644 src/braket/experimental/autoqasm/autograph/logging/ag_logging.py delete mode 100644 src/braket/experimental/autoqasm/autograph/operators/variables.py delete mode 100644 src/braket/experimental/autoqasm/autograph/operators/variables_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/__init__.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/anno.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/anno_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/ast_util.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/ast_util_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/cache.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/cache_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/cfg.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/cfg_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/common_transformers/__init__.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/common_transformers/anf.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/common_transformers/anf_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/error_utils.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/error_utils_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/errors.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/gast_util.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/inspect_utils.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/inspect_utils_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/inspect_utils_test.sh delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/loader.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/loader_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/naming.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/naming_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/origin_info.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/origin_info_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/parser.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/parser_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/pretty_printer.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/pretty_printer_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/qual_names.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/qual_names_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/__init__.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/activity.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/activity_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/annos.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness_py3_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions_py3_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_fndefs.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_fndefs_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/type_inference.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/static_analysis/type_inference_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/templates.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/templates_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/testing/basic_definitions.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/testing/codegen.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/testing/decorators.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/transformer.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/transformer_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/transpiler.py delete mode 100644 src/braket/experimental/autoqasm/autograph/pyct/transpiler_test.py delete mode 100644 src/braket/experimental/autoqasm/autograph/tf_utils/tf_decorator.py delete mode 100644 src/braket/experimental/autoqasm/autograph/tf_utils/tf_export.py delete mode 100644 src/braket/experimental/autoqasm/autograph/tf_utils/tf_stack.py delete mode 100644 src/braket/experimental/autoqasm/autograph/tf_utils/traceback_utils.py diff --git a/.coveragerc b/.coveragerc index 0024deed..79545ce2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -9,7 +9,6 @@ omit = **/braket/schema_common/* **/braket/task_result/* **/braket/simulator/* - **/autoqasm/autograph/* */site-packages/braket/* [paths] diff --git a/setup.cfg b/setup.cfg index 4712109f..d9dbb5b6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,7 +17,6 @@ filterwarnings= line_length = 100 multi_line_output = 3 include_trailing_comma = true -skip_glob = src/braket/experimental/autoqasm/autograph/* profile = black [flake8] @@ -39,5 +38,4 @@ exclude = .git bin build - src/braket/experimental/autoqasm/autograph venv diff --git a/setup.py b/setup.py index c02230c2..4df4d103 100644 --- a/setup.py +++ b/setup.py @@ -39,6 +39,7 @@ "boltons", "boto3>=1.28.53", "cloudpickle==2.2.1", + "diastatic-malt", "nest-asyncio", "networkx", "numpy", diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 562a59b2..b6c900ed 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -24,6 +24,8 @@ import openqasm3.ast as qasm_ast import oqpy.base +from malt.core import converter +from malt.impl.api import autograph_artifact, is_autograph_artifact import braket.experimental.autoqasm.constants as aq_constants import braket.experimental.autoqasm.instructions as aq_instructions @@ -33,12 +35,6 @@ from braket.aws import AwsDevice from braket.devices.device import Device from braket.experimental.autoqasm import errors -from braket.experimental.autoqasm.autograph.core import converter -from braket.experimental.autoqasm.autograph.impl.api_core import ( - autograph_artifact, - is_autograph_artifact, -) -from braket.experimental.autoqasm.autograph.tf_utils import tf_decorator from braket.experimental.autoqasm.program.gate_calibrations import GateCalibration from braket.experimental.autoqasm.types import QubitIdentifierType as Qubit @@ -205,8 +201,7 @@ def _wrapper(*args, **kwargs) -> Callable: if inspect.isfunction(func) or inspect.ismethod(func): _wrapper = functools.update_wrapper(_wrapper, func) - decorated_wrapper = tf_decorator.make_decorator(func, _wrapper) - return autograph_artifact(decorated_wrapper) + return autograph_artifact(_wrapper) def _autograph_optional_features() -> tuple[converter.Feature]: diff --git a/src/braket/experimental/autoqasm/autograph/ATTRIBUTION.md b/src/braket/experimental/autoqasm/autograph/ATTRIBUTION.md deleted file mode 100644 index a2dbd1cd..00000000 --- a/src/braket/experimental/autoqasm/autograph/ATTRIBUTION.md +++ /dev/null @@ -1,263 +0,0 @@ -# autograph - -The contents of this folder are copied with modification from the TensorFlow project under the Apache 2.0 license. - -** tensorflow; version 2.12.0 -- https://github.com/tensorflow/tensorflow/ - ---- - -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License 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. - -## Some of TensorFlow's code is derived from Caffe, which is subject to the following copyright notice: - -COPYRIGHT - -All contributions by the University of California: - -Copyright (c) 2014, The Regents of the University of California (Regents) -All rights reserved. - -All other contributions: - -Copyright (c) 2014, the respective contributors -All rights reserved. - -Caffe uses a shared copyright model: each contributor holds copyright over -their contributions to Caffe. The project versioning records all such -contribution and copyright details. If a contributor wants to further mark -their specific copyright on a particular contribution, they should indicate -their copyright solely in the commit message of the change when it is -committed. - -LICENSE - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -CONTRIBUTION AGREEMENT - -By contributing to the BVLC/caffe repository through pull-request, comment, -or otherwise, the contributor releases their content to the -license and copyright terms herein. - -## For TensorFlow see also this required NOTICE: - -Copyright 2016 The TensorFlow Authors. All Rights Reserved. diff --git a/src/braket/experimental/autoqasm/autograph/__init__.py b/src/braket/experimental/autoqasm/autograph/__init__.py deleted file mode 100644 index 186d2fc8..00000000 --- a/src/braket/experimental/autoqasm/autograph/__init__.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2016 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Conversion of eager-style Python into TensorFlow graph code. - -NOTE: In TensorFlow 2.0, AutoGraph is automatically applied when using -`tf.function`. This module contains lower-level APIs for advanced use. - -AutoGraph transforms a subset of Python which operates on TensorFlow objects -into equivalent TensorFlow graph code. When executing the graph, it has the same -effect as if you ran the original code in eager mode. -Python code which doesn't operate on TensorFlow objects remains functionally -unchanged, but keep in mind that `tf.function` only executes such code at trace -time, and generally will not be consistent with eager execution. - -For more information, see the -[AutoGraph reference documentation](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/autograph/g3doc/reference/index.md), -and the [tf.function guide](https://www.tensorflow.org/guide/function#autograph_transformations). -""" - -# TODO(mdan): Bring only the relevant symbols to the top level. -from braket.experimental.autoqasm.autograph import logging -from braket.experimental.autoqasm.autograph.core.converter import ConversionOptions -from braket.experimental.autoqasm.autograph.core.converter import Feature -from braket.experimental.autoqasm.autograph.impl.api_core import AutoGraphError -from braket.experimental.autoqasm.autograph.impl.api_core import StackTraceMapper -from braket.experimental.autoqasm.autograph.lang.directives import set_element_type -from braket.experimental.autoqasm.autograph.lang.directives import set_loop_options -from braket.experimental.autoqasm.autograph.logging import ag_logging - -# TODO(mdan): Revisit this list once we finalize the generated code mechanism. -_allowed_symbols = [ - # Main API - 'AutoGraphError', - 'ConversionOptions', - 'Feature', - 'StackTraceMapper', - # Python language "extensions" - 'set_element_type', - 'set_loop_options', - # Logging - 'logging', - 'ag_logging', -] diff --git a/src/braket/experimental/autoqasm/autograph/converters/__init__.py b/src/braket/experimental/autoqasm/autograph/converters/__init__.py deleted file mode 100644 index 7037b3ce..00000000 --- a/src/braket/experimental/autoqasm/autograph/converters/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2016 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Code converters used by Autograph.""" - -# Naming conventions: -# * each converter should specialize on a single idiom; be consistent with -# the Python reference for naming -# * all converters inherit core.converter.Base -# * module names describe the idiom that the converter covers, plural -# * the converter class is named consistent with the module, singular and -# includes the word Transformer -# -# Example: -# -# lists.py -# class ListTransformer(converter.Base) diff --git a/src/braket/experimental/autoqasm/autograph/converters/asserts.py b/src/braket/experimental/autoqasm/autograph/converters/asserts.py deleted file mode 100644 index 3ea9bb49..00000000 --- a/src/braket/experimental/autoqasm/autograph/converters/asserts.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2016 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Converts assert statements to their corresponding TF calls.""" - -import gast - -from braket.experimental.autoqasm.autograph.core import converter -from braket.experimental.autoqasm.autograph.pyct import templates - - -class AssertTransformer(converter.Base): - """Transforms Assert nodes to Call so they can be handled as functions.""" - - def visit_Assert(self, node): - self.generic_visit(node) - - # Note: The lone tf.Assert call will be wrapped with control_dependencies - # by side_effect_guards. - template = """ - ag__.assert_stmt(test, lambda: msg) - """ - - if node.msg is None: - return templates.replace( - template, - test=node.test, - msg=gast.Constant('Assertion error', kind=None)) - elif isinstance(node.msg, gast.Constant): - return templates.replace(template, test=node.test, msg=node.msg) - else: - raise NotImplementedError('can only convert string messages for now.') - - -def transform(node, ctx): - node = AssertTransformer(ctx).visit(node) - return node diff --git a/src/braket/experimental/autoqasm/autograph/converters/break_statements.py b/src/braket/experimental/autoqasm/autograph/converters/break_statements.py deleted file mode 100644 index 96945df6..00000000 --- a/src/braket/experimental/autoqasm/autograph/converters/break_statements.py +++ /dev/null @@ -1,185 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Lowers break statements to conditionals.""" - -from braket.experimental.autoqasm.autograph.core import converter -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import qual_names -from braket.experimental.autoqasm.autograph.pyct import templates -from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity -from braket.experimental.autoqasm.autograph.pyct.static_analysis.annos import NodeAnno - - -class _Break(object): - - def __init__(self): - self.used = False - self.control_var_name = None - - def __repr__(self): - return 'used: %s, var: %s' % (self.used, self.control_var_name) - - -class BreakTransformer(converter.Base): - """Canonicalizes break statements into additional conditionals.""" - - def visit_Break(self, node): - self.state[_Break].used = True - var_name = self.state[_Break].control_var_name - # TODO(mdan): This will fail when expanded inside a top-level else block. - template = """ - var_name = True - continue - """ - return templates.replace(template, var_name=var_name) - - def _guard_if_present(self, block, var_name): - """Prevents the block from executing if var_name is set.""" - if not block: - return block - - template = """ - if not var_name: - block - """ - node = templates.replace( - template, - var_name=var_name, - block=block) - return node - - def _process_body(self, nodes, break_var): - self.state[_Break].enter() - self.state[_Break].control_var_name = break_var - nodes = self.visit_block(nodes) - break_used = self.state[_Break].used - self.state[_Break].exit() - return nodes, break_used - - def visit_While(self, node): - original_node = node - scope = anno.getanno(node, NodeAnno.BODY_SCOPE) - break_var = self.ctx.namer.new_symbol('break_', scope.referenced) - - node.test = self.visit(node.test) - node.body, break_used = self._process_body(node.body, break_var) - # A break in the else clause applies to the containing scope. - node.orelse = self.visit_block(node.orelse) - - if not break_used: - template = """ - while test: - body - orelse - """ - node = templates.replace( - template, test=node.test, body=node.body, orelse=node.orelse) - - new_while_node = node[0] - anno.copyanno(original_node, new_while_node, anno.Basic.DIRECTIVES) - - return node - - # Python's else clause only triggers if the loop exited cleanly (e.g. - # break did not trigger). - guarded_orelse = self._guard_if_present(node.orelse, break_var) - - template = """ - var_name = False - while not var_name and test: - body - orelse - """ - node = templates.replace( - template, - var_name=break_var, - test=node.test, - body=node.body, - orelse=guarded_orelse) - - new_while_node = node[1] - anno.copyanno(original_node, new_while_node, anno.Basic.DIRECTIVES) - - return node - - def visit_For(self, node): - original_node = node - scope = anno.getanno(node, NodeAnno.BODY_SCOPE) - break_var = self.ctx.namer.new_symbol('break_', scope.referenced) - - node.target = self.visit(node.target) - node.iter = self.visit(node.iter) - node.body, break_used = self._process_body(node.body, break_var) - # A break in the else clause applies to the containing scope. - node.orelse = self.visit_block(node.orelse) - - if not break_used: - template = """ - for target in iter_: - body - orelse - """ - node = templates.replace( - template, - iter_=node.iter, - target=node.target, - body=node.body, - orelse=node.orelse) - - new_for_node = node[0] - anno.copyanno(original_node, new_for_node, anno.Basic.EXTRA_LOOP_TEST) - anno.copyanno(original_node, new_for_node, anno.Basic.DIRECTIVES) - - return node - - # Python's else clause only triggers if the loop exited cleanly (e.g. - # break did not trigger). - guarded_orelse = self._guard_if_present(node.orelse, break_var) - extra_test = templates.replace_as_expression( - 'not var_name', var_name=break_var) - - # The extra test is hidden in the AST, which will confuse the static - # analysis. To mitigate that, we insert a no-op statement that ensures - # the control variable is marked as used. - # TODO(mdan): Use a marker instead, e.g. ag__.condition_loop_on(var_name) - template = """ - var_name = False - for target in iter_: - (var_name,) - body - orelse - """ - node = templates.replace( - template, - var_name=break_var, - iter_=node.iter, - target=node.target, - body=node.body, - orelse=guarded_orelse) - - new_for_node = node[1] - anno.setanno(new_for_node, anno.Basic.EXTRA_LOOP_TEST, extra_test) - anno.copyanno(original_node, new_for_node, anno.Basic.DIRECTIVES) - - return node - - -def transform(node, ctx): - node = qual_names.resolve(node) - node = activity.resolve(node, ctx, None) - - transformer = BreakTransformer(ctx) - node = transformer.visit(node) - return node diff --git a/src/braket/experimental/autoqasm/autograph/converters/call_trees.py b/src/braket/experimental/autoqasm/autograph/converters/call_trees.py deleted file mode 100644 index d2ae58e5..00000000 --- a/src/braket/experimental/autoqasm/autograph/converters/call_trees.py +++ /dev/null @@ -1,221 +0,0 @@ -# Copyright 2016 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Handles function calls, by generating compiled function names and calls. - -Note: this transformer does not rename the top level object being converted; -that is the caller's responsibility. - -Requires function_scopes. -""" - -import gast - -from braket.experimental.autoqasm.autograph.core import converter -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import qual_names -from braket.experimental.autoqasm.autograph.pyct import templates -from braket.experimental.autoqasm.autograph.logging import ag_logging - - -# TODO(mdan): Rename to FunctionCallsTransformer. - - -class _Function(object): - - no_root = True - - def __init__(self): - self.context_name = None - - -set_trace_warned = False - - -class _ArgTemplateBuilder(object): - """Constructs a tuple representing the positional arguments in a call. - - Example (yes, it's legal Python 3): - - f(*args1, b, *args2, c, d) -> args1 + (b,) + args2 + (c, d) - """ - - def __init__(self): - self._arg_accumulator = [] - self._argspec = [] - self._finalized = False - - def _consume_args(self): - if self._arg_accumulator: - self._argspec.append( - gast.Tuple(elts=self._arg_accumulator, ctx=gast.Load())) - self._arg_accumulator = [] - - def add_arg(self, a): - self._arg_accumulator.append(a) - - def add_stararg(self, a): - self._consume_args() - self._argspec.append( - gast.Call( - gast.Name( - 'tuple', ctx=gast.Load(), annotation=None, type_comment=None), - args=[a], - keywords=())) - - def finalize(self): - self._consume_args() - self._finalized = True - - def to_ast(self): - assert self._finalized - if self._argspec: - result = self._argspec[0] - for i in range(1, len(self._argspec)): - result = gast.BinOp(result, gast.Add(), self._argspec[i]) - return result - return gast.Tuple([], gast.Load()) - - -class CallTreeTransformer(converter.Base): - """Transforms the call tree by renaming transformed symbols.""" - - def visit_Lambda(self, node): - if not anno.hasanno(node, 'function_context_name'): - # Lambda functions created during the conversion process have no - # context manager. - return self.generic_visit(node) - with self.state[_Function] as fn_scope: - fn_scope.context_name = anno.getanno(node, 'function_context_name') - return self.generic_visit(node) - - def visit_FunctionDef(self, node): - # Decorators and arg defaults are part of the outer scope. - node.decorator_list = self.visit_block(node.decorator_list) - node.args.defaults = self.visit_block(node.args.defaults) - for i, d in enumerate(node.args.kw_defaults): - if d is not None: - node.args.kw_defaults[i] = self.visit(d) - with self.state[_Function] as fn_scope: - # Note: if the conversion process ever creates helper functions, this - # assumption will no longer hold. - assert anno.hasanno(node, 'function_context_name'), ( - 'The function_scopes converter always creates a scope for functions.') - fn_scope.context_name = anno.getanno(node, 'function_context_name') - node.body = self.visit_block(node.body) - if node.returns: - node.returns = self.visit(node.returns) - return node - - def visit_With(self, node): - # Context manager calls (in node.items) are not converted. - node.body = self.visit_block(node.body) - return node - - def _args_to_tuple(self, node): - """Ties together all positional and *arg arguments in a single tuple.""" - # TODO(mdan): We could rewrite this to just a call to tuple(). Maybe better? - # For example for - # f(a, b, *args) - # instead of writing: - # (a, b) + args - # just write this? - # tuple(a, b, *args) - builder = _ArgTemplateBuilder() - for a in node.args: - if isinstance(a, gast.Starred): - builder.add_stararg(a.value) - else: - builder.add_arg(a) - builder.finalize() - return builder.to_ast() - - def _kwargs_to_dict(self, node): - """Ties together all keyword and **kwarg arguments in a single dict.""" - if node.keywords: - return gast.Call( - gast.Name( - 'dict', ctx=gast.Load(), annotation=None, type_comment=None), - args=(), - keywords=node.keywords) - else: - return parser.parse_expression('None') - - def visit_Call(self, node): - full_name = str(anno.getanno(node.func, anno.Basic.QN, default='')) - function_context_name = self.state[_Function].context_name - node = self.generic_visit(node) - - # TODO(mdan): Refactor converted_call as a 'Call' operator. - - # Calls to the internal 'ag__' module are never converted (though their - # arguments might be). - if full_name.startswith('ag__.'): - return node - - # Calls to the function context manager (inserted by function_scopes) are - # also safe. - if full_name.startswith(function_context_name + '.'): - return node - - # Calls to pdb.set_trace or ipdb.set_trace are never converted. We don't use - # the normal mechanisms to bypass these literals because they are sensitive - # to the frame they are being called from. - # TODO(mdan): Generalize this to a "static allowlist" config. - if full_name in ('pdb.set_trace', 'ipdb.set_trace', 'breakpoint'): - global set_trace_warned - if not set_trace_warned: - # TODO(mdan): Update and shorten once available on tensorflow.org. - ag_logging.warning( - 'Detected `pdb.set_trace()` in user code. The code' - ' generated by AutoGraph is not optimized for step-by-step' - ' debugging. See https://github.com/tensorflow/tensorflow/' - 'blob/master/tensorflow/python/autograph/g3doc/reference/' - 'debugging.md.') - set_trace_warned = True - return node - - if (full_name == 'print' and - not self.ctx.user.options.uses(converter.Feature.BUILTIN_FUNCTIONS)): - return node - - template = """ - ag__.converted_call(func, args, kwargs, function_ctx) - """ - new_call = templates.replace_as_expression( - template, - func=node.func, - args=self._args_to_tuple(node), - kwargs=self._kwargs_to_dict(node), - function_ctx=function_context_name) - - return new_call - - -def transform(node, ctx): - """Transform function call to the compiled counterparts. - - Args: - node: AST - ctx: EntityContext - Returns: - A tuple (node, new_names): - node: The transformed AST - new_names: set(string), containing any newly-generated names - """ - node = qual_names.resolve(node) - - node = CallTreeTransformer(ctx).visit(node) - return node diff --git a/src/braket/experimental/autoqasm/autograph/converters/conditional_expressions.py b/src/braket/experimental/autoqasm/autograph/converters/conditional_expressions.py deleted file mode 100644 index cfd1337c..00000000 --- a/src/braket/experimental/autoqasm/autograph/converters/conditional_expressions.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Converts the ternary conditional operator.""" - -import gast - -from braket.experimental.autoqasm.autograph.core import converter -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import templates - - -class ConditionalExpressionTransformer(converter.Base): - """Converts conditional expressions to functional form.""" - - def visit_IfExp(self, node): - template = ''' - ag__.if_exp( - test, - lambda: true_expr, - lambda: false_expr, - expr_repr) - ''' - expr_repr = parser.unparse(node.test, include_encoding_marker=False).strip() - return templates.replace_as_expression( - template, - test=node.test, - true_expr=node.body, - false_expr=node.orelse, - expr_repr=gast.Constant(expr_repr, kind=None)) - - -def transform(node, ctx): - node = ConditionalExpressionTransformer(ctx).visit(node) - return node diff --git a/src/braket/experimental/autoqasm/autograph/converters/continue_statements.py b/src/braket/experimental/autoqasm/autograph/converters/continue_statements.py deleted file mode 100644 index 0dce9fb2..00000000 --- a/src/braket/experimental/autoqasm/autograph/converters/continue_statements.py +++ /dev/null @@ -1,164 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Canonicalizes continue statements by de-sugaring into a control boolean.""" - -from braket.experimental.autoqasm.autograph.core import converter -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import qual_names -from braket.experimental.autoqasm.autograph.pyct import templates -from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity -from braket.experimental.autoqasm.autograph.pyct.static_analysis.annos import NodeAnno - - -class _Continue(object): - - def __init__(self): - self.used = False - self.control_var_name = None - - def __repr__(self): - return '<_Continue(used: {}, var: {})>'.format(self.used, - self.control_var_name) - - -class _Block(object): - """Tracks information about lexical blocks as they are visited in the AST. - - Mainly, this object tracks the creation of block guards that replace - `continue` statements (e.g. `if not continue_:`). - - Attributes: - create_guard_current: bool, whether to create a guard for the current - statement. - create_guard_next: bool, whether to create a guard for the next - statement. - is_loop_type: bool, whether this block is the body of a loop. - """ - - def __init__(self): - self.is_loop_type = False - self.create_guard_current = False - self.create_guard_next = False - - -class ContinueCanonicalizationTransformer(converter.Base): - """Canonicalizes continue statements into additional conditionals.""" - - def visit_Continue(self, node): - self.state[_Continue].used = True - for block in reversed(self.state[_Block].stack): - # See ContinueCanonicalizationTest.test_multiple_continues for an example - # it's necessary to create guards for all enclosing affected blocks, not - # just that of the current block. - block.create_guard_next = True - if block.is_loop_type: - # continue only affects the innermost loop - break - template = """ - var_name = True - """ - return templates.replace( - template, var_name=self.state[_Continue].control_var_name) - - def _postprocess_statement(self, node): - if self.state[_Continue].used: - block = self.state[_Block] - should_wrap_current = block.create_guard_current - # After processing propagate whether to guard the next statement - block.create_guard_current = block.create_guard_next - block.create_guard_next = False - if should_wrap_current: - template = """ - if not var_name: - original_node - """ - cond, = templates.replace( - template, - var_name=self.state[_Continue].control_var_name, - original_node=node) - return cond, cond.body - return node, None - - def _visit_loop_body(self, node, nodes): - self.state[_Continue].enter() - self.state[_Block].enter() - self.state[_Block].is_loop_type = True - scope = anno.getanno(node, NodeAnno.BODY_SCOPE) - continue_var = self.ctx.namer.new_symbol('continue_', scope.referenced) - self.state[_Continue].control_var_name = continue_var - - nodes = self.visit_block(nodes, after_visit=self._postprocess_statement) - - if self.state[_Continue].used: - template = """ - var_name = False - """ - control_var_init = templates.replace(template, var_name=continue_var) - nodes = control_var_init + nodes - - self.state[_Block].exit() - self.state[_Continue].exit() - return nodes - - def _visit_non_loop_body(self, nodes): - self.state[_Block].enter() - nodes = self.visit_block(nodes, after_visit=self._postprocess_statement) - self.state[_Block].exit() - return nodes - - def visit_While(self, node): - node.test = self.visit(node.test) - node.body = self._visit_loop_body(node, node.body) - # A continue in the else clause applies to the containing scope. - node.orelse = self._visit_non_loop_body(node.orelse) - return node - - def visit_For(self, node): - node.target = self.generic_visit(node.target) - node.iter = self.generic_visit(node.iter) - node.body = self._visit_loop_body(node, node.body) - # A continue in the else clause applies to the containing scope. - node.orelse = self._visit_non_loop_body(node.orelse) - return node - - def visit_If(self, node): - node.body = self._visit_non_loop_body(node.body) - node.orelse = self._visit_non_loop_body(node.orelse) - return node - - def visit_With(self, node): - node.items = self.visit_block(node.items) - node.body = self._visit_non_loop_body(node.body) - return node - - def visit_Try(self, node): - node.body = self._visit_non_loop_body(node.body) - node.orelse = self._visit_non_loop_body(node.orelse) - # In Python 3.8 and later continue is allowed in finally blocks - node.finalbody = self._visit_non_loop_body(node.finalbody) - node.handlers = self.visit_block(node.handlers) - return node - - def visit_ExceptHandler(self, node): - node.body = self._visit_non_loop_body(node.body) - return node - - -def transform(node, ctx): - node = qual_names.resolve(node) - node = activity.resolve(node, ctx, None) - - node = ContinueCanonicalizationTransformer(ctx).visit(node) - return node diff --git a/src/braket/experimental/autoqasm/autograph/converters/control_flow.py b/src/braket/experimental/autoqasm/autograph/converters/control_flow.py deleted file mode 100644 index 4bb1b9b3..00000000 --- a/src/braket/experimental/autoqasm/autograph/converters/control_flow.py +++ /dev/null @@ -1,413 +0,0 @@ -# Copyright 2016 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Handles control flow statements: while, for, if.""" - -import gast - -from braket.experimental.autoqasm.autograph.core import converter -from braket.experimental.autoqasm.autograph.lang import directives -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import cfg -from braket.experimental.autoqasm.autograph.pyct import origin_info -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import qual_names -from braket.experimental.autoqasm.autograph.pyct import templates -from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity -from braket.experimental.autoqasm.autograph.pyct.static_analysis import annos -from braket.experimental.autoqasm.autograph.pyct.static_analysis import liveness -from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_definitions -from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_fndefs - - -class _Function(object): - - scope = None - - -class ControlFlowTransformer(converter.Base): - """Transforms control flow structures like loops an conditionals.""" - - def visit_Lambda(self, node): - with self.state[_Function] as fn: - fn.scope = anno.getanno(node, anno.Static.SCOPE) - return self.generic_visit(node) - - def visit_FunctionDef(self, node): - with self.state[_Function] as fn: - fn.scope = anno.getanno(node, annos.NodeAnno.BODY_SCOPE) - return self.generic_visit(node) - - def _create_nonlocal_declarations(self, vars_): - vars_ = set(vars_) - results = [] - global_vars = self.state[_Function].scope.globals & vars_ - - if global_vars: - results.append(gast.Global([str(v) for v in global_vars])) - - nonlocal_vars = [ - v for v in vars_ if not v.is_composite() and v not in global_vars] - if nonlocal_vars: - results.append(gast.Nonlocal([str(v) for v in nonlocal_vars])) - - return results - - def _create_state_functions( - self, block_vars, nonlocal_declarations, getter_name, setter_name): - if not block_vars: - template = """ - def getter_name(): - return () - def setter_name(block_vars): - pass - """ - return templates.replace( - template, getter_name=getter_name, setter_name=setter_name) - - guarded_block_vars = [] - for v in block_vars: - if v.is_simple(): - guarded_block_vars.append(v) - else: - guarded_block_vars.append( - templates.replace_as_expression( - 'ag__.ldu(lambda: var_, name)', - var_=v, - name=gast.Constant(str(v), kind=None))) - - template = """ - def getter_name(): - return guarded_state_vars, - def setter_name(vars_): - nonlocal_declarations - state_vars, = vars_ - """ - return templates.replace( - template, - nonlocal_declarations=nonlocal_declarations, - getter_name=getter_name, - guarded_state_vars=guarded_block_vars, - setter_name=setter_name, - state_vars=tuple(block_vars)) - - def _create_loop_options(self, node): - if not anno.hasanno(node, anno.Basic.DIRECTIVES): - return gast.Dict([], []) - - loop_directives = anno.getanno(node, anno.Basic.DIRECTIVES) - if directives.set_loop_options not in loop_directives: - return gast.Dict([], []) - - opts_dict = loop_directives[directives.set_loop_options] - str_keys, values = zip(*opts_dict.items()) - keys = [gast.Constant(s, kind=None) for s in str_keys] - values = list(values) # ast and gast don't play well with tuples. - return gast.Dict(keys, values) - - def _create_undefined_assigns(self, undefined_symbols): - assignments = [] - for s in undefined_symbols: - template = ''' - var = ag__.Undefined(symbol_name) - ''' - assignments += templates.replace( - template, - var=s, - symbol_name=gast.Constant(s.ssf(), kind=None)) - return assignments - - def _get_block_basic_vars(self, modified, live_in, live_out): - nonlocals = self.state[_Function].scope.nonlocals - basic_scope_vars = [] - for s in modified: - if s.is_composite(): - # TODO(mdan): Raise an error when this happens for a TF scope. - continue - # Variables not live into or out of the scope are considered local to the - # scope. - if s in live_in or s in live_out or s in nonlocals: - basic_scope_vars.append(s) - continue - return frozenset(basic_scope_vars) - - def _get_block_composite_vars(self, modified, live_in): - # The scope variables corresponding to composite symbols (e.g. `self.x`). - composite_scope_vars = [] - for s in modified: - if not s.is_composite(): - continue - # Mutations made to objects created inside the scope will appear as writes - # to composite symbols. Because these mutations appear as modifications - # made to composite symbols, we check whether the composite's parent is - # actually live into the scope. - # Example: - # while cond: - # x = Foo() - # x.foo = 2 * x.foo # x.foo is live into the scope, but x is not. - # - # Note that some parents might not be symbols - for example, in x['foo'], - # 'foo' is a parent, but it's a literal, not a symbol. We don't check the - # liveness of literals. - support_set_symbols = tuple( - sss for sss in s.support_set if sss.is_symbol()) - if not all(sss in live_in for sss in support_set_symbols): - continue - composite_scope_vars.append(s) - return frozenset(composite_scope_vars) - - def _get_block_vars(self, node, modified): - """Determines the variables affected inside a control flow statement.""" - defined_in = anno.getanno(node, anno.Static.DEFINED_VARS_IN) - live_in = anno.getanno(node, anno.Static.LIVE_VARS_IN) - live_out = anno.getanno(node, anno.Static.LIVE_VARS_OUT) - fn_scope = self.state[_Function].scope - - basic_scope_vars = self._get_block_basic_vars( - modified, - live_in, - live_out) - composite_scope_vars = self._get_block_composite_vars(modified, live_in) - scope_vars = tuple(basic_scope_vars | composite_scope_vars) - - # Variables that are modified inside the scope, but not defined - # before entering it. Only simple variables must be defined. The - # composite ones will be implicitly checked at runtime. - possibly_undefined = ( - modified - defined_in - fn_scope.globals - fn_scope.nonlocals) - undefined = tuple(v for v in possibly_undefined if not v.is_composite()) - - # Variables that are modified inside the scope, and depend on values outside - # it. - input_only = basic_scope_vars & live_in - live_out - - # Place the outputs first, then sort lexicographically. - scope_vars = sorted(scope_vars, key=lambda v: (v in input_only, v)) - nouts = len(scope_vars) - len(input_only) - - return scope_vars, undefined, nouts - - def visit_If(self, node): - node = self.generic_visit(node) - body_scope = anno.getanno(node, annos.NodeAnno.BODY_SCOPE) - orelse_scope = anno.getanno(node, annos.NodeAnno.ORELSE_SCOPE) - - cond_vars, undefined, nouts = self._get_block_vars( - node, body_scope.bound | orelse_scope.bound) - - undefined_assigns = self._create_undefined_assigns(undefined) - - nonlocal_declarations = self._create_nonlocal_declarations(cond_vars) - - reserved = body_scope.referenced | orelse_scope.referenced - state_getter_name = self.ctx.namer.new_symbol('get_state', reserved) - state_setter_name = self.ctx.namer.new_symbol('set_state', reserved) - state_functions = self._create_state_functions( - cond_vars, nonlocal_declarations, state_getter_name, state_setter_name) - - orelse_body = node.orelse - if not orelse_body: - orelse_body = [gast.Pass()] - - template = """ - state_functions - def body_name(): - nonlocal_declarations - body - def orelse_name(): - nonlocal_declarations - orelse - undefined_assigns - ag__.if_stmt( - test, - body_name, - orelse_name, - state_getter_name, - state_setter_name, - (symbol_names,), - nouts) - """ - new_nodes = templates.replace( - template, - body=node.body, - body_name=self.ctx.namer.new_symbol('if_body', reserved), - orelse=orelse_body, - orelse_name=self.ctx.namer.new_symbol('else_body', reserved), - nonlocal_declarations=nonlocal_declarations, - nouts=gast.Constant(nouts, kind=None), - state_functions=state_functions, - state_getter_name=state_getter_name, - state_setter_name=state_setter_name, - symbol_names=tuple(gast.Constant(str(s), kind=None) for s in cond_vars), - test=node.test, - undefined_assigns=undefined_assigns) - origin_info.copy_origin(node, new_nodes[-1]) - return new_nodes - - def visit_While(self, node): - node = self.generic_visit(node) - body_scope = anno.getanno(node, annos.NodeAnno.BODY_SCOPE) - - loop_vars, undefined, _ = self._get_block_vars(node, body_scope.bound) - - undefined_assigns = self._create_undefined_assigns(undefined) - - nonlocal_declarations = self._create_nonlocal_declarations(loop_vars) - - reserved = body_scope.referenced - state_getter_name = self.ctx.namer.new_symbol('get_state', reserved) - state_setter_name = self.ctx.namer.new_symbol('set_state', reserved) - state_functions = self._create_state_functions( - loop_vars, nonlocal_declarations, state_getter_name, state_setter_name) - - opts = self._create_loop_options(node) - - template = """ - state_functions - def body_name(): - nonlocal_declarations - body - def test_name(): - return test - undefined_assigns - ag__.while_stmt( - test_name, - body_name, - state_getter_name, - state_setter_name, - (symbol_names,), - opts) - """ - new_nodes = templates.replace( - template, - body=node.body, - body_name=self.ctx.namer.new_symbol('loop_body', reserved), - nonlocal_declarations=nonlocal_declarations, - opts=opts, - state_functions=state_functions, - state_getter_name=state_getter_name, - state_setter_name=state_setter_name, - symbol_names=tuple(gast.Constant(str(s), kind=None) for s in loop_vars), - test=node.test, - test_name=self.ctx.namer.new_symbol('loop_test', reserved), - undefined_assigns=undefined_assigns) - origin_info.copy_origin(node, new_nodes[-1]) - return new_nodes - - def visit_For(self, node): - node = self.generic_visit(node) - body_scope = anno.getanno(node, annos.NodeAnno.BODY_SCOPE) - iter_scope = anno.getanno(node, annos.NodeAnno.ITERATE_SCOPE) - - loop_vars, undefined, _ = self._get_block_vars( - node, body_scope.bound | iter_scope.bound) - - undefined_assigns = self._create_undefined_assigns(undefined) - - nonlocal_declarations = self._create_nonlocal_declarations(loop_vars) - - reserved = body_scope.referenced | iter_scope.referenced - state_getter_name = self.ctx.namer.new_symbol('get_state', reserved) - state_setter_name = self.ctx.namer.new_symbol('set_state', reserved) - state_functions = self._create_state_functions( - loop_vars, nonlocal_declarations, state_getter_name, state_setter_name) - - opts = self._create_loop_options(node) - opts.keys.append(gast.Constant('iterate_names', kind=None)) - opts.values.append(gast.Constant( - parser.unparse(node.target, include_encoding_marker=False), kind=None)) - - if anno.hasanno(node, anno.Basic.EXTRA_LOOP_TEST): - extra_test = anno.getanno(node, anno.Basic.EXTRA_LOOP_TEST) - extra_test_name = self.ctx.namer.new_symbol( - 'extra_test', reserved) - template = """ - def extra_test_name(): - nonlocal_declarations - return extra_test_expr - """ - extra_test_function = templates.replace( - template, - extra_test_expr=extra_test, - extra_test_name=extra_test_name, - loop_vars=loop_vars, - nonlocal_declarations=nonlocal_declarations) - else: - extra_test_name = parser.parse_expression('None') - extra_test_function = [] - - # iterate_arg_name holds a single arg with the iterates, which may be a - # tuple. - iterate_arg_name = self.ctx.namer.new_symbol('itr', reserved) - template = """ - iterates = iterate_arg_name - """ - iterate_expansion = templates.replace( - template, iterate_arg_name=iterate_arg_name, iterates=node.target) - origin_info.copy_origin(node, iterate_expansion) - - template = """ - state_functions - def body_name(iterate_arg_name): - nonlocal_declarations - iterate_expansion - body - extra_test_function - undefined_assigns - ag__.for_stmt( - iterated, - extra_test_name, - body_name, - state_getter_name, - state_setter_name, - (symbol_names,), - opts) - """ - new_nodes = templates.replace( - template, - body=node.body, - body_name=self.ctx.namer.new_symbol('loop_body', reserved), - extra_test_function=extra_test_function, - extra_test_name=extra_test_name, - iterate_arg_name=iterate_arg_name, - iterate_expansion=iterate_expansion, - iterated=node.iter, - nonlocal_declarations=nonlocal_declarations, - opts=opts, - symbol_names=tuple(gast.Constant(str(s), kind=None) for s in loop_vars), - state_functions=state_functions, - state_getter_name=state_getter_name, - state_setter_name=state_setter_name, - undefined_assigns=undefined_assigns) - origin_info.copy_origin(node, new_nodes[-1]) - return new_nodes - - -class AnnotatedDef(reaching_definitions.Definition): - - def __init__(self): - super(AnnotatedDef, self).__init__() - self.directives = {} - - -def transform(node, ctx): - graphs = cfg.build(node) - node = qual_names.resolve(node) - node = activity.resolve(node, ctx, None) - node = reaching_definitions.resolve(node, ctx, graphs) - node = reaching_fndefs.resolve(node, ctx, graphs) - node = liveness.resolve(node, ctx, graphs) - - node = ControlFlowTransformer(ctx).visit(node) - return node diff --git a/src/braket/experimental/autoqasm/autograph/converters/control_flow_deprecated_py2.py b/src/braket/experimental/autoqasm/autograph/converters/control_flow_deprecated_py2.py deleted file mode 100644 index 575d64da..00000000 --- a/src/braket/experimental/autoqasm/autograph/converters/control_flow_deprecated_py2.py +++ /dev/null @@ -1,635 +0,0 @@ -# Copyright 2016 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Handles control flow statements: while, for, if. - -Python 2 compatibility version. Not maintained. -""" - -import gast - -from braket.experimental.autoqasm.autograph.core import converter -from braket.experimental.autoqasm.autograph.lang import directives -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import ast_util -from braket.experimental.autoqasm.autograph.pyct import cfg -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import qual_names -from braket.experimental.autoqasm.autograph.pyct import templates -from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity -from braket.experimental.autoqasm.autograph.pyct.static_analysis import annos -from braket.experimental.autoqasm.autograph.pyct.static_analysis import liveness -from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_definitions -from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_fndefs - - -# TODO(mdan): Refactor functions to make them smaller. - - -class ControlFlowTransformer(converter.Base): - """Transforms control flow structures like loops an conditionals.""" - - def _create_cond_branch(self, body_name, aliased_orig_names, - aliased_new_names, body, returns): - if len(returns) == 1: - template = """ - return retval - """ - return_stmt = templates.replace(template, retval=returns[0]) - else: - template = """ - return (retvals,) - """ - return_stmt = templates.replace(template, retvals=returns) - - if aliased_orig_names: - alias_declarations = [] - for new_name, old_name in zip(aliased_new_names, aliased_orig_names): - template = """ - try: - aliased_new_name = aliased_orig_name - except NameError: - aliased_new_name = ag__.Undefined(symbol_name) - """ - - alias_declarations.extend( - templates.replace( - template, - aliased_new_name=new_name, - aliased_orig_name=old_name, - symbol_name=gast.Constant(str(old_name), kind=None))) - - template = """ - def body_name(): - alias_declarations - body - return_stmt - """ - return templates.replace( - template, - alias_declarations=alias_declarations, - body_name=body_name, - body=body, - return_stmt=return_stmt) - else: - template = """ - def body_name(): - body - return_stmt - """ - return templates.replace( - template, body_name=body_name, body=body, return_stmt=return_stmt) - - def _create_cond_expr(self, results, test, body_name, orelse_name, - state_getter_name, state_setter_name, - basic_symbol_names, composite_symbol_names): - if results is not None: - template = """ - results = ag__.if_stmt(test, body_name, orelse_name, - state_getter_name, state_setter_name, - (basic_symbol_names,), - (composite_symbol_names,)) - """ - return templates.replace( - template, - test=test, - results=results, - body_name=body_name, - orelse_name=orelse_name, - state_getter_name=state_getter_name, - state_setter_name=state_setter_name, - basic_symbol_names=basic_symbol_names, - composite_symbol_names=composite_symbol_names) - else: - template = """ - ag__.if_stmt(test, body_name, orelse_name, getter_name, setter_name, - (basic_symbol_names,), (composite_symbol_names,)) - """ - return templates.replace( - template, - test=test, - body_name=body_name, - orelse_name=orelse_name, - getter_name=state_getter_name, - setter_name=state_setter_name, - basic_symbol_names=basic_symbol_names, - composite_symbol_names=composite_symbol_names) - - def _fmt_symbols(self, symbol_set): - if not symbol_set: - return 'no variables' - return ', '.join(map(str, symbol_set)) - - def _determine_aliased_symbols(self, scope, node_defined_in): - modified_live = scope.modified & node_defined_in - # Composite symbols are handled elsewhere see _create_state_functions - return {s for s in modified_live if not s.is_composite()} - - def _create_state_functions(self, composites, state_getter_name, - state_setter_name): - - if composites: - composite_tuple = tuple(composites) - - template = """ - def state_getter_name(): - return composite_tuple, - def state_setter_name(vals): - composite_tuple, = vals - """ - node = templates.replace( - template, - state_getter_name=state_getter_name, - state_setter_name=state_setter_name, - composite_tuple=composite_tuple) - else: - template = """ - def state_getter_name(): - return () - def state_setter_name(_): - pass - """ - node = templates.replace( - template, - state_getter_name=state_getter_name, - state_setter_name=state_setter_name) - - return node - - def _create_loop_options(self, node): - if not anno.hasanno(node, anno.Basic.DIRECTIVES): - return gast.Dict([], []) - - loop_directives = anno.getanno(node, anno.Basic.DIRECTIVES) - if directives.set_loop_options not in loop_directives: - return gast.Dict([], []) - - opts_dict = loop_directives[directives.set_loop_options] - str_keys, values = zip(*opts_dict.items()) - keys = [gast.Constant(s, kind=None) for s in str_keys] - values = list(values) # ast and gast don't play well with tuples. - return gast.Dict(keys, values) - - def _create_undefined_assigns(self, undefined_symbols): - assignments = [] - for s in undefined_symbols: - template = ''' - var = ag__.Undefined(symbol_name) - ''' - assignments += templates.replace( - template, - var=s, - symbol_name=gast.Constant(s.ssf(), kind=None)) - return assignments - - def visit_If(self, node): - body_scope = anno.getanno(node, annos.NodeAnno.BODY_SCOPE) - orelse_scope = anno.getanno(node, annos.NodeAnno.ORELSE_SCOPE) - defined_in = anno.getanno(node, anno.Static.DEFINED_VARS_IN) - live_out = anno.getanno(node, anno.Static.LIVE_VARS_OUT) - - # Note: this information needs to be extracted before the body conversion - # that happens in the call to generic_visit below, because the conversion - # generates nodes that lack static analysis annotations. - need_alias_in_body = self._determine_aliased_symbols( - body_scope, defined_in) - need_alias_in_orelse = self._determine_aliased_symbols( - orelse_scope, defined_in) - - node = self.generic_visit(node) - - modified_in_cond = body_scope.modified | orelse_scope.modified - returned_from_cond = set() - composites = set() - for s in modified_in_cond: - if s in live_out and not s.is_composite(): - returned_from_cond.add(s) - if s.is_composite(): - # Special treatment for compound objects, always return them. - # This allows special handling within the if_stmt itself. - # For example, in TensorFlow we need to restore the state of composite - # symbols to ensure that only effects from the executed branch are seen. - composites.add(s) - - created_in_body = body_scope.modified & returned_from_cond - defined_in - created_in_orelse = orelse_scope.modified & returned_from_cond - defined_in - - basic_created_in_body = tuple( - s for s in created_in_body if not s.is_composite()) - basic_created_in_orelse = tuple( - s for s in created_in_orelse if not s.is_composite()) - - # These variables are defined only in a single branch. This is fine in - # Python so we pass them through. Another backend, e.g. Tensorflow, may need - # to handle these cases specially or throw an Error. - possibly_undefined = (set(basic_created_in_body) ^ - set(basic_created_in_orelse)) - - # Alias the closure variables inside the conditional functions, to allow - # the functions access to the respective variables. - # We will alias variables independently for body and orelse scope, - # because different branches might write different variables. - aliased_body_orig_names = tuple(need_alias_in_body) - aliased_orelse_orig_names = tuple(need_alias_in_orelse) - aliased_body_new_names = tuple( - self.ctx.namer.new_symbol(s.ssf(), body_scope.referenced) - for s in aliased_body_orig_names) - aliased_orelse_new_names = tuple( - self.ctx.namer.new_symbol(s.ssf(), orelse_scope.referenced) - for s in aliased_orelse_orig_names) - - alias_body_map = dict(zip(aliased_body_orig_names, aliased_body_new_names)) - alias_orelse_map = dict( - zip(aliased_orelse_orig_names, aliased_orelse_new_names)) - - node_body = ast_util.rename_symbols(node.body, alias_body_map) - node_orelse = ast_util.rename_symbols(node.orelse, alias_orelse_map) - - cond_var_name = self.ctx.namer.new_symbol('cond', body_scope.referenced) - body_name = self.ctx.namer.new_symbol('if_true', body_scope.referenced) - orelse_name = self.ctx.namer.new_symbol('if_false', orelse_scope.referenced) - all_referenced = body_scope.referenced | orelse_scope.referenced - state_getter_name = self.ctx.namer.new_symbol('get_state', all_referenced) - state_setter_name = self.ctx.namer.new_symbol('set_state', all_referenced) - - returned_from_cond = tuple(returned_from_cond) - composites = tuple(composites) - - if returned_from_cond: - if len(returned_from_cond) == 1: - cond_results = returned_from_cond[0] - else: - cond_results = gast.Tuple([s.ast() for s in returned_from_cond], None) - - returned_from_body = tuple( - alias_body_map[s] if s in need_alias_in_body else s - for s in returned_from_cond) - returned_from_orelse = tuple( - alias_orelse_map[s] if s in need_alias_in_orelse else s - for s in returned_from_cond) - - else: - # When the cond would return no value, we leave the cond called without - # results. That in turn should trigger the side effect guards. The - # branch functions will return a dummy value that ensures cond - # actually has some return value as well. - cond_results = None - # TODO(mdan): Replace with None once side_effect_guards is retired. - returned_from_body = (templates.replace_as_expression( - 'ag__.match_staging_level(1, cond_var_name)', - cond_var_name=cond_var_name),) - returned_from_orelse = (templates.replace_as_expression( - 'ag__.match_staging_level(1, cond_var_name)', - cond_var_name=cond_var_name),) - - cond_assign = self.create_assignment(cond_var_name, node.test) - body_def = self._create_cond_branch( - body_name, - aliased_orig_names=aliased_body_orig_names, - aliased_new_names=aliased_body_new_names, - body=node_body, - returns=returned_from_body) - orelse_def = self._create_cond_branch( - orelse_name, - aliased_orig_names=aliased_orelse_orig_names, - aliased_new_names=aliased_orelse_new_names, - body=node_orelse, - returns=returned_from_orelse) - undefined_assigns = self._create_undefined_assigns(possibly_undefined) - composite_defs = self._create_state_functions( - composites, state_getter_name, state_setter_name) - - basic_symbol_names = tuple( - gast.Constant(str(symbol), kind=None) for symbol in returned_from_cond) - composite_symbol_names = tuple( - gast.Constant(str(symbol), kind=None) for symbol in composites) - - cond_expr = self._create_cond_expr(cond_results, cond_var_name, body_name, - orelse_name, state_getter_name, - state_setter_name, basic_symbol_names, - composite_symbol_names) - - if_ast = ( - undefined_assigns + composite_defs + body_def + orelse_def + - cond_assign + cond_expr) - return if_ast - - def _get_basic_loop_vars(self, modified_symbols, live_in, live_out): - # The loop variables corresponding to simple symbols (e.g. `x`). - basic_loop_vars = [] - for s in modified_symbols: - if s.is_composite(): - # TODO(mdan): Raise an error when this happens for a TF loop. - continue - # Variables not live into or out of the loop are considered local to the - # loop. - if s not in live_in and s not in live_out: - continue - basic_loop_vars.append(s) - return frozenset(basic_loop_vars) - - def _get_composite_loop_vars(self, modified_symbols, live_in): - # The loop variables corresponding to composite symbols (e.g. `self.x`). - composite_loop_vars = [] - for s in modified_symbols: - if not s.is_composite(): - continue - # Mutations made to objects created inside the loop will appear as writes - # to composite symbols. Because these mutations appear as modifications - # made to composite symbols, we check whether the composite's parent is - # actually live into the loop. - # Example: - # while cond: - # x = Foo() - # x.foo = 2 * x.foo # x.foo is live into the loop, but x is not. - # - # Note that some parents might not be symbols - for example, in x['foo'], - # 'foo' is a parent, but it's a literal, not a symbol. We don't check the - # liveness of literals. - support_set_symbols = tuple( - sss for sss in s.support_set if sss.is_symbol()) - if not all(sss in live_in for sss in support_set_symbols): - continue - composite_loop_vars.append(s) - return frozenset(composite_loop_vars) - - def _get_loop_vars(self, node, modified_symbols): - body_scope = anno.getanno(node, annos.NodeAnno.BODY_SCOPE) - defined_in = anno.getanno(node, anno.Static.DEFINED_VARS_IN) - live_in = anno.getanno(node, anno.Static.LIVE_VARS_IN) - live_out = anno.getanno(node, anno.Static.LIVE_VARS_OUT) - reserved_symbols = body_scope.referenced - - basic_loop_vars = self._get_basic_loop_vars( - modified_symbols, live_in, live_out) - composite_loop_vars = self._get_composite_loop_vars( - modified_symbols, live_in) - - # Variable that are used or defined inside the loop, but not defined - # before entering the loop. Only simple variables must be defined. The - # composite ones will be implicitly checked at runtime. - undefined_lives = basic_loop_vars - defined_in - - return (basic_loop_vars, composite_loop_vars, reserved_symbols, - undefined_lives) - - def _loop_var_constructs(self, basic_loop_vars): - loop_vars = tuple(basic_loop_vars) - loop_vars_ast_tuple = gast.Tuple([n.ast() for n in loop_vars], None) - - if len(loop_vars) == 1: - loop_vars = loop_vars[0] - - return loop_vars, loop_vars_ast_tuple - - def visit_While(self, node): - node = self.generic_visit(node) - - (basic_loop_vars, composite_loop_vars, reserved_symbols, - possibly_undefs) = self._get_loop_vars( - node, - anno.getanno(node, annos.NodeAnno.BODY_SCOPE).modified) - loop_vars, loop_vars_ast_tuple = self._loop_var_constructs( - basic_loop_vars) - - state_getter_name = self.ctx.namer.new_symbol('get_state', reserved_symbols) - state_setter_name = self.ctx.namer.new_symbol('set_state', reserved_symbols) - state_functions = self._create_state_functions( - composite_loop_vars, state_getter_name, state_setter_name) - - basic_symbol_names = tuple( - gast.Constant(str(symbol), kind=None) for symbol in basic_loop_vars) - composite_symbol_names = tuple( - gast.Constant(str(symbol), kind=None) for symbol in composite_loop_vars) - - opts = self._create_loop_options(node) - - # TODO(mdan): Use a single template. - # If the body and test functions took a single tuple for loop_vars, instead - # of *loop_vars, then a single template could be used. - if loop_vars: - template = """ - state_functions - def body_name(loop_vars): - body - return loop_vars, - def test_name(loop_vars): - return test - loop_vars_ast_tuple = ag__.while_stmt( - test_name, - body_name, - state_getter_name, - state_setter_name, - (loop_vars,), - (basic_symbol_names,), - (composite_symbol_names,), - opts) - """ - node = templates.replace( - template, - loop_vars=loop_vars, - loop_vars_ast_tuple=loop_vars_ast_tuple, - test_name=self.ctx.namer.new_symbol('loop_test', reserved_symbols), - test=node.test, - body_name=self.ctx.namer.new_symbol('loop_body', reserved_symbols), - body=node.body, - state_functions=state_functions, - state_getter_name=state_getter_name, - state_setter_name=state_setter_name, - basic_symbol_names=basic_symbol_names, - composite_symbol_names=composite_symbol_names, - opts=opts) - else: - template = """ - state_functions - def body_name(): - body - return () - def test_name(): - return test - ag__.while_stmt( - test_name, - body_name, - state_getter_name, - state_setter_name, - (), - (), - (composite_symbol_names,), - opts) - """ - node = templates.replace( - template, - test_name=self.ctx.namer.new_symbol('loop_test', reserved_symbols), - test=node.test, - body_name=self.ctx.namer.new_symbol('loop_body', reserved_symbols), - body=node.body, - state_functions=state_functions, - state_getter_name=state_getter_name, - state_setter_name=state_setter_name, - composite_symbol_names=composite_symbol_names, - opts=opts) - - undefined_assigns = self._create_undefined_assigns(possibly_undefs) - return undefined_assigns + node - - def visit_For(self, node): - node = self.generic_visit(node) - - (basic_loop_vars, composite_loop_vars, - reserved_symbols, possibly_undefs) = self._get_loop_vars( - node, (anno.getanno(node, annos.NodeAnno.BODY_SCOPE).modified - | anno.getanno(node, annos.NodeAnno.ITERATE_SCOPE).modified)) - loop_vars, loop_vars_ast_tuple = self._loop_var_constructs( - basic_loop_vars) - body_name = self.ctx.namer.new_symbol('loop_body', reserved_symbols) - - state_getter_name = self.ctx.namer.new_symbol('get_state', reserved_symbols) - state_setter_name = self.ctx.namer.new_symbol('set_state', reserved_symbols) - state_functions = self._create_state_functions( - composite_loop_vars, state_getter_name, state_setter_name) - - if anno.hasanno(node, anno.Basic.EXTRA_LOOP_TEST): - extra_test = anno.getanno(node, anno.Basic.EXTRA_LOOP_TEST) - extra_test_name = self.ctx.namer.new_symbol( - 'extra_test', reserved_symbols) - template = """ - def extra_test_name(loop_vars): - return extra_test_expr - """ - extra_test_function = templates.replace( - template, - extra_test_name=extra_test_name, - loop_vars=loop_vars, - extra_test_expr=extra_test) - else: - extra_test_name = parser.parse_expression('None') - extra_test_function = [] - - # Workaround for PEP-3113 - # iterates_var holds a single variable with the iterates, which may be a - # tuple. - iterates_var_name = self.ctx.namer.new_symbol( - 'iterates', reserved_symbols) - template = """ - iterates = iterates_var_name - """ - iterate_expansion = templates.replace( - template, - iterates=node.target, - iterates_var_name=iterates_var_name) - - undefined_assigns = self._create_undefined_assigns(possibly_undefs) - - basic_symbol_names = tuple( - gast.Constant(str(symbol), kind=None) for symbol in basic_loop_vars) - composite_symbol_names = tuple( - gast.Constant(str(symbol), kind=None) for symbol in composite_loop_vars) - - opts = self._create_loop_options(node) - - # TODO(mdan): Use a single template. - # If the body and test functions took a single tuple for loop_vars, instead - # of *loop_vars, then a single template could be used. - if loop_vars: - template = """ - undefined_assigns - state_functions - def body_name(iterates_var_name, loop_vars): - iterate_expansion - body - return loop_vars, - extra_test_function - loop_vars_ast_tuple = ag__.for_stmt( - iter_, - extra_test_name, - body_name, - state_getter_name, - state_setter_name, - (loop_vars,), - (basic_symbol_names,), - (composite_symbol_names,), - opts) - """ - return templates.replace( - template, - undefined_assigns=undefined_assigns, - loop_vars=loop_vars, - loop_vars_ast_tuple=loop_vars_ast_tuple, - iter_=node.iter, - iterate_expansion=iterate_expansion, - iterates_var_name=iterates_var_name, - extra_test_name=extra_test_name, - extra_test_function=extra_test_function, - body_name=body_name, - body=node.body, - state_functions=state_functions, - state_getter_name=state_getter_name, - state_setter_name=state_setter_name, - basic_symbol_names=basic_symbol_names, - composite_symbol_names=composite_symbol_names, - opts=opts) - else: - template = """ - undefined_assigns - state_functions - def body_name(iterates_var_name): - iterate_expansion - body - return () - extra_test_function - ag__.for_stmt( - iter_, - extra_test_name, - body_name, - state_getter_name, - state_setter_name, - (), - (), - (composite_symbol_names,), - opts) - """ - return templates.replace( - template, - undefined_assigns=undefined_assigns, - iter_=node.iter, - iterate_expansion=iterate_expansion, - iterates_var_name=iterates_var_name, - extra_test_name=extra_test_name, - extra_test_function=extra_test_function, - body_name=body_name, - body=node.body, - state_functions=state_functions, - state_getter_name=state_getter_name, - state_setter_name=state_setter_name, - composite_symbol_names=composite_symbol_names, - opts=opts) - - -class AnnotatedDef(reaching_definitions.Definition): - - def __init__(self): - super(AnnotatedDef, self).__init__() - self.directives = {} - - -def transform(node, ctx): - graphs = cfg.build(node) - node = qual_names.resolve(node) - node = activity.resolve(node, ctx, None) - node = reaching_definitions.resolve(node, ctx, graphs) - node = reaching_fndefs.resolve(node, ctx, graphs) - node = liveness.resolve(node, ctx, graphs) - - node = ControlFlowTransformer(ctx).visit(node) - return node diff --git a/src/braket/experimental/autoqasm/autograph/converters/directives.py b/src/braket/experimental/autoqasm/autograph/converters/directives.py deleted file mode 100644 index f68440b6..00000000 --- a/src/braket/experimental/autoqasm/autograph/converters/directives.py +++ /dev/null @@ -1,177 +0,0 @@ -# Copyright 2018 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Handles directives. - -This converter removes the directive functions from the code and moves the -information they specify into AST annotations. It is a specialized form of -static analysis, one that is specific to AutoGraph. - -Note that this requires that the actual directive functions are static - that -is, they do not change at runtime. So if you do something like this: - - tf.autograph.set_loop_options = - -Then the directive will may no longer be recognized. Furthermore, if the -converted function is cached, such an action may be irreversible. -""" - -import inspect - -import gast - -from braket.experimental.autoqasm.autograph.core import converter -from braket.experimental.autoqasm.autograph.lang import directives -from braket.experimental.autoqasm.autograph.pyct import anno -import inspect as tf_inspect - - -STATIC_VALUE = 'static_value' -"""Used for AST annotations, see visit_Name.""" - - -class _LoopScope(object): - - def __init__(self): - self.ast_node = None - self.statements_visited = 0 - - -def _map_args(call_node, function): - """Maps AST call nodes to the actual function's arguments. - - Args: - call_node: ast.Call - function: Callable[..., Any], the actual function matching call_node - Returns: - Dict[Text, ast.AST], mapping each of the function's argument names to - the respective AST node. - Raises: - ValueError: if the default arguments are not correctly set - """ - args = call_node.args - kwds = {kwd.arg: kwd.value for kwd in call_node.keywords} - call_args = tf_inspect.getcallargs(function, *args, **kwds) - - # Keyword arguments not specified in kwds will be mapped to their defaults, - # which are Python values. Since we don't currently have a way to transform - # those into AST references, we simply remove them. By convention, directives - # use UNSPECIFIED as default value for optional arguments. No other - # defaults should be present. - unexpected_defaults = [] - for k in call_args: - if (k not in kwds - and call_args[k] not in args - and call_args[k] is not directives.UNSPECIFIED): - unexpected_defaults.append(k) - if unexpected_defaults: - raise ValueError('Unexpected keyword argument values, %s, for function %s' - % (zip(unexpected_defaults, - [call_args[k] for k in unexpected_defaults]), - function)) - return {k: v for k, v in call_args.items() if v is not directives.UNSPECIFIED} - - -class DirectivesTransformer(converter.Base): - """Parses compiler directives and converts them into AST annotations.""" - - def _process_symbol_directive(self, call_node, directive): - if len(call_node.args) < 1: - raise ValueError('"%s" requires a positional first argument' - ' as the target' % directive.__name__) - target = call_node.args[0] - defs = anno.getanno(target, anno.Static.ORIG_DEFINITIONS) - for def_ in defs: - def_.directives[directive] = _map_args(call_node, directive) - return call_node - - def _process_statement_directive(self, call_node, directive): - if self.state[_LoopScope].statements_visited > 1: - raise ValueError( - '"%s" must be the first statement in the loop block' % ( - directive.__name__)) - if self.state[_LoopScope].level < 2: - raise ValueError( - '"%s" must be used inside a statement' % directive.__name__) - target = self.state[_LoopScope].ast_node - node_anno = anno.getanno(target, anno.Basic.DIRECTIVES, {}) - node_anno[directive] = _map_args(call_node, directive) - anno.setanno(target, anno.Basic.DIRECTIVES, node_anno) - return call_node - - def visit_Name(self, node): - node = self.generic_visit(node) - if isinstance(node.ctx, gast.Load): - defs = anno.getanno(node, anno.Static.DEFINITIONS, ()) - is_defined = bool(defs) - if not is_defined and node.id in self.ctx.info.namespace: - anno.setanno(node, STATIC_VALUE, self.ctx.info.namespace[node.id]) - return node - - def visit_Attribute(self, node): - node = self.generic_visit(node) - parent_val = anno.getanno(node.value, STATIC_VALUE, default=None) - if parent_val is not None and inspect.ismodule(parent_val): - if hasattr(parent_val, node.attr): - anno.setanno(node, STATIC_VALUE, getattr(parent_val, node.attr)) - return node - - def visit_Assign(self, node): - self.state[_LoopScope].statements_visited += 1 - return self.generic_visit(node) - - def visit_AugAssign(self, node): - self.state[_LoopScope].statements_visited += 1 - return self.generic_visit(node) - - def visit_Expr(self, node): - self.state[_LoopScope].statements_visited += 1 - node = self.generic_visit(node) - if isinstance(node.value, gast.Call): - call_node = node.value - static_val = anno.getanno(call_node.func, STATIC_VALUE, default=None) - if static_val is not None: - # Note: directive calls are not output in the generated code, hence - # the removal from the code by returning None. - - if static_val is directives.set_element_type: - self._process_symbol_directive(call_node, static_val) - return None - elif static_val is directives.set_loop_options: - self._process_statement_directive(call_node, static_val) - return None - return node - - # TODO(mdan): This will be insufficient for other control flow. - # That means that if we ever have a directive that affects things other than - # loops, we'll need support for parallel scopes, or have multiple converters. - def _track_and_visit_loop(self, node): - self.state[_LoopScope].enter() - self.state[_LoopScope].ast_node = node - node = self.generic_visit(node) - # Edge case: a loop with just one directive statement would become empty. - if not node.body: - node.body = [gast.Pass()] - self.state[_LoopScope].exit() - return node - - def visit_While(self, node): - return self._track_and_visit_loop(node) - - def visit_For(self, node): - return self._track_and_visit_loop(node) - - -def transform(node, ctx): - return DirectivesTransformer(ctx).visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/converters/functions.py b/src/braket/experimental/autoqasm/autograph/converters/functions.py deleted file mode 100644 index 92bbb89d..00000000 --- a/src/braket/experimental/autoqasm/autograph/converters/functions.py +++ /dev/null @@ -1,134 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Converts function definitions and lambdas by adding necessary boilerplate.""" - -import gast - -from braket.experimental.autoqasm.autograph.core import converter -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import qual_names -from braket.experimental.autoqasm.autograph.pyct import templates -from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity -from braket.experimental.autoqasm.autograph.pyct.static_analysis import annos - - -class _Function(object): - - def __init__(self): - self.context_name = None - - -class FunctionTransformer(converter.Base): - """Wraps function bodies around autograph-specific boilerplate.""" - - def _function_scope_options(self, fn_scope): - """Returns the options with which to create function scopes.""" - # Top-level function receive the options that were directly requested. - # All others receive the options corresponding to a recursive conversion. - # Note: this mainly controls the user_requested flag, which is important - # primarily because the FunctionScope context also creates a - # ControlStatusCtx(autograph=ENABLED) when user_requested is True. See - # function_wrappers.py. - if fn_scope.level == 2: - return self.ctx.user.options - return self.ctx.user.options.call_options() - - def visit_Lambda(self, node): - with self.state[_Function] as fn_scope: - node = self.generic_visit(node) - - # TODO(mdan): Fix the tests so that we can always add this decorator. - if fn_scope.level > 2: - return templates.replace_as_expression( - 'ag__.autograph_artifact(l)', l=node) - - scope = anno.getanno(node, anno.Static.SCOPE) - function_context_name = self.ctx.namer.new_symbol('lscope', - scope.referenced) - fn_scope.context_name = function_context_name - anno.setanno(node, 'function_context_name', function_context_name) - - template = """ - ag__.with_function_scope( - lambda function_context: body, function_context_name, options) - """ - node.body = templates.replace_as_expression( - template, - options=self._function_scope_options(fn_scope).to_ast(), - function_context=function_context_name, - function_context_name=gast.Constant(function_context_name, kind=None), - body=node.body) - - return node - - def visit_FunctionDef(self, node): - with self.state[_Function] as fn_scope: - scope = anno.getanno(node, annos.NodeAnno.BODY_SCOPE) - - function_context_name = self.ctx.namer.new_symbol('fscope', - scope.referenced) - fn_scope.context_name = function_context_name - anno.setanno(node, 'function_context_name', function_context_name) - - node = self.generic_visit(node) - - if fn_scope.level <= 2: - # Top-level functions lose their decorator because the conversion is - # always just-in-time and by the time it happens the decorators are - # already set to be applied. - node.decorator_list = [] - else: - # TODO(mdan): Fix the tests so that we can always add this decorator. - # Inner functions are converted already, so we insert a decorator to - # prevent double conversion. Double conversion would work too, but this - # saves the overhead. - node.decorator_list.append( - parser.parse_expression('ag__.autograph_artifact')) - - docstring_node = None - if node.body: - first_statement = node.body[0] - if (isinstance(first_statement, gast.Expr) and - isinstance(first_statement.value, gast.Constant)): - docstring_node = first_statement - node.body = node.body[1:] - - template = """ - with ag__.FunctionScope( - function_name, context_name, options) as function_context: - body - """ - wrapped_body = templates.replace( - template, - function_name=gast.Constant(node.name, kind=None), - context_name=gast.Constant(function_context_name, kind=None), - options=self._function_scope_options(fn_scope).to_ast(), - function_context=function_context_name, - body=node.body) - - if docstring_node is not None: - wrapped_body = [docstring_node] + wrapped_body - - node.body = wrapped_body - - return node - - -def transform(node, ctx): - node = qual_names.resolve(node) - node = activity.resolve(node, ctx, None) - - return FunctionTransformer(ctx).visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/converters/list_comprehensions.py b/src/braket/experimental/autoqasm/autograph/converters/list_comprehensions.py deleted file mode 100644 index e57af119..00000000 --- a/src/braket/experimental/autoqasm/autograph/converters/list_comprehensions.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright 2016 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Lowers list comprehensions into for and if statements. - -Example: - - result = [x * x for x in xs] - -becomes - - result = [] - for x in xs: - elt = x * x - result.append(elt) -""" - -import gast - -from braket.experimental.autoqasm.autograph.core import converter -from braket.experimental.autoqasm.autograph.pyct import templates - - -# TODO(mdan): This should covert directly to operator calls. - - -class ListCompTransformer(converter.Base): - """Lowers list comprehensions into standard control flow.""" - - def visit_Assign(self, node): - if not isinstance(node.value, gast.ListComp): - return self.generic_visit(node) - if len(node.targets) > 1: - raise NotImplementedError('multiple assignments') - - target, = node.targets - list_comp_node = node.value - - template = """ - target = [] - """ - initialization = templates.replace(template, target=target) - - template = """ - target.append(elt) - """ - body = templates.replace(template, target=target, elt=list_comp_node.elt) - - for gen in reversed(list_comp_node.generators): - for gen_if in reversed(gen.ifs): - template = """ - if test: - body - """ - body = templates.replace(template, test=gen_if, body=body) - template = """ - for target in iter_: - body - """ - body = templates.replace( - template, iter_=gen.iter, target=gen.target, body=body) - - return initialization + body - - -def transform(node, ctx): - return ListCompTransformer(ctx).visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/converters/lists.py b/src/braket/experimental/autoqasm/autograph/converters/lists.py deleted file mode 100644 index 86c3ad47..00000000 --- a/src/braket/experimental/autoqasm/autograph/converters/lists.py +++ /dev/null @@ -1,239 +0,0 @@ -# Copyright 2016 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Converter for list operations. - -This includes converting Python lists to TensorArray/TensorList. -""" - -# TODO(mdan): Elaborate the logic here. -# TODO(mdan): Does it even make sense to attempt to try to use TAs? -# The current rule (always convert to TensorArray) is naive and insufficient. -# In general, a better mechanism could look like: -# * convert to TensorList by default -# * leave as Python list if the user explicitly forbids it -# * convert to TensorArray only when complete write once behavior can be -# guaranteed (e.g. list comprehensions) - -import gast - -from braket.experimental.autoqasm.autograph.core import converter -from braket.experimental.autoqasm.autograph.lang import directives -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import qual_names -from braket.experimental.autoqasm.autograph.pyct import templates -from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity -from braket.experimental.autoqasm.autograph.pyct.static_analysis.annos import NodeAnno - - -class _Statement(object): - - def __init__(self): - self.pop_uses = None - - -class ListTransformer(converter.Base): - """Converts lists and related operations to their TF counterpart.""" - - def visit_List(self, node): - node = self.generic_visit(node) - template = """ - ag__.new_list(elements) - """ - return templates.replace_as_expression(template, elements=node) - - def _replace_append_call(self, node): - assert len(node.args) == 1 - assert isinstance(node.func, gast.Attribute) - template = """ - target = ag__.list_append(target, element) - """ - return templates.replace( - template, - target=node.func.value, - element=node.args[0]) - - def _replace_pop_call(self, node): - # Expressions that use pop() are converted to a statement + expression. - # - # For example: - # - # print(target.pop()) - # - # ... is converted to: - # - # target, target_pop = ag__.list_pop(target) - # print(target_pop) - # - # Here, we just generate the variable name and swap it in, - # and _generate_pop_operation will handle the rest. - # - # Multiple uses of pop() are allowed: - # - # print(tartget.pop(), target.pop()) - # print(tartget.pop().pop()) - # - assert isinstance(node.func, gast.Attribute) - scope = anno.getanno(node, NodeAnno.ARGS_SCOPE) - target_node = node.func.value - - # Attempt to use a related name if one exists. Otherwise use something - # generic. - if anno.hasanno(target_node, anno.Basic.QN): - target_name = anno.getanno(target_node, anno.Basic.QN).ssf() - else: - target_name = 'list_' - pop_var_name = self.ctx.namer.new_symbol(target_name, scope.referenced) - - stmt = self.state[_Statement] - if stmt.pop_uses is None: - stmt.pop_uses = [] - stmt.pop_uses.append((node, pop_var_name)) - - return templates.replace_as_expression('var_name', var_name=pop_var_name) - - def _replace_stack_call(self, node): - assert len(node.args) == 1 - dtype = self.get_definition_directive( - node.args[0], - directives.set_element_type, - 'dtype', - default=templates.replace_as_expression('None')) - template = """ - ag__.list_stack( - target, - opts=ag__.ListStackOpts( - element_dtype=dtype, - original_call=orig_call)) - """ - return templates.replace_as_expression( - template, - dtype=dtype, - target=node.args[0], - orig_call=node.func) - - def visit_Call(self, node): - node = self.generic_visit(node) - - # TODO(mdan): This is insufficient if target is a function argument. - # In the case of function arguments, we need to add the list to the - # function's return value, because it is being modified. - # TODO(mdan): Checking just the name is brittle, can it be improved? - if isinstance(node.func, gast.Attribute): - func_name = node.func.attr - if func_name == 'append' and (len(node.args) == 1): - node = self._replace_append_call(node) - elif func_name == 'pop' and (len(node.args) <= 1): - node = self._replace_pop_call(node) - elif (func_name == 'stack' and (len(node.args) == 1) and - (not node.keywords or node.keywords[0].arg == 'strict')): - # This avoids false positives with keyword args. - # TODO(mdan): handle kwargs properly. - node = self._replace_stack_call(node) - - return node - - def _generate_pop_operation(self, original_call_node, pop_var_name): - assert isinstance(original_call_node.func, gast.Attribute) - - if original_call_node.args: - pop_element = original_call_node.args[0] - else: - pop_element = parser.parse_expression('None') - - # The call will be something like "target.pop()", and the dtype is hooked to - # target, hence the func.value. - # TODO(mdan): For lists of lists, this won't work. - # The reason why it won't work is because it's unclear how to annotate - # the list as a "list of lists with a certain element type" when using - # operations like `l.pop().pop()`. - dtype = self.get_definition_directive( - original_call_node.func.value, - directives.set_element_type, - 'dtype', - default=templates.replace_as_expression('None')) - shape = self.get_definition_directive( - original_call_node.func.value, - directives.set_element_type, - 'shape', - default=templates.replace_as_expression('None')) - - template = """ - target, pop_var_name = ag__.list_pop( - target, element, - opts=ag__.ListPopOpts(element_dtype=dtype, element_shape=shape)) - """ - return templates.replace( - template, - target=original_call_node.func.value, - pop_var_name=pop_var_name, - element=pop_element, - dtype=dtype, - shape=shape) - - def _postprocess_statement(self, node): - """Inserts any separate pop() calls that node may use.""" - pop_uses = self.state[_Statement].pop_uses - if pop_uses: - replacements = [] - for original_call_node, pop_var_name in pop_uses: - replacements.extend( - self._generate_pop_operation(original_call_node, pop_var_name)) - replacements.append(node) - node = replacements - self.state[_Statement].exit() - return node, None - - def _visit_and_process_block(self, block): - return self.visit_block( - block, - before_visit=self.state[_Statement].enter, - after_visit=self._postprocess_statement) - - def visit_FunctionDef(self, node): - node.args = self.generic_visit(node.args) - node.decorator_list = self.visit_block(node.decorator_list) - node.body = self._visit_and_process_block(node.body) - return node - - def visit_For(self, node): - node.target = self.visit(node.target) - node.body = self._visit_and_process_block(node.body) - node.orelse = self._visit_and_process_block(node.orelse) - return node - - def visit_While(self, node): - node.test = self.visit(node.test) - node.body = self._visit_and_process_block(node.body) - node.orelse = self._visit_and_process_block(node.orelse) - return node - - def visit_If(self, node): - node.test = self.visit(node.test) - node.body = self._visit_and_process_block(node.body) - node.orelse = self._visit_and_process_block(node.orelse) - return node - - def visit_With(self, node): - node.items = self.visit_block(node.items) - node.body = self._visit_and_process_block(node.body) - return node - - -def transform(node, ctx): - node = qual_names.resolve(node) - node = activity.resolve(node, ctx, None) - - return ListTransformer(ctx).visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/converters/logical_expressions.py b/src/braket/experimental/autoqasm/autograph/converters/logical_expressions.py deleted file mode 100644 index b9592085..00000000 --- a/src/braket/experimental/autoqasm/autograph/converters/logical_expressions.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright 2016 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Converter for logical expressions, e.g. `a and b -> tf.logical_and(a, b)`.""" - -import gast - -from braket.experimental.autoqasm.autograph.core import converter -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import templates - -# TODO(mdan): Properly extract boolean ops according to lazy eval rules. -# Note that this isn't completely safe either, because tensors may have control -# dependencies. -# Note that for loops that should be done after the loop was converted to -# tf.while_loop so that the expanded conditionals are properly scoped. - -# Used to signal that an operand is safe for non-lazy evaluation. -SAFE_BOOLEAN_OPERAND = 'SAFE_BOOLEAN_OPERAND' - - -LOGICAL_OPERATORS = { - gast.And: 'ag__.and_', - gast.Not: 'ag__.not_', - gast.Or: 'ag__.or_', -} - -EQUALITY_OPERATORS = { - gast.Eq: 'ag__.eq', - gast.NotEq: 'ag__.not_eq', -} - - -class LogicalExpressionTransformer(converter.Base): - """Converts logical expressions to corresponding TF calls.""" - - def _overload_of(self, operator): - op_type = type(operator) - if op_type in LOGICAL_OPERATORS: - return LOGICAL_OPERATORS[op_type] - if self.ctx.user.options.uses(converter.Feature.EQUALITY_OPERATORS): - if op_type in EQUALITY_OPERATORS: - return EQUALITY_OPERATORS[op_type] - return None - - def _as_lambda(self, expr): - return templates.replace_as_expression('lambda: expr', expr=expr) - - def _as_binary_function(self, func_name, arg1, arg2): - return templates.replace_as_expression( - 'func_name(arg1, arg2)', - func_name=parser.parse_expression(func_name), - arg1=arg1, - arg2=arg2) - - def _as_binary_operation(self, op, arg1, arg2): - template = templates.replace_as_expression( - 'arg1 is arg2', # Note: `is` will be replaced with `op` below. - arg1=arg1, - arg2=arg2) - template.ops[0] = op - return template - - def _as_unary_function(self, func_name, arg): - return templates.replace_as_expression( - 'func_name(arg)', func_name=parser.parse_expression(func_name), arg=arg) - - def _process_binop(self, op, left, right): - overload = self._overload_of(op) - if overload is None: - return self._as_binary_operation(op, left, right) - return self._as_binary_function(overload, left, right) - - def visit_Compare(self, node): - node = self.generic_visit(node) - - ops_and_comps = list(zip(node.ops, node.comparators)) - left = node.left - - # Repeated comparisons are converted to conjunctions: - # a < b < c -> a < b and b < c - op_tree = None - while ops_and_comps: - op, right = ops_and_comps.pop(0) - binary_comparison = self._process_binop(op, left, right) - if op_tree is not None: - op_tree = self._as_binary_function('ag__.and_', - self._as_lambda(op_tree), - self._as_lambda(binary_comparison)) - else: - op_tree = binary_comparison - left = right - - assert op_tree is not None - return op_tree - - def visit_UnaryOp(self, node): - node = self.generic_visit(node) - - overload = self._overload_of(node.op) - if overload is None: - return node - - return self._as_unary_function(overload, node.operand) - - def visit_BoolOp(self, node): - node = self.generic_visit(node) - node_values = node.values - right = node.values.pop() - while node_values: - left = node_values.pop() - right = self._as_binary_function( - self._overload_of(node.op), self._as_lambda(left), - self._as_lambda(right)) - return right - - -def transform(node, ctx): - transformer = LogicalExpressionTransformer(ctx) - return transformer.visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/converters/return_statements.py b/src/braket/experimental/autoqasm/autograph/converters/return_statements.py deleted file mode 100644 index 42501ca3..00000000 --- a/src/braket/experimental/autoqasm/autograph/converters/return_statements.py +++ /dev/null @@ -1,402 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Canonicalizes functions with multiple returns to use just one.""" - -import gast - -from braket.experimental.autoqasm.autograph.core import converter -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import qual_names -from braket.experimental.autoqasm.autograph.pyct import templates -from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity -from braket.experimental.autoqasm.autograph.pyct.static_analysis.annos import NodeAnno - - -BODY_DEFINITELY_RETURNS = 'BODY_DEFINITELY_RETURNS' -ORELSE_DEFINITELY_RETURNS = 'ORELSE_DEFINITELY_RETURNS' -STMT_DEFINITELY_RETURNS = 'STMT_DEFINITELY_RETURNS' - - -class _RewriteBlock(object): - - def __init__(self): - self.definitely_returns = False - - -class ConditionalReturnRewriter(converter.Base): - """Rewrites a pattern where it's unobvious that all paths return a value. - - This rewrite allows avoiding intermediate None return values. - - The following pattern: - - if cond: - - return - else: - - - - is converted to: - - if cond: - - return - else: - - - - and vice-versa (if the else returns, subsequent statements are moved under the - if branch). - """ - - def visit_Return(self, node): - self.state[_RewriteBlock].definitely_returns = True - return node - - def _postprocess_statement(self, node): - # If the node definitely returns (e.g. it's a with statement with a - # return statement in it), then the current block also definitely returns. - if anno.getanno(node, STMT_DEFINITELY_RETURNS, default=False): - self.state[_RewriteBlock].definitely_returns = True - - # The special case: collapse a typical conditional return pattern into - # a single conditional with possibly returns on both branches. This - # reduces the use of None return values, which don't work with TF - # conditionals. - if (isinstance(node, gast.If) - and anno.getanno(node, BODY_DEFINITELY_RETURNS, default=False)): - return node, node.orelse - elif (isinstance(node, gast.If) - and anno.getanno(node, ORELSE_DEFINITELY_RETURNS, default=False)): - return node, node.body - - return node, None - - def _visit_statement_block(self, node, nodes): - self.state[_RewriteBlock].enter() - new_nodes = self.visit_block(nodes, after_visit=self._postprocess_statement) - block_definitely_returns = self.state[_RewriteBlock].definitely_returns - self.state[_RewriteBlock].exit() - return new_nodes, block_definitely_returns - - def visit_While(self, node): - node.test = self.visit(node.test) - node.body, _ = self._visit_statement_block(node, node.body) - node.orelse, _ = self._visit_statement_block(node, node.orelse) - return node - - def visit_For(self, node): - node.iter = self.visit(node.iter) - node.target = self.visit(node.target) - node.body, _ = self._visit_statement_block(node, node.body) - node.orelse, _ = self._visit_statement_block(node, node.orelse) - return node - - def visit_With(self, node): - node.items = self.visit_block(node.items) - node.body, definitely_returns = self._visit_statement_block(node, node.body) - if definitely_returns: - anno.setanno(node, STMT_DEFINITELY_RETURNS, True) - return node - - def visit_Try(self, node): - # We could decide whether a 'try' DEFINITELY_RETURNS based on its components - # It is not clear whether we want to do anything with this given - # a 'try' is likely to throw an exception in some circumstances. - node.body, _ = self._visit_statement_block(node, node.body) - node.orelse, _ = self._visit_statement_block(node, node.orelse) - node.finalbody, _ = self._visit_statement_block(node, node.finalbody) - node.handlers = self.visit_block(node.handlers) - return node - - def visit_ExceptHandler(self, node): - # To determine whether `try` DEFINITELY_RETURNS we need to revisit this. - node.body, _ = self._visit_statement_block(node, node.body) - return node - - def visit_If(self, node): - node.test = self.visit(node.test) - - node.body, body_definitely_returns = self._visit_statement_block( - node, node.body) - if body_definitely_returns: - anno.setanno(node, BODY_DEFINITELY_RETURNS, True) - - node.orelse, orelse_definitely_returns = self._visit_statement_block( - node, node.orelse) - if orelse_definitely_returns: - anno.setanno(node, ORELSE_DEFINITELY_RETURNS, True) - - if body_definitely_returns and orelse_definitely_returns: - self.state[_RewriteBlock].definitely_returns = True - - return node - - def visit_FunctionDef(self, node): - node.args = self.visit(node.args) - node.body, _ = self._visit_statement_block(node, node.body) - return node - - -class _Block(object): - - def __init__(self): - self.is_function = False - self.return_used = False - self.create_guard_next = False - self.create_guard_now = False - - def __repr__(self): - return 'used: {}'.format( - self.return_used) - - -class _Function(object): - - def __init__(self): - self.do_return_var_name = None - self.retval_var_name = None - - def __repr__(self): - return 'return control: {}, return value: {}'.format( - self.do_return_var_name, self.retval_var_name) - - -class ReturnStatementsTransformer(converter.Base): - """Lowers return statements into variables and conditionals. - - Specifically, the following pattern: - - - return val - - - is converted to: - - do_return = False - retval = None - - - - do_return = True - retval = val - - if not do_return: - - - return retval - - The conversion adjusts loops as well: - - - while cond: - - return retval - - is converted to: - - - while not do_return and cond: - - do_return = True - retval = val - """ - - def __init__(self, ctx, allow_missing_return): - super(ReturnStatementsTransformer, self).__init__(ctx) - self.allow_missing_return = allow_missing_return - - def visit_Return(self, node): - for block in reversed(self.state[_Block].stack): - block.return_used = True - block.create_guard_next = True - if block.is_function: - break - - retval = node.value if node.value else parser.parse_expression('None') - - # Note: If `return raises, then the return is aborted. - # The try-catch below ensures the variables remain consistent in that case. - template = """ - try: - do_return_var_name = True - retval_var_name = retval - except: - do_return_var_name = False - raise - """ - node = templates.replace( - template, - do_return_var_name=self.state[_Function].do_return_var_name, - retval_var_name=self.state[_Function].retval_var_name, - retval=retval) - - return node - - def _postprocess_statement(self, node): - if not self.state[_Block].return_used: - return node, None - - state = self.state[_Block] - if state.create_guard_now: - template = """ - if not do_return_var_name: - original_node - """ - cond, = templates.replace( - template, - do_return_var_name=self.state[_Function].do_return_var_name, - original_node=node) - node, block = cond, cond.body - else: - node, block = node, None - - state.create_guard_now = state.create_guard_next - state.create_guard_next = False - - return node, block - - def _visit_statement_block(self, node, nodes): - self.state[_Block].enter() - nodes = self.visit_block(nodes, after_visit=self._postprocess_statement) - self.state[_Block].exit() - return nodes - - def visit_While(self, node): - node.test = self.visit(node.test) - - # Add the check for return to the loop condition. - node.body = self._visit_statement_block(node, node.body) - if self.state[_Block].return_used: - node.test = templates.replace_as_expression( - 'not control_var and test', - test=node.test, - control_var=self.state[_Function].do_return_var_name) - - node.orelse = self._visit_statement_block(node, node.orelse) - return node - - def visit_For(self, node): - node.iter = self.visit(node.iter) - node.target = self.visit(node.target) - - # Add the check for return to the loop condition. - node.body = self._visit_statement_block(node, node.body) - if self.state[_Block].return_used: - extra_test = anno.getanno(node, anno.Basic.EXTRA_LOOP_TEST, default=None) - if extra_test is not None: - extra_test = templates.replace_as_expression( - 'not control_var and extra_test', - extra_test=extra_test, - control_var=self.state[_Function].do_return_var_name) - else: - extra_test = templates.replace_as_expression( - 'not control_var', - control_var=self.state[_Function].do_return_var_name) - anno.setanno(node, anno.Basic.EXTRA_LOOP_TEST, extra_test) - - node.orelse = self._visit_statement_block(node, node.orelse) - return node - - def visit_With(self, node): - node.items = self.visit_block(node.items) - node.body = self._visit_statement_block(node, node.body) - return node - - def visit_Try(self, node): - node.body = self._visit_statement_block(node, node.body) - node.orelse = self._visit_statement_block(node, node.orelse) - node.finalbody = self._visit_statement_block(node, node.finalbody) - node.handlers = self.visit_block(node.handlers) - return node - - def visit_ExceptHandler(self, node): - node.body = self._visit_statement_block(node, node.body) - return node - - def visit_If(self, node): - node.test = self.visit(node.test) - node.body = self._visit_statement_block(node, node.body) - node.orelse = self._visit_statement_block(node, node.orelse) - return node - - def visit_FunctionDef(self, node): - with self.state[_Function] as fn: - with self.state[_Block] as block: - block.is_function = True - - scope = anno.getanno(node, NodeAnno.BODY_SCOPE) - do_return_var_name = self.ctx.namer.new_symbol('do_return', - scope.referenced) - retval_var_name = self.ctx.namer.new_symbol('retval_', scope.referenced) - fn.do_return_var_name = do_return_var_name - fn.retval_var_name = retval_var_name - - node.body = self._visit_statement_block(node, node.body) - - if block.return_used: - - if self.allow_missing_return: - # The function would have a single `with` node that wraps the - # entire body. If the function had a docstring, the body has two - # nodes, with the `with` as the second node. - wrapper_node = node.body[-1] - assert isinstance(wrapper_node, gast.With), ( - 'This transformer requires the functions converter.') - - template = """ - do_return_var_name = False - retval_var_name = ag__.UndefinedReturnValue() - body - return function_context.ret(retval_var_name, do_return_var_name) - """ - - wrapper_node.body = templates.replace( - template, - body=wrapper_node.body, - do_return_var_name=do_return_var_name, - function_context=anno.getanno(node, 'function_context_name'), - retval_var_name=retval_var_name) - else: - template = """ - body - return retval_var_name - """ - node.body = templates.replace( - template, - body=node.body, - do_return_var_name=do_return_var_name, - retval_var_name=retval_var_name) - - return node - - -def transform(node, ctx, default_to_null_return=True): - """Ensure a function has only a single return, at the end.""" - node = qual_names.resolve(node) - node = activity.resolve(node, ctx, None) - - # Note: Technically, these two could be merged into a single walk, but - # keeping them separate helps with readability. - node = ConditionalReturnRewriter(ctx).visit(node) - - node = qual_names.resolve(node) - node = activity.resolve(node, ctx, None) - transformer = ReturnStatementsTransformer( - ctx, allow_missing_return=default_to_null_return) - node = transformer.visit(node) - return node diff --git a/src/braket/experimental/autoqasm/autograph/converters/slices.py b/src/braket/experimental/autoqasm/autograph/converters/slices.py deleted file mode 100644 index 48823e6e..00000000 --- a/src/braket/experimental/autoqasm/autograph/converters/slices.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright 2016 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Converter for slice operations.""" - -import gast - -from braket.experimental.autoqasm.autograph.core import converter -from braket.experimental.autoqasm.autograph.lang import directives -from braket.experimental.autoqasm.autograph.pyct import templates - - -class SliceTransformer(converter.Base): - """Converts slicing operations to their TF counterpart. - - Currently, relying on the default slice operator that Tensor uses is - insufficient, because TensorArray and tensor lists use dedicated index read - and write functions. - """ - - def _process_single_assignment(self, target, value): - if not isinstance(target, gast.Subscript): - return None - s = target.slice - if isinstance(s, (gast.Tuple, gast.Slice)): - return None - - template = """ - target = ag__.set_item(target, key, item) - """ - return templates.replace( - template, target=target.value, key=target.slice, item=value) - - def visit_Assign(self, node): - node = self.generic_visit(node) - # TODO(mdan): Support unpackings and multiple assignments. - if len(node.targets) != 1: - raise NotImplementedError('multiple assignment') - replacement = self._process_single_assignment(node.targets[0], node.value) - if replacement is not None: - return replacement - return node - - def visit_Subscript(self, node): - node = self.generic_visit(node) - s = node.slice - if isinstance(s, (gast.Tuple, gast.Slice)): - return node - - if not isinstance(node.ctx, gast.Load): - # Index writes are handled at a higher level, one at which the rvalue is - # also available. - return node - - dtype = self.get_definition_directive( - node.value, - directives.set_element_type, - 'dtype', - default=templates.replace_as_expression('None')) - - template = """ - ag__.get_item( - target, - key, - opts=ag__.GetItemOpts(element_dtype=dtype)) - """ - return templates.replace_as_expression( - template, target=node.value, key=s, dtype=dtype) - - -def transform(node, ctx): - return SliceTransformer(ctx).visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/converters/variables.py b/src/braket/experimental/autoqasm/autograph/converters/variables.py deleted file mode 100644 index cab01dc6..00000000 --- a/src/braket/experimental/autoqasm/autograph/converters/variables.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright 2020 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Overloads all variable read operations.""" - -import gast - -from braket.experimental.autoqasm.autograph.core import converter -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import templates - - -class VariableAccessTransformer(converter.Base): - """Rewrites basic symbol reads. - - This transformer rewrites variable reads with a "read" operator which allows - tracking activity. - - Example: - - For a basic statement: - - a = b + c - - This is translated to: - - a = ld(b) + ld(c) - - Augmented assignment operations also introduce a `ld` operator: - - a += b - - The assignment target also receives an operator to properly represent the - read: - - a = ld(a) - a += ld(b) - """ - - def visit_Name(self, node): - # Only the loads which existed in the original code are overloaded. - if not anno.hasanno(node, anno.Static.ORIG_DEFINITIONS): - return node - if isinstance(node.ctx, gast.Load): - node = templates.replace_as_expression('ag__.ld(var_)', var_=node) - return node - - def visit_Delete(self, node): - node = self.generic_visit(node) - - rewrite_targets = [] - for tgt in node.targets: - # Don't rewrite composites like `del a[0]`. - if isinstance(tgt, gast.Name): - rewrite_targets.append(tgt) - - if not rewrite_targets: - return node - - results = [] - for tgt in rewrite_targets: - template = """ - var_ = ag__.Undefined(var_name) - """ - results.extend(templates.replace( - template, var_=tgt, var_name=gast.Constant(tgt.id, kind=None))) - remaining_targets = [n for n in node.targets if n not in rewrite_targets] - if remaining_targets: - results.append(gast.Delete(targets=remaining_targets)) - - return results - - def visit_AugAssign(self, node): - if isinstance(node.target, gast.Name): - template = """ - var_ = ag__.ld(var_) - original - """ - node = templates.replace(template, var_=node.target, original=node) - else: - node = self.generic_visit(node) - return node - - -def transform(node, ctx): - return VariableAccessTransformer(ctx).visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/core/ag_ctx.py b/src/braket/experimental/autoqasm/autograph/core/ag_ctx.py deleted file mode 100644 index 1211ddf2..00000000 --- a/src/braket/experimental/autoqasm/autograph/core/ag_ctx.py +++ /dev/null @@ -1,103 +0,0 @@ -# Copyright 2019 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Thread-local context managers for AutoGraph.""" - -import enum -import inspect -import threading - -from braket.experimental.autoqasm.autograph.logging import ag_logging - - -stacks = threading.local() - - -def _control_ctx(): - if not hasattr(stacks, 'control_status'): - stacks.control_status = [_default_control_status_ctx()] - return stacks.control_status - - -def control_status_ctx(): - """Returns the current control context for autograph. - - This method is useful when calling `tf.__internal__.autograph.tf_convert`, - The context will be used by tf_convert to determine whether it should convert - the input function. See the sample usage like below: - - ``` - def foo(func): - return tf.__internal__.autograph.tf_convert( - input_fn, ctx=tf.__internal__.autograph.control_status_ctx())() - ``` - - Returns: - The current control context of autograph. - """ - ret = _control_ctx()[-1] - return ret - - -class Status(enum.Enum): - UNSPECIFIED = 0 - ENABLED = 1 - DISABLED = 2 - - -class ControlStatusCtx(object): - """A context that tracks whether autograph is enabled by the user.""" - - def __init__(self, status, options=None): - self.status = status - self.options = options - - def __enter__(self): - _control_ctx().append(self) - return self - - def __repr__(self): - return '{}[status={}, options={}]'.format( - self.__class__.__name__, self.status, self.options) - - def __exit__(self, unused_type, unused_value, unused_traceback): - assert _control_ctx()[-1] is self - _control_ctx().pop() - - -class NullCtx(object): - """Helper substitute for contextlib.nullcontext.""" - - def __enter__(self): - pass - - def __exit__(self, unused_type, unused_value, unused_traceback): - pass - - -def _default_control_status_ctx(): - return ControlStatusCtx(status=Status.UNSPECIFIED) - - -INSPECT_SOURCE_SUPPORTED = True -try: - inspect.getsource(ag_logging.log) -except OSError: - INSPECT_SOURCE_SUPPORTED = False - ag_logging.warning( - 'AutoGraph is not available in this environment: functions lack code' - ' information. This is typical of some environments like the interactive' - ' Python shell. See' - ' https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/autograph/g3doc/reference/limitations.md#access-to-source-code' - ' for more information.') diff --git a/src/braket/experimental/autoqasm/autograph/core/config.py b/src/braket/experimental/autoqasm/autograph/core/config.py deleted file mode 100644 index 8d9d338b..00000000 --- a/src/braket/experimental/autoqasm/autograph/core/config.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2016 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Global configuration.""" - -from braket.experimental.autoqasm.autograph.core import config_lib - -Action = config_lib.Action -Convert = config_lib.Convert -DoNotConvert = config_lib.DoNotConvert - - -# This list is evaluated in order and stops at the first rule that tests True -# for a definitely_convert of definitely_bypass call. -CONVERSION_RULES = ( - # Known packages - Convert('tensorflow.python.training.experimental'), - - # Builtin modules - DoNotConvert('collections'), - DoNotConvert('copy'), - DoNotConvert('cProfile'), - DoNotConvert('inspect'), - DoNotConvert('ipdb'), - DoNotConvert('linecache'), - DoNotConvert('mock'), - DoNotConvert('pathlib'), - DoNotConvert('pdb'), - DoNotConvert('posixpath'), - DoNotConvert('pstats'), - DoNotConvert('re'), - DoNotConvert('threading'), - DoNotConvert('urllib'), - - # Known libraries - DoNotConvert('matplotlib'), - DoNotConvert('numpy'), - DoNotConvert('pandas'), - DoNotConvert('tensorflow'), - DoNotConvert('PIL'), - DoNotConvert('absl.logging'), - - # TODO(b/133417201): Remove. - DoNotConvert('tensorflow_probability'), - - # TODO(b/133842282): Remove. - DoNotConvert('tensorflow_datasets.core'), - - DoNotConvert('keras'), -) diff --git a/src/braket/experimental/autoqasm/autograph/core/config_lib.py b/src/braket/experimental/autoqasm/autograph/core/config_lib.py deleted file mode 100644 index 9065bf36..00000000 --- a/src/braket/experimental/autoqasm/autograph/core/config_lib.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2016 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Global configuration support.""" - -import enum - - -# TODO(mdan): For better performance, allow each rule to take a set names. - - -class Rule(object): - """Base class for conversion rules.""" - - def __init__(self, module_prefix): - self._prefix = module_prefix - - def matches(self, module_name): - return (module_name.startswith(self._prefix + '.') or - module_name == self._prefix) - - -class Action(enum.Enum): - NONE = 0 - CONVERT = 1 - DO_NOT_CONVERT = 2 - - -class DoNotConvert(Rule): - """Indicates that this module should be not converted.""" - - def __str__(self): - return 'DoNotConvert rule for {}'.format(self._prefix) - - def get_action(self, module): - if self.matches(module.__name__): - return Action.DO_NOT_CONVERT - return Action.NONE - - -class Convert(Rule): - """Indicates that this module should be converted.""" - - def __str__(self): - return 'Convert rule for {}'.format(self._prefix) - - def get_action(self, module): - if self.matches(module.__name__): - return Action.CONVERT - return Action.NONE diff --git a/src/braket/experimental/autoqasm/autograph/core/converter.py b/src/braket/experimental/autoqasm/autograph/core/converter.py deleted file mode 100644 index 2d928dea..00000000 --- a/src/braket/experimental/autoqasm/autograph/core/converter.py +++ /dev/null @@ -1,314 +0,0 @@ -# Copyright 2016 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Converter construction support. - -This module contains a base class for all converters, as well as supporting -structures. These structures are referred to as contexts. - -The class hierarchy is as follows: - - - [extends] converter.Base - [extends] transformer.Base - [extends] gast.nodeTransformer - [uses] transformer.SourceInfo - [uses] converter.EntityContext - [uses] converter.ProgramContext - [uses] transformer.SourceInfo - -converter.Base is a specialization of transformer.Base for AutoGraph. It's a -very lightweight subclass that adds a `ctx` attribute holding the corresponding -EntityContext object (see below). Note that converters are not reusable, and -`visit` will raise an error if called more than once. - -converter.EntityContext contains mutable state associated with an entity that -the converter processes. - -converter.ProgramContext contains mutable state across related entities. For -example, when converting several functions that call one another, the -ProgramContext should be shared across these entities. - -Below is the overall flow at conversion: - - program_ctx = ProgramContext(, , ...) - while : - entity, source_info = - entity_ctx = EntityContext(program_ctx, source_info) - for : - converter = ConverterClass(entity_ctx) - - # May update entity_ctx and program_ctx - entity = converter.visit(entity) - - - -Note that pyct contains a small number of transformers used for static analysis. -These implement transformer.Base, rather than converter.Base, to avoid a -dependency on AutoGraph. -""" - -import enum - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import ast_util -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import templates -from braket.experimental.autoqasm.autograph.pyct import transformer - -# TODO(mdan): These contexts can be refactored into first class objects. -# For example, we could define Program and Entity abstractions that hold on -# to the actual entity and have conversion methods. - -# TODO(mdan): Add a test specific to this converter. - - -class Feature(enum.Enum): - """This enumeration represents optional conversion options. - - These conversion options are experimental. They are subject to change without - notice and offer no guarantees. - - _Example Usage_ - - ```python - optionals= tf.autograph.experimental.Feature.EQUALITY_OPERATORS - @tf.function(experimental_autograph_options=optionals) - def f(i): - if i == 0: # EQUALITY_OPERATORS allows the use of == here. - tf.print('i is zero') - ``` - - Attributes: - ALL: Enable all features. - AUTO_CONTROL_DEPS: Insert of control dependencies in the generated code. - ASSERT_STATEMENTS: Convert Tensor-dependent assert statements to tf.Assert. - BUILTIN_FUNCTIONS: Convert builtin functions applied to Tensors to - their TF counterparts. - EQUALITY_OPERATORS: Whether to convert the equality operator ('==') to - tf.math.equal. - LISTS: Convert list idioms, like initializers, slices, append, etc. - NAME_SCOPES: Insert name scopes that name ops according to context, like the - function they were defined in. - """ - - ALL = 'ALL' - - AUTO_CONTROL_DEPS = 'AUTO_CONTROL_DEPS' - ASSERT_STATEMENTS = 'ASSERT_STATEMENTS' - BUILTIN_FUNCTIONS = 'BUILTIN_FUNCTIONS' - EQUALITY_OPERATORS = 'EQUALITY_OPERATORS' - LISTS = 'LISTS' - NAME_SCOPES = 'NAME_SCOPES' - - @classmethod - def all(cls): - """Returns a tuple that enables all options.""" - return tuple(cls.__members__.values()) - - @classmethod - def all_but(cls, exclude): - """Returns a tuple that enables all but the excluded options.""" - if not isinstance(exclude, (list, tuple, set)): - exclude = (exclude,) - return tuple(set(cls.all()) - set(exclude) - {cls.ALL}) - - -STANDARD_OPTIONS = None # Forward definition. - - -class ConversionOptions(object): - """Immutable container for global conversion flags. - - Attributes: - recursive: bool, whether to recursively convert any user functions or - classes that the converted function may use. - user_requested: bool, whether the conversion was explicitly requested by - the user, as opposed to being performed as a result of other logic. This - value always auto-resets to False in child conversions. - optional_features: Union[Feature, Set[Feature]], controls the use of - optional features in the conversion process. See Feature for available - options. - """ - - def __init__(self, - recursive=False, - user_requested=False, - internal_convert_user_code=True, - optional_features=Feature.ALL): - self.recursive = recursive - self.user_requested = user_requested - # TODO(mdan): Rename to conversion_recursion_depth? - self.internal_convert_user_code = internal_convert_user_code - - if optional_features is None: - optional_features = () - elif isinstance(optional_features, Feature): - optional_features = (optional_features,) - optional_features = frozenset(optional_features) - self.optional_features = optional_features - - def as_tuple(self): - return (self.recursive, self.user_requested, - self.internal_convert_user_code, self.optional_features) - - def __hash__(self): - return hash(self.as_tuple()) - - def __eq__(self, other): - assert isinstance(other, ConversionOptions) - return self.as_tuple() == other.as_tuple() - - def __str__(self): - return 'ConversionOptions[{}]' - - def uses(self, feature): - return (Feature.ALL in self.optional_features or - feature in self.optional_features) - - def call_options(self): - """Returns the corresponding options to be used for recursive conversion.""" - return ConversionOptions( - recursive=self.recursive, - user_requested=False, - internal_convert_user_code=self.recursive, - optional_features=self.optional_features) - - def to_ast(self): - """Returns a representation of this object as an AST node. - - The AST node encodes a constructor that would create an object with the - same contents. - - Returns: - ast.Node - """ - if self == STANDARD_OPTIONS: - return parser.parse_expression('ag__.STD') - - template = """ - ag__.ConversionOptions( - recursive=recursive_val, - user_requested=user_requested_val, - optional_features=optional_features_val, - internal_convert_user_code=internal_convert_user_code_val) - """ - - def list_of_features(values): - return parser.parse_expression('({})'.format(', '.join( - 'ag__.{}'.format(str(v)) for v in values))) - - expr_ast = templates.replace( - template, - recursive_val=parser.parse_expression(str(self.recursive)), - user_requested_val=parser.parse_expression(str(self.user_requested)), - internal_convert_user_code_val=parser.parse_expression( - str(self.internal_convert_user_code)), - optional_features_val=list_of_features(self.optional_features)) - return expr_ast[0].value - - -STANDARD_OPTIONS = ConversionOptions( - recursive=True, - user_requested=False, - internal_convert_user_code=True, - optional_features=None) - - -class ProgramContext(object): - """ProgramContext keeps track of converting function hierarchies. - - Attributes: - options: ConversionOptions - autograph_module: Deprecated. Do not use. - """ - - def __init__(self, options, autograph_module=None): - self.options = options - self.autograph_module = autograph_module - - -class Base(transformer.Base): - """All converters should inherit from this class. - - Attributes: - ctx: EntityContext - """ - - def __init__(self, ctx): - super(Base, self).__init__(ctx) - - self._used = False - self._ast_depth = 0 - - def get_definition_directive(self, node, directive, arg, default): - """Returns the unique directive argument for a symbol. - - See lang/directives.py for details on directives. - - Example: - # Given a directive in the code: - ag.foo_directive(bar, baz=1) - - # One can write for an AST node Name(id='bar'): - get_definition_directive(node, ag.foo_directive, 'baz') - - Args: - node: ast.AST, the node representing the symbol for which the directive - argument is needed. - directive: Callable[..., Any], the directive to search. - arg: str, the directive argument to return. - default: Any - - Raises: - ValueError: if conflicting annotations have been found - """ - defs = anno.getanno(node, anno.Static.ORIG_DEFINITIONS, ()) - if not defs: - return default - - arg_values_found = [] - for def_ in defs: - if (directive in def_.directives and arg in def_.directives[directive]): - arg_values_found.append(def_.directives[directive][arg]) - - if not arg_values_found: - return default - - if len(arg_values_found) == 1: - return arg_values_found[0] - - # If multiple annotations reach the symbol, they must all match. If they do, - # return any of them. - first_value = arg_values_found[0] - for other_value in arg_values_found[1:]: - if not ast_util.matches(first_value, other_value): - qn = anno.getanno(node, anno.Basic.QN) - raise ValueError( - '%s has ambiguous annotations for %s(%s): %s, %s' % - (qn, directive.__name__, arg, parser.unparse(other_value).strip(), - parser.unparse(first_value).strip())) - return first_value - - def visit(self, node): - if not self._ast_depth: - if self._used: - raise ValueError('converter objects cannot be reused') - self._used = True - - self._ast_depth += 1 - try: - return super(Base, self).visit(node) - finally: - self._ast_depth -= 1 diff --git a/src/braket/experimental/autoqasm/autograph/core/function_wrappers.py b/src/braket/experimental/autoqasm/autograph/core/function_wrappers.py deleted file mode 100644 index 1d0bb198..00000000 --- a/src/braket/experimental/autoqasm/autograph/core/function_wrappers.py +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Support for wrapping converted functions bodies with auxiliary logic.""" - -from braket.experimental.autoqasm.autograph.core import ag_ctx -from braket.experimental.autoqasm.autograph.core import converter -from braket.experimental.autoqasm.autograph.operators import variables - - -# TODO(mdan): Move this into operators - it represents a function definition. - - -class FunctionScope(object): - """Context manager that wraps the body of a converted function. - - This context manager handles various operations related to the scope of a - function: - * optional TF name scopes - these name scopes match the name of the - function, for easy visualization in tensorBoard; - * optional automatic control dependencies - this adds the same mechanism - for control dependencies that is used by `@tf.function`; it can be - optionally enabled when using `tf.autograph.to_graph`; - * tracking of autograph conversion state (whether it's enabled by the user, - conversion options; - """ - - def __init__(self, function_name, scope_name, options): - self.name = scope_name - self.options = options - - if options.user_requested: - self.autograph_ctx = ag_ctx.ControlStatusCtx(ag_ctx.Status.ENABLED, - options) - self.callopts = options.call_options() - - use_name_scope = options.uses(converter.Feature.NAME_SCOPES) - self.use_name_scope = use_name_scope - if use_name_scope: - raise NotImplementedError("name_scopes is TensorFlow-specific") - - use_auto_deps = self.options.uses(converter.Feature.AUTO_CONTROL_DEPS) - self.use_auto_deps = use_auto_deps - if use_auto_deps: - raise NotImplementedError("auto_control_deps is TensorFlow-specific") - - def _sanitize(self, name): - """See https://www.tensorflow.org/api_docs/python/tf/Graph#name_scope.""" - # TensorFlow doesn't like leading underscores at the top level. - if name and name.startswith('_'): - name = 'fn' + name - return name - - def __enter__(self): - if self.options.user_requested: - self.autograph_ctx.__enter__() - if self.use_name_scope: - self.name_scope.__enter__() - if self.use_auto_deps: - self.autodeps_scope.__enter__() - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - if self.options.user_requested: - self.autograph_ctx.__exit__(exc_type, exc_val, exc_tb) - if self.use_name_scope: - self.name_scope.__exit__(exc_type, exc_val, exc_tb) - if self.use_auto_deps: - self.autodeps_scope.__exit__(exc_type, exc_val, exc_tb) - - def ret(self, value, did_return): - """Marks a value as returned from the function guarded by the scope.""" - del did_return - - if isinstance(value, variables.UndefinedReturnValue): - return None - - return value - - -def with_function_scope(thunk, scope_name, options): - """Inline version of the FunctionScope context manager.""" - with FunctionScope('lambda_', scope_name, options) as scope: - return thunk(scope) diff --git a/src/braket/experimental/autoqasm/autograph/core/unsupported_features_checker.py b/src/braket/experimental/autoqasm/autograph/core/unsupported_features_checker.py deleted file mode 100644 index 3e047f33..00000000 --- a/src/braket/experimental/autoqasm/autograph/core/unsupported_features_checker.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Checkers for detecting unsupported Python features.""" - -import gast - -from braket.experimental.autoqasm.autograph.pyct import errors - - -class UnsupportedFeaturesChecker(gast.NodeVisitor): - """Quick check for Python features we know we don't support. - - Any features detected will cause AutoGraph to not compile a function. - """ - - def visit_Attribute(self, node): - if (node.attr is not None - and node.attr.startswith('__') and not node.attr.endswith('__')): - raise errors.UnsupportedLanguageElementError( - 'mangled names are not yet supported') - self.generic_visit(node) - - def visit_For(self, node): - if node.orelse: - raise errors.UnsupportedLanguageElementError( - 'for/else statement not yet supported') - self.generic_visit(node) - - def visit_While(self, node): - if node.orelse: - raise errors.UnsupportedLanguageElementError( - 'while/else statement not yet supported') - self.generic_visit(node) - - # These checks could potentially be replaced with inspect.isgeneratorfunction - # to avoid a getsource/parse/ast-walk round trip. - def visit_Yield(self, node): - raise errors.UnsupportedLanguageElementError('generators are not supported') - - def visit_YieldFrom(self, node): - raise errors.UnsupportedLanguageElementError('generators are not supported') - - -def verify(node): - UnsupportedFeaturesChecker().visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/impl/api_core.py b/src/braket/experimental/autoqasm/autograph/impl/api_core.py deleted file mode 100644 index 1092619a..00000000 --- a/src/braket/experimental/autoqasm/autograph/impl/api_core.py +++ /dev/null @@ -1,161 +0,0 @@ -# Copyright 2016 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""This module contains the user- and codegen-facing API for AutoGraph.""" - -import inspect -import os -import sys -import traceback - -from braket.experimental.autoqasm.autograph.pyct import error_utils -from braket.experimental.autoqasm.autograph.pyct import errors -from braket.experimental.autoqasm.autograph.pyct import origin_info -from braket.experimental.autoqasm.autograph.logging import ag_logging as logging -from braket.experimental.autoqasm.autograph.tf_utils import tf_stack -import inspect as tf_inspect - - -def is_autograph_strict_conversion_mode(): - return int(os.environ.get('AUTOGRAPH_STRICT_CONVERSION', '0')) > 0 - - -def _log_callargs(f, args, kwargs): - """Logging helper.""" - logging.log(2, 'Defaults of %s : %s', f, f.__defaults__) - logging.log(2, 'KW defaults of %s : %s', f, f.__kwdefaults__) - - if kwargs is not None: - callargs = tf_inspect.getcallargs(f, *args, **kwargs) - else: - callargs = tf_inspect.getcallargs(f, *args) - - formatted_callargs = '\n'.join( - ' {}: {}'.format(k, v) for k, v in callargs.items()) - logging.log(2, 'Calling %s with\n%s\n', f, formatted_callargs) - - -# -# Error handling -# - - -# TODO(mdan): Export this symbol. -class AutoGraphError(errors.PyCTError): - """Base class for all AutoGraph exceptions.""" - pass - - -class ConversionError(AutoGraphError): - """Raised during the conversion process.""" - pass - - -class StagingError(AutoGraphError): - """Raised during the staging (i.e. Python execution) of converted code.""" - pass - - -class _ErrorMetadata(error_utils.ErrorMetadataBase): - """AutoGraph-specific error metadata. See base class.""" - - def create_exception(self, source_error): - preferred_type = type(source_error) - if preferred_type in (errors.PyCTError, AutoGraphError, ConversionError, - StagingError): - return preferred_type(self.get_message()) - - exc = super(_ErrorMetadata, self).create_exception(source_error) - if exc is not None: - return exc - - # Note: While changing an error's message property to change the message it - # displays will probably work a lot of times, there is no standard way in - # Python to do that. The safest way is therefore to create a new exception. - # For user defined exceptions, we could define an interface that allowed - # them to work under this mechanism. - return StagingError(self.get_message()) - - -def _attach_error_metadata(e, f): - """Augments an error with the metadata necessary for rewrite.""" - if hasattr(e, 'ag_pass_through'): - return - - metadata = getattr(e, 'ag_error_metadata', None) - source_map = f.ag_source_map - - if metadata is None: - logging.log(1, 'Caught error in user callable %s', f, exc_info=True) - message = '{}: {}'.format(e.__class__.__name__, e) - else: - message = None - - cause_tb = traceback.extract_tb(sys.exc_info()[2])[1:] - - e.ag_error_metadata = _ErrorMetadata(cause_tb, metadata, message, source_map, - __file__) - - -class StackTraceMapper(tf_stack.StackTraceMapper): - """Remaps generated code to code it originated from.""" - - def __init__(self, converted_fn): - super().__init__() - self._source_map = converted_fn.ag_source_map - # This may be called repeatedly: once on entry, by the superclass, then by - # each child context manager. - self._cached_map = None - - def get_effective_source_map(self): - if self._cached_map is not None: - return self._cached_map - - parent_map = self.parent.get_effective_source_map() - - effective_source_map = {} - for loc, origin in self._source_map.items(): - effective_source_map[(loc.filename, loc.lineno)] = (origin.loc.filename, - origin.loc.lineno, - origin.function_name) - - for key, value in parent_map.items(): - filename, lineno, _ = value - value_loc = origin_info.LineLocation(filename=filename, lineno=lineno) - if value_loc in self._source_map: - origin = self._source_map[value_loc] - effective_source_map[key] = (origin.loc.filename, origin.loc.lineno, - origin.function_name) - else: - effective_source_map[key] = value - - self._cached_map = effective_source_map - return effective_source_map - - -# -# Generated code support -# - - -def autograph_artifact(entity, extras=None): - if inspect.ismethod(entity): - setattr(entity.__func__, 'autograph_info__', extras) - else: - setattr(entity, 'autograph_info__', extras) - return entity - - -def is_autograph_artifact(entity): - return hasattr(entity, 'autograph_info__') diff --git a/src/braket/experimental/autoqasm/autograph/lang/directives.py b/src/braket/experimental/autoqasm/autograph/lang/directives.py deleted file mode 100644 index 8072d008..00000000 --- a/src/braket/experimental/autoqasm/autograph/lang/directives.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Directives are special no-op functions that serve as compilation markers. - -They provide static information like type hints, compilation and TensorFlow -overrides. - -These serve as annotations in the compiled code, allowing the user some control -over the compilation process. They have no functional role at runtime. -""" - -UNSPECIFIED = object() - - -def set_element_type(entity, dtype, shape=UNSPECIFIED): - """Indicates that the entity is expected hold items of specified type/shape. - - The staged TensorFlow ops will reflect and assert this data type. Ignored - otherwise. - - Args: - entity: The entity to annotate. - dtype: TensorFlow dtype value to assert for entity. - shape: Optional shape to assert for entity. - """ - del entity - del dtype - del shape - - -def set_loop_options( - parallel_iterations=UNSPECIFIED, - swap_memory=UNSPECIFIED, - maximum_iterations=UNSPECIFIED, - shape_invariants=UNSPECIFIED): - """Specifies additional arguments to be passed to the enclosing while_loop. - - The parameters apply to and only to the immediately enclosing loop. It only - has effect if the loop is staged as a TF while_loop; otherwise the parameters - have no effect. - - Usage: - - >>> @tf.function(autograph=True) - ... def f(): - ... n = 0 - ... for i in tf.range(10): - ... tf.autograph.experimental.set_loop_options(maximum_iterations=3) - ... n += 1 - ... return n - - >>> @tf.function(autograph=True) - ... def f(): - ... v = tf.constant((0,)) - ... for i in tf.range(3): - ... tf.autograph.experimental.set_loop_options( - ... shape_invariants=[(v, tf.TensorShape([None]))] - ... ) - ... v = tf.concat((v, [i]), 0) - ... return v - - Also see tf.while_loop. - - Args: - parallel_iterations: The maximum number of iterations allowed to run in - parallel at any given time. Note that this does not guarantee parallel - execution. - swap_memory: Whether to store intermediate values needed for - gradients on the CPU instead of GPU. - maximum_iterations: Allows limiting the total number of iterations executed - by the loop. - shape_invariants: Allows controlling the argument with the same name passed - to tf.while_loop. Unlike tf.while_loop, this is a list of - `(tensor, shape)` pairs. - """ - del parallel_iterations - del swap_memory - del maximum_iterations - del shape_invariants diff --git a/src/braket/experimental/autoqasm/autograph/logging/ag_logging.py b/src/braket/experimental/autoqasm/autograph/logging/ag_logging.py deleted file mode 100644 index 36a13a7e..00000000 --- a/src/braket/experimental/autoqasm/autograph/logging/ag_logging.py +++ /dev/null @@ -1,142 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Logging and debugging utilities.""" - -import os -import sys -import traceback - -# TODO(mdan): Use a custom logger class. -import logging - -VERBOSITY_VAR_NAME = 'AUTOGRAPH_VERBOSITY' -DEFAULT_VERBOSITY = 0 - -verbosity_level = None # vlog-like. Takes precedence over the env variable. -echo_log_to_stdout = False - -# In interactive Python, logging echo is enabled by default. -if hasattr(sys, 'ps1') or hasattr(sys, 'ps2'): - echo_log_to_stdout = True - - -def set_verbosity(level, alsologtostdout=False): - """Sets the AutoGraph verbosity level. - - _Debug logging in AutoGraph_ - - More verbose logging is useful to enable when filing bug reports or doing - more in-depth debugging. - - There are two means to control the logging verbosity: - - * The `set_verbosity` function - - * The `AUTOGRAPH_VERBOSITY` environment variable - - `set_verbosity` takes precedence over the environment variable. - - For example: - - ```python - import os - import tensorflow as tf - - os.environ['AUTOGRAPH_VERBOSITY'] = '5' - # Verbosity is now 5 - - tf.autograph.set_verbosity(0) - # Verbosity is now 0 - - os.environ['AUTOGRAPH_VERBOSITY'] = '1' - # No effect, because set_verbosity was already called. - ``` - - Logs entries are output to [absl](https://abseil.io)'s - [default output](https://abseil.io/docs/python/guides/logging), - with `INFO` level. - Logs can be mirrored to stdout by using the `alsologtostdout` argument. - Mirroring is enabled by default when Python runs in interactive mode. - - Args: - level: int, the verbosity level; larger values specify increased verbosity; - 0 means no logging. When reporting bugs, it is recommended to set this - value to a larger number, like 10. - alsologtostdout: bool, whether to also output log messages to `sys.stdout`. - """ - global verbosity_level - global echo_log_to_stdout - verbosity_level = level - echo_log_to_stdout = alsologtostdout - - -def trace(*args): - """Traces argument information at compilation time. - - `trace` is useful when debugging, and it always executes during the tracing - phase, that is, when the TF graph is constructed. - - _Example usage_ - - ```python - import tensorflow as tf - - for i in tf.range(10): - tf.autograph.trace(i) - # Output: - ``` - - Args: - *args: Arguments to print to `sys.stdout`. - """ - print(*args) - - -def get_verbosity(): - global verbosity_level - if verbosity_level is not None: - return verbosity_level - return int(os.getenv(VERBOSITY_VAR_NAME, DEFAULT_VERBOSITY)) - - -def has_verbosity(level): - return get_verbosity() >= level - - -def _output_to_stdout(msg, *args, **kwargs): - print(msg % args) - if kwargs.get('exc_info', False): - traceback.print_exc() - - -def error(level, msg, *args, **kwargs): - if has_verbosity(level): - logging.error(msg, *args, **kwargs) - if echo_log_to_stdout: - _output_to_stdout('ERROR: ' + msg, *args, **kwargs) - - -def log(level, msg, *args, **kwargs): - if has_verbosity(level): - logging.info(msg, *args, **kwargs) - if echo_log_to_stdout: - _output_to_stdout(msg, *args, **kwargs) - - -def warning(msg, *args, **kwargs): - logging.warning(msg, *args, **kwargs) - if echo_log_to_stdout: - _output_to_stdout('WARNING: ' + msg, *args, **kwargs) - sys.stdout.flush() diff --git a/src/braket/experimental/autoqasm/autograph/operators/variables.py b/src/braket/experimental/autoqasm/autograph/operators/variables.py deleted file mode 100644 index 115c4470..00000000 --- a/src/braket/experimental/autoqasm/autograph/operators/variables.py +++ /dev/null @@ -1,104 +0,0 @@ -# Copyright 2019 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Utilities used to capture Python idioms.""" - - -def ld(v): - """Load variable operator.""" - if isinstance(v, Undefined): - return v.read() - return v - - -def ldu(load_v, name): - """Load variable operator that returns Undefined when failing to evaluate. - - Note: the name ("load or return undefined") is abbreviated to minimize - the amount of clutter in generated code. - - This variant of `ld` is useful when loading symbols that may be undefined at - runtime, such as composite symbols, and whether they are defined or not cannot - be determined statically. For example `d['a']` is undefined when `d` is an - empty dict. - - Args: - load_v: Lambda that executes the actual read. - name: Human-readable name of the symbol being read. - Returns: - Either the value of the symbol, or Undefined, if the symbol is not fully - defined. - """ - try: - # TODO(mdan): Use locals()/globals() here. - return load_v() - except (KeyError, AttributeError, NameError): - return Undefined(name) - - -class Undefined(object): - """Represents an undefined symbol in Python. - - This is used to reify undefined symbols, which is required to use the - functional form of loops. - Example: - - while n > 0: - n = n - 1 - s = n - return s # Runtime error if n == 0 - - This is valid Python code and will not result in an error as long as n - is positive. The use of this class is to stay as close to Python semantics - as possible for staged code of this nature. - - Converted version of the above showing the possible usage of this class: - - s = Undefined('s') - init_state = (s,) - s = while_loop(cond, body, init_state) - return s # s is an instance of Undefined if the loop never runs - - Attributes: - symbol_name: Text, identifier for the undefined symbol - """ - - __slots__ = ('symbol_name',) - - def __init__(self, symbol_name): - self.symbol_name = symbol_name - - def read(self): - raise UnboundLocalError("'{}' is used before assignment".format( - self.symbol_name)) - - def __repr__(self): - return self.symbol_name - - def __getattribute__(self, name): - try: - # If it's an existing attribute, return it. - return object.__getattribute__(self, name) - except AttributeError: - # Otherwise return Undefined. - return self - - def __getitem__(self, i): - return self - - -# TODO(mdan): Refactor as a RetVal object, aggregating the value and do_return. -class UndefinedReturnValue(object): - """Represents a return value that is undefined.""" - pass diff --git a/src/braket/experimental/autoqasm/autograph/operators/variables_test.py b/src/braket/experimental/autoqasm/autograph/operators/variables_test.py deleted file mode 100644 index d7856571..00000000 --- a/src/braket/experimental/autoqasm/autograph/operators/variables_test.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2019 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for python_lang_utils module.""" - -from braket.experimental.autoqasm.autograph.operators import variables -from tensorflow.python.platform import test - - -class SpecialValuesTest(test.TestCase): - - def test_undefined(self): - undefined_symbol = variables.Undefined('name') - undefined_symbol2 = variables.Undefined('name') - - self.assertEqual(undefined_symbol.symbol_name, 'name') - self.assertEqual(undefined_symbol2.symbol_name, 'name') - self.assertNotEqual(undefined_symbol, undefined_symbol2) - - def test_undefined_operations(self): - undefined_symbol = variables.Undefined('name') - - self.assertIsInstance(undefined_symbol.foo, variables.Undefined) - self.assertIsInstance(undefined_symbol[0], variables.Undefined) - self.assertNotIsInstance(undefined_symbol.__class__, variables.Undefined) - - def test_read(self): - self.assertEqual(variables.ld(1), 1) - o = object() - self.assertEqual(variables.ld(o), o) - - self.assertIsNone(variables.ld(None)) - - def test_read_undefined(self): - with self.assertRaisesRegex(UnboundLocalError, 'used before assignment'): - variables.ld(variables.Undefined('a')) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/__init__.py b/src/braket/experimental/autoqasm/autograph/pyct/__init__.py deleted file mode 100644 index 9202a5ce..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Python source code transformation library.""" - diff --git a/src/braket/experimental/autoqasm/autograph/pyct/anno.py b/src/braket/experimental/autoqasm/autograph/pyct/anno.py deleted file mode 100644 index 59ff7035..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/anno.py +++ /dev/null @@ -1,174 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""AST node annotation support. - -Adapted from Tangent. -""" - -import enum - -# pylint:disable=g-bad-import-order - -import gast -# pylint:enable=g-bad-import-order - - -# TODO(mdan): Shorten the names. -# These names are heavily used, and anno.blaa -# TODO(mdan): Replace the attr-dict mechanism with a more typed solution. - - -class NoValue(enum.Enum): - """Base class for different types of AST annotations.""" - - def of(self, node, default=None): - return getanno(node, self, default=default) - - def add_to(self, node, value): - setanno(node, self, value) - - def exists(self, node): - return hasanno(node, self) - - def __repr__(self): - return str(self.name) - - -class Basic(NoValue): - """Container for basic annotation keys. - - The enum values are used strictly for documentation purposes. - """ - - QN = 'Qualified name, as it appeared in the code. See qual_names.py.' - SKIP_PROCESSING = ( - 'This node should be preserved as is and not processed any further.') - INDENT_BLOCK_REMAINDER = ( - 'When a node is annotated with this, the remainder of the block should' - ' be indented below it. The annotation contains a tuple' - ' (new_body, name_map), where `new_body` is the new indented block and' - ' `name_map` allows renaming symbols.') - ORIGIN = ('Information about the source code that converted code originated' - ' from. See origin_information.py.') - DIRECTIVES = ('User directives associated with a statement or a variable.' - ' Typically, they affect the immediately-enclosing statement.') - - EXTRA_LOOP_TEST = ( - 'A special annotation containing additional test code to be executed in' - ' for loops.') - - -class Static(NoValue): - """Container for static analysis annotation keys. - - The enum values are used strictly for documentation purposes. - """ - - # Symbols - # These flags are boolean. - IS_PARAM = 'Symbol is a parameter to the function being analyzed.' - - # Scopes - # Scopes are represented by objects of type activity.Scope. - SCOPE = 'The scope for the annotated node. See activity.py.' - # TODO(mdan): Drop these in favor of accessing the child's SCOPE. - ARGS_SCOPE = 'The scope for the argument list of a function call.' - COND_SCOPE = 'The scope for the test node of a conditional statement.' - BODY_SCOPE = ( - 'The scope for the main body of a statement (True branch for if ' - 'statements, main body for loops).') - ORELSE_SCOPE = ( - 'The scope for the orelse body of a statement (False branch for if ' - 'statements, orelse body for loops).') - - # Static analysis annotations. - DEFINITIONS = ( - 'Reaching definition information. See reaching_definitions.py.') - ORIG_DEFINITIONS = ( - 'The value of DEFINITIONS that applied to the original code before any' - ' conversion.') - DEFINED_FNS_IN = ( - 'Local function definitions that may exist when exiting the node. See' - ' reaching_fndefs.py') - DEFINED_VARS_IN = ( - 'Symbols defined when entering the node. See reaching_definitions.py.') - LIVE_VARS_OUT = ('Symbols live when exiting the node. See liveness.py.') - LIVE_VARS_IN = ('Symbols live when entering the node. See liveness.py.') - TYPES = 'Static type information. See type_inference.py.' - CLOSURE_TYPES = 'Types of closure symbols at each detected call site.' - VALUE = 'Static value information. See type_inference.py.' - - -FAIL = object() - - -def keys(node, field_name='___pyct_anno'): - if not hasattr(node, field_name): - return frozenset() - return frozenset(getattr(node, field_name).keys()) - - -def getanno(node, key, default=FAIL, field_name='___pyct_anno'): - if (default is FAIL or (hasattr(node, field_name) and - (key in getattr(node, field_name)))): - return getattr(node, field_name)[key] - return default - - -def hasanno(node, key, field_name='___pyct_anno'): - return hasattr(node, field_name) and key in getattr(node, field_name) - - -def setanno(node, key, value, field_name='___pyct_anno'): - annotations = getattr(node, field_name, {}) - setattr(node, field_name, annotations) - annotations[key] = value - - # So that the annotations survive gast_to_ast() and ast_to_gast() - if field_name not in node._fields: - node._fields += (field_name,) - - -def delanno(node, key, field_name='___pyct_anno'): - annotations = getattr(node, field_name) - del annotations[key] - if not annotations: - delattr(node, field_name) - node._fields = tuple(f for f in node._fields if f != field_name) - - -def copyanno(from_node, to_node, key, field_name='___pyct_anno'): - if hasanno(from_node, key, field_name=field_name): - setanno( - to_node, - key, - getanno(from_node, key, field_name=field_name), - field_name=field_name) - - -def dup(node, copy_map, field_name='___pyct_anno'): - """Recursively copies annotations in an AST tree. - - Args: - node: ast.AST - copy_map: Dict[Hashable, Hashable], maps a source anno key to a destination - key. All annotations with the source key will be copied to identical - annotations with the destination key. - field_name: str - """ - for n in gast.walk(node): - for k in copy_map: - if hasanno(n, k, field_name): - setanno(n, copy_map[k], getanno(n, k, field_name), field_name) diff --git a/src/braket/experimental/autoqasm/autograph/pyct/anno_test.py b/src/braket/experimental/autoqasm/autograph/pyct/anno_test.py deleted file mode 100644 index 77fefdff..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/anno_test.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for anno module.""" - -import ast - -from braket.experimental.autoqasm.autograph.pyct import anno -from tensorflow.python.platform import test - - -# TODO(mdan): Consider strong types instead of primitives. - - -class AnnoTest(test.TestCase): - - def test_basic(self): - node = ast.Name() - - self.assertEqual(anno.keys(node), set()) - self.assertFalse(anno.hasanno(node, 'foo')) - with self.assertRaises(AttributeError): - anno.getanno(node, 'foo') - - anno.setanno(node, 'foo', 3) - - self.assertEqual(anno.keys(node), {'foo'}) - self.assertTrue(anno.hasanno(node, 'foo')) - self.assertEqual(anno.getanno(node, 'foo'), 3) - self.assertEqual(anno.getanno(node, 'bar', default=7), 7) - - anno.delanno(node, 'foo') - - self.assertEqual(anno.keys(node), set()) - self.assertFalse(anno.hasanno(node, 'foo')) - with self.assertRaises(AttributeError): - anno.getanno(node, 'foo') - self.assertIsNone(anno.getanno(node, 'foo', default=None)) - - def test_copy(self): - node_1 = ast.Name() - anno.setanno(node_1, 'foo', 3) - - node_2 = ast.Name() - anno.copyanno(node_1, node_2, 'foo') - anno.copyanno(node_1, node_2, 'bar') - - self.assertTrue(anno.hasanno(node_2, 'foo')) - self.assertFalse(anno.hasanno(node_2, 'bar')) - - def test_duplicate(self): - node = ast.If( - test=ast.Num(1), - body=[ast.Expr(ast.Name('bar', ast.Load()))], - orelse=[]) - anno.setanno(node, 'spam', 1) - anno.setanno(node, 'ham', 1) - anno.setanno(node.body[0], 'ham', 1) - - anno.dup(node, {'spam': 'eggs'}) - - self.assertTrue(anno.hasanno(node, 'spam')) - self.assertTrue(anno.hasanno(node, 'ham')) - self.assertTrue(anno.hasanno(node, 'eggs')) - self.assertFalse(anno.hasanno(node.body[0], 'eggs')) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/ast_util.py b/src/braket/experimental/autoqasm/autograph/pyct/ast_util.py deleted file mode 100644 index 2b0aa2fb..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/ast_util.py +++ /dev/null @@ -1,344 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""AST manipulation utilities.""" - -import ast - -import gast - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import qual_names - - -class CleanCopier(object): - """NodeTransformer-like visitor that copies an AST.""" - - def __init__(self, preserve_annos): - super(CleanCopier, self).__init__() - self.preserve_annos = preserve_annos - - def copy(self, node): - """Returns a deep copy of node (excluding some fields, see copy_clean).""" - - if isinstance(node, list): - return [self.copy(n) for n in node] - elif isinstance(node, tuple): - return tuple(self.copy(n) for n in node) - elif not isinstance(node, (gast.AST, ast.AST)): - # Assuming everything that's not an AST, list or tuple is a value type - # and may simply be assigned. - return node - - assert isinstance(node, (gast.AST, ast.AST)) - - new_fields = {} - for f in node._fields: - if not f.startswith('__') and hasattr(node, f): - new_fields[f] = self.copy(getattr(node, f)) - new_node = type(node)(**new_fields) - - if self.preserve_annos: - for k in self.preserve_annos: - anno.copyanno(node, new_node, k) - return new_node - - -def copy_clean(node, preserve_annos=None): - """Creates a deep copy of an AST. - - The copy will not include fields that are prefixed by '__', with the - exception of user-specified annotations. - - Args: - node: ast.AST - preserve_annos: Optional[Set[Hashable]], annotation keys to include in the - copy - Returns: - ast.AST - """ - return CleanCopier(preserve_annos).copy(node) - - -class SymbolRenamer(gast.NodeTransformer): - """Transformer that can rename symbols to a simple names.""" - - def __init__(self, name_map): - self.name_map = name_map - - def _process_name_node(self, node): - qn = anno.getanno(node, anno.Basic.QN) - if qn in self.name_map: - new_node = gast.Name( - str(self.name_map[qn]), - ctx=node.ctx, - annotation=None, - type_comment=None) - # All annotations get carried over. - for k in anno.keys(node): - anno.copyanno(node, new_node, k) - return new_node - return self.generic_visit(node) - - def _process_list_of_strings(self, names): - for i in range(len(names)): - qn = qual_names.QN(names[i]) - if qn in self.name_map: - names[i] = str(self.name_map[qn]) - return names - - def visit_Nonlocal(self, node): - node.names = self._process_list_of_strings(node.names) - return node - - def visit_Global(self, node): - node.names = self._process_list_of_strings(node.names) - return node - - def visit_Name(self, node): - return self._process_name_node(node) - - def visit_Attribute(self, node): - if anno.hasanno(node, anno.Basic.QN): - return self._process_name_node(node) - # Renaming attributes is not supported. - return self.generic_visit(node) - - def visit_FunctionDef(self, node): - qn = qual_names.QN(node.name) - if qn in self.name_map: - node.name = str(self.name_map[qn]) - return self.generic_visit(node) - - -def rename_symbols(node, name_map): - """Renames symbols in an AST. Requires qual_names annotations.""" - renamer = SymbolRenamer(name_map) - if isinstance(node, list): - return [renamer.visit(n) for n in node] - elif isinstance(node, tuple): - return tuple(renamer.visit(n) for n in node) - return renamer.visit(node) - - -def keywords_to_dict(keywords): - """Converts a list of ast.keyword objects to a dict.""" - keys = [] - values = [] - for kw in keywords: - keys.append(gast.Constant(kw.arg, kind=None)) - values.append(kw.value) - return gast.Dict(keys=keys, values=values) - - -class PatternMatcher(gast.NodeVisitor): - """Matches a node against a pattern represented by a node.""" - - def __init__(self, pattern): - self.pattern = pattern - self.pattern_stack = [] - self.matches = True - - def compare_and_visit(self, node, pattern): - self.pattern_stack.append(self.pattern) - self.pattern = pattern - self.generic_visit(node) - self.pattern = self.pattern_stack.pop() - - def no_match(self): - self.matches = False - return False - - def is_wildcard(self, p): - if isinstance(p, (list, tuple)) and len(p) == 1: - p, = p - if isinstance(p, gast.Name) and p.id == '_': - return True - if p == '_': - return True - return False - - def generic_visit(self, node): - if not self.matches: - return - - pattern = self.pattern - for f in node._fields: - if f.startswith('__'): - continue - - if not hasattr(node, f): - if hasattr(pattern, f) and getattr(pattern, f): - return self.no_match() - else: - continue - if not hasattr(pattern, f): - return self.no_match() - - v = getattr(node, f) - p = getattr(pattern, f) - - if self.is_wildcard(p): - continue - if isinstance(v, (list, tuple)): - if not isinstance(p, (list, tuple)) or len(v) != len(p): - return self.no_match() - for v_item, p_item in zip(v, p): - self.compare_and_visit(v_item, p_item) - elif isinstance(v, (gast.AST, ast.AST)): - if not isinstance(v, type(p)) and not isinstance(p, type(v)): - return self.no_match() - self.compare_and_visit(v, p) - else: - # Assume everything else is a value type. - if v != p: - return self.no_match() - - -def matches(node, pattern): - """Basic pattern matcher for AST. - - The pattern may contain wildcards represented by the symbol '_'. A node - matches a pattern if for every node in the tree, either there is a node of - the same type in pattern, or a Name node with id='_'. - - Args: - node: ast.AST - pattern: ast.AST - Returns: - bool - """ - if isinstance(pattern, str): - pattern = parser.parse_str(pattern) - - matcher = PatternMatcher(pattern) - matcher.visit(node) - return matcher.matches - - -# TODO(mdan): Once we have error tracing, we may be able to just go to SSA. -def apply_to_single_assignments(targets, values, apply_fn): - """Applies a function to each individual assignment. - - This function can process a possibly-unpacked (e.g. a, b = c, d) assignment. - It tries to break down the unpacking if possible. In effect, it has the same - effect as passing the assigned values in SSA form to apply_fn. - - Examples: - - The following will result in apply_fn(a, c), apply_fn(b, d): - - a, b = c, d - - The following will result in apply_fn(a, c[0]), apply_fn(b, c[1]): - - a, b = c - - The following will result in apply_fn(a, (b, c)): - - a = b, c - - It uses the visitor pattern to allow subclasses to process single - assignments individually. - - Args: - targets: Union[List[ast.AST, ...], Tuple[ast.AST, ...], ast.AST, should be - used with the targets field of an ast.Assign node - values: ast.AST - apply_fn: Callable[[ast.AST, ast.AST], None], called with the - respective nodes of each single assignment - """ - if not isinstance(targets, (list, tuple)): - targets = (targets,) - for target in targets: - if isinstance(target, (gast.Tuple, gast.List)): - for i in range(len(target.elts)): - target_el = target.elts[i] - if isinstance(values, (gast.Tuple, gast.List)): - value_el = values.elts[i] - else: - idx = parser.parse_expression(str(i)) - value_el = gast.Subscript(values, idx, ctx=gast.Load()) - apply_to_single_assignments(target_el, value_el, apply_fn) - else: - apply_fn(target, values) - - -def parallel_walk(node, other): - """Walks two ASTs in parallel. - - The two trees must have identical structure. - - Args: - node: Union[ast.AST, Iterable[ast.AST]] - other: Union[ast.AST, Iterable[ast.AST]] - Yields: - Tuple[ast.AST, ast.AST] - Raises: - ValueError: if the two trees don't have identical structure. - """ - if isinstance(node, (list, tuple)): - node_stack = list(node) - else: - node_stack = [node] - - if isinstance(other, (list, tuple)): - other_stack = list(other) - else: - other_stack = [other] - - while node_stack and other_stack: - assert len(node_stack) == len(other_stack) - n = node_stack.pop() - o = other_stack.pop() - - if ((not isinstance(n, (ast.AST, gast.AST, str)) and n is not None) or - (not isinstance(o, (ast.AST, gast.AST, str)) and n is not None) or - n.__class__.__name__ != o.__class__.__name__): - raise ValueError('inconsistent nodes: {} ({}) and {} ({})'.format( - n, n.__class__.__name__, o, o.__class__.__name__)) - - yield n, o - - if isinstance(n, str): - assert isinstance(o, str), 'The check above should have ensured this' - continue - if n is None: - assert o is None, 'The check above should have ensured this' - continue - - for f in n._fields: - n_child = getattr(n, f, None) - o_child = getattr(o, f, None) - if f.startswith('__') or n_child is None or o_child is None: - continue - - if isinstance(n_child, (list, tuple)): - if (not isinstance(o_child, (list, tuple)) or - len(n_child) != len(o_child)): - raise ValueError( - 'inconsistent values for field {}: {} and {}'.format( - f, n_child, o_child)) - node_stack.extend(n_child) - other_stack.extend(o_child) - - elif isinstance(n_child, (gast.AST, ast.AST)): - node_stack.append(n_child) - other_stack.append(o_child) - - elif n_child != o_child: - raise ValueError( - 'inconsistent values for field {}: {} and {}'.format( - f, n_child, o_child)) diff --git a/src/braket/experimental/autoqasm/autograph/pyct/ast_util_test.py b/src/braket/experimental/autoqasm/autograph/pyct/ast_util_test.py deleted file mode 100644 index 7ced51c1..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/ast_util_test.py +++ /dev/null @@ -1,246 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for ast_util module.""" - -import ast -import collections -import textwrap - -import gast - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import ast_util -from braket.experimental.autoqasm.autograph.pyct import loader -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import pretty_printer -from braket.experimental.autoqasm.autograph.pyct import qual_names -from tensorflow.python.platform import test - - -class AstUtilTest(test.TestCase): - - def assertAstMatches(self, actual_node, expected_node_src): - expected_node = gast.parse('({})'.format(expected_node_src)).body[0] - msg = 'AST did not match expected:\n{}\nActual:\n{}'.format( - pretty_printer.fmt(expected_node), - pretty_printer.fmt(actual_node)) - self.assertTrue(ast_util.matches(actual_node, expected_node), msg) - - def setUp(self): - super(AstUtilTest, self).setUp() - self._invocation_counts = collections.defaultdict(lambda: 0) - - def test_rename_symbols_basic(self): - node = parser.parse('a + b') - node = qual_names.resolve(node) - - node = ast_util.rename_symbols( - node, {qual_names.QN('a'): qual_names.QN('renamed_a')}) - source = parser.unparse(node, include_encoding_marker=False) - expected_node_src = 'renamed_a + b' - - self.assertIsInstance(node.value.left.id, str) - self.assertAstMatches(node, source) - self.assertAstMatches(node, expected_node_src) - - def test_rename_symbols_attributes(self): - node = parser.parse('b.c = b.c.d') - node = qual_names.resolve(node) - - node = ast_util.rename_symbols( - node, {qual_names.from_str('b.c'): qual_names.QN('renamed_b_c')}) - - source = parser.unparse(node, include_encoding_marker=False) - self.assertEqual(source.strip(), 'renamed_b_c = renamed_b_c.d') - - def test_rename_symbols_nonlocal(self): - node = parser.parse('nonlocal a, b, c') - node = qual_names.resolve(node) - - node = ast_util.rename_symbols( - node, {qual_names.from_str('b'): qual_names.QN('renamed_b')}) - - source = parser.unparse(node, include_encoding_marker=False) - self.assertEqual(source.strip(), 'nonlocal a, renamed_b, c') - - def test_rename_symbols_global(self): - node = parser.parse('global a, b, c') - node = qual_names.resolve(node) - - node = ast_util.rename_symbols( - node, {qual_names.from_str('b'): qual_names.QN('renamed_b')}) - - source = parser.unparse(node, include_encoding_marker=False) - self.assertEqual(source.strip(), 'global a, renamed_b, c') - - def test_rename_symbols_annotations(self): - node = parser.parse('a[i]') - node = qual_names.resolve(node) - anno.setanno(node, 'foo', 'bar') - orig_anno = anno.getanno(node, 'foo') - - node = ast_util.rename_symbols(node, - {qual_names.QN('a'): qual_names.QN('b')}) - - self.assertIs(anno.getanno(node, 'foo'), orig_anno) - - def test_rename_symbols_function(self): - node = parser.parse('def f():\n pass') - node = ast_util.rename_symbols(node, - {qual_names.QN('f'): qual_names.QN('f1')}) - - source = parser.unparse(node, include_encoding_marker=False) - self.assertEqual(source.strip(), 'def f1():\n pass') - - def test_copy_clean(self): - node = parser.parse( - textwrap.dedent(""" - def f(a): - return a + 1 - """)) - setattr(node, '__foo', 'bar') - new_node = ast_util.copy_clean(node) - self.assertIsNot(new_node, node) - self.assertFalse(hasattr(new_node, '__foo')) - - def test_copy_clean_preserves_annotations(self): - node = parser.parse( - textwrap.dedent(""" - def f(a): - return a + 1 - """)) - anno.setanno(node, 'foo', 'bar') - anno.setanno(node, 'baz', 1) - new_node = ast_util.copy_clean(node, preserve_annos={'foo'}) - self.assertEqual(anno.getanno(new_node, 'foo'), 'bar') - self.assertFalse(anno.hasanno(new_node, 'baz')) - - def test_keywords_to_dict(self): - keywords = parser.parse_expression('f(a=b, c=1, d=\'e\')').keywords - d = ast_util.keywords_to_dict(keywords) - # Make sure we generate a usable dict node by attaching it to a variable and - # compiling everything. - node = parser.parse('def f(b): pass') - node.body.append(ast.Return(d)) - result, _, _ = loader.load_ast(node) - self.assertDictEqual(result.f(3), {'a': 3, 'c': 1, 'd': 'e'}) - - def assertMatch(self, target_str, pattern_str): - node = parser.parse_expression(target_str) - pattern = parser.parse_expression(pattern_str) - self.assertTrue(ast_util.matches(node, pattern)) - - def assertNoMatch(self, target_str, pattern_str): - node = parser.parse_expression(target_str) - pattern = parser.parse_expression(pattern_str) - self.assertFalse(ast_util.matches(node, pattern)) - - def test_matches_symbols(self): - self.assertMatch('foo', '_') - self.assertNoMatch('foo()', '_') - self.assertMatch('foo + bar', 'foo + _') - self.assertNoMatch('bar + bar', 'foo + _') - self.assertNoMatch('foo - bar', 'foo + _') - - def test_matches_function_args(self): - self.assertMatch('super(Foo, self).__init__(arg1, arg2)', - 'super(_).__init__(_)') - self.assertMatch('super().__init__()', 'super(_).__init__(_)') - self.assertNoMatch('super(Foo, self).bar(arg1, arg2)', - 'super(_).__init__(_)') - self.assertMatch('super(Foo, self).__init__()', 'super(Foo, _).__init__(_)') - self.assertNoMatch('super(Foo, self).__init__()', - 'super(Bar, _).__init__(_)') - - def _mock_apply_fn(self, target, source): - target = parser.unparse(target, include_encoding_marker=False) - source = parser.unparse(source, include_encoding_marker=False) - self._invocation_counts[(target.strip(), source.strip())] += 1 - - def test_apply_to_single_assignments_dynamic_unpack(self): - node = parser.parse('a, b, c = d') - ast_util.apply_to_single_assignments(node.targets, node.value, - self._mock_apply_fn) - self.assertDictEqual(self._invocation_counts, { - ('a', 'd[0]'): 1, - ('b', 'd[1]'): 1, - ('c', 'd[2]'): 1, - }) - - def test_apply_to_single_assignments_static_unpack(self): - node = parser.parse('a, b, c = d, e, f') - ast_util.apply_to_single_assignments(node.targets, node.value, - self._mock_apply_fn) - self.assertDictEqual(self._invocation_counts, { - ('a', 'd'): 1, - ('b', 'e'): 1, - ('c', 'f'): 1, - }) - - def test_parallel_walk(self): - src = """ - def f(a): - return a + 1 - """ - node = parser.parse(textwrap.dedent(src)) - for child_a, child_b in ast_util.parallel_walk(node, node): - self.assertEqual(child_a, child_b) - - def test_parallel_walk_string_leaves(self): - src = """ - def f(a): - global g - """ - node = parser.parse(textwrap.dedent(src)) - for child_a, child_b in ast_util.parallel_walk(node, node): - self.assertEqual(child_a, child_b) - - def test_parallel_walk_inconsistent_trees(self): - node_1 = parser.parse( - textwrap.dedent(""" - def f(a): - return a + 1 - """)) - node_2 = parser.parse( - textwrap.dedent(""" - def f(a): - return a + (a * 2) - """)) - node_3 = parser.parse( - textwrap.dedent(""" - def f(a): - return a + 2 - """)) - with self.assertRaises(ValueError): - for _ in ast_util.parallel_walk(node_1, node_2): - pass - # There is not particular reason to reject trees that differ only in the - # value of a constant. - # TODO(mdan): This should probably be allowed. - with self.assertRaises(ValueError): - for _ in ast_util.parallel_walk(node_1, node_3): - pass - - def assertLambdaNodes(self, matching_nodes, expected_bodies): - self.assertEqual(len(matching_nodes), len(expected_bodies)) - for node in matching_nodes: - self.assertIsInstance(node, gast.Lambda) - self.assertIn( - parser.unparse(node.body, include_encoding_marker=False).strip(), - expected_bodies) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/cache.py b/src/braket/experimental/autoqasm/autograph/pyct/cache.py deleted file mode 100644 index 2d125e68..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/cache.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright 2016 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Caching utilities.""" - -import inspect -import weakref - - -# TODO(mdan): Add a garbage collection hook for cleaning up modules. -class _TransformedFnCache(object): - """Generic hierarchical cache for transformed functions. - - The keys are soft references (i.e. they are discarded when the key is - destroyed) created from the source function by `_get_key`. The subkeys are - strong references and can be any value. Typically they identify different - kinds of transformation. - """ - - __slots__ = ('_cache',) - - def __init__(self): - self._cache = weakref.WeakKeyDictionary() - - def _get_key(self, entity): - raise NotImplementedError('subclasses must override') - - def has(self, entity, subkey): - key = self._get_key(entity) - parent = self._cache.get(key, None) - if parent is None: - return False - return subkey in parent - - def __getitem__(self, entity): - key = self._get_key(entity) - parent = self._cache.get(key, None) - if parent is None: - # The bucket is initialized to support this usage: - # cache[key][subkey] = value - self._cache[key] = parent = {} - return parent - - def __len__(self): - return len(self._cache) - - -class CodeObjectCache(_TransformedFnCache): - """A function cache based on code objects. - - Code objects are good proxies for the source code of a function. - - This cache efficiently handles functions that share code objects, such as - functions defined in a loop, bound methods, etc. - - The cache falls back to the function object, if it doesn't have a code object. - """ - - def _get_key(self, entity): - if hasattr(entity, '__code__'): - return entity.__code__ - else: - return entity - - -class UnboundInstanceCache(_TransformedFnCache): - """A function cache based on unbound function objects. - - Using the function for the cache key allows efficient handling of object - methods. - - Unlike the _CodeObjectCache, this discriminates between different functions - even if they have the same code. This is needed for decorators that may - masquerade as another function. - """ - - def _get_key(self, entity): - if inspect.ismethod(entity): - return entity.__func__ - return entity - - diff --git a/src/braket/experimental/autoqasm/autograph/pyct/cache_test.py b/src/braket/experimental/autoqasm/autograph/pyct/cache_test.py deleted file mode 100644 index 91252c17..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/cache_test.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for cache module.""" - -from braket.experimental.autoqasm.autograph.pyct import cache -from tensorflow.python.platform import test - - -class CacheTest(test.TestCase): - - def test_code_object_cache(self): - - def factory(x): - def test_fn(): - return x + 1 - return test_fn - - c = cache.CodeObjectCache() - - f1 = factory(1) - dummy = object() - - c[f1][1] = dummy - - self.assertTrue(c.has(f1, 1)) - self.assertFalse(c.has(f1, 2)) - self.assertIs(c[f1][1], dummy) - self.assertEqual(len(c), 1) - - f2 = factory(2) - - self.assertTrue(c.has(f2, 1)) - self.assertIs(c[f2][1], dummy) - self.assertEqual(len(c), 1) - - def test_unbound_instance_cache(self): - - class TestClass(object): - - def method(self): - pass - - c = cache.UnboundInstanceCache() - - o1 = TestClass() - dummy = object() - - c[o1.method][1] = dummy - - self.assertTrue(c.has(o1.method, 1)) - self.assertFalse(c.has(o1.method, 2)) - self.assertIs(c[o1.method][1], dummy) - self.assertEqual(len(c), 1) - - o2 = TestClass() - - self.assertTrue(c.has(o2.method, 1)) - self.assertIs(c[o2.method][1], dummy) - self.assertEqual(len(c), 1) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/cfg.py b/src/braket/experimental/autoqasm/autograph/pyct/cfg.py deleted file mode 100644 index 97e54b4d..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/cfg.py +++ /dev/null @@ -1,971 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Control flow graph (CFG) structure for Python AST representation. - -The CFG is a digraph with edges representing valid control flow. Each -node is associated with exactly one AST node, but not all AST nodes may have -a corresponding CFG counterpart. - -Once built, the CFG itself is immutable, but the values it holds need not be; -they are usually annotated with information extracted by walking the graph. - -Tip: Use `Graph.as_dot` to visualize the CFG using any DOT viewer. - -Note: the CFG tries to include all code paths that MAY be taken, with a single -notable exception: - * function calls do not generate edges corresponding to exceptions they may - raise (i.e. a function call in the middle of a block does not return or jump - to any except or finally block) -TODO(mdan): Consider adding the edges above. They'd only add ~O(n) edges. -TODO(mdan): Alternatively, consider adding an edge from try to all its excepts. -""" - -# TODO(mdan): The notion of 'statements' below is inaccurate. -# They should rather be called 'block statements', because they include -# statements that may have a body, e.g. if and while. - -import collections -import enum -import weakref - -import astunparse -import gast - -from braket.experimental.autoqasm.autograph.pyct import anno - - -class Node(object): - """A node in the CFG. - - Although new instances of this class are mutable, the objects that a user - finds in the CFG are typically not. - - The nodes represent edges in the CFG graph, and maintain pointers to allow - efficient walking in both forward and reverse order. The following property - holds for all nodes: "child in node.next" iff "node in child.prev". - - Attributes: - next: FrozenSet[Node, ...], the nodes that follow this node, in control flow - order - prev: FrozenSet[Node, ...], the nodes that precede this node, in reverse - control flow order - ast_node: ast.AST, the AST node corresponding to this CFG node - """ - - def __init__(self, next_, prev, ast_node): - self.next = next_ - self.prev = prev - self.ast_node = ast_node - - def freeze(self): - self.next = frozenset(self.next) - # Assumption: All CFG nodes have identical life spans, because the graph - # owns them. Nodes should never be used outside the context of an existing - # graph. - self.prev = weakref.WeakSet(self.prev) - - def __repr__(self): - if isinstance(self.ast_node, gast.FunctionDef): - return 'def %s' % self.ast_node.name - elif isinstance(self.ast_node, gast.ClassDef): - return 'class %s' % self.ast_node.name - elif isinstance(self.ast_node, gast.withitem): - # TODO(xjun): remove use of astunparse - return astunparse.unparse(self.ast_node.context_expr).strip() - return astunparse.unparse(self.ast_node).strip() - - -class Graph( - collections.namedtuple( - 'Graph', - ['entry', 'exit', 'error', 'index', 'stmt_prev', 'stmt_next'])): - """A Control Flow Graph. - - The CFG maintains an index to allow looking up a CFG node by the AST node to - which it is associated. The index can also be enumerated in top-down, depth - first order. - - Walking the graph in forward or reverse order is supported by double - parent-child links. - - Note: the error nodes are not wired to their corresponding finally guards, - because these are shared, and wiring them would create a reverse path from - normal control flow into the error nodes, which we want to avoid. - - The graph also maintains edges corresponding to higher level statements - like for-else loops. A node is considered successor of a statement if there - is an edge from a node that is lexically a child of that statement to a node - that is not. Statement predecessors are analogously defined. - - Attributes: - entry: Node, the entry node - exit: FrozenSet[Node, ...], the exit nodes - error: FrozenSet[Node, ...], nodes that exit due to an explicitly raised - error (errors propagated from function calls are not accounted) - index: Dict[ast.Node, Node], mapping AST nodes to the respective CFG node - stmt_prev: Dict[ast.Node, FrozenSet[Node, ...]], mapping statement AST nodes - to their predecessor CFG nodes - stmt_next: Dict[ast.Node, FrozenSet[Node, ...]], mapping statement AST nodes - to their successor CFG nodes - """ - - def __repr__(self): - return self.as_dot() - - def as_dot(self): - """Print CFG in DOT format.""" - result = 'digraph CFG {\n' - for node in self.index.values(): - result += ' %s [label="%s"];\n' % (id(node), node) - for node in self.index.values(): - for next_ in node.next: - result += ' %s -> %s;\n' % (id(node), id(next_)) - result += '}' - return result - - -class _WalkMode(enum.Enum): - FORWARD = 1 - REVERSE = 2 - - -# TODO(mdan): Rename to DataFlowAnalyzer. -# TODO(mdan): Consider specializations that use gen/kill/transfer abstractions. -class GraphVisitor(object): - """Base class for a CFG visitors. - - This implementation is not thread safe. - - The visitor has some facilities to simplify dataflow analyses. In particular, - it allows revisiting the nodes at the decision of the subclass. This can be - used to visit the graph until the state reaches a fixed point. - - For more details on dataflow analysis, see - https://www.seas.harvard.edu/courses/cs252/2011sp/slides/Lec02-Dataflow.pdf - - Note: the literature generally suggests visiting successor nodes only when the - state of the current node changed, regardless of whether that successor has - ever been visited. This implementation visits every successor at least once. - - Attributes: - graph: Graph - in_: Dict[Node, Any], stores node-keyed state during a visit - out: Dict[Node, Any], stores node-keyed state during a visit - """ - - def __init__(self, graph): - self.graph = graph - self.reset() - - def init_state(self, node): - """State initialization function. - - Optional to overload. - - An in/out state slot will be created for each node in the graph. Subclasses - must overload this to control what that is initialized to. - - Args: - node: Node - """ - raise NotImplementedError('Subclasses must implement this.') - - # TODO(mdan): Rename to flow? - def visit_node(self, node): - """Visitor function. - - Args: - node: Node - - Returns: - bool, whether the node should be revisited; subclasses can visit every - reachable node exactly once by always returning False - """ - raise NotImplementedError('Subclasses must implement this.') - - def reset(self): - self.in_ = { - node: self.init_state(node) for node in self.graph.index.values() - } - self.out = { - node: self.init_state(node) for node in self.graph.index.values() - } - - def can_ignore(self, node): - """Returns True if the node can safely be assumed not to touch variables.""" - ast_node = node.ast_node - if anno.hasanno(ast_node, anno.Basic.SKIP_PROCESSING): - return True - return isinstance(ast_node, - (gast.Break, gast.Continue, gast.Raise, gast.Pass)) - - def _visit_internal(self, mode): - """Visits the CFG, breadth-first.""" - assert mode in (_WalkMode.FORWARD, _WalkMode.REVERSE) - if mode == _WalkMode.FORWARD: - open_ = [self.graph.entry] - elif mode == _WalkMode.REVERSE: - open_ = list(self.graph.exit) - closed = set() - - while open_: - node = open_.pop(0) - closed.add(node) - - should_revisit = self.visit_node(node) - - if mode == _WalkMode.FORWARD: - children = node.next - elif mode == _WalkMode.REVERSE: - children = node.prev - - for next_ in children: - if should_revisit or next_ not in closed: - open_.append(next_) - - def visit_forward(self): - self._visit_internal(_WalkMode.FORWARD) - - def visit_reverse(self): - self._visit_internal(_WalkMode.REVERSE) - - -class GraphBuilder(object): - """Builder that constructs a CFG from a given AST. - - This GraphBuilder facilitates constructing the DAG that forms the CFG when - nodes - are supplied in lexical order (i.e., top-down, depth first). Under these - conditions, it supports building patterns found in typical structured - programs. - - This builder ignores the flow generated by exceptions, which are assumed to - always be catastrophic and present purely for diagnostic purposes (e.g. to - print debug information). Statements like raise and try/catch sections are - allowed and will generate control flow edges, but ordinary statements are - assumed not to raise exceptions. - - Finally sections are also correctly interleaved between break/continue/return - nodes and their subsequent statements. - - Important concepts: - * nodes - nodes refer to CFG nodes; AST nodes are qualified explicitly - * leaf set - since the graph is constructed gradually, a leaf set maintains - the CFG nodes that will precede the node that the builder expects to - receive next; when an ordinary node is added, it is connected to the - existing leaves and it in turn becomes the new leaf - * jump nodes - nodes that should generate edges other than what - ordinary nodes would; these correspond to break, continue and return - statements - * sections - logical delimiters for subgraphs that require special - edges; there are various types of nodes, each admitting various - types of jump nodes; sections are identified by their corresponding AST - node - """ - - # TODO(mdan): Perhaps detail this in a markdown doc. - # TODO(mdan): Add exception support. - - def __init__(self, parent_ast_node): - self.reset() - self.parent = parent_ast_node - - def reset(self): - """Resets the state of this factory.""" - self.head = None - self.errors = set() - self.node_index = {} - - # TODO(mdan): Too many primitives. Use classes. - self.leaves = set() - - # Note: This mechanism requires that nodes are added in lexical order (top - # to bottom, depth first). - self.active_stmts = set() - self.owners = {} # type: Set[any] - self.forward_edges = set() # type: Tuple[Node, Node] # (from, to) - - self.finally_sections = {} - # Dict values represent (entry, exits) - self.finally_section_subgraphs = { - } # type: Dict[ast.AST, Tuple[Node, Set[Node]]] - # Whether the guard section can be reached from the statement that precedes - # it. - self.finally_section_has_direct_flow = {} - # Finally sections that await their first node. - self.pending_finally_sections = set() - - # Exit jumps keyed by the section they affect. - self.exits = {} - - # The entry of loop sections, keyed by the section. - self.section_entry = {} - # Continue jumps keyed by the section they affect. - self.continues = {} - - # Raise jumps keyed by the except section guarding them. - self.raises = {} - - # The entry of conditional sections, keyed by the section. - self.cond_entry = {} - # Lists of leaf nodes corresponding to each branch in the section. - self.cond_leaves = {} - - def _connect_nodes(self, first, second): - """Connects nodes to signify that control flows from first to second. - - Args: - first: Union[Set[Node, ...], Node] - second: Node - """ - if isinstance(first, Node): - first.next.add(second) - second.prev.add(first) - self.forward_edges.add((first, second)) - else: - for node in first: - self._connect_nodes(node, second) - - def _add_new_node(self, ast_node): - """Grows the graph by adding a CFG node following the current leaves.""" - if ast_node in self.node_index: - raise ValueError('%s added twice' % ast_node) - # Assumption: All CFG nodes have identical life spans, because the graph - # owns them. Nodes should never be used outside the context of an existing - # graph. - node = Node(next_=set(), prev=weakref.WeakSet(), ast_node=ast_node) - self.node_index[ast_node] = node - self.owners[node] = frozenset(self.active_stmts) - - if self.head is None: - self.head = node - - for leaf in self.leaves: - self._connect_nodes(leaf, node) - - # If any finally section awaits its first node, populate it. - for section_id in self.pending_finally_sections: - self.finally_section_subgraphs[section_id][0] = node - self.pending_finally_sections = set() - - return node - - def begin_statement(self, stmt): - """Marks the beginning of a statement. - - Args: - stmt: Hashable, a key by which the statement can be identified in the - CFG's stmt_prev and stmt_next attributes - """ - self.active_stmts.add(stmt) - - def end_statement(self, stmt): - """Marks the end of a statement. - - Args: - stmt: Hashable, a key by which the statement can be identified in the - CFG's stmt_prev and stmt_next attributes; must match a key previously - passed to begin_statement. - """ - self.active_stmts.remove(stmt) - - def add_ordinary_node(self, ast_node): - """Grows the graph by adding an ordinary CFG node. - - Ordinary nodes are followed by the next node, in lexical order, that is, - they become the new leaf set. - - Args: - ast_node: ast.AST - - Returns: - Node - """ - node = self._add_new_node(ast_node) - self.leaves = set((node,)) - return node - - def _add_jump_node(self, ast_node, guards): - """Grows the graph by adding a jump node. - - Jump nodes are added to the current leaf set, and the leaf set becomes - empty. If the jump node is the last in a cond section, then it may be added - back to the leaf set by a separate mechanism. - - Args: - ast_node: ast.AST - guards: Tuple[ast.AST, ...], the finally sections active for this node - - Returns: - Node - """ - node = self._add_new_node(ast_node) - self.leaves = set() - # The guards themselves may not yet be complete, and will be wired later. - self.finally_sections[node] = guards - return node - - def _connect_jump_to_finally_sections(self, node): - """Connects a jump node to the finally sections protecting it.""" - cursor = set((node,)) - if node not in self.finally_sections: - return cursor - for guard_section_id in self.finally_sections[node]: - guard_begin, guard_ends = self.finally_section_subgraphs[guard_section_id] - self._connect_nodes(cursor, guard_begin) - cursor = guard_ends - del self.finally_sections[node] - # TODO(mdan): Should garbage-collect finally_section_subgraphs. - return cursor - - def add_exit_node(self, ast_node, section_id, guards): - """Grows the graph by adding an exit node. - - This node becomes an exit for the current section. - - Args: - ast_node: ast.AST - section_id: Hashable, the node for which ast_node should be considered to - be an exit node - guards: Tuple[ast.AST, ...], the finally sections that guard ast_node - - Returns: - Node - """ - node = self._add_jump_node(ast_node, guards) - self.exits[section_id].add(node) - return node - - def add_continue_node(self, ast_node, section_id, guards): - """Grows the graph by adding a reentry node. - - This node causes control flow to go back to the loop section's entry. - - Args: - ast_node: ast.AST - section_id: Hashable, the node for which ast_node should be considered to - be an exit node - guards: Tuple[ast.AST, ...], the finally sections that guard ast_node - """ - node = self._add_jump_node(ast_node, guards) - self.continues[section_id].add(node) - - def connect_raise_node(self, node, except_guards): - """Adds extra connection between a raise node and containing except guards. - - The node is a graph node, not an ast node. - - Args: - node: Node - except_guards: Tuple[ast.AST, ...], the except sections that guard node - """ - for guard in except_guards: - if guard in self.raises: - self.raises[guard].append(node) - else: - self.raises[guard] = [node] - - def enter_section(self, section_id): - """Enters a regular section. - - Regular sections admit exit jumps, which end the section. - - Args: - section_id: Hashable, the same node that will be used in calls to the - ast_node arg passed to add_exit_node - """ - assert section_id not in self.exits - self.exits[section_id] = set() - - def exit_section(self, section_id): - """Exits a regular section.""" - - # Exits are jump nodes, which may be protected. - for exit_ in self.exits[section_id]: - self.leaves |= self._connect_jump_to_finally_sections(exit_) - - del self.exits[section_id] - - def enter_loop_section(self, section_id, entry_node): - """Enters a loop section. - - Loop sections define an entry node. The end of the section always flows back - to the entry node. These admit continue jump nodes which also flow to the - entry node. - - Args: - section_id: Hashable, the same node that will be used in calls to the - ast_node arg passed to add_continue_node - entry_node: ast.AST, the entry node into the loop (e.g. the test node for - while loops) - """ - assert section_id not in self.section_entry - assert section_id not in self.continues - self.continues[section_id] = set() - node = self.add_ordinary_node(entry_node) - self.section_entry[section_id] = node - - def exit_loop_section(self, section_id): - """Exits a loop section.""" - self._connect_nodes(self.leaves, self.section_entry[section_id]) - - # continues are jump nodes, which may be protected. - for reentry in self.continues[section_id]: - guard_ends = self._connect_jump_to_finally_sections(reentry) - self._connect_nodes(guard_ends, self.section_entry[section_id]) - - # Loop nodes always loop back. - self.leaves = set((self.section_entry[section_id],)) - - del self.continues[section_id] - del self.section_entry[section_id] - - def enter_cond_section(self, section_id): - """Enters a conditional section. - - Conditional sections define an entry node, and one or more branches. - - Args: - section_id: Hashable, the same node that will be used in calls to the - section_id arg passed to new_cond_branch - """ - - assert section_id not in self.cond_entry - assert section_id not in self.cond_leaves - self.cond_leaves[section_id] = [] - - def new_cond_branch(self, section_id): - """Begins a new branch in a cond section.""" - assert section_id in self.cond_leaves - - if section_id in self.cond_entry: - # Subsequent splits move back to the split point, and memorize the - # current leaves. - self.cond_leaves[section_id].append(self.leaves) - self.leaves = self.cond_entry[section_id] - else: - # If this is the first time we split a section, just remember the split - # point. - self.cond_entry[section_id] = self.leaves - - def exit_cond_section(self, section_id): - """Exits a conditional section.""" - for split in self.cond_leaves[section_id]: - self.leaves |= split - del self.cond_entry[section_id] - del self.cond_leaves[section_id] - - def enter_except_section(self, section_id): - """Enters an except section.""" - if section_id in self.raises: - self.leaves.update(self.raises[section_id]) - - def enter_finally_section(self, section_id): - """Enters a finally section.""" - # TODO(mdan): This, not the caller, should track the active sections. - self.finally_section_subgraphs[section_id] = [None, None] - if self.leaves: - self.finally_section_has_direct_flow[section_id] = True - else: - self.finally_section_has_direct_flow[section_id] = False - self.pending_finally_sections.add(section_id) - - def exit_finally_section(self, section_id): - """Exits a finally section.""" - assert section_id not in self.pending_finally_sections, 'Empty finally?' - self.finally_section_subgraphs[section_id][1] = self.leaves - # If the guard can only be reached by a jump, then it will not flow - # into the statement that follows it. - if not self.finally_section_has_direct_flow[section_id]: - self.leaves = set() - del self.finally_section_has_direct_flow[section_id] - - def build(self): - """Returns the CFG accumulated so far and resets the builder. - - Returns: - Graph - """ - # Freeze the nodes. - for node in self.node_index.values(): - node.freeze() - - # Build the statement edges. - stmt_next = {} - stmt_prev = {} - - for node in self.node_index.values(): - for stmt in self.owners[node]: - if stmt not in stmt_prev: - stmt_prev[stmt] = set() - if stmt not in stmt_next: - stmt_next[stmt] = set() - - for first, second in self.forward_edges: - stmts_exited = self.owners[first] - self.owners[second] - for stmt in stmts_exited: - stmt_next[stmt].add(second) - stmts_entered = self.owners[second] - self.owners[first] - for stmt in stmts_entered: - stmt_prev[stmt].add(first) - for stmt in stmt_next: - stmt_next[stmt] = frozenset(stmt_next[stmt]) - for stmt in stmt_prev: - stmt_prev[stmt] = frozenset(stmt_prev[stmt]) - - # Construct the final graph object. - result = Graph( - entry=self.head, - exit=self.leaves, - error=self.errors, - index=self.node_index, - stmt_prev=stmt_prev, - stmt_next=stmt_next) - - # Reset the state. - self.reset() - - return result - - -class AstToCfg(gast.NodeVisitor): - """Converts an AST to CFGs. - - A separate CFG will be constructed for each function. - """ - - def __init__(self): - super(AstToCfg, self).__init__() - - self.builder_stack = [] - self.builder = None - self.cfgs = {} - - self.lexical_scopes = [] - - def _enter_lexical_scope(self, node): - self.lexical_scopes.append(node) - - def _exit_lexical_scope(self, node): - leaving_node = self.lexical_scopes.pop() - assert node == leaving_node - - def _get_enclosing_finally_scopes(self, stop_at): - included = [] - for node in reversed(self.lexical_scopes): - if isinstance(node, gast.Try) and node.finalbody: - included.append(node) - if isinstance(node, stop_at): - return node, included - return None, included - - def _get_enclosing_except_scopes(self, stop_at): - included = [] - for node in reversed(self.lexical_scopes): - if isinstance(node, gast.Try) and node.handlers: - included.extend(node.handlers) - if isinstance(node, stop_at): - break - return included - - def _process_basic_statement(self, node): - self.generic_visit(node) - self.builder.add_ordinary_node(node) - - def _process_exit_statement(self, - node, - exits_nodes_of_type, - may_exit_via_except=False): - self.generic_visit(node) - # Note: this is safe because we process functions separately. - try_node, guards = self._get_enclosing_finally_scopes(exits_nodes_of_type) - assert try_node is not None, '{} that is not enclosed by any of {}'.format( - node, exits_nodes_of_type) - - node = self.builder.add_exit_node(node, try_node, guards) - - if may_exit_via_except: - except_guards = self._get_enclosing_except_scopes(exits_nodes_of_type) - self.builder.connect_raise_node(node, except_guards) - - def _process_continue_statement(self, node, *loops_to_nodes_of_type): - # Note: this is safe because we process functions separately. - try_node, guards = self._get_enclosing_finally_scopes( - tuple(loops_to_nodes_of_type)) - if try_node is None: - raise ValueError('%s that is not enclosed by any of %s' % - (node, loops_to_nodes_of_type)) - self.builder.add_continue_node(node, try_node, guards) - - def visit_ClassDef(self, node): - # We also keep the ClassDef node in the CFG, since it technically is a - # statement. - # For example, this is legal and allows executing user code: - # - # class Foo(bar()): - # pass - # - # It also has a scope: - # - # class Bar(object): - # a = 1 - if self.builder is None: - self.generic_visit(node) - return - - self.builder.add_ordinary_node(node) - - self.builder_stack.append(self.builder) - self.builder = GraphBuilder(node) - self._enter_lexical_scope(node) - - self._process_basic_statement(node) - - self._exit_lexical_scope(node) - # TODO(mdan): Track the CFG local to the class definition as well? - self.builder = self.builder_stack.pop() - - def _process_function_def(self, node, is_lambda): - # The function body is stored in a separate graph, because function - # definitions have effects very different from function calls. - if self.builder is not None: - self.builder.add_ordinary_node(node) - - self.builder_stack.append(self.builder) - self.builder = GraphBuilder(node) - - self._enter_lexical_scope(node) - self.builder.enter_section(node) - - self._process_basic_statement(node.args) - if is_lambda: - self._process_exit_statement(node.body, (gast.Lambda,)) - else: - for stmt in node.body: - self.visit(stmt) - - self.builder.exit_section(node) - self._exit_lexical_scope(node) - - self.cfgs[node] = self.builder.build() - self.builder = self.builder_stack.pop() - - def visit_FunctionDef(self, node): - self._process_function_def(node, is_lambda=False) - - def visit_Lambda(self, node): - self._process_function_def(node, is_lambda=True) - - def visit_Return(self, node): - self._process_exit_statement(node, (gast.FunctionDef,)) - - def visit_Import(self, node): - self._process_basic_statement(node) - - def visit_ImportFrom(self, node): - self._process_basic_statement(node) - - def visit_Expr(self, node): - self._process_basic_statement(node) - - def visit_Assign(self, node): - self._process_basic_statement(node) - - def visit_AnnAssign(self, node): - self._process_basic_statement(node) - - def visit_AugAssign(self, node): - self._process_basic_statement(node) - - def visit_Pass(self, node): - self._process_basic_statement(node) - - def visit_Global(self, node): - self._process_basic_statement(node) - - def visit_Nonlocal(self, node): - self._process_basic_statement(node) - - def visit_Print(self, node): - self._process_basic_statement(node) - - def visit_Raise(self, node): - self._process_exit_statement( - node, (gast.FunctionDef,), may_exit_via_except=True) - self.builder.errors.add(node) - - def visit_Assert(self, node): - # Ignoring the effect of exceptions. - self._process_basic_statement(node) - - def visit_Delete(self, node): - self._process_basic_statement(node) - - def visit_If(self, node): - # No need to track ifs as lexical scopes, for now. - # Lexical scopes are generally tracked in order to be able to resolve the - # targets of jump statements like break/continue/etc. Since there is no - # statement that can interrupt a conditional, we don't need to track their - # lexical scope. That may change in the future. - self.builder.begin_statement(node) - - self.builder.enter_cond_section(node) - self._process_basic_statement(node.test) - - self.builder.new_cond_branch(node) - for stmt in node.body: - self.visit(stmt) - - self.builder.new_cond_branch(node) - for stmt in node.orelse: - self.visit(stmt) - - self.builder.exit_cond_section(node) - self.builder.end_statement(node) - - def visit_While(self, node): - self.builder.begin_statement(node) - self._enter_lexical_scope(node) - - self.builder.enter_section(node) - - self.generic_visit(node.test) - self.builder.enter_loop_section(node, node.test) - for stmt in node.body: - self.visit(stmt) - self.builder.exit_loop_section(node) - - # Note: although the orelse is technically part of the loop node, - # the statements inside it don't affect the loop itself. For example, a - # break in the loop's orelse will not affect the loop itself. - self._exit_lexical_scope(node) - - for stmt in node.orelse: - self.visit(stmt) - - self.builder.exit_section(node) - self.builder.end_statement(node) - - def visit_For(self, node): - self.builder.begin_statement(node) - self._enter_lexical_scope(node) - - self.builder.enter_section(node) - - # Note: Strictly speaking, this should be node.target + node.iter. - # However, the activity analysis accounts for this inconsistency, - # so dataflow analysis produces the correct values. - self.generic_visit(node.iter) - self.builder.enter_loop_section(node, node.iter) - # Also include the "extra loop test" annotation, to capture things like the - # control variable for return and break in for loops. - if anno.hasanno(node, anno.Basic.EXTRA_LOOP_TEST): - self._process_basic_statement( - anno.getanno(node, anno.Basic.EXTRA_LOOP_TEST)) - for stmt in node.body: - self.visit(stmt) - self.builder.exit_loop_section(node) - - # Note: although the orelse is technically part of the loop node, - # they don't count as loop bodies. For example, a break in the loop's - # orelse will affect the parent loop, not the current one. - self._exit_lexical_scope(node) - - for stmt in node.orelse: - self.visit(stmt) - - self.builder.exit_section(node) - self.builder.end_statement(node) - - def visit_Break(self, node): - self._process_exit_statement(node, ( - gast.While, - gast.For, - )) - - def visit_Continue(self, node): - self._process_continue_statement(node, ( - gast.While, - gast.For, - )) - - def visit_ExceptHandler(self, node): - self.builder.begin_statement(node) - self.builder.enter_except_section(node) - - if node.type is not None: - self.visit(node.type) - if node.name is not None: - self.visit(node.name) - - for stmt in node.body: - self.visit(stmt) - - self.builder.end_statement(node) - - def visit_Try(self, node): - self.builder.begin_statement(node) - self._enter_lexical_scope(node) - - # Note: the current simplification is that the try block fully executes - # regardless of whether an exception triggers or not. This is consistent - # with blocks free of try/except, which also don't account for the - # possibility of an exception being raised mid-block. - - for stmt in node.body: - self.visit(stmt) - # The orelse is an optional continuation of the body. - if node.orelse: - block_representative = node.orelse[0] - self.builder.enter_cond_section(block_representative) - self.builder.new_cond_branch(block_representative) - for stmt in node.orelse: - self.visit(stmt) - self.builder.new_cond_branch(block_representative) - self.builder.exit_cond_section(block_representative) - - self._exit_lexical_scope(node) - - if node.handlers: - # Using node would be inconsistent. Using the first handler node is also - # inconsistent, but less so. - block_representative = node.handlers[0] - self.builder.enter_cond_section(block_representative) - for block in node.handlers: - self.builder.new_cond_branch(block_representative) - self.visit(block) - self.builder.new_cond_branch(block_representative) - self.builder.exit_cond_section(block_representative) - - if node.finalbody: - self.builder.enter_finally_section(node) - for stmt in node.finalbody: - self.visit(stmt) - self.builder.exit_finally_section(node) - - self.builder.end_statement(node) - - def visit_With(self, node): - # TODO(mdan): Mark the context manager's exit call as exit guard. - for item in node.items: - self._process_basic_statement(item) - for stmt in node.body: - self.visit(stmt) - - -def build(node): - visitor = AstToCfg() - visitor.visit(node) - return visitor.cfgs diff --git a/src/braket/experimental/autoqasm/autograph/pyct/cfg_test.py b/src/braket/experimental/autoqasm/autograph/pyct/cfg_test.py deleted file mode 100644 index 5533e44c..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/cfg_test.py +++ /dev/null @@ -1,1602 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for cfg module.""" - -import gast - -from braket.experimental.autoqasm.autograph.pyct import cfg -from braket.experimental.autoqasm.autograph.pyct import parser -from tensorflow.python.platform import test - - -class CountingVisitor(cfg.GraphVisitor): - - def __init__(self, graph): - super(CountingVisitor, self).__init__(graph) - self.counts = {} - - def init_state(self, _): - return None - - def visit_node(self, node): - self.counts[node.ast_node] = self.counts.get(node.ast_node, 0) + 1 - return False # visit only once - - -class GraphVisitorTest(test.TestCase): - - def _build_cfg(self, fn): - node, _ = parser.parse_entity(fn, future_features=()) - cfgs = cfg.build(node) - return cfgs, node - - def test_basic_coverage_forward(self): - - def test_fn(a): - while a > 0: - a = 1 - break - return a # pylint:disable=unreachable - a = 2 - - graphs, node = self._build_cfg(test_fn) - graph, = graphs.values() - visitor = CountingVisitor(graph) - visitor.visit_forward() - - self.assertEqual(visitor.counts[node.args], 1) - self.assertEqual(visitor.counts[node.body[0].test], 1) - self.assertEqual(visitor.counts[node.body[0].body[0]], 1) - self.assertEqual(visitor.counts[node.body[0].body[1]], 1) - # The return node should be unreachable in forward direction. - self.assertNotIn(node.body[0].body[2], visitor.counts) - self.assertEqual(visitor.counts[node.body[1]], 1) - - def test_basic_coverage_reverse(self): - - def test_fn(a): - while a > 0: - a = 1 - break - return a # pylint:disable=unreachable - a = 2 - - graphs, node = self._build_cfg(test_fn) - graph, = graphs.values() - visitor = CountingVisitor(graph) - visitor.visit_reverse() - - self.assertEqual(visitor.counts[node.args], 1) - self.assertEqual(visitor.counts[node.body[0].test], 1) - self.assertEqual(visitor.counts[node.body[0].body[0]], 1) - self.assertEqual(visitor.counts[node.body[0].body[1]], 1) - self.assertEqual(visitor.counts[node.body[0].body[2]], 1) - self.assertEqual(visitor.counts[node.body[1]], 1) - - -class AstToCfgTest(test.TestCase): - - def _build_cfg(self, fn): - node, _ = parser.parse_entity(fn, future_features=()) - cfgs = cfg.build(node) - return cfgs - - def _repr_set(self, node_set): - return frozenset(repr(n) for n in node_set) - - def _as_set(self, elements): - if elements is None: - return frozenset() - elif isinstance(elements, str): - return frozenset((elements,)) - else: - return frozenset(elements) - - def assertGraphMatches(self, graph, edges): - """Tests whether the CFG contains the specified edges.""" - for prev, node_repr, next_ in edges: - matched = False - for cfg_node in graph.index.values(): - if repr(cfg_node) == node_repr: - if (self._as_set(prev) == frozenset(map(repr, cfg_node.prev)) and - self._as_set(next_) == frozenset(map(repr, cfg_node.next))): - matched = True - break - if not matched: - self.fail( - 'match failed for node "%s" in graph:\n%s' % (node_repr, graph)) - - def assertGraphEnds(self, graph, entry_repr, exit_reprs): - """Tests whether the CFG has the specified entry and exits.""" - self.assertEqual(repr(graph.entry), entry_repr) - self.assertSetEqual(frozenset(map(repr, graph.exit)), frozenset(exit_reprs)) - - def assertStatementEdges(self, graph, edges): - """Tests whether the CFG contains the specified statement edges.""" - for prev_node_reprs, node_repr, next_node_reprs in edges: - matched = False - partial_matches = [] - self.assertSetEqual( - frozenset(graph.stmt_next.keys()), frozenset(graph.stmt_prev.keys())) - for stmt_ast_node in graph.stmt_next: - ast_repr = '%s:%s' % (stmt_ast_node.__class__.__name__, - stmt_ast_node.lineno) - if ast_repr == node_repr: - actual_next = frozenset(map(repr, graph.stmt_next[stmt_ast_node])) - actual_prev = frozenset(map(repr, graph.stmt_prev[stmt_ast_node])) - partial_matches.append((actual_prev, node_repr, actual_next)) - if (self._as_set(prev_node_reprs) == actual_prev and - self._as_set(next_node_reprs) == actual_next): - matched = True - break - if not matched: - self.fail('edges mismatch for %s: %s' % (node_repr, partial_matches)) - - def test_straightline(self): - - def test_fn(a): - a += 1 - a = 2 - a = 3 - return - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (None, 'a', 'a += 1'), - ('a += 1', 'a = 2', 'a = 3'), - ('a = 2', 'a = 3', 'return'), - ('a = 3', 'return', None), - ), - ) - self.assertGraphEnds(graph, 'a', ('return',)) - - def test_straightline_no_return(self): - - def test_fn(a, b): - a = b + 1 - a += max(a) - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (None, 'a, b', 'a = (b + 1)'), - ('a = (b + 1)', 'a += max(a)', None), - ), - ) - self.assertGraphEnds(graph, 'a, b', ('a += max(a)',)) - - def test_unreachable_code(self): - - def test_fn(a): - return - a += 1 # pylint:disable=unreachable - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (None, 'a', 'return'), - ('a', 'return', None), - (None, 'a += 1', None), - ), - ) - self.assertGraphEnds(graph, 'a', ('return', 'a += 1')) - - def test_if_straightline(self): - - def test_fn(a): - if a > 0: - a = 1 - else: - a += -1 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (None, 'a', '(a > 0)'), - ('(a > 0)', 'a = 1', None), - ('(a > 0)', 'a += (- 1)', None), - ), - ) - self.assertStatementEdges( - graph, - (('a', 'If:2', None),), - ) - self.assertGraphEnds(graph, 'a', ('a = 1', 'a += (- 1)')) - - def test_branch_nested(self): - - def test_fn(a): - if a > 0: - if a > 1: - a = 1 - else: - a = 2 - else: - if a > 2: - a = 3 - else: - a = 4 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (None, 'a', '(a > 0)'), - ('a', '(a > 0)', ('(a > 1)', '(a > 2)')), - ('(a > 0)', '(a > 1)', ('a = 1', 'a = 2')), - ('(a > 1)', 'a = 1', None), - ('(a > 1)', 'a = 2', None), - ('(a > 0)', '(a > 2)', ('a = 3', 'a = 4')), - ('(a > 2)', 'a = 3', None), - ('(a > 2)', 'a = 4', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'If:2', None), - ('(a > 0)', 'If:3', None), - ('(a > 0)', 'If:8', None), - ), - ) - self.assertGraphEnds(graph, 'a', ('a = 1', 'a = 2', 'a = 3', 'a = 4')) - - def test_branch_straightline_unbalanced(self): - - def test_fn(a): - if a > 0: - a = 1 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (None, 'a', '(a > 0)'), - ('a', '(a > 0)', 'a = 1'), - ('(a > 0)', 'a = 1', None), - ), - ) - self.assertStatementEdges( - graph, - (('a', 'If:2', None),), - ) - self.assertGraphEnds(graph, 'a', ('(a > 0)', 'a = 1')) - - def test_branch_return(self): - - def test_fn(a): - if a > 0: - return - else: - a = 1 - a = 2 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', '(a > 0)', ('return', 'a = 1')), - ('(a > 0)', 'a = 1', 'a = 2'), - ('(a > 0)', 'return', None), - ('a = 1', 'a = 2', None), - ), - ) - self.assertStatementEdges( - graph, - (('a', 'If:2', 'a = 2'),), - ) - self.assertGraphEnds(graph, 'a', ('a = 2', 'return')) - - def test_branch_raise(self): - - def test_fn(a): - if a > 0: - raise a - else: - a = 1 - a = 2 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', '(a > 0)', ('raise a', 'a = 1')), - ('(a > 0)', 'a = 1', 'a = 2'), - ('(a > 0)', 'raise a', None), - ('a = 1', 'a = 2', None), - ), - ) - self.assertStatementEdges( - graph, - (('a', 'If:2', 'a = 2'),), - ) - self.assertGraphEnds(graph, 'a', ('a = 2', 'raise a')) - - def test_branch_return_minimal(self): - - def test_fn(a): - if a > 0: - return - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', '(a > 0)', 'return'), - ('(a > 0)', 'return', None), - ), - ) - self.assertStatementEdges( - graph, - (('a', 'If:2', None),), - ) - self.assertGraphEnds(graph, 'a', ('(a > 0)', 'return')) - - def test_while_straightline(self): - - def test_fn(a): - while a > 0: - a = 1 - a = 2 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (('a', 'a = 1'), '(a > 0)', ('a = 1', 'a = 2')), - ('(a > 0)', 'a = 1', '(a > 0)'), - ('(a > 0)', 'a = 2', None), - ), - ) - self.assertStatementEdges( - graph, - (('a', 'While:2', 'a = 2'),), - ) - self.assertGraphEnds(graph, 'a', ('a = 2',)) - - def test_while_else_straightline(self): - - def test_fn(a): - while a > 0: - a = 1 - else: # pylint:disable=useless-else-on-loop - a = 2 - a = 3 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (('a', 'a = 1'), '(a > 0)', ('a = 1', 'a = 2')), - ('(a > 0)', 'a = 1', '(a > 0)'), - ('(a > 0)', 'a = 2', 'a = 3'), - ('a = 2', 'a = 3', None), - ), - ) - self.assertStatementEdges( - graph, - (('a', 'While:2', 'a = 3'),), - ) - self.assertGraphEnds(graph, 'a', ('a = 3',)) - - def test_while_else_continue(self): - - def test_fn(a): - while a > 0: - if a > 1: - continue - else: - a = 0 - a = 1 - else: # pylint:disable=useless-else-on-loop - a = 2 - a = 3 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (('a', 'continue', 'a = 1'), '(a > 0)', ('(a > 1)', 'a = 2')), - ('(a > 0)', '(a > 1)', ('continue', 'a = 0')), - ('(a > 1)', 'continue', '(a > 0)'), - ('a = 0', 'a = 1', '(a > 0)'), - ('(a > 0)', 'a = 2', 'a = 3'), - ('a = 2', 'a = 3', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'While:2', 'a = 3'), - ('(a > 0)', 'If:3', ('a = 1', '(a > 0)')), - ), - ) - self.assertGraphEnds(graph, 'a', ('a = 3',)) - - def test_while_else_break(self): - - def test_fn(a): - while a > 0: - if a > 1: - break - a = 1 - else: - a = 2 - a = 3 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (('a', 'a = 1'), '(a > 0)', ('(a > 1)', 'a = 2')), - ('(a > 0)', '(a > 1)', ('break', 'a = 1')), - ('(a > 1)', 'break', 'a = 3'), - ('(a > 1)', 'a = 1', '(a > 0)'), - ('(a > 0)', 'a = 2', 'a = 3'), - (('break', 'a = 2'), 'a = 3', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'While:2', 'a = 3'), - ('(a > 0)', 'If:3', ('a = 1', 'a = 3')), - ), - ) - self.assertGraphEnds(graph, 'a', ('a = 3',)) - - def test_while_else_return(self): - - def test_fn(a): - while a > 0: - if a > 1: - return - a = 1 - else: # pylint:disable=useless-else-on-loop - a = 2 - a = 3 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (('a', 'a = 1'), '(a > 0)', ('(a > 1)', 'a = 2')), - ('(a > 0)', '(a > 1)', ('return', 'a = 1')), - ('(a > 1)', 'return', None), - ('(a > 1)', 'a = 1', '(a > 0)'), - ('(a > 0)', 'a = 2', 'a = 3'), - ('a = 2', 'a = 3', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'While:2', 'a = 3'), - ('(a > 0)', 'If:3', 'a = 1'), - ), - ) - self.assertGraphEnds(graph, 'a', ('a = 3', 'return')) - - def test_while_nested_straightline(self): - - def test_fn(a): - while a > 0: - while a > 1: - a = 1 - a = 2 - a = 3 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (('a', 'a = 2'), '(a > 0)', ('(a > 1)', 'a = 3')), - (('(a > 0)', 'a = 1'), '(a > 1)', ('a = 1', 'a = 2')), - ('(a > 1)', 'a = 1', '(a > 1)'), - ('(a > 1)', 'a = 2', '(a > 0)'), - ('(a > 0)', 'a = 3', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'While:2', 'a = 3'), - ('(a > 0)', 'While:3', 'a = 2'), - ), - ) - self.assertGraphEnds(graph, 'a', ('a = 3',)) - - def test_while_nested_continue(self): - - def test_fn(a): - while a > 0: - while a > 1: - if a > 3: - continue - a = 1 - a = 2 - a = 3 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (('a', 'a = 2'), '(a > 0)', ('(a > 1)', 'a = 3')), - (('(a > 0)', 'continue', 'a = 1'), '(a > 1)', ('(a > 3)', 'a = 2')), - ('(a > 1)', '(a > 3)', ('continue', 'a = 1')), - ('(a > 3)', 'continue', '(a > 1)'), - ('(a > 3)', 'a = 1', '(a > 1)'), - ('(a > 1)', 'a = 2', '(a > 0)'), - ('(a > 0)', 'a = 3', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'While:2', 'a = 3'), - ('(a > 0)', 'While:3', 'a = 2'), - ('(a > 1)', 'If:4', ('a = 1', '(a > 1)')), - ), - ) - self.assertGraphEnds(graph, 'a', ('a = 3',)) - - def test_while_nested_break(self): - - def test_fn(a): - while a > 0: - while a > 1: - if a > 2: - break - a = 1 - a = 2 - a = 3 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches(graph, ( - (('a', 'a = 2'), '(a > 0)', ('(a > 1)', 'a = 3')), - (('(a > 0)', 'a = 1'), '(a > 1)', ('(a > 2)', 'a = 2')), - ('(a > 1)', '(a > 2)', ('break', 'a = 1')), - ('(a > 2)', 'break', 'a = 2'), - ('(a > 2)', 'a = 1', '(a > 1)'), - (('(a > 1)', 'break'), 'a = 2', '(a > 0)'), - ('(a > 0)', 'a = 3', None), - )) - self.assertStatementEdges( - graph, - ( - ('a', 'While:2', 'a = 3'), - ('(a > 0)', 'While:3', 'a = 2'), - ('(a > 1)', 'If:4', ('a = 1', 'a = 2')), - ), - ) - self.assertGraphEnds(graph, 'a', ('a = 3',)) - - def test_for_straightline(self): - - def test_fn(a): - for a in range(0, a): - a = 1 - a = 2 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (('a', 'a = 1'), 'range(0, a)', ('a = 1', 'a = 2')), - ('range(0, a)', 'a = 1', 'range(0, a)'), - ('range(0, a)', 'a = 2', None), - ), - ) - self.assertStatementEdges( - graph, - (('a', 'For:2', 'a = 2'),), - ) - self.assertGraphEnds(graph, 'a', ('a = 2',)) - - def test_for_else_straightline(self): - - def test_fn(a): - for a in range(0, a): - a = 1 - else: # pylint:disable=useless-else-on-loop - a = 2 - a = 3 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (('a', 'a = 1'), 'range(0, a)', ('a = 1', 'a = 2')), - ('range(0, a)', 'a = 1', 'range(0, a)'), - ('range(0, a)', 'a = 2', 'a = 3'), - ('a = 2', 'a = 3', None), - ), - ) - self.assertStatementEdges( - graph, - (('a', 'For:2', 'a = 3'),), - ) - self.assertGraphEnds(graph, 'a', ('a = 3',)) - - def test_for_else_continue(self): - - def test_fn(a): - for a in range(0, a): - if a > 1: - continue - else: - a = 0 - a = 1 - else: # pylint:disable=useless-else-on-loop - a = 2 - a = 3 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (('a', 'continue', 'a = 1'), 'range(0, a)', ('(a > 1)', 'a = 2')), - ('range(0, a)', '(a > 1)', ('continue', 'a = 0')), - ('(a > 1)', 'continue', 'range(0, a)'), - ('(a > 1)', 'a = 0', 'a = 1'), - ('a = 0', 'a = 1', 'range(0, a)'), - ('range(0, a)', 'a = 2', 'a = 3'), - ('a = 2', 'a = 3', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'For:2', 'a = 3'), - ('range(0, a)', 'If:3', ('a = 1', 'range(0, a)')), - ), - ) - self.assertGraphEnds(graph, 'a', ('a = 3',)) - - def test_for_else_break(self): - - def test_fn(a): - for a in range(0, a): - if a > 1: - break - a = 1 - else: - a = 2 - a = 3 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (('a', 'a = 1'), 'range(0, a)', ('(a > 1)', 'a = 2')), - ('range(0, a)', '(a > 1)', ('break', 'a = 1')), - ('(a > 1)', 'break', 'a = 3'), - ('(a > 1)', 'a = 1', 'range(0, a)'), - ('range(0, a)', 'a = 2', 'a = 3'), - (('break', 'a = 2'), 'a = 3', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'For:2', 'a = 3'), - ('range(0, a)', 'If:3', ('a = 1', 'a = 3')), - ), - ) - self.assertGraphEnds(graph, 'a', ('a = 3',)) - - def test_for_else_return(self): - - def test_fn(a): - for a in range(0, a): - if a > 1: - return - a = 1 - else: # pylint:disable=useless-else-on-loop - a = 2 - a = 3 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (('a', 'a = 1'), 'range(0, a)', ('(a > 1)', 'a = 2')), - ('range(0, a)', '(a > 1)', ('return', 'a = 1')), - ('(a > 1)', 'return', None), - ('(a > 1)', 'a = 1', 'range(0, a)'), - ('range(0, a)', 'a = 2', 'a = 3'), - ('a = 2', 'a = 3', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'For:2', 'a = 3'), - ('range(0, a)', 'If:3', 'a = 1'), - ), - ) - self.assertGraphEnds(graph, 'a', ('a = 3', 'return')) - - def test_for_nested_straightline(self): - - def test_fn(a): - for a in range(0, a): - for b in range(1, a): - b += 1 - a = 2 - a = 3 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (('a', 'a = 2'), 'range(0, a)', ('range(1, a)', 'a = 3')), - (('range(0, a)', 'b += 1'), 'range(1, a)', ('b += 1', 'a = 2')), - ('range(1, a)', 'b += 1', 'range(1, a)'), - ('range(1, a)', 'a = 2', 'range(0, a)'), - ('range(0, a)', 'a = 3', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'For:2', 'a = 3'), - ('range(0, a)', 'For:3', 'a = 2'), - ), - ) - self.assertGraphEnds(graph, 'a', ('a = 3',)) - - def test_for_nested_continue(self): - - def test_fn(a): - for a in range(0, a): - for b in range(1, a): - if a > 3: - continue - b += 1 - a = 2 - a = 3 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (('a', 'a = 2'), 'range(0, a)', ('range(1, a)', 'a = 3')), - (('range(0, a)', 'continue', 'b += 1'), 'range(1, a)', - ('(a > 3)', 'a = 2')), - ('range(1, a)', '(a > 3)', ('continue', 'b += 1')), - ('(a > 3)', 'continue', 'range(1, a)'), - ('(a > 3)', 'b += 1', 'range(1, a)'), - ('range(1, a)', 'a = 2', 'range(0, a)'), - ('range(0, a)', 'a = 3', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'For:2', 'a = 3'), - ('range(0, a)', 'For:3', 'a = 2'), - ('range(1, a)', 'If:4', ('b += 1', 'range(1, a)')), - ), - ) - self.assertGraphEnds(graph, 'a', ('a = 3',)) - - def test_for_nested_break(self): - - def test_fn(a): - for a in range(0, a): - for b in range(1, a): - if a > 2: - break - b += 1 - a = 2 - a = 3 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (('a', 'a = 2'), 'range(0, a)', ('range(1, a)', 'a = 3')), - (('range(0, a)', 'b += 1'), 'range(1, a)', ('(a > 2)', 'a = 2')), - ('range(1, a)', '(a > 2)', ('break', 'b += 1')), - ('(a > 2)', 'break', 'a = 2'), - ('(a > 2)', 'b += 1', 'range(1, a)'), - (('range(1, a)', 'break'), 'a = 2', 'range(0, a)'), - ('range(0, a)', 'a = 3', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'For:2', 'a = 3'), - ('range(0, a)', 'For:3', 'a = 2'), - ('range(1, a)', 'If:4', ('b += 1', 'a = 2')), - ), - ) - self.assertGraphEnds(graph, 'a', ('a = 3',)) - - def test_complex(self): - - def test_fn(a): - b = 0 - while a > 0: - for b in range(0, a): - if a > 2: - break - if a > 3: - if a > 4: - continue - else: - max(a) - break - b += 1 - else: # for b in range(0, a): - return a - a = 2 - for a in range(1, a): - return b - a = 3 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (('b = 0', 'a = 2'), '(a > 0)', ('range(0, a)', 'range(1, a)')), - ( - ('(a > 0)', 'continue', 'b += 1'), - 'range(0, a)', - ('(a > 2)', 'return a'), - ), - ('range(0, a)', '(a > 2)', ('(a > 3)', 'break')), - ('(a > 2)', 'break', 'a = 2'), - ('(a > 2)', '(a > 3)', ('(a > 4)', 'b += 1')), - ('(a > 3)', '(a > 4)', ('continue', 'max(a)')), - ('(a > 4)', 'max(a)', 'break'), - ('max(a)', 'break', 'a = 2'), - ('(a > 4)', 'continue', 'range(0, a)'), - ('(a > 3)', 'b += 1', 'range(0, a)'), - ('range(0, a)', 'return a', None), - ('break', 'a = 2', '(a > 0)'), - ('(a > 0)', 'range(1, a)', ('return b', 'a = 3')), - ('range(1, a)', 'return b', None), - ('range(1, a)', 'a = 3', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('b = 0', 'While:3', 'range(1, a)'), - ('(a > 0)', 'For:4', 'a = 2'), - ('range(0, a)', 'If:5', ('(a > 3)', 'a = 2')), - ('(a > 2)', 'If:7', ('b += 1', 'a = 2', 'range(0, a)')), - ('(a > 3)', 'If:8', ('a = 2', 'range(0, a)')), - ('(a > 0)', 'For:17', 'a = 3'), - ), - ) - self.assertGraphEnds(graph, 'a', ('a = 3', 'return a', 'return b')) - - def test_finally_straightline(self): - - def test_fn(a): - try: - a += 1 - finally: - a = 2 - a = 3 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', 'a += 1', 'a = 2'), - ('a += 1', 'a = 2', 'a = 3'), - ('a = 2', 'a = 3', None), - ), - ) - self.assertGraphEnds(graph, 'a', ('a = 3',)) - - def test_return_finally(self): - - def test_fn(a): - try: - return a - finally: - a = 1 - a = 2 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', 'return a', 'a = 1'), - ('return a', 'a = 1', None), - (None, 'a = 2', None), - ), - ) - # Note, `a = 1` executes after `return a`. - self.assertGraphEnds(graph, 'a', ('a = 2', 'a = 1')) - - def test_break_finally(self): - - def test_fn(a): - while a > 0: - try: - break - finally: - a = 1 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', '(a > 0)', 'break'), - ('(a > 0)', 'break', 'a = 1'), - ('break', 'a = 1', None), - ), - ) - self.assertGraphEnds(graph, 'a', ('(a > 0)', 'a = 1')) - - def test_continue_finally(self): - - def test_fn(a): - while a > 0: - try: - continue - finally: - a = 1 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - (('a', 'a = 1'), '(a > 0)', 'continue'), - ('(a > 0)', 'continue', 'a = 1'), - ('continue', 'a = 1', '(a > 0)'), - ), - ) - self.assertGraphEnds(graph, 'a', ('(a > 0)',)) - - def test_with_straightline(self): - - def test_fn(a): - with max(a) as b: - a = 0 - return b - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', 'max(a)', 'a = 0'), - ('max(a)', 'a = 0', 'return b'), - ('a = 0', 'return b', None), - ), - ) - self.assertGraphEnds(graph, 'a', ('return b',)) - - def test_lambda_basic(self): - - def test_fn(a): - a = lambda b: a + b - return a - - graphs = self._build_cfg(test_fn) - for k, v in graphs.items(): - if isinstance(k, gast.Lambda): - lam_graph = v - else: - fn_graph = v - - self.assertGraphMatches( - fn_graph, - ( - ('a', '(lambda b: (a + b))', 'a = (lambda b: (a + b))'), - ('(lambda b: (a + b))', 'a = (lambda b: (a + b))', 'return a'), - ('a = (lambda b: (a + b))', 'return a', None), - ), - ) - self.assertGraphEnds(fn_graph, 'a', ('return a',)) - self.assertGraphMatches( - lam_graph, - ( - ('b', '(a + b)', None), - ), - ) - self.assertGraphEnds(lam_graph, 'b', ('(a + b)',)) - - def test_lambda_in_return(self): - - def test_fn(a): - return lambda b: a + b - - graphs = self._build_cfg(test_fn) - for k, v in graphs.items(): - if isinstance(k, gast.Lambda): - lam_graph = v - else: - fn_graph = v - - self.assertGraphMatches( - fn_graph, - ( - ('a', '(lambda b: (a + b))', 'return (lambda b: (a + b))'), - ('(lambda b: (a + b))', 'return (lambda b: (a + b))', None), - ), - ) - self.assertGraphEnds(fn_graph, 'a', ('return (lambda b: (a + b))',)) - self.assertGraphMatches( - lam_graph, - ( - ('b', '(a + b)', None), - ), - ) - self.assertGraphEnds(lam_graph, 'b', ('(a + b)',)) - - def test_lambda_in_while_loop_test(self): - - def test_fn(a): - while (lambda b: a + b)(a): - pass - - graphs = self._build_cfg(test_fn) - for k, v in graphs.items(): - if isinstance(k, gast.Lambda): - lam_graph = v - else: - fn_graph = v - - self.assertGraphMatches( - fn_graph, - ( - ('a', '(lambda b: (a + b))', '(lambda b: (a + b))(a)'), - (('(lambda b: (a + b))', 'pass'), '(lambda b: (a + b))(a)', 'pass'), - ('(lambda b: (a + b))(a)', 'pass', '(lambda b: (a + b))(a)'), - ), - ) - self.assertGraphEnds(fn_graph, 'a', ('(lambda b: (a + b))(a)',)) - self.assertGraphMatches( - lam_graph, - ( - ('b', '(a + b)', None), - ), - ) - self.assertGraphEnds(lam_graph, 'b', ('(a + b)',)) - - def test_lambda_in_for_loop_test(self): - - def test_fn(a): - for _ in (lambda b: a + b)(a): - pass - - graphs = self._build_cfg(test_fn) - for k, v in graphs.items(): - if isinstance(k, gast.Lambda): - lam_graph = v - else: - fn_graph = v - - self.assertGraphMatches( - fn_graph, - ( - ('a', '(lambda b: (a + b))', '(lambda b: (a + b))(a)'), - (('(lambda b: (a + b))', 'pass'), '(lambda b: (a + b))(a)', 'pass'), - ('(lambda b: (a + b))(a)', 'pass', '(lambda b: (a + b))(a)'), - ), - ) - self.assertGraphEnds(fn_graph, 'a', ('(lambda b: (a + b))(a)',)) - self.assertGraphMatches( - lam_graph, - ( - ('b', '(a + b)', None), - ), - ) - self.assertGraphEnds(lam_graph, 'b', ('(a + b)',)) - - def test_pass(self): - - def test_fn(a): # pylint:disable=unused-argument - pass - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', 'pass', None), - ), - ) - self.assertGraphEnds(graph, 'a', ('pass',)) - - def test_try_finally(self): - - def test_fn(a): - try: - a = 1 - finally: - a = 2 - return a - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', 'a = 1', 'a = 2'), - ('a = 1', 'a = 2', 'return a'), - ('a = 2', 'return a', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'Try:2', 'return a'), - ), - ) - self.assertGraphEnds(graph, 'a', ('return a',)) - - def test_try_except_single_bare(self): - - def test_fn(a): - try: - a = 1 - a = 2 - except: # pylint:disable=bare-except - a = 3 - return a - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', 'a = 1', 'a = 2'), - ('a = 2', 'a = 3', 'return a'), - (('a = 2', 'a = 3'), 'return a', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'Try:2', 'return a'), - ('a = 2', 'ExceptHandler:5', 'return a'), - ), - ) - self.assertGraphEnds(graph, 'a', ('return a',)) - - def test_try_except_single(self): - - def test_fn(a): - try: - a = 1 - a = 2 - except Exception1: # pylint:disable=undefined-variable - a = 3 - return a - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', 'a = 1', 'a = 2'), - ('a = 2', 'a = 3', 'return a'), - (('a = 2', 'a = 3'), 'return a', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'Try:2', 'return a'), - ('a = 2', 'ExceptHandler:5', 'return a'), - ), - ) - self.assertGraphEnds(graph, 'a', ('return a',)) - - def test_try_except_single_aliased(self): - - def test_fn(a): - try: - a = 1 - except Exception1 as e: # pylint:disable=undefined-variable,unused-variable - a = 2 - return a - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', 'a = 1', ('a = 2', 'return a')), - (('a = 1', 'a = 2'), 'return a', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'Try:2', 'return a'), - ('a = 1', 'ExceptHandler:4', 'return a'), - ), - ) - self.assertGraphEnds(graph, 'a', ('return a',)) - - def test_try_except_single_tuple_aliased(self): - - def test_fn(a): - try: - a = 1 - except (Exception1, Exception2) as e: # pylint:disable=undefined-variable,unused-variable - a = 2 - return a - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', 'a = 1', ('a = 2', 'return a')), - (('a = 1', 'a = 2'), 'return a', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'Try:2', 'return a'), - ('a = 1', 'ExceptHandler:4', 'return a'), - ), - ) - self.assertGraphEnds(graph, 'a', ('return a',)) - - def test_try_except_multiple(self): - - def test_fn(a): - try: - a = 1 - except Exception1: # pylint:disable=undefined-variable - a = 2 - except Exception2: # pylint:disable=undefined-variable - a = 3 - return a - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', 'a = 1', ('a = 2', 'a = 3', 'return a')), - (('a = 1', 'a = 2', 'a = 3'), 'return a', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'Try:2', 'return a'), - ('a = 1', 'ExceptHandler:4', 'return a'), - ('a = 1', 'ExceptHandler:6', 'return a'), - ), - ) - self.assertGraphEnds(graph, 'a', ('return a',)) - - def test_try_except_finally(self): - - def test_fn(a): - try: - a = 1 - except Exception1: # pylint:disable=undefined-variable - a = 2 - except Exception2: # pylint:disable=undefined-variable - a = 3 - finally: - a = 4 - return a - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', 'a = 1', ('a = 2', 'a = 3', 'a = 4')), - (('a = 1', 'a = 2', 'a = 3'), 'a = 4', 'return a'), - ('a = 4', 'return a', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'Try:2', 'return a'), - ('a = 1', 'ExceptHandler:4', 'a = 4'), - ('a = 1', 'ExceptHandler:6', 'a = 4'), - ), - ) - self.assertGraphEnds(graph, 'a', ('return a',)) - - def test_try_in_if(self): - - def test_fn(a): - try: - if a > 0: - a = 1 - else: - a = 2 - except Exception1: # pylint:disable=undefined-variable - a = 3 - a = 4 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', '(a > 0)', ('a = 1', 'a = 2')), - ('(a > 0)', 'a = 1', ('a = 3', 'a = 4')), - ('(a > 0)', 'a = 2', ('a = 3', 'a = 4')), - (('a = 1', 'a = 2'), 'a = 3', 'a = 4'), - (('a = 1', 'a = 2', 'a = 3'), 'a = 4', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a', 'Try:2', 'a = 4'), - ('a', 'If:3', ('a = 3', 'a = 4')), - (('a = 1', 'a = 2'), 'ExceptHandler:7', 'a = 4'), - ), - ) - self.assertGraphEnds(graph, 'a', ('a = 4',)) - - def test_try_in_if_all_branches_exit(self): - - def test_fn(a, b): - try: - if a > 0: - raise b - else: - return 0 - except b: - return 1 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a, b', '(a > 0)', ('raise b', 'return 0')), - ('(a > 0)', 'raise b', 'return 1'), - ('(a > 0)', 'return 0', None), - ('raise b', 'return 1', None), - ), - ) - self.assertStatementEdges( - graph, - ( - ('a, b', 'Try:2', None), - ('a, b', 'If:3', 'return 1'), - ('raise b', 'ExceptHandler:7', None), - ), - ) - self.assertGraphEnds(graph, 'a, b', ('return 0', 'return 1', 'raise b')) - - def test_raise_exits(self): - - def test_fn(a, b): - raise b - return a # pylint:disable=unreachable - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a, b', 'raise b', None), - (None, 'return a', None), - ), - ) - self.assertGraphEnds(graph, 'a, b', ('raise b', 'return a')) - - def test_raise_triggers_enclosing_finally(self): - - def test_fn(a): - try: - try: - raise a - return 1 # pylint:disable=unreachable - finally: - b = 1 - return 2 - finally: - b = 2 - return b - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', 'raise a', 'b = 1'), - (('raise a', 'return 1'), 'b = 1', 'b = 2'), - (None, 'return 1', 'b = 1'), - (None, 'return 2', 'b = 2'), - (('return 2', 'b = 1'), 'b = 2', None), - (None, 'return b', None), - ), - ) - self.assertGraphEnds( - graph, 'a', ('return b', 'b = 2')) - - def test_raise_adds_finally_sortcuts(self): - - def test_fn(a): - try: - try: - if a > 0: - raise a - c = 1 - finally: - b = 1 - c = 2 - finally: - b = 2 - return b, c - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', '(a > 0)', ('raise a', 'c = 1')), - ('(a > 0)', 'raise a', 'b = 1'), - ('(a > 0)', 'c = 1', 'b = 1'), - (('raise a', 'c = 1'), 'b = 1', ('c = 2', 'b = 2')), - ('b = 1', 'c = 2', 'b = 2'), - (('b = 1', 'c = 2'), 'b = 2', 'return (b, c)'), - ('b = 2', 'return (b, c)', None), - ), - ) - self.assertGraphEnds( - graph, 'a', ('return (b, c)', 'b = 2')) - - def test_raise_exits_via_except(self): - - def test_fn(a, b): - try: - raise b - except a: - c = 1 - except b: - c = 2 - finally: - c += 3 - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a, b', 'raise b', ('c = 1', 'c = 2', 'c += 3')), - ('raise b', 'c = 1', 'c += 3'), - ('raise b', 'c = 2', 'c += 3'), - (('raise b', 'c = 1', 'c = 2'), 'c += 3', None), - ), - ) - self.assertGraphEnds(graph, 'a, b', ('c += 3',)) - - def test_list_comprehension(self): - - def test_fn(a): - c = [b for b in a] - return c - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a', 'c = [b for b in a]', 'return c'), - ('c = [b for b in a]', 'return c', None), - ), - ) - self.assertGraphEnds(graph, 'a', ('return c',)) - - def test_class_definition_empty(self): - - def test_fn(a, b): - class C(a(b)): - pass - return C - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a, b', 'class C', 'return C'), - ('class C', 'return C', None), - ), - ) - self.assertGraphEnds(graph, 'a, b', ('return C',)) - - def test_class_definition_with_members(self): - - def test_fn(a, b): - class C(a(b)): - d = 1 - return C - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('a, b', 'class C', 'return C'), - ('class C', 'return C', None), - ), - ) - self.assertGraphEnds(graph, 'a, b', ('return C',)) - - def test_import(self): - - def test_fn(): - from a import b # pylint:disable=g-import-not-at-top - return b - - graph, = self._build_cfg(test_fn).values() - - self.assertGraphMatches( - graph, - ( - ('', 'from a import b', 'return b'), - ('from a import b', 'return b', None), - ), - ) - self.assertGraphEnds(graph, '', ('return b',)) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/common_transformers/__init__.py b/src/braket/experimental/autoqasm/autograph/pyct/common_transformers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/braket/experimental/autoqasm/autograph/pyct/common_transformers/anf.py b/src/braket/experimental/autoqasm/autograph/pyct/common_transformers/anf.py deleted file mode 100644 index 99ef0135..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/common_transformers/anf.py +++ /dev/null @@ -1,620 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Conversion to A-normal form. - -The general idea of A-normal form is that every intermediate value is -explicitly named with a variable. For more, see -https://en.wikipedia.org/wiki/A-normal_form. - -The specific converters used here are based on Python AST semantics as -documented at https://greentreesnakes.readthedocs.io/en/latest/. -""" - -import collections - -import gast - -from braket.experimental.autoqasm.autograph.pyct import gast_util -from braket.experimental.autoqasm.autograph.pyct import templates -from braket.experimental.autoqasm.autograph.pyct import transformer - - -# TODO(mdan): Replace with naming.Namer. -class DummyGensym: - """A dumb gensym that suffixes a stem by sequential numbers from 1000.""" - - def __init__(self): - # A proper implementation needs to account for: - # * ctx.info.namespace - # * all the symbols defined in the AST - # * the symbols generated so far - self._idx = 0 - - def new_name(self, stem='tmp'): - self._idx += 1 - return stem + '_' + str(1000 + self._idx) - - -REPLACE = lambda _1, _2, _3: True -LEAVE = lambda _1, _2, _3: False -ANY = object() - - -class ASTEdgePattern(collections.namedtuple( - 'ASTEdgePattern', ['parent', 'field', 'child'])): - """A pattern defining a type of AST edge. - - This consists of three components: - - The type of the parent node, checked with isinstance, - - The name of the field, checked with string equality, and - - The type of the child node, also checked with isinstance. - If all three match, the whole pattern is considered to match. - - In all three slots, the special value `anf.ANY` is treated as "match - anything". The internal nodes are produced from the `gast` library rather - than the standard `ast` module, which may affect `isinstance` checks. - """ - __slots__ = () - - def matches(self, parent, field, child): - """Computes whether this pattern matches the given edge.""" - if self.parent is ANY or isinstance(parent, self.parent): - pass # OK - else: - return False - if self.field is ANY or field == self.field: - pass # OK - else: - return False - return self.child is ANY or isinstance(child, self.child) - - -class AnfTransformer(transformer.Base): - """Performs the conversion to A-normal form (ANF).""" - - # The algorithm is a postorder recursive tree walk. Any given node A may, in - # general, require creation of a series B of Assign statements, which compute - # and explicitly name the intermediate values needed to compute the value of - # A. If A was already a statement, it can be replaced with the sequence B + - # [A]. If A was an expression, B needs to be propagated up the tree until a - # statement is encountered. Since the `ast.NodeTransformer` framework makes - # no provision for subtraversals returning side information, this class - # accumulates the sequence B in an instance variable. - - # The only other subtlety is that some Python statements (like `if`) have both - # expression fields (`test`) and statement list fields (`body` and `orelse`). - # Any additional assignments needed to name all the intermediate values in the - # `test` can be prepended to the `if` node, but assignments produced by - # processing the `body` and the `orelse` need to be kept together with them, - # and not accidentally lifted out of the `if`. - - def __init__(self, ctx, config): - """Creates an ANF transformer. - - Args: - ctx: transformer.Context - config: Configuration - """ - super(AnfTransformer, self).__init__(ctx) - if config is None: - # These could be pulled out, but are generally considered to already be in - # A-normal form. Thus they are left in by default, but could be pulled - # out if the configuration calls for it. - if gast_util.GAST2: - literal_node_types = ( - gast.Num, gast.Str, gast.Bytes, gast.NameConstant, - gast.Name # Name is here to cover True, False, and None in Python 2 - ) - elif gast_util.GAST3: - literal_node_types = ( - gast.Constant, - gast.Name # Name is here to cover True, False, and None in Python 2 - ) - else: - assert False - - self._overrides = [ - (ASTEdgePattern(ANY, ANY, literal_node_types), LEAVE), - (ASTEdgePattern(ANY, ANY, gast.expr), REPLACE)] - else: - self._overrides = config - self._gensym = DummyGensym() - self._pending_statements = [] - - def _consume_pending_statements(self): - ans = self._pending_statements - self._pending_statements = [] - return ans - - def _add_pending_statement(self, stmt): - self._pending_statements.append(stmt) - - def _match(self, pattern, parent, field, child): - if pattern is ANY: - return True - else: - return pattern.matches(parent, field, child) - - def _should_transform(self, parent, field, child): - for pat, result in self._overrides: - if self._match(pat, parent, field, child): - return result(parent, field, child) - # Fell off the end of the pattern list: do not transform - return False - - def _do_transform_node(self, node): - temp_name = self._gensym.new_name() - temp_assign = templates.replace( - 'temp_name = expr', temp_name=temp_name, expr=node)[0] - self._add_pending_statement(temp_assign) - answer = templates.replace('temp_name', temp_name=temp_name)[0] - return answer - - def _ensure_node_in_anf(self, parent, field, node): - """Puts `node` in A-normal form, by replacing it with a variable if needed. - - The exact definition of A-normal form is given by the configuration. The - parent and the incoming field name are only needed because the configuration - may be context-dependent. - - Args: - parent: An AST node, the parent of `node`. - field: The field name under which `node` is the child of `parent`. - node: An AST node, potentially to be replaced with a variable reference. - - Returns: - node: An AST node; the argument if transformation was not necessary, - or the new variable reference if it was. - """ - if node is None: - return node - if _is_trivial(node): - return node - if isinstance(node, list): - # If something's field was actually a list, e.g., variadic arguments. - return [self._ensure_node_in_anf(parent, field, n) for n in node] - if isinstance(node, gast.keyword): - node.value = self._ensure_node_in_anf(parent, field, node.value) - return node - if isinstance(node, (gast.Starred, gast.withitem, gast.slice)): - # These nodes aren't really extractable in their own right, but their - # subnodes might be. Propagate the parent and field name to the child - # nodes, instead of querying the configuration for children of, e.g., - # gast.Starred. - return self._ensure_fields_in_anf(node, parent, field) - if self._should_transform(parent, field, node): - return self._do_transform_node(node) - else: - return node - - def _ensure_fields_in_anf(self, node, parent=None, super_field=None): - for field in node._fields: - if field.startswith('__'): - continue - parent_supplied = node if parent is None else parent - field_supplied = field if super_field is None else super_field - setattr(node, field, self._ensure_node_in_anf( - parent_supplied, field_supplied, getattr(node, field))) - return node - - def _visit_strict_statement(self, node, children_ok_to_transform=True): - assert not self._pending_statements - node = self.generic_visit(node) - if children_ok_to_transform: - self._ensure_fields_in_anf(node) - results = self._consume_pending_statements() - results.append(node) - return results - - def _visit_trivial_only_statement(self, node, msg): - assert not self._pending_statements - node = self.generic_visit(node) - self._ensure_fields_in_anf(node) - if self._pending_statements: - raise ValueError(msg) - else: - return node - - def _visit_strict_expression(self, node): - node = self.generic_visit(node) - self._ensure_fields_in_anf(node) - return node - - def _visit_trivial_only_expression(self, node, msg): - k = len(self._pending_statements) - node = self.generic_visit(node) - self._ensure_fields_in_anf(node) - # This check relies on there being no opportunities to consume pending - # statements while traversing children of an expression. - if len(self._pending_statements) != k: - raise ValueError(msg) - else: - return node - - # Note on code order: These are listed in the same order as the grammar - # elements on https://github.com/serge-sans-paille/gast - - # FunctionDef, AsyncFunctionDef, and ClassDef should be correct by default. - - def visit_Return(self, node): - return self._visit_strict_statement(node) - - def visit_Delete(self, node): - return self._visit_strict_statement(node, children_ok_to_transform=False) - - def visit_Assign(self, node): - return self._visit_strict_statement(node, children_ok_to_transform=False) - - def visit_AugAssign(self, node): - return self._visit_strict_statement(node, children_ok_to_transform=False) - - def visit_Print(self, node): - return self._visit_strict_statement(node) - - def visit_For(self, node): - assert not self._pending_statements - # It's important to visit node.iter first, because any statements created - # thereby need to live outside the body. - self.visit(node.iter) - node.iter = self._ensure_node_in_anf(node, 'iter', node.iter) - iter_stmts = self._consume_pending_statements() - # This generic_visit will revisit node.iter, but that is correct because by - # this point the node.iter link has been checked. It may be somewhat - # expensive if the configuration didn't call for transforming node.iter, as - # then it may be large and will be uselessly transformed again. This - # behavior is what causes the documented effect that configuration callables - # may be invoked more than once of the same links; if the code is rewritten - # not to do that (anywhere), the docstring of `transform` should be updated. - node = self.generic_visit(node) - assert not self._pending_statements - iter_stmts.append(node) - return iter_stmts - - def visit_AsyncFor(self, node): - msg = ('Nontrivial AsyncFor nodes not supported yet ' - '(need to think through the semantics).') - return self._visit_trivial_only_statement(node, msg) - - def visit_While(self, node): - assert not self._pending_statements - self.visit(node.test) - node.test = self._ensure_node_in_anf(node, 'test', node.test) - if self._pending_statements: - msg = ('While with nontrivial test not supported yet ' - '(need to avoid precomputing the test).') - raise ValueError(msg) - # If traversing node.test yielded no statements extracted, the generic visit - # will do the right thing. - return self.generic_visit(node) - - def visit_If(self, node): - assert not self._pending_statements - # It's important to visit node.test first, because any statements created - # thereby need to live outside the body. - self.visit(node.test) - node.test = self._ensure_node_in_anf(node, 'test', node.test) - condition_stmts = self._consume_pending_statements() - # This generic_visit will revisit node.test, but that is correct because by - # this point the node.test link has been checked. It may be somewhat - # expensive if the configuration didn't call for transforming node.test, as - # then it may be large and will be uselessly transformed again. This - # happens in several places. - node = self.generic_visit(node) - assert not self._pending_statements - condition_stmts.append(node) - return condition_stmts - - def visit_With(self, node): - assert not self._pending_statements - # It's important to visit node.items first, because any statements created - # thereby need to live outside the body. - for item in node.items: - self.visit(item) - node.items = [self._ensure_node_in_anf(node, 'items', n) - for n in node.items] - contexts_stmts = self._consume_pending_statements() - # This generic_visit will revisit node.items, but that is correct because by - # this point the node.items link has been checked. It may be somewhat - # expensive if the configuration didn't call for transforming node.items, as - # then it may be large and will be uselessly transformed again. This - # happens in several places. - node = self.generic_visit(node) - assert not self._pending_statements - contexts_stmts.append(node) - return contexts_stmts - - def visit_AsyncWith(self, node): - msg = ('Nontrivial AsyncWith nodes not supported yet ' - '(need to think through the semantics).') - return self._visit_trivial_only_statement(node, msg) - - def visit_Raise(self, node): - return self._visit_strict_statement(node) - - # Try should be correct by default. - - def visit_Assert(self, node): - msg = ('Nontrivial Assert nodes not supported yet ' - '(need to avoid computing the test when assertions are off, and ' - 'avoid computing the irritant when the assertion does not fire).') - return self._visit_trivial_only_statement(node, msg) - - # Import and ImportFrom should be correct by default. - - def visit_Exec(self, node): - return self._visit_strict_statement(node) - - # Global and Nonlocal should be correct by default. - - def visit_Expr(self, node): - return self._visit_strict_statement(node, children_ok_to_transform=False) - - # Pass, Break, and Continue should be correct by default. - - def visit_BoolOp(self, node): - msg = ('Nontrivial BoolOp nodes not supported yet ' - '(need to preserve short-circuiting semantics).') - return self._visit_trivial_only_expression(node, msg) - - def visit_BinOp(self, node): - return self._visit_strict_expression(node) - - def visit_UnaryOp(self, node): - return self._visit_strict_expression(node) - - def visit_Lambda(self, node): - msg = ('Nontrivial Lambda nodes not supported ' - '(cannot insert statements into lambda bodies).') - return self._visit_trivial_only_expression(node, msg) - - def visit_IfExp(self, node): - msg = ('Nontrivial IfExp nodes not supported yet ' - '(need to convert to If statement, to evaluate branches lazily ' - 'and insert statements into them).') - return self._visit_trivial_only_expression(node, msg) - - def visit_Dict(self, node): - return self._visit_strict_expression(node) - - def visit_Set(self, node): - return self._visit_strict_expression(node) - - def visit_ListComp(self, node): - msg = ('ListComp nodes not supported ' - '(need to convert to a form that tolerates ' - 'assignment statements in clause bodies).') - raise ValueError(msg) - - def visit_SetComp(self, node): - msg = ('SetComp nodes not supported ' - '(need to convert to a form that tolerates ' - 'assignment statements in clause bodies).') - raise ValueError(msg) - - def visit_DictComp(self, node): - msg = ('DictComp nodes not supported ' - '(need to convert to a form that tolerates ' - 'assignment statements in clause bodies).') - raise ValueError(msg) - - def visit_GeneratorExp(self, node): - msg = ('GeneratorExp nodes not supported ' - '(need to convert to a form that tolerates ' - 'assignment statements in clause bodies).') - raise ValueError(msg) - - def visit_Await(self, node): - msg = ('Nontrivial Await nodes not supported yet ' - '(need to think through the semantics).') - return self._visit_trivial_only_expression(node, msg) - - def visit_Yield(self, node): - return self._visit_strict_expression(node) - - def visit_YieldFrom(self, node): - msg = ('Nontrivial YieldFrom nodes not supported yet ' - '(need to unit-test them in Python 2).') - return self._visit_trivial_only_expression(node, msg) - - def visit_Compare(self, node): - if len(node.ops) > 1: - msg = ('Multi-ary compare nodes not supported yet ' - '(need to preserve short-circuiting semantics).') - raise ValueError(msg) - return self._visit_strict_expression(node) - - def visit_Call(self, node): - return self._visit_strict_expression(node) - - def visit_Repr(self, node): - msg = ('Nontrivial Repr nodes not supported yet ' - '(need to research their syntax and semantics).') - return self._visit_trivial_only_expression(node, msg) - - def visit_FormattedValue(self, node): - msg = ('Nontrivial FormattedValue nodes not supported yet ' - '(need to unit-test them in Python 2).') - return self._visit_trivial_only_expression(node, msg) - - def visit_JoinedStr(self, node): - msg = ('Nontrivial JoinedStr nodes not supported yet ' - '(need to unit-test them in Python 2).') - return self._visit_trivial_only_expression(node, msg) - - def visit_Attribute(self, node): - return self._visit_strict_expression(node) - - def visit_Subscript(self, node): - return self._visit_strict_expression(node) - - # Starred and Name are correct by default, because the right thing to do is to - # just recur. - - def visit_List(self, node): - node = self.generic_visit(node) - if not isinstance(node.ctx, gast.Store): - self._ensure_fields_in_anf(node) - return node - - def visit_Tuple(self, node): - node = self.generic_visit(node) - if not isinstance(node.ctx, gast.Store): - self._ensure_fields_in_anf(node) - return node - - -def _is_py2_name_constant(node): - return isinstance(node, gast.Name) and node.id in ['True', 'False', 'None'] - - -def _is_trivial(node): - """Returns whether to consider the given node 'trivial'. - - The definition of 'trivial' is a node that can't meaningfully be pulled out - into its own assignment statement. - - This is surprisingly difficult to do robustly across versions of Python and - gast, as the parsing of constants has changed, if I may, constantly. - - Args: - node: An AST node to check for triviality - - Returns: - trivial: A Python `bool` indicating whether the node is trivial. - """ - trivial_node_types = ( - # Variable names - gast.Name, - # Non-nodes that show up as AST fields - bool, - str, - # Binary operators - gast.Add, - gast.Sub, - gast.Mult, - gast.Div, - gast.Mod, - gast.Pow, - gast.LShift, - gast.RShift, - gast.BitOr, - gast.BitXor, - gast.BitAnd, - gast.FloorDiv, - # Unary operators - gast.Invert, - gast.Not, - gast.UAdd, - gast.USub, - # Comparison operators - gast.Eq, - gast.NotEq, - gast.Lt, - gast.LtE, - gast.Gt, - gast.GtE, - gast.Is, - gast.IsNot, - gast.In, - gast.NotIn, - # Other leaf nodes that don't make sense standalone. - gast.expr_context, - ) - if isinstance(node, trivial_node_types) and not _is_py2_name_constant(node): - return True - if gast_util.is_ellipsis(node): - return True - - return False - - -def transform(node, ctx, config=None): - """Converts the given node to A-normal form (ANF). - - The general idea of A-normal form: https://en.wikipedia.org/wiki/A-normal_form - - The specific converters used here are based on Python AST semantics as - documented at https://greentreesnakes.readthedocs.io/en/latest/. - - What exactly should be considered A-normal form for any given programming - language is not completely obvious. The transformation defined here is - therefore configurable as to which syntax to replace with a fresh variable and - which to leave be. The configuration is intentionally flexible enough to - define very precise variable insertion transformations, should that be - desired. - - The configuration is a list of syntax rules, each of which is a 2-tuple: - - An `ASTEdgePattern` (which see) defining a type of AST edge, and - - Whether to transform children of such edges. - The special object `anf.ANY` may be used as a pattern that matches all edges. - - Each replacement directive is one of three possible things: - - The object `anf.REPLACE`, meaning "Replace this child node with a variable", - - The object `anf.LEAVE`, meaning "Do not replace this child node with a - variable", or - - A Python callable. If a callable, it is called with the parent node, the - field name, and the child node, and must compute a boolean indicating - whether to transform the child node or not. The callable is free to use - whatever context information it chooses. The callable may be invoked more - than once on the same link, and must produce the same answer each time. - - The syntax rules are tested in order, and the first match governs. If no rule - matches, the node is not transformed. - - The above rules notwithstanding, - - Variable references are never replaced with (fresh) variables, as that would - accomplish nothing. - - The left-hand children of Assign and AugAssign nodes, and the children of - Del nodes, are never replaced with variables, as that would break their - semantics. - - The right-hand children of Assign nodes are never replaced with variables, - as the original assignment would still have to be present in the result - to define the new variable. (That is, there's no point in transforming - `x = sin(y)` into `tmp = sin(y); x = tmp`.) - - The right-hand children of AugAssign nodes are never replaced with variables - either, but only because the difference from Assign was considered a - potential source of confusion (and it would have been slightly awkward in - the code to treat the RHS differently than the LHS). - - Various special-purpose AST nodes are not exposed to the configuration, lest - the transform produce invalid syntax like, e.g., `tmp = +; x = 1 tmp 2`. - - For example, the configuration - ```python - [(anf.ASTEdgePattern(anf.ANY, anf.ANY, gast.expr), anf.REPLACE)] - ``` - gives explicit fresh names to all expressions regardless of context (except as - outlined above), whereas - ```python - [(anf.ASTEdgePattern(gast.If, "test", anf.ANY), anf.REPLACE)] - ``` - only transforms the conditionals of `if` statements (but not, e.g., `while`). - - If no configuration is supplied, the default behavior is to transform all - expressions except literal constants, which is defined as a configuration as - ```python - # For Python 3, and gast library versions before 0.3 - literals = (gast.Num, gast.Str, gast.Bytes, gast.NameConstant) - [(anf.ASTEdgePattern(anf.ANY, anf.ANY, literals), anf.LEAVE), - (anf.ASTEdgePattern(anf.ANY, anf.ANY, gast.expr), anf.REPLACE)] - ``` - - Args: - node: The node to transform. - ctx: transformer.EntityInfo. TODO(mdan): What information does this - argument provide? - config: Optional ANF configuration. If omitted, ANF replaces all expression - expect literal constants. - """ - return AnfTransformer(ctx, config).visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/pyct/common_transformers/anf_test.py b/src/braket/experimental/autoqasm/autograph/pyct/common_transformers/anf_test.py deleted file mode 100644 index 4fb04e50..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/common_transformers/anf_test.py +++ /dev/null @@ -1,518 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for anf module.""" - -import textwrap - -import gast - -from braket.experimental.autoqasm.autograph.pyct import loader -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import transformer -from braket.experimental.autoqasm.autograph.pyct.common_transformers import anf -from tensorflow.python.platform import test - - -# TODO(mdan): These two functions no longer need to be at the top level. -# TODO(mdan): Don't use exec. -def exec_test_function(): - # The point is to test A-normal form conversion of exec - # pylint: disable=exec-used - exec('computed' + 5 + 'stuff', globals(), locals()) - - -def exec_expected_result(): - # pylint: disable=exec-used - tmp_1001 = 'computed' + 5 - tmp_1002 = tmp_1001 + 'stuff' - tmp_1003 = globals() - tmp_1004 = locals() - exec(tmp_1002, tmp_1003, tmp_1004) - - -class AnfTestBase(test.TestCase): - - def _simple_context(self): - entity_info = transformer.EntityInfo( - name='test_fn', - source_code=None, - source_file=None, - future_features=(), - namespace=None) - return transformer.Context(entity_info, None, None) - - def assert_same_ast(self, expected_node, node, msg=None): - expected_source = parser.unparse(expected_node, indentation=' ') - expected_str = textwrap.dedent(expected_source).strip() - got_source = parser.unparse(node, indentation=' ') - got_str = textwrap.dedent(got_source).strip() - self.assertEqual(expected_str, got_str, msg=msg) - - def assert_body_anfs_as_expected(self, expected_fn, test_fn, config=None): - # Testing the code bodies only. Wrapping them in functions so the - # syntax highlights nicely, but Python doesn't try to execute the - # statements. - exp_node, _ = parser.parse_entity(expected_fn, future_features=()) - node, _ = parser.parse_entity(test_fn, future_features=()) - node = anf.transform(node, self._simple_context(), config=config) - exp_name = exp_node.name - # Ignoring the function names in the result because they can't be - # the same (because both functions have to exist in the same scope - # at the same time). - node.name = exp_name - self.assert_same_ast(exp_node, node) - # Check that ANF is idempotent - node_repeated = anf.transform(node, self._simple_context()) - self.assert_same_ast(node_repeated, node) - - -class AnfTransformerTest(AnfTestBase): - - def test_basic(self): - def test_function(): - a = 0 - return a - - node, _ = parser.parse_entity(test_function, future_features=()) - node = anf.transform(node, self._simple_context()) - result, _, _ = loader.load_ast(node) - self.assertEqual(test_function(), result.test_function()) - - def test_binop_basic(self): - - def test_function(x, y, z): - a = x + y + z - return a - - def expected_result(x, y, z): - tmp_1001 = x + y - a = tmp_1001 + z - return a - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_if_basic(self): - - def test_function(a, b, c, e, f, g): - if a + b + c: - d = e + f + g - return d - - def expected_result(a, b, c, e, f, g): - tmp_1001 = a + b - tmp_1002 = tmp_1001 + c - if tmp_1002: - tmp_1003 = e + f - d = tmp_1003 + g - return d - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_nested_binop_and_return(self): - - def test_function(b, c, d, e): - return (2 * b + c) + (d + e) - - def expected_result(b, c, d, e): - tmp_1001 = 2 * b - tmp_1002 = tmp_1001 + c - tmp_1003 = d + e - tmp_1004 = tmp_1002 + tmp_1003 - return tmp_1004 - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_function_call_and_expr(self): - - def test_function(call_something, a, b, y, z, c, d, e, f, g, h, i): - call_something(a + b, y * z, kwarg=c + d, *(e + f), **(g + h + i)) - - def expected_result(call_something, a, b, y, z, c, d, e, f, g, h, i): - tmp_1001 = g + h - tmp_1002 = a + b - tmp_1003 = y * z - tmp_1004 = e + f - tmp_1005 = c + d - tmp_1006 = tmp_1001 + i - call_something(tmp_1002, tmp_1003, kwarg=tmp_1005, *tmp_1004, **tmp_1006) - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_with_and_print(self): - - def test_function(a, b, c): - with a + b + c as d: - print(2 * d + 1) - - def expected_result(a, b, c): - tmp_1001 = a + b - tmp_1002 = tmp_1001 + c - with tmp_1002 as d: - tmp_1003 = 2 * d - tmp_1004 = tmp_1003 + 1 - print(tmp_1004) - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_nested_multi_value_assign(self): - - def test_function(a, b, c): - x, y = a, a + b - (z, y), x = (c, y + b), x + a - return z, (y, x) - - def expected_result(a, b, c): - tmp_1001 = a + b - x, y = a, tmp_1001 - tmp_1002 = y + b - tmp_1003 = (c, tmp_1002) - tmp_1004 = x + a - (z, y), x = tmp_1003, tmp_1004 - tmp_1005 = y, x - tmp_1006 = z, tmp_1005 - return tmp_1006 - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_deeply_nested_multi_value_assign(self): - - def test_function(a): - [([(b, c), [d, e]], (f, g)), [(h, i, j), k]] = a - return [([(b, c), [d, e]], (f, g)), [(h, i, j), k]] - - def expected_result(a): - [([(b, c), [d, e]], (f, g)), [(h, i, j), k]] = a - tmp_1001 = b, c - tmp_1002 = [d, e] - tmp_1003 = [tmp_1001, tmp_1002] - tmp_1004 = f, g - tmp_1005 = h, i, j - tmp_1006 = tmp_1003, tmp_1004 - tmp_1007 = [tmp_1005, k] - tmp_1008 = [tmp_1006, tmp_1007] - return tmp_1008 - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_local_definition_and_binary_compare(self): - - def test_function(): - def foo(a, b): - return 2 * a < b - return foo - - def expected_result(): - def foo(a, b): - tmp_1001 = 2 * a - tmp_1002 = tmp_1001 < b - return tmp_1002 - return foo - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_list_literal(self): - - def test_function(a, b, c, d, e, f): - return [a + b, c + d, e + f] - - def expected_result(a, b, c, d, e, f): - tmp_1001 = a + b - tmp_1002 = c + d - tmp_1003 = e + f - tmp_1004 = [tmp_1001, tmp_1002, tmp_1003] - return tmp_1004 - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_tuple_literal_and_unary(self): - - def test_function(a, b, c, d, e, f): - return (a + b, -(c + d), e + f) - - def expected_result(a, b, c, d, e, f): - tmp_1001 = c + d - tmp_1002 = a + b - tmp_1003 = -tmp_1001 - tmp_1004 = e + f - tmp_1005 = (tmp_1002, tmp_1003, tmp_1004) - return tmp_1005 - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_set_literal(self): - - def test_function(a, b, c, d, e, f): - return set(a + b, c + d, e + f) - - def expected_result(a, b, c, d, e, f): - tmp_1001 = a + b - tmp_1002 = c + d - tmp_1003 = e + f - tmp_1004 = set(tmp_1001, tmp_1002, tmp_1003) - return tmp_1004 - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_dict_literal_and_repr(self): - - def test_function(foo, bar, baz): - return repr({foo + bar + baz: 7 | 8}) - - def expected_result(foo, bar, baz): - tmp_1001 = foo + bar - tmp_1002 = tmp_1001 + baz - tmp_1003 = 7 | 8 - tmp_1004 = {tmp_1002: tmp_1003} - tmp_1005 = repr(tmp_1004) - return tmp_1005 - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_field_read_and_write(self): - - def test_function(a, d): - a.b.c = d.e.f + 3 - - def expected_result(a, d): - tmp_1001 = a.b - tmp_1002 = d.e - tmp_1003 = tmp_1002.f - tmp_1001.c = tmp_1003 + 3 - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_subscript_read_and_write(self): - - def test_function(a, b, c, d, e, f): - a[b][c] = d[e][f] + 3 - - def expected_result(a, b, c, d, e, f): - tmp_1001 = a[b] - tmp_1002 = d[e] - tmp_1003 = tmp_1002[f] - tmp_1001[c] = tmp_1003 + 3 - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_augassign_and_delete(self): - - def test_function(a, x, y, z): - a += x + y + z - del a - del z[y][x] - - def expected_result(a, x, y, z): - tmp_1001 = x + y - a += tmp_1001 + z - del a - tmp_1002 = z[y] - del tmp_1002[x] - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_raise_yield_and_raise(self): - - def test_function(a, c, some_computed, exception): - yield a ** c - raise some_computed('complicated' + exception) - - def expected_result(a, c, some_computed, exception): - tmp_1001 = a ** c - yield tmp_1001 - tmp_1002 = 'complicated' + exception - tmp_1003 = some_computed(tmp_1002) - raise tmp_1003 - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_with_and_if_with_expressions(self): - - def test_function(foo, bar, function, quux, quozzle, w, x, y, z): - with foo + bar: - function(x + y) - if quux + quozzle: - function(z / w) - - def expected_result(foo, bar, function, quux, quozzle, w, x, y, z): - tmp_1001 = foo + bar - with tmp_1001: - tmp_1002 = x + y - function(tmp_1002) - tmp_1003 = quux + quozzle - if tmp_1003: - tmp_1004 = z / w - function(tmp_1004) - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_exec(self): - self.assert_body_anfs_as_expected(exec_expected_result, exec_test_function) - - def test_simple_while_and_assert(self): - - def test_function(foo, quux): - while foo: - assert quux - foo = foo + 1 * 3 - - def expected_result(foo, quux): - while foo: - assert quux - tmp_1001 = 1 * 3 - foo = foo + tmp_1001 - - self.assert_body_anfs_as_expected(expected_result, test_function) - - def test_for(self): - - def test_function(compute, something, complicated, foo): - for foo in compute(something + complicated): - bar = foo + 1 * 3 - return bar - - def expected_result(compute, something, complicated, foo): - tmp_1001 = something + complicated - tmp_1002 = compute(tmp_1001) - for foo in tmp_1002: - tmp_1003 = 1 * 3 - bar = foo + tmp_1003 - return bar - - self.assert_body_anfs_as_expected(expected_result, test_function) - - # This test collects several examples where the definition of A-normal form - # implemented by this transformer is questionable. Mostly it's here to spell - # out what the definition is in these cases. - def test_controversial(self): - - def test_function(b, c, d, f): - a = c + d - a.b = c + d - a[b] = c + d - a += c + d - a, b = c - a, b = c, d - a = f(c) - a = f(c + d) - a[b + d] = f.e(c + d) - - def expected_result(b, c, d, f): - a = c + d - a.b = c + d # Should be a.b = tmp? (Definitely not tmp = c + d) - a[b] = c + d # Should be a[b] = tmp? (Definitely not tmp = c + d) - a += c + d # Should be a += tmp? (Definitely not tmp = c + d) - a, b = c # Should be a = c[0], b = c[1]? Or not? - a, b = c, d # Should be a = c, b = d? Or not? - a = f(c) - tmp_1001 = c + d - a = f(tmp_1001) - tmp_1002 = b + d - tmp_1003 = f.e - tmp_1004 = c + d - a[tmp_1002] = tmp_1003(tmp_1004) # Or should be a[tmp1] = tmp2? - - self.assert_body_anfs_as_expected(expected_result, test_function) - - -class AnfNonTransformationTest(AnfTransformerTest): - """Test that specifying "no transformation" does nothing. - - Reuses all the examples of AnfTransformerTest by overriding - `assert_body_anfs_as_expected_`. - """ - - def assert_body_anfs_as_expected(self, expected_fn, test_fn): - # Testing the code bodies only. Wrapping them in functions so the - # syntax highlights nicely, but Python doesn't try to execute the - # statements. - node, _ = parser.parse_entity(test_fn, future_features=()) - orig_source = parser.unparse(node, indentation=' ') - orig_str = textwrap.dedent(orig_source).strip() - config = [(anf.ANY, anf.LEAVE)] # Configuration to transform nothing - node = anf.transform(node, self._simple_context(), config=config) - new_source = parser.unparse(node, indentation=' ') - new_str = textwrap.dedent(new_source).strip() - self.assertEqual(orig_str, new_str) - - -class AnfConfiguredTest(AnfTestBase): - - def test_constants_in_function_calls(self): - # An example specific configuration that differs from the default: Moving - # literals out of being directly passed to functions, but nothing else. - try: - # TODO(b/140808434): Fix this. - # gast pre-0.3 - literals = (gast.Num, gast.Str, gast.Bytes, gast.NameConstant, gast.Name) - except AttributeError: - # gast 0.3+ - literals = (gast.Constant, gast.Name) - config = [(anf.ASTEdgePattern(gast.Call, anf.ANY, literals), anf.REPLACE)] - - def test_function(x, frob): - return frob(x, x+1, 2) - - def expected_result(x, frob): - tmp_1001 = 2 - return frob(x, x+1, tmp_1001) - - self.assert_body_anfs_as_expected(expected_result, test_function, config) - - def test_anf_some_function_calls(self): - # Another example specific configuration that differs from the default: - # Moving all arguments out of some function calls but leaving others be. - allowlist = ['foo'] - - def transform(parent, field, child): - del field - del child - func_name = parent.func.id - return str(func_name) in allowlist - - config = [(anf.ASTEdgePattern(gast.Call, anf.ANY, anf.ANY), transform)] - - def test_function(x, foo, bar): - y = foo(x, x+1, 2) - return bar(y, y+1, 2) - - def expected_result(x, foo, bar): - tmp_1001 = x+1 - tmp_1002 = 2 - y = foo(x, tmp_1001, tmp_1002) - return bar(y, y+1, 2) - - self.assert_body_anfs_as_expected(expected_result, test_function, config) - - def test_touching_name_constant(self): - # Checking that the nodes for `True`, `False`, and `None` can be manipulated - # by a configuration. This is non-trivial, because in Python 2 those are - # represented as `Name`, which is the same node type as variable references. - specials = (gast.Name, gast.Constant) - config = [(anf.ASTEdgePattern(gast.Call, anf.ANY, specials), anf.REPLACE)] - - def test_function(f): - return f(True, False, None) - - def expected_result(f): - tmp_1001 = True - tmp_1002 = False - tmp_1003 = None - return f(tmp_1001, tmp_1002, tmp_1003) - - self.assert_body_anfs_as_expected(expected_result, test_function, config) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/error_utils.py b/src/braket/experimental/autoqasm/autograph/pyct/error_utils.py deleted file mode 100644 index 31c0a2cc..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/error_utils.py +++ /dev/null @@ -1,230 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Code transformation exceptions.""" - -import collections - -from braket.experimental.autoqasm.autograph.pyct import origin_info -from braket.experimental.autoqasm.autograph.tf_utils import traceback_utils - - -class FrameInfo( - collections.namedtuple('FrameInfo', - ('filename', 'lineno', 'function_name', 'code', - 'is_converted', 'is_allowlisted'))): - - __slots__ = () - - -def _stack_trace_inside_mapped_code(tb, source_map, converter_filename): - """Summarizes inner traceback frames up to the call to a given function. - - This functions locates the innermost (i.e. most recent) frame that corresponds - to code that can be mapped by source_map originated from, and returns a - translated stack trace ending at that frame. If no such frame is found, the - entire stack trace is summarized. - - For example, the following code: - - def f(): - for i in tf.range(1): - z = y + i # z only defined here - - Would generate this traceback: - - - ag__.for_stmt(...) - - return _known_len_tf_for_stmt(iter_, extra_test, body, init_state) - <_known_len_tf_for_stmt> - _disallow_undefs_into_loop(*init_state) - <_disallow_undefs_into_loop> - raise ... - - Which is then processed into: - - - for i in tf.range(1): - - return _known_len_tf_for_stmt(iter_, extra_test, body, init_state) - <_known_len_tf_for_stmt> - _disallow_undefs_into_loop(*init_state) - <_disallow_undefs_into_loop> - raise ... - - Args: - tb: traceback.FrameSummary, The traceback corresponding to an error. - Typically, the output of traceback.Summary.extract(capture_locals=True). - source_map: Dict[LineLocation, OriginInfo], a source map as created by - origin_info.create_source_map. - converter_filename: str, the file path of the converted module. Call frames - corresponding to this module are elided and their preceding frames are - marked as allowlisted. Note that frames enclosing converted code are - dropped using a different mechanism. - - Returns: - List[FrameInfo] - """ - result_frames = [] - for filename, line_number, function_name, text in reversed(tb): - - loc = origin_info.LineLocation(filename=filename, lineno=line_number) - if loc in source_map: - origin = source_map[loc] - fi = FrameInfo( - filename=origin.loc.filename, - lineno=origin.loc.lineno, - function_name=origin.function_name, - code=origin.source_code_line, - is_converted=True, - is_allowlisted=False) - result_frames.append(fi) - break - - if filename == converter_filename: - if result_frames: - prev = result_frames[-1] - assert not prev.is_converted # See the if above. - fi = FrameInfo( - filename=prev.filename, - lineno=prev.lineno, - function_name=prev.function_name, - code=prev.code, - is_converted=False, - is_allowlisted=True) - result_frames[-1] = fi - continue - - fi = FrameInfo( - filename=filename, - lineno=line_number, - function_name=function_name, - code=text, - is_converted=False, - is_allowlisted=False) - result_frames.append(fi) - - return tuple(result_frames) - - -KNOWN_STRING_CONSTRUCTOR_ERRORS = ( - AssertionError, - AttributeError, - NameError, - NotImplementedError, - RuntimeError, - StopIteration, - TypeError, - UnboundLocalError, - ValueError, -) - - -# KeyError escapes newlines in strings. We create a special subclass -# that doesn't do that. Overriding the name for display purposes; hopefully -# that won't create too many surprises. -class MultilineMessageKeyError(KeyError): - - def __init__(self, message, original_key): - super(MultilineMessageKeyError, self).__init__(original_key) - self.__message = message - - def __str__(self): - return self.__message - -MultilineMessageKeyError.__name__ = KeyError.__name__ - - -class ErrorMetadataBase(object): - """Container objects attached to exceptions raised in user code. - - This metadata allows re-raising exceptions that occur in generated code, with - a custom error message that includes a stack trace relative to user-readable - code from which the generated code originated. - """ - - __slots__ = ('translated_stack', 'cause_message') - - def __init__(self, callsite_tb, cause_metadata, cause_message, source_map, - converter_filename): - translated_stack = _stack_trace_inside_mapped_code( - callsite_tb, source_map, converter_filename) - - if cause_metadata is None: - self.translated_stack = translated_stack - self.cause_message = cause_message - else: - # Daisy chain the translated stacks. - self.translated_stack = ( - cause_metadata.translated_stack + (translated_stack[-1],)) - self.cause_message = cause_metadata.cause_message - - def get_message(self): - """Returns the message for the underlying exception.""" - lines = [] - - lines.append('in user code:') - lines.append('') - - for frame_info in reversed(self.translated_stack): - if (traceback_utils.is_traceback_filtering_enabled() and - not traceback_utils.include_frame(frame_info.filename)): - continue - - # Same format with Python traceback. - formatted_line = (f' File "{frame_info.filename}", line ' - f'{frame_info.lineno}, in {frame_info.function_name}') - if frame_info.is_converted: - formatted_line += ' *' - elif frame_info.is_allowlisted: - formatted_line += ' **' - lines.append(formatted_line) - - if frame_info.code is None: - code_snippet = '' - else: - code_snippet = frame_info.code.strip() - lines.append(' {}'.format(code_snippet)) - - lines.append('') - - message_lines = self.cause_message.split('\n') - for i in range(len(message_lines)): - message_lines[i] = ' ' + message_lines[i] - lines.extend(message_lines) - - lines.append('') - - return '\n'.join(lines) - - def create_exception(self, source_error): - """Creates exception from source_error.""" - preferred_type = type(source_error) - to_ret = None - if preferred_type.__init__ is Exception.__init__: - to_ret = preferred_type(self.get_message()) - if preferred_type in KNOWN_STRING_CONSTRUCTOR_ERRORS: - to_ret = preferred_type(self.get_message()) - elif preferred_type is KeyError: - to_ret = MultilineMessageKeyError(self.get_message(), self.cause_message) - - if to_ret is not None: - return to_ret.with_traceback(source_error.__traceback__) - - def to_exception(self, source_error): - exc = self.create_exception(source_error) - exc.__suppress_context__ = True - exc.ag_error_metadata = self - return exc diff --git a/src/braket/experimental/autoqasm/autograph/pyct/error_utils_test.py b/src/braket/experimental/autoqasm/autograph/pyct/error_utils_test.py deleted file mode 100644 index c049ca70..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/error_utils_test.py +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright 2019 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for error_utils module.""" - -import re - -from braket.experimental.autoqasm.autograph.pyct import error_utils -from braket.experimental.autoqasm.autograph.pyct import origin_info -from tensorflow.python.platform import test - - -class ErrorMetadataBaseTest(test.TestCase): - - def test_create_exception_default_constructor(self): - - class CustomError(Exception): - pass - - em = error_utils.ErrorMetadataBase( - callsite_tb=(), - cause_metadata=None, - cause_message='test message', - source_map={}, - converter_filename=None) - exc = em.create_exception(CustomError()) - self.assertIsInstance(exc, CustomError) - self.assertIn('test message', str(exc)) - - def test_create_exception_custom_constructor(self): - - class CustomError(Exception): - - def __init__(self): - super(CustomError, self).__init__('test_message') - - em = error_utils.ErrorMetadataBase( - callsite_tb=(), - cause_metadata=None, - cause_message='test message', - source_map={}, - converter_filename=None) - exc = em.create_exception(CustomError()) - self.assertIsNone(exc) - - def test_get_message_no_code(self): - callsite_tb = [ - ('/path/one.py', 11, 'test_fn_1', None), - ('/path/two.py', 171, 'test_fn_2', 'test code'), - ] - cause_message = 'Test message' - em = error_utils.ErrorMetadataBase( - callsite_tb=callsite_tb, - cause_metadata=None, - cause_message=cause_message, - source_map={}, - converter_filename=None) - self.assertRegex( - em.get_message(), - re.compile(('"/path/one.py", line 11, in test_fn_1.*' - '"/path/two.py", line 171, in test_fn_2.*' - 'Test message'), re.DOTALL)) - - def test_get_message_converted_code(self): - callsite_tb = [ - ('/path/one.py', 11, 'test_fn_1', 'test code 1'), - ('/path/two.py', 171, 'test_fn_2', 'test code 2'), - ('/path/three.py', 171, 'test_fn_3', 'test code 3'), - ] - cause_message = 'Test message' - em = error_utils.ErrorMetadataBase( - callsite_tb=callsite_tb, - cause_metadata=None, - cause_message=cause_message, - source_map={ - origin_info.LineLocation(filename='/path/two.py', lineno=171): - origin_info.OriginInfo( - loc=origin_info.LineLocation( - filename='/path/other_two.py', lineno=13), - function_name='converted_fn', - source_code_line='converted test code', - comment=None) - }, - converter_filename=None) - result = em.get_message() - self.assertRegex( - result, - re.compile((r'converted_fn \*.*' - r'"/path/three.py", line 171, in test_fn_3.*' - r'Test message'), re.DOTALL)) - self.assertNotRegex(result, re.compile('test_fn_1')) - - def test_get_message_call_overload(self): - - callsite_tb = [ - ('/path/one.py', 11, 'test_fn_1', 'test code 1'), - ('/path/two.py', 0, 'test_fn_2', 'test code 2'), - ('/path/three.py', 171, 'test_fn_3', 'test code 3'), - ] - cause_message = 'Test message' - em = error_utils.ErrorMetadataBase( - callsite_tb=callsite_tb, - cause_metadata=None, - cause_message=cause_message, - source_map={}, - converter_filename='/path/two.py') - self.assertRegex( - em.get_message(), - re.compile((r'"/path/one.py", line 11, in test_fn_1.*' - r'"/path/three.py", line 171, in test_fn_3 \*\*.*' - r'Test message'), re.DOTALL)) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/errors.py b/src/braket/experimental/autoqasm/autograph/pyct/errors.py deleted file mode 100644 index 78148013..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/errors.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Code transformation exceptions.""" - - -class PyCTError(Exception): - """Base class for all exceptions.""" - - -class UnsupportedLanguageElementError(PyCTError, NotImplementedError): - """Raised for code patterns that AutoGraph does not support.""" - - -class InaccessibleSourceCodeError(PyCTError, ValueError): - """Raised when inspect can not access source code.""" diff --git a/src/braket/experimental/autoqasm/autograph/pyct/gast_util.py b/src/braket/experimental/autoqasm/autograph/pyct/gast_util.py deleted file mode 100644 index bdbe5007..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/gast_util.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Gast compatibility library. Supports 0.2.2 and 0.3.2.""" -# TODO(mdan): Remove this file once it's safe to break compatibility. - -import functools - -import gast - - -GAST2 = hasattr(gast, 'Str') -GAST3 = not GAST2 - - -def _is_constant_gast_2(node): - return isinstance(node, (gast.Num, gast.Str, gast.Bytes, gast.Ellipsis, - gast.NameConstant)) - - -def _is_constant_gast_3(node): - return isinstance(node, gast.Constant) - - -def is_literal(node): - """Tests whether node represents a Python literal.""" - # Normal literals, True/False/None/Etc. in Python3 - if is_constant(node): - return True - - # True/False/None/Etc. in Python2 - if isinstance(node, gast.Name) and node.id in ['True', 'False', 'None']: - return True - - return False - - -def _is_ellipsis_gast_2(node): - return isinstance(node, gast.Ellipsis) - - -def _is_ellipsis_gast_3(node): - return isinstance(node, gast.Constant) and node.value == Ellipsis - - -if GAST2: - is_constant = _is_constant_gast_2 - is_ellipsis = _is_ellipsis_gast_2 - - Module = gast.Module - Name = gast.Name - Str = gast.Str - -elif GAST3: - is_constant = _is_constant_gast_3 - is_ellipsis = _is_ellipsis_gast_3 - - Module = functools.partial(gast.Module, type_ignores=None) # pylint:disable=invalid-name - Name = functools.partial(gast.Name, type_comment=None) # pylint:disable=invalid-name - Str = functools.partial(gast.Constant, kind=None) # pylint:disable=invalid-name - -else: - assert False diff --git a/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils.py b/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils.py deleted file mode 100644 index be7996dd..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils.py +++ /dev/null @@ -1,321 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Live entity inspection utilities. - -This module contains whatever inspect doesn't offer out of the box. -""" - -import builtins -import inspect -import itertools -import linecache -import sys -import threading -import types - -import inspect as tf_inspect - -# This lock seems to help avoid linecache concurrency errors. -_linecache_lock = threading.Lock() - -# Cache all the builtin elements in a frozen set for faster lookup. -_BUILTIN_FUNCTION_IDS = frozenset(id(v) for v in builtins.__dict__.values()) - - -def islambda(f): - if not tf_inspect.isfunction(f): - return False - # TODO(mdan): Look into checking the only the code object. - if not (hasattr(f, '__name__') and hasattr(f, '__code__')): - return False - # Some wrappers can rename the function, but changing the name of the - # code object is harder. - return ((f.__name__ == '') or (f.__code__.co_name == '')) - - -def isnamedtuple(f): - """Returns True if the argument is a namedtuple-like.""" - if not (tf_inspect.isclass(f) and issubclass(f, tuple)): - return False - if not hasattr(f, '_fields'): - return False - fields = getattr(f, '_fields') - if not isinstance(fields, tuple): - return False - if not all(isinstance(f, str) for f in fields): - return False - return True - - -def isbuiltin(f): - """Returns True if the argument is a built-in function.""" - if id(f) in _BUILTIN_FUNCTION_IDS: - return True - elif isinstance(f, types.BuiltinFunctionType): - return True - elif inspect.isbuiltin(f): - return True - elif f is eval: - return True - else: - return False - - -def isconstructor(cls): - """Returns True if the argument is an object constructor. - - In general, any object of type class is a constructor, with the exception - of classes created using a callable metaclass. - See below for why a callable metaclass is not a trivial combination: - https://docs.python.org/2.7/reference/datamodel.html#customizing-class-creation - - Args: - cls: Any - - Returns: - Bool - """ - return (inspect.isclass(cls) and - not (issubclass(cls.__class__, type) and - hasattr(cls.__class__, '__call__') and - cls.__class__.__call__ is not type.__call__)) - - -def _fix_linecache_record(obj): - """Fixes potential corruption of linecache in the presence of functools.wraps. - - functools.wraps modifies the target object's __module__ field, which seems - to confuse linecache in special instances, for example when the source is - loaded from a .par file (see https://google.github.io/subpar/subpar.html). - - This function simply triggers a call to linecache.updatecache when a mismatch - was detected between the object's __module__ property and the object's source - file. - - Args: - obj: Any - """ - if hasattr(obj, '__module__'): - obj_file = inspect.getfile(obj) - obj_module = obj.__module__ - - # A snapshot of the loaded modules helps avoid "dict changed size during - # iteration" errors. - loaded_modules = tuple(sys.modules.values()) - for m in loaded_modules: - if hasattr(m, '__file__') and m.__file__ == obj_file: - if obj_module is not m: - linecache.updatecache(obj_file, m.__dict__) - - -def getimmediatesource(obj): - """A variant of inspect.getsource that ignores the __wrapped__ property.""" - with _linecache_lock: - _fix_linecache_record(obj) - lines, lnum = inspect.findsource(obj) - return ''.join(inspect.getblock(lines[lnum:])) - - -def getnamespace(f): - """Returns the complete namespace of a function. - - Namespace is defined here as the mapping of all non-local variables to values. - This includes the globals and the closure variables. Note that this captures - the entire globals collection of the function, and may contain extra symbols - that it does not actually use. - - Args: - f: User defined function. - - Returns: - A dict mapping symbol names to values. - """ - namespace = dict(f.__globals__) - closure = f.__closure__ - freevars = f.__code__.co_freevars - if freevars and closure: - for name, cell in zip(freevars, closure): - try: - namespace[name] = cell.cell_contents - except ValueError: - # Cell contains undefined variable, omit it from the namespace. - pass - return namespace - - -def getqualifiedname(namespace, object_, max_depth=5, visited=None): - """Returns the name by which a value can be referred to in a given namespace. - - If the object defines a parent module, the function attempts to use it to - locate the object. - - This function will recurse inside modules, but it will not search objects for - attributes. The recursion depth is controlled by max_depth. - - Args: - namespace: Dict[str, Any], the namespace to search into. - object_: Any, the value to search. - max_depth: Optional[int], a limit to the recursion depth when searching - inside modules. - visited: Optional[Set[int]], ID of modules to avoid visiting. - Returns: Union[str, None], the fully-qualified name that resolves to the value - o, or None if it couldn't be found. - """ - if visited is None: - visited = set() - - # Copy the dict to avoid "changed size error" during concurrent invocations. - # TODO(mdan): This is on the hot path. Can we avoid the copy? - namespace = dict(namespace) - - for name in namespace: - # The value may be referenced by more than one symbol, case in which - # any symbol will be fine. If the program contains symbol aliases that - # change over time, this may capture a symbol that will later point to - # something else. - # TODO(mdan): Prefer the symbol that matches the value type name. - if object_ is namespace[name]: - return name - - # If an object is not found, try to search its parent modules. - parent = tf_inspect.getmodule(object_) - if (parent is not None and parent is not object_ and parent is not namespace): - # No limit to recursion depth because of the guard above. - parent_name = getqualifiedname( - namespace, parent, max_depth=0, visited=visited) - if parent_name is not None: - name_in_parent = getqualifiedname( - parent.__dict__, object_, max_depth=0, visited=visited) - assert name_in_parent is not None, ( - 'An object should always be found in its owner module') - return '{}.{}'.format(parent_name, name_in_parent) - - if max_depth: - # Iterating over a copy prevents "changed size due to iteration" errors. - # It's unclear why those occur - suspecting new modules may load during - # iteration. - for name in namespace.keys(): - value = namespace[name] - if tf_inspect.ismodule(value) and id(value) not in visited: - visited.add(id(value)) - name_in_module = getqualifiedname(value.__dict__, object_, - max_depth - 1, visited) - if name_in_module is not None: - return '{}.{}'.format(name, name_in_module) - return None - - -def getdefiningclass(m, owner_class): - """Resolves the class (e.g. one of the superclasses) that defined a method.""" - method_name = m.__name__ - for super_class in inspect.getmro(owner_class): - if ((hasattr(super_class, '__dict__') and - method_name in super_class.__dict__) or - (hasattr(super_class, '__slots__') and - method_name in super_class.__slots__)): - return super_class - return owner_class - - -def getmethodclass(m): - """Resolves a function's owner, e.g. - - a method's class. - - Note that this returns the object that the function was retrieved from, not - necessarily the class where it was defined. - - This function relies on Python stack frame support in the interpreter, and - has the same limitations that inspect.currentframe. - - Limitations. This function will only work correctly if the owned class is - visible in the caller's global or local variables. - - Args: - m: A user defined function - - Returns: - The class that this function was retrieved from, or None if the function - is not an object or class method, or the class that owns the object or - method is not visible to m. - - Raises: - ValueError: if the class could not be resolved for any unexpected reason. - """ - - # Callable objects: return their own class. - if (not hasattr(m, '__name__') and hasattr(m, '__class__') and - hasattr(m, '__call__')): - if isinstance(m.__class__, type): - return m.__class__ - - # Instance and class: return the class of "self". - m_self = getattr(m, '__self__', None) - if m_self is not None: - if inspect.isclass(m_self): - return m_self - return m_self.__class__ - - # Class, static and unbound methods: search all defined classes in any - # namespace. This is inefficient but more robust a method. - owners = [] - caller_frame = tf_inspect.currentframe().f_back - try: - # TODO(mdan): This doesn't consider cell variables. - # TODO(mdan): This won't work if the owner is hidden inside a container. - # Cell variables may be pulled using co_freevars and the closure. - for v in itertools.chain(caller_frame.f_locals.values(), - caller_frame.f_globals.values()): - if hasattr(v, m.__name__): - candidate = getattr(v, m.__name__) - # Py2 methods may be bound or unbound, extract im_func to get the - # underlying function. - if hasattr(candidate, 'im_func'): - candidate = candidate.im_func - if hasattr(m, 'im_func'): - m = m.im_func - if candidate is m: - owners.append(v) - finally: - del caller_frame - - if owners: - if len(owners) == 1: - return owners[0] - - # If multiple owners are found, and are not subclasses, raise an error. - owner_types = tuple(o if tf_inspect.isclass(o) else type(o) for o in owners) - for o in owner_types: - if tf_inspect.isclass(o) and issubclass(o, tuple(owner_types)): - return o - raise ValueError('Found too many owners of %s: %s' % (m, owners)) - - return None - - -def getfutureimports(entity): - """Detects what future imports are necessary to safely execute entity source. - - Args: - entity: Any object - - Returns: - A tuple of future strings - """ - if not (tf_inspect.isfunction(entity) or tf_inspect.ismethod(entity)): - return tuple() - return tuple( - sorted(name for name, value in entity.__globals__.items() - if getattr(value, '__module__', None) == '__future__')) diff --git a/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils_test.py b/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils_test.py deleted file mode 100644 index 0b20bbde..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils_test.py +++ /dev/null @@ -1,612 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for inspect_utils module.""" - -import abc -import collections -import functools -import imp -import textwrap - -from tensorflow.python import lib -from braket.experimental.autoqasm.autograph.pyct import inspect_utils -from braket.experimental.autoqasm.autograph.pyct.testing import basic_definitions -from braket.experimental.autoqasm.autograph.pyct.testing import decorators -from tensorflow.python.framework import constant_op -from tensorflow.python.platform import test - - -def decorator(f): - return f - - -def function_decorator(): - def dec(f): - return f - return dec - - -def wrapping_decorator(): - def dec(f): - def replacement(*_): - return None - - @functools.wraps(f) - def wrapper(*args, **kwargs): - return replacement(*args, **kwargs) - return wrapper - return dec - - -class TestClass: - - def member_function(self): - pass - - @decorator - def decorated_member(self): - pass - - @function_decorator() - def fn_decorated_member(self): - pass - - @wrapping_decorator() - def wrap_decorated_member(self): - pass - - @staticmethod - def static_method(): - pass - - @classmethod - def class_method(cls): - pass - - -def free_function(): - pass - - -def factory(): - return free_function - - -def free_factory(): - def local_function(): - pass - return local_function - - -class InspectUtilsTest(test.TestCase): - - def test_islambda(self): - def test_fn(): - pass - - self.assertTrue(inspect_utils.islambda(lambda x: x)) - self.assertFalse(inspect_utils.islambda(test_fn)) - - def test_islambda_renamed_lambda(self): - l = lambda x: 1 - l.__name__ = 'f' - self.assertTrue(inspect_utils.islambda(l)) - - def test_isnamedtuple(self): - nt = collections.namedtuple('TestNamedTuple', ['a', 'b']) - - class NotANamedTuple(tuple): - pass - - self.assertTrue(inspect_utils.isnamedtuple(nt)) - self.assertFalse(inspect_utils.isnamedtuple(NotANamedTuple)) - - def test_isnamedtuple_confounder(self): - """This test highlights false positives when detecting named tuples.""" - - class NamedTupleLike(tuple): - _fields = ('a', 'b') - - self.assertTrue(inspect_utils.isnamedtuple(NamedTupleLike)) - - def test_isnamedtuple_subclass(self): - """This test highlights false positives when detecting named tuples.""" - - class NamedTupleSubclass(collections.namedtuple('Test', ['a', 'b'])): - pass - - self.assertTrue(inspect_utils.isnamedtuple(NamedTupleSubclass)) - - def assertSourceIdentical(self, actual, expected): - self.assertEqual( - textwrap.dedent(actual).strip(), - textwrap.dedent(expected).strip() - ) - - def test_getimmediatesource_basic(self): - - def test_decorator(f): - - def f_wrapper(*args, **kwargs): - return f(*args, **kwargs) - - return f_wrapper - - expected = """ - def f_wrapper(*args, **kwargs): - return f(*args, **kwargs) - """ - - @test_decorator - def test_fn(a): - """Test docstring.""" - return [a] - - self.assertSourceIdentical( - inspect_utils.getimmediatesource(test_fn), expected) - - def test_getimmediatesource_noop_decorator(self): - - def test_decorator(f): - return f - - expected = ''' - @test_decorator - def test_fn(a): - """Test docstring.""" - return [a] - ''' - - @test_decorator - def test_fn(a): - """Test docstring.""" - return [a] - - self.assertSourceIdentical( - inspect_utils.getimmediatesource(test_fn), expected) - - def test_getimmediatesource_functools_wrapper(self): - - def wrapper_decorator(f): - - @functools.wraps(f) - def wrapper(*args, **kwargs): - return f(*args, **kwargs) - - return wrapper - - expected = textwrap.dedent(""" - @functools.wraps(f) - def wrapper(*args, **kwargs): - return f(*args, **kwargs) - """) - - @wrapper_decorator - def test_fn(a): - """Test docstring.""" - return [a] - - self.assertSourceIdentical( - inspect_utils.getimmediatesource(test_fn), expected) - - def test_getimmediatesource_functools_wrapper_different_module(self): - - expected = textwrap.dedent(""" - @functools.wraps(f) - def wrapper(*args, **kwargs): - return f(*args, **kwargs) - """) - - @decorators.wrapping_decorator - def test_fn(a): - """Test docstring.""" - return [a] - - self.assertSourceIdentical( - inspect_utils.getimmediatesource(test_fn), expected) - - def test_getimmediatesource_normal_decorator_different_module(self): - - expected = textwrap.dedent(""" - def standalone_wrapper(*args, **kwargs): - return f(*args, **kwargs) - """) - - @decorators.standalone_decorator - def test_fn(a): - """Test docstring.""" - return [a] - - self.assertSourceIdentical( - inspect_utils.getimmediatesource(test_fn), expected) - - def test_getimmediatesource_normal_functional_decorator_different_module( - self): - - expected = textwrap.dedent(""" - def functional_wrapper(*args, **kwargs): - return f(*args, **kwargs) - """) - - @decorators.functional_decorator() - def test_fn(a): - """Test docstring.""" - return [a] - - self.assertSourceIdentical( - inspect_utils.getimmediatesource(test_fn), expected) - - def test_getnamespace_globals(self): - ns = inspect_utils.getnamespace(factory) - self.assertEqual(ns['free_function'], free_function) - - def test_getnamespace_closure_with_undefined_var(self): - if False: # pylint:disable=using-constant-test - a = 1 - - def test_fn(): - return a - - ns = inspect_utils.getnamespace(test_fn) - self.assertNotIn('a', ns) - - a = 2 - ns = inspect_utils.getnamespace(test_fn) - - self.assertEqual(ns['a'], 2) - - def test_getnamespace_hermetic(self): - - # Intentionally hiding the global function to make sure we don't overwrite - # it in the global namespace. - free_function = object() # pylint:disable=redefined-outer-name - - def test_fn(): - return free_function - - ns = inspect_utils.getnamespace(test_fn) - globs = test_fn.__globals__ - self.assertTrue(ns['free_function'] is free_function) - self.assertFalse(globs['free_function'] is free_function) - - def test_getnamespace_locals(self): - - def called_fn(): - return 0 - - closed_over_list = [] - closed_over_primitive = 1 - - def local_fn(): - closed_over_list.append(1) - local_var = 1 - return called_fn() + local_var + closed_over_primitive - - ns = inspect_utils.getnamespace(local_fn) - self.assertEqual(ns['called_fn'], called_fn) - self.assertEqual(ns['closed_over_list'], closed_over_list) - self.assertEqual(ns['closed_over_primitive'], closed_over_primitive) - self.assertTrue('local_var' not in ns) - - def test_getqualifiedname(self): - foo = object() - qux = imp.new_module('quxmodule') - bar = imp.new_module('barmodule') - baz = object() - bar.baz = baz - - ns = { - 'foo': foo, - 'bar': bar, - 'qux': qux, - } - - self.assertIsNone(inspect_utils.getqualifiedname(ns, inspect_utils)) - self.assertEqual(inspect_utils.getqualifiedname(ns, foo), 'foo') - self.assertEqual(inspect_utils.getqualifiedname(ns, bar), 'bar') - self.assertEqual(inspect_utils.getqualifiedname(ns, baz), 'bar.baz') - - def test_getqualifiedname_efficiency(self): - foo = object() - - # We create a densely connected graph consisting of a relatively small - # number of modules and hide our symbol in one of them. The path to the - # symbol is at least 10, and each node has about 10 neighbors. However, - # by skipping visited modules, the search should take much less. - ns = {} - prev_level = [] - for i in range(10): - current_level = [] - for j in range(10): - mod_name = 'mod_{}_{}'.format(i, j) - mod = imp.new_module(mod_name) - current_level.append(mod) - if i == 9 and j == 9: - mod.foo = foo - if prev_level: - # All modules at level i refer to all modules at level i+1 - for prev in prev_level: - for mod in current_level: - prev.__dict__[mod.__name__] = mod - else: - for mod in current_level: - ns[mod.__name__] = mod - prev_level = current_level - - self.assertIsNone(inspect_utils.getqualifiedname(ns, inspect_utils)) - self.assertIsNotNone( - inspect_utils.getqualifiedname(ns, foo, max_depth=10000000000)) - - def test_getqualifiedname_cycles(self): - foo = object() - - # We create a graph of modules that contains circular references. The - # search process should avoid them. The searched object is hidden at the - # bottom of a path of length roughly 10. - ns = {} - mods = [] - for i in range(10): - mod = imp.new_module('mod_{}'.format(i)) - if i == 9: - mod.foo = foo - # Module i refers to module i+1 - if mods: - mods[-1].__dict__[mod.__name__] = mod - else: - ns[mod.__name__] = mod - # Module i refers to all modules j < i. - for prev in mods: - mod.__dict__[prev.__name__] = prev - mods.append(mod) - - self.assertIsNone(inspect_utils.getqualifiedname(ns, inspect_utils)) - self.assertIsNotNone( - inspect_utils.getqualifiedname(ns, foo, max_depth=10000000000)) - - def test_getqualifiedname_finds_via_parent_module(self): - # TODO(mdan): This test is vulnerable to change in the lib module. - # A better way to forge modules should be found. - self.assertEqual( - inspect_utils.getqualifiedname( - lib.__dict__, lib.io.file_io.FileIO, max_depth=1), - 'io.file_io.FileIO') - - def test_getmethodclass(self): - - self.assertEqual( - inspect_utils.getmethodclass(free_function), None) - self.assertEqual( - inspect_utils.getmethodclass(free_factory()), None) - - self.assertEqual( - inspect_utils.getmethodclass(TestClass.member_function), - TestClass) - self.assertEqual( - inspect_utils.getmethodclass(TestClass.decorated_member), - TestClass) - self.assertEqual( - inspect_utils.getmethodclass(TestClass.fn_decorated_member), - TestClass) - self.assertEqual( - inspect_utils.getmethodclass(TestClass.wrap_decorated_member), - TestClass) - self.assertEqual( - inspect_utils.getmethodclass(TestClass.static_method), - TestClass) - self.assertEqual( - inspect_utils.getmethodclass(TestClass.class_method), - TestClass) - - test_obj = TestClass() - self.assertEqual( - inspect_utils.getmethodclass(test_obj.member_function), - TestClass) - self.assertEqual( - inspect_utils.getmethodclass(test_obj.decorated_member), - TestClass) - self.assertEqual( - inspect_utils.getmethodclass(test_obj.fn_decorated_member), - TestClass) - self.assertEqual( - inspect_utils.getmethodclass(test_obj.wrap_decorated_member), - TestClass) - self.assertEqual( - inspect_utils.getmethodclass(test_obj.static_method), - TestClass) - self.assertEqual( - inspect_utils.getmethodclass(test_obj.class_method), - TestClass) - - def test_getmethodclass_locals(self): - - def local_function(): - pass - - class LocalClass: - - def member_function(self): - pass - - @decorator - def decorated_member(self): - pass - - @function_decorator() - def fn_decorated_member(self): - pass - - @wrapping_decorator() - def wrap_decorated_member(self): - pass - - self.assertEqual( - inspect_utils.getmethodclass(local_function), None) - - self.assertEqual( - inspect_utils.getmethodclass(LocalClass.member_function), - LocalClass) - self.assertEqual( - inspect_utils.getmethodclass(LocalClass.decorated_member), - LocalClass) - self.assertEqual( - inspect_utils.getmethodclass(LocalClass.fn_decorated_member), - LocalClass) - self.assertEqual( - inspect_utils.getmethodclass(LocalClass.wrap_decorated_member), - LocalClass) - - test_obj = LocalClass() - self.assertEqual( - inspect_utils.getmethodclass(test_obj.member_function), - LocalClass) - self.assertEqual( - inspect_utils.getmethodclass(test_obj.decorated_member), - LocalClass) - self.assertEqual( - inspect_utils.getmethodclass(test_obj.fn_decorated_member), - LocalClass) - self.assertEqual( - inspect_utils.getmethodclass(test_obj.wrap_decorated_member), - LocalClass) - - def test_getmethodclass_callables(self): - - class TestCallable: - - def __call__(self): - pass - - c = TestCallable() - self.assertEqual(inspect_utils.getmethodclass(c), TestCallable) - - def test_getmethodclass_no_bool_conversion(self): - - tensor = constant_op.constant([1]) - self.assertEqual( - inspect_utils.getmethodclass(tensor.get_shape), type(tensor)) - - def test_getdefiningclass(self): - - class Superclass: - - def foo(self): - pass - - def bar(self): - pass - - @classmethod - def class_method(cls): - pass - - class Subclass(Superclass): - - def foo(self): - pass - - def baz(self): - pass - - self.assertIs( - inspect_utils.getdefiningclass(Subclass.foo, Subclass), Subclass) - self.assertIs( - inspect_utils.getdefiningclass(Subclass.bar, Subclass), Superclass) - self.assertIs( - inspect_utils.getdefiningclass(Subclass.baz, Subclass), Subclass) - self.assertIs( - inspect_utils.getdefiningclass(Subclass.class_method, Subclass), - Superclass) - - def test_isbuiltin(self): - self.assertTrue(inspect_utils.isbuiltin(enumerate)) - self.assertTrue(inspect_utils.isbuiltin(eval)) - self.assertTrue(inspect_utils.isbuiltin(float)) - self.assertTrue(inspect_utils.isbuiltin(int)) - self.assertTrue(inspect_utils.isbuiltin(len)) - self.assertTrue(inspect_utils.isbuiltin(range)) - self.assertTrue(inspect_utils.isbuiltin(zip)) - self.assertFalse(inspect_utils.isbuiltin(function_decorator)) - - def test_isconstructor(self): - - class OrdinaryClass: - pass - - class OrdinaryCallableClass: - - def __call__(self): - pass - - class Metaclass(type): - pass - - class CallableMetaclass(type): - - def __call__(cls): - pass - - self.assertTrue(inspect_utils.isconstructor(OrdinaryClass)) - self.assertTrue(inspect_utils.isconstructor(OrdinaryCallableClass)) - self.assertTrue(inspect_utils.isconstructor(Metaclass)) - self.assertTrue(inspect_utils.isconstructor(Metaclass('TestClass', (), {}))) - self.assertTrue(inspect_utils.isconstructor(CallableMetaclass)) - - self.assertFalse(inspect_utils.isconstructor( - CallableMetaclass('TestClass', (), {}))) - - def test_isconstructor_abc_callable(self): - - class AbcBase(metaclass=abc.ABCMeta): - - @abc.abstractmethod - def __call__(self): - pass - - class AbcSubclass(AbcBase): - - def __init__(self): - pass - - def __call__(self): - pass - - self.assertTrue(inspect_utils.isconstructor(AbcBase)) - self.assertTrue(inspect_utils.isconstructor(AbcSubclass)) - - def test_getfutureimports_functions(self): - imps = inspect_utils.getfutureimports(basic_definitions.function_with_print) - self.assertNotIn('absolute_import', imps) - self.assertNotIn('division', imps) - self.assertNotIn('print_function', imps) - self.assertNotIn('generators', imps) - - def test_getfutureimports_lambdas(self): - imps = inspect_utils.getfutureimports(basic_definitions.simple_lambda) - self.assertNotIn('absolute_import', imps) - self.assertNotIn('division', imps) - self.assertNotIn('print_function', imps) - self.assertNotIn('generators', imps) - - def test_getfutureimports_methods(self): - imps = inspect_utils.getfutureimports( - basic_definitions.SimpleClass.method_with_print) - self.assertNotIn('absolute_import', imps) - self.assertNotIn('division', imps) - self.assertNotIn('print_function', imps) - self.assertNotIn('generators', imps) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils_test.sh b/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils_test.sh deleted file mode 100644 index 02dfae2a..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/inspect_utils_test.sh +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -# Test that runs inspect_utils_test as a .par file. - - -SCRIPT_DIR="$(dirname ${BASH_SOURCE[0]})" -${SCRIPT_DIR}/inspect_utils_test.par diff --git a/src/braket/experimental/autoqasm/autograph/pyct/loader.py b/src/braket/experimental/autoqasm/autograph/pyct/loader.py deleted file mode 100644 index f092a463..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/loader.py +++ /dev/null @@ -1,102 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Converting AST to code and Python entities. - -Adapted from Tangent. -""" - -import atexit -import errno -import importlib -import os -import sys -import tempfile - -from braket.experimental.autoqasm.autograph.pyct import origin_info -from braket.experimental.autoqasm.autograph.pyct import parser - - -def _remove_file(file_name): - """Remove a file, if it exists.""" - try: - os.remove(file_name) - except OSError as e: - if e.errno == errno.ENOENT: - # The file disappeared. Ignore this. Temporary files might get - # cleaned up, especially if they reside in /tmp. - pass - else: - raise - - -def load_source(source, delete_on_exit): - """Loads the given source code as a Python module.""" - with tempfile.NamedTemporaryFile( - mode='w', - suffix='.py', - prefix='__autograph_generated_file', - delete=False, - encoding='utf-8') as f: - module_name = os.path.basename(f.name[:-3]) - file_name = f.name - f.write(source) - - if delete_on_exit: - atexit.register(lambda: _remove_file(file_name)) - - spec = importlib.util.spec_from_file_location(module_name, file_name) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - # TODO(mdan): Use our own garbage-collected cache instead of sys.modules. - sys.modules[module_name] = module - return module, file_name - - -def load_ast(nodes, - indentation=' ', - include_source_map=False, - delete_on_exit=True): - """Loads the given AST as a Python module. - - Compiling the AST code this way ensures that the source code is readable by - e.g. `pdb` or `inspect`. - - Args: - nodes: Union[ast.AST, Iterable[ast.AST]], the code to compile, as an AST - object. - indentation: Text, the string to use for indentation. - include_source_map: bool, whether return a source map. - delete_on_exit: bool, whether to delete the temporary file used for - compilation on exit. - - Returns: - Tuple[module, Text, Dict[LineLocation, OriginInfo]], containing: - the module containing the unparsed nodes, the source code corresponding to - nodes, and the source map. Is include_source_map is False, the source map - will be None. - """ - if not isinstance(nodes, (list, tuple)): - nodes = (nodes,) - - source = parser.unparse(nodes, indentation=indentation) - module, _ = load_source(source, delete_on_exit) - - if include_source_map: - source_map = origin_info.create_source_map(nodes, source, module.__file__) - else: - source_map = None - - # TODO(mdan): Return a structured object. - return module, source, source_map diff --git a/src/braket/experimental/autoqasm/autograph/pyct/loader_test.py b/src/braket/experimental/autoqasm/autograph/pyct/loader_test.py deleted file mode 100644 index 2bc0a971..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/loader_test.py +++ /dev/null @@ -1,123 +0,0 @@ -# coding=utf-8 -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for loader module.""" - -import os -import textwrap - -import gast - -from braket.experimental.autoqasm.autograph.pyct import ast_util -from braket.experimental.autoqasm.autograph.pyct import loader -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import pretty_printer -from tensorflow.python.platform import test -import inspect as tf_inspect - - -class LoaderTest(test.TestCase): - - def assertAstMatches(self, actual_node, expected_node_src): - expected_node = gast.parse(expected_node_src).body[0] - - msg = 'AST did not match expected:\n{}\nActual:\n{}'.format( - pretty_printer.fmt(expected_node), - pretty_printer.fmt(actual_node)) - self.assertTrue(ast_util.matches(actual_node, expected_node), msg) - - def test_parse_load_identity(self): - - def test_fn(x): - a = True - b = '' - if a: - b = (x + 1) - return b - - node, _ = parser.parse_entity(test_fn, future_features=()) - module, _, _ = loader.load_ast(node) - source = tf_inspect.getsource(module.test_fn) - expected_node_src = textwrap.dedent(tf_inspect.getsource(test_fn)) - - self.assertAstMatches(node, source) - self.assertAstMatches(node, expected_node_src) - - def test_load_ast(self): - node = gast.FunctionDef( - name='f', - args=gast.arguments( - args=[ - gast.Name( - 'a', ctx=gast.Param(), annotation=None, type_comment=None) - ], - posonlyargs=[], - vararg=None, - kwonlyargs=[], - kw_defaults=[], - kwarg=None, - defaults=[]), - body=[ - gast.Return( - gast.BinOp( - op=gast.Add(), - left=gast.Name( - 'a', - ctx=gast.Load(), - annotation=None, - type_comment=None), - right=gast.Constant(1, kind=None))) - ], - decorator_list=[], - returns=None, - type_comment=None) - - module, source, _ = loader.load_ast(node) - - expected_node_src = """ - # coding=utf-8 - def f(a): - return (a + 1) - """ - expected_node_src = textwrap.dedent(expected_node_src) - - self.assertAstMatches(node, source) - self.assertAstMatches(node, expected_node_src) - - self.assertEqual(2, module.f(1)) - with open(module.__file__, 'r') as temp_output: - self.assertAstMatches(node, temp_output.read()) - - def test_load_source(self): - test_source = textwrap.dedent(u""" - # coding=utf-8 - def f(a): - '日本語 Δθₜ ← Δθₜ₋₁ + ∇Q(sₜ, aₜ)(rₜ + γₜ₊₁ max Q(⋅))' - return a + 1 - """) - module, _ = loader.load_source(test_source, delete_on_exit=True) - self.assertEqual(module.f(1), 2) - self.assertEqual( - module.f.__doc__, '日本語 Δθₜ ← Δθₜ₋₁ + ∇Q(sₜ, aₜ)(rₜ + γₜ₊₁ max Q(⋅))') - - def test_cleanup(self): - test_source = textwrap.dedent('') - _, filename = loader.load_source(test_source, delete_on_exit=True) - # Clean up the file before loader.py tries to remove it, to check that the - # latter can deal with that situation. - os.unlink(filename) - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/naming.py b/src/braket/experimental/autoqasm/autograph/pyct/naming.py deleted file mode 100644 index 17a62671..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/naming.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Symbol naming utilities.""" - -from braket.experimental.autoqasm.autograph.pyct import qual_names - - -class Namer(object): - """Symbol name generator.""" - - def __init__(self, global_namespace): - self.global_namespace = global_namespace - self.generated_names = set() - - def new_symbol(self, name_root, reserved_locals): - """See control_flow.SymbolNamer.new_symbol.""" - # reserved_locals may contain QNs. - all_reserved_locals = set() - for s in reserved_locals: - if isinstance(s, qual_names.QN): - all_reserved_locals.update(s.qn) - elif isinstance(s, str): - all_reserved_locals.add(s) - else: - raise ValueError('Unexpected symbol type "%s"' % type(s)) - - pieces = name_root.split('_') - if pieces[-1].isdigit(): - name_root = '_'.join(pieces[:-1]) - n = int(pieces[-1]) - else: - n = 0 - new_name = name_root - - while (new_name in self.global_namespace or - new_name in all_reserved_locals or new_name in self.generated_names): - n += 1 - new_name = '%s_%d' % (name_root, n) - - self.generated_names.add(new_name) - return new_name diff --git a/src/braket/experimental/autoqasm/autograph/pyct/naming_test.py b/src/braket/experimental/autoqasm/autograph/pyct/naming_test.py deleted file mode 100644 index 371cc19c..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/naming_test.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for naming module.""" - -from braket.experimental.autoqasm.autograph.pyct import naming -from tensorflow.python.platform import test - - -class NamerTest(test.TestCase): - - def test_new_symbol_tracks_names(self): - namer = naming.Namer({}) - self.assertEqual('temp', namer.new_symbol('temp', set())) - self.assertItemsEqual(('temp',), namer.generated_names) - - def test_new_symbol_avoids_duplicates(self): - namer = naming.Namer({}) - self.assertEqual('temp', namer.new_symbol('temp', set())) - self.assertEqual('temp_1', namer.new_symbol('temp', set())) - self.assertItemsEqual(('temp', 'temp_1'), namer.generated_names) - - def test_new_symbol_avoids_conflicts(self): - namer = naming.Namer({'temp': 1}) - # temp is reserved in the global namespace - self.assertEqual('temp_1', namer.new_symbol('temp', set())) - # temp_2 is reserved in the local namespace - self.assertEqual('temp_3', namer.new_symbol('temp', set(('temp_2',)))) - self.assertItemsEqual(('temp_1', 'temp_3'), namer.generated_names) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/origin_info.py b/src/braket/experimental/autoqasm/autograph/pyct/origin_info.py deleted file mode 100644 index ec64fab3..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/origin_info.py +++ /dev/null @@ -1,296 +0,0 @@ -# Copyright 2018 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Container for origin source code information before AutoGraph compilation.""" -import collections -import difflib -import io -import os -import tokenize - -import gast - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import ast_util -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import pretty_printer -import inspect as tf_inspect - - -class LineLocation( - collections.namedtuple('LineLocation', ('filename', 'lineno'))): - """Similar to Location, but without column information. - - Attributes: - filename: Text - lineno: int, 1-based - """ - pass - - -class Location( - collections.namedtuple('Location', ('filename', 'lineno', 'col_offset'))): - """Encodes code location information. - - Attributes: - filename: Text - lineno: int, 1-based - col_offset: int - line_loc: LineLocation - """ - - @property - def line_loc(self): - return LineLocation(self.filename, self.lineno) - - -class OriginInfo( - collections.namedtuple( - 'OriginInfo', - ('loc', 'function_name', 'source_code_line', 'comment'))): - """Container for information about the source code before conversion. - - Attributes: - loc: Location - function_name: Optional[Text] - source_code_line: Text - comment: Optional[Text] - """ - - def as_frame(self): - """Returns a 4-tuple consistent with the return of traceback.extract_tb.""" - return (self.loc.filename, self.loc.lineno, self.function_name, - self.source_code_line) - - def __repr__(self): - if self.loc.filename: - return '{}:{}:{}'.format( - os.path.split(self.loc.filename)[1], self.loc.lineno, - self.loc.col_offset) - return ':{}:{}'.format(self.loc.lineno, self.loc.col_offset) - - -# TODO(mdan): This source map should be a class - easier to refer to. -def create_source_map(nodes, code, filepath): - """Creates a source map between an annotated AST and the code it compiles to. - - Note: this function assumes nodes nodes, code and filepath correspond to the - same code. - - Args: - nodes: Iterable[ast.AST, ...], one or more AST modes. - code: Text, the source code in which nodes are found. - filepath: Text - - Returns: - Dict[LineLocation, OriginInfo], mapping locations in code to locations - indicated by origin annotations in node. - """ - reparsed_nodes = parser.parse(code, preamble_len=0, single_node=False) - for node in reparsed_nodes: - resolve(node, code, filepath, node.lineno, node.col_offset) - - source_map = {} - - try: - for before, after in ast_util.parallel_walk(nodes, reparsed_nodes): - # Note: generated code might not be mapped back to its origin. - # TODO(mdan): Generated code should always be mapped to something. - origin_info = anno.getanno(before, anno.Basic.ORIGIN, default=None) - final_info = anno.getanno(after, anno.Basic.ORIGIN, default=None) - if origin_info is None or final_info is None: - continue - - # Note: the keys are by line only, excluding the column offset. - line_loc = LineLocation(final_info.loc.filename, final_info.loc.lineno) - - existing_origin = source_map.get(line_loc) - if existing_origin is not None: - # Overlaps may exist because of child nodes, but almost never to - # different line locations. Exception make decorated functions, where - # both lines are mapped to the same line in the AST. - - # Line overlaps: keep bottom node. - if existing_origin.loc.line_loc == origin_info.loc.line_loc: - if existing_origin.loc.lineno >= origin_info.loc.lineno: - continue - - # In case of column overlaps, keep the leftmost node. - if existing_origin.loc.col_offset <= origin_info.loc.col_offset: - continue - - source_map[line_loc] = origin_info - - except ValueError as err: - new_msg = 'Inconsistent ASTs detected. This is a bug. Cause: \n' - new_msg += str(err) - new_msg += 'Diff:\n' - - for n, rn in zip(nodes, reparsed_nodes): - nodes_str = pretty_printer.fmt(n, color=False, noanno=True) - reparsed_nodes_str = pretty_printer.fmt(rn, color=False, noanno=True) - diff = difflib.context_diff( - nodes_str.split('\n'), - reparsed_nodes_str.split('\n'), - fromfile='Original nodes', - tofile='Reparsed nodes', - n=7) - diff = '\n'.join(diff) - new_msg += diff + '\n' - raise ValueError(new_msg) - - return source_map - - -class _Function: - - def __init__(self, name): - self.name = name - - -class OriginResolver(gast.NodeVisitor): - """Annotates an AST with additional source information like file name.""" - - def __init__(self, root_node, source_lines, comments_map, - context_lineno, context_col_offset, - filepath): - self._source_lines = source_lines - self._comments_map = comments_map - - if (hasattr(root_node, 'decorator_list') and root_node.decorator_list and - hasattr(root_node.decorator_list[0], 'lineno')): - # Typical case: functions. The line number of the first decorator - # is more accurate than the line number of the function itself in - # 3.8+. In earier versions they coincide. - self._lineno_offset = context_lineno - root_node.decorator_list[0].lineno - else: - # Fall back to the line number of the root node. - self._lineno_offset = context_lineno - root_node.lineno - - self._col_offset = context_col_offset - root_node.col_offset - - self._filepath = filepath - - self._function_stack = [] - - def _absolute_lineno(self, lineno): - return lineno + self._lineno_offset - - def _absolute_col_offset(self, col_offset): - if col_offset is None: - return 0 - return col_offset + self._col_offset - - def _attach_origin_info(self, node): - lineno = getattr(node, 'lineno', None) - col_offset = getattr(node, 'col_offset', None) - - if lineno is None: - return - - if self._function_stack: - function_name = self._function_stack[-1].name - else: - function_name = None - - source_code_line = self._source_lines[lineno - 1] - comment = self._comments_map.get(lineno) - - loc = Location(self._filepath, self._absolute_lineno(lineno), - self._absolute_col_offset(col_offset)) - origin = OriginInfo(loc, function_name, source_code_line, comment) - anno.setanno(node, 'lineno', lineno) - anno.setanno(node, anno.Basic.ORIGIN, origin) - - def visit(self, node): - entered_function = False - if isinstance(node, gast.FunctionDef): - entered_function = True - self._function_stack.append(_Function(node.name)) - - self._attach_origin_info(node) - self.generic_visit(node) - - if entered_function: - self._function_stack.pop() - - -def resolve(node, source, context_filepath, context_lineno, context_col_offset): - """Adds origin information to an AST, based on the source it was loaded from. - - This allows us to map the original source code line numbers to generated - source code. - - Note: the AST may be a part of a larger context (e.g. a function is part of - a module that may contain other things). However, this function does not - assume the source argument contains the entire context, nor that it contains - only code corresponding to node itself. However, it assumes that node was - parsed from the given source code. - For this reason, two extra arguments are required, and they indicate the - location of the node in the original context. - - Args: - node: gast.AST, the AST to annotate. - source: Text, the source code representing node. - context_filepath: Text - context_lineno: int - context_col_offset: int - """ - # TODO(mdan): Pull this to a separate utility. - code_reader = io.StringIO(source) - comments_map = {} - try: - for token in tokenize.generate_tokens(code_reader.readline): - tok_type, tok_string, loc, _, _ = token - srow, _ = loc - if tok_type == tokenize.COMMENT: - comments_map[srow] = tok_string.strip()[1:].strip() - except tokenize.TokenError: - if isinstance(node, gast.Lambda): - # Source code resolution in older Python versions is brittle for - # lambda functions, and may contain garbage. - pass - else: - raise - - source_lines = source.split('\n') - visitor = OriginResolver(node, source_lines, comments_map, - context_lineno, context_col_offset, - context_filepath) - visitor.visit(node) - - -def resolve_entity(node, source, entity): - """Like resolve, but extracts the context information from an entity.""" - lines, lineno = tf_inspect.getsourcelines(entity) - filepath = tf_inspect.getsourcefile(entity) - - # Poor man's attempt at guessing the column offset: count the leading - # whitespace. This might not work well with tabs. - definition_line = lines[0] - col_offset = len(definition_line) - len(definition_line.lstrip()) - - resolve(node, source, filepath, lineno, col_offset) - - -def copy_origin(from_node, to_node): - """Copies the origin info from a node to another, recursively.""" - origin = anno.Basic.ORIGIN.of(from_node, default=None) - if origin is None: - return - if not isinstance(to_node, (list, tuple)): - to_node = (to_node,) - for node in to_node: - for n in gast.walk(node): - anno.setanno(n, anno.Basic.ORIGIN, origin) diff --git a/src/braket/experimental/autoqasm/autograph/pyct/origin_info_test.py b/src/braket/experimental/autoqasm/autograph/pyct/origin_info_test.py deleted file mode 100644 index c1d32ac6..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/origin_info_test.py +++ /dev/null @@ -1,268 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for origin_info module.""" - -import inspect -import sys -import textwrap - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import inspect_utils -from braket.experimental.autoqasm.autograph.pyct import origin_info -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct.testing import basic_definitions -from tensorflow.python.platform import test -import inspect as tf_inspect - - -class OriginInfoTest(test.TestCase): - - def test_create_source_map(self): - - source = """ - def test_fn(x): - return x + 1 - """ - source = textwrap.dedent(source) - - node = parser.parse(source) - fake_origin = origin_info.OriginInfo( - loc=origin_info.Location('fake_filename', 3, 7), - function_name='fake_function_name', - source_code_line='fake source line', - comment=None) - anno.setanno(node, anno.Basic.ORIGIN, fake_origin) - - source_map = origin_info.create_source_map(node, source, 'test_filename') - - loc = origin_info.LineLocation('test_filename', 2) - self.assertIn(loc, source_map) - self.assertIs(source_map[loc], fake_origin) - - def _create_source_map(self, test_fn): - node, source = parser.parse_entity(test_fn, ()) - origin_info.resolve_entity(node, source, test_fn) - # Creating a source map with the source code as output will create - # an identity map. - return origin_info.create_source_map(node, source, 'test_filename') - - def test_create_source_map_identity(self): - test_fn = basic_definitions.simple_function - source_map = self._create_source_map(test_fn) - module_path = tf_inspect.getsourcefile(test_fn) - - # Origin line numbers below should match those in basic_definitions.py - fn_start = inspect.getsourcelines(test_fn)[1] - - definition_loc = origin_info.LineLocation('test_filename', 1) - self.assertIn(definition_loc, source_map) - self.assertEqual(source_map[definition_loc].loc.lineno, fn_start) - self.assertEqual(source_map[definition_loc].loc.filename, module_path) - self.assertEqual(source_map[definition_loc].function_name, - 'simple_function') - - def test_create_source_map_multiline_call(self): - test_fn = basic_definitions.function_with_multiline_call - source_map = self._create_source_map(test_fn) - module_path = tf_inspect.getsourcefile(test_fn) - - # Origin line numbers below should match those in basic_definitions.py - fn_start = inspect.getsourcelines(test_fn)[1] - - call_loc = origin_info.LineLocation('test_filename', 3) - self.assertIn(call_loc, source_map) - self.assertEqual(source_map[call_loc].loc.lineno, fn_start + 2) - self.assertEqual(source_map[call_loc].loc.filename, module_path) - self.assertEqual(source_map[call_loc].function_name, - 'function_with_multiline_call') - self.assertEqual(source_map[call_loc].source_code_line, ' return range(') - - second_arg_loc = origin_info.LineLocation('test_filename', 5) - self.assertIn(second_arg_loc, source_map) - self.assertEqual(source_map[second_arg_loc].loc.lineno, fn_start + 4) - self.assertEqual(source_map[second_arg_loc].loc.filename, module_path) - self.assertEqual(source_map[second_arg_loc].function_name, - 'function_with_multiline_call') - self.assertEqual(source_map[second_arg_loc].source_code_line, - ' x + 1,') - - def test_create_source_map_no_origin_info(self): - - test_fn = basic_definitions.simple_function - node, _ = parser.parse_entity(test_fn, - inspect_utils.getfutureimports(test_fn)) - # No origin information should result in an empty map. - test_fn_lines, _ = tf_inspect.getsourcelines(test_fn) - source_map = origin_info.create_source_map(node, '\n'.join(test_fn_lines), - test_fn) - - self.assertEmpty(source_map) - - def test_resolve(self): - - source = """ - def test_fn(x): - '''Docstring.''' - return x # comment - """ - source = textwrap.dedent(source) - node = parser.parse(source) - origin_info.resolve(node, source, 'test_file', 10, 10) - - def_origin = anno.getanno(node, anno.Basic.ORIGIN) - self.assertEqual(def_origin.loc.filename, 'test_file') - self.assertEqual(def_origin.loc.lineno, 10) - self.assertEqual(def_origin.loc.col_offset, 10) - self.assertEqual(def_origin.source_code_line, 'def test_fn(x):') - self.assertIsNone(def_origin.comment) - - docstring_origin = anno.getanno(node.body[0], anno.Basic.ORIGIN) - self.assertEqual(def_origin.loc.filename, 'test_file') - self.assertEqual(docstring_origin.loc.lineno, 11) - self.assertEqual(docstring_origin.loc.col_offset, 12) - self.assertEqual(docstring_origin.source_code_line, " '''Docstring.'''") - self.assertIsNone(docstring_origin.comment) - - ret_origin = anno.getanno(node.body[1], anno.Basic.ORIGIN) - self.assertEqual(def_origin.loc.filename, 'test_file') - self.assertEqual(ret_origin.loc.lineno, 12) - self.assertEqual(ret_origin.loc.col_offset, 12) - self.assertEqual(ret_origin.source_code_line, ' return x # comment') - self.assertEqual(ret_origin.comment, 'comment') - - def test_resolve_with_trailing_garbage(self): - # This comment will be missed because the tokenizer fails to reach it. - source = ' lambda: foo([], bar=1)), baz=2)()' - clean_source = 'lambda: foo([], bar=1)' - node = parser.parse(clean_source).value - origin_info.resolve(node, source, 'test_file', 10, 10) - - def_origin = anno.getanno(node, anno.Basic.ORIGIN) - self.assertEqual(def_origin.loc.lineno, 10) - self.assertEqual(def_origin.loc.col_offset, 10) - self.assertEqual(def_origin.source_code_line, source) - self.assertIsNone(def_origin.comment) - - def test_resolve_entity(self): - test_fn = basic_definitions.simple_function - node, source = parser.parse_entity( - test_fn, inspect_utils.getfutureimports(test_fn)) - origin_info.resolve_entity(node, source, test_fn) - - # The line numbers below should match those in basic_definitions.py - fn_start = inspect.getsourcelines(test_fn)[1] - - def_origin = anno.getanno(node, anno.Basic.ORIGIN) - self.assertEqual(def_origin.loc.lineno, fn_start) - self.assertEqual(def_origin.loc.col_offset, 0) - self.assertEqual(def_origin.source_code_line, 'def simple_function(x):') - self.assertIsNone(def_origin.comment) - - docstring_origin = anno.getanno(node.body[0], anno.Basic.ORIGIN) - self.assertEqual(docstring_origin.loc.lineno, fn_start + 1) - self.assertEqual(docstring_origin.loc.col_offset, 2) - self.assertEqual(docstring_origin.source_code_line, ' """Docstring."""') - self.assertIsNone(docstring_origin.comment) - - ret_origin = anno.getanno(node.body[1], anno.Basic.ORIGIN) - self.assertEqual(ret_origin.loc.lineno, fn_start + 2) - self.assertEqual(ret_origin.loc.col_offset, 2) - self.assertEqual(ret_origin.source_code_line, ' return x # comment') - self.assertEqual(ret_origin.comment, 'comment') - - def test_resolve_entity_nested_function(self): - test_fn = basic_definitions.nested_functions - node, source = parser.parse_entity( - test_fn, inspect_utils.getfutureimports(test_fn)) - origin_info.resolve_entity(node, source, test_fn) - - # The line numbers below should match those in basic_definitions.py - fn_start = inspect.getsourcelines(test_fn)[1] - - inner_def_origin = anno.getanno(node.body[1], anno.Basic.ORIGIN) - self.assertEqual(inner_def_origin.loc.lineno, fn_start + 3) - self.assertEqual(inner_def_origin.loc.col_offset, 2) - self.assertEqual(inner_def_origin.source_code_line, ' def inner_fn(y):') - self.assertIsNone(inner_def_origin.comment) - - inner_ret_origin = anno.getanno(node.body[1].body[0], anno.Basic.ORIGIN) - self.assertEqual(inner_ret_origin.loc.lineno, fn_start + 4) - self.assertEqual(inner_ret_origin.loc.col_offset, 4) - self.assertEqual(inner_ret_origin.source_code_line, ' return y') - self.assertIsNone(inner_ret_origin.comment) - - def test_resolve_entity_indented_block(self): - - test_fn = basic_definitions.SimpleClass.simple_method - node, source = parser.parse_entity(test_fn, - inspect_utils.getfutureimports(test_fn)) - origin_info.resolve_entity(node, source, test_fn) - - # The line numbers below should match those in basic_definitions.py - fn_start = inspect.getsourcelines(test_fn)[1] - - def_origin = anno.getanno(node, anno.Basic.ORIGIN) - self.assertEqual(def_origin.loc.lineno, fn_start) - self.assertEqual(def_origin.loc.col_offset, 2) - self.assertEqual(def_origin.source_code_line, 'def simple_method(self):') - self.assertIsNone(def_origin.comment) - - ret_origin = anno.getanno(node.body[0], anno.Basic.ORIGIN) - self.assertEqual(ret_origin.loc.lineno, fn_start + 1) - self.assertEqual(ret_origin.loc.col_offset, 4) - self.assertEqual(ret_origin.source_code_line, ' return self') - self.assertIsNone(ret_origin.comment) - - def test_resolve_entity_decorated_function(self): - test_fn = basic_definitions.decorated_function - node, source = parser.parse_entity(test_fn, - inspect_utils.getfutureimports(test_fn)) - origin_info.resolve_entity(node, source, test_fn) - - # The line numbers below should match those in basic_definitions.py - fn_start = inspect.getsourcelines(test_fn)[1] - - def_origin = anno.getanno(node, anno.Basic.ORIGIN) - if sys.version_info >= (3, 8): - self.assertEqual(def_origin.loc.lineno, fn_start + 2) - self.assertEqual(def_origin.source_code_line, - 'def decorated_function(x):') - else: - self.assertEqual(def_origin.loc.lineno, fn_start) - self.assertEqual(def_origin.source_code_line, '@basic_decorator') - self.assertEqual(def_origin.loc.col_offset, 0) - self.assertIsNone(def_origin.comment) - - if_origin = anno.getanno(node.body[0], anno.Basic.ORIGIN) - self.assertEqual(if_origin.loc.lineno, fn_start + 3) - self.assertEqual(if_origin.loc.col_offset, 2) - self.assertEqual(if_origin.source_code_line, ' if x > 0:') - self.assertIsNone(if_origin.comment) - - ret1_origin = anno.getanno(node.body[0].body[0], anno.Basic.ORIGIN) - self.assertEqual(ret1_origin.loc.lineno, fn_start + 4) - self.assertEqual(ret1_origin.loc.col_offset, 4) - self.assertEqual(ret1_origin.source_code_line, ' return 1') - self.assertIsNone(ret1_origin.comment) - - ret2_origin = anno.getanno(node.body[1], anno.Basic.ORIGIN) - self.assertEqual(ret2_origin.loc.lineno, fn_start + 5) - self.assertEqual(ret2_origin.loc.col_offset, 2) - self.assertEqual(ret2_origin.source_code_line, ' return 2') - self.assertIsNone(ret2_origin.comment) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/parser.py b/src/braket/experimental/autoqasm/autograph/pyct/parser.py deleted file mode 100644 index 95003553..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/parser.py +++ /dev/null @@ -1,396 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Converting code to AST. - -Adapted from Tangent. -""" - -import ast -import inspect -import io -import linecache -import re -import sys -import textwrap -import tokenize - -import astunparse -import gast - -from braket.experimental.autoqasm.autograph.pyct import errors -from braket.experimental.autoqasm.autograph.pyct import inspect_utils -import inspect as tf_inspect - - -PY2_PREAMBLE = textwrap.dedent(""" -""") -PY3_PREAMBLE = '' -MAX_SIZE = 0 - -if sys.version_info >= (3, 9): - astunparse = ast - -if sys.version_info >= (3,): - STANDARD_PREAMBLE = PY3_PREAMBLE - MAX_SIZE = sys.maxsize -else: - STANDARD_PREAMBLE = PY2_PREAMBLE - MAX_SIZE = sys.maxint - -STANDARD_PREAMBLE_LEN = STANDARD_PREAMBLE.count('__future__') - - -_LEADING_WHITESPACE = re.compile(r'\s*') - - -def _unfold_continuations(code_string): - """Removes any backslash line continuations from the code.""" - return code_string.replace('\\\n', '') - - -def dedent_block(code_string): - """Dedents a code so that its first line starts at row zero.""" - - code_string = _unfold_continuations(code_string) - - token_gen = tokenize.generate_tokens(io.StringIO(code_string).readline) - - block_indentation = None - tokens = [] - try: - for tok in token_gen: - tokens.append(tok) - except tokenize.TokenError: - # Resolution of lambda functions may yield incomplete code, which can - # in turn generate this error. We silently ignore this error because the - # parser may still be able to deal with it. - pass - - for tok in tokens: - tok_type, tok_string, _, _, _ = tok - if tok_type == tokenize.INDENT: - block_indentation = tok_string - block_level = len(block_indentation) - break - elif tok_type not in ( - tokenize.NL, tokenize.NEWLINE, tokenize.STRING, tokenize.COMMENT): - block_indentation = '' - break - - if not block_indentation: - return code_string - - block_level = len(block_indentation) - first_indent_uses_tabs = '\t' in block_indentation - for i, tok in enumerate(tokens): - tok_type, tok_string, _, _, _ = tok - if tok_type == tokenize.INDENT: - if ((' ' in tok_string and first_indent_uses_tabs) - or ('\t' in tok_string and not first_indent_uses_tabs)): - # TODO(mdan): We could attempt to convert tabs to spaces by unix rule. - # See: - # https://docs.python.org/3/reference/lexical_analysis.html#indentation - raise errors.UnsupportedLanguageElementError( - 'code mixing tabs and spaces for indentation is not allowed') - if len(tok_string) >= block_level: - tok_string = tok_string[block_level:] - tokens[i] = (tok_type, tok_string) - - new_code = tokenize.untokenize(tokens) - - # Note: untokenize respects the line structure, but not the whitespace within - # lines. For example, `def foo()` may be untokenized as `def foo ()` - # So instead of using the output of dedent, we match the leading whitespace - # on each line. - dedented_code = [] - for line, new_line in zip(code_string.split('\n'), new_code.split('\n')): - original_indent = re.match(_LEADING_WHITESPACE, line).group() - new_indent = re.match(_LEADING_WHITESPACE, new_line).group() - if len(original_indent) > len(new_indent): - dedented_line = line[len(original_indent) - len(new_indent):] - else: - dedented_line = line - dedented_code.append(dedented_line) - new_code = '\n'.join(dedented_code) - - return new_code - - -def parse_entity(entity, future_features): - """Returns the AST and source code of given entity. - - Args: - entity: Any, Python function/method/class - future_features: Iterable[Text], future features to use (e.g. - 'print_statement'). See - https://docs.python.org/2/reference/simple_stmts.html#future - - Returns: - gast.AST, Text: the parsed AST node; the source code that was parsed to - generate the AST (including any prefixes that this function may have added). - """ - if inspect_utils.islambda(entity): - return _parse_lambda(entity) - - try: - original_source = inspect_utils.getimmediatesource(entity) - except OSError as e: - raise errors.InaccessibleSourceCodeError( - f'Unable to locate the source code of {entity}. Note that functions' - ' defined in certain environments, like the interactive Python shell,' - ' do not expose their source code. If that is the case, you should' - ' define them in a .py source file. If you are certain the code is' - ' graph-compatible, wrap the call using' - f' @tf.autograph.experimental.do_not_convert. Original error: {e}') - - source = dedent_block(original_source) - - future_statements = tuple( - 'from __future__ import {}'.format(name) for name in future_features) - source = '\n'.join(future_statements + (source,)) - - return parse(source, preamble_len=len(future_features)), source - - -def _without_context(node, lines, minl, maxl): - """Returns a clean node and source code without indenting and context.""" - for n in gast.walk(node): - lineno = getattr(n, 'lineno', None) - if lineno is not None: - n.lineno = lineno - minl - end_lineno = getattr(n, 'end_lineno', None) - if end_lineno is not None: - n.end_lineno = end_lineno - minl - - code_lines = lines[minl - 1:maxl] - - # Attempt to clean up surrounding context code. - - end_col_offset = getattr(node, 'end_col_offset', None) - if end_col_offset is not None: - # This is only available in 3.8. - code_lines[-1] = code_lines[-1][:end_col_offset] - - col_offset = getattr(node, 'col_offset', None) - if col_offset is None: - # Older Python: try to find the "lambda" token. This is brittle. - match = re.search(r'(? 0: - return -x - return x - """ - - f = self._eval_code(parser.dedent_block(code), 'f') - self.assertEqual(f(1), -1) - self.assertEqual(f(-1), -1) - - def test_dedent_block_comments_out_of_line(self): - - code = """ - ### - def f(x): -### - if x > 0: - ### - return -x - ### - ### - return x - ### - """ - - f = self._eval_code(parser.dedent_block(code), 'f') - self.assertEqual(f(1), -1) - self.assertEqual(f(-1), -1) - - def test_dedent_block_multiline_string(self): - - code = """ - def f(): - ''' - Docstring. - ''' - return ''' - 1 - 2 - 3''' - """ - - f = self._eval_code(parser.dedent_block(code), 'f') - self.assertEqual(f.__doc__, '\n Docstring.\n ') - self.assertEqual(f(), '\n 1\n 2\n 3') - - def test_dedent_block_multiline_expression(self): - - code = """ - def f(): - return (1, -2, - 3) - """ - - f = self._eval_code(parser.dedent_block(code), 'f') - self.assertEqual(f(), (1, 2, 3)) - - def test_dedent_block_continuation(self): - - code = r""" - def f(): - a = \ - 1 - return a - """ - - f = self._eval_code(parser.dedent_block(code), 'f') - self.assertEqual(f(), 1) - - def test_dedent_block_continuation_in_string(self): - - code = r""" - def f(): - a = "a \ - b" - return a - """ - - f = self._eval_code(parser.dedent_block(code), 'f') - self.assertEqual(f(), 'a b') - - def test_parse_expression(self): - node = parser.parse_expression('a.b') - self.assertEqual('a', node.value.id) - self.assertEqual('b', node.attr) - - def test_unparse(self): - node = gast.If( - test=gast.Constant(1, kind=None), - body=[ - gast.Assign( - targets=[ - gast.Name( - 'a', - ctx=gast.Store(), - annotation=None, - type_comment=None) - ], - value=gast.Name( - 'b', ctx=gast.Load(), annotation=None, type_comment=None)) - ], - orelse=[ - gast.Assign( - targets=[ - gast.Name( - 'a', - ctx=gast.Store(), - annotation=None, - type_comment=None) - ], - value=gast.Constant('c', kind=None)) - ]) - - source = parser.unparse(node, indentation=' ') - self.assertEqual( - textwrap.dedent(""" - # coding=utf-8 - if 1: - a = b - else: - a = 'c' - """).strip(), source.strip()) - - def test_ext_slice_roundtrip(self): - def ext_slice(n): - return n[:, :], n[0, :], n[:, 0] - - node, _ = parser.parse_entity(ext_slice, future_features=()) - source = parser.unparse(node) - self.assertAstMatches(node, source, expr=False) - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/pretty_printer.py b/src/braket/experimental/autoqasm/autograph/pyct/pretty_printer.py deleted file mode 100644 index 184355ab..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/pretty_printer.py +++ /dev/null @@ -1,130 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Print an AST tree in a form more readable than ast.dump.""" - -import gast -import termcolor - - -class PrettyPrinter(gast.NodeVisitor): - """Print AST nodes.""" - - def __init__(self, color, noanno): - self.indent_lvl = 0 - self.result = '' - self.color = color - self.noanno = noanno - - def _color(self, string, color, attrs=None): - if self.color: - return termcolor.colored(string, color, attrs=attrs) - return string - - def _type(self, node): - return self._color(node.__class__.__name__, None, ['bold']) - - def _field(self, name): - return self._color(name, 'blue') - - def _value(self, name): - return self._color(name, 'magenta') - - def _warning(self, name): - return self._color(name, 'red') - - def _indent(self): - return self._color('| ' * self.indent_lvl, None, ['dark']) - - def _print(self, s): - self.result += s - self.result += '\n' - - def generic_visit(self, node, name=None): - # In very rare instances, a list can contain something other than a Node. - # e.g. Global contains a list of strings. - if isinstance(node, str): - if name: - self._print('%s%s="%s"' % (self._indent(), name, node)) - else: - self._print('%s"%s"' % (self._indent(), node)) - return - - if node._fields: - cont = ':' - else: - cont = '()' - - if name: - self._print('%s%s=%s%s' % (self._indent(), self._field(name), - self._type(node), cont)) - else: - self._print('%s%s%s' % (self._indent(), self._type(node), cont)) - - self.indent_lvl += 1 - for f in node._fields: - if self.noanno and f.startswith('__'): - continue - if not hasattr(node, f): - self._print('%s%s' % (self._indent(), self._warning('%s=' % f))) - continue - v = getattr(node, f) - if isinstance(v, list): - if v: - self._print('%s%s=[' % (self._indent(), self._field(f))) - self.indent_lvl += 1 - for n in v: - if n is not None: - self.generic_visit(n) - else: - self._print('%sNone' % (self._indent())) - self.indent_lvl -= 1 - self._print('%s]' % (self._indent())) - else: - self._print('%s%s=[]' % (self._indent(), self._field(f))) - elif isinstance(v, tuple): - if v: - self._print('%s%s=(' % (self._indent(), self._field(f))) - self.indent_lvl += 1 - for n in v: - if n is not None: - self.generic_visit(n) - else: - self._print('%sNone' % (self._indent())) - self.indent_lvl -= 1 - self._print('%s)' % (self._indent())) - else: - self._print('%s%s=()' % (self._indent(), self._field(f))) - elif isinstance(v, gast.AST): - self.generic_visit(v, f) - elif isinstance(v, bytes): - self._print('%s%s=%s' % (self._indent(), self._field(f), - self._value('b"%s"' % v))) - elif isinstance(v, str): - self._print('%s%s=%s' % (self._indent(), self._field(f), - self._value('u"%s"' % v))) - else: - self._print('%s%s=%s' % (self._indent(), self._field(f), - self._value(v))) - self.indent_lvl -= 1 - - -def fmt(node, color=True, noanno=False): - printer = PrettyPrinter(color, noanno) - if isinstance(node, (list, tuple)): - for n in node: - printer.visit(n) - else: - printer.visit(node) - return printer.result diff --git a/src/braket/experimental/autoqasm/autograph/pyct/pretty_printer_test.py b/src/braket/experimental/autoqasm/autograph/pyct/pretty_printer_test.py deleted file mode 100644 index 952b654a..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/pretty_printer_test.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for pretty_printer module.""" - -import ast -import textwrap - -from braket.experimental.autoqasm.autograph.pyct import pretty_printer -from tensorflow.python.platform import test - - -class PrettyPrinterTest(test.TestCase): - - def test_unicode_bytes(self): - source = textwrap.dedent(''' - def f(): - return b'b', u'u', 'depends_py2_py3' - ''') - node = ast.parse(source) - self.assertIsNotNone(pretty_printer.fmt(node)) - - def test_format(self): - node = ast.FunctionDef( - name='f', - args=ast.arguments( - args=[ast.Name(id='a', ctx=ast.Param())], - vararg=None, - kwarg=None, - defaults=[]), - body=[ - ast.Return( - ast.BinOp( - op=ast.Add(), - left=ast.Name(id='a', ctx=ast.Load()), - right=ast.Num(1))) - ], - decorator_list=[], - returns=None) - # Just checking for functionality, the color control characters make it - # difficult to inspect the result. - self.assertIsNotNone(pretty_printer.fmt(node)) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/qual_names.py b/src/braket/experimental/autoqasm/autograph/pyct/qual_names.py deleted file mode 100644 index c9562698..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/qual_names.py +++ /dev/null @@ -1,266 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Utilities for manipulating qualified names. - -A qualified name is a uniform way to refer to simple (e.g. 'foo') and composite -(e.g. 'foo.bar') syntactic symbols. - -This is *not* related to the __qualname__ attribute used by inspect, which -refers to scopes. -""" - -import collections - -import gast - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import parser - - -class CallerMustSetThis(object): - pass - - -class Symbol(collections.namedtuple('Symbol', ['name'])): - """Represents a Python symbol.""" - - -class Literal(collections.namedtuple('Literal', ['value'])): - """Represents a Python numeric literal.""" - - def __str__(self): - if isinstance(self.value, str): - return "'{}'".format(self.value) - return str(self.value) - - def __repr__(self): - return str(self) - - -# TODO(mdan): Use subclasses to remove the has_attr has_subscript booleans. -class QN(object): - """Represents a qualified name.""" - - def __init__(self, base, attr=None, subscript=None): - if attr is not None and subscript is not None: - raise ValueError('A QN can only be either an attr or a subscript, not ' - 'both: attr={}, subscript={}.'.format(attr, subscript)) - self._has_attr = False - self._has_subscript = False - - if attr is not None: - if not isinstance(base, QN): - raise ValueError( - 'for attribute QNs, base must be a QN; got instead "%s"' % base) - if not isinstance(attr, str): - raise ValueError('attr may only be a string; got instead "%s"' % attr) - self._parent = base - # TODO(mdan): Get rid of the tuple - it can only have 1 or 2 elements now. - self.qn = (base, attr) - self._has_attr = True - - elif subscript is not None: - if not isinstance(base, QN): - raise ValueError('For subscript QNs, base must be a QN.') - self._parent = base - self.qn = (base, subscript) - self._has_subscript = True - - else: - if not isinstance(base, (str, Literal)): - # TODO(mdan): Require Symbol instead of string. - raise ValueError( - 'for simple QNs, base must be a string or a Literal object;' - ' got instead "%s"' % type(base)) - assert '.' not in base and '[' not in base and ']' not in base - self._parent = None - self.qn = (base,) - - def is_symbol(self): - return isinstance(self.qn[0], str) - - def is_simple(self): - return len(self.qn) <= 1 - - def is_composite(self): - return len(self.qn) > 1 - - def has_subscript(self): - return self._has_subscript - - def has_attr(self): - return self._has_attr - - @property - def attr(self): - if not self._has_attr: - raise ValueError('Cannot get attr of non-attribute "%s".' % self) - return self.qn[1] - - @property - def parent(self): - if self._parent is None: - raise ValueError('Cannot get parent of simple name "%s".' % self.qn[0]) - return self._parent - - @property - def owner_set(self): - """Returns all the symbols (simple or composite) that own this QN. - - In other words, if this symbol was modified, the symbols in the owner set - may also be affected. - - Examples: - 'a.b[c.d]' has two owners, 'a' and 'a.b' - """ - owners = set() - if self.has_attr() or self.has_subscript(): - owners.add(self.parent) - owners.update(self.parent.owner_set) - return owners - - @property - def support_set(self): - """Returns the set of simple symbols that this QN relies on. - - This would be the smallest set of symbols necessary for the QN to - statically resolve (assuming properties and index ranges are verified - at runtime). - - Examples: - 'a.b' has only one support symbol, 'a' - 'a[i]' has two support symbols, 'a' and 'i' - """ - # TODO(mdan): This might be the set of Name nodes in the AST. Track those? - roots = set() - if self.has_attr(): - roots.update(self.parent.support_set) - elif self.has_subscript(): - roots.update(self.parent.support_set) - roots.update(self.qn[1].support_set) - else: - roots.add(self) - return roots - - def __hash__(self): - return hash(self.qn + (self._has_attr, self._has_subscript)) - - def __eq__(self, other): - return (isinstance(other, QN) and self.qn == other.qn and - self.has_subscript() == other.has_subscript() and - self.has_attr() == other.has_attr()) - - def __lt__(self, other): - return str(self) < str(other) - - def __gt__(self, other): - return str(self) > str(other) - - def __str__(self): - root = self.qn[0] - if self.has_subscript(): - return '{}[{}]'.format(root, self.qn[1]) - if self.has_attr(): - return '.'.join(map(str, self.qn)) - else: - return str(root) - - def __repr__(self): - return str(self) - - def ssf(self): - """Simple symbol form.""" - ssfs = [n.ssf() if isinstance(n, QN) else n for n in self.qn] - ssf_string = '' - for i in range(0, len(self.qn) - 1): - if self.has_subscript(): - delimiter = '_sub_' - else: - delimiter = '_' - ssf_string += ssfs[i] + delimiter - return ssf_string + ssfs[-1] - - def ast(self): - """AST representation.""" - # The caller must adjust the context appropriately. - if self.has_subscript(): - return gast.Subscript( - value=self.parent.ast(), - slice=self.qn[-1].ast(), - ctx=CallerMustSetThis) - if self.has_attr(): - return gast.Attribute( - value=self.parent.ast(), attr=self.qn[-1], ctx=CallerMustSetThis) - - base = self.qn[0] - if isinstance(base, str): - return gast.Name( - base, ctx=CallerMustSetThis, annotation=None, type_comment=None) - elif isinstance(base, Literal): - return gast.Constant(base.value, kind=None) - else: - assert False, ('the constructor should prevent types other than ' - 'str and Literal') - - -class QnResolver(gast.NodeTransformer): - """Annotates nodes with QN information. - - Note: Not using NodeAnnos to avoid circular dependencies. - """ - - def visit_Name(self, node): - node = self.generic_visit(node) - anno.setanno(node, anno.Basic.QN, QN(node.id)) - return node - - def visit_Attribute(self, node): - node = self.generic_visit(node) - if anno.hasanno(node.value, anno.Basic.QN): - anno.setanno(node, anno.Basic.QN, - QN(anno.getanno(node.value, anno.Basic.QN), attr=node.attr)) - return node - - def visit_Subscript(self, node): - # TODO(mdan): This may no longer apply if we overload getitem. - node = self.generic_visit(node) - s = node.slice - if isinstance(s, (gast.Tuple, gast.Slice)): - # TODO(mdan): Support range and multi-dimensional indices. - # Continuing silently because some demos use these. - return node - if isinstance(s, gast.Constant) and s.value != Ellipsis: - subscript = QN(Literal(s.value)) - else: - # The index may be an expression, case in which a name doesn't make sense. - if anno.hasanno(s, anno.Basic.QN): - subscript = anno.getanno(s, anno.Basic.QN) - else: - return node - if anno.hasanno(node.value, anno.Basic.QN): - anno.setanno(node, anno.Basic.QN, - QN(anno.getanno(node.value, anno.Basic.QN), - subscript=subscript)) - return node - - -def resolve(node): - return QnResolver().visit(node) - - -def from_str(qn_str): - node = parser.parse_expression(qn_str) - node = resolve(node) - return anno.getanno(node, anno.Basic.QN) diff --git a/src/braket/experimental/autoqasm/autograph/pyct/qual_names_test.py b/src/braket/experimental/autoqasm/autograph/pyct/qual_names_test.py deleted file mode 100644 index 2afff261..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/qual_names_test.py +++ /dev/null @@ -1,261 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for qual_names module.""" - -import textwrap - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import qual_names -from braket.experimental.autoqasm.autograph.pyct.qual_names import QN -from braket.experimental.autoqasm.autograph.pyct.qual_names import resolve -from tensorflow.python.platform import test - - -class QNTest(test.TestCase): - - def test_from_str(self): - a = QN('a') - b = QN('b') - a_dot_b = QN(a, attr='b') - a_sub_b = QN(a, subscript=b) - self.assertEqual(qual_names.from_str('a.b'), a_dot_b) - self.assertEqual(qual_names.from_str('a'), a) - self.assertEqual(qual_names.from_str('a[b]'), a_sub_b) - - def test_basic(self): - a = QN('a') - self.assertEqual(a.qn, ('a',)) - self.assertEqual(str(a), 'a') - self.assertEqual(a.ssf(), 'a') - self.assertEqual(a.ast().id, 'a') - self.assertFalse(a.is_composite()) - with self.assertRaises(ValueError): - _ = a.parent - - a_b = QN(a, attr='b') - self.assertEqual(a_b.qn, (a, 'b')) - self.assertEqual(str(a_b), 'a.b') - self.assertEqual(a_b.ssf(), 'a_b') - self.assertEqual(a_b.ast().value.id, 'a') - self.assertEqual(a_b.ast().attr, 'b') - self.assertTrue(a_b.is_composite()) - self.assertEqual(a_b.parent.qn, ('a',)) - - def test_subscripts(self): - a = QN('a') - b = QN('b') - a_sub_b = QN(a, subscript=b) - self.assertEqual(a_sub_b.qn, (a, b)) - self.assertEqual(str(a_sub_b), 'a[b]') - self.assertEqual(a_sub_b.ssf(), 'a_sub_b') - self.assertEqual(a_sub_b.ast().value.id, 'a') - self.assertEqual(a_sub_b.ast().slice.id, 'b') - self.assertTrue(a_sub_b.is_composite()) - self.assertTrue(a_sub_b.has_subscript()) - self.assertEqual(a_sub_b.parent.qn, ('a',)) - - c = QN('c') - b_sub_c = QN(b, subscript=c) - a_sub_b_sub_c = QN(a, subscript=b_sub_c) - self.assertEqual(a_sub_b_sub_c.qn, (a, b_sub_c)) - self.assertTrue(a_sub_b_sub_c.is_composite()) - self.assertTrue(a_sub_b_sub_c.has_subscript()) - self.assertEqual(b_sub_c.qn, (b, c)) - self.assertEqual(str(a_sub_b_sub_c), 'a[b[c]]') - self.assertEqual(a_sub_b_sub_c.ssf(), 'a_sub_b_sub_c') - self.assertEqual(a_sub_b_sub_c.ast().value.id, 'a') - self.assertEqual(a_sub_b_sub_c.ast().slice.value.id, 'b') - self.assertEqual(a_sub_b_sub_c.ast().slice.slice.id, 'c') - self.assertEqual(b_sub_c.ast().slice.id, 'c') - self.assertEqual(a_sub_b_sub_c.parent.qn, ('a',)) - with self.assertRaises(ValueError): - QN('a', 'b') - - def test_equality(self): - a = QN('a') - a2 = QN('a') - a_b = QN(a, attr='b') - self.assertEqual(a2.qn, ('a',)) - with self.assertRaises(ValueError): - _ = a.parent - - a_b2 = QN(a, attr='b') - self.assertEqual(a_b2.qn, (a, 'b')) - self.assertEqual(a_b2.parent.qn, ('a',)) - - self.assertTrue(a2 == a) - self.assertFalse(a2 is a) - - self.assertTrue(a_b.parent == a) - self.assertTrue(a_b2.parent == a) - - self.assertTrue(a_b2 == a_b) - self.assertFalse(a_b2 is a_b) - self.assertFalse(a_b2 == a) - a_sub_b = QN(a, subscript='b') - a_sub_b2 = QN(a, subscript='b') - self.assertTrue(a_sub_b == a_sub_b2) - self.assertFalse(a_sub_b == a_b) - - def test_nested_attrs_subscripts(self): - a = QN('a') - b = QN('b') - c = QN('c') - b_sub_c = QN(b, subscript=c) - a_sub_b_sub_c = QN(a, subscript=b_sub_c) - - b_dot_c = QN(b, attr='c') - a_sub__b_dot_c = QN(a, subscript=b_dot_c) - - a_sub_b = QN(a, subscript=b) - a_sub_b__dot_c = QN(a_sub_b, attr='c') - - a_dot_b = QN(a, attr='b') - a_dot_b_sub_c = QN(a_dot_b, subscript=c) - - self.assertEqual(str(a_sub_b_sub_c), 'a[b[c]]') - self.assertEqual(str(a_sub__b_dot_c), 'a[b.c]') - self.assertEqual(str(a_sub_b__dot_c), 'a[b].c') - self.assertEqual(str(a_dot_b_sub_c), 'a.b[c]') - - self.assertNotEqual(a_sub_b_sub_c, a_sub__b_dot_c) - self.assertNotEqual(a_sub_b_sub_c, a_sub_b__dot_c) - self.assertNotEqual(a_sub_b_sub_c, a_dot_b_sub_c) - - self.assertNotEqual(a_sub__b_dot_c, a_sub_b__dot_c) - self.assertNotEqual(a_sub__b_dot_c, a_dot_b_sub_c) - - self.assertNotEqual(a_sub_b__dot_c, a_dot_b_sub_c) - - def test_hashable(self): - d = {QN('a'): 'a', QN('b'): 'b'} - self.assertEqual(d[QN('a')], 'a') - self.assertEqual(d[QN('b')], 'b') - self.assertNotIn(QN('c'), d) - - def test_literals(self): - a = QN('a') - a_sub_str_b = QN(a, subscript=QN(qual_names.Literal('b'))) - a_sub_b = QN(a, subscript=QN('b')) - - self.assertNotEqual(a_sub_str_b, a_sub_b) - self.assertNotEqual(hash(a_sub_str_b), hash(a_sub_b)) - self.assertEqual(a_sub_str_b.ast().slice.value, 'b') - self.assertEqual(str(a_sub_str_b), "a['b']") - - a_sub_three = QN(a, subscript=QN(qual_names.Literal(3))) - self.assertEqual(a_sub_three.ast().slice.value, 3) - self.assertEqual(str(a_sub_three), 'a[3]') - - def test_support_set(self): - a = QN('a') - b = QN('b') - c = QN('c') - a_sub_b = QN(a, subscript=b) - a_dot_b = QN(a, attr='b') - a_dot_b_dot_c = QN(a_dot_b, attr='c') - a_dot_b_sub_c = QN(a_dot_b, subscript=c) - - self.assertSetEqual(a.support_set, set((a,))) - self.assertSetEqual(a_sub_b.support_set, set((a, b))) - self.assertSetEqual(a_dot_b.support_set, set((a,))) - self.assertSetEqual(a_dot_b_dot_c.support_set, set((a,))) - self.assertSetEqual(a_dot_b_sub_c.support_set, set((a, c))) - - def test_comparison(self): - less_than_apos = chr(ord('\'') - 1) - - self.assertGreater(QN('z'), QN(qual_names.Literal('a'))) - self.assertLess(QN(less_than_apos), QN(qual_names.Literal('a'))) - - self.assertGreater(QN(qual_names.Literal('z')), QN(less_than_apos)) - self.assertLess(QN(qual_names.Literal('a')), QN('z')) - - -class QNResolverTest(test.TestCase): - - def assertQNStringIs(self, node, qn_str): - self.assertEqual(str(anno.getanno(node, anno.Basic.QN)), qn_str) - - def test_resolve(self): - samples = """ - a - a.b - (c, d.e) - [f, (g.h.i)] - j(k, l) - """ - nodes = parser.parse(textwrap.dedent(samples), single_node=False) - nodes = tuple(resolve(node).value for node in nodes) - - self.assertQNStringIs(nodes[0], 'a') - self.assertQNStringIs(nodes[1], 'a.b') - self.assertQNStringIs(nodes[2].elts[0], 'c') - self.assertQNStringIs(nodes[2].elts[1], 'd.e') - self.assertQNStringIs(nodes[3].elts[0], 'f') - self.assertQNStringIs(nodes[3].elts[1], 'g.h.i') - self.assertQNStringIs(nodes[4].func, 'j') - self.assertQNStringIs(nodes[4].args[0], 'k') - self.assertQNStringIs(nodes[4].args[1], 'l') - - def test_subscript_resolve(self): - samples = """ - x[i] - x[i.b] - a.b[c] - a.b[x.y] - a[z[c]] - a[b[c[d]]] - a[b].c - a.b.c[d].e.f - a.b[c[d]].e.f - a.b[c[d.e.f].g].h - """ - nodes = parser.parse(textwrap.dedent(samples), single_node=False) - nodes = tuple(resolve(node).value for node in nodes) - - self.assertQNStringIs(nodes[0], 'x[i]') - self.assertQNStringIs(nodes[1], 'x[i.b]') - self.assertQNStringIs(nodes[2], 'a.b[c]') - self.assertQNStringIs(nodes[3], 'a.b[x.y]') - self.assertQNStringIs(nodes[4], 'a[z[c]]') - self.assertQNStringIs(nodes[5], 'a[b[c[d]]]') - self.assertQNStringIs(nodes[6], 'a[b].c') - self.assertQNStringIs(nodes[7], 'a.b.c[d].e.f') - self.assertQNStringIs(nodes[8], 'a.b[c[d]].e.f') - self.assertQNStringIs(nodes[9], 'a.b[c[d.e.f].g].h') - - def test_function_calls(self): - samples = """ - a.b - a.b() - a().b - z[i] - z[i]() - z()[i] - """ - nodes = parser.parse(textwrap.dedent(samples), single_node=False) - nodes = tuple(resolve(node).value for node in nodes) - self.assertQNStringIs(nodes[0], 'a.b') - self.assertQNStringIs(nodes[1].func, 'a.b') - self.assertQNStringIs(nodes[2].value.func, 'a') - self.assertQNStringIs(nodes[3], 'z[i]') - self.assertQNStringIs(nodes[4].func, 'z[i]') - self.assertQNStringIs(nodes[5].value.func, 'z') - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/__init__.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/__init__.py deleted file mode 100644 index 9ceaeaa8..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Static information resolution. - -This module contains utilities to help annotate AST nodes with as much runtime -information as can be possibly extracted without actually executing the code, -under that assumption that the context in which the code will run is known. - -Overall, the different analyses have the functions listed below: - - * activity: inventories symbols read, written to, params, etc. at different - levels - * liveness, reaching_definitions: dataflow analyses based on the program's CFG - and using the symbol information gathered by activity analysis -""" - diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/activity.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/activity.py deleted file mode 100644 index 81ca5152..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/activity.py +++ /dev/null @@ -1,706 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Activity analysis. - -Requires qualified name annotations (see qual_names.py). -""" - -import copy -import weakref - -import gast - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import qual_names -from braket.experimental.autoqasm.autograph.pyct import transformer -from braket.experimental.autoqasm.autograph.pyct.static_analysis.annos import NodeAnno - - -class Scope(object): - """Encloses local symbol definition and usage information. - - This can track for instance whether a symbol is modified in the current scope. - Note that scopes do not necessarily align with Python's scopes. For example, - the body of an if statement may be considered a separate scope. - - Caution - the AST references held by this object are weak. - - Scope objects are mutable during construction only, and must be frozen using - `Scope.finalize()` before use. Furthermore, a scope is consistent only after - all its children have been frozen. While analysing code blocks, scopes are - being gradually built, from the innermost scope outward. Freezing indicates - that the analysis of a code block is complete. Once frozen, mutation is no - longer allowed. `is_final` tracks whether the scope is frozen or not. Certain - properties, like `referenced`, are only accurate when called on frozen scopes. - - Attributes: - parent: Optional[Scope], the parent scope, if any. - isolated: bool, whether the scope is a true Python scope (e.g. the scope of - a function), or just a surrogate tracking an ordinary code block. Using - the terminology of the Python 3 reference documentation, True roughly - represents an actual scope, whereas False represents an ordinary code - block. - function_name: Optional[str], name of the function owning this scope. - isolated_names: Set[qual_names.QN], identifiers that are isolated to this - scope (even if the scope is not isolated). - annotations: Set[qual_names.QN], identifiers used as type annotations - in this scope. - read: Set[qual_names.QN], identifiers read in this scope. - modified: Set[qual_names.QN], identifiers modified in this scope. - deleted: Set[qual_names.QN], identifiers deleted in this scope. - bound: Set[qual_names.QN], names that are bound to this scope. See - https://docs.python.org/3/reference/executionmodel.html#binding-of-names - for a precise definition. - globals: Set[qual_names.QN], names that are explicitly marked as global in - this scope. Note that this doesn't include free read-only vars bound to - global symbols. - nonlocals: Set[qual_names.QN], names that are explicitly marked as nonlocal - in this scope. Note that this doesn't include free read-only vars bound to - global symbols. - free_vars: Set[qual_names.QN], the free variables in this scope. See - https://docs.python.org/3/reference/executionmodel.html for a precise - definition. - params: WeakValueDictionary[qual_names.QN, ast.Node], function arguments - visible in this scope, mapped to the function node that defines them. - enclosing_scope: Scope, the innermost isolated scope that is a transitive - parent of this scope. May be the scope itself. - referenced: Set[qual_names.QN], the totality of the symbols used by this - scope and its parents. - is_final: bool, whether the scope is frozen or not. - - Note - simple statements may never delete and modify a symbol at the same - time. However, compound ones like if statements can. In that latter case, it's - undefined whether the symbol is actually modified or deleted upon statement - exit. Certain analyses like reaching definitions need to be careful about - this. - """ - - # Note: this mutable-immutable pattern is used because using a builder would - # have taken a lot more boilerplate. - - def __init__(self, parent, isolated=True, function_name=None): - """Create a new scope. - - Args: - parent: A Scope or None. - isolated: Whether the scope is isolated, that is, whether variables - modified in this scope should be considered modified in the parent - scope. - function_name: Name of the function owning this scope. - """ - self.parent = parent - self.isolated = isolated - self.function_name = function_name - - self.isolated_names = set() - - self.read = set() - self.modified = set() - self.deleted = set() - - self.bound = set() - self.globals = set() - self.nonlocals = set() - self.annotations = set() - - self.params = weakref.WeakValueDictionary() - - # Certain fields can only be accessed after the scope and all its parent - # scopes have been fully built. This field guards that. - self.is_final = False - - @property - def enclosing_scope(self): - assert self.is_final - if self.parent is not None and not self.isolated: - return self.parent - return self - - @property - def referenced(self): - if self.parent is not None: - return self.read | self.parent.referenced - return self.read - - @property - def free_vars(self): - enclosing_scope = self.enclosing_scope - return enclosing_scope.read - enclosing_scope.bound - - def copy_from(self, other): - """Recursively copies the contents of this scope from another scope.""" - assert not self.is_final - if self.parent is not None: - assert other.parent is not None - self.parent.copy_from(other.parent) - self.isolated_names = copy.copy(other.isolated_names) - self.modified = copy.copy(other.modified) - self.read = copy.copy(other.read) - self.deleted = copy.copy(other.deleted) - self.bound = copy.copy(other.bound) - self.annotations = copy.copy(other.annotations) - self.params = copy.copy(other.params) - - @classmethod - def copy_of(cls, other): - if other.parent is not None: - assert other.parent is not None - parent = cls.copy_of(other.parent) - else: - parent = None - new_copy = cls(parent) - new_copy.copy_from(other) - return new_copy - - def merge_from(self, other): - """Adds all activity from another scope to this scope.""" - assert not self.is_final - if self.parent is not None: - assert other.parent is not None - self.parent.merge_from(other.parent) - self.isolated_names.update(other.isolated_names) - self.read.update(other.read) - self.modified.update(other.modified) - self.bound.update(other.bound) - self.deleted.update(other.deleted) - self.annotations.update(other.annotations) - self.params.update(other.params) - - def finalize(self): - """Freezes this scope.""" - assert not self.is_final - # TODO(mdan): freeze read, modified, bound. - if self.parent is not None: - assert not self.parent.is_final - if not self.isolated: - self.parent.read.update(self.read - self.isolated_names) - self.parent.modified.update(self.modified - self.isolated_names) - self.parent.bound.update(self.bound - self.isolated_names) - self.parent.globals.update(self.globals) - self.parent.nonlocals.update(self.nonlocals) - self.parent.annotations.update(self.annotations) - else: - # TODO(mdan): This is not accurate. - self.parent.read.update(self.read - self.bound) - self.parent.annotations.update(self.annotations - self.bound) - self.is_final = True - - def __repr__(self): - return 'Scope{r=%s, w=%s}' % (tuple(self.read), tuple(self.modified)) - - def mark_param(self, name, owner): - # Assumption: all AST nodes have the same life span. This lets us use - # a weak reference to mark the connection between a symbol node and the - # function node whose argument that symbol is. - self.params[name] = owner - - -class _Comprehension(object): - - no_root = True - - def __init__(self): - # TODO(mdan): Consider using an enum. - self.is_list_comp = False - self.targets = set() - - -class _FunctionOrClass(object): - - def __init__(self): - self.node = None - - -class ActivityAnalyzer(transformer.Base): - """Annotates nodes with local scope information. - - See Scope. - - The use of this class requires that qual_names.resolve() has been called on - the node. This class will ignore nodes have not been - annotated with their qualified names. - """ - - def __init__(self, context, parent_scope=None): - super(ActivityAnalyzer, self).__init__(context) - self.allow_skips = False - self.scope = Scope(parent_scope, isolated=True) - - # Note: all these flags crucially rely on the respective nodes are - # leaves in the AST, that is, they cannot contain other statements. - self._in_aug_assign = False - self._in_annotation = False - self._track_annotations_only = False - - @property - def _in_constructor(self): - context = self.state[_FunctionOrClass] - if context.level > 2: - innermost = context.stack[-1].node - parent = context.stack[-2].node - return (isinstance(parent, gast.ClassDef) and - (isinstance(innermost, gast.FunctionDef) and - innermost.name == '__init__')) - return False - - def _node_sets_self_attribute(self, node): - if anno.hasanno(node, anno.Basic.QN): - qn = anno.getanno(node, anno.Basic.QN) - # TODO(mdan): The 'self' argument is not guaranteed to be called 'self'. - if qn.has_attr and qn.parent.qn == ('self',): - return True - return False - - def _track_symbol(self, node, composite_writes_alter_parent=False): - if self._track_annotations_only and not self._in_annotation: - return - - # A QN may be missing when we have an attribute (or subscript) on a function - # call. Example: a().b - if not anno.hasanno(node, anno.Basic.QN): - return - qn = anno.getanno(node, anno.Basic.QN) - - # When inside a comprehension, ignore reads to any of the comprehensions's - # targets. This includes attributes or slices of those arguments. - for l in self.state[_Comprehension]: - if qn in l.targets: - return - if qn.owner_set & set(l.targets): - return - - if isinstance(node.ctx, gast.Store): - # In comprehensions, modified symbols are the comprehension targets. - if self.state[_Comprehension].level > 0: - self.state[_Comprehension].targets.add(qn) - return - - self.scope.modified.add(qn) - self.scope.bound.add(qn) - if qn.is_composite and composite_writes_alter_parent: - self.scope.modified.add(qn.parent) - if self._in_aug_assign: - self.scope.read.add(qn) - - elif isinstance(node.ctx, gast.Load): - self.scope.read.add(qn) - if self._in_annotation: - self.scope.annotations.add(qn) - - elif isinstance(node.ctx, gast.Param): - self.scope.bound.add(qn) - self.scope.mark_param(qn, self.state[_FunctionOrClass].node) - - elif isinstance(node.ctx, gast.Del): - # The read matches the Python semantics - attempting to delete an - # undefined symbol is illegal. - self.scope.read.add(qn) - # Targets of del are considered bound: - # https://docs.python.org/3/reference/executionmodel.html#binding-of-names - self.scope.bound.add(qn) - self.scope.deleted.add(qn) - - else: - raise ValueError('Unknown context {} for node "{}".'.format( - type(node.ctx), qn)) - - def _enter_scope(self, isolated, f_name=None): - self.scope = Scope(self.scope, isolated=isolated, function_name=f_name) - - def _exit_scope(self): - exited_scope = self.scope - exited_scope.finalize() - self.scope = exited_scope.parent - return exited_scope - - def _exit_and_record_scope(self, node, tag=anno.Static.SCOPE): - node_scope = self._exit_scope() - anno.setanno(node, tag, node_scope) - return node_scope - - def _process_statement(self, node): - self._enter_scope(False) - node = self.generic_visit(node) - self._exit_and_record_scope(node) - return node - - def _process_annotation(self, node): - self._in_annotation = True - node = self.visit(node) - self._in_annotation = False - return node - - def visit_Import(self, node): - return self._process_statement(node) - - def visit_ImportFrom(self, node): - return self._process_statement(node) - - def visit_Global(self, node): - self._enter_scope(False) - for name in node.names: - qn = qual_names.QN(name) - self.scope.read.add(qn) - self.scope.globals.add(qn) - self._exit_and_record_scope(node) - return node - - def visit_Nonlocal(self, node): - self._enter_scope(False) - for name in node.names: - qn = qual_names.QN(name) - self.scope.read.add(qn) - self.scope.bound.add(qn) - self.scope.nonlocals.add(qn) - self._exit_and_record_scope(node) - return node - - def visit_Expr(self, node): - return self._process_statement(node) - - def visit_Raise(self, node): - return self._process_statement(node) - - def visit_Return(self, node): - return self._process_statement(node) - - def visit_Assign(self, node): - return self._process_statement(node) - - def visit_AnnAssign(self, node): - self._enter_scope(False) - node.target = self.visit(node.target) - if node.value is not None: - # Can be None for pure declarations, e.g. `n: int`. This is a new thing - # enabled by type annotations, but does not influence static analysis - # (declarations are not definitions). - node.value = self.visit(node.value) - if node.annotation: - node.annotation = self._process_annotation(node.annotation) - self._exit_and_record_scope(node) - return node - - def visit_AugAssign(self, node): - # Special rules for AugAssign. Here, the AST only shows the target as - # written, when it is in fact also read. - self._enter_scope(False) - - self._in_aug_assign = True - node.target = self.visit(node.target) - self._in_aug_assign = False - - node.op = self.visit(node.op) - node.value = self.visit(node.value) - self._exit_and_record_scope(node) - return node - - def visit_Delete(self, node): - return self._process_statement(node) - - def visit_Name(self, node): - if node.annotation: - node.annotation = self._process_annotation(node.annotation) - self._track_symbol(node) - return node - - def visit_alias(self, node): - node = self.generic_visit(node) - - if node.asname is None: - # Only the root name is a real symbol operation. - qn = qual_names.QN(node.name.split('.')[0]) - else: - qn = qual_names.QN(node.asname) - - self.scope.modified.add(qn) - self.scope.bound.add(qn) - return node - - def visit_Attribute(self, node): - node = self.generic_visit(node) - if self._in_constructor and self._node_sets_self_attribute(node): - self._track_symbol(node, composite_writes_alter_parent=True) - else: - self._track_symbol(node) - return node - - def visit_Subscript(self, node): - node = self.generic_visit(node) - # Subscript writes (e.g. a[b] = "value") are considered to modify - # both the element itself (a[b]) and its parent (a). - self._track_symbol(node) - return node - - def visit_Print(self, node): - self._enter_scope(False) - node.values = self.visit_block(node.values) - node_scope = self._exit_and_record_scope(node) - anno.setanno(node, NodeAnno.ARGS_SCOPE, node_scope) - return node - - def visit_Assert(self, node): - return self._process_statement(node) - - def visit_Call(self, node): - self._enter_scope(False) - node.args = self.visit_block(node.args) - node.keywords = self.visit_block(node.keywords) - # TODO(mdan): Account starargs, kwargs - self._exit_and_record_scope(node, tag=NodeAnno.ARGS_SCOPE) - - node.func = self.visit(node.func) - return node - - def _process_block_node(self, node, block, scope_name): - self._enter_scope(False) - block = self.visit_block(block) - self._exit_and_record_scope(node, tag=scope_name) - return node - - def _process_parallel_blocks(self, parent, children): - # Because the scopes are not isolated, processing any child block - # modifies the parent state causing the other child blocks to be - # processed incorrectly. So we need to checkpoint the parent scope so that - # each child sees the same context. - before_parent = Scope.copy_of(self.scope) - after_children = [] - for child, scope_name in children: - self.scope.copy_from(before_parent) - parent = self._process_block_node(parent, child, scope_name) - after_child = Scope.copy_of(self.scope) - after_children.append(after_child) - for after_child in after_children: - self.scope.merge_from(after_child) - return parent - - def _process_comprehension(self, - node, - is_list_comp=False, - is_dict_comp=False): - with self.state[_Comprehension] as comprehension_: - comprehension_.is_list_comp = is_list_comp - # Note: it's important to visit the generators first to properly account - # for the variables local to these generators. Example: `x` is local to - # the expression `z for x in y for z in x`. - node.generators = self.visit_block(node.generators) - if is_dict_comp: - node.key = self.visit(node.key) - node.value = self.visit(node.value) - else: - node.elt = self.visit(node.elt) - return node - - def visit_comprehension(self, node): - # It is important to visit children in this order so that the reads to - # the target name are appropriately ignored. - node.iter = self.visit(node.iter) - node.target = self.visit(node.target) - return self.generic_visit(node) - - def visit_DictComp(self, node): - return self._process_comprehension(node, is_dict_comp=True) - - def visit_ListComp(self, node): - return self._process_comprehension(node, is_list_comp=True) - - def visit_SetComp(self, node): - return self._process_comprehension(node) - - def visit_GeneratorExp(self, node): - return self._process_comprehension(node) - - def visit_ClassDef(self, node): - with self.state[_FunctionOrClass] as fn: - fn.node = node - # The ClassDef node itself has a Scope object that tracks the creation - # of its name, along with the usage of any decorator accompanying it. - self._enter_scope(False) - node.decorator_list = self.visit_block(node.decorator_list) - self.scope.modified.add(qual_names.QN(node.name)) - self.scope.bound.add(qual_names.QN(node.name)) - node.bases = self.visit_block(node.bases) - node.keywords = self.visit_block(node.keywords) - self._exit_and_record_scope(node) - - # A separate Scope tracks the actual class definition. - self._enter_scope(True) - node = self.generic_visit(node) - self._exit_scope() - return node - - def _visit_node_list(self, nodes): - return [(None if n is None else self.visit(n)) for n in nodes] - - def _visit_arg_annotations(self, node): - node.args.kw_defaults = self._visit_node_list(node.args.kw_defaults) - node.args.defaults = self._visit_node_list(node.args.defaults) - self._track_annotations_only = True - node = self._visit_arg_declarations(node) - self._track_annotations_only = False - return node - - def _visit_arg_declarations(self, node): - node.args.posonlyargs = self._visit_node_list(node.args.posonlyargs) - node.args.args = self._visit_node_list(node.args.args) - if node.args.vararg is not None: - node.args.vararg = self.visit(node.args.vararg) - node.args.kwonlyargs = self._visit_node_list(node.args.kwonlyargs) - if node.args.kwarg is not None: - node.args.kwarg = self.visit(node.args.kwarg) - return node - - def visit_FunctionDef(self, node): - with self.state[_FunctionOrClass] as fn: - fn.node = node - # The FunctionDef node itself has a Scope object that tracks the creation - # of its name, along with the usage of any decorator accompanying it. - self._enter_scope(False) - node.decorator_list = self.visit_block(node.decorator_list) - if node.returns: - node.returns = self._process_annotation(node.returns) - # Argument annotartions (includeing defaults) affect the defining context. - node = self._visit_arg_annotations(node) - - function_name = qual_names.QN(node.name) - self.scope.modified.add(function_name) - self.scope.bound.add(function_name) - self._exit_and_record_scope(node) - - # A separate Scope tracks the actual function definition. - self._enter_scope(True, node.name) - - # Keep a separate scope for the arguments node, which is used in the CFG. - self._enter_scope(False, node.name) - - # Arg declarations only affect the function itself, and have no effect - # in the defining context whatsoever. - node = self._visit_arg_declarations(node) - - self._exit_and_record_scope(node.args) - - # Track the body separately. This is for compatibility reasons, it may not - # be strictly needed. - self._enter_scope(False, node.name) - node.body = self.visit_block(node.body) - self._exit_and_record_scope(node, NodeAnno.BODY_SCOPE) - - self._exit_and_record_scope(node, NodeAnno.ARGS_AND_BODY_SCOPE) - return node - - def visit_Lambda(self, node): - # Lambda nodes are treated in roughly the same way as FunctionDef nodes. - with self.state[_FunctionOrClass] as fn: - fn.node = node - # The Lambda node itself has a Scope object that tracks the creation - # of its name, along with the usage of any decorator accompanying it. - self._enter_scope(False) - node = self._visit_arg_annotations(node) - self._exit_and_record_scope(node) - - # A separate Scope tracks the actual function definition. - self._enter_scope(True) - - # Keep a separate scope for the arguments node, which is used in the CFG. - self._enter_scope(False) - node = self._visit_arg_declarations(node) - self._exit_and_record_scope(node.args) - - # Track the body separately. This is for compatibility reasons, it may not - # be strictly needed. - # TODO(mdan): Do remove it, it's confusing. - self._enter_scope(False) - node.body = self.visit(node.body) - - # The lambda body can contain nodes of types normally not found as - # statements, and may not have the SCOPE annotation needed by the CFG. - # So we attach one if necessary. - if not anno.hasanno(node.body, anno.Static.SCOPE): - anno.setanno(node.body, anno.Static.SCOPE, self.scope) - - self._exit_and_record_scope(node, NodeAnno.BODY_SCOPE) - - lambda_scope = self.scope - self._exit_and_record_scope(node, NodeAnno.ARGS_AND_BODY_SCOPE) - - # TODO(bhack:) https://github.com/tensorflow/tensorflow/issues/56089 - # remove after deprecation - # Exception: lambdas are assumed to be used in the place where - # they are defined. Therefore, their activity is passed on to the - # calling statement. - self.scope.read.update(lambda_scope.read - lambda_scope.bound) - - return node - - def visit_With(self, node): - self._enter_scope(False) - node = self.generic_visit(node) - self._exit_and_record_scope(node, NodeAnno.BODY_SCOPE) - return node - - def visit_withitem(self, node): - return self._process_statement(node) - - def visit_If(self, node): - self._enter_scope(False) - node.test = self.visit(node.test) - node_scope = self._exit_and_record_scope(node.test) - anno.setanno(node, NodeAnno.COND_SCOPE, node_scope) - - node = self._process_parallel_blocks(node, - ((node.body, NodeAnno.BODY_SCOPE), - (node.orelse, NodeAnno.ORELSE_SCOPE))) - return node - - def visit_For(self, node): - self._enter_scope(False) - node.target = self.visit(node.target) - node.iter = self.visit(node.iter) - self._exit_and_record_scope(node.iter) - - self._enter_scope(False) - self.visit(node.target) - if anno.hasanno(node, anno.Basic.EXTRA_LOOP_TEST): - self._process_statement(anno.getanno(node, anno.Basic.EXTRA_LOOP_TEST)) - self._exit_and_record_scope(node, tag=NodeAnno.ITERATE_SCOPE) - - node = self._process_parallel_blocks(node, - ((node.body, NodeAnno.BODY_SCOPE), - (node.orelse, NodeAnno.ORELSE_SCOPE))) - return node - - def visit_While(self, node): - self._enter_scope(False) - node.test = self.visit(node.test) - node_scope = self._exit_and_record_scope(node.test) - anno.setanno(node, NodeAnno.COND_SCOPE, node_scope) - - node = self._process_parallel_blocks(node, - ((node.body, NodeAnno.BODY_SCOPE), - (node.orelse, NodeAnno.ORELSE_SCOPE))) - return node - - def visit_ExceptHandler(self, node): - self._enter_scope(False) - # try/except oddity: as expected, it leaks any names you defined inside the - # except block, but not the name of the exception variable. - if node.name is not None: - self.scope.isolated_names.add(anno.getanno(node.name, anno.Basic.QN)) - node = self.generic_visit(node) - self._exit_scope() - return node - - -def resolve(node, context, parent_scope=None): - return ActivityAnalyzer(context, parent_scope).visit(node) diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/activity_test.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/activity_test.py deleted file mode 100644 index 3b80c836..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/activity_test.py +++ /dev/null @@ -1,868 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for activity module.""" - -import gast - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import naming -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import qual_names -from braket.experimental.autoqasm.autograph.pyct import transformer -from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity -from braket.experimental.autoqasm.autograph.pyct.static_analysis import annos -from tensorflow.python.platform import test - - -QN = qual_names.QN -NodeAnno = annos.NodeAnno - -global_a = 7 -global_b = 17 - - -class ScopeTest(test.TestCase): - - def assertMissing(self, qn, scope): - self.assertNotIn(qn, scope.read) - self.assertNotIn(qn, scope.modified) - - def assertReadOnly(self, qn, scope): - self.assertIn(qn, scope.read) - self.assertNotIn(qn, scope.modified) - - def assertWriteOnly(self, qn, scope): - self.assertNotIn(qn, scope.read) - self.assertIn(qn, scope.modified) - - def assertReadWrite(self, qn, scope): - self.assertIn(qn, scope.read) - self.assertIn(qn, scope.modified) - - def test_copy_from(self): - scope = activity.Scope(None) - scope.modified.add(QN('foo')) - other = activity.Scope(None) - other.copy_from(scope) - - self.assertWriteOnly(QN('foo'), other) - - scope.modified.add(QN('bar')) - scope.copy_from(other) - - self.assertMissing(QN('bar'), scope) - - def test_merge_from(self): - scope = activity.Scope(None) - other = activity.Scope(None) - - for col in (scope.modified, scope.read, scope.bound, scope.deleted): - col.add(QN('foo')) - - for col in (other.modified, other.read, other.bound, other.deleted): - col.add(QN('foo')) - col.add(QN('bar')) - - scope.merge_from(other) - - self.assertReadWrite(QN('foo'), scope) - self.assertReadWrite(QN('bar'), scope) - self.assertIn(QN('foo'), scope.bound) - self.assertIn(QN('bar'), scope.bound) - self.assertIn(QN('foo'), scope.deleted) - self.assertIn(QN('bar'), scope.deleted) - - def test_copy_of(self): - scope = activity.Scope(None) - scope.read.add(QN('foo')) - other = activity.Scope.copy_of(scope) - - self.assertReadOnly(QN('foo'), other) - - child_scope = activity.Scope(scope) - child_scope.read.add(QN('bar')) - other = activity.Scope.copy_of(child_scope) - - self.assertReadOnly(QN('bar'), other) - - def test_referenced(self): - scope = activity.Scope(None) - scope.read.add(QN('a')) - - child = activity.Scope(scope) - child.read.add(QN('b')) - - child2 = activity.Scope(child, isolated=False) - child2.read.add(QN('c')) - - child2.finalize() - child.finalize() - scope.finalize() - - self.assertIn(QN('c'), child2.referenced) - self.assertIn(QN('b'), child2.referenced) - self.assertIn(QN('a'), child2.referenced) - - self.assertIn(QN('c'), child.referenced) - self.assertIn(QN('b'), child.referenced) - self.assertIn(QN('a'), child.referenced) - - -class ActivityAnalyzerTestBase(test.TestCase): - - def _parse_and_analyze(self, test_fn): - # TODO(mdan): Use a custom FunctionTransformer here. - node, source = parser.parse_entity(test_fn, future_features=()) - entity_info = transformer.EntityInfo( - name=test_fn.__name__, - source_code=source, - source_file=None, - future_features=(), - namespace={}) - node = qual_names.resolve(node) - namer = naming.Namer({}) - ctx = transformer.Context(entity_info, namer, None) - node = activity.resolve(node, ctx) - return node, entity_info - - def assertSymbolSetsAre(self, expected, actual, name): - expected = set(expected) - actual = set(str(s) for s in actual) - self.assertSetEqual( - expected, actual, 'for symbol set: %s\n' - ' Expected: %s\n' - ' Got: %s\n' - ' Missing: %s\n' - ' Extra: %s\n' % (name.upper(), expected, actual, - expected - actual, actual - expected)) - - def assertScopeIs(self, scope, used, modified): - """Assert the scope contains specific used, modified & created variables.""" - self.assertSymbolSetsAre(used, scope.read, 'read') - self.assertSymbolSetsAre(modified, scope.modified, 'modified') - - -class ActivityAnalyzerTest(ActivityAnalyzerTestBase): - - def test_import(self): - - def test_fn(): - import a, b.x, y as c, z.u as d # pylint:disable=g-multiple-import,g-import-not-at-top,unused-variable - - node, _ = self._parse_and_analyze(test_fn) - scope = anno.getanno(node.body[0], anno.Static.SCOPE) - self.assertScopeIs(scope, (), ('a', 'b', 'c', 'd')) - - def test_import_from(self): - - def test_fn(): - from x import a # pylint:disable=g-import-not-at-top,unused-variable - from y import z as b # pylint:disable=g-import-not-at-top,unused-variable - - node, _ = self._parse_and_analyze(test_fn) - scope = anno.getanno(node.body[0], anno.Static.SCOPE) - self.assertScopeIs(scope, (), ('a',)) - scope = anno.getanno(node.body[1], anno.Static.SCOPE) - self.assertScopeIs(scope, (), ('b',)) - - def test_print_statement(self): - - def test_fn(a): - b = 0 - c = 1 - print(a, b) - return c - - node, _ = self._parse_and_analyze(test_fn) - print_node = node.body[2] - if isinstance(print_node, gast.Print): - # Python 2 - print_args_scope = anno.getanno(print_node, NodeAnno.ARGS_SCOPE) - else: - # Python 3 - assert isinstance(print_node, gast.Expr) - # The call node should be the one being annotated. - print_node = print_node.value - print_args_scope = anno.getanno(print_node, NodeAnno.ARGS_SCOPE) - # We basically need to detect which variables are captured by the call - # arguments. - self.assertScopeIs(print_args_scope, ('a', 'b'), ()) - - def test_call_args(self): - - def test_fn(a): - b = 0 - c = 1 - foo(a, b) # pylint:disable=undefined-variable - return c - - node, _ = self._parse_and_analyze(test_fn) - call_node = node.body[2].value - # We basically need to detect which variables are captured by the call - # arguments. - self.assertScopeIs( - anno.getanno(call_node, NodeAnno.ARGS_SCOPE), ('a', 'b'), ()) - - def test_call_args_attributes(self): - - def foo(*_): - pass - - def test_fn(a): - a.c = 0 - foo(a.b, a.c) - return a.d - - node, _ = self._parse_and_analyze(test_fn) - call_node = node.body[1].value - self.assertScopeIs( - anno.getanno(call_node, NodeAnno.ARGS_SCOPE), ('a', 'a.b', 'a.c'), ()) - - def test_call_args_subscripts(self): - - def foo(*_): - pass - - def test_fn(a): - b = 1 - c = 2 - foo(a[0], a[b]) - return a[c] - - node, _ = self._parse_and_analyze(test_fn) - call_node = node.body[2].value - self.assertScopeIs( - anno.getanno(call_node, NodeAnno.ARGS_SCOPE), - ('a', 'a[0]', 'a[b]', 'b'), ()) - - def test_while(self): - - def test_fn(a): - b = a - while b > 0: - c = b - b -= 1 - return b, c - - node, _ = self._parse_and_analyze(test_fn) - while_node = node.body[1] - self.assertScopeIs( - anno.getanno(while_node, NodeAnno.BODY_SCOPE), ('b',), ('b', 'c')) - self.assertScopeIs( - anno.getanno(while_node, NodeAnno.BODY_SCOPE).parent, ('a', 'b', 'c'), - ('b', 'c')) - self.assertScopeIs( - anno.getanno(while_node, NodeAnno.COND_SCOPE), ('b',), ()) - - def test_for(self): - - def test_fn(a): - b = a - for _ in a: - c = b - b -= 1 - return b, c - - node, _ = self._parse_and_analyze(test_fn) - for_node = node.body[1] - self.assertScopeIs( - anno.getanno(for_node, NodeAnno.ITERATE_SCOPE), (), ('_')) - self.assertScopeIs( - anno.getanno(for_node, NodeAnno.BODY_SCOPE), ('b',), ('b', 'c')) - self.assertScopeIs( - anno.getanno(for_node, NodeAnno.BODY_SCOPE).parent, ('a', 'b', 'c'), - ('b', 'c', '_')) - - def test_if(self): - - def test_fn(x): - if x > 0: - x = -x - y = 2 * x - z = -y - else: - x = 2 * x - y = -x - u = -y - return z, u - - node, _ = self._parse_and_analyze(test_fn) - if_node = node.body[0] - self.assertScopeIs( - anno.getanno(if_node, NodeAnno.BODY_SCOPE), ('x', 'y'), ('x', 'y', 'z')) - self.assertScopeIs( - anno.getanno(if_node, NodeAnno.BODY_SCOPE).parent, ('x', 'y', 'z', 'u'), - ('x', 'y', 'z', 'u')) - self.assertScopeIs( - anno.getanno(if_node, NodeAnno.ORELSE_SCOPE), ('x', 'y'), - ('x', 'y', 'u')) - self.assertScopeIs( - anno.getanno(if_node, NodeAnno.ORELSE_SCOPE).parent, - ('x', 'y', 'z', 'u'), ('x', 'y', 'z', 'u')) - - def test_if_attributes(self): - - def test_fn(a): - if a > 0: - a.b = -a.c - d = 2 * a - else: - a.b = a.c - d = 1 - return d - - node, _ = self._parse_and_analyze(test_fn) - if_node = node.body[0] - self.assertScopeIs( - anno.getanno(if_node, NodeAnno.BODY_SCOPE), ('a', 'a.c'), ('a.b', 'd')) - self.assertScopeIs( - anno.getanno(if_node, NodeAnno.ORELSE_SCOPE), ('a', 'a.c'), - ('a.b', 'd')) - self.assertScopeIs( - anno.getanno(if_node, NodeAnno.BODY_SCOPE).parent, ('a', 'a.c', 'd'), - ('a.b', 'd')) - - def test_if_subscripts(self): - - def test_fn(a, b, c, e): - if a > 0: - a[b] = -a[c] - d = 2 * a - else: - a[0] = e - d = 1 - return d - - node, _ = self._parse_and_analyze(test_fn) - if_node = node.body[0] - self.assertScopeIs( - anno.getanno(if_node, NodeAnno.BODY_SCOPE), ('a', 'b', 'c', 'a[c]'), - ('a[b]', 'd')) - # TODO(mdan): Should subscript writes (a[0] = 1) be considered to read "a"? - self.assertScopeIs( - anno.getanno(if_node, NodeAnno.ORELSE_SCOPE), ('a', 'e'), ('a[0]', 'd')) - self.assertScopeIs( - anno.getanno(if_node, NodeAnno.ORELSE_SCOPE).parent, - ('a', 'b', 'c', 'd', 'e', 'a[c]'), ('d', 'a[b]', 'a[0]')) - - def test_nested_if(self): - - def test_fn(b): - if b > 0: - if b < 5: - a = b - else: - a = b * b - return a - - node, _ = self._parse_and_analyze(test_fn) - inner_if_node = node.body[0].body[0] - self.assertScopeIs( - anno.getanno(inner_if_node, NodeAnno.BODY_SCOPE), ('b',), ('a',)) - self.assertScopeIs( - anno.getanno(inner_if_node, NodeAnno.ORELSE_SCOPE), ('b',), ('a',)) - - def test_nested_function(self): - - def test_fn(a): - - def f(x): - y = x * x - return y - - return f(a) - - node, _ = self._parse_and_analyze(test_fn) - - fn_node = node - scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(scope, ('a', 'f'), ('f',)) - - fn_def_node = node.body[0] - - scope = anno.getanno(fn_def_node, anno.Static.SCOPE) - self.assertScopeIs(scope, (), ('f')) - - scope = anno.getanno(fn_def_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(scope, ('x', 'y'), ('y',)) - - scope = anno.getanno(fn_def_node, NodeAnno.ARGS_AND_BODY_SCOPE) - self.assertScopeIs(scope, ('x', 'y'), ('y',)) - self.assertSymbolSetsAre(('x', 'y'), scope.bound, 'BOUND') - - def test_nested_lambda(self): - - def test_fn(a): - return lambda x: (x * a) - - node, _ = self._parse_and_analyze(test_fn) - - fn_node = node - scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(scope, ('a',), ()) - - return_node = node.body[0] - - scope = anno.getanno(return_node, anno.Static.SCOPE) - self.assertScopeIs(scope, ('a',), ()) - - lam_def_node = return_node.value - - scope = anno.getanno(lam_def_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(scope, ('a', 'x'), ()) - - scope = anno.getanno(lam_def_node, NodeAnno.ARGS_AND_BODY_SCOPE) - self.assertScopeIs(scope, ('a', 'x'), ()) - self.assertSymbolSetsAre(('x',), scope.bound, 'BOUND') - - def test_nested_function_arg_defaults(self): - - def test_fn(a): - - def f(x=a): - y = x * x - return y - - return f(a) - - node, _ = self._parse_and_analyze(test_fn) - fn_def_node = node.body[0] - - self.assertScopeIs( - anno.getanno(fn_def_node, anno.Static.SCOPE), ('a',), ('f',)) - - scope = anno.getanno(fn_def_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(scope, ('x', 'y'), ('y',)) - - scope = anno.getanno(fn_def_node, NodeAnno.ARGS_AND_BODY_SCOPE) - self.assertScopeIs(scope, ('x', 'y'), ('y',)) - self.assertSymbolSetsAre(('x', 'y'), scope.bound, 'BOUND') - - def test_constructor_attributes(self): - - class TestClass(object): - - def __init__(self, a): - self.b = a - self.b.c = 1 - - node, _ = self._parse_and_analyze(TestClass) - init_node = node.body[0] - self.assertScopeIs( - anno.getanno(init_node, NodeAnno.BODY_SCOPE), ('self', 'a', 'self.b'), - ('self', 'self.b', 'self.b.c')) - - def test_aug_assign_subscripts(self): - - def test_fn(a): - a[0] += 1 - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - self.assertScopeIs( - anno.getanno(fn_node, NodeAnno.BODY_SCOPE), ('a', 'a[0]'), ('a[0]',)) - - def test_return_vars_are_read(self): - - def test_fn(a, b, c): # pylint: disable=unused-argument - return c - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - self.assertScopeIs(anno.getanno(fn_node, NodeAnno.BODY_SCOPE), ('c',), ()) - self.assertScopeIs( - anno.getanno(node.body[0], anno.Static.SCOPE), ('c',), ()) - - def test_raise_names_are_read(self): - - def test_fn(a, b, c): # pylint: disable=unused-argument - raise b - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - self.assertScopeIs(anno.getanno(fn_node, NodeAnno.BODY_SCOPE), ('b',), ()) - self.assertScopeIs( - anno.getanno(node.body[0], anno.Static.SCOPE), ('b',), ()) - - def test_except_exposes_names(self): - - def test_fn(a, b, c): # pylint: disable=unused-argument - try: - pass - except: # pylint: disable=bare-except - b = c - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - self.assertScopeIs( - anno.getanno(fn_node, NodeAnno.BODY_SCOPE), ('c',), ('b',)) - - def test_except_hides_exception_var_name(self): - - def test_fn(a, b, c): # pylint: disable=unused-argument - try: - pass - except a as e: - b = e - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - self.assertScopeIs( - anno.getanno(fn_node, NodeAnno.BODY_SCOPE), ('a',), ('b',)) - - def test_aug_assign(self): - - def test_fn(a, b): - a += b - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - self.assertScopeIs( - anno.getanno(fn_node, NodeAnno.BODY_SCOPE), ('a', 'b'), ('a')) - - def test_aug_assign_rvalues(self): - - a = dict(bar=3) - - def foo(): - return a - - def test_fn(x): - foo()['bar'] += x - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - self.assertScopeIs( - anno.getanno(fn_node, NodeAnno.BODY_SCOPE), ('foo', 'x'), ()) - - def test_lambda(self): - - def test_fn(a, b): - return lambda: (a + b) - - node, _ = self._parse_and_analyze(test_fn) - - fn_node = node - scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(scope, ('a', 'b'), ()) - - lam_def_node = node.body[0].value - - scope = anno.getanno(lam_def_node, anno.Static.SCOPE) - self.assertScopeIs(scope, (), ()) - - scope = anno.getanno(lam_def_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(scope, ('a', 'b'), ()) - - scope = anno.getanno(lam_def_node, NodeAnno.ARGS_AND_BODY_SCOPE) - self.assertScopeIs(scope, ('a', 'b'), ()) - self.assertSymbolSetsAre((), scope.bound, 'BOUND') - - scope = anno.getanno(lam_def_node.args, anno.Static.SCOPE) - self.assertSymbolSetsAre((), scope.params.keys(), 'lambda params') - - def test_lambda_params_args(self): - - def test_fn(a, b): # pylint: disable=unused-argument - return lambda a: a + b - - node, _ = self._parse_and_analyze(test_fn) - - fn_node = node - scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - # Note: `a` in `a + b` is not "read" here because it's hidden by the `a` - # argument. - self.assertScopeIs(scope, ('b',), ()) - - lam_def_node = node.body[0].value - - scope = anno.getanno(lam_def_node, anno.Static.SCOPE) - self.assertScopeIs(scope, (), ()) - - scope = anno.getanno(lam_def_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(scope, ('a', 'b'), ()) - - scope = anno.getanno(lam_def_node, NodeAnno.ARGS_AND_BODY_SCOPE) - self.assertScopeIs(scope, ('a', 'b'), ()) - self.assertSymbolSetsAre(('a',), scope.bound, 'BOUND') - - scope = anno.getanno(lam_def_node.args, anno.Static.SCOPE) - self.assertSymbolSetsAre(('a',), scope.params.keys(), 'lambda params') - - def test_lambda_params_arg_defaults(self): - - def test_fn(a, b, c): # pylint: disable=unused-argument - return lambda b=c: a + b - - node, _ = self._parse_and_analyze(test_fn) - - fn_node = node - scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - # Note: `b` is not "read" here because it's hidden by the argument. - self.assertScopeIs(scope, ('a', 'c'), ()) - - lam_def_node = node.body[0].value - - scope = anno.getanno(lam_def_node, anno.Static.SCOPE) - self.assertScopeIs(scope, ('c',), ()) - - scope = anno.getanno(lam_def_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(scope, ('a', 'b'), ()) - - scope = anno.getanno(lam_def_node, NodeAnno.ARGS_AND_BODY_SCOPE) - self.assertScopeIs(scope, ('a', 'b'), ()) - self.assertSymbolSetsAre(('b',), scope.bound, 'BOUND') - - scope = anno.getanno(lam_def_node.args, anno.Static.SCOPE) - self.assertSymbolSetsAre(('b',), scope.params.keys(), 'lambda params') - - def test_lambda_complex(self): - - def test_fn(a, b, c, d, e): # pylint: disable=unused-argument - a = (lambda a, b, c=e: a + b + c)(d, 1, 2) + b - - node, _ = self._parse_and_analyze(test_fn) - - fn_node = node - scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(scope, ('d', 'b', 'e'), ('a',)) - - lam_def_node = node.body[0].value.left.func - - scope = anno.getanno(lam_def_node, anno.Static.SCOPE) - self.assertScopeIs(scope, ('e',), ()) - - scope = anno.getanno(lam_def_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(scope, ('a', 'b', 'c'), ()) - - scope = anno.getanno(lam_def_node, NodeAnno.ARGS_AND_BODY_SCOPE) - self.assertScopeIs(scope, ('a', 'b', 'c'), ()) - self.assertSymbolSetsAre(('a', 'b', 'c'), scope.bound, 'BOUND') - - scope = anno.getanno(lam_def_node.args, anno.Static.SCOPE) - self.assertSymbolSetsAre( - ('a', 'b', 'c'), scope.params.keys(), 'lambda params') - - def test_lambda_nested(self): - - def test_fn(a, b, c, d, e, f): # pylint: disable=unused-argument - a = lambda a, b: d(lambda b=f: a + b + c) # pylint: disable=undefined-variable - - node, _ = self._parse_and_analyze(test_fn) - - fn_node = node - scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(scope, ('d', 'c', 'f'), ('a',)) - - outer_lam_def = node.body[0].value - - scope = anno.getanno(outer_lam_def, anno.Static.SCOPE) - self.assertScopeIs(scope, (), ()) - - scope = anno.getanno(outer_lam_def, NodeAnno.BODY_SCOPE) - self.assertScopeIs(scope, ('d', 'f', 'a', 'c'), ()) - - scope = anno.getanno(outer_lam_def, NodeAnno.ARGS_AND_BODY_SCOPE) - self.assertScopeIs(scope, ('d', 'f', 'a', 'c'), ()) - self.assertSymbolSetsAre(('a', 'b'), scope.bound, 'BOUND') - - scope = anno.getanno(outer_lam_def.args, anno.Static.SCOPE) - self.assertSymbolSetsAre(('a', 'b'), scope.params.keys(), 'lambda params') - - inner_lam_def = outer_lam_def.body.args[0] - - scope = anno.getanno(inner_lam_def, anno.Static.SCOPE) - self.assertScopeIs(scope, ('f',), ()) - - scope = anno.getanno(inner_lam_def, NodeAnno.BODY_SCOPE) - self.assertScopeIs(scope, ('a', 'b', 'c'), ()) - - scope = anno.getanno(inner_lam_def, NodeAnno.ARGS_AND_BODY_SCOPE) - self.assertScopeIs(scope, ('a', 'b', 'c'), ()) - self.assertSymbolSetsAre(('b',), scope.bound, 'BOUND') - - scope = anno.getanno(inner_lam_def.args, anno.Static.SCOPE) - self.assertSymbolSetsAre(('b',), scope.params.keys(), 'lambda params') - - def test_comprehension_targets_are_isolated(self): - - def test_fn(a): - b = {c for c in a} # pylint:disable=unused-variable - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(body_scope, ('a',), ('b',)) - - def test_comprehension_targets_are_isolated_list_function_w_generator(self): - - def test_fn(a): - b = list(c for c in a) # pylint:disable=unused-variable - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(body_scope, ('a', 'list'), ('b',)) - - def test_list_comprehension_targets_are_sometimes_isolated(self): - - def test_fn(a): - b = [c for c in a] # pylint:disable=unused-variable - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(body_scope, ('a',), ('b',)) - - def test_comprehension_targets_are_isolated_in_augassign(self): - - def test_fn(a, b): - b += [c for c in a] # pylint:disable=unused-variable - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(body_scope, ('a', 'b'), ('b',)) - - def test_comprehension_generator_order(self): - - def test_fn(a, b, c): # pylint:disable=unused-argument - e = {d: (a, b) for (a, b) in c for d in b} # pylint:disable=unused-variable,g-complex-comprehension - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(body_scope, ('c',), ('e',)) - - def test_global_symbol(self): - - def test_fn(c): - global global_a - global global_b - global_a = global_b + c - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(body_scope, ('global_a', 'global_b', 'c'), ('global_a',)) - self.assertSetEqual(body_scope.globals, set( - (QN('global_a'), QN('global_b')))) - global_a_scope = anno.getanno(fn_node.body[0], anno.Static.SCOPE) - self.assertScopeIs(global_a_scope, ('global_a',), ()) - - def test_nonlocal_symbol(self): - nonlocal_a = 3 - nonlocal_b = 13 - - def test_fn(c): - nonlocal nonlocal_a - nonlocal nonlocal_b - nonlocal_a = nonlocal_b + c - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs( - body_scope, ('nonlocal_a', 'nonlocal_b', 'c'), ('nonlocal_a',)) - nonlocal_a_scope = anno.getanno(fn_node.body[0], anno.Static.SCOPE) - self.assertScopeIs(nonlocal_a_scope, ('nonlocal_a',), ()) - - def test_annotated_assign(self): - b = int - - def test_fn(c): - a: b = c - return a - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - - body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(body_scope, ('b', 'c', 'a'), ('a',)) - self.assertSymbolSetsAre(('b',), body_scope.annotations, 'annotations') - - ann_assign_scope = anno.getanno(fn_node.body[0], anno.Static.SCOPE) - self.assertScopeIs(ann_assign_scope, ('b', 'c'), ('a',)) - self.assertSymbolSetsAre( - ('b',), ann_assign_scope.annotations, 'annotations') - - def test_pure_definition(self): - b = int - - def test_fn(): - a: b - return a - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - - body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(body_scope, ('b', 'a'), ('a',)) - self.assertSymbolSetsAre(('b',), body_scope.annotations, 'annotations') - - ann_assign_scope = anno.getanno(fn_node.body[0], anno.Static.SCOPE) - self.assertScopeIs(ann_assign_scope, ('b',), ('a',)) - self.assertSymbolSetsAre( - ('b',), ann_assign_scope.annotations, 'annotations') - - def test_function_def_annotations(self): - b = int - c = int - - def test_fn(a: b) -> c: - return a - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - - fn_scope = anno.getanno(fn_node, anno.Static.SCOPE) - self.assertScopeIs(fn_scope, ('b', 'c'), ('test_fn',)) - self.assertSymbolSetsAre(('b', 'c'), fn_scope.annotations, 'annotations') - - body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(body_scope, ('a',), ()) - self.assertSymbolSetsAre((), body_scope.annotations, 'annotations') - - def test_class_definition_basic(self): - - def test_fn(a, b): - class C(a(b)): - d = 1 - return C - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(body_scope, ('a', 'b', 'C'), ('C',)) - - def test_class_definition_isolates_method_writes(self): - - def test_fn(a, b, c): - class C(a(b)): - d = 1 - - def e(self): - f = c + 1 - return f - return C - - node, _ = self._parse_and_analyze(test_fn) - fn_node = node - body_scope = anno.getanno(fn_node, NodeAnno.BODY_SCOPE) - self.assertScopeIs(body_scope, ('a', 'b', 'C', 'c'), ('C',)) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/annos.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/annos.py deleted file mode 100644 index 54d85cc3..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/annos.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Annotations used by the static analyzer.""" - -from enum import Enum - - -# TODO(mdan): Remove. - - -class NoValue(Enum): - - def __repr__(self): # pylint: disable=invalid-repr-returned - return self.name - - -class NodeAnno(NoValue): - """Additional annotations used by the static analyzer. - - These are in addition to the basic annotations declared in anno.py. - """ - - # Symbols - # These flags are boolean. - IS_LOCAL = 'Symbol is local to the function scope being analyzed.' - IS_PARAM = 'Symbol is a parameter to the function being analyzed.' - IS_MODIFIED_SINCE_ENTRY = ( - 'Symbol has been explicitly replaced in the current function scope.') - - # Scopes - # Scopes are represented by objects of type activity.Scope. - ARGS_SCOPE = 'The scope for the argument list of a function call.' - COND_SCOPE = 'The scope for the test node of a conditional statement.' - ITERATE_SCOPE = 'The scope for the iterate assignment of a for loop.' - ARGS_AND_BODY_SCOPE = ( - 'The scope for the main body of a function or lambda, including its' - ' arguments.') - BODY_SCOPE = ( - 'The scope for the main body of a statement (True branch for if ' - 'statements, main body for loops).') - ORELSE_SCOPE = ( - 'The scope for the orelse body of a statement (False branch for if ' - 'statements, orelse body for loops).') diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness.py deleted file mode 100644 index 36ee2bec..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness.py +++ /dev/null @@ -1,220 +0,0 @@ -# Copyright 2018 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Live variable analysis. - -See https://en.wikipedia.org/wiki/Live_variable_analysis for a definition of -the following idioms: live variable, live in, live out, which are used -throughout this file. - -This analysis attaches the following: - * symbols that are live at the exit of control flow statements - * symbols that are live at the entry of control flow statements - -Requires activity analysis. -""" - -import gast - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import cfg -from braket.experimental.autoqasm.autograph.pyct import transformer -from braket.experimental.autoqasm.autograph.pyct.static_analysis import annos - - -class Analyzer(cfg.GraphVisitor): - """CFG visitor that performs liveness analysis at statement level.""" - - def __init__(self, graph, include_annotations): - super(Analyzer, self).__init__(graph) - self.include_annotations = include_annotations - - def init_state(self, _): - return set() - - def lamba_check(self, fn_ast_node): - if isinstance(fn_ast_node, gast.Lambda): - # Exception: lambda functions are assumed to be used only in the - # place where they are defined, and not later. - return True - return False - - def visit_node(self, node): - prev_live_in = self.in_[node] - - if anno.hasanno(node.ast_node, anno.Static.SCOPE): - node_scope = anno.getanno(node.ast_node, anno.Static.SCOPE) - - gen = node_scope.read - if not self.include_annotations: - gen -= node_scope.annotations - # TODO(mdan): verify whether composites' parents need to be added. - # E.g. whether x needs to be added if x.y is live. Theoretically the - # activity analysis should have both so that wouldn't be needed. - kill = node_scope.modified | node_scope.deleted - - live_out = set() - for n in node.next: - live_out |= self.in_[n] - live_in = gen | (live_out - kill) - - reaching_functions = anno.getanno( - node.ast_node, anno.Static.DEFINED_FNS_IN) - for fn_ast_node in reaching_functions: - if self.lamba_check(fn_ast_node): - continue - fn_scope = anno.getanno(fn_ast_node, annos.NodeAnno.ARGS_AND_BODY_SCOPE) - # Any closure of a reaching function definition is conservatively - # considered live. - live_in |= (fn_scope.read - fn_scope.bound) - - else: - assert self.can_ignore(node), (node.ast_node, node) - - live_out = set() - for n in node.next: - live_out |= self.in_[n] - live_in = live_out - - self.in_[node] = live_in - self.out[node] = live_out - - # TODO(mdan): Move this to the superclass? - return prev_live_in != live_in - - -class TreeAnnotator(transformer.Base): - """Runs liveness analysis on each of the functions defined in the AST. - - If a function defined other local functions, those will have separate CFGs. - However, dataflow analysis needs to tie up these CFGs to properly emulate the - effect of closures. In the case of liveness, the parent function's live - variables must account for the variables that are live at the entry of each - subfunction. For example: - - def foo(): - # baz is live from here on - def bar(): - print(baz) - - This analyzer runs liveness analysis on each individual function, accounting - for the effect above. - """ - - def __init__(self, source_info, graphs, include_annotations): - super(TreeAnnotator, self).__init__(source_info) - self.include_annotations = include_annotations - self.allow_skips = False - self.graphs = graphs - self.current_analyzer = None - - def visit(self, node): - node = super(TreeAnnotator, self).visit(node) - if (self.current_analyzer is not None and - isinstance(node, gast.stmt) and - node in self.current_analyzer.graph.index): - cfg_node = self.current_analyzer.graph.index[node] - anno.setanno(node, anno.Static.LIVE_VARS_IN, - frozenset(self.current_analyzer.in_[cfg_node])) - return node - - def _analyze_function(self, node, is_lambda): - parent_analyzer = self.current_analyzer - - analyzer = Analyzer(self.graphs[node], self.include_annotations) - analyzer.visit_reverse() - self.current_analyzer = analyzer - node = self.generic_visit(node) - - self.current_analyzer = parent_analyzer - return node - - def visit_Lambda(self, node): - return self._analyze_function(node, is_lambda=True) - - def visit_FunctionDef(self, node): - return self._analyze_function(node, is_lambda=False) - - def _block_statement_live_out(self, node): - successors = self.current_analyzer.graph.stmt_next[node] - stmt_live_out = set() - for s in successors: - stmt_live_out.update(self.current_analyzer.in_[s]) - anno.setanno(node, anno.Static.LIVE_VARS_OUT, frozenset(stmt_live_out)) - return node - - def _block_statement_live_in(self, node, entry_node): - if entry_node in self.current_analyzer.graph.index: - cfg_node = self.current_analyzer.graph.index[entry_node] - stmt_live_in = frozenset(self.current_analyzer.in_[cfg_node]) - else: - assert anno.hasanno(entry_node, anno.Static.LIVE_VARS_IN), ( - 'If not matching a CFG node, must be a block statement:' - ' {}'.format(entry_node)) - stmt_live_in = anno.getanno(entry_node, anno.Static.LIVE_VARS_IN) - anno.setanno(node, anno.Static.LIVE_VARS_IN, stmt_live_in) - return node - - def visit_If(self, node): - node = self.generic_visit(node) - node = self._block_statement_live_out(node) - return self._block_statement_live_in(node, node.test) - - def visit_For(self, node): - node = self.generic_visit(node) - node = self._block_statement_live_out(node) - return self._block_statement_live_in(node, node.iter) - - def visit_While(self, node): - node = self.generic_visit(node) - node = self._block_statement_live_out(node) - return self._block_statement_live_in(node, node.test) - - def visit_Try(self, node): - node = self.generic_visit(node) - node = self._block_statement_live_out(node) - return self._block_statement_live_in(node, node.body[0]) - - def visit_ExceptHandler(self, node): - node = self.generic_visit(node) - node = self._block_statement_live_out(node) - return self._block_statement_live_in(node, node.body[0]) - - def visit_With(self, node): - node = self.generic_visit(node) - return self._block_statement_live_in(node, node.items[0]) - - def visit_Expr(self, node): - node = self.generic_visit(node) - cfg_node = self.current_analyzer.graph.index[node] - anno.setanno(node, anno.Static.LIVE_VARS_OUT, - frozenset(self.current_analyzer.out[cfg_node])) - return node - - -# TODO(mdan): Investigate the possibility of removing include_annotations. -def resolve(node, source_info, graphs, include_annotations=True): - """Resolves the live symbols at the exit of control flow statements. - - Args: - node: ast.AST - source_info: transformer.SourceInfo - graphs: Dict[ast.FunctionDef, cfg.Graph] - include_annotations: Bool, whether type annotations should be included in - the analysis. - Returns: - ast.AST - """ - node = TreeAnnotator(source_info, graphs, include_annotations).visit(node) - return node diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness_py3_test.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness_py3_test.py deleted file mode 100644 index 78b91932..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness_py3_test.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for liveness module, that only run in Python 3.""" - -from braket.experimental.autoqasm.autograph.pyct.static_analysis import annos -from braket.experimental.autoqasm.autograph.pyct.static_analysis import liveness_test -from tensorflow.python.platform import test - - -NodeAnno = annos.NodeAnno - - -class LivenessAnalyzerTest(liveness_test.LivenessAnalyzerTestBase): - """Tests which can only run in Python 3.""" - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness_test.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness_test.py deleted file mode 100644 index 709bd3fe..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/liveness_test.py +++ /dev/null @@ -1,575 +0,0 @@ -# Copyright 2018 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for liveness module.""" - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import cfg -from braket.experimental.autoqasm.autograph.pyct import naming -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import qual_names -from braket.experimental.autoqasm.autograph.pyct import transformer -from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity -from braket.experimental.autoqasm.autograph.pyct.static_analysis import liveness -from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_fndefs -from tensorflow.python.platform import test - - -global_a = 7 -global_b = 17 - - -class LivenessAnalyzerTestBase(test.TestCase): - - def _parse_and_analyze(self, test_fn): - # TODO(mdan): Use a custom FunctionTransformer here. - node, source = parser.parse_entity(test_fn, future_features=()) - entity_info = transformer.EntityInfo( - name=test_fn.__name__, - source_code=source, - source_file=None, - future_features=(), - namespace={}) - node = qual_names.resolve(node) - namer = naming.Namer({}) - ctx = transformer.Context(entity_info, namer, None) - node = activity.resolve(node, ctx) - graphs = cfg.build(node) - node = reaching_fndefs.resolve(node, ctx, graphs) - node = liveness.resolve(node, ctx, graphs) - return node - - def assertHasLiveOut(self, node, expected): - live_out = anno.getanno(node, anno.Static.LIVE_VARS_OUT) - live_out_strs = set(str(v) for v in live_out) - if not expected: - expected = () - if not isinstance(expected, tuple): - expected = (expected,) - self.assertSetEqual(live_out_strs, set(expected)) - - def assertHasLiveIn(self, node, expected): - live_in = anno.getanno(node, anno.Static.LIVE_VARS_IN) - live_in_strs = set(str(v) for v in live_in) - if not expected: - expected = () - if not isinstance(expected, tuple): - expected = (expected,) - self.assertSetEqual(live_in_strs, set(expected)) - - -class LivenessAnalyzerTest(LivenessAnalyzerTestBase): - - def test_live_out_try_block(self): - - def test_fn(x, a, b, c): # pylint:disable=unused-argument - if a > 0: - try: - pass - except: # pylint:disable=bare-except - pass - return x - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveOut(fn_body[0], 'x') - self.assertHasLiveOut(fn_body[0].body[0], 'x') - - def test_live_out_if_inside_except(self): - - def test_fn(x, a, b, c): # pylint:disable=unused-argument - if a > 0: - try: - pass - except: # pylint:disable=bare-except - if b > 0: - x = b - return x - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveOut(fn_body[0], 'x') - self.assertHasLiveOut(fn_body[0].body[0], 'x') - self.assertHasLiveOut(fn_body[0].body[0].handlers[0].body[0], 'x') - - def test_live_out_stacked_if(self): - - def test_fn(x, a): - if a > 0: - x = 0 - if a > 1: - x = 1 - return x - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveOut(fn_body[0], ('a', 'x')) - self.assertHasLiveOut(fn_body[1], 'x') - - def test_live_out_stacked_if_else(self): - - def test_fn(x, a): - if a > 0: - x = 0 - if a > 1: - x = 1 - else: - x = 2 - return x - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveOut(fn_body[0], 'a') - self.assertHasLiveOut(fn_body[1], 'x') - - def test_live_out_for_basic(self): - - def test_fn(x, a): - for i in range(a): - x += i - return x - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveOut(fn_body[0], 'x') - - def test_live_out_for_iterate(self): - - def test_fn(x, a): - for i in range(a): - x += i - return x, i # pylint:disable=undefined-loop-variable - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveOut(fn_body[0], ('x', 'i')) - - def test_live_out_attributes(self): - - def test_fn(x, a): - if a > 0: - x.y = 0 - return x.y - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveOut(fn_body[0], ('x.y', 'x')) - - def test_live_out_nested_functions(self): - - def test_fn(a, b): - if b: - a = [] - - def foo(): - return a - - foo() - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveOut(fn_body[0], 'a') - - def test_live_out_nested_functions_defined_ahead(self): - - def test_fn(a, b): - def foo(): - return a - - if b: - a = [] - - return foo - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveOut(fn_body[1], ('a', 'foo')) - - def test_live_out_nested_functions_defined_after(self): - - def test_fn(a, b): - if b: - a = [] - - def foo(): - return a - - return foo - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveOut(fn_body[0], ('a',)) - - def test_live_out_lambda(self): - - def test_fn(a, b): - if b: - a = [] - - foo = lambda: a - - if b: - pass - - return foo - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveOut(fn_body[0], ('a', 'b')) - #TODO(@bhack): replace this after deprecation - # https://github.com/tensorflow/tensorflow/issues/56089 - self.assertHasLiveOut(fn_body[2], ('foo',)) - #self.assertHasLiveOut(fn_body[2], ('a', 'foo')) - - def test_live_out_nested_functions_hidden_by_argument(self): - - def test_fn(b): - def foo(a): - return a - - if b: - a = [] # pylint:disable=unused-variable - - return foo - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveOut(fn_body[1], ('foo')) - - def test_live_out_nested_functions_isolation(self): - - def test_fn(b): - if b: - a = 0 # pylint:disable=unused-variable - - def child(): - max(a) # pylint:disable=used-before-assignment - a = 1 - return a - - child() - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveOut(fn_body[0], 'max') - - def test_live_out_deletion(self): - - def test_fn(x, y, a): - for _ in a: - if x: - del y - else: - y = 0 - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveOut(fn_body[0], ()) - - def test_live_in_pass(self): - - def test_fn(x, a, b, c): # pylint:disable=unused-argument - if a > 0: - pass - return x - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveIn(fn_body[0], ('a', 'x')) - self.assertHasLiveIn(fn_body[0].body[0], ('x',)) - self.assertHasLiveIn(fn_body[1], ('x',)) - - def test_live_in_raise(self): - - def test_fn(x, a, b, c): - if a > 0: - b = b + 1 - raise c - return x - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveIn(fn_body[0], ('a', 'b', 'c', 'x')) - self.assertHasLiveIn(fn_body[0].body[0], ('b', 'c')) - self.assertHasLiveIn(fn_body[1], ('x',)) - - def test_live_out_except_variable(self): - - def test_fn(x, a): - try: - pass - except a as b: - raise b - return x - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - # Note: 'a' is not live because there is no raise statement inside the - # try, and we discount the possibility of other code in the try block - # raising an error. - self.assertHasLiveIn(fn_body[0], ('b', 'x')) - - def test_live_in_return_statement(self): - - def test_fn(x, a, b, c): # pylint:disable=unused-argument - if a > 0: - return x - return x - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveIn(fn_body[0], ('a', 'x')) - self.assertHasLiveIn(fn_body[0].body[0], ('x',)) - self.assertHasLiveIn(fn_body[1], ('x',)) - - def test_live_in_try_block(self): - - def test_fn(x, a, b, c): # pylint:disable=unused-argument - if a > 0: - try: - pass - except: # pylint:disable=bare-except - pass - return x - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveIn(fn_body[0], ('a', 'x')) - self.assertHasLiveIn(fn_body[0].body[0], ('x',)) - self.assertHasLiveIn(fn_body[1], ('x',)) - - def test_live_in_try_orelse(self): - - def test_fn(x, a, b, c): # pylint:disable=unused-argument - if a > 0: - try: - pass - except: # pylint:disable=bare-except - pass - else: - x = b - return x - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveIn(fn_body[0], ('a', 'b', 'x')) - self.assertHasLiveIn(fn_body[0].body[0], ('b', 'x')) - self.assertHasLiveIn(fn_body[1], ('x',)) - - def test_live_in_if_inside_except(self): - - def test_fn(x, a, b, c): # pylint:disable=unused-argument - if a > 0: - try: - pass - except: # pylint:disable=bare-except - if b > 0: - x = b - return x - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveIn(fn_body[0], ('a', 'b', 'x')) - self.assertHasLiveIn(fn_body[0].body[0], ('b', 'x')) - self.assertHasLiveIn(fn_body[0].body[0].handlers[0].body[0], ('b', 'x')) - self.assertHasLiveIn(fn_body[1], ('x',)) - - def test_live_in_stacked_if(self): - - def test_fn(x, a, b, c): - if a > 0: - x = b - if c > 1: - x = 0 - return x - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveIn(fn_body[0], ('a', 'b', 'c', 'x')) - self.assertHasLiveIn(fn_body[1], ('c', 'x')) - - def test_live_in_stacked_if_else(self): - - def test_fn(x, a, b, c, d): - if a > 1: - x = b - else: - x = c - if d > 0: - x = 0 - return x - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveIn(fn_body[0], ('a', 'b', 'c', 'd')) - self.assertHasLiveIn(fn_body[1], ('d', 'x')) - - def test_live_in_for_basic(self): - - def test_fn(x, y, a): - for i in a: - x = i - y += x - z = 0 - return y, z - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveIn(fn_body[0], ('a', 'y', 'z')) - - def test_live_in_for_nested(self): - - def test_fn(x, y, a): - for i in a: - for j in i: - x = i - y += x - z = j - return y, z - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveIn(fn_body[0], ('a', 'y', 'z')) - - def test_live_in_deletion(self): - - def test_fn(x, y, a): - for _ in a: - if x: - del y - else: - y = 0 - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveIn(fn_body[0], ('a', 'x', 'y')) - - def test_live_in_generator_comprehension(self): - - def test_fn(y): - if all(x for x in y): - return - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveIn(fn_body[0], ('all', 'y')) - - def test_live_in_list_comprehension(self): - - def test_fn(y): - if [x for x in y]: - return - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveIn(fn_body[0], ('y',)) - - def test_live_in_list_comprehension_expression(self): - - def test_fn(y, s): - s += foo([x for x in y]) # pylint:disable=undefined-variable - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveIn(fn_body[0], ('y', 'foo', 's')) - - def test_live_in_set_comprehension(self): - - def test_fn(y): - if {x for x in y}: - return - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveIn(fn_body[0], ('y',)) - - def test_live_in_dict_comprehension(self): - - def test_fn(y): - if {k: v for k, v in y}: - return - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasLiveIn(fn_body[0], ('y',)) - - def test_global_symbol(self): - - def test_fn(c): - global global_a - global global_b - if global_a: - global_b = c - else: - global_b = c - return global_b - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - self.assertHasLiveOut(fn_body[2], ('global_b',)) - self.assertHasLiveIn(fn_body[2], ('global_a', 'c')) - - def test_nonlocal_symbol(self): - - nonlocal_a = 3 - nonlocal_b = 13 - - def test_fn(c): - nonlocal nonlocal_a - nonlocal nonlocal_b - if nonlocal_a: - nonlocal_b = c - else: - nonlocal_b = c - return nonlocal_b - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - self.assertHasLiveOut(fn_body[2], ('nonlocal_b',)) - self.assertHasLiveIn(fn_body[2], ('nonlocal_a', 'c')) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions.py deleted file mode 100644 index 6143b05a..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions.py +++ /dev/null @@ -1,288 +0,0 @@ -# Copyright 2018 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Reaching definition analysis. - -This analysis attaches a set of a Definition objects to each symbol, one -for each distinct definition that may reach it. The Definition objects are -mutable and may be used by subsequent analyses to further annotate data like -static type and value information. -The analysis also attaches the set of the symbols defined at the entry of -control flow statements. - -Requires activity analysis. -""" - -import weakref - -import gast - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import cfg -from braket.experimental.autoqasm.autograph.pyct import transformer - - -class Definition(object): - """Definition objects describe a unique definition of a variable. - - Subclasses of this may be used by passing an appropriate factory function to - resolve. - - Attributes: - param_of: Optional[ast.AST] - directives: Dict, optional definition annotations - """ - - def __init__(self): - self.param_of = None - self.directives = {} - - def __repr__(self): - return '%s[%d]' % (self.__class__.__name__, id(self)) - - -class _NodeState(object): - """Abstraction for the state of the CFG walk for reaching definition analysis. - - This is a value type. Only implements the strictly necessary operators. - - Attributes: - value: Dict[qual_names.QN, Set[Definition, ...]], the defined symbols and - their possible definitions - """ - - def __init__(self, init_from=None): - if init_from: - if isinstance(init_from, _NodeState): - self.value = { - s: set(other_infos) for s, other_infos in init_from.value.items() - } - elif isinstance(init_from, dict): - self.value = {s: set((init_from[s],)) for s in init_from} - else: - assert False, init_from - else: - self.value = {} - - def __eq__(self, other): - if frozenset(self.value.keys()) != frozenset(other.value.keys()): - return False - ret = all(self.value[s] == other.value[s] for s in self.value) - return ret - - def __ne__(self, other): - return not self.__eq__(other) - - def __or__(self, other): - assert isinstance(other, _NodeState) - result = _NodeState(self) - for s, other_infos in other.value.items(): - if s in result.value: - result.value[s].update(other_infos) - else: - result.value[s] = set(other_infos) - return result - - def __sub__(self, other): - assert isinstance(other, set) - result = _NodeState(self) - for s in other: - result.value.pop(s, None) - return result - - def __repr__(self): - return 'NodeState[%s]=%s' % (id(self), repr(self.value)) - - -class Analyzer(cfg.GraphVisitor): - """CFG visitor that determines reaching definitions at statement level.""" - - def __init__(self, graph, definition_factory): - self._definition_factory = definition_factory - super(Analyzer, self).__init__(graph) - self.gen_map = {} - - def init_state(self, _): - return _NodeState() - - def visit_node(self, node): - prev_defs_out = self.out[node] - - defs_in = _NodeState() - for n in node.prev: - defs_in |= self.out[n] - - if anno.hasanno(node.ast_node, anno.Static.SCOPE): - node_scope = anno.getanno(node.ast_node, anno.Static.SCOPE) - # The definition objects created by each node must be singletons because - # their ids are used in equality checks. - if node not in self.gen_map: - node_symbols = {} - # Every binding operation (assign, nonlocal, global, etc.) counts as a - # definition, with the exception of del, which only deletes without - # creating a new variable. - newly_defined = ((node_scope.bound | node_scope.globals) - - node_scope.deleted) - for s in newly_defined: - def_ = self._definition_factory() - node_symbols[s] = def_ - # Every param receives a definition. Params are not necessarily - # considered as "modified". - for s, p in node_scope.params.items(): - def_ = self._definition_factory() - def_.param_of = weakref.ref(p) - node_symbols[s] = def_ - self.gen_map[node] = _NodeState(node_symbols) - - gen = self.gen_map[node] - kill = node_scope.modified | node_scope.deleted - defs_out = gen | (defs_in - kill) - - gen = self.gen_map[node] - defs_out = gen | (defs_in - kill) - - else: - assert self.can_ignore(node), (node.ast_node, node) - defs_out = defs_in - - self.in_[node] = defs_in - self.out[node] = defs_out - - return prev_defs_out != defs_out - - -class TreeAnnotator(transformer.Base): - """AST visitor that annotates each symbol name with its reaching definitions. - - Simultaneously, the visitor runs the dataflow analysis on each function node, - accounting for the effect of closures. For example: - - def foo(): - bar = 1 - def baz(): - # bar = 1 reaches here - """ - - def __init__(self, source_info, graphs, definition_factory): - super(TreeAnnotator, self).__init__(source_info) - self.allow_skips = False - self.definition_factory = definition_factory - self.graphs = graphs - self.current_analyzer = None - self.current_cfg_node = None - - def visit_FunctionDef(self, node): - parent_analyzer = self.current_analyzer - subgraph = self.graphs[node] - - analyzer = Analyzer(subgraph, self.definition_factory) - analyzer.visit_forward() - - # Recursively process any remaining subfunctions. - self.current_analyzer = analyzer - node.args = self.visit(node.args) - node.body = self.visit_block(node.body) - self.current_analyzer = parent_analyzer - - return node - - def visit_Name(self, node): - if self.current_analyzer is None: - # Names may appear outside function defs - for example in class - # definitions. - return node - - analyzer = self.current_analyzer - cfg_node = self.current_cfg_node - - assert cfg_node is not None, ('name node, %s, outside of any statement?' - % node.id) - - qn = anno.getanno(node, anno.Basic.QN) - if isinstance(node.ctx, gast.Load): - anno.setanno(node, anno.Static.DEFINITIONS, - tuple(analyzer.in_[cfg_node].value.get(qn, ()))) - else: - anno.setanno(node, anno.Static.DEFINITIONS, - tuple(analyzer.out[cfg_node].value.get(qn, ()))) - - return node - - def _aggregate_predecessors_defined_in(self, node): - preds = self.current_analyzer.graph.stmt_prev[node] - node_defined_in = set() - for p in preds: - node_defined_in |= set(self.current_analyzer.out[p].value.keys()) - anno.setanno(node, anno.Static.DEFINED_VARS_IN, frozenset(node_defined_in)) - - def visit_If(self, node): - self._aggregate_predecessors_defined_in(node) - return self.generic_visit(node) - - def visit_For(self, node): - self._aggregate_predecessors_defined_in(node) - - # Manually accounting for the shortcoming described in - # cfg.AstToCfg.visit_For. - parent = self.current_cfg_node - self.current_cfg_node = self.current_analyzer.graph.index[node.iter] - node.target = self.visit(node.target) - self.current_cfg_node = parent - - node.iter = self.visit(node.iter) - node.body = self.visit_block(node.body) - node.orelse = self.visit_block(node.orelse) - - return node - - def visit_While(self, node): - self._aggregate_predecessors_defined_in(node) - return self.generic_visit(node) - - def visit_Try(self, node): - self._aggregate_predecessors_defined_in(node) - return self.generic_visit(node) - - def visit_ExceptHandler(self, node): - self._aggregate_predecessors_defined_in(node) - # TODO(mdan): Also track the exception type / name symbols. - node.body = self.visit_block(node.body) - return node - - def visit(self, node): - parent = self.current_cfg_node - - if (self.current_analyzer is not None and - node in self.current_analyzer.graph.index): - self.current_cfg_node = self.current_analyzer.graph.index[node] - node = super(TreeAnnotator, self).visit(node) - - self.current_cfg_node = parent - return node - - -def resolve(node, source_info, graphs, definition_factory=Definition): - """Resolves reaching definitions for each symbol. - - Args: - node: ast.AST - source_info: transformer.SourceInfo - graphs: Dict[ast.FunctionDef, cfg.Graph] - definition_factory: Callable[[], Definition] - Returns: - ast.AST - """ - visitor = TreeAnnotator(source_info, graphs, definition_factory) - node = visitor.visit(node) - return node diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions_py3_test.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions_py3_test.py deleted file mode 100644 index 8412ffd7..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions_py3_test.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for reaching_definitions module, that only run in Python 3.""" - -from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_definitions_test -from tensorflow.python.platform import test - - -class ReachingDefinitionsAnalyzerTest( - reaching_definitions_test.ReachingDefinitionsAnalyzerTestBase): - """Tests which can only run in Python 3.""" - - def test_nonlocal(self): - - a = 3 - b = 13 - - def test_fn(): - nonlocal a - nonlocal b - if a: - b = [] - return a, b - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasDefs(fn_body[2].test, 1) - self.assertHasDefs(fn_body[2].body[0].targets[0], 1) - self.assertHasDefs(fn_body[3].value.elts[0], 1) - self.assertHasDefs(fn_body[3].value.elts[1], 2) - - self.assertSameDef(fn_body[2].test, fn_body[3].value.elts[0]) - - self.assertHasDefinedIn(fn_body[2], ('a', 'b')) - - def test_nonlocal_in_nested_function(self): - - a = 3 - b = 13 - - def test_fn(): - a = 3 - b = 13 - - def local_fn(): - nonlocal a, b - if a: - b = [] - return a, b - - return local_fn() - - node = self._parse_and_analyze(test_fn) - local_body = node.body[2].body - - self.assertHasDefs(local_body[1].test, 1) - self.assertHasDefs(local_body[1].body[0].targets[0], 1) - self.assertHasDefs(local_body[2].value.elts[0], 1) - self.assertHasDefs(local_body[2].value.elts[1], 2) - - self.assertSameDef(local_body[1].test, local_body[2].value.elts[0]) - - # Note: the function name is visible inside the function body. But it's - # a closure variable, not a local. - # - # Example: - # - # >>> def f(): - # ... print(f) - # >>> g = f - # >>> f = 'something else' - # >>> g() - # something else - # - self.assertHasDefinedIn(local_body[1], ('a', 'b')) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions_test.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions_test.py deleted file mode 100644 index 49f13038..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_definitions_test.py +++ /dev/null @@ -1,526 +0,0 @@ -# Copyright 2018 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for reaching_definitions module.""" - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import cfg -from braket.experimental.autoqasm.autograph.pyct import naming -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import qual_names -from braket.experimental.autoqasm.autograph.pyct import transformer -from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity -from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_definitions -from tensorflow.python.platform import test - - -global_a = 7 -global_b = 17 - - -class ReachingDefinitionsAnalyzerTestBase(test.TestCase): - - def _parse_and_analyze(self, test_fn): - # TODO(mdan): Use a custom FunctionTransformer here. - node, source = parser.parse_entity(test_fn, future_features=()) - entity_info = transformer.EntityInfo( - name=test_fn.__name__, - source_code=source, - source_file=None, - future_features=(), - namespace={}) - node = qual_names.resolve(node) - namer = naming.Namer({}) - ctx = transformer.Context(entity_info, namer, None) - node = activity.resolve(node, ctx) - graphs = cfg.build(node) - node = reaching_definitions.resolve(node, ctx, graphs, - reaching_definitions.Definition) - return node - - def assertHasDefs(self, node, num): - defs = anno.getanno(node, anno.Static.DEFINITIONS) - self.assertEqual(len(defs), num) - for r in defs: - self.assertIsInstance(r, reaching_definitions.Definition) - - def assertHasDefinedIn(self, node, expected): - defined_in = anno.getanno(node, anno.Static.DEFINED_VARS_IN) - defined_in_str = set(str(v) for v in defined_in) - if not expected: - expected = () - if not isinstance(expected, tuple): - expected = (expected,) - self.assertSetEqual(defined_in_str, set(expected)) - - def assertSameDef(self, first, second): - self.assertHasDefs(first, 1) - self.assertHasDefs(second, 1) - self.assertIs( - anno.getanno(first, anno.Static.DEFINITIONS)[0], - anno.getanno(second, anno.Static.DEFINITIONS)[0]) - - def assertNotSameDef(self, first, second): - self.assertHasDefs(first, 1) - self.assertHasDefs(second, 1) - self.assertIsNot( - anno.getanno(first, anno.Static.DEFINITIONS)[0], - anno.getanno(second, anno.Static.DEFINITIONS)[0]) - - -class ReachingDefinitionsAnalyzerTest(ReachingDefinitionsAnalyzerTestBase): - - def test_conditional(self): - - def test_fn(a, b): - a = [] - if b: - a = [] - return a - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasDefs(fn_body[0].targets[0], 1) - self.assertHasDefs(fn_body[1].test, 1) - self.assertHasDefs(fn_body[1].body[0].targets[0], 1) - self.assertHasDefs(fn_body[2].value, 2) - - self.assertHasDefinedIn(fn_body[1], ('a', 'b')) - - def test_try_in_conditional(self): - - def test_fn(a, b): # pylint:disable=unused-argument - a = [] - if b: - try: - pass - except: # pylint:disable=bare-except - pass - return a - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasDefinedIn(fn_body[1], ('a', 'b')) - self.assertHasDefinedIn(fn_body[1].body[0], ('a', 'b')) - - def test_conditional_in_try_in_conditional(self): - - def test_fn(a, b): - a = [] - if b: - try: - if b: - a = [] - except TestException: # pylint:disable=undefined-variable,unused-variable - pass - return a - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasDefinedIn(fn_body[1], ('a', 'b')) - self.assertHasDefinedIn(fn_body[1].body[0], ('a', 'b')) - # Note: `TestException` and `e` are not tracked. - self.assertHasDefinedIn(fn_body[1].body[0].body[0], ('a', 'b')) - - def test_conditional_in_except_in_conditional(self): - - def test_fn(a, b): - a = [] - if b: - try: - pass - except TestException as e: # pylint:disable=undefined-variable,unused-variable - if b: - a = [] - return a - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasDefinedIn(fn_body[1], ('a', 'b')) - self.assertHasDefinedIn(fn_body[1].body[0], ('a', 'b')) - # Note: `TestException` and `e` are not tracked. - self.assertHasDefinedIn(fn_body[1].body[0].handlers[0].body[0], ('a', 'b')) - - def test_while(self): - - def test_fn(a): - max(a) - while True: - a = a - a = a - return a - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasDefs(fn_body[0].value.args[0], 1) - self.assertHasDefs(fn_body[1].body[0].targets[0], 1) - self.assertHasDefs(fn_body[1].body[1].targets[0], 1) - self.assertHasDefs(fn_body[1].body[1].value, 1) - # The loop does have an invariant test, but the CFG doesn't know that. - self.assertHasDefs(fn_body[1].body[0].value, 2) - self.assertHasDefs(fn_body[2].value, 2) - - def test_while_else(self): - - def test_fn(x, i): - y = 0 - while x: - x += i - if i: - break - else: - y = 1 - return x, y - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasDefs(fn_body[0].targets[0], 1) - self.assertHasDefs(fn_body[1].test, 2) - self.assertHasDefs(fn_body[1].body[0].target, 1) - self.assertHasDefs(fn_body[1].body[1].test, 1) - self.assertHasDefs(fn_body[1].orelse[0].targets[0], 1) - self.assertHasDefs(fn_body[2].value.elts[0], 2) - self.assertHasDefs(fn_body[2].value.elts[1], 2) - - def test_for_else(self): - - def test_fn(x, i): - y = 0 - for i in x: - x += i - if i: - break - else: - continue - else: - y = 1 - return x, y - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasDefs(fn_body[0].targets[0], 1) - self.assertHasDefs(fn_body[1].target, 1) - self.assertHasDefs(fn_body[1].body[0].target, 1) - self.assertHasDefs(fn_body[1].body[1].test, 1) - self.assertHasDefs(fn_body[1].orelse[0].targets[0], 1) - self.assertHasDefs(fn_body[2].value.elts[0], 2) - self.assertHasDefs(fn_body[2].value.elts[1], 2) - - def test_nested_functions(self): - - def test_fn(a, b): - a = [] - if b: - a = [] - - def foo(): - return a - - foo() - - return a - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - def_of_a_in_if = fn_body[1].body[0].targets[0] - - self.assertHasDefs(fn_body[0].targets[0], 1) - self.assertHasDefs(fn_body[1].test, 1) - self.assertHasDefs(def_of_a_in_if, 1) - self.assertHasDefs(fn_body[2].value, 2) - - inner_fn_body = fn_body[1].body[1].body - def_of_a_in_foo = inner_fn_body[0].value - # Even though `a` is visible in the inner functio above, the late binding - # makes it impossible to assume that the same value will be visible at - # call time. - self.assertHasDefs(def_of_a_in_foo, 0) - - def test_nested_functions_isolation(self): - - def test_fn(a): - a = 0 - - def child(): - a = 1 - return a - - child() - return a - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - parent_return = fn_body[3] - child_return = fn_body[1].body[1] - # The assignment `a = 1` makes `a` local to `child`. - self.assertNotSameDef(parent_return.value, child_return.value) - - def test_function_call_in_with(self): - - def foo(_): - pass - - def test_fn(a): - with foo(a): - return a - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasDefs(fn_body[0].items[0].context_expr.func, 0) - self.assertHasDefs(fn_body[0].items[0].context_expr.args[0], 1) - - def test_mutation_subscript(self): - - def test_fn(a): - l = [] - l[0] = a - return l - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - creation = fn_body[0].targets[0] - mutation = fn_body[1].targets[0].value - use = fn_body[2].value - self.assertSameDef(creation, mutation) - self.assertSameDef(creation, use) - - def test_deletion_partial(self): - - def test_fn(a): - a = 0 - if a: - del a - else: - a = 1 - return a - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - first_def = fn_body[0].targets[0] - second_def = fn_body[1].orelse[0].targets[0] - use = fn_body[2].value - self.assertNotSameDef(use, first_def) - self.assertSameDef(use, second_def) - - def test_deletion_total(self): - - def test_fn(a): - if a: - a = 0 - else: - a = 1 - del a - return a - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - use = fn_body[2].value - self.assertHasDefs(use, 0) - - def test_replacement(self): - - def foo(a): - return a - - def test_fn(a): - a = foo(a) - return a - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - param = node.args.args[0] - source = fn_body[0].value.args[0] - target = fn_body[0].targets[0] - retval = fn_body[1].value - self.assertSameDef(param, source) - self.assertNotSameDef(source, target) - self.assertSameDef(target, retval) - - def test_comprehension_leaking(self): - - def test_fn(a): - _ = [x for x in a] - return x # pylint:disable=undefined-loop-variable - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - listcomp_target = fn_body[0].value.generators[0].target - retval = fn_body[1].value - - # Python2 leaks list comprehension symbols. Python3 doesn't. - # For details, see: - # https://stackoverflow.com/questions/4198906/list-comprehension-rebinds-names-even-after-scope-of-comprehension-is-this-righ - self.assertHasDefs(retval, 0) - - def test_function_definition(self): - - def test_fn(): - def a(): - pass - if a: # pylint:disable=using-constant-test - a = None - return a - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasDefs(fn_body[1].test, 1) - self.assertHasDefs(fn_body[1].body[0].targets[0], 1) - self.assertHasDefs(fn_body[2].value, 2) - - self.assertHasDefinedIn(fn_body[1], ('a',)) - - def test_definitions_in_except_block(self): - - def test_fn(): - try: - pass - except ValueError: - a = None - if a: # pylint:disable=using-constant-test - a = None - return a - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasDefs(fn_body[1].test, 1) - self.assertHasDefs(fn_body[1].body[0].targets[0], 1) - self.assertHasDefs(fn_body[2].value, 2) - - self.assertHasDefinedIn(fn_body[1], ('a',)) - - def test_definitions_in_except_block_of_raising_try(self): - - def test_fn(): - try: - raise ValueError() - except ValueError: - a = None - if a: # pylint:disable=using-constant-test - a = None - return a - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasDefs(fn_body[1].test, 1) - self.assertHasDefs(fn_body[1].body[0].targets[0], 1) - self.assertHasDefs(fn_body[2].value, 2) - - self.assertHasDefinedIn(fn_body[1], ('a',)) - - def test_global(self): - - def test_fn(): - global global_a - global global_b - if global_a: - global_b = [] - return global_a, global_b - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasDefs(fn_body[2].test, 1) - self.assertHasDefs(fn_body[2].body[0].targets[0], 1) - self.assertHasDefs(fn_body[3].value.elts[0], 1) - self.assertHasDefs(fn_body[3].value.elts[1], 2) - - self.assertSameDef(fn_body[2].test, fn_body[3].value.elts[0]) - - self.assertHasDefinedIn(fn_body[2], ('global_a', 'global_b')) - - def test_nonlocal(self): - - a = 3 - b = 13 - - def test_fn(): - nonlocal a - nonlocal b - if a: - b = [] - return a, b - - node = self._parse_and_analyze(test_fn) - fn_body = node.body - - self.assertHasDefs(fn_body[2].test, 1) - self.assertHasDefs(fn_body[2].body[0].targets[0], 1) - self.assertHasDefs(fn_body[3].value.elts[0], 1) - self.assertHasDefs(fn_body[3].value.elts[1], 2) - - self.assertSameDef(fn_body[2].test, fn_body[3].value.elts[0]) - - self.assertHasDefinedIn(fn_body[2], ('a', 'b')) - - def test_nonlocal_in_nested_function(self): - - a = 3 - b = 13 - - def test_fn(): - a = 3 - b = 13 - - def local_fn(): - nonlocal a, b - if a: - b = [] - return a, b - - return local_fn() - - node = self._parse_and_analyze(test_fn) - local_body = node.body[2].body - - self.assertHasDefs(local_body[1].test, 1) - self.assertHasDefs(local_body[1].body[0].targets[0], 1) - self.assertHasDefs(local_body[2].value.elts[0], 1) - self.assertHasDefs(local_body[2].value.elts[1], 2) - - self.assertSameDef(local_body[1].test, local_body[2].value.elts[0]) - - # Note: the function name is visible inside the function body. But it's - # a closure variable, not a local. - # - # Example: - # - # >>> def f(): - # ... print(f) - # >>> g = f - # >>> f = 'something else' - # >>> g() - # something else - # - self.assertHasDefinedIn(local_body[1], ('a', 'b')) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_fndefs.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_fndefs.py deleted file mode 100644 index f49a964b..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_fndefs.py +++ /dev/null @@ -1,178 +0,0 @@ -# Copyright 2018 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""An analysis that determines the reach of a function definition. - -A function definition is said to reach a statement if that function may exist -(and therefore may be called) when that statement executes. -""" - -import gast - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import cfg -from braket.experimental.autoqasm.autograph.pyct import transformer - - -class Definition(object): - """Definition objects describe a unique definition of a function.""" - - def __init__(self, def_node): - self.def_node = def_node - - -class _NodeState(object): - """Abstraction for the state of the CFG walk for reaching definition analysis. - - This is a value type. Only implements the strictly necessary operators. - - Attributes: - value: Dict[qual_names.QN, Set[Definition, ...]], the defined symbols and - their possible definitions - """ - - def __init__(self, init_from=None): - if init_from: - self.value = set(init_from) - else: - self.value = set() - - def __eq__(self, other): - return self.value == other.value - - def __ne__(self, other): - return self.value != other.value - - def __or__(self, other): - assert isinstance(other, _NodeState) - result = _NodeState(self.value) - result.value.update(other.value) - return result - - def __add__(self, value): - result = _NodeState(self.value) - result.value.add(value) - return result - - def __repr__(self): - return 'NodeState[%s]=%s' % (id(self), repr(self.value)) - - -class Analyzer(cfg.GraphVisitor): - """CFG visitor that determines reaching definitions at statement level.""" - - def __init__(self, graph, external_defs): - super(Analyzer, self).__init__(graph) - # This allows communicating that nodes have extra reaching definitions, - # e.g. those that a function closes over. - self.external_defs = external_defs - - def init_state(self, _): - return _NodeState() - - def visit_node(self, node): - prev_defs_out = self.out[node] - - if node is self.graph.entry: - defs_in = _NodeState(self.external_defs) - else: - defs_in = prev_defs_out - - for n in node.prev: - defs_in |= self.out[n] - - defs_out = defs_in - if isinstance(node.ast_node, (gast.Lambda, gast.FunctionDef)): - defs_out += node.ast_node - - self.in_[node] = defs_in - self.out[node] = defs_out - - return prev_defs_out != defs_out - - -class TreeAnnotator(transformer.Base): - """AST visitor that annotates each symbol name with its reaching definitions. - - Simultaneously, the visitor runs the dataflow analysis on each function node, - accounting for the effect of closures. For example: - - def foo(): - def f(): - pass - def g(): - # `def f` reaches here - """ - - def __init__(self, source_info, graphs): - super(TreeAnnotator, self).__init__(source_info) - self.graphs = graphs - self.allow_skips = False - self.current_analyzer = None - - def _proces_function(self, node): - parent_analyzer = self.current_analyzer - subgraph = self.graphs[node] - - if (self.current_analyzer is not None - and node in self.current_analyzer.graph.index): - cfg_node = self.current_analyzer.graph.index[node] - defined_in = self.current_analyzer.in_[cfg_node].value - else: - defined_in = () - - analyzer = Analyzer(subgraph, defined_in) - analyzer.visit_forward() - - self.current_analyzer = analyzer - node = self.generic_visit(node) - self.current_analyzer = parent_analyzer - return node - - def visit_FunctionDef(self, node): - return self._proces_function(node) - - def visit_Lambda(self, node): - return self._proces_function(node) - - def visit(self, node): - # This can happen before entering the top level function - if (self.current_analyzer is not None - and node in self.current_analyzer.graph.index): - cfg_node = self.current_analyzer.graph.index[node] - anno.setanno(node, anno.Static.DEFINED_FNS_IN, - self.current_analyzer.in_[cfg_node].value) - - extra_node = anno.getanno(node, anno.Basic.EXTRA_LOOP_TEST, default=None) - if extra_node is not None: - cfg_node = self.current_analyzer.graph.index[extra_node] - anno.setanno(extra_node, anno.Static.DEFINED_FNS_IN, - self.current_analyzer.in_[cfg_node].value) - - return super(TreeAnnotator, self).visit(node) - - -def resolve(node, source_info, graphs): - """Resolves reaching definitions for each symbol. - - Args: - node: ast.AST - source_info: transformer.SourceInfo - graphs: Dict[ast.FunctionDef, cfg.Graph] - Returns: - ast.AST - """ - visitor = TreeAnnotator(source_info, graphs) - node = visitor.visit(node) - return node diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_fndefs_test.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_fndefs_test.py deleted file mode 100644 index ede7f41a..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/reaching_fndefs_test.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2018 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for reaching_fndefs module.""" - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import cfg -from braket.experimental.autoqasm.autograph.pyct import naming -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import qual_names -from braket.experimental.autoqasm.autograph.pyct import transformer -from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity -from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_definitions -from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_fndefs -from tensorflow.python.platform import test - - -class ReachingFndefsAnalyzerTest(test.TestCase): - - def _parse_and_analyze(self, test_fn): - # TODO(mdan): Use a custom FunctionTransformer here. - node, source = parser.parse_entity(test_fn, future_features=()) - entity_info = transformer.EntityInfo( - name=test_fn.__name__, - source_code=source, - source_file=None, - future_features=(), - namespace={}) - node = qual_names.resolve(node) - namer = naming.Namer({}) - ctx = transformer.Context(entity_info, namer, None) - node = activity.resolve(node, ctx) - graphs = cfg.build(node) - node = reaching_definitions.resolve(node, ctx, graphs) - node = reaching_fndefs.resolve(node, ctx, graphs) - return node - - def assertHasFnDefs(self, node): - anno.getanno(node, anno.Static.DEFINED_FNS_IN) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/type_inference.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/type_inference.py deleted file mode 100644 index 6504c2cc..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/type_inference.py +++ /dev/null @@ -1,624 +0,0 @@ -# Copyright 2020 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Type inference. - -This analysis annotates all symbols nodes of an AST with type information -extracted from static sources: - * type annotations - * global and local symbols visible to the function at analysis time - * literals - -Important: This analysis is static, and does not detect dynamic type changes. -The analysis attempts to use the values of external symbols, if available. These -values are also considered static for the purpose of analysis. - -Requires reaching function definitions analysis. -""" - -import itertools - -from typing import Any, Callable, Dict, Set - -import gast - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import cfg -from braket.experimental.autoqasm.autograph.pyct import qual_names -from braket.experimental.autoqasm.autograph.pyct import transformer -from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity -from braket.experimental.autoqasm.autograph.pyct.static_analysis import annos - - -class Resolver(object): - """Resolver objects handle the process of looking up actual names and types. - - Unless noted otherwise, all resolve_* methods: - * have a first namespace argument, mapping string to actual values - * have a second types_namespace argument, mapping string to actual inferred - types - * specify names as QN objects - * specify types as a Set of inferred types - - Unless noted otherwise, all resolve_* methods must return either: - * a set of `type` objects - * None - """ - - def res_name(self, ns, types_ns, name): - """Resolves the type/value an external (e.g. closure, global) variable. - - Args: - ns: namespace - types_ns: types namespace - name: symbol name - Returns: - Tuple (type, static_value). The first element is the type to use for - inferrence. The second is the static value to use. Return None to treat it - as unknown. - """ - raise NotImplementedError('subclasses must implement') - - def res_value(self, ns, value): - """Resolves the type a literal or static value.""" - raise NotImplementedError('subclasses must implement') - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - """Resolves the type of a (possibly annotated) function argument. - - Args: - ns: namespace - types_ns: types namespace - f_name: str, the function name - name: str, the argument name - type_anno: the type annotating the argument, if any - f_is_local: bool, whether the function is a local function - Returns: - Set of the argument types. - """ - raise NotImplementedError('subclasses must implement') - - def res_call(self, ns, types_ns, node, f_type, args, keywords): - """Resolves the return type an external function or method call. - - Args: - ns: namespace - types_ns: types namespace - node: str, the function name - f_type: types of the actual function being called, if known - args: types of each respective argument in node.args - keywords: types of each respective argument in node.keywords - - Returns: - Tuple (return_type, side_effect_types). The first element is just the - return types of the function. The second element is a map from - argument names to sets of types, and allow modelling side effects of - functions (for example via global or nonlocal). - """ - raise NotImplementedError('subclasses must implement') - - # TODO(mdan): Clean this up. - def res_slice(self, ns, types_ns, node_or_slice, value, slice_): - """Resolves the return type of slice operation.""" - raise NotImplementedError('subclasses must implement') - - def res_compare(self, ns, types_ns, node, left, right): - """Resolves the return type of a unary operation.""" - raise NotImplementedError('subclasses must implement') - - def res_unop(self, ns, types_ns, node, opnd): - """Resolves the return type of a unary operation.""" - raise NotImplementedError('subclasses must implement') - - def res_binop(self, ns, types_ns, node, left, right): - """Resolves the return type of a binary operation.""" - raise NotImplementedError('subclasses must implement') - - def res_list_literal(self, ns, elt_types): - """Resolves the type of a list literal from its elements.""" - raise NotImplementedError('subclasses must implement') - - -class _TypeMap(object): - """Abstraction for the state of the CFG walk for type inference. - - This is a value type. Only implements the strictly necessary operators. - - Attributes: - types: Dict[qual_names.QN, Set[Type]], mapping symbols to the set of - possible types. - """ - - def __init__(self, init_from=None): - if init_from: - assert isinstance(init_from, _TypeMap) - self.types = { - s: set(other_types) for s, other_types in init_from.types.items() - } - else: - self.types = {} - - def __eq__(self, other): - if frozenset(self.types.keys()) != frozenset(other.types.keys()): - return False - ret = all(self.types[s] == other.types[s] for s in self.types) - return ret - - def __ne__(self, other): - return not self.__eq__(other) - - def __or__(self, other): - assert isinstance(other, _TypeMap) - result = _TypeMap(self) - for s, other_types in other.types.items(): - if s not in result.types: - self_types = set() - result.types[s] = self_types - else: - self_types = result.types[s] - self_types.update(other_types) - return result - - def __repr__(self): - return 'SymbolTable {}'.format(self.types) - - -NO_VALUE = object() - - -class StmtInferrer(gast.NodeVisitor): - """Runs type inference on a single AST statement. - - This visitor annotates most nodes with type information. It also sets types - for the symbols modified by this statement in its types_out property. - - Note: this inferrer is able to capture side effects of functions, however, - these side effects will not be applied to the current expression. Doing so - would create too much of a dependence on the runtime's internal rules about - execution order. - Example: - - def f(): - nonlocal a - a = 1 - return a - - a = 0.0 - b = f() + a # a = float; side effect of f() ignored - print(a) # a = int; side effect of f() accounted for - """ - - def __init__(self, - resolver: Resolver, - scope: activity.Scope, - namespace: Dict[qual_names.QN, Any], - closure_types: Dict[qual_names.QN, Set[Any]], - types_in: _TypeMap): - self.resolver = resolver - self.scope = scope - self.namespace = namespace - self.closure_types = closure_types - self.types_in = types_in - self.new_symbols = {} - - # rvalue type. This property is set when encountering an assign operation, - # so that visiting nodes with Store ctx (typically found on left side of - # assignments) can infer the type they should receive. - self.rtype = None - - def visit(self, node): - types = super().visit(node) - if __debug__: - self._check_set(types) - if types is not None: - # TODO(mdan): Normalize by removing subtypes. - anno.setanno(node, anno.Static.TYPES, tuple(types)) - return types - - def _check_set(self, value): - if value is not None and not isinstance(value, set): - raise ValueError('{} method expected to return set, got {}'.format( - self.resolver, value)) - - def visit_Constant(self, node): - types = self.resolver.res_value(self.namespace, node.value) - if __debug__: - self._check_set(types) - return types - - def _apply_unpacking(self, node): - assert isinstance(node.ctx, gast.Store) - if self.rtype is not None: - original_stype = self.rtype - # TODO(mdan): Find a better way to express unpacking. - i_type = self.resolver.res_value(self.namespace, 0) - for i, elt in enumerate(node.elts): - self.rtype = self.resolver.res_slice( - self.namespace, self.types_in.types, i, original_stype, i_type) - self.visit(elt) - self.rtype = original_stype - return original_stype - return None - - def visit_Tuple(self, node): - if isinstance(node.ctx, gast.Load): - elt_types = () - for elt in node.elts: - types_ = self.visit(elt) - if types_ is None: - return None - elt_types += (types_,) - return set(itertools.product(*elt_types)) - return self._apply_unpacking(node) - - def visit_List(self, node): - if isinstance(node.ctx, gast.Load): - elt_types = tuple(self.visit(elt) for elt in node.elts) - return self.resolver.res_list_literal(self.namespace, elt_types) - return self._apply_unpacking(node) - - def visit_Set(self, node): - raise NotImplementedError() - - def visit_Name(self, node): - name = anno.getanno(node, anno.Basic.QN) - - if isinstance(node.ctx, gast.Load): - types = self.types_in.types.get(name, None) - if types is None: - if (name not in self.scope.bound) or (name in self.scope.nonlocals): - # TODO(mdan): Test with global variables. - if name in self.closure_types: - types = self.closure_types[name] - else: - types, value = self.resolver.res_name( - self.namespace, self.types_in.types, name) - if value is not None: - anno.setanno(node, anno.Static.VALUE, value) - - elif isinstance(node.ctx, gast.Param): - # The direct parent it the whole function scope. See activity.py. - f_is_local = self.scope.parent.parent is not None - - type_name = anno.getanno(node.annotation, anno.Basic.QN, None) - types = self.resolver.res_arg(self.namespace, self.types_in.types, - self.scope.function_name, name, type_name, - f_is_local) - if types is not None: - self.new_symbols[name] = types - - elif isinstance(node.ctx, gast.Store): - if self.rtype is not None: - self.new_symbols[name] = self.rtype - types = self.rtype - - else: - assert False, 'unknown ctx' - - if __debug__: - self._check_set(types) - - return types - - def visit_Attribute(self, node): - parent_types = self.visit(node.value) - - # Attempt to use the static value if known. - parent_value = anno.Static.VALUE.of(node.value, None) - if parent_value is not None: - static_value = getattr(parent_value, node.attr, NO_VALUE) - - if static_value is NO_VALUE: - # Unexpected failure to resolve attribute. Ask the resolver about the - # full name instead. - types, static_value = self.resolver.res_name( - self.namespace, self.types_in, anno.Basic.QN.of(node)) - anno.setanno(node, anno.Static.VALUE, static_value) - if __debug__: - self._check_set(types) - return types - - else: - # Fall back to the type if that is known. - if parent_types is None: - return None - - inferred_values = [getattr(t, node.attr, None) for t in parent_types] - if not inferred_values: - return None - - static_value = inferred_values[0] - if static_value is None: - return None - - if any(v is not static_value for v in inferred_values[1:]): - # Static value not stable, assume it's dynamic. - return None - - types = self.resolver.res_value(self.namespace, static_value) - anno.setanno(node, anno.Static.VALUE, static_value) - - if __debug__: - self._check_set(types) - - return types - - def visit_FunctionDef(self, node): - f_name = qual_names.QN(node.name) - - if node.decorator_list: - raise NotImplementedError('decorators: {}'.format(node.decorator_list)) - - ret_types = None - if node.returns: - ret_types, _ = self.resolver.res_name( - self.namespace, self.types_in.types, anno.Basic.QN.of(node.returns)) - if __debug__: - self._check_set(ret_types) - - if ret_types is None: - ret_types = {Any} - - f_types = set() - for rt in ret_types: - f_types.add(Callable[[Any], rt]) - - self.new_symbols[f_name] = f_types - # The definition of a function is an expression, hence has no return value. - return None - - def _resolve_typed_callable(self, f_types, arg_types, keyword_types): - ret_types = set() - for t in f_types: - - if isinstance(t, Callable): - # Note: these are undocummented - may be version-specific! - # Callable[[x], y]: __args__ are (x, y) - args = t.__args__ - if args: - ret_types.add(args[-1]) - else: - ret_types.add(Any) - else: - raise NotImplementedError('callable type {}'.format(type(t))) - - # Side effects can not be inferred based on type alone. - side_effects = None - return ret_types, side_effects - - def visit_Call(self, node): - self.visit(node.func) - - f_name = anno.Basic.QN.of(node.func) - arg_types = [self.visit(a) for a in node.args] - keyword_types = [self.visit(kw.value) for kw in node.keywords] - - if f_name in self.scope.bound: - # Local function, use local type definitions, if available. - f_type = self.types_in.types.get(f_name, None) - if f_type is None: - # No static type info available, nothing more to do. - ret_type, side_effects = None, None - else: - ret_type, side_effects = self._resolve_typed_callable( - f_type, arg_types, keyword_types) - - else: - # Nonlocal function, resolve externally. - f_type = anno.Static.TYPES.of(node.func, None) - ret_type, side_effects = self.resolver.res_call(self.namespace, - self.types_in.types, node, - f_type, arg_types, - keyword_types) - - if __debug__: - self._check_set(ret_type) - if side_effects: - if not isinstance(side_effects, dict): - raise ValueError( - 'side effects must be dict, got {}'.format(side_effects)) - for k, v in side_effects.items(): - if not isinstance(k, qual_names.QN): - raise ValueError('side effect keys must be QNs, got {}'.format(k)) - self._check_set(v) - - if side_effects: - self.new_symbols.update(side_effects) - return ret_type - - def visit_Expr(self, node): - return self.visit(node.value) - - def visit_Assign(self, node): - self.rtype = self.visit(node.value) - - for t in node.targets: - self.visit(t) - - self.rtype = None - - def visit_Subscript(self, node): - val_types = self.visit(node.value) - slice_types = self.visit(node.slice) - - if val_types is None or slice_types is None: - return None - - types = self.resolver.res_slice( - self.namespace, self.types_in.types, node, val_types, slice_types) - - if __debug__: - self._check_set(types) - - return types - - def visit_Compare(self, node): - left_types = self.visit(node.left) - right_types = [self.visit(c) for c in node.comparators] - - if left_types is None or any(t is None for t in right_types): - return None - - types = self.resolver.res_compare( - self.namespace, self.types_in.types, node, left_types, right_types) - - if __debug__: - self._check_set(types) - - return types - - def visit_BinOp(self, node): - left_types = self.visit(node.left) - right_types = self.visit(node.right) - - if left_types is None or right_types is None: - return None - - types = self.resolver.res_binop( - self.namespace, self.types_in.types, node, left_types, right_types) - - if __debug__: - self._check_set(types) - - return types - - def visit_UnaryOp(self, node): - opnd_types = self.visit(node.operand) - - if opnd_types is None: - return None - - types = self.resolver.res_unop( - self.namespace, self.types_in.types, node, opnd_types) - - if __debug__: - self._check_set(types) - - return types - - -class Analyzer(cfg.GraphVisitor): - """CFG visitor that propagates type information across statements.""" - - def __init__(self, graph, resolver, namespace, scope, closure_types): - """Creates a new analyzer. - - Args: - graph: cfg.Graph - resolver: Resolver - namespace: Dict[str, Any] - scope: activity.Scope - closure_types: Dict[QN, Set] - """ - super(Analyzer, self).__init__(graph) - self.resolver = resolver - self.namespace = namespace - self.scope = scope - self.closure_types = closure_types - - context_types = { - n: t for n, t in closure_types.items() if n not in scope.bound - } - if context_types: - self.context_types = _TypeMap() - self.context_types.types = context_types - else: - self.context_types = None - - def init_state(self, _): - return _TypeMap() - - def _update_closure_types(self, ast_node, types): - existing_types = anno.Static.CLOSURE_TYPES.of(ast_node, None) - - if existing_types is None: - existing_types = {} - anno.Static.CLOSURE_TYPES.add_to(ast_node, existing_types) - - for k, v in types.types.items(): - if k in existing_types: - existing_types[k].update(v) - else: - existing_types[k] = set(v) - - def visit_node(self, node): - prev_types_out = self.out[node] - - types_in = _TypeMap() - for n in node.prev: - types_in |= self.out[n] - if (self.context_types is not None) and (node is self.graph.entry): - types_in |= self.context_types - - types_out = _TypeMap(types_in) - ast_node = node.ast_node - - inferrer = StmtInferrer(self.resolver, self.scope, self.namespace, - self.closure_types, types_in) - inferrer.visit(ast_node) - types_out.types.update(inferrer.new_symbols) - - reaching_fndefs = anno.Static.DEFINED_FNS_IN.of(ast_node) - node_scope = anno.Static.SCOPE.of(ast_node, None) - if node_scope is not None: - # TODO(mdan): Check that it's actually safe to skip nodes without scope. - reads = {str(qn) for qn in node_scope.read} - for def_node in reaching_fndefs: - if def_node.name in reads: - self._update_closure_types(def_node, types_out) - - self.in_[node] = types_in - self.out[node] = types_out - - return prev_types_out != types_out - - -class FunctionVisitor(transformer.Base): - """AST visitor that applies type inference to each function separately.""" - - def __init__(self, source_info, graphs, resolver): - super(FunctionVisitor, self).__init__(source_info) - self.graphs = graphs - self.resolver = resolver - - def visit_FunctionDef(self, node): - subgraph = self.graphs[node] - scope = anno.getanno(node, annos.NodeAnno.ARGS_AND_BODY_SCOPE) - closure_types = anno.getanno(node, anno.Static.CLOSURE_TYPES, {}) - - analyzer = Analyzer(subgraph, self.resolver, self.ctx.info.namespace, scope, - closure_types) - analyzer.visit_forward() - - # Recursively process any remaining subfunctions. - node.body = self.visit_block(node.body) - - return node - - -def resolve(node, source_info, graphs, resolver): - """Performs type inference. - - Args: - node: ast.AST - source_info: transformer.SourceInfo - graphs: Dict[ast.FunctionDef, cfg.Graph] - resolver: Resolver - - Returns: - ast.AST - """ - visitor = FunctionVisitor(source_info, graphs, resolver) - node = visitor.visit(node) - return node diff --git a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/type_inference_test.py b/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/type_inference_test.py deleted file mode 100644 index 4dc4dbe0..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/static_analysis/type_inference_test.py +++ /dev/null @@ -1,942 +0,0 @@ -# Copyright 2020 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for type_inference module.""" - -from typing import Any, Callable, List - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import cfg -from braket.experimental.autoqasm.autograph.pyct import qual_names -from braket.experimental.autoqasm.autograph.pyct import transpiler -from braket.experimental.autoqasm.autograph.pyct.static_analysis import activity -from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_definitions -from braket.experimental.autoqasm.autograph.pyct.static_analysis import reaching_fndefs -from braket.experimental.autoqasm.autograph.pyct.static_analysis import type_inference -from tensorflow.python.platform import test - - -class BasicTestResolver(type_inference.Resolver): - """A very basic resolver for testing.""" - - def res_name(self, ns, types_ns, name): - str_name = str(name) - if str_name == 'int': - return {int}, int - return {type(ns[str_name])}, ns[str_name] - - def res_value(self, ns, value): - return {type(value)} - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - if type_anno is None: - return None - return {str(type_anno)} - - -class TestTranspiler(transpiler.GenericTranspiler): - - def __init__(self, resolver_type): - super().__init__() - self.resolver = resolver_type() - - def get_transformed_name(self, _): - return 'test_item' - - def transform_ast(self, node, ctx): - node = qual_names.resolve(node) - node = activity.resolve(node, ctx) - graphs = cfg.build(node) - node = reaching_definitions.resolve(node, ctx, graphs) - node = reaching_fndefs.resolve(node, ctx, graphs) - node = type_inference.resolve(node, ctx, graphs, self.resolver) - return node - - -class TypeInferenceAnalyzerTest(test.TestCase): - - def assertTypes(self, node, expected): - if not isinstance(expected, tuple): - expected = expected, - self.assertSetEqual( - set(anno.getanno(node, anno.Static.TYPES)), set(expected)) - - def assertClosureTypes(self, node, expected): - actual = anno.getanno(node, anno.Static.CLOSURE_TYPES) - actual = {str(k): v for k, v in actual.items()} - for k, v in expected.items(): - self.assertIn(k, actual) - self.assertEqual(actual[k], v) - - def test_no_inference_on_unknown_operand_types(self): - - class Resolver(type_inference.Resolver): - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - return None - - def test_fn(a, b): - return a < b, a - b - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - # With no information on operand types, the operators will infer nothing. - self.assertFalse( - anno.hasanno(fn_body[0].value.elts[0], anno.Static.TYPES)) - self.assertFalse( - anno.hasanno(fn_body[0].value.elts[1], anno.Static.TYPES)) - - def test_resolver_output_checked(self): - - class Resolver(type_inference.Resolver): - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - return 1 - - def test_fn(a): - del a - pass - - with self.assertRaisesRegex(ValueError, 'expected to return set'): - TestTranspiler(Resolver).transform(test_fn, None) - - def test_argument(self): - - test_self = self - - class Resolver(type_inference.Resolver): - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - test_self.assertFalse(f_is_local) - if name == qual_names.QN('a'): - test_self.assertEqual(type_anno, qual_names.QN('int')) - return {str(name) + '_type'} - - def test_fn(a: int, b): - return a, b - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].value.elts[0], 'a_type') - self.assertTypes(fn_body[0].value.elts[1], 'b_type') - - def test_argument_of_local_function(self): - - test_self = self - - class Resolver(type_inference.Resolver): - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - if f_name == 'test_fn': - test_self.assertFalse(f_is_local) - test_self.assertEqual(name, qual_names.QN('a')) - test_self.assertEqual(type_anno, qual_names.QN('int')) - elif f_name == 'foo': - test_self.assertTrue(f_is_local) - if name == qual_names.QN('x'): - test_self.assertEqual(type_anno, qual_names.QN('float')) - elif name == qual_names.QN('y'): - test_self.assertIsNone(type_anno) - else: - test_self.fail('unexpected argument {} for {}'.format(name, f_name)) - else: - test_self.fail('unexpected function name {}'.format(f_name)) - return {str(name) + '_type'} - - def test_fn(a: int): - - def foo(x: float, y): - return x, y - - return foo(a, a) - - tr = TestTranspiler(Resolver) - node, _ = tr.transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].body[0].value, (('x_type', 'y_type'),)) - self.assertTypes(fn_body[0].body[0].value.elts[0], 'x_type') - self.assertTypes(fn_body[0].body[0].value.elts[1], 'y_type') - - def test_assign_straightline(self): - - def test_fn(a: int, c: float): - b = a - return a, b, c - - node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].targets[0], 'int') - self.assertTypes(fn_body[0].value, 'int') - self.assertTypes(fn_body[1].value.elts[0], 'int') - self.assertTypes(fn_body[1].value.elts[1], 'int') - self.assertTypes(fn_body[1].value.elts[2], 'float') - - def test_expr(self): - - test_self = self - - class Resolver(type_inference.Resolver): - - def res_value(self, ns, value): - test_self.assertEqual(value, tc.a) - return {str} - - def res_name(self, ns, types_ns, name): - test_self.assertEqual(name, qual_names.QN('tc')) - return {TestClass}, tc - - def res_call(self, ns, types_ns, node, f_type, args, keywords): - test_self.assertEqual(f_type, (str,)) - return {int}, None - - class TestClass: - - def a(self): - pass - - tc = TestClass() - - def test_fn(): - tc.a() - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertEqual( - anno.getanno(fn_body[0].value.func, anno.Static.VALUE), tc.a) - self.assertTypes(fn_body[0].value.func, str) - self.assertTypes(fn_body[0].value, int) - self.assertTypes(fn_body[0], int) - - def test_assign_overwriting(self): - - def test_fn(a: int, b: float): - c = a - c = b - return c - - node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].targets[0], 'int') - self.assertTypes(fn_body[0].value, 'int') - self.assertTypes(fn_body[1].targets[0], 'float') - self.assertTypes(fn_body[1].value, 'float') - - def test_dynamic_attribute_of_static_value(self): - - test_self = self - - class Resolver(type_inference.Resolver): - - def res_value(self, ns, value): - test_self.assertEqual(value, tc.a) - return {int} - - def res_name(self, ns, types_ns, name): - test_self.assertEqual(name, qual_names.QN('tc')) - return {TestClass}, tc - - class TestClass: - - def __init__(self): - self.a = 1 - - tc = TestClass() - - def test_fn(): - return tc.a - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].value.value, TestClass) - self.assertTypes(fn_body[0].value, int) - self.assertIs(anno.getanno(fn_body[0].value.value, anno.Static.VALUE), tc) - self.assertEqual(anno.getanno(fn_body[0].value, anno.Static.VALUE), tc.a) - - def test_static_attribute_of_typed_value(self): - - test_self = self - - class TestClass: - - a = 1 - - tc = TestClass() - - class Resolver(type_inference.Resolver): - - def res_name(self, ns, types_ns, name): - test_self.assertEqual(name, qual_names.QN('tc')) - return {TestClass}, None - - def res_value(self, ns, value): - test_self.assertIs(value, tc.a) - return {str} - - def test_fn(): - return tc.a - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].value.value, TestClass) - self.assertTypes(fn_body[0].value, str) # Resolver is SOT - self.assertFalse(anno.hasanno(fn_body[0].value.value, anno.Static.VALUE)) - self.assertEqual(anno.getanno(fn_body[0].value, anno.Static.VALUE), 1) - - def test_static_attribute_of_ambiguous_type(self): - - test_self = self - - class TestClass1: - - a = 1 - - class TestClass2: - - a = 2 - - tc = TestClass1() - - class Resolver(type_inference.Resolver): - - def res_name(self, ns, types_ns, name): - test_self.assertEqual(name, qual_names.QN('tc')) - return {TestClass1, TestClass2}, None - - def res_value(self, ns, value): - test_self.assertIn(value, (1, 2)) - return {str} - - def test_fn(): - return tc.a - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].value.value, (TestClass1, TestClass2)) - self.assertFalse(anno.hasanno(fn_body[0].value, anno.Static.TYPES)) - self.assertFalse(anno.hasanno(fn_body[0].value.value, anno.Static.VALUE)) - self.assertFalse(anno.hasanno(fn_body[0].value, anno.Static.VALUE)) - - def test_property_of_typed_value(self): - - test_self = self - - class TestClass: - - @property - def a(self): - return 1 - - tc = TestClass() - - class Resolver(type_inference.Resolver): - - def res_name(self, ns, types_ns, name): - test_self.assertEqual(name, qual_names.QN('tc')) - return {TestClass}, None - - def res_value(self, ns, value): - test_self.assertIs(value, TestClass.a) - test_self.assertNotEqual(value, 1) # Can't evaluate property of class. - return {property} - - def test_fn(): - return tc.a - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].value.value, TestClass) - self.assertTypes(fn_body[0].value, property) - self.assertFalse(anno.hasanno(fn_body[0].value.value, anno.Static.VALUE)) - self.assertEqual( - anno.getanno(fn_body[0].value, anno.Static.VALUE), TestClass.a) - - def test_dynamic_attribute_of_typed_value(self): - - test_self = self - - class TestClass: - - def __init__(self): - self.a = 1 - - tc = TestClass() - - class Resolver(type_inference.Resolver): - - def res_name(self, ns, types_ns, name): - test_self.assertEqual(name, qual_names.QN('tc')) - return {TestClass}, None - - def test_fn(): - return tc.a - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].value.value, TestClass) - self.assertFalse(anno.hasanno(fn_body[0].value, anno.Static.TYPES)) - self.assertFalse(anno.hasanno(fn_body[0].value.value, anno.Static.VALUE)) - self.assertFalse(anno.hasanno(fn_body[0].value, anno.Static.VALUE)) - - def test_external_value(self): - - a = 'foo' - - def test_fn(): - b = a - return b - - node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].targets[0], str) - self.assertTypes(fn_body[1].value, str) - - def test_external_function(self): - - test_self = self - - class Resolver(type_inference.Resolver): - - def res_name(self, ns, types_ns, name): - test_self.assertEqual(name, qual_names.QN('g')) - return {str}, g - - def res_call(self, ns, types_ns, node, f_type, args, keywords): - test_self.assertEqual(f_type, (str,)) - test_self.assertEqual( - anno.getanno(node.func, anno.Basic.QN), qual_names.QN('g')) - return {float}, None - - def g() -> float: - return 1.0 - - def test_fn(): - a = g() - return a - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].value.func, str) - self.assertTypes(fn_body[0].targets[0], float) - self.assertTypes(fn_body[1].value, float) - - def test_external_function_side_effects(self): - - test_self = self - - class Resolver(type_inference.Resolver): - - def res_name(self, ns, types_ns, name): - test_self.assertEqual(name, qual_names.QN('g')) - return None, g - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - return {str(type_anno)} - - def res_call(self, ns, types_ns, node, f_type, args, keywords): - test_self.assertIsNone(f_type) - return None, {qual_names.QN('x'): {str}} - - def g(): - # The resolver will pretend that this function has the following body: - # - # nonlocal x - # x = 'a' - pass - - def test_fn(x: int): - y = x - g() - return x, y - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].targets[0], 'int') - self.assertTypes(fn_body[0].value, 'int') - self.assertTypes(fn_body[2].value.elts[0], str) - self.assertTypes(fn_body[2].value.elts[1], 'int') - - def test_local_function_closure(self): - - def test_fn(x: int): - - def foo(): - return x - - foo() - - node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].body[0].value, 'int') - self.assertClosureTypes(fn_body[0], {'x': {'int'}}) - - def test_local_function_closure_nested(self): - - def test_fn(x: int): - - def foo(): - - def bar(): - return x - - bar() - - foo() - - node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].body[0].body[0].value, 'int') - self.assertClosureTypes(fn_body[0], {'x': {'int'}}) - self.assertClosureTypes(fn_body[0].body[0], {'x': {'int'}}) - - def test_local_function_closure_mutable_var(self): - - def test_fn(x: int): - - def foo(): - nonlocal x - return x - - foo() - - node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].body[1].value, 'int') - self.assertClosureTypes(fn_body[0], {'x': {'int'}}) - - def test_local_function_closure_ignored_for_bound_symbols(self): - - def test_fn(x: float): # pylint:disable=unused-argument - - def foo(): - x = x + 1 # pylint:disable=used-before-assignment - - foo() - - node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) - fn_body = node.body - - self.assertFalse( - anno.hasanno(fn_body[0].body[0].value.left, anno.Static.TYPES)) - self.assertClosureTypes(fn_body[0], {'x': {'float'}}) - - def test_local_function_closure_uses_call_site_types(self): - - def test_fn(x: int): - - def foo(): - return x - - x = 1.0 - foo() - - node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].body[0].value, float) - self.assertTypes(fn_body[1].targets[0], float) - self.assertClosureTypes(fn_body[0], {'x': {float}}) - - def test_local_function_hides_locals(self): - - def test_fn(a: int): # pylint:disable=unused-argument - - def local_fn(v): - a = v - return a - - local_fn(1) - - node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) - fn_body = node.body - - self.assertFalse( - anno.hasanno(fn_body[0].body[0].targets[0], anno.Static.TYPES)) - - def test_local_function_type(self): - - def test_fn(x: int): - - def foo() -> int: - return x - - foo() - - node, _ = TestTranspiler(BasicTestResolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[1].value.func, Callable[[Any], int]) - self.assertTypes(fn_body[1].value, int) - self.assertTypes(fn_body[1], int) - - def test_side_effects_on_arg_function_closure(self): - - test_self = self - - class Resolver(type_inference.Resolver): - - def res_name(self, ns, types_ns, name): - test_self.assertEqual(name, qual_names.QN('g')) - return {Callable[[Callable], None]}, g - - def res_value(self, ns, value): - test_self.assertEqual(value, 1.0) - return {float} - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - return {str(type_anno)} - - def res_call(self, ns, types_ns, node, f_type, args, keywords): - test_self.assertEqual(node.func.id, 'g') - test_self.assertEqual(f_type, (Callable[[Callable], None],)) - return None, {qual_names.QN('x'): {str}} - - def g(foo): - # The resolver will convey that this function has the following body: - # - # nonlocal x - # x = 'a' - # foo() - del foo - pass - - def test_fn(x: int): # pylint:disable=unused-argument - - def foo(): - return x - - x = 1.0 - g(foo) - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].body[0].value, str) - - def test_subscript(self): - - test_self = self - - class Resolver(type_inference.Resolver): - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - return {list} - - def res_value(self, ns, value): - return {int} - - def res_slice(self, ns, types_ns, node, value, slice_): - test_self.assertSetEqual(value, {list}) - test_self.assertSetEqual(slice_, {int}) - return {str} - - def test_fn(a): - return a[1] - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].value, str) - self.assertTypes(fn_body[0].value.value, list) - self.assertTypes(fn_body[0].value.slice, int) - - def test_tuple_unpacking(self): - - test_self = self - - class Resolver(type_inference.Resolver): - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - return {list} - - def res_value(self, ns, value): - return {int} - - def res_slice(self, ns, types_ns, node_or_slice, value, slice_): - test_self.assertIn(node_or_slice, (0, 1)) - test_self.assertSetEqual(value, {list}) - test_self.assertSetEqual(slice_, {int}) - if node_or_slice == 0: - return {float} - else: - return {str} - - def test_fn(t): - a, b = t - return a, b - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[1].value, ((float, str),)) - self.assertTypes(fn_body[1].value.elts[0], float) - self.assertTypes(fn_body[1].value.elts[1], str) - - def test_compare(self): - - test_self = self - - class Resolver(type_inference.Resolver): - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - return {int} - - def res_compare(self, ns, types_ns, node, left, right): - test_self.assertSetEqual(left, {int}) - test_self.assertListEqual(right, [{int}]) - return {bool} - - def test_fn(a, b): - return a < b - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].value, bool) - self.assertTypes(fn_body[0].value.left, int) - self.assertTypes(fn_body[0].value.comparators[0], int) - - def test_binop(self): - - test_self = self - - class Resolver(type_inference.Resolver): - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - return {list} - - def res_binop(self, ns, types_ns, node, left, right): - test_self.assertSetEqual(left, {list}) - test_self.assertSetEqual(right, {list}) - return {float} - - def test_fn(a, b): - return a @ b - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].value, float) - self.assertTypes(fn_body[0].value.left, list) - self.assertTypes(fn_body[0].value.right, list) - - def test_unop(self): - - class Resolver(type_inference.Resolver): - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - return {list} - - def res_unop(self, ns, types_ns, node, opnd): - return {float} - - def test_fn(a): - return -a - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].value, float) - self.assertTypes(fn_body[0].value.operand, list) - - def test_tuple_literal(self): - - class Resolver(type_inference.Resolver): - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - return {int} - - def test_fn(a, b): - return a, b - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].value, ((int, int),)) - self.assertTypes(fn_body[0].value.elts[0], int) - self.assertTypes(fn_body[0].value.elts[1], int) - - def test_list_literal(self): - - class Resolver(type_inference.Resolver): - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - return {int} - - def res_list_literal(self, ns, elt_types): - all_types = set() - for s in elt_types: - all_types |= s - return {List[t] for t in all_types} - - def test_fn(a, b): - return [a, b] - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[0].value, List[int]) - self.assertTypes(fn_body[0].value.elts[0], int) - self.assertTypes(fn_body[0].value.elts[1], int) - - def test_tuple_unpacking_syntactic(self): - - test_self = self - - class Resolver(type_inference.Resolver): - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - if name == qual_names.QN('a'): - return {int} - else: - return {float} - - def res_value(self, ns, value): - test_self.assertIn(value, (0, 1)) - return int - - def res_slice(self, ns, types_ns, node_or_slice, value, slice_): - test_self.assertIn(node_or_slice, (0, 1)) - test_self.assertSetEqual(value, {(int, float)}) - test_self.assertEqual(slice_, int) - return {t[node_or_slice] for t in value} - - def test_fn(a, b): - c, d = a, b - return c, d - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[1].value, ((int, float),)) - self.assertTypes(fn_body[1].value.elts[0], int) - self.assertTypes(fn_body[1].value.elts[1], float) - - def test_tuple_unpacking_operational(self): - - test_self = self - - class Resolver(type_inference.Resolver): - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - return {(int, float)} - - def res_value(self, ns, value): - test_self.assertIn(value, (0, 1)) - return int - - def res_slice(self, ns, types_ns, node_or_slice, value, slice_): - test_self.assertIn(node_or_slice, (0, 1)) - test_self.assertSetEqual(value, {(int, float)}) - test_self.assertEqual(slice_, int) - return {t[node_or_slice] for t in value} - - def test_fn(a): - c, d = a - return c, d - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - self.assertTypes(fn_body[1].value, ((int, float),)) - self.assertTypes(fn_body[1].value.elts[0], int) - self.assertTypes(fn_body[1].value.elts[1], float) - - def test_list_expansion_syntactic(self): - - test_self = self - - class Resolver(type_inference.Resolver): - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - if name == qual_names.QN('a'): - return {int} - else: - return {float} - - def res_value(self, ns, value): - test_self.assertIn(value, (0, 1)) - return int - - def res_slice(self, ns, types_ns, node_or_slice, value, slice_): - test_self.assertIn(node_or_slice, (0, 1)) - test_self.assertSetEqual(value, {(int, float)}) - test_self.assertEqual(slice_, int) - return {t[node_or_slice] for t in value} - - def test_fn(a, b): - [c, d] = a, b - return c, d - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - # TODO(mdan): Whether it's List or Tuple might be open for interpretation. - self.assertTypes(fn_body[1].value, ((int, float),)) - self.assertTypes(fn_body[1].value.elts[0], int) - self.assertTypes(fn_body[1].value.elts[1], float) - - def test_list_expansion_operational(self): - - test_self = self - - class Resolver(type_inference.Resolver): - - def res_arg(self, ns, types_ns, f_name, name, type_anno, f_is_local): - if name == qual_names.QN('a'): - return {int} - else: - return {float} - - def res_value(self, ns, value): - test_self.assertIn(value, (0, 1)) - return int - - def res_slice(self, ns, types_ns, node_or_slice, value, slice_): - test_self.assertIn(node_or_slice, (0, 1)) - test_self.assertSetEqual(value, {(int, float)}) - test_self.assertEqual(slice_, int) - return {t[node_or_slice] for t in value} - - def test_fn(a, b): - [c, d] = a, b - return c, d - - node, _ = TestTranspiler(Resolver).transform(test_fn, None) - fn_body = node.body - - # TODO(mdan): Whether it's List or Tuple might be open for interpretation. - self.assertTypes(fn_body[1].value, ((int, float),)) - self.assertTypes(fn_body[1].value.elts[0], int) - self.assertTypes(fn_body[1].value.elts[1], float) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/templates.py b/src/braket/experimental/autoqasm/autograph/pyct/templates.py deleted file mode 100644 index 068a36b7..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/templates.py +++ /dev/null @@ -1,290 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""AST conversion templates. - -Adapted from Tangent. -""" - -import ast -import textwrap - -import gast - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import ast_util -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import qual_names - - -class ContextAdjuster(gast.NodeTransformer): - """Adjusts the ctx field of nodes to ensure consistency. - - This transformer can change the ctx fields of a variable, tuple and other - AST elements that allow one, based on whether the element is being read or - written. - """ - - def __init__(self, override_value): - self._ctx_override = override_value - - def visit(self, node): - original_override = self._ctx_override - node = super(ContextAdjuster, self).visit(node) - if hasattr(node, 'ctx'): - assert node.ctx is not None, 'node {} has ctx unset'.format(node) - self._ctx_override = original_override - return node - - def _apply_override(self, node): - if self._ctx_override is not None: - node.ctx = self._ctx_override() - - def visit_Attribute(self, node): - self._apply_override(node) - self._ctx_override = gast.Load - node = self.generic_visit(node) - return node - - def visit_Tuple(self, node): - self._apply_override(node) - return self.generic_visit(node) - - def visit_List(self, node): - self._apply_override(node) - return self.generic_visit(node) - - def visit_Name(self, node): - self._apply_override(node) - return self.generic_visit(node) - - def visit_Call(self, node): - self._apply_override(node) - # We may be able to override these to Load(), but for now it's simpler - # to just assert that they're set. - self._ctx_override = None - return self.generic_visit(node) - - def visit_Dict(self, node): - # We may be able to override these to Load(), but for now it's simpler - # to just assert that they're set. - self._ctx_override = None - return self.generic_visit(node) - - def visit_Subscript(self, node): - self._apply_override(node) - self._ctx_override = gast.Load - node.value = self.visit(node.value) - return self.generic_visit(node) - - def visit_comprehension(self, node): - # We may be able to override some of these, but for now it's simpler - # to just assert that they're set. - self._ctx_override = None - return self.generic_visit(node) - - def visit_Lambda(self, node): - # We may be able to override some of these, but for now it's simpler - # to just assert that they're set. - self._ctx_override = None - return self.generic_visit(node) - - -class ReplaceTransformer(gast.NodeTransformer): - """Replace AST nodes.""" - - def __init__(self, replacements): - """Create a new ReplaceTransformer. - - Args: - replacements: A mapping from placeholder names to (lists of) AST nodes - that these placeholders will be replaced by. - """ - self.replacements = replacements - self.in_replacements = False - self.preserved_annos = { - anno.Basic.DIRECTIVES, - anno.Basic.EXTRA_LOOP_TEST, - anno.Basic.ORIGIN, - anno.Basic.SKIP_PROCESSING, - anno.Static.ORIG_DEFINITIONS, - 'function_context_name', - } - - def _prepare_replacement(self, replaced, key): - """Prepares a replacement AST that's safe to swap in for a node. - - Args: - replaced: ast.AST, the node being replaced - key: Hashable, the key of the replacement AST - Returns: - ast.AST, the replacement AST - """ - repl = self.replacements[key] - - new_nodes = ast_util.copy_clean(repl, preserve_annos=self.preserved_annos) - if isinstance(new_nodes, gast.AST): - new_nodes = [new_nodes] - - return new_nodes - - def visit_Expr(self, node): - # When replacing a placeholder with an entire statement, the replacement - # must stand on its own and not be wrapped in an Expr. - new_value = self.visit(node.value) - if new_value is node.value: - return node - return new_value - - def visit_keyword(self, node): - if node.arg not in self.replacements: - return self.generic_visit(node) - - repl = self._prepare_replacement(node, node.arg) - if isinstance(repl, gast.keyword): - return repl - elif (repl and isinstance(repl, (list, tuple)) and - all(isinstance(r, gast.keyword) for r in repl)): - return repl - # TODO(mdan): We may allow replacing with a string as well. - # For example, if one wanted to replace foo with bar in foo=baz, then - # we could allow changing just node arg, so that we end up with bar=baz. - raise ValueError( - 'a keyword argument may only be replaced by another keyword or a ' - 'non-empty list of keywords. Found: {} for keyword {}'.format( - repl, node.arg)) - - def visit_FunctionDef(self, node): - node = self.generic_visit(node) - if node.name not in self.replacements: - return node - - repl = self.replacements[node.name] - if not isinstance(repl, (gast.Name, ast.Name)): - raise ValueError( - 'a function name can only be replaced by a Name node. Found: %s' % - repl) - node.name = repl.id - return node - - def visit_Attribute(self, node): - node = self.generic_visit(node) - if node.attr not in self.replacements: - return node - - repl = self.replacements[node.attr] - if not isinstance(repl, gast.Name): - raise ValueError( - 'An attribute can only be replaced by a Name node. Found: %s' % repl) - node.attr = repl.id - return node - - def visit_Name(self, node): - if node.id not in self.replacements: - return node - - new_nodes = self._prepare_replacement(node, node.id) - - if not new_nodes: - return new_nodes - - # Preserve the target context. - adjuster = ContextAdjuster(type(node.ctx)) - for n in new_nodes: - if hasattr(n, 'ctx'): - adjuster.visit(n) - - if len(new_nodes) == 1: - new_nodes, = new_nodes - - return new_nodes - - -def _convert_to_ast(n): - """Converts from a known data type to AST.""" - # Note: When generating AST nodes from strings/QNs in isolation, ctx is - # unknown. ctx must be filled in according to the template being used. - # See ReplaceTransformer.visit_Name. - if isinstance(n, str): - return gast.Name(id=n, ctx=None, annotation=None, type_comment=None) - if isinstance(n, qual_names.QN): - return n.ast() - if isinstance(n, list): - return [_convert_to_ast(e) for e in n] - if isinstance(n, tuple): - return tuple(_convert_to_ast(e) for e in n) - return n - - -def replace(template, **replacements): - """Replaces placeholders in a Python template. - - AST Name and Tuple nodes always receive the context that inferred from - the template. However, when replacing more complex nodes (that can potentially - contain Name children), then the caller is responsible for setting the - appropriate context. - - Args: - template: A string representing Python code. Any symbol name can be used - that appears in the template code can be used as placeholder. - **replacements: A mapping from placeholder names to (lists of) AST nodes - that these placeholders will be replaced by. String values are also - supported as a shorthand for AST Name nodes with the respective ID. - - Returns: - An AST node or list of AST nodes with the replacements made. If the - template was a function, a list will be returned. If the template was a - node, the same node will be returned. If the template was a string, an - AST node will be returned (a `Module` node in the case of a multi-line - string, an `Expr` node otherwise). - - Raises: - ValueError: if the arguments are incorrect. - """ - if not isinstance(template, str): - raise ValueError('Expected string template, got %s' % type(template)) - for k in replacements: - replacements[k] = _convert_to_ast(replacements[k]) - template_str = parser.STANDARD_PREAMBLE + textwrap.dedent(template) - nodes = parser.parse( - template_str, - preamble_len=parser.STANDARD_PREAMBLE_LEN, - single_node=False) - results = [] - for node in nodes: - node = ReplaceTransformer(replacements).visit(node) - if isinstance(node, (list, tuple)): - results.extend(node) - else: - results.append(node) - results = [qual_names.resolve(r) for r in results] - return results - - -def replace_as_expression(template, **replacements): - """Variant of replace that generates expressions, instead of code blocks.""" - replacement = replace(template, **replacements) - if len(replacement) != 1: - raise ValueError( - 'single expression expected; for more general templates use replace') - node, = replacement - - if isinstance(node, gast.Expr): - return node.value - elif isinstance(node, gast.Name): - return node - - raise ValueError( - 'the template is expected to generate an expression or a name node;' - ' instead found %s' % node) diff --git a/src/braket/experimental/autoqasm/autograph/pyct/templates_test.py b/src/braket/experimental/autoqasm/autograph/pyct/templates_test.py deleted file mode 100644 index 8980e2c6..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/templates_test.py +++ /dev/null @@ -1,338 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for templates module.""" - -import imp - -from absl.testing import parameterized -import gast - -from braket.experimental.autoqasm.autograph.pyct import loader -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import qual_names as qn -from braket.experimental.autoqasm.autograph.pyct import templates -from tensorflow.python.platform import test - - -class _CtxClearer(gast.NodeTransformer): - - def visit(self, node): - super(_CtxClearer, self).visit(node) - if hasattr(node, 'ctx'): - node.ctx = None - return node - - -def _parse_with_unset_ctx(expr_source): - ast_node = parser.parse_expression(expr_source) - _CtxClearer().visit(ast_node) - return ast_node - - -class _CtxChecker(gast.NodeTransformer): - - def __init__(self, test_instance, expected_ctx): - self.at_top_level = True - self.test_instance = test_instance - self.expected_ctx = expected_ctx - - def visit(self, node): - if hasattr(node, 'ctx'): - self.test_instance.assertIsInstance(node.ctx, self.expected_ctx) - if self.at_top_level: - self.at_top_level = False - self.expected_ctx = gast.Load - return super(_CtxChecker, self).visit(node) - - -class TemplatesTest(test.TestCase, parameterized.TestCase): - - def assertExpectedCtxSet(self, node, ctx): - """Assert that node has ctx=ctx at top and ctx=gast.Load everywhere else.""" - checker = _CtxChecker(self, ctx) - checker.visit(node) - - def test_replace_tuple(self): - template = """ - def test_fn(a, c): - return b, - """ - - node = templates.replace(template, b=('a', 'c'))[0] - result, _, _ = loader.load_ast(node) - - self.assertEqual((2, 3), result.test_fn(2, 3)) - - def test_replace_variable(self): - template = """ - def test_fn(a): - a += 1 - a = 2 * a + 1 - return b - """ - - node = templates.replace(template, a='b')[0] - result, _, _ = loader.load_ast(node) - self.assertEqual(7, result.test_fn(2)) - - def test_replace_function_name(self): - template = """ - def fname(a): - a += 1 - a = 2 * a + 1 - return a - """ - - node = templates.replace(template, fname='test_fn')[0] - result, _, _ = loader.load_ast(node) - self.assertEqual(7, result.test_fn(2)) - - def test_replace_code_block(self): - template = """ - def test_fn(a): - block - return a - """ - - class ShouldBeReplaced(object): - pass - - node = templates.replace( - template, - block=[ - gast.Assign( - [ - gast.Name( - 'a', - ctx=ShouldBeReplaced, - annotation=None, - type_comment=None) - ], - gast.BinOp( - gast.Name( - 'a', - ctx=ShouldBeReplaced, - annotation=None, - type_comment=None), gast.Add(), - gast.Constant(1, kind=None)), - ), - ] * 2)[0] - result, _, _ = loader.load_ast(node) - self.assertEqual(3, result.test_fn(1)) - - def test_replace_attribute(self): - template = """ - def test_fn(a): - return a.foo - """ - - node = templates.replace(template, foo='b')[0] - result, _, _ = loader.load_ast(node) - mod = imp.new_module('test') - mod.b = 3 - self.assertEqual(3, result.test_fn(mod)) - - with self.assertRaises(ValueError): - templates.replace(template, foo=1) - - def test_replace_attribute_context(self): - template = """ - def test_fn(foo): - foo = 0 - """ - - node = templates.replace( - template, - foo=parser.parse_expression('a.b.c'))[0] - self.assertIsInstance(node.body[0].targets[0].ctx, gast.Store) - self.assertIsInstance(node.body[0].targets[0].value.ctx, gast.Load) - self.assertIsInstance(node.body[0].targets[0].value.value.ctx, gast.Load) - - def test_replace_list_context(self): - template = """ - def test_fn(foo): - foo = 0 - """ - - node = templates.replace(template, foo=parser.parse_expression('[a, b]'))[0] - self.assertIsInstance(node.body[0].targets[0].ctx, gast.Store) - self.assertIsInstance(node.body[0].targets[0].elts[0].ctx, gast.Store) - self.assertIsInstance(node.body[0].targets[0].elts[1].ctx, gast.Store) - - def test_replace_tuple_context(self): - template = """ - def test_fn(foo): - foo = 0 - """ - - node = templates.replace(template, foo=parser.parse_expression('(a, b)'))[0] - self.assertIsInstance(node.body[0].targets[0].ctx, gast.Store) - self.assertIsInstance(node.body[0].targets[0].elts[0].ctx, gast.Store) - self.assertIsInstance(node.body[0].targets[0].elts[1].ctx, gast.Store) - - def test_replace_expression_context(self): - template = """ - def test_fn(): - foo - """ - - node = templates.replace( - template, foo=parser.parse_expression('a + 2 * b / -c'))[0] - self.assertIsInstance(node.body[0].left.ctx, gast.Load) - self.assertIsInstance(node.body[0].right.left.right.ctx, gast.Load) - - def test_replace_complex_context(self): - template = """ - def test_fn(): - foo = 0 - """ - - node = templates.replace( - template, foo=parser.parse_expression('bar(([a, b],)).baz'))[0] - self.assertIsInstance(node.body[0].targets[0].ctx, gast.Store) - function_call_arg = node.body[0].targets[0].value.args[0] - self.assertIsInstance(function_call_arg.elts[0].ctx, gast.Load) - self.assertIsInstance(function_call_arg.elts[0].elts[0].ctx, gast.Load) - self.assertIsInstance(function_call_arg.elts[0].elts[1].ctx, gast.Load) - - def test_replace_index(self): - template = """ - def test_fn(): - foo = 0 - """ - - node = templates.replace( - template, foo=parser.parse_expression('foo(a[b]).bar'))[0] - function_call_arg = node.body[0].targets[0].value.args[0] - self.assertIsInstance(function_call_arg.ctx, gast.Load) - self.assertIsInstance(function_call_arg.slice.ctx, gast.Load) - - def test_replace_call_keyword(self): - template = """ - def test_fn(): - def f(a, d, f): - return a + d + f - return f(1, kws=None) - """ - - source = parser.parse_expression('f(d=3, f=5)') - node = templates.replace(template, kws=source.keywords)[0] - result, _, _ = loader.load_ast(node) - self.assertEqual(9, result.test_fn()) - - with self.assertRaises(ValueError): - templates.replace(template, kws=[]) - templates.replace(template, kws=1) - - def test_replace_name_with_call(self): - template = """ - def test_fn(): - b = 5 - def g(a): - return 3 * a - def f(): - return g - return foo - """ - - source = parser.parse_expression('f()(b)') - node = templates.replace(template, foo=source)[0] - result, _, _ = loader.load_ast(node) - self.assertEqual(15, result.test_fn()) - - def test_replace_name_with_dict(self): - template = """ - def test_fn(): - return foo['bar'] - """ - - source = parser.parse_expression('{\'bar\': 3}') - node = templates.replace(template, foo=source)[0] - result, _, _ = loader.load_ast(node) - self.assertEqual(3, result.test_fn()) - - def test_replace_as_expression(self): - template = """ - foo(a) - """ - - node = templates.replace_as_expression(template, foo='bar', a='baz') - self.assertIsInstance(node, gast.Call) - self.assertEqual(node.func.id, 'bar') - self.assertEqual(node.args[0].id, 'baz') - - def test_replace_as_expression_restrictions(self): - template = """ - foo(a) - bar(b) - """ - with self.assertRaises(ValueError): - templates.replace_as_expression(template) - - def test_function_call_in_list(self): - template = """ - foo(bar) - """ - source = parser.parse_expression('[a(b(1))]') - templates.replace_as_expression(template, bar=source) - - def test_star_comprehension_in_function_call(self): - template = """ - a = foo(func, args) - """ - source = parser.parse_expression('bar(*[i for i in range(j)])') - node = templates.replace(template, func=source.func, args=source.args) - arg_node = node[0].value.args[1].value - self.assertIsInstance(arg_node.generators[0].target.ctx, gast.Store) - self.assertIsInstance(arg_node.elt.ctx, gast.Load) - - def test_lambda_in_function_call(self): - template = """ - a = foo(arg) - """ - source = parser.parse_expression('[lambda i: i]') - node = templates.replace(template, arg=source) - lambda_arg = node[0].value.args[0].elts[0] - self.assertIsInstance(lambda_arg.args.args[0].ctx, gast.Param) - self.assertIsInstance(lambda_arg.body.ctx, gast.Load) - - def test_replace_name_with_subscript(self): - template = """ - foo = bar - """ - replacement = qn.QN(qn.QN('dictionary'), subscript=qn.QN('key')) - - node = templates.replace(template, foo=replacement)[0].targets[0] - self.assertIsInstance(node.ctx, gast.Store) - self.assertIsInstance(node.value.ctx, gast.Load) - - @parameterized.named_parameters([ - ('mixed_attr_subscript', 'a.b["c"]'), - ('mixed_subscript_attr', 'a[b.c]'), - ('nested_subscript', 'a[b[c]]'), - ('repeated_subscript', 'a[b][c]'), - ]) - def test_replace_name_mixed_attr_subscript(self, expression_source): - template = 'foo = bar' - replacement = _parse_with_unset_ctx(expression_source) - - target_node = templates.replace(template, foo=replacement)[0].targets[0] - self.assertExpectedCtxSet(target_node, gast.Store) - - value_node = templates.replace(template, bar=replacement)[0].value - self.assertExpectedCtxSet(value_node, gast.Load) - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/testing/basic_definitions.py b/src/braket/experimental/autoqasm/autograph/pyct/testing/basic_definitions.py deleted file mode 100644 index 333b56ee..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/testing/basic_definitions.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Module with basic entity definitions for testing.""" - - -def simple_function(x): - """Docstring.""" - return x # comment - - -def nested_functions(x): - """Docstring.""" - - def inner_fn(y): - return y - - return inner_fn(x) - - -def function_with_print(): - print('foo') - - -simple_lambda = lambda: None - - -class SimpleClass(object): - - def simple_method(self): - return self - - def method_with_print(self): - print('foo') - - -def function_with_multiline_call(x): - """Docstring.""" - return range( - x, - x + 1, - ) - - -def basic_decorator(f): - return f - - -@basic_decorator -@basic_decorator -def decorated_function(x): - if x > 0: - return 1 - return 2 diff --git a/src/braket/experimental/autoqasm/autograph/pyct/testing/codegen.py b/src/braket/experimental/autoqasm/autograph/pyct/testing/codegen.py deleted file mode 100644 index e644edee..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/testing/codegen.py +++ /dev/null @@ -1,230 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Random code generation for testing/fuzzing.""" -# pylint: disable=invalid-name -import random -import string - -import gast -import numpy as np - -from braket.experimental.autoqasm.autograph.pyct import templates - - -class NodeSampler(object): - sample_map = None - - def sample(self): - nodes, magnitudes = zip(*self.sample_map.items()) - return np.random.choice( - nodes, p=np.array(magnitudes, dtype='float32') / np.sum(magnitudes)) - - -class StatementSampler(NodeSampler): - sample_map = dict(( - (gast.Assign, 10), - (gast.Print, 1), - (gast.If, 2), - (gast.While, 2), - (gast.For, 0), - )) - - -class ExpressionSampler(NodeSampler): - sample_map = dict(( - (gast.UnaryOp, 1), - (gast.BinOp, 8), - (gast.Name, 1), - (gast.Call, 0), - )) - - -class CompareSampler(NodeSampler): - sample_map = dict(( - (gast.Eq, 1), - (gast.NotEq, 1), - (gast.Lt, 1), - (gast.LtE, 1), - (gast.Gt, 1), - (gast.GtE, 1), - (gast.Is, 1), - (gast.IsNot, 1), - )) - - -class BinaryOpSampler(NodeSampler): - sample_map = dict(( - (gast.Add, 1), - (gast.Sub, 1), - (gast.Mult, 1), - (gast.Div, 1), - (gast.FloorDiv, 1), - (gast.Mod, 1), - (gast.Pow, 1), - )) - - -class UnaryOpSampler(NodeSampler): - sample_map = dict(((gast.USub, 1), (gast.UAdd, 0))) - - -class NameSampler(NodeSampler): - sample_map = dict(( - ('new', 1), - ('existing', 1), - )) - - -N_CONTROLFLOW_STATEMENTS = 10 -N_FUNCTIONDEF_STATEMENTS = 10 - - -class CodeGenerator(object): - """Generate random syntactically-valid Python ASTs.""" - - def __init__(self, max_depth=3, depth=0): - self.max_depth = max_depth - self.depth = depth - - def generate_statement(self): - """Generate a statement node, dispatching to the correct class method.""" - desired_node = StatementSampler().sample() - self.depth += 1 - - # Enforce some constraints on generating statements. - # E.g., if statements need at least 3 readable variables. - # If we fail to satisfy our constraints, draw another sample. - if desired_node in (gast.While, gast.For, gast.If): - if self.depth > self.max_depth: - return self.generate_statement() - - # Go get the generator method and run it - method = 'generate_' + desired_node.__name__ - visitor = getattr(self, method) - node = visitor() - self.depth -= 1 - return node - - def sample_node_list(self, low, high, generator): - """Generate a list of statements of random length. - - Args: - low: Fewest number of statements to generate. - high: Highest number of statements to generate. - generator: Function to call to generate nodes. - - Returns: - A list of statements. - """ - statements = [] - for _ in range(np.random.randint(low, high)): - statements.append(generator()) - return statements - - def generate_Name(self, ctx=gast.Load()): - variable_name = '_' + ''.join( - random.choice(string.ascii_lowercase) for _ in range(4)) - return gast.Name(variable_name, ctx=ctx, annotation=None) - - def generate_BinOp(self): - # TODO(alexbw): convert to generate_expression when we get to limit - # expression depth. - op = BinaryOpSampler().sample()() - return gast.BinOp(self.generate_Name(), op, self.generate_Name()) - - def generate_Compare(self): - op = CompareSampler().sample()() - return gast.Compare(self.generate_Name(), [op], [self.generate_Name()]) - - def generate_UnaryOp(self): - operand = self.generate_Name() - op = UnaryOpSampler().sample()() - return gast.UnaryOp(op, operand) - - def generate_expression(self): - desired_node = ExpressionSampler().sample() - # Go get the generator method and run it - method = 'generate_' + desired_node.__name__ - generator = getattr(self, method) - return generator() - - def generate_Assign(self): - """Generate an Assign node.""" - # Generate left-hand side - target_node = self.generate_Name(gast.Store()) - # Generate right-hand side - value_node = self.generate_expression() - # Put it all together - node = gast.Assign(targets=[target_node], value=value_node) - return node - - def generate_If(self): - """Generate an If node.""" - test = self.generate_Compare() - - # Generate true branch statements - body = self.sample_node_list( - low=1, - high=N_CONTROLFLOW_STATEMENTS // 2, - generator=self.generate_statement) - - # Generate false branch statements - orelse = self.sample_node_list( - low=1, - high=N_CONTROLFLOW_STATEMENTS // 2, - generator=self.generate_statement) - - node = gast.If(test, body, orelse) - return node - - def generate_While(self): - """Generate a While node.""" - - test = self.generate_Compare() - body = self.sample_node_list( - low=1, high=N_CONTROLFLOW_STATEMENTS, generator=self.generate_statement) - orelse = [] # not generating else statements - - node = gast.While(test, body, orelse) - return node - - def generate_Call(self): - raise NotImplementedError - - def generate_Return(self): - return gast.Return(self.generate_expression()) - - def generate_Print(self): - return templates.replace('print(x)', x=self.generate_expression())[0] - - def generate_FunctionDef(self): - """Generate a FunctionDef node.""" - - # Generate the arguments, register them as available - arg_vars = self.sample_node_list( - low=2, high=10, generator=lambda: self.generate_Name(gast.Param())) - args = gast.arguments(arg_vars, None, [], [], None, []) - - # Generate the function body - body = self.sample_node_list( - low=1, high=N_FUNCTIONDEF_STATEMENTS, generator=self.generate_statement) - body.append(self.generate_Return()) - fn_name = self.generate_Name().id - node = gast.FunctionDef(fn_name, args, body, (), None) - return node - - -def generate_random_functiondef(): - return CodeGenerator().generate_FunctionDef() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/testing/decorators.py b/src/braket/experimental/autoqasm/autograph/pyct/testing/decorators.py deleted file mode 100644 index 332686ae..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/testing/decorators.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Module with test decorators.""" - -import functools - - -def wrapping_decorator(f): - - @functools.wraps(f) - def wrapper(*args, **kwargs): - return f(*args, **kwargs) - - return wrapper - - -def standalone_decorator(f): - - def standalone_wrapper(*args, **kwargs): - return f(*args, **kwargs) - - return standalone_wrapper - - -def functional_decorator(): - - def decorator(f): - - def functional_wrapper(*args, **kwargs): - return f(*args, **kwargs) - - return functional_wrapper - - return decorator diff --git a/src/braket/experimental/autoqasm/autograph/pyct/transformer.py b/src/braket/experimental/autoqasm/autograph/pyct/transformer.py deleted file mode 100644 index 9c054210..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/transformer.py +++ /dev/null @@ -1,538 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""A node transformer that includes utilities for SCT.""" - -import collections -import enum - -import gast - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import pretty_printer -from braket.experimental.autoqasm.autograph.pyct import templates - - -class AnalysisLevel(enum.IntEnum): - - NONE = 0 - ACTIVITY = 1 - DEFINEDNESS = 2 - LIVENESS = 3 - - -# TODO(znado): Use namedtuple. -class Context(object): - """Contains information about a source code transformation. - - This object is mutable, and is updated during conversion. Not thread safe. - - Attributes: - info: EntityInfo, immutable. - namer: naming.Namer. - current_origin: origin_info.OriginInfo, holds the OriginInfo of the last - AST node to be processed successfully. Useful for error handling. - user: An user-supplied context object. The object is opaque to the - infrastructure, but will pe passed through to all custom transformations. - """ - - def __init__(self, info, namer, user_context): - self.info = info - self.namer = namer - self.current_origin = None - self.user = user_context - - -# TODO(mdan): Move to a standalone file. -class EntityInfo( - collections.namedtuple( - 'EntityInfo', - ('name', 'source_code', 'source_file', 'future_features', 'namespace')) -): - """Contains information about a Python entity. - - Immutable. - - Examples of entities include functions and classes. - - Attributes: - name: The name that identifies this entity. - source_code: The entity's source code. - source_file: The entity's source file. - future_features: Tuple[Text], the future features that this entity was - compiled with. See - https://docs.python.org/2/reference/simple_stmts.html#future. - namespace: Dict[str, ], containing symbols visible to the entity (excluding - parameters). - """ - pass - - -class _StateStack(object): - """Templated context manager. - - This class provides syntactic sugar for a stack of objects of known - type. It allows accessing attributes of the object at the top of the stack - directly against this object, which allows for very terse syntax. - - For example, this code: - - stack = _StateStack(Foo) - stack.enter() - stack.bar - - Is equivalent to: - - stack = [] - stack.append(Foo()) - foo = stack[-1] - foo.bar - - See _State for more on how this is used. - - Attributes: - type: Any, the type of objects that this stack holds - level: int, the current stack depth - stack: List[Any], the actual stack - value: Any, the instance of the object at the top of the stack - """ - - def __init__(self, type_): - # Because we override __setattr__, we need to attach these attributes using - # the superclass' setattr. - object.__setattr__(self, 'type', type_) - object.__setattr__(self, '_stack', []) - if not hasattr(type_, 'no_root'): - self.enter() - - def __enter__(self): - self.enter() - return self - - def __exit__(self, exc_type, exc_value, traceback): - self.exit() - - def enter(self): - self._stack.append(self.type()) - - def exit(self): - self._stack.pop() - - @property - def stack(self): - return self._stack - - @property - def level(self): - return len(self._stack) - - @property - def value(self): - return self._stack[-1] - - def __iter__(self): - return iter(self._stack) - - def __getattr__(self, key): - return getattr(self._stack[-1], key) - - def __setattr__(self, key, value): - setattr(self._stack[-1], key, value) - - -class _State(object): - """Syntactic sugar for accessing an instance of a StateStack context manager. - - This structure offers syntactic sugar over a dict of stacks of objects - of known type. These structures are useful to keep state during AST walks. - Multiple different scopes can be tracked in parallel. For example: - - s = _State() - - s[foo].enter() - s[bar].enter() # this will not affect s[foo] - - Element access has special semantics: - * keys are a data type - * element values are _StateStack(type=key) objects - * missing elements are automatically added, similarly to defaultdict - - For example, the following block : - - _State s - s[Foo] - - Is equivalent to: - - s = {} - if Foo not in s: - s[Foo] = Foo() - s[Foo] - - See Base for how it's used. - """ - - def __init__(self): - self._value = {} - - def __getitem__(self, key): - if key not in self._value: - self._value[key] = _StateStack(key) - return self._value[key] - - -class NodeStateTracker(object): - """Base class for general-purpose Python code transformation. - - This abstract class provides helpful functions, like state tracking within - the scope of arbitrary node, helpers for processing code blocks, debugging, - mapping of transformed code to original code, and others. - - Scope-local state tracking: to keep state across nodes, at the level of - (possibly nested) scopes, use enter/exit_local_scope and set/get_local. - You must call enter/exit_local_scope manually, but the transformer detects - when they are not properly paired. - - The transformer allows keeping state across calls that is local - to arbitrary nodes and their descendants, using the self.state attribute. - Multiple independent scopes are allowed and automatically constructed. - - For example, to keep track of the `If` node that encloses any `Name` node, - one can write: - - ``` - class FooType(object): - - def __init__(self): - self.foo_property = None - - class DummyTransformer(NodeStateTracker, ast.NodeTransformer): - - def visit_If(self, node): - self.state[FooType].enter() - self.state[FooType].foo_property = node - node = self.veneric_visit(node) - self.state[FooType].exit() - return node - - def visit_Name(self, node): - self.state[FooType].foo_property # will hold the innermost enclosing if - ``` - - Alternatively, the `enter()`/`exit()` calls can be managed by a `with` - statement: - - ``` - def visit_If(self, node): - with self.state[FooType] as foo: - foo.foo_property = node - return self.generic_visit(node) - ``` - """ - - # TODO(mdan): Document all extra features. - - def __init__(self, ctx): - """Initialize the transformer. - - Subclasses should call this. - - Args: - ctx: A Context object. - """ - self._lineno = 0 - self._col_offset = 0 - self.ctx = ctx - - # Allows scoping of local variables to keep state across calls to visit_* - # methods. Multiple scope hierarchies may exist and are keyed by tag. A - # scope is valid at one or more nodes and all its children. Scopes created - # in child nodes supersede their parent. Scopes are isolated from one - # another. - self.state = _State() - - def debug_print(self, node): - """Helper method useful for debugging. Prints the AST.""" - if __debug__: - print(pretty_printer.fmt(node)) - return node - - def debug_print_src(self, node): - """Helper method useful for debugging. Prints the AST as code.""" - if __debug__: - print(parser.unparse(node)) - return node - - def visit_block(self, nodes, before_visit=None, after_visit=None): - """A more powerful version of generic_visit for statement blocks. - - An example of a block is the body of an if statement. - - This function allows specifying a postprocessing callback (the - after_visit argument) argument which can be used to move nodes to a new - destination. This is done by after_visit by returning a non-null - second return value, e.g. return new_node, new_destination. - - For example, a transformer could perform the following move: - - foo() - bar() - baz() - - foo() - if cond: - bar() - baz() - - The above could be done with a postprocessor of this kind: - - def after_visit(node): - if node_is_function_call(bar): - new_container_node = build_cond() - new_container_node.body.append(node) - return new_container_node, new_container_node.body - else: - # Once we set a new destination, all subsequent items will be - # moved to it, so we don't need to explicitly handle baz. - return node, None - - Args: - nodes: enumerable of AST node objects. If None, the function returns None. - before_visit: optional callable that is called before visiting each item - in nodes - after_visit: optional callable that takes in an AST node and returns a - tuple (new_node, new_destination). It is called after visiting each item - in nodes. Is used in the same was as the - visit_* methods: new_node will replace the node; if not None, - new_destination must be a list, and subsequent nodes will be placed - in this list instead of the list returned by visit_block. - - Returns: - A list of AST node objects containing the transformed items fron nodes, - except those nodes that have been relocated using after_visit. - """ - if nodes is None: - return None - - results = [] - node_destination = results - for node in nodes: - if before_visit: - # TODO(mdan): We can modify node here too, if ever needed. - before_visit() - - replacement = self.visit(node) - - if after_visit and replacement: - replacement, new_destination = after_visit(replacement) - else: - new_destination = None - - if replacement: - if isinstance(replacement, (list, tuple)): - node_destination.extend(replacement) - else: - node_destination.append(replacement) - - # Allow the postprocessor to reroute the remaining nodes to a new list. - if new_destination is not None: - node_destination = new_destination - return results - - -# TODO(mdan): Rename to PythonCodeTransformer. -class Base(NodeStateTracker, gast.NodeTransformer): - """Base class for general-purpose Python-to-Python code transformation. - - This is an extension of ast.NodeTransformer that provides the additional - functions offered by NodeStateTracker. - """ - - def create_assignment(self, target, expression): - template = """ - target = expression - """ - return templates.replace(template, target=target, expression=expression) - - # TODO(mdan): Remove. - def apply_to_single_assignments(self, targets, values, apply_fn): - """Applies a function to each individual assignment. - - This function can process a possibly-unpacked (e.g. a, b = c, d) assignment. - It tries to break down the unpacking if possible. In effect, it has the same - effect as passing the assigned values in SSA form to apply_fn. - - Examples: - - The following will result in apply_fn(a, c), apply_fn(b, d): - - a, b = c, d - - The following will result in apply_fn(a, c[0]), apply_fn(b, c[1]): - - a, b = c - - The following will result in apply_fn(a, (b, c)): - - a = b, c - - It uses the visitor pattern to allow subclasses to process single - assignments individually. - - Args: - targets: list, tuple of or individual AST node. Should be used with the - targets field of an ast.Assign node. - values: an AST node. - apply_fn: a function of a single argument, which will be called with the - respective nodes of each single assignment. The signature is - apply_fn(target, value), no return value. - """ - if not isinstance(targets, (list, tuple)): - targets = (targets,) - for target in targets: - if isinstance(target, (gast.Tuple, gast.List)): - for i in range(len(target.elts)): - target_el = target.elts[i] - if isinstance(values, (gast.Tuple, gast.List)): - value_el = values.elts[i] - else: - value_el = gast.Subscript(values, i, ctx=gast.Store()) - self.apply_to_single_assignments(target_el, value_el, apply_fn) - else: - # TODO(mdan): Look into allowing to rewrite the AST here. - apply_fn(target, values) - - def visit(self, node): - if not isinstance(node, gast.AST): - # This is not that uncommon a mistake: various node bodies are lists, for - # example, posing a land mine for transformers that need to recursively - # call `visit`. The error needs to be raised before the exception handler - # below is installed, because said handler will mess up if `node` is not, - # in fact, a node. - msg = ('invalid value for "node": expected "ast.AST", got "{}"; to' - ' visit lists of nodes, use "visit_block" instead').format( - type(node)) - raise ValueError(msg) - - if anno.hasanno(node, anno.Basic.SKIP_PROCESSING): - return node - - parent_origin = self.ctx.current_origin - if anno.hasanno(node, anno.Basic.ORIGIN): - self.ctx.current_origin = anno.getanno(node, anno.Basic.ORIGIN) - - try: - processing_expr_node = isinstance(node, gast.Expr) - if processing_expr_node: - entry_expr_value = node.value - - result = super(Base, self).visit(node) - - # Adjust for consistency: replacing the value of an Expr with - # an Assign node removes the need for the Expr node. - if (processing_expr_node and isinstance(result, gast.Expr) and - (result.value is not entry_expr_value)): - # When the replacement is a list, it is assumed that the list came - # from a template that contained a number of statements, which - # themselves are standalone and don't require an enclosing Expr. - if isinstance(result.value, - (list, tuple, gast.Assign, gast.AugAssign)): - result = result.value - - # By default, all replacements receive the origin info of the replaced - # node. - if result is not node and result is not None: - inherited_origin = anno.getanno( - node, anno.Basic.ORIGIN, default=parent_origin) - if inherited_origin is not None: - nodes_to_adjust = result - if isinstance(result, (list, tuple)): - nodes_to_adjust = result - else: - nodes_to_adjust = (result,) - for n in nodes_to_adjust: - if not anno.hasanno(n, anno.Basic.ORIGIN): - anno.setanno(n, anno.Basic.ORIGIN, inherited_origin) - finally: - self.ctx.current_origin = parent_origin - - return result - - -class CodeGenerator(NodeStateTracker, gast.NodeVisitor): - """Base class for general-purpose Python-to-string code transformation. - - Similar to Base, but outputs arbitrary strings instead of a Python AST. - - This uses the same visitor mechanism that the standard NodeVisitor uses, - meaning that subclasses write handlers for the different kinds of nodes. - New code is generated using the emit method, which appends to a code buffer - that can be afterwards obtained from code_buffer. - - Example: - - class SimpleCodeGen(CodeGenerator): - - def visitIf(self, node): - self.emit('if ') - self.visit(node.test) - self.emit(' { ') - self.visit(node.body) - self.emit(' } else { ') - self.visit(node.orelse) - self.emit(' } ') - - node = ast.parse(...) - gen = SimpleCodeGen() - gen.visit(node) - # gen.code_buffer contains the resulting code - """ - - def __init__(self, ctx): - super(CodeGenerator, self).__init__(ctx) - - self._output_code = '' - self.source_map = {} - - def emit(self, code): - self._output_code += code - - @property - def code_buffer(self): - return self._output_code - - def visit(self, node): - if anno.hasanno(node, anno.Basic.SKIP_PROCESSING): - return - - parent_origin = self.ctx.current_origin - eof_before = len(self._output_code) - if anno.hasanno(node, anno.Basic.ORIGIN): - self.ctx.current_origin = anno.getanno(node, anno.Basic.ORIGIN) - - try: - ret = super(CodeGenerator, self).visit(node) - - # By default, all replacements receive the origin info of the replaced - # node. - eof_after = len(self._output_code) - if eof_before - eof_after: - inherited_origin = anno.getanno( - node, anno.Basic.ORIGIN, default=parent_origin) - if inherited_origin is not None: - self.source_map[(eof_before, eof_after)] = inherited_origin - return ret - finally: - self.ctx.current_origin = parent_origin diff --git a/src/braket/experimental/autoqasm/autograph/pyct/transformer_test.py b/src/braket/experimental/autoqasm/autograph/pyct/transformer_test.py deleted file mode 100644 index e1507b79..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/transformer_test.py +++ /dev/null @@ -1,364 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for templates module.""" - -import re -import gast - -from braket.experimental.autoqasm.autograph.pyct import anno -from braket.experimental.autoqasm.autograph.pyct import origin_info -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import transformer -from tensorflow.python.platform import test - - -class TransformerTest(test.TestCase): - - def _simple_context(self): - entity_info = transformer.EntityInfo( - name='Test_fn', - source_code=None, - source_file=None, - future_features=(), - namespace=None) - return transformer.Context(entity_info, None, None) - - def assertSameAnno(self, first, second, key): - self.assertIs(anno.getanno(first, key), anno.getanno(second, key)) - - def assertDifferentAnno(self, first, second, key): - self.assertIsNot(anno.getanno(first, key), anno.getanno(second, key)) - - def test_state_tracking(self): - - class LoopState(object): - pass - - class CondState(object): - pass - - class TestTransformer(transformer.Base): - - def visit(self, node): - anno.setanno(node, 'loop_state', self.state[LoopState].value) - anno.setanno(node, 'cond_state', self.state[CondState].value) - return super(TestTransformer, self).visit(node) - - def visit_While(self, node): - self.state[LoopState].enter() - node = self.generic_visit(node) - self.state[LoopState].exit() - return node - - def visit_If(self, node): - self.state[CondState].enter() - node = self.generic_visit(node) - self.state[CondState].exit() - return node - - tr = TestTransformer(self._simple_context()) - - def test_function(a): - a = 1 - while a: - _ = 'a' - if a > 2: - _ = 'b' - while True: - raise '1' - if a > 3: - _ = 'c' - while True: - raise '1' - - node, _ = parser.parse_entity(test_function, future_features=()) - node = tr.visit(node) - - fn_body = node.body - outer_while_body = fn_body[1].body - self.assertSameAnno(fn_body[0], outer_while_body[0], 'cond_state') - self.assertDifferentAnno(fn_body[0], outer_while_body[0], 'loop_state') - - first_if_body = outer_while_body[1].body - self.assertDifferentAnno(outer_while_body[0], first_if_body[0], - 'cond_state') - self.assertSameAnno(outer_while_body[0], first_if_body[0], 'loop_state') - - first_inner_while_body = first_if_body[1].body - self.assertSameAnno(first_if_body[0], first_inner_while_body[0], - 'cond_state') - self.assertDifferentAnno(first_if_body[0], first_inner_while_body[0], - 'loop_state') - - second_if_body = outer_while_body[2].body - self.assertDifferentAnno(first_if_body[0], second_if_body[0], 'cond_state') - self.assertSameAnno(first_if_body[0], second_if_body[0], 'loop_state') - - second_inner_while_body = second_if_body[1].body - self.assertDifferentAnno(first_inner_while_body[0], - second_inner_while_body[0], 'cond_state') - self.assertDifferentAnno(first_inner_while_body[0], - second_inner_while_body[0], 'loop_state') - - def test_state_tracking_context_manager(self): - - class CondState(object): - pass - - class TestTransformer(transformer.Base): - - def visit(self, node): - anno.setanno(node, 'cond_state', self.state[CondState].value) - return super(TestTransformer, self).visit(node) - - def visit_If(self, node): - with self.state[CondState]: - return self.generic_visit(node) - - tr = TestTransformer(self._simple_context()) - - def test_function(a): - a = 1 - if a > 2: - _ = 'b' - if a < 5: - _ = 'c' - _ = 'd' - - node, _ = parser.parse_entity(test_function, future_features=()) - node = tr.visit(node) - - fn_body = node.body - outer_if_body = fn_body[1].body - self.assertDifferentAnno(fn_body[0], outer_if_body[0], 'cond_state') - self.assertSameAnno(outer_if_body[0], outer_if_body[2], 'cond_state') - - inner_if_body = outer_if_body[1].body - self.assertDifferentAnno(inner_if_body[0], outer_if_body[0], 'cond_state') - - def test_visit_block_postprocessing(self): - - class TestTransformer(transformer.Base): - - def _process_body_item(self, node): - if isinstance(node, gast.Assign) and (node.value.id == 'y'): - if_node = gast.If( - gast.Name( - 'x', ctx=gast.Load(), annotation=None, type_comment=None), - [node], []) - return if_node, if_node.body - return node, None - - def visit_FunctionDef(self, node): - node.body = self.visit_block( - node.body, after_visit=self._process_body_item) - return node - - def test_function(x, y): - z = x - z = y - return z - - tr = TestTransformer(self._simple_context()) - - node, _ = parser.parse_entity(test_function, future_features=()) - node = tr.visit(node) - - self.assertEqual(len(node.body), 2) - self.assertIsInstance(node.body[0], gast.Assign) - self.assertIsInstance(node.body[1], gast.If) - self.assertIsInstance(node.body[1].body[0], gast.Assign) - self.assertIsInstance(node.body[1].body[1], gast.Return) - - def test_robust_error_on_list_visit(self): - - class BrokenTransformer(transformer.Base): - - def visit_If(self, node): - # This is broken because visit expects a single node, not a list, and - # the body of an if is a list. - # Importantly, the default error handling in visit also expects a single - # node. Therefore, mistakes like this need to trigger a type error - # before the visit called here installs its error handler. - # That type error can then be caught by the enclosing call to visit, - # and correctly blame the If node. - self.visit(node.body) - return node - - def test_function(x): - if x > 0: - return x - - tr = BrokenTransformer(self._simple_context()) - - node, _ = parser.parse_entity(test_function, future_features=()) - with self.assertRaises(ValueError) as cm: - node = tr.visit(node) - obtained_message = str(cm.exception) - expected_message = r'expected "ast.AST", got "\<(type|class) \'list\'\>"' - self.assertRegex(obtained_message, expected_message) - - def test_robust_error_on_ast_corruption(self): - # A child class should not be able to be so broken that it causes the error - # handling in `transformer.Base` to raise an exception. Why not? Because - # then the original error location is dropped, and an error handler higher - # up in the call stack gives misleading information. - - # Here we test that the error handling in `visit` completes, and blames the - # correct original exception, even if the AST gets corrupted. - - class NotANode(object): - pass - - class BrokenTransformer(transformer.Base): - - def visit_If(self, node): - node.body = NotANode() - raise ValueError('I blew up') - - def test_function(x): - if x > 0: - return x - - tr = BrokenTransformer(self._simple_context()) - - node, _ = parser.parse_entity(test_function, future_features=()) - with self.assertRaises(ValueError) as cm: - node = tr.visit(node) - obtained_message = str(cm.exception) - # The message should reference the exception actually raised, not anything - # from the exception handler. - expected_substring = 'I blew up' - self.assertIn(expected_substring, obtained_message) - - def test_origin_info_propagated_to_new_nodes(self): - - class TestTransformer(transformer.Base): - - def visit_If(self, node): - return gast.Pass() - - tr = TestTransformer(self._simple_context()) - - def test_fn(): - x = 1 - if x > 0: - x = 1 - return x - - node, source = parser.parse_entity(test_fn, future_features=()) - origin_info.resolve(node, source, 'test_file', 100, 0) - node = tr.visit(node) - - created_pass_node = node.body[1] - # Takes the line number of the if statement. - self.assertEqual( - anno.getanno(created_pass_node, anno.Basic.ORIGIN).loc.lineno, 102) - - def test_origin_info_preserved_in_moved_nodes(self): - - class TestTransformer(transformer.Base): - - def visit_If(self, node): - return node.body - - tr = TestTransformer(self._simple_context()) - - def test_fn(): - x = 1 - if x > 0: - x = 1 - x += 3 - return x - - node, source = parser.parse_entity(test_fn, future_features=()) - origin_info.resolve(node, source, 'test_file', 100, 0) - node = tr.visit(node) - - assign_node = node.body[1] - aug_assign_node = node.body[2] - # Keep their original line numbers. - self.assertEqual( - anno.getanno(assign_node, anno.Basic.ORIGIN).loc.lineno, 103) - self.assertEqual( - anno.getanno(aug_assign_node, anno.Basic.ORIGIN).loc.lineno, 104) - - -class CodeGeneratorTest(test.TestCase): - - def _simple_context(self): - entity_info = transformer.EntityInfo( - name='test_fn', - source_code=None, - source_file=None, - future_features=(), - namespace=None) - return transformer.Context(entity_info, None, None) - - def test_basic_codegen(self): - - class TestCodegen(transformer.CodeGenerator): - - def visit_Assign(self, node): - self.emit(parser.unparse(node, include_encoding_marker=False)) - self.emit('\n') - - def visit_Return(self, node): - self.emit(parser.unparse(node, include_encoding_marker=False)) - self.emit('\n') - - def visit_If(self, node): - self.emit('if ') - # This is just for simplifity. A real generator will walk the tree and - # emit proper code. - self.emit(parser.unparse(node.test, include_encoding_marker=False)) - self.emit(' {\n') - self.visit_block(node.body) - self.emit('} else {\n') - self.visit_block(node.orelse) - self.emit('}\n') - - tg = TestCodegen(self._simple_context()) - - def test_fn(): - x = 1 - if x > 0: - x = 2 - if x > 1: - x = 3 - return x - - node, source = parser.parse_entity(test_fn, future_features=()) - origin_info.resolve(node, source, 'test_file', 100, 0) - tg.visit(node) - - r = re.compile('.*'.join([ - r'x = 1', - r'if \(?x > 0\)? {', - r'x = 2', - r'if \(?x > 1\)? {', - r'x = 3', - r'} else {', - r'}', - r'} else {', - r'}', - r'return x']), re.DOTALL) - - self.assertRegex(tg.code_buffer, r) - # TODO(mdan): Test the source map. - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/pyct/transpiler.py b/src/braket/experimental/autoqasm/autograph/pyct/transpiler.py deleted file mode 100644 index aad92b13..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/transpiler.py +++ /dev/null @@ -1,495 +0,0 @@ -# Copyright 2016 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Generic source code transformation infrastructure.""" - -import inspect -import threading -import types - -import gast - -from braket.experimental.autoqasm.autograph.pyct import cache -from braket.experimental.autoqasm.autograph.pyct import inspect_utils -from braket.experimental.autoqasm.autograph.pyct import loader -from braket.experimental.autoqasm.autograph.pyct import naming -from braket.experimental.autoqasm.autograph.pyct import origin_info -from braket.experimental.autoqasm.autograph.pyct import parser -from braket.experimental.autoqasm.autograph.pyct import templates -from braket.experimental.autoqasm.autograph.pyct import transformer -from braket.experimental.autoqasm.autograph.logging import ag_logging as logging - - -def _wrap_into_factory(nodes, entity_name, inner_factory_name, - outer_factory_name, closure_vars, factory_args, - future_features): - """Wraps an AST into the body of a factory with consistent lexical context. - - The AST is expected to define some symbol with a name given by `entity_name`. - - This mechanism ensures that the resulting transformed entity has lexical - scoping identical to that of the source entity, while allowing extra - parametrization. - - Two nested factories achieve the following: - - 1. The inner factory dynamically creates the entity represented by `nodes`. - 2. The inner factory is parametrized by a custom set of arguments. - 3. The inner factory has a closure identical to that of the transformed - entity. - 4. The inner factory has local variables named like `args`, which `nodes` may - use as additional parameters. - 5. The inner factory returns the variables given by `entity_name`. - 6. The outer factory is niladic. - 7. The outer factory has no closure. - 8. The outer factory creates the necessary lexical scope for the inner - factory, so that the loaded code has the given configuration for - closure/globals. - 9. The outer factory returns the inner factory. - - Roughly speaking, the following code is generated: - - from __future__ import future_feature_1 - from __future__ import future_feature_2 - ... - - def outer_factory(): - closure_var_1 = None - closure_var_2 = None - ... - - def inner_factory(arg_1, arg_2, ...): - <> - return entity - - return inner_factory - - The lexical scoping is created using dummy symbol declarations which create - local variables in the body of the outer factory, so that the Python parser - correctly marks them as free non-global variables upon load (that is, it - creates cell slots for each symbol. These symbols are initialized with None, - but their values are not expected to be used; instead, the caller is expected - to replace them with the cells of the source entity. For more details, see: - https://docs.python.org/3/reference/executionmodel.html#binding-of-names - - Args: - nodes: Tuple[ast.AST], the source code to wrap. - entity_name: Union[Text, ast.AST], the name of the principal entity that - `nodes` define. - inner_factory_name: Text, the name of the inner factory. - outer_factory_name: Text, the name of the outer factory. - closure_vars: Iterable[Text], names of the closure variables for the inner - factory. - factory_args: Iterable[Text], names of additional arguments for the - inner factory. Useful to configure variables that the converted code can - use. Typically, these are modules. - future_features: Iterable[Text], names of future statements to associate the - code with. - - Returns: - ast.AST - """ - dummy_closure_defs = [] - for var_name in closure_vars: - template = """ - var_name = None - """ - dummy_closure_defs.extend(templates.replace(template, var_name=var_name)) - - if future_features: - future_imports = gast.ImportFrom( - module='__future__', - names=[gast.alias(name=name, asname=None) for name in future_features], - level=0) - else: - future_imports = [] - - factory_args = [ - gast.Name(name, ctx=gast.Param(), annotation=None, type_comment=None) - for name in factory_args - ] - - template = """ - future_imports - def outer_factory_name(): - dummy_closure_defs - def inner_factory_name(factory_args): - entity_defs - return entity_name - return inner_factory_name - """ - return templates.replace( - template, - dummy_closure_defs=dummy_closure_defs, - entity_defs=nodes, - entity_name=entity_name, - factory_args=factory_args, - future_imports=future_imports, - inner_factory_name=inner_factory_name, - outer_factory_name=outer_factory_name) - - -class _PythonFnFactory(object): - """Helper object that wraps a Python function factory.""" - - def __init__(self, name, freevars, extra_locals): - """Creates a new factory for a Python function. - - Args: - name: The function name. - freevars: The list of non-global free variables for the function. - extra_locals: Dict[Text, Any], names and values for custom variables that - are accessible to the generated code as local variables. - """ - self._name = name - self._freevars = freevars - self._extra_locals = extra_locals - - self._unbound_factory = None - self.module = None - self.source_map = None - - def create(self, - nodes, - namer, - inner_factory_name='inner_factory', - outer_factory_name='outer_factory', - future_features=()): - """Initializes a function.""" - if self._unbound_factory is not None: - raise ValueError('double initialization; create a new object instead') - - inner_factory_name = namer.new_symbol(inner_factory_name, ()) - outer_factory_name = namer.new_symbol(outer_factory_name, ()) - nodes = _wrap_into_factory(nodes, self._name, inner_factory_name, - outer_factory_name, self._freevars, - self._extra_locals.keys(), future_features) - - module, _, source_map = loader.load_ast( - nodes, include_source_map=True) - outer_factory = getattr(module, outer_factory_name) - self._unbound_factory = outer_factory() - self.module = module - self.source_map = source_map - - def instantiate(self, - globals_, - closure, - defaults=None, - kwdefaults=None): - """Creates a new function instance.""" - if self._unbound_factory is None: - raise ValueError('call create first') - - factory_code = self._unbound_factory.__code__ - factory_freevars = factory_code.co_freevars - closure_map = dict(zip(self._freevars, closure)) - factory_closure = tuple( - closure_map[name] for name in factory_code.co_freevars) - if len(factory_closure) != len(closure): - raise ValueError( - 'closure mismatch, requested {}, but source function had {}'.format( - self._freevars, factory_freevars)) - - bound_factory = types.FunctionType( - code=factory_code, - globals=globals_, - name=self._name, - argdefs=(), - closure=factory_closure) - - # The lint override is a false positive. - new_fn = bound_factory(**self._extra_locals) # pylint:disable=not-callable - - if defaults: - new_fn.__defaults__ = defaults - if kwdefaults: - new_fn.__kwdefaults__ = kwdefaults - - return new_fn - - -class GenericTranspiler(object): - """A generic transpiler for Python functions. - - Its interface is the `transform` API, which can process Python function - objects. Internally, it handles parsing. - - Users typically subclass this, customizing the `transform_ast` method. The - output of transformed_ast is returned directly by `transform`. Existing - methods like `transform_function` may also be overloaded. - - Example: - - class MyTransformer(GenericTranspiler): - - def transform_ast(self, node, ctx): - result = <> - return result - - transformer = MyTransfomer() - - result = transformer.transform(f, ...) - # result is the output - """ - - def get_transformed_name(self, node): - """Returns a name for the output function. Subclasses may override this.""" - if isinstance(node, gast.Lambda): - return 'lam' - elif isinstance(node, gast.FunctionDef): - return node.name - raise ValueError('Unknown node type {}'.format(node)) - - def transform_ast(self, node, ctx): - """Performs an actual transformation of a function's AST. - - Subclasses must implement this method, and do not usually call it. - - Args: - node: One or more ast.AST nodes representing the AST to be transformed. - ctx: transformer.Context. - """ - raise NotImplementedError('subclasses must override this') - - def transform(self, obj, user_context): - """Transforms a Python object. - - Users typically call this method. - - Args: - obj: A Python object, function, type, etc. - user_context: An opaque object (may be None) that is forwarded to - transform_ast, through the ctx.user attribute. - Returns: - The result of calling transform_function. - - Raises: - NotImplementedError: if the type of obj is not handled. - """ - if inspect.isfunction(obj) or inspect.ismethod(obj): - return self.transform_function(obj, user_context) - - raise NotImplementedError('Non-function: {}'.format(type(obj))) - - def _erase_arg_defaults(self, node): - """Erase arg default expressions, which would otherwise be unbound.""" - args = node.args - for i in range(len(args.defaults)): - args.defaults[i] = parser.parse_expression('None') - for i, d in enumerate(args.kw_defaults): - if d is not None: - args.kw_defaults[i] = parser.parse_expression('None') - return node - - def transform_module(self, mod, user_context): - """Transforms a module. - - Subclasses may override this method. The return value is opaque. - - The method receives the original AST. The result is passed as-is to the - output of `transform`. - - Args: - mod: A Python module. - user_context: An opaque object (may be None) that is forwarded to - transform_ast, through the ctx.user attribute. - Returns: - List[Tuple[Any, Any]]. By default it returns the output of transform_ast, - evaluated on each supported member, other than modules, together with a - `transformer.Context` containing information about the transformation - process. - """ - result = [] - for member in mod.__dict__.values(): - if inspect.ismodule(member): - continue # Not transforming modules recursively. - try: - result.append(self.transform(member, user_context)) - except NotImplementedError: - pass # Skip unsupported elements. - return result - - def transform_function(self, fn, user_context): - """Transforms a function. - - Subclasses may override this method. The return value is opaque. - - The method receives the original AST. The result is passed as-is to the - output of `transform`. - - Args: - fn: A function or lambda. - user_context: An opaque object (may be None) that is forwarded to - transform_ast, through the ctx.user attribute. - Returns: - Tuple[Any, Any]. By default it returns the output of transform_ast, - together with a `transformer.Context` containing information about the - transformation process. - """ - future_features = inspect_utils.getfutureimports(fn) - node, source = parser.parse_entity(fn, future_features=future_features) - logging.log(3, 'Source code of %s:\n\n%s\n', fn, source) - - origin_info.resolve_entity(node, source, fn) - - namespace = inspect_utils.getnamespace(fn) - namer = naming.Namer(namespace) - new_name = namer.new_symbol(self.get_transformed_name(node), ()) - entity_info = transformer.EntityInfo( - name=new_name, - source_code=source, - source_file='', - future_features=future_features, - namespace=namespace) - context = transformer.Context(entity_info, namer, user_context) - - node = self._erase_arg_defaults(node) - result = self.transform_ast(node, context) - - return result, context - - -class PyToPy(GenericTranspiler): - """A generic Python-to-Python transpiler. - - Its `transform` method offers a function-in, function-out interface. - Internally, it takes care of parsing, caching and loading of the translated - code. - - Users typically subclass this, overriding `transform_ast`. - - Usually, instances of this class are singletons, since each instance manages - its own cache. The caching can be controlled by overriding `get_caching_key`. - - Example: - - class MyTransformer(PyToPy): - - def transform_ast(self, node, ctx): - node = <> - return node - - transformer = MyTransfomer() - - new_f, module, source_map = transformer.transform_function(f, ...) - # new_f is a function with signature identical to f - - The transformed function has access to the same namespace as the original - function. To allow access to internal APIs, users may inject additional - symbols by overriding `get_extra_locals`. - """ - - def __init__(self): - self._cache_lock = threading.RLock() - self._cache = cache.CodeObjectCache() - - def get_extra_locals(self): - """Returns extra static local variables to be made to transformed code. - - Subclasses must override this. - - Returns: - extra_locals: A Dict[Text, Any] containing additional variables to make - available to the transformed code. - """ - raise NotImplementedError('subclasses must override this') - - def get_caching_key(self, user_context): - """Returns a unique key to use for caching. - - Subclasses must override this. - - Calls made to `transform_function` with functions that have the same code - object and caching key will return a cached instance on subsequent - invocations. - - Args: - user_context: The context object which was passed to `transform`. - - Returns: - extra_locals: A hashable. - """ - raise NotImplementedError('subclasses must override this') - - def _cached_factory(self, fn, cache_subkey): - cached_factory = self._cache[fn][cache_subkey] - logging.log(3, 'Cache hit for %s subkey %s: %s', fn, cache_subkey, - cached_factory) - return cached_factory - - def transform_function(self, fn, user_context): - """Transforms a function. See GenericTranspiler.trasnform_function. - - This overload wraps the parent's `transform_function`, adding caching and - facilities to instantiate the output as a Python object. It also - adds facilities to make new symbols available to the generated Python code, - visible as local variables - see `get_extra_locals`. - - Args: - fn: A function or lambda. - user_context: An opaque object (may be None) that is forwarded to - transform_ast, through the ctx.user attribute. - Returns: - A tuple: - * A function or lambda with the same signature and closure as `fn` - * The temporary module into which the transformed function was loaded - * The source map as a - Dict[origin_info.LineLocation, origin_info.OriginInfo] - """ - cache_subkey = self.get_caching_key(user_context) - - if self._cache.has(fn, cache_subkey): - # Fast path: use a lock-free check. - factory = self._cached_factory(fn, cache_subkey) - - else: - with self._cache_lock: - # Check again under lock. - if self._cache.has(fn, cache_subkey): - factory = self._cached_factory(fn, cache_subkey) - - else: - logging.log(1, '%s is not cached for subkey %s', fn, cache_subkey) - # TODO(mdan): Confusing overloading pattern. Fix. - nodes, ctx = super(PyToPy, self).transform_function(fn, user_context) - - if isinstance(nodes, gast.Lambda): - nodes = gast.Assign( - targets=[ - gast.Name( - ctx.info.name, - ctx=gast.Store(), - annotation=None, - type_comment=None) - ], - value=nodes) - else: - nodes.name = ctx.info.name - - if logging.has_verbosity(2): - logging.log(2, 'Transformed %s:\n\n%s\n', fn, parser.unparse(nodes)) - - factory = _PythonFnFactory( - ctx.info.name, fn.__code__.co_freevars, self.get_extra_locals()) - factory.create( - nodes, ctx.namer, future_features=ctx.info.future_features) - self._cache[fn][cache_subkey] = factory - - transformed_fn = factory.instantiate( - globals_=fn.__globals__, - closure=fn.__closure__ or (), - defaults=fn.__defaults__, - kwdefaults=getattr(fn, '__kwdefaults__', None)) - return transformed_fn, factory.module, factory.source_map diff --git a/src/braket/experimental/autoqasm/autograph/pyct/transpiler_test.py b/src/braket/experimental/autoqasm/autograph/pyct/transpiler_test.py deleted file mode 100644 index e5236430..00000000 --- a/src/braket/experimental/autoqasm/autograph/pyct/transpiler_test.py +++ /dev/null @@ -1,240 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for transpiler module.""" - -import threading - -import gast - -from braket.experimental.autoqasm.autograph.pyct import transformer -from braket.experimental.autoqasm.autograph.pyct import transpiler -from tensorflow.python.platform import test - - -class FlipSignTransformer(transformer.Base): - - def visit_BinOp(self, node): - if isinstance(node.op, gast.Add): - node.op = gast.Sub() - return self.generic_visit(node) - - -class TestTranspiler(transpiler.PyToPy): - - def get_caching_key(self, ctx): - del ctx - return 0 - - def get_extra_locals(self): - return {} - - def transform_ast(self, node, ctx): - return FlipSignTransformer(ctx).visit(node) - - -global_var_for_test_global = 1 -global_var_for_test_namespace_collisions = object() - - -class PyToPyTest(test.TestCase): - - def test_basic(self): - def f(a): - return a + 1 - - tr = TestTranspiler() - f, _, _ = tr.transform(f, None) - - self.assertEqual(f(1), 0) - - def test_closure(self): - b = 1 - - def f(a): - return a + b - - tr = TestTranspiler() - f, _, _ = tr.transform(f, None) - - self.assertEqual(f(1), 0) - b = 2 - self.assertEqual(f(1), -1) - - def test_global(self): - def f(a): - return a + global_var_for_test_global - - tr = TestTranspiler() - f, _, _ = tr.transform(f, None) - - global global_var_for_test_global - global_var_for_test_global = 1 - self.assertEqual(f(1), 0) - global_var_for_test_global = 2 - self.assertEqual(f(1), -1) - - def test_defaults(self): - b = 2 - c = 1 - - def f(a, d=c + 1): - return a + b + d - - tr = TestTranspiler() - f, _, _ = tr.transform(f, None) - - self.assertEqual(f(1), 1 - 2 - 2) - c = 0 - self.assertEqual(f(1), 1 - 2 - 2) # Defaults are evaluated at definition. - b = 1 - self.assertEqual(f(1), 1 - 2 - 1) - - def test_call_tree(self): - - def g(a): - return a + 1 - - def f(a): - return g(a) + 1 - - tr = TestTranspiler() - f, _, _ = tr.transform(f, None) - - self.assertEqual(f(1), 1 - 1 + 1) # Only f is converted. - - def test_lambda(self): - b = 2 - f = lambda x: (b + (x if x > 0 else -x)) - - tr = TestTranspiler() - f, _, _ = tr.transform(f, None) - - self.assertEqual(f(1), 2 - 1) - self.assertEqual(f(-1), 2 - 1) - - b = 3 - - self.assertEqual(f(1), 3 - 1) - self.assertEqual(f(-1), 3 - 1) - - def test_multiple_lambdas(self): - a, b = 1, 2 - # This can be disambiguated by the argument names. - f, _ = (lambda x: a + x, lambda y: b * y) - - tr = TestTranspiler() - f, _, _ = tr.transform(f, None) - - self.assertEqual(f(1), 1 - 1) - - def test_nested_functions(self): - b = 2 - - def f(x): - - def g(x): - return b + x - - return g(x) - - tr = TestTranspiler() - f, _, _ = tr.transform(f, None) - - self.assertEqual(f(1), 2 - 1) - - def test_nested_lambda(self): - b = 2 - - def f(x): - g = lambda x: b + x - return g(x) - - tr = TestTranspiler() - f, _, _ = tr.transform(f, None) - - self.assertEqual(f(1), 2 - 1) - - def test_concurrency(self): - - def f(): - pass - - outputs = [] - - tr = TestTranspiler() - # Note: this is not a test, it's a required invariant. - assert tr.get_caching_key(None) == tr.get_caching_key(None) - - def conversion_thread(): - _, mod, _ = tr.transform(f, None) - outputs.append(mod.__name__) - - threads = tuple( - threading.Thread(target=conversion_thread) for _ in range(10)) - for t in threads: - t.start() - for t in threads: - t.join() - - # Races would potentially create multiple functions / modules - # (non-deterministically, but with high likelihood). - self.assertEqual(len(set(outputs)), 1) - - def test_reentrance(self): - - def test_fn(): - return 1 + 1 - - class ReentrantTranspiler(transpiler.PyToPy): - - def __init__(self): - super(ReentrantTranspiler, self).__init__() - self._recursion_depth = 0 - - def get_caching_key(self, ctx): - del ctx - return 0 - - def get_extra_locals(self): - return {} - - def transform_ast(self, node, ctx): - self._recursion_depth += 1 - if self._recursion_depth < 2: - self.transform(test_fn, None) - return FlipSignTransformer(ctx).visit(node) - - tr = ReentrantTranspiler() - - f, _, _ = tr.transform(test_fn, None) - self.assertEqual(f(), 0) - - def test_namespace_collisions_avoided(self): - - class TestClass(object): - - def global_var_for_test_namespace_collisions(self): - return global_var_for_test_namespace_collisions - - tr = TestTranspiler() - obj = TestClass() - - f, _, _ = tr.transform( - obj.global_var_for_test_namespace_collisions, None) - self.assertIs(f(obj), global_var_for_test_namespace_collisions) - - -if __name__ == '__main__': - test.main() diff --git a/src/braket/experimental/autoqasm/autograph/tf_utils/tf_decorator.py b/src/braket/experimental/autoqasm/autograph/tf_utils/tf_decorator.py deleted file mode 100644 index 635aabca..00000000 --- a/src/braket/experimental/autoqasm/autograph/tf_utils/tf_decorator.py +++ /dev/null @@ -1,361 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Base TFDecorator class and utility functions for working with decorators. - -There are two ways to create decorators that TensorFlow can introspect into. -This is important for documentation generation purposes, so that function -signatures aren't obscured by the (*args, **kwds) signature that decorators -often provide. - -1. Call `tf_decorator.make_decorator` on your wrapper function. If your -decorator is stateless, or can capture all of the variables it needs to work -with through lexical closure, this is the simplest option. Create your wrapper -function as usual, but instead of returning it, return -`tf_decorator.make_decorator(target, your_wrapper)`. This will attach some -decorator introspection metadata onto your wrapper and return it. - -Example: - - def print_hello_before_calling(target): - def wrapper(*args, **kwargs): - print('hello') - return target(*args, **kwargs) - return tf_decorator.make_decorator(target, wrapper) - -2. Derive from TFDecorator. If your decorator needs to be stateful, you can -implement it in terms of a TFDecorator. Store whatever state you need in your -derived class, and implement the `__call__` method to do your work before -calling into your target. You can retrieve the target via -`super(MyDecoratorClass, self).decorated_target`, and call it with whatever -parameters it needs. - -Example: - - class CallCounter(tf_decorator.TFDecorator): - def __init__(self, target): - super(CallCounter, self).__init__('count_calls', target) - self.call_count = 0 - - def __call__(self, *args, **kwargs): - self.call_count += 1 - return super(CallCounter, self).decorated_target(*args, **kwargs) - - def count_calls(target): - return CallCounter(target) -""" -import inspect -from typing import Dict, Any - - -def _make_default_values(fullargspec: inspect.FullArgSpec) -> Dict[str, Any]: - """Returns default values from the function's fullargspec.""" - if fullargspec.defaults is not None: - defaults = { - name: value for name, value in zip( - fullargspec.args[-len(fullargspec.defaults):], fullargspec.defaults) - } - else: - defaults = {} - - if fullargspec.kwonlydefaults is not None: - defaults.update(fullargspec.kwonlydefaults) - - return defaults - - -def fullargspec_to_signature( - fullargspec: inspect.FullArgSpec) -> inspect.Signature: - """Repackages fullargspec information into an equivalent inspect.Signature.""" - defaults = _make_default_values(fullargspec) - parameters = [] - - for arg in fullargspec.args: - parameters.append( - inspect.Parameter( - arg, - inspect.Parameter.POSITIONAL_OR_KEYWORD, - default=defaults.get(arg, inspect.Parameter.empty), - ) - ) - - if fullargspec.varargs is not None: - parameters.append( - inspect.Parameter(fullargspec.varargs, inspect.Parameter.VAR_POSITIONAL) - ) - - for kwarg in fullargspec.kwonlyargs: - parameters.append( - inspect.Parameter( - kwarg, - inspect.Parameter.KEYWORD_ONLY, - default=defaults.get(kwarg, inspect.Parameter.empty), - ) - ) - - if fullargspec.varkw is not None: - parameters.append( - inspect.Parameter(fullargspec.varkw, inspect.Parameter.VAR_KEYWORD) - ) - - return inspect.Signature(parameters) - - -def make_decorator(target, - decorator_func, - decorator_name=None, - decorator_doc='', - decorator_argspec=None): - """Make a decorator from a wrapper and a target. - - Args: - target: The final callable to be wrapped. - decorator_func: The wrapper function. - decorator_name: The name of the decorator. If `None`, the name of the - function calling make_decorator. - decorator_doc: Documentation specific to this application of - `decorator_func` to `target`. - decorator_argspec: Override the signature using FullArgSpec. - - Returns: - The `decorator_func` argument with new metadata attached. - """ - if decorator_name is None: - decorator_name = inspect.currentframe().f_back.f_code.co_name - decorator = TFDecorator(decorator_name, target, decorator_doc, - decorator_argspec) - setattr(decorator_func, '_tf_decorator', decorator) - # Objects that are callables (e.g., a functools.partial object) may not have - # the following attributes. - if hasattr(target, '__name__'): - decorator_func.__name__ = target.__name__ - if hasattr(target, '__qualname__'): - decorator_func.__qualname__ = target.__qualname__ - if hasattr(target, '__module__'): - decorator_func.__module__ = target.__module__ - if hasattr(target, '__dict__'): - # Copy dict entries from target which are not overridden by decorator_func. - for name in target.__dict__: - if name not in decorator_func.__dict__: - decorator_func.__dict__[name] = target.__dict__[name] - if hasattr(target, '__doc__'): - decorator_func.__doc__ = decorator.__doc__ - decorator_func.__wrapped__ = target - # Keeping a second handle to `target` allows callers to detect whether the - # decorator was modified using `rewrap`. - decorator_func.__original_wrapped__ = target - if decorator_argspec: - decorator_func.__signature__ = fullargspec_to_signature( - decorator_argspec) - elif callable(target): - try: - signature = inspect.signature(target) - except (TypeError, ValueError): - # Certain callables such as builtins can not be inspected for signature. - pass - else: - bound_instance = _get_bound_instance(target) - # Present the decorated func as a method as well - if bound_instance and 'self' in signature.parameters: - signature = inspect.Signature(list(signature.parameters.values())[1:]) - decorator_func.__self__ = bound_instance - - decorator_func.__signature__ = signature - - return decorator_func - - -def _get_bound_instance(target): - """Returns the instance any of the targets is attached to.""" - decorators, target = unwrap(target) - for decorator in decorators: - if inspect.ismethod(decorator.decorated_target): - return decorator.decorated_target.__self__ - - -def _has_tf_decorator_attr(obj): - """Checks if object has _tf_decorator attribute. - - This check would work for mocked object as well since it would - check if returned attribute has the right type. - - Args: - obj: Python object. - """ - return (hasattr(obj, '_tf_decorator') and - isinstance(getattr(obj, '_tf_decorator'), TFDecorator)) - - -def rewrap(decorator_func, previous_target, new_target): - """Injects a new target into a function built by make_decorator. - - This function allows replacing a function wrapped by `decorator_func`, - assuming the decorator that wraps the function is written as described below. - - The decorator function must use `.__wrapped__` instead of the - wrapped function that is normally used: - - Example: - - # Instead of this: - def simple_parametrized_wrapper(*args, **kwds): - return wrapped_fn(*args, **kwds) - - tf_decorator.make_decorator(simple_parametrized_wrapper, wrapped_fn) - - # Write this: - def simple_parametrized_wrapper(*args, **kwds): - return simple_parametrized_wrapper.__wrapped__(*args, **kwds) - - tf_decorator.make_decorator(simple_parametrized_wrapper, wrapped_fn) - - Note that this process modifies decorator_func. - - Args: - decorator_func: Callable returned by `wrap`. - previous_target: Callable that needs to be replaced. - new_target: Callable to replace previous_target with. - - Returns: - The updated decorator. If decorator_func is not a tf_decorator, new_target - is returned. - """ - # Because the process mutates the decorator, we only need to alter the - # innermost function that wraps previous_target. - cur = decorator_func - innermost_decorator = None - target = None - while _has_tf_decorator_attr(cur): - innermost_decorator = cur - target = getattr(cur, '_tf_decorator') - if target.decorated_target is previous_target: - break - cur = target.decorated_target - assert cur is not None - - # If decorator_func is not a decorator, new_target replaces it directly. - if innermost_decorator is None: - # Consistency check. The caller should always pass the result of - # tf_decorator.unwrap as previous_target. If decorator_func is not a - # decorator, that will have returned decorator_func itself. - assert decorator_func is previous_target - return new_target - - target.decorated_target = new_target - - if inspect.ismethod(innermost_decorator): - # Bound methods can't be assigned attributes. Thankfully, they seem to - # be just proxies for their unbound counterpart, and we can modify that. - if hasattr(innermost_decorator, '__func__'): - innermost_decorator.__func__.__wrapped__ = new_target - elif hasattr(innermost_decorator, 'im_func'): - innermost_decorator.im_func.__wrapped__ = new_target - else: - innermost_decorator.__wrapped__ = new_target - else: - innermost_decorator.__wrapped__ = new_target - - return decorator_func - - -def unwrap(maybe_tf_decorator): - """Unwraps an object into a list of TFDecorators and a final target. - - Args: - maybe_tf_decorator: Any callable object. - - Returns: - A tuple whose first element is an list of TFDecorator-derived objects that - were applied to the final callable target, and whose second element is the - final undecorated callable target. If the `maybe_tf_decorator` parameter is - not decorated by any TFDecorators, the first tuple element will be an empty - list. The `TFDecorator` list is ordered from outermost to innermost - decorators. - """ - decorators = [] - cur = maybe_tf_decorator - while True: - if isinstance(cur, TFDecorator): - decorators.append(cur) - elif _has_tf_decorator_attr(cur): - decorators.append(getattr(cur, '_tf_decorator')) - else: - break - if not hasattr(decorators[-1], 'decorated_target'): - break - cur = decorators[-1].decorated_target - return decorators, cur - - -class TFDecorator(object): - """Base class for all TensorFlow decorators. - - TFDecorator captures and exposes the wrapped target, and provides details - about the current decorator. - """ - - def __init__(self, - decorator_name, - target, - decorator_doc='', - decorator_argspec=None): - self._decorated_target = target - self._decorator_name = decorator_name - self._decorator_doc = decorator_doc - self._decorator_argspec = decorator_argspec - if hasattr(target, '__name__'): - self.__name__ = target.__name__ - if hasattr(target, '__qualname__'): - self.__qualname__ = target.__qualname__ - if self._decorator_doc: - self.__doc__ = self._decorator_doc - elif hasattr(target, '__doc__') and target.__doc__: - self.__doc__ = target.__doc__ - else: - self.__doc__ = '' - - if decorator_argspec: - self.__signature__ = fullargspec_to_signature(decorator_argspec) - elif callable(target): - try: - self.__signature__ = inspect.signature(target) - except (TypeError, ValueError): - # Certain callables such as builtins can not be inspected for signature. - pass - - def __get__(self, instance, owner): - return self._decorated_target.__get__(instance, owner) - - def __call__(self, *args, **kwargs): - return self._decorated_target(*args, **kwargs) - - @property - def decorated_target(self): - return self._decorated_target - - @decorated_target.setter - def decorated_target(self, decorated_target): - self._decorated_target = decorated_target - - @property - def decorator_name(self): - return self._decorator_name - - @property - def decorator_doc(self): - return self._decorator_doc - - @property - def decorator_argspec(self): - return self._decorator_argspec \ No newline at end of file diff --git a/src/braket/experimental/autoqasm/autograph/tf_utils/tf_export.py b/src/braket/experimental/autoqasm/autograph/tf_utils/tf_export.py deleted file mode 100644 index 4d60f4bd..00000000 --- a/src/braket/experimental/autoqasm/autograph/tf_utils/tf_export.py +++ /dev/null @@ -1,420 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Utilities for exporting TensorFlow symbols to the API. - -Exporting a function or a class: - -To export a function or a class use tf_export decorator. For e.g.: -```python -@tf_export('foo', 'bar.foo') -def foo(...): - ... -``` - -If a function is assigned to a variable, you can export it by calling -tf_export explicitly. For e.g.: -```python -foo = get_foo(...) -tf_export('foo', 'bar.foo')(foo) -``` - - -Exporting a constant -```python -foo = 1 -tf_export('consts.foo').export_constant(__name__, 'foo') -``` -""" -import collections -import functools -import sys - -from braket.experimental.autoqasm.autograph.tf_utils import tf_decorator -import inspect as tf_inspect - -ESTIMATOR_API_NAME = 'estimator' -KERAS_API_NAME = 'keras' -TENSORFLOW_API_NAME = 'tensorflow' - -# List of subpackage names used by TensorFlow components. Have to check that -# TensorFlow core repo does not export any symbols under these names. -SUBPACKAGE_NAMESPACES = [ESTIMATOR_API_NAME] - -_Attributes = collections.namedtuple( - 'ExportedApiAttributes', ['names', 'constants']) - -# Attribute values must be unique to each API. -API_ATTRS = { - TENSORFLOW_API_NAME: _Attributes( - '_tf_api_names', - '_tf_api_constants'), - ESTIMATOR_API_NAME: _Attributes( - '_estimator_api_names', - '_estimator_api_constants'), - KERAS_API_NAME: _Attributes( - '_keras_api_names', - '_keras_api_constants') -} - -API_ATTRS_V1 = { - TENSORFLOW_API_NAME: _Attributes( - '_tf_api_names_v1', - '_tf_api_constants_v1'), - ESTIMATOR_API_NAME: _Attributes( - '_estimator_api_names_v1', - '_estimator_api_constants_v1'), - KERAS_API_NAME: _Attributes( - '_keras_api_names_v1', - '_keras_api_constants_v1') -} - - -class SymbolAlreadyExposedError(Exception): - """Raised when adding API names to symbol that already has API names.""" - pass - - -class InvalidSymbolNameError(Exception): - """Raised when trying to export symbol as an invalid or unallowed name.""" - pass - -_NAME_TO_SYMBOL_MAPPING = dict() - - -def get_symbol_from_name(name): - return _NAME_TO_SYMBOL_MAPPING.get(name) - - -def get_canonical_name_for_symbol( - symbol, api_name=TENSORFLOW_API_NAME, - add_prefix_to_v1_names=False): - """Get canonical name for the API symbol. - - Example: - ```python - from tensorflow.python.util import tf_export - cls = tf_export.get_symbol_from_name('keras.optimizers.Adam') - - # Gives `` - print(cls) - - # Gives `keras.optimizers.Adam` - print(tf_export.get_canonical_name_for_symbol(cls, api_name='keras')) - ``` - - Args: - symbol: API function or class. - api_name: API name (tensorflow or estimator). - add_prefix_to_v1_names: Specifies whether a name available only in V1 - should be prefixed with compat.v1. - - Returns: - Canonical name for the API symbol (for e.g. initializers.zeros) if - canonical name could be determined. Otherwise, returns None. - """ - if not hasattr(symbol, '__dict__'): - return None - api_names_attr = API_ATTRS[api_name].names - _, undecorated_symbol = tf_decorator.unwrap(symbol) - if api_names_attr not in undecorated_symbol.__dict__: - return None - api_names = getattr(undecorated_symbol, api_names_attr) - deprecated_api_names = undecorated_symbol.__dict__.get( - '_tf_deprecated_api_names', []) - - canonical_name = get_canonical_name(api_names, deprecated_api_names) - if canonical_name: - return canonical_name - - # If there is no V2 canonical name, get V1 canonical name. - api_names_attr = API_ATTRS_V1[api_name].names - api_names = getattr(undecorated_symbol, api_names_attr) - v1_canonical_name = get_canonical_name(api_names, deprecated_api_names) - if add_prefix_to_v1_names: - return 'compat.v1.%s' % v1_canonical_name - return v1_canonical_name - - -def get_canonical_name(api_names, deprecated_api_names): - """Get preferred endpoint name. - - Args: - api_names: API names iterable. - deprecated_api_names: Deprecated API names iterable. - Returns: - Returns one of the following in decreasing preference: - - first non-deprecated endpoint - - first endpoint - - None - """ - non_deprecated_name = next( - (name for name in api_names if name not in deprecated_api_names), - None) - if non_deprecated_name: - return non_deprecated_name - if api_names: - return api_names[0] - return None - - -def get_v1_names(symbol): - """Get a list of TF 1.* names for this symbol. - - Args: - symbol: symbol to get API names for. - - Returns: - List of all API names for this symbol including TensorFlow and - Estimator names. - """ - names_v1 = [] - tensorflow_api_attr_v1 = API_ATTRS_V1[TENSORFLOW_API_NAME].names - estimator_api_attr_v1 = API_ATTRS_V1[ESTIMATOR_API_NAME].names - keras_api_attr_v1 = API_ATTRS_V1[KERAS_API_NAME].names - - if not hasattr(symbol, '__dict__'): - return names_v1 - if tensorflow_api_attr_v1 in symbol.__dict__: - names_v1.extend(getattr(symbol, tensorflow_api_attr_v1)) - if estimator_api_attr_v1 in symbol.__dict__: - names_v1.extend(getattr(symbol, estimator_api_attr_v1)) - if keras_api_attr_v1 in symbol.__dict__: - names_v1.extend(getattr(symbol, keras_api_attr_v1)) - return names_v1 - - -def get_v2_names(symbol): - """Get a list of TF 2.0 names for this symbol. - - Args: - symbol: symbol to get API names for. - - Returns: - List of all API names for this symbol including TensorFlow and - Estimator names. - """ - names_v2 = [] - tensorflow_api_attr = API_ATTRS[TENSORFLOW_API_NAME].names - estimator_api_attr = API_ATTRS[ESTIMATOR_API_NAME].names - keras_api_attr = API_ATTRS[KERAS_API_NAME].names - - if not hasattr(symbol, '__dict__'): - return names_v2 - if tensorflow_api_attr in symbol.__dict__: - names_v2.extend(getattr(symbol, tensorflow_api_attr)) - if estimator_api_attr in symbol.__dict__: - names_v2.extend(getattr(symbol, estimator_api_attr)) - if keras_api_attr in symbol.__dict__: - names_v2.extend(getattr(symbol, keras_api_attr)) - return names_v2 - - -def get_v1_constants(module): - """Get a list of TF 1.* constants in this module. - - Args: - module: TensorFlow module. - - Returns: - List of all API constants under the given module including TensorFlow and - Estimator constants. - """ - constants_v1 = [] - tensorflow_constants_attr_v1 = API_ATTRS_V1[TENSORFLOW_API_NAME].constants - estimator_constants_attr_v1 = API_ATTRS_V1[ESTIMATOR_API_NAME].constants - - if hasattr(module, tensorflow_constants_attr_v1): - constants_v1.extend(getattr(module, tensorflow_constants_attr_v1)) - if hasattr(module, estimator_constants_attr_v1): - constants_v1.extend(getattr(module, estimator_constants_attr_v1)) - return constants_v1 - - -def get_v2_constants(module): - """Get a list of TF 2.0 constants in this module. - - Args: - module: TensorFlow module. - - Returns: - List of all API constants under the given module including TensorFlow and - Estimator constants. - """ - constants_v2 = [] - tensorflow_constants_attr = API_ATTRS[TENSORFLOW_API_NAME].constants - estimator_constants_attr = API_ATTRS[ESTIMATOR_API_NAME].constants - - if hasattr(module, tensorflow_constants_attr): - constants_v2.extend(getattr(module, tensorflow_constants_attr)) - if hasattr(module, estimator_constants_attr): - constants_v2.extend(getattr(module, estimator_constants_attr)) - return constants_v2 - - -class api_export(object): # pylint: disable=invalid-name - """Provides ways to export symbols to the TensorFlow API.""" - - def __init__(self, *args, **kwargs): # pylint: disable=g-doc-args - """Export under the names *args (first one is considered canonical). - - Args: - *args: API names in dot delimited format. - **kwargs: Optional keyed arguments. - v1: Names for the TensorFlow V1 API. If not set, we will use V2 API - names both for TensorFlow V1 and V2 APIs. - overrides: List of symbols that this is overriding - (those overrided api exports will be removed). Note: passing overrides - has no effect on exporting a constant. - api_name: Name of the API you want to generate (e.g. `tensorflow` or - `estimator`). Default is `tensorflow`. - allow_multiple_exports: Allow symbol to be exported multiple time under - different names. - """ - self._names = args - self._names_v1 = kwargs.get('v1', args) - if 'v2' in kwargs: - raise ValueError('You passed a "v2" argument to tf_export. This is not ' - 'what you want. Pass v2 names directly as positional ' - 'arguments instead.') - self._api_name = kwargs.get('api_name', TENSORFLOW_API_NAME) - self._overrides = kwargs.get('overrides', []) - self._allow_multiple_exports = kwargs.get('allow_multiple_exports', False) - - self._validate_symbol_names() - - def _validate_symbol_names(self): - """Validate you are exporting symbols under an allowed package. - - We need to ensure things exported by tf_export, estimator_export, etc. - export symbols under disjoint top-level package names. - - For TensorFlow, we check that it does not export anything under subpackage - names used by components (estimator, keras, etc.). - - For each component, we check that it exports everything under its own - subpackage. - - Raises: - InvalidSymbolNameError: If you try to export symbol under disallowed name. - """ - all_symbol_names = set(self._names) | set(self._names_v1) - if self._api_name == TENSORFLOW_API_NAME: - for subpackage in SUBPACKAGE_NAMESPACES: - if any(n.startswith(subpackage) for n in all_symbol_names): - raise InvalidSymbolNameError( - '@tf_export is not allowed to export symbols under %s.*' % ( - subpackage)) - else: - if not all(n.startswith(self._api_name) for n in all_symbol_names): - raise InvalidSymbolNameError( - 'Can only export symbols under package name of component. ' - 'e.g. tensorflow_estimator must export all symbols under ' - 'tf.estimator') - - def __call__(self, func): - """Calls this decorator. - - Args: - func: decorated symbol (function or class). - - Returns: - The input function with _tf_api_names attribute set. - - Raises: - SymbolAlreadyExposedError: Raised when a symbol already has API names - and kwarg `allow_multiple_exports` not set. - """ - api_names_attr = API_ATTRS[self._api_name].names - api_names_attr_v1 = API_ATTRS_V1[self._api_name].names - # Undecorate overridden names - for f in self._overrides: - _, undecorated_f = tf_decorator.unwrap(f) - delattr(undecorated_f, api_names_attr) - delattr(undecorated_f, api_names_attr_v1) - - _, undecorated_func = tf_decorator.unwrap(func) - self.set_attr(undecorated_func, api_names_attr, self._names) - self.set_attr(undecorated_func, api_names_attr_v1, self._names_v1) - - for name in self._names: - _NAME_TO_SYMBOL_MAPPING[name] = func - for name_v1 in self._names_v1: - _NAME_TO_SYMBOL_MAPPING['compat.v1.%s' % name_v1] = func - - return func - - def set_attr(self, func, api_names_attr, names): - # Check for an existing api. We check if attribute name is in - # __dict__ instead of using hasattr to verify that subclasses have - # their own _tf_api_names as opposed to just inheriting it. - if api_names_attr in func.__dict__: - if not self._allow_multiple_exports: - raise SymbolAlreadyExposedError( - 'Symbol %s is already exposed as %s.' % - (func.__name__, getattr(func, api_names_attr))) # pylint: disable=protected-access - setattr(func, api_names_attr, names) - - def export_constant(self, module_name, name): - """Store export information for constants/string literals. - - Export information is stored in the module where constants/string literals - are defined. - - e.g. - ```python - foo = 1 - bar = 2 - tf_export("consts.foo").export_constant(__name__, 'foo') - tf_export("consts.bar").export_constant(__name__, 'bar') - ``` - - Args: - module_name: (string) Name of the module to store constant at. - name: (string) Current constant name. - """ - module = sys.modules[module_name] - api_constants_attr = API_ATTRS[self._api_name].constants - api_constants_attr_v1 = API_ATTRS_V1[self._api_name].constants - - if not hasattr(module, api_constants_attr): - setattr(module, api_constants_attr, []) - # pylint: disable=protected-access - getattr(module, api_constants_attr).append( - (self._names, name)) - - if not hasattr(module, api_constants_attr_v1): - setattr(module, api_constants_attr_v1, []) - getattr(module, api_constants_attr_v1).append( - (self._names_v1, name)) - - -def kwarg_only(f): - """A wrapper that throws away all non-kwarg arguments.""" - f_argspec = tf_inspect.getfullargspec(f) - - def wrapper(*args, **kwargs): - if args: - raise TypeError( - '{f} only takes keyword args (possible keys: {kwargs}). ' - 'Please pass these args as kwargs instead.' - .format(f=f.__name__, kwargs=f_argspec.args)) - return f(**kwargs) - - return tf_decorator.make_decorator( - f, wrapper, decorator_argspec=f_argspec) - - -tf_export = functools.partial(api_export, api_name=TENSORFLOW_API_NAME) -keras_export = functools.partial(api_export, api_name=KERAS_API_NAME) \ No newline at end of file diff --git a/src/braket/experimental/autoqasm/autograph/tf_utils/tf_stack.py b/src/braket/experimental/autoqasm/autograph/tf_utils/tf_stack.py deleted file mode 100644 index 91cc8df3..00000000 --- a/src/braket/experimental/autoqasm/autograph/tf_utils/tf_stack.py +++ /dev/null @@ -1,168 +0,0 @@ -# Copyright 2015 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Functions used to extract and analyze stacks. Faster than Python libs.""" -# pylint: disable=g-bad-name -import collections -import inspect -import threading -import traceback - -# Generally such lookups should be done using `threading.local()`. See -# https://blogs.gnome.org/jamesh/2008/06/11/tls-python/ for a detailed -# explanation of why. However the transform stacks are expected to be empty -# when a thread is joined, so reusing the key does not introduce a correctness -# issue. Moreover, get_ident is faster than storing and retrieving a unique -# key in a thread local store. -_get_thread_key = threading.get_ident - - -# TODO(mdan): Move these to C++ as well. -# Moving to C++ can further avoid extra copies made by get_effective_map. -_source_mapper_stacks = collections.defaultdict(lambda: [SentinelMapper()]) -_source_filter_stacks = collections.defaultdict(lambda: [SentinelFilter()]) - - -class StackTraceTransform(object): - """Base class for stack trace transformation functions.""" - - _stack_dict = None # Subclasses should override - _thread_key = None - - def __enter__(self): - # Any given instance is assumed to be used by a single thread, which reduces - # expensive thread local lookups. - if self._thread_key is None: - self._thread_key = _get_thread_key() - else: - assert self._thread_key == _get_thread_key(), 'Shared across threads?' - - stack = self._stack_dict[self._thread_key] - self.parent = stack[-1] - stack.append(self) - self.update() - return self - - def __exit__(self, unused_type, unused_value, unused_traceback): - top = self._stack_dict[self._thread_key].pop() - assert top is self, 'Concurrent access?' - - def update(self): - raise NotImplementedError('subclasses need to override this') - - -class StackTraceMapper(StackTraceTransform): - """Allows remapping traceback information to different source code.""" - _stack_dict = _source_mapper_stacks - - def __init__(self): - pass - - def update(self): - pass - - def get_effective_source_map(self): - """Returns a map (filename, lineno) -> (filename, lineno, function_name).""" - raise NotImplementedError('subclasses need to override this') - - -EMPTY_DICT = {} - - -class SentinelMapper(StackTraceMapper): - - def get_effective_source_map(self): - return EMPTY_DICT - - -class StackTraceFilter(StackTraceTransform): - """Allows filtering traceback information by removing superfluous frames.""" - _stack_dict = _source_filter_stacks - - def __init__(self): - pass - - def update(self): - pass - - def get_filtered_filenames(self): - raise NotImplementedError('subclasses need to override this') - - -EMPTY_SET = frozenset() - - -class SentinelFilter(StackTraceFilter): - - def get_filtered_filenames(self): - return EMPTY_SET - - -class CurrentModuleFilter(StackTraceFilter): - """Filters stack frames from the module where this is used (best effort).""" - - def __init__(self): - super().__init__() - filter_filename = None - outer_f = None - f = inspect.currentframe() - try: - if f is not None: - # The current frame is __init__. The first outer frame should be the - # caller. - outer_f = f.f_back - if outer_f is not None: - filter_filename = inspect.getsourcefile(outer_f) - self._filename = filter_filename - # This may be called repeatedly: once on entry by the superclass, then by - # each child context manager. - self._cached_set = None - finally: - # Avoid reference cycles, see: - # https://docs.python.org/3.7/library/inspect.html#the-interpreter-stack - del f - del outer_f - - def get_filtered_filenames(self): - if self._cached_set is not None: - return self._cached_set - - filtered_filenames = frozenset((self._filename,)) - if self.parent is not None: - filtered_filenames |= self.parent.get_filtered_filenames() - self._cached_set = filtered_filenames - return filtered_filenames - - -def extract_stack(): - """An eager-friendly alternative to traceback.extract_stack. - - Returns: - A list-like FrameSummary containing StackFrame-like objects, which are - namedtuple-like objects with the following fields: filename, lineno, name, - line, meant to masquerade as traceback.FrameSummary objects. - """ - return traceback.extract_stack() - - -# TODO(mdan): Revisit these - a single location is almost always sufficient. -def extract_stack_for_op(c_op, stacklevel=1): - """Attaches the current stack trace to `c_op`. - - Args: - c_op: a TF_Operation object. - stacklevel: An integer for ignoring Python wrapper stack frames. - The default value of 1 ignores this function from the frame. - """ - return traceback.extract_stack() \ No newline at end of file diff --git a/src/braket/experimental/autoqasm/autograph/tf_utils/traceback_utils.py b/src/braket/experimental/autoqasm/autograph/tf_utils/traceback_utils.py deleted file mode 100644 index bf7cb88d..00000000 --- a/src/braket/experimental/autoqasm/autograph/tf_utils/traceback_utils.py +++ /dev/null @@ -1,157 +0,0 @@ -# Copyright 2021 The TensorFlow Authors. 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. -# ============================================================================== -"""Utilities related to TensorFlow exception stack trace prettifying.""" - -import os -import sys -import threading -import traceback -import types -from braket.experimental.autoqasm.autograph.tf_utils import tf_decorator -from braket.experimental.autoqasm.autograph.tf_utils.tf_export import tf_export - - -_ENABLE_TRACEBACK_FILTERING = threading.local() -_EXCLUDED_PATHS = ( - os.path.abspath(os.path.join(__file__, '..', '..')), -) - - -@tf_export('debugging.is_traceback_filtering_enabled') -def is_traceback_filtering_enabled(): - """Check whether traceback filtering is currently enabled. - - See also `tf.debugging.enable_traceback_filtering()` and - `tf.debugging.disable_traceback_filtering()`. Note that filtering out - internal frames from the tracebacks of exceptions raised by TensorFlow code - is the default behavior. - - Returns: - True if traceback filtering is enabled - (e.g. if `tf.debugging.enable_traceback_filtering()` was called), - and False otherwise (e.g. if `tf.debugging.disable_traceback_filtering()` - was called). - """ - value = getattr(_ENABLE_TRACEBACK_FILTERING, 'value', True) - return value - - -@tf_export('debugging.enable_traceback_filtering') -def enable_traceback_filtering(): - """Enable filtering out TensorFlow-internal frames in exception stack traces. - - Raw TensorFlow stack traces involve many internal frames, which can be - challenging to read through, while not being actionable for end users. - By default, TensorFlow filters internal frames in most exceptions that it - raises, to keep stack traces short, readable, and focused on what's - actionable for end users (their own code). - - If you have previously disabled traceback filtering via - `tf.debugging.disable_traceback_filtering()`, you can re-enable it via - `tf.debugging.enable_traceback_filtering()`. - - Raises: - RuntimeError: If Python version is not at least 3.7. - """ - if sys.version_info.major != 3 or sys.version_info.minor < 7: - raise RuntimeError( - f'Traceback filtering is only available with Python 3.7 or higher. ' - f'This Python version: {sys.version}') - global _ENABLE_TRACEBACK_FILTERING - _ENABLE_TRACEBACK_FILTERING.value = True - - -@tf_export('debugging.disable_traceback_filtering') -def disable_traceback_filtering(): - """Disable filtering out TensorFlow-internal frames in exception stack traces. - - Raw TensorFlow stack traces involve many internal frames, which can be - challenging to read through, while not being actionable for end users. - By default, TensorFlow filters internal frames in most exceptions that it - raises, to keep stack traces short, readable, and focused on what's - actionable for end users (their own code). - - Calling `tf.debugging.disable_traceback_filtering` disables this filtering - mechanism, meaning that TensorFlow exceptions stack traces will include - all frames, in particular TensorFlow-internal ones. - - **If you are debugging a TensorFlow-internal issue, you need to call - `tf.debugging.disable_traceback_filtering`**. - To re-enable traceback filtering afterwards, you can call - `tf.debugging.enable_traceback_filtering()`. - """ - global _ENABLE_TRACEBACK_FILTERING - _ENABLE_TRACEBACK_FILTERING.value = False - - -def include_frame(fname): - for exclusion in _EXCLUDED_PATHS: - if exclusion in fname: - return False - return True - - -def _process_traceback_frames(tb): - new_tb = None - tb_list = list(traceback.walk_tb(tb)) - for f, line_no in reversed(tb_list): - if include_frame(f.f_code.co_filename): - new_tb = types.TracebackType(new_tb, f, f.f_lasti, line_no) - if new_tb is None and tb_list: - f, line_no = tb_list[-1] - new_tb = types.TracebackType(new_tb, f, f.f_lasti, line_no) - return new_tb - - -def filter_traceback(fn): - """Decorator to filter out TF-internal stack trace frames in exceptions. - - Raw TensorFlow stack traces involve many internal frames, which can be - challenging to read through, while not being actionable for end users. - By default, TensorFlow filters internal frames in most exceptions that it - raises, to keep stack traces short, readable, and focused on what's - actionable for end users (their own code). - - Arguments: - fn: The function or method to decorate. Any exception raised within the - function will be reraised with its internal stack trace frames filtered - out. - - Returns: - Decorated function or method. - """ - if sys.version_info.major != 3 or sys.version_info.minor < 7: - return fn - - def error_handler(*args, **kwargs): - try: - if not is_traceback_filtering_enabled(): - return fn(*args, **kwargs) - except NameError: - # In some very rare cases, - # `is_traceback_filtering_enabled` (from the outer scope) may not be - # accessible from inside this function - return fn(*args, **kwargs) - - filtered_tb = None - try: - return fn(*args, **kwargs) - except Exception as e: - filtered_tb = _process_traceback_frames(e.__traceback__) - raise e.with_traceback(filtered_tb) from None - finally: - del filtered_tb - - return tf_decorator.make_decorator(fn, error_handler) \ No newline at end of file diff --git a/src/braket/experimental/autoqasm/converters/assignments.py b/src/braket/experimental/autoqasm/converters/assignments.py index c1f64a44..34f03363 100644 --- a/src/braket/experimental/autoqasm/converters/assignments.py +++ b/src/braket/experimental/autoqasm/converters/assignments.py @@ -17,9 +17,9 @@ import ast import gast +from malt.core import ag_ctx, converter +from malt.pyct import templates -from braket.experimental.autoqasm.autograph.core import ag_ctx, converter -from braket.experimental.autoqasm.autograph.pyct import templates from braket.experimental.autoqasm.operators.assignments import assign_for_output diff --git a/src/braket/experimental/autoqasm/converters/break_statements.py b/src/braket/experimental/autoqasm/converters/break_statements.py index 47884066..64695c2c 100644 --- a/src/braket/experimental/autoqasm/converters/break_statements.py +++ b/src/braket/experimental/autoqasm/converters/break_statements.py @@ -16,9 +16,10 @@ import ast +from malt.converters import break_statements +from malt.core import ag_ctx, converter + from braket.experimental.autoqasm import errors -from braket.experimental.autoqasm.autograph.converters import break_statements -from braket.experimental.autoqasm.autograph.core import ag_ctx, converter class BreakValidator(converter.Base): diff --git a/src/braket/experimental/autoqasm/converters/comparisons.py b/src/braket/experimental/autoqasm/converters/comparisons.py index 7b8d53a5..7fffdd81 100644 --- a/src/braket/experimental/autoqasm/converters/comparisons.py +++ b/src/braket/experimental/autoqasm/converters/comparisons.py @@ -16,9 +16,8 @@ import ast import gast - -from braket.experimental.autoqasm.autograph.core import ag_ctx, converter -from braket.experimental.autoqasm.autograph.pyct import templates +from malt.core import ag_ctx, converter +from malt.pyct import templates COMPARISON_OPERATORS = { gast.Lt: "ag__.lt_", diff --git a/src/braket/experimental/autoqasm/converters/return_statements.py b/src/braket/experimental/autoqasm/converters/return_statements.py index 63463141..2f599c77 100644 --- a/src/braket/experimental/autoqasm/converters/return_statements.py +++ b/src/braket/experimental/autoqasm/converters/return_statements.py @@ -17,11 +17,11 @@ import ast import gast +from malt.converters import return_statements +from malt.core import ag_ctx, converter +from malt.pyct import templates from braket.experimental.autoqasm import program -from braket.experimental.autoqasm.autograph.converters import return_statements -from braket.experimental.autoqasm.autograph.core import ag_ctx, converter -from braket.experimental.autoqasm.autograph.pyct import templates from braket.experimental.autoqasm.operators.assignments import assign_for_output diff --git a/src/braket/experimental/autoqasm/operators/__init__.py b/src/braket/experimental/autoqasm/operators/__init__.py index ba8e0331..04acb310 100644 --- a/src/braket/experimental/autoqasm/operators/__init__.py +++ b/src/braket/experimental/autoqasm/operators/__init__.py @@ -18,13 +18,8 @@ """ # Operators below are imported directly from core autograph implementation -from braket.experimental.autoqasm.autograph.impl.api_core import autograph_artifact # noqa: F401 -from braket.experimental.autoqasm.autograph.operators.variables import ( # noqa: F401 - Undefined, - UndefinedReturnValue, - ld, - ldu, -) +from malt.impl.api import autograph_artifact # noqa: F401 +from malt.operators.variables import Undefined, UndefinedReturnValue, ld, ldu # noqa: F401 from .assignments import assign_for_output, assign_stmt # noqa: F401 from .comparisons import gt_, gteq_, lt_, lteq_ # noqa: F401 diff --git a/src/braket/experimental/autoqasm/operators/assignments.py b/src/braket/experimental/autoqasm/operators/assignments.py index 23efb82c..2dac8e18 100644 --- a/src/braket/experimental/autoqasm/operators/assignments.py +++ b/src/braket/experimental/autoqasm/operators/assignments.py @@ -19,9 +19,9 @@ import oqpy import oqpy.base +from malt.operators.variables import UndefinedReturnValue from braket.experimental.autoqasm import constants, errors, program, types -from braket.experimental.autoqasm.autograph.operators.variables import UndefinedReturnValue from braket.experimental.autoqasm.types.conversions import var_type_from_oqpy diff --git a/src/braket/experimental/autoqasm/transpiler/transpiler.py b/src/braket/experimental/autoqasm/transpiler/transpiler.py index b78e0c03..815c9dfb 100644 --- a/src/braket/experimental/autoqasm/transpiler/transpiler.py +++ b/src/braket/experimental/autoqasm/transpiler/transpiler.py @@ -25,9 +25,7 @@ from typing import Any, Optional, Union import gast - -from braket.experimental.autoqasm import operators, program, types -from braket.experimental.autoqasm.autograph.converters import ( +from malt.converters import ( asserts, call_trees, conditional_expressions, @@ -40,25 +38,14 @@ slices, variables, ) -from braket.experimental.autoqasm.autograph.core import ( - ag_ctx, - converter, - function_wrappers, - unsupported_features_checker, -) -from braket.experimental.autoqasm.autograph.impl.api_core import ( - StackTraceMapper, - _attach_error_metadata, - _log_callargs, - is_autograph_artifact, -) -from braket.experimental.autoqasm.autograph.logging import ag_logging as logging -from braket.experimental.autoqasm.autograph.pyct import anno, cfg, qual_names, transpiler -from braket.experimental.autoqasm.autograph.pyct.static_analysis import ( - activity, - reaching_definitions, -) -from braket.experimental.autoqasm.autograph.tf_utils import tf_stack +from malt.core import ag_ctx, converter, unsupported_features_checker +from malt.impl.api import _attach_error_metadata, _log_callargs, is_autograph_artifact +from malt.operators import function_wrappers +from malt.pyct import anno, cfg, qual_names, transpiler +from malt.pyct.static_analysis import activity, reaching_definitions +from malt.utils import ag_logging as logging + +from braket.experimental.autoqasm import operators, program, types from braket.experimental.autoqasm.converters import ( assignments, break_statements, @@ -241,13 +228,12 @@ def converted_call( if exc: raise exc - with StackTraceMapper(converted_f), tf_stack.CurrentModuleFilter(): - try: - effective_kwargs = kwargs or {} - result = converted_f(*effective_args, **effective_kwargs) - except Exception as e: - _attach_error_metadata(e, converted_f) - raise + try: + effective_kwargs = kwargs or {} + result = converted_f(*effective_args, **effective_kwargs) + except Exception as e: + _attach_error_metadata(e, converted_f) + raise return types.wrap_value(result) diff --git a/test/unit_tests/braket/experimental/autoqasm/mock_transpiler.py b/test/unit_tests/braket/experimental/autoqasm/mock_transpiler.py index fa53451a..e30895a6 100644 --- a/test/unit_tests/braket/experimental/autoqasm/mock_transpiler.py +++ b/test/unit_tests/braket/experimental/autoqasm/mock_transpiler.py @@ -16,8 +16,8 @@ from typing import Union import gast +from malt.core import ag_ctx -from braket.experimental.autoqasm.autograph.core import ag_ctx from braket.experimental.autoqasm.transpiler import PyToOqpy # TODO: Implement a converter abstract class for better type hinting. diff --git a/test/unit_tests/braket/experimental/autoqasm/test_converters.py b/test/unit_tests/braket/experimental/autoqasm/test_converters.py index aa08f8d7..14992a75 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_converters.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_converters.py @@ -14,10 +14,10 @@ """Tests for the converters module.""" import pytest +from malt.core import ag_ctx, converter from mock_transpiler import MockTranspiler import braket.experimental.autoqasm as aq -from braket.experimental.autoqasm.autograph.core import ag_ctx, converter from braket.experimental.autoqasm.converters import assignments diff --git a/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py b/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py index ef066192..7f34a255 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py @@ -16,10 +16,10 @@ import functools import pytest +from malt.core.ag_ctx import ControlStatusCtx, Status +from malt.utils import ag_logging import braket.experimental.autoqasm as aq -from braket.experimental.autoqasm.autograph import ag_logging -from braket.experimental.autoqasm.autograph.core.ag_ctx import ControlStatusCtx, Status from braket.experimental.autoqasm.errors import UnknownQubitCountError from braket.experimental.autoqasm.instructions import cnot, h, measure, x diff --git a/tox.ini b/tox.ini index 161604ac..9ff7184f 100644 --- a/tox.ini +++ b/tox.ini @@ -112,7 +112,7 @@ skip_install = true deps = black commands = - black ./ --extend-exclude=src/braket/experimental/autoqasm/autograph {posargs} + black ./ {posargs} [testenv:black_check] basepython = python3 @@ -120,7 +120,7 @@ skip_install = true deps = black commands = - black --check ./ --extend-exclude=src/braket/experimental/autoqasm/autograph {posargs} + black --check ./ {posargs} [testenv:docs] basepython = python3 From c0daba33e303c90f066f82d2c8d0c1fe82882289 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Tue, 20 Feb 2024 18:03:50 -0500 Subject: [PATCH 1044/1165] feature: Add support for returning tuples of values from aq.main (#882) * Add support for returning tuples of values --- .../autoqasm/operators/assignments.py | 38 +++++++--- .../autoqasm/operators/return_statements.py | 10 ++- .../autoqasm/types/conversions.py | 6 ++ .../experimental/autoqasm/test_return.py | 75 +++++++++++++++++-- 4 files changed, 113 insertions(+), 16 deletions(-) diff --git a/src/braket/experimental/autoqasm/operators/assignments.py b/src/braket/experimental/autoqasm/operators/assignments.py index 2dac8e18..0419399c 100644 --- a/src/braket/experimental/autoqasm/operators/assignments.py +++ b/src/braket/experimental/autoqasm/operators/assignments.py @@ -15,6 +15,7 @@ """Operators for assignment statements.""" import copy +from collections.abc import Iterable from typing import Any import oqpy @@ -40,19 +41,13 @@ def assign_for_output(target_name: str, value: Any) -> Any: Any: Assignment value with updated name attribute if the value is an `oqpy` type. Otherwise, it returns unchanged assignment value. """ - aq_context = program.get_program_conversion_context() - - is_value_name_used = isinstance(value, oqpy.base.Var) and aq_context.is_var_name_used( - value.name - ) - + if value is None: + return None value = types.wrap_value(value) + aq_context = program.get_program_conversion_context() oqpy_program = aq_context.get_oqpy_program() - if not isinstance(value, (oqpy.base.OQPyExpression, oqpy.base.Var)): - return value - if isinstance(value, oqpy.base.OQPyExpression) and not isinstance( value, oqpy.base.Var ): # Classical types subclass from both Var and OQPyExpression, and we @@ -62,6 +57,28 @@ def assign_for_output(target_name: str, value: Any) -> Any: oqpy_program.set(target, value) return target + if isinstance(value, Iterable): + retvals = [] + for i, item in enumerate(value): + retvals.append(_add_assignment(f"{target_name}{i}", item)) + return retvals + else: + return _add_assignment(target_name, value) + + +def _add_assignment(target_name: str, value: Any) -> Any: + """Adds a statement to the underlying oqpy program that assigns `target_name` + to the `value`. + + Args: + target_name (str): The name of assignment target. + value (Any): The value of assignment. + + Returns: + Any: Value of the assignment. + """ + aq_context = program.get_program_conversion_context() + oqpy_program = aq_context.get_oqpy_program() target = copy.copy(value) target.init_expression = None target.name = target_name @@ -70,6 +87,9 @@ def assign_for_output(target_name: str, value: Any) -> Any: # Avoid statements like `a = a;` return value + is_value_name_used = isinstance(value, oqpy.base.Var) and aq_context.is_var_name_used( + value.name + ) if is_value_name_used or value.init_expression is None: oqpy_program.set(target, value) else: diff --git a/src/braket/experimental/autoqasm/operators/return_statements.py b/src/braket/experimental/autoqasm/operators/return_statements.py index 7e59512a..7e33f091 100644 --- a/src/braket/experimental/autoqasm/operators/return_statements.py +++ b/src/braket/experimental/autoqasm/operators/return_statements.py @@ -14,6 +14,7 @@ """Operations to override return statement behavior.""" +from collections.abc import Iterable from typing import Any from braket.experimental.autoqasm import program @@ -31,5 +32,10 @@ def return_output_from_main(name: str, value: Any) -> Any: Any: Returns the same value that is being returned in the statement. """ aq_context = program.get_program_conversion_context() - aq_context.register_output_parameter(name, value) - return aq_types.wrap_value(value) + if isinstance(value, Iterable): + for i, item in enumerate(value): + aq_context.register_output_parameter(f"{name}{i}", item) + return aq_types.wrap_value(tuple(value)) + else: + aq_context.register_output_parameter(name, value) + return aq_types.wrap_value(value) diff --git a/src/braket/experimental/autoqasm/types/conversions.py b/src/braket/experimental/autoqasm/types/conversions.py index 8d8d71e0..2c997e0f 100644 --- a/src/braket/experimental/autoqasm/types/conversions.py +++ b/src/braket/experimental/autoqasm/types/conversions.py @@ -112,6 +112,12 @@ def wrap_value(node: Any) -> Any: raise NotImplementedError(node) +@wrap_value.register(tuple) +def _(node: tuple): + wrapped_nodes = tuple(wrap_value(item) for item in node) + return wrapped_nodes + + @wrap_value.register(bool) def _(node: bool): return aq_types.BoolVar(node) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_return.py b/test/unit_tests/braket/experimental/autoqasm/test_return.py index f5465640..0ca5bd95 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_return.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_return.py @@ -139,17 +139,82 @@ def main(): assert main.to_ir() == expected -@pytest.mark.xfail(reason="Not implemented yet") def test_return_tuple(): @aq.main def main(): return 1, 2 expected = """OPENQASM 3.0; -output int[32] retval1_; -output int[32] retval2_; -retval1_ = 1; -retval2_ = 2;""" +output int[32] retval_0; +output int[32] retval_1; +retval_0 = 1; +retval_1 = 2;""" + + assert main.to_ir() == expected + + +def test_return_list_floats(): + @aq.main + def main(): + return [11.1, 2.222] + + expected = """OPENQASM 3.0; +output float[64] retval_0; +output float[64] retval_1; +retval_0 = 11.1; +retval_1 = 2.222;""" + + assert main.to_ir() == expected + + +def test_return_multi_meas(): + @aq.main + def main(): + a = measure(0) + b = measure(1) + return a, b, measure(2) + + expected = """OPENQASM 3.0; +bit a; +bit b; +output bit retval_0; +output bit retval_1; +output bit retval_2; +qubit[3] __qubits__; +bit __bit_0__; +__bit_0__ = measure __qubits__[0]; +a = __bit_0__; +bit __bit_1__; +__bit_1__ = measure __qubits__[1]; +b = __bit_1__; +bit __bit_2__; +__bit_2__ = measure __qubits__[2]; +retval_0 = a; +retval_1 = b; +retval_2 = __bit_2__;""" + + assert main.to_ir() == expected + + +def test_return_multi_types(): + @aq.main + def main(): + a = measure(0) + b = 1.11 + return a, True, b + + expected = """OPENQASM 3.0; +bit a; +output bit retval_0; +output bool retval_1; +output float[64] retval_2; +qubit[1] __qubits__; +bit __bit_0__; +__bit_0__ = measure __qubits__[0]; +a = __bit_0__; +retval_0 = a; +retval_1 = true; +retval_2 = 1.11;""" assert main.to_ir() == expected 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 1045/1165] 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 656353c6..2962c204 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 1046/1165] 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 e0736f80..98b87f07 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 1047/1165] 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 6dd9c349..57e0980b 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 fc41f12e..426481b7 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 1048/1165] 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 426481b7..359a8645 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 ff56a1fd7d96354297360d3dd54e788135796ce1 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Wed, 21 Feb 2024 17:04:54 -0500 Subject: [PATCH 1049/1165] Remove xfail test that has new bug report (#888) --- .../experimental/autoqasm/test_return.py | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_return.py b/test/unit_tests/braket/experimental/autoqasm/test_return.py index 0ca5bd95..776631c7 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_return.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_return.py @@ -117,28 +117,6 @@ def main(input_a: int): assert main.to_ir() == expected -@pytest.mark.xfail(reason="Not implemented yet") -def test_expressions_and_control_flow(): - @aq.main(num_qubits=3) - def main(): - val = 0.5 - for i in aq.range(3): - val = val + measure(i) - return val - - expected = """OPENQASM 3.0; -output float[64] val; -qubit[3] __qubits__; -val = 0.5; -for int i in [0:3 - 1] { - bit __bit_0__; - __bit_0__ = measure __qubits__[i]; - val = val + __bit_0__; -}""" - - assert main.to_ir() == expected - - def test_return_tuple(): @aq.main def main(): From d723235b665e83be5461468eb610551aa10d087f Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Fri, 23 Feb 2024 13:55:38 -0500 Subject: [PATCH 1050/1165] documentation: Update example notebooks to use AutoQASM return statements (#892) * documentation: Update example notebooks to use AutoQASM return statement. Update return value default name to return_value from retval_ --- .../1_Getting_started_with_AutoQASM.ipynb | 18 +- .../2_Expressing_classical_control_flow.ipynb | 14 +- .../3_1_Iterative_phase_estimation.ipynb | 12 +- .../3_2_magic_state_distillation.ipynb | 12 +- examples/autoqasm/4_Native_programming.ipynb | 220 ++++++++++++++++-- src/braket/experimental/autoqasm/constants.py | 3 + .../autoqasm/converters/return_statements.py | 4 +- .../braket/experimental/autoqasm/test_api.py | 12 +- .../experimental/autoqasm/test_program.py | 4 +- .../experimental/autoqasm/test_return.py | 72 +++--- .../experimental/autoqasm/test_types.py | 36 +-- 11 files changed, 305 insertions(+), 102 deletions(-) diff --git a/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb b/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb index 4b4159d4..d42b0869 100644 --- a/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb +++ b/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb @@ -46,7 +46,7 @@ "def bell_state():\n", " h(0)\n", " cnot(0, 1)\n", - " c = measure([0, 1])" + " return measure([0, 1])\n" ] }, { @@ -68,14 +68,14 @@ "output_type": "stream", "text": [ "OPENQASM 3.0;\n", - "bit[2] c;\n", + "output bit[2] return_value;\n", "qubit[2] __qubits__;\n", "h __qubits__[0];\n", "cnot __qubits__[0], __qubits__[1];\n", "bit[2] __bit_0__ = \"00\";\n", "__bit_0__[0] = measure __qubits__[0];\n", "__bit_0__[1] = measure __qubits__[1];\n", - "c = __bit_0__;\n" + "return_value = __bit_0__;\n" ] } ], @@ -101,14 +101,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "measurement counts: Counter({'11': 51, '00': 49})\n" + "measurement counts: Counter({'11': 50, '00': 50})\n" ] } ], "source": [ "device = LocalSimulator()\n", "result = device.run(bell_state, shots=100).result()\n", - "counts = Counter(result.measurements[\"c\"])\n", + "counts = Counter(result.measurements[\"return_value\"])\n", "print(\"measurement counts: \", counts)" ] }, @@ -120,8 +120,10 @@ "outputs": [ { "data": { - "text/plain": "
", - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjMAAAGwCAYAAABcnuQpAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAg50lEQVR4nO3df1BVdf7H8dfJH1dQwPx1LyQqbWg/UNs0TfoW5AaNue667PRLp7QyNbON2FYlKq+tgrqT0sZm6Zax21Lt7Ga1024LWaATuaLJalZmhcqO3EgjQEFIPd8/Gu94QxSv4Lkfez5mzoz3c869vHOGfM7nHriWbdu2AAAADHWe0wMAAACcCWIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEbr7PQAHe3o0aPau3evIiIiZFmW0+MAAIA2sG1b9fX1iomJ0XnnnXzv5ZyPmb179yo2NtbpMQAAQBAqKyvVv3//k15zzsdMRESEpO/+MiIjIx2eBgAAtEVdXZ1iY2P9/46fzDkfM8feWoqMjCRmAAAwTFtuEeEGYAAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARuvs9AAAEOoGzXvT6RGAkLVr8XinR2BnBgAAmI2YAQAARnM0ZrxeryzLCjg8Ho//vG3b8nq9iomJUVhYmJKTk7V9+3YHJwYAAKHG8Z2Zyy67TFVVVf5j27Zt/nNLly7VsmXLlJeXp7KyMnk8HqWkpKi+vt7BiQEAQChxPGY6d+4sj8fjP/r27Svpu12Z3NxcZWVlKS0tTQkJCcrPz1dDQ4MKCgocnhoAAIQKx2Nm586diomJUVxcnG699VZ98cUXkqSKigr5fD6lpqb6r3W5XEpKSlJpaWmrr9fU1KS6urqAAwAAnLscjZnRo0frT3/6k/79739r1apV8vl8SkxM1P79++Xz+SRJbrc74Dlut9t/7kRycnIUFRXlP2JjYzv0vwEAADjL0ZgZN26cfvnLX2ro0KG6/vrr9eab3/0uh/z8fP81lmUFPMe27RZrx8vMzFRtba3/qKys7JjhAQBASHD8babjde/eXUOHDtXOnTv9P9X0/V2Y6urqFrs1x3O5XIqMjAw4AADAuSukYqapqUkff/yxoqOjFRcXJ4/Ho6KiIv/55uZmlZSUKDEx0cEpAQBAKHH04wweeughTZgwQQMGDFB1dbUWLlyouro6TZkyRZZlKT09XdnZ2YqPj1d8fLyys7MVHh6uSZMmOTk2AAAIIY7GzP/+9z/ddttt2rdvn/r27aurrrpKGzZs0MCBAyVJc+bMUWNjo2bNmqWamhqNHj1ahYWFioiIcHJsAAAQQizbtm2nh+hIdXV1ioqKUm1tLffPAAgKHzQJtK6jPmjydP795lOzzxD/kwNaFwqfpgvg3BdSNwADAACcLmIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYLSQiZmcnBxZlqX09HT/mm3b8nq9iomJUVhYmJKTk7V9+3bnhgQAACEnJGKmrKxMK1eu1LBhwwLWly5dqmXLlikvL09lZWXyeDxKSUlRfX29Q5MCAIBQ43jMHDhwQJMnT9aqVat0/vnn+9dt21Zubq6ysrKUlpamhIQE5efnq6GhQQUFBQ5ODAAAQonjMXPfffdp/Pjxuv766wPWKyoq5PP5lJqa6l9zuVxKSkpSaWlpq6/X1NSkurq6gAMAAJy7Ojv5xV9++WV98MEHKisra3HO5/NJktxud8C62+3W7t27W33NnJwcLViwoH0HBQAAIcuxnZnKyko98MADevHFF9WtW7dWr7MsK+Cxbdst1o6XmZmp2tpa/1FZWdluMwMAgNDj2M7M5s2bVV1drREjRvjXjhw5onXr1ikvL087duyQ9N0OTXR0tP+a6urqFrs1x3O5XHK5XB03OAAACCmO7cz85Cc/0bZt21ReXu4/Ro4cqcmTJ6u8vFwXXnihPB6PioqK/M9pbm5WSUmJEhMTnRobAACEGMd2ZiIiIpSQkBCw1r17d/Xu3du/np6eruzsbMXHxys+Pl7Z2dkKDw/XpEmTnBgZAACEIEdvAD6VOXPmqLGxUbNmzVJNTY1Gjx6twsJCRUREOD0aAAAIESEVM8XFxQGPLcuS1+uV1+t1ZB4AABD6HP89MwAAAGeCmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGczRmVqxYoWHDhikyMlKRkZEaM2aM/vWvf/nP27Ytr9ermJgYhYWFKTk5Wdu3b3dwYgAAEGocjZn+/ftr8eLF2rRpkzZt2qSxY8fq5z//uT9Yli5dqmXLlikvL09lZWXyeDxKSUlRfX29k2MDAIAQ4mjMTJgwQTfeeKMGDx6swYMHa9GiRerRo4c2bNgg27aVm5urrKwspaWlKSEhQfn5+WpoaFBBQYGTYwMAgBASMvfMHDlyRC+//LIOHjyoMWPGqKKiQj6fT6mpqf5rXC6XkpKSVFpa2urrNDU1qa6uLuAAAADnLsdjZtu2berRo4dcLpdmzpypNWvW6NJLL5XP55Mkud3ugOvdbrf/3Ink5OQoKirKf8TGxnbo/AAAwFmOx8yQIUNUXl6uDRs26N5779WUKVP00Ucf+c9blhVwvW3bLdaOl5mZqdraWv9RWVnZYbMDAADndQ7mSZWVlbIsS/3795ckbdy4UQUFBbr00ks1ffr003qtrl276qKLLpIkjRw5UmVlZXryySc1d+5cSZLP51N0dLT/+urq6ha7NcdzuVxyuVyn+58EAAAMFdTOzKRJk/Tuu+9K+i42UlJStHHjRj388MN6/PHHz2gg27bV1NSkuLg4eTweFRUV+c81NzerpKREiYmJZ/Q1AADAuSOomPnwww81atQoSdJf//pXJSQkqLS0VAUFBXrhhRfa/DoPP/yw1q9fr127dmnbtm3KyspScXGxJk+eLMuylJ6eruzsbK1Zs0Yffvihpk6dqvDwcE2aNCmYsQEAwDkoqLeZvv32W/9bOW+//bZ+9rOfSZIuvvhiVVVVtfl1vvzyS91+++2qqqpSVFSUhg0bprfeekspKSmSpDlz5qixsVGzZs1STU2NRo8ercLCQkVERAQzNgAAOAcFFTOXXXaZnnnmGY0fP15FRUX67W9/K0nau3evevfu3ebXee6550563rIseb1eeb3eYMYEAAA/AEG9zbRkyRI9++yzSk5O1m233abhw4dLkt544w3/208AAABnQ1A7M8nJydq3b5/q6up0/vnn+9enT5+u7t27t9twAAAApxLUzszYsWNVX18fEDKS1KtXL91yyy3tMhgAAEBbBBUzxcXFam5ubrF+6NAhrV+//oyHAgAAaKvTeptp69at/j9/9NFHAR8rcOTIEb311lu64IIL2m86AACAUzitmLn88stlWZYsy9LYsWNbnA8LC9NTTz3VbsMBAACcymnFTEVFhWzb1oUXXqiNGzeqb9++/nNdu3ZVv3791KlTp3YfEgAAoDWnFTMDBw6UJB09erRDhgEAADhdQf1otiR9+umnKi4uVnV1dYu4eeyxx854MAAAgLYIKmZWrVqle++9V3369JHH45FlWf5zlmURMwAA4KwJKmYWLlyoRYsWae7cue09DwAAwGkJ6vfM1NTU6KabbmrvWQAAAE5bUDFz0003qbCwsL1nAQAAOG1Bvc100UUX6dFHH9WGDRs0dOhQdenSJeD8r371q3YZDgAA4FSCipmVK1eqR48eKikpUUlJScA5y7KIGQAAcNYEFTMVFRXtPQcAAEBQgrpnBgAAIFQEtTNz1113nfT8888/H9QwAAAApyuomKmpqQl4/O233+rDDz/UN998c8IPoAQAAOgoQcXMmjVrWqwdPXpUs2bN0oUXXnjGQwEAALRVu90zc9555+nBBx/U8uXL2+slAQAATqldbwD+/PPPdfjw4fZ8SQAAgJMK6m2mjIyMgMe2bauqqkpvvvmmpkyZ0i6DAQAAtEVQMbNly5aAx+edd5769u2rJ5544pQ/6QQAANCegoqZd999t73nAAAACEpQMXPMV199pR07dsiyLA0ePFh9+/Ztr7kAAADaJKgbgA8ePKi77rpL0dHRuvbaa3XNNdcoJiZGd999txoaGtp7RgAAgFYFFTMZGRkqKSnRP/7xD33zzTf65ptv9Prrr6ukpES//vWv23tGAACAVgX1NtPf//53/e1vf1NycrJ/7cYbb1RYWJhuvvlmrVixor3mAwAAOKmgdmYaGhrkdrtbrPfr14+3mQAAwFkVVMyMGTNG8+fP16FDh/xrjY2NWrBggcaMGdNuwwEAAJxKUG8z5ebmaty4cerfv7+GDx8uy7JUXl4ul8ulwsLC9p4RAACgVUHFzNChQ7Vz5069+OKL+uSTT2Tbtm699VZNnjxZYWFh7T0jAABAq4KKmZycHLndbt1zzz0B688//7y++uorzZ07t12GAwAAOJWg7pl59tlndfHFF7dYv+yyy/TMM8+c8VAAAABtFVTM+Hw+RUdHt1jv27evqqqqzngoAACAtgoqZmJjY/Xee++1WH/vvfcUExNzxkMBAAC0VVD3zEybNk3p6en69ttvNXbsWEnS2rVrNWfOHH4DMAAAOKuCipk5c+bo66+/1qxZs9Tc3CxJ6tatm+bOnavMzMx2HRAAAOBkgooZy7K0ZMkSPfroo/r4448VFham+Ph4uVyu9p4PAADgpIKKmWN69OihK6+8sr1mAQAAOG1B3QAMAAAQKogZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRHI2ZnJwcXXnllYqIiFC/fv00ceJE7dixI+Aa27bl9XoVExOjsLAwJScna/v27Q5NDAAAQo2jMVNSUqL77rtPGzZsUFFRkQ4fPqzU1FQdPHjQf83SpUu1bNky5eXlqaysTB6PRykpKaqvr3dwcgAAECo6O/nF33rrrYDHq1evVr9+/bR582Zde+21sm1bubm5ysrKUlpamiQpPz9fbrdbBQUFmjFjhhNjAwCAEBJS98zU1tZKknr16iVJqqiokM/nU2pqqv8al8ulpKQklZaWnvA1mpqaVFdXF3AAAIBzV8jEjG3bysjI0P/93/8pISFBkuTz+SRJbrc74Fq32+0/9305OTmKioryH7GxsR07OAAAcFTIxMzs2bO1detWvfTSSy3OWZYV8Ni27RZrx2RmZqq2ttZ/VFZWdsi8AAAgNDh6z8wx999/v9544w2tW7dO/fv39697PB5J3+3QREdH+9erq6tb7NYc43K55HK5OnZgAAAQMhzdmbFtW7Nnz9arr76qd955R3FxcQHn4+Li5PF4VFRU5F9rbm5WSUmJEhMTz/a4AAAgBDm6M3PfffepoKBAr7/+uiIiIvz3wURFRSksLEyWZSk9PV3Z2dmKj49XfHy8srOzFR4erkmTJjk5OgAACBGOxsyKFSskScnJyQHrq1ev1tSpUyVJc+bMUWNjo2bNmqWamhqNHj1ahYWFioiIOMvTAgCAUORozNi2fcprLMuS1+uV1+vt+IEAAIBxQuanmQAAAIJBzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjORoz69at04QJExQTEyPLsvTaa68FnLdtW16vVzExMQoLC1NycrK2b9/uzLAAACAkORozBw8e1PDhw5WXl3fC80uXLtWyZcuUl5ensrIyeTwepaSkqL6+/ixPCgAAQlVnJ7/4uHHjNG7cuBOes21bubm5ysrKUlpamiQpPz9fbrdbBQUFmjFjxtkcFQAAhKiQvWemoqJCPp9Pqamp/jWXy6WkpCSVlpa2+rympibV1dUFHAAA4NwVsjHj8/kkSW63O2Dd7Xb7z51ITk6OoqKi/EdsbGyHzgkAAJwVsjFzjGVZAY9t226xdrzMzEzV1tb6j8rKyo4eEQAAOMjRe2ZOxuPxSPpuhyY6Otq/Xl1d3WK35ngul0sul6vD5wMAAKEhZHdm4uLi5PF4VFRU5F9rbm5WSUmJEhMTHZwMAACEEkd3Zg4cOKDPPvvM/7iiokLl5eXq1auXBgwYoPT0dGVnZys+Pl7x8fHKzs5WeHi4Jk2a5ODUAAAglDgaM5s2bdJ1113nf5yRkSFJmjJlil544QXNmTNHjY2NmjVrlmpqajR69GgVFhYqIiLCqZEBAECIcTRmkpOTZdt2q+cty5LX65XX6z17QwEAAKOE7D0zAAAAbUHMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMZETNPP/204uLi1K1bN40YMULr1693eiQAABAiQj5mXnnlFaWnpysrK0tbtmzRNddco3HjxmnPnj1OjwYAAEJAyMfMsmXLdPfdd2vatGm65JJLlJubq9jYWK1YscLp0QAAQAjo7PQAJ9Pc3KzNmzdr3rx5AeupqakqLS094XOamprU1NTkf1xbWytJqqur65AZjzY1dMjrAueCjvq+O9v4Pgda11Hf58de17btU14b0jGzb98+HTlyRG63O2Dd7XbL5/Od8Dk5OTlasGBBi/XY2NgOmRFA66JynZ4AQEfr6O/z+vp6RUVFnfSakI6ZYyzLCnhs23aLtWMyMzOVkZHhf3z06FF9/fXX6t27d6vPwbmhrq5OsbGxqqysVGRkpNPjAOgAfJ//cNi2rfr6esXExJzy2pCOmT59+qhTp04tdmGqq6tb7NYc43K55HK5AtZ69uzZUSMiBEVGRvI/OeAcx/f5D8OpdmSOCekbgLt27aoRI0aoqKgoYL2oqEiJiYkOTQUAAEJJSO/MSFJGRoZuv/12jRw5UmPGjNHKlSu1Z88ezZw50+nRAABACAj5mLnlllu0f/9+Pf7446qqqlJCQoL++c9/auDAgU6PhhDjcrk0f/78Fm8zAjh38H2OE7HstvzMEwAAQIgK6XtmAAAAToWYAQAARiNmAACA0YgZAABgNGIGRnr66acVFxenbt26acSIEVq/fr3/nG3b8nq9iomJUVhYmJKTk7V9+3YHpwVwutatW6cJEyYoJiZGlmXptddeCzj/6quv6oYbblCfPn1kWZbKy8sdmROhgZiBcV555RWlp6crKytLW7Zs0TXXXKNx48Zpz549kqSlS5dq2bJlysvLU1lZmTwej1JSUlRfX+/w5ADa6uDBgxo+fLjy8vJaPX/11Vdr8eLFZ3kyhCJ+NBvGGT16tK644gqtWLHCv3bJJZdo4sSJys7OVkxMjNLT0zV37lxJ332Sutvt1pIlSzRjxgynxgYQJMuytGbNGk2cOLHFuV27dikuLk5btmzR5ZdfftZnQ2hgZwZGaW5u1ubNm5WamhqwnpqaqtLSUlVUVMjn8wWcd7lcSkpKUmlp6dkeFwBwFhAzMMq+fft05MiRFh806na75fP5/B9K2tp5AMC5h5iBkSzLCnhs23bA2qnOAwDOHcQMjNKnTx916tSpxS5LdXW13G63PB6PJLV6HgBw7iFmYJSuXbtqxIgRKioqClgvKipSYmKi4uLi5PF4As43NzerpKREiYmJZ3tcAMBZEPKfmg18X0ZGhm6//XaNHDlSY8aM0cqVK7Vnzx7NnDlTlmUpPT1d2dnZio+PV3x8vLKzsxUeHq5JkyY5PTqANjpw4IA+++wz/+OKigqVl5erV69eGjBggL7++mvt2bNHe/fulSTt2LFDkuTxePw7tPgBsQED/eEPf7AHDhxod+3a1b7iiivskpIS/7mjR4/a8+fPtz0ej+1yuexrr73W3rZtm4PTAjhd7777ri2pxTFlyhTbtm179erVJzw/f/58R+eGM/g9MwAAwGjcMwMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjED4IwlJycrPT291fODBg1Sbm7uWZll165dsixL5eXlZ+XrAXAeMQOgw5WVlWn69On+x5Zl6bXXXmvz81944QX17NmzTdfGxsaqqqpKCQkJpzklAFPxQZMAOlzfvn3Pytdpbm5W165d+aBB4AeGnRkA7eLw4cOaPXu2evbsqd69e+uRRx7RsY9+O/5tpkGDBkmSfvGLX8iyLP/j//73v7ruuusUERGhyMhIjRgxQps2bVJxcbHuvPNO1dbWyrIsWZYlr9frf62FCxdq6tSpioqK0j333NPibabi4mJZlqW1a9dq5MiRCg8PV2Jiov9Tlo9ZuHCh+vXrp4iICE2bNk3z5s3T5Zdf7j9fXFysUaNGqXv37urZs6euvvpq7d69u6P+OgGcBmIGQLvIz89X586d9Z///Ee///3vtXz5cv3xj39scV1ZWZkkafXq1aqqqvI/njx5svr376+ysjJt3rxZ8+bNU5cuXZSYmKjc3FxFRkaqqqpKVVVVeuihh/yv97vf/U4JCQnavHmzHn300Vbny8rK0hNPPKFNmzapc+fOuuuuu/zn/vKXv2jRokVasmSJNm/erAEDBmjFihX+84cPH9bEiROVlJSkrVu36v3339f06dNlWdYZ/70BOHO8zQSgXcTGxmr58uWyLEtDhgzRtm3btHz5ct1zzz0B1x17y6lnz54Bbwft2bNHv/nNb3TxxRdLkuLj4/3noqKiZFnWCd8+Gjt2bEDc7Nq164TzLVq0SElJSZKkefPmafz48Tp06JC6deump556SnfffbfuvPNOSdJjjz2mwsJCHThwQJJUV1en2tpa/fSnP9WPfvQjSdIll1xyWn8/ADoOOzMA2sVVV10VsFMxZswY7dy5U0eOHGnT8zMyMjRt2jRdf/31Wrx4sT7//PM2PW/kyJFtum7YsGH+P0dHR0uSqqurJUk7duzQqFGjAq4//nGvXr00depU3XDDDZowYYKefPJJVVVVtenrAuh4xAyAkOD1erV9+3aNHz9e77zzji699FKtWbPmlM/r3r17m16/S5cu/j8fi66jR4+2WDvm2P0+x6xevVrvv/++EhMT9corr2jw4MHasGFDm742gI5FzABoF9//h33Dhg2Kj49Xp06dWlzbpUuXE+7YDB48WA8++KAKCwuVlpam1atXS5K6du3a5h2eYAwZMkQbN24MWNu0aVOL63784x8rMzNTpaWlSkhIUEFBQYfNBKDtiBkA7aKyslIZGRnasWOHXnrpJT311FN64IEHTnjtoEGDtHbtWvl8PtXU1KixsVGzZ89WcXGxdu/erffee09lZWX++1IGDRqkAwcOaO3atdq3b58aGhradfb7779fzz33nPLz87Vz504tXLhQW7du9e/WVFRUKDMzU++//752796twsJCffrpp9w3A4QIbgAG0C7uuOMONTY2atSoUerUqZPuv//+gF+Ud7wnnnhCGRkZWrVqlS644AJ9+umn2r9/v+644w59+eWX6tOnj9LS0rRgwQJJUmJiombOnKlbbrlF+/fv1/z58/0/nt0eJk+erC+++EIPPfSQDh06pJtvvllTp07179aEh4frk08+UX5+vvbv36/o6GjNnj1bM2bMaLcZAATPsr//xjAAQCkpKfJ4PPrzn//s9CgAToGdGQA/eA0NDXrmmWd0ww03qFOnTnrppZf09ttvq6ioyOnRALQBOzMAfvAaGxs1YcIEffDBB2pqatKQIUP0yCOPKC0tzenRALQBMQMAAIzGTzMBAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjPb/6ba9dQ2md+AAAAAASUVORK5CYII=" + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjIAAAGwCAYAAACzXI8XAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/SrBM8AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAgr0lEQVR4nO3dfXBU5d2H8e+GJBsg7MaEkBBJBBUJiEAbBBatYhqN1FIxqVLKDIgIFQMV1hfMKCAWJtKpgtIA1mKwHSNKW7C0UwQjRKsJLxFUtKRo0aSTbBA0CQTyItnnD4ed7hMCYdlw9sbrM7MznHPvnvxkJnp5ztldm9fr9QoAAMBAYVYPAAAAEChCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGCrd6gM7W2tqqqqoq9ejRQzabzepxAABAB3i9Xh09elRJSUkKC2v/vMtFHzJVVVVKTk62egwAABCAyspK9enTp931iz5kevToIenbvwiHw2HxNAAAoCPq6+uVnJzs++94ey76kDl1OcnhcBAyAAAY5my3hXCzLwAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGNZGjJPPPGEbDab3yM1NdW33tjYqJycHMXFxSk6OlrZ2dmqqamxcGIAABBKLD8jc/XVV6u6utr3+Oc//+lbmzt3rjZt2qT169eruLhYVVVVysrKsnBaAAAQSiz/9uvw8HAlJia22V9XV6c1a9aosLBQ6enpkqSCggINHDhQpaWlGjVq1IUeFQAAhBjLz8gcOHBASUlJuvzyyzVp0iRVVFRIksrKytTS0qKMjAzfc1NTU5WSkqKSkpJ2j9fU1KT6+nq/BwAAuDhZekZm5MiRWrt2rQYMGKDq6motWrRIP/jBD7Rv3z55PB5FRkYqJibG7zUJCQnyeDztHjMvL0+LFi3q5Mm/1ffRv1+QnwOY6vOnbrN6hKDgdx1on9W/55aGzNixY31/HjJkiEaOHKnLLrtMr732mrp27RrQMXNzc+V2u33b9fX1Sk5OPu9ZAQBA6LH80tL/iomJ0VVXXaVPP/1UiYmJam5uVm1trd9zampqTntPzSl2u10Oh8PvAQAALk4hFTLHjh3TZ599pt69eystLU0REREqKiryrZeXl6uiokIul8vCKQEAQKiw9NLSQw89pHHjxumyyy5TVVWVFi5cqC5dumjixIlyOp2aNm2a3G63YmNj5XA4NHv2bLlcLt6xBAAAJFkcMv/97381ceJEHTlyRPHx8br++utVWlqq+Ph4SdKyZcsUFham7OxsNTU1KTMzUytXrrRyZAAAEEIsDZl169adcT0qKkr5+fnKz8+/QBMBAACThNQ9MgAAAOeCkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGCskAmZp556SjabTXPmzPHta2xsVE5OjuLi4hQdHa3s7GzV1NRYNyQAAAgpIREyu3bt0vPPP68hQ4b47Z87d642bdqk9evXq7i4WFVVVcrKyrJoSgAAEGosD5ljx45p0qRJeuGFF3TJJZf49tfV1WnNmjV65plnlJ6errS0NBUUFOi9995TaWlpu8drampSfX293wMAAFycLA+ZnJwc3XbbbcrIyPDbX1ZWppaWFr/9qampSklJUUlJSbvHy8vLk9Pp9D2Sk5M7bXYAAGAtS0Nm3bp1ev/995WXl9dmzePxKDIyUjExMX77ExIS5PF42j1mbm6u6urqfI/Kyspgjw0AAEJEuFU/uLKyUg888IC2bt2qqKiooB3XbrfLbrcH7XgAACB0WXZGpqysTIcOHdL3v/99hYeHKzw8XMXFxXruuecUHh6uhIQENTc3q7a21u91NTU1SkxMtGZoAAAQUiw7I/PDH/5QH330kd++qVOnKjU1VfPmzVNycrIiIiJUVFSk7OxsSVJ5ebkqKirkcrmsGBkAAIQYy0KmR48eGjx4sN++7t27Ky4uzrd/2rRpcrvdio2NlcPh0OzZs+VyuTRq1CgrRgYAACHGspDpiGXLliksLEzZ2dlqampSZmamVq5cafVYAAAgRIRUyGzfvt1vOyoqSvn5+crPz7dmIAAAENIs/xwZAACAQBEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMJalIbNq1SoNGTJEDodDDodDLpdL//jHP3zrjY2NysnJUVxcnKKjo5Wdna2amhoLJwYAAKHE0pDp06ePnnrqKZWVlWn37t1KT0/X7bffro8//liSNHfuXG3atEnr169XcXGxqqqqlJWVZeXIAAAghIRb+cPHjRvnt71kyRKtWrVKpaWl6tOnj9asWaPCwkKlp6dLkgoKCjRw4ECVlpZq1KhRVowMAABCSMjcI3Py5EmtW7dODQ0NcrlcKisrU0tLizIyMnzPSU1NVUpKikpKSto9TlNTk+rr6/0eAADg4mR5yHz00UeKjo6W3W7Xfffdpw0bNmjQoEHyeDyKjIxUTEyM3/MTEhLk8XjaPV5eXp6cTqfvkZyc3Mn/BAAAwCqWh8yAAQO0d+9e7dixQzNnztSUKVP0ySefBHy83Nxc1dXV+R6VlZVBnBYAAIQSS++RkaTIyEhdeeWVkqS0tDTt2rVLzz77rCZMmKDm5mbV1tb6nZWpqalRYmJiu8ez2+2y2+2dPTYAAAgBlp+R+f9aW1vV1NSktLQ0RUREqKioyLdWXl6uiooKuVwuCycEAAChwtIzMrm5uRo7dqxSUlJ09OhRFRYWavv27XrjjTfkdDo1bdo0ud1uxcbGyuFwaPbs2XK5XLxjCQAASLI4ZA4dOqTJkyerurpaTqdTQ4YM0RtvvKGbb75ZkrRs2TKFhYUpOztbTU1NyszM1MqVK60cGQAAhJCAQqayslI2m019+vSRJO3cuVOFhYUaNGiQZsyY0eHjrFmz5ozrUVFRys/PV35+fiBjAgCAi1xA98j8/Oc/17Zt2yRJHo9HN998s3bu3KnHHntMTz75ZFAHBAAAaE9AIbNv3z6NGDFCkvTaa69p8ODBeu+99/Tyyy9r7dq1wZwPAACgXQGFTEtLi+8tzm+++aZ+8pOfSPr2k3erq6uDNx0AAMAZBBQyV199tVavXq133nlHW7du1a233ipJqqqqUlxcXFAHBAAAaE9AIbN06VI9//zzGjNmjCZOnKihQ4dKkv7617/6LjkBAAB0toDetTRmzBgdPnxY9fX1uuSSS3z7Z8yYoe7duwdtOAAAgDMJ6IxMenq6jh496hcxkhQbG6sJEyYEZTAAAICzCShktm/frubm5jb7Gxsb9c4775z3UAAAAB1xTpeWPvzwQ9+fP/nkE3k8Ht/2yZMntXnzZl166aXBmw4AAOAMzilkhg0bJpvNJpvNpvT09DbrXbt21YoVK4I2HAAAwJmcU8gcPHhQXq9Xl19+uXbu3Kn4+HjfWmRkpHr16qUuXboEfUgAAIDTOaeQueyyyyRJra2tnTIMAADAuQj4268PHDigbdu26dChQ23CZsGCBec9GAAAwNkEFDIvvPCCZs6cqZ49eyoxMVE2m823ZrPZCBkAAHBBBBQyixcv1pIlSzRv3rxgzwMAANBhAX2OzNdff60777wz2LMAAACck4BC5s4779SWLVuCPQsAAMA5CejS0pVXXqn58+ertLRU11xzjSIiIvzWf/nLXwZlOAAAgDMJKGR+97vfKTo6WsXFxSouLvZbs9lshAwAALggAgqZgwcPBnsOAACAcxbQPTIAAAChIKAzMvfcc88Z11988cWAhgEAADgXAYXM119/7bfd0tKiffv2qba29rRfJgkAANAZAgqZDRs2tNnX2tqqmTNn6oorrjjvoQAAADoiaPfIhIWFye12a9myZcE6JAAAwBkF9Wbfzz77TN98800wDwkAANCugC4tud1uv22v16vq6mr9/e9/15QpU4IyGAAAwNkEFDJ79uzx2w4LC1N8fLyefvrps76jCQAAIFgCCplt27YFew4AAIBzFlDInPLll1+qvLxckjRgwADFx8cHZSgAAICOCOhm34aGBt1zzz3q3bu3brjhBt1www1KSkrStGnTdPz48WDPCAAAcFoBhYzb7VZxcbE2bdqk2tpa1dbW6vXXX1dxcbEefPDBYM8IAABwWgFdWvrzn/+sP/3pTxozZoxv349+9CN17dpVd911l1atWhWs+QAAANoV0BmZ48ePKyEhoc3+Xr16cWkJAABcMAGFjMvl0sKFC9XY2Ojbd+LECS1atEgulytowwEAAJxJQJeWli9frltvvVV9+vTR0KFDJUkffPCB7Ha7tmzZEtQBAQAA2hNQyFxzzTU6cOCAXn75Ze3fv1+SNHHiRE2aNEldu3YN6oAAAADtCShk8vLylJCQoOnTp/vtf/HFF/Xll19q3rx5QRkOAADgTAK6R+b5559Xampqm/1XX321Vq9efd5DAQAAdERAIePxeNS7d+82++Pj41VdXX3eQwEAAHREQCGTnJysd999t83+d999V0lJSec9FAAAQEcEdI/M9OnTNWfOHLW0tCg9PV2SVFRUpEceeYRP9gUAABdMQCHz8MMP68iRI7r//vvV3NwsSYqKitK8efOUm5sb1AEBAADaE1DI2Gw2LV26VPPnz9e//vUvde3aVf3795fdbg/2fAAAAO0KKGROiY6O1rXXXhusWQAAAM5JQDf7AgAAhAJCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGMvSkMnLy9O1116rHj16qFevXho/frzKy8v9ntPY2KicnBzFxcUpOjpa2dnZqqmpsWhiAAAQSiwNmeLiYuXk5Ki0tFRbt25VS0uLbrnlFjU0NPieM3fuXG3atEnr169XcXGxqqqqlJWVZeHUAAAgVIRb+cM3b97st7127Vr16tVLZWVluuGGG1RXV6c1a9aosLBQ6enpkqSCggINHDhQpaWlGjVqlBVjAwCAEBFS98jU1dVJkmJjYyVJZWVlamlpUUZGhu85qampSklJUUlJyWmP0dTUpPr6er8HAAC4OIVMyLS2tmrOnDm67rrrNHjwYEmSx+NRZGSkYmJi/J6bkJAgj8dz2uPk5eXJ6XT6HsnJyZ09OgAAsEjIhExOTo727dundevWnddxcnNzVVdX53tUVlYGaUIAABBqLL1H5pRZs2bpb3/7m95++2316dPHtz8xMVHNzc2qra31OytTU1OjxMTE0x7LbrfLbrd39sgAACAEWHpGxuv1atasWdqwYYPeeust9evXz289LS1NERERKioq8u0rLy9XRUWFXC7XhR4XAACEGEvPyOTk5KiwsFCvv/66evTo4bvvxel0qmvXrnI6nZo2bZrcbrdiY2PlcDg0e/ZsuVwu3rEEAACsDZlVq1ZJksaMGeO3v6CgQHfffbckadmyZQoLC1N2draampqUmZmplStXXuBJAQBAKLI0ZLxe71mfExUVpfz8fOXn51+AiQAAgElC5l1LAAAA54qQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxLQ+btt9/WuHHjlJSUJJvNpo0bN/qte71eLViwQL1791bXrl2VkZGhAwcOWDMsAAAIOZaGTENDg4YOHar8/PzTrv/617/Wc889p9WrV2vHjh3q3r27MjMz1djYeIEnBQAAoSjcyh8+duxYjR079rRrXq9Xy5cv1+OPP67bb79dkvSHP/xBCQkJ2rhxo372s5+d9nVNTU1qamrybdfX1wd/cAAAEBJC9h6ZgwcPyuPxKCMjw7fP6XRq5MiRKikpafd1eXl5cjqdvkdycvKFGBcAAFggZEPG4/FIkhISEvz2JyQk+NZOJzc3V3V1db5HZWVlp84JAACsY+mlpc5gt9tlt9utHgMAAFwAIXtGJjExUZJUU1Pjt7+mpsa3BgAAvttCNmT69eunxMREFRUV+fbV19drx44dcrlcFk4GAABChaWXlo4dO6ZPP/3Ut33w4EHt3btXsbGxSklJ0Zw5c7R48WL1799f/fr10/z585WUlKTx48dbNzQAAAgZlobM7t27ddNNN/m23W63JGnKlClau3atHnnkETU0NGjGjBmqra3V9ddfr82bNysqKsqqkQEAQAixNGTGjBkjr9fb7rrNZtOTTz6pJ5988gJOBQAATBGy98gAAACcDSEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxkRMvn5+erbt6+ioqI0cuRI7dy50+qRAABACAj5kHn11Vfldru1cOFCvf/++xo6dKgyMzN16NAhq0cDAAAWC/mQeeaZZzR9+nRNnTpVgwYN0urVq9WtWze9+OKLVo8GAAAsFm71AGfS3NyssrIy5ebm+vaFhYUpIyNDJSUlp31NU1OTmpqafNt1dXWSpPr6+qDP19p0POjHBC4mnfF7ZwV+14H2ddbv+anjer3eMz4vpEPm8OHDOnnypBISEvz2JyQkaP/+/ad9TV5enhYtWtRmf3JycqfMCKB9zuVWTwCgs3X27/nRo0fldDrbXQ/pkAlEbm6u3G63b7u1tVVfffWV4uLiZLPZLJwMna2+vl7JycmqrKyUw+GwehwAnYDf8+8Or9ero0ePKikp6YzPC+mQ6dmzp7p06aKamhq//TU1NUpMTDzta+x2u+x2u9++mJiYzhoRIcjhcPAvOOAix+/5d8OZzsScEtI3+0ZGRiotLU1FRUW+fa2trSoqKpLL5bJwMgAAEApC+oyMJLndbk2ZMkXDhw/XiBEjtHz5cjU0NGjq1KlWjwYAACwW8iEzYcIEffnll1qwYIE8Ho+GDRumzZs3t7kBGLDb7Vq4cGGbS4sALh78nuP/s3nP9r4mAACAEBXS98gAAACcCSEDAACMRcgAAABjETIAAMBYhAyM8vbbb2vcuHFKSkqSzWbTxo0b/db/8pe/6JZbbvF9kvPevXstmRPA+cvPz1ffvn0VFRWlkSNHaufOnb61xsZG5eTkKC4uTtHR0crOzm7z4an4biBkYJSGhgYNHTpU+fn57a5ff/31Wrp06QWeDEAwvfrqq3K73Vq4cKHef/99DR06VJmZmTp06JAkae7cudq0aZPWr1+v4uJiVVVVKSsry+KpYQXefg1j2Ww2bdiwQePHj2+z9vnnn6tfv37as2ePhg0bdsFnA3B+Ro4cqWuvvVa//e1vJX37qe7JycmaPXu2Zs6cqfj4eBUWFuqnP/2pJGn//v0aOHCgSkpKNGrUKCtHxwXGGRkAQEhpbm5WWVmZMjIyfPvCwsKUkZGhkpISlZWVqaWlxW89NTVVKSkpKikpsWJkWIiQAQCElMOHD+vkyZNtPsE9ISFBHo9HHo9HkZGRbb4Q+NQ6vlsIGQAAYCxCBgAQUnr27KkuXbq0eRdSTU2NEhMTlZiYqObmZtXW1p52Hd8thAwAIKRERkYqLS1NRUVFvn2tra0qKiqSy+VSWlqaIiIi/NbLy8tVUVEhl8tlxciwUMh/+zXwv44dO6ZPP/3Ut33w4EHt3btXsbGxSklJ0VdffaWKigpVVVVJ+vZfbpJ8/xcHwAxut1tTpkzR8OHDNWLECC1fvlwNDQ2aOnWqnE6npk2bJrfbrdjYWDkcDs2ePVsul4t3LH0XeQGDbNu2zSupzWPKlCler9frLSgoOO36woULLZ0bwLlbsWKFNyUlxRsZGekdMWKEt7S01Ld24sQJ7/333++95JJLvN26dfPecccd3urqagunhVX4HBkAAGAs7pEBAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAXDexowZozlz5rS73rdvXy1fvvyCzPL555/LZrNp7969F+TnAbAWIQOg0+3atUszZszwbdtsNm3cuLHDr1+7dq1iYmI69Nzk5GRVV1dr8ODB5zglABPxpZEAOl18fPwF+TnNzc2KjIzkC0KB7xDOyAAIim+++UazZs2S0+lUz549NX/+fJ36Krf/vbTUt29fSdIdd9whm83m2/7ggw900003qUePHnI4HEpLS9Pu3bu1fft2TZ06VXV1dbLZbLLZbHriiSd8x/rVr36lyZMny+FwaMaMGW0uLW3fvl02m01FRUUaPny4unXrptGjR/u+Gf2UxYsXq1evXurRo4fuvfdePfrooxo2bJhvffv27RoxYoS6d++umJgYXXfddfriiy86668TQAcRMgCC4qWXXlJ4eLh27typZ599Vs8884x+//vft3nerl27JEkFBQWqrq72bU+aNEl9+vTRrl27VFZWpkcffVQREREaPXq0li9fLofDoerqalVXV+uhhx7yHe83v/mNhg4dqj179mj+/PntzvfYY4/p6aef1u7duxUeHq577rnHt/byyy9ryZIlWrp0qcrKypSSkqJVq1b51r/55huNHz9eN954oz788EOVlJRoxowZstls5/33BuD8cGkJQFAkJydr2bJlstlsGjBggD766CMtW7ZM06dP93veqctMMTExfpeAKioq9PDDDys1NVWS1L9/f9+a0+mUzWY77SWj9PR0Pfjgg77tzz///LTzLVmyRDfeeKMk6dFHH9Vtt92mxsZGRUVFacWKFZo2bZqmTp0qSVqwYIG2bNmiY8eOSZLq6+tVV1enH//4x7riiiskSQMHDjynvx8AnYMzMgCCYtSoUX5nKFwulw4cOKCTJ0926PVut1v33nuvMjIy9NRTT+mzzz7r0OuGDx/eoecNGTLE9+fevXtLkg4dOiRJKi8v14gRI/ye/7/bsbGxuvvuu5WZmalx48bp2WefVXV1dYd+LoDORcgACAlPPPGEPv74Y91222166623NGjQIG3YsOGsr+vevXuHjh8REeH786ngam1t7fB8BQUFKikp0ejRo/Xqq6/qqquuUmlpaYdfD6BzEDIAgmLHjh1+26Wlperfv7+6dOnS5rkRERGnPVNz1VVXae7cudqyZYuysrJUUFAgSYqMjOzwmZ1ADBgwwHevzin/f1uSvve97yk3N1fvvfeeBg8erMLCwk6bCUDHEDIAgqKiokJut1vl5eV65ZVXtGLFCj3wwAOnfW7fvn1VVFQkj8ejr7/+WidOnNCsWbO0fft2ffHFF3r33Xe1a9cu330offv21bFjx1RUVKTDhw/r+PHjQZ199uzZWrNmjV566SUdOHBAixcv1ocffug7c3Pw4EHl5uaqpKREX3zxhbZs2aIDBw5wnwwQArjZF0BQTJ48WSdOnNCIESPUpUsXPfDAA34fgve/nn76abndbr3wwgu69NJL9e9//1tHjhzR5MmTVVNTo549eyorK0uLFi2SJI0ePVr33XefJkyYoCNHjmjhwoW+t2AHw6RJk/Sf//xHDz30kBobG3XXXXfp7rvv1s6dOyVJ3bp10/79+/XSSy/pyJEj6t27t3JycvSLX/wiaDMACIzNe+qDHgAAPjfffLMSExP1xz/+0epRAJwBZ2QAfOcdP35cq1evVmZmprp06aJXXnlFb775prZu3Wr1aADOgjMyAL7zTpw4oXHjxmnPnj1qbGzUgAED9PjjjysrK8vq0QCcBSEDAACMxbuWAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMb6PzNBakRu64XvAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] }, "metadata": {}, "output_type": "display_data" @@ -162,7 +164,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.11.4" } }, "nbformat": 4, diff --git a/examples/autoqasm/2_Expressing_classical_control_flow.ipynb b/examples/autoqasm/2_Expressing_classical_control_flow.ipynb index c4d32364..e5d12c72 100644 --- a/examples/autoqasm/2_Expressing_classical_control_flow.ipynb +++ b/examples/autoqasm/2_Expressing_classical_control_flow.ipynb @@ -129,7 +129,7 @@ " else:\n", " bell(3, 4)\n", "\n", - " c = measure([0, 1, 2, 3, 4])" + " return measure([0, 1, 2, 3, 4])\n" ] }, { @@ -150,13 +150,15 @@ "name": "stdout", "output_type": "stream", "text": [ - "measurement counts: Counter({'00011': 132, '00000': 130, '11100': 122, '10000': 116})\n" + "measurement counts: Counter({'00000': 137, '10000': 129, '00011': 119, '11100': 115})\n" ] }, { "data": { - "text/plain": "
", - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGwCAYAAABPSaTdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAoxUlEQVR4nO3df1TVdZ7H8ddXlCsSoPjjXu9ISbuYTZg2mI40JVlijj921rNZ6ZaNVjqmRdSYjJXYUVDnpOzCpqPbqJtrtmcnm3abdUFHMUNHRKi0TZ2GlAqGMgRUBMTv/tHxe7ohaXjhXj4+H+fcc7zf7+de3tfvgZ597/eKZdu2LQAAAEN1CvQAAAAAbYnYAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDROgd6gGBw/vx5ff7554qIiJBlWYEeBwAAXAbbtlVbWyuv16tOnVo+f0PsSPr8888VExMT6DEAAEArlJWVqV+/fi3uJ3YkRURESPr6LysyMjLA0wAAgMtRU1OjmJgY57/jLSF2JOetq8jISGIHAIAO5lKXoHCBMgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAo3UO9AAAEGz6z3870CNctT5ZOi7QI8BAnNkBAABGI3YAAIDRiB0AAGA0YgcAABiNC5SBVuIi1sDhIlYA3wdndgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABG6xzoAQAAaA/9578d6BGuWp8sHRfQr8+ZHQAAYLSAxs6uXbs0YcIEeb1eWZalN99809nX2NioZ599VoMGDVJ4eLi8Xq8eeughff755z7PUV9fr7lz56pXr14KDw/XxIkT9emnn7bzKwEAAMEqoLFz+vRpDR48WDk5Oc32nTlzRgcOHNDzzz+vAwcO6I033tCRI0c0ceJEn3UpKSnasmWLNm/erN27d+vUqVMaP368mpqa2utlAACAIBbQa3bGjh2rsWPHXnRfVFSU8vLyfLZlZ2dr2LBhOn78uK699lpVV1frlVde0auvvqq7775bkrRx40bFxMRo27ZtGjNmTJu/BgAAENw61DU71dXVsixL3bt3lyQVFRWpsbFRycnJzhqv16v4+HgVFBS0+Dz19fWqqanxuQEAADN1mNg5e/as5s+frylTpigyMlKSVFFRodDQUPXo0cNnrdvtVkVFRYvPlZmZqaioKOcWExPTprMDAIDA6RCx09jYqPvvv1/nz5/Xyy+/fMn1tm3LsqwW96elpam6utq5lZWV+XNcAAAQRII+dhobGzV58mSVlpYqLy/POasjSR6PRw0NDaqqqvJ5TGVlpdxud4vP6XK5FBkZ6XMDAABmCurYuRA6R48e1bZt29SzZ0+f/QkJCerSpYvPhczl5eU6ePCgEhMT23tcAAAQhAL6aaxTp07pz3/+s3O/tLRUJSUlio6Oltfr1T/8wz/owIED+u///m81NTU51+FER0crNDRUUVFRmjFjhp5++mn17NlT0dHReuaZZzRo0CDn01kAAODqFtDY2b9/v+68807nfmpqqiRp2rRpSk9P11tvvSVJGjJkiM/jduzYoaSkJEnSypUr1blzZ02ePFl1dXW66667tH79eoWEhLTLawAAAMEtoLGTlJQk27Zb3P9d+y7o2rWrsrOzlZ2d7c/RAACAIYL6mh0AAIArRewAAACjETsAAMBoAb1m52rQf/7bgR7hqvXJ0nGBHgEAEAQ4swMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMFNHZ27dqlCRMmyOv1yrIsvfnmmz77bdtWenq6vF6vwsLClJSUpEOHDvmsqa+v19y5c9WrVy+Fh4dr4sSJ+vTTT9vxVQAAgGAW0Ng5ffq0Bg8erJycnIvuX758uVasWKGcnBwVFhbK4/Fo9OjRqq2tddakpKRoy5Yt2rx5s3bv3q1Tp05p/Pjxampqaq+XAQAAgljnQH7xsWPHauzYsRfdZ9u2srKytGDBAk2aNEmStGHDBrndbm3atEkzZ85UdXW1XnnlFb366qu6++67JUkbN25UTEyMtm3bpjFjxrTbawEAAMEpaK/ZKS0tVUVFhZKTk51tLpdLI0eOVEFBgSSpqKhIjY2NPmu8Xq/i4+OdNRdTX1+vmpoanxsAADBT0MZORUWFJMntdvtsd7vdzr6KigqFhoaqR48eLa65mMzMTEVFRTm3mJgYP08PAACCRdDGzgWWZfnct2272bZvu9SatLQ0VVdXO7eysjK/zAoAAIJP0MaOx+ORpGZnaCorK52zPR6PRw0NDaqqqmpxzcW4XC5FRkb63AAAgJmCNnZiY2Pl8XiUl5fnbGtoaFB+fr4SExMlSQkJCerSpYvPmvLych08eNBZAwAArm4B/TTWqVOn9Oc//9m5X1paqpKSEkVHR+vaa69VSkqKMjIyFBcXp7i4OGVkZKhbt26aMmWKJCkqKkozZszQ008/rZ49eyo6OlrPPPOMBg0a5Hw6CwAAXN0CGjv79+/XnXfe6dxPTU2VJE2bNk3r16/XvHnzVFdXp9mzZ6uqqkrDhw9Xbm6uIiIinMesXLlSnTt31uTJk1VXV6e77rpL69evV0hISLu/HgAAEHwCGjtJSUmybbvF/ZZlKT09Xenp6S2u6dq1q7Kzs5Wdnd0GEwIAgI4uaK/ZAQAA8AdiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGC+rYOXfunJ577jnFxsYqLCxM119/vV588UWdP3/eWWPbttLT0+X1ehUWFqakpCQdOnQogFMDAIBgEtSxs2zZMq1evVo5OTn6v//7Py1fvly//vWvlZ2d7axZvny5VqxYoZycHBUWFsrj8Wj06NGqra0N4OQAACBYdA70AN9lz549+ru/+zuNGzdOktS/f3+99tpr2r9/v6Svz+pkZWVpwYIFmjRpkiRpw4YNcrvd2rRpk2bOnHnR562vr1d9fb1zv6ampo1fCQAACJSgPrPzk5/8RNu3b9eRI0ckSe+99552796tn/70p5Kk0tJSVVRUKDk52XmMy+XSyJEjVVBQ0OLzZmZmKioqyrnFxMS07QsBAAABE9Rndp599llVV1dr4MCBCgkJUVNTk5YsWaIHHnhAklRRUSFJcrvdPo9zu906duxYi8+blpam1NRU535NTQ3BAwCAoYI6dl5//XVt3LhRmzZt0k033aSSkhKlpKTI6/Vq2rRpzjrLsnweZ9t2s23f5HK55HK52mxuAAAQPII6dn75y19q/vz5uv/++yVJgwYN0rFjx5SZmalp06bJ4/FI+voMT9++fZ3HVVZWNjvbAwAArk5Bfc3OmTNn1KmT74ghISHOR89jY2Pl8XiUl5fn7G9oaFB+fr4SExPbdVYAABCcgvrMzoQJE7RkyRJde+21uummm1RcXKwVK1Zo+vTpkr5++yolJUUZGRmKi4tTXFycMjIy1K1bN02ZMiXA0wMAgGAQ1LGTnZ2t559/XrNnz1ZlZaW8Xq9mzpypF154wVkzb9481dXVafbs2aqqqtLw4cOVm5uriIiIAE4OAACCRVDHTkREhLKyspSVldXiGsuylJ6ervT09HabCwAAdBxBfc0OAADAlWpV7JSVlenTTz917u/bt08pKSlas2aN3wYDAADwh1bFzpQpU7Rjxw5JX3/se/To0dq3b59+9atf6cUXX/TrgAAAAFeiVbFz8OBBDRs2TJL0H//xH4qPj1dBQYE2bdqk9evX+3M+AACAK9Kq2GlsbHT+BeJt27Zp4sSJkqSBAweqvLzcf9MBAABcoVbFzk033aTVq1frnXfeUV5enu655x5J0ueff66ePXv6dUAAAIAr0arYWbZsmX7zm98oKSlJDzzwgAYPHixJeuutt5y3twAAAIJBq/6dnaSkJH355ZeqqalRjx49nO2PPfaYwsPD/TYcAADAlWrVmZ1Ro0aptrbWJ3QkKTo6Wvfdd59fBgMAAPCHVsXOzp071dDQ0Gz72bNn9c4771zxUAAAAP7yvd7Gev/9950/f/jhh6qoqHDuNzU1aevWrfrBD37gv+kAAACu0PeKnSFDhsiyLFmWpVGjRjXbHxYWpuzsbL8NBwAAcKW+V+yUlpbKtm1df/312rdvn3r37u3sCw0NVZ8+fRQSEuL3IQEAAFrre8XOddddJ0k6f/58mwwDAADgb6366LkkHTlyRDt37lRlZWWz+HnhhReueDAAAAB/aFXsrF27Vr/4xS/Uq1cveTweWZbl7LMsi9gBAABBo1Wxs3jxYi1ZskTPPvusv+cBAADwq1b9OztVVVW69957/T0LAACA37Uqdu69917l5ub6exYAAAC/a9XbWH/7t3+r559/Xnv37tWgQYPUpUsXn/1PPPGEX4YDAAC4Uq2KnTVr1uiaa65Rfn6+8vPzffZZlkXsAACAoNGq2CktLfX3HAAAAG2iVdfsAAAAdBStOrMzffr079z/29/+tlXDAAAA+FurYqeqqsrnfmNjow4ePKiTJ09e9BeEAgAABEqrYmfLli3Ntp0/f16zZ8/W9ddff8VDAQAA+Ivfrtnp1KmTnnrqKa1cudJfTwkAAHDF/HqB8scff6xz58758ykBAACuSKvexkpNTfW5b9u2ysvL9fbbb2vatGl+GQwAAMAfWhU7xcXFPvc7deqk3r1766WXXrrkJ7UAAADaU6tiZ8eOHf6eAwAAoE20KnYu+OKLL3T48GFZlqUBAwaod+/e/poLAADAL1p1gfLp06c1ffp09e3bV3fccYduv/12eb1ezZgxQ2fOnPH3jAAAAK3WqthJTU1Vfn6+/uu//ksnT57UyZMn9fvf/175+fl6+umn/T0jAABAq7Xqbazf/e53+s///E8lJSU52376058qLCxMkydP1qpVq/w1HwAAwBVp1ZmdM2fOyO12N9vep08f3sYCAABBpVWxM2LECC1cuFBnz551ttXV1WnRokUaMWKE34YDAAC4Uq16GysrK0tjx45Vv379NHjwYFmWpZKSErlcLuXm5vp7RgAAgFZrVewMGjRIR48e1caNG/XRRx/Jtm3df//9mjp1qsLCwvw9IwAAQKu1KnYyMzPldrv16KOP+mz/7W9/qy+++ELPPvusX4YDAAC4Uq26Zuc3v/mNBg4c2Gz7TTfdpNWrV1/xUAAAAP7SqtipqKhQ3759m23v3bu3ysvLr3goAAAAf2lV7MTExOjdd99ttv3dd9+V1+u94qG+6bPPPtM//uM/qmfPnurWrZuGDBmioqIiZ79t20pPT5fX61VYWJiSkpJ06NAhv84AAAA6rlZds/PII48oJSVFjY2NGjVqlCRp+/btmjdvnl//BeWqqirddtttuvPOO/U///M/6tOnjz7++GN1797dWbN8+XKtWLFC69ev14ABA7R48WKNHj1ahw8fVkREhN9mAQAAHVOrYmfevHn66quvNHv2bDU0NEiSunbtqmeffVZpaWl+G27ZsmWKiYnRunXrnG39+/d3/mzbtrKysrRgwQJNmjRJkrRhwwa53W5t2rRJM2fO9NssAACgY2rV21iWZWnZsmX64osvtHfvXr333nv66quv9MILL/h1uLfeektDhw7Vvffeqz59+uiWW27R2rVrnf2lpaWqqKhQcnKys83lcmnkyJEqKCho8Xnr6+tVU1PjcwMAAGZqVexccM011+jWW29VfHy8XC6Xv2Zy/OUvf9GqVasUFxen//3f/9WsWbP0xBNP6N/+7d8kfX2htKRmv7rC7XY7+y4mMzNTUVFRzi0mJsbvswMAgOBwRbHT1s6fP68f/ehHysjI0C233KKZM2fq0UcfbfaLRi3L8rlv23azbd+Ulpam6upq51ZWVtYm8wMAgMAL6tjp27evfvjDH/psu/HGG3X8+HFJksfjkaRmZ3EqKysv+otKL3C5XIqMjPS5AQAAMwV17Nx22206fPiwz7YjR47ouuuukyTFxsbK4/EoLy/P2d/Q0KD8/HwlJia266wAACA4terTWO3lqaeeUmJiojIyMjR58mTt27dPa9as0Zo1ayR9/fZVSkqKMjIyFBcXp7i4OGVkZKhbt26aMmVKgKcHAADBIKhj59Zbb9WWLVuUlpamF198UbGxscrKytLUqVOdNfPmzVNdXZ1mz56tqqoqDR8+XLm5ufwbOwAAQFKQx44kjR8/XuPHj29xv2VZSk9PV3p6evsNBQAAOoygvmYHAADgShE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoHSp2MjMzZVmWUlJSnG22bSs9PV1er1dhYWFKSkrSoUOHAjckAAAIKh0mdgoLC7VmzRrdfPPNPtuXL1+uFStWKCcnR4WFhfJ4PBo9erRqa2sDNCkAAAgmHSJ2Tp06palTp2rt2rXq0aOHs922bWVlZWnBggWaNGmS4uPjtWHDBp05c0abNm0K4MQAACBYdIjYefzxxzVu3DjdfffdPttLS0tVUVGh5ORkZ5vL5dLIkSNVUFDQ4vPV19erpqbG5wYAAMzUOdADXMrmzZt14MABFRYWNttXUVEhSXK73T7b3W63jh071uJzZmZmatGiRf4dFAAABKWgPrNTVlamJ598Uhs3blTXrl1bXGdZls9927abbfumtLQ0VVdXO7eysjK/zQwAAIJLUJ/ZKSoqUmVlpRISEpxtTU1N2rVrl3JycnT48GFJX5/h6du3r7OmsrKy2dmeb3K5XHK5XG03OAAACBpBfWbnrrvu0gcffKCSkhLnNnToUE2dOlUlJSW6/vrr5fF4lJeX5zymoaFB+fn5SkxMDODkAAAgWAT1mZ2IiAjFx8f7bAsPD1fPnj2d7SkpKcrIyFBcXJzi4uKUkZGhbt26acqUKYEYGQAABJmgjp3LMW/ePNXV1Wn27NmqqqrS8OHDlZubq4iIiECPBgAAgkCHi52dO3f63LcsS+np6UpPTw/IPAAAILgF9TU7AAAAV4rYAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRgjp2MjMzdeuttyoiIkJ9+vTRz372Mx0+fNhnjW3bSk9Pl9frVVhYmJKSknTo0KEATQwAAIJNUMdOfn6+Hn/8ce3du1d5eXk6d+6ckpOTdfr0aWfN8uXLtWLFCuXk5KiwsFAej0ejR49WbW1tACcHAADBonOgB/guW7du9bm/bt069enTR0VFRbrjjjtk27aysrK0YMECTZo0SZK0YcMGud1ubdq0STNnzrzo89bX16u+vt65X1NT03YvAgAABFRQn9n5turqaklSdHS0JKm0tFQVFRVKTk521rhcLo0cOVIFBQUtPk9mZqaioqKcW0xMTNsODgAAAqbDxI5t20pNTdVPfvITxcfHS5IqKiokSW6322et2+129l1MWlqaqqurnVtZWVnbDQ4AAAIqqN/G+qY5c+bo/fff1+7du5vtsyzL575t2822fZPL5ZLL5fL7jAAAIPh0iDM7c+fO1VtvvaUdO3aoX79+znaPxyNJzc7iVFZWNjvbAwAArk5BHTu2bWvOnDl644039Mc//lGxsbE++2NjY+XxeJSXl+dsa2hoUH5+vhITE9t7XAAAEISC+m2sxx9/XJs2bdLvf/97RUREOGdwoqKiFBYWJsuylJKSooyMDMXFxSkuLk4ZGRnq1q2bpkyZEuDpAQBAMAjq2Fm1apUkKSkpyWf7unXr9PDDD0uS5s2bp7q6Os2ePVtVVVUaPny4cnNzFRER0c7TAgCAYBTUsWPb9iXXWJal9PR0paent/1AAACgwwnqa3YAAACuFLEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxmTOy8/PLLio2NVdeuXZWQkKB33nkn0CMBAIAgYETsvP7660pJSdGCBQtUXFys22+/XWPHjtXx48cDPRoAAAgwI2JnxYoVmjFjhh555BHdeOONysrKUkxMjFatWhXo0QAAQIB1DvQAV6qhoUFFRUWaP3++z/bk5GQVFBRc9DH19fWqr6937ldXV0uSampq/D7f+fozfn9OXJ62OJ7fxLENHI6tudry2HJcA6etjuuF57Vt+zvXdfjY+fLLL9XU1CS32+2z3e12q6Ki4qKPyczM1KJFi5ptj4mJaZMZERhRWYGeAG2FY2sujq2Z2vq41tbWKioqqsX9HT52LrAsy+e+bdvNtl2Qlpam1NRU5/758+f11VdfqWfPni0+5mpUU1OjmJgYlZWVKTIyMtDjwI84tmbiuJqLY3txtm2rtrZWXq/3O9d1+Njp1auXQkJCmp3FqaysbHa25wKXyyWXy+WzrXv37m01YocXGRnJN5ehOLZm4riai2Pb3Hed0bmgw1+gHBoaqoSEBOXl5flsz8vLU2JiYoCmAgAAwaLDn9mRpNTUVD344IMaOnSoRowYoTVr1uj48eOaNWtWoEcDAAABZkTs3HfffTpx4oRefPFFlZeXKz4+Xn/4wx903XXXBXq0Ds3lcmnhwoXN3vJDx8exNRPH1Vwc2ytj2Zf6vBYAAEAH1uGv2QEAAPguxA4AADAasQMAAIxG7AAAAKMROwbZtWuXJkyYIK/XK8uy9Oabb/rst21b6enp8nq9CgsLU1JSkg4dOuSzpr6+XnPnzlWvXr0UHh6uiRMn6tNPP/VZU1VVpQcffFBRUVGKiorSgw8+qJMnT/qsOX78uCZMmKDw8HD16tVLTzzxhBoaGtriZV81Xn75ZcXGxqpr165KSEjQO++84+zj2HZs7XFslyxZosTERHXr1q3Ff0T1ySefVEJCglwul4YMGeLvl3lVudTP4zfeeENjxoxRr169ZFmWSkpKmj3HmjVrlJSUpMjISFmW1ex7UeJ79nIROwY5ffq0Bg8erJycnIvuX758uVasWKGcnBwVFhbK4/Fo9OjRqq2tddakpKRoy5Yt2rx5s3bv3q1Tp05p/PjxampqctZMmTJFJSUl2rp1q7Zu3aqSkhI9+OCDzv6mpiaNGzdOp0+f1u7du7V582b97ne/09NPP912L95wr7/+ulJSUrRgwQIVFxfr9ttv19ixY3X8+HFJHNuOrL2ObUNDg+6991794he/aHEW27Y1ffp03XfffW33gq8Sl/p5fPr0ad12221aunRpi89x5swZ3XPPPfrVr37V4hq+Zy+TDSNJsrds2eLcP3/+vO3xeOylS5c6286ePWtHRUXZq1evtm3btk+ePGl36dLF3rx5s7Pms88+szt16mRv3brVtm3b/vDDD21J9t69e501e/bssSXZH330kW3btv2HP/zB7tSpk/3ZZ585a1577TXb5XLZ1dXVbfJ6TTds2DB71qxZPtsGDhxoz58/n2PbwbXHsf2mdevW2VFRUd8508KFC+3Bgwe3/kXBx7d/Hn9TaWmpLckuLi5u8fE7duywJdlVVVU+2/mevXyc2blKlJaWqqKiQsnJyc42l8ulkSNHqqCgQJJUVFSkxsZGnzVer1fx8fHOmj179igqKkrDhw931vz4xz9WVFSUz5r4+HifX8w2ZswY1dfXq6ioqE1fp4kaGhpUVFTkc1wkKTk5WQUFBRzbDqy9ji3MxPfs5SN2rhIXflHqt385qtvtdvZVVFQoNDRUPXr0+M41ffr0afb8ffr08Vnz7a/To0cPhYaGNvuFrbi0L7/8Uk1NTS0eO45tx9VexxZm4nv28hE7VxnLsnzu27bdbNu3fXvNxda3Zg2+n0sdO45tx9UexxZm4nv28hA7VwmPxyNJzUq+srLSqX6Px6OGhgZVVVV955q//vWvzZ7/iy++8Fnz7a9TVVWlxsbGZv+HgUvr1auXQkJCWjx2HNuOq72OLczE9+zlI3auErGxsfJ4PMrLy3O2NTQ0KD8/X4mJiZKkhIQEdenSxWdNeXm5Dh486KwZMWKEqqurtW/fPmfNn/70J1VXV/usOXjwoMrLy501ubm5crlcSkhIaNPXaaLQ0FAlJCT4HBdJysvLU2JiIse2A2uvYwsz8T37PQTiqmi0jdraWru4uNguLi62JdkrVqywi4uL7WPHjtm2bdtLly61o6Ki7DfeeMP+4IMP7AceeMDu27evXVNT4zzHrFmz7H79+tnbtm2zDxw4YI8aNcoePHiwfe7cOWfNPffcY9988832nj177D179tiDBg2yx48f7+w/d+6cHR8fb9911132gQMH7G3bttn9+vWz58yZ035/GYbZvHmz3aVLF/uVV16xP/zwQzslJcUODw+3P/nkE9u2ObYdWXsd22PHjtnFxcX2okWL7Guuucb5WVFbW+usOXr0qF1cXGzPnDnTHjBggLOmvr6+/f5CDHGpn8cnTpywi4uL7bffftuWZG/evNkuLi62y8vLnecoLy+3i4uL7bVr19qS7F27dtnFxcX2iRMnnDV8z14eYscgFz6e+O3btGnTbNv++uPnCxcutD0ej+1yuew77rjD/uCDD3yeo66uzp4zZ44dHR1th4WF2ePHj7ePHz/us+bEiRP21KlT7YiICDsiIsKeOnVqs49EHjt2zB43bpwdFhZmR0dH23PmzLHPnj3bli/feP/yL/9iX3fddXZoaKj9ox/9yM7Pz3f2cWw7tvY4ttOmTbvoz4cdO3Y4a0aOHHnRNaWlpW358o10qZ/H69atu+j+hQsXOs+xcOHCi65Zt26ds4bv2ctj2bZtt/XZIwAAgEDhmh0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAO0iKSlJKSkpLe7v37+/srKy2mWWTz75RJZlqaSkpF2+HoDAInYABIXCwkI99thjzn3LsvTmm29e9uPXr1+v7t27X9bamJgYlZeXKz4+/ntOCaAj6hzoAQBAknr37t0uX6ehoUGhoaHyeDzt8vUABB5ndgC0m3PnzmnOnDnq3r27evbsqeeee04Xfj3fN9/G6t+/vyTp7//+72VZlnP/vffe05133qmIiAhFRkYqISFB+/fv186dO/Xzn/9c1dXVsixLlmUpPT3dea7Fixfr4YcfVlRUlB599NFmb2Pt3LlTlmVp+/btGjp0qLp166bExEQdPnzYZ/7FixerT58+ioiI0COPPKL58+dryJAhzv6dO3dq2LBhCg8PV/fu3XXbbbfp2LFjbfXXCeAyETsA2s2GDRvUuXNn/elPf9I///M/a+XKlfrXf/3XZusKCwslSevWrVN5eblzf+rUqerXr58KCwtVVFSk+fPnq0uXLkpMTFRWVpYiIyNVXl6u8vJyPfPMM87z/frXv1Z8fLyKior0/PPPtzjfggUL9NJLL2n//v3q3Lmzpk+f7uz793//dy1ZskTLli1TUVGRrr32Wq1atcrZf+7cOf3sZz/TyJEj9f7772vPnj167LHHZFnWFf+9AbgyvI0FoN3ExMRo5cqVsixLN9xwgz744AOtXLlSjz76qM+6C29pde/e3eftpuPHj+uXv/ylBg4cKEmKi4tz9kVFRcmyrIu+PTVq1Cif+Pnkk08uOt+SJUs0cuRISdL8+fM1btw4nT17Vl27dlV2drZmzJihn//855KkF154Qbm5uTp16pQkqaamRtXV1Ro/frz+5m/+RpJ04403fq+/HwBtgzM7ANrNj3/8Y58zHSNGjNDRo0fV1NR0WY9PTU3VI488orvvvltLly7Vxx9/fFmPGzp06GWtu/nmm50/9+3bV5JUWVkpSTp8+LCGDRvms/6b96Ojo/Xwww9rzJgxmjBhgv7pn/5J5eXll/V1AbQtYgdAh5Genq5Dhw5p3Lhx+uMf/6gf/vCH2rJlyyUfFx4eflnP36VLF+fPF6Ls/PnzzbZdcOF6owvWrVunPXv2KDExUa+//roGDBigvXv3XtbXBtB2iB0A7ebb/+Hfu3ev4uLiFBIS0mxtly5dLnrGZ8CAAXrqqaeUm5urSZMmad26dZKk0NDQyz5D1Bo33HCD9u3b57Nt//79zdbdcsstSktLU0FBgeLj47Vp06Y2mwnA5SF2ALSbsrIypaam6vDhw3rttdeUnZ2tJ5988qJr+/fvr+3bt6uiokJVVVWqq6vTnDlztHPnTh07dkzvvvuuCgsLneti+vfvr1OnTmn79u368ssvdebMGb/OPnfuXL3yyivasGGDjh49qsWLF+v99993zvaUlpYqLS1Ne/bs0bFjx5Sbm6sjR45w3Q4QBLhAGUC7eeihh1RXV6dhw4YpJCREc+fO9fmHBL/ppZdeUmpqqtauXasf/OAHOnLkiE6cOKGHHnpIf/3rX9WrVy9NmjRJixYtkiQlJiZq1qxZuu+++3TixAktXLjQ+fi5P0ydOlV/+ctf9Mwzz+js2bOaPHmyHn74YedsT7du3fTRRx9pw4YNOnHihPr27as5c+Zo5syZfpsBQOtY9rffdAYAXJbRo0fL4/Ho1VdfDfQoAL4DZ3YA4DKcOXNGq1ev1pgxYxQSEqLXXntN27ZtU15eXqBHA3AJnNkBgMtQV1enCRMm6MCBA6qvr9cNN9yg5557TpMmTQr0aAAugdgBAABG49NYAADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKP9P4FugkOtagcYAAAAAElFTkSuQmCC" + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGwCAYAAABPSaTdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/SrBM8AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAseUlEQVR4nO3dfVRVdd7//9dBbr05ICocKEhqTE0dM03C/JYZExo5mV6VxVVmJt1opTSmXBNaZoOaJemQZFepXUur6bpGK5uhDO+mRFTEbsyIJlJXcrBUON6BKPv3xyzPrzNKKh44h0/Px1p7Lffn89n7vPf5rPTV3p9zjs2yLEsAAACGCvB1AQAAAE2JsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYLRAXxfgD+rr67V37161a9dONpvN1+UAAIBzYFmWDh06pNjYWAUENHz/hrAjae/evYqLi/N1GQAAoBH27Nmjiy++uMF+wo6kdu3aSfrXm2W3231cDQAAOBcul0txcXHuf8cbQtiR3I+u7HY7YQcAgBbmbEtQWKAMAACMRtgBAABGI+wAAACj+TTsbNiwQcOGDVNsbKxsNptWrlzZ4NiHHnpINptNOTk5Hu0HDhxQWlqa7Ha7IiIiNHbsWB0+fLhpCwcAAC2GT8POkSNH1Lt3b+Xm5v7iuBUrVmjTpk2KjY09rS8tLU07duzQ6tWrtWrVKm3YsEHp6elNVTIAAGhhfPpprKFDh2ro0KG/OOaHH37Qo48+qg8//FCpqakefTt37lR+fr62bNmifv36SZIWLFigm2++WXPnzj1jOAIAAL8ufr1mp76+Xvfcc48mT56sHj16nNZfWFioiIgId9CRpOTkZAUEBKioqKjB89bW1srlcnlsAADATH4ddmbPnq3AwEA99thjZ+x3Op2KioryaAsMDFRkZKScTmeD583OzlZ4eLh749uTAQAwl9+GneLiYr300ktasmSJ13+vKjMzU9XV1e5tz549Xj0/AADwH34bdv7xj39o3759io+PV2BgoAIDA7Vr1y498cQT6ty5syTJ4XBo3759HsedOHFCBw4ckMPhaPDcISEh7m9L5luTAQAwm9/+XMQ999yj5ORkj7aUlBTdc889GjNmjCQpKSlJVVVVKi4uVt++fSVJa9asUX19vRITE5u9ZgAA4H98GnYOHz6sb7/91r1fXl6u7du3KzIyUvHx8erQoYPH+KCgIDkcDnXt2lWS1L17dw0ZMkTjxo1TXl6e6urqNGHCBI0aNYpPYgEAAEk+foy1detW9enTR3369JEkZWRkqE+fPpo2bdo5n2PZsmXq1q2bbrzxRt18880aOHCgFi1a1FQlAwCAFsZmWZbl6yJ8zeVyKTw8XNXV1azfAQCghTjXf7/9doEyAACAN/jtAmUA8JXOUz/wdQm/St/PSj37IKARuLMDAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAowX6ugCgpeo89QNfl/Cr9f2sVF+XAKAF4c4OAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjObTsLNhwwYNGzZMsbGxstlsWrlypbuvrq5OU6ZMUa9evdSmTRvFxsbq3nvv1d69ez3OceDAAaWlpclutysiIkJjx47V4cOHm/lKAACAv/Jp2Dly5Ih69+6t3Nzc0/qOHj2qbdu2KSsrS9u2bdNf//pXlZaW6ve//73HuLS0NO3YsUOrV6/WqlWrtGHDBqWnpzfXJQAAAD8X6MsXHzp0qIYOHXrGvvDwcK1evdqj7c9//rP69++v3bt3Kz4+Xjt37lR+fr62bNmifv36SZIWLFigm2++WXPnzlVsbOwZz11bW6va2lr3vsvl8tIVAQAAf9Oi1uxUV1fLZrMpIiJCklRYWKiIiAh30JGk5ORkBQQEqKioqMHzZGdnKzw83L3FxcU1dekAAMBHWkzYqamp0ZQpU3TXXXfJbrdLkpxOp6KiojzGBQYGKjIyUk6ns8FzZWZmqrq62r3t2bOnSWsHAAC+49PHWOeqrq5Od9xxhyzL0sKFCy/4fCEhIQoJCfFCZQAAwN/5fdg5FXR27dqlNWvWuO/qSJLD4dC+ffs8xp84cUIHDhyQw+Fo7lIBAH6s89QPfF3Cr9b3s1J9+vp+/RjrVNApKyvTxx9/rA4dOnj0JyUlqaqqSsXFxe62NWvWqL6+XomJic1dLgAA8EM+vbNz+PBhffvtt+798vJybd++XZGRkYqJidF//Md/aNu2bVq1apVOnjzpXocTGRmp4OBgde/eXUOGDNG4ceOUl5enuro6TZgwQaNGjWrwk1gAAODXxadhZ+vWrbrhhhvc+xkZGZKk0aNH6+mnn9Z7770nSbryyis9jlu7dq0GDRokSVq2bJkmTJigG2+8UQEBARo5cqTmz5/fLPUDAAD/59OwM2jQIFmW1WD/L/WdEhkZqeXLl3uzLAAAYBC/XrMDAABwoQg7AADAaIQdAABgNMIOAAAwGmEHAAAYze+/Qbml4xs7fcfX39gJAPAP3NkBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKP5NOxs2LBBw4YNU2xsrGw2m1auXOnRb1mWpk2bppiYGIWFhSk5OVllZWUeYw4cOKC0tDTZ7XZFRERo7NixOnz4cDNeBQAA8Gc+DTtHjhxR7969lZube8b+OXPmaP78+crLy1NRUZHatGmjlJQU1dTUuMekpaVpx44dWr16tVatWqUNGzYoPT29uS4BAAD4uUBfvvjQoUM1dOjQM/ZZlqWcnBw99dRTuvXWWyVJb7zxhqKjo7Vy5UqNGjVKO3fuVH5+vrZs2aJ+/fpJkhYsWKCbb75Zc+fOVWxsbLNdCwAA8E9+u2anvLxcTqdTycnJ7rbw8HAlJiaqsLBQklRYWKiIiAh30JGk5ORkBQQEqKioqMFz19bWyuVyeWwAAMBMfht2nE6nJCk6OtqjPTo62t3ndDoVFRXl0R8YGKjIyEj3mDPJzs5WeHi4e4uLi/Ny9QAAwF/4bdhpSpmZmaqurnZve/bs8XVJAACgifht2HE4HJKkyspKj/bKykp3n8Ph0L59+zz6T5w4oQMHDrjHnElISIjsdrvHBgAAzOS3YSchIUEOh0MFBQXuNpfLpaKiIiUlJUmSkpKSVFVVpeLiYveYNWvWqL6+XomJic1eMwAA8D8+/TTW4cOH9e2337r3y8vLtX37dkVGRio+Pl4TJ07UzJkz1aVLFyUkJCgrK0uxsbEaPny4JKl79+4aMmSIxo0bp7y8PNXV1WnChAkaNWoUn8QCAACSfBx2tm7dqhtuuMG9n5GRIUkaPXq0lixZoieffFJHjhxRenq6qqqqNHDgQOXn5ys0NNR9zLJlyzRhwgTdeOONCggI0MiRIzV//vxmvxYAAOCffBp2Bg0aJMuyGuy32WyaMWOGZsyY0eCYyMhILV++vCnKAwAABvDbNTsAAADeQNgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjObXYefkyZPKyspSQkKCwsLCdNlll+nZZ5+VZVnuMZZladq0aYqJiVFYWJiSk5NVVlbmw6oBAIA/8euwM3v2bC1cuFB//vOftXPnTs2ePVtz5szRggUL3GPmzJmj+fPnKy8vT0VFRWrTpo1SUlJUU1Pjw8oBAIC/CPR1Ab9k48aNuvXWW5WamipJ6ty5s958801t3rxZ0r/u6uTk5Oipp57SrbfeKkl64403FB0drZUrV2rUqFFnPG9tba1qa2vd+y6Xq4mvBAAA+Ipf39kZMGCACgoK9M0330iSPvvsM33yyScaOnSoJKm8vFxOp1PJycnuY8LDw5WYmKjCwsIGz5udna3w8HD3FhcX17QXAgAAfKZRd3b27Nkjm82miy++WJK0efNmLV++XFdccYXS09O9VtzUqVPlcrnUrVs3tWrVSidPntRzzz2ntLQ0SZLT6ZQkRUdHexwXHR3t7juTzMxMZWRkuPddLheBBwAAQzXqzs7dd9+ttWvXSvpX4Pjd736nzZs3649//KNmzJjhteL+8pe/aNmyZVq+fLm2bdumpUuXau7cuVq6dOkFnTckJER2u91jAwAAZmpU2Pnyyy/Vv39/Sf8KJD179tTGjRu1bNkyLVmyxGvFTZ48WVOnTtWoUaPUq1cv3XPPPZo0aZKys7MlSQ6HQ5JUWVnpcVxlZaW7DwAA/Lo1KuzU1dUpJCREkvTxxx/r97//vSSpW7duqqio8FpxR48eVUCAZ4mtWrVSfX29JCkhIUEOh0MFBQXufpfLpaKiIiUlJXmtDgAA0HI1as1Ojx49lJeXp9TUVK1evVrPPvusJGnv3r3q0KGD14obNmyYnnvuOcXHx6tHjx4qKSnRiy++qPvvv1+SZLPZNHHiRM2cOVNdunRRQkKCsrKyFBsbq+HDh3utDgAA0HI1KuzMnj1bt912m55//nmNHj1avXv3liS999577sdb3rBgwQJlZWXpkUce0b59+xQbG6sHH3xQ06ZNc4958skndeTIEaWnp6uqqkoDBw5Ufn6+QkNDvVYHAABouRoVdgYNGqSffvpJLpdL7du3d7enp6erTZs2XiuuXbt2ysnJUU5OToNjbDabZsyY4dWF0QAAwByNWrMzePBgHTp0yCPoSFJkZKTuvPNOrxQGAADgDY0KO+vWrdPx48dPa6+pqdE//vGPCy4KAADAW87rMdbnn3/u/vNXX33l8cV9J0+eVH5+vi666CLvVQcAAHCBzivsXHnllbLZbLLZbBo8ePBp/WFhYR4/0gkAAOBr5xV2ysvLZVmWLr30Um3evFmdOnVy9wUHBysqKkqtWrXyepEAAACNdV5h55JLLpEk95f6AQAA+LtGffRcksrKyrR27Vrt27fvtPDz8+/BAQAA8KVGhZ1XX31VDz/8sDp27CiHwyGbzebus9lshB0AAOA3GhV2Zs6cqeeee05Tpkzxdj0AAABe1ajv2Tl48KBuv/12b9cCAADgdY0KO7fffrs++ugjb9cCAADgdY16jPWb3/xGWVlZ2rRpk3r16qWgoCCP/scee8wrxQEAAFyoRoWdRYsWqW3btlq/fr3Wr1/v0Wez2Qg7AADAbzQq7JSXl3u7DgAAgCbRqDU7AAAALUWj7uzcf//9v9j/+uuvN6oYAAAAb2tU2Dl48KDHfl1dnb788ktVVVWd8QdCAQAAfKVRYWfFihWntdXX1+vhhx/WZZdddsFFAQAAeIvX1uwEBAQoIyND8+bN89YpAQAALphXFyj/85//1IkTJ7x5SgAAgAvSqMdYGRkZHvuWZamiokIffPCBRo8e7ZXCAAAAvKFRYaekpMRjPyAgQJ06ddILL7xw1k9qAQAANKdGhZ21a9d6uw4AAIAm0aiwc8qPP/6o0tJSSVLXrl3VqVMnrxQFAADgLY1aoHzkyBHdf//9iomJ0XXXXafrrrtOsbGxGjt2rI4ePertGgEAABqtUWEnIyND69ev1/vvv6+qqipVVVXp3Xff1fr16/XEE094u0YAAIBGa9RjrP/7v//T//7v/2rQoEHutptvvllhYWG64447tHDhQm/VBwAAcEEadWfn6NGjio6OPq09KiqKx1gAAMCvNCrsJCUlafr06aqpqXG3HTt2TM8884ySkpK8VhwAAMCFatRjrJycHA0ZMkQXX3yxevfuLUn67LPPFBISoo8++sirBQIAAFyIRoWdXr16qaysTMuWLdPXX38tSbrrrruUlpamsLAwrxYIAABwIRoVdrKzsxUdHa1x48Z5tL/++uv68ccfNWXKFK8UBwAAcKEatWbnlVdeUbdu3U5r79Gjh/Ly8i64KAAAAG9pVNhxOp2KiYk5rb1Tp06qqKi44KIAAAC8pVFhJy4uTp9++ulp7Z9++qliY2MvuCgAAABvadSanXHjxmnixImqq6vT4MGDJUkFBQV68skn+QZlAADgVxoVdiZPnqz9+/frkUce0fHjxyVJoaGhmjJlijIzM71aIAAAwIVoVNix2WyaPXu2srKytHPnToWFhalLly4KCQnxdn0AAAAXpFFh55S2bdvq6quv9lYtAAAAXteoBcoAAAAthd+HnR9++EH/+Z//qQ4dOigsLEy9evXS1q1b3f2WZWnatGmKiYlRWFiYkpOTVVZW5sOKAQCAP/HrsHPw4EFde+21CgoK0t///nd99dVXeuGFF9S+fXv3mDlz5mj+/PnKy8tTUVGR2rRpo5SUFI8fKQUAAL9eF7Rmp6nNnj1bcXFxWrx4sbstISHB/WfLspSTk6OnnnpKt956qyTpjTfeUHR0tFauXKlRo0Y1e80AAMC/+PWdnffee0/9+vXT7bffrqioKPXp00evvvqqu7+8vFxOp1PJycnutvDwcCUmJqqwsLDB89bW1srlcnlsAADATH4ddr777jstXLhQXbp00YcffqiHH35Yjz32mJYuXSrpXz9bIUnR0dEex0VHR7v7ziQ7O1vh4eHuLS4urukuAgAA+JRfh536+npdddVV+tOf/qQ+ffooPT1d48aNu+AfG83MzFR1dbV727Nnj5cqBgAA/savw05MTIyuuOIKj7bu3btr9+7dkiSHwyFJqqys9BhTWVnp7juTkJAQ2e12jw0AAJjJr8POtddeq9LSUo+2b775Rpdccomkfy1WdjgcKigocPe7XC4VFRUpKSmpWWsFAAD+ya8/jTVp0iQNGDBAf/rTn3THHXdo8+bNWrRokRYtWiTpXz9bMXHiRM2cOVNdunRRQkKCsrKyFBsbq+HDh/u2eAAA4Bf8OuxcffXVWrFihTIzMzVjxgwlJCQoJydHaWlp7jFPPvmkjhw5ovT0dFVVVWngwIHKz89XaGioDysHAAD+wq/DjiTdcsstuuWWWxrst9lsmjFjhmbMmNGMVQEAgJbCr9fsAAAAXCjCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBoLSrszJo1SzabTRMnTnS31dTUaPz48erQoYPatm2rkSNHqrKy0ndFAgAAv9Jiws6WLVv0yiuv6Le//a1H+6RJk/T+++/rnXfe0fr167V3716NGDHCR1UCAAB/0yLCzuHDh5WWlqZXX31V7du3d7dXV1frtdde04svvqjBgwerb9++Wrx4sTZu3KhNmzb5sGIAAOAvWkTYGT9+vFJTU5WcnOzRXlxcrLq6Oo/2bt26KT4+XoWFhQ2er7a2Vi6Xy2MDAABmCvR1AWfz1ltvadu2bdqyZctpfU6nU8HBwYqIiPBoj46OltPpbPCc2dnZeuaZZ7xdKgAA8EN+fWdnz549evzxx7Vs2TKFhoZ67byZmZmqrq52b3v27PHauQEAgH/x67BTXFysffv26aqrrlJgYKACAwO1fv16zZ8/X4GBgYqOjtbx48dVVVXlcVxlZaUcDkeD5w0JCZHdbvfYAACAmfz6MdaNN96oL774wqNtzJgx6tatm6ZMmaK4uDgFBQWpoKBAI0eOlCSVlpZq9+7dSkpK8kXJAADAz/h12GnXrp169uzp0damTRt16NDB3T527FhlZGQoMjJSdrtdjz76qJKSknTNNdf4omQAAOBn/DrsnIt58+YpICBAI0eOVG1trVJSUvTyyy/7uiwAAOAnWlzYWbduncd+aGiocnNzlZub65uCAACAX/PrBcoAAAAXirADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGM2vw052drauvvpqtWvXTlFRURo+fLhKS0s9xtTU1Gj8+PHq0KGD2rZtq5EjR6qystJHFQMAAH/j12Fn/fr1Gj9+vDZt2qTVq1errq5ON910k44cOeIeM2nSJL3//vt65513tH79eu3du1cjRozwYdUAAMCfBPq6gF+Sn5/vsb9kyRJFRUWpuLhY1113naqrq/Xaa69p+fLlGjx4sCRp8eLF6t69uzZt2qRrrrnmjOetra1VbW2te9/lcjXdRQAAAJ/y6zs7/666ulqSFBkZKUkqLi5WXV2dkpOT3WO6deum+Ph4FRYWNnie7OxshYeHu7e4uLimLRwAAPhMiwk79fX1mjhxoq699lr17NlTkuR0OhUcHKyIiAiPsdHR0XI6nQ2eKzMzU9XV1e5tz549TVk6AADwIb9+jPVz48eP15dffqlPPvnkgs8VEhKikJAQL1QFAAD8XYu4szNhwgStWrVKa9eu1cUXX+xudzgcOn78uKqqqjzGV1ZWyuFwNHOVAADAH/l12LEsSxMmTNCKFSu0Zs0aJSQkePT37dtXQUFBKigocLeVlpZq9+7dSkpKau5yAQCAH/Lrx1jjx4/X8uXL9e6776pdu3budTjh4eEKCwtTeHi4xo4dq4yMDEVGRsput+vRRx9VUlJSg5/EAgAAvy5+HXYWLlwoSRo0aJBH++LFi3XfffdJkubNm6eAgACNHDlStbW1SklJ0csvv9zMlQIAAH/l12HHsqyzjgkNDVVubq5yc3OboSIAANDS+PWaHQAAgAtF2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYzZiwk5ubq86dOys0NFSJiYnavHmzr0sCAAB+wIiw8/bbbysjI0PTp0/Xtm3b1Lt3b6WkpGjfvn2+Lg0AAPiYEWHnxRdf1Lhx4zRmzBhdccUVysvLU+vWrfX666/7ujQAAOBjgb4u4EIdP35cxcXFyszMdLcFBAQoOTlZhYWFZzymtrZWtbW17v3q6mpJksvl8np99bVHvX5OnJummM+fY259h7k1E/Nqrqaa21PntSzrF8e1+LDz008/6eTJk4qOjvZoj46O1tdff33GY7Kzs/XMM8+c1h4XF9ckNcI3wnN8XQGaCnNrJubVXE09t4cOHVJ4eHiD/S0+7DRGZmamMjIy3Pv19fU6cOCAOnToIJvN5sPK/IvL5VJcXJz27Nkju93u63LgJcyruZhbczG3Z2ZZlg4dOqTY2NhfHNfiw07Hjh3VqlUrVVZWerRXVlbK4XCc8ZiQkBCFhIR4tEVERDRViS2e3W7nPy4DMa/mYm7Nxdye7pfu6JzS4hcoBwcHq2/fviooKHC31dfXq6CgQElJST6sDAAA+IMWf2dHkjIyMjR69Gj169dP/fv3V05Ojo4cOaIxY8b4ujQAAOBjRoSdO++8Uz/++KOmTZsmp9OpK6+8Uvn5+actWsb5CQkJ0fTp00975IeWjXk1F3NrLub2wtiss31eCwAAoAVr8Wt2AAAAfglhBwAAGI2wAwAAjEbYAQAARiPsGGTDhg0aNmyYYmNjZbPZtHLlSo/+v/71r7rpppvc3xS9ffv2086xaNEiDRo0SHa7XTabTVVVVaeNOXDggNLS0mS32xUREaGxY8fq8OHDHmM+//xz/b//9/8UGhqquLg4zZkzx4tX+utztrm1LEvTpk1TTEyMwsLClJycrLKyMo8x3pq3d955R926dVNoaKh69eqlv/3tb16/3l+b3Nxcde7cWaGhoUpMTNTmzZvdfTU1NRo/frw6dOigtm3bauTIkad9ieru3buVmpqq1q1bKyoqSpMnT9aJEyc8xqxbt05XXXWVQkJC9Jvf/EZLliw5rzrQOE09txUVFbr77rt1+eWXKyAgQBMnTjythh07dmjkyJHq3LmzbDabcnJymupy/RZhxyBHjhxR7969lZub22D/wIEDNXv27AbPcfToUQ0ZMkT/9V//1eCYtLQ07dixQ6tXr9aqVau0YcMGpaenu/tdLpduuukmXXLJJSouLtbzzz+vp59+WosWLWr8xf3KnW1u58yZo/nz5ysvL09FRUVq06aNUlJSVFNT4x7jjXnbuHGj7rrrLo0dO1YlJSUaPny4hg8fri+//LLpLt5wb7/9tjIyMjR9+nRt27ZNvXv3VkpKivbt2ydJmjRpkt5//3298847Wr9+vfbu3asRI0a4jz958qRSU1N1/Phxbdy4UUuXLtWSJUs0bdo095jy8nKlpqbqhhtu0Pbt2zVx4kQ98MAD+vDDD8+5Dpy/5pjb2tpaderUSU899ZR69+59xjqOHj2qSy+9VLNmzWrwlwWMZ8FIkqwVK1acsa+8vNySZJWUlDR4/Nq1ay1J1sGDBz3av/rqK0uStWXLFnfb3//+d8tms1k//PCDZVmW9fLLL1vt27e3amtr3WOmTJlide3atdHXg//fv89tfX295XA4rOeff97dVlVVZYWEhFhvvvmmZVnem7c77rjDSk1N9agnMTHRevDBB716jb8m/fv3t8aPH+/eP3nypBUbG2tlZ2dbVVVVVlBQkPXOO++4+3fu3GlJsgoLCy3Lsqy//e1vVkBAgOV0Ot1jFi5caNntdvdcPvnkk1aPHj08XvfOO++0UlJSzqkONE5zzO3PXX/99dbjjz/+izVdcskl1rx58y7swlog7uzgvBQWFioiIkL9+vVztyUnJysgIEBFRUXuMdddd52Cg4PdY1JSUlRaWqqDBw82e82mKy8vl9PpVHJysrstPDxciYmJKiwslOS9eSssLPR4nVNjTr0Ozs/x48dVXFzs8Z4GBAQoOTlZhYWFKi4uVl1dnUd/t27dFB8f7zG3vXr18vgS1ZSUFLlcLu3YscM95pfm7Wx14Pw119zi3BB2cF6cTqeioqI82gIDAxUZGSmn0+ke8+/fXn1q/9QYeM+p9/RM7/nP58Qb89bQGOa1cX766SedPHmywffU6XQqODj4tB8q/ve5bey8uVwuHTt27Kx14Pw119zi3BB2AACA0Qg7OC8Oh+O0BYsnTpzQgQMH3AvfHA7HaZ8oOLX/q10c14ROvadnes9/PifemLeGxjCvjdOxY0e1atWqwffU4XDo+PHjp30q8t/ntrHzZrfbFRYWdtY6cP6aa25xbgg7OC9JSUmqqqpScXGxu23NmjWqr69XYmKie8yGDRtUV1fnHrN69Wp17dpV7du3b/aaTZeQkCCHw6GCggJ3m8vlUlFRkZKSkiR5b96SkpI8XufUmFOvg/MTHBysvn37eryn9fX1KigoUFJSkvr27augoCCP/tLSUu3evdtjbr/44guPMLt69WrZ7XZdccUV7jG/NG9nqwPnr7nmFufI1yuk4T2HDh2ySkpKrJKSEkuS9eKLL1olJSXWrl27LMuyrP3791slJSXWBx98YEmy3nrrLaukpMSqqKhwn6OiosIqKSmxXn31VUuStWHDBqukpMTav3+/e8yQIUOsPn36WEVFRdYnn3xidenSxbrrrrvc/VVVVVZ0dLR1zz33WF9++aX11ltvWa1bt7ZeeeWV5nszDHO2uZ01a5YVERFhvfvuu9bnn39u3XrrrVZCQoJ17Ngx9zm8MW+ffvqpFRgYaM2dO9fauXOnNX36dCsoKMj64osvmu/NMMxbb71lhYSEWEuWLLG++uorKz093YqIiHB/Auehhx6y4uPjrTVr1lhbt261kpKSrKSkJPfxJ06csHr27GnddNNN1vbt2638/HyrU6dOVmZmpnvMd999Z7Vu3dqaPHmytXPnTis3N9dq1aqVlZ+ff8514Pw1x9xaluX+u6Fv377W3XffbZWUlFg7duxw99fW1rrHxMTEWH/4wx+skpISq6ysrHneCD9A2DHIqY+L//s2evRoy7Isa/HixWfsnz59uvsc06dPP+OYxYsXu8fs37/fuuuuu6y2bdtadrvdGjNmjHXo0CGPWj777DNr4MCBVkhIiHXRRRdZs2bNaoZ3wFxnm9v6+norKyvLio6OtkJCQqwbb7zRKi0t9TiHt+btL3/5i3X55ZdbwcHBVo8ePawPPvigya7712LBggVWfHy8FRwcbPXv39/atGmTu+/YsWPWI488YrVv395q3bq1ddttt3n8D4plWdb3339vDR061AoLC7M6duxoPfHEE1ZdXZ3HmLVr11pXXnmlFRwcbF166aUe/02fSx1onOaY2zP93XDJJZe4+0993ci/b9dff31TXrpfsVmWZTX13SMAAABfYc0OAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg6AJjdo0CBNnDixwf7OnTsrJyenWWr5/vvvZbPZtH379mZ5PQC+R9gB4HNbtmxRenq6e99ms2nlypXnfPySJUsUERFxTmPj4uJUUVGhnj17nmeVAFqqQF8XAACdOnVqltc5fvy4goOD5XA4muX1APgH7uwAaBYnTpzQhAkTFB4ero4dOyorK0unfprv54+xOnfuLEm67bbbZLPZ3PufffaZbrjhBrVr1052u119+/bV1q1btW7dOo0ZM0bV1dWy2Wyy2Wx6+umn3ed69tlnde+998putys9Pf20x1jr1q2TzWZTQUGB+vXrp9atW2vAgAEqLS31qH/mzJmKiopSu3bt9MADD2jq1Km68sor3f3r1q1T//791aZNG0VEROjaa6/Vrl27murtBHAeCDsAmsXSpUsVGBiozZs366WXXtKLL76o//7v/z5t3JYtWyRJixcvVkVFhXs/LS1NF198sbZs2aLi4mJNnTpVQUFBGjBggHJycmS321VRUaGKigr94Q9/cJ9v7ty56t27t0pKSpSVldVgfX/84x/1wgsvaOvWrQoMDNT999/v7lu2bJmee+45zZ49W8XFxYqPj9fChQvd/SdOnNDw4cN1/fXX6/PPP1dhYaHS09Nls9ku+H0DcOF4jAWgWcTFxWnevHmy2Wzq2rWrvvjiC82bN0/jxo3zGHfqkVZERITH46bdu3dr8uTJ6tatmySpS5cu7r7w8HDZbLYzPp4aPHiwnnjiCff+999/f8b6nnvuOV1//fWSpKlTpyo1NVU1NTUKDQ3VggULNHbsWI0ZM0aSNG3aNH300Uc6fPiwJMnlcqm6ulq33HKLLrvsMklS9+7dz+v9AdB0uLMDoFlcc801Hnc6kpKSVFZWppMnT57T8RkZGXrggQeUnJysWbNm6Z///Oc5HdevX79zGvfb3/7W/eeYmBhJ0r59+yRJpaWl6t+/v8f4n+9HRkbqvvvuU0pKioYNG6aXXnpJFRUV5/S6AJoeYQdAi/D0009rx44dSk1N1Zo1a3TFFVdoxYoVZz2uTZs253T+oKAg959PhbL6+vpzrm/x4sUqLCzUgAED9Pbbb+vyyy/Xpk2bzvl4AE2HsAOgWRQVFXnsb9q0SV26dFGrVq1OGxsUFHTGOz6XX365Jk2apI8++kgjRozQ4sWLJUnBwcHnfIeoMbp27epeO3TKv+9LUp8+fZSZmamNGzeqZ8+eWr58eZPVBODcEXYANIvdu3crIyNDpaWlevPNN7VgwQI9/vjjZxzbuXNnFRQUyOl06uDBgzp27JgmTJigdevWadeuXfr000+1ZcsW97qYzp076/DhwyooKNBPP/2ko0ePerX2Rx99VK+99pqWLl2qsrIyzZw5U59//rn7DlB5ebkyMzNVWFioXbt26aOPPlJZWRnrdgA/wQJlAM3i3nvv1bFjx9S/f3+1atVKjz/+uMcXCf7cCy+8oIyMDL366qu66KKL9M0332j//v269957VVlZqY4dO2rEiBF65plnJEkDBgzQQw89pDvvvFP79+/X9OnT3R8/94a0tDR99913+sMf/qCamhrdcccduu+++7R582ZJUuvWrfX1119r6dKl2r9/v2JiYjR+/Hg9+OCDXqsBQOPZrFNfdAEAOGe/+93v5HA49D//8z++LgXAWXBnBwDO4ujRo8rLy1NKSopatWqlN998Ux9//LFWr17t69IAnAPu7ADAWRw7dkzDhg1TSUmJampq1LVrVz311FMaMWKEr0sDcA4IOwAAwGh8GgsAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMNr/B8Rx0ZpV4J3NAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] }, "metadata": {}, "output_type": "display_data" @@ -165,7 +167,7 @@ "source": [ "device = LocalSimulator()\n", "result = device.run(conditioned_bell, shots=500).result()\n", - "counts = Counter(result.measurements[\"c\"])\n", + "counts = Counter(result.measurements[\"return_value\"])\n", "print(\"measurement counts: \", counts)\n", "\n", "plt.bar(counts.keys(), counts.values())\n", @@ -266,7 +268,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.11.4" } }, "nbformat": 4, diff --git a/examples/autoqasm/3_1_Iterative_phase_estimation.ipynb b/examples/autoqasm/3_1_Iterative_phase_estimation.ipynb index 37a5b590..9fbae181 100644 --- a/examples/autoqasm/3_1_Iterative_phase_estimation.ipynb +++ b/examples/autoqasm/3_1_Iterative_phase_estimation.ipynb @@ -117,7 +117,9 @@ " c = measure(q_ancilla)\n", " b0[iteration] = c\n", "\n", - " reset(q_ancilla)" + " reset(q_ancilla)\n", + "\n", + " return b0" ] }, { @@ -143,8 +145,10 @@ }, { "data": { - "text/plain": "
", - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGwCAYAAABPSaTdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAiuUlEQVR4nO3df1RUdf7H8dcNdQIXxvzBjCQF7lJmmLaaJtVKpliZres5ZemW5s+WtIhM5WslehTSjsqunOzHburWUu3ZzWpPrQtZUoYmkmRaaT9QaYUoxQEVwR/3+0fHOc6iZcPADJ+ej3PmHO/n3rm8x394njt3GMu2bVsAAACGOi/YAwAAADQnYgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARmsT7AFCwcmTJ7Vv3z5FRkbKsqxgjwMAAM6Bbduqra1VTEyMzjvv7NdviB1J+/btU2xsbLDHAAAAfigvL1e3bt3Oup/YkRQZGSnp+/+sqKioIE8DAADORU1NjWJjY72/x8+G2JG8b11FRUUROwAAtDI/dgsKNygDAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwWlBj591339WIESMUExMjy7L06quv+uy3bVuZmZmKiYlReHi4kpOTtWPHDp9j6uvrNX36dHXu3Fnt27fXrbfeqq+//roFXwUAAAhlQY2dw4cPq3fv3srNzT3j/sWLF2vp0qXKzc1VcXGx3G63hg4dqtraWu8xaWlpWrNmjV566SVt2LBBhw4d0i233KITJ0601MsAAAAhzLJt2w72ENL331i6Zs0ajRw5UtL3V3ViYmKUlpamWbNmSfr+Ko7L5dKiRYs0depUeTwedenSRc8//7xGjx4tSdq3b59iY2P15ptvatiwYef0s2tqauR0OuXxePjWcwAAWolz/f0dsvfslJWVqbKyUikpKd41h8OhQYMGqaioSJJUUlKiY8eO+RwTExOjxMRE7zFnUl9fr5qaGp8HAAAwU5tgD3A2lZWVkiSXy+Wz7nK5tGfPHu8x7dq10wUXXNDomFPPP5Ps7GzNmzcvwBOfWdzsN1rk5wAAEKp2Pz48qD8/ZK/snGJZls+2bduN1v7Xjx2TkZEhj8fjfZSXlwdkVgAAEHpCNnbcbrckNbpCU1VV5b3a43a71dDQoOrq6rMecyYOh0NRUVE+DwAAYKaQjZ34+Hi53W4VFBR41xoaGlRYWKikpCRJUt++fdW2bVufYyoqKrR9+3bvMQAA4OctqPfsHDp0SF988YV3u6ysTKWlperYsaMuuugipaWlKSsrSwkJCUpISFBWVpYiIiI0ZswYSZLT6dTEiRP10EMPqVOnTurYsaNmzJihXr16aciQIcF6WQAAIIQENXa2bNmi66+/3rudnp4uSRo3bpxWrVqlmTNnqq6uTqmpqaqurtaAAQOUn5+vyMhI73OWLVumNm3a6Pbbb1ddXZ1uuOEGrVq1SmFhYS3+egAAQOgJmb+zE0zN+Xd2+DQWAODnrrk+jdXq/84OAABAIBA7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjBbSsXP8+HE98sgjio+PV3h4uLp376758+fr5MmT3mNs21ZmZqZiYmIUHh6u5ORk7dixI4hTAwCAUBLSsbNo0SI99dRTys3N1aeffqrFixfriSee0PLly73HLF68WEuXLlVubq6Ki4vldrs1dOhQ1dbWBnFyAAAQKtoEe4AfsnHjRv32t7/V8OHDJUlxcXF68cUXtWXLFknfX9XJycnRnDlzNGrUKEnS6tWr5XK5lJeXp6lTp57xvPX19aqvr/du19TUNPMrAQAAwRLSV3auvfZarVu3Trt27ZIkffTRR9qwYYNuvvlmSVJZWZkqKyuVkpLifY7D4dCgQYNUVFR01vNmZ2fL6XR6H7Gxsc37QgAAQNCE9JWdWbNmyePxqEePHgoLC9OJEye0cOFC3XnnnZKkyspKSZLL5fJ5nsvl0p49e8563oyMDKWnp3u3a2pqCB4AAAwV0rHz8ssv64UXXlBeXp4uv/xylZaWKi0tTTExMRo3bpz3OMuyfJ5n23ajtdM5HA45HI5mmxsAAISOkI6dhx9+WLNnz9Ydd9whSerVq5f27Nmj7OxsjRs3Tm63W9L3V3i6du3qfV5VVVWjqz0AAODnKaTv2Tly5IjOO893xLCwMO9Hz+Pj4+V2u1VQUODd39DQoMLCQiUlJbXorAAAIDSF9JWdESNGaOHChbrooot0+eWXa+vWrVq6dKkmTJgg6fu3r9LS0pSVlaWEhAQlJCQoKytLERERGjNmTJCnBwAAoSCkY2f58uV69NFHlZqaqqqqKsXExGjq1Kl67LHHvMfMnDlTdXV1Sk1NVXV1tQYMGKD8/HxFRkYGcXIAABAqLNu27WAPEWw1NTVyOp3yeDyKiooK6LnjZr8R0PMBANDa7H58eLOc91x/f4f0PTsAAABNRewAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaCEfO//973/1+9//Xp06dVJERIT69OmjkpIS737btpWZmamYmBiFh4crOTlZO3bsCOLEAAAglIR07FRXV+uaa65R27Zt9e9//1uffPKJlixZog4dOniPWbx4sZYuXarc3FwVFxfL7XZr6NChqq2tDd7gAAAgZLQJ9gA/ZNGiRYqNjdXKlSu9a3Fxcd5/27atnJwczZkzR6NGjZIkrV69Wi6XS3l5eZo6dWpLjwwAAEJMSF/Zef3119WvXz/ddtttio6O1pVXXqlnn33Wu7+srEyVlZVKSUnxrjkcDg0aNEhFRUVnPW99fb1qamp8HgAAwEwhHTtfffWVVqxYoYSEBP3nP//Rvffeq/vvv19//etfJUmVlZWSJJfL5fM8l8vl3Xcm2dnZcjqd3kdsbGzzvQgAABBUIR07J0+e1K9//WtlZWXpyiuv1NSpUzV58mStWLHC5zjLsny2bdtutHa6jIwMeTwe76O8vLxZ5gcAAMEX0rHTtWtX9ezZ02ftsssu0969eyVJbrdbkhpdxamqqmp0ted0DodDUVFRPg8AAGCmkI6da665Rjt37vRZ27Vrly6++GJJUnx8vNxutwoKCrz7GxoaVFhYqKSkpBadFQAAhKaQ/jTWgw8+qKSkJGVlZen222/X5s2b9cwzz+iZZ56R9P3bV2lpacrKylJCQoISEhKUlZWliIgIjRkzJsjTAwCAUBDSsXPVVVdpzZo1ysjI0Pz58xUfH6+cnByNHTvWe8zMmTNVV1en1NRUVVdXa8CAAcrPz1dkZGQQJwcAAKHCsm3b/qlPKi8vl2VZ6tatmyRp8+bNysvLU8+ePTVlypSAD9ncampq5HQ65fF4An7/TtzsNwJ6PgAAWpvdjw9vlvOe6+9vv+7ZGTNmjN555x1J398cPHToUG3evFn/93//p/nz5/s3MQAAQDPwK3a2b9+u/v37S5L+/ve/KzExUUVFRcrLy9OqVasCOR8AAECT+BU7x44dk8PhkCS99dZbuvXWWyVJPXr0UEVFReCmAwAAaCK/Yufyyy/XU089pffee08FBQW68cYbJUn79u1Tp06dAjogAABAU/gVO4sWLdLTTz+t5ORk3Xnnnerdu7ek77/L6tTbWwAAAKHAr4+eJycn67vvvlNNTY0uuOAC7/qUKVPUvn37gA0HAADQVH5d2Rk8eLBqa2t9QkeSOnbsqNGjRwdkMAAAgEDwK3bWr1+vhoaGRutHjx7Ve++91+ShAAAAAuUnvY21bds2778/+eQTny/gPHHihNauXasLL7wwcNMBAAA00U+KnT59+siyLFmWpcGDBzfaHx4eruXLlwdsOAAAgKb6SbFTVlYm27bVvXt3bd68WV26dPHua9eunaKjoxUWFhbwIQEAAPz1k2Ln4osvliSdPHmyWYYBAAAINL+/9XzXrl1av369qqqqGsXPY4891uTBAAAAAsGv2Hn22Wf1hz/8QZ07d5bb7ZZlWd59lmUROwAAIGT4FTsLFizQwoULNWvWrEDPAwAAEFB+/Z2d6upq3XbbbYGeBQAAIOD8ip3bbrtN+fn5gZ4FAAAg4Px6G+tXv/qVHn30UW3atEm9evVS27Ztffbff//9ARkOAACgqSzbtu2f+qT4+Pizn9Cy9NVXXzVpqJZWU1Mjp9Mpj8ejqKiogJ47bvYbAT0fAACtze7HhzfLec/197dfV3bKysr8HgwAAKAl+XXPDgAAQGvh15WdCRMm/OD+5557zq9hAAAAAs2v2KmurvbZPnbsmLZv366DBw+e8QtCAQAAgsWv2FmzZk2jtZMnTyo1NVXdu3dv8lAAAACBErB7ds477zw9+OCDWrZsWaBOCQAA0GQBvUH5yy+/1PHjxwN5SgAAgCbx622s9PR0n23btlVRUaE33nhD48aNC8hgAAAAgeBX7GzdutVn+7zzzlOXLl20ZMmSH/2kFgAAQEvyK3beeeedQM8BAADQLPyKnVO+/fZb7dy5U5Zl6ZJLLlGXLl0CNRcAAEBA+HWD8uHDhzVhwgR17dpVv/nNb3TdddcpJiZGEydO1JEjRwI9IwAAgN/8ip309HQVFhbqX//6lw4ePKiDBw/qtddeU2FhoR566KFAzwgAAOA3v97G+uc//6l//OMfSk5O9q7dfPPNCg8P1+23364VK1YEaj4AAIAm8evKzpEjR+RyuRqtR0dH8zYWAAAIKX7FzsCBAzV37lwdPXrUu1ZXV6d58+Zp4MCBARsOAACgqfx6GysnJ0c33XSTunXrpt69e8uyLJWWlsrhcCg/Pz/QMwIAAPjNr9jp1auXPv/8c73wwgv67LPPZNu27rjjDo0dO1bh4eGBnhEAAMBvfsVOdna2XC6XJk+e7LP+3HPP6dtvv9WsWbMCMhwAAEBT+XXPztNPP60ePXo0Wr/88sv11FNPNXkoAACAQPErdiorK9W1a9dG6126dFFFRUWThwIAAAgUv2InNjZW77//fqP1999/XzExMU0eCgAAIFD8umdn0qRJSktL07FjxzR48GBJ0rp16zRz5kz+gjIAAAgpfsXOzJkzdeDAAaWmpqqhoUGSdP7552vWrFnKyMgI6IAAAABN4VfsWJalRYsW6dFHH9Wnn36q8PBwJSQkyOFwBHo+AACAJvErdk75xS9+oauuuipQswAAAAScXzcoAwAAtBbEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMFqrip3s7GxZlqW0tDTvmm3byszMVExMjMLDw5WcnKwdO3YEb0gAABBSWk3sFBcX65lnntEVV1zhs7548WItXbpUubm5Ki4ultvt1tChQ1VbWxukSQEAQChpFbFz6NAhjR07Vs8++6wuuOAC77pt28rJydGcOXM0atQoJSYmavXq1Tpy5Ijy8vKCODEAAAgVrSJ27rvvPg0fPlxDhgzxWS8rK1NlZaVSUlK8aw6HQ4MGDVJRUdFZz1dfX6+amhqfBwAAMFObYA/wY1566SV9+OGHKi4ubrSvsrJSkuRyuXzWXS6X9uzZc9ZzZmdna968eYEdFAAAhKSQvrJTXl6uBx54QC+88ILOP//8sx5nWZbPtm3bjdZOl5GRIY/H432Ul5cHbGYAABBaQvrKTklJiaqqqtS3b1/v2okTJ/Tuu+8qNzdXO3fulPT9FZ6uXbt6j6mqqmp0ted0DodDDoej+QYHAAAhI6Sv7Nxwww36+OOPVVpa6n3069dPY8eOVWlpqbp37y63262CggLvcxoaGlRYWKikpKQgTg4AAEJFSF/ZiYyMVGJios9a+/bt1alTJ+96WlqasrKylJCQoISEBGVlZSkiIkJjxowJxsgAACDEhHTsnIuZM2eqrq5Oqampqq6u1oABA5Sfn6/IyMhgjwYAAEKAZdu2Hewhgq2mpkZOp1Mej0dRUVEBPXfc7DcCej4AAFqb3Y8Pb5bznuvv75C+ZwcAAKCpiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGC+nYyc7O1lVXXaXIyEhFR0dr5MiR2rlzp88xtm0rMzNTMTExCg8PV3Jysnbs2BGkiQEAQKgJ6dgpLCzUfffdp02bNqmgoEDHjx9XSkqKDh8+7D1m8eLFWrp0qXJzc1VcXCy3262hQ4eqtrY2iJMDAIBQ0SbYA/yQtWvX+myvXLlS0dHRKikp0W9+8xvZtq2cnBzNmTNHo0aNkiStXr1aLpdLeXl5mjp16hnPW19fr/r6eu92TU1N870IAAAQVCF9Zed/eTweSVLHjh0lSWVlZaqsrFRKSor3GIfDoUGDBqmoqOis58nOzpbT6fQ+YmNjm3dwAAAQNK0mdmzbVnp6uq699lolJiZKkiorKyVJLpfL51iXy+XddyYZGRnyeDzeR3l5efMNDgAAgiqk38Y63bRp07Rt2zZt2LCh0T7Lsny2bdtutHY6h8Mhh8MR8BkBAEDoaRVXdqZPn67XX39d77zzjrp16+Zdd7vdktToKk5VVVWjqz0AAODnKaRjx7ZtTZs2Ta+88orefvttxcfH++yPj4+X2+1WQUGBd62hoUGFhYVKSkpq6XEBAEAICum3se677z7l5eXptddeU2RkpPcKjtPpVHh4uCzLUlpamrKyspSQkKCEhARlZWUpIiJCY8aMCfL0AAAgFIR07KxYsUKSlJyc7LO+cuVKjR8/XpI0c+ZM1dXVKTU1VdXV1RowYIDy8/MVGRnZwtMCAIBQFNKxY9v2jx5jWZYyMzOVmZnZ/AMBAIBWJ6Tv2QEAAGgqYgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGMyZ2nnzyScXHx+v8889X37599d577wV7JAAAEAKMiJ2XX35ZaWlpmjNnjrZu3arrrrtON910k/bu3Rvs0QAAQJAZETtLly7VxIkTNWnSJF122WXKyclRbGysVqxYEezRAABAkLUJ9gBN1dDQoJKSEs2ePdtnPSUlRUVFRWd8Tn19verr673bHo9HklRTUxPw+U7WHwn4OQEAaE2a4/fr6ee1bfsHj2v1sfPdd9/pxIkTcrlcPusul0uVlZVnfE52drbmzZvXaD02NrZZZgQA4OfMmdO856+trZXT6Tzr/lYfO6dYluWzbdt2o7VTMjIylJ6e7t0+efKkDhw4oE6dOp31OQBap5qaGsXGxqq8vFxRUVHBHgdAANm2rdraWsXExPzgca0+djp37qywsLBGV3GqqqoaXe05xeFwyOFw+Kx16NChuUYEEAKioqKIHcBAP3RF55RWf4Nyu3bt1LdvXxUUFPisFxQUKCkpKUhTAQCAUNHqr+xIUnp6uu666y7169dPAwcO1DPPPKO9e/fq3nvvDfZoAAAgyIyIndGjR2v//v2aP3++KioqlJiYqDfffFMXX3xxsEcDEGQOh0Nz585t9NY1gJ8Py/6xz2sBAAC0Yq3+nh0AAIAfQuwAAACjETsAAMBoxA4AADAasQMg5L377rsaMWKEYmJiZFmWXn31VZ/9r7zyioYNG6bOnTvLsiyVlpY2Okd9fb2mT5+uzp07q3379rr11lv19ddf+xxTXV2tu+66S06nU06nU3fddZcOHjzYfC8MQIsgdgCEvMOHD6t3797Kzc096/5rrrlGjz/++FnPkZaWpjVr1uill17Shg0bdOjQId1yyy06ceKE95gxY8aotLRUa9eu1dq1a1VaWqq77ror4K8HQMvio+cAWhXLsrRmzRqNHDmy0b7du3crPj5eW7duVZ8+fbzrHo9HXbp00fPPP6/Ro0dLkvbt26fY2Fi9+eabGjZsmD799FP17NlTmzZt0oABAyRJmzZt0sCBA/XZZ5/p0ksvbYmXB6AZcGUHgPFKSkp07NgxpaSkeNdiYmKUmJiooqIiSdLGjRvldDq9oSNJV199tZxOp/cYAK0TsQPAeJWVlWrXrp0uuOACn3WXy+X9EuHKykpFR0c3em50dHSjLxoG0LoQOwB+tmzblmVZ3u3T/322YwC0PsQOAOO53W41NDSourraZ72qqkoul8t7zDfffNPoud9++633GACtE7EDwHh9+/ZV27ZtVVBQ4F2rqKjQ9u3blZSUJEkaOHCgPB6PNm/e7D3mgw8+kMfj8R4DoHUy4lvPAZjt0KFD+uKLL7zbZWVlKi0tVceOHXXRRRfpwIED2rt3r/bt2ydJ2rlzp6Tvr9a43W45nU5NnDhRDz30kDp16qSOHTtqxowZ6tWrl4YMGSJJuuyyy3TjjTdq8uTJevrppyVJU6ZM0S233MInsYBWjo+eAwh569ev1/XXX99ofdy4cVq1apVWrVqle+65p9H+uXPnKjMzU5J09OhRPfzww8rLy1NdXZ1uuOEGPfnkk4qNjfUef+DAAd1///16/fXXJUm33nqrcnNz1aFDh2Z5XQBaBrEDAACMxj07AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwBaRHJystLS0s66Py4uTjk5OS0yy+7du2VZlkpLS1vk5wEILmIHQEgoLi7WlClTvNuWZenVV1895+evWrXqnL/WITY2VhUVFUpMTPyJUwJojfgiUAAhoUuXLi3ycxoaGtSuXTu53e4W+XkAgo8rOwBazPHjxzVt2jR16NBBnTp10iOPPKJTX893+ttYcXFxkqTf/e53sizLu/3RRx/p+uuvV2RkpKKiotS3b19t2bJF69ev1z333COPxyPLsmRZlvcLQOPi4rRgwQKNHz9eTqdTkydPbvQ21vr162VZltatW6d+/fopIiJCSUlJ3m9PP2XBggWKjo5WZGSkJk2apNmzZ6tPnz7e/evXr1f//v3Vvn17dejQQddcc4327NnTXP+dAM4RsQOgxaxevVpt2rTRBx98oD/96U9atmyZ/vznPzc6rri4WJK0cuVKVVRUeLfHjh2rbt26qbi4WCUlJZo9e7batm2rpKQk5eTkKCoqShUVFaqoqNCMGTO853viiSeUmJiokpISPfroo2edb86cOVqyZIm2bNmiNm3aaMKECd59f/vb37Rw4UItWrRIJSUluuiii7RixQrv/uPHj2vkyJEaNGiQtm3bpo0bN2rKlCmyLKvJ/28Amoa3sQC0mNjYWC1btkyWZenSSy/Vxx9/rGXLlmny5Mk+x516S6tDhw4+bzft3btXDz/8sHr06CFJSkhI8O5zOp2yLOuMb08NHjzYJ3527959xvkWLlyoQYMGSZJmz56t4cOH6+jRozr//PO1fPlyTZw4Uffcc48k6bHHHlN+fr4OHTokSaqpqZHH49Ett9yiX/7yl5Kkyy677Cf9/wBoHlzZAdBirr76ap8rHQMHDtTnn3+uEydOnNPz09PTNWnSJA0ZMkSPP/64vvzyy3N6Xr9+/c7puCuuuML7765du0qSqqqqJEk7d+5U//79fY4/fbtjx44aP368hg0bphEjRuiPf/yjKioqzunnAmhexA6AViMzM1M7duzQ8OHD9fbbb6tnz55as2bNjz6vffv253T+tm3bev99KspOnjzZaO2UU/cbnbJy5Upt3LhRSUlJevnll3XJJZdo06ZN5/SzATQfYgdAi/nfX/ybNm1SQkKCwsLCGh3btm3bM17xueSSS/Tggw8qPz9fo0aN0sqVKyVJ7dq1O+crRP649NJLtXnzZp+1LVu2NDruyiuvVEZGhoqKipSYmKi8vLxmmwnAuSF2ALSY8vJypaena+fOnXrxxRe1fPlyPfDAA2c8Ni4uTuvWrVNlZaWqq6tVV1enadOmaf369dqzZ4/ef/99FRcXe++LiYuL06FDh7Ru3Tp99913OnLkSEBnnz59uv7yl79o9erV+vzzz7VgwQJt27bNe7WnrKxMGRkZ2rhxo/bs2aP8/Hzt2rWL+3aAEMANygBazN133626ujr1799fYWFhmj59us8fEjzdkiVLlJ6ermeffVYXXnihdu3apf379+vuu+/WN998o86dO2vUqFGaN2+eJCkpKUn33nuvRo8erf3792vu3Lnej58HwtixY/XVV19pxowZOnr0qG6//XaNHz/ee7UnIiJCn332mVavXq39+/era9eumjZtmqZOnRqwGQD4x7L/901nAMA5GTp0qNxut55//vlgjwLgB3BlBwDOwZEjR/TUU09p2LBhCgsL04svvqi33npLBQUFwR4NwI/gyg4AnIO6ujqNGDFCH374oerr63XppZfqkUce0ahRo4I9GoAfQewAAACj8WksAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNH+H35v7XyPK5xOAAAAAElFTkSuQmCC" + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGwCAYAAABPSaTdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/SrBM8AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAie0lEQVR4nO3df1RUdf7H8dcgiojOGP4YIDHY1sRfaWkq6ikzijZzc3UrW3czNWkNLcT8wUl0LQ31rD/SNSm3xPaoW+2uttbJ1UjthwhK2m+JCpX96qCpzCgKosz3j45zmlXLhoEZPj0f58w53c+9c3njPzy7c2fG4na73QIAADBUSKAHAAAAqEvEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMFhroAYJBTU2NDh06pBYtWshisQR6HAAAcAXcbrdOnjypmJgYhYRc/voNsSPp0KFDio2NDfQYAADAB6WlpWrXrt1l9xM7klq0aCHpu38sq9Ua4GkAAMCVcLlcio2N9fwdvxxiR/K8dGW1WokdAAAamB+7BYUblAEAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABgtoLHz7rvvasiQIYqJiZHFYtGGDRu89rvdbs2cOVPR0dEKDw9XUlKSiouLvY45fvy4Ro4cKavVqpYtW2rs2LE6depUPf4WAAAgmAU0dioqKtS9e3ctX778kvsXLFigpUuXKjs7W/n5+YqIiFBycrIqKys9x4wcOVKfffaZtmzZojfeeEPvvvuuUlJS6utXAAAAQc7idrvdgR5C+u4bS9evX6+hQ4dK+u6qTkxMjCZPnqwnnnhCkuR0OmW325WTk6MRI0boiy++UOfOnbVr1y716tVLkrRp0ybddddd+u9//6uYmJgr+tkul0s2m01Op5NvPQcAoIG40r/fQXvPTklJiRwOh5KSkjxrNptNffr0UV5eniQpLy9PLVu29ISOJCUlJSkkJET5+fmXPXdVVZVcLpfXAwAAmCk00ANcjsPhkCTZ7Xavdbvd7tnncDjUtm1br/2hoaGKjIz0HHMpWVlZmj17tp8nvrS46W/Wy88BACBY7Z83OKA/P2iv7NSljIwMOZ1Oz6O0tDTQIwEAgDoStLETFRUlSSorK/NaLysr8+yLiorSkSNHvPafO3dOx48f9xxzKWFhYbJarV4PAABgpqCNnfj4eEVFRSk3N9ez5nK5lJ+fr8TERElSYmKiysvLVVhY6DnmnXfeUU1Njfr06VPvMwMAgOAT0Ht2Tp06pa+++sqzXVJSor179yoyMlLt27dXWlqa5syZow4dOig+Pl6ZmZmKiYnxvGOrU6dOuvPOOzVu3DhlZ2erurpaEyZM0IgRI674nVgAAMBsAY2d3bt369Zbb/Vsp6enS5JGjRqlnJwcTZ06VRUVFUpJSVF5ebkGDBigTZs2qWnTpp7nrFmzRhMmTNBtt92mkJAQDR8+XEuXLq333wUAAASnoPmcnUCqy8/Z4d1YAICfu7p6N1aD/5wdAAAAfyB2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGC2oY+f8+fPKzMxUfHy8wsPDde211+rpp5+W2+32HON2uzVz5kxFR0crPDxcSUlJKi4uDuDUAAAgmAR17MyfP18rVqzQX/7yF33xxReaP3++FixYoGXLlnmOWbBggZYuXars7Gzl5+crIiJCycnJqqysDODkAAAgWIQGeoAfsmPHDt1zzz0aPHiwJCkuLk7r1q1TQUGBpO+u6ixZskQzZszQPffcI0l6+eWXZbfbtWHDBo0YMeKS562qqlJVVZVn2+Vy1fFvAgAAAiWor+z069dPubm5+vLLLyVJH330kd5//3396le/kiSVlJTI4XAoKSnJ8xybzaY+ffooLy/vsufNysqSzWbzPGJjY+v2FwEAAAET1Fd2pk+fLpfLpYSEBDVq1Ejnz5/X3LlzNXLkSEmSw+GQJNntdq/n2e12z75LycjIUHp6umfb5XIRPAAAGCqoY+fVV1/VmjVrtHbtWnXp0kV79+5VWlqaYmJiNGrUKJ/PGxYWprCwMD9OCgAAglVQx86UKVM0ffp0z7033bp104EDB5SVlaVRo0YpKipKklRWVqbo6GjP88rKytSjR49AjAwAAIJMUN+zc/r0aYWEeI/YqFEj1dTUSJLi4+MVFRWl3Nxcz36Xy6X8/HwlJibW66wAACA4BfWVnSFDhmju3Llq3769unTpoj179mjRokUaM2aMJMlisSgtLU1z5sxRhw4dFB8fr8zMTMXExGjo0KGBHR4AAASFoI6dZcuWKTMzU48++qiOHDmimJgYPfLII5o5c6bnmKlTp6qiokIpKSkqLy/XgAEDtGnTJjVt2jSAkwMAgGBhcX//44h/plwul2w2m5xOp6xWq1/PHTf9Tb+eDwCAhmb/vMF1ct4r/fsd1PfsAAAA1BaxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMFfez83//9n37/+9+rVatWCg8PV7du3bR7927PfrfbrZkzZyo6Olrh4eFKSkpScXFxACcGAADBJKhj58SJE+rfv78aN26st956S59//rkWLlyoq666ynPMggULtHTpUmVnZys/P18RERFKTk5WZWVlACcHAADBIjTQA/yQ+fPnKzY2VqtWrfKsxcfHe/7b7XZryZIlmjFjhu655x5J0ssvvyy73a4NGzZoxIgR9T4zAAAILkF9Zeff//63evXqpXvvvVdt27bVDTfcoJUrV3r2l5SUyOFwKCkpybNms9nUp08f5eXlXfa8VVVVcrlcXg8AAGCmoI6db775RitWrFCHDh30n//8R+PHj9djjz2m1atXS5IcDockyW63ez3Pbrd79l1KVlaWbDab5xEbG1t3vwQAAAiooI6dmpoa3XjjjXrmmWd0ww03KCUlRePGjVN2dnatzpuRkSGn0+l5lJaW+mliAAAQbII6dqKjo9W5c2evtU6dOungwYOSpKioKElSWVmZ1zFlZWWefZcSFhYmq9Xq9QAAAGYK6tjp37+/ioqKvNa+/PJLXXPNNZK+u1k5KipKubm5nv0ul0v5+flKTEys11kBAEBwCup3Y02aNEn9+vXTM888o/vuu08FBQV64YUX9MILL0iSLBaL0tLSNGfOHHXo0EHx8fHKzMxUTEyMhg4dGtjhAQBAUAjq2Lnpppu0fv16ZWRk6KmnnlJ8fLyWLFmikSNHeo6ZOnWqKioqlJKSovLycg0YMECbNm1S06ZNAzg5AAAIFha32+3+qU8qLS2VxWJRu3btJEkFBQVau3atOnfurJSUFL8PWddcLpdsNpucTqff79+Jm/6mX88HAEBDs3/e4Do575X+/fbpnp3f/e532rp1q6Tv3v59++23q6CgQE8++aSeeuop3yYGAACoAz7FzqeffqrevXtLkl599VV17dpVO3bs0Jo1a5STk+PP+QAAAGrFp9iprq5WWFiYJOntt9/Wr3/9a0lSQkKCDh8+7L/pAAAAasmn2OnSpYuys7P13nvvacuWLbrzzjslSYcOHVKrVq38OiAAAEBt+BQ78+fP1/PPP6+BAwfqgQceUPfu3SV9911WF17eAgAACAY+vfV84MCB+vbbb+VyuXTVVVd51lNSUhQREeG34QAAAGrLpys7gwYN0smTJ71CR5IiIyN1//33+2UwAAAAf/ApdrZt26azZ89etF5ZWan33nuv1kMBAAD4y096Gevjjz/2/Pfnn38uh8Ph2T5//rw2bdqkq6++2n/TAQAA1NJPip0ePXrIYrHIYrFo0KBBF+0PDw/XsmXL/DYcAABAbf2k2CkpKZHb7dYvfvELFRQUqE2bNp59TZo0Udu2bdWoUSO/DwkAAOCrnxQ711xzjSSppqamToYBAADwN5+/9by4uFhbt27VkSNHLoqfmTNn1nowAAAAf/ApdlauXKnx48erdevWioqKksVi8eyzWCzEDgAACBo+xc6cOXM0d+5cTZs2zd/zAAAA+JVPn7Nz4sQJ3Xvvvf6eBQAAwO98ip17771Xmzdv9vcsAAAAfufTy1i//OUvlZmZqZ07d6pbt25q3Lix1/7HHnvML8MBAADUlsXtdrt/6pPi4+Mvf0KLRd98802thqpvLpdLNptNTqdTVqvVr+eOm/6mX88HAEBDs3/e4Do575X+/fbpyk5JSYnPgwEAANQnn+7ZAQAAaCh8urIzZsyYH9z/0ksv+TQMAACAv/kUOydOnPDarq6u1qeffqry8vJLfkEoAABAoPgUO+vXr79oraamRuPHj9e1115b66EAAAD8xW/37ISEhCg9PV2LFy/21ykBAABqza83KH/99dc6d+6cP08JAABQKz69jJWenu617Xa7dfjwYb355psaNWqUXwYDAADwB59iZ8+ePV7bISEhatOmjRYuXPij79QCAACoTz7FztatW/09BwAAQJ3wKXYuOHr0qIqKiiRJHTt2VJs2bfwyFAAAgL/4dINyRUWFxowZo+joaN188826+eabFRMTo7Fjx+r06dP+nhEAAMBnPsVOenq6tm/fro0bN6q8vFzl5eV6/fXXtX37dk2ePNnfMwIAAPjMp5ex/vnPf+of//iHBg4c6Fm76667FB4ervvuu08rVqzw13wAAAC14tOVndOnT8tut1+03rZtW17GAgAAQcWn2ElMTNSsWbNUWVnpWTtz5oxmz56txMREvw0HAABQWz69jLVkyRLdeeedateunbp37y5J+uijjxQWFqbNmzf7dUAAAIDa8Cl2unXrpuLiYq1Zs0b79u2TJD3wwAMaOXKkwsPD/TogAABAbfgUO1lZWbLb7Ro3bpzX+ksvvaSjR49q2rRpfhkOAACgtny6Z+f5559XQkLCRetdunRRdnZ2rYcCAADwF59ix+FwKDo6+qL1Nm3a6PDhw7UeCgAAwF98ip3Y2Fh98MEHF61/8MEHiomJqfVQAAAA/uLTPTvjxo1TWlqaqqurNWjQIElSbm6upk6dyicoAwCAoOJT7EyZMkXHjh3To48+qrNnz0qSmjZtqmnTpikjI8OvAwIAANSGT7FjsVg0f/58ZWZm6osvvlB4eLg6dOigsLAwf88HAABQKz7FzgXNmzfXTTfd5K9ZAAAA/M6nG5QBAAAaCmIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYrUHFzrx582SxWJSWluZZq6ysVGpqqlq1aqXmzZtr+PDhKisrC9yQAAAgqDSY2Nm1a5eef/55XX/99V7rkyZN0saNG/Xaa69p+/btOnTokIYNGxagKQEAQLBpELFz6tQpjRw5UitXrtRVV13lWXc6nXrxxRe1aNEiDRo0SD179tSqVau0Y8cO7dy5M4ATAwCAYNEgYic1NVWDBw9WUlKS13phYaGqq6u91hMSEtS+fXvl5eVd9nxVVVVyuVxeDwAAYKbQQA/wY/7+97/rww8/1K5duy7a53A41KRJE7Vs2dJr3W63y+FwXPacWVlZmj17tr9HBQAAQSior+yUlpbq8ccf15o1a9S0aVO/nTcjI0NOp9PzKC0t9du5AQBAcAnq2CksLNSRI0d04403KjQ0VKGhodq+fbuWLl2q0NBQ2e12nT17VuXl5V7PKysrU1RU1GXPGxYWJqvV6vUAAABmCuqXsW677TZ98sknXmujR49WQkKCpk2bptjYWDVu3Fi5ubkaPny4JKmoqEgHDx5UYmJiIEYGAABBJqhjp0WLFuratavXWkREhFq1auVZHzt2rNLT0xUZGSmr1aqJEycqMTFRffv2DcTIAAAgyAR17FyJxYsXKyQkRMOHD1dVVZWSk5P13HPPBXosAAAQJCxut9sd6CECzeVyyWazyel0+v3+nbjpb/r1fAAANDT75w2uk/Ne6d/voL5BGQAAoLaIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYL6tjJysrSTTfdpBYtWqht27YaOnSoioqKvI6prKxUamqqWrVqpebNm2v48OEqKysL0MQAACDYBHXsbN++Xampqdq5c6e2bNmi6upq3XHHHaqoqPAcM2nSJG3cuFGvvfaatm/frkOHDmnYsGEBnBoAAAST0EAP8EM2bdrktZ2Tk6O2bduqsLBQN998s5xOp1588UWtXbtWgwYNkiStWrVKnTp10s6dO9W3b99LnreqqkpVVVWebZfLVXe/BAAACKigvrLzv5xOpyQpMjJSklRYWKjq6molJSV5jklISFD79u2Vl5d32fNkZWXJZrN5HrGxsXU7OAAACJgGEzs1NTVKS0tT//791bVrV0mSw+FQkyZN1LJlS69j7Xa7HA7HZc+VkZEhp9PpeZSWltbl6AAAIICC+mWs70tNTdWnn36q999/v9bnCgsLU1hYmB+mAgAAwa5BXNmZMGGC3njjDW3dulXt2rXzrEdFRens2bMqLy/3Or6srExRUVH1PCUAAAhGQR07brdbEyZM0Pr16/XOO+8oPj7ea3/Pnj3VuHFj5ebmetaKiop08OBBJSYm1ve4AAAgCAX1y1ipqalau3atXn/9dbVo0cJzH47NZlN4eLhsNpvGjh2r9PR0RUZGymq1auLEiUpMTLzsO7EAAMDPS1DHzooVKyRJAwcO9FpftWqVHnroIUnS4sWLFRISouHDh6uqqkrJycl67rnn6nlSAAAQrII6dtxu948e07RpUy1fvlzLly+vh4kAAEBDE9T37AAAANQWsQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjGRM7y5cvV1xcnJo2bao+ffqooKAg0CMBAIAgYETsvPLKK0pPT9esWbP04Ycfqnv37kpOTtaRI0cCPRoAAAgwI2Jn0aJFGjdunEaPHq3OnTsrOztbzZo100svvRTo0QAAQICFBnqA2jp79qwKCwuVkZHhWQsJCVFSUpLy8vIu+ZyqqipVVVV5tp1OpyTJ5XL5fb6aqtN+PycAAA1JXfx9/f553W73Dx7X4GPn22+/1fnz52W3273W7Xa79u3bd8nnZGVlafbs2Retx8bG1smMAAD8nNmW1O35T548KZvNdtn9DT52fJGRkaH09HTPdk1NjY4fP65WrVrJYrEEcDIA/uZyuRQbG6vS0lJZrdZAjwPAj9xut06ePKmYmJgfPK7Bx07r1q3VqFEjlZWVea2XlZUpKirqks8JCwtTWFiY11rLli3rakQAQcBqtRI7gIF+6IrOBQ3+BuUmTZqoZ8+eys3N9azV1NQoNzdXiYmJAZwMAAAEgwZ/ZUeS0tPTNWrUKPXq1Uu9e/fWkiVLVFFRodGjRwd6NAAAEGBGxM7999+vo0ePaubMmXI4HOrRo4c2bdp00U3LAH5+wsLCNGvWrIteugbw82Fx/9j7tQAAABqwBn/PDgAAwA8hdgAAgNGIHQAAYDRiBwAAGI3YARD03n33XQ0ZMkQxMTGyWCzasGGD1/5//etfuuOOOzyfgr53796LzlFZWanU1FS1atVKzZs31/Dhwy/6MNKDBw9q8ODBatasmdq2baspU6bo3LlzdfibAagPxA6AoFdRUaHu3btr+fLll90/YMAAzZ8//7LnmDRpkjZu3KjXXntN27dv16FDhzRs2DDP/vPnz2vw4ME6e/asduzYodWrVysnJ0czZ870++8DoH7x1nMADYrFYtH69es1dOjQi/bt379f8fHx2rNnj3r06OFZdzqdatOmjdauXavf/va3kqR9+/apU6dOysvLU9++ffXWW2/p7rvv1qFDhzyf0ZWdna1p06bp6NGjatKkSX38egDqAFd2ABivsLBQ1dXVSkpK8qwlJCSoffv2ysvLkyTl5eWpW7duXh9GmpycLJfLpc8++6zeZwbgP8QOAOM5HA41adLkoi/8tdvtcjgcnmP+91PXL2xfOAZAw0TsAAAAoxE7AIwXFRWls2fPqry83Gu9rKxMUVFRnmP+991ZF7YvHAOgYSJ2ABivZ8+eaty4sXJzcz1rRUVFOnjwoBITEyVJiYmJ+uSTT3TkyBHPMVu2bJHValXnzp3rfWYA/mPEt54DMNupU6f01VdfebZLSkq0d+9eRUZGqn379jp+/LgOHjyoQ4cOSfouZKTvrshERUXJZrNp7NixSk9PV2RkpKxWqyZOnKjExET17dtXknTHHXeoc+fO+sMf/qAFCxbI4XBoxowZSk1N5RvTgQaOt54DCHrbtm3TrbfeetH6qFGjlJOTo5ycHI0ePfqi/bNmzdKf/vQnSd99qODkyZO1bt06VVVVKTk5Wc8995zXS1QHDhzQ+PHjtW3bNkVERGjUqFGaN2+eQkP5/0KgISN2AACA0bhnBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgdAnRs4cKDS0tIuuz8uLk5Lliypl1n2798vi8WivXv31svPAxB4xA6AgNu1a5dSUlI82xaLRRs2bLji5+fk5Khly5ZXdGxsbKwOHz6srl27/sQpATRUfOELgIBr06ZNvfycs2fPqkmTJl7fhwXAfFzZAVAvzp07pwkTJshms6l169bKzMzUha/m+/7LWHFxcZKk3/zmN7JYLJ7tjz76SLfeeqtatGghq9Wqnj17avfu3dq2bZtGjx4tp9Mpi8Uii8Xi+fLPuLg4Pf3003rwwQdltVqVkpJy0ctY27Ztk8ViUW5urnr16qVmzZqpX79+nm9Ov2DOnDlq27atWrRooYcffljTp09Xjx49PPu3bdum3r17KyIiQi1btlT//v114MCBuvrnBPATEDsA6sXq1asVGhqqgoICPfvss1q0aJH++te/XnTcrl27JEmrVq3S4cOHPdsjR45Uu3bttGvXLhUWFmr69Olq3Lix+vXrpyVLlshqterw4cM6fPiwnnjiCc/5/vznP6t79+7as2ePMjMzLzvfk08+qYULF2r37t0KDQ3VmDFjPPvWrFmjuXPnav78+SosLFT79u21YsUKz/5z585p6NChuuWWW/Txxx8rLy9PKSkpslgstf53A1B7vIwFoF7ExsZq8eLFslgs6tixoz755BMtXrxY48aN8zruwktaLVu29Hq56eDBg5oyZYoSEhIkSR06dPDss9lsslgsl3x5atCgQZo8ebJne//+/Zecb+7cubrlllskSdOnT9fgwYNVWVmppk2batmyZRo7dqxGjx4tSZo5c6Y2b96sU6dOSZJcLpecTqfuvvtuXXvttZKkTp06/aR/HwB1hys7AOpF3759va50JCYmqri4WOfPn7+i56enp+vhhx9WUlKS5s2bp6+//vqKnterV68rOu7666/3/Hd0dLQk6ciRI5KkoqIi9e7d2+v4729HRkbqoYceUnJysoYMGaJnn31Whw8fvqKfC6DuETsAGoQ//elP+uyzzzR48GC988476ty5s9avX/+jz4uIiLii8zdu3Njz3xeirKam5ornW7VqlfLy8tSvXz+98soruu6667Rz584rfj6AukPsAKgX+fn5Xts7d+5Uhw4d1KhRo4uObdy48SWv+Fx33XWaNGmSNm/erGHDhmnVqlWSpCZNmlzxFSJfdOzY0XPv0AX/uy1JN9xwgzIyMrRjxw517dpVa9eurbOZAFw5YgdAvTh48KDS09NVVFSkdevWadmyZXr88ccveWxcXJxyc3PlcDh04sQJnTlzRhMmTNC2bdt04MABffDBB9q1a5fnvpi4uDidOnVKubm5+vbbb3X69Gm/zj5x4kS9+OKLWr16tYqLizVnzhx9/PHHnitAJSUlysjIUF5eng4cOKDNmzeruLiY+3aAIMENygDqxYMPPqgzZ86od+/eatSokR5//HGvDxL8voULFyo9PV0rV67U1VdfrS+//FLHjh3Tgw8+qLKyMrVu3VrDhg3T7NmzJUn9+vXTH//4R91///06duyYZs2a5Xn7uT+MHDlS33zzjZ544glVVlbqvvvu00MPPaSCggJJUrNmzbRv3z6tXr1ax44dU3R0tFJTU/XII4/4bQYAvrO4L3zQBQDgit1+++2KiorS3/72t0CPAuBHcGUHAH7E6dOnlZ2dreTkZDVq1Ejr1q3T22+/rS1btgR6NABXgCs7APAjzpw5oyFDhmjPnj2qrKxUx44dNWPGDA0bNizQowG4AsQOAAAwGu/GAgAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABjt/wFgOMP0atPibwAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] }, "metadata": {}, "output_type": "display_data" @@ -209,7 +213,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.11.4" } }, "nbformat": 4, diff --git a/examples/autoqasm/3_2_magic_state_distillation.ipynb b/examples/autoqasm/3_2_magic_state_distillation.ipynb index e0c3ad9e..01c932cf 100644 --- a/examples/autoqasm/3_2_magic_state_distillation.ipynb +++ b/examples/autoqasm/3_2_magic_state_distillation.ipynb @@ -110,7 +110,7 @@ "\n", " # measure data qubit in the target basis, measure ancilla in z-basis\n", " basis_rotation_pi6(q_data)\n", - " c = ins.measure([q_data, q_magic])" + " return ins.measure([q_data, q_magic])" ] }, { @@ -165,7 +165,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "measurement counts: Counter({'00': 46, '01': 39, '11': 15})\n", + "measurement counts: Counter({'00': 58, '01': 36, '11': 6})\n", "Z expectation value: 1.0\n" ] } @@ -173,7 +173,7 @@ "source": [ "# Get measurement result\n", "result = LocalSimulator().run(gate_teleportation, shots=100).result()\n", - "counts = Counter(result.measurements[\"c\"])\n", + "counts = Counter(result.measurements[\"return_value\"])\n", "print(\"measurement counts: \", counts)\n", "\n", "# Post-select the measurement outcome that measures \"0\" in ancilla\n", @@ -308,7 +308,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "measurement counts: Counter({'0000': 181, '1000': 63, '0100': 63, '0010': 62, '0101': 60, '1111': 59, '1101': 59, '0110': 54, '0111': 54, '1100': 52, '1010': 52, '1110': 50, '1001': 49, '1011': 48, '0001': 47, '0011': 47})\n" + "measurement counts: Counter({'0000': 166, '0111': 66, '1110': 66, '0101': 63, '0001': 61, '0100': 58, '1000': 57, '1101': 56, '1010': 56, '1111': 56, '0110': 54, '0011': 54, '0010': 53, '1001': 48, '1011': 47, '1100': 39})\n" ] } ], @@ -337,7 +337,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "success ratio: 0.181\n" + "success ratio: 0.166\n" ] } ], @@ -484,7 +484,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.11.4" } }, "nbformat": 4, diff --git a/examples/autoqasm/4_Native_programming.ipynb b/examples/autoqasm/4_Native_programming.ipynb index 16790387..f20a3122 100644 --- a/examples/autoqasm/4_Native_programming.ipynb +++ b/examples/autoqasm/4_Native_programming.ipynb @@ -46,12 +46,14 @@ "output_type": "stream", "text": [ "OPENQASM 3.0;\n", + "output bit[2] return_value;\n", "qubit[2] __qubits__;\n", "h __qubits__[0];\n", "cnot __qubits__[0], __qubits__[1];\n", "bit[2] __bit_0__ = \"00\";\n", "__bit_0__[0] = measure __qubits__[0];\n", - "__bit_0__[1] = measure __qubits__[1];\n" + "__bit_0__[1] = measure __qubits__[1];\n", + "return_value = __bit_0__;\n" ] } ], @@ -63,7 +65,7 @@ "def bell_state():\n", " h(0)\n", " cnot(0, 1)\n", - " measure([0, 1])\n", + " return measure([0, 1])\n", "\n", "\n", "print(bell_state.to_ir())" @@ -90,6 +92,7 @@ "output_type": "stream", "text": [ "OPENQASM 3.0;\n", + "output bit[2] return_value;\n", "pragma braket verbatim\n", "box {\n", " h $0;\n", @@ -97,7 +100,8 @@ "}\n", "bit[2] __bit_0__ = \"00\";\n", "__bit_0__[0] = measure $0;\n", - "__bit_0__[1] = measure $1;\n" + "__bit_0__[1] = measure $1;\n", + "return_value = __bit_0__;\n" ] } ], @@ -107,7 +111,7 @@ " with aq.verbatim():\n", " h(\"$0\")\n", " cnot(\"$0\", \"$1\")\n", - " measure([\"$0\", \"$1\"])\n", + " return measure([\"$0\", \"$1\"])\n", "\n", "\n", "print(bell_state.to_ir())" @@ -148,7 +152,7 @@ " with aq.verbatim():\n", " h(\"$0\")\n", " cnot(\"$0\", \"$1\")\n", - " measure([\"$0\", \"$1\"])\n", + " return measure([\"$0\", \"$1\"])\n", "except Exception as e:\n", " print(\"ERROR:\", e)" ] @@ -169,15 +173,201 @@ "cell_type": "code", "execution_count": 5, "id": "518b9d23", - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [ { "data": { - "text/plain": "import numpy as np\n\nimport braket.experimental.autoqasm as aq\nfrom braket.experimental.autoqasm.instructions import gpi, gpi2, ms\n\n\n@aq.gate\ndef h(q: aq.Qubit):\n gpi2(q, np.pi / 2)\n gpi(q, 0)\n\n\n@aq.gate\ndef u(q: aq.Qubit, a: float, b: float, c: float):\n gpi2(q, a)\n gpi(q, b)\n gpi2(q, c)\n\n\n@aq.gate\ndef rx(q: aq.Qubit, theta: float):\n u(q, np.pi / 2, theta / 2 + np.pi / 2, np.pi / 2)\n\n\n@aq.gate\ndef ry(q: aq.Qubit, theta: float):\n u(q, np.pi, theta / 2 + np.pi, np.pi)\n\n\n@aq.gate\ndef cnot(q0: aq.Qubit, q1: aq.Qubit):\n ry(q0, np.pi / 2)\n ms(q0, q1, 0, 0, np.pi / 2)\n rx(q0, -np.pi / 2)\n rx(q1, -np.pi / 2)\n ry(q0, -np.pi / 2)", - "text/html": "
import numpy as np\n\nimport braket.experimental.autoqasm as aq\nfrom braket.experimental.autoqasm.instructions import gpi, gpi2, ms\n\n\n@aq.gate\ndef h(q: aq.Qubit):\n    gpi2(q, np.pi / 2)\n    gpi(q, 0)\n\n\n@aq.gate\ndef u(q: aq.Qubit, a: float, b: float, c: float):\n    gpi2(q, a)\n    gpi(q, b)\n    gpi2(q, c)\n\n\n@aq.gate\ndef rx(q: aq.Qubit, theta: float):\n    u(q, np.pi / 2, theta / 2 + np.pi / 2, np.pi / 2)\n\n\n@aq.gate\ndef ry(q: aq.Qubit, theta: float):\n    u(q, np.pi, theta / 2 + np.pi, np.pi)\n\n\n@aq.gate\ndef cnot(q0: aq.Qubit, q1: aq.Qubit):\n    ry(q0, np.pi / 2)\n    ms(q0, q1, 0, 0, np.pi / 2)\n    rx(q0, -np.pi / 2)\n    rx(q1, -np.pi / 2)\n    ry(q0, -np.pi / 2)\n
\n", - "text/latex": "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n\\PY{k+kn}{import} \\PY{n+nn}{numpy} \\PY{k}{as} \\PY{n+nn}{np}\n\n\\PY{k+kn}{import} \\PY{n+nn}{braket}\\PY{n+nn}{.}\\PY{n+nn}{experimental}\\PY{n+nn}{.}\\PY{n+nn}{autoqasm} \\PY{k}{as} \\PY{n+nn}{aq}\n\\PY{k+kn}{from} \\PY{n+nn}{braket}\\PY{n+nn}{.}\\PY{n+nn}{experimental}\\PY{n+nn}{.}\\PY{n+nn}{autoqasm}\\PY{n+nn}{.}\\PY{n+nn}{instructions} \\PY{k+kn}{import} \\PY{n}{gpi}\\PY{p}{,} \\PY{n}{gpi2}\\PY{p}{,} \\PY{n}{ms}\n\n\n\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n\\PY{k}{def} \\PY{n+nf}{h}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{)}\\PY{p}{:}\n \\PY{n}{gpi2}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n \\PY{n}{gpi}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{l+m+mi}{0}\\PY{p}{)}\n\n\n\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n\\PY{k}{def} \\PY{n+nf}{u}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{a}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{,} \\PY{n}{b}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{,} \\PY{n}{c}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{)}\\PY{p}{:}\n \\PY{n}{gpi2}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{a}\\PY{p}{)}\n \\PY{n}{gpi}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{b}\\PY{p}{)}\n \\PY{n}{gpi2}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{c}\\PY{p}{)}\n\n\n\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n\\PY{k}{def} \\PY{n+nf}{rx}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{theta}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{)}\\PY{p}{:}\n \\PY{n}{u}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{,} \\PY{n}{theta} \\PY{o}{/} \\PY{l+m+mi}{2} \\PY{o}{+} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n\n\n\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n\\PY{k}{def} \\PY{n+nf}{ry}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{theta}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{)}\\PY{p}{:}\n \\PY{n}{u}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi}\\PY{p}{,} \\PY{n}{theta} \\PY{o}{/} \\PY{l+m+mi}{2} \\PY{o}{+} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi}\\PY{p}{)}\n\n\n\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n\\PY{k}{def} \\PY{n+nf}{cnot}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{q1}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{)}\\PY{p}{:}\n \\PY{n}{ry}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n \\PY{n}{ms}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{n}{q1}\\PY{p}{,} \\PY{l+m+mi}{0}\\PY{p}{,} \\PY{l+m+mi}{0}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n \\PY{n}{rx}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{o}{\\PYZhy{}}\\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n \\PY{n}{rx}\\PY{p}{(}\\PY{n}{q1}\\PY{p}{,} \\PY{o}{\\PYZhy{}}\\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n \\PY{n}{ry}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{o}{\\PYZhy{}}\\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n\\end{Verbatim}\n" + "text/html": [ + "
import numpy as np\n",
+       "\n",
+       "import braket.experimental.autoqasm as aq\n",
+       "from braket.experimental.autoqasm.instructions import gpi, gpi2, ms\n",
+       "\n",
+       "\n",
+       "@aq.gate\n",
+       "def h(q: aq.Qubit):\n",
+       "    gpi2(q, np.pi / 2)\n",
+       "    gpi(q, 0)\n",
+       "\n",
+       "\n",
+       "@aq.gate\n",
+       "def u(q: aq.Qubit, a: float, b: float, c: float):\n",
+       "    gpi2(q, a)\n",
+       "    gpi(q, b)\n",
+       "    gpi2(q, c)\n",
+       "\n",
+       "\n",
+       "@aq.gate\n",
+       "def rx(q: aq.Qubit, theta: float):\n",
+       "    u(q, np.pi / 2, theta / 2 + np.pi / 2, np.pi / 2)\n",
+       "\n",
+       "\n",
+       "@aq.gate\n",
+       "def ry(q: aq.Qubit, theta: float):\n",
+       "    u(q, np.pi, theta / 2 + np.pi, np.pi)\n",
+       "\n",
+       "\n",
+       "@aq.gate\n",
+       "def cnot(q0: aq.Qubit, q1: aq.Qubit):\n",
+       "    ry(q0, np.pi / 2)\n",
+       "    ms(q0, q1, 0, 0, np.pi / 2)\n",
+       "    rx(q0, -np.pi / 2)\n",
+       "    rx(q1, -np.pi / 2)\n",
+       "    ry(q0, -np.pi / 2)\n",
+       "
\n" + ], + "text/latex": [ + "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", + "\\PY{k+kn}{import} \\PY{n+nn}{numpy} \\PY{k}{as} \\PY{n+nn}{np}\n", + "\n", + "\\PY{k+kn}{import} \\PY{n+nn}{braket}\\PY{n+nn}{.}\\PY{n+nn}{experimental}\\PY{n+nn}{.}\\PY{n+nn}{autoqasm} \\PY{k}{as} \\PY{n+nn}{aq}\n", + "\\PY{k+kn}{from} \\PY{n+nn}{braket}\\PY{n+nn}{.}\\PY{n+nn}{experimental}\\PY{n+nn}{.}\\PY{n+nn}{autoqasm}\\PY{n+nn}{.}\\PY{n+nn}{instructions} \\PY{k+kn}{import} \\PY{n}{gpi}\\PY{p}{,} \\PY{n}{gpi2}\\PY{p}{,} \\PY{n}{ms}\n", + "\n", + "\n", + "\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n", + "\\PY{k}{def} \\PY{n+nf}{h}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{)}\\PY{p}{:}\n", + " \\PY{n}{gpi2}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", + " \\PY{n}{gpi}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{l+m+mi}{0}\\PY{p}{)}\n", + "\n", + "\n", + "\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n", + "\\PY{k}{def} \\PY{n+nf}{u}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{a}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{,} \\PY{n}{b}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{,} \\PY{n}{c}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{)}\\PY{p}{:}\n", + " \\PY{n}{gpi2}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{a}\\PY{p}{)}\n", + " \\PY{n}{gpi}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{b}\\PY{p}{)}\n", + " \\PY{n}{gpi2}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{c}\\PY{p}{)}\n", + "\n", + "\n", + "\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n", + "\\PY{k}{def} \\PY{n+nf}{rx}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{theta}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{)}\\PY{p}{:}\n", + " \\PY{n}{u}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{,} \\PY{n}{theta} \\PY{o}{/} \\PY{l+m+mi}{2} \\PY{o}{+} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", + "\n", + "\n", + "\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n", + "\\PY{k}{def} \\PY{n+nf}{ry}\\PY{p}{(}\\PY{n}{q}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{theta}\\PY{p}{:} \\PY{n+nb}{float}\\PY{p}{)}\\PY{p}{:}\n", + " \\PY{n}{u}\\PY{p}{(}\\PY{n}{q}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi}\\PY{p}{,} \\PY{n}{theta} \\PY{o}{/} \\PY{l+m+mi}{2} \\PY{o}{+} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi}\\PY{p}{)}\n", + "\n", + "\n", + "\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n", + "\\PY{k}{def} \\PY{n+nf}{cnot}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{,} \\PY{n}{q1}\\PY{p}{:} \\PY{n}{aq}\\PY{o}{.}\\PY{n}{Qubit}\\PY{p}{)}\\PY{p}{:}\n", + " \\PY{n}{ry}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", + " \\PY{n}{ms}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{n}{q1}\\PY{p}{,} \\PY{l+m+mi}{0}\\PY{p}{,} \\PY{l+m+mi}{0}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", + " \\PY{n}{rx}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{o}{\\PYZhy{}}\\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", + " \\PY{n}{rx}\\PY{p}{(}\\PY{n}{q1}\\PY{p}{,} \\PY{o}{\\PYZhy{}}\\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", + " \\PY{n}{ry}\\PY{p}{(}\\PY{n}{q0}\\PY{p}{,} \\PY{o}{\\PYZhy{}}\\PY{n}{np}\\PY{o}{.}\\PY{n}{pi} \\PY{o}{/} \\PY{l+m+mi}{2}\\PY{p}{)}\n", + "\\end{Verbatim}\n" + ], + "text/plain": [ + "import numpy as np\n", + "\n", + "import braket.experimental.autoqasm as aq\n", + "from braket.experimental.autoqasm.instructions import gpi, gpi2, ms\n", + "\n", + "\n", + "@aq.gate\n", + "def h(q: aq.Qubit):\n", + " gpi2(q, np.pi / 2)\n", + " gpi(q, 0)\n", + "\n", + "\n", + "@aq.gate\n", + "def u(q: aq.Qubit, a: float, b: float, c: float):\n", + " gpi2(q, a)\n", + " gpi(q, b)\n", + " gpi2(q, c)\n", + "\n", + "\n", + "@aq.gate\n", + "def rx(q: aq.Qubit, theta: float):\n", + " u(q, np.pi / 2, theta / 2 + np.pi / 2, np.pi / 2)\n", + "\n", + "\n", + "@aq.gate\n", + "def ry(q: aq.Qubit, theta: float):\n", + " u(q, np.pi, theta / 2 + np.pi, np.pi)\n", + "\n", + "\n", + "@aq.gate\n", + "def cnot(q0: aq.Qubit, q1: aq.Qubit):\n", + " ry(q0, np.pi / 2)\n", + " ms(q0, q1, 0, 0, np.pi / 2)\n", + " rx(q0, -np.pi / 2)\n", + " rx(q1, -np.pi / 2)\n", + " ry(q0, -np.pi / 2)" + ] }, "execution_count": 5, "metadata": {}, @@ -229,6 +419,7 @@ " rx(-1.5707963267948966) q1;\n", " ry(-1.5707963267948966) q0;\n", "}\n", + "output bit[2] return_value;\n", "pragma braket verbatim\n", "box {\n", " h $0;\n", @@ -236,7 +427,8 @@ "}\n", "bit[2] __bit_0__ = \"00\";\n", "__bit_0__[0] = measure $0;\n", - "__bit_0__[1] = measure $1;\n" + "__bit_0__[1] = measure $1;\n", + "return_value = __bit_0__;\n" ] } ], @@ -249,7 +441,7 @@ " with aq.verbatim():\n", " h(\"$0\")\n", " cnot(\"$0\", \"$1\")\n", - " measure([\"$0\", \"$1\"])\n", + " return measure([\"$0\", \"$1\"])\n", "\n", "\n", "print(bell_state.to_ir())" @@ -290,7 +482,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.11.4" } }, "nbformat": 4, diff --git a/src/braket/experimental/autoqasm/constants.py b/src/braket/experimental/autoqasm/constants.py index 629fc200..286971a8 100644 --- a/src/braket/experimental/autoqasm/constants.py +++ b/src/braket/experimental/autoqasm/constants.py @@ -35,3 +35,6 @@ RETVAL_VARIABLE_NAME = "retval_" """A special name for variables assigned to the return values of function calls.""" + +MAIN_RETURN_VAL_NAME = "return_value" +"""The default name for the value returned from autoqasm.main functions.""" diff --git a/src/braket/experimental/autoqasm/converters/return_statements.py b/src/braket/experimental/autoqasm/converters/return_statements.py index 2f599c77..0bc51652 100644 --- a/src/braket/experimental/autoqasm/converters/return_statements.py +++ b/src/braket/experimental/autoqasm/converters/return_statements.py @@ -21,7 +21,7 @@ from malt.core import ag_ctx, converter from malt.pyct import templates -from braket.experimental.autoqasm import program +from braket.experimental.autoqasm import constants, program from braket.experimental.autoqasm.operators.assignments import assign_for_output @@ -44,7 +44,7 @@ def visit_Return(self, node: ast.stmt) -> ast.stmt: "ag__.return_output_from_main(name_const_, value_))" ) - name = "retval_" + name = constants.MAIN_RETURN_VAL_NAME if isinstance(node.value, gast.Name): name = node.value.id diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index 4a524642..87c09828 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -484,7 +484,7 @@ def test_qasm_inline_var_condition(qasm_inline_var_condition) -> None: expected = """OPENQASM 3.0; bit __bit_0__ = 1; int[32] __int_1__ = 1; -output bit retval_; +output bit return_value; qubit[2] __qubits__; h __qubits__[0]; if (__bit_0__) { @@ -497,7 +497,7 @@ def test_qasm_inline_var_condition(qasm_inline_var_condition) -> None: } bit __bit_2__; __bit_2__ = measure __qubits__[1]; -retval_ = __bit_2__;""" +return_value = __bit_2__;""" assert qasm_inline_var_condition.to_ir() == expected @@ -526,13 +526,13 @@ def test_measurement_qubit_discovery(ground_state_measurements) -> None: def test_simple_measurement(ground_state_measurements) -> None: """Test that a program with only measurements is generated correctly.""" expected = """OPENQASM 3.0; -output bit[3] retval_; +output bit[3] return_value; qubit[6] __qubits__; bit[3] __bit_0__ = "000"; __bit_0__[0] = measure __qubits__[5]; __bit_0__[1] = measure __qubits__[2]; __bit_0__[2] = measure __qubits__[1]; -retval_ = __bit_0__;""" +return_value = __bit_0__;""" assert ground_state_measurements.to_ir() == expected @@ -593,7 +593,7 @@ def qasm_measurement_condition() -> aq.BitVar: def test_qasm_measurement_condition(qasm_measurement_condition) -> None: expected = """OPENQASM 3.0; -output bit retval_; +output bit return_value; qubit[2] __qubits__; h __qubits__[0]; bit __bit_0__; @@ -603,7 +603,7 @@ def test_qasm_measurement_condition(qasm_measurement_condition) -> None: } bit __bit_1__; __bit_1__ = measure __qubits__[1]; -retval_ = __bit_1__;""" +return_value = __bit_1__;""" assert qasm_measurement_condition.to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/braket/experimental/autoqasm/test_program.py index 2f690194..b70edf8c 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_program.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_program.py @@ -99,7 +99,7 @@ def circuit(float[64] angle) { rx(angle) __qubits__[0]; cnot __qubits__[0], __qubits__[1]; } -output bit retval_; +output bit return_value; qubit[2] __qubits__; for int i in [0:""" + str(scale) @@ -110,7 +110,7 @@ def circuit(float[64] angle) { } bit __bit_0__; __bit_0__ = measure __qubits__[1]; -retval_ = __bit_0__;""" +return_value = __bit_0__;""" ) for i, (scale, angle) in enumerate(itertools.product(scales, angles)): diff --git a/test/unit_tests/braket/experimental/autoqasm/test_return.py b/test/unit_tests/braket/experimental/autoqasm/test_return.py index 776631c7..d60e860d 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_return.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_return.py @@ -25,8 +25,8 @@ def main(): return 1.5 expected = """OPENQASM 3.0; -output float[64] retval_; -retval_ = 1.5;""" +output float[64] return_value; +return_value = 1.5;""" assert main.to_ir() == expected @@ -37,8 +37,8 @@ def main(): return 1 expected = """OPENQASM 3.0; -output int[32] retval_; -retval_ = 1;""" +output int[32] return_value; +return_value = 1;""" assert main.to_ir() == expected @@ -62,11 +62,11 @@ def main(): return measure(0) expected = """OPENQASM 3.0; -output bit retval_; +output bit return_value; qubit[1] __qubits__; bit __bit_0__; __bit_0__ = measure __qubits__[0]; -retval_ = __bit_0__;""" +return_value = __bit_0__;""" assert main.to_ir() == expected @@ -123,10 +123,10 @@ def main(): return 1, 2 expected = """OPENQASM 3.0; -output int[32] retval_0; -output int[32] retval_1; -retval_0 = 1; -retval_1 = 2;""" +output int[32] return_value0; +output int[32] return_value1; +return_value0 = 1; +return_value1 = 2;""" assert main.to_ir() == expected @@ -137,10 +137,10 @@ def main(): return [11.1, 2.222] expected = """OPENQASM 3.0; -output float[64] retval_0; -output float[64] retval_1; -retval_0 = 11.1; -retval_1 = 2.222;""" +output float[64] return_value0; +output float[64] return_value1; +return_value0 = 11.1; +return_value1 = 2.222;""" assert main.to_ir() == expected @@ -155,9 +155,9 @@ def main(): expected = """OPENQASM 3.0; bit a; bit b; -output bit retval_0; -output bit retval_1; -output bit retval_2; +output bit return_value0; +output bit return_value1; +output bit return_value2; qubit[3] __qubits__; bit __bit_0__; __bit_0__ = measure __qubits__[0]; @@ -167,9 +167,9 @@ def main(): b = __bit_1__; bit __bit_2__; __bit_2__ = measure __qubits__[2]; -retval_0 = a; -retval_1 = b; -retval_2 = __bit_2__;""" +return_value0 = a; +return_value1 = b; +return_value2 = __bit_2__;""" assert main.to_ir() == expected @@ -183,16 +183,16 @@ def main(): expected = """OPENQASM 3.0; bit a; -output bit retval_0; -output bool retval_1; -output float[64] retval_2; +output bit return_value0; +output bool return_value1; +output float[64] return_value2; qubit[1] __qubits__; bit __bit_0__; __bit_0__ = measure __qubits__[0]; a = __bit_0__; -retval_0 = a; -retval_1 = true; -retval_2 = 1.11;""" +return_value0 = a; +return_value1 = true; +return_value2 = 1.11;""" assert main.to_ir() == expected @@ -213,8 +213,8 @@ def main(val1, val2): expected = """OPENQASM 3.0; input float val1; input float val2; -output float[64] retval_; -retval_ = val1 + val2;""" +output float[64] return_value; +return_value = val1 + val2;""" assert main.to_ir() == expected @@ -227,8 +227,8 @@ def main(val1: int, val2: int): expected = """OPENQASM 3.0; input int[32] val1; input int[32] val2; -output int[32] retval_; -retval_ = val1 + val2;""" +output int[32] return_value; +return_value = val1 + val2;""" assert main.to_ir() == expected @@ -241,10 +241,10 @@ def main(val1: bool, val2: bool): expected = """OPENQASM 3.0; input bool val1; input bool val2; -output bool retval_; +output bool return_value; bool __bool_0__; __bool_0__ = val1 || val2; -retval_ = __bool_0__;""" +return_value = __bool_0__;""" assert main.to_ir() == expected @@ -259,7 +259,7 @@ def main(): expected = """OPENQASM 3.0; bit b0; bit b1; -output bit retval_; +output bit return_value; qubit[2] __qubits__; bit __bit_0__; __bit_0__ = measure __qubits__[0]; @@ -267,7 +267,7 @@ def main(): bit __bit_1__; __bit_1__ = measure __qubits__[1]; b1 = __bit_1__; -retval_ = b0 + b1;""" +return_value = b0 + b1;""" assert main.to_ir() == expected @@ -318,13 +318,13 @@ def ghz(int[32] n) { } } input int[32] n; -output bit[3] retval_; +output bit[3] return_value; qubit[10] __qubits__; ghz(n); bit[3] __bit_0__ = "000"; __bit_0__[0] = measure __qubits__[0]; __bit_0__[1] = measure __qubits__[1]; __bit_0__[2] = measure __qubits__[2]; -retval_ = __bit_0__;""" +return_value = __bit_0__;""" assert program.to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index e3b51647..5be4c6c9 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -59,10 +59,10 @@ def ret_test() -> bit { bit res = 1; return res; } -output bit retval_; +output bit return_value; bit __bit_1__; __bit_1__ = ret_test(); -retval_ = __bit_1__;""" +return_value = __bit_1__;""" assert main.to_ir() == expected @@ -84,10 +84,10 @@ def ret_test() -> int[32] { int[32] res = 1; return res; } -output int[32] retval_; +output int[32] return_value; int[32] __int_1__; __int_1__ = ret_test(); -retval_ = __int_1__;""" +return_value = __int_1__;""" assert main.to_ir() == expected @@ -109,10 +109,10 @@ def ret_test() -> float[64] { float[64] res = 1.0; return res; } -output float[64] retval_; +output float[64] return_value; float[64] __float_1__; __float_1__ = ret_test(); -retval_ = __float_1__;""" +return_value = __float_1__;""" assert main.to_ir() == expected @@ -134,10 +134,10 @@ def ret_test() -> bool { bool res = true; return res; } -output bool retval_; +output bool return_value; bool __bool_1__; __bool_1__ = ret_test(); -retval_ = __bool_1__;""" +return_value = __bool_1__;""" assert main.to_ir() == expected @@ -159,12 +159,12 @@ def ret_test() -> int: def add(int[32] a, int[32] b) -> int[32] { return a + b; } -output int[32] retval_; +output int[32] return_value; int[32] a = 5; int[32] b = 6; int[32] __int_2__; __int_2__ = add(a, b); -retval_ = __int_2__;""" +return_value = __int_2__;""" assert ret_test.to_ir() == expected @@ -282,10 +282,10 @@ def helper() -> int[32] { int[32] res = 1; return res; } -output int[32] retval_; +output int[32] return_value; int[32] __int_1__; __int_1__ = helper(); -retval_ = __int_1__;""" +return_value = __int_1__;""" assert ret_test.to_ir() == expected @@ -463,10 +463,10 @@ def retval_test() -> int[32] { int[32] retval_ = 1; return retval_; } -output int[32] retval_; +output int[32] return_value; int[32] __int_1__; __int_1__ = retval_test(); -retval_ = __int_1__;""" +return_value = __int_1__;""" assert caller.to_ir() == expected_qasm @@ -487,10 +487,10 @@ def retval_test() -> bit { bit retval_ = 1; return retval_; } -output bit retval_; +output bit return_value; bit __bit_1__; __bit_1__ = retval_test(); -retval_ = __bit_1__;""" +return_value = __bit_1__;""" assert caller.to_ir() == expected_qasm @@ -575,10 +575,10 @@ def retval_constant() -> int[32] { int[32] retval_ = 3; return retval_; } -output float[64] retval_; +output float[64] return_value; float[64] __float_4__; __float_4__ = retval_recursive(); -retval_ = __float_4__;""" +return_value = __float_4__;""" assert caller.to_ir() == expected_qasm From 879133cbb11b9f80fdc0017ba83a12657bcac1f9 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Fri, 23 Feb 2024 12:53:59 -0800 Subject: [PATCH 1051/1165] 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 711aeab1..3c6dbe66 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 be4e7254..be4b426d 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 7f9dc1a8..8df2118c 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 17027cec..5b3eb7e4 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 1052/1165] 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 57e0980b..9b4cdf90 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 359a8645..86b26bf6 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 1053/1165] 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 86b26bf6..fde388ff 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 1054/1165] 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 d3ad9125..cc339fe1 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 370d4508..1bf6818b 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 1055/1165] 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 9b4cdf90..9f5d4b9c 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 fde388ff..b0b886fa 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 1056/1165] 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 b0b886fa..02e831d5 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 1057/1165] 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 be4b426d..b2d12fe3 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 5b3eb7e4..72f9c4db 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 1058/1165] 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 9f5d4b9c..22e4bfd5 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 02e831d5..79980270 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 1059/1165] 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 79980270..6143a872 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 7ff8d31f57d7a5dcb3aa9038fec73ed82a05edb6 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Wed, 28 Feb 2024 13:59:34 -0500 Subject: [PATCH 1060/1165] feature: Support AutoQASM return statements for pulse (#886) * feature: Support AutoQASM return statements for pulse.capture_v0 instruction --- .../experimental/autoqasm/pulse/pulse.py | 50 ++++++++----------- .../experimental/autoqasm/test_return.py | 25 ++++++++++ 2 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/braket/experimental/autoqasm/pulse/pulse.py b/src/braket/experimental/autoqasm/pulse/pulse.py index 3aabfaf8..b05b450f 100644 --- a/src/braket/experimental/autoqasm/pulse/pulse.py +++ b/src/braket/experimental/autoqasm/pulse/pulse.py @@ -12,8 +12,7 @@ # language governing permissions and limitations under the License. -"""Pulse instructions that apply to frames or qubits. -""" +"""Pulse instructions that apply to frames or qubits.""" from typing import Union @@ -115,13 +114,32 @@ def play(frame: Frame, waveform: Waveform) -> None: _pulse_instruction("play", frame, waveform) -def capture_v0(frame: Frame) -> None: +def capture_v0(frame: Frame) -> BitVar: """Adds an instruction to capture the bit output from measuring the specified frame. Args: frame (Frame): Frame on which the capture operation needs to be performed. + Returns: + BitVar: A BitVar with the result of the capture. """ - _pulse_instruction("_capture_v0_with_return", frame) + + def _add_sequence() -> BitVar: + _validate_uniqueness(pulse_sequence._frames, frame) + extern_call = oqpy.declare_extern("capture_v0", [("frame", oqpy.FrameVar)], oqpy.bit) + bit_var = BitVar() + pulse_sequence._program.set(bit_var, extern_call(frame)) + pulse_sequence._capture_v0_count += 1 + pulse_sequence._frames[frame.id] = frame + return bit_var + + aq_context = aq_program.get_program_conversion_context() + aq_context._has_pulse_control = True + + pulse_sequence = PulseSequence() + pulse_sequence._program = aq_context.get_oqpy_program(mode=aq_program.ProgramMode.PULSE) + + with oqpy.Cal(pulse_sequence._program): + return _add_sequence() def delay( @@ -161,27 +179,3 @@ def barrier( if all(is_qubit_identifier_type(q) for q in qubits_or_frames): qubits_or_frames = QubitSet(_get_physical_qubit_indices(qubits_or_frames)) _pulse_instruction("barrier", qubits_or_frames) - - -def _pulse_sequence_capture_v0_with_return(self, frame: Frame) -> PulseSequence: - """ - Implement a custom capturing method to be register it to the `PulseSequence` class. This method - adds an instruction to capture the bit output from measuring the specified frame and assigns - the output to a bit variable explicitly. - - Args: - frame (Frame): Frame on which the capture operation needs to be performed. - - Returns: - PulseSequence: self, with the instruction added. - """ - _validate_uniqueness(self._frames, frame) - - extern_call = oqpy.declare_extern("capture_v0", [("frame", oqpy.FrameVar)], oqpy.bit) - self._program.set(BitVar(), extern_call(frame)) - self._capture_v0_count += 1 - self._frames[frame.id] = frame - return self - - -setattr(PulseSequence, "_capture_v0_with_return", _pulse_sequence_capture_v0_with_return) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_return.py b/test/unit_tests/braket/experimental/autoqasm/test_return.py index d60e860d..8f3aa786 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_return.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_return.py @@ -17,6 +17,8 @@ import braket.experimental.autoqasm as aq from braket.experimental.autoqasm.instructions import measure +from braket.experimental.autoqasm.pulse import capture_v0 +from braket.pulse import Frame, Port def test_float_lit(): @@ -328,3 +330,26 @@ def ghz(int[32] n) { return_value = __bit_0__;""" assert program.to_ir() == expected + + +def test_return_pulse_capture(): + port = Port(port_id="device_port_x0", dt=1e-9, properties={}) + frame = Frame(frame_id="frame1", frequency=2e9, port=port, phase=0, is_predefined=True) + + @aq.main + def program(): + return capture_v0(frame), capture_v0(frame) + + expected = """OPENQASM 3.0; +bit __bit_0__; +bit __bit_1__; +output bit return_value0; +output bit return_value1; +cal { + __bit_0__ = capture_v0(frame1); + __bit_1__ = capture_v0(frame1); +} +return_value0 = __bit_0__; +return_value1 = __bit_1__;""" + + assert program.to_ir() == expected 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 1061/1165] 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 b336a279..16f96bd3 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 1062/1165] 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 b3063c44..8b696f17 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 9d582652..78fbe45b 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 cc339fe1..fdd2f547 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 e6d78f11..a788b38c 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 3cf40fd8..91b19dba 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 816bc0ce..b94c60a9 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 1063/1165] 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 2962c204..2a56d807 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 1064/1165] 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 b12cde75..0ec7ff9b 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 1065/1165] 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 22e4bfd5..62d625ca 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 6143a872..d6dc5d35 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 1066/1165] 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 d6dc5d35..b77e9d94 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 1067/1165] 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 74b49db9..f216d2c1 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 1068/1165] 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 f216d2c1..516f93c7 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 1069/1165] 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 d2788746..a5fb5298 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 c255377b..accbce16 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 8b696f17..36f0e68f 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 00000000..3106afc4 --- /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 00000000..c8bfa265 --- /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 00000000..9b85c30e --- /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 00000000..9e1779cb --- /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 916bfb05..88e32d92 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 91b19dba..ecaad12b 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 00000000..96df634c --- /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 1070/1165] 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 62d625ca..150c88dd 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 b77e9d94..68f5f1a4 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 1071/1165] 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 68f5f1a4..52d1e573 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 1072/1165] 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 2a56d807..b8d0d43d 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 1073/1165] 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 150c88dd..1040afb3 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 52d1e573..d43c3860 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 1074/1165] 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 d43c3860..ba58da84 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 1075/1165] 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 516f93c7..f4ac4e91 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 1076/1165] 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 0ec7ff9b..a6a359e9 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 f4dc0ac6d80f9d3ddf5b01d47c5ed8a65bfcbe81 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 12 Mar 2024 11:51:47 -0400 Subject: [PATCH 1077/1165] feat: Explicit build API for AutoQASM main program (#841) --- src/braket/circuits/serialization.py | 7 +- src/braket/devices/local_simulator.py | 2 +- src/braket/experimental/autoqasm/api.py | 25 +- .../experimental/autoqasm/program/__init__.py | 1 + .../experimental/autoqasm/program/program.py | 65 ++++++ .../braket/experimental/autoqasm/test_api.py | 217 ++++++++++++------ .../experimental/autoqasm/test_converters.py | 26 ++- .../experimental/autoqasm/test_devices.py | 90 ++++---- .../autoqasm/test_gate_calibrations.py | 20 +- .../autoqasm/test_gate_decorator.py | 99 ++++---- .../experimental/autoqasm/test_operators.py | 40 ++-- .../experimental/autoqasm/test_parameters.py | 52 +++-- .../experimental/autoqasm/test_pragmas.py | 24 +- .../experimental/autoqasm/test_program.py | 2 +- .../experimental/autoqasm/test_pulse.py | 2 +- .../experimental/autoqasm/test_return.py | 9 +- .../experimental/autoqasm/test_transpiler.py | 36 +-- .../experimental/autoqasm/test_types.py | 109 +++++---- 18 files changed, 512 insertions(+), 314 deletions(-) diff --git a/src/braket/circuits/serialization.py b/src/braket/circuits/serialization.py index f41f193b..fae47816 100644 --- a/src/braket/circuits/serialization.py +++ b/src/braket/circuits/serialization.py @@ -38,15 +38,20 @@ class SerializableProgram(ABC): def to_ir( self, ir_type: IRType = IRType.OPENQASM, + allow_implicit_build: bool = True, ) -> 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. + allow_implicit_build (bool): Whether to allow the program to be implicitly + built as a side effect of calling this function. Defaults to True. Raises: - ValueError: If the supplied `ir_type` is not supported. + ValueError: Raised if the supplied `ir_type` is not supported. + RuntimeError: Raised if the program has not already been built and + `allow_implicit_build` is False. Returns: str: A representation of the program in the `ir_type` format. diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index f80d2fc0..4277002c 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -337,7 +337,7 @@ def _( simulator = self._delegate if DeviceActionType.OPENQASM not in simulator.properties.action: raise NotImplementedError(f"{type(simulator)} does not support OpenQASM programs") - program = Program(source=program.to_ir(ir_type=IRType.OPENQASM)) + program = Program(source=program.to_ir(ir_type=IRType.OPENQASM, allow_implicit_build=True)) if inputs: inputs_copy = program.inputs.copy() if program.inputs is not None else {} inputs_copy.update(inputs) diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index b6c900ed..829d7d6b 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -32,8 +32,6 @@ import braket.experimental.autoqasm.program as aq_program import braket.experimental.autoqasm.transpiler as aq_transpiler import braket.experimental.autoqasm.types as aq_types -from braket.aws import AwsDevice -from braket.devices.device import Device from braket.experimental.autoqasm import errors from braket.experimental.autoqasm.program.gate_calibrations import GateCalibration from braket.experimental.autoqasm.types import QubitIdentifierType as Qubit @@ -43,7 +41,6 @@ def main( func: Optional[Callable] = None, *, num_qubits: Optional[int] = None, - device: Optional[Union[Device, str]] = None, ) -> aq_program.Program | Callable[..., aq_program.Program]: """Decorator that converts a function into a Program object containing the quantum program. @@ -55,24 +52,20 @@ def main( is used with parentheses. num_qubits (Optional[int]): Configuration to set the total number of qubits to declare in the program. - device (Optional[Union[Device, str]]): Configuration to set the target device for the - program. Can be either an Device object or a valid Amazon Braket device ARN. Returns: Program | Callable[..., Program]: The Program object containing the converted quantum program, or a partial function of the `main` decorator. """ - if isinstance(device, str): - device = AwsDevice(device) - # decorator is called on a Program - if isinstance(func, aq_program.Program): + # decorator is called on a MainProgram + if isinstance(func, aq_program.MainProgram): return func # decorator is used with parentheses # (see _function_wrapper for more details) if not (func and callable(func)): - return functools.partial(main, num_qubits=num_qubits, device=device) + return functools.partial(main, num_qubits=num_qubits) program_builder = _function_wrapper( func, @@ -80,12 +73,11 @@ def main( converter_args={ "user_config": aq_program.UserConfig( num_qubits=num_qubits, - device=device, ) }, ) - return program_builder() + return aq_program.MainProgram(program_builder) def subroutine( @@ -235,7 +227,10 @@ def _convert_main( Returns: aq_program.Program: Generated AutoQASM Program. """ - kwargs = {} + if "device" in kwargs and kwargs["device"]: + user_config.device = kwargs["device"] + + param_dict = {} parameters = inspect.signature(f).parameters with aq_program.build_program(user_config) as program_conversion_context: @@ -243,14 +238,14 @@ def _convert_main( for param in parameters.values(): if param.kind == param.POSITIONAL_OR_KEYWORD: param_type = param.annotation if param.annotation is not param.empty else float - kwargs[param.name] = program_conversion_context.register_input_parameter( + param_dict[param.name] = program_conversion_context.register_input_parameter( param.name, param_type ) else: raise NotImplementedError # Process the program - aq_transpiler.converted_call(f, (), kwargs, options=options) + aq_transpiler.converted_call(f, (), param_dict, options=options) # Modify program to add global declarations if necessary _add_qubit_declaration(program_conversion_context) diff --git a/src/braket/experimental/autoqasm/program/__init__.py b/src/braket/experimental/autoqasm/program/__init__.py index 2e3aefab..31ba3d52 100644 --- a/src/braket/experimental/autoqasm/program/__init__.py +++ b/src/braket/experimental/autoqasm/program/__init__.py @@ -18,6 +18,7 @@ from .pragmas import verbatim # noqa: F401 from .program import ( # noqa: F401 GateArgs, + MainProgram, Program, ProgramConversionContext, ProgramMode, diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index cef0f45b..3000dee4 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -29,6 +29,7 @@ from sympy import Symbol import braket.experimental.autoqasm.types as aq_types +from braket.aws.aws_device import AwsDevice from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.serialization import IRType, SerializableProgram from braket.device_schema import DeviceActionType @@ -87,6 +88,65 @@ class ProgramMode(Enum): """For program conversion inside a context where only pulse operations are allowed.""" +class MainProgram(SerializableProgram): + """Represents an AutoQASM program. The program can be built by calling build(), or it can be + executed by passing it to the run() method of a Device object.""" + + def __init__(self, program_generator: Callable[[Device | None], Program]): + self._program_generator = program_generator + + def build(self, device: Device | str | None = None) -> Program: + """Builds and validates the AutoQASM program. If device is specified, program validation + is also performed against the properties of the device. + + Args: + device (Device | str | None): Configuration to set the target device for the + program. Can be either an Device object or a valid Amazon Braket device ARN. + + Returns: + Program: The generated AutoQASM program. + """ + if isinstance(device, str): + device = AwsDevice(device) + + return self._program_generator(device=device) + + def to_ir( + self, + ir_type: IRType = IRType.OPENQASM, + allow_implicit_build: bool = True, + serialization_properties: SerializationProperties = OpenQASMSerializationProperties(), + ) -> 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. + allow_implicit_build (bool): Whether to allow the program to be implicitly + built as a side effect of calling this function. Defaults to True. + serialization_properties (SerializationProperties): IR serialization configuration. + Default to OpenQASMSerializationProperties(). + + Raises: + ValueError: Raised if the supplied `ir_type` is not supported. + RuntimeError: Raised if `allow_implicit_build` is False, since a MainProgram object + has not yet been built. + + Returns: + str: A representation of the program in the `ir_type` format. + """ + if not allow_implicit_build: + raise RuntimeError( + "The AutoQASM program cannot be serialized because it has not yet been built. " + "To serialize the program, first call build() to obtain a built Program object, " + "and then call to_ir() on the returned Program object." + ) + + return self.build().to_ir( + ir_type=ir_type, serialization_properties=serialization_properties + ) + + class Program(SerializableProgram): """The program that has been generated with AutoQASM. This object can be passed to the run() method of a Braket Device.""" @@ -160,6 +220,7 @@ def make_bound_program(self, param_values: dict[str, float], strict: bool = Fals def to_ir( self, ir_type: IRType = IRType.OPENQASM, + allow_implicit_build: bool = True, serialization_properties: SerializationProperties = OpenQASMSerializationProperties(), ) -> str: """Serializes the program into an intermediate representation. @@ -167,6 +228,10 @@ def to_ir( Args: ir_type (IRType): The IRType to use for converting the program to its IR representation. Defaults to IRType.OPENQASM. + allow_implicit_build (bool): Whether to allow the program to be implicitly + built as a side effect of calling this function. Defaults to True. + This parameter is ignored for the Program class, since the program has + already been built. serialization_properties (SerializationProperties): IR serialization configuration. Default to OpenQASMSerializationProperties(). diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index 87c09828..ab824cfb 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -292,37 +292,41 @@ def test_bell_partial_measurement(bell_partial_measurement) -> None: def test_bell_measurement_invalid_declared_type() -> None: """Test measurement with result stored in a variable with invalid type.""" - expected_error_message = "Variables in assignment statements must have the same type" - with pytest.raises(errors.InvalidAssignmentStatement) as exc_info: - @aq.main - def bell_measurement_invalid_declared_type() -> None: - """A function that generates and measures a two-qubit Bell state. But stores - result in a variable with invalid type. - """ - c = aq.IntVar(0) - h(0) - cnot(0, 1) - c = measure(1) # noqa: F841 + @aq.main + def bell_measurement_invalid_declared_type() -> None: + """A function that generates and measures a two-qubit Bell state. But stores + result in a variable with invalid type. + """ + c = aq.IntVar(0) + h(0) + cnot(0, 1) + c = measure(1) # noqa: F841 + + with pytest.raises(errors.InvalidAssignmentStatement) as exc_info: + bell_measurement_invalid_declared_type.build() + expected_error_message = "Variables in assignment statements must have the same type" assert expected_error_message in str(exc_info.value) def test_bell_measurement_invalid_declared_size() -> None: """Test measurement with result stored in a variable with invalid size.""" - expected_error_message = "Variables in assignment statements must have the same size" - with pytest.raises(errors.InvalidAssignmentStatement) as exc_info: - @aq.main - def bell_measurement_invalid_declared_size() -> None: - """A function that generates and measures a two-qubit Bell state. But stores - result in a variable with invalid size. - """ - c = aq.BitVar([0, 0], size=2) - h(0) - cnot(0, 1) - c = measure(1) # noqa: F841 + @aq.main + def bell_measurement_invalid_declared_size() -> None: + """A function that generates and measures a two-qubit Bell state. But stores + result in a variable with invalid size. + """ + c = aq.BitVar([0, 0], size=2) + h(0) + cnot(0, 1) + c = measure(1) # noqa: F841 + with pytest.raises(errors.InvalidAssignmentStatement) as exc_info: + bell_measurement_invalid_declared_size.build() + + expected_error_message = "Variables in assignment statements must have the same size" assert expected_error_message in str(exc_info.value) @@ -635,53 +639,63 @@ def main(): def test_invalid_physical_qubit_fails() -> None: """Tests invalid physical qubit formatting.""" - with pytest.raises(ValueError): - @aq.main - def broken() -> None: - """Uses invalid string for qubit index""" - cnot("$0l", "$O1") + @aq.main + def broken() -> None: + """Uses invalid string for qubit index""" + cnot("$0l", "$O1") + + with pytest.raises(ValueError): + broken.build() def test_invalid_qubit_label_fails() -> None: """Tests random string fails for qubit label.""" - with pytest.raises(ValueError): - @aq.main - def broken() -> None: - """Uses invalid string for qubit index""" - h("nope") + @aq.main + def broken() -> None: + """Uses invalid string for qubit index""" + h("nope") + + with pytest.raises(ValueError): + broken.build() def test_float_qubit_index_fails() -> None: """Tests floats fails for qubit label.""" - with pytest.raises(TypeError): - @aq.main - def broken() -> None: - """Uses float for qubit index""" - i = 1 - h(i / 2) + @aq.main + def broken() -> None: + """Uses float for qubit index""" + i = 1 + h(i / 2) + + with pytest.raises(TypeError): + broken.build() def test_bool_qubit_index_fails() -> None: """Tests that an error is raised for boolean qubit type.""" - with pytest.raises(ValueError): - @aq.main - def broken() -> None: - """Uses invalid type for qubit index""" - h(True) + @aq.main + def broken() -> None: + """Uses invalid type for qubit index""" + h(True) + + with pytest.raises(ValueError): + broken.build() def test_invalid_qubit_type_fails() -> None: """Tests that an error is raised for other unusual qubit types.""" - with pytest.raises(ValueError): - @aq.main - def broken() -> None: - """Uses invalid type for qubit index""" - h(h) + @aq.main + def broken() -> None: + """Uses invalid type for qubit index""" + h(h) + + with pytest.raises(ValueError): + broken.build() def test_bit_array_name() -> None: @@ -745,26 +759,30 @@ def test_program_simple_expr() -> None: """Test that a program with simple expressions for the qubit index raises an error if the user doesn't specify the number of qubits. """ - with pytest.raises(errors.UnknownQubitCountError): - @aq.main - def simple_range() -> None: - "Uses aq.range iterator for qubit index." - for i in aq.range(5): - h(i) + @aq.main + def simple_range() -> None: + "Uses aq.range iterator for qubit index." + for i in aq.range(5): + h(i) + + with pytest.raises(errors.UnknownQubitCountError): + simple_range.build() def test_program_with_expr() -> None: """Test that a program with expressions for the qubit index raises an error if the user doesn't specify the number of qubits. """ - with pytest.raises(errors.UnknownQubitCountError): - @aq.main - def qubit_expr() -> None: - "Uses aq.range iterator for qubit index." - for i in aq.range(5): - h(i + 3) + @aq.main + def qubit_expr() -> None: + "Uses aq.range iterator for qubit index." + for i in aq.range(5): + h(i + 3) + + with pytest.raises(errors.UnknownQubitCountError): + qubit_expr.build() def test_multi_for_loop() -> None: @@ -931,6 +949,16 @@ def empty_program() -> None: assert empty_program.to_ir() == expected +def test_to_ir_implicit_build(empty_program) -> None: + """Test that to_ir works as expected with and without implicit build.""" + expected = """OPENQASM 3.0;""" + assert empty_program.build().to_ir(allow_implicit_build=False) == expected + assert empty_program.build().to_ir(allow_implicit_build=True) == expected + assert empty_program.to_ir(allow_implicit_build=True) == expected + with pytest.raises(RuntimeError): + empty_program.to_ir(allow_implicit_build=False) + + def test_main_no_return(): @aq.subroutine def tester(x: int) -> int: @@ -941,6 +969,57 @@ def main(): x = 3 tester(x) + expected = """OPENQASM 3.0; +def tester(int[32] x) -> bit { + bit __bit_0__; + __bit_0__ = measure __qubits__[x]; + return __bit_0__; +} +qubit[3] __qubits__; +bit __bit_1__; +__bit_1__ = tester(3);""" + + assert main.to_ir() == expected + + +def test_subroutine_declared_after_main(): + """Test that subroutines can be declared after the main function + and redeclared after initial serialization.""" + + @aq.main + def main() -> None: + my_subroutine() + + @aq.subroutine + def my_subroutine() -> None: + h(0) + cnot(0, 1) + + expected = """OPENQASM 3.0; +def my_subroutine() { + h __qubits__[0]; + cnot __qubits__[0], __qubits__[1]; +} +qubit[2] __qubits__; +my_subroutine();""" + + assert main.to_ir() == expected + + @aq.subroutine + def my_subroutine() -> None: # noqa: F811 + h(2) + cnot(2, 3) + + expected = """OPENQASM 3.0; +def my_subroutine() { + h __qubits__[2]; + cnot __qubits__[2], __qubits__[3]; +} +qubit[4] __qubits__; +my_subroutine();""" + + assert main.to_ir() == expected + def test_subroutine_args(): """Test that subroutines will fail if supplied args.""" @@ -985,11 +1064,12 @@ def bell(q0: int, q1: int) -> None: h(q0) cnot(q0, q1) - with pytest.raises(errors.AutoQasmTypeError): + @aq.main + def main(): + bell(0, 1) - @aq.main - def main(): - bell(0, 1) + with pytest.raises(errors.AutoQasmTypeError): + main.build() def test_empty_decorator_parentheses(): @@ -1058,8 +1138,9 @@ def circ(q: int, r: int): def test_input_qubit_indices_needs_num_qubits(): - with pytest.raises(errors.UnknownQubitCountError): + @aq.main + def circ(q: int, r: int): + h(2 * q + r) - @aq.main - def circ(q: int, r: int): - h(2 * q + r) + with pytest.raises(errors.UnknownQubitCountError): + circ.build() diff --git a/test/unit_tests/braket/experimental/autoqasm/test_converters.py b/test/unit_tests/braket/experimental/autoqasm/test_converters.py index 14992a75..a916f322 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_converters.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_converters.py @@ -66,20 +66,22 @@ def fn() -> None: def test_break_for_loop(): - with pytest.raises(aq.errors.UnsupportedFeatureError): + @aq.main + def main(): + for i in aq.range(3): + aq.gates.h(i) + break - @aq.main - def main(): - for i in aq.range(3): - aq.gates.h(i) - break + with pytest.raises(aq.errors.UnsupportedFeatureError): + main.build() def test_break_while_loop(): - with pytest.raises(aq.errors.UnsupportedFeatureError): + @aq.main + def uses_while_w_break(): + while aq.gates.measure(0): + aq.gates.x(0) + break - @aq.main - def uses_while_w_break(): - while aq.gates.measure(0): - aq.gates.x(0) - break + with pytest.raises(aq.errors.UnsupportedFeatureError): + uses_while_w_break.build() diff --git a/test/unit_tests/braket/experimental/autoqasm/test_devices.py b/test/unit_tests/braket/experimental/autoqasm/test_devices.py index 9c2dbfd7..d3f67162 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_devices.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_devices.py @@ -126,31 +126,33 @@ def test_device_parameter( for device in devices: - @aq.main(device=device) + @aq.main def my_program(): h(0) - assert my_program.to_ir() + assert my_program.build(device=device).to_ir() def test_insufficient_qubits(aws_device: Mock) -> None: aws_device.properties.paradigm.qubitCount = 9 - with pytest.raises(errors.InsufficientQubitCountError): + @aq.main(num_qubits=10) + def my_program(): + pass - @aq.main(device=aws_device, num_qubits=10) - def my_program(): - pass + with pytest.raises(errors.InsufficientQubitCountError): + my_program.build(device=aws_device) def test_unsupported_gate(aws_device: Mock) -> None: aws_device.properties.action[DeviceActionType.OPENQASM].supportedOperations = ["h"] - with pytest.raises(errors.UnsupportedGate): + @aq.main + def my_program(): + cphaseshift00(0, 1, 0.123) - @aq.main(device=aws_device) - def my_program(): - cphaseshift00(0, 1, 0.123) + with pytest.raises(errors.UnsupportedGate): + my_program.build(device=aws_device) def test_unsupported_native_gate(aws_device: Mock) -> None: @@ -158,13 +160,14 @@ def test_unsupported_native_gate(aws_device: Mock) -> None: aws_device.properties.action[DeviceActionType.OPENQASM].supportedPragmas = ["verbatim"] aws_device.properties.paradigm.nativeGateSet = ["x"] - with pytest.raises(errors.UnsupportedNativeGate): + @aq.main + def my_program(): + with aq.verbatim(): + x("$0") + h("$0") - @aq.main(device=aws_device) - def my_program(): - with aq.verbatim(): - x("$0") - h("$0") + with pytest.raises(errors.UnsupportedNativeGate): + my_program.build(device=aws_device) def test_supported_native_gate_inside_gate_definition(aws_device: Mock) -> None: @@ -172,17 +175,17 @@ def test_supported_native_gate_inside_gate_definition(aws_device: Mock) -> None: aws_device.properties.action[DeviceActionType.OPENQASM].supportedPragmas = ["verbatim"] aws_device.properties.paradigm.nativeGateSet = ["x"] - @aq.gate - def my_gate(q: aq.Qubit): - x(q) - - @aq.main(device=aws_device) + @aq.main def my_program(): with aq.verbatim(): x("$0") my_gate("$0") - assert my_program.to_ir() + @aq.gate + def my_gate(q: aq.Qubit): + x(q) + + assert my_program.build(device=aws_device).to_ir() def test_unsupported_native_gate_inside_gate_definition(aws_device: Mock) -> None: @@ -194,23 +197,25 @@ def test_unsupported_native_gate_inside_gate_definition(aws_device: Mock) -> Non def my_gate(q: aq.Qubit): h(q) - with pytest.raises(errors.UnsupportedNativeGate): + @aq.main + def my_program(): + with aq.verbatim(): + my_gate("$0") - @aq.main(device=aws_device) - def my_program(): - with aq.verbatim(): - my_gate("$0") + with pytest.raises(errors.UnsupportedNativeGate): + my_program.build(device=aws_device) def test_unsupported_verbatim_block(aws_device: Mock) -> None: aws_device.properties.action[DeviceActionType.OPENQASM].supportedPragmas = [] - with pytest.raises(errors.VerbatimBlockNotAllowed): + @aq.main + def my_program(): + with aq.verbatim(): + h("$0") - @aq.main(device=aws_device) - def my_program(): - with aq.verbatim(): - h("$0") + with pytest.raises(errors.VerbatimBlockNotAllowed): + my_program.build(device=aws_device) def test_validate_connectivity(aws_device: Mock) -> None: @@ -220,34 +225,35 @@ def test_validate_connectivity(aws_device: Mock) -> None: aws_device.properties.paradigm.connectivity.fullyConnected = False aws_device.properties.paradigm.connectivity.connectivityGraph = {"0": ["2"], "1": ["0"]} - with pytest.raises(errors.InvalidTargetQubit): + @aq.main + def my_program_invalid(): + with aq.verbatim(): + h("$0") + cnot("$0", "$1") - @aq.main(device=aws_device) - def my_program_invalid(): - with aq.verbatim(): - h("$0") - cnot("$0", "$1") + with pytest.raises(errors.InvalidTargetQubit): + my_program_invalid.build(device=aws_device) - @aq.main(device=aws_device) + @aq.main def my_program(): with aq.verbatim(): h("$0") cnot("$0", "$2") cnot("$1", "$0") - assert my_program.to_ir() + assert my_program.build(device=aws_device).to_ir() aws_device.properties.paradigm.connectivity.fullyConnected = True aws_device.properties.paradigm.connectivity.connectivityGraph = {} - @aq.main(device=aws_device) + @aq.main def my_program(): with aq.verbatim(): h("$0") cnot("$0", "$7") cnot("$5", "$2") - assert my_program.to_ir() + assert my_program.build(device=aws_device).to_ir() @pytest.mark.parametrize( diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py index 37afdb4a..000e37d9 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py @@ -51,7 +51,7 @@ def my_program(): rx(1.0) $1; """ ).strip() - qasm = my_program.with_calibrations([cal_1, cal_2]).to_ir() + qasm = my_program.build().with_calibrations([cal_1, cal_2]).to_ir() assert qasm == expected @@ -75,7 +75,7 @@ def my_program(): rx(1.0) $1; """ ).strip() - qasm = my_program.with_calibrations(cal_1).to_ir() + qasm = my_program.build().with_calibrations(cal_1).to_ir() assert qasm == expected @@ -91,7 +91,7 @@ def my_program(): rx("$1", 1.0) with pytest.raises(errors.InvalidCalibrationDefinition): - my_program.with_calibrations(cal_1) + my_program.build().with_calibrations(cal_1) def test_gate_calibrations_invalid_type(): @@ -123,7 +123,7 @@ def my_program(): for cal in [cal_1, cal_2, cal_3, cal_4, cal_5]: with pytest.raises(errors.ParameterTypeError): - my_program.with_calibrations(cal) + my_program.build().with_calibrations(cal) def test_gate_calibrations_insufficient_args(): @@ -142,10 +142,10 @@ def my_program(): rx("$1", 1.0) with pytest.raises(errors.InvalidCalibrationDefinition): - my_program.with_calibrations(cal_1) + my_program.build().with_calibrations(cal_1) with pytest.raises(errors.InvalidCalibrationDefinition): - my_program.with_calibrations(cal_2) + my_program.build().with_calibrations(cal_2) def test_gate_calibrations_duplicated_args(): @@ -160,7 +160,7 @@ def my_program(): rx("$1", 1.0) with pytest.raises(errors.InvalidCalibrationDefinition): - my_program.with_calibrations(cal_1) + my_program.build().with_calibrations(cal_1) def test_gate_calibrations_invalid_instructions(): @@ -176,7 +176,7 @@ def my_program(): rx("$1", 1.0) with pytest.raises(errors.InvalidCalibrationDefinition): - my_program.with_calibrations(cal_1) + my_program.build().with_calibrations(cal_1) def test_gate_calibrations_bind_calibrations_not_inplace(): @@ -191,7 +191,7 @@ def my_program(): rx("$1", 1.0) program_1 = my_program - program_2 = my_program.with_calibrations(cal_1) + program_2 = my_program.build().with_calibrations(cal_1) program_3 = my_program assert program_1.to_ir() == program_3.to_ir() != program_2.to_ir() @@ -227,5 +227,5 @@ def my_program(): my_gate(0.123) __qubits__[2]; """ ).strip() - qasm = my_program.with_calibrations(cal_1).to_ir() + qasm = my_program.build().with_calibrations(cal_1).to_ir() assert qasm == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py index b5fad84b..319a7810 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py @@ -68,11 +68,12 @@ class MyGate: def __init__(self, q: aq.Qubit): h(q) - with pytest.raises(ValueError): + @aq.main + def main(): + MyGate(0) - @aq.main - def main(): - MyGate(0) + with pytest.raises(ValueError): + main.build() def test_invalid_symbol() -> None: @@ -81,11 +82,12 @@ def my_gate(q: aq.Qubit): h(q) not_a_symbol() # noqa: F821 # type: ignore - with pytest.raises(NameError): + @aq.main + def main(): + my_gate(0) - @aq.main - def main(): - my_gate(0) + with pytest.raises(NameError): + main.build() def test_duplicate_gate_names() -> None: @@ -151,14 +153,15 @@ def my_gate(q0: aq.Qubit, q1: aq.Qubit): h(q0) x(q1) + @aq.main + def incorrect_arg_count(): + my_gate(0) + with pytest.raises( errors.ParameterTypeError, match='Incorrect number of arguments passed to gate "my_gate". Expected 2, got 1.', ): - - @aq.main - def incorrect_arg_count(): - my_gate(0) + incorrect_arg_count.build() def test_incorrect_arg_types() -> None: @@ -167,11 +170,12 @@ def my_gate(q: aq.Qubit, theta: float): h(q) rx(q, theta) - with pytest.raises(TypeError): + @aq.main + def incorrect_arg_types(): + my_gate(0.25, 0) - @aq.main - def incorrect_arg_types(): - my_gate(0.25, 0) + with pytest.raises(TypeError): + incorrect_arg_types.build() def test_missing_annotation() -> None: @@ -179,11 +183,12 @@ def test_missing_annotation() -> None: def my_gate(a): pass - with pytest.raises(errors.MissingParameterTypeError): + @aq.main + def my_program(): + my_gate("test") - @aq.main - def my_program(): - my_gate("test") + with pytest.raises(errors.MissingParameterTypeError): + my_program.build() def test_incorrect_annotation() -> None: @@ -191,11 +196,12 @@ def test_incorrect_annotation() -> None: def my_gate(a: str): pass - with pytest.raises(errors.ParameterTypeError): + @aq.main + def my_program(): + my_gate("test") - @aq.main - def my_program(): - my_gate("test") + with pytest.raises(errors.ParameterTypeError): + my_program.build() def test_no_qubit_args() -> None: @@ -203,14 +209,15 @@ def test_no_qubit_args() -> None: def not_a_gate(angle: float): pass + @aq.main + def my_program(): + not_a_gate(np.pi) + with pytest.raises( errors.ParameterTypeError, match='Gate definition "not_a_gate" has no arguments of type aq.Qubit.', ): - - @aq.main - def my_program(): - not_a_gate(np.pi) + my_program.build() def test_invalid_qubit_used() -> None: @@ -219,14 +226,15 @@ def my_gate(q: aq.Qubit): h(q) x(1) # invalid + @aq.main + def my_program(): + my_gate(0) + with pytest.raises( errors.InvalidGateDefinition, match='Gate definition "my_gate" uses qubit "1" which is not an argument to the gate.', ): - - @aq.main - def my_program(): - my_gate(0) + my_program.build() def test_invalid_angle_used() -> None: @@ -238,14 +246,15 @@ def my_gate(q: aq.Qubit, theta: float): rx(q, theta) rx(q, beta) # invalid + @aq.main + def my_program(): + my_gate(0, np.pi / 2) + with pytest.raises( errors.InvalidGateDefinition, match='Gate definition "my_gate" uses angle (.*) which is not an argument to the gate.', ): - - @aq.main - def my_program(): - my_gate(0, np.pi / 2) + my_program.build() def test_invalid_instruction() -> None: @@ -254,14 +263,15 @@ def my_gate(q: aq.Qubit): h(q) reset(q) # invalid + @aq.main + def my_program(): + my_gate(0) + with pytest.raises( errors.InvalidGateDefinition, match='Gate definition "my_gate" contains invalid operations.', ): - - @aq.main - def my_program(): - my_gate(0) + my_program.build() def test_invalid_control_flow() -> None: @@ -271,14 +281,15 @@ def my_gate(q: aq.Qubit): if measure(q): x(q) + @aq.main + def my_program(): + my_gate(0) + with pytest.raises( errors.InvalidGateDefinition, match='Gate definition "my_gate" contains invalid operations.', ): - - @aq.main - def my_program(): - my_gate(0) + my_program.build() def test_nested_gates() -> None: diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py index 7abbcc62..e3956406 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_operators.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py @@ -120,11 +120,12 @@ def cond_exp_assignment(): def test_unsupported_inline_conditional_assignment(else_value) -> None: """Tests conditional expression where the if and else clauses return different types.""" - with pytest.raises(UnsupportedConditionalExpressionError): + @aq.main + def cond_exp_assignment_different_types(): + a = aq.IntVar(1) if aq.BoolVar(True) else else_value() # noqa: F841 - @aq.main - def cond_exp_assignment_different_types(): - a = aq.IntVar(1) if aq.BoolVar(True) else else_value() # noqa: F841 + with pytest.raises(UnsupportedConditionalExpressionError): + cond_exp_assignment_different_types.build() def test_branch_assignment_undeclared() -> None: @@ -831,15 +832,16 @@ def test_control_flow(): def test_py_assert() -> None: """Test Python assertions inside an AutoQASM program.""" + @aq.main + def test_input_assert(value: bool): + assert value + not_supported = ( "Assertions are not supported for values that depend on " "measurement results or AutoQASM variables." ) with pytest.raises(NotImplementedError, match=not_supported): - - @aq.main - def test_input_assert(value: bool): - assert value + test_input_assert.build() true_var = 1 false_var = False @@ -848,21 +850,25 @@ def test_input_assert(value: bool): def test_assert(): assert true_var - with pytest.raises(AssertionError): + test_assert.to_ir() # does not raise an exception - @aq.main - def test_assert_false(): - assert false_var + @aq.main + def test_assert_false(): + assert false_var + + with pytest.raises(AssertionError): + test_assert_false.build() def test_measurement_assert() -> None: """Test assertions on measurement results inside an AutoQASM program.""" - with pytest.raises(NotImplementedError): + @aq.main + def test_assert(): + assert measure(0) - @aq.main - def test_assert(): - assert measure(0) + with pytest.raises(NotImplementedError): + test_assert.build() def test_py_list_ops() -> None: @@ -876,3 +882,5 @@ def test_list_ops(): assert b == 1 c = np.stack([a, a]) assert np.array_equal(c, [[2, 3, 4], [2, 3, 4]]) + + assert test_list_ops.to_ir() diff --git a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py index 9dd52e97..85b1ee07 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py @@ -345,7 +345,7 @@ def my_program(theta): delay[angle * 1s] $1; } rx(theta) $1;""" - qasm = my_program.with_calibrations(cal_1).to_ir() + qasm = my_program.build().with_calibrations(cal_1).to_ir() assert qasm == expected @@ -372,12 +372,12 @@ def parametric(alpha: float): __bit_0__ = measure __qubits__[0];""" assert parametric.to_ir() == unbound_expected - bound_prog = parametric.make_bound_program({"alpha": 0.5}) + bound_prog = parametric.build().make_bound_program({"alpha": 0.5}) # Original program unchanged assert parametric.to_ir() == unbound_expected assert bound_prog.to_ir() == bound_template.format(0.5) # Can rebind - bound_prog = parametric.make_bound_program({"alpha": 0.432143}) + bound_prog = parametric.build().make_bound_program({"alpha": 0.432143}) assert bound_prog.to_ir() == bound_template.format(0.432143) @@ -401,7 +401,7 @@ def parametric(alpha: float, beta: float): sub(alpha, beta) rx_alpha(2) - bound_prog = parametric.make_bound_program({"alpha": 0.5, "beta": 1.5}) + bound_prog = parametric.build().make_bound_program({"alpha": 0.5, "beta": 1.5}) expected = """OPENQASM 3.0; def sub(float[64] alpha, float[64] theta) { @@ -434,7 +434,7 @@ def parametric(alpha: float, beta: float): rx_alpha(2, alpha) rx_alpha(2, beta) - bound_prog = parametric.make_bound_program({"beta": np.pi}) + bound_prog = parametric.build().make_bound_program({"beta": np.pi}) expected = """OPENQASM 3.0; def rx_alpha(int[32] qubit, float[64] theta) { @@ -459,8 +459,8 @@ def cal_1(angle: float): def my_program(theta): rx("$1", theta) - qasm1 = my_program.with_calibrations(cal_1).make_bound_program({"theta": 0.6}).to_ir() - qasm2 = my_program.make_bound_program({"theta": 0.6}).with_calibrations(cal_1).to_ir() + qasm1 = my_program.build().with_calibrations(cal_1).make_bound_program({"theta": 0.6}).to_ir() + qasm2 = my_program.build().make_bound_program({"theta": 0.6}).with_calibrations(cal_1).to_ir() assert qasm1 == qasm2 expected = """OPENQASM 3.0; @@ -480,8 +480,8 @@ def empty_program(): pass qasm = empty_program.to_ir() - bound_program1 = empty_program.make_bound_program({}).to_ir() - bound_program2 = empty_program.make_bound_program({"alpha": 0.5}).to_ir() + bound_program1 = empty_program.build().make_bound_program({}).to_ir() + bound_program2 = empty_program.build().make_bound_program({"alpha": 0.5}).to_ir() assert qasm == bound_program1 == bound_program2 @@ -500,7 +500,7 @@ def parametric(alpha: float): bit __bit_0__; __bit_0__ = measure __qubits__[0];""" - bound_prog = parametric.make_bound_program({"alpha": 0.5}, strict=True) + bound_prog = parametric.build().make_bound_program({"alpha": 0.5}, strict=True) assert bound_prog.to_ir() == template.format(0.5) @@ -515,25 +515,27 @@ def parametric(alpha: float): with pytest.raises( aq.errors.ParameterNotFoundError, match="No parameter in the program named: beta" ): - parametric.make_bound_program({"beta": 0.5}, strict=True) + parametric.build().make_bound_program({"beta": 0.5}, strict=True) def test_duplicate_variable_name_fails(): """Test using a variable and FreeParameter with the same name.""" + @aq.main + def parametric_explicit(): + alpha = aq.FloatVar(1.2) # noqa: F841 + rx(0, FreeParameter("alpha")) + with pytest.raises(RuntimeError, match="conflicting variables with name alpha"): + parametric_explicit.build() - @aq.main - def parametric_explicit(): - alpha = aq.FloatVar(1.2) # noqa: F841 - rx(0, FreeParameter("alpha")) + @aq.main + def parametric(alpha): + alpha = aq.FloatVar(1.2) # noqa: F841 + rx(0, alpha) with pytest.raises(RuntimeError, match="conflicting variables with name alpha"): - - @aq.main - def parametric(alpha): - alpha = aq.FloatVar(1.2) # noqa: F841 - rx(0, alpha) + parametric.build() def test_binding_variable_fails(): @@ -546,7 +548,7 @@ def parametric(): with pytest.raises( aq.errors.ParameterNotFoundError, match="No parameter in the program named: beta" ): - parametric.make_bound_program({"beta": 0.5}, strict=True) + parametric.build().make_bound_program({"beta": 0.5}, strict=True) def test_compound_condition(): @@ -829,9 +831,9 @@ def parametric(val: float): }} bit __bit_1__; __bit_1__ = measure __qubits__[0];""" - bound_prog = parametric.make_bound_program({"val": 0}) + bound_prog = parametric.build().make_bound_program({"val": 0}) assert bound_prog.to_ir() == template.format(0) - bound_prog = parametric.make_bound_program({"val": 1}) + bound_prog = parametric.build().make_bound_program({"val": 1}) assert bound_prog.to_ir() == template.format(1) @@ -898,7 +900,7 @@ def parametric(phi): float phi = 1.5707963267948966; qubit[1] __qubits__; rx(2 * phi) __qubits__[0];""" - assert parametric.make_bound_program({"phi": np.pi / 2}).to_ir() == expected + assert parametric.build().make_bound_program({"phi": np.pi / 2}).to_ir() == expected def test_partially_bound_parameter_expressions(): @@ -914,7 +916,7 @@ def parametric(prefactor, theta): input float theta; qubit[1] __qubits__; gpi(prefactor * theta) __qubits__[0];""" - assert parametric.make_bound_program({"prefactor": 3}).to_ir() == expected + assert parametric.build().make_bound_program({"prefactor": 3}).to_ir() == expected def test_subroutine_parameter_expressions(): diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py b/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py index 2ffe007c..9ee0596c 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py @@ -56,19 +56,21 @@ def program_func() -> None: def test_nested_verbatim_box() -> None: - with pytest.raises(errors.VerbatimBlockNotAllowed): - - @aq.main - def program_func() -> None: + @aq.main + def program_func() -> None: + with aq.verbatim(): with aq.verbatim(): - with aq.verbatim(): - h(0) + h(0) + + with pytest.raises(errors.VerbatimBlockNotAllowed): + program_func.build() def test_verbatim_box_invalid_target_qubit() -> None: - with pytest.raises(errors.InvalidTargetQubit): + @aq.main + def program_func() -> None: + with aq.verbatim(): + h(0) - @aq.main - def program_func() -> None: - with aq.verbatim(): - h(0) + with pytest.raises(errors.InvalidTargetQubit): + program_func.build() diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/braket/experimental/autoqasm/test_program.py index b70edf8c..1876b630 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_program.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_program.py @@ -149,5 +149,5 @@ def teleportation(theta): b"\x1b[39;49;00m(theta)\x1b[37m \x1b[39;49;00m__qubits__[\x1b[34m1\x1b[39;49;00m];" b"\x1b[37m\x1b[39;49;00m\n" ).decode("utf-8") - teleportation.display() + teleportation.build().display() mock_print.assert_called_with(expected) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pulse.py b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py index 86389112..34cc0afc 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_pulse.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py @@ -222,7 +222,7 @@ def my_program(duration): } """ ).strip() - qasm = my_program.make_bound_program({"duration": 0.123}).to_ir() + qasm = my_program.build().make_bound_program({"duration": 0.123}).to_ir() assert qasm == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_return.py b/test/unit_tests/braket/experimental/autoqasm/test_return.py index 8f3aa786..1d3b614f 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_return.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_return.py @@ -200,11 +200,12 @@ def main(): def test_name_collisions(): - with pytest.raises(aq.errors.NameConflict): + @aq.main + def main(val): + return val - @aq.main - def main(val): - return val + with pytest.raises(aq.errors.NameConflict): + main.build() def test_return_inputs(): diff --git a/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py b/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py index 7f34a255..aa32bb5b 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py @@ -27,11 +27,12 @@ def test_convert_invalid_main_object() -> None: """Tests the aq.main decorator on something that is not a function.""" - with pytest.raises(ValueError): + @aq.main + class MyClass: + pass - @aq.main - class MyClass: - pass + with pytest.raises(ValueError): + MyClass.build() def test_convert_invalid_subroutine_object() -> None: @@ -41,11 +42,12 @@ def test_convert_invalid_subroutine_object() -> None: class MyClass: pass - with pytest.raises(ValueError): + @aq.main + def main(): + MyClass() - @aq.main - def main(): - MyClass() + with pytest.raises(ValueError): + main.build() def test_autograph_disabled() -> None: @@ -53,13 +55,15 @@ def test_autograph_disabled() -> None: and verifies that the function is not converted.""" with ControlStatusCtx(Status.DISABLED): - with pytest.raises(RuntimeError): - @aq.main - def my_program(): - h(0) - if measure(0): - x(0) + @aq.main + def my_program(): + h(0) + if measure(0): + x(0) + + with pytest.raises(RuntimeError): + my_program.build() def test_partial_function() -> None: @@ -80,8 +84,10 @@ def bell(q0: int, q1: int): h __qubits__[1]; cnot __qubits__[1], __qubits__[3];""" + bell_partial_no_num_qubits = aq.main(functools.partial(bell, 1)) with pytest.raises(UnknownQubitCountError): - aq.main(functools.partial(bell, 1)) + bell_partial_no_num_qubits.build() + bell_partial = aq.main(num_qubits=4)(functools.partial(bell, 1)) assert bell_partial.to_ir() == expected_partial diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index 5be4c6c9..e1fa630e 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -204,65 +204,72 @@ def declare_array(): def test_invalid_array_assignment(): """Test invalid array assignment.""" - with pytest.raises(aq.errors.InvalidAssignmentStatement): - @aq.main - def invalid(): - a = aq.ArrayVar([1, 2, 3], base_type=aq.IntVar, dimensions=[3]) - b = aq.ArrayVar([4, 5], base_type=aq.IntVar, dimensions=[2]) - a = b # noqa: F841 + @aq.main + def invalid(): + a = aq.ArrayVar([1, 2, 3], base_type=aq.IntVar, dimensions=[3]) + b = aq.ArrayVar([4, 5], base_type=aq.IntVar, dimensions=[2]) + a = b # noqa: F841 + + with pytest.raises(aq.errors.InvalidAssignmentStatement): + invalid.build() def test_declare_array_in_local_scope(): """Test declaring an array inside a local scope.""" - with pytest.raises(aq.errors.InvalidArrayDeclaration): - @aq.main - def declare_array(): - if aq.BoolVar(True): - _ = aq.ArrayVar([1, 2, 3], base_type=aq.IntVar, dimensions=[3]) + @aq.main + def declare_array(): + if aq.BoolVar(True): + _ = aq.ArrayVar([1, 2, 3], base_type=aq.IntVar, dimensions=[3]) + + with pytest.raises(aq.errors.InvalidArrayDeclaration): + declare_array.build() def test_declare_array_in_subroutine(): """Test declaring an array inside a subroutine.""" + @aq.main + def main() -> list[int]: + return declare_array() + @aq.subroutine def declare_array(): _ = aq.ArrayVar([1, 2, 3], dimensions=[3]) with pytest.raises(aq.errors.InvalidArrayDeclaration): - - @aq.main - def main() -> list[int]: - return declare_array() + main.build() def test_return_python_array(): """Test returning a python array of ints.""" + @aq.main(num_qubits=4) + def main(): + tester() + @aq.subroutine def tester() -> list[int]: return [1, 2, 3] with pytest.raises(aq.errors.UnsupportedSubroutineReturnType): - - @aq.main(num_qubits=4) - def main(): - tester() + main.build() def test_return_array_unsupported(): """Test unsupported array type.""" + @aq.main(num_qubits=4) + def main(): + tester([3.3]) + @aq.subroutine def tester(arr: list[float]) -> list[float]: return [1.2, 2.1] with pytest.raises(aq.errors.ParameterTypeError): - - @aq.main(num_qubits=4) - def main(): - tester([3.3]) + main.build() def test_return_func_call(): @@ -374,12 +381,13 @@ def test_map_array(): def annotation_test(input: list[int]): pass - with pytest.raises(aq.errors.ParameterTypeError): + @aq.main + def main(): + a = aq.ArrayVar([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dimensions=[10]) + annotation_test(a) - @aq.main - def main(): - a = aq.ArrayVar([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dimensions=[10]) - annotation_test(a) + with pytest.raises(aq.errors.ParameterTypeError): + main.build() def test_map_other(): @@ -605,11 +613,12 @@ def test_error_for_tuple_param() -> None: def param_test(input: tuple): pass - with pytest.raises(aq.errors.ParameterTypeError): + @aq.main + def main(): + param_test(aq.BitVar(1)) - @aq.main - def main(): - param_test(aq.BitVar(1)) + with pytest.raises(aq.errors.ParameterTypeError): + main.build() def test_error_for_missing_param_type() -> None: @@ -619,11 +628,12 @@ def test_error_for_missing_param_type() -> None: def param_test(input): pass - with pytest.raises(aq.errors.MissingParameterTypeError): + @aq.main + def main(): + param_test(aq.BitVar(1)) - @aq.main - def main(): - param_test(aq.BitVar(1)) + with pytest.raises(aq.errors.MissingParameterTypeError): + main.build() def test_ignore_ret_typehint_bool(): @@ -655,11 +665,12 @@ def test_ignore_ret_typehint_list(): def ret_test() -> int: return [1, 2, 3] - with pytest.raises(aq.errors.UnsupportedSubroutineReturnType): + @aq.main(num_qubits=4) + def main() -> float: + ret_test() - @aq.main(num_qubits=4) - def main() -> float: - ret_test() + with pytest.raises(aq.errors.UnsupportedSubroutineReturnType): + main.build() def test_ignore_missing_ret_typehint_list(): @@ -669,11 +680,12 @@ def test_ignore_missing_ret_typehint_list(): def ret_test(): return [1, 2, 3] - with pytest.raises(aq.errors.UnsupportedSubroutineReturnType): + @aq.main(num_qubits=4) + def main(): + ret_test() - @aq.main(num_qubits=4) - def main(): - ret_test() + with pytest.raises(aq.errors.UnsupportedSubroutineReturnType): + main.build() def test_ignore_missing_ret_typehint_float(): @@ -706,8 +718,9 @@ def test_param_array_list_missing_arg(): def param_test(arr: list) -> int: return 1 - with pytest.raises(aq.errors.ParameterTypeError): + @aq.main(num_qubits=4) + def main(): + param_test() - @aq.main(num_qubits=4) - def main(): - param_test() + with pytest.raises(aq.errors.ParameterTypeError): + main.build() From ebe3a8b594c5e63040bfcf84018381ab4ec2cea9 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:42:54 -0400 Subject: [PATCH 1078/1165] Change allow_implicit_build default to False (#909) --- .../1_Getting_started_with_AutoQASM.ipynb | 2 +- .../2_Expressing_classical_control_flow.ipynb | 4 +- examples/autoqasm/4_Native_programming.ipynb | 28 +++---- ...programming_and_dynamical_decoupling.ipynb | 4 +- .../6_Customize_gate_calibrations.ipynb | 10 +-- src/braket/aws/aws_quantum_task.py | 4 +- src/braket/circuits/serialization.py | 4 +- src/braket/experimental/autoqasm/README.md | 6 +- src/braket/experimental/autoqasm/__init__.py | 2 +- .../experimental/autoqasm/doc/decorators.md | 2 +- .../experimental/autoqasm/program/program.py | 4 +- .../experimental/autoqasm/test_annotations.py | 8 +- .../braket/experimental/autoqasm/test_api.py | 76 +++++++++---------- .../experimental/autoqasm/test_devices.py | 2 +- .../autoqasm/test_gate_calibrations.py | 4 +- .../autoqasm/test_gate_decorator.py | 12 +-- .../autoqasm/test_gate_definitions.py | 4 +- .../experimental/autoqasm/test_operators.py | 36 ++++----- .../experimental/autoqasm/test_parameters.py | 60 +++++++-------- .../experimental/autoqasm/test_pragmas.py | 2 +- .../experimental/autoqasm/test_program.py | 2 +- .../experimental/autoqasm/test_pulse.py | 8 +- .../experimental/autoqasm/test_return.py | 36 ++++----- .../autoqasm/test_serialization_config.py | 8 +- .../experimental/autoqasm/test_transpiler.py | 8 +- .../experimental/autoqasm/test_types.py | 46 +++++------ 26 files changed, 193 insertions(+), 189 deletions(-) diff --git a/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb b/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb index d42b0869..7f3bc068 100644 --- a/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb +++ b/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb @@ -80,7 +80,7 @@ } ], "source": [ - "print(bell_state.to_ir())" + "print(bell_state.build().to_ir())" ] }, { diff --git a/examples/autoqasm/2_Expressing_classical_control_flow.ipynb b/examples/autoqasm/2_Expressing_classical_control_flow.ipynb index e5d12c72..575cceec 100644 --- a/examples/autoqasm/2_Expressing_classical_control_flow.ipynb +++ b/examples/autoqasm/2_Expressing_classical_control_flow.ipynb @@ -87,7 +87,7 @@ " bell(2, 3)\n", "\n", "\n", - "print(two_bell.to_ir())" + "print(two_bell.build().to_ir())" ] }, { @@ -232,7 +232,7 @@ " bell(i, i + 1)\n", "\n", "\n", - "print(multiple_bell.to_ir())" + "print(multiple_bell.build().to_ir())" ] }, { diff --git a/examples/autoqasm/4_Native_programming.ipynb b/examples/autoqasm/4_Native_programming.ipynb index f20a3122..68b9ed64 100644 --- a/examples/autoqasm/4_Native_programming.ipynb +++ b/examples/autoqasm/4_Native_programming.ipynb @@ -68,7 +68,7 @@ " return measure([0, 1])\n", "\n", "\n", - "print(bell_state.to_ir())" + "print(bell_state.build().to_ir())" ] }, { @@ -114,7 +114,7 @@ " return measure([\"$0\", \"$1\"])\n", "\n", "\n", - "print(bell_state.to_ir())" + "print(bell_state.build().to_ir())" ] }, { @@ -126,9 +126,9 @@ "\n", "## Device-specific validation\n", "\n", - "Bypassing the mapping and compilation is only the first step of native programming. Because native programming is intended for targeting a program to a specific device, we need to specify the target device in the AutoQASM program. We can accomplish this by adding a `device` argument to the `@aq.main` decorator, passing the ARN of the Amazon Braket device (or, optionally, a `braket.devices.Device` object) that we want to target.\n", + "Bypassing the mapping and compilation is only the first step of native programming. Because native programming is intended for targeting a program to a specific device, we need to specify the target device in the AutoQASM program. We can accomplish this by adding a `device` argument to the `build()` call, passing the ARN of the Amazon Braket device (or, optionally, a `braket.devices.Device` object) that we want to target.\n", "\n", - "Here we modify the program to target the `Devices.IonQ.Aria1` device. When building this program, AutoQASM will validate that (among other things) the contents of any `verbatim` blocks respect the native gate set and connectivity of the target device. " + "Here we target the `Devices.IonQ.Aria1` device. When building this program, AutoQASM will validate that (among other things) the contents of any `verbatim` blocks respect the native gate set and connectivity of the target device. " ] }, { @@ -146,13 +146,15 @@ } ], "source": [ + "@aq.main\n", + "def bell_state():\n", + " with aq.verbatim():\n", + " h(\"$0\")\n", + " cnot(\"$0\", \"$1\")\n", + " return measure([\"$0\", \"$1\"])\n", + "\n", "try:\n", - " @aq.main(device=Devices.IonQ.Aria1)\n", - " def bell_state():\n", - " with aq.verbatim():\n", - " h(\"$0\")\n", - " cnot(\"$0\", \"$1\")\n", - " return measure([\"$0\", \"$1\"])\n", + " bell_state.build(device=Devices.IonQ.Aria1)\n", "except Exception as e:\n", " print(\"ERROR:\", e)" ] @@ -436,7 +438,7 @@ "from ionq_gates import h, cnot\n", "\n", "\n", - "@aq.main(device=Devices.IonQ.Aria1)\n", + "@aq.main\n", "def bell_state():\n", " with aq.verbatim():\n", " h(\"$0\")\n", @@ -444,7 +446,7 @@ " return measure([\"$0\", \"$1\"])\n", "\n", "\n", - "print(bell_state.to_ir())" + "print(bell_state.build(device=Devices.IonQ.Aria1).to_ir())" ] }, { @@ -482,7 +484,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.9.18" } }, "nbformat": 4, diff --git a/examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb b/examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb index 1d3a9129..cb250326 100644 --- a/examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb +++ b/examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb @@ -107,7 +107,7 @@ } ], "source": [ - "print(idle.to_ir())" + "print(idle.build().to_ir())" ] }, { @@ -254,7 +254,7 @@ } ], "source": [ - "print(idle_with_dd.to_ir())" + "print(idle_with_dd.build().to_ir())" ] }, { diff --git a/examples/autoqasm/6_Customize_gate_calibrations.ipynb b/examples/autoqasm/6_Customize_gate_calibrations.ipynb index 271efd65..76b379f8 100644 --- a/examples/autoqasm/6_Customize_gate_calibrations.ipynb +++ b/examples/autoqasm/6_Customize_gate_calibrations.ipynb @@ -206,7 +206,7 @@ "id": "9300f0f8", "metadata": {}, "source": [ - "Next, we will demonstrate how to attach `my_rx_cal` to the main quantum program `main_program`. The program is defined in the code block below." + "Next, we will demonstrate how to attach `my_rx_cal` to the main quantum program `my_program`. The program is defined in the code block below." ] }, { @@ -234,7 +234,7 @@ " rz(\"$1\", 0.123)\n", " measure(\"$0\")\n", "\n", - "print(my_program.to_ir())" + "print(my_program.build().to_ir())" ] }, { @@ -242,7 +242,7 @@ "id": "c7579516", "metadata": {}, "source": [ - "To attach gate calibrations to the program, call the `with_calibrations` method on `main_program` to create a new program with `my_rx_cal` attached, leaving the original intact. This allows you to experiment with different gate calibrations on the same program without the need to redefine the main program every time. \n", + "To attach gate calibrations to the program, call the `with_calibrations` method on the built program to create a new program with `my_rx_cal` attached, leaving the original intact. This allows you to experiment with different gate calibrations on the same program without the need to redefine the main program every time. \n", "\n", "In the printed OpenQASM script of the program, the gate definition for the `rx` gate becomes a `defcal` block." ] @@ -276,7 +276,7 @@ } ], "source": [ - "custom_program = my_program.with_calibrations(my_rx_cal)\n", + "custom_program = my_program.build().with_calibrations(my_rx_cal)\n", "print(custom_program.to_ir())" ] }, @@ -346,7 +346,7 @@ } ], "source": [ - "custom_program = my_program.with_calibrations([my_rx_cal, my_rz_cal])\n", + "custom_program = my_program.build().with_calibrations([my_rx_cal, my_rz_cal])\n", "print(custom_program.to_ir())" ] }, diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 0699aff8..28057629 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -644,7 +644,9 @@ def _( *args, **kwargs, ) -> AwsQuantumTask: - openqasm_program = OpenQASMProgram(source=serializable_program.to_ir(ir_type=IRType.OPENQASM)) + openqasm_program = OpenQASMProgram( + source=serializable_program.to_ir(ir_type=IRType.OPENQASM, allow_implicit_build=True) + ) return _create_internal( openqasm_program, aws_session, diff --git a/src/braket/circuits/serialization.py b/src/braket/circuits/serialization.py index fae47816..2aa47f49 100644 --- a/src/braket/circuits/serialization.py +++ b/src/braket/circuits/serialization.py @@ -38,7 +38,7 @@ class SerializableProgram(ABC): def to_ir( self, ir_type: IRType = IRType.OPENQASM, - allow_implicit_build: bool = True, + allow_implicit_build: bool = False, ) -> str: """Serializes the program into an intermediate representation. @@ -46,7 +46,7 @@ def to_ir( ir_type (IRType): The IRType to use for converting the program to its IR representation. Defaults to IRType.OPENQASM. allow_implicit_build (bool): Whether to allow the program to be implicitly - built as a side effect of calling this function. Defaults to True. + built as a side effect of calling this function. Defaults to False. Raises: ValueError: Raised if the supplied `ir_type` is not supported. diff --git a/src/braket/experimental/autoqasm/README.md b/src/braket/experimental/autoqasm/README.md index 5025b592..9e4aa8ad 100644 --- a/src/braket/experimental/autoqasm/README.md +++ b/src/braket/experimental/autoqasm/README.md @@ -167,9 +167,9 @@ represent a quantum program equivalently in either format, but using AutoQASM allows one to also make use of Python, including the Amazon Braket SDK. AutoQASM can be seen as implementing a builder pattern for OpenQASM. It -allows you serialize your program to OpenQASM with `Program.to_ir()`. The -interface is not strongly tied to OpenQASM, so we could serialize to other -formats in the future. +allows you serialize your program to OpenQASM by calling `to_ir()` on the +built program. The interface is not strongly tied to OpenQASM, so we could +serialize to other formats in the future. ### 3. What is the relationship between AutoQASM and the Amazon Braket SDK? diff --git a/src/braket/experimental/autoqasm/__init__.py b/src/braket/experimental/autoqasm/__init__.py index adc69a3b..976ac00f 100644 --- a/src/braket/experimental/autoqasm/__init__.py +++ b/src/braket/experimental/autoqasm/__init__.py @@ -27,7 +27,7 @@ def my_program(): return result program = my_program() - print(program.to_ir()) + print(program.build().to_ir()) The Python code above outputs the following OpenQASM program: diff --git a/src/braket/experimental/autoqasm/doc/decorators.md b/src/braket/experimental/autoqasm/doc/decorators.md index 7fcc166a..79e07297 100644 --- a/src/braket/experimental/autoqasm/doc/decorators.md +++ b/src/braket/experimental/autoqasm/doc/decorators.md @@ -51,7 +51,7 @@ def two_bell() -> None: bell(2, 3) ``` -Let's take a look at the serialized output from `two_bell.to_ir()`, which shows that the modularity of the subroutine is preserved. +Let's take a look at the serialized output from `two_bell.build().to_ir()`, which shows that the modularity of the subroutine is preserved. ``` OPENQASM 3.0; diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 3000dee4..678baf12 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -114,7 +114,7 @@ def build(self, device: Device | str | None = None) -> Program: def to_ir( self, ir_type: IRType = IRType.OPENQASM, - allow_implicit_build: bool = True, + allow_implicit_build: bool = False, serialization_properties: SerializationProperties = OpenQASMSerializationProperties(), ) -> str: """Serializes the program into an intermediate representation. @@ -123,7 +123,7 @@ def to_ir( ir_type (IRType): The IRType to use for converting the program to its IR representation. Defaults to IRType.OPENQASM. allow_implicit_build (bool): Whether to allow the program to be implicitly - built as a side effect of calling this function. Defaults to True. + built as a side effect of calling this function. Defaults to False. serialization_properties (SerializationProperties): IR serialization configuration. Default to OpenQASMSerializationProperties(). diff --git a/test/unit_tests/braket/experimental/autoqasm/test_annotations.py b/test/unit_tests/braket/experimental/autoqasm/test_annotations.py index 590a3e6a..da1a7464 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_annotations.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_annotations.py @@ -53,7 +53,7 @@ def main(): """ + f"{qasm_type} b = {qasm_value};" ) - assert main.to_ir() == expected + assert main.build().to_ir() == expected @pytest.mark.parametrize( @@ -87,7 +87,7 @@ def main(): subroutine_test();""" ) - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_range_annotations(): @@ -112,7 +112,7 @@ def main(): h __qubits__[i]; }""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_verbatim_box_annotations(): @@ -138,4 +138,4 @@ def main(): cnot $0, $1; }""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index ab824cfb..de113b03 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -36,7 +36,7 @@ def _test_on_local_sim(program: aq.Program, inputs=None) -> None: def test_empty_function(empty_program) -> None: """Test that a function with no instructions generates an empty program.""" expected = """OPENQASM 3.0;""" - assert empty_program.to_ir() == expected + assert empty_program.build().to_ir() == expected def test_sim_empty(empty_program) -> None: @@ -54,7 +54,7 @@ def test_multiple_calls(empty_subroutine, bell_state_subroutine) -> None: """ def count_function_calls(program: aq.Program, func_name: str) -> int: - return program.to_ir().count(f"{func_name}();") + return program.build().to_ir().count(f"{func_name}();") def empty_program_wrapper(): empty_subroutine() @@ -100,7 +100,7 @@ def physical_bell() { bell_state(); physical_bell();""" - assert call_subroutines.to_ir() == expected + assert call_subroutines.build().to_ir() == expected _test_on_local_sim(call_subroutines) @@ -143,7 +143,7 @@ def recursive_h(int[32] q) { } qubit[6] __qubits__; recursive_h(5);""" - assert recursive_h_wrapper.to_ir() == expected + assert recursive_h_wrapper.build().to_ir() == expected def test_sim_recursive_h_wrapper(recursive_h_wrapper): @@ -172,7 +172,7 @@ def recursive_h(int[32] q) { qubit[6] __qubits__; recursive_h(4);""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected @aq.subroutine @@ -200,7 +200,7 @@ def bell_state_arbitrary_qubits(qubit q0, qubit q1) { qubit[4] __qubits__; bell_state_arbitrary_qubits(__qubits__[0], __qubits__[1]); bell_state_arbitrary_qubits(__qubits__[2], __qubits__[3]);""" - assert double_bell_state.to_ir() == expected + assert double_bell_state.build().to_ir() == expected def test_sim_double_bell(double_bell_state) -> None: @@ -229,7 +229,7 @@ def test_bell_measurement_undeclared(bell_measurement_undeclared) -> None: __bit_0__[0] = measure __qubits__[0]; __bit_0__[1] = measure __qubits__[1]; c = __bit_0__;""" - assert bell_measurement_undeclared.to_ir() == expected + assert bell_measurement_undeclared.build().to_ir() == expected def test_sim_bell_measurement_undeclared(bell_measurement_undeclared) -> None: @@ -259,7 +259,7 @@ def test_bell_measurement_declared(bell_measurement_declared) -> None: __bit_1__[0] = measure __qubits__[0]; __bit_1__[1] = measure __qubits__[1]; c = __bit_1__;""" - assert bell_measurement_declared.to_ir() == expected + assert bell_measurement_declared.build().to_ir() == expected def test_sim_bell_measurement_declared(bell_measurement_declared) -> None: @@ -287,7 +287,7 @@ def test_bell_partial_measurement(bell_partial_measurement) -> None: bit __bit_0__; __bit_0__ = measure __qubits__[1]; c = __bit_0__;""" - assert bell_partial_measurement.to_ir() == expected + assert bell_partial_measurement.build().to_ir() == expected def test_bell_measurement_invalid_declared_type() -> None: @@ -357,7 +357,7 @@ def test_measure_physical_qubits(measure_physical_qubits) -> None: __bit_2__[0] = measure $0; __bit_2__[1] = measure $1; c = __bit_2__;""" - assert measure_physical_qubits.to_ir() == expected + assert measure_physical_qubits.build().to_ir() == expected @pytest.fixture @@ -380,7 +380,7 @@ def test_ghz_qasm_for_loop(ghz_qasm_for_loop) -> None: for int i in [0:4 - 1] { cnot __qubits__[i], __qubits__[i + 1]; }""" - assert ghz_qasm_for_loop.to_ir() == expected + assert ghz_qasm_for_loop.build().to_ir() == expected def test_sim_ghz_qasm_for_loop(ghz_qasm_for_loop) -> None: @@ -408,7 +408,7 @@ def test_ghz_py_for_loop(ghz_py_for_loop) -> None: cnot __qubits__[1], __qubits__[2]; cnot __qubits__[2], __qubits__[3]; cnot __qubits__[3], __qubits__[4];""" - assert ghz_py_for_loop.to_ir() == expected + assert ghz_py_for_loop.build().to_ir() == expected def test_sim_ghz_py_for_loop(ghz_py_for_loop) -> None: @@ -457,7 +457,7 @@ def qasm_simple_condition(bool do_cnot) -> bool { bool __bool_0__; """ expected += f"__bool_0__ = qasm_simple_condition({'true' if do_cnot else 'false'});" - assert build_qasm_simple_condition_wrapper(do_cnot).to_ir() == expected + assert build_qasm_simple_condition_wrapper(do_cnot).build().to_ir() == expected @pytest.mark.parametrize("do_cnot", [True, False]) @@ -502,7 +502,7 @@ def test_qasm_inline_var_condition(qasm_inline_var_condition) -> None: bit __bit_2__; __bit_2__ = measure __qubits__[1]; return_value = __bit_2__;""" - assert qasm_inline_var_condition.to_ir() == expected + assert qasm_inline_var_condition.build().to_ir() == expected def test_sim_qasm_inline_var_condition(qasm_inline_var_condition) -> None: @@ -524,7 +524,7 @@ def test_measurement_qubit_discovery(ground_state_measurements) -> None: """Test that qubits measured by integer are automatically discovered for the purpose of qubit declaration. """ - assert "qubit[6] __qubits__;" in ground_state_measurements.to_ir() + assert "qubit[6] __qubits__;" in ground_state_measurements.build().to_ir() def test_simple_measurement(ground_state_measurements) -> None: @@ -537,7 +537,7 @@ def test_simple_measurement(ground_state_measurements) -> None: __bit_0__[1] = measure __qubits__[2]; __bit_0__[2] = measure __qubits__[1]; return_value = __bit_0__;""" - assert ground_state_measurements.to_ir() == expected + assert ground_state_measurements.build().to_ir() == expected def test_sim_simple_measurement() -> None: @@ -575,7 +575,7 @@ def ground_state_measurements_subroutine() -> bit[3] { qubit[6] __qubits__; bit[3] __bit_1__ = "000"; __bit_1__ = ground_state_measurements_subroutine();""" - assert ground_state_measurements_wrapper.to_ir() == expected + assert ground_state_measurements_wrapper.build().to_ir() == expected @pytest.fixture @@ -608,7 +608,7 @@ def test_qasm_measurement_condition(qasm_measurement_condition) -> None: bit __bit_1__; __bit_1__ = measure __qubits__[1]; return_value = __bit_1__;""" - assert qasm_measurement_condition.to_ir() == expected + assert qasm_measurement_condition.build().to_ir() == expected def test_sim_measurement_condition(qasm_measurement_condition) -> None: @@ -617,13 +617,13 @@ def test_sim_measurement_condition(qasm_measurement_condition) -> None: def test_virtual_int_qubit_decl(bell_state_program) -> None: """Tests for ex. h(0) -> qubit[1] __qubits__""" - qasm = bell_state_program.to_ir() + qasm = bell_state_program.build().to_ir() assert "\nqubit[2] __qubits__;" in qasm def test_py_int_qubit_decl(ghz_py_for_loop) -> None: """Tests for ex. i = 1; h(i) -> qubit[1] __qubits__""" - qasm = ghz_py_for_loop.to_ir() + qasm = ghz_py_for_loop.build().to_ir() assert "\nqubit[5] __qubits__;" in qasm @@ -634,7 +634,7 @@ def test_physical_qubit_decl(physical_bell_subroutine) -> None: def main(): physical_bell_subroutine() - assert "__qubits__" not in main.to_ir() + assert "__qubits__" not in main.build().to_ir() def test_invalid_physical_qubit_fails() -> None: @@ -715,7 +715,7 @@ def my_program_wrapper() -> None: bit __bit_0__; __bit_0__ = measure __qubits__[0]; return __bit_0__;""" - assert expected in my_program_wrapper.to_ir() + assert expected in my_program_wrapper.build().to_ir() @pytest.fixture @@ -752,7 +752,7 @@ def test_bit_array_name_multi(reset) -> None: if (__bit_2__) { x __qubits__[0]; }""" - assert reset.to_ir() == expected + assert reset.build().to_ir() == expected def test_program_simple_expr() -> None: @@ -806,7 +806,7 @@ def prog(): for i in aq.range(5): h(i) - assert prog.to_ir() == expected + assert prog.build().to_ir() == expected @aq.subroutine @@ -837,7 +837,7 @@ def bell(qubit q0, qubit q1) { bell(__qubits__[0], __qubits__[1]); }""" - assert bell_in_for_loop.to_ir() == expected + assert bell_in_for_loop.build().to_ir() == expected @pytest.fixture @@ -873,7 +873,7 @@ def test_classical_variables_types(classical_variables_types): b = 15; float[64] c = 1.2; c = 3.4;""" - assert classical_variables_types.to_ir() == expected + assert classical_variables_types.build().to_ir() == expected def test_sim_classical_variables_types(classical_variables_types): @@ -894,7 +894,7 @@ def prog() -> None: a = 2; b = a; a = b;""" - assert prog.to_ir() == expected + assert prog.build().to_ir() == expected def test_assignment_measurement_results(): @@ -911,7 +911,7 @@ def prog() -> None: __bit_0__ = measure __qubits__[0]; a = __bit_0__; b = a;""" - assert prog.to_ir() == expected + assert prog.build().to_ir() == expected def test_nested_function(): @@ -936,7 +936,7 @@ def ghz(n: int): cnot __qubits__[0], __qubits__[3]; cnot __qubits__[0], __qubits__[4];""" - assert make_ghz.to_ir() == expected + assert make_ghz.build().to_ir() == expected def test_double_decorated_function(): @@ -946,7 +946,7 @@ def empty_program() -> None: pass expected = """OPENQASM 3.0;""" - assert empty_program.to_ir() == expected + assert empty_program.build().to_ir() == expected def test_to_ir_implicit_build(empty_program) -> None: @@ -979,7 +979,7 @@ def tester(int[32] x) -> bit { bit __bit_1__; __bit_1__ = tester(3);""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_subroutine_declared_after_main(): @@ -1003,7 +1003,7 @@ def my_subroutine() { qubit[2] __qubits__; my_subroutine();""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected @aq.subroutine def my_subroutine() -> None: # noqa: F811 @@ -1018,7 +1018,7 @@ def my_subroutine() { qubit[4] __qubits__; my_subroutine();""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_subroutine_args(): @@ -1086,7 +1086,7 @@ def nothing() { } nothing();""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_input_types(): @@ -1117,9 +1117,9 @@ def multiple_input_types_params(x: int, y: bool, z: float, u): rx(u + z) __qubits__[0]; }""" assert ( - multiple_input_types.to_ir() - == multiple_input_types_parens.to_ir() - == multiple_input_types_params.to_ir() + multiple_input_types.build().to_ir() + == multiple_input_types_parens.build().to_ir() + == multiple_input_types_params.build().to_ir() == expected_ir ) @@ -1134,7 +1134,7 @@ def circ(q: int, r: int): input int[32] r; qubit[8] __qubits__; h __qubits__[2 * q + r];""" - assert circ.to_ir() == expected_ir + assert circ.build().to_ir() == expected_ir def test_input_qubit_indices_needs_num_qubits(): diff --git a/test/unit_tests/braket/experimental/autoqasm/test_devices.py b/test/unit_tests/braket/experimental/autoqasm/test_devices.py index d3f67162..47c2b082 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_devices.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_devices.py @@ -295,5 +295,5 @@ def my_program(): } aws_session.create_quantum_task.assert_called_once() assert expected_run_call_args.items() <= run_call_args.items() - assert run_call_args_action["source"] == my_program.to_ir() + assert run_call_args_action["source"] == my_program.build().to_ir() assert run_call_args_action["inputs"] == inputs diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py index 000e37d9..a601910d 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py @@ -190,9 +190,9 @@ def cal_1(angle: float): def my_program(): rx("$1", 1.0) - program_1 = my_program + program_1 = my_program.build() program_2 = my_program.build().with_calibrations(cal_1) - program_3 = my_program + program_3 = my_program.build() assert program_1.to_ir() == program_3.to_ir() != program_2.to_ir() diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py index 319a7810..67aa97db 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py @@ -39,7 +39,7 @@ def my_program(): qubit[1] __qubits__; empty_gate __qubits__[0];""" - assert my_program.to_ir() == expected + assert my_program.build().to_ir() == expected _test_on_local_sim(my_program) @@ -57,7 +57,7 @@ def my_program(): qubit[1] __qubits__; empty_gate __qubits__[0];""" - assert my_program.to_ir() == expected + assert my_program.build().to_ir() == expected def test_gate_class() -> None: @@ -110,7 +110,7 @@ def main(): qubit[1] __qubits__; my_gate(0.7853981633974483) __qubits__[0];""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_duplicate_gate_names_in_subroutine() -> None: @@ -144,7 +144,7 @@ def define_gate_in_subroutine() { my_gate(0.7853981633974483) __qubits__[0]; define_gate_in_subroutine();""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_incorrect_arg_count() -> None: @@ -325,7 +325,7 @@ def my_program(): __bit_0__[0] = measure __qubits__[0]; __bit_0__[1] = measure __qubits__[1];""" - assert my_program.to_ir() == expected + assert my_program.build().to_ir() == expected _test_on_local_sim(my_program) @@ -357,4 +357,4 @@ def subroutine(int[32] q0, int[32] q1) { subroutine(0, 1); subroutine(2, 3);""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_definitions.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_definitions.py index a4253fb4..560b3ea3 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_gate_definitions.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_gate_definitions.py @@ -64,7 +64,7 @@ def test_bell_state_prep(bell_state_program) -> None: qubit[2] __qubits__; h __qubits__[0]; cnot __qubits__[0], __qubits__[1];""" - actual_result = bell_state_program.to_ir() + actual_result = bell_state_program.build().to_ir() assert actual_result == expected @@ -73,7 +73,7 @@ def test_physical_q_bell_state_prep(physical_bell_program) -> None: expected = """OPENQASM 3.0; h $0; cnot $0, $5;""" - actual_result = physical_bell_program.to_ir() + actual_result = physical_bell_program.build().to_ir() assert actual_result == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py index e3956406..78d4d095 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_operators.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py @@ -106,7 +106,7 @@ def cond_exp_assignment(): } a = __int_3__;""" - assert cond_exp_assignment.to_ir() == expected + assert cond_exp_assignment.build().to_ir() == expected @pytest.mark.parametrize( @@ -147,7 +147,7 @@ def branch_assignment_undeclared(): a = 2; }""" - assert branch_assignment_undeclared.to_ir() == expected + assert branch_assignment_undeclared.build().to_ir() == expected def test_branch_assignment_declared() -> None: @@ -170,7 +170,7 @@ def branch_assignment_declared(): a = 7; }""" - assert branch_assignment_declared.to_ir() == expected + assert branch_assignment_declared.build().to_ir() == expected def for_body(i: aq.Qubit) -> None: @@ -381,7 +381,7 @@ def do_and(bool a, bool b) -> bool { bool __bool_1__; __bool_1__ = do_and(true, false);""" - assert prog.to_ir() == expected + assert prog.build().to_ir() == expected def test_logical_op_or() -> None: @@ -402,7 +402,7 @@ def do_or(bool a, bool b) -> bool { bool __bool_1__; __bool_1__ = do_or(true, false);""" - assert prog.to_ir() == expected + assert prog.build().to_ir() == expected def test_logical_op_not() -> None: @@ -423,7 +423,7 @@ def do_not(bool a) -> bool { bool __bool_1__; __bool_1__ = do_not(true);""" - assert prog.to_ir() == expected + assert prog.build().to_ir() == expected def test_logical_op_eq() -> None: @@ -444,7 +444,7 @@ def do_eq(int[32] a, int[32] b) -> bool { bool __bool_1__; __bool_1__ = do_eq(1, 2);""" - assert prog.to_ir() == expected + assert prog.build().to_ir() == expected def test_logical_op_not_eq() -> None: @@ -465,7 +465,7 @@ def do_not_eq(int[32] a, int[32] b) -> bool { bool __bool_1__; __bool_1__ = do_not_eq(1, 2);""" - assert prog.to_ir() == expected + assert prog.build().to_ir() == expected def test_logical_ops_py() -> None: @@ -484,7 +484,7 @@ def prog(): expected = """OPENQASM 3.0;""" - assert prog.to_ir() == expected + assert prog.build().to_ir() == expected def test_comparison_lt() -> None: @@ -507,7 +507,7 @@ def prog(): if (__bool_1__) { h __qubits__[0]; }""" - assert prog.to_ir() == expected + assert prog.build().to_ir() == expected def test_comparison_gt() -> None: @@ -530,7 +530,7 @@ def prog(): if (__bool_1__) { h __qubits__[0]; }""" - assert prog.to_ir() == expected + assert prog.build().to_ir() == expected def test_comparison_ops_py() -> None: @@ -549,7 +549,7 @@ def prog(): assert all([c, d, not e, not f, h]) expected = """OPENQASM 3.0;""" - assert prog.to_ir() == expected + assert prog.build().to_ir() == expected @pytest.mark.parametrize( @@ -642,7 +642,7 @@ def slice(): bit b = 1; a[3] = b;""" - assert slice.to_ir() == expected + assert slice.build().to_ir() == expected def test_slice_bits_w_measure() -> None: @@ -663,7 +663,7 @@ def measure_to_slice(): c = __bit_1__; b0[3] = c;""" - assert measure_to_slice.to_ir() == expected + assert measure_to_slice.build().to_ir() == expected @pytest.mark.parametrize( @@ -807,7 +807,7 @@ def test_control_flow(): {} __qubits__[0];""".format( "h" if value else "x" ) - assert test_control_flow.to_ir() == expected + assert test_control_flow.build().to_ir() == expected def test_py_while() -> None: @@ -826,7 +826,7 @@ def test_control_flow(): h __qubits__[0]; h __qubits__[0];""" - assert test_control_flow.to_ir() == expected + assert test_control_flow.build().to_ir() == expected def test_py_assert() -> None: @@ -850,7 +850,7 @@ def test_input_assert(value: bool): def test_assert(): assert true_var - test_assert.to_ir() # does not raise an exception + test_assert.build().to_ir() # does not raise an exception @aq.main def test_assert_false(): @@ -883,4 +883,4 @@ def test_list_ops(): c = np.stack([a, a]) assert np.array_equal(c, [[2, 3, 4], [2, 3, 4]]) - assert test_list_ops.to_ir() + assert test_list_ops.build().to_ir() diff --git a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py index 85b1ee07..3ad96774 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py @@ -72,7 +72,7 @@ def test_simple_parametric(simple_parametric): rx(theta) __qubits__[0]; bit __bit_0__; __bit_0__ = measure __qubits__[0];""" - assert simple_parametric.to_ir() == expected + assert simple_parametric.build().to_ir() == expected def test_simple_parametric_fp(simple_parametric_fp): @@ -84,7 +84,7 @@ def test_simple_parametric_fp(simple_parametric_fp): rx(theta) __qubits__[0]; bit __bit_0__; __bit_0__ = measure __qubits__[0];""" - assert simple_parametric_fp.to_ir() == expected + assert simple_parametric_fp.build().to_ir() == expected def test_sim_simple(simple_parametric): @@ -126,7 +126,7 @@ def test_multiple_parameters(multi_parametric): __bit_0__[0] = measure __qubits__[0]; __bit_0__[1] = measure __qubits__[1]; c = __bit_0__;""" - assert multi_parametric.to_ir() == expected + assert multi_parametric.build().to_ir() == expected def test_sim_multi_param(multi_parametric): @@ -156,7 +156,7 @@ def parametric(alpha, theta): cnot __qubits__[0], __qubits__[1]; rx(theta) __qubits__[0]; rx(alpha) __qubits__[1];""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected @pytest.mark.xfail( @@ -183,7 +183,7 @@ def rx_alpha(int[32] qubit) { input float[64] alpha; qubit[3] __qubits__; rx_alpha(2);""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_captured_parameter(): @@ -202,7 +202,7 @@ def parametric(): qubit[2] __qubits__; rz(alpha) __qubits__[0]; rx(alpha) __qubits__[1];""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_multi_angle_gates(): @@ -218,7 +218,7 @@ def parametric(phi: float): input float phi; qubit[5] __qubits__; ms(phi, phi, 0.5) __qubits__[0], __qubits__[2];""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_sim_multi_angle(): @@ -245,7 +245,7 @@ def parametric(my_phi: float): input float my_phi; qubit[2] __qubits__; cphaseshift(my_phi) __qubits__[0], __qubits__[1];""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_simple_subroutine_arg(): @@ -266,7 +266,7 @@ def silly_rz(float[64] theta) { input float alpha; qubit[1] __qubits__; silly_rz(alpha);""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_parameters_passed_as_subroutine_arg(): @@ -290,7 +290,7 @@ def silly_ms(int[32] qubit_0, float[64] phi, float[64] theta) { qubit[5] __qubits__; silly_ms(1, alpha, 0.707); silly_ms(3, 0.5, beta);""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_sim_subroutine_arg(): @@ -325,7 +325,7 @@ def parametric(): input float θ; qubit[3] __qubits__; rx_theta(θ) __qubits__[2];""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_parametric_pulse_cals(): @@ -371,10 +371,10 @@ def parametric(alpha: float): bit __bit_0__; __bit_0__ = measure __qubits__[0];""" - assert parametric.to_ir() == unbound_expected + assert parametric.build().to_ir() == unbound_expected bound_prog = parametric.build().make_bound_program({"alpha": 0.5}) # Original program unchanged - assert parametric.to_ir() == unbound_expected + assert parametric.build().to_ir() == unbound_expected assert bound_prog.to_ir() == bound_template.format(0.5) # Can rebind bound_prog = parametric.build().make_bound_program({"alpha": 0.432143}) @@ -479,7 +479,7 @@ def test_bind_empty_program(): def empty_program(): pass - qasm = empty_program.to_ir() + qasm = empty_program.build().to_ir() bound_program1 = empty_program.build().make_bound_program({}).to_ir() bound_program2 = empty_program.build().make_bound_program({"alpha": 0.5}).to_ir() assert qasm == bound_program1 == bound_program2 @@ -575,7 +575,7 @@ def parametric(val: float): } bit __bit_3__; __bit_3__ = measure __qubits__[0];""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_lt_condition(): @@ -604,7 +604,7 @@ def parametric(val: float): } bit __bit_2__; __bit_2__ = measure __qubits__[0];""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_parameter_in_predicate_in_subroutine(): @@ -634,7 +634,7 @@ def sub(float[64] val) { sub(val); bit __bit_1__; __bit_1__ = measure __qubits__[0];""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_eq_condition(): @@ -666,7 +666,7 @@ def parametric(threshold: int): } bit __bit_2__; __bit_2__ = measure __qubits__[0];""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_sim_conditional_stmts(): @@ -716,7 +716,7 @@ def parametric(val: int): } bit __bit_1__; __bit_1__ = measure __qubits__[0];""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_param_or(): @@ -741,7 +741,7 @@ def parametric(alpha: float, beta: float): } bit __bit_1__; __bit_1__ = measure __qubits__[0];""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_param_and(): @@ -764,7 +764,7 @@ def parametric(alpha: float, beta: float): } bit __bit_1__; __bit_1__ = measure __qubits__[0];""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_param_and_float(): @@ -787,7 +787,7 @@ def parametric(alpha: float): } bit __bit_1__; __bit_1__ = measure __qubits__[0];""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_param_not(): @@ -809,7 +809,7 @@ def parametric(val: int): } bit __bit_1__; __bit_1__ = measure __qubits__[0];""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_parameter_binding_conditions(): @@ -859,8 +859,8 @@ def parametric_fp(): input float theta; qubit[1] __qubits__; gpi(2.0 * theta) __qubits__[0];""" - assert parametric.to_ir() == expected_1 - assert parametric_fp.to_ir() == expected_2 + assert parametric.build().to_ir() == expected_1 + assert parametric_fp.build().to_ir() == expected_2 def test_sim_expressions(): @@ -886,7 +886,7 @@ def parametric(alpha, theta): input float theta; qubit[1] __qubits__; gpi(alpha * theta) __qubits__[0];""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_bound_parameter_expressions(): @@ -937,7 +937,7 @@ def rotate(float[64] theta) { input float alpha; qubit[1] __qubits__; rotate(2 * alpha);""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_gate_parameter_expressions(): @@ -958,7 +958,7 @@ def parametric(alpha): input float alpha; qubit[1] __qubits__; rotate(2 * alpha) __qubits__[0];""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_conditional_parameter_expressions(): @@ -980,7 +980,7 @@ def parametric(phi): } bit __bit_1__; __bit_1__ = measure __qubits__[0];""" - assert parametric.to_ir() == expected + assert parametric.build().to_ir() == expected def test_parameter_expressions_range_index(): @@ -1000,5 +1000,5 @@ def two_n_hs(n: int): } bit __bit_0__; __bit_0__ = measure __qubits__[0];""" - assert two_n_hs.to_ir() == expected + assert two_n_hs.build().to_ir() == expected _test_parametric_on_local_sim(two_n_hs, {"n": 3}) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py b/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py index 9ee0596c..44832c56 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py @@ -52,7 +52,7 @@ def program_func() -> None: cnot $1, $2; }""" - assert program_func.to_ir() == expected + assert program_func.build().to_ir() == expected def test_nested_verbatim_box() -> None: diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/braket/experimental/autoqasm/test_program.py index 1876b630..fa039025 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_program.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_program.py @@ -114,7 +114,7 @@ def circuit(float[64] angle) { ) for i, (scale, angle) in enumerate(itertools.product(scales, angles)): - assert programs[i].to_ir() == expected(scale, angle) + assert programs[i].build().to_ir() == expected(scale, angle) @patch("builtins.print") diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pulse.py b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py index 34cc0afc..2c4db739 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_pulse.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_pulse.py @@ -64,7 +64,7 @@ def my_program(): } """ ).strip() - assert my_program.to_ir() == expected + assert my_program.build().to_ir() == expected def test_merge_cal_box() -> None: @@ -84,7 +84,7 @@ def my_program(): } """ ).strip() - assert my_program.to_ir() == expected + assert my_program.build().to_ir() == expected @pytest.mark.parametrize( @@ -203,7 +203,7 @@ def my_program(duration): } """ ).strip() - assert my_program.to_ir() == expected + assert my_program.build().to_ir() == expected def test_pulse_freeparameter_bound() -> None: @@ -252,4 +252,4 @@ def my_program(duration, duration2): } """ ).strip() - assert my_program.to_ir() == expected + assert my_program.build().to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_return.py b/test/unit_tests/braket/experimental/autoqasm/test_return.py index 1d3b614f..e2167e1a 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_return.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_return.py @@ -30,7 +30,7 @@ def main(): output float[64] return_value; return_value = 1.5;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_int_lit(): @@ -42,7 +42,7 @@ def main(): output int[32] return_value; return_value = 1;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_named_value(): @@ -55,7 +55,7 @@ def main(): output int[32] output_name; output_name = 1;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_return_measure(): @@ -70,7 +70,7 @@ def main(): __bit_0__ = measure __qubits__[0]; return_value = __bit_0__;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_named_measure(): @@ -86,7 +86,7 @@ def main() -> int: __bit_0__ = measure __qubits__[0]; b = __bit_0__;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_basic_arithmetic(): @@ -101,7 +101,7 @@ def main(): output int[32] val; val = __int_0__ + __int_1__;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_expressions(): @@ -116,7 +116,7 @@ def main(input_a: int): output int[32] val; val = 1 + input_a;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_return_tuple(): @@ -130,7 +130,7 @@ def main(): return_value0 = 1; return_value1 = 2;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_return_list_floats(): @@ -144,7 +144,7 @@ def main(): return_value0 = 11.1; return_value1 = 2.222;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_return_multi_meas(): @@ -173,7 +173,7 @@ def main(): return_value1 = b; return_value2 = __bit_2__;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_return_multi_types(): @@ -196,7 +196,7 @@ def main(): return_value1 = true; return_value2 = 1.11;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_name_collisions(): @@ -219,7 +219,7 @@ def main(val1, val2): output float[64] return_value; return_value = val1 + val2;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_return_ints(): @@ -233,7 +233,7 @@ def main(val1: int, val2: int): output int[32] return_value; return_value = val1 + val2;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_return_bools(): @@ -249,7 +249,7 @@ def main(val1: bool, val2: bool): __bool_0__ = val1 || val2; return_value = __bool_0__;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_return_bits(): @@ -272,7 +272,7 @@ def main(): b1 = __bit_1__; return_value = b0 + b1;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_returns_with_subroutine(): @@ -298,7 +298,7 @@ def helper() -> int[32] { __int_1__ = helper(); val = __int_1__;""" - assert ret_test.to_ir() == expected + assert ret_test.build().to_ir() == expected def test_return_measure_range(): @@ -330,7 +330,7 @@ def ghz(int[32] n) { __bit_0__[2] = measure __qubits__[2]; return_value = __bit_0__;""" - assert program.to_ir() == expected + assert program.build().to_ir() == expected def test_return_pulse_capture(): @@ -353,4 +353,4 @@ def program(): return_value0 = __bit_0__; return_value1 = __bit_1__;""" - assert program.to_ir() == expected + assert program.build().to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_serialization_config.py b/test/unit_tests/braket/experimental/autoqasm/test_serialization_config.py index 8ac2a5f2..3a71515e 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_serialization_config.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_serialization_config.py @@ -41,7 +41,7 @@ def my_program(): } """ ).strip() - qasm = my_program.to_ir( + qasm = my_program.build().to_ir( serialization_properties=OpenQASMSerializationProperties(auto_defcalgrammar=True) ) assert qasm == expected_true @@ -54,7 +54,7 @@ def my_program(): } """ ).strip() - qasm = my_program.to_ir( + qasm = my_program.build().to_ir( serialization_properties=OpenQASMSerializationProperties(auto_defcalgrammar=False) ) assert qasm == expected_false @@ -77,7 +77,7 @@ def my_program(): } """ ).strip() - qasm = my_program.to_ir( + qasm = my_program.build().to_ir( serialization_properties=OpenQASMSerializationProperties(include_externs=True) ) assert qasm == expected_true @@ -91,7 +91,7 @@ def my_program(): } """ ).strip() - qasm = my_program.to_ir( + qasm = my_program.build().to_ir( serialization_properties=OpenQASMSerializationProperties(include_externs=False) ) assert qasm == expected_false diff --git a/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py b/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py index aa32bb5b..8d59bd2f 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py @@ -89,10 +89,10 @@ def bell(q0: int, q1: int): bell_partial_no_num_qubits.build() bell_partial = aq.main(num_qubits=4)(functools.partial(bell, 1)) - assert bell_partial.to_ir() == expected_partial + assert bell_partial.build().to_ir() == expected_partial bell_noarg_partial = aq.main(functools.partial(bell, 1, 3)) - assert bell_noarg_partial.to_ir() == expected_no_arg_partial + assert bell_noarg_partial.build().to_ir() == expected_no_arg_partial def test_classmethod() -> None: @@ -113,8 +113,8 @@ def bell(cls, q0: int, q1: int): h __qubits__[q0]; cnot __qubits__[q0], __qubits__[q1];""" - assert aq.main(num_qubits=2)(MyClass.bell).to_ir() == expected - assert aq.main(num_qubits=2)(MyClass().bell).to_ir() == expected + assert aq.main(num_qubits=2)(MyClass.bell).build().to_ir() == expected + assert aq.main(num_qubits=2)(MyClass().bell).build().to_ir() == expected def test_with_verbose_logging() -> None: diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index e1fa630e..03083d45 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -64,7 +64,7 @@ def ret_test() -> bit { __bit_1__ = ret_test(); return_value = __bit_1__;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_return_int(): @@ -89,7 +89,7 @@ def ret_test() -> int[32] { __int_1__ = ret_test(); return_value = __int_1__;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_return_float(): @@ -114,7 +114,7 @@ def ret_test() -> float[64] { __float_1__ = ret_test(); return_value = __float_1__;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_return_bool(): @@ -139,7 +139,7 @@ def ret_test() -> bool { __bool_1__ = ret_test(); return_value = __bool_1__;""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_return_bin_expr(): @@ -166,7 +166,7 @@ def add(int[32] a, int[32] b) -> int[32] { __int_2__ = add(a, b); return_value = __int_2__;""" - assert ret_test.to_ir() == expected + assert ret_test.build().to_ir() == expected def test_return_none(): @@ -178,7 +178,7 @@ def ret_test() -> None: expected = "OPENQASM 3.0;" - assert ret_test.to_ir() == expected + assert ret_test.build().to_ir() == expected def test_declare_array(): @@ -199,7 +199,7 @@ def declare_array(): b[2] = 14; b = a;""" - assert declare_array.to_ir() == expected + assert declare_array.build().to_ir() == expected def test_invalid_array_assignment(): @@ -294,7 +294,7 @@ def helper() -> int[32] { __int_1__ = helper(); return_value = __int_1__;""" - assert ret_test.to_ir() == expected + assert ret_test.build().to_ir() == expected def test_map_bool(): @@ -313,7 +313,7 @@ def annotation_test(bool input) { } annotation_test(true);""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_map_int(): @@ -332,7 +332,7 @@ def annotation_test(int[32] input) { } annotation_test(1);""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_map_float(): @@ -351,7 +351,7 @@ def annotation_test(float[64] input) { } annotation_test(1.0);""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_map_qubit(): @@ -371,7 +371,7 @@ def annotation_test(qubit input) { qubit[2] __qubits__; annotation_test(__qubits__[1]);""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_map_array(): @@ -408,7 +408,7 @@ def annotation_test(bit input) { bit a = 1; annotation_test(a);""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_map_other_unnamed_arg(): @@ -428,7 +428,7 @@ def annotation_test(bit input) { bit __bit_0__ = 1; annotation_test(__bit_0__);""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_map_and_assign_arg(): @@ -452,7 +452,7 @@ def assign_param(int[32] c) { int[32] c = 0; assign_param(c);""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_unnamed_retval_python_type() -> None: @@ -476,7 +476,7 @@ def retval_test() -> int[32] { __int_1__ = retval_test(); return_value = __int_1__;""" - assert caller.to_ir() == expected_qasm + assert caller.build().to_ir() == expected_qasm def test_unnamed_retval_qasm_type() -> None: @@ -500,7 +500,7 @@ def retval_test() -> bit { __bit_1__ = retval_test(); return_value = __bit_1__;""" - assert caller.to_ir() == expected_qasm + assert caller.build().to_ir() == expected_qasm def test_recursive_unassigned_retval_python_type() -> None: @@ -525,7 +525,7 @@ def retval_recursive() -> int[32] { int[32] __int_3__; __int_3__ = retval_recursive();""" - assert main.to_ir() == expected_qasm + assert main.build().to_ir() == expected_qasm def test_recursive_assigned_retval_python_type() -> None: @@ -552,7 +552,7 @@ def retval_recursive() -> int[32] { int[32] __int_3__; __int_3__ = retval_recursive();""" - assert main.to_ir() == expected_qasm + assert main.build().to_ir() == expected_qasm def test_recursive_retval_expression_python_type() -> None: @@ -588,7 +588,7 @@ def retval_constant() -> int[32] { __float_4__ = retval_recursive(); return_value = __float_4__;""" - assert caller.to_ir() == expected_qasm + assert caller.build().to_ir() == expected_qasm def test_recursive_oqpy_type() -> None: @@ -603,7 +603,7 @@ def retval_recursive() -> aq.BitVar: def main(): retval_recursive() - assert "-> bit" in main.to_ir() + assert "-> bit" in main.build().to_ir() def test_error_for_tuple_param() -> None: @@ -655,7 +655,7 @@ def ret_test() -> bool { bool __bool_1__; __bool_1__ = ret_test();""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_ignore_ret_typehint_list(): @@ -708,7 +708,7 @@ def ret_test() -> float[64] { float[64] __float_1__; __float_1__ = ret_test();""" - assert main.to_ir() == expected + assert main.build().to_ir() == expected def test_param_array_list_missing_arg(): 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 1079/1165] 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 19108414..41f20da8 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 1080/1165] 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 1040afb3..a26d15ed 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 ba58da84..3aede694 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 1081/1165] 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 3aede694..d94f8a3e 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 1082/1165] 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 e757c6a6..592af684 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 1083/1165] 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 a1ae0c7b..d2f2099f 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 c61d2260..56d23b2e 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 1084/1165] 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 a26d15ed..43cb6684 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 d94f8a3e..f9e668a4 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 1085/1165] 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 f9e668a4..55053a2d 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 1086/1165] 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 c6ad36b2..54f54415 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 a02dfa6d..ed343027 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 36f0e68f..3f4918a1 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 cac1c153..a85ca6eb 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 1087/1165] 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 ed343027..4d0d06b5 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 1088/1165] 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 43cb6684..83ac54ff 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 55053a2d..dc2e7fdf 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 1089/1165] 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 dc2e7fdf..9c443c49 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 5e8f043174ebe2887cfc6357abe627e17c9ee7eb Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:40:27 -0400 Subject: [PATCH 1090/1165] feature: Provide access to global qubit register inside AutoQASM program (#918) * Add aq.qubits to loop over global qubit register * Move global_qubit_register to qubits module * Initialize global qubit register size with num_qubits * Move global_qubit_register into program conversion context * Add GlobalQubitRegister class * Update example notebooks to use aq.qubits() syntax * Implement aq.qubits as module-level attribute --- .../2_Expressing_classical_control_flow.ipynb | 18 ++-- .../3_1_Iterative_phase_estimation.ipynb | 6 +- .../3_2_magic_state_distillation.ipynb | 45 ++++----- src/braket/experimental/autoqasm/__init__.py | 8 +- src/braket/experimental/autoqasm/api.py | 9 +- .../autoqasm/instructions/__init__.py | 1 + .../autoqasm/instructions/measurements.py | 15 +-- .../autoqasm/instructions/qubits.py | 27 +++++- .../autoqasm/operators/control_flow.py | 10 +- .../experimental/autoqasm/program/program.py | 18 +++- .../experimental/autoqasm/types/types.py | 8 +- .../braket/experimental/autoqasm/test_api.py | 97 +++++++++++++++++++ 12 files changed, 202 insertions(+), 60 deletions(-) diff --git a/examples/autoqasm/2_Expressing_classical_control_flow.ipynb b/examples/autoqasm/2_Expressing_classical_control_flow.ipynb index 575cceec..590bc8d9 100644 --- a/examples/autoqasm/2_Expressing_classical_control_flow.ipynb +++ b/examples/autoqasm/2_Expressing_classical_control_flow.ipynb @@ -46,7 +46,7 @@ "outputs": [], "source": [ "@aq.subroutine\n", - "def bell(q0: int, q1: int) -> None:\n", + "def bell(q0: int, q1: int):\n", " h(q0)\n", " cnot(q0, q1)" ] @@ -82,7 +82,7 @@ ], "source": [ "@aq.main(num_qubits=4)\n", - "def two_bell() -> None:\n", + "def two_bell():\n", " bell(0, 1)\n", " bell(2, 3)\n", "\n", @@ -122,14 +122,14 @@ "outputs": [], "source": [ "@aq.main(num_qubits=5)\n", - "def conditioned_bell() -> None:\n", + "def conditioned_bell():\n", " h(0)\n", " if measure(0):\n", " bell(1, 2)\n", " else:\n", " bell(3, 4)\n", "\n", - " return measure([0, 1, 2, 3, 4])\n" + " return measure()\n" ] }, { @@ -150,12 +150,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "measurement counts: Counter({'00000': 137, '10000': 129, '00011': 119, '11100': 115})\n" + "measurement counts: Counter({'00000': 135, '10000': 133, '00011': 121, '11100': 111})\n" ] }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGwCAYAAABPSaTdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/SrBM8AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAseUlEQVR4nO3dfVRVdd7//9dBbr05ICocKEhqTE0dM03C/JYZExo5mV6VxVVmJt1opTSmXBNaZoOaJemQZFepXUur6bpGK5uhDO+mRFTEbsyIJlJXcrBUON6BKPv3xyzPrzNKKh44h0/Px1p7Lffn89n7vPf5rPTV3p9zjs2yLEsAAACGCvB1AQAAAE2JsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYLRAXxfgD+rr67V37161a9dONpvN1+UAAIBzYFmWDh06pNjYWAUENHz/hrAjae/evYqLi/N1GQAAoBH27Nmjiy++uMF+wo6kdu3aSfrXm2W3231cDQAAOBcul0txcXHuf8cbQtiR3I+u7HY7YQcAgBbmbEtQWKAMAACMRtgBAABGI+wAAACj+TTsbNiwQcOGDVNsbKxsNptWrlzZ4NiHHnpINptNOTk5Hu0HDhxQWlqa7Ha7IiIiNHbsWB0+fLhpCwcAAC2GT8POkSNH1Lt3b+Xm5v7iuBUrVmjTpk2KjY09rS8tLU07duzQ6tWrtWrVKm3YsEHp6elNVTIAAGhhfPpprKFDh2ro0KG/OOaHH37Qo48+qg8//FCpqakefTt37lR+fr62bNmifv36SZIWLFigm2++WXPnzj1jOAIAAL8ufr1mp76+Xvfcc48mT56sHj16nNZfWFioiIgId9CRpOTkZAUEBKioqKjB89bW1srlcnlsAADATH4ddmbPnq3AwEA99thjZ+x3Op2KioryaAsMDFRkZKScTmeD583OzlZ4eLh749uTAQAwl9+GneLiYr300ktasmSJ13+vKjMzU9XV1e5tz549Xj0/AADwH34bdv7xj39o3759io+PV2BgoAIDA7Vr1y498cQT6ty5syTJ4XBo3759HsedOHFCBw4ckMPhaPDcISEh7m9L5luTAQAwm9/+XMQ999yj5ORkj7aUlBTdc889GjNmjCQpKSlJVVVVKi4uVt++fSVJa9asUX19vRITE5u9ZgAA4H98GnYOHz6sb7/91r1fXl6u7du3KzIyUvHx8erQoYPH+KCgIDkcDnXt2lWS1L17dw0ZMkTjxo1TXl6e6urqNGHCBI0aNYpPYgEAAEk+foy1detW9enTR3369JEkZWRkqE+fPpo2bdo5n2PZsmXq1q2bbrzxRt18880aOHCgFi1a1FQlAwCAFsZmWZbl6yJ8zeVyKTw8XNXV1azfAQCghTjXf7/9doEyAACAN/jtAmUA8JXOUz/wdQm/St/PSj37IKARuLMDAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAowX6ugCgpeo89QNfl/Cr9f2sVF+XAKAF4c4OAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjObTsLNhwwYNGzZMsbGxstlsWrlypbuvrq5OU6ZMUa9evdSmTRvFxsbq3nvv1d69ez3OceDAAaWlpclutysiIkJjx47V4cOHm/lKAACAv/Jp2Dly5Ih69+6t3Nzc0/qOHj2qbdu2KSsrS9u2bdNf//pXlZaW6ve//73HuLS0NO3YsUOrV6/WqlWrtGHDBqWnpzfXJQAAAD8X6MsXHzp0qIYOHXrGvvDwcK1evdqj7c9//rP69++v3bt3Kz4+Xjt37lR+fr62bNmifv36SZIWLFigm2++WXPnzlVsbOwZz11bW6va2lr3vsvl8tIVAQAAf9Oi1uxUV1fLZrMpIiJCklRYWKiIiAh30JGk5ORkBQQEqKioqMHzZGdnKzw83L3FxcU1dekAAMBHWkzYqamp0ZQpU3TXXXfJbrdLkpxOp6KiojzGBQYGKjIyUk6ns8FzZWZmqrq62r3t2bOnSWsHAAC+49PHWOeqrq5Od9xxhyzL0sKFCy/4fCEhIQoJCfFCZQAAwN/5fdg5FXR27dqlNWvWuO/qSJLD4dC+ffs8xp84cUIHDhyQw+Fo7lIBAH6s89QPfF3Cr9b3s1J9+vp+/RjrVNApKyvTxx9/rA4dOnj0JyUlqaqqSsXFxe62NWvWqL6+XomJic1dLgAA8EM+vbNz+PBhffvtt+798vJybd++XZGRkYqJidF//Md/aNu2bVq1apVOnjzpXocTGRmp4OBgde/eXUOGDNG4ceOUl5enuro6TZgwQaNGjWrwk1gAAODXxadhZ+vWrbrhhhvc+xkZGZKk0aNH6+mnn9Z7770nSbryyis9jlu7dq0GDRokSVq2bJkmTJigG2+8UQEBARo5cqTmz5/fLPUDAAD/59OwM2jQIFmW1WD/L/WdEhkZqeXLl3uzLAAAYBC/XrMDAABwoQg7AADAaIQdAABgNMIOAAAwGmEHAAAYze+/Qbml4xs7fcfX39gJAPAP3NkBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKP5NOxs2LBBw4YNU2xsrGw2m1auXOnRb1mWpk2bppiYGIWFhSk5OVllZWUeYw4cOKC0tDTZ7XZFRERo7NixOnz4cDNeBQAA8Gc+DTtHjhxR7969lZube8b+OXPmaP78+crLy1NRUZHatGmjlJQU1dTUuMekpaVpx44dWr16tVatWqUNGzYoPT29uS4BAAD4uUBfvvjQoUM1dOjQM/ZZlqWcnBw99dRTuvXWWyVJb7zxhqKjo7Vy5UqNGjVKO3fuVH5+vrZs2aJ+/fpJkhYsWKCbb75Zc+fOVWxsbLNdCwAA8E9+u2anvLxcTqdTycnJ7rbw8HAlJiaqsLBQklRYWKiIiAh30JGk5ORkBQQEqKioqMFz19bWyuVyeWwAAMBMfht2nE6nJCk6OtqjPTo62t3ndDoVFRXl0R8YGKjIyEj3mDPJzs5WeHi4e4uLi/Ny9QAAwF/4bdhpSpmZmaqurnZve/bs8XVJAACgifht2HE4HJKkyspKj/bKykp3n8Ph0L59+zz6T5w4oQMHDrjHnElISIjsdrvHBgAAzOS3YSchIUEOh0MFBQXuNpfLpaKiIiUlJUmSkpKSVFVVpeLiYveYNWvWqL6+XomJic1eMwAA8D8+/TTW4cOH9e2337r3y8vLtX37dkVGRio+Pl4TJ07UzJkz1aVLFyUkJCgrK0uxsbEaPny4JKl79+4aMmSIxo0bp7y8PNXV1WnChAkaNWoUn8QCAACSfBx2tm7dqhtuuMG9n5GRIUkaPXq0lixZoieffFJHjhxRenq6qqqqNHDgQOXn5ys0NNR9zLJlyzRhwgTdeOONCggI0MiRIzV//vxmvxYAAOCffBp2Bg0aJMuyGuy32WyaMWOGZsyY0eCYyMhILV++vCnKAwAABvDbNTsAAADeQNgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjObXYefkyZPKyspSQkKCwsLCdNlll+nZZ5+VZVnuMZZladq0aYqJiVFYWJiSk5NVVlbmw6oBAIA/8euwM3v2bC1cuFB//vOftXPnTs2ePVtz5szRggUL3GPmzJmj+fPnKy8vT0VFRWrTpo1SUlJUU1Pjw8oBAIC/CPR1Ab9k48aNuvXWW5WamipJ6ty5s958801t3rxZ0r/u6uTk5Oipp57SrbfeKkl64403FB0drZUrV2rUqFFnPG9tba1qa2vd+y6Xq4mvBAAA+Ipf39kZMGCACgoK9M0330iSPvvsM33yyScaOnSoJKm8vFxOp1PJycnuY8LDw5WYmKjCwsIGz5udna3w8HD3FhcX17QXAgAAfKZRd3b27Nkjm82miy++WJK0efNmLV++XFdccYXS09O9VtzUqVPlcrnUrVs3tWrVSidPntRzzz2ntLQ0SZLT6ZQkRUdHexwXHR3t7juTzMxMZWRkuPddLheBBwAAQzXqzs7dd9+ttWvXSvpX4Pjd736nzZs3649//KNmzJjhteL+8pe/aNmyZVq+fLm2bdumpUuXau7cuVq6dOkFnTckJER2u91jAwAAZmpU2Pnyyy/Vv39/Sf8KJD179tTGjRu1bNkyLVmyxGvFTZ48WVOnTtWoUaPUq1cv3XPPPZo0aZKys7MlSQ6HQ5JUWVnpcVxlZaW7DwAA/Lo1KuzU1dUpJCREkvTxxx/r97//vSSpW7duqqio8FpxR48eVUCAZ4mtWrVSfX29JCkhIUEOh0MFBQXufpfLpaKiIiUlJXmtDgAA0HI1as1Ojx49lJeXp9TUVK1evVrPPvusJGnv3r3q0KGD14obNmyYnnvuOcXHx6tHjx4qKSnRiy++qPvvv1+SZLPZNHHiRM2cOVNdunRRQkKCsrKyFBsbq+HDh3utDgAA0HI1KuzMnj1bt912m55//nmNHj1avXv3liS999577sdb3rBgwQJlZWXpkUce0b59+xQbG6sHH3xQ06ZNc4958skndeTIEaWnp6uqqkoDBw5Ufn6+QkNDvVYHAABouRoVdgYNGqSffvpJLpdL7du3d7enp6erTZs2XiuuXbt2ysnJUU5OToNjbDabZsyY4dWF0QAAwByNWrMzePBgHTp0yCPoSFJkZKTuvPNOrxQGAADgDY0KO+vWrdPx48dPa6+pqdE//vGPCy4KAADAW87rMdbnn3/u/vNXX33l8cV9J0+eVH5+vi666CLvVQcAAHCBzivsXHnllbLZbLLZbBo8ePBp/WFhYR4/0gkAAOBr5xV2ysvLZVmWLr30Um3evFmdOnVy9wUHBysqKkqtWrXyepEAAACNdV5h55JLLpEk95f6AQAA+LtGffRcksrKyrR27Vrt27fvtPDz8+/BAQAA8KVGhZ1XX31VDz/8sDp27CiHwyGbzebus9lshB0AAOA3GhV2Zs6cqeeee05Tpkzxdj0AAABe1ajv2Tl48KBuv/12b9cCAADgdY0KO7fffrs++ugjb9cCAADgdY16jPWb3/xGWVlZ2rRpk3r16qWgoCCP/scee8wrxQEAAFyoRoWdRYsWqW3btlq/fr3Wr1/v0Wez2Qg7AADAbzQq7JSXl3u7DgAAgCbRqDU7AAAALUWj7uzcf//9v9j/+uuvN6oYAAAAb2tU2Dl48KDHfl1dnb788ktVVVWd8QdCAQAAfKVRYWfFihWntdXX1+vhhx/WZZdddsFFAQAAeIvX1uwEBAQoIyND8+bN89YpAQAALphXFyj/85//1IkTJ7x5SgAAgAvSqMdYGRkZHvuWZamiokIffPCBRo8e7ZXCAAAAvKFRYaekpMRjPyAgQJ06ddILL7xw1k9qAQAANKdGhZ21a9d6uw4AAIAm0aiwc8qPP/6o0tJSSVLXrl3VqVMnrxQFAADgLY1aoHzkyBHdf//9iomJ0XXXXafrrrtOsbGxGjt2rI4ePertGgEAABqtUWEnIyND69ev1/vvv6+qqipVVVXp3Xff1fr16/XEE094u0YAAIBGa9RjrP/7v//T//7v/2rQoEHutptvvllhYWG64447tHDhQm/VBwAAcEEadWfn6NGjio6OPq09KiqKx1gAAMCvNCrsJCUlafr06aqpqXG3HTt2TM8884ySkpK8VhwAAMCFatRjrJycHA0ZMkQXX3yxevfuLUn67LPPFBISoo8++sirBQIAAFyIRoWdXr16qaysTMuWLdPXX38tSbrrrruUlpamsLAwrxYIAABwIRoVdrKzsxUdHa1x48Z5tL/++uv68ccfNWXKFK8UBwAAcKEatWbnlVdeUbdu3U5r79Gjh/Ly8i64KAAAAG9pVNhxOp2KiYk5rb1Tp06qqKi44KIAAAC8pVFhJy4uTp9++ulp7Z9++qliY2MvuCgAAABvadSanXHjxmnixImqq6vT4MGDJUkFBQV68skn+QZlAADgVxoVdiZPnqz9+/frkUce0fHjxyVJoaGhmjJlijIzM71aIAAAwIVoVNix2WyaPXu2srKytHPnToWFhalLly4KCQnxdn0AAAAXpFFh55S2bdvq6quv9lYtAAAAXteoBcoAAAAthd+HnR9++EH/+Z//qQ4dOigsLEy9evXS1q1b3f2WZWnatGmKiYlRWFiYkpOTVVZW5sOKAQCAP/HrsHPw4EFde+21CgoK0t///nd99dVXeuGFF9S+fXv3mDlz5mj+/PnKy8tTUVGR2rRpo5SUFI8fKQUAAL9eF7Rmp6nNnj1bcXFxWrx4sbstISHB/WfLspSTk6OnnnpKt956qyTpjTfeUHR0tFauXKlRo0Y1e80AAMC/+PWdnffee0/9+vXT7bffrqioKPXp00evvvqqu7+8vFxOp1PJycnutvDwcCUmJqqwsLDB89bW1srlcnlsAADATH4ddr777jstXLhQXbp00YcffqiHH35Yjz32mJYuXSrpXz9bIUnR0dEex0VHR7v7ziQ7O1vh4eHuLS4urukuAgAA+JRfh536+npdddVV+tOf/qQ+ffooPT1d48aNu+AfG83MzFR1dbV727Nnj5cqBgAA/savw05MTIyuuOIKj7bu3btr9+7dkiSHwyFJqqys9BhTWVnp7juTkJAQ2e12jw0AAJjJr8POtddeq9LSUo+2b775Rpdccomkfy1WdjgcKigocPe7XC4VFRUpKSmpWWsFAAD+ya8/jTVp0iQNGDBAf/rTn3THHXdo8+bNWrRokRYtWiTpXz9bMXHiRM2cOVNdunRRQkKCsrKyFBsbq+HDh/u2eAAA4Bf8OuxcffXVWrFihTIzMzVjxgwlJCQoJydHaWlp7jFPPvmkjhw5ovT0dFVVVWngwIHKz89XaGioDysHAAD+wq/DjiTdcsstuuWWWxrst9lsmjFjhmbMmNGMVQEAgJbCr9fsAAAAXCjCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBoLSrszJo1SzabTRMnTnS31dTUaPz48erQoYPatm2rkSNHqrKy0ndFAgAAv9Jiws6WLVv0yiuv6Le//a1H+6RJk/T+++/rnXfe0fr167V3716NGDHCR1UCAAB/0yLCzuHDh5WWlqZXX31V7du3d7dXV1frtdde04svvqjBgwerb9++Wrx4sTZu3KhNmzb5sGIAAOAvWkTYGT9+vFJTU5WcnOzRXlxcrLq6Oo/2bt26KT4+XoWFhQ2er7a2Vi6Xy2MDAABmCvR1AWfz1ltvadu2bdqyZctpfU6nU8HBwYqIiPBoj46OltPpbPCc2dnZeuaZZ7xdKgAA8EN+fWdnz549evzxx7Vs2TKFhoZ67byZmZmqrq52b3v27PHauQEAgH/x67BTXFysffv26aqrrlJgYKACAwO1fv16zZ8/X4GBgYqOjtbx48dVVVXlcVxlZaUcDkeD5w0JCZHdbvfYAACAmfz6MdaNN96oL774wqNtzJgx6tatm6ZMmaK4uDgFBQWpoKBAI0eOlCSVlpZq9+7dSkpK8kXJAADAz/h12GnXrp169uzp0damTRt16NDB3T527FhlZGQoMjJSdrtdjz76qJKSknTNNdf4omQAAOBn/DrsnIt58+YpICBAI0eOVG1trVJSUvTyyy/7uiwAAOAnWlzYWbduncd+aGiocnNzlZub65uCAACAX/PrBcoAAAAXirADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGM2vw052drauvvpqtWvXTlFRURo+fLhKS0s9xtTU1Gj8+PHq0KGD2rZtq5EjR6qystJHFQMAAH/j12Fn/fr1Gj9+vDZt2qTVq1errq5ON910k44cOeIeM2nSJL3//vt65513tH79eu3du1cjRozwYdUAAMCfBPq6gF+Sn5/vsb9kyRJFRUWpuLhY1113naqrq/Xaa69p+fLlGjx4sCRp8eLF6t69uzZt2qRrrrnmjOetra1VbW2te9/lcjXdRQAAAJ/y6zs7/666ulqSFBkZKUkqLi5WXV2dkpOT3WO6deum+Ph4FRYWNnie7OxshYeHu7e4uLimLRwAAPhMiwk79fX1mjhxoq699lr17NlTkuR0OhUcHKyIiAiPsdHR0XI6nQ2eKzMzU9XV1e5tz549TVk6AADwIb9+jPVz48eP15dffqlPPvnkgs8VEhKikJAQL1QFAAD8XYu4szNhwgStWrVKa9eu1cUXX+xudzgcOn78uKqqqjzGV1ZWyuFwNHOVAADAH/l12LEsSxMmTNCKFSu0Zs0aJSQkePT37dtXQUFBKigocLeVlpZq9+7dSkpKau5yAQCAH/Lrx1jjx4/X8uXL9e6776pdu3budTjh4eEKCwtTeHi4xo4dq4yMDEVGRsput+vRRx9VUlJSg5/EAgAAvy5+HXYWLlwoSRo0aJBH++LFi3XfffdJkubNm6eAgACNHDlStbW1SklJ0csvv9zMlQIAAH/l12HHsqyzjgkNDVVubq5yc3OboSIAANDS+PWaHQAAgAtF2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYzZiwk5ubq86dOys0NFSJiYnavHmzr0sCAAB+wIiw8/bbbysjI0PTp0/Xtm3b1Lt3b6WkpGjfvn2+Lg0AAPiYEWHnxRdf1Lhx4zRmzBhdccUVysvLU+vWrfX666/7ujQAAOBjgb4u4EIdP35cxcXFyszMdLcFBAQoOTlZhYWFZzymtrZWtbW17v3q6mpJksvl8np99bVHvX5OnJummM+fY259h7k1E/Nqrqaa21PntSzrF8e1+LDz008/6eTJk4qOjvZoj46O1tdff33GY7Kzs/XMM8+c1h4XF9ckNcI3wnN8XQGaCnNrJubVXE09t4cOHVJ4eHiD/S0+7DRGZmamMjIy3Pv19fU6cOCAOnToIJvN5sPK/IvL5VJcXJz27Nkju93u63LgJcyruZhbczG3Z2ZZlg4dOqTY2NhfHNfiw07Hjh3VqlUrVVZWerRXVlbK4XCc8ZiQkBCFhIR4tEVERDRViS2e3W7nPy4DMa/mYm7Nxdye7pfu6JzS4hcoBwcHq2/fviooKHC31dfXq6CgQElJST6sDAAA+IMWf2dHkjIyMjR69Gj169dP/fv3V05Ojo4cOaIxY8b4ujQAAOBjRoSdO++8Uz/++KOmTZsmp9OpK6+8Uvn5+actWsb5CQkJ0fTp00975IeWjXk1F3NrLub2wtiss31eCwAAoAVr8Wt2AAAAfglhBwAAGI2wAwAAjEbYAQAARiPsGGTDhg0aNmyYYmNjZbPZtHLlSo/+v/71r7rpppvc3xS9ffv2086xaNEiDRo0SHa7XTabTVVVVaeNOXDggNLS0mS32xUREaGxY8fq8OHDHmM+//xz/b//9/8UGhqquLg4zZkzx4tX+utztrm1LEvTpk1TTEyMwsLClJycrLKyMo8x3pq3d955R926dVNoaKh69eqlv/3tb16/3l+b3Nxcde7cWaGhoUpMTNTmzZvdfTU1NRo/frw6dOigtm3bauTIkad9ieru3buVmpqq1q1bKyoqSpMnT9aJEyc8xqxbt05XXXWVQkJC9Jvf/EZLliw5rzrQOE09txUVFbr77rt1+eWXKyAgQBMnTjythh07dmjkyJHq3LmzbDabcnJymupy/RZhxyBHjhxR7969lZub22D/wIEDNXv27AbPcfToUQ0ZMkT/9V//1eCYtLQ07dixQ6tXr9aqVau0YcMGpaenu/tdLpduuukmXXLJJSouLtbzzz+vp59+WosWLWr8xf3KnW1u58yZo/nz5ysvL09FRUVq06aNUlJSVFNT4x7jjXnbuHGj7rrrLo0dO1YlJSUaPny4hg8fri+//LLpLt5wb7/9tjIyMjR9+nRt27ZNvXv3VkpKivbt2ydJmjRpkt5//3298847Wr9+vfbu3asRI0a4jz958qRSU1N1/Phxbdy4UUuXLtWSJUs0bdo095jy8nKlpqbqhhtu0Pbt2zVx4kQ98MAD+vDDD8+5Dpy/5pjb2tpaderUSU899ZR69+59xjqOHj2qSy+9VLNmzWrwlwWMZ8FIkqwVK1acsa+8vNySZJWUlDR4/Nq1ay1J1sGDBz3av/rqK0uStWXLFnfb3//+d8tms1k//PCDZVmW9fLLL1vt27e3amtr3WOmTJlide3atdHXg//fv89tfX295XA4rOeff97dVlVVZYWEhFhvvvmmZVnem7c77rjDSk1N9agnMTHRevDBB716jb8m/fv3t8aPH+/eP3nypBUbG2tlZ2dbVVVVVlBQkPXOO++4+3fu3GlJsgoLCy3Lsqy//e1vVkBAgOV0Ot1jFi5caNntdvdcPvnkk1aPHj08XvfOO++0UlJSzqkONE5zzO3PXX/99dbjjz/+izVdcskl1rx58y7swlog7uzgvBQWFioiIkL9+vVztyUnJysgIEBFRUXuMdddd52Cg4PdY1JSUlRaWqqDBw82e82mKy8vl9PpVHJysrstPDxciYmJKiwslOS9eSssLPR4nVNjTr0Ozs/x48dVXFzs8Z4GBAQoOTlZhYWFKi4uVl1dnUd/t27dFB8f7zG3vXr18vgS1ZSUFLlcLu3YscM95pfm7Wx14Pw119zi3BB2cF6cTqeioqI82gIDAxUZGSmn0+ke8+/fXn1q/9QYeM+p9/RM7/nP58Qb89bQGOa1cX766SedPHmywffU6XQqODj4tB8q/ve5bey8uVwuHTt27Kx14Pw119zi3BB2AACA0Qg7OC8Oh+O0BYsnTpzQgQMH3AvfHA7HaZ8oOLX/q10c14ROvadnes9/PifemLeGxjCvjdOxY0e1atWqwffU4XDo+PHjp30q8t/ntrHzZrfbFRYWdtY6cP6aa25xbgg7OC9JSUmqqqpScXGxu23NmjWqr69XYmKie8yGDRtUV1fnHrN69Wp17dpV7du3b/aaTZeQkCCHw6GCggJ3m8vlUlFRkZKSkiR5b96SkpI8XufUmFOvg/MTHBysvn37eryn9fX1KigoUFJSkvr27augoCCP/tLSUu3evdtjbr/44guPMLt69WrZ7XZdccUV7jG/NG9nqwPnr7nmFufI1yuk4T2HDh2ySkpKrJKSEkuS9eKLL1olJSXWrl27LMuyrP3791slJSXWBx98YEmy3nrrLaukpMSqqKhwn6OiosIqKSmxXn31VUuStWHDBqukpMTav3+/e8yQIUOsPn36WEVFRdYnn3xidenSxbrrrrvc/VVVVVZ0dLR1zz33WF9++aX11ltvWa1bt7ZeeeWV5nszDHO2uZ01a5YVERFhvfvuu9bnn39u3XrrrVZCQoJ17Ngx9zm8MW+ffvqpFRgYaM2dO9fauXOnNX36dCsoKMj64osvmu/NMMxbb71lhYSEWEuWLLG++uorKz093YqIiHB/Auehhx6y4uPjrTVr1lhbt261kpKSrKSkJPfxJ06csHr27GnddNNN1vbt2638/HyrU6dOVmZmpnvMd999Z7Vu3dqaPHmytXPnTis3N9dq1aqVlZ+ff8514Pw1x9xaluX+u6Fv377W3XffbZWUlFg7duxw99fW1rrHxMTEWH/4wx+skpISq6ysrHneCD9A2DHIqY+L//s2evRoy7Isa/HixWfsnz59uvsc06dPP+OYxYsXu8fs37/fuuuuu6y2bdtadrvdGjNmjHXo0CGPWj777DNr4MCBVkhIiHXRRRdZs2bNaoZ3wFxnm9v6+norKyvLio6OtkJCQqwbb7zRKi0t9TiHt+btL3/5i3X55ZdbwcHBVo8ePawPPvigya7712LBggVWfHy8FRwcbPXv39/atGmTu+/YsWPWI488YrVv395q3bq1ddttt3n8D4plWdb3339vDR061AoLC7M6duxoPfHEE1ZdXZ3HmLVr11pXXnmlFRwcbF166aUe/02fSx1onOaY2zP93XDJJZe4+0993ci/b9dff31TXrpfsVmWZTX13SMAAABfYc0OAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg6AJjdo0CBNnDixwf7OnTsrJyenWWr5/vvvZbPZtH379mZ5PQC+R9gB4HNbtmxRenq6e99ms2nlypXnfPySJUsUERFxTmPj4uJUUVGhnj17nmeVAFqqQF8XAACdOnVqltc5fvy4goOD5XA4muX1APgH7uwAaBYnTpzQhAkTFB4ero4dOyorK0unfprv54+xOnfuLEm67bbbZLPZ3PufffaZbrjhBrVr1052u119+/bV1q1btW7dOo0ZM0bV1dWy2Wyy2Wx6+umn3ed69tlnde+998putys9Pf20x1jr1q2TzWZTQUGB+vXrp9atW2vAgAEqLS31qH/mzJmKiopSu3bt9MADD2jq1Km68sor3f3r1q1T//791aZNG0VEROjaa6/Vrl27murtBHAeCDsAmsXSpUsVGBiozZs366WXXtKLL76o//7v/z5t3JYtWyRJixcvVkVFhXs/LS1NF198sbZs2aLi4mJNnTpVQUFBGjBggHJycmS321VRUaGKigr94Q9/cJ9v7ty56t27t0pKSpSVldVgfX/84x/1wgsvaOvWrQoMDNT999/v7lu2bJmee+45zZ49W8XFxYqPj9fChQvd/SdOnNDw4cN1/fXX6/PPP1dhYaHS09Nls9ku+H0DcOF4jAWgWcTFxWnevHmy2Wzq2rWrvvjiC82bN0/jxo3zGHfqkVZERITH46bdu3dr8uTJ6tatmySpS5cu7r7w8HDZbLYzPp4aPHiwnnjiCff+999/f8b6nnvuOV1//fWSpKlTpyo1NVU1NTUKDQ3VggULNHbsWI0ZM0aSNG3aNH300Uc6fPiwJMnlcqm6ulq33HKLLrvsMklS9+7dz+v9AdB0uLMDoFlcc801Hnc6kpKSVFZWppMnT57T8RkZGXrggQeUnJysWbNm6Z///Oc5HdevX79zGvfb3/7W/eeYmBhJ0r59+yRJpaWl6t+/v8f4n+9HRkbqvvvuU0pKioYNG6aXXnpJFRUV5/S6AJoeYQdAi/D0009rx44dSk1N1Zo1a3TFFVdoxYoVZz2uTZs253T+oKAg959PhbL6+vpzrm/x4sUqLCzUgAED9Pbbb+vyyy/Xpk2bzvl4AE2HsAOgWRQVFXnsb9q0SV26dFGrVq1OGxsUFHTGOz6XX365Jk2apI8++kgjRozQ4sWLJUnBwcHnfIeoMbp27epeO3TKv+9LUp8+fZSZmamNGzeqZ8+eWr58eZPVBODcEXYANIvdu3crIyNDpaWlevPNN7VgwQI9/vjjZxzbuXNnFRQUyOl06uDBgzp27JgmTJigdevWadeuXfr000+1ZcsW97qYzp076/DhwyooKNBPP/2ko0ePerX2Rx99VK+99pqWLl2qsrIyzZw5U59//rn7DlB5ebkyMzNVWFioXbt26aOPPlJZWRnrdgA/wQJlAM3i3nvv1bFjx9S/f3+1atVKjz/+uMcXCf7cCy+8oIyMDL366qu66KKL9M0332j//v269957VVlZqY4dO2rEiBF65plnJEkDBgzQQw89pDvvvFP79+/X9OnT3R8/94a0tDR99913+sMf/qCamhrdcccduu+++7R582ZJUuvWrfX1119r6dKl2r9/v2JiYjR+/Hg9+OCDXqsBQOPZrFNfdAEAOGe/+93v5HA49D//8z++LgXAWXBnBwDO4ujRo8rLy1NKSopatWqlN998Ux9//LFWr17t69IAnAPu7ADAWRw7dkzDhg1TSUmJampq1LVrVz311FMaMWKEr0sDcA4IOwAAwGh8GgsAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMNr/B8Rx0ZpV4J3NAAAAAElFTkSuQmCC", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGxCAYAAACEFXd4AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAArJElEQVR4nO3df1RVdb7/8deWH0c0PArmOZ4R0+6ln5h1MZ1oSkrFHH/crjNZ6TUrKx3TIjSTrzVSK0Htpszozcauo968ZnfdyaZucwt0Ei2sEGFKm7QaUirOUA4BKgLK/v4xy706IUZ44Bw+Ph9r7bXcn89n7/PefNZZvfrsvcGybdsWAACAobqEugAAAID2RNgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABgtMtQFhIOmpiZ9+eWXio2NlWVZoS4HAAC0gm3bqq2tlc/nU5cuLa/fEHYkffnll0pISAh1GQAAoA3Ky8vVr1+/FvtDGnZ27Nihp556SsXFxaqoqNCWLVt08803n3bsjBkztGbNGq1YsULp6elOe319vebNm6cXXnhBdXV1GjFihJ555pkzXvR3xcbGSvr7D6tHjx5nc0kAAKCD1NTUKCEhwfnveEtCGnaOHj2qwYMH66677tLPfvazFse9/PLLevfdd+Xz+Zr1paen69VXX9XmzZsVHx+vuXPnaty4cSouLlZERESr6jh166pHjx6EHQAAOpnvewQlpGFnzJgxGjNmzBnHfPHFF5o9e7beeOMNjR07NqCvurpaa9eu1fPPP6+RI0dKkjZu3KiEhARt3bpVo0ePbrfaAQBA5xDWb2M1NTVp6tSpevjhh3X55Zc36y8uLlZjY6PS0tKcNp/Pp6SkJBUWFrZ43vr6etXU1ARsAADATGEddpYuXarIyEg98MADp+33+/2Kjo5Wr169Ato9Ho/8fn+L583JyZHb7XY2Hk4GAMBcYRt2iouL9atf/Urr16//wa+D27Z9xmMyMzNVXV3tbOXl5WdbLgAACFNhG3Z27typyspK9e/fX5GRkYqMjNTBgwc1d+5cDRgwQJLk9XrV0NCgqqqqgGMrKyvl8XhaPLfL5XIeRuahZAAAzBa2YWfq1Kl6//33VVpa6mw+n08PP/yw3njjDUlScnKyoqKilJ+f7xxXUVGhvXv3KiUlJVSlAwCAMBLSt7GOHDmiTz75xNkvKytTaWmp4uLi1L9/f8XHxweMj4qKktfr1cUXXyxJcrvdmj59uubOnav4+HjFxcVp3rx5GjRokPN2FgAAOLeFNOzs3r1bN9xwg7OfkZEhSZo2bZrWr1/fqnOsWLFCkZGRmjRpkvNLBdevX9/q37EDAADMZtm2bYe6iFCrqamR2+1WdXU1z+8AANBJtPa/32H7zA4AAEAwEHYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABgtpL9n51wwYMFroS7hnPXZkrGhLgEAEAZY2QEAAEYj7AAAAKMRdgAAgNEIOwAAwGg8oAwA38GLBaHDiwVoD6zsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwWkjDzo4dOzR+/Hj5fD5ZlqWXX37Z6WtsbNQjjzyiQYMGqXv37vL5fLrjjjv05ZdfBpyjvr5ec+bMUe/evdW9e3dNmDBBn3/+eQdfCQAACFeRofzwo0ePavDgwbrrrrv0s5/9LKDv2LFj2rNnjx577DENHjxYVVVVSk9P14QJE7R7925nXHp6ul599VVt3rxZ8fHxmjt3rsaNG6fi4mJFRER09CXhHDJgwWuhLuGc9dmSsaEuAUAnEtKwM2bMGI0ZM+a0fW63W/n5+QFtK1eu1NChQ3Xo0CH1799f1dXVWrt2rZ5//nmNHDlSkrRx40YlJCRo69atGj16dLtfAwAACG+d6pmd6upqWZalnj17SpKKi4vV2NiotLQ0Z4zP51NSUpIKCwtbPE99fb1qamoCNgAAYKZOE3aOHz+uBQsWaPLkyerRo4ckye/3Kzo6Wr169QoY6/F45Pf7WzxXTk6O3G63syUkJLRr7QAAIHQ6RdhpbGzUbbfdpqamJj3zzDPfO962bVmW1WJ/Zmamqqurna28vDyY5QIAgDAS9mGnsbFRkyZNUllZmfLz851VHUnyer1qaGhQVVVVwDGVlZXyeDwtntPlcqlHjx4BGwAAMFNYh51TQefjjz/W1q1bFR8fH9CfnJysqKiogAeZKyoqtHfvXqWkpHR0uQAAIAyF9G2sI0eO6JNPPnH2y8rKVFpaqri4OPl8Pv385z/Xnj179L//+786efKk8xxOXFycoqOj5Xa7NX36dM2dO1fx8fGKi4vTvHnzNGjQIOftLAAAcG4LadjZvXu3brjhBmc/IyNDkjRt2jRlZWXplVdekSRdeeWVAce9+eabSk1NlSStWLFCkZGRmjRpkurq6jRixAitX7+e37EDAAAkhTjspKamyrbtFvvP1HdK165dtXLlSq1cuTKYpQEAAEOE9TM7AAAAZ4uwAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADBaZKgLAACgIwxY8FqoSzhnfbZkbEg/n5UdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaCENOzt27ND48ePl8/lkWZZefvnlgH7btpWVlSWfz6eYmBilpqZq3759AWPq6+s1Z84c9e7dW927d9eECRP0+eefd+BVAACAcBbSsHP06FENHjxYq1atOm3/smXLtHz5cq1atUpFRUXyer0aNWqUamtrnTHp6enasmWLNm/erLfeektHjhzRuHHjdPLkyY66DAAAEMYiQ/nhY8aM0ZgxY07bZ9u2cnNztXDhQk2cOFGStGHDBnk8Hm3atEkzZsxQdXW11q5dq+eff14jR46UJG3cuFEJCQnaunWrRo8e3WHXAgAAwlPYPrNTVlYmv9+vtLQ0p83lcmn48OEqLCyUJBUXF6uxsTFgjM/nU1JSkjPmdOrr61VTUxOwAQAAM4Vt2PH7/ZIkj8cT0O7xeJw+v9+v6Oho9erVq8Uxp5OTkyO32+1sCQkJQa4eAACEi7ANO6dYlhWwb9t2s7bv+r4xmZmZqq6udrby8vKg1AoAAMJP2IYdr9crSc1WaCorK53VHq/Xq4aGBlVVVbU45nRcLpd69OgRsAEAADOFbdgZOHCgvF6v8vPznbaGhgYVFBQoJSVFkpScnKyoqKiAMRUVFdq7d68zBgAAnNtC+jbWkSNH9Mknnzj7ZWVlKi0tVVxcnPr376/09HRlZ2crMTFRiYmJys7OVrdu3TR58mRJktvt1vTp0zV37lzFx8crLi5O8+bN06BBg5y3swAAwLktpGFn9+7duuGGG5z9jIwMSdK0adO0fv16zZ8/X3V1dZo1a5aqqqo0bNgw5eXlKTY21jlmxYoVioyM1KRJk1RXV6cRI0Zo/fr1ioiI6PDrAQAA4SekYSc1NVW2bbfYb1mWsrKylJWV1eKYrl27auXKlVq5cmU7VAgAADq7sH1mBwAAIBgIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRwjrsnDhxQo8++qgGDhyomJgYXXjhhXriiSfU1NTkjLFtW1lZWfL5fIqJiVFqaqr27dsXwqoBAEA4Ceuws3TpUj377LNatWqV/vznP2vZsmV66qmntHLlSmfMsmXLtHz5cq1atUpFRUXyer0aNWqUamtrQ1g5AAAIF2Eddnbt2qV//ud/1tixYzVgwAD9/Oc/V1pamnbv3i3p76s6ubm5WrhwoSZOnKikpCRt2LBBx44d06ZNm0JcPQAACAdhHXZ+8pOfaNu2bTpw4IAk6U9/+pPeeust/fSnP5UklZWVye/3Ky0tzTnG5XJp+PDhKiwsbPG89fX1qqmpCdgAAICZIkNdwJk88sgjqq6u1iWXXKKIiAidPHlSixcv1u233y5J8vv9kiSPxxNwnMfj0cGDB1s8b05Ojh5//PH2KxwAAISNsF7ZefHFF7Vx40Zt2rRJe/bs0YYNG/Rv//Zv2rBhQ8A4y7IC9m3bbtb2bZmZmaqurna28vLydqkfAACEXliv7Dz88MNasGCBbrvtNknSoEGDdPDgQeXk5GjatGnyer2S/r7C07dvX+e4ysrKZqs93+ZyueRyudq3eAAAEBbCemXn2LFj6tIlsMSIiAjn1fOBAwfK6/UqPz/f6W9oaFBBQYFSUlI6tFYAABCe2rSyU15eLsuy1K9fP0nSe++9p02bNumyyy7TfffdF7Tixo8fr8WLF6t///66/PLLVVJSouXLl+vuu++W9PfbV+np6crOzlZiYqISExOVnZ2tbt26afLkyUGrAwAAdF5tCjuTJ0/Wfffdp6lTp8rv92vUqFG6/PLLtXHjRvn9fv3yl78MSnErV67UY489plmzZqmyslI+n08zZswIOP/8+fNVV1enWbNmqaqqSsOGDVNeXp5iY2ODUgMAAOjc2nQba+/evRo6dKgk6b//+7+VlJSkwsJCbdq0SevXrw9acbGxscrNzdXBgwdVV1enTz/9VE8++aSio6OdMZZlKSsrSxUVFTp+/LgKCgqUlJQUtBoAAEDn1qaw09jY6Dzgu3XrVk2YMEGSdMkll6iioiJ41QEAAJylNoWdyy+/XM8++6x27typ/Px83XTTTZKkL7/8UvHx8UEtEAAA4Gy0KewsXbpUv/nNb5Samqrbb79dgwcPliS98sorzu0tAACAcNCmB5RTU1P19ddfq6amRr169XLa77vvPnXv3j1oxQEAAJytNq3s3HjjjaqtrQ0IOpIUFxenW2+9NSiFAQAABEObws727dvV0NDQrP348ePauXPnWRcFAAAQLD/oNtb777/v/PvDDz90/hCnJJ08eVKvv/66fvSjHwWvOgAAgLP0g8LOlVdeKcuyZFmWbrzxxmb9MTExWrlyZdCKAwAAOFs/KOyUlZXJtm1deOGFeu+993T++ec7fdHR0erTp48iIiKCXiQAAEBb/aCwc8EFF0iS84c4AQAAwl2bXj2XpAMHDmj79u2qrKxsFn6C9bexAAAAzlabws5zzz2nX/ziF+rdu7e8Xq8sy3L6LMsi7AAAgLDRprDz5JNPavHixXrkkUeCXQ8AAEBQten37FRVVemWW24Jdi0AAABB16awc8sttygvLy/YtQAAAARdm25j/eM//qMee+wxvfPOOxo0aJCioqIC+h944IGgFAcAAHC22hR21qxZo/POO08FBQUqKCgI6LMsi7ADAADCRpvCTllZWbDrAAAAaBdtemYHAACgs2jTys7dd999xv7f/va3bSoGAAAg2NoUdqqqqgL2GxsbtXfvXn3zzTen/QOhAAAAodKmsLNly5ZmbU1NTZo1a5YuvPDCsy4KAAAgWIL2zE6XLl300EMPacWKFcE6JQAAwFkL6gPKn376qU6cOBHMUwIAAJyVNt3GysjICNi3bVsVFRV67bXXNG3atKAUBgAAEAxtCjslJSUB+126dNH555+vp59++nvf1AIAAOhIbQo7b775ZrDrAAAAaBdtCjunfPXVV9q/f78sy9JFF12k888/P1h1AQAABEWbHlA+evSo7r77bvXt21fXX3+9rrvuOvl8Pk2fPl3Hjh0Ldo0AAABt1qawk5GRoYKCAr366qv65ptv9M033+j3v/+9CgoKNHfu3GDXCAAA0GZtuo31u9/9Tv/zP/+j1NRUp+2nP/2pYmJiNGnSJK1evTpY9QEAAJyVNq3sHDt2TB6Pp1l7nz59uI0FAADCSpvCzjXXXKNFixbp+PHjTltdXZ0ef/xxXXPNNUErDgAA4Gy16TZWbm6uxowZo379+mnw4MGyLEulpaVyuVzKy8sLdo0AAABt1qawM2jQIH388cfauHGjPvroI9m2rdtuu01TpkxRTExMsGsEAABoszaFnZycHHk8Ht17770B7b/97W/11Vdf6ZFHHglKcQAAAGerTc/s/OY3v9Ell1zSrP3yyy/Xs88+e9ZFAQAABEubwo7f71ffvn2btZ9//vmqqKg466IAAACCpU1hJyEhQW+//Xaz9rfffls+n++siwIAAAiWNj2zc8899yg9PV2NjY268cYbJUnbtm3T/Pnz+Q3KAAAgrLQp7MyfP19/+9vfNGvWLDU0NEiSunbtqkceeUSZmZlBLRAAAOBstCnsWJalpUuX6rHHHtOf//xnxcTEKDExUS6XK9j1AQAAnJU2PbNzynnnnaerr75aSUlJ7RZ0vvjiC/3rv/6r4uPj1a1bN1155ZUqLi52+m3bVlZWlnw+n2JiYpSamqp9+/a1Sy0AAKDzOauw096qqqp07bXXKioqSv/3f/+nDz/8UE8//bR69uzpjFm2bJmWL1+uVatWqaioSF6vV6NGjVJtbW3oCgcAAGGjTbexOsrSpUuVkJCgdevWOW0DBgxw/m3btnJzc7Vw4UJNnDhRkrRhwwZ5PB5t2rRJM2bMOO156+vrVV9f7+zX1NS0zwUAAICQC+uVnVdeeUVDhgzRLbfcoj59+uiqq67Sc8895/SXlZXJ7/crLS3NaXO5XBo+fLgKCwtbPG9OTo7cbrezJSQktOt1AACA0AnrsPOXv/xFq1evVmJiot544w3NnDlTDzzwgP7zP/9T0t9/uaEkeTyegOM8Ho/TdzqZmZmqrq52tvLy8va7CAAAEFJhfRurqalJQ4YMUXZ2tiTpqquu0r59+7R69WrdcccdzjjLsgKOs227Wdu3uVwu3hwDAOAcEdYrO3379tVll10W0HbppZfq0KFDkiSv1ytJzVZxKisrm632AACAc1NYh51rr71W+/fvD2g7cOCALrjgAknSwIED5fV6lZ+f7/Q3NDSooKBAKSkpHVorAAAIT2F9G+uhhx5SSkqKsrOzNWnSJL333ntas2aN1qxZI+nvt6/S09OVnZ2txMREJSYmKjs7W926ddPkyZNDXD0AAAgHYR12rr76am3ZskWZmZl64oknNHDgQOXm5mrKlCnOmPnz56uurk6zZs1SVVWVhg0bpry8PMXGxoawcgAAEC7COuxI0rhx4zRu3LgW+y3LUlZWlrKysjquKAAA0GmE9TM7AAAAZ4uwAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABitU4WdnJwcWZal9PR0p822bWVlZcnn8ykmJkapqanat29f6IoEAABhpdOEnaKiIq1Zs0ZXXHFFQPuyZcu0fPlyrVq1SkVFRfJ6vRo1apRqa2tDVCkAAAgnnSLsHDlyRFOmTNFzzz2nXr16Oe22bSs3N1cLFy7UxIkTlZSUpA0bNujYsWPatGlTi+err69XTU1NwAYAAMzUKcLO/fffr7Fjx2rkyJEB7WVlZfL7/UpLS3PaXC6Xhg8frsLCwhbPl5OTI7fb7WwJCQntVjsAAAitsA87mzdv1p49e5STk9Osz+/3S5I8Hk9Au8fjcfpOJzMzU9XV1c5WXl4e3KIBAEDYiAx1AWdSXl6uBx98UHl5eeratWuL4yzLCti3bbtZ27e5XC65XK6g1QkAAMJXWK/sFBcXq7KyUsnJyYqMjFRkZKQKCgr061//WpGRkc6KzndXcSorK5ut9gAAgHNTWIedESNG6IMPPlBpaamzDRkyRFOmTFFpaakuvPBCeb1e5efnO8c0NDSooKBAKSkpIawcAACEi7C+jRUbG6ukpKSAtu7duys+Pt5pT09PV3Z2thITE5WYmKjs7Gx169ZNkydPDkXJAAAgzIR12GmN+fPnq66uTrNmzVJVVZWGDRumvLw8xcbGhro0AAAQBjpd2Nm+fXvAvmVZysrKUlZWVkjqAQAA4S2sn9kBAAA4W4QdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGhhHXZycnJ09dVXKzY2Vn369NHNN9+s/fv3B4yxbVtZWVny+XyKiYlRamqq9u3bF6KKAQBAuAnrsFNQUKD7779f77zzjvLz83XixAmlpaXp6NGjzphly5Zp+fLlWrVqlYqKiuT1ejVq1CjV1taGsHIAABAuIkNdwJm8/vrrAfvr1q1Tnz59VFxcrOuvv162bSs3N1cLFy7UxIkTJUkbNmyQx+PRpk2bNGPGjFCUDQAAwkhYr+x8V3V1tSQpLi5OklRWVia/36+0tDRnjMvl0vDhw1VYWNjieerr61VTUxOwAQAAM3WasGPbtjIyMvSTn/xESUlJkiS/3y9J8ng8AWM9Ho/Tdzo5OTlyu93OlpCQ0H6FAwCAkOo0YWf27Nl6//339cILLzTrsywrYN+27WZt35aZmanq6mpnKy8vD3q9AAAgPIT1MzunzJkzR6+88op27Nihfv36Oe1er1fS31d4+vbt67RXVlY2W+35NpfLJZfL1X4FAwCAsBHWKzu2bWv27Nl66aWX9Mc//lEDBw4M6B84cKC8Xq/y8/OdtoaGBhUUFCglJaWjywUAAGEorFd27r//fm3atEm///3vFRsb6zyH43a7FRMTI8uylJ6eruzsbCUmJioxMVHZ2dnq1q2bJk+eHOLqAQBAOAjrsLN69WpJUmpqakD7unXrdOedd0qS5s+fr7q6Os2aNUtVVVUaNmyY8vLyFBsb28HVAgCAcBTWYce27e8dY1mWsrKylJWV1f4FAQCATiesn9kBAAA4W4QdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGjGhJ1nnnlGAwcOVNeuXZWcnKydO3eGuiQAABAGjAg7L774otLT07Vw4UKVlJTouuuu05gxY3To0KFQlwYAAELMiLCzfPlyTZ8+Xffcc48uvfRS5ebmKiEhQatXrw51aQAAIMQiQ13A2WpoaFBxcbEWLFgQ0J6WlqbCwsLTHlNfX6/6+npnv7q6WpJUU1MT9Pqa6o8F/ZxonfaYz29jbkOHuTVXe84t8xo67TWvp85r2/YZx3X6sPP111/r5MmT8ng8Ae0ej0d+v/+0x+Tk5Ojxxx9v1p6QkNAuNSI03LmhrgDthbk1F3Nrpvae19raWrnd7hb7O33YOcWyrIB927abtZ2SmZmpjIwMZ7+pqUl/+9vfFB8f3+Ix56KamholJCSovLxcPXr0CHU5CCLm1kzMq7mY29OzbVu1tbXy+XxnHNfpw07v3r0VERHRbBWnsrKy2WrPKS6XSy6XK6CtZ8+e7VVip9ejRw++XIZibs3EvJqLuW3uTCs6p3T6B5Sjo6OVnJys/Pz8gPb8/HylpKSEqCoAABAuOv3KjiRlZGRo6tSpGjJkiK655hqtWbNGhw4d0syZM0NdGgAACDEjws6tt96qw4cP64knnlBFRYWSkpL0hz/8QRdccEGoS+vUXC6XFi1a1OyWHzo/5tZMzKu5mNuzY9nf974WAABAJ9bpn9kBAAA4E8IOAAAwGmEHAAAYjbADAACMRtgx0DPPPKOBAweqa9euSk5O1s6dO50+27aVlZUln8+nmJgYpaamat++fQHH19fXa86cOerdu7e6d++uCRMm6PPPPw8YU1VVpalTp8rtdsvtdmvq1Kn65ptvAsYcOnRI48ePV/fu3dW7d2898MADamhoaLfrNl1HzOvixYuVkpKibt26tfiLNh988EElJyfL5XLpyiuvDPZlnnN27Nih8ePHy+fzybIsvfzyywH9fGc7p++b15deekmjR49W7969ZVmWSktLm51jzZo1Sk1NVY8ePWRZVrP5kpjX1iLsGObFF19Uenq6Fi5cqJKSEl133XUaM2aMDh06JElatmyZli9frlWrVqmoqEher1ejRo1SbW2tc4709HRt2bJFmzdv1ltvvaUjR45o3LhxOnnypDNm8uTJKi0t1euvv67XX39dpaWlmjp1qtN/8uRJjR07VkePHtVbb72lzZs363e/+53mzp3bcT8Mg3TUvDY0NOiWW27RL37xixZrsW1bd999t2699db2u+BzyNGjRzV48GCtWrXqtP18Zzun75vXo0eP6tprr9WSJUtaPMexY8d000036f/9v//X4hjmtZVsGGXo0KH2zJkzA9ouueQSe8GCBXZTU5Pt9XrtJUuWOH3Hjx+33W63/eyzz9q2bdvffPONHRUVZW/evNkZ88UXX9hdunSxX3/9ddu2bfvDDz+0JdnvvPOOM2bXrl22JPujjz6ybdu2//CHP9hdunSxv/jiC2fMCy+8YLtcLru6ujr4F264jpjXb1u3bp3tdrvPWNOiRYvswYMHt/2i0Iwke8uWLc4+31kzfHdev62srMyWZJeUlLR4/JtvvmlLsquqqgLamdfWY2XHIA0NDSouLlZaWlpAe1pamgoLC1VWVia/3x/Q73K5NHz4cBUWFkqSiouL1djYGDDG5/MpKSnJGbNr1y653W4NGzbMGfPjH/9Ybrc7YExSUlLAH2cbPXq06uvrVVxcHPyLN1hHzSvCD99ZnAnz2nqEHYN8/fXXOnnyZLM/gOrxeOT3+50/ltpSvyT5/X5FR0erV69eZxzTp0+fZp/fp0+fgDHf/ZxevXopOjq62R9txZl11Lwi/PCdxZkwr61H2DGQZVkB+7ZtB7R9X//pfN852joGrdcR84rwxHcWLWFeW4ewY5DevXsrIiKiWVqvrKyUx+OR1+uVpBb7Jcnr9aqhoUFVVVVnHPPXv/612ed/9dVXAWO++zlVVVVqbGxs9n8ZOLOOmleEH76zOBPmtfUIOwaJjo5WcnKy8vPzA9rz8/OVkpKigQMHyuv1BvQ3NDSooKBAKSkpkqTk5GRFRUUFjKmoqNDevXudMddcc42qq6v13nvvOWPeffddVVdXB4zZu3evKioqnDF5eXlyuVxKTk4O/sUbrKPmFeGH7yzOhHn9AULxVDTaz+bNm+2oqCh77dq19ocffminp6fb3bt3tz/77DPbtm17yZIlttvttl966SX7gw8+sG+//Xa7b9++dk1NjXOOmTNn2v369bO3bt1q79mzx77xxhvtwYMH2ydOnHDG3HTTTfYVV1xh79q1y961a5c9aNAge9y4cU7/iRMn7KSkJHvEiBH2nj177K1bt9r9+vWzZ8+e3XE/DIN01LwePHjQLikpsR9//HH7vPPOs0tKSuySkhK7trbWGfPxxx/bJSUl9owZM+yLLrrIGVNfX99xPxCD1NbWOj9DSfby5cvtkpIS++DBg7Zt853trL5vXg8fPmyXlJTYr732mi3J3rx5s11SUmJXVFQ456ioqLBLSkrs5557zpZk79ixwy4pKbEPHz7sjGFeW4ewY6B///d/ty+44AI7Ojra/qd/+ie7oKDA6WtqarIXLVpke71e2+Vy2ddff739wQcfBBxfV1dnz549246Li7NjYmLscePG2YcOHQoYc/jwYXvKlCl2bGysHRsba0+ZMqXZa5EHDx60x44da8fExNhxcXH27Nmz7ePHj7fbdZuuI+Z12rRptqRm25tvvumMGT58+GnHlJWVteflG+vUa8Xf3aZNm2bbNt/Zzur75nXdunWn7V+0aJFzjkWLFp12zLp165wxzGvrWLZt2+29egQAABAqPLMDAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAOgQ6Smpio9Pb3F/gEDBig3N7dDavnss89kWZZKS0s75PMAhBZhB0BYKCoq0n333efsW5all19+udXHr1+/Xj179mzV2ISEBFVUVCgpKekHVgmgM4oMdQEAIEnnn39+h3xOQ0ODoqOj5fV6O+TzAIQeKzsAOsyJEyc0e/Zs9ezZU/Hx8Xr00Ud16s/zffs21oABAyRJ//Iv/yLLspz9P/3pT7rhhhsUGxurHj16KDk5Wbt379b27dt11113qbq6WpZlybIsZWVlOed68skndeedd8rtduvee+9tdhtr+/btsixL27Zt05AhQ9StWzelpKRo//79AfU/+eST6tOnj2JjY3XPPfdowYIFuvLKK53+7du3a+jQoerevbt69uypa6+9VgcPHmyvHyeAViLsAOgwGzZsUGRkpN599139+te/1ooVK/Qf//EfzcYVFRVJktatW6eKigpnf8qUKerXr5+KiopUXFysBQsWKCoqSikpKcrNzVWPHj1UUVGhiooKzZs3zznfU089paSkJBUXF+uxxx5rsb6FCxfq6aef1u7duxUZGam7777b6fuv//ovLV68WEuXLlVxcbH69++v1atXO/0nTpzQzTffrOHDh+v999/Xrl27dN9998myrLP+uQE4O9zGAtBhEhIStGLFClmWpYsvvlgffPCBVqxYoXvvvTdg3KlbWj179gy43XTo0CE9/PDDuuSSSyRJiYmJTp/b7ZZlWae9PXXjjTcGhJ/PPvvstPUtXrxYw4cPlyQtWLBAY8eO1fHjx9W1a1etXLlS06dP11133SVJ+uUvf6m8vDwdOXJEklRTU6Pq6mqNGzdO//AP/yBJuvTSS3/QzwdA+2BlB0CH+fGPfxyw0nHNNdfo448/1smTJ1t1fEZGhu655x6NHDlSS5Ys0aefftqq44YMGdKqcVdccYXz7759+0qSKisrJUn79+/X0KFDA8Z/ez8uLk533nmnRo8erfHjx+tXv/qVKioqWvW5ANoXYQdAp5GVlaV9+/Zp7Nix+uMf/6jLLrtMW7Zs+d7junfv3qrzR0VFOf8+FcqampqatZ1y6nmjU9atW6ddu3YpJSVFL774oi666CK98847rfpsAO2HsAOgw3z3P/zvvPOOEhMTFRER0WxsVFTUaVd8LrroIj300EPKy8vTxIkTtW7dOklSdHR0q1eI2uLiiy/We++9F9C2e/fuZuOuuuoqZWZmqrCwUElJSdq0aVO71QSgdQg7ADpMeXm5MjIytH//fr3wwgtauXKlHnzwwdOOHTBggLZt2ya/36+qqirV1dVp9uzZ2r59uw4ePKi3335bRUVFznMxAwYM0JEjR7Rt2zZ9/fXXOnbsWFBrnzNnjtauXasNGzbo448/1pNPPqn333/fWe0pKytTZmamdu3apYMHDyovL08HDhzguR0gDPCAMoAOc8cdd6iurk5Dhw5VRESE5syZE/CLBL/t6aefVkZGhp577jn96Ec/0oEDB3T48GHdcccd+utf/6revXtr4sSJevzxxyVJKSkpmjlzpm699VYdPnxYixYtcl4/D4YpU6boL3/5i+bNm6fjx49r0qRJuvPOO53Vnm7duumjjz7Shg0bdPjwYfXt21ezZ8/WjBkzglYDgLax7O/edAYAtMqoUaPk9Xr1/PPPh7oUAGfAyg4AtMKxY8f07LPPavTo0YqIiNALL7ygrVu3Kj8/P9SlAfgerOwAQCvU1dVp/Pjx2rNnj+rr63XxxRfr0Ucf1cSJE0NdGoDvQdgBAABG420sAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBo/x/qNHUkUX6pGAAAAABJRU5ErkJggg==", "text/plain": [ "
" ] @@ -227,8 +227,8 @@ "\n", "\n", "@aq.main(num_qubits=n_bell * 2)\n", - "def multiple_bell() -> None:\n", - " for i in aq.range(0, 2 * n_bell, 2):\n", + "def multiple_bell():\n", + " for i in aq.range(0, len(aq.qubits), 2):\n", " bell(i, i + 1)\n", "\n", "\n", @@ -268,7 +268,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.9.18" } }, "nbformat": 4, diff --git a/examples/autoqasm/3_1_Iterative_phase_estimation.ipynb b/examples/autoqasm/3_1_Iterative_phase_estimation.ipynb index 9fbae181..4d3e9840 100644 --- a/examples/autoqasm/3_1_Iterative_phase_estimation.ipynb +++ b/examples/autoqasm/3_1_Iterative_phase_estimation.ipynb @@ -45,7 +45,7 @@ "outputs": [], "source": [ "@aq.subroutine\n", - "def phase_oracle(ancilla_qubit: int, data_qubit: int) -> None:\n", + "def phase_oracle(ancilla_qubit: int, data_qubit: int):\n", " \"\"\"Phase oracle that applies phase oracle on q1\n", " conditioned on q0.\n", "\n", @@ -145,7 +145,7 @@ }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGwCAYAAABPSaTdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/SrBM8AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAie0lEQVR4nO3df1RUdf7H8dcgiojOGP4YIDHY1sRfaWkq6ikzijZzc3UrW3czNWkNLcT8wUl0LQ31rD/SNSm3xPaoW+2uttbJ1UjthwhK2m+JCpX96qCpzCgKosz3j45zmlXLhoEZPj0f58w53c+9c3njPzy7c2fG4na73QIAADBUSKAHAAAAqEvEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMFhroAYJBTU2NDh06pBYtWshisQR6HAAAcAXcbrdOnjypmJgYhYRc/voNsSPp0KFDio2NDfQYAADAB6WlpWrXrt1l9xM7klq0aCHpu38sq9Ua4GkAAMCVcLlcio2N9fwdvxxiR/K8dGW1WokdAAAamB+7BYUblAEAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABgtoLHz7rvvasiQIYqJiZHFYtGGDRu89rvdbs2cOVPR0dEKDw9XUlKSiouLvY45fvy4Ro4cKavVqpYtW2rs2LE6depUPf4WAAAgmAU0dioqKtS9e3ctX778kvsXLFigpUuXKjs7W/n5+YqIiFBycrIqKys9x4wcOVKfffaZtmzZojfeeEPvvvuuUlJS6utXAAAAQc7idrvdgR5C+u4bS9evX6+hQ4dK+u6qTkxMjCZPnqwnnnhCkuR0OmW325WTk6MRI0boiy++UOfOnbVr1y716tVLkrRp0ybddddd+u9//6uYmJgr+tkul0s2m01Op5NvPQcAoIG40r/fQXvPTklJiRwOh5KSkjxrNptNffr0UV5eniQpLy9PLVu29ISOJCUlJSkkJET5+fmXPXdVVZVcLpfXAwAAmCk00ANcjsPhkCTZ7Xavdbvd7tnncDjUtm1br/2hoaGKjIz0HHMpWVlZmj17tp8nvrS46W/Wy88BACBY7Z83OKA/P2iv7NSljIwMOZ1Oz6O0tDTQIwEAgDoStLETFRUlSSorK/NaLysr8+yLiorSkSNHvPafO3dOx48f9xxzKWFhYbJarV4PAABgpqCNnfj4eEVFRSk3N9ez5nK5lJ+fr8TERElSYmKiysvLVVhY6DnmnXfeUU1Njfr06VPvMwMAgOAT0Ht2Tp06pa+++sqzXVJSor179yoyMlLt27dXWlqa5syZow4dOig+Pl6ZmZmKiYnxvGOrU6dOuvPOOzVu3DhlZ2erurpaEyZM0IgRI674nVgAAMBsAY2d3bt369Zbb/Vsp6enS5JGjRqlnJwcTZ06VRUVFUpJSVF5ebkGDBigTZs2qWnTpp7nrFmzRhMmTNBtt92mkJAQDR8+XEuXLq333wUAAASnoPmcnUCqy8/Z4d1YAICfu7p6N1aD/5wdAAAAfyB2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGC2oY+f8+fPKzMxUfHy8wsPDde211+rpp5+W2+32HON2uzVz5kxFR0crPDxcSUlJKi4uDuDUAAAgmAR17MyfP18rVqzQX/7yF33xxReaP3++FixYoGXLlnmOWbBggZYuXars7Gzl5+crIiJCycnJqqysDODkAAAgWIQGeoAfsmPHDt1zzz0aPHiwJCkuLk7r1q1TQUGBpO+u6ixZskQzZszQPffcI0l6+eWXZbfbtWHDBo0YMeKS562qqlJVVZVn2+Vy1fFvAgAAAiWor+z069dPubm5+vLLLyVJH330kd5//3396le/kiSVlJTI4XAoKSnJ8xybzaY+ffooLy/vsufNysqSzWbzPGJjY+v2FwEAAAET1Fd2pk+fLpfLpYSEBDVq1Ejnz5/X3LlzNXLkSEmSw+GQJNntdq/n2e12z75LycjIUHp6umfb5XIRPAAAGCqoY+fVV1/VmjVrtHbtWnXp0kV79+5VWlqaYmJiNGrUKJ/PGxYWprCwMD9OCgAAglVQx86UKVM0ffp0z7033bp104EDB5SVlaVRo0YpKipKklRWVqbo6GjP88rKytSjR49AjAwAAIJMUN+zc/r0aYWEeI/YqFEj1dTUSJLi4+MVFRWl3Nxcz36Xy6X8/HwlJibW66wAACA4BfWVnSFDhmju3Llq3769unTpoj179mjRokUaM2aMJMlisSgtLU1z5sxRhw4dFB8fr8zMTMXExGjo0KGBHR4AAASFoI6dZcuWKTMzU48++qiOHDmimJgYPfLII5o5c6bnmKlTp6qiokIpKSkqLy/XgAEDtGnTJjVt2jSAkwMAgGBhcX//44h/plwul2w2m5xOp6xWq1/PHTf9Tb+eDwCAhmb/vMF1ct4r/fsd1PfsAAAA1BaxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMFfez83//9n37/+9+rVatWCg8PV7du3bR7927PfrfbrZkzZyo6Olrh4eFKSkpScXFxACcGAADBJKhj58SJE+rfv78aN26st956S59//rkWLlyoq666ynPMggULtHTpUmVnZys/P18RERFKTk5WZWVlACcHAADBIjTQA/yQ+fPnKzY2VqtWrfKsxcfHe/7b7XZryZIlmjFjhu655x5J0ssvvyy73a4NGzZoxIgR9T4zAAAILkF9Zeff//63evXqpXvvvVdt27bVDTfcoJUrV3r2l5SUyOFwKCkpybNms9nUp08f5eXlXfa8VVVVcrlcXg8AAGCmoI6db775RitWrFCHDh30n//8R+PHj9djjz2m1atXS5IcDockyW63ez3Pbrd79l1KVlaWbDab5xEbG1t3vwQAAAiooI6dmpoa3XjjjXrmmWd0ww03KCUlRePGjVN2dnatzpuRkSGn0+l5lJaW+mliAAAQbII6dqKjo9W5c2evtU6dOungwYOSpKioKElSWVmZ1zFlZWWefZcSFhYmq9Xq9QAAAGYK6tjp37+/ioqKvNa+/PJLXXPNNZK+u1k5KipKubm5nv0ul0v5+flKTEys11kBAEBwCup3Y02aNEn9+vXTM888o/vuu08FBQV64YUX9MILL0iSLBaL0tLSNGfOHHXo0EHx8fHKzMxUTEyMhg4dGtjhAQBAUAjq2Lnpppu0fv16ZWRk6KmnnlJ8fLyWLFmikSNHeo6ZOnWqKioqlJKSovLycg0YMECbNm1S06ZNAzg5AAAIFha32+3+qU8qLS2VxWJRu3btJEkFBQVau3atOnfurJSUFL8PWddcLpdsNpucTqff79+Jm/6mX88HAEBDs3/e4Do575X+/fbpnp3f/e532rp1q6Tv3v59++23q6CgQE8++aSeeuop3yYGAACoAz7FzqeffqrevXtLkl599VV17dpVO3bs0Jo1a5STk+PP+QAAAGrFp9iprq5WWFiYJOntt9/Wr3/9a0lSQkKCDh8+7L/pAAAAasmn2OnSpYuys7P13nvvacuWLbrzzjslSYcOHVKrVq38OiAAAEBt+BQ78+fP1/PPP6+BAwfqgQceUPfu3SV9911WF17eAgAACAY+vfV84MCB+vbbb+VyuXTVVVd51lNSUhQREeG34QAAAGrLpys7gwYN0smTJ71CR5IiIyN1//33+2UwAAAAf/ApdrZt26azZ89etF5ZWan33nuv1kMBAAD4y096Gevjjz/2/Pfnn38uh8Ph2T5//rw2bdqkq6++2n/TAQAA1NJPip0ePXrIYrHIYrFo0KBBF+0PDw/XsmXL/DYcAABAbf2k2CkpKZHb7dYvfvELFRQUqE2bNp59TZo0Udu2bdWoUSO/DwkAAOCrnxQ711xzjSSppqamToYBAADwN5+/9by4uFhbt27VkSNHLoqfmTNn1nowAAAAf/ApdlauXKnx48erdevWioqKksVi8eyzWCzEDgAACBo+xc6cOXM0d+5cTZs2zd/zAAAA+JVPn7Nz4sQJ3Xvvvf6eBQAAwO98ip17771Xmzdv9vcsAAAAfufTy1i//OUvlZmZqZ07d6pbt25q3Lix1/7HHnvML8MBAADUlsXtdrt/6pPi4+Mvf0KLRd98802thqpvLpdLNptNTqdTVqvVr+eOm/6mX88HAEBDs3/e4Do575X+/fbpyk5JSYnPgwEAANQnn+7ZAQAAaCh8urIzZsyYH9z/0ksv+TQMAACAv/kUOydOnPDarq6u1qeffqry8vJLfkEoAABAoPgUO+vXr79oraamRuPHj9e1115b66EAAAD8xW/37ISEhCg9PV2LFy/21ykBAABqza83KH/99dc6d+6cP08JAABQKz69jJWenu617Xa7dfjwYb355psaNWqUXwYDAADwB59iZ8+ePV7bISEhatOmjRYuXPij79QCAACoTz7FztatW/09BwAAQJ3wKXYuOHr0qIqKiiRJHTt2VJs2bfwyFAAAgL/4dINyRUWFxowZo+joaN188826+eabFRMTo7Fjx+r06dP+nhEAAMBnPsVOenq6tm/fro0bN6q8vFzl5eV6/fXXtX37dk2ePNnfMwIAAPjMp5ex/vnPf+of//iHBg4c6Fm76667FB4ervvuu08rVqzw13wAAAC14tOVndOnT8tut1+03rZtW17GAgAAQcWn2ElMTNSsWbNUWVnpWTtz5oxmz56txMREvw0HAABQWz69jLVkyRLdeeedateunbp37y5J+uijjxQWFqbNmzf7dUAAAIDa8Cl2unXrpuLiYq1Zs0b79u2TJD3wwAMaOXKkwsPD/TogAABAbfgUO1lZWbLb7Ro3bpzX+ksvvaSjR49q2rRpfhkOAACgtny6Z+f5559XQkLCRetdunRRdnZ2rYcCAADwF59ix+FwKDo6+qL1Nm3a6PDhw7UeCgAAwF98ip3Y2Fh98MEHF61/8MEHiomJqfVQAAAA/uLTPTvjxo1TWlqaqqurNWjQIElSbm6upk6dyicoAwCAoOJT7EyZMkXHjh3To48+qrNnz0qSmjZtqmnTpikjI8OvAwIAANSGT7FjsVg0f/58ZWZm6osvvlB4eLg6dOigsLAwf88HAABQKz7FzgXNmzfXTTfd5K9ZAAAA/M6nG5QBAAAaCmIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYrUHFzrx582SxWJSWluZZq6ysVGpqqlq1aqXmzZtr+PDhKisrC9yQAAAgqDSY2Nm1a5eef/55XX/99V7rkyZN0saNG/Xaa69p+/btOnTokIYNGxagKQEAQLBpELFz6tQpjRw5UitXrtRVV13lWXc6nXrxxRe1aNEiDRo0SD179tSqVau0Y8cO7dy5M4ATAwCAYNEgYic1NVWDBw9WUlKS13phYaGqq6u91hMSEtS+fXvl5eVd9nxVVVVyuVxeDwAAYKbQQA/wY/7+97/rww8/1K5duy7a53A41KRJE7Vs2dJr3W63y+FwXPacWVlZmj17tr9HBQAAQSior+yUlpbq8ccf15o1a9S0aVO/nTcjI0NOp9PzKC0t9du5AQBAcAnq2CksLNSRI0d04403KjQ0VKGhodq+fbuWLl2q0NBQ2e12nT17VuXl5V7PKysrU1RU1GXPGxYWJqvV6vUAAABmCuqXsW677TZ98sknXmujR49WQkKCpk2bptjYWDVu3Fi5ubkaPny4JKmoqEgHDx5UYmJiIEYGAABBJqhjp0WLFuratavXWkREhFq1auVZHzt2rNLT0xUZGSmr1aqJEycqMTFRffv2DcTIAAAgyAR17FyJxYsXKyQkRMOHD1dVVZWSk5P13HPPBXosAAAQJCxut9sd6CECzeVyyWazyel0+v3+nbjpb/r1fAAANDT75w2uk/Ne6d/voL5BGQAAoLaIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYL6tjJysrSTTfdpBYtWqht27YaOnSoioqKvI6prKxUamqqWrVqpebNm2v48OEqKysL0MQAACDYBHXsbN++Xampqdq5c6e2bNmi6upq3XHHHaqoqPAcM2nSJG3cuFGvvfaatm/frkOHDmnYsGEBnBoAAAST0EAP8EM2bdrktZ2Tk6O2bduqsLBQN998s5xOp1588UWtXbtWgwYNkiStWrVKnTp10s6dO9W3b99LnreqqkpVVVWebZfLVXe/BAAACKigvrLzv5xOpyQpMjJSklRYWKjq6molJSV5jklISFD79u2Vl5d32fNkZWXJZrN5HrGxsXU7OAAACJgGEzs1NTVKS0tT//791bVrV0mSw+FQkyZN1LJlS69j7Xa7HA7HZc+VkZEhp9PpeZSWltbl6AAAIICC+mWs70tNTdWnn36q999/v9bnCgsLU1hYmB+mAgAAwa5BXNmZMGGC3njjDW3dulXt2rXzrEdFRens2bMqLy/3Or6srExRUVH1PCUAAAhGQR07brdbEyZM0Pr16/XOO+8oPj7ea3/Pnj3VuHFj5ebmetaKiop08OBBJSYm1ve4AAAgCAX1y1ipqalau3atXn/9dbVo0cJzH47NZlN4eLhsNpvGjh2r9PR0RUZGymq1auLEiUpMTLzsO7EAAMDPS1DHzooVKyRJAwcO9FpftWqVHnroIUnS4sWLFRISouHDh6uqqkrJycl67rnn6nlSAAAQrII6dtxu948e07RpUy1fvlzLly+vh4kAAEBDE9T37AAAANQWsQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjGRM7y5cvV1xcnJo2bao+ffqooKAg0CMBAIAgYETsvPLKK0pPT9esWbP04Ycfqnv37kpOTtaRI0cCPRoAAAgwI2Jn0aJFGjdunEaPHq3OnTsrOztbzZo100svvRTo0QAAQICFBnqA2jp79qwKCwuVkZHhWQsJCVFSUpLy8vIu+ZyqqipVVVV5tp1OpyTJ5XL5fb6aqtN+PycAAA1JXfx9/f553W73Dx7X4GPn22+/1fnz52W3273W7Xa79u3bd8nnZGVlafbs2Retx8bG1smMAAD8nNmW1O35T548KZvNdtn9DT52fJGRkaH09HTPdk1NjY4fP65WrVrJYrEEcDIA/uZyuRQbG6vS0lJZrdZAjwPAj9xut06ePKmYmJgfPK7Bx07r1q3VqFEjlZWVea2XlZUpKirqks8JCwtTWFiY11rLli3rakQAQcBqtRI7gIF+6IrOBQ3+BuUmTZqoZ8+eys3N9azV1NQoNzdXiYmJAZwMAAAEgwZ/ZUeS0tPTNWrUKPXq1Uu9e/fWkiVLVFFRodGjRwd6NAAAEGBGxM7999+vo0ePaubMmXI4HOrRo4c2bdp00U3LAH5+wsLCNGvWrIteugbw82Fx/9j7tQAAABqwBn/PDgAAwA8hdgAAgNGIHQAAYDRiBwAAGI3YARD03n33XQ0ZMkQxMTGyWCzasGGD1/5//etfuuOOOzyfgr53796LzlFZWanU1FS1atVKzZs31/Dhwy/6MNKDBw9q8ODBatasmdq2baspU6bo3LlzdfibAagPxA6AoFdRUaHu3btr+fLll90/YMAAzZ8//7LnmDRpkjZu3KjXXntN27dv16FDhzRs2DDP/vPnz2vw4ME6e/asduzYodWrVysnJ0czZ870++8DoH7x1nMADYrFYtH69es1dOjQi/bt379f8fHx2rNnj3r06OFZdzqdatOmjdauXavf/va3kqR9+/apU6dOysvLU9++ffXWW2/p7rvv1qFDhzyf0ZWdna1p06bp6NGjatKkSX38egDqAFd2ABivsLBQ1dXVSkpK8qwlJCSoffv2ysvLkyTl5eWpW7duXh9GmpycLJfLpc8++6zeZwbgP8QOAOM5HA41adLkoi/8tdvtcjgcnmP+91PXL2xfOAZAw0TsAAAAoxE7AIwXFRWls2fPqry83Gu9rKxMUVFRnmP+991ZF7YvHAOgYSJ2ABivZ8+eaty4sXJzcz1rRUVFOnjwoBITEyVJiYmJ+uSTT3TkyBHPMVu2bJHValXnzp3rfWYA/mPEt54DMNupU6f01VdfebZLSkq0d+9eRUZGqn379jp+/LgOHjyoQ4cOSfouZKTvrshERUXJZrNp7NixSk9PV2RkpKxWqyZOnKjExET17dtXknTHHXeoc+fO+sMf/qAFCxbI4XBoxowZSk1N5RvTgQaOt54DCHrbtm3TrbfeetH6qFGjlJOTo5ycHI0ePfqi/bNmzdKf/vQnSd99qODkyZO1bt06VVVVKTk5Wc8995zXS1QHDhzQ+PHjtW3bNkVERGjUqFGaN2+eQkP5/0KgISN2AACA0bhnBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgdAnRs4cKDS0tIuuz8uLk5Lliypl1n2798vi8WivXv31svPAxB4xA6AgNu1a5dSUlI82xaLRRs2bLji5+fk5Khly5ZXdGxsbKwOHz6srl27/sQpATRUfOELgIBr06ZNvfycs2fPqkmTJl7fhwXAfFzZAVAvzp07pwkTJshms6l169bKzMzUha/m+/7LWHFxcZKk3/zmN7JYLJ7tjz76SLfeeqtatGghq9Wqnj17avfu3dq2bZtGjx4tp9Mpi8Uii8Xi+fLPuLg4Pf3003rwwQdltVqVkpJy0ctY27Ztk8ViUW5urnr16qVmzZqpX79+nm9Ov2DOnDlq27atWrRooYcffljTp09Xjx49PPu3bdum3r17KyIiQi1btlT//v114MCBuvrnBPATEDsA6sXq1asVGhqqgoICPfvss1q0aJH++te/XnTcrl27JEmrVq3S4cOHPdsjR45Uu3bttGvXLhUWFmr69Olq3Lix+vXrpyVLlshqterw4cM6fPiwnnjiCc/5/vznP6t79+7as2ePMjMzLzvfk08+qYULF2r37t0KDQ3VmDFjPPvWrFmjuXPnav78+SosLFT79u21YsUKz/5z585p6NChuuWWW/Txxx8rLy9PKSkpslgstf53A1B7vIwFoF7ExsZq8eLFslgs6tixoz755BMtXrxY48aN8zruwktaLVu29Hq56eDBg5oyZYoSEhIkSR06dPDss9lsslgsl3x5atCgQZo8ebJne//+/Zecb+7cubrlllskSdOnT9fgwYNVWVmppk2batmyZRo7dqxGjx4tSZo5c6Y2b96sU6dOSZJcLpecTqfuvvtuXXvttZKkTp06/aR/HwB1hys7AOpF3759va50JCYmqri4WOfPn7+i56enp+vhhx9WUlKS5s2bp6+//vqKnterV68rOu7666/3/Hd0dLQk6ciRI5KkoqIi9e7d2+v4729HRkbqoYceUnJysoYMGaJnn31Whw8fvqKfC6DuETsAGoQ//elP+uyzzzR48GC988476ty5s9avX/+jz4uIiLii8zdu3Njz3xeirKam5ornW7VqlfLy8tSvXz+98soruu6667Rz584rfj6AukPsAKgX+fn5Xts7d+5Uhw4d1KhRo4uObdy48SWv+Fx33XWaNGmSNm/erGHDhmnVqlWSpCZNmlzxFSJfdOzY0XPv0AX/uy1JN9xwgzIyMrRjxw517dpVa9eurbOZAFw5YgdAvTh48KDS09NVVFSkdevWadmyZXr88ccveWxcXJxyc3PlcDh04sQJnTlzRhMmTNC2bdt04MABffDBB9q1a5fnvpi4uDidOnVKubm5+vbbb3X69Gm/zj5x4kS9+OKLWr16tYqLizVnzhx9/PHHnitAJSUlysjIUF5eng4cOKDNmzeruLiY+3aAIMENygDqxYMPPqgzZ86od+/eatSokR5//HGvDxL8voULFyo9PV0rV67U1VdfrS+//FLHjh3Tgw8+qLKyMrVu3VrDhg3T7NmzJUn9+vXTH//4R91///06duyYZs2a5Xn7uT+MHDlS33zzjZ544glVVlbqvvvu00MPPaSCggJJUrNmzbRv3z6tXr1ax44dU3R0tFJTU/XII4/4bQYAvrO4L3zQBQDgit1+++2KiorS3/72t0CPAuBHcGUHAH7E6dOnlZ2dreTkZDVq1Ejr1q3T22+/rS1btgR6NABXgCs7APAjzpw5oyFDhmjPnj2qrKxUx44dNWPGDA0bNizQowG4AsQOAAAwGu/GAgAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABjt/wFgOMP0atPibwAAAABJRU5ErkJggg==", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGwCAYAAABPSaTdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAiuUlEQVR4nO3df1RUdf7H8dcNdQIXxvzBjCQF7lJmmLaaJtVKpliZres5ZemW5s+WtIhM5WslehTSjsqunOzHburWUu3ZzWpPrQtZUoYmkmRaaT9QaYUoxQEVwR/3+0fHOc6iZcPADJ+ej3PmHO/n3rm8x394njt3GMu2bVsAAACGOi/YAwAAADQnYgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARmsT7AFCwcmTJ7Vv3z5FRkbKsqxgjwMAAM6Bbduqra1VTEyMzjvv7NdviB1J+/btU2xsbLDHAAAAfigvL1e3bt3Oup/YkRQZGSnp+/+sqKioIE8DAADORU1NjWJjY72/x8+G2JG8b11FRUUROwAAtDI/dgsKNygDAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwWlBj591339WIESMUExMjy7L06quv+uy3bVuZmZmKiYlReHi4kpOTtWPHDp9j6uvrNX36dHXu3Fnt27fXrbfeqq+//roFXwUAAAhlQY2dw4cPq3fv3srNzT3j/sWLF2vp0qXKzc1VcXGx3G63hg4dqtraWu8xaWlpWrNmjV566SVt2LBBhw4d0i233KITJ0601MsAAAAhzLJt2w72ENL331i6Zs0ajRw5UtL3V3ViYmKUlpamWbNmSfr+Ko7L5dKiRYs0depUeTwedenSRc8//7xGjx4tSdq3b59iY2P15ptvatiwYef0s2tqauR0OuXxePjWcwAAWolz/f0dsvfslJWVqbKyUikpKd41h8OhQYMGqaioSJJUUlKiY8eO+RwTExOjxMRE7zFnUl9fr5qaGp8HAAAwU5tgD3A2lZWVkiSXy+Wz7nK5tGfPHu8x7dq10wUXXNDomFPPP5Ps7GzNmzcvwBOfWdzsN1rk5wAAEKp2Pz48qD8/ZK/snGJZls+2bduN1v7Xjx2TkZEhj8fjfZSXlwdkVgAAEHpCNnbcbrckNbpCU1VV5b3a43a71dDQoOrq6rMecyYOh0NRUVE+DwAAYKaQjZ34+Hi53W4VFBR41xoaGlRYWKikpCRJUt++fdW2bVufYyoqKrR9+3bvMQAA4OctqPfsHDp0SF988YV3u6ysTKWlperYsaMuuugipaWlKSsrSwkJCUpISFBWVpYiIiI0ZswYSZLT6dTEiRP10EMPqVOnTurYsaNmzJihXr16aciQIcF6WQAAIIQENXa2bNmi66+/3rudnp4uSRo3bpxWrVqlmTNnqq6uTqmpqaqurtaAAQOUn5+vyMhI73OWLVumNm3a6Pbbb1ddXZ1uuOEGrVq1SmFhYS3+egAAQOgJmb+zE0zN+Xd2+DQWAODnrrk+jdXq/84OAABAIBA7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjBbSsXP8+HE98sgjio+PV3h4uLp376758+fr5MmT3mNs21ZmZqZiYmIUHh6u5ORk7dixI4hTAwCAUBLSsbNo0SI99dRTys3N1aeffqrFixfriSee0PLly73HLF68WEuXLlVubq6Ki4vldrs1dOhQ1dbWBnFyAAAQKtoEe4AfsnHjRv32t7/V8OHDJUlxcXF68cUXtWXLFknfX9XJycnRnDlzNGrUKEnS6tWr5XK5lJeXp6lTp57xvPX19aqvr/du19TUNPMrAQAAwRLSV3auvfZarVu3Trt27ZIkffTRR9qwYYNuvvlmSVJZWZkqKyuVkpLifY7D4dCgQYNUVFR01vNmZ2fL6XR6H7Gxsc37QgAAQNCE9JWdWbNmyePxqEePHgoLC9OJEye0cOFC3XnnnZKkyspKSZLL5fJ5nsvl0p49e8563oyMDKWnp3u3a2pqCB4AAAwV0rHz8ssv64UXXlBeXp4uv/xylZaWKi0tTTExMRo3bpz3OMuyfJ5n23ajtdM5HA45HI5mmxsAAISOkI6dhx9+WLNnz9Ydd9whSerVq5f27Nmj7OxsjRs3Tm63W9L3V3i6du3qfV5VVVWjqz0AAODnKaTv2Tly5IjOO893xLCwMO9Hz+Pj4+V2u1VQUODd39DQoMLCQiUlJbXorAAAIDSF9JWdESNGaOHChbrooot0+eWXa+vWrVq6dKkmTJgg6fu3r9LS0pSVlaWEhAQlJCQoKytLERERGjNmTJCnBwAAoSCkY2f58uV69NFHlZqaqqqqKsXExGjq1Kl67LHHvMfMnDlTdXV1Sk1NVXV1tQYMGKD8/HxFRkYGcXIAABAqLNu27WAPEWw1NTVyOp3yeDyKiooK6LnjZr8R0PMBANDa7H58eLOc91x/f4f0PTsAAABNRewAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaCEfO//973/1+9//Xp06dVJERIT69OmjkpIS737btpWZmamYmBiFh4crOTlZO3bsCOLEAAAglIR07FRXV+uaa65R27Zt9e9//1uffPKJlixZog4dOniPWbx4sZYuXarc3FwVFxfL7XZr6NChqq2tDd7gAAAgZLQJ9gA/ZNGiRYqNjdXKlSu9a3Fxcd5/27atnJwczZkzR6NGjZIkrV69Wi6XS3l5eZo6dWpLjwwAAEJMSF/Zef3119WvXz/ddtttio6O1pVXXqlnn33Wu7+srEyVlZVKSUnxrjkcDg0aNEhFRUVnPW99fb1qamp8HgAAwEwhHTtfffWVVqxYoYSEBP3nP//Rvffeq/vvv19//etfJUmVlZWSJJfL5fM8l8vl3Xcm2dnZcjqd3kdsbGzzvQgAABBUIR07J0+e1K9//WtlZWXpyiuv1NSpUzV58mStWLHC5zjLsny2bdtutHa6jIwMeTwe76O8vLxZ5gcAAMEX0rHTtWtX9ezZ02ftsssu0969eyVJbrdbkhpdxamqqmp0ted0DodDUVFRPg8AAGCmkI6da665Rjt37vRZ27Vrly6++GJJUnx8vNxutwoKCrz7GxoaVFhYqKSkpBadFQAAhKaQ/jTWgw8+qKSkJGVlZen222/X5s2b9cwzz+iZZ56R9P3bV2lpacrKylJCQoISEhKUlZWliIgIjRkzJsjTAwCAUBDSsXPVVVdpzZo1ysjI0Pz58xUfH6+cnByNHTvWe8zMmTNVV1en1NRUVVdXa8CAAcrPz1dkZGQQJwcAAKHCsm3b/qlPKi8vl2VZ6tatmyRp8+bNysvLU8+ePTVlypSAD9ncampq5HQ65fF4An7/TtzsNwJ6PgAAWpvdjw9vlvOe6+9vv+7ZGTNmjN555x1J398cPHToUG3evFn/93//p/nz5/s3MQAAQDPwK3a2b9+u/v37S5L+/ve/KzExUUVFRcrLy9OqVasCOR8AAECT+BU7x44dk8PhkCS99dZbuvXWWyVJPXr0UEVFReCmAwAAaCK/Yufyyy/XU089pffee08FBQW68cYbJUn79u1Tp06dAjogAABAU/gVO4sWLdLTTz+t5ORk3Xnnnerdu7ek77/L6tTbWwAAAKHAr4+eJycn67vvvlNNTY0uuOAC7/qUKVPUvn37gA0HAADQVH5d2Rk8eLBqa2t9QkeSOnbsqNGjRwdkMAAAgEDwK3bWr1+vhoaGRutHjx7Ve++91+ShAAAAAuUnvY21bds2778/+eQTny/gPHHihNauXasLL7wwcNMBAAA00U+KnT59+siyLFmWpcGDBzfaHx4eruXLlwdsOAAAgKb6SbFTVlYm27bVvXt3bd68WV26dPHua9eunaKjoxUWFhbwIQEAAPz1k2Ln4osvliSdPHmyWYYBAAAINL+/9XzXrl1av369qqqqGsXPY4891uTBAAAAAsGv2Hn22Wf1hz/8QZ07d5bb7ZZlWd59lmUROwAAIGT4FTsLFizQwoULNWvWrEDPAwAAEFB+/Z2d6upq3XbbbYGeBQAAIOD8ip3bbrtN+fn5gZ4FAAAg4Px6G+tXv/qVHn30UW3atEm9evVS27Ztffbff//9ARkOAACgqSzbtu2f+qT4+Pizn9Cy9NVXXzVpqJZWU1Mjp9Mpj8ejqKiogJ47bvYbAT0fAACtze7HhzfLec/197dfV3bKysr8HgwAAKAl+XXPDgAAQGvh15WdCRMm/OD+5557zq9hAAAAAs2v2KmurvbZPnbsmLZv366DBw+e8QtCAQAAgsWv2FmzZk2jtZMnTyo1NVXdu3dv8lAAAACBErB7ds477zw9+OCDWrZsWaBOCQAA0GQBvUH5yy+/1PHjxwN5SgAAgCbx622s9PR0n23btlVRUaE33nhD48aNC8hgAAAAgeBX7GzdutVn+7zzzlOXLl20ZMmSH/2kFgAAQEvyK3beeeedQM8BAADQLPyKnVO+/fZb7dy5U5Zl6ZJLLlGXLl0CNRcAAEBA+HWD8uHDhzVhwgR17dpVv/nNb3TdddcpJiZGEydO1JEjRwI9IwAAgN/8ip309HQVFhbqX//6lw4ePKiDBw/qtddeU2FhoR566KFAzwgAAOA3v97G+uc//6l//OMfSk5O9q7dfPPNCg8P1+23364VK1YEaj4AAIAm8evKzpEjR+RyuRqtR0dH8zYWAAAIKX7FzsCBAzV37lwdPXrUu1ZXV6d58+Zp4MCBARsOAACgqfx6GysnJ0c33XSTunXrpt69e8uyLJWWlsrhcCg/Pz/QMwIAAPjNr9jp1auXPv/8c73wwgv67LPPZNu27rjjDo0dO1bh4eGBnhEAAMBvfsVOdna2XC6XJk+e7LP+3HPP6dtvv9WsWbMCMhwAAEBT+XXPztNPP60ePXo0Wr/88sv11FNPNXkoAACAQPErdiorK9W1a9dG6126dFFFRUWThwIAAAgUv2InNjZW77//fqP1999/XzExMU0eCgAAIFD8umdn0qRJSktL07FjxzR48GBJ0rp16zRz5kz+gjIAAAgpfsXOzJkzdeDAAaWmpqqhoUGSdP7552vWrFnKyMgI6IAAAABN4VfsWJalRYsW6dFHH9Wnn36q8PBwJSQkyOFwBHo+AACAJvErdk75xS9+oauuuipQswAAAAScXzcoAwAAtBbEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMFqrip3s7GxZlqW0tDTvmm3byszMVExMjMLDw5WcnKwdO3YEb0gAABBSWk3sFBcX65lnntEVV1zhs7548WItXbpUubm5Ki4ultvt1tChQ1VbWxukSQEAQChpFbFz6NAhjR07Vs8++6wuuOAC77pt28rJydGcOXM0atQoJSYmavXq1Tpy5Ijy8vKCODEAAAgVrSJ27rvvPg0fPlxDhgzxWS8rK1NlZaVSUlK8aw6HQ4MGDVJRUdFZz1dfX6+amhqfBwAAMFObYA/wY1566SV9+OGHKi4ubrSvsrJSkuRyuXzWXS6X9uzZc9ZzZmdna968eYEdFAAAhKSQvrJTXl6uBx54QC+88ILOP//8sx5nWZbPtm3bjdZOl5GRIY/H432Ul5cHbGYAABBaQvrKTklJiaqqqtS3b1/v2okTJ/Tuu+8qNzdXO3fulPT9FZ6uXbt6j6mqqmp0ted0DodDDoej+QYHAAAhI6Sv7Nxwww36+OOPVVpa6n3069dPY8eOVWlpqbp37y63262CggLvcxoaGlRYWKikpKQgTg4AAEJFSF/ZiYyMVGJios9a+/bt1alTJ+96WlqasrKylJCQoISEBGVlZSkiIkJjxowJxsgAACDEhHTsnIuZM2eqrq5Oqampqq6u1oABA5Sfn6/IyMhgjwYAAEKAZdu2Hewhgq2mpkZOp1Mej0dRUVEBPXfc7DcCej4AAFqb3Y8Pb5bznuvv75C+ZwcAAKCpiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGC+nYyc7O1lVXXaXIyEhFR0dr5MiR2rlzp88xtm0rMzNTMTExCg8PV3Jysnbs2BGkiQEAQKgJ6dgpLCzUfffdp02bNqmgoEDHjx9XSkqKDh8+7D1m8eLFWrp0qXJzc1VcXCy3262hQ4eqtrY2iJMDAIBQ0SbYA/yQtWvX+myvXLlS0dHRKikp0W9+8xvZtq2cnBzNmTNHo0aNkiStXr1aLpdLeXl5mjp16hnPW19fr/r6eu92TU1N870IAAAQVCF9Zed/eTweSVLHjh0lSWVlZaqsrFRKSor3GIfDoUGDBqmoqOis58nOzpbT6fQ+YmNjm3dwAAAQNK0mdmzbVnp6uq699lolJiZKkiorKyVJLpfL51iXy+XddyYZGRnyeDzeR3l5efMNDgAAgiqk38Y63bRp07Rt2zZt2LCh0T7Lsny2bdtutHY6h8Mhh8MR8BkBAEDoaRVXdqZPn67XX39d77zzjrp16+Zdd7vdktToKk5VVVWjqz0AAODnKaRjx7ZtTZs2Ta+88orefvttxcfH++yPj4+X2+1WQUGBd62hoUGFhYVKSkpq6XEBAEAICum3se677z7l5eXptddeU2RkpPcKjtPpVHh4uCzLUlpamrKyspSQkKCEhARlZWUpIiJCY8aMCfL0AAAgFIR07KxYsUKSlJyc7LO+cuVKjR8/XpI0c+ZM1dXVKTU1VdXV1RowYIDy8/MVGRnZwtMCAIBQFNKxY9v2jx5jWZYyMzOVmZnZ/AMBAIBWJ6Tv2QEAAGgqYgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGMyZ2nnzyScXHx+v8889X37599d577wV7JAAAEAKMiJ2XX35ZaWlpmjNnjrZu3arrrrtON910k/bu3Rvs0QAAQJAZETtLly7VxIkTNWnSJF122WXKyclRbGysVqxYEezRAABAkLUJ9gBN1dDQoJKSEs2ePdtnPSUlRUVFRWd8Tn19verr673bHo9HklRTUxPw+U7WHwn4OQEAaE2a4/fr6ee1bfsHj2v1sfPdd9/pxIkTcrlcPusul0uVlZVnfE52drbmzZvXaD02NrZZZgQA4OfMmdO856+trZXT6Tzr/lYfO6dYluWzbdt2o7VTMjIylJ6e7t0+efKkDhw4oE6dOp31OQBap5qaGsXGxqq8vFxRUVHBHgdAANm2rdraWsXExPzgca0+djp37qywsLBGV3GqqqoaXe05xeFwyOFw+Kx16NChuUYEEAKioqKIHcBAP3RF55RWf4Nyu3bt1LdvXxUUFPisFxQUKCkpKUhTAQCAUNHqr+xIUnp6uu666y7169dPAwcO1DPPPKO9e/fq3nvvDfZoAAAgyIyIndGjR2v//v2aP3++KioqlJiYqDfffFMXX3xxsEcDEGQOh0Nz585t9NY1gJ8Py/6xz2sBAAC0Yq3+nh0AAIAfQuwAAACjETsAAMBoxA4AADAasQMg5L377rsaMWKEYmJiZFmWXn31VZ/9r7zyioYNG6bOnTvLsiyVlpY2Okd9fb2mT5+uzp07q3379rr11lv19ddf+xxTXV2tu+66S06nU06nU3fddZcOHjzYfC8MQIsgdgCEvMOHD6t3797Kzc096/5rrrlGjz/++FnPkZaWpjVr1uill17Shg0bdOjQId1yyy06ceKE95gxY8aotLRUa9eu1dq1a1VaWqq77ror4K8HQMvio+cAWhXLsrRmzRqNHDmy0b7du3crPj5eW7duVZ8+fbzrHo9HXbp00fPPP6/Ro0dLkvbt26fY2Fi9+eabGjZsmD799FP17NlTmzZt0oABAyRJmzZt0sCBA/XZZ5/p0ksvbYmXB6AZcGUHgPFKSkp07NgxpaSkeNdiYmKUmJiooqIiSdLGjRvldDq9oSNJV199tZxOp/cYAK0TsQPAeJWVlWrXrp0uuOACn3WXy+X9EuHKykpFR0c3em50dHSjLxoG0LoQOwB+tmzblmVZ3u3T/322YwC0PsQOAOO53W41NDSourraZ72qqkoul8t7zDfffNPoud9++633GACtE7EDwHh9+/ZV27ZtVVBQ4F2rqKjQ9u3blZSUJEkaOHCgPB6PNm/e7D3mgw8+kMfj8R4DoHUy4lvPAZjt0KFD+uKLL7zbZWVlKi0tVceOHXXRRRfpwIED2rt3r/bt2ydJ2rlzp6Tvr9a43W45nU5NnDhRDz30kDp16qSOHTtqxowZ6tWrl4YMGSJJuuyyy3TjjTdq8uTJevrppyVJU6ZM0S233MInsYBWjo+eAwh569ev1/XXX99ofdy4cVq1apVWrVqle+65p9H+uXPnKjMzU5J09OhRPfzww8rLy1NdXZ1uuOEGPfnkk4qNjfUef+DAAd1///16/fXXJUm33nqrcnNz1aFDh2Z5XQBaBrEDAACMxj07AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwBaRHJystLS0s66Py4uTjk5OS0yy+7du2VZlkpLS1vk5wEILmIHQEgoLi7WlClTvNuWZenVV1895+evWrXqnL/WITY2VhUVFUpMTPyJUwJojfgiUAAhoUuXLi3ycxoaGtSuXTu53e4W+XkAgo8rOwBazPHjxzVt2jR16NBBnTp10iOPPKJTX893+ttYcXFxkqTf/e53sizLu/3RRx/p+uuvV2RkpKKiotS3b19t2bJF69ev1z333COPxyPLsmRZlvcLQOPi4rRgwQKNHz9eTqdTkydPbvQ21vr162VZltatW6d+/fopIiJCSUlJ3m9PP2XBggWKjo5WZGSkJk2apNmzZ6tPnz7e/evXr1f//v3Vvn17dejQQddcc4327NnTXP+dAM4RsQOgxaxevVpt2rTRBx98oD/96U9atmyZ/vznPzc6rri4WJK0cuVKVVRUeLfHjh2rbt26qbi4WCUlJZo9e7batm2rpKQk5eTkKCoqShUVFaqoqNCMGTO853viiSeUmJiokpISPfroo2edb86cOVqyZIm2bNmiNm3aaMKECd59f/vb37Rw4UItWrRIJSUluuiii7RixQrv/uPHj2vkyJEaNGiQtm3bpo0bN2rKlCmyLKvJ/28Amoa3sQC0mNjYWC1btkyWZenSSy/Vxx9/rGXLlmny5Mk+x516S6tDhw4+bzft3btXDz/8sHr06CFJSkhI8O5zOp2yLOuMb08NHjzYJ3527959xvkWLlyoQYMGSZJmz56t4cOH6+jRozr//PO1fPlyTZw4Uffcc48k6bHHHlN+fr4OHTokSaqpqZHH49Ett9yiX/7yl5Kkyy677Cf9/wBoHlzZAdBirr76ap8rHQMHDtTnn3+uEydOnNPz09PTNWnSJA0ZMkSPP/64vvzyy3N6Xr9+/c7puCuuuML7765du0qSqqqqJEk7d+5U//79fY4/fbtjx44aP368hg0bphEjRuiPf/yjKioqzunnAmhexA6AViMzM1M7duzQ8OHD9fbbb6tnz55as2bNjz6vffv253T+tm3bev99KspOnjzZaO2UU/cbnbJy5Upt3LhRSUlJevnll3XJJZdo06ZN5/SzATQfYgdAi/nfX/ybNm1SQkKCwsLCGh3btm3bM17xueSSS/Tggw8qPz9fo0aN0sqVKyVJ7dq1O+crRP649NJLtXnzZp+1LVu2NDruyiuvVEZGhoqKipSYmKi8vLxmmwnAuSF2ALSY8vJypaena+fOnXrxxRe1fPlyPfDAA2c8Ni4uTuvWrVNlZaWqq6tVV1enadOmaf369dqzZ4/ef/99FRcXe++LiYuL06FDh7Ru3Tp99913OnLkSEBnnz59uv7yl79o9erV+vzzz7VgwQJt27bNe7WnrKxMGRkZ2rhxo/bs2aP8/Hzt2rWL+3aAEMANygBazN133626ujr1799fYWFhmj59us8fEjzdkiVLlJ6ermeffVYXXnihdu3apf379+vuu+/WN998o86dO2vUqFGaN2+eJCkpKUn33nuvRo8erf3792vu3Lnej58HwtixY/XVV19pxowZOnr0qG6//XaNHz/ee7UnIiJCn332mVavXq39+/era9eumjZtmqZOnRqwGQD4x7L/901nAMA5GTp0qNxut55//vlgjwLgB3BlBwDOwZEjR/TUU09p2LBhCgsL04svvqi33npLBQUFwR4NwI/gyg4AnIO6ujqNGDFCH374oerr63XppZfqkUce0ahRo4I9GoAfQewAAACj8WksAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNH+H35v7XyPK5xOAAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -213,7 +213,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.9.18" } }, "nbformat": 4, diff --git a/examples/autoqasm/3_2_magic_state_distillation.ipynb b/examples/autoqasm/3_2_magic_state_distillation.ipynb index 01c932cf..139b1616 100644 --- a/examples/autoqasm/3_2_magic_state_distillation.ipynb +++ b/examples/autoqasm/3_2_magic_state_distillation.ipynb @@ -69,7 +69,7 @@ "outputs": [], "source": [ "@aq.subroutine\n", - "def physical_magic_state_a_type(q: int) -> None:\n", + "def physical_magic_state_a_type(q: int):\n", " ins.h(q)\n", " ins.rz(q, np.pi/6)\n", "\n", @@ -165,7 +165,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "measurement counts: Counter({'00': 58, '01': 36, '11': 6})\n", + "measurement counts: Counter({'00': 50, '01': 37, '11': 13})\n", "Z expectation value: 1.0\n" ] } @@ -210,7 +210,7 @@ "outputs": [], "source": [ "@aq.subroutine\n", - "def physical_magic_state_t_type(q: int) -> None:\n", + "def physical_magic_state_t_type(q: int):\n", " ins.ry(q, np.arccos(1/np.sqrt(3)))\n", " ins.rz(q, np.pi/4)" ] @@ -231,7 +231,7 @@ "outputs": [], "source": [ "@aq.subroutine\n", - "def decoder(q0:int, q1:int, q2:int, q3:int, q4:int):\n", + "def decoder(q0: int, q1: int, q2: int, q3: int, q4: int):\n", " ins.cnot(q1, q0)\n", " ins.cz(q1, q0)\n", " ins.cz(q1, q2)\n", @@ -273,21 +273,19 @@ "source": [ "@aq.main(num_qubits=5)\n", "def distillation():\n", - " qubits = range(5)\n", - "\n", " # state preparation\n", - " for q in aq.ArrayVar(qubits, dimensions=[5]):\n", + " for q in aq.qubits:\n", " physical_magic_state_t_type(q)\n", "\n", " # decoding\n", - " decoder(*qubits)\n", + " decoder(*aq.qubits)\n", "\n", " # final rotation\n", - " ins.h(qubits[0])\n", - " ins.y(qubits[0])\n", + " ins.h(0)\n", + " ins.y(0)\n", "\n", " # measure ancilla\n", - " c = ins.measure(qubits[1:5])" + " c = ins.measure([1, 2, 3, 4])" ] }, { @@ -308,7 +306,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "measurement counts: Counter({'0000': 166, '0111': 66, '1110': 66, '0101': 63, '0001': 61, '0100': 58, '1000': 57, '1101': 56, '1010': 56, '1111': 56, '0110': 54, '0011': 54, '0010': 53, '1001': 48, '1011': 47, '1100': 39})\n" + "measurement counts: Counter({'0000': 178, '1011': 74, '1100': 70, '0111': 65, '0011': 63, '0110': 61, '0100': 58, '1000': 58, '1001': 57, '0001': 52, '0010': 52, '0101': 51, '1101': 46, '1111': 43, '1110': 38, '1010': 34})\n" ] } ], @@ -337,12 +335,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "success ratio: 0.166\n" + "success ratio: 0.178\n" ] } ], "source": [ - "success_count = len([x for x in result.measurements[\"c\"] if x==\"0000\"])\n", + "success_count = len([x for x in result.measurements[\"c\"] if x == \"0000\"])\n", "print(\"success ratio: \", success_count/n_shots)" ] }, @@ -387,31 +385,28 @@ "source": [ "@aq.main(num_qubits=5)\n", "def distillation_rus():\n", - " qubits = range(5)\n", - " aq_qubits = aq.ArrayVar(qubits, dimensions=[len(qubits)])\n", - "\n", " # RUS: repeat until measuring all-zero in ancilla\n", " c1 = aq.BoolVar(True)\n", " while c1:\n", " # state preparation\n", - " for q in aq_qubits:\n", + " for q in aq.qubits:\n", " ins.reset(q)\n", " physical_magic_state_t_type(q)\n", "\n", " # decoding\n", - " decoder(*qubits)\n", + " decoder(*aq.qubits)\n", "\n", " # measure ancilla\n", - " c = ins.measure(qubits[1:5])\n", + " c = ins.measure([1, 2, 3, 4])\n", " c1 = c[0] or c[1] or c[2] or c[3]\n", "\n", " # final rotation\n", - " ins.h(qubits[0])\n", - " ins.y(qubits[0])\n", + " ins.h(0)\n", + " ins.y(0)\n", "\n", " # measuring in the basis of magic state\n", - " basis_rotation_t_type(qubits[0])\n", - " c2 = ins.measure(qubits[0])" + " basis_rotation_t_type(0)\n", + " c2 = ins.measure(0)" ] }, { @@ -484,7 +479,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.9.18" } }, "nbformat": 4, diff --git a/src/braket/experimental/autoqasm/__init__.py b/src/braket/experimental/autoqasm/__init__.py index 976ac00f..e41ab18c 100644 --- a/src/braket/experimental/autoqasm/__init__.py +++ b/src/braket/experimental/autoqasm/__init__.py @@ -39,10 +39,16 @@ def my_program(): result[0] = measure __qubits__[0]; result[1] = measure __qubits__[1]; """ -from . import errors, operators # noqa: F401 +from . import errors, instructions, operators # noqa: F401 from .api import gate, gate_calibration, main, subroutine # noqa: F401 from .instructions import QubitIdentifierType as Qubit # noqa: F401 from .program import Program, build_program, verbatim # noqa: F401 from .transpiler import transpiler # noqa: F401 from .types import ArrayVar, BitVar, BoolVar, FloatVar, IntVar # noqa: F401 from .types import Range as range # noqa: F401 + + +def __getattr__(name): + if name == "qubits": + return instructions.global_qubit_register() + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 829d7d6b..25cabc1b 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -27,7 +27,6 @@ from malt.core import converter from malt.impl.api import autograph_artifact, is_autograph_artifact -import braket.experimental.autoqasm.constants as aq_constants import braket.experimental.autoqasm.instructions as aq_instructions import braket.experimental.autoqasm.program as aq_program import braket.experimental.autoqasm.transpiler as aq_transpiler @@ -291,13 +290,7 @@ def _add_qubit_declaration(program_conversion_context: aq_program.ProgramConvers ) # Declare the global qubit register - root_oqpy_program = program_conversion_context.get_oqpy_program( - scope=aq_program.ProgramScope.MAIN - ) - root_oqpy_program.declare( - [oqpy.Qubit(aq_constants.QUBIT_REGISTER, num_qubits)], - to_beginning=True, - ) + program_conversion_context.declare_global_qubit_register(num_qubits) def _convert_subroutine( diff --git a/src/braket/experimental/autoqasm/instructions/__init__.py b/src/braket/experimental/autoqasm/instructions/__init__.py index a8457b1f..e5e6e4bd 100644 --- a/src/braket/experimental/autoqasm/instructions/__init__.py +++ b/src/braket/experimental/autoqasm/instructions/__init__.py @@ -28,3 +28,4 @@ def bell(): from .gates import * # noqa: F401, F403 from .instructions import reset # noqa: F401 from .measurements import measure # noqa: F401 +from .qubits import global_qubit_register # noqa: F401 diff --git a/src/braket/experimental/autoqasm/instructions/measurements.py b/src/braket/experimental/autoqasm/instructions/measurements.py index 9738f61e..8e9d77bc 100644 --- a/src/braket/experimental/autoqasm/instructions/measurements.py +++ b/src/braket/experimental/autoqasm/instructions/measurements.py @@ -22,29 +22,32 @@ def my_program(): measure(0) """ +from __future__ import annotations from collections.abc import Iterable -from typing import Union from braket.experimental.autoqasm import program from braket.experimental.autoqasm import types as aq_types -from braket.experimental.autoqasm.instructions.qubits import _qubit +from braket.experimental.autoqasm.instructions.qubits import _qubit, global_qubit_register def measure( - qubits: Union[aq_types.QubitIdentifierType, Iterable[aq_types.QubitIdentifierType]] + qubits: aq_types.QubitIdentifierType | Iterable[aq_types.QubitIdentifierType] | None = None, ) -> aq_types.BitVar: """Add qubit measurement statements to the program and assign the measurement results to bit variables. Args: - qubits (Union[QubitIdentifierType, Iterable[QubitIdentifierType]]): The target qubits - to measure. + qubits (QubitIdentifierType | Iterable[QubitIdentifierType] | None): The target qubits + to measure. If None, all qubits will be measured. Default is None. Returns: BitVar: Bit variable the measurement results are assigned to. """ - if aq_types.is_qubit_identifier_type(qubits): + if qubits is None: + qubits = global_qubit_register() + + if isinstance(qubits, str) or not isinstance(qubits, Iterable): qubits = [qubits] oqpy_program = program.get_program_conversion_context().get_oqpy_program() diff --git a/src/braket/experimental/autoqasm/instructions/qubits.py b/src/braket/experimental/autoqasm/instructions/qubits.py index c72b71b5..de0370c2 100644 --- a/src/braket/experimental/autoqasm/instructions/qubits.py +++ b/src/braket/experimental/autoqasm/instructions/qubits.py @@ -14,9 +14,12 @@ """Utility functions that handle qubit construction and naming.""" +from __future__ import annotations + import re +from collections.abc import Iterable from functools import singledispatch -from typing import Any, Union +from typing import Any import oqpy.base from openpulse.printer import dumps @@ -44,10 +47,25 @@ def _get_physical_qubit_indices(qids: list[str]) -> list[int]: return braket_qubits -def _global_qubit_register(qubit_idx_expr: Union[int, str]) -> oqpy.Qubit: +def _global_qubit_register(qubit_idx_expr: int | str) -> oqpy.Qubit: return oqpy.Qubit(f"{constants.QUBIT_REGISTER}[{qubit_idx_expr}]", needs_declaration=False) +class GlobalQubitRegister(oqpy.Qubit): + def __init__(self, size: int | None): + super().__init__(name=constants.QUBIT_REGISTER, size=size, needs_declaration=False) + + def __len__(self) -> int: + return self.size + + def __iter__(self) -> Iterable: + return iter(range(len(self))) + + +def global_qubit_register() -> GlobalQubitRegister: + return program.get_program_conversion_context().global_qubit_register + + @singledispatch def _qubit(qid: Any) -> oqpy.Qubit: """Maps a given qubit representation to an oqpy qubit. @@ -78,6 +96,11 @@ def _(qid: int) -> oqpy.Qubit: return _global_qubit_register(qid) +@_qubit.register +def _(qid: GlobalQubitRegister) -> oqpy.Qubit: + raise ValueError("qubit index must be a single value, not a list or a register") + + @_qubit.register def _(qid: oqpy._ClassicalVar) -> oqpy.Qubit: # Indexed by variable, such as i in range(n); h(i) diff --git a/src/braket/experimental/autoqasm/operators/control_flow.py b/src/braket/experimental/autoqasm/operators/control_flow.py index a510f6a5..ad7cecef 100644 --- a/src/braket/experimental/autoqasm/operators/control_flow.py +++ b/src/braket/experimental/autoqasm/operators/control_flow.py @@ -20,11 +20,11 @@ import oqpy.base from braket.experimental.autoqasm import program -from braket.experimental.autoqasm.types import is_qasm_type +from braket.experimental.autoqasm.types import Range, is_qasm_type def for_stmt( - iter: Union[Iterable, oqpy.Range], + iter: Union[Iterable, oqpy.Range, oqpy.Qubit], extra_test: Optional[Callable[[], Any]], body: Callable[[Any], None], get_state: Any, @@ -35,7 +35,7 @@ def for_stmt( """Implements a for loop. Args: - iter (Union[Iterable, Range]): The iterable to be looped over. + iter (Union[Iterable, Range, Qubit]): The iterable to be looped over. extra_test (Optional[Callable[[], Any]]): A function to cause the loop to break if true. body (Callable[[Any], None]): The body of the for loop. get_state (Any): Unused. @@ -54,12 +54,14 @@ def for_stmt( def _oqpy_for_stmt( - iter: oqpy.Range, + iter: Union[oqpy.Range, oqpy.Qubit], body: Callable[[Any], None], opts: dict, ) -> None: """Overload of for_stmt that produces an oqpy for loop.""" program_conversion_context = program.get_program_conversion_context() + if isinstance(iter, oqpy.Qubit): + iter = Range(iter.size) with program_conversion_context.for_in(iter, opts["iterate_names"]) as f: body(f) diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 678baf12..6cf85734 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. """AutoQASM Program class, context managers, and related functions.""" + from __future__ import annotations import contextlib @@ -35,7 +36,11 @@ from braket.device_schema import DeviceActionType from braket.devices.device import Device from braket.experimental.autoqasm import constants, errors -from braket.experimental.autoqasm.instructions.qubits import _get_physical_qubit_indices, _qubit +from braket.experimental.autoqasm.instructions.qubits import ( + GlobalQubitRegister, + _get_physical_qubit_indices, + _qubit, +) from braket.experimental.autoqasm.program.serialization_properties import ( OpenQASMSerializationProperties, SerializationProperties, @@ -313,6 +318,7 @@ class ProgramConversionContext: def __init__(self, user_config: Optional[UserConfig] = None): self.subroutines_processing = set() # the set of subroutines queued for processing self.user_config = user_config or UserConfig() + self.global_qubit_register = GlobalQubitRegister(size=self.user_config.num_qubits) self.return_variable = None self.in_verbatim_block = False self.at_function_root_scope = True # whether we are at the root scope of main or subroutine @@ -368,6 +374,16 @@ def get_declared_qubits(self) -> Optional[int]: """ return self.user_config.num_qubits + def declare_global_qubit_register(self, size: int) -> None: + """Declare the global qubit register for the program. + + Args: + size (int): The size of the global qubit register to declare. + """ + root_oqpy_program = self.get_oqpy_program(scope=ProgramScope.MAIN) + self.global_qubit_register.size = size + root_oqpy_program.declare(self.global_qubit_register, to_beginning=True) + def register_gate(self, gate_name: str) -> None: """Register a gate that is used in this program. diff --git a/src/braket/experimental/autoqasm/types/types.py b/src/braket/experimental/autoqasm/types/types.py index 97d2e58b..f062eaaf 100644 --- a/src/braket/experimental/autoqasm/types/types.py +++ b/src/braket/experimental/autoqasm/types/types.py @@ -37,7 +37,13 @@ def is_qasm_type(val: Any) -> bool: Returns: bool: Whether the object is a QASM type. """ - qasm_types = (oqpy.Range, oqpy._ClassicalVar, oqpy.base.OQPyExpression, FreeParameterExpression) + qasm_types = ( + oqpy.Range, + oqpy._ClassicalVar, + oqpy.Qubit, + oqpy.base.OQPyExpression, + FreeParameterExpression, + ) # The input can either be a class, like oqpy.Range ... if type(val) is type: return issubclass(val, qasm_types) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index de113b03..6c7e7b56 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -1144,3 +1144,100 @@ def circ(q: int, r: int): with pytest.raises(errors.UnknownQubitCountError): circ.build() + + +def test_measure_all(): + @aq.main(num_qubits=4) + def main(): + return measure() + + expected_ir = """OPENQASM 3.0; +output bit[4] return_value; +qubit[4] __qubits__; +bit[4] __bit_0__ = "0000"; +__bit_0__[0] = measure __qubits__[0]; +__bit_0__[1] = measure __qubits__[1]; +__bit_0__[2] = measure __qubits__[2]; +__bit_0__[3] = measure __qubits__[3]; +return_value = __bit_0__;""" + assert main.build().to_ir() == expected_ir + + +def test_measure_all_using_iter(): + @aq.main(num_qubits=3) + def main(): + return measure(aq.qubits) + + expected_ir = """OPENQASM 3.0; +output bit[3] return_value; +qubit[3] __qubits__; +bit[3] __bit_0__ = "000"; +__bit_0__[0] = measure __qubits__[0]; +__bit_0__[1] = measure __qubits__[1]; +__bit_0__[2] = measure __qubits__[2]; +return_value = __bit_0__;""" + assert main.build().to_ir() == expected_ir + + +def test_gate_register_not_allowed(): + @aq.main(num_qubits=4) + def main(): + h(aq.qubits) + + with pytest.raises( + ValueError, match="qubit index must be a single value, not a list or a register" + ): + main.build() + + +def test_global_qubit_register_loop(): + @aq.main(num_qubits=5) + def main(): + for q in aq.qubits: + h(q) + + expected_ir = """OPENQASM 3.0; +qubit[5] __qubits__; +for int q in [0:5 - 1] { + h __qubits__[q]; +}""" + assert main.build().to_ir() == expected_ir + + +def test_global_qubit_register_needs_num_qubits(): + @aq.main + def main(): + for q in aq.qubits: + h(q) + + with pytest.raises(errors.UnknownQubitCountError): + main.build() + + +def test_global_qubit_register_len_aq_range(): + @aq.main(num_qubits=6) + def main(): + for q in aq.range(len(aq.qubits)): + h(q) + + expected_ir = """OPENQASM 3.0; +qubit[6] __qubits__; +for int q in [0:6 - 1] { + h __qubits__[q]; +}""" + assert main.build().to_ir() == expected_ir + + +def test_global_qubit_register_len_py_range(): + @aq.main(num_qubits=4) + def main(): + for q in range(len(aq.qubits)): + h(q) + + expected_ir = """OPENQASM 3.0; +qubit[4] __qubits__; +h __qubits__[0]; +h __qubits__[1]; +h __qubits__[2]; +h __qubits__[3];""" + assert main.build().to_ir() == expected_ir 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 1091/1165] 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 16f96bd3..6057b05c 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 98a9b30e..7e758d02 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 1092/1165] 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 83ac54ff..0e721a98 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 9c443c49..59f3b790 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 1093/1165] 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 59f3b790..71a21816 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 1094/1165] fix: change schemas constraint (#927) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6057b05c..16f96bd3 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 1095/1165] 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 aca79d59..fa49d9b0 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 16f96bd3..f2621de5 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 4e093e5b..ae33d402 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 4877a0b8..f284b5a6 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 7e758d02..98a9b30e 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 1096/1165] 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 0e721a98..b8a9aa14 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 71a21816..a2711b93 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 1097/1165] 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 a2711b93..9c4e6290 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 fa3455ca5c8e8d20284afe6e464fcf9e05047f13 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Thu, 28 Mar 2024 11:49:45 -0400 Subject: [PATCH 1098/1165] Replace usage of Optional and Union in type hints (#925) --- src/braket/experimental/autoqasm/api.py | 38 ++++++++-------- src/braket/experimental/autoqasm/errors.py | 4 +- .../autoqasm/operators/comparisons.py | 20 +++++---- .../operators/conditional_expressions.py | 12 ++--- .../autoqasm/operators/control_flow.py | 14 +++--- .../autoqasm/operators/data_structures.py | 12 ++--- .../autoqasm/operators/logical.py | 24 +++++----- .../experimental/autoqasm/operators/utils.py | 8 ++-- .../experimental/autoqasm/program/pragmas.py | 6 +-- .../experimental/autoqasm/program/program.py | 44 +++++++++---------- .../program/serialization_properties.py | 6 +-- .../experimental/autoqasm/pulse/pulse.py | 18 ++++---- .../autoqasm/transpiler/transpiler.py | 40 +++++++++-------- .../autoqasm/types/conversions.py | 12 ++--- .../experimental/autoqasm/types/types.py | 26 +++++------ 15 files changed, 150 insertions(+), 134 deletions(-) diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 25cabc1b..9319866a 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -20,7 +20,7 @@ import inspect from collections.abc import Callable from types import FunctionType -from typing import Any, Iterable, Optional, Union, get_args +from typing import Any, Iterable, get_args import openqasm3.ast as qasm_ast import oqpy.base @@ -37,9 +37,9 @@ def main( - func: Optional[Callable] = None, + func: Callable | None = None, *, - num_qubits: Optional[int] = None, + num_qubits: int | None = None, ) -> aq_program.Program | Callable[..., aq_program.Program]: """Decorator that converts a function into a Program object containing the quantum program. @@ -47,9 +47,9 @@ def main( function is called, and a new Program object is returned each time. Args: - func (Optional[Callable]): Decorated function. May be `None` in the case where decorator + func (Callable | None): Decorated function. May be `None` in the case where decorator is used with parentheses. - num_qubits (Optional[int]): Configuration to set the total number of qubits to declare in + num_qubits (int | None): Configuration to set the total number of qubits to declare in the program. Returns: @@ -80,15 +80,15 @@ def main( def subroutine( - func: Optional[Callable] = None, annotations: Optional[str | Iterable[str]] = None + func: Callable | None = None, annotations: str | Iterable[str] | None = None ) -> Callable[..., aq_program.Program]: """Decorator that converts a function into a callable that will insert a subroutine into the quantum program. Args: - func (Optional[Callable]): Decorated function. May be `None` in the case where decorator + func (Callable | None): Decorated function. May be `None` in the case where decorator is used with parentheses. - annotations (Optional[str | Iterable[str]]): Annotations to be added to the subroutine. + annotations (str | Iterable[str] | None): Annotations to be added to the subroutine. Returns: Callable[..., Program]: A callable which returns the converted @@ -103,11 +103,11 @@ def subroutine( ) -def gate(func: Optional[Callable] = None) -> Callable[..., None]: +def gate(func: Callable | None = None) -> Callable[..., None]: """Decorator that converts a function into a callable gate definition. Args: - func (Optional[Callable]): Decorated function. May be `None` in the case where decorator + func (Callable | None): Decorated function. May be `None` in the case where decorator is used with parentheses. Returns: @@ -139,21 +139,21 @@ def gate_calibration(*, implements: Callable, **kwargs) -> Callable[[], GateCali def _function_wrapper( - func: Optional[Callable], + func: Callable | None, *, converter_callback: Callable, - converter_args: Optional[dict[str, Any]] = None, -) -> Callable[..., Optional[Union[aq_program.Program, GateCalibration]]]: + converter_args: dict[str, Any] | None = None, +) -> Callable[..., aq_program.Program | GateCalibration | None]: """Wrapping and conversion logic around the user function `f`. Args: - func (Optional[Callable]): Decorated function. May be `None` in the case where decorator + func (Callable | None): Decorated function. May be `None` in the case where decorator is used with parentheses. converter_callback (Callable): The function converter, e.g., _convert_main. - converter_args (Optional[dict[str, Any]]): Extra arguments for the function converter. + converter_args (dict[str, Any] | None): Extra arguments for the function converter. Returns: - Callable[..., Optional[Union[Program, GateCalibration]]]: A callable which + Callable[..., Program | GateCalibration | None]: A callable which returns the converted construct, if any, when called. """ if not (func and callable(func)): @@ -489,7 +489,7 @@ def _make_return_instance_from_oqpy_return_type(return_type: Any) -> Any: return var_type() -def _get_bitvar_size(node: qasm_ast.BitType) -> Optional[int]: +def _get_bitvar_size(node: qasm_ast.BitType) -> int | None: if not isinstance(node, qasm_ast.BitType) or not node.size: return None return node.size.value @@ -663,14 +663,14 @@ def _convert_calibration( def _validate_calibration_args( gate_function: Callable, - decorator_args: dict[str, Union[Qubit, float]], + decorator_args: dict[str, Qubit | float], func_args: aq_program.GateArgs, ) -> None: """Validate the arguments passed to the calibration decorator and function. Args: gate_function (Callable): The gate function which calibration is being defined. - decorator_args (dict[str, Union[Qubit, float]]): The calibration decorator arguments. + decorator_args (dict[str, Qubit | float]): The calibration decorator arguments. func_args (aq_program.GateArgs): The gate function arguments. """ gate_args = _get_gate_args(gate_function) diff --git a/src/braket/experimental/autoqasm/errors.py b/src/braket/experimental/autoqasm/errors.py index 17aefcc8..98562882 100644 --- a/src/braket/experimental/autoqasm/errors.py +++ b/src/braket/experimental/autoqasm/errors.py @@ -14,7 +14,7 @@ """Errors raised in the AutoQASM build process.""" -from typing import Optional +from __future__ import annotations class AutoQasmError(Exception): @@ -90,7 +90,7 @@ class InsufficientQubitCountError(AutoQasmError): class UnsupportedConditionalExpressionError(AutoQasmError): """Conditional expressions which return values are not supported.""" - def __init__(self, true_type: Optional[type], false_type: Optional[type]): + def __init__(self, true_type: type | None, false_type: type | None): if_type = true_type.__name__ if true_type else "None" else_type = false_type.__name__ if false_type else "None" self.message = """\ diff --git a/src/braket/experimental/autoqasm/operators/comparisons.py b/src/braket/experimental/autoqasm/operators/comparisons.py index 6f08f29e..66a76e71 100644 --- a/src/braket/experimental/autoqasm/operators/comparisons.py +++ b/src/braket/experimental/autoqasm/operators/comparisons.py @@ -14,7 +14,9 @@ """Operators for comparison operators: <, <=, >, and >=.""" -from typing import Any, Union +from __future__ import annotations + +from typing import Any from braket.experimental.autoqasm import program from braket.experimental.autoqasm import types as aq_types @@ -22,7 +24,7 @@ from .utils import _register_and_convert_parameters -def lt_(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]: +def lt_(a: Any, b: Any) -> bool | aq_types.BoolVar: """Functional form of "<". Args: @@ -30,7 +32,7 @@ def lt_(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]: b (Any): The second expression. Returns: - Union[bool, BoolVar]: Whether the first expression is less than the second. + bool | BoolVar: Whether the first expression is less than the second. """ if aq_types.is_qasm_type(a) or aq_types.is_qasm_type(b): return _aq_lt(a, b) @@ -48,7 +50,7 @@ def _aq_lt(a: Any, b: Any) -> aq_types.BoolVar: return result -def lteq_(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]: +def lteq_(a: Any, b: Any) -> bool | aq_types.BoolVar: """Functional form of "<=". Args: @@ -56,7 +58,7 @@ def lteq_(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]: b (Any): The second expression. Returns: - Union[bool, BoolVar]: Whether the first expression is less than or equal to the second. + bool | BoolVar: Whether the first expression is less than or equal to the second. """ if aq_types.is_qasm_type(a) or aq_types.is_qasm_type(b): return _aq_lteq(a, b) @@ -74,7 +76,7 @@ def _aq_lteq(a: Any, b: Any) -> aq_types.BoolVar: return result -def gt_(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]: +def gt_(a: Any, b: Any) -> bool | aq_types.BoolVar: """Functional form of ">". Args: @@ -82,7 +84,7 @@ def gt_(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]: b (Any): The second expression. Returns: - Union[bool, BoolVar]: Whether the first expression is greater than the second. + bool | BoolVar: Whether the first expression is greater than the second. """ if aq_types.is_qasm_type(a) or aq_types.is_qasm_type(b): return _aq_gt(a, b) @@ -100,7 +102,7 @@ def _aq_gt(a: Any, b: Any) -> aq_types.BoolVar: return result -def gteq_(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]: +def gteq_(a: Any, b: Any) -> bool | aq_types.BoolVar: """Functional form of ">=". Args: @@ -108,7 +110,7 @@ def gteq_(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]: b (Any): The second expression. Returns: - Union[bool, BoolVar]: Whether the first expression is greater than or equal to the second. + bool | BoolVar: Whether the first expression is greater than or equal to the second. """ if aq_types.is_qasm_type(a) or aq_types.is_qasm_type(b): return _aq_gteq(a, b) diff --git a/src/braket/experimental/autoqasm/operators/conditional_expressions.py b/src/braket/experimental/autoqasm/operators/conditional_expressions.py index 27bfb7d2..75d19a8c 100644 --- a/src/braket/experimental/autoqasm/operators/conditional_expressions.py +++ b/src/braket/experimental/autoqasm/operators/conditional_expressions.py @@ -14,8 +14,10 @@ """Operators for conditional expressions (e.g. the ternary if statement).""" +from __future__ import annotations + from collections.abc import Callable -from typing import Any, Optional +from typing import Any import oqpy.base @@ -25,7 +27,7 @@ def if_exp( - cond: Any, if_true: Callable[[], Any], if_false: Callable[[], Any], expr_repr: Optional[str] + cond: Any, if_true: Callable[[], Any], if_false: Callable[[], Any], expr_repr: str | None ) -> Any: """Implements a conditional if expression. @@ -33,7 +35,7 @@ def if_exp( cond (Any): The condition of the if clause. if_true (Callable[[], Any]): The function to run if the condition is true. if_false (Callable[[], Any]): The function to run if the condition is false. - expr_repr (Optional[str]): The conditional expression represented as a string. + expr_repr (str | None): The conditional expression represented as a string. Returns: Any: The value returned from the conditional expression. @@ -48,8 +50,8 @@ def _oqpy_if_exp( cond: Any, if_true: Callable[[None], Any], if_false: Callable[[None], Any], - expr_repr: Optional[str], -) -> Optional[oqpy.base.Var]: + expr_repr: str | None, +) -> oqpy.base.Var | None: """Overload of if_exp that stages an oqpy conditional.""" result_var = None program_conversion_context = aq_program.get_program_conversion_context() diff --git a/src/braket/experimental/autoqasm/operators/control_flow.py b/src/braket/experimental/autoqasm/operators/control_flow.py index ad7cecef..a4af683c 100644 --- a/src/braket/experimental/autoqasm/operators/control_flow.py +++ b/src/braket/experimental/autoqasm/operators/control_flow.py @@ -14,8 +14,10 @@ """Operators for control flow constructs (e.g. if, for, while).""" +from __future__ import annotations + from collections.abc import Callable, Iterable -from typing import Any, Optional, Union +from typing import Any import oqpy.base @@ -24,8 +26,8 @@ def for_stmt( - iter: Union[Iterable, oqpy.Range, oqpy.Qubit], - extra_test: Optional[Callable[[], Any]], + iter: Iterable | oqpy.Range | oqpy.Qubit, + extra_test: Callable[[], Any] | None, body: Callable[[Any], None], get_state: Any, set_state: Any, @@ -35,8 +37,8 @@ def for_stmt( """Implements a for loop. Args: - iter (Union[Iterable, Range, Qubit]): The iterable to be looped over. - extra_test (Optional[Callable[[], Any]]): A function to cause the loop to break if true. + iter (Iterable | Range | Qubit): The iterable to be looped over. + extra_test (Callable[[], Any] | None): A function to cause the loop to break if true. body (Callable[[Any], None]): The body of the for loop. get_state (Any): Unused. set_state (Any): Unused. @@ -54,7 +56,7 @@ def for_stmt( def _oqpy_for_stmt( - iter: Union[oqpy.Range, oqpy.Qubit], + iter: oqpy.Range | oqpy.Qubit, body: Callable[[Any], None], opts: dict, ) -> None: diff --git a/src/braket/experimental/autoqasm/operators/data_structures.py b/src/braket/experimental/autoqasm/operators/data_structures.py index c27aeeea..1b3efc71 100644 --- a/src/braket/experimental/autoqasm/operators/data_structures.py +++ b/src/braket/experimental/autoqasm/operators/data_structures.py @@ -14,9 +14,11 @@ """Operators for other data structures (e.g. list).""" +from __future__ import annotations + import collections from collections.abc import Iterable -from typing import Any, Optional +from typing import Any class ListPopOpts(collections.namedtuple("ListPopOpts", ("element_dtype", "element_shape"))): @@ -41,12 +43,12 @@ def list_append(target: list, element: Any) -> list: return target -def list_pop(target: list, element: Optional[Any], opts: ListPopOpts) -> tuple: +def list_pop(target: list, element: Any, opts: ListPopOpts) -> tuple: """The list pop function. Args: target (list): An entity that supports append semantics. - element (Optional[Any]): The element index to pop. If None, pops the last element. + element (Any): The element index to pop. If None, pops the last element. opts (ListPopOpts): Metadata about the converted pop operation. Returns: @@ -69,11 +71,11 @@ def list_stack(target: list, opts: ListStackOpts) -> list: return opts.original_call(target) -def new_list(iterable: Optional[Iterable] = None) -> list: +def new_list(iterable: Iterable | None = None) -> list: """The list constructor. Args: - iterable (Optional[Iterable]): Optional elements to fill the list with. Defaults to None. + iterable (Iterable | None): Optional elements to fill the list with. Defaults to None. Returns: list: A list-like object. The exact return value depends on the initial elements. diff --git a/src/braket/experimental/autoqasm/operators/logical.py b/src/braket/experimental/autoqasm/operators/logical.py index c7bcf7c5..35474019 100644 --- a/src/braket/experimental/autoqasm/operators/logical.py +++ b/src/braket/experimental/autoqasm/operators/logical.py @@ -14,7 +14,9 @@ """Operators for logical boolean operators (e.g. not, and, or).""" -from typing import Any, Callable, Union +from __future__ import annotations + +from typing import Any, Callable import oqpy.base from openpulse import ast @@ -25,7 +27,7 @@ from .utils import _register_and_convert_parameters -def and_(a: Callable[[], Any], b: Callable[[], Any]) -> Union[bool, aq_types.BoolVar]: +def and_(a: Callable[[], Any], b: Callable[[], Any]) -> bool | aq_types.BoolVar: """Functional form of "and". Args: @@ -33,7 +35,7 @@ def and_(a: Callable[[], Any], b: Callable[[], Any]) -> Union[bool, aq_types.Boo b (Callable[[], Any]): Callable that returns the second expression. Returns: - Union[bool, BoolVar]: Whether both expressions are true. + bool | BoolVar: Whether both expressions are true. """ a = a() b = b() @@ -57,7 +59,7 @@ def _py_and(a: Any, b: Any) -> bool: return a and b -def or_(a: Callable[[], Any], b: Callable[[], Any]) -> Union[bool, aq_types.BoolVar]: +def or_(a: Callable[[], Any], b: Callable[[], Any]) -> bool | aq_types.BoolVar: """Functional form of "or". Args: @@ -65,7 +67,7 @@ def or_(a: Callable[[], Any], b: Callable[[], Any]) -> Union[bool, aq_types.Bool b (Callable[[], Any]): Callable that returns the second expression. Returns: - Union[bool, BoolVar]: Whether either expression is true. + bool | BoolVar: Whether either expression is true. """ a = a() b = b() @@ -89,14 +91,14 @@ def _py_or(a: Any, b: Any) -> bool: return a or b -def not_(a: Any) -> Union[bool, aq_types.BoolVar]: +def not_(a: Any) -> bool | aq_types.BoolVar: """Functional form of "not". Args: a (Any): Expression to negate. Returns: - Union[bool, BoolVar]: Whether the expression is false. + bool | BoolVar: Whether the expression is false. """ if aq_types.is_qasm_type(a): return _oqpy_not(a) @@ -118,7 +120,7 @@ def _py_not(a: Any) -> bool: return not a -def eq(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]: +def eq(a: Any, b: Any) -> bool | aq_types.BoolVar: """Functional form of "equal". Args: @@ -126,7 +128,7 @@ def eq(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]: b (Any): Second expression to compare. Returns: - Union[bool, BoolVar]: Whether the expressions are equal. + bool | BoolVar: Whether the expressions are equal. """ if aq_types.is_qasm_type(a) or aq_types.is_qasm_type(b): return _oqpy_eq(a, b) @@ -148,7 +150,7 @@ def _py_eq(a: Any, b: Any) -> bool: return a == b -def not_eq(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]: +def not_eq(a: Any, b: Any) -> bool | aq_types.BoolVar: """Functional form of "not equal". Args: @@ -156,7 +158,7 @@ def not_eq(a: Any, b: Any) -> Union[bool, aq_types.BoolVar]: b (Any): Second expression to compare. Returns: - Union[bool, BoolVar]: Whether the expressions are not equal. + bool | BoolVar: Whether the expressions are not equal. """ if aq_types.is_qasm_type(a) or aq_types.is_qasm_type(b): return _oqpy_not_eq(a, b) diff --git a/src/braket/experimental/autoqasm/operators/utils.py b/src/braket/experimental/autoqasm/operators/utils.py index a4ac7f81..9dabfeae 100644 --- a/src/braket/experimental/autoqasm/operators/utils.py +++ b/src/braket/experimental/autoqasm/operators/utils.py @@ -14,7 +14,9 @@ "Utility methods for operators." -from typing import Any, Union +from __future__ import annotations + +from typing import Any from braket.experimental.autoqasm import program from braket.experimental.autoqasm import types as aq_types @@ -22,7 +24,7 @@ def _register_and_convert_parameters( *args: tuple[Any], -) -> Union[list[aq_types.FloatVar], aq_types.FloatVar]: +) -> list[aq_types.FloatVar] | aq_types.FloatVar: """Adds FreeParameters to the program conversion context parameter registry, and returns the associated FloatVar objects. @@ -32,7 +34,7 @@ def _register_and_convert_parameters( FloatVars are more compatible with the program conversion operations. Returns: - Union[list[FloatVar], FloatVar]: FloatVars for program conversion. + list[FloatVar] | FloatVar: FloatVars for program conversion. """ program_conversion_context = program.get_program_conversion_context() program_conversion_context.register_args(args) diff --git a/src/braket/experimental/autoqasm/program/pragmas.py b/src/braket/experimental/autoqasm/program/pragmas.py index 0fdf52e7..8ec36b40 100644 --- a/src/braket/experimental/autoqasm/program/pragmas.py +++ b/src/braket/experimental/autoqasm/program/pragmas.py @@ -32,7 +32,7 @@ def pragma_example() -> None: import contextlib from enum import Enum -from typing import Iterable, Optional +from typing import Iterable from braket.device_schema import DeviceActionType from braket.experimental.autoqasm import errors, program @@ -46,7 +46,7 @@ class PragmaType(str, Enum): @contextlib.contextmanager -def verbatim(annotations: Optional[str | Iterable[str]] = None) -> None: +def verbatim(annotations: str | Iterable[str] | None = None) -> None: """Context management protocol that, when used with a `with` statement, wraps the code block in a verbatim block. @@ -54,7 +54,7 @@ def verbatim(annotations: Optional[str | Iterable[str]] = None) -> None: programmed without compilation or modification of any sort. Args: - annotations (Optional[str | Iterable[str]]): Annotations for the box. + annotations (str | Iterable[str] | None): Annotations for the box. Raises: errors.VerbatimBlockNotAllowed: If a verbatim block is not allowed at this point in diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 6cf85734..63da3b30 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -21,7 +21,7 @@ from collections.abc import Callable, Iterable from dataclasses import dataclass from enum import Enum -from typing import Any, Optional, Union +from typing import Any import oqpy.base import pygments @@ -66,10 +66,10 @@ def _get_local() -> threading.local: class UserConfig: """User-specified configurations that influence program building.""" - num_qubits: Optional[int] = None + num_qubits: int | None = None """The total number of qubits to declare in the program.""" - device: Optional[Device] = None + device: Device | None = None """The target device for the program.""" @@ -172,12 +172,12 @@ def __init__( self._oqpy_program = oqpy_program self._has_pulse_control = has_pulse_control - def with_calibrations(self, gate_calibrations: Union[Callable, list[Callable]]) -> Program: + def with_calibrations(self, gate_calibrations: Callable | list[Callable]) -> Program: """Add the gate calibrations to the program. The calibration added program is returned as a new object. The original program is not modified. Args: - gate_calibrations (Union[Callable, list[Callable]]): The gate calibrations to add to + gate_calibrations (Callable | list[Callable]): The gate calibrations to add to the main program. Calibration are passed as callable without evaluation. Returns: @@ -274,7 +274,7 @@ class GateArgs: """Represents a list of qubit and angle arguments for a gate definition.""" def __init__(self): - self._args: list[Union[oqpy.Qubit, oqpy.AngleVar]] = [] + self._args: list[oqpy.Qubit | oqpy.AngleVar] = [] def __len__(self): return len(self._args) @@ -315,7 +315,7 @@ def angle_indices(self) -> list[int]: class ProgramConversionContext: """The data structure used while converting a program. Intended for internal use.""" - def __init__(self, user_config: Optional[UserConfig] = None): + def __init__(self, user_config: UserConfig | None = None): self.subroutines_processing = set() # the set of subroutines queued for processing self.user_config = user_config or UserConfig() self.global_qubit_register = GlobalQubitRegister(size=self.user_config.num_qubits) @@ -368,7 +368,7 @@ def register_qubit(self, qubit: int) -> None: """Register a virtual qubit that is used in this program.""" self._virtual_qubits_used.add(qubit) - def get_declared_qubits(self) -> Optional[int]: + def get_declared_qubits(self) -> int | None: """Return the number of qubits to declare in the program, as specified by the user. Returns None if the user did not specify how many qubits are in the program. """ @@ -439,13 +439,13 @@ def _free_symbol_names(expr: FreeParameterExpression) -> Iterable[str]: def register_input_parameter( self, name: str, - type_: Union[float, int, bool] = float, + type_: float | int | bool = float, ) -> None: """Register an input parameter if it has not already been registered. Args: name (str): The name of the parameter to register with the program. - type_ (Union[float, int, bool]): The type of the parameter to register + type_ (float | int | bool): The type of the parameter to register with the program. Default: float. Raises: @@ -469,13 +469,13 @@ def register_input_parameter( return var def register_output_parameter( - self, name: str, value: Union[bool, int, float, oqpy.base.Var, oqpy.OQPyExpression, None] + self, name: str, value: bool | int | float | oqpy.base.Var | oqpy.OQPyExpression | None ) -> None: """Register a new output parameter if it is not None. Args: name (str): The name of the parameter to register with the program. - value (Union[bool, int, float, Var, OQPyExpression, None]): Value to + value (bool | int | float | Var | OQPyExpression | None): Value to register as an output parameter. """ if value is None: @@ -517,7 +517,7 @@ def add_io_declarations(self) -> None: parameter.name = parameter_name root_oqpy_program._add_var(parameter) - def get_target_device(self) -> Optional[Device]: + def get_target_device(self) -> Device | None: """Return the target device for the program, as specified by the user. Returns None if the user did not specify a target device. """ @@ -700,7 +700,7 @@ def _control_flow_block( finally: self.at_function_root_scope = original - def _add_annotations(self, annotations: Optional[str | Iterable[str]] = None) -> None: + def _add_annotations(self, annotations: str | Iterable[str] | None = None) -> None: oqpy_program = self.get_oqpy_program() for annotation in aq_types.make_annotations_list(annotations): oqpy_program.annotate(annotation) @@ -728,13 +728,13 @@ def else_block(self) -> contextlib._GeneratorContextManager: return self._control_flow_block(oqpy.Else(oqpy_program)) def for_in( - self, iterator: aq_types.Range, iterator_name: Optional[str] + self, iterator: aq_types.Range, iterator_name: str | None ) -> contextlib._GeneratorContextManager: """Sets the program conversion context into a for loop context. Args: iterator (Range): The iterator of the for loop. - iterator_name (Optional[str]): The symbol to use as the name of the iterator. + iterator_name (str | None): The symbol to use as the name of the iterator. Yields: _GeneratorContextManager: The context manager of the oqpy.ForIn block. @@ -805,14 +805,14 @@ def calibration_definition( @contextlib.contextmanager def box( self, - pragma: Optional[str] = None, - annotations: Optional[str | Iterable[str]] = None, + pragma: str | None = None, + annotations: str | Iterable[str] | None = None, ) -> None: """Sets the program conversion context into a box context. Args: - pragma (Optional[str]): Pragma to include before the box. Defaults to None. - annotations (Optional[str | Iterable[str]]): Annotations for the box. + pragma (str | None): Pragma to include before the box. Defaults to None. + annotations (str | Iterable[str] | None): Annotations for the box. """ oqpy_program = self.get_oqpy_program() if pragma: @@ -823,7 +823,7 @@ def box( @contextlib.contextmanager -def build_program(user_config: Optional[UserConfig] = None) -> None: +def build_program(user_config: UserConfig | None = None) -> None: """Creates a context manager which ensures there is a valid thread-local ProgramConversionContext object. If this context manager created the ProgramConversionContext object, it removes it from thread-local storage when @@ -837,7 +837,7 @@ def build_program(user_config: Optional[UserConfig] = None) -> None: program = program_conversion_context.make_program() Args: - user_config (Optional[UserConfig]): User-supplied program building options. + user_config (UserConfig | None): User-supplied program building options. """ try: owns_program_conversion_context = False diff --git a/src/braket/experimental/autoqasm/program/serialization_properties.py b/src/braket/experimental/autoqasm/program/serialization_properties.py index c63c11be..1c845b56 100644 --- a/src/braket/experimental/autoqasm/program/serialization_properties.py +++ b/src/braket/experimental/autoqasm/program/serialization_properties.py @@ -11,17 +11,17 @@ # 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 dataclasses import dataclass -from typing import Optional @dataclass class OpenQASMSerializationProperties: - auto_defcalgrammar: Optional[bool] = False + auto_defcalgrammar: bool | None = False """Whether to automatically include defcalgrammar when pulses are used. Default to False.""" - include_externs: Optional[bool] = False + include_externs: bool | None = False """Whether to include externs. Default to False.""" diff --git a/src/braket/experimental/autoqasm/pulse/pulse.py b/src/braket/experimental/autoqasm/pulse/pulse.py index b05b450f..775debf4 100644 --- a/src/braket/experimental/autoqasm/pulse/pulse.py +++ b/src/braket/experimental/autoqasm/pulse/pulse.py @@ -14,7 +14,7 @@ """Pulse instructions that apply to frames or qubits.""" -from typing import Union +from __future__ import annotations import oqpy @@ -143,17 +143,17 @@ def _add_sequence() -> BitVar: def delay( - qubits_or_frames: Union[Frame, list[Frame], QubitIdentifierType, list[QubitIdentifierType]], - duration: Union[float, oqpy.FloatVar, FreeParameterExpression], + qubits_or_frames: Frame | list[Frame] | QubitIdentifierType | list[QubitIdentifierType], + duration: float | oqpy.FloatVar | FreeParameterExpression, ) -> None: """Adds an instruction to advance the frame clock by the specified `duration` value. Args: - qubits_or_frames (Union[Frame, list[Frame], QubitIdentifierType, list[QubitIdentifierType]]): + qubits_or_frames (Frame | list[Frame] | QubitIdentifierType | list[QubitIdentifierType]): Qubits or frame(s) on which the delay needs to be introduced. - duration (Union[float, FloatVar, FreeParameterExpression]): Value (in seconds) defining the + duration (float | FloatVar | FreeParameterExpression): Value (in seconds) defining the duration of the delay. - """ # noqa: E501 + """ if not isinstance(qubits_or_frames, list): qubits_or_frames = [qubits_or_frames] if all(is_qubit_identifier_type(q) for q in qubits_or_frames): @@ -164,16 +164,16 @@ def delay( def barrier( - qubits_or_frames: Union[Frame, list[Frame], QubitIdentifierType, list[QubitIdentifierType]] + qubits_or_frames: Frame | list[Frame] | QubitIdentifierType | list[QubitIdentifierType], ) -> None: """Adds an instruction to align the frame clocks to the latest time across all the specified frames. When applied on qubits, it prevents compilations across the barrier, if the compiler supports barrier. Args: - qubits_or_frames (Union[Frame, list[Frame], QubitIdentifierType, list[QubitIdentifierType]]): + qubits_or_frames (Frame | list[Frame] | QubitIdentifierType | list[QubitIdentifierType]): Qubits or frame(s) on which the barrier needs to be introduced. - """ # noqa: E501 + """ if not isinstance(qubits_or_frames, list): qubits_or_frames = [qubits_or_frames] if all(is_qubit_identifier_type(q) for q in qubits_or_frames): diff --git a/src/braket/experimental/autoqasm/transpiler/transpiler.py b/src/braket/experimental/autoqasm/transpiler/transpiler.py index 815c9dfb..4696f86b 100644 --- a/src/braket/experimental/autoqasm/transpiler/transpiler.py +++ b/src/braket/experimental/autoqasm/transpiler/transpiler.py @@ -18,11 +18,13 @@ to reduce duplication if possible. """ +from __future__ import annotations + import functools import importlib import inspect from collections.abc import Callable -from typing import Any, Optional, Union +from typing import Any import gast from malt.converters import ( @@ -61,7 +63,7 @@ def __init__(self): super(PyToOqpy, self).__init__() self._extra_locals = None - def get_transformed_name(self, node: Union[gast.Lambda, gast.FunctionDef]) -> str: + def get_transformed_name(self, node: gast.Lambda | gast.FunctionDef) -> str: return "oq__" + super(PyToOqpy, self).get_transformed_name(node) def get_extra_locals(self) -> dict: @@ -91,8 +93,8 @@ def get_caching_key(self, ctx: ag_ctx.ControlStatusCtx) -> converter.ConversionO return ctx.options def _initial_analysis( - self, node: Union[gast.Lambda, gast.FunctionDef], ctx: ag_ctx.ControlStatusCtx - ) -> Union[gast.Lambda, gast.FunctionDef]: + self, node: gast.Lambda | gast.FunctionDef, ctx: ag_ctx.ControlStatusCtx + ) -> gast.Lambda | gast.FunctionDef: graphs = cfg.build(node) node = qual_names.resolve(node) node = activity.resolve(node, ctx, None) @@ -106,17 +108,17 @@ def _initial_analysis( return node def transform_ast( - self, node: Union[gast.Lambda, gast.FunctionDef], ctx: ag_ctx.ControlStatusCtx - ) -> Union[gast.Lambda, gast.FunctionDef]: + self, node: gast.Lambda | gast.FunctionDef, ctx: ag_ctx.ControlStatusCtx + ) -> gast.Lambda | gast.FunctionDef: """Performs an actual transformation of a function's AST. Args: - node (Union[Lambda, FunctionDef]): One or more ast.AST nodes + node (Lambda | FunctionDef): One or more ast.AST nodes representing the AST to be transformed. ctx (ControlStatusCtx): transformer context. Returns: - Union[Lambda, FunctionDef]: The root of the transformed AST. + Lambda | FunctionDef: The root of the transformed AST. """ unsupported_features_checker.verify(node) # TODO (#809): add forbidden_aq_program_usage_checker.verify(node) @@ -145,7 +147,7 @@ def transform_ast( return node -def _convert_actual(entity: Callable, program_ctx: Optional[ag_ctx.ControlStatusCtx]) -> Callable: +def _convert_actual(entity: Callable, program_ctx: ag_ctx.ControlStatusCtx | None) -> Callable: """Applies AutoGraph to entity.""" if not hasattr(entity, "__code__"): raise ValueError( @@ -171,9 +173,9 @@ def _convert_actual(entity: Callable, program_ctx: Optional[ag_ctx.ControlStatus def converted_call( f: Callable, args: tuple, - kwargs: Optional[dict], - caller_fn_scope: Optional[function_wrappers.FunctionScope] = None, - options: Optional[converter.ConversionOptions] = None, + kwargs: dict | None, + caller_fn_scope: function_wrappers.FunctionScope | None = None, + options: converter.ConversionOptions | None = None, ) -> Any: """Converts a function call inline. @@ -189,10 +191,10 @@ def converted_call( Args: f (Callable): The function to convert. args (tuple): the original positional arguments of f. - kwargs (Optional[dict]): the original keyword arguments of f. - caller_fn_scope (Optional[FunctionScope]): the function scope of the converted + kwargs (dict | None): the original keyword arguments of f. + caller_fn_scope (FunctionScope | None): the function scope of the converted function in which this call was originally made. Defaults to None. - options (Optional[ConversionOptions]): conversion options. If not + options (ConversionOptions | None): conversion options. If not specified, the value of caller_fn_scope.callopts is used. Either options or caller_fn_scope must be present. Defaults to None. @@ -241,9 +243,9 @@ def converted_call( def _converted_partial( f: Callable, args: tuple, - kwargs: Optional[dict], - caller_fn_scope: Optional[function_wrappers.FunctionScope] = None, - options: Optional[converter.ConversionOptions] = None, + kwargs: dict | None, + caller_fn_scope: function_wrappers.FunctionScope | None = None, + options: converter.ConversionOptions | None = None, ) -> Any: # Use copy to avoid mutating the underlying keywords. new_kwargs = f.keywords.copy() @@ -275,7 +277,7 @@ def _try_convert_actual( effective_args: tuple, kwargs: dict, options: converter.ConversionOptions, -) -> tuple[Callable, Optional[Exception]]: +) -> tuple[Callable, Exception | None]: converted_f = None exc = None try: diff --git a/src/braket/experimental/autoqasm/types/conversions.py b/src/braket/experimental/autoqasm/types/conversions.py index 2c997e0f..6a7c9ca2 100644 --- a/src/braket/experimental/autoqasm/types/conversions.py +++ b/src/braket/experimental/autoqasm/types/conversions.py @@ -13,9 +13,11 @@ """Type conversions between Python and the autoqasm representation for types.""" +from __future__ import annotations + import typing from functools import singledispatch -from typing import Any, Union +from typing import Any import numpy as np import oqpy @@ -77,11 +79,11 @@ def var_type_from_ast_type(ast_type: ast.ClassicalType) -> type: raise NotImplementedError -def var_type_from_oqpy(expr_or_var: Union[oqpy.base.OQPyExpression, oqpy.base.Var]) -> type: +def var_type_from_oqpy(expr_or_var: oqpy.base.OQPyExpression | oqpy.base.Var) -> type: """Returns the AutoQASM variable type corresponding to the provided OQPy object. Args: - expr_or_var (Union[OQPyExpression, Var]): An OQPy expression or variable. + expr_or_var (OQPyExpression | Var): An OQPy expression or variable. Returns: type: The corresponding AutoQASM variable type. @@ -125,13 +127,13 @@ def _(node: bool): @wrap_value.register(int) @wrap_value.register(np.integer) -def _(node: Union[int, np.integer]): +def _(node: int | np.integer): return aq_types.IntVar(node) @wrap_value.register(float) @wrap_value.register(np.floating) -def _(node: Union[float, np.floating]): +def _(node: float | np.floating): return aq_types.FloatVar(node) diff --git a/src/braket/experimental/autoqasm/types/types.py b/src/braket/experimental/autoqasm/types/types.py index f062eaaf..2412323b 100644 --- a/src/braket/experimental/autoqasm/types/types.py +++ b/src/braket/experimental/autoqasm/types/types.py @@ -16,7 +16,7 @@ from __future__ import annotations from collections.abc import Iterable -from typing import Any, List, Optional, Union, get_args +from typing import Any, List, Union, get_args import oqpy import oqpy.base @@ -51,7 +51,7 @@ def is_qasm_type(val: Any) -> bool: return isinstance(val, qasm_types) -def make_annotations_list(annotations: Optional[str | Iterable[str]]) -> List[str]: +def make_annotations_list(annotations: str | Iterable[str] | None) -> List[str]: return [annotations] if isinstance(annotations, str) else annotations or [] @@ -76,17 +76,17 @@ class Range(oqpy.Range): def __init__( self, start: int, - stop: Optional[int] = None, - step: Optional[int] = 1, - annotations: Optional[str | Iterable[str]] = None, + stop: int | None = None, + step: int | None = 1, + annotations: str | Iterable[str] | None = None, ): """Creates a range definition. Args: start (int): Start of the range. - stop (Optional[int]): End of the range. Defaults to None. - step (Optional[int]): Step of the range. Defaults to 1. - annotations (Optional[str | Iterable[str]]): Annotations for the range. + stop (int | None): End of the range. Defaults to None. + step (int | None): Step of the range. Defaults to 1. + annotations (str | Iterable[str] | None): Annotations for the range. """ if stop is None: stop = start @@ -96,7 +96,7 @@ def __init__( class ArrayVar(oqpy.ArrayVar): - def __init__(self, *args, annotations: Optional[str | Iterable[str]] = None, **kwargs): + def __init__(self, *args, annotations: str | Iterable[str] | None = None, **kwargs): if ( program.get_program_conversion_context().subroutines_processing or not program.get_program_conversion_context().at_function_root_scope @@ -111,7 +111,7 @@ def __init__(self, *args, annotations: Optional[str | Iterable[str]] = None, **k class BitVar(oqpy.BitVar): - def __init__(self, *args, annotations: Optional[str | Iterable[str]] = None, **kwargs): + def __init__(self, *args, annotations: str | Iterable[str] | None = None, **kwargs): super(BitVar, self).__init__( *args, annotations=make_annotations_list(annotations), **kwargs ) @@ -122,7 +122,7 @@ def __init__(self, *args, annotations: Optional[str | Iterable[str]] = None, **k class BoolVar(oqpy.BoolVar): - def __init__(self, *args, annotations: Optional[str | Iterable[str]] = None, **kwargs): + def __init__(self, *args, annotations: str | Iterable[str] | None = None, **kwargs): super(BoolVar, self).__init__( *args, annotations=make_annotations_list(annotations), **kwargs ) @@ -130,7 +130,7 @@ def __init__(self, *args, annotations: Optional[str | Iterable[str]] = None, **k class FloatVar(oqpy.FloatVar): - def __init__(self, *args, annotations: Optional[str | Iterable[str]] = None, **kwargs): + def __init__(self, *args, annotations: str | Iterable[str] | None = None, **kwargs): super(FloatVar, self).__init__( *args, annotations=make_annotations_list(annotations), **kwargs ) @@ -138,7 +138,7 @@ def __init__(self, *args, annotations: Optional[str | Iterable[str]] = None, **k class IntVar(oqpy.IntVar): - def __init__(self, *args, annotations: Optional[str | Iterable[str]] = None, **kwargs): + def __init__(self, *args, annotations: str | Iterable[str] | None = None, **kwargs): super(IntVar, self).__init__( *args, annotations=make_annotations_list(annotations), **kwargs ) 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 1099/1165] 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 fa49d9b0..aca79d59 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 f2621de5..6c0a9e98 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 1100/1165] 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 df17c03c..b93b2dfa 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 1101/1165] 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 46d6e3b0..a17864ed 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 3f4918a1..61fcd38b 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 00000000..d31555ba --- /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 64a3556c..031c2f5e 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 9b85c30e..83763a9a 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 dec914ba..b64d4e00 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 00000000..b7fef275 --- /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 88e32d92..7724d907 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 ecaad12b..9b34d662 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 00000000..8911da5e --- /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 96df634c..f78b4e74 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 8c0f8d9a..43dd06db 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 1102/1165] 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 b8a9aa14..fa01b546 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 9c4e6290..8fd99833 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 1103/1165] 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 8fd99833..2721571f 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 790ede5fe070d8c14621531a2b3641c5518e80a6 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 2 Apr 2024 16:39:43 -0400 Subject: [PATCH 1104/1165] fix: iterative variable assignments in AutoQASM (#930) --- .../autoqasm/operators/assignments.py | 27 +++++----- .../experimental/autoqasm/program/program.py | 21 +++++++- .../braket/experimental/autoqasm/test_api.py | 16 +++--- .../experimental/autoqasm/test_converters.py | 7 ++- .../experimental/autoqasm/test_operators.py | 52 +++++++++++++++++-- .../experimental/autoqasm/test_parameters.py | 13 +++-- .../experimental/autoqasm/test_types.py | 8 +-- 7 files changed, 106 insertions(+), 38 deletions(-) diff --git a/src/braket/experimental/autoqasm/operators/assignments.py b/src/braket/experimental/autoqasm/operators/assignments.py index 0419399c..f85ddecd 100644 --- a/src/braket/experimental/autoqasm/operators/assignments.py +++ b/src/braket/experimental/autoqasm/operators/assignments.py @@ -144,19 +144,20 @@ def assign_stmt(target_name: str, value: Any) -> Any: value = types.wrap_value(value) - if not isinstance(value, oqpy.base.Var): - return value - - if is_target_name_used: + if is_target_name_used and isinstance(value, (oqpy.base.Var, oqpy.base.OQPyExpression)): target = _get_oqpy_program_variable(target_name) _validate_assignment_types(target, value) - else: + elif isinstance(value, oqpy.base.Var): target = copy.copy(value) target.init_expression = None target.name = target_name + else: + return value oqpy_program = program_conversion_context.get_oqpy_program() - if is_value_name_used or value.init_expression is None: + + value_init_expression = value.init_expression if isinstance(value, oqpy.base.Var) else None + if is_value_name_used or value_init_expression is None: # Directly assign the value to the target. # For example: # a = b; @@ -170,17 +171,17 @@ def assign_stmt(target_name: str, value: Any) -> Any: # For example: # int[32] a = 10; # where `a` is at the root scope of the function (not inside any if/for/while block). - target.init_expression = value.init_expression - oqpy_program.declare(target) + target.init_expression = value_init_expression + oqpy_program._add_var(target) else: - # Set to `value.init_expression` to avoid declaring an unnecessary variable. + # Set to `value_init_expression` to avoid declaring an unnecessary variable. # The variable will be set in the current scope and auto-declared at the root scope. # For example, the `a = 1` and `a = 0` statements in the following: # int[32] a; # if (b == True) { a = 1; } # else { a = 0; } # where `b` is previously declared. - oqpy_program.set(target, value.init_expression) + oqpy_program.set(target, value_init_expression) return target @@ -211,12 +212,14 @@ def _validate_assignment_types(var1: oqpy.base.Var, var2: oqpy.base.Var) -> None "Variables in assignment statements must have the same type" ) - if isinstance(var1, oqpy.ArrayVar): + if isinstance(var1, oqpy.ArrayVar) and isinstance(var2, oqpy.ArrayVar): if var1.dimensions != var2.dimensions: raise errors.InvalidAssignmentStatement( "Arrays in assignment statements must have the same dimensions" ) - elif isinstance(var1, oqpy.classical_types._SizedVar): + elif isinstance(var1, oqpy.classical_types._SizedVar) and isinstance( + var2, oqpy.classical_types._SizedVar + ): var1_size = var1.size or 1 var2_size = var2.size or 1 if var1_size != var2_size: diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index 63da3b30..f19612d0 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -25,6 +25,7 @@ import oqpy.base import pygments +from openpulse import ast from openqasm_pygments import OpenQASM3Lexer from pygments.formatters.terminal import TerminalFormatter from sympy import Symbol @@ -509,10 +510,26 @@ def add_io_declarations(self) -> None: root_oqpy_program.undeclared_vars[parameter.name]._needs_declaration = True else: root_oqpy_program._add_var(parameter) + for parameter_name, parameter in self._output_parameters.items(): # Before adding the output variable to the program, remove any existing reference - root_oqpy_program.undeclared_vars.pop(parameter_name, None) - root_oqpy_program.declared_vars.pop(parameter_name, None) + popped_undeclared = root_oqpy_program.undeclared_vars.pop(parameter_name, None) + popped_declared = root_oqpy_program.declared_vars.pop(parameter_name, None) + + # Verify that we didn't find it in both lists + assert popped_undeclared is None or popped_declared is None + + popped = popped_undeclared if popped_undeclared is not None else popped_declared + if popped is not None and popped.init_expression is not None: + # Add an assignment statement to the beginning of the program to initialize + # the output parameter to the desired value. + # TODO: This logic uses oqpy internals - should it be moved into oqpy? + init_stmt = ast.ClassicalAssignment( + ast.Identifier(name=parameter_name), + ast.AssignmentOperator["="], + oqpy.base.to_ast(root_oqpy_program, popped.init_expression), + ) + root_oqpy_program._state.body.insert(0, init_stmt) parameter.name = parameter_name root_oqpy_program._add_var(parameter) diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index 6c7e7b56..8cc8d6c1 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -251,8 +251,8 @@ def bell_measurement_declared() -> None: def test_bell_measurement_declared(bell_measurement_declared) -> None: expected = """OPENQASM 3.0; -qubit[2] __qubits__; bit[2] c = "00"; +qubit[2] __qubits__; h __qubits__[0]; cnot __qubits__[0], __qubits__[1]; bit[2] __bit_1__ = "00"; @@ -863,16 +863,13 @@ def classical_variables_types() -> None: def test_classical_variables_types(classical_variables_types): expected = """OPENQASM 3.0; -bit a = 0; -a = 1; +bit a = 1; int[32] i = 1; bit[2] a_array = "00"; +int[32] b = 15; +float[64] c = 3.4; a_array[0] = 0; -a_array[i] = 1; -int[32] b = 10; -b = 15; -float[64] c = 1.2; -c = 3.4;""" +a_array[i] = 1;""" assert classical_variables_types.build().to_ir() == expected @@ -889,9 +886,8 @@ def prog() -> None: a = b # declared target, declared value # noqa: F841 expected = """OPENQASM 3.0; +int[32] a = 2; int[32] b; -int[32] a = 1; -a = 2; b = a; a = b;""" assert prog.build().to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_converters.py b/test/unit_tests/braket/experimental/autoqasm/test_converters.py index a916f322..534f44e9 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_converters.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_converters.py @@ -54,13 +54,12 @@ def fn() -> None: qasm = program_conversion_context.make_program().to_ir() expected_qasm = """OPENQASM 3.0; -int[32] e; -int[32] a = 5; +int[32] a = 1; float[64] b = 1.2; -a = 1; -e = a; +int[32] e; bool f = false; bool g = true; +e = a; g = f;""" assert qasm == expected_qasm diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py index 78d4d095..5280c032 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_operators.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py @@ -23,7 +23,7 @@ import braket.experimental.autoqasm as aq from braket.experimental.autoqasm import errors from braket.experimental.autoqasm.errors import UnsupportedConditionalExpressionError -from braket.experimental.autoqasm.instructions import cnot, h, measure, x +from braket.experimental.autoqasm.instructions import cnot, h, measure, rx, x @pytest.fixture @@ -162,8 +162,8 @@ def branch_assignment_declared(): a = aq.IntVar(7) # noqa: F841 expected = """OPENQASM 3.0; -bool __bool_1__ = true; int[32] a = 5; +bool __bool_1__ = true; if (__bool_1__) { a = 6; } else { @@ -173,6 +173,52 @@ def branch_assignment_declared(): assert branch_assignment_declared.build().to_ir() == expected +def test_iterative_assignment() -> None: + """Tests a for loop where a variable is updated on each iteration.""" + + @aq.main(num_qubits=3) + def iterative_assignment(): + val = aq.FloatVar(0.5) + for q in aq.qubits: + val = val + measure(q) + rx(0, val) + + expected = """OPENQASM 3.0; +float[64] val = 0.5; +qubit[3] __qubits__; +for int q in [0:3 - 1] { + bit __bit_1__; + __bit_1__ = measure __qubits__[q]; + val = val + __bit_1__; + rx(val) __qubits__[0]; +}""" + + assert iterative_assignment.build().to_ir() == expected + + +def test_iterative_output_assignment() -> None: + """Tests a for loop where an output variable is updated on each iteration.""" + + @aq.main(num_qubits=3) + def iterative_output_assignment(): + val = aq.FloatVar(0.5) + for q in aq.range(3): + val = val + measure(q) + return val + + expected = """OPENQASM 3.0; +output float[64] val; +val = 0.5; +qubit[3] __qubits__; +for int q in [0:3 - 1] { + bit __bit_1__; + __bit_1__ = measure __qubits__[q]; + val = val + __bit_1__; +}""" + + assert iterative_output_assignment.build().to_ir() == expected + + def for_body(i: aq.Qubit) -> None: h(i) @@ -655,9 +701,9 @@ def measure_to_slice(): b0[3] = c expected = """OPENQASM 3.0; +bit[10] b0 = "0000000000"; bit c; qubit[1] __qubits__; -bit[10] b0 = "0000000000"; bit __bit_1__; __bit_1__ = measure __qubits__[0]; c = __bit_1__; diff --git a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py index 3ad96774..bf91b00b 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py @@ -529,13 +529,20 @@ def parametric_explicit(): with pytest.raises(RuntimeError, match="conflicting variables with name alpha"): parametric_explicit.build() + +def test_assignment_to_input_variable_name(): + """Test assigning to overwrite an input variable within the program.""" + @aq.main def parametric(alpha): - alpha = aq.FloatVar(1.2) # noqa: F841 + alpha = aq.FloatVar(1.2) rx(0, alpha) - with pytest.raises(RuntimeError, match="conflicting variables with name alpha"): - parametric.build() + expected = """OPENQASM 3.0; +float[64] alpha = 1.2; +qubit[1] __qubits__; +rx(alpha) __qubits__[0];""" + assert parametric.build().to_ir() == expected def test_binding_variable_fails(): diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index 03083d45..f3bad422 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -159,9 +159,9 @@ def ret_test() -> int: def add(int[32] a, int[32] b) -> int[32] { return a + b; } -output int[32] return_value; int[32] a = 5; int[32] b = 6; +output int[32] return_value; int[32] __int_2__; __int_2__ = add(a, b); return_value = __int_2__;""" @@ -194,8 +194,8 @@ def declare_array(): expected = """OPENQASM 3.0; array[int[32], 3] a = {1, 2, 3}; -a[0] = 11; array[int[32], 3] b = {4, 5, 6}; +a[0] = 11; b[2] = 14; b = a;""" @@ -517,9 +517,9 @@ def main(): expected_qasm = """OPENQASM 3.0; def retval_recursive() -> int[32] { + int[32] retval_ = 1; int[32] __int_1__; __int_1__ = retval_recursive(); - int[32] retval_ = 1; return retval_; } int[32] __int_3__; @@ -543,10 +543,10 @@ def main(): expected_qasm = """OPENQASM 3.0; def retval_recursive() -> int[32] { int[32] a; + int[32] retval_ = 1; int[32] __int_1__; __int_1__ = retval_recursive(); a = __int_1__; - int[32] retval_ = 1; return retval_; } int[32] __int_3__; 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 1105/1165] 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 61fcd38b..a4b48629 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 9b34d662..7680f9b4 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 943da39b0924585206d263a78c1a06a0367cf0e2 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Fri, 5 Apr 2024 14:56:36 -0400 Subject: [PATCH 1106/1165] Fix declaration bug introduced by PR #930 (#938) --- .../autoqasm/operators/assignments.py | 2 +- .../experimental/autoqasm/program/program.py | 11 +++++++++++ .../braket/experimental/autoqasm/test_api.py | 16 ++++++++++------ .../experimental/autoqasm/test_converters.py | 10 ++++++---- .../experimental/autoqasm/test_operators.py | 6 +++--- .../experimental/autoqasm/test_parameters.py | 11 ++--------- .../braket/experimental/autoqasm/test_types.py | 8 ++++---- 7 files changed, 37 insertions(+), 27 deletions(-) diff --git a/src/braket/experimental/autoqasm/operators/assignments.py b/src/braket/experimental/autoqasm/operators/assignments.py index f85ddecd..7217513a 100644 --- a/src/braket/experimental/autoqasm/operators/assignments.py +++ b/src/braket/experimental/autoqasm/operators/assignments.py @@ -172,7 +172,7 @@ def assign_stmt(target_name: str, value: Any) -> Any: # int[32] a = 10; # where `a` is at the root scope of the function (not inside any if/for/while block). target.init_expression = value_init_expression - oqpy_program._add_var(target) + oqpy_program.declare(target) else: # Set to `value_init_expression` to avoid declaring an unnecessary variable. # The variable will be set in the current scope and auto-declared at the root scope. diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index f19612d0..b6af1894 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -519,6 +519,17 @@ def add_io_declarations(self) -> None: # Verify that we didn't find it in both lists assert popped_undeclared is None or popped_declared is None + # Remove the existing declaration statement, if any + if popped_declared is not None: + declarations = [ + stmt + for stmt in root_oqpy_program._state.body + if isinstance(stmt, ast.ClassicalDeclaration) + and stmt.identifier.name == parameter_name + ] + assert len(declarations) == 1 + root_oqpy_program._state.body.remove(declarations[0]) + popped = popped_undeclared if popped_undeclared is not None else popped_declared if popped is not None and popped.init_expression is not None: # Add an assignment statement to the beginning of the program to initialize diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index 8cc8d6c1..6c7e7b56 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -251,8 +251,8 @@ def bell_measurement_declared() -> None: def test_bell_measurement_declared(bell_measurement_declared) -> None: expected = """OPENQASM 3.0; -bit[2] c = "00"; qubit[2] __qubits__; +bit[2] c = "00"; h __qubits__[0]; cnot __qubits__[0], __qubits__[1]; bit[2] __bit_1__ = "00"; @@ -863,13 +863,16 @@ def classical_variables_types() -> None: def test_classical_variables_types(classical_variables_types): expected = """OPENQASM 3.0; -bit a = 1; +bit a = 0; +a = 1; int[32] i = 1; bit[2] a_array = "00"; -int[32] b = 15; -float[64] c = 3.4; a_array[0] = 0; -a_array[i] = 1;""" +a_array[i] = 1; +int[32] b = 10; +b = 15; +float[64] c = 1.2; +c = 3.4;""" assert classical_variables_types.build().to_ir() == expected @@ -886,8 +889,9 @@ def prog() -> None: a = b # declared target, declared value # noqa: F841 expected = """OPENQASM 3.0; -int[32] a = 2; int[32] b; +int[32] a = 1; +a = 2; b = a; a = b;""" assert prog.build().to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_converters.py b/test/unit_tests/braket/experimental/autoqasm/test_converters.py index 534f44e9..47dffdd5 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_converters.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_converters.py @@ -38,7 +38,7 @@ def test_assignment(program_ctx: ag_ctx.ControlStatusCtx) -> None: def fn() -> None: """user program to test""" a = aq.IntVar(5) # noqa: F841 - b = aq.FloatVar(1.2) # noqa: F841 + b = a # noqa: F841 c = 123 # noqa: F841 d = (0.123, "foo") # noqa: F841 a = aq.IntVar(1) # noqa: F841 @@ -54,12 +54,14 @@ def fn() -> None: qasm = program_conversion_context.make_program().to_ir() expected_qasm = """OPENQASM 3.0; -int[32] a = 1; -float[64] b = 1.2; +int[32] b; int[32] e; +int[32] a = 5; +b = a; +a = 1; +e = a; bool f = false; bool g = true; -e = a; g = f;""" assert qasm == expected_qasm diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/braket/experimental/autoqasm/test_operators.py index 5280c032..4d224428 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_operators.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_operators.py @@ -162,8 +162,8 @@ def branch_assignment_declared(): a = aq.IntVar(7) # noqa: F841 expected = """OPENQASM 3.0; -int[32] a = 5; bool __bool_1__ = true; +int[32] a = 5; if (__bool_1__) { a = 6; } else { @@ -184,8 +184,8 @@ def iterative_assignment(): rx(0, val) expected = """OPENQASM 3.0; -float[64] val = 0.5; qubit[3] __qubits__; +float[64] val = 0.5; for int q in [0:3 - 1] { bit __bit_1__; __bit_1__ = measure __qubits__[q]; @@ -701,9 +701,9 @@ def measure_to_slice(): b0[3] = c expected = """OPENQASM 3.0; -bit[10] b0 = "0000000000"; bit c; qubit[1] __qubits__; +bit[10] b0 = "0000000000"; bit __bit_1__; __bit_1__ = measure __qubits__[0]; c = __bit_1__; diff --git a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py index bf91b00b..d024eaa6 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_parameters.py @@ -529,20 +529,13 @@ def parametric_explicit(): with pytest.raises(RuntimeError, match="conflicting variables with name alpha"): parametric_explicit.build() - -def test_assignment_to_input_variable_name(): - """Test assigning to overwrite an input variable within the program.""" - @aq.main def parametric(alpha): alpha = aq.FloatVar(1.2) rx(0, alpha) - expected = """OPENQASM 3.0; -float[64] alpha = 1.2; -qubit[1] __qubits__; -rx(alpha) __qubits__[0];""" - assert parametric.build().to_ir() == expected + with pytest.raises(RuntimeError, match="conflicting variables with name alpha"): + parametric.build() def test_binding_variable_fails(): diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/braket/experimental/autoqasm/test_types.py index f3bad422..03083d45 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_types.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_types.py @@ -159,9 +159,9 @@ def ret_test() -> int: def add(int[32] a, int[32] b) -> int[32] { return a + b; } +output int[32] return_value; int[32] a = 5; int[32] b = 6; -output int[32] return_value; int[32] __int_2__; __int_2__ = add(a, b); return_value = __int_2__;""" @@ -194,8 +194,8 @@ def declare_array(): expected = """OPENQASM 3.0; array[int[32], 3] a = {1, 2, 3}; -array[int[32], 3] b = {4, 5, 6}; a[0] = 11; +array[int[32], 3] b = {4, 5, 6}; b[2] = 14; b = a;""" @@ -517,9 +517,9 @@ def main(): expected_qasm = """OPENQASM 3.0; def retval_recursive() -> int[32] { - int[32] retval_ = 1; int[32] __int_1__; __int_1__ = retval_recursive(); + int[32] retval_ = 1; return retval_; } int[32] __int_3__; @@ -543,10 +543,10 @@ def main(): expected_qasm = """OPENQASM 3.0; def retval_recursive() -> int[32] { int[32] a; - int[32] retval_ = 1; int[32] __int_1__; __int_1__ = retval_recursive(); a = __int_1__; + int[32] retval_ = 1; return retval_; } int[32] __int_3__; 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 1107/1165] 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 a17864ed..852dfba8 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 7680f9b4..51752e3d 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 1108/1165] 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 fa01b546..e125c940 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 2721571f..7a807475 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 1109/1165] 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 7a807475..5919ee26 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 1110/1165] 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 bdc3e92f..aeb96986 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 1111/1165] 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 e125c940..d1aa5586 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 5919ee26..15c11b7c 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 1112/1165] 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 15c11b7c..8692940b 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 1113/1165] 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 2a8193e5..a2548fc6 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 6c0a9e98..34d22016 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 faee13fd..69fcfdaf 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 1114/1165] 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 b93b2dfa..3bfb1673 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 1115/1165] 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 d1aa5586..c97490e5 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 8692940b..51f057a0 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 1116/1165] 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 51f057a0..e9af02fb 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 1117/1165] 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 00000000..574b985d --- /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 7bed1fe8..34e24191 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 1118/1165] 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 c97490e5..3f8fb890 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 e9af02fb..158bc4c4 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 1119/1165] 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 158bc4c4..f2ce90f8 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 dbd65a9e053afb7225186ffd3385703060894d35 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Wed, 10 Apr 2024 12:20:43 -0400 Subject: [PATCH 1120/1165] Update mcm-sim commit hash --- setup.py | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 0f04a7d4..8714e42c 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ # to get the version of the simulator that supports the mcm=True argument for Monte Carlo # simulation of mid-circuit measurement, which AutoQASM requires. # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main. - "amazon-braket-default-simulator @ git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git@7245414b79856a03ac0507f86f1eae92ff697cd0#egg=amazon-braket-default-simulator", # noqa E501 + "amazon-braket-default-simulator @ git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git@fb1d2de5f35b784dd098734ce3bcb016b1308748#egg=amazon-braket-default-simulator", # noqa E501 "oqpy~=0.3.5", "backoff", "boltons", diff --git a/tox.ini b/tox.ini index 1121c477..446cc393 100644 --- a/tox.ini +++ b/tox.ini @@ -149,4 +149,4 @@ commands = 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@7245414b79856a03ac0507f86f1eae92ff697cd0 # mcm-sim branch + git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git@fb1d2de5f35b784dd098734ce3bcb016b1308748 # mcm-sim branch 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 1121/1165] 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 852dfba8..4371637d 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 51752e3d..d7f6568d 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 1122/1165] 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 3f8fb890..9384714f 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 f2ce90f8..64b83764 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 1123/1165] 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 64b83764..7d0928a7 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 1124/1165] 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 34d22016..821748ef 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 8a9fd266..5bf3cc61 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 8d8cce10..af02471e 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 aeb96986..65cc2a07 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 0249b875..4c4f9467 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 23cca78b..be2d6fdb 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 1125/1165] 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 9384714f..ca418c44 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 7d0928a7..279cf689 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 1126/1165] 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 279cf689..5395acb5 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 1127/1165] 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 821748ef..12bec6f0 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 a4b48629..9bac39a6 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 031c2f5e..6622b346 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 7724d907..aec87556 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 d7f6568d..b29f3b99 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 f78b4e74..268c53a9 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 1128/1165] 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 ca418c44..6449e1be 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 5395acb5..5fd378c3 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 1129/1165] 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 5fd378c3..292d3ff7 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 1130/1165] 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 3bfb1673..84ff3de0 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 1131/1165] 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 6449e1be..c3956a4d 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 292d3ff7..8bdbbfaf 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 1132/1165] 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 8bdbbfaf..292d3ff7 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 1133/1165] 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 f6c6430f..cbf01838 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 9a473fd9..1522b9d6 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 574b985d..1906ce83 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 462f0e36..92637fe0 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 a558bec7..afdabd72 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 4212ba33..2ff6714c 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 d84f3a92..920f2cc2 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 0f99ca65..c5a26334 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 1134/1165] 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 c3956a4d..8640a1db 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 b37e87e3..4a16c9cd 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 75361f17..004e2bfe 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 d55de4e6..9515cbfa 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 43780aa3..390145ae 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 54f54415..c386469b 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 4d0d06b5..964fabe7 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 d2f2099f..6a1bd1ce 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 9bac39a6..bf0ee07d 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 7617493c..69ff6625 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 6622b346..66ea4191 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 fe256f1d..0cad544e 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 6dda8130..89f30cac 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 eb8ea0f2..e1790fd2 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 8d64888e..15cb8d1f 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 3106afc4..a633d318 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 c8bfa265..e1dda5a3 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 9e1779cb..0739724e 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 c4432dcf..04aeaff1 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 ade8a0f7..0bc110b2 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 afdabd72..0023f32f 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 2f77eec6..47c13625 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 1135/1165] 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 8640a1db..4731b026 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 292d3ff7..1b29b8f3 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 1136/1165] 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 1b29b8f3..5d339221 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 1137/1165] 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 1906ce83..39a47592 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 65cc2a07..83178c12 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 4c4f9467..8768dce6 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 1138/1165] 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 4731b026..2a86ca35 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 5d339221..6ce89b9b 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 1139/1165] 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 6ce89b9b..15eb8302 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 1140/1165] 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 39a47592..59732d4f 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 1141/1165] 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 2a86ca35..6a91e3d8 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 15eb8302..79184888 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 1142/1165] 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 79184888..07aeada4 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 1143/1165] 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 79545ce2..933315e8 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 5b4c4fab..49447e58 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 84ff3de0..bc4c2f9e 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 9537460e..78bb7eed 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 b29f3b99..c0c58ad3 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 1b6ac56c..3a7e621d 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 1144/1165] 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 933315e8..fe9662c0 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 ed79a0d6..04595aed 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 e824a6af..b6ca2319 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 7362a17b..0df22f51 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 1c79e803..b03bd1ef 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 a2548fc6..8a16ca23 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 4a16c9cd..23de8a04 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 004e2bfe..25c088ab 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 8c9eb90f..64c6939a 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 9c7f3aca..9a0a8efb 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 5c7db93a..1aa57cc4 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 87c2e1f7..93aac757 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 20525474..31493b78 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 8d996f4c..54d10b54 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 d9dbb5b6..d75c4f03 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 12bec6f0..6763c4a5 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 1d47a66b..24d4fc9a 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 c7df1fcf..49efa0d3 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 59732d4f..00b42021 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 390145ae..70fc3c07 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 3c6dbe66..37eb6760 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 c386469b..17ae2925 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 964fabe7..300963a6 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 6a1bd1ce..16a021e7 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 bf0ee07d..94d53248 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 bc4c2f9e..ee5ea684 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 66ea4191..b2dee415 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 0cad544e..e5d4fdf8 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 89f30cac..a73b7f33 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 1db40aa5..4dceeb4f 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 63625491..88921134 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 bc86e53b..456867ce 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 4ea56613..e8a60307 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 1a212650..5cb510f2 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 e1790fd2..26594ca6 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 904f7ac7..a8829f1a 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 2d5ccdb6..ae4f36a0 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 068c4171..b706e182 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 15cb8d1f..10c22808 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 0b73c5e7..325fa8f4 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 a633d318..4a7c9565 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 e1dda5a3..ad30b34a 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 83763a9a..f261b00b 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 0739724e..85567de2 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 3f2a28e4..dbc7b6b3 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 69fcfdaf..1dec56d3 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 c443d1b4..d850ee85 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 ad42006d..6d7d1836 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 3cd622e3..77f5f43d 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 af680615..4dd15607 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 04aeaff1..6d9d08f4 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 57c1f365..65cef387 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 5e2f12f8..9aa7dfac 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 b2d12fe3..8f5d3dcd 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 8a5a5333..e8da4ff8 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 677a3a44..41cbf049 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 82142a58..1ff5b4d4 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 98905dc5..3c4a01b5 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 78fbe45b..1f3a69e7 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 f7ec9d3b..d2dcf65e 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 41c541da..1750275a 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 3d7adb4a..40a6d25d 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 a788b38c..9d43127a 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 9ca43050..915d187a 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 9064b942..0de8e8e3 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 4be98b64..4c91ebd2 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 0bc110b2..7bfb57eb 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 b64d4e00..81f90ae7 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 0023f32f..67797d6c 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 41f20da8..901cc819 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 42303465..d2a74e6c 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 8ceae35c..16c001f3 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 8b2eae75..be0e49a8 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 74b40afb..540c09f6 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 42545854..06a92616 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 d9d6c3d4..aaca559f 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 a85ca6eb..7f9e6179 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 8df2118c..67ca9822 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 6e789ec9..16a72da7 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 56d23b2e..fb0b309f 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 ae33d402..4c5252c0 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 c0c58ad3..71eecd1f 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 3a7e621d..0b9ce52d 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 212e6594..1d04627e 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 982649f6..ed45b0aa 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 a9bd3f5a..5fffba43 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 b7e9c201..38689398 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 79f917af..b6430d4d 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 ed58d5d6..3a26e8c8 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 f284b5a6..a7e8bfe1 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 fdaff840..247d1873 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 6a5e2728..a4ac78f2 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 592af684..b1739d87 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 8cd1fbca..d12a29b0 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 c4199e81..56f02aa1 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 0c56d354..34f989c0 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 98f89cf8..0c04a5d9 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 1fd8d721..1d730b96 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 29d21e58..03f68b7c 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 43dd06db..5447bd8b 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 6b583c60..aca0aa20 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 c5a26334..729813aa 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 1145/1165] 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 6a91e3d8..4cb8ae8a 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 07aeada4..d1dfdba0 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 1146/1165] 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 d1dfdba0..fec1b4f0 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 1147/1165] 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 682f71f5..83be87eb 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 d0b3a341..3be348f3 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 16a021e7..b4cdfcd3 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 00000000..4ffcb8fc --- /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 98b87f07..f5efd722 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 00000000..332421e7 --- /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 1148/1165] 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 94d53248..f15e0364 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 72af596de60c97343313cbde95177d1e36e2bfd5 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Mon, 6 May 2024 09:14:19 -0400 Subject: [PATCH 1149/1165] Raise an error when a MainProgram is nested inside another MainProgram (#962) --- src/braket/experimental/autoqasm/errors.py | 4 ++++ src/braket/experimental/autoqasm/program/program.py | 13 +++++++++---- .../experimental/autoqasm/transpiler/transpiler.py | 1 - .../braket/experimental/autoqasm/test_api.py | 7 +++---- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/braket/experimental/autoqasm/errors.py b/src/braket/experimental/autoqasm/errors.py index 98562882..2b259f8e 100644 --- a/src/braket/experimental/autoqasm/errors.py +++ b/src/braket/experimental/autoqasm/errors.py @@ -118,3 +118,7 @@ class UnsupportedSubroutineReturnType(AutoQasmError): class NameConflict(AutoQasmError): """Name conflict between user-named variables.""" + + +class NestedMainProgramError(AutoQasmError): + """Main program nested inside another main program.""" diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/braket/experimental/autoqasm/program/program.py index b6af1894..fefcd229 100644 --- a/src/braket/experimental/autoqasm/program/program.py +++ b/src/braket/experimental/autoqasm/program/program.py @@ -112,6 +112,11 @@ def build(self, device: Device | str | None = None) -> Program: Returns: Program: The generated AutoQASM program. """ + if in_active_program_conversion_context(): + raise errors.NestedMainProgramError( + "Cannot build an AutoQASM program from within another AutoQASM program." + ) + if isinstance(device, str): device = AwsDevice(device) @@ -440,13 +445,13 @@ def _free_symbol_names(expr: FreeParameterExpression) -> Iterable[str]: def register_input_parameter( self, name: str, - type_: float | int | bool = float, + param_type: float | int | bool = float, ) -> None: """Register an input parameter if it has not already been registered. Args: name (str): The name of the parameter to register with the program. - type_ (float | int | bool): The type of the parameter to register + param_type (float | int | bool): The type of the parameter to register with the program. Default: float. Raises: @@ -454,9 +459,9 @@ def register_input_parameter( """ # TODO (#814): add type validation against existing inputs if name not in self._input_parameters: - aq_type = aq_types.map_parameter_type(type_) + aq_type = aq_types.map_parameter_type(param_type) if aq_type not in [oqpy.FloatVar, oqpy.IntVar, oqpy.BoolVar]: - raise NotImplementedError(type_) + raise NotImplementedError(param_type) # In case a FreeParameter has already created a FloatVar somewhere else, # we use need_declaration=False to avoid OQPy raising name conflict errors. diff --git a/src/braket/experimental/autoqasm/transpiler/transpiler.py b/src/braket/experimental/autoqasm/transpiler/transpiler.py index 4696f86b..609e91b5 100644 --- a/src/braket/experimental/autoqasm/transpiler/transpiler.py +++ b/src/braket/experimental/autoqasm/transpiler/transpiler.py @@ -121,7 +121,6 @@ def transform_ast( Lambda | FunctionDef: The root of the transformed AST. """ unsupported_features_checker.verify(node) - # TODO (#809): add forbidden_aq_program_usage_checker.verify(node) node = self._initial_analysis(node, ctx) # autograph converters diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/braket/experimental/autoqasm/test_api.py index 6c7e7b56..9a9a5b32 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_api.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_api.py @@ -1055,9 +1055,8 @@ def bell() -> None: bell() -@pytest.mark.xfail(reason="(#809) will fix by adding a checker to the transpiler") def test_main_from_main(): - """Can't call main from main!""" + """Can't build main from within another main!""" @aq.main(num_qubits=2) def bell(q0: int, q1: int) -> None: @@ -1066,9 +1065,9 @@ def bell(q0: int, q1: int) -> None: @aq.main def main(): - bell(0, 1) + bell.build() - with pytest.raises(errors.AutoQasmTypeError): + with pytest.raises(errors.NestedMainProgramError): main.build() From d62e288961af1b9a42b14667e1167fca301e2708 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Mon, 6 May 2024 12:04:13 -0400 Subject: [PATCH 1150/1165] feat: Add gate modifiers and additional supported gates to AutoQASM (#958) * Implement control, control_state, power gate modifiers * Enable gate modifiers on custom gates * Add prx gate * Add physical qubit test case * Update type checks * Use BasisStateInput for control_state * Simplify control_state logic * Add docstrings and improve test parameterizations --- src/braket/experimental/autoqasm/api.py | 5 +- .../autoqasm/instructions/gates.py | 157 ++++++++++++++---- .../autoqasm/instructions/instructions.py | 85 +++++++++- .../autoqasm/instructions/measurements.py | 8 +- .../autoqasm/test_gate_decorator.py | 21 +++ .../autoqasm/test_gate_definitions.py | 75 +++++++++ 6 files changed, 310 insertions(+), 41 deletions(-) diff --git a/src/braket/experimental/autoqasm/api.py b/src/braket/experimental/autoqasm/api.py index 9319866a..da39354c 100644 --- a/src/braket/experimental/autoqasm/api.py +++ b/src/braket/experimental/autoqasm/api.py @@ -538,7 +538,7 @@ def _convert_gate( ) qubit_args = [args[i] for i in gate_args.qubit_indices] angle_args = [args[i] for i in gate_args.angle_indices] - aq_instructions.instructions._qubit_instruction(gate_name, qubit_args, *angle_args) + aq_instructions.instructions._qubit_instruction(gate_name, qubit_args, *angle_args, **kwargs) def _wrap_for_oqpy_gate( @@ -574,6 +574,9 @@ def _get_gate_args(f: Callable) -> aq_program.GateArgs: gate_args = aq_program.GateArgs() sig = inspect.signature(f) for param in sig.parameters.values(): + if param.kind == param.VAR_KEYWORD: + continue + if param.annotation is param.empty: raise errors.MissingParameterTypeError( f'Parameter "{param.name}" for gate "{f.__name__}" ' diff --git a/src/braket/experimental/autoqasm/instructions/gates.py b/src/braket/experimental/autoqasm/instructions/gates.py index b8b5e28f..f7556c24 100644 --- a/src/braket/experimental/autoqasm/instructions/gates.py +++ b/src/braket/experimental/autoqasm/instructions/gates.py @@ -29,6 +29,7 @@ def ccnot( control_0: QubitIdentifierType, control_1: QubitIdentifierType, target: QubitIdentifierType, + **kwargs, ) -> None: """CCNOT gate or Toffoli gate. @@ -38,12 +39,13 @@ def ccnot( target (QubitIdentifierType): Target qubit. """ - _qubit_instruction("ccnot", [control_0, control_1, target]) + _qubit_instruction("ccnot", [control_0, control_1, target], **kwargs) def cnot( control: QubitIdentifierType, target: QubitIdentifierType, + **kwargs, ) -> None: """Controlled NOT gate. @@ -52,13 +54,14 @@ def cnot( target (QubitIdentifierType): Target qubit. """ - _qubit_instruction("cnot", [control, target]) + _qubit_instruction("cnot", [control, target], **kwargs) def cphaseshift( control: QubitIdentifierType, target: QubitIdentifierType, angle: GateParameterType, + **kwargs, ) -> None: """Controlled phase shift gate. @@ -68,13 +71,14 @@ def cphaseshift( angle (GateParameterType): Rotation angle in radians. """ - _qubit_instruction("cphaseshift", [control, target], angle) + _qubit_instruction("cphaseshift", [control, target], angle, **kwargs) def cphaseshift00( control: QubitIdentifierType, target: QubitIdentifierType, angle: GateParameterType, + **kwargs, ) -> None: """Controlled phase shift gate for phasing the \\|00> state. @@ -84,13 +88,14 @@ def cphaseshift00( angle (GateParameterType): Rotation angle in radians. """ - _qubit_instruction("cphaseshift00", [control, target], angle) + _qubit_instruction("cphaseshift00", [control, target], angle, **kwargs) def cphaseshift01( control: QubitIdentifierType, target: QubitIdentifierType, angle: GateParameterType, + **kwargs, ) -> None: """Controlled phase shift gate for phasing the \\|01> state. @@ -100,13 +105,14 @@ def cphaseshift01( angle (GateParameterType): Rotation angle in radians. """ - _qubit_instruction("cphaseshift01", [control, target], angle) + _qubit_instruction("cphaseshift01", [control, target], angle, **kwargs) def cphaseshift10( control: QubitIdentifierType, target: QubitIdentifierType, angle: GateParameterType, + **kwargs, ) -> None: """Controlled phase shift gate for phasing the \\|10> state. @@ -116,13 +122,14 @@ def cphaseshift10( angle (GateParameterType): Rotation angle in radians. """ - _qubit_instruction("cphaseshift10", [control, target], angle) + _qubit_instruction("cphaseshift10", [control, target], angle, **kwargs) def cswap( control: QubitIdentifierType, target_0: QubitIdentifierType, target_1: QubitIdentifierType, + **kwargs, ) -> None: """Controlled Swap gate. @@ -132,12 +139,13 @@ def cswap( target_1 (QubitIdentifierType): Target qubit 1. """ - _qubit_instruction("cswap", [control, target_0, target_1]) + _qubit_instruction("cswap", [control, target_0, target_1], **kwargs) def cv( control: QubitIdentifierType, target: QubitIdentifierType, + **kwargs, ) -> None: """Controlled Sqrt of NOT gate. @@ -146,12 +154,13 @@ def cv( target (QubitIdentifierType): Target qubit. """ - _qubit_instruction("cv", [control, target]) + _qubit_instruction("cv", [control, target], **kwargs) def cy( control: QubitIdentifierType, target: QubitIdentifierType, + **kwargs, ) -> None: """Controlled Pauli-Y gate. @@ -160,12 +169,13 @@ def cy( target (QubitIdentifierType): Target qubit. """ - _qubit_instruction("cy", [control, target]) + _qubit_instruction("cy", [control, target], **kwargs) def cz( control: QubitIdentifierType, target: QubitIdentifierType, + **kwargs, ) -> None: """Controlled Pauli-Z gate. @@ -174,12 +184,13 @@ def cz( target (QubitIdentifierType): Target qubit. """ - _qubit_instruction("cz", [control, target]) + _qubit_instruction("cz", [control, target], **kwargs) def ecr( target_0: QubitIdentifierType, target_1: QubitIdentifierType, + **kwargs, ) -> None: """An echoed RZX(pi/2) gate. @@ -188,12 +199,26 @@ def ecr( target_1 (QubitIdentifierType): Target qubit 1. """ - _qubit_instruction("ecr", [target_0, target_1]) + _qubit_instruction("ecr", [target_0, target_1], **kwargs) + + +def gphase( + angle: GateParameterType, + **kwargs, +) -> None: + """Global phase gate. + + Args: + angle (GateParameterType): Global phase in radians. + + """ + _qubit_instruction("gphase", [], angle, **kwargs) def gpi( target: QubitIdentifierType, angle: GateParameterType, + **kwargs, ) -> None: """IonQ GPi gate. @@ -202,12 +227,13 @@ def gpi( angle (GateParameterType): Rotation angle in radians. """ - _qubit_instruction("gpi", [target], angle) + _qubit_instruction("gpi", [target], angle, **kwargs) def gpi2( target: QubitIdentifierType, angle: GateParameterType, + **kwargs, ) -> None: """IonQ GPi2 gate. @@ -216,11 +242,12 @@ def gpi2( angle (GateParameterType): Rotation angle in radians. """ - _qubit_instruction("gpi2", [target], angle) + _qubit_instruction("gpi2", [target], angle, **kwargs) def h( target: QubitIdentifierType, + **kwargs, ) -> None: """Hadamard gate. @@ -228,11 +255,12 @@ def h( target (QubitIdentifierType): Target qubit. """ - _qubit_instruction("h", [target]) + _qubit_instruction("h", [target], **kwargs) def i( target: QubitIdentifierType, + **kwargs, ) -> None: """Identity gate. @@ -240,12 +268,13 @@ def i( target (QubitIdentifierType): Target qubit. """ - _qubit_instruction("i", [target]) + _qubit_instruction("i", [target], **kwargs) def iswap( target_0: QubitIdentifierType, target_1: QubitIdentifierType, + **kwargs, ) -> None: """ISwap gate. @@ -254,7 +283,7 @@ def iswap( target_1 (QubitIdentifierType): Target qubit 1. """ - _qubit_instruction("iswap", [target_0, target_1]) + _qubit_instruction("iswap", [target_0, target_1], **kwargs) def ms( @@ -263,6 +292,7 @@ def ms( angle_0: GateParameterType, angle_1: GateParameterType, angle_2: GateParameterType, + **kwargs, ) -> None: """IonQ Mølmer-Sørenson gate. @@ -274,12 +304,13 @@ def ms( angle_2 (GateParameterType): Rotation angle 2 in radians. """ - _qubit_instruction("ms", [target_0, target_1], angle_0, angle_1, angle_2) + _qubit_instruction("ms", [target_0, target_1], angle_0, angle_1, angle_2, **kwargs) def phaseshift( target: QubitIdentifierType, angle: GateParameterType, + **kwargs, ) -> None: """Phase shift gate. @@ -288,13 +319,31 @@ def phaseshift( angle (GateParameterType): Rotation angle in radians. """ - _qubit_instruction("phaseshift", [target], angle) + _qubit_instruction("phaseshift", [target], angle, **kwargs) + + +def prx( + target: QubitIdentifierType, + angle_0: GateParameterType, + angle_1: GateParameterType, + **kwargs, +) -> None: + """PhaseRx gate. + + Args: + target (QubitIdentifierType): Target qubit. + angle_0 (GateParameterType): First angle in radians. + angle_1 (GateParameterType): Second angle in radians. + + """ + _qubit_instruction("prx", [target], angle_0, angle_1, **kwargs) def pswap( target_0: QubitIdentifierType, target_1: QubitIdentifierType, angle: GateParameterType, + **kwargs, ) -> None: """PSwap gate. @@ -304,12 +353,13 @@ def pswap( angle (GateParameterType): Rotation angle in radians. """ - _qubit_instruction("pswap", [target_0, target_1], angle) + _qubit_instruction("pswap", [target_0, target_1], angle, **kwargs) def rx( target: QubitIdentifierType, angle: GateParameterType, + **kwargs, ) -> None: """X-axis rotation gate. @@ -318,12 +368,13 @@ def rx( angle (GateParameterType): Rotation angle in radians. """ - _qubit_instruction("rx", [target], angle) + _qubit_instruction("rx", [target], angle, **kwargs) def ry( target: QubitIdentifierType, angle: GateParameterType, + **kwargs, ) -> None: """Y-axis rotation gate. @@ -332,12 +383,13 @@ def ry( angle (GateParameterType): Rotation angle in radians. """ - _qubit_instruction("ry", [target], angle) + _qubit_instruction("ry", [target], angle, **kwargs) def rz( target: QubitIdentifierType, angle: GateParameterType, + **kwargs, ) -> None: """Z-axis rotation gate. @@ -346,11 +398,12 @@ def rz( angle (GateParameterType): Rotation angle in radians. """ - _qubit_instruction("rz", [target], angle) + _qubit_instruction("rz", [target], angle, **kwargs) def s( target: QubitIdentifierType, + **kwargs, ) -> None: """S gate. @@ -358,11 +411,12 @@ def s( target (QubitIdentifierType): Target qubit. """ - _qubit_instruction("s", [target]) + _qubit_instruction("s", [target], **kwargs) def si( target: QubitIdentifierType, + **kwargs, ) -> None: """Conjugate transpose of S gate. @@ -370,12 +424,13 @@ def si( target (QubitIdentifierType): Target qubit. """ - _qubit_instruction("si", [target]) + _qubit_instruction("si", [target], **kwargs) def swap( target_0: QubitIdentifierType, target_1: QubitIdentifierType, + **kwargs, ) -> None: """Swap gate. @@ -384,11 +439,12 @@ def swap( target_1 (QubitIdentifierType): Target qubit 1. """ - _qubit_instruction("swap", [target_0, target_1]) + _qubit_instruction("swap", [target_0, target_1], **kwargs) def t( target: QubitIdentifierType, + **kwargs, ) -> None: """T gate. @@ -396,11 +452,12 @@ def t( target (QubitIdentifierType): Target qubit. """ - _qubit_instruction("t", [target]) + _qubit_instruction("t", [target], **kwargs) def ti( target: QubitIdentifierType, + **kwargs, ) -> None: """Conjugate transpose of T gate. @@ -408,11 +465,31 @@ def ti( target (QubitIdentifierType): Target qubit. """ - _qubit_instruction("ti", [target]) + _qubit_instruction("ti", [target], **kwargs) + + +def u( + target: QubitIdentifierType, + angle_0: GateParameterType, + angle_1: GateParameterType, + angle_2: GateParameterType, + **kwargs, +) -> None: + """Generalized single-qubit rotation gate. + + Args: + target (QubitIdentifierType): Target qubit. + angle_0 (GateParameterType): Rotation angle theta in radians. + angle_1 (GateParameterType): Rotation angle phi in radians. + angle_2 (GateParameterType): Rotation angle lambda in radians. + + """ + _qubit_instruction("u", [target], angle_0, angle_1, angle_2, **kwargs) def v( target: QubitIdentifierType, + **kwargs, ) -> None: """Square root of not gate. @@ -420,11 +497,12 @@ def v( target (QubitIdentifierType): Target qubit. """ - _qubit_instruction("v", [target]) + _qubit_instruction("v", [target], **kwargs) def vi( target: QubitIdentifierType, + **kwargs, ) -> None: """Conjugate transpose of square root of not gate. @@ -432,11 +510,12 @@ def vi( target (QubitIdentifierType): Target qubit. """ - _qubit_instruction("vi", [target]) + _qubit_instruction("vi", [target], **kwargs) def x( target: QubitIdentifierType, + **kwargs, ) -> None: """Pauli-X gate. @@ -444,13 +523,14 @@ def x( target (QubitIdentifierType): Target qubit. """ - _qubit_instruction("x", [target]) + _qubit_instruction("x", [target], **kwargs) def xx( target_0: QubitIdentifierType, target_1: QubitIdentifierType, angle: GateParameterType, + **kwargs, ) -> None: """Ising XX coupling gate. @@ -460,13 +540,14 @@ def xx( angle (GateParameterType): Rotation angle in radians. """ - _qubit_instruction("xx", [target_0, target_1], angle) + _qubit_instruction("xx", [target_0, target_1], angle, **kwargs) def xy( target_0: QubitIdentifierType, target_1: QubitIdentifierType, angle: GateParameterType, + **kwargs, ) -> None: """XY gates @@ -476,11 +557,12 @@ def xy( angle (GateParameterType): Rotation angle in radians. """ - _qubit_instruction("xy", [target_0, target_1], angle) + _qubit_instruction("xy", [target_0, target_1], angle, **kwargs) def y( target: QubitIdentifierType, + **kwargs, ) -> None: """Pauli-Y gate. @@ -488,13 +570,14 @@ def y( target (QubitIdentifierType): Target qubit. """ - _qubit_instruction("y", [target]) + _qubit_instruction("y", [target], **kwargs) def yy( target_0: QubitIdentifierType, target_1: QubitIdentifierType, angle: GateParameterType, + **kwargs, ) -> None: """Ising YY coupling gate. @@ -504,11 +587,12 @@ def yy( angle (GateParameterType): Rotation angle in radians. """ - _qubit_instruction("yy", [target_0, target_1], angle) + _qubit_instruction("yy", [target_0, target_1], angle, **kwargs) def z( target: QubitIdentifierType, + **kwargs, ) -> None: """Pauli-Z gate. @@ -516,13 +600,14 @@ def z( target (QubitIdentifierType): Target qubit. """ - _qubit_instruction("z", [target]) + _qubit_instruction("z", [target], **kwargs) def zz( target_0: QubitIdentifierType, target_1: QubitIdentifierType, angle: GateParameterType, + **kwargs, ) -> None: """Ising ZZ coupling gate. @@ -532,4 +617,4 @@ def zz( angle (GateParameterType): Rotation angle in radians. """ - _qubit_instruction("zz", [target_0, target_1], angle) + _qubit_instruction("zz", [target_0, target_1], angle, **kwargs) diff --git a/src/braket/experimental/autoqasm/instructions/instructions.py b/src/braket/experimental/autoqasm/instructions/instructions.py index 63e3524d..818465e9 100644 --- a/src/braket/experimental/autoqasm/instructions/instructions.py +++ b/src/braket/experimental/autoqasm/instructions/instructions.py @@ -14,16 +14,51 @@ """Non-unitary instructions that apply to qubits.""" +from __future__ import annotations + +from collections.abc import Iterable from typing import Any +import oqpy + +from braket.circuits.basis_state import BasisState, BasisStateInput from braket.experimental.autoqasm import program as aq_program +from braket.experimental.autoqasm import types as aq_types from braket.experimental.autoqasm.instructions.qubits import _qubit from braket.experimental.autoqasm.types import QubitIdentifierType def _qubit_instruction( - name: str, qubits: list[QubitIdentifierType], *args: Any, is_unitary: bool = True + name: str, + qubits: Iterable[QubitIdentifierType], + *args: Any, + is_unitary: bool = True, + control: QubitIdentifierType | Iterable[QubitIdentifierType] | None = None, + control_state: BasisStateInput | None = None, + power: float | None = None, ) -> None: + """Adds an instruction to the program which acts on a specified set of qubits. + + Args: + name (str): The name of the instruction. + qubits (Iterable[QubitIdentifierType]): The qubits on which the instruction acts. + is_unitary (bool): Whether the instruction represents a unitary operation. Defaults to True. + control (QubitIdentifierType | Iterable[QubitIdentifierType] | None): The qubit or + list of qubits which are being used as the control qubits. If None, an empty list is + used, and the gate will not be controlled on any qubits. Defaults to None. + control_state (BasisStateInput | None): The basis state of the control qubits + that is required in order for the controlled gate to be performed. The order of the + bits in this basis state corresponds to the order of the qubits provided in `control`. + If None, the control state is assumed to be a bitstring of all 1s. Defaults to None. + power (float | None): The power to which the gate should be raised. If None, the gate + will not be raised to any power. Defaults to None. + + Note: + The params `control`, `control_state`, and `power` are frequently passed as kwargs + to this function from built-in gates defined in the `gates` module. This allows the + signatures and docstrings for each built-in gate to be more concise, though at the cost + of not having these gate modifier params explicitly documented for each gate. + """ program_conversion_context = aq_program.get_program_conversion_context() program_conversion_context.validate_gate_targets(qubits, args) @@ -31,8 +66,54 @@ def _qubit_instruction( program_conversion_context.register_gate(name) program_conversion_context.register_args(args) program_mode = aq_program.ProgramMode.UNITARY if is_unitary else aq_program.ProgramMode.NONE + pos_control, neg_control = _get_pos_neg_control(control, control_state) oqpy_program = program_conversion_context.get_oqpy_program(mode=program_mode) - oqpy_program.gate([_qubit(q) for q in qubits], name, *args) + oqpy_program.gate( + [_qubit(q) for q in qubits], + name, + *args, + control=pos_control, + neg_control=neg_control, + exp=power, + ) + + +def _get_pos_neg_control( + control: QubitIdentifierType | Iterable[QubitIdentifierType] | None = None, + control_state: BasisStateInput | None = None, +) -> tuple[list[oqpy.Qubit], list[oqpy.Qubit]]: + """Constructs the list of positive-control and negative-control qubits given + the list of control qubits and the corresponding control state. For the controlled gate + to be performed, all of the positive-control qubits must be in the 1 state, and all of the + negative-control qubits must be in the 0 state. + + Args: + control (QubitIdentifierType | Iterable[QubitIdentifierType] | None): The qubit + or list of qubits which are being used as the control qubits. If None, an empty list is + used. Defaults to None. + control_state (BasisStateInput | None): The basis state of the control qubits + that is required in order for the controlled gate to be performed. The order of the + bits in this basis state corresponds to the order of the qubits provided in `control`. + If None, the control state is assumed to be a bitstring of all 1s. Defaults to None. + + Returns: + tuple[list[Qubit], list[Qubit]]: A tuple of lists of `Qubit` objects, where the first list + contains the positive-control qubits, and the second list contains the negative-control + qubits. The union of the two lists is the same as the list of control qubits. + """ + if control is None: + control = [] + elif aq_types.is_qubit_identifier_type(control): + control = [control] + + if control_state is None: + control_state = [1] * len(control) + else: + control_state = BasisState(control_state, len(control)).as_tuple + + pos_control = [_qubit(q) for i, q in enumerate(control) if control_state[i] == 1] + neg_control = [_qubit(q) for i, q in enumerate(control) if control_state[i] == 0] + return pos_control, neg_control def reset(target: QubitIdentifierType) -> None: diff --git a/src/braket/experimental/autoqasm/instructions/measurements.py b/src/braket/experimental/autoqasm/instructions/measurements.py index 8e9d77bc..0bd33bce 100644 --- a/src/braket/experimental/autoqasm/instructions/measurements.py +++ b/src/braket/experimental/autoqasm/instructions/measurements.py @@ -28,7 +28,11 @@ def my_program(): from braket.experimental.autoqasm import program from braket.experimental.autoqasm import types as aq_types -from braket.experimental.autoqasm.instructions.qubits import _qubit, global_qubit_register +from braket.experimental.autoqasm.instructions.qubits import ( + GlobalQubitRegister, + _qubit, + global_qubit_register, +) def measure( @@ -47,7 +51,7 @@ def measure( if qubits is None: qubits = global_qubit_register() - if isinstance(qubits, str) or not isinstance(qubits, Iterable): + if aq_types.is_qubit_identifier_type(qubits) and not isinstance(qubits, GlobalQubitRegister): qubits = [qubits] oqpy_program = program.get_program_conversion_context().get_oqpy_program() diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py index 67aa97db..2491d543 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py @@ -358,3 +358,24 @@ def subroutine(int[32] q0, int[32] q1) { subroutine(2, 3);""" assert main.build().to_ir() == expected + + +def test_gate_modifiers() -> None: + @aq.gate + def t(q: aq.Qubit): + rz(q, np.pi / 4) + + @aq.main(num_qubits=4) + def main(): + t(1, control=0) + t(2, control=[0, 1], control_state="10", power=0.123) + + expected = """OPENQASM 3.0; +gate t q { + rz(0.7853981633974483) q; +} +qubit[4] __qubits__; +ctrl @ t __qubits__[0], __qubits__[1]; +ctrl @ negctrl @ pow(0.123) @ t __qubits__[0], __qubits__[1], __qubits__[2];""" + + assert main.build().to_ir() == expected diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_definitions.py b/test/unit_tests/braket/experimental/autoqasm/test_gate_definitions.py index 560b3ea3..008d14b8 100644 --- a/test/unit_tests/braket/experimental/autoqasm/test_gate_definitions.py +++ b/test/unit_tests/braket/experimental/autoqasm/test_gate_definitions.py @@ -28,6 +28,7 @@ cy, cz, ecr, + gphase, gpi, gpi2, h, @@ -36,6 +37,7 @@ measure, ms, phaseshift, + prx, pswap, reset, rx, @@ -46,6 +48,7 @@ swap, t, ti, + u, v, vi, x, @@ -109,6 +112,7 @@ def test_bell_with_measure() -> None: (cy, [0, 1], [], "\ncy __qubits__[0], __qubits__[1];"), (cz, [0, 1], [], "\ncz __qubits__[0], __qubits__[1];"), (ecr, [0, 1], [], "\necr __qubits__[0], __qubits__[1];"), + (gphase, [], [0.1], "\ngphase(0.1) ;"), (gpi, [0], [0.1], "\ngpi(0.1) __qubits__[0];"), (gpi2, [0], [0.1], "\ngpi2(0.1) __qubits__[0];"), (h, [0], [], "\nh __qubits__[0];"), @@ -116,6 +120,7 @@ def test_bell_with_measure() -> None: (iswap, [0, 1], [], "\niswap __qubits__[0], __qubits__[1];"), (ms, [0, 1], [0.1, 0.2, 0.3], "\nms(0.1, 0.2, 0.3) __qubits__[0], __qubits__[1];"), (phaseshift, [0], [0.1], "\nphaseshift(0.1) __qubits__[0];"), + (prx, [0], [0.1, 0.2], "\nprx(0.1, 0.2) __qubits__[0];"), (pswap, [0, 1], [0.1], "\npswap(0.1) __qubits__[0], __qubits__[1];"), (rx, [0], [0.1], "\nrx(0.1) __qubits__[0];"), (ry, [0], [0.1], "\nry(0.1) __qubits__[0];"), @@ -125,6 +130,7 @@ def test_bell_with_measure() -> None: (swap, [0, 1], [], "\nswap __qubits__[0], __qubits__[1];"), (t, [0], [], "\nt __qubits__[0];"), (ti, [0], [], "\nti __qubits__[0];"), + (u, [0], [0.1, 0.2, 0.3], "\nu(0.1, 0.2, 0.3) __qubits__[0];"), (v, [0], [], "\nv __qubits__[0];"), (vi, [0], [], "\nvi __qubits__[0];"), (x, [0], [], "\nx __qubits__[0];"), @@ -142,3 +148,72 @@ def test_gates(gate, qubits, params, expected_qasm) -> None: gate(*qubits, *params) assert expected_qasm in program_conversion_context.make_program().to_ir() + + +@pytest.mark.parametrize( + "control,control_state,expected_qasm", + [ + (0, None, "\nctrl @ x __qubits__[0], __qubits__[1];"), + ([0], None, "\nctrl @ x __qubits__[0], __qubits__[1];"), + (0, "1", "\nctrl @ x __qubits__[0], __qubits__[1];"), + ([0], "0", "\nnegctrl @ x __qubits__[0], __qubits__[1];"), + ([0], 0, "\nnegctrl @ x __qubits__[0], __qubits__[1];"), + ([0], [0], "\nnegctrl @ x __qubits__[0], __qubits__[1];"), + ], +) +def test_gate_modifiers_single_control(control, control_state, expected_qasm) -> None: + """Tests quantum gate modifiers to create a singly-controlled X gate.""" + with aq.build_program() as program_conversion_context: + x(1, control=control, control_state=control_state) + + assert expected_qasm in program_conversion_context.make_program().to_ir() + + +@pytest.mark.parametrize( + "control,control_state,expected_qasm", + [ + ([0, 1], "11", "\nctrl(2) @ x __qubits__[0], __qubits__[1], __qubits__[2];"), + ([0, 1], [1, 1], "\nctrl(2) @ x __qubits__[0], __qubits__[1], __qubits__[2];"), + ([0, 1], 3, "\nctrl(2) @ x __qubits__[0], __qubits__[1], __qubits__[2];"), + ([0, 1], "10", "\nctrl @ negctrl @ x __qubits__[0], __qubits__[1], __qubits__[2];"), + ], +) +def test_gate_modifiers_multi_control(control, control_state, expected_qasm) -> None: + """Tests quantum gate modifiers to create a multiply-controlled X gate.""" + with aq.build_program() as program_conversion_context: + x(2, control=control, control_state=control_state) + + assert expected_qasm in program_conversion_context.make_program().to_ir() + + +@pytest.mark.parametrize( + "control,control_state,power,expected_qasm", + [ + (None, None, -2.0, "\npow(-2.0) @ x __qubits__[1];"), + ([0], "1", 0.5, "\nctrl @ pow(0.5) @ x __qubits__[0], __qubits__[1];"), + ], +) +def test_gate_modifiers_power(control, control_state, power, expected_qasm) -> None: + """Tests quantum gate modifiers to create gates raised to powers.""" + with aq.build_program() as program_conversion_context: + x(1, control=control, control_state=control_state, power=power) + + assert expected_qasm in program_conversion_context.make_program().to_ir() + + +def test_gate_modifiers_physical_qubits() -> None: + with aq.build_program() as program_conversion_context: + x("$1", control="$0") + + assert "\nctrl @ x $0, $1;" in program_conversion_context.make_program().to_ir() + + +def test_invalid_gate_modifiers() -> None: + """Tests invalid quantum gate modifiers.""" + with aq.build_program() as _: + with pytest.raises(ValueError, match="length greater than the specified number of qubits"): + x(1, control=None, control_state="00") + with pytest.raises(ValueError, match="length greater than the specified number of qubits"): + x(1, control=0, control_state="00") + with pytest.raises(ValueError, match="length greater than the specified number of qubits"): + x(1, control=0, control_state=3) From 34ab0caa1f2a1dbad228c16499673b10fa069168 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 7 May 2024 11:29:57 -0400 Subject: [PATCH 1151/1165] Rearrange files, delete unnecessary folders --- doc/Makefile | 20 - doc/conf.py | 40 - .../autoqasm/doc => doc}/decorators.md | 0 doc/examples-adv-circuits-algorithms.rst | 50 - doc/examples-braket-features.rst | 47 - doc/examples-getting-started.rst | 58 - doc/examples-hybrid-jobs.rst | 33 - doc/examples-hybrid-quantum.rst | 29 - doc/examples-ml-pennylane.rst | 47 - doc/examples.rst | 16 - doc/getting-started.rst | 39 - doc/index.rst | 43 - doc/requirements.txt | 3 - .../1_Getting_started_with_AutoQASM.ipynb | 0 .../2_Expressing_classical_control_flow.ipynb | 0 .../3_1_Iterative_phase_estimation.ipynb | 0 .../3_2_magic_state_distillation.ipynb | 0 .../{autoqasm => }/4_Native_programming.ipynb | 0 ...programming_and_dynamical_decoupling.ipynb | 0 .../6_Customize_gate_calibrations.ipynb | 0 examples/bell.py | 23 - examples/bell_result_types.py | 60 - examples/debug_bell.py | 39 - examples/hybrid_job.py | 56 - examples/hybrid_job_script.py | 56 - examples/{autoqasm => }/ionq_gates.py | 0 examples/local_bell.py | 20 - examples/local_noise_simulation.py | 30 - examples/reservation.py | 35 - .../experimental => }/autoqasm/README.md | 0 .../experimental => }/autoqasm/__init__.py | 0 src/{braket/experimental => }/autoqasm/api.py | 0 .../experimental => }/autoqasm/constants.py | 0 .../autoqasm/converters/__init__.py | 0 .../autoqasm/converters/assignments.py | 0 .../autoqasm/converters/break_statements.py | 0 .../autoqasm/converters/comparisons.py | 0 .../autoqasm/converters/return_statements.py | 0 .../experimental => }/autoqasm/errors.py | 0 .../autoqasm/instructions/__init__.py | 0 .../autoqasm/instructions/gates.py | 0 .../autoqasm/instructions/instructions.py | 0 .../autoqasm/instructions/measurements.py | 0 .../autoqasm/instructions/qubits.py | 0 .../autoqasm/operators/__init__.py | 0 .../autoqasm/operators/assignments.py | 0 .../autoqasm/operators/comparisons.py | 0 .../operators/conditional_expressions.py | 0 .../autoqasm/operators/control_flow.py | 0 .../autoqasm/operators/data_structures.py | 0 .../autoqasm/operators/exceptions.py | 0 .../autoqasm/operators/logical.py | 0 .../autoqasm/operators/return_statements.py | 0 .../autoqasm/operators/slices.py | 0 .../autoqasm/operators/utils.py | 0 .../autoqasm/program/__init__.py | 0 .../autoqasm/program/gate_calibrations.py | 0 .../autoqasm/program/pragmas.py | 0 .../autoqasm/program/program.py | 0 .../program/serialization_properties.py | 0 .../autoqasm/pulse/__init__.py | 0 .../experimental => }/autoqasm/pulse/pulse.py | 0 .../autoqasm/transpiler/__init__.py | 0 .../autoqasm/transpiler/transpiler.py | 0 .../autoqasm/types/__init__.py | 0 .../autoqasm/types/conversions.py | 0 .../experimental => }/autoqasm/types/types.py | 0 src/braket/_sdk/__init__.py | 14 - src/braket/_sdk/_version.py | 18 - src/braket/ahs/__init__.py | 22 - .../ahs/analog_hamiltonian_simulation.py | 155 - src/braket/ahs/atom_arrangement.py | 129 - src/braket/ahs/discretization_types.py | 38 - src/braket/ahs/driving_field.py | 186 - src/braket/ahs/field.py | 68 - src/braket/ahs/hamiltonian.py | 60 - src/braket/ahs/local_detuning.py | 162 - src/braket/ahs/pattern.py | 55 - src/braket/ahs/shifting_field.py | 20 - src/braket/annealing/__init__.py | 15 - src/braket/annealing/problem.py | 154 - src/braket/aws/__init__.py | 19 - src/braket/aws/aws_device.py | 857 ---- src/braket/aws/aws_quantum_job.py | 659 --- src/braket/aws/aws_quantum_task.py | 880 ---- src/braket/aws/aws_quantum_task_batch.py | 453 -- src/braket/aws/aws_session.py | 891 ---- src/braket/aws/direct_reservations.py | 98 - src/braket/aws/queue_information.py | 81 - src/braket/circuits/__init__.py | 45 - src/braket/circuits/angled_gate.py | 459 -- src/braket/circuits/ascii_circuit_diagram.py | 18 - src/braket/circuits/basis_state.py | 84 - src/braket/circuits/braket_program_context.py | 176 - src/braket/circuits/circuit.py | 1677 ------- src/braket/circuits/circuit_diagram.py | 35 - src/braket/circuits/circuit_helpers.py | 47 - src/braket/circuits/compiler_directive.py | 105 - src/braket/circuits/compiler_directives.py | 53 - src/braket/circuits/free_parameter.py | 14 - .../circuits/free_parameter_expression.py | 14 - src/braket/circuits/gate.py | 226 - src/braket/circuits/gate_calibrations.py | 164 - src/braket/circuits/gates.py | 3867 ----------------- src/braket/circuits/instruction.py | 294 -- src/braket/circuits/measure.py | 99 - src/braket/circuits/moments.py | 350 -- src/braket/circuits/noise.py | 844 ---- src/braket/circuits/noise_helpers.py | 323 -- src/braket/circuits/noise_model/__init__.py | 30 - .../circuit_instruction_criteria.py | 56 - src/braket/circuits/noise_model/criteria.py | 114 - .../noise_model/criteria_input_parsing.py | 87 - .../circuits/noise_model/gate_criteria.py | 143 - .../noise_model/initialization_criteria.py | 33 - .../circuits/noise_model/noise_model.py | 397 -- .../noise_model/observable_criteria.py | 156 - .../qubit_initialization_criteria.py | 106 - .../noise_model/result_type_criteria.py | 33 - .../noise_model/unitary_gate_criteria.py | 124 - src/braket/circuits/noises.py | 1477 ------- src/braket/circuits/observable.py | 220 - src/braket/circuits/observables.py | 684 --- src/braket/circuits/operator.py | 37 - src/braket/circuits/parameterizable.py | 14 - src/braket/circuits/quantum_operator.py | 160 - .../circuits/quantum_operator_helpers.py | 125 - src/braket/circuits/qubit.py | 14 - src/braket/circuits/qubit_set.py | 14 - src/braket/circuits/result_type.py | 333 -- src/braket/circuits/result_types.py | 658 --- src/braket/circuits/serialization.py | 88 - .../ascii_circuit_diagram.py | 197 - .../text_circuit_diagram.py | 265 -- .../text_circuit_diagram_utils.py | 199 - .../unicode_circuit_diagram.py | 278 -- src/braket/circuits/translations.py | 186 - src/braket/circuits/unitary_calculation.py | 78 - src/braket/devices/__init__.py | 16 - src/braket/devices/device.py | 150 - src/braket/devices/devices.py | 60 - src/braket/devices/local_simulator.py | 388 -- src/braket/error_mitigation/__init__.py | 15 - src/braket/error_mitigation/debias.py | 22 - .../error_mitigation/error_mitigation.py | 29 - src/braket/experimental/__init__.py | 18 - src/braket/ipython_utils.py | 33 - src/braket/jobs/__init__.py | 36 - src/braket/jobs/_entry_point_template.py | 79 - src/braket/jobs/config.py | 83 - src/braket/jobs/data_persistence.py | 192 - src/braket/jobs/environment_variables.py | 77 - src/braket/jobs/hybrid_job.py | 428 -- src/braket/jobs/image_uri_config/base.json | 10 - .../jobs/image_uri_config/pl_pytorch.json | 10 - .../jobs/image_uri_config/pl_tensorflow.json | 10 - src/braket/jobs/image_uris.py | 95 - src/braket/jobs/local/__init__.py | 14 - src/braket/jobs/local/local_job.py | 344 -- src/braket/jobs/local/local_job_container.py | 297 -- .../jobs/local/local_job_container_setup.py | 279 -- src/braket/jobs/logs.py | 241 - src/braket/jobs/metrics.py | 41 - src/braket/jobs/metrics_data/__init__.py | 17 - .../cwl_insights_metrics_fetcher.py | 191 - .../jobs/metrics_data/cwl_metrics_fetcher.py | 157 - src/braket/jobs/metrics_data/definitions.py | 36 - src/braket/jobs/metrics_data/exceptions.py | 16 - .../jobs/metrics_data/log_metrics_parser.py | 205 - src/braket/jobs/quantum_job.py | 201 - src/braket/jobs/quantum_job_creation.py | 495 --- src/braket/jobs/serialization.py | 65 - src/braket/parametric/__init__.py | 17 - src/braket/parametric/free_parameter.py | 107 - .../parametric/free_parameter_expression.py | 245 -- src/braket/parametric/parameterizable.py | 49 - src/braket/pulse/__init__.py | 22 - src/braket/pulse/ast/approximation_parser.py | 596 --- src/braket/pulse/ast/free_parameters.py | 95 - src/braket/pulse/ast/qasm_parser.py | 64 - src/braket/pulse/ast/qasm_transformer.py | 59 - src/braket/pulse/frame.py | 83 - src/braket/pulse/port.py | 54 - src/braket/pulse/pulse_sequence.py | 446 -- src/braket/pulse/pulse_sequence_trace.py | 38 - src/braket/pulse/waveforms.py | 520 --- src/braket/quantum_information/__init__.py | 14 - .../quantum_information/pauli_string.py | 404 -- src/braket/registers/__init__.py | 15 - src/braket/registers/qubit.py | 66 - src/braket/registers/qubit_set.py | 92 - src/braket/tasks/__init__.py | 35 - ...iltonian_simulation_quantum_task_result.py | 162 - .../tasks/annealing_quantum_task_result.py | 173 - .../tasks/gate_model_quantum_task_result.py | 512 --- src/braket/tasks/local_quantum_task.py | 81 - src/braket/tasks/local_quantum_task_batch.py | 49 - .../photonic_model_quantum_task_result.py | 69 - src/braket/tasks/quantum_task.py | 79 - src/braket/tasks/quantum_task_batch.py | 38 - src/braket/timings/__init__.py | 14 - src/braket/timings/time_series.py | 422 -- src/braket/tracking/__init__.py | 14 - src/braket/tracking/pricing.py | 74 - src/braket/tracking/tracker.py | 300 -- src/braket/tracking/tracking_context.py | 59 - src/braket/tracking/tracking_events.py | 41 - test/integ_tests/conftest.py | 184 - .../gate_model_device_testing_utils.py | 743 ---- .../job_test_submodule_file.py | 3 - .../job_test_submodule/requirements.txt | 1 - test/integ_tests/job_test_script.py | 58 - test/integ_tests/requirements.txt | 1 - test/integ_tests/test_adjoint_gradient.py | 175 - test/integ_tests/test_aws_session_s3.py | 45 - test/integ_tests/test_cost_tracking.py | 117 - .../test_create_local_quantum_job.py | 154 - test/integ_tests/test_create_quantum_job.py | 311 -- .../test_density_matrix_simulator.py | 54 - test/integ_tests/test_device_creation.py | 140 - .../test_local_braket_simulator.py | 161 - .../integ_tests/test_local_noise_simulator.py | 141 - test/integ_tests/test_measure.py | 85 - test/integ_tests/test_pulse.py | 345 -- test/integ_tests/test_queue_information.py | 81 - test/integ_tests/test_reservation_arn.py | 77 - .../test_simulator_quantum_task.py | 293 -- .../test_tensor_network_simulator.py | 92 - test/integ_tests/test_user_agents.py | 33 - .../experimental => }/autoqasm/conftest.py | 0 .../autoqasm/mock_transpiler.py | 0 .../autoqasm/test_annotations.py | 0 .../experimental => }/autoqasm/test_api.py | 0 .../autoqasm/test_converters.py | 0 .../autoqasm/test_devices.py | 0 .../autoqasm/test_gate_calibrations.py | 0 .../autoqasm/test_gate_decorator.py | 0 .../autoqasm/test_gate_definitions.py | 0 .../autoqasm/test_operators.py | 0 .../autoqasm/test_parameters.py | 0 .../autoqasm/test_pragmas.py | 0 .../autoqasm/test_program.py | 0 .../experimental => }/autoqasm/test_pulse.py | 0 .../experimental => }/autoqasm/test_return.py | 0 .../autoqasm/test_serialization_config.py | 0 .../autoqasm/test_transpiler.py | 0 .../experimental => }/autoqasm/test_types.py | 0 .../ahs/test_analog_hamiltonian_simulation.py | 240 - .../braket/ahs/test_atom_arrangement.py | 113 - .../braket/ahs/test_driving_field.py | 175 - test/unit_tests/braket/ahs/test_field.py | 100 - .../unit_tests/braket/ahs/test_hamiltonian.py | 45 - .../braket/ahs/test_local_detuning.py | 139 - test/unit_tests/braket/ahs/test_pattern.py | 114 - .../braket/annealing/test_problem.py | 53 - .../braket/aws/common_test_utils.py | 370 -- test/unit_tests/braket/aws/test_aws_device.py | 2231 ---------- .../braket/aws/test_aws_quantum_job.py | 1129 ----- .../braket/aws/test_aws_quantum_task.py | 1296 ------ .../braket/aws/test_aws_quantum_task_batch.py | 205 - .../unit_tests/braket/aws/test_aws_session.py | 1407 ------ .../braket/aws/test_direct_reservations.py | 181 - .../circuits/noise/test_gate_criteria.py | 209 - .../braket/circuits/noise/test_noise_model.py | 490 --- .../noise/test_observable_criteria.py | 191 - .../test_qubit_initialization_criteria.py | 112 - .../noise/test_unitary_gate_criteria.py | 207 - .../braket/circuits/test_angled_gate.py | 214 - .../circuits/test_ascii_circuit_diagram.py | 958 ---- .../braket/circuits/test_basis_state.py | 108 - .../braket/circuits/test_circuit.py | 3612 --------------- .../braket/circuits/test_circuit_helpers.py | 114 - .../circuits/test_compiler_directive.py | 71 - .../circuits/test_compiler_directives.py | 55 - test/unit_tests/braket/circuits/test_gate.py | 119 - .../braket/circuits/test_gate_calibration.py | 159 - test/unit_tests/braket/circuits/test_gates.py | 1287 ------ .../braket/circuits/test_instruction.py | 204 - .../braket/circuits/test_measure.py | 100 - .../braket/circuits/test_moments.py | 187 - test/unit_tests/braket/circuits/test_noise.py | 490 --- .../braket/circuits/test_noise_helpers.py | 753 ---- .../unit_tests/braket/circuits/test_noises.py | 720 --- .../braket/circuits/test_observable.py | 216 - .../braket/circuits/test_observables.py | 716 --- .../braket/circuits/test_quantum_operator.py | 142 - .../circuits/test_quantum_operator_helpers.py | 139 - .../braket/circuits/test_result_type.py | 251 -- .../braket/circuits/test_result_types.py | 386 -- .../circuits/test_unicode_circuit_diagram.py | 1121 ----- .../braket/devices/test_local_simulator.py | 748 ---- test/unit_tests/braket/jobs/job_module.py | 2 - .../braket/jobs/local/test_local_job.py | 215 - .../jobs/local/test_local_job_container.py | 496 --- .../local/test_local_job_container_setup.py | 226 - .../test_cwl_insights_metrics_fetcher.py | 139 - .../metrics_data/test_cwl_metrics_fetcher.py | 133 - .../metrics_data/test_log_metrics_parser.py | 204 - .../braket/jobs/test_data_persistence.py | 338 -- .../braket/jobs/test_environment_variables.py | 66 - .../unit_tests/braket/jobs/test_hybrid_job.py | 554 --- .../unit_tests/braket/jobs/test_image_uris.py | 54 - test/unit_tests/braket/jobs/test_metrics.py | 35 - .../braket/jobs/test_quantum_job_creation.py | 679 --- .../braket/jobs/test_serialization.py | 58 - .../braket/parametric/test_free_parameter.py | 69 - .../test_free_parameter_expression.py | 183 - .../pulse/ast/test_approximation_parser.py | 1010 ----- test/unit_tests/braket/pulse/test_frame.py | 72 - test/unit_tests/braket/pulse/test_port.py | 61 - .../braket/pulse/test_pulse_sequence.py | 418 -- .../unit_tests/braket/pulse/test_waveforms.py | 363 -- .../quantum_information/test_pauli_string.py | 262 -- .../unit_tests/braket/registers/test_qubit.py | 57 - .../braket/registers/test_qubit_set.py | 70 - ...alog_hamiltonian_simulation_task_result.py | 316 -- .../test_annealing_quantum_task_result.py | 259 -- .../test_gate_model_quantum_task_result.py | 617 --- .../braket/tasks/test_local_quantum_task.py | 61 - .../tasks/test_local_quantum_task_batch.py | 40 - ...test_photonic_model_quantum_task_result.py | 87 - .../braket/tasks/test_tasks_init.py | 32 - test/unit_tests/braket/test_imports.py | 48 - test/unit_tests/braket/test_ipython_utils.py | 53 - .../braket/timings/test_time_series.py | 315 -- .../braket/tracking/test_pricing.py | 52 - .../braket/tracking/test_tracker.py | 224 - .../braket/tracking/test_tracking_context.py | 44 - 328 files changed, 63519 deletions(-) delete mode 100644 doc/Makefile delete mode 100644 doc/conf.py rename {src/braket/experimental/autoqasm/doc => doc}/decorators.md (100%) delete mode 100644 doc/examples-adv-circuits-algorithms.rst delete mode 100644 doc/examples-braket-features.rst delete mode 100644 doc/examples-getting-started.rst delete mode 100644 doc/examples-hybrid-jobs.rst delete mode 100644 doc/examples-hybrid-quantum.rst delete mode 100644 doc/examples-ml-pennylane.rst delete mode 100644 doc/examples.rst delete mode 100644 doc/getting-started.rst delete mode 100644 doc/index.rst delete mode 100644 doc/requirements.txt rename examples/{autoqasm => }/1_Getting_started_with_AutoQASM.ipynb (100%) rename examples/{autoqasm => }/2_Expressing_classical_control_flow.ipynb (100%) rename examples/{autoqasm => }/3_1_Iterative_phase_estimation.ipynb (100%) rename examples/{autoqasm => }/3_2_magic_state_distillation.ipynb (100%) rename examples/{autoqasm => }/4_Native_programming.ipynb (100%) rename examples/{autoqasm => }/5_Pulse_programming_and_dynamical_decoupling.ipynb (100%) rename examples/{autoqasm => }/6_Customize_gate_calibrations.ipynb (100%) delete mode 100644 examples/bell.py delete mode 100644 examples/bell_result_types.py delete mode 100644 examples/debug_bell.py delete mode 100644 examples/hybrid_job.py delete mode 100644 examples/hybrid_job_script.py rename examples/{autoqasm => }/ionq_gates.py (100%) delete mode 100644 examples/local_bell.py delete mode 100644 examples/local_noise_simulation.py delete mode 100644 examples/reservation.py rename src/{braket/experimental => }/autoqasm/README.md (100%) rename src/{braket/experimental => }/autoqasm/__init__.py (100%) rename src/{braket/experimental => }/autoqasm/api.py (100%) rename src/{braket/experimental => }/autoqasm/constants.py (100%) rename src/{braket/experimental => }/autoqasm/converters/__init__.py (100%) rename src/{braket/experimental => }/autoqasm/converters/assignments.py (100%) rename src/{braket/experimental => }/autoqasm/converters/break_statements.py (100%) rename src/{braket/experimental => }/autoqasm/converters/comparisons.py (100%) rename src/{braket/experimental => }/autoqasm/converters/return_statements.py (100%) rename src/{braket/experimental => }/autoqasm/errors.py (100%) rename src/{braket/experimental => }/autoqasm/instructions/__init__.py (100%) rename src/{braket/experimental => }/autoqasm/instructions/gates.py (100%) rename src/{braket/experimental => }/autoqasm/instructions/instructions.py (100%) rename src/{braket/experimental => }/autoqasm/instructions/measurements.py (100%) rename src/{braket/experimental => }/autoqasm/instructions/qubits.py (100%) rename src/{braket/experimental => }/autoqasm/operators/__init__.py (100%) rename src/{braket/experimental => }/autoqasm/operators/assignments.py (100%) rename src/{braket/experimental => }/autoqasm/operators/comparisons.py (100%) rename src/{braket/experimental => }/autoqasm/operators/conditional_expressions.py (100%) rename src/{braket/experimental => }/autoqasm/operators/control_flow.py (100%) rename src/{braket/experimental => }/autoqasm/operators/data_structures.py (100%) rename src/{braket/experimental => }/autoqasm/operators/exceptions.py (100%) rename src/{braket/experimental => }/autoqasm/operators/logical.py (100%) rename src/{braket/experimental => }/autoqasm/operators/return_statements.py (100%) rename src/{braket/experimental => }/autoqasm/operators/slices.py (100%) rename src/{braket/experimental => }/autoqasm/operators/utils.py (100%) rename src/{braket/experimental => }/autoqasm/program/__init__.py (100%) rename src/{braket/experimental => }/autoqasm/program/gate_calibrations.py (100%) rename src/{braket/experimental => }/autoqasm/program/pragmas.py (100%) rename src/{braket/experimental => }/autoqasm/program/program.py (100%) rename src/{braket/experimental => }/autoqasm/program/serialization_properties.py (100%) rename src/{braket/experimental => }/autoqasm/pulse/__init__.py (100%) rename src/{braket/experimental => }/autoqasm/pulse/pulse.py (100%) rename src/{braket/experimental => }/autoqasm/transpiler/__init__.py (100%) rename src/{braket/experimental => }/autoqasm/transpiler/transpiler.py (100%) rename src/{braket/experimental => }/autoqasm/types/__init__.py (100%) rename src/{braket/experimental => }/autoqasm/types/conversions.py (100%) rename src/{braket/experimental => }/autoqasm/types/types.py (100%) delete mode 100644 src/braket/_sdk/__init__.py delete mode 100644 src/braket/_sdk/_version.py delete mode 100644 src/braket/ahs/__init__.py delete mode 100644 src/braket/ahs/analog_hamiltonian_simulation.py delete mode 100644 src/braket/ahs/atom_arrangement.py delete mode 100644 src/braket/ahs/discretization_types.py delete mode 100644 src/braket/ahs/driving_field.py delete mode 100644 src/braket/ahs/field.py delete mode 100644 src/braket/ahs/hamiltonian.py delete mode 100644 src/braket/ahs/local_detuning.py delete mode 100644 src/braket/ahs/pattern.py delete mode 100644 src/braket/ahs/shifting_field.py delete mode 100644 src/braket/annealing/__init__.py delete mode 100644 src/braket/annealing/problem.py delete mode 100644 src/braket/aws/__init__.py delete mode 100644 src/braket/aws/aws_device.py delete mode 100644 src/braket/aws/aws_quantum_job.py delete mode 100644 src/braket/aws/aws_quantum_task.py delete mode 100644 src/braket/aws/aws_quantum_task_batch.py delete mode 100644 src/braket/aws/aws_session.py delete mode 100644 src/braket/aws/direct_reservations.py delete mode 100644 src/braket/aws/queue_information.py delete mode 100644 src/braket/circuits/__init__.py delete mode 100644 src/braket/circuits/angled_gate.py delete mode 100644 src/braket/circuits/ascii_circuit_diagram.py delete mode 100644 src/braket/circuits/basis_state.py delete mode 100644 src/braket/circuits/braket_program_context.py delete mode 100644 src/braket/circuits/circuit.py delete mode 100644 src/braket/circuits/circuit_diagram.py delete mode 100644 src/braket/circuits/circuit_helpers.py delete mode 100644 src/braket/circuits/compiler_directive.py delete mode 100644 src/braket/circuits/compiler_directives.py delete mode 100644 src/braket/circuits/free_parameter.py delete mode 100644 src/braket/circuits/free_parameter_expression.py delete mode 100644 src/braket/circuits/gate.py delete mode 100644 src/braket/circuits/gate_calibrations.py delete mode 100644 src/braket/circuits/gates.py delete mode 100644 src/braket/circuits/instruction.py delete mode 100644 src/braket/circuits/measure.py delete mode 100644 src/braket/circuits/moments.py delete mode 100644 src/braket/circuits/noise.py delete mode 100644 src/braket/circuits/noise_helpers.py delete mode 100644 src/braket/circuits/noise_model/__init__.py delete mode 100644 src/braket/circuits/noise_model/circuit_instruction_criteria.py delete mode 100644 src/braket/circuits/noise_model/criteria.py delete mode 100644 src/braket/circuits/noise_model/criteria_input_parsing.py delete mode 100644 src/braket/circuits/noise_model/gate_criteria.py delete mode 100644 src/braket/circuits/noise_model/initialization_criteria.py delete mode 100644 src/braket/circuits/noise_model/noise_model.py delete mode 100644 src/braket/circuits/noise_model/observable_criteria.py delete mode 100644 src/braket/circuits/noise_model/qubit_initialization_criteria.py delete mode 100644 src/braket/circuits/noise_model/result_type_criteria.py delete mode 100644 src/braket/circuits/noise_model/unitary_gate_criteria.py delete mode 100644 src/braket/circuits/noises.py delete mode 100644 src/braket/circuits/observable.py delete mode 100644 src/braket/circuits/observables.py delete mode 100644 src/braket/circuits/operator.py delete mode 100644 src/braket/circuits/parameterizable.py delete mode 100644 src/braket/circuits/quantum_operator.py delete mode 100644 src/braket/circuits/quantum_operator_helpers.py delete mode 100644 src/braket/circuits/qubit.py delete mode 100644 src/braket/circuits/qubit_set.py delete mode 100644 src/braket/circuits/result_type.py delete mode 100644 src/braket/circuits/result_types.py delete mode 100644 src/braket/circuits/serialization.py delete mode 100644 src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py delete mode 100644 src/braket/circuits/text_diagram_builders/text_circuit_diagram.py delete mode 100644 src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py delete mode 100644 src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py delete mode 100644 src/braket/circuits/translations.py delete mode 100644 src/braket/circuits/unitary_calculation.py delete mode 100644 src/braket/devices/__init__.py delete mode 100644 src/braket/devices/device.py delete mode 100644 src/braket/devices/devices.py delete mode 100644 src/braket/devices/local_simulator.py delete mode 100644 src/braket/error_mitigation/__init__.py delete mode 100644 src/braket/error_mitigation/debias.py delete mode 100644 src/braket/error_mitigation/error_mitigation.py delete mode 100644 src/braket/experimental/__init__.py delete mode 100644 src/braket/ipython_utils.py delete mode 100644 src/braket/jobs/__init__.py delete mode 100644 src/braket/jobs/_entry_point_template.py delete mode 100644 src/braket/jobs/config.py delete mode 100644 src/braket/jobs/data_persistence.py delete mode 100644 src/braket/jobs/environment_variables.py delete mode 100644 src/braket/jobs/hybrid_job.py delete mode 100644 src/braket/jobs/image_uri_config/base.json delete mode 100644 src/braket/jobs/image_uri_config/pl_pytorch.json delete mode 100644 src/braket/jobs/image_uri_config/pl_tensorflow.json delete mode 100644 src/braket/jobs/image_uris.py delete mode 100644 src/braket/jobs/local/__init__.py delete mode 100644 src/braket/jobs/local/local_job.py delete mode 100644 src/braket/jobs/local/local_job_container.py delete mode 100644 src/braket/jobs/local/local_job_container_setup.py delete mode 100644 src/braket/jobs/logs.py delete mode 100644 src/braket/jobs/metrics.py delete mode 100644 src/braket/jobs/metrics_data/__init__.py delete mode 100644 src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py delete mode 100644 src/braket/jobs/metrics_data/cwl_metrics_fetcher.py delete mode 100644 src/braket/jobs/metrics_data/definitions.py delete mode 100644 src/braket/jobs/metrics_data/exceptions.py delete mode 100644 src/braket/jobs/metrics_data/log_metrics_parser.py delete mode 100644 src/braket/jobs/quantum_job.py delete mode 100644 src/braket/jobs/quantum_job_creation.py delete mode 100644 src/braket/jobs/serialization.py delete mode 100644 src/braket/parametric/__init__.py delete mode 100644 src/braket/parametric/free_parameter.py delete mode 100644 src/braket/parametric/free_parameter_expression.py delete mode 100644 src/braket/parametric/parameterizable.py delete mode 100644 src/braket/pulse/__init__.py delete mode 100644 src/braket/pulse/ast/approximation_parser.py delete mode 100644 src/braket/pulse/ast/free_parameters.py delete mode 100644 src/braket/pulse/ast/qasm_parser.py delete mode 100644 src/braket/pulse/ast/qasm_transformer.py delete mode 100644 src/braket/pulse/frame.py delete mode 100644 src/braket/pulse/port.py delete mode 100644 src/braket/pulse/pulse_sequence.py delete mode 100644 src/braket/pulse/pulse_sequence_trace.py delete mode 100644 src/braket/pulse/waveforms.py delete mode 100644 src/braket/quantum_information/__init__.py delete mode 100644 src/braket/quantum_information/pauli_string.py delete mode 100644 src/braket/registers/__init__.py delete mode 100644 src/braket/registers/qubit.py delete mode 100644 src/braket/registers/qubit_set.py delete mode 100644 src/braket/tasks/__init__.py delete mode 100644 src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py delete mode 100644 src/braket/tasks/annealing_quantum_task_result.py delete mode 100644 src/braket/tasks/gate_model_quantum_task_result.py delete mode 100644 src/braket/tasks/local_quantum_task.py delete mode 100644 src/braket/tasks/local_quantum_task_batch.py delete mode 100644 src/braket/tasks/photonic_model_quantum_task_result.py delete mode 100644 src/braket/tasks/quantum_task.py delete mode 100644 src/braket/tasks/quantum_task_batch.py delete mode 100644 src/braket/timings/__init__.py delete mode 100644 src/braket/timings/time_series.py delete mode 100644 src/braket/tracking/__init__.py delete mode 100644 src/braket/tracking/pricing.py delete mode 100644 src/braket/tracking/tracker.py delete mode 100644 src/braket/tracking/tracking_context.py delete mode 100644 src/braket/tracking/tracking_events.py delete mode 100644 test/integ_tests/conftest.py delete mode 100644 test/integ_tests/gate_model_device_testing_utils.py delete mode 100644 test/integ_tests/job_test_module/job_test_submodule/job_test_submodule_file.py delete mode 100644 test/integ_tests/job_test_module/job_test_submodule/requirements.txt delete mode 100644 test/integ_tests/job_test_script.py delete mode 100644 test/integ_tests/requirements.txt delete mode 100644 test/integ_tests/test_adjoint_gradient.py delete mode 100644 test/integ_tests/test_aws_session_s3.py delete mode 100644 test/integ_tests/test_cost_tracking.py delete mode 100644 test/integ_tests/test_create_local_quantum_job.py delete mode 100644 test/integ_tests/test_create_quantum_job.py delete mode 100644 test/integ_tests/test_density_matrix_simulator.py delete mode 100644 test/integ_tests/test_device_creation.py delete mode 100644 test/integ_tests/test_local_braket_simulator.py delete mode 100644 test/integ_tests/test_local_noise_simulator.py delete mode 100644 test/integ_tests/test_measure.py delete mode 100644 test/integ_tests/test_pulse.py delete mode 100644 test/integ_tests/test_queue_information.py delete mode 100644 test/integ_tests/test_reservation_arn.py delete mode 100644 test/integ_tests/test_simulator_quantum_task.py delete mode 100644 test/integ_tests/test_tensor_network_simulator.py delete mode 100644 test/integ_tests/test_user_agents.py rename test/unit_tests/{braket/experimental => }/autoqasm/conftest.py (100%) rename test/unit_tests/{braket/experimental => }/autoqasm/mock_transpiler.py (100%) rename test/unit_tests/{braket/experimental => }/autoqasm/test_annotations.py (100%) rename test/unit_tests/{braket/experimental => }/autoqasm/test_api.py (100%) rename test/unit_tests/{braket/experimental => }/autoqasm/test_converters.py (100%) rename test/unit_tests/{braket/experimental => }/autoqasm/test_devices.py (100%) rename test/unit_tests/{braket/experimental => }/autoqasm/test_gate_calibrations.py (100%) rename test/unit_tests/{braket/experimental => }/autoqasm/test_gate_decorator.py (100%) rename test/unit_tests/{braket/experimental => }/autoqasm/test_gate_definitions.py (100%) rename test/unit_tests/{braket/experimental => }/autoqasm/test_operators.py (100%) rename test/unit_tests/{braket/experimental => }/autoqasm/test_parameters.py (100%) rename test/unit_tests/{braket/experimental => }/autoqasm/test_pragmas.py (100%) rename test/unit_tests/{braket/experimental => }/autoqasm/test_program.py (100%) rename test/unit_tests/{braket/experimental => }/autoqasm/test_pulse.py (100%) rename test/unit_tests/{braket/experimental => }/autoqasm/test_return.py (100%) rename test/unit_tests/{braket/experimental => }/autoqasm/test_serialization_config.py (100%) rename test/unit_tests/{braket/experimental => }/autoqasm/test_transpiler.py (100%) rename test/unit_tests/{braket/experimental => }/autoqasm/test_types.py (100%) delete mode 100644 test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py delete mode 100644 test/unit_tests/braket/ahs/test_atom_arrangement.py delete mode 100644 test/unit_tests/braket/ahs/test_driving_field.py delete mode 100644 test/unit_tests/braket/ahs/test_field.py delete mode 100644 test/unit_tests/braket/ahs/test_hamiltonian.py delete mode 100644 test/unit_tests/braket/ahs/test_local_detuning.py delete mode 100644 test/unit_tests/braket/ahs/test_pattern.py delete mode 100644 test/unit_tests/braket/annealing/test_problem.py delete mode 100644 test/unit_tests/braket/aws/common_test_utils.py delete mode 100644 test/unit_tests/braket/aws/test_aws_device.py delete mode 100644 test/unit_tests/braket/aws/test_aws_quantum_job.py delete mode 100644 test/unit_tests/braket/aws/test_aws_quantum_task.py delete mode 100644 test/unit_tests/braket/aws/test_aws_quantum_task_batch.py delete mode 100644 test/unit_tests/braket/aws/test_aws_session.py delete mode 100644 test/unit_tests/braket/aws/test_direct_reservations.py delete mode 100644 test/unit_tests/braket/circuits/noise/test_gate_criteria.py delete mode 100644 test/unit_tests/braket/circuits/noise/test_noise_model.py delete mode 100644 test/unit_tests/braket/circuits/noise/test_observable_criteria.py delete mode 100644 test/unit_tests/braket/circuits/noise/test_qubit_initialization_criteria.py delete mode 100644 test/unit_tests/braket/circuits/noise/test_unitary_gate_criteria.py delete mode 100644 test/unit_tests/braket/circuits/test_angled_gate.py delete mode 100644 test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py delete mode 100644 test/unit_tests/braket/circuits/test_basis_state.py delete mode 100644 test/unit_tests/braket/circuits/test_circuit.py delete mode 100644 test/unit_tests/braket/circuits/test_circuit_helpers.py delete mode 100644 test/unit_tests/braket/circuits/test_compiler_directive.py delete mode 100644 test/unit_tests/braket/circuits/test_compiler_directives.py delete mode 100644 test/unit_tests/braket/circuits/test_gate.py delete mode 100644 test/unit_tests/braket/circuits/test_gate_calibration.py delete mode 100644 test/unit_tests/braket/circuits/test_gates.py delete mode 100644 test/unit_tests/braket/circuits/test_instruction.py delete mode 100644 test/unit_tests/braket/circuits/test_measure.py delete mode 100644 test/unit_tests/braket/circuits/test_moments.py delete mode 100644 test/unit_tests/braket/circuits/test_noise.py delete mode 100644 test/unit_tests/braket/circuits/test_noise_helpers.py delete mode 100644 test/unit_tests/braket/circuits/test_noises.py delete mode 100644 test/unit_tests/braket/circuits/test_observable.py delete mode 100644 test/unit_tests/braket/circuits/test_observables.py delete mode 100644 test/unit_tests/braket/circuits/test_quantum_operator.py delete mode 100644 test/unit_tests/braket/circuits/test_quantum_operator_helpers.py delete mode 100644 test/unit_tests/braket/circuits/test_result_type.py delete mode 100644 test/unit_tests/braket/circuits/test_result_types.py delete mode 100644 test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py delete mode 100644 test/unit_tests/braket/devices/test_local_simulator.py delete mode 100644 test/unit_tests/braket/jobs/job_module.py delete mode 100644 test/unit_tests/braket/jobs/local/test_local_job.py delete mode 100644 test/unit_tests/braket/jobs/local/test_local_job_container.py delete mode 100644 test/unit_tests/braket/jobs/local/test_local_job_container_setup.py delete mode 100644 test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py delete mode 100644 test/unit_tests/braket/jobs/metrics_data/test_cwl_metrics_fetcher.py delete mode 100644 test/unit_tests/braket/jobs/metrics_data/test_log_metrics_parser.py delete mode 100644 test/unit_tests/braket/jobs/test_data_persistence.py delete mode 100644 test/unit_tests/braket/jobs/test_environment_variables.py delete mode 100644 test/unit_tests/braket/jobs/test_hybrid_job.py delete mode 100644 test/unit_tests/braket/jobs/test_image_uris.py delete mode 100644 test/unit_tests/braket/jobs/test_metrics.py delete mode 100644 test/unit_tests/braket/jobs/test_quantum_job_creation.py delete mode 100644 test/unit_tests/braket/jobs/test_serialization.py delete mode 100644 test/unit_tests/braket/parametric/test_free_parameter.py delete mode 100644 test/unit_tests/braket/parametric/test_free_parameter_expression.py delete mode 100644 test/unit_tests/braket/pulse/ast/test_approximation_parser.py delete mode 100644 test/unit_tests/braket/pulse/test_frame.py delete mode 100644 test/unit_tests/braket/pulse/test_port.py delete mode 100644 test/unit_tests/braket/pulse/test_pulse_sequence.py delete mode 100644 test/unit_tests/braket/pulse/test_waveforms.py delete mode 100644 test/unit_tests/braket/quantum_information/test_pauli_string.py delete mode 100644 test/unit_tests/braket/registers/test_qubit.py delete mode 100644 test/unit_tests/braket/registers/test_qubit_set.py delete mode 100644 test/unit_tests/braket/tasks/test_analog_hamiltonian_simulation_task_result.py delete mode 100644 test/unit_tests/braket/tasks/test_annealing_quantum_task_result.py delete mode 100644 test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py delete mode 100644 test/unit_tests/braket/tasks/test_local_quantum_task.py delete mode 100644 test/unit_tests/braket/tasks/test_local_quantum_task_batch.py delete mode 100644 test/unit_tests/braket/tasks/test_photonic_model_quantum_task_result.py delete mode 100644 test/unit_tests/braket/tasks/test_tasks_init.py delete mode 100644 test/unit_tests/braket/test_imports.py delete mode 100644 test/unit_tests/braket/test_ipython_utils.py delete mode 100755 test/unit_tests/braket/timings/test_time_series.py delete mode 100644 test/unit_tests/braket/tracking/test_pricing.py delete mode 100644 test/unit_tests/braket/tracking/test_tracker.py delete mode 100644 test/unit_tests/braket/tracking/test_tracking_context.py diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index 5f56cc65..00000000 --- a/doc/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = python -msphinx -SPHINXPROJ = amazon-braket-sdk -SOURCEDIR = . -BUILDDIR = ../build/documentation - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/doc/conf.py b/doc/conf.py deleted file mode 100644 index 8a16ca23..00000000 --- a/doc/conf.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Sphinx configuration.""" - -import datetime -from importlib.metadata import version - -# Sphinx configuration below. -project = "amazon-braket-sdk" -version = version(project) -release = version -copyright = f"{datetime.datetime.now().year}, Amazon.com" - -extensions = [ - "sphinxcontrib.apidoc", - "sphinx.ext.autodoc", - "sphinx.ext.viewcode", - "sphinx.ext.napoleon", - "sphinx.ext.todo", - "sphinx.ext.coverage", -] - -source_suffix = ".rst" -master_doc = "index" - -autoclass_content = "both" -autodoc_member_order = "bysource" -default_role = "py:obj" - -html_theme = "sphinx_rtd_theme" -htmlhelp_basename = f"{project}doc" - -language = "en" - -napoleon_use_rtype = False - -apidoc_module_dir = "../src/braket" -apidoc_output_dir = "_apidoc" -apidoc_excluded_paths = ["../test"] -apidoc_separate_modules = True -apidoc_module_first = True -apidoc_extra_args = ["-f", "--implicit-namespaces", "-H", "API Reference"] diff --git a/src/braket/experimental/autoqasm/doc/decorators.md b/doc/decorators.md similarity index 100% rename from src/braket/experimental/autoqasm/doc/decorators.md rename to doc/decorators.md diff --git a/doc/examples-adv-circuits-algorithms.rst b/doc/examples-adv-circuits-algorithms.rst deleted file mode 100644 index 23de8a04..00000000 --- a/doc/examples-adv-circuits-algorithms.rst +++ /dev/null @@ -1,50 +0,0 @@ -################################ -Advanced circuits and algorithms -################################ - -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 -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 -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. - -********************************************************************************************************************************************************************************************* -`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. diff --git a/doc/examples-braket-features.rst b/doc/examples-braket-features.rst deleted file mode 100644 index 25c088ab..00000000 --- a/doc/examples-braket-features.rst +++ /dev/null @@ -1,47 +0,0 @@ -################################ -Amazon Braket features -################################ - -Learn more about the indivudal features of Amazon Braket. - -.. toctree:: - :maxdepth: 2 - -***************************************************************************************************************************************************************************************************************************************************************** -`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. - -*********************************************************************************************************************************************************************** -`Allocating Qubits on QPU Devices `_ -*********************************************************************************************************************************************************************** - -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, -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. -You will learn about how TN1 works, how to use it, and which problems are best suited to run on TN1. - - -************************************************************************************************************************************************************************* -`Simulating noise on Amazon Braket `_ -************************************************************************************************************************************************************************* - -This notebook provides a detailed overview of noise simulation on Amazon Braket. -You will learn how to define noise channels, apply noise to new or existing circuits, and run those circuits -on the Amazon Braket noise simulators. diff --git a/doc/examples-getting-started.rst b/doc/examples-getting-started.rst deleted file mode 100644 index 64c6939a..00000000 --- a/doc/examples-getting-started.rst +++ /dev/null @@ -1,58 +0,0 @@ -############################## -Getting started -############################## - -Get started on Amazon Braket with some introductory examples. - -.. toctree:: - :maxdepth: 2 - -********************************************************************************************************************************************************* -`Getting started `_ -********************************************************************************************************************************************************* - -A hello-world tutorial that shows you how to build a simple circuit and run it on a local simulator. - -****************************************************************************************************************************************************************************************************************************** -`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, -and quantum metrology. - -**Note:** When a circuit is ran using a simulator, customers are required to use contiguous qubits/indices. - -********************************************************************************************************************************************************************************************************************************* -`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. - -****************************************************************************************************************************************************************************************************************************************************** -`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 -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, -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 -to the receiver (aka Bob), who is then able to decode the full two-bit message. diff --git a/doc/examples-hybrid-jobs.rst b/doc/examples-hybrid-jobs.rst deleted file mode 100644 index 56a1d9ec..00000000 --- a/doc/examples-hybrid-jobs.rst +++ /dev/null @@ -1,33 +0,0 @@ -################################ -Amazon Braket Hybrid Jobs -################################ - -Learn more about hybrid jobs on Amazon Braket. - -.. toctree:: - :maxdepth: 2 - -************************************************************************************************************************************************************************************************ -`Creating your first Hybrid Job `_ -************************************************************************************************************************************************************************************************ - -This tutorial shows how to run your first Amazon Braket Hybrid Job. - -********************************************************************************************************************************************************************************************************************************************************************* -`Quantum machine learning in Amazon Braket Hybrid Jobs `_ -********************************************************************************************************************************************************************************************************************************************************************* - -This notebook demonstrates a typical quantum machine learning workflow, including uploading data, monitoring training, and tuning hyperparameters. - -*************************************************************************************************************************************************************************************************************************** -`Using Pennylane with Braket Hybrid Jobs `_ -*************************************************************************************************************************************************************************************************************************** - -In this tutorial, we use PennyLane within Amazon Braket Hybrid Jobs to run the Quantum Approximate Optimization Algorithm (QAOA) on a Max-Cut problem. - -****************************************************************************************************************************************************************************** -`Bring your own container `_ -****************************************************************************************************************************************************************************** - -Amazon Braket has pre-configured containers for executing Amazon Braket Hybrid Jobs, which are sufficient for many use cases involving the Braket SDK and PennyLane. -However, if we want to use custom packages outside the scope of pre-configured containers, we have the ability to supply a custom-built container. In this tutorial, we show how to use Braket Hybrid Jobs to train a quantum machine learning model using BYOC (Bring Your Own Container). diff --git a/doc/examples-hybrid-quantum.rst b/doc/examples-hybrid-quantum.rst deleted file mode 100644 index 9a0a8efb..00000000 --- a/doc/examples-hybrid-quantum.rst +++ /dev/null @@ -1,29 +0,0 @@ -################################ -Hybrid quantum algorithms -################################ - -Learn more about hybrid quantum algorithms. - -.. toctree:: - :maxdepth: 2 - -************************************************************************************************************************************* -`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). - -**************************************************************************************************************************************************************** -`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. diff --git a/doc/examples-ml-pennylane.rst b/doc/examples-ml-pennylane.rst deleted file mode 100644 index 1aa57cc4..00000000 --- a/doc/examples-ml-pennylane.rst +++ /dev/null @@ -1,47 +0,0 @@ -######################################################## -Quantum machine learning and optimization with PennyLane -######################################################## - -Learn more about how to combine PennyLane with Amazon Braket. - -.. toctree:: - :maxdepth: 2 - -************************************************************************************************************************************************************************** -`Combining PennyLane with Amazon Braket `_ -************************************************************************************************************************************************************************** - -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 -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, -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, -making optimization more efficient. diff --git a/doc/examples.rst b/doc/examples.rst deleted file mode 100644 index 93aac757..00000000 --- a/doc/examples.rst +++ /dev/null @@ -1,16 +0,0 @@ -######## -Examples -######## - -There are several examples available in the Amazon Braket repo: -https://github.com/amazon-braket/amazon-braket-examples. - -.. toctree:: - :maxdepth: 2 - - examples-getting-started.rst - examples-braket-features.rst - examples-adv-circuits-algorithms.rst - examples-hybrid-quantum.rst - examples-ml-pennylane.rst - examples-hybrid-jobs.rst diff --git a/doc/getting-started.rst b/doc/getting-started.rst deleted file mode 100644 index 31493b78..00000000 --- a/doc/getting-started.rst +++ /dev/null @@ -1,39 +0,0 @@ -################################################# -Getting Started with the Amazon Braket Python SDK -################################################# - -It is easy to get started with Amazon Braket Python SDK. You can get -started using an Amazon Braket notebook instance or using -your own environment. - -For more information about Amazon Braket, see the full set of documentation -at https://docs.aws.amazon.com/braket/index.html. - -.. toctree:: - :maxdepth: 2 - -************************************************ -Getting started using an Amazon Braket notebook -************************************************ - -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: - -1. `Enable Amazon Braket `_. -2. `Create an Amazon Braket notebook instance `_. -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. - -*********************************** -Getting started in your environment -*********************************** - -You can install the Amazon Braket Python SDK in your environment -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 deleted file mode 100644 index 54d10b54..00000000 --- a/doc/index.rst +++ /dev/null @@ -1,43 +0,0 @@ -######################## -Amazon Braket Python SDK -######################## - -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 -homepage is in Github https://github.com/amazon-braket/amazon-braket-sdk-python. The project -includes SDK source, installation instructions, and other information. - -*************** -Getting Started -*************** - -.. toctree:: - :maxdepth: 2 - - getting-started - - -******** -Examples -******** - -Explore Amazon Braket examples. - -.. toctree:: - :maxdepth: 3 - - examples.rst - - -*************** -Python SDK APIs -*************** - -The Amazon Braket Python SDK APIs: - -.. toctree:: - :maxdepth: 2 - - _apidoc/modules diff --git a/doc/requirements.txt b/doc/requirements.txt deleted file mode 100644 index b80e6baf..00000000 --- a/doc/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -sphinx>7 -sphinx-rtd-theme>=1.3.0 -sphinxcontrib-apidoc diff --git a/examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb b/examples/1_Getting_started_with_AutoQASM.ipynb similarity index 100% rename from examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb rename to examples/1_Getting_started_with_AutoQASM.ipynb diff --git a/examples/autoqasm/2_Expressing_classical_control_flow.ipynb b/examples/2_Expressing_classical_control_flow.ipynb similarity index 100% rename from examples/autoqasm/2_Expressing_classical_control_flow.ipynb rename to examples/2_Expressing_classical_control_flow.ipynb diff --git a/examples/autoqasm/3_1_Iterative_phase_estimation.ipynb b/examples/3_1_Iterative_phase_estimation.ipynb similarity index 100% rename from examples/autoqasm/3_1_Iterative_phase_estimation.ipynb rename to examples/3_1_Iterative_phase_estimation.ipynb diff --git a/examples/autoqasm/3_2_magic_state_distillation.ipynb b/examples/3_2_magic_state_distillation.ipynb similarity index 100% rename from examples/autoqasm/3_2_magic_state_distillation.ipynb rename to examples/3_2_magic_state_distillation.ipynb diff --git a/examples/autoqasm/4_Native_programming.ipynb b/examples/4_Native_programming.ipynb similarity index 100% rename from examples/autoqasm/4_Native_programming.ipynb rename to examples/4_Native_programming.ipynb diff --git a/examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb b/examples/5_Pulse_programming_and_dynamical_decoupling.ipynb similarity index 100% rename from examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb rename to examples/5_Pulse_programming_and_dynamical_decoupling.ipynb diff --git a/examples/autoqasm/6_Customize_gate_calibrations.ipynb b/examples/6_Customize_gate_calibrations.ipynb similarity index 100% rename from examples/autoqasm/6_Customize_gate_calibrations.ipynb rename to examples/6_Customize_gate_calibrations.ipynb diff --git a/examples/bell.py b/examples/bell.py deleted file mode 100644 index e42b3b01..00000000 --- a/examples/bell.py +++ /dev/null @@ -1,23 +0,0 @@ -# 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.aws import AwsDevice -from braket.circuits import Circuit -from braket.devices import Devices - -device = AwsDevice(Devices.Amazon.SV1) - -# https://wikipedia.org/wiki/Bell_state -bell = Circuit().h(0).cnot(0, 1) -task = device.run(bell, shots=100) -print(task.result().measurement_counts) diff --git a/examples/bell_result_types.py b/examples/bell_result_types.py deleted file mode 100644 index 2bcb87b7..00000000 --- a/examples/bell_result_types.py +++ /dev/null @@ -1,60 +0,0 @@ -# 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, Observable -from braket.devices import LocalSimulator - -device = LocalSimulator() - -print("Example for shots=0") -# Result types can be requested in the circuit -# Example of result types for shots=0 -bell = ( - Circuit() - .h(0) - .cnot(0, 1) - .probability(target=[0]) - .expectation(observable=Observable.Z(), target=[1]) - .amplitude(state=["00"]) - .state_vector() -) - -# State vector and amplitude can only be requested when shots=0 for a simulator -# When shots=0 for a simulator, probability, expectation, variance are the exact values, -# not calculated from measurements -# Users cannot request Sample as a result when shots=0 -result = device.run(bell).result() -print("Marginal probability for target 0 in computational basis:", result.values[0]) -print("Expectation of target 1 in the computational basis:", result.values[1]) -print("Amplitude of state 00:", result.values[2]) -print("State vector:", result.values[3]) - -print("\nExample for shots>0") -# Example of result types for shots > 0 -bell = ( - 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]) -) - -# When shots>0 for a simulator, probability, expectation, variance are calculated from measurements -# Users can request sample as a result when shots > 0 -result = device.run(bell, shots=100).result() -print("Expectation of target 0, 1 in the basis of Pauli-Y @ Pauli-X:", result.values[0]) -print("Variance of target 0, 1 in the basis of Pauli-Y @ Pauli-X:", result.values[1]) -print("Samples of target 0, 1 in the basis of Pauli-Y @ Pauli-X:", result.values[2]) - -# Probability, sample, expectation, and variance are also supported for QPU devices diff --git a/examples/debug_bell.py b/examples/debug_bell.py deleted file mode 100644 index 00d68a45..00000000 --- a/examples/debug_bell.py +++ /dev/null @@ -1,39 +0,0 @@ -# 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 logging -import sys - -from braket.aws import AwsDevice -from braket.circuits import Circuit -from braket.devices import Devices - -logger = logging.getLogger("newLogger") # create new logger -logger.addHandler(logging.StreamHandler(stream=sys.stdout)) # configure to print to sys.stdout -logger.setLevel(logging.DEBUG) # print to sys.stdout all log messages with level DEBUG or above - -device = AwsDevice(Devices.Amazon.SV1) - -bell = Circuit().h(0).cnot(0, 1) -# pass in logger to device.run, enabling debugging logs to print to console -logger.info( - device.run( - bell, - shots=100, - poll_timeout_seconds=120, - poll_interval_seconds=0.25, - logger=logger, - ) - .result() - .measurement_counts -) diff --git a/examples/hybrid_job.py b/examples/hybrid_job.py deleted file mode 100644 index 7f54c995..00000000 --- a/examples/hybrid_job.py +++ /dev/null @@ -1,56 +0,0 @@ -# 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.aws import AwsDevice -from braket.circuits import Circuit, FreeParameter, Observable -from braket.devices import Devices -from braket.jobs import get_job_device_arn, hybrid_job -from braket.jobs.metrics import log_metric - - -@hybrid_job( - device=Devices.Amazon.SV1, - wait_until_complete=True, - # If you want to run the job in a device reservation window, - # change the device to the one you've reserved, - # uncomment the following line and fill in your reservation ARN - # reservation_arn="" -) -def run_hybrid_job(num_tasks=1): - # declare AwsDevice within the hybrid job - device = AwsDevice(get_job_device_arn()) - - # create a parametric circuit - circ = Circuit() - circ.rx(0, FreeParameter("theta")) - circ.cnot(0, 1) - circ.expectation(observable=Observable.X(), target=0) - - # initial parameter - theta = 0.0 - - for i in range(num_tasks): - # run task, specifying input parameter - task = device.run(circ, shots=100, inputs={"theta": theta}) - exp_val = task.result().values[0] - - # modify the parameter (e.g. gradient descent) - theta += exp_val - - log_metric(metric_name="exp_val", value=exp_val, iteration_number=i) - - return {"final_theta": theta, "final_exp_val": exp_val} - - -job = run_hybrid_job(num_tasks=5) -print(job.result()) diff --git a/examples/hybrid_job_script.py b/examples/hybrid_job_script.py deleted file mode 100644 index b544ff9d..00000000 --- a/examples/hybrid_job_script.py +++ /dev/null @@ -1,56 +0,0 @@ -# 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.aws import AwsDevice, AwsQuantumJob -from braket.circuits import Circuit, FreeParameter, Observable -from braket.devices import Devices -from braket.jobs import get_job_device_arn, save_job_result -from braket.jobs.metrics import log_metric - - -def run_hybrid_job(num_tasks: int): - # use the device specified in the hybrid job - device = AwsDevice(get_job_device_arn()) - - # create a parametric circuit - circ = Circuit() - circ.rx(0, FreeParameter("theta")) - circ.cnot(0, 1) - circ.expectation(observable=Observable.X(), target=0) - - # initial parameter - theta = 0.0 - - for i in range(num_tasks): - # run task, specifying input parameter - task = device.run(circ, shots=100, inputs={"theta": theta}) - exp_val = task.result().values[0] - - # modify the parameter (e.g. gradient descent) - theta += exp_val - - log_metric(metric_name="exp_val", value=exp_val, iteration_number=i) - - save_job_result({"final_theta": theta, "final_exp_val": exp_val}) - - -if __name__ == "__main__": - job = AwsQuantumJob.create( - device=Devices.Amazon.SV1, # choose priority device - source_module="hybrid_job_script.py", # specify file or directory with code to run - entry_point="hybrid_job_script:run_hybrid_job", # specify function to run - hyperparameters={"num_tasks": 5}, - wait_until_complete=True, - ) - print(job.result()) diff --git a/examples/autoqasm/ionq_gates.py b/examples/ionq_gates.py similarity index 100% rename from examples/autoqasm/ionq_gates.py rename to examples/ionq_gates.py diff --git a/examples/local_bell.py b/examples/local_bell.py deleted file mode 100644 index c15b1b21..00000000 --- a/examples/local_bell.py +++ /dev/null @@ -1,20 +0,0 @@ -# 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.devices import LocalSimulator - -device = LocalSimulator() - -bell = Circuit().h(0).cnot(0, 1) -print(device.run(bell, shots=100).result().measurement_counts) diff --git a/examples/local_noise_simulation.py b/examples/local_noise_simulation.py deleted file mode 100644 index 0857bfd0..00000000 --- a/examples/local_noise_simulation.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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, Noise -from braket.devices import LocalSimulator - -device = LocalSimulator("braket_dm") - -circuit = Circuit().x(0).x(1).bit_flip(0, probability=0.1) -print("First example: ") -print(circuit) -print(device.run(circuit, shots=1000).result().measurement_counts) - - -circuit = Circuit().x(0).x(1) -noise = Noise.BitFlip(probability=0.1) -circuit.apply_gate_noise(noise) -print("Second example: ") -print(circuit) -print(device.run(circuit, shots=1000).result().measurement_counts) diff --git a/examples/reservation.py b/examples/reservation.py deleted file mode 100644 index 83be87eb..00000000 --- a/examples/reservation.py +++ /dev/null @@ -1,35 +0,0 @@ -# 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.aws import AwsDevice, DirectReservation -from braket.circuits import Circuit -from braket.devices import Devices - -bell = Circuit().h(0).cnot(0, 1) -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. -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/experimental/autoqasm/README.md b/src/autoqasm/README.md similarity index 100% rename from src/braket/experimental/autoqasm/README.md rename to src/autoqasm/README.md diff --git a/src/braket/experimental/autoqasm/__init__.py b/src/autoqasm/__init__.py similarity index 100% rename from src/braket/experimental/autoqasm/__init__.py rename to src/autoqasm/__init__.py diff --git a/src/braket/experimental/autoqasm/api.py b/src/autoqasm/api.py similarity index 100% rename from src/braket/experimental/autoqasm/api.py rename to src/autoqasm/api.py diff --git a/src/braket/experimental/autoqasm/constants.py b/src/autoqasm/constants.py similarity index 100% rename from src/braket/experimental/autoqasm/constants.py rename to src/autoqasm/constants.py diff --git a/src/braket/experimental/autoqasm/converters/__init__.py b/src/autoqasm/converters/__init__.py similarity index 100% rename from src/braket/experimental/autoqasm/converters/__init__.py rename to src/autoqasm/converters/__init__.py diff --git a/src/braket/experimental/autoqasm/converters/assignments.py b/src/autoqasm/converters/assignments.py similarity index 100% rename from src/braket/experimental/autoqasm/converters/assignments.py rename to src/autoqasm/converters/assignments.py diff --git a/src/braket/experimental/autoqasm/converters/break_statements.py b/src/autoqasm/converters/break_statements.py similarity index 100% rename from src/braket/experimental/autoqasm/converters/break_statements.py rename to src/autoqasm/converters/break_statements.py diff --git a/src/braket/experimental/autoqasm/converters/comparisons.py b/src/autoqasm/converters/comparisons.py similarity index 100% rename from src/braket/experimental/autoqasm/converters/comparisons.py rename to src/autoqasm/converters/comparisons.py diff --git a/src/braket/experimental/autoqasm/converters/return_statements.py b/src/autoqasm/converters/return_statements.py similarity index 100% rename from src/braket/experimental/autoqasm/converters/return_statements.py rename to src/autoqasm/converters/return_statements.py diff --git a/src/braket/experimental/autoqasm/errors.py b/src/autoqasm/errors.py similarity index 100% rename from src/braket/experimental/autoqasm/errors.py rename to src/autoqasm/errors.py diff --git a/src/braket/experimental/autoqasm/instructions/__init__.py b/src/autoqasm/instructions/__init__.py similarity index 100% rename from src/braket/experimental/autoqasm/instructions/__init__.py rename to src/autoqasm/instructions/__init__.py diff --git a/src/braket/experimental/autoqasm/instructions/gates.py b/src/autoqasm/instructions/gates.py similarity index 100% rename from src/braket/experimental/autoqasm/instructions/gates.py rename to src/autoqasm/instructions/gates.py diff --git a/src/braket/experimental/autoqasm/instructions/instructions.py b/src/autoqasm/instructions/instructions.py similarity index 100% rename from src/braket/experimental/autoqasm/instructions/instructions.py rename to src/autoqasm/instructions/instructions.py diff --git a/src/braket/experimental/autoqasm/instructions/measurements.py b/src/autoqasm/instructions/measurements.py similarity index 100% rename from src/braket/experimental/autoqasm/instructions/measurements.py rename to src/autoqasm/instructions/measurements.py diff --git a/src/braket/experimental/autoqasm/instructions/qubits.py b/src/autoqasm/instructions/qubits.py similarity index 100% rename from src/braket/experimental/autoqasm/instructions/qubits.py rename to src/autoqasm/instructions/qubits.py diff --git a/src/braket/experimental/autoqasm/operators/__init__.py b/src/autoqasm/operators/__init__.py similarity index 100% rename from src/braket/experimental/autoqasm/operators/__init__.py rename to src/autoqasm/operators/__init__.py diff --git a/src/braket/experimental/autoqasm/operators/assignments.py b/src/autoqasm/operators/assignments.py similarity index 100% rename from src/braket/experimental/autoqasm/operators/assignments.py rename to src/autoqasm/operators/assignments.py diff --git a/src/braket/experimental/autoqasm/operators/comparisons.py b/src/autoqasm/operators/comparisons.py similarity index 100% rename from src/braket/experimental/autoqasm/operators/comparisons.py rename to src/autoqasm/operators/comparisons.py diff --git a/src/braket/experimental/autoqasm/operators/conditional_expressions.py b/src/autoqasm/operators/conditional_expressions.py similarity index 100% rename from src/braket/experimental/autoqasm/operators/conditional_expressions.py rename to src/autoqasm/operators/conditional_expressions.py diff --git a/src/braket/experimental/autoqasm/operators/control_flow.py b/src/autoqasm/operators/control_flow.py similarity index 100% rename from src/braket/experimental/autoqasm/operators/control_flow.py rename to src/autoqasm/operators/control_flow.py diff --git a/src/braket/experimental/autoqasm/operators/data_structures.py b/src/autoqasm/operators/data_structures.py similarity index 100% rename from src/braket/experimental/autoqasm/operators/data_structures.py rename to src/autoqasm/operators/data_structures.py diff --git a/src/braket/experimental/autoqasm/operators/exceptions.py b/src/autoqasm/operators/exceptions.py similarity index 100% rename from src/braket/experimental/autoqasm/operators/exceptions.py rename to src/autoqasm/operators/exceptions.py diff --git a/src/braket/experimental/autoqasm/operators/logical.py b/src/autoqasm/operators/logical.py similarity index 100% rename from src/braket/experimental/autoqasm/operators/logical.py rename to src/autoqasm/operators/logical.py diff --git a/src/braket/experimental/autoqasm/operators/return_statements.py b/src/autoqasm/operators/return_statements.py similarity index 100% rename from src/braket/experimental/autoqasm/operators/return_statements.py rename to src/autoqasm/operators/return_statements.py diff --git a/src/braket/experimental/autoqasm/operators/slices.py b/src/autoqasm/operators/slices.py similarity index 100% rename from src/braket/experimental/autoqasm/operators/slices.py rename to src/autoqasm/operators/slices.py diff --git a/src/braket/experimental/autoqasm/operators/utils.py b/src/autoqasm/operators/utils.py similarity index 100% rename from src/braket/experimental/autoqasm/operators/utils.py rename to src/autoqasm/operators/utils.py diff --git a/src/braket/experimental/autoqasm/program/__init__.py b/src/autoqasm/program/__init__.py similarity index 100% rename from src/braket/experimental/autoqasm/program/__init__.py rename to src/autoqasm/program/__init__.py diff --git a/src/braket/experimental/autoqasm/program/gate_calibrations.py b/src/autoqasm/program/gate_calibrations.py similarity index 100% rename from src/braket/experimental/autoqasm/program/gate_calibrations.py rename to src/autoqasm/program/gate_calibrations.py diff --git a/src/braket/experimental/autoqasm/program/pragmas.py b/src/autoqasm/program/pragmas.py similarity index 100% rename from src/braket/experimental/autoqasm/program/pragmas.py rename to src/autoqasm/program/pragmas.py diff --git a/src/braket/experimental/autoqasm/program/program.py b/src/autoqasm/program/program.py similarity index 100% rename from src/braket/experimental/autoqasm/program/program.py rename to src/autoqasm/program/program.py diff --git a/src/braket/experimental/autoqasm/program/serialization_properties.py b/src/autoqasm/program/serialization_properties.py similarity index 100% rename from src/braket/experimental/autoqasm/program/serialization_properties.py rename to src/autoqasm/program/serialization_properties.py diff --git a/src/braket/experimental/autoqasm/pulse/__init__.py b/src/autoqasm/pulse/__init__.py similarity index 100% rename from src/braket/experimental/autoqasm/pulse/__init__.py rename to src/autoqasm/pulse/__init__.py diff --git a/src/braket/experimental/autoqasm/pulse/pulse.py b/src/autoqasm/pulse/pulse.py similarity index 100% rename from src/braket/experimental/autoqasm/pulse/pulse.py rename to src/autoqasm/pulse/pulse.py diff --git a/src/braket/experimental/autoqasm/transpiler/__init__.py b/src/autoqasm/transpiler/__init__.py similarity index 100% rename from src/braket/experimental/autoqasm/transpiler/__init__.py rename to src/autoqasm/transpiler/__init__.py diff --git a/src/braket/experimental/autoqasm/transpiler/transpiler.py b/src/autoqasm/transpiler/transpiler.py similarity index 100% rename from src/braket/experimental/autoqasm/transpiler/transpiler.py rename to src/autoqasm/transpiler/transpiler.py diff --git a/src/braket/experimental/autoqasm/types/__init__.py b/src/autoqasm/types/__init__.py similarity index 100% rename from src/braket/experimental/autoqasm/types/__init__.py rename to src/autoqasm/types/__init__.py diff --git a/src/braket/experimental/autoqasm/types/conversions.py b/src/autoqasm/types/conversions.py similarity index 100% rename from src/braket/experimental/autoqasm/types/conversions.py rename to src/autoqasm/types/conversions.py diff --git a/src/braket/experimental/autoqasm/types/types.py b/src/autoqasm/types/types.py similarity index 100% rename from src/braket/experimental/autoqasm/types/types.py rename to src/autoqasm/types/types.py diff --git a/src/braket/_sdk/__init__.py b/src/braket/_sdk/__init__.py deleted file mode 100644 index b436b998..00000000 --- a/src/braket/_sdk/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# 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 ._version import __version__ # noqa: F401 diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py deleted file mode 100644 index fec1b4f0..00000000 --- a/src/braket/_sdk/_version.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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. - -"""Version information. -Version number (major.minor.patch[-label]) -""" - -__version__ = "1.78.1.dev0" diff --git a/src/braket/ahs/__init__.py b/src/braket/ahs/__init__.py deleted file mode 100644 index 5bf3cc61..00000000 --- a/src/braket/ahs/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# 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.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation # noqa: F401 -from braket.ahs.atom_arrangement import AtomArrangement, AtomArrangementItem, SiteType # 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 -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 deleted file mode 100644 index af02471e..00000000 --- a/src/braket/ahs/analog_hamiltonian_simulation.py +++ /dev/null @@ -1,155 +0,0 @@ -# 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 defaultdict -from functools import singledispatch - -import braket.ir.ahs as ir -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 - - -class AnalogHamiltonianSimulation: - LOCAL_DETUNING_PROPERTY = "local_detuning" - DRIVING_FIELDS_PROPERTY = "driving_fields" - - def __init__(self, register: AtomArrangement, hamiltonian: Hamiltonian) -> None: - """Creates an AnalogHamiltonianSimulation with a given setup, and terms. - - Args: - register (AtomArrangement): The initial atom arrangement for the simulation. - hamiltonian (Hamiltonian): The hamiltonian to simulate. - """ - self._register = register - self._hamiltonian = hamiltonian - - @property - def register(self) -> AtomArrangement: - """AtomArrangement: The initial atom arrangement for the simulation.""" - return self._register - - @property - def hamiltonian(self) -> Hamiltonian: - """Hamiltonian: The hamiltonian to simulate.""" - return self._hamiltonian - - def to_ir(self) -> ir.Program: - """Converts the Analog Hamiltonian Simulation into the canonical intermediate - representation. - - Returns: - ir.Program: A representation of the circuit in the IR format. - """ - return ir.Program( - setup=ir.Setup(ahs_register=self._register_to_ir()), - hamiltonian=self._hamiltonian_to_ir(), - ) - - def _register_to_ir(self) -> ir.AtomArrangement: - return ir.AtomArrangement( - sites=[site.coordinate for site in self.register], - filling=[1 if site.site_type == SiteType.FILLED else 0 for site in self.register], - ) - - def _hamiltonian_to_ir(self) -> ir.Hamiltonian: - terms = defaultdict(list) - for term in self.hamiltonian.terms: - term_type, term_ir = _get_term_ir(term) - terms[term_type].append(term_ir) - return ir.Hamiltonian( - drivingFields=terms[AnalogHamiltonianSimulation.DRIVING_FIELDS_PROPERTY], - localDetuning=terms[AnalogHamiltonianSimulation.LOCAL_DETUNING_PROPERTY], - ) - - 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. - - Args: - device (AwsDevice): The device for which to discretize the program. - - Returns: - AnalogHamiltonianSimulation: A discretized version of this program. - - Raises: - 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 - ): - raise DiscretizationError( - f"AwsDevice {device} does not accept {required_action_schema} action schema." - ) - - properties = DiscretizationProperties( - device.properties.paradigm.lattice, device.properties.paradigm.rydberg - ) - discretized_register = self.register.discretize(properties) - discretized_hamiltonian = self.hamiltonian.discretize(properties) - return AnalogHamiltonianSimulation( - register=discretized_register, hamiltonian=discretized_hamiltonian - ) - - -@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", - ), - ) diff --git a/src/braket/ahs/atom_arrangement.py b/src/braket/ahs/atom_arrangement.py deleted file mode 100644 index 24d4fc9a..00000000 --- a/src/braket/ahs/atom_arrangement.py +++ /dev/null @@ -1,129 +0,0 @@ -# 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 Iterator -from dataclasses import dataclass -from decimal import Decimal -from enum import Enum -from numbers import Number -from typing import Union - -import numpy as np - -from braket.ahs.discretization_types import DiscretizationError, DiscretizationProperties - - -class SiteType(Enum): - VACANT = "Vacant" - FILLED = "Filled" - - -@dataclass -class AtomArrangementItem: - """Represents an item (coordinate and metadata) in an atom arrangement.""" - - coordinate: tuple[Number, Number] - site_type: SiteType - - def _validate_coordinate(self) -> None: - if len(self.coordinate) != 2: - raise ValueError(f"{self.coordinate} must be of length 2") - for idx, num in enumerate(self.coordinate): - if not isinstance(num, Number): - raise TypeError(f"{num} at position {idx} must be a number") - - def _validate_site_type(self) -> None: - allowed_site_types = {SiteType.FILLED, SiteType.VACANT} - if self.site_type not in allowed_site_types: - raise ValueError(f"{self.site_type} must be one of {allowed_site_types}") - - def __post_init__(self) -> None: - self._validate_coordinate() - self._validate_site_type() - - -class AtomArrangement: - def __init__(self): - """Represents a set of coordinates that can be used as a register to an - AnalogHamiltonianSimulation. - """ - self._sites = [] - - def add( - self, - coordinate: Union[tuple[Number, Number], np.ndarray], - site_type: SiteType = SiteType.FILLED, - ) -> AtomArrangement: - """Add a coordinate to the atom arrangement. - - Args: - coordinate (Union[tuple[Number, Number], ndarray]): The coordinate of the - 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). - """ - self._sites.append(AtomArrangementItem(tuple(coordinate), site_type)) - return self - - def coordinate_list(self, coordinate_index: Number) -> list[Number]: - """Returns all the coordinates at the given index. - - Args: - coordinate_index (Number): The index to get for each coordinate. - - Returns: - list[Number]:The list of coordinates at the given index. - - Example: - To get a list of all x-coordinates: coordinate_list(0) - To get a list of all y-coordinates: coordinate_list(1) - """ - return [site.coordinate[coordinate_index] for site in self._sites] - - def __iter__(self) -> Iterator: - return self._sites.__iter__() - - def __len__(self): - return self._sites.__len__() - - def discretize(self, properties: DiscretizationProperties) -> AtomArrangement: - """Creates a discretized version of the atom arrangement, - rounding all site coordinates to the closest multiple of the - resolution. The types of the sites are unchanged. - - Args: - 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. - """ - try: - position_res = properties.lattice.geometry.positionResolution - discretized_arrangement = AtomArrangement() - for site in self._sites: - new_coordinates = tuple( - 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 diff --git a/src/braket/ahs/discretization_types.py b/src/braket/ahs/discretization_types.py deleted file mode 100644 index 49efa0d3..00000000 --- a/src/braket/ahs/discretization_types.py +++ /dev/null @@ -1,38 +0,0 @@ -# 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 dataclasses import dataclass -from typing import Any - - -class DiscretizationError(Exception): - """Raised if the discretization of the numerical values of the AHS program fails.""" - - -@dataclass -class DiscretizationProperties: - """Capabilities of a device that represent the resolution with which the device can - implement the parameters. - - :parameter lattice (Any): configuration values for discretization of the lattice geometry, - including the position resolution. - :parameter rydberg (Any): configuration values for discretization of Rydberg fields. - - Examples: - lattice.geometry.positionResolution = Decimal("1E-7") - rydberg.rydbergGlobal.timeResolution = Decimal("1E-9") - rydberg.rydbergGlobal.phaseResolution = Decimal("5E-7") - """ - - lattice: Any - rydberg: Any diff --git a/src/braket/ahs/driving_field.py b/src/braket/ahs/driving_field.py deleted file mode 100644 index cbf01838..00000000 --- a/src/braket/ahs/driving_field.py +++ /dev/null @@ -1,186 +0,0 @@ -# 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 Union - -from braket.ahs.discretization_types import DiscretizationProperties -from braket.ahs.field import Field -from braket.ahs.hamiltonian import Hamiltonian -from braket.timings.time_series import StitchBoundaryCondition, TimeSeries - - -class DrivingField(Hamiltonian): - def __init__( - self, - amplitude: Union[Field, TimeSeries], - phase: Union[Field, TimeSeries], - detuning: Union[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 - in an AnalogHamiltonianSimulation, defined by the formula - - .. math:: - H_{drive} (t) := \frac{\Omega(t)}{2} e^{i \phi(t)} \left( - \sum_k |g_k \rangle \langle r_k| + |r_k \rangle \langle g_k| - \right) - \Delta(t) \sum_k{| r_k \rangle \langle r_k |} - - where - - :math:`\Omega(t)` is the global Rabi frequency in rad/s, - - :math:`\phi(t)` is the global phase in rad/s, - - :math:`\Delta(t)` is the global detuning in rad/s, - - :math:`|g_k \rangle` is the ground state of atom :math:`k`, - - :math:`|r_k \rangle` is the Rydberg state of atom :math:`k`. - - with the sum :math:`\sum_k` taken over all target atoms. - - Args: - amplitude (Union[Field, TimeSeries]): global amplitude (:math:`\Omega(t)`). - Time is in s, and value is in rad/s. - phase (Union[Field, TimeSeries]): global phase (:math:`\phi(t)`). - Time is in s, and value is in rad/s. - detuning (Union[Field, TimeSeries]): global detuning (:math:`\Delta(t)`). - Time is in s, and value is in rad/s. - """ - super().__init__() - self._amplitude = amplitude if isinstance(amplitude, Field) else Field(amplitude) - self._phase = phase if isinstance(phase, Field) else Field(phase) - self._detuning = detuning if isinstance(detuning, Field) else Field(detuning) - - @property - def terms(self) -> list[Hamiltonian]: - return [self] - - @property - def amplitude(self) -> Field: - r"""Field: The global amplitude (:math:`\Omega(t)`). Time is in s, and value is in rad/s.""" - return self._amplitude - - @property - def phase(self) -> Field: - r"""Field: The global phase (:math:`\phi(t)`). Time is in s, and value is in rad/s.""" - return self._phase - - @property - def detuning(self) -> Field: - r"""Field: global detuning (:math:`\Delta(t)`). Time is in s, and value is in rad/s.""" - return self._detuning - - def stitch( - self, other: DrivingField, boundary: StitchBoundaryCondition = StitchBoundaryCondition.MEAN - ) -> DrivingField: - """Stitches two driving fields based on TimeSeries.stitch method. - The time points of the second DrivingField are shifted such that the first time point of - the second DrifingField coincides with the last time point of the first DrivingField. - The boundary point value is handled according to StitchBoundaryCondition argument value. - - Args: - other (DrivingField): 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. - - 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) - - return DrivingField(amplitude=amplitude, detuning=detuning, phase=phase) - - def discretize(self, properties: DiscretizationProperties) -> DrivingField: - """Creates a discretized version of the Hamiltonian. - - Args: - properties (DiscretizationProperties): Capabilities of a device that represent the - resolution with which the device can implement the parameters. - - Returns: - DrivingField: A new discretized 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=amplitude_value_resolution, - ) - - phase_value_resolution = driving_parameters.phaseResolution - discretized_phase = self.phase.discretize( - time_resolution=time_resolution, - value_resolution=phase_value_resolution, - ) - - detuning_value_resolution = driving_parameters.detuningResolution - discretized_detuning = self.detuning.discretize( - time_resolution=time_resolution, - value_resolution=detuning_value_resolution, - ) - return DrivingField( - amplitude=discretized_amplitude, phase=discretized_phase, detuning=discretized_detuning - ) - - @staticmethod - def from_lists( - times: list[float], amplitudes: list[float], detunings: list[float], phases: list[float] - ) -> DrivingField: - """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. - - Args: - times (list[float]): The time points of the driving field - amplitudes (list[float]): The values of the amplitude - 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. - """ - if not (len(times) == len(amplitudes) == len(detunings) == len(phases)): - raise ValueError( - f"The lengths of the lists for times({len(times)}), amplitudes({len(amplitudes)}),\ - detunings({len(detunings)}) and phases({len(phases)}) are not equal" - ) - - amplitude = TimeSeries() - detuning = TimeSeries() - phase = TimeSeries() - - for t, amplitude_value, detuning_value, phase_value in zip( - times, amplitudes, detunings, phases - ): - amplitude.put(t, amplitude_value) - detuning.put(t, detuning_value) - phase.put(t, phase_value) - - drive = DrivingField(amplitude=amplitude, detuning=detuning, phase=phase) - - return drive diff --git a/src/braket/ahs/field.py b/src/braket/ahs/field.py deleted file mode 100644 index 1522b9d6..00000000 --- a/src/braket/ahs/field.py +++ /dev/null @@ -1,68 +0,0 @@ -# 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 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: - """A space and time dependent parameter of a program. - - Args: - time_series (TimeSeries): The time series representing this field. - pattern (Optional[Pattern]): The local pattern of real numbers. - """ - self._time_series = time_series - self._pattern = pattern - - @property - def time_series(self) -> TimeSeries: - """TimeSeries: The time series representing this field.""" - return self._time_series - - @property - def pattern(self) -> Optional[Pattern]: - """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, - ) -> Field: - """Creates a discretized version of the field, - where time, value and pattern are rounded to the - closest multiple of their corresponding resolutions. - - Args: - time_resolution (Optional[Decimal]): Time resolution - value_resolution (Optional[Decimal]): Value resolution - pattern_resolution (Optional[Decimal]): Pattern resolution - - Returns: - Field: A new discretized field. - """ - discretized_time_series = self.time_series.discretize(time_resolution, value_resolution) - if self.pattern is None: - 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 diff --git a/src/braket/ahs/hamiltonian.py b/src/braket/ahs/hamiltonian.py deleted file mode 100644 index 548befc5..00000000 --- a/src/braket/ahs/hamiltonian.py +++ /dev/null @@ -1,60 +0,0 @@ -# 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 Optional - -from braket.ahs.discretization_types import DiscretizationProperties - - -class Hamiltonian: - def __init__(self, terms: Optional[list[Hamiltonian]] = None): - r"""A Hamiltonian representing a system to be simulated. - - A Hamiltonian :math:`H` may be expressed as a sum of multiple terms - - .. math:: - H = \sum_i H_i - """ - self._terms = terms or [] - - @property - def terms(self) -> list[Hamiltonian]: - """list[Hamiltonian]: The list of terms in this Hamiltonian.""" - return self._terms - - def discretize(self, properties: DiscretizationProperties) -> Hamiltonian: - """Creates a discretized version of the Hamiltonian. - - Args: - properties (DiscretizationProperties): Capabilities of a device that represent the - resolution with which the device can implement the parameters. - - Returns: - Hamiltonian: A new discretized Hamiltonian. - """ - terms = [term.discretize(properties) for term in self.terms] - return Hamiltonian(terms=terms) - - def __iadd__(self, other: Hamiltonian) -> Hamiltonian: - if type(self) is not Hamiltonian: - raise ValueError(f"Unable to modify Hamiltonian of type {type(self)}") - self._terms.extend(other.terms) - return self - - def __add__(self, other: Hamiltonian) -> Hamiltonian: - terms = [] - terms.extend(self.terms) - terms.extend(other.terms) - return Hamiltonian(terms) diff --git a/src/braket/ahs/local_detuning.py b/src/braket/ahs/local_detuning.py deleted file mode 100644 index 00b42021..00000000 --- a/src/braket/ahs/local_detuning.py +++ /dev/null @@ -1,162 +0,0 @@ -# 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 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. - """ - local_detuning_parameters = properties.rydberg.rydbergLocal - time_resolution = ( - local_detuning_parameters.timeResolution if local_detuning_parameters else None - ) - discretized_magnitude = self.magnitude.discretize( - time_resolution=time_resolution, - ) - return LocalDetuning(discretized_magnitude) diff --git a/src/braket/ahs/pattern.py b/src/braket/ahs/pattern.py deleted file mode 100644 index 92637fe0..00000000 --- a/src/braket/ahs/pattern.py +++ /dev/null @@ -1,55 +0,0 @@ -# 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 decimal import Decimal -from numbers import Number -from typing import Optional - - -class Pattern: - def __init__(self, series: list[Number]): - """Represents the spatial dependence of a Field. - - Args: - series (list[Number]): A series of numbers representing the the local - pattern of real numbers. - """ - self._series = series - - @property - def series(self) -> list[Number]: - """list[Number]: A series of numbers representing the local - pattern of real numbers. - """ - return self._series - - 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 (Optional[Decimal]): Resolution of the discretization - - Returns: - Pattern: The new discretized pattern - """ - 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/ahs/shifting_field.py b/src/braket/ahs/shifting_field.py deleted file mode 100644 index 34e24191..00000000 --- a/src/braket/ahs/shifting_field.py +++ /dev/null @@ -1,20 +0,0 @@ -# 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.ahs.local_detuning import LocalDetuning - -# 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 diff --git a/src/braket/annealing/__init__.py b/src/braket/annealing/__init__.py deleted file mode 100644 index 7534b9d7..00000000 --- a/src/braket/annealing/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# 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. - -# Execute initialization code in circuit module -from braket.annealing.problem import Problem, ProblemType # noqa: F401 diff --git a/src/braket/annealing/problem.py b/src/braket/annealing/problem.py deleted file mode 100644 index 9515cbfa..00000000 --- a/src/braket/annealing/problem.py +++ /dev/null @@ -1,154 +0,0 @@ -# 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 enum import Enum - -import braket.ir.annealing as ir - - -class ProblemType(str, Enum): - """The type of annealing problem. - - QUBO: Quadratic Unconstrained Binary Optimization, with values 1 and 0 - - ISING: Ising model, with values +/-1 - """ - - QUBO = "QUBO" - ISING = "ISING" - - -class Problem: - """Represents an annealing problem.""" - - def __init__( - self, - problem_type: ProblemType, - linear: dict[int, float] | None = None, - quadratic: dict[tuple[int, int], float] | None = None, - ): - """Initializes a `Problem`. - - Args: - problem_type (ProblemType): The type of annealing 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, - as a map of variables to coefficient - - Examples: - >>> problem = Problem( - >>> ProblemType.ISING, - >>> linear={1: 3.14}, - >>> quadratic={(1, 2): 10.08}, - >>> ) - >>> problem.add_linear_term(2, 1.618).add_quadratic_term((3, 4), 1337) - """ - self._problem_type = problem_type - self._linear = linear or {} - self._quadratic = quadratic or {} - - @property - def problem_type(self) -> ProblemType: - """The type of annealing problem. - - Returns: - ProblemType: The type of annealing problem - """ - return self._problem_type - - @property - 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 - """ - return self._linear - - @property - 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, - as a map of variables to coefficient - """ - return self._quadratic - - def add_linear_term(self, term: int, coefficient: float) -> Problem: - """Adds a linear term to the problem. - - Args: - term (int): The variable of the linear term - coefficient (float): The coefficient of the linear term - - Returns: - Problem: This problem object - """ - self._linear[term] = coefficient - return self - - 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 - - Returns: - Problem: This problem object - """ - self._linear.update(coefficients) - return self - - 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 - coefficient (float): The coefficient of the quadratic term - - Returns: - Problem: This problem object - """ - self._quadratic[term] = coefficient - return self - - 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 - - Returns: - Problem: This problem object - """ - self._quadratic.update(coefficients) - return self - - def to_ir(self) -> Problem: - """Converts this problem into IR representation. - - Returns: - Problem: IR representation of this problem object - """ - return ir.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 - }, - ) diff --git a/src/braket/aws/__init__.py b/src/braket/aws/__init__.py deleted file mode 100644 index 3be348f3..00000000 --- a/src/braket/aws/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# 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.aws.aws_device import AwsDevice, AwsDeviceType # noqa: F401 -from braket.aws.aws_quantum_job import AwsQuantumJob # noqa: F401 -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_device.py b/src/braket/aws/aws_device.py deleted file mode 100644 index 70fc3c07..00000000 --- a/src/braket/aws/aws_device.py +++ /dev/null @@ -1,857 +0,0 @@ -# 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 importlib -import json -import os -import urllib.request -import warnings -from datetime import datetime -from enum import Enum -from typing import Any, ClassVar, Optional, Union - -from botocore.errorfactory import ClientError -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.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): - """Possible AWS device types""" - - SIMULATOR = "SIMULATOR" - QPU = "QPU" - - -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. - """ - - REGIONS = ("us-east-1", "us-west-1", "us-west-2", "eu-west-2") - - DEFAULT_SHOTS_QPU = 1000 - DEFAULT_SHOTS_SIMULATOR = 0 - DEFAULT_MAX_PARALLEL = 10 - - _GET_DEVICES_ORDER_BY_KEYS = frozenset({"arn", "name", "type", "provider_name", "status"}) - - _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", - "Cphaseshift": "CPhaseShift", - "Xy": "XY", - } - - def __init__( - self, - arn: str, - 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`. - 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, - the current `aws_session` connects to a Region other than the Region in which the QPU is - physically located. When this occurs, a cloned `aws_session` is created for the Region - the QPU is located in. - - See `braket.aws.aws_device.AwsDevice.REGIONS` for the AWS regions provider - devices are located in across the AWS Braket service. - This is not a device specific tuple. - """ - super().__init__(name=None, status=None) - self._arn = arn - self._gate_calibrations = None - self._properties = None - self._provider_name = None - self._poll_interval_seconds = None - self._type = 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, - task_specification: Union[ - 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, - *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 - 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. - 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. - shots (Optional[int]): The number of times to run the circuit or annealing problem. - Default is 1000 for QPUs and 0 for simulators. - poll_timeout_seconds (float): The polling timeout for `AwsQuantumTask.result()`, - in seconds. Default: 5 days. - poll_interval_seconds (Optional[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[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 - `dict[tuple[Gate, QubitSet], PulseSequence]]` for a 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, - 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. - - Examples: - >>> circuit = Circuit().h(0).cnot(0, 1) - >>> device = AwsDevice("arn1") - >>> device.run(circuit, ("bucket-foo", "key-bar")) - - >>> circuit = Circuit().h(0).cnot(0, 1) - >>> device = AwsDevice("arn2") - >>> device.run(task_specification=circuit, - >>> s3_destination_folder=("bucket-foo", "key-bar")) - - >>> circuit = Circuit().h(0).cnot(0, 1) - >>> device = AwsDevice("arn3") - >>> device.run(task_specification=circuit, - >>> s3_destination_folder=("bucket-foo", "key-bar"), disable_qubit_rewiring=True) - - >>> problem = Problem( - >>> ProblemType.ISING, - >>> linear={1: 3.14}, - >>> quadratic={(1, 2): 10.08}, - >>> ) - >>> device = AwsDevice("arn4") - >>> device.run(problem, ("bucket-foo", "key-bar"), - >>> device_parameters={ - >>> "providerLevelParameters": {"postprocessingType": "SAMPLING"}} - >>> ) - - 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( - self._aws_session, - self._arn, - task_specification, - s3_destination_folder - or ( - AwsSession.parse_s3_uri(os.environ.get("AMZN_BRAKET_TASK_RESULTS_S3_URI")) - if "AMZN_BRAKET_TASK_RESULTS_S3_URI" in os.environ - else None - ) - or (self._aws_session.default_bucket(), "tasks"), - shots if shots is not None else self._default_shots, - poll_timeout_seconds=poll_timeout_seconds, - poll_interval_seconds=poll_interval_seconds or self._poll_interval_seconds, - inputs=inputs, - gate_definitions=gate_definitions, - reservation_arn=reservation_arn, - *aws_quantum_task_args, - **aws_quantum_task_kwargs, - ) - - def run_batch( - self, - task_specifications: Union[ - Union[ - Circuit, - Problem, - OpenQasmProgram, - BlackbirdProgram, - PulseSequence, - AnalogHamiltonianSimulation, - ], - list[ - Union[ - Circuit, - Problem, - OpenQasmProgram, - BlackbirdProgram, - PulseSequence, - AnalogHamiltonianSimulation, - ] - ], - ], - s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, - shots: Optional[int] = None, - max_parallel: Optional[int] = 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[Union[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, - **aws_quantum_task_kwargs, - ) -> AwsQuantumTaskBatch: - """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. - 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. - shots (Optional[int]): The number of times to run the circuit or annealing problem. - Default is 1000 for QPUs and 0 for simulators. - max_parallel (Optional[int]): The maximum number of quantum tasks to run on AWS in parallel. - Batch creation will fail if this value is greater than the maximum allowed - concurrent quantum tasks on the device. Default: 10 - max_connections (int): The maximum number of connections in the Boto3 connection pool. - Also the maximum number of thread pool workers for the batch. Default: 100 - poll_timeout_seconds (float): The polling timeout for `AwsQuantumTask.result()`, - in seconds. Default: 5 days. - 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 - 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 - `dict[tuple[Gate, QubitSet], PulseSequence]]` for a 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 (Optional[str]): 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, - those tasks do not need to be created with the reservation ARN. - Default: None. - - Returns: - AwsQuantumTaskBatch: A batch containing all of the quantum tasks run - - 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) - for task_specification in task_specifications - ] - return AwsQuantumTaskBatch( - AwsSession.copy_session(self._aws_session, max_connections=max_connections), - self._arn, - task_specifications, - s3_destination_folder - or ( - AwsSession.parse_s3_uri(os.environ.get("AMZN_BRAKET_TASK_RESULTS_S3_URI")) - if "AMZN_BRAKET_TASK_RESULTS_S3_URI" in os.environ - else None - ) - or (self._aws_session.default_bucket(), "tasks"), - 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, - poll_interval_seconds=poll_interval_seconds or self._poll_interval_seconds, - inputs=inputs, - gate_definitions=gate_definitions, - reservation_arn=reservation_arn, - *aws_quantum_task_args, - **aws_quantum_task_kwargs, - ) - - def refresh_metadata(self) -> None: - """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: - device_region = AwsDevice.get_device_region(self._arn) - return ( - self._get_regional_device_session(session) - if device_region - else self._get_non_regional_device_session(session) - ) - - def _get_regional_device_session(self, session: AwsSession) -> AwsSession: - device_region = AwsDevice.get_device_region(self._arn) - region_session = ( - session - if session.region == device_region - else AwsSession.copy_session(session, device_region) - ) - 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 - - 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 - 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) - try: - self._populate_properties(region_session) - return region_session - except ClientError as e: - if e.response["Error"]["Code"] != "ResourceNotFoundException": - raise e - raise ValueError(f"QPU '{self._arn}' not found") - - def _populate_properties(self, session: AwsSession) -> None: - metadata = session.get_device(self._arn) - self._name = metadata.get("deviceName") - 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 - ) - self._topology_graph = None - self._frames = None - self._ports = None - - @property - def type(self) -> str: - """str: Return the device type""" - return self._type - - @property - def provider_name(self) -> str: - """str: Return the provider name""" - return self._provider_name - - @property - def aws_session(self) -> AwsSession: - return self._aws_session - - @property - def arn(self) -> str: - """str: Return the ARN of the device""" - return self._arn - - @property - def gate_calibrations(self) -> Optional[GateCalibrations]: - """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: - Optional[GateCalibrations]: The calibration object. Returns `None` if the data - is not present. - """ - if not self._gate_calibrations: - self._gate_calibrations = self.refresh_gate_calibrations() - return self._gate_calibrations - - @property - def is_available(self) -> bool: - """Returns true if the device is currently available. - - Returns: - bool: Return if the device is currently available. - """ - if self.status != "ONLINE": - return False - - is_available_result = False - - current_datetime_utc = datetime.utcnow() - for execution_window in self.properties.service.executionWindows: - weekday = current_datetime_utc.weekday() - current_time_utc = current_datetime_utc.time().replace(microsecond=0) - - if current_time_utc < execution_window.windowEndHour < execution_window.windowStartHour: - weekday = (weekday - 1) % 7 - - matched_day = execution_window.executionDay == ExecutionDay.EVERYDAY - matched_day = matched_day or ( - execution_window.executionDay == ExecutionDay.WEEKDAYS and weekday < 5 - ) - matched_day = matched_day or ( - execution_window.executionDay == ExecutionDay.WEEKENDS and weekday > 4 - ) - ordered_days = ( - ExecutionDay.MONDAY, - ExecutionDay.TUESDAY, - ExecutionDay.WEDNESDAY, - ExecutionDay.THURSDAY, - ExecutionDay.FRIDAY, - ExecutionDay.SATURDAY, - ExecutionDay.SUNDAY, - ) - matched_day = matched_day or ( - execution_window.executionDay in ordered_days - and ordered_days.index(execution_window.executionDay) == weekday - ) - - matched_time = ( - execution_window.windowStartHour < execution_window.windowEndHour - and execution_window.windowStartHour - <= current_time_utc - <= execution_window.windowEndHour - ) or ( - execution_window.windowEndHour < execution_window.windowStartHour - and ( - current_time_utc >= execution_window.windowStartHour - or current_time_utc <= execution_window.windowEndHour - ) - ) - - is_available_result = is_available_result or (matched_day and matched_time) - - return is_available_result - - @property - # TODO: Add a link to the boto3 docs - def properties(self) -> DeviceCapabilities: - """DeviceCapabilities: Return the device properties - - Please see `braket.device_schema` in amazon-braket-schemas-python_ - - .. _amazon-braket-schemas-python: https://github.com/aws/amazon-braket-schemas-python - """ - return self._properties - - @property - def topology_graph(self) -> DiGraph: - """DiGraph: topology of device as a networkx `DiGraph` object. - - Examples: - >>> import networkx as nx - >>> device = AwsDevice("arn1") - >>> nx.draw_kamada_kawai(device.topology_graph, with_labels=True, font_weight="bold") - - >>> topology_subgraph = device.topology_graph.subgraph(range(8)) - >>> nx.draw_kamada_kawai(topology_subgraph, with_labels=True, font_weight="bold") - - >>> print(device.topology_graph.edges) - - Returns: - DiGraph: topology of QPU as a networkx `DiGraph` object. `None` if the topology - is not available for the device. - """ - if not self._topology_graph: - self._topology_graph = self._construct_topology_graph() - return self._topology_graph - - def _construct_topology_graph(self) -> DiGraph: - """Construct topology graph. If no such metadata is available, return `None`. - - Returns: - DiGraph: topology of QPU as a networkx `DiGraph` object. - """ - if hasattr(self.properties, "paradigm") and isinstance( - self.properties.paradigm, GateModelQpuParadigmProperties - ): - if self.properties.paradigm.connectivity.fullyConnected: - return complete_graph( - int(self.properties.paradigm.qubitCount), create_using=DiGraph() - ) - adjacency_lists = self.properties.paradigm.connectivity.connectivityGraph - edges = [] - for item in adjacency_lists.items(): - 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( - self.properties.provider, DwaveProviderProperties - ): - edges = self.properties.provider.couplers - return from_edgelist(edges, create_using=DiGraph()) - else: - return None - - @property - def _default_shots(self) -> int: - return ( - AwsDevice.DEFAULT_SHOTS_QPU if "qpu" in self.arn else AwsDevice.DEFAULT_SHOTS_SIMULATOR - ) - - @property - def _default_max_parallel(self) -> int: - return AwsDevice.DEFAULT_MAX_PARALLEL - - def __repr__(self): - return f"Device('name': {self.name}, 'arn': {self.arn})" - - def __eq__(self, other: AwsDevice): - if isinstance(other, AwsDevice): - return self.arn == other.arn - return NotImplemented - - @property - def frames(self) -> dict[str, Frame]: - """Returns a dict mapping frame ids to the frame objects for predefined frames - for this device. - """ - self._update_pulse_properties() - 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. - """ - self._update_pulse_properties() - return self._ports or {} - - @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, - 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 - 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']) - - Args: - arns (Optional[list[str]]): device ARN filter, default is `None` - names (Optional[list[str]]): device name filter, default is `None` - types (Optional[list[AwsDeviceType]]): device type filter, default is `None` - QPUs will be searched for all regions and simulators will only be - searched for the region of the current session. - statuses (Optional[list[str]]): device status filter, default is `None`. When `None` - is used, RETIRED devices will not be returned. To include RETIRED devices in - the results, use a filter that includes "RETIRED" for this parameter. - provider_names (Optional[list[str]]): provider name filter, default is `None` - order_by (str): field to order result by, default is `name`. - Accepted values are ['arn', 'name', 'type', 'provider_name', 'status'] - 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 or AwsDeviceType) - aws_session = aws_session or AwsSession() - device_map = {} - session_region = aws_session.boto_session.region_name - search_regions = ( - (session_region,) if types == {AwsDeviceType.SIMULATOR} else AwsDevice.REGIONS - ) - for region in search_regions: - session_for_region = ( - aws_session - if region == session_region - else AwsSession.copy_session(aws_session, region) - ) - # Simulators are only instantiated in the same region as the AWS session - types_for_region = sorted( - types if region == session_region else types - {AwsDeviceType.SIMULATOR} - ) - try: - region_device_arns = [ - result["deviceArn"] - for result in session_for_region.search_devices( - arns=arns, - names=names, - types=types_for_region, - statuses=statuses, - provider_names=provider_names, - ) - ] - 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( - f"{error_code}: Unable to search region '{region}' for devices." - " Please check your settings or try again later." - f" Continuing without devices in '{region}'.", - stacklevel=1, - ) - - devices = list(device_map.values()) - devices.sort(key=lambda x: getattr(x, order_by)) - return devices - - def _update_pulse_properties(self) -> None: - if not hasattr(self.properties, "pulse") or not isinstance( - self.properties.pulse, PulseDeviceActionProperties - ): - 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()), - ) - - @staticmethod - def get_device_region(device_arn: str) -> str: - """Gets the region from a device arn. - - Args: - device_arn (str): The device ARN. - - Raises: - ValueError: Raised if the ARN is not properly formatted - - Returns: - str: the region of the ARN. - """ - try: - return device_arn.split(":")[3] - 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 - to run on a particular device. - - Returns: - QueueDepthInfo: Instance of the QueueDepth class representing queue depth - information for quantum tasks and hybrid jobs. - Queue depth refers to the number of quantum tasks and hybrid jobs queued on a particular - device. The normal tasks refers to the quantum tasks not submitted via Hybrid Jobs. - Whereas, the priority tasks refers to the total number of quantum tasks waiting to run - submitted through Amazon Braket Hybrid Jobs. These tasks run before the normal tasks. - If the queue depth for normal or priority quantum tasks is greater than 4000, we display - their respective queue depth as '>4000'. Similarly, for hybrid jobs if there are more - than 1000 jobs queued on a device, display the hybrid jobs queue depth as '>1000'. - Additionally, for QPUs if hybrid jobs queue depth is 0, we display information about - priority and count of the running hybrid job. - - Example: - Queue depth information for a running job. - >>> device = AwsDevice(Device.Amazon.SV1) - >>> print(device.queue_depth()) - QueueDepthInfo(quantum_tasks={: '0', - : '1'}, jobs='0 (1 prioritized job(s) running)') - - If more than 4000 quantum tasks queued on a device. - >>> device = AwsDevice(Device.Amazon.DM1) - >>> print(device.queue_depth()) - QueueDepthInfo(quantum_tasks={: '>4000', - : '2000'}, jobs='100') - """ - metadata = self.aws_session.get_device(arn=self.arn) - queue_metadata = metadata.get("deviceQueueInfo") - queue_info = {} - - for response in queue_metadata: - queue_name = response.get("queue") - queue_priority = response.get("queuePriority") - queue_size = response.get("queueSize") - - if queue_name == "QUANTUM_TASKS_QUEUE": - priority_enum = QueueType(queue_priority) - queue_info.setdefault("quantum_tasks", {})[priority_enum] = queue_size - else: - queue_info["jobs"] = queue_size - - return QueueDepthInfo(**queue_info) - - def refresh_gate_calibrations(self) -> Optional[GateCalibrations]: - """Refreshes the gate calibration data upon request. - - If the device does not have calibration data, None is returned. - - Raises: - URLError: If the URL provided returns a non 2xx response. - - Returns: - Optional[GateCalibrations]: the calibration data for the device. None - is returned if the device does not have a gate calibrations URL associated. - """ - if ( - hasattr(self.properties, "pulse") - and hasattr(self.properties.pulse, "nativeGateCalibrationsRef") - and self.properties.pulse.nativeGateCalibrationsRef - ): - try: - with urllib.request.urlopen( - self.properties.pulse.nativeGateCalibrationsRef.split("?")[0] - ) as f: - json_calibration_data = self._parse_calibration_json( - json.loads(f.read().decode("utf-8")) - ) - return GateCalibrations(json_calibration_data) - except urllib.error.URLError as e: - raise urllib.error.URLError( - f"Unable to reach {self.properties.pulse.nativeGateCalibrationsRef}" - ) from e - else: - return None - - def _parse_waveforms(self, waveforms_json: dict) -> dict: - waveforms = {} - for waveform in waveforms_json: - parsed_waveform = _parse_waveform_from_calibration_schema(waveforms_json[waveform]) - waveforms[parsed_waveform.id] = parsed_waveform - return waveforms - - def _parse_pulse_sequence( - self, calibration: dict, waveforms: dict[ArbitraryWaveform] - ) -> PulseSequence: - return PulseSequence._parse_from_calibration_schema(calibration, waveforms, self.frames) - - 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 - corresponding `dict[tuple[Gate, QubitSet], PulseSequence]` to represent the calibration data. - - Args: - calibration_data (dict): The data to be parsed. Based on - https://github.com/aws/amazon-braket-schemas-python/blob/main/src/braket/device_schema/pulse/native_gate_calibrations_v1.py. - - Returns: - dict[tuple[Gate, QubitSet], PulseSequence]: The - structured data based on a mapping of `tuple[Gate, Qubit]` to its calibration represented as a - `PulseSequence`. - - """ # noqa: E501 - waveforms = self._parse_waveforms(calibration_data["waveforms"]) - parsed_calibration_data = {} - for qubit_node in calibration_data["gates"]: - qubit = calibration_data["gates"][qubit_node] - for gate_node in qubit: - for gate in qubit[gate_node]: - gate_capitalized = getattr( - self, - f"_{self.provider_name.upper()}_GATES_TO_BRAKET", - {}, - ).get(gate_node.capitalize(), gate_node.capitalize()) - gate_obj = ( - getattr(importlib.import_module("braket.circuits.gates"), gate_capitalized) - if gate_capitalized is not None - else None - ) - qubits = QubitSet([int(x) for x in gate["qubits"]]) - if gate_obj is None: - # We drop out gates that are not implemented in the BDK - continue - - argument = None - if gate["arguments"]: - argument = ( - float(gate["arguments"][0]) - if _is_float(gate["arguments"][0]) - else FreeParameter(gate["arguments"][0]) - ) - gate_qubit_key = ( - (gate_obj(argument), qubits) if argument else (gate_obj(), qubits) - ) - gate_qubit_pulse = self._parse_pulse_sequence(gate["calibrations"], waveforms) - parsed_calibration_data[gate_qubit_key] = gate_qubit_pulse - - return parsed_calibration_data diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py deleted file mode 100644 index 37eb6760..00000000 --- a/src/braket/aws/aws_quantum_job.py +++ /dev/null @@ -1,659 +0,0 @@ -# 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 -import tarfile -import tempfile -import time -from enum import Enum -from logging import Logger, getLogger -from pathlib import Path -from typing import Any, ClassVar - -import boto3 -from botocore.exceptions import ClientError - -from braket.aws import AwsDevice -from braket.aws.aws_session import AwsSession -from braket.aws.queue_information import HybridJobQueueInfo -from braket.jobs import logs -from braket.jobs.config import ( - CheckpointConfig, - InstanceConfig, - OutputDataConfig, - S3DataSourceConfig, - StoppingCondition, -) -from braket.jobs.data_persistence import load_job_result -from braket.jobs.metrics_data.cwl_insights_metrics_fetcher import CwlInsightsMetricsFetcher - -# TODO: Have added metric file in metrics folder, but have to decide on the name for keep -# for the files, since all those metrics are retrieved from the CW. -from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType -from braket.jobs.quantum_job import QuantumJob -from braket.jobs.quantum_job_creation import prepare_quantum_job - - -class AwsQuantumJob(QuantumJob): - """Amazon Braket implementation of a quantum job.""" - - 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" - - @classmethod - def create( - cls, - device: str, - source_module: str, - entry_point: str | None = None, - image_uri: str | None = None, - job_name: str | None = None, - code_location: str | None = None, - role_arn: str | None = None, - wait_until_complete: bool = False, - hyperparameters: dict[str, Any] | None = None, - input_data: str | dict | S3DataSourceConfig | None = None, - instance_config: InstanceConfig | None = None, - distribution: str | None = None, - stopping_condition: StoppingCondition | None = None, - output_data_config: OutputDataConfig | None = None, - copy_checkpoints_from_job: str | None = None, - checkpoint_config: CheckpointConfig | None = None, - 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. - - Args: - device (str): 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 queue so that only one hybrid job is running at a time. The device string is - accessible in the hybrid job instance as the environment variable - "AMZN_BRAKET_DEVICE_ARN". When using embedded simulators, you may provide the device - argument as a string of the form: "local:/". - - source_module (str): Path (absolute, relative or an S3 URI) to a python module to be - tarred and uploaded. If `source_module` is an S3 URI, it must point to a - tar.gz file. Otherwise, source_module may be a file or directory. - - entry_point (str | None): A str that specifies the entry point of the hybrid job, - relative to the source module. The entry point must be in the format - `importable.module` or `importable.module:callable`. For example, - `source_module.submodule:start_here` indicates the `start_here` function - contained in `source_module.submodule`. If source_module is an S3 URI, - entry point must be given. Default: source_module's name - - image_uri (str | None): A str that specifies the ECR image to use for executing the - hybrid job. `image_uris.retrieve_image()` function may be used for retrieving the - ECR image URIs for the containers supported by Braket. - Default = ``. - - job_name (str | None): A str that specifies the name with which the hybrid job is - created. Allowed pattern for hybrid job name: `^[a-zA-Z0-9](-*[a-zA-Z0-9]){0,50}$` - Default: f'{image_uri_type}-{timestamp}'. - - code_location (str | None): The S3 prefix URI where custom code will be uploaded. - Default: f's3://{default_bucket_name}/jobs/{job_name}/script'. - - role_arn (str | None): A str providing the IAM role ARN used to execute the - script. Default: IAM role returned by AwsSession's `get_default_jobs_role()`. - - wait_until_complete (bool): `True` if we should wait until the hybrid job completes. - This would tail the hybrid job logs as it waits. Otherwise `False`. - Default: `False`. - - hyperparameters (dict[str, Any] | None): Hyperparameters accessible to the hybrid job. - The hyperparameters are made accessible as a dict[str, str] to the hybrid job. - For convenience, this accepts other types for keys and values, but `str()` - is called to convert them before being passed on. Default: None. - - input_data (str | dict | S3DataSourceConfig | None): Information about the training - data. Dictionary maps channel names to local paths or S3 URIs. Contents found - at any local paths will be uploaded to S3 at - f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local - path, S3 URI, or S3DataSourceConfig is provided, it will be given a default - channel name "input". - Default: {}. - - instance_config (InstanceConfig | None): Configuration of the instance(s) for running - the classical code for the hybrid job. Default: - `InstanceConfig(instanceType='ml.m5.large', instanceCount=1, volumeSizeInGB=30)`. - - distribution (str | None): A str that specifies how the hybrid job should be - distributed. If set to "data_parallel", the hyperparameters for the hybrid job will - be set to use data parallelism features for PyTorch or TensorFlow. Default: None. - - stopping_condition (StoppingCondition | None): The maximum length of time, in seconds, - and the maximum number of quantum tasks that a hybrid job can run before being - forcefully stopped. - Default: StoppingCondition(maxRuntimeInSeconds=5 * 24 * 60 * 60). - - output_data_config (OutputDataConfig | None): Specifies the location for the output of - the hybrid job. - Default: OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data', - kmsKeyId=None). - - copy_checkpoints_from_job (str | None): A str that specifies the hybrid job ARN whose - checkpoint you want to use in the current hybrid job. Specifying this value will - copy over the checkpoint data from `use_checkpoints_from_job`'s checkpoint_config - s3Uri to the current hybrid job's checkpoint_config s3Uri, making it available at - checkpoint_config.localPath during the hybrid job execution. Default: None - - checkpoint_config (CheckpointConfig | None): Configuration that specifies the location - where checkpoint data is stored. - Default: CheckpointConfig(localPath='/opt/jobs/checkpoints', - s3Uri=f's3://{default_bucket_name}/jobs/{job_name}/checkpoints'). - - aws_session (AwsSession | None): AwsSession for connecting to AWS Services. - Default: AwsSession() - - tags (dict[str, str] | None): Dict specifying the key-value pairs for tagging this - hybrid job. - Default: {}. - - 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`. - - 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. - - Returns: - AwsQuantumJob: Hybrid job tracking the execution on Amazon Braket. - - Raises: - ValueError: Raises ValueError if the parameters are not valid. - """ - aws_session = AwsQuantumJob._initialize_session(aws_session, device, logger) - - create_job_kwargs = prepare_quantum_job( - device=device, - source_module=source_module, - entry_point=entry_point, - image_uri=image_uri, - job_name=job_name, - code_location=code_location, - role_arn=role_arn, - hyperparameters=hyperparameters, - input_data=input_data, - instance_config=instance_config, - distribution=distribution, - stopping_condition=stopping_condition, - output_data_config=output_data_config, - copy_checkpoints_from_job=copy_checkpoints_from_job, - checkpoint_config=checkpoint_config, - aws_session=aws_session, - tags=tags, - reservation_arn=reservation_arn, - ) - - job_arn = aws_session.create_job(**create_job_kwargs) - job = AwsQuantumJob(job_arn, aws_session, quiet) - - if wait_until_complete: - print(f"Initializing Braket Job: {job_arn}") - job.logs(wait=True) - - 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. - 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`. - - Raises: - ValueError: Supplied region and session region do not match. - """ - 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( - "The aws session region does not match the region for the supplied arn." - ) - self._aws_session = aws_session - else: - self._aws_session = AwsQuantumJob._default_session_for_job_arn(arn) - self._metadata = {} - - @staticmethod - def _is_valid_aws_session_region_for_job_arn(aws_session: AwsSession, job_arn: str) -> bool: - """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 - - @staticmethod - def _default_session_for_job_arn(job_arn: str) -> AwsSession: - """Get an AwsSession for the Hybrid Job ARN. The AWS session should be in the region of the - hybrid job. - - Args: - job_arn (str): The ARN for the quantum hybrid job. - - Returns: - AwsSession: `AwsSession` object with default `boto_session` in hybrid job's region. - """ - job_region = job_arn.split(":")[3] - boto_session = boto3.Session(region_name=job_region) - return AwsSession(boto_session=boto_session) - - @property - def arn(self) -> str: - """str: The ARN (Amazon Resource Name) of the quantum hybrid job.""" - return self._arn - - @property - 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. - - 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: The value of `status` in `metadata()`. This is the value of the `status` key - in the Amazon Braket `GetJob` operation. - - See Also: - `metadata()` - """ - return self.metadata(use_cached_value).get("status") - - def queue_position(self) -> HybridJobQueueInfo: - """The queue position details for the hybrid job. - - Returns: - HybridJobQueueInfo: Instance of HybridJobQueueInfo class representing - the queue position information for the hybrid job. The queue_position is - only returned when the hybrid job is not in RUNNING/CANCELLING/TERMINAL states, - else queue_position is returned as None. If the queue position of the hybrid - job is greater than 15, we return '>15' as the queue_position return value. - - Examples: - job status = QUEUED and position is 2 in the queue. - >>> job.queue_position() - HybridJobQueueInfo(queue_position='2', message=None) - - job status = QUEUED and position is 18 in the queue. - >>> job.queue_position() - HybridJobQueueInfo(queue_position='>15', message=None) - - job status = COMPLETED - >>> job.queue_position() - HybridJobQueueInfo(queue_position=None, - message='Job is in COMPLETED status. AmazonBraket does - not show queue position for this status.') - """ - response = self.metadata()["queueInfo"] - queue_position = None if response.get("position") == "None" else response.get("position") - message = response.get("message") - - return HybridJobQueueInfo(queue_position=queue_position, message=message) - - def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: - """Display logs for a given hybrid job, optionally tailing them until hybrid job is - complete. - - If the output is a tty or a Jupyter cell, it will be color-coded - based on which instance the log entry is from. - - Args: - wait (bool): `True` to keep looking for new log entries until the hybrid job completes; - otherwise `False`. Default: `False`. - - poll_interval_seconds (int): The interval of time, in seconds, between polling for - new log entries and hybrid job completion (default: 5). - - Raises: - exceptions.UnexpectedStatusException: If waiting and the training hybrid job fails. - """ - # The loop below implements a state machine that alternates between checking the hybrid job - # status and reading whatever is available in the logs at this point. Note, that if we were - # called with wait == False, we never check the hybrid job status. - # - # If wait == TRUE and hybrid job is not completed, the initial state is TAILING - # If wait == FALSE, the initial state is COMPLETE (doesn't matter if the hybrid job really - # is complete). - # - # The state table: - # - # STATE ACTIONS CONDITION NEW STATE - # ---------------- ---------------- ----------------- ---------------- - # TAILING Read logs, Pause, Get status Job complete JOB_COMPLETE - # Else TAILING - # JOB_COMPLETE Read logs, Pause Any COMPLETE - # COMPLETE Read logs, Exit N/A - # - # Notes: - # - The JOB_COMPLETE state forces us to do an extra pause and read any items that got to - # Cloudwatch after the job was marked complete. - - job_already_completed = self.state() in AwsQuantumJob.TERMINAL_STATES - log_state = ( - AwsQuantumJob.LogState.TAILING - if wait and not job_already_completed - else AwsQuantumJob.LogState.COMPLETE - ) - - log_group = AwsQuantumJob.LOG_GROUP - 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"] - 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, - self._logs_prefix, - stream_names, - positions, - instance_count, - has_streams, - color_wrap, - [previous_state, current_state], - None if self._quiet else self.queue_position().queue_position, - ) - previous_state = current_state - - if log_state == AwsQuantumJob.LogState.COMPLETE: - break - - if log_state == AwsQuantumJob.LogState.JOB_COMPLETE: - log_state = AwsQuantumJob.LogState.COMPLETE - elif current_state in AwsQuantumJob.TERMINAL_STATES: - log_state = AwsQuantumJob.LogState.JOB_COMPLETE - - def metadata(self, use_cached_value: bool = False) -> dict[str, Any]: - """Gets the hybrid job metadata defined in Amazon Braket. - - 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]: Dict that specifies the hybrid job metadata defined in Amazon Braket. - """ - if not use_cached_value or not self._metadata: - self._metadata = self._aws_session.get_job(self._arn) - return self._metadata - - def metrics( - self, - metric_type: MetricType = MetricType.TIMESTAMP, - statistic: MetricStatistic = MetricStatistic.MAX, - ) -> 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. For example, the table: - timestamp energy - 0 0.1 - 1 0.2 - would be represented as: - { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } - values may be integers, floats, strings or None. - - Args: - metric_type (MetricType): The type of metrics to get. Default: MetricType.TIMESTAMP. - - statistic (MetricStatistic): The statistic to determine which metric value to use - when there is a conflict. Default: MetricStatistic.MAX. - - Returns: - dict[str, list[Any]]: The metrics data. - """ - fetcher = CwlInsightsMetricsFetcher(self._aws_session) - metadata = self.metadata(True) - 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( - self.name, metric_type, statistic, job_start, job_end, self._logs_prefix - ) - - def cancel(self) -> str: - """Cancels the job. - - Returns: - str: Indicates the status of the job. - - Raises: - ClientError: If there are errors invoking the CancelJob API. - """ - cancellation_response = self._aws_session.cancel_job(self._arn) - return cancellation_response["cancellationStatus"] - - def result( - self, - 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 the `save_job_result` function. - - Args: - poll_timeout_seconds (float): The polling timeout, in seconds, for `result()`. - Default: 10 days. - poll_interval_seconds (float): The polling interval, in seconds, for `result()`. - Default: 5 seconds. - - Returns: - dict[str, Any]: Dict specifying the job results. - - Raises: - 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"] - - try: - self.download_result(temp_dir, poll_timeout_seconds, poll_interval_seconds) - except ClientError as e: - if e.response["Error"]["Code"] == "404": - return {} - else: - raise e - return AwsQuantumJob._read_and_deserialize_results(temp_dir, job_name) - - @staticmethod - def _read_and_deserialize_results(temp_dir: str, job_name: str) -> dict[str, Any]: - return load_job_result(Path(temp_dir, job_name, AwsQuantumJob.RESULTS_FILENAME)) - - def download_result( - self, - extract_to: str | None = None, - poll_timeout_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_TIMEOUT, - poll_interval_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_INTERVAL, - ) -> None: - """Downloads the results from the hybrid job output S3 bucket and extracts the tar.gz - bundle to the location specified by `extract_to`. If no location is specified, - the results are extracted to the current directory. - - Args: - extract_to (str | None): The directory to which the results are extracted. The results - are extracted to a folder titled with the hybrid job name within this directory. - Default= `Current working directory`. - poll_timeout_seconds (float): The polling timeout, in seconds, for `download_result()`. - Default: 10 days. - poll_interval_seconds (float): The polling interval, in seconds, for - `download_result()`.Default: 5 seconds. - - Raises: - 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 - job_response = self.metadata(True) - - while time.time() < timeout_time: - job_response = self.metadata(True) - job_state = self.state() - - if job_state in AwsQuantumJob.TERMINAL_STATES: - output_s3_path = job_response["outputDataConfig"]["s3Path"] - output_s3_uri = f"{output_s3_path}/output/model.tar.gz" - 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) - - raise TimeoutError( - f"{job_response['jobName']}: Polling for job completion " - f"timed out after {poll_timeout_seconds} seconds." - ) - - def _attempt_results_download(self, output_bucket_uri: str, output_s3_path: str) -> None: - try: - self._aws_session.download_from_s3( - s3_uri=output_bucket_uri, filename=AwsQuantumJob.RESULTS_TAR_FILENAME - ) - except ClientError as e: - 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: - with tarfile.open(AwsQuantumJob.RESULTS_TAR_FILENAME, "r:gz") as tar: - tar.extractall(extract_path) - - def __repr__(self) -> str: - return f"AwsQuantumJob('arn':'{self.arn}')" - - def __eq__(self, other: AwsQuantumJob) -> bool: - return self.arn == other.arn if isinstance(other, AwsQuantumJob) else False - - def __hash__(self) -> int: - return hash(self.arn) - - @staticmethod - def _initialize_session(session_value: AwsSession, device: str, logger: Logger) -> AwsSession: - aws_session = session_value or AwsSession() - if device.startswith("local:"): - return aws_session - device_region = AwsDevice.get_device_region(device) - return ( - AwsQuantumJob._initialize_regional_device_session(aws_session, device, logger) - if device_region - else AwsQuantumJob._initialize_non_regional_device_session(aws_session, device, logger) - ) - - @staticmethod - def _initialize_regional_device_session( - aws_session: AwsSession, device: AwsDevice, logger: Logger - ) -> AwsSession: - device_region = AwsDevice.get_device_region(device) - current_region = aws_session.region - if current_region != device_region: - aws_session = aws_session.copy_session(region=device_region) - 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 - - @staticmethod - def _initialize_non_regional_device_session( - aws_session: AwsSession, device: AwsDevice, logger: Logger - ) -> AwsSession: - 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 - - 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: - device_session.get_device(device) - 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 ValueError(f"QPU '{device}' not found.") diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py deleted file mode 100644 index f293f114..00000000 --- a/src/braket/aws/aws_quantum_task.py +++ /dev/null @@ -1,880 +0,0 @@ -# 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 asyncio -import time -from functools import singledispatch -from logging import Logger, getLogger -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, - DwaveAdvantageDeviceParameters, - DwaveDeviceParameters, -) -from braket.device_schema.dwave.dwave_2000Q_device_level_parameters_v1 import ( - Dwave2000QDeviceLevelParameters, -) -from braket.device_schema.dwave.dwave_advantage_device_level_parameters_v1 import ( - DwaveAdvantageDeviceLevelParameters, -) -from braket.device_schema.ionq import IonqDeviceParameters -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, - AnnealingTaskResult, - GateModelTaskResult, - PhotonicModelTaskResult, -) -from braket.tasks import ( - AnalogHamiltonianSimulationQuantumTaskResult, - AnnealingQuantumTaskResult, - GateModelQuantumTaskResult, - PhotonicModelQuantumTaskResult, - QuantumTask, -) -from braket.tracking.tracking_context import broadcast_event -from braket.tracking.tracking_events import _TaskCompletionEvent - - -class AwsQuantumTask(QuantumTask): - """Amazon Braket implementation of a quantum task. A quantum task can be a circuit, - 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: 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 - RESULTS_FILENAME = "results.json" - - @staticmethod - def create( - aws_session: AwsSession, - device_arn: str, - task_specification: Union[ - Circuit, - Problem, - OpenQASMProgram, - BlackbirdProgram, - PulseSequence, - AnalogHamiltonianSimulation, - ], - s3_destination_folder: AwsSession.S3DestinationFolder, - shots: int, - device_parameters: dict[str, Any] | None = 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, - quiet: bool = False, - reservation_arn: str | None = None, - *args, - **kwargs, - ) -> AwsQuantumTask: - """AwsQuantumTask factory method that serializes a quantum task specification - (either a quantum circuit or annealing problem), submits it to Amazon Braket, - and returns back an AwsQuantumTask tracking the execution. - - Args: - aws_session (AwsSession): AwsSession to connect to AWS with. - - 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. - - 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. - - shots (int): The number of times to run the quantum task on the device. If the device is - a simulator, this implies the state is sampled N times, where N = `shots`. - `shots=0` is only available on simulators and means that the simulator - will compute the exact results based on the quantum task specification. - - device_parameters (dict[str, Any] | None): Additional parameters to send to the device. - - disable_qubit_rewiring (bool): Whether to run the circuit with the exact qubits chosen, - without any rewiring downstream, if this is supported by the device. - Only applies to digital, gate-based circuits (as opposed to annealing problems). - If ``True``, no qubit rewiring is allowed; if ``False``, qubit rewiring is allowed. - Default: False - - tags (dict[str, str] | None): Tags, which are Key-Value pairs to add to this quantum - task. An example would be: - `{"state": "washington"}` - - inputs (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 (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 - 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, - those tasks do not need to be created with the reservation ARN. - Default: None. - - Returns: - AwsQuantumTask: AwsQuantumTask tracking the quantum task execution on the device. - - Note: - The following arguments are typically defined via clients of Device. - - `task_specification` - - `s3_destination_folder` - - `shots` - - 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." - ) - - create_task_kwargs = _create_common_params( - device_arn, - s3_destination_folder, - shots if shots is not None else AwsQuantumTask.DEFAULT_SHOTS, - ) - - 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( - { - "associations": [ - { - "arn": reservation_arn, - "type": "RESERVATION_TIME_WINDOW_ARN", - } - ] - } - ) - - if isinstance(task_specification, Circuit): - param_names = {param.name for param in task_specification.parameters} - if unbounded_parameters := param_names - set(inputs.keys()): - raise ValueError( - f"Cannot execute circuit with unbound parameters: {unbounded_parameters}" - ) - - return _create_internal( - task_specification, - aws_session, - create_task_kwargs, - device_arn, - device_parameters or {}, - disable_qubit_rewiring, - inputs, - gate_definitions=gate_definitions, - quiet=quiet, - *args, - **kwargs, - ) - - def __init__( - self, - arn: str, - 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, - ): - """Initializes an `AwsQuantumTask`. - - Args: - arn (str): The ARN of the quantum task. - 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 quantum task. - poll_timeout_seconds (float): The polling timeout for `result()`. Default: 5 days. - poll_interval_seconds (float): The polling interval for `result()`. Default: 1 second. - 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') - >>> task.state() - 'COMPLETED' - >>> result = task.result() - AnnealingQuantumTaskResult(...) - - >>> task = AwsQuantumTask(arn='task_arn', poll_timeout_seconds=300) - >>> result = task.result() - GateModelQuantumTaskResult(...) - """ - self._arn: str = arn - self._aws_session: AwsSession = aws_session or AwsQuantumTask._aws_session_for_task_arn( - task_arn=arn - ) - self._poll_timeout_seconds = poll_timeout_seconds - self._poll_interval_seconds = poll_interval_seconds - - self._logger = logger - self._quiet = quiet - - self._metadata: dict[str, Any] = {} - self._result: Union[ - GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult - ] = None - - @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. - - Returns: - AwsSession: `AwsSession` object with default `boto_session` in quantum task's region. - """ - task_region = task_arn.split(":")[3] - boto_session = boto3.Session(region_name=task_region) - return AwsSession(boto_session=boto_session) - - @property - def id(self) -> str: - """str: The ARN of the quantum task.""" - return self._arn - - def _cancel_future(self) -> None: - """Cancel the future if it exists. Else, create a cancelled future.""" - if not hasattr(self, "_future"): - self._future = asyncio.Future() - self._future.cancel() - - def cancel(self) -> None: - """Cancel the quantum task. This cancels the future and the quantum task in Amazon - 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. - - 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 - retrieved value is used, unless `GetQuantumTask` was never called, in which case - 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) - return self._metadata - - def state(self, use_cached_value: bool = False) -> str: - """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. - - Returns: - QuantumTaskQueueInfo: Instance of QuantumTaskQueueInfo class - representing the queue position information for the quantum task. - The queue_position is only returned when quantum task is not in - RUNNING/CANCELLING/TERMINAL states, else queue_position is returned as None. - The normal tasks refers to the quantum tasks not submitted via Hybrid Jobs. - Whereas, the priority tasks refers to the total number of quantum tasks waiting to run - submitted through Amazon Braket Hybrid Jobs. These tasks run before the normal tasks. - If the queue position for normal or priority quantum tasks is greater than 2000, - we display their respective queue position as '>2000'. - - Examples: - task status = QUEUED and queue position is 2050 - >>> task.queue_position() - QuantumTaskQueueInfo(queue_type=, - queue_position='>2000', message=None) - - task status = COMPLETED - >>> task.queue_position() - QuantumTaskQueueInfo(queue_type=, - queue_position=None, message='Task is in COMPLETED status. AmazonBraket does - not show queue position for this status.') - """ - response = self.metadata()["queueInfo"] - queue_type = QueueType(response["queuePriority"]) - queue_position = None if response.get("position") == "None" else response.get("position") - message = response.get("message") - - return QuantumTaskQueueInfo(queue_type, queue_position, message) - - def _status(self, use_cached_value: bool = False) -> str: - metadata = self.metadata(use_cached_value) - status = metadata.get("status") - if not use_cached_value and status in self.NO_RESULT_TERMINAL_STATES: - self._logger.warning(f"Task is in terminal state {status} and no result is available.") - if status == "FAILED": - failure_reason = metadata.get("failureReason", "unknown") - self._logger.warning(f"Task failure reason is: {failure_reason}.") - return status - - def _update_status_if_nonterminal(self) -> str: - # If metadata has not been populated, the first call to _status will fetch it, - # so the second _status call will no longer need to - metadata_absent = not self._metadata - cached = self._status(True) - return cached if cached in self.TERMINAL_STATES else self._status(metadata_absent) - - def result( - self, - ) -> Union[ - 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` - - This method is a blocking thread call and synchronously returns a result. - Call `async_result()` if you require an asynchronous invocation. - 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 - if self._result or ( - self._metadata and self._status(True) in self.NO_RESULT_TERMINAL_STATES - ): - return self._result - if self._metadata and self._status(True) in self.RESULTS_READY_STATES: - return self._download_result() - try: - async_result = self.async_result() - return async_result.get_loop().run_until_complete(async_result) - except asyncio.CancelledError: - # Future was cancelled, return whatever is in self._result if anything - self._logger.warning("Task future was cancelled") - return self._result - - def _get_future(self) -> asyncio.Future: - try: - asyncio.get_event_loop() - except Exception as e: - self._logger.debug(e) - self._logger.info("No event loop found; creating new event loop") - asyncio.set_event_loop(asyncio.new_event_loop()) - if not hasattr(self, "_future") or ( - self._future.done() - and not self._future.cancelled() - and self._result is None - # timed out and no result - and self._update_status_if_nonterminal() not in self.NO_RESULT_TERMINAL_STATES - ): - self._future = asyncio.get_event_loop().run_until_complete(self._create_future()) - return self._future - - def async_result(self) -> asyncio.Task: - """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. - 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. - - Returns: - asyncio.Task: An asyncio Task that contains the `_wait_for_completion()` coroutine. - """ - return asyncio.create_task(self._wait_for_completion()) - - async def _wait_for_completion( - self, - ) -> Union[ - GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult - ]: - """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`. - - 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() - - 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() - elif task_status in AwsQuantumTask.NO_RESULT_TERMINAL_STATES: - self._result = None - return None - else: - 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." - ) - self._result = None - return None - - def _has_reservation_arn_from_metadata(self, current_metadata: dict[str, Any]) -> bool: - return any( - association.get("type") == "RESERVATION_TIME_WINDOW_ARN" - for association in current_metadata.get("associations", []) - ) - - def _download_result( - self, - ) -> Union[ - GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult - ]: - current_metadata = self.metadata(True) - result_string = self._aws_session.retrieve_s3_object_body( - current_metadata["outputS3Bucket"], - current_metadata["outputS3Directory"] + f"/{AwsQuantumTask.RESULTS_FILENAME}", - ) - self._result = _format_result(BraketSchemaBase.parse_raw_schema(result_string)) - task_event = { - "arn": self.id, - "status": self.state(), - "execution_duration": None, - "has_reservation_arn": self._has_reservation_arn_from_metadata(current_metadata), - } - try: - task_event["execution_duration"] = ( - self._result.additional_metadata.simulatorMetadata.executionDuration - ) - except AttributeError: - pass - broadcast_event(_TaskCompletionEvent(**task_event)) - return self._result - - def __repr__(self) -> str: - return f"AwsQuantumTask('id/taskArn':'{self.id}')" - - def __eq__(self, other: AwsQuantumTask) -> bool: - return self.id == other.id if isinstance(other, AwsQuantumTask) else False - - def __hash__(self) -> int: - return hash(self.id) - - -@singledispatch -def _create_internal( - task_specification: Union[Circuit, Problem, BlackbirdProgram], - 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: dict[tuple[Gate, QubitSet], PulseSequence], - *args, - **kwargs, -) -> AwsQuantumTask: - raise TypeError("Invalid task specification type") - - -@_create_internal.register -def _( - pulse_sequence: PulseSequence, - aws_session: AwsSession, - create_task_kwargs: dict[str, Any], - device_arn: str, - _device_parameters: Union[dict, BraketSchemaBase], # Not currently used for OpenQasmProgram - _disable_qubit_rewiring: bool, - inputs: dict[str, float], - gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], - *args, - **kwargs, -) -> AwsQuantumTask: - openqasm_program = OpenQASMProgram( - source=pulse_sequence.to_ir(), - inputs=inputs or {}, - ) - - 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) - - -@_create_internal.register -def _( - openqasm_program: OpenQASMProgram, - 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: dict[tuple[Gate, QubitSet], PulseSequence], - *args, - **kwargs, -) -> AwsQuantumTask: - if inputs: - inputs_copy = openqasm_program.inputs.copy() if openqasm_program.inputs is not None else {} - inputs_copy.update(inputs) - openqasm_program = OpenQASMProgram( - source=openqasm_program.source, - inputs=inputs_copy, - ) - create_task_kwargs["action"] = openqasm_program.json() - if device_parameters: - final_device_parameters = ( - _circuit_device_params_from_dict( - device_parameters, - device_arn, - GateModelParameters(qubitCount=0), # qubitCount unused - ) - if isinstance(device_parameters, dict) - else device_parameters - ) - 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) - - -@_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, allow_implicit_build=True) - ) - 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, - 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: dict[tuple[Gate, QubitSet], PulseSequence], - *args, - **kwargs, -) -> AwsQuantumTask: - 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) - - -@_create_internal.register -def _( - circuit: Circuit, - 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: dict[tuple[Gate, QubitSet], PulseSequence], - *args, - **kwargs, -) -> AwsQuantumTask: - validate_circuit_and_shots(circuit, create_task_kwargs["shots"]) - # TODO: Update this to use `deviceCapabilities` from Amazon Braket's GetDevice operation - # in order to decide what parameters to build. - paradigm_parameters = GateModelParameters( - qubitCount=circuit.qubit_count, disableQubitRewiring=disable_qubit_rewiring - ) - final_device_parameters = ( - _circuit_device_params_from_dict(device_parameters or {}, 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 - ) - - openqasm_program = circuit.to_ir( - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - gate_definitions=gate_definitions, - ) - - if inputs: - inputs_copy = openqasm_program.inputs.copy() if openqasm_program.inputs is not None else {} - inputs_copy.update(inputs) - openqasm_program = OpenQASMProgram( - source=openqasm_program.source, - inputs=inputs_copy, - ) - - 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) - - -@_create_internal.register -def _( - problem: Problem, - aws_session: AwsSession, - create_task_kwargs: dict[str, Any], - device_arn: str, - device_parameters: Union[ - dict, - DwaveDeviceParameters, - DwaveAdvantageDeviceParameters, - Dwave2000QDeviceParameters, - ], - _: bool, - inputs: dict[str, float], - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], - *args, - **kwargs, -) -> AwsQuantumTask: - device_params = _create_annealing_device_params(device_parameters, device_arn) - 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) - - -@_create_internal.register -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]], - *args, - **kwargs, -) -> AwsQuantumTask: - 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) - - -def _circuit_device_params_from_dict( - device_parameters: dict, device_arn: str, paradigm_parameters: GateModelParameters -) -> GateModelSimulatorDeviceParameters: - if "errorMitigation" in device_parameters: - error_migitation = device_parameters["errorMitigation"] - device_parameters["errorMitigation"] = ( - error_migitation.serialize() - if isinstance(error_migitation, ErrorMitigation) - else error_migitation - ) - if "ionq" in device_arn: - return IonqDeviceParameters(paradigmParameters=paradigm_parameters, **device_parameters) - if "rigetti" in device_arn: - return RigettiDeviceParameters(paradigmParameters=paradigm_parameters) - if "oqc" in device_arn: - return OqcDeviceParameters(paradigmParameters=paradigm_parameters) - return GateModelSimulatorDeviceParameters(paradigmParameters=paradigm_parameters) - - -def _create_annealing_device_params( - device_params: dict[str, Any], device_arn: str -) -> Union[DwaveAdvantageDeviceParameters, Dwave2000QDeviceParameters]: - """Gets Annealing Device Parameters. - - Args: - device_params (dict[str, Any]): Additional parameters for the device. - device_arn (str): The ARN of the quantum device. - - Returns: - Union[DwaveAdvantageDeviceParameters, Dwave2000QDeviceParameters]: The device parameters. - - """ - if not isinstance(device_params, dict): - device_params = device_params.dict() - - # check for device level or provider level parameters - device_level_parameters = device_params.get("deviceLevelParameters", None) or device_params.get( - "providerLevelParameters", {} - ) - - # deleting since it may be the old version - if "braketSchemaHeader" in device_level_parameters: - del device_level_parameters["braketSchemaHeader"] - - if "Advantage" in device_arn: - device_level_parameters = DwaveAdvantageDeviceLevelParameters.parse_obj( - device_level_parameters - ) - return DwaveAdvantageDeviceParameters(deviceLevelParameters=device_level_parameters) - elif "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." - ) - - -def _create_common_params( - device_arn: str, s3_destination_folder: AwsSession.S3DestinationFolder, shots: int -) -> dict[str, Any]: - return { - "deviceArn": device_arn, - "outputS3Bucket": s3_destination_folder[0], - "outputS3KeyPrefix": s3_destination_folder[1], - "shots": shots, - } - - -@singledispatch -def _format_result( - result: Union[GateModelTaskResult, AnnealingTaskResult, PhotonicModelTaskResult], -) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]: - raise TypeError("Invalid result specification type") - - -@_format_result.register -def _(result: GateModelTaskResult) -> GateModelQuantumTaskResult: - GateModelQuantumTaskResult.cast_result_types(result) - return GateModelQuantumTaskResult.from_object(result) - - -@_format_result.register -def _(result: AnnealingTaskResult) -> AnnealingQuantumTaskResult: - return AnnealingQuantumTaskResult.from_object(result) - - -@_format_result.register -def _(result: PhotonicModelTaskResult) -> PhotonicModelQuantumTaskResult: - return PhotonicModelQuantumTaskResult.from_object(result) - - -@_format_result.register -def _( - result: AnalogHamiltonianSimulationTaskResult, -) -> 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 deleted file mode 100644 index 300963a6..00000000 --- a/src/braket/aws/aws_quantum_task_batch.py +++ /dev/null @@ -1,453 +0,0 @@ -# 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 time -from concurrent.futures.thread import ThreadPoolExecutor -from itertools import repeat -from typing import Any, Union - -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 - - -class AwsQuantumTaskBatch(QuantumTaskBatch): - """Executes a batch of quantum tasks in parallel. - - Using this class can yield vast speedups over executing quantum tasks sequentially, - and is particularly useful for computations that can be parallelized, - such as calculating quantum gradients or statistics of terms in a Hamiltonian. - - Note: there is no benefit to using this method with QPUs outside of their execution windows, - since results will not be available until the window opens. - """ - - MAX_CONNECTIONS_DEFAULT = 100 - MAX_RETRIES = 3 - - 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 - ] - ], - ], - s3_destination_folder: AwsSession.S3DestinationFolder, - shots: int, - max_parallel: int, - 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, - 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, - ): - """Creates a batch of quantum tasks. - - 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. - 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. - shots (int): The number of times to run the quantum task on the device. If the device is - a simulator, this implies the state is sampled N times, where N = `shots`. - `shots=0` is only available on simulators and means that the simulator - will compute the exact results based on the quantum task specification. - max_parallel (int): The maximum number of quantum tasks to run on AWS in parallel. - Batch creation will fail if this value is greater than the maximum allowed - concurrent quantum tasks on the device. - max_workers (int): The maximum number of thread pool workers. Default: 100 - poll_timeout_seconds (float): The polling timeout for `AwsQuantumTask.result()`, - 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 - 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, - 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, - task_specifications, - s3_destination_folder, - shots, - max_parallel, - max_workers, - poll_timeout_seconds, - poll_interval_seconds, - inputs, - gate_definitions, - reservation_arn, - *aws_quantum_task_args, - **aws_quantum_task_kwargs, - ) - self._aws_session = aws_session - self._results = None - self._unsuccessful = set() - - # Cache execution inputs for retries. - self._device_arn = device_arn - self._task_specifications = task_specifications - self._s3_destination_folder = s3_destination_folder - self._shots = shots - self._max_parallel = max_parallel - self._max_workers = max_workers - self._poll_timeout_seconds = poll_timeout_seconds - self._poll_interval_seconds = poll_interval_seconds - self._inputs = inputs - self._reservation_arn = reservation_arn - self._aws_quantum_task_args = aws_quantum_task_args - self._aws_quantum_task_kwargs = aws_quantum_task_kwargs - - @staticmethod - def _tasks_inputs_gatedefs( - task_specifications: Union[ - Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation], - list[ - Union[ - 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]], - ] = None, - ) -> list[ - tuple[ - Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation], - dict[str, float], - dict[tuple[Gate, QubitSet], PulseSequence], - ] - ]: - inputs = inputs or {} - gate_definitions = gate_definitions or {} - - single_task_type = ( - Circuit, - Problem, - OpenQasmProgram, - BlackbirdProgram, - AnalogHamiltonianSimulation, - ) - single_input_type = dict - single_gate_definitions_type = dict - - args = [task_specifications, inputs, gate_definitions] - single_arg_types = [single_task_type, single_input_type, single_gate_definitions_type] - - 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) - - 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 - - for i, arg_length in enumerate(arg_lengths): - if isinstance(args[i], (dict, single_task_type)): - args[i] = repeat(args[i], batch_length) - - tasks_inputs_definitions = list(zip(*args)) - - 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} - if unbounded_parameters := param_names - set(input_map.keys()): - raise ValueError( - f"Cannot execute circuit with unbound parameters: " - f"{unbounded_parameters}" - ) - - return tasks_inputs_definitions - - @staticmethod - def _execute( - aws_session: AwsSession, - device_arn: str, - task_specifications: Union[ - Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation], - list[ - Union[ - Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation - ] - ], - ], - s3_destination_folder: AwsSession.S3DestinationFolder, - shots: int, - max_parallel: int, - 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, - 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_inputs_gatedefs = AwsQuantumTaskBatch._tasks_inputs_gatedefs( - task_specifications, inputs, gate_definitions - ) - max_threads = min(max_parallel, max_workers) - remaining = [0 for _ in tasks_inputs_gatedefs] - try: - with ThreadPoolExecutor(max_workers=max_threads) as executor: - task_futures = [ - executor.submit( - AwsQuantumTaskBatch._create_task, - remaining, - aws_session, - device_arn, - task, - s3_destination_folder, - shots, - 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, gatedefs in tasks_inputs_gatedefs - ] - except KeyboardInterrupt: - # If an exception is thrown before the thread pool has finished, - # clean up the quantum tasks which have not yet been created before reraising it. - if "task_futures" in locals(): - for future in task_futures: - future.cancel() - - # Signal to the workers that there is no mork work to do - remaining.clear() - - raise - tasks = [future.result() for future in task_futures] - return tasks - - @staticmethod - def _create_task( - remaining: list[int], - aws_session: AwsSession, - device_arn: str, - task_specification: Union[ - 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, - reservation_arn: str | None = None, - *args, - **kwargs, - ) -> AwsQuantumTask: - task = AwsQuantumTask.create( - aws_session, - device_arn, - task_specification, - s3_destination_folder, - shots, - poll_interval_seconds=poll_interval_seconds, - inputs=inputs, - gate_definitions=gate_definitions, - reservation_arn=reservation_arn, - *args, - **kwargs, - ) - - remaining.pop() - - # If the quantum task hits a terminal state before all quantum tasks have been created, - # it can be returned immediately - while remaining and task.state() not in AwsQuantumTask.TERMINAL_STATES: - time.sleep(poll_interval_seconds) - return task - - def results( - self, - fail_unsuccessful: bool = False, - max_retries: int = MAX_RETRIES, - use_cached_value: bool = True, - ) -> list[AwsQuantumTask]: - """Retrieves the result of every quantum task in the batch. - - Polling for results happens in parallel; this method returns when all quantum tasks - have reached a terminal state. The result of this method is cached. - - Args: - fail_unsuccessful (bool): If set to `True`, this method will fail - if any quantum task in the batch fails to return a result even after - `max_retries` retries. - max_retries (int): Maximum number of times to retry any failed quantum tasks, - i.e. any quantum tasks in the `FAILED` or `CANCELLED` state or that didn't - complete within the timeout. Default: 3. - use_cached_value (bool): If `False`, will refetch the results from S3, - even when results have already been cached. Default: `True`. - - Returns: - list[AwsQuantumTask]: The 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: - 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 - } - - retries = 0 - while self._unsuccessful and retries < max_retries: - self.retry_unsuccessful_tasks() - retries += 1 - - if fail_unsuccessful and self._unsuccessful: - raise RuntimeError( - f"{len(self._unsuccessful)} tasks failed to complete after {max_retries} retries" - ) - return self._results - - @staticmethod - def _retrieve_results(tasks: list[AwsQuantumTask], max_workers: int) -> list[AwsQuantumTask]: - 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] - - def retry_unsuccessful_tasks(self) -> bool: - """Retries any quantum tasks in the batch without valid results. - - This method should only be called after `results()` has been called at least once. - The method will generate new quantum tasks for any failed quantum tasks, so `self.task` and - `self.results()` may return different values after a call to this method. - - Returns: - bool: Whether or not all retried quantum tasks completed successfully. - """ - if not self._results: - raise RuntimeError("results() should be called before attempting to retry") - unsuccessful_indices = [index for index, result in enumerate(self._results) if not result] - # Return early if there's nothing to retry - if not unsuccessful_indices: - return True - retried_tasks = AwsQuantumTaskBatch._execute( - self._aws_session, - self._device_arn, - [self._task_specifications[i] for i in unsuccessful_indices], - self._s3_destination_folder, - self._shots, - self._max_parallel, - self._max_workers, - self._poll_timeout_seconds, - self._poll_interval_seconds, - self._reservation_arn, - *self._aws_quantum_task_args, - **self._aws_quantum_task_kwargs, - ) - for index, task in zip(unsuccessful_indices, retried_tasks): - self._tasks[index] = task - - retried_results = AwsQuantumTaskBatch._retrieve_results(retried_tasks, self._max_workers) - for index, result in zip(unsuccessful_indices, retried_results): - self._results[index] = result - self._unsuccessful = { - task.id for task, result in zip(retried_tasks, retried_results) if not result - } - return not self._unsuccessful - - @property - def tasks(self) -> list[AwsQuantumTask]: - """list[AwsQuantumTask]: The quantum tasks in this batch, as a list of AwsQuantumTask - objects - """ - return list(self._tasks) - - @property - def size(self) -> int: - """int: The number of quantum tasks in the batch""" - return len(self._tasks) - - @property - def unfinished(self) -> set[str]: - """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. - """ - 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() - if status not in AwsQuantumTask.TERMINAL_STATES: - unfinished.add(task_id) - if status in AwsQuantumTask.NO_RESULT_TERMINAL_STATES: - self._unsuccessful.add(task_id) - return unfinished - - @property - def unsuccessful(self) -> set[str]: - """set[str]: The IDs of all the FAILED, CANCELLED, or timed out quantum tasks in the - batch. - """ - return set(self._unsuccessful) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py deleted file mode 100644 index b4cdfcd3..00000000 --- a/src/braket/aws/aws_session.py +++ /dev/null @@ -1,891 +0,0 @@ -# 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 itertools -import os -import os.path -import re -import warnings -from functools import cache -from pathlib import Path -from typing import Any, NamedTuple, Optional - -import backoff -import boto3 -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 - - -class AwsSession: - """Manage interactions with AWS services.""" - - class S3DestinationFolder(NamedTuple): - """A `NamedTuple` for an S3 bucket and object key.""" - - bucket: str - key: str - - def __init__( - self, - boto_session: boto3.Session | None = None, - braket_client: client | None = None, - config: Config | None = None, - default_bucket: str | None = None, - ): - """Initializes an `AwsSession`. - - Args: - 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 - and braket_client - and boto_session.region_name != braket_client.meta.region_name - ): - raise ValueError( - "Boto Session region and Braket Client region must match and currently " - f"they do not: Boto Session region is '{boto_session.region_name}', but " - f"Braket Client region is '{braket_client.meta.region_name}'." - ) - - self._config = config - - if braket_client: - self.boto_session = boto_session or boto3.Session( - region_name=braket_client.meta.region_name - ) - self.braket_client = braket_client - else: - self.boto_session = boto_session or boto3.Session( - region_name=os.environ.get("AWS_REGION") - ) - 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") - 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 - self._sts = None - self._logs = None - self._ecr = None - self._account_id = None - - @property - def region(self) -> str: - return self.boto_session.region_name - - @property - def account_id(self) -> str: - """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: - """Gets the IAM client. - - Returns: - client: The IAM Client. - """ - if not self._iam: - self._iam = self.boto_session.client("iam", region_name=self.region) - return self._iam - - @property - def s3_client(self) -> client: - """Gets the S3 client. - - Returns: - client: The S3 Client. - """ - if not self._s3: - self._s3 = self.boto_session.client("s3", region_name=self.region) - return self._s3 - - @property - def sts_client(self) -> client: - """Gets the STS client. - - Returns: - client: The STS Client. - """ - if not self._sts: - self._sts = self.boto_session.client("sts", region_name=self.region) - return self._sts - - @property - def logs_client(self) -> client: - """Gets the CloudWatch logs client. - - Returns: - client: The CloudWatch logs Client. - """ - if not self._logs: - self._logs = self.boto_session.client("logs", region_name=self.region) - return self._logs - - @property - def ecr_client(self) -> client: - """Gets the ECR client. - - Returns: - client: The ECR Client. - """ - if not self._ecr: - self._ecr = self.boto_session.client("ecr", region_name=self.region) - return self._ecr - - def _update_user_agent(self) -> None: - """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"). - """ - - def _notebook_instance_version() -> str: - # TODO: Replace with lifecycle configuration version once we have a way to access those - nbi_metadata_path = "/opt/ml/metadata/resource-metadata.json" - return "0" if os.path.exists(nbi_metadata_path) else "None" - - self._braket_user_agents = ( - f"BraketSdk/{braket_sdk.__version__} " - f"BraketSchemas/{braket_schemas.__version__} " - f"NotebookInstance/{_notebook_instance_version()}" - ) - - 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 - header. This method is typically only relevant for libraries integrating with the - Amazon Braket SDK. - - Args: - user_agent (str): The user_agent value to append to the header. - """ - 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) - - @staticmethod - def _add_cost_tracker_count_handler(request: awsrequest.AWSRequest, **kwargs) -> None: - request.headers.add_header("Braket-Trackers", str(len(active_trackers()))) - - # - # Quantum Tasks - # - def cancel_quantum_task(self, arn: str) -> None: - """Cancel the quantum task. - - Args: - arn (str): The ARN of the quantum task to cancel. - """ - response = self.braket_client.cancel_quantum_task(quantumTaskArn=arn) - broadcast_event(_TaskStatusEvent(arn=arn, status=response["cancellationStatus"])) - - def create_quantum_task(self, **boto3_kwargs) -> str: - """Create a quantum task. - - Args: - **boto3_kwargs: Keyword arguments for the Amazon Braket `CreateQuantumTask` - operation. - - 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: - boto3_kwargs["jobToken"] = job_token - response = self.braket_client.create_quantum_task(**boto3_kwargs) - broadcast_event( - _TaskCreationEvent( - arn=response["quantumTaskArn"], - shots=boto3_kwargs["shots"], - is_job_task=(job_token is not None), - device=boto3_kwargs["deviceArn"], - ) - ) - return response["quantumTaskArn"] - - def create_job(self, **boto3_kwargs) -> str: - """Create a quantum hybrid job. - - Args: - **boto3_kwargs: Keyword arguments for the Amazon Braket `CreateJob` operation. - - Returns: - str: The ARN of the hybrid job. - """ - response = self.braket_client.create_job(**boto3_kwargs) - return response["jobArn"] - - @staticmethod - def _should_giveup(err: Exception) -> bool: - return not ( - isinstance(err, ClientError) - and err.response["Error"]["Code"] - in [ - "ResourceNotFoundException", - "ThrottlingException", - ] - ) - - @backoff.on_exception( - backoff.expo, - ClientError, - max_tries=3, - jitter=backoff.full_jitter, - giveup=_should_giveup.__func__, - ) - def get_quantum_task(self, arn: str) -> dict[str, Any]: - """Gets the quantum task. - - Args: - arn (str): The ARN of the quantum task to get. - - Returns: - dict[str, Any]: The response from the Amazon Braket `GetQuantumTask` operation. - """ - response = self.braket_client.get_quantum_task( - quantumTaskArn=arn, additionalAttributeNames=["QueueInfo"] - ) - broadcast_event(_TaskStatusEvent(arn=response["quantumTaskArn"], status=response["status"])) - return response - - def get_default_jobs_role(self) -> str: - """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: - str: The ARN for the default IAM role for jobs execution created in the Amazon - Braket console. - - Raises: - RuntimeError: If no roles can be found with the prefix - `/service-role/AmazonBraketJobsExecutionRole`. - """ - roles_paginator = self.iam_client.get_paginator("list_roles") - for page in roles_paginator.paginate(PathPrefix="/service-role/"): - for role in page.get("Roles", []): - if role["RoleName"].startswith("AmazonBraketJobsExecutionRole"): - return role["Arn"] - raise RuntimeError( - "No default jobs roles found. Please create a role using the " - "Amazon Braket console or supply a custom role." - ) - - @backoff.on_exception( - backoff.expo, - ClientError, - max_tries=3, - jitter=backoff.full_jitter, - giveup=_should_giveup.__func__, - ) - def get_job(self, arn: str) -> dict[str, Any]: - """Gets the hybrid job. - - Args: - arn (str): The ARN of the hybrid job to get. - - Returns: - dict[str, Any]: The response from the Amazon Braket `GetQuantumJob` operation. - """ - return self.braket_client.get_job(jobArn=arn, additionalAttributeNames=["QueueInfo"]) - - def cancel_job(self, arn: str) -> dict[str, Any]: - """Cancel the hybrid job. - - Args: - arn (str): The ARN of the hybrid job to cancel. - - Returns: - dict[str, Any]: The response from the Amazon Braket `CancelJob` operation. - """ - 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. - - Args: - s3_bucket (str): The S3 bucket name. - s3_object_key (str): The S3 object key within the `s3_bucket`. - - Returns: - str: The body of the S3 object. - """ - s3 = self.boto_session.resource("s3", config=self._config) - obj = s3.Object(s3_bucket, s3_object_key) - return obj.get()["Body"].read().decode("utf-8") - - def upload_to_s3(self, filename: str, s3_uri: str) -> None: - """Upload file to S3. - - Args: - filename (str): local file to be uploaded. - s3_uri (str): The S3 URI where the file will be uploaded. - """ - bucket, key = self.parse_s3_uri(s3_uri) - 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 - - Args: - local_prefix (str): a prefix designating files to be uploaded to S3. All files - beginning with local_prefix will be uploaded. - s3_prefix (str): the corresponding S3 prefix that will replace the local prefix - when the data is uploaded. This will be an S3 URI and should include the bucket - (i.e. 's3://my-bucket/my/prefix-') - - Example: - local_prefix = "input", s3_prefix = "s3://my-bucket/dir/input" will upload: - - - 'input.csv' to 's3://my-bucket/dir/input.csv' - - 'input-2.csv' to 's3://my-bucket/dir/input-2.csv' - - 'input/data.txt' to 's3://my-bucket/dir/input/data.txt' - - 'input-dir/data.csv' to 's3://my-bucket/dir/input-dir/data.csv' - but will not upload: - - 'my-input.csv' - - 'my-dir/input.csv' - - To match all files within the directory "input" and upload them into - "s3://my-bucket/input", provide local_prefix = "input/" and - s3_prefix = "s3://my-bucket/input/" - """ - # support absolute paths - if Path(local_prefix).is_absolute(): - base_dir = Path(Path(local_prefix).anchor) - relative_prefix = str(Path(local_prefix).relative_to(base_dir)) - else: - base_dir = Path() - relative_prefix = local_prefix - for file in itertools.chain( - # files that match the prefix - base_dir.glob(f"{relative_prefix}*"), - # files inside of directories that match the prefix - base_dir.glob(f"{relative_prefix}*/**/*"), - ): - if file.is_file(): - s3_uri = str(file.as_posix()).replace(str(Path(local_prefix).as_posix()), s3_prefix) - self.upload_to_s3(str(file), s3_uri) - - def download_from_s3(self, s3_uri: str, filename: str) -> None: - """Download file from S3 - - Args: - s3_uri (str): The S3 uri from where the file will be downloaded. - filename (str): filename to save the file to. - """ - bucket, key = self.parse_s3_uri(s3_uri) - 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 - destination URIs are the same. - - Args: - source_s3_uri (str): S3 URI pointing to the object to be copied. - destination_s3_uri (str): S3 URI where the object will be copied to. - """ - if source_s3_uri == destination_s3_uri: - return - - source_bucket, source_key = self.parse_s3_uri(source_s3_uri) - destination_bucket, destination_key = self.parse_s3_uri(destination_s3_uri) - - self.s3_client.copy( - { - "Bucket": source_bucket, - "Key": source_key, - }, - destination_bucket, - destination_key, - ) - - 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 - 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. - - Args: - source_s3_path (str): S3 URI pointing to the directory to be copied. - destination_s3_path (str): S3 URI where the contents of the source_s3_path - directory will be copied to. - """ - if source_s3_path == destination_s3_path: - return - - source_bucket, source_prefix = AwsSession.parse_s3_uri(source_s3_path) - destination_bucket, destination_prefix = AwsSession.parse_s3_uri(destination_s3_path) - - source_keys = self.list_keys(source_bucket, source_prefix) - - for key in source_keys: - self.s3_client.copy( - { - "Bucket": source_bucket, - "Key": key, - }, - destination_bucket, - key.replace(source_prefix, destination_prefix, 1), - ) - - def list_keys(self, bucket: str, prefix: str) -> list[str]: - """Lists keys matching prefix in bucket. - - Args: - bucket (str): Bucket to be queried. - prefix (str): The S3 path prefix to be matched - - Returns: - list[str]: A list of all keys matching the prefix in - the bucket. - """ - list_objects = self.s3_client.list_objects_v2( - Bucket=bucket, - Prefix=prefix, - ) - keys = [obj["Key"] for obj in list_objects["Contents"]] - while list_objects["IsTruncated"]: - list_objects = self.s3_client.list_objects_v2( - Bucket=bucket, - Prefix=prefix, - ContinuationToken=list_objects["NextContinuationToken"], - ) - keys += [obj["Key"] for obj in list_objects["Contents"]] - return keys - - def default_bucket(self) -> str: - """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 - value of "amazon-braket--. Except in the case of a user- - specified bucket name, this method will create the default bucket if it does not - exist. - - Returns: - str: Name of the default bucket. - """ - if self._default_bucket: - return self._default_bucket - default_bucket = f"amazon-braket-{self.region}-{self.account_id}" - - self._create_s3_bucket_if_it_does_not_exist(bucket_name=default_bucket, region=self.region) - - self._default_bucket = default_bucket - return self._default_bucket - - def _create_s3_bucket_if_it_does_not_exist(self, bucket_name: str, region: str) -> None: - """Creates an S3 Bucket if it does not exist. - Also swallows a few common exceptions that indicate that the bucket already exists or - that it is being created. - - Args: - bucket_name (str): Name of the S3 bucket to be created. - region (str): The region in which to create the bucket. - - Raises: - botocore.exceptions.ClientError: If S3 throws an unexpected exception during bucket - creation. - If the exception is due to the bucket already existing or - already being created, no exception is raised. - """ - try: - if region == "us-east-1": - # 'us-east-1' cannot be specified because it is the default region: - # https://github.com/boto/boto3/issues/125 - self.s3_client.create_bucket(Bucket=bucket_name) - else: - self.s3_client.create_bucket( - Bucket=bucket_name, CreateBucketConfiguration={"LocationConstraint": region} - ) - self.s3_client.put_public_access_block( - Bucket=bucket_name, - PublicAccessBlockConfiguration={ - "BlockPublicAcls": True, - "IgnorePublicAcls": True, - "BlockPublicPolicy": True, - "RestrictPublicBuckets": True, - }, - ) - self.s3_client.put_bucket_policy( - Bucket=bucket_name, - Policy=f"""{{ - "Version": "2012-10-17", - "Statement": [ - {{ - "Effect": "Allow", - "Principal": {{ - "Service": [ - "braket.amazonaws.com" - ] - }}, - "Action": "s3:*", - "Resource": [ - "arn:aws:s3:::{bucket_name}", - "arn:aws:s3:::{bucket_name}/*" - ] - }} - ] - }}""", - ) - except ClientError as e: - error_code = e.response["Error"]["Code"] - message = e.response["Error"]["Message"] - - 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( - f"Provided default bucket '{bucket_name}' already exists " - f"for another account. Please supply alternative " - f"bucket name via AwsSession constructor `AwsSession()`." - ) from None - else: - raise - - def get_device(self, arn: str) -> dict[str, Any]: - """Calls the Amazon Braket `get_device` API to retrieve device metadata. - - Args: - arn (str): The ARN of the device. - - Returns: - dict[str, Any]: The response from the Amazon Braket `GetDevice` operation. - """ - return self.braket_client.get_device(deviceArn=arn) - - 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, - ) -> list[dict[str, Any]]: - """Get devices based on filters. The result is the AND of - all the filters `arns`, `names`, `types`, `statuses`, `provider_names`. - - Args: - arns (Optional[list[str]]): device ARN filter, default is `None`. - names (Optional[list[str]]): device name filter, default is `None`. - types (Optional[list[str]]): device type filter, default is `None`. - statuses (Optional[list[str]]): device status filter, default is `None`. When `None` - is used, RETIRED devices will not be returned. To include RETIRED devices in - the results, use a filter that includes "RETIRED" for this parameter. - provider_names (Optional[list[str]]): provider name list, default is `None`. - - Returns: - list[dict[str, Any]]: The response from the Amazon Braket `SearchDevices` operation. - """ - filters = [] - if arns: - filters.append({"name": "deviceArn", "values": arns}) - paginator = self.braket_client.get_paginator("search_devices") - page_iterator = paginator.paginate(filters=filters, PaginationConfig={"MaxItems": 100}) - results = [] - for page in page_iterator: - for result in page["devices"]: - if names and result["deviceName"] not in names: - continue - if types and result["deviceType"] not in types: - continue - if statuses and result["deviceStatus"] not in statuses: - continue - if statuses is None and result["deviceStatus"] == "RETIRED": - continue - if provider_names and result["providerName"] not in provider_names: - continue - results.append(result) - return results - - @staticmethod - def is_s3_uri(string: str) -> bool: - """Determines if a given string is an S3 URI. - - Args: - string (str): the string to check. - - Returns: - bool: Returns True if the given string is an S3 URI. - """ - try: - AwsSession.parse_s3_uri(string) - except ValueError: - return False - return True - - @staticmethod - def parse_s3_uri(s3_uri: str) -> tuple[str, str]: - """Parse S3 URI to get bucket and key - - Args: - s3_uri (str): S3 URI. - - Returns: - tuple[str, str]: Bucket and Key tuple. - - Raises: - ValueError: Raises a ValueError if the provided string is not - a valid S3 URI. - """ - 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(r"^https://([^./]+)\.[sS]3\.[^/]+/(.+)$", s3_uri) or re.match( - r"^[sS]3://([^./]+)/(.+)$", s3_uri - ) - if s3_uri_match is None: - raise AssertionError - 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 - - @staticmethod - def construct_s3_uri(bucket: str, *dirs: str) -> str: - """Create an S3 URI given a bucket and path. - - Args: - bucket (str): S3 URI. - *dirs (str): directories to be appended in the resulting S3 URI - - Returns: - str: S3 URI - - Raises: - ValueError: Raises a ValueError if the provided arguments are not - valid to generate an S3 URI - """ - if not dirs: - raise ValueError(f"Not a valid S3 location: s3://{bucket}") - return f"s3://{bucket}/{'/'.join(dirs)}" - - def describe_log_streams( - self, - log_group: str, - log_stream_prefix: str, - limit: Optional[int] = None, - next_token: Optional[str] = None, - ) -> dict[str, Any]: - """Describes CloudWatch log streams in a log group with a given prefix. - - Args: - log_group (str): Name of the log group. - log_stream_prefix (str): Prefix for log streams to include. - limit (Optional[int]): Limit for number of log streams returned. - default is 50. - next_token (Optional[str]): The token for the next set of items to return. - Would have been received in a previous call. - - Returns: - dict[str, Any]: Dictionary containing logStreams and nextToken - """ - log_stream_args = { - "logGroupName": log_group, - "logStreamNamePrefix": log_stream_prefix, - "orderBy": "LogStreamName", - } - - if limit: - log_stream_args["limit"] = limit - - if next_token: - log_stream_args["nextToken"] = next_token - - return self.logs_client.describe_log_streams(**log_stream_args) - - def get_log_events( - self, - log_group: str, - log_stream: str, - start_time: int, - start_from_head: bool = True, - next_token: Optional[str] = None, - ) -> dict[str, Any]: - """Gets CloudWatch log events from a given log stream. - - Args: - log_group (str): Name of the log group. - log_stream (str): Name of the log stream. - start_time (int): Timestamp that indicates a start time to include log events. - start_from_head (bool): Bool indicating to return oldest events first. default - is True. - next_token (Optional[str]): The token for the next set of items to return. - Would have been received in a previous call. - - Returns: - dict[str, Any]: Dictionary containing events, nextForwardToken, and nextBackwardToken - """ - log_events_args = { - "logGroupName": log_group, - "logStreamName": log_stream, - "startTime": start_time, - "startFromHead": start_from_head, - } - - if next_token: - log_events_args["nextToken"] = next_token - - return self.logs_client.get_log_events(**log_events_args) - - def copy_session( - self, - region: Optional[str] = None, - max_connections: Optional[int] = None, - ) -> AwsSession: - """Creates a new AwsSession based on the region. - - Args: - region (Optional[str]): Name of the region. Default = `None`. - max_connections (Optional[int]): The maximum number of connections in the - Boto3 connection pool. Default = `None`. - - Returns: - AwsSession: based on the region and boto config parameters. - """ - 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 - profile_name = profile_name if profile_name != "default" else None - if creds.method == "explicit": - boto_session = boto3.Session( - aws_access_key_id=creds.access_key, - aws_secret_access_key=creds.secret_key, - aws_session_token=creds.token, - region_name=new_region, - profile_name=profile_name, - ) - elif creds.method == "env": - boto_session = boto3.Session(region_name=new_region) - else: - boto_session = boto3.Session( - region_name=new_region, - profile_name=profile_name, - ) - 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 - def get_full_image_tag(self, image_uri: str) -> str: - """Get verbose image tag from image uri. - - Args: - image_uri (str): Image uri to get tag for. - - Returns: - str: Verbose image tag for given image. - """ - registry = image_uri.split(".")[0] - repository, tag = image_uri.split("/")[-1].split(":") - - # get image digest of latest image - digest = self.ecr_client.batch_get_image( - registryId=registry, - repositoryName=repository, - imageIds=[{"imageTag": tag}], - )["images"][0]["imageId"]["imageDigest"] - - # get all images matching digest (same image, different tags) - images = self.ecr_client.batch_get_image( - registryId=registry, - repositoryName=repository, - imageIds=[{"imageDigest": digest}], - )["images"] - - # find the tag with the python version info - for image in images: - if re.search(r"py\d\d+", tag := image["imageId"]["imageTag"]): - return tag - - raise ValueError("Full image tag missing.") diff --git a/src/braket/aws/direct_reservations.py b/src/braket/aws/direct_reservations.py deleted file mode 100644 index 4ffcb8fc..00000000 --- a/src/braket/aws/direct_reservations.py +++ /dev/null @@ -1,98 +0,0 @@ -# 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/src/braket/aws/queue_information.py b/src/braket/aws/queue_information.py deleted file mode 100644 index 77e5f355..00000000 --- a/src/braket/aws/queue_information.py +++ /dev/null @@ -1,81 +0,0 @@ -# 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 dataclasses import dataclass -from enum import Enum -from typing import Optional - - -class QueueType(str, Enum): - """Enumerates the possible priorities for the queue. - - Values: - NORMAL: Represents normal queue for the device. - PRIORITY: Represents priority queue for the device. - """ - - NORMAL = "Normal" - PRIORITY = "Priority" - - -@dataclass() -class QueueDepthInfo: - """Represents quantum tasks and hybrid jobs queue depth information. - - Attributes: - quantum_tasks (dict[QueueType, str]): number of quantum tasks waiting - to run on a device. This includes both 'Normal' and 'Priority' tasks. - For Example, {'quantum_tasks': {QueueType.NORMAL: '7', QueueType.PRIORITY: '3'}} - jobs (str): number of hybrid jobs waiting to run on a device. Additionally, for QPUs if - hybrid jobs queue depth is 0, we display information about priority and count of the - running hybrid jobs. Example, 'jobs': '0 (1 prioritized job(s) running)' - """ - - quantum_tasks: dict[QueueType, str] - jobs: str - - -@dataclass -class QuantumTaskQueueInfo: - """Represents quantum tasks queue information. - - Attributes: - queue_type (QueueType): type of the quantum_task queue either 'Normal' - or 'Priority'. - queue_position (Optional[str]): current position of your quantum task within a respective - device queue. This value can be None based on the state of the task. Default: None. - message (Optional[str]): Additional message information. This key is present only - if 'queue_position' is None. Default: None. - """ - - queue_type: QueueType - queue_position: Optional[str] = None - message: Optional[str] = None - - -@dataclass -class HybridJobQueueInfo: - """Represents hybrid job queue information. - - Attributes: - queue_position (Optional[str]): current position of your hybrid job within a respective - device queue. If the queue position of the hybrid job is greater than 15, we - return '>15' as the queue_position return value. The queue_position is only - returned when hybrid job is not in RUNNING/CANCELLING/TERMINAL states, else - queue_position is returned as None. - message (Optional[str]): Additional message information. This key is present only - if 'queue_position' is None. Default: None. - """ - - queue_position: Optional[str] = None - message: Optional[str] = None diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py deleted file mode 100644 index a5fb5298..00000000 --- a/src/braket/circuits/__init__.py +++ /dev/null @@ -1,45 +0,0 @@ -# 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 ( # noqa: F401 - circuit, - compiler_directives, - gates, - noises, - observables, - result_types, -) -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 -from braket.circuits.free_parameter import FreeParameter # noqa: F401 -from braket.circuits.free_parameter_expression import FreeParameterExpression # noqa: F401 -from braket.circuits.gate import Gate # noqa: F401 -from braket.circuits.gate_calibrations import GateCalibrations # noqa: F401 -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.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.text_diagram_builders.unicode_circuit_diagram import ( # noqa: F401 - UnicodeCircuitDiagram, -) diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py deleted file mode 100644 index 49447e58..00000000 --- a/src/braket/circuits/angled_gate.py +++ /dev/null @@ -1,459 +0,0 @@ -# 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 copy -import math -from collections.abc import Sequence -from functools import singledispatch -from typing import Optional, Union - -from sympy import Float - -from braket.circuits.free_parameter_expression import FreeParameterExpression -from braket.circuits.gate import Gate -from braket.circuits.parameterizable import Parameterizable - - -class AngledGate(Gate, Parameterizable): - """Class `AngledGate` represents a quantum gate that operates on N qubits and an angle.""" - - def __init__( - self, - angle: Union[FreeParameterExpression, float], - 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. - qubit_count (Optional[int]): The number of qubits that this gate interacts with. - ascii_symbols (Sequence[str]): ASCII string symbols for the gate. These are used when - 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. - For instance, if a CNOT instruction has the control qubit on the first index and - target qubit on the second index, the ASCII symbols should have `["C", "X"]` to - correlate a symbol with that index. - - Raises: - ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or - `ascii_symbols` length != `qubit_count`, or `angle` is `None` - """ - super().__init__(qubit_count=qubit_count, ascii_symbols=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 parameters(self) -> list[Union[FreeParameterExpression, float]]: - """Returns the parameters associated with the object, either unbound free parameters or - bound values. - - Returns: - list[Union[FreeParameterExpression, float]]: The free parameters or fixed value - associated with the object. - """ - return self._parameters - - @property - def angle(self) -> Union[FreeParameterExpression, float]: - """Returns the angle of the gate - - Returns: - Union[FreeParameterExpression, float]: The angle of the gate in radians - """ - return self._parameters[0] - - def bind_values(self, **kwargs) -> AngledGate: - """Takes in parameters and attempts to assign them to values. - - Returns: - AngledGate: A new Gate of the same type with the requested parameters bound. - - Raises: - NotImplementedError: Subclasses should implement this function. - """ - raise NotImplementedError - - def adjoint(self) -> list[Gate]: - """Returns the adjoint of this gate as a singleton list. - - Returns: - list[Gate]: A list containing the gate with negated angle. - """ - gate_ascii_name_index = self.ascii_symbols[0].find("(") - gate_ascii_name = self.ascii_symbols[0][:gate_ascii_name_index] - new_ascii_symbols = [ - angled_ascii_characters(gate_ascii_name, -self.angle) - ] * self.qubit_count - new = copy.copy(self) - new._parameters = [-angle for angle in self._parameters] - new._ascii_symbols = new_ascii_symbols - return [new] - - def __eq__(self, other: AngledGate): - return ( - isinstance(other, AngledGate) - and self.name == other.name - and _angles_equal(self.angle, other.angle) - ) - - def __repr__(self): - return f"{self.name}('angle': {self.angle}, 'qubit_count': {self.qubit_count})" - - def __hash__(self): - return hash((self.name, self.angle, self.qubit_count)) - - -class DoubleAngledGate(Gate, Parameterizable): - """Class `DoubleAngledGate` represents a quantum gate that operates on N qubits and - two angles. - """ - - def __init__( - self, - angle_1: Union[FreeParameterExpression, float], - angle_2: Union[FreeParameterExpression, float], - 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. - angle_2 (Union[FreeParameterExpression, float]): The second angle of the gate in - radians or expression representation. - qubit_count (Optional[int]): The number of qubits that this gate interacts with. - ascii_symbols (Sequence[str]): ASCII string symbols for the gate. These are used when - 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. - For instance, if a CNOT instruction has the control qubit on the first index and - target qubit on the second index, the ASCII symbols should have `["C", "X"]` to - correlate a symbol with that index. - - Raises: - ValueError: If `qubit_count` is less than 1, `ascii_symbols` are `None`, or - `ascii_symbols` length != `qubit_count`, or `angle_1` or `angle_2` is `None` - """ - super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) - if angle_1 is None or angle_2 is None: - raise ValueError("angles must not be None") - self._parameters = [ - ( - angle - if isinstance(angle, FreeParameterExpression) - else float(angle) # explicit casting in case angle is e.g. np.float32 - ) - for angle in (angle_1, angle_2) - ] - - @property - def parameters(self) -> list[Union[FreeParameterExpression, float]]: - """Returns the parameters associated with the object, either unbound free parameters or - bound values. - - Returns: - list[Union[FreeParameterExpression, float]]: The free parameters or fixed value - associated with the object. - """ - return self._parameters - - @property - def angle_1(self) -> Union[FreeParameterExpression, float]: - """Returns the first angle of the gate - - Returns: - Union[FreeParameterExpression, float]: The first angle of the gate in radians - """ - return self._parameters[0] - - @property - def angle_2(self) -> Union[FreeParameterExpression, float]: - """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: FreeParameterExpression | str) -> AngledGate: - """Takes in parameters and attempts to assign them to values. - - Args: - **kwargs (FreeParameterExpression | str): The parameters that are being assigned. - - Returns: - AngledGate: A new Gate of the same type with the requested parameters bound. - - Raises: - NotImplementedError: Subclasses should implement this function. - """ - raise NotImplementedError - - def adjoint(self) -> list[Gate]: - """Returns the adjoint of this gate as a singleton list. - - Returns: - list[Gate]: A list containing the gate with negated angle. - """ - raise NotImplementedError - - def __eq__(self, other: DoubleAngledGate): - return ( - isinstance(other, DoubleAngledGate) - and self.name == other.name - and _angles_equal(self.angle_1, other.angle_1) - and _angles_equal(self.angle_2, other.angle_2) - ) - - def __repr__(self): - return ( - f"{self.name}('angles': ({self.angle_1}, {self.angle_2}), " - f"'qubit_count': {self.qubit_count})" - ) - - def __hash__(self): - return hash((self.name, self.angle_1, self.angle_2, self.qubit_count)) - - -class TripleAngledGate(Gate, Parameterizable): - """Class `TripleAngledGate` represents a quantum gate that operates on N qubits and - three angles. - """ - - def __init__( - self, - angle_1: Union[FreeParameterExpression, float], - angle_2: Union[FreeParameterExpression, float], - angle_3: Union[FreeParameterExpression, float], - 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. - angle_2 (Union[FreeParameterExpression, float]): The second angle of the gate in - radians or expression representation. - angle_3 (Union[FreeParameterExpression, float]): The third angle of the gate in - radians or expression representation. - qubit_count (Optional[int]): The number of qubits that this gate interacts with. - ascii_symbols (Sequence[str]): ASCII string symbols for the gate. These are used when - 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. - For instance, if a CNOT instruction has the control qubit on the first index and - target qubit on the second index, the ASCII symbols should have `["C", "X"]` to - correlate a symbol with that index. - - Raises: - ValueError: If `qubit_count` is less than 1, `ascii_symbols` are `None`, or - `ascii_symbols` length != `qubit_count`, or `angle_1` or `angle_2` or `angle_3` - is `None` - """ - super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) - if angle_1 is None or angle_2 is None or angle_3 is None: - raise ValueError("angles must not be None") - self._parameters = [ - ( - angle - if isinstance(angle, FreeParameterExpression) - else float(angle) # explicit casting in case angle is e.g. np.float32 - ) - for angle in (angle_1, angle_2, angle_3) - ] - - @property - def parameters(self) -> list[Union[FreeParameterExpression, float]]: - """Returns the parameters associated with the object, either unbound free parameters or - bound values. - - Returns: - list[Union[FreeParameterExpression, float]]: The free parameters or fixed value - associated with the object. - """ - return self._parameters - - @property - def angle_1(self) -> Union[FreeParameterExpression, float]: - """Returns the first angle of the gate - - Returns: - Union[FreeParameterExpression, float]: The first angle of the gate in radians - """ - return self._parameters[0] - - @property - def angle_2(self) -> Union[FreeParameterExpression, float]: - """Returns the second angle of the gate - - Returns: - Union[FreeParameterExpression, float]: The second angle of the gate in radians - """ - return self._parameters[1] - - @property - def angle_3(self) -> Union[FreeParameterExpression, float]: - """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: FreeParameterExpression | str) -> AngledGate: - """Takes in parameters and attempts to assign them to values. - - Args: - **kwargs (FreeParameterExpression | str): The parameters that are being assigned. - - Returns: - AngledGate: A new Gate of the same type with the requested parameters bound. - - Raises: - NotImplementedError: Subclasses should implement this function. - """ - raise NotImplementedError - - def adjoint(self) -> list[Gate]: - """Returns the adjoint of this gate as a singleton list. - - Returns: - list[Gate]: A list containing the gate with negated angle. - """ - raise NotImplementedError - - def __eq__(self, other: TripleAngledGate): - return ( - isinstance(other, TripleAngledGate) - and self.name == other.name - and _angles_equal(self.angle_1, other.angle_1) - and _angles_equal(self.angle_2, other.angle_2) - and _angles_equal(self.angle_3, other.angle_3) - ) - - def __repr__(self): - return ( - f"{self.name}('angles': ({self.angle_1}, {self.angle_2}, {self.angle_3}), " - f"'qubit_count': {self.qubit_count})" - ) - - 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: Union[FreeParameterExpression, float], angle_2: Union[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): - return angle_1 == angle_2 - - -def angled_ascii_characters(gate: str, angle: Union[FreeParameterExpression, float]) -> str: - """Generates a formatted ascii representation of an angled gate. - - Args: - gate (str): The name of the gate. - angle (Union[FreeParameterExpression, float]): The angle for the gate. - - Returns: - str: Returns the ascii representation for an angled gate. - - """ - return f'{gate}({angle:{".2f" if isinstance(angle, (float, Float)) else ""}})' - - -def _multi_angled_ascii_characters( - gate: str, - *angles: Union[FreeParameterExpression, float], -) -> str: - """Generates a formatted ascii representation of an angled gate. - - Args: - gate (str): The name of the gate. - *angles (Union[FreeParameterExpression, float]): angles in radians. - - Returns: - str: Returns the ascii representation for an angled gate. - - """ - - def format_string(angle: FreeParameterExpression | float) -> str: - """Formats an angle for ASCII representation. - - Args: - angle (FreeParameterExpression | float): The angle to format. - - Returns: - str: The ASCII representation of the angle. - """ - return ".2f" if isinstance(angle, (float, Float)) else "" - - return f"{gate}({', '.join(f'{angle:{format_string(angle)}}' for angle in angles)})" - - -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 (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 - angles updated. - """ - new_angle = ( - gate.angle.subs(kwargs) if isinstance(gate.angle, FreeParameterExpression) else gate.angle - ) - return type(gate)(angle=new_angle) - - -def _get_angles( - gate: DoubleAngledGate | TripleAngledGate, **kwargs: FreeParameterExpression | str -) -> DoubleAngledGate | TripleAngledGate: - """Gets the angle with all values substituted in that are requested. - - Args: - 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: - DoubleAngledGate | TripleAngledGate: A new gate of the type of the AngledGate - originally used with all angles updated. - """ - 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 angles - } - return type(gate)(**new_angles_args) diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py deleted file mode 100644 index accbce16..00000000 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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. - -# 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/basis_state.py b/src/braket/circuits/basis_state.py deleted file mode 100644 index 86578fc8..00000000 --- a/src/braket/circuits/basis_state.py +++ /dev/null @@ -1,84 +0,0 @@ -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): - self.state = _as_tuple(state, size) - - @property - def size(self) -> int: - return len(self.state) - - @property - def as_tuple(self) -> tuple: - return self.state - - @property - def as_int(self) -> int: - return 2 ** np.arange(self.size)[::-1] @ self.state - - @property - def as_string(self) -> str: - return "".join(map(str, self.state)) - - def __len__(self) -> int: - return len(self.state) - - def __iter__(self): - return iter(self.state) - - def __eq__(self, other: BasisState): - 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: int): - return BasisState(self.state[item]) - - -BasisStateInput = Union[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) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py deleted file mode 100644 index 4371637d..00000000 --- a/src/braket/circuits/braket_program_context.py +++ /dev/null @@ -1,176 +0,0 @@ -# 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 typing import Optional, Union - -import numpy as np -from sympy import Expr, Number - -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, - 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 - - -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. - """ - super().__init__() - self._circuit = circuit or Circuit() - - @property - def circuit(self) -> Circuit: - """The circuit being built in this context.""" - return self._circuit - - def is_builtin_gate(self, name: str) -> bool: - """Whether the gate is currently in scope as a built-in Braket gate. - - Args: - name (str): name of the built-in Braket gate - - Returns: - bool: return TRUE if it is a built-in gate else FALSE. - """ - 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: 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 - ) -> None: - """Add Braket gate to the circuit. - - Args: - gate_name (str): name of the built-in Braket gate. - target (tuple[int]): control_qubits + target_qubits. - ctrl_modifiers (list[int]): Quantum state on which to control the - operation. Must be a binary sequence of same length as number of qubits in - `control-qubits` in target. 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. - power(float): Integer or fractional power to raise the gate to. - """ - target_qubits = target[len(ctrl_modifiers) :] - control_qubits = target[: len(ctrl_modifiers)] - instruction = Instruction( - BRAKET_GATES[gate_name](*params[0]), - target=target_qubits, - control=control_qubits, - control_state=ctrl_modifiers, - power=power, - ) - self._circuit.add_instruction(instruction) - - def add_custom_unitary( - self, - unitary: np.ndarray, - target: tuple[int], - ) -> None: - """Add a custom Unitary instruction to the circuit - - Args: - unitary (np.ndarray): unitary matrix - target (tuple[int]): control_qubits + target_qubits - """ - instruction = Instruction(Unitary(unitary), target) - self._circuit.add_instruction(instruction) - - def add_noise_instruction( - self, noise_instruction: str, target: list[int], probabilities: list[float] - ) -> None: - """Method to add a noise instruction to the circuit - - Args: - noise_instruction (str): The name of the noise operation - target (list[int]): The target qubit or qubits to which the noise operation is applied. - probabilities (list[float]): The probabilities associated with each possible outcome - of the noise operation. - """ - instruction = Instruction( - one_prob_noise_map[noise_instruction](*probabilities), target=target - ) - self._circuit.add_instruction(instruction) - - def add_kraus_instruction(self, matrices: list[np.ndarray], target: list[int]) -> None: - """Method to add a Kraus instruction to the circuit - - Args: - matrices (list[ndarray]): The matrices defining the Kraus operation - target (list[int]): The target qubit or qubits to which the Kraus operation is applied. - """ - instruction = Instruction(Kraus(matrices), target) - self._circuit.add_instruction(instruction) - - def add_result(self, result: Results) -> None: - """Abstract method to add result type to the circuit - - Args: - result (Results): The result object representing the measurement results - """ - self._circuit.add_result_type(braket_result_to_result_type(result)) - - def handle_parameter_value( - self, value: Union[float, Expr] - ) -> Union[float, FreeParameterExpression]: - """Convert parameter value to required format. - - Args: - value (Union[float, Expr]): Value of the parameter - - Returns: - Union[float, FreeParameterExpression]: Return the value directly if numeric, - otherwise wraps the symbolic expression as a `FreeParameterExpression`. - """ - if isinstance(value, Expr): - evaluated_value = value.evalf() - if isinstance(evaluated_value, Number): - 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. - """ - 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/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py deleted file mode 100644 index f15e0364..00000000 --- a/src/braket/circuits/circuit.py +++ /dev/null @@ -1,1677 +0,0 @@ -# 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 -from collections.abc import Callable, Iterable -from numbers import Number -from typing import Any, Optional, TypeVar, Union - -import numpy as np -import oqpy -from sympy import Expr - -from braket.circuits import compiler_directives -from braket.circuits.free_parameter import FreeParameter -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 ( - apply_noise_to_gates, - apply_noise_to_moments, - check_noise_target_gates, - check_noise_target_qubits, - check_noise_target_unitary, - wrap_with_list, -) -from braket.circuits.observable import Observable -from braket.circuits.observables import TensorProduct -from braket.circuits.parameterizable import Parameterizable -from braket.circuits.result_type import ( - ObservableParameterResultType, - ObservableResultType, - ResultType, -) -from braket.circuits.serialization import ( - IRType, - OpenQASMSerializationProperties, - 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 -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 -from braket.pulse.waveforms import Waveform -from braket.registers.qubit import QubitInput -from braket.registers.qubit_set import QubitSet, QubitSetInput - -SubroutineReturn = TypeVar( - "SubroutineReturn", Iterable[Instruction], Instruction, ResultType, Iterable[ResultType] -) -SubroutineCallable = TypeVar("SubroutineCallable", bound=Callable[..., SubroutineReturn]) -AddableTypes = TypeVar("AddableTypes", SubroutineReturn, SubroutineCallable) - - -class Circuit: - """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. - - See :mod:`braket.circuits.result_types` module for all of the supported result types. - - `AddableTypes` are `Instruction`, iterable of `Instruction`, `ResultType`, - iterable of `ResultType`, or `SubroutineCallable` - """ - - _ALL_QUBITS = "ALL" # Flag to indicate all qubits in _qubit_observable_mapping - - @classmethod - def register_subroutine(cls, func: SubroutineCallable) -> None: - """Register the subroutine `func` as an attribute of the `Circuit` class. The attribute name - is the name of `func`. - - Args: - func (SubroutineCallable): The function of the subroutine to add to the class. - - Examples: - >>> def h_on_all(target): - ... circ = Circuit() - ... 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: - return self.add(func, *args, **kwargs) - - function_name = func.__name__ - setattr(cls, function_name, method_from_subroutine) - - function_attr = getattr(cls, function_name) - 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. - - Raises: - TypeError: If `addable` is an unsupported type. - - Examples: - >>> circ = Circuit([Instruction(Gate.H(), 4), Instruction(Gate.CNot(), [4, 5])]) - >>> circ = Circuit().h(0).cnot(0, 1) - >>> circ = Circuit().h(0).cnot(0, 1).probability([0, 1]) - - >>> @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]) - - """ - self._moments: Moments = Moments() - self._result_types: dict[ResultType] = {} - self._qubit_observable_mapping: dict[Union[int, Circuit._ALL_QUBITS], Observable] = {} - self._qubit_observable_target_mapping: dict[int, tuple[int]] = {} - self._qubit_observable_set = set() - 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) - - @property - 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.""" - return list(self._moments.values()) - - @property - def result_types(self) -> list[ResultType]: - """list[ResultType]: Get a list of requested result types in the circuit.""" - return list(self._result_types.keys()) - - @property - def basis_rotation_instructions(self) -> list[Instruction]: - """Gets a list of basis rotation instructions. - - Returns: - list[Instruction]: Get a list of basis rotation instructions in the circuit. - These basis rotation instructions are added if result types are requested for - an observable other than Pauli-Z. - - This only makes sense if all observables are simultaneously measurable; - if not, this method will return an empty list. - """ - # 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 = [] - 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 - ) - return basis_rotation_instructions - - target_lists = sorted(set(self._qubit_observable_target_mapping.values())) - for target_list in target_lists: - observable = self._qubit_observable_mapping[target_list[0]] - basis_rotation_instructions += Circuit._observable_to_instruction( - observable, target_list - ) - return basis_rotation_instructions - - @staticmethod - def _observable_to_instruction( - observable: Observable, target_list: list[int] - ) -> list[Instruction]: - return [Instruction(gate, target_list) for gate in observable.basis_rotation_gates] - - @property - def moments(self) -> Moments: - """Moments: Get the `moments` for this circuit. Note that this includes observables.""" - return 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. - """ - all_qubits = self._moments.qubits.union(self._qubit_observable_set) - return len(all_qubits) - - @property - def qubits(self) -> QubitSet: - """QubitSet: Get a copy of the qubits for this circuit.""" - return QubitSet(self._moments.qubits.union(self._qubit_observable_set)) - - @property - def parameters(self) -> set[FreeParameter]: - """Gets a set of the parameters in the Circuit. - - Returns: - set[FreeParameter]: The `FreeParameters` in the Circuit. - """ - return self._parameters - - def add_result_type( - self, - result_type: ResultType, - target: QubitSetInput | None = None, - target_mapping: dict[QubitInput, QubitInput] | None = None, - ) -> Circuit: - """Add a requested result type to `self`, returns `self` for chaining ability. - - Args: - result_type (ResultType): `ResultType` to add into `self`. - target (QubitSetInput | None): Target qubits for the - `result_type`. - Default = `None`. - target_mapping (dict[QubitInput, QubitInput] | None): A dictionary of - qubit mappings to apply to the `result_type.target`. Key is the qubit in - `result_type.target` and the value is what the key will be changed to. - Default = `None`. - - - Returns: - Circuit: self - - Note: - Target and target_mapping will only be applied to those requested result types with - the attribute `target`. The result_type will be appended to the end of the dict keys of - `circuit.result_types` only if it does not already exist in `circuit.result_types` - - Raises: - TypeError: If both `target_mapping` and `target` are supplied. - ValueError: If a measure instruction exists on the current circuit. - - Examples: - >>> result_type = ResultType.Probability(target=[0, 1]) - >>> circ = Circuit().add_result_type(result_type) - >>> print(circ.result_types[0]) - Probability(target=QubitSet([Qubit(0), Qubit(1)])) - - >>> result_type = ResultType.Probability(target=[0, 1]) - >>> circ = Circuit().add_result_type(result_type, target_mapping={0: 10, 1: 11}) - >>> print(circ.result_types[0]) - Probability(target=QubitSet([Qubit(10), Qubit(11)])) - - >>> result_type = ResultType.Probability(target=[0, 1]) - >>> circ = Circuit().add_result_type(result_type, target=[10, 11]) - >>> print(circ.result_types[0]) - Probability(target=QubitSet([Qubit(10), Qubit(11)])) - - >>> result_type = ResultType.StateVector() - >>> circ = Circuit().add_result_type(result_type) - >>> print(circ.result_types[0]) - StateVector() - """ - 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 - elif target_mapping: - # Target mapping has been supplied, copy result_type - result_type_to_add = result_type.copy(target_mapping=target_mapping) - else: - # ResultType with target - result_type_to_add = result_type.copy(target=target) - - if result_type_to_add not in self._result_types: - 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. - supports_basis_rotation_instructions = not isinstance( - result_type_to_add, ObservableParameterResultType - ) - if ( - observable - and self._observables_simultaneously_measurable - and supports_basis_rotation_instructions - ): - # Only check if all observables can be simultaneously measured - self._add_to_qubit_observable_mapping(observable, result_type_to_add.target) - - self._add_to_qubit_observable_set(result_type_to_add) - # using dict as an ordered set, value is arbitrary - self._result_types[result_type_to_add] = None - return self - - @staticmethod - def _extract_observable(result_type: ResultType) -> Optional[Observable]: - if isinstance(result_type, ResultType.Probability): - return Observable.Z() # computational basis - elif isinstance(result_type, ObservableResultType): - return result_type.observable - else: - return None - - def _add_to_qubit_observable_mapping( - self, observable: Observable, observable_target: QubitSet - ) -> None: - targets = observable_target or list(self._qubit_observable_set) - all_qubits_observable = self._qubit_observable_mapping.get(Circuit._ALL_QUBITS) - tensor_product_dict = ( - Circuit._tensor_product_index_dict(observable, observable_target) - if isinstance(observable, TensorProduct) - else None - ) - identity = Observable.I() - for i in range(len(targets)): - target = targets[i] - new_observable = tensor_product_dict[i][0] if tensor_product_dict else observable - current_observable = all_qubits_observable or self._qubit_observable_mapping.get(target) - - add_observable = not current_observable or ( - current_observable == identity and new_observable != identity - ) - if ( - not add_observable - and current_observable != identity - and new_observable != identity - and current_observable != new_observable - ): - return self._encounter_noncommuting_observable() - - if observable_target: - new_targets = ( - tensor_product_dict[i][1] if tensor_product_dict else tuple(observable_target) - ) - - if add_observable: - self._qubit_observable_target_mapping[target] = new_targets - self._qubit_observable_mapping[target] = new_observable - elif new_observable.qubit_count > 1: - current_target = self._qubit_observable_target_mapping.get(target) - if current_target and current_target != new_targets: - return self._encounter_noncommuting_observable() - - if not observable_target and observable != identity: - if all_qubits_observable and all_qubits_observable != observable: - return self._encounter_noncommuting_observable() - self._qubit_observable_mapping[Circuit._ALL_QUBITS] = observable - - @staticmethod - def _tensor_product_index_dict( - observable: TensorProduct, observable_target: QubitSet - ) -> dict[int, tuple[Observable, tuple[int, ...]]]: - obj_dict = {} - i = 0 - factors = list(observable.factors) - total = factors[0].qubit_count - while factors: - if i >= total: - factors.pop(0) - if factors: - total += factors[0].qubit_count - if factors: - first = total - factors[0].qubit_count - obj_dict[i] = (factors[0], tuple(observable_target[first:total])) - i += 1 - return obj_dict - - 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 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, - instruction: Instruction, - target: QubitSetInput | None = None, - target_mapping: dict[QubitInput, QubitInput] | None = None, - ) -> Circuit: - """Add an instruction to `self`, returns `self` for chaining ability. - - 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`. - - Returns: - Circuit: self - - 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]) - >>> circ = Circuit().add_instruction(instr) - >>> print(circ.instructions[0]) - Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(0), Qubit(1))) - - >>> instr = Instruction(Gate.CNot(), [0, 1]) - >>> circ = Circuit().add_instruction(instr, target_mapping={0: 10, 1: 11}) - >>> print(circ.instructions[0]) - Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(10), Qubit(11))) - - >>> instr = Instruction(Gate.CNot(), [0, 1]) - >>> circ = Circuit().add_instruction(instr, target=[10, 11]) - >>> print(circ.instructions[0]) - Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(10), Qubit(11))) - - >>> instr = Instruction(Gate.H(), 0) - >>> circ = Circuit().add_instruction(instr, target=[10, 11]) - >>> print(circ.instructions[0]) - Instruction('operator': 'H', 'target': QubitSet(Qubit(10),)) - >>> print(circ.instructions[1]) - Instruction('operator': 'H', 'target': QubitSet(Qubit(11),)) - """ - 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 - 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] - elif target_mapping: - # Target mapping has been supplied, copy instruction - instructions_to_add = [instruction.copy(target_mapping=target_mapping)] - elif hasattr(instruction.operator, "qubit_count") and instruction.operator.qubit_count == 1: - # single qubit operator with target, add an instruction for each target - instructions_to_add = [instruction.copy(target=qubit) for qubit in target] - else: - # non single qubit operator with target, add instruction with target - instructions_to_add = [instruction.copy(target=target)] - - if self._check_for_params(instruction): - for param in instruction.operator.parameters: - 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)) - self._moments.add(instructions_to_add) - - return self - - def _check_for_params(self, instruction: Instruction) -> bool: - """This checks for free parameters in an :class:{Instruction}. Checks children classes of - :class:{Parameterizable}. - - Args: - instruction (Instruction): The instruction to check for a - :class:{FreeParameterExpression}. - - Returns: - bool: Whether an object is parameterized. - """ - return issubclass(type(instruction.operator), Parameterizable) and any( - issubclass(type(param), FreeParameterExpression) - for param in instruction.operator.parameters - ) - - def add_circuit( - self, - circuit: Circuit, - target: QubitSetInput | None = None, - target_mapping: dict[QubitInput, QubitInput] | None = None, - ) -> Circuit: - """Add a `Circuit` to `self`, returning `self` for chaining ability. - - Args: - circuit (Circuit): Circuit to add into self. - target (QubitSetInput | None): Target qubits for the - supplied circuit. This is a macro over `target_mapping`; `target` is converted to - a `target_mapping` by zipping together a sorted `circuit.qubits` and `target`. - Default = `None`. - target_mapping (dict[QubitInput, QubitInput] | None): A dictionary of - qubit mappings to apply to the qubits of `circuit.instructions`. Key is the qubit - to map, and the value is what to change it to. Default = `None`. - - Returns: - Circuit: self - - Raises: - TypeError: If both `target_mapping` and `target` are supplied. - - Note: - Supplying `target` sorts `circuit.qubits` to have deterministic behavior since - `circuit.qubits` ordering is based on how instructions are inserted. - Use caution when using this with circuits that with a lot of qubits, as the sort - can be resource-intensive. Use `target_mapping` to use a linear runtime to remap - the qubits. - - Requested result types of the circuit that will be added will be appended to the end - of the list for the existing requested result types. A result type to be added that is - equivalent to an existing requested result type will not be added. - - Examples: - >>> widget = Circuit().h(0).cnot(0, 1) - >>> circ = Circuit().add_circuit(widget) - >>> instructions = list(circ.instructions) - >>> print(instructions[0]) - Instruction('operator': 'H', 'target': QubitSet(Qubit(0),)) - >>> print(instructions[1]) - Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(0), Qubit(1))) - - >>> widget = Circuit().h(0).cnot(0, 1) - >>> circ = Circuit().add_circuit(widget, target_mapping={0: 10, 1: 11}) - >>> instructions = list(circ.instructions) - >>> print(instructions[0]) - Instruction('operator': 'H', 'target': QubitSet(Qubit(10),)) - >>> print(instructions[1]) - Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(10), Qubit(11))) - - >>> widget = Circuit().h(0).cnot(0, 1) - >>> circ = Circuit().add_circuit(widget, target=[10, 11]) - >>> instructions = list(circ.instructions) - >>> print(instructions[0]) - Instruction('operator': 'H', 'target': QubitSet(Qubit(10),)) - >>> print(instructions[1]) - Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(10), Qubit(11))) - """ - 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: - keys = sorted(circuit.qubits) - values = target - target_mapping = dict(zip(keys, values)) - - for instruction in circuit.instructions: - self.add_instruction(instruction, target_mapping=target_mapping) - - for result_type in circuit.result_types: - self.add_result_type(result_type, target_mapping=target_mapping) - - return self - - def add_verbatim_box( - self, - verbatim_circuit: Circuit, - target: QubitSetInput | None = None, - target_mapping: dict[QubitInput, QubitInput] | None = None, - ) -> Circuit: - """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. - target (QubitSetInput | None): Target qubits for the - supplied circuit. This is a macro over `target_mapping`; `target` is converted to - a `target_mapping` by zipping together a sorted `circuit.qubits` and `target`. - Default = `None`. - target_mapping (dict[QubitInput, QubitInput] | None): A dictionary of - qubit mappings to apply to the qubits of `circuit.instructions`. Key is the qubit - to map, and the value is what to change it to. Default = `None`. - - Returns: - Circuit: self - - Raises: - TypeError: If both `target_mapping` and `target` are supplied. - ValueError: If `circuit` has result types attached - - Examples: - >>> widget = Circuit().h(0).h(1) - >>> circ = Circuit().add_verbatim_box(widget) - >>> print(list(circ.instructions)) - [Instruction('operator': StartVerbatimBox, 'target': QubitSet([])), - Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(0)])), - Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(1)])), - Instruction('operator': EndVerbatimBox, 'target': QubitSet([]))] - - >>> widget = Circuit().h(0).cnot(0, 1) - >>> circ = Circuit().add_verbatim_box(widget, target_mapping={0: 10, 1: 11}) - >>> print(list(circ.instructions)) - [Instruction('operator': StartVerbatimBox, 'target': QubitSet([])), - Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(10)])), - Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(11)])), - Instruction('operator': EndVerbatimBox, 'target': QubitSet([]))] - - >>> widget = Circuit().h(0).cnot(0, 1) - >>> circ = Circuit().add_verbatim_box(widget, target=[10, 11]) - >>> print(list(circ.instructions)) - [Instruction('operator': StartVerbatimBox, 'target': QubitSet([])), - Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(10)])), - Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(11)])), - Instruction('operator': EndVerbatimBox, 'target': QubitSet([]))] - """ - 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: - keys = sorted(verbatim_circuit.qubits) - values = target - target_mapping = dict(zip(keys, values)) - - 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: - self.add_instruction(instruction, target_mapping=target_mapping) - self.add_instruction(Instruction(compiler_directives.EndVerbatimBox())) - 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) -> Circuit: - """ - Add a `measure` operator to `self` ensuring only the target qubits are measured. - - Args: - target_qubits (QubitSetInput): target qubits to measure. - - 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': Measure, 'target': QubitSet([Qubit(0)])] - """ - 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.") - - # 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 - - 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, - ) -> Circuit: - """Apply `noise` to the circuit according to `target_gates`, `target_unitary` and - `target_qubits`. - - For any parameter that is None, that specification is ignored (e.g. if `target_gates` - is None then the noise is applied after every gate in `target_qubits`). - If `target_gates` and `target_qubits` are both None, then `noise` is - applied to every qubit after every gate. - - Noise is either applied to `target_gates` or `target_unitary`, so they cannot be - provided at the same time. - - When `noise.qubit_count` == 1, ie. `noise` is single-qubit, `noise` is added to all - qubits in `target_gates` or `target_unitary` (or to all qubits in `target_qubits` - if `target_gates` is None). - - When `noise.qubit_count` > 1 and `target_gates` is not None, the number of qubits of - any gate in `target_gates` must be the same as `noise.qubit_count`. - - When `noise.qubit_count` > 1, `target_gates` and `target_unitary` is None, noise is - only applied to gates with the same qubit_count in target_qubits. - - Args: - noise (Union[type[Noise], Iterable[type[Noise]]]): Noise channel(s) to be applied - to the circuit. - target_gates (Optional[Union[type[Gate], Iterable[type[Gate]]]]): Gate class or - List of Gate classes which `noise` is applied to. Default=None. - target_unitary (Optional[ndarray]): matrix of the target unitary gates. Default=None. - target_qubits (Optional[QubitSetInput]): Index or indices of qubit(s). - Default=None. - - Returns: - Circuit: self - - Raises: - TypeError: - If `noise` is not Noise type. - If `target_gates` is not a Gate type, Iterable[Gate]. - If `target_unitary` is not a np.ndarray type. - If `target_qubits` has non-integers or negative integers. - IndexError: - If applying noise to an empty circuit. - If `target_qubits` is out of range of circuit.qubits. - ValueError: - If both `target_gates` and `target_unitary` are provided. - 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: - 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. - If no `target_gates` or `target_unitary` exist in `target_qubits` or - in the whole circuit when they are not given. - - Examples: - :: - >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1) - >>> print(circ) - T : |0|1|2| - - q0 : -X-Z-C- - | - q1 : -Y-X-X- - - T : |0|1|2| - - >>> noise = Noise.Depolarizing(probability=0.1) - >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1) - >>> print(circ.apply_gate_noise(noise, target_gates = Gate.X)) - T : | 0 | 1 |2| - - q0 : -X-DEPO(0.1)-Z-----------C- - | - q1 : -Y-----------X-DEPO(0.1)-X- - - T : | 0 | 1 |2| - - >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1) - >>> print(circ.apply_gate_noise(noise, target_qubits = 1)) - T : | 0 | 1 | 2 | - - q0 : -X-----------Z-----------C----------- - | - q1 : -Y-DEPO(0.1)-X-DEPO(0.1)-X-DEPO(0.1)- - - T : | 0 | 1 | 2 | - - >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1) - >>> print(circ.apply_gate_noise(noise, - ... target_gates = [Gate.X,Gate.Y], - ... target_qubits = [0,1]) - ... ) - T : | 0 | 1 |2| - - q0 : -X-DEPO(0.1)-Z-----------C- - | - q1 : -Y-DEPO(0.1)-X-DEPO(0.1)-X- - - T : | 0 | 1 |2| - - """ - # check whether gate noise is applied to an empty circuit - if not self.qubits: - raise IndexError("Gate noise cannot be applied to an empty circuit.") - - # check if target_gates and target_unitary are both given - if (target_unitary is not None) and (target_gates is not None): - raise ValueError("target_unitary and target_gates cannot be input at the same time.") - - # check target_qubits - target_qubits = check_noise_target_qubits(self, 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 - self._check_if_qubit_measured(instruction=noise, target=target_qubits) - - # make noise a list - noise = wrap_with_list(noise) - - # make target_gates a list - if target_gates is not None: - target_gates = wrap_with_list(target_gates) - # remove duplicate items - target_gates = list(dict.fromkeys(target_gates)) - - for noise_channel in noise: - if not isinstance(noise_channel, Noise): - raise TypeError("Noise must be an instance of the Noise class") - # check whether target_gates is valid - if target_gates is not None: - check_noise_target_gates(noise_channel, target_gates) - if target_unitary is not None: - check_noise_target_unitary(noise_channel, target_unitary) - - 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) - - def apply_initialization_noise( - self, - noise: Union[type[Noise], Iterable[type[Noise]]], - target_qubits: Optional[QubitSetInput] = None, - ) -> Circuit: - """Apply `noise` at the beginning of the circuit for every qubit (default) or - target_qubits`. - - Only when `target_qubits` is given can the noise be applied to an empty circuit. - - When `noise.qubit_count` > 1, the number of qubits in target_qubits must be equal - to `noise.qubit_count`. - - Args: - noise (Union[type[Noise], Iterable[type[Noise]]]): Noise channel(s) to be applied - to the circuit. - target_qubits (Optional[QubitSetInput]): Index or indices of qubit(s). - Default=None. - - Returns: - Circuit: self - - Raises: - TypeError: - If `noise` is not Noise type. - If `target_qubits` has non-integers or negative integers. - IndexError: - If applying noise to an empty circuit when `target_qubits` is not given. - ValueError: - If `noise.qubit_count` > 1 and the number of qubits in target_qubits is - not the same as `noise.qubit_count`. - - Examples: - >>> 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) - >>> 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() - >>> print(circ.apply_initialization_noise(noise, target_qubits = [0, 1])) - - """ - if (len(self.qubits) == 0) and (target_qubits is None): - raise IndexError( - "target_qubits must be provided in order to" - " apply the initialization noise to an empty circuit." - ) - - target_qubits = check_noise_target_qubits(self, target_qubits) - - # make noise a list - noise = wrap_with_list(noise) - for noise_channel in noise: - if not isinstance(noise_channel, Noise): - raise TypeError("Noise must be an instance of the Noise class") - if noise_channel.qubit_count > 1 and noise_channel.qubit_count != len(target_qubits): - raise ValueError( - "target_qubits needs to be provided for this multi-qubit noise channel," - " and the number of qubits in target_qubits must be the same as defined by" - " the multi-qubit noise channel." - ) - - 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 `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: - param_values (dict[str, Number]): A mapping of FreeParameter names - to a value to assign to them. - strict (bool): If True, raises a ValueError if any of the FreeParameters - in param_values do not appear in the circuit. False by default. - - Returns: - Circuit: Returns a circuit with all present parameters fixed to their respective - values. - """ - if strict: - self._validate_parameters(param_values) - return self._use_parameter_value(param_values) - - def _validate_parameters(self, parameter_values: dict[str, Number]) -> None: - """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 there are no parameters that match the key for the arg - param_values. - """ - 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}") - - def _use_parameter_value(self, param_values: dict[str, Number]) -> Circuit: - """Creates a `Circuit` that uses the parameter values passed in. - - Args: - param_values (dict[str, Number]): A mapping of FreeParameter names - to a value to assign to them. - - Returns: - Circuit: A Circuit with specified parameters swapped for their - values. - - """ - fixed_circ = Circuit() - for val in param_values.values(): - self._validate_parameter_value(val) - for instruction in self.instructions: - if self._check_for_params(instruction): - fixed_circ.add( - Instruction( - instruction.operator.bind_values(**param_values), target=instruction.target - ) - ) - else: - fixed_circ.add(instruction) - fixed_circ.add(self.result_types) - return fixed_circ - - @staticmethod - def _validate_parameter_value(val: Any) -> None: - """Validates the value being used is a `Number`. - - Args: - val (Any): The value be verified. - - Raises: - ValueError: If the value is not a Number - """ - if not isinstance(val, Number): - raise ValueError( - 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, - ) -> Circuit: - """Apply `noise` right before measurement in every qubit (default) or target_qubits`. - - Only when `target_qubits` is given can the noise be applied to an empty circuit. - - When `noise.qubit_count` > 1, the number of qubits in target_qubits must be equal - to `noise.qubit_count`. - - Args: - noise (Union[type[Noise], Iterable[type[Noise]]]): Noise channel(s) to be applied - to the circuit. - target_qubits (Optional[QubitSetInput]): Index or indices of qubit(s). - Default=None. - - Returns: - Circuit: self - - Raises: - TypeError: - If `noise` is not Noise type. - If `target_qubits` has non-integers. - IndexError: - If applying noise to an empty circuit. - ValueError: - If `target_qubits` has negative integers. - If `noise.qubit_count` > 1 and the number of qubits in target_qubits is - not the same as `noise.qubit_count`. - - Examples: - >>> 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) - >>> 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() - >>> print(circ.apply_initialization_noise(noise, target_qubits = [0, 1])) - - """ - if (len(self.qubits) == 0) and (target_qubits is None): - raise IndexError( - "target_qubits must be provided in order to" - " apply the readout noise to an empty circuit." - ) - - if target_qubits is None: - target_qubits = self.qubits - else: - if not isinstance(target_qubits, list): - target_qubits = [target_qubits] - if not all(isinstance(q, int) for q in target_qubits): - raise TypeError("target_qubits must be integer(s)") - if any(q < 0 for q in target_qubits): - raise ValueError("target_qubits must contain only non-negative integers.") - target_qubits = QubitSet(target_qubits) - - # make noise a list - noise = wrap_with_list(noise) - for noise_channel in noise: - if not isinstance(noise_channel, Noise): - raise TypeError("Noise must be an instance of the Noise class") - if noise_channel.qubit_count > 1 and noise_channel.qubit_count != len(target_qubits): - raise ValueError( - "target_qubits needs to be provided for this multi-qubit noise channel," - " and the number of qubits in target_qubits must be the same as defined by" - " the multi-qubit noise channel." - ) - - 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 - `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 - input it allows. - - Args: - addable (AddableTypes): The item(s) to add to self. Default = `None`. - - Returns: - Circuit: self - - Raises: - TypeError: If `addable` is an unsupported type - - See Also: - `add_circuit()` - - `add_instruction()` - - `add_result_type()` - - Examples: - >>> circ = Circuit().add([Instruction(Gate.H(), 4), Instruction(Gate.CNot(), [4, 5])]) - >>> circ = Circuit().add([ResultType.StateVector()]) - - >>> circ = Circuit().h(4).cnot([4, 5]) - - >>> @circuit.subroutine() - >>> def bell_pair(target): - ... return Circuit().h(target[0]).cnot(target[0: 2]) - ... - >>> circ = Circuit().add(bell_pair, [4,5]) - """ - - def _flatten(addable: Union[Iterable, AddableTypes]) -> AddableTypes: - if isinstance(addable, Iterable): - for item in addable: - yield from _flatten(item) - else: - yield addable - - for item in _flatten(addable): - if isinstance(item, Instruction): - self.add_instruction(item, *args, **kwargs) - elif isinstance(item, ResultType): - self.add_result_type(item, *args, **kwargs) - elif isinstance(item, Circuit): - self.add_circuit(item, *args, **kwargs) - elif callable(item): - self.add(item(*args, **kwargs)) - else: - raise TypeError(f"Cannot add a '{type(item)}' to a Circuit") - - return self - - def adjoint(self) -> Circuit: - """Returns the adjoint of this circuit. - - This is the adjoint of every instruction of the circuit, in reverse order. Result types, - and consequently basis rotations will stay in the same order at the end of the circuit. - - Returns: - Circuit: The adjoint of the circuit. - """ - circ = Circuit() - for instr in reversed(self.instructions): - circ.add(instr.adjoint()) - for result_type in self._result_types: - circ.add_result_type(result_type) - return circ - - def diagram(self, circuit_diagram_class: type = UnicodeCircuitDiagram) -> str: - """Get a diagram for the current circuit. - - Args: - circuit_diagram_class (type): A `CircuitDiagram` class that builds the - diagram for this circuit. Default = `AsciiCircuitDiagram`. - - Returns: - str: An ASCII string circuit diagram. - """ - return circuit_diagram_class.build_diagram(self) - - def to_ir( - self, - ir_type: IRType = IRType.JAQCD, - 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. - 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 (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 (dict[tuple[Gate, QubitSet], PulseSequence] | None): The - calibration data for the device. default: None. - - Returns: - Union[OpenQasmProgram, JaqcdProgram]: A representation of the circuit in the - `ir_type` format. - - Raises: - 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: - if serialization_properties and not isinstance( - serialization_properties, OpenQASMSerializationProperties - ): - raise ValueError( - "serialization_properties must be of type OpenQASMSerializationProperties " - "for IRType.OPENQASM." - ) - return self._to_openqasm( - serialization_properties or OpenQASMSerializationProperties(), - gate_definitions.copy(), - ) - else: - 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: - """Converts an OpenQASM program to a Braket Circuit object. - - Args: - source (Union[str, OpenQasmProgram]): OpenQASM string. - inputs (Optional[dict[str, io_type]]): Inputs to the circuit. - - Returns: - Circuit: Braket Circuit implementing the OpenQASM program. - """ - if isinstance(source, OpenQasmProgram): - if inputs: - inputs_copy = source.inputs.copy() if source.inputs is not None else {} - inputs_copy.update(inputs) - inputs = inputs_copy - source = source.source - from braket.circuits.braket_program_context import BraketProgramContext - - return Interpreter(BraketProgramContext()).build_circuit( - source=source, - inputs=inputs, - is_file=False, - ) - - def _to_jaqcd(self) -> JaqcdProgram: - jaqcd_ir_type = IRType.JAQCD - ir_instructions = [instr.to_ir(ir_type=jaqcd_ir_type) for instr in self.instructions] - ir_results = [result_type.to_ir(ir_type=jaqcd_ir_type) for result_type in self.result_types] - ir_basis_rotation_instructions = [ - instr.to_ir(ir_type=jaqcd_ir_type) for instr in self.basis_rotation_instructions - ] - return JaqcdProgram.construct( - instructions=ir_instructions, - results=ir_results, - basis_rotation_instructions=ir_basis_rotation_instructions, - ) - - def _to_openqasm( - self, - serialization_properties: OpenQASMSerializationProperties, - gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], - ) -> 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 - ] - ) - - 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 - ] - ) - # 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 - else self.qubits - ) - for idx, qubit in enumerate(qubits): - qubit_target = serialization_properties.format_target(int(qubit)) - ir_instructions.append(f"b[{idx}] = measure {qubit_target};") - - return OpenQasmProgram.construct(source="\n".join(ir_instructions), inputs={}) - - def _create_openqasm_header( - self, - serialization_properties: OpenQASMSerializationProperties, - 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) - ir_instructions.extend(f"input float {parameter};" for parameter in self.parameters) - if not self.result_types: - 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 - ir_instructions.append(f"qubit[{total_qubits}] q;") - elif serialization_properties.qubit_reference_type != QubitReferenceType.PHYSICAL: - raise ValueError( - f"Invalid qubit_reference_type " - f"{serialization_properties.qubit_reference_type} supplied." - ) - - if frame_wf_declarations: - ir_instructions.append(frame_wf_declarations) - return ir_instructions - - def _validate_gate_calibrations_uniqueness( - self, - gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], - frames: dict[str, Frame], - waveforms: dict[str, Waveform], - ) -> None: - for calibration in gate_definitions.values(): - for frame in calibration._frames.values(): - _validate_uniqueness(frames, frame) - frames[frame.id] = frame - for waveform in calibration._waveforms.values(): - _validate_uniqueness(waveforms, waveform) - waveforms[waveform.id] = waveform - - def _generate_frame_wf_defcal_declarations( - 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) - - 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: - 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) - - 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) - - return None - - 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 - - frames = {} - waveforms = {} - for instruction in self.instructions: - if isinstance(instruction.operator, PulseGate): - for frame in instruction.operator.pulse_sequence._frames.values(): - _validate_uniqueness(frames, frame) - frames[frame.id] = frame - for waveform in instruction.operator.pulse_sequence._waveforms.values(): - _validate_uniqueness(waveforms, waveform) - waveforms[waveform.id] = waveform - # this will change with full parametric calibration support - elif isinstance(instruction.operator, Parameterizable): - fixed_argument_calibrations = self._add_fixed_argument_calibrations( - gate_definitions, instruction - ) - gate_definitions |= fixed_argument_calibrations - return frames, waveforms - - def _add_fixed_argument_calibrations( - self, - gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], - instruction: Instruction, - ) -> dict[tuple[Gate, QubitSet], PulseSequence]: - """Adds calibrations with arguments set to the instruction parameter values - - Given the collection of parameters in instruction.operator, this function looks for matching - parametric calibrations that have free parameters. If such a calibration is found and the - number N of its free parameters equals the number of instruction parameters, we can bind - the arguments of the calibration and add it to the calibration dictionary. - - If N is smaller, it is probably impossible to assign the instruction parameter values to the - corresponding calibration parameters so we raise an error. - If N=0, we ignore it as it will not be removed by _generate_frame_wf_defcal_declarations. - - Args: - gate_definitions (dict[tuple[Gate, QubitSet], PulseSequence]): a dictionary of - calibrations - instruction (Instruction): a Circuit instruction - - Returns: - dict[tuple[Gate, QubitSet], PulseSequence]: additional calibrations - - Raises: - NotImplementedError: in two cases: (i) if the instruction contains unbound parameters - and the calibration dictionary contains a parametric calibration applicable to this - instructions; (ii) if the calibration is defined with a partial number of unbound - parameters. - """ - additional_calibrations = {} - for key, calibration in gate_definitions.items(): - gate = key[0] - target = key[1] - if target != instruction.target: - continue - if isinstance(gate, type(instruction.operator)) and len( - instruction.operator.parameters - ) == len(gate.parameters): - free_parameter_number = sum( - isinstance(p, FreeParameterExpression) for p in gate.parameters - ) - if free_parameter_number == 0: - continue - elif free_parameter_number < len(gate.parameters): - raise NotImplementedError( - "Calibrations with a partial number of fixed parameters are not supported." - ) - elif any( - isinstance(p, FreeParameterExpression) for p in instruction.operator.parameters - ): - raise NotImplementedError( - "Parametric calibrations cannot be attached with parametric circuits." - ) - bound_key = ( - 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) - } - ) - return additional_calibrations - - def to_unitary(self) -> np.ndarray: - """Returns the unitary matrix representation of the entire circuit. - - Note: - The performance of this method degrades with qubit count. It might be slow for - `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)`) - - Raises: - TypeError: If circuit is not composed only of `Gate` instances, - i.e. a circuit with `Noise` operators will raise this error. - - Examples: - >>> circ = Circuit().h(0).cnot(0, 1) - >>> circ.to_unitary() - array([[ 0.70710678+0.j, 0. +0.j, 0.70710678+0.j, - 0. +0.j], - [ 0. +0.j, 0.70710678+0.j, 0. +0.j, - 0.70710678+0.j], - [ 0. +0.j, 0.70710678+0.j, 0. +0.j, - -0.70710678+0.j], - [ 0.70710678+0.j, 0. +0.j, -0.70710678+0.j, - 0. +0.j]]) - """ - if qubits := self.qubits: - return calculate_unitary_big_endian(self.instructions, qubits) - else: - return np.zeros(0, dtype=complex) - - @property - def qubits_frozen(self) -> bool: - """bool: Whether the circuit's qubits are frozen, that is, cannot be remapped. - - This may happen because the circuit contains compiler directives preventing compilation - of a part of the circuit, which consequently means that none of the other qubits can be - rewired either for the program to still make sense. - """ - return self._has_compiler_directives - - @property - def observables_simultaneously_measurable(self) -> bool: - """bool: Whether the circuit's observables are simultaneously measurable - - If this is False, then the circuit can only be run when shots = 0, as sampling (shots > 0) - measures the circuit in the observables' shared eigenbasis. - """ - return self._observables_simultaneously_measurable - - def _encounter_noncommuting_observable(self) -> None: - self._observables_simultaneously_measurable = False - # No longer simultaneously measurable, so no need to track - self._qubit_observable_mapping.clear() - self._qubit_observable_target_mapping.clear() - - def _copy(self) -> Circuit: - copy = Circuit().add(self.instructions) - copy.add(self.result_types) - return copy - - def copy(self) -> Circuit: - """Return a shallow copy of the circuit. - - Returns: - Circuit: A shallow copy of the circuit. - """ - return self._copy() - - def __iadd__(self, addable: AddableTypes) -> Circuit: - return self.add(addable) - - def __add__(self, addable: AddableTypes) -> Circuit: - new = self._copy() - new.add(addable) - return new - - 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})" - ) - - def __str__(self): - return self.diagram() - - 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: 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 = {} - if arg is not None: - for param in self.parameters: - param_values[str(param)] = arg - for key, val in kwargs.items(): - param_values[str(key)] = val - return self.make_bound_circuit(param_values) - - -def subroutine(register: bool = False) -> Callable: - """Subroutine is a function that returns instructions, result types, or circuits. - - Args: - register (bool): If `True`, adds this subroutine into the `Circuit` class. - Default = `False`. - - Returns: - Callable: The subroutine function. - - Examples: - >>> @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),)) - """ - - def _subroutine_function_wrapper(func: Callable[..., SubroutineReturn]) -> SubroutineReturn: - if register: - Circuit.register_subroutine(func) - return func - - return _subroutine_function_wrapper diff --git a/src/braket/circuits/circuit_diagram.py b/src/braket/circuits/circuit_diagram.py deleted file mode 100644 index cc39aa7e..00000000 --- a/src/braket/circuits/circuit_diagram.py +++ /dev/null @@ -1,35 +0,0 @@ -# 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 - -import braket.circuits.circuit as cir - - -class CircuitDiagram(ABC): - """A class that builds circuit diagrams.""" - - @staticmethod - @abstractmethod - def build_diagram(circuit: cir.Circuit) -> str: - """Build a diagram for the specified `circuit`. - - Args: - circuit (cir.Circuit): The circuit to build a diagram for. - - Returns: - str: String representation for the circuit diagram. - An empty string is returned for an empty circuit. - """ diff --git a/src/braket/circuits/circuit_helpers.py b/src/braket/circuits/circuit_helpers.py deleted file mode 100644 index 1a50c4c8..00000000 --- a/src/braket/circuits/circuit_helpers.py +++ /dev/null @@ -1,47 +0,0 @@ -# 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, ResultType - - -def validate_circuit_and_shots(circuit: Circuit, shots: int) -> None: - """Validates if circuit and shots are correct before running on a device - - Args: - circuit (Circuit): circuit to validate - shots (int): shots to validate - - Raises: - 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 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`" - ) - elif 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): - 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 deleted file mode 100644 index ad2c701c..00000000 --- a/src/braket/circuits/compiler_directive.py +++ /dev/null @@ -1,105 +0,0 @@ -# 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 Sequence -from typing import Any - -from braket.circuits.operator import Operator -from braket.circuits.serialization import IRType, SerializationProperties -from braket.registers.qubit_set import QubitSet - - -class CompilerDirective(Operator): - """A directive specifying how the compiler should process a part of the circuit. - - For example, a directive may tell the compiler not to modify some gates in the circuit. - """ - - 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. - """ - if ascii_symbols is None: - raise ValueError("ascii_symbols must not be None") - self._ascii_symbols = tuple(ascii_symbols) - - @property - def name(self) -> str: - return self.__class__.__name__ - - @property - def ascii_symbols(self) -> tuple[str, ...]: - """tuple[str, ...]: Returns the ascii symbols for the compiler directive.""" - return self._ascii_symbols - - def to_ir( - self, - target: QubitSet | None = None, - ir_type: IRType = IRType.JAQCD, - serialization_properties: SerializationProperties | None = None, - **kwargs, - ) -> Any: - """Returns IR object of the compiler directive. - - Args: - target (QubitSet | None): target qubit(s). Defaults to None - ir_type(IRType) : The IRType to use for converting the compiler directive 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 - properties supplied must correspond to the supplied `ir_type`. Defaults to None. - - Returns: - Any: IR object of the compiler directive. - - 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() - else: - raise ValueError(f"Supplied ir_type {ir_type} is not supported.") - - def _to_jaqcd(self) -> Any: - """Returns the JAQCD representation of the compiler directive.""" - raise NotImplementedError("to_jaqcd has not been implemented yet.") - - def _to_openqasm(self) -> str: - """Returns the openqasm string representation of the compiler directive.""" - raise NotImplementedError("to_openqasm has not been implemented yet.") - - def counterpart(self) -> CompilerDirective: - """Returns the "opposite" counterpart to this compiler directive. - - For example, the counterpart of a directive that starts a box - is the directive that ends the box. - - Returns: - CompilerDirective: The counterpart compiler directive - """ - raise NotImplementedError( - f"Compiler directive {self.name} does not have counterpart implemented" - ) - - def __eq__(self, other: CompilerDirective): - return isinstance(other, CompilerDirective) and self.name == other.name - - def __repr__(self): - return self.name diff --git a/src/braket/circuits/compiler_directives.py b/src/braket/circuits/compiler_directives.py deleted file mode 100644 index 9376d338..00000000 --- a/src/braket/circuits/compiler_directives.py +++ /dev/null @@ -1,53 +0,0 @@ -# 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 typing import Any - -import braket.ir.jaqcd as ir -from braket.circuits.compiler_directive import CompilerDirective - - -class StartVerbatimBox(CompilerDirective): - """Prevents the compiler from modifying any ensuing instructions - until the appearance of a corresponding ``EndVerbatimBox``. - """ - - def __init__(self): - super().__init__(["StartVerbatim"]) - - def counterpart(self) -> CompilerDirective: - return EndVerbatimBox() - - def _to_jaqcd(self, *args, **kwargs) -> Any: - return ir.StartVerbatimBox.construct() - - def _to_openqasm(self) -> str: - return "#pragma braket verbatim\nbox{" - - -class EndVerbatimBox(CompilerDirective): - """Marks the end of a portion of code following a StartVerbatimBox that prevents the enclosed - instructions from being modified by the compiler. - """ - - def __init__(self): - super().__init__(["EndVerbatim"]) - - def counterpart(self) -> CompilerDirective: - return StartVerbatimBox() - - def _to_jaqcd(self, *args, **kwargs) -> Any: - return ir.EndVerbatimBox.construct() - - def _to_openqasm(self) -> str: - return "}" diff --git a/src/braket/circuits/free_parameter.py b/src/braket/circuits/free_parameter.py deleted file mode 100644 index 4b9015db..00000000 --- a/src/braket/circuits/free_parameter.py +++ /dev/null @@ -1,14 +0,0 @@ -# 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.parametric.free_parameter import FreeParameter # noqa: F401 diff --git a/src/braket/circuits/free_parameter_expression.py b/src/braket/circuits/free_parameter_expression.py deleted file mode 100644 index 0ba88340..00000000 --- a/src/braket/circuits/free_parameter_expression.py +++ /dev/null @@ -1,14 +0,0 @@ -# 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.parametric.free_parameter_expression import FreeParameterExpression # noqa: F401 diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py deleted file mode 100644 index 453b121f..00000000 --- a/src/braket/circuits/gate.py +++ /dev/null @@ -1,226 +0,0 @@ -# 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 Sequence -from itertools import groupby -from typing import Any, Optional - -from braket.circuits.basis_state import BasisState, BasisStateInput -from braket.circuits.quantum_operator import QuantumOperator -from braket.circuits.serialization import ( - IRType, - OpenQASMSerializationProperties, - SerializationProperties, -) -from braket.registers.qubit_set import QubitSet - - -class Gate(QuantumOperator): - """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 - printing a diagram of circuits. Length must be the same as `qubit_count`, and - index ordering is expected to correlate with target ordering on the instruction. - For instance, if CNOT instruction has the control qubit on the first index and - target qubit on the second index. Then ASCII symbols would have ["C", "X"] to - correlate a symbol with that index. - - Raises: - ValueError: `qubit_count` is less than 1, `ascii_symbols` are `None`, or - `ascii_symbols` length != `qubit_count` - """ - # 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() - - def adjoint(self) -> list[Gate]: - """Returns a list of gates that implement the adjoint of this gate. - - This is a list because some gates do not have an inverse defined by a single existing gate. - - Returns: - list[Gate]: The gates comprising the adjoint of this gate. - """ - raise NotImplementedError(f"Gate {self.name} does not have adjoint implemented") - - def to_ir( - self, - target: QubitSet, - ir_type: IRType = IRType.JAQCD, - serialization_properties: Optional[SerializationProperties] = None, - *, - control: Optional[QubitSet] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Any: - r"""Returns IR object of quantum operator and target - - Args: - target (QubitSet): target qubit(s). - ir_type(IRType) : The IRType to use for converting the gate object to its - IR representation. Defaults to IRType.JAQCD. - serialization_properties (Optional[SerializationProperties]): 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. - control (Optional[QubitSet]): Control qubit(s). Only supported for OpenQASM. - 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: - Any: IR object of the quantum operator and target - - Raises: - ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization - properties don't correspond to the `ir_type`. - ValueError: If gate modifiers are supplied with `ir_type` Jaqcd. - """ - if ir_type == IRType.JAQCD: - 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 serialization_properties and not isinstance( - serialization_properties, OpenQASMSerializationProperties - ): - raise ValueError( - "serialization_properties must be of type OpenQASMSerializationProperties " - "for IRType.OPENQASM." - ) - return self._to_openqasm( - target, - serialization_properties or OpenQASMSerializationProperties(), - control=control, - control_state=control_state, - power=power, - ) - else: - 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. - - Args: - target (QubitSet): target qubit(s). - - Returns: - Any: JAQCD object representing the gate. - """ - raise NotImplementedError("to_jaqcd is not implemented.") - - def _to_openqasm( - self, - target: QubitSet, - serialization_properties: OpenQASMSerializationProperties, - *, - control: Optional[QubitSet] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> str: - """Returns the OpenQASM string representation of the gate. - - Args: - target (QubitSet): target qubit(s). - serialization_properties (OpenQASMSerializationProperties): The serialization properties - to use while serializing the object to the IR representation. - control (Optional[QubitSet]): 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: - str: Representing the openqasm representation of the gate. - """ - target_qubits = [serialization_properties.format_target(int(qubit)) for qubit in target] - if control: - control_qubits = [ - serialization_properties.format_target(int(qubit)) for qubit in control - ] - control_state = (1,) * len(control) if control_state is None else control_state - control_basis_state = BasisState(control_state, len(control)) - control_modifiers = [] - 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})" - ) - ] - control_modifiers.append("") - qubits = control_qubits + target_qubits - control_prefix = " @ ".join(control_modifiers) - else: - qubits = target_qubits - control_prefix = "" - inv_prefix = "inv @ " if power and power < 0 else "" - power_prefix = f"pow({abs_power}) @ " if (abs_power := abs(power)) != 1 else "" - param_string = ( - f"({', '.join(map(str, self.parameters))})" if hasattr(self, "parameters") else "" - ) - - return ( - f"{inv_prefix}{power_prefix}{control_prefix}" - f"{self._qasm_name}{param_string}{','.join([f' {qubit}' for qubit in qubits])};" - ) - - @property - def ascii_symbols(self) -> tuple[str, ...]: - """tuple[str, ...]: Returns the ascii symbols for the quantum operator.""" - return self._ascii_symbols - - def __eq__(self, other: Gate): - return isinstance(other, Gate) and self.name == other.name - - def __repr__(self): - return f"{self.name}('qubit_count': {self._qubit_count})" - - def __hash__(self): - return hash((self.name, self.qubit_count)) - - @classmethod - def register_gate(cls, gate: type[Gate]) -> None: - """Register a gate implementation by adding it into the Gate class. - - Args: - gate (type[Gate]): Gate class to register. - """ - setattr(cls, gate.__name__, gate) diff --git a/src/braket/circuits/gate_calibrations.py b/src/braket/circuits/gate_calibrations.py deleted file mode 100644 index 69ff6625..00000000 --- a/src/braket/circuits/gate_calibrations.py +++ /dev/null @@ -1,164 +0,0 @@ -# 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 copy import deepcopy -from typing import Any - -from braket.circuits.gate import Gate -from braket.circuits.serialization import ( - IRType, - OpenQASMSerializationProperties, - QubitReferenceType, -) -from braket.pulse.pulse_sequence import PulseSequence -from braket.registers.qubit_set import QubitSet - - -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 - - 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. - - """ - 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`. - - Returns: - dict[tuple[Gate, QubitSet], PulseSequence]: The calibration data Dictionary. - """ - return self._pulse_sequences - - @pulse_sequences.setter - def pulse_sequences(self, value: Any) -> None: - """Sets the mapping of (Gate, Qubit) to the corresponding `PulseSequence`. - - Args: - value(Any): The value for the pulse_sequences property to be set to. - - Raises: - TypeError: Raised if the type is not dict[tuple[Gate, QubitSet], PulseSequence] - - """ - if isinstance(value, dict) and all( - isinstance(k[0], Gate) and isinstance(k[1], QubitSet) and isinstance(v, PulseSequence) - for (k, v) in value.items() - ): - self._pulse_sequences = value - else: - raise TypeError( - "The value for pulse_sequence must be of type: " - "dict[tuple[Gate, QubitSet], PulseSequence]" - ) - - def copy(self) -> GateCalibrations: - """Returns a copy of the object. - - Returns: - GateCalibrations: a copy of the calibrations. - """ - return GateCalibrations(deepcopy(self._pulse_sequences)) - - def __len__(self): - return len(self._pulse_sequences) - - def filter( - 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 (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: - GateCalibrations: A filtered GateCalibrations object. - """ - 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 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: 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 - - Raises: - ValueError: Key does not exist in the `GateCalibrations` object. - - 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(): - raise ValueError( - f"The key {calibration_key} does not exist in this GateCalibrations object." - ) - return ( - self.pulse_sequences[calibration_key] - .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 - - 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], - ] - ) - - 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 deleted file mode 100644 index ee5ea684..00000000 --- a/src/braket/circuits/gates.py +++ /dev/null @@ -1,3867 +0,0 @@ -# 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 copy import deepcopy -from typing import Any, Optional, Union - -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, - DoubleAngledGate, - TripleAngledGate, - _get_angles, - _multi_angled_ascii_characters, - angled_ascii_characters, - get_angle, -) -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 -from braket.circuits.instruction import Instruction -from braket.circuits.parameterizable import Parameterizable -from braket.circuits.quantum_operator_helpers import ( - is_unitary, - verify_quantum_operator_matrix_dimensions, -) -from braket.circuits.serialization import OpenQASMSerializationProperties -from braket.pulse.ast.qasm_parser import ast_to_qasm -from braket.pulse.pulse_sequence import PulseSequence -from braket.registers.qubit import QubitInput -from braket.registers.qubit_set import QubitSet, QubitSetInput - -""" -To add a new gate: - 1. Implement the class and extend `Gate` - 2. Add a method with the `@circuit.subroutine(register=True)` decorator. Method name - will be added into the `Circuit` class. This method is the default way - clients add this gate to a circuit. - 3. Register the class with the `Gate` class via `Gate.register_gate()`. -""" - - -# Single qubit gates # - - -class H(Gate): - r"""Hadamard gate. - - Unitary matrix: - - .. math:: \mathtt{H} = \frac{1}{\sqrt{2}} \begin{bmatrix} - 1 & 1 \\ - 1 & -1 \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["H"]) - - @property - def _qasm_name(self) -> str: - return "h" - - def adjoint(self) -> list[Gate]: - return [H()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.H.construct(target=target[0]) - - def to_matrix(self) -> np.ndarray: - return 1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def h( - target: QubitSetInput, - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""Hadamard gate. - - Unitary matrix: - - .. math:: \mathtt{H} = \frac{1}{\sqrt{2}} \begin{bmatrix} - 1 & 1 \\ - 1 & -1 \end{bmatrix}. - - Args: - target (QubitSetInput): Target qubit(s) - 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]: `Iterable` of H instructions. - - Examples: - >>> circ = Circuit().h(0) - >>> circ = Circuit().h([0, 1, 2]) - """ - return [ - Instruction( - H(), target=qubit, control=control, control_state=control_state, power=power - ) - for qubit in QubitSet(target) - ] - - -Gate.register_gate(H) - - -class I(Gate): # noqa: E742 - r"""Identity gate. - - Unitary matrix: - - .. math:: \mathtt{I} = \begin{bmatrix} - 1 & 0 \\ - 0 & 1 \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["I"]) - - @property - def _qasm_name(self) -> str: - return "i" - - def adjoint(self) -> list[Gate]: - return [I()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.I.construct(target=target[0]) - - def to_matrix(self) -> np.ndarray: - return np.eye(2, dtype=complex) - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def i( - target: QubitSetInput, - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""Identity gate. - - Unitary matrix: - - .. math:: \mathtt{I} = \begin{bmatrix} - 1 & 0 \\ - 0 & 1 \end{bmatrix}. - - Args: - target (QubitSetInput): Target qubit(s) - 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]: `Iterable` of I instructions. - - Examples: - >>> circ = Circuit().i(0) - >>> circ = Circuit().i([0, 1, 2]) - """ - return [ - Instruction( - I(), target=qubit, control=control, control_state=control_state, power=power - ) - for qubit in QubitSet(target) - ] - - -Gate.register_gate(I) - - -class GPhase(AngledGate): - r"""Global phase gate. - - Unitary matrix: - - .. math:: \mathtt{gphase}(\gamma) = e^{i \gamma} I_1 = \begin{bmatrix} - e^{i \gamma} \end{bmatrix}. - - Args: - angle (Union[FreeParameterExpression, float]): angle in radians. - - Raises: - ValueError: If `angle` is not present - """ - - 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 = \begin{bmatrix} - e^{i \gamma} \end{bmatrix}. - - 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. - - Unitary matrix: - - .. math:: \mathtt{X} = \begin{bmatrix} - 0 & 1 \\ - 1 & 0 - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["X"]) - - @property - def _qasm_name(self) -> str: - return "x" - - def adjoint(self) -> list[Gate]: - return [X()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.X.construct(target=target[0]) - - def to_matrix(self) -> np.ndarray: - return np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def x( - target: QubitSetInput, - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""Pauli-X gate. - - Unitary matrix: - - .. math:: \mathtt{X} = \begin{bmatrix} - 0 & 1 \\ - 1 & 0 - \end{bmatrix}. - - Args: - target (QubitSetInput): Target qubit(s) - 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]: `Iterable` of X instructions. - - Examples: - >>> circ = Circuit().x(0) - >>> circ = Circuit().x([0, 1, 2]) - """ - return [ - Instruction( - X(), target=qubit, control=control, control_state=control_state, power=power - ) - for qubit in QubitSet(target) - ] - - -Gate.register_gate(X) - - -class Y(Gate): - r"""Pauli-Y gate. - - Unitary matrix: - - .. math:: \mathtt{Y} = \begin{bmatrix} - 0 & -i \\ - i & 0 - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["Y"]) - - @property - def _qasm_name(self) -> str: - return "y" - - def adjoint(self) -> list[Gate]: - return [Y()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.Y.construct(target=target[0]) - - def to_matrix(self) -> np.ndarray: - return np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def y( - target: QubitSetInput, - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""Pauli-Y gate. - - Unitary matrix: - - .. math:: \mathtt{Y} = \begin{bmatrix} - 0 & -i \\ - i & 0 - \end{bmatrix}. - - Args: - target (QubitSetInput): Target qubit(s) - 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]: `Iterable` of Y instructions. - - Examples: - >>> circ = Circuit().y(0) - >>> circ = Circuit().y([0, 1, 2]) - """ - return [ - Instruction( - Y(), target=qubit, control=control, control_state=control_state, power=power - ) - for qubit in QubitSet(target) - ] - - -Gate.register_gate(Y) - - -class Z(Gate): - r"""Pauli-Z gate. - - Unitary matrix: - - .. math:: \mathtt{Z} = \begin{bmatrix} - 1 & 0 \\ - 0 & -1 - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["Z"]) - - @property - def _qasm_name(self) -> str: - return "z" - - def adjoint(self) -> list[Gate]: - return [Z()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.Z.construct(target=target[0]) - - def to_matrix(self) -> np.ndarray: - return np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def z( - target: QubitSetInput, - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""Pauli-Z gate. - - .. math:: \mathtt{Z} = \begin{bmatrix} - 1 & 0 \\ - 0 & -1 - \end{bmatrix}. - - Args: - target (QubitSetInput): Target qubit(s) - 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]: `Iterable` of Z instructions. - - Examples: - >>> circ = Circuit().z(0) - >>> circ = Circuit().z([0, 1, 2]) - """ - return [ - Instruction( - Z(), target=qubit, control=control, control_state=control_state, power=power - ) - for qubit in QubitSet(target) - ] - - -Gate.register_gate(Z) - - -class S(Gate): - r"""S gate. - - Unitary matrix: - - .. math:: \mathtt{S} = \begin{bmatrix} - 1 & 0 \\ - 0 & i - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["S"]) - - @property - def _qasm_name(self) -> str: - return "s" - - def adjoint(self) -> list[Gate]: - return [Si()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.S.construct(target=target[0]) - - def to_matrix(self) -> np.ndarray: - return np.array([[1.0, 0.0], [0.0, 1.0j]], dtype=complex) - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def s( - target: QubitSetInput, - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""S gate. - - .. math:: \mathtt{S} = \begin{bmatrix} - 1 & 0 \\ - 0 & i - \end{bmatrix}. - - Args: - target (QubitSetInput): Target qubit(s) - 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]: `Iterable` of S instructions. - - Examples: - >>> circ = Circuit().s(0) - >>> circ = Circuit().s([0, 1, 2]) - """ - return [ - Instruction( - S(), target=qubit, control=control, control_state=control_state, power=power - ) - for qubit in QubitSet(target) - ] - - -Gate.register_gate(S) - - -class Si(Gate): - r"""Conjugate transpose of S gate. - - Unitary matrix: - - .. math:: \mathtt{S}^\dagger = \begin{bmatrix} - 1 & 0 \\ - 0 & -i - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["Si"]) - - @property - def _qasm_name(self) -> str: - return "si" - - def adjoint(self) -> list[Gate]: - return [S()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.Si.construct(target=target[0]) - - def to_matrix(self) -> np.ndarray: - return np.array([[1, 0], [0, -1j]], dtype=complex) - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def si( - target: QubitSetInput, - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""Conjugate transpose of S gate. - - .. math:: \mathtt{S}^\dagger = \begin{bmatrix} - 1 & 0 \\ - 0 & -i - \end{bmatrix}. - - Args: - target (QubitSetInput): Target qubit(s) - 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]: Iterable of Si instructions. - - Examples: - >>> circ = Circuit().si(0) - >>> circ = Circuit().si([0, 1, 2]) - """ - return [ - Instruction( - Si(), target=qubit, control=control, control_state=control_state, power=power - ) - for qubit in QubitSet(target) - ] - - -Gate.register_gate(Si) - - -class T(Gate): - r"""T gate. - - Unitary matrix: - - .. math:: \mathtt{T} = \begin{bmatrix} - 1 & 0 \\ - 0 & e^{i \pi/4} - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["T"]) - - @property - def _qasm_name(self) -> str: - return "t" - - def adjoint(self) -> list[Gate]: - return [Ti()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.T.construct(target=target[0]) - - def to_matrix(self) -> np.ndarray: - return np.array([[1.0, 0.0], [0.0, np.exp(1j * np.pi / 4)]], dtype=complex) - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def t( - target: QubitSetInput, - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""T gate. - - .. math:: \mathtt{T} = \begin{bmatrix} - 1 & 0 \\ - 0 & e^{i \pi/4} - \end{bmatrix}. - - Args: - target (QubitSetInput): Target qubit(s) - 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]: `Iterable` of T instructions. - - Examples: - >>> circ = Circuit().t(0) - >>> circ = Circuit().t([0, 1, 2]) - """ - return [ - Instruction( - T(), target=qubit, control=control, control_state=control_state, power=power - ) - for qubit in QubitSet(target) - ] - - -Gate.register_gate(T) - - -class Ti(Gate): - r"""Conjugate transpose of T gate. - - Unitary matrix: - - .. math:: \mathtt{T}^\dagger = \begin{bmatrix} - 1 & 0 \\ - 0 & e^{-i \pi/4} - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["Ti"]) - - @property - def _qasm_name(self) -> str: - return "ti" - - def adjoint(self) -> list[Gate]: - return [T()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.Ti.construct(target=target[0]) - - def to_matrix(self) -> np.ndarray: - return np.array([[1.0, 0.0], [0.0, np.exp(-1j * np.pi / 4)]], dtype=complex) - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def ti( - target: QubitSetInput, - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""Conjugate transpose of T gate. - - .. math:: \mathtt{T}^\dagger = \begin{bmatrix} - 1 & 0 \\ - 0 & e^{-i \pi/4} - \end{bmatrix}. - - Args: - target (QubitSetInput): Target qubit(s) - 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]: `Iterable` of Ti instructions. - - Examples: - >>> circ = Circuit().ti(0) - >>> circ = Circuit().ti([0, 1, 2]) - """ - return [ - Instruction( - Ti(), target=qubit, control=control, control_state=control_state, power=power - ) - for qubit in QubitSet(target) - ] - - -Gate.register_gate(Ti) - - -class V(Gate): - r"""Square root of X gate (V gate). - - Unitary matrix: - - .. math:: \mathtt{V} = \frac{1}{2}\begin{bmatrix} - 1+i & 1-i \\ - 1-i & 1+i - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["V"]) - - @property - def _qasm_name(self) -> str: - return "v" - - def adjoint(self) -> list[Gate]: - return [Vi()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.V.construct(target=target[0]) - - def to_matrix(self) -> np.ndarray: - return np.array([[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]], dtype=complex) - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def v( - target: QubitSetInput, - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""Square root of X gate (V gate). - - .. math:: \mathtt{V} = \frac{1}{2}\begin{bmatrix} - 1+i & 1-i \\ - 1-i & 1+i - \end{bmatrix}. - - Args: - target (QubitSetInput): Target qubit(s) - 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]: `Iterable` of V instructions. - - Examples: - >>> circ = Circuit().v(0) - >>> circ = Circuit().v([0, 1, 2]) - """ - return [ - Instruction( - V(), target=qubit, control=control, control_state=control_state, power=power - ) - for qubit in QubitSet(target) - ] - - -Gate.register_gate(V) - - -class Vi(Gate): - r"""Conjugate transpose of square root of X gate (conjugate transpose of V). - - Unitary matrix: - - .. math:: \mathtt{V}^\dagger = \frac{1}{2}\begin{bmatrix} - 1-i & 1+i \\ - 1+i & 1-i - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["Vi"]) - - @property - def _qasm_name(self) -> str: - return "vi" - - def adjoint(self) -> list[Gate]: - return [V()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.Vi.construct(target=target[0]) - - def to_matrix(self) -> np.ndarray: - return np.array(([[0.5 - 0.5j, 0.5 + 0.5j], [0.5 + 0.5j, 0.5 - 0.5j]]), dtype=complex) - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def vi( - target: QubitSetInput, - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""Conjugate transpose of square root of X gate (conjugate transpose of V). - - .. math:: \mathtt{V}^\dagger = \frac{1}{2}\begin{bmatrix} - 1-i & 1+i \\ - 1+i & 1-i - \end{bmatrix}. - - Args: - target (QubitSetInput): Target qubit(s) - 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]: `Iterable` of Vi instructions. - - Examples: - >>> circ = Circuit().vi(0) - >>> circ = Circuit().vi([0, 1, 2]) - """ - return [ - Instruction( - Vi(), target=qubit, control=control, control_state=control_state, power=power - ) - for qubit in QubitSet(target) - ] - - -Gate.register_gate(Vi) - - -# Single qubit gates with rotation # - - -class Rx(AngledGate): - r"""X-axis rotation gate. - - Unitary matrix: - - .. math:: \mathtt{R_x}(\phi) = \begin{bmatrix} - \cos{(\phi/2)} & -i \sin{(\phi/2)} \\ - -i \sin{(\phi/2)} & \cos{(\phi/2)} - \end{bmatrix}. - - Args: - angle (Union[FreeParameterExpression, float]): angle in radians. - """ - - def __init__(self, angle: Union[FreeParameterExpression, float]): - super().__init__( - angle=angle, - qubit_count=None, - ascii_symbols=[angled_ascii_characters("Rx", angle)], - ) - - @property - def _qasm_name(self) -> str: - return "rx" - - def _to_jaqcd(self, target: QubitSet, **kwargs) -> Any: - return ir.Rx.construct(target=target[0], angle=self.angle) - - def to_matrix(self) -> np.ndarray: - r"""Returns a matrix representation of this gate. - - Returns: - np.ndarray: The matrix representation of this gate. - """ - cos = np.cos(self.angle / 2) - sin = np.sin(self.angle / 2) - return np.array([[cos, -1j * sin], [-1j * sin, cos]], dtype=complex) - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - def bind_values(self, **kwargs) -> AngledGate: - return get_angle(self, **kwargs) - - @staticmethod - @circuit.subroutine(register=True) - def rx( - target: QubitSetInput, - angle: Union[FreeParameterExpression, float], - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""X-axis rotation gate. - - .. math:: \mathtt{R_x}(\phi) = \begin{bmatrix} - \cos{(\phi/2)} & -i \sin{(\phi/2)} \\ - -i \sin{(\phi/2)} & \cos{(\phi/2)} - \end{bmatrix}. - - Args: - target (QubitSetInput): Target qubit(s). - angle (Union[FreeParameterExpression, float]): 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]: Rx instruction. - - Examples: - >>> circ = Circuit().rx(0, 0.15) - """ - return [ - Instruction( - Rx(angle), target=qubit, control=control, control_state=control_state, power=power - ) - for qubit in QubitSet(target) - ] - - -Gate.register_gate(Rx) - - -class Ry(AngledGate): - r"""Y-axis rotation gate. - - Unitary matrix: - - .. math:: \mathtt{R_y}(\phi) = \begin{bmatrix} - \cos{(\phi/2)} & -\sin{(\phi/2)} \\ - \sin{(\phi/2)} & \cos{(\phi/2)} - \end{bmatrix}. - - Args: - angle (Union[FreeParameterExpression, float]): angle in radians. - """ - - def __init__(self, angle: Union[FreeParameterExpression, float]): - super().__init__( - angle=angle, - qubit_count=None, - ascii_symbols=[angled_ascii_characters("Ry", angle)], - ) - - @property - def _qasm_name(self) -> str: - return "ry" - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.Ry.construct(target=target[0], angle=self.angle) - - def to_matrix(self) -> np.ndarray: - r"""Returns a matrix representation of this gate. - - Returns: - np.ndarray: The matrix representation of this gate. - """ - cos = np.cos(self.angle / 2) - sin = np.sin(self.angle / 2) - return np.array([[cos, -sin], [+sin, cos]], dtype=complex) - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - def bind_values(self, **kwargs) -> AngledGate: - return get_angle(self, **kwargs) - - @staticmethod - @circuit.subroutine(register=True) - def ry( - target: QubitSetInput, - angle: Union[FreeParameterExpression, float], - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""Y-axis rotation gate. - - .. math:: \mathtt{R_y}(\phi) = \begin{bmatrix} - \cos{(\phi/2)} & -\sin{(\phi/2)} \\ - \sin{(\phi/2)} & \cos{(\phi/2)} - \end{bmatrix}. - - Args: - target (QubitSetInput): Target qubit(s). - angle (Union[FreeParameterExpression, float]): 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]: Rx instruction. - - - Examples: - >>> circ = Circuit().ry(0, 0.15) - """ - return [ - Instruction( - Ry(angle), target=qubit, control=control, control_state=control_state, power=power - ) - for qubit in QubitSet(target) - ] - - -Gate.register_gate(Ry) - - -class Rz(AngledGate): - r"""Z-axis rotation gate. - - Unitary matrix: - - .. math:: \mathtt{R_z}(\phi) = \begin{bmatrix} - e^{-i \phi/2} & 0 \\ - 0 & e^{i \phi/2} - \end{bmatrix}. - - Args: - angle (Union[FreeParameterExpression, float]): angle in radians. - """ - - def __init__(self, angle: Union[FreeParameterExpression, float]): - super().__init__( - angle=angle, - qubit_count=None, - ascii_symbols=[angled_ascii_characters("Rz", angle)], - ) - - @property - def _qasm_name(self) -> str: - return "rz" - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.Rz.construct(target=target[0], angle=self.angle) - - def to_matrix(self) -> np.ndarray: - return np.array( - [[np.exp(-1j * self.angle / 2), 0], [0, np.exp(1j * self.angle / 2)]], dtype=complex - ) - - def bind_values(self, **kwargs) -> AngledGate: - return get_angle(self, **kwargs) - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def rz( - target: QubitSetInput, - angle: Union[FreeParameterExpression, float], - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""Z-axis rotation gate. - - .. math:: \mathtt{R_z}(\phi) = \begin{bmatrix} - e^{-i \phi/2} & 0 \\ - 0 & e^{i \phi/2} - \end{bmatrix}. - - Args: - target (QubitSetInput): Target qubit(s). - angle (Union[FreeParameterExpression, float]): 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]: Rx instruction. - - Examples: - >>> circ = Circuit().rz(0, 0.15) - """ - return [ - Instruction( - Rz(angle), target=qubit, control=control, control_state=control_state, power=power - ) - for qubit in QubitSet(target) - ] - - -Gate.register_gate(Rz) - - -class PhaseShift(AngledGate): - r"""Phase shift gate. - - Unitary matrix: - - .. math:: \mathtt{PhaseShift}(\phi) = \begin{bmatrix} - 1 & 0 \\ - 0 & e^{i \phi} - \end{bmatrix} - - Args: - angle (Union[FreeParameterExpression, float]): angle in radians. - """ - - def __init__(self, angle: Union[FreeParameterExpression, float]): - super().__init__( - angle=angle, - qubit_count=None, - ascii_symbols=[angled_ascii_characters("PHASE", angle)], - ) - - @property - def _qasm_name(self) -> str: - return "phaseshift" - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.PhaseShift.construct(target=target[0], angle=self.angle) - - def to_matrix(self) -> np.ndarray: - return np.array([[1.0, 0.0], [0.0, np.exp(1j * self.angle)]], dtype=complex) - - def bind_values(self, **kwargs) -> AngledGate: - return get_angle(self, **kwargs) - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def phaseshift( - target: QubitSetInput, - angle: Union[FreeParameterExpression, float], - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""Phase shift gate. - - .. math:: \mathtt{PhaseShift}(\phi) = \begin{bmatrix} - 1 & 0 \\ - 0 & e^{i \phi} - \end{bmatrix} - - Args: - target (QubitSetInput): Target qubit(s). - angle (Union[FreeParameterExpression, float]): 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]: PhaseShift instruction. - - Examples: - >>> circ = Circuit().phaseshift(0, 0.15) - """ - return [ - Instruction( - PhaseShift(angle), - target=qubit, - control=control, - control_state=control_state, - power=power, - ) - for qubit in QubitSet(target) - ] - - -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: - np.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 # - - -class CNot(Gate): - r"""Controlled NOT gate. - - Unitary matrix: - - .. math:: \mathtt{CNOT} = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & 1 \\ - 0 & 0 & 1 & 0 \\ - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["C", "X"]) - - @property - def _qasm_name(self) -> str: - return "cnot" - - def adjoint(self) -> list[Gate]: - return [CNot()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.CNot.construct(control=target[0], target=target[1]) - - def to_matrix(self) -> np.ndarray: - return np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0], - ], - dtype=complex, - ) - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def cnot(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruction: - r"""Controlled NOT gate. - - .. math:: \mathtt{CNOT} = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & 1 \\ - 0 & 0 & 1 & 0 \\ - \end{bmatrix}. - - Args: - control (QubitSetInput): Control qubit(s). The last control qubit - is absorbed into the target of the instruction. - target (QubitInput): Target qubit index. - 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: CNot instruction. - - Examples: - >>> circ = Circuit().cnot(0, 1) - """ - control_qubits = QubitSet(control) - absorbed_control = control_qubits.pop() - return Instruction( - CNot(), target=[absorbed_control, target], control=control_qubits, power=power - ) - - -Gate.register_gate(CNot) - - -class Swap(Gate): - r"""Swap gate. - - Unitary matrix: - - .. math:: \mathtt{SWAP} = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 0 & 1 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & 1 \\ - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["SWAP", "SWAP"]) - - @property - def _qasm_name(self) -> str: - return "swap" - - def adjoint(self) -> list[Gate]: - return [Swap()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.Swap.construct(targets=[target[0], target[1]]) - - def to_matrix(self) -> np.ndarray: - return np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - ], - dtype=complex, - ) - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def swap( - target1: QubitInput, - target2: QubitInput, - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Instruction: - r"""Swap gate. - - .. math:: \mathtt{SWAP} = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 0 & 1 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & 1 \\ - \end{bmatrix}. - - Args: - target1 (QubitInput): Target qubit 1 index. - target2 (QubitInput): Target qubit 2 index. - 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: Swap instruction. - - Examples: - >>> circ = Circuit().swap(0, 1) - """ - return Instruction( - Swap(), - target=[target1, target2], - control=control, - control_state=control_state, - power=power, - ) - - -Gate.register_gate(Swap) - - -class ISwap(Gate): - r"""ISwap gate. - - Unitary matrix: - - .. math:: \mathtt{iSWAP} = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 0 & i & 0 \\ - 0 & i & 0 & 0 \\ - 0 & 0 & 0 & 1 \\ - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["ISWAP", "ISWAP"]) - - @property - def _qasm_name(self) -> str: - return "iswap" - - def adjoint(self) -> list[Gate]: - return [self, self, self] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.ISwap.construct(targets=[target[0], target[1]]) - - def to_matrix(self) -> np.ndarray: - return np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 1.0j, 0.0], - [0.0, 1.0j, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - ], - dtype=complex, - ) - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def iswap( - target1: QubitInput, - target2: QubitInput, - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Instruction: - r"""ISwap gate. - - .. math:: \mathtt{iSWAP} = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 0 & i & 0 \\ - 0 & i & 0 & 0 \\ - 0 & 0 & 0 & 1 \\ - \end{bmatrix}. - - Args: - target1 (QubitInput): Target qubit 1 index. - target2 (QubitInput): Target qubit 2 index. - 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: ISwap instruction. - - Examples: - >>> circ = Circuit().iswap(0, 1) - """ - return Instruction( - ISwap(), - target=[target1, target2], - control=control, - control_state=control_state, - power=power, - ) - - -Gate.register_gate(ISwap) - - -class PSwap(AngledGate): - r"""PSwap gate. - - Unitary matrix: - - .. math:: \mathtt{PSWAP}(\phi) = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 0 & e^{i \phi} & 0 \\ - 0 & e^{i \phi} & 0 & 0 \\ - 0 & 0 & 0 & 1 \\ - \end{bmatrix}. - - Args: - angle (Union[FreeParameterExpression, float]): angle in radians. - """ - - def __init__(self, angle: Union[FreeParameterExpression, float]): - super().__init__( - angle=angle, - qubit_count=None, - ascii_symbols=[ - angled_ascii_characters("PSWAP", angle), - angled_ascii_characters("PSWAP", angle), - ], - ) - - @property - def _qasm_name(self) -> str: - return "pswap" - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.PSwap.construct(targets=[target[0], target[1]], angle=self.angle) - - def to_matrix(self) -> np.ndarray: - return np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, np.exp(1j * self.angle), 0.0], - [0.0, np.exp(1j * self.angle), 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - ], - dtype=complex, - ) - - def bind_values(self, **kwargs) -> AngledGate: - return get_angle(self, **kwargs) - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def pswap( - target1: QubitInput, - target2: QubitInput, - angle: Union[FreeParameterExpression, float], - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Instruction: - r"""PSwap gate. - - .. math:: \mathtt{PSWAP}(\phi) = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 0 & e^{i \phi} & 0 \\ - 0 & e^{i \phi} & 0 & 0 \\ - 0 & 0 & 0 & 1 \\ - \end{bmatrix}. - - Args: - target1 (QubitInput): Target qubit 1 index. - target2 (QubitInput): Target qubit 2 index. - angle (Union[FreeParameterExpression, float]): 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: - Instruction: PSwap instruction. - - Examples: - >>> circ = Circuit().pswap(0, 1, 0.15) - """ - return Instruction( - PSwap(angle), - target=[target1, target2], - control=control, - control_state=control_state, - power=power, - ) - - -Gate.register_gate(PSwap) - - -class XY(AngledGate): - r"""XY gate. - - Unitary matrix: - - .. math:: \mathtt{XY}(\phi) = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & \cos{(\phi/2)} & i\sin{(\phi/2)} & 0 \\ - 0 & i\sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\ - 0 & 0 & 0 & 1 \\ - \end{bmatrix}. - - Reference: https://arxiv.org/abs/1912.04424v1 - - - Args: - angle (Union[FreeParameterExpression, float]): angle in radians. - """ - - def __init__(self, angle: Union[FreeParameterExpression, float]): - super().__init__( - angle=angle, - qubit_count=None, - ascii_symbols=[ - angled_ascii_characters("XY", angle), - angled_ascii_characters("XY", angle), - ], - ) - - @property - def _qasm_name(self) -> str: - return "xy" - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.XY.construct(targets=[target[0], target[1]], angle=self.angle) - - def to_matrix(self) -> np.ndarray: - r"""Returns a matrix representation of this gate. - - Returns: - np.ndarray: The matrix representation of this gate. - """ - cos = np.cos(self.angle / 2) - sin = np.sin(self.angle / 2) - return np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, cos, 1.0j * sin, 0.0], - [0.0, 1.0j * sin, cos, 0.0], - [0.0, 0.0, 0.0, 1.0], - ], - dtype=complex, - ) - - def bind_values(self, **kwargs) -> AngledGate: - return get_angle(self, **kwargs) - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def xy( - target1: QubitInput, - target2: QubitInput, - angle: Union[FreeParameterExpression, float], - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Instruction: - r"""XY gate. - - .. math:: \mathtt{XY}(\phi) = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & \cos{(\phi/2)} & i\sin{(\phi/2)} & 0 \\ - 0 & i\sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\ - 0 & 0 & 0 & 1 \\ - \end{bmatrix}. - - Args: - target1 (QubitInput): Target qubit 1 index. - target2 (QubitInput): Target qubit 2 index. - angle (Union[FreeParameterExpression, float]): 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: - Instruction: XY instruction. - - Examples: - >>> circ = Circuit().xy(0, 1, 0.15) - """ - return Instruction( - XY(angle), - target=[target1, target2], - control=control, - control_state=control_state, - power=power, - ) - - -Gate.register_gate(XY) - - -class CPhaseShift(AngledGate): - r"""Controlled phase shift gate. - - Unitary matrix: - - .. math:: \mathtt{CPhaseShift}(\phi) = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & e^{i \phi} - \end{bmatrix}. - - Args: - angle (Union[FreeParameterExpression, float]): angle in radians. - """ - - def __init__(self, angle: Union[FreeParameterExpression, float]): - super().__init__( - angle=angle, - qubit_count=None, - ascii_symbols=["C", angled_ascii_characters("PHASE", angle)], - ) - - @property - def _qasm_name(self) -> str: - return "cphaseshift" - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.CPhaseShift.construct(control=target[0], target=target[1], angle=self.angle) - - def to_matrix(self) -> np.ndarray: - return np.diag([1.0, 1.0, 1.0, np.exp(1j * self.angle)]) - - def bind_values(self, **kwargs) -> AngledGate: - return get_angle(self, **kwargs) - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def cphaseshift( - control: QubitSetInput, - target: QubitInput, - angle: Union[FreeParameterExpression, float], - power: float = 1, - ) -> Instruction: - r"""Controlled phase shift gate. - - .. math:: \mathtt{CPhaseShift}(\phi) = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & e^{i \phi} - \end{bmatrix}. - - Args: - control (QubitSetInput): Control qubit(s). The last control qubit - is absorbed into the target of the instruction. - target (QubitInput): Target qubit index. - angle (Union[FreeParameterExpression, float]): angle in radians. - 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: CPhaseShift instruction. - - Examples: - >>> circ = Circuit().cphaseshift(0, 1, 0.15) - """ - control_qubits = QubitSet(control) - absorbed_control = control_qubits.pop() - return Instruction( - CPhaseShift(angle), - target=[absorbed_control, target], - control=control_qubits, - power=power, - ) - - -Gate.register_gate(CPhaseShift) - - -class CPhaseShift00(AngledGate): - r"""Controlled phase shift gate for phasing the \|00> state. - - Unitary matrix: - - .. math:: \mathtt{CPhaseShift00}(\phi) = \begin{bmatrix} - e^{i \phi} & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & 1 - \end{bmatrix}. - - Args: - angle (Union[FreeParameterExpression, float]): angle in radians. - """ - - def __init__(self, angle: Union[FreeParameterExpression, float]): - super().__init__( - angle=angle, - qubit_count=None, - ascii_symbols=["C", angled_ascii_characters("PHASE00", angle)], - ) - - @property - def _qasm_name(self) -> str: - return "cphaseshift00" - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.CPhaseShift00.construct(control=target[0], target=target[1], angle=self.angle) - - def to_matrix(self) -> np.ndarray: - return np.diag([np.exp(1j * self.angle), 1.0, 1.0, 1.0]) - - def bind_values(self, **kwargs) -> AngledGate: - return get_angle(self, **kwargs) - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def cphaseshift00( - control: QubitSetInput, - target: QubitInput, - angle: Union[FreeParameterExpression, float], - power: float = 1, - ) -> Instruction: - r"""Controlled phase shift gate for phasing the \|00> state. - - .. math:: \mathtt{CPhaseShift00}(\phi) = \begin{bmatrix} - e^{i \phi} & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & 1 - \end{bmatrix}. - - Args: - control (QubitSetInput): Control qubit(s). The last control qubit - is absorbed into the target of the instruction. - target (QubitInput): Target qubit index. - angle (Union[FreeParameterExpression, float]): angle in radians. - 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: CPhaseShift00 instruction. - - Examples: - >>> circ = Circuit().cphaseshift00(0, 1, 0.15) - """ - control_qubits = QubitSet(control) - absorbed_control = control_qubits.pop() - return Instruction( - CPhaseShift00(angle), - target=[absorbed_control, target], - control=control_qubits, - power=power, - ) - - -Gate.register_gate(CPhaseShift00) - - -class CPhaseShift01(AngledGate): - r"""Controlled phase shift gate for phasing the \|01> state. - - Unitary matrix: - - .. math:: \mathtt{CPhaseShift01}(\phi) = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & e^{i \phi} & 0 & 0 \\ - 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & 1 - \end{bmatrix}. - - Args: - angle (Union[FreeParameterExpression, float]): angle in radians. - """ - - def __init__(self, angle: Union[FreeParameterExpression, float]): - super().__init__( - angle=angle, - qubit_count=None, - ascii_symbols=["C", angled_ascii_characters("PHASE01", angle)], - ) - - @property - def _qasm_name(self) -> str: - return "cphaseshift01" - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.CPhaseShift01.construct(control=target[0], target=target[1], angle=self.angle) - - def to_matrix(self) -> np.ndarray: - return np.diag([1.0, np.exp(1j * self.angle), 1.0, 1.0]) - - def bind_values(self, **kwargs) -> AngledGate: - return get_angle(self, **kwargs) - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def cphaseshift01( - control: QubitSetInput, - target: QubitInput, - angle: Union[FreeParameterExpression, float], - power: float = 1, - ) -> Instruction: - r"""Controlled phase shift gate for phasing the \|01> state. - - .. math:: \mathtt{CPhaseShift01}(\phi) = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & e^{i \phi} & 0 & 0 \\ - 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & 1 - \end{bmatrix}. - - Args: - control (QubitSetInput): Control qubit(s). The last control qubit - is absorbed into the target of the instruction. - target (QubitInput): Target qubit index. - angle (Union[FreeParameterExpression, float]): angle in radians. - 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: CPhaseShift01 instruction. - - Examples: - >>> circ = Circuit().cphaseshift01(0, 1, 0.15) - """ - control_qubits = QubitSet(control) - absorbed_control = control_qubits.pop() - return Instruction( - CPhaseShift01(angle), - target=[absorbed_control, target], - control=control_qubits, - power=power, - ) - - -Gate.register_gate(CPhaseShift01) - - -class CPhaseShift10(AngledGate): - r"""Controlled phase shift gate for phasing the \\|10> state. - - Unitary matrix: - - .. math:: \mathtt{CPhaseShift10}(\phi) = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & e^{i \phi} & 0 \\ - 0 & 0 & 0 & 1 - \end{bmatrix}. - - Args: - angle (Union[FreeParameterExpression, float]): angle in radians. - """ - - def __init__(self, angle: Union[FreeParameterExpression, float]): - super().__init__( - angle=angle, - qubit_count=None, - ascii_symbols=["C", angled_ascii_characters("PHASE10", angle)], - ) - - @property - def _qasm_name(self) -> str: - return "cphaseshift10" - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.CPhaseShift10.construct(control=target[0], target=target[1], angle=self.angle) - - def to_matrix(self) -> np.ndarray: - return np.diag([1.0, 1.0, np.exp(1j * self.angle), 1.0]) - - def bind_values(self, **kwargs) -> AngledGate: - return get_angle(self, **kwargs) - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def cphaseshift10( - control: QubitSetInput, - target: QubitInput, - angle: Union[FreeParameterExpression, float], - power: float = 1, - ) -> Instruction: - r"""Controlled phase shift gate for phasing the \\|10> state. - - .. math:: \mathtt{CPhaseShift10}(\phi) = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & e^{i \phi} & 0 \\ - 0 & 0 & 0 & 1 - \end{bmatrix}. - - Args: - control (QubitSetInput): Control qubit(s). The last control qubit - is absorbed into the target of the instruction. - target (QubitInput): Target qubit index. - angle (Union[FreeParameterExpression, float]): angle in radians. - 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: CPhaseShift10 instruction. - - Examples: - >>> circ = Circuit().cphaseshift10(0, 1, 0.15) - """ - control_qubits = QubitSet(control) - absorbed_control = control_qubits.pop() - return Instruction( - CPhaseShift10(angle), - target=[absorbed_control, target], - control=control_qubits, - power=power, - ) - - -Gate.register_gate(CPhaseShift10) - - -class CV(Gate): - r"""Controlled Sqrt of X gate. - - Unitary matrix: - - .. math:: \mathtt{CV} = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 0.5+0.5i & 0.5-0.5i \\ - 0 & 0 & 0.5-0.5i & 0.5+0.5i - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["C", "V"]) - - @property - def _qasm_name(self) -> str: - return "cv" - - def adjoint(self) -> list[Gate]: - return [self, self, self] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.CV.construct(control=target[0], target=target[1]) - - def to_matrix(self) -> np.ndarray: - return np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 0.5 + 0.5j, 0.5 - 0.5j], # if the control bit, then apply the V gate - [0.0, 0.0, 0.5 - 0.5j, 0.5 + 0.5j], # which is the sqrt(NOT) gate. - ], - dtype=complex, - ) - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def cv(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruction: - r"""Controlled Sqrt of X gate. - - .. math:: \mathtt{CV} = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 0.5+0.5i & 0.5-0.5i \\ - 0 & 0 & 0.5-0.5i & 0.5+0.5i - \end{bmatrix}. - - Args: - control (QubitSetInput): Control qubit(s). The last control qubit - is absorbed into the target of the instruction. - target (QubitInput): Target qubit index. - 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: CV instruction. - - Examples: - >>> circ = Circuit().cv(0, 1) - """ - control_qubits = QubitSet(control) - absorbed_control = control_qubits.pop() - return Instruction( - CV(), target=[absorbed_control, target], control=control_qubits, power=power - ) - - -Gate.register_gate(CV) - - -class CY(Gate): - r"""Controlled Pauli-Y gate. - - Unitary matrix: - - .. math:: \mathtt{CY} = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & -i \\ - 0 & 0 & i & 0 - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["C", "Y"]) - - @property - def _qasm_name(self) -> str: - return "cy" - - def adjoint(self) -> list[Gate]: - return [CY()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.CY.construct(control=target[0], target=target[1]) - - def to_matrix(self) -> np.ndarray: - return np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 0.0, -1.0j], - [0.0, 0.0, +1.0j, 0.0], - ], - dtype=complex, - ) - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def cy(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruction: - r"""Controlled Pauli-Y gate. - - .. math:: \mathtt{CY} = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & -i \\ - 0 & 0 & i & 0 - \end{bmatrix}. - - Args: - control (QubitSetInput): Control qubit(s). The last control qubit - is absorbed into the target of the instruction. - target (QubitInput): Target qubit index. - 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: CY instruction. - - Examples: - >>> circ = Circuit().cy(0, 1) - """ - control_qubits = QubitSet(control) - absorbed_control = control_qubits.pop() - return Instruction( - CY(), target=[absorbed_control, target], control=control_qubits, power=power - ) - - -Gate.register_gate(CY) - - -class CZ(Gate): - r"""Controlled Pauli-Z gate. - - Unitary matrix: - - .. math:: \mathtt{CZ} = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & -1 - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["C", "Z"]) - - @property - def _qasm_name(self) -> str: - return "cz" - - def adjoint(self) -> list[Gate]: - return [CZ()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.CZ.construct(control=target[0], target=target[1]) - - def to_matrix(self) -> np.ndarray: - return np.diag([complex(1.0), 1.0, 1.0, -1.0]) - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def cz(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruction: - r"""Controlled Pauli-Z gate. - - .. math:: \mathtt{CZ} = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & -1 - \end{bmatrix}. - - Args: - control (QubitSetInput): Control qubit(s). The last control qubit - is absorbed into the target of the instruction. - target (QubitInput): Target qubit index. - 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: CZ instruction. - - Examples: - >>> circ = Circuit().cz(0, 1) - """ - control_qubits = QubitSet(control) - absorbed_control = control_qubits.pop() - return Instruction( - CZ(), target=[absorbed_control, target], control=control_qubits, power=power - ) - - -Gate.register_gate(CZ) - - -class ECR(Gate): - r"""An echoed RZX(pi/2) gate (ECR gate). - - Unitary matrix: - - .. math:: \mathtt{ECR} = \begin{bmatrix} - 0 & 0 & 1 & i \\ - 0 & 0 & i & 1 \\ - 1 & -i & 0 & 0 \\ - -i & 1 & 0 & 0 - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["ECR", "ECR"]) - - @property - def _qasm_name(self) -> str: - return "ecr" - - def adjoint(self) -> list[Gate]: - return [ECR()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.ECR.construct(targets=[target[0], target[1]]) - - def to_matrix(self) -> np.ndarray: - return ( - 1 - / np.sqrt(2) - * np.array( - [[0, 0, 1, 1.0j], [0, 0, 1.0j, 1], [1, -1.0j, 0, 0], [-1.0j, 1, 0, 0]], - dtype=complex, - ) - ) - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def ecr( - target1: QubitInput, - target2: QubitInput, - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Instruction: - r"""An echoed RZX(pi/2) gate (ECR gate). - - .. math:: \mathtt{ECR} = \begin{bmatrix} - 0 & 0 & 1 & i \\ - 0 & 0 & i & 1 \\ - 1 & -i & 0 & 0 \\ - -i & 1 & 0 & 0 - \end{bmatrix}. - - Args: - target1 (QubitInput): Target qubit 1 index. - target2 (QubitInput): Target qubit 2 index. - 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: ECR instruction. - - Examples: - >>> circ = Circuit().ecr(0, 1) - """ - return Instruction( - ECR(), - target=[target1, target2], - control=control, - control_state=control_state, - power=power, - ) - - -Gate.register_gate(ECR) - - -class XX(AngledGate): - r"""Ising XX coupling gate. - - Unitary matrix: - - .. math:: \mathtt{XX}(\phi) = \begin{bmatrix} - \cos{(\phi/2)} & 0 & 0 & -i \sin{(\phi/2)} \\ - 0 & \cos{(\phi/2)} & -i \sin{(\phi/2)} & 0 \\ - 0 & -i \sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\ - -i \sin{(\phi/2)} & 0 & 0 & \cos{(\phi/2)} - \end{bmatrix}. - - Reference: https://arxiv.org/abs/1707.06356 - - Args: - angle (Union[FreeParameterExpression, float]): angle in radians. - """ - - def __init__(self, angle: Union[FreeParameterExpression, float]): - super().__init__( - angle=angle, - qubit_count=None, - ascii_symbols=[ - angled_ascii_characters("XX", angle), - angled_ascii_characters("XX", angle), - ], - ) - - @property - def _qasm_name(self) -> str: - return "xx" - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.XX.construct(targets=[target[0], target[1]], angle=self.angle) - - def to_matrix(self) -> np.ndarray: - r"""Returns a matrix representation of this gate. - - Returns: - np.ndarray: The matrix representation of this gate. - """ - cos = np.cos(self.angle / 2) - isin = 1.0j * np.sin(self.angle / 2) - return np.array( - [ - [cos, 0.0, 0.0, -isin], - [0.0, cos, -isin, 0.0], - [0.0, -isin, cos, 0.0], - [-isin, 0.0, 0.0, cos], - ], - dtype=complex, - ) - - def bind_values(self, **kwargs) -> AngledGate: - return get_angle(self, **kwargs) - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def xx( - target1: QubitInput, - target2: QubitInput, - angle: Union[FreeParameterExpression, float], - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Instruction: - r"""Ising XX coupling gate. - - .. math:: \mathtt{XX}(\phi) = \begin{bmatrix} - \cos{(\phi/2)} & 0 & 0 & -i \sin{(\phi/2)} \\ - 0 & \cos{(\phi/2)} & -i \sin{(\phi/2)} & 0 \\ - 0 & -i \sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\ - -i \sin{(\phi/2)} & 0 & 0 & \cos{(\phi/2)} - \end{bmatrix}. - - Args: - target1 (QubitInput): Target qubit 1 index. - target2 (QubitInput): Target qubit 2 index. - angle (Union[FreeParameterExpression, float]): 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: - Instruction: XX instruction. - - Examples: - >>> circ = Circuit().xx(0, 1, 0.15) - """ - return Instruction( - XX(angle), - target=[target1, target2], - control=control, - control_state=control_state, - power=power, - ) - - -Gate.register_gate(XX) - - -class YY(AngledGate): - r"""Ising YY coupling gate. - - Unitary matrix: - - .. math:: \mathtt{YY}(\phi) = \begin{bmatrix} - \cos{(\phi/2)} & 0 & 0 & i \sin{(\phi/2)} \\ - 0 & \cos{(\phi/2)} & -i \sin{(\phi/2)} & 0 \\ - 0 & -i \sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\ - i \sin{(\phi/2)} & 0 & 0 & \cos{(\phi/2)} - \end{bmatrix}. - - Reference: https://arxiv.org/abs/1707.06356 - - Args: - angle (Union[FreeParameterExpression, float]): angle in radians. - """ - - def __init__(self, angle: Union[FreeParameterExpression, float]): - super().__init__( - angle=angle, - qubit_count=None, - ascii_symbols=[ - angled_ascii_characters("YY", angle), - angled_ascii_characters("YY", angle), - ], - ) - - @property - def _qasm_name(self) -> str: - return "yy" - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.YY.construct(targets=[target[0], target[1]], angle=self.angle) - - def to_matrix(self) -> np.ndarray: - r"""Returns a matrix representation of this gate. - - Returns: - np.ndarray: The matrix representation of this gate. - """ - cos = np.cos(self.angle / 2) - isin = 1.0j * np.sin(self.angle / 2) - return np.array( - [ - [cos, 0.0, 0.0, isin], - [0.0, cos, -isin, 0.0], - [0.0, -isin, cos, 0.0], - [isin, 0.0, 0.0, cos], - ], - dtype=complex, - ) - - def bind_values(self, **kwargs) -> AngledGate: - return get_angle(self, **kwargs) - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def yy( - target1: QubitInput, - target2: QubitInput, - angle: Union[FreeParameterExpression, float], - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Instruction: - r"""Ising YY coupling gate. - - .. math:: \mathtt{YY}(\phi) = \begin{bmatrix} - \cos{(\phi/2)} & 0 & 0 & i \sin{(\phi/2)} \\ - 0 & \cos{(\phi/2)} & -i \sin{(\phi/2)} & 0 \\ - 0 & -i \sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\ - i \sin{(\phi/2)} & 0 & 0 & \cos{(\phi/2)} - \end{bmatrix}. - - Args: - target1 (QubitInput): Target qubit 1 index. - target2 (QubitInput): Target qubit 2 index. - angle (Union[FreeParameterExpression, float]): 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: - Instruction: YY instruction. - - Examples: - >>> circ = Circuit().yy(0, 1, 0.15) - """ - return Instruction( - YY(angle), - target=[target1, target2], - control=control, - control_state=control_state, - power=power, - ) - - -Gate.register_gate(YY) - - -class ZZ(AngledGate): - r"""Ising ZZ coupling gate. - - Unitary matrix: - - .. math:: \mathtt{ZZ}(\phi) = \begin{bmatrix} - e^{-i\phi/2} & 0 & 0 & 0 \\ - 0 & e^{i\phi/2} & 0 & 0 \\ - 0 & 0 & e^{i\phi/2} & 0 \\ - 0 & 0 & 0 & e^{-i\phi/2} - \end{bmatrix}. - - Reference: https://arxiv.org/abs/1707.06356 - - Args: - angle (Union[FreeParameterExpression, float]): angle in radians. - """ - - def __init__(self, angle: Union[FreeParameterExpression, float]): - super().__init__( - angle=angle, - qubit_count=None, - ascii_symbols=[ - angled_ascii_characters("ZZ", angle), - angled_ascii_characters("ZZ", angle), - ], - ) - - @property - def _qasm_name(self) -> str: - return "zz" - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.ZZ.construct(targets=[target[0], target[1]], angle=self.angle) - - def to_matrix(self) -> np.ndarray: - return np.array( - [ - [np.exp(-1j * (self.angle / 2)), 0.0, 0.0, 0.0], - [0.0, np.exp(1j * (self.angle / 2)), 0.0, 0.0], - [0.0, 0.0, np.exp(1j * (self.angle / 2)), 0.0], - [0.0, 0.0, 0.0, np.exp(-1j * (self.angle / 2))], - ], - dtype=complex, - ) - - def bind_values(self, **kwargs) -> AngledGate: - return get_angle(self, **kwargs) - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def zz( - target1: QubitInput, - target2: QubitInput, - angle: Union[FreeParameterExpression, float], - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Instruction: - r"""Ising ZZ coupling gate. - - .. math:: \mathtt{ZZ}(\phi) = \begin{bmatrix} - e^{-i\phi/2} & 0 & 0 & 0 \\ - 0 & e^{i\phi/2} & 0 & 0 \\ - 0 & 0 & e^{i\phi/2} & 0 \\ - 0 & 0 & 0 & e^{-i\phi/2} - \end{bmatrix}. - - Args: - target1 (QubitInput): Target qubit 1 index. - target2 (QubitInput): Target qubit 2 index. - angle (Union[FreeParameterExpression, float]): 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: - Instruction: ZZ instruction. - - Examples: - >>> circ = Circuit().zz(0, 1, 0.15) - """ - return Instruction( - ZZ(angle), - target=[target1, target2], - control=control, - control_state=control_state, - power=power, - ) - - -Gate.register_gate(ZZ) - - -# Three qubit gates # - - -class CCNot(Gate): - r"""CCNOT gate or Toffoli gate. - - Unitary matrix: - - .. math:: \mathtt{CCNOT} = \begin{bmatrix} - 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["C", "C", "X"]) - - @property - def _qasm_name(self) -> str: - return "ccnot" - - def adjoint(self) -> list[Gate]: - return [CCNot()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.CCNot.construct(controls=[target[0], target[1]], target=target[2]) - - def to_matrix(self) -> np.ndarray: - return np.array( - [ - [1, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 0], - [0, 0, 0, 1, 0, 0, 0, 0], - [0, 0, 0, 0, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 1, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 1], - [0, 0, 0, 0, 0, 0, 1, 0], - ], - dtype=complex, - ) - - @staticmethod - def fixed_qubit_count() -> int: - return 3 - - @staticmethod - @circuit.subroutine(register=True) - def ccnot( - control1: QubitInput, - control2: QubitInput, - target: QubitInput, - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Instruction: - r"""CCNOT gate or Toffoli gate. - - .. math:: \mathtt{CCNOT} = \begin{bmatrix} - 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ - \end{bmatrix}. - - Args: - control1 (QubitInput): Control qubit 1 index. - control2 (QubitInput): Control qubit 2 index. - target (QubitInput): Target qubit index. - control (Optional[QubitSetInput]): Control qubit(s), in addition to - control1 and control2. 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. Control state only applies to control qubits specified with - the control argument, not control1 and control2. 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: CCNot instruction. - - Examples: - >>> circ = Circuit().ccnot(0, 1, 2) - """ - return Instruction( - CCNot(), - target=[control1, control2, target], - control=control, - control_state=control_state, - power=power, - ) - - -Gate.register_gate(CCNot) - - -class CSwap(Gate): - r"""Controlled Swap gate. - - Unitary matrix: - - .. math:: \mathtt{CSWAP} = \begin{bmatrix} - 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\ - \end{bmatrix}. - """ - - def __init__(self): - super().__init__(qubit_count=None, ascii_symbols=["C", "SWAP", "SWAP"]) - - @property - def _qasm_name(self) -> str: - return "cswap" - - def adjoint(self) -> list[Gate]: - return [CSwap()] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.CSwap.construct(control=target[0], targets=[target[1], target[2]]) - - def to_matrix(self) -> np.ndarray: - return np.array( - [ - [1, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 0], - [0, 0, 0, 1, 0, 0, 0, 0], - [0, 0, 0, 0, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 1, 0], - [0, 0, 0, 0, 0, 1, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 1], - ], - dtype=complex, - ) - - @staticmethod - def fixed_qubit_count() -> int: - return 3 - - @staticmethod - @circuit.subroutine(register=True) - def cswap( - control: QubitSetInput, - target1: QubitInput, - target2: QubitInput, - power: float = 1, - ) -> Instruction: - r"""Controlled Swap gate. - - .. math:: \mathtt{CSWAP} = \begin{bmatrix} - 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\ - \end{bmatrix}. - - Args: - control (QubitSetInput): Control qubit(s). The last control qubit - is absorbed into the target of the instruction. - target1 (QubitInput): Target qubit 1 index. - target2 (QubitInput): Target qubit 2 index. - 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: CSwap instruction. - - Examples: - >>> circ = Circuit().cswap(0, 1, 2) - """ - control_qubits = QubitSet(control) - absorbed_control = control_qubits.pop() - return Instruction( - CSwap(), - target=[absorbed_control, target1, target2], - control=control_qubits, - power=power, - ) - - -Gate.register_gate(CSwap) - - -class GPi(AngledGate): - r"""IonQ GPi gate. - - Unitary matrix: - - .. math:: \mathtt{GPi}(\phi) = \begin{bmatrix} - 0 & e^{-i \phi} \\ - e^{i \phi} & 0 - \end{bmatrix}. - - Args: - angle (Union[FreeParameterExpression, float]): angle in radians. - """ - - def __init__(self, angle: Union[FreeParameterExpression, float]): - super().__init__( - angle=angle, - qubit_count=None, - ascii_symbols=[angled_ascii_characters("GPi", angle)], - ) - - @property - 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], - ] - ) - - def adjoint(self) -> list[Gate]: - return [GPi(self.angle)] - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - def bind_values(self, **kwargs) -> GPi: - return get_angle(self, **kwargs) - - @staticmethod - @circuit.subroutine(register=True) - def gpi( - target: QubitSetInput, - angle: Union[FreeParameterExpression, float], - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""IonQ GPi gate. - - .. math:: \mathtt{GPi}(\phi) = \begin{bmatrix} - 0 & e^{-i \phi} \\ - e^{i \phi} & 0 - \end{bmatrix}. - - Args: - target (QubitSetInput): Target qubit(s). - angle (Union[FreeParameterExpression, float]): 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]: GPi instruction. - - Examples: - >>> circ = Circuit().gpi(0, 0.15) - """ - return [ - Instruction( - GPi(angle), target=qubit, control=control, control_state=control_state, power=power - ) - for qubit in QubitSet(target) - ] - - -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. - - Unitary matrix: - - .. math:: \mathtt{GPi2}(\phi) = \frac{1}{\sqrt{2}} \begin{bmatrix} - 1 & -i e^{-i \phi} \\ - -i e^{i \phi} & 1 - \end{bmatrix}. - - Args: - angle (Union[FreeParameterExpression, float]): angle in radians. - """ - - def __init__(self, angle: Union[FreeParameterExpression, float]): - super().__init__( - angle=angle, - qubit_count=None, - ascii_symbols=[angled_ascii_characters("GPi2", angle)], - ) - - @property - 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) - - def adjoint(self) -> list[Gate]: - return [GPi2(self.angle + np.pi)] - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - def bind_values(self, **kwargs) -> GPi2: - return get_angle(self, **kwargs) - - @staticmethod - @circuit.subroutine(register=True) - def gpi2( - target: QubitSetInput, - angle: Union[FreeParameterExpression, float], - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""IonQ GPi2 gate. - - .. math:: \mathtt{GPi2}(\phi) = \frac{1}{\sqrt{2}} \begin{bmatrix} - 1 & -i e^{-i \phi} \\ - -i e^{i \phi} & 1 - \end{bmatrix}. - - Args: - target (QubitSetInput): Target qubit(s). - angle (Union[FreeParameterExpression, float]): 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]: GPi2 instruction. - - Examples: - >>> circ = Circuit().gpi2(0, 0.15) - """ - return [ - Instruction( - GPi2(angle), target=qubit, control=control, control_state=control_state, power=power - ) - for qubit in QubitSet(target) - ] - - -Gate.register_gate(GPi2) - - -class MS(TripleAngledGate): - r"""IonQ Mølmer-Sørensen gate. - - Unitary matrix: - - .. math:: &\mathtt{MS}(\phi_0, \phi_1, \theta) =\\ &\begin{bmatrix} - \cos{\frac{\theta}{2}} & 0 & - 0 & -ie^{-i (\phi_0 + \phi_1)}\sin{\frac{\theta}{2}} \\ - 0 & \cos{\frac{\theta}{2}} & - -ie^{-i (\phi_0 - \phi_1)}\sin{\frac{\theta}{2}} & 0 \\ - 0 & -ie^{i (\phi_0 - \phi_1)}\sin{\frac{\theta}{2}} & - \cos{\frac{\theta}{2}} & 0 \\ - -ie^{i (\phi_0 + \phi_1)}\sin{\frac{\theta}{2}} & 0 - & 0 & \cos{\frac{\theta}{2}} - \end{bmatrix}. - - Args: - 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. - """ - - def __init__( - self, - angle_1: Union[FreeParameterExpression, float], - angle_2: Union[FreeParameterExpression, float], - angle_3: Union[FreeParameterExpression, float] = np.pi / 2, - ): - super().__init__( - angle_1=angle_1, - angle_2=angle_2, - angle_3=angle_3, - qubit_count=None, - ascii_symbols=[_multi_angled_ascii_characters("MS", angle_1, angle_2, angle_3)] * 2, - ) - - @property - def _qasm_name(self) -> str: - return "ms" - - def to_matrix(self) -> np.ndarray: - 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), - ], - ] - ) - - def adjoint(self) -> list[Gate]: - return [MS(self.angle_1 + np.pi, self.angle_2, self.angle_3)] - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - def bind_values(self, **kwargs) -> MS: - return _get_angles(self, **kwargs) - - @staticmethod - @circuit.subroutine(register=True) - def ms( - target1: QubitInput, - target2: QubitInput, - angle_1: Union[FreeParameterExpression, float], - angle_2: Union[FreeParameterExpression, float], - angle_3: Union[FreeParameterExpression, float] = np.pi / 2, - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Iterable[Instruction]: - r"""IonQ Mølmer-Sørensen gate. - - .. math:: &\mathtt{MS}(\phi_0, \phi_1, \theta) =\\ &\begin{bmatrix} - \cos{\frac{\theta}{2}} & 0 & - 0 & -ie^{-i (\phi_0 + \phi_1)}\sin{\frac{\theta}{2}} \\ - 0 & \cos{\frac{\theta}{2}} & - -ie^{-i (\phi_0 - \phi_1)}\sin{\frac{\theta}{2}} & 0 \\ - 0 & -ie^{i (\phi_0 - \phi_1)}\sin{\frac{\theta}{2}} & - \cos{\frac{\theta}{2}} & 0 \\ - -ie^{i (\phi_0 + \phi_1)}\sin{\frac{\theta}{2}} & 0 - & 0 & \cos{\frac{\theta}{2}} - \end{bmatrix}. - - Args: - target1 (QubitInput): Target qubit 1 index. - target2 (QubitInput): Target qubit 2 index. - angle_1 (Union[FreeParameterExpression, float]): angle in radians. - angle_2 (Union[FreeParameterExpression, float]): angle in radians. - angle_3 (Union[FreeParameterExpression, float]): 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]: MS instruction. - - Examples: - >>> circ = Circuit().ms(0, 1, 0.15, 0.34) - """ - return [ - Instruction( - MS(angle_1, angle_2, angle_3), - target=[target1, target2], - control=control, - control_state=control_state, - power=power, - ) - ] - - -Gate.register_gate(MS) - - -class Unitary(Gate): - """Arbitrary unitary gate. - - Args: - matrix (numpy.ndarray): Unitary matrix which defines the gate. - display_name (str): Name to be used for an instance of this unitary gate - for circuit diagrams. Defaults to `U`. - - 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 unitary. - """ - - def __init__(self, matrix: np.ndarray, display_name: str = "U"): - verify_quantum_operator_matrix_dimensions(matrix) - self._matrix = np.array(matrix, dtype=complex) - qubit_count = int(np.log2(self._matrix.shape[0])) - - if not is_unitary(self._matrix): - raise ValueError(f"{self._matrix} is not unitary") - - super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) - - def to_matrix(self) -> np.ndarray: - return np.array(self._matrix) - - def adjoint(self) -> list[Gate]: - return [Unitary(self._matrix.conj().T, display_name=f"({self.ascii_symbols})^†")] - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.Unitary.construct( - targets=list(target), - matrix=Unitary._transform_matrix_to_ir(self._matrix), - ) - - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - qubits = [serialization_properties.format_target(int(qubit)) for qubit in target] - formatted_matrix = np.array2string( - self._matrix, - separator=", ", - formatter={"all": lambda x: format_complex(x)}, - threshold=float("inf"), - ).replace("\n", "") - - return f"#pragma braket unitary({formatted_matrix}) {', '.join(qubits)}" - - def __eq__(self, other: Unitary): - return self.matrix_equivalence(other) if isinstance(other, Unitary) else False - - def __hash__(self): - return hash((self.name, str(self._matrix), self.qubit_count)) - - @staticmethod - def _transform_matrix_to_ir(matrix: np.ndarray) -> list: - return [[[element.real, element.imag] for element in row] for row in matrix.tolist()] - - @staticmethod - @circuit.subroutine(register=True) - def unitary(targets: QubitSet, matrix: np.ndarray, display_name: str = "U") -> Instruction: - r"""Arbitrary unitary gate. - - Args: - targets (QubitSet): Target qubits. - matrix (numpy.ndarray): Unitary matrix which defines the gate. Matrix should be - compatible with the supplied targets, with `2 ** len(targets) == matrix.shape[0]`. - display_name (str): Name to be used for an instance of this unitary gate - for circuit diagrams. Defaults to `U`. - - Returns: - Instruction: Unitary instruction. - - Raises: - ValueError: If `matrix` is not a two-dimensional square matrix, - or has a dimension length that is not compatible with the `targets`, - or is not unitary, - - Examples: - >>> circ = Circuit().unitary(matrix=np.array([[0, 1],[1, 0]]), targets=[0]) - """ - # todo: handle controlled unitary - if 2 ** len(targets) != matrix.shape[0]: - raise ValueError("Dimensions of the supplied unitary are incompatible with the targets") - - return Instruction(Unitary(matrix, display_name), target=targets) - - -Gate.register_gate(Unitary) - - -class PulseGate(Gate, Parameterizable): - """Arbitrary pulse gate which provides the ability to embed custom pulse sequences - within circuits. - - Args: - pulse_sequence (PulseSequence): PulseSequence to embed within the circuit. - qubit_count (int): The number of qubits this pulse gate operates on. - display_name (str): Name to be used for an instance of this pulse gate - for circuit diagrams. Defaults to `PG`. - """ - - def __init__(self, pulse_sequence: PulseSequence, qubit_count: int, display_name: str = "PG"): - if pulse_sequence._capture_v0_count > 0: - raise ValueError( - "The supplied pulse sequence contains capture instructions which " - "can not be embedded in a PulseGate." - ) - self._pulse_sequence = deepcopy(pulse_sequence) - super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) - - @property - def pulse_sequence(self) -> PulseSequence: - """PulseSequence: The underlying PulseSequence of this gate.""" - return self._pulse_sequence - - @property - def parameters(self) -> list[FreeParameter]: - r"""Returns the list of `FreeParameter` s associated with the gate.""" - return list(self._pulse_sequence.parameters) - - def bind_values(self, **kwargs) -> PulseGate: - """Takes in parameters and returns an object with specified parameters - replaced with their values. - - Returns: - PulseGate: A copy of this gate with the requested parameters bound. - """ - new_pulse_sequence = self._pulse_sequence.make_bound_pulse_sequence(kwargs) - return PulseGate(new_pulse_sequence, self.qubit_count, self.ascii_symbols[0]) - - def to_matrix(self) -> np.ndarray: - raise NotImplementedError("PulseGate does not support conversion to a matrix.") - - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties, **kwargs - ) -> str: - new_program = Program(None) - new_program += self._pulse_sequence._program - # Suppress declaration of frame and waveform vars as they have already been declared - for v in list(new_program.undeclared_vars.values()): - new_program._mark_var_declared(v) - return ast_to_qasm(new_program.to_ast(include_externs=False, encal=True)) - - @staticmethod - @circuit.subroutine(register=True) - def pulse_gate( - targets: QubitSet, - pulse_sequence: PulseSequence, - display_name: str = "PG", - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Instruction: - r"""Arbitrary pulse gate which provides the ability to embed custom pulse sequences - within circuits. - - Args: - targets (QubitSet): Target qubits. Note: These are only for representational purposes. - The actual targets are determined by the frames used in the pulse sequence. - pulse_sequence (PulseSequence): PulseSequence to embed within the circuit. - display_name (str): Name to be used for an instance of this pulse gate - for circuit diagrams. Defaults to `PG`. - 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: Pulse gate instruction. - - Examples: - >>> pulse_seq = PulseSequence().set_frequency(frame, frequency).... - >>> circ = Circuit().pulse_gate(pulse_sequence=pulse_seq, targets=[0]) - """ - return Instruction( - PulseGate(pulse_sequence, len(QubitSet(targets)), display_name), - target=targets, - control=control, - control_state=control_state, - power=power, - ) - - -Gate.register_gate(PulseGate) - - -def format_complex(number: complex) -> str: - """Format a complex number into + im to be consumed by the braket unitary pragma - - Args: - number (complex): A complex number. - - Returns: - str: The formatted string. - """ - if number.real: - 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: - return "0" diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py deleted file mode 100644 index bedfd0c4..00000000 --- a/src/braket/circuits/instruction.py +++ /dev/null @@ -1,294 +0,0 @@ -# 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, Optional - -from braket.circuits.basis_state import BasisState, BasisStateInput -from braket.circuits.compiler_directive import CompilerDirective -from braket.circuits.gate import Gate -from braket.circuits.operator import Operator -from braket.circuits.quantum_operator import QuantumOperator -from braket.circuits.serialization import IRType, SerializationProperties -from braket.registers.qubit import QubitInput -from braket.registers.qubit_set import QubitSet, QubitSetInput - -# InstructionOperator is a type alias, and it can be expanded to include other operators -InstructionOperator = Operator - - -class Instruction: - """An instruction is a quantum directive that describes the quantum task to perform on a quantum - device. - """ - - def __init__( - self, - operator: InstructionOperator, - target: Optional[QubitSetInput] = None, - *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, - power: float = 1, - ) -> Instruction: - """InstructionOperator includes objects of type `Gate` and `Noise` only. - - Args: - operator (InstructionOperator): Operator for the instruction. - target (Optional[QubitSetInput]): Target qubits that the operator is applied to. - Default is None. - control (Optional[QubitSetInput]): Target qubits that the operator is controlled on. - Default is 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. - - Raises: - ValueError: If `operator` is empty or any integer in `target` does not meet the `Qubit` - or `QubitSet` class requirements. Also, if operator qubit count does not equal - the size of the target qubit set. - TypeError: If a `Qubit` class can't be constructed from `target` due to an incorrect - `typing`. - - Examples: - >>> Instruction(Gate.CNot(), [0, 1]) - Instruction('operator': CNOT, 'target': QubitSet(Qubit(0), Qubit(1))) - >>> instr = Instruction(Gate.CNot()), QubitSet([0, 1])]) - Instruction('operator': CNOT, 'target': QubitSet(Qubit(0), Qubit(1))) - >>> instr = Instruction(Gate.H(), 0) - Instruction('operator': H, 'target': QubitSet(Qubit(0),)) - >>> instr = Instruction(Gate.Rx(0.12), 0) - Instruction('operator': Rx, 'target': QubitSet(Qubit(0),)) - >>> instr = Instruction(Gate.Rx(0.12, control=1), 0) - Instruction( - 'operator': Rx, - 'target': QubitSet(Qubit(0),), - 'control': QubitSet(Qubit(1),), - ) - """ - if not operator: - raise ValueError("Operator cannot be empty") - target_set = QubitSet(target) - control_set = QubitSet(control) - if isinstance(operator, QuantumOperator) and len(target_set) != operator.qubit_count: - raise ValueError( - f"Operator qubit count {operator.qubit_count} must be equal to" - f" size of target qubit set {target_set}" - ) - self._operator = operator - self._target = target_set - self._control = control_set - self._control_state = BasisState( - (1,) * len(control_set) if control_state is None else control_state, - len(control_set), - ) - self._power = power - - @property - def operator(self) -> InstructionOperator: - """Operator: The operator for the instruction, for example, `Gate`.""" - return self._operator - - @property - def target(self) -> QubitSet: - """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.""" - return self._control - - @property - def control_state(self) -> BasisState: - """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.""" - return self._power - - def adjoint(self) -> list[Instruction]: - """Returns a list of Instructions implementing adjoint of this instruction's own operator - - This operation only works on Gate operators and compiler directives. - - Returns: - list[Instruction]: A list of new instructions that comprise the adjoint of this operator - - Raises: - NotImplementedError: If `operator` is not of type `Gate` or `CompilerDirective` - """ - operator = self._operator - if isinstance(operator, Gate): - return [ - Instruction( - gate, - self._target, - control=self._control, - control_state=self._control_state, - power=self._power, - ) - for gate in operator.adjoint() - ] - elif isinstance(operator, CompilerDirective): - return [Instruction(operator.counterpart(), self._target)] - raise NotImplementedError(f"Adjoint not supported for {operator}") - - def to_ir( - self, - ir_type: IRType = IRType.JAQCD, - serialization_properties: SerializationProperties | None = None, - ) -> Any: - """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: - ir_type(IRType) : The IRType to use for converting the instruction object to its - IR representation. - 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 instruction. - """ - kwargs = {} - if self.control: - kwargs["control"] = self.control - kwargs["control_state"] = self.control_state - if self.power != 1: - kwargs["power"] = self.power - return self._operator.to_ir( - [int(qubit) for qubit in self._target], - ir_type=ir_type, - serialization_properties=serialization_properties, - **kwargs, - ) - - @property - def ascii_symbols(self) -> tuple[str, ...]: - """tuple[str, ...]: Returns the ascii symbols for the instruction's operator.""" - return self._operator.ascii_symbols - - 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, - power: float = 1, - ) -> Instruction: - """Return a shallow copy of the instruction. - - Note: - If `target_mapping` is specified, then `self.target` is mapped to the specified - qubits. This is useful apply an instruction to a circuit and change the target qubits. - Same relationship holds for `control_mapping`. - - Args: - target_mapping (Optional[dict[QubitInput, QubitInput]]): A dictionary of - qubit mappings to apply to the target. Key is the qubit in this `target` and the - value is what the key is changed to. Default = `None`. - target (Optional[QubitSetInput]): Target qubits for the new instruction. - Default is None. - control_mapping (Optional[dict[QubitInput, QubitInput]]): A dictionary of - qubit mappings to apply to the control. Key is the qubit in this `control` and the - value is what the key is changed to. Default = `None`. - control (Optional[QubitSetInput]): Control qubits for the new instruction. - Default is 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: A shallow copy of the instruction. - - Raises: - TypeError: If both `target_mapping` and `target` are supplied. - - Examples: - >>> instr = Instruction(Gate.H(), 0) - >>> new_instr = instr.copy() - >>> new_instr.target - QubitSet(Qubit(0)) - >>> new_instr = instr.copy(target_mapping={0: 5}) - >>> new_instr.target - QubitSet(Qubit(5)) - >>> new_instr = instr.copy(target=[5]) - >>> new_instr.target - QubitSet(Qubit(5)) - """ - if target_mapping and target is not None: - raise TypeError("Only 'target_mapping' or 'target' can be supplied, but not both.") - if control_mapping and control is not None: - raise TypeError("Only 'control_mapping' or 'control' can be supplied, but not both.") - - new_target = self._target.map(target_mapping or {}) if target is None else target - new_control = self._control.map(control_mapping or {}) if control is None else control - new_control_state = self._control_state if control_state is None else control_state - - return Instruction( - self._operator, - new_target, - control=new_control, - control_state=new_control_state, - power=power, - ) - - def __repr__(self): - return ( - f"Instruction('operator': {self._operator}, " - f"'target': {self._target}, " - f"'control': {self._control}, " - f"'control_state': {self._control_state.as_tuple}, " - f"'power': {self.power})" - ) - - def __eq__(self, other: Instruction): - if isinstance(other, Instruction): - return ( - self._operator, - self._target, - self._control, - self._control_state, - self._power, - ) == ( - other._operator, - other._target, - other._control, - other._control_state, - other._power, - ) - return NotImplemented - - def __pow__(self, power: float, modulo: float = None): - new_power = self.power * power - if modulo is not None: - new_power %= modulo - return self.copy(power=new_power) diff --git a/src/braket/circuits/measure.py b/src/braket/circuits/measure.py deleted file mode 100644 index d31555ba..00000000 --- a/src/braket/circuits/measure.py +++ /dev/null @@ -1,99 +0,0 @@ -# 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 deleted file mode 100644 index b2dee415..00000000 --- a/src/braket/circuits/moments.py +++ /dev/null @@ -1,350 +0,0 @@ -# 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 OrderedDict -from collections.abc import ItemsView, Iterable, KeysView, Mapping, ValuesView -from enum import Enum -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.measure import Measure -from braket.circuits.noise import Noise -from braket.registers.qubit import Qubit -from braket.registers.qubit_set import QubitSet - - -class MomentType(str, Enum): - """The type of moments. - GATE: a gate - NOISE: a noise channel added directly to the circuit - GATE_NOISE: a gate-based noise channel - 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" - NOISE = "noise" - GATE_NOISE = "gate_noise" - INITIALIZATION_NOISE = "initialization_noise" - READOUT_NOISE = "readout_noise" - COMPILER_DIRECTIVE = "compiler_directive" - GLOBAL_PHASE = "global_phase" - MEASURE = "measure" - - -class MomentsKey(NamedTuple): - """Key of the Moments mapping. - - Args: - time: moment - qubits: qubit set - moment_type: The type of the moment - noise_index: the number of noise channels at the same moment. For gates, this is the - number of gate_noise channels associated with that gate. For all other noise - types, noise_index starts from 0; but for gate noise, it starts from 1. - """ - - time: int - qubits: QubitSet - moment_type: MomentType - noise_index: int - subindex: int = 0 - - -class Moments(Mapping[MomentsKey, Instruction]): - 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. - - This data structure is useful to determine a dependency of instructions, such as - printing or optimizing circuit structure, before sending it to a quantum - device. The original insertion order is preserved and can be retrieved via the `values()` - method. - - Args: - instructions (Iterable[Instruction] | None): Instructions to initialize self. - Default = None. - - Examples: - >>> moments = Moments() - >>> moments.add([Instruction(Gate.H(), 0), Instruction(Gate.CNot(), [0, 1])]) - >>> moments.add([Instruction(Gate.H(), 0), Instruction(Gate.H(), 1)]) - >>> for i, item in enumerate(moments.items()): - ... 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)])) - Item 1 - Key: MomentsKey(time=1, qubits=QubitSet([Qubit(0), Qubit(1)])) - Value: Instruction('operator': CNOT, 'target': QubitSet([Qubit(0), Qubit(1)])) - Item 2 - Key: MomentsKey(time=2, qubits=QubitSet([Qubit(0)])) - Value: Instruction('operator': H, 'target': QubitSet([Qubit(0)])) - Item 3 - Key: MomentsKey(time=2, qubits=QubitSet([Qubit(1)])) - Value: Instruction('operator': H, 'target': QubitSet([Qubit(1)])) - """ - - def __init__(self, instructions: Iterable[Instruction] | None = None): - self._moments: OrderedDict[MomentsKey, Instruction] = OrderedDict() - self._max_times: dict[Qubit, int] = {} - self._qubits = QubitSet() - self._depth = 0 - self._time_all_qubits = -1 - self._number_gphase_in_current_moment = 0 - - self.add(instructions or []) - - @property - def depth(self) -> int: - """int: Get the depth (number of slices) of self.""" - return self._depth - - @property - def qubit_count(self) -> int: - """int: Get the number of qubits used across all of the instructions.""" - return len(self._qubits) - - @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. - - Note: - Don't mutate this object, any changes may impact the behavior of this class and / or - consumers. If you need to mutate this, then copy it via `QubitSet(moments.qubits())`. - """ - return self._qubits - - def time_slices(self) -> dict[int, list[Instruction]]: - """Get instructions keyed by time. - - Returns: - dict[int, list[Instruction]]: Key is the time and value is a list of instructions that - occur at that moment in time. The order of instructions is in no particular order. - - Note: - This is a computed result over self and can be freely mutated. This is re-computed with - 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(): - instructions = time_slices.get(key.time, []) - instructions.append(instruction) - time_slices[key.time] = instructions - - return time_slices - - def add( - self, instructions: Union[Iterable[Instruction], Instruction], noise_index: int = 0 - ) -> None: - """Add one or more instructions to self. - - Args: - instructions (Union[Iterable[Instruction], Instruction]): Instructions to add to self. - The instruction is added to the max time slice in which the instruction fits. - noise_index (int): the number of noise channels at the same moment. For gates, this - is the number of gate_noise channels associated with that gate. For all other noise - types, noise_index starts from 0; but for gate noise, it starts from 1. - """ - if isinstance(instructions, Instruction): - instructions = [instructions] - for instruction in instructions: - self._add(instruction, noise_index) - - 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 - 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": - 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) - - 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: - 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( - 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. - noise_index (int): The number of noise channels at the same moment. For gates, this - is the number of gate_noise channels associated with that gate. For all other noise - types, noise_index starts from 0; but for gate noise, it starts from 1. - """ - qubit_range = instruction.target - time = max(0, *[self._max_time_for_qubit(qubit) for qubit in qubit_range]) - if input_type == MomentType.INITIALIZATION_NOISE: - time = 0 - - while MomentsKey(time, qubit_range, input_type, noise_index) in self._moments: - noise_index += 1 - - self._moments[MomentsKey(time, qubit_range, input_type, noise_index)] = instruction - self._qubits.update(qubit_range) - - def sort_moments(self) -> None: - """Make the disordered moments in order. - - 1. Make the readout noise in the end - 2. Make the initialization noise at the beginning - """ - # key for NOISE, GATE and GATE_NOISE - key_noise = [] - # key for INITIALIZATION_NOISE - key_initialization_noise = [] - # key for READOUT_NOISE - key_readout_noise = [] - moment_copy = OrderedDict() - sorted_moment = OrderedDict() - last_measure = self._depth - - 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) - - for key in key_initialization_noise: - sorted_moment[key] = moment_copy[key] - 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(last_measure - 1, 0) - - for key in key_readout_noise: - sorted_moment[ - MomentsKey(max_time, key.qubits, MomentType.READOUT_NOISE, key.noise_index) - ] = moment_copy[key] - - self._moments = sorted_moment - - 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) - - # - # Implement abstract methods, default to calling `self`'s underlying dictionary - # - - def keys(self) -> KeysView[MomentsKey]: - """Return a view of self's keys.""" - return self._moments.keys() - - def items(self) -> ItemsView[MomentsKey, Instruction]: - """Return a view of self's (key, instruction).""" - return self._moments.items() - - def values(self) -> ValuesView[Instruction]: - """Return a view of self's instructions. - - Returns: - ValuesView[Instruction]: The (in-order) instructions. - """ - self.sort_moments() - return self._moments.values() - - def get(self, key: MomentsKey, default: Any | None = None) -> Instruction: - """Get the instruction in self by key. - - Args: - key (MomentsKey): Key of the instruction to fetch. - default (Any | None): Value to return if `key` is not in `moments`. Default = `None`. - - Returns: - Instruction: `moments[key]` if `key` in `moments`, else `default` is returned. - """ - return self._moments.get(key, default) - - def __getitem__(self, key: MomentsKey): - return self._moments.__getitem__(key) - - def __iter__(self): - return self._moments.__iter__() - - def __len__(self): - return self._moments.__len__() - - def __contains__(self, item: MomentsKey): - return self._moments.__contains__(item) - - def __eq__(self, other: Moments): - if isinstance(other, Moments): - return self._moments == other._moments - return NotImplemented - - def __ne__(self, other: Moments): - result = self.__eq__(other) - return not result if result is not NotImplemented else NotImplemented - - def __repr__(self): - return self._moments.__repr__() - - def __str__(self): - return self._moments.__str__() diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py deleted file mode 100644 index e5d4fdf8..00000000 --- a/src/braket/circuits/noise.py +++ /dev/null @@ -1,844 +0,0 @@ -# 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, Sequence -from typing import Any, ClassVar, Optional, Union - -import numpy as np - -from braket.circuits.free_parameter import FreeParameter -from braket.circuits.free_parameter_expression import FreeParameterExpression -from braket.circuits.parameterizable import Parameterizable -from braket.circuits.quantum_operator import QuantumOperator -from braket.circuits.serialization import ( - IRType, - OpenQASMSerializationProperties, - SerializationProperties, -) -from braket.registers.qubit_set import QubitSet - - -class Noise(QuantumOperator): - """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 - the metadata that defines what the noise channel is and what it does. - """ - - 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 - are used when printing a diagram of circuits. Length must be the same as - `qubit_count`, and index ordering is expected to correlate with target ordering - on the instruction. - - Raises: - ValueError: `qubit_count` is less than 1, `ascii_symbols` are None, or - length of `ascii_symbols` is not equal to `qubit_count` - """ - super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) - - @property - def name(self) -> str: - """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, - target: QubitSet, - ir_type: IRType = IRType.JAQCD, - serialization_properties: SerializationProperties | None = None, - ) -> Any: - """Returns IR object of quantum operator and target - - Args: - target (QubitSet): target qubit(s) - ir_type(IRType) : The IRType to use for converting the noise 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 - properties supplied must correspond to the supplied `ir_type`. Defaults to None. - - Returns: - Any: IR object of the quantum operator and target - - Raises: - ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization - properties don't correspond to the `ir_type`. - """ - if ir_type == IRType.JAQCD: - return self._to_jaqcd(target) - elif ir_type == IRType.OPENQASM: - if serialization_properties and not isinstance( - serialization_properties, OpenQASMSerializationProperties - ): - raise ValueError( - "serialization_properties must be of type OpenQASMSerializationProperties " - "for IRType.OPENQASM." - ) - return self._to_openqasm( - target, serialization_properties or OpenQASMSerializationProperties() - ) - else: - 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. - - Args: - target (QubitSet): target qubit(s). - - Returns: - Any: JAQCD object representing the noise. - """ - raise NotImplementedError("to_jaqcd has not been implemented yet.") - - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ) -> str: - """Returns the openqasm string representation of the noise. - - Args: - target (QubitSet): target qubit(s). - serialization_properties (OpenQASMSerializationProperties): The serialization properties - to use while serializing the object to the IR representation. - - Returns: - str: Representing the openqasm representation of the noise. - """ - raise NotImplementedError("to_openqasm has not been implemented yet.") - - def to_matrix(self, *args, **kwargs) -> Iterable[np.ndarray]: - """Returns a list of matrices defining the Kraus matrices of the noise channel. - - Returns: - Iterable[ndarray]: list of matrices defining the Kraus matrices of the noise channel. - """ - raise NotImplementedError("to_matrix has not been implemented yet.") - - def __eq__(self, other: Noise): - return self.name == other.name if isinstance(other, Noise) else False - - def __repr__(self): - return f"{self.name}('qubit_count': {self.qubit_count})" - - @classmethod - def from_dict(cls, noise: dict) -> Noise: - """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. - - Returns: - Noise: An object of this class that corresponds to the passed in dictionary. - """ - if "__class__" in noise: - noise_name = noise["__class__"] - noise_cls = getattr(cls, noise_name) - return noise_cls.from_dict(noise) - raise NotImplementedError - - @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. - """ - setattr(cls, noise.__name__, noise) - - -class SingleProbabilisticNoise(Noise, Parameterizable): - """Class `SingleProbabilisticNoise` represents the bit/phase flip noise channel on N qubits - parameterized by a single probability. - """ - - def __init__( - self, - probability: Union[FreeParameterExpression, float], - qubit_count: Optional[int], - ascii_symbols: Sequence[str], - max_probability: float = 0.5, - ): - """Initializes a `SingleProbabilisticNoise`. - - Args: - probability (Union[FreeParameterExpression, float]): The probability that the - noise occurs. - qubit_count (Optional[int]): The number of qubits to apply noise. - ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when - 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. - max_probability (float): Maximum allowed probability of the noise channel. Default: 0.5 - - Raises: - ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or - `ascii_symbols` length != `qubit_count`, `probability` is not `float` or - `FreeParameterExpression`, `probability` > 1/2, or `probability` < 0 - """ - super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) - - if not isinstance(probability, FreeParameterExpression): - _validate_param_value(probability, "probability", max_probability) - self._probability = probability - - @property - def probability(self) -> float: - """The probability that parametrizes the noise channel. - - Returns: - float: The probability that parametrizes the noise channel. - """ - return self._probability - - def __repr__(self): - return f"{self.name}('probability': {self.probability}, 'qubit_count': {self.qubit_count})" - - def __str__(self): - return f"{self.name}({self.probability})" - - @property - def parameters(self) -> list[Union[FreeParameterExpression, float]]: - """Returns the parameters associated with the object, either unbound free parameter - expressions or bound values. - - Returns: - list[Union[FreeParameterExpression, float]]: The free parameter expressions - or fixed values associated with the object. - """ - return [self._probability] - - 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. - - Returns: - SingleProbabilisticNoise: A new Noise object of the same type with the requested - parameters bound. - - Raises: - NotImplementedError: Subclasses should implement this function. - """ - raise NotImplementedError - - def to_dict(self) -> dict: - """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. - """ - return { - "__class__": self.__class__.__name__, - "probability": _parameter_to_dict(self.probability), - "qubit_count": self.qubit_count, - "ascii_symbols": self.ascii_symbols, - } - - -class SingleProbabilisticNoise_34(SingleProbabilisticNoise): - """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], - ascii_symbols: Sequence[str], - ): - """Initializes a `SingleProbabilisticNoise_34`. - - Args: - probability (Union[FreeParameterExpression, float]): The probability that the - noise occurs. - qubit_count (Optional[int]): The number of qubits to apply noise. - ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when - 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: - ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or - `ascii_symbols` length != `qubit_count`, `probability` is not `float` or - `FreeParameterExpression`, `probability` > 3/4, or `probability` < 0 - """ - super().__init__( - probability=probability, - qubit_count=qubit_count, - ascii_symbols=ascii_symbols, - max_probability=0.75, - ) - - -class SingleProbabilisticNoise_1516(SingleProbabilisticNoise): - """Class `SingleProbabilisticNoise` represents the TwoQubitDepolarizing noise channel - parameterized by a single probability. - """ - - def __init__( - self, - probability: Union[FreeParameterExpression, float], - qubit_count: Optional[int], - ascii_symbols: Sequence[str], - ): - """Initializes a `SingleProbabilisticNoise_1516`. - - Args: - probability (Union[FreeParameterExpression, float]): The probability that the - noise occurs. - qubit_count (Optional[int]): The number of qubits to apply noise. - ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when - 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: - ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or - `ascii_symbols` length != `qubit_count`, `probability` is not `float` or - `FreeParameterExpression`, `probability` > 15/16, or `probability` < 0 - """ - super().__init__( - probability=probability, - qubit_count=qubit_count, - ascii_symbols=ascii_symbols, - max_probability=0.9375, - ) - - -class MultiQubitPauliNoise(Noise, Parameterizable): - """Class `MultiQubitPauliNoise` represents a general multi-qubit Pauli channel, - parameterized by up to 4**N - 1 probabilities. - """ - - _allowed_substrings: ClassVar = {"I", "X", "Y", "Z"} - - def __init__( - self, - probabilities: dict[str, Union[FreeParameterExpression, float]], - qubit_count: Optional[int], - ascii_symbols: Sequence[str], - ): - """[summary] - - Args: - probabilities (dict[str, Union[FreeParameterExpression, float]]): A dictionary with - Pauli strings as keys and the probabilities as values, i.e. {"XX": 0.1. "IZ": 0.2}. - qubit_count (Optional[int]): The number of qubits the Pauli noise acts on. - ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when - 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: - ValueError: If - `qubit_count` < 1, - `ascii_symbols` is `None`, - `ascii_symbols` length != `qubit_count`, - `probabilities` are not `float`s or FreeParameterExpressions, - any of `probabilities` > 1 or `probabilities` < 0, - the sum of all probabilities is > 1, - if "II" is specified as a Pauli string, - if any Pauli string contains invalid strings, - or if the length of probabilities is greater than 4**qubit_count. - 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 - - if not probabilities: - raise ValueError("Pauli dictionary must not be empty.") - - identity = self.qubit_count * "I" - if identity in probabilities: - raise ValueError( - f"{identity} is not allowed as a key. Please enter only non-identity Pauli strings." - ) - - total_prob = 0 - for pauli_string, prob in probabilities.items(): - MultiQubitPauliNoise._validate_pauli_string( - pauli_string, self.qubit_count, self._allowed_substrings - ) - if not isinstance(prob, FreeParameterExpression): - _validate_param_value(prob, f"probability for {pauli_string}") - 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}." - ) - - @classmethod - def _validate_pauli_string( - cls, pauli_str: str, qubit_count: int, allowed_substrings: set[str] - ) -> None: - if not isinstance(pauli_str, str): - 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}." - ) - 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}." - ) - - def __repr__(self): - return ( - f"{self.name}('probabilities' : {self._probabilities}, " - f"'qubit_count': {self.qubit_count})" - ) - - def __str__(self): - return f"{self.name}({self._probabilities})" - - 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. - """ - return self._probabilities - - @property - def parameters(self) -> list[Union[FreeParameterExpression, float]]: - """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`. - - Returns: - list[Union[FreeParameterExpression, float]]: The free parameter expressions - or fixed values associated with the object. - """ - return [ - self._probabilities[pauli_string] for pauli_string in sorted(self._probabilities.keys()) - ] - - def bind_values(self, **kwargs) -> MultiQubitPauliNoise: - """Takes in parameters and attempts to assign them to values. - - Returns: - MultiQubitPauliNoise: A new Noise object of the same type with the requested - parameters bound. - - Raises: - NotImplementedError: Subclasses should implement this function. - """ - raise NotImplementedError - - def to_dict(self) -> dict: - """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 = { - pauli_string: _parameter_to_dict(prob) - for pauli_string, prob in self._probabilities.items() - } - return { - "__class__": self.__class__.__name__, - "probabilities": probabilities, - "qubit_count": self.qubit_count, - "ascii_symbols": self.ascii_symbols, - } - - -class PauliNoise(Noise, Parameterizable): - """Class `PauliNoise` represents the a single-qubit Pauli noise channel - acting on one qubit. It is parameterized by three probabilities. - """ - - def __init__( - self, - probX: Union[FreeParameterExpression, float], - probY: Union[FreeParameterExpression, float], - probZ: Union[FreeParameterExpression, float], - 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. - probY (Union[FreeParameterExpression, float]): The Y coefficient of the Kraus operators - in the channel. - probZ (Union[FreeParameterExpression, float]): The Z coefficient of the Kraus operators - in the channel. - qubit_count (Optional[int]): The number of qubits to apply noise. - ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when - 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: - ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or - `ascii_symbols` length != `qubit_count`, `probX` or `probY` or `probZ` - is not `float` or FreeParameterExpression, `probX` or `probY` or `probZ` > 1.0, or - `probX` or `probY` or `probZ` < 0.0, or `probX`+`probY`+`probZ` > 1 - """ - super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) - - total = 0 - total += PauliNoise._get_param_float(probX, "probX") - total += PauliNoise._get_param_float(probY, "probY") - total += PauliNoise._get_param_float(probZ, "probZ") - if total > 1: - raise ValueError("the sum of probX, probY, probZ cannot be larger than 1") - self._parameters = [probX, probY, probZ] - - @staticmethod - def _get_param_float(param: Union[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. - - Args: - param (Union[FreeParameterExpression, float]): The probability to validate - param_name (str): The name of the probability parameter - - Returns: - float: The value of the parameter, or 0 if it is a free parameter expression - """ - if isinstance(param, FreeParameterExpression): - return 0 - _validate_param_value(param, param_name) - return float(param) - - @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. - """ - return self._parameters[0] - - @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. - """ - return self._parameters[1] - - @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. - """ - return self._parameters[2] - - def __repr__(self): - return ( - f"{self.name}(" - f"'probX': {self._parameters[0]}, " - f"'probY': {self._parameters[1]}, " - f"'probZ': {self._parameters[2]}, " - f"'qubit_count': {self.qubit_count}" - f")" - ) - - def __str__(self): - return f"{self.name}({self._parameters[0]}, {self._parameters[1]}, {self._parameters[2]})" - - 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. - - Parameters are in the order [probX, probY, probZ] - - Returns: - list[Union[FreeParameterExpression, float]]: The free parameter expressions - or fixed values associated with the object. - """ - return self._parameters - - def bind_values(self, **kwargs) -> PauliNoise: - """Takes in parameters and attempts to assign them to values. - - Returns: - PauliNoise: A new Noise object of the same type with the requested - parameters bound. - - Raises: - NotImplementedError: Subclasses should implement this function. - """ - raise NotImplementedError - - def to_dict(self) -> dict: - """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. - """ - return { - "__class__": self.__class__.__name__, - "probX": _parameter_to_dict(self.probX), - "probY": _parameter_to_dict(self.probY), - "probZ": _parameter_to_dict(self.probZ), - "qubit_count": self.qubit_count, - "ascii_symbols": self.ascii_symbols, - } - - -class DampingNoise(Noise, Parameterizable): - """Class `DampingNoise` represents a damping noise channel - on N qubits parameterized by gamma. - """ - - def __init__( - self, - gamma: Union[FreeParameterExpression, float], - qubit_count: Optional[int], - ascii_symbols: Sequence[str], - ): - """Initializes a `DampingNoise`. - - Args: - gamma (Union[FreeParameterExpression, float]): Probability of damping. - qubit_count (Optional[int]): The number of qubits to apply noise. - ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when - 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: - ValueError: If - `qubit_count` < 1, - `ascii_symbols` is `None`, - `len(ascii_symbols)` != `qubit_count`, - `gamma` is not `float` or `FreeParameterExpression`, - or `gamma` > 1.0 or `gamma` < 0.0. - """ - super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) - - if not isinstance(gamma, FreeParameterExpression): - _validate_param_value(gamma, "gamma") - self._gamma = gamma - - @property - def gamma(self) -> float: - """Probability of damping. - - Returns: - float: Probability of damping. - """ - return self._gamma - - def __repr__(self): - return f"{self.name}('gamma': {self.gamma}, 'qubit_count': {self.qubit_count})" - - def __str__(self): - return f"{self.name}({self.gamma})" - - @property - def parameters(self) -> list[Union[FreeParameterExpression, float]]: - """Returns the parameters associated with the object, either unbound free parameter - expressions or bound values. - - Returns: - list[Union[FreeParameterExpression, float]]: The free parameter expressions - or fixed values associated with the object. - """ - return [self._gamma] - - 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. - - Returns: - DampingNoise: A new Noise object of the same type with the requested - parameters bound. - - Raises: - NotImplementedError: Subclasses should implement this function. - """ - raise NotImplementedError - - def to_dict(self) -> dict: - """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. - """ - return { - "__class__": self.__class__.__name__, - "gamma": _parameter_to_dict(self.gamma), - "qubit_count": self.qubit_count, - "ascii_symbols": self.ascii_symbols, - } - - -class GeneralizedAmplitudeDampingNoise(DampingNoise): - """Class `GeneralizedAmplitudeDampingNoise` represents the generalized amplitude damping - noise channel on N qubits parameterized by gamma and probability. - """ - - def __init__( - self, - gamma: Union[FreeParameterExpression, float], - probability: Union[FreeParameterExpression, float], - 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 - excited by the environment. - qubit_count (Optional[int]): The number of qubits to apply noise. - ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when - 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: - ValueError: If - `qubit_count` < 1, - `ascii_symbols` is `None`, - `len(ascii_symbols)` != `qubit_count`, - `probability` or `gamma` is not `float` or `FreeParameterExpression`, - `probability` > 1.0 or `probability` < 0.0, - or `gamma` > 1.0 or `gamma` < 0.0. - """ - super().__init__(gamma=gamma, qubit_count=qubit_count, ascii_symbols=ascii_symbols) - - if not isinstance(probability, FreeParameterExpression): - _validate_param_value(probability, "probability") - self._probability = probability - - @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. - """ - return self._probability - - def __repr__(self): - return ( - f"{self.name}('gamma': {self.gamma}, " - f"'probability': {self.probability}, " - f"'qubit_count': {self.qubit_count})" - ) - - def __str__(self): - return f"{self.name}({self.gamma}, {self.probability})" - - @property - def parameters(self) -> list[Union[FreeParameterExpression, float]]: - """Returns the parameters associated with the object, either unbound free parameter - expressions or bound values. - - Parameters are in the order [gamma, probability] - - Returns: - list[Union[FreeParameterExpression, float]]: The free parameter expressions - or fixed values associated with the object. - """ - return [self.gamma, self.probability] - - def __eq__(self, other: GeneralizedAmplitudeDampingNoise): - if isinstance(other, GeneralizedAmplitudeDampingNoise): - return ( - self.name == other.name - and self.gamma == other.gamma - and self.probability == other.probability - ) - return False - - def to_dict(self) -> dict: - """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. - """ - return { - "__class__": self.__class__.__name__, - "gamma": _parameter_to_dict(self.gamma), - "probability": _parameter_to_dict(self.probability), - "qubit_count": self.qubit_count, - "ascii_symbols": self.ascii_symbols, - } - - -def _validate_param_value( - parameter: Union[FreeParameterExpression, float], param_name: str, maximum: float = 1.0 -) -> None: - """Validates the value of a given parameter. - - Args: - parameter (Union[FreeParameterExpression, float]): The parameter to validate - param_name (str): The name of the parameter - maximum (float): The maximum value of the parameter. Default: 1.0 - """ - if not isinstance(parameter, float): - raise TypeError(f"{param_name} must be float type") - if not (maximum >= parameter >= 0.0): - 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]: - """Converts a parameter to a dictionary if it's a FreeParameter, otherwise returns the float. - - Args: - parameter(Union[FreeParameter, float]): The parameter to convert. - - Returns: - Union[dict, float]: A dictionary representation of a FreeParameter if the parameter - is a FreeParameter, otherwise returns the float. - """ - if isinstance(parameter, FreeParameter): - return parameter.to_dict() - return parameter diff --git a/src/braket/circuits/noise_helpers.py b/src/braket/circuits/noise_helpers.py deleted file mode 100644 index a73b7f33..00000000 --- a/src/braket/circuits/noise_helpers.py +++ /dev/null @@ -1,323 +0,0 @@ -# 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.abc import Iterable -from typing import TYPE_CHECKING, Any, Optional, Union - -import numpy as np - -from braket.circuits.gate import Gate -from braket.circuits.instruction import Instruction -from braket.circuits.moments import Moments -from braket.circuits.noise import Noise -from braket.circuits.quantum_operator_helpers import is_unitary -from braket.registers.qubit_set import QubitSet, QubitSetInput - -if TYPE_CHECKING: # pragma: no cover - from braket.circuits.circuit import Circuit - - -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. - """ - 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" - " 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. - - Returns: - list[Any]: The item wrapped in a list. - """ - if an_item is not None and not isinstance(an_item, list): - an_item = [an_item] - return an_item - - -def check_noise_target_gates(noise: Noise, target_gates: Iterable[type[Gate]]) -> None: - """Helper function to check - 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 - List of Gate classes which `noise` is applied to. - """ - if not all(isinstance(g, type) and issubclass(g, Gate) for g in target_gates): - raise TypeError("All elements in target_gates must be an instance of the Gate class") - - if noise.qubit_count > 1: - for g in target_gates: - fixed_qubit_count = g.fixed_qubit_count() - if fixed_qubit_count is NotImplemented: - raise ValueError( - f"Target gate {g} can be instantiated on a variable number of qubits," - " but noise can only target gates with fixed qubit counts." - ) - if fixed_qubit_count != noise.qubit_count: - raise ValueError( - f"Target gate {g} acts on {fixed_qubit_count} qubits," - f" but {noise} acts on {noise.qubit_count} qubits." - ) - - -def check_noise_target_unitary(noise: Noise, target_unitary: np.ndarray) -> None: - """Helper function to check - 1. whether the input matrix is a np.ndarray type; - 2. whether the target_unitary is a unitary; - - Args: - 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") - - if not is_unitary(target_unitary): - raise ValueError("target_unitary must be a unitary") - - -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. - - Args: - circuit (Circuit): A circuit where `noise` is to be checked. - target_qubits (Optional[QubitSetInput]): Index or indices of qubit(s). - - Returns: - QubitSet: The target qubits. - """ - if target_qubits is None: - target_qubits = circuit.qubits - else: - 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 any(q < 0 for q in target_qubits): - raise ValueError("target_qubits must contain only non-negative integers.") - - target_qubits = QubitSet(target_qubits) - - return 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. - - When `noise.qubit_count` == 1, `noise` is added to all qubits in `target_qubits`. - - When `noise.qubit_count` > 1, `noise.qubit_count` must be the same as the length of - `target_qubits`. - - Args: - 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. - position (str): The position to add the noise to. May be 'initialization' or - 'readout_noise'. - - Returns: - Circuit: modified circuit. - """ - noise_instructions = [] - 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 - 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") - - # add existing instructions - for moment_key in circuit.moments: - instruction = circuit.moments[moment_key] - # if the instruction is noise instruction - if isinstance(instruction.operator, Noise): - new_moments.add_noise(instruction, moment_key.moment_type, moment_key.noise_index) - # if the instruction is a gate instruction - else: - new_moments.add([instruction], moment_key.noise_index) - - if position == "readout": - for noise in noise_instructions: - new_moments.add_noise(noise, "readout_noise") - - circuit._moments = new_moments - - return circuit - - -def _apply_noise_to_gates_helper( - noise: Iterable[type[Noise]], - target_qubits: QubitSet, - instruction: Instruction, - noise_index: int, - intersection: QubitSet, - noise_applied: bool, - new_noise_instruction: Iterable, -) -> tuple[Iterable[Instruction], int, bool]: - """Helper function to work out the noise instructions to be attached to a gate. - - Args: - noise (Iterable[type[Noise]]): Noise channel(s) to be applied - to the circuit. - target_qubits (QubitSet): Index or indices of qubits which `noise` is applied to. - instruction (Instruction): Instruction of the gate which `noise` is applied to. - noise_index (int): The number of noise channels applied to the gate. - intersection (QubitSet): Intersection of target_qubits and the qubits associated - with the gate. - noise_applied (bool): Whether noise is applied or not. - new_noise_instruction (Iterable): current new noise instructions to be attached - to the circuit. - - Returns: - tuple[Iterable[Instruction], int, bool]: A tuple of three values: - 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 - """ - for noise_channel in noise: - if noise_channel.qubit_count == 1: - for qubit in intersection: - # apply noise to the qubit if it is in target_qubits - noise_index += 1 - new_noise_instruction.append((Instruction(noise_channel, qubit), 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 - - -def apply_noise_to_gates( - circuit: Circuit, - noise: Iterable[type[Noise]], - target_gates: Union[Iterable[type[Gate]], np.ndarray], - target_qubits: QubitSet, -) -> Circuit: - """Apply noise after target gates in target qubits. - - When `noise.qubit_count` == 1, `noise` is applied to target_qubits after `target_gates`. - - When `noise.qubit_count` > 1, all elements in `target_gates`, if is given, must have - the same number of qubits as `noise.qubit_count`. - - Args: - 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 - which `noise` is applied to. - target_qubits (QubitSet): Index or indices of qubits which `noise` is applied to. - - Returns: - Circuit: modified circuit. - - Raises: - 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. - 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 - - for moment_key in circuit.moments: - instruction = circuit.moments[moment_key] - - # add the instruction to new_moments if it is noise instruction - if isinstance(instruction.operator, Noise): - new_moments.add_noise(instruction, moment_key.moment_type, moment_key.noise_index) - - # if the instruction is a gate instruction - else: - new_noise_instruction = [] - noise_index = moment_key.noise_index - if isinstance(target_gates, np.ndarray): - if (instruction.operator.name == "Unitary") and ( - np.array_equiv(instruction.operator._matrix, target_gates) - ): - intersection = list(set(instruction.target) & set(target_qubits)) - ( - new_noise_instruction, - noise_index, - noise_applied, - ) = _apply_noise_to_gates_helper( - noise, - target_qubits, - instruction, - noise_index, - intersection, - noise_applied, - new_noise_instruction, - ) - - elif (target_gates is None) or ( - instruction.operator.name in [g.__name__ for g in target_gates] - ): - intersection = list(set(instruction.target) & set(target_qubits)) - new_noise_instruction, noise_index, noise_applied = _apply_noise_to_gates_helper( - noise, - target_qubits, - instruction, - noise_index, - intersection, - noise_applied, - new_noise_instruction, - ) - - # add the gate and gate noise instructions to new_moments - new_moments.add([instruction], noise_index=noise_index) - for instruction, noise_index in new_noise_instruction: - new_moments.add_noise(instruction, "gate_noise", noise_index) - - no_noise_applied_warning(noise_applied) - circuit._moments = new_moments - return circuit diff --git a/src/braket/circuits/noise_model/__init__.py b/src/braket/circuits/noise_model/__init__.py deleted file mode 100644 index 717e5057..00000000 --- a/src/braket/circuits/noise_model/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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.noise_model.circuit_instruction_criteria import ( # noqa: F401 - CircuitInstructionCriteria, -) -from braket.circuits.noise_model.criteria import ( # noqa: F401 - Criteria, - CriteriaKey, - CriteriaKeyResult, -) -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.observable_criteria import ObservableCriteria # noqa: F401 -from braket.circuits.noise_model.qubit_initialization_criteria import ( # noqa: F401 - QubitInitializationCriteria, -) -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/circuit_instruction_criteria.py b/src/braket/circuits/noise_model/circuit_instruction_criteria.py deleted file mode 100644 index 4dceeb4f..00000000 --- a/src/braket/circuits/noise_model/circuit_instruction_criteria.py +++ /dev/null @@ -1,56 +0,0 @@ -# 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 abc import abstractmethod -from typing import Optional, Union - -from braket.circuits.instruction import Instruction -from braket.circuits.noise_model.criteria import Criteria -from braket.registers.qubit_set import QubitSetInput - - -class CircuitInstructionCriteria(Criteria): - """Criteria that implement these methods may be used to determine gate noise.""" - - @abstractmethod - def instruction_matches(self, instruction: Instruction) -> bool: - """Returns True if an Instruction matches the criteria. - - Args: - instruction (Instruction): An Instruction to match. - - Raises: - NotImplementedError: Not implemented. - - Returns: - bool: True if an Instruction matches the criteria. - """ - raise NotImplementedError - - @staticmethod - 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. - - Args: - qubits (Optional[set[Union[int, tuple[int]]]]): The qubits provided to the criteria. - target (QubitSetInput): Targets of an instruction. - - Returns: - bool: True if the provided target should be matched by the given qubits. - """ - if qubits is None: - return True - target = [int(item) for item in target] - 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 deleted file mode 100644 index 88921134..00000000 --- a/src/braket/circuits/noise_model/criteria.py +++ /dev/null @@ -1,114 +0,0 @@ -# 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 collections.abc import Iterable -from enum import Enum -from typing import Any, Union - - -class CriteriaKey(str, Enum): - """Specifies the types of keys that a criteria may use to match an instruction, - observable, etc. - """ - - QUBIT = "QUBIT" - GATE = "GATE" - UNITARY_GATE = "UNITARY_GATE" - OBSERVABLE = "OBSERVABLE" - - -class CriteriaKeyResult(str, Enum): - """The get_keys() method may return this enum instead of actual keys for - a given criteria key type. - """ - - ALL = "ALL" - - -class Criteria(ABC): - """Represents conditions that need to be met for a noise to apply to a circuit.""" - - @abstractmethod - def applicable_key_types(self) -> Iterable[CriteriaKey]: - """Returns the relevant set of keys for the Criteria - - This informs what the Criteria operates on and can be used to optimize - which Criteria is relevant. - - Returns: - Iterable[CriteriaKey]: The relevant set of keys for the Criteria. - """ - raise NotImplementedError - - @abstractmethod - def get_keys(self, key_type: CriteriaKey) -> Union[CriteriaKeyResult, set[Any]]: - """Returns a set of key for a given key type. - - Args: - key_type (CriteriaKey): The criteria key type. - - Returns: - Union[CriteriaKeyResult, set[Any]]: Returns a set of keys for a key type. The - actual returned keys will depend on the CriteriaKey. If the provided key type - is not relevant the returned list will be empty. If the provided key type is - relevant for all possible inputs, the string CriteriaKeyResult.ALL will be returned. - """ - raise NotImplementedError - - def __eq__(self, other: Criteria): - if not isinstance(other, Criteria): - return NotImplemented - if self.applicable_key_types() != other.applicable_key_types(): - return False - 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: - """Converts this Criteria object into a dict representation - - Returns: - dict: A dictionary object representing the Criteria. - """ - raise NotImplementedError - - @classmethod - def from_dict(cls, criteria: dict) -> Criteria: - """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. - - Returns: - Criteria: An object of this class that corresponds to the passed in dictionary. - """ - if "__class__" in criteria: - criteria_name = criteria["__class__"] - criteria_cls = getattr(Criteria, criteria_name) - return criteria_cls.from_dict(criteria) - raise NotImplementedError - - @classmethod - def register_criteria(cls, criteria: type[Criteria]) -> None: - """Register a criteria implementation by adding it into the Criteria class. - - Args: - criteria (type[Criteria]): Criteria class to register. - """ - setattr(cls, criteria.__name__, criteria) diff --git a/src/braket/circuits/noise_model/criteria_input_parsing.py b/src/braket/circuits/noise_model/criteria_input_parsing.py deleted file mode 100644 index 456867ce..00000000 --- a/src/braket/circuits/noise_model/criteria_input_parsing.py +++ /dev/null @@ -1,87 +0,0 @@ -# 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 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]]: - """Processes the quantum operator input to __init__ to validate and return a set of - QuantumOperators. - - Args: - operators (Union[QuantumOperator, Iterable[QuantumOperator]]): QuantumOperator input. - - Returns: - Optional[set[QuantumOperator]]: The set of relevant QuantumOperators or None if none - is specified. - - Throws: - ValueError: If no quantum operator are provided, if the quantum operator don't all operate - on the same number of qubits. - """ - if not operators: - return None - if not isinstance(operators, Iterable): - return {operators} - fixed_qubit_counts = {operator.fixed_qubit_count() for operator in operators} - if len(fixed_qubit_counts) != 1: - raise ValueError("All operators in a criteria must operate on the same number of qubits.") - return set(operators) - - -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. - - Args: - qubits (Optional[QubitSetInput]): Qubit input. - expected_qubit_count (Optional[int]): The expected number of qubits that the input - gates operates on. If the value is non-zero, this method will validate that the - expected qubit count matches the actual qubit count. Default is 0. - - Returns: - Optional[set[Union[int, tuple[int]]]]: The set of qubit targets, or None if no qubits - are specified. - """ - if qubits is None: - return None - if not isinstance(qubits, Iterable): - return {int(qubits)} - if len(qubits) == 0: - return None - types = {type(item) for item in qubits} - if len(types) != 1: - raise TypeError("Qubit targets must be all the same type.") - qubit_count = ( - expected_qubit_count if expected_qubit_count is not None and expected_qubit_count > 0 else 1 - ) - if isinstance(qubits[0], Iterable): - qubit_count = ( - expected_qubit_count - if expected_qubit_count is not None and expected_qubit_count > 0 - else len(qubits[0]) - ) - target_set_all_same = all(len(item) == qubit_count for item in qubits) - if not target_set_all_same: - raise ValueError(f"Qubits must all target {qubit_count}-qubit operations.") - if qubit_count == 1: - return {item[0] for item in qubits} - return {tuple(item) for item in qubits} - return {tuple(qubits)} if qubit_count > 1 else set(qubits) diff --git a/src/braket/circuits/noise_model/gate_criteria.py b/src/braket/circuits/noise_model/gate_criteria.py deleted file mode 100644 index 7870e9b6..00000000 --- a/src/braket/circuits/noise_model/gate_criteria.py +++ /dev/null @@ -1,143 +0,0 @@ -# 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.gate import Gate -from braket.circuits.instruction import Instruction -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_operator_input, - parse_qubit_input, -) -from braket.registers.qubit_set import QubitSetInput - - -class GateCriteria(CircuitInstructionCriteria): - """This class models noise Criteria based on named Braket SDK Gates.""" - - def __init__( - self, - gates: Optional[Union[Gate, Iterable[Gate]]] = None, - qubits: Optional[QubitSetInput] = None, - ): - """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 - must have the same fixed_qubit_count(). Optional. If gates are not provided - this matcher will match on all gates. - qubits (Optional[QubitSetInput]): A set of relevant qubits. If no qubits - are provided, all (possible) qubits are considered to be relevant. - - Raises: - ValueError: If the gates don't all operate on the same number of qubits, or if - qubits are not valid targets for the provided gates. - """ - self._gates = parse_operator_input(gates) - expected_qubit_count = next(iter(self._gates)).fixed_qubit_count() if self._gates else 0 - self._qubits = parse_qubit_input(qubits, expected_qubit_count) - - def __str__(self): - gate_names = {gate.__name__ for gate in self._gates} if self._gates is not None else None - return f"{self.__class__.__name__}({gate_names}, {self._qubits})" - - def __repr__(self): - gate_names = {gate.__name__ for gate in self._gates} if self._gates is not None else None - 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. - """ - return [CriteriaKey.QUBIT, CriteriaKey.GATE] - - 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: - GATE will return a set of Gate classes that are relevant to this Criteria. - 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 list. - """ - if key_type == CriteriaKey.GATE: - return CriteriaKeyResult.ALL if self._gates is None else self._gates - 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 - gates = [gate.__name__ for gate in self._gates] if self._gates is not None else None - return { - "__class__": self.__class__.__name__, - "gates": gates, - "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 operator is one of the Gates provided in the constructor and - the target is a qubit (or set of qubits) provided in the constructor. - If gates were not provided in the constructor, then this method will accept any Gate. - If qubits were not provided in the constructor, then this method will accept any - Instruction target. - """ - if isinstance(instruction, Iterable): - return False - if not isinstance(instruction.operator, Gate): - return False - if self._gates is not None and type(instruction.operator) not in self._gates: - 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 GateCriteria. - - Returns: - Criteria: A deserialized GateCriteria represented by the passed in - serialized data. - """ - gates = ( - [getattr(Gate, name) for name in criteria["gates"]] - if criteria["gates"] is not None - else None - ) - return GateCriteria(gates, criteria["qubits"]) - - -Criteria.register_criteria(GateCriteria) diff --git a/src/braket/circuits/noise_model/initialization_criteria.py b/src/braket/circuits/noise_model/initialization_criteria.py deleted file mode 100644 index 4bf29a35..00000000 --- a/src/braket/circuits/noise_model/initialization_criteria.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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 abc import abstractmethod - -from braket.circuits.noise_model.criteria import Criteria -from braket.registers.qubit_set import QubitSetInput - - -class InitializationCriteria(Criteria): - """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. - - Args: - qubits (QubitSetInput): A qubit or set of qubits that may match the criteria. - - Returns: - QubitSetInput: The subset of passed qubits that match the criteria. - """ - raise NotImplementedError diff --git a/src/braket/circuits/noise_model/noise_model.py b/src/braket/circuits/noise_model/noise_model.py deleted file mode 100644 index e8a60307..00000000 --- a/src/braket/circuits/noise_model/noise_model.py +++ /dev/null @@ -1,397 +0,0 @@ -# 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 defaultdict -from dataclasses import dataclass -from typing import Optional - -from braket.circuits.circuit import Circuit -from braket.circuits.gate import Gate -from braket.circuits.instruction import Instruction -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.result_type_criteria import ResultTypeCriteria -from braket.circuits.result_types import ObservableResultType -from braket.registers.qubit_set import QubitSetInput - - -@dataclass -class NoiseModelInstruction: - """Represents a single instruction for a Noise Model.""" - - noise: Noise - criteria: Criteria - - def __init__(self, noise: Noise, criteria: Criteria): - if not isinstance(noise, Noise): - raise ValueError(f"{noise} must be a Noise type.") - if not isinstance(criteria, Criteria): - raise ValueError(f"{criteria} must be a Criteria type.") - self.noise = noise - self.criteria = criteria - - def __repr__(self): - return f"NoiseModelInstruction(noise={self.noise}, criteria={self.criteria})" - - def __str__(self): - return f"{self.noise}, {self.criteria}" - - def to_dict(self) -> dict: - """Converts this object to a dictionary.""" - return {"noise": self.noise.to_dict(), "criteria": self.criteria.to_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. - - Args: - noise_model_item (dict): A dictionary representation of an object of this class. - - Returns: - NoiseModelInstruction: An object of this class that corresponds - to the passed in dictionary. - """ - return NoiseModelInstruction( - noise=Noise.from_dict(noise_model_item["noise"]), - criteria=Criteria.from_dict(noise_model_item["criteria"]), - ) - - -@dataclass -class NoiseModelInstructions: - """Represents the instructions in a noise model, separated by type.""" - - initialization_noise: list[NoiseModelInstruction] - gate_noise: list[NoiseModelInstruction] - readout_noise: list[NoiseModelInstruction] - - -class NoiseModel: - """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 - a phase flip. - """ - - def __init__(self, instructions: list[NoiseModelInstruction] = None): - self._instructions = instructions or [] - - def __repr__(self): - return str({"instructions": list(self._instructions)}) - - def __str__(self): - instructions = self.get_instructions_by_type() - all_strings = [] - all_strings.extend( - NoiseModel._items_to_string("Initialization Noise:", instructions.initialization_noise) - ) - all_strings.extend(NoiseModel._items_to_string("Gate Noise:", instructions.gate_noise)) - all_strings.extend( - NoiseModel._items_to_string("Readout Noise:", instructions.readout_noise) - ) - return "\n".join(all_strings) - - @property - def instructions(self) -> list[NoiseModelInstruction]: - """List all the noise in the NoiseModel. - - Returns: - list[NoiseModelInstruction]: The noise model instructions. - """ - return self._instructions - - def add_noise(self, noise: Noise, criteria: Criteria) -> NoiseModel: - """Modifies the NoiseModel to add noise with a given Criteria. - - Args: - noise (Noise): The noise to add. - criteria (Criteria): The criteria that determines when the noise will be applied. - - Returns: - NoiseModel: This NoiseModel object. - """ - return self._add_instruction(NoiseModelInstruction(noise, criteria)) - - def insert_noise(self, index: int, noise: Noise, criteria: Criteria) -> NoiseModel: - """Modifies the NoiseModel to insert a noise with a given Criteria at particular position. - - Args: - index (int): The index at which to insert. - noise (Noise): The noise to insert. - criteria (Criteria): The criteria that determines when the noise will be applied. - - Returns: - NoiseModel: This NoiseModel object. - """ - self._instructions.insert(index, NoiseModelInstruction(noise, criteria)) - return self - - def _add_instruction(self, instruction: NoiseModelInstruction) -> NoiseModel: - """Modifies the NoiseModel to add noise with a given Criteria. - - Args: - instruction (NoiseModelInstruction): The noise model instruction to add. - - Returns: - NoiseModel: This NoiseModel object. - """ - self._instructions.append(instruction) - return self - - def remove_noise(self, index: int) -> NoiseModel: - """Removes the noise and corresponding criteria from the NoiseModel at the given index. - - Args: - index (int): The index of the instruction to remove. - - Returns: - NoiseModel: This NoiseModel object. - - Throws: - IndexError: If the provided index is not less than the number of noise (as returned - from items()). - """ - self._instructions.pop(index) - return self - - def get_instructions_by_type(self) -> NoiseModelInstructions: - """Returns the noise in this model by noise type. - - Returns: - NoiseModelInstructions: The noise model instructions. - """ - init_noise = [] - gate_noise = [] - readout_noise = [] - for item in self._instructions: - if isinstance(item.criteria, InitializationCriteria): - init_noise.append(item) - elif isinstance(item.criteria, CircuitInstructionCriteria): - gate_noise.append(item) - elif isinstance(item.criteria, ResultTypeCriteria): - readout_noise.append(item) - return NoiseModelInstructions( - initialization_noise=init_noise, - gate_noise=gate_noise, - readout_noise=readout_noise, - ) - - def from_filter( - self, - qubit: Optional[QubitSetInput] = None, - 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 - specified, the returned NoiseModel will be the same as this one. - - Args: - qubit (Optional[QubitSetInput]): The qubit to filter. Default is None. - If not None, the returned NoiseModel will only have Noise that might be applicable - to the passed qubit (or qubit list, if the criteria acts on a multi-qubit Gate). - gate (Optional[Gate]): The gate to filter. Default is None. If not None, - the returned NoiseModel will only have Noise that might be applicable - to the passed Gate. - noise (Optional[type[Noise]]): The noise class to filter. Default is None. - If not None, the returned NoiseModel will only have noise that is of the same - class as the given noise class. - - Returns: - NoiseModel: A noise model containing Noise and Criteria that are applicable for - the given filter. - """ - new_model = NoiseModel() - for item in self._instructions: - if noise is not None and not isinstance(item.noise, noise): - continue - if gate is not None: - gate_keys = item.criteria.get_keys(CriteriaKey.GATE) - if gate_keys != CriteriaKeyResult.ALL and gate not in gate_keys: - continue - if qubit is not None: - qubit_keys = item.criteria.get_keys(CriteriaKey.QUBIT) - if qubit_keys != CriteriaKeyResult.ALL and qubit not in qubit_keys: - continue - new_model.add_noise(item.noise, item.criteria) - 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` - 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. - - Args: - circuit (Circuit): a circuit to apply `noise` to. - - Returns: - Circuit: A new circuit that's a `noisy` version of the passed in circuit. - """ - instructions = self.get_instructions_by_type() - new_circuit = NoiseModel._apply_gate_noise(circuit, instructions.gate_noise) - new_circuit = NoiseModel._apply_init_noise(new_circuit, instructions.initialization_noise) - return NoiseModel._apply_readout_noise(new_circuit, instructions.readout_noise) - - def to_dict(self) -> dict: - """Converts this object to a dictionary.""" - return {"instructions": [item.to_dict() for item in self._instructions]} - - @classmethod - def _apply_gate_noise( - cls, - 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 - circuit. - - Args: - circuit (Circuit): a circuit to apply `noise` to. - gate_noise_instructions (list[NoiseModelInstruction]): a list of gate noise - instructions to apply to the circuit. - - Returns: - Circuit: A new circuit that's a `noisy` version of the passed in circuit. The targets - set will be populated with the list of targets in the new circuit. - """ - 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)) - for result_type in circuit.result_types: - new_circuit.add_result_type(result_type) - return new_circuit - - @classmethod - def _apply_init_noise( - cls, - circuit: Circuit, - init_noise_instructions: list[NoiseModelInstruction], - ) -> 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. - init_noise_instructions (list[NoiseModelInstruction]): A list of initialization noise - model instructions. - - Returns: - Circuit: The passed in circuit, with the initialization noise applied. - """ - if not init_noise_instructions: - return circuit - for item in init_noise_instructions: - qubits = item.criteria.qubit_intersection(circuit.qubits) - if len(qubits) > 0: - circuit.apply_initialization_noise(item.noise, list(qubits)) - return circuit - - @classmethod - def _apply_readout_noise( - cls, - circuit: Circuit, - readout_noise_instructions: list[NoiseModelInstruction], - ) -> 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. - readout_noise_instructions (list[NoiseModelInstruction]): The list of readout noise - to apply. - - Returns: - Circuit: The passed in circuit, with the readout noise applied. - """ - if not readout_noise_instructions or not circuit.result_types: - return circuit - return _apply_noise_on_observable_result_types(circuit, readout_noise_instructions) - - @classmethod - def _items_to_string( - cls, instructions_title: str, instructions: list[NoiseModelInstruction] - ) -> list[str]: - """Creates a string representation of a list of instructions. - - Args: - instructions_title (str): The title for this list of instructions. - instructions (list[NoiseModelInstruction]): A list of instructions. - - Returns: - list[str]: A list of string representations of the passed instructions. - """ - results = [] - if instructions: - results.append(instructions_title) - results.extend(f" {item}" for item in instructions) - return results - - @classmethod - def from_dict(cls, noise_dict: dict) -> NoiseModel: - """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. - - Returns: - NoiseModel: An object of this class that corresponds to the passed in dictionary. - """ - model = NoiseModel() - all( - model._add_instruction(NoiseModelInstruction.from_dict(item)) - for item in noise_dict["instructions"] - ) - return model - - -def _apply_noise_on_observable_result_types( - circuit: Circuit, readout_noise_instructions: list[NoiseModelInstruction] -) -> Circuit: - """Applies readout noise based on the observable result types in the circuit. Each applicable - Noise will be applied only once to a target in the ObservableResultType. - - 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 applied. - """ - result_types = circuit.result_types - noise_to_apply = defaultdict(set) - for result_type in 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): - for target_qubit in target_qubits: - noise_to_apply[target_qubit].add(item_index) - for qubit in noise_to_apply: - for noise_item_index in noise_to_apply[qubit]: - item = readout_noise_instructions[noise_item_index] - circuit.apply_readout_noise(item.noise, qubit) - return circuit diff --git a/src/braket/circuits/noise_model/observable_criteria.py b/src/braket/circuits/noise_model/observable_criteria.py deleted file mode 100644 index 5cb510f2..00000000 --- a/src/braket/circuits/noise_model/observable_criteria.py +++ /dev/null @@ -1,156 +0,0 @@ -# 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.noise_model.criteria import Criteria, CriteriaKey, CriteriaKeyResult -from braket.circuits.noise_model.criteria_input_parsing import ( - parse_operator_input, - parse_qubit_input, -) -from braket.circuits.noise_model.result_type_criteria import ResultTypeCriteria -from braket.circuits.observable import Observable -from braket.circuits.result_type import ObservableResultType, ResultType -from braket.registers.qubit_set import QubitSetInput - - -class ObservableCriteria(ResultTypeCriteria): - """This class models noise Criteria based on the Braket SDK Observable classes.""" - - def __init__( - self, - observables: Optional[Union[Observable, Iterable[Observable]]] = None, - qubits: Optional[QubitSetInput] = None, - ): - """Creates Observable-based Criteria. See instruction_matches() for more details. - - Args: - observables (Optional[Union[Observable, Iterable[Observable]]]): A set of relevant - Observables. Observables must only operate on a single qubit. Optional. If - observables are not specified, this criteria will match on any observable. - qubits (Optional[QubitSetInput]): A set of relevant qubits. If no qubits - are provided, all (possible) qubits are considered to be relevant. - - Throws: - ValueError: If the operators operate on more than one qubit. - """ - self._observables = parse_operator_input(observables) - self._qubits = parse_qubit_input(qubits, 1) - - def __str__(self): - observables_names = ( - {observable.__name__ for observable in self._observables} - if self._observables is not None - else None - ) - return f"{self.__class__.__name__}({observables_names}, {self._qubits})" - - def __repr__(self): - observables_names = ( - {observable.__name__ for observable in self._observables} - if self._observables is not None - else None - ) - 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. - """ - return [CriteriaKey.OBSERVABLE, 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: - OBSERVABLE will return a set of Observable classes that are relevant to this Criteria, - or CriteriaKeyResult.ALL if the Criteria is relevant for all (possible) observables. - 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.OBSERVABLE: - return CriteriaKeyResult.ALL if self._observables is None else set(self._observables) - 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. - """ - observables = ( - [observable.__name__ for observable in self._observables] - if self._observables is not None - else None - ) - qubits = list(self._qubits) if self._qubits is not None else None - return { - "__class__": self.__class__.__name__, - "observables": observables, - "qubits": qubits, - } - - def result_type_matches(self, result_type: ResultType) -> bool: - """Returns true if a result type matches the criteria. - - 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. - If observables were not provided in the constructor, then this method will accept any - Observable. - If qubits were not provided in the constructor, then this method will accept any - result type target. - """ - if not isinstance(result_type, ObservableResultType): - return False - if self._observables is not None and type(result_type.observable) not in self._observables: - return False - if self._qubits is None: - return True - target = list(result_type.target) - return target[0] in self._qubits if target else True - - @classmethod - def from_dict(cls, criteria: dict) -> Criteria: - """Deserializes a dictionary into a Criteria object. - - Args: - criteria (dict): A dictionary representation of a GateCriteria. - - Returns: - Criteria: A deserialized GateCriteria represented by the passed in - serialized data. - """ - observables = ( - [getattr(Observable, name) for name in criteria["observables"]] - if criteria["observables"] is not None - else None - ) - return ObservableCriteria(observables, criteria["qubits"]) - - -Criteria.register_criteria(ObservableCriteria) diff --git a/src/braket/circuits/noise_model/qubit_initialization_criteria.py b/src/braket/circuits/noise_model/qubit_initialization_criteria.py deleted file mode 100644 index 26594ca6..00000000 --- a/src/braket/circuits/noise_model/qubit_initialization_criteria.py +++ /dev/null @@ -1,106 +0,0 @@ -# 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.noise_model.criteria import Criteria, CriteriaKey, CriteriaKeyResult -from braket.circuits.noise_model.criteria_input_parsing import parse_qubit_input -from braket.circuits.noise_model.initialization_criteria import InitializationCriteria -from braket.registers.qubit_set import QubitSet, QubitSetInput - - -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. - - 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) - - 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]: - """Gets the QUBIT criteria key. - - Returns: - Iterable[CriteriaKey]: This Criteria operates on Qubits, but is valid for all Gates. - """ - 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 qubit_intersection(self, qubits: QubitSetInput) -> QubitSetInput: - """Returns subset of passed qubits that match the criteria. - - Args: - qubits (QubitSetInput): A qubit or set of qubits that may match the criteria. - - Returns: - QubitSetInput: The subset of passed qubits that match the criteria. - """ - target_qubit = QubitSet(qubits) - if self._qubits is None: - return target_qubit - return self._qubits.intersection(target_qubit) - - @classmethod - def from_dict(cls, criteria: dict) -> Criteria: - """Deserializes a dictionary into a Criteria object. - - Args: - criteria (dict): A dictionary representation of a QubitCriteria. - - Returns: - Criteria: A deserialized QubitCriteria represented by the passed in - serialized data. - """ - return QubitInitializationCriteria(criteria["qubits"]) - - -Criteria.register_criteria(QubitInitializationCriteria) diff --git a/src/braket/circuits/noise_model/result_type_criteria.py b/src/braket/circuits/noise_model/result_type_criteria.py deleted file mode 100644 index 4d52c5c2..00000000 --- a/src/braket/circuits/noise_model/result_type_criteria.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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 abc import abstractmethod - -from braket.circuits.noise_model.criteria import Criteria -from braket.circuits.result_type import ResultType - - -class ResultTypeCriteria(Criteria): - """Criteria that implement these methods may be used to determine readout noise.""" - - @abstractmethod - def result_type_matches(self, result_type: ResultType) -> bool: - """Returns true if a result type matches the criteria. - - Args: - result_type (ResultType): A result type or list of result types to match. - - Returns: - bool: True if the result type matches the criteria. - """ - raise NotImplementedError diff --git a/src/braket/circuits/noise_model/unitary_gate_criteria.py b/src/braket/circuits/noise_model/unitary_gate_criteria.py deleted file mode 100644 index 34b348e2..00000000 --- a/src/braket/circuits/noise_model/unitary_gate_criteria.py +++ /dev/null @@ -1,124 +0,0 @@ -# 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.gates import Unitary -from braket.circuits.instruction import Instruction -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 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. - - 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. - - Raises: - ValueError: If unitary is not a Unitary type. - """ - if not isinstance(unitary, Unitary): - raise TypeError("unitary must be a Unitary type") - self._unitary = unitary - self._qubits = parse_qubit_input(qubits) - - def __str__(self): - return f"{self.__class__.__name__}(unitary={self._unitary}, qubits={self._qubits})" - - 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. - """ - return [CriteriaKey.QUBIT, CriteriaKey.UNITARY_GATE] - - 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: - UNITARY_GATE will return a set containing the bytes of the unitary matrix representing - the unitary gate. - 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 list. - """ - if key_type == CriteriaKey.UNITARY_GATE: - return {self._unitary.to_matrix().tobytes()} - 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__, - "unitary": self._unitary, - "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 operator is one of the Unitary gates provided in the - constructor 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 ignore - the Instruction target. - """ - if isinstance(instruction, Iterable): - return False - if instruction.operator != self._unitary: - 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 UnitaryGateCriteria. - - Returns: - Criteria: A deserialized UnitaryGateCriteria represented by the passed in - serialized data. - """ - return UnitaryGateCriteria(criteria["unitary"], criteria["qubits"]) - - -Criteria.register_criteria(UnitaryGateCriteria) diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py deleted file mode 100644 index a8829f1a..00000000 --- a/src/braket/circuits/noises.py +++ /dev/null @@ -1,1477 +0,0 @@ -# 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 itertools -from collections.abc import Iterable -from typing import Any, ClassVar, Union - -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 -from braket.circuits.gates import format_complex -from braket.circuits.instruction import Instruction -from braket.circuits.noise import ( - DampingNoise, - GeneralizedAmplitudeDampingNoise, - MultiQubitPauliNoise, - Noise, - PauliNoise, - SingleProbabilisticNoise, - SingleProbabilisticNoise_34, - SingleProbabilisticNoise_1516, -) -from braket.circuits.quantum_operator_helpers import ( - is_cptp, - verify_quantum_operator_matrix_dimensions, -) -from braket.circuits.serialization import OpenQASMSerializationProperties -from braket.registers.qubit import QubitInput -from braket.registers.qubit_set import QubitSet, QubitSetInput - -""" -To add a new Noise implementation: - 1. Implement the class and extend `Noise` - 2. Add a method with the `@circuit.subroutine(register=True)` decorator. Method name - will be added into the `Circuit` class. This method is the default way clients add - this noise to a circuit. - 3. Register the class with the `Noise` class via `Noise.register_noise()`. -""" - - -class BitFlip(SingleProbabilisticNoise): - 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} - - where - - .. math:: - I = \\left( - \\begin{matrix} - 1 & 0 \\\\ - 0 & 1 - \\end{matrix} - \\right) - - X = \\left( - \\begin{matrix} - 0 & 1 \\\\ - 1 & 0 - \\end{matrix} - \\right) - - p = \\text{probability} - - This noise channel is shown as `BF` in circuit diagrams. - """ - - def __init__(self, probability: Union[FreeParameterExpression, float]): - super().__init__( - probability=probability, - qubit_count=None, - ascii_symbols=[_ascii_representation("BF", [probability])], - ) - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.BitFlip.construct(target=target[0], probability=self.probability) - - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"#pragma braket noise bit_flip({self.probability}) {target_qubit}" - - 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. - """ - K0 = np.sqrt(1 - self.probability) * np.eye(2, dtype=complex) - K1 = np.sqrt(self.probability) * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) - return [K0, K1] - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def bit_flip(target: QubitSetInput, probability: float) -> Iterable[Instruction]: - """Registers this function into the circuit class. - - Args: - target (QubitSetInput): Target qubit(s) - probability (float): Probability of bit flipping. - - Returns: - Iterable[Instruction]: `Iterable` of BitFlip instructions. - - Examples: - >>> circ = Circuit().bit_flip(0, probability=0.1) - """ - return [ - Instruction(Noise.BitFlip(probability=probability), target=qubit) - for qubit in QubitSet(target) - ] - - 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 - parameters bound. - """ - return BitFlip(probability=_substitute_value(self._probability, **kwargs)) - - @classmethod - def from_dict(cls, noise: dict) -> Noise: - """Converts a dictionary representation of this class into this class. - - Args: - noise(dict): The dictionary representation of this noise. - - Returns: - Noise: A Noise object that represents the passed in dictionary. - """ - return BitFlip(probability=_parameter_from_dict(noise["probability"])) - - -Noise.register_noise(BitFlip) - - -class PhaseFlip(SingleProbabilisticNoise): - 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} - - where - - .. math:: - I = \\left( - \\begin{matrix} - 1 & 0 \\\\ - 0 & 1 - \\end{matrix} - \\right) - - Z = \\left( - \\begin{matrix} - 1 & 0 \\\\ - 0 & -1 - \\end{matrix} - \\right) - - p = \\text{probability} - - This noise channel is shown as `PF` in circuit diagrams. - """ - - def __init__(self, probability: Union[FreeParameterExpression, float]): - super().__init__( - probability=probability, - qubit_count=None, - ascii_symbols=[_ascii_representation("PF", [probability])], - ) - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.PhaseFlip.construct(target=target[0], probability=self.probability) - - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"#pragma braket noise phase_flip({self.probability}) {target_qubit}" - - 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. - """ - K0 = np.sqrt(1 - self.probability) * np.eye(2, dtype=complex) - K1 = np.sqrt(self.probability) * np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) - return [K0, K1] - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def phase_flip(target: QubitSetInput, probability: float) -> Iterable[Instruction]: - """Registers this function into the circuit class. - - Args: - target (QubitSetInput): Target qubit(s) - probability (float): Probability of phase flipping. - - Returns: - Iterable[Instruction]: `Iterable` of PhaseFlip instructions. - - Examples: - >>> circ = Circuit().phase_flip(0, probability=0.1) - """ - return [ - Instruction(Noise.PhaseFlip(probability=probability), target=qubit) - for qubit in QubitSet(target) - ] - - 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 - parameters bound. - """ - return PhaseFlip(probability=_substitute_value(self._probability, **kwargs)) - - @classmethod - def from_dict(cls, noise: dict) -> Noise: - """Converts a dictionary representation of this class into this class. - - Args: - noise(dict): The dictionary representation of this noise. - - Returns: - Noise: A Noise object that represents the passed in dictionary. - """ - return PhaseFlip(probability=_parameter_from_dict(noise["probability"])) - - -Noise.register_noise(PhaseFlip) - - -class PauliChannel(PauliNoise): - r"""Pauli noise channel which transforms a density matrix :math:`\\rho` according to: - - .. math:: - \\rho \\Rightarrow (1-probX-probY-probZ) \\rho - + probX X \\rho X^{\\dagger} - + probY Y \\rho Y^{\\dagger} - + probZ Z \\rho Z^{\\dagger} - - where - - .. math:: - I = \\left( - \\begin{matrix} - 1 & 0 \\\\ - 0 & 1 - \\end{matrix} - \\right) - - X = \\left( - \\begin{matrix} - 0 & 1 \\\\ - 1 & 0 - \\end{matrix} - \\right) - - Y = \\left( - \\begin{matrix} - 0 & -i \\\\ - i & 0 - \\end{matrix} - \\right) - - Z = \\left( - \\begin{matrix} - 1 & 0 \\\\ - 0 & -1 - \\end{matrix} - \\right) - - This noise channel is shown as `PC` in circuit diagrams. - """ - - def __init__( - self, - probX: Union[FreeParameterExpression, float], - probY: Union[FreeParameterExpression, float], - probZ: Union[FreeParameterExpression, float], - ): - """Creates PauliChannel noise. - - Args: - probX (Union[FreeParameterExpression, float]): X rotation probability. - probY (Union[FreeParameterExpression, float]): Y rotation probability. - probZ (Union[FreeParameterExpression, float]): Z rotation probability. - """ - super().__init__( - probX=probX, - probY=probY, - probZ=probZ, - qubit_count=None, - ascii_symbols=[_ascii_representation("PC", [probX, probY, probZ])], - ) - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.PauliChannel.construct( - target=target[0], probX=self.probX, probY=self.probY, probZ=self.probZ - ) - - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return ( - f"#pragma braket noise pauli_channel" - f"({self.probX}, {self.probY}, {self.probZ}) {target_qubit}" - ) - - 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. - """ - K0 = np.sqrt(1 - self.probX - self.probY - self.probZ) * np.eye(2, dtype=complex) - K1 = np.sqrt(self.probX) * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) - K2 = np.sqrt(self.probY) * 1j * np.array([[0.0, -1.0], [1.0, 0.0]], dtype=complex) - K3 = np.sqrt(self.probZ) * np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) - return [K0, K1, K2, K3] - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def pauli_channel( - target: QubitSetInput, probX: float, probY: float, probZ: float - ) -> Iterable[Instruction]: - """Registers this function into the circuit class. - - Args: - target (QubitSetInput): Target qubit(s) - probability list[float]: Probabilities for the Pauli X, Y and Z noise - happening in the Kraus channel. - probX (float): X rotation probability. - probY (float): Y rotation probability. - probZ (float): Z rotation probability. - - Returns: - Iterable[Instruction]: `Iterable` of PauliChannel instructions. - - Examples: - >>> circ = Circuit().pauli_channel(0, probX=0.1, probY=0.2, probZ=0.3) - """ - return [ - Instruction(Noise.PauliChannel(probX=probX, probY=probY, probZ=probZ), target=qubit) - for qubit in QubitSet(target) - ] - - def bind_values(self, **kwargs) -> Noise: - """Takes in parameters and attempts to assign them to values. - - Returns: - Noise: A new Noise object of the same type with the requested - parameters bound. - """ - probX = _substitute_value(self.probX, **kwargs) - probY = _substitute_value(self.probY, **kwargs) - probZ = _substitute_value(self.probZ, **kwargs) - - return PauliChannel(probX=probX, probY=probY, probZ=probZ) - - @classmethod - def from_dict(cls, noise: dict) -> Noise: - """Converts a dictionary representation of this class into this class. - - Args: - noise(dict): The dictionary representation of this noise. - - Returns: - Noise: A Noise object that represents the passed in dictionary. - """ - return PauliChannel( - probX=_parameter_from_dict(noise["probX"]), - probY=_parameter_from_dict(noise["probY"]), - probZ=_parameter_from_dict(noise["probZ"]), - ) - - -Noise.register_noise(PauliChannel) - - -class Depolarizing(SingleProbabilisticNoise_34): - r"""Depolarizing noise channel which transforms a density matrix :math:`\\rho` according to: - - .. math:: - \\rho \\Rightarrow (1-p) \\rho - + p/3 X \\rho X^{\\dagger} - + p/3 Y \\rho Y^{\\dagger} - + p/3 Z \\rho Z^{\\dagger} - - where - - .. math:: - I = \\left( - \\begin{matrix} - 1 & 0 \\\\ - 0 & 1 - \\end{matrix} - \\right) - - X = \\left( - \\begin{matrix} - 0 & 1 \\\\ - 1 & 0 - \\end{matrix} - \\right) - - Y = \\left( - \\begin{matrix} - 0 & -i \\\\ - i & 0 - \\end{matrix} - \\right) - - Z = \\left( - \\begin{matrix} - 1 & 0 \\\\ - 0 & -1 - \\end{matrix} - \\right) - - p = \\text{probability} - - This noise channel is shown as `DEPO` in circuit diagrams. - """ - - def __init__(self, probability: Union[FreeParameterExpression, float]): - super().__init__( - probability=probability, - qubit_count=None, - ascii_symbols=[_ascii_representation("DEPO", [probability])], - ) - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.Depolarizing.construct(target=target[0], probability=self.probability) - - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"#pragma braket noise depolarizing({self.probability}) {target_qubit}" - - 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. - """ - K0 = np.sqrt(1 - self.probability) * np.eye(2, dtype=complex) - K1 = np.sqrt(self.probability / 3) * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) - K2 = np.sqrt(self.probability / 3) * 1j * np.array([[0.0, -1.0], [1.0, 0.0]], dtype=complex) - K3 = np.sqrt(self.probability / 3) * np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) - return [K0, K1, K2, K3] - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def depolarizing(target: QubitSetInput, probability: float) -> Iterable[Instruction]: - """Registers this function into the circuit class. - - Args: - target (QubitSetInput): Target qubit(s) - probability (float): Probability of depolarizing. - - Returns: - Iterable[Instruction]: `Iterable` of Depolarizing instructions. - - Examples: - >>> circ = Circuit().depolarizing(0, probability=0.1) - """ - return [ - Instruction(Noise.Depolarizing(probability=probability), target=qubit) - for qubit in QubitSet(target) - ] - - def bind_values(self, **kwargs) -> Noise: - """Takes in parameters and attempts to assign them to values. - - Returns: - Noise: A new Noise object of the same type with the requested - parameters bound. - """ - return Depolarizing(probability=_substitute_value(self._probability, **kwargs)) - - @classmethod - def from_dict(cls, noise: dict) -> Noise: - """Converts a dictionary representation of this class into this class. - - Args: - noise(dict): The dictionary representation of this noise. - - Returns: - Noise: A Noise object that represents the passed in dictionary. - """ - return Depolarizing(probability=_parameter_from_dict(noise["probability"])) - - -Noise.register_noise(Depolarizing) - - -class TwoQubitDepolarizing(SingleProbabilisticNoise_1516): - r"""Two-Qubit Depolarizing noise channel which transforms a - density matrix :math:`\\rho` according to: - - .. math:: - \\rho \\Rightarrow (1-p) \\rho + p/15 ( - IX \\rho IX^{\\dagger} + IY \\rho IY^{\\dagger} + IZ \\rho IZ^{\\dagger} - + XI \\rho XI^{\\dagger} + XX \\rho XX^{\\dagger} + XY \\rho XY^{\\dagger} - + XZ \\rho XZ^{\\dagger} + YI \\rho YI^{\\dagger} + YX \\rho YX^{\\dagger} - + YY \\rho YY^{\\dagger} + YZ \\rho YZ^{\\dagger} + ZI \\rho ZI^{\\dagger} - + ZX \\rho ZX^{\\dagger} + ZY \\rho ZY^{\\dagger} + ZZ \\rho ZZ^{\\dagger}) - - where - - .. math:: - I = \\left( - \\begin{matrix} - 1 & 0 \\\\ - 0 & 1 - \\end{matrix} - \\right) - - X = \\left( - \\begin{matrix} - 0 & 1 \\\\ - 1 & 0 - \\end{matrix} - \\right) - - Y = \\left( - \\begin{matrix} - 0 & -i \\\\ - i & 0 - \\end{matrix} - \\right) - - Z = \\left( - \\begin{matrix} - 1 & 0 \\\\ - 0 & -1 - \\end{matrix} - \\right) - - p = \\text{probability} - - This noise channel is shown as `DEPO` in circuit diagrams. - """ - - def __init__(self, probability: Union[FreeParameterExpression, float]): - super().__init__( - probability=probability, - qubit_count=None, - ascii_symbols=[_ascii_representation("DEPO", [probability])] * 2, - ) - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.TwoQubitDepolarizing.construct( - targets=[target[0], target[1]], probability=self.probability - ) - - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ) -> str: - target_qubit_0 = serialization_properties.format_target(int(target[0])) - target_qubit_1 = serialization_properties.format_target(int(target[1])) - return ( - f"#pragma braket noise two_qubit_depolarizing({self.probability}) " - f"{target_qubit_0}, {target_qubit_1}" - ) - - 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) - SZ = np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) - - K_list_single = [SI, SX, SY, SZ] - K_list = [np.kron(i, j) for i in K_list_single for j in K_list_single] - - K_list[0] *= np.sqrt(1 - self._probability) - - K_list[1:] = [np.sqrt(self._probability / 15) * i for i in K_list[1:]] - - return K_list - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def two_qubit_depolarizing( - target1: QubitInput, target2: QubitInput, probability: float - ) -> Iterable[Instruction]: - """Registers this function into the circuit class. - - Args: - target1 (QubitInput): Target qubit 1. - target2 (QubitInput): Target qubit 2. - probability (float): Probability of two-qubit depolarizing. - - Returns: - Iterable[Instruction]: `Iterable` of Depolarizing instructions. - - Examples: - >>> circ = Circuit().two_qubit_depolarizing(0, 1, probability=0.1) - """ - return [ - Instruction( - Noise.TwoQubitDepolarizing(probability=probability), target=[target1, target2] - ) - ] - - def bind_values(self, **kwargs) -> Noise: - """Takes in parameters and attempts to assign them to values. - - Returns: - Noise: A new Noise object of the same type with the requested - parameters bound. - """ - return TwoQubitDepolarizing(probability=_substitute_value(self._probability, **kwargs)) - - @classmethod - def from_dict(cls, noise: dict) -> Noise: - """Converts a dictionary representation of this class into this class. - - Args: - noise(dict): The dictionary representation of this noise. - - Returns: - Noise: A Noise object that represents the passed in dictionary. - """ - return TwoQubitDepolarizing(probability=_parameter_from_dict(noise["probability"])) - - -Noise.register_noise(TwoQubitDepolarizing) - - -class TwoQubitDephasing(SingleProbabilisticNoise_34): - r"""Two-Qubit Dephasing noise channel which transforms a - density matrix :math:`\\rho` according to: - - .. math:: - \\rho \\Rightarrow (1-p) \\rho + p/3 ( - IZ \\rho IZ^{\\dagger} + ZI \\rho ZI^{\\dagger} + ZZ \\rho ZZ^{\\dagger}) - - where - - .. math:: - I = \\left( - \\begin{matrix} - 1 & 0 \\\\ - 0 & 1 - \\end{matrix} - \\right) - - Z = \\left( - \\begin{matrix} - 1 & 0 \\\\ - 0 & -1 - \\end{matrix} - \\right) - - p = \\text{probability} - - This noise channel is shown as `DEPH` in circuit diagrams. - """ - - def __init__(self, probability: Union[FreeParameterExpression, float]): - super().__init__( - probability=probability, - qubit_count=None, - ascii_symbols=[_ascii_representation("DEPH", [probability])] * 2, - ) - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.TwoQubitDephasing.construct( - targets=[target[0], target[1]], probability=self.probability - ) - - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ) -> str: - target_qubit_0 = serialization_properties.format_target(int(target[0])) - target_qubit_1 = serialization_properties.format_target(int(target[1])) - return ( - f"#pragma braket noise two_qubit_dephasing({self.probability}) " - f"{target_qubit_0}, {target_qubit_1}" - ) - - 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) - SZ = np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) - K0 = np.sqrt(1 - self._probability) * np.kron(SI, SI) - K1 = np.sqrt(self._probability / 3) * np.kron(SI, SZ) - K2 = np.sqrt(self._probability / 3) * np.kron(SZ, SI) - K3 = np.sqrt(self._probability / 3) * np.kron(SZ, SZ) - - return [K0, K1, K2, K3] - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def two_qubit_dephasing( - target1: QubitInput, target2: QubitInput, probability: float - ) -> Iterable[Instruction]: - """Registers this function into the circuit class. - - Args: - target1 (QubitInput): Target qubit 1. - target2 (QubitInput): Target qubit 2. - probability (float): Probability of two-qubit dephasing. - - Returns: - Iterable[Instruction]: `Iterable` of Dephasing instructions. - - Examples: - >>> circ = Circuit().two_qubit_dephasing(0, 1, probability=0.1) - """ - return [ - Instruction(Noise.TwoQubitDephasing(probability=probability), target=[target1, target2]) - ] - - def bind_values(self, **kwargs) -> Noise: - """Takes in parameters and attempts to assign them to values. - - Returns: - Noise: A new Noise object of the same type with the requested - parameters bound. - """ - return TwoQubitDephasing(probability=_substitute_value(self._probability, **kwargs)) - - @classmethod - def from_dict(cls, noise: dict) -> Noise: - """Converts a dictionary representation of this class into this class. - - Args: - noise(dict): The dictionary representation of this noise. - - Returns: - Noise: A Noise object that represents the passed in dictionary. - """ - return TwoQubitDephasing(probability=_parameter_from_dict(noise["probability"])) - - -Noise.register_noise(TwoQubitDephasing) - - -class TwoQubitPauliChannel(MultiQubitPauliNoise): - r"""Two-Qubit Pauli noise channel which transforms a - density matrix :math:`\\rho` according to: - - .. math:: - \\rho \\Rightarrow (1-p) \\rho + - p_{IX} IX \\rho IX^{\\dagger} + - p_{IY} IY \\rho IY^{\\dagger} + - p_{IZ} IZ \\rho IZ^{\\dagger} + - p_{XI} XI \\rho XI^{\\dagger} + - p_{XX} XX \\rho XX^{\\dagger} + - p_{XY} XY \\rho XY^{\\dagger} + - p_{XZ} XZ \\rho XZ^{\\dagger} + - p_{YI} YI \\rho YI^{\\dagger} + - p_{YX} YX \\rho YX^{\\dagger} + - p_{YY} YY \\rho YY^{\\dagger} + - p_{YZ} YZ \\rho YZ^{\\dagger} + - p_{ZI} ZI \\rho ZI^{\\dagger} + - p_{ZX} ZX \\rho ZX^{\\dagger} + - p_{ZY} ZY \\rho ZY^{\\dagger} + - p_{ZZ} ZZ \\rho ZZ^{\\dagger}) - - where - - .. math:: - I = \\left( - \\begin{matrix} - 1 & 0 \\\\ - 0 & 1 - \\end{matrix} - \\right) - - X = \\left( - \\begin{matrix} - 0 & 1 \\\\ - 1 & 0 - \\end{matrix} - \\right) - - Y = \\left( - \\begin{matrix} - 0 & -i \\\\ - i & 0 - \\end{matrix} - \\right) - - Z = \\left( - \\begin{matrix} - 1 & 0 \\\\ - 0 & -1 - \\end{matrix} - \\right) - - p = \\text{sum of all probabilities} - - This noise channel is shown as `PC_2({"pauli_string": probability})` in circuit diagrams. - """ - - _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: ClassVar = ["".join(x) for x in _tensor_products_strings] - - def __init__(self, probabilities: dict[str, float]): - super().__init__( - probabilities=probabilities, - qubit_count=None, - ascii_symbols=[ - f"PC2({probabilities})", - f"PC2({probabilities})", - ], - ) - self._matrix = None - - 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. - """ - if self._matrix is not None: - return self._matrix - total_prob = sum(self._probabilities.values()) - K_list = [np.sqrt(1 - total_prob) * np.identity(4)] # "II" element - for pstring in self._names_list[1:]: # ignore "II" - if pstring in self._probabilities: - mat = np.sqrt(self._probabilities[pstring]) * np.kron( - self._paulis[pstring[0]], self._paulis[pstring[1]] - ) - K_list.append(mat) - else: - K_list.append(np.zeros((4, 4))) - self._matrix = K_list - return self._matrix - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.MultiQubitPauliChannel.construct( - targets=[target[0], target[1]], probabilities=self.probabilities - ) - - @staticmethod - def fixed_qubit_count() -> int: - return 2 - - @staticmethod - @circuit.subroutine(register=True) - def two_qubit_pauli_channel( - target1: QubitInput, target2: QubitInput, probabilities: dict[str, float] - ) -> Iterable[Instruction]: - """Registers this function into the circuit class. - - Args: - target1 (QubitInput): Target qubit 1. - target2 (QubitInput): Target qubit 2. - probabilities (dict[str, float]): Probability of two-qubit Pauli channel. - - Returns: - Iterable[Instruction]: `Iterable` of Depolarizing instructions. - - Examples: - >>> circ = Circuit().two_qubit_pauli_channel(0, 1, {"XX": 0.1}) - """ - return [ - Instruction( - Noise.TwoQubitPauliChannel(probabilities=probabilities), - target=[target1, target2], - ) - ] - - def bind_values(self, **kwargs) -> Noise: - """Takes in parameters and attempts to assign them to values. - - Returns: - Noise: A new Noise object of the same type with the requested - parameters bound. - """ - probabilities = { - pauli_string: _substitute_value(prob, **kwargs) - for pauli_string, prob in self._probabilities.items() - } - return TwoQubitPauliChannel(probabilities=probabilities) - - @classmethod - def from_dict(cls, noise: dict) -> Noise: - """Converts a dictionary representation of this class into this class. - - Args: - noise(dict): The dictionary representation of this noise. - - Returns: - Noise: A Noise object that represents the passed in dictionary. - """ - probabilities = { - pauli_string: _parameter_from_dict(prob) - for pauli_string, prob in noise["probabilities"].items() - } - return TwoQubitPauliChannel(probabilities=probabilities) - - -Noise.register_noise(TwoQubitPauliChannel) - - -class AmplitudeDamping(DampingNoise): - 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} - - where - - .. math:: - E_0 = \\left( - \\begin{matrix} - 1 & 0 \\\\ - 0 & \\sqrt{1-\\gamma} - \\end{matrix} - \\right) - - E_1 = \\left( - \\begin{matrix} - 0 & \\sqrt{\\gamma} \\\\ - 0 & 0 - \\end{matrix} - \\right) - - This noise channel is shown as `AD` in circuit diagrams. - """ - - def __init__(self, gamma: Union[FreeParameterExpression, float]): - super().__init__( - gamma=gamma, - qubit_count=None, - ascii_symbols=[_ascii_representation("AD", [gamma])], - ) - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.AmplitudeDamping.construct(target=target[0], gamma=self.gamma) - - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"#pragma braket noise amplitude_damping({self.gamma}) {target_qubit}" - - 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. - """ - K0 = np.array([[1.0, 0.0], [0.0, np.sqrt(1 - self.gamma)]], dtype=complex) - K1 = np.array([[0.0, np.sqrt(self.gamma)], [0.0, 0.0]], dtype=complex) - return [K0, K1] - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def amplitude_damping(target: QubitSetInput, gamma: float) -> Iterable[Instruction]: - """Registers this function into the circuit class. - - Args: - target (QubitSetInput): Target qubit(s). - gamma (float): decaying rate of the amplitude damping channel. - - Returns: - Iterable[Instruction]: `Iterable` of AmplitudeDamping instructions. - - Examples: - >>> circ = Circuit().amplitude_damping(0, gamma=0.1) - """ - return [ - Instruction(Noise.AmplitudeDamping(gamma=gamma), target=qubit) - for qubit in QubitSet(target) - ] - - def bind_values(self, **kwargs) -> Noise: - """Takes in parameters and attempts to assign them to values. - - Returns: - Noise: A new Noise object of the same type with the requested - parameters bound. - """ - return AmplitudeDamping(gamma=_substitute_value(self._gamma, **kwargs)) - - @classmethod - def from_dict(cls, noise: dict) -> Noise: - """Converts a dictionary representation of this class into this class. - - Args: - noise(dict): The dictionary representation of this noise. - - Returns: - Noise: A Noise object that represents the passed in dictionary. - """ - return AmplitudeDamping(gamma=_parameter_from_dict(noise["gamma"])) - - -Noise.register_noise(AmplitudeDamping) - - -class GeneralizedAmplitudeDamping(GeneralizedAmplitudeDampingNoise): - 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} - + E_2 \\rho E_2^{\\dagger} + E_3 \\rho E_3^{\\dagger} - - where - - .. math:: - E_0 = \\sqrt(probability)\\left( - \\begin{matrix} - 1 & 0 \\\\ - 0 & \\sqrt{1-\\gamma} - \\end{matrix} - \\right) - - E_1 = \\sqrt(probability)\\left( - \\begin{matrix} - 0 & \\sqrt{\\gamma} \\\\ - 0 & 0 - \\end{matrix} - \\right) - E_2 = \\sqrt(1-probability)\\left( - \\begin{matrix} - \\sqrt{1-\\gamma} & 0 \\\\ - 0 & 1 - \\end{matrix} - \\right) - E_3 = \\sqrt(1-probability)\\left( - \\begin{matrix} - 0 & 0 \\\\ - \\sqrt{\\gamma} & 0 - \\end{matrix} - \\right) - - This noise channel is shown as `GAD` in circuit diagrams. - """ - - def __init__( - self, - gamma: Union[FreeParameterExpression, float], - probability: Union[FreeParameterExpression, float], - ): - super().__init__( - gamma=gamma, - probability=probability, - qubit_count=None, - ascii_symbols=[_ascii_representation("GAD", [gamma, probability])], - ) - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.GeneralizedAmplitudeDamping.construct( - target=target[0], gamma=self.gamma, probability=self.probability - ) - - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return ( - "#pragma braket noise generalized_amplitude_damping(" - f"{self.gamma}, {self.probability}) {target_qubit}" - ) - - 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. - """ - K0 = np.sqrt(self.probability) * np.array( - [[1.0, 0.0], [0.0, np.sqrt(1 - self.gamma)]], dtype=complex - ) - K1 = np.sqrt(self.probability) * np.array( - [[0.0, np.sqrt(self.gamma)], [0.0, 0.0]], dtype=complex - ) - K2 = np.sqrt(1 - self.probability) * np.array([[np.sqrt(1 - self.gamma), 0.0], [0.0, 1.0]]) - K3 = np.sqrt(1 - self.probability) * np.array([[0.0, 0.0], [np.sqrt(self.gamma), 0.0]]) - return [K0, K1, K2, K3] - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def generalized_amplitude_damping( - target: QubitSetInput, gamma: float, probability: float - ) -> Iterable[Instruction]: - """Registers this function into the circuit class. - - Args: - target (QubitSetInput): Target qubit(s). - gamma (float): The damping rate of the amplitude damping channel. - probability(float): Probability of the system being excited by the environment. - - Returns: - Iterable[Instruction]: `Iterable` of GeneralizedAmplitudeDamping instructions. - - Examples: - >>> circ = Circuit().generalized_amplitude_damping(0, gamma=0.1, probability = 0.9) - """ - return [ - Instruction( - Noise.GeneralizedAmplitudeDamping(gamma=gamma, probability=probability), - target=qubit, - ) - for qubit in QubitSet(target) - ] - - def bind_values(self, **kwargs) -> Noise: - """Takes in parameters and attempts to assign them to values. - - Returns: - Noise: A new Noise object of the same type with the requested - parameters bound. - """ - gamma = _substitute_value(self._gamma, **kwargs) - probability = _substitute_value(self._probability, **kwargs) - return GeneralizedAmplitudeDamping(gamma=gamma, probability=probability) - - @classmethod - def from_dict(cls, noise: dict) -> Noise: - """Converts a dictionary representation of this class into this class. - - Args: - noise(dict): The dictionary representation of this noise. - - Returns: - Noise: A Noise object that represents the passed in dictionary. - """ - return GeneralizedAmplitudeDamping( - gamma=_parameter_from_dict(noise["gamma"]), - probability=_parameter_from_dict(noise["probability"]), - ) - - -Noise.register_noise(GeneralizedAmplitudeDamping) - - -class PhaseDamping(DampingNoise): - 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} - - where - - .. math:: - E_0 = \\left( - \\begin{matrix} - 1 & 0 \\\\ - 0 & \\sqrt{1-gamma} - \\end{matrix} - \\right) - - E_1 = \\left( - \\begin{matrix} - 0 & 0 \\\\ - 0 & \\sqrt{gamma} - \\end{matrix} - \\right) - - p = \\text{probability} - - This noise channel is shown as `PD` in circuit diagrams. - """ - - def __init__(self, gamma: Union[FreeParameterExpression, float]): - super().__init__( - gamma=gamma, - qubit_count=None, - ascii_symbols=[_ascii_representation("PD", [gamma])], - ) - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.PhaseDamping.construct(target=target[0], gamma=self.gamma) - - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ) -> str: - target_qubit = serialization_properties.format_target(int(target[0])) - return f"#pragma braket noise phase_damping({self.gamma}) {target_qubit}" - - 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. - """ - K0 = np.array([[1.0, 0.0], [0.0, np.sqrt(1 - self.gamma)]], dtype=complex) - K1 = np.array([[0.0, 0.0], [0.0, np.sqrt(self.gamma)]], dtype=complex) - return [K0, K1] - - @staticmethod - def fixed_qubit_count() -> int: - return 1 - - @staticmethod - @circuit.subroutine(register=True) - def phase_damping(target: QubitSetInput, gamma: float) -> Iterable[Instruction]: - """Registers this function into the circuit class. - - Args: - target (QubitSetInput): Target qubit(s) - gamma (float): Probability of phase damping. - - Returns: - Iterable[Instruction]: `Iterable` of PhaseDamping instructions. - - Examples: - >>> circ = Circuit().phase_damping(0, gamma=0.1) - """ - return [ - Instruction(Noise.PhaseDamping(gamma=gamma), target=qubit) for qubit in QubitSet(target) - ] - - def bind_values(self, **kwargs) -> Noise: - """Takes in parameters and attempts to assign them to values. - - Returns: - Noise: A new Noise object of the same type with the requested - parameters bound. - """ - return PhaseDamping(gamma=_substitute_value(self._gamma, **kwargs)) - - @classmethod - def from_dict(cls, noise: dict) -> Noise: - """Converts a dictionary representation of this class into this class. - - Args: - noise(dict): The dictionary representation of this noise. - - Returns: - Noise: A Noise object that represents the passed in dictionary. - """ - return PhaseDamping(gamma=_parameter_from_dict(noise["gamma"])) - - -Noise.register_noise(PhaseDamping) - - -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. - """ - - 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 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 - qubit_count = int(np.log2(self._matrices[0].shape[0])) - if qubit_count > 2: - raise ValueError("Kraus operators with more than two qubits are not supported.") - if len(matrices) > 2 ** (2 * qubit_count): - raise ValueError("The number of Kraus operators is beyond limit.") - - if not is_cptp(self._matrices): - raise ValueError( - "The input matrices do not define a completely-positive trace-preserving map." - ) - - super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) - - 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. - """ - return self._matrices - - def _to_jaqcd(self, target: QubitSet) -> Any: - return ir.Kraus.construct( - targets=list(target), - matrices=Kraus._transform_matrix_to_ir(self._matrices), - ) - - def _to_openqasm( - self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties - ) -> str: - matrix_list = ", ".join( - np.array2string( - matrix, - separator=", ", - formatter={"all": lambda x: format_complex(x)}, - ).replace("\n", "") - for matrix in self._matrices - ) - qubit_list = ", ".join( - serialization_properties.format_target(int(qubit)) for qubit in target - ) - return f"#pragma braket noise kraus({matrix_list}) {qubit_list}" - - @staticmethod - def _transform_matrix_to_ir(matrices: Iterable[np.ndarray]) -> list: - serializable = [] - for matrix in matrices: - matrix_as_list = [ - [[element.real, element.imag] for element in row] for row in matrix.tolist() - ] - serializable.append(matrix_as_list) - return serializable - - @staticmethod - @circuit.subroutine(register=True) - def kraus( - targets: QubitSetInput, matrices: Iterable[np.array], display_name: str = "KR" - ) -> Iterable[Instruction]: - """Registers this function into the circuit class. - - Args: - targets (QubitSetInput): Target qubit(s) - matrices (Iterable[array]): Matrices that define a general noise channel. - display_name (str): The display name. - - Returns: - Iterable[Instruction]: `Iterable` of Kraus instructions. - - Examples: - >>> K0 = np.eye(4) * np.sqrt(0.9) - >>> K1 = np.kron([[1., 0.],[0., 1.]], [[0., 1.],[1., 0.]]) * np.sqrt(0.1) - >>> circ = Circuit().kraus([1, 0], matrices=[K0, K1]) - """ - if 2 ** len(targets) != matrices[0].shape[0]: - raise ValueError( - "Dimensions of the supplied Kraus matrices are incompatible with the targets" - ) - - return Instruction( - Noise.Kraus(matrices=matrices, display_name=display_name), target=targets - ) - - def to_dict(self) -> dict: - """Converts this object into a dictionary representation. Not implemented at this time. - - Returns: - dict: Not implemented at this time.. - """ - raise NotImplementedError - - @classmethod - def from_dict(cls, noise: dict) -> Noise: - """Converts a dictionary representation of this class into this class. - - Args: - noise(dict): The dictionary representation of this noise. - - Returns: - Noise: A Noise object that represents the passed in dictionary. - """ - raise NotImplementedError - - -Noise.register_noise(Kraus) - - -def _ascii_representation( - noise: str, parameters: list[Union[FreeParameterExpression, float]] -) -> str: - """Generates a formatted ascii representation of a noise. - - Args: - noise (str): The name of the noise. - parameters (list[Union[FreeParameterExpression, float]]): The parameters to the noise. - - Returns: - str: The ascii representation of the noise. - """ - 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})" - - -def _substitute_value(expr: float, **kwargs) -> Union[FreeParameterExpression, float]: - return expr.subs(kwargs) if isinstance(expr, FreeParameterExpression) else expr - - -def _parameter_from_dict(parameter: Union[dict, float]) -> Union[FreeParameter, float]: - """Converts a parameter from a dictionary if it's a FreeParameter, otherwise returns the float. - - Args: - parameter(Union[dict, float]): The parameter to convert. - - Returns: - Union[FreeParameter, float]: A FreeParameter representing the parameter, if the parameter - is a dictionary, otherwise returns the float. - """ - if isinstance(parameter, dict): - return FreeParameter.from_dict(parameter) - return parameter diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py deleted file mode 100644 index d3f3fc86..00000000 --- a/src/braket/circuits/observable.py +++ /dev/null @@ -1,220 +0,0 @@ -# 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 numbers -from collections.abc import Sequence -from copy import deepcopy -from typing import Union - -import numpy as np - -from braket.circuits.gate import Gate -from braket.circuits.quantum_operator import QuantumOperator -from braket.circuits.serialization import ( - IRType, - OpenQASMSerializationProperties, - SerializationProperties, -) -from braket.registers.qubit_set import QubitSet - - -class Observable(QuantumOperator): - """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. - """ - - def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): - super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) - self._coef = 1 - - def _unscaled(self) -> Observable: - return Observable(qubit_count=self.qubit_count, ascii_symbols=self.ascii_symbols) - - def to_ir( - self, - target: QubitSet | None = None, - ir_type: IRType = IRType.JAQCD, - serialization_properties: SerializationProperties | None = None, - ) -> Union[str, list[Union[str, list[list[list[float]]]]]]: - """Returns the IR representation for the observable - - Args: - target (QubitSet | 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 - 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: - Union[str, list[Union[str, list[list[list[float]]]]]]: The IR representation for - the observable. - - Raises: - ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization - properties don't correspond to the `ir_type`. - """ - if ir_type == IRType.JAQCD: - return self._to_jaqcd() - elif ir_type == IRType.OPENQASM: - if serialization_properties and not isinstance( - serialization_properties, OpenQASMSerializationProperties - ): - raise ValueError( - "serialization_properties must be of type OpenQASMSerializationProperties " - "for IRType.OPENQASM." - ) - return self._to_openqasm( - serialization_properties or OpenQASMSerializationProperties(), target - ) - else: - raise ValueError(f"Supplied ir_type {ir_type} is not supported.") - - def _to_jaqcd(self) -> list[Union[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, - ) -> 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. - - Returns: - str: Representing the openqasm representation of the result type. - """ - raise NotImplementedError("to_openqasm has not been implemented yet.") - - @property - def coefficient(self) -> int: - """The coefficient of the observable. - - Returns: - 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. - - Returns: - tuple[Gate, ...]: The basis rotation gates for this observable. - """ - raise NotImplementedError - - @property - def eigenvalues(self) -> np.ndarray: - """Returns the eigenvalues of this observable. - - Returns: - np.ndarray: The eigenvalues of this observable. - """ - raise NotImplementedError - - def eigenvalue(self, index: int) -> float: - """Returns the eigenvalue of this observable at the given index. - - The eigenvalues are ordered by their corresponding computational basis state - after diagonalization. - - Args: - index (int): The index of the desired eigenvalue - - Returns: - float: The `index` th eigenvalue of the observable. - """ - raise NotImplementedError - - @classmethod - def register_observable(cls, observable: Observable) -> None: - """Register an observable implementation by adding it into the `Observable` class. - - Args: - observable (Observable): Observable class to register. - """ - setattr(cls, observable.__name__, observable) - - 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) -> Observable: - """Scalar multiplication""" - if isinstance(other, numbers.Number): - observable_copy = deepcopy(self) - observable_copy._coef *= other - return observable_copy - raise TypeError("Observable coefficients must be numbers.") - - def __rmul__(self, other: Observable) -> Observable: - return 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: Observable): - if not isinstance(other, Observable): - raise ValueError("Can only perform subtraction between observables.") - - return self + (-1 * other) - - def __repr__(self) -> str: - return f"{self.name}('qubit_count': {self.qubit_count})" - - 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 - eigenvalues of (+1, -1). - """ - - def __init__(self, ascii_symbols: Sequence[str]): - super().__init__(qubit_count=1, ascii_symbols=ascii_symbols) - self._eigenvalues = (1.0, -1.0) # immutable - - def _unscaled(self) -> StandardObservable: - return StandardObservable(ascii_symbols=self.ascii_symbols) - - @property - def eigenvalues(self) -> np.ndarray: - return self.coefficient * np.array(self._eigenvalues) - - def eigenvalue(self, index: int) -> float: - return self.coefficient * self._eigenvalues[index] - - @property - def ascii_symbols(self) -> tuple[str, ...]: - return tuple( - f"{self.coefficient if self.coefficient != 1 else ''}{ascii_symbol}" - for ascii_symbol in self._ascii_symbols - ) diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py deleted file mode 100644 index ae4f36a0..00000000 --- a/src/braket/circuits/observables.py +++ /dev/null @@ -1,684 +0,0 @@ -# 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 functools -import itertools -import math -import numbers -from copy import deepcopy -from typing import ClassVar, Union - -import numpy as np - -from braket.circuits.gate import Gate -from braket.circuits.observable import Observable, StandardObservable -from braket.circuits.quantum_operator_helpers import ( - get_pauli_eigenvalues, - is_hermitian, - verify_quantum_operator_matrix_dimensions, -) -from braket.circuits.serialization import IRType, OpenQASMSerializationProperties -from braket.registers.qubit_set import QubitSet - - -class H(StandardObservable): - """Hadamard operation as an observable.""" - - def __init__(self): - """Examples: - >>> Observable.H() - """ - super().__init__(ascii_symbols=["H"]) - - def _unscaled(self) -> StandardObservable: - return H() - - def _to_jaqcd(self) -> list[str]: - if self.coefficient != 1: - raise ValueError("Observable coefficients not supported with Jaqcd") - return ["h"] - - 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" - - def to_matrix(self) -> np.ndarray: - return self.coefficient * ( - 1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) - ) - - @property - def basis_rotation_gates(self) -> tuple[Gate, ...]: - return (Gate.Ry(-math.pi / 4),) - - -Observable.register_observable(H) - - -class I(Observable): # noqa: E742 - """Identity operation as an observable.""" - - def __init__(self): - """Examples: - >>> Observable.I() - """ - super().__init__(qubit_count=1, ascii_symbols=["I"]) - - def _unscaled(self) -> Observable: - return I() - - def _to_jaqcd(self) -> list[str]: - if self.coefficient != 1: - raise ValueError("Observable coefficients not supported with Jaqcd") - return ["i"] - - 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" - - def to_matrix(self) -> np.ndarray: - return self.coefficient * np.eye(2, dtype=complex) - - @property - def basis_rotation_gates(self) -> tuple[Gate, ...]: - return () - - @property - def eigenvalues(self) -> np.ndarray: - """Returns the eigenvalues of this observable. - - Returns: - np.ndarray: The eigenvalues of this observable. - """ - return self.coefficient * np.ones(2) - - def eigenvalue(self, index: int) -> float: - return self.coefficient * 1.0 - - -Observable.register_observable(I) - - -class X(StandardObservable): - """Pauli-X operation as an observable.""" - - def __init__(self): - """Examples: - >>> Observable.X() - """ - super().__init__(ascii_symbols=["X"]) - - def _unscaled(self) -> StandardObservable: - return X() - - def _to_jaqcd(self) -> list[str]: - if self.coefficient != 1: - raise ValueError("Observable coefficients not supported with Jaqcd") - return ["x"] - - 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" - - def to_matrix(self) -> np.ndarray: - return self.coefficient * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) - - @property - def basis_rotation_gates(self) -> tuple[Gate, ...]: - return (Gate.H(),) - - -Observable.register_observable(X) - - -class Y(StandardObservable): - """Pauli-Y operation as an observable.""" - - def __init__(self): - """Examples: - >>> Observable.Y() - """ - super().__init__(ascii_symbols=["Y"]) - - def _unscaled(self) -> StandardObservable: - return Y() - - def _to_jaqcd(self) -> list[str]: - if self.coefficient != 1: - raise ValueError("Observable coefficients not supported with Jaqcd") - return ["y"] - - 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" - - def to_matrix(self) -> np.ndarray: - return self.coefficient * np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) - - @property - def basis_rotation_gates(self) -> tuple[Gate, ...]: - return Gate.Z(), Gate.S(), Gate.H() - - -Observable.register_observable(Y) - - -class Z(StandardObservable): - """Pauli-Z operation as an observable.""" - - def __init__(self): - """Examples: - >>> Observable.Z() - """ - super().__init__(ascii_symbols=["Z"]) - - def _unscaled(self) -> StandardObservable: - return Z() - - def _to_jaqcd(self) -> list[str]: - if self.coefficient != 1: - raise ValueError("Observable coefficients not supported with Jaqcd") - return ["z"] - - 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" - - def to_matrix(self) -> np.ndarray: - return self.coefficient * np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) - - @property - def basis_rotation_gates(self) -> tuple[Gate, ...]: - return () - - -Observable.register_observable(Z) - - -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 - - Examples: - >>> t1 = Observable.Y() @ Observable.X() - >>> 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.factors - (Z('qubit_count': 1), Y('qubit_count': 1), X('qubit_count': 1)) - - Note: You must provide the list of observables for the tensor product to be evaluated - in the order that you want the tensor product to be calculated. - For `TensorProduct(observables=[ob1, ob2, ob3])`, the tensor product's matrix is the - result of the tensor product of `ob1`, `ob2`, `ob3`, or `np.kron(np.kron(ob1.to_matrix(), - ob2.to_matrix()), ob3.to_matrix())`. - """ - flattened_observables = [] - for obs in observables: - if isinstance(obs, TensorProduct): - 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) - # 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) - display_name = ( - 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) - self._coef = coefficient - self._factors = unscaled_factors - self._factor_dimensions = tuple( - len(factor.to_matrix()) for factor in reversed(self._factors) - ) - self._eigenvalue_indices = {} - self._all_eigenvalues = None - - @property - def ascii_symbols(self) -> tuple[str, ...]: - return tuple( - f"{self.coefficient if self.coefficient != 1 else ''}" - f"{'@'.join([obs.ascii_symbols[0] for obs in self.factors])}" - for _ in range(self.qubit_count) - ) - - def _unscaled(self) -> Observable: - copied = TensorProduct(observables=self.factors) - copied._coef = 1 - return copied - - def _to_jaqcd(self) -> list[str]: - if self.coefficient != 1: - raise ValueError("Observable coefficients not supported with Jaqcd") - ir = [] - for obs in self.factors: - ir.extend(obs.to_ir()) - return ir - - def _to_openqasm( - self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None - ) -> str: - coef_prefix = f"{self.coefficient} * " if self.coefficient != 1 else "" - factors = [] - use_qubits = iter(target) - for obs in self._factors: - obs_target = QubitSet() - num_qubits = int(np.log2(obs.to_matrix().shape[0])) - for _ in range(num_qubits): - obs_target.add(next(use_qubits)) - factors.append( - obs.to_ir( - target=obs_target, - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - ) - ) - return f"{coef_prefix}{' @ '.join(factors)}" - - @property - def factors(self) -> tuple[Observable, ...]: - """tuple[Observable]: The observables that comprise this tensor product.""" - return self._factors - - def to_matrix(self) -> np.ndarray: - return self.coefficient * functools.reduce( - np.kron, [obs.to_matrix() for obs in self.factors] - ) - - @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. - """ - gates = [] - for obs in self.factors: - gates.extend(obs.basis_rotation_gates) - return tuple(gates) - - @property - def eigenvalues(self) -> np.ndarray: - """Returns the eigenvalues of this observable. - - Returns: - np.ndarray: The eigenvalues of this observable. - """ - if self._all_eigenvalues is None: - self._all_eigenvalues = TensorProduct._compute_eigenvalues( - self._factors, self.qubit_count - ) - return self.coefficient * self._all_eigenvalues - - def eigenvalue(self, index: int) -> float: - """Returns the eigenvalue of this observable at the given index. - - The eigenvalues are ordered by their corresponding computational basis state - after diagonalization. - - Args: - index (int): The index of the desired eigenvalue - - Returns: - float: The `index` th eigenvalue of the observable. - """ - if index in self._eigenvalue_indices: - return self._eigenvalue_indices[index] - dimension = 2**self.qubit_count - if index >= dimension: - raise ValueError( - f"Index {index} requested but observable has at most {dimension} eigenvalues" - ) - # Calculating the eigenvalue amounts to converting the index to a new heterogeneous base - # and multiplying the eigenvalues of each factor at the corresponding digit - product = 1 - quotient = index - 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] - - def __repr__(self): - return "TensorProduct(" + ", ".join([repr(o) for o in self.factors]) + ")" - - def __eq__(self, other: TensorProduct): - return self.matrix_equivalence(other) - - @staticmethod - def _compute_eigenvalues(observables: tuple[Observable], num_qubits: int) -> np.ndarray: - if False in [isinstance(observable, StandardObservable) for observable in observables]: - # Tensor product of observables contains a mixture - # of standard and non-standard observables - eigenvalues = np.array([1]) - for k, g in itertools.groupby(observables, lambda x: isinstance(x, StandardObservable)): - if k: - # Subgroup g contains only standard observables. - eigenvalues = np.kron(eigenvalues, get_pauli_eigenvalues(len(list(g)))) - else: - # Subgroup g contains only non-standard observables. - for nonstandard in g: - # loop through all non-standard observables - eigenvalues = np.kron(eigenvalues, nonstandard.eigenvalues) - else: - eigenvalues = get_pauli_eigenvalues(num_qubits=num_qubits) - - eigenvalues.setflags(write=False) - return eigenvalues - - -Observable.register_observable(TensorProduct) - - -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 - observable for circuit diagrams. Defaults to `Hamiltonian`. - - Examples: - >>> t1 = -3 * Observable.Y() + 2 * Observable.X() - Sum(X('qubit_count': 1), Y('qubit_count': 1)) - >>> t1.summands - (X('qubit_count': 1), Y('qubit_count': 1)) - """ - flattened_observables = [] - for obs in observables: - if isinstance(obs, Sum): - flattened_observables.extend(iter(obs.summands)) - else: - flattened_observables.append(obs) - - self._summands = tuple(flattened_observables) - 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: numbers.Number) -> Observable: - """Scalar multiplication""" - if isinstance(other, numbers.Number): - sum_copy = deepcopy(self) - for i, _obs in enumerate(sum_copy.summands): - sum_copy._summands[i]._coef *= other - return sum_copy - raise TypeError("Observable coefficients must be numbers.") - - def _to_jaqcd(self) -> list[str]: - raise NotImplementedError("Sum Observable is not supported in Jaqcd") - - def _to_openqasm( - self, - serialization_properties: OpenQASMSerializationProperties, - target: list[QubitSet] = None, - ) -> str: - if len(self.summands) != len(target): - 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)): - if term.qubit_count != len(term_target): - raise ValueError( - f"Invalid target for term {i} of Sum. " - f"Expected {term.qubit_count} targets, got {len(term_target)}" - ) - return " + ".join( - obs.to_ir( - target=term_target, - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - ) - for obs, term_target in zip(self.summands, target) - ).replace("+ -", "- ") - - @property - def summands(self) -> tuple[Observable, ...]: - """tuple[Observable]: The observables that comprise this sum.""" - return self._summands - - def to_matrix(self) -> np.ndarray: - raise NotImplementedError("Matrix operation is not supported for Sum") - - @property - def basis_rotation_gates(self) -> tuple[Gate, ...]: - raise NotImplementedError("Basis rotation calculation not supported for Sum") - - @property - def eigenvalues(self) -> np.ndarray: - raise NotImplementedError("Eigenvalue calculation not supported for Sum") - - def eigenvalue(self, index: int) -> float: - raise NotImplementedError("Eigenvalue calculation not supported for Sum") - - def __repr__(self): - return "Sum(" + ", ".join([repr(o) for o in self.summands]) + ")" - - def __eq__(self, other: Sum): - return repr(self) == repr(other) - - @staticmethod - def _compute_eigenvalues(observables: tuple[Observable], num_qubits: int) -> np.ndarray: - raise NotImplementedError("Eigenvalue calculation not supported for Sum") - - -Observable.register_observable(Sum) - - -class Hermitian(Observable): - """Hermitian matrix as an observable.""" - - # Cache of eigenpairs - _eigenpairs: ClassVar = {} - - def __init__(self, matrix: np.ndarray, display_name: str = "Hermitian"): - """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`. - - 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. - - Examples: - >>> Observable.Hermitian(matrix=np.array([[0, 1],[1, 0]])) - """ - verify_quantum_operator_matrix_dimensions(matrix) - self._matrix = np.array(matrix, dtype=complex) - if not is_hermitian(self._matrix): - raise ValueError(f"{self._matrix} is not hermitian") - - qubit_count = int(np.log2(self._matrix.shape[0])) - eigendecomposition = Hermitian._get_eigendecomposition(self._matrix) - self._eigenvalues = eigendecomposition["eigenvalues"] - self._diagonalizing_gates = ( - Gate.Unitary(matrix=eigendecomposition["eigenvectors"].conj().T), - ) - - super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) - - def _unscaled(self) -> Observable: - return Hermitian(matrix=self._matrix, display_name=self.ascii_symbols[0]) - - def _to_jaqcd(self) -> list[list[list[list[float]]]]: - if self.coefficient != 1: - raise ValueError("Observable coefficients not supported with Jaqcd") - return [ - [[[element.real, element.imag] for element in row] for row in self._matrix.tolist()] - ] - - 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 = ", ".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" - - def _serialized_matrix_openqasm_matrix(self) -> str: - serialized = str([[f"{complex(elem)}" for elem in row] for row in self._matrix.tolist()]) - for replacements in [("(", ""), (")", ""), ("'", ""), ("j", "im")]: - serialized = serialized.replace(replacements[0], replacements[1]) - return serialized - - def to_matrix(self) -> np.ndarray: - return self.coefficient * self._matrix - - def __eq__(self, other: Hermitian) -> bool: - return self.matrix_equivalence(other) - - @property - def basis_rotation_gates(self) -> tuple[Gate, ...]: - return self._diagonalizing_gates - - @property - def eigenvalues(self) -> np.ndarray: - """Returns the eigenvalues of this observable. - - Returns: - np.ndarray: The eigenvalues of this observable. - """ - return self._eigenvalues - - def eigenvalue(self, index: int) -> float: - return self._eigenvalues[index] - - @staticmethod - def _get_eigendecomposition(matrix: np.ndarray) -> dict[str, np.ndarray]: - """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. - - Args: - matrix (ndarray): The Hermitian matrix. - - Returns: - dict[str, ndarray]: The keys are "eigenvectors_conj_t", mapping to the - conjugate transpose of a matrix whose columns are the eigenvectors of the matrix, - and "eigenvalues", a list of associated eigenvalues in the order of their - corresponding eigenvectors in the "eigenvectors" matrix. These cached values - are immutable. - """ - mat_key = tuple(matrix.flatten().tolist()) - if mat_key not in Hermitian._eigenpairs: - eigenvalues, eigenvectors = np.linalg.eigh(matrix) - eigenvalues.setflags(write=False) - Hermitian._eigenpairs[mat_key] = { - "eigenvectors": eigenvectors, - "eigenvalues": eigenvalues, - } - return Hermitian._eigenpairs[mat_key] - - def __repr__(self): - matrix_str = np.array2string(self.to_matrix()).replace("\n", ",") - return f"{self.name}('qubit_count': {self.qubit_count}, 'matrix': {matrix_str})" - - -Observable.register_observable(Hermitian) - - -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 - observables or a single observable. - - Args: - ir_observable (list[Union[str, list[list[list[float]]]]]): observable as defined in IR - - Returns: - Observable: observable object - """ - 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 - - -def _observable_from_ir_list_item(observable: Union[str, list[list[list[float]]]]) -> Observable: - if observable == "i": - return I() - elif observable == "h": - return H() - elif observable == "x": - return X() - elif observable == "y": - return Y() - elif 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 diff --git a/src/braket/circuits/operator.py b/src/braket/circuits/operator.py deleted file mode 100644 index ccd63ac3..00000000 --- a/src/braket/circuits/operator.py +++ /dev/null @@ -1,37 +0,0 @@ -# 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 abc import ABC, abstractmethod -from typing import Any - - -class Operator(ABC): - """An operator is the abstract definition of an operation for a quantum device.""" - - @property - @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. - 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. - """ diff --git a/src/braket/circuits/parameterizable.py b/src/braket/circuits/parameterizable.py deleted file mode 100644 index a4a9925b..00000000 --- a/src/braket/circuits/parameterizable.py +++ /dev/null @@ -1,14 +0,0 @@ -# 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.parametric.parameterizable import Parameterizable # noqa: F401 diff --git a/src/braket/circuits/quantum_operator.py b/src/braket/circuits/quantum_operator.py deleted file mode 100644 index b706e182..00000000 --- a/src/braket/circuits/quantum_operator.py +++ /dev/null @@ -1,160 +0,0 @@ -# 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 Sequence -from typing import Any, Optional - -import numpy as np - -from braket.circuits.operator import Operator - - -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 - should be ``None``, and ``fixed_qubit_count`` should be implemented to return - the qubit count; if ``fixed_qubit_count`` is implemented and an int is passed in, - it must equal ``fixed_qubit_count``, or instantiation will raise a ValueError. - An int must be passed in if instances can have a varying number of qubits, - in which case ``fixed_qubit_count`` should not be implemented, - ascii_symbols (Sequence[str]): ASCII string symbols for the quantum operator. - These are used when printing a diagram of circuits. - Length must be the same as `qubit_count`, and index ordering is expected - to correlate with target ordering on the instruction. - For instance, if CNOT instruction has the control qubit on the first index and - target qubit on the second index. Then ASCII symbols would have ["C", "X"] to - correlate a symbol with that index. - - Raises: - TypeError: `qubit_count` is not an int - ValueError: `qubit_count` is less than 1, `ascii_symbols` are `None`, - ``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 - 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: - self._qubit_count = fixed_qubit_count - - if not isinstance(self._qubit_count, int): - raise TypeError(f"qubit_count, {self._qubit_count}, must be an integer") - - if self._qubit_count < 1: - raise ValueError(f"qubit_count, {self._qubit_count}, must be greater than zero") - - if ascii_symbols is None: - raise ValueError("ascii_symbols must not be None") - - if len(ascii_symbols) != self._qubit_count: - msg = ( - f"ascii_symbols, {ascii_symbols}," - f" length must equal qubit_count, {self._qubit_count}" - ) - raise ValueError(msg) - self._ascii_symbols = tuple(ascii_symbols) - - @staticmethod - def fixed_qubit_count() -> int: - """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, - this method returns ``NotImplemented``. - - Returns: - int: The number of qubits this quantum operator acts on. - """ - return NotImplemented - - @property - def qubit_count(self) -> int: - """int: The number of qubits this quantum operator acts on.""" - return self._qubit_count - - @property - def ascii_symbols(self) -> tuple[str, ...]: - """tuple[str, ...]: Returns the ascii symbols for the quantum operator.""" - return self._ascii_symbols - - @property - def name(self) -> str: - """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: 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: 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: - 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 - - Args: - other (QuantumOperator): Quantum operator instance to compare this quantum operator to - - Returns: - bool: If matrix forms of this quantum operator and the other quantum operator - are equivalent - """ - if not isinstance(other, QuantumOperator): - return False - try: - return np.allclose(self.to_matrix(), other.to_matrix()) - except ValueError: - return False - - def __repr__(self): - return f"{self.name}('qubit_count': {self.qubit_count})" diff --git a/src/braket/circuits/quantum_operator_helpers.py b/src/braket/circuits/quantum_operator_helpers.py deleted file mode 100644 index 10c22808..00000000 --- a/src/braket/circuits/quantum_operator_helpers.py +++ /dev/null @@ -1,125 +0,0 @@ -# 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 functools import lru_cache - -import numpy as np - - -def verify_quantum_operator_matrix_dimensions(matrix: np.ndarray) -> None: - """Verifies matrix is square and matrix dimensions are positive powers of 2, - raising `ValueError` otherwise. - - Args: - matrix (ndarray): matrix to verify - - Raises: - ValueError: If `matrix` is not a two-dimensional square matrix, - or has a dimension length that is not a positive power of 2 - """ - if not is_square_matrix(matrix): - raise ValueError(f"{matrix} is not a two-dimensional square matrix") - - matrix = np.array(matrix, dtype=complex) - qubit_count = int(np.log2(matrix.shape[0])) - - if 2**qubit_count != matrix.shape[0] or qubit_count < 1: - raise ValueError(f"`matrix` dimension {matrix.shape[0]} is not a positive power of 2") - - -def is_hermitian(matrix: np.ndarray) -> bool: - r"""Whether matrix is Hermitian - - A square matrix :math:`U` is Hermitian if - - .. math:: U = U^\dagger - - where :math:`U^\dagger` is the conjugate transpose of :math:`U`. - - Args: - matrix (ndarray): matrix to verify - - Returns: - bool: If matrix is Hermitian - """ - return np.allclose(matrix, matrix.conj().T) - - -def is_square_matrix(matrix: np.ndarray) -> bool: - """Whether matrix is square, meaning it has exactly two dimensions and the dimensions are equal - - Args: - matrix (np.ndarray): matrix to verify - - Returns: - bool: If matrix is square - """ - return len(matrix.shape) == 2 and matrix.shape[0] == matrix.shape[1] - - -def is_unitary(matrix: np.ndarray) -> bool: - r"""Whether matrix is unitary - - A square matrix :math:`U` is unitary if - - .. math:: UU^\dagger = I - - where :math:`U^\dagger` is the conjugate transpose of :math:`U` - and :math:`I` is the identity matrix. - - Args: - matrix (np.ndarray): matrix to verify - - Returns: - bool: If matrix is unitary - """ - return np.allclose(np.eye(len(matrix)), matrix.dot(matrix.T.conj())) - - -def is_cptp(matrices: Iterable[np.ndarray]) -> bool: - """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. - - Args: - matrices (Iterable[ndarray]): List of matrices representing Kraus operators. - - Returns: - bool: If the matrices define a CPTP map. - """ - E = sum(np.dot(matrix.T.conjugate(), matrix) for matrix in matrices) - return np.allclose(E, np.eye(*E.shape)) - - -@lru_cache -def get_pauli_eigenvalues(num_qubits: int) -> np.ndarray: - """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: - np.ndarray: the eigenvalues of a Pauli product operator of the given size - """ - if num_qubits == 1: - 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.setflags(write=False) - return eigs diff --git a/src/braket/circuits/qubit.py b/src/braket/circuits/qubit.py deleted file mode 100644 index f82e91fa..00000000 --- a/src/braket/circuits/qubit.py +++ /dev/null @@ -1,14 +0,0 @@ -# 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.registers import Qubit, QubitInput # noqa: F401 diff --git a/src/braket/circuits/qubit_set.py b/src/braket/circuits/qubit_set.py deleted file mode 100644 index b2c1bbfc..00000000 --- a/src/braket/circuits/qubit_set.py +++ /dev/null @@ -1,14 +0,0 @@ -# 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.registers.qubit_set import QubitSet, QubitSetInput # noqa: F401 diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py deleted file mode 100644 index b66d4da6..00000000 --- a/src/braket/circuits/result_type.py +++ /dev/null @@ -1,333 +0,0 @@ -# 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, Union - -from braket.circuits.free_parameter import FreeParameter -from braket.circuits.observable import Observable -from braket.circuits.observables import Sum -from braket.circuits.serialization import ( - IRType, - OpenQASMSerializationProperties, - SerializationProperties, -) -from braket.registers.qubit import QubitInput -from braket.registers.qubit_set import QubitSet, QubitSetInput - - -class ResultType: - """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. - - Raises: - ValueError: `ascii_symbols` is `None` - """ - if ascii_symbols is None: - raise ValueError("ascii_symbols must not be None") - - self._ascii_symbols = ascii_symbols - - @property - def ascii_symbols(self) -> list[str]: - """list[str]: Returns the ascii symbols for the requested result type.""" - return self._ascii_symbols - - @property - def name(self) -> str: - """Returns the name of the result type - - Returns: - str: The name of the result type as a string - """ - return self.__class__.__name__ - - def to_ir( - self, - ir_type: IRType = IRType.JAQCD, - serialization_properties: SerializationProperties | None = None, - **kwargs, - ) -> Any: - """Returns IR object of the result type - - Args: - 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 - properties supplied must correspond to the supplied `ir_type`. Defaults to None. - - Returns: - Any: IR object of the result type - - Raises: - ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization - properties don't correspond to the `ir_type`. - """ - if ir_type == IRType.JAQCD: - return self._to_jaqcd() - elif ir_type == IRType.OPENQASM: - if serialization_properties and not isinstance( - serialization_properties, OpenQASMSerializationProperties - ): - raise ValueError( - "serialization_properties must be of type OpenQASMSerializationProperties " - "for IRType.OPENQASM." - ) - return self._to_openqasm(serialization_properties or OpenQASMSerializationProperties()) - else: - raise ValueError(f"Supplied ir_type {ir_type} is not supported.") - - def _to_jaqcd(self) -> Any: - """Returns the JAQCD representation of the result type.""" - 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. - - 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. - """ - raise NotImplementedError("to_openqasm has not been implemented yet.") - - def copy( - self, - target_mapping: dict[QubitInput, QubitInput] | None = None, - target: QubitSetInput | None = None, - ) -> ResultType: - """Return a shallow copy of the result type. - - Note: - If `target_mapping` is specified, then `self.target` is mapped to the specified - qubits. This is useful apply an instruction to a circuit and change the target qubits. - - Args: - target_mapping (dict[QubitInput, QubitInput] | None): A dictionary of - qubit mappings to apply to the target. Key is the qubit in this `target` and the - value is what the key is changed to. Default = `None`. - target (QubitSetInput | None): Target qubits for the new instruction. - - Returns: - ResultType: A shallow copy of the result type. - - Raises: - TypeError: If both `target_mapping` and `target` are supplied. - - Examples: - >>> result_type = ResultType.Probabilities(targets=[0]) - >>> new_result_type = result_type.copy() - >>> new_result_type.targets - QubitSet(Qubit(0)) - >>> new_result = result_type.copy(target_mapping={0: 5}) - >>> new_result_type.target - QubitSet(Qubit(5)) - >>> new_result = result_type.copy(target=[5]) - >>> new_result_type.target - QubitSet(Qubit(5)) - """ - copy = self.__copy__() - 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 hasattr(copy, "target"): - copy.target = target - elif hasattr(copy, "target"): - copy.target = self._target.map(target_mapping or {}) - return copy - - @classmethod - def register_result_type(cls, result_type: type[ResultType]) -> None: - """Register a result type implementation by adding it into the `ResultType` class. - - Args: - result_type (type[ResultType]): `ResultType` class to register. - """ - setattr(cls, result_type.__name__, result_type) - - def __repr__(self) -> str: - return f"{self.name}()" - - def __hash__(self) -> int: - return hash(repr(self)) - - -class ObservableResultType(ResultType): - """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. - - See :mod:`braket.circuits.observables` module for all of the supported observables. - """ - - 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. - 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 - only operate on 1 qubit and it will be applied to all qubits in parallel - - Raises: - ValueError: if target=None and the observable's qubit count is not 1. - Or, if `target!=None` and the observable's qubit count and the number of target - qubits are not equal. Or, if `target!=None` and the observable's qubit count and - the number of `ascii_symbols` are not equal. - """ - 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(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" - ) - - @property - def observable(self) -> Observable: - return self._observable - - @property - def target(self) -> QubitSet: - return self._target - - @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: ObservableResultType) -> bool: - if isinstance(other, ObservableResultType): - return ( - self.name == other.name - and self.target == other.target - and self.observable == other.observable - ) - return NotImplemented - - def __repr__(self) -> str: - return f"{self.name}(observable={self.observable}, target={self.target})" - - def __copy__(self) -> ObservableResultType: - return type(self)(observable=self.observable, target=self.target) - - def __hash__(self) -> int: - return super().__hash__() - - -class ObservableParameterResultType(ObservableResultType): - """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. - If no parameters are specified, observable will be applied to all the free parameters. - - See :mod:`braket.circuits.observables` module for all of the supported observables. - """ - - def __init__( - self, - ascii_symbols: list[str], - observable: Observable, - target: QubitSetInput | None = None, - parameters: list[Union[str, FreeParameter]] | None = None, - ): - super().__init__(ascii_symbols, observable, target) - - self._parameters = ( - [(param.name if isinstance(param, FreeParameter) else param) for param in parameters] - if parameters - else parameters - ) - - """ - Args: - ascii_symbols (list[str]): ASCII string symbols for the result type. This is used when - printing a diagram of circuits. - 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 only operate on 1 - qubit and it will be applied to all qubits in parallel. - parameters (list[Union[str, FreeParameter]] | None): List of string inputs or - FreeParameter objects. These inputs will be used as parameters for - gradient calculation. Default: `all`. - - Raises: - ValueError: if target=None and the observable's qubit count is not 1. - Or, if `target!=None` and the observable's qubit count and the number of target - qubits are not equal. Or, if `target!=None` and the observable's qubit count and - the number of `ascii_symbols` are not equal. - """ - - @property - def parameters(self) -> list[str]: - return self._parameters - - def __repr__(self) -> str: - return ( - f"{self.name}(observable={self.observable}, target={self.target}, " - f"parameters={self.parameters})" - ) - - def __copy__(self) -> ObservableResultType: - return type(self)( - observable=self.observable, target=self.target, parameters=self.parameters - ) diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py deleted file mode 100644 index 325fa8f4..00000000 --- a/src/braket/circuits/result_types.py +++ /dev/null @@ -1,658 +0,0 @@ -# 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 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, - ResultType, -) -from braket.circuits.serialization import IRType, OpenQASMSerializationProperties -from braket.registers.qubit_set import QubitSet, QubitSetInput - -""" -To add a new result type: - 1. Implement the class and extend `ResultType` - 2. Add a method with the `@circuit.subroutine(register=True)` decorator. Method name - is added into the `Circuit` class. This method is the default way - clients add this result type to a circuit. - 3. Register the class with the `ResultType` class via `ResultType.register_result_type()`. -""" - - -class StateVector(ResultType): - """The full state vector as a requested result type. - This is available on simulators only when `shots=0`. - """ - - def __init__(self): - super().__init__(ascii_symbols=["StateVector"]) - - def _to_jaqcd(self) -> ir.StateVector: - return ir.StateVector.construct() - - def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: - return "#pragma braket result state_vector" - - @staticmethod - @circuit.subroutine(register=True) - def state_vector() -> ResultType: - """Registers this function into the circuit class. - - Returns: - ResultType: state vector as a requested result type - - Examples: - >>> circ = Circuit().state_vector() - """ - return ResultType.StateVector() - - def __eq__(self, other: StateVector) -> bool: - return isinstance(other, StateVector) - - def __copy__(self) -> StateVector: - return type(self)() - - # must redefine __hash__ since __eq__ is overwritten - # https://docs.python.org/3/reference/datamodel.html#object.__hash__ - def __hash__(self) -> int: - return super().__hash__() - - -ResultType.register_result_type(StateVector) - - -class DensityMatrix(ResultType): - """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 - full density matrix is returned. - - Examples: - >>> ResultType.DensityMatrix(target=[0, 1]) - """ - self._target = QubitSet(target) - ascii_symbols = ["DensityMatrix"] * len(self._target) if self._target else ["DensityMatrix"] - super().__init__(ascii_symbols=ascii_symbols) - - @property - def target(self) -> QubitSet: - return self._target - - @target.setter - def target(self, target: QubitSetInput) -> None: - """Sets the target qubit set. - - Args: - target (QubitSetInput): The target qubit set. - """ - self._target = QubitSet(target) - - 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() - - def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: - if not self.target: - return "#pragma braket result density_matrix all" - targets = ", ".join( - serialization_properties.format_target(int(target)) for target in self.target - ) - return f"#pragma braket result density_matrix {targets}" - - @staticmethod - @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 - full density matrix is returned. - - Returns: - ResultType: density matrix as a requested result type - - Examples: - >>> circ = Circuit().density_matrix(target=[0, 1]) - """ - return ResultType.DensityMatrix(target=target) - - def __eq__(self, other: DensityMatrix) -> bool: - if isinstance(other, DensityMatrix): - return self.target == other.target - return False - - def __repr__(self) -> str: - return f"DensityMatrix(target={self.target})" - - def __copy__(self) -> DensityMatrix: - return type(self)(target=self.target) - - # must redefine __hash__ since __eq__ is overwritten - # https://docs.python.org/3/reference/datamodel.html#object.__hash__ - def __hash__(self) -> int: - return super().__hash__() - - -ResultType.register_result_type(DensityMatrix) - - -class AdjointGradient(ObservableParameterResultType): - """The gradient of the expectation value of the provided observable, applied to target, - with respect to the given parameter. - """ - - def __init__( - self, - observable: Observable, - 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. - target (list[QubitSetInput] | None): Target qubits that the result type is requested - for. Each term in the target list should have the same number of qubits as the - corresponding term in the observable. Default is `None`, which means the - observable must operate only on 1 qubit and it is applied to all qubits - in parallel. - parameters (list[Union[str, FreeParameter]] | None): The free parameters in the circuit - to differentiate with respect to. Default: `all`. - - 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.AdjointGradient(observable=Observable.Z(), - target=0, parameters=["alpha", "beta"]) - - >>> tensor_product = Observable.Y() @ Observable.Z() - >>> hamiltonian = Observable.Y() @ Observable.Z() + Observable.H() - >>> ResultType.AdjointGradient( - >>> observable=tensor_product, - >>> target=[[0, 1], [2]], - >>> parameters=["alpha", "beta"], - >>> ) - """ - if isinstance(observable, Sum): - target_qubits = reduce(QubitSet.union, map(QubitSet, target), QubitSet()) - else: - target_qubits = QubitSet(target) - - super().__init__( - ascii_symbols=[f"AdjointGradient({observable.ascii_symbols[0]})"] * len(target_qubits), - observable=observable, - target=target, - parameters=parameters, - ) - - def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: - observable_ir = self.observable.to_ir( - target=self.target, - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - ) - - pragma_parameters = ", ".join(self.parameters) if self.parameters else "all" - - return ( - f"#pragma braket result adjoint_gradient " - f"expectation({observable_ir}) {pragma_parameters}" - ) - - @staticmethod - @circuit.subroutine(register=True) - def adjoint_gradient( - observable: Observable, - target: list[QubitSetInput] | None = None, - parameters: list[Union[str, FreeParameter]] | None = None, - ) -> ResultType: - """Registers this function into the circuit class. - - Args: - observable (Observable): The expectation value of this observable is the function - against which parameters in the gradient are differentiated. - target (list[QubitSetInput] | None): Target qubits that the result type is requested - for. Each term in the target list should have the same number of qubits as the - corresponding term in the observable. Default is `None`, which means the - observable must operate only on 1 qubit and it is applied to all qubits - in parallel. - parameters (list[Union[str, FreeParameter]] | None): The free parameters in the circuit - to differentiate with respect to. Default: `all`. - - Returns: - ResultType: gradient computed via adjoint differentiation as a requested result type - - 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] - >>> ) - """ - return ResultType.AdjointGradient( - observable=observable, target=target, parameters=parameters - ) - - -ResultType.register_result_type(AdjointGradient) - - -class Amplitude(ResultType): - """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" - - Raises: - ValueError: If state is `None` or an empty list, or - state is not a list of strings of '0' and '1' - - Examples: - >>> ResultType.Amplitude(state=['01', '10']) - """ - if ( - not state - or not isinstance(state, list) - or not all( - isinstance(amplitude, str) and re.fullmatch("^[01]+$", amplitude) - for amplitude in state - ) - ): - raise ValueError( - "A non-empty list of states must be specified in binary encoding e.g. ['01', '10']" - ) - super().__init__(ascii_symbols=[f"Amplitude({','.join(state)})"]) - self._state = state - - @property - def state(self) -> list[str]: - return self._state - - def _to_jaqcd(self) -> ir.Amplitude: - return ir.Amplitude.construct(states=self.state) - - def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: - states = ", ".join(f'"{state}"' for state in self.state) - return f"#pragma braket result amplitude {states}" - - @staticmethod - @circuit.subroutine(register=True) - def amplitude(state: list[str]) -> ResultType: - """Registers this function into the circuit class. - - Args: - state (list[str]): list of quantum states as strings with "0" and "1" - - Returns: - ResultType: state vector as a requested result type - - Examples: - >>> circ = Circuit().amplitude(state=["01", "10"]) - """ - return ResultType.Amplitude(state=state) - - def __eq__(self, other: Amplitude): - return self.state == other.state if isinstance(other, Amplitude) else False - - def __repr__(self): - return f"Amplitude(state={self.state})" - - def __copy__(self): - return type(self)(state=self.state) - - def __hash__(self) -> int: - return super().__hash__() - - -ResultType.register_result_type(Amplitude) - - -class Probability(ResultType): - """Probability in the computational basis as the requested result type. - - It can be the probability of all states if no targets are specified, or the marginal - probability of a restricted set of states if only a subset of all qubits are specified as - targets. - - For `shots>0`, this is calculated by measurements. For `shots=0`, this is supported - only on simulators and represents the exact result. - """ - - 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 - circuit. - - Examples: - >>> ResultType.Probability(target=[0, 1]) - """ - self._target = QubitSet(target) - ascii_symbols = ["Probability"] * len(self._target) if self._target else ["Probability"] - super().__init__(ascii_symbols=ascii_symbols) - - @property - def target(self) -> QubitSet: - return self._target - - @target.setter - def target(self, target: QubitSetInput) -> None: - """Sets the target qubit set. - - Args: - target (QubitSetInput): The target qubit set. - """ - self._target = QubitSet(target) - - 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() - - def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: - if not self.target: - return "#pragma braket result probability all" - targets = ", ".join( - serialization_properties.format_target(int(target)) for target in self.target - ) - return f"#pragma braket result probability {targets}" - - @staticmethod - @circuit.subroutine(register=True) - def probability(target: QubitSetInput | None = None) -> ResultType: - """Registers this function into the circuit class. - - Args: - target (QubitSetInput | None): The target qubits that the - result type is requested for. Default is `None`, which means all qubits for the - circuit. - - Returns: - ResultType: probability as a requested result type - - Examples: - >>> circ = Circuit().probability(target=[0, 1]) - """ - return ResultType.Probability(target=target) - - def __eq__(self, other: Probability) -> bool: - return self.target == other.target if isinstance(other, Probability) else False - - def __repr__(self) -> str: - return f"Probability(target={self.target})" - - def __copy__(self) -> Probability: - return type(self)(target=self.target) - - def __hash__(self) -> int: - return super().__hash__() - - -ResultType.register_result_type(Probability) - - -class Expectation(ObservableResultType): - """Expectation of the specified target qubit set and observable as the requested result type. - - If no targets are specified, the observable must operate only on 1 qubit and it - is 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. - - For `shots>0`, this is calculated by measurements. For `shots=0`, this is supported - only by simulators and represents the exact result. - - See :mod:`braket.circuits.observables` module for all of the supported observables. - """ - - 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. - - - Examples: - >>> ResultType.Expectation(observable=Observable.Z(), target=0) - - >>> tensor_product = Observable.Y() @ Observable.Z() - >>> ResultType.Expectation(observable=tensor_product, target=[0, 1]) - """ - super().__init__( - ascii_symbols=[f"Expectation({obs_ascii})" for obs_ascii in observable.ascii_symbols], - observable=observable, - target=target, - ) - - def _to_jaqcd(self) -> ir.Expectation: - if self.target: - 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()) - - def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: - observable_ir = self.observable.to_ir( - target=self.target, - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - ) - return f"#pragma braket result expectation {observable_ir}" - - @staticmethod - @circuit.subroutine(register=True) - def expectation(observable: Observable, target: QubitSetInput | None = None) -> ResultType: - """Registers this function into the circuit class. - - 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. - - Returns: - ResultType: expectation as a requested result type - - Examples: - >>> circ = Circuit().expectation(observable=Observable.Z(), target=0) - """ - return ResultType.Expectation(observable=observable, target=target) - - -ResultType.register_result_type(Expectation) - - -class Sample(ObservableResultType): - """Sample of specified target qubit set and observable as the requested result type. - - If no targets are specified, the observable must operate only on 1 qubit and it - is applied to all qubits in parallel. Otherwise, the number of specified targets - must equal the number of qubits the observable can be applied to. - - This is only available for `shots>0`. - - See :mod:`braket.circuits.observables` module for all of the supported observables. - """ - - 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. - - Examples: - >>> ResultType.Sample(observable=Observable.Z(), target=0) - - >>> tensor_product = Observable.Y() @ Observable.Z() - >>> ResultType.Sample(observable=tensor_product, target=[0, 1]) - """ - super().__init__( - ascii_symbols=[f"Sample({obs_ascii})" for obs_ascii in observable.ascii_symbols], - observable=observable, - target=target, - ) - - def _to_jaqcd(self) -> ir.Sample: - if self.target: - 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()) - - def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: - observable_ir = self.observable.to_ir( - target=self.target, - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - ) - return f"#pragma braket result sample {observable_ir}" - - @staticmethod - @circuit.subroutine(register=True) - def sample(observable: Observable, target: QubitSetInput | None = None) -> ResultType: - """Registers this function into the circuit class. - - 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. - - Returns: - ResultType: sample as a requested result type - - Examples: - >>> circ = Circuit().sample(observable=Observable.Z(), target=0) - """ - return ResultType.Sample(observable=observable, target=target) - - -ResultType.register_result_type(Sample) - - -class Variance(ObservableResultType): - """Variance of specified target qubit set and observable as the requested result type. - - If no targets are specified, the observable must operate only on 1 qubit and it - is applied to all qubits in parallel. Otherwise, the number of targets specified - must equal the number of qubits that the observable can be applied to. - - For `shots>0`, this is calculated by measurements. For `shots=0`, this is supported - only by simulators and represents the exact result. - - See :mod:`braket.circuits.observables` module for all of the supported observables. - """ - - 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 - 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.Variance(observable=Observable.Z(), target=0) - - >>> tensor_product = Observable.Y() @ Observable.Z() - >>> ResultType.Variance(observable=tensor_product, target=[0, 1]) - """ - super().__init__( - ascii_symbols=[f"Variance({obs_ascii})" for obs_ascii in observable.ascii_symbols], - observable=observable, - target=target, - ) - - def _to_jaqcd(self) -> ir.Variance: - if self.target: - 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()) - - def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: - observable_ir = self.observable.to_ir( - target=self.target, - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - ) - return f"#pragma braket result variance {observable_ir}" - - @staticmethod - @circuit.subroutine(register=True) - def variance(observable: Observable, target: QubitSetInput | None = None) -> ResultType: - """Registers this function into the circuit class. - - 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 - only operate on 1 qubit and it will be applied to all qubits in parallel - - Returns: - ResultType: variance as a requested result type - - Examples: - >>> circ = Circuit().variance(observable=Observable.Z(), target=0) - """ - return ResultType.Variance(observable=observable, target=target) - - -ResultType.register_result_type(Variance) diff --git a/src/braket/circuits/serialization.py b/src/braket/circuits/serialization.py deleted file mode 100644 index 2aa47f49..00000000 --- a/src/braket/circuits/serialization.py +++ /dev/null @@ -1,88 +0,0 @@ -# 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 abc import ABC, abstractmethod -from dataclasses import dataclass -from enum import Enum - - -class IRType(str, Enum): - """Defines the available IRTypes for circuit serialization.""" - - OPENQASM = "OPENQASM" - JAQCD = "JAQCD" - - -class QubitReferenceType(str, Enum): - """Defines how qubits should be referenced in the generated OpenQASM string. - See https://qiskit.github.io/openqasm/language/types.html#quantum-types - for details. - """ - - VIRTUAL = "VIRTUAL" - PHYSICAL = "PHYSICAL" - - -class SerializableProgram(ABC): - @abstractmethod - def to_ir( - self, - ir_type: IRType = IRType.OPENQASM, - allow_implicit_build: bool = False, - ) -> 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. - allow_implicit_build (bool): Whether to allow the program to be implicitly - built as a side effect of calling this function. Defaults to False. - - Raises: - ValueError: Raised if the supplied `ir_type` is not supported. - RuntimeError: Raised if the program has not already been built and - `allow_implicit_build` is False. - - Returns: - str: A representation of the program in the `ir_type` format. - """ - - -@dataclass -class OpenQASMSerializationProperties: - """Properties for serializing a circuit to OpenQASM. - - qubit_reference_type (QubitReferenceType): determines whether to use - logical qubits or physical qubits (q[i] vs $i). - """ - - qubit_reference_type: QubitReferenceType = QubitReferenceType.VIRTUAL - - def format_target(self, target: int) -> str: - """Format a target qubit to the appropriate OpenQASM representation. - - Args: - target (int): The target qubit. - - Returns: - str: The OpenQASM representation of the target qubit. - """ - qubit_reference_format = ( - "q[{}]" if self.qubit_reference_type == QubitReferenceType.VIRTUAL else "${}" - ) - return qubit_reference_format.format(target) - - -# Type alias to refer to possible serialization properties. Can be expanded once -# new properties are added. -SerializationProperties = OpenQASMSerializationProperties diff --git a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py deleted file mode 100644 index 4a7c9565..00000000 --- a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py +++ /dev/null @@ -1,197 +0,0 @@ -# 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 = 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)) - - 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 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. - - 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 - ) + "{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 deleted file mode 100644 index ad30b34a..00000000 --- a/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py +++ /dev/null @@ -1,265 +0,0 @@ -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 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. - - 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 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 - - 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 deleted file mode 100644 index f261b00b..00000000 --- a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py +++ /dev/null @@ -1,199 +0,0 @@ -# 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.measure import Measure -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 = sum( - item.operator.angle - for item in items - if ( - isinstance(item, Instruction) - and isinstance(item.operator, Gate) - and item.operator.name == "GPhase" - ) - ) - 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, Noise and Measure operators for instructions at the moment - if isinstance(item, Instruction) and not isinstance( - item.operator, (Gate, Noise, CompilerDirective, Measure) - ): - 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 deleted file mode 100644 index 85567de2..00000000 --- a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py +++ /dev/null @@ -1,278 +0,0 @@ -# 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 = 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)) - 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 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. - - 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(): - 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/src/braket/circuits/translations.py b/src/braket/circuits/translations.py deleted file mode 100644 index 78bb7eed..00000000 --- a/src/braket/circuits/translations.py +++ /dev/null @@ -1,186 +0,0 @@ -# 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 reduce, singledispatch -from typing import Union - -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, - Expectation, - Probability, - Sample, - StateVector, - Variance, -) -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, - "y": braket_gates.Y, - "z": braket_gates.Z, - "cv": braket_gates.CV, - "cnot": braket_gates.CNot, - "cy": braket_gates.CY, - "cz": braket_gates.CZ, - "ecr": braket_gates.ECR, - "s": braket_gates.S, - "si": braket_gates.Si, - "t": braket_gates.T, - "ti": braket_gates.Ti, - "v": braket_gates.V, - "vi": braket_gates.Vi, - "phaseshift": braket_gates.PhaseShift, - "cphaseshift": braket_gates.CPhaseShift, - "cphaseshift00": braket_gates.CPhaseShift00, - "cphaseshift01": braket_gates.CPhaseShift01, - "cphaseshift10": braket_gates.CPhaseShift10, - "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, - "xy": braket_gates.XY, - "xx": braket_gates.XX, - "yy": braket_gates.YY, - "zz": braket_gates.ZZ, - "ccnot": braket_gates.CCNot, - "cswap": braket_gates.CSwap, - "gpi": braket_gates.GPi, - "gpi2": braket_gates.GPi2, - "prx": braket_gates.PRx, - "ms": braket_gates.MS, - "unitary": braket_gates.Unitary, -} - -one_prob_noise_map = { - "bit_flip": noises.BitFlip, - "phase_flip": noises.PhaseFlip, - "pauli_channel": noises.PauliChannel, - "depolarizing": noises.Depolarizing, - "two_qubit_depolarizing": noises.TwoQubitDepolarizing, - "two_qubit_dephasing": noises.TwoQubitDephasing, - "amplitude_damping": noises.AmplitudeDamping, - "generalized_amplitude_damping": noises.GeneralizedAmplitudeDamping, - "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: - """Gets the observable. - - Args: - obs (Union[Observable, list]): The observable(s) to get translated. - - Returns: - Observable: The translated observable. - """ - return _get_observable(obs) - - -@singledispatch -def _get_observable(obs: Union[models.Observable, list]) -> Observable: - raise NotImplementedError - - -@_get_observable.register(list) -def _(obs): - raise NotImplementedError - - -@_get_observable.register(str) -def _(name: str): - return getattr(observables, name.upper())() - - -def get_tensor_product(observable: Union[models.Observable, list]) -> Observable: - """Generate an braket circuit observable - - Args: - observable (Union[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) - - -@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: - 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) diff --git a/src/braket/circuits/unitary_calculation.py b/src/braket/circuits/unitary_calculation.py deleted file mode 100644 index 9fa40428..00000000 --- a/src/braket/circuits/unitary_calculation.py +++ /dev/null @@ -1,78 +0,0 @@ -# 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 - -import numpy as np -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 - - -def calculate_unitary_big_endian( - instructions: Iterable[Instruction], qubits: QubitSet -) -> np.ndarray: - """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 - `len(qubits)` > 10. - - Args: - instructions (Iterable[Instruction]): The instructions for which the unitary matrix - will be calculated. - qubits (QubitSet): The actual qubits used by the instructions. - - Returns: - np.ndarray: A numpy array with shape (2^qubit_count, 2^qubit_count) representing the - `instructions` as a unitary matrix. - - Raises: - TypeError: If `instructions` is not composed only of `Gate` instances, - i.e. a circuit with `Noise` operators will raise this error. - Any `CompilerDirective` instructions will be ignored, as these should - not affect the unitary representation of the circuit. - """ - qubits_sorted = sorted(qubits) - qubit_count = len(qubits_sorted) - # Map qubits to contiguous indices starting from 0 - index_substitutions = {qubits_sorted[i]: i for i in range(qubit_count)} - rank = 2**qubit_count - # Initialize identity unitary as type (rank, rank) tensor - unitary = np.eye(rank, dtype=complex).reshape([2] * 2 * qubit_count) - - for instruction in instructions: - if isinstance(instruction.operator, CompilerDirective): - continue - if not isinstance(instruction.operator, Gate): - raise TypeError("Only Gate operators are supported to build the unitary") - - base_gate_matrix = instruction.operator.to_matrix() - if int(instruction.power) == instruction.power: - gate_matrix = np.linalg.matrix_power(base_gate_matrix, int(instruction.power)) - else: - gate_matrix = fractional_matrix_power(base_gate_matrix, instruction.power) - - unitary = multiply_matrix( - unitary, - gate_matrix, - tuple(index_substitutions[qubit] for qubit in instruction.target), - controls=instruction.control, - control_state=instruction.control_state, - ) - - return unitary.reshape(rank, rank) diff --git a/src/braket/devices/__init__.py b/src/braket/devices/__init__.py deleted file mode 100644 index 1c45599a..00000000 --- a/src/braket/devices/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# 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.devices.device import Device # noqa: F401 -from braket.devices.devices import Devices # noqa: F401 -from braket.devices.local_simulator import LocalSimulator # noqa: F401 diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py deleted file mode 100644 index dbc7b6b3..00000000 --- a/src/braket/devices/device.py +++ /dev/null @@ -1,150 +0,0 @@ -# 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 warnings -from abc import ABC, abstractmethod -from typing import Any, Optional, Union - -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 - - -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 - """ - self._name = name - self._status = status - - @abstractmethod - def run( - self, - task_specification: Union[Circuit, Problem], - shots: Optional[int], - inputs: Optional[dict[str, float]], - *args, - **kwargs, - ) -> QuantumTask: - """Run a quantum task specification on this quantum device. A quantum task can be a circuit - or an annealing problem. - - Args: - task_specification (Union[Circuit, Problem]): 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`. - 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 - """ - - @abstractmethod - def run_batch( - self, - task_specifications: Union[ - Union[Circuit, Problem], - list[Union[Circuit, Problem]], - ], - shots: Optional[int], - max_parallel: Optional[int], - inputs: Optional[Union[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]]]): - 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 - 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 - """ - - @property - def name(self) -> str: - """Return the name of this Device. - - Returns: - str: The name of this Device - """ - return self._name - - @property - def status(self) -> str: - """Return the status of this Device. - - Returns: - str: The status of this Device - """ - return self._status - - def _validate_device_noise_model_support(self, noise_model: NoiseModel) -> None: - 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 = {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/devices.py b/src/braket/devices/devices.py deleted file mode 100644 index f4fe8ff8..00000000 --- a/src/braket/devices/devices.py +++ /dev/null @@ -1,60 +0,0 @@ -# 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 enum import Enum - - -class Devices: - class _Amazon(str, Enum): - SV1 = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" - TN1 = "arn:aws:braket:::device/quantum-simulator/amazon/tn1" - DM1 = "arn:aws:braket:::device/quantum-simulator/amazon/dm1" - - class _DWave(str, Enum): - _Advantage1 = "arn:aws:braket:::device/qpu/d-wave/Advantage_system1" - _Advantage3 = "arn:aws:braket:::device/qpu/d-wave/Advantage_system3" - _Advantage4 = "arn:aws:braket:::device/qpu/d-wave/Advantage_system4" - _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 _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" - Aria2 = "arn:aws:braket:us-east-1::device/qpu/ionq/Aria-2" - 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" - - class _QuEra(str, Enum): - Aquila = "arn:aws:braket:us-east-1::device/qpu/quera/Aquila" - - class _Rigetti(str, Enum): - _Aspen8 = "arn:aws:braket:::device/qpu/rigetti/Aspen-8" - _Aspen9 = "arn:aws:braket:::device/qpu/rigetti/Aspen-9" - _Aspen10 = "arn:aws:braket:::device/qpu/rigetti/Aspen-10" - _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" - - class _Xanadu(str, Enum): - _Borealis = "arn:aws:braket:us-east-1::device/qpu/xanadu/Borealis" - - Amazon = _Amazon - # DWave = _DWave - IonQ = _IonQ - OQC = _OQC - QuEra = _QuEra - Rigetti = _Rigetti - # Xanadu = _Xanadu diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py deleted file mode 100644 index a560ddaf..00000000 --- a/src/braket/devices/local_simulator.py +++ /dev/null @@ -1,388 +0,0 @@ -# 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 sys -from functools import singledispatchmethod -from itertools import repeat -from multiprocessing import Pool -from os import cpu_count -from typing import Any, Optional, Union - -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 -from braket.simulator import BraketSimulator -from braket.task_result import AdditionalMetadata, TaskMetadata -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 - -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): - """A simulator meant to run directly on the user's machine. - - This class wraps a BraketSimulator object so that it can be run and returns - results using constructs from the SDK rather than Braket IR. - """ - - def __init__( - self, - 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 - `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__( - name=delegate.__class__.__name__, - status="AVAILABLE", - ) - self._delegate = delegate - if noise_model: - self._validate_device_noise_model_support(noise_model) - self._noise_model = noise_model - - def run( - self, - task_specification: Union[ - Circuit, Problem, Program, AnalogHamiltonianSimulation, SerializableProgram - ], - shots: int = 0, - inputs: Optional[dict[str, float]] = None, - *args: Any, - **kwargs: Any, - ) -> LocalQuantumTask: - """Runs the given task with the wrapped local simulator. - - Args: - task_specification (Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, SerializableProgram]): # noqa E501 - The 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. - Sampling is not supported for shots=0. - 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 - of the simulation - - Note: - If running a circuit, the number of qubits will be passed - to the backend as the argument after the circuit itself. - - Examples: - >>> circuit = Circuit().h(0).cnot(0, 1) - >>> 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( # noqa: C901 - self, - task_specifications: Union[ - Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, SerializableProgram], - list[ - Union[Circuit, Problem, Program, AnalogHamiltonianSimulation, SerializableProgram] - ], - ], - shots: Optional[int] = 0, - max_parallel: Optional[int] = None, - inputs: Optional[Union[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, Program, AnalogHamiltonianSimulation, SerializableProgram], list[Union[Circuit, Problem, Program, 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. - max_parallel (Optional[int]): The maximum number of quantum tasks to run in parallel. Default - is the number of CPU. - 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: {}. - - Returns: - LocalQuantumTaskBatch: A batch containing all of the quantum tasks run - - See Also: - `braket.tasks.local_quantum_task_batch.LocalQuantumTaskBatch` - """ # noqa E501 - 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() - - single_task = isinstance( - task_specifications, - (Circuit, Program, Problem, AnalogHamiltonianSimulation), - ) - - single_input = isinstance(inputs, dict) - - 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) - - if single_input: - inputs = repeat(inputs) - - tasks_and_inputs = zip(task_specifications, inputs) - - if single_task and single_input: - tasks_and_inputs = [next(tasks_and_inputs)] - else: - tasks_and_inputs = list(tasks_and_inputs) - - for task_specification, input_map in tasks_and_inputs: - if isinstance(task_specification, Circuit): - 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}" - ) - - 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) - - @property - def properties(self) -> DeviceCapabilities: - """DeviceCapabilities: Return the device properties - - Please see `braket.device_schema` in amazon-braket-schemas-python_ - - .. _amazon-braket-schemas-python: https://github.com/aws/amazon-braket-schemas-python - """ - return self._delegate.properties - - @staticmethod - def registered_backends() -> set[str]: - """Gets the backends that have been registered as entry points - - Returns: - set[str]: The names of the available backends that can be passed - into LocalSimulator's constructor - """ - return set(_simulator_devices.keys()) - - def _run_internal_wrap( - self, - task_specification: Union[ - Circuit, Problem, Program, AnalogHamiltonianSimulation, SerializableProgram - ], - shots: Optional[int] = None, - inputs: Optional[dict[str, float]] = None, - *args, - **kwargs, - ) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: - """Wraps _run_interal for pickle dump""" - return self._run_internal( - task_specification, shots, inputs=inputs, *args, **kwargs - ) # pragma: no cover (this line sometimes doesn't get detected by codecov) - - @singledispatchmethod - def _get_simulator(self, simulator: Union[str, BraketSimulator]) -> LocalSimulator: - raise TypeError("Simulator must either be a string or a BraketSimulator instance") - - @_get_simulator.register - def _(self, backend_name: str): - 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): - return backend_impl - - @singledispatchmethod - def _run_internal( - self, - task_specification: Union[ - Circuit, Problem, Program, AnalogHamiltonianSimulation, AHSProgram, SerializableProgram - ], - shots: Optional[int] = None, - *args, - **kwargs, - ) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: - 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, - ): - 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) - 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) - 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: Program, - shots: Optional[int] = None, - inputs: Optional[dict[str, float]] = None, - *args, - **kwargs, - ): - simulator = self._delegate - if DeviceActionType.OPENQASM not in simulator.properties.action: - raise NotImplementedError(f"{type(simulator)} does not support OpenQASM programs") - if inputs: - inputs_copy = program.inputs.copy() if program.inputs is not None else {} - inputs_copy.update(inputs) - program = Program( - source=program.source, - inputs=inputs_copy, - ) - results = simulator.run(program, shots, *args, **kwargs) - return GateModelQuantumTaskResult.from_object(results) - - @_run_internal.register - def _( - self, - program: SerializableProgram, - shots: Optional[int] = None, - inputs: Optional[dict[str, float]] = None, - *args, - **kwargs, - ): - simulator = self._delegate - if DeviceActionType.OPENQASM not in simulator.properties.action: - raise NotImplementedError(f"{type(simulator)} does not support OpenQASM programs") - program = Program(source=program.to_ir(ir_type=IRType.OPENQASM, allow_implicit_build=True)) - if inputs: - inputs_copy = program.inputs.copy() if program.inputs is not None else {} - inputs_copy.update(inputs) - program = Program( - source=program.source, - inputs=inputs_copy, - ) - # Pass mcm=True to the simulator to enable mid-circuit measurement simulation. - # When setting mcm=True, the simulator returns only the measurement results, - # which we then wrap into a GateModelQuantumTaskResult object to return. - kwargs["mcm"] = True - results = simulator.run(program, shots, *args, **kwargs) - return GateModelQuantumTaskResult( - task_metadata=TaskMetadata.construct(id=""), - additional_metadata=AdditionalMetadata.construct(), - measurements=results, - ) - - @_run_internal.register - def _( - self, - program: AnalogHamiltonianSimulation, - shots: Optional[int] = None, - *args, - **kwargs, - ): - 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) - - @_run_internal.register - def _( - self, - program: AHSProgram, - shots: Optional[int] = None, - *args, - **kwargs, - ): - 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) diff --git a/src/braket/error_mitigation/__init__.py b/src/braket/error_mitigation/__init__.py deleted file mode 100644 index 5121cf10..00000000 --- a/src/braket/error_mitigation/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# 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.error_mitigation.debias import Debias # noqa: F401 -from braket.error_mitigation.error_mitigation import ErrorMitigation # noqa: F401 diff --git a/src/braket/error_mitigation/debias.py b/src/braket/error_mitigation/debias.py deleted file mode 100644 index 8beddc7e..00000000 --- a/src/braket/error_mitigation/debias.py +++ /dev/null @@ -1,22 +0,0 @@ -# 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 import error_mitigation -from braket.error_mitigation.error_mitigation import ErrorMitigation - - -class Debias(ErrorMitigation): - """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 deleted file mode 100644 index 95e6b658..00000000 --- a/src/braket/error_mitigation/error_mitigation.py +++ /dev/null @@ -1,29 +0,0 @@ -# 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 import error_mitigation - - -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/experimental/__init__.py b/src/braket/experimental/__init__.py deleted file mode 100644 index 89ff77e6..00000000 --- a/src/braket/experimental/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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. - -"""This module implements experimental features of the Amazon Braket SDK. -Features in this module may be changed, removed, or deprecated without notice. -""" - -from . import autoqasm # noqa: F401 diff --git a/src/braket/ipython_utils.py b/src/braket/ipython_utils.py deleted file mode 100644 index d850ee85..00000000 --- a/src/braket/ipython_utils.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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 sys - - -def running_in_jupyter() -> bool: - """Determine if running within Jupyter. - - Inspired by https://github.com/ipython/ipython/issues/11694 - - Returns: - bool: True if running in Jupyter, else False. - """ - in_ipython = 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"] - - ip = get_ipython() - in_ipython = ip is not None - - return getattr(ip, "kernel", None) is not None if in_ipython else False diff --git a/src/braket/jobs/__init__.py b/src/braket/jobs/__init__.py deleted file mode 100644 index 0a6fb520..00000000 --- a/src/braket/jobs/__init__.py +++ /dev/null @@ -1,36 +0,0 @@ -# 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.jobs.config import ( # noqa: F401 - CheckpointConfig, - InstanceConfig, - OutputDataConfig, - S3DataSourceConfig, - StoppingCondition, -) -from braket.jobs.data_persistence import ( # noqa: F401 - load_job_checkpoint, - load_job_result, - save_job_checkpoint, - save_job_result, -) -from braket.jobs.environment_variables import ( # noqa: F401 - get_checkpoint_dir, - get_hyperparameters, - get_input_data_dir, - get_job_device_arn, - get_job_name, - get_results_dir, -) -from braket.jobs.hybrid_job import hybrid_job # noqa: F401 -from braket.jobs.image_uris import Framework, retrieve_image # noqa: F401 diff --git a/src/braket/jobs/_entry_point_template.py b/src/braket/jobs/_entry_point_template.py deleted file mode 100644 index dceea98d..00000000 --- a/src/braket/jobs/_entry_point_template.py +++ /dev/null @@ -1,79 +0,0 @@ -run_entry_point = """ -import cloudpickle -import os -from braket.jobs import get_results_dir, save_job_result -from braket.jobs_data import PersistedJobDataFormat - - -# load and run serialized entry point function -recovered = cloudpickle.loads({serialized}) -def {function_name}(): - try: - # set working directory to results dir - os.chdir(get_results_dir()) - - # create symlinks to input data - links = link_input() - - result = recovered() - finally: - clean_links(links) - if result is not None: - save_job_result(result, data_format=PersistedJobDataFormat.PICKLED_V4) - return result -""" - -symlink_input_data = ''' -from pathlib import Path -from braket.jobs import get_input_data_dir - - -def make_link(input_link_path, input_data_path, links): - """ Create symlink from input_link_path to input_data_path. """ - input_link_path.parent.mkdir(parents=True, exist_ok=True) - input_link_path.symlink_to(input_data_path) - print(input_link_path, '->', input_data_path) - links[input_link_path] = input_data_path - - -def link_input(): - links = {{}} - dirs = set() - # map of data sources to lists of matched local files - prefix_matches = {prefix_matches} - - for channel, data in {input_data_items}: - - if channel in {prefix_channels}: - # link all matched files - for input_link_name in prefix_matches[channel]: - input_link_path = Path(input_link_name) - input_data_path = Path(get_input_data_dir(channel)) / input_link_path.name - make_link(input_link_path, input_data_path, links) - - else: - input_link_path = Path(data) - if channel in {directory_channels}: - # link directory source directly to input channel directory - input_data_path = Path(get_input_data_dir(channel)) - else: - # link file source to file within input channel directory - input_data_path = Path(get_input_data_dir(channel), Path(data).name) - make_link(input_link_path, input_data_path, links) - - return links - - -def clean_links(links): - for link, target in links.items(): - if link.is_symlink and link.readlink() == target: - link.unlink() - - if link.is_relative_to(Path()): - for dir in link.parents[:-1]: - try: - dir.rmdir() - except: - # directory not empty - pass -''' diff --git a/src/braket/jobs/config.py b/src/braket/jobs/config.py deleted file mode 100644 index 7c84b42d..00000000 --- a/src/braket/jobs/config.py +++ /dev/null @@ -1,83 +0,0 @@ -# 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 dataclasses import dataclass - - -@dataclass -class CheckpointConfig: - """Configuration that specifies the location where checkpoint data is stored.""" - - localPath: str = "/opt/jobs/checkpoints" - s3Uri: str | None = None - - -@dataclass -class InstanceConfig: - """Configuration of the instance(s) used to run the hybrid job.""" - - instanceType: str = "ml.m5.large" - volumeSizeInGb: int = 30 - instanceCount: int = 1 - - -@dataclass -class OutputDataConfig: - """Configuration that specifies the location for the output of the hybrid job.""" - - s3Path: str | None = None - kmsKeyId: str | None = None - - -@dataclass -class StoppingCondition: - """Conditions that specify when the hybrid job should be forcefully stopped.""" - - maxRuntimeInSeconds: int = 5 * 24 * 60 * 60 - - -@dataclass -class DeviceConfig: - device: str - - -class S3DataSourceConfig: - """Data source for data that lives on S3. - - Attributes: - config (dict[str, dict]): config passed to the Braket API - """ - - def __init__( - self, - s3_data: str, - content_type: str | None = None, - ): - """Create a definition for input data used by a Braket Hybrid job. - - Args: - s3_data (str): Defines the location of s3 data to train on. - content_type (str | None): MIME type of the input data (default: None). - """ - self.config = { - "dataSource": { - "s3DataSource": { - "s3Uri": s3_data, - } - } - } - - if content_type is not None: - self.config["contentType"] = content_type diff --git a/src/braket/jobs/data_persistence.py b/src/braket/jobs/data_persistence.py deleted file mode 100644 index 0386ed7a..00000000 --- a/src/braket/jobs/data_persistence.py +++ /dev/null @@ -1,192 +0,0 @@ -# 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 pathlib import Path -from typing import Any - -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( - checkpoint_data: dict[str, Any], - 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 - `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`. - - Note: This function for storing the checkpoints is only for use inside the job container - as it writes data to directories and references env variables set in the containers. - - - Args: - checkpoint_data (dict[str, Any]): Dict that specifies the checkpoint data to be persisted. - checkpoint_file_suffix (str): str that specifies the file suffix to be used for - the checkpoint filename. The resulting filename - `f"{job_name}(_{checkpoint_file_suffix}).json"` is used to save the checkpoints. - Default: "" - data_format (PersistedJobDataFormat): 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: - ValueError: If the supplied `checkpoint_data` is `None` or empty. - """ - if not checkpoint_data: - raise ValueError("The checkpoint_data argument cannot be empty.") - checkpoint_directory = get_checkpoint_dir() - job_name = get_job_name() - checkpoint_file_path = ( - f"{checkpoint_directory}/{job_name}_{checkpoint_file_suffix}.json" - if checkpoint_file_suffix - else f"{checkpoint_directory}/{job_name}.json" - ) - with open(checkpoint_file_path, "w") 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()) - - -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 - 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 - job's name. - - Note: This function for loading hybrid job checkpoints is only for use inside the job container - as it writes data to directories and references env variables set in the containers. - - - Args: - job_name (str | None): str that specifies the name of the job whose checkpoints - are to be loaded. Default: current job name. - - checkpoint_file_suffix (str): str specifying the file suffix that is used to - locate the checkpoint file to load. The resulting file name - `f"{job_name}(_{checkpoint_file_suffix}).json"` is used to locate the - checkpoint file. Default: "" - - Returns: - dict[str, Any]: Dict that contains the checkpoint data persisted in the checkpoint file. - - Raises: - FileNotFoundError: If the file `f"{job_name}(_{checkpoint_file_suffix})"` could not be found - in the directory specified by the container environment variable `CHECKPOINT_DIR`. - ValueError: If the data stored in the checkpoint file can't be deserialized (possibly due to - corruption). - """ - job_name = job_name or get_job_name() - checkpoint_directory = get_checkpoint_dir() - checkpoint_file_path = ( - f"{checkpoint_directory}/{job_name}_{checkpoint_file_suffix}.json" - if checkpoint_file_suffix - else f"{checkpoint_directory}/{job_name}.json" - ) - with open(checkpoint_file_path) as f: - persisted_data = PersistedJobData.parse_raw(f.read()) - deserialized_data = deserialize_values( - persisted_data.dataDictionary, persisted_data.dataFormat - ) - return deserialized_data - - -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: - return PersistedJobData.parse_raw(f.read()) - except FileNotFoundError: - return PersistedJobData( - dataDictionary={}, - dataFormat=PersistedJobDataFormat.PLAINTEXT, - ) - - -def load_job_result(filename: str | Path | None = None) -> dict[str, Any]: - """Loads job result of currently running job. - - Args: - filename (str | Path | None): Location of job results. Default `results.json` in job - results directory in a job instance or in working directory locally. This file - must be in the format used by `save_job_result`. - - Returns: - 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 - - -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 - environment variable `AMZN_BRAKET_JOB_RESULTS_DIR`, with the filename 'results.json'. - The `result_data` values are serialized to the specified `data_format`. - - Note: This function for storing the results is only for use inside the job container - as it writes data to directories and references env variables set in the containers. - - Args: - result_data (dict[str, Any] | Any): Dict that specifies the result data to be - persisted. If result data is not a dict, then it will be wrapped as - `{"result": result_data}`. - 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} - - current_persisted_data = _load_persisted_data() - - if current_persisted_data.dataFormat == PersistedJobDataFormat.PICKLED_V4: - # if results are already pickled, maintain pickled format - # if user explicitly specifies plaintext, raise error - if data_format == PersistedJobDataFormat.PLAINTEXT: - raise TypeError( - "Cannot update results object serialized with " - f"{current_persisted_data.dataFormat.value} using data format " - f"{data_format.value}." - ) - - data_format = PersistedJobDataFormat.PICKLED_V4 - - # if not specified or already pickled, default to plaintext - data_format = data_format or PersistedJobDataFormat.PLAINTEXT - - current_results = deserialize_values( - current_persisted_data.dataDictionary, - current_persisted_data.dataFormat, - ) - updated_results = current_results | result_data - - with open(Path(get_results_dir()) / "results.json", "w") 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 deleted file mode 100644 index 6d7d1836..00000000 --- a/src/braket/jobs/environment_variables.py +++ /dev/null @@ -1,77 +0,0 @@ -# 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 - - -def get_job_name() -> str: - """Get the name of the current job. - - Returns: - str: The name of the job if in a job, else an empty string. - """ - return os.getenv("AMZN_BRAKET_JOB_NAME", "") - - -def get_job_device_arn() -> str: - """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". - """ - return os.getenv("AMZN_BRAKET_DEVICE_ARN", "local:none/none") - - -def get_input_data_dir(channel: str = "input") -> str: - """Get the job input data directory. - - Args: - channel (str): The name of the input channel. Default value - corresponds to the default input channel name, `input`. - - Returns: - str: The input directory, defaulting to current working directory. - """ - input_dir = os.getenv("AMZN_BRAKET_INPUT_DIR", ".") - return f"{input_dir}/{channel}" if input_dir != "." else input_dir - - -def get_results_dir() -> str: - """Get the job result directory. - - Returns: - str: The results directory, defaulting to current working directory. - """ - return os.getenv("AMZN_BRAKET_JOB_RESULTS_DIR", ".") - - -def get_checkpoint_dir() -> str: - """Get the job checkpoint directory. - - Returns: - str: The checkpoint directory, defaulting to current working directory. - """ - return os.getenv("AMZN_BRAKET_CHECKPOINT_DIR", ".") - - -def get_hyperparameters() -> dict[str, str]: - """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")) as f: - return json.load(f) - return {} diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py deleted file mode 100644 index 77f5f43d..00000000 --- a/src/braket/jobs/hybrid_job.py +++ /dev/null @@ -1,428 +0,0 @@ -# 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 functools -import importlib.util -import inspect -import re -import shutil -import sys -import tempfile -import warnings -from collections.abc import Callable, Iterable -from logging import Logger, getLogger -from pathlib import Path -from types import ModuleType -from typing import Any - -import cloudpickle - -from braket.aws.aws_session import AwsSession -from braket.jobs._entry_point_template import run_entry_point, symlink_input_data -from braket.jobs.config import ( - CheckpointConfig, - InstanceConfig, - OutputDataConfig, - S3DataSourceConfig, - StoppingCondition, -) -from braket.jobs.image_uris import Framework, built_in_images, retrieve_image -from braket.jobs.quantum_job import QuantumJob -from braket.jobs.quantum_job_creation import _generate_default_job_name - - -def hybrid_job( - *, - device: str | None, - include_modules: str | ModuleType | Iterable[str | ModuleType] | None = None, - dependencies: str | Path | list[str] | None = None, - local: bool = False, - job_name: str | None = None, - image_uri: str | None = None, - input_data: str | dict | S3DataSourceConfig | None = None, - wait_until_complete: bool = False, - instance_config: InstanceConfig | None = None, - distribution: str | None = None, - copy_checkpoints_from_job: str | None = None, - checkpoint_config: CheckpointConfig | None = None, - role_arn: str | None = None, - stopping_condition: StoppingCondition | None = None, - output_data_config: OutputDataConfig | None = None, - 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 - when the decorated function is called. - - 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`, `logger`, and `quiet`. - - 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 - queue so that only one hybrid job is running at a time. The device string is accessible - in the hybrid job instance as the environment variable "AMZN_BRAKET_DEVICE_ARN". - When using embedded simulators, you may provide the device argument as string of the - form: "local:/" or `None`. - - include_modules (str | ModuleType | Iterable[str | ModuleType] | None): Either a - single module or module name or a list of module or module names referring to local - modules to be included. Any references to members of these modules in the hybrid job - algorithm code will be serialized as part of the algorithm code. Default: `[]` - - dependencies (str | Path | list[str] | None): Path (absolute or relative) to a - requirements.txt file, or alternatively a list of strings, with each string being a - `requirement specifier `_, to be used for the hybrid job. - - local (bool): Whether to use local mode for the hybrid job. Default: `False` - - job_name (str | None): A string that specifies the name with which the job is created. - Allowed pattern for job name: `^[a-zA-Z0-9](-*[a-zA-Z0-9]){0,50}$`. Defaults to - f'{decorated-function-name}-{timestamp}'. - - image_uri (str | None): A str that specifies the ECR image to use for executing the job. - `retrieve_image()` function may be used for retrieving the ECR image URIs - for the containers supported by Braket. Default: ``. - - input_data (str | dict | S3DataSourceConfig | None): Information about the training - data. Dictionary maps channel names to local paths or S3 URIs. Contents found - at any local paths will be uploaded to S3 at - f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}'. If a local - path, S3 URI, or S3DataSourceConfig is provided, it will be given a default - channel name "input". - Default: {}. - - wait_until_complete (bool): `True` if we should wait until the job completes. - This would tail the job logs as it waits. Otherwise `False`. Ignored if using - local mode. Default: `False`. - - instance_config (InstanceConfig | None): Configuration of the instance(s) for running the - classical code for the hybrid job. Default: - `InstanceConfig(instanceType='ml.m5.large', instanceCount=1, volumeSizeInGB=30)`. - - distribution (str | None): A str that specifies how the job should be distributed. - If set to "data_parallel", the hyperparameters for the job will be set to use data - parallelism features for PyTorch or TensorFlow. Default: `None`. - - copy_checkpoints_from_job (str | None): A str that specifies the job ARN whose - checkpoint you want to use in the current job. Specifying this value will copy - over the checkpoint data from `use_checkpoints_from_job`'s checkpoint_config - s3Uri to the current job's checkpoint_config s3Uri, making it available at - checkpoint_config.localPath during the job execution. Default: `None` - - checkpoint_config (CheckpointConfig | None): Configuration that specifies the - location where checkpoint data is stored. - Default: `CheckpointConfig(localPath='/opt/jobs/checkpoints', - s3Uri=f's3://{default_bucket_name}/jobs/{job_name}/checkpoints')`. - - role_arn (str | None): A str providing the IAM role ARN used to execute the - script. Default: IAM role returned by AwsSession's `get_default_jobs_role()`. - - stopping_condition (StoppingCondition | None): The maximum length of time, in seconds, - and the maximum number of tasks that a job can run before being forcefully stopped. - Default: StoppingCondition(maxRuntimeInSeconds=5 * 24 * 60 * 60). - - output_data_config (OutputDataConfig | None): Specifies the location for the output of - the job. - Default: `OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data', - kmsKeyId=None)`. - - aws_session (AwsSession | None): AwsSession for connecting to AWS Services. - Default: AwsSession() - - tags (dict[str, str] | None): Dict specifying the key-value pairs for tagging this job. - Default: {}. - - 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. - - Returns: - Callable: the callable for creating a Hybrid Job. - """ - _validate_python_version(image_uri, aws_session) - - def _hybrid_job(entry_point: Callable) -> Callable: - @functools.wraps(entry_point) - 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. - """ - with _IncludeModules(include_modules), tempfile.TemporaryDirectory( - dir="", prefix="decorator_job_" - ) as temp_dir: - 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), - ] - ) - entry_point_file.write(template) - - if dependencies: - _process_dependencies(dependencies, temp_dir_path) - - job_args = { - "device": device or "local:none/none", - "source_module": temp_dir, - "entry_point": ( - f"{temp_dir}.{entry_point_file_path.stem}:{entry_point.__name__}" - ), - "wait_until_complete": wait_until_complete, - "job_name": job_name or _generate_default_job_name(func=entry_point), - "hyperparameters": _log_hyperparameters(entry_point, args, kwargs), - "logger": logger, - } - optional_args = { - "image_uri": image_uri, - "input_data": input_data, - "instance_config": instance_config, - "distribution": distribution, - "checkpoint_config": checkpoint_config, - "copy_checkpoints_from_job": copy_checkpoints_from_job, - "role_arn": role_arn, - "stopping_condition": stopping_condition, - "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(): - if value is not None: - job_args[key] = value - - job = _create_job(job_args, local) - return job - - return job_wrapper - - return _hybrid_job - - -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() - # user provides a custom image_uri - if image_uri and image_uri not in built_in_images(aws_session.region): - print( - "Skipping python version validation, make sure versions match " - "between local environment and container." - ) - else: - # set default image_uri to base - 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 (sys.version_info.major, sys.version_info.minor) != ( - int(major_version), - int(minor_version), - ): - raise RuntimeError( - "Python version must match between local environment and container. " - f"Client is running Python {sys.version_info.major}.{sys.version_info.minor} " - f"locally, but container uses Python {major_version}.{minor_version}." - ) - - -def _process_dependencies(dependencies: str | Path | list[str], temp_dir: Path) -> None: - if isinstance(dependencies, (str, Path)): - # requirements file - shutil.copy(Path(dependencies).resolve(), temp_dir / "requirements.txt") - else: - # list of packages - with open(temp_dir / "requirements.txt", "w") as f: - f.write("\n".join(dependencies)) - - -class _IncludeModules: - def __init__(self, modules: str | ModuleType | Iterable[str | ModuleType] = None): - modules = modules or [] - if isinstance(modules, (str, ModuleType)): - modules = [modules] - self._modules = [ - (importlib.import_module(module) if isinstance(module, str) else module) - for module in modules - ] - - def __enter__(self): - """Register included modules with cloudpickle to be pickled by value""" - for module in self._modules: - cloudpickle.register_pickle_by_value(module) - - def __exit__(self, exc_type, exc_val, exc_tb): - """Unregister included modules with cloudpickle to be pickled by value""" - for module in self._modules: - cloudpickle.unregister_pickle_by_value(module) - - -def _serialize_entry_point(entry_point: Callable, args: tuple, kwargs: dict) -> str: - """Create an entry point from a function""" - wrapped_entry_point = functools.partial(entry_point, *args, **kwargs) - - try: - serialized = cloudpickle.dumps(wrapped_entry_point) - except Exception as e: - raise RuntimeError( - "Serialization failed for decorator hybrid job. If you are referencing " - "an object from outside the function scope, either directly or through " - "function parameters, try instantiating the object inside the decorated " - "function instead." - ) from e - - return run_entry_point.format( - serialized=serialized, - function_name=entry_point.__name__, - ) - - -def _log_hyperparameters(entry_point: Callable, args: tuple, kwargs: dict) -> dict: - """Capture function arguments as hyperparameters""" - signature = inspect.signature(entry_point) - bound_args = signature.bind(*args, **kwargs) - bound_args.apply_defaults() - hyperparameters = {} - for param, value in bound_args.arguments.items(): - param_kind = signature.parameters[param].kind - 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) - else: - warnings.warn( - "Positional only arguments will not be logged to the hyperparameters file.", - stacklevel=1, - ) - return {name: _sanitize(value) for name, value in hyperparameters.items()} - - -def _sanitize(hyperparameter: Any) -> str: - """Sanitize forbidden characters from hp strings""" - string_hp = str(hyperparameter) - - sanitized = ( - string_hp - # replace forbidden characters with close matches - .replace("\n", " ") - .replace("$", "?") - .replace("(", "{") - .replace("&", "+") - .replace("`", "'") - # not technically forbidden, but to avoid mismatched parens - .replace(")", "}") - ) - - # 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 sanitized - - -def _process_input_data(input_data: dict) -> list[str]: - """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 - input data matches exactly one directory: cwd/dirname/* -> channel/* - else (multiple matches, possibly including exact): - cwd/prefix_match -> channel/prefix_match, for each match - """ - input_data = input_data or {} - if not isinstance(input_data, dict): - 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(prefix)] - - def is_prefix(path: str) -> bool: - return len(matches(path)) > 1 or not Path(path).exists() - - prefix_channels = set() - directory_channels = set() - file_channels = set() - - for channel, data in input_data.items(): - if AwsSession.is_s3_uri(str(data)) or isinstance(data, S3DataSourceConfig): - channel_arg = f'channel="{channel}"' if channel != "input" else "" - print( - "Input data channels mapped to an S3 source will not be available in " - 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(str(data)): - prefix_channels.add(channel) - elif Path(data).is_dir(): - directory_channels.add(channel) - else: - file_channels.add(channel) - - return symlink_input_data.format( - prefix_matches={channel: matches(input_data[channel]) for channel in prefix_channels}, - input_data_items=[ - (channel, data) - for channel, data in input_data.items() - if channel in prefix_channels | directory_channels | file_channels - ], - prefix_channels=prefix_channels, - directory_channels=directory_channels, - ) - - -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 - - for aws_only_arg in [ - "wait_until_complete", - "copy_checkpoints_from_job", - "instance_config", - "distribution", - "stopping_condition", - "tags", - "logger", - ]: - if aws_only_arg in job_args: - del job_args[aws_only_arg] - return LocalQuantumJob.create(**job_args) - else: - from braket.aws import AwsQuantumJob - - return AwsQuantumJob.create(**job_args) diff --git a/src/braket/jobs/image_uri_config/base.json b/src/braket/jobs/image_uri_config/base.json deleted file mode 100644 index eb71e60f..00000000 --- a/src/braket/jobs/image_uri_config/base.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "registry": "292282985366", - "repository": "amazon-braket-base-jobs", - "supported_regions": [ - "us-east-1", - "us-west-1", - "us-west-2", - "eu-west-2" - ] -} diff --git a/src/braket/jobs/image_uri_config/pl_pytorch.json b/src/braket/jobs/image_uri_config/pl_pytorch.json deleted file mode 100644 index c7e28fbd..00000000 --- a/src/braket/jobs/image_uri_config/pl_pytorch.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "registry": "292282985366", - "repository": "amazon-braket-pytorch-jobs", - "supported_regions": [ - "us-east-1", - "us-west-1", - "us-west-2", - "eu-west-2" - ] -} diff --git a/src/braket/jobs/image_uri_config/pl_tensorflow.json b/src/braket/jobs/image_uri_config/pl_tensorflow.json deleted file mode 100644 index 3278a871..00000000 --- a/src/braket/jobs/image_uri_config/pl_tensorflow.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "registry": "292282985366", - "repository": "amazon-braket-tensorflow-jobs", - "supported_regions": [ - "us-east-1", - "us-west-1", - "us-west-2", - "eu-west-2" - ] -} diff --git a/src/braket/jobs/image_uris.py b/src/braket/jobs/image_uris.py deleted file mode 100644 index af6c5012..00000000 --- a/src/braket/jobs/image_uris.py +++ /dev/null @@ -1,95 +0,0 @@ -# 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 -from enum import Enum -from functools import cache - - -class Framework(str, Enum): - """Supported Frameworks for pre-built containers""" - - BASE = "BASE" - PL_TENSORFLOW = "PL_TENSORFLOW" - PL_PYTORCH = "PL_PYTORCH" - - -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} - - -@cache -def retrieve_image(framework: Framework, region: str) -> str: - """Retrieves the ECR URI for the Docker image matching the specified arguments. - - Args: - framework (Framework): The name of the framework. - region (str): The AWS region for the Docker image. - - Returns: - str: The ECR URI for the corresponding Amazon Braket Docker image. - - Raises: - ValueError: If any of the supplied values are invalid or the combination of inputs - specified is not supported. - """ - # Validate framework - framework = Framework(framework) - config = _config_for_framework(framework) - registry = _registry_for_region(config, region) - tag = f"{config['repository']}:latest" - return f"{registry}.dkr.ecr.{region}.amazonaws.com/{tag}" - - -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. - """ - 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: - """Retrieves the registry for the specified region from the configuration. - - Args: - config (dict[str, str]): Dict containing the framework configuration. - region (str): str that specifies the region for which the registry is retrieved. - - Returns: - str: str that specifies the registry for the supplied region. - - Raises: - ValueError: If the supplied region is invalid or not supported. - """ - if region not in (supported_regions := config["supported_regions"]): - raise ValueError( - f"Unsupported region: {region}. You may need to upgrade your SDK version for newer " - f"regions. Supported region(s): {supported_regions}" - ) - return config["registry"] diff --git a/src/braket/jobs/local/__init__.py b/src/braket/jobs/local/__init__.py deleted file mode 100644 index 6fe353f7..00000000 --- a/src/braket/jobs/local/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# 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.jobs.local.local_job import LocalQuantumJob # noqa: F401 diff --git a/src/braket/jobs/local/local_job.py b/src/braket/jobs/local/local_job.py deleted file mode 100644 index 4dd15607..00000000 --- a/src/braket/jobs/local/local_job.py +++ /dev/null @@ -1,344 +0,0 @@ -# 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 time -from typing import Any - -from braket.aws.aws_session import AwsSession -from braket.jobs.config import CheckpointConfig, OutputDataConfig, S3DataSourceConfig -from braket.jobs.image_uris import Framework, retrieve_image -from braket.jobs.local.local_job_container import _LocalJobContainer -from braket.jobs.local.local_job_container_setup import setup_container -from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType -from braket.jobs.metrics_data.log_metrics_parser import LogMetricsParser -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): - """Amazon Braket implementation of a hybrid job that runs locally.""" - - @classmethod - def create( - cls, - device: str, - source_module: str, - entry_point: str | None = None, - image_uri: str | None = None, - job_name: str | None = None, - code_location: str | None = None, - role_arn: str | None = None, - hyperparameters: dict[str, Any] | None = None, - input_data: str | dict | S3DataSourceConfig | None = None, - output_data_config: OutputDataConfig | None = None, - checkpoint_config: CheckpointConfig | None = None, - aws_session: AwsSession | None = None, - local_container_update: bool = True, - ) -> LocalQuantumJob: - """Creates and runs hybrid job by setting up and running the customer script in a local - docker container. - - Args: - device (str): 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 queue so that only one hybrid job is running at a time. The device string is - accessible in the hybrid job instance as the environment variable - "AMZN_BRAKET_DEVICE_ARN". When using embedded simulators, you may provide the device - argument as a string of the form: "local:/". - - source_module (str): Path (absolute, relative or an S3 URI) to a python module to be - tarred and uploaded. If `source_module` is an S3 URI, it must point to a - tar.gz file. Otherwise, source_module may be a file or directory. - - entry_point (str | None): A str that specifies the entry point of the hybrid job, - relative to the source module. The entry point must be in the format - `importable.module` or `importable.module:callable`. For example, - `source_module.submodule:start_here` indicates the `start_here` function - contained in `source_module.submodule`. If source_module is an S3 URI, - entry point must be given. Default: source_module's name - - image_uri (str | None): A str that specifies the ECR image to use for executing the - hybrid job. `image_uris.retrieve_image()` function may be used for retrieving the - ECR image URIs for the containers supported by Braket. - Default = ``. - - job_name (str | None): A str that specifies the name with which the hybrid job is - created. - Default: f'{image_uri_type}-{timestamp}'. - - code_location (str | None): The S3 prefix URI where custom code will be uploaded. - Default: f's3://{default_bucket_name}/jobs/{job_name}/script'. - - role_arn (str | None): This field is currently not used for local hybrid jobs. Local - hybrid jobs will use the current role's credentials. This may be subject to change. - - hyperparameters (dict[str, Any] | None): Hyperparameters accessible to the hybrid job. - The hyperparameters are made accessible as a Dict[str, str] to the hybrid job. - For convenience, this accepts other types for keys and values, but `str()` - is called to convert them before being passed on. Default: None. - - input_data (str | dict | S3DataSourceConfig | None): Information about the training - data. Dictionary maps channel names to local paths or S3 URIs. Contents found - at any local paths will be uploaded to S3 at - f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local - path, S3 URI, or S3DataSourceConfig is provided, it will be given a default - channel name "input". - Default: {}. - - output_data_config (OutputDataConfig | None): Specifies the location for the output of - the hybrid job. - Default: OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data', - kmsKeyId=None). - - checkpoint_config (CheckpointConfig | None): Configuration that specifies the location - where checkpoint data is stored. - Default: CheckpointConfig(localPath='/opt/jobs/checkpoints', - s3Uri=f's3://{default_bucket_name}/jobs/{job_name}/checkpoints'). - - aws_session (AwsSession | None): AwsSession for connecting to AWS Services. - Default: AwsSession() - - local_container_update (bool): Perform an update, if available, from ECR to the local - 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. - """ - create_job_kwargs = prepare_quantum_job( - device=device, - source_module=source_module, - entry_point=entry_point, - image_uri=image_uri, - job_name=job_name, - code_location=code_location, - role_arn=role_arn, - hyperparameters=hyperparameters, - input_data=input_data, - output_data_config=output_data_config, - checkpoint_config=checkpoint_config, - aws_session=aws_session, - ) - - job_name = create_job_kwargs["jobName"] - if os.path.isdir(job_name): - raise ValueError( - f"A local directory called {job_name} already exists. " - f"Please use a different job name." - ) - - session = aws_session or AwsSession() - algorithm_specification = create_job_kwargs["algorithmSpecification"] - if "containerImage" in algorithm_specification: - image_uri = algorithm_specification["containerImage"]["uri"] - else: - image_uri = retrieve_image(Framework.BASE, session.region) - - with _LocalJobContainer( - image_uri=image_uri, force_update=local_container_update - ) as container: - 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: - log_file.write(container.run_log) - if "checkpointConfig" in create_job_kwargs: - checkpoint_config = create_job_kwargs["checkpointConfig"] - if "localPath" in checkpoint_config: - checkpoint_path = checkpoint_config["localPath"] - container.copy_from(checkpoint_path, os.path.join(job_name, "checkpoints")) - run_log = container.run_log - 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. - - 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") - self._arn = arn - self._run_log = run_log - self._name = arn.partition("job/")[-1] - if not run_log and not os.path.isdir(self.name): - raise ValueError(f"Unable to find local job results for {self.name}") - - @property - def arn(self) -> str: - """str: The ARN (Amazon Resource Name) of the hybrid job.""" - return self._arn - - @property - def name(self) -> str: - """str: The name of the hybrid job.""" - return self._name - - @property - 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")) as log_file: - self._run_log = log_file.read() - 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: - """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". - """ - return "COMPLETED" - - 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 - """ - - def cancel(self) -> str: - """When running the hybrid job in local mode, the cancelling a running is not possible. - - Returns: - str: None - """ - - def download_result( - self, - extract_to: str | None = None, - poll_timeout_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_TIMEOUT, - poll_interval_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_INTERVAL, - ) -> None: - """When running the hybrid job in local mode, results are automatically stored locally. - - Args: - extract_to (str | None): The directory to which the results are extracted. The results - are extracted to a folder titled with the hybrid job name within this directory. - Default= `Current working directory`. - poll_timeout_seconds (float): The polling timeout, in seconds, for `result()`. - Default: 10 days. - poll_interval_seconds (float): The polling interval, in seconds, for `result()`. - Default: 5 seconds. - """ - - def result( - self, - poll_timeout_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_TIMEOUT, - poll_interval_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_INTERVAL, - ) -> dict[str, Any]: - """Retrieves the `LocalQuantumJob` result persisted using `save_job_result` function. - - Args: - poll_timeout_seconds (float): The polling timeout, in seconds, for `result()`. - Default: 10 days. - 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")) as f: - persisted_data = PersistedJobData.parse_raw(f.read()) - deserialized_data = deserialize_values( - persisted_data.dataDictionary, persisted_data.dataFormat - ) - return deserialized_data - except FileNotFoundError as e: - raise ValueError( - f"Unable to find results in the local job directory {self.name}." - ) from e - - def metrics( - self, - metric_type: MetricType = MetricType.TIMESTAMP, - statistic: MetricStatistic = MetricStatistic.MAX, - ) -> 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. - - Args: - metric_type (MetricType): The type of metrics to get. Default: MetricType.TIMESTAMP. - statistic (MetricStatistic): The statistic to determine which metric value to use - when there is a conflict. Default: MetricStatistic.MAX. - - Example: - timestamp energy - 0 0.1 - 1 0.2 - would be represented as: - { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } - values may be integers, floats, strings or None. - - Returns: - dict[str, list[Any]]: The metrics data. - """ - parser = LogMetricsParser() - current_time = str(time.time()) - for line in self.run_log.splitlines(): - if line.startswith("Metrics -"): - parser.parse_log_message(current_time, line) - return parser.get_parsed_metrics(metric_type, statistic) - - def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: - """Display container logs for a given hybrid job - - Args: - wait (bool): `True` to keep looking for new log entries until the hybrid job completes; - otherwise `False`. Default: `False`. - poll_interval_seconds (int): The interval of time, in seconds, between polling for - new log entries and hybrid job completion (default: 5). - - """ - return print(self.run_log) diff --git a/src/braket/jobs/local/local_job_container.py b/src/braket/jobs/local/local_job_container.py deleted file mode 100644 index 6d9d08f4..00000000 --- a/src/braket/jobs/local/local_job_container.py +++ /dev/null @@ -1,297 +0,0 @@ -# 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 base64 -import re -import subprocess -from logging import Logger, getLogger -from pathlib import PurePosixPath - -from braket.aws.aws_session import AwsSession - - -class _LocalJobContainer: - """Uses docker CLI to run Braket Hybrid Jobs on a local docker container.""" - - ECR_URI_PATTERN = r"^((\d+)\.dkr\.ecr\.([^.]+)\.[^/]*)/([^:]*):(.*)$" - CONTAINER_CODE_PATH = "/opt/ml/code/" - - def __init__( - self, - image_uri: str, - aws_session: AwsSession | None = None, - logger: Logger = getLogger(__name__), - force_update: bool = False, - ): - """Represents and provides functions for interacting with a Braket Hybrid Jobs docker - 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. - 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 available. - Default: False - """ - self._aws_session = aws_session or AwsSession() - self.image_uri = image_uri - self.run_log = None - self._container_name = None - self._logger = logger - self._force_update = force_update - - def __enter__(self): - """Creates and starts the local docker container.""" - self._container_name = self._start_container(self.image_uri, self._force_update) - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - """Stops and removes the local docker container.""" - self._end_session() - - @staticmethod - 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 - their values. - - Returns: - 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 = [] - for key in environment_variables: - env_list.append("-e") - env_list.append(f"{key}={environment_variables[key]}") - return env_list - - @staticmethod - 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. - - Returns: - str: The UTF-8 encoded output of running the command. - """ - output = subprocess.check_output(command) - return output.decode("utf-8").strip() - - def _login_to_ecr(self, account_id: str, ecr_url: str) -> None: - """Logs in docker to an ECR repository using the client AWS credentials. - - 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]) - if not authorization_data_result: - raise ValueError( - "Unable to get permissions to access to log in to docker. " - "Please pull down the container before proceeding." - ) - authorization_data = authorization_data_result["authorizationData"][0] - raw_token = base64.b64decode(authorization_data["authorizationToken"]) - token = raw_token.decode("utf-8").strip("AWS:") - subprocess.run(["docker", "login", "-u", "AWS", "-p", token, ecr_url]) - - def _pull_image(self, image_uri: str) -> None: - """Pulls an image from ECR. - - 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) - if not ecr_pattern_match: - raise ValueError( - f"The URL {image_uri} is not available locally and does not seem to " - f"be a valid AWS ECR URL." - "Please pull down the container, or specify a valid ECR URL, " - "before proceeding." - ) - 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]) - - def _start_container(self, image_uri: str, force_update: bool) -> str: - """Runs a docker container in a busy loop so that it will accept further commands. The - call to this function must be matched with end_session to stop the container. - - Args: - 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. - """ - image_name = self._check_output_formatted(["docker", "images", "-q", image_uri]) - if not image_name: - self._pull_image(image_uri) - image_name = self._check_output_formatted(["docker", "images", "-q", image_uri]) - if not image_name: - raise ValueError( - f"The URL {image_uri} is not available locally and can not be pulled from ECR." - " Please pull down the container before proceeding." - ) - elif force_update: - try: - self._pull_image(image_uri) - image_name = self._check_output_formatted(["docker", "images", "-q", image_uri]) - 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"] - ) - - def makedir(self, dir_path: str) -> None: - """Creates a directory path in the container. - - Args: - dir_path(str): The directory path to create. - - Raises: - subprocess.CalledProcessError: If unable to make the directory. - """ - try: - 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 - - def copy_to(self, source: str, destination: str) -> None: - """Copies a local file or directory to the container. - - Args: - source(str): The local file or directory to copy. - destination(str): The path to the file or directory where the source should be copied. - - Raises: - subprocess.CalledProcessError: If unable to copy. - """ - 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}"] - ) - except subprocess.CalledProcessError as e: - output = e.output.decode("utf-8").strip() - self._logger.error(output) - raise e - - def copy_from(self, source: str, destination: str) -> None: - """Copies a file or directory from the container locally. - - Args: - source(str): The container file or directory to copy. - destination(str): The path to the file or directory where the source should be copied. - - Raises: - subprocess.CalledProcessError: If unable to copy. - """ - try: - 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 - - def run_local_job( - self, - 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 - 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"] - ) - if not start_program_name: - raise ValueError( - "Start program not found. " - "The specified container is not setup to run Braket Jobs. " - "Please see setup instructions for creating your own containers." - ) - - 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) - - 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) - - def _end_session(self) -> None: - """Stops and removes the local container.""" - subprocess.run(["docker", "stop", self._container_name]) - - -def _stream_output(process: subprocess.Popen) -> str: - exit_code = None - run_log = "" - - while exit_code is None: - stdout = process.stdout.readline().decode("utf-8") - print(stdout, end="") - run_log += stdout - exit_code = process.poll() - - if exit_code != 0: - error_message = f"Process exited with code: {exit_code}" - print(error_message) - run_log += error_message - - return run_log diff --git a/src/braket/jobs/local/local_job_container_setup.py b/src/braket/jobs/local/local_job_container_setup.py deleted file mode 100644 index 65cef387..00000000 --- a/src/braket/jobs/local/local_job_container_setup.py +++ /dev/null @@ -1,279 +0,0 @@ -# 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 tempfile -from collections.abc import Iterable -from logging import Logger, getLogger -from pathlib import Path -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: 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. - - 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 - options requested by the customer. - """ - logger = getLogger(__name__) - _create_expected_paths(container, **creation_kwargs) - run_environment_variables = {} - run_environment_variables |= _get_env_credentials(aws_session, logger) - run_environment_variables.update( - _get_env_script_mode_config(creation_kwargs["algorithmSpecification"]["scriptModeConfig"]) - ) - run_environment_variables.update(_get_env_default_vars(aws_session, **creation_kwargs)) - if _copy_hyperparameters(container, **creation_kwargs): - run_environment_variables.update(_get_env_hyperparameters()) - if _copy_input_data_list(container, aws_session, **creation_kwargs): - run_environment_variables.update(_get_env_input_data()) - return run_environment_variables - - -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]: - """Gets the account credentials from boto so they can be added as environment variables to - the running container. - - Args: - aws_session (AwsSession): AwsSession for connecting to AWS Services. - 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 - to the running container. - """ - credentials = aws_session.boto_session.get_credentials() - if credentials.token is None: - logger.info("Using the long-lived AWS credentials found in session") - return { - "AWS_ACCESS_KEY_ID": str(credentials.access_key), - "AWS_SECRET_ACCESS_KEY": str(credentials.secret_key), - } - logger.warning( - "Using the short-lived AWS credentials found in session. They might expire while running." - ) - return { - "AWS_ACCESS_KEY_ID": str(credentials.access_key), - "AWS_SECRET_ACCESS_KEY": str(credentials.secret_key), - "AWS_SESSION_TOKEN": str(credentials.token), - } - - -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 - parameters for running a Braket Hybrid Job. - - Returns: - dict[str, str]: The set of key/value pairs that should be added as environment variables - to the running container. - """ - result = { - "AMZN_BRAKET_SCRIPT_S3_URI": script_mode_config["s3Uri"], - "AMZN_BRAKET_SCRIPT_ENTRY_POINT": script_mode_config["entryPoint"], - } - if "compressionType" in script_mode_config: - result["AMZN_BRAKET_SCRIPT_COMPRESSION_TYPE"] = script_mode_config["compressionType"] - return result - - -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 - to the running container. - """ - job_name = creation_kwargs["jobName"] - bucket, location = AwsSession.parse_s3_uri(creation_kwargs["outputDataConfig"]["s3Path"]) - return { - "AWS_DEFAULT_REGION": aws_session.region, - "AMZN_BRAKET_JOB_NAME": job_name, - "AMZN_BRAKET_DEVICE_ARN": creation_kwargs["deviceConfig"]["device"], - "AMZN_BRAKET_JOB_RESULTS_DIR": "/opt/braket/model", - "AMZN_BRAKET_CHECKPOINT_DIR": creation_kwargs["checkpointConfig"]["localPath"], - "AMZN_BRAKET_OUT_S3_BUCKET": bucket, - "AMZN_BRAKET_TASK_RESULTS_S3_URI": f"s3://{bucket}/jobs/{job_name}/tasks", - "AMZN_BRAKET_JOB_RESULTS_S3_PATH": str(Path(location, job_name, "output").as_posix()), - } - - -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 - to the running container. - """ - return { - "AMZN_BRAKET_HP_FILE": "/opt/braket/input/config/hyperparameters.json", - } - - -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 - to the running container. - """ - return { - "AMZN_BRAKET_INPUT_DIR": "/opt/braket/input/data", - } - - -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. - """ - if "hyperParameters" not in creation_kwargs: - return False - 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: - json.dump(hyperparameters, write_file) - container.copy_to(str(file_path), "/opt/ml/input/config/hyperparameters.json") - return True - - -def _download_input_data( - aws_session: AwsSession, - download_dir: str, - 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 - 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 - # directory with the same name as the channel. This behavior is the same - # whether or not s3 prefix ends with a "/". Moreover, if s3 prefix ends - # with a "/", this is certainly the behavior to expect, since it can only - # match a directory. - # If s3 prefix matches any files exactly, or matches as a prefix of any - # files or directories, then all files and directories matching s3 prefix - # will be copied into a directory with the same name as the channel. - channel_name = input_data["channelName"] - s3_uri_prefix = input_data["dataSource"]["s3DataSource"]["s3Uri"] - bucket, prefix = AwsSession.parse_s3_uri(s3_uri_prefix) - s3_keys = aws_session.list_keys(bucket, prefix) - top_level = prefix if _is_dir(prefix, s3_keys) else str(Path(prefix).parent) - found_item = False - try: - Path(download_dir, channel_name).mkdir() - 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) - if not s3_key.endswith("/"): - download_path.parent.mkdir(parents=True, exist_ok=True) - aws_session.download_from_s3( - AwsSession.construct_s3_uri(bucket, s3_key), str(download_path) - ) - found_item = True - if not found_item: - raise RuntimeError(f"No data found for channel '{channel_name}'") - - -def _is_dir(prefix: str, keys: Iterable[str]) -> bool: - """Determine whether the prefix refers to a directory. - - Args: - prefix (str): The prefix to check. - keys (Iterable[str]): The set of paths to check. - - Returns: - bool: True if the prefix refers to a directory. - """ - if prefix.endswith("/"): - return True - return all(key.startswith(f"{prefix}/") for key in keys) - - -def _copy_input_data_list( - 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. - - 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. - """ - if "inputDataConfig" not in creation_kwargs: - return False - - input_data_list = creation_kwargs["inputDataConfig"] - with tempfile.TemporaryDirectory() as temp_dir: - for input_data in input_data_list: - _download_input_data(aws_session, temp_dir, input_data) - container.copy_to(temp_dir, "/opt/ml/input/data/") - return bool(input_data_list) diff --git a/src/braket/jobs/logs.py b/src/braket/jobs/logs.py deleted file mode 100644 index 9aa7dfac..00000000 --- a/src/braket/jobs/logs.py +++ /dev/null @@ -1,241 +0,0 @@ -# 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 collections -import os -import sys -from collections.abc import Generator - -############################################################################## -# -# Support for reading logs -# -############################################################################## -from typing import ClassVar, Optional - -from botocore.exceptions import ClientError - -from braket.aws.aws_session import AwsSession - - -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: ClassVar = [34, 35, 32, 36, 33] - - def __init__(self, force: bool = False): - """Initialize a `ColorWrap`. - - Args: - force (bool): If True, the render output is colorized wherever the - output is. Default: False. - """ - self.colorize = force or sys.stdout.isatty() or os.environ.get("JPY_PARENT_PID", None) - - def __call__(self, index: int, s: str): - """Prints the string, colorized or not, depending on the environment. - - Args: - index (int): The instance number. - s (str): The string to print. - """ - if self.colorize: - self._color_wrap(index, s) - else: - print(s) - - def _color_wrap(self, index: int, s: str) -> None: - """Prints the string in a color determined by the index. - - Args: - index (int): The instance number. - s (str): The string to print (color-wrapped). - """ - 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]]: - """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. - - 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 - the position of the stream in this list. - positions (dict[str, Position]): A list of (timestamp, skip) pairs which represent - the last record read from each stream. - - Yields: - 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) - for s in streams - ] - events = [] - for s in event_iters: - try: - events.append(next(s)) - except StopIteration: - events.append(None) - - while any(events): - i = events.index(min(events, key=lambda x: x["timestamp"] if x else float("inf"))) - yield i, events[i] - try: - events[i] = next(event_iters[i]) - except StopIteration: - events[i] = None - - -def log_stream( - aws_session: AwsSession, log_group: str, stream_name: str, start_time: int = 0, skip: int = 0 -) -> Generator[dict]: - """A generator for log items in a single stream. - This yields all the items that are available at the current moment. - - Args: - aws_session (AwsSession): The AwsSession for interfacing with CloudWatch. - log_group (str): The name of the log group. - stream_name (str): The name of the specific stream. - start_time (int): The time stamp value to start reading the logs from. Default: 0. - skip (int): The number of log entries to skip at the start. Default: 0 (This is for - when there are multiple entries at the same timestamp.) - - Yields: - 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 - while event_count > 0: - response = aws_session.get_log_events( - log_group, - stream_name, - start_time, - start_from_head=True, - next_token=next_token, - ) - next_token = response["nextForwardToken"] - events = response["events"] - event_count = len(events) - if event_count > skip: - events = events[skip:] - skip = 0 - else: - skip = skip - event_count - events = [] - 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_count: int, - has_streams: bool, - color_wrap: ColorWrap, - state: list[str], - queue_position: Optional[str] = None, -) -> bool: - """Flushes log streams to stdout. - - Args: - 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 - 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 - 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. - has_streams (bool): Whether the function has already been called once all streams have - 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` - - Raises: - Exception: Any exception found besides a ResourceNotFoundException. - - Returns: - bool: Returns 'True' if any streams have been flushed. - """ - if len(stream_names) < stream_count: - # Log streams are created whenever a container starts writing to stdout/err, - # so this list may be dynamic until we have a stream for every instance. - try: - streams = aws_session.describe_log_streams( - log_group, - stream_prefix, - limit=stream_count, - ) - # stream_names = [...] wouldn't modify the list by reference. - new_streams = [ - s["logStreamName"] - for s in streams["logStreams"] - if s["logStreamName"] not in stream_names - ] - stream_names.extend(new_streams) - 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 - # errors thrown about that until logging begins. - err = e.response.get("Error", {}) - if err.get("Code") != "ResourceNotFoundException": - raise - - if stream_names: - if not has_streams: - print() - has_streams = True - for idx, event in multi_stream_iter(aws_session, log_group, stream_names, positions): - color_wrap(idx, event["message"]) - ts, count = positions[stream_names[idx]] - if event["timestamp"] == ts: - 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/src/braket/jobs/metrics.py b/src/braket/jobs/metrics.py deleted file mode 100644 index 991370f3..00000000 --- a/src/braket/jobs/metrics.py +++ /dev/null @@ -1,41 +0,0 @@ -# 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 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, -) -> None: - """Records Braket Hybrid Job metrics. - - Args: - metric_name (str): The name 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. - - 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};"] - if iteration_number is not None: - metric_list.append(f" iteration_number={iteration_number};") - metric_line = "".join(metric_list) - print(metric_line) diff --git a/src/braket/jobs/metrics_data/__init__.py b/src/braket/jobs/metrics_data/__init__.py deleted file mode 100644 index 273b6800..00000000 --- a/src/braket/jobs/metrics_data/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# 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.jobs.metrics_data.cwl_metrics_fetcher import CwlMetricsFetcher # noqa: F401 -from braket.jobs.metrics_data.definitions import MetricPeriod, MetricStatistic # noqa: F401 -from braket.jobs.metrics_data.exceptions import MetricsRetrievalError # noqa: F401 -from braket.jobs.metrics_data.log_metrics_parser import LogMetricsParser # noqa: F401 diff --git a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py deleted file mode 100644 index 8f5d3dcd..00000000 --- a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py +++ /dev/null @@ -1,191 +0,0 @@ -# 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 time -from logging import Logger, getLogger -from typing import Any, Optional, Union - -from braket.aws.aws_session import AwsSession -from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType -from braket.jobs.metrics_data.exceptions import MetricsRetrievalError -from braket.jobs.metrics_data.log_metrics_parser import LogMetricsParser - - -class CwlInsightsMetricsFetcher: - LOG_GROUP_NAME = "/aws/braket/jobs" - QUERY_DEFAULT_JOB_DURATION = 3 * 60 * 60 - - def __init__( - self, - aws_session: AwsSession, - poll_timeout_seconds: float = 10, - 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, - in seconds. Default: 10 seconds. - poll_interval_seconds (float): The interval of time, in seconds, between polling - for results. Default: 1 second. - logger (Logger): Logger object with which to write logs, such as quantum task statuses - while waiting for a quantum task to be in a terminal state. Default is - `getLogger(__name__)` - """ - self._poll_timeout_seconds = poll_timeout_seconds - self._poll_interval_seconds = poll_interval_seconds - self._logger = logger - 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]: - """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. - - Returns: - 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. - - 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. - """ - timeout_time = time.time() + self._poll_timeout_seconds - 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"]: - raise MetricsRetrievalError(f"Query {query_id} failed with status {query_status}.") - elif query_status == "Complete": - return response["results"] - else: - time.sleep(self._poll_interval_seconds) - 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 - to 'all_metrics' along with the timestamp for the entry. - - Args: - 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. - parser (LogMetricsParser) : The CWL metrics parser. - """ - 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) - - 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. - - Args: - 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. - """ - parser = LogMetricsParser() - for result in results: - self._parse_log_line(result, parser) - return parser.get_parsed_metrics(metric_type, statistic) - - def get_metrics_for_job( - self, - job_name: str, - metric_type: MetricType = MetricType.TIMESTAMP, - 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. - - Args: - job_name (str): The name of the Hybrid Job. The name must be exact to ensure only the - relevant metrics are retrieved. - metric_type (MetricType): The type of metrics to get. Default is MetricType.TIMESTAMP. - statistic (MetricStatistic): The statistic to determine which metric value to use - when there is a conflict. Default is MetricStatistic.MAX. - job_start_time (int | None): The time when the hybrid job started. - 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 - are the column names and the values are a list containing the values in each row. - - Example: - timestamp energy - 0 0.1 - 1 0.2 - would be represented as: - { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } - The values may be integers, floats, strings or None. - """ - 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).replace("/", "\\/") - - query = ( - f"fields @timestamp, @message " - f"| filter @logStream like /^{stream_prefix}\\// " - f"| filter @message like /Metrics - /" - ) - - response = self._logs_client.start_query( - logGroupName=self.LOG_GROUP_NAME, - startTime=query_start_time, - endTime=query_end_time, - queryString=query, - limit=10000, - ) - - query_id = response["queryId"] - - results = self._get_metrics_results_sync(query_id) - - return self._parse_log_query_results(results, metric_type, statistic) diff --git a/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py deleted file mode 100644 index e8da4ff8..00000000 --- a/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py +++ /dev/null @@ -1,157 +0,0 @@ -# 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 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 -from braket.jobs.metrics_data.log_metrics_parser import LogMetricsParser - - -class CwlMetricsFetcher: - LOG_GROUP_NAME = "/aws/braket/jobs" - - def __init__( - self, - aws_session: AwsSession, - 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, - in seconds. Default: 10 seconds. - 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__)` - """ - self._poll_timeout_seconds = poll_timeout_seconds - self._logger = logger - self._logs_client = aws_session.logs_client - - @staticmethod - def _is_metrics_message(message: str) -> bool: - """Returns true if a given message is designated as containing Metrics. - - Args: - message (str): The message to check. - - Returns: - bool: True if the given message is designated as containing Metrics; False otherwise. - """ - return "Metrics -" in message if message else False - - def _parse_metrics_from_log_stream( - self, - stream_name: str, - timeout_time: float, - parser: LogMetricsParser, - ) -> None: - """Synchronously retrieves the algorithm metrics logged in a given hybrid job log stream. - - Args: - stream_name (str): The name of the log stream. - timeout_time (float) : We stop getting metrics if the current time is beyond - the timeout time. - parser (LogMetricsParser) : The CWL metrics parser. - """ - kwargs = { - "logGroupName": self.LOG_GROUP_NAME, - "logStreamName": stream_name, - "startFromHead": True, - "limit": 10000, - } - - previous_token = None - while time.time() < timeout_time: - response = self._logs_client.get_log_events(**kwargs) - for event in response.get("events"): - message = event.get("message") - if self._is_metrics_message(message): - parser.parse_log_message(event.get("timestamp"), message) - next_token = response.get("nextForwardToken") - if not next_token or next_token == previous_token: - return - previous_token = next_token - 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. - - 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. - """ - kwargs = { - "logGroupName": self.LOG_GROUP_NAME, - "logStreamNamePrefix": f"{job_name}/algo-", - } - log_streams = [] - 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) - if next_token := response.get("nextToken"): - kwargs["nextToken"] = next_token - else: - return log_streams - self._logger.warning("Timed out waiting for all metrics. Data may be incomplete.") - return log_streams - - def get_metrics_for_job( - self, - 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. - - Args: - job_name (str): The name of the Hybrid Job. The name must be exact to ensure only the - relevant metrics are retrieved. - metric_type (MetricType): The type of metrics to get. Default is MetricType.TIMESTAMP. - statistic (MetricStatistic): The statistic to determine which metric value to use - when there is a conflict. Default is MetricStatistic.MAX. - - Returns: - 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: - timestamp energy - 0 0.1 - 1 0.2 - would be represented as: - { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } - values may be integers, floats, strings or None. - """ - timeout_time = time.time() + self._poll_timeout_seconds - - parser = LogMetricsParser() - - log_streams = self._get_log_streams_for_job(job_name, timeout_time) - for log_stream in log_streams: - self._parse_metrics_from_log_stream(log_stream, timeout_time, parser) - - return parser.get_parsed_metrics(metric_type, statistic) diff --git a/src/braket/jobs/metrics_data/definitions.py b/src/braket/jobs/metrics_data/definitions.py deleted file mode 100644 index a3e77a78..00000000 --- a/src/braket/jobs/metrics_data/definitions.py +++ /dev/null @@ -1,36 +0,0 @@ -# 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 enum import Enum, unique - - -@unique -class MetricPeriod(Enum): - """Period over which the cloudwatch metric is aggregated.""" - - ONE_MINUTE: int = 60 - - -@unique -class MetricStatistic(Enum): - """Metric data aggregation to use over the specified period.""" - - MIN: str = "Min" - MAX: str = "Max" - - -@unique -class MetricType(Enum): - """Metric type.""" - - TIMESTAMP: str = "Timestamp" - ITERATION_NUMBER: str = "IterationNumber" diff --git a/src/braket/jobs/metrics_data/exceptions.py b/src/braket/jobs/metrics_data/exceptions.py deleted file mode 100644 index 41cbf049..00000000 --- a/src/braket/jobs/metrics_data/exceptions.py +++ /dev/null @@ -1,16 +0,0 @@ -# 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. - - -class MetricsRetrievalError(Exception): - """Raised when retrieving metrics fails.""" diff --git a/src/braket/jobs/metrics_data/log_metrics_parser.py b/src/braket/jobs/metrics_data/log_metrics_parser.py deleted file mode 100644 index 1ff5b4d4..00000000 --- a/src/braket/jobs/metrics_data/log_metrics_parser.py +++ /dev/null @@ -1,205 +0,0 @@ -# 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 collections.abc import Iterator -from logging import Logger, getLogger -from typing import Optional, Union - -from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType - - -class LogMetricsParser: - """This class is used to parse metrics from log lines, and return them in a more - convenient format. - """ - - METRICS_DEFINITIONS = re.compile(r"(\w+)\s*=\s*([^;]+)\s*;") - TIMESTAMP = "timestamp" - ITERATION_NUMBER = "iteration_number" - NODE_ID = "node_id" - NODE_TAG = re.compile(r"^\[([^\]]*)\]") - - def __init__( - self, - logger: Logger = getLogger(__name__), - ): - self._logger = logger - self.all_metrics = [] - - @staticmethod - def _get_value( - current_value: Optional[Union[str, float, int]], - new_value: Union[str, float, int], - statistic: MetricStatistic, - ) -> Union[str, float, int]: - """Gets the value based on a statistic. - - Args: - current_value (Optional[Union[str, float, int]]): The current value. - - new_value (Union[str, float, int]): The new value. - - statistic (MetricStatistic): The statistic to determine which value to use. - - Returns: - Union[str, float, int]: the value. - """ - if current_value is None: - return new_value - if statistic == MetricStatistic.MAX: - 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]]: - """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 - is in the format { : }. This implies that multiple metrics - with the same name are deduped to the last instance of that metric. - """ - metrics = {} - for match in all_matches: - subgroup = match.groups() - value = subgroup[1] - try: - metrics[subgroup[0]] = float(value) - except ValueError: - self._logger.warning(f"Unable to convert value {value} to a float.") - 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 - on that line. The timestamp is also added to match the corresponding values. - - Args: - timestamp (str): A formatted string representing the timestamp for any found metrics. - - message (str): A log line from a log. - """ - if not message: - return - all_matches = self.METRICS_DEFINITIONS.finditer(message) - parsed_metrics = self._get_metrics_from_log_line_matches(all_matches) - if not parsed_metrics: - return - if timestamp and self.TIMESTAMP not in parsed_metrics: - parsed_metrics[self.TIMESTAMP] = timestamp - if node_match := self.NODE_TAG.match(message): - parsed_metrics[self.NODE_ID] = node_match.group(1) - self.all_metrics.append(parsed_metrics) - - 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 - 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 - to None. - 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 - pivot_indices: dict[int, int] = {} - table: dict[str, list[Optional[Union[str, float, int]]]] = {} - for metric in self.all_metrics: - if pivot in metric: - # If no node_id is present, pair pivot value with None for the key. - metric_pivot = (metric[pivot], metric.get(self.NODE_ID)) - if metric_pivot not in pivot_indices: - pivot_indices[metric_pivot] = row_count - row_count += 1 - for column_name in metric: - table[column_name] = [None] - for column_name in table: - table[column_name] = [None] * row_count - return table, 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 - 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 - to determine which value is returned. - For example, for the metrics: - "iteration_number" : 0, "metricA" : 2, "metricB" : 1, - "iteration_number" : 0, "metricA" : 1, - "no_pivot_column" : 0, "metricA" : 0, - "iteration_number" : 1, "metricA" : 2, - "iteration_number" : 1, "node_id" : "nodeB", "metricB" : 0, - - The result with iteration_number as the pivot, statistic of MIN the result will be: - iteration_number node_id metricA metricB - 0 None 1 1 - 1 None 2 None - 1 nodeB None 0 - - Args: - pivot (str): The name of the pivot column. Must be TIMESTAMP or ITERATION_NUMBER. - statistic (MetricStatistic): The statistic to determine which value to use. - - Returns: - 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: - if pivot in metric: - metric_pivot = (metric[pivot], metric.get(self.NODE_ID)) - row = pivot_indices[metric_pivot] - for column_name in metric: - table[column_name][row] = self._get_value( - table[column_name][row], metric[column_name], statistic - ) - return table - - 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 - containing the values in each row. - - Args: - 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. - - Example: - timestamp energy - 0 0.1 - 1 0.2 - would be represented as: - { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } - values may be integers, floats, strings or None. - """ - if metric_type == MetricType.ITERATION_NUMBER: - return self.get_metric_data_with_pivot(self.ITERATION_NUMBER, statistic) - return self.get_metric_data_with_pivot(self.TIMESTAMP, statistic) diff --git a/src/braket/jobs/quantum_job.py b/src/braket/jobs/quantum_job.py deleted file mode 100644 index 32c660bc..00000000 --- a/src/braket/jobs/quantum_job.py +++ /dev/null @@ -1,201 +0,0 @@ -# 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 typing import Any - -from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType - - -class QuantumJob(ABC): - DEFAULT_RESULTS_POLL_TIMEOUT = 864000 - DEFAULT_RESULTS_POLL_INTERVAL = 5 - - @property - @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. - """ - - @property - @abstractmethod - def name(self) -> str: - """The name of the hybrid job. - - Returns: - str: The name of the hybrid job. - """ - - @abstractmethod - 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: The value of `status` in `metadata()`. This is the value of the `status` key - in the Amazon Braket `GetJob` operation. - - See Also: - `metadata()` - """ - - @abstractmethod - def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: - """Display logs for a given hybrid job, optionally tailing them until hybrid job is - complete. - - If the output is a tty or a Jupyter cell, it will be color-coded - based on which instance the log entry is from. - - Args: - wait (bool): `True` to keep looking for new log entries until the hybrid job completes; - otherwise `False`. Default: `False`. - - poll_interval_seconds (int): The interval of time, in seconds, between polling for - new log entries and hybrid job completion (default: 5). - - Raises: - RuntimeError: If waiting and the hybrid job fails. - """ - # The loop below implements a state machine that alternates between checking the hybrid job - # status and reading whatever is available in the logs at this point. Note, that if we were - # called with wait == False, we never check the hybrid job status. - # - # If wait == TRUE and hybrid job is not completed, the initial state is TAILING - # If wait == FALSE, the initial state is COMPLETE (doesn't matter if the hybrid job really - # is complete). - # - # The state table: - # - # STATE ACTIONS CONDITION NEW STATE - # ---------------- ---------------- ----------------- ---------------- - # TAILING Read logs, Pause, Get status Job complete JOB_COMPLETE - # Else TAILING - # JOB_COMPLETE Read logs, Pause Any COMPLETE - # COMPLETE Read logs, Exit N/A - # - # Notes: - # - The JOB_COMPLETE state forces us to do an extra pause and read any items that got to - # Cloudwatch after the job was marked complete. - - @abstractmethod - def metadata(self, use_cached_value: bool = False) -> dict[str, Any]: - """Gets the job metadata defined in Amazon Braket. - - 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]: Dict that specifies the hybrid job metadata defined in Amazon Braket. - """ - - @abstractmethod - def metrics( - self, - metric_type: MetricType = MetricType.TIMESTAMP, - statistic: MetricStatistic = MetricStatistic.MAX, - ) -> 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. - - Args: - metric_type (MetricType): The type of metrics to get. Default: MetricType.TIMESTAMP. - - statistic (MetricStatistic): The statistic to determine which metric value to use - when there is a conflict. Default: MetricStatistic.MAX. - - Returns: - dict[str, list[Any]]: The metrics data. - - Example: - timestamp energy - 0 0.1 - 1 0.2 - would be represented as: - { "timestamp" : [0, 1], "energy" : [0.1, 0.2] } - values may be integers, floats, strings or None. - """ - - @abstractmethod - def cancel(self) -> str: - """Cancels the hybrid job. - - Returns: - str: Indicates the status of the hybrid job. - - Raises: - ClientError: If there are errors invoking the CancelJob API. - """ - - @abstractmethod - def result( - self, - poll_timeout_seconds: float = DEFAULT_RESULTS_POLL_TIMEOUT, - poll_interval_seconds: float = DEFAULT_RESULTS_POLL_INTERVAL, - ) -> dict[str, Any]: - """Retrieves the hybrid job result persisted using save_job_result() function. - - Args: - poll_timeout_seconds (float): The polling timeout, in seconds, for `result()`. - Default: 10 days. - - poll_interval_seconds (float): The polling interval, in seconds, for `result()`. - Default: 5 seconds. - - - Returns: - dict[str, Any]: Dict specifying the hybrid job results. - - Raises: - RuntimeError: if hybrid job is in a FAILED or CANCELLED state. - TimeoutError: if hybrid job execution exceeds the polling timeout period. - """ - - @abstractmethod - def download_result( - self, - extract_to: str | None = None, - poll_timeout_seconds: float = DEFAULT_RESULTS_POLL_TIMEOUT, - poll_interval_seconds: float = DEFAULT_RESULTS_POLL_INTERVAL, - ) -> None: - """Downloads the results from the hybrid job output S3 bucket and extracts the tar.gz - bundle to the location specified by `extract_to`. If no location is specified, - the results are extracted to the current directory. - - Args: - extract_to (str | None): The directory to which the results are extracted. The results - are extracted to a folder titled with the hybrid job name within this directory. - Default= `Current working directory`. - - poll_timeout_seconds (float): The polling timeout, in seconds, for `download_result()`. - Default: 10 days. - - poll_interval_seconds (float): The polling interval, in seconds, for - `download_result()`.Default: 5 seconds. - - Raises: - RuntimeError: if hybrid job is in a FAILED or CANCELLED state. - TimeoutError: if hybrid job execution exceeds the polling timeout period. - """ diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py deleted file mode 100644 index 3c4a01b5..00000000 --- a/src/braket/jobs/quantum_job_creation.py +++ /dev/null @@ -1,495 +0,0 @@ -# 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 importlib.util -import os -import re -import sys -import tarfile -import tempfile -import time -import warnings -from collections.abc import Callable -from dataclasses import asdict -from pathlib import Path -from typing import Any - -from braket.aws.aws_session import AwsSession -from braket.jobs.config import ( - CheckpointConfig, - DeviceConfig, - InstanceConfig, - OutputDataConfig, - S3DataSourceConfig, - StoppingCondition, -) -from braket.jobs.image_uris import Framework, retrieve_image - - -def prepare_quantum_job( - device: str, - source_module: str, - entry_point: str | None = None, - image_uri: str | None = None, - job_name: str | None = None, - code_location: str | None = None, - role_arn: str | None = None, - hyperparameters: dict[str, Any] | None = None, - input_data: str | dict | S3DataSourceConfig | None = None, - instance_config: InstanceConfig | None = None, - distribution: str | None = None, - stopping_condition: StoppingCondition | None = None, - output_data_config: OutputDataConfig | None = None, - copy_checkpoints_from_job: str | None = None, - checkpoint_config: CheckpointConfig | None = None, - aws_session: AwsSession | None = None, - tags: dict[str, str] | None = None, - reservation_arn: str | None = None, -) -> dict: - """Creates a hybrid job by invoking the Braket CreateJob API. - - Args: - device (str): 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 - queue so that only one hybrid job is running at a time. The device string is accessible - in the hybrid job instance as the environment variable "AMZN_BRAKET_DEVICE_ARN". - When using embedded simulators, you may provide the device argument as string of the - form: "local:/". - - source_module (str): Path (absolute, relative or an S3 URI) to a python module to be - tarred and uploaded. If `source_module` is an S3 URI, it must point to a - tar.gz file. Otherwise, source_module may be a file or directory. - - entry_point (str | None): A str that specifies the entry point of the hybrid job, relative - to the source module. The entry point must be in the format - `importable.module` or `importable.module:callable`. For example, - `source_module.submodule:start_here` indicates the `start_here` function - contained in `source_module.submodule`. If source_module is an S3 URI, - entry point must be given. Default: source_module's name - - image_uri (str | None): A str that specifies the ECR image to use for executing the hybrid - job.`image_uris.retrieve_image()` function may be used for retrieving the ECR image URIs - for the containers supported by Braket. Default = ``. - - job_name (str | None): A str that specifies the name with which the hybrid job is created. - The hybrid job name must be between 0 and 50 characters long and cannot contain - underscores. - Default: f'{image_uri_type}-{timestamp}'. - - code_location (str | None): The S3 prefix URI where custom code will be uploaded. - Default: f's3://{default_bucket_name}/jobs/{job_name}/script'. - - role_arn (str | None): A str providing the IAM role ARN used to execute the - script. Default: IAM role returned by AwsSession's `get_default_jobs_role()`. - - hyperparameters (dict[str, Any] | None): Hyperparameters accessible to the hybrid job. - The hyperparameters are made accessible as a Dict[str, str] to the hybrid job. - For convenience, this accepts other types for keys and values, but `str()` - is called to convert them before being passed on. Default: None. - - input_data (str | dict | S3DataSourceConfig | None): Information about the training - data. Dictionary maps channel names to local paths or S3 URIs. Contents found - at any local paths will be uploaded to S3 at - f's3://{default_bucket_name}/jobs/{job_name}/data/{channel_name}. If a local - path, S3 URI, or S3DataSourceConfig is provided, it will be given a default - channel name "input". - Default: {}. - - instance_config (InstanceConfig | None): Configuration of the instance(s) for running the - classical code for the hybrid job. Defaults to - `InstanceConfig(instanceType='ml.m5.large', instanceCount=1, volumeSizeInGB=30)`. - - distribution (str | None): A str that specifies how the hybrid job should be distributed. - If set to "data_parallel", the hyperparameters for the hybrid job will be set to use - data parallelism features for PyTorch or TensorFlow. Default: None. - - stopping_condition (StoppingCondition | None): The maximum length of time, in seconds, - and the maximum number of quantum tasks that a hybrid job can run before being - forcefully stopped. Default: StoppingCondition(maxRuntimeInSeconds=5 * 24 * 60 * 60). - - output_data_config (OutputDataConfig | None): Specifies the location for the output of the - hybrid job. - Default: OutputDataConfig(s3Path=f's3://{default_bucket_name}/jobs/{job_name}/data', - kmsKeyId=None). - - copy_checkpoints_from_job (str | None): A str that specifies the hybrid job ARN whose - checkpoint you want to use in the current hybrid job. Specifying this value will copy - over the checkpoint data from `use_checkpoints_from_job`'s checkpoint_config s3Uri to - the current hybrid job's checkpoint_config s3Uri, making it available at - checkpoint_config.localPath during the hybrid job execution. Default: None - - checkpoint_config (CheckpointConfig | None): Configuration that specifies the location where - checkpoint data is stored. - Default: CheckpointConfig(localPath='/opt/jobs/checkpoints', - s3Uri=f's3://{default_bucket_name}/jobs/{job_name}/checkpoints'). - - aws_session (AwsSession | None): AwsSession for connecting to AWS Services. - Default: AwsSession() - - tags (dict[str, str] | None): Dict specifying the key-value pairs for tagging this - hybrid job. - Default: {}. - - 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. - - Returns: - dict: Hybrid job tracking the execution on Amazon Braket. - - Raises: - ValueError: Raises ValueError if the parameters are not valid. - """ - param_datatype_map = { - "instance_config": (instance_config, InstanceConfig), - "stopping_condition": (stopping_condition, StoppingCondition), - "output_data_config": (output_data_config, OutputDataConfig), - "checkpoint_config": (checkpoint_config, CheckpointConfig), - } - - _validate_params(param_datatype_map) - aws_session = aws_session or AwsSession() - device_config = DeviceConfig(device) - 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, timestamp) - instance_config = instance_config or InstanceConfig() - stopping_condition = stopping_condition or StoppingCondition() - output_data_config = output_data_config or OutputDataConfig() - checkpoint_config = checkpoint_config or CheckpointConfig() - code_location = code_location or AwsSession.construct_s3_uri( - default_bucket, - "jobs", - job_name, - timestamp, - "script", - ) - - if AwsSession.is_s3_uri(source_module): - _process_s3_source_module(source_module, entry_point, aws_session, code_location) - else: - # if entry point is None, it will be set to default here - entry_point = _process_local_source_module( - source_module, entry_point, aws_session, code_location - ) - algorithm_specification = { - "scriptModeConfig": { - "entryPoint": entry_point, - "s3Uri": f"{code_location}/source.tar.gz", - "compressionType": "GZIP", - } - } - image_uri = image_uri or retrieve_image(Framework.BASE, aws_session.region) - algorithm_specification["containerImage"] = {"uri": image_uri} - if not output_data_config.s3Path: - output_data_config.s3Path = AwsSession.construct_s3_uri( - default_bucket, - "jobs", - job_name, - timestamp, - "data", - ) - if not checkpoint_config.s3Uri: - checkpoint_config.s3Uri = AwsSession.construct_s3_uri( - default_bucket, - "jobs", - job_name, - timestamp, - "checkpoints", - ) - if copy_checkpoints_from_job: - checkpoints_to_copy = aws_session.get_job(copy_checkpoints_from_job)["checkpointConfig"][ - "s3Uri" - ] - aws_session.copy_s3_directory(checkpoints_to_copy, checkpoint_config.s3Uri) - if distribution == "data_parallel": - distributed_hyperparams = { - "sagemaker_distributed_dataparallel_enabled": "true", - "sagemaker_instance_type": instance_config.instanceType, - } - hyperparameters |= distributed_hyperparams - - create_job_kwargs = { - "jobName": job_name, - "roleArn": role_arn, - "algorithmSpecification": algorithm_specification, - "inputDataConfig": input_data_list, - "instanceConfig": asdict(instance_config), - "outputDataConfig": asdict(output_data_config, dict_factory=_exclude_nones_factory), - "checkpointConfig": asdict(checkpoint_config), - "deviceConfig": asdict(device_config), - "hyperParameters": hyperparameters, - "stoppingCondition": asdict(stopping_condition), - "tags": tags, - } - - if reservation_arn: - create_job_kwargs["associations"] = [ - { - "arn": reservation_arn, - "type": "RESERVATION_TIME_WINDOW_ARN", - } - ] - - return create_job_kwargs - - -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. - """ - 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( - f"Job name exceeded {max_length} characters. " - f"Truncating name to {name}-{timestamp}.", - stacklevel=1, - ) - 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 - ) - 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 - provided. - - Args: - source_module (str): S3 URI pointing to the tarred source module. - entry_point (str): Entry point for the hybrid job. - 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.") - if not source_module.lower().endswith(".tar.gz"): - raise ValueError( - "If source_module is an S3 URI, it must point to a tar.gz file. " - f"Not a valid S3 URI for parameter `source_module`: {source_module}" - ) - aws_session.copy_s3_object(source_module, f"{code_location}/source.tar.gz") - - -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 - 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. - aws_session (AwsSession): AwsSession for uploading tarred 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. - """ - try: - # raises FileNotFoundError if not found - abs_path_source_module = Path(source_module).resolve(strict=True) - 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) - _tar_and_upload_to_code_location(abs_path_source_module, aws_session, code_location) - return entry_point - - -def _validate_entry_point(source_module_path: Path, entry_point: str) -> None: - """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)) - try: - # second argument allows relative imports - importlib.invalidate_caches() - module = importlib.util.find_spec(importable, source_module_path.stem) - if module is None: - raise AssertionError - except (ModuleNotFoundError, AssertionError) as e: - raise ValueError(f"Entry point module was not found: {importable}") from e - finally: - sys.path.pop() - - -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. - - Args: - source_module_path (Path): Path to source module. - aws_session (AwsSession): AwsSession for uploading source module. - code_location (str): S3 URI pointing to the location where the tarred - source module will be uploaded to. - """ - with tempfile.TemporaryDirectory() as temp_dir: - with tarfile.open(f"{temp_dir}/source.tar.gz", "w:gz", dereference=True) as tar: - tar.add(source_module_path, arcname=source_module_path.name) - aws_session.upload_to_s3(f"{temp_dir}/source.tar.gz", f"{code_location}/source.tar.gz") - - -def _validate_params(dict_arr: dict[str, tuple[any, any]]) -> None: - """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 - - if user_input and not isinstance(user_input, expected_datatype): - raise ValueError( - f"'{parameter_name}' should be of '{expected_datatype}' " - f"but user provided {type(user_input)}." - ) - - -def _process_input_data( - 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. - - Args: - input_data (str | dict | S3DataSourceConfig): Either a channel definition or a - dictionary mapping channel names to channel definitions, where a channel definition - 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. - """ - if not isinstance(input_data, dict): - 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, subdirectory - ) - return _convert_input_to_config(input_data) - - -def _process_channel( - 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. - - Args: - location (str): Local prefix or S3 prefix. - 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. - """ - if AwsSession.is_s3_uri(location): - return S3DataSourceConfig(location) - # 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]]: - """Convert a dictionary mapping channel names to S3DataSourceConfigs into a list of channel - configs compatible with the Braket API. - - Args: - input_data (dict[str, S3DataSourceConfig]): A dictionary mapping channel names to - S3DataSourceConfig objects. - - Returns: - list[dict[str, Any]]: A list of channel configs. - """ - return [ - { - "channelName": channel_name, - **data_config.config, - } - for channel_name, data_config in input_data.items() - ] - - -def _exclude_nones_factory(items: list[tuple]) -> dict: - return {k: v for k, v in items if v is not None} diff --git a/src/braket/jobs/serialization.py b/src/braket/jobs/serialization.py deleted file mode 100644 index 179a4497..00000000 --- a/src/braket/jobs/serialization.py +++ /dev/null @@ -1,65 +0,0 @@ -# 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 codecs -import pickle -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`. - - Args: - 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 - the specified `data_format`. - """ - return ( - { - k: codecs.encode(pickle.dumps(v, protocol=4), "base64").decode() - for k, v in data_dictionary.items() - } - if data_format == PersistedJobDataFormat.PICKLED_V4 - else data_dictionary - ) - - -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`. - - Args: - 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 - the specified `data_format` to plaintext. - """ - return ( - {k: pickle.loads(codecs.decode(v.encode(), "base64")) for k, v in data_dictionary.items()} - if data_format == PersistedJobDataFormat.PICKLED_V4 - else data_dictionary - ) diff --git a/src/braket/parametric/__init__.py b/src/braket/parametric/__init__.py deleted file mode 100644 index 054f0af7..00000000 --- a/src/braket/parametric/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# 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.parametric.free_parameter import FreeParameter # noqa: F401 -from braket.parametric.free_parameter_expression import FreeParameterExpression # noqa: F401 -from braket.parametric.parameterizable import Parameterizable # noqa: F401 diff --git a/src/braket/parametric/free_parameter.py b/src/braket/parametric/free_parameter.py deleted file mode 100644 index 1f3a69e7..00000000 --- a/src/braket/parametric/free_parameter.py +++ /dev/null @@ -1,107 +0,0 @@ -# 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 -from typing import Union - -from sympy import Symbol - -from braket.parametric.free_parameter_expression import FreeParameterExpression - - -class FreeParameter(FreeParameterExpression): - """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 - for a numerical value later on. Circuits with FreeParameters must have all the inputs - provided at execution or substituted prior to execution. - - Examples: - >>> alpha, beta = FreeParameter("alpha"), FreeParameter("beta") - >>> circuit = Circuit().rx(target=0, angle=alpha).ry(target=1, angle=beta) - >>> circuit = circuit(alpha=0.3) - >>> device = LocalSimulator() - >>> device.run(circuit, inputs={'beta': 0.5} shots=10) - """ - - def __init__(self, name: str): - """Initializes a new :class:'FreeParameter' object. - - Args: - name (str): Name of the :class:'FreeParameter'. Can be a unicode value. - - Examples: - >>> param1 = FreeParameter("theta") - >>> param1 = FreeParameter("\u03B8") - """ - self._set_name(name) - super().__init__(expression=self._name) - - @property - def name(self) -> str: - """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. - - Args: - parameter_values (dict[str, Number]): A mapping of parameter to its - corresponding value. - - Returns: - Union[FreeParameter, Number]: The substituted value if this parameter is in - parameter_values, otherwise returns self - """ - return parameter_values.get(self.name, self) - - def __str__(self): - return str(self.name) - - def __hash__(self) -> int: - return hash(tuple(self.name)) - - 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'. - - Returns: - str: The name of the class:'FreeParameter' to represent the class. - """ - 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 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__, - "name": self.name, - } - - @classmethod - def from_dict(cls, parameter: dict) -> FreeParameter: - return FreeParameter(parameter["name"]) diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py deleted file mode 100644 index fdd2f547..00000000 --- a/src/braket/parametric/free_parameter_expression.py +++ /dev/null @@ -1,245 +0,0 @@ -# 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 ast -import operator -from functools import reduce -from numbers import Number -from typing import Any, Union - -import sympy -from oqpy.base import OQPyExpression -from oqpy.classical_types import FloatVar - - -class FreeParameterExpression: - """Class 'FreeParameterExpression' - - Objects that can take a parameter all inherit from :class:'Parameterizable'. - FreeParametersExpressions can hold FreeParameters that can later be - swapped out for a number. Circuits or PulseSequences with FreeParameters - present will NOT run. Values must be substituted prior to execution. - """ - - 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. - - Below are examples of how FreeParameterExpressions should be made. - - 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") - """ - self._operations = { - ast.Add: self.__add__, - ast.Sub: self.__sub__, - ast.Mult: self.__mul__, - ast.Pow: self.__pow__, - ast.USub: self.__neg__, - } - if isinstance(expression, FreeParameterExpression): - self._expression = expression.expression - elif isinstance(expression, (Number, sympy.Expr)): - self._expression = expression - elif isinstance(expression, str): - self._expression = self._parse_string_expression(expression).expression - else: - raise NotImplementedError - - @property - def expression(self) -> Union[Number, sympy.Expr]: - """Gets the expression. - - Returns: - Union[Number, Expr]: The expression for the FreeParameterExpression. - """ - return self._expression - - def subs( - self, parameter_values: dict[str, Number] - ) -> Union[FreeParameterExpression, Number, sympy.Expr]: - """ - Similar to a substitution in Sympy. Parameters are swapped for corresponding values or - expressions from the dictionary. - - Args: - parameter_values (dict[str, Number]): A mapping of parameters to their corresponding - values to be assigned. - - Returns: - Union[FreeParameterExpression, Number, Expr]: A numerical value if there are no - symbols left in the expression otherwise returns a new FreeParameterExpression. - """ - new_parameter_values = {} - for key, val in parameter_values.items(): - if issubclass(type(key), FreeParameterExpression): - new_parameter_values[key.expression] = val - else: - new_parameter_values[key] = val - - subbed_expr = self._expression.subs(new_parameter_values) - if isinstance(subbed_expr, Number): - return subbed_expr - else: - return FreeParameterExpression(subbed_expr) - - 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.Constant): - return FreeParameterExpression(node.n) - elif isinstance(node, ast.Name): - 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)}") - 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(): - 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}") - - 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: FreeParameterExpression): - return FreeParameterExpression(other + self.expression) - - 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: FreeParameterExpression): - return FreeParameterExpression(other - self.expression) - - 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: 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) - else: - return FreeParameterExpression(self.expression**other) - - def __rpow__(self, other: FreeParameterExpression): - return FreeParameterExpression(other**self.expression) - - def __neg__(self): - return FreeParameterExpression(-1 * self.expression) - - 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'. - - Returns: - str: The expression of the class:'FreeParameterExpression' to represent the class. - """ - 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", needs_declaration=False - ) - fvar.size = None - fvar.type.size = None - return fvar - - -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 (Union[FreeParameterExpression, str]): The kwargs to use to substitute. - - Returns: - Any: The substituted parameters. - """ - if isinstance(parameter, FreeParameterExpression): - substituted = parameter.subs(kwargs) - if isinstance(substituted, sympy.Number): - substituted = float(substituted) - return substituted - return parameter - - -def _is_float(argument: str) -> bool: - """Checks if a string can be cast into a float. - - Args: - argument (str): String to check. - - Returns: - bool: Returns true if the string can be cast as a float. False, otherwise. - - """ - try: - float(argument) - return True - except ValueError: - return False diff --git a/src/braket/parametric/parameterizable.py b/src/braket/parametric/parameterizable.py deleted file mode 100644 index 90c4dc58..00000000 --- a/src/braket/parametric/parameterizable.py +++ /dev/null @@ -1,49 +0,0 @@ -# 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 typing import Any, Union - -from braket.parametric.free_parameter import FreeParameter -from braket.parametric.free_parameter_expression import FreeParameterExpression - - -class Parameterizable(ABC): - """A parameterized object is the abstract definition of an object - that can take in FreeParameterExpressions. - """ - - @property - @abstractmethod - def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float]]: - """Get the parameters. - - Returns: - list[Union[FreeParameterExpression, FreeParameter, float]]: The parameters associated - with the object, either unbound free parameter expressions or bound values. The order - of the parameters is determined by the subclass. - """ - - @abstractmethod - 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/__init__.py b/src/braket/pulse/__init__.py deleted file mode 100644 index 01ef6689..00000000 --- a/src/braket/pulse/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# 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.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, - GaussianWaveform, -) diff --git a/src/braket/pulse/ast/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py deleted file mode 100644 index d2dcf65e..00000000 --- a/src/braket/pulse/ast/approximation_parser.py +++ /dev/null @@ -1,596 +0,0 @@ -# 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 collections import defaultdict -from collections.abc import KeysView -from dataclasses import dataclass -from typing import Any, ClassVar, Optional, Union - -import numpy as np -from openpulse import ast -from openqasm3.visitor import QASMVisitor -from oqpy import Program - -from braket.pulse.frame import Frame -from braket.pulse.waveforms import ( - ConstantWaveform, - DragGaussianWaveform, - GaussianWaveform, - Waveform, -) -from braket.timings.time_series import TimeSeries - - -@dataclass -class _FrameState: - dt: float - frequency: float = 0 - phase: float = 0 - current_time: float = 0 - amplitude: float = 0 - scale: float = 1 - - -@dataclass -class _ParseState: - variables: dict - frame_data: dict[str, _FrameState] - - -class _ApproximationParser(QASMVisitor[_ParseState]): - """Walk the AST and build the output signal amplitude, frequency and phases - for each channel. - """ - - 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={}, 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) - - 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 parsed return value. - """ - return super().visit(node, context) - - def _get_frame_parameters( - self, parameters: list[ast.Expression], context: _ParseState - ) -> Union[KeysView, list[str]]: - frame_ids = set() - for expression in parameters: - identifier_name = self.visit(expression, context) - if match := re.search(r"^\$[0-9]+$", identifier_name): - qubit_number = match.group()[1:] - frame_ids.update(self._qubit_frames_mapping.get(qubit_number, [])) - else: - frame_ids.add(identifier_name) - return frame_ids - - def _delay_frame(self, frame_id: str, to_delay_time: float, context: _ParseState) -> None: - frame_data = context.frame_data[frame_id] - if to_delay_time >= frame_data.current_time + frame_data.dt: - start_time = frame_data.current_time - self.amplitudes[frame_id].put(start_time, 0) - self.frequencies[frame_id].put(start_time, frame_data.frequency) - self.phases[frame_id].put(start_time, frame_data.phase) - if to_delay_time >= frame_data.current_time + (2 * frame_data.dt): - end_time = to_delay_time - frame_data.dt - self.amplitudes[frame_id].put(end_time, 0) - self.frequencies[frame_id].put(end_time, frame_data.frequency) - self.phases[frame_id].put(end_time, frame_data.phase) - context.frame_data[frame_id].current_time = to_delay_time - - 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. - """ - for statement in node.statements: - self.visit(statement, context) - - 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 - ) -> 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: - context.variables[identifier] = self.visit(node.init_expression, context) - elif type(node.type) == ast.FrameType: - pass - elif type(node.type) != ast.PortType: - raise NotImplementedError - - def visit_DelayInstruction(self, node: ast.DelayInstruction, context: _ParseState) -> None: - """Visit a Delay Instruction. - node.duration, node.qubits - delay[100ns] $0; - - Args: - node (ast.DelayInstruction): The classical declaration. - context (_ParseState): The parse state context. - """ - duration = self.visit(node.duration, context) - frames = self._get_frame_parameters(node.qubits, context) - if len(frames) == 0: - # 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) - # 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) - barrier_time = _ceil_approx(max_time / lcm) * lcm - - for frame_id in frames: - self._delay_frame(frame_id, barrier_time + duration, context) - - def visit_QuantumBarrier(self, node: ast.QuantumBarrier, context: _ParseState) -> None: - """Visit a Quantum Barrier. - 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: - # 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) - # 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) - barrier_time = _ceil_approx(max_time / lcm) * lcm - - for frame_id in frames: - self._delay_frame(frame_id, barrier_time, context) - - def visit_FunctionCall(self, node: ast.FunctionCall, context: _ParseState) -> Any: - """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) - - def visit_Identifier(self, node: ast.Identifier, context: _ParseState) -> Any: - """Visit Identifier. - node.name - x - 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] - else: - return node.name - - def visit_UnaryExpression(self, node: ast.UnaryExpression, context: _ParseState) -> bool: - """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) - elif node.op == ast.UnaryOperator["!"]: - return not self.visit(node.expression, context) - elif node.op == ast.UnaryOperator["~"]: - return ~self.visit(node.expression, context) - else: - raise NotImplementedError - - # flake8: noqa: C901 - def visit_BinaryExpression(self, node: ast.BinaryExpression, context: _ParseState) -> Any: - """Visit Binary Expression. - node.lhs, node.rhs, node.op - 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) - - op = ast.BinaryOperator - - if node.op == op["+"]: - return lhs + rhs - elif node.op == op["-"]: - return lhs - rhs - elif node.op == op["*"]: - return lhs * rhs - elif node.op == op["/"]: - return lhs / rhs - elif node.op == op["%"]: - return lhs % rhs - elif node.op == op["**"]: - return lhs**rhs - elif node.op == op[">"]: - return lhs > rhs - elif node.op == op["<"]: - return lhs < rhs - elif node.op == op[">="]: - return lhs >= rhs - elif node.op == op["<="]: - return lhs <= rhs - elif node.op == op["=="]: - return lhs == rhs - elif node.op == op["!="]: - return lhs != rhs - elif node.op == op["&&"]: - return lhs and rhs - elif node.op == op["||"]: - return lhs or rhs - elif node.op == op["|"]: - return lhs | rhs - elif node.op == op["^"]: - return lhs ^ rhs - elif node.op == op["&"]: - return lhs & rhs - elif node.op == op["<<"]: - return lhs << rhs - elif node.op == op[">>"]: - return lhs >> rhs - else: - raise NotImplementedError - - 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) -> 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) -> complex: - """Visit Imaginary Number Literal. - node.value - 1.3im - Args: - 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) -> 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) -> 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 bool(node.value) - - 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}") - multiplier = 10 ** (-3 * self.TIME_UNIT_TO_EXP[node.unit.name]) - return multiplier * node.value - - # The following are function call declarations supported by the parser. - - 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. - """ - frame = self.visit(node.arguments[0], context) - value = self.visit(node.arguments[1], context) - context.frame_data[frame].frequency = value - - 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. - """ - frame = self.visit(node.arguments[0], context) - value = self.visit(node.arguments[1], context) - context.frame_data[frame].frequency += value - - 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. - """ - frame = self.visit(node.arguments[0], context) - value = self.visit(node.arguments[1], context) - context.frame_data[frame].phase = value % (2 * np.pi) - - 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. - """ - frame = self.visit(node.arguments[0], context) - value = self.visit(node.arguments[1], context) - context.frame_data[frame].phase += value - context.frame_data[frame].phase %= 2 * np.pi - - 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. - """ - frame = self.visit(node.arguments[0], context) - value = self.visit(node.arguments[1], context) - context.frame_data[frame].scale = value - - 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. - """ - - 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): - amps = self.visit(node.arguments[1], context) - 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) - elif isinstance(amps, str): - raise NameError(f"waveform '{amps}' is not defined.") - else: - raise NotImplementedError - frame_data = context.frame_data[frame_id] - for value in amps: - self.amplitudes[frame_id].put( - frame_data.current_time, complex(frame_data.scale * value) - ) - self.frequencies[frame_id].put(frame_data.current_time, frame_data.frequency) - self.phases[frame_id].put(frame_data.current_time, frame_data.phase) - frame_data.current_time += frame_data.dt - - 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. - """ - args = [self.visit(arg, context) for arg in node.arguments] - return ConstantWaveform(*args) - - 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. - """ - args = [self.visit(arg, context) for arg in node.arguments] - return GaussianWaveform(*args) - - 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. - """ - args = [self.visit(arg, context) for arg in node.arguments] - return DragGaussianWaveform(*args) - - -def _init_frame_data(frames: dict[str, Frame]) -> dict[str, _FrameState]: - 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: - if m := ( - re.search(r"q(\d+)_q(\d+)_[a-z_]+", frameId) or re.search(r"[rq](\d+)_[a-z_]+", frameId) - ): - for qubit in m.groups(): - if qubit in mapping: - mapping[qubit].append(frameId) - else: - mapping[qubit] = [frameId] - return mapping - - -def _lcm_floats(*dts: list[float]) -> float: - """Return the least common multiple of time increments of a list of frames - A time increment is the inverse of the corresponding sample rate which is considered - an integer. - LCM of rational numbers is lcm = (LCM of numerators) / (GCD of denominators) - Hence the LCM of dts is 1/gcd([sample rates]) - - 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:]: - res_gcd = np.gcd(res_gcd, sr) - return 1 / res_gcd - - -def _ceil_approx(number: float) -> int: - return int(number) + 1 if abs(number - int(number)) > 0.001 else int(number) diff --git a/src/braket/pulse/ast/free_parameters.py b/src/braket/pulse/ast/free_parameters.py deleted file mode 100644 index 1750275a..00000000 --- a/src/braket/pulse/ast/free_parameters.py +++ /dev/null @@ -1,95 +0,0 @@ -# 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 operator -from typing import Union - -from openpulse import ast -from openqasm3.visitor import QASMTransformer -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], program: Program): - self.param_values = param_values - self.program = program - super().__init__() - - def visit_Identifier( - self, identifier: ast.Identifier - ) -> 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[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. - """ - lhs = self.visit(node.lhs) - rhs = self.visit(node.rhs) - 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["*"]: - 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: - node (UnaryExpression): The node. - - Returns: - Union[UnaryExpression, FloatLiteral]: The transformed identifier. - """ - 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 deleted file mode 100644 index 25832aa5..00000000 --- a/src/braket/pulse/ast/qasm_parser.py +++ /dev/null @@ -1,64 +0,0 @@ -# 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 io - -from openpulse import ast -from openpulse.printer import Printer -from openqasm3.printer import PrinterState - - -class _PulsePrinter(Printer): - """Walks the AST and prints it to an OpenQASM3 string.""" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - 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.name)) - - def visit_ClassicalDeclaration( - self, node: ast.ClassicalDeclaration, context: PrinterState - ) -> 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 (PrinterState): The printer state context. - """ - # Skip port declarations in output - if not isinstance(node.type, ast.PortType): - super().visit_ClassicalDeclaration(node, context) - - -def ast_to_qasm(ast: ast.Program) -> str: - """Converts an AST program to OpenQASM - - Args: - ast (ast.Program): The AST program. - - Returns: - str: a str representing the OpenPulse program encoding the program. - """ - out = io.StringIO() - _PulsePrinter(out, indent=" ").visit(ast) - return out.getvalue().strip() diff --git a/src/braket/pulse/ast/qasm_transformer.py b/src/braket/pulse/ast/qasm_transformer.py deleted file mode 100644 index 40a6d25d..00000000 --- a/src/braket/pulse/ast/qasm_transformer.py +++ /dev/null @@ -1,59 +0,0 @@ -# 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 typing import Any, Optional - -from openpulse import ast -from openqasm3.visitor import QASMTransformer - - -class _IRQASMTransformer(QASMTransformer): - """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. - """ - - def __init__(self, register_identifier: Optional[str] = None): - self._register_identifier = register_identifier - self._capture_v0_count = 0 - super().__init__() - - 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. - """ - if ( - not isinstance(expression_statement.expression, ast.FunctionCall) - or expression_statement.expression.name.name != "capture_v0" - or not self._register_identifier - ): - 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/frame.py b/src/braket/pulse/frame.py deleted file mode 100644 index 63700d22..00000000 --- a/src/braket/pulse/frame.py +++ /dev/null @@ -1,83 +0,0 @@ -# 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 typing import Any, Optional - -from oqpy import FrameVar as OQFrame -from oqpy.base import OQPyExpression - -from braket.pulse.port import Port - - -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. - """ - - def __init__( - self, - frame_id: str, - port: Port, - frequency: float, - phase: float = 0, - 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. - frequency (float): frequency to which this frame should be initialized. - phase (float): phase to which this frame should be initialized. Defaults to 0. - is_predefined (bool): bool indicating whether this is a predefined frame on - the device. Defaults to False. - properties (Optional[dict[str, Any]]): Dict containing properties of this frame. - Defaults to None. - """ - self._frame_id = frame_id - self.port = port - self.frequency = frequency - self.phase = phase - self.is_predefined = is_predefined - self.properties = properties - - @property - def id(self) -> str: - """Returns a str indicating the frame id.""" - return self._frame_id - - def __eq__(self, other: Frame) -> bool: - return ( - ( - (self.id == other.id) - and (self.port == other.port) - and math.isclose(self.frequency, other.frequency) - and math.isclose(self.phase, other.phase) - ) - if isinstance(other, Frame) - else False - ) - - def _to_oqpy_expression(self) -> OQPyExpression: - return OQFrame( - port=self.port._to_oqpy_expression(), - frequency=self.frequency, - phase=self.phase, - name=self.id, - needs_declaration=not self.is_predefined, - ) diff --git a/src/braket/pulse/port.py b/src/braket/pulse/port.py deleted file mode 100644 index 99b1acca..00000000 --- a/src/braket/pulse/port.py +++ /dev/null @@ -1,54 +0,0 @@ -# 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, Optional - -from oqpy import PortVar -from oqpy.base import OQPyExpression - - -class Port: - """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. - properties (Optional[dict[str, Any]]): Dict containing properties of - this port. Defaults to None. - """ - self._port_id = port_id - self._dt = dt - self.properties = properties - - @property - def id(self) -> str: - """Returns a str indicating the port id.""" - return self._port_id - - @property - def dt(self) -> float: - """Returns the smallest time step that may be used on the control hardware.""" - return self._dt - - def __eq__(self, other: Port) -> bool: - return self.id == other.id if isinstance(other, Port) else False - - def _to_oqpy_expression(self) -> OQPyExpression: - return PortVar(name=self.id) diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py deleted file mode 100644 index 9d43127a..00000000 --- a/src/braket/pulse/pulse_sequence.py +++ /dev/null @@ -1,446 +0,0 @@ -# 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 builtins -from copy import deepcopy -from inspect import signature -from typing import Any, Union - -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 -from braket.parametric.parameterizable import Parameterizable -from braket.pulse.ast.approximation_parser import _ApproximationParser -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 -from braket.pulse.pulse_sequence_trace import PulseSequenceTrace -from braket.pulse.waveforms import Waveform -from braket.registers.qubit_set import QubitSet - - -class PulseSequence: - """A representation of a collection of instructions to be performed on a quantum device - and the requested results. - """ - - def __init__(self): - self._capture_v0_count = 0 - self._program = Program(simplify_constants=False) - self._frames = {} - self._waveforms = {} - self._free_parameters = set() - - def to_time_trace(self) -> PulseSequenceTrace: - """Generate an approximate trace of the amplitude, frequency, phase for each frame - contained in the PulseSequence, under the action of the instructions contained in - the pulse sequence. - - Returns: - PulseSequenceTrace: The approximation information with each attribute - (amplitude, frequency and phase) mapping a str (frame id) to a TimeSeries - (containing the time evolution of that attribute). - """ - parser = _ApproximationParser(deepcopy(self._program), self._frames) - return PulseSequenceTrace( - amplitudes=parser.amplitudes, frequencies=parser.frequencies, phases=parser.phases - ) - - @property - def parameters(self) -> set[FreeParameter]: - """Returns the set of `FreeParameter` s in the PulseSequence.""" - return self._free_parameters.copy() - - 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. - - Args: - frame (Frame): Frame for which the frequency needs to be set. - frequency (Union[float, FreeParameterExpression]): frequency value to set - for the specified frame. - - 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) - self._frames[frame.id] = frame - return self - - 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. - - Args: - frame (Frame): Frame for which the frequency needs to be shifted. - frequency (Union[float, FreeParameterExpression]): frequency value by which to shift - the frequency for the specified frame. - - Returns: - PulseSequence: self, with the instruction added. - """ - _validate_uniqueness(self._frames, frame) - self._register_free_parameters(frequency) - self._program.shift_frequency(frame=frame, freq=frequency) - self._frames[frame.id] = frame - return self - - 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. - - Args: - frame (Frame): Frame for which the frequency needs to be set. - phase (Union[float, FreeParameterExpression]): phase value to set - for the specified frame. - - Returns: - PulseSequence: self, with the instruction added. - """ - _validate_uniqueness(self._frames, frame) - self._register_free_parameters(phase) - self._program.set_phase(frame=frame, phase=phase) - self._frames[frame.id] = frame - return self - - 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. - - Args: - frame (Frame): Frame for which the phase needs to be shifted. - phase (Union[float, FreeParameterExpression]): phase value by which to shift - the phase for the specified frame. - - Returns: - PulseSequence: self, with the instruction added. - """ - _validate_uniqueness(self._frames, frame) - self._register_free_parameters(phase) - self._program.shift_phase(frame=frame, phase=phase) - self._frames[frame.id] = frame - return self - - 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. - - Args: - frame (Frame): Frame for which the scale needs to be set. - scale (Union[float, FreeParameterExpression]): scale value to set - on the specified frame. - - Returns: - PulseSequence: self, with the instruction added. - """ - _validate_uniqueness(self._frames, frame) - self._register_free_parameters(scale) - self._program.set_scale(frame=frame, scale=scale) - self._frames[frame.id] = frame - return self - - def delay( - self, - 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. - - 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. - """ - 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] - _validate_uniqueness(self._frames, qubits_or_frames) - self._program.delay(time=duration, qubits_or_frames=qubits_or_frames) - for frame in qubits_or_frames: - self._frames[frame.id] = frame - else: - 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 - frames. - - Args: - qubits_or_frames (Union[list[Frame], QubitSet]): Qubits or frames which the delay - needs to be introduced. - - Returns: - PulseSequence: self, with the instruction added. - """ - if not isinstance(qubits_or_frames, QubitSet): - _validate_uniqueness(self._frames, qubits_or_frames) - self._program.barrier(qubits_or_frames=qubits_or_frames) - for frame in qubits_or_frames: - self._frames[frame.id] = frame - else: - physical_qubits = [PhysicalQubits[int(x)] for x in qubits_or_frames] - self._program.barrier(qubits_or_frames=physical_qubits) - return self - - def play(self, frame: Frame, waveform: Waveform) -> PulseSequence: - """Adds an instruction to play the specified waveform on the supplied frame. - - Args: - frame (Frame): Frame on which the specified waveform signal would be output. - waveform (Waveform): Waveform envelope specifying the signal to output on the - specified frame. - - Returns: - PulseSequence: returns self. - """ - _validate_uniqueness(self._frames, frame) - _validate_uniqueness(self._waveforms, waveform) - if isinstance(waveform, Parameterizable): - for param in waveform.parameters: - self._register_free_parameters(param) - self._program.play(frame=frame, waveform=waveform) - self._frames[frame.id] = frame - self._waveforms[waveform.id] = waveform - return self - - def capture_v0(self, frame: Frame) -> PulseSequence: - """Adds an instruction to capture the bit output from measuring the specified frame. - - Args: - frame (Frame): Frame on which the capture operation needs - to be performed. - - Returns: - PulseSequence: self, with the instruction added. - """ - _validate_uniqueness(self._frames, frame) - self._program.function_call("capture_v0", [frame]) - self._capture_v0_count += 1 - self._frames[frame.id] = frame - 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 - share the same name, all the parameters of that name will be set to the mapped value. - - Args: - param_values (dict[str, float]): A mapping of FreeParameter names - to a value to assign to them. - - Returns: - PulseSequence: Returns a PulseSequence with all present parameters fixed to - their respective values. - """ - program = deepcopy(self._program) - tree: ast.Program = program.to_ast(include_externs=False, ignore_needs_declaration=True) - new_tree: ast.Program = _FreeParameterTransformer(param_values, program).visit(tree) - - 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) - - new_pulse_sequence = PulseSequence() - new_pulse_sequence._program = new_program - new_pulse_sequence._frames = deepcopy(self._frames) - new_pulse_sequence._waveforms = { - wf.id: wf.bind_values(**param_values) if isinstance(wf, Parameterizable) else wf - for wf in deepcopy(self._waveforms).values() - } - - # Update waveforms to bind values - for v in new_program.undeclared_vars: - if v in self._waveforms: - new_program.undeclared_vars[v] = new_pulse_sequence._waveforms[ - v - ]._to_oqpy_expression() - - new_pulse_sequence._capture_v0_count = self._capture_v0_count - new_pulse_sequence._free_parameters = { - p for p in self._free_parameters if p.name not in param_values - } - - return new_pulse_sequence - - 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) - 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: - register_identifier = "psb" - program.declare( - BitVar[self._capture_v0_count](name=register_identifier), to_beginning=True - ) - tree = program.to_ast(encal=True, include_externs=False) - tree = _IRQASMTransformer(register_identifier).visit(tree) - else: - tree = program.to_ast(encal=True, include_externs=False) - return ast_to_qasm(tree) - - def _register_free_parameters( - self, - parameter: Union[float, FreeParameterExpression], - ) -> None: - if isinstance(parameter, FreeParameterExpression) and isinstance( - parameter.expression, Expr - ): - for p in parameter.expression.free_symbols: - self._free_parameters.add(FreeParameter(p.name)) - - def _parse_arg_from_calibration_schema( - self, argument: dict, waveforms: dict[Waveform], frames: dict[Frame] - ) -> Any: - nonprimitive_arg_type = { - "frame": frames.get, - "waveform": waveforms.get, - "expr": FreeParameterExpression, - } - if argument["type"] in nonprimitive_arg_type: - return nonprimitive_arg_type[argument["type"]](argument["value"]) - else: - 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 - - 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. - """ - calibration_sequence = cls() - for instr in calibration: - 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) - return calibration_sequence - - 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 = {} - if arg is not None: - for param in self.parameters: - param_values[str(param)] = arg - for key, val in kwargs.items(): - param_values[str(key)] = val - return self.make_bound_pulse_sequence(param_values) - - def __eq__(self, other: PulseSequence): - sort_input_parameters = True - return ( - isinstance(other, PulseSequence) - and self.parameters == other.parameters - and self.to_ir(sort_input_parameters) == other.to_ir(sort_input_parameters) - ) - - -def _validate_uniqueness( - mapping: dict[str, Any], values: Union[Frame, Waveform, list[Frame], list[Waveform]] -) -> None: - if not isinstance(values, list): - values = [values] - - for value in values: - if value.id in mapping and mapping[value.id] != value: - raise ValueError( - f"{value.id} has already been used for defining {mapping[value.id]} " - f"which differs from {value}" - ) diff --git a/src/braket/pulse/pulse_sequence_trace.py b/src/braket/pulse/pulse_sequence_trace.py deleted file mode 100644 index dba4762b..00000000 --- a/src/braket/pulse/pulse_sequence_trace.py +++ /dev/null @@ -1,38 +0,0 @@ -# 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 dataclasses import dataclass - -from braket.timings.time_series import TimeSeries - - -@dataclass -class PulseSequenceTrace: - """This class encapsulates the data representing the PulseSequence execution. It contains - the trace of amplitude, frequency and phase information for each frame in the - PulseSequence. - - Args: - amplitudes (dict): A dictionary of frame ID to a TimeSeries of complex values specifying - the waveform amplitude. - frequencies (dict):A dictionary of frame ID to a TimeSeries of float values specifying - the waveform frequency. - phases (dict):A dictionary of frame ID to a TimeSeries of float values specifying - the waveform phase. - """ - - amplitudes: dict[str, TimeSeries] - frequencies: dict[str, TimeSeries] - phases: dict[str, TimeSeries] diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py deleted file mode 100644 index 915d187a..00000000 --- a/src/braket/pulse/waveforms.py +++ /dev/null @@ -1,520 +0,0 @@ -# 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 random -import string -from abc import ABC, abstractmethod -from typing import Optional, Union - -import numpy as np -from oqpy import WaveformVar, bool_, complex128, declare_waveform_generator, duration, float64 -from oqpy.base import OQPyExpression - -from braket.parametric.free_parameter import FreeParameter -from braket.parametric.free_parameter_expression import ( - FreeParameterExpression, - subs_if_free_parameter, -) -from braket.parametric.parameterizable import Parameterizable - - -class Waveform(ABC): - """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 - time at which the signal is captured. See https://openqasm.com/language/openpulse.html#waveforms - for more details. - """ - - @abstractmethod - def _to_oqpy_expression(self) -> OQPyExpression: - """Returns an OQPyExpression defining this waveform.""" - - @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: - 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 # 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. - """ - - -class ArbitraryWaveform(Waveform): - """An arbitrary waveform with amplitudes at each timestep explicitly specified using - 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 - of the frame to which waveform is applied to. - id (Optional[str]): The identifier used for declaring this waveform. A random string of - ascii characters is assigned by default. - """ - 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: ArbitraryWaveform): - return isinstance(other, ArbitraryWaveform) and (self.amplitudes, self.id) == ( - other.amplitudes, - other.id, - ) - - def _to_oqpy_expression(self) -> OQPyExpression: - """Returns an OQPyExpression defining this waveform. - - Returns: - OQPyExpression: The OQPyExpression. - """ - return WaveformVar(init_expression=self.amplitudes, 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. - - Raises: - NotImplementedError: This class does not implement sample. - - Returns: - np.ndarray: The sample amplitudes for this waveform. - """ - raise NotImplementedError - - @staticmethod - def _from_calibration_schema(waveform_json: dict) -> ArbitraryWaveform: - wave_id = waveform_json["waveformId"] - complex_amplitudes = [complex(i[0], i[1]) for i in waveform_json["amplitudes"]] - return ArbitraryWaveform(complex_amplitudes, wave_id) - - -class ConstantWaveform(Waveform, Parameterizable): - """A constant waveform which holds the supplied `iq` value as its amplitude for the - 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. - iq (complex): complex value specifying the amplitude of the waveform. - id (Optional[str]): The identifier used for declaring this waveform. A random string of - ascii characters is assigned by default. - """ - self.length = length - 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 - expressions or bound values. - - Returns: - list[Union[FreeParameterExpression, FreeParameter, float]]: a list of parameters. - """ - return [self.length] - - 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. - """ - constructor_kwargs = { - "length": subs_if_free_parameter(self.length, **kwargs), - "iq": self.iq, - "id": self.id, - } - return ConstantWaveform(**constructor_kwargs) - - def __eq__(self, other: ConstantWaveform): - return isinstance(other, ConstantWaveform) and (self.length, self.iq, self.id) == ( - other.length, - other.iq, - other.id, - ) - - def _to_oqpy_expression(self) -> OQPyExpression: - """Returns an OQPyExpression defining this waveform. - - Returns: - OQPyExpression: The OQPyExpression. - """ - constant_generator = declare_waveform_generator( - "constant", [("length", duration), ("iq", complex128)] - ) - return WaveformVar( - init_expression=constant_generator(self.length, self.iq), - 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. - """ - # 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 - - @staticmethod - def _from_calibration_schema(waveform_json: dict) -> ConstantWaveform: - wave_id = waveform_json["waveformId"] - length = iq = None - for val in waveform_json["arguments"]: - if val["name"] == "length": - length = ( - float(val["value"]) - if val["type"] == "float" - else FreeParameterExpression(val["value"]) - ) - if val["name"] == "iq": - iq = ( - complex(val["value"]) - if val["type"] == "complex" - else FreeParameterExpression(val["value"]) - ) - return ConstantWaveform(length=length, iq=iq, id=wave_id) - - -class DragGaussianWaveform(Waveform, Parameterizable): - """A gaussian waveform with an additional gaussian derivative component and lifting applied.""" - - def __init__( - self, - length: Union[float, FreeParameterExpression], - sigma: Union[float, FreeParameterExpression], - beta: Union[float, FreeParameterExpression], - amplitude: Union[float, FreeParameterExpression] = 1, - 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. - sigma (Union[float, FreeParameterExpression]): A measure (in seconds) of - how wide or narrow the Gaussian peak is. - beta (Union[float, FreeParameterExpression]): The correction amplitude. - amplitude (Union[float, FreeParameterExpression]): The amplitude of the - waveform envelope. Defaults to 1. - zero_at_edges (bool): bool specifying whether the waveform amplitude is clipped to - zero 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.sigma = sigma - self.beta = beta - self.amplitude = amplitude - 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 - expressions or bound values. - """ - return [self.length, self.sigma, self.beta, self.amplitude] - - 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. - """ - constructor_kwargs = { - "length": subs_if_free_parameter(self.length, **kwargs), - "sigma": subs_if_free_parameter(self.sigma, **kwargs), - "beta": subs_if_free_parameter(self.beta, **kwargs), - "amplitude": subs_if_free_parameter(self.amplitude, **kwargs), - "zero_at_edges": self.zero_at_edges, - "id": self.id, - } - return DragGaussianWaveform(**constructor_kwargs) - - def __eq__(self, other: DragGaussianWaveform): - return isinstance(other, DragGaussianWaveform) and ( - self.length, - self.sigma, - self.beta, - self.amplitude, - self.zero_at_edges, - self.id, - ) == (other.length, other.sigma, other.beta, other.amplitude, other.zero_at_edges, other.id) - - def _to_oqpy_expression(self) -> OQPyExpression: - """Returns an OQPyExpression defining this waveform. - - Returns: - OQPyExpression: The OQPyExpression. - """ - drag_gaussian_generator = declare_waveform_generator( - "drag_gaussian", - [ - ("length", duration), - ("sigma", duration), - ("beta", float64), - ("amplitude", float64), - ("zero_at_edges", bool_), - ], - ) - return WaveformVar( - init_expression=drag_gaussian_generator( - self.length, - self.sigma, - self.beta, - 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) - t0 = self.length / 2 - zero_at_edges_int = int(self.zero_at_edges) - samples = ( - (1 - (1.0j * self.beta * ((sample_range - t0) / self.sigma**2))) - * ( - 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) -> DragGaussianWaveform: - 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 DragGaussianWaveform(**waveform_parameters) - - -class GaussianWaveform(Waveform, Parameterizable): - """A waveform with amplitudes following a gaussian distribution for the specified parameters.""" - - def __init__( - self, - length: Union[float, FreeParameterExpression], - sigma: Union[float, FreeParameterExpression], - amplitude: Union[float, FreeParameterExpression] = 1, - 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. - sigma (Union[float, FreeParameterExpression]): A measure (in seconds) of how wide - or narrow the Gaussian peak is. - amplitude (Union[float, FreeParameterExpression]): The amplitude of the waveform - envelope. Defaults to 1. - zero_at_edges (bool): bool specifying whether the waveform amplitude is clipped to - zero 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.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"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 - expressions or bound values. - """ - return [self.length, self.sigma, self.amplitude] - - 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. - """ - constructor_kwargs = { - "length": subs_if_free_parameter(self.length, **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 GaussianWaveform(**constructor_kwargs) - - def __eq__(self, other: GaussianWaveform): - return isinstance(other, GaussianWaveform) and ( - self.length, - self.sigma, - self.amplitude, - self.zero_at_edges, - self.id, - ) == (other.length, 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. - """ - gaussian_generator = declare_waveform_generator( - "gaussian", - [ - ("length", duration), - ("sigma", duration), - ("amplitude", float64), - ("zero_at_edges", bool_), - ], - ) - return WaveformVar( - init_expression=gaussian_generator( - self.length, - 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) - t0 = self.length / 2 - zero_at_edges_int = int(self.zero_at_edges) - samples = ( - 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: - 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 GaussianWaveform(**waveform_parameters) - - -def _make_identifier_name() -> str: - return "".join([random.choice(string.ascii_letters) for _ in range(10)]) # noqa S311 - - -def _parse_waveform_from_calibration_schema(waveform: dict) -> Waveform: - waveform_names = { - "arbitrary": ArbitraryWaveform._from_calibration_schema, - "drag_gaussian": DragGaussianWaveform._from_calibration_schema, - "gaussian": GaussianWaveform._from_calibration_schema, - "constant": ConstantWaveform._from_calibration_schema, - } - if "amplitudes" in waveform: - waveform["name"] = "arbitrary" - if waveform["name"] in waveform_names: - return waveform_names[waveform["name"]](waveform) - waveform_id = waveform["waveformId"] - raise ValueError(f"The waveform {waveform_id} of cannot be constructed") diff --git a/src/braket/quantum_information/__init__.py b/src/braket/quantum_information/__init__.py deleted file mode 100644 index 647d0667..00000000 --- a/src/braket/quantum_information/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# 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.quantum_information.pauli_string import PauliString # noqa: F401 diff --git a/src/braket/quantum_information/pauli_string.py b/src/braket/quantum_information/pauli_string.py deleted file mode 100644 index 0de8e8e3..00000000 --- a/src/braket/quantum_information/pauli_string.py +++ /dev/null @@ -1,404 +0,0 @@ -# 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 itertools -from typing import Optional, Union - -from braket.circuits.circuit import Circuit -from braket.circuits.observables import I, TensorProduct, X, Y, Z - -_IDENTITY = "I" -_PAULI_X = "X" -_PAULI_Y = "Y" -_PAULI_Z = "Z" -_PAULI_INDICES = {_IDENTITY: 0, _PAULI_X: 1, _PAULI_Y: 2, _PAULI_Z: 3} -_PRODUCT_MAP = { - "X": {"Y": ["Z", 1j], "Z": ["Y", -1j]}, - "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} - - -class PauliString: - """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") - if isinstance(pauli_string, PauliString): - self._phase = pauli_string._phase - self._qubit_count = pauli_string._qubit_count - self._nontrivial = pauli_string._nontrivial - elif isinstance(pauli_string, str): - self._phase, factors_str = PauliString._split(pauli_string) - self._qubit_count = len(factors_str) - self._nontrivial = { - i: factors_str[i] for i in range(len(factors_str)) if factors_str[i] != "I" - } - else: - raise TypeError(f"Pauli word {pauli_string} must be of type {PauliString} or {str}") - - @property - def phase(self) -> int: - """int: The phase of the Pauli string. - - Can be one of +/-1 - """ - return self._phase - - @property - def qubit_count(self) -> int: - """int: The number of qubits this Pauli string acts on.""" - return self._qubit_count - - 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)] - ) - - def weight_n_substrings(self, weight: int) -> tuple[PauliString, ...]: - r"""Returns every substring of this Pauli string with exactly `weight` nontrivial factors. - - The number of substrings is equal to :math:`\binom{n}{w}`, where :math`n` is the number of - nontrivial (non-identity) factors in the Pauli string and :math`w` is `weight`. - - Args: - weight (int): The number of non-identity factors in the substrings. - - Returns: - tuple[PauliString, ...]: A tuple of weight-n Pauli substrings. - """ - substrings = [] - for indices in itertools.combinations(self._nontrivial, weight): - factors = [ - ( - self._nontrivial[qubit] - if qubit in set(indices).intersection(self._nontrivial) - else "I" - ) - for qubit in range(self._qubit_count) - ] - substrings.append( - PauliString(f"{PauliString._phase_to_str(self._phase)}{''.join(factors)}") - ) - return tuple(substrings) - - def eigenstate(self, signs: Optional[Union[str, list[int], tuple[int, ...]]] = 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 - Pauli operator. For example, a Pauli string +XYZ and signs ++- has factors +X, +Y and -Z, - with the corresponding qubits in states `|+⟩` , `|i⟩` , and `|1⟩` respectively (the global - phase of the Pauli string is ignored). - - Args: - signs (Optional[Union[str, list[int], tuple[int, ...]]]): The sign of each factor of the - eigenstate, specified either as a string of "+" and "_", or as a list or tuple of - +/-1. The length of signs must be equal to the length of the Pauli string. If not - specified, it is assumed to be all +. Default: None. - - Returns: - Circuit: A circuit that prepares the desired eigenstate of the Pauli string. - - Raises: - ValueError: If the length of signs is not equal to that of the Pauli string or the signs - are invalid. - """ - qubit_count = self._qubit_count - if not signs: - signs = "+" * qubit_count - elif len(signs) != qubit_count: - raise ValueError( - f"signs must be the same length of the Pauli string ({qubit_count}), " - f"but was {len(signs)}" - ) - signs_tup = ( - tuple(_SIGN_MAP.get(sign) for sign in signs) if isinstance(signs, str) else tuple(signs) - ) - if not set(signs_tup) <= {1, -1}: - raise ValueError(f"signs must be +/-1, got {signs}") - return self._generate_eigenstate_circuit(signs_tup) - - def dot(self, other: PauliString, inplace: bool = False) -> PauliString: - """Right multiplies this Pauli string with the argument. - - Returns the result of multiplying the current circuit by the argument on its right. For - example, if called on `-XYZ` with argument `ZYX`, then `YIY` is the result. In-place - computation is off by default. - - Args: - other (PauliString): The right multiplicand. - inplace (bool): If `True`, `self` is updated to hold the product. - - Returns: - PauliString: The resultant circuit from right multiplying `self` with `other`. - - Raises: - ValueError: If the lengths of the Pauli strings being multiplied differ. - """ - if self._qubit_count != other._qubit_count: - raise ValueError( - f"Input Pauli string must be of length ({self._qubit_count}), " - f"not {other._qubit_count}" - ) - pauli_result = "" - phase_result = self._phase * other._phase - for i in range(self._qubit_count): - # Are either identity? - if i not in self._nontrivial and i not in other._nontrivial: - pauli_result += "I" - elif i not in self._nontrivial: - pauli_result += other._nontrivial[i] - elif i not in other._nontrivial: - pauli_result += self._nontrivial[i] - elif self._nontrivial[i] == other._nontrivial[i]: - pauli_result += "I" - else: - gate, phase = _PRODUCT_MAP[self._nontrivial[i]][other._nontrivial[i]] - pauli_result += gate - phase_result *= phase - - # ignore complex global phase - if phase_result.real < 0 or phase_result.imag < 0: - pauli_result = f"-{pauli_result}" - out_pauli_string = PauliString(pauli_result) - - if inplace: - self._phase = out_pauli_string._phase - self._qubit_count = out_pauli_string._qubit_count - self._nontrivial = out_pauli_string._nontrivial - return out_pauli_string - - def __mul__(self, other: PauliString) -> PauliString: - """Right multiplication operator overload using `dot()`. - - Returns the result of multiplying the current circuit by the argument on its right. - - Args: - other (PauliString): The right multiplicand. - - Returns: - PauliString: The resultant circuit from right multiplying `self` with `other`. - - Raises: - ValueError: If the lengths of the Pauli strings being multiplied differ. - - See Also: - `braket.quantum_information.PauliString.dot()` - """ - return self.dot(other) - - def __imul__(self, other: PauliString) -> PauliString: - """Operator overload for right-multiplication assignment (`*=`) using `dot()`. - - Right-multiplies `self` by `other`, and assigns the result to `self`. - - Args: - other (PauliString): The right multiplicand. - - Returns: - PauliString: The resultant circuit from right multiplying `self` with `other`. - - Raises: - ValueError: If the lengths of the Pauli strings being multiplied differ. - - See Also: - `braket.quantum_information.PauliString.dot()` - """ - return self.dot(other, inplace=True) - - def power(self, n: int, inplace: bool = False) -> PauliString: - """Composes Pauli string with itself n times. - - Args: - n (int): The number of times to self-multiply. Can be any integer value. - inplace (bool): Update `self` if `True` - - Returns: - PauliString: If `n` is positive, result from self-multiplication `n` times. - If zero, identity. If negative, self-multiplication from trivial - inverse (recall Pauli operators are involutory). - - Raises: - ValueError: If `n` isn't a plain Python `int`. - """ - if not isinstance(n, int): - raise ValueError("Must be raised to integer power") - - # Since pauli ops involutory, result is either identity or unchanged - pauli_other = PauliString(self) - if n % 2 == 0: - pauli_other._phase = 1 - pauli_other._nontrivial = {} - - if inplace: - self._phase = pauli_other._phase - self._qubit_count = pauli_other._qubit_count - self._nontrivial = pauli_other._nontrivial - return pauli_other - - def __pow__(self, n: int) -> PauliString: - """Pow operator overload for Pauli string composition. - - Syntactic sugar for `power()`. - - Args: - n (int): The number of times to self-multiply. Can be any integer - - Returns: - PauliString: If `n` is positive, result from self-multiplication `n` times. - If zero, identity. If negative, self-multiplication from trivial - inverse (recall Pauli operators are involutory). - - Raises: - ValueError: If `n` isn't a plain Python `int`. - - See Also: - `braket.quantum_information.PauliString.power()` - """ - return self.power(n) - - def __ipow__(self, n: int) -> PauliString: - """Operator overload for in-place pow assignment (`**=`) using `power()`. - - Syntactic sugar for in-place `power()`. - - Args: - n (int): The number of times to self-multiply. Can be any integer - - Returns: - PauliString: If `n` is positive, result from self-multiplication `n` times. - If zero, identity. If negative, self-multiplication from trivial - inverse (recall Pauli operators are involutory). - - Raises: - ValueError: If `n` isn't a plain Python `int`. - - See Also: - `braket.quantum_information.PauliString.power()` - """ - return self.power(n, inplace=True) - - def to_circuit(self) -> Circuit: - """Returns circuit represented by this `PauliString`. - - Returns: - Circuit: The circuit for this `PauliString`. - """ - circ = Circuit() - for qubit in range(self._qubit_count): - if qubit not in self._nontrivial: - circ = circ.i(qubit) - elif self._nontrivial[qubit] == "X": - circ = circ.x(qubit) - elif self._nontrivial[qubit] == "Y": - circ = circ.y(qubit) - else: - circ = circ.z(qubit) - return circ - - def __eq__(self, other: PauliString): - if isinstance(other, PauliString): - return ( - self._phase == other._phase - and self._nontrivial == other._nontrivial - and self._qubit_count == other._qubit_count - ) - return False - - def __getitem__(self, item: int): - if item >= self._qubit_count: - raise IndexError(item) - return _PAULI_INDICES[self._nontrivial.get(item, "I")] - - 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) - ] - return f"{PauliString._phase_to_str(self._phase)}{''.join(factors)}" - - @staticmethod - def _split(pauli_word: str) -> tuple[int, str]: - index = 0 - phase = 1 - if pauli_word[index] in {"+", "-"}: - phase *= int(f"{pauli_word[index]}1") - index += 1 - unsigned = pauli_word[index:] - if not unsigned: - raise ValueError("Pauli string cannot be empty") - if set(unsigned) - _PAULI_INDICES.keys(): - raise ValueError(f"{pauli_word} is not a valid Pauli string") - return phase, unsigned - - @staticmethod - def _phase_to_str(phase: int) -> str: - return "+" if phase > 0 else "-" - - def _generate_eigenstate_circuit(self, signs: tuple[int, ...]) -> Circuit: - circ = Circuit() - for qubit in range(len(signs)): - state = signs[qubit] * self[qubit] - if state == -3: - circ.x(qubit) - elif state == 1: - circ.h(qubit) - elif state == -1: - circ.x(qubit).h(qubit) - elif state == 2: - circ.h(qubit).s(qubit) - elif state == -2: - circ.h(qubit).si(qubit) - return circ diff --git a/src/braket/registers/__init__.py b/src/braket/registers/__init__.py deleted file mode 100644 index 0f433333..00000000 --- a/src/braket/registers/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# 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.registers.qubit import Qubit, QubitInput # noqa: F401 -from braket.registers.qubit_set import QubitSet, QubitSetInput # noqa: F401 diff --git a/src/braket/registers/qubit.py b/src/braket/registers/qubit.py deleted file mode 100644 index 4c91ebd2..00000000 --- a/src/braket/registers/qubit.py +++ /dev/null @@ -1,66 +0,0 @@ -# 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 numbers -from typing import Union - -QubitInput = Union["Qubit", int] - - -class Qubit(int): - """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) -> 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) - """ - if not isinstance(index, numbers.Integral): - raise TypeError(f"Supplied qubit index, {index}, must be an integer.") - if index < 0: - raise ValueError(f"Supplied qubit index, {index}, cannot be less than zero.") - return super().__new__(cls, index) - - def __repr__(self): - return f"Qubit({super().__repr__()})" - - def __str__(self): - return self.__repr__() - - @staticmethod - def new(qubit: QubitInput) -> Qubit: - """Helper constructor - if input is a `Qubit` it returns the same value, - else a new `Qubit` is constructed. - - Args: - qubit (QubitInput): `Qubit` index. If `type == Qubit` then the `qubit` is returned. - - Returns: - Qubit: The qubit. - """ - return qubit if isinstance(qubit, Qubit) else Qubit(qubit) diff --git a/src/braket/registers/qubit_set.py b/src/braket/registers/qubit_set.py deleted file mode 100644 index e7bed6aa..00000000 --- a/src/braket/registers/qubit_set.py +++ /dev/null @@ -1,92 +0,0 @@ -# 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, Union - -from boltons.setutils import IndexedSet - -from braket.registers.qubit import Qubit, QubitInput - -QubitSetInput = Union[QubitInput, Iterable[QubitInput]] - - -def _flatten(other: Any) -> Any: - if isinstance(other, Iterable) and not isinstance(other, str): - for item in other: - yield from _flatten(item) - else: - yield other - - -class QubitSet(IndexedSet): - """An ordered, unique set of quantum bits. - - Note: - QubitSet implements `__hash__()` but is a mutable object, therefore be careful when - mutating this object. - """ - - def __init__(self, qubits: QubitSetInput | None = None): - """Initializes a `QubitSet`. - - Args: - qubits (QubitSetInput | None): Qubits to be included in the `QubitSet`. - Default is `None`. - - Examples: - >>> 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 - 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`. - If this instance contains a qubit that is not in the `mapping` that qubit is not modified. - - Args: - mapping (dict[QubitInput, QubitInput]): A dictionary of qubit mappings to - apply. Key is the qubit in this instance to target, and the value is what - the key will be changed to. - - Returns: - QubitSet: A new QubitSet with the `mapping` applied. - - Examples: - >>> qubits = QubitSet([0, 1]) - >>> 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) - - def __hash__(self): - return hash(tuple(self)) diff --git a/src/braket/tasks/__init__.py b/src/braket/tasks/__init__.py deleted file mode 100644 index d40b0547..00000000 --- a/src/braket/tasks/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -# 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 import ipython_utils -from braket.tasks.analog_hamiltonian_simulation_quantum_task_result import ( # noqa: F401 - AnalogHamiltonianSimulationQuantumTaskResult, - AnalogHamiltonianSimulationShotStatus, -) -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.quantum_task import QuantumTask # noqa: F401 -from braket.tasks.quantum_task_batch import QuantumTaskBatch # noqa: F401 - -# Apply nest_asyncio if currently running within Jupyter. This ensures anything that uses -# asyncio will run in Jupyter without any issues. -# -# Inspired by https://github.com/ipython/ipython/issues/11694 and -# https://github.com/jupyter/notebook/issues/3397#issuecomment-419386811 -if ipython_utils.running_in_jupyter(): - import nest_asyncio - - nest_asyncio.apply() diff --git a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py deleted file mode 100644 index 7bfb57eb..00000000 --- a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py +++ /dev/null @@ -1,162 +0,0 @@ -# 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 -from dataclasses import dataclass -from enum import Enum - -import numpy as np - -from braket.task_result import ( - AdditionalMetadata, - AnalogHamiltonianSimulationTaskResult, - TaskMetadata, -) - - -class AnalogHamiltonianSimulationShotStatus(str, Enum): - SUCCESS = "Success" - PARTIAL_SUCCESS = "Partial Success" - FAILURE = "Failure" - - -@dataclass -class ShotResult: - status: AnalogHamiltonianSimulationShotStatus - pre_sequence: np.ndarray = None - post_sequence: np.ndarray = None - - def __eq__(self, other: ShotResult) -> bool: - if isinstance(other, ShotResult): - return ( - self.status == other.status - and _equal_sequences(self.pre_sequence, other.pre_sequence) - and _equal_sequences(self.post_sequence, other.post_sequence) - ) - return NotImplemented - - -@dataclass -class AnalogHamiltonianSimulationQuantumTaskResult: - task_metadata: TaskMetadata - additional_metadata: AdditionalMetadata - measurements: list[ShotResult] = None - - def __eq__(self, other: AnalogHamiltonianSimulationQuantumTaskResult) -> bool: - if isinstance(other, AnalogHamiltonianSimulationQuantumTaskResult): - return ( - self.task_metadata.id == other.task_metadata.id - and self.measurements == other.measurements - ) - return NotImplemented - - @staticmethod - def from_object( - result: AnalogHamiltonianSimulationTaskResult, - ) -> AnalogHamiltonianSimulationQuantumTaskResult: - return AnalogHamiltonianSimulationQuantumTaskResult._from_object_internal(result) - - @staticmethod - def from_string(result: str) -> AnalogHamiltonianSimulationQuantumTaskResult: - return AnalogHamiltonianSimulationQuantumTaskResult._from_object_internal( - AnalogHamiltonianSimulationTaskResult.parse_raw(result) - ) - - @classmethod - def _from_object_internal( - cls, result: AnalogHamiltonianSimulationTaskResult - ) -> AnalogHamiltonianSimulationQuantumTaskResult: - if result.measurements is not None: - measurements = AnalogHamiltonianSimulationQuantumTaskResult._get_measurements(result) - else: - measurements = None - return cls( - task_metadata=result.taskMetadata, - additional_metadata=result.additionalMetadata, - measurements=measurements, - ) - - @classmethod - def _get_measurements(cls, result: AnalogHamiltonianSimulationTaskResult) -> list[ShotResult]: - measurements = [] - for measurement in result.measurements: - status = AnalogHamiltonianSimulationShotStatus(measurement.shotMetadata.shotStatus) - if measurement.shotResult.preSequence: - pre_sequence = np.asarray(measurement.shotResult.preSequence, dtype=int) - else: - pre_sequence = None - if measurement.shotResult.postSequence: - post_sequence = np.asarray(measurement.shotResult.postSequence, dtype=int) - else: - post_sequence = None - measurements.append(ShotResult(status, pre_sequence, post_sequence)) - return measurements - - def get_counts(self) -> dict[str, int]: - """Aggregate state counts from AHS shot results. - - Notes: - We use the following convention to denote the state of an atom (site). - e: empty site - r: Rydberg state atom - g: ground state atom - - Returns: - dict[str, int]: number of times each state configuration is measured. - Returns None if none of shot measurements are successful. - Only successful shots contribute to the state count. - """ - state_counts = Counter() - states = ["e", "r", "g"] - for shot in self.measurements: - if shot.status == AnalogHamiltonianSimulationShotStatus.SUCCESS: - pre = shot.pre_sequence - 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) - ] - state = "".join(states[s_idx] for s_idx in state_idx) - state_counts.update([state]) - - return dict(state_counts) - - def get_avg_density(self) -> np.ndarray: - """Get the average Rydberg state densities from the result - - Returns: - np.ndarray: The average densities from the result - """ - counts = self.get_counts() - - N_ryd, N_ground = [], [] - for shot, count in counts.items(): - N_ryd.append([count if s == "r" else 0 for s in shot]) - N_ground.append([count if s == "g" else 0 for s in shot]) - - 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 - - -def _equal_sequences(sequence0: np.ndarray, sequence1: np.ndarray) -> bool: - if sequence0 is None and sequence1 is None: - return True - if sequence0 is None or sequence1 is None: - return False - return np.allclose(sequence0, sequence1) diff --git a/src/braket/tasks/annealing_quantum_task_result.py b/src/braket/tasks/annealing_quantum_task_result.py deleted file mode 100644 index bf5e9900..00000000 --- a/src/braket/tasks/annealing_quantum_task_result.py +++ /dev/null @@ -1,173 +0,0 @@ -# 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 Generator -from dataclasses import dataclass -from typing import Optional - -import numpy as np - -from braket.annealing import ProblemType -from braket.task_result import AdditionalMetadata, AnnealingTaskResult, TaskMetadata - - -@dataclass -class AnnealingQuantumTaskResult: - """Result of an annealing problem quantum task execution. This class is intended - to be initialized by a QuantumTask class. - - Args: - 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. - variable_count (int): the number of variables - problem_type (ProblemType): the type of annealing problem - task_metadata (TaskMetadata): Quantum task metadata. - additional_metadata (AdditionalMetadata): Additional metadata about the quantum task - """ - - record_array: np.recarray - variable_count: int - problem_type: ProblemType - task_metadata: TaskMetadata - additional_metadata: AdditionalMetadata - - def data( - self, - selected_fields: Optional[list[str]] = None, - sorted_by: str = "value", - reverse: bool = False, - ) -> Generator[tuple]: - """Yields the data in record_array - - Args: - 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: - Generator[tuple]: data in record_array - """ - if selected_fields is None: - selected_fields = ["solution", "value", "solution_count"] - - if sorted_by is None: - order = np.arange(len(self.record_array)) - else: - order = np.argsort(self.record_array[sorted_by]) - - if reverse: - order = np.flip(order) - - for i in order: - yield tuple(self.record_array[field][i] for field in selected_fields) - - 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 - # elements in the array are equal. - self_fields = ( - self.variable_count, - self.problem_type, - self.task_metadata, - self.additional_metadata, - ) - other_fields = ( - other.variable_count, - other.problem_type, - other.task_metadata, - other.additional_metadata, - ) - return (self.record_array == other.record_array).all() and self_fields == other_fields - return NotImplemented - - @staticmethod - def from_object(result: AnnealingTaskResult) -> AnnealingQuantumTaskResult: - """Create AnnealingQuantumTaskResult from AnnealingTaskResult object - - Args: - result (AnnealingTaskResult): AnnealingTaskResult object - - Returns: - AnnealingQuantumTaskResult: An AnnealingQuantumTaskResult based on the - given result object - """ - return AnnealingQuantumTaskResult._from_object(result) - - @staticmethod - def from_string(result: str) -> AnnealingQuantumTaskResult: - """Create AnnealingQuantumTaskResult from string - - Args: - result (str): JSON object string - - Returns: - AnnealingQuantumTaskResult: An AnnealingQuantumTaskResult based on the given string - """ - return AnnealingQuantumTaskResult._from_object(AnnealingTaskResult.parse_raw(result)) - - @classmethod - def _from_object(cls, result: AnnealingTaskResult) -> AnnealingQuantumTaskResult: - solutions = np.asarray(result.solutions, dtype=int) - values = np.asarray(result.values, dtype=float) - if not result.solutionCounts: - solution_counts = np.ones(len(solutions), dtype=int) - else: - solution_counts = np.asarray(result.solutionCounts, dtype=int) - record_array = AnnealingQuantumTaskResult._create_record_array( - solutions, solution_counts, values - ) - variable_count = result.variableCount - problem_type = ProblemType[result.additionalMetadata.action.type.value] - task_metadata = result.taskMetadata - additional_metadata = result.additionalMetadata - return cls( - record_array=record_array, - variable_count=variable_count, - problem_type=problem_type, - task_metadata=task_metadata, - additional_metadata=additional_metadata, - ) - - @staticmethod - def _create_record_array( - solutions: np.ndarray, solution_counts: np.ndarray, values: np.ndarray - ) -> np.recarray: - """Create a solutions record for AnnealingQuantumTaskResult - - Args: - 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 = [ - ("solution", solutions.dtype, (variable_count,)), - ("value", values.dtype), - ("solution_count", solution_counts.dtype), - ] - - record = np.rec.array(np.zeros(num_solutions, dtype=datatypes)) - record["solution"] = solutions - record["value"] = values - record["solution_count"] = solution_counts - return record diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py deleted file mode 100644 index 81f90ae7..00000000 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ /dev/null @@ -1,512 +0,0 @@ -# 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 json -from collections import Counter -from collections.abc import Callable -from dataclasses import dataclass -from typing import Any, Optional, TypeVar, Union - -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, - GateModelTaskResult, - ResultTypeValue, - TaskMetadata, -) - -T = TypeVar("T") - - -@dataclass -class GateModelQuantumTaskResult: - """Result of a gate model quantum task execution. This class is intended - to be initialized by a QuantumTask class. - - Args: - task_metadata (TaskMetadata): Quantum task metadata. - additional_metadata (AdditionalMetadata): Additional metadata about the quantum task - result_types (list[dict[str, Any]]): List of dictionaries where each dictionary - has two keys: 'Type' (the result type in IR JSON form) and - 'Value' (the result value for this result type). - This can be an empty list if no result types are specified in the IR. - This is calculated from `measurements` and - the IR of the circuit program when `shots>0`. - values (list[Any]): The values for result types requested in the circuit. - This can be an empty list if no result types are specified in the IR. - This is calculated from `measurements` and - the IR of the circuit program when `shots>0`. - measurements (numpy.ndarray, optional): 2d array - row is shot and column is qubit. - Default is None. Only available when shots > 0. The qubits in `measurements` - are the ones in `GateModelQuantumTaskResult.measured_qubits`. - measured_qubits (list[int], optional): The indices of the measured qubits. Default - is None. Only available when shots > 0. Indicates which qubits are in - `measurements`. - measurement_counts (Counter, optional): A `Counter` of measurements. Key is the measurements - in a big endian binary string. Value is the number of times that measurement occurred. - Default is None. Only available when shots > 0. Note that the keys in `Counter` are - unordered. - measurement_probabilities (dict[str, float], optional): - A dictionary of probabilistic results. - Key is the measurements in a big endian binary string. - Value is the probability the measurement occurred. - Default is None. Only available when shots > 0. - measurements_copied_from_device (bool, optional): flag whether `measurements` - were copied from device. If false, `measurements` are calculated from device data. - Default is None. Only available when shots > 0. - measurement_counts_copied_from_device (bool, optional): flag whether `measurement_counts` - were copied from device. If False, `measurement_counts` are calculated from device data. - Default is None. Only available when shots > 0. - measurement_probabilities_copied_from_device (bool, optional): flag whether - `measurement_probabilities` were copied from device. If false, - `measurement_probabilities` are calculated from device data. Default is None. - Only available when shots > 0. - """ - - task_metadata: TaskMetadata - additional_metadata: AdditionalMetadata - result_types: list[ResultTypeValue] = None - values: list[Any] = None - measurements: np.ndarray = None - measured_qubits: list[int] = None - measurement_counts: Counter = None - measurement_probabilities: dict[str, float] = None - measurements_copied_from_device: bool = None - measurement_counts_copied_from_device: bool = None - measurement_probabilities_copied_from_device: bool = None - - _result_types_indices: dict[str, int] = None - - def __post_init__(self): - if self.result_types is not None: - 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 - requested in the circuit sent to the device for this quantum task result. - - Args: - result_type (ResultType): result type requested - - Returns: - Any: value of the result corresponding to the result type - - Raises: - ValueError: If result type is not found in result. - Result types must be added to the circuit before the circuit is run on a device. - """ - rt_ir = result_type.to_ir() - try: - 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 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): - 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. - - Returns: - Optional[str]: The compiled circuit or None. - """ - metadata = self.additional_metadata - if not metadata: - return None - if metadata.rigettiMetadata: - return metadata.rigettiMetadata.compiledProgram - elif metadata.oqcMetadata: - return metadata.oqcMetadata.compiledProgram - else: - return None - - @staticmethod - 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) - - @staticmethod - 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()) - - measurement_probabilities = { - key: count / shots for key, count in measurement_counts.items() - } - return measurement_probabilities - - @staticmethod - 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: A dictionary of probabilistic results. - Key is the measurements in a big endian binary string. - Value is the probability the measurement occurred. - """ - measurements_list = [] - for bitstring in measurement_probabilities: - measurement = list(bitstring) - individual_measurement_list = [measurement] * int( - round(measurement_probabilities[bitstring] * shots) - ) - measurements_list.extend(individual_measurement_list) - return np.asarray(measurements_list, dtype=int) - - @staticmethod - def from_object(result: GateModelTaskResult) -> GateModelQuantumTaskResult: - """Create GateModelQuantumTaskResult from GateModelTaskResult object. - - Args: - result (GateModelTaskResult): GateModelTaskResult object - - Returns: - GateModelQuantumTaskResult: A GateModelQuantumTaskResult based on the given dict - - Raises: - ValueError: If neither "Measurements" nor "MeasurementProbabilities" is a key - in the result dict - """ - return GateModelQuantumTaskResult._from_object_internal(result) - - @staticmethod - def from_string(result: str) -> GateModelQuantumTaskResult: - """Create GateModelQuantumTaskResult from string. - - Args: - result (str): JSON object string, with GateModelQuantumTaskResult attributes as keys. - - Returns: - GateModelQuantumTaskResult: A GateModelQuantumTaskResult based on the given string - - Raises: - ValueError: If neither "Measurements" nor "MeasurementProbabilities" is a key - in the result dict - """ - obj = GateModelTaskResult.parse_raw(result) - GateModelQuantumTaskResult.cast_result_types(obj) - return GateModelQuantumTaskResult._from_object_internal(obj) - - @classmethod - def _from_object_internal(cls, result: GateModelTaskResult) -> GateModelQuantumTaskResult: - if result.taskMetadata.shots > 0: - return GateModelQuantumTaskResult._from_object_internal_computational_basis_sampling( - result - ) - else: - return GateModelQuantumTaskResult._from_dict_internal_simulator_only(result) - - @classmethod - def _from_object_internal_computational_basis_sampling( - cls, result: GateModelTaskResult - ) -> GateModelQuantumTaskResult: - task_metadata = result.taskMetadata - additional_metadata = result.additionalMetadata - if result.measurements: - measurements = np.asarray(result.measurements, dtype=int) - m_counts = GateModelQuantumTaskResult.measurement_counts_from_measurements(measurements) - m_probs = GateModelQuantumTaskResult.measurement_probabilities_from_measurement_counts( - m_counts - ) - measurements_copied_from_device = True - m_counts_copied_from_device = False - m_probabilities_copied_from_device = False - elif result.measurementProbabilities: - shots = task_metadata.shots - m_probs = result.measurementProbabilities - measurements = GateModelQuantumTaskResult.measurements_from_measurement_probabilities( - m_probs, shots - ) - m_counts = GateModelQuantumTaskResult.measurement_counts_from_measurements(measurements) - measurements_copied_from_device = False - m_counts_copied_from_device = False - m_probabilities_copied_from_device = True - else: - raise ValueError( - 'One of "measurements" or "measurementProbabilities" must be populated in', - " the result obj", - ) - measured_qubits = result.measuredQubits - 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 - # specifies result types inline and parsing result types is more involved - # (ie. may involve dereferencing logical qubits at runtime), so the parsed - # result type specifications need to be returned, even if not calculated - # during simulation. - if not isinstance(result.resultTypes[0], ResultTypeValue): - result_types = GateModelQuantumTaskResult._calculate_result_types( - json.dumps({"results": [rt.dict() for rt in result.resultTypes]}), - measurements, - measured_qubits, - ) - else: - result_types = result.resultTypes - else: - result_types = GateModelQuantumTaskResult._calculate_result_types( - additional_metadata.action.json(), measurements, measured_qubits - ) - values = [rt.value for rt in result_types] - return cls( - task_metadata=task_metadata, - additional_metadata=additional_metadata, - result_types=result_types, - values=values, - measurements=measurements, - measured_qubits=measured_qubits, - measurement_counts=m_counts, - measurement_probabilities=m_probs, - measurements_copied_from_device=measurements_copied_from_device, - measurement_counts_copied_from_device=m_counts_copied_from_device, - measurement_probabilities_copied_from_device=m_probabilities_copied_from_device, - ) - - @classmethod - def _from_dict_internal_simulator_only( - cls, result: GateModelTaskResult - ) -> GateModelQuantumTaskResult: - task_metadata = result.taskMetadata - additional_metadata = result.additionalMetadata - result_types = result.resultTypes - values = [rt.value for rt in result_types] - return cls( - task_metadata=task_metadata, - additional_metadata=additional_metadata, - result_types=result_types, - values=values, - ) - - @staticmethod - def cast_result_types(gate_model_task_result: GateModelTaskResult) -> None: - """Casts the result types to the types expected by the SDK. - - Args: - gate_model_task_result (GateModelTaskResult): GateModelTaskResult representing the - results. - """ - 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([complex(*value) for value in result_type.value]) - - @staticmethod - def _calculate_result_types( - ir_string: str, measurements: np.ndarray, measured_qubits: list[int] - ) -> list[ResultTypeValue]: - ir = json.loads(ir_string) - result_types = [] - if not ir.get("results"): - return result_types - for result_type in ir["results"]: - 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( - GateModelQuantumTaskResult._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( - GateModelQuantumTaskResult._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}") - 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: Optional[list[int]] - ) -> 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], - measurements: np.ndarray, - measured_qubits: list[int], - observable: Observable, - targets: list[int], - ) -> Union[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 - ] - - @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: Optional[list[int]] - ) -> np.ndarray: - measurements = GateModelQuantumTaskResult._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) - - # count the basis state occurrences, and construct the probability vector - basis_states, counts = np.unique(indices, return_counts=True) - probabilities = np.zeros([2**num_measured_qubits], dtype=np.float64) - probabilities[basis_states] = counts / shots - return probabilities - - @staticmethod - def _variance_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.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): - rt_type.observable = GateModelQuantumTaskResult._replace_neg_zero(rt_type.observable) - return repr(dict(sorted(dict(rt_type).items(), key=lambda x: x[0]))) - - @staticmethod - def _replace_neg_zero(observable_matrix: Union[list, int]) -> Union[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 diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py deleted file mode 100644 index 8417c71b..00000000 --- a/src/braket/tasks/local_quantum_task.py +++ /dev/null @@ -1,81 +0,0 @@ -# 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 asyncio -from typing import Union - -from braket.tasks import ( - AnnealingQuantumTaskResult, - GateModelQuantumTaskResult, - PhotonicModelQuantumTaskResult, - QuantumTask, -) - - -class LocalQuantumTask(QuantumTask): - """A quantum task containing the results of a local simulation. - - Since this class is instantiated with the results, cancel() and run_async() are unsupported. - """ - - def __init__( - self, - result: Union[ - GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult - ], - ): - self._id = result.task_metadata.id - self._result = result - - @property - def id(self) -> str: - """Gets the task ID. - - Returns: - str: The ID of the task. - """ - return str(self._id) - - def cancel(self) -> None: - """Cancel the quantum task.""" - 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( - self, - ) -> Union[ - GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult - ]: - return self._result - - def async_result(self) -> asyncio.Task: - """Get the quantum task result asynchronously. - - Raises: - NotImplementedError: Asynchronous local simulation unsupported - - Returns: - asyncio.Task: Get the quantum task result asynchronously. - """ - # TODO: Allow for asynchronous simulation - raise NotImplementedError("Asynchronous local simulation unsupported") - - def __repr__(self) -> str: - return f"LocalQuantumTask('id':{self.id})" diff --git a/src/braket/tasks/local_quantum_task_batch.py b/src/braket/tasks/local_quantum_task_batch.py deleted file mode 100644 index a47a47e6..00000000 --- a/src/braket/tasks/local_quantum_task_batch.py +++ /dev/null @@ -1,49 +0,0 @@ -# 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 typing import Union - -from braket.tasks import ( - AnnealingQuantumTaskResult, - GateModelQuantumTaskResult, - PhotonicModelQuantumTaskResult, - QuantumTaskBatch, -) - - -class LocalQuantumTaskBatch(QuantumTaskBatch): - """Executes a batch of quantum tasks in parallel. - - Since this class is instantiated with the results, cancel() and run_async() are unsupported. - """ - - def __init__( - self, - results: list[ - Union[ - GateModelQuantumTaskResult, - AnnealingQuantumTaskResult, - PhotonicModelQuantumTaskResult, - ] - ], - ): - self._results = results - - def results( - self, - ) -> list[ - Union[ - GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult - ] - ]: - return self._results diff --git a/src/braket/tasks/photonic_model_quantum_task_result.py b/src/braket/tasks/photonic_model_quantum_task_result.py deleted file mode 100644 index 59eba68a..00000000 --- a/src/braket/tasks/photonic_model_quantum_task_result.py +++ /dev/null @@ -1,69 +0,0 @@ -# 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 dataclasses import dataclass - -import numpy as np - -from braket.task_result import AdditionalMetadata, PhotonicModelTaskResult, TaskMetadata - - -@dataclass -class PhotonicModelQuantumTaskResult: - task_metadata: TaskMetadata - additional_metadata: AdditionalMetadata - measurements: np.ndarray = None - - 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. - - Args: - result (PhotonicModelTaskResult): PhotonicModelTaskResult object - - Returns: - PhotonicModelQuantumTaskResult: A PhotonicModelQuantumTaskResult based on the given dict - - Raises: - ValueError: If "measurements" is not a key in the result dict - """ - return PhotonicModelQuantumTaskResult._from_object_internal(result) - - @staticmethod - def from_string(result: str) -> PhotonicModelQuantumTaskResult: - return PhotonicModelQuantumTaskResult._from_object_internal( - PhotonicModelTaskResult.parse_raw(result) - ) - - @classmethod - def _from_object_internal( - cls, result: PhotonicModelTaskResult - ) -> PhotonicModelQuantumTaskResult: - task_metadata = result.taskMetadata - additional_metadata = result.additionalMetadata - if result.measurements is not None: - measurements = np.asarray(result.measurements, dtype=int) - else: - measurements = None - return cls( - task_metadata=task_metadata, - additional_metadata=additional_metadata, - measurements=measurements, - ) diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py deleted file mode 100644 index f07a1be7..00000000 --- a/src/braket/tasks/quantum_task.py +++ /dev/null @@ -1,79 +0,0 @@ -# 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 asyncio -from abc import ABC, abstractmethod -from typing import Any, Union - -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 QuantumTask(ABC): - """An abstraction over a quantum task on a quantum device.""" - - @property - @abstractmethod - def id(self) -> str: - """Get the quantum task ID. - - Returns: - str: The quantum task ID. - """ - - @abstractmethod - def cancel(self) -> None: - """Cancel the quantum task.""" - - @abstractmethod - def state(self) -> str: - """Get the state of the quantum task. - - Returns: - str: State of the quantum task. - """ - - @abstractmethod - def result( - self, - ) -> Union[ - GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult - ]: - """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 - - @abstractmethod - def async_result(self) -> asyncio.Task: - """Get the quantum task result asynchronously. - - Returns: - asyncio.Task: Get the quantum task result asynchronously. - """ - - 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 - request. Default is False. - - Returns: - dict[str, Any]: The metadata regarding the quantum task. If `use_cached_value` is True, - then the value retrieved from the most recent request is used. - """ diff --git a/src/braket/tasks/quantum_task_batch.py b/src/braket/tasks/quantum_task_batch.py deleted file mode 100644 index 790f6e19..00000000 --- a/src/braket/tasks/quantum_task_batch.py +++ /dev/null @@ -1,38 +0,0 @@ -# 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 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 -from braket.tasks.photonic_model_quantum_task_result import PhotonicModelQuantumTaskResult - - -class QuantumTaskBatch(ABC): - """An abstraction over a quantum task batch on a quantum device.""" - - @abstractmethod - def results( - self, - ) -> list[ - Union[ - GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult - ] - ]: - """Get the quantum task results. - - Returns: - list[Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]]: Get - the quantum task results. - """ # noqa: E501 diff --git a/src/braket/timings/__init__.py b/src/braket/timings/__init__.py deleted file mode 100644 index 15b6c5dc..00000000 --- a/src/braket/timings/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# 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.timings.time_series import TimeSeries, TimeSeriesItem # noqa: F401 diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py deleted file mode 100644 index 67797d6c..00000000 --- a/src/braket/timings/time_series.py +++ /dev/null @@ -1,422 +0,0 @@ -# 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 OrderedDict -from collections.abc import Iterator -from dataclasses import dataclass -from decimal import Decimal -from enum import Enum -from numbers import Number -from typing import Optional - - -@dataclass -class TimeSeriesItem: - time: Number - value: Number - - -class StitchBoundaryCondition(str, Enum): - MEAN = "mean" - LEFT = "left" - RIGHT = "right" - - -class TimeSeries: - def __init__(self): - self._series = OrderedDict() - self._sorted = True - self._largest_time = -1 - - def put( - self, - time: Number, - value: Number, - ) -> TimeSeries: - """Puts a value to the time series at the given time. A value passed to an existing - time will overwrite the current value. - - Args: - time (Number): The time of the value. - value (Number): The value to add to the time series. - - Returns: - TimeSeries: returns self (to allow for chaining). - """ - if time in self._series: - self._series[time] = TimeSeriesItem(time, value) - elif time > self._largest_time: - self._series[time] = TimeSeriesItem(time, value) - self._largest_time = time - else: - self._series[time] = TimeSeriesItem(time, value) - self._sorted = False - return self - - def times(self) -> list[Number]: - """Returns the times in the time series. - - Returns: - list[Number]: The times in the time series. - """ - self._ensure_sorted() - return [item.time for item in self._series.values()] - - def values(self) -> list[Number]: - """Returns the values in the time series. - - Returns: - list[Number]: The values in the time series. - """ - self._ensure_sorted() - return [item.value for item in self._series.values()] - - def __iter__(self) -> Iterator: - self._ensure_sorted() - return self._series.values().__iter__() - - def __len__(self): - return self._series.values().__len__() - - def _ensure_sorted(self) -> None: - if not self._sorted: - self._series = OrderedDict(sorted(self._series.items())) - self._sorted = True - - @staticmethod - def from_lists(times: list[float], values: list[float]) -> TimeSeries: - """Create a time series from the list of time and value points - - Args: - times (list[float]): list of time points - values (list[float]): list of value points - - 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( - f"The lengths of the times({len(times)})\ - and values({len(values)}) lists are not equal." - ) - - ts = TimeSeries() - for t, v in zip(times, values): - ts.put(t, v) - return ts - - @staticmethod - 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. - - Args: - times (list | float | TimeSeries): list of time points or a time series - constant (float): constant value - - Returns: - TimeSeries: A constant time series - """ - if not isinstance(times, list): - times = times.times() - - ts = TimeSeries() - for t in times: - ts.put(t, constant) - return ts - - def concatenate(self, other: TimeSeries) -> TimeSeries: - """Concatenates two time series ino to a single time series. - The time points in the final time series are obtained by concatenating - two lists of time points from the first and the second time series. - Similarly, the values in the final time series is a concatenated list - of the values in the first and the second time series. - - Args: - other (TimeSeries): The second time series to be concatenated - Notes: - Keeps the time points in both time series unchanged. - Assumes that the time points in the first TimeSeries - are at earlier times then the time points in the second 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]) - time_series_2 = TimeSeries.from_lists(times=[0.2, 0.3], values=[4, 5]) - - concat_ts = time_series_1.concatenate(time_series_2) - - Result: - 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( - "The time points in the first TimeSeries must be strictly smaller \ - then the time points in the second 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): - new_time_series.put(t, v) - - return new_time_series - - def stitch( - self, other: TimeSeries, boundary: StitchBoundaryCondition = StitchBoundaryCondition.MEAN - ) -> TimeSeries: - """Stitch two time series to a single time series. The time points of the - second time series are shifted such that the first time point of the second series - coincides with the last time point of the first series. - The boundary point value is handled according to StitchBoundaryCondition argument value. - - Args: - other (TimeSeries): The second time series 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. - - 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]) - 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 len(self.times()) == 0: - return TimeSeries.from_lists(times=other.times(), values=other.values()) - if len(other.times()) == 0: - return TimeSeries.from_lists(times=self.times(), values=self.values()) - - new_time_series = TimeSeries() - left_t, right_t = self.times()[-1], other.times()[0] - other_times = [t - right_t + left_t for t in other.times()] - new_times = self.times() + other_times[1:] - - left, right = self.values()[-1], other.values()[0] - if boundary == StitchBoundaryCondition.MEAN: - bndry_val = 0.5 * sum([left, right]) - elif boundary == StitchBoundaryCondition.LEFT: - bndry_val = left - elif boundary == StitchBoundaryCondition.RIGHT: - bndry_val = right - else: - raise ValueError( - f"Boundary handler value {boundary} is not allowed. \ - Possible options are: 'mean', 'left', 'right'." - ) - - 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) - - return new_time_series - - 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 (Optional[Decimal]): Time resolution - value_resolution (Optional[Decimal]): Value resolution - - Returns: - TimeSeries: A new discretized time series. - """ - discretized_ts = TimeSeries() - for item in self: - 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 - def periodic_signal(times: list[float], values: list[float], num_repeat: int = 1) -> TimeSeries: - """Create a periodic time series by repeating the same block multiple times. - - 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 repetitions - - Raises: - ValueError: If the first and last values are not the same - - Returns: - TimeSeries: A new periodic time series. - """ - if values[0] != values[-1]: - 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): - new_time_series = new_time_series.stitch(repeating_block) - - return new_time_series - - @staticmethod - def trapezoidal_signal( - area: float, value_max: float, slew_rate_max: float, time_separation_min: float = 0.0 - ) -> TimeSeries: - """Get a trapezoidal time series with specified area, maximum value, maximum slew rate - and minimum separation of time points - - Args: - area (float): Total area under the time series - value_max (float): The maximum value of the time series - 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 - - Notes: - The area of a time series f(t) is defined as the time integral of - 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: - raise ValueError("The maximum value of the trapezoidal time series has to be positive.") - if slew_rate_max <= 0.0: - raise ValueError( - "The maximum slew rate of the trapezoidal time series has to be positive." - ) - if time_separation_min < 0.0: - raise ValueError( - "The minimum separation of time points of the trapezoidal time series " - "has to be non-negative." - ) - - # Determine the ramp time to reach the max allowed value - t_ramp = max(time_separation_min, value_max / slew_rate_max) - - # The max achievable area if there are 3 time points: [0, t_ramp, 2 * t_ramp] - area_threshold_1 = t_ramp * value_max - - # The max achievable area if there are 4 time points: - # [0, t_ramp, t_ramp + time_separation_min, 2 * t_ramp + time_separation_min] - area_threshold_2 = (t_ramp + time_separation_min) * value_max - - if area <= area_threshold_1: - # Determine the max value if area <= area_threshold_1 - value = area / t_ramp - times = [0, t_ramp, 2 * t_ramp] - values = [0, value, 0] - elif area <= area_threshold_2: - # Determine the max value if area_threshold_1 < area <= area_threshold_2 - value = area / (t_ramp + time_separation_min) - times = [0, t_ramp, t_ramp + time_separation_min, 2 * t_ramp + time_separation_min] - values = [0, value, value, 0] - else: - # Determine the t_plateau if area > area_threshold_2 - t_plateau = (area - area_threshold_2) / value_max + time_separation_min - times = [0, t_ramp, t_ramp + t_plateau, 2 * t_ramp + t_plateau] - values = [0, value_max, value_max, 0] - - return TimeSeries.from_lists(times, values) - - -# 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) - 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. - - Args: - first (TimeSeries): A time series. - second (TimeSeries): A time series. - tolerance (Number): The tolerance value. - - Returns: - bool: True if the times and values in two time series are all within (less than) - a given tolerance range. If the time series are not the same size, this function - will return False. - """ - if len(first) != len(second): - return False - if len(first) == 0: - return True - first_times = first.times() - second_times = second.times() - first_values = first.values() - second_values = second.values() - for index in range(len(first)): - if abs(first_times[index] - second_times[index]) >= tolerance: - return False - if abs(first_values[index] - second_values[index]) >= tolerance: - return False - return True diff --git a/src/braket/tracking/__init__.py b/src/braket/tracking/__init__.py deleted file mode 100644 index 652111f2..00000000 --- a/src/braket/tracking/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# 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.tracking.tracker import Tracker # noqa: F401 diff --git a/src/braket/tracking/pricing.py b/src/braket/tracking/pricing.py deleted file mode 100644 index c269208c..00000000 --- a/src/braket/tracking/pricing.py +++ /dev/null @@ -1,74 +0,0 @@ -# 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 csv -import io -import os -from functools import lru_cache - -import urllib3 - - -class Pricing: - def __init__(self): - self._price_list = [] - - def get_prices(self) -> None: - """Retrieves the price list.""" - # Using AWS Pricing Bulk API. Format for the response is described at - # https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/reading-an-offer.html - - 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 - ) - response = http.request( - "GET", - price_url, - preload_content=False, - ) - response.auto_close = False - - text_response = io.TextIOWrapper(response) - - # Data starts on line 6 - # - # > The first five rows of the CSV are the metadata for the offer file. The sixth row has - # > all the column names for the products and their attributes... - # https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/reading-an-offer.html#csv - for _ in range(5): - text_response.readline() - self._price_list = list(csv.DictReader(text_response)) - - @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. - """ - if not self._price_list: - self.get_prices() - return [ - entry for entry in self._price_list if all(entry[k] == v for k, v in kwargs.items()) - ] - - -_pricing = Pricing() -price_search = _pricing.price_search diff --git a/src/braket/tracking/tracker.py b/src/braket/tracking/tracker.py deleted file mode 100644 index 47c13625..00000000 --- a/src/braket/tracking/tracker.py +++ /dev/null @@ -1,300 +0,0 @@ -# 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 datetime import timedelta -from decimal import Decimal -from functools import singledispatchmethod -from typing import Any - -from braket.tracking.pricing import price_search -from braket.tracking.tracking_context import deregister_tracker, register_tracker -from braket.tracking.tracking_events import ( - _TaskCompletionEvent, - _TaskCreationEvent, - _TaskStatusEvent, -) - -MIN_SIMULATOR_DURATION = timedelta(milliseconds=3000) - - -class Tracker: - """Amazon Braket cost tracker. - Use this class to track costs incurred from quantum tasks on Amazon Braket. - """ - - def __init__(self): - self._resources = {} # Key = quantum_task_arn - - def __enter__(self): - register_tracker(self) - return self - - def __exit__(self, *args): - deregister_tracker(self) - - def start(self) -> Tracker: - """Start tracking resources with this tracker. - - Returns: - Tracker: self. - """ - return self.__enter__() - - def stop(self) -> Tracker: - """Stop tracking resources with this tracker. - - Returns: - Tracker: self. - """ - return self.__exit__() - - 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. - - Returns: - list[str]: The list of quantum task ids for quantum tasks tracked by this tracker. - """ - 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. - - 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 - charges. Estimated charges do not factor in any discounts or credits, and you may - experience additional charges based on your use of other services such as - Amazon Elastic Compute Cloud (Amazon EC2). - - Returns: - Decimal: The estimated total cost in USD - """ - 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) - return total_cost - - def simulator_tasks_cost(self) -> Decimal: - """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 - to ensure that the simulator usage is included in the cost estimate. - - 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 - charges. Estimated charges do not factor in any discounts or credits, and you may - experience additional charges based on your use of other services such as - Amazon Elastic Compute Cloud (Amazon EC2). - - Returns: - Decimal: The estimated total cost in USD - """ - 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) - return total_cost - - def quantum_tasks_statistics(self) -> dict[str, dict[str, Any]]: - """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 - 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. - - Example: - >>> tracker.quantum_tasks_statistics() - {'qpu_device_foo': - {'shots' : 1000, - 'tasks' : { 'COMPLETED' : 4, - 'QUEUED' : 1 }, - }, - 'simulator_device_bar': - {'shots' : 1000 - 'tasks' : { 'COMPLETED' : 2, - 'CREATED' : 1}, - 'execution_duration' : datetime.timedelta(seconds=5, microseconds=654321), - 'billed_execution_duration' : datetime.timedelta(seconds=6, microseconds=123456)}} - """ - stats = {} - for _, details in self._resources.items(): - device_stats = stats.get(details["device"], {}) - - shots = device_stats.get("shots", 0) + details["shots"] - device_stats["shots"] = shots - - task_states = device_stats.get("tasks", {}) - task_states[details["status"]] = task_states.get(details["status"], 0) + 1 - device_stats["tasks"] = task_states - - if "execution_duration" in details: - duration = ( - device_stats.get("execution_duration", timedelta(0)) - + details["execution_duration"] - ) - billed_duration = ( - timedelta(0) - if details.get("has_reservation_arn") - else ( - device_stats.get("billed_execution_duration", timedelta(0)) - + details["billed_duration"] - ) - ) - - device_stats["execution_duration"] = duration - device_stats["billed_execution_duration"] = billed_duration - - stats.setdefault(details["device"], {}).update(device_stats) - - 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: - 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) - ) - - -def _get_qpu_task_cost(task_arn: str, details: dict) -> Decimal: - if details["status"] in ["FAILED", "CANCELLED"] or details.get("has_reservation_arn"): - return Decimal(0) - task_region = task_arn.split(":")[3] - - search_dict = {"Region Code": task_region} - - device_name = details["device"].split("/")[-1] - device_name = device_name[0].upper() + device_name[1:] - if "2000Q" in device_name: - device_name = "2000Q" - elif "Advantage_system" in device_name: - device_name = "Advantage_system" - - if details["job_task"]: - search_dict["Device Name"] = device_name - shot_product_family = "Braket Managed Jobs QPU Task Shot" - task_product_family = "Braket Managed Jobs QPU Task" - else: - search_dict["DeviceName"] = device_name # The difference in spelling is intentional - shot_product_family = "Quantum Task-Shot" - task_product_family = "Quantum Task" - - search_dict["Product Family"] = shot_product_family - shot_prices = price_search(**search_dict) - if len(shot_prices) != 1: - raise ValueError(f"Found {len(shot_prices)} products matching {search_dict}") - - search_dict["Product Family"] = task_product_family - task_prices = price_search(**search_dict) - if len(task_prices) != 1: - raise ValueError(f"Found {len(task_prices)} products matching {search_dict}") - - shot_price = shot_prices[0] - task_price = task_prices[0] - - for price in [shot_price, task_price]: - if price["Currency"] != "USD": - raise ValueError(f"Expected USD, found {price['Currency']}") - - shot_cost = Decimal(shot_price["PricePerUnit"]) * details["shots"] - task_cost = Decimal(task_price["PricePerUnit"]) * 1 - - return shot_cost + task_cost - - -def _get_simulator_task_cost(task_arn: str, details: dict) -> Decimal: - if not details.get("billed_duration"): - return Decimal(0) - task_region = task_arn.split(":")[3] - - device_name = details["device"].split("/")[-1].upper() - - if details["job_task"]: - product_family = "Braket Managed Jobs Simulator Task" - operation = "Managed-Jobs" - else: - product_family = "Simulator Task" - operation = "CompleteTask" - if details["status"] == "FAILED" and device_name == "TN1": - # Rehearsal step of TN1 can fail and charges still apply. - operation = "FailedTask" - - search_dict = { - "Region Code": task_region, - "Version": device_name, - "Product Family": product_family, - "operation": operation, - } - - duration_prices = price_search(**search_dict) - - if len(duration_prices) != 1: - raise ValueError(f"Found {len(duration_prices)} products matching {search_dict}") - - duration_price = duration_prices[0] - - if duration_price["Currency"] != "USD": - raise ValueError(f"Expected USD, found {duration_price['Currency']}") - - duration_cost = ( - 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 deleted file mode 100644 index 37f4a3dc..00000000 --- a/src/braket/tracking/tracking_context.py +++ /dev/null @@ -1,59 +0,0 @@ -# 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 - - -class TrackingContext: - def __init__(self): - self._trackers = set() - - def register_tracker(self, tracker: Tracker) -> None: # noqa F821 - """Registers a tracker. - - Args: - tracker (Tracker): The tracker. - """ - self._trackers.add(tracker) - - def deregister_tracker(self, tracker: Tracker) -> None: # noqa F821 - """Deregisters a tracker. - - Args: - tracker (Tracker): The tracker. - """ - self._trackers.remove(tracker) - - 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) -> set: - """Gets the active trackers. - - Returns: - set: The set of active trackers. - """ - return self._trackers - - -_tracking_context = TrackingContext() -register_tracker = _tracking_context.register_tracker -deregister_tracker = _tracking_context.deregister_tracker -broadcast_event = _tracking_context.broadcast_event -active_trackers = _tracking_context.active_trackers diff --git a/src/braket/tracking/tracking_events.py b/src/braket/tracking/tracking_events.py deleted file mode 100644 index 793ff038..00000000 --- a/src/braket/tracking/tracking_events.py +++ /dev/null @@ -1,41 +0,0 @@ -# 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 dataclasses import dataclass -from typing import Optional - - -@dataclass -class _TrackingEvent: - arn: str - - -@dataclass -class _TaskCreationEvent(_TrackingEvent): - shots: int - is_job_task: bool - device: str - - -@dataclass -class _TaskCompletionEvent(_TrackingEvent): - execution_duration: Optional[float] - status: str - has_reservation_arn: bool = False - - -@dataclass -class _TaskStatusEvent(_TrackingEvent): - status: str diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py deleted file mode 100644 index b8d0d43d..00000000 --- a/test/integ_tests/conftest.py +++ /dev/null @@ -1,184 +0,0 @@ -# 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 -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 - if endpoint := os.getenv("BRAKET_ENDPOINT"): - node.workerinput["BRAKET_ENDPOINT"] = endpoint - - -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(): - profile_name = os.environ["AWS_PROFILE"] - return boto3.session.Session(profile_name=profile_name) - - -@pytest.fixture(scope="session") -def aws_session(boto_session): - return AwsSession(boto_session) - - -@pytest.fixture(scope="session") -def s3_resource(boto_session): - return boto_session.resource("s3") - - -@pytest.fixture(scope="session") -def s3_client(boto_session): - return boto_session.client("s3") - - -@pytest.fixture(scope="session") -def account_id(boto_session): - return boto_session.client("sts").get_caller_identity()["Account"] - - -@pytest.fixture(scope="session") -def s3_bucket(s3_resource, s3_client, account_id, boto_session): - """Create / get S3 bucket for tests""" - - region_name = boto_session.region_name - bucket_name = f"amazon-braket-sdk-integ-tests-{account_id}" - bucket = s3_resource.Bucket(bucket_name) - - try: - # Determine if bucket exists - s3_client.head_bucket(Bucket=bucket_name) - except ClientError as e: - error_code = e.response["Error"]["Code"] - if error_code == "404": - bucket.create( - ACL="private", CreateBucketConfiguration={"LocationConstraint": region_name} - ) - - return bucket_name - - -@pytest.fixture(scope="module") -def s3_prefix(): - """Returns the module path of the test, e.g. integ_tests/test_simulator_quantum_task""" - - # current test path, e.g. ... - # test/integ_tests/test_simulator_quantum_task.py::test_simulator_quantum_task (setup) - current_test_path = os.environ.get("PYTEST_CURRENT_TEST") - - # strip off the filename extension and test/ - return current_test_path.rsplit(".py")[0].replace("test/", "") - - -@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(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(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) diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py deleted file mode 100644 index 901cc819..00000000 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ /dev/null @@ -1,743 +0,0 @@ -# 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 concurrent.futures -import math -from typing import Any, Union - -import numpy as np - -from braket.aws import AwsDevice -from braket.circuits import Circuit, Gate, Instruction, Observable, ResultType -from braket.circuits.quantum_operator_helpers import get_pauli_eigenvalues -from braket.circuits.serialization import IRType -from braket.devices import Device -from braket.ir.openqasm import Program as OpenQasmProgram -from braket.tasks import GateModelQuantumTaskResult - - -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]): - # |110> should get back value of "110" - state_110 = Circuit().x(0).x(1).i(2) - result = device.run(state_110, **run_kwargs).result() - assert result.measurement_counts.most_common(1)[0][0] == "110" - state_110_qasm = state_110.to_ir(ir_type=IRType.OPENQASM) - result = device.run(state_110_qasm, **run_kwargs).result() - assert result.measurement_counts.most_common(1)[0][0] == "110" - - # |001> should get back value of "001" - state_001 = Circuit().i(0).i(1).x(2) - result = device.run(state_001, **run_kwargs).result() - assert result.measurement_counts.most_common(1)[0][0] == "001" - state_001_qasm = state_001.to_ir(ir_type=IRType.OPENQASM) - result = device.run(state_001_qasm, **run_kwargs).result() - assert result.measurement_counts.most_common(1)[0][0] == "001" - - -def no_result_types_testing( - program: Union[Circuit, OpenQasmProgram], - device: Device, - run_kwargs: dict[str, Any], - expected: dict[str, float], -): - shots = run_kwargs["shots"] - tol = get_tol(shots) - result = device.run(program, **run_kwargs).result() - probabilities = result.measurement_probabilities - for bitstring in probabilities: - assert np.allclose(probabilities[bitstring], expected[bitstring], **tol) - assert len(result.measurements) == shots - - -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]): - shots = run_kwargs["shots"] - tol = get_tol(shots) - bell = ( - Circuit() - .h(0) - .cnot(0, 1) - .expectation(observable=Observable.X(), target=[2]) - .variance(observable=Observable.Y(), target=[3]) - ) - bell_qasm = bell.to_ir(ir_type=IRType.OPENQASM) - 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) - - -def result_types_zero_shots_bell_pair_testing( - device: Device, - include_state_vector: bool, - run_kwargs: dict[str, Any], - include_amplitude: bool = True, -): - circuit = ( - Circuit() - .h(0) - .cnot(0, 1) - .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) - ) - if include_amplitude: - circuit.amplitude(["01", "10", "00", "11"]) - if include_state_vector: - circuit.state_vector() - 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( - ResultType.Expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) - ), - 1 / np.sqrt(2), - ) - if include_state_vector: - assert np.allclose( - result.get_value_by_result_type(ResultType.StateVector()), - np.array([1, 0, 0, 1]) / np.sqrt(2), - ) - if include_amplitude: - amplitude = result.get_value_by_result_type( - ResultType.Amplitude(["01", "10", "00", "11"]) - ) - assert np.isclose(amplitude["01"], 0) - assert np.isclose(amplitude["10"], 0) - assert np.isclose(amplitude["00"], 1 / np.sqrt(2)) - assert np.isclose(amplitude["11"], 1 / np.sqrt(2)) - - -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() - tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) - for task in tasks: - result = device.run(task, **run_kwargs).result() - assert len(result.result_types) == 1 - assert np.allclose( - result.get_value_by_result_type(ResultType.Probability()), - np.array([0.5, 0, 0, 0.5]), - **tol, - ) - - -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) - tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) - for task in tasks: - result = device.run(task, **run_kwargs).result() - assert len(result.result_types) == 1 - assert np.allclose( - result.get_value_by_result_type(ResultType.Probability(target=0)), - np.array([0.5, 0.5]), - **tol, - ) - - -def result_types_nonzero_shots_bell_pair_testing(device: Device, run_kwargs: dict[str, Any]): - circuit = ( - Circuit() - .h(0) - .cnot(0, 1) - .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) - .sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) - ) - tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) - for task in tasks: - result = device.run(task, **run_kwargs).result() - assert len(result.result_types) == 2 - assert ( - 0.6 - < result.get_value_by_result_type( - ResultType.Expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) - ) - < 0.8 - ) - assert ( - len( - result.get_value_by_result_type( - ResultType.Sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) - ) - ) - == run_kwargs["shots"] - ) - - -def result_types_hermitian_testing( - device: Device, run_kwargs: dict[str, Any], test_program: bool = True -): - shots = run_kwargs["shots"] - theta = 0.543 - array = np.array([[1, 2j], [-2j, 0]]) - - circuit = ( - Circuit() - .rx(0, theta) - .variance(Observable.Hermitian(array), 0) - .expectation(Observable.Hermitian(array), 0) - ) - if shots: - circuit.add_result_type(ResultType.Sample(Observable.Hermitian(array), 0)) - 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() - - expected_mean = 2 * np.sin(theta) + 0.5 * np.cos(theta) + 0.5 - expected_var = 0.25 * (np.sin(theta) - 4 * np.cos(theta)) ** 2 - expected_eigs = np.linalg.eigvalsh(array) - assert_variance_expectation_sample_result( - result, shots, expected_var, expected_mean, expected_eigs - ) - - -def result_types_all_selected_testing( - device: Device, run_kwargs: dict[str, Any], test_program: bool = True -): - shots = run_kwargs["shots"] - theta = 0.543 - array = np.array([[1, 2j], [-2j, 0]]) - - circuit = ( - Circuit() - .rx(0, theta) - .rx(1, theta) - .variance(Observable.Hermitian(array)) - .expectation(Observable.Hermitian(array), 0) - ) - if shots: - circuit.add_result_type(ResultType.Sample(Observable.Hermitian(array), 1)) - - 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() - - expected_mean = 2 * np.sin(theta) + 0.5 * np.cos(theta) + 0.5 - var = 0.25 * (np.sin(theta) - 4 * np.cos(theta)) ** 2 - expected_var = [var, var] - expected_eigs = np.linalg.eigvalsh(array) - assert_variance_expectation_sample_result( - result, shots, expected_var, expected_mean, expected_eigs - ) - - -def get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, shots) -> Circuit: - circuit = ( - Circuit() - .rx(0, theta) - .rx(1, phi) - .rx(2, varphi) - .cnot(0, 1) - .cnot(1, 2) - .variance(obs, obs_targets) - .expectation(obs, obs_targets) - ) - if shots: - circuit.sample(obs, obs_targets) - return circuit - - -def assert_variance_expectation_sample_result( - result: GateModelQuantumTaskResult, - shots: int, - expected_var: float, - expected_mean: float, - expected_eigs: np.ndarray, -): - tol = get_tol(shots) - variance = result.values[0] - expectation = result.values[1] - if shots: - samples = result.values[2] - assert np.allclose(sorted(list(set(samples))), sorted(expected_eigs), **tol) - assert np.allclose(np.mean(samples), expected_mean, **tol) - assert np.allclose(np.var(samples), expected_var, **tol) - assert np.allclose(expectation, expected_mean, **tol) - assert np.allclose(variance, expected_var, **tol) - - -def result_types_tensor_x_y_testing(device: Device, run_kwargs: dict[str, Any]): - shots = run_kwargs["shots"] - theta = 0.432 - phi = 0.123 - varphi = -0.543 - obs = Observable.X() @ Observable.Y() - obs_targets = [0, 2] - 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 - ) - - -def result_types_tensor_z_z_testing(device: Device, run_kwargs: dict[str, Any]): - shots = run_kwargs["shots"] - theta = 0.432 - phi = 0.123 - varphi = -0.543 - obs = Observable.Z() @ Observable.Z() - obs_targets = [0, 2] - 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 - ) - - -def result_types_tensor_hermitian_hermitian_testing(device: Device, run_kwargs: dict[str, Any]): - shots = run_kwargs["shots"] - theta = 0.432 - 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], - ] - ) - 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) - 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 - ) - - -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 - varphi = -0.543 - obs = Observable.Z() @ Observable.H() @ Observable.Y() - obs_targets = [0, 1, 2] - 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 - ) - - -def result_types_tensor_z_hermitian_testing(device: Device, run_kwargs: dict[str, Any]): - shots = run_kwargs["shots"] - 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], - ] - ) - 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) - 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 - ) - - -def result_types_tensor_y_hermitian_testing(device: Device, run_kwargs: dict[str, Any]): - shots = run_kwargs["shots"] - 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], - ] - ) - 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) - 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 - ) - - -def result_types_noncommuting_testing(device: Device, run_kwargs: dict[str, Any]): - shots = 0 - 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], - ] - ) - obs1 = Observable.X() @ Observable.Y() - obs1_targets = [0, 2] - obs2 = Observable.Z() @ Observable.Z() - obs2_targets = [0, 2] - obs3 = Observable.Y() @ Observable.Hermitian(array) - obs3_targets = [0, 1, 2] - circuit = ( - get_result_types_three_qubit_circuit(theta, phi, varphi, obs1, obs1_targets, shots) - .expectation(obs2, obs2_targets) - .expectation(obs3, obs3_targets) - ) - tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) - 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) - assert np.allclose(result.values[3], expected_mean3) - - -def result_types_noncommuting_flipped_targets_testing(device: Device, run_kwargs: dict[str, Any]): - circuit = ( - Circuit() - .h(0) - .cnot(0, 1) - .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) - .expectation(observable=Observable.H() @ Observable.X(), target=[1, 0]) - ) - tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) - for task in tasks: - result = device.run(task, shots=0, **run_kwargs).result() - assert np.allclose(result.values[0], np.sqrt(2) / 2) - assert np.allclose(result.values[1], np.sqrt(2) / 2) - - -def result_types_noncommuting_all(device: Device, run_kwargs: dict[str, Any]): - array = np.array([[1, 2j], [-2j, 0]]) - circuit = ( - Circuit() - .h(0) - .cnot(0, 1) - .expectation(observable=Observable.Hermitian(array)) - .expectation(observable=Observable.X()) - ) - tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) - for task in tasks: - result = device.run(task, shots=0, **run_kwargs).result() - assert np.allclose(result.values[0], [0.5, 0.5]) - assert np.allclose(result.values[1], [0, 0]) - - -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) - - def run_circuit(circuit): - task = device.run(circuit, **run_kwargs) - return task.result() - - futures = [] - num_threads = 2 - - tasks = (bell, bell.to_ir(ir_type=IRType.OPENQASM)) - for task in tasks: - with concurrent.futures.ThreadPoolExecutor() as executor: - for _ in range(num_threads): - future = executor.submit(run_circuit, task) - futures.append(future) - for future in futures: - result = future.result() - assert np.allclose(result.measurement_probabilities["00"], 0.5, **tol) - assert np.allclose(result.measurement_probabilities["11"], 0.5, **tol) - assert len(result.measurements) == shots - - -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() - tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) - for task in tasks: - result = device.run(task, **run_kwargs).result() - assert len(result.result_types) == 1 - assert np.allclose( - result.get_value_by_result_type(ResultType.Probability()), - np.array([0.0, 0.1, 0, 0.9]), - **tol, - ) - - -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) - K1 = np.kron(np.array([[0.0, 1.0], [1.0, 0.0]]), np.array([[0.0, 1.0], [1.0, 0.0]])) * np.sqrt( - 0.1 - ) - circuit = Circuit().x(0).x(1).kraus((0, 1), [K0, K1]).probability() - tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) - for task in tasks: - result = device.run(task, **run_kwargs).result() - assert len(result.result_types) == 1 - assert np.allclose( - result.get_value_by_result_type(ResultType.Probability()), - np.array([0.1, 0.0, 0, 0.9]), - **tol, - ) - - -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)] - tasks_list = (circuits, [circuit.to_ir(ir_type=IRType.OPENQASM) for circuit in circuits]) - for tasks in tasks_list: - batch = device.run_batch(tasks, max_parallel=5, **run_kwargs) - results = batch.results() - for result in results: - assert np.allclose(result.measurement_probabilities["00"], 0.5, **tol) - assert np.allclose(result.measurement_probabilities["11"], 0.5, **tol) - assert len(result.measurements) == shots - assert [task.result() for task in batch.tasks] == results - - -def bell_pair_openqasm_testing(device: AwsDevice, run_kwargs: dict[str, Any]): - openqasm_string = ( - "OPENQASM 3;" - "qubit[2] q;" - "bit[2] c;" - "h q[0];" - "cnot q[0], q[1];" - "c[0] = measure q[0];" - "c[1] = measure q[1];" - ) - hardcoded_openqasm = OpenQasmProgram(source=openqasm_string) - circuit = Circuit().h(0).cnot(0, 1) - generated_openqasm = circuit.to_ir(ir_type=IRType.OPENQASM) - - for program in hardcoded_openqasm, generated_openqasm: - no_result_types_testing(program, device, run_kwargs, {"00": 0.5, "11": 0.5}) - - -def openqasm_noisy_circuit_1qubit_noise_full_probability( - device: Device, run_kwargs: dict[str, Any] -): - shots = run_kwargs["shots"] - tol = get_tol(shots) - openqasm_string = ( - "OPENQASM 3;" - "qubit[2] q;" - "x q[0];" - "x q[1];" - "#pragma braket noise bit_flip(0.1) q[0]" - "#pragma braket result probability q[0], q[1]" - ) - hardcoded_openqasm = OpenQasmProgram(source=openqasm_string) - circuit = Circuit().x(0).x(1).bit_flip(0, 0.1).probability([0, 1]) - generated_openqasm = circuit.to_ir(ir_type=IRType.OPENQASM) - - for program in hardcoded_openqasm, generated_openqasm: - result = device.run(program, **run_kwargs).result() - assert len(result.result_types) == 1 - assert np.allclose( - result.get_value_by_result_type(ResultType.Probability(target=[0, 1])), - np.array([0.0, 0.1, 0, 0.9]), - **tol, - ) - - -def openqasm_result_types_bell_pair_testing(device: Device, run_kwargs: dict[str, Any]): - openqasm_string = ( - "OPENQASM 3;" - "qubit[2] q;" - "h q[0];" - "cnot q[0], q[1];" - "#pragma braket result expectation h(q[0]) @ x(q[1])" - "#pragma braket result sample h(q[0]) @ x(q[1])" - ) - hardcoded_openqasm = OpenQasmProgram(source=openqasm_string) - circuit = ( - Circuit() - .h(0) - .cnot(0, 1) - .expectation(Observable.H() @ Observable.X(), (0, 1)) - .sample(Observable.H() @ Observable.X(), (0, 1)) - ) - generated_openqasm = circuit.to_ir(ir_type=IRType.OPENQASM) - - for program in hardcoded_openqasm, generated_openqasm: - result = device.run(program, **run_kwargs).result() - assert len(result.result_types) == 2 - assert ( - 0.6 - < result.get_value_by_result_type( - ResultType.Expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) - ) - < 0.8 - ) - assert ( - len( - result.get_value_by_result_type( - ResultType.Sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) - ) - ) - == run_kwargs["shots"] - ) - - -def many_layers(n_qubits: int, n_layers: int) -> Circuit: - """ - Function to return circuit with many layers. - - :param int n_qubits: number of qubits - :param int n_layers: number of layers - :return: Constructed easy circuit - :rtype: Circuit - """ - qubits = range(n_qubits) - circuit = Circuit() # instantiate circuit object - for q in range(n_qubits): - circuit.h(q) - for layer in range(n_layers): - if (layer + 1) % 100 != 0: - for qubit in range(len(qubits)): - angle = np.random.uniform(0, 2 * math.pi) - gate = np.random.choice( - [Gate.Rx(angle), Gate.Ry(angle), Gate.Rz(angle), Gate.H()], 1, replace=True - )[0] - circuit.add_instruction(Instruction(gate, qubit)) - else: - for q in range(0, n_qubits, 2): - circuit.cnot(q, q + 1) - for q in range(1, n_qubits - 1, 2): - circuit.cnot(q, q + 1) - return circuit diff --git a/test/integ_tests/job_test_module/job_test_submodule/job_test_submodule_file.py b/test/integ_tests/job_test_module/job_test_submodule/job_test_submodule_file.py deleted file mode 100644 index 6c916a60..00000000 --- a/test/integ_tests/job_test_module/job_test_submodule/job_test_submodule_file.py +++ /dev/null @@ -1,3 +0,0 @@ -def submodule_helper(): - print("import successful!") - return {"status": "SUCCESS"} diff --git a/test/integ_tests/job_test_module/job_test_submodule/requirements.txt b/test/integ_tests/job_test_module/job_test_submodule/requirements.txt deleted file mode 100644 index e079f8a6..00000000 --- a/test/integ_tests/job_test_module/job_test_submodule/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -pytest diff --git a/test/integ_tests/job_test_script.py b/test/integ_tests/job_test_script.py deleted file mode 100644 index d2a74e6c..00000000 --- a/test/integ_tests/job_test_script.py +++ /dev/null @@ -1,58 +0,0 @@ -# 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.aws import AwsDevice -from braket.circuits import Circuit -from braket.jobs import ( - get_hyperparameters, - get_job_device_arn, - save_job_checkpoint, - save_job_result, -) -from braket.jobs_data import PersistedJobDataFormat - - -def start_here(): - hyperparameters = get_hyperparameters() - - if hyperparameters["test_case"] == "completed": - completed_job_script() - else: - failed_job_script() - - -def failed_job_script(): - print("Test job started!!!!!") - open("fake_file") - - -def completed_job_script(): - print("Test job started!!!!!") - - # Use the device declared in the Orchestration Script - device = AwsDevice(get_job_device_arn()) - - bell = Circuit().h(0).cnot(0, 1) - for _ 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") - save_job_checkpoint({"some_data": "abc"}, data_format=PersistedJobDataFormat.PICKLED_V4) - - print("Test job completed!!!!!") - - -def job_helper(): - print("import successful!") - return {"status": "SUCCESS"} diff --git a/test/integ_tests/requirements.txt b/test/integ_tests/requirements.txt deleted file mode 100644 index e079f8a6..00000000 --- a/test/integ_tests/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -pytest diff --git a/test/integ_tests/test_adjoint_gradient.py b/test/integ_tests/test_adjoint_gradient.py deleted file mode 100644 index 99ab7dfe..00000000 --- a/test/integ_tests/test_adjoint_gradient.py +++ /dev/null @@ -1,175 +0,0 @@ -# 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.aws import AwsQuantumTask, AwsQuantumTaskBatch -from braket.circuits import Circuit, Observable -from braket.parametric import FreeParameter - - -@pytest.fixture -def sv1_device(): - return "arn:aws:braket:::device/quantum-simulator/amazon/sv1" - - -def test_adjoint_gradient_quantum_task_with_nested_targets( - aws_session, s3_destination_folder, sv1_device -): - theta = FreeParameter("theta") - inputs = {"theta": 0.2} - circ = ( - Circuit() - .rx(0, theta) - .adjoint_gradient( - observable=(-2 * Observable.Y()) @ (3 * Observable.I()) - + 0.75 * Observable.Y() @ Observable.Z(), - target=[[0, 1], [2, 3]], - parameters=["theta"], - ) - ) - - expected_openqasm = ( - "OPENQASM 3.0;\n" - "input float theta;\n" - "qubit[4] q;\n" - "rx(theta) q[0];\n" - "#pragma braket result adjoint_gradient expectation(-6 * y(q[0]) @ i(q[1]) + 0.75 * " - "y(q[2]) @ z(q[3])) theta" - ) - - gradient_task = AwsQuantumTask.create( - aws_session=aws_session, - task_specification=circ, - s3_destination_folder=s3_destination_folder, - device_arn=sv1_device, - shots=0, - inputs=inputs, - ) - - assert gradient_task.result().additional_metadata.action.source == expected_openqasm - assert gradient_task.result().values == [ - {"expectation": 1.1920159847703675, "gradient": {"theta": 5.880399467047451}} - ] - assert gradient_task.result().result_types[0].type.observable == "-6 * y @ i + 0.75 * y @ z" - assert gradient_task.result().additional_metadata.action.inputs == inputs - - -def test_adjoint_gradient_with_standard_observable_terms( - aws_session, s3_destination_folder, sv1_device -): - theta = FreeParameter("theta") - inputs = {"theta": 0.2} - circ = ( - Circuit() - .rx(0, theta) - .adjoint_gradient( - observable=(2 * Observable.X()) + (3 * Observable.Y()) - Observable.Z(), - target=[[0], [1], [2]], - parameters=["theta"], - ) - ) - - expected_openqasm = ( - "OPENQASM 3.0;\n" - "input float theta;\n" - "qubit[3] q;\n" - "rx(theta) q[0];\n" - "#pragma braket result adjoint_gradient expectation(2 * x(q[0]) + 3 * y(q[1]) " - "- 1 * z(q[2])) theta" - ) - - gradient_task = AwsQuantumTask.create( - aws_session=aws_session, - task_specification=circ, - s3_destination_folder=s3_destination_folder, - device_arn=sv1_device, - shots=0, - inputs=inputs, - ) - - assert gradient_task.result().additional_metadata.action.source == expected_openqasm - assert gradient_task.result().values == [{"expectation": -1.0, "gradient": {"theta": 0.0}}] - assert gradient_task.result().result_types[0].type.observable == "2 * x + 3 * y - 1 * z" - assert gradient_task.result().additional_metadata.action.inputs == inputs - - -def test_adjoint_gradient_with_batch_circuits(aws_session, s3_destination_folder, sv1_device): - theta = FreeParameter("theta") - - inputs = {"theta": 0.2} - circ_1 = ( - Circuit() - .rx(0, theta) - .adjoint_gradient( - observable=(2 * Observable.Y()) @ (3 * Observable.I()), - target=[0, 1], - parameters=["theta"], - ) - ) - circ_2 = ( - Circuit() - .rx(0, theta) - .adjoint_gradient( - observable=(-2 * Observable.Y()) @ (3 * Observable.I()) - + 0.75 * Observable.Y() @ Observable.Z(), - target=[[0, 1], [0, 1]], - parameters=["theta"], - ) - ) - - expected_openqasm = [ - ( - "OPENQASM 3.0;\n" - "input float theta;\n" - "qubit[2] q;\n" - "rx(theta) q[0];\n" - "#pragma braket result adjoint_gradient expectation(6 * y(q[0]) @ i(q[1])) theta" - ), - ( - "OPENQASM 3.0;\n" - "input float theta;\n" - "qubit[2] q;\n" - "rx(theta) q[0];\n" - "#pragma braket result adjoint_gradient expectation(-6 * y(q[0]) @ i(q[1]) + 0.75 * " - "y(q[0]) @ z(q[1])) theta" - ), - ] - - expected_result_values = [ - [{"expectation": -1.1920159847703675, "gradient": {"theta": -5.880399467047451}}], - [{"expectation": 1.0430139866740715, "gradient": {"theta": 5.145349533666519}}], - ] - expected_observables = ["6 * y @ i", "-6 * y @ i + 0.75 * y @ z"] - - gradient_batch_tasks = AwsQuantumTaskBatch( - aws_session=aws_session, - device_arn=sv1_device, - task_specifications=[circ_1, circ_2], - shots=0, - max_parallel=1, - s3_destination_folder=s3_destination_folder, - inputs=inputs, - ) - - for i in range(2): - assert ( - gradient_batch_tasks.tasks[i].result().additional_metadata.action.source - == expected_openqasm[i] - ) - assert gradient_batch_tasks.tasks[i].result().values == expected_result_values[i] - assert ( - gradient_batch_tasks.tasks[i].result().result_types[0].type.observable - == expected_observables[i] - ) - assert gradient_batch_tasks.tasks[i].result().additional_metadata.action.inputs == inputs diff --git a/test/integ_tests/test_aws_session_s3.py b/test/integ_tests/test_aws_session_s3.py deleted file mode 100644 index 8b1a48d4..00000000 --- a/test/integ_tests/test_aws_session_s3.py +++ /dev/null @@ -1,45 +0,0 @@ -# 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 pytest - -TEST_S3_OBJ_CONTENTS = { - "TaskMetadata": { - "Id": "blah", - "Status": "COMPLETED", - } -} - - -@pytest.fixture() -def s3_key(s3_resource, s3_bucket, s3_prefix): - obj = s3_resource.Object(s3_bucket, f"{s3_prefix}/test_task_reading.json") - - try: - obj_body = obj.get()["Body"].read().decode("utf-8") - assert obj_body == json.dumps(TEST_S3_OBJ_CONTENTS) - except s3_resource.meta.client.exceptions.NoSuchKey: - # Put s3 object - obj.put(ACL="private", Body=json.dumps(TEST_S3_OBJ_CONTENTS, indent=4)) - except AssertionError: - # Put s3 object - obj.put(ACL="private", Body=json.dumps(TEST_S3_OBJ_CONTENTS, indent=4)) - - return obj.key - - -def test_retrieve_s3_object_body(aws_session, s3_bucket, s3_key): - obj_body = aws_session.retrieve_s3_object_body(s3_bucket, s3_key) - assert obj_body == json.dumps(TEST_S3_OBJ_CONTENTS, indent=4) diff --git a/test/integ_tests/test_cost_tracking.py b/test/integ_tests/test_cost_tracking.py deleted file mode 100644 index d638f80b..00000000 --- a/test/integ_tests/test_cost_tracking.py +++ /dev/null @@ -1,117 +0,0 @@ -# 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 unittest.mock as mock -from datetime import timedelta - -import boto3 -import pytest -from botocore.exceptions import ClientError - -from braket.aws import AwsDevice, AwsSession -from braket.circuits import Circuit -from braket.tracking import Tracker -from braket.tracking.tracker import MIN_SIMULATOR_DURATION - - -@pytest.mark.parametrize( - "qpu", - [ - "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony", - "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy", - "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3", - ], -) -def test_qpu_tracking(qpu): - circuit = Circuit().h(0) - with Tracker() as t: - device = AwsDevice(qpu) - # Mock out task creation against the service - device._aws_session.braket_client.create_quantum_task = mock.Mock( - return_value={ - "quantumTaskArn": ( - f"arn:aws:braket:{device._aws_session.region}" - ":1234567890:quantum-task/e9e6bd31-5ba3-4027-948d-93c5f12e2942" - ) - } - ) - device.run(circuit, shots=10) - - assert t.qpu_tasks_cost() > 0 - - -def test_simulator_tracking(): - circuit = Circuit().h(0).cnot(0, 1) - device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") - - with Tracker() as t: - task0 = device.run(circuit, shots=100) - task1 = device.run(circuit, shots=100) - assert t.quantum_tasks_statistics() == { - "arn:aws:braket:::device/quantum-simulator/amazon/sv1": { - "shots": 200, - "tasks": {"CREATED": 2}, - } - } - task0.result() - task1.result() - - try: - device.run(circuit, shots=100).cancel() - except ClientError as e: - if not e.response["Error"]["Message"].startswith( - "Amazon Braket cannot cancel a quantum task in the COMPLETED status" - ): - raise e - - quantum_stats = t.quantum_tasks_statistics()[device.arn] - assert quantum_stats["shots"] == 300 - assert quantum_stats["tasks"] == {"COMPLETED": 2, "CANCELLING": 1} - assert quantum_stats["execution_duration"] > timedelta(0) - assert quantum_stats["billed_execution_duration"] >= quantum_stats["execution_duration"] - assert quantum_stats["billed_execution_duration"] >= 2 * MIN_SIMULATOR_DURATION - - assert t.qpu_tasks_cost() == 0 - assert t.simulator_tasks_cost() > 0 - - -def test_all_devices_price_search(): - devices = AwsDevice.get_devices(statuses=["ONLINE", "OFFLINE"]) - - tasks = {} - for region in AwsDevice.REGIONS: - 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 - pass - - t = Tracker() - t._resources = tasks - - assert t.qpu_tasks_cost() + t.simulator_tasks_cost() > 0 diff --git a/test/integ_tests/test_create_local_quantum_job.py b/test/integ_tests/test_create_local_quantum_job.py deleted file mode 100644 index 16c001f3..00000000 --- a/test/integ_tests/test_create_local_quantum_job.py +++ /dev/null @@ -1,154 +0,0 @@ -# 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 re -import tempfile -from pathlib import Path - -import pytest - -from braket.jobs.local import LocalQuantumJob - - -def test_completed_local_job(aws_session, capsys): - """Asserts the hybrid job is completed with the respective files and folders for logs, - results and checkpoints. Validate the results are what we expect. Also, - assert that logs contains all the necessary steps for setup and running - the hybrid job is displayed to the user. - """ - absolute_source_module = str(Path("test/integ_tests/job_test_script.py").resolve()) - current_dir = Path.cwd() - - with tempfile.TemporaryDirectory() as temp_dir: - try: - os.chdir(temp_dir) - job = LocalQuantumJob.create( - "arn:aws:braket:::device/quantum-simulator/amazon/sv1", - source_module=absolute_source_module, - entry_point="job_test_script:start_here", - hyperparameters={"test_case": "completed"}, - aws_session=aws_session, - ) - - job_name = job.name - pattern = f"^local:job/{job_name}$" - re.match(pattern=pattern, string=job.arn) - - assert job.state() == "COMPLETED" - assert Path(job_name).is_dir() - - # Check results match the expectations. - assert Path(f"{job_name}/results.json").exists() - assert job.result() == {"converged": True, "energy": -0.2} - - # Validate checkpoint files and data - assert Path(f"{job_name}/checkpoints/{job_name}.json").exists() - assert Path(f"{job_name}/checkpoints/{job_name}_plain_data.json").exists() - - for file_name, expected_data in [ - ( - f"{job_name}/checkpoints/{job_name}_plain_data.json", - { - "braketSchemaHeader": { - "name": "braket.jobs_data.persisted_job_data", - "version": "1", - }, - "dataDictionary": {"some_data": "abc"}, - "dataFormat": "plaintext", - }, - ), - ( - f"{job_name}/checkpoints/{job_name}.json", - { - "braketSchemaHeader": { - "name": "braket.jobs_data.persisted_job_data", - "version": "1", - }, - "dataDictionary": {"some_data": "gASVBwAAAAAAAACMA2FiY5Qu\n"}, - "dataFormat": "pickled_v4", - }, - ), - ]: - with open(file_name) as f: - assert json.loads(f.read()) == expected_data - - # Capture logs - assert Path(f"{job_name}/log.txt").exists() - job.logs() - log_data, errors = capsys.readouterr() - - logs_to_validate = [ - "Beginning Setup", - "Running Code As Process", - "Test job started!!!!!", - "Test job completed!!!!!", - "Code Run Finished", - ] - - for data in logs_to_validate: - assert data in log_data - - finally: - os.chdir(current_dir) - - -def test_failed_local_job(aws_session, capsys): - """Asserts the hybrid job is failed with the output, checkpoints not created in bucket - and only logs are populated. Validate the calling result function raises - the ValueError. Also, check if the logs displays the required error message. - """ - absolute_source_module = str(Path("test/integ_tests/job_test_script.py").resolve()) - current_dir = Path.cwd() - - with tempfile.TemporaryDirectory() as temp_dir: - try: - os.chdir(temp_dir) - job = LocalQuantumJob.create( - "arn:aws:braket:::device/quantum-simulator/amazon/sv1", - source_module=absolute_source_module, - entry_point="job_test_script:start_here", - hyperparameters={"test_case": "failed"}, - aws_session=aws_session, - ) - - job_name = job.name - pattern = f"^local:job/{job_name}$" - re.match(pattern=pattern, string=job.arn) - - assert Path(job_name).is_dir() - - # Check no files are populated in checkpoints folder. - assert not any(Path(f"{job_name}/checkpoints").iterdir()) - - # Check results match the expectations. - error_message = f"Unable to find results in the local job directory {job_name}." - with pytest.raises(ValueError, match=error_message): - job.result() - - assert Path(f"{job_name}/log.txt").exists() - job.logs() - log_data, errors = capsys.readouterr() - - logs_to_validate = [ - "Beginning Setup", - "Running Code As Process", - "Test job started!!!!!", - "Code Run Finished", - ] - - 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 deleted file mode 100644 index be0e49a8..00000000 --- a/test/integ_tests/test_create_quantum_job.py +++ /dev/null @@ -1,311 +0,0 @@ -# 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.path -import re -import sys -import tempfile -import time -from pathlib import Path - -import job_test_script -import pytest -from job_test_module.job_test_submodule.job_test_submodule_file import submodule_helper - -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) - - -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 = 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. - 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. - 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"], - )[1] - keys = aws_session.list_keys( - bucket=s3_bucket, - prefix=f"jobs/{job_name}/{subdirectory}/", - ) - assert keys == [f"jobs/{job_name}/{subdirectory}/script/source.tar.gz"] - - # no results saved - assert job.result() == {} - - job.logs() - log_data, errors = capsys.readouterr() - assert errors == "" - logs_to_validate = [ - "Invoking script with the following command:", - "braket_container.py", - "Running Code As Process", - "Test job started!!!!!", - "FileNotFoundError: [Errno 2] No such file or directory: 'fake_file'", - "Code Run Finished", - '"user_entry_point": "braket_container.py"', - ] - - for data in logs_to_validate: - assert data in log_data - - assert job.metadata()["failureReason"] == ( - "AlgorithmError: FileNotFoundError: [Errno 2] " - "No such file or directory: 'fake_file', exit code: 1" - ) - - -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 = 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 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. - 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"], - )[1] - keys = aws_session.list_keys( - bucket=s3_bucket, - prefix=f"jobs/{job_name}/{subdirectory}/", - ) - for expected_key in [ - 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}/{subdirectory}/checkpoints/{job_name}_plain_data.json", - { - "braketSchemaHeader": { - "name": "braket.jobs_data.persisted_job_data", - "version": "1", - }, - "dataDictionary": {"some_data": "abc"}, - "dataFormat": "plaintext", - }, - ), - ( - f"jobs/{job_name}/{subdirectory}/checkpoints/{job_name}.json", - { - "braketSchemaHeader": { - "name": "braket.jobs_data.persisted_job_data", - "version": "1", - }, - "dataDictionary": {"some_data": "gASVBwAAAAAAAACMA2FiY5Qu\n"}, - "dataFormat": "pickled_v4", - }, - ), - ]: - assert ( - json.loads( - aws_session.retrieve_s3_object_body(s3_bucket=s3_bucket, s3_object_key=s3_key) - ) - == expected_data - ) - - current_dir = Path.cwd() - - with tempfile.TemporaryDirectory() as temp_dir: - os.chdir(temp_dir) - try: - # Check results match the expectations. - assert job.result() == {"converged": True, "energy": -0.2} - finally: - os.chdir(current_dir) - - # Check the logs and validate it contains required output. - job.logs(wait=True) - log_data, errors = capsys.readouterr() - assert errors == "" - logs_to_validate = [ - "Invoking script with the following command:", - "braket_container.py", - "Running Code As Process", - "Test job started!!!!!", - "Test job completed!!!!!", - "Code Run Finished", - '"user_entry_point": "braket_container.py"', - "Reporting training SUCCESS", - ] - - for data in logs_to_validate: - assert data in log_data - - -@pytest.mark.xfail( - (sys.version_info.major, sys.version_info.minor) != decorator_python_version(), - raises=RuntimeError, - reason="Python version mismatch", -) -def test_decorator_job(): - class MyClass: - attribute = "value" - - def __str__(self): - return f"MyClass({self.attribute})" - - @hybrid_job( - device=Devices.Amazon.SV1, - include_modules="job_test_script", - dependencies=str(Path("test", "integ_tests", "requirements.txt")), - 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") as f: - assert f.readlines() == ["pytest\n"] - with open(Path("test", "integ_tests", "requirements.txt")) as f: - assert f.readlines() == ["pytest\n"] - assert dir(pytest) - assert a.attribute == "value" - assert b == 2 - assert c == 0 - assert d == 5 - assert extras["extra_arg"] == "extra_value" - - hp_file = os.environ["AMZN_BRAKET_HP_FILE"] - with open(hp_file) as f: - hyperparameters = json.load(f) - assert hyperparameters == { - "a": "MyClass{value}", - "b": "2", - "c": "0", - "d": "5", - "extra_arg": "extra_value", - } - - with open("test/output_file.txt", "w") as f: - f.write("hello") - - return job_test_script.job_helper() - - job = decorator_job(MyClass(), 2, d=5, extra_arg="extra_value") - assert job.result()["status"] == "SUCCESS" - - current_dir = Path.cwd() - with tempfile.TemporaryDirectory() as temp_dir: - os.chdir(temp_dir) - try: - job.download_result() - with open(Path(job.name, "test", "output_file.txt")) as f: - assert f.read() == "hello" - assert ( - Path(job.name, "results.json").exists() - and Path(job.name, "test").exists() - and not Path(job.name, "test", "integ_tests").exists() - ) - finally: - os.chdir(current_dir) - - -@pytest.mark.xfail( - (sys.version_info.major, sys.version_info.minor) != decorator_python_version(), - raises=RuntimeError, - reason="Python version mismatch", -) -def test_decorator_job_submodule(): - @hybrid_job( - device=Devices.Amazon.SV1, - include_modules=[ - "job_test_module", - ], - dependencies=Path( - "test", "integ_tests", "job_test_module", "job_test_submodule", "requirements.txt" - ), - input_data={ - "my_input": str(Path("test", "integ_tests", "requirements.txt")), - "my_dir": str(Path("test", "integ_tests", "job_test_module")), - }, - ) - def decorator_job_submodule(): - 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")) as f: - assert f.readlines() == ["pytest\n"] - with open( - Path(get_input_data_dir("my_dir")) / "job_test_submodule" / "requirements.txt" - ) as f: - assert f.readlines() == ["pytest\n"] - with open( - Path( - "test", - "integ_tests", - "job_test_module", - "job_test_submodule", - "requirements.txt", - ), - ) as f: - assert f.readlines() == ["pytest\n"] - assert dir(pytest) - save_job_result(submodule_helper()) - - job = decorator_job_submodule() - assert job.result()["status"] == "SUCCESS" diff --git a/test/integ_tests/test_density_matrix_simulator.py b/test/integ_tests/test_density_matrix_simulator.py deleted file mode 100644 index 1fba5ebc..00000000 --- a/test/integ_tests/test_density_matrix_simulator.py +++ /dev/null @@ -1,54 +0,0 @@ -import math - -import numpy as np -import pytest -from gate_model_device_testing_utils import get_tol - -from braket.aws import AwsDevice -from braket.circuits import Circuit, Noise, Observable - -SHOTS = 500 -DM1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/dm1" -SIMULATOR_ARNS = [DM1_ARN] - - -@pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -def test_mixed_states(simulator_arn, aws_session, s3_destination_folder): - num_qubits = 10 - circuit = _mixed_states(num_qubits) - device = AwsDevice(simulator_arn, aws_session) - - tol = get_tol(SHOTS) - result = device.run(circuit, shots=SHOTS, s3_destination_folder=s3_destination_folder).result() - probabilities = result.measurement_probabilities - probability_sum = 0 - for bitstring in probabilities: - assert probabilities[bitstring] >= 0 - probability_sum += probabilities[bitstring] - assert math.isclose(probability_sum, 1, rel_tol=tol["rtol"], abs_tol=tol["atol"]) - assert len(result.measurements) == SHOTS - - -def _mixed_states(n_qubits: int) -> Circuit: - noise = Noise.PhaseFlip(probability=0.2) - circ = Circuit() - for qubit in range(0, n_qubits - 2, 3): - circ.x(qubit).y(qubit + 1).cnot(qubit, qubit + 2).x(qubit + 1).z(qubit + 2) - circ.apply_gate_noise(noise, target_qubits=[qubit, qubit + 2]) - - # attach the result types - circ.probability() - circ.expectation(observable=Observable.Z(), target=0) - - return circ - - -@pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -def test_density_matrix_result_type(simulator_arn): - circ = Circuit().bit_flip(0, 0.2).density_matrix() - device = AwsDevice(simulator_arn) - density_matrix_result = device.run(circ).result().result_types[0].value - assert np.allclose( - density_matrix_result, - [[[0.8, 0.0], [0.0, 0.0]], [[0.0, 0.0], [0.2, 0.0]]], - ) diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py deleted file mode 100644 index 540c09f6..00000000 --- a/test/integ_tests/test_device_creation.py +++ /dev/null @@ -1,140 +0,0 @@ -# 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.aws import AwsDevice -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" -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)] -) -def test_device_creation(arn, created_braket_devices): - device = created_braket_devices[arn] - assert device.arn == arn - assert device.name - assert device.status - assert device.type - assert device.provider_name - assert device.properties - - -@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 - assert device.frames - - -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] - - -@pytest.mark.parametrize("arn", [(RIGETTI_ARN), (IONQ_ARN), (OQC_ARN), (SIMULATOR_ARN)]) -def test_get_devices_arn(arn): - results = AwsDevice.get_devices(arns=[arn]) - assert results[0].arn == 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 - - -def test_get_devices_others(): - provider_names = ["Amazon Braket"] - types = ["SIMULATOR"] - statuses = ["ONLINE"] - results = AwsDevice.get_devices(provider_names=provider_names, types=types, statuses=statuses) - assert results - for result in results: - assert result.provider_name in provider_names - assert result.type in types - assert result.status in statuses - - -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 - - -def _get_provider_name(device: AwsDevice) -> str: - arn_provider = device.arn.split("/")[2] - - # capitalize as in provider name - provider_primary_name = device.provider_name.split()[0] - if arn_provider == provider_primary_name.lower(): - capitalized = provider_primary_name - else: - capitalized = arn_provider.upper() - - # remove dashes - return capitalized.replace("-", "") - - -def _get_device_name(device: AwsDevice) -> str: - arn_device_name = device.arn.split("/")[-1] - - device_name = ( - arn_device_name.replace("Advantage_system", "Advantage").replace("_", "").replace("-", "") - ) - - if device.provider_name == "Amazon Braket": - device_name = device_name.upper() - return device_name - - -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]): - provider_name = _get_provider_name(device) - if provider_name not in active_providers: - provider_name = f"_{provider_name}" - device_name = _get_device_name(device) - if device.status == "RETIRED": - device_name = f"_{device_name}" - - assert getattr(getattr(Devices, provider_name), device_name) == device.arn - - -def test_device_enum(braket_devices, created_braket_devices): - active_providers = _get_active_providers(braket_devices) - - # validate all devices in API - 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 = created_braket_devices[device_arn] - _validate_device(device, active_providers) diff --git a/test/integ_tests/test_local_braket_simulator.py b/test/integ_tests/test_local_braket_simulator.py deleted file mode 100644 index ff1b3755..00000000 --- a/test/integ_tests/test_local_braket_simulator.py +++ /dev/null @@ -1,161 +0,0 @@ -# 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 gate_model_device_testing_utils import ( - multithreaded_bell_pair_testing, - no_result_types_bell_pair_testing, - qubit_ordering_testing, - result_types_all_selected_testing, - result_types_bell_pair_full_probability_testing, - result_types_bell_pair_marginal_probability_testing, - result_types_hermitian_testing, - result_types_noncommuting_all, - result_types_noncommuting_flipped_targets_testing, - result_types_noncommuting_testing, - result_types_nonzero_shots_bell_pair_testing, - result_types_observable_not_in_instructions, - result_types_tensor_hermitian_hermitian_testing, - result_types_tensor_x_y_testing, - result_types_tensor_y_hermitian_testing, - result_types_tensor_z_h_y_testing, - result_types_tensor_z_hermitian_testing, - result_types_tensor_z_z_testing, - result_types_zero_shots_bell_pair_testing, -) - -from braket.devices import LocalSimulator - -DEVICE = LocalSimulator() -SHOTS = 8000 - - -def test_multithreaded_bell_pair(caplog): - multithreaded_bell_pair_testing(DEVICE, {"shots": SHOTS}) - assert not caplog.text - - -def test_no_result_types_bell_pair(caplog): - no_result_types_bell_pair_testing(DEVICE, {"shots": SHOTS}) - assert not caplog.text - - -def test_qubit_ordering(caplog): - qubit_ordering_testing(DEVICE, {"shots": SHOTS}) - assert not caplog.text - - -def test_result_types_no_shots(caplog): - result_types_zero_shots_bell_pair_testing(DEVICE, True, {"shots": 0}) - assert not caplog.text - - -def test_result_types_nonzero_shots_bell_pair(caplog): - result_types_nonzero_shots_bell_pair_testing(DEVICE, {"shots": SHOTS}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_bell_pair_full_probability(shots, caplog): - result_types_bell_pair_full_probability_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_bell_pair_marginal_probability(shots, caplog): - result_types_bell_pair_marginal_probability_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_hermitian(shots, caplog): - result_types_hermitian_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_x_y(shots, caplog): - result_types_tensor_x_y_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_z_z(shots, caplog): - result_types_tensor_z_z_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_z_h_y(shots, caplog): - result_types_tensor_z_h_y_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_z_hermitian(shots, caplog): - result_types_tensor_z_hermitian_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_hermitian_hermitian(shots, caplog): - result_types_tensor_hermitian_hermitian_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_y_hermitian(shots, caplog): - result_types_tensor_y_hermitian_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_all_selected(shots, caplog): - result_types_all_selected_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -def test_result_types_noncommuting(caplog): - result_types_noncommuting_testing(DEVICE, {}) - assert not caplog.text - - -def test_result_types_noncommuting_flipped_targets(caplog): - result_types_noncommuting_flipped_targets_testing(DEVICE, {}) - assert not caplog.text - - -def test_result_types_noncommuting_all(caplog): - result_types_noncommuting_all(DEVICE, {}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_observable_not_in_instructions(shots, caplog): - result_types_observable_not_in_instructions(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize( - "backend, device_name", - [ - ("default", "StateVectorSimulator"), - ("braket_sv", "StateVectorSimulator"), - ("braket_dm", "DensityMatrixSimulator"), - ("braket_ahs", "RydbergAtomSimulator"), - ], -) -def test_local_simulator_device_names(backend, device_name, caplog): - local_simulator_device = LocalSimulator(backend) - assert local_simulator_device.name == device_name - assert not caplog.text diff --git a/test/integ_tests/test_local_noise_simulator.py b/test/integ_tests/test_local_noise_simulator.py deleted file mode 100644 index 26753d2c..00000000 --- a/test/integ_tests/test_local_noise_simulator.py +++ /dev/null @@ -1,141 +0,0 @@ -# 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 gate_model_device_testing_utils import ( - no_result_types_bell_pair_testing, - noisy_circuit_1qubit_noise_full_probability, - noisy_circuit_2qubit_noise_full_probability, - qubit_ordering_testing, - result_types_all_selected_testing, - result_types_bell_pair_full_probability_testing, - result_types_bell_pair_marginal_probability_testing, - result_types_hermitian_testing, - result_types_noncommuting_all, - result_types_noncommuting_flipped_targets_testing, - result_types_noncommuting_testing, - result_types_nonzero_shots_bell_pair_testing, - result_types_tensor_hermitian_hermitian_testing, - result_types_tensor_x_y_testing, - result_types_tensor_y_hermitian_testing, - result_types_tensor_z_h_y_testing, - result_types_tensor_z_hermitian_testing, - result_types_tensor_z_z_testing, -) - -from braket.devices import LocalSimulator - -DEVICE = LocalSimulator("braket_dm") -SHOTS = 8000 - - -def test_no_result_types_bell_pair(caplog): - no_result_types_bell_pair_testing(DEVICE, {"shots": SHOTS}) - assert not caplog.text - - -def test_qubit_ordering(caplog): - qubit_ordering_testing(DEVICE, {"shots": SHOTS}) - assert not caplog.text - - -def test_result_types_nonzero_shots_bell_pair(caplog): - result_types_nonzero_shots_bell_pair_testing(DEVICE, {"shots": SHOTS}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_bell_pair_full_probability(shots, caplog): - result_types_bell_pair_full_probability_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_bell_pair_marginal_probability(shots, caplog): - result_types_bell_pair_marginal_probability_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_hermitian(shots, caplog): - result_types_hermitian_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_x_y(shots, caplog): - result_types_tensor_x_y_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_z_z(shots, caplog): - result_types_tensor_z_z_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_z_h_y(shots, caplog): - result_types_tensor_z_h_y_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_z_hermitian(shots, caplog): - result_types_tensor_z_hermitian_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_hermitian_hermitian(shots, caplog): - result_types_tensor_hermitian_hermitian_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_tensor_y_hermitian(shots, caplog): - result_types_tensor_y_hermitian_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_result_types_all_selected(shots, caplog): - result_types_all_selected_testing(DEVICE, {"shots": shots}) - assert not caplog.text - - -def test_result_types_noncommuting(caplog): - result_types_noncommuting_testing(DEVICE, {}) - assert not caplog.text - - -def test_result_types_noncommuting_flipped_targets(caplog): - result_types_noncommuting_flipped_targets_testing(DEVICE, {}) - assert not caplog.text - - -def test_result_types_noncommuting_all(caplog): - result_types_noncommuting_all(DEVICE, {}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_noisy_circuit_1qubit_noise(shots, caplog): - noisy_circuit_1qubit_noise_full_probability(DEVICE, {"shots": shots}) - assert not caplog.text - - -@pytest.mark.parametrize("shots", [0, SHOTS]) -def test_noisy_circuit_2qubit_noise(shots, caplog): - noisy_circuit_2qubit_noise_full_probability(DEVICE, {"shots": shots}) - assert not caplog.text diff --git a/test/integ_tests/test_measure.py b/test/integ_tests/test_measure.py deleted file mode 100644 index b7fef275..00000000 --- a/test/integ_tests/test_measure.py +++ /dev/null @@ -1,85 +0,0 @@ -# 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/integ_tests/test_pulse.py b/test/integ_tests/test_pulse.py deleted file mode 100644 index 4eb3ffa9..00000000 --- a/test/integ_tests/test_pulse.py +++ /dev/null @@ -1,345 +0,0 @@ -import math - -import numpy as np -import pytest - -from braket.aws import AwsDevice, AwsQuantumTask -from braket.circuits import Circuit, Gate, GateCalibrations, QubitSet -from braket.parametric import FreeParameter -from braket.pulse import ArbitraryWaveform, Frame, Port, PulseSequence - - -@pytest.fixture -def device(): - return AwsDevice("arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3") - - -@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, - ] - ) - - -@pytest.fixture -def port(): - return Port("test_port_ff", dt=1e-9) - - -@pytest.fixture -def frame_id(device): - return next(iter(device.frames)) - - -@pytest.fixture -def frame(frame_id, port): - return Frame(frame_id, port, 1e6, is_predefined=True) - - -@pytest.fixture -def pulse_sequence(frame, arbitrary_waveform): - return ( - PulseSequence() - .barrier(qubits_or_frames=[frame]) - .play(frame=frame, waveform=arbitrary_waveform) - ) - - -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( - q0: str, - q1: str, - shift_phases_q0: float, - shift_phases_q1: float, - 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] - - dt = device.properties.pulse.ports[q0_q1_cz_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) - .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 - - -def test_pulse_bell(arbitrary_waveform, device): - if device.status == "OFFLINE": - pytest.skip("Device offline") - ( - a, - b, - ) = ( - 10, - 113, - ) # 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) - - bell_pair_with_gates = Circuit().h(a).h(b).cz(a, b).h(b) - bell_pair_with_pulses_unbound = ( - h_gate(a) + h_gate(b) + Circuit().pulse_gate([a, b], cz) + h_gate(b) - ) - bell_pair_with_pulses = bell_pair_with_pulses_unbound(theta_0=p0, theta_1=p1) - - num_shots = 1000 - gate_task = device.run(bell_pair_with_gates, shots=num_shots, disable_qubit_rewiring=True) - pulse_task = device.run(bell_pair_with_pulses, shots=num_shots) - - if not device.is_available: - try: - assert gate_task.state not in AwsQuantumTask.TERMINAL_STATES - assert pulse_task.state not in AwsQuantumTask.TERMINAL_STATES - finally: - gate_task.cancel() - pulse_task.cancel() - return - - gate_measurements = gate_task.result().measurement_counts - pulse_measurements = pulse_task.result().measurement_counts - - # 1-smoothing to avoid nans for 0 counts - observed = ( - np.array([pulse_measurements.get(state, 1) for state in ("00", "01", "10", "11")]) - / num_shots - ) - expected = ( - np.array([gate_measurements.get(state, 1) for state in ("00", "01", "10", "11")]) - / num_shots - ) - chi_squared = np.sum((observed - expected) ** 2 / expected) - assert chi_squared < 10 # adjust this threshold if test is flaky - - -def test_pulse_sequence(arbitrary_waveform, device): - if device.status == "OFFLINE": - pytest.skip("Device offline") - ( - a, - b, - ) = ( - 10, - 113, - ) # 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_with_pulses_unbound = cz_pulse(a, b, theta_0, theta_1, a_b_cz_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) - .capture_v0(q0_readout_frame) - .capture_v0(q1_readout_frame) - ) - cz_with_gates = Circuit().cz(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) - - if not device.is_available: - try: - assert gate_task.state not in AwsQuantumTask.TERMINAL_STATES - assert pulse_task.state not in AwsQuantumTask.TERMINAL_STATES - finally: - gate_task.cancel() - pulse_task.cancel() - return - - gate_measurements = gate_task.result().measurement_counts - pulse_measurements = pulse_task.result().measurement_counts - - # 1-smoothing to avoid nans for 0 counts - observed = ( - np.array([pulse_measurements.get(state, 1) for state in ("00", "01", "10", "11")]) - / num_shots - ) - expected = ( - np.array([gate_measurements.get(state, 1) for state in ("00", "01", "10", "11")]) - / num_shots - ) - chi_squared = np.sum((observed - expected) ** 2 / expected) - assert chi_squared < 10 # adjust this threshold if test is flaky - - -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) - user_calibration_task = device.run( - bell_circuit, - gate_definitions=user_gate_calibrations.pulse_sequences, - shots=num_shots, - disable_qubit_rewiring=True, - ) - device_calibration_task = device.run( - bell_circuit, - gate_definitions=device.gate_calibrations.pulse_sequences, - shots=num_shots, - disable_qubit_rewiring=True, - ) - - if not device.is_available: - try: - assert user_calibration_task.state not in AwsQuantumTask.TERMINAL_STATES - assert device_calibration_task.state not in AwsQuantumTask.TERMINAL_STATES - finally: - user_calibration_task.cancel() - device_calibration_task.cancel() - return - - assert user_calibration_task.result().measurement_counts - assert device_calibration_task.result().measurement_counts diff --git a/test/integ_tests/test_queue_information.py b/test/integ_tests/test_queue_information.py deleted file mode 100644 index 7e7590a8..00000000 --- a/test/integ_tests/test_queue_information.py +++ /dev/null @@ -1,81 +0,0 @@ -# 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.aws import AwsDevice -from braket.aws.queue_information import ( - HybridJobQueueInfo, - QuantumTaskQueueInfo, - QueueDepthInfo, - QueueType, -) -from braket.circuits import Circuit -from braket.devices import Devices - - -def test_task_queue_position(): - device = AwsDevice(Devices.Amazon.SV1) - - bell = Circuit().h(0).cnot(0, 1) - task = device.run(bell, shots=10) - - # call the queue_position method. - queue_information = task.queue_position() - - # data type validations - assert isinstance(queue_information, QuantumTaskQueueInfo) - assert isinstance(queue_information.queue_type, QueueType) - assert isinstance(queue_information.queue_position, (str, type(None))) - - # assert queue priority - assert queue_information.queue_type in [QueueType.NORMAL, QueueType.PRIORITY] - - # assert message - if queue_information.queue_position is None: - assert queue_information.message is not None - assert isinstance(queue_information.message, (str, type(None))) - else: - assert queue_information.message is None - - -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() - - # data type validations - assert isinstance(queue_information, HybridJobQueueInfo) - - # assert message - assert queue_information.queue_position is None - assert isinstance(queue_information.message, str) - - -def test_queue_depth(): - device = AwsDevice(Devices.Amazon.SV1) - - # call the queue_depth method. - queue_information = device.queue_depth() - - # data type validations - assert isinstance(queue_information, QueueDepthInfo) - assert isinstance(queue_information.quantum_tasks, dict) - assert isinstance(queue_information.jobs, str) - - for key, value in queue_information.quantum_tasks.items(): - assert isinstance(key, QueueType) - assert isinstance(value, str) diff --git a/test/integ_tests/test_reservation_arn.py b/test/integ_tests/test_reservation_arn.py deleted file mode 100644 index f5efd722..00000000 --- a/test/integ_tests/test_reservation_arn.py +++ /dev/null @@ -1,77 +0,0 @@ -# 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 sys - -import pytest -from botocore.exceptions import ClientError - -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 -def reservation_arn(aws_session): - return ( - f"arn:aws:braket:{aws_session.region}" - f":{aws_session.account_id}:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" - ) - - -def test_create_task_via_invalid_reservation_arn_on_qpu(reservation_arn): - circuit = Circuit().h(0) - device = AwsDevice(Devices.IonQ.Harmony) - - with pytest.raises(ClientError, match="Reservation arn is invalid"): - 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): - circuit = Circuit().h(0) - 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) - - 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( - (sys.version_info.major, sys.version_info.minor) != decorator_python_version(), - raises=RuntimeError, - reason="Python version mismatch", -) -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, - reservation_arn=reservation_arn, - ) - def hello_job(): - device = AwsDevice(get_job_device_arn()) - bell = Circuit().h(0).cnot(0, 1) - task = device.run(bell, shots=10) - measurements = task.result().measurements - return measurements - - hello_job() diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py deleted file mode 100644 index 0dd4f3f3..00000000 --- a/test/integ_tests/test_simulator_quantum_task.py +++ /dev/null @@ -1,293 +0,0 @@ -# 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 pytest -from gate_model_device_testing_utils import ( - batch_bell_pair_testing, - bell_pair_openqasm_testing, - get_tol, - many_layers, - multithreaded_bell_pair_testing, - no_result_types_bell_pair_testing, - openqasm_noisy_circuit_1qubit_noise_full_probability, - openqasm_result_types_bell_pair_testing, - qubit_ordering_testing, - result_types_all_selected_testing, - result_types_bell_pair_full_probability_testing, - result_types_bell_pair_marginal_probability_testing, - result_types_hermitian_testing, - result_types_noncommuting_all, - result_types_noncommuting_flipped_targets_testing, - result_types_noncommuting_testing, - result_types_nonzero_shots_bell_pair_testing, - result_types_observable_not_in_instructions, - result_types_tensor_hermitian_hermitian_testing, - result_types_tensor_x_y_testing, - result_types_tensor_y_hermitian_testing, - result_types_tensor_z_h_y_testing, - result_types_tensor_z_hermitian_testing, - result_types_tensor_z_z_testing, - result_types_zero_shots_bell_pair_testing, -) - -from braket.aws import AwsDevice - -# 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 = 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] -ARNS_WITH_SHOTS = [(SV1_ARN, SHOTS), (SV1_ARN, 0), (DM1_ARN, SHOTS), (DM1_ARN, 0)] - - -@pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -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, braket_simulators): - device = braket_simulators[simulator_arn] - qubit_ordering_testing(device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder}) - - -@pytest.mark.parametrize( - "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, braket_simulators -): - device = braket_simulators[simulator_arn] - result_types_zero_shots_bell_pair_testing( - device, - False, - {"shots": 0, "s3_destination_folder": s3_destination_folder}, - include_amplitude, - ) - - -@pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -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, braket_simulators -): - device = braket_simulators[simulator_arn] - result_types_bell_pair_full_probability_testing( - device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} - ) - - -@pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -def test_result_types_bell_pair_marginal_probability( - simulator_arn, aws_session, s3_destination_folder, braket_simulators -): - 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, 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, 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, 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, braket_simulators -): - device = braket_simulators[simulator_arn] - result_types_tensor_z_z_testing( - device, {"shots": shots, "s3_destination_folder": s3_destination_folder} - ) - - -@pytest.mark.parametrize("simulator_arn,shots", ARNS_WITH_SHOTS) -def test_result_types_tensor_hermitian_hermitian( - simulator_arn, shots, aws_session, s3_destination_folder, braket_simulators -): - 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, 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, 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, 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, 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, braket_simulators -): - 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, 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, braket_simulators -): - 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, 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, 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, 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, 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, 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} - ) - - -@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, braket_simulators -): - num_qubits = 10 - circuit = many_layers(num_qubits, num_layers) - device = braket_simulators[simulator_arn] - - tol = get_tol(SHOTS) - result = device.run(circuit, shots=SHOTS, s3_destination_folder=s3_destination_folder).result() - probabilities = result.measurement_probabilities - probability_sum = 0 - for bitstring in probabilities: - assert probabilities[bitstring] >= 0 - probability_sum += probabilities[bitstring] - assert math.isclose(probability_sum, 1, rel_tol=tol["rtol"], abs_tol=tol["atol"]) - assert len(result.measurements) == SHOTS diff --git a/test/integ_tests/test_tensor_network_simulator.py b/test/integ_tests/test_tensor_network_simulator.py deleted file mode 100644 index 5c406ead..00000000 --- a/test/integ_tests/test_tensor_network_simulator.py +++ /dev/null @@ -1,92 +0,0 @@ -# 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 random - -import pytest -from gate_model_device_testing_utils import bell_pair_openqasm_testing, no_result_types_testing - -from braket.aws import AwsDevice -from braket.circuits import Circuit - -SHOTS = 100 -TN1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/tn1" -SIMULATOR_ARNS = [TN1_ARN] - - -@pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -def test_ghz(simulator_arn, aws_session, s3_destination_folder): - num_qubits = 50 - circuit = _ghz(num_qubits) - device = AwsDevice(simulator_arn, aws_session) - no_result_types_testing( - circuit, - device, - {"shots": SHOTS, "s3_destination_folder": s3_destination_folder}, - {"0" * num_qubits: 0.5, "1" * num_qubits: 0.5}, - ) - - -@pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -def test_qft_iqft_h(simulator_arn, aws_session, s3_destination_folder): - num_qubits = 24 - h_qubit = random.randint(0, num_qubits - 1) - circuit = _inverse_qft(_qft(Circuit().h(h_qubit), num_qubits), num_qubits) - device = AwsDevice(simulator_arn, aws_session) - no_result_types_testing( - circuit, - device, - {"shots": SHOTS, "s3_destination_folder": s3_destination_folder}, - {"0" * num_qubits: 0.5, "0" * h_qubit + "1" + "0" * (num_qubits - h_qubit - 1): 0.5}, - ) - - -@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) - bell_pair_openqasm_testing( - device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} - ) - - -def _ghz(num_qubits): - circuit = Circuit() - circuit.h(0) - for qubit in range(num_qubits - 1): - circuit.cnot(qubit, qubit + 1) - return circuit - - -def _qft(circuit, num_qubits): - for i in range(num_qubits): - circuit.h(i) - for j in range(1, num_qubits - i): - circuit.cphaseshift(i + j, i, math.pi / (2**j)) - - for qubit in range(math.floor(num_qubits / 2)): - circuit.swap(qubit, num_qubits - qubit - 1) - - return circuit - - -def _inverse_qft(circuit, num_qubits): - for qubit in range(math.floor(num_qubits / 2)): - circuit.swap(qubit, num_qubits - qubit - 1) - - for i in reversed(range(num_qubits)): - for j in reversed(range(1, num_qubits - i)): - circuit.cphaseshift(i + j, i, -math.pi / (2**j)) - circuit.h(i) - - return circuit diff --git a/test/integ_tests/test_user_agents.py b/test/integ_tests/test_user_agents.py deleted file mode 100644 index 009a9a4a..00000000 --- a/test/integ_tests/test_user_agents.py +++ /dev/null @@ -1,33 +0,0 @@ -from botocore import awsrequest - -import braket._schemas as braket_schemas -import braket._sdk as braket_sdk - - -def test_default_user_agent(aws_session): - braket_agents = [ - f"BraketSdk/{braket_sdk.__version__}", - f"BraketSchemas/{braket_schemas.__version__}", - "NotebookInstance/", - ] - - def assert_in_user_agent(request: awsrequest.AWSPreparedRequest, **kwargs): - user_agent = request.headers.get("User-Agent") - for agent in braket_agents: - 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() - - -def test_add_user_agent(aws_session): - aws_session.add_braket_user_agent("foo/1.0") - - def assert_in_user_agent(request: awsrequest.AWSPreparedRequest, **kwargs): - user_agent = request.headers.get("User-Agent") - 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/experimental/autoqasm/conftest.py b/test/unit_tests/autoqasm/conftest.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/conftest.py rename to test/unit_tests/autoqasm/conftest.py diff --git a/test/unit_tests/braket/experimental/autoqasm/mock_transpiler.py b/test/unit_tests/autoqasm/mock_transpiler.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/mock_transpiler.py rename to test/unit_tests/autoqasm/mock_transpiler.py diff --git a/test/unit_tests/braket/experimental/autoqasm/test_annotations.py b/test/unit_tests/autoqasm/test_annotations.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/test_annotations.py rename to test/unit_tests/autoqasm/test_annotations.py diff --git a/test/unit_tests/braket/experimental/autoqasm/test_api.py b/test/unit_tests/autoqasm/test_api.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/test_api.py rename to test/unit_tests/autoqasm/test_api.py diff --git a/test/unit_tests/braket/experimental/autoqasm/test_converters.py b/test/unit_tests/autoqasm/test_converters.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/test_converters.py rename to test/unit_tests/autoqasm/test_converters.py diff --git a/test/unit_tests/braket/experimental/autoqasm/test_devices.py b/test/unit_tests/autoqasm/test_devices.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/test_devices.py rename to test/unit_tests/autoqasm/test_devices.py diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py b/test/unit_tests/autoqasm/test_gate_calibrations.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/test_gate_calibrations.py rename to test/unit_tests/autoqasm/test_gate_calibrations.py diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py b/test/unit_tests/autoqasm/test_gate_decorator.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/test_gate_decorator.py rename to test/unit_tests/autoqasm/test_gate_decorator.py diff --git a/test/unit_tests/braket/experimental/autoqasm/test_gate_definitions.py b/test/unit_tests/autoqasm/test_gate_definitions.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/test_gate_definitions.py rename to test/unit_tests/autoqasm/test_gate_definitions.py diff --git a/test/unit_tests/braket/experimental/autoqasm/test_operators.py b/test/unit_tests/autoqasm/test_operators.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/test_operators.py rename to test/unit_tests/autoqasm/test_operators.py diff --git a/test/unit_tests/braket/experimental/autoqasm/test_parameters.py b/test/unit_tests/autoqasm/test_parameters.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/test_parameters.py rename to test/unit_tests/autoqasm/test_parameters.py diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pragmas.py b/test/unit_tests/autoqasm/test_pragmas.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/test_pragmas.py rename to test/unit_tests/autoqasm/test_pragmas.py diff --git a/test/unit_tests/braket/experimental/autoqasm/test_program.py b/test/unit_tests/autoqasm/test_program.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/test_program.py rename to test/unit_tests/autoqasm/test_program.py diff --git a/test/unit_tests/braket/experimental/autoqasm/test_pulse.py b/test/unit_tests/autoqasm/test_pulse.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/test_pulse.py rename to test/unit_tests/autoqasm/test_pulse.py diff --git a/test/unit_tests/braket/experimental/autoqasm/test_return.py b/test/unit_tests/autoqasm/test_return.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/test_return.py rename to test/unit_tests/autoqasm/test_return.py diff --git a/test/unit_tests/braket/experimental/autoqasm/test_serialization_config.py b/test/unit_tests/autoqasm/test_serialization_config.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/test_serialization_config.py rename to test/unit_tests/autoqasm/test_serialization_config.py diff --git a/test/unit_tests/braket/experimental/autoqasm/test_transpiler.py b/test/unit_tests/autoqasm/test_transpiler.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/test_transpiler.py rename to test/unit_tests/autoqasm/test_transpiler.py diff --git a/test/unit_tests/braket/experimental/autoqasm/test_types.py b/test/unit_tests/autoqasm/test_types.py similarity index 100% rename from test/unit_tests/braket/experimental/autoqasm/test_types.py rename to test/unit_tests/autoqasm/test_types.py diff --git a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py deleted file mode 100644 index 83178c12..00000000 --- a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py +++ /dev/null @@ -1,240 +0,0 @@ -# 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 decimal import Decimal -from unittest.mock import Mock - -import numpy as np -import pytest - -from braket.ahs.analog_hamiltonian_simulation import ( - AnalogHamiltonianSimulation, - AtomArrangement, - DiscretizationError, - DrivingField, - LocalDetuning, - SiteType, -) -from braket.ahs.atom_arrangement import AtomArrangementItem -from braket.ahs.field import Field -from braket.ahs.pattern import Pattern -from braket.ir.ahs.program_v1 import Program -from braket.timings.time_series import TimeSeries - - -@pytest.fixture -def register(): - return ( - AtomArrangement() - .add((0.0, 0.0)) - .add((0.0, 3.0e-6)) - .add((0.0, 6.0e-6)) - .add((3.0e-6, 0.0)) - .add((3.0e-6, 3.0e-6)) - .add((3.0e-6, 3.0e-6), SiteType.VACANT) - .add((3.0e-6, 6.0e-6), SiteType.VACANT) - ) - - -@pytest.fixture -def driving_field(): - return DrivingField( - TimeSeries().put(0.0, 0.0).put(3.0e-7, 2.51327e7).put(2.7e-6, 2.51327e7).put(3.0e-6, 0.0), - TimeSeries().put(0.0, 0).put(3.0e-6, 0), - TimeSeries() - .put(0.0, -1.25664e8) - .put(3.0e-7, -1.25664e8) - .put(2.7e-6, 1.25664e8) - .put(3.0e-6, 1.25664e8), - ) - - -@pytest.fixture -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]), - ) - ) - - -def test_create(): - mock0 = Mock() - mock1 = Mock() - ahs = AnalogHamiltonianSimulation(register=mock0, hamiltonian=mock1) - assert mock0 == ahs.register - assert mock1 == ahs.hamiltonian - - -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 - assert problem == Program.parse_raw_schema(problem.json()) - - -def test_to_ir_empty(): - hamiltonian = Mock() - hamiltonian.terms = [] - ahs = AnalogHamiltonianSimulation(register=AtomArrangement(), hamiltonian=hamiltonian) - problem = ahs.to_ir() - assert Program.parse_raw(problem.json()) == problem - assert problem == Program.parse_raw_schema(problem.json()) - - -@pytest.mark.xfail(raises=TypeError) -def test_to_ir_invalid_hamiltonian(register): - hamiltonian = Mock() - hamiltonian.terms = [Mock()] - ahs = AnalogHamiltonianSimulation(register=register, hamiltonian=hamiltonian) - ahs.to_ir() - - -@pytest.mark.xfail(raises=DiscretizationError) -def test_invalid_action(): - action = Mock() - action.actionType = "not-a-valid-AHS-action" - device = Mock() - device.properties.action = {"braket.ir.ahs.program": action} - - AnalogHamiltonianSimulation(register=Mock(), hamiltonian=Mock()).discretize(device) - - -@pytest.mark.xfail(raises=DiscretizationError) -def test_invalid_action_name(): - action = Mock() - action.actionType = "braket.ir.ahs.program" - device = Mock() - device.properties.action = {"not-a-valid-AHS-action": action} - - AnalogHamiltonianSimulation(register=Mock(), hamiltonian=Mock()).discretize(device) - - -def test_discretize(register, driving_field, local_detuning): - hamiltonian = driving_field + local_detuning - ahs = AnalogHamiltonianSimulation(register=register, hamiltonian=hamiltonian) - - action = Mock() - action.actionType = "braket.ir.ahs.program" - - device = Mock() - device.properties.action = {"braket.ir.ahs.program": action} - - device.properties.paradigm.lattice.geometry.positionResolution = Decimal("1E-7") - - device.properties.paradigm.rydberg.rydbergGlobal.timeResolution = Decimal("1E-9") - device.properties.paradigm.rydberg.rydbergGlobal.rabiFrequencyResolution = Decimal("400") - device.properties.paradigm.rydberg.rydbergGlobal.detuningResolution = Decimal("0.2") - device.properties.paradigm.rydberg.rydbergGlobal.phaseResolution = Decimal("5E-7") - - device.properties.paradigm.rydberg.rydbergLocal.timeResolution = Decimal("1E-9") - - discretized_ahs = ahs.discretize(device) - discretized_ir = discretized_ahs.to_ir() - discretized_json = json.loads(discretized_ir.json()) - assert discretized_json["setup"]["ahs_register"] == { - "filling": [1, 1, 1, 1, 1, 0, 0], - "sites": [ - ["0E-7", "0E-7"], - ["0E-7", "0.0000030"], - ["0E-7", "0.0000060"], - ["0.0000030", "0E-7"], - ["0.0000030", "0.0000030"], - ["0.0000030", "0.0000030"], - ["0.0000030", "0.0000060"], - ], - } - assert discretized_json["hamiltonian"]["drivingFields"][0]["amplitude"] == { - "pattern": "uniform", - "time_series": { - "times": ["0E-9", "3.00E-7", "0.000002700", "0.000003000"], - "values": ["0", "25132800", "25132800", "0"], - }, - } - assert discretized_json["hamiltonian"]["drivingFields"][0]["phase"] == { - "pattern": "uniform", - "time_series": {"times": ["0E-9", "0.000003000"], "values": ["0E-7", "0E-7"]}, - } - assert discretized_json["hamiltonian"]["drivingFields"][0]["detuning"] == { - "pattern": "uniform", - "time_series": { - "times": ["0E-9", "3.00E-7", "0.000002700", "0.000003000"], - "values": ["-125664000.0", "-125664000.0", "125664000.0", "125664000.0"], - }, - } - local_detuning = discretized_json["hamiltonian"]["localDetuning"][0]["magnitude"] - assert local_detuning == { - "pattern": ["0.5", "1", "0.5", "0.5", "0.5", "0.5"], - "time_series": { - "times": ["0E-9", "0.000003000"], - "values": ["-125664000", "125664000"], - }, - } - - -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], - ] - ) - register = AtomArrangement() - for site in sites: - register.add(site) - - ahs = AnalogHamiltonianSimulation(register=register, hamiltonian=hamiltonian) - sites_in_ir = ahs.to_ir().setup.ahs_register.sites - expected_sites_in_ir = [ - [Decimal("0.0"), Decimal("0.0")], - [Decimal("0.0"), Decimal("1e-6")], - [Decimal("1e-6"), Decimal("2e-6")], - ] - - assert sites_in_ir == expected_sites_in_ir - - -@pytest.mark.xfail(raises=ValueError) -def test_site_validation_wrong_length(): - register = AtomArrangement() - register.add(np.array([0.0, 1e-6, -1e-6])) - - -@pytest.mark.xfail(raises=TypeError) -def test_site_validation_non_number(): - register = AtomArrangement() - register.add( - [ - "not-a-number", - [ - "also-not-a-number", - ], - ] - ) - - -@pytest.mark.xfail(raises=TypeError) -def test_site_validation_not_a_tuple(): - AtomArrangementItem(None, SiteType.FILLED) - - -@pytest.mark.xfail(raises=ValueError) -def test_site_validation_invalid_site_type(): - register = AtomArrangement() - register.add([0.0, 0.0], "not-a-valid-site-type") diff --git a/test/unit_tests/braket/ahs/test_atom_arrangement.py b/test/unit_tests/braket/ahs/test_atom_arrangement.py deleted file mode 100644 index 06a92616..00000000 --- a/test/unit_tests/braket/ahs/test_atom_arrangement.py +++ /dev/null @@ -1,113 +0,0 @@ -# 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 -from unittest.mock import Mock - -import pytest - -from braket.ahs.analog_hamiltonian_simulation import DiscretizationError -from braket.ahs.atom_arrangement import AtomArrangement, SiteType - - -@pytest.fixture -def default_atom_arrangement(): - atom_arrangement = ( - AtomArrangement() - .add((0, 3.1e-6), SiteType.FILLED) - .add((0, 5.99e-6)) - .add((3.001e-6, 0)) - .add((3e-6, 3e-6)) - .add((-3.01e-6, 6.5e-6)) - ) - return atom_arrangement - - -def test_add_chaining(): - atom_arrangement = ( - AtomArrangement() - .add(coordinate=(0, 0), site_type=SiteType.FILLED) - .add((0, 3), SiteType.FILLED) - .add((0, 6)) - .add((3, 0)) - .add((3, 3)) - .add((3, 6)) - .add((3, 0)) - ) - assert len(atom_arrangement) == 7 - - -def test_iteration(): - values = [(0, 0), (0.1, 0.2), (Decimal(0.3), Decimal(0.4))] - atom_arrangement = AtomArrangement() - for value in values: - atom_arrangement.add(value) - returned_values = [site.coordinate for site in atom_arrangement] - assert values == returned_values - - -def test_coordinate_list(): - values = [(0, 0), (0.1, 0.2), (Decimal(0.3), Decimal(0.4))] - atom_arrangement = AtomArrangement() - for value in values: - atom_arrangement.add(value) - for coord_index in range(2): - coords = atom_arrangement.coordinate_list(coord_index) - assert coords == [value[coord_index] for value in values] - - -@pytest.mark.parametrize( - "position_res, expected_x, expected_y", - [ - # default x: [0, 0, 3.001e-6, 3e-6, -3.01e-6] - # default y: [3.1e-6, 5.99e-6, 0, 3e-6, 6.5e-6] - ( - Decimal("1e-6"), - [Decimal("0"), Decimal("0"), Decimal("3e-6"), Decimal("3e-6"), Decimal("-3e-6")], - [Decimal("3e-6"), Decimal("6e-6"), Decimal("0"), Decimal("3e-6"), Decimal("6e-6")], - ), - ( - Decimal("1e-7"), - [Decimal("0"), Decimal("0"), Decimal("3e-6"), Decimal("3e-6"), Decimal("-3e-6")], - [Decimal("3.1e-6"), Decimal("6e-6"), Decimal("0"), Decimal("3e-6"), Decimal("6.5e-6")], - ), - ( - Decimal("2e-7"), - [Decimal("0"), Decimal("0"), Decimal("3e-6"), Decimal("3e-6"), Decimal("-3e-6")], - [Decimal("3e-6"), Decimal("6e-6"), Decimal("0"), Decimal("3e-6"), Decimal("6.4e-6")], - ), - ( - Decimal("1e-8"), - [Decimal("0"), Decimal("0"), Decimal("3e-6"), Decimal("3e-6"), Decimal("-3.01e-6")], - [ - Decimal("3.1e-6"), - Decimal("5.99e-6"), - Decimal("0"), - Decimal("3e-6"), - Decimal("6.5e-6"), - ], - ), - ], -) -def test_discretize(default_atom_arrangement, position_res, expected_x, expected_y): - properties = Mock() - properties.lattice.geometry.positionResolution = position_res - actual = default_atom_arrangement.discretize(properties) - assert expected_x == actual.coordinate_list(0) - assert expected_y == actual.coordinate_list(1) - - -@pytest.mark.xfail(raises=DiscretizationError) -def test_invalid_discretization_properties(default_atom_arrangement): - properties = "not-a-valid-discretization-property" - default_atom_arrangement.discretize(properties) diff --git a/test/unit_tests/braket/ahs/test_driving_field.py b/test/unit_tests/braket/ahs/test_driving_field.py deleted file mode 100644 index 1597c765..00000000 --- a/test/unit_tests/braket/ahs/test_driving_field.py +++ /dev/null @@ -1,175 +0,0 @@ -# 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 Mock - -import numpy as np -import pytest - -from braket.ahs.driving_field import DrivingField -from braket.ahs.field import Field -from braket.ahs.hamiltonian import Hamiltonian -from braket.timings.time_series import StitchBoundaryCondition, TimeSeries - - -@pytest.fixture -def default_driving_field(): - return DrivingField(Mock(spec=Field), Mock(spec=Field), Mock(spec=Field)) - - -def test_create(): - mock0 = Mock(spec=Field) - mock1 = Mock(spec=Field) - mock2 = Mock(spec=Field) - field = DrivingField(amplitude=mock0, phase=mock1, detuning=mock2) - assert mock0 == field.amplitude - assert mock1 == field.phase - assert mock2 == field.detuning - - -def test_create_non_field(): - mock0 = Mock(spec=TimeSeries) - mock1 = Mock(spec=TimeSeries) - mock2 = Mock(spec=TimeSeries) - field = DrivingField(amplitude=mock0, phase=mock1, detuning=mock2) - assert mock0 == field.amplitude.time_series - assert mock1 == field.phase.time_series - assert mock2 == field.detuning.time_series - - -def test_add_hamiltonian(default_driving_field): - expected = [default_driving_field, Mock(), Mock(), Mock()] - result = expected[0] + Hamiltonian([expected[1], expected[2], expected[3]]) - assert result.terms == expected - - -def test_add_to_hamiltonian(default_driving_field): - expected = [Mock(), Mock(), Mock(), default_driving_field] - result = Hamiltonian([expected[0], expected[1], expected[2]]) + expected[3] - assert result.terms == expected - - -def test_add_to_other(): - field0 = DrivingField(Mock(spec=Field), Mock(spec=Field), Mock(spec=Field)) - field1 = DrivingField(Mock(spec=Field), Mock(spec=Field), Mock(spec=Field)) - result = field0 + field1 - assert type(result) is Hamiltonian - assert result.terms == [field0, field1] - - -def test_add_to_self(default_driving_field): - result = default_driving_field + default_driving_field - assert type(result) is Hamiltonian - assert result.terms == [default_driving_field, default_driving_field] - - -def test_iadd_to_other(default_driving_field): - expected = [Mock(), Mock(), Mock(), default_driving_field] - other = Hamiltonian([expected[0], expected[1], expected[2]]) - other += expected[3] - assert other.terms == expected - - -def test_discretize(): - amplitude_mock = Mock(spec=Field) - amplitude_mock.discretize.return_value = Mock(spec=Field) - phase_mock = Mock(spec=Field) - phase_mock.discretize.return_value = Mock(spec=Field) - detuning_mock = Mock(spec=Field) - detuning_mock.discretize.return_value = Mock(spec=Field) - mock_properties = Mock() - field = DrivingField(amplitude=amplitude_mock, phase=phase_mock, detuning=detuning_mock) - discretized_field = field.discretize(mock_properties) - amplitude_mock.discretize.assert_called_with( - time_resolution=mock_properties.rydberg.rydbergGlobal.timeResolution, - value_resolution=mock_properties.rydberg.rydbergGlobal.rabiFrequencyResolution, - ) - phase_mock.discretize.assert_called_with( - time_resolution=mock_properties.rydberg.rydbergGlobal.timeResolution, - value_resolution=mock_properties.rydberg.rydbergGlobal.phaseResolution, - ) - detuning_mock.discretize.assert_called_with( - time_resolution=mock_properties.rydberg.rydbergGlobal.timeResolution, - value_resolution=mock_properties.rydberg.rydbergGlobal.detuningResolution, - ) - assert field is not discretized_field - assert discretized_field.amplitude == amplitude_mock.discretize.return_value - assert discretized_field.phase == phase_mock.discretize.return_value - assert discretized_field.detuning == detuning_mock.discretize.return_value - - -def test_from_lists(): - times = [0, 0.1, 0.2] - amplitudes = [0.5, 0.8, 0.9] - detunings = [0.3, 0.7, 0.6] - phases = [0.2, 0.4, 0.6] - - dr_field = DrivingField.from_lists(times, amplitudes, detunings, phases) - assert dr_field.amplitude.time_series.values() == amplitudes - assert dr_field.detuning.time_series.values() == detunings - assert dr_field.phase.time_series.values() == phases - - assert dr_field.amplitude.time_series.times() == times - assert dr_field.detuning.time_series.times() == times - assert dr_field.phase.time_series.times() == times - - -@pytest.mark.xfail(raises=ValueError) -def test_from_lists_not_eq_length(): - times = [0, 0.1] - amplitudes = [0.5, 0.8, 0.9] - detunings = [0.3, 0.7, 0.6] - phases = [0.2, 0.4, 0.6] - - DrivingField.from_lists(times, amplitudes, detunings, phases) - - -def test_stitch(): - dr_field_1 = DrivingField.from_lists( - times=[0, 0.1, 0.2], - amplitudes=[1, 2, 3.5], - detunings=[1.2, 3.4, 5.6], - phases=[2.1, 4.2, 1.3], - ) - dr_field_2 = DrivingField.from_lists( - times=[0.4, 0.5, 0.6], - amplitudes=[0.11, 0.22, 0.35], - detunings=[1.12, 3.14, 5.16], - phases=[2.11, 4.12, 1.13], - ) - - new_dr = dr_field_1.stitch(dr_field_2, boundary=StitchBoundaryCondition.RIGHT) - new_times = new_dr.amplitude.time_series.times() - - amplitudes_1 = dr_field_1.amplitude.time_series.values() - amplitudes_2 = dr_field_2.amplitude.time_series.values() - new_amplitudes = new_dr.amplitude.time_series.values() - - detunings_1 = dr_field_1.detuning.time_series.values() - detunings_2 = dr_field_2.detuning.time_series.values() - new_detunings = new_dr.detuning.time_series.values() - - phases_1 = dr_field_1.phase.time_series.values() - phases_2 = dr_field_2.phase.time_series.values() - new_phases = new_dr.phase.time_series.values() - - expected_times = [0, 0.1, 0.2, 0.3, 0.4] - np.testing.assert_almost_equal(new_times, expected_times) - np.testing.assert_almost_equal(new_amplitudes, amplitudes_1[:-1] + amplitudes_2) - np.testing.assert_almost_equal(new_detunings, detunings_1[:-1] + detunings_2) - np.testing.assert_almost_equal(new_phases, phases_1[:-1] + phases_2) - - -@pytest.mark.xfail(raises=ValueError) -def test_iadd_to_itself(default_driving_field): - default_driving_field += Hamiltonian(Mock()) diff --git a/test/unit_tests/braket/ahs/test_field.py b/test/unit_tests/braket/ahs/test_field.py deleted file mode 100644 index 2ff6714c..00000000 --- a/test/unit_tests/braket/ahs/test_field.py +++ /dev/null @@ -1,100 +0,0 @@ -# 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 -from unittest.mock import Mock - -import pytest - -from braket.ahs.field import Field -from braket.ahs.pattern import Pattern -from braket.timings.time_series import TimeSeries - - -@pytest.fixture -def default_time_series(): - default_values = [(2700, 25.1327), (300, 25.1327), (600, 15.1327), (Decimal(0.3), Decimal(0.4))] - time_series = TimeSeries() - for value in default_values: - time_series.put(value[0], value[1]) - return time_series - - -@pytest.fixture -def default_pattern(): - return Pattern(series=[0, 0.1, 1, 0.5, 0.2, 0.001, 1e-10]) - - -@pytest.fixture -def default_field(default_time_series, default_pattern): - return Field(time_series=default_time_series, pattern=default_pattern) - - -@pytest.fixture -def default_uniform_field(default_time_series): - return Field(time_series=default_time_series) - - -def test_create(): - mock0 = Mock() - mock1 = Mock() - field = Field(time_series=mock0, pattern=mock1) - assert mock0 == field.time_series - assert mock1 == field.pattern - - -@pytest.mark.parametrize( - "time_res, value_res, pattern_res", - [ - (Decimal("0.1"), Decimal("10"), Decimal("0.5")), - (Decimal("10"), Decimal("20"), Decimal("0.1")), - (Decimal("100"), Decimal("0.1"), Decimal("1")), - ], -) -def test_discretize( - default_time_series, default_pattern, default_field, time_res, value_res, pattern_res -): - expected = Field( - time_series=default_time_series.discretize(time_res, value_res), - pattern=default_pattern.discretize(pattern_res), - ) - actual = default_field.discretize(time_res, value_res, pattern_res) - assert 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("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")), - ], -) -def test_uniform_field( - default_time_series, default_uniform_field, time_res, value_res, pattern_res -): - expected = Field(time_series=default_time_series.discretize(time_res, value_res)) - actual = default_uniform_field.discretize(time_res, value_res, pattern_res) - assert ( - (expected.pattern is None) and (actual.pattern is None) - ) 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() diff --git a/test/unit_tests/braket/ahs/test_hamiltonian.py b/test/unit_tests/braket/ahs/test_hamiltonian.py deleted file mode 100644 index e7a3b308..00000000 --- a/test/unit_tests/braket/ahs/test_hamiltonian.py +++ /dev/null @@ -1,45 +0,0 @@ -# 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 Mock - -from braket.ahs.hamiltonian import Hamiltonian - - -def test_create(): - hamiltonian = Hamiltonian() - assert hamiltonian.terms == [] - - -def test_add(): - mocks = [Mock(), Mock(), Mock(), Mock()] - result = Hamiltonian([mocks[0], mocks[1], mocks[2]]) + Hamiltonian([mocks[3]]) - assert result.terms == mocks - - -def test_iadd(): - mocks = [Mock(), Mock(), Mock(), Mock()] - hamiltonian = Hamiltonian([mocks[0]]) - hamiltonian += Hamiltonian([mocks[1], mocks[2], mocks[3]]) - assert hamiltonian.terms == mocks - - -def test_discretize(): - mocks = [Mock(), Mock(), Mock()] - mock_properties = Mock() - hamiltonian = Hamiltonian(mocks) - discretized_hamiltonian = hamiltonian.discretize(mock_properties) - for index in range(len(mocks)): - mocks[index].discretize.assert_called_with(mock_properties) - assert discretized_hamiltonian.terms[index] == mocks[index].discretize.return_value - assert hamiltonian is not discretized_hamiltonian diff --git a/test/unit_tests/braket/ahs/test_local_detuning.py b/test/unit_tests/braket/ahs/test_local_detuning.py deleted file mode 100644 index 8768dce6..00000000 --- a/test/unit_tests/braket/ahs/test_local_detuning.py +++ /dev/null @@ -1,139 +0,0 @@ -# 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 Mock - -import pytest - -from braket.ahs.hamiltonian import Hamiltonian -from braket.ahs.local_detuning import LocalDetuning -from braket.timings.time_series import StitchBoundaryCondition - - -@pytest.fixture -def default_local_detuning(): - return LocalDetuning(Mock()) - - -def test_create(): - mock0 = Mock() - field = LocalDetuning(magnitude=mock0) - assert mock0 == field.magnitude - - -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_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 = LocalDetuning(Mock()) - field1 = LocalDetuning(Mock()) - result = field0 + field1 - assert type(result) is Hamiltonian - assert result.terms == [field0, field1] - - -def test_add_to_self(default_local_detuning): - result = default_local_detuning + default_local_detuning - assert type(result) is Hamiltonian - assert result.terms == [default_local_detuning, default_local_detuning] - - -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 - - -def test_from_lists(): - times = [0, 0.1, 0.2, 0.3] - glob_amplitude = [0.5, 0.8, 0.9, 1.0] - pattern = [0.3, 0.7, 0.6, -0.5, 0, 1.6] - - 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 - - assert sh_field.magnitude.time_series.times() == times - - -@pytest.mark.xfail(raises=ValueError) -def test_from_lists_not_eq_length(): - times = [0, 0.1, 0.2] - glob_amplitude = [0.5, 0.8, 0.9, 1.0] - pattern = [0.3, 0.7, 0.6, -0.5, 0, 1.6] - - LocalDetuning.from_lists(times, glob_amplitude, pattern) - - -def test_stitch(): - times_1 = [0, 0.1, 0.2, 0.3] - glob_amplitude_1 = [0.5, 0.8, 0.9, 1.0] - pattern_1 = [0.3, 0.7, 0.6, -0.5, 0, 1.6] - - times_2 = [0, 0.1, 0.2, 0.3] - glob_amplitude_2 = [0.5, 0.8, 0.9, 1.0] - pattern_2 = pattern_1 - - 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) - - expected_times = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6] - expected_amplitude = glob_amplitude_1 + glob_amplitude_2[1:] - assert new_sh_field.magnitude.time_series.times() == expected_times - assert new_sh_field.magnitude.time_series.values() == expected_amplitude - assert new_sh_field.magnitude.pattern == sh_field_1.magnitude.pattern - - -@pytest.mark.xfail(raises=ValueError) -def test_stitch_not_eq_pattern(): - times_1 = [0, 0.1, 0.2, 0.3] - glob_amplitude_1 = [0.5, 0.8, 0.9, 1.0] - pattern_1 = [0.3, 0.7, 0.6, -0.5, 0, 1.6] - - times_2 = [0.4, 0.5, 0.6, 0.7] - 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 = 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) - - -def test_discretize(): - magnitude_mock = Mock() - mock_properties = 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, - ) - assert field is not discretized_field - assert discretized_field.magnitude == magnitude_mock.discretize.return_value - - -@pytest.mark.xfail(raises=ValueError) -def test_iadd_to_itself(default_local_detuning): - default_local_detuning += Hamiltonian(Mock()) diff --git a/test/unit_tests/braket/ahs/test_pattern.py b/test/unit_tests/braket/ahs/test_pattern.py deleted file mode 100644 index 920f2cc2..00000000 --- a/test/unit_tests/braket/ahs/test_pattern.py +++ /dev/null @@ -1,114 +0,0 @@ -# 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.pattern import Pattern - - -@pytest.fixture -def default_values(): - return [ - Decimal(0), - Decimal("0.1"), - Decimal(1), - Decimal("0.5"), - Decimal("0.2"), - Decimal("0.001"), - Decimal("1e-10"), - ] - - -@pytest.fixture -def default_pattern(default_values): - return Pattern(series=default_values) - - -def test_create(): - expected_series = [1, 2, Decimal(3.1)] - pattern = Pattern(expected_series) - assert expected_series == pattern.series - - -@pytest.mark.parametrize( - "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"), - [ - Decimal("0"), - Decimal("0.1"), - Decimal("1"), - Decimal("0.5"), - Decimal("0.2"), - Decimal("0.001"), - Decimal("0"), - ], - ), - ( - Decimal("0.1"), - [ - Decimal("0"), - Decimal("0.1"), - Decimal("1"), - Decimal("0.5"), - Decimal("0.2"), - Decimal("0"), - Decimal("0"), - ], - ), - ( - Decimal("0.5"), - [ - Decimal("0"), - Decimal("0"), - Decimal("1"), - Decimal("0.5"), - Decimal("0"), - Decimal("0"), - Decimal("0"), - ], - ), - ( - Decimal("0.9"), - [ - Decimal("0"), - Decimal("0"), - Decimal("0.9"), - Decimal("0.9"), - Decimal("0"), - Decimal("0"), - Decimal("0"), - ], - ), - ], -) -def test_discretize(default_pattern, res, expected_series): - print(default_pattern.series) - print(res, default_pattern.discretize(res).series) - assert expected_series == default_pattern.discretize(res).series diff --git a/test/unit_tests/braket/annealing/test_problem.py b/test/unit_tests/braket/annealing/test_problem.py deleted file mode 100644 index a80863af..00000000 --- a/test/unit_tests/braket/annealing/test_problem.py +++ /dev/null @@ -1,53 +0,0 @@ -# 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 braket.ir.annealing as ir -from braket.annealing.problem import Problem, ProblemType - - -def test_creation(): - problem = Problem(ProblemType.ISING, linear={1: 3.14}, quadratic={(1, 2): 10.08}) - assert problem.problem_type == ProblemType.ISING - assert problem.linear == {1: 3.14} - assert problem.quadratic == {(1, 2): 10.08} - - -def test_add_linear_term(): - problem = Problem(ProblemType.QUBO) - problem.add_linear_term(1, 3.14) - assert problem.linear == {1: 3.14} - - -def test_add_linear_terms(): - problem = Problem(ProblemType.QUBO) - problem.add_linear_terms({1: 3.14}) - assert problem.linear == {1: 3.14} - - -def test_add_quadratic_term(): - problem = Problem(ProblemType.QUBO) - problem.add_quadratic_term((1, 2), 10.08) - assert problem.quadratic == {(1, 2): 10.08} - - -def test_add_quadratic_terms(): - problem = Problem(ProblemType.QUBO) - problem.add_quadratic_terms({(1, 2): 10.08}) - assert problem.quadratic == {(1, 2): 10.08} - - -def test__to_ir(): - problem = Problem(ProblemType.QUBO).add_linear_term(1, 3.14).add_quadratic_term((1, 2), 10.08) - assert problem.to_ir() == ir.Problem( - type=ir.ProblemType.QUBO, linear={1: 3.14}, quadratic={"1,2": 10.08} - ) diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py deleted file mode 100644 index aaca559f..00000000 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ /dev/null @@ -1,370 +0,0 @@ -# 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 - -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" -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" - -RIGETTI_REGION = "us-west-1" - - -class MockS3: - MOCK_TASK_METADATA = { - "braketSchemaHeader": {"name": "braket.task_result.task_metadata", "version": "1"}, - "id": "task_arn", - "shots": 100, - "deviceId": "default", - } - - 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"}], - }, - }, - } - ) - - 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, - }, - { - "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"}], - }, - }, - } - ) - - 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}, - }, - "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", - }, - "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]", - }, - "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], - }, - }, - { - "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( - aws_quantum_task_mock, - device, - default_s3_folder, - default_shots, - default_poll_timeout, - default_poll_interval, - circuit, - s3_destination_folder, # Treated as positional arg - shots, # Treated as positional arg - poll_timeout_seconds, # Treated as positional arg - poll_interval_seconds, # Treated as positional arg - inputs, # Treated as positional arg - gate_definitions, # Treated as positional arg - reservation_arn, # Treated as positional arg - extra_args, - extra_kwargs, -): - task_mock = Mock() - aws_quantum_task_mock.return_value = task_mock - - run_args = [] - if s3_destination_folder is not None: - run_args.append(s3_destination_folder) - if shots is not None: - run_args.append(shots) - if poll_timeout_seconds is not None: - run_args.append(poll_timeout_seconds) - if poll_interval_seconds is not None: - run_args.append(poll_interval_seconds) - if inputs is not None: - run_args.append(inputs) - if gate_definitions is not None: - run_args.append(gate_definitions) - run_args += extra_args or [] - run_kwargs = extra_kwargs or {} - if reservation_arn: - run_kwargs.update({"reservation_arn": reservation_arn}) - - task = device.run(circuit, *run_args, **run_kwargs) - assert task == task_mock - - create_args, create_kwargs = _create_task_args_and_kwargs( - default_s3_folder, - default_shots, - default_poll_timeout, - default_poll_interval, - s3_destination_folder, - shots, - poll_timeout_seconds, - poll_interval_seconds, - inputs, - gate_definitions, - reservation_arn, - extra_args, - extra_kwargs, - ) - - aws_quantum_task_mock.assert_called_with( - device._aws_session, device.arn, circuit, *create_args, **create_kwargs - ) - - -def run_batch_and_assert( - aws_quantum_task_mock, - aws_session_mock, - device, - default_s3_folder, - default_shots, - default_poll_timeout, - default_poll_interval, - circuits, - s3_destination_folder, - shots, - max_parallel, - max_connections, - poll_timeout_seconds, - poll_interval_seconds, - inputs, - gate_definitions, - reservation_arn, - extra_args, - extra_kwargs, -): - task_mock = Mock() - task_mock.state.return_value = "COMPLETED" - aws_quantum_task_mock.return_value = task_mock - new_session_mock = Mock() - aws_session_mock.return_value = new_session_mock - - run_args = [] - if s3_destination_folder is not None: - run_args.append(s3_destination_folder) - if shots is not None: - run_args.append(shots) - if max_parallel is not None: - run_args.append(max_parallel) - if max_connections is not None: - run_args.append(max_connections) - if poll_timeout_seconds is not None: - run_args.append(poll_timeout_seconds) - if poll_interval_seconds is not None: - run_args.append(poll_interval_seconds) - if inputs is not None: - run_args.append(inputs) - if gate_definitions is not None: - run_args.append(gate_definitions) - run_args += extra_args or [] - run_kwargs = extra_kwargs or {} - if reservation_arn: - run_kwargs.update({"reservation_arn": reservation_arn}) - - batch = device.run_batch(circuits, *run_args, **run_kwargs) - assert batch.tasks == [task_mock for _ in range(len(circuits))] - - create_args, create_kwargs = _create_task_args_and_kwargs( - default_s3_folder, - default_shots, - default_poll_timeout, - default_poll_interval, - s3_destination_folder, - shots, - poll_timeout_seconds, - poll_interval_seconds, - inputs, - gate_definitions, - reservation_arn, - extra_args, - extra_kwargs, - ) - - max_pool_connections = max_connections or AwsQuantumTaskBatch.MAX_CONNECTIONS_DEFAULT - - # aws_session_mock.call_args.kwargs syntax is newer than Python 3.7 - assert aws_session_mock.call_args[1]["config"].max_pool_connections == max_pool_connections - aws_quantum_task_mock.assert_any_call( - new_session_mock, device.arn, circuits[0], *create_args, **create_kwargs - ) - aws_quantum_task_mock.assert_any_call( - new_session_mock, device.arn, circuits[1], *create_args, **create_kwargs - ) - - -def _create_task_args_and_kwargs( - default_s3_folder, - default_shots, - default_poll_timeout, - default_poll_interval, - s3_folder, - shots, - poll_timeout_seconds, - poll_interval_seconds, - inputs, - gate_definitions, - reservation_arn, - extra_args, - extra_kwargs, -): - create_args = [ - 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 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, - } - ) - 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 deleted file mode 100644 index 7f9e6179..00000000 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ /dev/null @@ -1,2231 +0,0 @@ -# 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 io -import json -import os -import textwrap -from datetime import datetime -from unittest.mock import Mock, PropertyMock, patch -from urllib.error import URLError - -import networkx as nx -import pytest -from botocore.exceptions import ClientError -from common_test_utils import ( - DM1_ARN, - DWAVE_ARN, - IONQ_ARN, - OQC_ARN, - RIGETTI_ARN, - RIGETTI_REGION, - SV1_ARN, - TN1_ARN, - run_and_assert, - run_batch_and_assert, -) -from jsonschema import validate - -from braket.aws import AwsDevice, AwsDeviceType, AwsQuantumTask -from braket.aws.queue_information import QueueDepthInfo, QueueType -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 -from braket.device_schema.simulators import GateModelSimulatorDeviceCapabilities -from braket.ir.openqasm import Program as OpenQasmProgram -from braket.pulse import DragGaussianWaveform, Frame, Port, PulseSequence - -MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_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.jaqcd.program": { - "actionType": "braket.ir.jaqcd.program", - "version": ["1"], - "supportedOperations": ["H"], - } - }, - "paradigm": { - "qubitCount": 30, - "nativeGateSet": ["ccnot", "cy"], - "connectivity": {"fullyConnected": False, "connectivityGraph": {"1": ["2", "3"]}}, - }, - "deviceParameters": {}, -} - -MOCK_GATE_MODEL_QPU_CAPABILITIES_1 = RigettiDeviceCapabilities.parse_obj( - MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_1 -) - -MOCK_gate_calibrations_JSON = { - "gates": { - "0": { - "cphaseshift": [ - { - "name": "cphaseshift", - "qubits": ["0", "1"], - "arguments": ["-1.5707963267948966"], - "calibrations": [ - { - "name": "barrier", - "arguments": [{"name": "qubit", "value": "0", "type": "string"}], - }, - { - "name": "play", - "arguments": [ - {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"}, - { - "name": "waveform", - "value": "wf_drag_gaussian_0", - "type": "waveform", - }, - ], - }, - { - "name": "barrier", - "arguments": [ - {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"} - ], - }, - { - "name": "barrier", - "arguments": None, - }, - { - "name": "delay", - "arguments": [ - {"name": "duration", "value": 3e-07, "type": "float"}, - {"name": "qubit", "value": "0", "type": "string"}, - {"name": "qubit", "value": "1", "type": "string"}, - ], - }, - { - "name": "delay", - "arguments": [ - {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"}, - {"name": "duration", "value": 3e-07, "type": "float"}, - ], - }, - { - "name": "shift_phase", - "arguments": [ - {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"}, - {"name": "phase", "value": 3e-07, "type": "float"}, - ], - }, - { - "name": "shift_frequency", - "arguments": [ - {"name": "frequency", "value": "theta", "type": "expr"}, - {"name": "frame", "value": "q0_q1_cphase_frame", "type": "frame"}, - {"name": "extra", "value": "q0_q1_cphase_frame", "type": "string"}, - ], - }, - ], - } - ], - "rx": [ - { - "gateName": "rx", - "gateId": "rz_1", - "qubits": "0", - "arguments": ["theta"], - "calibrations": [ - {"name": "barrier", "arguments": None}, - ], - } - ], - }, - "0_1": { - "cz": [ - { - "gateName": "cz", - "gateId": "cz_0_1", - "qubits": ["1", "0"], - "arguments": [], - "calibrations": [ - {"name": "barrier", "arguments": None}, - ], - } - ], - "rx_12": [], - }, - }, - "waveforms": { - "q0_q1_cz_CZ": { - "waveformId": "q0_q1_cz_CZ", - "amplitudes": [[0.0, 0.0], [0.0, 0.0]], - }, - "wf_drag_gaussian_0": { - "waveformId": "wf_drag_gaussian_0", - "name": "drag_gaussian", - "arguments": [ - {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, - {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, - {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, - {"name": "beta", "value": 7.494904522022295e-10, "type": "float"}, - ], - }, - "wf_gaussian_0": { - "waveformId": "wf_gaussian_0", - "name": "gaussian", - "arguments": [ - {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, - {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, - {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, - ], - }, - "wf_constant": { - "waveformId": "wf_constant", - "name": "constant", - "arguments": [ - {"name": "length", "value": 2, "type": "float"}, - {"name": "iq", "value": 0.23, "type": "complex"}, - ], - }, - }, -} - - -def test_mock_rigetti_schema_1(): - validate(MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_1, RigettiDeviceCapabilities.schema()) - - -MOCK_GATE_MODEL_QPU_1 = { - "deviceName": "Aspen-10", - "deviceType": "QPU", - "providerName": "Rigetti", - "deviceStatus": "OFFLINE", - "deviceCapabilities": MOCK_GATE_MODEL_QPU_CAPABILITIES_1.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)"}, - ], -} - -MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_2 = { - "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.jaqcd.program": { - "actionType": "braket.ir.jaqcd.program", - "version": ["1"], - "supportedOperations": ["H"], - } - }, - "paradigm": { - "qubitCount": 30, - "nativeGateSet": ["ccnot", "cy"], - "connectivity": {"fullyConnected": True, "connectivityGraph": {}}, - }, - "deviceParameters": {}, -} - -MOCK_GATE_MODEL_QPU_CAPABILITIES_2 = RigettiDeviceCapabilities.parse_obj( - MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_2 -) - - -def test_mock_rigetti_schema_2(): - validate(MOCK_GATE_MODEL_QPU_CAPABILITIES_JSON_2, RigettiDeviceCapabilities.schema()) - - -MOCK_GATE_MODEL_QPU_2 = { - "deviceName": "Blah", - "deviceType": "QPU", - "providerName": "blahhhh", - "deviceStatus": "OFFLINE", - "deviceCapabilities": MOCK_GATE_MODEL_QPU_CAPABILITIES_2.json(), -} - -MOCK_GATE_MODEL_QPU_3 = { - "deviceName": "Lucy", - "deviceType": "QPU", - "providerName": "OQC", - "deviceStatus": "OFFLINE", - "deviceCapabilities": MOCK_GATE_MODEL_QPU_CAPABILITIES_1.json(), -} - -MOCK_DWAVE_QPU_CAPABILITIES_JSON = { - "braketSchemaHeader": { - "name": "braket.device_schema.dwave.dwave_device_capabilities", - "version": "1", - }, - "provider": { - "annealingOffsetStep": 1.45, - "annealingOffsetStepPhi0": 1.45, - "annealingOffsetRanges": [[1.45, 1.45], [1.45, 1.45]], - "annealingDurationRange": [1, 2, 3], - "couplers": [[1, 2], [2, 3]], - "defaultAnnealingDuration": 1, - "defaultProgrammingThermalizationDuration": 1, - "defaultReadoutThermalizationDuration": 1, - "extendedJRange": [1, 2, 3], - "hGainScheduleRange": [1, 2, 3], - "hRange": [1, 2, 3], - "jRange": [1, 2, 3], - "maximumAnnealingSchedulePoints": 1, - "maximumHGainSchedulePoints": 1, - "perQubitCouplingRange": [1, 2, 3], - "programmingThermalizationDurationRange": [1, 2, 3], - "qubits": [1, 2, 3], - "qubitCount": 1, - "quotaConversionRate": 1, - "readoutThermalizationDurationRange": [1, 2, 3], - "taskRunDurationRange": [1, 2, 3], - "topology": {}, - }, - "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"], - } - }, - "deviceParameters": {}, -} - -MOCK_DWAVE_QPU_CAPABILITIES = DwaveDeviceCapabilities.parse_obj(MOCK_DWAVE_QPU_CAPABILITIES_JSON) - - -def test_d_wave_schema(): - validate(MOCK_DWAVE_QPU_CAPABILITIES_JSON, DwaveDeviceCapabilities.schema()) - - -MOCK_DWAVE_QPU = { - "deviceName": "Advantage_system1.1", - "deviceType": "QPU", - "providerName": "provider1", - "deviceStatus": "ONLINE", - "deviceCapabilities": MOCK_DWAVE_QPU_CAPABILITIES.json(), -} - -MOCK_GATE_MODEL_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.jaqcd.program": { - "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": {}, -} - -MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES = GateModelSimulatorDeviceCapabilities.parse_obj( - MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES_JSON -) - - -def test_gate_model_sim_schema(): - validate( - MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES_JSON, GateModelSimulatorDeviceCapabilities.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", - "providerName": "provider1", - "deviceStatus": "ONLINE", - "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", -) - - -@pytest.fixture( - params=[ - "arn:aws:braket:us-west-1::device/quantum-simulator/amazon/sim", - "arn:aws:braket:::device/quantum-simulator/amazon/sim", - ] -) -def arn(request): - return request.param - - -@pytest.fixture -def s3_destination_folder(): - return "bucket-foo", "key-bar" - - -@pytest.fixture -def bell_circuit(): - return Circuit().h(0) - - -@pytest.fixture -def openqasm_program(): - return OpenQasmProgram(source="OPENQASM 3.0; h $0;") - - -@pytest.fixture(params=["bell_circuit", "openqasm_program"]) -def circuit(request): - return request.getfixturevalue(request.param) - - -@pytest.fixture -def multiple_circuit_inputs(): - theta = FreeParameter("theta") - beta = FreeParameter("beta") - return Circuit().ry(angle=theta, target=0).rx(angle=beta, target=1) - - -@pytest.fixture() -def single_circuit_input(): - theta = FreeParameter("theta") - return Circuit().ry(angle=theta, target=0) - - -@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 device(aws_session): - def _device(arn): - aws_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 - aws_session.search_devices.return_value = [MOCK_GATE_MODEL_QPU_1] - return AwsDevice(arn, aws_session) - - return _device - - -@pytest.mark.parametrize( - "device_capabilities, get_device_data", - [ - (MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES, MOCK_GATE_MODEL_SIMULATOR), - (MOCK_GATE_MODEL_QPU_CAPABILITIES_1, MOCK_GATE_MODEL_QPU_1), - (MOCK_DWAVE_QPU_CAPABILITIES, MOCK_DWAVE_QPU), - ], -) -def test_device_aws_session(device_capabilities, get_device_data, arn): - mock_session = Mock() - mock_session.get_device.return_value = get_device_data - mock_session.region = RIGETTI_REGION - device = AwsDevice(arn, mock_session) - _assert_device_fields(device, device_capabilities, get_device_data) - assert device.aws_session == mock_session - - -@patch("braket.aws.aws_device.AwsSession") -def test_device_simulator_no_aws_session(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) - _assert_device_fields(device, MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES, MOCK_GATE_MODEL_SIMULATOR) - aws_session.get_device.assert_called_with(arn) - - -@patch("braket.aws.aws_device.AwsSession.copy_session") -@patch("braket.aws.aws_device.AwsSession") -@pytest.mark.parametrize( - "get_device_side_effect", - [ - [MOCK_GATE_MODEL_QPU_1], - [ - ClientError( - { - "Error": { - "Code": "ResourceNotFoundException", - } - }, - "getDevice", - ), - MOCK_GATE_MODEL_QPU_1, - ], - ], -) -def test_device_qpu_no_aws_session( - aws_session_init, mock_copy_session, get_device_side_effect, aws_session -): - arn = RIGETTI_ARN - mock_session = Mock() - mock_session.get_device.side_effect = get_device_side_effect - aws_session.get_device.side_effect = ClientError( - { - "Error": { - "Code": "ResourceNotFoundException", - } - }, - "getDevice", - ) - aws_session_init.return_value = aws_session - mock_copy_session.return_value = mock_session - device = AwsDevice(arn) - _assert_device_fields(device, MOCK_GATE_MODEL_QPU_CAPABILITIES_1, MOCK_GATE_MODEL_QPU_1) - - -@patch("braket.aws.aws_device.AwsSession.copy_session") -@patch("braket.aws.aws_device.AwsSession") -def test_regional_device_region_switch(aws_session_init, mock_copy_session, aws_session): - device_region = "device-region" - arn = f"arn:aws:braket:{device_region}::device/quantum-simulator/amazon/sim" - aws_session_init.return_value = aws_session - mock_session = Mock() - mock_session.get_device.return_value = MOCK_GATE_MODEL_SIMULATOR - mock_copy_session.return_value = mock_session - device = AwsDevice(arn) - aws_session.get_device.assert_not_called() - mock_copy_session.assert_called_once() - mock_copy_session.assert_called_with(aws_session, device_region) - _assert_device_fields(device, MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES, MOCK_GATE_MODEL_SIMULATOR) - - -@patch("braket.aws.aws_device.AwsSession") -@pytest.mark.parametrize( - "get_device_side_effect, expected_exception", - [ - ( - [ - ClientError( - { - "Error": { - "Code": "ResourceNotFoundException", - } - }, - "getDevice", - ) - ], - ValueError, - ), - ( - [ - ClientError( - { - "Error": { - "Code": "ThrottlingException", - } - }, - "getDevice", - ) - ], - ClientError, - ), - ], -) -def test_regional_device_raises_error( - aws_session_init, get_device_side_effect, expected_exception, aws_session -): - arn = "arn:aws:braket:us-west-1::device/quantum-simulator/amazon/sim" - aws_session.get_device.side_effect = get_device_side_effect - aws_session_init.return_value = aws_session - with pytest.raises(expected_exception): - AwsDevice(arn) - aws_session.get_device.assert_called_once() - - -def test_device_refresh_metadata(arn): - mock_session = Mock() - mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 - mock_session.region = RIGETTI_REGION - device = AwsDevice(arn, mock_session) - _assert_device_fields(device, MOCK_GATE_MODEL_QPU_CAPABILITIES_1, MOCK_GATE_MODEL_QPU_1) - - mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_2 - device.refresh_metadata() - _assert_device_fields(device, MOCK_GATE_MODEL_QPU_CAPABILITIES_2, MOCK_GATE_MODEL_QPU_2) - - -MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 = { - "braketSchemaHeader": { - "name": "braket.device_schema.pulse.pulse_device_action_properties", - "version": "1", - }, - "supportedQhpTemplateWaveforms": {}, - "supportedFunctions": {}, - "ports": { - "q0_ff": { - "portId": "q0_ff", - "direction": "tx", - "portType": "ff", - "dt": 1e-09, - "qubitMappings": None, - "centerFrequencies": [375000000.0], - "qhpSpecificProperties": None, - } - }, - "frames": { - "q0_q1_cphase_frame": { - "frameId": "q0_q1_cphase_frame", - "portId": "q0_ff", - "frequency": 4276236.85736918, - "centerFrequency": 375000000.0, - "phase": 1.0, - "associatedGate": "cphase", - "qubitMappings": [0, 1], - "qhpSpecificProperties": None, - } - }, - "nativeGateCalibrationsRef": "file://hostname/foo/bar", -} - -MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_2 = { - "braketSchemaHeader": { - "name": "braket.device_schema.pulse.pulse_device_action_properties", - "version": "1", - }, - "supportedQhpTemplateWaveforms": {}, - "supportedFunctions": {}, - "ports": { - "q0_ff": { - "portId": "q0_ff", - "direction": "tx", - "portType": "ff", - "dt": 1e-09, - "qubitMappings": None, - "centerFrequencies": [375000000.0], - "qhpSpecificProperties": None, - } - }, - "nativeGateCalibrationssRef": "file://hostname/foo/bar", -} - - -def get_pulse_model(capabilities_json): - device_json = { - "braketSchemaHeader": { - "name": "braket.device_schema.rigetti.rigetti_device_capabilities", - "version": "1", - }, - "service": { - "executionWindows": [ - { - "executionDay": "Everyday", - "windowStartHour": "11:00", - "windowEndHour": "12:00", - } - ], - "shotsRange": [1, 10], - }, - "provider": { - "specs": { - "1Q": { - "0": { - "fActiveReset": 0.9715, - "fRO": 0.951, - "f1QRB": 0.997339217568556, - "f1QRB_std_err": 0.00006690422818326937, - "f1Q_simultaneous_RB": 0.9949723201166536, - "f1Q_simultaneous_RB_std_err": 0.00021695233492231294, - "T1": 0.000010019627401991471, - "T2": 0.000018156447816365015, - } - }, - "2Q": { - "0-1": { - "fCZ": 0.9586440436264603, - "fCZ_std_err": 0.007025921432645824, - "fCPHASE": 0.9287330972713645, - "fCPHASE_std_err": 0.009709406809550082, - "fXY": 0.9755179214520402, - "fXY_std_err": 0.0060234488782598536, - }, - }, - } - }, - "action": { - "braket.ir.jaqcd.program": { - "actionType": "braket.ir.jaqcd.program", - "version": ["1"], - "supportedOperations": ["H"], - } - }, - "paradigm": { - "qubitCount": 30, - "nativeGateSet": ["ccnot", "cy"], - "connectivity": {"fullyConnected": False, "connectivityGraph": {"1": ["2", "3"]}}, - }, - "deviceParameters": {}, - "pulse": capabilities_json, - } - device_obj = RigettiDeviceCapabilities.parse_obj(device_json) - return { - "deviceName": "M-2-Pulse", - "deviceType": "QPU", - "providerName": "Rigetti", - "deviceStatus": "OFFLINE", - "deviceCapabilities": device_obj.json(), - } - - -@pytest.mark.parametrize( - "pulse_device_capabilities", - [ - MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1, - MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_2, - ], -) -def test_device_pulse_metadata(pulse_device_capabilities): - mock_session = Mock() - mock_session.get_device.return_value = get_pulse_model(pulse_device_capabilities) - mock_session.region = RIGETTI_REGION - device = AwsDevice("arn:aws:braket:us-west-1::TestName", mock_session) - assert device.ports == {"q0_ff": Port("q0_ff", 1e-9)} - port = device.ports["q0_ff"] - assert port.properties == pulse_device_capabilities["ports"]["q0_ff"] - if "frames" in pulse_device_capabilities: - assert device.frames == { - "q0_q1_cphase_frame": Frame( - "q0_q1_cphase_frame", Port("q0_ff", 1e-9), 4276236.85736918, 1.0 - ) - } - frame = device.frames["q0_q1_cphase_frame"] - assert frame.is_predefined is True - assert frame.properties == pulse_device_capabilities["frames"]["q0_q1_cphase_frame"] - else: - assert device.frames == {} - - -def test_gate_calibration_refresh_no_url(arn): - mock_session = Mock() - mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 - mock_session.region = RIGETTI_REGION - device = AwsDevice(arn, mock_session) - - assert device.refresh_gate_calibrations() is None - - -@patch("urllib.request.urlopen") -def test_device_gate_calibrations_exists(mock_url_request): - # The data is accessed using a device manager so here data is prepped and passed for the return val. - response_data_content = { - "gates": { - "0_1": { - "cphaseshift": [ - { - "name": "cphaseshift", - "qubits": ["0", "1"], - "arguments": ["-1.5707963267948966"], - "calibrations": [ - { - "name": "play", - "arguments": [ - { - "name": "waveform", - "value": "wf_drag_gaussian_0", - "type": "waveform", - }, - { - "name": "frame", - "value": "q0_q1_cphase_frame", - "type": "frame", - }, - ], - }, - ], - } - ], - "rx_12": [{"name": "rx_12", "qubits": ["0"]}], - }, - }, - "waveforms": { - "wf_drag_gaussian_0": { - "waveformId": "wf_drag_gaussian_0", - "name": "drag_gaussian", - "arguments": [ - {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, - {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, - {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, - {"name": "beta", "value": 7.494904522022295e-10, "type": "float"}, - ], - }, - }, - } - - response_data_stream = io.BytesIO(json.dumps(response_data_content).encode("utf-8")) - mock_url_request.return_value.__enter__.return_value = response_data_stream - mock_session = Mock() - mock_session.get_device.return_value = get_pulse_model( - MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 - ) - device = AwsDevice(RIGETTI_ARN, mock_session) - - expected_waveforms = { - "wf_drag_gaussian_0": DragGaussianWaveform( - length=6.000000000000001e-8, - sigma=6.369913502160144e-9, - amplitude=-0.4549282253548838, - beta=7.494904522022295e-10, - id="wf_drag_gaussian_0", - ) - } - expected_ngc = GateCalibrations( - pulse_sequences={ - (Gate.CPhaseShift(-1.5707963267948966), QubitSet([0, 1])): PulseSequence().play( - device.frames["q0_q1_cphase_frame"], expected_waveforms["wf_drag_gaussian_0"] - ) - } - ) - assert device.gate_calibrations == expected_ngc - # Called twice to check that the property stays the same after being initially fetched - assert device.gate_calibrations == expected_ngc - - -@pytest.mark.xfail(raises=URLError) -@patch("urllib.request.urlopen") -def test_refresh_data_url_error(mock_url_request): - mock_url_request.side_effect = URLError("mock reason") - mock_session = Mock() - mock_session.get_device.return_value = get_pulse_model( - MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 - ) - device = AwsDevice(RIGETTI_ARN, mock_session) - - device.gate_calibrations - - -def test_equality(arn): - mock_session = Mock() - mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 - mock_session.region = RIGETTI_REGION - device_1 = AwsDevice(arn, mock_session) - device_2 = AwsDevice(arn, mock_session) - other_device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/bar", mock_session) - non_device = "HI" - - assert device_1 == device_2 - assert device_1 is not device_2 - assert device_1 != other_device - assert device_1 != non_device - - -def test_repr(arn): - mock_session = Mock() - mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 - mock_session.region = RIGETTI_REGION - device = AwsDevice(arn, mock_session) - expected = f"Device('name': {device.name}, 'arn': {device.arn})" - assert repr(device) == expected - - -def test_device_simulator_not_found(): - mock_session = Mock() - mock_session.region = "test-region-1" - mock_session.get_device.side_effect = ClientError( - { - "Error": { - "Code": "ResourceNotFoundException", - "Message": ( - "Braket device 'arn:aws:braket:::device/quantum-simulator/amazon/tn1' " - "not found in us-west-1. You can find a list of all supported device " - "ARNs and the regions in which they are available in the documentation: " - "https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html" - ), - } - }, - "getDevice", - ) - simulator_not_found = ( - "Simulator 'arn:aws:braket:::device/simulator/a/b' not found in 'test-region-1'" - ) - with pytest.raises(ValueError, match=simulator_not_found): - AwsDevice("arn:aws:braket:::device/simulator/a/b", mock_session) - - -@patch("braket.aws.aws_device.AwsSession.copy_session") -def test_device_qpu_not_found(mock_copy_session): - mock_session = Mock() - mock_session.get_device.side_effect = ClientError( - { - "Error": { - "Code": "ResourceNotFoundException", - "Message": ( - "Braket device 'arn:aws:braket:::device/quantum-simulator/amazon/tn1' " - "not found in us-west-1. You can find a list of all supported device " - "ARNs and the regions in which they are available in the documentation: " - "https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html" - ), - } - }, - "getDevice", - ) - mock_copy_session.return_value = mock_session - qpu_not_found = "QPU 'arn:aws:braket:::device/qpu/a/b' not found" - with pytest.raises(ValueError, match=qpu_not_found): - AwsDevice("arn:aws:braket:::device/qpu/a/b", mock_session) - - -@patch("braket.aws.aws_device.AwsSession.copy_session") -def test_device_qpu_exception(mock_copy_session): - mock_session = Mock() - mock_session.get_device.side_effect = ( - ClientError( - { - "Error": { - "Code": "ResourceNotFoundException", - "Message": ( - "Braket device 'arn:aws:braket:::device/quantum-simulator/amazon/tn1' " - "not found in us-west-1. You can find a list of all supported device " - "ARNs and the regions in which they are available in the documentation: " - "https://docs.aws.amazon.com/braket/latest/developerguide/braket-" - "devices.html" - ), - } - }, - "getDevice", - ), - ClientError( - { - "Error": { - "Code": "OtherException", - "Message": "Some other message", - } - }, - "getDevice", - ), - ) - mock_copy_session.return_value = mock_session - qpu_exception = ( - "An error occurred \\(OtherException\\) when calling the " - "getDevice operation: Some other message" - ) - with pytest.raises(ClientError, match=qpu_exception): - AwsDevice("arn:aws:braket:::device/qpu/a/b", mock_session) - - -@patch("braket.aws.aws_device.AwsSession.copy_session") -def test_device_non_qpu_region_error(mock_copy_session): - mock_session = Mock() - mock_session.get_device.side_effect = ClientError( - { - "Error": { - "Code": "ExpiredTokenError", - "Message": ("Some other error that isn't ResourceNotFoundException"), - } - }, - "getDevice", - ) - mock_copy_session.return_value = mock_session - expired_token = ( - "An error occurred \\(ExpiredTokenError\\) when calling the getDevice operation: " - "Some other error that isn't ResourceNotFoundException" - ) - with pytest.raises(ClientError, match=expired_token): - AwsDevice("arn:aws:braket:::device/qpu/a/b", mock_session) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_no_extra(aws_quantum_task_mock, device, circuit): - _run_and_assert( - aws_quantum_task_mock, - device, - circuit, - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_reservation_arn(aws_quantum_task_mock, device, circuit): - _run_and_assert( - aws_quantum_task_mock, - device, - circuit, - reservation_arn="arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9", - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask") -def test_run_param_circuit_with_no_inputs( - aws_quantum_task_mock, single_circuit_input, device, s3_destination_folder -): - cannot_execute_with_unbound = "Cannot execute circuit with unbound parameters: {'theta'}" - - with pytest.raises(ValueError, match=cannot_execute_with_unbound): - _run_and_assert( - aws_quantum_task_mock, - device, - single_circuit_input, - s3_destination_folder, - 10, - 86400, - 0.25, - {}, - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_param_circuit_with_inputs( - aws_quantum_task_mock, single_circuit_input, device, s3_destination_folder -): - inputs = {"theta": 0.2} - - _run_and_assert( - aws_quantum_task_mock, - device, - single_circuit_input, - s3_destination_folder, - 10, - 86400, - 0.25, - inputs, - ) - - -@patch("braket.aws.aws_session.boto3.Session") -@patch("braket.aws.aws_session.AwsSession") -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_param_circuit_with_reservation_arn_batch_task( - aws_quantum_task_mock, - aws_session_mock, - boto_session_mock, - single_circuit_input, - device, - s3_destination_folder, -): - inputs = {"theta": 0.2} - circ_1 = Circuit().rx(angle=0.2, target=0) - circuits = [circ_1, single_circuit_input] - - _run_batch_and_assert( - aws_quantum_task_mock, - aws_session_mock, - device, - circuits, - s3_destination_folder, - 10, - 20, - 50, - 43200, - 0.25, - inputs, - {}, - reservation_arn="arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9", - ) - - -@patch("braket.aws.aws_session.boto3.Session") -@patch("braket.aws.aws_session.AwsSession") -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_param_circuit_with_inputs_batch_task( - aws_quantum_task_mock, - aws_session_mock, - boto_session_mock, - single_circuit_input, - device, - s3_destination_folder, -): - inputs = {"theta": 0.2} - circ_1 = Circuit().rx(angle=0.2, target=0) - circuits = [circ_1, single_circuit_input] - - _run_batch_and_assert( - aws_quantum_task_mock, - aws_session_mock, - device, - circuits, - s3_destination_folder, - 10, - 20, - 50, - 43200, - 0.25, - inputs, - {}, - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask") -def test_run_param_circuit_with_invalid_input( - aws_quantum_task_mock, single_circuit_input, device, s3_destination_folder -): - inputs = {"beta": 0.2} - cannot_execute_with_unbound = "Cannot execute circuit with unbound parameters: {'theta'}" - with pytest.raises(ValueError, match=cannot_execute_with_unbound): - _run_and_assert( - aws_quantum_task_mock, - device, - single_circuit_input, - s3_destination_folder, - 10, - 86400, - 0.25, - inputs, - ) - - -@patch("braket.aws.aws_session.boto3.Session") -@patch("braket.aws.aws_session.AwsSession") -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_batch_param_circuit_with_no_inputs( - aws_quantum_task_mock, - aws_session_mock, - boto_session_mock, - single_circuit_input, - device, - s3_destination_folder, -): - circ_1 = Circuit().ry(angle=3, target=0) - circuits = [circ_1, single_circuit_input] - - cannot_execute_with_unbound = "Cannot execute circuit with unbound parameters: {'theta'}" - - with pytest.raises(ValueError, match=cannot_execute_with_unbound): - _run_batch_and_assert( - aws_quantum_task_mock, - aws_session_mock, - device, - circuits, - s3_destination_folder, - 1000, - 20, - 50, - 43200, - 0.25, - {}, - ) - - -@patch("braket.aws.aws_session.boto3.Session") -@patch("braket.aws.aws_session.AwsSession") -@patch("braket.aws.aws_quantum_task_batch.AwsQuantumTask.create") -def test_run_multi_param_batch_circuit_with_input( - aws_quantum_task_mock, - aws_session_mock, - boto_session_mock, - multiple_circuit_inputs, - device, - s3_destination_folder, -): - inputs = {"beta": 0.2} - circ_1 = Circuit().ry(angle=3, target=0) - circuits = [circ_1, multiple_circuit_inputs] - - cannot_execute_with_unbound = "Cannot execute circuit with unbound parameters: {'theta'}" - with pytest.raises(ValueError, match=cannot_execute_with_unbound): - _run_batch_and_assert( - aws_quantum_task_mock, - aws_session_mock, - device, - circuits, - s3_destination_folder, - 1000, - 20, - 50, - 43200, - 0.25, - inputs, - ) - - -@patch("braket.aws.aws_session.boto3.Session") -@patch("braket.aws.aws_session.AwsSession") -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_param_batch_circuit_with_invalid_input( - aws_quantum_task_mock, - aws_session_mock, - boto_session_mock, - single_circuit_input, - aws_session, - device, - s3_destination_folder, -): - inputs = {"beta": 0.2} - circ_1 = Circuit().ry(angle=3, target=0) - circuits = [circ_1, single_circuit_input] - cannot_execute_with_unbound = "Cannot execute circuit with unbound parameters: {'theta'}" - with pytest.raises(ValueError, match=cannot_execute_with_unbound): - _run_batch_and_assert( - aws_quantum_task_mock, - aws_session_mock, - device, - circuits, - s3_destination_folder, - 1000, - 20, - 50, - 43200, - 0.25, - inputs, - ) - - -@patch("braket.aws.aws_session.boto3.Session") -@patch("braket.aws.aws_session.AwsSession") -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_batch_circuit_with_task_and_input_mismatch( - aws_quantum_task_mock, - aws_session_mock, - boto_session_mock, - single_circuit_input, - openqasm_program, - device, - s3_destination_folder, -): - 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, task specifications and gate definitions must be equal in length." - ) - - with pytest.raises(ValueError, match=wrong_number_of_inputs): - _run_batch_and_assert( - aws_quantum_task_mock, - aws_session_mock, - device, - task_specifications, - s3_destination_folder, - 1000, - 20, - 50, - 43200, - 0.25, - inputs, - {}, - ) - - -@patch("braket.aws.aws_session.boto3.Session") -@patch("braket.aws.aws_session.AwsSession") -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_multiple_task_multiple_batch_inputs_invalid_config( - aws_quantum_task_mock, aws_session_mock, boto_session_mock, device, s3_destination_folder -): - theta = FreeParameter("theta") - multiple_task = [Circuit().rx(angle=theta, target=1)] * 2 - multiple_inputs = [{"theta": 0.2}, {"beta": 0.3}] - cannot_execute_with_unbound = "Cannot execute circuit with unbound parameters: {'theta'}" - with pytest.raises(ValueError, match=cannot_execute_with_unbound): - _run_batch_and_assert( - aws_quantum_task_mock, - aws_session_mock, - device, - multiple_task, - s3_destination_folder, - 1000, - 20, - 50, - 43200, - 0.25, - multiple_inputs, - ) - - -@patch("braket.aws.aws_session.boto3.Session") -@patch("braket.aws.aws_session.AwsSession") -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_single_task_single_input_batch_missing_input( - aws_quantum_task_mock, aws_session_mock, boto_session_mock, device, s3_destination_folder -): - theta = FreeParameter("theta") - task = Circuit().rx(angle=theta, target=0) - inputs = {"beta": 0.2} - cannot_execute_with_unbound = "Cannot execute circuit with unbound parameters: {'theta'}" - with pytest.raises(ValueError, match=cannot_execute_with_unbound): - _run_batch_and_assert( - aws_quantum_task_mock, - aws_session_mock, - device, - task, - s3_destination_folder, - 1000, - 20, - 50, - 43200, - 0.25, - inputs, - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_positional_args(aws_quantum_task_mock, device, circuit, s3_destination_folder): - _run_and_assert( - aws_quantum_task_mock, - device, - circuit, - s3_destination_folder, - 100, - 86400, - 0.25, - ["foo"], - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_kwargs(aws_quantum_task_mock, device, circuit, s3_destination_folder): - _run_and_assert( - aws_quantum_task_mock, - device, - circuit, - s3_destination_folder, - extra_kwargs={"bar": 1, "baz": 2}, - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_shots(aws_quantum_task_mock, device, circuit, s3_destination_folder): - _run_and_assert(aws_quantum_task_mock, device, circuit, s3_destination_folder, 100) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_shots_kwargs(aws_quantum_task_mock, device, circuit, s3_destination_folder): - _run_and_assert( - aws_quantum_task_mock, - device, - circuit, - s3_destination_folder, - 100, - extra_kwargs={"bar": 1, "baz": 2}, - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_default_bucket_not_called(aws_quantum_task_mock, device, circuit, s3_destination_folder): - device = device(RIGETTI_ARN) - run_and_assert( - aws_quantum_task_mock, - device, - MOCK_DEFAULT_S3_DESTINATION_FOLDER, - AwsDevice.DEFAULT_SHOTS_QPU, - AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, - AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, - circuit, - s3_destination_folder, - shots=None, - poll_timeout_seconds=None, - poll_interval_seconds=None, - inputs=None, - gate_definitions=None, - reservation_arn=None, - extra_args=None, - extra_kwargs=None, - ) - device._aws_session.default_bucket.assert_not_called() - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_device_poll_interval_kwargs( - aws_quantum_task_mock, aws_session, circuit, s3_destination_folder -): - poll_interval_seconds = 200 - capabilities = MOCK_GATE_MODEL_QPU_CAPABILITIES_1 - capabilities.service.getTaskPollIntervalMillis = poll_interval_seconds - properties = { - "deviceName": "Aspen-10", - "deviceType": "QPU", - "providerName": "provider1", - "deviceStatus": "OFFLINE", - "deviceCapabilities": capabilities.json(), - } - aws_session.get_device.return_value = properties - _run_and_assert( - aws_quantum_task_mock, - lambda arn: AwsDevice(arn, aws_session), - circuit, - s3_destination_folder, - 100, - 86400, - poll_interval_seconds / 1000.0, - extra_kwargs={"bar": 1, "baz": 2}, - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_shots_poll_timeout_kwargs( - aws_quantum_task_mock, device, circuit, s3_destination_folder -): - _run_and_assert( - aws_quantum_task_mock, - device, - circuit, - s3_destination_folder, - 100, - 86400, - extra_kwargs={"bar": 1, "baz": 2}, - ) - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_positional_args_and_kwargs( - aws_quantum_task_mock, device, circuit, s3_destination_folder -): - _run_and_assert( - aws_quantum_task_mock, - device, - circuit, - s3_destination_folder, - 100, - 86400, - 0.25, - {}, - {}, - "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9", - None, - {"bar": 1, "baz": 2}, - ) - - -@patch.dict( - os.environ, - {"AMZN_BRAKET_TASK_RESULTS_S3_URI": "s3://env_bucket/env/path"}, -) -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_env_variables(aws_quantum_task_mock, device, circuit, arn): - device(arn).run(circuit) - assert aws_quantum_task_mock.call_args_list[0][0][3] == ("env_bucket", "env/path") - - -@patch("braket.aws.aws_session.boto3.Session") -@patch("braket.aws.aws_session.AwsSession") -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_batch_no_extra( - aws_quantum_task_mock, - aws_session_mock, - boto_session_mock, - device, - circuit, - s3_destination_folder, -): - _run_batch_and_assert( - aws_quantum_task_mock, - aws_session_mock, - device, - [circuit for _ in range(10)], - s3_destination_folder, - 1000, - 20, - 50, - 43200, - 0.25, - {}, - {}, - ) - - -@patch("braket.aws.aws_session.boto3.Session") -@patch("braket.aws.aws_session.AwsSession") -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_batch_with_shots( - aws_quantum_task_mock, - aws_session_mock, - boto_session_mock, - device, - circuit, - s3_destination_folder, -): - _run_batch_and_assert( - aws_quantum_task_mock, - aws_session_mock, - device, - [circuit for _ in range(10)], - s3_destination_folder, - 1000, - 20, - 50, - 43200, - 0.25, - {}, - {}, - ) - - -@patch("braket.aws.aws_session.boto3.Session") -@patch("braket.aws.aws_session.AwsSession") -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_batch_with_max_parallel_and_kwargs( - aws_quantum_task_mock, - aws_session_mock, - boto_session_mock, - device, - circuit, - s3_destination_folder, -): - _run_batch_and_assert( - aws_quantum_task_mock, - aws_session_mock, - device, - [circuit for _ in range(10)], - s3_destination_folder, - 1000, - 20, - 50, - 43200, - 0.25, - inputs={"theta": 0.2}, - gate_definitions={}, - extra_kwargs={"bar": 1, "baz": 2}, - ) - - -@patch("braket.aws.aws_session.boto3.Session") -@patch.dict( - os.environ, - {"AMZN_BRAKET_TASK_RESULTS_S3_URI": "s3://env_bucket/env/path"}, -) -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_batch_env_variables(aws_quantum_task_mock, boto_session_mock, device, circuit, arn): - device(arn).run_batch([circuit]) - assert aws_quantum_task_mock.call_args_list[0][0][3] == ("env_bucket", "env/path") - - -def _run_and_assert( - aws_quantum_task_mock, - device_factory, - circuit, - s3_destination_folder=None, # Treated as positional arg - shots=None, # Treated as positional arg - poll_timeout_seconds=None, # Treated as positional arg - poll_interval_seconds=None, # Treated as positional arg - inputs=None, # Treated as positional arg - gate_definitions=None, # Treated as positional arg - reservation_arn=None, # Treated as positional arg - extra_args=None, - extra_kwargs=None, -): - run_and_assert( - aws_quantum_task_mock, - device_factory("arn:aws:braket:::device/quantum-simulator/amazon/sim"), - MOCK_DEFAULT_S3_DESTINATION_FOLDER, - AwsDevice.DEFAULT_SHOTS_SIMULATOR, - AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, - AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, - circuit, - s3_destination_folder, - shots, - poll_timeout_seconds, - poll_interval_seconds, - inputs, - gate_definitions, - reservation_arn, - extra_args, - extra_kwargs, - ) - - -def _run_batch_and_assert( - aws_quantum_task_mock, - aws_session_mock, - device_factory, - circuits, - s3_destination_folder=None, # Treated as positional arg - shots=None, # Treated as positional arg - max_parallel=None, # Treated as positional arg - max_connections=None, # Treated as positional arg - poll_timeout_seconds=None, # Treated as a positional arg - poll_interval_seconds=None, # Treated as positional arg - inputs=None, # Treated as positional arg - gate_definitions=None, # Treated as positional arg - reservation_arn=None, # Treated as positional arg - extra_args=None, - extra_kwargs=None, -): - run_batch_and_assert( - aws_quantum_task_mock, - aws_session_mock, - device_factory("arn:aws:braket:::device/quantum-simulator/amazon/sim"), - MOCK_DEFAULT_S3_DESTINATION_FOLDER, - AwsDevice.DEFAULT_SHOTS_SIMULATOR, - AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, - AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, - circuits, - s3_destination_folder, - shots, - max_parallel, - max_connections, - poll_timeout_seconds, - poll_interval_seconds, - inputs, - gate_definitions, - reservation_arn, - extra_args, - extra_kwargs, - ) - - -def _assert_device_fields(device, expected_properties, expected_device_data): - assert device.name == expected_device_data.get("deviceName") - assert device.properties == expected_properties - assert device.status == expected_device_data.get("deviceStatus") - assert device.provider_name == expected_device_data.get("providerName") - assert device.type == AwsDeviceType(expected_device_data.get("deviceType")) - if device.topology_graph: - assert device.topology_graph.edges == device._construct_topology_graph().edges - assert device.frames == {} - assert device.ports == {} - - -@patch("braket.aws.aws_device.AwsSession.copy_session") -def test_get_devices(mock_copy_session, aws_session): - aws_session.search_devices.side_effect = [ - # us-west-1 - [ - { - "deviceArn": SV1_ARN, - "deviceName": "SV1", - "deviceType": "SIMULATOR", - "deviceStatus": "ONLINE", - "providerName": "Amazon Braket", - } - ], - ValueError("should not be reachable"), - ] - aws_session.get_device.side_effect = [ - MOCK_GATE_MODEL_SIMULATOR, - ValueError("should not be reachable"), - ] - session_for_region = Mock() - session_for_region.search_devices.side_effect = [ - # us-east-1 - [ - { - "deviceArn": IONQ_ARN, - "deviceName": "IonQ Device", - "deviceType": "QPU", - "deviceStatus": "ONLINE", - "providerName": "IonQ", - }, - ], - # us-west-2 - [ - { - "deviceArn": DWAVE_ARN, - "deviceName": "Advantage_system1.1", - "deviceType": "QPU", - "deviceStatus": "ONLINE", - "providerName": "D-Wave", - }, - # Should not be reached because already instantiated in us-west-1 - { - "deviceArn": SV1_ARN, - "deviceName": "SV1", - "deviceType": "SIMULATOR", - "deviceStatus": "ONLINE", - "providerName": "Amazon Braket", - }, - ], - # eu-west-2 - [ - { - "deviceArn": OQC_ARN, - "deviceName": "Lucy", - "deviceType": "QPU", - "deviceStatus": "ONLINE", - "providerName": "OQC", - } - ], - # Only two regions to search outside of current - ValueError("should not be reachable"), - ] - session_for_region.get_device.side_effect = [ - MOCK_DWAVE_QPU, - MOCK_GATE_MODEL_QPU_2, - MOCK_GATE_MODEL_QPU_3, - 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 - results = AwsDevice.get_devices( - arns=[SV1_ARN, DWAVE_ARN, IONQ_ARN, OQC_ARN], - provider_names=["Amazon Braket", "D-Wave", "IonQ", "OQC"], - statuses=["ONLINE"], - aws_session=aws_session, - ) - assert [result.name for result in results] == ["Advantage_system1.1", "Blah", "Lucy", "SV1"] - - -@patch("braket.aws.aws_device.AwsSession.copy_session") -def test_get_devices_simulators_only(mock_copy_session, aws_session): - aws_session.search_devices.side_effect = [ - [ - { - "deviceArn": SV1_ARN, - "deviceName": "SV1", - "deviceType": "SIMULATOR", - "deviceStatus": "ONLINE", - "providerName": "Amazon Braket", - } - ], - ValueError("should not be reachable"), - ] - aws_session.get_device.side_effect = [ - MOCK_GATE_MODEL_SIMULATOR, - ValueError("should not be reachable"), - ] - session_for_region = Mock() - session_for_region.search_devices.side_effect = ValueError("should not be reachable") - session_for_region.get_device.side_effect = ValueError("should not be reachable") - mock_copy_session.return_value = session_for_region - results = AwsDevice.get_devices( - arns=[SV1_ARN, TN1_ARN], - types=["SIMULATOR"], - provider_names=["Amazon Braket"], - statuses=["ONLINE"], - aws_session=aws_session, - ) - # Only one region should be searched - assert [result.name for result in results] == ["SV1"] - - -@pytest.mark.filterwarnings("ignore:Test Code:") -@patch("braket.aws.aws_device.AwsSession.copy_session") -def test_get_devices_with_error_in_region(mock_copy_session, aws_session): - aws_session.search_devices.side_effect = [ - # us-west-1 - [ - { - "deviceArn": SV1_ARN, - "deviceName": "SV1", - "deviceType": "SIMULATOR", - "deviceStatus": "ONLINE", - "providerName": "Amazon Braket", - } - ], - ValueError("should not be reachable"), - ] - aws_session.get_device.side_effect = [ - MOCK_GATE_MODEL_SIMULATOR, - ValueError("should not be reachable"), - ] - session_for_region = Mock() - session_for_region.search_devices.side_effect = [ - # us-east-1 - [ - { - "deviceArn": IONQ_ARN, - "deviceName": "IonQ Device", - "deviceType": "QPU", - "deviceStatus": "ONLINE", - "providerName": "IonQ", - }, - ], - # us-west-2 - ClientError( - { - "Error": { - "Code": "Test Code", - "Message": "Test Message", - } - }, - "Test Operation", - ), - # eu-west-2 - [ - { - "deviceArn": OQC_ARN, - "deviceName": "Lucy", - "deviceType": "QPU", - "deviceStatus": "ONLINE", - "providerName": "OQC", - } - ], - # Only two regions to search outside of current - ValueError("should not be reachable"), - ] - session_for_region.get_device.side_effect = [ - MOCK_GATE_MODEL_QPU_2, - MOCK_GATE_MODEL_QPU_3, - 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 - results = AwsDevice.get_devices( - statuses=["ONLINE"], - aws_session=aws_session, - ) - assert [result.name for result in results] == ["Blah", "Lucy", "SV1"] - - -@pytest.mark.xfail(raises=ValueError) -def test_get_devices_invalid_order_by(): - AwsDevice.get_devices(order_by="foo") - - -@patch("braket.aws.aws_device.datetime") -def test_get_device_availability(mock_utc_now): - - class Expando: - pass - - class MockDevice(AwsDevice): - def __init__(self, status, *execution_window_args): - self._status = status - self._properties = Expando() - self._properties.service = Expando() - 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 = ( - { - "test_devices": ( - ("always_on_device", MockDevice("ONLINE", ("Everyday", "00:00", "23:59:59"))), - ("offline_device", MockDevice("OFFLINE", ("Everyday", "00:00", "23:59:59"))), - ("retired_device", MockDevice("RETIRED", ("Everyday", "00:00", "23:59:59"))), - ("missing_schedule_device", MockDevice("ONLINE")), - ), - "test_items": ( - (datetime(2021, 12, 6, 10, 0, 0), (1, 0, 0, 0)), - (datetime(2021, 12, 7, 10, 0, 0), (1, 0, 0, 0)), - (datetime(2021, 12, 8, 10, 0, 0), (1, 0, 0, 0)), - (datetime(2021, 12, 9, 10, 0, 0), (1, 0, 0, 0)), - (datetime(2021, 12, 10, 10, 0, 0), (1, 0, 0, 0)), - (datetime(2021, 12, 11, 10, 0, 0), (1, 0, 0, 0)), - (datetime(2021, 12, 12, 10, 0, 0), (1, 0, 0, 0)), - ), - }, - { - "test_devices": ( - ("midday_everyday_device", MockDevice("ONLINE", ("Everyday", "07:00", "17:00"))), - ("midday_weekday_device", MockDevice("ONLINE", ("Weekdays", "07:00", "17:00"))), - ("midday_weekend_device", MockDevice("ONLINE", ("Weekend", "07:00", "17:00"))), - ("evening_everyday_device", MockDevice("ONLINE", ("Everyday", "17:00", "07:00"))), - ("evening_weekday_device", MockDevice("ONLINE", ("Weekdays", "17:00", "07:00"))), - ("evening_weekend_device", MockDevice("ONLINE", ("Weekend", "17:00", "07:00"))), - ), - "test_items": ( - (datetime(2021, 12, 6, 5, 0, 0), (0, 0, 0, 1, 0, 1)), - (datetime(2021, 12, 6, 10, 0, 0), (1, 1, 0, 0, 0, 0)), - (datetime(2021, 12, 6, 20, 0, 0), (0, 0, 0, 1, 1, 0)), - (datetime(2021, 12, 7, 5, 0, 0), (0, 0, 0, 1, 1, 0)), - (datetime(2021, 12, 7, 10, 0, 0), (1, 1, 0, 0, 0, 0)), - (datetime(2021, 12, 7, 20, 0, 0), (0, 0, 0, 1, 1, 0)), - (datetime(2021, 12, 8, 5, 0, 0), (0, 0, 0, 1, 1, 0)), - (datetime(2021, 12, 8, 10, 0, 0), (1, 1, 0, 0, 0, 0)), - (datetime(2021, 12, 8, 20, 0, 0), (0, 0, 0, 1, 1, 0)), - (datetime(2021, 12, 9, 5, 0, 0), (0, 0, 0, 1, 1, 0)), - (datetime(2021, 12, 9, 10, 0, 0), (1, 1, 0, 0, 0, 0)), - (datetime(2021, 12, 9, 20, 0, 0), (0, 0, 0, 1, 1, 0)), - (datetime(2021, 12, 10, 5, 0, 0), (0, 0, 0, 1, 1, 0)), - (datetime(2021, 12, 10, 10, 0, 0), (1, 1, 0, 0, 0, 0)), - (datetime(2021, 12, 10, 20, 0, 0), (0, 0, 0, 1, 1, 0)), - (datetime(2021, 12, 11, 5, 0, 0), (0, 0, 0, 1, 1, 0)), - (datetime(2021, 12, 11, 10, 0, 0), (1, 0, 1, 0, 0, 0)), - (datetime(2021, 12, 11, 20, 0, 0), (0, 0, 0, 1, 0, 1)), - (datetime(2021, 12, 12, 5, 0, 0), (0, 0, 0, 1, 0, 1)), - (datetime(2021, 12, 12, 10, 0, 0), (1, 0, 1, 0, 0, 0)), - (datetime(2021, 12, 12, 20, 0, 0), (0, 0, 0, 1, 0, 1)), - ), - }, - { - "test_devices": ( - ("monday_device", MockDevice("ONLINE", ("Monday", "07:00", "17:00"))), - ("tuesday_device", MockDevice("ONLINE", ("Tuesday", "07:00", "17:00"))), - ("wednesday_device", MockDevice("ONLINE", ("Wednesday", "07:00", "17:00"))), - ("thursday_device", MockDevice("ONLINE", ("Thursday", "07:00", "17:00"))), - ("friday_device", MockDevice("ONLINE", ("Friday", "07:00", "17:00"))), - ("saturday_device", MockDevice("ONLINE", ("Saturday", "07:00", "17:00"))), - ("sunday_device", MockDevice("ONLINE", ("Sunday", "07:00", "17:00"))), - ( - "monday_friday_device", - MockDevice( - "ONLINE", ("Monday", "07:00", "17:00"), ("Friday", "07:00", "17:00") - ), - ), - ), - "test_items": ( - (datetime(2021, 12, 6, 5, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), - (datetime(2021, 12, 6, 10, 0, 0), (1, 0, 0, 0, 0, 0, 0, 1)), - (datetime(2021, 12, 6, 20, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), - (datetime(2021, 12, 7, 5, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), - (datetime(2021, 12, 7, 10, 0, 0), (0, 1, 0, 0, 0, 0, 0, 0)), - (datetime(2021, 12, 7, 20, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), - (datetime(2021, 12, 8, 5, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), - (datetime(2021, 12, 8, 10, 0, 0), (0, 0, 1, 0, 0, 0, 0, 0)), - (datetime(2021, 12, 8, 20, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), - (datetime(2021, 12, 9, 5, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), - (datetime(2021, 12, 9, 10, 0, 0), (0, 0, 0, 1, 0, 0, 0, 0)), - (datetime(2021, 12, 9, 20, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), - (datetime(2021, 12, 10, 5, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), - (datetime(2021, 12, 10, 10, 0, 0), (0, 0, 0, 0, 1, 0, 0, 1)), - (datetime(2021, 12, 10, 20, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), - (datetime(2021, 12, 11, 5, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), - (datetime(2021, 12, 11, 10, 0, 0), (0, 0, 0, 0, 0, 1, 0, 0)), - (datetime(2021, 12, 11, 20, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), - (datetime(2021, 12, 12, 5, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), - (datetime(2021, 12, 12, 10, 0, 0), (0, 0, 0, 0, 0, 0, 1, 0)), - (datetime(2021, 12, 12, 20, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)), - ), - }, - ) - - 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 - - # flake8: noqa: C501 - for i in range(len(test_item[1])): - device_name = test_set["test_devices"][i][0] - device = test_set["test_devices"][i][1] - type(device).properties = PropertyMock(return_value=Expando()) - type(device).properties.service = PropertyMock(return_value=Expando()) - device.properties.service.executionWindows = ( - device._properties.service.executionWindows - ) - 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}" - - -@pytest.mark.parametrize( - "get_device_data, expected_graph", - [ - (MOCK_GATE_MODEL_QPU_1, nx.DiGraph([(1, 2), (1, 3)])), - (MOCK_GATE_MODEL_QPU_2, nx.complete_graph(30, nx.DiGraph())), - (MOCK_DWAVE_QPU, nx.DiGraph([(1, 2), (2, 3)])), - ], -) -def test_device_topology_graph_data(get_device_data, expected_graph, arn): - mock_session = Mock() - mock_session.get_device.return_value = get_device_data - mock_session.region = RIGETTI_REGION - device = AwsDevice(arn, mock_session) - assert nx.is_isomorphic(device.topology_graph, expected_graph) - new_val = "new_val" - device._topology_graph = new_val - assert device.topology_graph == new_val - - -def test_device_no_href(): - mock_session = Mock() - mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 - AwsDevice(DWAVE_ARN, mock_session) - - -def test_parse_calibration_data(): - mock_session = Mock() - mock_session.get_device.return_value = get_pulse_model( - MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 - ) - device = AwsDevice(DWAVE_ARN, mock_session) - calibration_data = device._parse_calibration_json(MOCK_gate_calibrations_JSON) - device_ngc = GateCalibrations(calibration_data) - - expected_waveforms = { - "wf_drag_gaussian_0": DragGaussianWaveform( - length=6.000000000000001e-8, - sigma=6.369913502160144e-9, - amplitude=-0.4549282253548838, - beta=7.494904522022295e-10, - id="wf_drag_gaussian_0", - ) - } - expected_pulse_sequences = { - (Gate.CPhaseShift(-1.5707963267948966), QubitSet([0, 1])): PulseSequence() - .barrier(QubitSet(0)) - .play(device.frames["q0_q1_cphase_frame"], expected_waveforms["wf_drag_gaussian_0"]) - .barrier([device.frames["q0_q1_cphase_frame"]]) - .barrier([]) - .delay(QubitSet([0, 1]), 3e-07) - .delay([device.frames["q0_q1_cphase_frame"]], 3e-07) - .shift_phase(device.frames["q0_q1_cphase_frame"], 3e-07) - .shift_frequency(device.frames["q0_q1_cphase_frame"], FreeParameter("theta")), - (Gate.Rx(FreeParameter("theta")), QubitSet(0)): PulseSequence().barrier([]), - (Gate.CZ(), QubitSet([1, 0])): PulseSequence().barrier([]), - } - expected_ngc = GateCalibrations(pulse_sequences=expected_pulse_sequences) - assert device_ngc == expected_ngc - - -@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"}, - }, - } - ), - ], -) -@pytest.mark.xfail(raises=ValueError) -def test_parse_calibration_data_bad_instr(bad_input): - mock_session = Mock() - mock_session.get_device.return_value = get_pulse_model( - MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 - ) - device = AwsDevice(DWAVE_ARN, mock_session) - device._parse_calibration_json(bad_input) - - -def test_queue_depth(arn): - mock_session = Mock() - mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 - mock_session.region = RIGETTI_REGION - device = AwsDevice(arn, mock_session) - assert device.queue_depth() == QueueDepthInfo( - 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/aws/test_aws_quantum_job.py b/test/unit_tests/braket/aws/test_aws_quantum_job.py deleted file mode 100644 index 67ca9822..00000000 --- a/test/unit_tests/braket/aws/test_aws_quantum_job.py +++ /dev/null @@ -1,1129 +0,0 @@ -# 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 datetime -import json -import logging -import os -import re -import tarfile -import tempfile -from unittest.mock import Mock, patch - -import pytest -from botocore.exceptions import ClientError - -from braket.aws import AwsQuantumJob, AwsSession -from braket.aws.queue_information import HybridJobQueueInfo - - -@pytest.fixture -def aws_session(quantum_job_arn, job_region): - _aws_session = Mock(spec=AwsSession) - _aws_session.create_job.return_value = quantum_job_arn - _aws_session.default_bucket.return_value = "default-bucket-name" - _aws_session.get_default_jobs_role.return_value = "default-role-arn" - _aws_session.construct_s3_uri.side_effect = ( - lambda bucket, *dirs: f"s3://{bucket}/{'/'.join(dirs)}" - ) - - def fake_copy_session(region): - _aws_session.region = region - return _aws_session - - _aws_session.copy_session.side_effect = fake_copy_session - _aws_session.list_keys.return_value = ["job-path/output/model.tar.gz"] - _aws_session.region = job_region - - _braket_client_mock = Mock(meta=Mock(region_name=job_region)) - _aws_session.braket_client = _braket_client_mock - return _aws_session - - -@pytest.fixture -def generate_get_job_response(): - def _get_job_response(**kwargs): - response = { - "ResponseMetadata": { - "RequestId": "d223b1a0-ee5c-4c75-afa7-3c29d5338b62", - "HTTPStatusCode": 200, - }, - "algorithmSpecification": { - "scriptModeConfig": { - "entryPoint": "my_file:start_here", - "s3Uri": "s3://amazon-braket-jobs/job-path/my_file.py", - } - }, - "checkpointConfig": { - "localPath": "/opt/omega/checkpoints", - "s3Uri": "s3://amazon-braket-jobs/job-path/checkpoints", - }, - "createdAt": datetime.datetime(2021, 6, 28, 21, 4, 51), - "deviceConfig": { - "device": "arn:aws:braket:::device/qpu/rigetti/Aspen-10", - }, - "hyperParameters": { - "foo": "bar", - }, - "inputDataConfig": [ - { - "channelName": "training_input", - "dataSource": { - "s3DataSource": { - "s3Uri": "s3://amazon-braket-jobs/job-path/input", - } - }, - } - ], - "instanceConfig": { - "instanceCount": 1, - "instanceType": "ml.m5.large", - "volumeSizeInGb": 1, - }, - "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}, - } - response.update(kwargs) - - return response - - return _get_job_response - - -@pytest.fixture -def generate_cancel_job_response(): - def _cancel_job_response(**kwargs): - response = { - "ResponseMetadata": { - "RequestId": "857b0893-2073-4ad6-b828-744af8400dfe", - "HTTPStatusCode": 200, - }, - "cancellationStatus": "CANCELLING", - "jobArn": "arn:aws:braket:us-west-2:875981177017:job/job-test-20210628140446", - } - response.update(kwargs) - return response - - return _cancel_job_response - - -@pytest.fixture -def quantum_job_name(): - return "job-test-20210628140446" - - -@pytest.fixture -def job_region(): - return "us-west-2" - - -@pytest.fixture -def quantum_job_arn(quantum_job_name, job_region): - return f"arn:aws:braket:{job_region}:875981177017:job/{quantum_job_name}" - - -@pytest.fixture -def quantum_job(quantum_job_arn, aws_session): - return AwsQuantumJob(quantum_job_arn, aws_session) - - -def test_equality(quantum_job_arn, aws_session, job_region): - new_aws_session = Mock(region=job_region) - quantum_job_1 = AwsQuantumJob(quantum_job_arn, aws_session) - quantum_job_2 = AwsQuantumJob(quantum_job_arn, aws_session) - quantum_job_3 = AwsQuantumJob(quantum_job_arn, new_aws_session) - other_quantum_job = AwsQuantumJob( - "arn:aws:braket:us-west-2:875981177017:job/other-job", aws_session - ) - non_quantum_job = quantum_job_1.arn - - assert quantum_job_1 == quantum_job_2 - assert quantum_job_1 == quantum_job_3 - assert quantum_job_1 is not quantum_job_2 - assert quantum_job_1 is not quantum_job_3 - assert quantum_job_1 is quantum_job_1 - assert quantum_job_1 != other_quantum_job - assert quantum_job_1 != non_quantum_job - - -def test_hash(quantum_job): - assert hash(quantum_job) == hash(quantum_job.arn) - - -@pytest.mark.parametrize( - "arn, expected_region", - [ - ("arn:aws:braket:us-west-2:875981177017:job/job-name", "us-west-2"), - ("arn:aws:braket:us-west-1:1234567890:job/job-name", "us-west-1"), - ], -) -@patch("braket.aws.aws_quantum_job.boto3.Session") -@patch("braket.aws.aws_quantum_job.AwsSession") -def test_quantum_job_constructor_default_session( - aws_session_mock, mock_session, arn, expected_region -): - mock_boto_session = Mock() - aws_session_mock.return_value = Mock() - mock_session.return_value = mock_boto_session - job = AwsQuantumJob(arn) - mock_session.assert_called_with(region_name=expected_region) - aws_session_mock.assert_called_with(boto_session=mock_boto_session) - assert job.arn == arn - assert job._aws_session == aws_session_mock.return_value - - -def test_quantum_job_constructor_invalid_region(aws_session): - region_mismatch = "The aws session region does not match the region for the supplied arn." - arn = "arn:aws:braket:unknown-region:875981177017:job/quantum_job_name" - with pytest.raises(ValueError, match=region_mismatch): - AwsQuantumJob(arn, aws_session) - - -@patch("braket.aws.aws_quantum_job.boto3.Session") -def test_quantum_job_constructor_explicit_session(mock_session, quantum_job_arn, job_region): - aws_session_mock = Mock(region=job_region) - job = AwsQuantumJob(quantum_job_arn, aws_session_mock) - assert job._aws_session == aws_session_mock - assert job.arn == quantum_job_arn - mock_session.assert_not_called() - - -def test_metadata(quantum_job, aws_session, generate_get_job_response, quantum_job_arn): - get_job_response_running = generate_get_job_response(status="RUNNING") - aws_session.get_job.return_value = get_job_response_running - assert quantum_job.metadata() == get_job_response_running - aws_session.get_job.assert_called_with(quantum_job_arn) - - get_job_response_completed = generate_get_job_response(status="COMPLETED") - aws_session.get_job.return_value = get_job_response_completed - assert quantum_job.metadata() == get_job_response_completed - aws_session.get_job.assert_called_with(quantum_job_arn) - assert aws_session.get_job.call_count == 2 - - -def test_metadata_caching(quantum_job, aws_session, generate_get_job_response, quantum_job_arn): - get_job_response_running = generate_get_job_response(status="RUNNING") - aws_session.get_job.return_value = get_job_response_running - assert quantum_job.metadata(True) == get_job_response_running - - get_job_response_completed = generate_get_job_response(status="COMPLETED") - aws_session.get_job.return_value = get_job_response_completed - assert quantum_job.metadata(True) == get_job_response_running - aws_session.get_job.assert_called_with(quantum_job_arn) - assert aws_session.get_job.call_count == 1 - - -def test_queue_position(quantum_job, aws_session, generate_get_job_response): - state_1 = "COMPLETED" - queue_info = { - "queue": "JOBS_QUEUE", - "position": "None", - "message": "Job is in COMPLETED status. " - "AmazonBraket does not show queue position for this status.", - } - get_job_response_completed = generate_get_job_response(status=state_1, queueInfo=queue_info) - aws_session.get_job.return_value = get_job_response_completed - assert quantum_job.queue_position() == HybridJobQueueInfo( - queue_position=None, message=queue_info["message"] - ) - - state_2 = "QUEUED" - queue_info = {"queue": "JOBS_QUEUE", "position": "2"} - get_job_response_queued = generate_get_job_response(status=state_2, queueInfo=queue_info) - aws_session.get_job.return_value = get_job_response_queued - assert quantum_job.queue_position() == HybridJobQueueInfo(queue_position="2", message=None) - - -def test_state(quantum_job, aws_session, generate_get_job_response, quantum_job_arn): - state_1 = "RUNNING" - get_job_response_running = generate_get_job_response(status=state_1) - aws_session.get_job.return_value = get_job_response_running - assert quantum_job.state() == state_1 - aws_session.get_job.assert_called_with(quantum_job_arn) - - state_2 = "COMPLETED" - get_job_response_completed = generate_get_job_response(status=state_2) - aws_session.get_job.return_value = get_job_response_completed - assert quantum_job.state() == state_2 - aws_session.get_job.assert_called_with(quantum_job_arn) - assert aws_session.get_job.call_count == 2 - - -def test_state_caching(quantum_job, aws_session, generate_get_job_response, quantum_job_arn): - state_1 = "RUNNING" - get_job_response_running = generate_get_job_response(status=state_1) - aws_session.get_job.return_value = get_job_response_running - assert quantum_job.state(True) == state_1 - - state_2 = "COMPLETED" - get_job_response_completed = generate_get_job_response(status=state_2) - aws_session.get_job.return_value = get_job_response_completed - assert quantum_job.state(True) == state_1 - aws_session.get_job.assert_called_with(quantum_job_arn) - assert aws_session.get_job.call_count == 1 - - -@pytest.fixture() -def result_setup(quantum_job_name): - with tempfile.TemporaryDirectory() as temp_dir: - os.chdir(temp_dir) - file_path = "results.json" - - 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", - } - ) - ) - - with tarfile.open("model.tar.gz", "w:gz") as tar: - tar.add(file_path, arcname=os.path.basename(file_path)) - - yield - - result_dir = f"{os.getcwd()}/{quantum_job_name}" - - if os.path.exists(result_dir): - os.remove(f"{result_dir}/results.json") - os.rmdir(f"{result_dir}/") - - if os.path.isfile("model.tar.gz"): - os.remove("model.tar.gz") - - os.chdir("..") - - -@pytest.mark.parametrize("state", sorted(AwsQuantumJob.TERMINAL_STATES)) -def test_results_when_job_is_completed( - quantum_job, aws_session, generate_get_job_response, result_setup, state -): - expected_saved_data = {"converged": True, "energy": -0.2} - - get_job_response_completed = generate_get_job_response(status=state) - quantum_job._aws_session.get_job.return_value = get_job_response_completed - actual_data = quantum_job.result() - - job_metadata = quantum_job.metadata(True) - s3_path = job_metadata["outputDataConfig"]["s3Path"] - - output_bucket_uri = f"{s3_path}/output/model.tar.gz" - quantum_job._aws_session.download_from_s3.assert_called_with( - s3_uri=output_bucket_uri, filename="model.tar.gz" - ) - assert actual_data == expected_saved_data - - -def test_download_result_when_job_is_running( - quantum_job, aws_session, generate_get_job_response, result_setup -): - poll_timeout_seconds, poll_interval_seconds, state = 1, 0.5, "RUNNING" - get_job_response_completed = generate_get_job_response(status=state) - aws_session.get_job.return_value = get_job_response_completed - job_metadata = quantum_job.metadata(True) - - with pytest.raises( - TimeoutError, - match=f"{job_metadata['jobName']}: Polling for job completion " - f"timed out after {poll_timeout_seconds} seconds.", - ): - quantum_job.download_result( - poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds - ) - - -def test_download_result_when_extract_path_not_provided( - quantum_job, generate_get_job_response, aws_session, result_setup -): - state = "COMPLETED" - expected_saved_data = {"converged": True, "energy": -0.2} - get_job_response_completed = generate_get_job_response(status=state) - quantum_job._aws_session.get_job.return_value = get_job_response_completed - job_metadata = quantum_job.metadata(True) - job_name = job_metadata["jobName"] - quantum_job.download_result() - - with open(f"{job_name}/results.json") as file: - actual_data = json.loads(file.read())["dataDictionary"] - assert expected_saved_data == actual_data - - -def test_download_result_when_extract_path_provided( - quantum_job, generate_get_job_response, aws_session, result_setup -): - expected_saved_data = {"converged": True, "energy": -0.2} - state = "COMPLETED" - get_job_response_completed = generate_get_job_response(status=state) - aws_session.get_job.return_value = get_job_response_completed - job_metadata = quantum_job.metadata(True) - job_name = job_metadata["jobName"] - - with tempfile.TemporaryDirectory() as temp_dir: - quantum_job.download_result(temp_dir) - - with open(f"{temp_dir}/{job_name}/results.json") as file: - actual_data = json.loads(file.read())["dataDictionary"] - assert expected_saved_data == actual_data - - -def test_empty_dict_returned_when_result_not_saved( - quantum_job, generate_get_job_response, aws_session -): - state = "COMPLETED" - get_job_response_completed = generate_get_job_response(status=state) - aws_session.get_job.return_value = get_job_response_completed - - exception_response = { - "Error": { - "Code": "404", - "Message": "Not Found", - } - } - quantum_job._aws_session.download_from_s3 = Mock( - side_effect=ClientError(exception_response, "HeadObject") - ) - assert quantum_job.result() == {} - - -def test_results_not_in_s3_for_download(quantum_job, generate_get_job_response, aws_session): - state = "COMPLETED" - get_job_response_completed = generate_get_job_response(status=state) - aws_session.get_job.return_value = get_job_response_completed - job_metadata = quantum_job.metadata(True) - output_s3_path = job_metadata["outputDataConfig"]["s3Path"] - - error_message = f"Error retrieving results, could not find results at '{output_s3_path}" - - exception_response = { - "Error": { - "Code": "404", - "Message": "Not Found", - } - } - quantum_job._aws_session.download_from_s3 = Mock( - side_effect=ClientError(exception_response, "HeadObject") - ) - with pytest.raises(ClientError, match=error_message): - quantum_job.download_result() - - -def test_results_raises_error_for_non_404_errors( - quantum_job, generate_get_job_response, aws_session -): - state = "COMPLETED" - get_job_response_completed = generate_get_job_response(status=state) - aws_session.get_job.return_value = get_job_response_completed - - error = "An error occurred \\(402\\) when calling the SomeObject operation: Something" - - exception_response = { - "Error": { - "Code": "402", - "Message": "Something", - } - } - quantum_job._aws_session.download_from_s3 = Mock( - side_effect=ClientError(exception_response, "SomeObject") - ) - with pytest.raises(ClientError, match=error): - quantum_job.result() - - -@patch("braket.aws.aws_quantum_job.AwsQuantumJob.download_result") -def test_results_json_file_not_in_tar( - result_download, quantum_job, aws_session, generate_get_job_response -): - state = "COMPLETED" - get_job_response_completed = generate_get_job_response(status=state) - quantum_job._aws_session.get_job.return_value = get_job_response_completed - assert quantum_job.result() == {} - - -@pytest.fixture -def entry_point(): - return "test-source-module.entry_point:func" - - -@pytest.fixture -def bucket(): - return "braket-region-id" - - -@pytest.fixture( - params=[ - None, - "aws.location/custom-jobs:tag.1.2.3", - "other.uri/custom-name:tag", - "other-custom-format.com", - ] -) -def image_uri(request): - return request.param - - -@pytest.fixture(params=["given_job_name", "default_job_name"]) -def job_name(request): - if request.param == "given_job_name": - return "test-job-name" - - -@pytest.fixture -def s3_prefix(job_name): - return f"{job_name}/non-default" - - -@pytest.fixture(params=["local_source", "s3_source"]) -def source_module(request, bucket, s3_prefix): - if request.param == "local_source": - return "test-source-module" - elif request.param == "s3_source": - return AwsSession.construct_s3_uri(bucket, "test-source-prefix", "source.tar.gz") - - -@pytest.fixture -def role_arn(): - return "arn:aws:iam::0000000000:role/AmazonBraketInternalSLR" - - -@pytest.fixture( - params=[ - "arn:aws:braket:us-west-2::device/qpu/test/device-name", - "arn:aws:braket:::device/qpu/test/device-name", - ] -) -def device_arn(request): - return request.param - - -@pytest.fixture -def reservation_arn(): - return "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" - - -@pytest.fixture -def prepare_job_args(aws_session, device_arn, reservation_arn): - return { - "device": device_arn, - "source_module": Mock(), - "entry_point": Mock(), - "image_uri": Mock(), - "job_name": Mock(), - "code_location": Mock(), - "role_arn": Mock(), - "hyperparameters": Mock(), - "input_data": Mock(), - "instance_config": Mock(), - "distribution": Mock(), - "stopping_condition": Mock(), - "output_data_config": Mock(), - "copy_checkpoints_from_job": Mock(), - "checkpoint_config": Mock(), - "aws_session": aws_session, - "tags": Mock(), - "reservation_arn": reservation_arn, - } - - -def test_str(quantum_job): - expected = f"AwsQuantumJob('arn':'{quantum_job.arn}')" - assert str(quantum_job) == expected - - -def test_arn(quantum_job_arn, aws_session): - quantum_job = AwsQuantumJob(quantum_job_arn, aws_session) - assert quantum_job.arn == quantum_job_arn - - -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 - - -def test_no_arn_setter(quantum_job): - # Python 3.11 error output differs from Python 3.10 <= - with pytest.raises(AttributeError): - quantum_job.arn = 123 - - -@pytest.mark.parametrize("wait_until_complete", [True, False]) -@patch("braket.aws.aws_quantum_job.AwsQuantumJob.logs") -@patch("braket.aws.aws_quantum_job.prepare_quantum_job") -def test_create_job( - mock_prepare_quantum_job, - mock_logs, - aws_session, - prepare_job_args, - quantum_job_arn, - wait_until_complete, -): - test_response_args = {"testArgs": "MyTestArg"} - mock_prepare_quantum_job.return_value = test_response_args - job = AwsQuantumJob.create(wait_until_complete=wait_until_complete, **prepare_job_args) - mock_prepare_quantum_job.assert_called_with(**prepare_job_args) - aws_session.create_job.assert_called_with(**test_response_args) - if wait_until_complete: - mock_logs.assert_called_once() - else: - mock_logs.assert_not_called() - assert job.arn == quantum_job_arn - - -def test_create_fake_arg(): - unexpected_kwarg = "create\\(\\) got an unexpected keyword argument 'fake_arg'" - with pytest.raises(TypeError, match=unexpected_kwarg): - AwsQuantumJob.create( - device="device", - source_module="source", - fake_arg="fake_value", - ) - - -def test_cancel_job(quantum_job_arn, aws_session, generate_cancel_job_response): - cancellation_status = "CANCELLING" - aws_session.cancel_job.return_value = generate_cancel_job_response( - cancellationStatus=cancellation_status - ) - quantum_job = AwsQuantumJob(quantum_job_arn, aws_session) - status = quantum_job.cancel() - aws_session.cancel_job.assert_called_with(quantum_job_arn) - assert status == cancellation_status - - -def test_cancel_job_surfaces_exception(quantum_job, aws_session): - exception_response = { - "Error": { - "Code": "ValidationException", - "Message": "unit-test-error", - } - } - error_string = re.escape( - "An error occurred (ValidationException) when calling the " - "cancel_job operation: unit-test-error" - ) - aws_session.cancel_job.side_effect = ClientError(exception_response, "cancel_job") - with pytest.raises(ClientError, match=error_string): - quantum_job.cancel() - - -@pytest.mark.parametrize( - "generate_get_job_response_kwargs", - [ - { - "status": "RUNNING", - }, - { - "status": "COMPLETED", - }, - { - "status": "COMPLETED", - "startedAt": datetime.datetime(2021, 1, 1, 1, 0, 0, 0), - }, - {"status": "COMPLETED", "endedAt": datetime.datetime(2021, 1, 1, 1, 0, 0, 0)}, - { - "status": "COMPLETED", - "startedAt": datetime.datetime(2021, 1, 1, 1, 0, 0, 0), - "endedAt": datetime.datetime(2021, 1, 1, 1, 0, 0, 0), - }, - ], -) -@patch( - "braket.jobs.metrics_data.cwl_insights_metrics_fetcher." - "CwlInsightsMetricsFetcher.get_metrics_for_job" -) -def test_metrics( - metrics_fetcher_mock, - quantum_job, - aws_session, - generate_get_job_response, - generate_get_job_response_kwargs, -): - get_job_response_running = generate_get_job_response(**generate_get_job_response_kwargs) - aws_session.get_job.return_value = get_job_response_running - - expected_metrics = {"Test": [1]} - metrics_fetcher_mock.return_value = expected_metrics - metrics = quantum_job.metrics() - assert metrics == expected_metrics - - -@pytest.fixture -def log_stream_responses(): - return ( - ClientError( - { - "Error": { - "Code": "ResourceNotFoundException", - "Message": "This shouldn't get raised...", - } - }, - "DescribeLogStreams", - ), - {"logStreams": []}, - {"logStreams": [{"logStreamName": "stream-1"}]}, - ) - - -@pytest.fixture -def log_events_responses(): - return ( - {"nextForwardToken": None, "events": [{"timestamp": 1, "message": "hi there #1"}]}, - {"nextForwardToken": None, "events": []}, - { - "nextForwardToken": None, - "events": [ - {"timestamp": 1, "message": "hi there #1"}, - {"timestamp": 2, "message": "hi there #2"}, - ], - }, - {"nextForwardToken": None, "events": []}, - { - "nextForwardToken": None, - "events": [ - {"timestamp": 2, "message": "hi there #2"}, - {"timestamp": 2, "message": "hi there #2a"}, - {"timestamp": 3, "message": "hi there #3"}, - ], - }, - {"nextForwardToken": None, "events": []}, - ) - - -def test_logs( - quantum_job, - generate_get_job_response, - log_events_responses, - log_stream_responses, - 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="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( - ( - "..", - "hi there #1", - "hi there #2", - "hi there #2a", - "hi there #3", - "", - ) - ) - - -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, - generate_get_job_response, - log_events_responses, - log_stream_responses, - capsys, -): - quantum_job._aws_session.get_job.side_effect = ( - generate_get_job_response(status="RUNNING", instanceConfig={"instanceCount": 2}), - 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"}) - quantum_job._aws_session.describe_log_streams.side_effect = log_stream_responses - - event_counts = { - "stream-1": 0, - "stream-2": 0, - } - - def get_log_events(log_group, log_stream, start_time, start_from_head, next_token): - log_events_dict = { - "stream-1": log_events_responses, - "stream-2": log_events_responses, - } - log_events_dict["stream-1"] += ( - { - "nextForwardToken": None, - "events": [], - }, - { - "nextForwardToken": None, - "events": [], - }, - ) - log_events_dict["stream-2"] += ( - { - "nextForwardToken": None, - "events": [ - {"timestamp": 3, "message": "hi there #3"}, - {"timestamp": 4, "message": "hi there #4"}, - ], - }, - { - "nextForwardToken": None, - "events": [], - }, - ) - event_counts[log_stream] += 1 - return log_events_dict[log_stream][event_counts[log_stream]] - - quantum_job._aws_session.get_log_events.side_effect = get_log_events - - 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", - "", - ) - ) - - -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"), - ) - quantum_job._aws_session.describe_log_streams.side_effect = ( - ClientError( - { - "Error": { - "Code": "UnknownCode", - "Message": "Some error message", - } - }, - "DescribeLogStreams", - ), - ) - - with pytest.raises(ClientError, match="Some error message"): - quantum_job.logs(wait=True, poll_interval_seconds=0) - - -def test_initialize_session_for_valid_non_regional_device(aws_session, caplog): - device_arn = "arn:aws:braket:::device/qpu/test/device-name" - first_region = aws_session.region - logger = logging.getLogger(__name__) - - aws_session.get_device.side_effect = [ - ClientError( - { - "Error": { - "Code": "ResourceNotFoundException", - } - }, - "getDevice", - ), - ClientError( - { - "Error": { - "Code": "ResourceNotFoundException", - } - }, - "getDevice", - ), - device_arn, - ] - - caplog.set_level(logging.INFO) - AwsQuantumJob._initialize_session(aws_session, device_arn, logger) - - assert f"Changed session region from '{first_region}' to '{aws_session.region}'" in caplog.text - - -def test_initialize_session_for_valid_regional_device(aws_session, caplog): - device_arn = f"arn:aws:braket:{aws_session.region}::device/qpu/test/device-name" - logger = logging.getLogger(__name__) - aws_session.get_device.return_value = device_arn - caplog.set_level(logging.INFO) - AwsQuantumJob._initialize_session(aws_session, device_arn, logger) - assert not caplog.text - - -@pytest.mark.parametrize( - "get_device_side_effect, expected_exception", - [ - ( - [ - ClientError( - { - "Error": { - "Code": "ResourceNotFoundException", - } - }, - "getDevice", - ) - ], - ValueError, - ), - ( - [ - ClientError( - { - "Error": { - "Code": "ThrottlingException", - } - }, - "getDevice", - ) - ], - ClientError, - ), - ], -) -def test_regional_device_raises_error( - get_device_side_effect, expected_exception, aws_session, caplog -): - device_arn = f"arn:aws:braket:{aws_session.region}::device/qpu/test/device-name" - aws_session.get_device.side_effect = get_device_side_effect - logger = logging.getLogger(__name__) - caplog.set_level(logging.INFO) - with pytest.raises(expected_exception): - AwsQuantumJob._initialize_session(aws_session, device_arn, logger) - aws_session.get_device.assert_called_with(device_arn) - assert not caplog.text - - -def test_regional_device_switches(aws_session, caplog): - original_region = aws_session.region - device_region = "us-east-1" - device_arn = f"arn:aws:braket:{device_region}::device/qpu/test/device-name" - mock_session = Mock() - mock_session.get_device.side_effect = device_arn - aws_session.copy_session.side_effect = [mock_session] - logger = logging.getLogger(__name__) - caplog.set_level(logging.INFO) - - assert mock_session == AwsQuantumJob._initialize_session(aws_session, device_arn, logger) - - aws_session.copy_session.assert_called_with(region=device_region) - mock_session.get_device.assert_called_with(device_arn) - assert f"Changed session region from '{original_region}' to '{device_region}'" in caplog.text - - -def test_initialize_session_for_invalid_device(aws_session, device_arn): - logger = logging.getLogger(__name__) - aws_session.get_device.side_effect = ClientError( - { - "Error": { - "Code": "ResourceNotFoundException", - } - }, - "getDevice", - ) - - device_not_found = f"'{device_arn}' not found." - with pytest.raises(ValueError, match=device_not_found): - AwsQuantumJob._initialize_session(aws_session, device_arn, logger) - - -def test_no_region_routing_simulator(aws_session): - logger = logging.getLogger(__name__) - - aws_session.get_device.side_effect = ClientError( - { - "Error": { - "Code": "ResourceNotFoundException", - } - }, - "getDevice", - ) - - device_arn = "arn:aws:braket:::device/simulator/test/device-name" - device_not_found = f"Simulator '{device_arn}' not found in 'us-west-2'" - with pytest.raises(ValueError, match=device_not_found): - AwsQuantumJob._initialize_session(aws_session, device_arn, logger) - - -def test_exception_in_credentials_session_region(device_arn, aws_session): - logger = logging.getLogger(__name__) - - aws_session.get_device.side_effect = ClientError( - { - "Error": { - "Code": "SomeOtherErrorMessage", - } - }, - "getDevice", - ) - - error_message = ( - "An error occurred \\(SomeOtherErrorMessage\\) " - "when calling the getDevice operation: Unknown" - ) - with pytest.raises(ClientError, match=error_message): - AwsQuantumJob._initialize_session(aws_session, device_arn, logger) - - -def test_exceptions_in_all_device_regions(aws_session): - device_arn = "arn:aws:braket:::device/qpu/test/device-name" - logger = logging.getLogger(__name__) - - aws_session.get_device.side_effect = [ - ClientError( - { - "Error": { - "Code": "ResourceNotFoundException", - } - }, - "getDevice", - ), - ClientError( - { - "Error": { - "Code": "SomeOtherErrorMessage", - } - }, - "getDevice", - ), - ] - - error_message = ( - "An error occurred \\(SomeOtherErrorMessage\\) " - "when calling the getDevice operation: Unknown" - ) - with pytest.raises(ClientError, match=error_message): - AwsQuantumJob._initialize_session(aws_session, device_arn, logger) - - -@patch("braket.aws.aws_quantum_job.AwsSession") -def test_initialize_session_local_device(mock_new_session, aws_session): - logger = logging.getLogger(__name__) - device = "local:provider.device.name" - # don't change a provided AwsSession - assert AwsQuantumJob._initialize_session(aws_session, device, logger) == aws_session - # otherwise, create an AwsSession with the profile defaults - assert AwsQuantumJob._initialize_session(None, device, logger) == mock_new_session() - - -def test_bad_device_arn_format(aws_session): - logger = logging.getLogger(__name__) - device_not_found = ( - "Device ARN is not a valid format: bad-arn-format. For valid Braket ARNs, " - "see 'https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html'" - ) - - 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/aws/test_aws_quantum_task.py b/test/unit_tests/braket/aws/test_aws_quantum_task.py deleted file mode 100644 index 16a72da7..00000000 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ /dev/null @@ -1,1296 +0,0 @@ -# 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 asyncio -import json -import threading -import time -from unittest.mock import MagicMock, Mock, patch - -import pytest -from common_test_utils import MockS3 -from jsonschema import validate - -from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation -from braket.annealing.problem import Problem, ProblemType -from braket.aws import AwsQuantumTask -from braket.aws.aws_quantum_task import _create_annealing_device_params -from braket.aws.aws_session import AwsSession -from braket.aws.queue_information import QuantumTaskQueueInfo, QueueType -from braket.circuits import Circuit -from braket.circuits.gates import PulseGate -from braket.circuits.serialization import ( - IRType, - OpenQASMSerializationProperties, - QubitReferenceType, -) -from braket.device_schema import GateModelParameters, error_mitigation -from braket.device_schema.dwave import ( - Dwave2000QDeviceParameters, - DwaveAdvantageDeviceParameters, - DwaveDeviceParameters, -) -from braket.device_schema.ionq import IonqDeviceParameters -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.debias import Debias -from braket.ir.blackbird import Program as BlackbirdProgram -from braket.ir.openqasm import Program as OpenQASMProgram -from braket.pulse import Frame, Port, PulseSequence -from braket.tasks import ( - AnalogHamiltonianSimulationQuantumTaskResult, - AnnealingQuantumTaskResult, - GateModelQuantumTaskResult, - PhotonicModelQuantumTaskResult, -) - -S3_TARGET = AwsSession.S3DestinationFolder("foo", "bar") - -IONQ_ARN = "device/qpu/ionq" -RIGETTI_ARN = "device/qpu/rigetti" -OQC_ARN = "device/qpu/oqc" -SIMULATOR_ARN = "device/quantum-simulator" -XANADU_ARN = "device/qpu/xanadu" - -DEVICE_PARAMETERS = [ - (IONQ_ARN, IonqDeviceParameters), - (RIGETTI_ARN, RigettiDeviceParameters), - (OQC_ARN, OqcDeviceParameters), - (SIMULATOR_ARN, GateModelSimulatorDeviceParameters), -] - - -@pytest.fixture -def aws_session(): - mock = Mock() - _mock_metadata(mock, "RUNNING") - return mock - - -@pytest.fixture -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) - - -@pytest.fixture -def annealing_task(aws_session): - return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) - - -@pytest.fixture -def photonic_model_task(aws_session): - return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) - - -@pytest.fixture -def arn(): - return "foo:bar:arn" - - -@pytest.fixture -def circuit(): - return Circuit().h(0).cnot(0, 1) - - -@pytest.fixture -def problem(): - return Problem(ProblemType.ISING, linear={1: 3.14}, quadratic={(1, 2): 10.08}) - - -@pytest.fixture -def openqasm_program(): - return OpenQASMProgram(source="OPENQASM 3.0; h $0;") - - -@pytest.fixture -def blackbird_program(): - return BlackbirdProgram(source="Vac | q[0]") - - -@pytest.fixture -def pulse_sequence(): - return PulseSequence().set_frequency( - Frame( - frame_id="predefined_frame_1", - frequency=2e9, - port=Port(port_id="device_port_x0", dt=1e-9, properties={}), - phase=0, - is_predefined=True, - ), - 6e6, - ) - - -@pytest.fixture -def pulse_gate(pulse_sequence): - return PulseGate(pulse_sequence, 1, "my_PG") - - -@pytest.fixture -def em(): - return Debias() - - -@pytest.fixture -def ahs_problem(): - mock = Mock(spec=AnalogHamiltonianSimulation) - mock.to_ir.return_value.json.return_value = "Test AHS Problem" - return mock - - -def test_equality(arn, aws_session): - quantum_task_1 = AwsQuantumTask(arn, aws_session) - quantum_task_2 = AwsQuantumTask(arn, aws_session) - other_quantum_task = AwsQuantumTask("different:arn", aws_session) - non_quantum_task = quantum_task_1.id - - assert quantum_task_1 == quantum_task_2 - assert quantum_task_1 is not quantum_task_2 - assert quantum_task_1 != other_quantum_task - assert quantum_task_1 != non_quantum_task - - -def test_str(quantum_task): - expected = f"AwsQuantumTask('id/taskArn':'{quantum_task.id}')" - assert str(quantum_task) == expected - - -def test_hash(quantum_task): - assert hash(quantum_task) == hash(quantum_task.id) - - -def test_id_getter(arn, aws_session): - quantum_task = AwsQuantumTask(arn, aws_session) - assert quantum_task.id == arn - - -@pytest.mark.xfail(raises=AttributeError) -def test_no_id_setter(quantum_task): - quantum_task.id = 123 - - -def test_metadata(quantum_task): - metadata_1 = {"status": "RUNNING"} - quantum_task._aws_session.get_quantum_task.return_value = metadata_1 - assert quantum_task.metadata() == metadata_1 - quantum_task._aws_session.get_quantum_task.assert_called_with(quantum_task.id) - - metadata_2 = {"status": "COMPLETED"} - quantum_task._aws_session.get_quantum_task.return_value = metadata_2 - assert quantum_task.metadata(use_cached_value=True) == metadata_1 - - -def test_metadata_call_if_none(quantum_task): - metadata_1 = {"status": "RUNNING"} - quantum_task._aws_session.get_quantum_task.return_value = metadata_1 - assert quantum_task.metadata(use_cached_value=True) == metadata_1 - quantum_task._aws_session.get_quantum_task.assert_called_with(quantum_task.id) - - -def test_has_reservation_arn_from_metadata(quantum_task): - metadata_true = { - "associations": [ - { - "arn": "123", - "type": "RESERVATION_TIME_WINDOW_ARN", - } - ] - } - assert quantum_task._has_reservation_arn_from_metadata(metadata_true) - - metadata_false = { - "status": "RUNNING", - "associations": [ - { - "arn": "123", - "type": "other", - } - ], - } - assert not quantum_task._has_reservation_arn_from_metadata(metadata_false) - - -def test_queue_position(quantum_task): - state_1 = "QUEUED" - _mock_metadata(quantum_task._aws_session, state_1) - assert quantum_task.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._aws_session, state_2) - assert quantum_task.queue_position() == QuantumTaskQueueInfo( - queue_type=QueueType.NORMAL, queue_position=None, message=message - ) - - -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) - assert quantum_task.state() == state_1 - quantum_task._aws_session.get_quantum_task.assert_called_with(quantum_task.id) - - state_2 = "COMPLETED" - _mock_metadata(quantum_task._aws_session, state_2) - assert quantum_task.state(use_cached_value=True) == state_1 - - state_3 = "FAILED" - _mock_metadata(quantum_task._aws_session, state_3) - assert quantum_task.state() == state_3 - - state_4 = "CANCELLED" - _mock_metadata(quantum_task._aws_session, state_4) - assert quantum_task.state() == state_4 - - -def test_cancel(quantum_task): - future = quantum_task.async_result() - - assert not future.done() - quantum_task.cancel() - - assert quantum_task.result() is None - assert future.cancelled() - quantum_task._aws_session.cancel_quantum_task.assert_called_with(quantum_task.id) - - -def test_cancel_without_fetching_result(quantum_task): - quantum_task.cancel() - - assert quantum_task.result() is None - assert quantum_task._future.cancelled() - quantum_task._aws_session.cancel_quantum_task.assert_called_with(quantum_task.id) - - -def asyncio_get_event_loop_side_effect(*args, **kwargs): - yield ValueError("unit-test-exception") - mock = MagicMock() - while True: - yield mock - - -@patch("braket.aws.aws_quantum_task.asyncio") -def test_initialize_asyncio_event_loop_if_required(mock_asyncio, quantum_task): - mock_asyncio.get_event_loop.side_effect = asyncio_get_event_loop_side_effect() - mock_asyncio.set_event_loop.return_value = MagicMock() - mock_asyncio.new_event_loop.return_value = MagicMock() - - quantum_task._get_future() - - assert mock_asyncio.get_event_loop.call_count == 2 - assert mock_asyncio.set_event_loop.call_count == 1 - assert mock_asyncio.new_event_loop.call_count == 1 - - -def test_result_circuit(circuit_task): - _mock_metadata(circuit_task._aws_session, "COMPLETED") - _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_GATE_MODEL) - - expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_GATE_MODEL) - assert circuit_task.result() == expected - - 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) - - expected = AnnealingQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_ANNEALING) - assert annealing_task.result() == expected - - s3_bucket = annealing_task.metadata()["outputS3Bucket"] - s3_object_key = annealing_task.metadata()["outputS3Directory"] - annealing_task._aws_session.retrieve_s3_object_body.assert_called_with( - s3_bucket, f"{s3_object_key}/results.json" - ) - - -def test_result_photonic_model(photonic_model_task): - _mock_metadata(photonic_model_task._aws_session, "COMPLETED") - _mock_s3(photonic_model_task._aws_session, MockS3.MOCK_S3_RESULT_PHOTONIC_MODEL) - - expected = PhotonicModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_PHOTONIC_MODEL) - assert photonic_model_task.result() == expected - - s3_bucket = photonic_model_task.metadata()["outputS3Bucket"] - s3_object_key = photonic_model_task.metadata()["outputS3Directory"] - photonic_model_task._aws_session.retrieve_s3_object_body.assert_called_with( - s3_bucket, f"{s3_object_key}/results.json" - ) - - -def test_result_analog_hamiltonian_simulation(quantum_task): - _mock_metadata(quantum_task._aws_session, "COMPLETED") - _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_ANALOG_HAMILTONIAN_SIMULTION) - - expected = AnalogHamiltonianSimulationQuantumTaskResult.from_string( - MockS3.MOCK_S3_RESULT_ANALOG_HAMILTONIAN_SIMULTION - ) - assert quantum_task.result() == expected - - s3_bucket = quantum_task.metadata()["outputS3Bucket"] - s3_object_key = quantum_task.metadata()["outputS3Directory"] - quantum_task._aws_session.retrieve_s3_object_body.assert_called_with( - s3_bucket, f"{s3_object_key}/results.json" - ) - - -@pytest.mark.xfail(raises=TypeError) -def test_result_invalid_type(circuit_task): - _mock_metadata(circuit_task._aws_session, "COMPLETED") - _mock_s3(circuit_task._aws_session, json.dumps(MockS3.MOCK_TASK_METADATA)) - circuit_task.result() - - -def test_result_circuit_cached(circuit_task): - _mock_metadata(circuit_task._aws_session, "COMPLETED") - expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_GATE_MODEL) - circuit_task._result = expected - assert circuit_task.result() == expected - assert not circuit_task._aws_session.retrieve_s3_object_body.called - - -def test_no_result(circuit_task): - _mock_metadata(circuit_task._aws_session, "FAILED") - circuit_task._result = None - assert circuit_task.result() is None - assert not circuit_task._aws_session.retrieve_s3_object_body.called - - -@pytest.mark.parametrize( - "result_string", - [MockS3.MOCK_S3_RESULT_GATE_MODEL, MockS3.MOCK_S3_RESULT_GATE_MODEL_WITH_RESULT_TYPES], -) -def test_result_cached_future(circuit_task, result_string): - _mock_metadata(circuit_task._aws_session, "COMPLETED") - _mock_s3(circuit_task._aws_session, result_string) - circuit_task.result() - - _mock_s3(circuit_task._aws_session, "") - expected = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_GATE_MODEL) - assert circuit_task.result() == expected - - -@pytest.mark.parametrize( - "status, result", - [ - ("COMPLETED", GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_GATE_MODEL)), - ("FAILED", None), - ], -) -def test_async_result(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, "RUNNING") - _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 - - -@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) - result = quantum_task.result() - assert result is None - - -def test_timeout_completed(aws_session): - _mock_metadata(aws_session, "RUNNING") - _mock_s3(aws_session, MockS3.MOCK_S3_RESULT_GATE_MODEL) - - # Setup the poll timing such that the timeout will occur after one API poll - quantum_task = AwsQuantumTask( - "foo:bar:arn", - aws_session, - poll_timeout_seconds=0.5, - poll_interval_seconds=1, - ) - assert quantum_task.result() is None - _mock_metadata(aws_session, "COMPLETED") - assert quantum_task.state() == "COMPLETED" - assert quantum_task.result() == GateModelQuantumTaskResult.from_string( - MockS3.MOCK_S3_RESULT_GATE_MODEL - ) - # Cached status is still COMPLETED, so result should be fetched - _mock_metadata(aws_session, "RUNNING") - quantum_task._result = None - assert quantum_task.result() == GateModelQuantumTaskResult.from_string( - MockS3.MOCK_S3_RESULT_GATE_MODEL - ) - - -def test_timeout_no_result_terminal_state(aws_session): - _mock_metadata(aws_session, "RUNNING") - _mock_s3(aws_session, MockS3.MOCK_S3_RESULT_GATE_MODEL) - - # Setup the poll timing such that the timeout will occur after one API poll - quantum_task = AwsQuantumTask( - "foo:bar:arn", - aws_session, - poll_timeout_seconds=0.5, - poll_interval_seconds=1, - ) - assert quantum_task.result() is None - - _mock_metadata(aws_session, "FAILED") - assert quantum_task.result() is None - - -@pytest.mark.xfail(raises=ValueError) -def test_create_invalid_s3_folder(aws_session, arn, circuit): - AwsQuantumTask.create(aws_session, arn, circuit, ("bucket",), 1000) - - -@pytest.mark.xfail(raises=TypeError) -def test_create_invalid_task_specification(aws_session, arn): - mocked_task_arn = "task-arn-1" - aws_session.create_quantum_task.return_value = mocked_task_arn - AwsQuantumTask.create(aws_session, arn, "foo", S3_TARGET, 1000) - - -def test_create_openqasm_program(aws_session, arn, openqasm_program): - aws_session.create_quantum_task.return_value = arn - shots = 21 - AwsQuantumTask.create(aws_session, SIMULATOR_ARN, openqasm_program, S3_TARGET, shots) - - _assert_create_quantum_task_called_with( - aws_session, - SIMULATOR_ARN, - openqasm_program.json(), - S3_TARGET, - shots, - ) - - -def test_create_openqasm_program_em(aws_session, arn, openqasm_program, em): - aws_session.create_quantum_task.return_value = arn - shots = 21 - AwsQuantumTask.create( - aws_session, - IONQ_ARN, - openqasm_program, - S3_TARGET, - shots, - device_parameters={"errorMitigation": em}, - ) - _assert_create_quantum_task_called_with( - aws_session, - IONQ_ARN, - openqasm_program.json(), - S3_TARGET, - shots, - device_parameters=IonqDeviceParameters( - paradigmParameters=GateModelParameters(qubitCount=0), - errorMitigation=[error_mitigation.Debias()], - ), - ) - - -def test_create_openqasm_program_em_serialized(aws_session, arn, openqasm_program, em): - aws_session.create_quantum_task.return_value = arn - shots = 21 - AwsQuantumTask.create( - aws_session, - IONQ_ARN, - openqasm_program, - S3_TARGET, - shots, - device_parameters={"errorMitigation": em.serialize()}, - ) - _assert_create_quantum_task_called_with( - aws_session, - IONQ_ARN, - openqasm_program.json(), - S3_TARGET, - shots, - device_parameters=IonqDeviceParameters( - paradigmParameters=GateModelParameters(qubitCount=0), - errorMitigation=[error_mitigation.Debias()], - ), - ) - - -def test_create_blackbird_program(aws_session, arn, blackbird_program): - aws_session.create_quantum_task.return_value = arn - shots = 21 - AwsQuantumTask.create(aws_session, XANADU_ARN, blackbird_program, S3_TARGET, shots) - - _assert_create_quantum_task_called_with( - aws_session, - XANADU_ARN, - blackbird_program.json(), - S3_TARGET, - shots, - ) - - -def test_create_ahs_problem(aws_session, arn, ahs_problem): - aws_session.create_quantum_task.return_value = arn - shots = 21 - AwsQuantumTask.create(aws_session, SIMULATOR_ARN, ahs_problem, S3_TARGET, shots) - - _assert_create_quantum_task_called_with( - aws_session, - SIMULATOR_ARN, - ahs_problem.to_ir().json(), - S3_TARGET, - shots, - ) - - -def test_create_task_with_reservation_arn(aws_session, arn, ahs_problem): - aws_session.create_quantum_task.return_value = arn - shots = 21 - reservation_arn = ( - "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" - ) - AwsQuantumTask.create( - aws_session, - SIMULATOR_ARN, - ahs_problem, - S3_TARGET, - shots, - reservation_arn=reservation_arn, - ) - - _assert_create_quantum_task_called_with( - aws_session, - SIMULATOR_ARN, - ahs_problem.to_ir().json(), - S3_TARGET, - shots, - reservation_arn=reservation_arn, - ) - - -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_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) - - _assert_create_quantum_task_called_with( - aws_session, - SIMULATOR_ARN, - expected_program.json(), - S3_TARGET, - 10, - ) - - -@pytest.mark.parametrize("device_arn,device_parameters_class", DEVICE_PARAMETERS) -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_program = OpenQASMProgram(source=expected_openqasm, inputs={}) - - aws_session.create_quantum_task.return_value = arn - AwsQuantumTask.create(aws_session, device_arn, pulse_gate_circuit, S3_TARGET, 10) - - _assert_create_quantum_task_called_with( - aws_session, - device_arn, - expected_program.json(), - S3_TARGET, - 10, - device_parameters_class( - paradigmParameters=GateModelParameters( - qubitCount=pulse_gate_circuit.qubit_count, disableQubitRewiring=False - ) - ), - ) - - -@pytest.mark.parametrize("device_arn,device_parameters_class", DEVICE_PARAMETERS) -def test_create_circuit_with_shots(device_arn, device_parameters_class, aws_session, circuit): - mocked_task_arn = "task-arn-1" - aws_session.create_quantum_task.return_value = mocked_task_arn - shots = 53 - - task = AwsQuantumTask.create(aws_session, device_arn, circuit, S3_TARGET, shots) - assert task == AwsQuantumTask(mocked_task_arn, aws_session) - program = circuit.to_ir(ir_type=IRType.OPENQASM) - assert program.inputs == {} - - _assert_create_quantum_task_called_with( - aws_session, - device_arn, - program.json(), - S3_TARGET, - shots, - device_parameters_class( - paradigmParameters=GateModelParameters( - qubitCount=circuit.qubit_count, disableQubitRewiring=False - ) - ), - ) - - -def test_create_circuit_em(aws_session, circuit, em): - mocked_task_arn = "task-arn-1" - aws_session.create_quantum_task.return_value = mocked_task_arn - shots = 53 - - task = AwsQuantumTask.create( - aws_session, IONQ_ARN, circuit, S3_TARGET, shots, device_parameters={"errorMitigation": em} - ) - assert task == AwsQuantumTask(mocked_task_arn, aws_session) - program = circuit.to_ir(ir_type=IRType.OPENQASM) - assert program.inputs == {} - - _assert_create_quantum_task_called_with( - aws_session, - IONQ_ARN, - program.json(), - S3_TARGET, - shots, - IonqDeviceParameters( - paradigmParameters=GateModelParameters(qubitCount=circuit.qubit_count), - errorMitigation=[error_mitigation.Debias()], - ), - ) - - -@pytest.mark.parametrize("device_arn,device_parameters_class", DEVICE_PARAMETERS) -def test_create_circuit_with_input_params( - device_arn, device_parameters_class, aws_session, circuit -): - mocked_task_arn = "task-arn-1" - aws_session.create_quantum_task.return_value = mocked_task_arn - shots = 53 - inputs = {"beta": 3} - - task = AwsQuantumTask.create(aws_session, device_arn, circuit, S3_TARGET, shots, inputs=inputs) - assert task == AwsQuantumTask(mocked_task_arn, aws_session) - program = circuit.to_ir(ir_type=IRType.OPENQASM) - assert program.inputs == {} - program.inputs.update(inputs) - - _assert_create_quantum_task_called_with( - aws_session, - device_arn, - program.json(), - S3_TARGET, - shots, - device_parameters_class( - paradigmParameters=GateModelParameters( - qubitCount=circuit.qubit_count, disableQubitRewiring=False - ) - ), - ) - - -@pytest.mark.parametrize( - "device_arn,device_parameters_class", [(RIGETTI_ARN, RigettiDeviceParameters)] -) -def test_create_circuit_with_disabled_rewiring( - device_arn, device_parameters_class, aws_session, circuit -): - mocked_task_arn = "task-arn-1" - aws_session.create_quantum_task.return_value = mocked_task_arn - shots = 53 - - task = AwsQuantumTask.create( - aws_session, device_arn, circuit, S3_TARGET, shots, disable_qubit_rewiring=True - ) - assert task == AwsQuantumTask(mocked_task_arn, aws_session) - - _assert_create_quantum_task_called_with( - aws_session, - device_arn, - circuit.to_ir( - ir_type=IRType.OPENQASM, - serialization_properties=OpenQASMSerializationProperties(QubitReferenceType.PHYSICAL), - ).json(), - S3_TARGET, - shots, - device_parameters_class( - paradigmParameters=GateModelParameters( - qubitCount=circuit.qubit_count, disableQubitRewiring=True - ) - ), - ) - - -@pytest.mark.parametrize( - "device_arn,device_parameters_class, disable_qubit_rewiring", - [(RIGETTI_ARN, RigettiDeviceParameters, True), (RIGETTI_ARN, RigettiDeviceParameters, False)], -) -def test_create_circuit_with_verbatim( - device_arn, device_parameters_class, disable_qubit_rewiring, aws_session -): - circ = Circuit().add_verbatim_box(Circuit().h(0)) - mocked_task_arn = "task-arn-1" - aws_session.create_quantum_task.return_value = mocked_task_arn - shots = 1337 - - task = AwsQuantumTask.create( - aws_session, - device_arn, - circ, - S3_TARGET, - shots, - disable_qubit_rewiring=disable_qubit_rewiring, - ) - assert task == AwsQuantumTask(mocked_task_arn, aws_session) - - serialization_properties = OpenQASMSerializationProperties( - qubit_reference_type=QubitReferenceType.PHYSICAL - ) - - _assert_create_quantum_task_called_with( - aws_session, - device_arn, - circ.to_ir( - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - ).json(), - S3_TARGET, - shots, - device_parameters_class( - paradigmParameters=GateModelParameters( - qubitCount=circ.qubit_count, disableQubitRewiring=disable_qubit_rewiring - ) - ), - ) - - -@pytest.mark.xfail(raises=ValueError) -def test_create_circuit_with_shots_value_error(aws_session, arn, circuit): - mocked_task_arn = "task-arn-1" - aws_session.create_quantum_task.return_value = mocked_task_arn - AwsQuantumTask.create(aws_session, arn, circuit, S3_TARGET, 0) - - -@pytest.mark.parametrize( - "device_parameters,arn", - [ - ( - { - "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", - ), - ( - { - "deviceLevelParameters": { - "postprocessingType": "OPTIMIZATION", - "beta": 0.2, - "annealingOffsets": [3.67, 6.123], - "annealingSchedule": [[13.37, 10.08], [3.14, 1.618]], - "annealingDuration": 1, - "autoScale": False, - "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", - ), - pytest.param( - { - "deviceLevelParameters": { - "postprocessingType": "OPTIMIZATION", - "beta": 0.2, - "annealingOffsets": [3.67, 6.123], - "annealingSchedule": [[13.37, 10.08], [3.14, 1.618]], - "annealingDuration": 1, - "autoScale": False, - "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", - # this doesn't fail... yet - # marks=pytest.mark.xfail(reason='beta not a valid parameter for Advantage device'), - ), - pytest.param( - { - "deviceLevelParameters": { - "postprocessingType": "OPTIMIZATION", - "beta": 0.2, - "annealingOffsets": [3.67, 6.123], - "annealingSchedule": [[13.37, 10.08], [3.14, 1.618]], - "annealingDuration": 1, - "autoScale": False, - "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/fake_arn", - marks=pytest.mark.xfail(reason="Bad ARN"), - ), - ( - { - "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", - ), - ( - 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", - ), - ( - DwaveDeviceParameters.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/Advantage_system1", - ), - ( - DwaveAdvantageDeviceParameters.parse_obj( - { - "deviceLevelParameters": { - "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", - ), - ( - 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", - ), - ( - Dwave2000QDeviceParameters.parse_obj({"deviceLevelParameters": {}}), - "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6", - ), - pytest.param( - {}, - "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6", - ), - ], -) -def test_from_annealing(device_parameters, aws_session, arn, problem): - mocked_task_arn = "task-arn-1" - aws_session.create_quantum_task.return_value = mocked_task_arn - task = AwsQuantumTask.create( - aws_session, - arn, - problem, - S3_TARGET, - 1000, - device_parameters=device_parameters, - ) - assert task == AwsQuantumTask(mocked_task_arn, aws_session) - annealing_parameters = _create_annealing_device_params(device_parameters, device_arn=arn) - validate( - json.loads(annealing_parameters.json(exclude_none=True)), annealing_parameters.schema() - ) - _assert_create_quantum_task_called_with( - aws_session, - arn, - problem.to_ir().json(), - S3_TARGET, - 1000, - annealing_parameters, - ) - - -@pytest.mark.parametrize("device_arn,device_parameters_class", DEVICE_PARAMETERS) -def test_create_with_tags(device_arn, device_parameters_class, aws_session, circuit): - mocked_task_arn = "task-arn-tags" - aws_session.create_quantum_task.return_value = mocked_task_arn - shots = 53 - tags = {"state": "washington"} - - task = AwsQuantumTask.create(aws_session, device_arn, circuit, S3_TARGET, shots, tags=tags) - assert task == AwsQuantumTask(mocked_task_arn, aws_session) - _assert_create_quantum_task_called_with( - aws_session, - device_arn, - circuit.to_ir(ir_type=IRType.OPENQASM).json(), - S3_TARGET, - shots, - device_parameters_class( - paradigmParameters=GateModelParameters(qubitCount=circuit.qubit_count) - ), - tags, - ) - - -def test_init_new_thread(aws_session, arn): - tasks_list = [] - threading.Thread(target=_init_and_add_to_list, args=(aws_session, arn, tasks_list)).start() - time.sleep(0.1) - assert len(tasks_list) == 1 - - -@patch("braket.aws.aws_quantum_task.boto3.Session") -def test_aws_session_for_task_arn(mock_session): - region = "us-west-2" - arn = f"arn:aws:aqx:{region}:account_id:quantum-task:task_id" - mock_boto_session = Mock() - mock_session.return_value = mock_boto_session - mock_boto_session.region_name = region - aws_session = AwsQuantumTask._aws_session_for_task_arn(arn) - mock_session.assert_called_with(region_name=region) - assert aws_session.boto_session == mock_boto_session - - -def _init_and_add_to_list(aws_session, arn, task_list): - task_list.append(AwsQuantumTask(arn, aws_session)) - - -def _assert_create_quantum_task_called_with( - aws_session, - arn, - task_description, - s3_results_prefix, - shots, - device_parameters=None, - tags=None, - reservation_arn=None, -): - test_kwargs = { - "deviceArn": arn, - "outputS3Bucket": s3_results_prefix[0], - "outputS3KeyPrefix": s3_results_prefix[1], - "action": task_description, - "shots": shots, - } - - if device_parameters is not None: - test_kwargs["deviceParameters"] = device_parameters.json(exclude_none=True) - if tags is not None: - test_kwargs["tags"] = tags - if reservation_arn: - test_kwargs["associations"] = [ - { - "arn": reservation_arn, - "type": "RESERVATION_TIME_WINDOW_ARN", - } - ] - aws_session.create_quantum_task.assert_called_with(**test_kwargs) - - -def _mock_metadata(aws_session, state): - message = ( - f"'Task is in {state} status. AmazonBraket does not show queue position for this status.'" - ) - if state in AwsQuantumTask.TERMINAL_STATES or state in ["RUNNING", "CANCELLING"]: - aws_session.get_quantum_task.return_value = { - "status": state, - "outputS3Bucket": S3_TARGET.bucket, - "outputS3Directory": S3_TARGET.key, - "queueInfo": { - "queue": "QUANTUM_TASKS_QUEUE", - "position": "None", - "queuePriority": "Normal", - "message": message, - }, - } - else: - aws_session.get_quantum_task.return_value = { - "status": state, - "outputS3Bucket": S3_TARGET.bucket, - "outputS3Directory": S3_TARGET.key, - "queueInfo": { - "queue": "QUANTUM_TASKS_QUEUE", - "position": "2", - "queuePriority": "Normal", - }, - } - - -def _mock_s3(aws_session, result): - aws_session.retrieve_s3_object_body.return_value = result - - -@pytest.mark.parametrize("source_input", [{}, {"gamma": 0.15}, None]) -@pytest.mark.parametrize("device_arn", DEVICE_PARAMETERS[0]) -def test_program_inputs(aws_session, device_arn, source_input): - bell_qasm = """ - OPENQASM 3; - input float theta; - qubit[2] q; - bit[2] c; - h q[0]; - cnot q[0], q[1]; - c = measure q; - """ - openqasm_program = OpenQASMProgram(source=bell_qasm, inputs=source_input) - aws_session.create_quantum_task.return_value = arn - shots = 21 - inputs = {"theta": 0.2} - AwsQuantumTask.create( - aws_session, device_arn, openqasm_program, S3_TARGET, shots, inputs=inputs - ) - - assert openqasm_program.inputs == source_input - inputs_copy = openqasm_program.inputs.copy() if openqasm_program.inputs is not None else {} - inputs_copy.update(inputs) - openqasm_program = OpenQASMProgram(source=openqasm_program.source, inputs=inputs_copy) - - _assert_create_quantum_task_called_with( - aws_session, - device_arn, - openqasm_program.json(), - S3_TARGET, - shots, - ) 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 deleted file mode 100644 index 2c748ece..00000000 --- a/test/unit_tests/braket/aws/test_aws_quantum_task_batch.py +++ /dev/null @@ -1,205 +0,0 @@ -# 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 random -import uuid -from unittest.mock import Mock, PropertyMock, patch - -import pytest -from common_test_utils import MockS3 - -from braket.aws import AwsQuantumTaskBatch, AwsSession -from braket.circuits import Circuit -from braket.tasks import GateModelQuantumTaskResult - -S3_TARGET = AwsSession.S3DestinationFolder("foo", "bar") - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_creation(mock_create): - task_mock = Mock() - type(task_mock).id = PropertyMock(side_effect=uuid.uuid4) - task_mock.state.return_value = "RUNNING" - mock_create.return_value = task_mock - - batch_size = 10 - batch = AwsQuantumTaskBatch( - Mock(), - "foo", - _circuits(batch_size), - S3_TARGET, - 1000, - max_parallel=10, - reservaion_arn=( - "arn:aws:braket:us-west-2:123456789123:" - "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" - ), - ) - assert batch.size == batch_size - assert batch.tasks == [task_mock for _ in range(batch_size)] - assert len(batch.unfinished) == batch_size - assert not batch.unsuccessful - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_successful(mock_create): - task_mock = Mock() - type(task_mock).id = PropertyMock(side_effect=uuid.uuid4) - task_mock.state.return_value = "COMPLETED" - result = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_GATE_MODEL) - task_mock.result.return_value = result - mock_create.return_value = task_mock - - batch_size = 15 - batch = AwsQuantumTaskBatch( - Mock(), - "foo", - _circuits(batch_size), - S3_TARGET, - 1000, - max_parallel=10, - reservaion_arn=( - "arn:aws:braket:us-west-2:123456789123:" - "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" - ), - ) - assert batch.size == batch_size - assert not batch.unfinished - assert not batch.unsuccessful - assert batch.results() == [result for _ in range(batch_size)] - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_unsuccessful(mock_create): - task_mock = Mock() - task_id = uuid.uuid4() - type(task_mock).id = PropertyMock(return_value=task_id) - task_mock.state.return_value = random.choice(["CANCELLED", "FAILED"]) - task_mock.result.return_value = None - mock_create.return_value = task_mock - - batch = AwsQuantumTaskBatch( - Mock(), - "foo", - [Circuit().h(0).cnot(0, 1)], - S3_TARGET, - 1000, - max_parallel=10, - reservaion_arn=( - "arn:aws:braket:us-west-2:123456789123:" - "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" - ), - ) - assert not batch.unfinished - assert batch.unsuccessful == {task_id} - assert batch.results() == [None] - with pytest.raises(RuntimeError): - assert batch.results(fail_unsuccessful=True) == [None] - batch._unsuccessful = set() - with pytest.raises(RuntimeError): - batch.results(fail_unsuccessful=True, use_cached_value=False) - assert batch.unsuccessful == {task_id} - - -@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_retry(mock_create): - bad_task_mock = Mock() - type(bad_task_mock).id = PropertyMock(side_effect=uuid.uuid4) - bad_task_mock.state.return_value = random.choice(["CANCELLED", "FAILED"]) - bad_task_mock.result.return_value = None - - good_task_mock = Mock() - # task id already mocked when setting up bad_task_mock - good_task_mock.state.return_value = "COMPLETED" - result = GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_GATE_MODEL) - good_task_mock.result.return_value = result - - mock_create.side_effect = [bad_task_mock, good_task_mock, bad_task_mock, good_task_mock] - - batch = AwsQuantumTaskBatch( - Mock(), - "foo", - [Circuit().h(0).cnot(0, 1), Circuit().h(1).cnot(0, 1)], - S3_TARGET, - 1000, - max_parallel=10, - reservaion_arn=( - "arn:aws:braket:us-west-2:123456789123:" - "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" - ), - ) - assert not batch.unfinished - assert batch.results(max_retries=0) == [None, result] - - # Retrying should get rid of the failures - assert batch.results(fail_unsuccessful=True, max_retries=3, use_cached_value=False) == [ - result, - result, - ] - assert batch.unsuccessful == set() - - # Don't retry if there's nothing to retry - mock_create.side_effect = [bad_task_mock] - assert batch.retry_unsuccessful_tasks() - assert batch.unsuccessful == set() - - # Error if called before there are any results - batch._results = None - with pytest.raises(RuntimeError): - batch.retry_unsuccessful_tasks() - - -@patch("braket.aws.aws_quantum_task_batch.ThreadPoolExecutor") -def test_abort(mock_threadpool): - batch_size = 10 - num_workers = 2 - mock_threadpool().__exit__.side_effect = KeyboardInterrupt() - - with pytest.raises(KeyboardInterrupt): - AwsQuantumTaskBatch( - Mock(), - "foo", - _circuits(batch_size), - S3_TARGET, - 1000, - max_parallel=num_workers, - reservaion_arn=( - "arn:aws:braket:us-west-2:123456789123:" - "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" - ), - ) - - -@patch("concurrent.futures.ThreadPoolExecutor.submit") -def test_early_abort(mock_submit): - batch_size = 10 - num_workers = 2 - mock_submit.side_effect = [Mock(), KeyboardInterrupt()] - - with pytest.raises(KeyboardInterrupt): - AwsQuantumTaskBatch( - Mock(), - "foo", - _circuits(batch_size), - S3_TARGET, - 1000, - max_parallel=num_workers, - reservaion_arn=( - "arn:aws:braket:us-west-2:123456789123:" - "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" - ), - ) - - -def _circuits(batch_size): - return [Circuit().h(0).cnot(0, 1) for _ in range(batch_size)] diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py deleted file mode 100644 index fb0b309f..00000000 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ /dev/null @@ -1,1407 +0,0 @@ -# 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 -import time -from http.client import HTTPMessage -from pathlib import Path -from unittest.mock import MagicMock, Mock, patch - -import boto3 -import pytest -from botocore.exceptions import ClientError -from botocore.stub import Stubber - -import braket._schemas as braket_schemas -import braket._sdk as braket_sdk -from braket.aws import AwsSession - -TEST_S3_OBJ_CONTENTS = { - "TaskMetadata": { - "Id": "blah", - } -} - - -@pytest.fixture -def boto_session(): - _boto_session = Mock() - _boto_session.region_name = "us-west-2" - _boto_session.profile_name = "test-profile" - return _boto_session - - -@pytest.fixture -def braket_client(): - _braket_client = Mock() - _braket_client.meta.region_name = "us-west-2" - return _braket_client - - -@pytest.fixture -def aws_session(boto_session, braket_client, account_id): - _aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) - - _aws_session._sts = Mock() - _aws_session._sts.get_caller_identity.return_value = { - "Account": account_id, - } - _aws_session._s3 = Mock() - return _aws_session - - -@pytest.fixture -def aws_explicit_session(): - _boto_session = Mock() - _boto_session.region_name = "us-test-1" - _boto_session.profile_name = "test-profile" - - creds = Mock() - creds.access_key = "access key" - creds.secret_key = "secret key" - creds.token = "token" - creds.method = "explicit" - _boto_session.get_credentials.return_value = creds - - _aws_session = Mock() - _aws_session.boto_session = _boto_session - _aws_session._default_bucket = "amazon-braket-us-test-1-00000000" - _aws_session.default_bucket.return_value = _aws_session._default_bucket - _aws_session._custom_default_bucket = False - _aws_session.account_id = "00000000" - _aws_session.region = "us-test-1" - return _aws_session - - -@pytest.fixture -def aws_env_session(): - _boto_session = Mock() - _boto_session.region_name = "us-test-1" - _boto_session.profile_name = "test-profile" - - creds = Mock() - creds.method = "env" - _boto_session.get_credentials.return_value = creds - - _aws_session = Mock() - _aws_session.boto_session = _boto_session - _aws_session._default_bucket = "amazon-braket-us-test-1-00000000" - _aws_session.default_bucket.return_value = _aws_session._default_bucket - _aws_session._custom_default_bucket = False - _aws_session.account_id = "00000000" - _aws_session.region = "us-test-1" - return _aws_session - - -@pytest.fixture -def account_id(): - return "000000000" - - -@pytest.fixture -def job_role_name(): - return "AmazonBraketJobsExecutionRole-134534514345" - - -@pytest.fixture -def job_role_arn(job_role_name): - return f"arn:aws:iam::0000000000:role/{job_role_name}" - - -@pytest.fixture -def get_job_response(): - return { - "algorithmSpecification": { - "scriptModeConfig": { - "entryPoint": "my_file:start_here", - "s3Uri": "s3://amazon-braket-jobs/job-path/my_file.py", - } - }, - "checkpointConfig": { - "localPath": "/opt/omega/checkpoints", - "s3Uri": "s3://amazon-braket-jobs/job-path/checkpoints", - }, - "instanceConfig": { - "instanceCount": 1, - "instanceType": "ml.m5.large", - "volumeSizeInGb": 1, - }, - "jobArn": "arn:aws:braket:us-west-2:875981177017:job/job-test-20210628140446", - "jobName": "job-test-20210628140446", - "outputDataConfig": {"s3Path": "s3://amazon-braket-jobs/job-path/output"}, - "roleArn": "arn:aws:iam::875981177017:role/AmazonBraketJobRole", - "status": "RUNNING", - } - - -@pytest.fixture -def resource_not_found_response(): - return { - "Error": { - "Code": "ResourceNotFoundException", - "Message": "unit-test-error", - } - } - - -@pytest.fixture -def throttling_response(): - return { - "Error": { - "Code": "ThrottlingException", - "Message": "unit-test-error", - } - } - - -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) - - -def test_user_supplied_braket_client(): - boto_session = Mock() - boto_session.region_name = "foobar" - braket_client = Mock() - braket_client.meta.region_name = "foobar" - 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) - boto_session.client.assert_any_call( - "braket", - config=config, - endpoint_url="some-endpoint", - ) - - -def test_region(): - boto_region = "boto-region" - braket_region = "braket-region" - - boto_session = Mock() - boto_session.region_name = boto_region - braket_client = Mock() - braket_client.meta.region_name = braket_region - - assert ( - AwsSession( - boto_session=boto_session, - ).region - == boto_region - ) - - assert ( - AwsSession( - braket_client=braket_client, - ).region - == braket_region - ) - - regions_must_match = ( - "Boto Session region and Braket Client region must match and currently " - "they do not: Boto Session region is 'boto-region', but " - "Braket Client region is 'braket-region'." - ) - with pytest.raises(ValueError, match=regions_must_match): - AwsSession( - boto_session=boto_session, - braket_client=braket_client, - ) - - -def test_iam(aws_session): - aws_session._iam = Mock() - assert aws_session.iam_client - aws_session.boto_session.client.assert_not_called() - aws_session._iam = None - assert aws_session.iam_client - aws_session.boto_session.client.assert_called_with("iam", region_name="us-west-2") - - -def test_s3(aws_session): - assert aws_session.s3_client - aws_session.boto_session.client.assert_not_called() - aws_session._s3 = None - assert aws_session.s3_client - aws_session.boto_session.client.assert_called_with("s3", region_name="us-west-2") - - -def test_sts(aws_session): - assert aws_session.sts_client - aws_session.boto_session.client.assert_not_called() - aws_session._sts = None - assert aws_session.sts_client - aws_session.boto_session.client.assert_called_with("sts", region_name="us-west-2") - - -def test_logs(aws_session): - aws_session._logs = Mock() - assert aws_session.logs_client - aws_session.boto_session.client.assert_not_called() - aws_session._logs = None - assert aws_session.logs_client - aws_session.boto_session.client.assert_called_with("logs", region_name="us-west-2") - - -def test_ecr(aws_session): - aws_session._ecr = Mock() - assert aws_session.ecr_client - aws_session.boto_session.client.assert_not_called() - aws_session._ecr = None - assert aws_session.ecr_client - aws_session.boto_session.client.assert_called_with("ecr", region_name="us-west-2") - - -@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): - request = Mock() - request.headers = HTTPMessage() - boto_session = Mock() - boto_session.region_name = "foobar" - braket_client = Mock() - braket_client.meta.region_name = "foobar" - 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) - 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 - - -@patch("braket.aws.aws_session.active_trackers") -def test_add_cost_tracker_count(active_trackers_mock, aws_session): - request = Mock() - active_trackers_mock.return_value = {"A tracker"} - aws_session._add_cost_tracker_count_handler(request) - request.headers.add_header.assert_called_with("Braket-Trackers", "1") - active_trackers_mock.return_value = {} - aws_session._add_cost_tracker_count_handler(request) - request.headers.add_header.assert_called_with("Braket-Trackers", "0") - - -def test_retrieve_s3_object_body_success(boto_session): - bucket_name = "braket-integ-test" - filename = "tasks/test_task_1.json" - - mock_resource = Mock() - boto_session.resource.return_value = mock_resource - mock_object = Mock() - mock_resource.Object.return_value = mock_object - mock_body_object = Mock() - mock_object.get.return_value = {"Body": mock_body_object} - mock_read_object = Mock() - mock_body_object.read.return_value = mock_read_object - 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) - 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) - - 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) - - -@pytest.mark.xfail(raises=ClientError) -def test_retrieve_s3_object_body_client_error(boto_session): - bucket_name = "braket-integ-test" - filename = "tasks/test_task_1.json" - - mock_resource = Mock() - boto_session.resource.return_value = mock_resource - mock_object = Mock() - mock_resource.Object.return_value = mock_object - mock_object.get.side_effect = ClientError( - {"Error": {"Code": "ValidationException", "Message": "NoSuchKey"}}, "Operation" - ) - aws_session = AwsSession(boto_session=boto_session) - aws_session.retrieve_s3_object_body(bucket_name, filename) - - -def test_get_device(boto_session, braket_client): - return_val = {"deviceArn": "arn1", "deviceName": "name1"} - braket_client.get_device.return_value = return_val - aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) - metadata = aws_session.get_device("arn1") - assert return_val == metadata - - -def test_cancel_quantum_task(aws_session): - arn = "foo:bar:arn" - aws_session.braket_client.cancel_quantum_task.return_value = { - "quantumTaskArn": arn, - "cancellationStatus": "CANCELLING OR CANCELLED", - } - - assert aws_session.cancel_quantum_task(arn) is None - aws_session.braket_client.cancel_quantum_task.assert_called_with(quantumTaskArn=arn) - - -def test_create_quantum_task(aws_session): - arn = "foo:bar:arn" - aws_session.braket_client.create_quantum_task.return_value = {"quantumTaskArn": arn} - - kwargs = { - "backendArn": "arn:aws:us-west-2:abc:xyz:abc", - "cwLogGroupArn": "arn:aws:us-west-2:abc:xyz:abc", - "destinationUrl": "http://s3-us-west-2.amazonaws.com/task-output-bar-1/output.json", - "program": {"ir": '{"instructions":[]}', "qubitCount": 4}, - "shots": 1, - "deviceArn": "foo:bar:device_arn", - } - assert aws_session.create_quantum_task(**kwargs) == arn - aws_session.braket_client.create_quantum_task.assert_called_with(**kwargs) - - -def test_create_quantum_task_with_job_token(aws_session): - arn = "arn:aws:braket:us-west-2:1234567890:task/task-name" - job_token = "arn:aws:braket:us-west-2:1234567890:job/job-name" - aws_session.braket_client.create_quantum_task.return_value = {"quantumTaskArn": arn} - - kwargs = { - "backendArn": "arn:aws:us-west-2:abc:xyz:abc", - "cwLogGroupArn": "arn:aws:us-west-2:abc:xyz:abc", - "destinationUrl": "http://s3-us-west-2.amazonaws.com/task-output-foo-1/output.json", - "program": {"ir": '{"instructions":[]}', "qubitCount": 4}, - "shots": 1, - "deviceArn": "foo:bar:device_arn", - } - with patch.dict(os.environ, {"AMZN_BRAKET_JOB_TOKEN": job_token}): - assert aws_session.create_quantum_task(**kwargs) == arn - kwargs["jobToken"] = job_token - aws_session.braket_client.create_quantum_task.assert_called_with(**kwargs) - - -def test_get_quantum_task(aws_session): - arn = "foo:bar:arn" - status = "STATUS" - queue_info = ["QueueInfo"] - return_value = {"quantumTaskArn": arn, "status": status} - aws_session.braket_client.get_quantum_task.return_value = return_value - - assert aws_session.get_quantum_task(arn) == return_value - aws_session.braket_client.get_quantum_task.assert_called_with( - quantumTaskArn=arn, additionalAttributeNames=queue_info - ) - - -def test_get_quantum_task_retry(aws_session, throttling_response, resource_not_found_response): - arn = "foo:bar:arn" - status = "STATUS" - queue_info = ["QueueInfo"] - return_value = {"quantumTaskArn": arn, "status": status} - - aws_session.braket_client.get_quantum_task.side_effect = [ - ClientError(resource_not_found_response, "unit-test"), - ClientError(throttling_response, "unit-test"), - return_value, - ] - - assert aws_session.get_quantum_task(arn) == return_value - aws_session.braket_client.get_quantum_task.assert_called_with( - quantumTaskArn=arn, additionalAttributeNames=queue_info - ) - assert aws_session.braket_client.get_quantum_task.call_count == 3 - - -def test_get_quantum_task_fail_after_retries( - aws_session, throttling_response, resource_not_found_response -): - aws_session.braket_client.get_quantum_task.side_effect = [ - ClientError(resource_not_found_response, "unit-test"), - ClientError(throttling_response, "unit-test"), - ClientError(throttling_response, "unit-test"), - ] - - with pytest.raises(ClientError): - aws_session.get_quantum_task("some-arn") - assert aws_session.braket_client.get_quantum_task.call_count == 3 - - -def test_get_quantum_task_does_not_retry_other_exceptions(aws_session): - exception_response = { - "Error": { - "Code": "SomeOtherException", - "Message": "unit-test-error", - } - } - - aws_session.braket_client.get_quantum_task.side_effect = [ - ClientError(exception_response, "unit-test"), - ] - - with pytest.raises(ClientError): - aws_session.get_quantum_task("some-arn") - assert aws_session.braket_client.get_quantum_task.call_count == 1 - - -def test_get_job(aws_session, get_job_response): - arn = "arn:aws:braket:us-west-2:1234567890:job/job-name" - queue_info = ["QueueInfo"] - aws_session.braket_client.get_job.return_value = get_job_response - - assert aws_session.get_job(arn) == get_job_response - aws_session.braket_client.get_job.assert_called_with( - jobArn=arn, additionalAttributeNames=queue_info - ) - - -def test_get_job_retry( - aws_session, get_job_response, throttling_response, resource_not_found_response -): - arn = "arn:aws:braket:us-west-2:1234567890:job/job-name" - queue_info = ["QueueInfo"] - - aws_session.braket_client.get_job.side_effect = [ - ClientError(resource_not_found_response, "unit-test"), - ClientError(throttling_response, "unit-test"), - get_job_response, - ] - - assert aws_session.get_job(arn) == get_job_response - aws_session.braket_client.get_job.assert_called_with( - jobArn=arn, additionalAttributeNames=queue_info - ) - assert aws_session.braket_client.get_job.call_count == 3 - - -def test_get_job_fail_after_retries(aws_session, throttling_response, resource_not_found_response): - arn = "arn:aws:braket:us-west-2:1234567890:job/job-name" - queue_info = ["QueueInfo"] - - aws_session.braket_client.get_job.side_effect = [ - ClientError(resource_not_found_response, "unit-test"), - ClientError(throttling_response, "unit-test"), - ClientError(throttling_response, "unit-test"), - ] - - with pytest.raises(ClientError): - aws_session.get_job(arn) - aws_session.braket_client.get_job.assert_called_with( - jobArn=arn, additionalAttributeNames=queue_info - ) - assert aws_session.braket_client.get_job.call_count == 3 - - -def test_get_job_does_not_retry_other_exceptions(aws_session): - arn = "arn:aws:braket:us-west-2:1234567890:job/job-name" - queue_info = ["QueueInfo"] - exception_response = { - "Error": { - "Code": "SomeOtherException", - "Message": "unit-test-error", - } - } - - aws_session.braket_client.get_job.side_effect = [ - ClientError(exception_response, "unit-test"), - ] - - with pytest.raises(ClientError): - aws_session.get_job(arn) - aws_session.braket_client.get_job.assert_called_with( - jobArn=arn, additionalAttributeNames=queue_info - ) - assert aws_session.braket_client.get_job.call_count == 1 - - -def test_cancel_job(aws_session): - arn = "arn:aws:braket:us-west-2:1234567890:job/job-name" - cancel_job_response = { - "ResponseMetadata": { - "RequestId": "857b0893-2073-4ad6-b828-744af8400dfe", - "HTTPStatusCode": 200, - }, - "cancellationStatus": "CANCELLING", - "jobArn": "arn:aws:braket:us-west-2:1234567890:job/job-name", - } - aws_session.braket_client.cancel_job.return_value = cancel_job_response - - assert aws_session.cancel_job(arn) == cancel_job_response - aws_session.braket_client.cancel_job.assert_called_with(jobArn=arn) - - -@pytest.mark.parametrize( - "exception_type", - [ - "ResourceNotFoundException", - "ValidationException", - "AccessDeniedException", - "ThrottlingException", - "InternalServiceException", - "ConflictException", - ], -) -def test_cancel_job_surfaces_errors(exception_type, aws_session): - arn = "arn:aws:braket:us-west-2:1234567890:job/job-name" - exception_response = { - "Error": { - "Code": "SomeOtherException", - "Message": "unit-test-error", - } - } - - aws_session.braket_client.cancel_job.side_effect = [ - ClientError(exception_response, "unit-test"), - ] - - with pytest.raises(ClientError): - aws_session.cancel_job(arn) - aws_session.braket_client.cancel_job.assert_called_with(jobArn=arn) - assert aws_session.braket_client.cancel_job.call_count == 1 - - -@pytest.mark.parametrize( - "input,output", - [ - ( - {}, - [ - { - "deviceArn": "arn1", - "deviceName": "name1", - "deviceType": "SIMULATOR", - "deviceStatus": "ONLINE", - "providerName": "pname1", - }, - { - "deviceArn": "arn2", - "deviceName": "name2", - "deviceType": "SIMULATOR", - "deviceStatus": "OFFLINE", - "providerName": "pname1", - }, - { - "deviceArn": "arn3", - "deviceName": "name3", - "deviceType": "QPU", - "deviceStatus": "ONLINE", - "providerName": "pname2", - }, - ], - ), - ( - {"names": ["name1"]}, - [ - { - "deviceArn": "arn1", - "deviceName": "name1", - "deviceType": "SIMULATOR", - "deviceStatus": "ONLINE", - "providerName": "pname1", - }, - ], - ), - ( - {"types": ["SIMULATOR"]}, - [ - { - "deviceArn": "arn1", - "deviceName": "name1", - "deviceType": "SIMULATOR", - "deviceStatus": "ONLINE", - "providerName": "pname1", - }, - { - "deviceArn": "arn2", - "deviceName": "name2", - "deviceType": "SIMULATOR", - "deviceStatus": "OFFLINE", - "providerName": "pname1", - }, - ], - ), - ( - {"statuses": ["ONLINE"]}, - [ - { - "deviceArn": "arn1", - "deviceName": "name1", - "deviceType": "SIMULATOR", - "deviceStatus": "ONLINE", - "providerName": "pname1", - }, - { - "deviceArn": "arn3", - "deviceName": "name3", - "deviceType": "QPU", - "deviceStatus": "ONLINE", - "providerName": "pname2", - }, - ], - ), - ( - {"statuses": ["RETIRED"]}, - [ - { - "deviceArn": "arn4", - "deviceName": "name4", - "deviceType": "QPU", - "deviceStatus": "RETIRED", - "providerName": "pname3", - }, - ], - ), - ( - {"provider_names": ["pname2"]}, - [ - { - "deviceArn": "arn3", - "deviceName": "name3", - "deviceType": "QPU", - "deviceStatus": "ONLINE", - "providerName": "pname2", - }, - ], - ), - ( - { - "provider_names": ["pname2"], - "types": ["QPU"], - "statuses": ["ONLINE"], - "names": ["name3"], - }, - [ - { - "deviceArn": "arn3", - "deviceName": "name3", - "deviceType": "QPU", - "deviceStatus": "ONLINE", - "providerName": "pname2", - }, - ], - ), - ( - { - "provider_names": ["pname1"], - "types": ["SIMULATOR"], - "statuses": ["ONLINE"], - }, - [ - { - "deviceArn": "arn1", - "deviceName": "name1", - "deviceType": "SIMULATOR", - "deviceStatus": "ONLINE", - "providerName": "pname1", - }, - ], - ), - ], -) -def test_search_devices(input, output, aws_session): - return_value = [ - { - "devices": [ - { - "deviceArn": "arn1", - "deviceName": "name1", - "deviceType": "SIMULATOR", - "deviceStatus": "ONLINE", - "providerName": "pname1", - }, - { - "deviceArn": "arn2", - "deviceName": "name2", - "deviceType": "SIMULATOR", - "deviceStatus": "OFFLINE", - "providerName": "pname1", - }, - { - "deviceArn": "arn3", - "deviceName": "name3", - "deviceType": "QPU", - "deviceStatus": "ONLINE", - "providerName": "pname2", - }, - { - "deviceArn": "arn4", - "deviceName": "name4", - "deviceType": "QPU", - "deviceStatus": "RETIRED", - "providerName": "pname3", - }, - ] - } - ] - mock_paginator = Mock() - mock_iterator = MagicMock() - aws_session.braket_client.get_paginator.return_value = mock_paginator - mock_paginator.paginate.return_value = mock_iterator - mock_iterator.__iter__.return_value = return_value - - assert aws_session.search_devices(**input) == output - - -def test_search_devices_arns(aws_session): - return_value = [ - { - "devices": [ - { - "deviceArn": "arn1", - "deviceName": "name1", - "deviceType": "SIMULATOR", - "deviceStatus": "ONLINE", - "providerName": "pname1", - } - ] - } - ] - mock_paginator = Mock() - mock_iterator = MagicMock() - aws_session.braket_client.get_paginator.return_value = mock_paginator - mock_paginator.paginate.return_value = mock_iterator - mock_iterator.__iter__.return_value = return_value - - assert aws_session.search_devices(arns=["arn1"]) == return_value[0]["devices"] - mock_paginator.paginate.assert_called_with( - filters=[ - {"name": "deviceArn", "values": ["arn1"]}, - ], - PaginationConfig={"MaxItems": 100}, - ) - - -def test_create_job(aws_session): - arn = "foo:bar:arn" - aws_session.braket_client.create_job.return_value = {"jobArn": arn} - - kwargs = { - "jobName": "job-name", - "roleArn": "role-arn", - "algorithmSpecification": { - "scriptModeConfig": { - "entryPoint": "entry-point", - "s3Uri": "s3-uri", - "compressionType": "GZIP", - } - }, - } - assert aws_session.create_job(**kwargs) == arn - aws_session.braket_client.create_job.assert_called_with(**kwargs) - - -@pytest.mark.parametrize( - "string, valid", - ( - ("s3://bucket/key", True), - ("S3://bucket/key", True), - ("https://bucket-name-123.s3.us-west-2.amazonaws.com/key/with/dirs", True), - ("https://bucket-name-123.S3.us-west-2.amazonaws.com/key/with/dirs", True), - ("https://bucket-name-123.S3.us-west-2.amazonaws.com/", False), - ("https://bucket-name-123.S3.us-west-2.amazonaws.com", False), - ("https://S3.us-west-2.amazonaws.com", False), - ("s3://bucket/", False), - ("s3://bucket", False), - ("s3://////", False), - ("http://bucket/key", False), - ("bucket/key", False), - ), -) -def test_is_s3_uri(string, valid): - assert AwsSession.is_s3_uri(string) == valid - - -@pytest.mark.parametrize( - "uri, bucket, key", - ( - ( - "s3://bucket-name-123/key/with/multiple/dirs", - "bucket-name-123", - "key/with/multiple/dirs", - ), - ( - "s3://bucket-name-123/key-with_one.dirs", - "bucket-name-123", - "key-with_one.dirs", - ), - ( - "https://bucket-name-123.s3.us-west-2.amazonaws.com/key/with/dirs", - "bucket-name-123", - "key/with/dirs", - ), - ( - "https://bucket-name-123.S3.us-west-2.amazonaws.com/key/with/dirs", - "bucket-name-123", - "key/with/dirs", - ), - ), -) -def test_parse_s3_uri(uri, bucket, key): - assert bucket, key == AwsSession.parse_s3_uri(uri) - - -@pytest.mark.parametrize( - "uri", - ( - "s3://bucket.name-123/key-with_one.dirs", - "http://bucket-name-123/key/with/multiple/dirs", - "bucket-name-123/key/with/multiple/dirs", - "s3://bucket-name-123/", - "s3://bucket-name-123", - ), -) -def test_parse_s3_uri_invalid(uri): - with pytest.raises(ValueError, match=f"Not a valid S3 uri: {uri}"): - AwsSession.parse_s3_uri(uri) - - -@pytest.mark.parametrize( - "bucket, dirs", - [ - ("bucket", ("d1", "d2", "d3")), - ("bucket-123-braket", ("dir",)), - pytest.param( - "braket", - (), - marks=pytest.mark.xfail(raises=ValueError, strict=True), - ), - ], -) -def test_construct_s3_uri(bucket, dirs): - parsed_bucket, parsed_key = AwsSession.parse_s3_uri(AwsSession.construct_s3_uri(bucket, *dirs)) - assert parsed_bucket == bucket - assert parsed_key == "/".join(dirs) - - -def test_get_default_jobs_role(aws_session, job_role_arn, job_role_name): - iam_client = boto3.client("iam") - with Stubber(iam_client) as stub: - stub.add_response( - "list_roles", - { - "Roles": [ - { - "Arn": "arn:aws:iam::0000000000:role/nonJobsRole", - "RoleName": "nonJobsRole", - "Path": "/", - "RoleId": "nonJobsRole-213453451345-431513", - "CreateDate": time.time(), - } - ] - * 100, - "IsTruncated": True, - "Marker": "resp-marker", - }, - {"PathPrefix": "/service-role/"}, - ) - stub.add_response( - "list_roles", - { - "Roles": [ - { - "Arn": job_role_arn, - "RoleName": job_role_name, - "Path": "/", - "RoleId": f"{job_role_name}-213453451345-431513", - "CreateDate": time.time(), - } - ], - "IsTruncated": False, - }, - {"Marker": "resp-marker", "PathPrefix": "/service-role/"}, - ) - aws_session._iam = iam_client - assert aws_session.get_default_jobs_role() == job_role_arn - - -def test_get_default_jobs_role_not_found(aws_session, job_role_arn, job_role_name): - iam_client = boto3.client("iam") - with Stubber(iam_client) as stub: - stub.add_response( - "list_roles", - { - "Roles": [ - { - "Arn": "arn:aws:iam::0000000000:role/nonJobsRole", - "RoleName": "nonJobsRole", - "Path": "/", - "RoleId": "nonJobsRole-213453451345-431513", - "CreateDate": time.time(), - } - ] - * 100, - "IsTruncated": True, - "Marker": "resp-marker", - }, - {"PathPrefix": "/service-role/"}, - ) - stub.add_response( - "list_roles", - { - "Roles": [ - { - "Arn": "arn:aws:iam::0000000000:role/nonJobsRole2", - "RoleName": "nonJobsRole2", - "Path": "/", - "RoleId": "nonJobsRole2-213453451345-431513", - "CreateDate": time.time(), - } - ], - "IsTruncated": False, - }, - {"Marker": "resp-marker", "PathPrefix": "/service-role/"}, - ) - aws_session._iam = iam_client - with pytest.raises(RuntimeError): - aws_session.get_default_jobs_role() - - -def test_upload_to_s3(aws_session): - filename = "file.txt" - s3_uri = "s3://bucket-123/key" - bucket, key = "bucket-123", "key" - aws_session.upload_to_s3(filename, s3_uri) - 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) - - Path("input-dir", "pref-dir", "sub-pref-dir").mkdir(parents=True) - Path("input-dir", "not-pref-dir").mkdir() - - # these should all get uploaded - Path("input-dir", "pref-dir", "sub-pref-dir", "very-nested.txt").touch() - Path("input-dir", "pref-dir", "nested.txt").touch() - Path("input-dir", "pref.txt").touch() - Path("input-dir", "pref-and-more.txt").touch() - - # these should not - Path("input-dir", "false-pref.txt").touch() - Path("input-dir", "not-pref-dir", "pref-fake.txt").touch() - - aws_session.upload_to_s3 = Mock() - aws_session.upload_local_data("input-dir/pref", "s3://bucket/pref") - call_args = {args for args, kwargs in aws_session.upload_to_s3.call_args_list} - assert call_args == { - ( - str(Path("input-dir", "pref-dir", "sub-pref-dir", "very-nested.txt")), - "s3://bucket/pref-dir/sub-pref-dir/very-nested.txt", - ), - (str(Path("input-dir", "pref-dir", "nested.txt")), "s3://bucket/pref-dir/nested.txt"), - (str(Path("input-dir", "pref.txt")), "s3://bucket/pref.txt"), - (str(Path("input-dir", "pref-and-more.txt")), "s3://bucket/pref-and-more.txt"), - } - os.chdir("..") - - -def test_upload_local_data_absolute(aws_session): - with tempfile.TemporaryDirectory() 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() - - # these should all get uploaded - Path(temp_dir, "input-dir", "pref-dir", "sub-pref-dir", "very-nested.txt").touch() - Path(temp_dir, "input-dir", "pref-dir", "nested.txt").touch() - Path(temp_dir, "input-dir", "pref.txt").touch() - Path(temp_dir, "input-dir", "pref-and-more.txt").touch() - - # these should not - Path(temp_dir, "input-dir", "false-pref.txt").touch() - Path(temp_dir, "input-dir", "not-pref-dir", "pref-fake.txt").touch() - - aws_session.upload_to_s3 = Mock() - aws_session.upload_local_data(str(Path(temp_dir, "input-dir", "pref")), "s3://bucket/pref") - call_args = {args for args, kwargs in aws_session.upload_to_s3.call_args_list} - assert call_args == { - ( - str(Path(temp_dir, "input-dir", "pref-dir", "sub-pref-dir", "very-nested.txt")), - "s3://bucket/pref-dir/sub-pref-dir/very-nested.txt", - ), - ( - str(Path(temp_dir, "input-dir", "pref-dir", "nested.txt")), - "s3://bucket/pref-dir/nested.txt", - ), - (str(Path(temp_dir, "input-dir", "pref.txt")), "s3://bucket/pref.txt"), - ( - str(Path(temp_dir, "input-dir", "pref-and-more.txt")), - "s3://bucket/pref-and-more.txt", - ), - } - - -def test_download_from_s3(aws_session): - filename = "model.tar.gz" - s3_uri = ( - "s3://amazon-braket-jobs/job-path/output/" - "BraketJob-875981177017-job-test-20210628140446/output/model.tar.gz" - ) - bucket, key = ( - "amazon-braket-jobs", - "job-path/output/BraketJob-875981177017-job-test-20210628140446/output/model.tar.gz", - ) - aws_session.download_from_s3(s3_uri, filename) - aws_session._s3.download_file.assert_called_with(bucket, key, filename) - - -def test_copy_identical_s3(aws_session): - s3_uri = "s3://bucket/key" - aws_session.copy_s3_object(s3_uri, s3_uri) - aws_session.boto_session.client.return_value.copy.assert_not_called() - - -def test_copy_s3(aws_session): - source_s3_uri = "s3://here/now" - dest_s3_uri = "s3://there/then" - source_bucket, source_key = AwsSession.parse_s3_uri(source_s3_uri) - dest_bucket, dest_key = AwsSession.parse_s3_uri(dest_s3_uri) - aws_session.copy_s3_object(source_s3_uri, dest_s3_uri) - aws_session._s3.copy.assert_called_with( - { - "Bucket": source_bucket, - "Key": source_key, - }, - dest_bucket, - dest_key, - ) - - -def test_copy_identical_s3_directory(aws_session): - s3_uri = "s3://bucket/prefix/" - aws_session.copy_s3_directory(s3_uri, s3_uri) - aws_session.boto_session.client.return_value.copy.assert_not_called() - - -def test_copy_s3_directory(aws_session): - aws_session.list_keys = Mock(return_value=[f"now/key-{i}" for i in range(5)]) - source_s3_uri = "s3://here/now" - dest_s3_uri = "s3://there/then" - aws_session.copy_s3_directory(source_s3_uri, dest_s3_uri) - for i in range(5): - aws_session.s3_client.copy.assert_any_call( - { - "Bucket": "here", - "Key": f"now/key-{i}", - }, - "there", - f"then/key-{i}", - ) - - -def test_list_keys(aws_session): - bucket, prefix = "bucket", "prefix" - aws_session.s3_client.list_objects_v2.side_effect = [ - { - "IsTruncated": True, - "Contents": [ - {"Key": "copy-test/copy.txt"}, - {"Key": "copy-test/copy2.txt"}, - ], - "NextContinuationToken": "next-continuation-token", - }, - { - "IsTruncated": False, - "Contents": [ - {"Key": "copy-test/nested/double-nested/double-nested.txt"}, - {"Key": "copy-test/nested/nested.txt"}, - ], - }, - ] - keys = aws_session.list_keys(bucket, prefix) - assert keys == [ - "copy-test/copy.txt", - "copy-test/copy2.txt", - "copy-test/nested/double-nested/double-nested.txt", - "copy-test/nested/nested.txt", - ] - - -def test_default_bucket(aws_session, account_id): - region = "test-region-0" - aws_session.boto_session.region_name = region - assert aws_session.default_bucket() == f"amazon-braket-{region}-{account_id}" - - -def test_default_bucket_given(aws_session): - default_bucket = "default_bucket" - aws_session._default_bucket = default_bucket - assert aws_session.default_bucket() == default_bucket - aws_session._s3.create_bucket.assert_not_called() - - -@patch.dict("os.environ", {"AMZN_BRAKET_OUT_S3_BUCKET": "default_bucket_env"}) -def test_default_bucket_env_variable(boto_session, braket_client): - aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) - assert aws_session.default_bucket() == "default_bucket_env" - - -@pytest.mark.parametrize( - "region", - ( - "test-region-0", - "us-east-1", - ), -) -def test_create_s3_bucket_if_it_does_not_exist(aws_session, region, account_id): - bucket = f"amazon-braket-{region}-{account_id}" - aws_session._create_s3_bucket_if_it_does_not_exist(bucket, region) - kwargs = { - "Bucket": bucket, - "CreateBucketConfiguration": { - "LocationConstraint": region, - }, - } - if region == "us-east-1": - del kwargs["CreateBucketConfiguration"] - aws_session._s3.create_bucket.assert_called_with(**kwargs) - aws_session._s3.put_public_access_block.assert_called_with( - Bucket=bucket, - PublicAccessBlockConfiguration={ - "BlockPublicAcls": True, - "IgnorePublicAcls": True, - "BlockPublicPolicy": True, - "RestrictPublicBuckets": True, - }, - ) - - -@pytest.mark.parametrize( - "error", - ( - ClientError( - { - "Error": { - "Code": "BucketAlreadyOwnedByYou", - "Message": "Your previous request to create the named bucket succeeded " - "and you already own it.", - } - }, - "CreateBucket", - ), - ClientError( - { - "Error": { - "Code": "OperationAborted", - "Message": "A conflicting conditional operation is currently in progress " - "against this resource. Please try again.", - } - }, - "CreateBucket", - ), - pytest.param( - ClientError( - { - "Error": { - "Code": "OtherCode", - "Message": "This should fail properly.", - } - }, - "CreateBucket", - ), - marks=pytest.mark.xfail(raises=ClientError, strict=True), - ), - ), -) -def test_create_s3_bucket_if_it_does_not_exist_error(aws_session, error, account_id): - region = "test-region-0" - bucket = f"amazon-braket-{region}-{account_id}" - aws_session._s3.create_bucket.side_effect = error - aws_session._create_s3_bucket_if_it_does_not_exist(bucket, region) - - -@pytest.mark.xfail(raises=ValueError) -def test_bucket_already_exists_for_another_account(aws_session): - exception_response = { - "Error": { - "Code": "BucketAlreadyExists", - "Message": "This should fail properly.", - } - } - bucket_name, region = "some-bucket-123", "test-region" - aws_session._s3.create_bucket.side_effect = ClientError(exception_response, "CreateBucket") - aws_session._create_s3_bucket_if_it_does_not_exist(bucket_name, region) - - -@pytest.mark.parametrize( - "limit, next_token", - ( - (None, None), - (10, None), - (None, "next-token"), - (10, "next-token"), - ), -) -def test_describe_log_streams(aws_session, limit, next_token): - aws_session._logs = Mock() - - log_group = "log_group" - log_stream_prefix = "log_stream_prefix" - - describe_log_stream_args = { - "logGroupName": log_group, - "logStreamNamePrefix": log_stream_prefix, - "orderBy": "LogStreamName", - } - - if limit: - describe_log_stream_args["limit"] = limit - - if next_token: - describe_log_stream_args["nextToken"] = next_token - - aws_session.describe_log_streams(log_group, log_stream_prefix, limit, next_token) - - aws_session._logs.describe_log_streams.assert_called_with(**describe_log_stream_args) - - -@pytest.mark.parametrize( - "next_token", - (None, "next-token"), -) -def test_get_log_events(aws_session, next_token): - aws_session._logs = Mock() - - log_group = "log_group" - log_stream_name = "log_stream_name" - start_time = "timestamp" - start_from_head = True - - log_events_args = { - "logGroupName": log_group, - "logStreamName": log_stream_name, - "startTime": start_time, - "startFromHead": start_from_head, - } - - if 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) - - aws_session._logs.get_log_events.assert_called_with(**log_events_args) - - -@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" - 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 copied_session._default_bucket is None - - -@patch("boto3.Session") -def test_copy_explicit_session(boto_session_init, aws_explicit_session): - boto_session_init.return_value = Mock() - AwsSession.copy_session(aws_explicit_session, "us-west-2") - boto_session_init.assert_called_with( - aws_access_key_id="access key", - aws_secret_access_key="secret key", - aws_session_token="token", - region_name="us-west-2", - profile_name="test-profile", - ) - - -@patch("boto3.Session") -def test_copy_env_session(boto_session_init, aws_env_session): - boto_session_init.return_value = Mock() - AwsSession.copy_session(aws_env_session, "us-west-2") - boto_session_init.assert_called_with( - region_name="us-west-2", - ) - - -@patch("boto3.Session") -def test_copy_session_custom_default_bucket(mock_boto, aws_session): - mock_boto.return_value.region_name = "us-test-1" - aws_session._default_bucket = "my-own-default" - aws_session._custom_default_bucket = True - copied_session = AwsSession.copy_session(aws_session) - assert copied_session._default_bucket == "my-own-default" - - -def test_add_braket_user_agent(aws_session): - user_agent = "newAgent/1.0" - aws_session.add_braket_user_agent(user_agent) - aws_session.add_braket_user_agent(user_agent) - aws_session._braket_user_agents.count(user_agent) == 1 - - -def test_get_full_image_tag(aws_session): - aws_session.ecr_client.batch_get_image.side_effect = ( - {"images": [{"imageId": {"imageDigest": "my-digest"}}]}, - { - "images": [ - {"imageId": {"imageTag": "my-tag"}}, - {"imageId": {"imageTag": "my-tag-py3"}}, - {"imageId": {"imageTag": "my-tag-py310"}}, - {"imageId": {"imageTag": "latest"}}, - ] - }, - AssertionError("Image tag not cached"), - ) - image_uri = "123456.image_uri/repo-name:my-tag" - assert aws_session.get_full_image_tag(image_uri) == "my-tag-py310" - assert aws_session.get_full_image_tag(image_uri) == "my-tag-py310" - - -def test_get_full_image_tag_no_py_info(aws_session): - aws_session.ecr_client.batch_get_image.side_effect = ( - {"images": [{"imageId": {"imageDigest": "my-digest"}}]}, - { - "images": [ - {"imageId": {"imageTag": "my-tag"}}, - {"imageId": {"imageTag": "latest"}}, - ] - }, - ) - image_uri = "123456.image_uri/repo-name:my-tag" - - no_py_info = "Full image tag missing." - with pytest.raises(ValueError, match=no_py_info): - aws_session.get_full_image_tag(image_uri) diff --git a/test/unit_tests/braket/aws/test_direct_reservations.py b/test/unit_tests/braket/aws/test_direct_reservations.py deleted file mode 100644 index 332421e7..00000000 --- a/test/unit_tests/braket/aws/test_direct_reservations.py +++ /dev/null @@ -1,181 +0,0 @@ -# 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) diff --git a/test/unit_tests/braket/circuits/noise/test_gate_criteria.py b/test/unit_tests/braket/circuits/noise/test_gate_criteria.py deleted file mode 100644 index 3bd4f054..00000000 --- a/test/unit_tests/braket/circuits/noise/test_gate_criteria.py +++ /dev/null @@ -1,209 +0,0 @@ -# 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 Gate, Instruction, Observable -from braket.circuits.noise_model import ( - CriteriaKey, - CriteriaKeyResult, - GateCriteria, - ObservableCriteria, -) - - -@pytest.mark.parametrize( - "gates, qubits", - [ - (None, range(3)), - (None, None), - (Gate.H, range(3)), - ([Gate.H], range(3)), - ([Gate.I, Gate.H], range(3)), - (Gate.H, 1), - ([Gate.H], [1]), - ([Gate.I, Gate.H], [1, 0]), - (Gate.H, None), - ], -) -def test_happy_case(gates, qubits): - criteria = GateCriteria(gates=gates, qubits=qubits) - assert criteria.applicable_key_types() == [CriteriaKey.QUBIT, CriteriaKey.GATE] - if gates is None: - assert CriteriaKeyResult.ALL == criteria.get_keys(CriteriaKey.GATE) - else: - assert Gate.H in criteria.get_keys(CriteriaKey.GATE) - if qubits is None: - assert CriteriaKeyResult.ALL == criteria.get_keys(CriteriaKey.QUBIT) - else: - assert 1 in criteria.get_keys(CriteriaKey.QUBIT) - - -@pytest.mark.parametrize( - "gates, qubits", - [ - (None, range(3)), - (None, None), - (Gate.H, range(3)), - ([Gate.H], 1), - ([Gate.H], [1]), - ([Gate.H], [[1]]), - ([Gate.CNot], [0, 1]), - ([Gate.CNot], [[1, 2]]), - ([Gate.CNot], [[3, 4], [5, 6]]), - ([Gate.I, Gate.H], range(3)), - ([Gate.I, Gate.H], None), - ], -) -def test_serialization(gates, qubits): - test_criteria = GateCriteria(gates=gates, qubits=qubits) - serialized_criteria = test_criteria.to_dict() - assert serialized_criteria["__class__"] == "GateCriteria" - assert "gates" in serialized_criteria - assert "qubits" in serialized_criteria - deserialized_criteria = GateCriteria.from_dict(serialized_criteria) - assert test_criteria == deserialized_criteria - - -@pytest.mark.parametrize( - "gates, qubits, matching_instructions, non_matching_instructions", - [ - (None, 1, [Instruction(Gate.H(), 1)], [Instruction(Gate.CNot(), [0, 1])]), - ( - None, - None, - [Instruction(Gate.H(), 1), Instruction(Gate.CNot(), [0, 1])], - [Instruction(Observable.X, 0)], - ), - ( - Gate.H, - range(3), - [Instruction(Gate.H(), 0), Instruction(Gate.H(), 1)], - [ - Instruction(Gate.H(), 3), - Instruction(Gate.I(), 1), - [Instruction(Gate.H(), 0)], - ], - ), - (Gate.H, 1, [Instruction(Gate.H(), 1)], [Instruction(Gate.H(), 0)]), - ([Gate.H], [1], [Instruction(Gate.H(), 1)], [Instruction(Gate.H(), 0)]), - ( - [Gate.CNot], - [0, 1], - [Instruction(Gate.CNot(), [0, 1])], - [Instruction(Gate.CNot(), [1, 0])], - ), - ( - [Gate.CNot], - [[0, 1], [1, 2]], - [Instruction(Gate.CNot(), [0, 1]), Instruction(Gate.CNot(), [1, 2])], - [Instruction(Gate.CNot(), [1, 0])], - ), - ( - [Gate.I, Gate.H], - range(3), - [Instruction(Gate.H(), 0), Instruction(Gate.I(), 2)], - [Instruction(Gate.H(), 3), Instruction(Gate.X(), 1)], - ), - ( - Gate.H, - None, - [Instruction(Gate.H(), 0), Instruction(Gate.H(), 10)], - [Instruction(Gate.I(), 1), Instruction(Gate.X(), 3)], - ), - ( - Gate.H, - [], - [Instruction(Gate.H(), 0), Instruction(Gate.H(), 10)], - [Instruction(Gate.I(), 1), Instruction(Gate.X(), 3)], - ), - ], -) -def test_matcher(gates, qubits, matching_instructions, non_matching_instructions): - criteria = GateCriteria(gates=gates, qubits=qubits) - for instruction in matching_instructions: - assert criteria.instruction_matches(instruction) - for instruction in non_matching_instructions: - assert not criteria.instruction_matches(instruction) - - -@pytest.mark.parametrize( - "gates, qubits", - [ - (Gate.CNot, [[0, 1], 2]), - ], -) -@pytest.mark.xfail(raises=TypeError) -def test_invalid_type_params(gates, qubits): - GateCriteria(gates=gates, qubits=qubits) - - -@pytest.mark.parametrize( - "gates, qubits", - [ - ([Gate.H, Gate.CNot], 1), - (Gate.CNot, [[0, 1], [2]]), - ], -) -@pytest.mark.xfail(raises=ValueError) -def test_invalid_value_params(gates, qubits): - GateCriteria(gates=gates, qubits=qubits) - - -def test_representation(): - criteria = GateCriteria(gates=[Gate.I], qubits=0) - str_representation = criteria.__repr__() - assert str_representation == "GateCriteria(gates={'I'}, qubits={0})" - - -def test_string(): - criteria = GateCriteria(gates=[Gate.I], qubits=0) - str_representation = criteria.__str__() - assert str_representation == "GateCriteria({'I'}, {0})" - - -def test_get_keys_for_unknown_keytypes(): - criteria = GateCriteria(gates=[Gate.I], qubits=0) - result = criteria.get_keys(CriteriaKey.UNITARY_GATE) - assert len(result) == 0 - - -@pytest.mark.parametrize( - "criteria0, criteria1", - [ - (GateCriteria(Gate.H, range(3)), GateCriteria(Gate.H, range(3))), - (GateCriteria(Gate.H, [0, 1, 2]), GateCriteria(Gate.H, range(3))), - (GateCriteria(Gate.H, [1, 2, 0]), GateCriteria(Gate.H, range(3))), - (GateCriteria(Gate.H), GateCriteria(Gate.H, None)), - ( - GateCriteria([Gate.H, Gate.I], range(3)), - GateCriteria([Gate.I, Gate.H], range(3)), - ), - ], -) -def test_equal_criteria(criteria0, criteria1): - assert criteria0 == criteria1 - - -@pytest.mark.parametrize( - "criteria0, criteria1", - [ - (GateCriteria(Gate.H, range(3)), GateCriteria(Gate.I, range(3))), - (GateCriteria(Gate.H, [1, 2, 3]), GateCriteria(Gate.H, range(3))), - (GateCriteria(Gate.H), GateCriteria(Gate.H, range(3))), - (GateCriteria(Gate.H, range(3)), float(3)), - (GateCriteria(Gate.H, range(3)), ObservableCriteria(Observable.X, range(3))), - ], -) -def test_not_equal_criteria(criteria0, criteria1): - assert criteria0 != criteria1 diff --git a/test/unit_tests/braket/circuits/noise/test_noise_model.py b/test/unit_tests/braket/circuits/noise/test_noise_model.py deleted file mode 100644 index bf5974e4..00000000 --- a/test/unit_tests/braket/circuits/noise/test_noise_model.py +++ /dev/null @@ -1,490 +0,0 @@ -# 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 Mock - -import pytest - -from braket.circuits import Circuit, Gate, Noise, Observable -from braket.circuits.gates import Unitary -from braket.circuits.noise_model import ( - CircuitInstructionCriteria, - Criteria, - GateCriteria, - NoiseModel, - ObservableCriteria, - QubitInitializationCriteria, - UnitaryGateCriteria, -) -from braket.circuits.noises import BitFlip, Depolarizing, PauliChannel, TwoQubitDepolarizing - - -def h_unitary(): - return Unitary(Gate.H().to_matrix()) - - -@pytest.fixture -def default_noise_model(): - noise_list = [] - criteria_list = [] - noise_model = NoiseModel() - for i in range(3): - noise = Mock(spec=Noise) - criteria = Mock(spec=CircuitInstructionCriteria) - criteria.instruction_matches.return_value = i % 2 == 0 - noise_model.add_noise(noise, criteria) - noise_list.append(noise) - criteria_list.append(criteria) - return noise_model, noise_list, criteria_list - - -def test_simple_add_noise(): - noise_model = NoiseModel() - assert len(noise_model.instructions) == 0 - mock_noise = Mock(spec=Noise) - mock_criteria = Mock(spec=CircuitInstructionCriteria) - noise_model.add_noise(mock_noise, mock_criteria) - noise_list = noise_model.instructions - assert len(noise_list) == 1 - assert mock_noise == noise_list[0].noise - assert mock_criteria == noise_list[0].criteria - - -def test_insert_noise(default_noise_model): - noise_model, noise_list, criteria_list = default_noise_model - listed_noise = noise_model.instructions - assert len(listed_noise) == 3 - - mock_noise = Mock(spec=Noise) - mock_criteria = Mock(spec=CircuitInstructionCriteria) - - noise_model.insert_noise(1, mock_noise, mock_criteria) - - listed_noise = noise_model.instructions - assert len(listed_noise) == 4 - assert listed_noise[0].noise == noise_list[0] - assert listed_noise[0].criteria == criteria_list[0] - assert listed_noise[1].noise == mock_noise - assert listed_noise[1].criteria == mock_criteria - assert listed_noise[2].noise == noise_list[1] - assert listed_noise[2].criteria == criteria_list[1] - assert listed_noise[3].noise == noise_list[2] - assert listed_noise[3].criteria == criteria_list[2] - - -def test_remove_noise(default_noise_model): - noise_model, noise_list, _ = default_noise_model - - listed_noise = noise_model.instructions - assert len(listed_noise) == 3 - - noise_model.remove_noise(1) - - listed_noise = noise_model.instructions - assert len(listed_noise) == 2 - assert listed_noise[0].noise == noise_list[0] - assert listed_noise[1].noise == noise_list[2] - - -@pytest.mark.parametrize( - "gate, qubit, noise, noise_type, expected_length", - [ - (None, None, None, None, 6), - (None, None, PauliChannel, None, 2), - (Gate.H, None, None, None, 3), - (Gate.CNot, None, None, None, 2), - (None, 0, None, None, 5), - # (None, (0, 1), None, None, 1), # TODO: I'm not sure of the best way to fix this. - (Gate.CNot, (0, 1), None, None, 1), - (Gate.CNot, (1, 0), None, None, 0), - (Gate.H, 2, None, None, 1), - (Gate.H, None, Depolarizing, None, 2), - ], -) -def test_filter(gate, qubit, noise, noise_type, expected_length): - noise_model = ( - NoiseModel() - .add_noise(PauliChannel(0.01, 0.02, 0.03), GateCriteria(Gate.I, [0, 1])) - .add_noise(Depolarizing(0.04), GateCriteria(Gate.H)) - .add_noise(Depolarizing(0.05), GateCriteria(Gate.CNot, [0, 1])) - .add_noise(PauliChannel(0.06, 0.07, 0.08), GateCriteria(Gate.H, [0, 1])) - .add_noise(Depolarizing(0.09), GateCriteria(None, 0)) - .add_noise(Depolarizing(0.10), QubitInitializationCriteria([0, 1])) - ) - result_model = noise_model.from_filter(qubit=qubit, gate=gate, noise=noise) - assert len(result_model.instructions) == expected_length - - -@pytest.mark.parametrize( - "noise_types, expected_string", - [ - ([], ""), - ([Criteria], ""), - ( - [GateCriteria], - "Gate Noise:\n my_noise 0, my_criteria 0\n my_noise 1, my_criteria 1", - ), - ( - [Criteria, GateCriteria, ObservableCriteria, QubitInitializationCriteria], - "Initialization Noise:\n my_noise 0, my_criteria 0\n my_noise 1, my_criteria 1\n" - "Gate Noise:\n my_noise 0, my_criteria 0\n my_noise 1, my_criteria 1\n" - "Readout Noise:\n my_noise 0, my_criteria 0\n my_noise 1, my_criteria 1", - ), - ], -) -def test_str_representation(noise_types, expected_string): - noise_model = NoiseModel() - - for noise_type in noise_types: - for index in range(2): - mock_noise = Mock(spec=Noise) - mock_noise.__str__ = Mock(return_value=f"my_noise {index}") - mock_criteria = Mock(spec=noise_type) - mock_criteria.__str__ = Mock(return_value=f"my_criteria {index}") - noise_model.add_noise(mock_noise, mock_criteria) - - str_representation = noise_model.__str__() - assert str_representation == expected_string - - -def test_repr_representation(): - noise_model = NoiseModel() - mock_noise = Mock(spec=Noise) - mock_noise.__repr__ = Mock(return_value="n") - mock_criteria = Mock(spec=Criteria) - mock_criteria.__repr__ = Mock(return_value="c") - noise_model.add_noise(mock_noise, mock_criteria) - str_representation = noise_model.__repr__() - assert str_representation == "{'instructions': [NoiseModelInstruction(noise=n, criteria=c)]}" - - -def test_serialization(): - noise_model = ( - NoiseModel() - .add_noise(PauliChannel(0.01, 0.02, 0.03), GateCriteria(Gate.I, [0, 1])) - .add_noise(Depolarizing(0.04), GateCriteria(Gate.H)) - .add_noise(Depolarizing(0.05), GateCriteria(Gate.CNot, [0, 1])) - .add_noise(PauliChannel(0.06, 0.07, 0.08), GateCriteria(Gate.H, [0, 1])) - ) - serialized_model = noise_model.to_dict() - deserialized_model = NoiseModel.from_dict(serialized_model) - assert len(deserialized_model.instructions) == len(noise_model.instructions) - for index, deserialized_item in enumerate(deserialized_model.instructions): - assert noise_model.instructions[index].noise == deserialized_item.noise - assert noise_model.instructions[index].criteria == deserialized_item.criteria - assert deserialized_model is not None - - -def test_apply(): - noise_model = ( - NoiseModel() - .add_noise(PauliChannel(0.01, 0.02, 0.03), GateCriteria(Gate.I, [0, 1])) - .add_noise(Depolarizing(0.04), GateCriteria(Gate.H)) - .add_noise(TwoQubitDepolarizing(0.05), GateCriteria(Gate.CNot, [0, 1])) - .add_noise(PauliChannel(0.06, 0.07, 0.08), GateCriteria(Gate.H, [0, 1])) - .add_noise(Depolarizing(0.10), UnitaryGateCriteria(h_unitary(), 0)) - .add_noise(Depolarizing(0.06), ObservableCriteria(Observable.Z, 0)) - .add_noise(Depolarizing(0.09), QubitInitializationCriteria(0)) - ) - layer1 = Circuit().h(0).cnot(0, 1).sample(Observable.Z(), 0) - layer2 = Circuit().unitary([0], h_unitary().to_matrix()) - circuit = layer1 + layer2 - noisy_circuit_from_circuit = noise_model.apply(circuit) - expected_circuit = ( - Circuit() - .depolarizing(0, 0.09) - .h(0) - .depolarizing(0, 0.04) - .pauli_channel(0, 0.06, 0.07, 0.08) - .cnot(0, 1) - .two_qubit_depolarizing(0, 1, 0.05) - .unitary([0], h_unitary().to_matrix()) - .depolarizing(0, 0.10) - .apply_readout_noise(Depolarizing(0.06), 0) - .sample(Observable.Z(), 0) - ) - assert noisy_circuit_from_circuit == expected_circuit - - -def test_apply_in_order(): - noise_model = ( - NoiseModel() - .add_noise(Depolarizing(0.01), GateCriteria(Gate.H)) - .add_noise(Depolarizing(0.02), GateCriteria(Gate.H)) - ) - circuit = Circuit().h(0) - noisy_circuit = noise_model.apply(circuit) - expected_circuit = circuit.apply_gate_noise([Depolarizing(0.01), Depolarizing(0.02)]) - assert noisy_circuit == expected_circuit - - -@pytest.mark.parametrize( - "noise_model, input_circuit, expected_circuit", - [ - ( - # model with noise on H(0) - NoiseModel().add_noise(Depolarizing(0.01), GateCriteria(Gate.H, 0)), - # input circuit has an H gate on qubit 0 - Circuit().h(0).cnot(0, 1), - # expected circuit has noise on qubit 0 - Circuit().h(0).depolarizing(0, 0.01).cnot(0, 1), - ), - ( - # model with noise on H(0) - NoiseModel().add_noise(Depolarizing(0.01), GateCriteria(Gate.H, 0)), - # input circuit has two H gates on qubit 0 - Circuit().h(0).h(0).cnot(0, 1), - # expected circuit has noise on qubit 0 - Circuit().h(0).depolarizing(0, 0.01).h(0).depolarizing(0, 0.01).cnot(0, 1), - ), - ( - # model with noise on all gates, on qubits 0, 1 - NoiseModel().add_noise(Depolarizing(0.01), GateCriteria(None, [0, 1])), - # input circuit - Circuit().h(0).h(1).cnot(0, 1), - # expected circuit has noise on qubit 0 - Circuit().h(0).depolarizing(0, 0.01).h(1).depolarizing(1, 0.01).cnot(0, 1), - ), - ( - # model with noise on all gates, on qubits [0, 1] - NoiseModel().add_noise(Depolarizing(0.01), GateCriteria(None, [[0, 1]])), - # input circuit - Circuit().h(0).h(1).cnot(0, 1), - # expected circuit has noise on the CNot gate - Circuit().h(0).h(1).cnot(0, 1).depolarizing(0, 0.01).depolarizing(1, 0.01), - ), - ( - # model with noise on all gates, on qubits [0, 1] - NoiseModel().add_noise(TwoQubitDepolarizing(0.01), GateCriteria(None, [[0, 1]])), - # input circuit - Circuit().h(0).h(1).cnot(0, 1), - # expected circuit has noise on the CNot gate - Circuit().h(0).h(1).cnot(0, 1).two_qubit_depolarizing(0, 1, 0.01), - ), - ( - # model with noise on a unitary H(0) - NoiseModel().add_noise(Depolarizing(0.01), UnitaryGateCriteria(h_unitary(), 0)), - # input circuit has a unitary H gate on qubit 0 - Circuit().unitary([0], h_unitary().to_matrix()).cnot(0, 1), - # expected circuit has noise on qubit 0 - Circuit().unitary([0], h_unitary().to_matrix()).depolarizing(0, 0.01).cnot(0, 1), - ), - ], -) -def test_gate_noise(noise_model, input_circuit, expected_circuit): - result_circuit = noise_model.apply(input_circuit) - assert result_circuit == expected_circuit - - -@pytest.mark.parametrize( - "noise_model, input_circuit, expected_circuit", - [ - ( - # model - NoiseModel().add_noise(Depolarizing(0.01), QubitInitializationCriteria()), - # input circuit - Circuit().h(0).cnot(0, 1), - # expected circuit has noise on both qubits - Circuit().depolarizing(0, 0.01).depolarizing(1, 0.01).h(0).cnot(0, 1), - ), - ( - # model - NoiseModel().add_noise(Depolarizing(0.01), QubitInitializationCriteria(range(4))), - # input circuit - Circuit().h(0).cnot(0, 1), - # expected circuit has noise on both qubits - Circuit().depolarizing(0, 0.01).depolarizing(1, 0.01).h(0).cnot(0, 1), - ), - ( - # model only specifies noise on one qubit - NoiseModel().add_noise(Depolarizing(0.01), QubitInitializationCriteria(1)), - # input circuit - Circuit().h(0).cnot(0, 1), - # expected circuit has noise on one qubit - Circuit().depolarizing(1, 0.01).h(0).cnot(0, 1), - ), - ( - # model only specifies noise on an unrelated qubit - NoiseModel().add_noise(Depolarizing(0.01), QubitInitializationCriteria(2)), - # input circuit - Circuit().h(0).cnot(0, 1), - # expected circuit has no noise applied - Circuit().h(0).cnot(0, 1), - ), - ( - # model does not specify initialization noise - NoiseModel().add_noise(Depolarizing(0.01), GateCriteria(Gate.X, 2)), - # input circuit - Circuit().h(0).cnot(0, 1), - # expected circuit has no noise applied - Circuit().h(0).cnot(0, 1), - ), - ], -) -def test_apply_initialization_noise(noise_model, input_circuit, expected_circuit): - result_circuit = noise_model.apply(input_circuit) - assert result_circuit == expected_circuit - - -@pytest.mark.parametrize( - "noise_model, input_circuit, expected_circuit", - [ - ( - # model - NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(Observable.Z)), - # input circuit has no explicit observables - Circuit().h(0).cnot(0, 1), - # expected circuit has no noise applied - Circuit().h(0).cnot(0, 1), - ), - ( - # model has observable criteria only on one qubit - NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(Observable.Z, 0)), - # input circuit has no explicit observables - Circuit().h(0).cnot(0, 1), - # expected circuit has no noise applied - Circuit().h(0).cnot(0, 1), - ), - ( - # model - NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(Observable.Z)), - # input circuit has explicit explicit observables - Circuit().h(0).cnot(0, 1).sample(Observable.Z(), 0), - # expected circuit has noise applied - Circuit().h(0).cnot(0, 1).depolarizing(0, 0.01).sample(Observable.Z(), 0), - ), - ( - # model - NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(Observable.X, 0)), - # input circuit doesn't contain observable X - Circuit().h(0).cnot(0, 1), - # expected circuit has no change. - Circuit().h(0).cnot(0, 1), - ), - ( - # model - NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(Observable.X, 0)), - # input circuit contains observable X - Circuit().h(0).cnot(0, 1).sample(Observable.X(), 0), - # expected circuit noise applied. - Circuit().h(0).cnot(0, 1).depolarizing(0, 0.01).sample(Observable.X(), 0), - ), - ( - # model only has an observable on Z - NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(Observable.Z, 0)), - # input circuit contains observable X - Circuit().h(0).cnot(0, 1).sample(Observable.X(), 0), - # expected circuit has no change - Circuit().h(0).cnot(0, 1).sample(Observable.X(), 0), - ), - ( - # model uses qubit criteria - NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(None, None)), - # input circuit doesn't contain observables - Circuit().h(0).cnot(0, 1), - # expected circuit has no noise applied - Circuit().h(0).cnot(0, 1), - ), - ( - # model uses qubit criteria on non-related qubits - NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(None, [2, 3])), - # input circuit doesn't contain observables - Circuit().h(0).cnot(0, 1), - # expected circuit has no change - Circuit().h(0).cnot(0, 1), - ), - ( - # model uses qubit criteria - NoiseModel().add_noise(Depolarizing(0.01), ObservableCriteria(None, [0, 1])), - # input circuit contains observable X - Circuit().h(0).cnot(0, 1).sample(Observable.X(), 0), - # expected circuit noise applied. - Circuit().h(0).cnot(0, 1).depolarizing(0, 0.01).sample(Observable.X(), 0), - ), - ( - # model uses observable and qubit criteria - NoiseModel() - .add_noise(Depolarizing(0.01), ObservableCriteria(Observable.X, 0)) - .add_noise(Depolarizing(0.02), ObservableCriteria(None, [0, 1])), - # input circuit contains observable X - Circuit().h(0).cnot(0, 1).sample(Observable.X(), 0), - # expected circuit noise applied. - Circuit() - .h(0) - .cnot(0, 1) - .depolarizing(0, 0.01) - .depolarizing(0, 0.02) - .sample(Observable.X(), 0), - ), - ( - # model uses observable criteria with any observable/qubit. - NoiseModel().add_noise(BitFlip(0.01), ObservableCriteria(None, None)), - # input circuit contains many different types of result types for qubit 0 - Circuit() - .h(0) - .cnot(0, 1) - .probability(target=[0, 1]) - .probability(target=0) - .expectation(observable=Observable.Z(), target=0) - .sample(observable=Observable.X(), target=0) - .variance(observable=Observable.Z(), target=0), - # expected circuit only applies BitFlip once to qubit 0 - Circuit() - .h(0) - .cnot(0, 1) - .probability(target=[0, 1]) - .probability(target=0) - .expectation(observable=Observable.Z(), target=0) - .sample(observable=Observable.X(), target=0) - .variance(observable=Observable.Z(), target=0) - .apply_readout_noise(BitFlip(0.01), 0), - ), - ( - # model uses observable criteria with any observable/qubit. - NoiseModel().add_noise(BitFlip(0.01), ObservableCriteria(None, None)), - # input circuit only has a probability result type - Circuit().h(0).cnot(0, 1).probability(target=[0, 1]).probability(target=0), - # expected circuit has no noise applied - Circuit().h(0).cnot(0, 1).probability(target=[0, 1]).probability(target=0), - ), - ], -) -def test_apply_readout_noise(noise_model, input_circuit, expected_circuit): - result_circuit = noise_model.apply(input_circuit) - assert result_circuit == expected_circuit - - -@pytest.mark.xfail(raises=IndexError) -def test_remove_noise_at_invalid_index(): - noise_model = NoiseModel() - noise_model.remove_noise(index=0) - 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)) - - -@pytest.mark.xfail(raises=ValueError) -def test_add_invalid_criteria(): - noise_model = NoiseModel() - 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([]) diff --git a/test/unit_tests/braket/circuits/noise/test_observable_criteria.py b/test/unit_tests/braket/circuits/noise/test_observable_criteria.py deleted file mode 100644 index 406a3c7a..00000000 --- a/test/unit_tests/braket/circuits/noise/test_observable_criteria.py +++ /dev/null @@ -1,191 +0,0 @@ -# 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 Observable, ResultType -from braket.circuits.noise_model import CriteriaKey, CriteriaKeyResult, ObservableCriteria - - -@pytest.mark.parametrize( - "observables, qubits", - [ - (None, range(3)), - (None, None), - (Observable.X, range(3)), - ([Observable.X], range(3)), - ([Observable.X, Observable.Y], range(3)), - (Observable.X, 1), - ([Observable.X], [1]), - ([Observable.X, Observable.Y], [1, 0]), - (Observable.X, None), - ], -) -def test_happy_case(observables, qubits): - criteria = ObservableCriteria(observables=observables, qubits=qubits) - assert criteria.applicable_key_types() == [ - CriteriaKey.OBSERVABLE, - CriteriaKey.QUBIT, - ] - if observables is None: - assert CriteriaKeyResult.ALL == criteria.get_keys(CriteriaKey.OBSERVABLE) - else: - assert Observable.X in criteria.get_keys(CriteriaKey.OBSERVABLE) - if qubits is None: - assert CriteriaKeyResult.ALL == criteria.get_keys(CriteriaKey.QUBIT) - else: - assert 1 in criteria.get_keys(CriteriaKey.QUBIT) - - -@pytest.mark.parametrize( - "observables, qubits", - [ - (None, range(3)), - (None, None), - (Observable.X, range(3)), - ([Observable.X], 1), - ([Observable.X], [1]), - ([Observable.X], [[1]]), - ([Observable.X, Observable.Y], range(3)), - ([Observable.X, Observable.Y], None), - ], -) -def test_serialization(observables, qubits): - test_criteria = ObservableCriteria(observables=observables, qubits=qubits) - serialized_criteria = test_criteria.to_dict() - assert serialized_criteria["__class__"] == "ObservableCriteria" - assert "observables" in serialized_criteria - assert "qubits" in serialized_criteria - deserialized_criteria = ObservableCriteria.from_dict(serialized_criteria) - assert test_criteria == deserialized_criteria - - -@pytest.mark.parametrize( - "observables, qubits, matching_result_type, non_matching_result_type", - [ - ( - None, - range(3), - [ - ResultType.Sample(Observable.X(), 0), - ResultType.Sample(Observable.Z(), 2), - ], - [ResultType.Sample(Observable.X(), 4)], - ), - ( - None, - None, - [ - ResultType.Sample(Observable.X(), 0), - ResultType.Sample(Observable.Z(), 10), - ], - [ResultType.Probability(0)], - ), - ( - Observable.X, - range(3), - [ - ResultType.Sample(Observable.X(), 0), - ResultType.Sample(Observable.X(), 1), - ResultType.Sample(Observable.X()), - ], - [ - ResultType.Sample(Observable.X(), 3), - ResultType.Sample(Observable.Y(), 1), - ], - ), - ( - Observable.X, - 1, - [ResultType.Sample(Observable.X(), 1)], - [ResultType.Sample(Observable.X(), 0)], - ), - ( - [Observable.X], - [1], - [ResultType.Sample(Observable.X(), 1)], - [ResultType.Sample(Observable.X(), 0)], - ), - ( - [Observable.X, Observable.Y], - range(3), - [ - ResultType.Sample(Observable.X(), 0), - ResultType.Sample(Observable.Y(), 2), - ], - [ - ResultType.Sample(Observable.X(), 3), - ResultType.Sample(Observable.Z(), 1), - ], - ), - ( - Observable.X, - None, - [ - ResultType.Sample(Observable.X(), 0), - ResultType.Sample(Observable.X(), 10), - ], - [ - ResultType.Sample(Observable.Y(), 1), - ResultType.Sample(Observable.Z(), 3), - ], - ), - ( - Observable.X, - [], - [ - ResultType.Sample(Observable.X(), 0), - ResultType.Sample(Observable.X(), 10), - ], - [ - ResultType.Sample(Observable.Y(), 1), - ResultType.Sample(Observable.Z(), 3), - ], - ), - ], -) -def test_matcher(observables, qubits, matching_result_type, non_matching_result_type): - criteria = ObservableCriteria(observables=observables, qubits=qubits) - for result_type in matching_result_type: - assert criteria.result_type_matches(result_type) - for instruction in non_matching_result_type: - assert not criteria.result_type_matches(instruction) - - -@pytest.mark.parametrize( - "observables, qubits", - [ - ([Observable.X], [[0, 1]]), - ], -) -@pytest.mark.xfail(raises=ValueError) -def test_invalid_params(observables, qubits): - ObservableCriteria(observables=observables, qubits=qubits) - - -def test_representation(): - criteria = ObservableCriteria(observables=[Observable.X], qubits=0) - str_representation = criteria.__repr__() - assert str_representation == "ObservableCriteria(observables={'X'}, qubits={0})" - - -def test_string(): - criteria = ObservableCriteria(observables=[Observable.X], qubits=0) - str_representation = criteria.__str__() - assert str_representation == "ObservableCriteria({'X'}, {0})" - - -def test_get_keys_for_unknown_keytypes(): - criteria = ObservableCriteria(observables=[Observable.X], qubits=0) - result = criteria.get_keys(CriteriaKey.UNITARY_GATE) - assert len(result) == 0 diff --git a/test/unit_tests/braket/circuits/noise/test_qubit_initialization_criteria.py b/test/unit_tests/braket/circuits/noise/test_qubit_initialization_criteria.py deleted file mode 100644 index b61a8484..00000000 --- a/test/unit_tests/braket/circuits/noise/test_qubit_initialization_criteria.py +++ /dev/null @@ -1,112 +0,0 @@ -# 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.noise_model import CriteriaKey, CriteriaKeyResult, QubitInitializationCriteria - - -@pytest.mark.parametrize( - "qubits", - [1, range(3), None, [1], [[1]]], -) -def test_happy_case(qubits): - criteria = QubitInitializationCriteria(qubits=qubits) - assert criteria.applicable_key_types() == [CriteriaKey.QUBIT] - if qubits is None: - assert CriteriaKeyResult.ALL == criteria.get_keys(CriteriaKey.QUBIT) - else: - assert 1 in criteria.get_keys(CriteriaKey.QUBIT) - - -@pytest.mark.parametrize( - "qubits", - [ - 1, - range(3), - [1], - [[1]], - [0, 1], - [[1, 2]], - [[3, 4], [5, 6]], - None, - ], -) -def test_serialization(qubits): - test_criteria = QubitInitializationCriteria(qubits=qubits) - serialized_criteria = test_criteria.to_dict() - assert serialized_criteria["__class__"] == "QubitInitializationCriteria" - assert "qubits" in serialized_criteria - deserialized_criteria = QubitInitializationCriteria.from_dict(serialized_criteria) - assert test_criteria == deserialized_criteria - - -@pytest.mark.parametrize( - "qubits, input_qubits, expected_result", - [ - ( - range(3), - [0, 1, 2, [2], [[2]], 4, [5], [[6]], [0, 1]], - {0, 1, 2}, - ), - ( - None, - [0, 1, 2, [2], [[2]], 4, [5], [[6]], [0, 1]], - {0, 1, 2, 4, 5, 6}, - ), - ], -) -def test_matcher(qubits, input_qubits, expected_result): - criteria = QubitInitializationCriteria(qubits=qubits) - result = criteria.qubit_intersection(input_qubits) - assert result == expected_result - - -@pytest.mark.parametrize( - "qubits", - [ - ([[0, 1], 2]), - ], -) -@pytest.mark.xfail(raises=TypeError) -def test_invalid_param_types(qubits): - QubitInitializationCriteria(qubits=qubits) - - -@pytest.mark.parametrize( - "qubits", - [ - ([[0, 1], [2]]), - ], -) -@pytest.mark.xfail(raises=ValueError) -def test_invalid_params(qubits): - QubitInitializationCriteria(qubits=qubits) - - -def test_representation(): - criteria = QubitInitializationCriteria(qubits=range(4)) - str_representation = criteria.__repr__() - assert str_representation == "QubitInitializationCriteria(qubits={0, 1, 2, 3})" - - -def test_string(): - criteria = QubitInitializationCriteria(qubits=range(4)) - str_representation = criteria.__str__() - assert str_representation == "QubitInitializationCriteria({0, 1, 2, 3})" - - -def test_get_keys_for_unknown_keytypes(): - criteria = QubitInitializationCriteria(qubits=0) - result = criteria.get_keys(CriteriaKey.UNITARY_GATE) - assert len(result) == 0 diff --git a/test/unit_tests/braket/circuits/noise/test_unitary_gate_criteria.py b/test/unit_tests/braket/circuits/noise/test_unitary_gate_criteria.py deleted file mode 100644 index 8fb4636c..00000000 --- a/test/unit_tests/braket/circuits/noise/test_unitary_gate_criteria.py +++ /dev/null @@ -1,207 +0,0 @@ -# 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 Gate, Instruction, Observable -from braket.circuits.gates import Unitary -from braket.circuits.noise_model import ( - CriteriaKey, - CriteriaKeyResult, - ObservableCriteria, - UnitaryGateCriteria, -) - - -def h_unitary(): - return Unitary(Gate.H().to_matrix()) - - -def i_unitary(): - return Unitary(Gate.I().to_matrix()) - - -def x_unitary(): - return Unitary(Gate.X().to_matrix()) - - -def cnot_unitary(): - return Unitary(Gate.CNot().to_matrix()) - - -@pytest.mark.parametrize( - "unitary, qubits", - [ - (h_unitary(), range(3)), - (h_unitary(), 1), - (h_unitary(), None), - ], -) -def test_happy_case(unitary, qubits): - criteria = UnitaryGateCriteria(unitary=unitary, qubits=qubits) - assert criteria.applicable_key_types() == [ - CriteriaKey.QUBIT, - CriteriaKey.UNITARY_GATE, - ] - assert h_unitary().to_matrix().tobytes() in criteria.get_keys(CriteriaKey.UNITARY_GATE) - if qubits is None: - assert CriteriaKeyResult.ALL == criteria.get_keys(CriteriaKey.QUBIT) - else: - assert 1 in criteria.get_keys(CriteriaKey.QUBIT) - - -def test_serialization(): - test_criteria = UnitaryGateCriteria(unitary=h_unitary(), qubits=range(3)) - serialized_criteria = test_criteria.to_dict() - assert serialized_criteria["__class__"] == "UnitaryGateCriteria" - assert "unitary" in serialized_criteria - assert "qubits" in serialized_criteria - deserialized_criteria = UnitaryGateCriteria.from_dict(serialized_criteria) - assert test_criteria == deserialized_criteria - - -@pytest.mark.parametrize( - "unitary, qubits, matching_instructions, non_matching_instructions", - [ - ( - h_unitary(), - range(3), - [ - Instruction(h_unitary(), 0), - Instruction(h_unitary(), 1), - ], - [ - Instruction(Gate.H, 3), - Instruction(h_unitary(), 3), - Instruction(i_unitary(), 1), - [Instruction(h_unitary(), 0)], - ], - ), - ( - cnot_unitary(), - [[0, 1]], - [ - Instruction(cnot_unitary(), [0, 1]), - ], - [ - Instruction(Gate.CNot(), [0, 1]), - ], - ), - ( - h_unitary(), - 1, - [Instruction(h_unitary(), 1)], - [Instruction(h_unitary(), 0)], - ), - ( - h_unitary(), - None, - [Instruction(h_unitary(), 0), Instruction(h_unitary(), 10)], - [Instruction(i_unitary(), 1), Instruction(x_unitary(), 3)], - ), - ( - h_unitary(), - [], - [Instruction(h_unitary(), 0), Instruction(h_unitary(), 10)], - [Instruction(i_unitary(), 1), Instruction(x_unitary(), 3)], - ), - ], -) -def test_matcher(unitary, qubits, matching_instructions, non_matching_instructions): - criteria = UnitaryGateCriteria(unitary=unitary, qubits=qubits) - for instruction in matching_instructions: - assert criteria.instruction_matches(instruction) - for instruction in non_matching_instructions: - assert not criteria.instruction_matches(instruction) - - -@pytest.mark.parametrize( - "unitary, qubits", - [ - (None, 1), - ([], 1), - ([h_unitary()], 1), - (np.zeros(15, dtype=int), 1), - ], -) -@pytest.mark.xfail(raises=TypeError) -def test_invalid_params(unitary, qubits): - UnitaryGateCriteria(unitary=unitary, qubits=qubits) - - -def test_representation(): - criteria = UnitaryGateCriteria(unitary=h_unitary(), qubits=0) - str_representation = criteria.__repr__() - assert ( - str_representation == "UnitaryGateCriteria(unitary=Unitary('qubit_count': 1), qubits={0})" - ) # noqa - - -def test_string(): - criteria = UnitaryGateCriteria(unitary=h_unitary(), qubits=0) - str_representation = criteria.__str__() - assert ( - str_representation == "UnitaryGateCriteria(unitary=Unitary('qubit_count': 1), qubits={0})" - ) # noqa - - -def test_get_keys_for_unknown_keytypes(): - criteria = UnitaryGateCriteria(unitary=h_unitary(), qubits=0) - result = criteria.get_keys(CriteriaKey.GATE) - assert len(result) == 0 - - -@pytest.mark.parametrize( - "criteria0, criteria1", - [ - ( - UnitaryGateCriteria(h_unitary(), range(3)), - UnitaryGateCriteria(h_unitary(), range(3)), - ), - ( - UnitaryGateCriteria(h_unitary(), [0, 1, 2]), - UnitaryGateCriteria(h_unitary(), range(3)), - ), - ( - UnitaryGateCriteria(h_unitary(), [1, 2, 0]), - UnitaryGateCriteria(h_unitary(), range(3)), - ), - (UnitaryGateCriteria(h_unitary()), UnitaryGateCriteria(h_unitary(), None)), - ], -) -def test_equal_criteria(criteria0, criteria1): - assert criteria0 == criteria1 - - -@pytest.mark.parametrize( - "criteria0, criteria1", - [ - ( - UnitaryGateCriteria(h_unitary(), range(3)), - UnitaryGateCriteria(i_unitary(), range(3)), - ), - ( - UnitaryGateCriteria(h_unitary(), [1, 2, 3]), - UnitaryGateCriteria(h_unitary(), range(3)), - ), - (UnitaryGateCriteria(h_unitary()), UnitaryGateCriteria(h_unitary(), range(3))), - (UnitaryGateCriteria(h_unitary(), range(3)), float(3)), - ( - UnitaryGateCriteria(h_unitary(), range(3)), - ObservableCriteria(Observable.X, range(3)), - ), - ], -) -def test_not_equal_criteria(criteria0, criteria1): - assert criteria0 != criteria1 diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py deleted file mode 100644 index 4c5252c0..00000000 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ /dev/null @@ -1,214 +0,0 @@ -# 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 numpy as np -import pytest -from pydantic.v1 import BaseModel - -from braket.circuits import AngledGate, FreeParameter, FreeParameterExpression, Gate -from braket.circuits.angled_gate import DoubleAngledGate, TripleAngledGate - - -@pytest.fixture -def angled_gate(): - return AngledGate(angle=0.15, qubit_count=1, ascii_symbols=["foo"]) - - -def test_is_operator(angled_gate): - assert isinstance(angled_gate, Gate) - - -def test_angle_is_none(): - with pytest.raises(ValueError, match="angle must not be None"): - AngledGate(qubit_count=1, ascii_symbols=["foo"], angle=None) - - -def test_getters(): - qubit_count = 2 - ascii_symbols = ("foo", "bar") - angle = 0.15 - gate = AngledGate(angle=angle, qubit_count=qubit_count, ascii_symbols=ascii_symbols) - - assert gate.qubit_count == qubit_count - assert gate.ascii_symbols == ascii_symbols - assert gate.angle == angle - - -def test_angle_setter(angled_gate): - with pytest.raises(AttributeError): - angled_gate.angle = 0.14 - - -def test_adjoint(angled_gate): - assert angled_gate.adjoint() == [AngledGate(angle=-0.15, qubit_count=1, ascii_symbols=["foo"])] - - -def test_adjoint_parameterized(): - theta = FreeParameter("theta") - angled_gate = AngledGate(angle=(theta + 1), qubit_count=1, ascii_symbols=["foo"]) - assert angled_gate.adjoint() == [ - AngledGate(angle=-(theta + 1), qubit_count=1, ascii_symbols=["foo"]) - ] - - -def test_equality(angled_gate): - gate = AngledGate(angle=0.15, qubit_count=1, ascii_symbols=["bar"]) - other_gate = AngledGate(angle=0.3, qubit_count=1, ascii_symbols=["foo"]) - non_gate = "non gate" - - assert angled_gate == gate - assert angled_gate is not gate - assert angled_gate != other_gate - assert angled_gate != non_gate - - -def test_symbolic_equality(): - symbol1 = FreeParameter("theta") - symbol2 = FreeParameter("phi") - symbol3 = FreeParameter("theta") - gate1 = AngledGate(angle=symbol1, qubit_count=1, ascii_symbols=["bar"]) - gate2 = AngledGate(angle=symbol1, qubit_count=1, ascii_symbols=["bar"]) - gate3 = AngledGate(angle=symbol3, qubit_count=1, ascii_symbols=["bar"]) - other_gate = AngledGate(angle=symbol2, qubit_count=1, ascii_symbols=["foo"]) - - assert gate1 == gate2 - assert gate1 == gate3 - assert gate1 is not gate2 - assert gate1 != other_gate - - -def test_mixed_angle_equality(): - symbol1 = FreeParameter("theta") - gate1 = AngledGate(angle=symbol1, qubit_count=1, ascii_symbols=["bar"]) - gate2 = AngledGate(angle=0.15, qubit_count=1, ascii_symbols=["foo"]) - - assert gate1 != gate2 - assert gate2 != gate1 - - -def test_angle_adjoint(): - symbol1 = FreeParameter("theta") - gate1 = AngledGate(angle=symbol1, qubit_count=1, ascii_symbols=["bar(theta)"]) - gate2 = AngledGate(angle=0.15, qubit_count=1, ascii_symbols=["foo(0.15)"]) - - gate1_adj = gate1.adjoint() - gate2_adj = gate2.adjoint() - - assert len(gate1_adj) == len(gate2_adj) == 1 - assert np.array_equal(gate1_adj[0].ascii_symbols, ["bar(-theta)"]) - assert np.array_equal(gate2_adj[0].ascii_symbols, ["foo(-0.15)"]) - - -def test_bind_values(): - theta = FreeParameter("theta") - gate = AngledGate(angle=theta, qubit_count=1, ascii_symbols=["bar"]) - with pytest.raises(NotImplementedError): - gate.bind_values(theta=1) - - -def test_angled_gate_with_expr(): - expr = FreeParameterExpression(FreeParameter("theta") + 1) - new_expr = expr.subs({"theta": 1}) - gate = AngledGate(angle=new_expr, qubit_count=1, ascii_symbols=["bar"]) - expected = AngledGate(angle=2, qubit_count=1, ascii_symbols=["bar"]) - - assert gate == expected - - -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[1]) - assert angle_value == angled_gate.angle - - -def test_double_angle_is_none(): - with pytest.raises(ValueError, match="angles must not be None"): - DoubleAngledGate(qubit_count=1, ascii_symbols=["foo"], angle_1=None, angle_2=1) - - -def test_triple_angle_is_none(): - with pytest.raises(ValueError, match="angles must not be None"): - TripleAngledGate(qubit_count=1, ascii_symbols=["foo"], angle_1=None, angle_2=1, angle_3=2) - - -def test_double_angle_equality(): - gate = DoubleAngledGate(angle_1=0.15, angle_2=3, qubit_count=1, ascii_symbols=["bar"]) - equal_gate = DoubleAngledGate(angle_1=0.15, angle_2=3, qubit_count=1, ascii_symbols=["bar"]) - other_gate = AngledGate(angle=0.3, qubit_count=1, ascii_symbols=["foo"]) - non_gate = "non gate" - - assert equal_gate == gate - assert equal_gate is not gate - assert gate != other_gate - assert gate != non_gate - - -def test_double_angle_symbolic_equality(): - symbol1 = FreeParameter("theta") - symbol2 = FreeParameter("phi") - symbol3 = FreeParameter("theta") - gate1 = DoubleAngledGate(angle_1=symbol1, angle_2=1, qubit_count=1, ascii_symbols=["bar"]) - gate2 = DoubleAngledGate(angle_1=symbol1, angle_2=1, qubit_count=1, ascii_symbols=["bar"]) - gate3 = DoubleAngledGate(angle_1=symbol3, angle_2=1, qubit_count=1, ascii_symbols=["bar"]) - other_gate = DoubleAngledGate(angle_1=symbol2, angle_2=1, qubit_count=1, ascii_symbols=["foo"]) - - assert gate1 == gate2 - assert gate1 == gate3 - assert gate1 is not gate2 - assert gate1 != other_gate - - -def test_double_angle_repr(): - assert ( - repr(DoubleAngledGate(qubit_count=1, ascii_symbols=["foo"], angle_1=1, angle_2=2)) - == "DoubleAngledGate('angles': (1.0, 2.0), 'qubit_count': 1)" - ) - - -def test_triple_angle_repr(): - assert ( - repr( - TripleAngledGate(qubit_count=1, ascii_symbols=["foo"], angle_1=1, angle_2=2, angle_3=3) - ) - == "TripleAngledGate('angles': (1.0, 2.0, 3.0), 'qubit_count': 1)" - ) - - -def test_double_angle_parameters(): - assert DoubleAngledGate( - qubit_count=1, ascii_symbols=["foo"], angle_1=1, angle_2=2 - ).parameters == [1, 2] - - -def test_hash_double_angle(): - symbol1 = FreeParameter("theta") - assert hash( - DoubleAngledGate(angle_1=symbol1, angle_2=1, qubit_count=1, ascii_symbols=["bar"]) - ) == hash(DoubleAngledGate(angle_1=symbol1, angle_2=1, qubit_count=1, ascii_symbols=["bar"])) - - -def test_hash_triple_angle(): - symbol1 = FreeParameter("theta") - assert hash( - TripleAngledGate( - angle_1=symbol1, angle_2=1, angle_3=3, qubit_count=1, ascii_symbols=["bar"] - ) - ) == hash( - TripleAngledGate( - angle_1=symbol1, angle_2=1, angle_3=3, qubit_count=1, ascii_symbols=["bar"] - ) - ) diff --git a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py deleted file mode 100644 index aec87556..00000000 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ /dev/null @@ -1,958 +0,0 @@ -# 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 ( - AsciiCircuitDiagram, - Circuit, - FreeParameter, - Gate, - Instruction, - Noise, - Observable, - Operator, -) -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()) == "" - - -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|") - _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_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 = 15 - 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|7|8|9|10|11|12|13|", - " ", - "q0 : -C-------------------------------", - " | ", - "q1 : -X-C-----------------------------", - " | ", - "q2 : ---X-C---------------------------", - " | ", - "q3 : -----X-C-------------------------", - " | ", - "q4 : -------X-C-----------------------", - " | ", - "q5 : ---------X-C---------------------", - " | ", - "q6 : -----------X-C-------------------", - " | ", - "q7 : -------------X-C-----------------", - " | ", - "q8 : ---------------X-C---------------", - " | ", - "q9 : -----------------X-C-------------", - " | ", - "q10 : -------------------X-C-----------", - " | ", - "q11 : ---------------------X--C--------", - " | ", - "q12 : ------------------------X--C-----", - " | ", - "q13 : ---------------------------X--C--", - " | ", - "q14 : ------------------------------X--", - "", - "T : |0|1|2|3|4|5|6|7|8|9|10|11|12|13|", - ) - _assert_correct_diagram(circ, expected) - - -def test_connector_across_two_qubits(): - circ = Circuit().cnot(3, 4).h(range(2, 6)) - expected = ( - "T : |0|1|", - " ", - "q2 : -H---", - " ", - "q3 : -C-H-", - " | ", - "q4 : -X-H-", - " ", - "q5 : -H---", - "", - "T : |0|1|", - ) - _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 = ( - "T : |0|1|", - " ", - "q2 : -H---", - " ", - "q3 : -C-H-", - " | ", - "q4 : -C-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 : -C---H-", - " | ", - "q1 : -|-C---", - " | | ", - "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 : -|--------C---", - " | | ", - "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 : ---C---", - " | ", - "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 : ---C---", - " | ", - "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-C-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-C-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-C-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-C---EndVerbatim-", - " | | | ", - "q1 : -|---------------X-C-|-----------", - " | | | ", - "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-C---EndVerbatim-", - " | | | ", - "q1 : ---|---------------X-C-|-----------", - " | | | ", - "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-C---EndVerbatim-H-", - " | | | ", - "q1 : -|---------------X-C-|-------------", - " | | | ", - "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 : ---|---------------|-----------C-", - " | | | ", - "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 : ---C---StartVerbatim---EndVerbatim---", - " | | | ", - "q1 : -H-X-C-|-------------H-|-------------", - " | | | ", - "q2 : -----X-|---------------|-----------C-", - " | | | ", - "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-C-", - " | ", - "q2 : ---X-", - "", - "T : |0|1|", - ) - _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 : -C---H-Variance(Y)----Sample(Y)-", - " | | ", - "q1 : -|-C------------------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 : -C---H-Variance(Y)------------", - " | ", - "q1 : -|-C---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 : -C---H-Variance(Y)---------", - " | ", - "q1 : -|-C---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-C-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, 2), control=0, power=4)) - expected = ( - "T : | 0 | 1 | 2 | 3 | 4 |", - " ", - "q0 : -H---------(FOO^-1)-C-------C-------C-------", - " | | | ", - "q1 : -(H^0)--------------(FOO^2)-C-------(FOO^4)-", - " | | ", - "q2 : -(H^-3.14)------------------(FOO^3)-(FOO^4)-", - "", - "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) - - -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_basis_state.py b/test/unit_tests/braket/circuits/test_basis_state.py deleted file mode 100644 index 166e7c8f..00000000 --- a/test/unit_tests/braket/circuits/test_basis_state.py +++ /dev/null @@ -1,108 +0,0 @@ -import pytest - -from braket.circuits.basis_state import BasisState - - -@pytest.mark.parametrize( - "basis_state_input, size, as_tuple, as_int, as_string", - ( - ( - [1, 0, 1], - None, - (1, 0, 1), - 5, - "101", - ), - ( - [1, 0, 1], - 5, - (0, 0, 1, 0, 1), - 5, - "00101", - ), - ( - "1", - 3, - (0, 0, 1), - 1, - "001", - ), - ( - "101", - None, - (1, 0, 1), - 5, - "101", - ), - ( - 5, - None, - (1, 0, 1), - 5, - "101", - ), - ( - 5, - 4, - (0, 1, 0, 1), - 5, - "0101", - ), - ), -) -def test_as_props(basis_state_input, size, as_tuple, as_int, 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") diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py deleted file mode 100644 index 71eecd1f..00000000 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ /dev/null @@ -1,3612 +0,0 @@ -# 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 Mock - -import numpy as np -import pytest - -import braket.ir.jaqcd as jaqcd -from braket.circuits import ( - Circuit, - FreeParameter, - FreeParameterExpression, - Gate, - Instruction, - Moments, - Noise, - Observable, - QubitSet, - ResultType, - UnicodeCircuitDiagram, - circuit, - compiler_directives, - gates, - noise, - 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, - OpenQASMSerializationProperties, - QubitReferenceType, -) -from braket.circuits.translations import braket_result_to_result_type -from braket.ir.openqasm import Program as OpenQasmProgram -from braket.pulse import DragGaussianWaveform, Frame, GaussianWaveform, Port, PulseSequence - - -@pytest.fixture -def cnot(): - return Circuit().add_instruction(Instruction(Gate.CNot(), [0, 1])) - - -@pytest.fixture -def cnot_instr(): - return Instruction(Gate.CNot(), [0, 1]) - - -@pytest.fixture -def h(): - return Circuit().add_instruction(Instruction(Gate.H(), 0)) - - -@pytest.fixture -def h_instr(): - return Instruction(Gate.H(), 0) - - -@pytest.fixture -def prob(): - return ResultType.Probability([0, 1]) - - -@pytest.fixture -def cnot_prob(cnot_instr, prob): - return Circuit().add_result_type(prob).add_instruction(cnot_instr) - - -@pytest.fixture -def bell_pair(prob): - return ( - Circuit() - .add_instruction(Instruction(Gate.H(), 0)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - .add_result_type(prob) - ) - - -@pytest.fixture -def port(): - return Port(port_id="device_port_x0", dt=1e-9, properties={}) - - -@pytest.fixture -def predefined_frame_1(port): - return Frame( - frame_id="predefined_frame_1", frequency=2e9, port=port, phase=0, is_predefined=True - ) - - -@pytest.fixture -def user_defined_frame(port): - return Frame( - frame_id="user_defined_frame_0", - port=port, - frequency=1e7, - phase=3.14, - is_predefined=False, - properties={"associatedGate": "rz"}, - ) - - -@pytest.fixture -def pulse_sequence(predefined_frame_1): - return ( - PulseSequence() - .set_frequency( - predefined_frame_1, - 6e6, - ) - .play( - predefined_frame_1, - DragGaussianWaveform(length=3e-3, sigma=0.4, beta=0.2, id="drag_gauss_wf"), - ) - ) - - -@pytest.fixture -def pulse_sequence_2(predefined_frame_1): - return ( - PulseSequence() - .shift_phase( - predefined_frame_1, - FreeParameter("alpha"), - ) - .set_phase( - predefined_frame_1, - FreeParameter("gamma"), - ) - .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 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])) - calibration_key_2 = (Gate.Rx(FreeParameter("theta")), QubitSet([0])) - calibration_key_3 = ( - 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, - } - ) - - -def test_repr_instructions(h): - expected = f"Circuit('instructions': {h.instructions})" - assert repr(h) == expected - - -def test_repr_result_types(cnot_prob): - circuit = cnot_prob - expected = ( - f"Circuit('instructions': {circuit.instructions}" - + f", 'result_types': {circuit.result_types})" - ) - assert repr(circuit) == expected - - -def test_str(h): - expected = UnicodeCircuitDiagram.build_diagram(h) - assert str(h) == expected - - -def test_equality(): - circ_1 = Circuit().h(0).probability([0, 1]) - circ_2 = Circuit().h(0).probability([0, 1]) - other_circ = Circuit().h(1) - non_circ = "non circuit" - - assert circ_1 == circ_2 - assert circ_1 is not circ_2 - assert circ_1 != other_circ - assert circ_1 != non_circ - - -def test_call(): - alpha = FreeParameter("alpha") - theta = FreeParameter("theta") - circ = Circuit().h(0).rx(angle=theta, target=1).ry(angle=alpha, target=0) - new_circ = circ(theta=1, alpha=0) - expected = Circuit().h(0).rx(angle=1, target=1).ry(angle=0, target=0) - assert new_circ == expected and not new_circ.parameters - - -def test_call_with_result_type(prob): - alpha = FreeParameter("alpha") - theta = FreeParameter("theta") - circ = Circuit().h(0).rx(angle=theta, target=1).ry(angle=alpha, target=0).add_result_type(prob) - new_circ = circ(theta=1, alpha=0) - expected = Circuit().h(0).rx(angle=1, target=1).ry(angle=0, target=0).add_result_type(prob) - - assert new_circ == expected and not new_circ.parameters - assert new_circ.observables_simultaneously_measurable - assert new_circ.result_types == [prob] - - -def test_call_one_param_not_bound(): - alpha = FreeParameter("alpha") - theta = FreeParameter("theta") - 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 = {alpha} - assert new_circ == expected_circ and new_circ.parameters == expected_parameters - - -def test_call_with_default_parameter_val(): - alpha = FreeParameter("alpha") - beta = FreeParameter("beta") - theta = FreeParameter("theta") - gamma = FreeParameter("gamma") - circ = ( - Circuit() - .h(0) - .rx(angle=theta, target=1) - .ry(angle=alpha, target=0) - .ry(angle=beta, target=2) - .rx(angle=gamma, target=1) - ) - new_circ = circ(np.pi, theta=1, alpha=0) - expected = ( - Circuit() - .h(0) - .rx(angle=1, target=1) - .ry(angle=0, target=0) - .ry(angle=np.pi, target=2) - .rx(angle=np.pi, target=1) - ) - assert new_circ == expected and not new_circ.parameters - - -def test_add_result_type_default(prob): - circ = Circuit().add_result_type(prob) - assert circ.observables_simultaneously_measurable - assert circ.result_types == [prob] - - -def test_add_result_type_with_mapping(prob): - expected = [ResultType.Probability([10, 11])] - circ = Circuit().add_result_type(prob, target_mapping={0: 10, 1: 11}) - assert circ.observables_simultaneously_measurable - assert circ.result_types == expected - - -def test_add_result_type_with_target(prob): - expected = [ResultType.Probability([10, 11])] - circ = Circuit().add_result_type(prob, target=[10, 11]) - assert circ.observables_simultaneously_measurable - assert circ.result_types == expected - - -def test_add_result_type_already_exists(): - expected = [ResultType.StateVector()] - circ = Circuit(expected).add_result_type(expected[0]) - assert circ.observables_simultaneously_measurable - assert circ.result_types == expected - - -def test_add_result_type_observable_conflict_target(): - circ = Circuit().add_result_type(ResultType.Probability([0, 1])) - circ.add_result_type(ResultType.Expectation(observable=Observable.Y(), target=0)) - assert not circ.observables_simultaneously_measurable - assert not circ.basis_rotation_instructions - - -def test_add_result_type_observable_conflict_all(): - circ = Circuit().add_result_type(ResultType.Probability()) - circ.add_result_type(ResultType.Expectation(observable=Observable.Y())) - assert not circ.observables_simultaneously_measurable - assert not circ.basis_rotation_instructions - - -def test_add_result_type_observable_conflict_all_target_then_selected_target(): - circ = Circuit().add_result_type(ResultType.Probability()) - circ.add_result_type(ResultType.Expectation(observable=Observable.Y(), target=[0])) - assert not circ.observables_simultaneously_measurable - assert not circ.basis_rotation_instructions - - -def test_add_result_type_observable_conflict_different_selected_targets_then_all_target(): - circ = Circuit().add_result_type(ResultType.Expectation(observable=Observable.Z(), target=[0])) - circ.add_result_type(ResultType.Expectation(observable=Observable.Y(), target=[1])) - circ.add_result_type(ResultType.Expectation(observable=Observable.Y())) - assert not circ.observables_simultaneously_measurable - assert not circ.basis_rotation_instructions - - -def test_add_result_type_observable_conflict_selected_target_then_all_target(): - circ = Circuit().add_result_type(ResultType.Expectation(observable=Observable.Y(), target=[1])) - circ.add_result_type(ResultType.Probability()) - assert not circ.observables_simultaneously_measurable - assert not circ.basis_rotation_instructions - - -def test_add_result_type_observable_no_conflict_all_target(): - expected = [ - ResultType.Probability(), - ResultType.Expectation(observable=Observable.Z(), target=[0]), - ] - circ = Circuit(expected) - assert circ.observables_simultaneously_measurable - assert circ.result_types == expected - - -def test_add_result_type_observable_no_conflict_target_all(): - expected = [ - ResultType.Expectation(observable=Observable.Z(), target=[0]), - ResultType.Probability(), - ] - circ = Circuit(expected) - assert circ.observables_simultaneously_measurable - assert circ.result_types == expected - - -def test_add_result_type_observable_no_conflict_all(): - expected = [ - ResultType.Variance(observable=Observable.Y()), - ResultType.Expectation(observable=Observable.Y()), - ] - circ = Circuit(expected) - assert circ.observables_simultaneously_measurable - assert circ.result_types == expected - - -def test_add_result_type_observable_no_conflict_all_identity(): - expected = [ - ResultType.Variance(observable=Observable.Y()), - ResultType.Expectation(observable=Observable.I()), - ResultType.Expectation(observable=Observable.Y()), - ] - circ = Circuit(expected) - assert circ.observables_simultaneously_measurable - assert circ.result_types == expected - - -def test_add_result_type_observable_no_conflict_state_vector_obs_return_value(): - expected = [ - ResultType.StateVector(), - ResultType.Expectation(observable=Observable.Y()), - ] - circ = Circuit(expected) - assert circ.observables_simultaneously_measurable - assert circ.result_types == expected - - -def test_add_result_type_same_observable_wrong_target_order_tensor_product(): - circ = ( - Circuit() - .add_result_type( - ResultType.Expectation(observable=Observable.Y() @ Observable.X(), target=[0, 1]) - ) - .add_result_type( - ResultType.Variance(observable=Observable.Y() @ Observable.X(), target=[1, 0]) - ) - ) - assert not circ.observables_simultaneously_measurable - assert not circ.basis_rotation_instructions - - -def test_add_result_type_same_observable_wrong_target_order_hermitian(): - array = np.eye(4) - circ = ( - Circuit() - .add_result_type( - ResultType.Expectation(observable=Observable.Hermitian(matrix=array), target=[0, 1]) - ) - .add_result_type( - ResultType.Variance(observable=Observable.Hermitian(matrix=array), target=[1, 0]) - ) - ) - assert not circ.observables_simultaneously_measurable - assert not circ.basis_rotation_instructions - - -def test_add_result_type_with_target_and_mapping(prob): - with pytest.raises(TypeError): - Circuit().add_result_type(prob, target=[10], target_mapping={0: 10}) - - -def test_add_instruction_default(cnot_instr): - circ = Circuit().add_instruction(cnot_instr) - assert circ.instructions == [cnot_instr] - - -def test_add_instruction_with_mapping(cnot_instr): - expected = [Instruction(Gate.CNot(), [10, 11])] - circ = Circuit().add_instruction(cnot_instr, target_mapping={0: 10, 1: 11}) - assert circ.instructions == expected - - -def test_add_instruction_with_target(cnot_instr): - expected = [Instruction(Gate.CNot(), [10, 11])] - circ = Circuit().add_instruction(cnot_instr, target=[10, 11]) - assert circ.instructions == expected - - -def test_add_multiple_single_qubit_instruction(h_instr): - circ = Circuit().add_instruction(h_instr, target=[0, 1, 2, 3]) - expected = Circuit().h(0).h(1).h(2).h(3) - assert circ == expected - - -def test_add_instruction_with_target_and_mapping(h): - with pytest.raises(TypeError): - Circuit().add_instruction(h, target=[10], target_mapping={0: 10}) - - -def test_add_circuit_default(bell_pair): - circ = Circuit().add_circuit(bell_pair) - assert circ == bell_pair - - -def test_add_circuit_with_mapping(bell_pair): - circ = Circuit().add_circuit(bell_pair, target_mapping={0: 10, 1: 11}) - expected = ( - Circuit() - .add_instruction(Instruction(Gate.H(), 10)) - .add_instruction(Instruction(Gate.CNot(), [10, 11])) - .add_result_type(ResultType.Probability([10, 11])) - ) - assert circ == expected - - -def test_add_circuit_with_target(bell_pair): - circ = Circuit().add_circuit(bell_pair, target=[10, 11]) - expected = ( - Circuit() - .add_instruction(Instruction(Gate.H(), 10)) - .add_instruction(Instruction(Gate.CNot(), [10, 11])) - .add_result_type(ResultType.Probability([10, 11])) - ) - assert circ == expected - - -def test_add_circuit_with_target_and_non_continuous_qubits(): - widget = Circuit().h(5).h(50).h(100) - circ = Circuit().add_circuit(widget, target=[1, 3, 5]) - expected = ( - Circuit() - .add_instruction(Instruction(Gate.H(), 1)) - .add_instruction(Instruction(Gate.H(), 3)) - .add_instruction(Instruction(Gate.H(), 5)) - ) - assert circ == expected - - -def test_add_circuit_with_target_and_mapping(h): - with pytest.raises(TypeError): - Circuit().add_circuit(h, target=[10], target_mapping={0: 10}) - - -def test_add_verbatim_box(): - circ = Circuit().h(0).add_verbatim_box(Circuit().cnot(0, 1)) - expected = ( - Circuit() - .add_instruction(Instruction(Gate.H(), 0)) - .add_instruction(Instruction(compiler_directives.StartVerbatimBox())) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - .add_instruction(Instruction(compiler_directives.EndVerbatimBox())) - ) - assert circ == expected - - -def test_add_verbatim_box_different_qubits(): - circ = Circuit().h(1).add_verbatim_box(Circuit().h(0)).cnot(3, 4) - expected = ( - Circuit() - .add_instruction(Instruction(Gate.H(), 1)) - .add_instruction(Instruction(compiler_directives.StartVerbatimBox())) - .add_instruction(Instruction(Gate.H(), 0)) - .add_instruction(Instruction(compiler_directives.EndVerbatimBox())) - .add_instruction(Instruction(Gate.CNot(), [3, 4])) - ) - assert circ == expected - - -def test_add_verbatim_box_no_preceding(): - circ = Circuit().add_verbatim_box(Circuit().h(0)).cnot(2, 3) - expected = ( - Circuit() - .add_instruction(Instruction(compiler_directives.StartVerbatimBox())) - .add_instruction(Instruction(Gate.H(), 0)) - .add_instruction(Instruction(compiler_directives.EndVerbatimBox())) - .add_instruction(Instruction(Gate.CNot(), [2, 3])) - ) - assert circ == expected - - -def test_add_verbatim_box_empty(): - circuit = Circuit().add_verbatim_box(Circuit()) - assert circuit == Circuit() - assert not circuit.qubits_frozen - - -def test_add_verbatim_box_with_mapping(cnot): - circ = Circuit().add_verbatim_box(cnot, target_mapping={0: 10, 1: 11}) - expected = ( - Circuit() - .add_instruction(Instruction(compiler_directives.StartVerbatimBox())) - .add_instruction(Instruction(Gate.CNot(), [10, 11])) - .add_instruction(Instruction(compiler_directives.EndVerbatimBox())) - ) - assert circ == expected - - -def test_add_verbatim_box_with_target(cnot): - circ = Circuit().add_verbatim_box(cnot, target=[10, 11]) - expected = ( - Circuit() - .add_instruction(Instruction(compiler_directives.StartVerbatimBox())) - .add_instruction(Instruction(Gate.CNot(), [10, 11])) - .add_instruction(Instruction(compiler_directives.EndVerbatimBox())) - ) - assert circ == expected - - -def test_add_verbatim_box_with_target_and_mapping(h): - with pytest.raises(TypeError): - Circuit().add_verbatim_box(h, target=[10], target_mapping={0: 10}) - - -def test_add_verbatim_box_result_types(): - with pytest.raises(ValueError): - Circuit().h(0).add_verbatim_box( - Circuit().cnot(0, 1).expectation(observable=Observable.X(), target=0) - ) - - -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(): - circ = Circuit().measure([0, 1, 2]) - expected = ( - Circuit() - .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): - 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\\) 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\\) 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]) - - -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_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).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}) - - # 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().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 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_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." - 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]) - - -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_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 = measure q;", - ] - ), - 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") - - -def test_add_with_instruction_with_default(cnot_instr): - circ = Circuit().add(cnot_instr) - assert circ == Circuit().add_instruction(cnot_instr) - - -def test_add_with_instruction_with_mapping(cnot_instr): - target_mapping = {0: 10, 1: 11} - circ = Circuit().add(cnot_instr, target_mapping=target_mapping) - expected = Circuit().add_instruction(cnot_instr, target_mapping=target_mapping) - assert circ == expected - - -def test_add_with_instruction_with_target(cnot_instr): - target = [10, 11] - circ = Circuit().add(cnot_instr, target=target) - expected = Circuit().add_instruction(cnot_instr, target=target) - assert circ == expected - - -def test_add_with_circuit_with_default(bell_pair): - circ = Circuit().add(bell_pair) - assert circ == Circuit().add_circuit(bell_pair) - - -def test_add_with_circuit_with_mapping(bell_pair): - target_mapping = {0: 10, 1: 11} - circ = Circuit().add(bell_pair, target_mapping=target_mapping) - expected = Circuit().add_circuit(bell_pair, target_mapping=target_mapping) - assert circ == expected - - -def test_add_with_circuit_with_target(bell_pair): - target = [10, 11] - circ = Circuit().add(bell_pair, target=target) - expected = Circuit().add_circuit(bell_pair, target=target) - assert circ == expected - - -def test_adjoint(): - circ = Circuit().s(0).add_verbatim_box(Circuit().rz(0, 0.123)).expectation(Observable.X(), 0) - expected = Circuit() - expected.add_verbatim_box(Circuit().rz(0, -0.123)) - expected.si(0) - expected.expectation(Observable.X(), 0) - actual = circ.adjoint() - assert actual == expected - assert circ == expected.adjoint() - assert circ == actual.adjoint() - - -def test_adjoint_subcircuit_free_parameter(): - circ = Circuit().h(0).add_circuit(Circuit().s(0).rz(0, FreeParameter("theta")).adjoint()).x(0) - expected = Circuit().h(0).rz(0, -FreeParameter("theta")).si(0).x(0) - assert circ == expected - - -def test_circuit_copy(h, bell_pair, cnot_instr): - original = Circuit().add(h).add(bell_pair).add(cnot_instr) - copy = original.copy() - - assert copy is not original - assert copy == original - - -def test_circuit_copy_with_modification(h, bell_pair, cnot_instr): - original = Circuit().add(h).add(bell_pair) - copy = original.copy().add(cnot_instr) - - assert copy != original - - -def test_iadd_operator(cnot_instr, h): - circ = Circuit() - circ += h - circ += cnot_instr - circ += [h, cnot_instr] - - assert circ == Circuit().add(h).add(cnot_instr).add(h).add(cnot_instr) - - -def test_add_operator(h, bell_pair): - addition = h + bell_pair + h + h - expected = Circuit().add(h).add(bell_pair).add(h).add(h) - - assert addition == expected - assert addition != (h + h + bell_pair + h) - - -def test_iadd_with_unknown_type(h): - with pytest.raises(TypeError): - h += 100 - - -def test_subroutine_register(): - # register a private method to avoid Sphinx docs picking this up - @circuit.subroutine(register=True) - def _foo(target): - """this docstring will be added to the registered attribute""" - return Instruction(Gate.H(), target) - - circ = Circuit()._foo(0) - assert circ == Circuit(Instruction(Gate.H(), 0)) - assert Circuit._foo.__doc__ == _foo.__doc__ - - -def test_subroutine_returns_circuit(): - @circuit.subroutine() - def foo(target): - return Circuit().add(Instruction(Gate.H(), 0)) - - circ = Circuit().add(foo, 0) - assert circ == Circuit(Instruction(Gate.H(), 0)) - - -def test_subroutine_returns_instruction(): - @circuit.subroutine() - def foo(target): - return Instruction(Gate.H(), 0) - - circ = Circuit().add(foo, 0) - assert circ == Circuit(Instruction(Gate.H(), 0)) - - -def test_subroutine_returns_iterable(): - @circuit.subroutine() - def foo(target): - for qubit in range(1): - yield Instruction(Gate.H(), qubit) - - circ = Circuit().add(foo, 0) - assert circ == Circuit(Instruction(Gate.H(), 0)) - - -def test_subroutine_nested(): - @circuit.subroutine() - def h(target): - for qubit in target: - yield Instruction(Gate.H(), qubit) - - @circuit.subroutine() - def h_nested(target): - for qubit in target: - yield h(target) - - circ = Circuit().add(h_nested, [0, 1]) - expected = Circuit([Instruction(Gate.H(), j) for i in range(2) for j in range(2)]) - assert circ == expected - - -def test_ir_empty_instructions_result_types(): - circ = Circuit() - assert circ.to_ir() == jaqcd.Program( - instructions=[], results=[], basis_rotation_instructions=[] - ) - - -def test_ir_non_empty_instructions_result_types(): - circ = Circuit().h(0).cnot(0, 1).probability([0, 1]) - expected = jaqcd.Program( - instructions=[jaqcd.H(target=0), jaqcd.CNot(control=0, target=1)], - results=[jaqcd.Probability(targets=[0, 1])], - basis_rotation_instructions=[], - ) - assert circ.to_ir() == expected - - -def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): - circ = Circuit().h(0).cnot(0, 1).sample(observable=Observable.X(), target=[0]) - expected = jaqcd.Program( - instructions=[jaqcd.H(target=0), jaqcd.CNot(control=0, target=1)], - results=[jaqcd.Sample(observable=["x"], targets=[0])], - basis_rotation_instructions=[jaqcd.H(target=0)], - ) - 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", - [ - ( - 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];", - ] - ), - inputs={}, - ), - ), - ( - 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;", - ] - ), - inputs={}, - ), - ), - ( - Circuit() - .rx(0, 0.15) - .add_verbatim_box(Circuit().rx(4, 0.3)) - .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", - ] - ), - inputs={}, - ), - ), - ( - Circuit() - .rx(0, 0.15) - .rx(4, 0.3) - .bit_flip(3, probability=0.2) - .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])", - ] - ), - inputs={}, - ), - ), - ( - 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];", - ] - ), - inputs={}, - ), - ), - ( - Circuit() - .rx(0, 0.15, control=2, control_state=0) - .rx(1, 0.3, control=[2, 3]) - .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];", - ] - ), - inputs={}, - ), - ), - ( - 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];", - ] - ), - inputs={}, - ), - ), - ( - 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];", - ] - ), - inputs={}, - ), - ), - pytest.param( - Circuit().h(0, power=-2.5).h(0, power=0).rx(0, angle=FreeParameter("theta")), - OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), - OpenQasmProgram( - source="", - inputs={}, - ), - marks=pytest.mark.xfail( - reason="Parametric calibrations cannot be attached with parametric circuits." - ), - ), - ], -) -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( - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - gate_definitions=gate_calibrations.pulse_sequences, - ) - == 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) - 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, - } - ) - - 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];", - ] - ), - inputs={}, - ) - - assert ( - circ.to_ir( - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - gate_definitions=gate_calibrations.pulse_sequences, - ) - == expected_ir - ) - - -@pytest.mark.xfail( - reasons="Calibrations with a partial number of fixed parameters are not supported." -) -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} - ), - ) - circuit.to_ir( - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - gate_definitions=gate_calibrations.pulse_sequences, - ) - - -def test_circuit_user_gate(pulse_sequence_2): - class Foo(Gate, Parameterizable): - def __init__( - self, - bar, - ): - super().__init__(qubit_count=1, ascii_symbols=["Foo"]) - self._parameters = [bar] - - @property - def parameters(self): - return self._parameters - - def bind_values(self, **kwargs): - raise NotImplementedError - - @property - def _qasm_name(self): - return "foo" - - def __hash__(self): - return hash((self.name, self.parameters[0], self.qubit_count)) - - @staticmethod - @circuit.subroutine(register=True) - def foo( - target, - bar, - ): - return Instruction(Foo(bar), target=target) - - Gate.register_gate(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} - ) - } - ) - - 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];", - ] - ), - inputs={}, - ) - - assert ( - circ.to_ir( - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - gate_definitions=gate_calibrations.pulse_sequences, - ) - == expected_ir - ) - - -@pytest.mark.parametrize( - "expected_circuit, ir", - [ - ( - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - inputs={}, - ), - ), - ( - Circuit().h(0).state_vector(), - OpenQasmProgram( - 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])", - ] - ), - 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])", - ] - ), - 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])", - ] - ), - 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]", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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"', - ] - ), - inputs={}, - ), - ), - ( - Circuit() - .rx(0, 0.15, control=2, control_state=0) - .rx(1, 0.3, control=[2, 3]) - .cnot(target=0, control=[2, 3, 4]) - .measure(0) - .measure(1) - .measure(2) - .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];", - ] - ), - inputs={}, - ), - ), - ( - 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( - [ - "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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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])", - ] - ), - 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])", - ] - ), - 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]", - ] - ), - inputs={}, - ), - ), - ( - 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( - [ - "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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - inputs={}, - ), - ), - ], -) -def test_from_ir(expected_circuit, ir): - assert Circuit.from_ir(source=ir.source, inputs=ir.inputs) == expected_circuit - assert Circuit.from_ir(source=ir) == expected_circuit - - -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];", - ] - ), - inputs={"theta": 0.2, "phi": 0.3}, - ) - assert Circuit.from_ir(source=openqasm, inputs={"phi": 0.1}) == circuit - - -@pytest.mark.parametrize( - "expected_circuit, ir", - [ - ( - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - 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];", - ] - ), - inputs={}, - ), - ), - ], -) -def test_from_ir_advanced_openqasm(expected_circuit, ir): - circuit_from_ir = Circuit.from_ir(source=ir.source, inputs=ir.inputs) - - assert circuit_from_ir == expected_circuit - - -def test_braket_result_to_result_type_raises_type_error(): - with pytest.raises(TypeError, match="Result type str is not supported"): - braket_result_to_result_type("type error test") - - -@pytest.mark.parametrize( - "ir_type, serialization_properties, expected_exception, expected_message", - [ - ( - "invalid-ir-type", - OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), - ValueError, - "Supplied ir_type invalid-ir-type is not supported.", - ), - ( - IRType.OPENQASM, - OpenQASMSerializationProperties("invalid-qubit-reference-type"), - ValueError, - "Invalid qubit_reference_type invalid-qubit-reference-type supplied.", - ), - ( - IRType.OPENQASM, - "invalid-serialization-properties", - ValueError, - "serialization_properties must be of type OpenQASMSerializationProperties " - "for IRType.OPENQASM.", - ), - ], -) -def test_circuit_to_ir_invalid_inputs( - ir_type, serialization_properties, expected_exception, expected_message -): - circuit = Circuit().h(0).cnot(0, 1) - with pytest.raises(expected_exception) as exc: - circuit.to_ir(ir_type, serialization_properties=serialization_properties) - assert exc.value.args[0] == expected_message - - -def test_to_unitary_empty_instructions_returns_empty_array(): - circ = Circuit() - circ.to_unitary() == [] - - -@pytest.mark.parametrize( - "circuit", - [ - (Circuit().phaseshift(0, 0.15).apply_gate_noise(noise.Noise.BitFlip(probability=0.1))), - (Circuit().cnot(1, 0).apply_gate_noise(noise.Noise.TwoQubitDepolarizing(probability=0.1))), - ( - Circuit() - .x(1) - .i(2) - .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[1]) - ), - ( - Circuit() - .x(1) - .i(2) - .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[2]) - ), - (Circuit().x(1).i(2).apply_gate_noise(noise.Noise.BitFlip(probability=0.1))), - (Circuit().x(1).apply_gate_noise(noise.Noise.BitFlip(probability=0.1)).i(2)), - ( - Circuit() - .y(1) - .z(2) - .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[1]) - ), - ( - Circuit() - .y(1) - .z(2) - .apply_gate_noise(noise.Noise.BitFlip(probability=0.1), target_qubits=[2]) - ), - (Circuit().y(1).z(2).apply_gate_noise(noise.Noise.BitFlip(probability=0.1))), - (Circuit().y(1).apply_gate_noise(noise.Noise.BitFlip(probability=0.1)).z(2)), - ( - Circuit() - .cphaseshift(2, 1, 0.15) - .si(3) - .apply_gate_noise( - noise.Noise.TwoQubitDepolarizing(probability=0.1), target_qubits=[1, 2] - ) - ), - ( - Circuit() - .cphaseshift(2, 1, 0.15) - .apply_gate_noise(noise.Noise.TwoQubitDepolarizing(probability=0.1)) - .si(3) - ), - ], -) -def test_to_unitary_noise_raises_error(circuit): - with pytest.raises(TypeError): - circuit.to_unitary() - - -def test_to_unitary_parameterized(): - theta = FreeParameter("theta") - circ = Circuit().rx(angle=theta, target=0) - with pytest.raises(TypeError): - np.allclose(circ.to_unitary()) - - -def test_to_unitary_noise_not_apply_returns_expected_unitary(recwarn): - circuit = ( - Circuit() - .cphaseshift(1, 2, 0.15) - .si(3) - .apply_gate_noise(noise.Noise.TwoQubitDepolarizing(probability=0.1), target_qubits=[1, 3]) - ) - - assert len(recwarn) == 1 - assert str(recwarn[0].message).startswith("Noise is not applied to any gate") - - assert np.allclose( - circuit.to_unitary(), - np.kron(gates.CPhaseShift(0.15).to_matrix(), gates.Si().to_matrix()), - ) - - -def test_to_unitary_with_compiler_directives_returns_expected_unitary(): - circuit = Circuit().add_verbatim_box(Circuit().cphaseshift(1, 2, 0.15).si(3)) - assert np.allclose( - circuit.to_unitary(), - np.kron(gates.CPhaseShift(0.15).to_matrix(), gates.Si().to_matrix()), - ) - - -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", - [ - (Circuit().h(0), gates.H().to_matrix()), - (Circuit().h(0).add_result_type(ResultType.Probability(target=[0])), gates.H().to_matrix()), - (Circuit().h(1), gates.H().to_matrix()), - (Circuit().h(2), gates.H().to_matrix()), - (Circuit().x(0), gates.X().to_matrix()), - (Circuit().y(0), gates.Y().to_matrix()), - (Circuit().z(0), gates.Z().to_matrix()), - (Circuit().s(0), gates.S().to_matrix()), - (Circuit().si(0), gates.Si().to_matrix()), - (Circuit().t(0), gates.T().to_matrix()), - (Circuit().ti(0), gates.Ti().to_matrix()), - (Circuit().v(0), gates.V().to_matrix()), - (Circuit().vi(0), gates.Vi().to_matrix()), - (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()), - (Circuit().cnot(2, 4), gates.CNot().to_matrix()), - (Circuit().swap(0, 1), gates.Swap().to_matrix()), - (Circuit().swap(1, 0), gates.Swap().to_matrix()), - (Circuit().iswap(0, 1), gates.ISwap().to_matrix()), - (Circuit().iswap(1, 0), gates.ISwap().to_matrix()), - (Circuit().pswap(0, 1, 0.15), gates.PSwap(0.15).to_matrix()), - (Circuit().pswap(1, 0, 0.15), gates.PSwap(0.15).to_matrix()), - (Circuit().xy(0, 1, 0.15), gates.XY(0.15).to_matrix()), - (Circuit().xy(1, 0, 0.15), gates.XY(0.15).to_matrix()), - (Circuit().cphaseshift(0, 1, 0.15), gates.CPhaseShift(0.15).to_matrix()), - (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()), - (Circuit().yy(0, 1, 0.15), gates.YY(0.15).to_matrix()), - (Circuit().zz(0, 1, 0.15), gates.ZZ(0.15).to_matrix()), - (Circuit().ccnot(0, 1, 2), gates.CCNot().to_matrix()), - ( - Circuit() - .ccnot(0, 1, 2) - .add_result_type(ResultType.Expectation(observable=Observable.Y(), target=[1])), - gates.CCNot().to_matrix(), - ), - (Circuit().ccnot(0, 1, 2), gates.CCNot().to_matrix()), - (Circuit().cswap(0, 1, 2), gates.CSwap().to_matrix()), - (Circuit().cswap(0, 2, 1), gates.CSwap().to_matrix()), - (Circuit().h(1), gates.H().to_matrix()), - (Circuit().x(1).i(2), np.kron(gates.X().to_matrix(), np.eye(2))), - (Circuit().y(1).z(2), np.kron(gates.Y().to_matrix(), gates.Z().to_matrix())), - (Circuit().rx(1, 0.15), gates.Rx(0.15).to_matrix()), - (Circuit().ry(1, 0.15).i(2), np.kron(gates.Ry(0.15).to_matrix(), np.eye(2))), - (Circuit().rz(1, 0.15).s(2), np.kron(gates.Rz(0.15).to_matrix(), gates.S().to_matrix())), - (Circuit().pswap(1, 2, 0.15), gates.PSwap(0.15).to_matrix()), - (Circuit().pswap(2, 1, 0.15), gates.PSwap(0.15).to_matrix()), - (Circuit().xy(1, 2, 0.15).i(3), np.kron(gates.XY(0.15).to_matrix(), np.eye(2))), - (Circuit().xy(2, 1, 0.15).i(3), np.kron(gates.XY(0.15).to_matrix(), np.eye(2))), - ( - Circuit().cphaseshift(1, 2, 0.15).si(3), - np.kron(gates.CPhaseShift(0.15).to_matrix(), gates.Si().to_matrix()), - ), - (Circuit().ccnot(1, 2, 3), gates.CCNot().to_matrix()), - (Circuit().ccnot(2, 1, 3), gates.CCNot().to_matrix()), - (Circuit().cswap(1, 2, 3).i(4), np.kron(gates.CSwap().to_matrix(), np.eye(2))), - (Circuit().cswap(1, 3, 2).i(4), np.kron(gates.CSwap().to_matrix(), np.eye(2))), - (Circuit().cswap(1, 2, 3).t(4), np.kron(gates.CSwap().to_matrix(), gates.T().to_matrix())), - (Circuit().cswap(1, 3, 2).t(4), np.kron(gates.CSwap().to_matrix(), gates.T().to_matrix())), - (Circuit().h(0).h(0), gates.I().to_matrix()), - (Circuit().h(0).x(0), np.dot(gates.X().to_matrix(), gates.H().to_matrix())), - (Circuit().x(0).h(0), np.dot(gates.H().to_matrix(), gates.X().to_matrix())), - ( - Circuit().y(0).z(1).cnot(0, 1), - np.dot(gates.CNot().to_matrix(), np.kron(gates.Y().to_matrix(), gates.Z().to_matrix())), - ), - ( - Circuit().z(0).y(1).cnot(0, 1), - np.dot(gates.CNot().to_matrix(), np.kron(gates.Z().to_matrix(), gates.Y().to_matrix())), - ), - ( - Circuit().y(0).z(1).cnot(0, 1).cnot(1, 2), - np.dot( - np.dot( - np.kron(np.eye(2), gates.CNot().to_matrix()), - np.kron(gates.CNot().to_matrix(), np.eye(2)), - ), - np.kron(np.kron(gates.Y().to_matrix(), gates.Z().to_matrix()), np.eye(2)), - ), - ), - ( - Circuit().z(0).y(1).cnot(0, 1).ccnot(0, 1, 2), - np.dot( - np.dot( - gates.CCNot().to_matrix(), - np.kron(gates.CNot().to_matrix(), np.eye(2)), - ), - np.kron(np.kron(gates.Z().to_matrix(), gates.Y().to_matrix()), np.eye(2)), - ), - ), - ( - Circuit().cnot(1, 0), - np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - ], - dtype=complex, - ), - ), - ( - Circuit().x(0, control=1), - np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - ], - dtype=complex, - ), - ), - ( - Circuit().x(1, control=0, power=0.5), - np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 0.5 + 0.5j, 0.5 - 0.5j], - [0.0, 0.0, 0.5 - 0.5j, 0.5 + 0.5j], - ], - dtype=complex, - ), - ), - ( - Circuit().x(1, control=0, power=2), - np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - ], - dtype=complex, - ), - ), - ( - Circuit().ccnot(1, 2, 0), - np.array( - [ - [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], - ], - dtype=complex, - ), - ), - ( - Circuit().ccnot(2, 1, 0), - np.array( - [ - [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], - ], - dtype=complex, - ), - ), - ( - Circuit().ccnot(0, 2, 1), - np.array( - [ - [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], - ], - dtype=complex, - ), - ), - ( - Circuit().ccnot(2, 0, 1), - np.array( - [ - [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], - ], - dtype=complex, - ), - ), - ( - Circuit().s(0).v(1).cnot(0, 1).cnot(2, 1), - np.dot( - np.dot( - np.dot( - np.kron( - np.eye(2), - np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - ], - dtype=complex, - ), - ), - np.kron( - np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0], - ], - dtype=complex, - ), - np.eye(2), - ), - ), - np.kron(np.kron(np.eye(2), gates.V().to_matrix()), np.eye(2)), - ), - np.kron(gates.S().to_matrix(), np.eye(4)), - ), - ), - ( - Circuit().z(0).y(1).cnot(1, 0).ccnot(2, 1, 0), - np.dot( - np.dot( - np.dot( - np.array( - [ - [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], - ], - dtype=complex, - ), - np.kron( - np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - ], - dtype=complex, - ), - np.eye(2), - ), - ), - np.kron(np.kron(np.eye(2), gates.Y().to_matrix()), np.eye(2)), - ), - np.kron(gates.Z().to_matrix(), np.eye(4)), - ), - ), - ( - Circuit().z(0).y(1).cnot(1, 0).ccnot(2, 0, 1), - np.dot( - np.dot( - np.dot( - np.array( - [ - [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], - ], - dtype=complex, - ), - np.kron( - np.array( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - ], - dtype=complex, - ), - np.eye(2), - ), - ), - np.kron(np.kron(np.eye(2), gates.Y().to_matrix()), np.eye(2)), - ), - np.kron(gates.Z().to_matrix(), np.eye(4)), - ), - ), - ], -) -def test_to_matrix_one_gate_returns_expected_unitary(circuit, expected_unitary): - assert np.allclose(circuit.to_unitary(), expected_unitary) - - -def test_circuit_with_symbol(): - theta = FreeParameter("theta") - - circ = ( - Circuit() - .ry(angle=theta, target=0) - .ry(angle=theta, target=1) - .ry(angle=theta, target=2) - .ry(angle=theta, target=3) - ) - expected = ( - Circuit() - .ry(angle=theta, target=0) - .ry(angle=theta, target=1) - .ry(angle=theta, target=2) - .ry(angle=theta, target=3) - ) - assert circ == expected - - -def test_basis_rotation_instructions_all(): - circ = Circuit().h(0).cnot(0, 1).sample(observable=Observable.Y()) - expected = [ - Instruction(Gate.Z(), 0), - Instruction(Gate.S(), 0), - Instruction(Gate.H(), 0), - Instruction(Gate.Z(), 1), - Instruction(Gate.S(), 1), - Instruction(Gate.H(), 1), - ] - assert circ.basis_rotation_instructions == expected - - -def test_basis_rotation_instructions_target(): - circ = Circuit().h(0).cnot(0, 1).expectation(observable=Observable.X(), target=0) - expected = [Instruction(Gate.H(), 0)] - assert circ.basis_rotation_instructions == expected - - -def test_basis_rotation_instructions_tensor_product(): - circ = ( - Circuit() - .h(0) - .cnot(0, 1) - .expectation(observable=Observable.X() @ Observable.Y() @ Observable.Y(), target=[0, 1, 2]) - ) - expected = [ - Instruction(Gate.H(), 0), - Instruction(Gate.Z(), 1), - Instruction(Gate.S(), 1), - Instruction(Gate.H(), 1), - Instruction(Gate.Z(), 2), - Instruction(Gate.S(), 2), - Instruction(Gate.H(), 2), - ] - assert circ.basis_rotation_instructions == expected - - -def test_basis_rotation_instructions_tensor_product_shared_factors(): - circ = ( - Circuit() - .h(0) - .cnot(0, 1) - .expectation(observable=Observable.X() @ Observable.Y() @ Observable.Y(), target=[0, 1, 2]) - .expectation(observable=Observable.X() @ Observable.Y(), target=[0, 1]) - ) - expected = [ - Instruction(Gate.H(), 0), - Instruction(Gate.Z(), 1), - Instruction(Gate.S(), 1), - Instruction(Gate.H(), 1), - Instruction(Gate.Z(), 2), - Instruction(Gate.S(), 2), - Instruction(Gate.H(), 2), - ] - assert circ.basis_rotation_instructions == expected - - -def test_basis_rotation_instructions_identity(): - circ = ( - Circuit() - .h(0) - .cnot(0, 1) - .cnot(1, 2) - .cnot(2, 3) - .cnot(3, 4) - .expectation(observable=Observable.X(), target=[0]) - .expectation(observable=Observable.I(), target=[2]) - .expectation(observable=Observable.I() @ Observable.Y(), target=[1, 3]) - .expectation(observable=Observable.I(), target=[0]) - .expectation(observable=Observable.X() @ Observable.I(), target=[1, 3]) - .expectation(observable=Observable.Y(), target=[2]) - ) - expected = [ - Instruction(Gate.H(), 0), - Instruction(Gate.H(), 1), - Instruction(Gate.Z(), 2), - Instruction(Gate.S(), 2), - Instruction(Gate.H(), 2), - Instruction(Gate.Z(), 3), - Instruction(Gate.S(), 3), - Instruction(Gate.H(), 3), - ] - assert circ.basis_rotation_instructions == expected - - -def test_basis_rotation_instructions_multiple_result_types_different_targets(): - circ = ( - Circuit() - .h(0) - .cnot(0, 1) - .expectation(observable=Observable.X(), target=0) - .sample(observable=Observable.H(), target=1) - ) - expected = [Instruction(Gate.H(), 0), Instruction(Gate.Ry(-np.pi / 4), 1)] - assert circ.basis_rotation_instructions == expected - - -def test_basis_rotation_instructions_multiple_result_types_same_targets(): - circ = ( - Circuit() - .h(0) - .cnot(0, 1) - .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) - .sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) - .variance(observable=Observable.H() @ Observable.X(), target=[0, 1]) - ) - expected = [Instruction(Gate.Ry(-np.pi / 4), 0), Instruction(Gate.H(), 1)] - assert circ.basis_rotation_instructions == expected - - -def test_basis_rotation_instructions_multiple_result_types_all_specified_same_targets(): - circ = ( - Circuit() - .h(0) - .cnot(0, 1) - .expectation(observable=Observable.H()) - .sample(observable=Observable.H(), target=[0]) - ) - expected = [Instruction(Gate.Ry(-np.pi / 4), 0), Instruction(Gate.Ry(-np.pi / 4), 1)] - assert circ.basis_rotation_instructions == expected - - -def test_basis_rotation_instructions_multiple_result_types_specified_all_same_targets(): - circ = ( - Circuit() - .h(0) - .cnot(0, 1) - .sample(observable=Observable.H(), target=[0]) - .expectation(observable=Observable.H()) - ) - expected = [Instruction(Gate.Ry(-np.pi / 4), 0), Instruction(Gate.Ry(-np.pi / 4), 1)] - assert circ.basis_rotation_instructions == expected - - -def test_basis_rotation_instructions_multiple_result_types_same_targets_hermitian(): - circ = ( - Circuit() - .h(0) - .cnot(0, 1) - .sample(observable=Observable.Hermitian(matrix=np.array([[1, 0], [0, -1]])), target=[1]) - .expectation( - observable=Observable.Hermitian(matrix=np.array([[1, 0], [0, -1]])), target=[1] - ) - ) - expected = [Instruction(Gate.Unitary(matrix=np.array([[0, 1], [1, 0]])), target=[1])] - assert circ.basis_rotation_instructions == expected - - -def test_basis_rotation_instructions_multiple_result_types_different_hermitian_targets(): - circ = ( - Circuit() - .h(0) - .cnot(0, 1) - .sample(observable=Observable.Hermitian(matrix=np.array([[1, 0], [0, -1]])), target=[1]) - .expectation(observable=Observable.Hermitian(matrix=np.array([[0, 1], [1, 0]])), target=[0]) - ) - expected = [ - Instruction( - Gate.Unitary( - matrix=1.0 / np.sqrt(2.0) * np.array([[-1.0, 1.0], [1.0, 1.0]], dtype=complex) - ), - target=[0], - ), - Instruction(Gate.Unitary(matrix=np.array([[0, 1], [1, 0]])), target=[1]), - ] - assert circ.basis_rotation_instructions == expected - - -def test_basis_rotation_instructions_multiple_result_types_tensor_product_hermitian(): - circ = ( - Circuit() - .h(0) - .cnot(0, 1) - .cnot(1, 2) - .sample( - observable=Observable.Hermitian(matrix=np.array([[1, 0], [0, -1]])) @ Observable.H(), - target=[0, 1], - ) - .variance( - observable=Observable.Hermitian(matrix=np.array([[1, 0], [0, -1]])) @ Observable.H(), - target=[0, 1], - ) - .expectation(observable=Observable.Hermitian(matrix=np.array([[0, 1], [1, 0]])), target=[2]) - ) - expected = [ - Instruction(Gate.Unitary(matrix=np.array([[0, 1], [1, 0]])), target=[0]), - Instruction(Gate.Ry(-np.pi / 4), 1), - Instruction( - Gate.Unitary( - matrix=1.0 / np.sqrt(2.0) * np.array([[-1.0, 1.0], [1.0, 1.0]], dtype=complex) - ), - target=[2], - ), - ] - assert circ.basis_rotation_instructions == expected - - -def test_basis_rotation_instructions_multiple_result_types_tensor_product_hermitian_qubit_count_2(): - circ = ( - Circuit() - .h(0) - .cnot(0, 1) - .cnot(1, 2) - .expectation(observable=Observable.I(), target=[1]) - .sample( - observable=Observable.Hermitian(matrix=np.eye(4)) @ Observable.H(), target=[0, 1, 2] - ) - .variance(observable=Observable.H(), target=[2]) - .variance(observable=Observable.Hermitian(matrix=np.eye(4)), target=[0, 1]) - .expectation(observable=Observable.I(), target=[0]) - ) - expected = [ - Instruction(Gate.Unitary(matrix=np.eye(4)), target=[0, 1]), - Instruction(Gate.Ry(-np.pi / 4), 2), - ] - assert circ.basis_rotation_instructions == expected - - -def test_basis_rotation_instructions_multiple_result_types_tensor_product_probability(): - circ = ( - Circuit() - .h(0) - .cnot(0, 1) - .cnot(1, 2) - .probability([0, 1]) - .sample(observable=Observable.Z() @ Observable.Z() @ Observable.H(), target=[0, 1, 2]) - .variance(observable=Observable.H(), target=[2]) - ) - expected = [ - Instruction(Gate.Ry(-np.pi / 4), 2), - ] - assert circ.basis_rotation_instructions == expected - - -def test_basis_rotation_instructions_call_twice(): - circ = ( - Circuit() - .h(0) - .cnot(0, 1) - .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) - .sample(observable=Observable.H() @ Observable.X(), target=[0, 1]) - .variance(observable=Observable.H() @ Observable.X(), target=[0, 1]) - ) - expected = [Instruction(Gate.Ry(-np.pi / 4), 0), Instruction(Gate.H(), 1)] - assert circ.basis_rotation_instructions == expected - assert circ.basis_rotation_instructions == expected - - -def test_depth_getter(h): - assert h.depth is h._moments.depth - - -def test_depth_setter(h): - with pytest.raises(AttributeError): - h.depth = 1 - - -def test_instructions_getter(h): - assert h.instructions == list(h._moments.values()) - - -def test_instructions_setter(h, h_instr): - with pytest.raises(AttributeError): - h.instructions = [h_instr] - - -def test_moments_getter(h): - assert h.moments is h._moments - - -def test_moments_setter(h): - with pytest.raises(AttributeError): - h.moments = Moments() - - -def test_qubit_count_getter(h): - assert h.qubit_count is h._moments.qubit_count - - -def test_qubit_count_setter(h): - with pytest.raises(AttributeError): - h.qubit_count = 1 - - -@pytest.mark.parametrize( - "circuit,expected_qubit_count", - [ - (Circuit().h(0).h(1).h(2), 3), - ( - Circuit() - .h(0) - .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) - .sample(observable=Observable.H() @ Observable.X(), target=[0, 1]), - 2, - ), - ( - Circuit().h(0).probability([1, 2]).state_vector(), - 1, - ), - ( - Circuit() - .h(0) - .variance(observable=Observable.H(), target=1) - .state_vector() - .amplitude(["01"]), - 2, - ), - ], -) -def test_qubit_count(circuit, expected_qubit_count): - assert circuit.qubit_count == expected_qubit_count - - -@pytest.mark.parametrize( - "circuit,expected_qubits", - [ - (Circuit().h(0).h(1).h(2), QubitSet([0, 1, 2])), - ( - Circuit() - .h(0) - .expectation(observable=Observable.H() @ Observable.X(), target=[0, 1]) - .sample(observable=Observable.H() @ Observable.X(), target=[0, 1]), - QubitSet([0, 1]), - ), - ( - Circuit().h(0).probability([1, 2]).state_vector(), - QubitSet([0]), - ), - ( - Circuit() - .h(0) - .variance(observable=Observable.H(), target=1) - .state_vector() - .amplitude(["01"]), - QubitSet([0, 1]), - ), - ], -) -def test_circuit_qubits(circuit, expected_qubits): - assert circuit.qubits == expected_qubits - - -def test_qubits_getter(h): - assert h.qubits == h._moments.qubits - assert h.qubits is not h._moments.qubits - - -def test_qubits_setter(h): - with pytest.raises(AttributeError): - h.qubits = QubitSet(1) - - -def test_diagram(h): - expected = "foo bar diagram" - mock_diagram = Mock() - mock_diagram.build_diagram.return_value = expected - - assert h.diagram(mock_diagram) == expected - mock_diagram.build_diagram.assert_called_with(h) - - -def test_add_parameterized_check_true(): - theta = FreeParameter("theta") - circ = ( - Circuit() - .ry(angle=theta, target=0) - .ry(angle=theta, target=1) - .ry(angle=theta, target=2) - .ry(angle=theta, target=3) - ) - expected = {theta} - assert circ.parameters == expected - - -def test_add_parameterized_instr_parameterized_circ_check_true(): - theta = FreeParameter("theta") - alpha = FreeParameter("alpha") - 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 = {theta, alpha} - assert circ.parameters == expected - - -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 = {theta} - assert circ.parameters == expected - - -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 = {theta} - assert circ.parameters == expected - - -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 = {theta} - assert circ.parameters == expected - - -@pytest.mark.parametrize( - "input_circ", - [ - (Circuit().ry(angle=1, target=0).ry(angle=2, target=1)), - (Circuit().ry(angle=1, target=0).add_circuit(Circuit().ry(angle=2, target=0))), - ], -) -def test_parameterized_check_false(input_circ): - circ = input_circ - expected = 0 - - assert len(circ.parameters) == expected - - -def test_parameters(): - theta = FreeParameter("theta") - circ = Circuit().ry(angle=theta, target=0).ry(angle=theta, target=1).ry(angle=theta, target=2) - expected = {theta} - assert circ.parameters == expected - - -def test_no_parameters(): - circ = Circuit().ry(angle=0.12, target=0).ry(angle=0.25, target=1).ry(angle=0.6, target=2) - expected = set() - - assert circ.parameters == expected - - -def test_make_bound_circuit_strict(): - theta = FreeParameter("theta") - input_val = np.pi - circ = Circuit().ry(angle=theta, target=0).ry(angle=theta, target=1).ry(angle=theta, target=2) - circ_new = circ.make_bound_circuit({"theta": input_val}, strict=True) - expected = ( - Circuit().ry(angle=np.pi, target=0).ry(angle=np.pi, target=1).ry(angle=np.pi, target=2) - ) - - assert circ_new == expected - - -def test_make_bound_circuit_strict_false(): - input_val = np.pi - theta = FreeParameter("theta") - param_superset = {"theta": input_val, "alpha": input_val, "beta": input_val} - circ = Circuit().ry(angle=theta, target=0).ry(angle=theta, target=1).ry(angle=theta, target=2) - circ_new = circ.make_bound_circuit(param_superset) - expected = ( - Circuit().ry(angle=np.pi, target=0).ry(angle=np.pi, target=1).ry(angle=np.pi, target=2) - ) - - assert circ_new == expected - - -def test_make_bound_circuit_multiple(): - theta = FreeParameter("theta") - alpha = FreeParameter("alpha") - input_val = np.pi - circ = Circuit().ry(angle=theta, target=0).ry(angle=theta, target=1).ry(angle=alpha, target=2) - circ_new = circ.make_bound_circuit({"theta": input_val, "alpha": input_val}) - expected = ( - Circuit().ry(angle=np.pi, target=0).ry(angle=np.pi, target=1).ry(angle=np.pi, target=2) - ) - - assert circ_new == expected - - -def test_make_bound_circuit_partial_bind(): - theta = FreeParameter("theta") - alpha = FreeParameter("alpha") - input_val = np.pi - circ = Circuit().ry(angle=theta, target=0).ry(angle=theta, target=1).ry(angle=alpha, target=2) - circ_new = circ.make_bound_circuit({"theta": input_val}) - expected_circ = ( - Circuit().ry(angle=np.pi, target=0).ry(angle=np.pi, target=1).ry(angle=alpha, target=2) - ) - expected_parameters = {alpha} - assert circ_new == expected_circ and circ_new.parameters == expected_parameters - - -def test_make_bound_circuit_non_existent_param(): - theta = FreeParameter("theta") - input_val = np.pi - circ = Circuit().ry(angle=theta, target=0).ry(angle=theta, target=1).ry(angle=theta, target=2) - with pytest.raises(ValueError): - circ.make_bound_circuit({"alpha": input_val}, strict=True) - - -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): - circ.make_bound_circuit({"theta": input_val}) - - -def test_circuit_with_expr(): - theta = FreeParameter("theta") - alpha = FreeParameter("alpha") - circ = ( - Circuit() - .ry(angle=theta * 2 + theta, target=0) - .rx(angle=(alpha + theta + 2 * alpha * theta), target=2) - .rz(angle=theta, target=1) - ) - circ.add_instruction(Instruction(Gate.Ry(alpha), 3)) - - new_circ = circ(theta=1, alpha=np.pi) - expected = ( - Circuit() - .ry(angle=3, target=0) - .rx(angle=(3 * np.pi + 1), target=2) - .rz(angle=1, target=1) - .ry(angle=np.pi, target=3) - ) - - assert new_circ == expected - - -def test_circuit_with_expr_not_fully_bound(): - theta = FreeParameter("theta") - alpha = FreeParameter("alpha") - circ = ( - Circuit() - .ry(angle=theta * 2 + theta, target=0) - .rx(angle=(alpha + theta + 2 * alpha * theta), target=2) - .rz(angle=theta, target=1) - ) - circ.add_instruction(Instruction(Gate.Ry(alpha), 3)) - - new_circ = circ(theta=1) - expected = ( - Circuit() - .ry(angle=3, target=0) - .rx(angle=(3 * alpha + 1), target=2) - .rz(angle=1, target=1) - .ry(angle=alpha, target=3) - ) - assert new_circ == expected - - -def test_pulse_circuit_to_openqasm(predefined_frame_1, user_defined_frame): - pulse_sequence_1 = ( - PulseSequence() - .set_frequency(predefined_frame_1, 3e9) - .play(predefined_frame_1, GaussianWaveform(length=1e-3, sigma=0.7, id="gauss_wf")) - .play( - predefined_frame_1, - DragGaussianWaveform(length=3e-3, sigma=0.4, beta=0.2, id="drag_gauss_wf"), - ) - ) - - pulse_sequence_2 = ( - PulseSequence() - .set_frequency(predefined_frame_1, 3e9) - .play(user_defined_frame, GaussianWaveform(length=1e-3, sigma=0.7, id="gauss_wf")) - .play( - predefined_frame_1, - DragGaussianWaveform(length=3e-3, sigma=0.4, beta=0.2, id="drag_gauss_wf_2"), - ) - ) - - circuit = ( - Circuit().h(0).pulse_gate(0, pulse_sequence_1).x(1).pulse_gate(1, pulse_sequence_2).h(1) - ) - - pulse_sequence_2.play( - user_defined_frame, GaussianWaveform(length=1e-3, sigma=0.7, id="gauss_wf_ignore") - ) - - assert circuit.to_ir( - ir_type=IRType.OPENQASM, - 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;", - ] - ) - - -def test_pulse_circuit_conflicting_wf(predefined_frame_1, user_defined_frame): - pulse_sequence_1 = ( - PulseSequence() - .set_frequency(predefined_frame_1, 3e9) - .play(predefined_frame_1, GaussianWaveform(length=1e-3, sigma=0.7, id="gauss_wf")) - ) - - pulse_sequence_2 = ( - PulseSequence() - .set_frequency(predefined_frame_1, 3e9) - .play(user_defined_frame, GaussianWaveform(length=1e-3, sigma=0.3, id="gauss_wf")) - ) - - circuit = ( - Circuit().h(0).pulse_gate(0, pulse_sequence_1).x(1).pulse_gate(1, pulse_sequence_2).h(1) - ) - - with pytest.raises(ValueError): - circuit.to_ir( - ir_type=IRType.OPENQASM, - serialization_properties=OpenQASMSerializationProperties( - qubit_reference_type=QubitReferenceType.PHYSICAL - ), - ) - - -def test_pulse_circuit_conflicting_frame(user_defined_frame): - user_defined_frame_x = Frame( - user_defined_frame.id, - Port("wrong_port", 1e-9), - user_defined_frame.frequency, - user_defined_frame.phase, - ) - pulse_sequence_user_defined_frame_x = ( - PulseSequence() - .set_frequency(user_defined_frame_x, 3e9) - .play(user_defined_frame_x, GaussianWaveform(length=1e-3, sigma=0.7, id="gauss_wf")) - ) - - pulse_sequence_user_defined_frame = ( - PulseSequence() - .set_frequency(user_defined_frame, 3e9) - .play(user_defined_frame, GaussianWaveform(length=1e-3, sigma=0.7, id="gauss_wf")) - ) - - circuit = ( - Circuit() - .h(0) - .pulse_gate(0, pulse_sequence_user_defined_frame) - .x(1) - .pulse_gate(1, pulse_sequence_user_defined_frame_x) - .h(1) - ) - - with pytest.raises(ValueError): - circuit.to_ir( - ir_type=IRType.OPENQASM, - serialization_properties=OpenQASMSerializationProperties( - qubit_reference_type=QubitReferenceType.PHYSICAL - ), - ) - - -def test_parametrized_pulse_circuit(user_defined_frame): - frequency_parameter = FreeParameter("frequency") - length = FreeParameter("length") - theta = FreeParameter("theta") - pulse_sequence = ( - PulseSequence() - .set_frequency(user_defined_frame, frequency_parameter) - .play(user_defined_frame, GaussianWaveform(length=length, sigma=0.7, id="gauss_wf")) - ) - - circuit = ( - Circuit().rx(angle=theta, target=0).pulse_gate(pulse_sequence=pulse_sequence, targets=1) - ) - - assert circuit.parameters == {frequency_parameter, length, theta} - - bound_half = circuit(theta=0.5, length=1e-5) - assert bound_half.to_ir( - ir_type=IRType.OPENQASM, - 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;", - ] - ) - - bound = bound_half(frequency=1e7) - - assert bound.to_ir( - ir_type=IRType.OPENQASM, - 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;", - ] - ) - - -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_circuit_helpers.py b/test/unit_tests/braket/circuits/test_circuit_helpers.py deleted file mode 100644 index 8960325c..00000000 --- a/test/unit_tests/braket/circuits/test_circuit_helpers.py +++ /dev/null @@ -1,114 +0,0 @@ -# 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, Observable, observables -from braket.circuits.circuit_helpers import validate_circuit_and_shots - - -def test_validate_circuit_and_shots_no_instructions(): - 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, 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, match="No result types specified for circuit and shots=0."): - validate_circuit_and_shots(Circuit().h(0), 0) - - -def test_validate_circuit_and_shots_100_no_results(): - assert validate_circuit_and_shots(Circuit().h(0), 100) is None - - -def test_validate_circuit_and_shots_0_results(): - assert validate_circuit_and_shots(Circuit().h(0).state_vector(), 0) is None - - -def test_validate_circuit_and_shots_100_results(): - assert validate_circuit_and_shots(Circuit().h(0).probability(), 100) is None - - -def test_validate_circuit_and_shots_100_results_mixed_result(): - assert ( - validate_circuit_and_shots( - Circuit().h(0).expectation(observable=Observable.Z(), target=0), 100 - ) - is None - ) - - -def test_validate_circuit_and_shots_100_result_state_vector(): - 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, match="StateVector or Amplitude cannot be specified when shots>0" - ): - validate_circuit_and_shots(Circuit().h(0).amplitude(state=["0"]), 100) - - -def test_validate_circuit_and_shots_0_noncommuting(): - validate_circuit_and_shots( - Circuit() - .h(0) - .expectation(observables.X() @ observables.Y(), [0, 1]) - .expectation(observables.Y() @ observables.X(), [0, 1]), - 0, - ) - - -def test_validate_circuit_and_shots_100_noncommuting(): - with pytest.raises(ValueError, match="Observables cannot be sampled simultaneously"): - validate_circuit_and_shots( - Circuit() - .h(0) - .expectation(observables.X() @ observables.Y(), [0, 1]) - .expectation(observables.Y() @ observables.X(), [0, 1]), - 100, - ) - - -def test_probability_limit(): - circ = Circuit() - for i in range(50): - circ.h(i) - circ.probability() - - too_many_qubits = "Probability target must be less than or equal to 40 qubits." - with pytest.raises(ValueError, match=too_many_qubits): - validate_circuit_and_shots(circ, 100) diff --git a/test/unit_tests/braket/circuits/test_compiler_directive.py b/test/unit_tests/braket/circuits/test_compiler_directive.py deleted file mode 100644 index f212b380..00000000 --- a/test/unit_tests/braket/circuits/test_compiler_directive.py +++ /dev/null @@ -1,71 +0,0 @@ -# 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 Operator -from braket.circuits.compiler_directive import CompilerDirective -from braket.circuits.serialization import IRType - - -@pytest.fixture -def ascii_symbols(): - return ["foo"] - - -@pytest.fixture -def compiler_directive(ascii_symbols): - return CompilerDirective(ascii_symbols=ascii_symbols) - - -def test_is_operator(compiler_directive): - assert isinstance(compiler_directive, Operator) - - -def test_ascii_symbols(compiler_directive, ascii_symbols): - assert compiler_directive.ascii_symbols == tuple(ascii_symbols) - - -def test_none_ascii(): - with pytest.raises(ValueError): - CompilerDirective(ascii_symbols=None) - - -def test_name(compiler_directive): - expected = compiler_directive.__class__.__name__ - assert compiler_directive.name == expected - - -def test_counterpart_not_implemented_by_default(compiler_directive): - with pytest.raises(NotImplementedError): - compiler_directive.counterpart() - - -def test_str(compiler_directive): - assert str(compiler_directive) == compiler_directive.name - - -@pytest.mark.parametrize( - "ir_type, serialization_properties, expected_exception, expected_message", - [ - (IRType.JAQCD, None, NotImplementedError, "to_jaqcd has not been implemented yet."), - (IRType.OPENQASM, None, NotImplementedError, "to_openqasm has not been implemented yet."), - ("invalid-ir-type", None, ValueError, "Supplied ir_type invalid-ir-type is not supported."), - ], -) -def test_compiler_directive_to_ir( - ir_type, serialization_properties, expected_exception, expected_message, compiler_directive -): - with pytest.raises(expected_exception) as exc: - compiler_directive.to_ir(0, ir_type, serialization_properties=serialization_properties) - assert exc.value.args[0] == expected_message diff --git a/test/unit_tests/braket/circuits/test_compiler_directives.py b/test/unit_tests/braket/circuits/test_compiler_directives.py deleted file mode 100644 index db0b7061..00000000 --- a/test/unit_tests/braket/circuits/test_compiler_directives.py +++ /dev/null @@ -1,55 +0,0 @@ -# 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 braket.ir.jaqcd as ir -from braket.circuits import compiler_directives -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" diff --git a/test/unit_tests/braket/circuits/test_gate.py b/test/unit_tests/braket/circuits/test_gate.py deleted file mode 100644 index 0b6d9be6..00000000 --- a/test/unit_tests/braket/circuits/test_gate.py +++ /dev/null @@ -1,119 +0,0 @@ -# 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 Gate, QuantumOperator, QubitSet -from braket.circuits.serialization import IRType - - -@pytest.fixture -def gate(): - return Gate(qubit_count=1, ascii_symbols=["foo"]) - - -def test_is_operator(gate): - assert isinstance(gate, QuantumOperator) - - -def test_adjoint_not_implemented_by_default(gate): - with pytest.raises(NotImplementedError): - gate.adjoint() - - -def test_to_matrix_not_implemented_by_default(gate): - with pytest.raises(NotImplementedError): - gate.to_matrix(None) - - -def test_matrix_equivalence(): - gate1 = Gate.H() - gate2 = Gate.H() - gate3 = Gate.CNot() - assert gate1.matrix_equivalence(gate2) - assert not gate2.matrix_equivalence(gate3) - - -def test_matrix_equivalence_non_gate(): - gate1 = Gate.H() - assert not gate1.matrix_equivalence(1) - - -def test_str(gate): - expected = f"{gate.name}('qubit_count': {gate.qubit_count})" - assert str(gate) == expected - - -def test_str_angle(): - gate = Gate.Rx(0.5) - expected = f"{gate.name}('angle': {gate.angle}, 'qubit_count': {gate.qubit_count})" - assert str(gate) == expected - - -def test_equality(): - gate_1 = Gate(qubit_count=1, ascii_symbols=["foo"]) - gate_2 = Gate(qubit_count=1, ascii_symbols=["bar"]) - other_gate = Gate.Rx(angle=0.34) - non_gate = "non gate" - - assert gate_1 == gate_2 - assert gate_1 is not gate_2 - assert gate_1 != other_gate - assert gate_1 != non_gate - - -def test_register_gate(): - class _FooGate(Gate): - def __init__(self): - super().__init__(qubit_count=1, ascii_symbols=["foo"]) - - Gate.register_gate(_FooGate) - assert Gate._FooGate().name == _FooGate().name - - -@pytest.mark.parametrize( - "ir_type, serialization_properties, expected_exception, expected_message, control", - [ - (IRType.JAQCD, None, NotImplementedError, "to_jaqcd is not implemented.", None), - ( - IRType.JAQCD, - None, - ValueError, - "Gate modifiers are not supported with Jaqcd.", - QubitSet(0), - ), - ( - "invalid-ir-type", - None, - ValueError, - "Supplied ir_type invalid-ir-type is not supported.", - None, - ), - ( - IRType.OPENQASM, - "invalid-property-type", - ValueError, - "serialization_properties must be of type OpenQASMSerializationProperties for " - "IRType.OPENQASM.", - None, - ), - ], -) -def test_gate_to_ir( - ir_type, serialization_properties, expected_exception, expected_message, gate, control -): - with pytest.raises(expected_exception) as exc: - gate.to_ir( - QubitSet(0), ir_type, serialization_properties=serialization_properties, control=control - ) - assert exc.value.args[0] == expected_message diff --git a/test/unit_tests/braket/circuits/test_gate_calibration.py b/test/unit_tests/braket/circuits/test_gate_calibration.py deleted file mode 100644 index de3fa2fc..00000000 --- a/test/unit_tests/braket/circuits/test_gate_calibration.py +++ /dev/null @@ -1,159 +0,0 @@ -# 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 Gate, QubitSet -from braket.circuits.gate_calibrations import GateCalibrations -from braket.pulse import Frame, Port, PulseSequence - - -@pytest.fixture -def port(): - return Port("test_port_ff", dt=1e-9) - - -@pytest.fixture -def frame_id(): - return "test_frame_rf" - - -@pytest.fixture -def frame(frame_id, port): - return Frame(frame_id, port, 1e6, is_predefined=True) - - -@pytest.fixture -def pulse_sequence(frame): - return ( - PulseSequence() - .barrier(qubits_or_frames=[frame]) - .delay(qubits_or_frames=[frame], duration=1000) - ) - - -def test_gc_creation(pulse_sequence): - calibration_key = (Gate.H(), QubitSet([0, 1])) - calibration = GateCalibrations({calibration_key: pulse_sequence}) - - assert calibration.pulse_sequences[calibration_key] == pulse_sequence - - -def test_gc_copy(pulse_sequence): - calibration_key = (Gate.H(), QubitSet([0, 1])) - calibration = GateCalibrations({calibration_key: pulse_sequence}) - - assert calibration == calibration.copy() - - -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, - } - ) - expected_calibration_1 = GateCalibrations({calibration_key: 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 - 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): - 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;", - "}", - ] - ) - - assert calibration.to_ir() == expected_ir - - -@pytest.mark.xfail(raises=ValueError) -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;", - "}", - ] - ) - 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;", - "}", - ] - ) - 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} - ) - - assert len(calibration) == 2 - - -@pytest.mark.parametrize( - "bad_input", - [ - ({(Gate.Rx(1), "string"): PulseSequence()}), - ({(Gate.Rx(1), QubitSet(0)): 4}), - ({("string_a", "string_b"): PulseSequence()}), - ], -) -@pytest.mark.xfail(raises=TypeError) -def test_bad_pulse_sequence(bad_input): - GateCalibrations(bad_input) diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py deleted file mode 100644 index 0b9ce52d..00000000 --- a/test/unit_tests/braket/circuits/test_gates.py +++ /dev/null @@ -1,1287 +0,0 @@ -# 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 functools - -import numpy as np -import pytest - -import braket.ir.jaqcd as ir -from braket.circuits import Circuit, FreeParameter, Gate, Instruction, QubitSet -from braket.circuits.serialization import ( - IRType, - OpenQASMSerializationProperties, - QubitReferenceType, -) -from braket.ir.jaqcd.shared_models import ( - Angle, - DoubleControl, - DoubleTarget, - MultiTarget, - SingleControl, - SingleTarget, - TwoDimensionalMatrix, -) -from braket.pulse import ArbitraryWaveform, Frame, Port, PulseSequence - - -class NoTarget: - pass - - -class DoubleAngle: - 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], {}), - (Gate.Z, "z", ir.Z, [SingleTarget], {}), - (Gate.S, "s", ir.S, [SingleTarget], {}), - (Gate.Si, "si", ir.Si, [SingleTarget], {}), - (Gate.T, "t", ir.T, [SingleTarget], {}), - (Gate.Ti, "ti", ir.Ti, [SingleTarget], {}), - (Gate.V, "v", ir.V, [SingleTarget], {}), - (Gate.Vi, "vi", ir.Vi, [SingleTarget], {}), - (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], {}), - (Gate.Swap, "swap", ir.Swap, [DoubleTarget], {}), - (Gate.CSwap, "cswap", ir.CSwap, [SingleControl, DoubleTarget], {}), - (Gate.ISwap, "iswap", ir.ISwap, [DoubleTarget], {}), - (Gate.PSwap, "pswap", ir.PSwap, [DoubleTarget, Angle], {}), - (Gate.XY, "xy", ir.XY, [DoubleTarget, Angle], {}), - (Gate.PhaseShift, "phaseshift", ir.PhaseShift, [SingleTarget, Angle], {}), - (Gate.CPhaseShift, "cphaseshift", ir.CPhaseShift, [SingleControl, SingleTarget, Angle], {}), - ( - Gate.CPhaseShift00, - "cphaseshift00", - ir.CPhaseShift00, - [SingleControl, SingleTarget, Angle], - {}, - ), - ( - Gate.CPhaseShift01, - "cphaseshift01", - ir.CPhaseShift01, - [SingleControl, SingleTarget, Angle], - {}, - ), - ( - Gate.CPhaseShift10, - "cphaseshift10", - ir.CPhaseShift10, - [SingleControl, SingleTarget, Angle], - {}, - ), - (Gate.CY, "cy", ir.CY, [SingleTarget, SingleControl], {}), - (Gate.CZ, "cz", ir.CZ, [SingleTarget, SingleControl], {}), - (Gate.ECR, "ecr", ir.ECR, [DoubleTarget], {}), - (Gate.XX, "xx", ir.XX, [DoubleTarget, Angle], {}), - (Gate.YY, "yy", ir.YY, [DoubleTarget, Angle], {}), - (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, - "unitary", - ir.Unitary, - [TwoDimensionalMatrix, MultiTarget], - {"input_type": complex}, - ), - ( - Gate.Unitary, - "unitary", - ir.Unitary, - [TwoDimensionalMatrix, MultiTarget], - {"input_type": float}, - ), - ( - Gate.Unitary, - "unitary", - ir.Unitary, - [TwoDimensionalMatrix, MultiTarget], - {"input_type": int}, - ), -] - -parameterizable_gates = [ - Gate.GPhase, - Gate.Rx, - Gate.Ry, - Gate.Rz, - Gate.U, - Gate.PhaseShift, - Gate.PSwap, - Gate.XX, - Gate.XY, - Gate.YY, - Gate.ZZ, - Gate.CPhaseShift, - Gate.CPhaseShift00, - Gate.CPhaseShift01, - Gate.CPhaseShift10, - Gate.GPi, - Gate.GPi2, - Gate.PRx, - Gate.MS, -] - - -invalid_unitary_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]])), - (np.array([[0, 1], [1, 1]])), -] - - -def no_target_valid_input(**kwargs): - return {} - - -def single_target_valid_input(**kwargs): - return {"target": 2} - - -def double_target_valid_ir_input(**kwargs): - return {"targets": [2, 3]} - - -def double_target_valid_input(**kwargs): - return {"target1": 2, "target2": 3} - - -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} - - -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]} - - -def double_control_valid_input(**kwargs): - return {"control1": 0, "control2": 1} - - -def multi_target_valid_input(**kwargs): - return {"targets": [5]} - - -def two_dimensional_matrix_valid_ir_input(**kwargs): - return {"matrix": [[[0, 0], [1, 0]], [[1, 0], [0, 0]]]} - - -def two_dimensional_matrix_valid_input(**kwargs): - input_type = kwargs.get("input_type") - return {"matrix": np.array([[input_type(0), input_type(1)], [input_type(1), input_type(0)]])} - - -valid_ir_switcher = { - "NoTarget": no_target_valid_input, - "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, - "DoubleControl": double_control_valid_ir_input, - "MultiTarget": multi_target_valid_input, - "TwoDimensionalMatrix": two_dimensional_matrix_valid_ir_input, -} - -valid_subroutine_switcher = dict( - valid_ir_switcher, - **{ - "TwoDimensionalMatrix": two_dimensional_matrix_valid_input, - "DoubleTarget": double_target_valid_input, - "DoubleControl": double_control_valid_input, - }, -) - - -def create_valid_ir_input(irsubclasses): - input = {} - for subclass in irsubclasses: - 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 |= valid_subroutine_switcher.get(subclass.__name__, lambda: "Invalid subclass")( - **kwargs - ) - return input - - -def create_valid_target_input(irsubclasses): - 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 == 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())) - elif subclass == MultiTarget: - 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 not in (Angle, TwoDimensionalMatrix, DoubleAngle, TripleAngle): - raise ValueError("Invalid subclass") - input = {"target": QubitSet(qubit_set)} - input["control"] = QubitSet(control_qubit_set) - input["control_state"] = control_state - return input - - -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: - input.update(two_dimensional_matrix_valid_input(**kwargs)) - return input - - -def create_valid_instruction_input(testclass, irsubclasses, **kwargs): - input = create_valid_target_input(irsubclasses) - input["operator"] = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) - return input - - -def calculate_qubit_count(irsubclasses): - qubit_count = 0 - for subclass in irsubclasses: - if subclass == SingleTarget: - qubit_count += 1 - elif subclass == DoubleTarget: - qubit_count += 2 - elif subclass == SingleControl: - qubit_count += 1 - elif subclass == DoubleControl: - qubit_count += 2 - elif subclass == MultiTarget: - qubit_count += 3 - elif subclass not in ( - NoTarget, - Angle, - TwoDimensionalMatrix, - DoubleAngle, - TripleAngle, - ): - raise ValueError("Invalid subclass") - return qubit_count - - -@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) -def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs): - if irclass is not None: - expected = irclass(**create_valid_ir_input(irsubclasses)) - actual = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)).to_ir( - **create_valid_target_input(irsubclasses) - ) - assert actual == expected - - -@pytest.mark.parametrize( - "gate, target, serialization_properties, expected_ir", - [ - ( - Gate.Rx(angle=0.17), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "rx(0.17) q[4];", - ), - ( - Gate.Rx(angle=0.17), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "rx(0.17) $4;", - ), - ( - Gate.X(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "x q[4];", - ), - ( - Gate.X(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "x $4;", - ), - ( - Gate.Z(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "z q[4];", - ), - ( - Gate.Z(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "z $4;", - ), - ( - Gate.Y(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "y q[4];", - ), - ( - Gate.Y(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "y $4;", - ), - ( - Gate.H(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "h q[4];", - ), - ( - Gate.H(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "h $4;", - ), - ( - Gate.Ry(angle=0.17), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "ry(0.17) q[4];", - ), - ( - Gate.Ry(angle=0.17), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "ry(0.17) $4;", - ), - ( - Gate.ZZ(angle=0.17), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "zz(0.17) q[4], q[5];", - ), - ( - Gate.ZZ(angle=0.17), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "zz(0.17) $4, $5;", - ), - ( - Gate.I(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "i q[4];", - ), - ( - Gate.I(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "i $4;", - ), - ( - Gate.V(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "v q[4];", - ), - ( - Gate.V(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "v $4;", - ), - ( - Gate.CY(), - [0, 1], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cy q[0], q[1];", - ), - ( - Gate.CY(), - [0, 1], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "cy $0, $1;", - ), - ( - Gate.Rz(angle=0.17), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "rz(0.17) q[4];", - ), - ( - Gate.Rz(angle=0.17), - [4], - 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], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "xx(0.17) q[4], q[5];", - ), - ( - Gate.XX(angle=0.17), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "xx(0.17) $4, $5;", - ), - ( - Gate.T(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "t q[4];", - ), - ( - Gate.T(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "t $4;", - ), - ( - Gate.CZ(), - [0, 1], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "cz $0, $1;", - ), - ( - Gate.CZ(), - [0, 1], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cz q[0], q[1];", - ), - ( - Gate.YY(angle=0.17), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "yy(0.17) q[4], q[5];", - ), - ( - Gate.YY(angle=0.17), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "yy(0.17) $4, $5;", - ), - ( - Gate.XY(angle=0.17), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "xy(0.17) q[4], q[5];", - ), - ( - Gate.XY(angle=0.17), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "xy(0.17) $4, $5;", - ), - ( - Gate.ISwap(), - [0, 1], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "iswap $0, $1;", - ), - ( - Gate.ISwap(), - [0, 1], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "iswap q[0], q[1];", - ), - ( - Gate.Swap(), - [0, 1], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "swap $0, $1;", - ), - ( - Gate.Swap(), - [0, 1], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "swap q[0], q[1];", - ), - ( - Gate.ECR(), - [0, 1], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "ecr $0, $1;", - ), - ( - Gate.ECR(), - [0, 1], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "ecr q[0], q[1];", - ), - ( - Gate.CV(), - [0, 1], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "cv $0, $1;", - ), - ( - Gate.CV(), - [0, 1], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cv q[0], q[1];", - ), - ( - Gate.Vi(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "vi q[4];", - ), - ( - Gate.Vi(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "vi $4;", - ), - ( - Gate.CSwap(), - [0, 1, 2], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cswap q[0], q[1], q[2];", - ), - ( - Gate.CSwap(), - [0, 1, 2], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "cswap $0, $1, $2;", - ), - ( - Gate.CPhaseShift01(angle=0.17), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cphaseshift01(0.17) q[4], q[5];", - ), - ( - Gate.CPhaseShift01(angle=0.17), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "cphaseshift01(0.17) $4, $5;", - ), - ( - Gate.CPhaseShift00(angle=0.17), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cphaseshift00(0.17) q[4], q[5];", - ), - ( - Gate.CPhaseShift00(angle=0.17), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "cphaseshift00(0.17) $4, $5;", - ), - ( - Gate.CPhaseShift(angle=0.17), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cphaseshift(0.17) q[4], q[5];", - ), - ( - Gate.CPhaseShift(angle=0.17), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "cphaseshift(0.17) $4, $5;", - ), - ( - Gate.S(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "s q[4];", - ), - ( - Gate.S(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "s $4;", - ), - ( - Gate.Si(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "si q[4];", - ), - ( - Gate.Si(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "si $4;", - ), - ( - Gate.Ti(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "ti q[4];", - ), - ( - Gate.Ti(), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "ti $4;", - ), - ( - Gate.PhaseShift(angle=0.17), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "phaseshift(0.17) q[4];", - ), - ( - Gate.PhaseShift(angle=0.17), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "phaseshift(0.17) $4;", - ), - ( - Gate.CNot(), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cnot q[4], q[5];", - ), - ( - Gate.CNot(), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "cnot $4, $5;", - ), - ( - Gate.PSwap(angle=0.17), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "pswap(0.17) q[4], q[5];", - ), - ( - Gate.PSwap(angle=0.17), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "pswap(0.17) $4, $5;", - ), - ( - Gate.CPhaseShift10(angle=0.17), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "cphaseshift10(0.17) q[4], q[5];", - ), - ( - Gate.CPhaseShift10(angle=0.17), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "cphaseshift10(0.17) $4, $5;", - ), - ( - Gate.CCNot(), - [4, 5, 6], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "ccnot q[4], q[5], q[6];", - ), - ( - Gate.CCNot(), - [4, 5, 6], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "ccnot $4, $5, $6;", - ), - ( - Gate.Unitary(Gate.CCNot().to_matrix()), - [4, 5, 6], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket unitary([" - "[1.0, 0, 0, 0, 0, 0, 0, 0], " - "[0, 1.0, 0, 0, 0, 0, 0, 0], " - "[0, 0, 1.0, 0, 0, 0, 0, 0], " - "[0, 0, 0, 1.0, 0, 0, 0, 0], " - "[0, 0, 0, 0, 1.0, 0, 0, 0], " - "[0, 0, 0, 0, 0, 1.0, 0, 0], " - "[0, 0, 0, 0, 0, 0, 0, 1.0], " - "[0, 0, 0, 0, 0, 0, 1.0, 0]" - "]) q[4], q[5], q[6]", - ), - ( - Gate.Unitary(Gate.CCNot().to_matrix()), - [4, 5, 6], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "#pragma braket unitary([" - "[1.0, 0, 0, 0, 0, 0, 0, 0], " - "[0, 1.0, 0, 0, 0, 0, 0, 0], " - "[0, 0, 1.0, 0, 0, 0, 0, 0], " - "[0, 0, 0, 1.0, 0, 0, 0, 0], " - "[0, 0, 0, 0, 1.0, 0, 0, 0], " - "[0, 0, 0, 0, 0, 1.0, 0, 0], " - "[0, 0, 0, 0, 0, 0, 0, 1.0], " - "[0, 0, 0, 0, 0, 0, 1.0, 0]" - "]) $4, $5, $6", - ), - ( - Gate.Unitary(np.round(Gate.ECR().to_matrix(), 8)), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket unitary([" - "[0, 0, 0.70710678, 0.70710678im], " - "[0, 0, 0.70710678im, 0.70710678], " - "[0.70710678, -0.70710678im, 0, 0], " - "[-0.70710678im, 0.70710678, 0, 0]" - "]) q[4], q[5]", - ), - ( - Gate.Unitary(np.round(Gate.ECR().to_matrix(), 8)), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "#pragma braket unitary([" - "[0, 0, 0.70710678, 0.70710678im], " - "[0, 0, 0.70710678im, 0.70710678], " - "[0.70710678, -0.70710678im, 0, 0], " - "[-0.70710678im, 0.70710678, 0, 0]" - "]) $4, $5", - ), - ( - Gate.Unitary(np.round(Gate.T().to_matrix(), 8)), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket unitary([[1.0, 0], [0, 0.70710678 + 0.70710678im]]) q[4]", - ), - ( - Gate.Unitary(np.round(Gate.T().to_matrix(), 8)), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "#pragma braket unitary([[1.0, 0], [0, 0.70710678 + 0.70710678im]]) $4", - ), - ( - Gate.Unitary(np.array([[1.0, 0], [0, 0.70710678 - 0.70710678j]])), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket unitary([[1.0, 0], [0, 0.70710678 - 0.70710678im]]) q[4]", - ), - ( - Gate.PulseGate( - PulseSequence().play( - Frame("user_frame", Port("device_port_x", 1e-9), 1e9), - ArbitraryWaveform([1, 2], "arb_wf"), - ), - 1, - ), - [0], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "\n".join(["cal {", " play(user_frame, arb_wf);", "}"]), - ), - ( - Gate.GPi(angle=0.17), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "gpi(0.17) q[4];", - ), - ( - Gate.GPi(angle=0.17), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "gpi(0.17) $4;", - ), - ( - Gate.GPi2(angle=0.17), - [4], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "gpi2(0.17) q[4];", - ), - ( - Gate.GPi2(angle=0.17), - [4], - 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], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - f"ms(0.17, 3.45, {np.pi / 2}) q[4], q[5];", - ), - ( - Gate.MS(angle_1=0.17, angle_2=3.45), - [4, 5], - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - f"ms(0.17, 3.45, {np.pi / 2}) $4, $5;", - ), - ], -) -def test_gate_to_ir_openqasm(gate, target, serialization_properties, expected_ir): - assert ( - gate.to_ir( - target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties - ) - == expected_ir - ) - - -@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) -def test_ir_instruction_level(testclass, subroutine_name, irclass, irsubclasses, kwargs): - if irclass is not None: - expected = irclass(**create_valid_ir_input(irsubclasses)) - instruction = Instruction( - **create_valid_instruction_input(testclass, irsubclasses, **kwargs) - ) - actual = instruction.to_ir() - assert actual == expected - - -@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) -def test_gate_subroutine(testclass, subroutine_name, irclass, irsubclasses, kwargs): - qubit_count = calculate_qubit_count(irsubclasses) - subroutine = getattr(Circuit(), subroutine_name) - assert subroutine(**create_valid_subroutine_input(irsubclasses, **kwargs)) == Circuit( - Instruction(**create_valid_instruction_input(testclass, irsubclasses, **kwargs)) - ) - if qubit_count == 1: - multi_targets = [0, 1, 2] - 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: - 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) - - -@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)) - matrices = [elem.to_matrix() for elem in gate.adjoint()] - matrices.append(gate.to_matrix()) - identity = np.eye(2**gate.qubit_count) - assert np.allclose(functools.reduce(lambda a, b: a @ b, matrices), identity) - - -@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) -def test_gate_to_matrix(testclass, subroutine_name, irclass, irsubclasses, kwargs): - gate1 = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) - gate2 = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) - assert isinstance(gate1.to_matrix(), np.ndarray) - assert gate1.matrix_equivalence(gate2) - - -@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) -def test_fixed_qubit_count(testclass, subroutine_name, irclass, irsubclasses, kwargs): - fixed_qubit_count = testclass.fixed_qubit_count() - if fixed_qubit_count is not NotImplemented: - gate = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) - assert gate.qubit_count == fixed_qubit_count - - -# Additional Unitary gate tests - - -def test_equality(): - u1 = Gate.Unitary(np.array([[0 + 0j, 1 + 0j], [1 + 0j, 0 + 0j]])) - u2 = Gate.Unitary(np.array([[0, 1], [1, 0]], dtype=np.float32), display_name=["u2"]) - other_gate = Gate.Unitary(np.array([[1, 0], [0, 1]])) - non_gate = "non gate" - - assert u1 == u2 - assert u1 is not u2 - assert u1 != other_gate - assert u1 != non_gate - - -def test_free_param_equality(): - param1 = FreeParameter("theta") - param2 = FreeParameter("phi") - rx1 = Gate.Rx(param1) - rx2 = Gate.Rx(param1) - other_gate = Gate.Rx(param2) - - assert rx1 == rx2 - assert rx1 is not rx2 - assert rx1 != other_gate - assert rx1 != param1 - - -def test_large_unitary(): - matrix = np.eye(16, dtype=np.float32) - # Permute rows of matrix - matrix[[*range(16)]] = matrix[[(i + 1) % 16 for i in range(16)]] - unitary = Gate.Unitary(matrix) - assert unitary.qubit_count == 4 - - -@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 = 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) - new_gate = param_gate.bind_values(**mapping) - expected = gate(*range(num_params)) - - assert type(new_gate) is type(param_gate) and new_gate == expected - 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) - - -def test_bind_values_pulse_gate(): - qubit_count = 1 - 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")), - qubit_count, - ) - - def to_ir(pulse_gate): - return pulse_gate.to_ir(range(pulse_gate.qubit_count), IRType.OPENQASM) - - a = 3 - 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 + b);", - " delay[c * 1s] user_frame;", - "}", - ] - ) - - assert a_bound_ir == to_ir( - Gate.PulseGate(gate.pulse_sequence.make_bound_pulse_sequence({"a": a}), qubit_count) - ) - assert a_bound_ir != to_ir(gate) - - c = 4e-6 - ac_bound = a_bound.bind_values(c=c) - ac_bound_ir = to_ir(ac_bound) - assert ac_bound_ir == to_ir( - Gate.PulseGate(a_bound.pulse_sequence.make_bound_pulse_sequence({"c": c}), qubit_count) - ) - assert ac_bound_ir != a_bound_ir - - -def test_pulse_gate_capture_throws(): - with pytest.raises(ValueError): - Circuit().pulse_gate( - 0, PulseSequence().capture_v0(Frame("user_frame", Port("device_port_x", dt=1e-9), 1e9)) - ) - - -@pytest.mark.parametrize("matrix", invalid_unitary_matrices) -def test_unitary_invalid_matrix(matrix): - with pytest.raises(ValueError): - Gate.Unitary(matrix=matrix) - - -def test_unitary_matrix_target_size_mismatch(): - with pytest.raises(ValueError): - Circuit().unitary( - matrix=np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]), targets=[0] - ) - - -def test_pulse_gate_to_matrix(): - with pytest.raises(NotImplementedError): - Gate.PulseGate( - PulseSequence().play( - Frame("user_frame", Port("device_port_x", 1e-9), 1e9), - ArbitraryWaveform([1, 2], "arb_wf"), - ), - 1, - ).to_matrix() - - -@pytest.mark.parametrize( - "gate, target, control, control_state, expected_ir", - ( - (Gate.H(), QubitSet(0), QubitSet(1), None, "ctrl @ h q[1], q[0];"), - (Gate.H(), QubitSet(0), QubitSet([1, 2]), None, "ctrl(2) @ h q[1], q[2], q[0];"), - (Gate.Ry(angle=1.23), QubitSet(0), QubitSet([2]), None, "ctrl @ ry(1.23) q[2], q[0];"), - ( - Gate.MS(angle_1=0.17, angle_2=3.45), - QubitSet(0), - QubitSet([1, 2]), - None, - f"ctrl(2) @ ms(0.17, 3.45, {np.pi / 2}) q[1], q[2], q[0];", - ), - ( - Gate.CCNot(), - QubitSet([0, 1, 2]), - QubitSet([3, 4]), - None, - "ctrl(2) @ ccnot q[3], q[4], q[0], q[1], q[2];", - ), - ( - Gate.Z(), - QubitSet([0]), - QubitSet([1, 2, 3, 4, 5, 6, 7]), - [1, 1, 1, 0, 0, 1, 0], - "ctrl(3) @ negctrl(2) @ ctrl @ negctrl @ " - "z q[1], q[2], q[3], q[4], q[5], q[6], q[7], q[0];", - ), - ( - Gate.Z(), - QubitSet([0]), - QubitSet([1, 2, 3, 4, 5, 6, 7]), - "1110010", - "ctrl(3) @ negctrl(2) @ ctrl @ negctrl @ " - "z q[1], q[2], q[3], q[4], q[5], q[6], q[7], q[0];", - ), - ( - Gate.Z(), - QubitSet([0]), - QubitSet([1, 2, 3, 4, 5, 6, 7]), - 114, - "ctrl(3) @ negctrl(2) @ ctrl @ negctrl @ " - "z q[1], q[2], q[3], q[4], q[5], q[6], q[7], q[0];", - ), - ( - Gate.Z(), - QubitSet([0]), - QubitSet([1, 2, 3]), - [1, 0], - "negctrl @ ctrl @ negctrl @ z q[1], q[2], q[3], q[0];", - ), - ( - Gate.Z(), - QubitSet([0]), - QubitSet([1, 2, 3]), - "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): - serialization_properties = OpenQASMSerializationProperties( - qubit_reference_type=QubitReferenceType.VIRTUAL - ) - assert ( - gate.to_ir( - target, - control=control, - control_state=control_state, - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - ) - == expected_ir - ) - - -@pytest.mark.parametrize( - "control, control_state, error_string", - ( - ( - [0, 1], - [1, 0, 1], - "State value represents a binary sequence of length greater " - "than the specified number of qubits.", - ), - ( - [0, 1], - "101", - "State value represents a binary sequence of length greater " - "than the specified number of qubits.", - ), - ( - [0, 1], - 5, - "State value represents a binary sequence of length greater " - "than the specified number of qubits.", - ), - ), -) -def test_gate_control_invalid_state(control, control_state, error_string): - with pytest.raises(ValueError, match=error_string): - Gate.X().to_ir( - target=[0], - control=control, - control_state=control_state, - ir_type=IRType.OPENQASM, - serialization_properties=OpenQASMSerializationProperties( - qubit_reference_type=QubitReferenceType.VIRTUAL - ), - ) - - -@pytest.mark.parametrize( - "gate, target, power, expected_ir", - ( - (Gate.H(), QubitSet(0), 2, "pow(2) @ h q[0];"), - (Gate.H(), QubitSet(0), 2.0, "pow(2.0) @ h q[0];"), - (Gate.H(), QubitSet(0), 2.5, "pow(2.5) @ h q[0];"), - (Gate.H(), QubitSet(0), 0, "pow(0) @ h q[0];"), - (Gate.H(), QubitSet(0), -2, "inv @ pow(2) @ h q[0];"), - (Gate.H(), QubitSet(0), -2.0, "inv @ pow(2.0) @ h q[0];"), - (Gate.H(), QubitSet(0), -2.5, "inv @ pow(2.5) @ h q[0];"), - ), -) -def test_gate_power(gate, target, power, expected_ir): - serialization_properties = OpenQASMSerializationProperties( - qubit_reference_type=QubitReferenceType.VIRTUAL - ) - assert ( - gate.to_ir( - target, - power=power, - ir_type=IRType.OPENQASM, - serialization_properties=serialization_properties, - ) - == expected_ir - ) - - -def test_hash(): - assert hash(Gate.Unitary(Gate.CCNot().to_matrix())) == hash( - Gate.Unitary(Gate.CCNot().to_matrix()) - ) diff --git a/test/unit_tests/braket/circuits/test_instruction.py b/test/unit_tests/braket/circuits/test_instruction.py deleted file mode 100644 index 1d04627e..00000000 --- a/test/unit_tests/braket/circuits/test_instruction.py +++ /dev/null @@ -1,204 +0,0 @@ -# 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 Gate, Instruction, Noise, Qubit, QubitSet, compiler_directives -from braket.circuits.serialization import ( - IRType, - OpenQASMSerializationProperties, - QubitReferenceType, -) - - -@pytest.fixture -def instr(): - return Instruction(Gate.H(), 0) - - -@pytest.fixture -def cnot(): - return Instruction(Gate.CNot(), [0, 1]) - - -@pytest.fixture -def ccry(): - return Instruction(Gate.Ry(1.23), 0, control=[1, 2]) - - -def test_empty_operator(): - with pytest.raises(ValueError): - Instruction(None, target=0) - - -def test_non_matching_qubit_set_and_qubit_count(): - with pytest.raises(ValueError): - Instruction(Gate.CNot(), target=[0, 0]) - - -def test_init_with_qubits(): - target = QubitSet([0, 1]) - instr = Instruction(Gate.CNot(), target) - assert instr.target == target - - -def test_init_with_qubit(): - target = Qubit(0) - instr = Instruction(Gate.H(), target) - assert instr.target == QubitSet(0) - - -def test_init_with_int(): - target = 0 - instr = Instruction(Gate.H(), target) - assert instr.target == QubitSet(0) - - -def test_init_with_sequence(): - target = [0, Qubit(1)] - instr = Instruction(Gate.CNot(), target) - assert instr.target == QubitSet([0, 1]) - - -def test_getters(): - target = [0, 1] - operator = Gate.CNot() - instr = Instruction(operator, target) - - assert instr.operator == operator - assert instr.target == QubitSet([0, 1]) - - -def test_operator_setter(instr): - with pytest.raises(AttributeError): - instr.operator = Gate.H() - - -def test_target_setter(instr): - with pytest.raises(AttributeError): - instr.target = QubitSet(0) - - -def test_adjoint_gate(): - instr = Instruction(Gate.S(), 0) - adj = instr.adjoint() - assert adj == [Instruction(Gate.Si(), 0)] - - -def test_adjoint_compiler_directive(): - instr = Instruction(compiler_directives.StartVerbatimBox()).adjoint() - assert instr == [Instruction(compiler_directives.EndVerbatimBox())] - - -def test_adjoint_unsupported(): - with pytest.raises(NotImplementedError): - Instruction(Noise.BitFlip(0.1), 0).adjoint() - - -def test_str(instr): - expected = ( - 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 - - -def test_equality(): - instr_1 = Instruction(Gate.H(), 0) - instr_2 = Instruction(Gate.H(), 0) - other_instr = Instruction(Gate.CNot(), [0, 1]) - non_instr = "non instruction" - - assert instr_1 == instr_2 - assert instr_1 is not instr_2 - assert instr_1 != other_instr - assert instr_1 != non_instr - - -def test_to_ir(): - expected_target = QubitSet([0, 1]) - expected_ir = "foo bar value" - expected_ir_type = IRType.OPENQASM - expected_serialization_properties = OpenQASMSerializationProperties( - qubit_reference_type=QubitReferenceType.PHYSICAL - ) - - class FooGate(Gate): - def __init__(self): - super().__init__(qubit_count=2, ascii_symbols=["foo", "bar"]) - - def to_ir(self, target, ir_type, serialization_properties): - assert target == expected_target - assert ir_type == expected_ir_type - assert serialization_properties == expected_serialization_properties - return expected_ir - - instr = Instruction(FooGate(), expected_target) - assert instr.to_ir(expected_ir_type, expected_serialization_properties) == expected_ir - - -def test_copy_creates_new_object(instr): - copy = instr.copy() - assert copy == instr - assert copy is not instr - - -def test_copy_with_mapping(cnot): - target_mapping = {0: 10, 1: 11} - expected = Instruction(Gate.CNot(), [10, 11]) - assert cnot.copy(target_mapping=target_mapping) == expected - - -def test_copy_with_control_mapping(ccry): - control_mapping = {1: 10, 2: 11} - expected = Instruction(Gate.Ry(1.23), target=1, control=[10, 11]) - assert ccry.copy(target=1, control_mapping=control_mapping) == expected - - -def test_copy_with_target(cnot): - target = [10, 11] - expected = Instruction(Gate.CNot(), target) - assert cnot.copy(target=target) == expected - - -def test_copy_with_control(ccry): - control = [10, 11] - expected = Instruction(Gate.Ry(1.23), 3, control=control) - assert ccry.copy(target=3, control=control) == expected - - -def test_copy_with_target_and_mapping(instr): - cant_do_both = "Only 'target_mapping' or 'target' can be supplied, but not both." - with pytest.raises(TypeError, match=cant_do_both): - instr.copy(target=[10], target_mapping={0: 10}) - - -def test_copy_with_control_target_and_mapping(instr): - cant_do_both = "Only 'control_mapping' or 'control' can be supplied, but not both." - with pytest.raises(TypeError, match=cant_do_both): - instr.copy(target=[10], control=[10], control_mapping={0: 10}) - - -def test_pow(instr): - assert instr.power == 1 - cubed = instr**3 - assert instr.power == 1 - assert cubed.power == 3 - then_squared = cubed**2 - assert cubed.power == 3 - assert then_squared.power == 6 - modded = then_squared.__pow__(6, 5) - assert modded.power == 1 diff --git a/test/unit_tests/braket/circuits/test_measure.py b/test/unit_tests/braket/circuits/test_measure.py deleted file mode 100644 index 8911da5e..00000000 --- a/test/unit_tests/braket/circuits/test_measure.py +++ /dev/null @@ -1,100 +0,0 @@ -# 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_moments.py b/test/unit_tests/braket/circuits/test_moments.py deleted file mode 100644 index ed45b0aa..00000000 --- a/test/unit_tests/braket/circuits/test_moments.py +++ /dev/null @@ -1,187 +0,0 @@ -# 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 import OrderedDict - -import pytest - -from braket.circuits import Gate, Instruction, Moments, MomentsKey, QubitSet - - -def cnot(q1, q2): - return Instruction(Gate.CNot(), [q1, q2]) - - -def h(q): - return Instruction(Gate.H(), q) - - -@pytest.fixture -def moments(): - return Moments([h(0), h(0)]) - - -def test_add(): - moments = Moments() - moments.add([h(0)]) - moments.add([h(0)]) - - expected = OrderedDict() - expected[MomentsKey(0, QubitSet(0), "gate", 0)] = h(0) - expected[MomentsKey(1, QubitSet(0), "gate", 0)] = h(0) - assert OrderedDict(moments) == expected - - -def test_add_single_insturction(): - moments = Moments() - moments.add(h(0)) - - expected = OrderedDict() - expected[MomentsKey(0, QubitSet(0), "gate", 0)] = h(0) - assert OrderedDict(moments) == expected - - -def test_default_constructor(): - moments = Moments() - assert OrderedDict(moments) == OrderedDict() - assert moments.depth == 0 - assert moments.qubits == QubitSet() - - -def test_constructor_with_instructions(): - moments = Moments([h(0), h(1)]) - expected = Moments() - expected.add([h(0)]) - expected.add([h(1)]) - assert moments == expected - - -def test_depth(): - moments = Moments([h(0), h(1)]) - assert moments.depth == 1 - - moments.add([cnot(0, 2), h(3)]) - assert moments.depth == 2 - - -def test_depth_setter(moments): - with pytest.raises(AttributeError): - moments.depth = 5 - - -def test_overlaping_qubits(): - moments = Moments([h(0), h(0)]) - assert moments.depth == 2 - - moments.add([cnot(0, 3), h(1)]) - assert moments.depth == 3 - - moments.add([cnot(2, 4)]) - assert moments.depth == 3 - - -def test_qubits(): - moments = Moments([h(0), h(10), h(5)]) - expected = QubitSet([0, 10, 5]) - assert moments.qubits == expected - assert moments.qubit_count == len(expected) - - -def test_qubits_setter(moments): - with pytest.raises(AttributeError): - moments.qubits = QubitSet(1) - - -def test_qubit_count_setter(moments): - with pytest.raises(AttributeError): - moments.qubit_count = 1 - - -def test_time_slices(): - moments = Moments([h(0), h(1), cnot(0, 1)]) - expected = {0: [h(0), h(1)], 1: [cnot(0, 1)]} - assert moments.time_slices() == expected - - -def test_keys(): - moments = Moments([h(0), h(0), h(1)]) - expected = [ - MomentsKey(0, QubitSet(0), "gate", 0), - MomentsKey(1, QubitSet(0), "gate", 0), - MomentsKey(0, QubitSet(1), "gate", 0), - ] - assert list(moments.keys()) == expected - - -def test_items(): - moments = Moments([h(0), h(0), h(1)]) - expected = [ - (MomentsKey(0, QubitSet(0), "gate", 0), h(0)), - (MomentsKey(1, QubitSet(0), "gate", 0), h(0)), - (MomentsKey(0, QubitSet(1), "gate", 0), h(1)), - ] - assert list(moments.items()) == expected - - -def test_values(): - moments = Moments([h(0), h(0), h(1)]) - expected = [h(0), h(0), h(1)] - assert list(moments.values()) == expected - - -def test_get(): - moments = Moments([h(0)]) - unknown_key = MomentsKey(100, QubitSet(100), "gate", 0) - assert moments.get(MomentsKey(0, QubitSet(0), "gate", 0)) == h(0) - assert moments.get(unknown_key) is None - assert moments.get(unknown_key, h(0)) == h(0) - - -def test_getitem(): - moments = Moments([h(0)]) - assert moments[MomentsKey(0, QubitSet(0), "gate", 0)] == h(0) - - -def test_iter(moments): - assert list(moments) == list(moments.keys()) - - -def test_len(): - moments = Moments([h(0), h(0)]) - assert len(moments) == 2 - - -def test_contains(): - moments = Moments([h(0), h(0)]) - assert MomentsKey(0, QubitSet(0), "gate", 0) in moments - assert MomentsKey(0, QubitSet(100), "gate", 0) not in moments - - -def test_equals(): - moments_1 = Moments([h(0)]) - moments_2 = Moments([h(0)]) - other_moments = Moments([h(1)]) - non_moments = "non moments" - - assert moments_1 == moments_2 - assert moments_1 is not moments_2 - assert moments_1 != other_moments - assert moments_1 != non_moments - - -def test_repr(moments): - assert repr(moments) == repr(OrderedDict(moments)) - - -def test_str(moments): - assert str(moments) == str(OrderedDict(moments)) diff --git a/test/unit_tests/braket/circuits/test_noise.py b/test/unit_tests/braket/circuits/test_noise.py deleted file mode 100644 index 81b217d3..00000000 --- a/test/unit_tests/braket/circuits/test_noise.py +++ /dev/null @@ -1,490 +0,0 @@ -# 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 pytest - -from braket.circuits import Operator -from braket.circuits.free_parameter import FreeParameter -from braket.circuits.noise import ( - DampingNoise, - GeneralizedAmplitudeDampingNoise, - MultiQubitPauliNoise, - Noise, - PauliNoise, - SingleProbabilisticNoise, - SingleProbabilisticNoise_34, - SingleProbabilisticNoise_1516, -) -from braket.circuits.serialization import IRType - -invalid_data_qubit_count = [(0, ["foo"])] -invalid_data_ascii_symbols = [(1, None)] -invalid_data_ascii_symbols_length = [(2, ["foo", "boo", "braket"])] -invalid_data_prob = [float("nan"), float("inf"), float("-inf"), 0.95, -2.6] -invalid_data_prob_2 = ["a", 1.0 + 1j] -invalid_data_prob_damping = [float("nan"), float("inf"), float("-inf"), 1.5, -2.6] -invalid_data_prob_damping_2 = ["a", 1.0 + 1j] - - -@pytest.fixture -def base_noise(): - return Noise(qubit_count=1, ascii_symbols=["foo"]) - - -@pytest.fixture -def single_probability_noise(): - return SingleProbabilisticNoise(probability=0.1, qubit_count=1, ascii_symbols=["foo"]) - - -@pytest.fixture -def single_probability_noise_34(): - return SingleProbabilisticNoise_34(probability=0.1, qubit_count=1, ascii_symbols=["foo"]) - - -@pytest.fixture -def single_probability_noise_1516(): - return SingleProbabilisticNoise_1516(probability=0.1, qubit_count=1, ascii_symbols=["foo"]) - - -@pytest.fixture -def pauli_noise(): - return PauliNoise(probX=0.1, probY=0.2, probZ=0.3, qubit_count=1, ascii_symbols=["foo"]) - - -@pytest.fixture -def damping_noise(): - return DampingNoise(gamma=0.2, qubit_count=1, ascii_symbols=["foo"]) - - -@pytest.fixture -def generalized_amplitude_damping_noise(): - return GeneralizedAmplitudeDampingNoise( - gamma=0.2, probability=0.9, qubit_count=1, ascii_symbols=["foo"] - ) - - -@pytest.mark.parametrize("qubit_count, ascii_symbols", invalid_data_qubit_count) -def test_invalid_data_qubit_count(qubit_count, ascii_symbols): - with pytest.raises(ValueError): - Noise(qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("qubit_count, ascii_symbols", invalid_data_ascii_symbols) -def test_invalid_data_ascii_symbols(qubit_count, ascii_symbols): - with pytest.raises(ValueError): - Noise(qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("qubit_count, ascii_symbols", invalid_data_ascii_symbols_length) -def test_invalid_data_ascii_symbols_length(qubit_count, ascii_symbols): - with pytest.raises(ValueError): - Noise(qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("probability", invalid_data_prob) -def test_invalid_data_single_prob(probability): - qubit_count = 1 - ascii_symbols = ["foo"] - with pytest.raises(ValueError): - SingleProbabilisticNoise(probability, qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("probability", invalid_data_prob) -def test_invalid_data_single_prob_34(probability): - qubit_count = 1 - ascii_symbols = ["foo"] - with pytest.raises(ValueError): - SingleProbabilisticNoise_34(probability, qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("probability", invalid_data_prob) -def test_invalid_data_single_prob_1516(probability): - qubit_count = 1 - ascii_symbols = ["foo"] - with pytest.raises(ValueError): - SingleProbabilisticNoise_1516(probability, qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("probability", invalid_data_prob_2) -def test_invalid_data_type_single_prob(probability): - qubit_count = 1 - ascii_symbols = ["foo"] - with pytest.raises(TypeError): - SingleProbabilisticNoise(probability, qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("probability", invalid_data_prob_2) -def test_invalid_data_type_single_prob_34(probability): - qubit_count = 1 - ascii_symbols = ["foo"] - with pytest.raises(TypeError): - SingleProbabilisticNoise_34(probability, qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("probability", invalid_data_prob_2) -def test_invalid_data_type_single_prob_1516(probability): - qubit_count = 1 - ascii_symbols = ["foo"] - with pytest.raises(TypeError): - SingleProbabilisticNoise_1516(probability, qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("probX", invalid_data_prob) -def test_invalid_data_pauli_probX(probX): - qubit_count = 1 - ascii_symbols = ["foo"] - probY = 0.1 - probZ = 0.1 - with pytest.raises(ValueError): - PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("probY", invalid_data_prob) -def test_invalid_data_pauli_probY(probY): - qubit_count = 1 - ascii_symbols = ["foo"] - probX = 0.1 - probZ = 0.1 - with pytest.raises(ValueError): - PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("probZ", invalid_data_prob) -def test_invalid_data_pauli_probZ(probZ): - qubit_count = 1 - ascii_symbols = ["foo"] - probX = 0.1 - probY = 0.1 - with pytest.raises(ValueError): - PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("probX", invalid_data_prob_2) -def test_invalid_data_type_pauli_probX(probX): - qubit_count = 1 - ascii_symbols = ["foo"] - probY = 0.1 - probZ = 0.1 - with pytest.raises(TypeError): - PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("probY", invalid_data_prob_2) -def test_invalid_data_type_pauli_probY(probY): - qubit_count = 1 - ascii_symbols = ["foo"] - probX = 0.1 - probZ = 0.1 - with pytest.raises(TypeError): - PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("probZ", invalid_data_prob_2) -def test_invalid_data_type_pauli_probZ(probZ): - qubit_count = 1 - ascii_symbols = ["foo"] - probX = 0.1 - probY = 0.1 - with pytest.raises(TypeError): - PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) - - -def test_invalid_data_pauli_sum(): - qubit_count = 1 - ascii_symbols = ["foo"] - probX = 0.1 - probY = 0.1 - probZ = 0.9 - with pytest.raises(ValueError): - PauliNoise(probX, probY, probZ, qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("gamma", invalid_data_prob_damping) -def test_invalid_data_damping_prob(gamma): - qubit_count = 1 - ascii_symbols = ["foo"] - with pytest.raises(ValueError): - DampingNoise(gamma, qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("probability", invalid_data_prob_damping) -def test_invalid_data_generalized_amplitude_damping_prob(probability): - qubit_count = 1 - ascii_symbols = ["foo"] - gamma = 0.1 - with pytest.raises(ValueError): - GeneralizedAmplitudeDampingNoise(gamma, probability, qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("gamma", invalid_data_prob_damping_2) -def test_invalid_data_type_damping_prob(gamma): - qubit_count = 1 - ascii_symbols = ["foo"] - with pytest.raises(TypeError): - DampingNoise(gamma, qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("probability", invalid_data_prob_damping_2) -def test_invalid_data_type_generalized_amplitude_damping_prob(probability): - qubit_count = 1 - ascii_symbols = ["foo"] - gamma = 0.1 - with pytest.raises(TypeError): - GeneralizedAmplitudeDampingNoise(gamma, probability, qubit_count, ascii_symbols) - - -@pytest.mark.parametrize("gamma", invalid_data_prob_damping) -def test_invalid_data_generalized_amplitude_damping_gamma(gamma): - qubit_count = 1 - ascii_symbols = ["foo"] - probability = 0.1 - with pytest.raises(ValueError): - GeneralizedAmplitudeDampingNoise(gamma, probability, qubit_count, ascii_symbols) - - -def test_ascii_symbols(base_noise): - assert base_noise.ascii_symbols == ("foo",) - - -def test_is_operator(base_noise): - assert isinstance(base_noise, Operator) - - -@pytest.mark.xfail(raises=NotImplementedError) -def test_to_ir_not_implemented_by_default(base_noise): - base_noise.to_ir(None) - - -@pytest.mark.parametrize( - "ir_type, serialization_properties, expected_exception, expected_message", - [ - (IRType.JAQCD, None, NotImplementedError, "to_jaqcd has not been implemented yet."), - (IRType.OPENQASM, None, NotImplementedError, "to_openqasm has not been implemented yet."), - ("invalid-ir-type", None, ValueError, "Supplied ir_type invalid-ir-type is not supported."), - ( - IRType.OPENQASM, - "invalid-serialization-properties", - ValueError, - "serialization_properties must be of type OpenQASMSerializationProperties for " - "IRType.OPENQASM.", - ), - ], -) -def test_noise_to_ir( - ir_type, serialization_properties, expected_exception, expected_message, base_noise -): - with pytest.raises(expected_exception) as exc: - base_noise.to_ir(0, ir_type, serialization_properties=serialization_properties) - assert exc.value.args[0] == expected_message - - -def test_to_matrix_not_implemented_by_default(base_noise): - with pytest.raises(NotImplementedError): - base_noise.to_matrix(None) - - -def test_invalid_deserializatoin(): - with pytest.raises(NotImplementedError): - Noise.from_dict({}) - - -@pytest.mark.parametrize( - "noise, expected_string, expected_repr", - [ - (Noise(1, ["foo"]), "Noise('qubit_count': 1)", "Noise('qubit_count': 1)"), - ( - SingleProbabilisticNoise(0.1, 1, ["foo"]), - "SingleProbabilisticNoise(0.1)", - "SingleProbabilisticNoise('probability': 0.1, 'qubit_count': 1)", - ), - ( - DampingNoise(0.1, 1, ["foo"]), - "DampingNoise(0.1)", - "DampingNoise('gamma': 0.1, 'qubit_count': 1)", - ), - ( - GeneralizedAmplitudeDampingNoise(0.1, 0.2, 1, ["foo"]), - "GeneralizedAmplitudeDampingNoise(0.1, 0.2)", - "GeneralizedAmplitudeDampingNoise('gamma': 0.1, 'probability': 0.2, 'qubit_count': 1)", - ), - ( - PauliNoise(0.1, 0.2, 0.3, 1, ["foo"]), - "PauliNoise(0.1, 0.2, 0.3)", - "PauliNoise('probX': 0.1, 'probY': 0.2, 'probZ': 0.3, 'qubit_count': 1)", - ), - ( - MultiQubitPauliNoise({"X": 0.2}, 1, ["foo"]), - "MultiQubitPauliNoise({'X': 0.2})", - "MultiQubitPauliNoise('probabilities' : {'X': 0.2}, 'qubit_count': 1)", - ), - ], -) -def test_noise_str_repr(noise, expected_string, expected_repr): - assert str(noise) == expected_string - assert repr(noise) == expected_repr - - -@pytest.mark.parametrize( - "noise", - [ - SingleProbabilisticNoise(0.1, 1, ["foo"]), - DampingNoise(0.1, 1, ["foo"]), - GeneralizedAmplitudeDampingNoise(0.1, 0.2, 1, ["foo"]), - PauliNoise(0.1, 0.2, 0.3, 1, ["foo"]), - MultiQubitPauliNoise({"X": 0.2}, 1, ["foo"]), - ], -) -def test_noise_serialization(noise): - representation = noise.to_dict() - assert isinstance(representation, dict) - serialized = json.dumps(representation) - assert isinstance(serialized, str) - - -@pytest.mark.parametrize( - "noise, equal_noise, unequal_noise, param_noise", - [ - ( - SingleProbabilisticNoise(0.1, 1, ["foo"]), - SingleProbabilisticNoise(0.1, 1, ["foo"]), - SingleProbabilisticNoise(0.2, 1, ["foo"]), - SingleProbabilisticNoise(FreeParameter("alpha"), 1, ["foo"]), - ), - ( - DampingNoise(0.1, 1, ["foo"]), - DampingNoise(0.1, 1, ["foo"]), - DampingNoise(0.2, 1, ["foo"]), - DampingNoise(FreeParameter("alpha"), 1, ["foo"]), - ), - ( - GeneralizedAmplitudeDampingNoise(0.1, 0.2, 1, ["foo"]), - GeneralizedAmplitudeDampingNoise(0.1, 0.2, 1, ["foo"]), - GeneralizedAmplitudeDampingNoise(0.2, 0.2, 1, ["foo"]), - GeneralizedAmplitudeDampingNoise(FreeParameter("alpha"), 0.2, 1, ["foo"]), - ), - ( - PauliNoise(0.1, 0.2, 0.3, 1, ["foo"]), - PauliNoise(0.1, 0.2, 0.3, 1, ["foo"]), - PauliNoise(0.2, 0.2, 0.3, 1, ["foo"]), - PauliNoise(FreeParameter("x"), FreeParameter("y"), FreeParameter("z"), 1, ["foo"]), - ), - ( - MultiQubitPauliNoise({"X": 0.2}, 1, ["foo"]), - MultiQubitPauliNoise({"X": 0.2}, 1, ["foo"]), - MultiQubitPauliNoise({"X": 0.3}, 1, ["foo"]), - MultiQubitPauliNoise({"X": FreeParameter("alpha")}, 1, ["foo"]), - ), - ], -) -def test_noise_equality(noise, equal_noise, unequal_noise, param_noise): - assert noise == noise - assert noise is noise - assert noise == equal_noise - assert noise is not equal_noise - assert noise != unequal_noise - assert noise != param_noise - assert noise != Noise(qubit_count=1, ascii_symbols=["foo"]) - - -def test_noise_base_not_equal_to_different_type(): - assert Noise(qubit_count=1, ascii_symbols=["foo"]) != "foo" - - -def test_register_noise(): - class _FooNoise(Noise): - def __init__(self): - super().__init__(qubit_count=1, ascii_symbols=["foo"]) - - Noise.register_noise(_FooNoise) - assert Noise._FooNoise().name == _FooNoise().name - - -@pytest.mark.parametrize( - "noise_class, params", - [ - (SingleProbabilisticNoise, {"probability": 0.6}), - (SingleProbabilisticNoise, {"probability": -0.1}), - (SingleProbabilisticNoise_34, {"probability": 0.76}), - (SingleProbabilisticNoise_34, {"probability": -0.1}), - (SingleProbabilisticNoise_1516, {"probability": 0.93755}), - (SingleProbabilisticNoise_1516, {"probability": -0.1}), - (MultiQubitPauliNoise, {"probabilities": {"X": 0.4, "Y": 0.7}}), - (MultiQubitPauliNoise, {"probabilities": {"X": 0.4, "Y": -0.7}}), - (PauliNoise, {"probX": 0.5, "probY": 0.5, "probZ": 0.5}), - (PauliNoise, {"probX": -0.1, "probY": 0, "probZ": 0}), - (DampingNoise, {"gamma": -0.1}), - (DampingNoise, {"gamma": 1.1}), - (GeneralizedAmplitudeDampingNoise, {"gamma": 0.1, "probability": -0.2}), - (GeneralizedAmplitudeDampingNoise, {"gamma": 0.1, "probability": 1.2}), - ], -) -def test_invalid_values(noise_class, params): - with pytest.raises(ValueError): - noise_class(**params, qubit_count=1, ascii_symbols=["foo"]) - - -@pytest.mark.parametrize( - "probs, qubit_count, ascii_symbols", - [ - ({"X": 0.1}, 1, ["PC"]), - ({"XXY": 0.1}, 3, ["PC3", "PC3", "PC3"]), - ({"YX": 0.1, "IZ": 0.2}, 2, ["PC2", "PC2"]), - ], -) -def test_multi_qubit_noise(probs, qubit_count, ascii_symbols): - noise = MultiQubitPauliNoise(probs, qubit_count, ascii_symbols) - assert noise.probabilities == probs - assert noise.qubit_count == qubit_count - assert noise.ascii_symbols == tuple(ascii_symbols) - assert noise.parameters == [probs[key] for key in sorted(probs.keys())] - - -@pytest.mark.xfail(raises=ValueError) -class TestInvalidMultiQubitNoise: - qubit_count = 1 - ascii_symbols = ["PC2"] - - def test_non_empty(self): - MultiQubitPauliNoise({}, self.qubit_count, self.ascii_symbols) - - def test_non_identity(self): - MultiQubitPauliNoise({"I": 0.1}, self.qubit_count, self.ascii_symbols) - - def test_non_equal_length_paulis(self): - MultiQubitPauliNoise({"X": 0.1, "XY": 0.1}, 1, self.ascii_symbols) - MultiQubitPauliNoise({"X": 0.1, "Y": 0.1}, 2, ["PC2", "PC2"]) - - def test_prob_over_one(self): - MultiQubitPauliNoise({"X": 0.9, "Y": 0.9}, 1, self.ascii_symbols) - MultiQubitPauliNoise({"XX": 0.9, "YY": 0.9}, 1, self.ascii_symbols) - - def test_prob_under_one(self): - MultiQubitPauliNoise({"X": -0.6, "Y": -0.9}, 1, self.ascii_symbols) - MultiQubitPauliNoise({"XX": -0.9, "YY": -0.9}, 2, ["PC2", "PC2"]) - - def test_non_pauli_string(self): - MultiQubitPauliNoise({"T": 0.1}, 1, self.ascii_symbols) - - def test_individual_probs(self): - MultiQubitPauliNoise({"X": -0.1}, 1, self.ascii_symbols) - MultiQubitPauliNoise({"X": 1.1}, 1, self.ascii_symbols) - - @pytest.mark.xfail(raises=TypeError) - def test_keys_strings(self): - MultiQubitPauliNoise({1: 1.1}, 1, self.ascii_symbols) - - @pytest.mark.xfail(raises=TypeError) - def test_values_floats(self): - MultiQubitPauliNoise({"X": "str"}, 1, self.ascii_symbols) diff --git a/test/unit_tests/braket/circuits/test_noise_helpers.py b/test/unit_tests/braket/circuits/test_noise_helpers.py deleted file mode 100644 index f2c956ea..00000000 --- a/test/unit_tests/braket/circuits/test_noise_helpers.py +++ /dev/null @@ -1,753 +0,0 @@ -# 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.circuit import Circuit -from braket.circuits.gate import Gate -from braket.circuits.instruction import Instruction -from braket.circuits.moments import Moments -from braket.circuits.noise import Noise -from braket.circuits.noise_helpers import apply_noise_to_gates, apply_noise_to_moments -from braket.registers.qubit_set import QubitSet - -invalid_data_noise_type = [Gate.X(), None, 1.5] -invalid_data_target_gates_type = [[-1, "foo"], [1.5, None, -1], "X", [Gate.X, "CNot"]] -invalid_data_target_qubits_value = [-1] -invalid_data_target_qubits_type = [1.5, "foo", ["foo", 1]] -invalid_data_target_unitary_value = [np.array([[0, 0], [1, 0]])] -invalid_data_target_unitary_type = [[[0, 1], [1, 0]]] - - -@pytest.fixture -def circuit_2qubit(): - return Circuit().x(0).y(1).x(0).x(1).cnot(0, 1) - - -@pytest.fixture -def circuit_2qubit_parametrized(): - return Circuit().x(0).y(1).x(0).rx(1, np.pi).xy(0, 1, np.pi / 2) - - -@pytest.fixture -def circuit_2qubit_with_unitary(): - return Circuit().x(0).y(1).x(0).x(1).cnot(0, 1).unitary([0], matrix=np.array([[0, 1], [1, 0]])) - - -@pytest.fixture -def circuit_2qubit_not_dense(): - # there are some qubits and some time that are not occupied by a gate - return Circuit().x(0).y(1).x(0).cnot(0, 1) - - -@pytest.fixture -def circuit_3qubit(): - return Circuit().x(0).y(1).cnot(0, 1).z(2).cz(2, 1).cnot(0, 2).cz(1, 2) - - -@pytest.fixture -def noise_1qubit(): - return Noise.BitFlip(probability=0.1) - - -@pytest.fixture -def noise_1qubit_2(): - return Noise.Depolarizing(probability=0.1) - - -@pytest.fixture -def noise_2qubit(): - E0 = np.sqrt(0.8) * np.eye(4) - E1 = np.sqrt(0.2) * np.kron(np.array([[0, 1], [1, 0]]), np.array([[0, 1], [1, 0]])) - return Noise.Kraus(matrices=[E0, E1]) - - -def test_apply_gate_noise_to_empty_circuit(noise_1qubit): - with pytest.raises(IndexError): - Circuit().apply_gate_noise(noise_1qubit) - - -def test_apply_initialization_noise_to_empty_circuit(noise_1qubit): - with pytest.raises(IndexError): - Circuit().apply_initialization_noise(noise_1qubit) - - -def test_apply_readout_noise_to_empty_circuit(noise_1qubit): - with pytest.raises(IndexError): - Circuit().apply_readout_noise(noise_1qubit) - - -def test_apply_gate_noise_with_target_gates_and_unitary(circuit_2qubit, noise_1qubit): - with pytest.raises(ValueError): - circuit_2qubit.apply_gate_noise( - noise_1qubit, target_gates=Gate.X, target_unitary=np.array([[0, 1], [1, 0]]) - ) - - -def test_apply_gate_noise_to_outside_qubit_range(circuit_2qubit, noise_1qubit): - with pytest.raises(IndexError): - circuit_2qubit.apply_gate_noise(noise_1qubit, target_qubits=[0, 1, 2]) - - -@pytest.mark.parametrize("noise", invalid_data_noise_type) -def test_apply_gate_noise_invalid_noise_type(circuit_2qubit, noise): - with pytest.raises(TypeError): - circuit_2qubit.apply_gate_noise(noise) - - -@pytest.mark.parametrize("noise", invalid_data_noise_type) -def test_apply_initialization_noise_invalid_noise_type(circuit_2qubit, noise): - with pytest.raises(TypeError): - circuit_2qubit.apply_initialization_noise(noise) - - -@pytest.mark.parametrize("noise", invalid_data_noise_type) -def test_apply_readout_noise_invalid_noise_type(circuit_2qubit, noise): - with pytest.raises(TypeError): - circuit_2qubit.apply_readout_noise(noise) - - -@pytest.mark.parametrize("target_gates", invalid_data_target_gates_type) -def test_apply_gate_noise_invalid_target_gates_type(circuit_2qubit, noise_1qubit, target_gates): - with pytest.raises(TypeError): - circuit_2qubit.apply_gate_noise(noise_1qubit, target_gates=target_gates) - - -@pytest.mark.parametrize("target_unitary", invalid_data_target_unitary_type) -def test_apply_gate_noise_invalid_target_unitary_type(circuit_2qubit, noise_1qubit, target_unitary): - with pytest.raises(TypeError): - circuit_2qubit.apply_gate_noise(noise_1qubit, target_unitary=target_unitary) - - -@pytest.mark.parametrize("target_unitary", invalid_data_target_unitary_value) -def test_apply_gate_noise_invalid_target_unitary_value( - circuit_2qubit, noise_1qubit, target_unitary -): - with pytest.raises(ValueError): - circuit_2qubit.apply_gate_noise(noise_1qubit, target_unitary=target_unitary) - - -@pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_value) -def test_apply_gate_noise_invalid_target_qubits_value(circuit_2qubit, noise_1qubit, target_qubits): - with pytest.raises(ValueError): - circuit_2qubit.apply_gate_noise(noise_1qubit, target_qubits=target_qubits, target_gates=[]) - - -@pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_value) -def test_apply_initialization_noise_invalid_target_qubits_value( - circuit_2qubit, noise_1qubit, target_qubits -): - with pytest.raises(ValueError): - circuit_2qubit.apply_initialization_noise(noise_1qubit, target_qubits=target_qubits) - - -@pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_value) -def test_apply_readout_noise_invalid_target_qubits_value( - circuit_2qubit, noise_1qubit, target_qubits -): - with pytest.raises(ValueError): - circuit_2qubit.apply_readout_noise(noise_1qubit, target_qubits=target_qubits) - - -@pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_type) -def test_apply_gate_noise_invalid_target_qubits_type(circuit_2qubit, noise_1qubit, target_qubits): - with pytest.raises(TypeError): - circuit_2qubit.apply_gate_noise(noise_1qubit, target_qubits=target_qubits) - - -@pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_type) -def test_apply_initialization_noise_invalid_target_qubits_type( - circuit_2qubit, noise_1qubit, target_qubits -): - with pytest.raises(TypeError): - circuit_2qubit.apply_initialization_noise(noise_1qubit, target_qubits=target_qubits) - - -@pytest.mark.parametrize("target_qubits", invalid_data_target_qubits_type) -def test_apply_readout_noise_invalid_target_qubits_type( - circuit_2qubit, noise_1qubit, target_qubits -): - with pytest.raises(TypeError): - circuit_2qubit.apply_readout_noise(noise_1qubit, target_qubits=target_qubits) - - -def test_apply_gate_noise_fixed_qubit_count_not_implemented(noise_2qubit): - circ = Circuit().unitary([0, 1], matrix=np.eye(4)) - with pytest.raises(ValueError): - circ.apply_gate_noise(noise_2qubit, target_gates=Gate.Unitary) - - -def test_apply_gate_noise_mismatch_qubit_count_with_target_gates(noise_2qubit): - circ = Circuit().cswap(0, 1, 2) - with pytest.raises(ValueError): - circ.apply_gate_noise(noise_2qubit, target_gates=Gate.CSwap) - - -def test_apply_initialization_noise_mismatch_qubit_count_with_target_qubits(noise_2qubit): - circ = Circuit().cswap(0, 1, 2) - with pytest.raises(ValueError): - circ.apply_initialization_noise(noise_2qubit, target_qubits=[0, 1, 2]) - - -def test_apply_readout_noise_mismatch_qubit_count_with_target_qubits(noise_2qubit): - circ = Circuit().cswap(0, 1, 2) - with pytest.raises(ValueError): - circ.apply_readout_noise(noise_2qubit, target_qubits=[0, 1, 2]) - - -def test_apply_gate_noise_1QubitNoise_1(circuit_2qubit, noise_1qubit): - circ = circuit_2qubit.apply_gate_noise( - noise_1qubit, - target_gates=[Gate.X, Gate.Z], - target_qubits=[0, 1], - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(noise_1qubit, 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - ) - - assert circ == expected - - -def test_apply_gate_noise_1QubitNoise_parametrized(circuit_2qubit_parametrized, noise_1qubit): - circ = circuit_2qubit_parametrized.apply_gate_noise( - noise_1qubit, - target_gates=[Gate.X, Gate.Rx], - target_qubits=[0, 1], - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(Gate.Rx(np.pi), 1)) - .add_instruction(Instruction(noise_1qubit, 1)) - .add_instruction(Instruction(Gate.XY(np.pi / 2), [0, 1])) - ) - - assert circ == expected - - -def test_apply_gate_noise_2QubitNoise(circuit_2qubit, noise_2qubit): - circ = circuit_2qubit.apply_gate_noise( - noise_2qubit, - target_gates=[Gate.CNot], - target_qubits=[0, 1], - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - .add_instruction(Instruction(noise_2qubit, [0, 1])) - ) - - assert circ == expected - - -def test_apply_gate_noise_2QubitNoise2_parametrized(circuit_2qubit_parametrized, noise_2qubit): - circ = circuit_2qubit_parametrized.apply_gate_noise( - noise_2qubit, - target_gates=[Gate.XY], - target_qubits=[0, 1], - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Rx(np.pi), 1)) - .add_instruction(Instruction(Gate.XY(np.pi / 2), [0, 1])) - .add_instruction(Instruction(noise_2qubit, [0, 1])) - ) - - assert circ == expected - - -def test_apply_gate_noise_1QubitNoise_1_unitary(circuit_2qubit_with_unitary, noise_1qubit): - circ = circuit_2qubit_with_unitary.apply_gate_noise( - noise_1qubit, - target_unitary=np.array([[0, 1], [1, 0]]), - target_qubits=[0, 1], - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - .add_instruction(Instruction(Gate.Unitary(np.array([[0, 1], [1, 0]]), "U"), 0)) - .add_instruction(Instruction(noise_1qubit, 0)) - ) - - assert circ == expected - - -def test_apply_noise_to_gates_1QubitNoise_1(circuit_2qubit, noise_1qubit): - circ = apply_noise_to_gates( - circuit_2qubit, - [noise_1qubit], - target_gates=[Gate.X], - target_qubits=QubitSet([0, 1]), - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(noise_1qubit, 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - ) - - assert circ == expected - - -def test_apply_noise_to_gates_1QubitNoise_2(circuit_2qubit, noise_1qubit): - circ = apply_noise_to_gates( - circuit_2qubit, - [noise_1qubit], - target_gates=[Gate.X], - target_qubits=QubitSet(0), - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - ) - - assert circ == expected - - -def test_apply_noise_to_gates_2QubitNoise_1(circuit_3qubit, noise_2qubit): - circ = apply_noise_to_gates( - circuit_3qubit, - [noise_2qubit], - target_gates=[Gate.CNot], - target_qubits=QubitSet([0, 1, 2]), - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - .add_instruction(Instruction(noise_2qubit, [0, 1])) - .add_instruction(Instruction(Gate.Z(), 2)) - .add_instruction(Instruction(Gate.CZ(), [2, 1])) - .add_instruction(Instruction(Gate.CNot(), [0, 2])) - .add_instruction(Instruction(noise_2qubit, [0, 2])) - .add_instruction(Instruction(Gate.CZ(), [1, 2])) - ) - - assert circ == expected - - -def test_apply_noise_to_gates_2QubitNoise_2( - circuit_3qubit, noise_2qubit, noise_1qubit, noise_1qubit_2 -): - circ = apply_noise_to_gates( - circuit_3qubit, - [noise_1qubit, noise_2qubit, noise_1qubit_2], - target_gates=[Gate.CZ], - target_qubits=QubitSet([1, 2]), - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - .add_instruction(Instruction(Gate.Z(), 2)) - .add_instruction(Instruction(Gate.CZ(), [2, 1])) - .add_instruction(Instruction(noise_1qubit, 1)) - .add_instruction(Instruction(noise_1qubit, 2)) - .add_instruction(Instruction(noise_2qubit, [2, 1])) - .add_instruction(Instruction(noise_1qubit_2, 1)) - .add_instruction(Instruction(noise_1qubit_2, 2)) - .add_instruction(Instruction(Gate.CNot(), [0, 2])) - .add_instruction(Instruction(Gate.CZ(), [1, 2])) - .add_instruction(Instruction(noise_1qubit, 1)) - .add_instruction(Instruction(noise_1qubit, 2)) - .add_instruction(Instruction(noise_2qubit, [1, 2])) - .add_instruction(Instruction(noise_1qubit_2, 1)) - .add_instruction(Instruction(noise_1qubit_2, 2)) - ) - - assert circ == expected - - -def test_apply_noise_to_gates_2QubitNoise_3( - circuit_3qubit, noise_2qubit, noise_1qubit, noise_1qubit_2 -): - circ = apply_noise_to_gates( - circuit_3qubit, - [noise_1qubit, noise_2qubit, noise_1qubit_2], - target_gates=[Gate.Z], - target_qubits=QubitSet([1, 2]), - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - .add_instruction(Instruction(Gate.Z(), 2)) - .add_instruction(Instruction(noise_1qubit, 2)) - .add_instruction(Instruction(noise_1qubit_2, 2)) - .add_instruction(Instruction(Gate.CZ(), [2, 1])) - .add_instruction(Instruction(Gate.CNot(), [0, 2])) - .add_instruction(Instruction(Gate.CZ(), [1, 2])) - ) - - assert circ == expected - - -def test_apply_initialization_noise_1QubitNoise_1(circuit_2qubit, noise_1qubit): - circ = circuit_2qubit.apply_initialization_noise( - [noise_1qubit], - target_qubits=[0, 1], - ) - - expected = ( - Circuit() - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(noise_1qubit, 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - ) - - assert circ == expected - - -def test_apply_noise_to_moments_initialization_1QubitNoise_1(circuit_2qubit, noise_1qubit): - circ = apply_noise_to_moments( - circuit_2qubit, - [noise_1qubit], - target_qubits=QubitSet([0, 1]), - position="initialization", - ) - - expected = ( - Circuit() - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(noise_1qubit, 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - ) - - assert circ == expected - - -def test_apply_noise_to_moments_initialization_2QubitNoise_1(circuit_2qubit, noise_2qubit): - circ = apply_noise_to_moments( - circuit_2qubit, - [noise_2qubit], - target_qubits=QubitSet([0, 1]), - position="initialization", - ) - - expected = ( - Circuit() - .add_instruction(Instruction(noise_2qubit, [0, 1])) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - ) - - assert circ == expected - - -def test_apply_noise_to_moments_initialization_2QubitNoise_2( - circuit_2qubit, noise_2qubit, noise_1qubit, noise_1qubit_2 -): - circ = apply_noise_to_moments( - circuit_2qubit, - [noise_1qubit, noise_2qubit, noise_1qubit_2], - target_qubits=QubitSet([0, 1]), - position="initialization", - ) - - expected = ( - Circuit() - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(noise_1qubit, 1)) - .add_instruction(Instruction(noise_2qubit, [0, 1])) - .add_instruction(Instruction(noise_1qubit_2, 0)) - .add_instruction(Instruction(noise_1qubit_2, 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - ) - - assert circ == expected - - -def test_apply_readout_noise_1QubitNoise_1(circuit_2qubit, noise_1qubit): - circ = circuit_2qubit.apply_readout_noise( - [noise_1qubit], - target_qubits=[0, 1], - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(noise_1qubit, 1)) - ) - - assert circ == expected - - -def test_noise_not_applied_1QubitNoise_1(circuit_2qubit, noise_2qubit): - circ = circuit_2qubit.apply_gate_noise( - [noise_2qubit], - target_qubits=[1], - target_gates=[], - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - ) - - assert circ == expected - - -def test_apply_multiple_noise_1QubitNoise_1(circuit_2qubit, noise_1qubit, noise_1qubit_2): - circ = circuit_2qubit.apply_gate_noise(noise_1qubit).apply_readout_noise( - noise_1qubit_2, - target_qubits=[0, 1], - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(noise_1qubit, 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(noise_1qubit, 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(noise_1qubit, 1)) - .add_instruction(Instruction(noise_1qubit_2, 0)) - .add_instruction(Instruction(noise_1qubit_2, 1)) - ) - - assert circ == expected - - -def test_apply_multiple_noise_1QubitNoise_2(circuit_2qubit, noise_1qubit, noise_1qubit_2): - circ = circuit_2qubit.apply_gate_noise( - noise_1qubit, - target_gates=[Gate.X], - ).apply_gate_noise( - noise_1qubit_2, - target_qubits=[0], - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(noise_1qubit_2, 0)) - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(noise_1qubit_2, 0)) - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(noise_1qubit, 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - .add_instruction(Instruction(noise_1qubit_2, 0)) - ) - - assert circ == expected - - -def test_apply_noise_to_moments_readout_1QubitNoise_1(circuit_2qubit, noise_1qubit): - circ = apply_noise_to_moments( - circuit_2qubit, - [noise_1qubit], - target_qubits=QubitSet([0, 1]), - position="readout", - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(noise_1qubit, 1)) - ) - - assert circ == expected - - -def test_apply_noise_to_moments_readout_1QubitNoise_3(circuit_2qubit, noise_1qubit, noise_1qubit_2): - circ = apply_noise_to_moments( - circuit_2qubit, - noise=[noise_1qubit, noise_1qubit_2], - target_qubits=QubitSet([0, 1]), - position="readout", - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - .add_instruction(Instruction(noise_1qubit, 0)) - .add_instruction(Instruction(noise_1qubit, 1)) - .add_instruction(Instruction(noise_1qubit_2, 0)) - .add_instruction(Instruction(noise_1qubit_2, 1)) - ) - - assert circ == expected - - -def test_apply_noise_to_moments_readout_2QubitNoise_1(circuit_2qubit, noise_2qubit): - circ = apply_noise_to_moments( - circuit_2qubit, - [noise_2qubit], - target_qubits=QubitSet([0, 1]), - position="readout", - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - .add_instruction(Instruction(noise_2qubit, [0, 1])) - ) - - assert circ == expected - - -def test_apply_noise_to_moments_initialization_1QubitNoise_2(circuit_2qubit, noise_1qubit): - circ = apply_noise_to_moments( - circuit_2qubit, - [noise_1qubit], - target_qubits=QubitSet(1), - position="initialization", - ) - - expected = ( - Circuit() - .add_instruction(Instruction(noise_1qubit, 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - ) - - assert circ == expected - - -def test_apply_noise_to_moments_readout_1QubitNoise_2(circuit_2qubit, noise_1qubit): - circ = apply_noise_to_moments( - circuit_2qubit, - [noise_1qubit], - target_qubits=QubitSet(1), - position="readout", - ) - - expected = ( - Circuit() - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.Y(), 1)) - .add_instruction(Instruction(Gate.X(), 0)) - .add_instruction(Instruction(Gate.X(), 1)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) - .add_instruction(Instruction(noise_1qubit, 1)) - ) - - assert circ == expected - - -def test_apply_noise_to_gates_1QubitNoise_not_dense(circuit_2qubit_not_dense, noise_1qubit): - circ = apply_noise_to_gates( - circuit_2qubit_not_dense, - [noise_1qubit], - target_qubits=QubitSet([0, 1]), - target_gates=None, - ) - - expected_moments = Moments() - expected_moments._add(Instruction(Gate.X(), 0), noise_index=1) - expected_moments.add_noise(Instruction(noise_1qubit, 0), "gate_noise", 1) - expected_moments._add(Instruction(Gate.Y(), 1), noise_index=1) - expected_moments.add_noise(Instruction(noise_1qubit, 1), "gate_noise", 1) - expected_moments._add(Instruction(Gate.X(), 0), noise_index=1) - expected_moments.add_noise(Instruction(noise_1qubit, 0), "gate_noise", 1) - expected_moments._add(Instruction(Gate.CNot(), [0, 1]), noise_index=2) - expected_moments.add_noise(Instruction(noise_1qubit, 0), "gate_noise", 1) - expected_moments.add_noise(Instruction(noise_1qubit, 1), "gate_noise", 2) - - assert circ.moments == expected_moments diff --git a/test/unit_tests/braket/circuits/test_noises.py b/test/unit_tests/braket/circuits/test_noises.py deleted file mode 100644 index 5fffba43..00000000 --- a/test/unit_tests/braket/circuits/test_noises.py +++ /dev/null @@ -1,720 +0,0 @@ -# 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 - -import braket.ir.jaqcd as ir -from braket.circuits import Circuit, Instruction, Noise, QubitSet -from braket.circuits.free_parameter import FreeParameter -from braket.circuits.serialization import ( - IRType, - OpenQASMSerializationProperties, - QubitReferenceType, -) -from braket.ir.jaqcd.shared_models import ( - DampingProbability, - DampingSingleProbability, - DoubleControl, - DoubleTarget, - MultiProbability, - MultiTarget, - SingleControl, - SingleProbability, - SingleProbability_34, - SingleProbability_1516, - SingleTarget, - TripleProbability, - TwoDimensionalMatrixList, -) - -testdata = [ - (Noise.BitFlip, "bit_flip", ir.BitFlip, [SingleTarget, SingleProbability], {}), - (Noise.PhaseFlip, "phase_flip", ir.PhaseFlip, [SingleTarget, SingleProbability], {}), - (Noise.Depolarizing, "depolarizing", ir.Depolarizing, [SingleTarget, SingleProbability_34], {}), - ( - Noise.AmplitudeDamping, - "amplitude_damping", - ir.AmplitudeDamping, - [SingleTarget, DampingProbability], - {}, - ), - ( - Noise.GeneralizedAmplitudeDamping, - "generalized_amplitude_damping", - ir.GeneralizedAmplitudeDamping, - [SingleTarget, DampingProbability, DampingSingleProbability], - {}, - ), - ( - Noise.PhaseDamping, - "phase_damping", - ir.PhaseDamping, - [SingleTarget, DampingProbability], - {}, - ), - ( - Noise.TwoQubitDepolarizing, - "two_qubit_depolarizing", - ir.TwoQubitDepolarizing, - [DoubleTarget, SingleProbability_1516], - {}, - ), - ( - Noise.TwoQubitDephasing, - "two_qubit_dephasing", - ir.TwoQubitDephasing, - [DoubleTarget, SingleProbability_34], - {}, - ), - ( - Noise.TwoQubitPauliChannel, - "two_qubit_pauli_channel", - ir.MultiQubitPauliChannel, - [DoubleTarget, MultiProbability], - {}, - ), - ( - Noise.PauliChannel, - "pauli_channel", - ir.PauliChannel, - [SingleTarget, TripleProbability], - {}, - ), - ( - Noise.Kraus, - "kraus", - ir.Kraus, - [TwoDimensionalMatrixList, MultiTarget], - {"input_type": complex}, - ), - ( - Noise.Kraus, - "kraus", - ir.Kraus, - [TwoDimensionalMatrixList, MultiTarget], - {"input_type": float}, - ), - ( - Noise.Kraus, - "kraus", - ir.Kraus, - [TwoDimensionalMatrixList, MultiTarget], - {"input_type": int}, - ), -] - - -invalid_kraus_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], [3, 4, 5], [6, 7, 8]])]), - ([np.array([[0, 1], [1, 1]])]), - ([np.array([[1, 0], [0, 1]]), np.array([[0, 1], [1, 0]])]), - ([np.array([[1, 0], [0, 1]]) * np.sqrt(0.5), np.eye(4) * np.sqrt(0.5)]), - ([np.eye(8)]), - ([np.eye(2), np.eye(2), np.eye(2), np.eye(2), np.eye(2)]), -] - - -def single_target_valid_input(**kwargs): - return {"target": 2} - - -def double_target_valid_ir_input(**kwargs): - return {"targets": [2, 3]} - - -def double_target_valid_input(**kwargs): - return {"target1": 2, "target2": 3} - - -def single_probability_valid_input(**kwargs): - return {"probability": 0.1234} - - -def single_probability_34_valid_input(**kwargs): - return {"probability": 0.1234} - - -def single_probability_1516_valid_input(**kwargs): - return {"probability": 0.1234} - - -def damping_single_probability_valid_input(**kwargs): - return {"probability": 0.1234} - - -def damping_probability_valid_input(**kwargs): - return {"gamma": 0.1234} - - -def triple_probability_valid_input(**kwargs): - return {"probX": 0.1234, "probY": 0.1324, "probZ": 0.1423} - - -def single_control_valid_input(**kwargs): - return {"control": 0} - - -def double_control_valid_ir_input(**kwargs): - return {"controls": [0, 1]} - - -def double_control_valid_input(**kwargs): - return {"control1": 0, "control2": 1} - - -def multi_target_valid_input(**kwargs): - return {"targets": [5]} - - -def two_dimensional_matrix_list_valid_ir_input(**kwargs): - return {"matrices": [[[[0, 0], [1, 0]], [[1, 0], [0, 0]]]]} - - -def two_dimensional_matrix_list_valid_input(**kwargs): - input_type = kwargs.get("input_type") - return { - "matrices": [np.array([[input_type(0), input_type(1)], [input_type(1), input_type(0)]])] - } - - -def multi_probability_valid_input(**kwargs): - return {"probabilities": {"XX": 0.1}} - - -def multi_probability_invalid_input(**kwargs): - return {"probabilities": {"XX": 1.1}} - - -valid_ir_switcher = { - "SingleTarget": single_target_valid_input, - "DoubleTarget": double_target_valid_ir_input, - "SingleProbability": single_probability_valid_input, - "SingleProbability_34": single_probability_34_valid_input, - "SingleProbability_1516": single_probability_1516_valid_input, - "DampingProbability": damping_probability_valid_input, - "DampingSingleProbability": damping_single_probability_valid_input, - "TripleProbability": triple_probability_valid_input, - "MultiProbability": multi_probability_valid_input, - "SingleControl": single_control_valid_input, - "DoubleControl": double_control_valid_ir_input, - "MultiTarget": multi_target_valid_input, - "TwoDimensionalMatrixList": two_dimensional_matrix_list_valid_ir_input, -} - - -valid_subroutine_switcher = dict( - valid_ir_switcher, - **{ - "TwoDimensionalMatrixList": two_dimensional_matrix_list_valid_input, - "DoubleTarget": double_target_valid_input, - "DoubleControl": double_control_valid_input, - }, -) - - -def create_valid_ir_input(irsubclasses): - input = {} - for subclass in irsubclasses: - 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 |= valid_subroutine_switcher.get(subclass.__name__, lambda: "Invalid subclass")( - **kwargs - ) - return input - - -def create_valid_target_input(irsubclasses): - qubit_set = [] - # based on the concept that control goes first in target input - for subclass in irsubclasses: - if subclass == SingleTarget: - qubit_set.extend(list(single_target_valid_input().values())) - elif subclass == DoubleTarget: - qubit_set.extend(list(double_target_valid_ir_input().values())) - elif subclass == MultiTarget: - qubit_set.extend(list(multi_target_valid_input().values())) - elif subclass == SingleControl: - 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 all( - subclass != i - for i in [ - SingleProbability, - SingleProbability_34, - SingleProbability_1516, - DampingSingleProbability, - DampingProbability, - TripleProbability, - TwoDimensionalMatrixList, - MultiProbability, - ] - ): - raise ValueError("Invalid subclass") - input = {"target": QubitSet(qubit_set)} - return input - - -def create_valid_noise_class_input(irsubclasses, **kwargs): - input = {} - if SingleProbability in irsubclasses: - input |= single_probability_valid_input() - if SingleProbability_34 in irsubclasses: - input.update(single_probability_34_valid_input()) - if SingleProbability_1516 in irsubclasses: - input.update(single_probability_1516_valid_input()) - if DampingSingleProbability in irsubclasses: - input.update(damping_single_probability_valid_input()) - if DampingProbability in irsubclasses: - input.update(damping_probability_valid_input()) - if TripleProbability in irsubclasses: - input.update(triple_probability_valid_input()) - if MultiProbability in irsubclasses: - input.update(multi_probability_valid_input()) - if TwoDimensionalMatrixList in irsubclasses: - input.update(two_dimensional_matrix_list_valid_input(**kwargs)) - return input - - -def create_valid_instruction_input(testclass, irsubclasses, **kwargs): - input = create_valid_target_input(irsubclasses) - input["operator"] = testclass(**create_valid_noise_class_input(irsubclasses, **kwargs)) - return input - - -def calculate_qubit_count(irsubclasses): - qubit_count = 0 - for subclass in irsubclasses: - if subclass == SingleTarget: - qubit_count += 1 - elif subclass == DoubleTarget: - qubit_count += 2 - elif subclass == SingleControl: - qubit_count += 1 - elif subclass == DoubleControl: - qubit_count += 2 - elif subclass == MultiTarget: - qubit_count += 3 - elif all( - subclass != i - for i in [ - SingleProbability, - SingleProbability_34, - SingleProbability_1516, - DampingSingleProbability, - DampingProbability, - TripleProbability, - MultiProbability, - TwoDimensionalMatrixList, - ] - ): - raise ValueError("Invalid subclass") - return qubit_count - - -@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) -def test_ir_noise_level(testclass, subroutine_name, irclass, irsubclasses, kwargs): - expected = irclass(**create_valid_ir_input(irsubclasses)) - actual = testclass(**create_valid_noise_class_input(irsubclasses, **kwargs)).to_ir( - **create_valid_target_input(irsubclasses) - ) - assert actual == expected - - -@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) -def test_ir_instruction_level(testclass, subroutine_name, irclass, irsubclasses, kwargs): - expected = irclass(**create_valid_ir_input(irsubclasses)) - instruction = Instruction(**create_valid_instruction_input(testclass, irsubclasses, **kwargs)) - actual = instruction.to_ir() - assert actual == expected - - -@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) -def test_noise_subroutine(testclass, subroutine_name, irclass, irsubclasses, kwargs): - qubit_count = calculate_qubit_count(irsubclasses) - subroutine = getattr(Circuit(), subroutine_name) - assert subroutine(**create_valid_subroutine_input(irsubclasses, **kwargs)) == Circuit( - Instruction(**create_valid_instruction_input(testclass, irsubclasses, **kwargs)) - ) - if qubit_count == 1: - multi_targets = [0, 1, 2] - 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 |= single_probability_valid_input() - if SingleProbability_34 in irsubclasses: - subroutine_input.update(single_probability_34_valid_input()) - if SingleProbability_1516 in irsubclasses: - subroutine_input.update(single_probability_1516_valid_input()) - if DampingSingleProbability in irsubclasses: - subroutine_input.update(damping_single_probability_valid_input()) - if DampingProbability in irsubclasses: - subroutine_input.update(damping_probability_valid_input()) - if TripleProbability in irsubclasses: - subroutine_input.update(triple_probability_valid_input()) - if MultiProbability in irsubclasses: - subroutine_input.update(multi_probability_valid_input()) - - circuit1 = subroutine(**subroutine_input) - circuit2 = Circuit(instruction_list) - assert circuit1 == circuit2 - - -@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) -def test_noise_to_matrix(testclass, subroutine_name, irclass, irsubclasses, kwargs): - noise1 = testclass(**create_valid_noise_class_input(irsubclasses, **kwargs)) - noise2 = testclass(**create_valid_noise_class_input(irsubclasses, **kwargs)) - assert all(isinstance(matrix, np.ndarray) for matrix in noise1.to_matrix()) - assert all(np.allclose(m1, m2) for m1, m2 in zip(noise1.to_matrix(), noise2.to_matrix())) - - -@pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) -def test_fixed_qubit_count(testclass, subroutine_name, irclass, irsubclasses, kwargs): - fixed_qubit_count = testclass.fixed_qubit_count() - if fixed_qubit_count is not NotImplemented: - noise = testclass(**create_valid_noise_class_input(irsubclasses, **kwargs)) - assert noise.qubit_count == fixed_qubit_count - - -@pytest.mark.parametrize( - "parameterized_noise", - [ - (Noise.BitFlip(0.1)), - (Noise.BitFlip(FreeParameter("alpha"))), - (Noise.PhaseFlip(0.1)), - (Noise.PhaseFlip(FreeParameter("alpha"))), - (Noise.Depolarizing(0.1)), - (Noise.Depolarizing(FreeParameter("alpha"))), - (Noise.AmplitudeDamping(0.1)), - (Noise.AmplitudeDamping(FreeParameter("alpha"))), - (Noise.GeneralizedAmplitudeDamping(0.1, 0.2)), - (Noise.GeneralizedAmplitudeDamping(FreeParameter("alpha"), FreeParameter("beta"))), - (Noise.PhaseDamping(0.1)), - (Noise.PhaseDamping(FreeParameter("alpha"))), - (Noise.TwoQubitDepolarizing(0.1)), - (Noise.TwoQubitDepolarizing(FreeParameter("alpha"))), - (Noise.TwoQubitDephasing(0.1)), - (Noise.TwoQubitDephasing(FreeParameter("alpha"))), - (Noise.TwoQubitPauliChannel({"XX": 0.1, "YY": 0.2})), - (Noise.TwoQubitPauliChannel({"XX": FreeParameter("x"), "YY": FreeParameter("y")})), - (Noise.PauliChannel(0.1, 0.2, 0.3)), - (Noise.PauliChannel(FreeParameter("x"), FreeParameter("y"), FreeParameter("z"))), - ], -) -def test_serialization(parameterized_noise): - serialized = parameterized_noise.to_dict() - serialized_str = json.dumps(serialized) - deserialized_dict = json.loads(serialized_str) - deserialized = Noise.from_dict(deserialized_dict) - assert deserialized == parameterized_noise - - -@pytest.mark.parametrize( - "parameterized_noise, params, expected_noise", - [ - (Noise.BitFlip(FreeParameter("alpha")), {"alpha": 0.1}, Noise.BitFlip(0.1)), - (Noise.PhaseFlip(FreeParameter("alpha")), {"alpha": 0.1}, Noise.PhaseFlip(0.1)), - (Noise.Depolarizing(FreeParameter("alpha")), {"alpha": 0.1}, Noise.Depolarizing(0.1)), - ( - Noise.AmplitudeDamping(FreeParameter("alpha")), - {"alpha": 0.1}, - Noise.AmplitudeDamping(0.1), - ), - ( - Noise.GeneralizedAmplitudeDamping(FreeParameter("alpha"), FreeParameter("beta")), - {"alpha": 0.1}, - Noise.GeneralizedAmplitudeDamping(0.1, FreeParameter("beta")), - ), - (Noise.PhaseDamping(FreeParameter("alpha")), {"alpha": 0.1}, Noise.PhaseDamping(0.1)), - ( - Noise.TwoQubitDepolarizing(FreeParameter("alpha")), - {"alpha": 0.1}, - Noise.TwoQubitDepolarizing(0.1), - ), - ( - Noise.TwoQubitDephasing(FreeParameter("alpha")), - {"alpha": 0.1}, - Noise.TwoQubitDephasing(0.1), - ), - ( - Noise.TwoQubitPauliChannel({"XX": FreeParameter("x"), "YY": FreeParameter("y")}), - {"x": 0.1}, - Noise.TwoQubitPauliChannel({"XX": 0.1, "YY": FreeParameter("y")}), - ), - ( - Noise.PauliChannel(FreeParameter("x"), FreeParameter("y"), FreeParameter("z")), - {"x": 0.1, "z": 0.2}, - Noise.PauliChannel(0.1, FreeParameter("y"), 0.2), - ), - ], -) -def test_parameter_binding(parameterized_noise, params, expected_noise): - result_noise = parameterized_noise.bind_values(**params) - assert result_noise == expected_noise - - -def test_parameterized_noise(): - noise = Noise.PauliChannel(FreeParameter("a"), 0.2, FreeParameter("b")) - assert noise.probX == FreeParameter("a") - assert noise.probY == 0.2 - assert noise.probZ == FreeParameter("b") - - -# Additional Unitary noise tests - - -@pytest.mark.parametrize("matrices", invalid_kraus_matrices) -def test_kraus_invalid_matrix(matrices): - with pytest.raises(ValueError): - Noise.Kraus(matrices=matrices) - - -def test_kraus_matrix_target_size_mismatch(): - with pytest.raises(ValueError): - Circuit().kraus( - matrices=[np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])], - targets=[0], - ) - - -@pytest.mark.parametrize( - "probs", - [ - {"X": -0.1}, - {"XY": 1.1}, - {"TX": 0.1}, - {"X": 0.5, "Y": 0.6}, - {"X": 0.1, "YY": 0.2}, - {"II": 0.9, "XX": 0.1}, - ], -) -def test_invalid_values_pauli_channel_two_qubit(probs): - with pytest.raises(ValueError): - Noise.TwoQubitPauliChannel(probs) - - -@pytest.mark.parametrize( - "probs", - [ - {"XY": 0.1}, - {"XX": 0.1, "ZZ": 0.2}, - ], -) -def test_valid_values_pauli_channel_two_qubit(probs): - noise = Noise.TwoQubitPauliChannel(probs) - assert len(noise.to_matrix()) == 16 - - -@pytest.mark.parametrize( - "noise, serialization_properties, target, expected_ir", - [ - ( - Noise.BitFlip(0.5), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3], - "#pragma braket noise bit_flip(0.5) q[3]", - ), - ( - Noise.BitFlip(0.5), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3], - "#pragma braket noise bit_flip(0.5) $3", - ), - ( - Noise.PhaseFlip(0.5), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3], - "#pragma braket noise phase_flip(0.5) q[3]", - ), - ( - Noise.PhaseFlip(0.5), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3], - "#pragma braket noise phase_flip(0.5) $3", - ), - ( - Noise.PauliChannel(0.1, 0.2, 0.3), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3], - "#pragma braket noise pauli_channel(0.1, 0.2, 0.3) q[3]", - ), - ( - Noise.PauliChannel(0.1, 0.2, 0.3), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3], - "#pragma braket noise pauli_channel(0.1, 0.2, 0.3) $3", - ), - ( - Noise.Depolarizing(0.5), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3], - "#pragma braket noise depolarizing(0.5) q[3]", - ), - ( - Noise.Depolarizing(0.5), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3], - "#pragma braket noise depolarizing(0.5) $3", - ), - ( - Noise.TwoQubitDepolarizing(0.5), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3, 5], - "#pragma braket noise two_qubit_depolarizing(0.5) q[3], q[5]", - ), - ( - Noise.TwoQubitDepolarizing(0.5), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3, 5], - "#pragma braket noise two_qubit_depolarizing(0.5) $3, $5", - ), - ( - Noise.TwoQubitDephasing(0.5), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3, 5], - "#pragma braket noise two_qubit_dephasing(0.5) q[3], q[5]", - ), - ( - Noise.TwoQubitDephasing(0.5), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3, 5], - "#pragma braket noise two_qubit_dephasing(0.5) $3, $5", - ), - ( - Noise.AmplitudeDamping(0.5), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3], - "#pragma braket noise amplitude_damping(0.5) q[3]", - ), - ( - Noise.AmplitudeDamping(0.5), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3], - "#pragma braket noise amplitude_damping(0.5) $3", - ), - ( - Noise.GeneralizedAmplitudeDamping(0.5, 0.1), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3], - "#pragma braket noise generalized_amplitude_damping(0.5, 0.1) q[3]", - ), - ( - Noise.GeneralizedAmplitudeDamping(0.5, 0.1), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3], - "#pragma braket noise generalized_amplitude_damping(0.5, 0.1) $3", - ), - ( - Noise.PhaseDamping(0.5), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3], - "#pragma braket noise phase_damping(0.5) q[3]", - ), - ( - Noise.PhaseDamping(0.5), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3], - "#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), - ] - ), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3, 5], - "#pragma braket noise kraus([" - "[0.9486832980505138, 0, 0, 0], " - "[0, 0.9486832980505138, 0, 0], " - "[0, 0, 0.9486832980505138, 0], " - "[0, 0, 0, 0.9486832980505138]], [" - "[0, 0.31622776601683794, 0, 0], " - "[0.31622776601683794, 0, 0, 0], " - "[0, 0, 0, 0.31622776601683794], " - "[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), - ] - ), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3, 5], - "#pragma braket noise kraus([" - "[0.9486832980505138, 0, 0, 0], " - "[0, 0.9486832980505138, 0, 0], " - "[0, 0, 0.9486832980505138, 0], " - "[0, 0, 0, 0.9486832980505138]], [" - "[0, 0.31622776601683794, 0, 0], " - "[0.31622776601683794, 0, 0, 0], " - "[0, 0, 0, 0.31622776601683794], " - "[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]]), - ] - ), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3], - "#pragma braket noise kraus([" - "[0.9486833im, 0], [0, 0.9486833im]], [" - "[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]]), - ] - ), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3], - "#pragma braket noise kraus([" - "[0.9486833im, 0], [0, 0.9486833im]], [" - "[0, 0.31622777], [0.31622777, 0]]) $3", - ), - ], -) -def test_noise_to_ir_openqasm(noise, serialization_properties, target, expected_ir): - assert ( - noise.to_ir( - target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties - ) - == expected_ir - ) diff --git a/test/unit_tests/braket/circuits/test_observable.py b/test/unit_tests/braket/circuits/test_observable.py deleted file mode 100644 index 38689398..00000000 --- a/test/unit_tests/braket/circuits/test_observable.py +++ /dev/null @@ -1,216 +0,0 @@ -# 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 Observable, QuantumOperator, StandardObservable -from braket.circuits.serialization import IRType - - -@pytest.fixture -def observable(): - return Observable(qubit_count=1, ascii_symbols=["foo"]) - - -@pytest.fixture -def standard_observable(): - return StandardObservable(ascii_symbols=["foo"]) - - -@pytest.fixture -def unscaled_observable(observable): - return observable._unscaled() - - -@pytest.fixture -def unscaled_standard_observable(standard_observable): - return standard_observable._unscaled() - - -def test_is_operator(observable): - assert isinstance(observable, QuantumOperator) - - -def test_qubit_count_lt_one(): - with pytest.raises(ValueError): - Observable(qubit_count=0, ascii_symbols=[]) - - -def test_none_ascii(): - with pytest.raises(ValueError): - Observable(qubit_count=1, ascii_symbols=None) - - -def test_mismatch_length_ascii(): - with pytest.raises(ValueError): - Observable(qubit_count=1, ascii_symbols=["foo", "bar"]) - - -def test_name(observable): - expected = observable.__class__.__name__ - assert observable.name == expected - - -def test_getters(): - qubit_count = 2 - ascii_symbols = ("foo", "bar") - observable = Observable(qubit_count=qubit_count, ascii_symbols=ascii_symbols) - - assert observable.qubit_count == qubit_count - assert observable.ascii_symbols == ascii_symbols - - -def test_qubit_count_setter(observable): - with pytest.raises(AttributeError): - observable.qubit_count = 10 - - -def test_ascii_symbols_setter(observable): - with pytest.raises(AttributeError): - observable.ascii_symbols = ["foo", "bar"] - - -def test_name_setter(observable): - with pytest.raises(AttributeError): - observable.name = "hi" - - -@pytest.mark.parametrize( - "ir_type, serialization_properties, expected_exception, expected_message", - [ - (IRType.JAQCD, None, NotImplementedError, "to_jaqcd has not been implemented yet."), - (IRType.OPENQASM, None, NotImplementedError, "to_openqasm has not been implemented yet."), - ("invalid-ir-type", None, ValueError, "Supplied ir_type invalid-ir-type is not supported."), - ( - IRType.OPENQASM, - "invalid-property-type", - ValueError, - "serialization_properties must be of type OpenQASMSerializationProperties for " - "IRType.OPENQASM.", - ), - ], -) -def test_observable_to_ir( - ir_type, serialization_properties, expected_exception, expected_message, observable -): - with pytest.raises(expected_exception) as exc: - observable.to_ir(0, ir_type, serialization_properties=serialization_properties) - assert exc.value.args[0] == expected_message - - -def test_to_matrix_not_implemented_by_default(observable): - with pytest.raises(NotImplementedError): - observable.to_matrix(None) - - -def test_basis_rotation_gates_not_implemented_by_default(observable): - with pytest.raises(NotImplementedError): - observable.basis_rotation_gates - - -def test_eigenvalues_not_implemented_by_default(observable): - with pytest.raises(NotImplementedError): - observable.eigenvalues - - -def test_eigenvalue_not_implemented_by_default(observable): - with pytest.raises(NotImplementedError): - observable.eigenvalue(0) - - -def test_str(observable): - expected = f"{observable.name}('qubit_count': {observable.qubit_count})" - assert str(observable) == expected - assert observable.coefficient == 1 - - -def test_register_observable(): - class _FooObservable(Observable): - def __init__(self): - super().__init__(qubit_count=1, ascii_symbols=["foo"]) - - Observable.register_observable(_FooObservable) - assert Observable._FooObservable().name == _FooObservable().name - - -def test_matmul_observable(): - o1 = Observable.I() - o2 = Observable.Z() - o3 = o1 @ o2 - assert isinstance(o3, Observable.TensorProduct) - assert o3.qubit_count == 2 - assert o3.to_ir() == ["i", "z"] - assert o3.ascii_symbols == ("I@Z", "I@Z") - - -def test_matmul_non_observable(): - with pytest.raises(ValueError): - Observable.I() @ "a" - - -def test_observable_equality(): - o1 = Observable.I() - o2 = Observable.I() - o3 = Observable.Z() - o4 = "a" - assert o1 == o2 - assert o1 != o3 - assert o1 != o4 - - -def test_standard_observable_subclass_of_observable(standard_observable): - assert isinstance(standard_observable, Observable) - - -def test_unscaled_standard_observable_subclass_of_observable(unscaled_standard_observable): - assert isinstance(unscaled_standard_observable, Observable) - - -def test_standard_observable_eigenvalues(standard_observable): - assert np.allclose(standard_observable.eigenvalues, np.array([1, -1])) - - -def test_unscaled_standard_observable_eigenvalues(unscaled_standard_observable): - assert np.allclose(unscaled_standard_observable.eigenvalues, np.array([1, -1])) - - -def test_observable_coeffs(observable): - observable = 2 * observable - assert observable.coefficient == 2 - unscaled_observable = observable._unscaled() - assert unscaled_observable.coefficient == 1 - assert isinstance(unscaled_observable, 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): - 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): - 2 * observable - parameter - - -def test_sum_observable_with_subtraction(): - obs1 = 6 * Observable.X() - obs2 = -4 * Observable.Y() - result = obs1 - obs2 - assert isinstance(result, Observable.Sum) - assert result.qubit_count == 1 - assert np.array_equal(result.summands, (6 * Observable.X(), -4 * Observable.Y())) diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py deleted file mode 100644 index b6430d4d..00000000 --- a/test/unit_tests/braket/circuits/test_observables.py +++ /dev/null @@ -1,716 +0,0 @@ -# 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 -import numpy.testing as npt -import pytest - -from braket.circuits import Gate, Observable -from braket.circuits.observables import observable_from_ir -from braket.circuits.quantum_operator_helpers import get_pauli_eigenvalues -from braket.circuits.serialization import ( - IRType, - OpenQASMSerializationProperties, - 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, serialization_properties, target, expected_ir", - [ - ( - Observable.I(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3], - "i(q[3])", - ), - ( - Observable.I(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3], - "i($3)", - ), - ( - Observable.I(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - None, - "i all", - ), - ( - Observable.X(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3], - "x(q[3])", - ), - ( - Observable.X(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3], - "x($3)", - ), - ( - Observable.X(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - None, - "x all", - ), - ( - Observable.Y(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3], - "y(q[3])", - ), - ( - Observable.Y(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3], - "y($3)", - ), - ( - Observable.Y(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - None, - "y all", - ), - ( - Observable.Z(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3], - "z(q[3])", - ), - ( - Observable.Z(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3], - "z($3)", - ), - ( - Observable.Z(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - None, - "z all", - ), - ( - Observable.H(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3], - "h(q[3])", - ), - ( - Observable.H(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3], - "h($3)", - ), - ( - Observable.H(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - None, - "h all", - ), - ( - Observable.Hermitian(np.eye(4)), - 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)), - 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)), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - None, - "hermitian([[1+0im, 0im], [0im, 1+0im]]) all", - ), - ( - Observable.H() @ Observable.Z(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3, 0], - "h(q[3]) @ z(q[0])", - ), - ( - Observable.H() @ Observable.Z(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3, 0], - "h($3) @ z($0)", - ), - ( - Observable.H() @ Observable.Z() @ Observable.I(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3, 0, 1], - "h(q[3]) @ z(q[0]) @ i(q[1])", - ), - ( - Observable.H() @ Observable.Z() @ Observable.I(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3, 0, 1], - "h($3) @ z($0) @ i($1)", - ), - ( - Observable.Hermitian(np.eye(4)) @ Observable.I(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - [3, 0, 1], - "hermitian([[1+0im, 0im, 0im, 0im], [0im, 1+0im, 0im, 0im], " - "[0im, 0im, 1+0im, 0im], [0im, 0im, 0im, 1+0im]]) q[3], q[0]" - " @ i(q[1])", - ), - ( - Observable.I() @ Observable.Hermitian(np.eye(4)), - 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()), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3], - "6 * z($3)", - ), - ( - (2 * Observable.I()) @ (2 * Observable.Hermitian(np.eye(4))), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3, 0, 1], - "4 * 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", - ), - ( - Observable.Z() + 2 * Observable.H(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [[3], [4]], - "z($3) + 2 * h($4)", - ), - ( - 3 * (Observable.H() + 2 * Observable.X()), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [[3], [0]], - "3 * h($3) + 6 * x($0)", - ), - ( - 3 * (Observable.H() + 2 * Observable.H()), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [[3], [3]], - "3 * h($3) + 6 * h($3)", - ), - ( - 3 * (Observable.H() + 2 * Observable.H()), - 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(), - 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(), - 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()))), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [[0], [1, 2]], - "8 * z($0) + 48 * x($1) @ y($2)", - ), - ], -) -def test_observables_to_ir_openqasm(observable, serialization_properties, target, expected_ir): - assert ( - observable.to_ir( - target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties - ) - == expected_ir - ) - - -@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()]), - ], -) -def test_observable_coef_jaqcd(observable): - coef_not_supported_with_jaqcd = "Observable coefficients not supported with Jaqcd" - with pytest.raises(ValueError, match=coef_not_supported_with_jaqcd): - observable.to_ir(target=0, ir_type=IRType.JAQCD) - - -@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()), - ], -) -def test_invalid_scalar_multiplication(expression, observable): - with pytest.raises(TypeError, match="Observable coefficients must be numbers."): - expression * observable - - -@pytest.mark.parametrize( - "observable, matrix", - [ - ( - (-3 * Observable.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(), - np.array([[3.0 + 0.0j, 0.0 + 0.0j], [0.0 + 0.0j, -3.0 + 0.0j]]), - ), - ( - (2 * Observable.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(), - np.array([[0.0 + 0.0j, 1.2 + 0.0j], [1.2 + 0.0j, 0.0 + 0.0j]]), - ), - ( - (1e-2 * Observable.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([[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(), - np.array( - [ - [1.41421356 + 0.0j, 1.41421356 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j], - [1.41421356 + 0.0j, -1.41421356 + 0.0j, 0.0 + 0.0j, -0.0 + 0.0j], - [0.0 + 0.0j, 0.0 + 0.0j, -1.41421356 + 0.0j, -1.41421356 + 0.0j], - [0.0 + 0.0j, -0.0 + 0.0j, -1.41421356 + 0.0j, 1.41421356 + 0.0j], - ], - ), - ), - ], -) -def test_valid_scaled_matrix(observable, matrix): - npt.assert_allclose(observable, matrix) - - -@pytest.mark.parametrize( - "observable, eigenvalue", - [ - (-2 * Observable.I().eigenvalues, np.array([-2.0, -2.0])), - ( - 3e-2 * Observable.Hermitian(matrix=np.array([[0, 1], [1, 0]])).eigenvalues, - np.array([-0.03, 0.03]), - ), - ], -) -def test_valid_scaled_eigenvalues(observable, eigenvalue): - npt.assert_allclose(observable, eigenvalue) - - -@pytest.mark.parametrize( - "testobject,gateobject,expected_ir,basis_rotation_gates,eigenvalues", testdata -) -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) - - -@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( - "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( - "testobject,gateobject,expected_ir,basis_rotation_gates,eigenvalues", testdata -) -def test_observable_from_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenvalues): - assert testobject == observable_from_ir(expected_ir) - - -# Hermitian - - -@pytest.mark.parametrize("matrix", invalid_hermitian_matrices) -def test_hermitian_invalid_matrix(matrix): - with pytest.raises(ValueError): - Observable.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()) - a4 = "hi" - assert a1 == a2 - assert a1 != a3 - assert a1 != a4 - - -def test_hermitian_to_ir(): - matrix = Observable.I().to_matrix() - obs = Observable.Hermitian(matrix=matrix) - assert obs.to_ir() == [[[[1, 0], [0, 0]], [[0, 0], [1, 0]]]] - - -@pytest.mark.parametrize( - "matrix,eigenvalues", - [ - (np.array([[1.0, 0.0], [0.0, 1.0]]), np.array([1, 1])), - (np.array([[0, -1j], [1j, 0]]), np.array([-1.0, 1.0])), - (np.array([[1, 1 - 1j], [1 + 1j, -1]]), np.array([-np.sqrt(3), np.sqrt(3)])), - ], -) -def test_hermitian_eigenvalues(matrix, eigenvalues): - compare_eigenvalues(Observable.Hermitian(matrix=matrix), eigenvalues) - - -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()] - ) - assert expected == actual - - -@pytest.mark.parametrize( - "matrix,basis_rotation_matrix", - [ - ( - np.array([[0.0, 1.0], [1.0, 0.0]]), - np.array([[-0.70710678, 0.70710678], [0.70710678, 0.70710678]]).conj().T, - ), - ( - np.array([[0, -1j], [1j, 0]]), - 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], - ] - ), - ), - ], -) -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 == (expected_unitary,) - assert expected_unitary.matrix_equivalence(actual_rotation_gates[0]) - - -def test_observable_from_ir_hermitian_value_error(): - ir_observable = [[[[1.0, 0], [0, 1]], [[0.0, 1], [1, 0]]]] - with pytest.raises(ValueError): - observable_from_ir(ir_observable) - - -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]])) - - -def test_hermitian_str(): - assert ( - str(Observable.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]])" - ) - - -# TensorProduct - - -def test_tensor_product_to_ir(): - t = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.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_matmul_tensor(): - t1 = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) - 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 - assert t3.ascii_symbols == tuple(["Z@I@X@Hermitian@Y"] * 5) - - -def test_tensor_product_matmul_observable(): - t1 = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) - o1 = Observable.I() - t = t1 @ o1 - assert t.to_ir() == ["z", "i", "x", "i"] - assert t.qubit_count == 4 - assert t.ascii_symbols == tuple(["Z@I@X@I"] * 4) - - -def test_tensor_product_eigenvalue_index_out_of_bounds(): - obs = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) - with pytest.raises(ValueError): - obs.eigenvalue(8) - - -def test_tensor_product_value_error(): - with pytest.raises(ValueError): - Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) @ "a" - - -def test_tensor_product_rmatmul_observable(): - t1 = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) - o1 = Observable.I() - t = o1 @ t1 - assert t.to_ir() == ["i", "z", "i", "x"] - assert t.qubit_count == 4 - assert t.ascii_symbols == tuple(["I@Z@I@X"] * 4) - - -@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])), - ( - Observable.X() - @ Observable.Hermitian( - np.array([[-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) - ) - @ Observable.Y(), - np.array([-1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1]), - ), - ], -) -def test_tensor_product_eigenvalues(observable, eigenvalues): - compare_eigenvalues(observable, eigenvalues) - # Test caching - observable._factors = () - compare_eigenvalues(observable, eigenvalues) - - -@pytest.mark.parametrize( - "observable,basis_rotation_gates", - [ - (Observable.X() @ Observable.Y(), (Gate.H(), Gate.Z(), Gate.S(), Gate.H())), - ( - Observable.X() @ Observable.Y() @ Observable.Z(), - (Gate.H(), Gate.Z(), Gate.S(), Gate.H()), - ), - ( - Observable.X() @ Observable.Y() @ Observable.I(), - (Gate.H(), Gate.Z(), Gate.S(), Gate.H()), - ), - (Observable.X() @ Observable.H(), (Gate.H(), Gate.Ry(-np.pi / 4))), - ], -) -def test_tensor_product_basis_rotation_gates(observable, basis_rotation_gates): - assert observable.basis_rotation_gates == basis_rotation_gates - - -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"]) - assert expected_observable == actual_observable - - -def test_observable_from_ir_tensor_product_value_error(): - with pytest.raises(ValueError): - observable_from_ir(["z", "i", "foo"]) - - -def compare_eigenvalues(observable, expected): - assert np.allclose(observable.eigenvalues, expected) - assert np.allclose( - np.array([observable.eigenvalue(i) for i in range(2**observable.qubit_count)]), - 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()]) - - -# Sum of observables - - -@pytest.mark.parametrize( - "observable,basis_rotation_gates", - [(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" - with pytest.raises(NotImplementedError, match=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 - - -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() - - -def test_invalid_targets_config_for_sum_obs(): - observable, serialization_properties = ( - 2 * Observable.X() @ Observable.Y() + 0.75 * Observable.Y() @ Observable.Z(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - ) - target = [[0, 1]] - - target_len_mismatch_for_sum_terms = "Invalid target of length 1 for Sum with 2 terms" - - with pytest.raises(ValueError, match=target_len_mismatch_for_sum_terms): - observable.to_ir( - target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties - ) - - -def test_sum_obs_str(): - assert ( - str(Observable.Sum([2 * Observable.X() + 3 * Observable.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()]) - assert t1 == t2 - assert t2 != t3 - assert t1 != t3 - assert t3 == t4 - - -def test_invalid_target_length_for_sum_obs_term(): - observable, serialization_properties = ( - 2 * Observable.Y() + 0.75 * Observable.Y() @ Observable.Z(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - ) - target = [[0, 1], [0, 1]] - - invalid_target_len_for_term = "Invalid target for term 0 of Sum. Expected 1 targets, got 2" - - with pytest.raises(ValueError, match=invalid_target_len_for_term): - observable.to_ir( - target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties - ) - - -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() diff --git a/test/unit_tests/braket/circuits/test_quantum_operator.py b/test/unit_tests/braket/circuits/test_quantum_operator.py deleted file mode 100644 index 3a26e8c8..00000000 --- a/test/unit_tests/braket/circuits/test_quantum_operator.py +++ /dev/null @@ -1,142 +0,0 @@ -# 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 Operator, QuantumOperator - - -class _DummyQuantumOperator(QuantumOperator): - @staticmethod - def fixed_qubit_count(): - return 2 - - -@pytest.fixture -def ascii_symbols(): - return ["foo"] - - -@pytest.fixture -def quantum_operator(ascii_symbols): - return QuantumOperator(qubit_count=1, ascii_symbols=ascii_symbols) - - -def test_is_operator(quantum_operator): - assert isinstance(quantum_operator, Operator) - - -def test_ascii_symbols(quantum_operator, ascii_symbols): - assert quantum_operator.ascii_symbols == tuple(ascii_symbols) - - -def test_fixed_qubit_count_implemented(): - operator = _DummyQuantumOperator(qubit_count=None, ascii_symbols=["foo", "bar"]) - assert operator.qubit_count == _DummyQuantumOperator.fixed_qubit_count() - - -def test_qubit_count_fixed_qubit_count_unequal(): - with pytest.raises(ValueError): - _DummyQuantumOperator(qubit_count=1, ascii_symbols=["foo", "bar"]) - - -def test_qubit_count_not_int(): - with pytest.raises(TypeError): - QuantumOperator(qubit_count="hello", ascii_symbols=[]) - - -def test_qubit_count_lt_one(): - with pytest.raises(ValueError): - QuantumOperator(qubit_count=0, ascii_symbols=[]) - - -def test_none_ascii(): - with pytest.raises(ValueError): - QuantumOperator(qubit_count=1, ascii_symbols=None) - - -def test_mismatch_length_ascii(): - with pytest.raises(ValueError): - QuantumOperator(qubit_count=1, ascii_symbols=["foo", "bar"]) - - -def test_name(quantum_operator): - expected = quantum_operator.__class__.__name__ - assert quantum_operator.name == expected - - -def test_getters(): - qubit_count = 2 - ascii_symbols = ("foo", "bar") - quantum_operator = QuantumOperator(qubit_count=qubit_count, ascii_symbols=ascii_symbols) - - assert quantum_operator.qubit_count == qubit_count - assert quantum_operator.ascii_symbols == ascii_symbols - - -def test_qubit_count_setter(quantum_operator): - with pytest.raises(AttributeError): - quantum_operator.qubit_count = 10 - - -def test_ascii_symbols_setter(quantum_operator): - with pytest.raises(AttributeError): - quantum_operator.ascii_symbols = ["foo", "bar"] - - -def test_name_setter(quantum_operator): - with pytest.raises(AttributeError): - quantum_operator.name = "hi" - - -def test_to_ir_not_implemented_by_default(quantum_operator): - with pytest.raises(NotImplementedError): - quantum_operator.to_ir(None) - - -def test_to_matrix_not_implemented_by_default(quantum_operator): - with pytest.raises(NotImplementedError): - quantum_operator.to_matrix(None) - - -def test_matrix_equivalence(): - class _Foo1(QuantumOperator): - def __init__(self): - super().__init__(qubit_count=1, ascii_symbols=["foo"]) - - def to_matrix(self): - return np.array([[1.0, 0.0], [0.0, 1.0j]]) - - class _Foo2(QuantumOperator): - def __init__(self): - super().__init__(qubit_count=1, ascii_symbols=["foo"]) - - def to_matrix(self): - return np.array([[1.0, 0.0], [1.0, 0.0]]) - - quantum_operator1 = _Foo1() - quantum_operator2 = _Foo1() - quantum_operator3 = _Foo2() - assert quantum_operator1.matrix_equivalence(quantum_operator2) - assert not quantum_operator1.matrix_equivalence(quantum_operator3) - - -def test_matrix_equivalence_non_quantum_operator(): - quantum_operator1 = QuantumOperator(qubit_count=1, ascii_symbols=["foo"]) - assert not quantum_operator1.matrix_equivalence(1) - - -def test_str(quantum_operator): - expected = f"{quantum_operator.name}('qubit_count': {quantum_operator.qubit_count})" - assert str(quantum_operator) == expected diff --git a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py b/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py deleted file mode 100644 index c0f8b2e2..00000000 --- a/test/unit_tests/braket/circuits/test_quantum_operator_helpers.py +++ /dev/null @@ -1,139 +0,0 @@ -# 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 functools - -import numpy as np -import pytest - -from braket.circuits.quantum_operator_helpers import ( - get_pauli_eigenvalues, - is_cptp, - is_hermitian, - is_square_matrix, - is_unitary, - verify_quantum_operator_matrix_dimensions, -) - -valid_unitary_hermitian_matrix = np.array([[0, 1], [1, 0]]) - -valid_CPTP_matrices = [ - np.array([[0, 1], [1, 0]]) / np.sqrt(2), - np.array([[0, 1], [1, 0]]) / np.sqrt(2), -] - -invalid_dimension_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]])), -] - -invalid_unitary_matrices_false = [(np.array([[0, 1], [1, 1]])), (np.array([[1, 2], [3, 4]]))] - -invalid_hermitian_matrices_false = [(np.array([[1, 0], [0, 1j]])), (np.array([[1, 2], [3, 4]]))] - -invalid_CPTP_matrices_false = [np.array([[1, 0], [0, 1]]), np.array([[0, 1], [1, 0]])] - -invalid_matrix_type_error = np.array([[0, 1], ["a", 0]]) - -z_matrix = np.array([[1, 0], [0, -1]]) - - -def test_verify_quantum_operator_matrix_dimensions(): - assert verify_quantum_operator_matrix_dimensions(valid_unitary_hermitian_matrix) is None - - -def test_is_unitary_true(): - assert is_unitary(valid_unitary_hermitian_matrix) - - -def test_is_hermitian_true(): - assert is_hermitian(valid_unitary_hermitian_matrix) - - -def test_is_cptp_true(): - assert is_cptp(valid_CPTP_matrices) - - -def test_is_square_matrix(): - assert is_square_matrix(valid_unitary_hermitian_matrix) - - -@pytest.mark.parametrize("matrix", invalid_dimension_matrices) -def test_verify_quantum_operator_matrix_dimensions_value_error(matrix): - with pytest.raises(ValueError): - verify_quantum_operator_matrix_dimensions(matrix) - - -@pytest.mark.parametrize("matrix", invalid_unitary_matrices_false) -def test_is_unitary_false(matrix): - assert not is_unitary(matrix) - - -@pytest.mark.parametrize("matrix", invalid_hermitian_matrices_false) -def test_is_hermitian_false(matrix): - assert not is_hermitian(matrix) - - -def test_is_cptp_false(): - assert not is_cptp(invalid_CPTP_matrices_false) - - -def test_is_hermitian_exception(): - with pytest.raises(Exception): - is_hermitian(invalid_matrix_type_error) - - -def test_is_unitary_exception(): - with pytest.raises(Exception): - is_unitary(invalid_matrix_type_error) - - -def test_is_cptp_exception(): - with pytest.raises(Exception): - is_cptp([invalid_matrix_type_error]) - - -def test_get_pauli_eigenvalues_correct_eigenvalues_one_qubit(): - """Test the get_pauli_eigenvalues function for one qubit""" - assert np.array_equal(get_pauli_eigenvalues(1), np.diag(z_matrix)) - - -def test_get_pauli_eigenvalues_correct_eigenvalues_two_qubits(): - """Test the get_pauli_eigenvalues function for two qubits""" - assert np.array_equal(get_pauli_eigenvalues(2), np.diag(np.kron(z_matrix, z_matrix))) - - -def test_get_pauli_eigenvalues_correct_eigenvalues_three_qubits(): - """Test the get_pauli_eigenvalues function for three qubits""" - assert np.array_equal( - get_pauli_eigenvalues(3), - np.diag(np.kron(z_matrix, np.kron(z_matrix, z_matrix))), - ) - - -@pytest.mark.parametrize("depth", list(range(1, 6))) -def test_get_pauli_eigenvalues_cache_usage(depth): - """Test that the right number of cachings have been executed after clearing the cache""" - get_pauli_eigenvalues.cache_clear() - get_pauli_eigenvalues(depth) - assert functools._CacheInfo(depth - 1, depth, 128, depth) == get_pauli_eigenvalues.cache_info() - - -@pytest.mark.parametrize("num_qubits", [1, 2]) -def test_get_pauli_eigenvalues_immutable(num_qubits): - with pytest.raises(ValueError): - get_pauli_eigenvalues(num_qubits)[0] = 100 diff --git a/test/unit_tests/braket/circuits/test_result_type.py b/test/unit_tests/braket/circuits/test_result_type.py deleted file mode 100644 index bc7cc090..00000000 --- a/test/unit_tests/braket/circuits/test_result_type.py +++ /dev/null @@ -1,251 +0,0 @@ -# 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 braket.circuits import Observable, ObservableResultType, ResultType -from braket.circuits.free_parameter import FreeParameter -from braket.circuits.result_type import ObservableParameterResultType -from braket.circuits.serialization import IRType - - -@pytest.fixture -def result_type(): - return ResultType(ascii_symbols=["foo"]) - - -@pytest.fixture -def prob(): - return ResultType.Probability([0, 1]) - - -@pytest.fixture -def sv(): - return ResultType.StateVector() - - -def test_none_ascii(): - with pytest.raises(ValueError): - ResultType(ascii_symbols=None) - - -def test_name(result_type): - expected = result_type.__class__.__name__ - assert result_type.name == expected - - -def test_ascii_symbol(): - ascii_symbols = ["foo"] - result_type = ResultType(ascii_symbols=ascii_symbols) - assert result_type.ascii_symbols == ascii_symbols - - -def test_equality_statevector(): - result1 = ResultType.StateVector() - result2 = ResultType.StateVector() - result3 = ResultType.Probability([1]) - result4 = "hi" - assert result1 == result2 - assert result1 != result3 - assert result1 != result4 - - -def test_equality_densitymatrix(): - result1 = ResultType.DensityMatrix() - result2 = ResultType.DensityMatrix() - result3 = ResultType.StateVector() - result4 = "foo" - assert result1 == result2 - assert result1 != result3 - assert result1 != result4 - - -def test_ascii_symbol_setter(result_type): - with pytest.raises(AttributeError): - result_type.ascii_symbols = ["bar"] - - -def test_name_setter(result_type): - with pytest.raises(AttributeError): - result_type.name = "hi" - - -def test_register_result(): - class _FooResultType(ResultType): - def __init__(self): - super().__init__(ascii_symbols=["foo"]) - - ResultType.register_result_type(_FooResultType) - assert ResultType._FooResultType().name == _FooResultType().name - - -def test_copy_creates_new_object(prob): - copy = prob.copy() - assert copy == prob - assert copy is not prob - - -def test_copy_with_mapping_target(sv): - target_mapping = {0: 10, 1: 11} - expected = ResultType.StateVector() - assert sv.copy(target_mapping=target_mapping) == expected - - -def test_copy_with_mapping_target_hasattr(prob): - target_mapping = {0: 10, 1: 11} - expected = ResultType.Probability([10, 11]) - assert prob.copy(target_mapping=target_mapping) == expected - - -def test_copy_with_target_hasattr(prob): - target = [10, 11] - expected = ResultType.Probability(target) - assert prob.copy(target=target) == expected - - -def test_copy_with_target(sv): - target = [10, 11] - expected = ResultType.StateVector() - assert sv.copy(target=target) == expected - - -def test_copy_with_target_and_mapping(prob): - with pytest.raises(TypeError): - prob.copy(target=[10], target_mapping={0: 10}) - - -# ObservableResultType - - -def test_expectation_init_value_error_target(): - with pytest.raises(ValueError): - ObservableResultType( - ascii_symbols=["Obs", "Obs"], observable=Observable.X() @ Observable.Y(), target=[] - ) - - -def test_expectation_init_value_error_ascii_symbols(): - with pytest.raises(ValueError): - ObservableResultType( - ascii_symbols=["Obs"], observable=Observable.X() @ Observable.Y(), target=[1, 2] - ) - - -def test_obs_rt_init_value_error_qubit_count(): - with pytest.raises(ValueError): - ObservableResultType(ascii_symbols=["Obs"], observable=Observable.X(), target=[0, 1]) - - -def test_obs_rt_equality(): - a1 = ObservableResultType(ascii_symbols=["Obs"], observable=Observable.X(), target=0) - a2 = ObservableResultType(ascii_symbols=["Obs"], observable=Observable.X(), target=0) - a3 = ObservableResultType(ascii_symbols=["Obs"], observable=Observable.X(), target=1) - a4 = "hi" - assert a1 == a2 - assert a1 != a3 - assert a1 != a4 - assert ResultType.Variance(observable=Observable.Y(), target=0) != ResultType.Expectation( - observable=Observable.Y(), target=0 - ) - - -def test_obs_rt_repr(): - a1 = ObservableResultType(ascii_symbols=["Obs"], observable=Observable.X(), target=0) - assert ( - str(a1) - == "ObservableResultType(observable=X('qubit_count': 1), target=QubitSet([Qubit(0)]))" - ) - - -@pytest.mark.parametrize( - "ir_type, serialization_properties, expected_exception, expected_message", - [ - (IRType.JAQCD, None, NotImplementedError, "to_jaqcd has not been implemented yet."), - (IRType.OPENQASM, None, NotImplementedError, "to_openqasm has not been implemented yet."), - ("invalid-ir-type", None, ValueError, "Supplied ir_type invalid-ir-type is not supported."), - ( - IRType.OPENQASM, - "invalid-serialization-properties", - ValueError, - "serialization_properties must be of type OpenQASMSerializationProperties for " - "IRType.OPENQASM.", - ), - ], -) -def test_result_type_to_ir( - ir_type, serialization_properties, expected_exception, expected_message, result_type -): - with pytest.raises(expected_exception) as exc: - result_type.to_ir(ir_type, serialization_properties=serialization_properties) - assert exc.value.args[0] == expected_message - - -# Observable Result Type with Params - - -def test_expectation_init_value_error_target_adjoint_gradient(): - tensor_operation_error = re.escape( - "Observable TensorProduct(X('qubit_count': 1), " - "Y('qubit_count': 1)) must only operate on 1 qubit for target=None" - ) - with pytest.raises(ValueError, match=tensor_operation_error): - ObservableParameterResultType( - ascii_symbols=["Obs", "Obs"], - observable=Observable.X() @ Observable.Y(), - target=[], - parameters=["alpha"], - ) - - -def test_expectation_init_value_error_ascii_symbols_adjoint_gradient(): - ascii_and_obs_qubit_count_mismatch = ( - "Observable's qubit count and the number of ASCII symbols must be equal" - ) - with pytest.raises(ValueError, match=ascii_and_obs_qubit_count_mismatch): - ObservableParameterResultType( - ascii_symbols=["Obs"], - observable=Observable.X() @ Observable.Y(), - target=[1, 2], - parameters=[], - ) - - -def test_obs_rt_init_value_error_qubit_count_adjoint_gradient(): - obs_and_target_count_mismatch = re.escape( - "Observable's qubit count 1 and the size of the target " - "qubit set QubitSet([Qubit(0), Qubit(1)]) must be equal" - ) - with pytest.raises(ValueError, match=obs_and_target_count_mismatch): - ObservableParameterResultType( - ascii_symbols=["Obs"], observable=Observable.X(), target=[0, 1] - ) - - -def test_valid_result_type_for__adjoint_gradient(): - ObservableParameterResultType( - ascii_symbols=["Obs", "Obs"], - observable=Observable.X() @ Observable.Y(), - target=[0, 1], - parameters=["alpha", FreeParameter("beta")], - ) - - -def test_obs_rt_repr_adjoint_gradient(): - a1 = ObservableParameterResultType( - ascii_symbols=["Obs"], observable=Observable.X(), target=0, parameters=["alpha"] - ) - assert ( - str(a1) == "ObservableParameterResultType(observable=X('qubit_count': 1), " - "target=QubitSet([Qubit(0)]), parameters=['alpha'])" - ) diff --git a/test/unit_tests/braket/circuits/test_result_types.py b/test/unit_tests/braket/circuits/test_result_types.py deleted file mode 100644 index 5f76eeaf..00000000 --- a/test/unit_tests/braket/circuits/test_result_types.py +++ /dev/null @@ -1,386 +0,0 @@ -# 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 braket.ir.jaqcd as ir -from braket.circuits import Circuit, Observable, ResultType -from braket.circuits.free_parameter import FreeParameter -from braket.circuits.result_type import ObservableParameterResultType -from braket.circuits.result_types import ObservableResultType -from braket.circuits.serialization import ( - IRType, - OpenQASMSerializationProperties, - QubitReferenceType, -) - -testdata = [ - (ResultType.StateVector, "state_vector", ir.StateVector, {}, {}), - (ResultType.DensityMatrix, "density_matrix", ir.DensityMatrix, {}, {}), - ( - ResultType.DensityMatrix, - "density_matrix", - ir.DensityMatrix, - {"target": [0, 1]}, - {"targets": [0, 1]}, - ), - (ResultType.Amplitude, "amplitude", ir.Amplitude, {"state": ["0"]}, {"states": ["0"]}), - ( - ResultType.Probability, - "probability", - ir.Probability, - {"target": [0, 1]}, - {"targets": [0, 1]}, - ), - ( - ResultType.Probability, - "probability", - ir.Probability, - {"target": None}, - {}, - ), - ( - ResultType.Expectation, - "expectation", - ir.Expectation, - {"observable": Observable.Z(), "target": [0]}, - {"observable": ["z"], "targets": [0]}, - ), - ( - ResultType.Expectation, - "expectation", - ir.Expectation, - {"observable": Observable.Z() @ Observable.I() @ Observable.X(), "target": [0, 1, 2]}, - {"observable": ["z", "i", "x"], "targets": [0, 1, 2]}, - ), - ( - ResultType.Expectation, - "expectation", - ir.Expectation, - {"observable": Observable.Hermitian(matrix=Observable.I().to_matrix()), "target": [0]}, - {"observable": [[[[1.0, 0], [0, 0]], [[0, 0], [1.0, 0]]]], "targets": [0]}, - ), - ( - ResultType.Expectation, - "expectation", - ir.Expectation, - {"observable": Observable.Hermitian(matrix=Observable.I().to_matrix()), "target": None}, - {"observable": [[[[1.0, 0], [0, 0]], [[0, 0], [1.0, 0]]]]}, - ), - ( - ResultType.Sample, - "sample", - ir.Sample, - {"observable": Observable.Z(), "target": [0]}, - {"observable": ["z"], "targets": [0]}, - ), - ( - ResultType.Sample, - "sample", - ir.Sample, - {"observable": Observable.Z(), "target": None}, - {"observable": ["z"]}, - ), - ( - ResultType.Variance, - "variance", - ir.Variance, - {"observable": Observable.Z(), "target": [0]}, - {"observable": ["z"], "targets": [0]}, - ), - ( - ResultType.Variance, - "variance", - ir.Variance, - {"observable": Observable.Z(), "target": None}, - {"observable": ["z"]}, - ), - ( - ResultType.AdjointGradient, - "adjoint_gradient", - ir.AdjointGradient, - {"observable": Observable.Z(), "target": None, "parameters": None}, - {"observable": ["z"]}, - ), - ( - ResultType.AdjointGradient, - "adjoint_gradient", - ir.AdjointGradient, - {"observable": Observable.Z(), "target": [0], "parameters": ["alpha", "beta"]}, - {"observable": ["z"], "targets": [0], "parameters": ["alpha", FreeParameter("beta")]}, - ), - ( - ResultType.AdjointGradient, - "adjoint_gradient", - ir.AdjointGradient, - { - "observable": Observable.Hermitian(matrix=Observable.Z().to_matrix()), - "target": [0], - "parameters": ["alpha", "beta"], - }, - { - "observable": [[[[1.0, 0], [0, 0]], [[0, 0], [-1.0, 0]]]], - "targets": [0], - "parameters": ["alpha", "beta"], - }, - ), -] - - -@pytest.mark.parametrize("testclass,subroutine_name,irclass,input,ir_input", testdata) -def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): - if testclass == ResultType.AdjointGradient: - jaqcd_not_implemented = "to_jaqcd has not been implemented yet." - with pytest.raises(NotImplementedError, match=jaqcd_not_implemented): - testclass(**input).to_ir() - else: - expected = irclass(**ir_input) - actual = testclass(**input).to_ir() - assert actual == expected - - -@pytest.mark.parametrize( - "result_type, serialization_properties, expected_ir", - [ - ( - ResultType.Expectation(Observable.I(), target=0), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result expectation i(q[0])", - ), - ( - ResultType.Expectation(Observable.I()), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result expectation i all", - ), - ( - ResultType.StateVector(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result state_vector", - ), - ( - ResultType.DensityMatrix(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result density_matrix all", - ), - ( - ResultType.DensityMatrix([0, 2]), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result density_matrix q[0], q[2]", - ), - ( - ResultType.DensityMatrix(0), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "#pragma braket result density_matrix $0", - ), - ( - ResultType.Amplitude(["01", "10"]), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - '#pragma braket result amplitude "01", "10"', - ), - ( - ResultType.Probability(), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result probability all", - ), - ( - ResultType.Probability([0, 2]), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result probability q[0], q[2]", - ), - ( - ResultType.Probability(0), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "#pragma braket result probability $0", - ), - ( - ResultType.Sample(Observable.I(), target=0), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result sample i(q[0])", - ), - ( - ResultType.Sample(Observable.I()), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result sample i all", - ), - ( - ResultType.Variance(Observable.I(), target=0), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result variance i(q[0])", - ), - ( - ResultType.Variance(Observable.I()), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result variance i all", - ), - ( - ResultType.AdjointGradient(Observable.I(), target=0, parameters=["alpha", "beta"]), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result adjoint_gradient expectation(i(q[0])) alpha, beta", - ), - ( - ResultType.AdjointGradient(Observable.I(), target=0, parameters=["alpha"]), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result adjoint_gradient expectation(i(q[0])) alpha", - ), - ( - ResultType.AdjointGradient( - Observable.H() @ Observable.I(), - target=[0, 1], - parameters=[FreeParameter("alpha"), "beta", FreeParameter("gamma")], - ), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result adjoint_gradient expectation(h(q[0]) @ i(q[1])) " - "alpha, beta, gamma", - ), - ( - ResultType.AdjointGradient( - Observable.H() @ Observable.I() + 2 * Observable.Z(), - target=[[0, 1], [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.I(), target=0, parameters=[]), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result adjoint_gradient expectation(i(q[0])) all", - ), - ( - ResultType.AdjointGradient( - Observable.X() @ Observable.Y(), target=[0, 1], parameters=[] - ), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result adjoint_gradient expectation(x(q[0]) @ y(q[1])) all", - ), - ( - ResultType.AdjointGradient( - Observable.Hermitian(matrix=Observable.I().to_matrix()), target=0, parameters=[] - ), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), - "#pragma braket result adjoint_gradient expectation(hermitian([[1+0im, 0im], " - "[0im, 1+0im]]) q[0]) all", - ), - ], -) -def test_result_to_ir_openqasm(result_type, serialization_properties, expected_ir): - assert ( - result_type.to_ir(IRType.OPENQASM, serialization_properties=serialization_properties) - == expected_ir - ) - - -@pytest.mark.parametrize("testclass,subroutine_name,irclass,input,ir_input", testdata) -def test_result_subroutine(testclass, subroutine_name, irclass, input, ir_input): - subroutine = getattr(Circuit(), subroutine_name) - assert subroutine(**input) == Circuit(testclass(**input)) - - -@pytest.mark.parametrize("testclass,subroutine_name,irclass,input,ir_input", testdata) -def test_result_equality(testclass, subroutine_name, irclass, input, ir_input): - a1 = testclass(**input) - a2 = a1.copy() - assert a1 == a2 - assert a1 is not a2 - - -# Amplitude - - -@pytest.mark.parametrize( - "state", - ((["2", "11"]), ([1, 0]), ([0.1, 0]), ("-0", "1"), (["", ""]), (None), ([None, None]), ("10")), -) -def test_amplitude_init_invalid_state_value_error(state): - with pytest.raises(ValueError): - ResultType.Amplitude(state=state) - - -def test_amplitude_equality(): - a1 = ResultType.Amplitude(state=["0", "1"]) - a2 = ResultType.Amplitude(state=["0", "1"]) - a3 = ResultType.Amplitude(state=["01", "11", "10"]) - a4 = "hi" - assert a1 == a2 - assert a1 != a3 - assert a1 != a4 - - -# Probability - - -def test_probability_equality(): - a1 = ResultType.Probability([0, 1]) - a2 = ResultType.Probability([0, 1]) - a3 = ResultType.Probability([0, 1, 2]) - a4 = "hi" - assert a1 == a2 - assert a1 != a3 - assert a1 != a4 - - -# Expectation - - -def test_expectation_parent_class(): - assert isinstance( - ResultType.Expectation(observable=Observable.X(), target=0), ObservableResultType - ) - - -# Sample - - -def test_sample_parent_class(): - assert isinstance(ResultType.Sample(observable=Observable.X(), target=0), ObservableResultType) - - -# Variance - - -def test_variance_parent_class(): - assert isinstance( - ResultType.Variance(observable=Observable.X(), target=0), ObservableResultType - ) - - -# AdjointGradient - - -def test_adjoint_gradient_parent_class(): - assert isinstance( - ResultType.AdjointGradient( - observable=Observable.X(), target=0, parameters=["alpha", FreeParameter("beta")] - ), - ObservableParameterResultType, - ) - - -@pytest.mark.parametrize( - "target", - ( - [[0], [0, 1], [2]], - [[0, 1], [0, 1]], - ), -) -def test_incorrect_target_adjoint_gradient(target): - match = ( - "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." - ) - with pytest.raises(ValueError, match=match): - ResultType.AdjointGradient( - 2 * Observable.Z() + 3 * Observable.X() @ Observable.Y(), - target, - ) diff --git a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py deleted file mode 100644 index 268c53a9..00000000 --- a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py +++ /dev/null @@ -1,1121 +0,0 @@ -# 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, - Noise, - 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) - - -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) - - -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) diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py deleted file mode 100644 index a7e8bfe1..00000000 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ /dev/null @@ -1,748 +0,0 @@ -# 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 textwrap -import warnings -from typing import Any, Optional -from unittest.mock import Mock, patch - -import pytest -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 -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, 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 -from braket.task_result import AnnealingTaskResult, GateModelTaskResult -from braket.task_result.analog_hamiltonian_simulation_task_result_v1 import ( - AnalogHamiltonianSimulationTaskResult, -) -from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult -from braket.tasks.analog_hamiltonian_simulation_quantum_task_result import ( - 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", - }, - "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", - }, - "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, - }, - }, - }, - } -) - -AHS_RESULT = AnalogHamiltonianSimulationTaskResult( - **{ - "taskMetadata": { - "id": "rydberg", - "shots": 100, - "deviceId": "rydbergLocalSimulator", - }, - } -) - - -class DummyCircuitSimulator(BraketSimulator): - def run( - self, - program: ir.jaqcd.Program, - qubits: int, - shots: Optional[int], - inputs: Optional[dict[str, float]], - *args, - **kwargs, - ) -> dict[str, Any]: - self._shots = shots - self._qubits = qubits - return GATE_MODEL_RESULT - - @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.openqasm.program": { - "actionType": "braket.ir.openqasm.program", - "version": ["1"], - }, - "braket.ir.jaqcd.program": { - "actionType": "braket.ir.jaqcd.program", - "version": ["1"], - }, - }, - "deviceParameters": {}, - } - ) - - -class DummyJaqcdSimulator(BraketSimulator): - def run( - self, program: ir.jaqcd.Program, qubits: int, shots: Optional[int], *args, **kwargs - ) -> dict[str, Any]: - if not isinstance(program, ir.jaqcd.Program): - raise TypeError("Not a Jaqcd program") - self._shots = shots - self._qubits = qubits - return GATE_MODEL_RESULT - - @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"], - }, - }, - "deviceParameters": {}, - } - ) - - def assert_shots(self, shots): - assert self._shots == shots - - def assert_qubits(self, qubits): - assert self._qubits == qubits - - -class DummyProgramSimulator(BraketSimulator): - def run( - self, - openqasm_ir: Program, - shots: int = 0, - batch_size: int = 1, - ) -> GateModelTaskResult: - return GATE_MODEL_RESULT - - @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"], - } - }, - "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): - def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> AnnealingTaskResult: - return ANNEALING_RESULT - - @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"], - } - }, - "deviceParameters": {}, - } - ) - - -class DummyRydbergSimulator(BraketSimulator): - def run( - self, program: AnalogHamiltonianSimulation, *args, **kwargs - ) -> AnalogHamiltonianSimulationTaskResult: - return AHS_RESULT - - @property - def properties(self) -> DeviceCapabilities: - properties = { - "service": { - "executionWindows": [ - { - "executionDay": "Everyday", - "windowStartHour": "00:00", - "windowEndHour": "23:59:59", - } - ], - "shotsRange": [0, 10], - }, - "action": {"braket.ir.ahs.program": {}}, - } - - RydbergSimulatorDeviceCapabilities = create_model( - "RydbergSimulatorDeviceCapabilities", **properties - ) - - 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, -} - -mock_ahs_program = AnalogHamiltonianSimulation( - register=AtomArrangement(), hamiltonian=Hamiltonian() -) - - -def test_load_from_entry_point(): - sim = LocalSimulator("dummy_oq3") - task = sim.run(Circuit().h(0).cnot(0, 1), 10) - assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) - - -def test_run_gate_model(): - dummy = DummyProgramSimulator() - sim = LocalSimulator(dummy) - task = sim.run(Circuit().h(0).cnot(0, 1), 10) - assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) - - -def test_batch_circuit(): - dummy = DummyProgramSimulator() - theta = FreeParameter("theta") - task = Circuit().rx(angle=theta, target=0) - device = LocalSimulator(dummy) - num_tasks = 3 - circuits = [task for _ in range(num_tasks)] - inputs = [{"theta": i} for i in range(num_tasks)] - batch = device.run_batch(circuits, inputs=inputs, shots=10) - assert len(batch.results()) == num_tasks - for x in batch.results(): - assert x == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) - - -def test_batch_with_max_parallel(): - dummy = DummyProgramSimulator() - task = Circuit().h(0).cnot(0, 1) - device = LocalSimulator(dummy) - num_tasks = 3 - circuits = [task for _ in range(num_tasks)] - batch = device.run_batch(circuits, shots=10, max_parallel=2) - assert len(batch.results()) == num_tasks - for x in batch.results(): - assert x == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) - - -def test_batch_with_annealing_problems(): - dummy = DummyAnnealingSimulator() - problem = Problem(ProblemType.ISING) - device = LocalSimulator(dummy) - num_tasks = 3 - problems = [problem for _ in range(num_tasks)] - batch = device.run_batch(problems, shots=10) - assert len(batch.results()) == num_tasks - for x in batch.results(): - assert x == AnnealingQuantumTaskResult.from_object(ANNEALING_RESULT) - - -def test_batch_circuit_without_inputs(): - dummy = DummyProgramSimulator() - bell = Circuit().h(0).cnot(0, 1) - device = LocalSimulator(dummy) - num_tasks = 3 - circuits = [bell for _ in range(num_tasks)] - batch = device.run_batch(circuits, shots=10) - assert len(batch.results()) == num_tasks - for x in batch.results(): - assert x == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) - - -def test_batch_circuit_with_unbound_parameters(): - dummy = DummyProgramSimulator() - device = LocalSimulator(dummy) - theta = FreeParameter("theta") - task = Circuit().rx(angle=theta, target=0) - inputs = {"beta": 0.2} - cannot_execute_with_unbound = "Cannot execute circuit with unbound parameters: {'theta'}" - with pytest.raises(ValueError, match=cannot_execute_with_unbound): - device.run_batch(task, inputs=inputs, shots=10) - - -def test_batch_circuit_with_single_task(): - dummy = DummyProgramSimulator() - bell = Circuit().h(0).cnot(0, 1) - device = LocalSimulator(dummy) - batch = device.run_batch(bell, shots=10) - assert len(batch.results()) == 1 - assert batch.results()[0] == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) - - -def test_batch_circuit_with_task_and_input_mismatch(): - dummy = DummyProgramSimulator() - bell = Circuit().h(0).cnot(0, 1) - device = LocalSimulator(dummy) - num_tasks = 3 - circuits = [bell for _ in range(num_tasks)] - inputs = [{} for _ in range(num_tasks - 1)] - with pytest.raises(ValueError): - device.run_batch(circuits, inputs=inputs, shots=10) - - -def test_run_gate_model_inputs(): - dummy = DummyProgramSimulator() - dummy.run = Mock(return_value=GATE_MODEL_RESULT) - sim = LocalSimulator(dummy) - circuit = Circuit().rx(0, FreeParameter("theta")) - 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];", - ) - ), - inputs={"theta": 2}, - ), - 10, - ) - assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) - - -def test_run_program_model_inputs(): - dummy = DummyProgramSimulator() - dummy.run = Mock(return_value=GATE_MODEL_RESULT) - sim = LocalSimulator(dummy) - inputs = {"theta": 2} - source_string = ( - "OPENQASM 3.0;", - "input float theta;", - "bit[1] b;", - "qubit[1] q;", - "rx(theta) q[0];", - "b[0] = measure q[0];", - ) - program = Program.construct(source="\n".join(source_string), inputs=inputs) - update_inputs = {"beta": 3} - 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) - assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) - - -def test_run_jaqcd_only(): - dummy = DummyJaqcdSimulator() - sim = LocalSimulator(dummy) - task = sim.run(Circuit().h(0).cnot(0, 1), 10) - dummy.assert_shots(10) - dummy.assert_qubits(2) - assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) - - -def test_run_program_model(): - dummy = DummyProgramSimulator() - sim = LocalSimulator(dummy) - task = sim.run( - Program( - 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() - sim = LocalSimulator(dummy) - sim.run(Circuit().h(0).cnot(0, 1)) - - -def test_run_annealing(): - sim = LocalSimulator(DummyAnnealingSimulator()) - task = sim.run(Problem(ProblemType.ISING)) - assert task.result() == AnnealingQuantumTaskResult.from_object(ANNEALING_RESULT) - - -def test_run_ahs(): - sim = LocalSimulator(DummyRydbergSimulator()) - task = sim.run(mock_ahs_program) - assert task.result() == AnalogHamiltonianSimulationQuantumTaskResult.from_object(AHS_RESULT) - - task = sim.run(mock_ahs_program.to_ir()) - assert task.result() == AnalogHamiltonianSimulationQuantumTaskResult.from_object(AHS_RESULT) - - -def test_registered_backends(): - assert LocalSimulator.registered_backends() == { - "dummy", - "dummy_oq3", - "dummy_jaqcd", - "dummy_oq3_dm", - } - - -@pytest.mark.xfail(raises=TypeError) -def test_init_invalid_backend_type(): - LocalSimulator(1234) - - -@pytest.mark.xfail(raises=ValueError) -def test_init_unregistered_backend(): - LocalSimulator("foo") - - -@pytest.mark.xfail(raises=NotImplementedError) -def test_run_unsupported_type(): - sim = LocalSimulator(DummyCircuitSimulator()) - sim.run("I'm unsupported") - - -@pytest.mark.xfail(raises=NotImplementedError) -def test_run_annealing_unsupported(): - sim = LocalSimulator(DummyCircuitSimulator()) - sim.run(Problem(ProblemType.ISING)) - - -@pytest.mark.xfail(raises=NotImplementedError) -def test_run_qubit_gate_unsupported(): - sim = LocalSimulator(DummyAnnealingSimulator()) - sim.run(Circuit().h(0).cnot(0, 1), 1000) - - -def test_properties(): - dummy = DummyCircuitSimulator() - 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 diff --git a/test/unit_tests/braket/jobs/job_module.py b/test/unit_tests/braket/jobs/job_module.py deleted file mode 100644 index 5dbc56d0..00000000 --- a/test/unit_tests/braket/jobs/job_module.py +++ /dev/null @@ -1,2 +0,0 @@ -def some_helper(): - return "success" diff --git a/test/unit_tests/braket/jobs/local/test_local_job.py b/test/unit_tests/braket/jobs/local/test_local_job.py deleted file mode 100644 index 185b059e..00000000 --- a/test/unit_tests/braket/jobs/local/test_local_job.py +++ /dev/null @@ -1,215 +0,0 @@ -# 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, mock_open, patch - -import pytest - -from braket.jobs.local.local_job import LocalQuantumJob - - -@pytest.fixture -def aws_session(): - _aws_session = Mock() - return _aws_session - - -@pytest.fixture -def job_results(): - return {"dataFormat": "plaintext", "dataDictionary": {"some_results": {"excellent": "here"}}} - - -@pytest.fixture -def run_log(): - test_log = ( - "This is a multi-line log.\n" - "This is the next line.\n" - "Metrics - timestamp=1633027264.5406773; Cost=-4.034; iteration_number=0;\n" - "Metrics - timestamp=1633027288.6284382; Cost=-3.957; iteration_number=1;\n" - ) - return test_log - - -@pytest.fixture -def test_envs(): - return {"Test": "Env"} - - -@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": {}, - } - ), - ], -) -@patch("braket.jobs.local.local_job.prepare_quantum_job") -@patch("braket.jobs.local.local_job.retrieve_image") -@patch("braket.jobs.local.local_job.setup_container") -@patch("braket.jobs.local.local_job._LocalJobContainer") -@patch("os.path.isdir") -def test_create( - mock_dir, - mock_container, - mock_setup, - mock_retrieve_image, - mock_prepare_job, - aws_session, - creation_kwargs, - job_results, - run_log, - test_envs, -): - with patch("builtins.open", mock_open()) as file_open: - mock_dir.return_value = False - mock_prepare_job.return_value = creation_kwargs - - mock_container_open = mock_container.return_value.__enter__.return_value - mock_container_open.run_log = run_log - file_read = file_open() - file_read.read.return_value = json.dumps(job_results) - mock_setup.return_value = test_envs - - job = LocalQuantumJob.create( - device=Mock(), - source_module=Mock(), - entry_point=Mock(), - image_uri=Mock(), - job_name=Mock(), - code_location=Mock(), - role_arn=Mock(), - hyperparameters=Mock(), - input_data=Mock(), - output_data_config=Mock(), - checkpoint_config=Mock(), - aws_session=aws_session, - ) - assert job.name == "Test-Job-Name" - assert job.arn == "local:job/Test-Job-Name" - assert job.state() == "COMPLETED" - assert job.run_log == run_log - assert job.metadata() is None - assert job.cancel() is None - assert job.download_result() is None - assert job.logs() is None - assert job.result() == job_results["dataDictionary"] - assert job.metrics() == { - "Cost": [-4.034, -3.957], - "iteration_number": [0.0, 1.0], - "timestamp": [1633027264.5406773, 1633027288.6284382], - } - mock_setup.assert_called_with(mock_container_open, aws_session, **creation_kwargs) - mock_container_open.run_local_job.assert_called_with(test_envs) - - -def test_create_invalid_arg(): - unexpected_kwarg = "create\\(\\) got an unexpected keyword argument 'wait_until_complete'" - with pytest.raises(TypeError, match=unexpected_kwarg): - LocalQuantumJob.create( - device="device", - source_module="source", - wait_until_complete=True, - ) - - -@patch("os.path.isdir") -def test_read_runlog_file(mock_dir): - mock_dir.return_value = True - with patch("builtins.open", mock_open()) as file_open: - file_read = file_open() - file_read.read.return_value = "Test Log" - job = LocalQuantumJob("local:job/Fake-Job") - assert job.run_log == "Test Log" - - -@patch("braket.jobs.local.local_job.prepare_quantum_job") -@patch("os.path.isdir") -def test_create_existing_job(mock_dir, mock_prepare_job, aws_session): - mock_dir.return_value = True - mock_prepare_job.return_value = { - "jobName": "Test-Job-Name", - "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, - "checkpointConfig": {"localPath": "test/local/path/"}, - } - dir_already_exists = ( - "A local directory called Test-Job-Name already exists. Please use a different job name." - ) - with pytest.raises(ValueError, match=dir_already_exists): - LocalQuantumJob.create( - device=Mock(), - source_module=Mock(), - entry_point=Mock(), - image_uri=Mock(), - job_name=Mock(), - code_location=Mock(), - role_arn=Mock(), - hyperparameters=Mock(), - input_data=Mock(), - output_data_config=Mock(), - checkpoint_config=Mock(), - aws_session=aws_session, - ) - - -def test_invalid_arn(): - invalid_arn = "Arn Invalid-Arn is not a valid local job arn" - with pytest.raises(ValueError, match=invalid_arn): - LocalQuantumJob("Invalid-Arn") - - -def test_missing_job_dir(): - missing_dir = "Unable to find local job results for Missing-Dir" - with pytest.raises(ValueError, match=missing_dir): - LocalQuantumJob("local:job/Missing-Dir") - - -@patch("os.path.isdir") -def test_missing_runlog_file(mock_dir): - mock_dir.return_value = True - job = LocalQuantumJob("local:job/Fake-Dir") - no_file = "Unable to find logs in the local job directory Fake-Dir." - with pytest.raises(ValueError, match=no_file): - job.run_log - - -@patch("os.path.isdir") -def test_missing_results_file(mock_dir): - mock_dir.return_value = True - job = LocalQuantumJob("local:job/Fake-Dir") - no_results = "Unable to find results in the local job directory Fake-Dir." - with pytest.raises(ValueError, match=no_results): - job.result() 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 deleted file mode 100644 index 47efceef..00000000 --- a/test/unit_tests/braket/jobs/local/test_local_job_container.py +++ /dev/null @@ -1,496 +0,0 @@ -# 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 base64 -import subprocess -from pathlib import Path, PurePosixPath -from unittest.mock import Mock, patch - -import pytest - -from braket.jobs.local.local_job_container import _LocalJobContainer - - -@pytest.fixture -def repo_uri(): - return "012345678901.dkr.ecr.us-west-2.amazonaws.com" - - -@pytest.fixture -def image_uri(repo_uri): - return f"{repo_uri}/my-repo:my-tag" - - -@pytest.fixture -def aws_session(): - _aws_session = Mock() - return _aws_session - - -@pytest.fixture -def popen_result(): - mock = Mock() - mock.stdout.readline.side_effect = [ - str.encode("this\n"), - str.encode("is a\n"), - str.encode("test\n"), - ] - mock.poll.side_effect = [None, None, 0] - return mock - - -@patch("subprocess.check_output") -@patch("subprocess.run") -def test_start_and_stop(mock_run, mock_check_output, image_uri, aws_session): - local_image_name = "LocalImageName" - running_container_name = "RunningContainer" - mock_check_output.side_effect = [ - str.encode(local_image_name), - str.encode(running_container_name), - ] - 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"] - ) - assert mock_check_output.call_count == 2 - mock_run.assert_any_call(["docker", "stop", running_container_name]) - assert mock_run.call_count == 1 - - -@pytest.mark.parametrize( - "forced_update, check_output, local_image_name", - [ - ( - False, - [ - str.encode(""), # This means that the container image does not exist locally. - str.encode("LocalImageName"), - str.encode("RunningContainer"), - ], - "LocalImageName", - ), - ( - True, - [ - str.encode(""), # This means that the container image does not exist locally. - str.encode("LocalImageName"), - str.encode("RunningContainer"), - ], - "LocalImageName", - ), - ( - True, # When force update is true, we'll pull containers, even if they exist locally. - [ - str.encode("PreUpdateName"), # This means that the container image exists locally. - str.encode("PostUpdateName"), # This means that the container image exists locally. - str.encode("RunningContainer"), - ], - "PostUpdateName", - ), - ], -) -@patch("subprocess.check_output") -@patch("subprocess.run") -def test_pull_container( - mock_run, - mock_check_output, - repo_uri, - image_uri, - aws_session, - forced_update, - check_output, - local_image_name, -): - running_container_name = "RunningContainer" - test_token = "Test Token" - mock_check_output.side_effect = check_output - aws_session.ecr_client.get_authorization_token.return_value = { - "authorizationData": [{"authorizationToken": base64.b64encode(str.encode(test_token))}] - } - with _LocalJobContainer( - image_uri=image_uri, aws_session=aws_session, force_update=forced_update - ): - 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"] - ) - 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]) - mock_run.assert_any_call(["docker", "stop", running_container_name]) - assert mock_run.call_count == 3 - - -@patch("subprocess.check_output") -@patch("subprocess.run") -def test_pull_container_forced_update_invalid_name( - mock_run, mock_check_output, repo_uri, aws_session -): - local_image_name = "LocalImageName" - running_container_name = "RunningContainer" - mock_logger = Mock() - mock_check_output.side_effect = [ - str.encode(local_image_name), - str.encode(running_container_name), - ] - with _LocalJobContainer( - image_uri=local_image_name, aws_session=aws_session, force_update=True, logger=mock_logger - ): - 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"] - ) - assert mock_check_output.call_count == 2 - mock_run.assert_any_call(["docker", "stop", running_container_name]) - assert mock_run.call_count == 1 - - -@patch("subprocess.check_output") -@patch("subprocess.run") -@patch("subprocess.Popen") -def test_run_job_success( - mock_popen, mock_run, mock_check_output, repo_uri, image_uri, aws_session, popen_result -): - local_image_name = "LocalImageName" - running_container_name = "RunningContainer" - env_variables = { - "ENV0": "VALUE0", - "ENV1": "VALUE1", - } - run_program_name = "Run Program Name" - mock_check_output.side_effect = [ - str.encode(local_image_name), - str.encode(running_container_name), - str.encode(run_program_name), - ] - mock_popen.return_value = popen_result - with _LocalJobContainer(image_uri, aws_session) as container: - 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"] - ) - assert mock_check_output.call_count == 3 - mock_popen.assert_called_with( - [ - "docker", - "exec", - "-w", - "/opt/ml/code/", - "-e", - "ENV0=VALUE0", - "-e", - "ENV1=VALUE1", - running_container_name, - "python", - run_program_name, - ], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - mock_run.assert_called_with(["docker", "stop", running_container_name]) - - -@patch("subprocess.check_output") -@patch("subprocess.run") -@patch("subprocess.Popen") -def test_run_customer_script_fails( - mock_popen, mock_run, mock_check_output, repo_uri, image_uri, aws_session, popen_result -): - local_image_name = "LocalImageName" - running_container_name = "RunningContainer" - env_variables = { - "ENV0": "VALUE0", - "ENV1": "VALUE1", - } - run_program_name = "Run Program Name" - mock_check_output.side_effect = [ - str.encode(local_image_name), - str.encode(running_container_name), - str.encode(run_program_name), - ] - popen_result.poll.side_effect = [None, None, 400] - mock_popen.return_value = popen_result - with _LocalJobContainer(image_uri, aws_session) as container: - 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"] - ) - assert mock_check_output.call_count == 3 - mock_popen.assert_called_with( - [ - "docker", - "exec", - "-w", - "/opt/ml/code/", - "-e", - "ENV0=VALUE0", - "-e", - "ENV1=VALUE1", - running_container_name, - "python", - run_program_name, - ], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - mock_run.assert_called_with(["docker", "stop", running_container_name]) - - -@patch("subprocess.check_output") -@patch("subprocess.run") -@patch("subprocess.Popen") -def test_running_throws_exception( - mock_popen, mock_run, mock_check_output, repo_uri, image_uri, aws_session, popen_result -): - mock_logger = Mock() - local_image_name = "LocalImageName" - running_container_name = "RunningContainer" - env_variables = { - "ENV0": "VALUE0", - "ENV1": "VALUE1", - } - run_program_name = "Run Program Name" - mock_check_output.side_effect = [ - str.encode(local_image_name), - str.encode(running_container_name), - str.encode(run_program_name), - ] - expected_exception = Exception("Test Error") - mock_popen.side_effect = [expected_exception, None] - with _LocalJobContainer(image_uri, aws_session, mock_logger) as container: - container.run_local_job(env_variables) - 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) - - -@patch("subprocess.check_output") -@patch("subprocess.run") -def test_make_dir(mock_run, mock_check_output, repo_uri, image_uri, aws_session): - local_image_name = "LocalImageName" - running_container_name = "RunningContainer" - test_dir_path = "/test/dir/path" - mock_check_output.side_effect = [ - str.encode(local_image_name), - str.encode(running_container_name), - str.encode(""), - ] - 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] - ) - assert mock_check_output.call_count == 3 - mock_run.assert_any_call(["docker", "stop", running_container_name]) - assert mock_run.call_count == 1 - - -@patch("subprocess.check_output") -@patch("subprocess.run") -def test_copy_to(mock_run, mock_check_output, repo_uri, image_uri, aws_session): - local_image_name = "LocalImageName" - running_container_name = "RunningContainer" - source_path = str(Path("test", "source", "dir", "path", "srcfile.txt")) - dest_path = str(PurePosixPath("test", "dest", "dir", "path", "dstfile.txt")) - mock_check_output.side_effect = [ - str.encode(local_image_name), - str.encode(running_container_name), - str.encode(""), - str.encode(""), - ] - 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}"] - ) - assert mock_check_output.call_count == 4 - mock_run.assert_any_call(["docker", "stop", running_container_name]) - assert mock_run.call_count == 1 - - -@patch("subprocess.check_output") -@patch("subprocess.run") -def test_copy_from(mock_run, mock_check_output, repo_uri, image_uri, aws_session): - local_image_name = "LocalImageName" - running_container_name = "RunningContainer" - source_path = "/test/source/dir/path/srcfile.txt" - dest_path = "/test/dest/dir/path/dstfile.txt" - mock_check_output.side_effect = [ - str.encode(local_image_name), - str.encode(running_container_name), - str.encode(""), - str.encode(""), - ] - 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] - ) - assert mock_check_output.call_count == 3 - mock_run.assert_any_call(["docker", "stop", running_container_name]) - assert mock_run.call_count == 1 - - -@patch("subprocess.check_output") -@patch("subprocess.run") -@pytest.mark.xfail(raises=ValueError) -def test_run_fails_no_program(mock_run, mock_check_output, repo_uri, image_uri, aws_session): - local_image_name = "LocalImageName" - running_container_name = "RunningContainer" - env_variables = { - "ENV0": "VALUE0", - "ENV1": "VALUE1", - } - mock_check_output.side_effect = [ - str.encode(local_image_name), - str.encode(running_container_name), - str.encode(""), - ] - with _LocalJobContainer(image_uri, aws_session) as container: - container.run_local_job(env_variables) - - -@patch("subprocess.check_output") -@patch("subprocess.run") -@pytest.mark.xfail(raises=subprocess.CalledProcessError) -def test_make_dir_fails(mock_run, mock_check_output, repo_uri, image_uri, aws_session): - local_image_name = "LocalImageName" - running_container_name = "RunningContainer" - test_dir_path = "/test/dir/path" - mock_check_output.side_effect = [ - str.encode(local_image_name), - str.encode(running_container_name), - subprocess.CalledProcessError("Test Error", "test", str.encode("test output")), - ] - with _LocalJobContainer(image_uri, aws_session) as container: - container.makedir(test_dir_path) - - -@patch("subprocess.check_output") -@patch("subprocess.run") -@pytest.mark.xfail(raises=subprocess.CalledProcessError) -def test_copy_to_fails(mock_run, mock_check_output, repo_uri, image_uri, aws_session): - local_image_name = "LocalImageName" - running_container_name = "RunningContainer" - source_path = "/test/source/dir/path/srcfile.txt" - dest_path = "/test/dest/dir/path/dstfile.txt" - mock_check_output.side_effect = [ - str.encode(local_image_name), - str.encode(running_container_name), - subprocess.CalledProcessError("Test Error", "test", str.encode("test output")), - ] - with _LocalJobContainer(image_uri, aws_session) as container: - container.copy_to(source_path, dest_path) - - -@patch("subprocess.check_output") -@patch("subprocess.run") -@pytest.mark.xfail(raises=subprocess.CalledProcessError) -def test_copy_from_fails(mock_run, mock_check_output, repo_uri, image_uri, aws_session): - local_image_name = "LocalImageName" - running_container_name = "RunningContainer" - source_path = "/test/source/dir/path/srcfile.txt" - dest_path = "/test/dest/dir/path/dstfile.txt" - mock_check_output.side_effect = [ - str.encode(local_image_name), - str.encode(running_container_name), - subprocess.CalledProcessError("Test Error", "test", str.encode("test output")), - ] - with _LocalJobContainer(image_uri, aws_session) as container: - container.copy_from(source_path, dest_path) - - -@patch("subprocess.check_output") -@patch("subprocess.run") -@pytest.mark.xfail(raises=ValueError) -def test_pull_fails_no_auth(mock_run, mock_check_output, repo_uri, image_uri, aws_session): - local_image_name = "LocalImageName" - running_container_name = "RunningContainer" - mock_check_output.side_effect = [ - str.encode(""), - str.encode(local_image_name), - str.encode(running_container_name), - ] - aws_session.ecr_client.get_authorization_token.return_value = {} - with _LocalJobContainer(image_uri, aws_session): - pass - - -@patch("subprocess.check_output") -@patch("subprocess.run") -@pytest.mark.xfail(raises=ValueError) -def test_pull_fails_invalid_uri(mock_run, mock_check_output, aws_session): - local_image_name = "LocalImageName" - running_container_name = "RunningContainer" - mock_check_output.side_effect = [ - str.encode(""), - str.encode(local_image_name), - str.encode(running_container_name), - ] - aws_session.ecr_client.get_authorization_token.return_value = {} - with _LocalJobContainer("TestURI", aws_session): - pass - - -@patch("subprocess.check_output") -@patch("subprocess.run") -@pytest.mark.xfail(raises=ValueError) -def test_pull_fails_unknown_reason(mock_run, mock_check_output, repo_uri, image_uri, aws_session): - test_token = "Test Token" - mock_check_output.side_effect = [ - str.encode(""), - str.encode(""), - ] - aws_session.ecr_client.get_authorization_token.return_value = { - "authorizationData": [{"authorizationToken": base64.b64encode(str.encode(test_token))}] - } - with _LocalJobContainer(image_uri, aws_session): - pass 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 deleted file mode 100644 index 51199055..00000000 --- a/test/unit_tests/braket/jobs/local/test_local_job_container_setup.py +++ /dev/null @@ -1,226 +0,0 @@ -# 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 pathlib import Path -from unittest.mock import Mock, mock_open, patch - -import pytest - -from braket.jobs.local.local_job_container_setup import setup_container - - -@pytest.fixture -def aws_session(): - _aws_session = Mock() - _aws_session.boto_session.get_credentials.return_value.access_key = "Test Access Key" - _aws_session.boto_session.get_credentials.return_value.secret_key = "Test Secret Key" - _aws_session.boto_session.get_credentials.return_value.token = None - _aws_session.region = "Test Region" - _aws_session.list_keys.side_effect = lambda bucket, prefix: [ - key - for key in [ - "input-dir/", - "input-dir/file-1.txt", - "input-dir/file-2.txt", - ] - if key.startswith(prefix) - ] - return _aws_session - - -@pytest.fixture -def container(): - _container = Mock() - return _container - - -@pytest.fixture -def creation_kwargs(): - return { - "algorithmSpecification": { - "scriptModeConfig": { - "entryPoint": "my_file:start_here", - "s3Uri": "s3://amazon-braket-jobs/job-path/my_file.py", - } - }, - "checkpointConfig": { - "localPath": "/opt/omega/checkpoints", - "s3Uri": "s3://amazon-braket-jobs/job-path/checkpoints", - }, - "outputDataConfig": {"s3Path": "s3://test_bucket/test_location/"}, - "deviceConfig": {"device": "test device ARN"}, - "jobName": "Test-Job-Name", - "roleArn": "arn:aws:iam::875981177017:role/AmazonBraketJobRole", - } - - -@pytest.fixture -def compressed_script_mode_config(): - return { - "scriptModeConfig": { - "entryPoint": "my_file:start_here", - "s3Uri": "s3://amazon-braket-jobs/job-path/my_archive.gzip", - "compressionType": "gzip", - } - } - - -@pytest.fixture -def expected_envs(): - return { - "AMZN_BRAKET_CHECKPOINT_DIR": "/opt/omega/checkpoints", - "AMZN_BRAKET_DEVICE_ARN": "test device ARN", - "AMZN_BRAKET_JOB_NAME": "Test-Job-Name", - "AMZN_BRAKET_JOB_RESULTS_DIR": "/opt/braket/model", - "AMZN_BRAKET_JOB_RESULTS_S3_PATH": "test_location/Test-Job-Name/output", - "AMZN_BRAKET_OUT_S3_BUCKET": "test_bucket", - "AMZN_BRAKET_SCRIPT_ENTRY_POINT": "my_file:start_here", - "AMZN_BRAKET_SCRIPT_S3_URI": "s3://amazon-braket-jobs/job-path/my_file.py", - "AMZN_BRAKET_TASK_RESULTS_S3_URI": "s3://test_bucket/jobs/Test-Job-Name/tasks", - "AWS_ACCESS_KEY_ID": "Test Access Key", - "AWS_DEFAULT_REGION": "Test Region", - "AWS_SECRET_ACCESS_KEY": "Test Secret Key", - } - - -@pytest.fixture -def input_data_config(): - return [ - # s3 prefix is a single file - { - "channelName": "single-file", - "dataSource": {"s3DataSource": {"s3Uri": "s3://input_bucket/input-dir/file-1.txt"}}, - }, - # s3 prefix is a directory no slash - { - "channelName": "directory-no-slash", - "dataSource": {"s3DataSource": {"s3Uri": "s3://input_bucket/input-dir"}}, - }, - # s3 prefix is a directory with slash - { - "channelName": "directory-slash", - "dataSource": {"s3DataSource": {"s3Uri": "s3://input_bucket/input-dir/"}}, - }, - # s3 prefix is a prefix for a directory - { - "channelName": "directory-prefix", - "dataSource": {"s3DataSource": {"s3Uri": "s3://input_bucket/input"}}, - }, - # s3 prefix is a prefix for multiple files - { - "channelName": "files-prefix", - "dataSource": {"s3DataSource": {"s3Uri": "s3://input_bucket/input-dir/file"}}, - }, - ] - - -def test_basic_setup(container, aws_session, creation_kwargs, expected_envs): - aws_session.parse_s3_uri.return_value = ["test_bucket", "test_location"] - envs = setup_container(container, aws_session, **creation_kwargs) - assert envs == expected_envs - container.makedir.assert_any_call("/opt/ml/model") - container.makedir.assert_any_call(expected_envs["AMZN_BRAKET_CHECKPOINT_DIR"]) - assert container.makedir.call_count == 2 - - -def test_compressed_script_mode( - container, aws_session, creation_kwargs, expected_envs, compressed_script_mode_config -): - creation_kwargs["algorithmSpecification"] = compressed_script_mode_config - expected_envs["AMZN_BRAKET_SCRIPT_S3_URI"] = "s3://amazon-braket-jobs/job-path/my_archive.gzip" - expected_envs["AMZN_BRAKET_SCRIPT_COMPRESSION_TYPE"] = "gzip" - aws_session.parse_s3_uri.return_value = ["test_bucket", "test_location"] - envs = setup_container(container, aws_session, **creation_kwargs) - assert envs == expected_envs - container.makedir.assert_any_call("/opt/ml/model") - container.makedir.assert_any_call(expected_envs["AMZN_BRAKET_CHECKPOINT_DIR"]) - assert container.makedir.call_count == 2 - - -@patch("json.dump") -@patch("tempfile.TemporaryDirectory") -def test_hyperparameters(tempfile, json, container, aws_session, creation_kwargs, expected_envs): - with patch("builtins.open", mock_open()): - tempfile.return_value.__enter__.return_value = "temporaryDir" - creation_kwargs["hyperParameters"] = {"test": "hyper"} - expected_envs["AMZN_BRAKET_HP_FILE"] = "/opt/braket/input/config/hyperparameters.json" - aws_session.parse_s3_uri.return_value = ["test_bucket", "test_location"] - envs = setup_container(container, aws_session, **creation_kwargs) - assert envs == expected_envs - container.makedir.assert_any_call("/opt/ml/model") - container.makedir.assert_any_call(expected_envs["AMZN_BRAKET_CHECKPOINT_DIR"]) - assert container.makedir.call_count == 2 - container.copy_to.assert_called_with( - os.path.join("temporaryDir", "hyperparameters.json"), - "/opt/ml/input/config/hyperparameters.json", - ) - - -def test_input(container, aws_session, creation_kwargs, input_data_config): - creation_kwargs.update({"inputDataConfig": input_data_config}) - setup_container(container, aws_session, **creation_kwargs) - download_locations = [call[0][1] for call in aws_session.download_from_s3.call_args_list] - expected_downloads = [ - Path("single-file", "file-1.txt"), - Path("directory-no-slash", "file-1.txt"), - Path("directory-no-slash", "file-2.txt"), - Path("directory-slash", "file-1.txt"), - Path("directory-slash", "file-2.txt"), - Path("directory-prefix", "input-dir", "file-1.txt"), - Path("directory-prefix", "input-dir", "file-2.txt"), - Path("files-prefix", "file-1.txt"), - Path("files-prefix", "file-2.txt"), - ] - - for download, expected_download in zip(download_locations, expected_downloads): - assert download.endswith(str(expected_download)) - - -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"}}, - } - ) - 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): - setup_container(container, aws_session, **creation_kwargs) - - -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"}}, - } - ) - 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): - setup_container(container, aws_session, **creation_kwargs) - - -def test_temporary_credentials(container, aws_session, creation_kwargs, expected_envs): - aws_session.boto_session.get_credentials.return_value.token = "Test Token" - expected_envs["AWS_SESSION_TOKEN"] = "Test Token" - aws_session.parse_s3_uri.return_value = ["test_bucket", "test_location"] - envs = setup_container(container, aws_session, **creation_kwargs) - assert envs == expected_envs - container.makedir.assert_any_call("/opt/ml/model") - container.makedir.assert_any_call(expected_envs["AMZN_BRAKET_CHECKPOINT_DIR"]) - assert container.makedir.call_count == 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 deleted file mode 100644 index 72f9c4db..00000000 --- a/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py +++ /dev/null @@ -1,139 +0,0 @@ -# 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 Mock, call, patch - -import pytest - -from braket.jobs.metrics_data import MetricsRetrievalError -from braket.jobs.metrics_data.cwl_insights_metrics_fetcher import CwlInsightsMetricsFetcher - - -@pytest.fixture -def aws_session(): - _aws_session = Mock() - return _aws_session - - -EXAMPLE_METRICS_LOG_LINES = [ - [ - {"field": "@timestamp", "value": "Test timestamp 0"}, - {"field": "@message", "value": "Test value 0"}, - ], - [ - {"field": "@timestamp", "value": "Test timestamp 1"}, - {"field": "@message", "value": "Test value 1"}, - ], - [ - {"field": "@timestamp", "value": "Test timestamp 2"}, - ], - [ - {"field": "@message", "value": "Test value 3"}, - ], - [], -] - -EXPECTED_CALL_LIST = [ - call("Test timestamp 0", "Test value 0"), - call("Test timestamp 1", "Test value 1"), - call(None, "Test value 3"), -] - - -@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(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) - 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\\//" - " | filter @message like /Metrics - /", - limit=10000, - ) - assert mock_add_metrics.call_args_list == EXPECTED_CALL_LIST - 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 - - logs_client_mock.start_query.return_value = {"queryId": "test"} - logs_client_mock.get_query_results.return_value = {"status": "Queued"} - - fetcher = CwlInsightsMetricsFetcher(aws_session, 0.1, 0.2) - result = fetcher.get_metrics_for_job("test_job") - logs_client_mock.get_query_results.assert_called() - assert result == {} - - -@pytest.mark.xfail(raises=MetricsRetrievalError) -def test_get_all_metrics_failed(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": "Failed"} - - fetcher = CwlInsightsMetricsFetcher(aws_session) - fetcher.get_metrics_for_job("test_job") 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 deleted file mode 100644 index 247d1873..00000000 --- a/test/unit_tests/braket/jobs/metrics_data/test_cwl_metrics_fetcher.py +++ /dev/null @@ -1,133 +0,0 @@ -# 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 Mock, call, patch - -import pytest - -from braket.jobs.metrics_data.cwl_metrics_fetcher import CwlMetricsFetcher - - -@pytest.fixture -def aws_session(): - _aws_session = Mock() - return _aws_session - - -EXAMPLE_METRICS_LOG_LINES = [ - { - "timestamp": "Test timestamp 0", - "message": "Metrics - Test value 0", - }, - { - "timestamp": "Test timestamp 1", - "message": "Metrics - Test value 1", - }, - { - "timestamp": "Test timestamp 2", - }, - { - "message": "Metrics - Test value 3", - }, - { - # This metrics fetcher will filter out log line that don't have a "Metrics -" tag. - "message": "No prefix, Test value 4", - }, -] - -EXPECTED_CALL_LIST = [ - call("Test timestamp 0", "Metrics - Test value 0"), - call("Test timestamp 1", "Metrics - Test value 1"), - call(None, "Metrics - Test value 3"), -] - - -@patch("braket.jobs.metrics_data.cwl_metrics_fetcher.LogMetricsParser.get_parsed_metrics") -@patch("braket.jobs.metrics_data.cwl_metrics_fetcher.LogMetricsParser.parse_log_message") -def test_get_all_metrics_complete_results(mock_add_metrics, mock_get_metrics, aws_session): - logs_client_mock = Mock() - aws_session.logs_client = logs_client_mock - - logs_client_mock.describe_log_streams.return_value = { - "logStreams": [{"logStreamName": "stream name"}, {}] - } - logs_client_mock.get_log_events.return_value = { - "events": EXAMPLE_METRICS_LOG_LINES, - "nextForwardToken": None, - } - expected_result = {"Test": [0]} - mock_get_metrics.return_value = expected_result - - fetcher = CwlMetricsFetcher(aws_session) - result = fetcher.get_metrics_for_job("test_job") - assert mock_add_metrics.call_args_list == EXPECTED_CALL_LIST - assert result == expected_result - - -@patch("braket.jobs.metrics_data.cwl_metrics_fetcher.LogMetricsParser.parse_log_message") -def test_get_log_streams_timeout(mock_add_metrics, aws_session): - logs_client_mock = Mock() - aws_session.logs_client = logs_client_mock - - logs_client_mock.describe_log_streams.return_value = { - "logStreams": [{"logStreamName": "stream name"}], - "nextToken": "forever", - } - logs_client_mock.get_log_events.return_value = { - "events": EXAMPLE_METRICS_LOG_LINES, - } - - fetcher = CwlMetricsFetcher(aws_session, 0.1) - result = fetcher.get_metrics_for_job("test_job") - mock_add_metrics.assert_not_called() - assert result == {} - - -@patch("braket.jobs.metrics_data.cwl_metrics_fetcher.LogMetricsParser.parse_log_message") -def test_get_no_streams_returned(mock_add_metrics, aws_session): - logs_client_mock = Mock() - aws_session.logs_client = logs_client_mock - - logs_client_mock.describe_log_streams.return_value = {} - - fetcher = CwlMetricsFetcher(aws_session) - result = fetcher.get_metrics_for_job("test_job") - logs_client_mock.describe_log_streams.assert_called() - mock_add_metrics.assert_not_called() - assert result == {} - - -@patch("braket.jobs.metrics_data.cwl_metrics_fetcher.LogMetricsParser.get_parsed_metrics") -@patch("braket.jobs.metrics_data.cwl_metrics_fetcher.LogMetricsParser.parse_log_message") -def test_get_metrics_timeout(mock_add_metrics, mock_get_metrics, aws_session): - logs_client_mock = Mock() - aws_session.logs_client = logs_client_mock - - logs_client_mock.describe_log_streams.return_value = { - "logStreams": [{"logStreamName": "stream name"}] - } - logs_client_mock.get_log_events.side_effect = get_log_events_forever - expected_result = {"Test": [0]} - mock_get_metrics.return_value = expected_result - - fetcher = CwlMetricsFetcher(aws_session, 0.1) - result = fetcher.get_metrics_for_job("test_job") - logs_client_mock.get_log_events.assert_called() - mock_add_metrics.assert_called() - assert result == expected_result - - -def get_log_events_forever(*args, **kwargs): - token = kwargs.get("nextToken") - 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/metrics_data/test_log_metrics_parser.py b/test/unit_tests/braket/jobs/metrics_data/test_log_metrics_parser.py deleted file mode 100644 index 4d9ca9dc..00000000 --- a/test/unit_tests/braket/jobs/metrics_data/test_log_metrics_parser.py +++ /dev/null @@ -1,204 +0,0 @@ -# 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.jobs.metrics_data import LogMetricsParser -from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType - -MALFORMED_METRICS_LOG_LINES = [ - {"timestamp": "Test timestamp 0", "message": ""}, - {"timestamp": "Test timestamp 1", "message": "No semicolon metric0=2.0"}, - {"timestamp": "Test timestamp 2", "message": "metric0=not_a_number;"}, - {"timestamp": "Test timestamp 3", "message": "also not a number metric0=2 . 0;"}, - {"timestamp": "Test timestamp 3", "message": "metric0=;"}, - {"timestamp": "Test timestamp 3", "message": "metric0= ;"}, - {"timestamp": "Test timestamp 4"}, - {"unknown": "Unknown"}, -] - -SIMPLE_METRICS_LOG_LINES = [ - # This is a standard line of what our metrics may look like - { - "timestamp": "Test timestamp 0", - "message": "Metrics - metric0=0.0; metric1=1.0; metric2=2.0 ;", - }, - # This line overwrites the timestamp by having it output in the metrics. - { - "timestamp": "Test timestamp 1", - "message": "Metrics - timestamp=1628019160; metric0=0.1; metric2= 2.1;", - }, - # This line adds metric3 that won't have values for any other timestamp - { - "timestamp": "Test timestamp 2", - "message": "Metrics - metric0=0.2; metric1=1.2; metric2= 2.2 ; metric3=0.2;", - }, - # This line adds metrics expressed as exponents - { - "timestamp": "Test timestamp 3", - "message": "Metrics - metric0=-0.4; metric1=3.14e-22; metric2=3.14E22;", - }, -] - -SIMPLE_METRICS_RESULT = { - "timestamp": [ - "Test timestamp 0", - 1628019160, - "Test timestamp 2", - "Test timestamp 3", - ], - "metric0": [0.0, 0.1, 0.2, -0.4], - "metric1": [1.0, None, 1.2, 3.14e-22], - "metric2": [2.0, 2.1, 2.2, 3.14e22], - "metric3": [None, None, 0.2, None], -} - -MULTINODE_METRICS_LOG_LINES = [ - { - "timestamp": "Test timestamp 0", - "message": "[nodeA]:Metrics - metric0=1.0;", - }, - # This line logs the same metric from a different node. - { - "timestamp": "Test timestamp 0", - "message": "[nodeB]:Metrics - metric0=2.0;", - }, - # This line also logs a metric unique to one node. - { - "timestamp": "Test timestamp 1", - "message": "[nodeA]:Metrics - metricA=3.0;", - }, - # This line logs a metric without a node tag. - { - "timestamp": "Test timestamp 1", - "message": "Metrics - metric0=0.0;", - }, -] - -MULTINODES_METRICS_RESULT = { - "timestamp": ["Test timestamp 0", "Test timestamp 0", "Test timestamp 1", "Test timestamp 1"], - "node_id": [ - "nodeA", - "nodeB", - "nodeA", - None, - ], - "metric0": [1.0, 2.0, None, 0.0], - "metricA": [None, None, 3.0, None], -} - -# This will test how metrics are combined when the multiple metrics have the same timestamp -SINGLE_TIMESTAMP_METRICS_LOG_LINES = [ - {"timestamp": "Test timestamp 0", "message": "Metrics - metric0=0.0;"}, - {"timestamp": "Test timestamp 0", "message": "Metrics - metric0=0.1; metric1=1.1;"}, - {"timestamp": "Test timestamp 0", "message": "Metrics - metric0=0.2; metric2=2.8;"}, - {"timestamp": "Test timestamp 0", "message": "Metrics - metric0=0.3; metric1=1.3;"}, - {"timestamp": "Test timestamp 0", "message": "Metrics - metric1=1.4; metric2=2.4;"}, - { - "timestamp": "Test timestamp 0", - "message": "Metrics - metric0=0.5; metric1=1.5; metric2=2.5;", - }, - {"timestamp": "Test timestamp 0", "message": "Metrics - metric1=0.6; metric0=0.6;"}, -] - - -ITERATION_AND_TIMESTAMPS_LOG_LINES = [ - {"timestamp": "Test timestamp 0", "message": "Metrics - iteration_number=0; metric0=0.0;"}, - { - "timestamp": "Test timestamp 1", - "message": "Metrics - metric0=0.1; metric1=1.1; iteration_number=0;", - }, - {"timestamp": "Test timestamp 2", "message": "Metrics - metric0=0.2; metric2=2.8;"}, - {"timestamp": "Test timestamp 3", "message": "Metrics - metric0=0.3; metric1=1.3;"}, - { - "timestamp": "Test timestamp 4", - "message": "Metrics - metric1=1.4; metric2=2.4; iteration_number=0;", - }, - { - "timestamp": "Test timestamp 5", - "message": "Metrics - metric0=0.5; metric1=1.5; metric2=2.5;", - }, - { - "timestamp": "Test timestamp 6", - "message": "Metrics - metric1=0.6; iteration_number=0; metric0=0.6;", - }, -] - - -SINGLE_TIMESTAMP_MAX_RESULTS = { - "timestamp": ["Test timestamp 0"], - "metric0": [0.6], - "metric1": [1.5], - "metric2": [2.8], -} - -SINGLE_TIMESTAMP_MIN_RESULTS = { - "timestamp": ["Test timestamp 0"], - "metric0": [0.0], - "metric1": [0.6], - "metric2": [2.4], -} - -ITERATION_NUMBER_MAX_RESULTS = { - "iteration_number": [0], - "timestamp": ["Test timestamp 6"], - "metric0": [0.6], - "metric1": [1.4], - "metric2": [2.4], -} - - -@pytest.mark.parametrize( - "log_events, metric_type, metric_stat, metrics_results", - [ - ([], MetricType.TIMESTAMP, MetricStatistic.MAX, {}), - (MALFORMED_METRICS_LOG_LINES, MetricType.TIMESTAMP, MetricStatistic.MAX, {}), - ( - SIMPLE_METRICS_LOG_LINES, - MetricType.TIMESTAMP, - MetricStatistic.MAX, - SIMPLE_METRICS_RESULT, - ), - ( - MULTINODE_METRICS_LOG_LINES, - MetricType.TIMESTAMP, - MetricStatistic.MAX, - MULTINODES_METRICS_RESULT, - ), - ( - SINGLE_TIMESTAMP_METRICS_LOG_LINES, - MetricType.TIMESTAMP, - MetricStatistic.MAX, - SINGLE_TIMESTAMP_MAX_RESULTS, - ), - ( - SINGLE_TIMESTAMP_METRICS_LOG_LINES, - MetricType.TIMESTAMP, - MetricStatistic.MIN, - SINGLE_TIMESTAMP_MIN_RESULTS, - ), - ( - ITERATION_AND_TIMESTAMPS_LOG_LINES, - MetricType.ITERATION_NUMBER, - MetricStatistic.MAX, - ITERATION_NUMBER_MAX_RESULTS, - ), - # TODO: https://app.asana.com/0/1199668788990775/1200502190825620 - # We should also test some real-world data, once we have it. - ], -) -def test_get_all_metrics_complete_results(log_events, metric_type, metric_stat, metrics_results): - parser = LogMetricsParser() - for log_event in log_events: - parser.parse_log_message(log_event.get("timestamp"), log_event.get("message")) - assert parser.get_parsed_metrics(metric_type, metric_stat) == metrics_results diff --git a/test/unit_tests/braket/jobs/test_data_persistence.py b/test/unit_tests/braket/jobs/test_data_persistence.py deleted file mode 100644 index a4ac78f2..00000000 --- a/test/unit_tests/braket/jobs/test_data_persistence.py +++ /dev/null @@ -1,338 +0,0 @@ -# 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 -from dataclasses import dataclass -from unittest.mock import patch - -import numpy as np -import pytest - -from braket.jobs.data_persistence import ( - load_job_checkpoint, - load_job_result, - save_job_checkpoint, - save_job_result, -) -from braket.jobs_data import PersistedJobDataFormat - - -@pytest.mark.parametrize( - "job_name, file_suffix, data_format, checkpoint_data, expected_saved_data", - [ - ( - "job_plaintext_simple_dict", - "", - 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", - } - ), - ), - ( - "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", - } - ), - ), - ], -) -def test_save_job_checkpoint( - job_name, file_suffix, data_format, checkpoint_data, expected_saved_data -): - with tempfile.TemporaryDirectory() as tmp_dir: - with patch.dict( - os.environ, {"AMZN_BRAKET_CHECKPOINT_DIR": tmp_dir, "AMZN_BRAKET_JOB_NAME": job_name} - ): - save_job_checkpoint(checkpoint_data, file_suffix, data_format) - - expected_file_location = ( - f"{tmp_dir}/{job_name}_{file_suffix}.json" - if file_suffix - else f"{tmp_dir}/{job_name}.json" - ) - with open(expected_file_location) as expected_file: - assert expected_file.read() == expected_saved_data - - -@pytest.mark.xfail(raises=ValueError) -@pytest.mark.parametrize("checkpoint_data", [{}, None]) -def test_save_job_checkpoint_raises_error_empty_data(checkpoint_data): - job_name = "foo" - with tempfile.TemporaryDirectory() as tmp_dir: - with patch.dict( - os.environ, {"AMZN_BRAKET_CHECKPOINT_DIR": tmp_dir, "AMZN_BRAKET_JOB_NAME": job_name} - ): - save_job_checkpoint(checkpoint_data) - - -@pytest.mark.parametrize( - "job_name, file_suffix, data_format, saved_data, expected_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", - } - ), - {"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", - } - ), - {"converged": True, "energy": -0.2}, - ), - ], -) -def test_load_job_checkpoint( - job_name, file_suffix, data_format, saved_data, expected_checkpoint_data -): - with tempfile.TemporaryDirectory() as tmp_dir: - file_path = ( - f"{tmp_dir}/{job_name}_{file_suffix}.json" - if file_suffix - else f"{tmp_dir}/{job_name}.json" - ) - with open(file_path, "w") as f: - f.write(saved_data) - - with patch.dict( - os.environ, {"AMZN_BRAKET_CHECKPOINT_DIR": tmp_dir, "AMZN_BRAKET_JOB_NAME": job_name} - ): - loaded_data = load_job_checkpoint(job_name, file_suffix) - assert loaded_data == expected_checkpoint_data - - -@pytest.mark.xfail(raises=FileNotFoundError) -def test_load_job_checkpoint_raises_error_file_not_exists(): - job_name = "old_job" - file_suffix = "correct_suffix" - with tempfile.TemporaryDirectory() as tmp_dir: - file_path = f"{tmp_dir}/{job_name}_{file_suffix}.json" - with open(file_path, "w") as _: - pass - - with patch.dict( - os.environ, {"AMZN_BRAKET_CHECKPOINT_DIR": tmp_dir, "AMZN_BRAKET_JOB_NAME": job_name} - ): - load_job_checkpoint(job_name, "wrong_suffix") - - -@pytest.mark.xfail(raises=ValueError) -def test_load_job_checkpoint_raises_error_corrupted_data(): - job_name = "old_job_corrupted_data" - file_suffix = "foo" - with tempfile.TemporaryDirectory() as tmp_dir: - 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", - } - ) - ) - - with patch.dict( - os.environ, {"AMZN_BRAKET_CHECKPOINT_DIR": tmp_dir, "AMZN_BRAKET_JOB_NAME": job_name} - ): - load_job_checkpoint(job_name, file_suffix) - - -@dataclass -class CustomClassToPersist: - float_val: float - str_val: str - bool_val: bool - - -def test_save_and_load_job_checkpoint(): - with tempfile.TemporaryDirectory() as tmp_dir: - job_name = "job_name_1" - data = { - "np_array": np.array([1]), - "custom_class": CustomClassToPersist(3.4, "str", True), - "none_value": None, - "nested_dict": {"a": {"b": False}}, - } - with patch.dict( - os.environ, {"AMZN_BRAKET_CHECKPOINT_DIR": tmp_dir, "AMZN_BRAKET_JOB_NAME": job_name} - ): - save_job_checkpoint(data, data_format=PersistedJobDataFormat.PICKLED_V4) - retrieved = load_job_checkpoint(job_name) - assert retrieved == data - - -@pytest.mark.parametrize( - "data_format, result_data, expected_saved_data", - [ - ( - 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", - } - ), - ), - ( - 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", - } - ), - ), - ], -) -def test_save_job_result(data_format, result_data, expected_saved_data): - with tempfile.TemporaryDirectory() as tmp_dir: - with patch.dict(os.environ, {"AMZN_BRAKET_JOB_RESULTS_DIR": tmp_dir}): - save_job_result(result_data, data_format) - - expected_file_location = f"{tmp_dir}/results.json" - with open(expected_file_location) as expected_file: - assert expected_file.read() == expected_saved_data - - -@pytest.mark.parametrize("result_data", [{}, None]) -def test_save_job_result_does_not_raise_error_empty_data(result_data): - with tempfile.TemporaryDirectory() as tmp_dir: - with patch.dict(os.environ, {"AMZN_BRAKET_JOB_RESULTS_DIR": tmp_dir}): - save_job_result(result_data) - - -@pytest.mark.parametrize( - "first_result_data," - "first_data_format," - "second_result_data," - "second_data_format," - "expected_result_data", - ( - ( - "hello", - PersistedJobDataFormat.PLAINTEXT, - "goodbye", - PersistedJobDataFormat.PLAINTEXT, - {"result": "goodbye"}, - ), - ( - "hello", - PersistedJobDataFormat.PLAINTEXT, - "goodbye", - PersistedJobDataFormat.PICKLED_V4, - {"result": "goodbye"}, - ), - ("hello", PersistedJobDataFormat.PICKLED_V4, "goodbye", None, {"result": "goodbye"}), - ( - # not json serializable - PersistedJobDataFormat, - PersistedJobDataFormat.PICKLED_V4, - {"other_field": "value"}, - None, - {"result": PersistedJobDataFormat, "other_field": "value"}, - ), - ), -) -def test_update_result_data( - first_result_data, - first_data_format, - second_result_data, - second_data_format, - expected_result_data, -): - with tempfile.TemporaryDirectory() as tmp_dir: - with patch.dict(os.environ, {"AMZN_BRAKET_JOB_RESULTS_DIR": tmp_dir}): - save_job_result(first_result_data, first_data_format) - save_job_result(second_result_data, second_data_format) - - assert load_job_result() == expected_result_data - - -def test_update_pickled_results_as_plaintext_error(): - with tempfile.TemporaryDirectory() as tmp_dir: - with patch.dict(os.environ, {"AMZN_BRAKET_JOB_RESULTS_DIR": tmp_dir}): - save_job_result(np.arange(5), PersistedJobDataFormat.PICKLED_V4) - - cannot_convert_pickled_to_plaintext = ( - "Cannot update results object serialized with " - "pickled_v4 using data format plaintext." - ) - with pytest.raises(TypeError, match=cannot_convert_pickled_to_plaintext): - save_job_result("hello", PersistedJobDataFormat.PLAINTEXT) diff --git a/test/unit_tests/braket/jobs/test_environment_variables.py b/test/unit_tests/braket/jobs/test_environment_variables.py deleted file mode 100644 index 2c8a2f54..00000000 --- a/test/unit_tests/braket/jobs/test_environment_variables.py +++ /dev/null @@ -1,66 +0,0 @@ -import json -import os -import tempfile -from pathlib import Path -from unittest.mock import patch - -from braket.jobs import ( - get_checkpoint_dir, - get_hyperparameters, - get_input_data_dir, - get_job_device_arn, - get_job_name, - get_results_dir, -) - - -def test_job_name(): - assert get_job_name() == "" - job_name = "my_job_name" - with patch.dict(os.environ, {"AMZN_BRAKET_JOB_NAME": job_name}): - assert get_job_name() == job_name - - -def test_job_device_arn(): - assert get_job_device_arn() == "local:none/none" - device_arn = "my_device_arn" - with patch.dict(os.environ, {"AMZN_BRAKET_DEVICE_ARN": device_arn}): - assert get_job_device_arn() == device_arn - - -def test_input_data_dir(): - assert get_input_data_dir() == "." - input_path = "my/input/path" - with patch.dict(os.environ, {"AMZN_BRAKET_INPUT_DIR": input_path}): - assert get_input_data_dir() == f"{input_path}/input" - channel_name = "my_channel" - assert get_input_data_dir(channel_name) == f"{input_path}/{channel_name}" - - -def test_results_dir(): - assert get_results_dir() == "." - results_dir = "my_results_dir" - with patch.dict(os.environ, {"AMZN_BRAKET_JOB_RESULTS_DIR": results_dir}): - assert get_results_dir() == results_dir - - -def test_checkpoint_dir(): - assert get_checkpoint_dir() == "." - checkpoint_dir = "my_checkpoint_dir" - with patch.dict(os.environ, {"AMZN_BRAKET_CHECKPOINT_DIR": checkpoint_dir}): - assert get_checkpoint_dir() == checkpoint_dir - - -def test_hyperparameters(): - assert get_hyperparameters() == {} - hp_file = "my_hyperparameters.json" - 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 open(str(Path(temp_dir) / hp_file), "w") as f: - json.dump(hyperparameters, f) - assert get_hyperparameters() == hyperparameters diff --git a/test/unit_tests/braket/jobs/test_hybrid_job.py b/test/unit_tests/braket/jobs/test_hybrid_job.py deleted file mode 100644 index b1739d87..00000000 --- a/test/unit_tests/braket/jobs/test_hybrid_job.py +++ /dev/null @@ -1,554 +0,0 @@ -import ast -import importlib -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 - -import job_module -import pytest -from cloudpickle import cloudpickle - -from braket.aws import AwsQuantumJob -from braket.devices import Devices -from braket.jobs import hybrid_job -from braket.jobs.config import ( - CheckpointConfig, - InstanceConfig, - OutputDataConfig, - S3DataSourceConfig, - StoppingCondition, -) -from braket.jobs.hybrid_job import _sanitize, _serialize_entry_point -from braket.jobs.local import LocalQuantumJob - - -@pytest.fixture -def aws_session(): - aws_session = MagicMock() - python_version_str = f"py{sys.version_info.major}{sys.version_info.minor}" - aws_session.get_full_image_tag.return_value = f"1.0-cpu-{python_version_str}-ubuntu22.04" - aws_session.region = "us-west-2" - return aws_session - - -@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_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" - - @hybrid_job(device=None, aws_session=aws_session) - 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 - - source_module = mock_tempdir_name - entry_point = f"{mock_tempdir_name}.entry_point:my_entry" - wait_until_complete = False - - device = "local:none/none" - - my_entry() - - mock_create.assert_called_with( - device=device, - source_module=source_module, - entry_point=entry_point, - wait_until_complete=wait_until_complete, - job_name="my-entry-123000", - hyperparameters={"c": "0", "d": "1.0"}, - logger=getLogger("braket.jobs.hybrid_job"), - aws_session=aws_session, - ) - assert mock_tempdir.return_value.__exit__.called - - -@pytest.mark.parametrize("include_modules", (job_module, ["job_module"])) -@patch("braket.jobs.image_uris.retrieve_image") -@patch("sys.stdout") -@patch("time.time", return_value=123.0) -@patch("cloudpickle.register_pickle_by_value") -@patch("cloudpickle.unregister_pickle_by_value") -@patch("shutil.copy") -@patch("builtins.open") -@patch.object(AwsQuantumJob, "create") -def test_decorator_non_defaults( - mock_create, - _mock_open, - mock_copy, - mock_register, - mock_unregister, - mock_time, - mock_stdout, - mock_retrieve, - include_modules, -): - mock_retrieve.return_value = "should-not-be-used" - dependencies = "my_requirements.txt" - image_uri = "my_image.uri" - default_instance = InstanceConfig() - distribution = "data_parallel" - copy_checkpoints_from_job = "arn/other-job" - checkpoint_config = CheckpointConfig(localPath="local", s3Uri="s3") - role_arn = "role_arn" - stopping_condition = StoppingCondition(maxRuntimeInSeconds=10) - output_data_config = OutputDataConfig(s3Path="s3") - aws_session = MagicMock() - tags = {"my_tag": "my_value"} - reservation_arn = ( - "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" - ) - logger = getLogger(__name__) - - with tempfile.TemporaryDirectory() as tempdir: - Path(tempdir, "temp_dir").mkdir() - Path(tempdir, "temp_file").touch() - - input_data = { - "my_prefix": "my_input_data", - "my_dir": Path(tempdir, "temp_dir"), - "my_file": Path(tempdir, "temp_file"), - "my_s3_prefix": "s3://bucket/path/to/prefix", - "my_s3_config": S3DataSourceConfig(s3_data="s3://bucket/path/to/prefix"), - } - - @hybrid_job( - device=Devices.Amazon.SV1, - include_modules=include_modules, - dependencies=dependencies, - image_uri=image_uri, - input_data=input_data, - wait_until_complete=True, - instance_config=default_instance, - distribution=distribution, - checkpoint_config=checkpoint_config, - copy_checkpoints_from_job=copy_checkpoints_from_job, - role_arn=role_arn, - stopping_condition=stopping_condition, - output_data_config=output_data_config, - aws_session=aws_session, - tags=tags, - reservation_arn=reservation_arn, - logger=logger, - ) - def my_entry(a, b: int, c=0, d: float = 1.0, **extras) -> str: - return "my entry return value" - - mock_tempdir = MagicMock(spec=tempfile.TemporaryDirectory) - mock_tempdir_name = "job_temp_dir_00000" - mock_tempdir.__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" - wait_until_complete = True - - s3_not_linked = ( - "Input data channels mapped to an S3 source will not be available in the working " - 'directory. Use `get_input_data_dir(channel="my_s3_prefix")` to read input data ' - "from S3 source inside the job container." - ) - - with patch("tempfile.TemporaryDirectory", return_value=mock_tempdir): - my_entry("a", 2, 3, 4, extra_param="value", another=6) - - mock_create.assert_called_with( - device=device, - source_module=source_module, - entry_point=entry_point, - image_uri=image_uri, - input_data=input_data, - wait_until_complete=wait_until_complete, - job_name="my-entry-123000", - instance_config=default_instance, - distribution=distribution, - hyperparameters={ - "a": "a", - "b": "2", - "c": "3", - "d": "4", - "extra_param": "value", - "another": "6", - }, - checkpoint_config=checkpoint_config, - copy_checkpoints_from_job=copy_checkpoints_from_job, - role_arn=role_arn, - stopping_condition=stopping_condition, - output_data_config=output_data_config, - aws_session=aws_session, - tags=tags, - logger=logger, - reservation_arn=reservation_arn, - ) - included_module = importlib.import_module("job_module") - mock_register.assert_called_with(included_module) - mock_unregister.assert_called_with(included_module) - mock_copy.assert_called_with( - Path("my_requirements.txt").resolve(), Path(mock_tempdir_name, "requirements.txt") - ) - assert mock_tempdir.__exit__.called - mock_stdout.write.assert_any_call(s3_not_linked) - - -@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_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" - input_prefix = "my_input" - - @hybrid_job(device=None, input_data=input_prefix, aws_session=aws_session) - def my_entry(): - return "my entry return value" - - mock_tempdir_name = "job_temp_dir_00000" - mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name - - source_module = mock_tempdir_name - entry_point = f"{mock_tempdir_name}.entry_point:my_entry" - wait_until_complete = False - - device = "local:none/none" - - my_entry() - - mock_create.assert_called_with( - device=device, - source_module=source_module, - entry_point=entry_point, - wait_until_complete=wait_until_complete, - job_name="my-entry-123000", - hyperparameters={}, - logger=getLogger("braket.jobs.hybrid_job"), - input_data=input_prefix, - aws_session=aws_session, - ) - assert mock_tempdir.return_value.__exit__.called - - -@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_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" - dependency_list = ["dep_1", "dep_2", "dep_3"] - - @hybrid_job( - device=None, - aws_session=aws_session, - dependencies=dependency_list, - ) - 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 - - source_module = mock_tempdir_name - entry_point = f"{mock_tempdir_name}.entry_point:my_entry" - wait_until_complete = False - - device = "local:none/none" - - my_entry() - - mock_create.assert_called_with( - device=device, - source_module=source_module, - entry_point=entry_point, - wait_until_complete=wait_until_complete, - job_name="my-entry-123000", - hyperparameters={"c": "0", "d": "1.0"}, - logger=getLogger("braket.jobs.hybrid_job"), - 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.return_value.__enter__.return_value.write.assert_called_with( - "\n".join(dependency_list) - ) - - -@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_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" - - @hybrid_job(device=Devices.Amazon.SV1, local=True, aws_session=aws_session) - def my_entry(): - return "my entry return value" - - 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() - - 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, - ) - assert mock_tempdir.return_value.__exit__.called - - -@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_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" - - @hybrid_job( - device=Devices.Amazon.SV1, - local=True, - wait_until_complete=True, - copy_checkpoints_from_job="arn/other-job", - instance_config=InstanceConfig(), - distribution="data_parallel", - stopping_condition=StoppingCondition(), - tags={"my_tag": "my_value"}, - logger=getLogger(__name__), - aws_session=aws_session, - ) - def my_entry(): - return "my entry return value" - - 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() - - 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, - ) - assert mock_tempdir.return_value.__exit__.called - - -@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_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" - - @hybrid_job(device="local:braket/default", aws_session=aws_session) - def this_is_a_50_character_func_name_for_testing_names(): - return "my entry return value" - - mock_tempdir_name = "job_temp_dir_00000" - mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name - - device = "local:braket/default" - source_module = mock_tempdir_name - entry_point = ( - f"{mock_tempdir_name}.entry_point:this_is_a_50_character_func_name_for_testing_names" - ) - wait_until_complete = False - - with pytest.warns(UserWarning): - this_is_a_50_character_func_name_for_testing_names() - - expected_job_name = "this-is-a-50-character-func-name-for-testin-123000" - - mock_create.assert_called_with( - device=device, - source_module=source_module, - entry_point=entry_point, - wait_until_complete=wait_until_complete, - job_name=expected_job_name, - hyperparameters={}, - logger=getLogger("braket.jobs.hybrid_job"), - 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"], "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_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" - - @hybrid_job(device="local:braket/default", aws_session=aws_session) - def my_entry(pos_only, /): - return "my entry return value" - - mock_tempdir_name = "job_temp_dir_00000" - mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name - - device = "local:braket/default" - source_module = mock_tempdir_name - entry_point = f"{mock_tempdir_name}.entry_point:my_entry" - wait_until_complete = False - - pos_only_warning = "Positional only arguments will not be logged to the hyperparameters file." - with pytest.warns(match=pos_only_warning): - my_entry("pos_only") - - mock_create.assert_called_with( - device=device, - source_module=source_module, - entry_point=entry_point, - wait_until_complete=wait_until_complete, - job_name="my-entry-123000", - hyperparameters={}, - logger=getLogger("braket.jobs.hybrid_job"), - aws_session=aws_session, - ) - assert mock_tempdir.return_value.__exit__.called - - -@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_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" - - @hybrid_job(device="local:braket/default", aws_session=aws_session) - def my_entry(*args): - return "my entry return value" - - mock_tempdir_name = "job_temp_dir_00000" - mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name - - device = "local:braket/default" - source_module = mock_tempdir_name - entry_point = f"{mock_tempdir_name}.entry_point:my_entry" - wait_until_complete = False - - pos_only_warning = "Positional only arguments will not be logged to the hyperparameters file." - with pytest.warns(match=pos_only_warning): - my_entry("pos_only") - - mock_create.assert_called_with( - device=device, - source_module=source_module, - entry_point=entry_point, - wait_until_complete=wait_until_complete, - job_name="my-entry-123000", - hyperparameters={}, - logger=getLogger("braket.jobs.hybrid_job"), - aws_session=aws_session, - ) - assert mock_tempdir.return_value.__exit__.called - - -def test_serialization_error(aws_session): - ssl_context = SSLContext(protocol=PROTOCOL_TLS_CLIENT) - - @hybrid_job(device=None, aws_session=aws_session) - def fails_serialization(): - print(ssl_context) - - serialization_failed = ( - "Serialization failed for decorator hybrid job. If you are referencing " - "an object from outside the function scope, either directly or through " - "function parameters, try instantiating the object inside the decorated " - "function instead." - ) - with pytest.raises(RuntimeError, match=serialization_failed): - fails_serialization() - - -def test_serialization_wrapping(): - def my_entry(*args, **kwargs): - print("something with \" and ' and \n") - return 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)[1] - byte_str = ast.literal_eval(pickled_str) - - recovered = cloudpickle.loads(byte_str) - assert recovered() == (args, kwargs) - - -def test_python_validation(aws_session): - aws_session.get_full_image_tag.return_value = "1.0-cpu-py38-ubuntu22.04" - - bad_version = ( - "Python version must match between local environment and container. " - f"Client is running Python {sys.version_info.major}.{sys.version_info.minor} " - "locally, but container uses Python 3.8." - ) - with pytest.raises(RuntimeError, match=bad_version): - - @hybrid_job(device=None, aws_session=aws_session) - def my_job(): - pass - - -@pytest.mark.parametrize( - "hyperparameter, expected", - ( - ( - "with\nnewline", - "with newline", - ), - ( - "with weird chars: (&$`)", - "with weird chars: {+?'}", - ), - ( - "?" * 2600, - f"{'?' * 2477}...{'?' * 20}", - ), - ), -) -def test_sanitize_hyperparameters(hyperparameter, expected): - assert _sanitize(hyperparameter) == expected diff --git a/test/unit_tests/braket/jobs/test_image_uris.py b/test/unit_tests/braket/jobs/test_image_uris.py deleted file mode 100644 index 2e6ae396..00000000 --- a/test/unit_tests/braket/jobs/test_image_uris.py +++ /dev/null @@ -1,54 +0,0 @@ -# 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.jobs.image_uris import Framework, retrieve_image - - -@pytest.mark.parametrize( - "region, framework, expected_uri", - [ - ( - "us-west-1", - Framework.BASE, - "292282985366.dkr.ecr.us-west-1.amazonaws.com/amazon-braket-base-jobs:latest", - ), - ( - "us-east-1", - Framework.PL_TENSORFLOW, - "292282985366.dkr.ecr.us-east-1.amazonaws.com/amazon-braket-tensorflow-jobs:latest", - ), - ( - "us-west-2", - Framework.PL_PYTORCH, - "292282985366.dkr.ecr.us-west-2.amazonaws.com/amazon-braket-pytorch-jobs:latest", - ), - ], -) -def test_retrieve_image_default_version(region, framework, expected_uri): - assert retrieve_image(framework, region) == expected_uri - - -@pytest.mark.parametrize( - "region, framework", - [ - ("eu-west-1", Framework.BASE), - (None, Framework.BASE), - ("us-west-1", None), - ("us-west-1", "foo"), - ], -) -@pytest.mark.xfail(raises=ValueError) -def test_retrieve_image_incorrect_input(region, framework): - retrieve_image(framework, region) diff --git a/test/unit_tests/braket/jobs/test_metrics.py b/test/unit_tests/braket/jobs/test_metrics.py deleted file mode 100644 index 1670cee3..00000000 --- a/test/unit_tests/braket/jobs/test_metrics.py +++ /dev/null @@ -1,35 +0,0 @@ -from unittest.mock import patch - -import pytest - -from braket.jobs.metrics import log_metric - - -@pytest.mark.parametrize( - "test_value, test_timestamp, test_iteration, result_string", - [ - # Happy case - (0.1, 1, 2, "Metrics - timestamp=1; TestName=0.1; iteration_number=2;"), - # We handle exponent values - (3.14e-22, 1, 2, "Metrics - timestamp=1; TestName=3.14e-22; iteration_number=2;"), - # When iteration number is not provided, we don't print it - (5, 1, None, "Metrics - timestamp=1; TestName=5;"), - # When iteration number is 0, we do print it - (5, 1, 0, "Metrics - timestamp=1; TestName=5; iteration_number=0;"), - # When timestamp is not provided, we use time.time() - (-3.14, None, 2, "Metrics - timestamp=time_mocked; TestName=-3.14; iteration_number=2;"), - ], -) -@patch("time.time") -@patch("builtins.print") -def test_log_metric( - print_mock, time_mock, test_value, test_timestamp, test_iteration, result_string -): - time_mock.return_value = "time_mocked" - log_metric( - metric_name="TestName", - value=test_value, - timestamp=test_timestamp, - iteration_number=test_iteration, - ) - print_mock.assert_called_with(result_string) diff --git a/test/unit_tests/braket/jobs/test_quantum_job_creation.py b/test/unit_tests/braket/jobs/test_quantum_job_creation.py deleted file mode 100644 index d12a29b0..00000000 --- a/test/unit_tests/braket/jobs/test_quantum_job_creation.py +++ /dev/null @@ -1,679 +0,0 @@ -# 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 datetime -import tempfile -import time -from collections import defaultdict -from dataclasses import asdict -from pathlib import Path -from unittest.mock import Mock, patch - -import pytest - -from braket.aws import AwsSession -from braket.jobs import Framework, retrieve_image -from braket.jobs.config import ( - CheckpointConfig, - InstanceConfig, - OutputDataConfig, - S3DataSourceConfig, - StoppingCondition, -) -from braket.jobs.quantum_job_creation import ( - _exclude_nones_factory, - _generate_default_job_name, - _process_input_data, - _process_local_source_module, - _process_s3_source_module, - _tar_and_upload_to_code_location, - _validate_entry_point, - prepare_quantum_job, -) - - -@pytest.fixture -def aws_session(): - _aws_session = Mock(spec=AwsSession) - _aws_session.default_bucket.return_value = "default-bucket-name" - _aws_session.get_default_jobs_role.return_value = "default-role-arn" - _aws_session.region = "us-east-1" - return _aws_session - - -@pytest.fixture -def entry_point(): - return "test-source-dir.entry_point:func" - - -@pytest.fixture -def bucket(): - return "braket-region-id" - - -@pytest.fixture -def tags(): - return {"tag-key": "tag-value"} - - -@pytest.fixture( - params=[ - None, - "aws.location/amazon-braket-custom-jobs:tag.1.2.3", - "other.uri/amazon-braket-custom-name:tag", - "other.uri/custom-non-managed:tag", - "other-custom-format.com", - ] -) -def image_uri(request): - return request.param - - -@pytest.fixture(params=["given_job_name", "default_job_name"]) -def job_name(request): - if request.param == "given_job_name": - return "test-job-name" - - -@pytest.fixture -def s3_prefix(job_name): - return f"{job_name}/non-default" - - -@pytest.fixture(params=["local_source", "s3_source"]) -def source_module(request, bucket): - if request.param == "local_source": - return "test-source-module" - elif request.param == "s3_source": - return AwsSession.construct_s3_uri(bucket, "test-source-prefix", "source.tar.gz") - - -@pytest.fixture -def code_location(bucket, s3_prefix): - return AwsSession.construct_s3_uri(bucket, s3_prefix, "script") - - -@pytest.fixture -def role_arn(): - return "arn:aws:iam::0000000000:role/AmazonBraketInternalSLR" - - -@pytest.fixture -def device(): - return "arn:aws:braket:::device/qpu/test/device-name" - - -@pytest.fixture -def hyperparameters(): - return { - "param": "value", - "other-param": 100, - } - - -@pytest.fixture(params=["dict", "local"]) -def input_data(request, bucket): - if request.param == "dict": - return { - "s3_input": f"s3://{bucket}/data/prefix", - "local_input": "local/prefix", - "config_input": S3DataSourceConfig(f"s3://{bucket}/config/prefix"), - } - elif request.param == "local": - return "local/prefix" - - -@pytest.fixture -def instance_config(): - return InstanceConfig( - instanceType="ml.m5.large", - volumeSizeInGb=1, - ) - - -@pytest.fixture(params=[False, True]) -def data_parallel(request): - return request.param - - -@pytest.fixture -def distribution(data_parallel): - return "data_parallel" if data_parallel else None - - -@pytest.fixture -def stopping_condition(): - return StoppingCondition( - maxRuntimeInSeconds=1200, - ) - - -@pytest.fixture -def output_data_config(bucket, s3_prefix): - return OutputDataConfig( - s3Path=AwsSession.construct_s3_uri(bucket, s3_prefix, "output"), - ) - - -@pytest.fixture -def checkpoint_config(bucket, s3_prefix): - return CheckpointConfig( - localPath="/opt/omega/checkpoints", - s3Uri=AwsSession.construct_s3_uri(bucket, s3_prefix, "checkpoints"), - ) - - -@pytest.fixture -def reservation_arn(): - return "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" - - -@pytest.fixture -def generate_get_job_response(): - def _get_job_response(**kwargs): - response = { - "ResponseMetadata": { - "RequestId": "d223b1a0-ee5c-4c75-afa7-3c29d5338b62", - "HTTPStatusCode": 200, - }, - "algorithmSpecification": { - "scriptModeConfig": { - "entryPoint": "my_file:start_here", - "s3Uri": "s3://amazon-braket-jobs/job-path/my_file.py", - } - }, - "checkpointConfig": { - "localPath": "/opt/omega/checkpoints", - "s3Uri": "s3://amazon-braket-jobs/job-path/checkpoints", - }, - "createdAt": datetime.datetime(2021, 6, 28, 21, 4, 51), - "deviceConfig": { - "device": "arn:aws:braket:::device/qpu/rigetti/Aspen-10", - }, - "hyperParameters": { - "foo": "bar", - }, - "inputDataConfig": [ - { - "channelName": "training_input", - "dataSource": { - "s3DataSource": { - "s3Uri": "s3://amazon-braket-jobs/job-path/input", - } - }, - } - ], - "instanceConfig": { - "instanceType": "ml.m5.large", - "volumeSizeInGb": 1, - }, - "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"}, - "roleArn": "arn:aws:iam::875981177017:role/AmazonBraketJobRole", - "status": "RUNNING", - "stoppingCondition": {"maxRuntimeInSeconds": 1200}, - } - response.update(kwargs) - - return response - - return _get_job_response - - -@pytest.fixture(params=["fixtures", "defaults", "nones"]) -def create_job_args( - request, - aws_session, - entry_point, - image_uri, - source_module, - job_name, - code_location, - role_arn, - device, - hyperparameters, - input_data, - instance_config, - distribution, - stopping_condition, - output_data_config, - checkpoint_config, - tags, - reservation_arn, -): - if request.param == "fixtures": - return { - key: value - for key, value in { - "device": device, - "source_module": source_module, - "entry_point": entry_point, - "image_uri": image_uri, - "job_name": job_name, - "code_location": code_location, - "role_arn": role_arn, - "hyperparameters": hyperparameters, - "input_data": input_data, - "instance_config": instance_config, - "distribution": distribution, - "stopping_condition": stopping_condition, - "output_data_config": output_data_config, - "checkpoint_config": checkpoint_config, - "aws_session": aws_session, - "tags": tags, - "reservation_arn": reservation_arn, - }.items() - if value is not None - } - elif request.param == "defaults": - return { - "device": device, - "source_module": source_module, - "entry_point": entry_point, - "aws_session": aws_session, - } - elif request.param == "nones": - return defaultdict( - lambda: None, - device=device, - source_module=source_module, - entry_point=entry_point, - aws_session=aws_session, - ) - - -@patch("tarfile.TarFile.add") -@patch("importlib.util.find_spec") -@patch("braket.jobs.quantum_job_creation.Path") -@patch("time.time") -def test_create_job( - mock_time, - mock_path, - mock_findspec, - mock_tarfile, - aws_session, - source_module, - create_job_args, -): - mock_path.return_value.resolve.return_value.parent = "parent_dir" - mock_path.return_value.resolve.return_value.stem = source_module - mock_path.return_value.name = "file_name" - mock_time.return_value = datetime.datetime.now().timestamp() - expected_kwargs = _translate_creation_args(create_job_args) - result_kwargs = prepare_quantum_job(**create_job_args) - assert expected_kwargs == result_kwargs - - -def _translate_creation_args(create_job_args): - aws_session = create_job_args["aws_session"] - create_job_args = defaultdict(lambda: None, **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, timestamp, "script" - ) - role_arn = create_job_args["role_arn"] or aws_session.get_default_jobs_role() - device = create_job_args["device"] - hyperparameters = create_job_args["hyperparameters"] or {} - hyperparameters = {str(key): str(value) for key, value in hyperparameters.items()} - input_data = create_job_args["input_data"] or {} - instance_config = create_job_args["instance_config"] or InstanceConfig() - reservation_arn = create_job_args["reservation_arn"] - if create_job_args["distribution"] == "data_parallel": - distributed_hyperparams = { - "sagemaker_distributed_dataparallel_enabled": "true", - "sagemaker_instance_type": instance_config.instanceType, - } - 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") - ) - 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, timestamp, "checkpoints" - ) - ) - entry_point = create_job_args["entry_point"] - source_module = create_job_args["source_module"] - if not AwsSession.is_s3_uri(source_module): - entry_point = entry_point or Path(source_module).stem - algorithm_specification = { - "scriptModeConfig": { - "entryPoint": entry_point, - "s3Uri": f"{code_location}/source.tar.gz", - "compressionType": "GZIP", - } - } - image_uri = image_uri or retrieve_image(Framework.BASE, aws_session.region) - algorithm_specification["containerImage"] = {"uri": image_uri} - tags = create_job_args.get("tags", {}) - - test_kwargs = { - "jobName": job_name, - "roleArn": role_arn, - "algorithmSpecification": algorithm_specification, - "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), - "deviceConfig": {"device": device}, - "hyperParameters": hyperparameters, - "stoppingCondition": asdict(stopping_condition), - "tags": tags, - } - - if reservation_arn: - test_kwargs["associations"] = [ - { - "arn": reservation_arn, - "type": "RESERVATION_TIME_WINDOW_ARN", - } - ] - - return test_kwargs - - -@patch("time.time") -def test_generate_default_job_name(mock_time, image_uri): - job_type_mapping = { - None: "-default", - "aws.location/amazon-braket-custom-jobs:tag.1.2.3": "-custom", - "other.uri/amazon-braket-custom-name:tag": "-custom-name", - "other.uri/custom-non-managed:tag": "", - "other-custom-format.com": "", - } - job_type = job_type_mapping[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( - "source_module", - ( - "s3://bucket/source_module.tar.gz", - "s3://bucket/SOURCE_MODULE.TAR.GZ", - ), -) -def test_process_s3_source_module(source_module, aws_session): - _process_s3_source_module(source_module, "entry_point", aws_session, "code_location") - aws_session.copy_s3_object.assert_called_with(source_module, "code_location/source.tar.gz") - - -def test_process_s3_source_module_not_tar_gz(aws_session): - must_be_tar_gz = ( - "If source_module is an S3 URI, it must point to a tar.gz file. " - "Not a valid S3 URI for parameter `source_module`: s3://bucket/source_module" - ) - with pytest.raises(ValueError, match=must_be_tar_gz): - _process_s3_source_module( - "s3://bucket/source_module", "entry_point", aws_session, "code_location" - ) - - -def test_process_s3_source_module_no_entry_point(aws_session): - entry_point_required = "If source_module is an S3 URI, entry_point must be provided." - with pytest.raises(ValueError, match=entry_point_required): - _process_s3_source_module("s3://bucket/source_module", None, aws_session, "code_location") - - -@patch("braket.jobs.quantum_job_creation._tar_and_upload_to_code_location") -@patch("braket.jobs.quantum_job_creation._validate_entry_point") -def test_process_local_source_module(validate_mock, tar_and_upload_mock, aws_session): - with tempfile.TemporaryDirectory() as temp_dir: - source_module = Path(temp_dir, "source_module") - source_module.touch() - - _process_local_source_module( - str(source_module), "entry_point", aws_session, "code_location" - ) - - source_module_abs_path = Path(temp_dir, "source_module").resolve() - validate_mock.assert_called_with(source_module_abs_path, "entry_point") - tar_and_upload_mock.assert_called_with(source_module_abs_path, aws_session, "code_location") - - -def test_process_local_source_module_not_found(aws_session): - with tempfile.TemporaryDirectory() as temp_dir: - source_module = str(Path(temp_dir, "source_module").as_posix()) - source_module_not_found = f"Source module not found: {source_module}" - with pytest.raises(ValueError, match=source_module_not_found): - _process_local_source_module(source_module, "entry_point", aws_session, "code_location") - - -def test_validate_entry_point_default_file(): - with tempfile.TemporaryDirectory() as temp_dir: - source_module_path = Path(temp_dir, "source_module.py") - source_module_path.touch() - # import source_module - _validate_entry_point(source_module_path, "source_module") - # from source_module import func - _validate_entry_point(source_module_path, "source_module:func") - # import . - _validate_entry_point(source_module_path, ".") - # from . import func - _validate_entry_point(source_module_path, ".:func") - - -def test_validate_entry_point_default_directory(): - with tempfile.TemporaryDirectory() as temp_dir: - source_module_path = Path(temp_dir, "source_module") - source_module_path.mkdir() - # import source_module - _validate_entry_point(source_module_path, "source_module") - # from source_module import func - _validate_entry_point(source_module_path, "source_module:func") - # import . - _validate_entry_point(source_module_path, ".") - # from . import func - _validate_entry_point(source_module_path, ".:func") - - -def test_validate_entry_point_submodule_file(): - with tempfile.TemporaryDirectory() as temp_dir: - source_module_path = Path(temp_dir, "source_module") - source_module_path.mkdir() - Path(source_module_path, "submodule.py").touch() - # from source_module import submodule - _validate_entry_point(source_module_path, "source_module.submodule") - # from source_module.submodule import func - _validate_entry_point(source_module_path, "source_module.submodule:func") - # from . import submodule - _validate_entry_point(source_module_path, ".submodule") - # from .submodule import func - _validate_entry_point(source_module_path, ".submodule:func") - - -def test_validate_entry_point_submodule_init(): - with tempfile.TemporaryDirectory() as temp_dir: - source_module_path = Path(temp_dir, "source_module") - source_module_path.mkdir() - Path(source_module_path, "submodule.py").touch() - with open(str(Path(source_module_path, "__init__.py")), "w") as f: - f.write("from . import submodule as renamed") - # from source_module import renamed - _validate_entry_point(source_module_path, "source_module:renamed") - # from . import renamed - _validate_entry_point(source_module_path, ".:renamed") - - -def test_validate_entry_point_source_module_not_found(): - with tempfile.TemporaryDirectory() as temp_dir: - source_module_path = Path(temp_dir, "source_module") - source_module_path.mkdir() - Path(source_module_path, "submodule.py").touch() - - # catches ModuleNotFoundError - module_not_found = "Entry point module was not found: fake_source_module.submodule" - with pytest.raises(ValueError, match=module_not_found): - _validate_entry_point(source_module_path, "fake_source_module.submodule") - - # catches AssertionError for module is not None - submodule_not_found = "Entry point module was not found: source_module.fake_submodule" - with pytest.raises(ValueError, match=submodule_not_found): - _validate_entry_point(source_module_path, "source_module.fake_submodule") - - -@patch("tarfile.TarFile.add") -def test_tar_and_upload_to_code_location(mock_tar_add, aws_session): - with tempfile.TemporaryDirectory() as temp_dir: - source_module_path = Path(temp_dir, "source_module") - source_module_path.mkdir() - _tar_and_upload_to_code_location(source_module_path, aws_session, "code_location") - mock_tar_add.assert_called_with(source_module_path, arcname="source_module") - local, s3 = aws_session.upload_to_s3.call_args_list[0][0] - assert local.endswith("source.tar.gz") - assert s3 == "code_location/source.tar.gz" - - -@patch("braket.jobs.quantum_job_creation._process_local_source_module") -@patch("braket.jobs.quantum_job_creation._validate_entry_point") -@patch("braket.jobs.quantum_job_creation._validate_params") -def test_copy_checkpoints( - mock_validate_input, - mock_validate_entry_point, - mock_process_local_source, - aws_session, - entry_point, - device, - checkpoint_config, - generate_get_job_response, -): - other_checkpoint_uri = "s3://amazon-braket-jobs/job-path/checkpoints" - aws_session.get_job.return_value = generate_get_job_response( - checkpointConfig={ - "s3Uri": other_checkpoint_uri, - } - ) - prepare_quantum_job( - device=device, - source_module="source_module", - entry_point=entry_point, - copy_checkpoints_from_job="other-job-arn", - checkpoint_config=checkpoint_config, - aws_session=aws_session, - ) - aws_session.copy_s3_directory.assert_called_with(other_checkpoint_uri, checkpoint_config.s3Uri) - - -def test_invalid_input_parameters(entry_point, aws_session): - error_message = ( - "'instance_config' should be of '' " - "but user provided ." - ) - with pytest.raises(ValueError, match=error_message): - prepare_quantum_job( - aws_session=aws_session, - entry_point=entry_point, - device="arn:aws:braket:::device/quantum-simulator/amazon/sv1", - source_module="alpha_test_job", - hyperparameters={ - "param-1": "first parameter", - "param-2": "second param", - }, - instance_config=2, - ) - - -@pytest.mark.parametrize( - "input_data, input_data_configs", - ( - ( - "local/prefix", - [ - { - "channelName": "input", - "dataSource": { - "s3DataSource": { - "s3Uri": "s3://default-bucket-name/jobs/job-name/ts/data/input/prefix", - }, - }, - } - ], - ), - ( - "s3://my-bucket/my/prefix-", - [ - { - "channelName": "input", - "dataSource": { - "s3DataSource": { - "s3Uri": "s3://my-bucket/my/prefix-", - }, - }, - } - ], - ), - ( - S3DataSourceConfig( - "s3://my-bucket/my/manifest.json", - content_type="text/csv", - ), - [ - { - "channelName": "input", - "dataSource": { - "s3DataSource": { - "s3Uri": "s3://my-bucket/my/manifest.json", - }, - }, - "contentType": "text/csv", - } - ], - ), - ( - { - "local-input": "local/prefix", - "s3-input": "s3://my-bucket/my/prefix-", - "config-input": S3DataSourceConfig( - "s3://my-bucket/my/manifest.json", - ), - }, - [ - { - "channelName": "local-input", - "dataSource": { - "s3DataSource": { - "s3Uri": "s3://default-bucket-name/jobs/job-name/ts/" - "data/local-input/prefix", - }, - }, - }, - { - "channelName": "s3-input", - "dataSource": { - "s3DataSource": { - "s3Uri": "s3://my-bucket/my/prefix-", - }, - }, - }, - { - "channelName": "config-input", - "dataSource": { - "s3DataSource": { - "s3Uri": "s3://my-bucket/my/manifest.json", - }, - }, - }, - ], - ), - ), -) -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, "ts") == input_data_configs diff --git a/test/unit_tests/braket/jobs/test_serialization.py b/test/unit_tests/braket/jobs/test_serialization.py deleted file mode 100644 index bbd1c623..00000000 --- a/test/unit_tests/braket/jobs/test_serialization.py +++ /dev/null @@ -1,58 +0,0 @@ -# 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.jobs.serialization import deserialize_values, serialize_values -from braket.jobs_data import PersistedJobDataFormat - - -@pytest.mark.parametrize( - "data_format, submitted_data, expected_serialized_data", - [ - ( - PersistedJobDataFormat.PLAINTEXT, - {"converged": True, "energy": -0.2}, - {"converged": True, "energy": -0.2}, - ), - ( - PersistedJobDataFormat.PICKLED_V4, - {"converged": True, "energy": -0.2}, - {"converged": "gASILg==\n", "energy": "gASVCgAAAAAAAABHv8mZmZmZmZou\n"}, - ), - ], -) -def test_job_serialize_data(data_format, submitted_data, expected_serialized_data): - serialized_data = serialize_values(submitted_data, data_format) - assert serialized_data == expected_serialized_data - - -@pytest.mark.parametrize( - "data_format, submitted_data, expected_deserialized_data", - [ - ( - PersistedJobDataFormat.PLAINTEXT, - {"converged": True, "energy": -0.2}, - {"converged": True, "energy": -0.2}, - ), - ( - PersistedJobDataFormat.PICKLED_V4, - {"converged": "gASILg==\n", "energy": "gASVCgAAAAAAAABHv8mZmZmZmZou\n"}, - {"converged": True, "energy": -0.2}, - ), - ], -) -def test_job_deserialize_data(data_format, submitted_data, expected_deserialized_data): - deserialized_data = deserialize_values(submitted_data, data_format) - assert deserialized_data == expected_deserialized_data diff --git a/test/unit_tests/braket/parametric/test_free_parameter.py b/test/unit_tests/braket/parametric/test_free_parameter.py deleted file mode 100644 index b94c60a9..00000000 --- a/test/unit_tests/braket/parametric/test_free_parameter.py +++ /dev/null @@ -1,69 +0,0 @@ -# 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.parametric import FreeParameter - - -@pytest.fixture -def free_parameter(): - return FreeParameter("theta") - - -def test_bad_input(): - 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): - assert isinstance(free_parameter, FreeParameter) - - -def test_equality(): - param_1 = FreeParameter("theta") - param_2 = FreeParameter("theta") - other_param = FreeParameter("phi") - non_param = "non circuit" - - assert param_1 == param_2 - assert param_1 is not param_2 - assert param_1 != other_param - assert param_1 != non_param - - -def test_str(free_parameter): - expected = "theta" - assert str(free_parameter) == expected - - -def test_hash(free_parameter): - assert hash(free_parameter) == hash(tuple(free_parameter.name)) - - -def test_rep(free_parameter): - assert repr(free_parameter) == free_parameter.name - - -def test_sub_successful(free_parameter): - assert free_parameter.subs({"theta": 1}) == 1 - - -def test_sub_wrong_param(free_parameter): - assert free_parameter.subs({"alpha": 1}) == FreeParameter("theta") diff --git a/test/unit_tests/braket/parametric/test_free_parameter_expression.py b/test/unit_tests/braket/parametric/test_free_parameter_expression.py deleted file mode 100644 index 1bf6818b..00000000 --- a/test/unit_tests/braket/parametric/test_free_parameter_expression.py +++ /dev/null @@ -1,183 +0,0 @@ -# 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.parametric import FreeParameter, FreeParameterExpression -from braket.parametric.free_parameter_expression import subs_if_free_parameter - - -@pytest.fixture -def free_parameter_expression(): - return FreeParameterExpression(FreeParameter("theta") - 1) - - -def test_is_free_param_expr(free_parameter_expression): - assert isinstance(free_parameter_expression, FreeParameterExpression) - - -@pytest.mark.xfail(raises=NotImplementedError) -def test_constructor_bad_input(): - FreeParameterExpression(["test"]) - - -def test_equality(): - expr_1 = FreeParameterExpression(FreeParameter("theta") + 1) - expr_2 = FreeParameterExpression(FreeParameter("theta") + 1) - other_expr = FreeParameterExpression(FreeParameter("theta")) - non_expr = "non circuit" - - assert expr_1 == expr_2 - assert expr_1 is not expr_2 - assert expr_1 != other_expr - assert expr_1 != non_expr - - -def test_equality_str(): - expr_1 = FreeParameterExpression("-theta+2*theta") - expr_2 = FreeParameterExpression(-FreeParameter("theta") + 2 * FreeParameter("theta")) - param_values = {"theta": 1} - assert expr_1 == expr_2 - assert expr_1.subs(param_values) == expr_2.subs(param_values) - assert hasattr(expr_1.expression, "free_symbols") and hasattr(expr_2.expression, "free_symbols") - - -@pytest.mark.xfail(raises=ValueError) -def test_unsupported_bin_op_str(): - FreeParameterExpression("theta/1") - - -@pytest.mark.xfail(raises=ValueError) -def test_unsupported_un_op_str(): - FreeParameterExpression("~theta") - - -@pytest.mark.xfail(raises=ValueError) -def test_unsupported_node_str(): - FreeParameterExpression("theta , 1") - - -def test_commutativity(): - add_1 = 1 + FreeParameterExpression(FreeParameter("theta")) - add_2 = FreeParameterExpression(FreeParameter("theta")) + 1 - mul_1 = FreeParameterExpression(FreeParameter("theta") * 1) - mul_2 = FreeParameterExpression(1 * FreeParameter("theta")) - - assert add_1 == add_2 - assert mul_1 == mul_2 - - -def test_add(): - add_expr = FreeParameter("theta") + FreeParameter("theta") - expected = FreeParameterExpression(2 * FreeParameter("theta")) - assert add_expr == expected - - -def test_sub(): - sub_expr = FreeParameter("theta") - FreeParameter("alpha") - 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 - - -def test_mul(): - mul_expr = FreeParameter("theta") * FreeParameter("alpha") * 2 * FreeParameter("theta") - expected = FreeParameterExpression(FreeParameter("theta") ** 2 * FreeParameter("alpha") * 2) - 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) - assert mul_expr == expected - - -def test_pow_constant(): - mul_expr = FreeParameter("theta") ** 2 - expected = FreeParameterExpression(FreeParameter("theta") ** 2) - assert mul_expr == expected - - -def test_r_pow(): - mul_expr = 2 ** FreeParameter("theta") - expected = FreeParameterExpression(2 ** FreeParameter("theta")) - assert mul_expr == expected - - -def test_neg(): - expr = FreeParameter("theta") * FreeParameter("alpha") * 2 - expected_expr = -FreeParameter("theta") * -FreeParameter("alpha") * -2 - assert -expr == expected_expr and -(-expr) == expr - - -def test_sub_string(): - theta = FreeParameter("theta") - expr = theta + 1 - assert expr.subs({"theta": 1}) == 2 - - -def test_sub_free_parameter(): - theta = FreeParameter("theta") - expr = theta + 1 - param_values = {theta: 1} - assert expr.subs(param_values) == 2 - - -def test_sub_return_expression(): - expr = FreeParameter("theta") + 1 + FreeParameter("alpha") - subbed_expr = expr.subs({"alpha": 1}) - expected = FreeParameter("theta") + 2 - - assert subbed_expr == expected - - -@pytest.mark.parametrize( - "param, kwargs, expected_value, expected_type", - [ - (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}, - 0.4 + 2 * FreeParameter("x"), - FreeParameterExpression, - ), - ], -) -def test_subs_if_free_parameter(param, kwargs, expected_value, expected_type): - value = subs_if_free_parameter(param, **kwargs) - assert value == expected_value - assert isinstance(value, expected_type) diff --git a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py deleted file mode 100644 index 56f02aa1..00000000 --- a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py +++ /dev/null @@ -1,1010 +0,0 @@ -# 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 typing import Union -from unittest.mock import Mock - -import numpy as np -import pytest -from openpulse import ast -from oqpy import IntVar - -from braket.pulse import ArbitraryWaveform, ConstantWaveform, DragGaussianWaveform, GaussianWaveform -from braket.pulse.ast.approximation_parser import _ApproximationParser -from braket.pulse.frame import Frame -from braket.pulse.port import Port -from braket.pulse.pulse_sequence import PulseSequence -from braket.registers.qubit_set import QubitSet -from braket.timings.time_series import TimeSeries, _all_close - - -@pytest.fixture -def port(): - return Port(port_id="device_port_x0", dt=1e-9, properties={}) - - -def test_bare_pulsequence(): - pulse_seq = PulseSequence() - results = pulse_seq.to_time_trace() - verify_results(results, {}, {}, {}) - - -def test_delay(port): - frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) - pulse_seq = PulseSequence().delay(frame, 3e-9) - - expected_amplitudes = {"frame1": TimeSeries()} - expected_frequencies = {"frame1": TimeSeries()} - expected_phases = {"frame1": TimeSeries()} - - expected_amplitudes["frame1"].put(0, 0).put(2e-9, 0) - expected_frequencies["frame1"].put(0, 1e8).put(2e-9, 1e8) - expected_phases["frame1"].put(0, 0).put(2e-9, 0) - - parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) - - verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - - -def test_delay_multiple_frames(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) - pulse_seq = ( - PulseSequence() - .play(frame1, ConstantWaveform(12e-9, 0.75)) # Inst1 - .delay([frame1, frame2], 10e-9) # Inst 2 - .play(frame1, ConstantWaveform(16e-9, 1)) # Inst3 - .play(frame2, ConstantWaveform(8e-9, -1)) # Inst4 - ) - - expected_amplitudes = {"frame1": TimeSeries(), "frame2": TimeSeries()} - expected_frequencies = {"frame1": TimeSeries(), "frame2": TimeSeries()} - expected_phases = {"frame1": TimeSeries(), "frame2": TimeSeries()} - - # Inst1 - shift_time_frame1 = 0 - shift_time_frame2 = 0 - pulse_length = 12e-9 - times = np.arange(0, pulse_length, port.dt) - values = 0.75 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) - expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) - expected_phases["frame1"].put(shift_time_frame1 + t, 0) - - # Inst2 - # Delay frame1 and frame2 by 10e-9 - # frame2 is 0 from 0ns to 21ns - shift_time_frame1 += pulse_length - pulse_length = 10e-9 - - expected_amplitudes["frame1"].put(shift_time_frame1, 0).put( - shift_time_frame1 + pulse_length - port.dt, 0 - ) - expected_frequencies["frame1"].put(shift_time_frame1, 1e8).put( - shift_time_frame1 + pulse_length - port.dt, 1e8 - ) - expected_phases["frame1"].put(shift_time_frame1, 0).put( - shift_time_frame1 + pulse_length - port.dt, 0 - ) - - # sync frames (to shift_time_frame1) and then add delay of pulse_length - expected_amplitudes["frame2"].put(0, 0).put(shift_time_frame1 + pulse_length - port.dt, 0) - expected_frequencies["frame2"].put(0, 1e8).put(shift_time_frame1 + pulse_length - port.dt, 1e8) - expected_phases["frame2"].put(0, 0).put(shift_time_frame1 + pulse_length - port.dt, 0) - - # Inst3 - shift_time_frame1 += pulse_length - pulse_length = 16e-9 - times = np.arange(0, pulse_length, port.dt) - values = 1 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) - expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) - expected_phases["frame1"].put(shift_time_frame1 + t, 0) - - # Inst4 - shift_time_frame2 = shift_time_frame1 - pulse_length = 8e-9 - times = np.arange(0, pulse_length, port.dt) - values = -1 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["frame2"].put(shift_time_frame2 + t, v) - expected_frequencies["frame2"].put(shift_time_frame2 + t, 1e8) - expected_phases["frame2"].put(shift_time_frame2 + t, 0) - - parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) - - verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - - -def test_delay_qubits(port): - frame1 = Frame(frame_id="q0_frame", port=port, frequency=1e8, phase=0, is_predefined=False) - frame2 = Frame(frame_id="q0_q1_frame", port=port, frequency=1e8, phase=0, is_predefined=False) - pulse_seq = ( - PulseSequence() - .play(frame1, ConstantWaveform(12e-9, 0.75)) # Inst1 - .delay(QubitSet([0, 1, 2]), 10e-9) # Inst 2 - .play(frame1, ConstantWaveform(16e-9, 1)) # Inst3 - .play(frame2, ConstantWaveform(8e-9, -1)) # Inst4 - ) - expected_amplitudes = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} - expected_frequencies = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} - expected_phases = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} - - # Inst1 - shift_time_frame1 = 0 - shift_time_frame2 = 0 - pulse_length = 12e-9 - times = np.arange(0, pulse_length, port.dt) - values = 0.75 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["q0_frame"].put(shift_time_frame1 + t, v) - expected_frequencies["q0_frame"].put(shift_time_frame1 + t, 1e8) - expected_phases["q0_frame"].put(shift_time_frame1 + t, 0) - - # Inst2 - # Delay frame1 and frame2 by 10e-9 - # frame2 is 0 from 0ns to 21ns - shift_time_frame1 += pulse_length - pulse_length = 10e-9 - - expected_amplitudes["q0_frame"].put(shift_time_frame1, 0).put( - shift_time_frame1 + pulse_length - port.dt, 0 - ) - expected_frequencies["q0_frame"].put(shift_time_frame1, 1e8).put( - shift_time_frame1 + pulse_length - port.dt, 1e8 - ) - expected_phases["q0_frame"].put(shift_time_frame1, 0).put( - shift_time_frame1 + pulse_length - port.dt, 0 - ) - - # sync frames (to shift_time_frame1) and then add delay of pulse_length - expected_amplitudes["q0_q1_frame"].put(0, 0).put(shift_time_frame1 + pulse_length - port.dt, 0) - expected_frequencies["q0_q1_frame"].put(0, 1e8).put( - shift_time_frame1 + pulse_length - port.dt, 1e8 - ) - expected_phases["q0_q1_frame"].put(0, 0).put(shift_time_frame1 + pulse_length - port.dt, 0) - - # Inst3 - shift_time_frame1 += pulse_length - pulse_length = 16e-9 - times = np.arange(0, pulse_length, port.dt) - values = 1 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["q0_frame"].put(shift_time_frame1 + t, v) - expected_frequencies["q0_frame"].put(shift_time_frame1 + t, 1e8) - expected_phases["q0_frame"].put(shift_time_frame1 + t, 0) - - # Inst4 - shift_time_frame2 = shift_time_frame1 - pulse_length = 8e-9 - times = np.arange(0, pulse_length, port.dt) - values = -1 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["q0_q1_frame"].put(shift_time_frame2 + t, v) - expected_frequencies["q0_q1_frame"].put(shift_time_frame2 + t, 1e8) - expected_phases["q0_q1_frame"].put(shift_time_frame2 + t, 0) - - parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) - - verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - assert list(parser.amplitudes.keys()) == ["q0_frame", "q0_q1_frame"] # no frame belonging to $2 - - -def test_delay_no_args(port): - frame1 = Frame(frame_id="q0_frame", port=port, frequency=1e8, phase=0, is_predefined=False) - frame2 = Frame(frame_id="q0_q1_frame", port=port, frequency=1e8, phase=0, is_predefined=False) - pulse_seq = ( - PulseSequence() - .play(frame1, ConstantWaveform(12e-9, 0.75)) # Inst1 - .delay([], 10e-9) # Inst 2 - .play(frame1, ConstantWaveform(16e-9, 1)) # Inst3 - .play(frame2, ConstantWaveform(8e-9, -1)) # Inst4 - ) - expected_amplitudes = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} - expected_frequencies = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} - expected_phases = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} - - # Inst1 - shift_time_frame1 = 0 - shift_time_frame2 = 0 - pulse_length = 12e-9 - times = np.arange(0, pulse_length, port.dt) - values = 0.75 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["q0_frame"].put(shift_time_frame1 + t, v) - expected_frequencies["q0_frame"].put(shift_time_frame1 + t, 1e8) - expected_phases["q0_frame"].put(shift_time_frame1 + t, 0) - - # Inst2 - # Delay frame1 and frame2 by 10e-9 - # frame2 is 0 from 0ns to 21ns - shift_time_frame1 += pulse_length - pulse_length = 10e-9 - - expected_amplitudes["q0_frame"].put(shift_time_frame1, 0).put( - shift_time_frame1 + pulse_length - port.dt, 0 - ) - expected_frequencies["q0_frame"].put(shift_time_frame1, 1e8).put( - shift_time_frame1 + pulse_length - port.dt, 1e8 - ) - expected_phases["q0_frame"].put(shift_time_frame1, 0).put( - shift_time_frame1 + pulse_length - port.dt, 0 - ) - - # sync frames (to shift_time_frame1) and then add delay of pulse_length - expected_amplitudes["q0_q1_frame"].put(0, 0).put(shift_time_frame1 + pulse_length - port.dt, 0) - expected_frequencies["q0_q1_frame"].put(0, 1e8).put( - shift_time_frame1 + pulse_length - port.dt, 1e8 - ) - expected_phases["q0_q1_frame"].put(0, 0).put(shift_time_frame1 + pulse_length - port.dt, 0) - - # Inst3 - shift_time_frame1 += pulse_length - pulse_length = 16e-9 - times = np.arange(0, pulse_length, port.dt) - values = 1 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["q0_frame"].put(shift_time_frame1 + t, v) - expected_frequencies["q0_frame"].put(shift_time_frame1 + t, 1e8) - expected_phases["q0_frame"].put(shift_time_frame1 + t, 0) - - # Inst4 - shift_time_frame2 = shift_time_frame1 - pulse_length = 8e-9 - times = np.arange(0, pulse_length, port.dt) - values = -1 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["q0_q1_frame"].put(shift_time_frame2 + t, v) - expected_frequencies["q0_q1_frame"].put(shift_time_frame2 + t, 1e8) - expected_phases["q0_q1_frame"].put(shift_time_frame2 + t, 0) - - parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) - - verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - assert list(parser.amplitudes.keys()) == ["q0_frame", "q0_q1_frame"] # no frame belonging to $2 - - -def test_predefined_frame(port): - frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=True) - pulse_seq = PulseSequence().delay(frame, 3e-9) - - expected_amplitudes = {"frame1": TimeSeries()} - expected_frequencies = {"frame1": TimeSeries()} - expected_phases = {"frame1": TimeSeries()} - - expected_amplitudes["frame1"].put(0, 0).put(2e-9, 0) - expected_frequencies["frame1"].put(0, 1e8).put(2e-9, 1e8) - expected_phases["frame1"].put(0, 0).put(2e-9, 0) - - for statement in pulse_seq._program._state.body: - assert not isinstance(statement, ast.FrameType) - - parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) - - verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - - -def test_set_shift_phase(port): - frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) - pulse_seq = ( - PulseSequence() - .set_phase(frame, 1) - .delay(frame, 2e-9) - .shift_phase(frame, 2) - .delay(frame, 5e-9) - .set_phase(frame, 0) - ) - expected_amplitudes = {"frame1": TimeSeries()} - expected_frequencies = {"frame1": TimeSeries()} - expected_phases = {"frame1": TimeSeries()} - - expected_amplitudes["frame1"].put(0, 0).put(1e-9, 0) - expected_frequencies["frame1"].put(0, 1e8).put(1e-9, 1e8) - expected_phases["frame1"].put(0, 1).put(1e-9, 1) - - expected_amplitudes["frame1"].put(2e-9, 0).put(6e-9, 0) - expected_frequencies["frame1"].put(2e-9, 1e8).put(6e-9, 1e8) - expected_phases["frame1"].put(2e-9, 3).put(6e-9, 3) - - parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) - - verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - - -def test_set_shift_phase_beyond_2_pi(port): - frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) - pulse_seq = ( - PulseSequence() - .set_phase(frame, 5 * np.pi / 2) - .delay(frame, 2e-9) - .shift_phase(frame, -np.pi) - .delay(frame, 5e-9) - .set_phase(frame, 0) - ) - expected_amplitudes = {"frame1": TimeSeries()} - expected_frequencies = {"frame1": TimeSeries()} - expected_phases = {"frame1": TimeSeries()} - - # 5pi/2 is reduced to pi/2 - expected_amplitudes["frame1"].put(0, 0).put(1e-9, 0) - expected_frequencies["frame1"].put(0, 1e8).put(1e-9, 1e8) - expected_phases["frame1"].put(0, np.pi / 2).put(1e-9, np.pi / 2) - - # shift_phase adds -pi to the phase of the last point -> 3pi/2 - expected_amplitudes["frame1"].put(2e-9, 0).put(6e-9, 0) - expected_frequencies["frame1"].put(2e-9, 1e8).put(6e-9, 1e8) - expected_phases["frame1"].put(2e-9, 3 * np.pi / 2).put(6e-9, 3 * np.pi / 2) - - parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) - - 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 = ( - PulseSequence() - .set_frequency(frame, 2e8) - .delay(frame, 20e-9) - .shift_frequency(frame, -0.1e8) - .delay(frame, 10e-9) - ) - - expected_amplitudes = {"frame1": TimeSeries()} - expected_frequencies = {"frame1": TimeSeries()} - expected_phases = {"frame1": TimeSeries()} - - expected_amplitudes["frame1"].put(0, 0).put(19e-9, 0) - expected_frequencies["frame1"].put(0, 2e8).put(19e-9, 2e8) - expected_phases["frame1"].put(0, 0).put(19e-9, 0) - - expected_amplitudes["frame1"].put(20e-9, 0).put(29e-9, 0) - expected_frequencies["frame1"].put(20e-9, 1.9e8).put(29e-9, 1.9e8) - expected_phases["frame1"].put(20e-9, 0).put(29e-9, 0) - - parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) - - verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - - -def test_play_arbitrary_waveforms(port): - frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) - my_arb_wf = ArbitraryWaveform([0.4 + 0.1j, -0.8 + 0.1j, 1 + 0.2j]) - pulse_seq = PulseSequence().play(frame, my_arb_wf).capture_v0(frame) - - expected_amplitudes = {"frame1": TimeSeries()} - expected_frequencies = {"frame1": TimeSeries()} - expected_phases = {"frame1": TimeSeries()} - - times = np.arange(0, 4e-9, port.dt) - for t, v in zip(times, [0.4 + 0.1j, -0.8 + 0.1j, 1 + 0.2j]): - 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(frame)) - - verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - - -@pytest.mark.xfail(raises=NameError) -def test_missing_waveform(port): - frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) - my_arb_wf = ArbitraryWaveform([0.4 + 0.1j, -0.8 + 0.1j, 1 + 0.2j]) - pulse_seq = PulseSequence() - identifier = my_arb_wf._to_oqpy_expression() - identifier._needs_declaration = False - pulse_seq._program.play(frame, identifier.to_ast(pulse_seq._program)) - _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) - - -def test_play_literal(port): - frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) - pulse_seq = PulseSequence() - pulse_seq._program.play(frame=frame, waveform=[0.4 + 0.1j, -0.8 + 0.1j, 1 + 0.2j]) - - expected_amplitudes = {"frame1": TimeSeries()} - expected_frequencies = {"frame1": TimeSeries()} - expected_phases = {"frame1": TimeSeries()} - - times = np.arange(0, 4e-9, port.dt) - for t, v in zip(times, [0.4 + 0.1j, -0.8 + 0.1j, 1 + 0.2j]): - 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(frame)) - - verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - - -def test_classical_variable_declaration(port): - frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) - pulse_seq = PulseSequence() - i = IntVar(5, "i") - pulse_seq._program.increment(i, 1) - - with pytest.raises(NotImplementedError): - _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) - - -def test_play_constant_waveforms(port): - frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) - length = 6e-9 - pulse_seq = PulseSequence().play(frame, ConstantWaveform(length, 0.75)) - - expected_amplitudes = {"frame1": TimeSeries()} - expected_frequencies = {"frame1": TimeSeries()} - expected_phases = {"frame1": TimeSeries()} - - times = np.arange(0, length, port.dt) - values = 0.75 * np.ones_like(times) - 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(frame)) - - verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - - -def test_set_scale(port): - frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) - pulse_seq = ( - PulseSequence() - .play(frame, ConstantWaveform(10e-9, 0.66)) - .set_scale(frame, 0.5) - .play(frame, ConstantWaveform(20e-9, 0.66)) - ) - - expected_amplitudes = {"frame1": TimeSeries()} - expected_frequencies = {"frame1": TimeSeries()} - expected_phases = {"frame1": TimeSeries()} - - times = np.arange(0, 10e-9, port.dt) - values = 0.66 * np.ones_like(times) - 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) - - shift_time = 10e-9 - times = np.arange(0, 20e-9, port.dt) - values = 0.33 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["frame1"].put(shift_time + t, v) - expected_frequencies["frame1"].put(shift_time + t, 1e8) - expected_phases["frame1"].put(shift_time + t, 0) - - parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame)) - - verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - - -def test_play_gaussian_waveforms(port): - frame1 = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) - - # First gaussian with zero_at_edges=False - gaussian_wf_ZaE_False = GaussianWaveform( - length=1e-8, sigma=1.69e-9, amplitude=1.0, zero_at_edges=False - ) - pulse_seq = PulseSequence().play(frame1, gaussian_wf_ZaE_False) - - times = np.arange(0, 1e-8, port.dt) - values = np.array( - [ - complex(0.012568049266111367), - complex(0.06074792381889125), - complex(0.20688853998666168), - complex(0.49645839613814063), - complex(0.839403382587157), - complex(1.0), - complex(0.839403382587157), - complex(0.49645839613814063), - complex(0.20688853998666168), - complex(0.06074792381889125), - ], - 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) - - # Add a delay between the two waveforms - pulse_seq.delay(frame1, 2e-8) - expected_amplitudes["frame1"].put(1e-8, 0).put(29e-9, 0) - expected_frequencies["frame1"].put(1e-8, 1e8).put(29e-9, 1e8) - expected_phases["frame1"].put(1e-8, 0).put(29e-9, 0) - - # Second gaussian with zero_at_edges=True - gaussian_wf_ZaE_True = GaussianWaveform( - length=1e-8, sigma=1.69e-9, amplitude=1.0, zero_at_edges=True - ) - pulse_seq.play(frame1, gaussian_wf_ZaE_True) - - times = np.arange(0, 1e-8, port.dt) - values = np.array( - [ - complex(0.0), - complex(0.04879310874736347), - complex(0.1967938049565093), - complex(0.49004931075238933), - complex(0.8373593063365198), - complex(1.0), - complex(0.8373593063365196), - complex(0.49004931075238906), - complex(0.1967938049565092), - complex(0.048793108747363416), - ], - dtype=np.complex128, - ) - - shift_time = 30e-9 - for t, v in zip(times, values): - expected_amplitudes["frame1"].put(t + shift_time, v) - expected_frequencies["frame1"].put(t + shift_time, 1e8) - expected_phases["frame1"].put(t + shift_time, 0) - - parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame1)) - - verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - - -def test_play_drag_gaussian_waveforms(port): - frame1 = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) - - expected_amplitudes = {"frame1": TimeSeries()} - expected_frequencies = {"frame1": TimeSeries()} - expected_phases = {"frame1": TimeSeries()} - - drag_gaussian_wf_ZaE_False = DragGaussianWaveform( - length=1e-8, sigma=1.69e-9, amplitude=1.0, beta=1e-9, zero_at_edges=False - ) - drag_gaussian_wf_ZaE_True = DragGaussianWaveform( - length=1e-8, sigma=1.69e-9, amplitude=1.0, beta=1e-9, zero_at_edges=True - ) - pulse_seq = ( - PulseSequence() - .play(frame1, drag_gaussian_wf_ZaE_False) - .delay(frame1, 2e-8) - .play(frame1, drag_gaussian_wf_ZaE_True) - ) - - # First DRAG_Gaussian with zero_at_edges=False - times = np.arange(0, 1e-8, port.dt) - values = np.array( - [ - complex(0.012568049266111367, 0.02200211698839566), - complex(0.06074792381889125, 0.08507814687005531), - complex(0.20688853998666168, 0.21731228597037397), - complex(0.49645839613814063, 0.3476477687322857), - complex(0.839403382587157, 0.29389845684225235), - complex(1.0, 0.0), - complex(0.839403382587157, -0.29389845684225235), - complex(0.49645839613814063, -0.3476477687322857), - complex(0.20688853998666168, -0.21731228597037397), - complex(0.06074792381889125, -0.08507814687005531), - ], - dtype=np.complex128, - ) - - 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) - - # Add a delay between the two waveforms - shift_time = 10e-9 - expected_amplitudes["frame1"].put(shift_time + 0, 0).put(shift_time + 19e-9, 0) - expected_frequencies["frame1"].put(shift_time + 0, 1e8).put(shift_time + 19e-9, 1e8) - expected_phases["frame1"].put(shift_time + 0, 0).put(shift_time + 19e-9, 0) - - # Second DRAG_Gaussian with zero_at_edges=True - times = np.arange(0, 1e-8, port.dt) - values = np.array( - [ - complex(0.0, 0.0), - complex(0.04879310874736347, 0.0683352946288484), - complex(0.1967938049565093, 0.20670894396888342), - complex(0.49004931075238933, 0.34315977084303023), - complex(0.8373593063365198, 0.2931827689284408), - complex(1.0, 0), - complex(0.8373593063365196, -0.293182768928441), - complex(0.49004931075238906, -0.34315977084303023), - complex(0.1967938049565092, -0.20670894396888334), - complex(0.048793108747363416, -0.06833529462884834), - ], - dtype=np.complex128, - ) - - 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) - expected_phases["frame1"].put(t + shift_time, 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) - pulse_seq = ( - PulseSequence() - .play(frame1, ConstantWaveform(12e-9, 0.75)) # Inst1 - .barrier([frame1, frame2]) # Inst2 - .play(frame1, ConstantWaveform(16e-9, 1)) # Inst3 - .play(frame2, ConstantWaveform(8e-9, -1)) # Inst4 - ) - - expected_amplitudes = {"frame1": TimeSeries(), "frame2": TimeSeries()} - expected_frequencies = {"frame1": TimeSeries(), "frame2": TimeSeries()} - expected_phases = {"frame1": TimeSeries(), "frame2": TimeSeries()} - - # Inst1 - shift_time_frame1 = 0 - pulse_length = 12e-9 - times = np.arange(0, pulse_length, port.dt) - values = 0.75 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) - expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) - expected_phases["frame1"].put(shift_time_frame1 + t, 0) - - # Inst2 - # Delay frame2 from 0ns to 11ns - shift_time_frame2 = 0 - - expected_amplitudes["frame2"].put(0, 0).put(11e-9, 0) - expected_frequencies["frame2"].put(0, 1e8).put(11e-9, 1e8) - expected_phases["frame2"].put(0, 0).put(11e-9, 0) - - # Inst3 - shift_time_frame1 += pulse_length - pulse_length = 16e-9 - times = np.arange(0, pulse_length, port.dt) - values = 1 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) - expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) - expected_phases["frame1"].put(shift_time_frame1 + t, 0) - - # Inst4 - shift_time_frame2 = shift_time_frame1 - pulse_length = 8e-9 - times = np.arange(0, pulse_length, port.dt) - values = -1 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["frame2"].put(shift_time_frame2 + t, v) - expected_frequencies["frame2"].put(shift_time_frame2 + t, 1e8) - expected_phases["frame2"].put(shift_time_frame2 + t, 0) - - parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) - - verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - - -def test_barrier_no_args(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) - pulse_seq = ( - PulseSequence() - .play(frame1, ConstantWaveform(12e-9, 0.75)) # Inst1 - .barrier([]) # Inst2 - .play(frame1, ConstantWaveform(16e-9, 1)) # Inst3 - .play(frame2, ConstantWaveform(8e-9, -1)) # Inst4 - ) - - expected_amplitudes = {"frame1": TimeSeries(), "frame2": TimeSeries()} - expected_frequencies = {"frame1": TimeSeries(), "frame2": TimeSeries()} - expected_phases = {"frame1": TimeSeries(), "frame2": TimeSeries()} - - # Inst1 - shift_time_frame1 = 0 - pulse_length = 12e-9 - times = np.arange(0, pulse_length, port.dt) - values = 0.75 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) - expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) - expected_phases["frame1"].put(shift_time_frame1 + t, 0) - - # Inst2 - # Delay frame2 from 0ns to 11ns - shift_time_frame2 = 0 - - expected_amplitudes["frame2"].put(0, 0).put(11e-9, 0) - expected_frequencies["frame2"].put(0, 1e8).put(11e-9, 1e8) - expected_phases["frame2"].put(0, 0).put(11e-9, 0) - - # Inst3 - shift_time_frame1 += pulse_length - pulse_length = 16e-9 - times = np.arange(0, pulse_length, port.dt) - values = 1 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) - expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) - expected_phases["frame1"].put(shift_time_frame1 + t, 0) - - # Inst4 - shift_time_frame2 = shift_time_frame1 - pulse_length = 8e-9 - times = np.arange(0, pulse_length, port.dt) - values = -1 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["frame2"].put(shift_time_frame2 + t, v) - expected_frequencies["frame2"].put(shift_time_frame2 + t, 1e8) - expected_phases["frame2"].put(shift_time_frame2 + t, 0) - - parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) - - verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - - -def test_barrier_qubits(port): - frame1 = Frame(frame_id="q0_frame", port=port, frequency=1e8, phase=0, is_predefined=False) - frame2 = Frame(frame_id="q0_q1_frame", port=port, frequency=1e8, phase=0, is_predefined=False) - pulse_seq = ( - PulseSequence() - .play(frame1, ConstantWaveform(12e-9, 0.75)) # Inst1 - .barrier(QubitSet([0, 1, 2])) # Inst 2 - .play(frame1, ConstantWaveform(16e-9, 1)) # Inst3 - .play(frame2, ConstantWaveform(8e-9, -1)) # Inst4 - ) - - expected_amplitudes = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} - expected_frequencies = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} - expected_phases = {"q0_frame": TimeSeries(), "q0_q1_frame": TimeSeries()} - - # Inst1 - shift_time_frame1 = 0 - pulse_length = 12e-9 - times = np.arange(0, pulse_length, port.dt) - values = 0.75 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["q0_frame"].put(shift_time_frame1 + t, v) - expected_frequencies["q0_frame"].put(shift_time_frame1 + t, 1e8) - expected_phases["q0_frame"].put(shift_time_frame1 + t, 0) - - # Inst2 - # Delay frame2 from 0ns to 11ns - shift_time_frame2 = 0 - - expected_amplitudes["q0_q1_frame"].put(0, 0).put(11e-9, 0) - expected_frequencies["q0_q1_frame"].put(0, 1e8).put(11e-9, 1e8) - expected_phases["q0_q1_frame"].put(0, 0).put(11e-9, 0) - - # Inst3 - shift_time_frame1 += pulse_length - pulse_length = 16e-9 - times = np.arange(0, pulse_length, port.dt) - values = 1 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["q0_frame"].put(shift_time_frame1 + t, v) - expected_frequencies["q0_frame"].put(shift_time_frame1 + t, 1e8) - expected_phases["q0_frame"].put(shift_time_frame1 + t, 0) - - # Inst4 - shift_time_frame2 = shift_time_frame1 - pulse_length = 8e-9 - times = np.arange(0, pulse_length, port.dt) - values = -1 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["q0_q1_frame"].put(shift_time_frame2 + t, v) - expected_frequencies["q0_q1_frame"].put(shift_time_frame2 + t, 1e8) - expected_phases["q0_q1_frame"].put(shift_time_frame2 + t, 0) - - parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) - - verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - assert list(parser.amplitudes.keys()) == ["q0_frame", "q0_q1_frame"] # no frame belonging to $2 - - -def test_barrier_different_dt(): - port1 = Port(port_id="device_port_x1", dt=5e-9, properties={}) - port2 = Port(port_id="device_port_x2", dt=4e-9, properties={}) - frame1 = Frame(frame_id="frame1", port=port1, frequency=1e8, phase=0, is_predefined=False) - frame2 = Frame(frame_id="frame2", port=port2, frequency=1e8, phase=0, is_predefined=False) - pulse_seq = ( - PulseSequence() - .play(frame1, ConstantWaveform(25e-9, 0.75)) # Inst 1 - .barrier([frame1, frame2]) # Inst 2 - .play(frame1, ConstantWaveform(40e-9, 1)) # Inst 3 - .play(frame2, ConstantWaveform(28e-9, -1)) # Inst 4 - ) - - expected_amplitudes = {"frame1": TimeSeries(), "frame2": TimeSeries()} - expected_frequencies = {"frame1": TimeSeries(), "frame2": TimeSeries()} - expected_phases = {"frame1": TimeSeries(), "frame2": TimeSeries()} - - # Inst1 - shift_time_frame1 = 0 - pulse_length = 25e-9 - times = np.arange(0, pulse_length, port1.dt) - values = 0.75 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) - expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) - expected_phases["frame1"].put(shift_time_frame1 + t, 0) - - # Inst2 - # barrier at 40ns (first point coinciding with frame2 (every 20ns)) - shift_time_frame1 = shift_time_frame2 = 40e-9 - - latest_time_frame1 = expected_amplitudes["frame1"].times()[-1] - expected_amplitudes["frame1"].put(latest_time_frame1 + port1.dt, 0).put( - shift_time_frame1 - port1.dt, 0 - ) - expected_frequencies["frame1"].put(latest_time_frame1 + port1.dt, 1e8).put( - shift_time_frame1 - port1.dt, 1e8 - ) - expected_phases["frame1"].put(latest_time_frame1 + port1.dt, 0).put( - shift_time_frame1 - port1.dt, 0 - ) - - expected_amplitudes["frame2"].put(0, 0).put(shift_time_frame2 - port2.dt, 0) - expected_frequencies["frame2"].put(0, 1e8).put(shift_time_frame2 - port2.dt, 1e8) - expected_phases["frame2"].put(0, 0).put(shift_time_frame2 - port2.dt, 0) - - # Inst3 - pulse_length = 40e-9 - times = np.arange(0, pulse_length, port1.dt) - values = 1 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) - expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) - expected_phases["frame1"].put(shift_time_frame1 + t, 0) - - # Inst4 - pulse_length = 28e-9 - times = np.arange(0, pulse_length, port2.dt) - values = -1 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["frame2"].put(shift_time_frame2 + t, v) - expected_frequencies["frame2"].put(shift_time_frame2 + t, 1e8) - expected_phases["frame2"].put(shift_time_frame2 + t, 0) - - parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) - - verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - - -def test_pad_different_dt(port): - port1 = Port(port_id="device_port_x1", dt=5e-9, properties={}) - port2 = Port(port_id="device_port_x2", dt=4e-9, properties={}) - frame1 = Frame(frame_id="frame1", port=port1, frequency=1e8, phase=0, is_predefined=False) - frame2 = Frame(frame_id="frame2", port=port2, frequency=1e8, phase=0, is_predefined=False) - - pulse_seq = ( - PulseSequence() - .play(frame1, ConstantWaveform(20e-9, 0.75)) - .play(frame2, ConstantWaveform(28e-9, -1)) - ) - - expected_amplitudes = {"frame1": TimeSeries(), "frame2": TimeSeries()} - expected_frequencies = {"frame1": TimeSeries(), "frame2": TimeSeries()} - expected_phases = {"frame1": TimeSeries(), "frame2": TimeSeries()} - - # Inst1 - shift_time_frame1 = 0 - pulse_length = 20e-9 - times = np.arange(0, pulse_length, port1.dt) - values = 0.75 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["frame1"].put(shift_time_frame1 + t, v) - expected_frequencies["frame1"].put(shift_time_frame1 + t, 1e8) - expected_phases["frame1"].put(shift_time_frame1 + t, 0) - - # Inst2 - shift_time_frame2 = 0 - pulse_length = 28e-9 - times = np.arange(0, pulse_length, port2.dt) - values = -1 * np.ones_like(times) - for t, v in zip(times, values): - expected_amplitudes["frame2"].put(shift_time_frame2 + t, v) - expected_frequencies["frame2"].put(shift_time_frame2 + t, 1e8) - expected_phases["frame2"].put(shift_time_frame2 + t, 0) - - parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) - - verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) - - -@pytest.mark.parametrize( - "literal_type, lhs, operator, rhs, expected_result", - [ - (ast.FloatLiteral, 3, "+", 2, 5), - (ast.FloatLiteral, 3, "-", 2, 1), - (ast.FloatLiteral, 3, "*", 2, 6), - (ast.FloatLiteral, 3, "/", 2, 1.5), - (ast.FloatLiteral, 3, "%", 2, 1), - (ast.FloatLiteral, 3, "**", 2, 9), - (ast.FloatLiteral, 3, "<", 2, False), - (ast.FloatLiteral, 3, ">", 2, True), - (ast.FloatLiteral, 3, ">=", 2, True), - (ast.FloatLiteral, 3, "<=", 2, False), - (ast.FloatLiteral, 3, "==", 2, False), - (ast.FloatLiteral, 3, "!=", 2, True), - (ast.BooleanLiteral, True, "&&", False, False), - (ast.BooleanLiteral, True, "||", False, True), - (ast.BooleanLiteral, True, "|", False, True), - (ast.BooleanLiteral, True, "^", False, True), - (ast.BooleanLiteral, True, "&", False, False), - (ast.IntegerLiteral, 3, "<<", 2, 12), - (ast.IntegerLiteral, 3, ">>", 2, 0), - ], -) -def test_binary_operations(literal_type, lhs, operator, rhs, expected_result): - expression = ast.BinaryExpression( - lhs=literal_type(lhs), - op=ast.BinaryOperator[operator], - rhs=literal_type(rhs), - ) - pulse_seq = PulseSequence() - parser = _ApproximationParser(program=pulse_seq._program, frames={}) - result = parser.visit_BinaryExpression(expression, Mock()) - assert result == expected_result - - -@pytest.mark.parametrize( - "literal_type, operator, literal, expected_result", - [ - (ast.FloatLiteral, "-", 3, -3), - (ast.BooleanLiteral, "!", True, False), - (ast.IntegerLiteral, "~", 3, ~3), - ], -) -def test_unary_operations(literal_type, operator, literal, expected_result): - expression = ast.UnaryExpression( - op=ast.UnaryOperator[operator], expression=literal_type(literal) - ) - pulse_seq = PulseSequence() - parser = _ApproximationParser(program=pulse_seq._program, frames={}) - result = parser.visit_UnaryExpression(expression, Mock()) - assert result == expected_result - - -@pytest.mark.xfail(raises=ValueError) -def test_duration_literal(): - literal = Mock() - pulse_seq = PulseSequence() - parser = _ApproximationParser(program=pulse_seq._program, frames={}) - parser.visit_DurationLiteral(literal, Mock()) - - -def verify_results(results, expected_amplitudes, expected_frequencies, expected_phases): - for frame_id in expected_amplitudes.keys(): - assert _all_close(results.amplitudes[frame_id], expected_amplitudes[frame_id], 1e-10) - assert _all_close(results.frequencies[frame_id], expected_frequencies[frame_id], 1e-10) - 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): - frames = [frames] - frame_dict = dict() - for frame in frames: - frame_dict[frame.id] = frame - return frame_dict diff --git a/test/unit_tests/braket/pulse/test_frame.py b/test/unit_tests/braket/pulse/test_frame.py deleted file mode 100644 index b70fe7ee..00000000 --- a/test/unit_tests/braket/pulse/test_frame.py +++ /dev/null @@ -1,72 +0,0 @@ -# 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 oqpy import FrameVar as OQpyFrame -from oqpy import PortVar -from oqpy.base import expr_matches - -from braket.pulse import Frame, Port - - -@pytest.fixture -def port(): - return Port("test_port_ff", dt=1e-9) - - -@pytest.fixture -def frame_id(): - return "test_frame_rf" - - -def test_frame_no_properties(frame_id, port): - frame = Frame(frame_id, port, 1e6, is_predefined=True) - assert frame.id == frame_id - assert frame.port == port - assert frame.frequency == 1e6 - assert frame.phase == 0 - assert frame.is_predefined - assert frame.properties is None - - -def test_frame_to_oqpy_expression(port, frame_id): - frequency = 4e7 - phase = 1.57 - frame = Frame(frame_id, port, frequency, phase, True, {"dummy_property": "foo"}) - expected_expression = OQpyFrame( - port=PortVar(port.id), - frequency=frequency, - phase=phase, - name=frame_id, - needs_declaration=False, - ) - oq_expression = frame._to_oqpy_expression() - assert expr_matches(oq_expression, expected_expression) - - -def test_frame_equality(frame_id, port): - f = Frame(frame_id, port, 1e4, 0.57) - uneqs = [ - Frame("wrong_id", port, f.frequency, f.phase, {"foo": "bar"}), - Frame(f.id, Port("foo", dt=1e-9), f.frequency, f.phase), - Frame(f.id, f.port, 1e5, f.phase), - Frame(f.id, f.port, f.frequency, 0.23), - ] - eqs = [ - Frame(f.id, f.port, f.frequency, f.phase, {"a": "b"}), - Frame(f.id, f.port, f.frequency, f.phase), - ] - for x in uneqs: - assert f != x - for x in eqs: - assert f == x diff --git a/test/unit_tests/braket/pulse/test_port.py b/test/unit_tests/braket/pulse/test_port.py deleted file mode 100644 index f307ea87..00000000 --- a/test/unit_tests/braket/pulse/test_port.py +++ /dev/null @@ -1,61 +0,0 @@ -# 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 oqpy import PortVar -from oqpy.base import expr_matches - -from braket.pulse import Port - - -@pytest.fixture -def port_id(): - return "test_port_ff" - - -@pytest.fixture -def port_time_resolution(): - return 1e-9 - - -@pytest.fixture -def port_properties(): - return {"dt": 1e-6, "direction": "tx"} - - -@pytest.fixture -def port(port_id, port_time_resolution, port_properties): - return Port(port_id, port_time_resolution, port_properties) - - -def test_port_no_properties(port_id, port_time_resolution): - port = Port(port_id, port_time_resolution) - assert port.id == port_id - assert port.dt == port_time_resolution - assert port.properties is None - - -def test_port_to_oqpy_expression(port, port_id): - expected_expression = PortVar(port_id) - assert expr_matches(port._to_oqpy_expression(), expected_expression) - - -def test_port_equality(port, port_time_resolution): - p2 = Port(port.id, port_time_resolution) - p3 = Port("random_id", port_time_resolution, properties=port.properties) - p4 = Port(port.id, port_time_resolution, properties={"random_property": "foo"}) - p5 = Port(port.id, 0) - assert port == p2 - assert port == p4 - assert port == p5 - assert port != p3 diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py deleted file mode 100644 index da8e0a8a..00000000 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ /dev/null @@ -1,418 +0,0 @@ -# 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 FreeParameter, QubitSet -from braket.pulse import ( - ArbitraryWaveform, - ConstantWaveform, - DragGaussianWaveform, - Frame, - GaussianWaveform, - Port, - PulseSequence, -) - - -@pytest.fixture -def port(): - return Port(port_id="device_port_x0", dt=1e-3, properties={}) - - -@pytest.fixture -def predefined_frame_1(port): - return Frame( - frame_id="predefined_frame_1", frequency=2e9, port=port, phase=0, is_predefined=True - ) - - -@pytest.fixture -def predefined_frame_2(port): - return Frame(frame_id="predefined_frame_2", frequency=1e6, port=port, is_predefined=True) - - -@pytest.fixture -def user_defined_frame(port): - return Frame( - frame_id="user_defined_frame_0", - port=port, - frequency=1e7, - phase=3.14, - is_predefined=False, - properties={"associatedGate": "rz"}, - ) - - -@pytest.fixture -def conflicting_user_defined_frame(): - return Frame( - frame_id="user_defined_frame_0", - port=Port("wrong_port", dt=1e-9), - frequency=1e7, - phase=3.14, - is_predefined=False, - properties={"associatedGate": "rz"}, - ) - - -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);", - "}", - ] - ) - assert pulse_sequence.to_ir() == expected_str - - -def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined_frame_2): - param = FreeParameter("a") + 2 * FreeParameter("b") - pulse_sequence = ( - PulseSequence() - .set_frequency(predefined_frame_1, param) - .shift_frequency(predefined_frame_1, param) - .set_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) - .delay(predefined_frame_1, param) - .delay(predefined_frame_1, 1e-3) - .barrier([predefined_frame_1, predefined_frame_2]) - .play( - predefined_frame_1, - GaussianWaveform( - length=FreeParameter("length_g"), sigma=FreeParameter("sigma_g"), id="gauss_wf" - ), - ) - .play( - predefined_frame_2, - DragGaussianWaveform( - length=FreeParameter("length_dg"), - sigma=FreeParameter("sigma_dg"), - beta=0.2, - id="drag_gauss_wf", - ), - ) - .play( - predefined_frame_1, - ConstantWaveform( - length=FreeParameter("length_c"), iq=complex(2, 0.3), id="constant_wf" - ), - ) - .play( - predefined_frame_2, - ArbitraryWaveform([complex(1, 0.4), 0, 0.3, complex(0.1, 0.2)], id="arb_wf"), - ) - .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};", - " 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[(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);", - " play(predefined_frame_1, constant_wf);", - " play(predefined_frame_2, arb_wf);", - " psb[1] = capture_v0(predefined_frame_2);", - "}", - ] - ) - assert pulse_sequence.to_ir() == expected_str_unbound - 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} - ) - b_bound_call = pulse_sequence(b=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;", - "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};", - " 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);", - " 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};", - " 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);", - " 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 - - -@pytest.mark.parametrize( - "method_name, method_kwargs", - [ - ("set_frequency", {"frequency": 1e4}), - ("shift_frequency", {"frequency": 2e4}), - ("set_phase", {"phase": 0.3}), - ("shift_phase", {"phase": 0.2}), - ("set_scale", {"scale": 0.7}), - ("delay", {"duration": 1e-5}), - ("barrier", None), - ("play", {"waveform": ConstantWaveform(1e-3, complex(1, 2), "wf_id")}), - ], -) -def test_pulse_sequence_conflicting_frames( - user_defined_frame, conflicting_user_defined_frame, method_name, method_kwargs -): - ps = PulseSequence().shift_frequency(user_defined_frame, 1e2) - method = getattr(ps, method_name) - - with pytest.raises(ValueError): - if method_kwargs: - method(conflicting_user_defined_frame, **method_kwargs) - else: - method(conflicting_user_defined_frame) - - -def test_pulse_sequence_conflicting_wf(user_defined_frame): - wf = ConstantWaveform(1e-3, complex(1, 2), "wf_id") - conflicting_wf = ConstantWaveform(1e-4, complex(1, 2), "wf_id") - ps = PulseSequence().play(user_defined_frame, wf) - - with pytest.raises(ValueError): - ps.play(user_defined_frame, conflicting_wf) - - -def test_pulse_sequence_to_time_trace_program_mutation(user_defined_frame): - wf = ConstantWaveform(1e-3, complex(1, 2), "wf_id") - ps = PulseSequence().play(user_defined_frame, wf) - initial_vars = dict(ps._program.undeclared_vars) - ps.to_time_trace() - assert initial_vars == ps._program.undeclared_vars - - -def test_pulse_sequence_to_ir(predefined_frame_1, predefined_frame_2): - pulse_sequence = ( - PulseSequence() - .set_frequency(predefined_frame_1, 3e9) - .shift_frequency(predefined_frame_1, 1e9) - .set_phase(predefined_frame_1, -0.5) - .shift_phase(predefined_frame_1, 0.1) - .set_scale(predefined_frame_1, 0.25) - .capture_v0(predefined_frame_1) - .delay([predefined_frame_1, predefined_frame_2], 2e-9) - .delay(predefined_frame_1, 1e-6) - .delay(QubitSet(0), 1e-3) - .barrier(QubitSet([0, 1])) - .barrier([predefined_frame_1, predefined_frame_2]) - .play(predefined_frame_1, GaussianWaveform(length=1e-3, sigma=0.7, id="gauss_wf")) - .play( - predefined_frame_2, - DragGaussianWaveform(length=3e-3, sigma=0.4, beta=0.2, id="drag_gauss_wf"), - ) - .play( - predefined_frame_1, - ConstantWaveform(length=4e-3, iq=complex(2, 0.3), id="constant_wf"), - ) - .play( - predefined_frame_2, - ArbitraryWaveform([complex(1, 0.4), 0, 0.3, complex(0.1, 0.2)], id="arb_wf"), - ) - .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};", - " 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);", - " psb[1] = capture_v0(predefined_frame_2);", - "}", - ] - ) - assert pulse_sequence.to_ir() == expected_str - - -def test_parse_from_calibration_schema(predefined_frame_1, predefined_frame_2): - waveforms = { - "drag_gauss_wf": DragGaussianWaveform(length=3e-3, sigma=0.4, beta=0.2, id="drag_gauss_wf") - } - frames = {predefined_frame_1.id: predefined_frame_1, predefined_frame_2.id: predefined_frame_2} - - calibration_instrs = [ - { - "name": "barrier", - "arguments": [{"name": "qubit", "value": "0", "type": "string"}], - }, - { - "name": "play", - "arguments": [ - {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, - { - "name": "waveform", - "value": "drag_gauss_wf", - "type": "waveform", - }, - ], - }, - { - "name": "barrier", - "arguments": [ - {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, - {"name": "frame", "value": "predefined_frame_2", "type": "frame"}, - ], - }, - { - "name": "barrier", - "arguments": None, - }, - { - "name": "delay", - "arguments": [ - {"name": "duration", "value": 3e-07, "type": "float"}, - {"name": "qubit", "value": "0", "type": "string"}, - {"name": "qubit", "value": "1", "type": "string"}, - ], - }, - { - "name": "delay", - "arguments": [ - {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, - {"name": "duration", "value": 3e-07, "type": "float"}, - ], - }, - { - "name": "shift_phase", - "arguments": [ - {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, - {"name": "phase", "value": 3e-07, "type": "float"}, - ], - }, - { - "name": "shift_frequency", - "arguments": [ - {"name": "frequency", "value": "theta", "type": "expr"}, - {"name": "frame", "value": "predefined_frame_1", "type": "frame"}, - {"name": "extra", "value": "predefined_frame_1", "type": "string"}, - ], - }, - ] - - expected_pulse_sequence = ( - PulseSequence() - .barrier(QubitSet(0)) - .play(predefined_frame_1, waveforms["drag_gauss_wf"]) - .barrier([predefined_frame_1, predefined_frame_2]) - .barrier([]) - .delay(QubitSet([0, 1]), 3e-07) - .delay([predefined_frame_1], 3e-07) - .shift_phase(predefined_frame_1, 3e-07) - .shift_frequency(predefined_frame_1, FreeParameter("theta")) - ) - - assert ( - PulseSequence._parse_from_calibration_schema(calibration_instrs, waveforms, frames) - == expected_pulse_sequence - ) diff --git a/test/unit_tests/braket/pulse/test_waveforms.py b/test/unit_tests/braket/pulse/test_waveforms.py deleted file mode 100644 index 34f989c0..00000000 --- a/test/unit_tests/braket/pulse/test_waveforms.py +++ /dev/null @@ -1,363 +0,0 @@ -# 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 re -from copy import deepcopy - -import numpy as np -import pytest -from oqpy import Program - -from braket.circuits.free_parameter import FreeParameter -from braket.pulse import ArbitraryWaveform, ConstantWaveform, DragGaussianWaveform, GaussianWaveform -from braket.pulse.ast.qasm_parser import ast_to_qasm -from braket.pulse.waveforms import _parse_waveform_from_calibration_schema - - -@pytest.mark.parametrize( - "amps", - [ - [complex(1, 2), complex(0.3, -1), 0, 4.2], - np.array([complex(1, 2), complex(0.3, -1), 0, 4.2]), - ], -) -def test_arbitrary_waveform(amps): - waveform_id = "arb_wf_x" - wf = ArbitraryWaveform(amps, waveform_id) - assert wf.amplitudes == list(amps) - assert wf.id == waveform_id - oq_exp = wf._to_oqpy_expression() - assert oq_exp.init_expression == list(amps) - assert oq_exp.name == wf.id - - -def test_arbitrary_waveform_repr(): - amps = [1, 4, 5] - waveform_id = "arb_wf_x" - wf = ArbitraryWaveform(amps, waveform_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) - assert wf.amplitudes == amps - assert re.match(r"[A-Za-z]{10}", wf.id) - - -def test_arbitrary_wf_eq(): - wf = ArbitraryWaveform([1, 4, 5], "wf_x") - wf_2 = ArbitraryWaveform(wf.amplitudes, wf.id) - assert wf_2 == wf - for att in ["amplitudes", "id"]: - wfc = deepcopy(wf_2) - setattr(wfc, att, "wrong_value") - assert wf != wfc - - -def test_arbitrary_waveform_not_castable_into_list(): - amps = 1 - with pytest.raises(TypeError): - ArbitraryWaveform(amps) - - -def test_constant_waveform(): - length = 4e-3 - iq = 4 - id = "const_wf_x" - wf = ConstantWaveform(length, iq, id) - assert wf.length == length - assert wf.iq == iq - assert wf.id == id - - _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(): - amps = [1, 4, 5] - wf = ArbitraryWaveform(amps) - assert wf.amplitudes == amps - assert re.match(r"[A-Za-z]{10}", wf.id) - - -def test_constant_wf_eq(): - wf = ConstantWaveform(4e-3, complex(2, 3), "wf_c") - wf_2 = ConstantWaveform(wf.length, wf.iq, wf.id) - assert wf_2 == wf - for att in ["length", "iq", "id"]: - wfc = deepcopy(wf_2) - setattr(wfc, att, "wrong_value") - assert wf != wfc - - -def test_constant_wf_free_params(): - wf = ConstantWaveform( - FreeParameter("length_v") + FreeParameter("length_w"), iq=complex(2, -3), id="const_wf" - ) - assert wf.parameters == [FreeParameter("length_v") + FreeParameter("length_w")] - _assert_wf_qasm( - wf, - "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(6.0us, 2.0 - 3.0im);") - - -def test_drag_gaussian_waveform(): - 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) - assert wf.id == id - assert wf.zero_at_edges == zero_at_edges - assert wf.amplitude == amplitude - assert wf.beta == beta - assert wf.sigma == sigma - assert wf.length == length - - _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(): - length = 4e-9 - sigma = 0.3 - beta = 0.6 - wf = DragGaussianWaveform(length, sigma, beta) - assert re.match(r"[A-Za-z]{10}", wf.id) - assert wf.zero_at_edges is False - assert wf.amplitude == 1 - assert wf.beta == beta - assert wf.sigma == sigma - assert wf.length == length - - -def test_drag_gaussian_wf_eq(): - wf = DragGaussianWaveform(4e-3, 0.3, 0.2, 0.7, True, "wf_dg") - wf_2 = DragGaussianWaveform(wf.length, wf.sigma, wf.beta, wf.amplitude, wf.zero_at_edges, wf.id) - assert wf_2 == wf - for att in ["length", "sigma", "beta", "amplitude", "zero_at_edges", "id"]: - wfc = deepcopy(wf_2) - setattr(wfc, att, "wrong_value") - assert wf != wfc - - -def test_drag_gaussian_wf_free_params(): - wf = DragGaussianWaveform( - FreeParameter("length_v"), - FreeParameter("sigma_a") + FreeParameter("sigma_b"), - FreeParameter("beta_y"), - FreeParameter("amp_z"), - id="d_gauss_wf", - ) - assert wf.parameters == [ - FreeParameter("length_v"), - FreeParameter("sigma_a") + FreeParameter("sigma_b"), - FreeParameter("beta_y"), - FreeParameter("amp_z"), - ] - _assert_wf_qasm( - wf, - "waveform d_gauss_wf = " - "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) - assert wf_2.parameters == [ - 0.6, - 0.4 + FreeParameter("sigma_b"), - FreeParameter("beta_y"), - FreeParameter("amp_z"), - ] - _assert_wf_qasm( - wf_2, - "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(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(): - length = 4e-9 - sigma = 0.3 - wf = GaussianWaveform(length, sigma) - assert re.match(r"[A-Za-z]{10}", wf.id) - assert wf.zero_at_edges is False - assert wf.amplitude == 1 - assert wf.sigma == sigma - assert wf.length == length - - -def test_gaussian_wf_eq(): - wf = GaussianWaveform(4e-3, 0.3, 0.7, True, "wf_dg") - wf_2 = GaussianWaveform(wf.length, wf.sigma, wf.amplitude, wf.zero_at_edges, wf.id) - assert wf_2 == wf - for att in ["length", "sigma", "amplitude", "zero_at_edges", "id"]: - wfc = deepcopy(wf_2) - setattr(wfc, att, "wrong_value") - assert wf != wfc - - -def test_gaussian_wf_free_params(): - wf = GaussianWaveform( - FreeParameter("length_v"), FreeParameter("sigma_x"), FreeParameter("amp_z"), id="gauss_wf" - ) - assert wf.parameters == [ - FreeParameter("length_v"), - FreeParameter("sigma_x"), - FreeParameter("amp_z"), - ] - _assert_wf_qasm( - wf, - "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(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(600.0ms, 300.0ms, 0.1, false);") - - -def _assert_wf_qasm(waveform, expected_qasm): - p = Program(None) - p.declare(waveform._to_oqpy_expression()) - assert ast_to_qasm(p.to_ast(include_externs=False)) == expected_qasm - - -@pytest.mark.parametrize( - "waveform_json, waveform", - [ - ( - { - "waveformId": "q0_q1_cz_CZ", - "amplitudes": [[0.0, 0.0], [0.0, 0.0]], - }, - ArbitraryWaveform(id="q0_q1_cz_CZ", amplitudes=[complex(0.0, 0.0), complex(0.0, 0.0)]), - ), - ( - { - "waveformId": "wf_drag_gaussian_0", - "name": "drag_gaussian", - "arguments": [ - {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, - {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, - {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, - {"name": "beta", "value": 7.494904522022295e-10, "type": "float"}, - ], - }, - DragGaussianWaveform( - id="wf_drag_gaussian_0", - sigma=6.369913502160144e-9, - length=6.000000000000001e-8, - beta=7.494904522022295e-10, - amplitude=-0.4549282253548838, - ), - ), - ( - { - "waveformId": "wf_gaussian_0", - "name": "gaussian", - "arguments": [ - {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, - {"name": "sigma", "value": 6.369913502160144e-9, "type": "float"}, - {"name": "amplitude", "value": -0.4549282253548838, "type": "float"}, - ], - }, - GaussianWaveform( - id="wf_gaussian_0", - length=6.000000000000001e-8, - sigma=6.369913502160144e-9, - amplitude=-0.4549282253548838, - ), - ), - ( - { - "waveformId": "wf_constant", - "name": "constant", - "arguments": [ - {"name": "length", "value": 2.1, "type": "float"}, - {"name": "iq", "value": 0.23, "type": "complex"}, - ], - }, - ConstantWaveform(id="wf_constant", length=2.1, iq=0.23), - ), - ], -) -def test_parse_waveform_from_calibration_schema(waveform_json, waveform): - assert _parse_waveform_from_calibration_schema(waveform_json) == waveform diff --git a/test/unit_tests/braket/quantum_information/test_pauli_string.py b/test/unit_tests/braket/quantum_information/test_pauli_string.py deleted file mode 100644 index c3959d30..00000000 --- a/test/unit_tests/braket/quantum_information/test_pauli_string.py +++ /dev/null @@ -1,262 +0,0 @@ -# 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 functools -import itertools -import math - -import numpy as np -import pytest - -from braket.circuits import gates -from braket.circuits.circuit import Circuit -from braket.circuits.observables import I, X, Y, Z -from braket.quantum_information import PauliString - -ORDER = ["I", "X", "Y", "Z"] -PAULI_INDEX_MATRICES = { - 0: gates.I().to_matrix(), - 1: gates.X().to_matrix(), - 2: gates.Y().to_matrix(), - 3: gates.Z().to_matrix(), -} -SIGN_MAP = {"+": 1, "-": -1} - - -@pytest.mark.parametrize( - "pauli_string, string, phase, observable, obs_with_id", - [ - ("+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, obs_with_id): - instance = PauliString(pauli_string) - assert str(instance) == string - assert instance.phase == phase - pauli_list = list(str(pauli_string)) - if pauli_list[0] in {"-", "+"}: - pauli_list.pop(0) - stripped = "".join(pauli_list) - assert len(instance) == len(stripped) - assert len(instance) == instance.qubit_count - for i in range(len(instance)): - assert ORDER[instance[i]] == stripped[i] - 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( - "other", ["foo", PauliString("+XYZ"), PauliString("-XI"), PauliString("-XYZI")] -) -def test_not_equal(other): - assert PauliString("-XYZ") != other - - -@pytest.mark.xfail(raises=ValueError) -def test_none(): - PauliString(None) - - -@pytest.mark.xfail(raises=ValueError) -@pytest.mark.parametrize("invalid_string", ["XAY", "-BYZ", "+", "-", "xyz", "", None]) -def test_invalid_string(invalid_string): - PauliString(invalid_string) - - -@pytest.mark.xfail(raises=TypeError) -def test_invalid_type(): - PauliString(1234) - - -@pytest.mark.xfail(raises=IndexError) -@pytest.mark.parametrize("string", ["XZY", "-YIIXZ", "+IXIYIZ"]) -def test_index_out_of_bounds(string): - PauliString(string)[len(string)] - - -@pytest.mark.parametrize( - "string,weight", - # Make phase explicit for test simplicity - list(itertools.product(["-ZYX", "-IXIIXYZ", "+ZXYXY"], [1, 2, 3])), -) -def test_weight_n_substrings(string, weight): - pauli_string = PauliString(string) - qubit_count = pauli_string.qubit_count - nontrivial = [qubit for qubit in range(qubit_count) if pauli_string[qubit]] - substrings = [] - for indices in itertools.combinations(nontrivial, weight): - factors = [string[qubit + 1] if qubit in indices else "I" for qubit in range(qubit_count)] - substrings.append(PauliString(f"{string[0]}{''.join(factors)}")) - actual = pauli_string.weight_n_substrings(weight) - assert actual == tuple(substrings) - assert len(actual) == math.comb(len(nontrivial), weight) - - -@pytest.mark.parametrize( - "string,signs", - list(itertools.product(["X", "Y", "Z"], ["+", "-"])) - + [("ZIY", "+--"), ("YIXIZ", [1, 1, -1, -1, 1]), ("XYZ", (-1, -1, -1)), ("XZIY", None)], -) -def test_eigenstate(string, signs): - pauli_string = PauliString(string) - circuit = pauli_string.eigenstate(signs) - for qubit in range(len(string)): - circuit.i(qubit) - initial_state = np.zeros(2 ** len(pauli_string)) - initial_state[0] = 1 - state = circuit.to_unitary() @ initial_state - - if not signs: - signs = [1] * len(string) - signs_list = [SIGN_MAP[sign] for sign in signs] if isinstance(signs, str) else signs - positive = [signs_list[i] for i in range(len(string)) if signs_list[i] < 0 and string[i] != "I"] - total_sign = 1 if len(positive) % 2 == 0 else -1 - pauli_matrix = functools.reduce( - np.kron, [PAULI_INDEX_MATRICES[pauli] for pauli in pauli_string] - ) - actual = total_sign * pauli_matrix @ state - assert np.allclose(actual, state) - - -@pytest.mark.xfail(raises=ValueError) -@pytest.mark.parametrize("sign", ["+ab", "--+-", [+2, -1, -1], (-1, 1j, 1)]) -def test_eigenstate_invalid_signs(sign): - PauliString("XYZ").eigenstate(sign) - - -@pytest.mark.parametrize( - "circ_arg_1, circ_arg_2, circ_res", - [ - ("III", "+III", "III"), - ("Z", "I", "Z"), - ("I", "-Y", "-Y"), - ("XYXZY", "+XYXZY", "IIIII"), - ("XYZ", "ZYX", "YIY"), - ("YZ", "ZX", "-XY"), - ("-Z", "Y", "X"), - ("Z", "Y", "-X"), - ], -) -def test_dot(circ_arg_1, circ_arg_2, circ_res): - circ1 = PauliString(circ_arg_1) - circ2 = circ1.dot(PauliString(circ_arg_2)) - assert circ2 == PauliString(circ_res) - assert circ1 == circ1 - - # Test in-place computation - circ1 = PauliString(circ_arg_1) - circ1.dot(PauliString(circ_arg_2), inplace=True) - assert circ1 == PauliString(circ_res) - - # Test operator overloads - circ1 = PauliString(circ_arg_1) - circ2 = PauliString(circ_arg_2) - assert circ1 * circ2 == PauliString(circ_res) - circ1 *= circ2 - assert circ1 == PauliString(circ_res) - - -@pytest.mark.xfail(raises=ValueError) -@pytest.mark.parametrize( - "circ1, circ2, operation", - [ - (PauliString("III"), PauliString("II"), "dot()"), - (PauliString("III"), PauliString("II"), "*"), - (PauliString("III"), PauliString("II"), "*="), - (PauliString("IXI"), PauliString("II"), "dot()"), - (PauliString("IXI"), PauliString("II"), "*"), - (PauliString("IXI"), PauliString("II"), "*="), - ], -) -def test_dot_unequal_lengths(circ1, circ2, operation): - if operation == "dot()": - circ1.dot(circ2) - elif operation == "*": - circ1 * circ2 - elif operation == "*=": - circ1 *= circ2 - - -@pytest.mark.parametrize( - "circ, n, circ_res", - [ - ("-X", 1, "-X"), - ("Y", 2, "I"), - ("Y", -2, "I"), - ("-X", 3, "-X"), - ("XYZ", 5, "XYZ"), - ("XYZ", -5, "XYZ"), - ("XYZ", 6, "III"), - ("XYZ", 1, "XYZ"), - ("-YX", 0, "II"), - ("Y", 0, "I"), - ], -) -def test_power(circ, n, circ_res): - circ1 = PauliString(circ) - circ2 = circ1.power(n) - assert circ2 == PauliString(circ_res) - assert circ1 == PauliString(circ) - - # Test in-place computation - circ1.power(n, inplace=True) - assert circ1 == PauliString(circ_res) - - # Test operator overloads - circ1 = PauliString(circ) - assert (circ1**n) == PauliString(circ_res) - circ1 **= n - assert circ1 == PauliString(circ_res) - - -@pytest.mark.xfail(raises=ValueError) -@pytest.mark.parametrize( - "circ, n, operation", - [ - (PauliString("XYZ"), "-1.2", "power()"), - (PauliString("XYZ"), "-1.2", "**"), - (PauliString("XYZ"), "-1.2", "**="), - (PauliString("ZYX"), "1.2", "power()"), - (PauliString("ZYX"), "1.2", "**"), - (PauliString("ZYX"), "1.2", "**="), - (PauliString("I"), "3j", "power()"), - (PauliString("I"), "3j", "**"), - (PauliString("I"), "3j", "**="), - ], -) -def test_power_invalid_exp(circ, n, operation): - if operation == "power()": - circ.power(n) - elif operation == "**": - circ**n - elif operation == "**=": - circ **= n - - -@pytest.mark.parametrize( - "circ, circ_res", - [ - (PauliString("I"), Circuit().i(0)), - (PauliString("-X"), Circuit().x(0)), - (PauliString("IYX"), Circuit().i(0).y(1).x(2)), - (PauliString("ZIIIX"), Circuit().z(0).i(1).i(2).i(3).x(4)), - ], -) -def test_to_circuit(circ, circ_res): - assert circ.to_circuit() == circ_res diff --git a/test/unit_tests/braket/registers/test_qubit.py b/test/unit_tests/braket/registers/test_qubit.py deleted file mode 100644 index 0c04a5d9..00000000 --- a/test/unit_tests/braket/registers/test_qubit.py +++ /dev/null @@ -1,57 +0,0 @@ -# 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.registers import Qubit - - -@pytest.fixture -def qubit(): - return Qubit(5) - - -def test_index_lt_zero(): - with pytest.raises(ValueError): - Qubit(-1) - - -@pytest.mark.parametrize("qubit_arg", ("not a number", 0.5)) -def test_index_non_int(qubit_arg): - with pytest.raises(TypeError): - Qubit(qubit_arg) - - -@pytest.mark.parametrize("qubit_index", (0, 5, np.int64(5))) -def test_index_gte_zero(qubit_index): - Qubit(qubit_index) - - -def test_str(qubit): - expected = f"Qubit({int(qubit)})" - assert str(qubit) == expected - - -def test_new_with_qubit(): - qubit = Qubit(0) - qubit_new = Qubit.new(qubit) - assert qubit_new == qubit - assert qubit_new is qubit - - -def test_new_with_int(): - qubit = 0 - qubit_new = Qubit.new(qubit) - assert qubit_new == qubit - assert qubit_new is not qubit diff --git a/test/unit_tests/braket/registers/test_qubit_set.py b/test/unit_tests/braket/registers/test_qubit_set.py deleted file mode 100644 index 1d730b96..00000000 --- a/test/unit_tests/braket/registers/test_qubit_set.py +++ /dev/null @@ -1,70 +0,0 @@ -# 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.registers import Qubit, QubitSet - - -@pytest.fixture -def qubits(): - return QubitSet([0, Qubit(1)]) - - -def test_qubit_set_is_ordered_set(): - qubits = QubitSet([1, 2, 3, 1, 2, 3]) - assert qubits == QubitSet([1, 2, 3]) - - -def test_default_input(): - assert QubitSet() == QubitSet([]) - - -def test_with_single(): - assert QubitSet(0) == (Qubit(0),) - - -def test_with_iterable(): - assert QubitSet([0, 1]) == (Qubit(0), Qubit(1)) - - -def test_with_nested_iterable(): - 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]]) == (Qubit(0), Qubit(1), Qubit(2), Qubit(3)) - - -def test_flattening_does_not_recurse_infinitely(): - with pytest.raises(TypeError): # str instead of expected int - QubitSet("kaboom") - - -def test_map_creates_new_object(qubits): - mapped_qubits = qubits.map({}) - assert mapped_qubits == qubits - assert mapped_qubits is not qubits - - -def test_map_happy_case(): - mapping = {Qubit(0): Qubit(11), Qubit(1): Qubit(5)} - qubits = QubitSet([0, 1]) - mapped_qubits = qubits.map(mapping) - - assert mapped_qubits == QubitSet([11, 5]) - - -def test_hash(qubits): - assert hash(qubits) == hash(tuple(qubits)) 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 deleted file mode 100644 index be2d6fdb..00000000 --- a/test/unit_tests/braket/tasks/test_analog_hamiltonian_simulation_task_result.py +++ /dev/null @@ -1,316 +0,0 @@ -# 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.ir.ahs import Program -from braket.task_result import ( - AdditionalMetadata, - AnalogHamiltonianSimulationShotMeasurement, - AnalogHamiltonianSimulationShotMetadata, - AnalogHamiltonianSimulationShotResult, - AnalogHamiltonianSimulationTaskResult, - TaskMetadata, -) -from braket.tasks import ( - AnalogHamiltonianSimulationQuantumTaskResult, - AnalogHamiltonianSimulationShotStatus, -) -from braket.tasks.analog_hamiltonian_simulation_quantum_task_result import ShotResult - - -@pytest.fixture -def task_metadata(): - return TaskMetadata(**{"id": "task_arn", "deviceId": "arn1", "shots": 100}) - - -@pytest.fixture -def additional_metadata(): - return AdditionalMetadata( - action=Program( - setup={ - "ahs_register": { - "sites": [ - [0.0, 0.0], - [0.0, 3.0e-6], - [0.0, 6.0e-6], - [3.0e-6, 0.0], - [3.0e-6, 3.0e-6], - [3.0e-6, 6.0e-6], - ], - "filling": [1, 1, 1, 1, 0, 0], - } - }, - hamiltonian={ - "drivingFields": [ - { - "amplitude": { - "time_series": { - "values": [0.0, 2.51327e7, 2.51327e7, 0.0], - "times": [0.0, 3.0e-7, 2.7e-6, 3.0e-6], - }, - "pattern": "uniform", - }, - "phase": { - "time_series": {"values": [0, 0], "times": [0.0, 3.0e-6]}, - "pattern": "uniform", - }, - "detuning": { - "time_series": { - "values": [-1.25664e8, -1.25664e8, 1.25664e8, 1.25664e8], - "times": [0.0, 3.0e-7, 2.7e-6, 3.0e-6], - }, - "pattern": "uniform", - }, - } - ], - "localDetuning": [ - { - "magnitude": { - "time_series": { - "values": [-1.25664e8, 1.25664e8], - "times": [0.0, 3.0e-6], - }, - "pattern": [0.5, 1.0, 0.5, 0.5, 0.5, 0.5], - } - } - ], - }, - ) - ) - - -@pytest.fixture -def success_measurement(): - return AnalogHamiltonianSimulationShotMeasurement( - shotMetadata=AnalogHamiltonianSimulationShotMetadata(shotStatus="Success"), - shotResult=AnalogHamiltonianSimulationShotResult( - preSequence=[1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], - postSequence=[0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0], - ), - ) - - -@pytest.fixture -def partial_success_measurement(): - return AnalogHamiltonianSimulationShotMeasurement( - shotMetadata=AnalogHamiltonianSimulationShotMetadata(shotStatus="Partial Success"), - shotResult=AnalogHamiltonianSimulationShotResult( - preSequence=[1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], postSequence=None - ), - ) - - -@pytest.fixture -def failed_measurement(): - return AnalogHamiltonianSimulationShotMeasurement( - shotMetadata=AnalogHamiltonianSimulationShotMetadata(shotStatus="Failure"), - shotResult=AnalogHamiltonianSimulationShotResult(preSequence=None, postSequence=None), - ) - - -@pytest.fixture -def success_measurement_extended(): - return AnalogHamiltonianSimulationShotMeasurement( - shotMetadata=AnalogHamiltonianSimulationShotMetadata(shotStatus="Success"), - shotResult=AnalogHamiltonianSimulationShotResult( - preSequence=[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - postSequence=[1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1], - ), - ) - - -@pytest.fixture -def measurements(success_measurement, partial_success_measurement, failed_measurement): - return [success_measurement, partial_success_measurement, failed_measurement] - - -@pytest.fixture -def measurements_extended( - success_measurement, - success_measurement_extended, -): - return [ - success_measurement, - success_measurement_extended, - ] - - -@pytest.fixture -def result_str_1(task_metadata, additional_metadata, measurements): - result = AnalogHamiltonianSimulationTaskResult( - taskMetadata=task_metadata, - additionalMetadata=additional_metadata, - measurements=measurements, - ) - return result.json() - - -@pytest.fixture -def result_str_2(task_metadata, additional_metadata, measurements): - result = AnalogHamiltonianSimulationTaskResult( - taskMetadata=task_metadata, - additionalMetadata=additional_metadata, - measurements=None, - ) - return result.json() - - -@pytest.fixture -def result_str_3(task_metadata, additional_metadata, measurements_extended): - result = AnalogHamiltonianSimulationTaskResult( - taskMetadata=task_metadata, - additionalMetadata=additional_metadata, - measurements=measurements_extended, - ) - return result.json() - - -def validate_result_from_str_1(result): - assert len(result.measurements) == 3 - assert result.measurements[0].status == AnalogHamiltonianSimulationShotStatus.SUCCESS - np.testing.assert_equal(result.measurements[0].pre_sequence, [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1]) - np.testing.assert_equal(result.measurements[0].post_sequence, [0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0]) - assert result.measurements[1].status == AnalogHamiltonianSimulationShotStatus.PARTIAL_SUCCESS - np.testing.assert_equal(result.measurements[1].pre_sequence, [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1]) - assert result.measurements[1].post_sequence is None - assert result.measurements[2].status == AnalogHamiltonianSimulationShotStatus.FAILURE - assert result.measurements[2].pre_sequence is None - assert result.measurements[2].post_sequence is None - assert result.additional_metadata.action is not None - - -def test_from_object(result_str_1, task_metadata): - result = AnalogHamiltonianSimulationQuantumTaskResult.from_object( - AnalogHamiltonianSimulationTaskResult.parse_raw(result_str_1) - ) - assert result.task_metadata == task_metadata - validate_result_from_str_1(result) - - -def test_from_string(result_str_1, task_metadata): - result = AnalogHamiltonianSimulationQuantumTaskResult.from_string(result_str_1) - assert result.task_metadata == task_metadata - validate_result_from_str_1(result) - - -def test_from_object_equal_to_from_string(result_str_1): - assert AnalogHamiltonianSimulationQuantumTaskResult.from_object( - AnalogHamiltonianSimulationTaskResult.parse_raw(result_str_1) - ) == AnalogHamiltonianSimulationQuantumTaskResult.from_string(result_str_1) - - -def test_equality(task_metadata, result_str_1, result_str_2): - result_1 = AnalogHamiltonianSimulationQuantumTaskResult.from_string(result_str_1) - result_2 = AnalogHamiltonianSimulationQuantumTaskResult.from_string(result_str_1) - other_result = AnalogHamiltonianSimulationQuantumTaskResult.from_string(result_str_2) - non_result = "not a quantum task result" - - assert result_1 == result_2 - assert result_1 is not result_2 - assert result_1 != other_result - assert result_1 != AnalogHamiltonianSimulationQuantumTaskResult( - task_metadata=task_metadata, - additional_metadata=additional_metadata, - measurements=[result_1.measurements[1], result_1.measurements[0]], - ) - assert result_1 != non_result - - -def test_get_counts(result_str_3): - result = AnalogHamiltonianSimulationQuantumTaskResult.from_string(result_str_3) - - counts = result.get_counts() - # Partial Success and Failure result status are mapped to counts = None - expected_counts = {"rrrgeggrrgr": 1, "grggrgrrrrg": 1} - assert counts == expected_counts - - -def test_get_counts_failed_task(task_metadata): - measurement = ShotResult(AnalogHamiltonianSimulationShotStatus.FAILURE, [], []) - result = AnalogHamiltonianSimulationQuantumTaskResult( - task_metadata=task_metadata, - additional_metadata=additional_metadata, - measurements=[measurement], - ) - - counts = result.get_counts() - expected_counts = {} - assert counts == expected_counts - - -def test_avg_density(result_str_3): - result = AnalogHamiltonianSimulationQuantumTaskResult.from_string(result_str_3) - - density = result.get_avg_density() - expected_density = [0.5, 1.0, 0.5, 0.0, 1.0, 0.0, 0.5, 1.0, 1.0, 0.5, 0.5] - np.testing.assert_almost_equal(density, expected_density) - - -@pytest.mark.parametrize( - "shot0, shot1", - [ - ( - ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [], []), - ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [], []), - ), - ( - ShotResult(AnalogHamiltonianSimulationShotStatus.FAILURE, [1], [2]), - ShotResult(AnalogHamiltonianSimulationShotStatus.FAILURE, [1], [2]), - ), - ( - ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, None, None), - ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, None, None), - ), - ( - ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, None, [1, 2]), - ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, None, [1, 2]), - ), - ( - ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [1, 2], None), - ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [1, 2], None), - ), - ], -) -def test_shot_result_equals(shot0, shot1): - assert shot0 == shot1 - - -@pytest.mark.parametrize( - "shot0, shot1", - [ - ( - ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [], []), - ShotResult(AnalogHamiltonianSimulationShotStatus.FAILURE, [], []), - ), - ( - ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [1], [2]), - ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [2], [1]), - ), - ( - ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [1], [2]), - ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, None, [2]), - ), - ( - ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [1], None), - ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [1], [2]), - ), - ( - ShotResult(AnalogHamiltonianSimulationShotStatus.SUCCESS, [1], None), - "not a shot", - ), - ], -) -def test_shot_result_not_equals(shot0, shot1): - assert shot0 != shot1 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 deleted file mode 100644 index 03f68b7c..00000000 --- a/test/unit_tests/braket/tasks/test_annealing_quantum_task_result.py +++ /dev/null @@ -1,259 +0,0 @@ -# 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.ir.annealing import Problem -from braket.task_result import ( - AdditionalMetadata, - AnnealingTaskResult, - DwaveMetadata, - DwaveTiming, - TaskMetadata, -) -from braket.tasks import AnnealingQuantumTaskResult - - -@pytest.fixture -def solutions(): - return [[-1, -1, -1, -1], [1, -1, 1, 1], [1, -1, -1, 1]] - - -@pytest.fixture -def values(): - return [0.0, 1.0, 2.0] - - -@pytest.fixture -def variable_count(): - return 4 - - -@pytest.fixture -def solution_counts(): - return [3, 2, 4] - - -@pytest.fixture -def problem_type(): - return "ISING" - - -@pytest.fixture -def task_metadata(): - return TaskMetadata(**{"id": "task_arn", "deviceId": "arn1", "shots": 100}) - - -@pytest.fixture -def dwave_metadata(): - return DwaveMetadata( - activeVariables=[0], - timing=DwaveTiming( - qpuSamplingTime=100, - qpuAnnealTimePerSample=20, - qpuReadoutTimePerSample=274, - qpuAccessTime=10917, - qpuAccessOverheadTime=3382, - qpuProgrammingTime=9342, - qpuDelayTimePerSample=21, - totalPostProcessingTime=117, - postProcessingOverheadTime=117, - totalRealTime=10917, - runTimeChip=1575, - annealTimePerRun=20, - readoutTimePerRun=274, - ), - ) - - -@pytest.fixture -def additional_metadata(problem_type, dwave_metadata): - problem = Problem( - type=problem_type, - linear={0: 0.3333, 1: -0.333, 4: -0.333, 5: 0.333}, - quadratic={"0,4": 0.667, "0,5": -1, "1,4": 0.667, "1,5": 0.667}, - ) - return AdditionalMetadata(action=problem, dwaveMetadata=dwave_metadata) - - -@pytest.fixture -def result_str_1( - solutions, values, solution_counts, variable_count, task_metadata, additional_metadata -): - result = AnnealingTaskResult( - solutions=solutions, - variableCount=variable_count, - values=values, - solutionCounts=solution_counts, - taskMetadata=task_metadata, - additionalMetadata=additional_metadata, - ) - return result.json() - - -@pytest.fixture -def result_str_2(solutions, values, variable_count, task_metadata, additional_metadata): - result = AnnealingTaskResult( - solutions=solutions, - variableCount=variable_count, - values=values, - taskMetadata=task_metadata, - additionalMetadata=additional_metadata, - ) - return result.json() - - -@pytest.fixture -def result_str_3(solutions, values, variable_count, task_metadata, additional_metadata): - result = AnnealingTaskResult( - solutionCounts=[], - solutions=solutions, - variableCount=variable_count, - values=values, - taskMetadata=task_metadata, - additionalMetadata=additional_metadata, - ) - return result.json() - - -@pytest.fixture -def annealing_result( - solutions, - values, - solution_counts, - variable_count, - problem_type, - additional_metadata, - task_metadata, -): - solutions = np.asarray(solutions, dtype=int) - values = np.asarray(values, dtype=float) - solution_counts = np.asarray(solution_counts, dtype=int) - record_array = AnnealingQuantumTaskResult._create_record_array( - solutions, solution_counts, values - ) - return AnnealingQuantumTaskResult( - record_array=record_array, - variable_count=variable_count, - problem_type=problem_type, - task_metadata=task_metadata, - additional_metadata=additional_metadata, - ) - - -def test_from_object( - result_str_1, - solutions, - values, - solution_counts, - variable_count, - problem_type, - task_metadata, - additional_metadata, -): - result = AnnealingQuantumTaskResult.from_object(AnnealingTaskResult.parse_raw(result_str_1)) - solutions = np.asarray(solutions, dtype=int) - values = np.asarray(values, dtype=float) - solution_counts = np.asarray(solution_counts, dtype=int) - assert result.variable_count == variable_count - assert result.problem_type == problem_type - assert result.task_metadata == task_metadata - assert result.additional_metadata == additional_metadata - np.testing.assert_equal( - result.record_array, - AnnealingQuantumTaskResult._create_record_array(solutions, solution_counts, values), - ) - - -def test_from_string( - result_str_1, - solutions, - values, - solution_counts, - variable_count, - problem_type, - task_metadata, - additional_metadata, -): - result = AnnealingQuantumTaskResult.from_string(result_str_1) - solutions = np.asarray(solutions, dtype=int) - values = np.asarray(values, dtype=float) - solution_counts = np.asarray(solution_counts, dtype=int) - assert result.variable_count == variable_count - assert result.problem_type == problem_type - assert result.task_metadata == task_metadata - assert result.additional_metadata == additional_metadata - np.testing.assert_equal( - result.record_array, - AnnealingQuantumTaskResult._create_record_array(solutions, solution_counts, values), - ) - - -def test_from_string_solution_counts_none(result_str_2, solutions): - result = AnnealingQuantumTaskResult.from_string(result_str_2) - np.testing.assert_equal(result.record_array.solution_count, np.ones(len(solutions), dtype=int)) - - -def test_from_string_solution_counts_empty_list(result_str_3, solutions): - result = AnnealingQuantumTaskResult.from_string(result_str_3) - np.testing.assert_equal(result.record_array.solution_count, np.ones(len(solutions), dtype=int)) - - -def test_data_sort_by_none(annealing_result, solutions, values, solution_counts): - d = list(annealing_result.data(sorted_by=None)) - for i in range(len(solutions)): - assert (d[i][0] == solutions[i]).all() - assert d[i][1] == values[i] - assert d[i][2] == solution_counts[i] - - -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] == (values[i],) - - -def test_data_reverse(annealing_result, solutions, values, solution_counts): - d = list(annealing_result.data(reverse=True)) - num_solutions = len(solutions) - for i in range(num_solutions): - assert (d[i][0] == solutions[num_solutions - i - 1]).all() - assert d[i][1] == values[num_solutions - i - 1] - assert d[i][2] == solution_counts[num_solutions - i - 1] - - -def test_data_sort_by(annealing_result, solutions, values, solution_counts): - d = list(annealing_result.data(sorted_by="solution_count")) - min_index = np.argmin(solution_counts) - assert (d[0][0] == solutions[min_index]).all() - assert d[0][1] == values[min_index] - assert d[0][2] == solution_counts[min_index] - - -def test_from_object_equal_to_from_string(result_str_1): - assert AnnealingQuantumTaskResult.from_object( - AnnealingTaskResult.parse_raw(result_str_1) - ) == AnnealingQuantumTaskResult.from_string(result_str_1) - - -def test_equality(result_str_1, result_str_2): - result_1 = AnnealingQuantumTaskResult.from_string(result_str_1) - result_2 = AnnealingQuantumTaskResult.from_string(result_str_1) - other_result = AnnealingQuantumTaskResult.from_string(result_str_2) - non_result = "not a quantum task result" - - assert result_1 == result_2 - assert result_1 is not result_2 - assert result_1 != other_result - assert result_1 != non_result 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 deleted file mode 100644 index 5447bd8b..00000000 --- a/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py +++ /dev/null @@ -1,617 +0,0 @@ -# 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 collections import Counter -from unittest.mock import patch - -import numpy as np -import pytest - -from braket.circuits import Observable, ResultType -from braket.ir import jaqcd, openqasm -from braket.task_result import ( - AdditionalMetadata, - GateModelTaskResult, - ResultTypeValue, - TaskMetadata, -) -from braket.task_result.oqc_metadata_v1 import OqcMetadata -from braket.task_result.rigetti_metadata_v1 import RigettiMetadata -from braket.tasks import GateModelQuantumTaskResult - - -@pytest.fixture -def task_metadata_shots(): - return TaskMetadata(**{"id": "task_arn", "deviceId": "default", "shots": 100}) - - -@pytest.fixture -def task_metadata_zero_shots(): - return TaskMetadata(**{"id": "task_arn", "deviceId": "default", "shots": 0}) - - -@pytest.fixture -def additional_metadata(): - program = jaqcd.Program(instructions=[jaqcd.CNot(control=0, target=1)]) - return AdditionalMetadata(action=program) - - -@pytest.fixture -def additional_metadata_openqasm(): - program = openqasm.Program( - source=""" - qubit[2] q; - bit[2] c; - - h q[0]; - cnot q[0], q[1]; - - c = measure q; - """ - ) - return AdditionalMetadata(action=program) - - -@pytest.fixture -def quil_program(): - return """ -PRAGMA INITIAL_REWIRING "NAIVE" -RESET -DECLARE ro BIT[2] -PRAGMA PRESERVE_BLOCK -RX(1.5707963267948966) 0 -RX(1.5707963267948966) 7 -RZ(1.5707963267948966) 0 -RZ(1.5707963267948966) 7 -RX(-1.5707963267948966) 7 -CZ 0 7 -RZ(3.141592653589793) 7 -RX(1.5707963267948966) 7 -RZ(1.5707963267948966) 7 -RX(-1.5707963267948966) 7 -PRAGMA END_PRESERVE_BLOCK -MEASURE 0 ro[0] -MEASURE 7 ro[1] -""" - - -@pytest.fixture -def additional_metadata_rigetti(quil_program): - program = openqasm.Program( - source=""" - OPENQASM 3.0; - bit[2] b; - h $0; - cnot $0, $7; - b[0] = measure $0; - b[1] = measure $7; - """ - ) - rigetti_metadata = RigettiMetadata(compiledProgram=quil_program) - - return AdditionalMetadata(action=program, rigettiMetadata=rigetti_metadata) - - -@pytest.fixture -def qasm2_program(): - return """ - OPENQASM 2.0; - include "qelib1.inc"; - - qreg node[8]; - creg b[2]; - u3(0.5*pi,0.0*pi,1.0*pi) node[0]; - cx node[0],node[1]; - measure node[0] -> b[0]; - measure node[1] -> b[1]; - """ - - -@pytest.fixture -def additional_metadata_oqc(qasm2_program): - program = openqasm.Program( - source=""" - OPENQASM 3.0; - bit[2] b; - qubit[8] q; - h q[0]; - cnot q[0], q[7]; - b[0] = measure q[0]; - b[1] = measure q[7]; - """ - ) - oqc_metadata = OqcMetadata(compiledProgram=qasm2_program) - - return AdditionalMetadata(action=program, oqcMetadata=oqc_metadata) - - -@pytest.fixture -def result_obj_1(task_metadata_shots, additional_metadata): - return GateModelTaskResult( - measurements=[[0, 0], [0, 1], [0, 1], [0, 1]], - measuredQubits=[0, 1], - taskMetadata=task_metadata_shots, - additionalMetadata=additional_metadata, - ) - - -@pytest.fixture -def result_rigetti(result_obj_1, additional_metadata_rigetti): - result = GateModelQuantumTaskResult.from_object(result_obj_1) - result.additional_metadata = additional_metadata_rigetti - return result - - -@pytest.fixture -def result_oqc(result_obj_1, additional_metadata_oqc): - result = GateModelQuantumTaskResult.from_object(result_obj_1) - result.additional_metadata = additional_metadata_oqc - return result - - -@pytest.fixture -def result_str_1(result_obj_1): - return result_obj_1.json() - - -@pytest.fixture -def result_str_2(result_obj_1): - result_obj_1.taskMetadata.id = "task_arn_2" - return result_obj_1.json() - - -@pytest.fixture -def result_str_3(task_metadata_shots, additional_metadata): - return GateModelTaskResult( - measurementProbabilities={"011000": 0.9999999999999982}, - measuredQubits=list(range(6)), - taskMetadata=task_metadata_shots, - additionalMetadata=additional_metadata, - ).json() - - -@pytest.fixture -def result_obj_4(task_metadata_zero_shots, additional_metadata): - return GateModelTaskResult( - resultTypes=[ - ResultTypeValue.construct( - type=jaqcd.Probability(targets=[0]), value=np.array([0.5, 0.5]) - ), - ResultTypeValue.construct( - type=jaqcd.StateVector(), - value=np.array([complex(0.70710678, 0), 0, 0, complex(0.70710678, 0)]), - ), - ResultTypeValue.construct( - type=jaqcd.Expectation(observable=["y"], targets=[0]), value=0.0 - ), - ResultTypeValue.construct( - type=jaqcd.Variance(observable=["y"], targets=[0]), value=0.1 - ), - ResultTypeValue.construct( - type=jaqcd.Amplitude(states=["00"]), value={"00": complex(0.70710678, 0)} - ), - ], - measuredQubits=list(range(2)), - taskMetadata=task_metadata_zero_shots, - additionalMetadata=additional_metadata, - ) - - -@pytest.fixture -def result_str_4(task_metadata_zero_shots, additional_metadata): - result = GateModelTaskResult( - resultTypes=[ - ResultTypeValue(type=jaqcd.Probability(targets=[0]), value=[0.5, 0.5]), - ResultTypeValue( - type=jaqcd.StateVector(), value=[(0.70710678, 0), (0, 0), (0, 0), (0.70710678, 0)] - ), - ResultTypeValue(type=jaqcd.Expectation(observable=["y"], targets=[0]), value=0.0), - ResultTypeValue(type=jaqcd.Variance(observable=["y"], targets=[0]), value=0.1), - ResultTypeValue(type=jaqcd.Amplitude(states=["00"]), value={"00": (0.70710678, 0)}), - ], - measuredQubits=list(range(2)), - taskMetadata=task_metadata_zero_shots, - additionalMetadata=additional_metadata, - ) - return result.json() - - -@pytest.fixture -def result_obj_5(task_metadata_shots): - return GateModelTaskResult( - taskMetadata=task_metadata_shots, - additionalMetadata=AdditionalMetadata( - action=jaqcd.Program( - instructions=[jaqcd.CNot(control=0, target=1), jaqcd.CNot(control=2, target=3)], - results=[jaqcd.Probability(targets=[1]), jaqcd.Expectation(observable=["z"])], - ) - ), - measurements=[ - [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], - ], - measuredQubits=[0, 1, 2, 3], - ) - - -@pytest.fixture -def malformatted_results_1(task_metadata_shots, additional_metadata): - return GateModelTaskResult( - measuredQubits=list(range(6)), - taskMetadata=task_metadata_shots, - additionalMetadata=additional_metadata, - ).json() - - -@pytest.fixture -def openqasm_result_obj_shots(task_metadata_shots, additional_metadata_openqasm): - return GateModelTaskResult.construct( - measurements=[[0, 0], [0, 1], [0, 1], [0, 1]], - measuredQubits=[0, 1], - taskMetadata=task_metadata_shots, - additionalMetadata=additional_metadata_openqasm, - resultTypes=[jaqcd.Probability()], - ) - - -def test_openqasm_shots_calculate_result_types(openqasm_result_obj_shots): - result = GateModelQuantumTaskResult._from_object_internal_computational_basis_sampling( - openqasm_result_obj_shots - ) - assert result.result_types[0].type == jaqcd.Probability() - assert np.array_equal(result.result_types[0].value, [0.25, 0.75, 0, 0]) - - -test_ir_results = [ - (jaqcd.Probability(targets=[1]), np.array([0.6, 0.4])), - (jaqcd.Probability(targets=[1, 2]), np.array([0.4, 0.2, 0.0, 0.4])), - ( - jaqcd.Probability(), - np.array([0.1, 0.2, 0.2, 0.0, 0.0, 0.0, 0.0, 0.2, 0.0, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2]), - ), - (jaqcd.Sample(targets=[1], observable=["z"]), np.array([1, -1, 1, 1, -1, -1, 1, -1, 1, 1])), - ( - jaqcd.Sample(targets=[1, 2], observable=["x", "y"]), - np.array([-1, 1, 1, -1, 1, 1, 1, 1, 1, 1]), - ), - ( - jaqcd.Sample(observable=["z"]), - [ - np.array([1, -1, -1, 1, -1, 1, 1, 1, 1, 1]), - np.array([1, -1, 1, 1, -1, -1, 1, -1, 1, 1]), - np.array([-1, -1, 1, -1, -1, -1, 1, -1, 1, 1]), - np.array([1, -1, -1, 1, -1, -1, -1, -1, 1, -1]), - ], - ), - (jaqcd.Expectation(targets=[1], observable=["z"]), 0.2), - (jaqcd.Expectation(targets=[1], observable=[[[[-1, 0], [0, 0]], [[0, 0], [1, 0]]]]), -0.2), - (jaqcd.Expectation(targets=[1, 2], observable=["z", "y"]), 0.6), - (jaqcd.Expectation(observable=["z"]), [0.4, 0.2, -0.2, -0.4]), - (jaqcd.Variance(targets=[1], observable=["z"]), 0.96), - (jaqcd.Variance(targets=[1], observable=[[[[-1, 0], [0, 0]], [[0, 0], [1, 0]]]]), 0.96), - (jaqcd.Variance(targets=[1, 2], observable=["z", "y"]), 0.64), - (jaqcd.Variance(observable=["z"]), [0.84, 0.96, 0.96, 0.84]), -] - - -def test_get_compiled_circuit_rigetti(result_rigetti, quil_program): - """Test get_compiled_circuit method.""" - assert result_rigetti.get_compiled_circuit() == quil_program - - -def test_get_compiled_circuit_oqc(result_oqc, qasm2_program): - """Test get_compiled_circuit method.""" - assert result_oqc.get_compiled_circuit() == qasm2_program - - -def test_get_compiled_circuit_no_qhp_metadata(result_obj_1): - """Test get_compiled_circuit method.""" - result = GateModelQuantumTaskResult.from_object(result_obj_1) - assert result.get_compiled_circuit() is None - - -def test_get_compiled_circuit_no_metadata(result_obj_1): - """Test that the method does not raise an error if metadata is missing.""" - result = GateModelQuantumTaskResult.from_object(result_obj_1) - result.additional_metadata = None - assert result.get_compiled_circuit() is None - - -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]] - ) - measurement_counts = GateModelQuantumTaskResult.measurement_counts_from_measurements( - measurements - ) - expected_counts: Counter = {"1010": 3, "0000": 1, "1000": 2} - assert expected_counts == measurement_counts - - -def test_measurement_probabilities_from_measurement_counts(): - counts = {"00": 1, "01": 1, "10": 1, "11": 97} - probabilities = {"00": 0.01, "01": 0.01, "10": 0.01, "11": 0.97} - - m_probabilities = GateModelQuantumTaskResult.measurement_probabilities_from_measurement_counts( - counts - ) - - assert m_probabilities == probabilities - - -def test_measurements_from_measurement_probabilities(): - shots = 5 - probabilities = {"00": 0.2, "01": 0.2, "10": 0.2, "11": 0.4} - measurements_list = [["0", "0"], ["0", "1"], ["1", "0"], ["1", "1"], ["1", "1"]] - expected_results = np.asarray(measurements_list, dtype=int) - - measurements = GateModelQuantumTaskResult.measurements_from_measurement_probabilities( - probabilities, shots - ) - - assert np.allclose(measurements, expected_results) - - -def test_from_string_measurements(result_str_1): - result_obj = GateModelTaskResult.parse_raw(result_str_1) - task_result = GateModelQuantumTaskResult.from_string(result_str_1) - expected_measurements = np.asarray(result_obj.measurements, dtype=int) - assert task_result.task_metadata == result_obj.taskMetadata - assert task_result.additional_metadata == result_obj.additionalMetadata - assert np.array2string(task_result.measurements) == np.array2string(expected_measurements) - assert not task_result.measurement_counts_copied_from_device - assert not task_result.measurement_probabilities_copied_from_device - assert task_result.measurements_copied_from_device - assert task_result.measured_qubits == result_obj.measuredQubits - assert task_result.values == [] - assert task_result.result_types == [] - - -def test_from_object_result_types(result_obj_5): - result_obj = result_obj_5 - task_result = GateModelQuantumTaskResult.from_object(result_obj) - expected_measurements = np.asarray(result_obj.measurements, dtype=int) - assert np.array2string(task_result.measurements) == np.array2string(expected_measurements) - assert np.allclose(task_result.values[0], np.array([0.6, 0.4])) - assert task_result.values[1] == [0.4, 0.2, -0.2, -0.4] - assert task_result.result_types[0].type == jaqcd.Probability(targets=[1]) - assert task_result.result_types[1].type == jaqcd.Expectation(observable=["z"]) - - -def test_from_string_measurement_probabilities(result_str_3): - result_obj = GateModelTaskResult.parse_raw(result_str_3) - task_result = GateModelQuantumTaskResult.from_string(result_str_3) - assert task_result.measurement_probabilities == result_obj.measurementProbabilities - shots = 100 - 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 _ 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 - - -def test_from_object_equal_to_from_string(result_obj_1, result_str_1, result_str_3): - assert GateModelQuantumTaskResult.from_object( - result_obj_1 - ) == GateModelQuantumTaskResult.from_string(result_str_1) - assert GateModelQuantumTaskResult.from_object( - GateModelTaskResult.parse_raw(result_str_3) - ) == GateModelQuantumTaskResult.from_string(result_str_3) - - -def test_equality(result_str_1, result_str_2): - result_1 = GateModelQuantumTaskResult.from_string(result_str_1) - result_2 = GateModelQuantumTaskResult.from_string(result_str_1) - other_result = GateModelQuantumTaskResult.from_string(result_str_2) - non_result = "not a quantum task result" - - assert result_1 == result_2 - assert result_1 is not result_2 - assert result_1 != other_result - assert result_1 != non_result - - -def test_from_string_simulator_only(result_obj_4, result_str_4): - result_obj = result_obj_4 - result = GateModelQuantumTaskResult.from_string(result_str_4) - assert len(result.result_types) == len(result_obj.resultTypes) - for i in range(len(result.result_types)): - rt = result.result_types[i] - expected_rt = result_obj.resultTypes[i] - assert rt.type == expected_rt.type - if isinstance(rt.value, np.ndarray): - assert np.allclose(rt.value, expected_rt.value) - else: - assert rt.value == expected_rt.value - - -def test_get_value_by_result_type(result_obj_4): - result = GateModelQuantumTaskResult.from_object(result_obj_4) - assert np.allclose( - result.get_value_by_result_type(ResultType.Probability(target=0)), result.values[0] - ) - assert np.allclose(result.get_value_by_result_type(ResultType.StateVector()), result.values[1]) - assert ( - result.get_value_by_result_type(ResultType.Expectation(observable=Observable.Y(), target=0)) - == result.values[2] - ) - assert ( - result.get_value_by_result_type(ResultType.Variance(observable=Observable.Y(), target=0)) - == result.values[3] - ) - assert result.get_value_by_result_type(ResultType.Amplitude(state=["00"])) == result.values[4] - - -@pytest.mark.xfail(raises=ValueError) -def test_get_value_by_result_type_value_error(result_obj_4): - result = GateModelQuantumTaskResult.from_object(result_obj_4) - result.get_value_by_result_type(ResultType.Probability(target=[0, 1])) - - -@pytest.mark.xfail(raises=ValueError) -def test_shots_no_measurements_no_measurement_probs(malformatted_results_1): - GateModelQuantumTaskResult.from_string(malformatted_results_1) - - -@pytest.mark.parametrize("ir_result,expected_result", test_ir_results) -def test_calculate_ir_results(ir_result, expected_result): - ir_string = jaqcd.Program( - 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], - ] - ) - result_types = GateModelQuantumTaskResult._calculate_result_types( - ir_string, measurements, measured_qubits - ) - assert len(result_types) == 1 - assert result_types[0].type == ir_result - assert np.allclose(result_types[0].value, expected_result) - - -@pytest.mark.xfail(raises=ValueError) -def test_calculate_ir_results_value_error(): - ir_string = json.dumps({"results": [{"type": "foo"}]}) - measured_qubits = [0] - measurements = np.array([[0]]) - GateModelQuantumTaskResult._calculate_result_types(ir_string, measurements, measured_qubits) - - -@pytest.mark.parametrize( - "observable_1, observable_2", - [ - ( - jaqcd.Expectation(targets=[1, 2], observable=["x"]), - jaqcd.Expectation(observable=["x"], targets=[1, 2]), - ), - pytest.param( - jaqcd.Expectation(observable=["x"], targets=[1, 2]), - jaqcd.Expectation(observable=["x"], targets=[2, 1]), - marks=pytest.mark.xfail, - ), - pytest.param( - jaqcd.Expectation(observable=["x"], targets=[1, 2]), - jaqcd.Sample(observable=["x"], targets=[2, 1]), - marks=pytest.mark.xfail, - ), - ( - jaqcd.Expectation( - observable=[ - [[[0, 0], [0.512345, 0]], [[0.543215, 0], [0, 0]]], - [[[1, 0], [1, 0]], [[1, 0], [-1, 0]]], - ], - targets=[1, 2], - ), - jaqcd.Expectation( - observable=[ - [[[0, 0], [0.512345, 0]], [[0.543215, 0], [0, 0]]], - [[[1, 0], [1, 0]], [[1, 0], [-1, 0]]], - ], - targets=[1, 2], - ), - ), - ( - jaqcd.Expectation(observable=["y", "z"], targets=[1, 2]), - jaqcd.Expectation(observable=["y", "z"], targets=[1, 2]), - ), - ], -) -def test_hash_result_types(observable_1, observable_2): - assert GateModelQuantumTaskResult._result_type_hash( - observable_1 - ) == GateModelQuantumTaskResult._result_type_hash(observable_2) - - -@patch( - "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": { - "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": 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, - }, - }, - } - ) - res = GateModelQuantumTaskResult.from_string(result_str) - assert ( - res.get_value_by_result_type(ResultType.Variance(observable=Observable.Z(), target=[0])) - == 12 - ) - calculate_result_types_mocked.assert_not_called() diff --git a/test/unit_tests/braket/tasks/test_local_quantum_task.py b/test/unit_tests/braket/tasks/test_local_quantum_task.py deleted file mode 100644 index aca0aa20..00000000 --- a/test/unit_tests/braket/tasks/test_local_quantum_task.py +++ /dev/null @@ -1,61 +0,0 @@ -# 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 uuid - -import numpy as np -import pytest - -from braket.task_result import TaskMetadata -from braket.tasks import GateModelQuantumTaskResult -from braket.tasks.local_quantum_task import LocalQuantumTask - -RESULT = GateModelQuantumTaskResult( - 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], - result_types=None, - values=None, -) - -TASK = LocalQuantumTask(RESULT) - - -def test_id(): - # Task ID is valid UUID - uuid.UUID(TASK.id) - - -def test_state(): - assert TASK.state() == "COMPLETED" - - -def test_result(): - assert RESULT.task_metadata.id == TASK.id - assert TASK.result() == RESULT - - -@pytest.mark.xfail(raises=NotImplementedError) -def test_cancel(): - TASK.cancel() - - -@pytest.mark.xfail(raises=NotImplementedError) -def test_async(): - TASK.async_result() - - -def test_str(): - expected = f"LocalQuantumTask('id':{TASK.id})" - assert str(TASK) == expected 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 deleted file mode 100644 index 3c7c523b..00000000 --- a/test/unit_tests/braket/tasks/test_local_quantum_task_batch.py +++ /dev/null @@ -1,40 +0,0 @@ -# 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 uuid - -import numpy as np - -from braket.task_result import TaskMetadata -from braket.tasks import GateModelQuantumTaskResult -from braket.tasks.local_quantum_task_batch import LocalQuantumTaskBatch - -RESULTS = [ - GateModelQuantumTaskResult( - 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], - result_types=None, - values=None, - ) -] - -TASK_BATCH = LocalQuantumTaskBatch(RESULTS) - - -def test_results(): - assert TASK_BATCH.results() == RESULTS - assert len(TASK_BATCH.results()) == 1 diff --git a/test/unit_tests/braket/tasks/test_photonic_model_quantum_task_result.py b/test/unit_tests/braket/tasks/test_photonic_model_quantum_task_result.py deleted file mode 100644 index c3110b8e..00000000 --- a/test/unit_tests/braket/tasks/test_photonic_model_quantum_task_result.py +++ /dev/null @@ -1,87 +0,0 @@ -# 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.ir.blackbird import Program as BlackbirdProgram -from braket.task_result import ( - AdditionalMetadata, - PhotonicModelTaskResult, - TaskMetadata, - XanaduMetadata, -) -from braket.tasks import PhotonicModelQuantumTaskResult - - -@pytest.fixture -def task_metadata(): - return TaskMetadata(**{"id": "task_arn", "deviceId": "arn1", "shots": 100}) - - -@pytest.fixture -def xanadu_metadata(): - return XanaduMetadata(compiledProgram="DECLARE ro BIT[2];") - - -@pytest.fixture -def blackbird_program(): - return BlackbirdProgram(source="Vac | q[0]") - - -@pytest.fixture -def additional_metadata(blackbird_program, xanadu_metadata): - return AdditionalMetadata(action=blackbird_program, xanaduMetadata=xanadu_metadata) - - -@pytest.fixture -def measurements(): - return [[[1, 2, 3, 4]], [[4, 3, 2, 1]], [[0, 0, 0, 0]]] - - -@pytest.fixture -def result_1(measurements, task_metadata, additional_metadata): - return PhotonicModelTaskResult( - measurements=measurements, - taskMetadata=task_metadata, - additionalMetadata=additional_metadata, - ) - - -@pytest.fixture -def result_1_str(result_1): - return result_1.json() - - -@pytest.fixture -def empty_result(task_metadata, additional_metadata): - updated_metadata = task_metadata.copy() - updated_metadata.id = "empty_arn" - return PhotonicModelTaskResult( - taskMetadata=updated_metadata, additionalMetadata=additional_metadata - ) - - -def test_from_object_equals_from_string(result_1, result_1_str): - assert PhotonicModelQuantumTaskResult.from_object( - result_1 - ) == PhotonicModelQuantumTaskResult.from_string(result_1_str) - - -def test_equality(result_1, empty_result): - quantum_result1 = PhotonicModelQuantumTaskResult.from_object(result_1) - quantum_empty = PhotonicModelQuantumTaskResult.from_object(empty_result) - non_result = "not a quantum task result" - - assert quantum_result1 == quantum_result1 - assert quantum_result1 != quantum_empty - assert quantum_result1 != non_result diff --git a/test/unit_tests/braket/tasks/test_tasks_init.py b/test/unit_tests/braket/tasks/test_tasks_init.py deleted file mode 100644 index 610c29bc..00000000 --- a/test/unit_tests/braket/tasks/test_tasks_init.py +++ /dev/null @@ -1,32 +0,0 @@ -# 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 sys -from unittest.mock import patch - -import braket.tasks - - -@patch("braket.ipython_utils.running_in_jupyter") -def test_nest_asyncio_not_applied(running_in_jupyter): - running_in_jupyter.return_value = False - importlib.reload(braket.tasks) - assert "nest_asyncio" not in sys.modules - - -@patch("braket.ipython_utils.running_in_jupyter") -def test_nest_asyncio_is_applied(running_in_jupyter): - running_in_jupyter.return_value = True - importlib.reload(braket.tasks) - assert "nest_asyncio" in sys.modules diff --git a/test/unit_tests/braket/test_imports.py b/test/unit_tests/braket/test_imports.py deleted file mode 100644 index 728c6311..00000000 --- a/test/unit_tests/braket/test_imports.py +++ /dev/null @@ -1,48 +0,0 @@ -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", 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,)) - 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) diff --git a/test/unit_tests/braket/test_ipython_utils.py b/test/unit_tests/braket/test_ipython_utils.py deleted file mode 100644 index b275905a..00000000 --- a/test/unit_tests/braket/test_ipython_utils.py +++ /dev/null @@ -1,53 +0,0 @@ -# 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 sys -from unittest.mock import Mock - -import braket.ipython_utils as ipython_utils - - -def test_running_in_jupyter(): - assert not ipython_utils.running_in_jupyter() - - -def test_ipython_imported_but_ipython_none(): - _mock_ipython(None) - assert not ipython_utils.running_in_jupyter() - - -def test_ipython_imported_but_not_in_jupyter(): - _mock_ipython(MockIPython(None)) - assert not ipython_utils.running_in_jupyter() - - -def test_ipython_imported_and_in_jupyter(): - _mock_ipython(MockIPython("non empty kernel")) - assert ipython_utils.running_in_jupyter() - - -def get_ipython(): - pass - - -def _mock_ipython(get_ipython_result): - module = sys.modules["test_ipython_utils"] - sys.modules["IPython"] = module - - get_ipython = Mock(return_value=get_ipython_result) - sys.modules["IPython"].__dict__["get_ipython"] = get_ipython - - -class MockIPython: - def __init__(self, kernel): - self.kernel = kernel diff --git a/test/unit_tests/braket/timings/test_time_series.py b/test/unit_tests/braket/timings/test_time_series.py deleted file mode 100755 index 729813aa..00000000 --- a/test/unit_tests/braket/timings/test_time_series.py +++ /dev/null @@ -1,315 +0,0 @@ -# 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.timings.time_series import StitchBoundaryCondition, TimeSeries, _all_close - - -@pytest.fixture -def default_values(): - return [ - (2700, Decimal("25.1327")), - (300, Decimal("25.1327")), - (600, Decimal("15.1327")), - (Decimal("0.3"), Decimal("0.4")), - ] - - -@pytest.fixture -def default_time_series(default_values): - time_series = TimeSeries() - for value in default_values: - time_series.put(value[0], value[1]) - return time_series - - -def test_add_chaining(): - time_series = ( - TimeSeries() - .put(time=0, value=0) - .put(300, 25.1327) - .put(2700, 25.1327) - .put(3000, 0) - .put(2700, 25.1327) - ) - assert len(time_series) == 4 - - -def test_iteration_sorted(default_values, default_time_series): - sorted_returned_values = [(item.time, item.value) for item in default_time_series] - assert sorted_returned_values == sorted(default_values) - - -def test_get_sorted(default_values, default_time_series): - sorted_values = sorted(default_values) - assert default_time_series.times() == [item[0] for item in sorted_values] - assert default_time_series.values() == [item[1] for item in sorted_values] - - -def test_constant_like(): - times = list(range(10)) - constant_ts = TimeSeries.constant_like(times, constant=3.14) - assert times == constant_ts.times() - assert constant_ts.values() == [3.14] * len(times) - - -def test_periodic_signal(): - times = list(range(4)) - values = [0, 1, 3, 0] - new_ts = TimeSeries.periodic_signal(times=times, values=values, num_repeat=3) - expected_times = list(range(10)) - expected_values = [0, 1, 3, 0, 1, 3, 0, 1, 3, 0] - - assert new_ts.times() == expected_times - assert new_ts.values() == expected_values - - -def test_constant_like_with_time_series(): - time_series = TimeSeries().put(0.0, 0.0).put(1.2, 3.14) - constant_ts = TimeSeries.constant_like(time_series, constant=3.14) - times = time_series.times() - assert times == constant_ts.times() - assert constant_ts.values() == [3.14] * len(times) - - -@pytest.mark.parametrize( - "area, value_max, slew_rate_max, time_separation_min", - [ - (2.0, 2.0, 4.0, 1.0), - (1.0, 2.0, 4.0, 1.0), - (4.0, 2.0, 1.0, 1.0), - (1.0, 2.0, 1.0, 1.0), - ], -) -def test_trapezoidal_signal_as_triangular_signal( - area, value_max, slew_rate_max, time_separation_min -): - ts = TimeSeries.trapezoidal_signal(area, value_max, slew_rate_max, time_separation_min) - t_ramp = max(time_separation_min, value_max / slew_rate_max) - value = area / t_ramp - assert ts.times() == [0, t_ramp, 2 * t_ramp] - assert ts.values() == [0, value, 0] - - -@pytest.mark.parametrize( - "area, value_max, slew_rate_max, time_separation_min", - [ - (4.0, 2.0, 4.0, 1.0), - (2.1, 2.0, 4.0, 1.0), - (6.0, 2.0, 1.0, 1.0), - (4.1, 2.0, 1.0, 1.0), - ], -) -def test_trapezoidal_signal_as_min_trapezoidal_signal( - area, value_max, slew_rate_max, time_separation_min -): - ts = TimeSeries.trapezoidal_signal(area, value_max, slew_rate_max, time_separation_min) - t_ramp = max(time_separation_min, value_max / slew_rate_max) - value = area / (t_ramp + time_separation_min) - assert ts.times() == [0, t_ramp, t_ramp + time_separation_min, 2 * t_ramp + time_separation_min] - assert ts.values() == [0, value, value, 0] - - -@pytest.mark.parametrize( - "area, value_max, slew_rate_max, time_separation_min", - [ - (4.1, 2.0, 4.0, 1.0), - (6.1, 2.0, 1.0, 1.0), - ], -) -def test_trapezoidal_signal_with_large_area(area, value_max, slew_rate_max, time_separation_min): - ts = TimeSeries.trapezoidal_signal(area, value_max, slew_rate_max, time_separation_min) - t_ramp = max(time_separation_min, value_max / slew_rate_max) - t_plateau = area / value_max - t_ramp - assert ts.times() == [0, t_ramp, t_ramp + t_plateau, 2 * t_ramp + t_plateau] - assert ts.values() == [0, value_max, value_max, 0] - - -@pytest.mark.xfail(raises=ValueError) -@pytest.mark.parametrize( - "area, value_max, slew_rate_max, time_separation_min", - [ - (-4.1, 2.0, 4.0, 1.0), - (4.1, -2.0, 4.0, 1.0), - (4.1, 2.0, -4.0, 1.0), - (4.1, 2.0, 4.0, -1.0), - ], -) -def test_trapezoidal_signal_with_negative_para(area, value_max, slew_rate_max, time_separation_min): - TimeSeries.trapezoidal_signal(area, value_max, slew_rate_max, time_separation_min) - - -@pytest.mark.xfail(raises=ValueError) -def test_periodic_signal_not_eq_length(): - times = list(range(5)) - values = [0.5, 1, 1, 0] - TimeSeries.periodic_signal(times=times, values=values, num_repeat=3) - - -def test_concatenate(): - times_1 = list(range(4)) - values_1 = [0.5, 1, 1, 0] - time_series_1 = TimeSeries.from_lists(times=times_1, values=values_1) - - times_2 = list(range(4, 8)) - values_2 = [-0.5, -1, -1, 0] - time_series_2 = TimeSeries.from_lists(times=times_2, values=values_2) - - new_ts = time_series_1.concatenate(time_series_2) - - assert new_ts.times() == times_1 + times_2 - assert new_ts.values() == values_1 + values_2 - - new_ts = time_series_1.concatenate(TimeSeries()) - assert new_ts.times() == times_1 - assert new_ts.values() == values_1 - - new_ts = TimeSeries().concatenate(time_series_1) - assert new_ts.times() == times_1 - assert new_ts.values() == values_1 - - new_ts = TimeSeries().concatenate(TimeSeries()) - assert new_ts.times() == [] - assert new_ts.values() == [] - - -@pytest.mark.xfail(raises=ValueError) -def test_concatenate_not_ordered(): - times_1 = list(range(4)) - values_1 = [0.5, 1, 1, 0] - time_series_1 = TimeSeries.from_lists(times=times_1, values=values_1) - - times_2 = list(range(4)) - values_2 = [-0.5, -1, -1, 0] - time_series_2 = TimeSeries.from_lists(times=times_2, values=values_2) - - time_series_1.concatenate(time_series_2) - - -def test_from_lists(): - times = list(range(4)) - values = [0.5, 1, 1, 0] - ts = TimeSeries.from_lists(times=times, values=values) - assert ts.times() == times - assert ts.values() == values - - -@pytest.mark.xfail(raises=ValueError) -def test_from_lists_not_equal_size(): - times = list(range(4)) - values = [0.5, 1, 1] - TimeSeries.from_lists(times=times, values=values) - - -def test_stitch(): - times_1 = list(range(4)) - values_1 = [0.5, 1, 1, 0] - time_series_1 = TimeSeries.from_lists(times=times_1, values=values_1) - - times_2 = list(range(4)) - values_2 = [-0.5, -1, -1, 0] - time_series_2 = TimeSeries.from_lists(times=times_2, values=values_2) - - new_ts_mean = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.MEAN) - new_ts_left = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.LEFT) - new_ts_right = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.RIGHT) - - excepted_times = list(range(7)) - assert new_ts_mean.times() == excepted_times - assert new_ts_left.times() == excepted_times - assert new_ts_right.times() == excepted_times - - assert new_ts_mean.values() == [0.5, 1, 1, -0.25, -1, -1, 0] - assert new_ts_left.values() == [0.5, 1, 1, 0, -1, -1, 0] - assert new_ts_right.values() == [0.5, 1, 1, -0.5, -1, -1, 0] - - -def test_stitch_empty_ts(): - times = list(range(4)) - values = [0.5, 1, 1, 0] - time_series = TimeSeries.from_lists(times=times, values=values) - new_ts = time_series.stitch(TimeSeries()) - assert new_ts.times() == times - assert new_ts.values() == values - - new_ts = TimeSeries().stitch(TimeSeries()) - assert new_ts.times() == [] - assert new_ts.values() == [] - - new_ts = TimeSeries().stitch(time_series) - assert new_ts.times() == time_series.times() - assert new_ts.values() == time_series.values() - - -@pytest.mark.xfail(raises=ValueError) -def test_stitch_wrong_bndry_value(): - times_1 = list(range(4)) - values_1 = [0.5, 1, 1, 0] - time_series_1 = TimeSeries.from_lists(times=times_1, values=values_1) - - times_2 = list(range(4)) - values_2 = [-0.5, -1, -1, 0] - time_series_2 = TimeSeries.from_lists(times=times_2, values=values_2) - - time_series_1.stitch(time_series_2, boundary="average") - - -@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 - (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): - value_res = Decimal("1") - assert expected_times == default_time_series.discretize(time_res, value_res).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 - (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")]), - (Decimal(100), [Decimal("0"), Decimal("0"), Decimal("0"), Decimal("0")]), - ], -) -def test_discretize_values(default_time_series, value_res, expected_values): - time_res = Decimal("0.1") - assert expected_values == default_time_series.discretize(time_res, value_res).values() - - -@pytest.mark.parametrize( - "first_series, second_series, expected_result", - [ - (TimeSeries(), TimeSeries(), True), - (TimeSeries().put(0.1, 0.2), TimeSeries(), 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): - result = _all_close(first_series, second_series) - assert result == expected_result diff --git a/test/unit_tests/braket/tracking/test_pricing.py b/test/unit_tests/braket/tracking/test_pricing.py deleted file mode 100644 index 0d6b4348..00000000 --- a/test/unit_tests/braket/tracking/test_pricing.py +++ /dev/null @@ -1,52 +0,0 @@ -# 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 io -from unittest.mock import patch - -import pytest - -from braket.tracking.pricing import Pricing - - -@pytest.fixture -def mock_http(): - with patch("urllib3.PoolManager") as http_mock: - http_mock().request.return_value = io.BytesIO( - b"""line1 -line2 -line3 -line4 -line5 -A,B -1,1 -1,2 -""" - ) - yield http_mock() - - -def test_search_prices(mock_http): - pricer = Pricing() - assert pricer.price_search(A="0") == [] - assert pricer.price_search(A="1", B="1") == [{"A": "1", "B": "1"}] - assert pricer.price_search(A="1") == [{"A": "1", "B": "1"}, {"A": "1", "B": "2"}] - - -@patch.dict("os.environ", {"BRAKET_PRICE_OFFERS_URL": "https://myurl"}) -def test_price_offer_env_var(mock_http): - pricer = Pricing() - pricer.get_prices() - - mock_http.request.assert_called_with("GET", "https://myurl", preload_content=False) diff --git a/test/unit_tests/braket/tracking/test_tracker.py b/test/unit_tests/braket/tracking/test_tracker.py deleted file mode 100644 index a97ce57b..00000000 --- a/test/unit_tests/braket/tracking/test_tracker.py +++ /dev/null @@ -1,224 +0,0 @@ -# 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 datetime import timedelta -from decimal import Decimal -from unittest.mock import patch - -import pytest - -from braket.tracking.tracker import Tracker -from braket.tracking.tracking_context import active_trackers -from braket.tracking.tracking_events import ( - _TaskCompletionEvent, - _TaskCreationEvent, - _TaskStatusEvent, -) - - -@pytest.fixture() -def empty_tracker(): - with Tracker() as _tracker: - yield _tracker - - -def test_tracker_enter_exit(): - x = len(active_trackers()) - with Tracker() as t: - assert len(active_trackers()) == 1 + x - with Tracker() as s: - assert len(active_trackers()) == 2 + x - assert s in active_trackers() - assert t in active_trackers() - assert len(active_trackers()) == x - - -def test_tracker_start_stop(): - t = Tracker() - assert t not in active_trackers() - t.start() - assert t in active_trackers() - t.stop() - assert t not in active_trackers() - - -def test_receive_fake_event(empty_tracker): - event = "NOT AN EVENT" - with pytest.raises(ValueError): - empty_tracker.receive_event(event) - - -CREATE_EVENTS = [ - _TaskCreationEvent(arn="task1:::region", shots=100, is_job_task=True, device="qpu/foo"), - _TaskCreationEvent(arn="task2:::region", shots=100, is_job_task=False, device="qpu/foo"), - _TaskCreationEvent( - arn="job_sim_task:::region", shots=0, is_job_task=True, device="simulator/bar" - ), - _TaskCreationEvent( - arn="notjob_sim_task:::region", shots=0, is_job_task=False, device="simulator/bar" - ), - _TaskCreationEvent( - arn="task_fail:::region", shots=0, is_job_task=False, device="simulator/tn1" - ), - _TaskCreationEvent( - arn="task_cancel:::region", shots=0, is_job_task=False, device="simulator/baz" - ), - _TaskCreationEvent( - arn="2000qtask:::region", shots=100, is_job_task=False, device="qpu/2000Qxyz" - ), - _TaskCreationEvent( - arn="adv_task:::region", shots=100, is_job_task=False, device="qpu/Advantage_system123" - ), - _TaskCreationEvent( - arn="unfinished_sim_task:::region", shots=1000, is_job_task=False, device="simulator/bar" - ), - _TaskCreationEvent( - arn="no_price:::region", shots=1000, is_job_task=False, device="something_else" - ), - _TaskCreationEvent( - arn="unbilled_task0:::region", - shots=100, - is_job_task=True, - device="qpu/foo", - ), - _TaskCreationEvent( - arn="unbilled_task1:::region", - shots=100, - is_job_task=True, - device="qpu/foo", - ), -] - -GET_EVENTS = [ - _TaskStatusEvent(arn="untracked_task:::region", status="FOO"), - _TaskStatusEvent(arn="task1:::region", status="BAR"), - _TaskStatusEvent(arn="task2:::region", status="FAILED"), -] -COMPLETE_EVENTS = [ - _TaskCompletionEvent(arn="untracked_task:::region", execution_duration=999999, status="BAR"), - _TaskCompletionEvent(arn="task1:::region", execution_duration=None, status="COMPLETED"), - _TaskCompletionEvent(arn="job_sim_task:::region", execution_duration=123, status="COMPLETED"), - _TaskCompletionEvent( - arn="notjob_sim_task:::region", execution_duration=1729, status="COMPLETED" - ), - _TaskCompletionEvent(arn="task_fail:::region", execution_duration=12345, status="FAILED"), - _TaskCompletionEvent(arn="task_cancel:::region", execution_duration=None, status="CANCELLED"), - _TaskCompletionEvent( - arn="unbilled_task0:::region", - execution_duration=123, - status="COMPLETED", - has_reservation_arn=True, - ), - _TaskCompletionEvent( - arn="unbilled_task1:::region", - execution_duration=123, - status="COMPLETED", - has_reservation_arn=True, - ), -] - - -@pytest.fixture -def create_tracker(empty_tracker): - for e in CREATE_EVENTS: - empty_tracker.receive_event(e) - return empty_tracker - - -@pytest.fixture -def get_tracker(create_tracker): - for e in GET_EVENTS: - create_tracker.receive_event(e) - return create_tracker - - -@pytest.fixture -def completed_tracker(get_tracker): - for e in COMPLETE_EVENTS: - get_tracker.receive_event(e) - return get_tracker - - -def test_tracked_resources(create_tracker): - assert len(CREATE_EVENTS) == len(create_tracker.tracked_resources()) - - -def mock_qpu_price(**kwargs): - if "Shot" in kwargs["Product Family"]: - return [{"PricePerUnit": "0.001", "Currency": "USD"}] - else: - return [{"PricePerUnit": "1.0", "Currency": "USD"}] - - -@patch("braket.tracking.tracker.price_search") -def test_qpu_task_cost(price_mock, completed_tracker): - price_mock.side_effect = mock_qpu_price - cost = completed_tracker.qpu_tasks_cost() - assert cost == Decimal("3.3") - - price_mock.side_effect = [[]] - with pytest.raises(ValueError, match="Found 0 products"): - completed_tracker.qpu_tasks_cost() - - price_mock.side_effect = [[{}], [{}, {}]] - with pytest.raises(ValueError, match="Found 2 products"): - completed_tracker.qpu_tasks_cost() - - price_mock.side_effect = [[{"Currency": "BAD"}], [{"Currency": "BAD"}]] - with pytest.raises(ValueError, match="Expected USD"): - completed_tracker.qpu_tasks_cost() - - -@patch("braket.tracking.tracker.price_search") -def test_simulator_task_cost(price_mock, completed_tracker): - price_mock.return_value = [{"PricePerUnit": "6.0", "Currency": "USD", "Unit": "minutes"}] - cost = completed_tracker.simulator_tasks_cost() - expected = Decimal("0.0001") * (3000 + 3000 + 12345) - assert cost == expected - - price_mock.return_value = [] - with pytest.raises(ValueError, match="Found 0 products"): - completed_tracker.simulator_tasks_cost() - - price_mock.return_value = [{"Currency": "BAD"}] - with pytest.raises(ValueError, match="Expected USD"): - completed_tracker.simulator_tasks_cost() - - -def test_quantum_task_statistics(completed_tracker): - stats = completed_tracker.quantum_tasks_statistics() - expected = { - "qpu/foo": { - "shots": 400, - "tasks": {"COMPLETED": 3, "FAILED": 1}, - "execution_duration": timedelta(microseconds=246000), - "billed_execution_duration": timedelta(0), - }, - "simulator/bar": { - "shots": 1000, - "tasks": {"COMPLETED": 2, "CREATED": 1}, - "execution_duration": timedelta(seconds=1, microseconds=852000), - "billed_execution_duration": timedelta(seconds=6), - }, - "simulator/tn1": { - "shots": 0, - "tasks": {"FAILED": 1}, - "execution_duration": timedelta(seconds=12, microseconds=345000), - "billed_execution_duration": timedelta(seconds=12, microseconds=345000), - }, - "simulator/baz": {"shots": 0, "tasks": {"CANCELLED": 1}}, - "qpu/2000Qxyz": {"shots": 100, "tasks": {"CREATED": 1}}, - "qpu/Advantage_system123": {"shots": 100, "tasks": {"CREATED": 1}}, - "something_else": {"shots": 1000, "tasks": {"CREATED": 1}}, - } - assert stats == expected diff --git a/test/unit_tests/braket/tracking/test_tracking_context.py b/test/unit_tests/braket/tracking/test_tracking_context.py deleted file mode 100644 index 2963bbd0..00000000 --- a/test/unit_tests/braket/tracking/test_tracking_context.py +++ /dev/null @@ -1,44 +0,0 @@ -# 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 Mock - -from braket.tracking.tracking_context import ( - active_trackers, - broadcast_event, - deregister_tracker, - register_tracker, -) - - -def test_tracking_context(): - assert active_trackers() == set() - - -def test_register_deregister_tracker(): - register_tracker("foo") - assert active_trackers() == {"foo"} - register_tracker("bar") - register_tracker("bar") - assert active_trackers() == {"foo", "bar"} - deregister_tracker("foo") - assert active_trackers() == {"bar"} - deregister_tracker("bar") - - -def test_broadcast_event(): - tracker = Mock() - register_tracker(tracker) - broadcast_event("EVENT") - tracker.receive_event.assert_called_with("EVENT") - deregister_tracker(tracker) From 77fd3141fde80398e2dc47832cbea4098224c62b Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 7 May 2024 11:30:12 -0400 Subject: [PATCH 1152/1165] Update files in repository root --- CONTRIBUTING.md | 31 ++--- README.md | 324 +++++++++++++++++++++--------------------------- setup.py | 32 ++--- tox.ini | 35 ++---- 4 files changed, 169 insertions(+), 253 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0df22f51..3788087a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,7 @@ information to effectively respond to your bug report or contribution. We welcome you to use the GitHub issue tracker to report bugs or suggest features. -When filing an issue, please check [existing open](https://github.com/amazon-braket/amazon-braket-sdk-python/issues) and [recently closed](https://github.com/amazon-braket/amazon-braket-sdk-python/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20) issues to make sure somebody else hasn't already +When filing an issue, please check [existing open](https://github.com/amazon-braket/autoqasm/issues) and [recently closed](https://github.com/amazon-braket/autoqasm/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20) issues to make sure somebody else hasn't already reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: * A reproducible test case or series of steps. @@ -52,35 +52,22 @@ Before sending us a pull request, please ensure that: ### Pull Down the Code -1. If you do not already have one, create a GitHub account by following the prompts at [Join Github](https://github.com/join). -1. Create a fork of this repository on GitHub. You should end up with a fork at `https://github.com//amazon-braket-sdk-python`. +1. If you do not already have one, create a GitHub account by following the prompts at [Join GitHub](https://github.com/join). +1. Create a fork of this repository on GitHub. You should end up with a fork at `https://github.com//autoqasm`. 1. Follow the instructions at [Fork a Repo](https://help.github.com/en/articles/fork-a-repo) to fork a GitHub repository. -1. Clone your fork of the repository: `git clone https://github.com//amazon-braket-sdk-python` where `` is your github username. +1. Clone your fork of the repository: `git clone https://github.com//autoqasm` where `` is your github username. ### Run the Unit Tests 1. Install tox using `pip install tox` 1. Install coverage using `pip install '.[test]'` -1. cd into the amazon-braket-sdk-python folder: `cd amazon-braket-sdk-python` or `cd /environment/amazon-braket-sdk-python` +1. cd into the autoqasm folder: `cd autoqasm` or `cd /environment/autoqasm` 1. Run the following tox command and verify that all unit tests pass: `tox -e unit-tests` You can also pass in various pytest arguments `tox -e unit-tests -- your-arguments` to run selected tests. For more information, please see [pytest usage](https://docs.pytest.org/en/stable/usage.html). -### Run the Integration Tests - -Run the integration tests to make sure that the system as a whole still works. - -1. Follow the instructions at [Set Up the AWS Command Line Interface (AWS CLI)](https://docs.aws.amazon.com/polly/latest/dg/setup-aws-cli.html). -1. Set the `AWS_PROFILE` information - ```bash - export AWS_PROFILE=Your_Profile_Name - ``` -1. Run the following tox command and verify that integ tests pass: `tox -e integ-tests` - -You can also pass in various pytest arguments `tox -e integ-tests -- your-arguments` to run selected tests. For more information, please see [pytest usage](https://docs.pytest.org/en/stable/usage.html). - ### Make and Test Your Change 1. Create a new git branch: @@ -145,7 +132,7 @@ timeline Pre-Pull Request
(make PR) : Code linting (tox -e linters) : Docs linting (tox -e docs) : Static typing analysis (covered by the linters) - : Tests (tox -e unit-tests|tox -e integ-tests) + : Tests (tox -e unit-tests) Pull Request
(CI checks) : Semantic PR title check : Related issue check @@ -215,12 +202,8 @@ You can then find the generated HTML files in `build/documentation/html`. ## Find Contributions to Work On -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. +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/autoqasm/labels/help%20wanted) issues is a great place to start. -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 b03bd1ef..3778c4e3 100644 --- a/README.md +++ b/README.md @@ -1,251 +1,205 @@ -# Amazon Braket Python SDK +# AutoQASM -[![Latest Version](https://img.shields.io/pypi/v/amazon-braket-sdk.svg)](https://pypi.python.org/pypi/amazon-braket-sdk) -[![Supported Python Versions](https://img.shields.io/pypi/pyversions/amazon-braket-sdk.svg)](https://pypi.python.org/pypi/amazon-braket-sdk) -[![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) +**AutoQASM is not an officially supported AWS product.** -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. +This experimental module offers a new quantum-imperative programming experience embedded in Python +for developing quantum programs. -## Prerequisites -Before you begin working with the Amazon Braket SDK, make sure that you've installed or configured the following prerequisites. +All of the code in the `experimental` module is _experimental_ software. We may change, remove, or +deprecate parts of the AutoQASM API without notice. The name AutoQASM is a working title and is +also subject to change. -### Python 3.9 or greater -Download and install Python 3.9 or greater from [Python.org](https://www.python.org/downloads/). +For a fully supported quantum developer experience, +please continue to use the rest of the Amazon Braket Python SDK by following +[these instructions](https://github.com/amazon-braket/amazon-braket-sdk-python#installing-the-amazon-braket-python-sdk). +If you are interested in our active development efforts, and you are not +afraid of a few bugs, please keep on reading! -### Git -Install Git from https://git-scm.com/downloads. Installation instructions are provided on the download page. +## Why AutoQASM? -### IAM user or role with required permissions -As a managed service, Amazon Braket performs operations on your behalf on the AWS hardware that is managed by Amazon Braket. Amazon Braket can perform only operations that the user permits. You can read more about which permissions are necessary in the AWS Documentation. +AutoQASM provides a Pythonic developer experience for writing quantum programs. The working title "AutoQASM" is derived from the name of the [AutoGraph module of TensorFlow](https://www.tensorflow.org/api_docs/python/tf/autograph). AutoQASM uses AutoGraph to construct quantum assembly (QASM) programs rather than TensorFlow graphs. -The Braket Python SDK should not require any additional permissions aside from what is required for using Braket. However, if you are using an IAM role with a path in it, you should grant permission for iam:GetRole. +AutoQASM provides a natural interface for expressing quantum programs with mid-circuit measurements +and classical control flow using native Python language features. It allows the construction of +modular programs consisting of common programming constructs such as loops and subroutines. This +enables a more imperative programming style than constructing programs via a series of function calls +on a circuit object. -To learn more about IAM user, roles, and policies, see [Adding and Removing IAM Identity Permissions](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage-attach-detach.html). +AutoQASM programs can be serialized to OpenQASM. This textual representation for quantum programs is widely supported and enables interoperability among various frameworks. A crucial part of our serialization process is that modular structures within the program, such as loops and subroutines, are preserved when serializing to OpenQASM. -### Boto3 and setting up AWS credentials +Although it is still a work in progress, the intent is that AutoQASM will support any quantum programming paradigm which falls into the [OpenQASM 3.0](https://openqasm.com) language scope. AutoQASM supports serializing quantum programs to OpenQASM, which allows the programs to interoperate with any library or service that supports OpenQASM programs, such as Amazon Braket. -Follow the installation [instructions](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html) for Boto3 and setting up AWS credentials. +See the [Quick Start](#quick-start) section below, as well as the AutoQASM [example notebooks](../../../../examples/autoqasm), for examples of AutoQASM usage. -**Note:** Make sure that your AWS region is set to one supported by Amazon Braket. You can check this in your AWS configuration file, which is located by default at `~/.aws/config`. -### Configure your AWS account with the resources necessary for Amazon Braket -If you are new to Amazon Braket, onboard to the service and create the resources necessary to use Amazon Braket using the [AWS console](https://console.aws.amazon.com/braket/home ). +## Installation -## Installing the Amazon Braket Python SDK - -The Amazon Braket Python SDK can be installed with pip as follows: - -```bash -pip install amazon-braket-sdk +AutoQASM is an experimental module and is not yet part of the released Amazon Braket SDK. +To use AutoQASM, you'll need to install directly from the `feature/autoqasm` branch: ``` - -You can also install from source by cloning this repository and running a pip install command in the root directory of the repository: - -```bash git clone https://github.com/amazon-braket/amazon-braket-sdk-python.git cd amazon-braket-sdk-python -pip install . +git checkout feature/autoqasm +pip install -e . ``` -### Check the version you have installed -You can view the version of the amazon-braket-sdk you have installed by using the following command: -```bash -pip show amazon-braket-sdk -``` +## Quick start -You can also check your version of `amazon-braket-sdk` from within Python: +In this section, we will show how to get started with AutoQASM. AutoQASM allows you to build +quantum programs with a simplified syntax and run the programs on the service. It uses the circuit +model programming paradigm that is also used in the Amazon Braket SDK. +First, import the following modules and functions: ``` ->>> import braket._sdk as braket_sdk ->>> braket_sdk.__version__ +import autoqasm as aq +from autoqasm.instructions import h, cnot, measure ``` -### Updating the Amazon Braket Python SDK -You can update the version of the amazon-braket-sdk you have installed by using the following command: -```bash -pip install amazon-braket-sdk --upgrade --upgrade-strategy eager -``` - -## Usage - -### Running a circuit on an AWS simulator - -```python -import boto3 -from braket.aws import AwsDevice -from braket.circuits import Circuit - -device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1") +To create a quantum program using the AutoQASM experience, you decorate a function with `@aq.main`. +This allows AutoQASM to hook into the program definition and generate an output format that is accepted +by quantum devices. -bell = Circuit().h(0).cnot(0, 1) -task = device.run(bell, shots=100) -print(task.result().measurement_counts) +For instance, we can create a Bell state like so: ``` - -The code sample imports the Amazon Braket framework, then defines the device to use (the SV1 AWS simulator). It then creates a Bell Pair circuit, executes the circuit on the simulator and prints the results of the hybrid job. This example can be found in `../examples/bell.py`. - -### Running multiple quantum tasks at once - -Many quantum algorithms need to run multiple independent circuits, and submitting the circuits in parallel can be faster than submitting them one at a time. In particular, parallel quantum task processing provides a significant speed up when using simulator devices. The following example shows how to run a batch of quantum tasks on SV1: - -```python -circuits = [bell for _ in range(5)] -batch = device.run_batch(circuits, shots=100) -# The result of the first quantum task in the batch -print(batch.results()[0].measurement_counts) +# A program that generates a maximally entangled state +@aq.main +def bell_state() -> None: + h(0) + cnot(0, 1) ``` -### Running a hybrid job - -```python -from braket.aws import AwsQuantumJob +You can view the output format, which is OpenQASM, by running `bell_state.display()`. -job = AwsQuantumJob.create( - device="arn:aws:braket:::device/quantum-simulator/amazon/sv1", - source_module="job.py", - entry_point="job:run_job", - wait_until_complete=True, -) -print(job.result()) +AutoQASM enables users to use more complicated program constructs with a compact and readable +structure. We can demonstrate this with a program that conditionally prepares multiple Bell states +on qubit pairs (1, 2) and (3, 4). ``` -where `run_job` is a function in the file `job.py`. - - -The code sample imports the Amazon Braket framework, then creates a hybrid job with the entry point being the `run_job` function. The hybrid job creates quantum tasks against the SV1 AWS Simulator. The hybrid job runs synchronously, and prints logs until it completes. The complete example can be found in `../examples/job.py`. - -### Available Simulators -Amazon Braket provides access to two types of simulators: fully managed simulators, available through the Amazon Braket service, and the local simulators that are part of the Amazon Braket SDK. - -- Fully managed simulators offer high-performance circuit simulations. These simulators can handle circuits larger than circuits that run on quantum hardware. For example, the SV1 state vector simulator shown in the previous examples requires approximately 1 or 2 hours to complete a 34-qubit, dense, and square circuit (circuit depth = 34), depending on the type of gates used and other factors. -- The Amazon Braket Python SDK includes an implementation of quantum simulators that can run circuits on your local, classic hardware. For example the braket_sv local simulator is well suited for rapid prototyping on small circuits up to 25 qubits, depending on the hardware specifications of your Braket notebook instance or your local environment. An example of how to execute the quantum task locally is included in the repository `../examples/local_bell.py`. - -For a list of available simulators and their features, consult the [Amazon Braket Developer Guide](https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html). +@aq.main(num_qubits=5) +def conditional_multi_bell_states() -> None: + h(0) + if measure(0): + for i in aq.range(2): + qubit = 2 * i + 1 + h(qubit) + cnot(qubit, qubit+1) -### Debugging logs - -Quantum tasks sent to QPUs don't always run right away. To view quantum task status, you can enable debugging logs. An example of how to enable these logs is included in repo: `../examples/debug_bell.py`. This example enables quantum task logging so that status updates are continuously printed to the terminal after a quantum task is executed. The logs can also be configured to save to a file or output to another stream. You can use the debugging example to get information on the quantum tasks you submit, such as the current status, so that you know when your quantum task completes. - -### Running a Quantum Algorithm on a Quantum Computer -With Amazon Braket, you can run your quantum circuit on a physical quantum computer. - -The following example executes the same Bell Pair example described to validate your configuration on a Rigetti quantum computer. + measure([0,1,2,3,4]) +``` -```python -import boto3 -from braket.circuits import Circuit -from braket.aws import AwsDevice +AutoQASM can support subroutines and complex control flow. You can use the Python runtime +and quantum runtime side-by-side. There are rough edges at the moment, but we're actively smoothing +them out! -device = AwsDevice("arn:aws:braket:::device/qpu/rigetti/Aspen-8") +The Amazon Braket local simulator supports AutoQASM programs as input. +Let's simulate the `conditional_multi_bell_states` program: -bell = Circuit().h(0).cnot(0, 1) -task = device.run(bell) -print(task.result().measurement_counts) ``` +from braket.devices.local_simulator import LocalSimulator -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 -print(task.result().measurement_counts) +device = LocalSimulator() +task = device.run(conditional_multi_bell_states, shots=100) +result = task.result() ``` -To select a quantum hardware device, specify its ARN as the value of the `device_arn` argument. A list of available quantum devices and their features can be found in the [Amazon Braket Developer Guide](https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html). - -**Important** Quantum tasks may not run immediately on the QPU. The QPUs only execute quantum tasks during execution windows. To find their execution windows, please refer to the [AWS console](https://console.aws.amazon.com/braket/home) in the "Devices" tab. - -## Sample Notebooks -Sample Jupyter notebooks can be found in the [amazon-braket-examples](https://github.com/amazon-braket/amazon-braket-examples/) repo. - -## Braket Python SDK API Reference Documentation - -The API reference, can be found on [Read the Docs](https://amazon-braket-sdk-python.readthedocs.io/en/latest/). - -**To generate the API Reference HTML in your local environment** +Read more about AutoQASM decorators like `@aq.main` [here](doc/decorators.md). -To generate the HTML, first change directories (`cd`) to position the cursor in the `amazon-braket-sdk-python` directory. Then, run the following command to generate the HTML documentation files: +For more example usage of AutoQASM, visit the [example notebooks](../../../../examples/autoqasm). -```bash -pip install tox -tox -e docs -``` +## Architecture -To view the generated documentation, open the following file in a browser: -`../amazon-braket-sdk-python/build/documentation/html/index.html` +AutoQASM is built on top of the `autograph` component of TensorFlow. A quantum program is +written as a Python function which is decorated with `@aq.main`. When calling this +decorated function, the user’s Python function is converted into a transformed Python function +by `autograph`. This transformed function is then executed to produce an AutoQASM `Program` +object which can be simulated and/or serialized to OpenQASM. -## Testing +The conversion process allows AutoQASM to provide custom handling for native Python control +flow keywords such as `if`, `for`, and `while` and to preserve this control flow in the resulting +quantum program in order to realize functionality such as classical feedback on mid-circuit +measurement, efficient representation of loops, and modularity of subroutine definitions. -This repository has both unit and integration tests. +## Plans -To run the tests, make sure to install test dependencies first: +The AutoQASM project is undergoing rapid development. +The current status and future plans are tracked in +the [AutoQASM GitHub project](https://github.com/orgs/amazon-braket/projects/2/). -```bash -pip install -e "amazon-braket-sdk-python[test]" -``` +## Contributing and sharing feedback -### Unit Tests +We welcome feature requests, bug reports, or +general feedback, which you can share with us by +[opening up an issue](https://github.com/amazon-braket/amazon-braket-sdk-python/issues/new/choose). We also +welcome pull requests, examples, and documentation -- please open an issue describing your work +when you get started, or comment on an existing issue with your intentions. Pull requests should be +targeted to the `feature/autoqasm` branch of the https://github.com/amazon-braket/amazon-braket-sdk-python +repository. For more details on contributing to the Amazon Braket SDK, please read the +[contributing guidelines](../../../../CONTRIBUTING.md). -To run the unit tests: +For questions, you can get help via the Quantum Technologies section of +[AWS RePost](https://repost.aws/topics/TAxin6L9GYR5a3NElq8AHIqQ/quantum-technologies). +Please tag your question with "Amazon Braket" and mention AutoQASM in the question title. -```bash -tox -e unit-tests -``` +## Tests -You can also pass in various pytest arguments to run selected tests: - -```bash -tox -e unit-tests -- your-arguments +To run only AutoQASM tests (and skip the rest of the Amazon Braket SDK unit tests), run: ``` - -For more information, please see [pytest usage](https://docs.pytest.org/en/stable/usage.html). - -To run linters and doc generators and unit tests: - -```bash -tox +tox -e unit-tests -- test/unit_test/braket/experimental/autoqasm ``` -### Integration Tests +Note that you may first need to run `pip install -e .[test]`. More information on running tests +can be found in the [top-level README](../../../../README.md). -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). +## Frequently asked questions -After you create a profile, use the following command to set the `AWS_PROFILE` so that all future commands can access your AWS account and resources. +### 1. Will AutoQASM be extended to contain a library of quantum algorithms or quantum applications? -```bash -export AWS_PROFILE=YOUR_PROFILE_NAME -``` -To run the integration tests for local hybrid jobs, you need to have Docker installed and running. To install Docker follow these instructions: [Install Docker](https://docs.docker.com/get-docker/) +No, we are focused on AutoQASM as an interface for low-level expression of +quantum programs: circuits, gates and pulses. Higher-level algorithm +libraries could be implemented using AutoQASM and benefit from modular +AutoQASM functionality such as subroutines. -Run the tests: +### 2. What is the relationship between AutoQASM and OpenQASM? -```bash -tox -e integ-tests -``` +AutoQASM is a quantum programming interface built in Python. +OpenQASM is a quantum assembly language, often used as a serialization format +for quantum programming frameworks and quantum hardware providers. We can +represent a quantum program equivalently in either format, but using AutoQASM +allows one to also make use of Python, including the Amazon Braket SDK. -As with unit tests, you can also pass in various pytest arguments: +AutoQASM can be seen as implementing a builder pattern for OpenQASM. It +allows you serialize your program to OpenQASM by calling `to_ir()` on the +built program. The interface is not strongly tied to OpenQASM, so we could +serialize to other formats in the future. -```bash -tox -e integ-tests -- your-arguments -``` +### 3. What is the relationship between AutoQASM and the Amazon Braket SDK? -## Support +AutoQASM lives alongside the Amazon Braket SDK as an experimental feature +branch. It supplements the program building experience and integrates with +Amazon Braket SDK features. For instance, one can build a program through +AutoQASM, and then use the SDK to run the program on a local simulator or on +an Amazon Braket device. -### Issues and Bug Reports +### 4. Does AutoQASM support other providers beyond Amazon Braket? -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). +Yes. AutoQASM serializes to OpenQASM, and so it is applicable to any library +or QPU that supports OpenQASM. We do have features that use the Amazon Braket +SDK, such as [device-specific validation](../../../../examples/autoqasm/4_Native_programming.ipynb). +Because AutoQASM is open-source, anyone could +build similar integrations for another service. Reach out if you're +interested in doing this and would like support. -### 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. +### 5. Does AutoQASM offer special support for device-specific programming? -### Code contributors +Yes, AutoQASM has device-specific validation to support native programming. +We plan to expand this functionality in the future. Learn more with our +[native programming example notebook](../../../../examples/autoqasm/4_Native_programming.ipynb). -[![Contributors](https://contrib.rocks/image?repo=amazon-braket/amazon-braket-sdk-python)](https://github.com/amazon-braket/amazon-braket-sdk-python/graphs/contributors) +### 6. Do the devices available through Amazon Braket support all of AutoQASM's features? -## License -This project is licensed under the Apache-2.0 License. +No, for example, the `reset` instruction is not supported by all devices. In +general, different QPUs and QHPs support different sets of features, so +AutoQASM will often support features that a particular device doesn't +support. We intend that AutoQASM will eventually be able to generate any +program representable by OpenQASM 3.0, with additional Python-side features +such as validation and visualization. diff --git a/setup.py b/setup.py index 11bc60ee..2203570b 100644 --- a/setup.py +++ b/setup.py @@ -13,39 +13,33 @@ from setuptools import find_namespace_packages, setup -with open("README.md") as fh: +with open("README.md", "r") as fh: long_description = fh.read() -with open("src/braket/_sdk/_version.py") as f: +with open("src/autoqasm/_sdk/_version.py") as f: version = f.readlines()[-1].split()[-1].strip("\"'") setup( - name="amazon-braket-sdk", + name="autoqasm", version=version, license="Apache License 2.0", python_requires=">= 3.9", packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "amazon-braket-schemas>=1.21.3", - # Pin the latest commit of mcm-sim branch of the amazon-braket-default-simulator repo + # Pin the latest commit of feature/autoqasm branch of amazon-braket/amazon-braket-sdk-python.git # noqa E501 + "amazon-braket-sdk @ git+https://github.com/amazon-braket/amazon-braket-sdk-python.git@d62e288961af1b9a42b14667e1167fca301e2708#egg=amazon-braket-sdk", # noqa E501 + # Pin the latest commit of mcm-sim branch of amazon-braket/amazon-braket-default-simulator-python.git # noqa E501 # to get the version of the simulator that supports the mcm=True argument for Monte Carlo # simulation of mid-circuit measurement, which AutoQASM requires. - # NOTE: This change should remain in the feature/autoqasm branch; do not merge to main. - "amazon-braket-default-simulator @ git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git@ab068c860963c29842d7649c741f88da669597eb#egg=amazon-braket-default-simulator", # noqa E501 + # NOTE: This is currently installed automatically due to feature/autoqasm requirements + # "amazon-braket-default-simulator @ git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git@f17d3070a4f87a3bbef677e385a2e94dd386af78#egg=amazon-braket-default-simulator", # noqa E501 "oqpy~=0.3.5", - "backoff", - "boltons", - "boto3>=1.28.53", - "cloudpickle==2.2.1", "diastatic-malt", - "nest-asyncio", - "networkx", - "numpy<2", + "numpy", "openpulse", "openqasm3", "sympy", - "backports.entry-points-selectable", "astunparse", "gast", "termcolor", @@ -71,14 +65,12 @@ ], }, include_package_data=True, - url="https://github.com/amazon-braket/amazon-braket-sdk-python", + url="https://github.com/amazon-braket/autoqasm", author="Amazon Web Services", - description=( - "An open source library for interacting with quantum computing devices on Amazon Braket" - ), + description=("Python-native programming library for developing quantum programs"), long_description=long_description, long_description_content_type="text/markdown", - keywords="Amazon AWS Quantum", + keywords="Amazon AWS Braket Quantum", classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", diff --git a/tox.ini b/tox.ini index b78d47de..cee9487b 100644 --- a/tox.ini +++ b/tox.ini @@ -19,25 +19,10 @@ basepython = python3 deps = {[test-deps]deps} commands = - pytest {posargs} --cov=braket --cov-report term-missing --cov-report html --cov-report xml --cov-append -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} -passenv = - AWS_PROFILE - BRAKET_ENDPOINT -commands = - pytest test/integ_tests {posargs} + pytest {posargs} --cov=autoqasm --cov-report term-missing --cov-report html --cov-report xml --cov-append extras = test [testenv:notebooks] -# NOTE: Do not merge this part to main. The AutoQASM example notebooks should be moved -# to the example notebooks repo rather than living in the Braket SDK repo. usedevelop=True basepython = python3 deps = @@ -45,13 +30,13 @@ deps = notebook matplotlib commands = - jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb - jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/2_Expressing_classical_control_flow.ipynb - jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/3_1_Iterative_phase_estimation.ipynb - jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/3_2_magic_state_distillation.ipynb - jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/4_Native_programming.ipynb - jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/5_Pulse_programming_and_dynamical_decoupling.ipynb - jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/6_Customize_gate_calibrations.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/1_Getting_started_with_AutoQASM.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/2_Expressing_classical_control_flow.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/3_1_Iterative_phase_estimation.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/3_2_magic_state_distillation.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/4_Native_programming.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/5_Pulse_programming_and_dynamical_decoupling.ipynb + jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/6_Customize_gate_calibrations.ipynb extras = test [testenv:linters] @@ -149,4 +134,6 @@ commands = 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@ab068c860963c29842d7649c741f88da669597eb # mcm-sim branch + git+https://github.com/amazon-braket/amazon-braket-sdk-python.git@d62e288961af1b9a42b14667e1167fca301e2708 # feature/autoqasm branch + # NOTE: This is currently installed automatically due to feature/autoqasm requirements + #git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git@f17d3070a4f87a3bbef677e385a2e94dd386af78 # mcm-sim branch From 1c75aadd235028f93b90fee697d7b5ceaecc51fe Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 7 May 2024 12:03:38 -0400 Subject: [PATCH 1153/1165] Replace autoqasm import paths --- examples/1_Getting_started_with_AutoQASM.ipynb | 4 ++-- examples/2_Expressing_classical_control_flow.ipynb | 4 ++-- examples/3_1_Iterative_phase_estimation.ipynb | 4 ++-- examples/3_2_magic_state_distillation.ipynb | 4 ++-- examples/4_Native_programming.ipynb | 12 ++++++------ ...ulse_programming_and_dynamical_decoupling.ipynb | 6 +++--- examples/6_Customize_gate_calibrations.ipynb | 8 ++++---- examples/ionq_gates.py | 4 ++-- src/autoqasm/README.md | 4 ++-- src/autoqasm/__init__.py | 4 ++-- src/autoqasm/api.py | 14 +++++++------- src/autoqasm/converters/assignments.py | 2 +- src/autoqasm/converters/break_statements.py | 2 +- src/autoqasm/converters/return_statements.py | 4 ++-- src/autoqasm/instructions/gates.py | 4 ++-- src/autoqasm/instructions/instructions.py | 8 ++++---- src/autoqasm/instructions/measurements.py | 6 +++--- src/autoqasm/instructions/qubits.py | 2 +- src/autoqasm/operators/assignments.py | 4 ++-- src/autoqasm/operators/comparisons.py | 4 ++-- src/autoqasm/operators/conditional_expressions.py | 6 +++--- src/autoqasm/operators/control_flow.py | 4 ++-- src/autoqasm/operators/exceptions.py | 2 +- src/autoqasm/operators/logical.py | 4 ++-- src/autoqasm/operators/return_statements.py | 4 ++-- src/autoqasm/operators/slices.py | 4 ++-- src/autoqasm/operators/utils.py | 4 ++-- src/autoqasm/program/gate_calibrations.py | 4 ++-- src/autoqasm/program/pragmas.py | 2 +- src/autoqasm/program/program.py | 10 +++++----- src/autoqasm/pulse/pulse.py | 6 +++--- src/autoqasm/transpiler/transpiler.py | 4 ++-- src/autoqasm/types/conversions.py | 4 ++-- src/autoqasm/types/types.py | 2 +- test/unit_tests/autoqasm/conftest.py | 4 ++-- test/unit_tests/autoqasm/mock_transpiler.py | 2 +- test/unit_tests/autoqasm/test_annotations.py | 4 ++-- test/unit_tests/autoqasm/test_api.py | 6 +++--- test/unit_tests/autoqasm/test_converters.py | 4 ++-- test/unit_tests/autoqasm/test_devices.py | 6 +++--- test/unit_tests/autoqasm/test_gate_calibrations.py | 6 +++--- test/unit_tests/autoqasm/test_gate_decorator.py | 6 +++--- test/unit_tests/autoqasm/test_gate_definitions.py | 4 ++-- test/unit_tests/autoqasm/test_operators.py | 8 ++++---- test/unit_tests/autoqasm/test_parameters.py | 6 +++--- test/unit_tests/autoqasm/test_pragmas.py | 6 +++--- test/unit_tests/autoqasm/test_program.py | 4 ++-- test/unit_tests/autoqasm/test_pulse.py | 6 +++--- test/unit_tests/autoqasm/test_return.py | 6 +++--- .../autoqasm/test_serialization_config.py | 6 +++--- test/unit_tests/autoqasm/test_transpiler.py | 6 +++--- test/unit_tests/autoqasm/test_types.py | 4 ++-- 52 files changed, 129 insertions(+), 129 deletions(-) diff --git a/examples/1_Getting_started_with_AutoQASM.ipynb b/examples/1_Getting_started_with_AutoQASM.ipynb index 7f3bc068..aa947001 100644 --- a/examples/1_Getting_started_with_AutoQASM.ipynb +++ b/examples/1_Getting_started_with_AutoQASM.ipynb @@ -22,8 +22,8 @@ "\n", "# AWS imports: Import Braket SDK modules\n", "from braket.devices.local_simulator import LocalSimulator\n", - "import braket.experimental.autoqasm as aq\n", - "from braket.experimental.autoqasm.instructions import measure, h, cnot" + "import autoqasm as aq\n", + "from autoqasm.instructions import measure, h, cnot" ] }, { diff --git a/examples/2_Expressing_classical_control_flow.ipynb b/examples/2_Expressing_classical_control_flow.ipynb index 590bc8d9..4a5311b8 100644 --- a/examples/2_Expressing_classical_control_flow.ipynb +++ b/examples/2_Expressing_classical_control_flow.ipynb @@ -23,8 +23,8 @@ "\n", "# AWS imports: Import Braket SDK modules\n", "from braket.devices.local_simulator import LocalSimulator\n", - "import braket.experimental.autoqasm as aq\n", - "from braket.experimental.autoqasm.instructions import measure, h, cnot" + "import autoqasm as aq\n", + "from autoqasm.instructions import measure, h, cnot" ] }, { diff --git a/examples/3_1_Iterative_phase_estimation.ipynb b/examples/3_1_Iterative_phase_estimation.ipynb index 4d3e9840..e0baa66c 100644 --- a/examples/3_1_Iterative_phase_estimation.ipynb +++ b/examples/3_1_Iterative_phase_estimation.ipynb @@ -25,8 +25,8 @@ "\n", "# AWS imports: Import Braket SDK modules\n", "from braket.devices.local_simulator import LocalSimulator\n", - "import braket.experimental.autoqasm as aq\n", - "from braket.experimental.autoqasm.instructions import measure, x, rz, h, cphaseshift, reset" + "import autoqasm as aq\n", + "from autoqasm.instructions import measure, x, rz, h, cphaseshift, reset" ] }, { diff --git a/examples/3_2_magic_state_distillation.ipynb b/examples/3_2_magic_state_distillation.ipynb index 139b1616..59ffd9b0 100644 --- a/examples/3_2_magic_state_distillation.ipynb +++ b/examples/3_2_magic_state_distillation.ipynb @@ -38,8 +38,8 @@ "\n", "# AWS imports: Import Braket SDK modules\n", "from braket.devices.local_simulator import LocalSimulator\n", - "import braket.experimental.autoqasm as aq\n", - "import braket.experimental.autoqasm.instructions as ins" + "import autoqasm as aq\n", + "import autoqasm.instructions as ins" ] }, { diff --git a/examples/4_Native_programming.ipynb b/examples/4_Native_programming.ipynb index 68b9ed64..f00e5f52 100644 --- a/examples/4_Native_programming.ipynb +++ b/examples/4_Native_programming.ipynb @@ -24,7 +24,7 @@ "\n", "# AWS imports: Import Braket SDK modules\n", "from braket.devices import Devices\n", - "import braket.experimental.autoqasm as aq" + "import autoqasm as aq" ] }, { @@ -58,7 +58,7 @@ } ], "source": [ - "from braket.experimental.autoqasm.instructions import h, cnot, measure\n", + "from autoqasm.instructions import h, cnot, measure\n", "\n", "\n", "@aq.main\n", @@ -256,8 +256,8 @@ ".output_html .vm { color: #19177C } /* Name.Variable.Magic */\n", ".output_html .il { color: #666666 } /* Literal.Number.Integer.Long */
import numpy as np\n",
        "\n",
-       "import braket.experimental.autoqasm as aq\n",
-       "from braket.experimental.autoqasm.instructions import gpi, gpi2, ms\n",
+       "import autoqasm as aq\n",
+       "from autoqasm.instructions import gpi, gpi2, ms\n",
        "\n",
        "\n",
        "@aq.gate\n",
@@ -335,8 +335,8 @@
       "text/plain": [
        "import numpy as np\n",
        "\n",
-       "import braket.experimental.autoqasm as aq\n",
-       "from braket.experimental.autoqasm.instructions import gpi, gpi2, ms\n",
+       "import autoqasm as aq\n",
+       "from autoqasm.instructions import gpi, gpi2, ms\n",
        "\n",
        "\n",
        "@aq.gate\n",
diff --git a/examples/5_Pulse_programming_and_dynamical_decoupling.ipynb b/examples/5_Pulse_programming_and_dynamical_decoupling.ipynb
index cb250326..01a720ec 100644
--- a/examples/5_Pulse_programming_and_dynamical_decoupling.ipynb
+++ b/examples/5_Pulse_programming_and_dynamical_decoupling.ipynb
@@ -35,9 +35,9 @@
    "source": [
     "import numpy as np\n",
     "\n",
-    "import braket.experimental.autoqasm as aq\n",
-    "from braket.experimental.autoqasm import pulse\n",
-    "from braket.experimental.autoqasm.instructions import rx, rz\n",
+    "import autoqasm as aq\n",
+    "from autoqasm import pulse\n",
+    "from autoqasm.instructions import rx, rz\n",
     "\n",
     "from braket.aws import AwsDevice\n",
     "from braket.devices import Devices\n",
diff --git a/examples/6_Customize_gate_calibrations.ipynb b/examples/6_Customize_gate_calibrations.ipynb
index 76b379f8..6e4edc41 100644
--- a/examples/6_Customize_gate_calibrations.ipynb
+++ b/examples/6_Customize_gate_calibrations.ipynb
@@ -21,9 +21,9 @@
    "source": [
     "import numpy as np\n",
     "\n",
-    "import braket.experimental.autoqasm as aq\n",
-    "from braket.experimental.autoqasm import pulse\n",
-    "from braket.experimental.autoqasm.instructions import rx, rz, measure\n",
+    "import autoqasm as aq\n",
+    "from autoqasm import pulse\n",
+    "from autoqasm.instructions import rx, rz, measure\n",
     "\n",
     "from braket.aws import AwsDevice\n",
     "from braket.devices import Devices\n",
@@ -156,7 +156,7 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "Help on function rx in module braket.experimental.autoqasm.instructions.gates:\n",
+      "Help on function rx in module autoqasm.instructions.gates:\n",
       "\n",
       "rx(target: Union[int, str, braket.registers.qubit.Qubit, oqpy.classical_types._ClassicalVar, oqpy.base.OQPyExpression, oqpy.quantum_types.Qubit], angle: Union[float, braket.parametric.free_parameter_expression.FreeParameterExpression, oqpy.classical_types._ClassicalVar]) -> None\n",
       "    X-axis rotation gate.\n",
diff --git a/examples/ionq_gates.py b/examples/ionq_gates.py
index a8e69c6b..e1bf635c 100644
--- a/examples/ionq_gates.py
+++ b/examples/ionq_gates.py
@@ -1,7 +1,7 @@
 import numpy as np
 
-import braket.experimental.autoqasm as aq
-from braket.experimental.autoqasm.instructions import gpi, gpi2, ms
+import autoqasm as aq
+from autoqasm.instructions import gpi, gpi2, ms
 
 
 @aq.gate
diff --git a/src/autoqasm/README.md b/src/autoqasm/README.md
index 9e4aa8ad..3778c4e3 100644
--- a/src/autoqasm/README.md
+++ b/src/autoqasm/README.md
@@ -51,8 +51,8 @@ model programming paradigm that is also used in the Amazon Braket SDK.
 
 First, import the following modules and functions:
 ```
-import braket.experimental.autoqasm as aq
-from braket.experimental.autoqasm.instructions import h, cnot, measure
+import autoqasm as aq
+from autoqasm.instructions import h, cnot, measure
 ```
 
 To create a quantum program using the AutoQASM experience, you decorate a function with `@aq.main`.
diff --git a/src/autoqasm/__init__.py b/src/autoqasm/__init__.py
index e41ab18c..bc370aa1 100644
--- a/src/autoqasm/__init__.py
+++ b/src/autoqasm/__init__.py
@@ -16,8 +16,8 @@
 
 The basic usage of AutoQASM is as follows:
 
-    import braket.experimental.autoqasm as aq
-    from braket.experimental.autoqasm.instructions import h, cnot, measure
+    import autoqasm as aq
+    from autoqasm.instructions import h, cnot, measure
 
     @aq.main
     def my_program():
diff --git a/src/autoqasm/api.py b/src/autoqasm/api.py
index da39354c..d4a567ce 100644
--- a/src/autoqasm/api.py
+++ b/src/autoqasm/api.py
@@ -27,13 +27,13 @@
 from malt.core import converter
 from malt.impl.api import autograph_artifact, is_autograph_artifact
 
-import braket.experimental.autoqasm.instructions as aq_instructions
-import braket.experimental.autoqasm.program as aq_program
-import braket.experimental.autoqasm.transpiler as aq_transpiler
-import braket.experimental.autoqasm.types as aq_types
-from braket.experimental.autoqasm import errors
-from braket.experimental.autoqasm.program.gate_calibrations import GateCalibration
-from braket.experimental.autoqasm.types import QubitIdentifierType as Qubit
+import autoqasm.instructions as aq_instructions
+import autoqasm.program as aq_program
+import autoqasm.transpiler as aq_transpiler
+import autoqasm.types as aq_types
+from autoqasm import errors
+from autoqasm.program.gate_calibrations import GateCalibration
+from autoqasm.types import QubitIdentifierType as Qubit
 
 
 def main(
diff --git a/src/autoqasm/converters/assignments.py b/src/autoqasm/converters/assignments.py
index 34f03363..7b81718d 100644
--- a/src/autoqasm/converters/assignments.py
+++ b/src/autoqasm/converters/assignments.py
@@ -20,7 +20,7 @@
 from malt.core import ag_ctx, converter
 from malt.pyct import templates
 
-from braket.experimental.autoqasm.operators.assignments import assign_for_output
+from autoqasm.operators.assignments import assign_for_output
 
 
 class AssignTransformer(converter.Base):
diff --git a/src/autoqasm/converters/break_statements.py b/src/autoqasm/converters/break_statements.py
index 64695c2c..dd393577 100644
--- a/src/autoqasm/converters/break_statements.py
+++ b/src/autoqasm/converters/break_statements.py
@@ -19,7 +19,7 @@
 from malt.converters import break_statements
 from malt.core import ag_ctx, converter
 
-from braket.experimental.autoqasm import errors
+from autoqasm import errors
 
 
 class BreakValidator(converter.Base):
diff --git a/src/autoqasm/converters/return_statements.py b/src/autoqasm/converters/return_statements.py
index 0bc51652..dae2ba4c 100644
--- a/src/autoqasm/converters/return_statements.py
+++ b/src/autoqasm/converters/return_statements.py
@@ -21,8 +21,8 @@
 from malt.core import ag_ctx, converter
 from malt.pyct import templates
 
-from braket.experimental.autoqasm import constants, program
-from braket.experimental.autoqasm.operators.assignments import assign_for_output
+from autoqasm import constants, program
+from autoqasm.operators.assignments import assign_for_output
 
 
 class ReturnTransformer(converter.Base):
diff --git a/src/autoqasm/instructions/gates.py b/src/autoqasm/instructions/gates.py
index f7556c24..0b35223e 100644
--- a/src/autoqasm/instructions/gates.py
+++ b/src/autoqasm/instructions/gates.py
@@ -19,8 +19,8 @@
 import oqpy
 
 from braket.circuits.free_parameter_expression import FreeParameterExpression
-from braket.experimental.autoqasm.instructions.instructions import _qubit_instruction
-from braket.experimental.autoqasm.types import QubitIdentifierType
+from autoqasm.instructions.instructions import _qubit_instruction
+from autoqasm.types import QubitIdentifierType
 
 GateParameterType = Union[float, FreeParameterExpression, oqpy._ClassicalVar]
 
diff --git a/src/autoqasm/instructions/instructions.py b/src/autoqasm/instructions/instructions.py
index 818465e9..33de668f 100644
--- a/src/autoqasm/instructions/instructions.py
+++ b/src/autoqasm/instructions/instructions.py
@@ -22,10 +22,10 @@
 import oqpy
 
 from braket.circuits.basis_state import BasisState, BasisStateInput
-from braket.experimental.autoqasm import program as aq_program
-from braket.experimental.autoqasm import types as aq_types
-from braket.experimental.autoqasm.instructions.qubits import _qubit
-from braket.experimental.autoqasm.types import QubitIdentifierType
+from autoqasm import program as aq_program
+from autoqasm import types as aq_types
+from autoqasm.instructions.qubits import _qubit
+from autoqasm.types import QubitIdentifierType
 
 
 def _qubit_instruction(
diff --git a/src/autoqasm/instructions/measurements.py b/src/autoqasm/instructions/measurements.py
index 0bd33bce..e2ec8c4e 100644
--- a/src/autoqasm/instructions/measurements.py
+++ b/src/autoqasm/instructions/measurements.py
@@ -26,9 +26,9 @@ def my_program():
 
 from collections.abc import Iterable
 
-from braket.experimental.autoqasm import program
-from braket.experimental.autoqasm import types as aq_types
-from braket.experimental.autoqasm.instructions.qubits import (
+from autoqasm import program
+from autoqasm import types as aq_types
+from autoqasm.instructions.qubits import (
     GlobalQubitRegister,
     _qubit,
     global_qubit_register,
diff --git a/src/autoqasm/instructions/qubits.py b/src/autoqasm/instructions/qubits.py
index de0370c2..c6a63604 100644
--- a/src/autoqasm/instructions/qubits.py
+++ b/src/autoqasm/instructions/qubits.py
@@ -24,7 +24,7 @@
 import oqpy.base
 from openpulse.printer import dumps
 
-from braket.experimental.autoqasm import constants, errors, program
+from autoqasm import constants, errors, program
 
 
 def _get_physical_qubit_indices(qids: list[str]) -> list[int]:
diff --git a/src/autoqasm/operators/assignments.py b/src/autoqasm/operators/assignments.py
index 7217513a..bc13f3e3 100644
--- a/src/autoqasm/operators/assignments.py
+++ b/src/autoqasm/operators/assignments.py
@@ -22,8 +22,8 @@
 import oqpy.base
 from malt.operators.variables import UndefinedReturnValue
 
-from braket.experimental.autoqasm import constants, errors, program, types
-from braket.experimental.autoqasm.types.conversions import var_type_from_oqpy
+from autoqasm import constants, errors, program, types
+from autoqasm.types.conversions import var_type_from_oqpy
 
 
 def assign_for_output(target_name: str, value: Any) -> Any:
diff --git a/src/autoqasm/operators/comparisons.py b/src/autoqasm/operators/comparisons.py
index 66a76e71..1817177c 100644
--- a/src/autoqasm/operators/comparisons.py
+++ b/src/autoqasm/operators/comparisons.py
@@ -18,8 +18,8 @@
 
 from typing import Any
 
-from braket.experimental.autoqasm import program
-from braket.experimental.autoqasm import types as aq_types
+from autoqasm import program
+from autoqasm import types as aq_types
 
 from .utils import _register_and_convert_parameters
 
diff --git a/src/autoqasm/operators/conditional_expressions.py b/src/autoqasm/operators/conditional_expressions.py
index 75d19a8c..aec09df0 100644
--- a/src/autoqasm/operators/conditional_expressions.py
+++ b/src/autoqasm/operators/conditional_expressions.py
@@ -21,9 +21,9 @@
 
 import oqpy.base
 
-from braket.experimental.autoqasm import program as aq_program
-from braket.experimental.autoqasm import types as aq_types
-from braket.experimental.autoqasm.errors import UnsupportedConditionalExpressionError
+from autoqasm import program as aq_program
+from autoqasm import types as aq_types
+from autoqasm.errors import UnsupportedConditionalExpressionError
 
 
 def if_exp(
diff --git a/src/autoqasm/operators/control_flow.py b/src/autoqasm/operators/control_flow.py
index a4af683c..4c7ee15c 100644
--- a/src/autoqasm/operators/control_flow.py
+++ b/src/autoqasm/operators/control_flow.py
@@ -21,8 +21,8 @@
 
 import oqpy.base
 
-from braket.experimental.autoqasm import program
-from braket.experimental.autoqasm.types import Range, is_qasm_type
+from autoqasm import program
+from autoqasm.types import Range, is_qasm_type
 
 
 def for_stmt(
diff --git a/src/autoqasm/operators/exceptions.py b/src/autoqasm/operators/exceptions.py
index bfe7cefa..47dcd8c9 100644
--- a/src/autoqasm/operators/exceptions.py
+++ b/src/autoqasm/operators/exceptions.py
@@ -16,7 +16,7 @@
 
 from collections.abc import Callable
 
-from braket.experimental.autoqasm.types import is_qasm_type
+from autoqasm.types import is_qasm_type
 
 
 def assert_stmt(test: bool, message: Callable) -> None:
diff --git a/src/autoqasm/operators/logical.py b/src/autoqasm/operators/logical.py
index 35474019..8e66a9e0 100644
--- a/src/autoqasm/operators/logical.py
+++ b/src/autoqasm/operators/logical.py
@@ -21,8 +21,8 @@
 import oqpy.base
 from openpulse import ast
 
-from braket.experimental.autoqasm import program
-from braket.experimental.autoqasm import types as aq_types
+from autoqasm import program
+from autoqasm import types as aq_types
 
 from .utils import _register_and_convert_parameters
 
diff --git a/src/autoqasm/operators/return_statements.py b/src/autoqasm/operators/return_statements.py
index 7e33f091..215ff388 100644
--- a/src/autoqasm/operators/return_statements.py
+++ b/src/autoqasm/operators/return_statements.py
@@ -17,8 +17,8 @@
 from collections.abc import Iterable
 from typing import Any
 
-from braket.experimental.autoqasm import program
-from braket.experimental.autoqasm import types as aq_types
+from autoqasm import program
+from autoqasm import types as aq_types
 
 
 def return_output_from_main(name: str, value: Any) -> Any:
diff --git a/src/autoqasm/operators/slices.py b/src/autoqasm/operators/slices.py
index 726abea8..ca1f6f4d 100644
--- a/src/autoqasm/operators/slices.py
+++ b/src/autoqasm/operators/slices.py
@@ -19,8 +19,8 @@
 
 import oqpy.base
 
-from braket.experimental.autoqasm import program
-from braket.experimental.autoqasm.types import is_qasm_type, wrap_value
+from autoqasm import program
+from autoqasm.types import is_qasm_type, wrap_value
 
 
 class GetItemOpts(collections.namedtuple("GetItemOpts", ("element_dtype",))):
diff --git a/src/autoqasm/operators/utils.py b/src/autoqasm/operators/utils.py
index 9dabfeae..9d89b1c5 100644
--- a/src/autoqasm/operators/utils.py
+++ b/src/autoqasm/operators/utils.py
@@ -18,8 +18,8 @@
 
 from typing import Any
 
-from braket.experimental.autoqasm import program
-from braket.experimental.autoqasm import types as aq_types
+from autoqasm import program
+from autoqasm import types as aq_types
 
 
 def _register_and_convert_parameters(
diff --git a/src/autoqasm/program/gate_calibrations.py b/src/autoqasm/program/gate_calibrations.py
index cd3d2835..b34dbfa7 100644
--- a/src/autoqasm/program/gate_calibrations.py
+++ b/src/autoqasm/program/gate_calibrations.py
@@ -16,8 +16,8 @@
 
 from collections.abc import Callable, Iterable
 
-from braket.experimental.autoqasm.program import Program
-from braket.experimental.autoqasm.types import QubitIdentifierType as Qubit
+from autoqasm.program import Program
+from autoqasm.types import QubitIdentifierType as Qubit
 
 
 class GateCalibration:
diff --git a/src/autoqasm/program/pragmas.py b/src/autoqasm/program/pragmas.py
index 8ec36b40..28143533 100644
--- a/src/autoqasm/program/pragmas.py
+++ b/src/autoqasm/program/pragmas.py
@@ -35,7 +35,7 @@ def pragma_example() -> None:
 from typing import Iterable
 
 from braket.device_schema import DeviceActionType
-from braket.experimental.autoqasm import errors, program
+from autoqasm import errors, program
 
 
 class PragmaType(str, Enum):
diff --git a/src/autoqasm/program/program.py b/src/autoqasm/program/program.py
index fefcd229..537c4942 100644
--- a/src/autoqasm/program/program.py
+++ b/src/autoqasm/program/program.py
@@ -30,23 +30,23 @@
 from pygments.formatters.terminal import TerminalFormatter
 from sympy import Symbol
 
-import braket.experimental.autoqasm.types as aq_types
+import autoqasm.types as aq_types
 from braket.aws.aws_device import AwsDevice
 from braket.circuits.free_parameter_expression import FreeParameterExpression
 from braket.circuits.serialization import IRType, SerializableProgram
 from braket.device_schema import DeviceActionType
 from braket.devices.device import Device
-from braket.experimental.autoqasm import constants, errors
-from braket.experimental.autoqasm.instructions.qubits import (
+from autoqasm import constants, errors
+from autoqasm.instructions.qubits import (
     GlobalQubitRegister,
     _get_physical_qubit_indices,
     _qubit,
 )
-from braket.experimental.autoqasm.program.serialization_properties import (
+from autoqasm.program.serialization_properties import (
     OpenQASMSerializationProperties,
     SerializationProperties,
 )
-from braket.experimental.autoqasm.types import QubitIdentifierType as Qubit
+from autoqasm.types import QubitIdentifierType as Qubit
 from braket.pulse.ast.qasm_parser import ast_to_qasm
 
 # Create the thread-local object for the program conversion context.
diff --git a/src/autoqasm/pulse/pulse.py b/src/autoqasm/pulse/pulse.py
index 775debf4..1a43cf72 100644
--- a/src/autoqasm/pulse/pulse.py
+++ b/src/autoqasm/pulse/pulse.py
@@ -18,9 +18,9 @@
 
 import oqpy
 
-from braket.experimental.autoqasm import program as aq_program
-from braket.experimental.autoqasm.instructions.qubits import _get_physical_qubit_indices
-from braket.experimental.autoqasm.types import BitVar, QubitIdentifierType, is_qubit_identifier_type
+from autoqasm import program as aq_program
+from autoqasm.instructions.qubits import _get_physical_qubit_indices
+from autoqasm.types import BitVar, QubitIdentifierType, is_qubit_identifier_type
 from braket.parametric import FreeParameterExpression
 from braket.parametric.free_parameter import FreeParameter
 from braket.pulse import PulseSequence
diff --git a/src/autoqasm/transpiler/transpiler.py b/src/autoqasm/transpiler/transpiler.py
index 609e91b5..1275dbd4 100644
--- a/src/autoqasm/transpiler/transpiler.py
+++ b/src/autoqasm/transpiler/transpiler.py
@@ -47,8 +47,8 @@
 from malt.pyct.static_analysis import activity, reaching_definitions
 from malt.utils import ag_logging as logging
 
-from braket.experimental.autoqasm import operators, program, types
-from braket.experimental.autoqasm.converters import (
+from autoqasm import operators, program, types
+from autoqasm.converters import (
     assignments,
     break_statements,
     comparisons,
diff --git a/src/autoqasm/types/conversions.py b/src/autoqasm/types/conversions.py
index 6a7c9ca2..023d9ab7 100644
--- a/src/autoqasm/types/conversions.py
+++ b/src/autoqasm/types/conversions.py
@@ -23,8 +23,8 @@
 import oqpy
 from openpulse import ast
 
-from braket.experimental.autoqasm import errors
-from braket.experimental.autoqasm import types as aq_types
+from autoqasm import errors
+from autoqasm import types as aq_types
 
 
 def map_parameter_type(python_type: type) -> type:
diff --git a/src/autoqasm/types/types.py b/src/autoqasm/types/types.py
index 2412323b..b74fac61 100644
--- a/src/autoqasm/types/types.py
+++ b/src/autoqasm/types/types.py
@@ -23,7 +23,7 @@
 from openpulse import ast
 
 from braket.circuits import FreeParameterExpression
-from braket.experimental.autoqasm import errors, program
+from autoqasm import errors, program
 from braket.registers import Qubit
 
 
diff --git a/test/unit_tests/autoqasm/conftest.py b/test/unit_tests/autoqasm/conftest.py
index d4bb60c5..d336c101 100644
--- a/test/unit_tests/autoqasm/conftest.py
+++ b/test/unit_tests/autoqasm/conftest.py
@@ -17,8 +17,8 @@
 
 import pytest
 
-import braket.experimental.autoqasm as aq
-from braket.experimental.autoqasm.instructions import cnot, h
+import autoqasm as aq
+from autoqasm.instructions import cnot, h
 
 
 @pytest.fixture
diff --git a/test/unit_tests/autoqasm/mock_transpiler.py b/test/unit_tests/autoqasm/mock_transpiler.py
index e30895a6..3d81fd6a 100644
--- a/test/unit_tests/autoqasm/mock_transpiler.py
+++ b/test/unit_tests/autoqasm/mock_transpiler.py
@@ -18,7 +18,7 @@
 import gast
 from malt.core import ag_ctx
 
-from braket.experimental.autoqasm.transpiler import PyToOqpy
+from autoqasm.transpiler import PyToOqpy
 
 # TODO: Implement a converter abstract class for better type hinting.
 
diff --git a/test/unit_tests/autoqasm/test_annotations.py b/test/unit_tests/autoqasm/test_annotations.py
index da1a7464..55aaf47b 100644
--- a/test/unit_tests/autoqasm/test_annotations.py
+++ b/test/unit_tests/autoqasm/test_annotations.py
@@ -20,8 +20,8 @@
 
 import pytest
 
-import braket.experimental.autoqasm as aq
-from braket.experimental.autoqasm.instructions import cnot, h
+import autoqasm as aq
+from autoqasm.instructions import cnot, h
 
 
 @pytest.mark.parametrize(
diff --git a/test/unit_tests/autoqasm/test_api.py b/test/unit_tests/autoqasm/test_api.py
index 9a9a5b32..174bd03f 100644
--- a/test/unit_tests/autoqasm/test_api.py
+++ b/test/unit_tests/autoqasm/test_api.py
@@ -18,11 +18,11 @@
 
 import pytest
 
-import braket.experimental.autoqasm as aq
+import autoqasm as aq
 from braket.default_simulator import StateVectorSimulator
 from braket.devices.local_simulator import LocalSimulator
-from braket.experimental.autoqasm import errors
-from braket.experimental.autoqasm.instructions import cnot, h, measure, rx, x
+from autoqasm import errors
+from autoqasm.instructions import cnot, h, measure, rx, x
 from braket.tasks.local_quantum_task import LocalQuantumTask
 
 
diff --git a/test/unit_tests/autoqasm/test_converters.py b/test/unit_tests/autoqasm/test_converters.py
index 47dffdd5..c56fb8fc 100644
--- a/test/unit_tests/autoqasm/test_converters.py
+++ b/test/unit_tests/autoqasm/test_converters.py
@@ -17,8 +17,8 @@
 from malt.core import ag_ctx, converter
 from mock_transpiler import MockTranspiler
 
-import braket.experimental.autoqasm as aq
-from braket.experimental.autoqasm.converters import assignments
+import autoqasm as aq
+from autoqasm.converters import assignments
 
 
 @pytest.fixture(autouse=True)
diff --git a/test/unit_tests/autoqasm/test_devices.py b/test/unit_tests/autoqasm/test_devices.py
index 47c2b082..365629df 100644
--- a/test/unit_tests/autoqasm/test_devices.py
+++ b/test/unit_tests/autoqasm/test_devices.py
@@ -19,13 +19,13 @@
 
 import pytest
 
-import braket.experimental.autoqasm as aq
+import autoqasm as aq
 from braket.aws import AwsDevice
 from braket.device_schema import DeviceActionType
 from braket.device_schema.simulators import GateModelSimulatorDeviceCapabilities
 from braket.devices import Devices
-from braket.experimental.autoqasm import errors
-from braket.experimental.autoqasm.instructions import cnot, cphaseshift00, h, rx, x
+from autoqasm import errors
+from autoqasm.instructions import cnot, cphaseshift00, h, rx, x
 from braket.parametric import FreeParameter
 
 RIGETTI_REGION = "us-west-1"
diff --git a/test/unit_tests/autoqasm/test_gate_calibrations.py b/test/unit_tests/autoqasm/test_gate_calibrations.py
index a601910d..c75429ba 100644
--- a/test/unit_tests/autoqasm/test_gate_calibrations.py
+++ b/test/unit_tests/autoqasm/test_gate_calibrations.py
@@ -17,9 +17,9 @@
 
 import pytest
 
-import braket.experimental.autoqasm as aq
-from braket.experimental.autoqasm import errors, pulse
-from braket.experimental.autoqasm.instructions import h, rx
+import autoqasm as aq
+from autoqasm import errors, pulse
+from autoqasm.instructions import h, rx
 
 
 def test_gate_calibrations_fixed_args():
diff --git a/test/unit_tests/autoqasm/test_gate_decorator.py b/test/unit_tests/autoqasm/test_gate_decorator.py
index 2491d543..b6b934f9 100644
--- a/test/unit_tests/autoqasm/test_gate_decorator.py
+++ b/test/unit_tests/autoqasm/test_gate_decorator.py
@@ -18,9 +18,9 @@
 import pytest
 from test_api import _test_on_local_sim
 
-import braket.experimental.autoqasm as aq
-from braket.experimental.autoqasm import errors
-from braket.experimental.autoqasm.instructions import h, measure, reset, rx, rz, x
+import autoqasm as aq
+from autoqasm import errors
+from autoqasm.instructions import h, measure, reset, rx, rz, x
 
 
 @aq.gate
diff --git a/test/unit_tests/autoqasm/test_gate_definitions.py b/test/unit_tests/autoqasm/test_gate_definitions.py
index 008d14b8..34c5cf2e 100644
--- a/test/unit_tests/autoqasm/test_gate_definitions.py
+++ b/test/unit_tests/autoqasm/test_gate_definitions.py
@@ -15,8 +15,8 @@
 
 import pytest
 
-import braket.experimental.autoqasm as aq
-from braket.experimental.autoqasm.instructions import (
+import autoqasm as aq
+from autoqasm.instructions import (
     ccnot,
     cnot,
     cphaseshift,
diff --git a/test/unit_tests/autoqasm/test_operators.py b/test/unit_tests/autoqasm/test_operators.py
index 4d224428..ce3ffd9a 100644
--- a/test/unit_tests/autoqasm/test_operators.py
+++ b/test/unit_tests/autoqasm/test_operators.py
@@ -20,10 +20,10 @@
 import oqpy.base
 import pytest
 
-import braket.experimental.autoqasm as aq
-from braket.experimental.autoqasm import errors
-from braket.experimental.autoqasm.errors import UnsupportedConditionalExpressionError
-from braket.experimental.autoqasm.instructions import cnot, h, measure, rx, x
+import autoqasm as aq
+from autoqasm import errors
+from autoqasm.errors import UnsupportedConditionalExpressionError
+from autoqasm.instructions import cnot, h, measure, rx, x
 
 
 @pytest.fixture
diff --git a/test/unit_tests/autoqasm/test_parameters.py b/test/unit_tests/autoqasm/test_parameters.py
index d024eaa6..54b23c1e 100644
--- a/test/unit_tests/autoqasm/test_parameters.py
+++ b/test/unit_tests/autoqasm/test_parameters.py
@@ -16,12 +16,12 @@
 import numpy as np
 import pytest
 
-import braket.experimental.autoqasm as aq
+import autoqasm as aq
 from braket.circuits import FreeParameter
 from braket.default_simulator import StateVectorSimulator
 from braket.devices.local_simulator import LocalSimulator
-from braket.experimental.autoqasm import pulse
-from braket.experimental.autoqasm.instructions import (
+from autoqasm import pulse
+from autoqasm.instructions import (
     cnot,
     cphaseshift,
     gpi,
diff --git a/test/unit_tests/autoqasm/test_pragmas.py b/test/unit_tests/autoqasm/test_pragmas.py
index 44832c56..9b91d75e 100644
--- a/test/unit_tests/autoqasm/test_pragmas.py
+++ b/test/unit_tests/autoqasm/test_pragmas.py
@@ -15,9 +15,9 @@
 
 import pytest
 
-import braket.experimental.autoqasm as aq
-from braket.experimental.autoqasm import errors
-from braket.experimental.autoqasm.instructions import cnot, h
+import autoqasm as aq
+from autoqasm import errors
+from autoqasm.instructions import cnot, h
 
 
 def test_basic_box() -> None:
diff --git a/test/unit_tests/autoqasm/test_program.py b/test/unit_tests/autoqasm/test_program.py
index fa039025..bfac7c32 100644
--- a/test/unit_tests/autoqasm/test_program.py
+++ b/test/unit_tests/autoqasm/test_program.py
@@ -20,9 +20,9 @@
 import oqpy.base
 import pytest
 
-import braket.experimental.autoqasm as aq
+import autoqasm as aq
 from braket.circuits.serialization import IRType
-from braket.experimental.autoqasm.instructions import cnot, measure, rx
+from autoqasm.instructions import cnot, measure, rx
 
 
 def test_program_conversion_context() -> None:
diff --git a/test/unit_tests/autoqasm/test_pulse.py b/test/unit_tests/autoqasm/test_pulse.py
index 2c4db739..05549d07 100644
--- a/test/unit_tests/autoqasm/test_pulse.py
+++ b/test/unit_tests/autoqasm/test_pulse.py
@@ -17,9 +17,9 @@
 
 import pytest
 
-import braket.experimental.autoqasm as aq
-from braket.experimental.autoqasm.instructions import rx
-from braket.experimental.autoqasm.pulse import (
+import autoqasm as aq
+from autoqasm.instructions import rx
+from autoqasm.pulse import (
     barrier,
     capture_v0,
     delay,
diff --git a/test/unit_tests/autoqasm/test_return.py b/test/unit_tests/autoqasm/test_return.py
index e2167e1a..f0f7b051 100644
--- a/test/unit_tests/autoqasm/test_return.py
+++ b/test/unit_tests/autoqasm/test_return.py
@@ -15,9 +15,9 @@
 
 import pytest
 
-import braket.experimental.autoqasm as aq
-from braket.experimental.autoqasm.instructions import measure
-from braket.experimental.autoqasm.pulse import capture_v0
+import autoqasm as aq
+from autoqasm.instructions import measure
+from autoqasm.pulse import capture_v0
 from braket.pulse import Frame, Port
 
 
diff --git a/test/unit_tests/autoqasm/test_serialization_config.py b/test/unit_tests/autoqasm/test_serialization_config.py
index 3a71515e..428cde1f 100644
--- a/test/unit_tests/autoqasm/test_serialization_config.py
+++ b/test/unit_tests/autoqasm/test_serialization_config.py
@@ -15,9 +15,9 @@
 
 import textwrap
 
-import braket.experimental.autoqasm as aq
-from braket.experimental.autoqasm.program import OpenQASMSerializationProperties
-from braket.experimental.autoqasm.pulse import barrier, play
+import autoqasm as aq
+from autoqasm.program import OpenQASMSerializationProperties
+from autoqasm.pulse import barrier, play
 from braket.pulse import Frame, GaussianWaveform, Port
 
 PORT = Port(port_id="device_port_x0", dt=1e-9, properties={})
diff --git a/test/unit_tests/autoqasm/test_transpiler.py b/test/unit_tests/autoqasm/test_transpiler.py
index 8d59bd2f..08167542 100644
--- a/test/unit_tests/autoqasm/test_transpiler.py
+++ b/test/unit_tests/autoqasm/test_transpiler.py
@@ -19,9 +19,9 @@
 from malt.core.ag_ctx import ControlStatusCtx, Status
 from malt.utils import ag_logging
 
-import braket.experimental.autoqasm as aq
-from braket.experimental.autoqasm.errors import UnknownQubitCountError
-from braket.experimental.autoqasm.instructions import cnot, h, measure, x
+import autoqasm as aq
+from autoqasm.errors import UnknownQubitCountError
+from autoqasm.instructions import cnot, h, measure, x
 
 
 def test_convert_invalid_main_object() -> None:
diff --git a/test/unit_tests/autoqasm/test_types.py b/test/unit_tests/autoqasm/test_types.py
index 03083d45..5b63dc44 100644
--- a/test/unit_tests/autoqasm/test_types.py
+++ b/test/unit_tests/autoqasm/test_types.py
@@ -16,8 +16,8 @@
 import oqpy
 import pytest
 
-import braket.experimental.autoqasm as aq
-from braket.experimental.autoqasm.types import Range
+import autoqasm as aq
+from autoqasm.types import Range
 
 
 @pytest.mark.parametrize(

From e2f079da841f5c003cad6ab03e8887a1dbf555b8 Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Tue, 7 May 2024 12:09:18 -0400
Subject: [PATCH 1154/1165] Lint

---
 src/autoqasm/instructions/gates.py             |  2 +-
 src/autoqasm/instructions/instructions.py      |  2 +-
 src/autoqasm/instructions/measurements.py      |  6 +-----
 src/autoqasm/program/pragmas.py                |  1 +
 src/autoqasm/program/program.py                | 18 +++++++-----------
 src/autoqasm/pulse/pulse.py                    |  8 ++++----
 src/autoqasm/transpiler/transpiler.py          |  7 +------
 src/autoqasm/types/types.py                    |  4 ++--
 test/unit_tests/autoqasm/test_api.py           |  6 +++---
 test/unit_tests/autoqasm/test_devices.py       |  6 +++---
 test/unit_tests/autoqasm/test_parameters.py    | 18 ++++--------------
 test/unit_tests/autoqasm/test_program.py       |  2 +-
 test/unit_tests/autoqasm/test_pulse.py         |  4 ++--
 test/unit_tests/autoqasm/test_return.py        |  2 +-
 .../autoqasm/test_serialization_config.py      |  3 ++-
 15 files changed, 34 insertions(+), 55 deletions(-)

diff --git a/src/autoqasm/instructions/gates.py b/src/autoqasm/instructions/gates.py
index 0b35223e..4e748856 100644
--- a/src/autoqasm/instructions/gates.py
+++ b/src/autoqasm/instructions/gates.py
@@ -17,8 +17,8 @@
 from typing import Union
 
 import oqpy
-
 from braket.circuits.free_parameter_expression import FreeParameterExpression
+
 from autoqasm.instructions.instructions import _qubit_instruction
 from autoqasm.types import QubitIdentifierType
 
diff --git a/src/autoqasm/instructions/instructions.py b/src/autoqasm/instructions/instructions.py
index 33de668f..5a0024b8 100644
--- a/src/autoqasm/instructions/instructions.py
+++ b/src/autoqasm/instructions/instructions.py
@@ -20,8 +20,8 @@
 from typing import Any
 
 import oqpy
-
 from braket.circuits.basis_state import BasisState, BasisStateInput
+
 from autoqasm import program as aq_program
 from autoqasm import types as aq_types
 from autoqasm.instructions.qubits import _qubit
diff --git a/src/autoqasm/instructions/measurements.py b/src/autoqasm/instructions/measurements.py
index e2ec8c4e..0e02768f 100644
--- a/src/autoqasm/instructions/measurements.py
+++ b/src/autoqasm/instructions/measurements.py
@@ -28,11 +28,7 @@ def my_program():
 
 from autoqasm import program
 from autoqasm import types as aq_types
-from autoqasm.instructions.qubits import (
-    GlobalQubitRegister,
-    _qubit,
-    global_qubit_register,
-)
+from autoqasm.instructions.qubits import GlobalQubitRegister, _qubit, global_qubit_register
 
 
 def measure(
diff --git a/src/autoqasm/program/pragmas.py b/src/autoqasm/program/pragmas.py
index 28143533..49b31d27 100644
--- a/src/autoqasm/program/pragmas.py
+++ b/src/autoqasm/program/pragmas.py
@@ -35,6 +35,7 @@ def pragma_example() -> None:
 from typing import Iterable
 
 from braket.device_schema import DeviceActionType
+
 from autoqasm import errors, program
 
 
diff --git a/src/autoqasm/program/program.py b/src/autoqasm/program/program.py
index 537c4942..c97c82ad 100644
--- a/src/autoqasm/program/program.py
+++ b/src/autoqasm/program/program.py
@@ -25,29 +25,25 @@
 
 import oqpy.base
 import pygments
+from braket.aws.aws_device import AwsDevice
+from braket.circuits.free_parameter_expression import FreeParameterExpression
+from braket.circuits.serialization import IRType, SerializableProgram
+from braket.device_schema import DeviceActionType
+from braket.devices.device import Device
+from braket.pulse.ast.qasm_parser import ast_to_qasm
 from openpulse import ast
 from openqasm_pygments import OpenQASM3Lexer
 from pygments.formatters.terminal import TerminalFormatter
 from sympy import Symbol
 
 import autoqasm.types as aq_types
-from braket.aws.aws_device import AwsDevice
-from braket.circuits.free_parameter_expression import FreeParameterExpression
-from braket.circuits.serialization import IRType, SerializableProgram
-from braket.device_schema import DeviceActionType
-from braket.devices.device import Device
 from autoqasm import constants, errors
-from autoqasm.instructions.qubits import (
-    GlobalQubitRegister,
-    _get_physical_qubit_indices,
-    _qubit,
-)
+from autoqasm.instructions.qubits import GlobalQubitRegister, _get_physical_qubit_indices, _qubit
 from autoqasm.program.serialization_properties import (
     OpenQASMSerializationProperties,
     SerializationProperties,
 )
 from autoqasm.types import QubitIdentifierType as Qubit
-from braket.pulse.ast.qasm_parser import ast_to_qasm
 
 # Create the thread-local object for the program conversion context.
 _local = threading.local()
diff --git a/src/autoqasm/pulse/pulse.py b/src/autoqasm/pulse/pulse.py
index 1a43cf72..790eb9da 100644
--- a/src/autoqasm/pulse/pulse.py
+++ b/src/autoqasm/pulse/pulse.py
@@ -17,10 +17,6 @@
 from __future__ import annotations
 
 import oqpy
-
-from autoqasm import program as aq_program
-from autoqasm.instructions.qubits import _get_physical_qubit_indices
-from autoqasm.types import BitVar, QubitIdentifierType, is_qubit_identifier_type
 from braket.parametric import FreeParameterExpression
 from braket.parametric.free_parameter import FreeParameter
 from braket.pulse import PulseSequence
@@ -29,6 +25,10 @@
 from braket.pulse.waveforms import Waveform
 from braket.registers.qubit_set import QubitSet
 
+from autoqasm import program as aq_program
+from autoqasm.instructions.qubits import _get_physical_qubit_indices
+from autoqasm.types import BitVar, QubitIdentifierType, is_qubit_identifier_type
+
 
 def _pulse_instruction(name: str, frame: Frame, *args) -> None:
     """Define a pulse instruction.
diff --git a/src/autoqasm/transpiler/transpiler.py b/src/autoqasm/transpiler/transpiler.py
index 1275dbd4..71b455a1 100644
--- a/src/autoqasm/transpiler/transpiler.py
+++ b/src/autoqasm/transpiler/transpiler.py
@@ -48,12 +48,7 @@
 from malt.utils import ag_logging as logging
 
 from autoqasm import operators, program, types
-from autoqasm.converters import (
-    assignments,
-    break_statements,
-    comparisons,
-    return_statements,
-)
+from autoqasm.converters import assignments, break_statements, comparisons, return_statements
 
 
 class PyToOqpy(transpiler.PyToPy):
diff --git a/src/autoqasm/types/types.py b/src/autoqasm/types/types.py
index b74fac61..3739740e 100644
--- a/src/autoqasm/types/types.py
+++ b/src/autoqasm/types/types.py
@@ -20,11 +20,11 @@
 
 import oqpy
 import oqpy.base
+from braket.circuits import FreeParameterExpression
+from braket.registers import Qubit
 from openpulse import ast
 
-from braket.circuits import FreeParameterExpression
 from autoqasm import errors, program
-from braket.registers import Qubit
 
 
 def is_qasm_type(val: Any) -> bool:
diff --git a/test/unit_tests/autoqasm/test_api.py b/test/unit_tests/autoqasm/test_api.py
index 174bd03f..546716d6 100644
--- a/test/unit_tests/autoqasm/test_api.py
+++ b/test/unit_tests/autoqasm/test_api.py
@@ -17,13 +17,13 @@
 """
 
 import pytest
-
-import autoqasm as aq
 from braket.default_simulator import StateVectorSimulator
 from braket.devices.local_simulator import LocalSimulator
+from braket.tasks.local_quantum_task import LocalQuantumTask
+
+import autoqasm as aq
 from autoqasm import errors
 from autoqasm.instructions import cnot, h, measure, rx, x
-from braket.tasks.local_quantum_task import LocalQuantumTask
 
 
 def _test_on_local_sim(program: aq.Program, inputs=None) -> None:
diff --git a/test/unit_tests/autoqasm/test_devices.py b/test/unit_tests/autoqasm/test_devices.py
index 365629df..2cac4d5d 100644
--- a/test/unit_tests/autoqasm/test_devices.py
+++ b/test/unit_tests/autoqasm/test_devices.py
@@ -18,15 +18,15 @@
 from unittest.mock import Mock, patch
 
 import pytest
-
-import autoqasm as aq
 from braket.aws import AwsDevice
 from braket.device_schema import DeviceActionType
 from braket.device_schema.simulators import GateModelSimulatorDeviceCapabilities
 from braket.devices import Devices
+from braket.parametric import FreeParameter
+
+import autoqasm as aq
 from autoqasm import errors
 from autoqasm.instructions import cnot, cphaseshift00, h, rx, x
-from braket.parametric import FreeParameter
 
 RIGETTI_REGION = "us-west-1"
 
diff --git a/test/unit_tests/autoqasm/test_parameters.py b/test/unit_tests/autoqasm/test_parameters.py
index 54b23c1e..14a15ad7 100644
--- a/test/unit_tests/autoqasm/test_parameters.py
+++ b/test/unit_tests/autoqasm/test_parameters.py
@@ -15,25 +15,15 @@
 
 import numpy as np
 import pytest
-
-import autoqasm as aq
 from braket.circuits import FreeParameter
 from braket.default_simulator import StateVectorSimulator
 from braket.devices.local_simulator import LocalSimulator
-from autoqasm import pulse
-from autoqasm.instructions import (
-    cnot,
-    cphaseshift,
-    gpi,
-    h,
-    measure,
-    ms,
-    rx,
-    rz,
-    x,
-)
 from braket.tasks.local_quantum_task import LocalQuantumTask
 
+import autoqasm as aq
+from autoqasm import pulse
+from autoqasm.instructions import cnot, cphaseshift, gpi, h, measure, ms, rx, rz, x
+
 
 def _test_parametric_on_local_sim(program: aq.Program, inputs: dict[str, float]) -> np.ndarray:
     device = LocalSimulator(backend=StateVectorSimulator())
diff --git a/test/unit_tests/autoqasm/test_program.py b/test/unit_tests/autoqasm/test_program.py
index bfac7c32..6f3d1b54 100644
--- a/test/unit_tests/autoqasm/test_program.py
+++ b/test/unit_tests/autoqasm/test_program.py
@@ -19,9 +19,9 @@
 
 import oqpy.base
 import pytest
+from braket.circuits.serialization import IRType
 
 import autoqasm as aq
-from braket.circuits.serialization import IRType
 from autoqasm.instructions import cnot, measure, rx
 
 
diff --git a/test/unit_tests/autoqasm/test_pulse.py b/test/unit_tests/autoqasm/test_pulse.py
index 05549d07..dd8f2187 100644
--- a/test/unit_tests/autoqasm/test_pulse.py
+++ b/test/unit_tests/autoqasm/test_pulse.py
@@ -16,6 +16,8 @@
 import textwrap
 
 import pytest
+from braket.parametric import FreeParameter
+from braket.pulse import ArbitraryWaveform, Frame, Port
 
 import autoqasm as aq
 from autoqasm.instructions import rx
@@ -30,8 +32,6 @@
     shift_frequency,
     shift_phase,
 )
-from braket.parametric import FreeParameter
-from braket.pulse import ArbitraryWaveform, Frame, Port
 
 PORT = Port(port_id="device_port_x0", dt=1e-9, properties={})
 FRAME1 = Frame(frame_id="predefined_frame_1", frequency=2e9, port=PORT, phase=0, is_predefined=True)
diff --git a/test/unit_tests/autoqasm/test_return.py b/test/unit_tests/autoqasm/test_return.py
index f0f7b051..6527ec9b 100644
--- a/test/unit_tests/autoqasm/test_return.py
+++ b/test/unit_tests/autoqasm/test_return.py
@@ -14,11 +14,11 @@
 """AutoQASM tests exercising the return statement for `aq.main`."""
 
 import pytest
+from braket.pulse import Frame, Port
 
 import autoqasm as aq
 from autoqasm.instructions import measure
 from autoqasm.pulse import capture_v0
-from braket.pulse import Frame, Port
 
 
 def test_float_lit():
diff --git a/test/unit_tests/autoqasm/test_serialization_config.py b/test/unit_tests/autoqasm/test_serialization_config.py
index 428cde1f..d3c9d184 100644
--- a/test/unit_tests/autoqasm/test_serialization_config.py
+++ b/test/unit_tests/autoqasm/test_serialization_config.py
@@ -15,10 +15,11 @@
 
 import textwrap
 
+from braket.pulse import Frame, GaussianWaveform, Port
+
 import autoqasm as aq
 from autoqasm.program import OpenQASMSerializationProperties
 from autoqasm.pulse import barrier, play
-from braket.pulse import Frame, GaussianWaveform, Port
 
 PORT = Port(port_id="device_port_x0", dt=1e-9, properties={})
 FRAME = Frame(frame_id="predefined_frame_1", frequency=2e9, port=PORT, phase=0, is_predefined=True)

From 8e39283beaa215819289b278041dd7cd7a3d7936 Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Tue, 7 May 2024 12:10:25 -0400
Subject: [PATCH 1155/1165] Add _sdk folder

---
 src/autoqasm/_sdk/__init__.py | 14 ++++++++++++++
 src/autoqasm/_sdk/_version.py | 18 ++++++++++++++++++
 2 files changed, 32 insertions(+)
 create mode 100644 src/autoqasm/_sdk/__init__.py
 create mode 100644 src/autoqasm/_sdk/_version.py

diff --git a/src/autoqasm/_sdk/__init__.py b/src/autoqasm/_sdk/__init__.py
new file mode 100644
index 00000000..b436b998
--- /dev/null
+++ b/src/autoqasm/_sdk/__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 ._version import __version__  # noqa: F401
diff --git a/src/autoqasm/_sdk/_version.py b/src/autoqasm/_sdk/_version.py
new file mode 100644
index 00000000..112520fd
--- /dev/null
+++ b/src/autoqasm/_sdk/_version.py
@@ -0,0 +1,18 @@
+# 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.
+
+"""Version information.
+Version number (major.minor.patch[-label])
+"""
+
+__version__ = "0.0.1.dev0"

From c7cc442669aab522232ba7529ae34ff05451cf0a Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Tue, 7 May 2024 12:14:53 -0400
Subject: [PATCH 1156/1165] Put version directly in setup.py

---
 setup.py                      |  5 +----
 src/autoqasm/_sdk/__init__.py | 14 --------------
 src/autoqasm/_sdk/_version.py | 18 ------------------
 3 files changed, 1 insertion(+), 36 deletions(-)
 delete mode 100644 src/autoqasm/_sdk/__init__.py
 delete mode 100644 src/autoqasm/_sdk/_version.py

diff --git a/setup.py b/setup.py
index 2203570b..958fa479 100644
--- a/setup.py
+++ b/setup.py
@@ -16,12 +16,9 @@
 with open("README.md", "r") as fh:
     long_description = fh.read()
 
-with open("src/autoqasm/_sdk/_version.py") as f:
-    version = f.readlines()[-1].split()[-1].strip("\"'")
-
 setup(
     name="autoqasm",
-    version=version,
+    version="0.0.1.dev0",
     license="Apache License 2.0",
     python_requires=">= 3.9",
     packages=find_namespace_packages(where="src", exclude=("test",)),
diff --git a/src/autoqasm/_sdk/__init__.py b/src/autoqasm/_sdk/__init__.py
deleted file mode 100644
index b436b998..00000000
--- a/src/autoqasm/_sdk/__init__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# 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 ._version import __version__  # noqa: F401
diff --git a/src/autoqasm/_sdk/_version.py b/src/autoqasm/_sdk/_version.py
deleted file mode 100644
index 112520fd..00000000
--- a/src/autoqasm/_sdk/_version.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# 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.
-
-"""Version information.
-Version number (major.minor.patch[-label])
-"""
-
-__version__ = "0.0.1.dev0"

From ffe52efaf943682f725910067b3317de389595e1 Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Tue, 7 May 2024 12:23:12 -0400
Subject: [PATCH 1157/1165] Readme updates for standalone repo

---
 README.md | 60 +++++++++++++++++++++++++------------------------------
 1 file changed, 27 insertions(+), 33 deletions(-)

diff --git a/README.md b/README.md
index 3778c4e3..2a489495 100644
--- a/README.md
+++ b/README.md
@@ -3,16 +3,13 @@
 **AutoQASM is not an officially supported AWS product.**
 
 This experimental module offers a new quantum-imperative programming experience embedded in Python
-for developing quantum programs.
-
-All of the code in the `experimental` module is _experimental_ software. We may change, remove, or
+for developing quantum programs. AutoQASM is _experimental_ software. We may change, remove, or
 deprecate parts of the AutoQASM API without notice. The name AutoQASM is a working title and is
 also subject to change.
 
-For a fully supported quantum developer experience,
-please continue to use the rest of the Amazon Braket Python SDK by following
+For a fully supported quantum development experience, please use the Amazon Braket Python SDK by following
 [these instructions](https://github.com/amazon-braket/amazon-braket-sdk-python#installing-the-amazon-braket-python-sdk).
-If you are interested in our active development efforts, and you are not
+If you are interested in our active development efforts on AutoQASM, and you are not
 afraid of a few bugs, please keep on reading!
 
 ## Why AutoQASM?
@@ -29,17 +26,20 @@ AutoQASM programs can be serialized to OpenQASM. This textual representation for
 
 Although it is still a work in progress, the intent is that AutoQASM will support any quantum programming paradigm which falls into the [OpenQASM 3.0](https://openqasm.com) language scope. AutoQASM supports serializing quantum programs to OpenQASM, which allows the programs to interoperate with any library or service that supports OpenQASM programs, such as Amazon Braket.
 
-See the [Quick Start](#quick-start) section below, as well as the AutoQASM [example notebooks](../../../../examples/autoqasm), for examples of AutoQASM usage.
+See the [Quick Start](#quick-start) section below, as well as the AutoQASM [example notebooks](examples), for examples of AutoQASM usage.
 
 
 ## Installation
 
-AutoQASM is an experimental module and is not yet part of the released Amazon Braket SDK.
-To use AutoQASM, you'll need to install directly from the `feature/autoqasm` branch:
+To install the latest experimental release of AutoQASM, use `pip`:
+```
+pip install autoqasm
+```
+
+Alternatively, to use AutoQASM in development mode, install from a local clone of this GitHub repository:
 ```
-git clone https://github.com/amazon-braket/amazon-braket-sdk-python.git
-cd amazon-braket-sdk-python
-git checkout feature/autoqasm
+git clone https://github.com/amazon-braket/autoqasm.git
+cd autoqasm
 pip install -e .
 ```
 
@@ -63,7 +63,7 @@ For instance, we can create a Bell state like so:
 ```
 # A program that generates a maximally entangled state
 @aq.main
-def bell_state() -> None:
+def bell_state():
     h(0)
     cnot(0, 1)
 ```
@@ -103,7 +103,7 @@ result = task.result()
 
 Read more about AutoQASM decorators like `@aq.main` [here](doc/decorators.md).
 
-For more example usage of AutoQASM, visit the [example notebooks](../../../../examples/autoqasm).
+For more example usage of AutoQASM, visit the [example notebooks](examples).
 
 ## Architecture
 
@@ -128,12 +128,11 @@ the [AutoQASM GitHub project](https://github.com/orgs/amazon-braket/projects/2/)
 
 We welcome feature requests, bug reports, or
 general feedback, which you can share with us by
-[opening up an issue](https://github.com/amazon-braket/amazon-braket-sdk-python/issues/new/choose). We also
+[opening up an issue](https://github.com/amazon-braket/autoqasm/issues/new/choose). We also
 welcome pull requests, examples, and documentation -- please open an issue describing your work
-when you get started, or comment on an existing issue with your intentions. Pull requests should be
-targeted to the `feature/autoqasm` branch of the https://github.com/amazon-braket/amazon-braket-sdk-python
-repository. For more details on contributing to the Amazon Braket SDK, please read the
-[contributing guidelines](../../../../CONTRIBUTING.md).
+when you get started, or comment on an existing issue with your intentions.
+For more details on contributing to AutoQASM, please read the
+[contributing guidelines](CONTRIBUTING.md).
 
 For questions, you can get help via the Quantum Technologies section of
 [AWS RePost](https://repost.aws/topics/TAxin6L9GYR5a3NElq8AHIqQ/quantum-technologies).
@@ -141,14 +140,11 @@ Please tag your question with "Amazon Braket" and mention AutoQASM in the questi
 
 ## Tests
 
-To run only AutoQASM tests (and skip the rest of the Amazon Braket SDK unit tests), run:
+To run AutoQASM unit tests, run:
 ```
-tox -e unit-tests -- test/unit_test/braket/experimental/autoqasm
+tox -e unit-tests
 ```
 
-Note that you may first need to run `pip install -e .[test]`. More information on running tests
-can be found in the [top-level README](../../../../README.md).
-
 ## Frequently asked questions
 
 ###  1. Will AutoQASM be extended to contain a library of quantum algorithms or quantum applications?
@@ -173,8 +169,8 @@ serialize to other formats in the future.
 
 ### 3. What is the relationship between AutoQASM and the Amazon Braket SDK?
 
-AutoQASM lives alongside the Amazon Braket SDK as an experimental feature
-branch. It supplements the program building experience and integrates with
+AutoQASM lives alongside the Amazon Braket SDK as an experimental package.
+It supplements the program building experience and integrates with
 Amazon Braket SDK features. For instance, one can build a program through
 AutoQASM, and then use the SDK to run the program on a local simulator or on
 an Amazon Braket device.
@@ -182,18 +178,16 @@ an Amazon Braket device.
 ### 4. Does AutoQASM support other providers beyond Amazon Braket?
 
 Yes. AutoQASM serializes to OpenQASM, and so it is applicable to any library
-or QPU that supports OpenQASM. We do have features that use the Amazon Braket
-SDK, such as [device-specific validation](../../../../examples/autoqasm/4_Native_programming.ipynb).
-Because AutoQASM is open-source, anyone could
-build similar integrations for another service. Reach out if you're
-interested in doing this and would like support.
-
+or QPU that supports OpenQASM. AutoQASM does have some features that use the Amazon Braket
+SDK, such as [device-specific validation](examples/4_Native_programming.ipynb).
+Because AutoQASM is open-source, anyone could build similar integrations for another service.
+Reach out if you're interested in doing this and would like support.
 
 ### 5. Does AutoQASM offer special support for device-specific programming?
 
 Yes, AutoQASM has device-specific validation to support native programming.
 We plan to expand this functionality in the future. Learn more with our
-[native programming example notebook](../../../../examples/autoqasm/4_Native_programming.ipynb).
+[native programming example notebook](examples/4_Native_programming.ipynb).
 
 ### 6. Do the devices available through Amazon Braket support all of AutoQASM's features?
 

From 98f5f929646a2cefccf3a305a49c44d216748e9d Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Tue, 7 May 2024 13:00:36 -0400
Subject: [PATCH 1158/1165] Update workflows

---
 .github/workflows/dependent-tests.yml | 44 ---------------------------
 .github/workflows/python-package.yml  |  2 --
 2 files changed, 46 deletions(-)
 delete mode 100644 .github/workflows/dependent-tests.yml

diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml
deleted file mode 100644
index aca79d59..00000000
--- a/.github/workflows/dependent-tests.yml
+++ /dev/null
@@ -1,44 +0,0 @@
-name: Dependent tests
-
-on:
-  pull_request:
-    branches:
-      - main
-      - feature/**
-
-permissions:
-  contents: read
-
-jobs:
-  build:
-
-    runs-on: ${{ matrix.os }}
-    strategy:
-      matrix:
-        os: [ubuntu-latest, macos-latest, windows-latest]
-        python-version: ["3.9", "3.10", "3.11"]
-        dependent:
-          - amazon-braket-pennylane-plugin-python
-
-    steps:
-    - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
-    - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
-      with:
-        python-version: ${{ matrix.python-version }}
-    - name: Install dependencies
-      run: |
-        pip install --upgrade pip
-        pip install --upgrade git+https://github.com/aws/amazon-braket-schemas-python.git@main
-        pip install --upgrade git+https://github.com/aws/amazon-braket-default-simulator-python.git@main
-        pip install -e .
-        cd ..
-        git clone https://github.com/aws/${{ matrix.dependent }}.git
-        cd ${{ matrix.dependent }}
-        # Update the amazon-braket-sdk dependency to reference the current commit
-        python ${GITHUB_WORKSPACE}/.github/scripts/update_dependency.py
-        pip install -e .[test]
-    - name: Run unit tests
-      run: |
-        cd ../${{ matrix.dependent }}
-        pytest
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 8992e2f2..749aa43f 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -36,8 +36,6 @@ jobs:
       run: |
         tox -e unit-tests
     - name: Run AutoQASM example notebooks
-      # NOTE: Do not merge this part to main. The AutoQASM example notebooks should be moved
-      # to the example notebooks repo rather than living in the Braket SDK repo.
       run: |
         pip install -e .
         pip install notebook matplotlib

From 959355df62719ae9e626c0e34e1521eee807dbdd Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Tue, 7 May 2024 13:11:27 -0400
Subject: [PATCH 1159/1165] Workflow updates

---
 .github/workflows/python-package.yml | 8 ++++----
 CODEOWNERS                           | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 749aa43f..37db37c6 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -39,10 +39,10 @@ jobs:
       run: |
         pip install -e .
         pip install notebook matplotlib
-        jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/1_Getting_started_with_AutoQASM.ipynb
-        jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/2_Expressing_classical_control_flow.ipynb
-        jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/3_1_Iterative_phase_estimation.ipynb
-        jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/autoqasm/3_2_magic_state_distillation.ipynb
+        jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/1_Getting_started_with_AutoQASM.ipynb
+        jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/2_Expressing_classical_control_flow.ipynb
+        jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/3_1_Iterative_phase_estimation.ipynb
+        jupyter nbconvert --to html --execute --ExecutePreprocessor.kernel_name=python3 ./examples/3_2_magic_state_distillation.ipynb
     - name: Upload coverage report to Codecov
       uses: codecov/codecov-action@54bcd8715eee62d40e33596ef5e8f0f48dbbccab # v4.1.0
       with:
diff --git a/CODEOWNERS b/CODEOWNERS
index df13e062..7ed6a6df 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -3,4 +3,4 @@
 # These owners will be the default owners for everything in
 # the repo. Unless a later match takes precedence, these accounts
 # will be requested for review when someone opens a pull request.
-* @amazon-braket/braket-maintainers
+* @amazon-braket/braket-dev

From 965d73d96564ff7afcc3db6aa49648bc2a9793dd Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Tue, 7 May 2024 13:21:25 -0400
Subject: [PATCH 1160/1165] Delete duplicate readme

---
 src/autoqasm/README.md | 205 -----------------------------------------
 1 file changed, 205 deletions(-)
 delete mode 100644 src/autoqasm/README.md

diff --git a/src/autoqasm/README.md b/src/autoqasm/README.md
deleted file mode 100644
index 3778c4e3..00000000
--- a/src/autoqasm/README.md
+++ /dev/null
@@ -1,205 +0,0 @@
-# AutoQASM
-
-**AutoQASM is not an officially supported AWS product.**
-
-This experimental module offers a new quantum-imperative programming experience embedded in Python
-for developing quantum programs.
-
-All of the code in the `experimental` module is _experimental_ software. We may change, remove, or
-deprecate parts of the AutoQASM API without notice. The name AutoQASM is a working title and is
-also subject to change.
-
-For a fully supported quantum developer experience,
-please continue to use the rest of the Amazon Braket Python SDK by following
-[these instructions](https://github.com/amazon-braket/amazon-braket-sdk-python#installing-the-amazon-braket-python-sdk).
-If you are interested in our active development efforts, and you are not
-afraid of a few bugs, please keep on reading!
-
-## Why AutoQASM?
-
-AutoQASM provides a Pythonic developer experience for writing quantum programs. The working title "AutoQASM" is derived from the name of the [AutoGraph module of TensorFlow](https://www.tensorflow.org/api_docs/python/tf/autograph). AutoQASM uses AutoGraph to construct quantum assembly (QASM) programs rather than TensorFlow graphs.
-
-AutoQASM provides a natural interface for expressing quantum programs with mid-circuit measurements
-and classical control flow using native Python language features. It allows the construction of
-modular programs consisting of common programming constructs such as loops and subroutines. This
-enables a more imperative programming style than constructing programs via a series of function calls
-on a circuit object.
-
-AutoQASM programs can be serialized to OpenQASM. This textual representation for quantum programs is widely supported and enables interoperability among various frameworks. A crucial part of our serialization process is that modular structures within the program, such as loops and subroutines, are preserved when serializing to OpenQASM.
-
-Although it is still a work in progress, the intent is that AutoQASM will support any quantum programming paradigm which falls into the [OpenQASM 3.0](https://openqasm.com) language scope. AutoQASM supports serializing quantum programs to OpenQASM, which allows the programs to interoperate with any library or service that supports OpenQASM programs, such as Amazon Braket.
-
-See the [Quick Start](#quick-start) section below, as well as the AutoQASM [example notebooks](../../../../examples/autoqasm), for examples of AutoQASM usage.
-
-
-## Installation
-
-AutoQASM is an experimental module and is not yet part of the released Amazon Braket SDK.
-To use AutoQASM, you'll need to install directly from the `feature/autoqasm` branch:
-```
-git clone https://github.com/amazon-braket/amazon-braket-sdk-python.git
-cd amazon-braket-sdk-python
-git checkout feature/autoqasm
-pip install -e .
-```
-
-## Quick start
-
-In this section, we will show how to get started with AutoQASM. AutoQASM allows you to build
-quantum programs with a simplified syntax and run the programs on the service. It uses the circuit
-model programming paradigm that is also used in the Amazon Braket SDK.
-
-First, import the following modules and functions:
-```
-import autoqasm as aq
-from autoqasm.instructions import h, cnot, measure
-```
-
-To create a quantum program using the AutoQASM experience, you decorate a function with `@aq.main`.
-This allows AutoQASM to hook into the program definition and generate an output format that is accepted
-by quantum devices.
-
-For instance, we can create a Bell state like so:
-```
-# A program that generates a maximally entangled state
-@aq.main
-def bell_state() -> None:
-    h(0)
-    cnot(0, 1)
-```
-
-You can view the output format, which is OpenQASM, by running `bell_state.display()`.
-
-AutoQASM enables users to use more complicated program constructs with a compact and readable
-structure. We can demonstrate this with a program that conditionally prepares multiple Bell states
-on qubit pairs (1, 2) and (3, 4).
-```
-@aq.main(num_qubits=5)
-def conditional_multi_bell_states() -> None:
-    h(0)
-    if measure(0):
-        for i in aq.range(2):
-            qubit = 2 * i + 1
-            h(qubit)
-            cnot(qubit, qubit+1)
-
-    measure([0,1,2,3,4])
-```
-
-AutoQASM can support subroutines and complex control flow. You can use the Python runtime
-and quantum runtime side-by-side. There are rough edges at the moment, but we're actively smoothing
-them out!
-
-The Amazon Braket local simulator supports AutoQASM programs as input.
-Let's simulate the `conditional_multi_bell_states` program:
-
-```
-from braket.devices.local_simulator import LocalSimulator
-
-device = LocalSimulator()
-task = device.run(conditional_multi_bell_states, shots=100)
-result = task.result()
-```
-
-Read more about AutoQASM decorators like `@aq.main` [here](doc/decorators.md).
-
-For more example usage of AutoQASM, visit the [example notebooks](../../../../examples/autoqasm).
-
-## Architecture
-
-AutoQASM is built on top of the `autograph` component of TensorFlow. A quantum program is
-written as a Python function which is decorated with `@aq.main`. When calling this
-decorated function, the user’s Python function is converted into a transformed Python function
-by `autograph`. This transformed function is then executed to produce an AutoQASM `Program`
-object which can be simulated and/or serialized to OpenQASM.
-
-The conversion process allows AutoQASM to provide custom handling for native Python control
-flow keywords such as `if`, `for`, and `while` and to preserve this control flow in the resulting
-quantum program in order to realize functionality such as classical feedback on mid-circuit
-measurement, efficient representation of loops, and modularity of subroutine definitions.
-
-## Plans
-
-The AutoQASM project is undergoing rapid development.
-The current status and future plans are tracked in
-the [AutoQASM GitHub project](https://github.com/orgs/amazon-braket/projects/2/).
-
-## Contributing and sharing feedback
-
-We welcome feature requests, bug reports, or
-general feedback, which you can share with us by
-[opening up an issue](https://github.com/amazon-braket/amazon-braket-sdk-python/issues/new/choose). We also
-welcome pull requests, examples, and documentation -- please open an issue describing your work
-when you get started, or comment on an existing issue with your intentions. Pull requests should be
-targeted to the `feature/autoqasm` branch of the https://github.com/amazon-braket/amazon-braket-sdk-python
-repository. For more details on contributing to the Amazon Braket SDK, please read the
-[contributing guidelines](../../../../CONTRIBUTING.md).
-
-For questions, you can get help via the Quantum Technologies section of
-[AWS RePost](https://repost.aws/topics/TAxin6L9GYR5a3NElq8AHIqQ/quantum-technologies).
-Please tag your question with "Amazon Braket" and mention AutoQASM in the question title.
-
-## Tests
-
-To run only AutoQASM tests (and skip the rest of the Amazon Braket SDK unit tests), run:
-```
-tox -e unit-tests -- test/unit_test/braket/experimental/autoqasm
-```
-
-Note that you may first need to run `pip install -e .[test]`. More information on running tests
-can be found in the [top-level README](../../../../README.md).
-
-## Frequently asked questions
-
-###  1. Will AutoQASM be extended to contain a library of quantum algorithms or quantum applications?
-
-No, we are focused on AutoQASM as an interface for low-level expression of
-quantum programs: circuits, gates and pulses. Higher-level algorithm
-libraries could be implemented using AutoQASM and benefit from modular
-AutoQASM functionality such as subroutines.
-
-### 2. What is the relationship between AutoQASM and OpenQASM?
-
-AutoQASM is a quantum programming interface built in Python.
-OpenQASM is a quantum assembly language, often used as a serialization format
-for quantum programming frameworks and quantum hardware providers. We can
-represent a quantum program equivalently in either format, but using AutoQASM
-allows one to also make use of Python, including the Amazon Braket SDK.
-
-AutoQASM can be seen as implementing a builder pattern for OpenQASM. It
-allows you serialize your program to OpenQASM by calling `to_ir()` on the
-built program. The interface is not strongly tied to OpenQASM, so we could
-serialize to other formats in the future.
-
-### 3. What is the relationship between AutoQASM and the Amazon Braket SDK?
-
-AutoQASM lives alongside the Amazon Braket SDK as an experimental feature
-branch. It supplements the program building experience and integrates with
-Amazon Braket SDK features. For instance, one can build a program through
-AutoQASM, and then use the SDK to run the program on a local simulator or on
-an Amazon Braket device.
-
-### 4. Does AutoQASM support other providers beyond Amazon Braket?
-
-Yes. AutoQASM serializes to OpenQASM, and so it is applicable to any library
-or QPU that supports OpenQASM. We do have features that use the Amazon Braket
-SDK, such as [device-specific validation](../../../../examples/autoqasm/4_Native_programming.ipynb).
-Because AutoQASM is open-source, anyone could
-build similar integrations for another service. Reach out if you're
-interested in doing this and would like support.
-
-
-### 5. Does AutoQASM offer special support for device-specific programming?
-
-Yes, AutoQASM has device-specific validation to support native programming.
-We plan to expand this functionality in the future. Learn more with our
-[native programming example notebook](../../../../examples/autoqasm/4_Native_programming.ipynb).
-
-### 6. Do the devices available through Amazon Braket support all of AutoQASM's features?
-
-No, for example, the `reset` instruction is not supported by all devices. In
-general, different QPUs and QHPs support different sets of features, so
-AutoQASM will often support features that a particular device doesn't
-support. We intend that AutoQASM will eventually be able to generate any
-program representable by OpenQASM 3.0, with additional Python-side features
-such as validation and visualization.

From 4fcd0f8c008626ccbdf16fa304d4bcc211cac9ce Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Tue, 7 May 2024 14:09:08 -0400
Subject: [PATCH 1161/1165] Update simulator commit hash comments

---
 setup.py | 2 +-
 tox.ini  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/setup.py b/setup.py
index 958fa479..51167782 100644
--- a/setup.py
+++ b/setup.py
@@ -30,7 +30,7 @@
         # to get the version of the simulator that supports the mcm=True argument for Monte Carlo
         # simulation of mid-circuit measurement, which AutoQASM requires.
         # NOTE: This is currently installed automatically due to feature/autoqasm requirements
-        # "amazon-braket-default-simulator @ git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git@f17d3070a4f87a3bbef677e385a2e94dd386af78#egg=amazon-braket-default-simulator",  # noqa E501
+        # "amazon-braket-default-simulator @ git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git@ab068c860963c29842d7649c741f88da669597eb#egg=amazon-braket-default-simulator",  # noqa E501
         "oqpy~=0.3.5",
         "diastatic-malt",
         "numpy",
diff --git a/tox.ini b/tox.ini
index cee9487b..a1a041bb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -136,4 +136,4 @@ deps =
     git+https://github.com/amazon-braket/amazon-braket-schemas-python.git
     git+https://github.com/amazon-braket/amazon-braket-sdk-python.git@d62e288961af1b9a42b14667e1167fca301e2708  # feature/autoqasm branch
     # NOTE: This is currently installed automatically due to feature/autoqasm requirements
-    #git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git@f17d3070a4f87a3bbef677e385a2e94dd386af78  # mcm-sim branch
+    #git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git@ab068c860963c29842d7649c741f88da669597eb  # mcm-sim branch

From 79020a84253384b1aed1432afc8c72d45813aadc Mon Sep 17 00:00:00 2001
From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com>
Date: Tue, 7 May 2024 14:38:06 -0400
Subject: [PATCH 1162/1165] Update notebook import cell formatting

---
 .../1_Getting_started_with_AutoQASM.ipynb     | 10 ++--
 .../2_Expressing_classical_control_flow.ipynb |  8 +--
 examples/3_1_Iterative_phase_estimation.ipynb |  2 +
 examples/3_2_magic_state_distillation.ipynb   |  2 +
 examples/4_Native_programming.ipynb           |  7 +--
 ...programming_and_dynamical_decoupling.ipynb | 31 ++++++++----
 examples/6_Customize_gate_calibrations.ipynb  | 50 ++++++++++++-------
 7 files changed, 71 insertions(+), 39 deletions(-)

diff --git a/examples/1_Getting_started_with_AutoQASM.ipynb b/examples/1_Getting_started_with_AutoQASM.ipynb
index aa947001..e33d5b7d 100644
--- a/examples/1_Getting_started_with_AutoQASM.ipynb
+++ b/examples/1_Getting_started_with_AutoQASM.ipynb
@@ -22,6 +22,8 @@
     "\n",
     "# AWS imports: Import Braket SDK modules\n",
     "from braket.devices.local_simulator import LocalSimulator\n",
+    "\n",
+    "# AutoQASM imports\n",
     "import autoqasm as aq\n",
     "from autoqasm.instructions import measure, h, cnot"
    ]
@@ -46,7 +48,7 @@
     "def bell_state():\n",
     "    h(0)\n",
     "    cnot(0, 1)\n",
-    "    return measure([0, 1])\n"
+    "    return measure([0, 1])"
    ]
   },
   {
@@ -101,7 +103,7 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "measurement counts:  Counter({'11': 50, '00': 50})\n"
+      "measurement counts:  Counter({'11': 52, '00': 48})\n"
      ]
     }
    ],
@@ -120,7 +122,7 @@
    "outputs": [
     {
      "data": {
-      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjIAAAGwCAYAAACzXI8XAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/SrBM8AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAgr0lEQVR4nO3dfXBU5d2H8e+GJBsg7MaEkBBJBBUJiEAbBBatYhqN1FIxqVLKDIgIFQMV1hfMKCAWJtKpgtIA1mKwHSNKW7C0UwQjRKsJLxFUtKRo0aSTbBA0CQTyItnnD4ed7hMCYdlw9sbrM7MznHPvnvxkJnp5ztldm9fr9QoAAMBAYVYPAAAAEChCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGCrd6gM7W2tqqqqoq9ejRQzabzepxAABAB3i9Xh09elRJSUkKC2v/vMtFHzJVVVVKTk62egwAABCAyspK9enTp931iz5kevToIenbvwiHw2HxNAAAoCPq6+uVnJzs++94ey76kDl1OcnhcBAyAAAY5my3hXCzLwAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGNZGjJPPPGEbDab3yM1NdW33tjYqJycHMXFxSk6OlrZ2dmqqamxcGIAABBKLD8jc/XVV6u6utr3+Oc//+lbmzt3rjZt2qT169eruLhYVVVVysrKsnBaAAAQSiz/9uvw8HAlJia22V9XV6c1a9aosLBQ6enpkqSCggINHDhQpaWlGjVq1IUeFQAAhBjLz8gcOHBASUlJuvzyyzVp0iRVVFRIksrKytTS0qKMjAzfc1NTU5WSkqKSkpJ2j9fU1KT6+nq/BwAAuDhZekZm5MiRWrt2rQYMGKDq6motWrRIP/jBD7Rv3z55PB5FRkYqJibG7zUJCQnyeDztHjMvL0+LFi3q5Mm/1ffRv1+QnwOY6vOnbrN6hKDgdx1on9W/55aGzNixY31/HjJkiEaOHKnLLrtMr732mrp27RrQMXNzc+V2u33b9fX1Sk5OPu9ZAQBA6LH80tL/iomJ0VVXXaVPP/1UiYmJam5uVm1trd9zampqTntPzSl2u10Oh8PvAQAALk4hFTLHjh3TZ599pt69eystLU0REREqKiryrZeXl6uiokIul8vCKQEAQKiw9NLSQw89pHHjxumyyy5TVVWVFi5cqC5dumjixIlyOp2aNm2a3G63YmNj5XA4NHv2bLlcLt6xBAAAJFkcMv/97381ceJEHTlyRPHx8br++utVWlqq+Ph4SdKyZcsUFham7OxsNTU1KTMzUytXrrRyZAAAEEIsDZl169adcT0qKkr5+fnKz8+/QBMBAACThNQ9MgAAAOeCkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGCskAmZp556SjabTXPmzPHta2xsVE5OjuLi4hQdHa3s7GzV1NRYNyQAAAgpIREyu3bt0vPPP68hQ4b47Z87d642bdqk9evXq7i4WFVVVcrKyrJoSgAAEGosD5ljx45p0qRJeuGFF3TJJZf49tfV1WnNmjV65plnlJ6errS0NBUUFOi9995TaWlpu8drampSfX293wMAAFycLA+ZnJwc3XbbbcrIyPDbX1ZWppaWFr/9qampSklJUUlJSbvHy8vLk9Pp9D2Sk5M7bXYAAGAtS0Nm3bp1ev/995WXl9dmzePxKDIyUjExMX77ExIS5PF42j1mbm6u6urqfI/Kyspgjw0AAEJEuFU/uLKyUg888IC2bt2qqKiooB3XbrfLbrcH7XgAACB0WXZGpqysTIcOHdL3v/99hYeHKzw8XMXFxXruuecUHh6uhIQENTc3q7a21u91NTU1SkxMtGZoAAAQUiw7I/PDH/5QH330kd++qVOnKjU1VfPmzVNycrIiIiJUVFSk7OxsSVJ5ebkqKirkcrmsGBkAAIQYy0KmR48eGjx4sN++7t27Ky4uzrd/2rRpcrvdio2NlcPh0OzZs+VyuTRq1CgrRgYAACHGspDpiGXLliksLEzZ2dlqampSZmamVq5cafVYAAAgRIRUyGzfvt1vOyoqSvn5+crPz7dmIAAAENIs/xwZAACAQBEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMJalIbNq1SoNGTJEDodDDodDLpdL//jHP3zrjY2NysnJUVxcnKKjo5Wdna2amhoLJwYAAKHE0pDp06ePnnrqKZWVlWn37t1KT0/X7bffro8//liSNHfuXG3atEnr169XcXGxqqqqlJWVZeXIAAAghIRb+cPHjRvnt71kyRKtWrVKpaWl6tOnj9asWaPCwkKlp6dLkgoKCjRw4ECVlpZq1KhRVowMAABCSMjcI3Py5EmtW7dODQ0NcrlcKisrU0tLizIyMnzPSU1NVUpKikpKSto9TlNTk+rr6/0eAADg4mR5yHz00UeKjo6W3W7Xfffdpw0bNmjQoEHyeDyKjIxUTEyM3/MTEhLk8XjaPV5eXp6cTqfvkZyc3Mn/BAAAwCqWh8yAAQO0d+9e7dixQzNnztSUKVP0ySefBHy83Nxc1dXV+R6VlZVBnBYAAIQSS++RkaTIyEhdeeWVkqS0tDTt2rVLzz77rCZMmKDm5mbV1tb6nZWpqalRYmJiu8ez2+2y2+2dPTYAAAgBlp+R+f9aW1vV1NSktLQ0RUREqKioyLdWXl6uiooKuVwuCycEAAChwtIzMrm5uRo7dqxSUlJ09OhRFRYWavv27XrjjTfkdDo1bdo0ud1uxcbGyuFwaPbs2XK5XLxjCQAASLI4ZA4dOqTJkyerurpaTqdTQ4YM0RtvvKGbb75ZkrRs2TKFhYUpOztbTU1NyszM1MqVK60cGQAAhJCAQqayslI2m019+vSRJO3cuVOFhYUaNGiQZsyY0eHjrFmz5ozrUVFRys/PV35+fiBjAgCAi1xA98j8/Oc/17Zt2yRJHo9HN998s3bu3KnHHntMTz75ZFAHBAAAaE9AIbNv3z6NGDFCkvTaa69p8ODBeu+99/Tyyy9r7dq1wZwPAACgXQGFTEtLi+8tzm+++aZ+8pOfSPr2k3erq6uDNx0AAMAZBBQyV199tVavXq133nlHW7du1a233ipJqqqqUlxcXFAHBAAAaE9AIbN06VI9//zzGjNmjCZOnKihQ4dKkv7617/6LjkBAAB0toDetTRmzBgdPnxY9fX1uuSSS3z7Z8yYoe7duwdtOAAAgDMJ6IxMenq6jh496hcxkhQbG6sJEyYEZTAAAICzCShktm/frubm5jb7Gxsb9c4775z3UAAAAB1xTpeWPvzwQ9+fP/nkE3k8Ht/2yZMntXnzZl166aXBmw4AAOAMzilkhg0bJpvNJpvNpvT09DbrXbt21YoVK4I2HAAAwJmcU8gcPHhQXq9Xl19+uXbu3Kn4+HjfWmRkpHr16qUuXboEfUgAAIDTOaeQueyyyyRJra2tnTIMAADAuQj4268PHDigbdu26dChQ23CZsGCBec9GAAAwNkEFDIvvPCCZs6cqZ49eyoxMVE2m823ZrPZCBkAAHBBBBQyixcv1pIlSzRv3rxgzwMAANBhAX2OzNdff60777wz2LMAAACck4BC5s4779SWLVuCPQsAAMA5CejS0pVXXqn58+ertLRU11xzjSIiIvzWf/nLXwZlOAAAgDMJKGR+97vfKTo6WsXFxSouLvZbs9lshAwAALggAgqZgwcPBnsOAACAcxbQPTIAAAChIKAzMvfcc88Z11988cWAhgEAADgXAYXM119/7bfd0tKiffv2qba29rRfJgkAANAZAgqZDRs2tNnX2tqqmTNn6oorrjjvoQAAADoiaPfIhIWFye12a9myZcE6JAAAwBkF9Wbfzz77TN98800wDwkAANCugC4tud1uv22v16vq6mr9/e9/15QpU4IyGAAAwNkEFDJ79uzx2w4LC1N8fLyefvrps76jCQAAIFgCCplt27YFew4AAIBzFlDInPLll1+qvLxckjRgwADFx8cHZSgAAICOCOhm34aGBt1zzz3q3bu3brjhBt1www1KSkrStGnTdPz48WDPCAAAcFoBhYzb7VZxcbE2bdqk2tpa1dbW6vXXX1dxcbEefPDBYM8IAABwWgFdWvrzn/+sP/3pTxozZoxv349+9CN17dpVd911l1atWhWs+QAAANoV0BmZ48ePKyEhoc3+Xr16cWkJAABcMAGFjMvl0sKFC9XY2Ojbd+LECS1atEgulytowwEAAJxJQJeWli9frltvvVV9+vTR0KFDJUkffPCB7Ha7tmzZEtQBAQAA2hNQyFxzzTU6cOCAXn75Ze3fv1+SNHHiRE2aNEldu3YN6oAAAADtCShk8vLylJCQoOnTp/vtf/HFF/Xll19q3rx5QRkOAADgTAK6R+b5559Xampqm/1XX321Vq9efd5DAQAAdERAIePxeNS7d+82++Pj41VdXX3eQwEAAHREQCGTnJysd999t83+d999V0lJSec9FAAAQEcEdI/M9OnTNWfOHLW0tCg9PV2SVFRUpEceeYRP9gUAABdMQCHz8MMP68iRI7r//vvV3NwsSYqKitK8efOUm5sb1AEBAADaE1DI2Gw2LV26VPPnz9e//vUvde3aVf3795fdbg/2fAAAAO0KKGROiY6O1rXXXhusWQAAAM5JQDf7AgAAhAJCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGMvSkMnLy9O1116rHj16qFevXho/frzKy8v9ntPY2KicnBzFxcUpOjpa2dnZqqmpsWhiAAAQSiwNmeLiYuXk5Ki0tFRbt25VS0uLbrnlFjU0NPieM3fuXG3atEnr169XcXGxqqqqlJWVZeHUAAAgVIRb+cM3b97st7127Vr16tVLZWVluuGGG1RXV6c1a9aosLBQ6enpkqSCggINHDhQpaWlGjVqlBVjAwCAEBFS98jU1dVJkmJjYyVJZWVlamlpUUZGhu85qampSklJUUlJyWmP0dTUpPr6er8HAAC4OIVMyLS2tmrOnDm67rrrNHjwYEmSx+NRZGSkYmJi/J6bkJAgj8dz2uPk5eXJ6XT6HsnJyZ09OgAAsEjIhExOTo727dundevWnddxcnNzVVdX53tUVlYGaUIAABBqLL1H5pRZs2bpb3/7m95++2316dPHtz8xMVHNzc2qra31OytTU1OjxMTE0x7LbrfLbrd39sgAACAEWHpGxuv1atasWdqwYYPeeust9evXz289LS1NERERKioq8u0rLy9XRUWFXC7XhR4XAACEGEvPyOTk5KiwsFCvv/66evTo4bvvxel0qmvXrnI6nZo2bZrcbrdiY2PlcDg0e/ZsuVwu3rEEAACsDZlVq1ZJksaMGeO3v6CgQHfffbckadmyZQoLC1N2draampqUmZmplStXXuBJAQBAKLI0ZLxe71mfExUVpfz8fOXn51+AiQAAgElC5l1LAAAA54qQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAQAAxiJkAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxLQ+btt9/WuHHjlJSUJJvNpo0bN/qte71eLViwQL1791bXrl2VkZGhAwcOWDMsAAAIOZaGTENDg4YOHar8/PzTrv/617/Wc889p9WrV2vHjh3q3r27MjMz1djYeIEnBQAAoSjcyh8+duxYjR079rRrXq9Xy5cv1+OPP67bb79dkvSHP/xBCQkJ2rhxo372s5+d9nVNTU1qamrybdfX1wd/cAAAEBJC9h6ZgwcPyuPxKCMjw7fP6XRq5MiRKikpafd1eXl5cjqdvkdycvKFGBcAAFggZEPG4/FIkhISEvz2JyQk+NZOJzc3V3V1db5HZWVlp84JAACsY+mlpc5gt9tlt9utHgMAAFwAIXtGJjExUZJUU1Pjt7+mpsa3BgAAvttCNmT69eunxMREFRUV+fbV19drx44dcrlcFk4GAABChaWXlo4dO6ZPP/3Ut33w4EHt3btXsbGxSklJ0Zw5c7R48WL1799f/fr10/z585WUlKTx48dbNzQAAAgZlobM7t27ddNNN/m23W63JGnKlClau3atHnnkETU0NGjGjBmqra3V9ddfr82bNysqKsqqkQEAQAixNGTGjBkjr9fb7rrNZtOTTz6pJ5988gJOBQAATBGy98gAAACcDSEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxEyAADAWIQMAAAwFiEDAACMRcgAAABjETIAAMBYhAwAADAWIQMAAIxFyAAAAGMRMgAAwFiEDAAAMBYhAwAAjEXIAAAAYxkRMvn5+erbt6+ioqI0cuRI7dy50+qRAABACAj5kHn11Vfldru1cOFCvf/++xo6dKgyMzN16NAhq0cDAAAWC/mQeeaZZzR9+nRNnTpVgwYN0urVq9WtWze9+OKLVo8GAAAsFm71AGfS3NyssrIy5ebm+vaFhYUpIyNDJSUlp31NU1OTmpqafNt1dXWSpPr6+qDP19p0POjHBC4mnfF7ZwV+14H2ddbv+anjer3eMz4vpEPm8OHDOnnypBISEvz2JyQkaP/+/ad9TV5enhYtWtRmf3JycqfMCKB9zuVWTwCgs3X27/nRo0fldDrbXQ/pkAlEbm6u3G63b7u1tVVfffWV4uLiZLPZLJwMna2+vl7JycmqrKyUw+GwehwAnYDf8+8Or9ero0ePKikp6YzPC+mQ6dmzp7p06aKamhq//TU1NUpMTDzta+x2u+x2u9++mJiYzhoRIcjhcPAvOOAix+/5d8OZzsScEtI3+0ZGRiotLU1FRUW+fa2trSoqKpLL5bJwMgAAEApC+oyMJLndbk2ZMkXDhw/XiBEjtHz5cjU0NGjq1KlWjwYAACwW8iEzYcIEffnll1qwYIE8Ho+GDRumzZs3t7kBGLDb7Vq4cGGbS4sALh78nuP/s3nP9r4mAACAEBXS98gAAACcCSEDAACMRcgAAABjETIAAMBYhAyM8vbbb2vcuHFKSkqSzWbTxo0b/db/8pe/6JZbbvF9kvPevXstmRPA+cvPz1ffvn0VFRWlkSNHaufOnb61xsZG5eTkKC4uTtHR0crOzm7z4an4biBkYJSGhgYNHTpU+fn57a5ff/31Wrp06QWeDEAwvfrqq3K73Vq4cKHef/99DR06VJmZmTp06JAkae7cudq0aZPWr1+v4uJiVVVVKSsry+KpYQXefg1j2Ww2bdiwQePHj2+z9vnnn6tfv37as2ePhg0bdsFnA3B+Ro4cqWuvvVa//e1vJX37qe7JycmaPXu2Zs6cqfj4eBUWFuqnP/2pJGn//v0aOHCgSkpKNGrUKCtHxwXGGRkAQEhpbm5WWVmZMjIyfPvCwsKUkZGhkpISlZWVqaWlxW89NTVVKSkpKikpsWJkWIiQAQCElMOHD+vkyZNtPsE9ISFBHo9HHo9HkZGRbb4Q+NQ6vlsIGQAAYCxCBgAQUnr27KkuXbq0eRdSTU2NEhMTlZiYqObmZtXW1p52Hd8thAwAIKRERkYqLS1NRUVFvn2tra0qKiqSy+VSWlqaIiIi/NbLy8tVUVEhl8tlxciwUMh/+zXwv44dO6ZPP/3Ut33w4EHt3btXsbGxSklJ0VdffaWKigpVVVVJ+vZfbpJ8/xcHwAxut1tTpkzR8OHDNWLECC1fvlwNDQ2aOnWqnE6npk2bJrfbrdjYWDkcDs2ePVsul4t3LH0XeQGDbNu2zSupzWPKlCler9frLSgoOO36woULLZ0bwLlbsWKFNyUlxRsZGekdMWKEt7S01Ld24sQJ7/333++95JJLvN26dfPecccd3urqagunhVX4HBkAAGAs7pEBAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMYiZAAAgLEIGQAAYCxCBgAAGIuQAXDexowZozlz5rS73rdvXy1fvvyCzPL555/LZrNp7969F+TnAbAWIQOg0+3atUszZszwbdtsNm3cuLHDr1+7dq1iYmI69Nzk5GRVV1dr8ODB5zglABPxpZEAOl18fPwF+TnNzc2KjIzkC0KB7xDOyAAIim+++UazZs2S0+lUz549NX/+fJ36Krf/vbTUt29fSdIdd9whm83m2/7ggw900003qUePHnI4HEpLS9Pu3bu1fft2TZ06VXV1dbLZbLLZbHriiSd8x/rVr36lyZMny+FwaMaMGW0uLW3fvl02m01FRUUaPny4unXrptGjR/u+Gf2UxYsXq1evXurRo4fuvfdePfrooxo2bJhvffv27RoxYoS6d++umJgYXXfddfriiy86668TQAcRMgCC4qWXXlJ4eLh27typZ599Vs8884x+//vft3nerl27JEkFBQWqrq72bU+aNEl9+vTRrl27VFZWpkcffVQREREaPXq0li9fLofDoerqalVXV+uhhx7yHe83v/mNhg4dqj179mj+/PntzvfYY4/p6aef1u7duxUeHq577rnHt/byyy9ryZIlWrp0qcrKypSSkqJVq1b51r/55huNHz9eN954oz788EOVlJRoxowZstls5/33BuD8cGkJQFAkJydr2bJlstlsGjBggD766CMtW7ZM06dP93veqctMMTExfpeAKioq9PDDDys1NVWS1L9/f9+a0+mUzWY77SWj9PR0Pfjgg77tzz///LTzLVmyRDfeeKMk6dFHH9Vtt92mxsZGRUVFacWKFZo2bZqmTp0qSVqwYIG2bNmiY8eOSZLq6+tVV1enH//4x7riiiskSQMHDjynvx8AnYMzMgCCYtSoUX5nKFwulw4cOKCTJ0926PVut1v33nuvMjIy9NRTT+mzzz7r0OuGDx/eoecNGTLE9+fevXtLkg4dOiRJKi8v14gRI/ye/7/bsbGxuvvuu5WZmalx48bp2WefVXV1dYd+LoDORcgACAlPPPGEPv74Y91222166623NGjQIG3YsOGsr+vevXuHjh8REeH786ngam1t7fB8BQUFKikp0ejRo/Xqq6/qqquuUmlpaYdfD6BzEDIAgmLHjh1+26Wlperfv7+6dOnS5rkRERGnPVNz1VVXae7cudqyZYuysrJUUFAgSYqMjOzwmZ1ADBgwwHevzin/f1uSvve97yk3N1fvvfeeBg8erMLCwk6bCUDHEDIAgqKiokJut1vl5eV65ZVXtGLFCj3wwAOnfW7fvn1VVFQkj8ejr7/+WidOnNCsWbO0fft2ffHFF3r33Xe1a9cu330offv21bFjx1RUVKTDhw/r+PHjQZ199uzZWrNmjV566SUdOHBAixcv1ocffug7c3Pw4EHl5uaqpKREX3zxhbZs2aIDBw5wnwwQArjZF0BQTJ48WSdOnNCIESPUpUsXPfDAA34fgve/nn76abndbr3wwgu69NJL9e9//1tHjhzR5MmTVVNTo549eyorK0uLFi2SJI0ePVr33XefJkyYoCNHjmjhwoW+t2AHw6RJk/Sf//xHDz30kBobG3XXXXfp7rvv1s6dOyVJ3bp10/79+/XSSy/pyJEj6t27t3JycvSLX/wiaDMACIzNe+qDHgAAPjfffLMSExP1xz/+0epRAJwBZ2QAfOcdP35cq1evVmZmprp06aJXXnlFb775prZu3Wr1aADOgjMyAL7zTpw4oXHjxmnPnj1qbGzUgAED9PjjjysrK8vq0QCcBSEDAACMxbuWAACAsQgZAABgLEIGAAAYi5ABAADGImQAAICxCBkAAGAsQgYAABiLkAEAAMb6PzNBakRu64XvAAAAAElFTkSuQmCC",
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjMAAAGwCAYAAABcnuQpAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAg6ElEQVR4nO3df3AU9f3H8dfKjyOBJMivOyIBoh74I4AVBBOridTEoqWlmfEXjIKKgog1phaIUTkcSISOEGsqClVMa6N2WlE7tjQRSaRGSkBSEBVRI8QhZwRjEiAkAvv94zvccA0/kiNh7wPPx8zNsJ/d27xhJvXZ3U3Osm3bFgAAgKHOcXoAAACAU0HMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBonZ0eoKMdPnxYu3btUlRUlCzLcnocAADQCrZtq6GhQbGxsTrnnBNfeznjY2bXrl2Ki4tzegwAABCCqqoqDRgw4ITHnPExExUVJen//zGio6MdngYAALRGfX294uLiAv8dP5EzPmaO3FqKjo4mZgAAMExrHhHhAWAAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEbr7PQAphs8522nRwDC1ldP3uj0CADOAlyZAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEZzNGZ8Pp8sywp6eTyewH7btuXz+RQbG6uIiAilpKRo69atDk4MAADCjeNXZi699FJVV1cHXlu2bAnsW7RokRYvXqz8/HyVl5fL4/EoNTVVDQ0NDk4MAADCieOfzdS5c+egqzFH2LatvLw8ZWdnKz09XZJUUFAgt9utwsJCTZs27Zjna2pqUlNTU2C7vr6+YwYHAABhwfErM9u3b1dsbKzi4+N166236ssvv5QkVVZWyu/3Ky0tLXCsy+VScnKyysrKjnu+3NxcxcTEBF5xcXEd/ncAAADOcTRmxowZoz/+8Y/617/+peXLl8vv9yspKUl79uyR3++XJLnd7qD3uN3uwL5jycrKUl1dXeBVVVXVoX8HAADgLEdvM40bNy7w52HDhikxMVEXXHCBCgoKdOWVV0qSLMsKeo9t2y3WjuZyueRyuTpmYAAAEHYcv810tO7du2vYsGHavn174Dma/70KU1NT0+JqDQAAOHuFVcw0NTXpk08+Uf/+/RUfHy+Px6Pi4uLA/ubmZpWWliopKcnBKQEAQDhx9DbTww8/rPHjx2vgwIGqqanR/PnzVV9fr8mTJ8uyLGVkZCgnJ0der1der1c5OTmKjIzUxIkTnRwbAACEEUdj5uuvv9Ztt92m3bt3q2/fvrryyiu1bt06DRo0SJI0a9YsNTY2asaMGaqtrdWYMWNUVFSkqKgoJ8cGAABhxLJt23Z6iI5UX1+vmJgY1dXVKTo6ut3PP3jO2+1+TuBM8dWTNzo9AgBDteW/32H1zAwAAEBbETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGiO/p4ZADABv4IBOL5w+BUMXJkBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGC5uYyc3NlWVZysjICKzZti2fz6fY2FhFREQoJSVFW7dudW5IAAAQdsIiZsrLy7Vs2TINHz48aH3RokVavHix8vPzVV5eLo/Ho9TUVDU0NDg0KQAACDeOx8zevXs1adIkLV++XOeee25g3bZt5eXlKTs7W+np6UpISFBBQYH279+vwsJCBycGAADhxPGYuf/++3XjjTfquuuuC1qvrKyU3+9XWlpaYM3lcik5OVllZWXHPV9TU5Pq6+uDXgAA4MzV2ckv/uqrr+rDDz9UeXl5i31+v1+S5Ha7g9bdbrd27Nhx3HPm5uZq3rx57TsoAAAIW45dmamqqtKDDz6ol19+Wd26dTvucZZlBW3btt1i7WhZWVmqq6sLvKqqqtptZgAAEH4cuzKzceNG1dTUaOTIkYG1Q4cO6b333lN+fr62bdsm6f+v0PTv3z9wTE1NTYurNUdzuVxyuVwdNzgAAAgrjl2Z+clPfqItW7aooqIi8Bo1apQmTZqkiooKnX/++fJ4PCouLg68p7m5WaWlpUpKSnJqbAAAEGYcuzITFRWlhISEoLXu3burd+/egfWMjAzl5OTI6/XK6/UqJydHkZGRmjhxohMjAwCAMOToA8AnM2vWLDU2NmrGjBmqra3VmDFjVFRUpKioKKdHAwAAYSKsYqakpCRo27Is+Xw++Xw+R+YBAADhz/HfMwMAAHAqiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0YgZAABgNGIGAAAYjZgBAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGI2YAQAARiNmAACA0RyNmaVLl2r48OGKjo5WdHS0EhMT9c9//jOw37Zt+Xw+xcbGKiIiQikpKdq6dauDEwMAgHDjaMwMGDBATz75pDZs2KANGzZo7Nix+sUvfhEIlkWLFmnx4sXKz89XeXm5PB6PUlNT1dDQ4OTYAAAgjIQUM1VVVfr6668D2+vXr1dGRoaWLVvWpvOMHz9eN9xwg4YMGaIhQ4ZowYIF6tGjh9atWyfbtpWXl6fs7Gylp6crISFBBQUF2r9/vwoLC0MZGwAAnIFCipmJEydqzZo1kiS/36/U1FStX79ejzzyiJ544omQBjl06JBeffVV7du3T4mJiaqsrJTf71daWlrgGJfLpeTkZJWVlR33PE1NTaqvrw96AQCAM1dIMfPRRx9p9OjRkqS//OUvSkhIUFlZmQoLC/XSSy+16VxbtmxRjx495HK5NH36dK1cuVKXXHKJ/H6/JMntdgcd73a7A/uOJTc3VzExMYFXXFxc2/5yAADAKCHFzA8//CCXyyVJeuedd/Tzn/9cknTRRRepurq6TecaOnSoKioqtG7dOt13332aPHmyPv7448B+y7KCjrdtu8Xa0bKyslRXVxd4VVVVtWkeAABglpBi5tJLL9Vzzz2ntWvXqri4WD/96U8lSbt27VLv3r3bdK6uXbvqwgsv1KhRo5Sbm6sRI0bo6aeflsfjkaQWV2FqampaXK05msvlCvx01JEXAAA4c4UUMwsXLtTzzz+vlJQU3XbbbRoxYoQk6a233grcfgqVbdtqampSfHy8PB6PiouLA/uam5tVWlqqpKSkU/oaAADgzNE5lDelpKRo9+7dqq+v17nnnhtYv/fee9W9e/dWn+eRRx7RuHHjFBcXp4aGBr366qsqKSnRqlWrZFmWMjIylJOTI6/XK6/Xq5ycHEVGRmrixImhjA0AAM5AIcXM2LFj9frrrweFjCT16tVLEyZM0Lvvvtuq83zzzTe6/fbbVV1drZiYGA0fPlyrVq1SamqqJGnWrFlqbGzUjBkzVFtbqzFjxqioqEhRUVGhjA0AAM5AIcVMSUmJmpubW6wfOHBAa9eubfV5XnjhhRPutyxLPp9PPp+vrSMCAICzRJtiZvPmzYE/f/zxx0EP5x46dEirVq3Seeed137TAQAAnESbYuayyy6TZVmyLEtjx45tsT8iIkLPPPNMuw0HAABwMm2KmcrKStm2rfPPP1/r169X3759A/u6du2qfv36qVOnTu0+JAAAwPG0KWYGDRokSTp8+HCHDAMAANBWIT0ALEmfffaZSkpKVFNT0yJuHn/88VMeDAAAoDVCipnly5frvvvuU58+feTxeII+XsCyLGIGAACcNiHFzPz587VgwQLNnj27vecBAABok5A+zqC2tlY33XRTe88CAADQZiHFzE033aSioqL2ngUAAKDNQrrNdOGFF+qxxx7TunXrNGzYMHXp0iVo/69+9at2GQ4AAOBkQoqZZcuWqUePHiotLVVpaWnQPsuyiBkAAHDahBQzlZWV7T0HAABASEJ6ZgYAACBchHRl5q677jrh/hdffDGkYQAAANoqpJipra0N2v7hhx/00Ucf6fvvvz/mB1ACAAB0lJBiZuXKlS3WDh8+rBkzZuj8888/5aEAAABaq92emTnnnHP00EMPacmSJe11SgAAgJNq1weAv/jiCx08eLA9TwkAAHBCId1myszMDNq2bVvV1dV6++23NXny5HYZDAAAoDVCiplNmzYFbZ9zzjnq27evnnrqqZP+pBMAAEB7Cilm1qxZ095zAAAAhCSkmDni22+/1bZt22RZloYMGaK+ffu211wAAACtEtIDwPv27dNdd92l/v3765prrtHVV1+t2NhY3X333dq/f397zwgAAHBcIcVMZmamSktL9fe//13ff/+9vv/+e7355psqLS3Vr3/96/aeEQAA4LhCus30t7/9TX/961+VkpISWLvhhhsUERGhm2++WUuXLm2v+QAAAE4opCsz+/fvl9vtbrHer18/bjMBAIDTKqSYSUxM1Ny5c3XgwIHAWmNjo+bNm6fExMR2Gw4AAOBkQrrNlJeXp3HjxmnAgAEaMWKELMtSRUWFXC6XioqK2ntGAACA4wopZoYNG6bt27fr5Zdf1qeffirbtnXrrbdq0qRJioiIaO8ZAQAAjiukmMnNzZXb7dY999wTtP7iiy/q22+/1ezZs9tlOAAAgJMJ6ZmZ559/XhdddFGL9UsvvVTPPffcKQ8FAADQWiHFjN/vV//+/Vus9+3bV9XV1ac8FAAAQGuFFDNxcXF6//33W6y///77io2NPeWhAAAAWiukZ2amTp2qjIwM/fDDDxo7dqwkafXq1Zo1axa/ARgAAJxWIcXMrFmz9N1332nGjBlqbm6WJHXr1k2zZ89WVlZWuw4IAABwIiHFjGVZWrhwoR577DF98sknioiIkNfrlcvlau/5AAAATiikmDmiR48euuKKK9prFgAAgDYL6QFgAACAcEHMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADCaozGTm5urK664QlFRUerXr58mTJigbdu2BR1j27Z8Pp9iY2MVERGhlJQUbd261aGJAQBAuHE0ZkpLS3X//fdr3bp1Ki4u1sGDB5WWlqZ9+/YFjlm0aJEWL16s/Px8lZeXy+PxKDU1VQ0NDQ5ODgAAwkVnJ7/4qlWrgrZXrFihfv36aePGjbrmmmtk27by8vKUnZ2t9PR0SVJBQYHcbrcKCws1bdq0FudsampSU1NTYLu+vr5j/xIAAMBRYfXMTF1dnSSpV69ekqTKykr5/X6lpaUFjnG5XEpOTlZZWdkxz5Gbm6uYmJjAKy4uruMHBwAAjgmbmLFtW5mZmfrxj3+shIQESZLf75ckud3uoGPdbndg3//KyspSXV1d4FVVVdWxgwMAAEc5epvpaDNnztTmzZv173//u8U+y7KCtm3bbrF2hMvlksvl6pAZAQBA+AmLKzMPPPCA3nrrLa1Zs0YDBgwIrHs8HklqcRWmpqamxdUaAABwdnI0Zmzb1syZM/X666/r3XffVXx8fND++Ph4eTweFRcXB9aam5tVWlqqpKSk0z0uAAAIQ47eZrr//vtVWFioN998U1FRUYErMDExMYqIiJBlWcrIyFBOTo68Xq+8Xq9ycnIUGRmpiRMnOjk6AAAIE47GzNKlSyVJKSkpQesrVqzQlClTJEmzZs1SY2OjZsyYodraWo0ZM0ZFRUWKioo6zdMCAIBw5GjM2LZ90mMsy5LP55PP5+v4gQAAgHHC4gFgAACAUBEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIzmaMy89957Gj9+vGJjY2VZlt54442g/bZty+fzKTY2VhEREUpJSdHWrVudGRYAAIQlR2Nm3759GjFihPLz84+5f9GiRVq8eLHy8/NVXl4uj8ej1NRUNTQ0nOZJAQBAuOrs5BcfN26cxo0bd8x9tm0rLy9P2dnZSk9PlyQVFBTI7XarsLBQ06ZNO52jAgCAMBW2z8xUVlbK7/crLS0tsOZyuZScnKyysrLjvq+pqUn19fVBLwAAcOYK25jx+/2SJLfbHbTudrsD+44lNzdXMTExgVdcXFyHzgkAAJwVtjFzhGVZQdu2bbdYO1pWVpbq6uoCr6qqqo4eEQAAOMjRZ2ZOxOPxSPr/KzT9+/cPrNfU1LS4WnM0l8sll8vV4fMBAIDwELZXZuLj4+XxeFRcXBxYa25uVmlpqZKSkhycDAAAhBNHr8zs3btXn3/+eWC7srJSFRUV6tWrlwYOHKiMjAzl5OTI6/XK6/UqJydHkZGRmjhxooNTAwCAcOJozGzYsEHXXnttYDszM1OSNHnyZL300kuaNWuWGhsbNWPGDNXW1mrMmDEqKipSVFSUUyMDAIAw42jMpKSkyLbt4+63LEs+n08+n+/0DQUAAIwSts/MAAAAtAYxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDAAAMBoxAwAAjEbMAAAAoxEzAADAaMQMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADCaETHz7LPPKj4+Xt26ddPIkSO1du1ap0cCAABhIuxj5rXXXlNGRoays7O1adMmXX311Ro3bpx27tzp9GgAACAMhH3MLF68WHfffbemTp2qiy++WHl5eYqLi9PSpUudHg0AAISBzk4PcCLNzc3auHGj5syZE7SelpamsrKyY76nqalJTU1Nge26ujpJUn19fYfMeLhpf4ecFzgTdNT33enG9zlwfB31fX7kvLZtn/TYsI6Z3bt369ChQ3K73UHrbrdbfr//mO/Jzc3VvHnzWqzHxcV1yIwAji8mz+kJAHS0jv4+b2hoUExMzAmPCeuYOcKyrKBt27ZbrB2RlZWlzMzMwPbhw4f13XffqXfv3sd9D84M9fX1iouLU1VVlaKjo50eB0AH4Pv87GHbthoaGhQbG3vSY8M6Zvr06aNOnTq1uApTU1PT4mrNES6XSy6XK2itZ8+eHTUiwlB0dDT/Iwec4fg+Pzuc7IrMEWH9AHDXrl01cuRIFRcXB60XFxcrKSnJoakAAEA4CesrM5KUmZmp22+/XaNGjVJiYqKWLVumnTt3avr06U6PBgAAwkDYx8wtt9yiPXv26IknnlB1dbUSEhL0j3/8Q4MGDXJ6NIQZl8uluXPntrjNCODMwfc5jsWyW/MzTwAAAGEqrJ+ZAQAAOBliBgAAGI2YAQAARiNmAACA0YgZGOW9997T+PHjFRsbK8uy9MYbbwTtf/3113X99derT58+sixLFRUVjswJ4NQ9++yzio+PV7du3TRy5EitXbs2sM+2bfl8PsXGxioiIkIpKSnaunWrg9PCScQMjLJv3z6NGDFC+fn5x91/1VVX6cknnzzNkwFoT6+99poyMjKUnZ2tTZs26eqrr9a4ceO0c+dOSdKiRYu0ePFi5efnq7y8XB6PR6mpqWpoaHB4cjiBH82GsSzL0sqVKzVhwoQW+7766ivFx8dr06ZNuuyyy077bABOzZgxY3T55Zdr6dKlgbWLL75YEyZMUE5OjmJjY5WRkaHZs2dLkpqamuR2u7Vw4UJNmzbNqbHhEK7MAADCSnNzszZu3Ki0tLSg9bS0NJWVlamyslJ+vz9ov8vlUnJyssrKyk73uAgDxAwAIKzs3r1bhw4davGBwm63W36/P/Dhw8fbj7MPMQMACEuWZQVt27YdtHay/Th7EDMAgLDSp08fderUqcVVlpqaGrndbnk8Hkk67n6cfYgZAEBY6dq1q0aOHKni4uKg9eLiYiUlJSk+Pl4ejydof3Nzs0pLS5WUlHS6x0UYCPtPzQaOtnfvXn3++eeB7crKSlVUVKhXr14aOHCgvvvuO+3cuVO7du2SJG3btk2S5PF4Av9vDkD4y8zM1O23365Ro0YpMTFRy5Yt086dOzV9+nRZlqWMjAzl5OTI6/XK6/UqJydHkZGRmjhxotOjwwk2YJA1a9bYklq8Jk+ebNu2ba9YseKY++fOnevo3ADa7ve//709aNAgu2vXrvbll19ul5aWBvYdPnzYnjt3ru3xeGyXy2Vfc8019pYtWxycFk7i98wAAACj8cwMAAAwGjEDAACMRswAAACjETMAAMBoxAwAADAaMQMAAIxGzAAAAKMRMwAAwGjEDIBTlpKSooyMjOPuHzx4sPLy8k7LLF999ZUsy1JFRcVp+XoAnEfMAOhw5eXluvfeewPblmXpjTfeaPX7X3rpJfXs2bNVx8bFxam6uloJCQltnBKAqfigSQAdrm/fvqfl6zQ3N6tr1658qChwluHKDIB2cfDgQc2cOVM9e/ZU79699eijj+rIR78dfZtp8ODBkqRf/vKXsiwrsP3f//5X1157raKiohQdHa2RI0dqw4YNKikp0Z133qm6ujpZliXLsuTz+QLnmj9/vqZMmaKYmBjdc889LW4zlZSUyLIsrV69WqNGjVJkZKSSkpICn6h+xPz589WvXz9FRUVp6tSpmjNnji677LLA/pKSEo0ePVrdu3dXz549ddVVV2nHjh0d9c8JoA2IGQDtoqCgQJ07d9Z//vMf/e53v9OSJUv0hz/8ocVx5eXlkqQVK1aouro6sD1p0iQNGDBA5eXl2rhxo+bMmaMuXbooKSlJeXl5io6OVnV1taqrq/Xwww8Hzvfb3/5WCQkJ2rhxox577LHjzpedna2nnnpKGzZsUOfOnXXXXXcF9v35z3/WggULtHDhQm3cuFEDBw7U0qVLA/sPHjyoCRMmKDk5WZs3b9YHH3yge++9V5ZlnfK/G4BTx20mAO0iLi5OS5YskWVZGjp0qLZs2aIlS5bonnvuCTruyC2nnj17Bt0O2rlzp37zm9/ooosukiR5vd7AvpiYGFmWdczbR2PHjg2Km6+++uqY8y1YsEDJycmSpDlz5ujGG2/UgQMH1K1bNz3zzDO6++67deedd0qSHn/8cRUVFWnv3r2SpPr6etXV1elnP/uZLrjgAknSxRdf3KZ/HwAdhyszANrFlVdeGXSlIjExUdu3b9ehQ4da9f7MzExNnTpV1113nZ588kl98cUXrXrfqFGjWnXc8OHDA3/u37+/JKmmpkaStG3bNo0ePTro+KO3e/XqpSlTpuj666/X+PHj9fTTT6u6urpVXxdAxyNmAIQFn8+nrVu36sYbb9S7776rSy65RCtXrjzp+7p3796q83fp0iXw5yPRdfjw4RZrRxx53ueIFStW6IMPPlBSUpJee+01DRkyROvWrWvV1wbQsYgZAO3if//Dvm7dOnm9XnXq1KnFsV26dDnmFZshQ4booYceUlFRkdLT07VixQpJUteuXVt9hScUQ4cO1fr164PWNmzY0OK4H/3oR8rKylJZWZkSEhJUWFjYYTMBaD1iBkC7qKqqUmZmprZt26ZXXnlFzzzzjB588MFjHjt48GCtXr1afr9ftbW1amxs1MyZM1VSUqIdO3bo/fffV3l5eeC5lMGDB2vv3r1avXq1du/erf3797fr7A888IBeeOEFFRQUaPv27Zo/f742b94cuFpTWVmprKwsffDBB9qxY4eKior02Wef8dwMECZ4ABhAu7jjjjvU2Nio0aNHq1OnTnrggQeCflHe0Z566illZmZq+fLlOu+88/TZZ59pz549uuOOO/TNN9+oT58+Sk9P17x58yRJSUlJmj59um655Rbt2bNHc+fODfx4dnuYNGmSvvzySz388MM6cOCAbr75Zk2ZMiVwtSYyMlKffvqpCgoKtGfPHvXv318zZ87UtGnT2m0GAKGz7P+9MQwAUGpqqjwej/70pz85PQqAk+DKDICz3v79+/Xcc8/p+uuvV6dOnfTKK6/onXfeUXFxsdOjAWgFrswAOOs1NjZq/Pjx+vDDD9XU1KShQ4fq0UcfVXp6utOjAWgFYgYAABiNn2YCAABGI2YAAIDRiBkAAGA0YgYAABiNmAEAAEYjZgAAgNGIGQAAYDRiBgAAGO3/AG9Oqq4AjvguAAAAAElFTkSuQmCC",
       "text/plain": [
        "
" ] @@ -164,7 +166,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.9.18" } }, "nbformat": 4, diff --git a/examples/2_Expressing_classical_control_flow.ipynb b/examples/2_Expressing_classical_control_flow.ipynb index 4a5311b8..2dc40384 100644 --- a/examples/2_Expressing_classical_control_flow.ipynb +++ b/examples/2_Expressing_classical_control_flow.ipynb @@ -23,6 +23,8 @@ "\n", "# AWS imports: Import Braket SDK modules\n", "from braket.devices.local_simulator import LocalSimulator\n", + "\n", + "# AutoQASM imports\n", "import autoqasm as aq\n", "from autoqasm.instructions import measure, h, cnot" ] @@ -150,12 +152,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "measurement counts: Counter({'00000': 135, '10000': 133, '00011': 121, '11100': 111})\n" + "measurement counts: Counter({'11100': 131, '00000': 131, '00011': 124, '10000': 114})\n" ] }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGxCAYAAACEFXd4AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAArJElEQVR4nO3df1RVdb7/8deWH0c0PArmOZ4R0+6ln5h1MZ1oSkrFHH/crjNZ6TUrKx3TIjSTrzVSK0Htpszozcauo968ZnfdyaZucwt0Ei2sEGFKm7QaUirOUA4BKgLK/v4xy706IUZ44Bw+Ph9r7bXcn89n7/PefNZZvfrsvcGybdsWAACAobqEugAAAID2RNgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABgtMtQFhIOmpiZ9+eWXio2NlWVZoS4HAAC0gm3bqq2tlc/nU5cuLa/fEHYkffnll0pISAh1GQAAoA3Ky8vVr1+/FvtDGnZ27Nihp556SsXFxaqoqNCWLVt08803n3bsjBkztGbNGq1YsULp6elOe319vebNm6cXXnhBdXV1GjFihJ555pkzXvR3xcbGSvr7D6tHjx5nc0kAAKCD1NTUKCEhwfnveEtCGnaOHj2qwYMH66677tLPfvazFse9/PLLevfdd+Xz+Zr1paen69VXX9XmzZsVHx+vuXPnaty4cSouLlZERESr6jh166pHjx6EHQAAOpnvewQlpGFnzJgxGjNmzBnHfPHFF5o9e7beeOMNjR07NqCvurpaa9eu1fPPP6+RI0dKkjZu3KiEhARt3bpVo0ePbrfaAQBA5xDWb2M1NTVp6tSpevjhh3X55Zc36y8uLlZjY6PS0tKcNp/Pp6SkJBUWFrZ43vr6etXU1ARsAADATGEddpYuXarIyEg98MADp+33+/2Kjo5Wr169Ato9Ho/8fn+L583JyZHb7XY2Hk4GAMBcYRt2iouL9atf/Urr16//wa+D27Z9xmMyMzNVXV3tbOXl5WdbLgAACFNhG3Z27typyspK9e/fX5GRkYqMjNTBgwc1d+5cDRgwQJLk9XrV0NCgqqqqgGMrKyvl8XhaPLfL5XIeRuahZAAAzBa2YWfq1Kl6//33VVpa6mw+n08PP/yw3njjDUlScnKyoqKilJ+f7xxXUVGhvXv3KiUlJVSlAwCAMBLSt7GOHDmiTz75xNkvKytTaWmp4uLi1L9/f8XHxweMj4qKktfr1cUXXyxJcrvdmj59uubOnav4+HjFxcVp3rx5GjRokPN2FgAAOLeFNOzs3r1bN9xwg7OfkZEhSZo2bZrWr1/fqnOsWLFCkZGRmjRpkvNLBdevX9/q37EDAADMZtm2bYe6iFCrqamR2+1WdXU1z+8AANBJtPa/32H7zA4AAEAwEHYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABgtpL9n51wwYMFroS7hnPXZkrGhLgEAEAZY2QEAAEYj7AAAAKMRdgAAgNEIOwAAwGg8oAwA38GLBaHDiwVoD6zsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwWkjDzo4dOzR+/Hj5fD5ZlqWXX37Z6WtsbNQjjzyiQYMGqXv37vL5fLrjjjv05ZdfBpyjvr5ec+bMUe/evdW9e3dNmDBBn3/+eQdfCQAACFeRofzwo0ePavDgwbrrrrv0s5/9LKDv2LFj2rNnjx577DENHjxYVVVVSk9P14QJE7R7925nXHp6ul599VVt3rxZ8fHxmjt3rsaNG6fi4mJFRER09CXhHDJgwWuhLuGc9dmSsaEuAUAnEtKwM2bMGI0ZM+a0fW63W/n5+QFtK1eu1NChQ3Xo0CH1799f1dXVWrt2rZ5//nmNHDlSkrRx40YlJCRo69atGj16dLtfAwAACG+d6pmd6upqWZalnj17SpKKi4vV2NiotLQ0Z4zP51NSUpIKCwtbPE99fb1qamoCNgAAYKZOE3aOHz+uBQsWaPLkyerRo4ckye/3Kzo6Wr169QoY6/F45Pf7WzxXTk6O3G63syUkJLRr7QAAIHQ6RdhpbGzUbbfdpqamJj3zzDPfO962bVmW1WJ/Zmamqqurna28vDyY5QIAgDAS9mGnsbFRkyZNUllZmfLz851VHUnyer1qaGhQVVVVwDGVlZXyeDwtntPlcqlHjx4BGwAAMFNYh51TQefjjz/W1q1bFR8fH9CfnJysqKiogAeZKyoqtHfvXqWkpHR0uQAAIAyF9G2sI0eO6JNPPnH2y8rKVFpaqri4OPl8Pv385z/Xnj179L//+786efKk8xxOXFycoqOj5Xa7NX36dM2dO1fx8fGKi4vTvHnzNGjQIOftLAAAcG4LadjZvXu3brjhBmc/IyNDkjRt2jRlZWXplVdekSRdeeWVAce9+eabSk1NlSStWLFCkZGRmjRpkurq6jRixAitX7+e37EDAAAkhTjspKamyrbtFvvP1HdK165dtXLlSq1cuTKYpQEAAEOE9TM7AAAAZ4uwAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADBaZKgLAACgIwxY8FqoSzhnfbZkbEg/n5UdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaCENOzt27ND48ePl8/lkWZZefvnlgH7btpWVlSWfz6eYmBilpqZq3759AWPq6+s1Z84c9e7dW927d9eECRP0+eefd+BVAACAcBbSsHP06FENHjxYq1atOm3/smXLtHz5cq1atUpFRUXyer0aNWqUamtrnTHp6enasmWLNm/erLfeektHjhzRuHHjdPLkyY66DAAAEMYiQ/nhY8aM0ZgxY07bZ9u2cnNztXDhQk2cOFGStGHDBnk8Hm3atEkzZsxQdXW11q5dq+eff14jR46UJG3cuFEJCQnaunWrRo8e3WHXAgAAwlPYPrNTVlYmv9+vtLQ0p83lcmn48OEqLCyUJBUXF6uxsTFgjM/nU1JSkjPmdOrr61VTUxOwAQAAM4Vt2PH7/ZIkj8cT0O7xeJw+v9+v6Oho9erVq8Uxp5OTkyO32+1sCQkJQa4eAACEi7ANO6dYlhWwb9t2s7bv+r4xmZmZqq6udrby8vKg1AoAAMJP2IYdr9crSc1WaCorK53VHq/Xq4aGBlVVVbU45nRcLpd69OgRsAEAADOFbdgZOHCgvF6v8vPznbaGhgYVFBQoJSVFkpScnKyoqKiAMRUVFdq7d68zBgAAnNtC+jbWkSNH9Mknnzj7ZWVlKi0tVVxcnPr376/09HRlZ2crMTFRiYmJys7OVrdu3TR58mRJktvt1vTp0zV37lzFx8crLi5O8+bN06BBg5y3swAAwLktpGFn9+7duuGGG5z9jIwMSdK0adO0fv16zZ8/X3V1dZo1a5aqqqo0bNgw5eXlKTY21jlmxYoVioyM1KRJk1RXV6cRI0Zo/fr1ioiI6PDrAQAA4SekYSc1NVW2bbfYb1mWsrKylJWV1eKYrl27auXKlVq5cmU7VAgAADq7sH1mBwAAIBgIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRwjrsnDhxQo8++qgGDhyomJgYXXjhhXriiSfU1NTkjLFtW1lZWfL5fIqJiVFqaqr27dsXwqoBAEA4Ceuws3TpUj377LNatWqV/vznP2vZsmV66qmntHLlSmfMsmXLtHz5cq1atUpFRUXyer0aNWqUamtrQ1g5AAAIF2Eddnbt2qV//ud/1tixYzVgwAD9/Oc/V1pamnbv3i3p76s6ubm5WrhwoSZOnKikpCRt2LBBx44d06ZNm0JcPQAACAdhHXZ+8pOfaNu2bTpw4IAk6U9/+pPeeust/fSnP5UklZWVye/3Ky0tzTnG5XJp+PDhKiwsbPG89fX1qqmpCdgAAICZIkNdwJk88sgjqq6u1iWXXKKIiAidPHlSixcv1u233y5J8vv9kiSPxxNwnMfj0cGDB1s8b05Ojh5//PH2KxwAAISNsF7ZefHFF7Vx40Zt2rRJe/bs0YYNG/Rv//Zv2rBhQ8A4y7IC9m3bbtb2bZmZmaqurna28vLydqkfAACEXliv7Dz88MNasGCBbrvtNknSoEGDdPDgQeXk5GjatGnyer2S/r7C07dvX+e4ysrKZqs93+ZyueRyudq3eAAAEBbCemXn2LFj6tIlsMSIiAjn1fOBAwfK6/UqPz/f6W9oaFBBQYFSUlI6tFYAABCe2rSyU15eLsuy1K9fP0nSe++9p02bNumyyy7TfffdF7Tixo8fr8WLF6t///66/PLLVVJSouXLl+vuu++W9PfbV+np6crOzlZiYqISExOVnZ2tbt26afLkyUGrAwAAdF5tCjuTJ0/Wfffdp6lTp8rv92vUqFG6/PLLtXHjRvn9fv3yl78MSnErV67UY489plmzZqmyslI+n08zZswIOP/8+fNVV1enWbNmqaqqSsOGDVNeXp5iY2ODUgMAAOjc2nQba+/evRo6dKgk6b//+7+VlJSkwsJCbdq0SevXrw9acbGxscrNzdXBgwdVV1enTz/9VE8++aSio6OdMZZlKSsrSxUVFTp+/LgKCgqUlJQUtBoAAEDn1qaw09jY6Dzgu3XrVk2YMEGSdMkll6iioiJ41QEAAJylNoWdyy+/XM8++6x27typ/Px83XTTTZKkL7/8UvHx8UEtEAAA4Gy0KewsXbpUv/nNb5Samqrbb79dgwcPliS98sorzu0tAACAcNCmB5RTU1P19ddfq6amRr169XLa77vvPnXv3j1oxQEAAJytNq3s3HjjjaqtrQ0IOpIUFxenW2+9NSiFAQAABEObws727dvV0NDQrP348ePauXPnWRcFAAAQLD/oNtb777/v/PvDDz90/hCnJJ08eVKvv/66fvSjHwWvOgAAgLP0g8LOlVdeKcuyZFmWbrzxxmb9MTExWrlyZdCKAwAAOFs/KOyUlZXJtm1deOGFeu+993T++ec7fdHR0erTp48iIiKCXiQAAEBb/aCwc8EFF0iS84c4AQAAwl2bXj2XpAMHDmj79u2qrKxsFn6C9bexAAAAzlabws5zzz2nX/ziF+rdu7e8Xq8sy3L6LMsi7AAAgLDRprDz5JNPavHixXrkkUeCXQ8AAEBQten37FRVVemWW24Jdi0AAABB16awc8sttygvLy/YtQAAAARdm25j/eM//qMee+wxvfPOOxo0aJCioqIC+h944IGgFAcAAHC22hR21qxZo/POO08FBQUqKCgI6LMsi7ADAADCRpvCTllZWbDrAAAAaBdtemYHAACgs2jTys7dd999xv7f/va3bSoGAAAg2NoUdqqqqgL2GxsbtXfvXn3zzTen/QOhAAAAodKmsLNly5ZmbU1NTZo1a5YuvPDCsy4KAAAgWIL2zE6XLl300EMPacWKFcE6JQAAwFkL6gPKn376qU6cOBHMUwIAAJyVNt3GysjICNi3bVsVFRV67bXXNG3atKAUBgAAEAxtCjslJSUB+126dNH555+vp59++nvf1AIAAOhIbQo7b775ZrDrAAAAaBdtCjunfPXVV9q/f78sy9JFF12k888/P1h1AQAABEWbHlA+evSo7r77bvXt21fXX3+9rrvuOvl8Pk2fPl3Hjh0Ldo0AAABt1qawk5GRoYKCAr366qv65ptv9M033+j3v/+9CgoKNHfu3GDXCAAA0GZtuo31u9/9Tv/zP/+j1NRUp+2nP/2pYmJiNGnSJK1evTpY9QEAAJyVNq3sHDt2TB6Pp1l7nz59uI0FAADCSpvCzjXXXKNFixbp+PHjTltdXZ0ef/xxXXPNNUErDgAA4Gy16TZWbm6uxowZo379+mnw4MGyLEulpaVyuVzKy8sLdo0AAABt1qawM2jQIH388cfauHGjPvroI9m2rdtuu01TpkxRTExMsGsEAABoszaFnZycHHk8Ht17770B7b/97W/11Vdf6ZFHHglKcQAAAGerTc/s/OY3v9Ell1zSrP3yyy/Xs88+e9ZFAQAABEubwo7f71ffvn2btZ9//vmqqKg466IAAACCpU1hJyEhQW+//Xaz9rfffls+n++siwIAAAiWNj2zc8899yg9PV2NjY268cYbJUnbtm3T/Pnz+Q3KAAAgrLQp7MyfP19/+9vfNGvWLDU0NEiSunbtqkceeUSZmZlBLRAAAOBstCnsWJalpUuX6rHHHtOf//xnxcTEKDExUS6XK9j1AQAAnJU2PbNzynnnnaerr75aSUlJ7RZ0vvjiC/3rv/6r4uPj1a1bN1155ZUqLi52+m3bVlZWlnw+n2JiYpSamqp9+/a1Sy0AAKDzOauw096qqqp07bXXKioqSv/3f/+nDz/8UE8//bR69uzpjFm2bJmWL1+uVatWqaioSF6vV6NGjVJtbW3oCgcAAGGjTbexOsrSpUuVkJCgdevWOW0DBgxw/m3btnJzc7Vw4UJNnDhRkrRhwwZ5PB5t2rRJM2bMOO156+vrVV9f7+zX1NS0zwUAAICQC+uVnVdeeUVDhgzRLbfcoj59+uiqq67Sc8895/SXlZXJ7/crLS3NaXO5XBo+fLgKCwtbPG9OTo7cbrezJSQktOt1AACA0AnrsPOXv/xFq1evVmJiot544w3NnDlTDzzwgP7zP/9T0t9/uaEkeTyegOM8Ho/TdzqZmZmqrq52tvLy8va7CAAAEFJhfRurqalJQ4YMUXZ2tiTpqquu0r59+7R69WrdcccdzjjLsgKOs227Wdu3uVwu3hwDAOAcEdYrO3379tVll10W0HbppZfq0KFDkiSv1ytJzVZxKisrm632AACAc1NYh51rr71W+/fvD2g7cOCALrjgAknSwIED5fV6lZ+f7/Q3NDSooKBAKSkpHVorAAAIT2F9G+uhhx5SSkqKsrOzNWnSJL333ntas2aN1qxZI+nvt6/S09OVnZ2txMREJSYmKjs7W926ddPkyZNDXD0AAAgHYR12rr76am3ZskWZmZl64oknNHDgQOXm5mrKlCnOmPnz56uurk6zZs1SVVWVhg0bpry8PMXGxoawcgAAEC7COuxI0rhx4zRu3LgW+y3LUlZWlrKysjquKAAA0GmE9TM7AAAAZ4uwAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABitU4WdnJwcWZal9PR0p822bWVlZcnn8ykmJkapqanat29f6IoEAABhpdOEnaKiIq1Zs0ZXXHFFQPuyZcu0fPlyrVq1SkVFRfJ6vRo1apRqa2tDVCkAAAgnnSLsHDlyRFOmTNFzzz2nXr16Oe22bSs3N1cLFy7UxIkTlZSUpA0bNujYsWPatGlTi+err69XTU1NwAYAAMzUKcLO/fffr7Fjx2rkyJEB7WVlZfL7/UpLS3PaXC6Xhg8frsLCwhbPl5OTI7fb7WwJCQntVjsAAAitsA87mzdv1p49e5STk9Osz+/3S5I8Hk9Au8fjcfpOJzMzU9XV1c5WXl4e3KIBAEDYiAx1AWdSXl6uBx98UHl5eeratWuL4yzLCti3bbtZ27e5XC65XK6g1QkAAMJXWK/sFBcXq7KyUsnJyYqMjFRkZKQKCgr061//WpGRkc6KzndXcSorK5ut9gAAgHNTWIedESNG6IMPPlBpaamzDRkyRFOmTFFpaakuvPBCeb1e5efnO8c0NDSooKBAKSkpIawcAACEi7C+jRUbG6ukpKSAtu7duys+Pt5pT09PV3Z2thITE5WYmKjs7Gx169ZNkydPDkXJAAAgzIR12GmN+fPnq66uTrNmzVJVVZWGDRumvLw8xcbGhro0AAAQBjpd2Nm+fXvAvmVZysrKUlZWVkjqAQAA4S2sn9kBAAA4W4QdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGhhHXZycnJ09dVXKzY2Vn369NHNN9+s/fv3B4yxbVtZWVny+XyKiYlRamqq9u3bF6KKAQBAuAnrsFNQUKD7779f77zzjvLz83XixAmlpaXp6NGjzphly5Zp+fLlWrVqlYqKiuT1ejVq1CjV1taGsHIAABAuIkNdwJm8/vrrAfvr1q1Tnz59VFxcrOuvv162bSs3N1cLFy7UxIkTJUkbNmyQx+PRpk2bNGPGjFCUDQAAwkhYr+x8V3V1tSQpLi5OklRWVia/36+0tDRnjMvl0vDhw1VYWNjieerr61VTUxOwAQAAM3WasGPbtjIyMvSTn/xESUlJkiS/3y9J8ng8AWM9Ho/Tdzo5OTlyu93OlpCQ0H6FAwCAkOo0YWf27Nl6//339cILLzTrsywrYN+27WZt35aZmanq6mpnKy8vD3q9AAAgPIT1MzunzJkzR6+88op27Nihfv36Oe1er1fS31d4+vbt67RXVlY2W+35NpfLJZfL1X4FAwCAsBHWKzu2bWv27Nl66aWX9Mc//lEDBw4M6B84cKC8Xq/y8/OdtoaGBhUUFCglJaWjywUAAGEorFd27r//fm3atEm///3vFRsb6zyH43a7FRMTI8uylJ6eruzsbCUmJioxMVHZ2dnq1q2bJk+eHOLqAQBAOAjrsLN69WpJUmpqakD7unXrdOedd0qS5s+fr7q6Os2aNUtVVVUaNmyY8vLyFBsb28HVAgCAcBTWYce27e8dY1mWsrKylJWV1f4FAQCATiesn9kBAAA4W4QdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGjGhJ1nnnlGAwcOVNeuXZWcnKydO3eGuiQAABAGjAg7L774otLT07Vw4UKVlJTouuuu05gxY3To0KFQlwYAAELMiLCzfPlyTZ8+Xffcc48uvfRS5ebmKiEhQatXrw51aQAAIMQiQ13A2WpoaFBxcbEWLFgQ0J6WlqbCwsLTHlNfX6/6+npnv7q6WpJUU1MT9Pqa6o8F/ZxonfaYz29jbkOHuTVXe84t8xo67TWvp85r2/YZx3X6sPP111/r5MmT8ng8Ae0ej0d+v/+0x+Tk5Ojxxx9v1p6QkNAuNSI03LmhrgDthbk1F3Nrpvae19raWrnd7hb7O33YOcWyrIB927abtZ2SmZmpjIwMZ7+pqUl/+9vfFB8f3+Ix56KamholJCSovLxcPXr0CHU5CCLm1kzMq7mY29OzbVu1tbXy+XxnHNfpw07v3r0VERHRbBWnsrKy2WrPKS6XSy6XK6CtZ8+e7VVip9ejRw++XIZibs3EvJqLuW3uTCs6p3T6B5Sjo6OVnJys/Pz8gPb8/HylpKSEqCoAABAuOv3KjiRlZGRo6tSpGjJkiK655hqtWbNGhw4d0syZM0NdGgAACDEjws6tt96qw4cP64knnlBFRYWSkpL0hz/8QRdccEGoS+vUXC6XFi1a1OyWHzo/5tZMzKu5mNuzY9nf974WAABAJ9bpn9kBAAA4E8IOAAAwGmEHAAAYjbADAACMRtgx0DPPPKOBAweqa9euSk5O1s6dO50+27aVlZUln8+nmJgYpaamat++fQHH19fXa86cOerdu7e6d++uCRMm6PPPPw8YU1VVpalTp8rtdsvtdmvq1Kn65ptvAsYcOnRI48ePV/fu3dW7d2898MADamhoaLfrNl1HzOvixYuVkpKibt26tfiLNh988EElJyfL5XLpyiuvDPZlnnN27Nih8ePHy+fzybIsvfzyywH9fGc7p++b15deekmjR49W7969ZVmWSktLm51jzZo1Sk1NVY8ePWRZVrP5kpjX1iLsGObFF19Uenq6Fi5cqJKSEl133XUaM2aMDh06JElatmyZli9frlWrVqmoqEher1ejRo1SbW2tc4709HRt2bJFmzdv1ltvvaUjR45o3LhxOnnypDNm8uTJKi0t1euvv67XX39dpaWlmjp1qtN/8uRJjR07VkePHtVbb72lzZs363e/+53mzp3bcT8Mg3TUvDY0NOiWW27RL37xixZrsW1bd999t2699db2u+BzyNGjRzV48GCtWrXqtP18Zzun75vXo0eP6tprr9WSJUtaPMexY8d000036f/9v//X4hjmtZVsGGXo0KH2zJkzA9ouueQSe8GCBXZTU5Pt9XrtJUuWOH3Hjx+33W63/eyzz9q2bdvffPONHRUVZW/evNkZ88UXX9hdunSxX3/9ddu2bfvDDz+0JdnvvPOOM2bXrl22JPujjz6ybdu2//CHP9hdunSxv/jiC2fMCy+8YLtcLru6ujr4F264jpjXb1u3bp3tdrvPWNOiRYvswYMHt/2i0Iwke8uWLc4+31kzfHdev62srMyWZJeUlLR4/JtvvmlLsquqqgLamdfWY2XHIA0NDSouLlZaWlpAe1pamgoLC1VWVia/3x/Q73K5NHz4cBUWFkqSiouL1djYGDDG5/MpKSnJGbNr1y653W4NGzbMGfPjH/9Ybrc7YExSUlLAH2cbPXq06uvrVVxcHPyLN1hHzSvCD99ZnAnz2nqEHYN8/fXXOnnyZLM/gOrxeOT3+50/ltpSvyT5/X5FR0erV69eZxzTp0+fZp/fp0+fgDHf/ZxevXopOjq62R9txZl11Lwi/PCdxZkwr61H2DGQZVkB+7ZtB7R9X//pfN852joGrdcR84rwxHcWLWFeW4ewY5DevXsrIiKiWVqvrKyUx+OR1+uVpBb7Jcnr9aqhoUFVVVVnHPPXv/612ed/9dVXAWO++zlVVVVqbGxs9n8ZOLOOmleEH76zOBPmtfUIOwaJjo5WcnKy8vPzA9rz8/OVkpKigQMHyuv1BvQ3NDSooKBAKSkpkqTk5GRFRUUFjKmoqNDevXudMddcc42qq6v13nvvOWPeffddVVdXB4zZu3evKioqnDF5eXlyuVxKTk4O/sUbrKPmFeGH7yzOhHn9AULxVDTaz+bNm+2oqCh77dq19ocffminp6fb3bt3tz/77DPbtm17yZIlttvttl966SX7gw8+sG+//Xa7b9++dk1NjXOOmTNn2v369bO3bt1q79mzx77xxhvtwYMH2ydOnHDG3HTTTfYVV1xh79q1y961a5c9aNAge9y4cU7/iRMn7KSkJHvEiBH2nj177K1bt9r9+vWzZ8+e3XE/DIN01LwePHjQLikpsR9//HH7vPPOs0tKSuySkhK7trbWGfPxxx/bJSUl9owZM+yLLrrIGVNfX99xPxCD1NbWOj9DSfby5cvtkpIS++DBg7Zt853trL5vXg8fPmyXlJTYr732mi3J3rx5s11SUmJXVFQ456ioqLBLSkrs5557zpZk79ixwy4pKbEPHz7sjGFeW4ewY6B///d/ty+44AI7Ojra/qd/+ie7oKDA6WtqarIXLVpke71e2+Vy2ddff739wQcfBBxfV1dnz549246Li7NjYmLscePG2YcOHQoYc/jwYXvKlCl2bGysHRsba0+ZMqXZa5EHDx60x44da8fExNhxcXH27Nmz7ePHj7fbdZuuI+Z12rRptqRm25tvvumMGT58+GnHlJWVteflG+vUa8Xf3aZNm2bbNt/Zzur75nXdunWn7V+0aJFzjkWLFp12zLp165wxzGvrWLZt2+29egQAABAqPLMDAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAOgQ6Smpio9Pb3F/gEDBig3N7dDavnss89kWZZKS0s75PMAhBZhB0BYKCoq0n333efsW5all19+udXHr1+/Xj179mzV2ISEBFVUVCgpKekHVgmgM4oMdQEAIEnnn39+h3xOQ0ODoqOj5fV6O+TzAIQeKzsAOsyJEyc0e/Zs9ezZU/Hx8Xr00Ud16s/zffs21oABAyRJ//Iv/yLLspz9P/3pT7rhhhsUGxurHj16KDk5Wbt379b27dt11113qbq6WpZlybIsZWVlOed68skndeedd8rtduvee+9tdhtr+/btsixL27Zt05AhQ9StWzelpKRo//79AfU/+eST6tOnj2JjY3XPPfdowYIFuvLKK53+7du3a+jQoerevbt69uypa6+9VgcPHmyvHyeAViLsAOgwGzZsUGRkpN599139+te/1ooVK/Qf//EfzcYVFRVJktatW6eKigpnf8qUKerXr5+KiopUXFysBQsWKCoqSikpKcrNzVWPHj1UUVGhiooKzZs3zznfU089paSkJBUXF+uxxx5rsb6FCxfq6aef1u7duxUZGam7777b6fuv//ovLV68WEuXLlVxcbH69++v1atXO/0nTpzQzTffrOHDh+v999/Xrl27dN9998myrLP+uQE4O9zGAtBhEhIStGLFClmWpYsvvlgffPCBVqxYoXvvvTdg3KlbWj179gy43XTo0CE9/PDDuuSSSyRJiYmJTp/b7ZZlWae9PXXjjTcGhJ/PPvvstPUtXrxYw4cPlyQtWLBAY8eO1fHjx9W1a1etXLlS06dP11133SVJ+uUvf6m8vDwdOXJEklRTU6Pq6mqNGzdO//AP/yBJuvTSS3/QzwdA+2BlB0CH+fGPfxyw0nHNNdfo448/1smTJ1t1fEZGhu655x6NHDlSS5Ys0aefftqq44YMGdKqcVdccYXz7759+0qSKisrJUn79+/X0KFDA8Z/ez8uLk533nmnRo8erfHjx+tXv/qVKioqWvW5ANoXYQdAp5GVlaV9+/Zp7Nix+uMf/6jLLrtMW7Zs+d7junfv3qrzR0VFOf8+FcqampqatZ1y6nmjU9atW6ddu3YpJSVFL774oi666CK98847rfpsAO2HsAOgw3z3P/zvvPOOEhMTFRER0WxsVFTUaVd8LrroIj300EPKy8vTxIkTtW7dOklSdHR0q1eI2uLiiy/We++9F9C2e/fuZuOuuuoqZWZmqrCwUElJSdq0aVO71QSgdQg7ADpMeXm5MjIytH//fr3wwgtauXKlHnzwwdOOHTBggLZt2ya/36+qqirV1dVp9uzZ2r59uw4ePKi3335bRUVFznMxAwYM0JEjR7Rt2zZ9/fXXOnbsWFBrnzNnjtauXasNGzbo448/1pNPPqn333/fWe0pKytTZmamdu3apYMHDyovL08HDhzguR0gDPCAMoAOc8cdd6iurk5Dhw5VRESE5syZE/CLBL/t6aefVkZGhp577jn96Ec/0oEDB3T48GHdcccd+utf/6revXtr4sSJevzxxyVJKSkpmjlzpm699VYdPnxYixYtcl4/D4YpU6boL3/5i+bNm6fjx49r0qRJuvPOO53Vnm7duumjjz7Shg0bdPjwYfXt21ezZ8/WjBkzglYDgLax7O/edAYAtMqoUaPk9Xr1/PPPh7oUAGfAyg4AtMKxY8f07LPPavTo0YqIiNALL7ygrVu3Kj8/P9SlAfgerOwAQCvU1dVp/Pjx2rNnj+rr63XxxRfr0Ucf1cSJE0NdGoDvQdgBAABG420sAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBo/x/qNHUkUX6pGAAAAABJRU5ErkJggg==", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGwCAYAAABPSaTdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAozklEQVR4nO3df1TVdZ7H8ddXflyRAMUfXElK2qVswnQW05GmJEvM8cfOeiYr3bLRSse0iBqVsRI7Cepsyg5sOraNsrVme3ayabdZF3QUM3RExErb1JkhpeIOZQioCAjf/aPj93RD0uDCvXx6Ps6553i/38+9vC/fsKff+wUs27ZtAQAAGKqHvwcAAADoTMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIwW7O8BAkFLS4s+/fRTRUREyLIsf48DAAAug23bqqurU2xsrHr0aPv8DbEj6dNPP1VcXJy/xwAAAO1QUVGhQYMGtbmf2JEUEREh6ctPVmRkpJ+nAQAAl6O2tlZxcXHO/8fbQuxIzltXkZGRxA4AAN3MpS5B4QJlAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGC/b3AKYbvPgtf4/wnfXRion+HgHdFF+3/sPXLToDZ3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYLRgfw8AdFeDF7/l7xG+sz5aMdHfIwDoRjizAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACj+TV2du3apcmTJys2NlaWZemNN95w9jU1NWnRokUaOnSowsPDFRsbq/vvv1+ffvqp13M0NDRowYIF6tevn8LDwzVlyhR9/PHHXfxKAABAoPJr7Jw5c0bDhg1TXl5eq31nz57VgQMH9PTTT+vAgQN6/fXXdfToUU2ZMsVrXVpamrZs2aLNmzdr9+7dOn36tCZNmqTm5uauehkAACCA+fWHCk6YMEETJky46L6oqCgVFhZ6bcvNzdXIkSN14sQJXXXVVaqpqdFLL72kl19+WXfccYck6ZVXXlFcXJy2bdum8ePHX/S5Gxoa1NDQ4Nyvra310SsCAACBpltds1NTUyPLstS7d29JUmlpqZqampSamuqsiY2NVWJiooqLi9t8nuzsbEVFRTm3uLi4zh4dAAD4SbeJnXPnzmnx4sWaPn26IiMjJUkej0ehoaHq06eP19qYmBh5PJ42nysjI0M1NTXOraKiolNnBwAA/tMtfjdWU1OT7rnnHrW0tOiFF1645HrbtmVZVpv7XS6XXC6XL0cEAAABKuDP7DQ1NWnatGkqLy9XYWGhc1ZHktxutxobG1VdXe31mKqqKsXExHT1qAAAIAAFdOxcCJ1jx45p27Zt6tu3r9f+pKQkhYSEeF3IXFlZqUOHDik5ObmrxwUAAAHIr29jnT59Wn/605+c++Xl5Tp48KCio6MVGxurn/zkJzpw4ID++7//W83Nzc51ONHR0QoNDVVUVJRmz56tJ554Qn379lV0dLSefPJJDR061PnuLAAA8N3m19jZv3+/brvtNud+enq6JGnmzJnKzMzUm2++KUkaPny41+N27NihlJQUSdKaNWsUHBysadOmqb6+Xrfffrs2btyooKCgLnkNAAAgsPk1dlJSUmTbdpv7v2nfBT179lRubq5yc3N9ORoAADBEQF+zAwAA0FHEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjdYvfeg4AQEcNXvyWv0f4zvpoxUS/fnzO7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACj+TV2du3apcmTJys2NlaWZemNN97w2m/btjIzMxUbG6uwsDClpKTo8OHDXmsaGhq0YMEC9evXT+Hh4ZoyZYo+/vjjLnwVAAAgkPk1ds6cOaNhw4YpLy/vovtXrVql1atXKy8vTyUlJXK73Ro3bpzq6uqcNWlpadqyZYs2b96s3bt36/Tp05o0aZKam5u76mUAAIAAFuzPDz5hwgRNmDDhovts21ZOTo6WLFmiqVOnSpLy8/MVExOjTZs2ac6cOaqpqdFLL72kl19+WXfccYck6ZVXXlFcXJy2bdum8ePHd9lrAQAAgSlgr9kpLy+Xx+NRamqqs83lcmnMmDEqLi6WJJWWlqqpqclrTWxsrBITE501F9PQ0KDa2lqvGwAAMFPAxo7H45EkxcTEeG2PiYlx9nk8HoWGhqpPnz5trrmY7OxsRUVFObe4uDgfTw8AAAJFwMbOBZZled23bbvVtq+71JqMjAzV1NQ4t4qKCp/MCgAAAk/Axo7b7ZakVmdoqqqqnLM9brdbjY2Nqq6ubnPNxbhcLkVGRnrdAACAmQI2duLj4+V2u1VYWOhsa2xsVFFRkZKTkyVJSUlJCgkJ8VpTWVmpQ4cOOWsAAMB3m1+/G+v06dP605/+5NwvLy/XwYMHFR0drauuukppaWnKyspSQkKCEhISlJWVpV69emn69OmSpKioKM2ePVtPPPGE+vbtq+joaD355JMaOnSo891ZAADgu82vsbN//37ddtttzv309HRJ0syZM7Vx40YtXLhQ9fX1mjdvnqqrqzVq1CgVFBQoIiLCecyaNWsUHBysadOmqb6+Xrfffrs2btyooKCgLn89AAAg8Pg1dlJSUmTbdpv7LctSZmamMjMz21zTs2dP5ebmKjc3txMmBAAA3V3AXrMDAADgC8QOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwAAwGjEDgAAMBqxAwAAjEbsAAAAoxE7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIwW0LFz/vx5PfXUU4qPj1dYWJiuueYaPfvss2ppaXHW2LatzMxMxcbGKiwsTCkpKTp8+LAfpwYAAIEkoGNn5cqVWrdunfLy8vR///d/WrVqlX75y18qNzfXWbNq1SqtXr1aeXl5Kikpkdvt1rhx41RXV+fHyQEAQKAI6NjZs2eP/v7v/14TJ07U4MGD9ZOf/ESpqanav3+/pC/P6uTk5GjJkiWaOnWqEhMTlZ+fr7Nnz2rTpk1+nh4AAASCgI6dH/7wh9q+fbuOHj0qSXr33Xe1e/du/ehHP5IklZeXy+PxKDU11XmMy+XSmDFjVFxc3ObzNjQ0qLa21usGAADMFOzvAb7JokWLVFNToyFDhigoKEjNzc1avny57r33XkmSx+ORJMXExHg9LiYmRsePH2/zebOzs7Vs2bLOGxwAAASMgD6z89prr+mVV17Rpk2bdODAAeXn5+uf/umflJ+f77XOsiyv+7Ztt9r2VRkZGaqpqXFuFRUVnTI/AADwv4A+s/Pzn/9cixcv1j333CNJGjp0qI4fP67s7GzNnDlTbrdb0pdneAYOHOg8rqqqqtXZnq9yuVxyuVydOzwAAAgIAX1m5+zZs+rRw3vEoKAg51vP4+Pj5Xa7VVhY6OxvbGxUUVGRkpOTu3RWAAAQmAL6zM7kyZO1fPlyXXXVVbrhhhtUVlam1atXa9asWZK+fPsqLS1NWVlZSkhIUEJCgrKystSrVy9Nnz7dz9MDAIBAENCxk5ubq6efflrz5s1TVVWVYmNjNWfOHD3zzDPOmoULF6q+vl7z5s1TdXW1Ro0apYKCAkVERPhxcgAAECgCOnYiIiKUk5OjnJycNtdYlqXMzExlZmZ22VwAAKD7COhrdgAAADqK2AEAAEYjdgAAgNHaFTsVFRX6+OOPnfv79u1TWlqa1q9f77PBAAAAfKFdsTN9+nTt2LFD0pc/0G/cuHHat2+ffvGLX+jZZ5/16YAAAAAd0a7YOXTokEaOHClJ+o//+A8lJiaquLhYmzZt0saNG305HwAAQIe0K3aampqcX7ewbds2TZkyRZI0ZMgQVVZW+m46AACADmpX7Nxwww1at26d3n77bRUWFurOO++UJH366afq27evTwcEAADoiHbFzsqVK/XrX/9aKSkpuvfeezVs2DBJ0ptvvum8vQUAABAI2vUTlFNSUvT555+rtrZWffr0cbY//PDDCg8P99lwAAAAHdWuMztjx45VXV2dV+hIUnR0tO6++26fDAYAAOAL7YqdnTt3qrGxsdX2c+fO6e233+7wUAAAAL7yrd7Geu+995w/f/DBB/J4PM795uZmbd26VVdeeaXvpgMAAOigbxU7w4cPl2VZsixLY8eObbU/LCxMubm5PhsOAACgo75V7JSXl8u2bV1zzTXat2+f+vfv7+wLDQ3VgAEDFBQU5PMhAQAA2utbxc7VV18tSWppaemUYQAAAHytXd96LklHjx7Vzp07VVVV1Sp+nnnmmQ4PBgAA4Avtip0XX3xRP/vZz9SvXz+53W5ZluXssyyL2AEAAAGjXbHz3HPPafny5Vq0aJGv5wEAAPCpdv2cnerqat11112+ngUAAMDn2hU7d911lwoKCnw9CwAAgM+1622sv/3bv9XTTz+tvXv3aujQoQoJCfHa/+ijj/pkOAAAgI5qV+ysX79eV1xxhYqKilRUVOS1z7IsYgcAAASMdsVOeXm5r+cAAADoFO26ZgcAAKC7aNeZnVmzZn3j/t/85jftGgYAAMDX2hU71dXVXvebmpp06NAhnTp16qK/IBQAAMBf2hU7W7ZsabWtpaVF8+bN0zXXXNPhoQAAAHzFZ9fs9OjRQ48//rjWrFnjq6cEAADoMJ9eoPznP/9Z58+f9+VTAgAAdEi73sZKT0/3um/btiorK/XWW29p5syZPhkMAADAF9oVO2VlZV73e/Toof79++v555+/5HdqAQAAdKV2xc6OHTt8PQcAAECnaFfsXPDZZ5/pyJEjsixL1157rfr37++ruQAAAHyiXRconzlzRrNmzdLAgQN166236pZbblFsbKxmz56ts2fP+npGAACAdmtX7KSnp6uoqEj/9V//pVOnTunUqVP63e9+p6KiIj3xxBO+nhEAAKDd2vU21m9/+1v953/+p1JSUpxtP/rRjxQWFqZp06Zp7dq1vpoPAACgQ9p1Zufs2bOKiYlptX3AgAG8jQUAAAJKu2Jn9OjRWrp0qc6dO+dsq6+v17JlyzR69GifDQcAANBR7XobKycnRxMmTNCgQYM0bNgwWZalgwcPyuVyqaCgwNczAgAAtFu7Ymfo0KE6duyYXnnlFX344YeybVv33HOPZsyYobCwMF/PCAAA0G7tip3s7GzFxMTooYce8tr+m9/8Rp999pkWLVrkk+EAAAA6ql3X7Pz617/WkCFDWm2/4YYbtG7dug4PBQAA4Cvtih2Px6OBAwe22t6/f39VVlZ2eKiv+uSTT/SP//iP6tu3r3r16qXhw4ertLTU2W/btjIzMxUbG6uwsDClpKTo8OHDPp0BAAB0X+2Knbi4OL3zzjuttr/zzjuKjY3t8FAXVFdX6+abb1ZISIj+53/+Rx988IGef/559e7d21mzatUqrV69Wnl5eSopKZHb7da4ceNUV1fnszkAAED31a5rdh588EGlpaWpqalJY8eOlSRt375dCxcu9OlPUF65cqXi4uK0YcMGZ9vgwYOdP9u2rZycHC1ZskRTp06VJOXn5ysmJkabNm3SnDlzfDYLAADontoVOwsXLtQXX3yhefPmqbGxUZLUs2dPLVq0SBkZGT4b7s0339T48eN11113qaioSFdeeaXmzZvnXBhdXl4uj8ej1NRU5zEul0tjxoxRcXFxm7HT0NCghoYG535tba3PZgYAAIGlXW9jWZallStX6rPPPtPevXv17rvv6osvvtAzzzzj0+H+8pe/aO3atUpISND//u//au7cuXr00Uf1b//2b5K+vHZIUquf5hwTE+Psu5js7GxFRUU5t7i4OJ/ODQAAAke7zuxccMUVV+imm27y1SyttLS0aMSIEcrKypIkff/739fhw4e1du1a3X///c46y7K8HmfbdqttX5WRkaH09HTnfm1tLcEDAICh2nVmp6sMHDhQ3/ve97y2XX/99Tpx4oQkye12S1KrszhVVVUX/d1dF7hcLkVGRnrdAACAmQI6dm6++WYdOXLEa9vRo0d19dVXS5Li4+PldrtVWFjo7G9sbFRRUZGSk5O7dFYAABCYOvQ2Vmd7/PHHlZycrKysLE2bNk379u3T+vXrtX79eklfvn2VlpamrKwsJSQkKCEhQVlZWerVq5emT5/u5+kBAEAgCOjYuemmm7RlyxZlZGTo2WefVXx8vHJycjRjxgxnzcKFC1VfX6958+apurpao0aNUkFBgSIiIvw4OQAACBQBHTuSNGnSJE2aNKnN/ZZlKTMzU5mZmV03FAAA6DYC+podAACAjiJ2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGC0bhU72dnZsixLaWlpzjbbtpWZmanY2FiFhYUpJSVFhw8f9t+QAAAgoHSb2CkpKdH69et14403em1ftWqVVq9erby8PJWUlMjtdmvcuHGqq6vz06QAACCQdIvYOX36tGbMmKEXX3xRffr0cbbbtq2cnBwtWbJEU6dOVWJiovLz83X27Flt2rSpzedraGhQbW2t1w0AAJipW8TOI488ookTJ+qOO+7w2l5eXi6Px6PU1FRnm8vl0pgxY1RcXNzm82VnZysqKsq5xcXFddrsAADAvwI+djZv3qwDBw4oOzu71T6PxyNJiomJ8doeExPj7LuYjIwM1dTUOLeKigrfDg0AAAJGsL8H+CYVFRV67LHHVFBQoJ49e7a5zrIsr/u2bbfa9lUul0sul8tncwIAgMAV0Gd2SktLVVVVpaSkJAUHBys4OFhFRUX61a9+peDgYOeMztfP4lRVVbU62wMAAL6bAjp2br/9dr3//vs6ePCgcxsxYoRmzJihgwcP6pprrpHb7VZhYaHzmMbGRhUVFSk5OdmPkwMAgEAR0G9jRUREKDEx0WtbeHi4+vbt62xPS0tTVlaWEhISlJCQoKysLPXq1UvTp0/3x8gAACDABHTsXI6FCxeqvr5e8+bNU3V1tUaNGqWCggJFRET4ezQAABAAul3s7Ny50+u+ZVnKzMxUZmamX+YBAACBLaCv2QEAAOgoYgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGC2gYyc7O1s33XSTIiIiNGDAAP34xz/WkSNHvNbYtq3MzEzFxsYqLCxMKSkpOnz4sJ8mBgAAgSagY6eoqEiPPPKI9u7dq8LCQp0/f16pqak6c+aMs2bVqlVavXq18vLyVFJSIrfbrXHjxqmurs6PkwMAgEAR7O8BvsnWrVu97m/YsEEDBgxQaWmpbr31Vtm2rZycHC1ZskRTp06VJOXn5ysmJkabNm3SnDlz/DE2AAAIIAF9ZuframpqJEnR0dGSpPLycnk8HqWmpjprXC6XxowZo+Li4jafp6GhQbW1tV43AABgpm4TO7ZtKz09XT/84Q+VmJgoSfJ4PJKkmJgYr7UxMTHOvovJzs5WVFSUc4uLi+u8wQEAgF91m9iZP3++3nvvPb366qut9lmW5XXftu1W274qIyNDNTU1zq2iosLn8wIAgMAQ0NfsXLBgwQK9+eab2rVrlwYNGuRsd7vdkr48wzNw4EBne1VVVauzPV/lcrnkcrk6b2AAABAwAvrMjm3bmj9/vl5//XX94Q9/UHx8vNf++Ph4ud1uFRYWOtsaGxtVVFSk5OTkrh4XAAAEoIA+s/PII49o06ZN+t3vfqeIiAjnOpyoqCiFhYXJsiylpaUpKytLCQkJSkhIUFZWlnr16qXp06f7eXoAABAIAjp21q5dK0lKSUnx2r5hwwY98MADkqSFCxeqvr5e8+bNU3V1tUaNGqWCggJFRER08bQAACAQBXTs2LZ9yTWWZSkzM1OZmZmdPxAAAOh2AvqaHQAAgI4idgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGIHAAAYjdgBAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGI3YAQAARiN2AACA0YgdAABgNGNi54UXXlB8fLx69uyppKQkvf322/4eCQAABAAjYue1115TWlqalixZorKyMt1yyy2aMGGCTpw44e/RAACAnxkRO6tXr9bs2bP14IMP6vrrr1dOTo7i4uK0du1af48GAAD8LNjfA3RUY2OjSktLtXjxYq/tqampKi4uvuhjGhoa1NDQ4NyvqamRJNXW1vp8vpaGsz5/TlyezjieX8Wx9R+Orbk689hyXP2ns47rhee1bfsb13X72Pn888/V3NysmJgYr+0xMTHyeDwXfUx2draWLVvWantcXFynzAj/iMrx9wToLBxbc3FszdTZx7Wurk5RUVFt7u/2sXOBZVle923bbrXtgoyMDKWnpzv3W1pa9MUXX6hv375tPua7qLa2VnFxcaqoqFBkZKS/x4EPcWzNxHE1F8f24mzbVl1dnWJjY79xXbePnX79+ikoKKjVWZyqqqpWZ3sucLlccrlcXtt69+7dWSN2e5GRkXxxGYpjayaOq7k4tq190xmdC7r9BcqhoaFKSkpSYWGh1/bCwkIlJyf7aSoAABAouv2ZHUlKT0/XfffdpxEjRmj06NFav369Tpw4oblz5/p7NAAA4GdGxM7dd9+tkydP6tlnn1VlZaUSExP1+9//XldffbW/R+vWXC6Xli5d2uotP3R/HFszcVzNxbHtGMu+1PdrAQAAdGPd/podAACAb0LsAAAAoxE7AADAaMQOAAAwGrFjkF27dmny5MmKjY2VZVl64403vPa//vrrGj9+vPr16yfLsnTw4MFWz7F+/XqlpKQoMjJSlmXp1KlTrdZUV1frvvvuU1RUlKKionTfffe1WnfixAlNnjxZ4eHh6tevnx599FE1Njb67sV+B73wwguKj49Xz549lZSUpLffftvZZ9u2MjMzFRsbq7CwMKWkpOjw4cNej29oaNCCBQvUr18/hYeHa8qUKfr444+91ixfvlzJycnq1atXmz9o87HHHlNSUpJcLpeGDx/u65f5ndQVx5av2651qb+POa5di9gxyJkzZzRs2DDl5eW1uf/mm2/WihUr2nyOs2fP6s4779QvfvGLNtdMnz5dBw8e1NatW7V161YdPHhQ9913n7O/ublZEydO1JkzZ7R7925t3rxZv/3tb/XEE0+0/8V9x7322mtKS0vTkiVLVFZWpltuuUUTJkzQiRMnJEmrVq3S6tWrlZeXp5KSErndbo0bN051dXXOc6SlpWnLli3avHmzdu/erdOnT2vSpElqbm521jQ2Nuquu+7Sz372szZnsW1bs2bN0t133915L/g7pKuOLV+3XetSfx9zXLuYDSNJsrds2XLRfeXl5bYku6ysrM3H79ixw5ZkV1dXe23/4IMPbEn23r17nW179uyxJdkffvihbdu2/fvf/97u0aOH/cknnzhrXn31Vdvlctk1NTXtfk3fZSNHjrTnzp3rtW3IkCH24sWL7ZaWFtvtdtsrVqxw9p07d86Oioqy161bZ9u2bZ86dcoOCQmxN2/e7Kz55JNP7B49ethbt25t9fE2bNhgR0VFfeNMS5cutYcNG9b+FwXbtrvm2PJ1619f//uY49r1OLODb2XPnj2KiorSqFGjnG0/+MEPFBUVpeLiYmdNYmKi1y9mGz9+vBoaGlRaWtrlM3d3jY2NKi0tVWpqqtf21NRUFRcXq7y8XB6Px2u/y+XSmDFjnGNSWlqqpqYmrzWxsbFKTEx01qDrddWx5es2sHBcux6xg2/F4/FowIABrbYPGDDA+WWsHo+n1S9h7dOnj0JDQ1v9wlZc2ueff67m5uZWn9OYmBh5PB7nc9rWfunLYxIaGqo+ffq0uQZdr6uOLV+3gYXj2vWIHXxrlmW12mbbttf2y1mDb+frn7tLfc4v5/PNMQkMXXFs+boNPBzXrkPs4Ftxu93661//2mr7Z5995vzrwe12t/oXQ3V1tZqamlr9CwOX1q9fPwUFBbX6nFZVVSkmJkZut1uS2twvfXlMGhsbVV1d3eYadL2uOrZ83QYWjmvXI3bwrYwePVo1NTXat2+fs+2Pf/yjampqlJyc7Kw5dOiQKisrnTUFBQVyuVxKSkrq8pm7u9DQUCUlJamwsNBre2FhoZKTkxUfHy+32+21v7GxUUVFRc4xSUpKUkhIiNeayspKHTp0yFmDrtdVx5av28DCcfUDf1wVjc5RV1dnl5WV2WVlZbYke/Xq1XZZWZl9/Phx27Zt++TJk3ZZWZn91ltv2ZLszZs322VlZXZlZaXzHJWVlXZZWZn94osv2pLsXbt22WVlZfbJkyedNXfeead944032nv27LH37NljDx061J40aZKz//z583ZiYqJ9++232wcOHLC3bdtmDxo0yJ4/f37XfTIMs3nzZjskJMR+6aWX7A8++MBOS0uzw8PD7Y8++si2bdtesWKFHRUVZb/++uv2+++/b9977732wIED7draWuc55s6daw8aNMjetm2bfeDAAXvs2LH2sGHD7PPnzztrjh8/bpeVldnLli2zr7jiCue/p7q6OmfNsWPH7LKyMnvOnDn2tdde66xpaGjouk+IQbrq2PJ127Uu9fcxx7VrETsGufDt4l+/zZw507btL7+d+GL7ly5d6jzH0qVLL7pmw4YNzpqTJ0/aM2bMsCMiIuyIiAh7xowZrb5F/fjx4/bEiRPtsLAwOzo62p4/f7597ty5zv8kGOxf/uVf7KuvvtoODQ21/+7v/s4uKipy9rW0tNhLly613W637XK57FtvvdV+//33vR5fX19vz58/346OjrbDwsLsSZMm2SdOnPBaM3PmzIse/x07djhrxowZc9E15eXlnfnyjdYVx5av2651qb+POa5dy7Jt2+7ss0cAAAD+wjU7AADAaMQOAAAwGrEDAACMRuwAAACjETsAAMBoxA4AADAasQMAAIxG7AAAAKMROwC6REpKitLS0trcP3jwYOXk5HTJLB999JEsy9LBgwe75OMB8C9iB0BAKCkp0cMPP+zctyxLb7zxxmU/fuPGjerdu/dlrY2Li1NlZaUSExO/5ZQAuqNgfw8AAJLUv3//Lvk4jY2NCg0Nldvt7pKPB8D/OLMDoMucP39e8+fPV+/evdW3b1899dRTuvDr+b76NtbgwYMlSf/wD/8gy7Kc+++++65uu+02RUREKDIyUklJSdq/f7927typn/70p6qpqZFlWbIsS5mZmc5zPffcc3rggQcUFRWlhx56qNXbWDt37pRlWdq+fbtGjBihXr16KTk5WUeOHPGa/7nnntOAAQMUERGhBx98UIsXL9bw4cOd/Tt37tTIkSMVHh6u3r176+abb9bx48c769MJ4DIROwC6TH5+voKDg/XHP/5Rv/rVr7RmzRr967/+a6t1JSUlkqQNGzaosrLSuT9jxgwNGjRIJSUlKi0t1eLFixUSEqLk5GTl5OQoMjJSlZWVqqys1JNPPuk83y9/+UslJiaqtLRUTz/9dJvzLVmyRM8//7z279+v4OBgzZo1y9n37//+71q+fLlWrlyp0tJSXXXVVVq7dq2z//z58/rxj3+sMWPG6L333tOePXv08MMPy7KsDn/eAHQMb2MB6DJxcXFas2aNLMvSddddp/fff19r1qzRQw895LXuwltavXv39nq76cSJE/r5z3+uIUOGSJISEhKcfVFRUbIs66JvT40dO9Yrfj766KOLzrd8+XKNGTNGkrR48WJNnDhR586dU8+ePZWbm6vZs2frpz/9qSTpmWeeUUFBgU6fPi1Jqq2tVU1NjSZNmqS/+Zu/kSRdf/313+rzA6BzcGYHQJf5wQ9+4HWmY/To0Tp27Jiam5sv6/Hp6el68MEHdccdd2jFihX685//fFmPGzFixGWtu/HGG50/Dxw4UJJUVVUlSTpy5IhGjhzptf6r96Ojo/XAAw9o/Pjxmjx5sv75n/9ZlZWVl/VxAXQuYgdAt5GZmanDhw9r4sSJ+sMf/qDvfe972rJlyyUfFx4eflnPHxIS4vz5QpS1tLS02nbBheuNLtiwYYP27Nmj5ORkvfbaa7r22mu1d+/ey/rYADoPsQOgy3z9f/x79+5VQkKCgoKCWq0NCQm56Bmfa6+9Vo8//rgKCgo0depUbdiwQZIUGhp62WeI2uO6667Tvn37vLbt37+/1brvf//7ysjIUHFxsRITE7Vp06ZOmwnA5SF2AHSZiooKpaen68iRI3r11VeVm5urxx577KJrBw8erO3bt8vj8ai6ulr19fWaP3++du7cqePHj+udd95RSUmJc13M4MGDdfr0aW3fvl2ff/65zp4969PZFyxYoJdeekn5+fk6duyYnnvuOb333nvO2Z7y8nJlZGRoz549On78uAoKCnT06FGu2wECABcoA+gy999/v+rr6zVy5EgFBQVpwYIFXj9I8Kuef/55paen68UXX9SVV16po0eP6uTJk7r//vv117/+Vf369dPUqVO1bNkySVJycrLmzp2ru+++WydPntTSpUudbz/3hRkzZugvf/mLnnzySZ07d07Tpk3TAw884Jzt6dWrlz788EPl5+fr5MmTGjhwoObPn685c+b4bAYA7WPZX3/TGQBwWcaNGye3262XX37Z36MA+Aac2QGAy3D27FmtW7dO48ePV1BQkF599VVt27ZNhYWF/h4NwCVwZgcALkN9fb0mT56sAwcOqKGhQdddd52eeuopTZ061d+jAbgEYgcAABiN78YCAABGI3YAAIDRiB0AAGA0YgcAABiN2AEAAEYjdgAAgNGIHQAAYDRiBwAAGO3/ASAmhWlZTD/rAAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -248,7 +250,7 @@ "id": "2619f38d", "metadata": {}, "source": [ - "In this notebook, you have learned how do use AutoQASM to express classical control flow in your quantum program. AutoQASM not only enables writing program with real-time control flow based on measurement results, but also increases the readability by taking advantage of high level programming features such as for-loops and subroutines." + "In this notebook, you have learned how to use AutoQASM to express classical control flow in your quantum program. AutoQASM not only enables writing program with real-time control flow based on measurement results, but also increases the readability by taking advantage of high level programming features such as for-loops and subroutines." ] } ], diff --git a/examples/3_1_Iterative_phase_estimation.ipynb b/examples/3_1_Iterative_phase_estimation.ipynb index e0baa66c..b95a9141 100644 --- a/examples/3_1_Iterative_phase_estimation.ipynb +++ b/examples/3_1_Iterative_phase_estimation.ipynb @@ -25,6 +25,8 @@ "\n", "# AWS imports: Import Braket SDK modules\n", "from braket.devices.local_simulator import LocalSimulator\n", + "\n", + "# AutoQASM imports\n", "import autoqasm as aq\n", "from autoqasm.instructions import measure, x, rz, h, cphaseshift, reset" ] diff --git a/examples/3_2_magic_state_distillation.ipynb b/examples/3_2_magic_state_distillation.ipynb index 59ffd9b0..8482a8e3 100644 --- a/examples/3_2_magic_state_distillation.ipynb +++ b/examples/3_2_magic_state_distillation.ipynb @@ -38,6 +38,8 @@ "\n", "# AWS imports: Import Braket SDK modules\n", "from braket.devices.local_simulator import LocalSimulator\n", + "\n", + "# AutoQASM imports\n", "import autoqasm as aq\n", "import autoqasm.instructions as ins" ] diff --git a/examples/4_Native_programming.ipynb b/examples/4_Native_programming.ipynb index f00e5f52..44280e71 100644 --- a/examples/4_Native_programming.ipynb +++ b/examples/4_Native_programming.ipynb @@ -24,6 +24,8 @@ "\n", "# AWS imports: Import Braket SDK modules\n", "from braket.devices import Devices\n", + "\n", + "# AutoQASM imports\n", "import autoqasm as aq" ] }, @@ -199,7 +201,6 @@ ".output_html .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n", ".output_html .gd { color: #A00000 } /* Generic.Deleted */\n", ".output_html .ge { font-style: italic } /* Generic.Emph */\n", - ".output_html .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */\n", ".output_html .gr { color: #E40000 } /* Generic.Error */\n", ".output_html .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n", ".output_html .gi { color: #008400 } /* Generic.Inserted */\n", @@ -296,8 +297,8 @@ "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", "\\PY{k+kn}{import} \\PY{n+nn}{numpy} \\PY{k}{as} \\PY{n+nn}{np}\n", "\n", - "\\PY{k+kn}{import} \\PY{n+nn}{braket}\\PY{n+nn}{.}\\PY{n+nn}{experimental}\\PY{n+nn}{.}\\PY{n+nn}{autoqasm} \\PY{k}{as} \\PY{n+nn}{aq}\n", - "\\PY{k+kn}{from} \\PY{n+nn}{braket}\\PY{n+nn}{.}\\PY{n+nn}{experimental}\\PY{n+nn}{.}\\PY{n+nn}{autoqasm}\\PY{n+nn}{.}\\PY{n+nn}{instructions} \\PY{k+kn}{import} \\PY{n}{gpi}\\PY{p}{,} \\PY{n}{gpi2}\\PY{p}{,} \\PY{n}{ms}\n", + "\\PY{k+kn}{import} \\PY{n+nn}{autoqasm} \\PY{k}{as} \\PY{n+nn}{aq}\n", + "\\PY{k+kn}{from} \\PY{n+nn}{autoqasm}\\PY{n+nn}{.}\\PY{n+nn}{instructions} \\PY{k+kn}{import} \\PY{n}{gpi}\\PY{p}{,} \\PY{n}{gpi2}\\PY{p}{,} \\PY{n}{ms}\n", "\n", "\n", "\\PY{n+nd}{@aq}\\PY{o}{.}\\PY{n}{gate}\n", diff --git a/examples/5_Pulse_programming_and_dynamical_decoupling.ipynb b/examples/5_Pulse_programming_and_dynamical_decoupling.ipynb index 01a720ec..a974fd38 100644 --- a/examples/5_Pulse_programming_and_dynamical_decoupling.ipynb +++ b/examples/5_Pulse_programming_and_dynamical_decoupling.ipynb @@ -33,15 +33,26 @@ "metadata": {}, "outputs": [], "source": [ + "# general imports\n", "import numpy as np\n", "\n", - "import autoqasm as aq\n", - "from autoqasm import pulse\n", - "from autoqasm.instructions import rx, rz\n", - "\n", + "# AWS imports: Import Braket SDK modules\n", "from braket.aws import AwsDevice\n", "from braket.devices import Devices\n", "\n", + "# AutoQASM imports\n", + "import autoqasm as aq\n", + "from autoqasm import pulse\n", + "from autoqasm.instructions import rx, rz" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "47e8f99a-87af-433d-90bf-d603a7d28175", + "metadata": {}, + "outputs": [], + "source": [ "device = AwsDevice(Devices.Rigetti.AspenM3)" ] }, @@ -64,7 +75,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "8ebd3454", "metadata": {}, "outputs": [], @@ -89,7 +100,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "3e76fdca", "metadata": {}, "outputs": [ @@ -144,7 +155,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "9130f6b8", "metadata": {}, "outputs": [], @@ -178,7 +189,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "464ee2ed", "metadata": {}, "outputs": [], @@ -216,7 +227,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "e117caf0", "metadata": {}, "outputs": [ @@ -283,7 +294,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.9.18" } }, "nbformat": 4, diff --git a/examples/6_Customize_gate_calibrations.ipynb b/examples/6_Customize_gate_calibrations.ipynb index 6e4edc41..23de66e0 100644 --- a/examples/6_Customize_gate_calibrations.ipynb +++ b/examples/6_Customize_gate_calibrations.ipynb @@ -19,17 +19,28 @@ "metadata": {}, "outputs": [], "source": [ + "# general imports\n", "import numpy as np\n", "\n", - "import autoqasm as aq\n", - "from autoqasm import pulse\n", - "from autoqasm.instructions import rx, rz, measure\n", - "\n", + "# AWS imports: Import Braket SDK modules\n", "from braket.aws import AwsDevice\n", "from braket.devices import Devices\n", "from braket.circuits import Gate, QubitSet, FreeParameter\n", "from braket.pulse import DragGaussianWaveform\n", "\n", + "# AutoQASM imports\n", + "import autoqasm as aq\n", + "from autoqasm import pulse\n", + "from autoqasm.instructions import rx, rz, measure" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1dda4463-2dbb-40e1-a90c-66f1278ce5cb", + "metadata": {}, + "outputs": [], + "source": [ "device = AwsDevice(Devices.Rigetti.AspenM3)" ] }, @@ -51,7 +62,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "f1e5a16f", "metadata": {}, "outputs": [], @@ -69,7 +80,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "3248ed04", "metadata": {}, "outputs": [ @@ -105,7 +116,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "81bb487f", "metadata": {}, "outputs": [ @@ -115,11 +126,12 @@ "text": [ "OPENQASM 3.0;\n", "cal {\n", + " input float theta;\n", " barrier $1;\n", - " shift_phase(q1_rf_frame, -1.0*theta);\n", - " shift_phase(q0_q1_xy_frame, 0.5*theta);\n", - " shift_phase(q1_q2_xy_frame, 0.5*theta);\n", - " shift_phase(q1_q16_xy_frame, 0.5*theta);\n", + " shift_phase(q1_rf_frame, -1.0 * theta);\n", + " shift_phase(q0_q1_xy_frame, 0.5 * theta);\n", + " shift_phase(q1_q2_xy_frame, 0.5 * theta);\n", + " shift_phase(q1_q16_xy_frame, 0.5 * theta);\n", " barrier $1;\n", "}\n" ] @@ -148,7 +160,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "63e15583", "metadata": {}, "outputs": [ @@ -158,7 +170,7 @@ "text": [ "Help on function rx in module autoqasm.instructions.gates:\n", "\n", - "rx(target: Union[int, str, braket.registers.qubit.Qubit, oqpy.classical_types._ClassicalVar, oqpy.base.OQPyExpression, oqpy.quantum_types.Qubit], angle: Union[float, braket.parametric.free_parameter_expression.FreeParameterExpression, oqpy.classical_types._ClassicalVar]) -> None\n", + "rx(target: Union[int, str, braket.registers.qubit.Qubit, oqpy.classical_types._ClassicalVar, oqpy.base.OQPyExpression, oqpy.quantum_types.Qubit], angle: Union[float, braket.parametric.free_parameter_expression.FreeParameterExpression, oqpy.classical_types._ClassicalVar], **kwargs) -> None\n", " X-axis rotation gate.\n", " \n", " Args:\n", @@ -182,7 +194,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "64ebd3a2", "metadata": {}, "outputs": [], @@ -211,7 +223,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "bae7731a", "metadata": {}, "outputs": [ @@ -249,7 +261,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "6b7cc158", "metadata": {}, "outputs": [ @@ -290,7 +302,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "49d9155b", "metadata": {}, "outputs": [], @@ -314,7 +326,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "id": "a40c95c7", "metadata": {}, "outputs": [ @@ -384,7 +396,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.9.18" } }, "nbformat": 4, From 7e2a770636e9d35abc8449ed301c193d225adcaa Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 7 May 2024 14:39:39 -0400 Subject: [PATCH 1163/1165] Lint example notebooks --- .../2_Expressing_classical_control_flow.ipynb | 2 +- examples/3_1_Iterative_phase_estimation.ipynb | 1 + examples/3_2_magic_state_distillation.ipynb | 21 ++++++++++--------- examples/4_Native_programming.ipynb | 1 + ...programming_and_dynamical_decoupling.ipynb | 16 ++++++++------ examples/6_Customize_gate_calibrations.ipynb | 13 +++++++----- 6 files changed, 32 insertions(+), 22 deletions(-) diff --git a/examples/2_Expressing_classical_control_flow.ipynb b/examples/2_Expressing_classical_control_flow.ipynb index 2dc40384..d4c56940 100644 --- a/examples/2_Expressing_classical_control_flow.ipynb +++ b/examples/2_Expressing_classical_control_flow.ipynb @@ -131,7 +131,7 @@ " else:\n", " bell(3, 4)\n", "\n", - " return measure()\n" + " return measure()" ] }, { diff --git a/examples/3_1_Iterative_phase_estimation.ipynb b/examples/3_1_Iterative_phase_estimation.ipynb index b95a9141..57ea4034 100644 --- a/examples/3_1_Iterative_phase_estimation.ipynb +++ b/examples/3_1_Iterative_phase_estimation.ipynb @@ -86,6 +86,7 @@ "source": [ "n_iterations = 4\n", "\n", + "\n", "@aq.main(num_qubits=2)\n", "def ipe():\n", " \"\"\"Iterative phase estimation algorithm.\"\"\"\n", diff --git a/examples/3_2_magic_state_distillation.ipynb b/examples/3_2_magic_state_distillation.ipynb index 8482a8e3..0612f3e8 100644 --- a/examples/3_2_magic_state_distillation.ipynb +++ b/examples/3_2_magic_state_distillation.ipynb @@ -73,11 +73,12 @@ "@aq.subroutine\n", "def physical_magic_state_a_type(q: int):\n", " ins.h(q)\n", - " ins.rz(q, np.pi/6)\n", + " ins.rz(q, np.pi / 6)\n", + "\n", "\n", "@aq.subroutine\n", "def basis_rotation_pi6(q: int):\n", - " ins.rz(q, -np.pi/6)\n", + " ins.rz(q, -np.pi / 6)\n", " ins.h(q)" ] }, @@ -146,7 +147,7 @@ " new_probs[new_bitstring] += value\n", "\n", " total_shots = sum(new_probs.values())\n", - " return {k:v/total_shots for k,v in new_probs.items()}" + " return {k: v / total_shots for k, v in new_probs.items()}" ] }, { @@ -179,7 +180,7 @@ "print(\"measurement counts: \", counts)\n", "\n", "# Post-select the measurement outcome that measures \"0\" in ancilla\n", - "post_selected_counts = {k:v for k,v in counts.items() if k[1]==\"0\"}\n", + "post_selected_counts = {k: v for k, v in counts.items() if k[1] == \"0\"}\n", "\n", "# Compute the expectation value of Z observable on the data qubit\n", "marginal_probs = get_marginal_probs(post_selected_counts, [0])\n", @@ -213,8 +214,8 @@ "source": [ "@aq.subroutine\n", "def physical_magic_state_t_type(q: int):\n", - " ins.ry(q, np.arccos(1/np.sqrt(3)))\n", - " ins.rz(q, np.pi/4)" + " ins.ry(q, np.arccos(1 / np.sqrt(3)))\n", + " ins.rz(q, np.pi / 4)" ] }, { @@ -343,7 +344,7 @@ ], "source": [ "success_count = len([x for x in result.measurements[\"c\"] if x == \"0000\"])\n", - "print(\"success ratio: \", success_count/n_shots)" + "print(\"success ratio: \", success_count / n_shots)" ] }, { @@ -366,8 +367,8 @@ "source": [ "@aq.subroutine\n", "def basis_rotation_t_type(q: int):\n", - " ins.rz(q, -np.pi/4)\n", - " ins.ry(q, -np.arccos(1/np.sqrt(3)))" + " ins.rz(q, -np.pi / 4)\n", + " ins.ry(q, -np.arccos(1 / np.sqrt(3)))" ] }, { @@ -436,7 +437,7 @@ "source": [ "result = LocalSimulator().run(distillation_rus, shots=20).result()\n", "counts = Counter(result.measurements[\"c2\"])\n", - "probs = {str(k):v/sum(counts.values()) for k,v in counts.items()}\n", + "probs = {str(k): v / sum(counts.values()) for k, v in counts.items()}\n", "\n", "expval = probs.get(\"0\", 0) - probs.get(\"1\", 0)\n", "print(\"Z expectation value: \", expval)" diff --git a/examples/4_Native_programming.ipynb b/examples/4_Native_programming.ipynb index 44280e71..6cd72fb8 100644 --- a/examples/4_Native_programming.ipynb +++ b/examples/4_Native_programming.ipynb @@ -155,6 +155,7 @@ " cnot(\"$0\", \"$1\")\n", " return measure([\"$0\", \"$1\"])\n", "\n", + "\n", "try:\n", " bell_state.build(device=Devices.IonQ.Aria1)\n", "except Exception as e:\n", diff --git a/examples/5_Pulse_programming_and_dynamical_decoupling.ipynb b/examples/5_Pulse_programming_and_dynamical_decoupling.ipynb index a974fd38..f54203e9 100644 --- a/examples/5_Pulse_programming_and_dynamical_decoupling.ipynb +++ b/examples/5_Pulse_programming_and_dynamical_decoupling.ipynb @@ -83,6 +83,7 @@ "qubit = 0\n", "idle_duration = 10e-6\n", "\n", + "\n", "@aq.main\n", "def idle():\n", " control_frame = device.frames[f\"q{qubit}_rf_frame\"]\n", @@ -162,11 +163,13 @@ "source": [ "pi = np.pi\n", "\n", + "\n", "def x_pulse(qubit: int):\n", " # Pi pulse apply on X-axis of Bloch sphere\n", " qubit = f\"${qubit}\"\n", " rx(qubit, pi)\n", "\n", + "\n", "def y_pulse(qubit: int):\n", " # Pi pulse apply on Y-axis of Bloch sphere\n", " qubit = f\"${qubit}\"\n", @@ -198,22 +201,23 @@ "idle_duration = 10e-6\n", "n_cycles = 3\n", "\n", + "\n", "@aq.main\n", "def idle_with_dd():\n", - " dd_spacing = idle_duration / (4*n_cycles)\n", + " dd_spacing = idle_duration / (4 * n_cycles)\n", "\n", " control_frame = device.frames[f\"q{qubit}_rf_frame\"]\n", - " \n", + "\n", " pulse.delay(control_frame, dd_spacing)\n", " for _ in aq.range(n_cycles):\n", " x_pulse(qubit)\n", - " pulse.delay(control_frame, 2*dd_spacing)\n", + " pulse.delay(control_frame, 2 * dd_spacing)\n", " y_pulse(qubit)\n", - " pulse.delay(control_frame, 2*dd_spacing)\n", + " pulse.delay(control_frame, 2 * dd_spacing)\n", " x_pulse(qubit)\n", - " pulse.delay(control_frame, 2*dd_spacing)\n", + " pulse.delay(control_frame, 2 * dd_spacing)\n", " y_pulse(qubit)\n", - " \n", + "\n", " pulse.delay(control_frame, dd_spacing)" ] }, diff --git a/examples/6_Customize_gate_calibrations.ipynb b/examples/6_Customize_gate_calibrations.ipynb index 23de66e0..834ca56c 100644 --- a/examples/6_Customize_gate_calibrations.ipynb +++ b/examples/6_Customize_gate_calibrations.ipynb @@ -202,9 +202,10 @@ "wf = DragGaussianWaveform(\n", " 24e-9, 2.547965400864e-9, 2.370235498840002e-10, 0.293350447987059, False, \"wf_drag_gaussian_1\"\n", ")\n", - "q0_rf_frame = device.frames['q0_rf_frame']\n", + "q0_rf_frame = device.frames[\"q0_rf_frame\"]\n", "\n", - "@aq.gate_calibration(implements=rx, target=\"$0\", angle=np.pi/2)\n", + "\n", + "@aq.gate_calibration(implements=rx, target=\"$0\", angle=np.pi / 2)\n", "def my_rx_cal():\n", " pulse.barrier(\"$0\")\n", " pulse.shift_frequency(q0_rf_frame, -321047.14178613486 - 100)\n", @@ -242,10 +243,11 @@ "source": [ "@aq.main\n", "def my_program():\n", - " rx(\"$0\", np.pi/2)\n", + " rx(\"$0\", np.pi / 2)\n", " rz(\"$1\", 0.123)\n", " measure(\"$0\")\n", "\n", + "\n", "print(my_program.build().to_ir())" ] }, @@ -307,12 +309,13 @@ "metadata": {}, "outputs": [], "source": [ - "q1_rf_frame = device.frames['q1_rf_frame']\n", + "q1_rf_frame = device.frames[\"q1_rf_frame\"]\n", + "\n", "\n", "@aq.gate_calibration(implements=rz, target=\"$1\")\n", "def my_rz_cal(angle: float):\n", " pulse.barrier(\"$1\")\n", - " pulse.shift_frequency(q1_rf_frame, -1.0*angle)\n", + " pulse.shift_frequency(q1_rf_frame, -1.0 * angle)\n", " pulse.barrier(\"$1\")" ] }, From 32821c3271e36ec9ea4cca4a818200d608898ca9 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 7 May 2024 15:04:08 -0400 Subject: [PATCH 1164/1165] Enable sphinx build --- doc/Makefile | 20 ++++++++++++++++++ doc/conf.py | 40 ++++++++++++++++++++++++++++++++++++ doc/index.rst | 48 ++++++++++++++++++++++++++++++++++++++++++++ doc/requirements.txt | 3 +++ 4 files changed, 111 insertions(+) create mode 100644 doc/Makefile create mode 100644 doc/conf.py create mode 100644 doc/index.rst create mode 100644 doc/requirements.txt diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 00000000..20c83493 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = python -msphinx +SPHINXPROJ = autoqasm +SOURCEDIR = . +BUILDDIR = ../build/documentation + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 00000000..644a7616 --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,40 @@ +"""Sphinx configuration.""" + +import datetime +from importlib.metadata import version + +# Sphinx configuration below. +project = "autoqasm" +version = version(project) +release = version +copyright = f"{datetime.datetime.now().year}, Amazon.com" + +extensions = [ + "sphinxcontrib.apidoc", + "sphinx.ext.autodoc", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", + "sphinx.ext.todo", + "sphinx.ext.coverage", +] + +source_suffix = ".rst" +master_doc = "index" + +autoclass_content = "both" +autodoc_member_order = "bysource" +default_role = "py:obj" + +html_theme = "sphinx_rtd_theme" +htmlhelp_basename = f"{project}doc" + +language = "en" + +napoleon_use_rtype = False + +apidoc_module_dir = "../src/autoqasm" +apidoc_output_dir = "_apidoc" +apidoc_excluded_paths = ["../test"] +apidoc_separate_modules = True +apidoc_module_first = True +apidoc_extra_args = ["-f", "--implicit-namespaces", "-H", "API Reference"] diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 00000000..fb753ff8 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,48 @@ +######################## +AutoQASM +######################## + +**AutoQASM is not an officially supported AWS product.** + +This experimental module offers a new quantum-imperative programming experience embedded in Python +for developing quantum programs. AutoQASM is *experimental* software. We may change, remove, or +deprecate parts of the AutoQASM API without notice. The name AutoQASM is a working title and is +also subject to change. + +This documentation provides information about the AutoQASM library. The project +is hosted on GitHub at https://github.com/amazon-braket/autoqasm. The project +includes the source code, installation instructions, and other information. + + +*************** +Getting Started with AutoQASM +*************** + +For details on how to get started with AutoQASM, see the project +readme at https://github.com/amazon-braket/autoqasm/blob/main/README.md. + + +*************** +AutoQASM Decorators +*************** + +For details on usage of AutoQASM decorators such as `@aq.main`, see +the decorators documentation at https://github.com/amazon-braket/autoqasm/blob/main/doc/decorators.md + + +******** +Examples +******** + +There are several example notebooks available in the AutoQASM repo at +https://github.com/amazon-braket/autoqasm/tree/main/examples. + + +****************************** +AutoQASM API Documentation +****************************** + +.. toctree:: + :maxdepth: 2 + + _apidoc/modules diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 00000000..b80e6baf --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,3 @@ +sphinx>7 +sphinx-rtd-theme>=1.3.0 +sphinxcontrib-apidoc From acad08339e9a2df0d4dc8a8ed8d82f5c54d37111 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 7 May 2024 15:33:39 -0400 Subject: [PATCH 1165/1165] Update mcm-sim branch hashes and comments --- setup.py | 13 ++++++------- tox.ini | 6 ++---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/setup.py b/setup.py index 51167782..1c584af5 100644 --- a/setup.py +++ b/setup.py @@ -24,13 +24,12 @@ packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - # Pin the latest commit of feature/autoqasm branch of amazon-braket/amazon-braket-sdk-python.git # noqa E501 - "amazon-braket-sdk @ git+https://github.com/amazon-braket/amazon-braket-sdk-python.git@d62e288961af1b9a42b14667e1167fca301e2708#egg=amazon-braket-sdk", # noqa E501 - # Pin the latest commit of mcm-sim branch of amazon-braket/amazon-braket-default-simulator-python.git # noqa E501 - # to get the version of the simulator that supports the mcm=True argument for Monte Carlo - # simulation of mid-circuit measurement, which AutoQASM requires. - # NOTE: This is currently installed automatically due to feature/autoqasm requirements - # "amazon-braket-default-simulator @ git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git@ab068c860963c29842d7649c741f88da669597eb#egg=amazon-braket-default-simulator", # noqa E501 + # Pin the latest commit of mcm-sim branch of amazon-braket/amazon-braket-sdk-python.git + # and amazon-braket/amazon-braket-default-simulator-python.git to get the version of the + # simulator that supports the mcm=True argument for Monte Carlo simulation of mid-circuit + # measurement, which AutoQASM requires. + "amazon-braket-sdk @ git+https://github.com/amazon-braket/amazon-braket-sdk-python.git@ff73de68cf6ac2d0a921e8fe62693e5b9ae2e321#egg=amazon-braket-sdk", # noqa E501 + "amazon-braket-default-simulator @ git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git@ab068c860963c29842d7649c741f88da669597eb#egg=amazon-braket-default-simulator", # noqa E501 "oqpy~=0.3.5", "diastatic-malt", "numpy", diff --git a/tox.ini b/tox.ini index a1a041bb..4f451aeb 100644 --- a/tox.ini +++ b/tox.ini @@ -133,7 +133,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-sdk-python.git@d62e288961af1b9a42b14667e1167fca301e2708 # feature/autoqasm branch - # NOTE: This is currently installed automatically due to feature/autoqasm requirements - #git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git@ab068c860963c29842d7649c741f88da669597eb # mcm-sim branch + git+https://github.com/amazon-braket/amazon-braket-sdk-python.git@ff73de68cf6ac2d0a921e8fe62693e5b9ae2e321 # mcm-sim branch + git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git@ab068c860963c29842d7649c741f88da669597eb # mcm-sim branch